dotfiles のリポジトリ管理で悲劇を防ぐために

この記事は [ニフティグループ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/nifty) 22日目の記事です。
昨日は @shotaws さんの [TerraformでALBの高度なリスナールールがより簡潔に書けるようになる前の話](https://qiita.com/shotaws/items/0519d3eb2c768456cfee) でした。
自分のチームはあまりまだ IaC が浸透してないので、刺激になりますね！

---

## はじめに

dotfiles をリポジトリ管理しておくと各環境で同じ設定ファイルが利用できて色々と捗る。
実際に dotfiles を反映する際、既定のパスに設定ファイル本体を置く代わりにシンボリックリンクを作成している人は多いと思う。

自分も dotfiles を作成してその利便性を享受し始めていた頃、悲劇は突然やってきた。

## 悲劇その1

```bash
$ ls -l ~/.zshrc
-rw-r--r-- 1 hamakou108 5811 Dec 21 16:18 /Users/hamakou108/.zshrc
$ ln -sf ~/my/dotfiles/.zshrc ~/.zshrc
$ ls -l ~/.zshrc
lrwxr-xr-x 1 hamakou108 91 Dec 21 16:24 /Users/hamakou108/.zshrc -> /Users/hamakou108/my/dotfiles/.zshrc
```

おわかりいただけただろうか...？

リポジトリ管理された `~/my/dotfiles/.zshrc` のリンク `~/.zshrc` を作成しようとしている。
このとき `ln` に `-f` オプションを付与して実行しているため、**既存の .zshrc が上書き**されてしまっている。
リポジトリ管理する前に書き溜めた設定たちは、もう戻ってこない...。

## 悲劇その2

```bash
$ ls -la ~/.config/nvim
lrwxr-xr-x 1 hamakou108 86 Dec 21 16:20 /Users/hamakou108/.config/nvim -> /Users/hamakou108/my/old/dotfiles/config/nvim/
$ ln -sf ~/my/new/dotfiles/config/nvim ~/.config/nvim
$ ls -la ~/.config/nvim
lrwxr-xr-x 1 hamakou108 86 Dec 21 16:20 /Users/hamakou108/.config/nvim -> /Users/hamakou108/my/old/dotfiles/config/nvim/
$ ls -la ~/my/old/dotfiles/config/nvim
-rw-r--r-- 1 hamakou108 1093 Jun 10  2019 init.vim
lrwxr-xr-x 1 hamakou108    4 Dec 21 17:24 nvim -> /Users/hamakou108/my/new/dotfiles/config/nvim/
```

おわかりいただけただろうか...？

元々 `~/my/old/dotfiles/config/nvim` というディレクトリに向けられたリンク `~/.config/nvim` があり、その向き先を別のディレクトリ `~/my/new/dotfiles/config/nvim` に変更しようとしている。
しかしリンクの向き先は変更されておらず、代わりに `~/my/old/dotfiles/config/nvim` の直下にリンクが作成されてしまっている。

ディレクトリのリンクを更新する場合、 `-n` オプションを付与してリンクを通常ファイルと同様に扱う必要がある。
`-n` オプションがない場合は通常のディレクトリと同様に扱われるため、上記のような挙動となってしまう。

参考: [linux \- Why create a link like this: ln \-nsf? \- Super User](https://superuser.com/questions/81164/why-create-a-link-like-this-ln-nsf/81168)

## 悲劇を繰り返さないために

リンク作成をスクリプトで自動化する。

```bash:sample.sh
#!/usr/bin/env bash

function make_link () {
    src=$1
    dst=$2

    # $dst がシンボリックリンクでない場合は終了
    if [[ -e $dst && ! -L $dst ]]; then
        echo "$dst is not a symbolic link"
        return 1
    fi

    # $dst が通常のファイルかディレクトリかに応じて
    # 付与するオプションを変更
    if [[ -f $src ]]; then
        ln -sf $src $dst
    elif [[ -d $src ]]; then
        ln -nsf $src $dst
    fi
}

dotfiles_dir="${HOME}/my/new/dotfiles"
make_link ${dotfiles_dir}/config/zsh/zshrc ~/.zshrc
make_link ${dotfiles_dir}/config/nvim      ~/.config/nvim
```

実行してみる。

```bash
$ ls -la ~/.zshrc ~/.config/nvim
lrwxr-xr-x 1 hamakou108   86 Dec 21 18:05 /Users/hamakou108/.config/nvim -> /Users/hamakou108/my/old/dotfiles/config/nvim/
-rw-r--r-- 1 hamakou108 5811 Dec 21 18:05 /Users/hamakou108/.zshrc
$ ./sample.sh
/Users/hamakou108/.zshrc is not a symbolic link
$ ls -la ~/.zshrc ~/.config/nvim
lrwxr-xr-x 1 hamakou108   86 Dec 21 18:08 /Users/hamakou108/.config/nvim -> /Users/hamakou108/my/new/dotfiles/config/nvim/
-rw-r--r-- 1 hamakou108 5811 Dec 21 18:05 /Users/hamakou108/.zshrc
```

既存の設定ファイルは上書きれておらず、ディレクトリのリンクも想定通りに更新された。

## まとめ

- シンボリックリンクの扱いには気をつけよう。
- 快適な dotfiles ライフを！

---

明日は @sasaki_17 さんです。
よろしくお願いします！