最近実践している OpenSSH の設定ファイルの分割

OpenSSH の設定ファイル `~/.ssh/config` のファイル分割について考察してみることにしました。

* 2016年8月リリースの OpenSSH 7.3 から Include キーワードが導入されています（[参考](https://qiita.com/masa0x80/items/ecb692ad93f7d06a07b0)）。この記事は、古い OpenSSH の環境への配慮などから、Include キーワードを使わない方法をそのまま解説しています。

# 世間で模索される設定ファイルの分割

古めの OpenSSH の設定ファイルは分割だったり外部ファイル読み込みをサポートしていません。Apache の Include ディレクティブのようなものがあるといいのですが、現状では分割管理したい人が独自に色々な手法を試みているようです。

* [ssh config を分割する - Qiita](http://qiita.com/shohhei1126/items/5d559556403c87d0065a)
* [.ssh/configをファイル分割管理するようにした - Qiita](http://qiita.com/tukiyo3/items/6afb1e9c8e2ca411bc47)
* [~/.ssh/configを分割して管理する - Qiita](http://qiita.com/tumf/items/73e495e1274bc25acf5f)
* [[Mac]シロク流、sshの設定ファイル分割管理 - Qiita](http://qiita.com/shuntaro_tamura/items/8509cd84f0938b8f9903)

だいたいの手法は以下のように分別できます。

* 分割管理したファイルを手作業で `~/.ssh/config` に結合する手法
* 分割管理したファイルを自動的に `~/.ssh/config` として結合する `ssh` コマンドなどの上書き or 別定義
* 別の設定ファイル体系を持つ `ssh` をベースとした自作別コマンド

`alias ssh='cat ~/.ssh/conf.d/*.conf > ~/.ssh/config ; ssh'` など、結局は裏で `~/.ssh/config` を作る方法に収斂されるわけですが、いくつか悩ましい部分があります。

* `~/.ssh/config` が作られたまま残ってしまうので、うっかりそちらを編集してしまい、分割したファイルのメンテナンスを忘れることがある
  * 意識の問題でもある
  * `cat` で結合生成して `ssh` した後に `rm` すればいいという意見も
* 既に `~/.ssh/config` があって結合の必要がない場合も結合することを容認しても、`/.ssh/config` のタイムスタンプ (mtime) に惑わされることもある

個人のコマンドなので結合コストの問題は無視したとしても、大元のデータがどこであるとかメタデータであるとかで油断したときに混乱させられるのは嫌だなぁと感じました。

`~/.ssh/config` を恒久的に置かないという作戦であれば

```bash
function ssh {
  cat ~/.ssh/conf.d/*.conf > ~/.ssh/config
  builtin ssh "$@"
  rm ~/.ssh/config
}
```

といった方法や、さらにコマンド内でファイルディスクリプタを作って捨てるような

```bash
function ssh {
  builtin ssh -F <(cat ~/.ssh/conf.d/*.conf) "$@"
}
# または
alias ssh='ssh -F <(cat ~/.ssh/conf.d/*.conf)'
```

といった方法もありそうです。

`<()` オペラントを知るなら `echo <(:)` といったコマンドを打ってみるとよいでしょう（: は[何もしない bash 組み込みコマンド](http://qiita.com/xtetsuji/items/381dc17241bda548045d)）。

ただ、私は分割した設定ファイルをバージョン管理していることもあり、結合は意識的にやったほうがあっているようでした。また、`ssh` コマンド類を上書き定義している or 別のラッパーコマンドがあるというのもしっくりこなかったです（別の環境で混乱しそうとか）。

# 設定ファイルを分割するメリット

普通には分割はできないわけで手間がデメリットだったりする設定ファイルの分割ですが、メリットもあるので行いたいという人もいます。私が考えるメリットを列挙してみます。

* 接続ホストが大量になってくると、設定ファイルを分割することで見通しが良くなる
* バージョン管理時には、大きなファイルを管理するよりも小さな複数のファイルを管理する方がコンフリクトが起こりづらかったりといったメリットがある
* 他人に OpenSSH の設定ファイルを例示する際、開示してはいけないところを回避しつつ開示して良いところだけをすぐに出しやすい
* ファイル分割という習慣だけで設定の片付けができる（一枚ファイルに書いていく方法だとどうしても散らかりやすい）

設定ファイルのバージョン管理をしている人は最近では珍しくなくなりましたが、特に他人に例示というところは意外なメリットだったりします。

# 私の設定ファイル管理

なるべくシンプルに、意識的な手間と利便性と環境依存性の少なさを取った方法を模索していますが、今のところは以下の様な方法をとっています。

## バージョン管理

2004年くらいから CVS を使って雑にバージョン管理を行っていましたが、最近は Git でバージョン管理をしています。秘匿性の少ないドットファイル類は GitHub で公開していますが、OpenSSH のような秘匿性の高いものは BitBucket でプライベートリポジトリを作って管理しています。

今の OpenSSH 設定ファイルは ssh_config という BitBucket プライベートリポジトリで以下のような感じ。

![Bitbucket の ssh_config リポジトリ.png](https://qiita-image-store.s3.amazonaws.com/0/8760/fd452d6f-2a48-3bfa-8794-f4cd5f709685.png)

使いたい場合には初回に

```shell-session
$ cd ~/.ssh/ ; git clone git@bitbucket.org:xtetsuji/ssh_config.git vc
```

などしています。ワーキングディレクトリは `vc` とか目立つ短いものにしておくといいかなという考えです。後々変わるかもしれません。

`~/.ssh/` 自体をバージョン管理する方法も考えたのですが、さすがに秘密鍵はリモートリポジトリに入れたくないし、`known_hosts` のように無意識的に編集されるファイルのバージョン管理は私には面倒事が多かったのでバージョン管理はやめました（気が向いた時にコミットしたらプッシュ時にコンフリクトしたりとか）。

秘密鍵はローカルメディアにバックアップ（または Mac の TimeMachine バックアップ）、`konwn_hosts` などは世代バックアップをしています。

独特の設定があるリモートホストについては、`hosts/ホスト名/` といったディレクトリに `config` などの各種ファイルを放り込む運用にしています。

## 結合の手法

ファイル群が新たなファイルを作るというのはまさに Make の仕事。だいたいどこにでも入っている `make` コマンドを使うことにしました。

```makefile

CP	= cp -p
CONFIG_FILES	= $(shell echo conf.d/*.conf)

usage:
	@echo "Usage:"
	@echo "  update    git update"
	@echo "  config    generate config from conf.d/*.conf"
	@echo "  ../config same as config"
	@echo "  keylist   show keylist from keylist.txt"
	@echo "  deploy    deploy config and keylist.txt to ~/.ssh/"
	@echo "  clean     remove config and deploy flag"

update:
	git fetch
	git pull origin master

config ../config: $(CONFIG_FILES)
	{ \
	echo "# -*- ssh-config -*-" ; \
	cat $(CONFIG_FILES) | grep -E -v '^#( -\*-|//)' ; \
	} > $@
	perl -p -i -e 's/^# \$$sh\((.*)\)$$/ join "\n", qx{$$1} /e' $@

keylist: keylist.txt
	grep -v '^#' $<

deploy: config keylist.txt
	$(CP) config $(SSH_DIR)
	$(CP) keylist.txt $(SSH_DIR)
	echo "This file is flag file" > deploy

clean:
	rm -f config
	rm -f deploy
```

最初は `make config deploy clean` というコマンドで作成→デプロイ→掃除の流れをやっていたのですが、面倒だしワーキングディレクトリに `config` ができるのもややこしいので、`make ../config` だけにしました。これは `~/.ssh/vc` がワーキングディレクトリであれば、`../config` が古ければ（`conf.d/*.conf` の mtime の最も新しいものより古ければ）作りなおすという自然な手順となります。mtime の計算をシェルスクリプトでやる必要などがないのが楽。

# 意識的にファイルを結合する方法

上記手順では、ファイル結合に関する設定ファイルの編集のサイクルは

* `~/.ssh/vc/conf.d` 以下のファイルを更新
* `make -C ~/.ssh/vc ../config`
* 設定の正しさを確認（`ssh` できるかくらい）
* 変更を add && commit && push

といったものです。結局バージョン管理するなら意識的にならざるをえないし、今のところはこれで満足しています。

バージョン管理されているところは `~/.ssh/vc` だということを意識さえしておけば、`~/.ssh/config` が存在し続けたとしてもバージョン管理上の無意識な失敗（`~/.ssh/config 自体をいじってしまうとか）もだいたい避けられます。