🕌

最強の WSL 環境を作る

2022/01/09に公開
2

最強の WSL 環境を作る

まあ、何が最強なのかよくわからないのですが。

WSLg は GUI が動いて音もなるので大変便利なのですが、systemd が動作していない弱点があります。LXD を多用している身としては結構しんどいのですね。

snapd が使えないので、JetBrains の開発ツールをインストールするのもちょっと面倒。まあ、Ubuntu Make を使えばいいのでそれほど気にはしていないのですが...

というわけで、定期的にいろいろ試している今日この頃、ようやく自分が常用しているものが全部動く環境が作れました。

セットアップ

(2022/01/11 追記)

手順は、入力するコマンドを PowerShell だったり、WSL だったりといりみだれます。次のルールで書いていますので、間違えないようにしてください。

プロンプトが > のときは PowerShell に入力してください。

> echo hello

プロンプトが $ のときは WSL の Linux 環境で入力してください。

$ echo hello

WSLg 環境の構築

何はなくとも WSLg 環境の構築です。

WSL カーネルが古いと WSLg が動かないので念のためアップデートします。

> wsl --update

WSL 環境を作ります。

> wsl --install -d Ubuntu

ユーザー名とかパスワードをいつものように設定してください。apt コマンドでアップグレードもしておきましょう。

$ sudo apt update
$ sudo apt full-upgrade

ターミナルとかをインストールします。

$ sudo apt install xfce4-terminal x11-apps

WSLg の Wayland コンポジターで表示されるはずです。

$ xfce4-terminal &

xeyes を起動して楽しみましょう。初回起動は 10 秒くらい待たされるかも。

$ xeyes &

後で詳しく解説しますが、xfce4-terminal は Wayland ネイティブアプリで、xeyes は X11 アプリです。

genie で systemd 化する

genie はコンテナーとして systemd を起動するみたいです。(嘘ついてたらごめんなさい)

genie は .NET 5 Runtime が必要なのでインストールします。セットアップ方法はUbuntu に .NET SDK または .NET ランタイムをインストールするを参考にしてください。

$ sudo apt install dotnet-runtime-5.0

続けて genie をインストールします。セットアップ方法は公式サイトを参考にしてください。

$ sudo apt install systemd-genie

genie は起動時にすべてのサービスの起動を待つのですが、実際にはいくつか起動できないサービスがあります。標準では待機時間の最大が 240 秒となっていてかなり長いので、30 秒くらいに変更します。

/etc/genie.ini ファイルを開いて、systemd-timeout の値を 30 位にしてください。

/etc/genie.ini
[genie]
...

systemd-timeout=30

...

いったん、WSL 環境を停止します。

> wsl -t Ubuntu

WSL 環境で genie を起動します。(-s はシェルを起動します)

余談ですが、ホスト名に -wsl というサフィックスがつきます。

> wsl -d Ubuntu genie -s
Waiting for systemd....!!!!!

Timed out waiting for systemd to enter running state.
This may indicate a systemd configuration error.
Attempting to continue.
Failed units will now be displayed (systemctl list-units --failed):
  UNIT                       LOAD   ACTIVE SUB    DESCRIPTION
● systemd-remount-fs.service loaded failed failed Remount Root and Kernel File Systems
● multipathd.socket          loaded failed failed multipathd control socket

LOAD   = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
SUB    = The low-level unit activation state, values depend on unit type.

2 loaded units listed.

最初は起動しないサービスのせいで 30 秒ほど待たされるので気長に待ちましょう。

メッセージにある通り、systemd-remount-fs.service と multipathd.socket の二つのサービスが起動していません。問題が出るサービスなどは Systemd units known to be problematic under WSLにあります。

systemd-remount-fs.service はパーティションにラベル cloudimg-rootfs がないのが原因のようです。

$ cat /etc/fstab
LABEL=cloudimg-rootfs   /        ext4   defaults        0 1

ルートパーティションのデバイスを調べます。ドキュメントでは /dev/sdb なことが多いよと書かれていましたが、自分の手元では /dev/sdc でした。

$ mount | grep '\s/\s'
/dev/sdc on / type ext4 (rw,relatime,discard,errors=remount-ro,data=ordered)

ラベルをつけます。

$ sudo e2label /dev/sdc cloudimg-rootfs

multipathd.socket は WSL そのものが非対応らしいので、無効化します。

$ sudo systemctl disable multipathd.socket

これで、次回からは 5 秒くらいで起動するようになります。

LXD をセットアップする

すんなり動きそうなのですが、動きませんでした...

lxd グループに自分を追加します。

$ sudo usermod -aG lxd $USER

いったんシェルを抜けて、再度 genie を起動します。

$ exit
> wsl -d Ubuntu genie -s

そのままだと lxd init に失敗するので、https://github.com/lxc/lxd/issues/8724#issuecomment-828800317 に書かれているおまじないをします。

$ sudo iptables -A INPUT
$ sudo systemctl reload snap.lxd.daemon

(2022/01/11 sudo systemctl reload snap.lxd.daemon を追加しました)

あとはいつも通り、lxd init で初期化します。質問されますが、すべてデフォルトで構いません。

$ sudo lxd init

...

コンテナーを作成して起動します。

$ lxc launch ubuntu:20.04 ubuntu

```shell
$ lxc exec ubuntu bash
#

無事使えました!

(2022/01/11 追記)

残念ながら、再起動すると LXD コンテナーの起動に失敗します。lxd init 前のおまじないを、WSL 環境の起動のたびに実行してください。

$ sudo iptables -A INPUT
$ sudo systemctl reload snap.lxd.daemon

LXD デーモンの起動前に INPUT ルールが追加されるように設定すればいいだけですけど、どうすればいいんだろう?

X11 アプリケーションが動くようにする

最近の genie は xfce4-terminal のような Wayland ネイティブアプリはそのまま WSLg で表示できます。(うまく動かない時は、wsl -t Ubuntu と環境を一度停止してから再度試してみてください)

しかし、X11 アプリケーションはそのままでは表示できませんでした。

WSL その222 - Linux GUIアプリを動かすWSLgのアーキテクチャーと仕組みを見るとわかりやすいです。

上記ブログから引用した図ですが、WSLg では通常の WSL 環境の Ubuntu のコンテナーとは別に Wayland 環境用のシステムコンテナーが別途起動しています。双方のコンテナーの通信はソケットで行われています。

X11 アプリケーションは XWayland で、Wayland アプリケーションは Wayland と別々のソケットで通信しています。genie は Wayland のソケットはよしなにしてくれているようですが、X11 のソケットは genie 側からはアクセスできず、X11 アプリは起動しません。

https://github.com/arkane-systems/genie/issues/175#issuecomment-922526126 にこのソケットを genie 側からアクセスできるようにするハックがあるのでそれを使います。(少しいじっています)

次の内容を ~/.bashrc の最後に追加してください。

wsl_mount_root=/mnt
wsl_windir="${wsl_mount_root}/c/WINDOWS/System32"
wsl_distro=$WSL_DISTRO_NAME
wsl_wslg_display=0

# check if in-built wslg socket exists at all
if test -S $wsl_mount_root/wslg/.X11-unix/X0; then
  # fix is needed in case system socket in /tmp doesn't exist or it's true, canonical path isn't the wslg socket's one
  if ! test -e /tmp/.X11-unix/X${wsl_wslg_display} || ! test "$(readlink -f /tmp/.X11-unix/X${wsl_wslg_display})" = "$wsl_mount_root/wslg/.X11-unix/X0"; then
    echo -e "* WSL: restoring WSLG X11 server socket on /tmp/.X11-unix/X${wsl_wslg_display}"
    # recreate /tmp/.X11-unix directory if needed
    if test ! -d /tmp/.X11-unix; then
      $wsl_windir/wsl.exe -d $wsl_distro -u root install -dm 1777 -o root /tmp/.X11-unix
    fi
    # delete existing socket if needed. this must obviously break something that created that socket
    # and you'd rather define display not colliding with other sockets
    if test -e /tmp/.X11-unix/X${wsl_wslg_display}; then
      $wsl_windir/wsl.exe -d $wsl_distro -u root rm -Rf /tmp/.X11-unix/X${wsl_wslg_display} /tmp/.X${wsl_wslg_display}-lock
    fi
    # if desired display is different than wslg's default 0, check if WSLG socket doesn't already exist on X0
    # if yes, delete it aswell to avoid having the same wslg socket bound to two separate display numbers
    if test $wsl_wslg_display -ne 0; then
      if test -e /tmp/.X11-unix/X0 && test "$(readlink -f /tmp/.X11-unix/X0)" = "$wsl_mount_root/wslg/.X11-unix/X0"; then
        $wsl_windir/wsl.exe -d $wsl_distro -u root rm -Rf /tmp/.X11-unix/X0 /tmp/.X0-lock
      fi
    fi
    # create symlink to wslg socket
    $wsl_windir/wsl.exe -d $wsl_distro -u root ln -s $wsl_mount_root/wslg/.X11-unix/X0 /tmp/.X11-unix/X${wsl_wslg_display}
    # export updated display number for user's convenience
  export DISPLAY=:${wsl_wslg_display}
  fi
fi

念のため、WSL 環境を停止して再度起動しなおします。

> wsl -t Ubuntu
> wsl -d Ubuntu genie -s
Waiting for systemd....!!!!!!!
* WSL: restoring WSLG X11 server socket on /tmp/.X11-unix/X0

テストで xeyes を起動します。

$ xeyes &

少し待たされますが表示されるはずです。

余談ですが、起動時のスイッチで Wayland で動作するアプリケーションもあるようです。Firefox - ArchWiki などがそうです。

$ MOZ_ENABLE_WAYLAND=1 firefox

Wayland か X11 かは xeyes の目玉が動くかどうかで区別できるそうです。Xwayland アプリケーションであることを視認する

日本語化と日本語入力

WSLg でそのまま Windows の IME が使えればいいんですけど、そうではありません。

WSL2にFcitx+Mozcを入れて日本語入力するを参考に、Mozc をセットアップします。

$ sudo apt install language-pack-ja fcitx-mozc dbus-x11 fonts-takao-* fonts-noto-*
$ sudo update-locale LANG=ja_JP.UTF8
$ sudo sh -c "dbus-uuidgen > /var/lib/dbus/machine-id"
$ cat << 'EOS' | tee -a ~/.profile
#Added by https://astherier.com/blog/2020/08/install-fcitx-mozc-on-wsl2-ubuntu2004/
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export XMODIFIERS=@im=fcitx
export DefaultIMModule=fcitx
if [ $SHLVL = 1 ] ; then
  (fcitx-autostart > /dev/null 2>&1 &)
  xset -r 49  > /dev/null 2>&1
fi
#end
EOS

WSL 環境を一旦停止して再度起動します。ただし、genie -s だと日本語入力時の変換候補のウインドウが出ませんでした。genie -l だと問題がないようです。(環境変数かなにかの問題だと思うのですが...)

> wsl -t Ubuntu
> wsl -d Ubuntu genie -l
Waiting for systemd....!!!!!!!!!!!
Connected to the local host. Press ^] three times within 1s to exit session.

Ubuntu 20.04.3 LTS shuttle-wsl pts/1

shuttle-wsl login:

ログインユーザーとパスワードを入力してログインします。

このままだと X11 アプリケーションが英語キーボード配列になってしまいます。fcitx の設定でキーボードを変更します。

$ fcitx-config-gtk3

スクリーンショットのようになるように、英語キーボードを削除して、日本語キーボードを追加して一番上に持ってきてください。

これで日本語入力も問題ないはずです。

好みに応じて

そのままだと文字が小さすぎたので... 150% 拡大にします。

$ gsettings set org.gnome.desktop.interface text-scaling-factor 1.5

JetBrains Rider をインストールします。

$ sudo snap install rider --classic

GNU EMacs をインストールします。

$ sudo apt install emacs

最後に

めんどくせー!

あ、「普通にネイティブの Ubuntu 使えばいいじゃん」とか突っ込まないでください...

Discussion