Skip to content

Migrate dotbot → chezmoi + mise-as-primary-installer#21

Merged
vaintrub merged 18 commits into
masterfrom
chore/chezmoi-migration
May 17, 2026
Merged

Migrate dotbot → chezmoi + mise-as-primary-installer#21
vaintrub merged 18 commits into
masterfrom
chore/chezmoi-migration

Conversation

@vaintrub
Copy link
Copy Markdown
Owner

@vaintrub vaintrub commented May 16, 2026

Summary

Replaces dotbot with chezmoi as the dotfile manager and consolidates all dev-tool installs under mise (cross-platform, declarative, single source of truth). Adds a 3-tier install profile system (core / dev / workstation) with always-prompt + detected-env hint. Introduces bats-tested install library, 1Password-aware GitHub token cascade, and CI coverage for both profile=core and profile=dev.

Net: −881 LOC removed (dotbot config, vendored helpers, bash installer), +3482 LOC added (chezmoi templates, mise.toml, lib + tests, docs).

Architecture

Layer Source-of-truth file Mechanism
Dotfile manager chezmoi (XDG default source ~/.local/share/chezmoi/) replaces dotbot
Dev tools (cross-platform) dot_config/mise/config.toml.tmpl mise aqua + github + direct-URL backends
OS-native packages .chezmoidata/packages.yaml brew bundle (Mac) / apt / dnf (Linux)
Install profiles .chezmoi.toml.tmpl promptChoiceOnce w/ detected-env hint, default core
Composite OS key .osid (derived once in .chezmoi.toml.tmpl) darwin / linux-<id>
Install entry point lib/install-packages.sh + run_onchange_after_50-… wrapper thin-wrapper + fat-library; bats-testable

Profile tiers (cascade)

  • core — dotfile baseline only (zsh, vim, tmux, git, curl, ca-certs + fzf/zoxide for zshrc bindings). ~50 MB. Use case: VPS, container, jetson via SSH, recovery.
  • dev — core + 4 language toolchains (Go/Python/Node/Rust) + 29 mise binaries (kubectl/helm/jq/gh/…/op/rtk/claude-code/codex) + OS-native dev brews. Post-installs: goimports, ssh-audit, rtk init for Claude+Codex hooks. ~1.9 GB. Use case: headless dev box.
  • workstation — dev + GUI apps (Mac casks: iTerm2, Docker Desktop, VSCode, ngrok, 1Password desktop, fonts). Use case: primary GUI machine.

What lives where

  • AI CLIs (claude-code, codex) — mise aqua backend (native binaries, no Node coupling).
  • op (1Password CLI) — mise direct-URL backend (cross-platform; replaces Mac-only brew install + no-Linux gap).
  • rtk — mise github backend (cross-platform; replaces Mac-only brew formula + Linux curl-pipe). rtk init runs as a post-install function in lib/install-packages.sh.
  • Plugins (Claude+Codex) — declared in packages.yaml, installed by run_onchange_after_70-install-plugins. caveman bundled.

GitHub token cascade (avoids mise's 60/hr anonymous API limit)

In the install-packages wrapper: op readgh auth token → anonymous. Template-time {{ lookPath "op" }} / {{ lookPath "gh" }} guards keep the rendered script lean on machines without either tool. Token only in process env, never on disk.

Tests

  • tests/unit/install-packages.bats — 31 unit tests with mocked externals (apt-get / sudo / mise / id / rtk / brew). Covers: profile predicates, debian/fedora dispatch, mise install + missing-tool warning, post-install funcs (goimports/ssh-audit/rtk-init), main() routing per OS+profile.
  • tests/files/common.bats — profile-agnostic post-apply asserts (dotfiles present, mise+fzf+zoxide on PATH, core apt packages installed).
  • tests/files/core.bats — core-only asserts (no dev tools, no rtk).
  • tests/files/dev.bats — dev-only asserts (mise tools, post-installs, AI CLIs). Includes --version execute-checks for claude/codex/op/rtk/gh/delta to catch broken-binary cases (corrupt aqua download, glibc mismatch).
  • CI matrix: unitapply-core + apply-dev (parallel). actions/cache@v4 for ~/.local/share/mise/installs keyed by mise config hash.

Test plan

  • bats tests/unit/install-packages.bats — 31 pass + 1 skip (env-dependent)
  • Local chezmoi apply on Mac (workstation) — idempotent, diff empty after two-apply convergence
  • CI unit job — pass
  • CI apply-core job — pass (clean Ubuntu sandbox, profile=core)
  • CI apply-dev job — pass (clean Ubuntu sandbox, profile=dev, full toolchain install, all bats asserts including --version execute-checks)
  • Jetson re-apply: chezmoi update && chezmoi apply — picks up rtk via mise (glibc skip on jammy), op cascade for token, plugins re-register
  • Fresh Mac install dry-run via chezmoi diff after one-line bootstrap

Out of scope / accepted

  • iTerm2 plist driftiterm/com.googlecode.iterm2.plist is the live binary plist read+written by iTerm2 via PrefsCustomFolder; drift is incidental, committed only when intentional config changes happen.
  • brew bundle cleanup — deferred (dry-run output is 100+ lines of false positives from cask transitive deps; needs separate design).
  • post-install-hint trim — UX call, not urgent.
  • node@lts orphan after AI CLIs moved to aqua — kept for ad-hoc project-level npm work.
  • Two-apply convergence — inherent to chezmoi's modify_ + lookPath interaction (settings.json regenerates against rtk-init-modified file; dot_gitconfig.tmpl lookPath "delta" flips truthy after pass 1). Documented in CI workflow.

vaintrub added 16 commits May 16, 2026 22:54
Three resilience fixes surfaced by a fresh dev-profile install on a
jetson where a leftover Docker engine, a leftover npm-installed Claude
binary, and the GitHub anonymous API rate limit all interacted badly.

1. Drop `docker.io` / `docker` from the dev tier.
   Docker is too distro-specific to auto-install — `docker.io` from the
   Ubuntu repo and `docker-ce` from Docker's official apt repo can't
   coexist, and a pre-existing `docker-ce` install makes `apt-get install
   docker.io` fail with a hard conflict (containerd.io vs containerd,
   docker-ce vs docker.io). The error cascade prevents ALL remaining
   apt installs from succeeding in the same invocation. Users now pick
   the Docker flavour deliberately:
     `sudo apt install docker.io`              # simple, Ubuntu repo
     Docker's official apt repo for docker-ce   # production, newer
     `workstation` profile on Mac               # Docker Desktop cask

2. npm install -g --force on AI globals.
   Pre-existing `~/.local/bin/claude` (left over from a prior dotbot
   install or manual one-shot npm install) makes `npm install -g
   @anthropic-ai/claude-code` fail with EEXIST. The --force flag
   tells npm to silently overwrite. Idempotent on clean systems.
   npm 7+ accepts the flag; mise's bundled Node ≥18 ships npm 9+.

3. GITHUB_TOKEN auto-detect from `gh auth token` in the install-packages
   wrapper, before sourcing lib/install-packages.sh. mise's aqua backend
   hits `api.github.com` once per tool — the 25-tool dev tier generates
   30-50 GH API calls on fresh install. Anonymous limit is 60/hr; easy
   to exhaust on a slow link / shared IP / multiple re-runs. First apply
   on a fresh machine: gh not yet installed → block no-ops silently.
   Subsequent applies: gh present on PATH (mise shims) → cached token
   from `gh auth login` is exported. Existing `$GITHUB_TOKEN` env (CI
   scenario) is respected via `-z "${GITHUB_TOKEN:-}"` guard.

README updated:
  - Profile table: docker.io removed from dev/Linux row + explanatory
    paragraph below.
  - First-apply latency section: GitHub API rate-limit subsection with
    two unblock paths (export GITHUB_TOKEN or `gh auth login`).

bats unit tests untouched — mocked DOTFILES_DEV_APT lists don't
reference docker.io. CI runs core profile which never touched docker.io
anyway. No test regression.
Replaces PR-#21's gh-only token auto-detect with a 3-step cascade:
1Password → gh CLI → anonymous. Native chezmoi `onepasswordRead` template
function was considered and rejected: it bakes the secret as a literal
string into the rendered .sh under $TMPDIR (single-exec but on-disk
window between render and exec). Shell-runtime `op read` keeps the
token in process env only; the rendered script contains the REFERENCE
path (op://Personal/GitHub API Token/credential) not the credential.

Template-time `{{ lookPath "op" }}` and `{{ lookPath "gh" }}` guards keep
the rendered .sh lean — machines without `op` get only the gh path;
machines without either get just the empty-token fall-through.

The `op read` call uses `--no-newline` (supported since 1Password CLI
2.0) to avoid a trailing \n that would corrupt
`Authorization: Bearer <tok>` headers.

Item path convention: `op://Personal/GitHub API Token/credential`
(twpayne's convention). Override via `$DOTFILES_OP_GITHUB_REF` env for
custom vault layouts. Item created once by the user; `op read` finds
it on every apply.

Packages.yaml:
- `1password-cli` added to `packages.dev.brews` — Mac CLI binary, ~5 MB.
  After install-packages runs once, the next apply renders the wrapper
  with the op block included (lookPath sees the CLI on PATH).
- `1password` cask added to `packages.gui.mac_casks` — desktop app
  enables Touch ID biometric integration with the `op` CLI (no
  `op signin` needed per call).
- Linux: no auto-install (1Password's apt repo needs signed-key setup;
  jetson/VPS headless boxes don't have biometric). Users opt in via
  https://1password.com/downloads/command-line.

References: twpayne uses native `onepasswordRead` for long-lived secrets
baked into target files (SSH key, dot_zshrc env vars) but ZERO usage in
.chezmoiscripts/ — install-time tokens are exported via shell env.
Pattern lifted from that precedent.
Adds a second post-apply job `apply-dev` to the CI workflow that runs
alongside `apply-core`. Catches dev-tier regressions before they hit
jetson / VPS — the three bugs PR #21 fixed (docker.io conflict, npm
EEXIST, GH rate-limit) all surfaced only at dev tier and would have
been caught here.

Cost: ~45 sec per push on warm mise-installs cache (5 GB/repo quota,
~1.5 GB used), ~5 min on cold (after mise.toml.tmpl changes). Public
repo → unlimited free Actions minutes, zero block risk. apply-core
+ apply-dev run in parallel under `needs: unit`.

Mechanics:
- `actions/cache@v4` keyed by hash of dot_config/mise/config.toml.tmpl:
  cache busts cleanly when tools are added/removed from the manifest.
- `GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}` lifts mise's 60/hr
  anonymous limit to 1000/hr (auto-provided by GitHub Actions).
- Pre-create ~/.config/chezmoi/chezmoi.toml with profile=dev so
  promptChoiceOnce skips the prompt (would otherwise hang on no-TTY).
- Symlink $GITHUB_WORKSPACE → ~/.local/share/chezmoi/ so every
  chezmoi invocation finds the source without --source flag.

`tests/files/dev.bats` covers (40 asserts):
- Profile sanity (profile=dev in chezmoi.toml)
- Languages on PATH: node, go, python, rustc
- CLI utilities (mise aqua): jq, gh, delta, shellcheck, fd, yq, gitleaks
- Cloud + K8s: kubectl, helm, k9s, kustomize, stern, argocd, tofu,
  aws, rclone, cloudflared
- Networking + Go ecosystem + pkg managers: websocat, buf,
  golangci-lint, goreleaser, gotestsum, protoc-gen-go, pnpm, uv
- Post-installs: goimports, ssh-audit
- AI CLIs: claude (--force overwrite), codex
- No (missing) mise tools (asserts full install landed)
- chezmoi diff empty post-apply (idempotency, with diagnostic output
  on failure)
- Apt dev packages: htop, tree, wget, nmap

References:
- Mise CI caching: https://mise.jdx.dev/continuous-integration.html
- shunk031/dotfiles testable-dotfiles pattern (multi-tier bats split).
Three coordinated improvements after jetson deploy surfaced silent
failures:

F3 — `mise_install_tools()` partial-failure summary
  After `mise install --yes`, count `(missing)` rows in `mise ls` and
  emit a WARNING block with concrete recovery steps (gh auth login OR
  1Password item path OR `chezmoi apply` retry). On jetson, 11 of 25
  aqua tools failed silently due to 60/hr anonymous GH API limit —
  install-packages script exited 0, user found `aws: not found` only
  later. Now the WARNING fires explicitly + lists the fix paths.

  Bats unit tests added:
  - Warning fires when mise ls returns any `(missing)` line, contains
    "WARNING", "gh auth login", "op://Personal/GitHub API Token".
  - No warning when mise ls is clean.

F4 — `.chezmoiscripts/run_once_after_99-post-install-hint.sh.tmpl`
  One-time hint printed at the end of first apply (numerical prefix 99
  = runs last among run_once_after_* scripts; chezmoi state bucket
  tracks single execution). Tells user:
  - `exec zsh` to reload current shell with mise shims on PATH (the
    "aws: command not found" gotcha — shell predates the apply).
  - `mise ls` to inspect installed state.
  - `gh auth login` / `op item create` if any tools missing.
  - `~/.config/mise/config.local.toml` for per-machine tool skip.
  - `chezmoi state delete-bucket --bucket=scriptState` to re-trigger.

F6 — README new sections
  - First-time bootstrap checklist (numbered, includes pre-auth step).
  - Recovery (broken first apply) — rate-limit, reshim, state-bucket
    reset, profile re-prompt, Touch ID re-trigger.
  - Per-machine override (~/.config/mise/config.local.toml).
  - 1Password integration (item path convention, override env var,
    biometric semantics, opt-in nature, fallback to gh/anon).
apply-dev job's first CI run failed 5 of 33 common.bats asserts because
common.bats was actually CORE-tier-specific in disguise: 'profile is
core', 'no rtk binary', 'no claude CLI', 'no dev tools', etc. all
break under profile=dev.

Refactor:
- tests/files/common.bats — keep only PROFILE-AGNOSTIC asserts (dotfile
  presence, mise + fzf + zoxide on PATH, core apt packages, no stale
  fnm/pyenv dirs). Runs from both apply-core AND apply-dev.
- tests/files/core.bats — NEW. Core-tier-only asserts (profile=core,
  no rtk, no claude/codex, no dev tools in mise config).
- tests/files/dev.bats — unchanged. Dev-tier-only asserts.

CI workflow:
- apply-core: bats common.bats + bats core.bats
- apply-dev:  bats common.bats + bats dev.bats

Plus: two-apply convergence pattern in both apply jobs.
First apply installs everything (mise tools, rtk, claude/codex via
npm). Second apply lets the modify_settings.json template re-render
against the rtk-init-modified settings.json (sprig mergeOverwrite +
toPrettyJson outputs alphabetised JSON; rtk init writes in insertion
order; without a second apply the alphabetisation drift surfaces in
'chezmoi diff'). Same fix lets the {{ lookPath "delta" }} block in
dot_gitconfig.tmpl find delta on PATH (mise installed it in pass 1).

CI run 25975576899 cause: first apply state had:
- 0600 mode on ~/.claude/settings.json (rtk init's write) vs 0644
  expected (no private_ prefix on modify_settings.json)
- key order drift between rtk-init's output and modify_'s alphabetised
  output

Second apply re-runs modify_settings.json against rtk's output, mode
goes back to 0644, content gets alphabetised — chezmoi diff empty.
After two-apply convergence pattern was added, apply-dev still showed
.gitconfig diff with `pager = delta` + [delta] + [interactive] blocks
appearing as "would-add" — meaning the 2nd `chezmoi apply` rendered
dot_gitconfig.tmpl with `{{ lookPath "delta" }}` still FALSE.

Cause: workflow only added ~/.local/bin to GITHUB_PATH after installing
chezmoi. mise's tool binaries live at ~/.local/share/mise/installs/...
exposed via shims at ~/.local/share/mise/shims/<tool>. That shims dir
was never added to PATH, so chezmoi's template-time `lookPath "delta"`
couldn't find delta even after mise installed it.

Fix: also append ~/.local/share/mise/shims to GITHUB_PATH right after
chezmoi install. The dir doesn't exist until apply 1 populates it,
but that's fine — PATH lookup just fails until then; apply 2 sees
the populated shims and renders delta blocks correctly.

Applied to both apply-core and apply-dev jobs (template-replace-all).
Apply-core was passing only by coincidence — delta isn't in mise.toml
at core tier, so `lookPath "delta"` was false in both apply passes
(consistent → no drift). The fix is still needed for apply-core for
any future cross-profile lookPath-dependent block.
…plate hash)

Bats captured 'chezmoi: warning: config file template has changed, run
chezmoi init to regenerate config file' as drift on apply-dev's
'chezmoi diff is empty' assertion. Pre-seeded chezmoi.toml never got
its template-hash cached in chezmoi's configState bucket — every apply
emitted the warning.

Replace the manual seed block with `chezmoi init --apply --promptDefaults`.
init reads the template, renders chezmoi.toml, AND caches the hash.
With no CODESPACES env set on the apply-dev job, the template's
$defaultProfile auto-detection picks 'dev' on Linux (matches the
existing apply-core flow which sets CODESPACES=true → picks 'core').

apply-core was already using this pattern; apply-dev now mirrors it.
…uit)

apply-dev's previous failure was 'profile is core' instead of 'profile
is dev'. Cause: --promptDefaults makes promptChoiceOnce return the
LITERAL default arg ("core") regardless of the template's computed
\$defaultProfile auto-detect. So on CI it picked core.

Fix: pre-seed ~/.config/chezmoi/chezmoi.toml with profile=dev (+ name
+ email + osid) BEFORE running chezmoi init. promptChoiceOnce's
hasKey check finds the cached value and short-circuits — no prompt,
no --promptDefaults fallthrough. The subsequent chezmoi init still
runs (re-renders chezmoi.toml with same content + caches the
template hash in configState) so the second apply doesn't emit the
'config file template has changed' warning.

This combines the two patterns we tried separately:
- Seed (for profile selection)
- Init (for template hash caching)
Move both binaries off OS package managers onto mise:
- op (1Password CLI): direct-URL via unqualified 'op' shortname in mise
  registry. NB: aqua:1password/cli ALSO resolves but its aqua entry is
  missing repo_owner/repo_name fields, so mise install errors out. The
  shortname route uses mise's direct-URL backend (CDN, not GitHub releases),
  which works on Mac + Linux arm64/amd64.
- rtk: github:rtk-ai/rtk backend. Same artefact as previous brew formula
  (Mac) + curl-pipe (Linux). Single source of truth.

Drop rtk from packages.dev.brews; install-rtk.sh.tmpl simplified to just
the rtk-init step (binary install now handled upstream by mise_install_tools
in install-packages). glibc-too-old graceful-skip kept for Ubuntu 22.04.

1password desktop cask kept in gui.mac_casks (biometric provider for op
CLI, separate concern from the binary).
Stale leftover from earlier "move op to gui.mac_casks" decision that was
superseded by the mise migration. Workstation tier was reinstalling the
cask every apply, shadowing mise's `op` install on Mac via brew's PATH
precedence (/opt/homebrew/bin before mise install dirs in non-mise-activated
shells).

Also fix dev.brews comment that mis-named the mise registry entry as
`aqua:1password/cli` — the actual key is the unqualified `op` shortname
(direct-URL backend), since the aqua entry is broken (missing
repo_owner/repo_name fields).

Keeps 1password desktop cask (separate concern — biometric provider for op).
S3 — Move rtk init logic from `.chezmoiscripts/run_onchange_after_60-
install-rtk.sh.tmpl` into `lib/install-packages.sh::post_install_rtk_init()`.
Co-located with the other post-install funcs (goimports, ssh-audit,
npm-globals). Called from main() at dev tier+ after npm globals. Same
effective order as before (60 ran lexicographically after 50 anyway).

  - Standalone script: 69 LOC .tmpl → deleted.
  - Lib function: 38 LOC, mockable in bats.
  - 5 new unit tests cover: rtk missing, --version fails (glibc too old),
    claude-only path, codex-only path, neither-present (banner-only).

Hash-bucket granularity loss accepted: rtk init changes now bust install-
packages script hash (full re-run) — but every step is idempotent (brew
bundle, apt, mise install) so re-run cost is negligible.

S4 — Drop `linux_fd_symlink_fallback()`. Was inactive in every code path:
  - Mac: returned early on `DOTFILES_OS != linux`.
  - Linux dev: mise installs aqua:sharkdp/fd before the fallback runs;
    the `! command -v fd` guard always false → no-op.
  - Linux core: fd is not promised at core tier (zshrc bindings use fzf+
    zoxide only).

Net: -82 LOC across script + lib, +85 LOC tests.
Drops the npm-global install path for the AI CLIs in favour of mise's
aqua backend. Anthropic ships claude-code as a standalone bun-bundled
binary since v2.1.x (via GCS bucket; aqua-registry proxies the URL);
OpenAI codex is native rust under `rust-` prefixed tags. No Node
middleman, no `--force` workaround for dotbot-era leftovers, mirrors
the op+rtk unification pattern.

Auto-update concern researched in advance: per Anthropic docs
(https://code.claude.com/docs/en/setup) package-manager installs do
not auto-update by default, so mise pin stays in sync.

Changes:
- dot_config/mise/config.toml.tmpl: add `aqua:anthropics/claude-code`
  + `aqua:openai/codex` to the dev block, with fallback note pointing
  at `npm:` shortname if aqua entries ever break (1password-cli
  precedent).
- packages.yaml: drop `dev.npm_global` — list no longer consumed.
- run_onchange_after_50-install-packages.sh.tmpl: drop
  `DOTFILES_DEV_NPM_GLOBAL` env export.
- lib/install-packages.sh: delete `_resolve_npm` + `npm_install_ai_globals`
  (~42 LOC) and the npm call from `main()`. Header doc updated.
- tests/unit/install-packages.bats: drop NPM_CALLED / DOTFILES_DEV_NPM_GLOBAL
  assertions from main-dispatcher tests.

Net: -22 LOC across script+lib+tests.

Verified locally on Mac (workstation profile):
- `mise ls` shows aqua:anthropics/claude-code 2.1.143 + aqua:openai/codex 0.130.0
- After `rm ~/.local/share/mise/installs/node/lts/bin/{claude,codex}`
  (one-off cleanup), `which claude` resolves to aqua install path.
- `claude --version` and `codex --version` work.
- rtk hook still wires correctly (uses PATH-based "rtk hook claude",
  not absolute binary path).

Fallback if aqua-registry stales an entry: 1-line swap to
`npm:@anthropic-ai/claude-code` / `npm:@openai/codex`.
C1 — `.chezmoi.toml.tmpl` profile-tier comment block was describing the
state before op/rtk/claude/codex moved to mise. Rewrite to enumerate the
current mise registry layout (aqua + direct-URL + github backends) and
drop the stale "AI npm globals" reference.

C2 — `run_onchange_after_70-install-plugins.sh.tmpl` had a comment
"Same as script 60: ..." but script 60 was merged into the lib in commit
2d6ac28. Replace with bare description of the brew_shellenv guard.

N3 — `tests/files/dev.bats` added six `--version` execute-checks for
claude, codex, op, gh, delta, rtk. The existing `command -v X` tests
only verified PATH discovery — they didn't catch broken-binary cases
(corrupt aqua download, glibc mismatch, upstream naming-scheme change).
rtk has `|| skip` to handle glibc < 2.39 gracefully on older runners.

No behaviour change in apply path; tests strengthened.
Documentation had drifted behind four recent install-path migrations:
- op (1Password CLI) now via mise direct-URL backend (was Mac brew only)
- rtk now via mise github backend (was Mac brew + Linux curl-pipe)
- claude-code + codex now via mise aqua backend (was npm-global through
  mise's node)
- run_onchange_after_60-install-rtk script merged into lib (was standalone)

README.md changes:
- Tool count "25 aqua tools" → "27 mise tools" (added op, rtk, claude-code,
  codex)
- Quick-start install paragraph: drop rtk from brews, drop "npm install -g"
  for AI CLIs, mention mise's aqua/github/direct-URL backends
- Drop bullet about 60-install-rtk (script no longer exists)
- Drop "Default Linux profile is dev (auto-picked)" — chezmoi init now
  ALWAYS prompts (with detected-env hint), no silent auto-pick
- Update profile table dev row: list current mise tools and OS-native
  brew/apt sets; remove rtk from Mac brew row
- §"1Password integration": describe mise-installed `op` (cross-platform)
  instead of Mac-only brew install
- §"Tools managed by mise" table: add Auth+secrets, Token-saving proxy,
  AI CLIs sections; remove `claude-code`/`codex` from Post-install row;
  add gitleaks
- §"OS-native packages" table: rename "mac" row → "workstation"; drop rtk
  from Mac brew dev row; add 1password desktop to casks
- §"rtk auto-refresh on version change" → "rtk install + init": rewrite
  to describe mise-managed binary + post_install_rtk_init lib function;
  drop the obsolete trailing-hash-comment trick (was a workaround for
  the deleted standalone script)
- §"Cross-platform via templates": script prefix list "50-, 60-, 70-, 80-"
  → "50-, 70-, 99-" (60 merged, 80 doesn't exist)
- §"Soft hardening": drop "Pin rtk install URL" item (moot — rtk now from
  mise github backend, version tracking via mise registry)

AGENTS.md changes:
- §"Bootstrap scripts": run_onchange_after_* section's "rtk install" →
  reference to the lib's post_install_rtk_init function (binary install
  is mise's job; only the init step lives in a chezmoi script via lib)
The translation was introduced when the install profile tiers were
renamed from core/dev/mac to core/dev/workstation, so existing machines
with profile="mac" cached in chezmoi.toml would silently migrate on
next init without manual cleanup.

All known machines have now applied with the new tier names — the
translation block is dead code. Drop the legacy block + the matching
"Migrating from older data shapes" paragraph in README.md.

If a machine with stale profile="mac" ever turns up: chezmoi will error
at init (value not in {core, dev, workstation}); fix is one line edit
to ~/.config/chezmoi/chezmoi.toml setting profile="workstation".
Verification pass after the previous docs-sync commit caught two issues:

1. Tool count was "27 mise tools" in three places (README §intro, §profile
   dev row, §first-apply-latency). Actual mise.toml dev block now declares
   29 binaries (aqua + github + direct-URL backends) plus 4 language
   toolchains. The "27" was a stale carry-over from before claude+codex
   were added. Replaced with "4 language toolchains + 29 binaries via
   mise" so the prose matches the rendered config.

2. Script-prefix list "50/60/70/80-" was stale: script 60 was merged into
   the lib in commit 2d6ac28; script 80 has never existed in this repo.
   Current set is 50 (install-packages → lib), 70 (install-plugins),
   99 (post-install-hint). Both README §Map-of-the-repo and AGENTS.md
   table-of-paths now show "50/70/99-".

No behavioural changes — pure docs accuracy.
@vaintrub vaintrub changed the title fix(install): resilient docker + npm + GitHub-token bootstrap Migrate dotbot → chezmoi + mise-as-primary-installer May 17, 2026
vaintrub added 2 commits May 17, 2026 12:32
Per repo convention "default to writing no comments — only when WHY is
non-obvious". Comment density had crept up over the migration arc;
several blocks duplicated info already in code, in README, or in git
history. Pruning rule applied:

  Drop  WHAT-comments (well-named identifiers cover this).
  Drop  historical refs ("previously...", "replaces...", "was X")
        — git log is authoritative.
  Drop  task/fix refs ("for the X flow", "added for issue Y").
  Drop  multi-paragraph blocks where one line suffices.
  Keep  WHY for non-obvious workarounds: aqua:1password/cli broken
        entry, rtk glibc ≥2.39 requirement, op read --no-newline,
        chezmoi onepasswordRead vs runtime cascade rationale,
        libpq keg-only force-link.
  Keep  Section dividers (one-liners).

Files touched (LOC trim):
  lib/install-packages.sh                  251 → 199  (-52)
  dot_config/mise/config.toml.tmpl         109 →  81  (-28)
  run_onchange_after_50-install-packages   81 →  54  (-27)
  run_onchange_after_70-install-plugins    83 →  61  (-22)
  run_once_after_99-post-install-hint      44 →  36  (-8)
  hooks/ensure-prereqs.sh                 126 →  91  (-35)
  .chezmoidata/packages.yaml              135 →  94  (-41)
  .chezmoi.toml.tmpl                      106 →  68  (-38)

Net: -249 LOC removed across comments + structure consolidation.
No behavioural changes — bats unit (31/31) + chezmoi apply remain
idempotent + apply-time output identical (rtk init banner, mise install
output, etc.).
Re-verification after the comment-trim commit found three drift points:

1. README §"OS-native packages" called the third packages.yaml key
   `packages.mac` — actual key is `packages.gui` (renamed when the
   profile was renamed core/dev/mac → core/dev/workstation).
2. README §"GitHub API rate limit" said "25 tools × 1-2 calls" — actual
   count of aqua + github backends is ~29-30 tools that hit api.github.com.
3. AGENTS §"Bootstrap script split" claimed the wrapper was 25 LOC —
   actual is ~55 LOC (includes the 1Password→gh GITHUB_TOKEN cascade
   added later).

Also unified the mise-tool count phrasing between README and AGENTS so
both docs use the same framing: "29 aqua binaries + op (direct-URL) +
rtk (github)" across all tiers, with the dev-tier-specific count (27)
labelled as such in the profile table to avoid the ambiguity that 29 vs
27 created.

No behavioural changes — pure docs accuracy after the trim pass.
@vaintrub vaintrub merged commit 624d11b into master May 17, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant