This repo used to be managed with rcm (rcup +
rcrc). It now uses chezmoi. This document explains the new
structure and the reasoning behind it, for anyone (human or otherwise) trying to
understand why a given file is named or organized the way it is.
rcm's rcrc declared what to manage via EXCLUDES/DOTFILES_DIRS — a central config
file that had to be kept in sync with the repo's contents. chezmoi instead reads the
filename itself as the instruction, so there's nothing separate to keep in sync:
| Prefix | Meaning | Example |
|---|---|---|
dot_ |
deploy as ~/.foo |
dot_zshrc → ~/.zshrc |
private_ |
deploy with 0600/0700 perms |
private_dot_ssh/private_config → ~/.ssh/config (0600) |
symlink_ |
deploy as a symlink; file content is the target path | symlink_dot_claude.tmpl → ~/.claude → ~/dotfiles/claude |
run_once_* |
shell script, runs once per unique content (tracked in chezmoi's state) | run_once_install-vim-plug.sh.tmpl |
run_onchange_* |
shell script, re-runs whenever its rendered content changes | run_onchange_update-vim-plugins.sh.tmpl (keyed off a sha256sum of dot_vimrc.bundles) |
| (no prefix) | left alone, not deployed to $HOME at all |
Brewfile, and anything matched by .chezmoiignore |
private_dot_ssh/ alone only gives the directory 0700 perms — files inside still come
out 0644 unless they also carry their own private_ prefix. That's why ssh/config
became private_dot_ssh/private_config, not just private_dot_ssh/config.
chezmoi aborts the entire apply if any script exits non-zero — unlike rcm's
hooks/post-up, which had no set -e and silently tolerated failures. Two commands had
pre-existing flaky/non-TTY failure modes that never mattered under rcm's tolerant hook but
would now block every apply:
vim -E -s +PlugUpgrade +qainrun_once_install-vim-plug.sh.tmplcan exit 1 even when run standalone, for unclear reasons unrelated to this migration.reset -Qinrun_onchange_update-vim-plugins.sh.tmplfails withinappropriate ioctl for devicewhen run outside a real TTY.
Appending || true to both restores the old "best effort, don't block" behavior rather
than papering over an unrelated bug.
chezmoi's run_once_/run_onchange_ semantics let each step declare its own re-run
condition — something a single monolithic hook script couldn't express:
- vim-plug install (
run_once_install-vim-plug.sh.tmpl) — only needs to run once ever. - vim plugin update (
run_onchange_update-vim-plugins.sh.tmpl) — should re-run wheneverdot_vimrc.bundleschanges, so it's keyed to that file's hash. - doctor checks (
run_once_doctor-checks.sh.tmpl) — one-time sanity checks (git_template/HEADcleanup,/etc/zshenvpath_helperwarning).
rcm just ran the whole hooks/post-up script on every rcup, with no way to scope a part
of it to "only when this file changes."
These are repo-only artifacts (docs, the bootstrap script itself) that should never land
in $HOME. rcm handled the equivalent via EXCLUDES="*.md LICENSE CODEOWNERS" in rcrc;
.chezmoiignore is the direct replacement. claude and obsidian are also listed there,
since those directories are reached only via symlink_dot_claude.tmpl or the explicit
link-obsidian script, not by chezmoi managing them directly.
Early planning considered adding a chezmoi profile template variable to switch git
identity per machine. It turned out gitconfig already does this via git's own
includeIf "gitdir:~/datadog/" mechanism, pulling in ~/.gitconfig.datadog for
work-tree paths. That's a cleaner, git-native solution than layering chezmoi templating on
top, so it was left alone.
rcm's convention was per-machine override files living outside the repo. chezmoi's
untracked .local-suffixed files (~/.gitconfig.local, ~/.zshrc.local,
~/.aliases.local, ~/.vimrc.local) achieve the same thing more simply — no separate
directory to remember to check, and each tracked config file already has an
include/source-guard pointing at its .local counterpart.
See the "Migrating an Existing Machine from rcm" section in SETUP.md.