俺様サーバー構築記 - Subversion + Dropbear @ノートパソコン

「[俺様サーバー構築記 - 基本方針](https://qiita.com/katz_engineer/items/dbabcff4e14e9db2b10f)」以来構築を続けてきた俺様パソコン環境に、[Subversionをインストール](https://qiita.com/katz_engineer/items/b893316b4d1bb7731e87)しました。

これはサーバー用途を想定したデスクトップマシンなので、クライアント用途を想定しているノートパソコン達にもインストールします。接続の際には svn+ssh を使用します。

svn+ssh には幾つかの種類があり、それぞれ特徴があります。

# 事前準備

## ノートパソコン（クライアント側）への Subversion インストール

何はともあれインストール。その前にスナップショットを撮りましょう。万が一の際には rollback できます。

```console:クライアント側
$ su -
パスワード: 〈ノートパソコンのrootのパスワード〉
# zfs snapshot tank/main@$(date +%Y%m%d_%H%M%S)_before_svn
# zfs list -t snapshot -S name | head -n2
NAME                                        USED  AVAIL     REFER  MOUNTPOINT
tank/main@20190715_201435_before_svn        114K      -     5.48G  -
# pacman -S --noconfirm --noprogressbar svn
〈省略〉
# exit
ログアウト
$ svn --version | head -n2
svn, version 1.12.0 (r1857323)
   compiled Jun  7 2019, 01:40:12 on x86_64-pc-linux-gnu
```

## 簡易Subversionサーバへの接続確認

ここから先は、Subversionサーバはデスクトップマシンで動いています。まずはその確認。

```console:サーバ側
# systemctl status svnserve
* svnserve.service - Subversion protocol daemon
   Loaded: loaded (/usr/lib/systemd/system/svnserve.service; enabled; vendor preset: disabled)
  Drop-In: /etc/systemd/system/svnserve.service.d
           `-override.conf
   Active: active (running) since Mon 2019-07-01 21:24:22 JST; 1 weeks 6 days ago
  Process: 6371 ExecStart=/usr/bin/svnserve --daemon $SVNSERVE_ARGS (code=exited, status=0/SUCCESS)
 Main PID: 6372 (svnserve)
    Tasks: 1 (limit: 4915)
   Memory: 1.8M
   CGroup: /system.slice/svnserve.service
           `-6372 /usr/bin/svnserve --daemon -r /srv/svn/repo

Jul 03 23:08:50 server0 svnserve[28616]: DIGEST-MD5 common mech free
Jul 03 23:08:50 server0 svnserve[28628]: DIGEST-MD5 common mech free
Jul 03 23:20:51 server0 svnserve[28808]: DIGEST-MD5 common mech free
Jul 03 23:20:51 server0 svnserve[28821]: DIGEST-MD5 common mech free
Jul 03 23:20:51 server0 svnserve[28828]: DIGEST-MD5 common mech free
Jul 03 23:20:51 server0 svnserve[28835]: DIGEST-MD5 common mech free
Jul 03 23:20:51 server0 svnserve[28840]: DIGEST-MD5 common mech free
Jul 03 23:20:55 server0 svnserve[28886]: DIGEST-MD5 common mech free
Jul 03 23:20:55 server0 svnserve[28893]: DIGEST-MD5 common mech free
Jul 03 23:20:55 server0 svnserve[28898]: DIGEST-MD5 common mech free
```

テスト用のプロジェクトを作成し、接続先となるサーバマシンのIPアドレスを確認します。

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

Subversionに登録しておいたユーザとパスワードを使用して、クライアントマシンにおいてテスト用プロジェクトを操作します。

```console:クライアント側
$ cd
$ mkdir prj
$ cd prj
$ read U
〈Subversionユーザ〉
$ read -s P
〈Subversionユーザのパスワード; 入力しても画面に表示されない〉
$ svn co --username "$U" --password "$P" svn://10.0.0.121/test/
Checked out revision 0.
```

一応ちゃんとクライアント⇔サーバ間で通信できているようです。

## 簡易Subversionサーバの停止

最低限の疎通確認が取れたので、現在起動している svnserve は停止させます。
ついでに disable して、サーバマシン再起動時にも動き出さないようにします。

```console:サーバ側
# systemctl stop svnserve
# systemctl disable svnserve
Removed /etc/systemd/system/multi-user.target.wants/svnserve.service.
```

# Subversionクライアント(1) サーバマシンへ ssh 接続可能なユーザの場合

## 状況の確認

サーバマシン上に home ディレクトリを持つユーザの名前と一致するような Subversion ユーザの場合、全体に共通の設定を1つ作っておけば使い回す事が出来ます。
但し、サーバマシン上のリポジトリである ```/srv/svn/repo``` ディレクトリに設定されているパーミッションに従った不自由が強制される事になります。[前回インストールした設定](https://qiita.com/katz_engineer/items/b893316b4d1bb7731e87)の場合は、ユーザを svn グループに所属させておく必要があります。

今回はユーザ名 taro を例とします。

```console:サーバ側
# grep taro /etc/passwd
taro:x:1000:1000::/home/taro:/bin/bash
# grep taro /srv/svn/conf/passwd
taro = This is 1 pen.
```

前提条件:
ssh で、クライアント⇔サーバのマシン間通信が出来る。
今回は ssh ではなくて Dropbear になりますが。

```console:クライアント側
$ whoami
taro
$ dbclient -p 60022 10.0.1.121 "whoami"
taro
```

## サーバマシン上での設定

ユーザ taro を svn グループに所属させます。

```console:サーバ側
# usermod -aG svn taro
```

## クライアントマシン上での設定

クライアントマシンの全ユーザ共通設定として、下記のように ```/etc/subversion/config``` ファイルを作成しておきます。

```console:クライアント側
$ su
パスワード:
# mkdir /etc/subversion
# cat >/etc/subversion/config <<___
> [tunnels]
> ssh = dbclient -p 60022
> ___
# exit
```

## 動作確認と評価

これで ssh 接続による Subversion 操作が可能になります。…が、リポジトリのフルパス ```/srv/svn/repo/test``` を指定しなければなりません。

```console:クライアント側
$ mkdir prj
$ cd prj
$ svn co svn+ssh://10.0.1.121/srv/svn/repo/test/
リビジョン 0 をチェックアウトしました
$ cd test
$ cat >document.txt <<___
> Twinkle, twinkle, little star
> ___
$ svn add *
A         document.txt
$ svn ci -m "first commit"
追加しています              document.txt
ファイルのデータを送信しています .done
Committing transaction...
リビジョン 1 をコミットしました。
```

この手順だと最も簡単な手順で svn+ssh できます。```/etc/subversion/config``` を一度作成してしまえば、それ以降はマシン内のすべてのユーザで Subversion を使用できるようになります。

その代わり、リポジトリのフルパスを指定しなければなりません。サーバにおけるフルパスをクライアント側で指定するというのは、場合によってはセキュリティ上のリスクにもなるでしょう。

その為か、ArchWiki にはサーバ svnserve のラッパシェルスクリプトを作成して、その中でリポジトリのディレクトリを指定するように書かれています。それだと非常に便利ですが、PATH に指定するディレクトリの順番によって挙動が変わってしまうという副作用があります。忘れた頃に問題が発生して、思い出す為に丸一日潰してしまったとか、そんな碌でもない結果になりそうで怖い…

# Subversionクライアント(2) サーバマシンへ ssh 接続可能なユーザに svn 専用キーを作成

先程のユーザ taro を使用します。

```console:サーバ側
# grep taro /etc/passwd
taro:x:1000:1000::/home/taro:/bin/bash
# groups taro
svn taro
# grep taro /srv/svn/conf/passwd
taro = This is 1 pen.
# hostname -i
10.0.1.121
```

```console:クライアント側
$ whoami
taro
$ dbclient -p 60022 10.0.1.121 "whoami"
taro
```

## サーバマシン上での設定

Subversion に関する基本的な設定、具体的にはリポジトリのディレクトリを、サーバ上に設定ファイルの形で保存しておきます。ファイル名は何でも良いです。今回は ```basic.conf``` にしました。bash で解釈できる文法で記述します。

```console:サーバ側
# cat >/etc/subversion/basic.conf <<'___'
> SVN_HOME=~svn
> SVN_REPO=$SVN_HOME/repo
> ___
```

## クライアント上でのユーザ個別の設定

### Subversion 用の秘密鍵と公開鍵を作成

```dropbearkey``` は標準出力に余計な行も出力するので、それを除いて必要な行だけを公開鍵として保存します。

```console:クライアント側
$ 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 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIfBpQEUMsuMCYYz564j8JbOjxxr7DZSDYG0lC1qiydfMgth6hXpU84lgAg8smHRpbBn/ZUSEhEiUBoDzSB8OWQ= taro@client0
Fingerprint: sha1!! 7d:13:6d:1d:57:af:85:ff:9e:4e:de:cf:90:d0:f8:cc:f0:4d:7b:4c
$ cat authorized_keys_svn 
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIfBpQEUMsuMCYYz564j8JbOjxxr7DZSDYG0lC1qiydfMgth6hXpU84lgAg8smHRpbBn/ZUSEhEiUBoDzSB8OWQ= taro@client0
```

これをサーバマシンに、```svnserve``` コマンドの為の指定と共に書き込みます。オプションなどがゴチャゴチャしてますが、詳細は参考文献を見て下さい。

参考文献: [最後の注意です - 起動コマンドの制御 - SSH 設定の技法 - svnserve, 専用サーバ - 6. サーバの設定 - Subversion によるバージョン管理 For Subversion 1.2](http://jtdan.com/vcs/svn/svn-book/book.html#svn.serverconfig.svnserve.sshtricks.fixedcmd)

```console:クライアント側
$ cat authorized_keys_svn | dbclient -p 60022 10.0.1.121 'source /etc/subversion/basic.conf; echo command=\"svnserve -tr $SVN_REPO\",no-port-forwarding,no-agent-forwarding,no-pty $(cat) >>~/.ssh/authorized_keys'
```

### Subversion のトンネルスキームを設定

```$HOME/.subversion/config``` ファイルを下記のように加工します。

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

## 動作確認と評価

```console:クライアント側
$ rm -rf ~/prj
$ mkdir ~/prj
$ cd ~/prj
$ svn co svn+ssh://10.0.1.121/test/
A    test/document.txt
リビジョン 1 をチェックアウトしました。
$ cd test
$ cat >>document.txt <<___
> How I wonder what you are
> ___
$ svn ci -m "second commit"
送信しています              document.txt
ファイルのデータを送信しています .done
Committing transaction...
リビジョン 2 をコミットしました。
```

ユーザ毎に個別の設定をしなければならないのが少々面倒ですね。
しかしそれさえ出来れば、サーバ上のフルパスをユーザが把握するなんて危険な事を強制しなくて済みます。

# Subversionクライアント(3) サーバマシンに登録されていないユーザの場合

## 準備

サーバマシンに登録されていないユーザとして、クライアントに新しいユーザ jiro を作成します。

サーバ上に jiro が居ない事を確認。

```console:サーバ側
# grep jiro /etc/passwd | wc -l
0
```

クライアント上に jiro を作成します。

```console:クライアント側
$ su -
パスワード:
# useradd -m jiro
# passwd jiro
新しいパスワード: 〈jiro のパスワード〉
新しいパスワードを再入力してください: 〈jiro のパスワード〉
passwd: パスワードは正しく更新されました
# exit
```

## サーバ上の設定

Subversion に関する基本的な設定、具体的にはリポジトリのディレクトリを、サーバ上に設定ファイルの形で保存しておきます。先程作成した ```/etc/subversion/basic.conf``` を確認します。

```console:サーバ側
# cat /etc/subversion/basic.conf
SVN_HOME=~svn
SVN_REPO=$SVN_HOME/repo
```

サーバ上に存在しないユーザで ssh アクセスする為、 svn ユーザとしてログインする事にします。 svn ユーザでログインできるようにし、サーバマシン上の svn ホームディレクトリに .ssh ディレクトリを用意します。

```console:サーバ側
# usermod -s /bin/bash svn
# source /etc/subversion/basic.conf 
# cd $SVN_HOME
# mkdir .ssh
# chown svn:svn .ssh
```

## クライアント上でのユーザ個別の設定

ユーザ jiro で秘密鍵と公開鍵のペアを作成し、サーバ上のユーザ svn に対して登録します。登録の際にサーバへアクセスする為、一捻りした手順を実施します。

### Subversion 用の秘密鍵と公開鍵を作成

```dropbearkey``` は標準出力に余計な行も出力するので、それを除いて必要な行だけを公開鍵として保存します。

```console:クライアント側
$ su - jiro
パスワード: 〈jiro のパスワード〉
$ whoami
jiro
$ 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 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCvHbuWeBbr/3kgb4iqAjkZQH7oJWtWJikBtbiaJi9nsnAS1CqpknFUuC6NxmmOz2Jx8at3vGuQFvbbgPOHIOpc= jiro@client0
Fingerprint: sha1!! 38:94:62:e9:39:6b:2e:04:f3:7a:11:f4:1c:28:b2:c7:6b:4d:5b:40
```

これをサーバマシンに、```svnserve``` コマンドの為の指定と共に書き込みます。が、 jiro はサーバに居ないので、サーバに接続できる別のユーザを経由します。そのため手順が少々ややこしくなります。

0. サーバに接続できるユーザ taro を利用して、サーバの一時ディレクトリ ```/tmp``` （のサブディレクトリ）にコピー
0. サーバ側で ```/tmp``` （のサブディレクトリ）上の公開鍵を ```/srv/svn/.ssh``` の公開鍵置き場にコピー
0. その際、同時に ```svnserve``` コマンドの為の指定を書き込みます。オプションなどがゴチャゴチャしてますが、詳細は参考文献を見て下さい。

参考文献: [最後の注意です - 起動コマンドの制御 - SSH 設定の技法 - svnserve, 専用サーバ - 6. サーバの設定 - Subversion によるバージョン管理 For Subversion 1.2](http://jtdan.com/vcs/svn/svn-book/book.html#svn.serverconfig.svnserve.sshtricks.fixedcmd)

```console:クライアント側
$ su
パスワード:
# cat ~jiro/.ssh/authorized_keys_svn | dbclient -l taro -i ~taro/.ssh/id_dropbear -p 60022 10.0.1.121 "mkdir /tmp/jiro; cat >/tmp/jiro/authorized_keys_svn"

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
# rm -rf $HOME/.ssh
# exit
```

```/srv/svn/.ssh/authorized_keys``` ファイルに jiro の公開鍵を登録します。

```console:サーバ側
# cd /tmp/jiro
# source /etc/subversion/basic.conf  
# SVN_USER=$(cat authorized_keys_svn | cut -d" " -f3 | cut -d@ -f1)
# 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/jiro
```

### Subversion のトンネルスキームを設定

jiro に ```$HOME/.subversion/config``` ファイルを作る為に、適当に svn コマンドを実行します。当然、エラーになりますが、それで良いのです。

（2019/08/06 修正ここから）
エラーにならないコマンドとして ```svn --version``` を実行します。それでも ```$HOME/.subversion``` は作成されますので。そして ```config``` ファイルを修正します。

```console:クライアント側
$ whoami
jiro
$ svn --version >/dev/null
$ sed -i -e"/\[tunnels\]/a ssh = dbclient -l svn -i $HOME/.ssh/id_dropbear_svn -p 60022" $HOME/.subversion/config
```
（2019/08/06 修正ここまで）

## 動作確認と評価

（2019/08/06 修正ここから）
```svn --version``` コマンドを実行したので、ssh に関してのメッセージがここで表示されるようになります。

```console:クライアント側
$ whoami
jiro
$ mkdir ~/prj
$ cd ~/prj
$ svn co svn+ssh://10.0.1.121/test/

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) 
A    test/document.txt
リビジョン 2 をチェックアウトしました。
$ cd test
$ cat >>document.txt <<___
> Up above the world so high
> ___
$ svn ci -m "third commit"
送信しています              document.txt
ファイルのデータを送信しています .done
Committing transaction...
リビジョン 3 をコミットしました。
$ svn log -r HEAD --xml | xmllint --xpath '/log/logentry/author/text()' -
jiro
```

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

commit したユーザはきちんと jiro になっています。

この方法の最大の特徴は、サーバに登録されたユーザでなくても Subversion へアクセスできる点です。その分、公開鍵を正しく svn ユーザに届けて登録する手順が面倒になりますが。

サーバに登録されていないユーザでの Subversion アクセスと言うと、最も有用な使い方の一つは root ユーザによるバージョン管理でしょう。例えば ```/etc``` のバージョン管理です。

先の発展の足掛かりを得られました。やったね :thumbsup_tone2: