在 WSL2 中完美使用 systemd 的方法
WSL2 和一般 Linux 有個很大的差別在於他有微軟自己的 init,而非一般 Linux distro 常見的 systemd。這會造成的一個問題是有些仰賴 systemd 的 distro 的 package manager 在使用上會有點問題,例如 Arch Linux。
這篇簡單紀錄了在 WSL2 中的 ArchWSL 完美設定出 systemd 環境的方法。其他的 Distro 應該也能使用這篇的方法。
參考用的 dotfiles: maple3142/dotfiles
2022/11/17 更新: 因為今天 WSL 正式於 Microsoft Store 上推出 1.0.0 之後我終於能在 Windows 10 上安裝新版的 WSL 了,並且能成功使用內建的 systemd 支援,這部分可以參考最後的段落。
systemd bottle#
要在 WSL2 中跑 systemd 的方法用的是 namespace 的功能,會需要使用別人寫好的工具幫你把 systemd 包裝在那個環境之中,讓它變成 PID 1 之後就能正常運作了。可以從下面兩個擇一安裝:
- arkane-systems/genie: C# 寫的,功能較完整
- sorah/subsystemctl: Rust 寫的,速度很快
兩個我都有用過,用法也相當接近,後來因為效能上的考量,最後使用的是 subsystemctl。本文後面也都是以 subsystemctl 來介紹。
shell 方面都假設為使用的是 zsh
基本操作#
安裝完成後要先用 sudo subsystemctl start 把 systemd 啟用,每次重啟 WSL 之後也都要重新做一次這件事,這部分可以利用在你的 .zshrc 中寫入:
if [[ -v WSL_DISTRO_NAME ]] then
if (( $+commands[subsystemctl] )); then
if ! subsystemctl is-running; then
sudo subsystemctl start
fi
fi
fi這樣它就會自動幫你看情況啟用,所以基本上只需要輸入一次 sudo 的密碼而已。
之後可以用 subsystemctl shell 進入可以執行 systemd 的環境,或是用 subsystemctl exec 直接執行想要的指令,例如 subsystemctl exec -- systemctl status docker。
使用
subsystemctl exec最好要加上--,不然後面指令的 flags 也會被 subsystemctl 當成是它自己的 flags
例如要更新 Arch Linux 就記得要用 subsystemctl exec -- pacman -Syu,這樣如果有觸發到需要 systemd 的 package 也能正常使用。
這樣其實就基本上能使用 systemd,如果到這邊就滿意的話那就可以不用讀下去了。後面的文章是在說明怎麼把 subsystemctl shell 弄成可以自動啟用的方法。
subsystemctl shell 的一些問題#
subsystemctl shell 看起來很方便,自然會想直接利用 .zshrc 讓它進入 systemd 的環境之中,不過實際上使用會發現它還有很多麻煩的小地方要處裡。
例如當你輸入 exit 之後會發現它只退出了 subsystemctl shell,而沒把整個 shell 給退出之類的小問題。
另一個問題是輸入 cat,然後輸入 abc 之後按兩下 Backspace 之後可能會發現它變成了 abc\cb: arkane-systems/genie#145
一些 WSL 該有的環境變數如 WSL_DISTRO_NAME 不見了,這會讓許多 WSL 的功能產生問題,例如執行 .exe 檔案的功能之類的。這個是最大的問題,也是最難修正的一個問題。
修復 exit#
這個其實很好處理,把單純的 subsystemctl shell 改成 exec subsystemctl shell 即可。前者是 shell 會去 fork 一個新的 process 出來,然後在裡面執行指令,而後者是把當前的 process 直接替換掉。在後者的情況下它 exit 的時候就是把整個 process 給退出了,所以就能解決這個問題。
if ! subsystemctl is-inside; then
exec subsystemctl shell --quiet
fi修復 Backspace#
這個問題的解決方法就直接寫在了 issue 底下,執行 stty -echoprt 或是 stty sane 之後就能正常使用了。
雖然他說是 Ubuntu-specific 的問題,但是我在 Arch Linux 下也有這個問題,幸好解法一樣
修復環境變數#
這個是最大的問題,執行 subsystemctl shell 之後許多的環境變數都會消失不見,包括 PWD PATH 和 WSL_DISTRO_NAME 等等,所以修復好這個是很重要的。
可以在 genie 的 readme 中看到它有支援複製環境變數的功能,讀一下 source code 之後可以知道它是把環境變數等相關資訊先寫入到一個檔案之中,然後再用另外的腳本把它載入回來而已。
這個功能雖然 subsystemctl 不支援,但是其實可以自己實作。我是直接在 .zshrc 中實作,這樣自己就不用去改 Rust 的程式 (因為我也不太會改...)。
整個完整的 implementation 如下,把它放到 .zshrc 的開頭即可:
# Start genie in WSL if exists
if [[ -f ~/.subsystemctl_env ]] then
source ~/.subsystemctl_env
rm ~/.subsystemctl_env
stty -echoprt # fix backspace
fi
if [[ -v WSL_DISTRO_NAME ]] then
if (( $+commands[subsystemctl] )); then
if ! subsystemctl is-running; then
sudo subsystemctl start
fi
if ! subsystemctl is-inside; then
cat > ~/.subsystemctl_env << EOF
export PATH="$PATH"
export WSL_DISTRO_NAME="$WSL_DISTRO_NAME"
export WSL_INTEROP="$WSL_INTEROP"
export WSLENV="$WSLENV"
export DISPLAY="$DISPLAY"
export WAYLAND_DISPLAY="$WAYLAND_DISPLAY"
export PULSE_SERVER="$PULSE_SERVER"
cd "$PWD"
EOF
exec subsystemctl shell --quiet
rm ~/.subsystemctl_env # should never reach here, but it is convenient for testing...
fi
fi
fi這個作法目前唯一的缺點是當你用 ssh 連到 wsl 之中的時候不會有 WSL 相關的環境變數而已,其他一切正常。
WSLg#
如果想要使用 WSLg 而非一般的 X server 的話還需要點其他的調整才行。
要使用 WSLg 的話 DISPLAY 要設定為 :0,不過直接在 subsystemctl shell 的 namespace 中還是無法正常使用。Google 可以搜尋到這個解決方案,簡單來說就是把 /tmp/.X11-unix/X0 link 到 /mnt/wslg/.X11-unix/X0 去即可。
所以就加上這部分的 code 即可:
if [[ -v WSL_DISTRO_NAME ]] then
if [[ -S /mnt/wslg/.X11-unix/X0 ]] then
WSLG_EXIST=1 # prefer wslg if it exists
if [[ ! -S /tmp/.X11-unix/X0 ]] then
# fix wslg not working in subsystemctl namespace
# https://github.com/arkane-systems/genie/issues/175#issuecomment-922526126
ln -s /mnt/wslg/.X11-unix/X0 /tmp/.X11-unix/X0
fi
fi
if (( $+commands[subsystemctl] )); then
# omitted...那個 WSLG_EXIST 是因為我在下面會根據這個決定要不要改動 DISPLAY 到 X server 去:
if [[ "1" != "$WSLG_EXIST" ]] then
export DISPLAY=$(ip route show default | awk '{print $3}'):0
fi[更新] 使用新版 WSL 內建的 systemd#
編輯 /etc/wsl.conf 新增這幾行:
[boot]
systemd=true然後如果前面有使用 subsystemctl 的話請把檢查條件改為:
if [[ "$(ps --no-headers -o comm 1)" != "systemd" ]] && (( $+commands[subsystemctl] )); then然後 wsl --shutdown 後重啟利用 ps --no-headers -o comm 1 檢查看看是不是 systemd,之後可以測試如 systemctl status 的指令確定有沒有正常運作。