14
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Dotfiles の整頓とバージョン管理:XDG Base Directory Specification と YADM の活用ガイド

Posted at

はじめに

初めまして、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

count-dotfiles.jpg
なんと、113 個もの dotfiles が散らばっています 🙀
自分の部屋や仕事用の机は整理するように、自分の $HOME も整頓したほうがいいと思います。

それでは、どうやって dotfiles を整頓し、効率的に管理するかを模索していきましょう。

Dotfiles の整頓およびバージョン管理の手順

  1. $HOME に dotfiles を格納するためのをフォルダー作成する
  2. Dotfiles をフォルダーに移動させ、ファイルの場所変更にも対応させる
  3. 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

telegram-cloud-photo-size-5-6138567233576744813-y.jpg

画像のように、xdj-ninja さんは $HOME に存在する dotfiles を検出してくれます。さらに、xdg-ninja に収録されているソフトウェアには、どうすれば XDG 仕様に対応するかまでも教えてくれます。

これらを一つずつ解決していきましょう。まずは、一番よく使っているシェルから始めます。

ZSH

私は普段 zsh を使っています。xdg-ninja さんにどう対応すればいいか聞きましょう!

2023-03-30 21.52.40.jpg
私の $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 で確認できます。

image.png
では /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 で指定できます。

手順

  1. .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/
    
  2. /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 の権限を参照して設定した
    
  3. 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 を変更して、移動させれば良いのです。

  1. 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 ###
    
  2. $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 つあります:

  1. 変更の追跡・比較・復元:過去の設定変更を追跡し、変更の差分を確認したり、誤って行った変更を簡単に元に戻したりすることができる。
  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 の場合、Homebrewhomebrew でソフトウェアをインストールする必要があるかもしれません。こうした環境設定はシェルスクリプトを書けば簡単にできますね。

このように、コマンドを自動的に読み込んで実行することは、英語では「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 は簡単に機密ファイルを暗号化・復号できます。しかし、この機能は gpgopenssl コマンドが使える場合でしか使えないです。具体的な方法は YADM の公式ドキュメント を読んでください。

4. Hook

YADM には、git hook のように、hook を使うことができます。

例えば、通常 git リポジトリには READMELICENSE などのファイルが含まれています。しかし、そのままクローンすると、それらのファイルが $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.mdUNLICENSE ファイルを除いてすべてのファイルをクローンするための設定を行っています。これにより、必要なファイルだけが $HOME 内にコピーされるようになります。


より詳しい説明は YADM の公式ドキュメント を読んでください。

終わりに

初めて Qiita での投稿、最後まで読んでいただき、ありがとうございました!
自分の開発環境を整え、効率的に管理する方法について共有できて嬉しいです。
YADM についての記事は qiita でもおそらく初めてのものです。
この記事が他の方々の役に立ち、新しい知識やアイデアのひらめきを提供できることを願っています。

最後に私の dotfiles リポジトリを共有します。

参考文献

  1. 使用 YADM 整理你的 dotfiles(中国語簡体字)
  2. XDG Base Directory Specification の仕様(英語)
  3. XDG 仕様 をサポートするソフトウェアのリスト(英語)
  4. 上記リストの日本語バージョン(日本語)
  5. xdg-ninja のリポジトリ(英語)
  6. zsh startup files のドキュメント(英語)
  7. [Reddit] /etc/zshenv vs /etc/zsh/zshenv(英語)
  8. YADM の公式サイト(英語)
14
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?