This document explains how the dotfiles system is designed, how components interact, and the order in which things are initialized.
┌─────────────────────────────────────────────────────────────────────────────┐
│ chezmoi init / apply │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ .chezmoi.toml.tmpl │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 1. Detect OS (darwin/linux) │ │
│ │ 2. Detect machine type (work/personal from hostname) │ │
│ │ 3. Detect GUI environment (X11/Wayland/macOS) │ │
│ │ 4. Prompt for git credentials and work-specific settings │ │
│ │ 5. Generate template variables (.machine, .git, .codex) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
┌──────────────────────────┼──────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────────────┐ ┌───────────────────────┐ ┌───────────────────────┐
│ run_once_before_* │ │ run_onchange_* │ │ Config Files │
│ (First-time setup) │ │ (On content change) │ │ (Templates) │
├───────────────────────┤ ├───────────────────────┤ ├───────────────────────┤
│ • Create directories │ │ • Install packages │ │ • Shell configs │
│ │ │ (Homebrew/pacman) │ │ • Git configuration │
│ │ │ • npm global packages │ │ • Neovim setup │
│ │ │ │ │ • Tool configs │
└───────────────────────┘ └───────────────────────┘ └───────────────────────┘
│ │ │
▼ ▼ │
┌───────────────────────┐ ┌───────────────────────┐ │
│ run_once_after_* │ │ Config Files │◄────────────┘
│ (Post-apply setup) │ │ Applied to ~ │
├───────────────────────┤ └───────────────────────┘
│ • Install Oh My Zsh │
│ • Generate SSH keys │
└───────────────────────┘
When you run chezmoi init or chezmoi apply, the following happens in order:
-
Template Processing -
.chezmoi.toml.tmplis evaluated- OS detection via
{{ .chezmoi.os }} - Machine type from hostname patterns
- GUI detection from environment variables
- User prompted for git credentials
- OS detection via
-
Config Generated -
~/.config/chezmoi/chezmoi.tomlcreated with:[data.machine] type = "work" os = "darwin" hasGui = true # ... more variables [data.git] name = "Your Name" email = "[email protected]"
Scripts run in alphabetical order before files are applied:
| Order | Script | Purpose |
|---|---|---|
| 00 | run_once_before_00-setup-directories.sh |
Creates ~/Projects, ~/.config, ~/.local/{bin,share,state} |
Note: run_onchange_before_* scripts also run here if their content changed:
| Script | Purpose | Trigger |
|---|---|---|
run_onchange_before_install-packages-darwin.sh.tmpl |
Install Homebrew + 108 packages | Package list changes |
run_onchange_before_install-packages-linux.sh.tmpl |
Install via pacman/apt/dnf | Package list changes |
Config files are templated and applied:
dot_zshrc.tmpl→~/.zshrcdot_gitconfig.tmpl→~/.gitconfigdot_config/nvim/*→~/.config/nvim/*- etc.
| Order | Script | Purpose |
|---|---|---|
| 01 | run_once_after_01-install-ohmyzsh.sh.tmpl |
Install Oh My Zsh + Powerlevel10k theme |
| 02 | run_once_after_02-generate-ssh-keys.sh.tmpl |
Generate Ed25519 SSH key, add to macOS keychain |
Chezmoi uses special prefixes to control file behavior:
| Prefix | Meaning | Example |
|---|---|---|
dot_ |
Creates dotfile (prefixes with .) |
dot_zshrc → .zshrc |
private_ |
Sets 0600 permissions | private_dot_ssh/ |
run_once_ |
Script runs once ever | run_once_after_01-install-ohmyzsh.sh.tmpl |
run_onchange_ |
Script runs when content changes | run_onchange_before_install-packages-darwin.sh.tmpl |
before_ |
Runs before file application | run_once_before_00-setup-directories.sh |
after_ |
Runs after file application | run_once_after_01-install-ohmyzsh.sh.tmpl |
.tmpl |
File is a Go template | dot_gitconfig.tmpl |
Scripts execute in this order:
run_once_before_*andrun_onchange_before_*(alphabetically)- File application
run_once_after_*andrun_onchange_after_*(alphabetically)
Numbers in filenames (e.g., 00-, 01-) control alphabetical ordering.
When you open a terminal, zsh loads configuration in this order:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. ~/.zshenv (all shells) │
│ • Basic environment variables │
│ • EDITOR, HISTSIZE, XDG paths │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 2. ~/.zprofile (login shells only) │
│ • Homebrew initialization │
│ • Docker socket setup │
│ • Source ~/.env if exists │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 3. ~/.zshrc (interactive shells) │
│ • Powerlevel10k instant prompt │
│ • Oh My Zsh initialization │
│ • Source conf.d/*.zsh modules │
│ • Source ~/.aliases.zsh │
│ • Source ~/.p10k.zsh │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 4. ~/.config/zsh/conf.d/*.zsh (loaded by .zshrc) │
│ • 00-path.zsh - PATH management │
│ • 10-tools.zsh - Tool initialization (asdf, nvm, atuin, etc.) │
│ • 20-completions.zsh - Shell completions (kubectl, gh, gt) │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────┐
│ chezmoi │
│ (core engine) │
└────────┬────────┘
│
┌────────────────┼────────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ git │ │ curl │ │ make │
│ │ │ │ │(optional)│
└──────────┘ └──────────┘ └──────────┘
After First Apply:
┌─────────────────────────────────────────────────┐
│ Homebrew │
│ │ │
│ ┌─────────────────┼─────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────┐ ┌───────────┐ ┌──────────┐ │
│ │ node │ │Development│ │ CLI │ │
│ │ │ │ Tools │ │ Tools │ │
│ └──┬───┘ └───────────┘ └──────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ npm globals │ │
│ │ (claude-code,│ │
│ │ aws-cdk,..) │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Oh My Zsh │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Powerlevel10k│ │
│ └──────────────┘ │
└─────────────────────────────────────────────────┘
After full application, your home directory will have:
~/
├── .config/
│ ├── chezmoi/ # Chezmoi configuration
│ │ └── chezmoi.toml # Generated from .chezmoi.toml.tmpl
│ ├── nvim/ # Neovim configuration
│ ├── zsh/
│ │ └── conf.d/ # Modular zsh configs
│ ├── kitty/ # Kitty terminal
│ ├── atuin/ # Shell history sync
│ └── gh/ # GitHub CLI
├── .local/
│ ├── bin/ # User binaries
│ ├── share/
│ │ └── chezmoi/ # Dotfiles source repository
│ └── state/
│ └── zsh/ # Zsh state files
├── .oh-my-zsh/ # Oh My Zsh installation
├── .ssh/ # SSH keys (generated)
├── Projects/ # Project directory
├── git/ # Git repositories
├── .zshrc # Main shell config
├── .zprofile # Login shell config
├── .zshenv # Environment variables
├── .gitconfig # Git configuration
├── .tmux.conf # Tmux configuration
├── .aliases.zsh # Shared aliases
└── .p10k.zsh # Powerlevel10k theme
Chezmoi tracks what has been applied:
| Location | Purpose |
|---|---|
~/.local/share/chezmoi/ |
Source repository (your dotfiles) |
~/.config/chezmoi/chezmoi.toml |
Generated configuration with template data |
~/.local/share/chezmoi/.chezmoistate.boltdb |
Script execution state (which run_once_ scripts have run) |
run_once_*: Never re-run unless you clear staterun_onchange_*: Re-run when script content changes (via hash)
To force re-run of run_once_* scripts:
chezmoi state delete-bucket --bucket=scriptState
chezmoi applyTemplates use Go template syntax for conditional behavior:
// OS-specific
{{ if .machine.isDarwin }}
# macOS only
{{ end }}
{{ if .machine.isLinux }}
# Linux only
{{ end }}
// Machine type
{{ if .machine.isWork }}
# Work machine only
{{ end }}
// GUI detection
{{ if .machine.hasGui }}
# Only install if GUI available
{{ end }}See testing/README.md for the full testing documentation.
testing/
├── docker-compose.yml # Linux test containers (Arch)
├── Dockerfile # Ansible control node
├── playbooks/
│ └── test-dotfiles.yml # Main test playbook
├── inventories/
│ └── hosts.yml # Test targets
└── macos/
└── README.md # macOS VM testing (Tart/Lima)
- TEMPLATES.md - Template syntax and variables
- PROFILES.md - Work vs personal profiles
- SCRIPTS.md - Detailed script documentation
- SHELL.md - Shell configuration details