在 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
中寫入:
1 | if [[ -v WSL_DISTRO_NAME ]] then |
這樣它就會自動幫你看情況啟用,所以基本上只需要輸入一次 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
給退出了,所以就能解決這個問題。
1 | if ! subsystemctl is-inside; then |
修復 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
的開頭即可:
1 | # Start genie in WSL if exists |
這個作法目前唯一的缺點是當你用 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 即可:
1 | if [[ -v WSL_DISTRO_NAME ]] then |
那個 WSLG_EXIST
是因為我在下面會根據這個決定要不要改動
DISPLAY
到 X server 去:
1 | if [[ "1" != "$WSLG_EXIST" ]] then |
[更新] 使用新版 WSL 內建的 systemd
編輯 /etc/wsl.conf
新增這幾行:
1 | [boot] |
然後如果前面有使用 subsystemctl
的話請把檢查條件改為:
1 | if [[ "$(ps --no-headers -o comm 1)" != "systemd" ]] && (( $+commands[subsystemctl] )); then |
然後 wsl --shutdown
後重啟利用
ps --no-headers -o comm 1
檢查看看是不是
systemd
,之後可以測試如 systemctl status
的指令確定有沒有正常運作。