Externals are content chezmoi fetches instead of storing in the repo. Every entry is a `git-repo` external: 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` | [prdlk/nvim](https://github.com/prdlk/nvim) | Neovim config, kept in its own repo |
| `~/.config/bat/syntaxes/sublime-purescript-syntax` | [tellnobody1/sublime-purescript-syntax](https://github.com/tellnobody1/sublime-purescript-syntax) | PureScript syntax for bat, compiled into bat's cache by the [bat cache scripts](/chezmoi/scripts#build-bat-cache) |
| `~/.config/tmux/plugins/tpm` | [tmux-plugins/tpm](https://github.com/tmux-plugins/tpm) | tmux plugin manager, run from the bottom of `tmux.conf` ([tmux](/terminal/tmux)). Cloned inside the tmux checkout, whose `.gitignore` excludes `plugins/` |
No entry in `.chezmoiexternal.toml` 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.
Per-app config repos, one file each in `.chezmoiexternals/<app>.toml`. Each is a `git-repo` external with `refreshPeriod = "168h"` and `pull.args = ["--ff-only"]`, so `chezmoi apply` fast-forwards them at most once a week. The repos hold plain config files (no chezmoi prefixes) and are not part of `chezmoi managed` contents.
| `omarchy.toml` | `~/.config/omarchy` | [prdlk/omarchy](https://github.com/prdlk/omarchy) — `shell.json`, hooks, menu extensions, themed templates; git-installed themes/plugins are gitignored there and restored by its `install-extras` script, `calendars.json` is never committed |
| `noctalia.toml` | `~/.config/noctalia` | [prdlk/noctalia](https://github.com/prdlk/noctalia) — hand-written `config.toml` (shell, screenshot folder, theme, plugins, night light, notifications, OSD, wallpaper), `bar.toml` (bar layout, per-monitor overrides, widgets), and `palettes/` (custom palette JSON); merged under the GUI's unmanaged `~/.local/state/noctalia/settings.toml`, which wins on conflicts; `plugins/` holds runtime state with embedded tokens and is gitignored there. Linux desktop/laptop roles only ([role gate](#chezmoiignore)) |
Edit these in place under `~/.config/<app>`, then commit and push from that checkout.
## Submodules [#submodules]
Three source directories are git submodules, listed in `.gitmodules`. They keep chezmoi source naming and are applied in symlink mode like the rest of the tree. `chezmoi init` clones them.
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
```txt
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 |
`.gitignore` covers the source side of the same problem: `dot_config/lazygit/dot_omc` and `__pycache__/` never get committed even though symlink mode lets tools write into the source tree.
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, Umbriel, and Hyprland configs (`dot_config/niri/`, `dot_config/umbriel/`, `dot_config/hypr/`), the Noctalia external (`~/.config/noctalia`, never cloned there), 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 systemd user units (`dot_config/systemd/user/kitty-day-save.service`, `projects-sync.service`, `projects-sync.timer`), or the `niri-launch`, `umbriel-launch`, `umbriel-smart-move`, `cp-appid`, `kitty-day-save` and `projects-sync` scripts. The `niri-dynamic-workspaces` rule matches nothing in the current source tree; it only takes effect if that config is added later.
See [Desktop](/desktop/niri) 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 waybar config (`dot_config/waybar/`) is not gated at all, so it applies on every role. `dot_config/niri/scripts/` is covered by `.config/niri/**`.
Noctalia's umbriel template rewrites this file on every theme change, so it stays out of the repo on every machine. `config.toml` includes it as an optional file; see [Umbriel](/desktop/umbriel#theme).