chezmoi

Manage dotfiles across multiple machines with chezmoi. Use when working with dotfile management, templating, secrets, machine-specific configs, or chezmoi commands.

SKILL.md

GitHub

Chezmoi Dotfile Manager

Manage your dotfiles across multiple diverse machines, securely.

Quick Reference

Command Purpose
chezmoi init Initialize chezmoi (creates ~/.local/share/chezmoi)
chezmoi add <file> Add file to source state
chezmoi edit <file> Edit source version of file
chezmoi diff Preview changes before applying
chezmoi apply Apply changes to home directory
chezmoi update Pull latest changes and apply
chezmoi cd Open shell in source directory
chezmoi doctor Check for problems

Local Workflow Contract

On this machine, use the dotfiles helper as the control plane instead of jumping straight to chezmoi update.

Dry Run First

dotfiles status                 # Summary dashboard for chezmoi + ~/.agents
dotfiles sync                   # Dry-run planner, writes latest report
dotfiles inspect report         # Full dry-run report
dotfiles inspect chezmoi-diff   # Full target/source diff
dotfiles inspect chezmoi-remote # Remote commits not yet local
dotfiles inspect agents-remote  # ~/.agents remote commits not yet local

Explicit Apply Paths

dotfiles sync --apply chezmoi   # Re-add, commit, pull --rebase, push, apply
dotfiles sync --apply agents    # Commit, pull --rebase, push ~/.agents
dotfiles sync --apply all       # Run both explicit apply paths
dotfiles pull                   # Pull chezmoi source repo and apply
dotfiles agents-pull            # Pull ~/.agents with rebase

Behavior Rules

  • dotfiles sync is the safe entry point. It performs a dry run and does not mutate state.
  • Read the dry-run report before choosing an apply action when local and remote both moved.
  • ~/.agents stays a separate git repo; it is surfaced in the same dashboard but is not folded into chezmoi source state.
  • The latest dry-run report is written to ~/.local/state/lev/dotfiles-sync-plan.latest.txt.

One-liner Bootstrap

sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply $GITHUB_USERNAME

Core Concepts

Source State vs Target State

  • Source state: Files in ~/.local/share/chezmoi/ (managed by chezmoi)
  • Target state: Files in your home directory (what chezmoi manages)
  • Destination directory: Usually ~ (your home)

File Naming Conventions

Prefix/Suffix Meaning Example
dot_ Creates . prefix dot_bashrc → .bashrc
private_ Mode 0600 private_dot_ssh/
executable_ Mode +x executable_dot_local/bin/myscript
readonly_ Read-only readonly_dot_config/
empty_ Create empty file empty_dot_gitkeep
symlink_ Create symlink symlink_dot_config
.tmpl Template file dot_bashrc.tmpl

Directory Attributes

~/.local/share/chezmoi/
├── dot_config/
│   ├── exact_nvim/           # exact_ = remove unlisted files
│   └── private_gnupg/        # private_ = mode 0700
├── private_dot_ssh/
│   └── private_config        # mode 0600
└── dot_bashrc.tmpl           # template

Templating

Basic Template Syntax

{{ .chezmoi.hostname }}       # Current hostname
{{ .chezmoi.os }}             # darwin, linux, windows
{{ .chezmoi.arch }}           # amd64, arm64
{{ .chezmoi.username }}       # Current user
{{ .chezmoi.homeDir }}        # Home directory path

Conditionals

# dot_bashrc.tmpl
{{ if eq .chezmoi.os "darwin" -}}
export HOMEBREW_PREFIX="/opt/homebrew"
{{ else if eq .chezmoi.os "linux" -}}
export HOMEBREW_PREFIX="${HOMEBREW_PREFIX:-$HOME/.linuxbrew}"
{{ end -}}

Machine-specific Config

Create ~/.config/chezmoi/chezmoi.toml for machine-specific data:

[data]
    email = "me@work.com"
    git_signing_key = "ABC123"
    
[data.work]
    enabled = true

Then in templates:

{{ .email }}
{{ if .work.enabled }}
# Work-specific config
{{ end }}

Prompt for Data on Init

Create .chezmoi.<format>.tmpl (e.g., .chezmoi.toml.tmpl):

[data]
    email = {{ promptString "Email address" | quote }}
    signingkey = {{ promptString "GPG signing key" | quote }}

Password Manager Integration

1Password

# In template
{{ onepasswordRead "op://vault/item/field" }}
{{ (onepassword "item-name").password }}

Bitwarden

{{ (bitwarden "item-name").login.password }}

pass (Unix Password Store)

{{ pass "path/to/secret" }}

Generic (Any Password Manager)

# In chezmoi.toml
[secret]
    command = "my-secret-tool"
    args = ["get"]

# In templates
{{ secret "secret-name" }}

Encryption

Using age (Recommended)

# Generate key
chezmoi age-keygen

# Configure in chezmoi.toml
[age]
    identity = "~/.config/chezmoi/key.txt"
    recipient = "age1..."

Encrypt Files

# Add encrypted file
chezmoi add --encrypt ~/.ssh/id_rsa

# Result: encrypted_private_dot_ssh/private_id_rsa.age

Scripts

Run Scripts on Apply

Prefix Runs When
run_ Every apply
run_once_ Only once (tracked by checksum)
run_onchange_ When script content changes

Script Ordering

Scripts run alphabetically. Use numeric prefixes:

.chezmoiscripts/
├── run_once_before_01-install-homebrew.sh
├── run_once_before_02-install-packages.sh
└── run_onchange_after_reload-shell.sh

Script Template

#!/bin/bash
# run_once_before_install-packages.sh.tmpl

{{ if eq .chezmoi.os "darwin" -}}
brew bundle --file=/dev/stdin <<EOF
brew "ripgrep"
brew "fd"
cask "wezterm"
EOF
{{ else if eq .chezmoi.os "linux" -}}
sudo apt-get install -y ripgrep fd-find
{{ end -}}

External Files

Import from URL/Archive

Create .chezmoiexternal.toml:

[".oh-my-zsh"]
    type = "archive"
    url = "https://github.com/ohmyzsh/ohmyzsh/archive/master.tar.gz"
    stripComponents = 1
    refreshPeriod = "168h"

[".local/bin/starship"]
    type = "file"
    url = "https://starship.rs/install.sh"
    executable = true

Import from Git

[".tmux/plugins/tpm"]
    type = "git-repo"
    url = "https://github.com/tmux-plugins/tpm.git"
    refreshPeriod = "168h"

Configuration File

Located at ~/.config/chezmoi/chezmoi.toml:

# Source directory (default: ~/.local/share/chezmoi)
sourceDir = "~/.dotfiles"

# Diff tool
[diff]
    pager = "delta"

# Merge tool  
[merge]
    command = "nvim"
    args = ["-d", "{{ .Destination }}", "{{ .Source }}", "{{ .Target }}"]

# Git auto-commit
[git]
    autoCommit = true
    autoPush = false

# Data for templates
[data]
    name = "Your Name"
    email = "your@email.com"

Common Workflows

New Machine Setup

# Install chezmoi and apply dotfiles
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply $GITHUB_USERNAME

Daily Updates

# Pull changes and apply
chezmoi update

# Or step-by-step
chezmoi git pull
chezmoi diff
chezmoi apply

Adding New Files

chezmoi add ~/.bashrc                    # Regular file
chezmoi add --encrypt ~/.ssh/config      # Encrypted
chezmoi add --template ~/.gitconfig      # As template
chezmoi add --autotemplate ~/.config/X   # Auto-detect template

Editing Files

chezmoi edit ~/.bashrc     # Edit source, then apply
chezmoi edit-config        # Edit chezmoi.toml

Troubleshooting

See references/troubleshooting.md for common issues including:

  • Vimpager/AnsiEsc ANSI escape code issues
  • Diff pager configuration
  • Encryption setup problems

References

Template Functions Reference

Filesystem

Function Description
glob "pattern" Find files matching pattern
include "path" Include file contents
stat "path" Get file info
lookPath "cmd" Find command in PATH

Strings

Function Description
contains "substr" "str" Check substring
hasPrefix "pre" "str" Check prefix
hasSuffix "suf" "str" Check suffix
replace "old" "new" "str" Replace in string
trim "str" Trim whitespace
quote "str" Quote for TOML

Data Formats

Function Description
toJson .data Convert to JSON
toToml .data Convert to TOML
toYaml .data Convert to YAML
fromJson "str" Parse JSON
fromToml "str" Parse TOML
fromYaml "str" Parse YAML

Execution

Function Description
output "cmd" "args"... Run command, return output
env "VAR" Get environment variable