WSL2 と Tera Term で X Forwarding 

# 概要

Windows Subsystem for Linux 2 (WSL2) に Tera Term で SSH で接続し X Forwarding で X クライアント アプリケーションを動作させる手順をまとめます。また、接続を自動化する Tera Term マクロを作成しました。

## 利点

- VcXsrv に対する Windows Defender ファイアウォールでパブリックはもちろんプライベートも閉じてしまってよい。
- VcXsrv のデフォルト インストールではディスプレイ番号 0 の場合 **Disable access control** (`-ac`) オプションが不要。
- X Forwarding が WSL2 の `$DISPLAY` を自動的に設定。
- ターミナル側からディスプレイ番号を指定可能[^1]。
- `/etc/ssh/sshd_config` も (Ubuntu-18.04 の場合) デフォルトのまま。

[^1]: 試していませんが Putty でも可能か。 `ssh.exe -X` は `set DISPLAY=:0.0` としてから実行するとエラーになります。

ちなみに、 SSH の X Forwarding を使わない場合は [Can't use X-Server in WSL 2 #4106 の代表的な投稿](https://github.com/microsoft/WSL/issues/4106#issuecomment-638199622 "Can't use X-Server in WSL 2 · Issue #4106 · microsoft/WSL · GitHub") によれば

> 1. Windows Security -> Firewall & network protection -> Allow an app through firewall -> make sure VcXsrv has both public and private checked
> 2. Launch VcXsrv with "Disable access control" ticked
> 3. DISPLAY variable:
> 
```bash
export DISPLAY=`grep -oP "(?<=nameserver ).+" /etc/resolv.conf`:0.0
```

## 欠点

- `$DISPLAY` を手動で `.bash_profile` に書くのに比べ随分と手間暇がかかる。
- (`$DISPLAY` の自動設定と Windows ファイアウォールのデフォルトにこだわると) 他のターミナル アプリから X を使えない[^2]。

[^2]: 正確には X クライアントに都度都度 `-display <WindowsIPAddress>:0.0` オプションをつければ使えます。

## 資材調達

- [Windows 10 用 Windows Subsystem for Linux のインストール ガイド](https://docs.microsoft.com/ja-jp/windows/wsl/install-win10 "Windows Subsystem for Linux (WSL) を Windows 10 にインストールする | Microsoft Docs")
- [VcXsrv Windows X Server](https://sourceforge.net/projects/vcxsrv/ "VcXsrv Windows X Server download | SourceForge.net")
- [Tera Term Home Page](https://ttssh2.osdn.jp/ "Tera Term Open Source Project")

### 動作確認バージョン

| ソフトウェア | バージョン |
|:--|:--|
| Windows 10 Home | 2004 (ビルド 19041.388) |
| Linux ディストリビューション | Ubuntu 18.04 |
| VcXsrv | 1.20.8.1 |
| Tera Term | 4.105 |

# 手順

## 1. 公開鍵と秘密鍵の作成

`ssh-keygen.exe` を使ったほうが手ばやい気もしますが、 Tera Term を使いこなすために:

1. **Tera Term** を起動。
1. **Tera Term: 新しい接続** は **キャンセル** 。
1. **設定(S)** > **SSH鍵生成(N)…** > **TTSSH: 鍵生成** ですべてデフォルト[^3]のまま **生成(G)** 。
1. 数秒で鍵が生成されグレイアウト部分が解除される。 **鍵のパスフレーズ:** も今回は空欄のまま **公開鍵の保存(I)** と **秘密鍵の保存(P)** 。保存先は任意だが、次の公開鍵の登録と WSL2 への接続のときに参照する。ここでは
    1. フォルダーは `C:\Users\<UserName>\Documents\TTSSH` (右クリックのコンテキスト メニューで作成)
    1. 公開鍵のファイル名はデフォルトの `id_rsa.pub`
    1. 秘密鍵のファイル名はデフォルトの `id_rsa`

[^3]: オプションの意味がよく分かっていませんが、公衆網に接続するわけでもないので。

## 2. 公開鍵を WSL2 に登録

(初稿では `authorized_keys` に対する認識と調査が不足していて、また、  `authorized_keys` が存在していない状態を想定していて、単純にリネーム コピーしましたが、 `authorized_keys` には複数の公開鍵を追加していけるものと知りましたので、それにふさわしい方法に修正します。)

WSL2 のコンソールを開き公開鍵を `~/.ssh` ディレクトリー配下の `authorized_keys` に追記 (存在しなければ作成):

```console
$ test -e ~/.ssh || mkdir ~/.ssh
$ cat /mnt/c/Users/<UserName>/Documents/TTSSH/id_rsa.pub >>~/.ssh/authorized_keys
```

WSL2 からは上記手順での公開鍵の保存先例が  `/mnt/c/Users/<UserName>/Documents/TTSSH/id_rsa.pub` という形で参照されます。

`>>` がファイルの末尾に追記 (存在しなければファイルを作成) を指示していますが、これをうっかり単一の `>` にしてしまうと、もし `authorized_keys` が存在していたとすると、上書きになってもともと登録されていた鍵が消失してしまいます。

## 3. WSL2 (Ubuntu 18.04) で `sshd` の起動

初期状態で **コマンド プロンプト** から `sshd` の状態を確認[^4]:

```console
C>wsl.exe --exec /usr/sbin/service ssh status
 * sshd is not running

C>echo %errorlevel%
3

```

[^4]: PowerShell なら `$LASTEXITCODE` 。

`sshd` を起動:

```console
C>wsl.exe --user root --exec /usr/sbin/service ssh start
 * Starting OpenBSD Secure Shell server sshd                                                                     [ OK ]

C>echo %errorlevel%
0

```

なお、 Windows を再起動すると `sshd` は停止し自動では開始しません。後に記載する Tera Term マクロはこのことに配慮しています。

## 4. VcXsrv の起動

次項の Tera Term で接続の前ならいつでも可。

### **XLaunch** ウィザード経由で起動する方法

1. **XLaunch** を起動。
2. **Display settings**: **Display number** に **0** を入れる[^5]。 **次へ(N) >** 。
3. **Client setup**: **次へ(N) >** 。
4. **Extra settings**: デフォルトのインストール状態では手順 2. **Display number** が **0** の場合は **Disable access control** にチェックは不要。 **次へ(N) >** 。
5. **Finish configuration**: **完了**[^6] 。
6. **システム トレイ** で ***X*** のアイコンをポイントし **ツールチップ** に ***ComputerName*****:0.0 - 0 clients** と表示されることを確認。

[^5]: デフォルトの **-1** でも最初の起動では 0 になるはずですが念のため。

[^6]: **Save configuration** はオプションですが、次回はここで保存された `*.xlaunch` ファイルをダブル クリックすると保存された設定ですぐに VcXsrv が起動します。

### コマンドラインから起動する方法

```console
C>"%ProgramFiles%\VcXsrv\vcxsrv.exe" :0 -multiwindow
```

PowerShell の場合は

```powershell
& "$env:ProgramFiles\VcXsrv\vcxsrv.exe" :0 -multiwindow
```

## 5. Tera Term で X Forwarding を指定して接続

1. **設定(S)** > **SSH転送(O)…** > **SSHポート転送** > **リモートのXアプリケーションをローカルのXサーバに表示する** にチェック[^7]。
2. **ファイル(F)** > **新しい接続(N)…** 
3. **Tera Term: 新しい接続**: **ホスト(T):** `localhost` 。その他はデフォルトで **OK**  。
4. **SSH認証**: **ユーザ名:** WSL2 のユーザー名。 **パスフレーズ(P):** は今回は空欄のまま。 **RSA/DSA/ECDSA/ED25519鍵を使う** を選択、 **秘密鍵(K):** `C:\Users\<UserName>\Documents\TTSSH\id_rsa` 。
5. `$DISPLAY` が次のように設定されていれば成功[^8]。

```console
$ echo $DISPLAY
localhost:10.0
```

[^7]: この状態を保存し次回に自動で読み込むには **設定(S)** > **設定の保存(S)…** から **ドキュメント** フォルダーに `TERATERM.INI` という名前で保存します。

[^8]: Tera Term をもう一つ起動し同様に接続すると `localhost:11.0` とディスプレイ番号がインクリメントされます。

### WSL2 への `localhost` 接続について

当方ではあるとき突然 `localhost` で WSL2 への接続が拒否されるようになりました。

[**ここ**](https://github.com/microsoft/WSL/issues/5439#issuecomment-646981033 "WSL2 not forwarding ports · Issue #5439 · microsoft/WSL · GitHub") の情報にしたがって、 **コントロール パネル\ハードウェアとサウンド\電源オプション\システム設定** の **高速スタートアップを有効にする (推奨)** のチェックを外したら、 (再起動しなくても) 接続が復帰しました。

## 6. X クライアント アプリケーションのインストールと起動テスト

まずはパッケージをアップデート:

```console
$ sudo apt update && sudo apt upgrade
```

起動テスト用として定番アイテムの `x11-apps` パッケージをインストール:

```console
$ sudo apt install x11-apps
```

たとえば

```
$ xlogo -fg red -bg green &
```

## VcXsrv のディスプレイ番号と複数起動について

[TTSSH コマンドラインの /ssh-X オプションのヘルプ](https://ttssh2.osdn.jp/manual/4/ja/commandline/ttssh.html#ssh-x "TTSSH コマンドライン") より引用しますと

> /ssh-X[[\<hostname\>]:\<displaynumber\>[.\<screennumber\>]]
X11転送を有効にする
> 転送先ディスプレイを指定した場合は、その転送先へX11接続を転送する。
> 転送先ディスプレイを指定しなかった場合は、以下の優先順位で転送先が決定される。
> 1. 設定ファイルの [TTSSH] セクションの X11Display 設定
> 2. 環境変数 DISPLAY の値
> 3. localhost:0.0

上記手順の状態では 3. localhost:0.0 で転送先が決定されます。上記手順で VcXsrv のディスプレイ番号に 0 を念のため明示的に指定したのはこのためです。

上記手順の状態で VcXsrv にディスプレイ番号 1 を指定してもう一つ起動してみます。また、状態を分かりやすくするためにマルチ ウィンドウを外します。

ここがややこしいのですが、デフォルトのインストール状態で `%ProgramFiles%\VcXsrv\X0.hosts` というテキスト ファイルがあり、これがディスプレイ番号 0 のアクセス許可を指定しています。

```console
C>type "%ProgramFiles%\VcXsrv\X0.hosts"
localhost
inet6:localhost
```

SSH を使わないで WSL2 で `/etc/resolv.conf` などから VcXsrv が稼働している IP アドレスを拾って `$DISPLAY` に設定するやり方だと、本来ならば `X0.hosts` にその IP アドレスを追記してやらなければなりません。しかし、この IP アドレスは変動します。

ディスプレイ番号 1 については `X1.hosts` が対応しますが、デフォルトでは存在しません。

ここでは手抜きで `-ac` (Disable access control) オプションを使ってしまいます。

```console
C>"%ProgramFiles%\VcXsrv\vcxsrv.exe" :1 -ac
```

背景が真っ黒なウインドウが一つ現れます。

そして、環境変数 `%DISPLAY%` をセットしてから Tera Term ももう一つ起動します。

```
C>set DISPLAY=localhost:1.0

C>"%ProgramFiles(x86)%\teraterm\ttermpro.exe" localhost:22 /ssh /2 /user=<WslUserName> /auth=publickey /keyfile=%USERPROFILE%\Documents\TTSSH\id_rsa /ssh-X

```

いま起動したほうの Tera Term のコンソールでたとえば `xeyes` を起動してみます。

```console
$  xeyes &
[1] 1048
```

すると、先ほどの背景が真っ黒な VcXsrv のウインドウの中に `xeyes` が現れます。この中ではまだウインドウ マネージャーが動いていないので、この `xeyes` を閉じるには Tera Term のコンソールで `kill %1` などとします[^9]。

[^9]: この `1` は `xeyes &` コマンドの下に表示された `[...]` の中の数字と同じものを指定。 

なお、この Tera Term でディスプレイ番号を指定する項目は GUI には見あたりません。

# Tera Term マクロ

以上を踏まえた Tera Term マクロを作成しました。

- `exec` で `wsl.exe` を呼び出し sshd の状態をチェックし必要に応じて起動しています。 [**Windows と時刻がずれる問題**](https://github.com/microsoft/WSL/issues/4245 "system date is not same with windows (WSL 2) · Issue #4245 · microsoft/WSL · GitHub") の暫定的対処もここに盛り込んでしまいました[^10]。
- `exec` で外部コマンドの終了コードしかうけとれないのがつらいところです[^11]。接続までしかしていないので、 Tera Term マクロ (TTL) に拘泥することもなかったのかもしれませんが、今回は TTL をすこし研究したかったのです。
- VcXsrv を起動していないと警告ダイアログが二回出ますが、事後に VcXsrv を起動しても X クライアントは動作することもあって、対策はしていません。
- 一応 `/ssh-X` オプションの引数を受け取れるようにしてあります。

[^10]: `hwclock -s` だと 1 秒以上ずれが残ることに気づいたので、 `date -s` にしてみましたが 0.5 秒程度ずれます。 (`echo %TIME% && wsl -e date +%H:%M:%S.%N` で確認。)

[^11]: 一時ファイルやクリップボード経由というのは気乗りしません。

使用例:

```console
C>"%ProgramFiles(x86)%\teraterm\ttpmacro.exe" /V %USERPROFILE%\Documents\Scripts\connect_wsl2.ttl localhost:1.0
```

```text:connect_wsl2.ttl
; https://devblogs.microsoft.com/commandline/whats-new-for-wsl-in-insiders-preview-build-18945/
host = 'localhost' 

username = '<WslUserName>'

getspecialfolder dir 'MyDocuments'
makepath keyfile dir 'TTSSH\id_rsa'

expandenv wsl '%windir%\Sysnative\wsl.exe '

; https://github.com/microsoft/WSL/issues/4245
getdate date '%Y-%m-%d %H:%M:%S'
sprintf2 wsldate '%s%s%' wsl ' --user root --exec /bin/date -s ' date
exec wsldate 'hide' 1

sprintf2 wslsshstatus '%s%s' wsl '--exec /usr/sbin/service ssh status'
exec wslsshstatus 'hide' 1
if result = 3 then
  sprintf2 wslsshstart '%s%s' wsl '--user root --exec /usr/sbin/service ssh start'
  exec wslsshstart 'hide' 1
endif
if result = 0 then
  ttsshopts = host
  strconcat ttsshopts ':22 /ssh /2 /auth=publickey /user='
  strconcat ttsshopts username
  strconcat ttsshopts ' /keyfile='
  strconcat ttsshopts keyfile
  strconcat ttsshopts ' /ssh-X'
  strconcat ttsshopts param2
  connect ttsshopts
endif
```

# 付録 Tera Term の Tips

- **設定(S)** > **設定の保存(S)…** でデフォルトのまま **ドキュメント** に `TERATERM.INI` として保存すると、次回からの起動時に読み込んでくれる。
- Ubuntu のプロンプトをカラーにするには `TERATERM.INI` の `TermType=xterm` を `TermType=xterm-256color` に変更する。
- Alt キーがターミナルに伝わるようにするには **設定(S)** > **キーボード(K)…** > **Tera Term: キーボードの設定** で **Meta キー:** を好みに応じて **off** 以外にする。