Chezmoi supports running scripts as part of the dotfiles application process. Scripts automate setup tasks like installing packages, configuring tools, and generating keys.
| Type | Prefix | Runs When |
|---|---|---|
| run_once | run_once_ |
First time only (state tracked) |
| run_onchange | run_onchange_ |
When script content changes (hash-based) |
Scripts run in this order:
run_once_before_*(alphabetically)run_onchange_before_*(alphabetically)- File application
run_once_after_*(alphabetically)run_onchange_after_*(alphabetically)
Numbers in filenames (e.g., 00-, 01-) control sort order within each phase.
Purpose: Creates standard directory structure before any files are applied.
Runs: Once, before files
Creates:
~/Projects/ # Project workspace
~/git/ # Git repositories
~/Downloads/ # Downloads folder
~/Documents/ # Documents folder
~/.config/ # XDG config directory
~/.local/bin/ # User binaries
~/.local/share/ # User data
~/.local/state/ # User state
~/.cache/zsh/ # Zsh cache
~/.local/state/zsh/ # Zsh state (history)
Source:
#!/bin/bash
set -euo pipefail
echo "Creating standard directories..."
mkdir -p "${HOME}"/{Projects,git,Downloads,Documents}
mkdir -p "${HOME}/.config"
mkdir -p "${HOME}/.local"/{bin,share,state}
mkdir -p "${HOME}/.cache/zsh"
mkdir -p "${HOME}/.local/state/zsh"
echo "Directories created successfully."Purpose: Installs Homebrew and all packages on macOS.
Runs: When package list changes (content hash)
OS: macOS only (filename indicates darwin)
Actions:
- Installs Homebrew if missing
- Installs 108+ Homebrew packages and casks
- Installs npm global packages
- Installs fonts
Key Packages:
- Development: neovim, git, node, python, go, rust
- CLI: ripgrep, fd, bat, fzf, jq, yq
- DevOps: docker, kubectl, terraform, ansible
- GUI (if detected): iTerm2, Chrome, Slack
See PACKAGES.md for complete list.
Purpose: Installs packages on Linux distributions.
Runs: When package list changes
OS: Linux only
Supports:
- Arch Linux (pacman)
- Ubuntu/Debian (apt)
- Fedora (dnf)
Actions:
- Detects distribution
- Updates package cache
- Installs development tools
- Installs GUI apps if X11/Wayland detected
Purpose: Installs Oh My Zsh and Powerlevel10k theme.
Runs: Once, after files are applied
Actions:
- Installs Oh My Zsh (unattended mode)
- Clones Powerlevel10k theme
Source:
#!/bin/bash
set -euo pipefail
if [[ ! -d "${HOME}/.oh-my-zsh" ]]; then
echo "Installing Oh My Zsh..."
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended
fi
# Install Powerlevel10k theme
P10K_DIR="${HOME}/.oh-my-zsh/custom/themes/powerlevel10k"
if [[ ! -d "${P10K_DIR}" ]]; then
echo "Installing Powerlevel10k..."
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git "${P10K_DIR}"
fi
echo "Oh My Zsh and Powerlevel10k installed successfully."Purpose: Generates Ed25519 SSH keys.
Runs: Once, after files are applied
Actions:
- Generates Ed25519 SSH key pair
- Adds to macOS keychain (on Darwin)
- Displays public key for adding to GitHub/GitLab
Source (templated):
#!/bin/bash
set -euo pipefail
SSH_DIR="{{ .chezmoi.homeDir }}/.ssh"
if [[ ! -f "${SSH_DIR}/id_ed25519" ]]; then
echo "Generating SSH key..."
ssh-keygen -t ed25519 -C "{{ .git.email }}" -f "${SSH_DIR}/id_ed25519" -N ""
{{ if .machine.isDarwin }}
# Add to macOS keychain
ssh-add --apple-use-keychain "${SSH_DIR}/id_ed25519"
{{ end }}
echo "SSH key generated: ${SSH_DIR}/id_ed25519.pub"
echo "Add this to your GitHub/GitLab account:"
cat "${SSH_DIR}/id_ed25519.pub"
else
echo "SSH key already exists, skipping..."
fiChezmoi stores script execution state in:
~/.local/share/chezmoi/.chezmoistate.boltdb
- run_once_: Records that script has run; never runs again
- run_onchange_: Stores content hash; reruns if hash changes
# Show script state
chezmoi state dumpTo re-run run_once_* scripts:
# Clear all script state
chezmoi state delete-bucket --bucket=scriptState
# Re-apply (scripts will run again)
chezmoi apply# Apply files only, skip all scripts
chezmoi apply --exclude scripts
# Apply only scripts
chezmoi apply --include scriptsrun_once_before_00-my-script.sh # Runs once, before files, order 00
run_once_after_10-another.sh.tmpl # Runs once, after files, order 10, templated
run_onchange_before_packages.sh.tmpl # Runs on change, before files, templated
Add .tmpl suffix if script needs template variables:
#!/bin/bash
# run_once_after_example.sh.tmpl
{{ if .machine.isDarwin }}
echo "Setting up macOS..."
# macOS-specific setup
{{ end }}
{{ if .machine.isWork }}
echo "Configuring work tools..."
# Work-specific setup
{{ end }}-
Idempotency: Scripts should be safe to run multiple times
# Good: Check before creating if [[ ! -d "$DIR" ]]; then mkdir -p "$DIR" fi # Good: Use mkdir -p (idempotent) mkdir -p "$DIR"
-
Error Handling: Use
set -euo pipefail#!/bin/bash set -euo pipefail
-
Progress Messages: Show what's happening
echo "Installing dependencies..." # ... commands echo "Dependencies installed successfully."
-
Conditional Execution: Skip if already done
if command -v some-tool &> /dev/null; then echo "some-tool already installed, skipping..." exit 0 fi
# See scripts being executed
chezmoi apply -v# See what would run without executing
chezmoi apply --dry-run -v# View templated script content
chezmoi cat ~/.local/share/chezmoi/run_once_after_01-install-ohmyzsh.sh.tmpl
# Or execute template directly
chezmoi execute-template < run_once_after_01-install-ohmyzsh.sh.tmpl- ARCHITECTURE.md - Script execution in the overall flow
- TEMPLATES.md - Template syntax for .tmpl scripts
- PACKAGES.md - What the package scripts install