Externals and ignore rules
The git repos chezmoi clones into $HOME and every rule in .chezmoiignore.tmpl, including the role gate for the Linux desktop layer.
.chezmoiexternal.toml
Externals are content chezmoi fetches instead of storing in the repo. Both entries are git-repo externals: chezmoi runs git clone when the target directory is missing. Because they are real git checkouts, they are never symlinked into the source tree.
[".config/nvim"]
type = "git-repo"
url = "https://github.com/prdlk/nvim.git"
[".config/nvim".pull]
args = ["--ff-only"]
[".config/bat/syntaxes/sublime-purescript-syntax"]
type = "git-repo"
url = "https://github.com/tellnobody1/sublime-purescript-syntax.git"
[".config/bat/syntaxes/sublime-purescript-syntax".pull]
args = ["--ff-only"]
| Target | Repo | Used by |
|---|---|---|
~/.config/nvim |
prdlk/nvim | Neovim config, kept in its own repo |
~/.config/bat/syntaxes/sublime-purescript-syntax |
tellnobody1/sublime-purescript-syntax | PureScript syntax for bat, compiled into bat’s cache by the bat cache scripts |
No refreshPeriod
Neither entry sets refreshPeriod, so it stays at chezmoi’s default of never. chezmoi clones each repo on a fresh machine and otherwise leaves it alone: chezmoi apply and chezmoi update do not pull. The local nvim checkout always wins over the remote, so in-progress Neovim edits and unpushed commits are never disturbed by an apply.
To update explicitly, run chezmoi apply --refresh-externals (-R), which pulls with git pull --ff-only. --ff-only makes the pull fail instead of creating a merge commit when the local checkout has diverged.
.chezmoiignore.tmpl
Patterns match target paths (relative to $HOME). Matching entries are neither written nor removed. The file is a template, so the desktop block appears only on machines where it applies. Source entries that start with . (.github/, .claude/, .crush/, .gitignore) are ignored by chezmoi without needing a rule.
Repo docs
MIGRATION-NOTES.md
README.md
CLAUDE.md
AGENTS.md
docs
.blume
node_modules
blume.config.ts
package.json
bun.lock
public
dist
Files at the source root that would otherwise land in $HOME as ~/README.md, ~/docs, ~/package.json, and so on. This covers the repo README and agent instruction files, plus this Blume docs site and its build output.
Runtime and plugin state inside managed trees
| Pattern | Why |
|---|---|
.config/lazygit/.omc/** |
Tool state written inside the lazygit config dir |
.config/yazi/plugins/** |
Installed by ya pkg install from package.toml (script) |
.config/tmux/plugins/** |
tpm and the plugins it installs (40-post) |
.config/fish/fish_variables |
fish universal variables, rewritten by fish at runtime |
.config/nvim/lazy-lock.json |
lazy.nvim lockfile inside the nvim external |
.config/nvim/.git, .config/nvim/.git/** |
The external’s git metadata |
.config/nvim/.omc/**, .config/nvim/.crush/**, .config/nvim/.understand-anything/** |
Agent tool state inside the nvim checkout |
.gitignore covers the source side of the same problem: dot_config/lazygit/dot_omc, dot_config/tmux/plugins, and __pycache__/ never get committed even though symlink mode lets tools write into the source tree.
mise
.config/mise
.config/mise/**
mise tool versions stay local to each machine and are not managed.
Linux desktop layer (role-gated)
{{ if or (ne .chezmoi.os "linux") (and (ne .role "desktop") (ne .role "laptop")) }}
.config/niri
.config/niri/**
.config/noctalia
.config/noctalia/**
.config/niri-dynamic-workspaces
.config/niri-dynamic-workspaces/**
.config/fontconfig
.config/fontconfig/**
.config/mimeapps.list
.config/systemd
.config/systemd/**
.local/bin/niri-launch
.config/fish/auto-Niri.fish
.local/bin/cp-appid
.config/tmux/niri-title.sh
{{ end }}
The block is emitted, and these paths skipped, unless the OS is linux and the role is desktop or laptop. mac and server machines, and any non-Linux machine regardless of role, never receive the niri compositor config (dot_config/niri/), fontconfig (dot_config/fontconfig/fonts.conf), default-app associations (dot_config/mimeapps.list), the fish auto-start hook (dot_config/fish/auto-Niri.fish), the tmux window-title helper, or the niri-launch and cp-appid scripts. The noctalia, niri-dynamic-workspaces, and systemd rules match nothing in the current source tree; they only take effect if those configs are added later.
| Machine | Desktop layer |
|---|---|
Linux, desktop or laptop |
Applied |
Linux, server or mac |
Ignored |
| macOS or other OS, any role | Ignored |
See Desktop for what the layer contains. Gaps in the gate: the block lists niri-launch but not its siblings (niri-launch-ai, niri-launch-browser, niri-launch-terminal, niri-launch-webapp), and the Hyprland and waybar configs (dot_config/hypr/, dot_config/waybar/) are not gated at all, so those apply on every role. dot_config/niri/scripts/ is covered by .config/niri/**.
Secrets in runtime state
.config/noctalia/plugins
.config/noctalia/plugins/**
noctalia writes plugin state containing embedded tokens here. These paths are ignored on every machine so tokens are never pulled into the repo.
Python byte-cache
.config/kitty/__pycache__
.config/kitty/__pycache__/**
kitty imports tab_bar.py and the other Python helpers in ~/.config/kitty, and Python writes __pycache__ next to them. In symlink mode that directory would otherwise appear in the source tree.