chezmoiは「dotfilesの管理用リポジトリ」と「実際のホームディレクトリ」を分離し、
テンプレートや条件分岐を使って安全に反映(apply)できるツールです。
本記事はその導入メモです。環境はmacOSを前提としています。
dotfilesとは
PCのdotfiles(ホームディレクトリに配置されている .zshrc などのドットから始まるファイル)はユーザー設定や初期設定を保存するために使用するファイル群です。
このファイル群をこれまでは手作業でメンテ・コピペして運用してきたのですが、chezmoiというツールを使うと安全かつ一元管理できるという情報を耳にし、今回初めて導入してみました。
chezmoiのインストール
Install を参考にbrewでインストールします。
$ brew install chezmoi
インストールができたらQuick startを参考にコマンドを実行していきます。
$ chezmoi init (予め作成したGitHubリポジトリURL)
$ file .local/share/chezmoi
.local/share/chezmoi: directory
$ chezmoi add ~/.zshrc
$ file .local/share/chezmoi/dot_zshrc
.local/share/chezmoi/dot_zshrc: Unicode text, UTF-8 text
.local/share/chezmoi がdotfilesを管理するディレクトリのようです。 chezmoi add したファイルはここにコピーされていきます。
dotfilesの編集とテンプレートの導入
次のコマンドで管理対象となった dot_zshrc を編集します。デフォルトだと $EDITOR にセットされているエディタが起動するらしく私の場合は nvim(LazyVim) が起動しました。
$ chezmoi edit ~/.zshrc
これで編集しても良いのですが、将来別のPCに乗り換えるときに備えてtemplateを使ってみたいと思いました。次のコマンドでtemplateを使えるようにします。
$ chezmoi chattr +template ~/.zshrc
# MEMO: chezmoi addの時点で以下のように入力しても同じ結果となる
$ chezmoi add --template ~/.zshrc
修正後の .zshrc のテンプレートは次のようになりました。
eval "$(mise activate zsh)"
eval "$(starship init zsh)"
eval "$(direnv hook zsh)"
bindkey -v
autoload -U compinit
compinit -u
export HISTFILE={{ .chezmoi.homeDir }}/.zsh_history
export HISTSIZE=1000
export SAVEHIST=100000
export LDFLAGS="-L$HOMEBREW_PREFIX/lib"
export CFLAGS="-I$HOMEBREW_PREFIX/include"
export PICO_SDK_PATH={{ .chezmoi.homeDir }}/ghq/github.com/raspberrypi/pico-sdk
export PICO_EXTRAS_PATH={{ .chezmoi.homeDir }}/ghq/github.com/raspberrypi/pico-extras
export PATH="{{ .chezmoi.homeDir }}/.rd/bin:$PATH"
alias ls='ls -G'
alias be='bundle exec'
alias vim="nvim"
alias vi="nvim"
alias get_idf=". {{ .chezmoi.homeDir }}/ghq/github.com/espressif/esp-idf/export.sh"
if [[ ! -f {{ .chezmoi.homeDir }}/.local/share/zinit/zinit.git/zinit.zsh ]]; then
print -P "%F{33} %F{220}Installing %F{33}ZDHARMA-CONTINUUM%F{220} Initiative Plugin Manager (%F{33}zdharma-continuum/zinit%F{220})…%f"
command mkdir -p "{{ .chezmoi.homeDir }}/.local/share/zinit" && command chmod g-rwX "{{ .chezmoi.homeDir }}/.local/share/zinit"
command git clone https://github.com/zdharma-continuum/zinit "{{ .chezmoi.homeDir }}/.local/share/zinit/zinit.git" && \
print -P "%F{33} %F{34}Installation successful.%f%b" || \
print -P "%F{160} The clone has failed.%f%b"
fi
source "{{ .chezmoi.homeDir }}/.local/share/zinit/zinit.git/zinit.zsh"
autoload -Uz _zinit
(( ${+_comps} )) && _comps[zinit]=_zinit
zinit light scmbreeze/scm_breeze
zinit light zsh-users/zsh-autosuggestions
zinit light zdharma/fast-syntax-highlighting
zinit light mollifier/anyframe
bindkey '^f' anyframe-widget-cdr
autoload -Uz chpwd_recent_dirs cdr add-zsh-hook
add-zsh-hook chpwd chpwd_recent_dirs
bindkey '^r' anyframe-widget-execute-history
bindkey '^b' anyframe-widget-checkout-git-branch
bindkey '^g' anyframe-widget-cd-ghq-repository
bindkey '^k' anyframe-widget-kill
ちなみに、次のコマンドを実行することでテンプレートを置き換えた .zshrc を確認できます。デバッグ用途として使えますね。
$ chezmoi execute-template < .local/share/chezmoi/dot_zshrc.tmpl
このテンプレートから .zshrc を書き換える際は次のコマンドを実行します。
$ chezmoi apply
基本的にはこれを管理対象のdotfilesの数だけ繰り返します。私は次のファイルを管理対象に加えました。
~/.gitconfig~/.zprofile~/.Brewfile~/.config/mise/config
1Passwordで管理している秘匿情報をdotfilesに埋め込む
.ssh/id_ed25519 など秘匿情報が書き込まれているファイルをそのまま管理対象にするのは、Privateリポジトリだとしても躊躇われます。そんなときは1Passwordで秘匿情報を管理するようにして、その情報をテンプレートに埋め込むことができます。
次のドキュメントを参考に1Password CLIがインストールされていること、当該秘匿情報がすでに1Passwordで「ドキュメント」として管理されていることが前提です。
次のコマンドで .ssh/id_ed25519 を管理対象に加え、編集します。
$ chezmoi add --template .ssh/id_ed25519
$ chezmoi edit .ssh/id_ed25519
次のように記載します。
{{ onepasswordRead "op://Private/xxxxxxxxxxxxxxxxxxxxxxxxxx/private_key"}}
op:// から始まる文字列は1Passwordにおけるシークレット参照と呼ばれるもので、以下ドキュメントを参考に取得ができます。
この状態で chezmoi apply すると、1Passwordに存在する秘匿情報がdotfilesに埋め込まれます。
ちなみにSSHキーは 1Password SSH agentという仕組みが1Password側に用意されているので、こちらを使った方が手間を省けそうということに後ほど気づきました。
dotfilesの反映前後でシェルスクリプトを実行する
run_ から始まるファイルを作っておくと、dotfilesを chezmoi apply の前後にそのシェルスクリプトを実行できます。
インストールスクリプトを準備しておけばPCのセットアップそのものも自動化できることになります。
以下はMacの設定をいい感じに変更するスクリプトです。1回だけ実行します。
#!/bin/bash
set -e
{{ if eq .chezmoi.os "darwin" }}
# メニューバーの時計を秒まで表示
defaults write com.apple.menuextra.clock ShowSeconds -bool true
# キーのリピートを高速化
defaults write -g InitialKeyRepeat -int 15
defaults write -g KeyRepeat -int 2
# スクロールバーの表示:スクロール時に表示
defaults write -g AppleShowScrollBars -string "WhenScrolling"
# Dock:自動的に隠す
defaults write com.apple.dock autohide -bool true
# Mission Control:ホットコーナー
# 右下でスクリーンセーバー
defaults write com.apple.dock wvous-br-corner -int 5
defaults write com.apple.dock wvous-br-modifier -int 0
# スリープとスクリーンセーバの解除にパスワードを要求
defaults write com.apple.screensaver askForPassword -bool true
# パスワードを要求するまでの秒数
defaults write com.apple.screensaver askForPasswordDelay -int 5
# タップでクリックを許可
defaults write com.apple.AppleMultitouchTrackpad Clicking -bool "true"
defaults write com.apple.driver.AppleBluetoothMultitouch.trackpad Clicking -bool "true"
defaults -currentHost write -g com.apple.mouse.tapBehavior -bool "true"
# 二本指で右クリック
defaults write com.apple.AppleMultitouchTrackpad TrackpadRightClick -bool true
# カーソルの移動速度を変更 (1〜15)
defaults write -g com.apple.trackpad.scaling -float 10
# Finder:隠しファイル/フォルダを表示
defaults write com.apple.finder AppleShowAllFiles true
# Finder:拡張子を表示
defaults write NSGlobalDomain AppleShowAllExtensions -bool true
# バッテリを%表示
defaults write com.apple.menuextra.battery ShowPercent -string "YES"
# .DS_storeを作らない
defaults write com.apple.desktopservices DSDontWriteNetworkStores -bool TRUE
## 本体キーボードのCapsLockキーの動作をControlにリマップ
keyboard_id="$(ioreg -c AppleEmbeddedKeyboard -r | grep -Eiw "VendorID|ProductID" | awk '{ print $4 }' | paste -s -d'-\n' -)-0"
defaults -currentHost write -g com.apple.keyboard.modifiermapping.${keyboard_id} -array-add "
<dict>
<key>HIDKeyboardModifierMappingDst</key>\
<integer>30064771300</integer>\
<key>HIDKeyboardModifierMappingSrc</key>\
<integer>30064771129</integer>\
</dict>
"
{{ end }}
以下がdotfilesの反映前にHomebrewとzinitをインストールするためのスクリプトです。プレフィックスを run_once_before_ とすることでdotfilesの変更前に1回だけ実行します。
#!/bin/bash
set -e
{{ if eq .chezmoi.os "darwin" }}
# Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# zinit
bash -c "$(curl --fail --show-error --silent --location https://raw.githubusercontent.com/zdharma-continuum/zinit/HEAD/scripts/install.sh)"
{{ end }}
そして、以下がdotfilesの反映後に諸々ツールをインストールするためのスクリプトです。こちらは run_once_after_ というプレフィックスを用いて、dotfilesの反映後に1回だけ実行します。
{{- if eq .chezmoi.os "darwin" -}}
#!/bin/zsh
set -e
# Homebrew
brew bundle --global
# mise
mise install
# LazyVim
rm -rf ~/.config/nvim.bak
mv ~/.config/nvim{,.bak}
git clone https://github.com/LazyVim/starter ~/.config/nvim
rm -rf ~/.config/nvim/.git
{{- end -}}
Homebrewとmiseで大半のツールがインストールできるので、かなり楽になりました。
他のOSとdotfilesを共用する
chezmoiは ~/.local/share/chezmoi/.chezmoiignore というファイルを作成することで、 chezmoi apply 時に無視するファイルを指定できます。
https://www.chezmoi.io/user-guide/manage-machine-to-machine-differences/#ignore-files-or-a-directory-on-different-machines
このファイルにもtemplateが使え、OSに応じて無視するファイルを変えることで他のOSのdotfilesを同じリポジトリで管理できます。
{{ if ne .chezmoi.os "darwin" }}
.Brewfile
{{ end }}
{{ if eq .chezmoi.os "darwin" }}
.bashrc
.bash_profile
{{ else }}
.zshrc
.zprofile
{{ end }}
ちなみに、OSによって反映するファイルそのものを切り替えたい場合はサフィックスにOSを指定することができます。
GitHubへのpush
管理対象のdotfilesをGitHubにpushします。 chezmoi コマンドの後ろ側にGitコマンドを付ければOKです。
commit の前に -- が必要な点のみ注意しましょう。
$ chezmoi git status
$ chezmoi git add .
$ chezmoi git -- commit -m 'Initialize.'
$ chezmoi git push origin master
push前に秘匿情報が含まれていないこと、remoteに指定したリポジトリが間違えていないことを確認するようにしましょう。
まとめ
実際にはすぐにPCを買い替えるというわけではありませんが、今回dotfilesの整理ができたことで「最悪使っているPCが壊れても割と簡単に環境を復旧できる」という安心感が持てました。
chezmoi自体さまざまな仕組みを兼ね備えていて、ドキュメントを読み込めば読み込むほどやれることが増えていくので、使いながらさらにドキュメントを読み込んでいきたいです。