こんにちは、ナナオです。

今回はchezmoiというツールでdotfileを管理する基盤を作っていこうと思います。

chezmoiとは

WSLとかlinuxとかMacとか触ってると、.sshとか.zshrcとかいろいろ出来上がると思います。

chezmoiはそんなdotfileたちを管理するためのツールです。

chezmoi - chezmoi

インストール&ファイルを管理対象に含める

miseを使ってインストールします。

mise use -g chezmoi

brewでもインストールできます。

brew install chezmoi

インストールはこれで完了です。

コマンドを確認してみましょう。

❯ chezmoi
Manage your dotfiles across multiple diverse machines, securely

Usage:
  chezmoi [command]

Documentation commands:
  doctor               Check your system for potential problems
  help                 Print help about a command
  license              Print license

Daily commands:
  add                  Add an existing file, directory, or symlink to the source state
  apply                Update the destination directory to match the target state
  chattr               Change the attributes of a target in the source state
  diff                 Print the diff between the target state and the destination state
  edit                 Edit the source state of a target
  forget               Remove a target from the source state
  init                 Setup the source directory and update the destination directory to match the target state
  merge                Perform a three-way merge between the destination state, the source state, and the target state
  merge-all            Perform a three-way merge for each modified file
  re-add               Re-add modified files
  status               Show the status of targets
  update               Pull and apply any changes

Template commands:
  cat                  Print the target contents of a file, script, or symlink
  data                 Print the template data
  execute-template     Execute the given template(s)

Advanced commands:
  cd                   Launch a shell in the source directory
  edit-config          Edit the configuration file
  edit-config-template Edit the configuration file template
  generate             Generate a file for use with chezmoi
  git                  Run git in the source directory
  ignored              Print ignored targets
  managed              List the managed entries in the destination directory
  unmanaged            List the unmanaged files in the destination directory
  verify               Exit with success if the destination state matches the target state, fail otherwise

Encryption commands:
  age                  Interact with age
  age-keygen           Generate an age identity or convert an age identity to an age recipient
  decrypt              Decrypt file or standard input
  edit-encrypted       Edit an encrypted file
  encrypt              Encrypt file or standard input

Remote commands:
  docker               Use your dotfiles in a Docker container
  ssh                  SSH to a host and initialize dotfiles

Migration commands:
  archive              Generate a tar archive of the target state
  destroy              Permanently delete an entry from the source state, the destination directory, and the state
  import               Import an archive into the source state
  purge                Purge chezmoi's configuration and data
  upgrade              Upgrade chezmoi to the latest released version

Internal commands:
  cat-config           Print the configuration file
  completion           Generate shell completion code
  dump                 Generate a dump of the target state
  dump-config          Dump the configuration values
  secret               Interact with a secret manager
  source-path          Print the source path of a target
  state                Manipulate the persistent state
  target-path          Print the target path of a source path

Flags:
      --age-recipient string                           Override age recipient
      --age-recipient-file string                      Override age recipient
      --cache path                                     Set cache directory (default /home/banan/.cache/chezmoi)
      --color bool|auto                                Colorize output (default auto)
  -c, --config path                                    Set config file
      --config-format <none>|json|toml|yaml            Set config file format
      --debug                                          Include debug information in output
  -D, --destination path                               Set destination directory (default /home/banan)
  -n, --dry-run                                        Do not make any modifications to the destination directory
      --force                                          Make all changes without prompting
  -h, --help                                           help for chezmoi
      --interactive                                    Prompt for all changes
  -k, --keep-going                                     Keep going as far as possible after an error
      --less-interactive                               Prompt for changed or pre-existing targets
      --mode file|symlink                              Mode (default file)
      --no-pager                                       Do not use the pager
      --no-tty                                         Do not attempt to get a TTY for prompts
  -o, --output path                                    Write output to path instead of stdout
      --override-data string                           Override data
      --override-data-file path                        Override data with file
      --persistent-state path                          Set persistent state file
      --progress bool|auto                             Display progress bars (default auto)
  -R, --refresh-externals always|auto|never[=always]   Refresh external cache (default auto)
  -S, --source path                                    Set source directory (default /home/banan/.local/share/chezmoi)
      --source-path                                    Specify targets by source path
      --use-builtin-age bool|auto                      Use builtin age (default auto)
      --use-builtin-diff                               Use builtin diff
      --use-builtin-git bool|auto                      Use builtin git (default auto)
  -v, --verbose                                        Make output more verbose
      --version                                        version for chezmoi
  -W, --working-tree path                              Set working tree directory

Use "chezmoi [command] --help" for more information about a command.

なにやらいろいろ出力されました。

早速dotfileを管理してみましょう。

以下のコマンドを実行して初期化します。

❯ chezmoi init
chezmoi: mkdir /run/user/1000/: permission denied

あら、エラーが出てしましました。

どうやらWSL固有の問題っぽいです。

以下のようにXDG_RUNTIME_DIR環境変数の指定を空にして実行します。

XDG_RUNTIME_DIR="" chezmoi init

実行に成功しました。

毎回設定するのは面倒なので、miseで環境変数として登録しておきましょう。

mise set -g XDG_RUNTIME_DIR=""

そしたら早速dotファイルを追加してみます。

chezmoi add ~/.tigrc

追加したファイルを管理対象から外すにはforgetコマンドを使用します。

chezmoi forget ~/.tigrc

今回はこの状態で変更を適用しましょう。

ここでいう変更の適用とは、chezmoiで管理しているファイル(先ほどの例だと.tigrc)の中身を、chezmoiのディレクトリ内のファイル(dot_tigrcという名前で保存されている)と同期させるという意味です。

chezmoi apply

一通りの操作はこれで完了です。

chezmoiの実態をgithubで管理する

さて、先ほどの手順でローカル上のchezmoiのディレクトリに、管理したいdotfileを含めることができました。

あとはこれをgithub上に上げれば、ほかのPCからも参照可能になります。

ということで上げていきましょう。

chezmoi cd # chezmoiのディレクトリに移動
gh repo create # リポジトリを作成
git branch -m main # ブランチ名を変更
# コミットとプッシュ
git add --all
git commit -m "first commit"
git push origin main

ghコマンド使うと楽に作成できますね。

もう一台のPCでchezmoiを使う

では、環境を変えてもう一台でもchezmoiをセットアップしていきます。

インストールは同じくmiseを使って行いました。

initコマンドの実行時に、先ほど作成したGithubのリポジトリを参照するようにします。

chezmoi init git@github.com:satodaiki/chezmoi-dotfiles.git

あとはupdateをすれば、先ほどの設定が適用されます!

chezmoi update

ファイルが競合する場合

.zshrcといったファイルは個別の開発環境ごとに記載内容がブレることがあります。

そういった場合はmergeコマンドを使って解決するか、templateを使います。

今回はmergeを使った方法を試します。

まずはそれぞれの環境で違う内容を記載したdotfile(.zshrc)を追加します。

chezmoi add ~/.zshrc

プッシュを行います。

chezmoi git commit -- -m "Add zshrc"
chezmoi git push origin main

次に別PCで作業します。

ここでupdateをしてしまうと、別PCの.zshrcの内容が消失してしまうので気を付けましょう。

(私はやらかしてしまいました、実行前にcatで表示しておいたおかげで助かりましたが。。)

既存のファイルがある場合は必ず以下のようにバックアップを取ってからupdateを実行してください。

cp ~/.zshrc ~/.zshrc.bak

updateを実行します。

chezmoi update

こうすると~/.zshrcが上書きされます。

一度~/.zshrc.bakの内容を~/.zshrcに書き換えます。

この状態でdiffを見てみましょう。

❯ chezmoi diff
diff --git a/.zshrc b/.zshrc
index b7188ab14736522d260ea61aefd778622270fa5a..07241a3d3cd3add88186694ae8a33022eb87c3f1 100644
--- a/.zshrc
+++ b/.zshrc
@@ -1,39 +1,33 @@
-# homebrewの設定
+# brewの設定
 eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"

-# miseのアクティベート
+# miseの設定
 eval "$(mise activate zsh)"

 # znapの設定
+# https://neer-engineer.com/525df03075fc272fc364d71a58b9f5a6/
 source ~/.znap/znap.zsh
-
-# リポジトリの設定
-# これがないとホームディレクトリ上にプラグインなどのリポジトリフォルダが出来上がってしまう
 zstyle ':znap:*' repos-dir ~/.znap/repos
-
-# プロンプトの設定
-# ohmyzshのテーマを使いたい場合は以下のように実装する
-# znap prompt ohmyzsh/ohmyzsh random
 znap prompt sindresorhus/pure
-
-# ohmyzsh関連の設定
-znap source ohmyzsh/ohmyzsh plugins/git
-znap source ohmyzsh/ohmyzsh plugins/docker
-znap source ohmyzsh/ohmyzsh plugins/brew
-
-# プラグインの設定
-znap install zsh-users/zsh-completions
 znap source marlonrichert/zsh-autocomplete
 znap source zsh-users/zsh-autosuggestions
 znap source zsh-users/zsh-syntax-highlighting
+znap source zsh-users/zsh-completions
 znap source hlissner/zsh-autopair
-znap source marlonrichert/zsh-edit

 # kubectlの設定
 source <(kubectl completion zsh)
+znap source ohmyzsh/ohmyzsh plugins/kubectl
+
+# terraformの設定
+autoload -U +X bashcompinit && bashcompinit
+complete -o nospace -C /home/linuxbrew/.linuxbrew/Cellar/terraform/1.14.3/bin/terraform terraform
+znap source ohmyzsh/ohmyzsh plugins/terraform

-# opensslの設定
-export PATH="/home/linuxbrew/.linuxbrew/opt/openssl@1.1/bin:$PATH"
+# awsの設定
+autoload bashcompinit && bashcompinit
+autoload -Uz compinit && compinit
+complete -C '/home/linuxbrew/.linuxbrew/bin/aws_completer' aws

-# OpenCodeの設定
-source <(opencode completion zsh)
+# giboの設定
+source <(gibo completion zsh)

うーん、かなりいろいろ違いますね。

この状態でmergeコマンドを実行すると、vimdiffが起動します。

chezmoi merge ~/.zshrc

…が、vimdiffは難しいので手動でマージします。

一旦chezmoiのdot_zshrcと同期しましょう。

chezmoi re-add ~/.zshrc

マージ作業が終わったらリモートリポジトリにプッシュします。

chezmoi git add -- --all
chezmoi git commit -- -m "Merge zshrc"
chezmoi git push

ファイル競合は解決できました!

(スマートな方法ではないけど。。)

感想

簡単にdotfileを管理することができました。

ただファイル競合に関してはもっとスマートな方法で解決したかった。。

追記

別PCからchezmoi updateをしようとしたところ、以下のようなエラーになりました。

❯ chezmoi update
There is no tracking information for the current branch.
Please specify which branch you want to rebase against.
See git-pull(1) for details.

    git pull <remote> <branch>

If you wish to set tracking information for this branch you can do so with:

    git branch --set-upstream-to=origin/<branch> main

chezmoi: git: exit status 1

おそらく内部的にgit pullコマンドが走っているからでしょう。

エラー出力にもある通り、以下のようなコマンドを打てば解決します。

git branch --set-upstream-to=origin/main main

参照

https://zenn.dev/ryo_kawamata/articles/introduce-chezmoi

chezmoiでdotfilesを1年間運用した知見について | Wantedly Engineer Blog