Skip to content

Convert machine setup to declarative mise bootstrap - #21

Open
ericboehs wants to merge 5 commits into
masterfrom
mise-bootstrap
Open

Convert machine setup to declarative mise bootstrap#21
ericboehs wants to merge 5 commits into
masterfrom
mise-bootstrap

Conversation

@ericboehs

Copy link
Copy Markdown
Owner

Replaces the imperative bootstrap.sh (329 lines) and defaults.sh (79 lines)
with a declarative mise.toml. Setup is now mise bootstrap — convergent,
dry-runnable, and diffable.

What moved where

Old New
brew install / apt-get install loops [bootstrap.packages] (brew:, brew-cask:, apt: — the prefix is the platform filter)
ln -s loop over a hardcoded file list [dotfiles], 25 entries
git clone of gcd [bootstrap.repos]
defaults write × 37 [bootstrap.macos.*] curated + raw [bootstrap.macos.defaults.<domain>]
Nerd font zip download brew-cask:font-inconsolata-nerd-font
Linux shims, TPM, nvim/treesitter [tasks.bootstrap] + three bootstrap:* subtasks

Bugs this surfaced and fixes

  • .tmux.conf binds 9 helper scripts but bootstrap.sh symlinked only 4 —
    dead bindings on a fresh install. The TOML links ~/.tmux as a whole
    directory, which is what the live machine actually has.
  • bootstrap.sh linked .ssh/config, but the repo has no .ssh/ — it would
    prompt over a real ~/.ssh/config or leave a broken symlink. Dropped.
  • ~/.config/lsd/colors.yaml was a broken symlink to a file nothing creates.
    The new config repairs it.

Ordering notes

TPM is a task, not a [bootstrap.repos] entry: repos run at step 7 and
dotfiles at step 8, so cloning into ~/.tmux/plugins/ would materialize a
real ~/.tmux and block the whole-directory symlink. Submodules init from a
pre-dotfiles hook for the same reason.

Two defaults keys live in hooks rather than [bootstrap.macos.defaults]
because template expressions do not render in defaults values (verified
against a junk domain): com.apple.screencapture location needs $HOME, and
NSGlobalDomain com.apple.mouse.tapBehavior needs -currentHost.

Parity verification

  • macOS defaults: extracted domain key pairs from defaults.sh and from
    mise bootstrap macos defaults status, diffed with comm. 37 keys covered;
    the only two in the old script but not the TOML are the two moved to hooks.
    Nothing extra.
  • Dotfiles: mise bootstrap dotfiles status → 25 entries, 23 applied,
    2 differs (one real file byte-identical to the repo copy, one broken
    symlink) — both resolved by --force-dotfiles.
  • Dry run: confirmed mise bootstrap -n prints the bootstrap task without
    executing it.

CI

.github/workflows/bootstrap.yml drives the real thing on Ubuntu instead of
asserting symlinks by hand: mise bootstrap -y --force-dotfiles --skip tools,task, then mise bootstrap dotfiles status --missing (exit 1 on
drift), a CLI-tool presence check, and a re-run no-op check. Hardened per the
gh-actions-security checklist — SHA-pinned checkout, permissions: {}
default-deny, persist-credentials: false.

Also

iTerm2 references swapped to Ghostty (bin/code-editor, two
terminal-notifier -sender bundle IDs, a .tmux.conf comment), and the dead
minpac submodule plus four stale .vim* ignore rules removed.

Not done here

mise bootstrap has not been run for real on the live machine yet — only
-n and the per-step status commands. The old scripts are already deleted, so
this branch is the only path once merged.

Port bootstrap.sh and defaults.sh to mise's declarative bootstrap:
packages, repos, dotfiles, macOS defaults and hooks in mise.toml, with
the four genuinely imperative steps left as `bootstrap:*` tasks.

Verified against this machine with `mise bootstrap -n`: all 37 macOS
defaults keys from defaults.sh are covered (the two that aren't
expressible — the -currentHost tap behavior and the $HOME-relative
screenshot location — moved to hooks), and 23 of 25 dotfile entries
already report `applied`.

Two fixes fall out of the port:
- ~/.tmux is declared as one directory link, matching what this machine
  actually has, so .tmux.conf's bindings for agenda.sh, btop.sh,
  goto-window.sh, move-window.sh and session-jump.sh resolve on a fresh
  install; bootstrap.sh linked only four of the nine helpers.
- The dead ~/.ssh/config entry is dropped — the repo has no .ssh/, so
  bootstrap.sh would either prompt over a real config or leave a broken
  symlink.

mise.toml is un-ignored here because the global gitignore excludes
per-project mise configs; this repo's copy is the setup itself.

bootstrap.sh and defaults.sh stay until this is proven on a fresh
machine.
mise.toml now covers everything both scripts did, so delete them and
point the README and CI at `mise bootstrap`.

CI drives the real documented path — install mise from mise.run, then
`mise bootstrap --force-dotfiles --skip tools,task` — and asserts
convergence with `mise bootstrap dotfiles status --missing` instead of
hand-listing every expected symlink. [tools] and the bootstrap task are
skipped for runtime (bootstrap:nvim compiles every treesitter parser);
bootstrap:linux-shims still runs on its own since it creates the fd/bat
shims the tool check asserts on. Submodules are no longer checked out by
actions/checkout so the pre-dotfiles hook is exercised instead.

Also harden the workflow while it's open: default-deny permissions with
per-job grants, actions/checkout pinned to a SHA and bumped to v7.0.1,
and persist-credentials disabled. zizmor reports no findings.

shellcheck now covers .tmux/*.sh, which is what's left of the repo's
shell scripts; .claude/scripts/statusline.sh has pre-existing warnings
and is left for its own change.

Drop the iTerm2 Catppuccin import task and the iTerm2 pointer in
.tmux.conf — Ghostty is the terminal now.
code-editor opened iTerm, and claude-man/monitor_tmux_pane passed
-sender com.googlecode.iterm2 to terminal-notifier so click-through
focused an app that is no longer installed.
.gitmodules declared .vim/pack/minpac/opt/minpac but .vim/ has no
gitlink in the index (and no longer exists in the repo), so the entry
was inert config. The four .vim* .gitignore lines guarded paths that
are likewise gone.
Homebrew runs on Linux, so the brew: prefix only filters in one
direction — the Ubuntu CI job installed the entire macOS list alongside
the apt one and then died building terminal-notifier from source, which
needs xcodebuild. Every package entry now carries an explicit os filter.

The filter needs mise 2026.8.4, so declare min_version; without it an
older mise reports only 'invalid type: map, expected a string'.
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