我工作機是 MacBook、家裡有台 Linux 桌機、公司還丟了一台 Windows NUC 過來。三台機器的 .gitconfig、.zshrc、.tmux.conf 要同步,但每台 OS 的細節又不一樣——Windows 需要額外指定 sslCAInfo 指到 scoop 裝的 git 憑證路徑,macOS 要跑 Homebrew,Linux 要跑 apt。
以前我用 symlink + shell script 硬搞,現在改用 chezmoi。同一份 dotfiles repo,三台機器 chezmoi init --apply 一行搞定。
為什麼不是 stow、yadm 或 dotbot
dotfiles 管理器很多,chezmoi 勝出的點在三個:
- Go template:同一個檔案在不同 OS 會展開成不同內容,不用維護三份
.gitconfig - 加密原生整合:age、gpg 直接接,私密檔案能放進公開 repo
- onchange script:裝 Homebrew package 的 script 只在清單變更時跑,不會每次 apply 都重裝
stow 是純 symlink,沒模板;yadm 是 git wrapper,模板靠外掛;dotbot 要寫 YAML 清單。chezmoi 把這些整合在一個 binary 裡。
安裝與初始化
| |
新機器從既有 repo 拉下來並直接套用:
| |
這行會做三件事:clone repo → 跑 template engine → 把結果寫到 $HOME。
檔名命名規則
chezmoi 靠檔名前綴決定套用時的行為。這設計讓 repo 裡的檔案本身就是設定,不需要額外的 manifest。
| 前綴 | 作用 | 範例 |
|---|---|---|
dot_ | 目標是隱藏檔 | dot_zshrc → ~/.zshrc |
private_ | 只留 user 權限(0600) | private_dot_ssh → ~/.ssh |
executable_ | 加可執行權限 | executable_bin_foo |
encrypted_ | 用 age/gpg 加密 | encrypted_dot_env |
symlink_ | 建 symlink | symlink_dot_bashrc |
readonly_ | 拿掉寫入權限 | readonly_dot_config.toml |
檔尾 .tmpl | 套 template 引擎 | dot_gitconfig.tmpl |
前綴可以疊加。我的 repo 裡有這樣的組合:
| |
用 template 處理機器差異
這是 chezmoi 最實用的功能。我的 dot_gitconfig.tmpl 長這樣:
| |
.name 和 .email 從 ~/.config/chezmoi/chezmoi.toml 讀,不同機器可以有不同值;{{ if eq .chezmoi.os "windows" }} 只在 Windows 展開。apply 的時候 chezmoi 會把 .tmpl 吃掉,寫出乾淨的 .gitconfig。
chezmoi 內建很多變數:
| |
要看某台機器展開後的結果,不用真的 apply:
| |
用 age 加密私密檔
我 repo 是公開的,但裡面有 SSH key 跟資料庫密碼備份檔。這些用 age 加密後才進 commit。
先產 age key:
| |
接著在 ~/.config/chezmoi/chezmoi.toml 設定:
| |
然後用 --encrypt 加檔:
| |
repo 裡只會看到 private_dot_ssh/encrypted_private_id_ed25519.age,打開是亂碼。apply 時 chezmoi 會用 ~/key.txt 解密後寫到目標位置。
唯一的天大陷阱:key.txt 本身絕對不能放進 repo。我的作法是把它 GPG 加密後放到密碼管理器,新機器要先手動還原 key.txt,然後才能 chezmoi init --apply。
run_onchange script:套件清單變才重裝
我的 repo 有個 .chezmoiscripts/darwin/run_onchange_00_install-packages.sh.tmpl,裡面長這樣:
| |
檔名的 run_onchange_ 是關鍵:chezmoi 只有在這個 script 內容 hash 變了才會執行。套件清單沒改就不重跑,避免每次 chezmoi apply 都花五分鐘在 brew install 已裝好的東西。
script 命名有四種:
| 前綴 | 觸發時機 |
|---|---|
run_once_ | 同內容一輩子只跑一次 |
run_onchange_ | 內容變了才跑 |
run_onchange_before_ | 套檔案之前跑(裝 package manager) |
run_onchange_after_ | 套檔案之後跑(啟用 fish plugin) |
檔名前面的數字(00_、01_、02_)控制執行順序。
.chezmoiroot:repo 子目錄當 source
看了我 repo 有注意到所有檔案都在 home/ 底下:
| |
.chezmoiroot 這個檔案告訴 chezmoi「source 在子目錄 home/」,這樣 repo 根目錄就能放 README、install script 之類的專案檔案,不會被 chezmoi 當成 dotfiles 處理。
對於想把 dotfiles repo 當正常專案維護的人很實用。
.chezmoiignore:跳過某些檔案
跟 .gitignore 語法一樣,但支援 template。範例:
| |
非 macOS 的機器就會忽略 aerospace 視窗管理器設定跟 Library 資料夾。
常用指令
| |
chezmoi doctor 會列出加密工具、template 引擎、git 等的可用狀態,新機器出問題先跑這個。
跟 zoxide、fish 的整合
我的 fish shell 設定、zoxide 初始化、tmux plugin 都在 chezmoi 管理下。每次換新機器:
- 還原 age key
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply recca0120- run_onchange script 自動跑 brew / apt / scoop 裝所有 CLI 工具
- 設定檔全部就位
- 打開 fish,zoxide、starship、fzf 都是設定好的
整個新機 setup 大概 20 分鐘,主要時間花在等 brew install 下載。
缺點與注意事項
chezmoi 不是萬靈丹:
- Template 學習曲線:Go template 語法對新手有點硬,
{{- }}跟{{ }}的空白處理要花時間搞懂 - Debug 痛苦:template 展開錯的時候錯誤訊息很簡略,常要
chezmoi execute-template單獨測 - age key 管理:key 掉了所有加密檔都解不開,務必另外備份(我 GPG 加密後放密碼管理器)
- 初次 apply 要小心:如果
$HOME已經有手改過的 dotfiles,apply 會覆蓋掉,先chezmoi diff確認
