From 02a8ff08f9c4d6952a334320ec1553dd3e24d8ed Mon Sep 17 00:00:00 2001 From: Jev Date: Tue, 7 Apr 2026 22:51:42 +0200 Subject: [PATCH] update tooling --- helpers.py | 32 +++++++++++++++++++++++ snippets/fzf.sh | 1 + snippets/zoxide.sh | 1 + tasks.py | 64 ++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 helpers.py create mode 100644 snippets/fzf.sh create mode 100644 snippets/zoxide.sh diff --git a/helpers.py b/helpers.py new file mode 100644 index 0000000..aa24256 --- /dev/null +++ b/helpers.py @@ -0,0 +1,32 @@ +from pathlib import Path + +YELLOW = "\033[33m" +RESET = "\033[0m" + +BASHRC = "~/.bashrc" +SNIPPETS_DIR = Path(__file__).parent / "snippets" + + +def load_snippet(name: str) -> str: + """Read a shell snippet from the snippets directory.""" + return (SNIPPETS_DIR / f"{name}.sh").read_text() + + +def run(c, cmd: str) -> None: + """Print command in yellow then execute it with a pty.""" + print(f"{YELLOW}$ {cmd}{RESET}") + c.run(cmd, pty=True) + + +def append_bashrc_section(c, marker: str, content: str) -> None: + """Idempotently add a named section to ~/.bashrc, replacing it if it already exists.""" + import re + + begin = f"# BEGIN {marker}" + end = f"# END {marker}" + section = f"\n{begin}\n{content.strip()}\n{end}\n" + + bashrc = Path(BASHRC).expanduser() + text = bashrc.read_text() + text = re.sub(rf"\n?{re.escape(begin)}.*?{re.escape(end)}\n?", "", text, flags=re.DOTALL) + bashrc.write_text(text + section) diff --git a/snippets/fzf.sh b/snippets/fzf.sh new file mode 100644 index 0000000..0de0f4e --- /dev/null +++ b/snippets/fzf.sh @@ -0,0 +1 @@ +#export FZF_DEFAULT_OPTS='--bind ctrl-f:preview-page-down,ctrl-b:preview-page-up --preview "batcat --color=always {}"' diff --git a/snippets/zoxide.sh b/snippets/zoxide.sh new file mode 100644 index 0000000..10a7f20 --- /dev/null +++ b/snippets/zoxide.sh @@ -0,0 +1 @@ +eval "$(zoxide init bash)" diff --git a/tasks.py b/tasks.py index c357997..bba7f89 100644 --- a/tasks.py +++ b/tasks.py @@ -1,13 +1,73 @@ +import getpass + from invoke import task +from helpers import append_bashrc_section, load_snippet, run + +APT_PACKAGES = [ + "git", + "curl", + "micro", + "mc", + "detox", + "tree", +] + @task def install_claude(c): """Install Claude Code via the official install script.""" - c.run("curl -fsSL https://claude.ai/install.sh | bash", pty=True) + run(c, "curl -fsSL https://claude.ai/install.sh | bash") + @task def install_copilot(c): """Install GitHub Copilot via the official install script.""" - c.run("curl -fsSL https://gh.io/copilot-install | bash", pty=True) + run(c, "curl -fsSL https://gh.io/copilot-install | bash") + +@task +def install_apt_packages(c): + """Update apt cache and install base packages.""" + run(c, "sudo apt-get update") + run(c, f"sudo apt-get install -y {' '.join(APT_PACKAGES)}") + + +@task +def install_docker(c): + """Install Docker if not already installed and add current user to docker group.""" + run( + c, + "command -v docker >/dev/null 2>&1 || curl -fsSL https://get.docker.com | sudo sh", + ) + run(c, f"sudo usermod -aG docker {getpass.getuser()}") + + +@task +def install_uv(c): + """Install uv for the current user if not already installed.""" + run(c, "test -f ~/.local/bin/uv || curl -LsSf https://astral.sh/uv/install.sh | sh") + + +@task +def install_fzf(c): + """Install fzf from git, bat for preview, and configure .bashrc.""" + run(c, "sudo apt-get install -y bat") + run( + c, + "test -d ~/.fzf || git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf", + ) + run(c, "~/.fzf/install --all") + append_bashrc_section(c, "fzf", load_snippet("fzf")) + + +@task +def install_zoxide(c): + """Install zoxide, configure .bashrc, """ + run(c, "sudo apt install -y zoxide fzf") + append_bashrc_section(c, "zoxide", load_snippet("zoxide")) + + +@task(install_apt_packages, install_docker, install_uv, install_claude, install_fzf) +def bootstrap(c): + """Install all base tools: apt packages, Docker, uv, Claude Code, and fzf."""