--- name: add-bootstrap-module description: Use when adding a new top-level module to this dotfiles repo (a new directory with an install.sh that bootstrap.sh runs) — e.g. installing a new runtime, configuring a new service, or dropping a new managed rc file. Covers the module contract, ordering in bootstrap.sh, and the README entry. --- # Adding a bootstrap module A module is a top-level directory with an `install.sh` that bootstrap runs. It must be safe to run standalone *and* to re-run on an already-configured box. ## The contract Every module's `install.sh` satisfies all of these. Copy the shape from `node/install.sh` — it is the newest and cleanest example. 1. **POSIX `sh`, not bash.** `#!/usr/bin/env sh` + `set -eu`. No `[[`, no arrays, no `local`. These boxes are RHEL- *and* Debian-family; don't assume bash is the `/bin/sh`. 2. **A `WHY:` header comment.** Not what the script does — the code says that. Explain the failure it prevents or the constraint that forced the design. `node/install.sh` is the model: it explains that a missing `npx` surfaces only as a dead MCP server, which is easy to misdiagnose. 3. **Locate the repo without depending on `$PWD`:** ```sh SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" REPO_ROOT="$(dirname -- "$SCRIPT_DIR")" ``` 4. **Reuse `lib/`.** `lib/os-detect.sh` for `pkg_install` (handles dnf/yum vs apt); `lib/fetch.sh` for `fetch_arch`, `have_version`/`mark_version`, and the `install_*_bin` helpers. Source with a shellcheck directive: ```sh # shellcheck source=../lib/fetch.sh . "$REPO_ROOT/lib/fetch.sh" ``` 5. **Idempotent, and it verifies rather than assumes.** Check the tool *runs*, not just that the file exists — a Kasm redeploy can leave a present-but- broken tree behind. `node/` and `ansible/` both guard this way. 6. **Log to stderr** via a `say()` helper, so stdout stays usable. 7. **Prefer `$HOME`.** Everything under `~/.local` survives a Kasm redeploy (which wipes `/` but keeps `$HOME`) and needs no sudo. If the module *must* use sudo, say so in its header comment and in the README — the repo tracks which modules do. ## Wiring it up **`bootstrap.sh`** — add the call. Order is load-bearing and each existing out-of-order entry carries a comment explaining why (`docker/` before `bin/` so kind's gate sees the daemon; `docker-rootless-cgroup/` last because it needs the rootless daemon already up). If your module has an ordering constraint, add it *with* a comment; if it doesn't, append it near related modules. **`README.md`** — add a bullet to the `## Modules` list. Match the existing voice: what it installs, where it puts it, whether it uses sudo, and any host it no-ops on. **`~/.zshrc.local`** — if the module reads a secret or per-machine value, add the variable to `zsh/zshrc.local.example` and to the README's variable list. Never commit the value itself. ## Non-systemd hosts Kasm has no systemd. Anything that would `systemctl enable` must degrade gracefully — see `docker/install.sh`, which detects the situation and no-ops rather than failing the whole bootstrap run. ## Before you're done Run the `verify-module` skill. At minimum: `shellcheck`, run standalone, then run a second time and confirm the output says "already present" rather than redoing the work.