Skip to content

Commit 69cf6e3

Browse files
Kabuki94claude
andcommitted
fix(wsl): rshared root + /dev/{net/tun,fuse} pre-sysinit; dedupe sysusers/tmpfiles
Root cause: WSL2's /init mounts / private, so systemd's per-unit mount(NULL, "/", NULL, MS_REC|MS_SLAVE, NULL) returns EOPNOTSUPP and NAMESPACE setup fails for any unit using PrivateTmp/ProtectSystem/etc. That cascade is what we've been seeing as cockpit.service status=226/ NAMESPACE AND every Quadlet (mios-forge, mios-ai, mios-aichat-build) failing rootless network/storage setup -- slirp4netns can't open /dev/net/tun and fuse-overlayfs can't mount /dev/fuse, both of which are missing because systemd-udevd is condition-gated off on WSL2 (ConditionPathIsReadWrite=/sys is false there). mios-wsl-early.service is the new pre-sysinit fixup unit. Runs once per boot, no sandboxing (it's what makes sandboxing possible for everything else), gated on ConditionVirtualization=wsl. Two ops: 1. mount --make-rshared / 2. mknod /dev/net/tun (c 10:200) and /dev/fuse (c 10:229) if missing Ordering: After=systemd-tmpfiles-setup-dev.service systemd-remount-fs .service / Before=sysinit.target basic.target shutdown.target. That puts it before any unit with hardening directives or rootless podman plumbing, so cockpit-certificate-ensure's NAMESPACE step succeeds and fuse-overlayfs/slirp4netns can find their device nodes. Defense-in-depth on cockpit.service.d/10-mios-container.conf: also reset BindPaths/BindReadOnlyPaths/TemporaryFileSystem/Extension*/ RootDirectory/RootImage/MountAPIVFS/DynamicUser/RestrictFileSystems. With rshared root these are unnecessary, but a future cockpit-ws unit file bump that adds any of them would otherwise re-trigger NAMESPACE setup. Cheap insurance. Boot warnings cleanup (same journal): * usr/lib/sysusers.d/10-mios.conf -- drop `g input - -` (declared in 00-coreos-static.conf) and `g dialout - -` (declared in setup.conf) so the per-boot "Conflict ... ignoring line" lines disappear. * usr/lib/tmpfiles.d/mios.conf -- drop redundant `d /var/home/mios` line (mios-user.conf already owns it via `C` + `Z`). * usr/lib/tmpfiles.d/mios-grd.conf -- drop `d /var/lib/gnome-remote- desktop` (declared in upstream gnome-remote-desktop tmpfiles). * usr/lib/tmpfiles.d/mios-infra.conf -- drop `d /etc/pam.d` (declared in upstream pam tmpfiles). 90-mios.preset: enable mios-wsl-early.service (must come before mios- wsl-firstboot/init/runtime-dir; comment block in the preset spells out why it's first). Existing .gitignore whitelists already cover both new files (usr/libexec/mios/** and mios-*.service). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 1f74f44 commit 69cf6e3

8 files changed

Lines changed: 116 additions & 5 deletions

File tree

usr/lib/systemd/system-preset/90-mios.preset

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ enable cloud-final.service
6868
enable qemu-guest-agent.service
6969

7070
# --- WSL2 fallback & initialization (ConditionVirtualization=wsl gated) ---
71+
# mios-wsl-early MUST stay first: it makes / rshared and creates
72+
# /dev/{net/tun,fuse} before any unit with sandboxing or rootless
73+
# podman fires. Without it, cockpit fails 226/NAMESPACE and every
74+
# Quadlet fails fuse-overlayfs/slirp4netns at boot.
75+
enable mios-wsl-early.service
7176
enable mios-wsl-firstboot.service
7277
enable mios-wsl-init.service
7378
enable mios-wsl-runtime-dir.service

usr/lib/systemd/system/cockpit.service.d/10-mios-container.conf

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,16 @@ DeviceAllow=
7575
ReadWritePaths=
7676
ReadOnlyPaths=
7777
InaccessiblePaths=
78+
# These also trigger exec_needs_mount_namespace() in src/core/exec-invoke.c.
79+
# Resetting them is cheap insurance against a future cockpit-ws unit-file
80+
# bump that adds any of them upstream:
81+
BindPaths=
82+
BindReadOnlyPaths=
83+
TemporaryFileSystem=
84+
ExtensionDirectories=
85+
ExtensionImages=
86+
RootDirectory=
87+
RootImage=
88+
MountAPIVFS=no
89+
DynamicUser=no
90+
RestrictFileSystems=
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[Unit]
2+
Description='MiOS' WSL2 pre-sysinit fixups (rshared root + /dev/{net/tun,fuse})
3+
Documentation=https://github.com/MiOS-DEV/MiOS
4+
ConditionVirtualization=wsl
5+
DefaultDependencies=no
6+
After=systemd-tmpfiles-setup-dev.service systemd-remount-fs.service
7+
Before=sysinit.target basic.target shutdown.target
8+
Conflicts=shutdown.target
9+
10+
[Service]
11+
Type=oneshot
12+
RemainAfterExit=yes
13+
ExecStart=/usr/libexec/mios/wsl-early
14+
# Intentionally no sandboxing: this unit is what makes sandboxing possible
15+
# for everything else. Setting any of {PrivateTmp, ProtectSystem,
16+
# ReadWritePaths, ...} here would deadlock on the very issue we're fixing.
17+
18+
[Install]
19+
WantedBy=sysinit.target

usr/lib/sysusers.d/10-mios.conf

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ g kvm 36 -
1313
g video 39 -
1414
g render 105 -
1515
g libvirt - -
16-
g input - -
17-
g dialout - -
16+
# 'input' is declared in 00-coreos-static.conf and 'dialout' in setup.conf;
17+
# omitting them here avoids per-boot "Conflict ... ignoring line" warnings.
18+
# `m mios input` / `m mios dialout` below still resolve via NSS.
1819
g docker - -
1920
g mios 1000
2021

usr/lib/tmpfiles.d/mios-grd.conf

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
d /var/lib/gnome-remote-desktop 0770 gnome-remote-desktop gnome-remote-desktop -
1+
# /var/lib/gnome-remote-desktop is created by upstream's gnome-remote-desktop
2+
# tmpfiles.d (also at 0770 grd:grd), so we don't re-declare it here.
23
d /var/lib/mios 0755 root root -

usr/lib/tmpfiles.d/mios-infra.conf

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ d /etc/cdi 0755 root root -
6262
d /etc/greenboot 0755 root root -
6363

6464
# -- PAM & Auth ---------------------------------------------------------------
65-
d /etc/pam.d 0755 root root -
65+
# /etc/pam.d is shipped by the pam RPM and declared in upstream tmpfiles;
66+
# re-declaring it here triggers a duplicate-line warning at boot.
6667
d /etc/sudoers.d 0750 root root -
6768

6869
# -- Cloud-Init & Multipath ---------------------------------------------------

usr/lib/tmpfiles.d/mios.conf

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ d /srv/ai/outputs 0755 mios-ai mios-ai - -
2424
d /srv/ai/collections 0755 mios-ai mios-ai - -
2525

2626
# --- bootc linting compliance ---
27-
d /var/home/mios 0755 mios mios - -
27+
# /var/home/mios is owned by mios-user.conf (C+Z lines that copy /etc/skel
28+
# and chown). Keeping it here too caused a duplicate-line warning at boot.
2829
d /var/lib/AccountsService 0775 root root - -
2930
d /var/lib/AccountsService/icons 0775 root root - -
3031
d /var/lib/AccountsService/users 0700 root root - -

usr/libexec/mios/wsl-early

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/usr/bin/env bash
2+
# MiOS -- wsl-early: WSL2 pre-sysinit fixups.
3+
#
4+
# Runs as PID 1's child with full root caps BEFORE basic.target and BEFORE
5+
# any service that creates a mount namespace. Fixes two WSL2-specific gaps
6+
# that cause cascading failures across cockpit and rootless podman:
7+
#
8+
# 1. Root mount propagation -- WSL2's /init mounts / private, which makes
9+
# systemd's per-unit `mount(NULL, "/", NULL, MS_REC|MS_SLAVE, NULL)`
10+
# return EOPNOTSUPP. That's the actual cause of `cockpit.service:
11+
# Failed to set up mount namespacing: Operation not supported` and
12+
# every "status=226/NAMESPACE" failure on WSL. Marking root rshared
13+
# lets every subsequent unit's PrivateTmp/ProtectSystem/etc. clone
14+
# a private mount namespace cleanly.
15+
#
16+
# 2. /dev/net/tun and /dev/fuse -- the WSL2 kernel builds tun + fuse
17+
# in-tree, but systemd-udevd is condition-gated off (ConditionPathIs
18+
# ReadWrite=/sys fails because /sys is RO under WSL2's /init), so
19+
# nothing auto-creates the char device nodes. Without /dev/net/tun
20+
# slirp4netns can't open its tap fd; without /dev/fuse fuse-overlayfs
21+
# reports "cannot mount: No such file or directory" and rootless
22+
# podman storage init fails entirely. We mknod them by hand here.
23+
#
24+
# Why this script and not tmpfiles.d:
25+
# * `c!` lines in tmpfiles.d would work in theory, but tmpfiles is run
26+
# after some early namespace-using units have already failed, and
27+
# because tmpfiles itself uses sandboxing on some unit paths, mknod
28+
# can return EPERM under the same propagation lock that bites cockpit.
29+
# Doing it from a no-sandbox oneshot before basic.target avoids both.
30+
set -euo pipefail
31+
32+
_log() { logger -t mios-wsl-early "$*" 2>/dev/null || true; echo "[wsl-early] $*" >&2; }
33+
34+
# Hard gate: only on WSL.
35+
if [[ ! -f /proc/sys/fs/binfmt_misc/WSLInterop && ! -d /run/WSL ]]; then
36+
case "$(systemd-detect-virt 2>/dev/null || echo unknown)" in
37+
wsl) ;;
38+
*) _log "not running under WSL; nothing to do"; exit 0 ;;
39+
esac
40+
fi
41+
42+
# 1. Make / rshared so per-unit mount namespaces can MS_SLAVE-propagate.
43+
# Idempotent: a second --make-rshared on an already-shared mount is a no-op.
44+
if mount --make-rshared / 2>/dev/null; then
45+
_log "mount --make-rshared / OK"
46+
else
47+
_log "WARN: mount --make-rshared / failed (continuing -- some namespace setups may still fail)"
48+
fi
49+
50+
# 2. Ensure /dev/net/tun (c 10:200) and /dev/fuse (c 10:229) exist.
51+
# Both are kernel-builtin on the microsoft-standard-WSL2 kernel; the
52+
# nodes only need to be present in /dev for slirp4netns and
53+
# fuse-overlayfs to open them.
54+
mkdir -p /dev/net 2>/dev/null || true
55+
if [[ ! -e /dev/net/tun ]]; then
56+
if mknod /dev/net/tun c 10 200 2>/dev/null && chmod 0666 /dev/net/tun 2>/dev/null; then
57+
_log "mknod /dev/net/tun (c 10:200) OK"
58+
else
59+
_log "WARN: /dev/net/tun mknod failed (slirp4netns may not work)"
60+
fi
61+
fi
62+
if [[ ! -e /dev/fuse ]]; then
63+
if mknod /dev/fuse c 10 229 2>/dev/null && chmod 0666 /dev/fuse 2>/dev/null; then
64+
_log "mknod /dev/fuse (c 10:229) OK"
65+
else
66+
_log "WARN: /dev/fuse mknod failed (fuse-overlayfs may not work)"
67+
fi
68+
fi
69+
70+
_log "WSL2 early fixups complete"

0 commit comments

Comments
 (0)