在 Windows Subsystem For Linux 2 内使用 systemd 的三种方法

一切后果自行承担。

WSL1 时代就有人提出了这个问题,到 WSL2 依然没有解决。

方法一

这个方法只支持 Windows 10 版本号高于 20201 的系统。意味着在本文撰稿时的 20H2 (19042) 无法使用。

/etc/wsl.conf 内添加如下内容:

[boot]
command = "sudo daemonize /usr/bin/unshare -fp --mount-proc /lib/systemd/systemd --system-unit=basic.target"

这个方法是由 @Biswa96 发现的。因为在初始化 WSL 之前,/init 进程会加载 wsl.conf,所以一个子进程会被分出来。这个子进程然后在系统调用层执行这行代码,最后通过 posix_spawn(3) 生成一个进程。

方法二

这个方法来自 wsl2-hacks 这个项目。大致是创建一个 fake shell,然后这个 shell 会截断所有去 wsl.exe bash ...的调用,并且会将这个请求发送给在 real bash 内运行的 systemd。我在 Debian 10、Ubuntu 20.04、Arch Linux 以及 RHEL 8 里测试通过了。

首先,更新系统并安装必要的包(Debian 系):

$ sudo apt update
$ sudo apt install dbus policykit-1 daemonize

ArchLinux 需要安装 dbus policykit daemonize (daemonize 需要 archlinuxcn 源,或者手动从 AUR 安装) 这三个包。

RHEL 需要安装 dbus polkit,而 daemonize 这个包需要从 EPEL 下载并手动安装,可以在这里获取到。

第二步,就是创建一个假的 bash。

$ sudo touch /usr/bin/bash-bootstrap-services
$ sudo chmod +x /usr/bin/bash-bootstrap-services
$ sudo editor /usr/bin/bash-bootstrap-services

接下来,将 <YOURUSER> 替换成你的用户名。

#!/bin/bash
# your WSL2 username
UNAME="<YOURUSER>"

UUID=$(id -u "${UNAME}")
UGID=$(id -g "${UNAME}")
UHOME=$(getent passwd "${UNAME}" | cut -d: -f6)
USHELL=$(getent passwd "${UNAME}" | cut -d: -f7)

if [[ -p /dev/stdin || "${BASH_ARGC}" > 0 && "${BASH_ARGV[1]}" != "-c" ]]; then
    USHELL=/bin/bash
fi

if [[ "${PWD}" = "/root" ]]; then
    cd "${UHOME}"
fi

# get pid of systemd
SYSTEMD_PID=$(pgrep -xo systemd)

# if we're already in the systemd environment
if [[ "${SYSTEMD_PID}" -eq "1" ]]; then
    exec "${USHELL}" "$@"
fi

# start systemd if not started
/usr/bin/daemonize -l "${HOME}/.systemd.lock" /usr/bin/unshare -fp --mount-proc /lib/systemd/systemd --system-unit=basic.target 2>/dev/null
# wait for systemd to start
while [[ "${SYSTEMD_PID}" = "" ]]; do
    sleep 0.05
    SYSTEMD_PID=$(pgrep -xo systemd)
done

# enter systemd namespace
exec /usr/bin/nsenter -t "${SYSTEMD_PID}" -m -p --wd="${PWD}" /sbin/runuser -s "${USHELL}" "${UNAME}" -- "${@}"

在保存之前,最好看一下 daemonize 的二进制文件在哪里,可以通过 whereis daemonize 来看到。有的发行版在 /usr/bin 下,而有的在 /usr/sbin 下。如果这个写错了会导致重进时 WSL2 宕机。

第三步,将这个 fake bash 设置成用户 root 的 shell。因为我们要 root 权限才能完成这个 hack。

$ sudo editor /etc/passwd

通过编辑 /etc/passwd 来更改 shell,编辑后看起来应该像这样:

root:x:0:0:root:/root:/usr/bin/bash-bootstrap-services

第四步,保存并在 PowerShell 内运行如下命令:

> wsl --shutdown
> ubuntu2004.exe config --default-user root

最后一行根据不同的 Distro,需要运行不同的 exe。例如 Arch 就是 arch.exe,Debian 就是 debian.exe

第五步,就是重新启动你的 WSL2 实例啦!看看是不是和往常一样登入进了自己的用户,并且可以用 systemd 了呢?

$ systemctl is-active dbus
active
$ sudo systemctl status mysql
● mariadb.service - MariaDB 10.3.27 database server
   Loaded: loaded (/lib/systemd/system/mariadb.service; enabled; vendor preset: enabled)
....

如果你还想在实例启动的时候运行一些命令,那么可以写入 /etc/rc.local 这个文件。

$ sudo touch /etc/rc.local
$ sudo chmod +x /etc/rc.local
$ sudo editor /etc/rc.local

创建之后,这么写就行了:

#!/bin/sh -e

# your commands here...

exit 0

注意,/etc/rc.local 只在第一次开启 WSL2 时有效。

方法三

使用 genie


如果喜欢本文,欢迎点击下方的「鼓掌」按钮!

如果上面没有加载出任何东西,可以点击这里