[BREAKING] Provide runtime environment systemd services from munix

These services evolve as munix evolves, so they should not be part of
the system closures themselves. Mount them into /run/systemd instead.

(Yes, making /run/systemd/system a symlink to RO files is unfortunate,
 that could be changed in the future. FS prep code is annoying too..)
This commit is contained in:
Val Packett 2026-03-06 04:53:30 -03:00
parent 604ebc1356
commit 38a96b79b3
18 changed files with 125 additions and 112 deletions

View file

@ -10,6 +10,7 @@
libkrun,
muvm,
sidebus-broker,
wl-cross-domain-proxy,
pkgs,
}:
@ -32,6 +33,7 @@ mkShell {
passt
bubblewrap
sidebus-broker
wl-cross-domain-proxy
]
++ (with pkgs; [
meson

View file

@ -121,6 +121,7 @@
munix = pkgs.callPackage ./packages/munix {
mesa = self'.packages.mesa;
muvm = self'.packages.muvm;
wl-cross-domain-proxy = self'.packages.wl-cross-domain-proxy;
sidebus-broker = sidebus.packages.${system}.sidebus-broker;
};
@ -130,6 +131,7 @@
devShells.default = pkgs.callPackage ./devShells {
libkrun = self'.packages.libkrun;
muvm = self'.packages.muvm;
wl-cross-domain-proxy = self'.packages.wl-cross-domain-proxy;
sidebus-broker = sidebus.packages.${system}.sidebus-broker;
};
};

View file

@ -90,6 +90,8 @@ fn main() -> Result<(), std::io::Error> {
std::fs::write("/run/localtime", &localtime)?;
std::fs::write("/run/resolv.conf", &resolv_conf)?;
std::fs::write("/run/machine-id", &gen_machine_id())?;
std::fs::create_dir("/run/systemd")?;
std::os::unix::fs::symlink("/opt/systemd", "/run/systemd/system")?;
std::os::unix::fs::symlink(&closure, "/run/current-system")?;
if let Ok(tmp_graphics) =
std::fs::read(format!("{closure}/etc/tmpfiles.d/graphics-driver.conf"))

10
munix
View file

@ -2,8 +2,10 @@
SCRIPT_PATH=$(dirname $(realpath -s $0))
MUVM_PATH=$(dirname $(which muvm))
PASST_PATH=$(dirname $(which passt))
WL_PROXY_PATH=$(dirname $(which wl-cross-domain-proxy))
HOST_OPENGL_DRIVER=/run/opengl-driver
: "${MICROVM_DEFAULT_COMMAND:=bash}"
: "${MUNIX_SYSTEMD_UNITS:="${SCRIPT_PATH}/systemd"}"
MICROVM_CLOSURE=
MICROVM_COMMAND=()
MICROVM_UID=1337
@ -41,6 +43,7 @@ while [ "$#" -gt 0 ]; do
--munix-bin-dir) SCRIPT_PATH="$2"; shift 2;;
--muvm-bin-dir) MUVM_PATH="$2"; shift 2;;
--passt-bin-dir) PASST_PATH="$2"; shift 2;;
--wl-proxy-bin-dir) WL_PROXY_PATH="$2"; shift 2;;
--) shift 1; MICROVM_COMMAND+=("$@"); break;;
-*) echo "munix: unknown option: $1" >&2; exit 1;;
*)
@ -68,6 +71,11 @@ if [ "$PASST_PATH" = "" ]; then
exit 1
fi
if [ "$WL_PROXY_PATH" = "" ]; then
echo "munix: wl-cross-domain-proxy not found, provide a --wl-proxy-bin-dir or fix \$PATH" >&2
exit 1
fi
if [ ! -e "$HOST_OPENGL_DRIVER" ]; then
echo "munix: host graphics driver not found, provide a --host-opengl-driver" >&2
exit 1
@ -227,10 +235,12 @@ bwrap --unshare-all --share-net \
--ro-bind "$MUVM_PATH" /run/munix/muvm \
--ro-bind "$PASST_PATH" /run/munix/passt \
--ro-bind "$SCRIPT_PATH/micro-activate" /opt/bin/micro-activate \
--ro-bind "$WL_PROXY_PATH/wl-cross-domain-proxy" /opt/bin/wl-cross-domain-proxy \
--ro-bind "$MUVM_PATH/muvm-guest" /opt/bin/muvm-remote \
--ro-bind "$MUVM_PATH/muvm-guest" /opt/bin/muvm-configure-network \
--ro-bind "$MUVM_PATH/muvm-guest" /opt/bin/muvm-pwbridge \
--ro-bind "$MUVM_PATH/muvm-guest" /opt/bin/muvm-dbusbridge \
--ro-bind "$MUNIX_SYSTEMD_UNITS" /opt/systemd \
--symlink "$MICROVM_CLOSURE/etc" /etc \
--symlink "$MICROVM_CLOSURE/sw/bin/sh" /bin/sh \
--symlink "$MICROVM_CLOSURE/sw/bin/env" /usr/bin/env \

View file

@ -9,12 +9,6 @@
...
}:
let
useTTY = {
TTYPath = "/dev/hvc0";
StandardOutput = "tty";
StandardInput = "tty";
StandardError = "tty";
};
runtimeDir = "/run/vm-user";
system = pkgs.stdenv.hostPlatform.system;
in
@ -171,95 +165,6 @@ in
systemd.settings.Manager.DefaultEnvironment = "XDG_RUNTIME_DIR=${runtimeDir}";
systemd.services.muvm-remote = {
enable = true;
description = "microVM Application runner";
onFailure = [ "exit.target" ];
onSuccess = [ "exit.target" ];
wants = [ "sockets.target" ];
after = [ "sockets.target" ];
wantedBy = [ "microvm.target" ];
serviceConfig = {
Type = "exec";
PassEnvironment = [
"MESA_LOADER_DRIVER_OVERRIDE"
"MUVM_REMOTE_CONFIG"
# "KRUN_CONFIG"
"TERM"
"XDG_SESSION_TYPE"
"SDL_VIDEODRIVER"
"QT_QPA_PLATFORM"
"_JAVA_AWT_WM_NONREPARENTING"
"ELECTRON_OZONE_PLATFORM_HINT"
"GTK_USE_PORTAL"
"QT_QPA_PLATFORMTHEME"
];
Environment = [
"WAYLAND_DISPLAY=wayland-1"
"DBUS_SESSION_BUS_ADDRESS=unix:path=${runtimeDir}/dbus.sock"
"PATH=/run/current-system/sw/bin"
];
User = "appvm";
Group = "appvm";
ExecStartPre = "+/run/current-system/sw/bin/chown appvm:appvm ${runtimeDir}";
ExecStart = "/opt/bin/muvm-remote";
ExecStopPost = ''+${pkgs.python3}/bin/python -c "import os,fcntl,struct;print(os.getenv('EXIT_STATUS', '1'));fcntl.ioctl(os.open('/', os.O_RDONLY), 0x7602, int(os.getenv('EXIT_STATUS', '1')))"'';
}
// useTTY;
};
systemd.services.muvm-configure-network = {
enable = true;
description = "microVM Network configuration";
wantedBy = [ "microvm.target" ];
serviceConfig.Type = "oneshot";
serviceConfig.ExecStart = "/opt/bin/muvm-configure-network";
};
systemd.sockets.muvm-pwbridge = {
enable = true;
description = "PipeWire cross-domain proxy socket";
wantedBy = [ "microvm.target" ];
partOf = [ "muvm-pwbridge.service" ];
listenStreams = [ "${runtimeDir}/pipewire-0" ];
socketConfig = {
SocketUser = "appvm";
SocketGroup = "appvm";
};
};
systemd.services.muvm-pwbridge = {
enable = true;
description = "PipeWire cross-domain proxy";
requires = [ "muvm-pwbridge.socket" ];
serviceConfig.Type = "exec";
serviceConfig.ExecStart = "/opt/bin/muvm-pwbridge";
};
systemd.sockets.wayland-proxy = {
enable = true;
description = "Wayland cross-domain proxy socket";
wantedBy = [ "microvm.target" ];
partOf = [ "wayland-proxy.service" ];
listenStreams = [ "${runtimeDir}/wayland-1" ];
socketConfig = {
SocketUser = "appvm";
SocketGroup = "appvm";
FileDescriptorName = "wayland";
};
};
systemd.services.wayland-proxy = {
enable = true;
description = "Wayland cross-domain proxy";
requires = [ "wayland-proxy.socket" ];
serviceConfig = {
ExecStartPre = "+/run/current-system/sw/bin/chmod 0666 /dev/dri/card0 /dev/dri/renderD128";
ExecStart = "${self.packages.${system}.wl-cross-domain-proxy}/bin/wl-cross-domain-proxy --listen-fd --filter-global wp_presentation";
User = "appvm";
Group = "appvm";
};
};
systemd.sockets.session-bus = {
enable = true;
description = "D-Bus session bus socket";
@ -281,20 +186,6 @@ in
Group = "appvm";
};
};
systemd.services.session-bus-bridge = {
enable = true;
description = "D-Bus session bus";
wantedBy = ["microvm.target"];
requires = ["session-bus.socket" "session-bus.service"];
after = ["session-bus.service"];
serviceConfig = {
Environment = ["DBUS_SESSION_BUS_ADDRESS=unix:path=${runtimeDir}/dbus.sock"];
ExecStartPre = "+/run/current-system/sw/bin/chmod 0666 /dev/dri/card0 /dev/dri/renderD128";
ExecStart = "/opt/bin/muvm-dbusbridge";
User = "appvm";
Group = "appvm";
};
};
hardware.graphics.enable = true;
hardware.graphics.package = self.packages.${system}.mesa;

View file

@ -1,9 +1,18 @@
{ stdenv, writeScriptBin, symlinkJoin, makeWrapper, muvm, passt, bubblewrap, sidebus-broker, mesa, rustc }:
{ stdenv, writeScriptBin, symlinkJoin, makeWrapper, muvm, passt, bubblewrap, sidebus-broker, wl-cross-domain-proxy, mesa, rustc }:
let
munixScript = (writeScriptBin "munix" (builtins.readFile ../../munix)).overrideAttrs(old: {
buildCommand = "${old.buildCommand}\n patchShebangs $out";
});
munixSystemd = stdenv.mkDerivation {
name = "munix-systemd";
src = ../../systemd;
dontUnpack = true;
installPhase = ''
mkdir -p $out
cp -aR $src/* $out
'';
};
microActivate = stdenv.mkDerivation {
name = "micro-activate";
src = ../../micro-activate.rs;
@ -19,9 +28,9 @@ let
};
in symlinkJoin {
name = "munix";
paths = [ munixScript microActivate muvm passt bubblewrap sidebus-broker ];
paths = [ munixScript microActivate muvm passt bubblewrap sidebus-broker wl-cross-domain-proxy ];
buildInputs = [ makeWrapper ];
postBuild = ''
wrapProgram $out/bin/munix --prefix PATH : $out/bin --set FALLBACK_OPENGL_DRIVER ${mesa}
wrapProgram $out/bin/munix --prefix PATH : $out/bin --set FALLBACK_OPENGL_DRIVER ${mesa} --set MUNIX_SYSTEMD_UNITS ${munixSystemd}
'';
}

View file

@ -0,0 +1 @@
../muvm-configure-network.service

View file

@ -0,0 +1 @@
../muvm-remote.service

View file

@ -0,0 +1 @@
../pipewire-bridge.socket

View file

@ -0,0 +1 @@
../session-bus-bridge.service

View file

@ -0,0 +1 @@
../wayland-bridge.socket

View file

@ -0,0 +1,6 @@
[Unit]
Description=microVM Network configuration
[Service]
Type=oneshot
ExecStart=/opt/bin/muvm-configure-network

View file

@ -0,0 +1,33 @@
[Unit]
After=sockets.target
Description=microVM Application runner
OnFailure=exit.target
OnSuccess=exit.target
Wants=sockets.target
[Service]
Type=exec
# Environment="LOCALE_ARCHIVE=/nix/store/1hilqf0v1nm2w8xj87pwpm0rgq6lvhlv-glibc-locales-2.42-47/lib/locale/locale-archive"
# Environment="TZDIR=/nix/store/80izpiglrlqv2zb7rd7m5274k2dr142l-tzdata-2025c/share/zoneinfo"
Environment=WAYLAND_DISPLAY=wayland-1
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/vm-user/dbus.sock
Environment=PATH=/run/current-system/sw/bin
PassEnvironment=MESA_LOADER_DRIVER_OVERRIDE
PassEnvironment=MUVM_REMOTE_CONFIG
PassEnvironment=TERM
PassEnvironment=XDG_SESSION_TYPE
PassEnvironment=SDL_VIDEODRIVER
PassEnvironment=QT_QPA_PLATFORM
PassEnvironment=_JAVA_AWT_WM_NONREPARENTING
PassEnvironment=ELECTRON_OZONE_PLATFORM_HINT
PassEnvironment=GTK_USE_PORTAL
PassEnvironment=QT_QPA_PLATFORMTHEME
ExecStartPre=+/run/current-system/sw/bin/chown appvm:appvm /run/vm-user
ExecStart=/opt/bin/muvm-remote
ExecStopPost=+/nix/store/ygzfhw5nxrn7qqfqmz2s9p6cikcjqqkh-python3-3.13.11/bin/python -c "import os,fcntl,struct;print(os.getenv('EXIT_STATUS', '1'));fcntl.ioctl(os.open('/', os.O_RDONLY), 0x7602, int(os.getenv('EXIT_STATUS', '1')))"
User=appvm
Group=appvm
StandardError=tty
StandardInput=tty
StandardOutput=tty
TTYPath=/dev/hvc0

View file

@ -0,0 +1,12 @@
[Unit]
Description=PipeWire VM bridge
Requires=pipewire-bridge.socket
[Service]
Type=exec
ExecStart=/opt/bin/muvm-pwbridge
# Environment=RUST_LOG=debug
# StandardError=tty
# StandardOutput=tty
# TTYPath=/dev/hvc0

View file

@ -0,0 +1,8 @@
[Unit]
Description=PipeWire VM bridge socket
PartOf=pipewire-bridge.service
[Socket]
SocketGroup=appvm
SocketUser=appvm
ListenStream=/run/vm-user/pipewire-0

View file

@ -0,0 +1,11 @@
[Unit]
After=session-bus.service
Description=D-Bus VM integration bridge
Requires=session-bus.socket session-bus.service
[Service]
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/vm-user/dbus.sock
ExecStartPre=+/run/current-system/sw/bin/chmod 0666 /dev/dri/card0 /dev/dri/renderD128
ExecStart=/opt/bin/muvm-dbusbridge
Group=appvm
User=appvm

View file

@ -0,0 +1,13 @@
[Unit]
Description=Wayland VM bridge
Requires=wayland-bridge.socket
[Service]
ExecStartPre=+/run/current-system/sw/bin/chmod 0666 /dev/dri/card0 /dev/dri/renderD128
ExecStart=/opt/bin/wl-cross-domain-proxy --listen-fd --filter-global wp_presentation
User=appvm
Group=appvm
StandardError=tty
StandardOutput=tty
TTYPath=/dev/hvc0

View file

@ -0,0 +1,9 @@
[Unit]
Description=Wayland VM bridge socket
PartOf=wayland-bridge.service
[Socket]
FileDescriptorName=wayland
SocketGroup=appvm
SocketUser=appvm
ListenStream=/run/vm-user/wayland-1