Snapd on WSL2 (Insiders only for now)


#1

OMGee

Graphical apps seem to work correctly now when you pair with a Windows X11 server such as the one within MobaXterm. See firefox running in the screenshot.

YooToobe

(https://youtu.be/cWlYe0CE2iU)

Howto

Prerequisites

This requires WSL2 (the new generation of Windows System for Linux) which is only available to insiders on the Fast Track. You need to have installed WSL2 by the methods documented at https://docs.microsoft.com/en-us/windows/wsl/wsl2-install.

Ubuntu prerequisites

Once you have your Ubuntu 18.04 installed from the store and you’ve opened it the first time, you need to install some extra packages that aren’t part of the out-of-the-box filesystem:

  • daemonize
  • dbus-user-session
  • fontconfig

These can be installed by running the following in the bash shell:

sudo apt-get update && sudo apt-get install -yqq daemonize dbus-user-session fontconfig

/etc/profile.d/00-wsl2-systemd.sh

To always enable systemd and by extension snapd when you start bash you need to create a file at /etc/profile.d/00-wsl2-systemd.sh with the following content:

SYSTEMD_PID=$(ps -ef | grep '/lib/systemd/systemd --system-unit=basic.target$' | grep -v unshare | awk '{print $2}')
if [ -z "$SYSTEMD_PID" ] || [ "$SYSTEMD_PID" != "1" ]; then
    exec sudo /usr/sbin/enter-systemd-namespace
fi
if [ -n "$PRE_NAMESPACE_PATH" ]; then
    export PATH="$PRE_NAMESPACE_PATH"
fi

/usr/sbin/enter-systemd-namespace

Next you need to create a new file at /usr/sbin/enter-systemd-namespace, with executable permissions (chmod +x), and containing the following code:

#!/bin/bash

if [ "$UID" != 0 ]; then
    echo "You need to run $0 through sudo"
    exit 1
fi

SYSTEMD_PID=$(ps -ef | grep '/lib/systemd/systemd --system-unit=basic.target$' | grep -v unshare | awk '{print $2}')
if [ -z "$SYSTEMD_PID" ]; then
    /usr/sbin/daemonize /usr/bin/unshare --fork --pid --mount-proc /lib/systemd/systemd --system-unit=basic.target
    while [ -z "$SYSTEMD_PID" ]; do
        SYSTEMD_PID=$(ps -ef | grep '/lib/systemd/systemd --system-unit=basic.target$' | grep -v unshare | awk '{print $2}')
    done
fi

if [ -n "$SYSTEMD_PID" ] && [ "$SYSTEMD_PID" != "1" ]; then
    exec /usr/bin/nsenter -t "$SYSTEMD_PID" -a env \
        DISPLAY="$DISPLAY" \
        PRE_NAMESPACE_PATH="$PRE_NAMESPACE_PATH" \
        WSL_INTEROP="$WSL_INTEROP" \
        WSL_DISTRO_NAME="$WSL_DISTRO_NAME" \
        WSLENV="$WSLENV" \
        WSLPATH="$WSLPATH" \
        /bin/login -p -f "$LOGNAME"
fi

Sudoers

Finally, using sudo visudo, add the following sudoers rule to the bottom of the sudoers file:

Defaults        env_keep += WSLPATH
Defaults        env_keep += WSLENV
Defaults        env_keep += WSL_INTEROP
Defaults        env_keep += WSL_DISTRO_NAME
Defaults        env_keep += LOGNAME
Defaults        env_keep += PRE_NAMESPACE_PATH
%sudo ALL=(ALL) NOPASSWD: /usr/sbin/enter-systemd-namespace

Done

Now every time you start the terminal/bash it will automatically put you inside a pid namespace with snapd running.

Diagnostics of the environment by snapd

Below are some simple diagnostic listings that show the environment according to snapd.

$ snap version
snap    2.40+18.04
snapd   2.40+18.04
series  16
ubuntu  18.04
kernel  4.19.59-microsoft-standard

** We only have partial confinement :frowning: **

$ snap debug confinement
partial

** We’re missing apparmor **

$ snap debug sandbox-features
confinement-options:  classic devmode
dbus:                 mediated-bus-access
kmod:                 mediated-modprobe
mount:                freezer-cgroup-v1 layouts mount-namespace per-snap-persistency per-snap-profiles per-snap-updates per-snap-user-profiles stale-base-invalidation
seccomp:              bpf-actlog bpf-argument-filtering kernel:allow kernel:errno kernel:kill_process kernel:kill_thread kernel:log kernel:trace kernel:trap
udev:                 device-cgroup-v1 tagging

Edits (newest edit is last)

Edited to improve profile script.

Edited to update scripts - the old scripts are below for reference

00-wsl2-systemd.sh

SYSTEMD_PID=$(ps -ef | grep '/lib/systemd/systemd --system-unit=basic.target$' | grep -v unshare | awk '{print $2}')

if [ -z "$SYSTEMD_PID" ]; then
   sudo /usr/sbin/daemonize /usr/bin/unshare --fork --pid --mount-proc /lib/systemd/systemd --system-unit=basic.target
   SYSTEMD_PID=$(ps -ef | grep '/lib/systemd/systemd --system-unit=basic.target$' | grep -v unshare | awk '{print $2}')
fi

if [ -n "$SYSTEMD_PID" ] && [ "$SYSTEMD_PID" != "1" ]; then
    exec sudo /usr/bin/nsenter -t $SYSTEMD_PID -a su - $LOGNAME
fi

sudoers

%sudo ALL=(ALL) NOPASSWD: /usr/sbin/daemonize /usr/bin/unshare --fork --pid --mount-proc /lib/systemd/systemd --system-unit=basic.target
%sudo ALL=(ALL) NOPASSWD: /usr/bin/nsenter -t [0-9]* -a su - [a-zA-Z0-9]*

Update dependencies and profile script

  • Add APT dependencies for Ubuntu
  • Replace su with login to initialise a user.slice and user@.service within the session
    • This starts a systemd user instance with dbus supported (dbus is provided by dbus-user-session in the Ubuntu Dependencies section)

Update enter-systemd-namespace script

  • Add while loop to wait for systemd to actually start

Update scripts for WSL Interoperability (can start Windows apps from the PID namespace)

  • Pass $WSL_INTEROP to enter-systemd-namespace in 00-wsl2-systemd.sh profile script
  • Use $WSL_INTEROP in enter-systemd-namespace when entering the PID namespace

Update sudo config and scripts for env-passthru

  • Use sudoers configuration for environment passthrough
  • Remove brittle command-line environment variable pass-through

#2

I’m unsure if it’s currently a bug in WSL2, or if the PID Namespace that I create for systemd is conflicting with the ability to execute Windows .exes from within the WSL2 environment. I’m getting segfaults when I attempt to do so, but it isn’t clear where the segfault is occuring and strace is not helping :slight_smile: I think it’s crashing within Microsoft’s init binary which is hooked-up as the handler for .exe files via the binfmt_misc capability.


#3

I managed to get GUI apps running. Check the latest version of the scripts in the original post, along with some new pre-requisites that need adding via APT.

If you’ve already set your box up, replace the scripts with the ones above and run the APT commands to install the bare-minimum packages for gui apps to operate.


#4

The problem is that anything that drops the environment (and hence the WSL_INTEROP environment variable that points at the socket the inits use to communicate with each other) breaks interop - which includes sudo and various other utilities.

There’s a fix inbound, but for the moment, using sudo -E et. al. to preserve the environment, or simply copying WSL_INTEROP into the new namespace/sudo session/etc. manually can serve as a workaround. See here: https://github.com/microsoft/WSL/issues/4465


#5

Thanks for the pointer. I’ve updated the scripts to pass the $WSL_INTEROP variable into the PID namespace now.


#6

Updated the scripts again to use sudo to pass through the environment variables from the initial shell into the root shell used to spawn systemd.


#7

Wow, I am so down with this

What are your CPU/RAM utilization numbers in Windows before & after launching a GUI snap via x11 server?

Edit: Sorry I thought of another question, too - which version of Windows are you using?

Would really like to attempt this. Currently on 18363.356 (slow) insider build. Haven’t been able to get snapd to launch, does it require Linux (non-microsoft) kernel in WSL?


#8

Don’t think y’all’ve got WSL 2 in slow ring yet, have you (WSL 2 is in build 18917 or higher, latest is 18980)?

Last I checked, it was still 19H2 builds, with the 20H1 changeover not due until later this month/next month.


#9

Also, when I got snapd to work under WSL 2, I had apparmor in the kernel, but I’m still only getting partial confinement. Anyone out there who can suggest what might need tweaking to get to strict?

Relevant output:

❯ snap debug confinement
partial

❯ snap debug sandbox-features
apparmor:             kernel:caps kernel:domain kernel:file kernel:mount kernel:namespaces kernel:network_v8 kernel:policy kernel:ptrace kernel:query kernel:rlimit kernel:signal parser:unsafe policy:downgraded support-level:partial
confinement-options:  classic devmode
dbus:                 mediated-bus-access
kmod:                 mediated-modprobe
mount:                freezer-cgroup-v1 layouts mount-namespace per-snap-persistency per-snap-profiles per-snap-updates per-snap-user-profiles stale-base-invalidation
seccomp:              bpf-actlog bpf-argument-filtering kernel:allow kernel:errno kernel:kill_process kernel:kill_thread kernel:log kernel:trace kernel:trap
udev:                 device-cgroup-v1 tagging

#10

Hi, yes I have never tried Insiders before so I just went with what it recommended at first

Did some research and realized I needed 18862+ (I believe) so I set it to “fast” and now I’m at 18990 and have WSL2

Still working on getting snapd to work, have to set up the systemd container to spoof the init requirement, just haven’t gotten around to it yet

Thanks for your help :smiley:


#11

I’ve developed a tool to do all the systemd container-ing for you in one go, if that helps any:


#12

rad yes thank you! :smiley: