WSL2 init: emerging OpenRC

Introduction

Since WSL2 has been released, the community has tried (quite successfully) to add SystemD to their distros.

Currently, the most commons solutions are:

Note: Other solutions exist, but these two are the most known in the WSL community.

Both solutions allow you to run SystemD as PID1. These solutions evolved along the years and are today very well integrated with WSL2 and, more recently, with WSL2g.

However, in the Linux community, there’s quite a debate on SystemD itself, and not all the Linux distros use it.

So, “what if” you could run another init system instead of SystemD?

Sources

As I often stated: all ideas have an inspiration or are an alternative to something already done/existing.

For this blog post, my main inspirations were:

Also, a very special mention to the Gentoo wiki which might be one of the best sources of knowledge for Linux.

Other references will be properly linked in the blog post.

So to everyone who already took the time to create content: THANK YOU!

Prerequisites

The following list might be quite long, so I won’t explain how to install it. However I will put the links that I used my own setup.

Here is my setup:

OpenRC: the challenger

OpenRC is the “other” very known init system, used by distros like Alpine, Gentoo and Devuan (just to name the few more known).

While quite known, OpenRC has never been implemented in a WSL2 distro (to my own knowledge), until Rancher Desktop v0.7.0.

The Rancher Desktop OS implemented OpenRC in its simplest form, without re-entering PID1 and it has a set of scripts that takes care of registering and launching the services.

This is where you will “fork” it, and build an OpenRC init system with PID1, by copying what as been done with SystemD on WSL2.

Getting ready

As stated, you’ll use Gentoo as the distro for this example. And, as you might already know, Gentoo is not on the store, therefore you have to create a custom distro.

As there’s already a lot of blogs on how to create a custom distro (i.e. WSL2+RHEL8: The Whale with the Red Hat), here is the “fast track” (read: commands only):

# [Optional] Create a `bin` directory on Windows home and add it to your PATH variable
mkdir $env:USERPROFILE\bin
$userenv = [System.Environment]::GetEnvironmentVariable("Path", "User")
[System.Environment]::SetEnvironmentVariable("PATH", $userenv + "; " + $env:USERPROFILE + "\bin", "User")
## Restart your terminal or add temporarily the directory to your PATH variable
$env:PATH="$env:USERPROFILE\bin;$env:PATH"

# Assuming you don't have any container runtime (Docker Desktop, Podman, Rancher Desktop), download Google Crane
invoke-webrequest -URI https://github.com/google/go-containerregistry/releases/download/v0.8.0/go-containerregistry_Windows_x86_64.tar.gz -Outfile $env:USERPROFILE\bin\crane.tar.gz

# Check the file content
tar -tf $env:USERPROFILE\bin\crane.tar.gz

# Extract only the "crane.exe" file to the "bin" directory
tar -C $env:USERPROFILE\bin -xzf $env:USERPROFILE\bin\crane.tar.gz crane.exe

## [Optional] Delete the archive file
rm $env:USERPROFILE\bin\crane.tar.gz

# [Optional] Create a "wslsources" and "wsldistros" directories
mkdir C:\wsldistros,C:\wslsources

# Download Gentoo rootfs on Docker Hub
crane.exe export gentoo/stage3 C:\wslsources\gentoo.tar

# Create the Gentoo WSL2 custom distro
wsl.exe --import gentoo C:\wsldistros\gentoo C:\wslsources\gentoo.tar --version 2

# Launch the Gentoo WSL2 custom distro
wsl.exe -d gentoo

Custom distro install

Note: Google Crane allows us to save a container image into a rootfs, instead of saving the layers (i.e. docker save <container image>)

We have now a new WSL2 custom distro, so it’s time to configure OpenRC.

Lightning fast configuration

Before you configure anything, as the OS is brand new, you need to ensure it’s up-to-date. If you also picked Gentoo, then the package manager is Portage:

emerge --sync

Gentoo update

With the OS updated, you can install applications that you’ll need later on:

emerge vim sudo

Initial configuration

The first configuration file that you need to create, is wsl.conf. This file has several settings that will be applied to the current distro only. One of these settings is the [boot] setting, which runs a command when the distro is started.

For OpenRC, the init process is /sbin/init:

# Create/Edit the wsl.conf file with your favorite editor
vi /etc/wsl.conf

# Add/Edit the [boot] command and save the file
[boot]
command = "/usr/bin/env -i /usr/bin/unshare --pid --mount-proc --fork --propagation private -- sh -c 'exec /sbin/init'"

[Optional] We can already see what this change to by terminating the distro session and starting a new one

# Terminate the WSL2 distro session from Powershell
wsl.exe --terminate gentoo

# Start a new WSL2 distro session
wsl.exe -d gentoo

Once the new session started, you can check the running processes:

# Check the running processes
ps -ef

# Check the OpenRC status
rc-status

# Check the current OpenRC services
rc-update

OpenRC initial configuration

PID1 is the target

While OpenRC is now running and you can list the services, the WSL2 init process is still the one running as PID1. Thanks to all the work done on WSL2 SystemD, you can leverage the knowledge and apply it to OpenRC:

# Create a script for entering PID 1 and save it in /etc/profile.d/
vi /etc/profile.d/wsl-init.sh

## Content of the file /etc/profile.d/wsl-init.sh
#!/bin/bash

# Get PID of /sbin/init
sleep 1
pid="$(ps -u root -o pid,args | awk -e '$2 ~ /^init/ { print $1 }')"

# Run WSL service script
if [ "$pid" -ne 1 ]; then
  # Export ENV variables
  if [ "$USER" != "root" ]; then
    [ -f "$HOME/.openrc.env" ] && rm "$HOME/.openrc.env"
    export > "$HOME/.openrc.env"
  fi

  echo "Entering /sbin/init PID: $pid"
  exec sudo /usr/bin/nsenter -p -m -t "${pid}" -- su - "$USER"
fi

# Import ENV variables
if [ -f "$HOME/.openrc.env" ]; then
  set -a
  source "$HOME/.openrc.env"
  set +a
  rm "$HOME/.openrc.env"
fi

Note: the scripts located in /etc/profile.d/ do not need to be made executable (i.e. chmod +x ...)

You can now terminate the WSL2 distro session and start a new one where you will have OpenRC as PID1:

# Terminate the WSL2 distro session from Powershell
wsl.exe --terminate gentoo

# Start a new WSL2 distro session
wsl.exe -d gentoo

Once the new session started, you can check the running processes:

# Check the running processes
ps -ef

OpenRC with PID1

Conclusion

While SystemD is now as small as a one script, or has very advanced and useful installers, OpenRC implementation seems very simple in comparison.

But, as stated in the introduction, this due to the fact that the work of community has come this far and implementing it now, makes maybe even more sense as we have a better grasp on how WSL2 works.

I hope this will help you and as usual, if you do anything (cool or not) with it, tag me on Twitter (@nunixtech) as I’m always willing to see what are your own crazy ideas.

>>> Nunix out <<<

Bonus 1: Docker on WSL2, the service way

I could not resist and add a Bonus section to show what OpenRC on WSL2 could do for us.

One of the main advantages, is that it will allow us to run applications such as Docker, and allow them to start when the session start.

Here is the installation, still on Gentoo:

# Search for Docker in the Gentoo repo
emerge -s Gentoo

# Install the Docker package
emerge app-containers/docker

Install Docker

As stated at the end of the install log, you can find the command to run for starting Docker “at boot”:

# Add Docker to the "default" boot profile
rc-update add docker default

# Check if the service has been correctly registered
rc-update show default

# Check if Docker is correctly running
docker version

Install Docker service

Finally, terminate the WSL2 distro session:

# Terminate the WSL2 distro session from Powershell
wsl.exe --terminate gentoo

# Start a new WSL2 distro session
wsl.exe -d gentoo

And start a new WSL2 distro session, Docker should be running:

# Check if Docker is running
docker version

# [Optional] Create your first Docker container
docker run --rm hello-world

Create a Docker container

Bonus 2: OpenRC loves Ketchup (K3sup)

Ok, I must admit I’m really excited by all the opportunities we have with OpenRC (same goes for SystemD). So while we are in the containers/Cloud Native world, let’s use two of the best tools I came across for deploying, very easily, a K3s node cluster (K3sup) and the needed tooling (Arkade)

Let’s install Arkade first and get all the tools needed with it:

# Install Arkade with the official script
curl -sLS https://get.arkade.dev | sh

# Get both K3sup and Kubectl
ark get k3sup kubectl

Install Arkade, K3sup and Kubectl

Now, create a one node K3s cluster:

# [Optional] Update your path with the Arkade bin directory
export PATH=$PATH:$HOME/.arkade/bin/

# Create the K3s cluster with K3sup
k3sup install --local

# Test the cluster with the commands displayed at the end of the install log
export KUBECONFIG=/root/kubeconfig
kubectl config set-context default
kubectl get node -o wide

# Check if the service is correctly registered
rc-status default

Install K3s cluster