关于 chezmoi
面向使用 chezmoi 管理多台机器配置文件的开发者,提供从初始化、添加文件到安全应用变更的操作指导。内容重点包括 Go 模板、加密、文件属性和应用前检查。
主要功能
- 01跨机器管理配置文件
- 02使用 Go 模板按环境生成文件
- 03通过属性控制文件行为与权限
- 04加密存储敏感文件
- 05运行一次性或变更触发脚本
- 06应用前检查差异与执行计划
适用场景
- 01在新机器上初始化配置文件
- 02按操作系统或主机定制配置
- 03安全纳入令牌等秘密
- 04排查配置应用造成的意外更改
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ paulnsorensen/ skillz-that-grillz --skill chezmoi安装后在 Claude Code 中输入/chezmoi确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/bootstrap.mdpitfalls.mdscripts.mdsecrets.mdtemplating.md详细介绍
chezmoi
封装 chezmoi — 使用源状态语义、Go 模板和静态加密,在多台机器间管理 dotfiles。
适用场景
- 在新机器上初始化 dotfiles
- 向现有的 chezmoi 管理的 dotfiles 添加新文件
- 按操作系统、主机或角色(工作与个人)为 dotfile 创建模板
- 通过 1Password / Bitwarden / age / gpg 添加密钥
- 编写
run_once_安装脚本 - 排查“chezmoi apply 改了我没想到会改的内容”
- 为一组机器设计
.chezmoi.toml.tmpl配置模板
基本概念
chezmoi 将源目录(~/.local/share/chezmoi,一个 git 仓库)映射到
目标目录(用户的主目录)。源文件名包含决定目标名称和行为的属性前缀。带有 .tmpl 后缀的源文件会作为 Go text/template 进行渲染,并写入对应的目标路径。.chezmoitemplates/ 下的文件是可供其他模板包含的可复用模板片段,不会自行生成目标文件。位于 ~/.config/chezmoi/chezmoi.toml 的配置文件(通常在首次 init 时由 .chezmoi.toml.tmpl 生成)保存每台机器各自的变量。
人们遇到问题通常有两个原因:
- 他们直接编辑
~/.local/share/chezmoi/下的文件,而不使用chezmoi edit $TARGET。这会破坏模板和解密往返过程。 - 他们因为“仓库是私有的”就以明文提交密钥。仓库会泄露,fork 会泄露,历史记录会永久留存。请使用密钥后端。
配置文件的位置
有两个容易混淆的文件。两者都可以使用 toml、yaml、json 或
jsonc 格式(根据扩展名自动检测;同时存在两种格式会报错,而不是“优先使用第一个”):
| 文件 | 路径 | 创建者 |
|---|---|---|
chezmoi.<fmt>(配置) | $XDG_CONFIG_HOME/chezmoi/chezmoi.<fmt>,默认为 ~/.config/chezmoi/chezmoi.<fmt>(最常见的是 .toml)。不在源仓库中。 | 由 chezmoi init(或 apply --init)根据下方模板生成。按机器区分。保存密钥、API 令牌、机器专属设置 — 永远不要提交。 |
.chezmoi.<fmt>.tmpl(配置模板) | 源状态根目录:~/.local/share/chezmoi/.chezmoi.<fmt>.tmpl(最常见的是 .toml)。提交到 dotfiles 仓库。 | 由你编写。使用 promptBoolOnce / promptChoiceOnce / promptStringOnce,以便新机器在首次 init 时生成正确的配置。 |
使用 --config <path> 覆盖位置(如果扩展名不常见,请使用
--config-format <fmt>)。通过 chezmoi cat-config 和 chezmoi dump-config 查看生效的配置值。
协议
1. 确认用户的需求
| 用户说 | 转到 |
|---|---|
| “从头设置 chezmoi” / “新机器” | §2 初始化 |
| “把这个文件添加到我的 dotfiles” | §3 添加文件 |
| “让这个 dotfile 根据操作系统 / 主机变化” | references/templating.md |
| “加密这个” / “存储这个令牌” | references/secrets.md |
| “首次 apply 时运行脚本” | references/scripts.md |
| “chezmoi 做了奇怪的事” | references/pitfalls.md |
| “设计我的完整配置” | references/bootstrap.md |
2. 初始化(新机器)
一行命令即可完成克隆、渲染模板和应用:
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply $GITHUB_USERNAME
有关完整的 .chezmoi.toml.tmpl 配置方案,其中使用 promptBoolOnce 区分工作、个人和无头环境,请参阅 references/bootstrap.md。
3. 添加文件
chezmoi add ~/.zshrc # plain copy
chezmoi add --template ~/.gitconfig # add as a template (.tmpl suffix added)
chezmoi add --encrypt ~/.ssh/config # encrypted at rest
chezmoi chattr +template ~/.zshrc # turn an existing managed file into a template
chezmoi edit ~/.zshrc # edit the SOURCE — handles decrypt + .tmpl correctly
然后执行安全应用流程(§5)。
4. 文件命名参考
这是用户最容易弄错的表格。前缀必须按特定顺序排列,且允许使用的前缀集合取决于目标类型——
/reference/source-state-attributes/ 是权威来源。
| 前缀 | 对目标的影响 |
|---|---|
dot_ | 在开头添加 .(dot_zshrc → ~/.zshrc) |
private_ | 移除组和其他用户的权限(模式 0600 / 0700) |
readonly_ | 移除所有写权限位 |
executable_ | 添加可执行权限位 |
empty_ | 允许空文件(默认情况下会将其移除) |
encrypted_ | 写入目标前,使用 age/gpg 解密源文件 |
symlink_ | 创建符号链接而不是常规文件(文件形式,目标是符号链接目标) |
exact_(目录) | 删除目标目录中源文件里不存在的内容 |
create_ | 仅在目标不存在时创建——绝不覆盖 |
modify_ | 将源文件视为生成目标内容的脚本(或模板,见下文) |
run_ | 将源文件视为要执行的脚本(而非目标文件) |
before_ / after_ | 为脚本相对于应用文件的执行排序 |
once_(脚本) | 仅在此前未成功运行过该内容时运行 |
onchange_(脚本) | 仅当此文件名对应的内容发生变化时运行 |
remove_ | 如果目标存在,则将其移除 |
external_(目录) | 将目录标记为外部目录——与 .chezmoiexternal.<fmt> / .chezmoiexternals/ 配套使用;chezmoi 会忽略子条目的属性 |
literal_(任意位置) | 从此处起停止解析后续属性 |
各目标类型允许的前缀顺序(顺序固定;调整顺序会导致解析错误):
| 目标类型 | 允许的前缀(按顺序) | 可选后缀 |
|---|---|---|
| 目录 | remove_、external_、exact_、private_、readonly_、dot_ | — |
| 常规文件 | encrypted_、private_、readonly_、empty_、executable_、dot_ | .tmpl |
| 创建文件 | create_、encrypted_、private_、readonly_、empty_、executable_、dot_ | .tmpl |
| 修改文件 | modify_、encrypted_、private_、readonly_、executable_、dot_ | .tmpl |
| 移除文件 | remove_、dot_ | — |
| 脚本 | run_、once_ 或 onchange_、before_ 或 after_ | .tmpl |
| 符号链接 | symlink_、dot_ | .tmpl |
后缀 .tmpl 表示该文件是模板。配置了相应加密方式后,.age / .asc 会自动移除。
修改文件详解。 modify_ 源文件默认是脚本——它从标准输入读取当前目标,并将新目标写入标准输出。如果文件包含字符串 chezmoi:modify-template,则会移除包含该标记的行,并将其余内容视为模板;现有目标可通过 .chezmoi.stdin 访问。需要精细修改结构化文件(JSON、TOML、YAML)时,请使用模板形式;需要使用真正的解释器(jq、awk、sed)时,请使用脚本形式。
5. 安全应用流程
应用任何会触及重要文件的更改前,请先运行检查三件套:
chezmoi status # one line per changed file
chezmoi diff # unified diff of pending changes
chezmoi apply --dry-run -v # full plan, including scripts that would run
chezmoi apply # only after the above looks right
在你信任的机器上进行常规拉取时:
chezmoi update # = git pull --autostash --rebase + apply
chezmoi git pull -- --autostash --rebase && chezmoi diff # preview only — no apply
6. 安全编辑
chezmoi edit ~/.zshrc # opens source in $EDITOR, handles .tmpl + encryption
chezmoi cd # subshell in the source directory (for git operations)
chezmoi re-add # refresh source from current target after editing target
chezmoi merge ~/.zshrc # 3-way merge target ↔ source ↔ destination
chezmoi merge-all # 3-way merge every file whose actual ≠ target (add --init to re-render config first)
编辑器的选择顺序:edit.command → $VISUAL → $EDITOR → vi(Windows 上为 notepad.exe)。如果编辑器的运行时间短于 edit.minDuration(默认值为 1s),chezmoi 会打印警告——这可以捕捉到“我打开了文件,但实际上并未编辑”的情况。设为 0 可关闭警告。
切勿直接在编辑器中打开 ~/.local/share/chezmoi/encrypted_dot_ssh/... 或 *.tmpl 文件——你看到的文件并不是 chezmoi 看到的文件。
7. 应用顺序(心智模型)
chezmoi apply 是确定性的。每次运行都会:
- 读取源状态,读取目标状态,并计算目标状态。
- 按目标名称的 ASCII 顺序运行
run_before_脚本。 - 按目标名称的 ASCII 顺序更新条目。目录(包括由 externals 实体化的目录)会先于其包含的文件写入。externals 会在此阶段获取并展开。
- 按 ASCII 顺序运行
run_after_脚本。
影响:
run_before_脚本不得读取由 external 提供的文件——externals 会在第 3 阶段应用,而不是之前。- 比较目标名称时,会去掉所有属性前缀。对于
create_alpha和modify_dot_beta,.beta会先于alpha更新,因为在 ASCII 顺序中.beta<alpha。 - chezmoi 假定源和目标在运行期间不会发生变化。向其中任一处写入的
run_before_脚本都违反了这一假设——行为未定义。
8. 部分文件管理
当 chezmoi 不应管理整个文件时,根据文件的归属方选择一种模式:
modify_——用户拥有文件,chezmoi 写入一个区块。 源会从标准输入读取现有目标,并将新目标写入标准输出。
脚本形式:modify_dot_zshrc:
#!/usr/bin/env bash
set -euo pipefail
# Strip any prior chezmoi block from the existing file …
awk '/# chezmoi:begin/{s=1;next} /# chezmoi:end/{s=0;next} !s'
# … then append the current managed block.
cat <<'EOF'
# chezmoi:begin
export EDITOR=nvim
alias g=git
# chezmoi:end
EOF
模板形式(用于结构化文件):modify_dot_config_private_app.yaml.tmpl:
{{- /* chezmoi:modify-template */ -}}
{{- $cfg := dict -}}
{{- if .chezmoi.stdin -}}{{- $cfg = .chezmoi.stdin | fromYaml -}}{{- end -}}
{{- $_ := set $cfg "telemetry" false -}}
{{- $_ := set $cfg "user" (dict "email" .email) -}}
{{ toYaml $cfg }}
if .chezmoi.stdin 条件用于处理首次应用时目标尚不存在的情况。使用 fromJsonc / fromToml / fromYaml + toPrettyJson / toToml / toYaml 完成往返转换。
.chezmoitemplates/——chezmoi 拥有文件,片段可共享。 可复用区块存放在 .chezmoitemplates/ 下,通过 {{ template "name" . }} 引入常规模板(如果需要将结果通过管道传给函数,则使用 includeTemplate "name" .):
.chezmoitemplates/zsh-prompt # one file
dot_zshrc.tmpl # {{ template "zsh-prompt" . }}
dot_config/fish/config.fish.tmpl # {{ template "zsh-prompt" . }} too
根据归属方选择模式。 用户可以在托管区块之外自由编辑文件时,使用 modify_。chezmoi 渲染整个文件但你希望复用共享部分时,使用 .chezmoitemplates/。在同一个目标中混用两种模式通常不是好做法。
硬性规则
这些规则不可妥协,每一条都对应一个真实的隐患:
- 绝不提交明文密钥,即使是私有仓库也不行。仓库可能会泄露、被派生、被记录日志或被备份。从
references/secrets.md中选择一种后端。 - 绝不直接编辑加密或模板化的源文件。 使用
chezmoi edit $TARGET,以确保完成解密和模板往返转换。 - 在应用到共享或重要文件之前,务必检查——使用
chezmoi diff或chezmoi apply --dry-run -v。目录上的exact_会删除非托管条目;应提前确认这一点。 prompt*模板函数只能用于配置文件模板(.chezmoi.toml.tmpl)。在常规点文件模板中使用它们,会导致每次 apply / diff / status 都弹出提示,从而无法自动化。
密钥决策树(摘要)
完整详情请参阅 references/secrets.md。每个仓库只选择一种。
- 公共仓库 + 云端密码管理器 → 1Password CLI / Bitwarden 模板函数。密钥在应用时获取,不会写入磁盘。
- 私有仓库 + 支持离线使用 → age(
encrypted_前缀)。身份文件位于~/.config/chezmoi/key.txt,存放在仓库之外。 - 团队共享 → 使用 SOPS 加密的
.chezmoidata/secrets.yaml。 - 隔离网络 / 首次引导 → 被 gitignore 忽略的明文
.chezmoidata/local.yaml(过渡方案,不适合作为长期方案)。
混用后端只会增加需要维护的部分,并不会让任何内容更安全。
模板快速参考
完整详情请参阅 references/templating.md。
{{- if eq .chezmoi.os "darwin" }}
export HOMEBREW_PREFIX="/opt/homebrew"
{{- else if eq .chezmoi.os "linux" }}
export PATH="/usr/local/bin:$PATH"
{{- end }}
{{- if and (eq .chezmoi.os "darwin") (lookPath "brew") }}
# brew is installed
{{- end }}
email = {{ .email | quote }}
内置模板变量包括 .chezmoi.os、.chezmoi.arch、
.chezmoi.hostname、.chezmoi.username、.chezmoi.homeDir、
.chezmoi.sourceDir。用户自定义数据存放在 chezmoi.toml 的
[data] 下,或任意 .chezmoidata/*.{toml,yaml,json} 文件中。
chezmoi 使用 Go 的 missingkey=error 选项运行模板,因此像
{{ .gitub.user }} 这样的拼写错误会导致应用失败,而不是渲染出空值。
只有确有充分理由时才通过 [template] options = ["missingkey=zero"] 覆盖此设置——失去拼写错误保护通常得不偿失。
对于符号链接目标,会移除渲染结果开头和结尾的空白;如果结果为空,则会移除符号链接。对于文件目标,空渲染结果会移除文件(使用 empty_ 前缀可保留文件)。
特殊文件和目录
源目录中所有以 . 开头的条目都会被忽略,以下特殊项除外。每次操作时都会按以下顺序处理:
.chezmoiroot → .chezmoi.<fmt>.tmpl → 数据文件 → .chezmoitemplates/
→ .chezmoiignore → .chezmoiremove → 外部资源 → .chezmoiversion。
| 路径 | 作用 |
|---|---|
.chezmoiroot | 单行文件,指向作为源根目录的子目录 |
.chezmoi.<fmt>.tmpl | 配置文件模板,用于首次执行 chezmoi init 时(以及使用 --init 时) |
.chezmoidata.<fmt> / .chezmoidata/ | 在渲染任何模板之前,合并到模板 . 命名空间中的数据 |
.chezmoitemplates/ | 可复用的模板片段({{ template "name" . }})——不是目标文件 |
.chezmoiscripts/ | 不与目标文件绑定的脚本;遵循 run_before_ / run_after_ |
.chezmoiignore | 按目标设置的忽略列表——相对于源目录,可使用模板 |
.chezmoiremove | 应用时要移除的目标——可使用模板 |
.chezmoiexternal.<fmt> / .chezmoiexternals/ | 应用时从 URL 获取文件或归档 |
.chezmoiversion | 此源代码仓库所需的 chezmoi 最低版本 |
.chezmoiignore 匹配的是相对于源目录的路径(例如 dot_zshrc),而不是
目标路径。很多人总会弄错这一点。
<fmt> 表示 toml、yaml、json 或 jsonc。目录形式
(.chezmoidata/、.chezmoiexternals/)会按字典顺序合并其内容,并与同前缀的文件形式合并——这对于将大型配置拆分到多个文件中,或按角色组合外部资源很有用。
不要做的事
- 编写与之竞争的点文件管理器,或为
chezmoi apply编写封装层 - 当
~/.local/share/chezmoi/下的文件是模板或已加密时,直接编辑这些文件 - 将
.env/secrets.yaml提交到“私有”仓库 - 在共享机器上未先运行
chezmoi diff就建议执行chezmoi apply - 在
.chezmoi.toml.tmpl之外使用prompt*模板函数 - 未给出明确的书面理由,就在同一仓库中混用多个密钥后端(age + 1Password + SOPS)
注意事项
- 运行模板脚本时出现
fork/exec ...: exec format error,表示#!前有换行符。在第一行模板的结束}}内添加-,即可抑制该换行符。 - 脚本出现
fork/exec ...: permission denied,表示$TMPDIR挂载时启用了noexec。在chezmoi.toml中设置scriptTempDir。 - 模板中的 Windows 换行符(CRLF)会导致 shell 脚本在 Linux/macOS 上无法正常运行。在 dotfiles 仓库的
.gitattributes中添加* text eol=lf,或设置core.autocrlf = input。 - 在新机器上,如果
.chezmoi.toml.tmpl尚不存在就运行chezmoi init,会静默跳过逐台机器的提示。添加特定主机的文件前,先提交该模板。 - 出现异常时,首先运行
chezmoi doctor——它会一次性报告缺失的依赖项(op、age等)、损坏的路径和 shell 集成问题。 - Context7 可能不可用。chezmoi CLI 可通过
chezmoi help和chezmoi help <command>自行提供文档;遇到这种情况时改用它们。 - 在
~/.config/chezmoi/中同时存在两个配置文件(例如chezmoi.toml和chezmoi.yaml)会报错,并非“优先使用第一个”。迁移格式时,在同一次提交中删除旧文件。 - 配置格式根据扩展名检测。如果通过管道传入来自特殊路径的配置,可使用
--config-format强制指定格式。 chezmoi <unknown>会在报错前,先在$PATH中查找chezmoi-<unknown>(git 风格的插件约定)。这便于添加仓库本地的辅助工具;但如果拼写错误意外运行了无关的二进制文件,也会令人措手不及。
参考 URL 索引
chezmoi 文档更新很快——每隔几个版本就会新增模板函数、新的配置键和新的前缀。如果问题超出了本技能的范围,请获取实时页面,不要根据训练数据猜测。优先使用 mcp__tavily__tavily_extract(单个 URL,extract_depth: "advanced",format: "markdown")——它会直接返回规范的
/reference/* 内容。仅在无法使用时才改用 Context7。不要使用 tavily_map 或爬取页面——以下 URL 稳定不变;选择确切的 URL 即可。
调用示例:
mcp__tavily__tavily_extract(
urls: ["https://www.chezmoi.io/reference/templates/functions/jq/"],
extract_depth: "advanced",
format: "markdown",
)
内容位置索引:
| 主题 | URL |
|---|---|
| 概念(源 / 目标 / 目的地 / 工作树) | /reference/concepts/ |
| 各目标类型的源状态属性顺序 | /reference/source-state-attributes/ |
| 目标类型(文件 / 目录 / 符号链接 / 脚本 / 修改 / 创建 / 移除) | /reference/target-types/ |
chezmoi apply 的应用顺序 | /reference/application-order/ |
| 所有命令行标志(全局 / 常用 / 开发者) | /reference/command-line-flags/ |
单条命令参考(chezmoi <name>) | /reference/commands/<name>/ |
| 配置文件结构 | /reference/configuration-file/ |
| 钩子(read-source-state / apply / 等) | /reference/configuration-file/hooks/ |
| 按扩展名划分的脚本解释器 | /reference/configuration-file/interpreters/ |
特殊文件(.chezmoiignore 等) | /reference/special-files/<name>/ |
特殊目录(.chezmoidata/ 等) | /reference/special-directories/<name>/ |
模板变量(.chezmoi.os 等) | /reference/templates/variables/ |
模板指令(chezmoi:template: 等) | /reference/templates/directives/ |
| 内置模板函数 | /reference/templates/functions/<name>/ |
init 模板函数(promptBoolOnce 等) | /reference/templates/init-functions/<name>/ |
| 密码管理器模板函数 | /reference/templates/<vendor>-functions/<name>/(1password、bitwarden、dashlane、doppler、ejson、gopass、keepassxc、keeper、keyring、lastpass、pass、passhole、protonpass、vault、aws-secrets-manager、azure-key-vault、secret) |
| GitHub 模板函数 | /reference/templates/github-functions/<name>/ |
| 插件 | /reference/plugins/ |
判断方法:用户问“前缀 X 有什么作用”时 → source-state-attributes;问“chezmoi <cmd> 有什么作用”时 → commands/<cmd>;问“哪个模板变量存放 Y”时 → templates/variables;问“如何从 Z 中读取机密”时 → templates/<vendor>-functions。使用 mcp__tavily__tavily_extract 仅获取所需页面——单个 URL、advanced 深度——这样答案始终基于截至今天的规范文档,而不是本技能上次编辑时掌握的信息。