俺様サーバー構築記 - Subversion による /etc のバージョン管理@ノートパソコン

「[俺様サーバー構築記 - 基本方針](https://qiita.com/katz_engineer/items/dbabcff4e14e9db2b10f)」以来構築を続けてきた俺様パソコン環境ですが、サーバマシンに[Subversionをインストール](https://qiita.com/katz_engineer/items/b893316b4d1bb7731e87)し、クライアントマシンにも [svn+ssh でアクセスできるように設定](https://qiita.com/katz_engineer/items/678badfbfa842ffc3c14)しました。

これらを基盤として、```/etc``` をバージョン管理します。こういう事は Etckeeper が定番のようで、[ArchLinux にも Etckeeper のパッケージがあります](https://wiki.archlinux.jp/index.php/Etckeeper)。但しこれは Git などの今時の分散バージョン管理ツールだけが対象でした。Subversion は対象外。残念です。

分散バージョン管理は個人的に不勉強ですので、これからの勉強を改めて誓うとして、今回は Subversion を利用したバージョン管理を独自に工夫します。

# 序

Subversion サーバはデスクトップマシンに立ててあります。
参考文献：[俺様サーバー構築記 - Subversionサーバーの構築@デスクトップパソコン](https://qiita.com/katz_engineer/items/b893316b4d1bb7731e87)

ノートパソコンからは svn+ssh でアクセスできるように準備しています。但し、今回は root ユーザからアクセスするようにするので、後程その設定を追加します。
参考文献：[俺様サーバー構築記 - Subversion + Dropbear @ノートパソコン](https://qiita.com/katz_engineer/items/678badfbfa842ffc3c14)

```/etc``` バージョン管理については EtcManager という名前のプロジェクトにします。短縮名は etcmgr とし、Subversion プロジェクト等はこの短縮名で用意します。

# 作業の流れ

0. Subversion リポジトリに etcmgr プロジェクトを作成
0. client0 マシンから etcmgr にアクセスするユーザとして root_client0 を用意
0. etcmgr プロジェクトのセキュリティを設定
0. client0 マシンの ```/etc``` のバージョン管理を開始
0. pacman フックを作成

# etcmgr

## プロジェクトの初期設定

Subversion リポジトリに etcmgr プロジェクトを作成します。
これは最初に1度だけ実施し、以降は各マシン毎のサブフォルダを作成していきます。

```console:サーバ側
# /srv/svn/createprj.sh etcmgr
Edit svnserve.conf and authz in the directory /srv/svn/repo/etcmgr/conf as needed.
The file passwd is in the directory /srv/svn/conf, edit it as needed, too.
```

他のプロジェクトと違い、セキュリティ設定を厳しくします。基本的には各マシンの root ユーザからアクセスしますが、それ以外のユーザはアクセスを禁止します。読み出しも禁止。

その為の下準備として、 ```/srv/svn/repo/etcmgr/conf``` 以下の設定ファイルを操作します。不要なコメントも全部削除しておきます。

```console:サーバ側
# cd /srv/svn/repo/etcmgr/conf
# sed -i -e "s:^\(authz-db[[:space:]]*=\).*$:\1 $(pwd)/authz:" -e "s:^\(password-db[[:space:]]*=\).*$:# \1:" svnserve.conf 
# cat >authz <<___
> [aliases]
> 
> [groups]
> 
> [/]
> * = rw
> 
> ___
# rm passwd
```

結果として、各ファイルの内容は下記の通りとなります。

```console:サーバ側
# cat svnserve.conf 
[general]
anon-access = none
auth-access = write
# password-db =
authz-db = /srv/svn/repo/etcmgr/conf/authz
realm = Project etcmgr
# cat authz 
[aliases]

[groups]

[/]
* = rw

# cat passwd
cat: passwd: No such file or directory
```

## root_client0 ユーザ

プロジェクト etcmgr にアクセスする Subversion ユーザ名は下記の通りとします。クライアントマシンの root ユーザから下記ユーザ名で Subversion にアクセスする形になります。

<dl>
<dd>root_＜マシン名＞</dd>
</dl>

当然サーバにも（そしてクライアントにも）登録されていないユーザとなるので、以前調査した[サーバマシンに登録されていないユーザを Subversion アクセスできるように設定](https://qiita.com/katz_engineer/items/678badfbfa842ffc3c14#subversion%E3%82%AF%E3%83%A9%E3%82%A4%E3%82%A2%E3%83%B3%E3%83%883-%E3%82%B5%E3%83%BC%E3%83%90%E3%83%9E%E3%82%B7%E3%83%B3%E3%81%AB%E7%99%BB%E9%8C%B2%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%81%AA%E3%81%84%E3%83%A6%E3%83%BC%E3%82%B6%E3%81%AE%E5%A0%B4%E5%90%88)する方法を利用します。

なお、下記で、 taro はサーバ側に Dropbear アクセスできる一般ユーザであり、サーバの IP アドレスは 10.0.1.121 です。

```console:クライアント側
# cd
# mkdir .ssh
# cd .ssh
# dropbearkey -t ecdsa -s 256 -f id_dropbear_svn | tee >(tail -n+2 | head -n1 >authorized_keys_svn)
Generating 256 bit ecdsa key, this may take a while...
Public key portion is:
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBB13AN+v0oUvaeS23hsP4Ak8nzn8GVqMk+3SEPed1bzd2YRAKKNbOGRtVtuCcQnkILI5WWqMTzB03JRUomhZxVY= root@client0
Fingerprint: sha1!! 8c:3f:25:d6:d0:89:28:3a:74:d6:a6:6c:ee:9e:e5:0b:5b:0c:99:d6
# su taro
$ cat authorized_keys_svn | dbclient -p 60022 10.0.1.121 "mkdir /tmp/root_key; cat >/tmp/root_key/authorized_keys_svn"
$ exit
exit
```

```console:サーバ側
# cd /tmp/root_key
# source /etc/subversion/basic.conf
# SVN_USER=$(cat authorized_keys_svn | cut -d" " -f3 | tr @ _)
# cat >>$SVN_HOME/.ssh/authorized_keys <<___
> command="svnserve -tr $SVN_REPO --tunnel-user=$SVN_USER",no-port-forwarding,no-agent-forwarding,no-pty $(cat authorized_keys_svn)
> ___
# cd
# rm -r /tmp/root_key
```

ちなみに ```authorized_keys``` へ追加された行は下記のようになります。参考まで。

```console:サーバ側
# tail -n1 $SVN_HOME/.ssh/authorized_keys
command="svnserve -tr /srv/svn/repo --tunnel-user=root_client0",no-port-forwarding,no-agent-forwarding,no-pty ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBB13AN+v0oUvaeS23hsP4Ak8nzn8GVqMk+3SEPed1bzd2YRAKKNbOGRtVtuCcQnkILI5WWqMTzB03JRUomhZxVY= root@client0
```

## Subversion のセキュリティ

client0 マシンのディレクトリは root_client0 ユーザだけが読み書き出来るようにします。まぁ、気休めですね。```/etc``` ディレクトリは、読むだけなら誰でも読めますし。マシン外から読めないようにしておくのは正しいですが。

```console:サーバ側
# cat >>$SVN_REPO/etcmgr/conf/authz <<___
> [/client0]
> root_client0 = rw
> * = 
>    
> ___
```

## トンネルスキーム設定

```/root/.subversion``` を svn コマンドに作成させてから config ファイルを修正します。

```console:クライアント側
# svn --version >/dev/null
# sed -i -e"/\[tunnels\]/a ssh = dbclient -l svn -i $HOME/.ssh/id_dropbear_svn -p 60022" $HOME/.subversion/config
```

## ```/etc``` 管理開始

（2019/08/24 修正ここから）
```.pwd.lock``` と ```.updated``` は、バージョン管理は不要です。どうも更新時に設定されるロックファイル等で、特に後者は ArchLinux に特有のファイルっぽい。という訳で無視設定にします。

参考文献1: [Re: What is /etc/.pwd.lock for ? - debian-user Jul 2005 by thread](https://lists.debian.org/debian-user/2005/07/msg02949.html)
参考文献2: [karol 2014-07-31 00:12:19 - [SOLVED] /etc/.updated? - System Administration - ArchLinux Forums](https://bbs.archlinux.org/viewtopic.php?pid=1441907#p1441907)

が、普通に ```svn propset svn:ignore``` すると ```.updated``` はエラーになります。理由がよくわかりません。そのため一捻りした結果が下記になります。

```console:クライアント側
# svn mkdir --parents svn+ssh://10.0.1.121/etcmgr/$(hostname)/etc -m "Prepare the target directory /etc"

Host '10.0.1.121' is not in the trusted hosts file.
(ecdsa-sha2-nistp256 fingerprint sha1!! c9:c0:6f:79:53:a6:17:b2:27:82:ee:a3:c9:a0:92:23:db:0d:fc:82)
Do you want to continue connecting? (y/n) y
Committing transaction...
リビジョン 1 をコミットしました。
# svn co svn+ssh://10.0.1.121/etcmgr/$(hostname)/etc /etc
リビジョン 1 をチェックアウトしました。
# svn propset svn:ignore -F - /etc <<___
> .pwd.lock
> .updated
> ___
属性 'svn:ignore' を '/etc' に設定しました
# svn add /etc/*
〈省略〉
A         /etc/zfs/zpool.d/iostat
A         /etc/zfs/zpool.d/test_progress
A         /etc/zfs/zpool.d/enc
# svn ci /etc -m "initial commit"
〈省略〉
追加しています              etc/zfs/zpool.d/vendor
追加しています              etc/zfs/zpool.d/w_proc
追加しています              etc/zfs/zpool.d/w_ucor
ファイルのデータを送信しています ..............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................done
Committing transaction...
リビジョン 2 をコミットしました。
```

（2019/08/24 修正ここまで）

Subversion リポジトリのセキュリティ設定により、異なるユーザ taro ではファイル一覧を見る事も出来ません。

```console:クライアント側
$ whoami
taro
$ svn list svn+ssh://10.0.1.121/etcmgr | wc -l
0
$ svn list svn+ssh://10.0.1.121/etcmgr/$(hostname)
svn: E170001: Authorization failed
```

## pacman フック

pacman を使った時に、自動的に commit されるようにします。ただ、これは良し悪しだと考えています。```pacman -Syu``` の時など自動的に commit してくれるのは助かりますが、新しいパッケージをインストールした時に自動 commit されると、メッセージに覚書を残せなくなります。そこの所を良く検討して判断して下さい。その辺の制御は、下記の ```etcmgr-pre-install.hook``` における [Trigger] Operation の設定である程度は制御できると思います。

（2019/08/10 追加ここから）
[Trigger] Operation = Update だけにすれば良いようです。
（2019/08/10 追加ここまで）

自動 commit する場合、その為のシェルスクリプトをクライアントマシンに作成して ```/opt/etcmgr/bin``` に置きます。

（2019/09/22 修正ここから）
GnuPG によるパッケージの暗号化がどうとかで、 ```/etc/pacman.d/gnupg``` ディレクトリに UNIX ソケットが生成される事があるようです。 Subversion はこれを処理できないのでエラーが発生します。

```console
svn: E200007: パス '/etc/pacman.d/gnupg/S.gpg-agent' のノード種別はサポートされていません
svn: E200007: Unsupported node kind for path '/etc/pacman.d/gnupg/S.gpg-agent'
```

これを避ける為に、管理対象ファイル追加の処理においてフィルタを設定します。

```console:クライアント側/opt/etcmgr/bin/autocommit.sh
#!/bin/bash

ETC_DIR="$1"
if [[ -z "$ETC_DIR" ]]; then
  echo "$(basename $0) <dirname>"
  exit 1
fi

exec &> >(tee >(systemd-cat -t etcmgr))

cd "$ETC_DIR"
SVN_STATUS=$(svn st)
if [[ -z "$SVN_STATUS" ]]; then exit; fi

echo "auto commit $ETC_DIR when done pacman command${2+: }$2"
echo "$SVN_STATUS" | grep "^?" | cut -b9- | xargs -I{} find {} -type f -or -type d -or -type l | xargs -r svn add
echo "$SVN_STATUS" | grep "^!" | cut -b9- | xargs -r svn rm
svn ci -m "auto commit when done pacman command${2+: }$2"
```

（2019/09/22 修正ここまで）

これ、 ```svn+ssh://10.0.1.121/etcmgr/autocommit.sh``` として Subversion に登録してしまった方が良いかも知れません…

そして ```/opt/etcmgr/bin/autocommit.sh``` が出来たら、pacman のフックを作成します。

参考文献：[2.2 フック - 2 設定 - pacman - ArchWiki](https://wiki.archlinux.jp/index.php/Pacman#.E3.83.95.E3.83.83.E3.82.AF)

（2019/09/27 修正ここから）
```Operation = Install``` と ```Operation = Remove``` はコメントアウトして、Upgrade の際だけ自動 commit されるようにします。
パッケージのインストールや削除の際には手動で ```svn commit``` しましょう。適切なコメントを残す必要もありますので。

更に、pacman は事前と事後にフックを設定できるようです。念の為に両方用意した方が良いでしょう。

```console:クライアント側/usr/share/libalpm/hooks/etcmgr-pre-install.hook 
# etcmgr pre-install hook

[Trigger]
# Operation = Install
Operation = Upgrade
# Operation = Remove
Type = File
Target = etc/*

[Action]
Description = etcmgr: pre-transaction commit
When = PreTransaction
Exec = /opt/etcmgr/bin/autocommit.sh /etc pre-transaction
AbortOnFail
```

```console:クライアント側/usr/share/libalpm/hooks/etcmgr-post-install.hook 
# etcmgr post-install hook

[Trigger]
# Operation = Install
Operation = Upgrade
# Operation = Remove
Type = File
Target = etc/*

[Action]
Description = etcmgr: post-transaction commit
When = PostTransaction
Exec = /opt/etcmgr/bin/autocommit.sh /etc post-transaction
```

試してみます。

```console:クライアント側
# pacman -Syu --noconfirm --noprogressbar
〈省略〉
:: トランザクション前のフックを実行...
(1/2) Remove DKMS modules
==> dkms remove zfs/0.8.1 -k 5.2.9-arch1-1-ARCH
(2/2) etcmgr: pre-transaction commit
auto commit /etc when done pacman command: pre-transaction
送信しています              .updated
ファイルのデータを送信しています .done
Committing transaction...
リビジョン 27 をコミットしました。
〈省略〉
:: トランザクション後のフックを実行...
〈省略〉
(13/24) etcmgr: post-transaction commit
auto commit /etc when done pacman command: post-transaction
A         fonts/conf.avail/05-reset-dirs-sample.conf
A         fonts/conf.avail/09-autohint-if-no-hinting.conf
A         fonts/conf.avail/35-lang-normalize.conf
A         systemd/pstore.conf
D         dbus-1/system.d/org.freedesktop.UPower.conf
送信しています              X11/xinit/xinitrc.d/50-systemd-user.sh
削除しています              dbus-1/system.d/org.freedesktop.UPower.conf
追加しています              fonts/conf.avail/05-reset-dirs-sample.conf
追加しています              fonts/conf.avail/09-autohint-if-no-hinting.conf
追加しています              fonts/conf.avail/35-lang-normalize.conf
送信しています              fonts/conf.avail/40-nonlatin.conf
送信しています              fonts/conf.avail/45-generic.conf
送信しています              fonts/conf.avail/45-latin.conf
送信しています              fonts/conf.avail/60-generic.conf
送信しています              fonts/conf.avail/60-latin.conf
送信しています              fonts/conf.avail/65-nonlatin.conf
送信しています              fonts/fonts.conf
送信しています              fonts/fonts.dtd
送信しています              ld.so.cache
送信しています              mail.rc
送信しています              pacman.d/gnupg/pubring.gpg
送信しています              pacman.d/gnupg/tofu.db
送信しています              pacman.d/gnupg/trustdb.gpg
送信しています              profile.d/vte.sh
送信しています              services
送信しています              ssl/misc/tsget.pl
送信しています              systemd/networkd.conf
追加しています              systemd/pstore.conf
送信しています              systemd/system.conf
送信しています              systemd/user.conf
ファイルのデータを送信しています ........................done
Committing transaction...
リビジョン 28 をコミットしました。
〈省略〉
# export LESSCHARSET=dos
# journalctl -at etcmgr
〈省略〉
 9月 25 21:34:44 honoka etcmgr[14200]: auto commit /etc when done pacman command: pre-transaction
 9月 25 21:34:44 honoka etcmgr[14200]: 送信しています              .updated
 9月 25 21:34:44 honoka etcmgr[14200]: ファイルのデータを送信しています .done
 9月 25 21:34:44 honoka etcmgr[14200]: Committing transaction...
 9月 25 21:34:44 honoka etcmgr[14200]: リビジョン 27 をコミットしました。
 9月 25 22:21:46 honoka etcmgr[113921]: auto commit /etc when done pacman command: post-transaction
 9月 25 22:21:47 honoka etcmgr[113921]: A         fonts/conf.avail/05-reset-dirs-sample.conf
 9月 25 22:21:47 honoka etcmgr[113921]: A         fonts/conf.avail/09-autohint-if-no-hinting.conf
 9月 25 22:21:47 honoka etcmgr[113921]: A         fonts/conf.avail/35-lang-normalize.conf
 9月 25 22:21:47 honoka etcmgr[113921]: A         systemd/pstore.conf
 9月 25 22:21:47 honoka etcmgr[113921]: D         dbus-1/system.d/org.freedesktop.UPower.conf
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              X11/xinit/xinitrc.d/50-systemd-user.sh
 9月 25 22:21:48 honoka etcmgr[113921]: 削除しています              dbus-1/system.d/org.freedesktop.UPower.conf
 9月 25 22:21:48 honoka etcmgr[113921]: 追加しています              fonts/conf.avail/05-reset-dirs-sample.conf
 9月 25 22:21:48 honoka etcmgr[113921]: 追加しています              fonts/conf.avail/09-autohint-if-no-hinting.conf
 9月 25 22:21:48 honoka etcmgr[113921]: 追加しています              fonts/conf.avail/35-lang-normalize.conf
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              fonts/conf.avail/40-nonlatin.conf
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              fonts/conf.avail/45-generic.conf
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              fonts/conf.avail/45-latin.conf
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              fonts/conf.avail/60-generic.conf
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              fonts/conf.avail/60-latin.conf
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              fonts/conf.avail/65-nonlatin.conf
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              fonts/fonts.conf
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              fonts/fonts.dtd
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              ld.so.cache
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              mail.rc
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              pacman.d/gnupg/pubring.gpg
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              pacman.d/gnupg/tofu.db
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              pacman.d/gnupg/trustdb.gpg
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              profile.d/vte.sh
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              services
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              ssl/misc/tsget.pl
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              systemd/networkd.conf
 9月 25 22:21:48 honoka etcmgr[113921]: 追加しています              systemd/pstore.conf
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              systemd/system.conf
 9月 25 22:21:48 honoka etcmgr[113921]: 送信しています              systemd/user.conf
 9月 25 22:21:49 honoka etcmgr[113921]: ファイルのデータを送信しています ........................done
 9月 25 22:21:49 honoka etcmgr[113921]: Committing transaction...
 9月 25 22:21:49 honoka etcmgr[113921]: リビジョン 28 をコミットしました。
# svn log -r 27:HEAD /etc
------------------------------------------------------------------------
r27 | root_honoka | 2019-09-25 21:28:55 +0900 (2019年09月25日 (水)) | 1 line

auto commit when done pacman command: pre-transaction
------------------------------------------------------------------------
r28 | root_honoka | 2019-09-25 22:15:59 +0900 (2019年09月25日 (水)) | 1 line

auto commit when done pacman command: post-transaction
------------------------------------------------------------------------
```

（2019/09/27 修正ここまで）

（2019/08/13 修正ここから）
```journalctl``` の不穏なメッセージ「svn: E200007: パス '/etc/pacman.d/gnupg/S.gpg-agent' のノード種別はサポートされていません」は発生しなくなった筈です。
（2019/08/13 修正ここまで）

# 結

Etckeeper 風に、 ```/etc``` を Subversion で管理するようにしました。
今回はノートパソコンに導入しましたが、サーバマシンでも同様の手順で実施できると思います。

やったね :thumbsup_tone2: