`Include`キーワードで`ssh_config`を分割できるようになった件

## 要約
- `ssh_config`の分割には`Include`キーワードが使えます
- `Include`キーワードは2016-08-01リリースのOpenSSH 7.3から導入されています

## `Include`キーワード
先日、何気なく`man ssh_config`していたところ`Include`という便利キーワードの存在に気づきました。`Include`キーワードは、どうやら2016-08-01にリリースされたOpenSSH 7.3で導入されたようです。

## `Include`でできること

```
$ man ssh_config
...
     Include
             Include the specified configuration file(s).  Multiple pathnames may be specified and each pathname may contain glob(3) wildcards and, for user configurations,
             shell-like ``~'' references to user home directories.  Files without absolute paths are assumed to be in ~/.ssh if included in a user configuration file or
             /etc/ssh if included from the system configuration file.  Include directive may appear inside a Match or Host block to perform conditional inclusion.
...
```

説明を読むと、`Include`キーワードを使うことで`~/.ssh/config`の設定を別ファイルに切り出すことができることがわかります。

## ファイルの分割方法

`Include`キーワードを使うことで

```
$ cat ~/.ssh/config
Host hoge
  HostName hoge.example.com
  User hoge
  IdentityFile ~/.ssh/id.pem
  
Host fuga
  HostName fuga
  Port 2222
  User abc
  IdentityFile ~/.ssh/abc.pem
  
Host xyz
  HostName xyz.example.com
  Port 2222
  User abc
  IdentityFile ~/.ssh/abc.pem
  
Host *
  ServerAliveInterval 300
  AddKeysToAgent yes
```

このような`~/.ssh/config`を下記のように分割することができます。

### ディレクトリー構成

今回は分割したファイルを`$HOME/.ssh/conf.d/`配下に置くことにします。

```
$ tree ~/.ssh
/Users/masa0x80/.ssh
├── ...
├── conf.d
│   ├── commons
│   │   └── abc
│   └── hosts
│       ├── hoge
│       └── misc
├── config
└── ...


```

### `~/.ssh/config`の中身

`Include`を使って`Host directive`を切り出してみます。

```
$ cat ~/.ssh/cofig
Include conf.d/hosts/*

Host *
  ServerAliveInterval 300
  AddKeysToAgent yes
```

`ssh_config`の設定は基本的に先勝（先に定義されたものが優先される）なので、まず`Include`で`Host directive`を読み込んでから`Host *`で全体的な設定を行うのが良いです。

`Include`は`Match`や`Host`の中にも置くことができますので記述順序には注意が必要です。例えば、下記のように`Host directive`の後に`Include`を記載すると、`ssh foo`の時にのみ`conf.d/hoge`が読み込まれるために、期待通りの挙動になりません。

```
Host foo
  HostName ...

Include conf.d/hosts/*
```

### `~/.ssh/conf.d/hosts/hoge`の中身

`hoge`に関する設定を単純にファイルに切り出しただけです。

```
$ cat ~/.ssh/conf.d/hosts/hoge
Host hoge
  HostName hoge.example.com
  User hoge
  IdentityFile ~/.ssh/id.pem
```

### `~/.ssh/conf.d/hosts/misc`の中身

`Host directive`を複数書くこともできますし、更に`Include`することもできます。

```
$ cat ~/.ssh/conf.d/hosts/misc
Host fuga
  HostName fuga.example.com
  Include conf.d/commons/abc
  
Host xyz
  HostName xyz.example.com
  Include conf.d/commons/abc
```

`*`や`?`を使って設定を書きにくい場合は、`Host directive`の中で`Include`を使用して共通設定を読み込むと便利です。今回は下記の様にも書けますが、設定が大きくなってくると`Include`を使った方が誤爆を防ぎやすいと思います。

```
Host fuga
  HostName fuga.example.com
  
Host xyz
  HostName xyz.example.com

Host fuga xyz
  Port 2222
  User abc
  IdentityFile ~/.ssh/abc.pem
```

### `~/.ssh/conf.d/commons/abc`の中身

`~/.ssh/conf.d/hosts/misc`から参照される設定値です。

```
$ cat ~/.ssh/conf.d/commons/abc
Port 2222
User abc
IdentityFile ~/.ssh/abc.pem
```

## `fzf`との連携

### zshの場合

下記のような関数を準備しておくことで`^s^s`と入力することで、`ssh_config`に記載されているホスト名を補完することができて便利です。

なお、読み込むファイルは`~/.ssh/config`と`~/.ssh/conf.d/`配下のファイルになっているので適宜修正ください。

```zsh:complete-ssh-host.zsh
complete-ssh-host() {
  local host="$(command egrep -i '^Host\s+.+' $HOME/.ssh/config $(find $HOME/.ssh/conf.d -type f 2>/dev/null) | command egrep -v '[*?]' | awk '{print $2}' | sort | fzf)"

  if [ ! -z "$host" ]; then
    LBUFFER+="$host"
  fi
  zle reset-prompt
}
zle -N complete-ssh-host
bindkey '^s^s' complete-ssh-host
```

`fzf`の部分を`peco`に変更することで`peco`利用に変更することもできます。

### fishの場合

拙作の`complete_ssh_host.fish`を利用すると上記のzshと同等の処理（`^s^s`で補完）ができるようになります。

`fisherman`を使ってインストールする場合は下記で利用できます。

```
fisher masa0x80/complete_ssh_host.fish
```

あるいは、こちらも拙作の`fresco`というプラグインマネージャーを使ってインストールする場合は下記となります。

```
$ # Install fresco
$ curl https://raw.githubusercontent.com/masa0x80/fresco/master/install | fish
$ exec fish -l

$ # Install plugin
$ fresco masa0x80/complete_ssh_host.fish
```

`fresco`を使うとfishのスクリプトを`ghq`を使って管理できて便利です。