はじめに
初めまして、Qiita での初投稿となります。よろしくお願いします 😸
新しい Macbook Pro を買って、環境設定している最中です。
そこで、元の開発環境は「ホームディレクトリ(以降 $HOME
)には dotfiles が多すぎてめちゃくちゃだ!」と気づきました。Dotfiles を効果的に管理する方法について調べて、実際に試してみた結果をメモしておきたいと思います。
私の環境
OS - macOS Monterey Version 12.6.3
Terminal - iTerm2 Build 3.4.19
Shell - zsh 5.8.1 (x86_64-apple-darwin21.0)
そもそも dotfile とは?
Dotfile(ドットファイル)とは、ファイル名がドットで始まるファイルのことを指します。通常、ユーザー固有のアプリケーション設定に使われます。例えば .bashrc
、.vimrc
、.gitconfig
などが該当します。
私の $HOME にはたくさんの dotfiles があります。数えてみましょう:
$ find ~ -maxdepth 1 -name '.*' -exec basename {} ";" | wc -l
なんと、113 個もの dotfiles が散らばっています 🙀
自分の部屋や仕事用の机は整理するように、自分の $HOME
も整頓したほうがいいと思います。
それでは、どうやって dotfiles を整頓し、効率的に管理するかを模索していきましょう。
Dotfiles の整頓およびバージョン管理の手順
-
$HOME
に dotfiles を格納するためのをフォルダー作成する - Dotfiles をフォルダーに移動させ、ファイルの場所変更にも対応させる
- YADM を利用して dotfiles をバージョン管理を行う
1. $HOME
に dotfiles を格納するためのをフォルダー作成する
最初に、散らばっている dotfiles をフォルダーにまとめて整頓します。
では、どういう基準で dotfiles を分類すればいいですか?もちろん、各 dotfile を使用するソフトウェアによって分類してもいいものの、ここでは広く使われている仕様である「XDG Base Directory Specification」に従って分類しましょう。
XDG Base Directory Specification
「XDG Base Directory Specification(以降 XDG 仕様)」というのは、X Window System や systemd などを生み出したプロジェクトである「freedesktop.org」が定めた、dotfiles の管理方法に関する仕様です。この仕様により、dotfiles はその使用目的に応じて異なる場所に保存されることとなりました。
この仕様は、主に 5 つの使用目的に基づいて dotfiles を分類しています:
使用目的 | 環境変数 | デフォルト値 | 説明 |
---|---|---|---|
config | $XDG_CONFIG_HOME |
$HOME/.config/ |
設定ファイル |
cache | $XDG_CACHE_HOME |
$HOME/.cache/ |
重要でないファイル(キャッシュ) |
data | $XDG_DATA_HOME |
$HOME/.local/share/ |
データファイル |
state | $XDG_STATE_HOME |
$HOME/.local/state/ |
状態ファイル |
runtime | $XDG_RUNTIME_DIR |
ランタイムファイルやその他のファイルオブジェクト |
$XDG_RUNTIME_DIR
はユーザー固有のもので、今回の dotfiles を管理する上では関係ない話になるため、飛ばしましょう。興味のある方は仕様を読んでください。
Dotfiles を整理する前に、まずはそれらのフォルダーを用意しましょう。
$ mkdir -p $HOME/.config/ $HOME/.cache/ $HOME/.local/share/ $HOME/.local/state/
2. Dotfiles をフォルダーに移動させ、ファイルの場所変更にも対応させる
フォルダーを作った後は、ソフトウェアごとに dotfiles を相応するフォルダーに移動させます。
すでに XDG 仕様に対応しているソフトウェアに対しては、そのまま移動するだけでは問題ない一方、そうじゃないソフトウェアには少し工夫が必要です。たとえば、ソフトウェアが使う環境変数を調整して、dotfiles の場所変更に対応させるか、alias を作ってコマンドラインオプションで dotfiles の場所を指定するなどの工夫が求められます。
そこで、より効率的に作業を進めるようなツールを紹介します。
xdg-ninja
XDG 忍者?忍術でも使いますか?いや違います。
A shell script which checks your $HOME for unwanted files and directories.
xdj-ninja の README より引用
xdg-ninja というのは、$HOME
には不要なファイルやディレクトリを見つけて教えてくれる、すごく便利なシェルスクリプトです。
「$HOME
には不要」?$HOME
に保存して何が悪いですか?
Because you wouldn't let just anyone into your $HOME
引用元:同上
訳:なぜなら、自分の家には誰にでも入らせるわけにはいかないからです。
ふざけた話はさておき、実際にインストールして使ってみましょう。
$ brew install --formula xdj-ninja
$ xdj-ninja
画像のように、xdj-ninja さんは $HOME
に存在する dotfiles を検出してくれます。さらに、xdg-ninja に収録されているソフトウェアには、どうすれば XDG 仕様に対応するかまでも教えてくれます。
これらを一つずつ解決していきましょう。まずは、一番よく使っているシェルから始めます。
ZSH
私は普段 zsh を使っています。xdg-ninja さんにどう対応すればいいか聞きましょう!
私の $HOME
には zsh の関連ファイルは主に 3 種類があります。
A. 起動時に読み込まれるファイル(zsh startup files)
- .zshenv
、.zshrc
B. コマンド履歴を記録するヒストリファイル
- .zsh_history
C. 補完システムに使われているキャッシュファイル
- .zcompdump
xdg-ninja の指示通りに、それぞれのファイルを対処しましょう。
A. 起動時に読み込まれるファイル
起動時に読み込まれるファイルは XDG 仕様では設定ファイルとして分類されます。
そのため、zsh 関連の設定ファイルは $XDG_CONFIG_HOME/zsh/
に移動させることになります。
xdj-ninja さんのアドバイスも確認しましょう。
[zsh]: $HOME/.zshenv
Move file to *"$HOME"/.config/zsh/.zshenv* and export the following environment variable in */etc/zsh/zshenv*:
export ZDOTDIR="$HOME"/.config/zsh
まずはファイルを "$HOME"/.config/zsh/.zshenv
に移動させて、次に /etc/zsh/zshenv
の中で $ZDOTDIR
の環境変数も変更します。
/etc/zsh/zshenv
vs /etc/zshenv
ここは /etc/zsh/zshenv
と書いてあるけど、私の環境(macOS)では /etc/zshenv
を使います。
これは $ man zsh
で確認できます。
では /etc/zsh/zshenv
と /etc/zshenv
はどう違うかというと、
The default directory for the 'global' config files in the zsh repository is simply /etc, and this is what you'll see e.g. on the Web site, but the paths can all be changed when the shell is built. Several Linux distributions use /etc/zsh. The documentation should reflect the configured paths; if your distribution's man pages don't do that they might be building it weirdly.
Reddit の /etc/zshenv vs /etc/zsh/zshenv より引用
どうやらシェルはビルド時にグローバル設定のパスを変えられるようです。いくつかの Linux ディストリビューションでは /etc/zsh/
を使うが、私の環境では /etc/
を使います。みなさんも確認してみてくださいね。
ちなみに xdg-ninja の開発者 b3nj5m1n さんは macOS のユーザーではないです。(本人:I'm not a mac user)おそらくそれで対応していないでしょう。
$ZDOTDIR
zsh では $ZDOTDIR
という環境変数が存在します。
The directory to search for shell startup files (.zshrc, etc), if not $HOME.
zsh のドキュメントより
.zshrc
など、シェル起動時に読み込まれるファイルを検索するディレクトリは $ZDOTDIR
で指定できます。
手順
-
.zshenv
に XDG 仕様の環境変数を追加する# ~/.zshenv export XDG_DATA_HOME=$HOME/.local/share/ export XDG_CONFIG_HOME=$HOME/.config/ export XDG_STATE_HOME=$HOME/.local/state/ export XDG_CACHE_HOME=$HOME/.cache/
-
/etc/zshenv
で$ZDOTDIR
を変更する
(ここは OS によって違うから、「 /etc/zsh/zshenv vs /etc/zshenv 」を読んでから各自の判断でやってください)$ echo "export ZDOTDIR=$HOME/.config/zsh" | sudo tee -a /etc/zshenv $ sudo chmod 444 /etc/zshenv # /etc/zshrc の権限を参照して設定した
-
zsh 起動時に読み込まれるファイルを
$ZDOTDIR
に移動させる$ mkdir -p ~/.config/zsh $ for zsh_startup_files in {.zshenv,.zprofile,.zshrc,.zlogin,.zlogout}; do mv ~/$zsh_startup_files ~/.config/zsh/; done;
source
コマンドで変更を適用させるか、シェルを再起動しなければならないが、これらの変更は全て終わってからやりましょう。
B. コマンド履歴を記録するヒストリファイル
A のように、ヒストリファイルの位置を指定する環境変数 $HISTFILE
を変更して、移動させれば良いのです。
-
zsh 起動時に読み込まれるファイルに
$HISTFILE
を設定する
通常、環境変数は.zshenv
に設定すべきだが、どうやら Apple さんは/etc/.zshrc
にて$HISTFILE
を設定したようです。ファイルが読み込まれる順番を考慮して、$XDG_CONFIG/zsh/.zshrc
に設定することにします。また、皆さんの環境は異なるかもしれないので、自身の判断で適切な場所に設定を入れてください。以下は私の history 関連の設定例です。# $XDG_CONGIG/zsh/.zshrc ### HISTORY ### export HISTFILE=$XDG_STATE_HOME/zsh/history (( HISTSIZE = (2 ** 31) - 1 )) # Number of history can be saved in memory (( SAVEHIST = (2 ** 31) - 1 )) # Number of history can be saved in HISTFILE setopt INC_APPEND_HISTORY # Write to the history file immediastely, not when the shell exits. setopt SHARE_HISTORY # Share history between all sessions. setopt HIST_IGNORE_DUPS # Don't record an entry that was just recorded again. ### HISTORY ###
-
$HISTFILE
を移動させる$ mkdir -p ~/.local/state $ mv $HISTFILE ~/.local/state
C. 補完システムに使われているキャッシュファイル
zsh の補完システムについてはドキュメントを読んでください。ここで言いたいのは、compinit
コマンドを実行すると、.zcompdump
というキャッシュファイルが生成されることです。さらに -d
フラグを付けば、生成されるファイルの場所を指定できることです。
$ compinit -d some/random/path/for/cache/file
私はデフォルトシェル zsh の他に、zsh のフレームワークである Oh My Zsh やプラグインマネージャーである antigen を使っています。これらのソフトウェアは内部で compinit
を呼び出すため、キャッシュファイルが生成されます。
ここで触れた内容はあくまで zsh 関連の dotfiles の整理に焦点を当てているので、他のソフトウェアによって生成されたファイルについては割愛させていただきます。気になる方は、それぞれのソフトウェアの GitHub レポジトリを調べてみてください。例えば Oh My Zsh にはキャッシュファイルの生成位置を変えるための環境変数が提供されています。
最後に、シェルを再起動すれば、zsh 関連の dotfiles の整理が終わります。
他のソフトウェアの dotfiles も同様の手順で対応できると思います。
もし dotfiles の場所変更に対応するための環境変数が提供されていないが、コマンドラインにはオプションで dotfiles の位置を指定できる場合は、下記の例のように alias を作りましょう:
$ alias command="command --config-file $XDG_CONGIG/command/.config"
環境変数もオプションも提供されていない場合は、自分で hack して解決するか、そのソフトウェアだけに対して dotfiles の移動を諦めましょう 💦
3. YADM を利用して dotfiles をバージョン管理を行う
Dotfiles の整頓が済んだら、最後はバージョン管理をします。
なぜ dotfiles をバージョン管理するのでしょうか?その理由は大きく 2 つあります:
- 変更の追跡・比較・復元:過去の設定変更を追跡し、変更の差分を確認したり、誤って行った変更を簡単に元に戻したりすることができる。
- 複数デバイスの同期:複数のデバイスの間で設定を簡単に同期させることができる。
YADM
そこで YADM (Yet Another Dotfiles Manager) の出番です。
GNU Stow との比較
ここで疑問を抱く人もいるかもしれません。
dotfiles を管理する手段としてよく使われている GNU Stow があります。では、なぜわざわざ YADM を選ぶのでしょうか?
GNU Stow を始めたいくつかのツールは symlink で dotfiles を管理しています。そのため、まず dotfiles を特定のディレクトリーに移動させる必要があります。それに対して、YADM は dotfiles の配置場所に制約を受けずに管理できます。この記事では XDG 仕様で dotfiles を整頓しているため、この点において YADM は有利です。
git との比較
では git を使うのはどうでしょうか?
実際、YADM は git のラッパーです。yadm
のサブコマンドは基本的に git
と同じように使用します。branch、merge、rebuse、submodule などの機能がすべて使えます。
git を直接に使うよりも、YADM を使った方がいくつかの利点があります。
まず、YADM はデフォルトで $XDG_CONFIG_HOME/yadm/repo.git
にベアリポジトリ(bare repository)を作って、それをデフォルトのリポジトリとして使います。そのため、現在のディレクトリが git リポジトリであるかどうか、また現在のディレクトリがどこであるかに関わらず、yadm
の操作を行うことができます。さらに、デフォルトリポジトリがベアリポジトリとなるため、ユーザーが追加したファイルのみが git の追跡対象になります。もし $HOME
内で通常の git リポジトリを作ったら、そのリポジトリは再帰的に $HOME
内の全てのファイルが追跡対象にすることになります。
実際に YADM を試してみよう
実際に YADM を使って、先ほど整頓した zsh の dotfiles をバージョン管理してみましょう:
$ brew install --formula yadm
$ cd ~
$ yadm init
$ yadm add $XDG_CONFIG_HOME/zsh
$ yadm commit
# GitHub で新しくリポジトリを作成してください。
$ yadm remote add origin [your remote repository]
# Push する前には Git やリモートリポジトリの設定を済ましておいてくださいね。
$ yadm push -u origin main
これによって、zsh の dotfiles が YADM でバージョン管理されるようになります。
以降は、いつも git
を操作するように yadm
を使えば、ファイルの変更を追跡したり、同期させたりすることが楽にできます。
YADM の他の便利な機能
実は YADM はバージョン管理の他に、いくつか便利な機能も提供しています。
1. Bootstrap
今 dotfiles は $ yadm clone
で簡単に同期させることができるものの、多くの場合、環境設定はそれだけじゃ終わりません。例えば、macOS の場合、Homebrew や homebrew
でソフトウェアをインストールする必要があるかもしれません。こうした環境設定はシェルスクリプトを書けば簡単にできますね。
このように、コマンドを自動的に読み込んで実行することは、英語では「Bootstrap」と言います。
YADM は $ yadm bootstrap
というサブコマンドを提供しています。これはデフォルトで $HOME/.config/yadm/bootstrap
のシェルスクリプトを実行します。
もし bootstrap ファイルが存在する場合、$ yadm clone
の操作が完了すると、bootstrap ファイルを実行するかどうかのプロンプトが表示されます。それで楽に環境設定できます。
Found .config/yadm/bootstrap
It appears that a bootstrap program exists.
Would you like to execute it now? (y/n)
2. 環境固有の dotfile
たまに、異なる OS、アーキテクチャ、ユーザーに対して異なる設定が必要な場合があります。YADM はこのようなケースに対して 2 つの方法を提供しています。
一つは、次のように一つの dotfile に対して複数のバージョンを作って、ファイル名の後ろに条件を付けます。
$HOME/path/example.txt##default
$HOME/path/example.txt##os.Darwin
$HOME/path/example.txt##os.Linux,hostname.host1
もう一つはテンプレートを使います。これは、dotfile の一部だけが変化した場合には便利です。
{% if yadm.os == "Darwin" %}
This block is included for MacOS
{% else %}
This block is included for any other OS
{% endif %}
3. 暗号化
SSH キーなどの機密ファイルはもちろんバージョン管理できます。しかし、平文のまま GitHub などの公開システムにプッシュするのはあまりにも危険です。YADM は簡単に機密ファイルを暗号化・復号できます。しかし、この機能は gpg
か openssl
コマンドが使える場合でしか使えないです。具体的な方法は YADM の公式ドキュメント を読んでください。
4. Hook
YADM には、git hook のように、hook を使うことができます。
例えば、通常 git リポジトリには README
と LICENSE
などのファイルが含まれています。しかし、そのままクローンすると、それらのファイルが $HOME
を汚します。そこで、post_clone の hook と sparse-checkout 機能を使えば、クローンが完了すると特定のファイルを削除することができます。
# ~/.config/yadm/hooks/post_clone
# このスクリプトは https://sspai.com/post/66894 より引用
# remove yadm readme
if [[ ! -f "${YADM_HOOK_REPO}/info/sparse-checkout" ]]; then
yadm gitconfig core.sparseCheckout true
cat << EOF > "${YADM_HOOK_REPO}/info/sparse-checkout"
# Generated by $0
/*
!README.md
!UNLICENSE
EOF
yadm checkout --quiet
fi
この例では、README.md
と UNLICENSE
ファイルを除いてすべてのファイルをクローンするための設定を行っています。これにより、必要なファイルだけが $HOME
内にコピーされるようになります。
より詳しい説明は YADM の公式ドキュメント を読んでください。
終わりに
初めて Qiita での投稿、最後まで読んでいただき、ありがとうございました!
自分の開発環境を整え、効率的に管理する方法について共有できて嬉しいです。
YADM についての記事は qiita でもおそらく初めてのものです。
この記事が他の方々の役に立ち、新しい知識やアイデアのひらめきを提供できることを願っています。
最後に私の dotfiles リポジトリを共有します。