--- name: add-cli-tool description: Use when adding a userland CLI binary to bin/install.sh (jq, kubectl, helm, k9s, stern, yq, tofu, task, op, opencode, …). Covers the install_ function pattern, arch tokens, the .versions idempotency stamp, and when a tool does NOT belong in bin/. --- # Adding a CLI tool to `bin/` `bin/install.sh` installs self-contained release binaries into `~/.local/bin`: no sudo, nothing outside `$HOME`, so tools persist across Kasm redeploys. ## Does it belong here? `bin/` is for **single, statically linked binaries**. If the tool is a tree (interpreter + libraries), it does not fit `install_*_bin` and needs its own module that unpacks into `~/.local/opt/` and symlinks the CLIs — that is exactly why `ansible/` and `node/` exist. Prefer static builds: after a Kasm redeploy the root filesystem's shared libraries are gone, and a dynamically linked binary in `$HOME` will break. ## The pattern One `install_` function, pinned version, guard, install, stamp: ```sh # — one line on what it is. Note any asset-naming quirk. install_() { ver=v1.2.3 have_version "$ver" && { fsay " $ver already installed."; return 0; } install_tarball_bin \ "https://github.com///releases/download/$ver/_linux_$DEB.tar.gz" \ "" mark_version "$ver" } ``` Then call it at the bottom of the file with the others. **Keep `ver` byte-identical to the upstream tag.** Some tools tag `v1.2.3`, some `1.2.3` (jq uses a bare `1.8.2`). `have_version` does a string compare against the stamp in `~/.local/bin/.versions`, so a mismatch just means the tool silently reinstalls on every bootstrap. ## Arch tokens `bin/install.sh` precomputes both spellings — pick the one the upstream asset actually uses, don't call `uname` yourself: - `$ARCH` → `x86_64` / `aarch64` (rust-style release names) - `$DEB` → `amd64` / `arm64` (Go/Debian-style — most devops tools) ## Picking the helper - `install_raw_bin ` — the asset *is* the binary (jq, kubectl, yq). - `install_tarball_bin ` — `.tar.gz` (helm, k9s). - `install_zip_bin ` — `.zip` (the 1Password CLI). Asset names are inconsistent and this is where it usually goes wrong: k9s capitalizes `Linux`, helm nests under `linux-$DEB/`. Fetch the release page and read the real asset name rather than guessing — a 404 from `curl -fsSL` will abort bootstrap under `set -eu`. ## Conditional installs A tool that only works in some environments gets a gate, like kind — installed only when a **rootful** Docker daemon answers, because it does not work against rootless Docker on Kasm. Reuse `docker_full_available()` rather than writing a new probe. ## Finally Add the tool to the `bin/` bullet in `README.md`. Then run it twice and confirm the second run prints "already installed."