From fd88d8bba2250ea15eb4ecd6335101890eadeb38 Mon Sep 17 00:00:00 2001 From: Nic O'Connor Date: Wed, 8 Jul 2026 10:25:18 -0400 Subject: [PATCH] Add bin/ module: userland CLI tools into ~/.local/bin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New bin/install.sh downloads self-contained release binaries (ripgrep, fd, bat, fzf, jq) into ~/.local/bin — no sudo, nothing outside $HOME, so they persist across Kasm redeploys with no reinstall. Statically linked where available, so they keep working even after the root fs is wiped. Idempotent: pinned versions already present are skipped. lib/fetch.sh adds arch detection + tarball/raw binary download helpers. zsh/zshrc puts ~/.local/bin on PATH; bootstrap.sh runs the module. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 8 ++++- bin/install.sh | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++ bootstrap.sh | 1 + lib/fetch.sh | 65 +++++++++++++++++++++++++++++++++++++++ zsh/zshrc | 6 ++++ 5 files changed, 162 insertions(+), 1 deletion(-) create mode 100755 bin/install.sh create mode 100644 lib/fetch.sh diff --git a/README.md b/README.md index f2575e7..64b1c29 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,14 @@ Already have a clone? Just run it directly: - `git/` — sets git `user.name`/`user.email` and configures `credential.helper store` (plaintext `~/.git-credentials`, 0600) for HTTPS auth. +- `bin/` — installs userland CLI tools (ripgrep, fd, bat, fzf, jq) as + self-contained release binaries into `~/.local/bin`. No sudo, nothing + outside `$HOME`, so they persist across Kasm redeploys with no reinstall. + Idempotent: pinned versions already present are skipped. `zsh/zshrc` puts + `~/.local/bin` on `PATH`. - `mcp-config/` — shared MCP gateway config sync for laptops (LiteLLM / opencode). Independent of `bootstrap.sh`; see its own README. `lib/os-detect.sh` provides shared package-manager detection (`dnf`/`yum` -vs `apt`) used by module install scripts. +vs `apt`); `lib/fetch.sh` provides arch detection and binary-download +helpers. Both are used by module install scripts. diff --git a/bin/install.sh b/bin/install.sh new file mode 100755 index 0000000..70814b5 --- /dev/null +++ b/bin/install.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env sh +# bin/install.sh — install userland CLI tools into ~/.local/bin. +# +# Every tool is a self-contained release binary downloaded from upstream and +# dropped into ~/.local/bin (no sudo, nothing outside $HOME), so it persists +# across Kasm redeploys. Idempotent: each tool is skipped when the pinned +# version is already installed. To add a tool, pin a version, add an +# install_ function, and call it at the bottom. +set -eu + +SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +REPO_ROOT="$(dirname -- "$SCRIPT_DIR")" + +# shellcheck source=../lib/fetch.sh +. "$REPO_ROOT/lib/fetch.sh" + +ARCH="$(fetch_arch)" + +# ripgrep — fast recursive search. x86_64 ships musl (static); aarch64 gnu. +install_ripgrep() { + ver=15.1.0 + have_version rg "$ver" && { fsay "ripgrep $ver already installed."; return 0; } + case "$ARCH" in + x86_64) triple=x86_64-unknown-linux-musl ;; + aarch64) triple=aarch64-unknown-linux-gnu ;; + esac + install_tarball_bin \ + "https://github.com/BurntSushi/ripgrep/releases/download/$ver/ripgrep-$ver-$triple.tar.gz" \ + "ripgrep-$ver-$triple/rg" rg +} + +# fd — fast, user-friendly find. musl (static) on both arches. +install_fd() { + ver=10.4.2 + have_version fd "$ver" && { fsay "fd $ver already installed."; return 0; } + triple="$ARCH-unknown-linux-musl" + install_tarball_bin \ + "https://github.com/sharkdp/fd/releases/download/v$ver/fd-v$ver-$triple.tar.gz" \ + "fd-v$ver-$triple/fd" fd +} + +# bat — cat with syntax highlighting. musl (static) on both arches. +install_bat() { + ver=0.26.1 + have_version bat "$ver" && { fsay "bat $ver already installed."; return 0; } + triple="$ARCH-unknown-linux-musl" + install_tarball_bin \ + "https://github.com/sharkdp/bat/releases/download/v$ver/bat-v$ver-$triple.tar.gz" \ + "bat-v$ver-$triple/bat" bat +} + +# fzf — general-purpose fuzzy finder (static Go binary). +install_fzf() { + ver=0.74.0 + have_version fzf "$ver" && { fsay "fzf $ver already installed."; return 0; } + case "$ARCH" in + x86_64) a=amd64 ;; + aarch64) a=arm64 ;; + esac + install_tarball_bin \ + "https://github.com/junegunn/fzf/releases/download/v$ver/fzf-$ver-linux_$a.tar.gz" \ + fzf fzf +} + +# jq — command-line JSON processor (single static binary). +install_jq() { + ver=1.8.2 + have_version jq "$ver" && { fsay "jq $ver already installed."; return 0; } + case "$ARCH" in + x86_64) a=amd64 ;; + aarch64) a=arm64 ;; + esac + install_raw_bin \ + "https://github.com/jqlang/jq/releases/download/jq-$ver/jq-linux-$a" jq +} + +install_ripgrep +install_fd +install_bat +install_fzf +install_jq + +fsay "Done. Tools in $BIN_DIR (added to PATH by zsh/zshrc)." diff --git a/bootstrap.sh b/bootstrap.sh index 182ec8a..e1f3670 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -31,3 +31,4 @@ fi "$SCRIPT_DIR/zsh/install.sh" "$SCRIPT_DIR/git/install.sh" +"$SCRIPT_DIR/bin/install.sh" diff --git a/lib/fetch.sh b/lib/fetch.sh new file mode 100644 index 0000000..6139443 --- /dev/null +++ b/lib/fetch.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env sh +# lib/fetch.sh — helpers for installing userland binaries into ~/.local/bin. +# +# Everything lands under $HOME, so installed tools survive Kasm redeploys +# (which wipe the root filesystem but keep $HOME) with no reinstall needed. +# No sudo, no system packages. Prefer statically linked (musl) release +# binaries so they keep working even after the root fs — and its shared +# libraries — is reset. +# +# Source this (callers already run `set -eu`), then use fetch_arch, +# have_version, install_tarball_bin, and install_raw_bin. + +BIN_DIR="${BIN_DIR:-$HOME/.local/bin}" + +fsay() { printf '%s\n' "$*" >&2; } + +# Normalize `uname -m` to the canonical arch tokens used below. +fetch_arch() { + case "$(uname -m)" in + x86_64|amd64) printf 'x86_64\n' ;; + aarch64|arm64) printf 'aarch64\n' ;; + *) fsay "fetch: unsupported arch $(uname -m)"; return 1 ;; + esac +} + +# Download to using whatever downloader is available. +_fetch_dl() { + if command -v curl >/dev/null 2>&1; then + curl -fsSL "$1" -o "$2" + elif command -v wget >/dev/null 2>&1; then + wget -qO "$2" "$1" + else + fsay "fetch: need curl or wget"; return 1 + fi +} + +# have_version — true if ~/.local/bin/ exists +# and reports the given version, so bootstrap can skip a re-download. +have_version() { + _bin="$BIN_DIR/$1" + [ -x "$_bin" ] || return 1 + "$_bin" --version 2>/dev/null | grep -qF "$2" +} + +# install_tarball_bin — download a +# .tar.gz, extract, and install one binary from it into ~/.local/bin. +install_tarball_bin() { + mkdir -p "$BIN_DIR" + _tmp="$(mktemp -d)" + _fetch_dl "$1" "$_tmp/archive.tar.gz" + tar -xzf "$_tmp/archive.tar.gz" -C "$_tmp" + install -m 0755 "$_tmp/$2" "$BIN_DIR/$3" + rm -rf "$_tmp" + fsay "installed $BIN_DIR/$3" +} + +# install_raw_bin — download a single raw binary. +install_raw_bin() { + mkdir -p "$BIN_DIR" + _tmp="$(mktemp)" + _fetch_dl "$1" "$_tmp" + install -m 0755 "$_tmp" "$BIN_DIR/$2" + rm -f "$_tmp" + fsay "installed $BIN_DIR/$2" +} diff --git a/zsh/zshrc b/zsh/zshrc index f74d8b6..3c5ab6a 100644 --- a/zsh/zshrc +++ b/zsh/zshrc @@ -2,6 +2,12 @@ # overwritten on the next bootstrap run. Edit zsh/zshrc in the dotfiles repo # instead. +# Userland tools installed by dotfiles/bin/install.sh live here. +case ":$PATH:" in + *":$HOME/.local/bin:"*) ;; + *) export PATH="$HOME/.local/bin:$PATH" ;; +esac + export ZSH="$HOME/.oh-my-zsh" ZSH_THEME="robbyrussell"