Skip to content

arena: chezmoi - #14

Closed
drewjs wants to merge 3 commits into
mainfrom
arena/chezmoi
Closed

drewjs wants to merge 3 commits into
mainfrom
arena/chezmoi

Conversation

@drewjs

@drewjs drewjs commented Oct 17, 2026 •

Copy link
Copy Markdown
Owner

Why

A fresh Mac should install the tools that machine needs, and only those, without a second copy of the Stow tree.

./setup asks once, saves the choice in ~/.config/dotfiles/tools, and runs chezmoi. The apply script template reads that file and links the matching Stow packages. A second run with the same choice leaves those links and already-installed formulae alone.

Add a tool by appending one row to tools.tsv.

What changed

  • tools.tsv is the list of installable tools, their Homebrew packages, and dependencies.
  • ./setup is the one command. Chezmoi applies the saved list.
  • The macOS workflow runs shellcheck, a subset install, a full install, and a second run that must match the first snapshot.

Scope

This covers a fresh Mac and a remembered subset. stow <tool> from the repo root still works. Existing package directories stay in the repo.

Tradeoffs

A plain manifest script as the installer was rejected. Chezmoi has to be the thing that applies the choice.

Nix was rejected. It would replace the Stow layout.

Copying every config into chezmoi dot_ files was rejected. A new file would need its own template guard, and stow <tool> would no longer be the same tree.

Blast radius

These are the behaviors that remove or overwrite a config Andrew already has.

  • If a destination file exists and is not already the file from this repo, setup moves that file to ~/.local/share/dotfiles/backups/<timestamp>/ and links the repo file in its place. The old bytes are the backup. The old path now points at the repo.
  • If a parent directory such as ~/.config is a symlink, setup moves that symlink into the same backup directory and creates a real directory. When the symlink pointed at a package in this repo, setup links that package again so its files stay available.
  • On a Mac that does not have Homebrew yet, the Homebrew installer may append its shellenv line to ~/.zprofile. If ~/.zprofile already exists, setup copies it into the backup directory first.

A rerun that names fewer tools leaves the earlier links where they are. Formulae that are already installed stay at the installed version. setup does not start yabai or skhd, and it does not write ~/.zshrc.local.

Verification

./lib/dotfiles.sh self-test passed on Linux. It checked a starship-only link, rejection of other packages, an idempotent second apply, a backup before replace, preservation of a folded ~/.config symlink, and ./setup through chezmoi.

setup on macos-latest passed in https://github.com/drewjs/dotfiles/actions/runs/39075354540. shellcheck, subset, and full are green. The subset job checked that unselected tools stayed absent. The full and subset jobs each ran setup a second time and the snapshot diff was empty.

Open in Web Open in Cursor 

./setup saves a per-machine tool list and chezmoi links only that
closure. The Stow packages stay in the repo for machines that still
call stow directly.

Co-authored-by: drewjsdev <drewjsdev@pm.me>
@claude

claude Bot commented Oct 17, 2026

Copy link
Copy Markdown

Review

Bug: empty TSV columns collapse (lib/dotfiles.sh, registry_load)

IFS=$'\t' read treats tab as IFS whitespace, so adjacent tabs merge into one delimiter. Empty columns get skipped and later fields shift left.

  • ghostty: the row is ghostty<TAB>ghostty<TAB><TAB>ghostty<TAB>. The cask lands in formulae, so setup runs brew install ghostty as a formula. That fails, so the full CI job should fail.
  • zsh: starship shifts from depends into casks. The zsh→starship dependency is lost. setup then tries brew install --cask starship.

The self-test doesn't catch the zsh case. It applies starship earlier in the same $HOME, so the starship link is already there.

Fixes:

  • Parse with cut -f or awk -F'\t', which preserve empty fields.
  • Or put a placeholder such as - in empty columns.
  • Add a self-test that applies zsh in a fresh $HOME and asserts starship is linked. Also assert reg_casks[ghostty] is ghostty.

Other issues

  • Destructive detach: detach_blocking_symlink calls backup_path "$dir" on any symlinked ancestor, even one that isn't from this repo. A user's ~/.config symlink to a dotfiles dir or Dropbox is moved aside and replaced. It is backed up and documented, but I'd prompt or require a flag for non-repo symlinks.
  • Unselected configs: Re-linking preserved packages (PRESERVE) restores configs for tools the user didn't select. This contradicts "only those tools".
  • --no-folding mismatch: stow_package uses --no-folding. The README says plain stow <tool> still works, but plain stow would fold directories. Mixing the two can produce the folded-symlink state this code works around.
  • Python dependency: resolve_path and mtime_ns spawn python3 once per file. python3 is missing on a fresh Mac until the Xcode CLT are installed, and brew install may not have run yet. realpath isn't available on macOS 12 and earlier. Consider a pure-bash readlink loop. This is also slow when called across the whole tree.
  • Unpinned installer: ensure_brew pipes curl .../Homebrew/install/HEAD/install.sh into bash. That is the standard Homebrew method, but note that it is an unpinned remote execution.
  • Template injection: run_before_00-apply-tools.sh.tmpl quotes tool IDs through the template. The dotfiles.sh check validates IDs later, so this is low risk.
  • run_before timing: chezmoi's run_before_ script will rerun the whole apply on every chezmoi apply. That is fine since it is idempotent, but it means plain chezmoi apply outside ./setup also does a brew check. Document this or use run_onchange_.
  • Dead code and naming: emit_tree has a mtime check that makes snapshots brittle across touch. tool_in_closure, formula_required and cask_required could share one helper. dotfiles.sh is 800+ lines of bash 3.2 workarounds. Consider whether a short Python or Ruby script would be easier to maintain.

CI

  • subset/full jobs don't install chezmoi/stow explicitly. setup does it via brew, so this is OK.
  • Everything runs on macos-latest only. Linux self-test coverage is not in CI. self-test skips the chezmoi path if chezmoi is absent.
  • The full job only runs on macOS, and it will hit the ghostty bug above.
  • Pin action versions by SHA if you care about supply chain.

Docs

README and AGENTS.md changes are concise and match the repo's style.

🤖 Generated with Claude Code

cursoragent and others added 2 commits October 17, 2026 22:23
Bash read treats tab as whitespace and drops empty fields, so a
dependency was passed to brew as a cask.

Co-authored-by: drewjsdev <drewjsdev@pm.me>
Stow skips .gitignore and a few VCS names. Requiring those links
made a full apply fail after the packages were installed.

Co-authored-by: drewjsdev <drewjsdev@pm.me>
@drewjs drewjs closed this Oct 17, 2026
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.

2 participants