Files
command-center/omp-docs/research/chezmoi-evaluation.md
T
Frank 02883d3ed2 docs: rebuild KB in OKF v0.1 with live-verified facts
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.
2026-08-28 07:57:13 -07:00

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
dotfiles
chezmoi
configuration-management
secrets
age-encryption
fleet-management

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:


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 Concepts Reference

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 .chezmoiroot containing the relative path to this subdirectory." "For example, given ~/.local/share/chezmoi/.chezmoiroot containing home, then chezmoi will read the source state from the home subdirectory."

— Customize Your Source Directory

The config file location is also configurable:

"The -C, --config-path flag writes the generated config file to path instead of the default location."

— chezmoi init docs

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/template for 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."

— chezmoiignore Reference


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 age command 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 the secret.command configuration variable."

— Secret Functions Reference

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-keyring library."

"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/config always has permissions 0600 then if you create an empty file called dot_kube/private_config in your source state, chezmoi will ensure ~/.kube/config's permissions are 0600 when you run chezmoi apply without changing its contents."

— Manage Different Types of File

Private files are also excluded from chezmoi diff output by default.

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 the executable_, private_, and readonly_ 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/foo will ignore all files beginning with an f in dir except for dir/foo."

— chezmoiignore Reference

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 string chezmoi:modify-template will 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, .keep files, .git dir) doesn't clutter the application dotfiles
  • Standard pattern — many reference repos exist
  • Can use .chezmoiroot to 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 .chezmoiroot pointing 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/chezmoi default 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

  1. 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.

  2. 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.

  3. 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 get to 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)."

  4. 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.

  5. 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.

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:

  1. age encryption for files that must be in git (SSH keys, GPG keys, config files with embedded secrets)
  2. GNOME Keyring (keyring template function) for API tokens and credentials used by applications
  3. gopass as a fallback if GPG infrastructure is already in place

Decision-Relevant Tradeoffs

"[bare git] doesn't support private files, password manager integration, templates, whole-file encryption, file removal, run-once scripts, or shell completion."

— Comparison Table

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."

— Comparison Table

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.json should 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)

chezmoi is an excellent fit for this fleet. The reasons:

  1. 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.kernel variables let templates branch correctly.

  2. Builtin age encryption solves the agent-threat-model problem. No external dependencies needed. SSH keys and config secrets in git are encrypted at rest.

  3. GNOME Keyring integration covers the Linux desktop path. dankware runs CachyOS with GNOME Keyring available.

  4. Single binary distribution. Go binary, no Python dependencies, no root install required — important for the ssh-able remote boxes.

  5. 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"
  1. go install github.com/twpayne/chezmoi/v2@latest (or install from package manager)
  2. chezmoi init — initialize source in ~/.local/share/chezmoi
  3. chezmoi age-keygen -o ~/.config/age/keys.txt — generate age keys
  4. Create age recipient in config and encrypt any secrets
  5. chezmoi add ~/.bashrc ~/.gitconfig ~/.config/ghostty etc.
  6. Use chezmoi add --template for machine-specific configs
  7. Push source repo to GitHub (private repo)
  8. 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-template for 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 .chezmoiignore if they're also committed.