Skip to content
Registry

Registry

The registry is dothaven’s single declarative source of truth for the config files and directories it knows about. Every entry is one Go struct in internal/registry/registry.go. There is no config file to edit and no plugin system — the registry is compiled into the binary, and both collect and backup are driven by the same list. Add or change an entry once, and discovery, auditing, and copying all follow.

The entry model

Each registered source is an Entry:

type Entry struct {
	ID          string
	Name        string
	Paths       map[string]string // keyed by GOOS: "darwin", "linux", "windows"
	Category    string
	Kind        Kind
	Fields      []string          // JSONExtract only (empty = all keys)
	BackupDest  string
	Sensitivity Sensitivity
	Redact      func(string) string
}
  • ID — stable section key in snapshots (e.g. shell.zshrc).
  • Name — human-readable label.
  • Paths — per-OS path templates (see Paths and ~ expansion).
  • Category — grouping used by --only / --skip and in summaries.
  • Kind — how the source is read (see Entry kinds).
  • Fields — for JSONExtract, which top-level keys to pull (empty = all).
  • BackupDest — relative destination path inside a backup tree.
  • Sensitivitylow, medium, or high (see Sensitivity levels).
  • Redact — optional content scrubber applied when redaction is on.

Entry kinds

Kind decides how Collect turns a path into a snapshot section. There are four:

KindReadsSnapshot section produced
FileWhole file contentContent — the trimmed file text (redacted if a Redact rule applies)
FileMetadataFile, but not its contentPairsexists: true and lines: <count> only
DirDirectory listingItems — one row per entry name, sorted
JSONExtractJSON file, selected fieldsPairs — key/value pairs from the chosen top-level keys

The mapping to snapshot shapes (Content, Pairs, Items) comes straight from Collect in registry.go:

  • File reads the file, optionally runs the Redact function when redaction is enabled, trims surrounding whitespace, and stores the result as the section’s Content.
  • FileMetadata reads the file only to count lines. It never stores the content — the section is just {exists: "true", lines: "<n>"}. This is how a large generated file like .p10k.zsh is recorded without dragging its body into the snapshot.
  • Dir lists the directory, sorts the names, and emits one Item per name. An empty or unreadable directory is skipped entirely.
  • JSONExtract parses the file as JSON and pulls the keys named in Fields. If a selected field is itself an object, its inner keys are flattened into the pair set; otherwise the field’s scalar value is stored. An empty Fields means “extract every top-level key.”

Any source that does not exist on disk (or fails to read/parse) is silently skipped, so the registry can list more than any one machine has.

Sensitivity levels

Every entry carries a Sensitivity of low, medium, or high. This is a classification of how dangerous the file’s contents are if they leak — it documents intent and drives how you should treat each entry, especially when exporting to chezmoi for age-encryption.

LevelMeaningExamples in the registry
lowSafe to read and share; no secrets expectedshell rc files, .gitconfig, editor settings, .tmux.conf
mediumMay contain identifying or environment detail~/.ssh/config, AWS CLI config, gcloud configurations
highHolds credentials or private key material~/.npmrc, AWS credentials, kubeconfig, Docker config, GnuPG home
Sensitivity drives real behavior, not just labeling. Entries with a Redact rule have their content scrubbed during collect/backup. A high-sensitivity entry with no redactor (e.g. AWS credentials, the GnuPG home) is excluded from a plaintext backup — content scanning can’t be trusted to catch every secret (binary key material has no signature). Carry those with chezmoi-export, which age-encrypts them at rest.

Paths and ~ expansion

Paths is a map keyed by Go’s runtime.GOOS"darwin", "linux", or "windows". ResolvePath picks the template for the current OS and expands it:

func ResolvePath(e Entry, home string) string {
	tmpl, ok := e.Paths[runtime.GOOS]
	if !ok {
		return "" // entry not applicable on this platform
	}
	return strings.Replace(tmpl, "~", home, 1)
}

Two rules follow from this:

  • Leading ~ is replaced by the home directory (first occurrence only).
  • No entry for the current OS means an empty path, and the entry is skipped by both Collect and BackupTargets. That is why some entries (shell rc files, .p10k.zsh, GnuPG, gcloud) define only darwin and linux — they are simply absent on Windows.

Windows templates use %USERPROFILE% and %APPDATA% literally; these are not shell-expanded by ResolvePath (it only substitutes ~).

The Redact rule

Redact is an optional func(string) string that scrubs a File entry’s content before it is stored. It runs only for Kind: File, and only when redaction is enabled (the default; disabled with --no-redact). Two registry entries set one today, both from internal/scan:

  • ssh.config uses RedactSSHConfig, which replaces HostName and IdentityFile values with [REDACTED] while keeping the file’s structure.
  • npm.config uses RedactNpmTokens, which replaces the value after _authToken= with [REDACTED].

These are structure-preserving: the keys and layout survive so the redacted file still reads as a valid config, only the secret value is masked.

Registered entries

The registry currently declares around 130 entries across 18 categories — including a lang category for per-language toolchain config (Ruby, Python, Go, Rust, PHP, .NET, JS, Elixir, Julia) and broad cloud/DevOps, git-ecosystem, and database-client coverage. The lists below are grouped by category; large categories show the notable entries and end with “and more.” The path shown is the macOS/Linux (~-relative) template; Windows templates differ where defined and some entries are macOS/Linux-only.

Every credential-bearing entry is classified high. On a chezmoi export those are age-encrypted at rest, and because they carry no Redact rule they are excluded from a plaintext backup entirely.

ai

AI assistant configs, skills, and project-memory files for Claude, Cursor, Gemini, and Windsurf.

IDNamePathKindSensitivity
ai.claude.settingsClaude Settings~/.claude/settings.jsonJSONExtractlow
ai.claude.skillsClaude Skills~/.claude/skillsDirlow
ai.claude.mdCLAUDE.md~/.claude/CLAUDE.mdFilelow
ai.cursor.mcpCursor MCP Config~/.cursor/mcp.jsonFilelow
ai.gemini.settingsGemini Settings~/.gemini/settings.jsonJSONExtractlow
ai.gemini.mdGEMINI.md~/.gemini/GEMINI.mdFilelow
ai.windsurf.mcpWindsurf MCP Config~/.codeium/windsurf/mcp_config.jsonFilelow

…and the matching skills directories for Cursor, Gemini, and Windsurf.

The Claude settings entry extracts only the permissions and enabledPlugins fields; the Gemini settings entry extracts all top-level keys.

shell

macOS/Linux only.

IDNamePathKindSensitivity
shell.zshrc.zshrc~/.zshrcFilelow
shell.zprofile.zprofile~/.zprofileFilelow
shell.bashrc.bashrc~/.bashrcFilelow
shell.profile.profile~/.profileFilelow
shell.fishFish Config~/.config/fishDirlow
shell.nushellNushell Config~/.config/nushellDirlow

…and shell.zshenv, shell.bash_profile, and shell.inputrc.

git

IDNamePathKindSensitivity
git.config.gitconfig~/.gitconfigFilelow
git.ignore.gitignore_global~/.gitignore_globalFilelow
git.attributes.gitattributes_global~/.gitattributes_globalFilelow
gh.configGitHub CLI Config~/.config/gh/config.ymlFilelow

editor

IDNamePathKindSensitivity
editor.zedZed Settings~/.config/zed/settings.jsonFilelow
editor.cursorCursor Settings~/Library/Application Support/Cursor/User/settings.jsonFilelow
editor.nvimNeovim Config~/.config/nvimDirlow
editor.vscode.settingsVS Code Settings~/Library/Application Support/Code/User/settings.jsonFilelow
editor.helixHelix Config~/.config/helixDirlow
editor.editorconfig.editorconfig~/.editorconfigFilelow

…and editor.vimrc, VS Code keybindings/snippets, Doom Emacs, and Sublime Text.

The Cursor settings path differs by OS: ~/.config/Cursor/User/settings.json on Linux and %APPDATA%/Cursor/User/settings.json on Windows.

terminal

macOS/Linux only.

IDNamePathKindSensitivity
terminal.p10k.p10k.zsh~/.p10k.zshFileMetadatalow
terminal.tmux.tmux.conf~/.tmux.confFilelow
terminal.starshipStarship prompt~/.config/starship.tomlFilelow
terminal.ghosttyGhostty~/.config/ghostty/configFilelow

…and Alacritty, Kitty, and WezTerm.

.p10k.zsh is recorded as metadata only (exists + lines), not content.

ssh

IDNamePathKindSensitivityRedact
ssh.configSSH Config~/.ssh/configFilemediumRedactSSHConfig

npm

IDNamePathKindSensitivityRedact
npm.config.npmrc~/.npmrcFilehighRedactNpmTokens

.npmrc is the one high entry with a redactor: its _authToken value is masked in plaintext output, so it is the exception that can be backed up scrubbed.

bun

IDNamePathKindSensitivity
bun.config.bunfig.toml~/.bunfig.tomlFilelow

cloud

Cloud and PaaS CLI configs. The config-style files are medium; anything that stores auth tokens or keys is high (age-encrypted on export, never in a plaintext backup).

IDNamePathKindSensitivity
cloud.aws.configAWS CLI config~/.aws/configFilemedium
cloud.aws.credentialsAWS CLI credentials~/.aws/credentialsFilehigh
cloud.gcloud.configurationsgcloud configurations~/.config/gcloud/configurationsDirmedium
cloud.kube.configkubeconfig~/.kube/configFilehigh
cloud.docker.configDocker config~/.docker/config.jsonFilehigh
cloud.vercelVercel CLI~/Library/Application Support/com.vercel.cli/auth.jsonFilehigh
cloud.stripeStripe CLI~/.config/stripe/config.tomlFilehigh

…and Azure, OCI, DigitalOcean, Fly.io, Linode, Hetzner, Netlify, Supabase, Railway, Terraform Cloud, Pulumi, and Cloudflared — all high.

gcloud configurations and most token-bearing entries are macOS/Linux only.

devops

IDNamePathKindSensitivity
devops.helmHelm repositories~/.config/helm/repositories.yamlFilemedium
devops.k9sk9s config~/.config/k9s/config.yamlFilelow
devops.colimaColima config~/.colima/default/colima.yamlFilelow
devops.podmanPodman config~/.config/containersDirmedium

macOS/Linux only.

build

Build tools that may hold repository credentials, so both are high.

IDNamePathKindSensitivity
build.mavenMaven settings~/.m2/settings.xmlFilehigh
build.gradleGradle properties~/.gradle/gradle.propertiesFilehigh

db

IDNamePathKindSensitivity
db.pgpass.pgpass~/.pgpassFilehigh
db.mycnf.my.cnf~/.my.cnfFilehigh
db.psqlrc.psqlrc~/.psqlrcFilelow
db.sqliterc.sqliterc~/.sqlitercFilelow

.pgpass and .my.cnf carry DB passwords, so both are high.

net

macOS/Linux only.

IDNamePathKindSensitivity
net.curlrc.curlrc~/.curlrcFilemedium
net.wgetrc.wgetrc~/.wgetrcFilemedium

dev

IDNamePathKindSensitivity
dev.direnvdirenv~/.config/direnvDirlow

macOS/Linux only.

apps

IDNamePathKindSensitivity
apps.karabinerKarabiner~/.config/karabiner/karabiner.jsonFilelow

macOS only.

vm

Version-manager declarative config (live installed versions come from collectors). macOS/Linux only.

IDNamePathKindSensitivity
vm.tool-versions.tool-versions~/.tool-versionsFilelow
vm.nvmrc.nvmrc~/.nvmrcFilelow
vm.misemise config~/.config/mise/config.tomlFilelow
vm.asdfrc.asdfrc~/.asdfrcFilelow

secrets

Bare credential stores. All high: age-encrypted on chezmoi export and never written to a plaintext backup. macOS/Linux only.

IDNamePathKindSensitivity
secrets.netrc.netrc~/.netrcFilehigh
secrets.vaultVault token~/.vault-tokenFilehigh
secrets.gnupgGnuPG home~/.gnupgDirhigh

The GnuPG entry is declarative: it is a no-op until ~/.gnupg holds real keys.

How the registry feeds collect and backup

The same Entries slice drives two different projections.

Collect

collect calls registry.Collect(env, home, redact, registry.Entries). For each entry it resolves the path, skips it if empty or missing, and reads it according to its Kind into a snapshot section. Redaction (on by default) applies a File entry’s Redact rule before the content is stored:

dothaven collect

Pass --no-redact to keep raw values:

dothaven collect --no-redact

Backup

backup and restore both consume registry.BackupTargets(home, entries), which is the single projection of the registry into copy operations. For every entry that has a path on the current platform it produces a BackupTarget:

type BackupTarget struct {
	Src      string             // resolved live path
	Dest     string             // entry's BackupDest, relative to the backup tree
	Category string
	IsDir    bool               // true when Kind == Dir
	Redact   func(string) string
}

Src is the resolved live path, Dest is the entry’s BackupDest, IsDir reflects whether the kind is Dir, and Redact carries the same optional scrubber. Because backup and restore read from one projection, a file always maps back to the live path it came from.

dothaven backup

Backups honor the same redaction default and accept category filters that match the entry Category field:

dothaven backup --only shell,git
dothaven backup --skip cloud,secrets
dothaven backup --archive          # write a .tar.gz instead of a directory
dothaven backup --no-redact        # keep raw values

The output directory follows dothaven’s standard resolution: an explicit -o wins; otherwise <cwd>/reports when run inside a git repo, else ~/.local/share/dothaven.

Sensitive entries are best carried through the hybrid model: dothaven discovers, audits, and exports them; chezmoi stores them and encrypts with age. Losing the age key means those encrypted files are unrecoverable, so back the key up separately.

Missing a tool?

dothaven aims to be a superset of what chezmoi covers. If a config or CLI you use isn’t in the registry yet, adding it is usually a one-line entry — open a request with the tool name and its config path:

Related