Compare commits

..

No commits in common. "main" and "refactoring-flake" have entirely different histories.

33 changed files with 480 additions and 1072 deletions

3
.gitignore vendored
View file

@ -1,5 +1,2 @@
result
/testvm*
/target
/micro-activate
.direnv/

9
.gitmodules vendored Normal file
View file

@ -0,0 +1,9 @@
[submodule "muvm"]
path = muvm
url = https://github.com/valpackett/muvm
[submodule "libkrun"]
path = libkrun
url = https://github.com/containers/libkrun
[submodule "libkrunfw"]
path = libkrunfw
url = https://github.com/valpackett/libkrunfw

View file

@ -21,19 +21,6 @@ This will start an interactive bash session inside the microVM.
nix run '.#munix' -- testvm fastfetch
```
**Create a custom VM:**
Use the template to bootstrap a new munix project:
```bash
mkdir my-vm && cd my-vm
nix flake init -t 'git+https://git.clan.lol/clan/munix#musictest'
git init && git add flake.nix
nix run
```
This creates a `flake.nix` with a music player demo (MPD + Euphonica). Edit the module to customize your VM.
## munix Options
- `--uid UID`, `-u UID` - Set microVM UID (default: 1337)
@ -68,3 +55,5 @@ PATH=$PWD/muvm/target/release:$PATH ./munix testvm
- For Wayland: `XDG_RUNTIME_DIR` and `WAYLAND_DISPLAY` set
## Known Issues
- **PipeWire audio**: Not yet working.

View file

@ -1,49 +1,16 @@
{
mkShell,
lib,
systemd,
cargo,
rust-analyzer,
rustfmt,
passt,
bubblewrap,
libkrun,
muvm,
sidebus-broker,
wl-cross-domain-proxy,
wl-backdrop,
pkgs,
}:
{ mkShell, lib, systemd, cargo, rust-analyzer, rustfmt, passt, bubblewrap, libkrun, muvm }:
let
projects = [
libkrun
muvm
];
in
mkShell {
projects = [ libkrun muvm ];
in mkShell {
MUVM_UDEVD_PATH = "${systemd}/lib/systemd/systemd-udevd";
nativeBuildInputs = lib.concatMap (pkg: pkg.nativeBuildInputs) projects;
buildInputs =
(lib.concatMap (pkg: pkg.buildInputs) projects)
++ [
# virglrenderer
cargo
rust-analyzer
rustfmt
passt
bubblewrap
sidebus-broker
wl-cross-domain-proxy
wl-backdrop
]
++ (with pkgs; [
meson
wayland
wayland-protocols
wayland-scanner
cairo
libgbm
]);
# Enough things to compile wl-cross-domain-proxy, muvm, etc. in development
buildInputs = (lib.concatMap (pkg: pkg.buildInputs) projects) ++ [
# virglrenderer
cargo
rust-analyzer
rustfmt
passt
bubblewrap
];
}

148
flake.lock generated
View file

@ -1,66 +1,48 @@
{
"nodes": {
"flake-parts": {
"flake-utils": {
"inputs": {
"nixpkgs-lib": [
"nixpkgs"
]
"systems": "systems"
},
"locked": {
"lastModified": 1769996383,
"narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"libkrun-src": {
"flake": false,
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1772170018,
"narHash": "sha256-pi4Mrx9wFE8zT8lx+0su2bP1tTkJBh5FaGNQjAFUAx4=",
"owner": "valpackett",
"repo": "libkrun",
"rev": "eeafbc55379b60379414168e7a22d5f95b73d7a0",
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "valpackett",
"repo": "libkrun",
"rev": "eeafbc55379b60379414168e7a22d5f95b73d7a0",
"type": "github"
}
},
"muvm-src": {
"flake": false,
"locked": {
"lastModified": 1772176363,
"narHash": "sha256-aSWulv3ml4XmMYnFOkZCd2YBLIY0Rr8CUHK1NDYk5jw=",
"owner": "valpackett",
"repo": "muvm",
"rev": "c68742bcedb96deb6f23ed5a83188022d1cdf71d",
"type": "github"
},
"original": {
"owner": "valpackett",
"repo": "muvm",
"rev": "c68742bcedb96deb6f23ed5a83188022d1cdf71d",
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1770197578,
"narHash": "sha256-AYqlWrX09+HvGs8zM6ebZ1pwUqjkfpnv8mewYwAo+iM=",
"lastModified": 1761672384,
"narHash": "sha256-o9KF3DJL7g7iYMZq9SWgfS1BFlNbsm6xplRjVlOCkXI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "00c21e4c93d963c50d4c0c89bfa84ed6e0694df2",
"rev": "08dacfca559e1d7da38f3cf05f1f45ee9bfd213c",
"type": "github"
},
"original": {
@ -72,62 +54,64 @@
},
"root": {
"inputs": {
"flake-parts": "flake-parts",
"libkrun-src": "libkrun-src",
"muvm-src": "muvm-src",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"sidebus": "sidebus",
"wl-backdrop": "wl-backdrop"
"virtwl": "virtwl"
}
},
"sidebus": {
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"virtwl": {
"inputs": {
"flake-parts": [
"flake-parts"
],
"flake-utils": "flake-utils_2",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1772183103,
"narHash": "sha256-9jbqBtaLUdOeT95PVUMz45JdUpVeJ25ZYZHEOQn9XsI=",
"ref": "main",
"rev": "c42eaef55440e2594677ede5279bd8c3eaf128f2",
"lastModified": 1758075582,
"narHash": "sha256-o2lpXQLaM9QcZVr+sAxvh83CqJW1QkFhfja6K40ndmA=",
"ref": "wip",
"rev": "5a5df73a11b2f6bf671a8fc89926ac993e0fbb78",
"shallow": true,
"submodules": true,
"type": "git",
"url": "https://git.clan.lol/clan/sidebus"
"url": "https://github.com/valpackett/wayland-proxy-virtwl"
},
"original": {
"ref": "main",
"ref": "wip",
"shallow": true,
"submodules": true,
"type": "git",
"url": "https://git.clan.lol/clan/sidebus"
}
},
"wl-backdrop": {
"inputs": {
"flake-parts": [
"flake-parts"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1772781884,
"narHash": "sha256-epVLmAHnJi4EoZea5DUmxB3r6SPSMu3Nzki7JzbK0sA=",
"ref": "main",
"rev": "20e3910ef60aa627933a5d750b60cc3dcd0653cf",
"shallow": true,
"type": "git",
"url": "https://git.clan.lol/valpackett/wl-backdrop"
},
"original": {
"ref": "main",
"shallow": true,
"type": "git",
"url": "https://git.clan.lol/valpackett/wl-backdrop"
"url": "https://github.com/valpackett/wayland-proxy-virtwl"
}
}
},

200
flake.nix
View file

@ -5,142 +5,76 @@
};
inputs = {
self.submodules = true;
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs";
sidebus.url = "git+https://git.clan.lol/clan/sidebus?shallow=1&ref=main";
sidebus.inputs.nixpkgs.follows = "nixpkgs";
sidebus.inputs.flake-parts.follows = "flake-parts";
wl-backdrop.url = "git+https://git.clan.lol/valpackett/wl-backdrop?shallow=1&ref=main";
wl-backdrop.inputs.nixpkgs.follows = "nixpkgs";
wl-backdrop.inputs.flake-parts.follows = "flake-parts";
# To override with local checkouts during development, use the --override-input CLI flag!
muvm-src = {
url = "github:valpackett/muvm/c68742bcedb96deb6f23ed5a83188022d1cdf71d"; # v0.5.0+custom-init+dbus
flake = false;
};
libkrun-src = {
url = "github:valpackett/libkrun/eeafbc55379b60379414168e7a22d5f95b73d7a0"; # PR #558 (map permission fix) + D-Bus WIP
flake = false;
};
# libkrunfw-src = {
# url = "github:containers/libkrunfw/20484a2e60290acb74c43ccfd6e1ea4caf41d470"; # v5.1.0
# flake = false;
# };
# nixpkgs.url = "git+https://github.com/valpackett/nixpkgs?shallow=1&submodules=1&ref=..";
flake-utils.url = "github:numtide/flake-utils";
virtwl.url = "git+https://github.com/valpackett/wayland-proxy-virtwl?shallow=1&submodules=1&ref=wip";
virtwl.inputs.nixpkgs.follows = "nixpkgs";
};
outputs =
inputs@{
self,
nixpkgs,
flake-parts,
sidebus,
wl-backdrop,
muvm-src,
libkrun-src,
# libkrunfw-src,
...
}:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = [
"x86_64-linux"
"aarch64-linux"
outputs = { self, nixpkgs, flake-utils, virtwl, ... }: {
nixosModules.testvm = nixpkgs.lib.modules.importApply ./nixosModules/testvm.nix { inherit virtwl; };
nixosModules.default = nixpkgs.lib.modules.importApply ./nixosModules/default.nix { inherit self virtwl; };
nixosConfigurations.testvm-x86_64 = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
self.nixosModules.default
self.nixosModules.testvm
{ nixpkgs.config.allowUnfree = true; }
];
flake = {
nixosModules.testvm = nixpkgs.lib.modules.importApply ./nixosModules/testvm.nix { };
nixosModules.default = nixpkgs.lib.modules.importApply ./nixosModules/default.nix {
inherit self;
};
templates.musictest = {
description = "Music player demo VM with MPD and Euphonica";
path = ./templates/musictest;
};
nixosConfigurations.testvm-x86_64 = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
self.nixosModules.default
self.nixosModules.testvm
{ nixpkgs.config.allowUnfree = true; }
];
};
nixosConfigurations.testvm-aarch64 = nixpkgs.lib.nixosSystem {
system = "aarch64-linux";
modules = [
self.nixosModules.default
self.nixosModules.testvm
{ nixpkgs.config.allowUnfree = true; }
];
};
};
perSystem =
{
pkgs,
system,
self',
...
}:
{
_module.args.pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
};
checks =
(pkgs.lib.mapAttrs' (n: pkgs.lib.nameValuePair "package-${n}") self'.packages)
// (pkgs.lib.mapAttrs' (n: pkgs.lib.nameValuePair "devShell-${n}") self'.devShells)
// (pkgs.lib.optionalAttrs (system == "x86_64-linux") {
nixos-testvm = self.nixosConfigurations.testvm-x86_64.config.system.build.toplevel;
})
// (pkgs.lib.optionalAttrs (system == "aarch64-linux") {
nixos-testvm = self.nixosConfigurations.testvm-aarch64.config.system.build.toplevel;
});
packages = {
# Packages support variant parameter: null (default), "sev", or "tdx"
# To build a variant: packages.libkrunfw.override { variant = "sev"; }
libkrunfw = pkgs.callPackage ./packages/libkrunfw {
# libkrunfw-src = libkrunfw-src;
};
libkrun = pkgs.callPackage ./packages/libkrun {
libkrunfw = self'.packages.libkrunfw;
libkrun-src = libkrun-src;
};
mesa = pkgs.callPackage ./packages/mesa { };
muvm = pkgs.callPackage ./packages/muvm {
libkrun = self'.packages.libkrun;
muvm-src = muvm-src;
};
munix = pkgs.callPackage ./packages/munix {
mesa = self'.packages.mesa;
muvm = self'.packages.muvm;
wl-cross-domain-proxy = self'.packages.wl-cross-domain-proxy;
wl-backdrop = wl-backdrop.packages.${system}.wl-backdrop;
sidebus-broker = sidebus.packages.${system}.sidebus-broker;
};
wl-cross-domain-proxy = pkgs.callPackage ./packages/wl-cross-domain-proxy { };
};
devShells.default = pkgs.callPackage ./devShells {
libkrun = self'.packages.libkrun;
muvm = self'.packages.muvm;
wl-cross-domain-proxy = self'.packages.wl-cross-domain-proxy;
wl-backdrop = wl-backdrop.packages.${system}.wl-backdrop;
sidebus-broker = sidebus.packages.${system}.sidebus-broker;
};
};
};
nixosConfigurations.testvm-aarch64 = nixpkgs.lib.nixosSystem {
system = "aarch64-linux";
modules = [
self.nixosModules.default
self.nixosModules.testvm
{ nixpkgs.config.allowUnfree = true; }
];
};
} // flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" ] (system:
let
pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
};
in {
checks =
(pkgs.lib.mapAttrs' (n: pkgs.lib.nameValuePair "package-${n}") self.packages.${system})
// (pkgs.lib.mapAttrs' (n: pkgs.lib.nameValuePair "devShell-${n}") self.devShells.${system})
// (pkgs.lib.optionalAttrs (system == "x86_64-linux") {
nixos-testvm = self.nixosConfigurations.testvm-x86_64.config.system.build.toplevel;
})
// (pkgs.lib.optionalAttrs (system == "aarch64-linux") {
nixos-testvm = self.nixosConfigurations.testvm-aarch64.config.system.build.toplevel;
});
packages = {
# Packages support variant parameter: null (default), "sev", or "tdx"
# To build a variant: packages.libkrunfw.override { variant = "sev"; }
libkrunfw = pkgs.callPackage ./packages/libkrunfw { };
libkrun = pkgs.callPackage ./packages/libkrun {
libkrunfw = self.packages.${system}.libkrunfw;
};
mesa = pkgs.callPackage ./packages/mesa { };
muvm = pkgs.callPackage ./packages/muvm {
libkrun = self.packages.${system}.libkrun;
};
munix = pkgs.callPackage ./packages/munix {
muvm = self.packages.${system}.muvm;
};
};
devShells.default = pkgs.callPackage ./devShells {
libkrun = self.packages.${system}.libkrun;
muvm = self.packages.${system}.muvm;
};
});
}

1
libkrun Submodule

@ -0,0 +1 @@
Subproject commit 72b8b0870c3ecad0388f52150a68f79083fcc86f

1
libkrunfw Submodule

@ -0,0 +1 @@
Subproject commit 4b98077866355ecfed46781ab6143d21127bf977

View file

@ -1,155 +0,0 @@
use std::os::raw::{c_char, c_int, c_ulong, c_void};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::process::CommandExt;
const MS_RDONLY: c_ulong = 0x01;
const MS_NOSUID: c_ulong = 0x02;
const MS_NODEV: c_ulong = 0x04;
const MS_RELATIME: c_ulong = 0x200000;
const MS_STRICTATIME: c_ulong = 0x1000000;
const CLONE_NEWTIME: c_int = 0x80;
unsafe extern "C" {
fn mount(
src: *const c_char,
target: *const c_char,
fstype: *const c_char,
flags: c_ulong,
data: *const c_void,
) -> c_int;
fn getrandom(buf: *mut u8, buflen: usize, flags: u32) -> c_int;
fn unshare(flags: c_int) -> c_int;
}
fn gen_machine_id() -> String {
use std::fmt::Write as _;
let mut bytes: [u8; 16] = [0; 16];
if unsafe { getrandom(bytes.as_mut_ptr(), 16, 0) } == -1 {
eprintln!("[micro-activate] getrandom failed!");
}
let mut result = String::with_capacity(32);
for b in bytes {
let _ = write!(result, "{:02x}", b);
}
result
}
fn parse_tmpfiles_line(line: &str) -> Option<(&str, &str)> {
// NOTE: does not support actual whitespace inside quotes
// (that's not gonna appear in these files we parse)
let mut it = line
.split_whitespace()
.map(|s| s.trim_start_matches('\'').trim_end_matches('\''));
let instr = it.next()?;
if !instr.starts_with('L') {
return None;
}
let src = it.next()?;
let _ = it.next()?;
let _ = it.next()?;
let _ = it.next()?;
let _ = it.next()?;
let dst = it.next()?;
Some((src, dst))
}
fn link_tmpfiles(contents: &[u8]) -> Result<(), std::io::Error> {
for (src, dst) in str::from_utf8(contents)
.unwrap()
.lines()
.flat_map(parse_tmpfiles_line)
{
std::os::unix::fs::symlink(dst, src)?;
}
Ok(())
}
fn main() -> Result<(), std::io::Error> {
let closure = std::env::var("MICROVM_CLOSURE").unwrap();
// systemd really wants /run to be a mountpoint and will mount a tmpfs on its own
// if it's not already a mountpoint. Well, it's correct: reaching into virtiofs
// (which is what not-mounting would entail) for /run stuff is not great.
//
// Let's preserve the fixed passed-in files and set up the NixOS symlinks in the new mount.
let resolv_conf = std::fs::read("/run/resolv.conf")?;
let localtime = std::fs::read("/run/localtime")?;
assert_eq!(
unsafe {
mount(
c"tmpfs".as_ptr(),
c"/run".as_ptr(),
c"tmpfs".as_ptr(),
MS_NOSUID | MS_NODEV | MS_STRICTATIME,
std::ptr::null(),
)
},
0
);
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"))
{
link_tmpfiles(&tmp_graphics)?;
} else {
eprintln!("[micro-activate] Could not find the closure's graphics-driver.conf!");
}
// We need the /etc metadata overlay not just for abstract correctness, but even just to
// allow the regular user to run systemctl (it doesn't like passwd being owned by non-root)..
let metadata_img = std::fs::read_link(format!("{closure}/etc-metadata-image"))
.expect("The closure must use an immutable /etc overlay!");
let basedir = std::fs::read_link(format!("{closure}/etc-basedir"))
.expect("The closure must use an immutable /etc overlay!");
let overlay_opts = std::ffi::CString::new(format!(
"redirect_dir=on,metacopy=on,lowerdir=/run/etc.meta::{}",
basedir.display()
))
.unwrap();
std::fs::create_dir("/run/etc.meta")?;
std::fs::remove_file("/etc")?;
std::fs::create_dir("/etc")?;
unsafe {
assert_eq!(
mount(
metadata_img.as_os_str().as_bytes().as_ptr() as *const c_char,
c"/run/etc.meta".as_ptr(),
c"erofs".as_ptr(),
MS_RDONLY | MS_NODEV | MS_NOSUID,
std::ptr::null(),
),
0
);
assert_eq!(
mount(
c"overlay".as_ptr(),
c"/etc".as_ptr(),
c"overlay".as_ptr(),
MS_NODEV | MS_NOSUID | MS_RELATIME,
overlay_opts.as_ptr() as *const c_void,
),
0
);
}
if let Ok(offset) = std::env::var("BOOT_TIME_OFFSET") {
if unsafe { unshare(CLONE_NEWTIME) } != 0 {
eprintln!("[micro-activate] Could not unshare time!");
} else {
std::fs::write(
"/proc/self/timens_offsets",
format!("monotonic {offset}\nboottime {offset}\n"),
)?;
}
}
let mut args = std::env::args_os().skip(1);
let cmd = args.next().unwrap();
Err(std::process::Command::new(cmd).args(args).exec())
}

171
munix
View file

@ -2,46 +2,19 @@
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))
WL_BACKDROP_PATH=$(dirname $(which wl-backdrop))
HOST_OPENGL_DRIVER=/run/opengl-driver
: "${MICROVM_DEFAULT_COMMAND:=bash}"
: "${MUNIX_SYSTEMD_UNITS:="${SCRIPT_PATH}/systemd"}"
MICROVM_CLOSURE=
MICROVM_COMMAND=()
MICROVM_UID=1337
MICROVM_GID=1337
BWRAP_ARGS=()
MUVM_ARGS=()
SIDEBUS_ARGS=()
GPU=1
WAYLAND=1
PIPEWIRE=1
X11=0
ENV_DEFAULTS=1
USING_PUBLISH=0
export TMP=/tmp TMPDIR=/tmp TEMP=/tmp TEMPDIR=/tmp LC_ALL=C
if [ ! -e "$HOST_OPENGL_DRIVER" ]; then
HOST_OPENGL_DRIVER="$FALLBACK_OPENGL_DRIVER"
fi
# A little bit cursed: pre-pass to relaunch with new env var..
BG_COLOR=
pargs=()
while [ "$#" -gt 0 ]; do
case "$1" in
--bg-color) BG_COLOR="$2"; shift 2;;
--wl-backdrop-bin-dir) WL_BACKDROP_PATH="$2"; shift 2;;
*) pargs+=("$1"); shift;;
esac
done
if [ "$BG_COLOR" = "" ]; then
set -- "${pargs[@]}"
else
exec "$WL_BACKDROP_PATH/wl-backdrop" --background "$BG_COLOR" -- "$(readlink -f "$0")" "${pargs[@]}"
fi
while [ "$#" -gt 0 ]; do
case "$1" in
-u|--uid) MICROVM_UID="$2"; shift 2;;
@ -49,19 +22,15 @@ while [ "$#" -gt 0 ]; do
--no-gpu) GPU=0; shift 1;;
--no-wayland) WAYLAND=0; shift 1;;
--no-pipewire) PIPEWIRE=0; shift 1;;
--no-env-defaults) ENV_DEFAULTS=0; shift 1;;
--x11) X11=1; shift 1;;
-b|--bind) BWRAP_ARGS+=("--bind" "$2" "$3"); SIDEBUS_ARGS+=("--path-mapping" "$3=$2"); shift 3;;
--ro-bind) BWRAP_ARGS+=("--ro-bind" "$2" "$3"); SIDEBUS_ARGS+=("--path-mapping" "$3=$2"); shift 3;;
-e|--expose) BWRAP_ARGS+=("--bind" "$2" "$2"); SIDEBUS_ARGS+=("--path-mapping" "$2=$2"); shift 2;;
--ro-expose) BWRAP_ARGS+=("--ro-bind" "$2" "$2"); SIDEBUS_ARGS+=("--path-mapping" "$2=$2"); shift 2;;
-p|--publish) USING_PUBLISH=1; MUVM_ARGS+=("--publish=$2"); shift 2;;
--bind) BWRAP_ARGS+=("--bind" "$2" "$3"); shift 3;;
--ro-bind) BWRAP_ARGS+=("--ro-bind" "$2" "$3"); shift 3;;
--expose) BWRAP_ARGS+=("--bind" "$2" "$2"); shift 2;;
--ro-expose) BWRAP_ARGS+=("--ro-bind" "$2" "$2"); shift 2;;
--host-opengl-driver) HOST_OPENGL_DRIVER="$2"; shift 2;;
--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;;
--wl-backdrop-bin-dir) shift 2;;
--) shift 1; MICROVM_COMMAND+=("$@"); break;;
-*) echo "munix: unknown option: $1" >&2; exit 1;;
*)
@ -79,36 +48,11 @@ if [ "$MICROVM_CLOSURE" = "" ]; then
exit 1
fi
if [ "$MUVM_PATH" = "" ]; then
echo "munix: muvm not found, provide a --muvm-bin-dir or fix \$PATH" >&2
exit 1
fi
if [ "$PASST_PATH" = "" ]; then
echo "munix: passt not found, provide a --passt-bin-dir or fix \$PATH" >&2
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 [ "$WL_BACKDROP_PATH" = "" ]; then
echo "munix: wl-backdrop not found, provide a --wl-backdrop-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
fi
# Resolve symlinks automatically
MICROVM_CLOSURE=$(realpath "$MICROVM_CLOSURE")
if [ ${#MICROVM_COMMAND[@]} -eq 0 ]; then
MICROVM_COMMAND=("$MICROVM_DEFAULT_COMMAND")
MICROVM_COMMAND=("bash")
fi
if [ "$GPU" -eq 1 ]; then
@ -129,7 +73,7 @@ if [ "$GPU" -eq 1 ]; then
driver_mod="$(readlink "$driver_link")"
driver_name="${driver_mod##*/}"
case "$driver_name" in
amdgpu|msm_dpu) # TODO: i915
amdgpu|msm) # TODO: i915
echo "munix: ${card##*/} gpu driver is '$driver_name', using vdrm" >&2;
GPU_MODE=drm
break;;
@ -158,15 +102,7 @@ if [ "$WAYLAND" -eq 1 ]; then
"--bind" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY"
"--setenv" "WAYLAND_DISPLAY" "$WAYLAND_DISPLAY"
)
if [ "$ENV_DEFAULTS" -eq 1 ]; then
MUVM_ARGS+=(
"-e" "XDG_SESSION_TYPE=wayland"
"-e" "SDL_VIDEODRIVER=wayland"
"-e" "QT_QPA_PLATFORM=wayland"
"-e" "_JAVA_AWT_WM_NONREPARENTING=1" # e.g. with xwayland-satellite
"-e" "ELECTRON_OZONE_PLATFORM_HINT=wayland" # 28 < Electron < 39; newer should work by default
)
fi
MUVM_ARGS+=("-e" "WAYLAND_DISPLAY=wayland-1") # the proxy is managed by us, not muvm
fi
if [ "$PIPEWIRE" -eq 1 ]; then
@ -184,11 +120,6 @@ if [ "$PIPEWIRE" -eq 1 ]; then
PIPEWIRE_REMOTE=pipewire-0
fi
BWRAP_ARGS+=("--bind" "$PIPEWIRE_RUNTIME_DIR/$PIPEWIRE_REMOTE" "$PIPEWIRE_RUNTIME_DIR/$PIPEWIRE_REMOTE")
if [ "$ENV_DEFAULTS" -eq 1 ]; then
MUVM_ARGS+=(
"-e" "SDL_AUDIO_DRIVER=pipewire"
)
fi
fi
if [ "$X11" -eq 1 ]; then
@ -200,87 +131,43 @@ else
unset DISPLAY XAUTHORITY
fi
declare -a BG_PIDS
cleanup() {
for pid in "${BG_PIDS[@]}"; do
if kill -0 "$pid" 2>/dev/null; then
echo "Killing process $pid"
kill -INT "$pid"
wait "$pid" 2>/dev/null
fi
done
exit
}
trap cleanup EXIT INT TERM
HOST_RUNTIME_DIR="$XDG_RUNTIME_DIR/munix.$$"
mkdir -p $HOST_RUNTIME_DIR
rm $HOST_RUNTIME_DIR/*
mkdir -p "$HOST_RUNTIME_DIR/home"
RUST_LOG=debug sidebus-broker \
--path-mapping "/home=$HOST_RUNTIME_DIR/home" \
"${SIDEBUS_ARGS[@]}" \
--guest-mountpoint /mnt/munix-doc-portal/doc \
--runtime-dir "$HOST_RUNTIME_DIR" \
--unix-path "$HOST_RUNTIME_DIR/port.sock" & # >/dev/null 2>&1 &
BG_PIDS+=("$!")
while [ ! -S "$HOST_RUNTIME_DIR/port.sock" ]; do sleep 0.1; done
BWRAP_ARGS=( # prepend home mount before other mounts to not override custom bind mounts under /home
--bind "$HOST_RUNTIME_DIR/home" /home
"${BWRAP_ARGS[@]}"
--bind "$HOST_RUNTIME_DIR" /mnt/munix-doc-portal
--setenv "RUTABAGA_DBUS_CLIENT_SOCKET" /mnt/munix-doc-portal/port.sock
)
if [ "$ENV_DEFAULTS" -eq 1 ]; then
MUVM_ARGS+=(
"-e" "GTK_USE_PORTAL=1" # GTK 3 including Firefox
"-e" "QT_QPA_PLATFORMTHEME=xdgdesktopportal"
)
fi
if [ "$USING_PUBLISH" -eq 1 ]; then
printf "\n\n\n\e[1mNote: due to a \e[31mBUG\e[39m with port publishing (-p) you have to send the first outgoing packet (e.g. ping -c1 8.8.8.8) before your ports start receiving traffic. Sorry for the inconvenience!\e[39;0m\n\n\n\n" >&2
fi
# xxx: some time is lost to the starting process..
[[ "$(</proc/uptime)" =~ ([0-9]+)\.([0-9]+) ]]
BOOT_TIME_OFFSET="${BASH_REMATCH[1]} $(( ${BASH_REMATCH[2]} * 1000000 ))"
# do not 'exec' because of cleanup :)
bwrap --unshare-all --share-net \
exec bwrap --unshare-all --share-net \
--uid $MICROVM_UID --gid $MICROVM_GID \
--tmpfs / \
--dir /run --dir /var --symlink /run /var/run --dir /tmp --dir /mnt --dir /bin --dir /usr/bin \
--dir /run --dir /var --symlink /run /var/run --dir /tmp \
--proc /proc --ro-bind /sys /sys \
--dev /dev --dir /dev/input --dev-bind /dev/kvm /dev/kvm \
--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 \
--symlink "$MICROVM_CLOSURE" /run/current-system \
--symlink "$MICROVM_CLOSURE/etc/systemd" /etc/systemd \
--ro-bind /nix/store /nix/store \
--file 13 /run/resolv.conf \
--file 14 /run/localtime \
--ro-bind /run/systemd/resolve /run/systemd/resolve \
--file 11 /etc/passwd \
--file 12 /etc/group \
--file 13 /etc/resolv.conf \
--dir "$XDG_RUNTIME_DIR" \
--setenv PATH "/run/munix/muvm:/run/munix/passt:$MICROVM_CLOSURE/sw/bin" \
"${BWRAP_ARGS[@]}" \
muvm \
--custom-init-cmdline "/opt/bin/micro-activate $MICROVM_CLOSURE/sw/sbin/init --log-target=console" \
--custom-init-cmdline "$MICROVM_CLOSURE/sw/sbin/init --log-target=console" \
"${MUVM_ARGS[@]}" \
-e container=munix \
-e MICROVM_CLOSURE="$MICROVM_CLOSURE" \
-e MICROVM_UID="$MICROVM_UID" -e MICROVM_GID="$MICROVM_GID" \
-e BOOT_TIME_OFFSET="$BOOT_TIME_OFFSET" \
-i -t -- "${MICROVM_COMMAND[@]}" \
13< /etc/resolv.conf \
14< /etc/localtime
-i -t "${MICROVM_COMMAND[@]}" \
11< <(cat <<EOF
munix:x:$MICROVM_UID:$MICROVM_GID:Hypervisor:/:/run/current-system/sw/bin/nologin
nobody:x:65534:65534:Unprivileged account:/var/empty:/run/current-system/sw/bin/nologin
EOF
) \
12< <(cat <<EOF
munix:x:$MICROVM_GID:
nogroup:x:65534:
EOF
) \
13< /etc/resolv.conf
# --log-level=debug

1
muvm Submodule

@ -0,0 +1 @@
Subproject commit d6c7496fdb40d825f5fdd0777f1e613432c5a1b2

View file

@ -1,204 +1,209 @@
{
self,
}:
{
pkgs,
lib,
utils,
config,
...
}:
let
{ self, virtwl }:
{ pkgs, lib, utils, config, ... }: let
useTTY = {
TTYPath = "/dev/hvc0";
StandardOutput = "tty";
StandardInput = "tty";
StandardError = "tty";
};
runtimeDir = "/run/vm-user";
system = pkgs.stdenv.hostPlatform.system;
in
{
options.virtualisation.munix.defaultCommand = lib.mkOption {
type = lib.types.str;
default = "bash";
description = "Default command to run when starting the VM without arguments.";
in {
boot.isContainer = true;
fileSystems."/".device = lib.mkDefault "/dev/sda"; # dummy
# Disable unused things
environment.defaultPackages = lib.mkDefault [ ];
documentation = {
enable = lib.mkDefault false;
doc.enable = lib.mkDefault false;
info.enable = lib.mkDefault false;
man.enable = lib.mkDefault false;
nixos.enable = lib.mkDefault false;
};
services.logrotate.enable = false;
services.udisks2.enable = false;
system.tools.nixos-generate-config.enable = false;
system.activationScripts.specialfs = lib.mkForce "";
systemd.coredump.enable = false;
networking.firewall.enable = false;
powerManagement.enable = false;
boot.kexec.enable = false;
console.enable = false;
# Configure activation / systemd
boot.initrd.systemd.enable = true; # for etc.overlay, but we don't have initrd
system.etc.overlay.enable = true; # erofs
system.etc.overlay.mutable = false;
systemd.sysusers.enable = true;
services.userborn.enable = false;
services.udev.enable = lib.mkDefault true;
services.udev.packages = lib.mkDefault [];
environment.etc."resolv.conf".text = "# to be overridden with mount";
environment.etc."machine-id".text = "# to be overridden with mount";
environment.etc."systemd/system".source = lib.mkForce (utils.systemdUtils.lib.generateUnits {
type = "system";
units = config.systemd.units;
upstreamUnits = [
"sysinit.target"
"local-fs.target"
"nss-user-lookup.target"
"umount.target"
"shutdown.target"
"reboot.target"
"exit.target"
"final.target"
"systemd-exit.service"
"systemd-journald.socket"
"systemd-journald-audit.socket"
"systemd-journald-dev-log.socket"
"systemd-journald.service"
"systemd-sysusers.service"
"systemd-udevd-kernel.socket"
"systemd-udevd-control.socket"
"systemd-udevd.service"
"systemd-tmpfiles-setup.service"
"user.slice"
];
upstreamWants = ["multi-user.target.wants"];
});
# systemd.package = pkgs.systemdMinimal; # no sysusers
systemd.defaultUnit = "microvm.target";
systemd.targets.microvm = {
description = "Minimal microVM system";
wants = ["systemd-journald.socket" "systemd-udevd.service" "dbus.socket"];
unitConfig.AllowIsolate = "yes";
};
systemd.services.systemd-remount-fs.enable = lib.mkForce false;
systemd.services.systemd-pstore.enable = lib.mkForce false;
systemd.services.lastlog2-import.enable = lib.mkForce false;
systemd.services.suid-sgid-wrappers.enable = lib.mkForce false;
systemd.services.systemd-sysusers.serviceConfig = {
ExecStartPre = lib.mkForce [];
ExecStartPost = lib.mkForce [];
};
systemd.services.microvm-nixos-activation = {
enable = true;
description = "NixOS Activation";
wantedBy = ["local-fs.target"];
before = ["systemd-tmpfiles-setup.service" "systemd-sysusers.service"];
requires = ["systemd-tmpfiles-setup.service" "systemd-sysusers.service"];
unitConfig.DefaultDependencies = false;
serviceConfig = {
Type = "oneshot";
PassEnvironment = ["MICROVM_CLOSURE" "MICROVM_UID" "MICROVM_GID"];
} // useTTY;
script = ''
PATH=$MICROVM_CLOSURE/sw/bin
cp /etc/resolv.conf /run/
$MICROVM_CLOSURE/activate || true
mount --bind /run/resolv.conf /etc/resolv.conf
mount --bind /run/machine-id /etc/machine-id
chown 1337:1337 /run
'';
};
config = {
boot.isContainer = true;
fileSystems."/".device = lib.mkDefault "/dev/sda"; # dummy
# Configure user account
users.mutableUsers = false;
users.users.appvm = {
uid = 1337;
group = "appvm";
isNormalUser = false;
isSystemUser = true;
home = "/home/appvm";
description = "microVM User";
extraGroups = [ "wheel" "video" "input" "systemd-journal" ];
};
users.groups.appvm.gid = 1337;
users.allowNoPasswordLogin = true;
systemd.tmpfiles.rules = ["d ${runtimeDir} 1337 1337 -"];
# Disable unused things
environment.defaultPackages = lib.mkDefault [ ];
documentation = {
enable = lib.mkDefault false;
doc.enable = lib.mkDefault false;
info.enable = lib.mkDefault false;
man.enable = lib.mkDefault false;
nixos.enable = lib.mkDefault false;
};
services.logrotate.enable = false;
services.udisks2.enable = false;
system.tools.nixos-generate-config.enable = false;
system.activationScripts.specialfs = lib.mkForce "";
systemd.coredump.enable = false;
networking.firewall.enable = false;
powerManagement.enable = false;
boot.kexec.enable = false;
console.enable = false;
# Configure services
# Configure activation / systemd
boot.initrd.systemd.enable = true; # for etc.overlay, but we don't have initrd
system.etc.overlay.enable = true; # erofs
system.etc.overlay.mutable = false;
system.systemBuilderCommands = # XXX: removed with the introduction of nixos-init
''
ln -s ${config.system.build.etcMetadataImage} $out/etc-metadata-image
ln -s ${config.system.build.etcBasedir} $out/etc-basedir
'';
system.switch.enable = false;
services.udev.enable = lib.mkDefault true;
services.udev.packages = lib.mkDefault [ ];
services.resolved.enable = false;
environment.etc."resolv.conf".source = "/run/resolv.conf";
environment.etc."machine-id".source = "/run/machine-id";
environment.etc."localtime".source = "/run/localtime";
environment.etc."systemd/system".source = lib.mkForce (
utils.systemdUtils.lib.generateUnits {
type = "system";
units = config.systemd.units;
upstreamUnits = [
"sysinit.target"
"local-fs.target"
"nss-user-lookup.target"
"umount.target"
"sockets.target"
"shutdown.target"
"reboot.target"
"exit.target"
"final.target"
"systemd-exit.service"
"systemd-journald.socket"
"systemd-journald-audit.socket"
"systemd-journald-dev-log.socket"
"systemd-journald.service"
"systemd-udevd-kernel.socket"
"systemd-udevd-control.socket"
"user.slice"
];
upstreamWants = [ "multi-user.target.wants" ];
}
);
systemd.services.muvm-remote = {
enable = true;
description = "microVM Application runner";
onFailure = ["exit.target"];
onSuccess = ["exit.target"];
after = ["microvm-nixos-activation.service"];
wantedBy = ["microvm.target"];
serviceConfig = {
Type = "exec";
PassEnvironment = ["TERM" "MUVM_REMOTE_CONFIG"]; # "KRUN_CONFIG"];
Environment = ["XDG_RUNTIME_DIR=${runtimeDir}" "WAYLAND_DISPLAY=wayland-1" "PATH=/run/current-system/sw/bin"];
User = "appvm";
Group = "appvm";
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.package = pkgs.systemdMinimal; # no analyze
systemd.defaultUnit = "microvm.target";
systemd.targets.microvm = {
description = "Minimal microVM system";
wants = [
"systemd-journald.socket"
"systemd-udevd.service"
"dbus.socket"
];
unitConfig.AllowIsolate = "yes";
};
systemd.services.generate-shutdown-ramfs.enable = lib.mkForce false;
systemd.services.systemd-remount-fs.enable = lib.mkForce false;
systemd.services.systemd-pstore.enable = lib.mkForce false;
systemd.services.lastlog2-import.enable = lib.mkForce false;
systemd.services.suid-sgid-wrappers.enable = lib.mkForce false;
systemd.services.systemd-udevd = {
# Redefine to remove the Before deps and get out of the critical chain
enable = true;
description = "Rule-based Manager for Device Events and Files";
unitConfig.DefaultDependencies = "no";
serviceConfig = {
CapabilityBoundingSet = "~CAP_SYS_TIME CAP_WAKE_ALARM";
Delegate = "";
DelegateSubgroup = "udev";
Type = "notify-reload";
OOMScoreAdjust = "-1000";
Sockets = "systemd-udevd-control.socket systemd-udevd-kernel.socket systemd-udevd-varlink.socket";
Restart = "always";
RestartSec = "0";
ExecStart = "${pkgs.systemd}/lib/systemd/systemd-udevd";
FileDescriptorStoreMax = "512";
FileDescriptorStorePreserve = "yes";
KillMode = "mixed";
TasksMax = "infinity";
PrivateMounts = "yes";
ProtectHostname = "yes";
MemoryDenyWriteExecute = "yes";
RestrictAddressFamilies = "AF_UNIX AF_NETLINK AF_INET AF_INET6";
RestrictRealtime = "yes";
RestrictSUIDSGID = "yes";
SystemCallFilter = [
"@system-service @module @raw-io bpf"
"~@clock"
];
SystemCallErrorNumber = "EPERM";
SystemCallArchitectures = "native";
LockPersonality = "yes";
IPAddressDeny = "any";
WatchdogSec = "3min";
};
};
systemd.services.muvm-configure-network = {
enable = true;
description = "microVM Network configuration";
wantedBy = ["microvm.target"];
serviceConfig.Type = "oneshot";
serviceConfig.ExecStart = "/opt/bin/muvm-configure-network";
};
# Configure user accounts
systemd.sysusers.enable = false;
services.userborn.enable = true;
services.userborn.static = true;
users.mutableUsers = false;
users.users.appvm = {
uid = 1337;
group = "appvm";
isNormalUser = false;
isSystemUser = true;
home = "/home/appvm";
description = "microVM User";
shell = pkgs.bash; # not nologin, despite being a "system" user
extraGroups = [
"wheel"
"video"
"input"
"systemd-journal"
];
};
users.groups.appvm.gid = 1337;
users.allowNoPasswordLogin = true;
# Configure services
systemd.settings.Manager.DefaultEnvironment = "XDG_RUNTIME_DIR=${runtimeDir}";
systemd.sockets.session-bus = {
enable = true;
description = "D-Bus session bus socket";
wantedBy = [ "microvm.target" ];
partOf = [ "session-bus.service" ];
listenStreams = [ "${runtimeDir}/dbus.sock" ];
socketConfig = {
SocketUser = "appvm";
SocketGroup = "appvm";
};
};
systemd.services.session-bus = {
enable = true;
description = "D-Bus session bus";
requires = [ "session-bus.socket" ];
serviceConfig = {
ExecStart = "${pkgs.dbus}/bin/dbus-daemon --session --address=systemd: --nofork --nopidfile --syslog-only"; # no systemd activation, we don't run a *session* systemd
User = "appvm";
Group = "appvm";
};
};
hardware.graphics.enable = true;
hardware.graphics.package = self.packages.${system}.mesa;
system.build.munix = pkgs.symlinkJoin {
name = "munix";
paths = [ self.packages.${system}.munix ];
buildInputs = [ pkgs.makeWrapper ];
postBuild = ''
wrapProgram $out/bin/munix \
--add-flags ${config.system.build.toplevel} \
--set MICROVM_DEFAULT_COMMAND ${lib.escapeShellArg config.virtualisation.munix.defaultCommand}
'';
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.services.wait-for-udev = {
enable = true;
description = "Wait for device rules being applied";
wantedBy = ["microvm.target"];
requires = ["systemd-udevd.service"];
after = ["systemd-udevd.service"];
serviceConfig = {
Type = "oneshot";
ExecStart = [
"udevadm trigger --action=add"
"udevadm settle"
];
};
};
systemd.sockets.wayland-proxy-virtwl = {
enable = true;
description = "Wayland cross-domain proxy socket";
wantedBy = ["microvm.target"];
partOf = ["wayland-proxy-virtwl.service"];
listenStreams = [ "${runtimeDir}/wayland-1" ];
socketConfig = {
SocketUser = "appvm";
SocketGroup = "appvm";
FileDescriptorName = "wayland";
};
};
systemd.services.wayland-proxy-virtwl = {
enable = true;
description = "Wayland cross-domain proxy";
after = ["wait-for-udev.service"];
requires = ["wayland-proxy-virtwl.socket" "wait-for-udev.service"];
serviceConfig = {
ExecStart = "${virtwl.packages.x86_64-linux.proxy}/bin/wayland-proxy-virtwl --virtio-gpu";
Environment = ["XDG_RUNTIME_DIR=${runtimeDir}"];
User = "appvm";
Group = "appvm";
};
};
hardware.graphics.enable = true;
hardware.graphics.package = self.packages.${pkgs.system}.mesa;
}

View file

@ -1,12 +1,12 @@
{ }:
{ virtwl }:
{ pkgs, ... }: {
system.stateVersion = "26.05";
system.stateVersion = "25.11";
fonts.packages = [ pkgs.adwaita-fonts pkgs.dejavu_fonts ];
programs.dconf.enable = true;
environment.systemPackages = [
pkgs.fastfetch
pkgs.htop
virtwl.packages.${pkgs.system}.proxy
pkgs.wayland-utils
pkgs.weston
pkgs.waycheck
@ -15,8 +15,8 @@
pkgs.mesa-demos
pkgs.xorg.xeyes
pkgs.xterm
pkgs.vkquake
# pkgs.veloren
# pkgs.vkquake # build broken: Program 'spirv-remap' not found
pkgs.veloren
pkgs.kdePackages.kate
pkgs.adwaita-icon-theme
pkgs.amberol
@ -27,18 +27,8 @@
pkgs.ffmpeg-full
pkgs.mpv
pkgs.libva-utils
pkgs.pipewire # cli
pkgs.tailscale
pkgs.zerotierone
pkgs.localsend
pkgs.ashpd-demo
pkgs.nautilus
pkgs.rewaita
pkgs.wl-clipboard-rs
pkgs.snapshot
pkgs.gst_all_1.gst-plugins-base
pkgs.gst_all_1.gst-plugins-good
pkgs.gst_all_1.gst-plugins-bad
pkgs.gst_all_1.gstreamer
];
}

View file

@ -1,32 +1,19 @@
{
libkrun,
libkrunfw,
libkrun-src,
rustPlatform,
libcap_ng,
variant ? null,
...
}:
{ libkrun, libkrunfw, rustPlatform, variant ? null, ... }:
let
libkrunfw' = libkrunfw.override { inherit variant; };
libkrun' = libkrun.override {
withBlk = true;
withNet = true;
withGpu = true;
# --- stick to the override used in nixpkgs' muvm package to reuse nixos.org cache when not overriding src ---
# withSound = true; # not for pipewire forwarding, anyway
# withTimesync = true; # why not?..
# ---------
inherit variant;
libkrunfw = libkrunfw';
};
in
# libkrun'
libkrun'.overrideAttrs (old: {
src = libkrun-src;
(libkrun.override {
withBlk = true;
withGpu = true;
withSound = true;
withNet = true;
inherit variant;
libkrunfw = libkrunfw';
}).overrideAttrs (old: {
src = ../../libkrun;
cargoDeps = rustPlatform.importCargoLock {
lockFile = "${libkrun-src}/Cargo.lock";
lockFile = ../../libkrun/Cargo.lock;
};
buildInputs = old.buildInputs ++ [ libcap_ng ]; # new dep
# mesonFlags = [ (lib.mesonOption "decoders" "gles,vulkan,composer") ]; # no magma(?)
})

View file

@ -1,22 +1,19 @@
{
libkrunfw,
# libkrunfw-src,
# fetchurl,
variant ? null,
...
}:
{ libkrunfw, fetchurl, variant ? null, ... }:
let
libkrunfw' = libkrunfw.override {
inherit variant;
(libkrunfw.override {
inherit variant;
}).overrideAttrs (old: {
version = "5.0.0";
src = ../../libkrunfw;
# src = fetchFromGitHub {
# owner = "containers";
# repo = "libkrunfw";
# tag = "v4.10.0";
# hash = "sha256-mq2gw0+xL6qUZE/fk0vLT3PEpzPV8p+iwRFJHXVOMnk=";
# };
kernelSrc = fetchurl {
url = "mirror://kernel/linux/kernel/v6.x/linux-6.12.44.tar.xz";
hash = "sha256-tlAhDtMCeyJJadFIqjd0Uqmq065/KFGr7dMa3+8Wva4=";
};
in
libkrunfw'
# libkrunfw'.overrideAttrs (old: {
# version = "5.1.0";
# src = libkrunfw-src;
# kernelSrc = fetchurl {
# url = "mirror://kernel/linux/kernel/v6.x/linux-6.12.62.tar.xz";
# hash = "sha256-E+LGhayPq13Zkt0QVzJVTa5RSu81DCqMdBjnt062LBM=";
# };
# })
# buildInputs = old.buildInputs;
})

View file

@ -1,10 +1,16 @@
{ mesa, lib, stdenv }:
{ mesa, lib }:
(mesa.override {
# nothing currently
vulkanDrivers = [
"amd"
"intel"
"microsoft-experimental" # removing this breaks the build
"nouveau"
"swrast"
"virtio"
"gfxstream" # probably not going to use this though
];
}).overrideAttrs (new: old: {
mesonFlags = old.mesonFlags ++
lib.optionals stdenv.hostPlatform.isx86_64 [ (lib.mesonBool "amdgpu-virtio" true) ];
# not that amdgpu can't be found on aarch64 but let's avoid rebuilds for now
# patches = old.patches ++ [ ];
mesonFlags = old.mesonFlags ++ [ (lib.mesonBool "amdgpu-virtio" true) ];
patches = old.patches ++ [ ../../radvmmio.patch ]; # already merged to git
})

View file

@ -1,36 +1,14 @@
{ stdenv, writeScriptBin, symlinkJoin, makeWrapper, muvm, passt, bubblewrap, sidebus-broker, wl-cross-domain-proxy, wl-backdrop, mesa, rustc }:
{ writeScriptBin, symlinkJoin, makeWrapper, muvm, passt, bubblewrap }:
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;
dontUnpack = true;
nativeBuildInputs = [ rustc ];
buildPhase = ''
rustc -C opt-level=s -C panic=abort --edition 2024 -o micro-activate $src
'';
installPhase = ''
mkdir -p $out/bin
mv micro-activate $out/bin
'';
};
in symlinkJoin {
name = "munix";
paths = [ munixScript microActivate muvm passt bubblewrap sidebus-broker wl-cross-domain-proxy wl-backdrop ];
paths = [ munixScript muvm passt bubblewrap ];
buildInputs = [ makeWrapper ];
postBuild = ''
wrapProgram $out/bin/munix --prefix PATH : $out/bin --set FALLBACK_OPENGL_DRIVER ${mesa} --set MUNIX_SYSTEMD_UNITS ${munixSystemd}
wrapProgram $out/bin/munix --prefix PATH : $out/bin
'';
}

View file

@ -1,12 +1,12 @@
{ muvm, libkrun, muvm-src, systemd, rustPlatform }:
{ muvm, libkrun, systemd, rustPlatform }:
(muvm.override {
libkrun = libkrun;
}).overrideAttrs (old: {
postPatch = ""; # no more sysctl; udevd now takes the var anyway; XXX: fex
MUVM_UDEVD_PATH = "${systemd}/lib/systemd/systemd-udevd";
src = muvm-src;
src = ../../muvm;
cargoDeps = rustPlatform.importCargoLock {
lockFile = "${muvm-src}/Cargo.lock";
lockFile = ../../muvm/Cargo.lock;
};
})

View file

@ -1,44 +0,0 @@
{
lib,
rustPlatform,
# fetchpatch,
fetchFromGitea,
pkg-config,
libdrm,
}:
# TODO: upstream
rustPlatform.buildRustPackage {
pname = "wl-cross-domain-proxy";
version = "0-unstable-2026-01-30";
src = fetchFromGitea {
domain = "codeberg.org";
owner = "drakulix";
repo = "wl-cross-domain-proxy";
rev = "c6ce1ca89fb4d6f4f18d3aaf88324d40d4589177";
hash = "sha256-ydyT4DFzWzhzOZR591UOgLjVQt/v6hRSNjzM3QtohlU=";
};
nativeBuildInputs = [ pkg-config ];
buildInputs = [ libdrm ];
cargoHash = "sha256-k3dmxIuCQoOrn/VwauTdzuRw/XKQB6LPLgO5ql0rE7E=";
cargoPatches = [
# (fetchpatch {
# name = "XXX.patch";
# url = "https://codeberg.org/drakulix/wl-cross-domain-proxy/pulls/XXX.patch";
# hash = lib.fakeHash;
# })
];
meta = {
homepage = "https://codeberg.org/drakulix/wl-cross-domain-proxy";
description = "Proxy for the wayland protocol across virtio-gpu cross-domain context";
mainProgram = "wl-cross-domain-proxy";
platforms = lib.platforms.linux;
license = [ lib.licenses.mit ];
maintainers = [ lib.maintainers.valpackett ];
};
}

42
radvmmio.patch Normal file
View file

@ -0,0 +1,42 @@
From c2fd030644cf2074f9d2ffd155839b2e943473d0 Mon Sep 17 00:00:00 2001
From: Val Packett <val@packett.cool>
Date: Wed, 10 Sep 2025 15:44:41 -0300
Subject: [PATCH] radv: detect platform:virtio-mmio devices for virtgpu native
context
VirtIO devices can be configured as platform devices instead of PCI,
which is especially common in microVM projects like libkrun.
Let's allow RADV to probe MMIO virtgpu devices.
Signed-off-by: Val Packett <val@invisiblethingslab.com>
---
src/amd/vulkan/radv_physical_device.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/amd/vulkan/radv_physical_device.c b/src/amd/vulkan/radv_physical_device.c
index 07b5b000b3ae..cd6f4c3488a1 100644
--- a/src/amd/vulkan/radv_physical_device.c
+++ b/src/amd/vulkan/radv_physical_device.c
@@ -2507,14 +2507,15 @@ create_drm_physical_device(struct vk_instance *vk_instance, struct _drmDevice *d
#ifndef _WIN32
bool supported_device = false;
- if (!(device->available_nodes & (1 << DRM_NODE_RENDER)) || device->bustype != DRM_BUS_PCI)
+ if (!(device->available_nodes & (1 << DRM_NODE_RENDER)))
return VK_ERROR_INCOMPATIBLE_DRIVER;
#ifdef HAVE_AMDGPU_VIRTIO
- supported_device |= device->deviceinfo.pci->vendor_id == VIRTGPU_PCI_VENDOR_ID;
+ supported_device |= device->bustype == DRM_BUS_PCI && device->deviceinfo.pci->vendor_id == VIRTGPU_PCI_VENDOR_ID;
+ supported_device |= device->bustype == DRM_BUS_PLATFORM; /* virtio-mmio */
#endif
- supported_device |= device->deviceinfo.pci->vendor_id == ATI_VENDOR_ID;
+ supported_device |= device->bustype == DRM_BUS_PCI && device->deviceinfo.pci->vendor_id == ATI_VENDOR_ID;
if (!supported_device)
return VK_ERROR_INCOMPATIBLE_DRIVER;
--
2.50.1

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,33 +0,0 @@
[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

@ -1,12 +0,0 @@
[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

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

View file

@ -1,11 +0,0 @@
[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

@ -1,13 +0,0 @@
[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

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

View file

@ -1,71 +0,0 @@
{
nixConfig = {
extra-substituters = [ "https://cache.clan.lol" ];
extra-trusted-public-keys = [
"cache.clan.lol-1:3KztgSAB5R1M+Dz7vzkBGzXdodizbgLXGXKXlcQLA28="
];
};
inputs = {
munix.url = "git+https://git.clan.lol/clan/munix?shallow=1&ref=main";
nixpkgs.follows = "munix/nixpkgs";
};
outputs =
{
nixpkgs,
munix,
...
}:
let
forAllSystems = nixpkgs.lib.genAttrs [
"x86_64-linux"
"aarch64-linux"
];
musictest-vm =
system:
nixpkgs.lib.nixosSystem {
modules = [
munix.nixosModules.default
(
{ pkgs, ... }:
{
system.stateVersion = "26.05";
virtualisation.munix.defaultCommand = "euphonica";
nixpkgs.hostPlatform = system;
programs.dconf.enable = true;
fonts.packages = with pkgs; [ adwaita-fonts ];
environment.systemPackages = with pkgs; [ euphonica ];
# Local background service as a demo that doesn't require network creds :)
services.mpd = {
enable = true;
startWhenNeeded = true;
musicDirectory = "/etc/demo-music";
user = "appvm";
group = "appvm";
settings.audio_output = [
{
type = "pipewire";
name = "Pipewire Output";
}
];
};
environment.etc."demo-music/0101GhostsI.ogg".source = pkgs.fetchurl {
# just a CC-BY-SA licensed example
url = "https://archive.org/download/NineInchNailsGhostsI-Iv24bit48khz/0101GhostsI.ogg";
sha256 = "0iijm1c191aqkxybl4a4gvlpnf72hk4896lwvp0xixkhds88qzxi";
};
}
)
];
};
in
{
packages = forAllSystems (system: {
default = (musictest-vm system).config.system.build.munix;
});
};
}