Skip to content

Chezmoi

Chezmoi manages dotfiles by maintaining a source directory (~/.local/share/chezmoi) and applying files to the home directory.

File Naming Conventions

Source files use special prefixes that chezmoi interprets:

Prefix Effect Example
dot_ Deployed with . prefix dot_zshrc~/.zshrc
executable_ Sets file executable (755) executable_tmux-seshtmux-sesh (executable)
private_ Sets permissions to 0600 private_dot_env~/.env (private)
*.tmpl Processed as Go template dot_gitconfig.tmpl~/.gitconfig (templated)
create_ Only create, never update Preserves user modifications

Directories follow the same rules: dot_config/ maps to ~/.config/.

Templates

Files ending in .tmpl are Go templates processed with chezmoi data. The data is defined in .chezmoi.yaml.tmpl:

data:
  email: "15628653+swibrow@users.noreply.github.com"
  name: "Samuel"
  signingkey: "28257C9F"

These values are prompted once on first chezmoi init and cached. Use them in templates:

# dot_gitconfig.tmpl
[user]
  email = {{ .email }}
  name = {{ .name }}
  signingkey = {{ .signingkey }}

External Dependencies

.chezmoiexternal.yaml pulls in git repos automatically:

".config/tmux/plugins/tpm":
  type: git-repo
  url: "https://github.com/tmux-plugins/tpm"
  refreshPeriod: 168h    # Re-pull weekly
  clone:
    args: ["--depth", "1"]

Private Layer

This repo is public, so two things stay out of it: secret env values, and any string naming the employer or its GitHub orgs. Both live in a private repo cloned to ~/.config/dotfiles-private by .chezmoiexternal.yaml. Everything else — tool config, aliases, completions — stays here.

Path Consumed by
mise.toml mise, via a ~/.config/mise/conf.d/work.toml symlink — all age-encrypted secrets
work.zsh .zshrc and browser-openwork_orgs, GH_DEFAULT_ORG
finicky-work.js inlined into ~/.finicky.js by the dot_finicky.js.tmpl template
claude_work/CLAUDE.md ~/.claude_work/CLAUDE.md symlink
Brewfile the brew bundle run script — private tap and its formulae

Everything public degrades gracefully when the private repo is absent: .zshrc and browser-open source work.zsh only if it is readable, the finicky template omits the work handler, and the symlinks simply dangle.

Add or rotate a secret with mise run secret:set NAME, which writes the age-encrypted value into the private repo (commit it there).

Key Commands

Command Purpose
chezmoi apply Apply all changes
chezmoi diff Preview pending changes
chezmoi edit ~/.zshrc Edit a managed file
chezmoi add ~/.config/foo/bar Start managing a new file
chezmoi update Pull from remote and apply
chezmoi cd Navigate to the source directory
chezmoi doctor Diagnose common issues
chezmoi data Show template data

Quick access

The shell function cm is a shortcut — cm alone runs chezmoi cd, and cm <args> forwards to chezmoi.

Ignored Files

.chezmoiignore lists files that chezmoi should not manage — things like README.md, CLAUDE.md, the docs site, and other repo-only files.

Run Scripts

Scripts in .chezmoiscripts/ execute during apply. The naming controls behavior:

Prefix Behavior
run_once_before_ Runs once, before file apply
run_once_after_ Runs once, after file apply
run_onchange_before_ Re-runs when file content changes

The numeric prefix (00, 01, 02...) controls execution order.

Brewfile Management

brew install / brew uninstall keep the Brewfiles up to date on their own — see Brewfile. The files live at homebrew/ in the source dir and are symlinked to ~/.config/homebrew/, so brew bundle writes into the repo rather than into a copy an apply would overwrite.

To regenerate one from scratch:

brew bundle dump --force --no-vscode

Always use --no-vscode

The --no-vscode flag prevents VS Code extensions from being included in the Brewfile.