どうも。
この記事は dotfiles Advent Calendar 2019 - Qiita の21日目の記事です。
タイトルの元ネタは有名な映画である「博士の異常な愛情 または私は如何にして心配するのを止めて水爆を愛するようになったか」です。
私の dotfiles である rokoucha/dotfiles が管理を開始した2017年からどのような遍歴をたどってきたのかについてお話させて頂きます。
私の dotfiels の歴史は大きく分けて5期に分けられます。
dotfiles を管理する前はどうしていたのか、その答えは「必要な物だけ Gist などに置いておく」でした。 例えば昔使っていた xmonad というウィンドウマネージャの設定ファイルは Gist に残っています。
記念すべき1期はただ dotfile を配置しただけの簡単な物でした。 その時のツリー を見ると、丁度 i3 を使い始めた頃という事が分かります。
自動セットアップに憧れてごにょごにょして心が折れたようで、ツリーにはインストールしてくれないインストールスクリプトが置いてあります。 その後、dotfile を追加したようで凄い量のファイルがツリーに出てきました。 この時は共通な dotfile と環境依存な dotfile を合成して各環境毎に最適な dotfiles を構築しようとしていたようです。
諦めが悪いのでまた自動セットアップに挑戦しました。ツリーの見た目はあまり変わらないですね。
このころから .zsh
フォルダに .zshrc
や .zshenv
などの内容を分割して配置し、自動ロードするようにしたようです。
この仕組みはまだ使っていて、ちょっと遅くなる問題がありますがそこそこ便利なのでオススメです。
また、セットアップスクリプトがやっと完成しました。このセットアップシステムは第1世代ですね。
なお、セットアップシステムの仕様については後で解説します。
この頃からまた複数環境に対応するシステムを構築しようと考え、uname
を見たりして特定の環境だけ追加ファイルを展開するようにしたりしました。
ツリーを見ると分かるとおり、基本的には共通化し、Arch Linux と WSL でどうしても環境依存になってしまうファイルだけを分けるようにしました。
セットアップシステムとしては第2世代となります。
他人の dotfiles と自分の dotfiles を見比べた時に、ファイルが散らばっていて見辛いと思うようになりました。 また、メンテナンスがめちゃめちゃダルいしなんかうまく展開出来ないという事が起きるようになりました。
「もうシェルスクリプトで管理するのはよくないのでは?」という悟りを開き、GNU Make に全てを委ねる事にしました。 ツリーを見ると分かる通り、最初期のように dotfiles がルートに配置されるようになり、見通しが良くなりました。
セットアップシステムは今までから刷新した第3世代です。
おまたせしました、セットアップシステムのお話です。
/dotroot
内のファイルを ~
にリンクしていくだけの簡単なスクリプトです。
おもしろい点としては、ファイルのみリンクしフォルダは mkdir
するようになっています。
というのも /.config
を直接リンクしてしまうと、dotfiles で管理していないファイルがリポジトリに混入してしまいます。
それを防ぐために、わざわざフォルダを全部掘ってファイルだけリンクするようになっています。
for file in `\find $dotfiles_path/dotroot -type f`; do
dotfile=${file#$dotfiles_path/dotroot/}
dotpath=$(dirname "$dotfile")
echo "Install $dotfile to $install_path/$dotfile"
mkdir -p "$install_path/$dotpath"
if [ -L $install_path/$dotfile ] ;then
# もし、dotfileがシンボリックリンクなら消す
rm "$install_path/$dotfile"
elif [ -f $install_path/$dotfile ] ;then
# もし、dotfileが既に存在するならバックアップ送り
mkdir -p $old_dotfiles_path
mv "$install_path/$dotfile" "$old_dotfiles_path/$dotfile"
fi
# 実際にリンクを張る
ln -s "$file" "$install_path/$dotfile"
done
なお実際には既に存在している場合の処理などもあるためかなり複雑なロジックになっています。 よく自分で書いたなと思います、もう書ける自信がないです…
またフックが用意されているので、ファイルの展開後にプラグインマネージャをインストールしたりといった事も自動化できます。
第1世代をベースに、uname
による環境の判定や環境ごとに展開する内容を変更するようにしました。
環境の判定は普通に正規表現にマッチするかチェックしてるだけです。
# Environment detector
if expr "$(uname -r)" : ".*Microsoft$" > /dev/null; then
# WSL
_ENV="WSL"
elif expr "$(uname -r)" : ".*ARCH$" > /dev/null; then
# ArchLinux
_ENV="Arch"
else
# Some linux
_ENV=""
fi
ファイルを展開する部分は環境毎にフォルダを分けたのでより複雑になりました。
共通なファイルは common
に、環境依存ファイルは適当な名前のフォルダに配置するようになっています。
そのため、どの環境でも common
だけは必ず展開するようになっています。
for env in "common" $_ENV; do
echo "# $env"
# shellcheck disable=SC2044
for app in $(find "$_DOTFILES/$env" -maxdepth 1 ! -path "$_DOTFILES/$env" -type d -exec basename {} \;); do
echo "Deploy $app:"
# shellcheck disable=SC2044
for file in $(find "$_DOTFILES/$env/$app" -type f); do
dotfile="${file#$_DOTFILES/$env/$app}"
dotpath=$(dirname "$dotfile")
echo " - $dotfile to $_INSTALL$dotfile"
mkdir -p "$_INSTALL/$dotpath"
if [ -L "$_INSTALL$dotfile" ] ;then
# Delete when target is symbolic link
rm "$_INSTALL$dotfile"
elif [ -f "$_INSTALL$dotfile" ] ;then
# Move when target is file
mkdir -p "$_OLD_DOTFILES/$dotpath"
mv "$_INSTALL$dotfile" "$_OLD_DOTFILES$dotfile"
fi
# Link file
ln -s "$file" "$_INSTALL$dotfile"
done
done
done
でかいですね…
GNU Make に全てを委ねるようにしました。
GNU Make はとても便利で内部コマンドだけでも dotfiles をリストアップできちゃうのですが、コマンドを駆使してより便利にしました。
# 除外するファイル/フォルダ
EXCLUSION := .git/\* docker-compose.yml Dockerfile LICENSE Makefile README.md
# dotfiles をリストアップ
DOTFILES := $(shell printf " ! -path $(DOTFILES_PATH)/%s" $(EXCLUSION) | xargs find $(DOTFILES_PATH) -type f | sed 's|^$(DOTFILES_PATH)/||')
普通に EXCLUSION
に指定されたファイル以外をリストアップしてるだけです。
展開はリストアップされたファイル一覧を内部コマンドの foreach
で回してフォルダを掘ってリンクするだけです。
なんと1行で出来ちゃうんです!
$(foreach dotfile,$(DOTFILES),mkdir -p "$(INSTALL_PATH)/$(dir $(dotfile))"; $(LN) "$(abspath $(dotfile))" "$(INSTALL_PATH)/$(dotfile)";)
便利な時代になりましたね…
この世代からはただファイルを展開するだけではなく、ソフトウェアのインストールも出来るようにしました。 タスクとして用意する事で、従来のフックよりも柔軟に指定する事が可能になりました。
例えば Arch Linux ユーザー御用達の yay は次のタスクでインストールできます。
yay: git ## Install Yay
@if ! type yay >/dev/null 2>&1; then \
$(eval YAY_TEMP := $(shell mktemp -d)) \
git clone https://aur.archlinux.org/yay.git "$(YAY_TEMP)"; \
sh -c "cd \"$(YAY_TEMP)\"; makepkg -sri --noconfirm"; \
rm -rf "$(YAY_TEMP)"; \
fi
yay -Syu --noconfirm
普通にコマンドをぺたぺた書くだけでタスクが作れちゃうんです!
グループ的な物も作れちゃいます。
##@ Group tasks
.PHONY: arch cli
arch-cli: yay docker git gnupg openssh vim xdg-user-dirs zsh ## Install Arch Linux CLI applications
cli: dircolos vundle zplugin zprezto asdf ## Install CLI applications
もうシェルスクリプトには帰れないですね…
また Docker でデバッグできるようにしたので、手元の環境を破壊しながらデバッグする必要もなくなりました。
最初の頃は何でもこの dotfiles で管理してやる!という気概があったのですが、今はほどほどに…と大分丸くなってしまいました。 まあそれだけ何でも管理するというのは難しいという事なんですよね、どんどん複雑化してしまうので…
GNU Make に委ねてからはある程度シンプルになって管理しやすくなったので、KISS の原則のありがたみを全身で味わってます。 皆さんも GNU Make に全てを委ねませんか?
冪等性を担保したいなーと思ってます。 冪等性を担保すればアンインストールも出来るようになりますし、他のタスクへの依存を明示できるようになるのでめちゃ便利になります。 あとは一回実行したタスクを何回も走らせなくて済むようにしたいですね、タスクを実行したかどうかをファイルがあるかで判別するのが一番楽なのですがファイル散蒔かれるのはちょっと…
Docker 化したので CI 回したりテスト実装したりもしたいところ。
やっぱり dotfiles は便利です、たった1つのコマンドで環境を構築してくれるので手軽に環境を破壊できます。 皆さんもご自慢の dotfiles を公開してみませんか?