WSL2 で cron を有効にして OpenSSH server を走らせる

## 前提
- WSL2 が有効．
- Ubuntu がデフォルトのディストリビューションで WSL2 に設定してあること．
- Ubuntu のデフォルトのユーザがグループ sudo に入っていること．

## 手順

### (a) Cron の有効化

先づ，Ubuntu に `/srv/cron-start.sh` というファイルを作成します．（ファイル名は何でも良い）

```bash
#!/bin/sh
service cron restart
```

`chown root: /srv/cron-start.sh && chmod 0744 /srv/cron-start.sh`

次に，このファイルをパスワード無しで root で実行することができる様にします：

`sudo visudo` で sudoers の末尾に追加：

```
%sudo ALL = NOPASSWD: /srv/cron-start.sh
```

そして，Windows のログイン項目を作成します：

1. Windows+R を押し，`shell:startup` と入力し，Enter．
2. 開いたフォルダに，`wsl-cron.bat` というファイルを作成，VS Code などのエディタで（CRLF 行末になる様に注意して）編集：

```bat
wsl.exe sudo /srv/cron-start.sh
```

### (b) OpenSSH server の有効化

openssh-server がインストールせられていることを確認します：

```bash
apt install openssh-server
```

次いで，`/etc/cron.d/cron-sshd` なりを作成：

```
@reboot  root  service ssh restart
```

この時点で，初めて `sudo /srv/cron-start.sh` を実行すると，sshd も起動するはずです．

ここで，`~/.ssh/authorized_keys` に SSH をそこからしようとしているところの公開鍵を追加するのを忘れずに．

この状態で Windows 側の PowerShell なりから

```powershell
ssh.exe user@localhost
```

ができるようになっているはずです．

然し，これだけではこのコンピュータの外部から SSH することはできません．

### (c) WSL2 の SSH に外部からアクセス可能ならしめる

先づ，Windows Firewall で有効になっているドメインに対して，Inbound TCP 22 を許可します．これには色々な方法がございましょうから，各自の課題とします．

続き，PowerShell (as Administrator) で実行することの：

```powershell
netsh.exe interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=22 connectionaddress=127.0.0.1 connectport=22
```

これだけです．これで外部から IPv4 で WSL2 に SSH することができる様になりましょう．尚，外部 IPv6 ネットワークからアクセス可能ならしめる術は，各自で研究なさってください．

## 応用課題

これで，cron が利用可能になりましたから，様〻なことをすることができます．

例えば各ユーザにおいて起動時にコマンドをバックグラウンドで実行するには，以下のようにできましょう：

```bash
$ crontab
@reboot [command] </dev/null >&0 2>&0 &
^D
```


## 参考

何故，この様な面倒臭いことをせねばならないかと言えば，WSL がまっとうな init を提供していませんから，runlevel が利用可能にないからして，当然に一括で統べてのサーヴィスを起動するなどが不可能であるからです．

尚，WSL2 では WSL2 側で listen しているポートは自動的に Windows の localhost に転送せられます；然しながら，これは localhost だけですから，外部からアクセスせさせるために，netsh で IPv4 -> IPv4 の転送設定を行ったのであります．

## 追記 2020-11-04

portproxy を設定したままにすると，次回起動時に WSL がポートが使用済であると判断して肝心の 127.0.0.1 への転送を行ってくれなくなるという不具合を発見いたしました．斯様の問題を避けるには，127.0.0.1 に頼るのをやめ，起動の度に WSL 側から netsh を実行するスクリプトを書く必要があります．

WSL から：

```bash
powershell.exe -Command '$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()); $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)'
```

を実行すると，`True` が返ってくることから，WSL2 から管理者権限の PowerShell を実行できることが判ります．これを利用して，以下の樣なスクリプト `/srv/forward-ports.sh` を作成して：

```bash
#!/bin/sh

# This can be executed as any user.

addr=$( ip -4 -o addr show dev eth0 | grep global | awk '{print $4}' | head -n1 | sed 's/\(.*\)\/.*/\1/' )
POWERSHELL_PATH=/mnt/c/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe

powerShell ()
{
        "$POWERSHELL_PATH" -Command - 2>&1 | tr -d '\r'
}

is_int ()
{
        [ "$1" ] && [ "$1" -gt 0 ] && [ "$1" -lt 65536 ] >/dev/null 2>&1
}

is_admin=$( echo '$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()); $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)' | powerShell )

[ "$is_admin" = True ] || {
        echo "Failed to launch PowerShell as Administrator ($is_admin), exiting..." >&2
        exit 1
}

while is_int "$1" ; do
        port=$(( "$1" ))
        powerShell <<EOF >/dev/null 2>&1
netsh interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=$port
netsh interface portproxy delete v6tov4 listenaddress=:: listenport=$port
netsh interface portproxy delete v4tov6 listenaddress=0.0.0.0 listenport=$port
netsh interface portproxy delete v6tov6 listenaddress=:: listenport=$port
netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=$port connectaddress=$addr connectport=$port
netsh interface portproxy add v6tov4 listenaddress=:: listenport=$port connectaddress=$addr connectport=$port
netsh advfirewall firewall delete rule name="WSL TCP $port"
netsh advfirewall firewall add rule name="WSL TCP $port" dir=in action=allow protocol=TCP localport=$port
EOF
        echo "Forwarding: 0.0.0.0:$port -> $addr:$port"
        echo "Forwarding: [::]:$port -> $addr:$port"
        shift
done
```

```bash
chmod a+x /srv/forward-ports.sh
```

`/etc/cron.d/wsl-forward-tcp-22`:

```
@reboot root /srv/forward-ports.sh 22 >/dev/null 2>&1
```

これで，ポートの開放および転送を起動時に自動で行う樣になります．

今すぐ手動で行うには，

```
$ /srv/forward-ports.sh 22
```

# 参考文献

- https://docs.microsoft.com/en-us/windows-server/networking/technologies/netsh/netsh-interface-portproxy