Apply scripts
Every run_ script chezmoi executes during apply, when each one fires, and what it does on each OS and role.
Six scripts run during chezmoi apply. Four live in .chezmoiscripts/, a directory whose scripts run as if they sat at the source root without creating a matching target directory. Two sit at the repo root. All six are templates and all use #!/bin/sh with set -eu.
Execution order
before_ scripts run before any file is written; after_ scripts run after every file, symlink, and external is in place. Within a phase chezmoi runs scripts in ASCII order of their target names, so the numeric prefixes come first.
10-packages (before, onchange)
packages.yaml.Files, symlinks, externals
20-gh-extensions (after, onchange)
30-user-services (after, once)
40-post (after, onchange)
build-bat-cache (after, onchange)
install-yazi-plugins (after, onchange)
| Script | Phase | Frequency | Re-run trigger |
|---|---|---|---|
.chezmoiscripts/run_onchange_before_10-packages.sh.tmpl |
before | onchange | sha256 of .chezmoidata/packages.yaml; also the role and OS, which change the rendered package list |
.chezmoiscripts/run_onchange_after_20-gh-extensions.sh.tmpl |
after | onchange | sha256 of packages.shared.gh_extensions as JSON |
.chezmoiscripts/run_once_after_30-user-services.sh.tmpl |
after | once | Runs once per distinct rendered content |
.chezmoiscripts/run_onchange_after_40-post.sh.tmpl |
after | onchange | Only edits to the script itself (or an OS change) |
run_onchange_after_build-bat-cache.sh.tmpl |
after | onchange | sha256 of dot_config/bat/themes/cyberdream.tmTheme and dot_config/bat/config |
run_onchange_after_install-yazi-plugins.sh.tmpl |
after | onchange | sha256 of dot_config/yazi/package.toml |
run_onchange_ scripts re-run whenever their rendered content differs from the last successful run. The hash triggers work by writing a checksum of another file into a comment, so editing that file changes the script’s content:
# packages.yaml hash: {{ include ".chezmoidata/packages.yaml" | sha256sum }}
run_once_ scripts run once per unique rendered content and again only if that content changes or the previous run failed.
10-packages
.chezmoiscripts/run_onchange_before_10-packages.sh.tmpl installs everything in packages.yaml. It runs before files are applied so the tools exist when configs land.
- Uses
pacmandirectly when running as root, otherwisesudo pacman. pacman -S --needed --noconfirmwith every entry inpackages.arch.pacman.- For roles
desktopandlaptop, a secondpacman -S --needed --noconfirmwithpackages.arch.desktop.pacman. - Builds the AUR list:
packages.arch.desktop.aurpluspackages.arch.aurfordesktop/laptop, onlypackages.arch.aurfor other roles. - If the list is non-empty and
paruexists, runsparu -S --needed --noconfirmon it. Withoutparuit printswarning: paru not found — skipping AUR packages: …and continues.
- Exits with an error pointing at https://brew.sh if
brewis missing. brew installwith every entry inpackages.darwin.brew.brew install --caskwith every entry inpackages.darwin.cask.
The role is not consulted on macOS.
On any other OS the script renders to just the shebang and comments and does nothing.
20-gh-extensions
.chezmoiscripts/run_onchange_after_20-gh-extensions.sh.tmpl installs each packages.shared.gh_extensions entry on every OS.
- Exits 0 with
warning: gh not installed — skipping extensionsifghis missing. - Exits 0 with
warning: gh not authenticated — run 'gh auth login', then 'chezmoi apply' againifgh auth statusfails. - Reads
gh extension list, then runsgh extension install <repo>only for extensions not already present.
30-user-services
.chezmoiscripts/run_once_after_30-user-services.sh.tmpl enables session-critical systemd user units on Linux.
- If
~/.config/systemd/user/nirinit.serviceexists andsystemctlis available, runssystemctl --user daemon-reloadandsystemctl --user enable nirinit.service. Failures are ignored. - On non-Linux systems it renders to
#!/bin/sh/exit 0.
Other personal automation units (brain-lens, omp-loop, rclone, hermes, voxtype) are deliberately not enabled; the script comment says to enable them by hand per machine. The repo does not currently contain a dot_config/systemd/ directory, so the unit file has to exist on the machine already for this script to do anything.
40-post
.chezmoiscripts/run_onchange_after_40-post.sh.tmpl is post-apply housekeeping. It has no hash comment, so it only re-runs when the script itself is edited.
| Step | Condition | Action |
|---|---|---|
| tmux plugin manager | tmux installed and ~/.config/tmux/plugins/tpm missing |
git clone --depth=1 https://github.com/tmux-plugins/tpm ~/.config/tmux/plugins/tpm |
| bat cache | bat installed |
bat cache --build |
| Font cache | Linux, fc-cache installed |
fc-cache -f |
Each step ignores failures (|| true). tmux.conf expects tpm at that path; see tmux.
build-bat-cache
run_onchange_after_build-bat-cache.sh.tmpl rebuilds bat’s syntax and theme cache when the managed inputs change. It hashes dot_config/bat/themes/cyberdream.tmTheme and dot_config/bat/config, exits quietly if bat is missing, and otherwise runs bat cache --build. This registers the cyberdream theme and the PureScript syntax cloned by .chezmoiexternal.toml.
install-yazi-plugins
run_onchange_after_install-yazi-plugins.sh.tmpl reinstalls yazi plugins whenever dot_config/yazi/package.toml changes. It exits quietly if ya is missing or ${XDG_CONFIG_HOME:-$HOME/.config}/yazi/package.toml does not exist, and otherwise runs ya pkg install. The installed plugins land in ~/.config/yazi/plugins/, which .chezmoiignore.tmpl excludes. The plugin list is on the yazi page.