Perfect Method to Use systemd in WSL2
This article is automatically translated by LLM, so the translation may be inaccurate or incomplete. If you find any mistake, please let me know.
You can find the original article here .
The major difference between WSL2 and general Linux is that it uses Microsoft's own init, rather than the commonly seen systemd in typical Linux distros. This can cause some issues with package managers that rely on systemd, such as Arch Linux.
This article briefly documents the method to perfectly set up a systemd environment in ArchWSL within WSL2. Other distros should also be able to use this method.
Reference dotfiles: maple3142/dotfiles
Update on 2022/11/17: Since WSL officially launched version 1.0.0 on the Microsoft Store today, I can finally install the new version of WSL on Windows 10 and successfully use the built-in systemd. You can refer to the last paragraph for this part.
systemd bottle
To run systemd in WSL2, we use the namespace feature, requiring tools written by others to wrap systemd in that environment, allowing it to function as PID 1. You can choose one of the following two tools to install:
- arkane-systems/genie: Written in C#, more feature-complete
- sorah/subsystemctl: Written in Rust, very fast
I've used both, and their usage is quite similar. Due to performance considerations, I ultimately used subsystemctl. The rest of this article will introduce subsystemctl.
Assuming the shell used is zsh
Basic Operations
After installation, you need to use sudo subsystemctl start
to enable systemd. You need to do this every time you restart WSL. You can automate this by adding the following to your .zshrc
:
if [[ -v WSL_DISTRO_NAME ]] then
if (( $+commands[subsystemctl] )); then
if ! subsystemctl is-running; then
sudo subsystemctl start
fi
fi
fi
This way, it will automatically enable systemd based on the situation, so you only need to enter the sudo password once.
You can then use subsystemctl shell
to enter an environment where systemd can run, or use subsystemctl exec
to directly execute commands, such as subsystemctl exec -- systemctl status docker
.
It's best to add
--
when usingsubsystemctl exec
, otherwise the flags of the subsequent commands will be interpreted as flags for subsystemctl
For example, to update Arch Linux, remember to use subsystemctl exec -- pacman -Syu
, so that packages requiring systemd can function properly.
This setup allows basic use of systemd. If you're satisfied with this, you can stop reading here. The rest of the article explains how to automate the subsystemctl shell
process.
Issues with subsystemctl shell
subsystemctl shell
seems convenient, so you might want to use .zshrc
to enter the systemd environment directly. However, there are many small issues to handle.
For example, after typing exit
, you'll find it only exits subsystemctl shell
without exiting the entire shell.
Another issue is typing cat
, then abc
, and pressing Backspace twice might result in abc\cb
: arkane-systems/genie#145
Some WSL environment variables like WSL_DISTRO_NAME
disappear, causing many WSL features to malfunction, such as executing .exe
files. This is the biggest and most difficult issue to fix.
Fixing exit
This is easy to handle. Change subsystemctl shell
to exec subsystemctl shell
. The former forks a new process to execute the command, while the latter replaces the current process. In the latter case, exiting will terminate the entire process, solving the issue.
if ! subsystemctl is-inside; then
exec subsystemctl shell --quiet
fi
Fixing Backspace
The solution to this problem is directly mentioned in the issue. Executing stty -echoprt
or stty sane
will fix it.
Although it's said to be an Ubuntu-specific issue, I encountered it in Arch Linux as well. Fortunately, the solution is the same.
Fixing Environment Variables
This is the biggest issue. After executing subsystemctl shell
, many environment variables disappear, including PWD
, PATH
, and WSL_DISTRO_NAME
. Fixing this is crucial.
Genie's readme mentions it supports copying environment variables. Reading the source code reveals it writes environment variables and related information to a file, then loads them back with another script.
Although subsystemctl
doesn't support this feature, you can implement it yourself. I implemented it directly in .zshrc
, avoiding the need to modify Rust code (which I'm not good at).
The complete implementation is as follows. Place it at the beginning of .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
The only drawback of this approach is that WSL-related environment variables won't be present when connecting to WSL via ssh. Everything else works fine.
WSLg
If you want to use WSLg instead of a general X server, some additional adjustments are needed.
To use WSLg, DISPLAY
should be set to :0
. However, it won't work directly in the subsystemctl shell
namespace. You can find a solution on Google, which involves linking /tmp/.X11-unix/X0
to /mnt/wslg/.X11-unix/X0
.
Add the following 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...
The WSLG_EXIST
variable is used to decide whether to change DISPLAY
to the X server:
if [[ "1" != "$WSLG_EXIST" ]] then
export DISPLAY=$(ip route show default | awk '{print $3}'):0
fi
[Update] Using the New WSL Built-in systemd
Edit /etc/wsl.conf
and add the following lines:
[boot]
systemd=true
If you previously used subsystemctl
, change the check condition to:
if [[ "$(ps --no-headers -o comm 1)" != "systemd" ]] && (( $+commands[subsystemctl] )); then
Then, after wsl --shutdown
and restarting, use ps --no-headers -o comm 1
to check if it's systemd
. You can then test commands like systemctl status
to ensure they work properly.