WSL2でSystemdを使うハック

(追記)
この記事はobsoleteです（使われなくなりました）。[systemd を使用して WSL で Linux サービスを管理する | Microsoft Learn](https://learn.microsoft.com/ja-jp/windows/wsl/systemd)に従ってください。
(追記ここまで)

# initについて

Linuxを含むUnix系OSの起動に使われるシステム。カーネルから最初に起動され、プロセスID 1が付与され、ほかのすべてのプロセスの先祖として動作し、さまざま初期処理を行うプログラム。

Linuxでは、SysVInit → Upstart → Systemd のように置き換えれらてきた。

- [Wikipedia: init](https://ja.wikipedia.org/wiki/Init)
- [Qiita: initまとめ(ざっくり)](https://qiita.com/h_tyokinuhata/items/26b7dd3526e7061596b9)

# WSLのinitについて

WSLはMSによるカスタムのinitを使っている。Windowsとの相互運用に必要な重要な処理を行っており、置き換えられない。一方で、Systemdのような高度なことは行わない。

Systemdもインストールはされるが、無効化されている。有効にしようとしても、PID 1で動作しないので、`systemctl` によるサービス管理がエラーになる。

# `systemd` を PID 1 で動かす方法（基本方針編）

Ubuntuのコミュニティサイトに [Using snapd in WSL2](https://discourse.ubuntu.com/t/using-snapd-in-wsl2/12113) という投稿があったので解説してみる。

## `unshare` コマンドで簡易コンテナーを作る

最初に、以下のコマンドを実行する。

```
sudo daemonize /usr/bin/unshare --fork --pid --mount-proc /lib/systemd/systemd --system-unit=basic.target 
```

`unshare` コマンドは、実行コンテキストに名前空間を割り当てて、ほかのプロセスから分離する。
オプションは以下のものを指定している。

* `--fork` ： 指定したプログラム（`/lib/systemd/systemd --system-unit=basic.target`）を`unshare`の子プロセスとしてforkさせる
* `--pid` ： PID 名前空間を分離する
* `--mount-proc` ： マウント名前空間を分離し、`/proc` を再マウントする

`systemd` コマンドは、`unshare` によって、隔離された名前空間のPID 1として起動する。なお、マルチユーザーの基本サービスが定義された `basic.target` がターゲットとして指定されている。

そして、それら全体を現在のシェルから切り離してデーモン化するため、`demonize` コマンドを使用している。

この時点で `systemd` はPID 1として動き出すものの、あくまで隔離された簡易コンテナー環境の中だけの話である。簡易コンテナーの外でサービスを管理しようとすると、簡易コンテナーの中の `systemd` と通信できずに失敗する。

## `nsenter` コマンドで簡易コンテナーの中に入る

というわけで、サービスを管理する場合は簡易コンテナーに入らないといけない。それには、以下のコマンドを実行する。

```
exec sudo nsenter --target $(pidof systemd) --all su - $LOGNAME
```

`nsenter` コマンドは、隔離された名前空間を持つ簡易コンテナ―に入ってプログラムを実行する。オプションは以下のものを指定している。

* `--target $(pidof systemd)` : 簡易コンテナ―として隔離実行されている `systemd` の実際の PID をターゲットとして指定
* `--all` : ターゲットプロセスのすべての名前空間をマウントしなおす。
* `su - $LOGNAME` : 現在のログインユーザーとしてログインしなおす。

そして、現在のプロセスを置き換えるため、 `exec` コマンドを使用している。

ここまで成功すれば、`systemctl` でも `snap` でも動かせるようになっているはず。

#  `systemd` を PID 1 で動かす方法（派生編）

この方法で起動した `systemd` や `nsenter` は、永続的ではないので、毎回実行しないといけない。

`systemd` は、Windowsを再起動して最初にWSL2を実行するときに実行しておく必要がある。

`nsenter` は、WSL2を実行するたびに毎回最初に実行しておく必要がある。

これらを毎回実行するのが面倒な人には、次の3つの方法を紹介する。

## `genie` コマンド

https://github.com/arkane-systems/genie/

`genie` コマンドは、 `unshare` と `nsenter` のラッパー的なコマンド。コマンド名は、瓶（コンテナー）に封じられた魔法の精[^1]から。

[^1]: 「アラジンと魔法のランプ」の方が有名だけど、あの話、原典の千夜一夜物語には収録されていないらしいよ。

インストール方法と使い方は上記githubのreadmeを参照のこと。

## （追記） `subsystemctl` コマンド

[@sora_h](https://twitter.com/sora_h)さん作のコマンドもあります。上記のgenieに影響を受けたとのこと。

https://github.com/sorah/subsystemctl

## `/etc/bash.bashrc` で自動実行させる

方法は下記のリンク先に書いてある通り。

https://forum.snapcraft.io/t/running-snaps-on-wsl2-insiders-only-for-now/13033

一応、手順を簡単に解説しておく。

* `/usr/sbin/start-systemd-namespace` と `/usr/sbin/enter-systemd-namespace` という2つのスクリプトファイルを作成しておく
* `/usr/sbin/start-systemd-namespace`の中で実行する `sudo /usr/sbin/enter-systemd-namespace` コマンドにはパスワードが不要なように設定しておく
* `/etc/bash.bashrc` で `source /usr/sbin/start-systemd-namespace` を実行するようにしておく
* 環境変数 `BASH_ENV` に `/etc/bash.bashrc` を設定しておく（Windows側に `%WSLENV%` を設定して引き継ぐ）

`sudo`コマンドの設定は `sudo visudo`コマンドを使って `/etc/sudoers` ファイルを編集するのが基本だが、今回の `/usr/sbin/enter-systemd-namespace` のための設定は `/etc/sudoers.d/` ディレクトリに切り出しておくのもよい。[その方法を紹介しているページはこちら](https://hoverbear.org/blog/getting-the-most-out-of-wsl/)。

## ログインシェルを置き換える

方法は下記のリンク先に書いてある通り。

https://github.com/shayne/wsl2-hacks

一応、手順を簡単に解説しておく。

* `/usr/bin/bash` スクリプトファイルを作成しておく
* `/etc/passwd` ファイルを編集して、rootユーザーのログインシェルを `/usr/bin/bash` に設定する
* ログインユーザーをrootにする
  * Linuxディストリビューションをストアアプリとしてインストールしている場合は `[ディストリビューション名].exe --config --default-user root` を実行する
  * そうでない場合は `wsl.exe [ディストリビューション名]` コマンドで起動する際にユーザーを指定しなければrootユーザーで起動するはず

一応紹介はしたものの、2番目の `bash.bashrc` で自動実行させる方法の方がおすすめ。