教程·阅读约 4 分钟·
从 GNU Stow 迁移到 Chezmoi——多机点文件管理方案

从 GNU Stow 迁移到 Chezmoi——多机点文件管理方案

点文件管理是每个开发者的必修课。本文详细对比 GNU Stow 和 Chezmoi 两种方案,并给出完整的迁移指南,涵盖多机同步、敏感文件管理和开机初始化配置。

原文来源:Redowan Delowar — 从 GNU Stow 迁移到 Chezmoi 的完整经验分享,Redowan's Reflections 博客

点文件(dotfiles)管理是每个程序员迟早要面对的问题。.zshrc.gitconfignvim/、各种 CLI 工具的配置……随着时间推移,这些散落在 ~ 目录各处的配置文件越来越难以维护。

如果你有两台以上的电脑(比如一台办公 Mac、一台个人 Mac、一台 Linux 服务器),问题就更复杂了——每台机器的配置有差异,但又共享大部分相同的内容。

今天要介绍的是如何从传统的 GNU Stow 方案平滑迁移到 Chezmoi,以及为什么 Chezmoi 在多机场景下好得多。

为什么离开 GNU Stow?

GNU Stow 是目前最流行的点文件管理工具之一。它的工作原理很简单:把你的配置放在一个目录里,然后用符号链接(symlink)把它们映射到 ~ 目录。

code
~/dotfiles/
├── zsh/
   └── .zshrc
└── git/
    └── .gitconfig

然后 stow zsh 会在 ~ 下创建一个指向 ~/dotfiles/zsh/.zshrc 的符号链接。

这个方法简单、透明,我用了好几年。但长期使用下来,几个问题越来越突出:

1. 符号链接是双向的(这也是最大的坑)

Stow 用符号链链接到仓库目录。你在 ~ 下修改 .zshrc,实际上是在直接修改仓库里的文件。这本是设计意图,但问题在于:

如果你在办公电脑上改了某个配置,提交并推送到了 GitHub,然后在个人电脑上 pull——如果两台机器在这段时间内都对同一个文件做了修改,就会产生冲突。

一个真实的冲突场景

  • 周一到办公室,你为了调试加了几个 echo.zshrc(它们最终被提交了)
  • 周二在家工作,你发现 .zshrc 里某个 alias 不好用,改成了另一个写法
  • 周三回到办公室,git pull 之后你才发现——哦,这两个版本的 .zshrc 都要合并

更隐蔽的问题:有时你只是想试一个配置,随手改了 ~/.zshrc,根本没想提交——但 Stow 的符号链接模式下,改了就是改了,仓库永远是脏的。

2. 新机器初始化麻烦

换新电脑时,Stow 方案的问题是:目标位置如果有同名文件存在(比如新 Mac 默认就有 .zshrc 文件),stow 命令会拒绝覆盖。你需要手动删除目标文件,然后再 stow。

code
# 每个冲突文件都要手动处理
rm ~/.zshrc
rm ~/.gitconfig
# ... 重复十几次
stow */

这种机械重复的操作很容易漏掉某个文件。

3. 多个「软件包」的管理成本

Stow 的理念是「一个目录一个包」。我的 dotfiles 目录结构一开始只有 zsh/git/,后来增加了 nvim/tmux/ghostty/claude/……每套工具单独 stow,但有些相互依赖,有些有先后顺序——最终变成了一个需要自己维护的 shell 脚本。

—— 广告 ——

Chezmoi 如何解决这些问题?

Chezmoi 是 Tom Payne 用 Go 写的点文件管理工具。核心设计理念和 Stow 完全不同。

Chezmoi 的核心模型

Chezmoi 不再使用符号链接。它的源目录(默认在 ~/.local/share/chezmoi)是真正的 Git 仓库,通过 chezmoi apply 命令将文件「写入」到目标位置。

关键差异

  • Stow:源文件 = 目标文件(符号链接连通两边)
  • Chezmoi:源文件 → apply → 目标文件(源是唯一真相来源)
code
# 添加一个文件到 chezmoi 管理
chezmoi add ~/.zshrc
 
# 查看源文件和目标文件的差异
chezmoi diff
 
# 将源文件应用到目标位置
chezmoi apply

文件命名规则

Chezmoi 用一套命名规则来编码文件属性。比如:

真实路径Chezmoi 中的文件名含义
~/.zshrcdot_zshrc点号变为 dot_ 前缀
~/.config/gh/config.ymldot_config/gh/config.yml子目录结构保留
~/.ssh/private_keyprivate_dot_ssh/private_keyprivate_ = 0600 权限
~/.zshrc (Go 模板)dot_zshrc.tmpl.tmpl = 使用 Go 模板渲染

真正的关键优势:写入而非链接

因为 Chezmoi 将文件写入而非链接,所以:

  1. 仓库不会被动变脏——你在 ~ 下修改 .zshrc,仓库里的源文件纹丝不动
  2. 新机器直接覆盖——chezmoi apply 会覆盖已存在的文件(并有备份)
  3. 每台机器可以有不同配置——这是通过 Go 模板(.tmpl)实现的

我的 Chezmoi 配置实战

以下是我迁移后的完整配置树:

code
~/.local/share/chezmoi/
├── .chezmoi.toml.tmpl        # 机器级变量定义
├── .chezmoiignore             # 不 apply 的文件
├── .chezmoiscripts/           # 自动化脚本
│   └── macos/
│       ├── run_onchange_after_disable-macos-animations.sh
│       ├── run_onchange_after_init-macos-machine.sh.tmpl
│       └── run_onchange_before_install-homebrew-bundle.sh.tmpl
├── .gitignore
├── Brewfile                   # Homebrew 包清单
├── dot_agents/skills/          # AI 代理技能文件夹
├── dot_claude/
│   ├── settings.json
│   └── symlink_skills.tmpl
├── dot_codex/private_config.toml
├── dot_config/gh/
│   ├── config.yml
│   └── private_hosts.yml
├── dot_config/ghostty/config
├── dot_gitconfig
├── dot_gitconfig-pers          # 个人项目 Git 配置
├── dot_gitconfig-werk          # 工作项目 Git 配置
├── dot_shellcheckrc
├── dot_zsh_aliases
└── dot_zshrc

多 Git 配置的巧妙处理

一个常见的需求是:工作和个人项目用不同的 Git 用户名和邮箱。解决方案是 gitconfigincludeIf 指令:

code
[includeIf "gitdir:~/canvas/pers/"]
    path = ~/.gitconfig-pers
 
[includeIf "gitdir:~/canvas/werk/"]
    path = ~/.gitconfig-werk

这样,在 ~/canvas/pers/ 下的任何仓库自动使用个人配置,~/canvas/werk/ 下的自动使用工作配置。

敏感文件自动设权限

private_ 前缀标记的文件会在 apply 时自动设为 0600 权限:

code
chezmoi add ~/.ssh/config
# 自动变成 private_dot_ssh/config

机器级变量模板

.chezmoi.toml.tmpl 文件定义了每台机器的变量:

code
{{- $machineName := promptStringOnce . "machineName" "machineName" .chezmoi.hostname -}}
[data]
    machineName = {{ $machineName | quote }}

第一次在新机器上运行 chezmoi init --apply 时会提示输入机器名,之后值会缓存在 ~/.config/chezmoi/chezmoi.toml 中。

然后在模板文件中引用:

code
# dot_zshrc.tmpl 中的条件判断
{{ if eq .machineName "work-mac" }}
export WORK_MODE=true
{{ end }}

新 Mac 的开箱体验

这是 Chezmoi 最惊艳的部分。在新 Mac 上只需要两步:

code
# 1. 安装 Chezmoi
brew install chezmoi
 
# 2. 从 GitHub 初始化并应用配置
chezmoi init --apply \
    --promptString machineName=mini \
    https://github.com/你的用户名/dotfiles.git

这个命令会:

  1. 克隆你的 dotfiles 仓库
  2. 运行 .chezmoiscripts/ 中的 before 脚本(自动安装 Homebrew 包)
  3. 将配置 apply 到 ~ 目录
  4. 运行 after 脚本(设置 macOS 系统偏好、禁用动画等)

背后的魔法是 .chezmoiscripts/ 目录下的自动化脚本:

code
#!/usr/bin/env bash
# Brewfile checksum: {{ include "Brewfile" | sha256sum }}
brewfile={{ joinPath .chezmoi.sourceDir "Brewfile" | quote }}
"$brew_bin" bundle check --no-upgrade --file "$brewfile" >/dev/null 2>&1 \
    || "$brew_bin" bundle install --no-upgrade --file "$brewfile"

注意那个 SHA256 checksum 注释——当 Brewfile 内容变化时,脚本的 checksum 也会变,Chezmoi 会自动重新执行脚本。这是确保依赖始终同步的优雅方案。

Brewfile 示例:

code
brew "chezmoi"
brew "fzf"
brew "gh"
brew "micro"
brew "ripgrep"
brew "uv"
cask "claude-code"
cask "codex"
cask "ghostty"
cask "raycast"

run_onchange_ 前缀确保脚本仅在首次 apply 或内容变化时执行,不是每次运行都跑。

日常使用:5 个最常用的命令

1. 修改配置并立即生效

code
chezmoi edit --apply ~/.zshrc

一句话完成:编辑源文件 → apply 到目标位置。不需要手动 chezmoi apply

2. 从实际文件更新源文件

当你直接在 ~ 下修改了某个配置(比如测试时手改的),想把改动同步到源文件:

code
chezmoi add ~/.zshrc

如果想批量同步所有被管理的文件:

code
chezmoi re-add

3. 查看源文件和真实文件的差异

code
chezmoi diff

确认改动无误后再 apply,避免误操作。

4. 提交并推送

code
chezmoi cd   # 进入源目录
git add -A
git commit -m "Update zsh config"
git push
exit

5. 在其他机器上拉取更新

code
chezmoi update --verbose

或者更安全的方式:先拉取再预览差异,确认无误后再 apply:

code
chezmoi git pull -- --autostash --rebase
chezmoi diff
chezmoi apply --verbose

迁移要点总结

场景Stow 方案Chezmoi 方案
文件同步方式符号链接(双向)apply/写入(单向)
新机器设置手动删除冲突文件后 stow一行命令自动完成
多机配置差异维护多个分支Go 模板 + 机器变量
敏感文件管理手动设权限private_ 前缀自动处理
自动化脚本手动执行run_onchange_ 脚本自动触
系统偏好设置独立脚本手动运行同 Chezmoi 一同发布

如果你现在还在用 Stow 管理点文件,建议尝试迁移到 Chezmoi。刚开始可能会觉得命名规则有点不习惯,但一旦上手,你很快就会发现「无需处理符号链接冲突」带来的安全感是值得的。

分享到
微博Twitter

© 2026 四月 · CC BY-NC-SA 4.0

原文链接:https://aprilzz.com/tutorials/migrate-stow-to-chezmoi