Copy of the legacy ~/.pi/agent/docs tree rebuilt as the command-center knowledge base: concept files promoted to path identity, index listings stripped of frontmatter, all wikilinks/relative links resolved, every checkable claim verified against the live fleet (Dankserver = Ryzen 7 7700X + RTX 3090; Strix Halo is Dankclaw), unverifiable claims tagged. kb-lint exits 0.
21 KiB
type, title, description, resource, timestamp, tags
| type | title | description | resource | timestamp | tags | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| research | Chezmoi Evaluation for Dotfiles Management | Evaluation of chezmoi (v2.72.0) as a dotfiles management tool for the dankware fleet. | https://github.com/twpayne/chezmoi | 2026-08-28 |
|
Chezmoi Evaluation for Dotfiles Management
Overview
chezmoi is a Go-binary (MIT license, 21k+ GitHub stars) that manages dotfiles across diverse machines by declaring desired state in a source directory and applying the delta to the target directory. It is the only dotfiles tool in the dotfiles comparison table that provides private files, whole-file encryption, password-manager integration, template-based machine-to-machine differences, partial-file management, and symlink differences — all in a single distribution.
The current release is v2.72.0 (2026-08-02). It is not yet installed on this machine.
Canonical references:
- Source: https://github.com/twpayne/chezmoi
- Docs: https://www.chezmoi.io
How Chezmoi Works
Core Concepts
"The source directory is where chezmoi stores the source state. By default it is
~/.local/share/chezmoi."
"The config file contains machine-specific data. By default it is
~/.config/chezmoi/chezmoi.toml."
"The target state is the desired state of the destination directory. It is computed from the source state, the config file, and the destination state."
chezmoi operates in three phases: read source state (files, templates, metadata), read config file (machine-specific data block), compute target state, then make minimal filesystem changes.
Source Directory Customization
chezmoi defaults to ~/.local/share/chezmoi as its source directory, but this is fully customizable:
"By default, chezmoi uses the root of your dotfiles repo as the root of the source state."
— Customize Your Source Directory
For the proposed setup where ~/.omp IS the central config repo, chezmoi can read the source state from a subdirectory using a .chezmoiroot file:
"You can specify that chezmoi should read the source state from a subdirectory of the source directory instead by creating a file called
.chezmoirootcontaining the relative path to this subdirectory." "For example, given~/.local/share/chezmoi/.chezmoirootcontaininghome, then chezmoi will read the source state from thehomesubdirectory."
— Customize Your Source Directory
The config file location is also configurable:
"The
-C,--config-pathflag writes the generated config file to path instead of the default location."
This means chezmoi can live entirely within ~/.omp with minimal friction.
Template System for Per-Machine Differences
.toml Configuration + .toml.chezmoi Templates
Config is stored as ~/.config/chezmoi/chezmoi.toml (also supports YAML, JSON, JSONC):
[data]
email = "me@home.org"
Files that need per-machine variation are added with --template, creating .filename.tmpl in the source state. Templates use Go's text/template with the Sprig function library:
"chezmoi achieves this functionality by using
text/templatefor the source state where needed."
"If, after executing the template, the file contents are empty, the target file will be removed. This can be used to ensure that files are only present on certain machines."
— Manage Machine-to-Machine Differences
Available Variables
chezmoi auto-populates machine identity variables including .chezmoi.hostname, .chezmoi.os, .chezmoi.arch, .chezmoi.homeDir, .chezmoi.sourceDir, .chezmoi.fqdnHostname, .chezmoi.osRelease (Linux distro info), and .chezmoi.kernel. Custom variables are defined in the config data block.
"For a full list of variables, run:
chezmoi data"
— Manage Machine-to-Machine Differences
Conditional Ignoring with .chezmoiignore
For coarser-grained per-machine file control:
".chezmoiignore is interpreted as a template, whether or not it has a .tmpl extension. This allows different files to be ignored on different machines."
Secrets Integration
Age Encryption (Built-In)
chezmoi has builtin age encryption — no external age binary required:
"chezmoi has builtin support for age encryption which is automatically used if the
agecommand is not found in$PATH."
Configuration in the config file:
encryption = "age"
[age]
identity = "/home/user/key.txt"
recipient = "age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p"
Multiple identities and recipients are supported. Files are encrypted in the source state (git-stored) and decrypted on-the-fly during apply/diff/status.
Caveat: "The builtin age encryption does not support passphrases, symmetric encryption, or SSH keys." — this is by design, as the docs note: "chezmoi needs to decrypt files regularly ... not just when running chezmoi apply. Prompting for a passphrase each time would quickly become tiresome."
Key generation: chezmoi age-keygen -o identity.txt
Password Manager Template Functions
chezmoi provides template functions for retrieving secrets from:
| Backend | Template Functions | Notes |
|---|---|---|
| 1Password | onepassword, onepasswordRead, onepasswordDocument |
Full item, field, and document retrieval |
| Bitwarden | bitwarden, bitwardenFields, bitwardenAttachment, bitwardenSecrets, rbw |
CLI and API modes |
| gopass | gopass, gopassCat, gopassRaw |
Uses gopass show CLI |
| pass | pass, passFields, passRaw |
Uses pass show CLI |
| Keyring | keyring |
Uses zalando/go-keyring — integrates with GNOME Keyring on Linux, macOS Keychain, Windows CredMgr |
| Vault | vault |
HashiCorp Vault |
| Dashlane | dashlanePassword, dashlaneNote |
|
| Keeper | keeper, keeperDataFields, keeperFindPassword |
|
| KeepassXC | keepassxc, keepassxcAttribute, keepassxcAttachment |
|
| LastPass | lastpass, lastpassRaw |
|
| Proton Pass | protonPass, protonPassJSON |
|
| Doppler | doppler, dopplerProjectJson |
|
| AWS Secrets Manager | awsSecretsManager, awsSecretsManagerRaw |
|
| Azure Key Vault | azureKeyVault |
|
| EJSON | ejsonDecrypt, ejsonDecryptWithKey |
Etcd EJSON format |
"Using a password manager with chezmoi enables you to maintain a public dotfiles repository while keeping your secrets secure."
"When chezmoi applies a template with a secret referenced from a password manager, it will automatically fetch the secret value and insert it into the generated destination file."
— Password Manager Integration
Generic Secret Command
"The
secret*template functions return the output of the generic secret command defined by thesecret.commandconfiguration variable."
This allows plugging in any custom secrets backend.
Keyring for GNOME Keyring (Linux)
"chezmoi includes support for Keychain (on macOS), GNOME Keyring (on Linux and FreeBSD), and Windows Credentials Manager (on Windows) via the
zalando/go-keyringlibrary."
"Set values with:
chezmoi secret keyring set --service=$SERVICE --user=$USER"
— Keychain and Windows Credentials Manager
This is relevant for dankware — CachyOS uses GNOME Keyring (KDE uses kwallet, but the system has GNOME components). On the Strix Halo (Arch Linux) and RTX 3090 (FreeToken) boxes, GNOME Keyring or kwallet would be available depending on desktop environment.
File Management Modes
Private Files
Files with a private_ prefix in the source state get mode 0600 on the target:
"For example, if you want to ensure that
~/.kube/configalways has permissions 0600 then if you create an empty file calleddot_kube/private_configin your source state, chezmoi will ensure~/.kube/config's permissions are 0600 when you runchezmoi applywithout changing its contents."
— Manage Different Types of File
Private files are also excluded from chezmoi diff output by default.
Symlink vs Managed-File Modes
chezmoi defaults to managed-file mode: it copies contents to the target. But it also supports symlinks:
"You can track changes to these files by replacing with a symlink back to a file in your source directory, which is under version control."
The create_ attribute controls symlinks for files that should exist but whose contents shouldn't be managed — useful for letting applications edit their own config:
"chezmoi's
create_attributes allows you to tell chezmoi to create a file if it does not already exist. chezmoi, however, will apply any permission changes from theexecutable_,private_, andreadonly_attributes."
— Manage Different Types of File
Ignoring Files
.chezmoiignore handles patterns:
".chezmoiignore files in source state subdirectories apply only to that subdirectory."
"Patterns can be excluded by starting the line with a
!, for example:dir/f*then!dir/foowill ignore all files beginning with anfindirexcept fordir/foo."
Modify Scripts
For partial file management, modify_ scripts transform file contents in-place:
"A
modify_script receives the current contents of the file on the standard input and chezmoi reads the target contents of the file from the script's standard output."
"
modify_scripts that contain the stringchezmoi:modify-templatewill have all lines containing that string removed, and the rest of the file will be interpreted as a template."
— Manage Different Types of File
chezmoi-in-Repo vs Separate chezmoi Repo
Option A: Separate chezmoi repo (traditional)
chezmoi traditionally uses ~/.local/share/chezmoi as its source directory, typically initialized from a separate GitHub repo (e.g., github.com/dank/dotfiles). This is the most common pattern and is well-documented.
Pros:
- Clean separation between config management tool state and application configs
- chezmoi's internal state (
.chezmoi.json,.chezmoiremove,.keepfiles,.gitdir) doesn't clutter the application dotfiles - Standard pattern — many reference repos exist
- Can use
.chezmoirootto point to a subdirectory within a separate repo
Cons:
- Two git repos to manage locally (one for chezmoi source, one for the dotfiles app configs)
- Cross-repo workflow overhead
Option B: chezmoi inside ~/.omp (proposed)
Since ~/.omp is already the central config command center, chezmoi could use it directly:
- Set
sourceDir = "/home/dank/.omp/dotfiles"in config - Or create
.chezmoirootpointing to a subdirectory
Pros:
- Single repo for everything — config center, dotfiles, infrastructure-as-code, agent config
- Simpler deployment: one clone, one
chezmoi apply - ~/.omp already hosts models.json, models.yml, agent config, and is git-tracked
Cons:
- chezmoi's internal state files would be mixed into the dotfiles repo
- The
.local/share/chezmoidefault wouldn't apply — config and source both need to live inside ~/.omp - chezmoi's auto-commit/push hooks would modify ~/.omp, which is the orchestrator repo
- Need to be careful not to have circular dependencies: agent config (in ~/.omp) → models.json → model downloads → agent execution
Recommendation
Option A is strongly preferred for the initial deployment. The separation of concerns is worth the minor operational overhead. The ~/.omp repo can still use chezmoi's templating via execute-template for generating agent config from machine-specific data, while a separate chezmoi repo handles the dotfiles themselves. A .chezmoiroot inside the separate repo can organize files cleanly (e.g., home/dot_bashrc, home/dot_gitconfig, data/hostname-specific.yml).
The two-repo pattern is supported natively and well-documented. chezmoi's docs explicitly recommend it: "By default, chezmoi uses the root of your dotfiles repo as the root of the source state." — implying the standard pattern is a dedicated repo.
Secrets Under an AGENTS-in-the-Loop Threat Model
The threat model is distinctive: agents (Claude Code, OMP) can read any plaintext file on the system. The secrecy boundary is not humans — it is the tool ecosystem.
Implications
-
Encryption-at-rest is mandatory. Secrets stored in git (even private repos) must be encrypted because any compromised agent session, any script that dumps file contents, or any log file leak would expose them. chezmoi's builtin age encryption solves this: files are encrypted in the source repo and decrypted at apply-time.
-
In-memory secrets are acceptable. If a secret flows through a password manager's API at apply time and lands only in the decrypted target file (not in git), the threat is reduced. The target file is only plaintext on the local machine where the agent runs — and the agent running there is the one that legitimately needs the secret.
-
Keyring is the best Linux backend for this fleet. GNOME Keyring stores secrets encrypted in-process; the agent process would need to invoke
chezmoi secret keyring getto retrieve them, which means they transit through the CLI but never land in git. On dankware (CachyOS + niri), GNOME Keyring is the default. On the two ssh-able boxes, keyring support exists: "chezmoi includes support for GNOME Keyring (on Linux and FreeBSD)." -
gopass is a strong secondary option. If gpg-agent is set up on all machines, gopass's git-backed encrypted store is well-suited. The template function
{{ gopass "$PASS_NAME" }}retrieves secrets at apply time without ever storing them in the source repo. -
age symmetric encryption has a gap. Builtin symmetric mode with a passphrase "is not supported because chezmoi needs to decrypt files regularly" — this is a real limitation for agents that can't interactively type passphrases. Use asymmetric age (key-based) instead.
Recommended Secrets Architecture
Source repo (git-stored) Target filesystem
───────────────────── ──────────────────
private_dot_gpg/age/ ───(age decrypt)──▶ ~/.gnupg/
private_config.txt.enc
private_dot_ssh/id_rsa.enc
template files (.tmpl) ───(gopass/bitwarden)──▶ ~/.config/app/secrets.yml
CF_API_TOKEN='{{ gopass "cloudflare/api-token" }}'
Priority order for this fleet:
- age encryption for files that must be in git (SSH keys, GPG keys, config files with embedded secrets)
- GNOME Keyring (
keyringtemplate function) for API tokens and credentials used by applications - gopass as a fallback if GPG infrastructure is already in place
Decision-Relevant Tradeoffs
vs Bare Git Symlinks
"[bare git] doesn't support private files, password manager integration, templates, whole-file encryption, file removal, run-once scripts, or shell completion."
chezmoi provides a significantly richer feature set for the same cost of a single binary install.
vs yadm
"[yadm] supports private files and whole-file encryption, but lacks password manager integration, password manager template functions, and has limited template support."
Symlink Mode
Symlinks work well when the source of truth IS a specific path (e.g., VSCode settings stored in the chezmoi repo). The docs provide a concrete example:
"Copy the configuration file to your source directory:
cp ~/.config/Code/User/settings.json $(chezmoi source-path)" "Tell chezmoi to ignore this file:echo settings.json >> $(chezmoi source-path)/.chezmoiignore" "Tell chezmoi that~/.config/Code/User/settings.jsonshould be a symlink to the file in your source directory"
— Manage Different Types of File
Use symlinks for configs that external programs modify and must write back to (VSCode settings.json, Firefox prefs.js). Use managed-file mode for configs that are source-of-truth in the repo.
.chezmoiignore vs .gitignore
.chezmoiignore controls chezmoi's apply behavior; .gitignore controls git. They serve orthogonal purposes. A file can be ignored by chezmoi but tracked by git (for documentation, templates, or machine-exclusion), or vice versa.
Recommendation for This Fleet
Setup
- Machine fleet: dankware (Intel Ultra 7, CachyOS, niri), dankclaw (Strix Halo, llama.cpp frank-default-large on port 7770), dankserver (RTX 3090, FreeToken, frank-default-small)
- Central config repo:
~/.omp— already a git repo with agent config, model manifests, and the KB - OS mix: All Linux, but different distros (CachyOS, Arch, custom FreeToken)
Verdict: Recommended with moderate reservations
chezmoi is an excellent fit for this fleet. The reasons:
-
Template system handles the heterogeneity. The three machines have different CPUs (Ultra 7 vs Strix Halo vs 3090), different distros, and different use cases.
.chezmoi.hostname,.chezmoi.osRelease, and.chezmoi.kernelvariables let templates branch correctly. -
Builtin age encryption solves the agent-threat-model problem. No external dependencies needed. SSH keys and config secrets in git are encrypted at rest.
-
GNOME Keyring integration covers the Linux desktop path. dankware runs CachyOS with GNOME Keyring available.
-
Single binary distribution. Go binary, no Python dependencies, no root install required — important for the ssh-able remote boxes.
-
Well above the competition. Every alternative (bare git, yadm, vcsh, rcm, dotbot) lacks one or more of: private files, encryption, password managers, template variables, or per-machine conditional ignores.
Proposed Configuration
# ~/.config/chezmoi/chezmoi.toml
encryption = "age"
[age]
identity = "/home/dank/.config/age/keys.txt"
recipient = "age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p"
[git]
autoAdd = false
autoCommit = false
autoPush = false
[update]
strategy = "always"
[sourcePath]
[sourcePath.home]
target = "/home/dank/.local/share/chezmoi"
Recommended Initial Steps
go install github.com/twpayne/chezmoi/v2@latest(or install from package manager)chezmoi init— initialize source in~/.local/share/chezmoichezmoi age-keygen -o ~/.config/age/keys.txt— generate age keys- Create age recipient in config and encrypt any secrets
chezmoi add ~/.bashrc ~/.gitconfig ~/.config/ghosttyetc.- Use
chezmoi add --templatefor machine-specific configs - Push source repo to GitHub (private repo)
- On remote boxes:
chezmoi init --apply dankware/dotfiles
Risks and Mitigations
- Builtin age doesn't support passphrases. Mitigation: use key-based (asymmetric) age, not passphrase-based symmetric.
- chezmoi + ~/.omp overlap. Mitigation: keep them separate (Option A). ~/.omp can
execute-templatefor simple variable interpolation in agent config, but let a dedicated chezmoi repo own the dotfiles. - Agent can still read decrypted targets. This is inherent to the threat model — the agent that needs the secret also needs to read it. Mitigation: use short-lived tokens where possible, and keep age private keys in
.chezmoiignoreif they're also committed.