Compare commits

...
Sign in to create a new pull request.

70 commits

Author SHA1 Message Date
Val Packett
a7fe813f17 Add wl-backdrop (WIP) --bg-color option 2026-03-06 06:45:39 -03:00
Val Packett
38a96b79b3 [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..)
2026-03-06 06:09:48 -03:00
Val Packett
604ebc1356 [BREAKING] Switch to virtgpu-based D-Bus tunneling
This introduces support for drag&drop and printing portals, and later
camera/screencasting should be possible as well. However we break
backwards compatibility with already built closures because the
nixosModule needs to be changed.

In the next commit, the runtime environment related services will be
removed from the nixosModule to prevent unnecessary future breakage.
2026-03-06 05:09:55 -03:00
Val Packett
e00609ce73 nix: add some camera/pw testing tools to testvm 2026-02-27 02:50:54 -03:00
Val Packett
beeb5f5d9f Fork libkrun again to fix buffer mapping permission issue (fix #18)
Initial D-Bus WIP is also there..
2026-02-27 02:48:17 -03:00
zimbatm
adf2d24b60 fix(template): use structured settings for MPD audio output
Replace deprecated extraConfig with settings.audio_output
2026-02-24 11:08:53 +01:00
Val Packett
981393443b Set up a monotonic clock boot time offset in the VM 2026-02-20 05:17:13 -03:00
Val Packett
8c3878f117 Pass timezone through to the VM
What a way to discover that this was missing- PipeWire camera was
freezing unless something on the host was already streaming it…

gstclock.c:1086:gst_clock_get_internal_time:<pipewireclock0> internal time 1:01:04.622699903
gstclock.c:1129:gst_clock_get_time:<pipewireclock0> adjusted time 5123776:20:12.866176008

Well, that wasn't even caused by the TZ but it made me think to fix it..

NOTE for local dev, rebuild micro-activate now
2026-02-20 04:19:18 -03:00
Val Packett
322b6efc1c nix: testvm: update stateVersion, add rewaita (theme coloring for demos) 2026-02-13 14:55:42 -03:00
Val Packett
820fcd3da1 nix: chase removal of etc overlay symlinks, fixes #14 2026-02-13 14:55:09 -03:00
Val Packett
53f827553f nix: add nautilus to testvm
Will be useful for testing drag-out
2026-02-07 01:24:55 -03:00
Val Packett
09f1eb76de nix: comment out libkrun(fw) source overrides
As we're currently using entirely upstream ones right now. Let's reuse
the cache.nixos.org builds.
2026-02-07 01:24:44 -03:00
Val Packett
9708e5fb64 nix: update nixpkgs
now includes the most recent libkrun(fw) releases
2026-02-07 00:21:19 -03:00
Val Packett
67666b8f46 nix: update sidebus and flake-parts
sidebus now uses flake-parts too
2026-02-07 00:17:26 -03:00
Val Packett
585970fe92 munix: do not set display from the script
It is now entirely managed by systemd.
2026-01-30 06:12:35 -03:00
Val Packett
a82b0de310 munix: set wayland/pw/portal enablement variables for toolkits by default 2026-01-30 06:10:48 -03:00
Val Packett
8113c269cb munix: add more arg shorthands 2026-01-30 05:47:22 -03:00
Val Packett
38213f4ada munix: add publish argument for port forwarding, fix #11
TODO: fix https://github.com/AsahiLinux/muvm/issues/178 as well
2026-01-30 05:44:13 -03:00
Val Packett
425ff896c8 nix: deorbit virtwl-proxy 2026-01-30 04:35:20 -03:00
Val Packett
9911b15fb7 nix: switch to wl-cross-domain-proxy 2026-01-30 04:27:22 -03:00
Val Packett
13b2afcc14 nix: add package for wl-cross-domain-proxy 2026-01-30 03:16:39 -03:00
Val Packett
c89c9e4d9b nix: testvm: more test apps 2026-01-30 03:15:49 -03:00
Val Packett
40d82a0fd8 nix: use static userborn
as it's upstream now
2026-01-30 01:39:17 -03:00
Val Packett
c11c6c6292 nix: update inputs 2026-01-30 00:02:31 -03:00
Val Packett
447ccad362 nix: devShell: add packages for proxy development 2026-01-29 20:54:07 -03:00
Jörg Thalheim
2d7860294d switch from flake-utils to flake-parts
there is not so much of a difference in this project, but
it means adding this project to clan, one less flake input
and also more consistency with other projects in clan.
2026-01-16 15:13:00 +01:00
Jörg Thalheim
06a26e7dee munix: add -- before command args to prevent option parsing
muvm was interpreting command arguments like '-c' (from /bin/sh -c)
as its own options. Adding '--' separates muvm options from the
command and its arguments.
2026-01-15 17:10:05 +01:00
Val Packett
214d4c4500 nixos: set shell for appvm user
Surprisingly, gdb of all things uses the login shell from /etc/passwd to
launch the program under test, so I was seeing the
"This account is currently not available" message there.
2026-01-09 06:46:59 -03:00
Val Packett
612453a3bc Update sidebus 2025-12-18 04:29:48 -03:00
Val Packett
787ca12b1e Start sidebus-agent strictly before the apps
Gtk uses G_DBUS_CALL_FLAGS_NO_AUTO_START in all the (early) portal calls
so we basically get a version of https://gitlab.gnome.org/GNOME/gtk/-/issues/7379
if we try to lazily start the agent..
2025-12-18 04:24:17 -03:00
Val Packett
f336a0d5ff micro-activate: generate machine-id randomly
D-Bus is supposed to (?) use it to decide whether it can use FD passing,
shared memory, etc. and while we do a lot of cross-domain magic it's not
quite seamless :) so let's not reuse the host one.
2025-12-18 04:21:52 -03:00
Jörg Thalheim
1d864e0ded template: simplify with forAllSystems and inline module
Use the forAllSystems pattern to reduce duplication and inline the
NixOS module directly in nixosSystem call. This eliminates the need for
separate nixosModules and nixosConfigurations outputs, making the
template more concise and easier to understand.

Also use virtualisation.munix namespace for the option.
2025-12-15 16:48:58 +01:00
Jörg Thalheim
ced0559be8 nixos: add munix.defaultCommand option and system.build.munix
Add a NixOS option to configure the default command for the VM and
provide a system.build.munix output that wraps munix with the correct
toplevel and default command. This reduces boilerplate in downstream
flakes since they no longer need to manually wrap munix.

The template now uses these new features, significantly simplifying
the apps definition.
2025-12-15 16:24:56 +01:00
Jörg Thalheim
2d721419e6 readme: move example to flake template
Replace inline flake.nix example with a proper flake template that users
can instantiate with `nix flake init`. This makes it easier to get
started and we can easier test the example.
2025-12-15 16:12:56 +01:00
Val Packett
fb53769c7a readme: add basic initial example of defining a vm (fix #8)
Starting point to simplify
2025-12-09 06:45:30 -03:00
Val Packett
ab5f412524 Add dconf to testvm
Pretty much required by GTK apps
2025-12-09 06:41:09 -03:00
Val Packett
20e7e88e07 Update sidebus (now reverse-client to a session bus in the guest) 2025-12-09 06:41:01 -03:00
Val Packett
bfb8352c55 nix: update sidebus (unhardcode user/group IDs) 2025-12-05 05:00:27 -03:00
Val Packett
cc135479ef systemd: chown XDG_RUNTIME_DIR again
This was temporarily gone due to refactorings (throwing out tmpfiles).
2025-12-05 04:09:40 -03:00
Val Packett
bfc037e615 systemd: hoist XDG_RUNTIME_DIR up to manager defaults
In some cases it might be needed in arbitrary services.. such as mpd
needing PipeWire to play sound into
2025-12-05 04:08:46 -03:00
Val Packett
73d2501781 systemd: use sockets.target
This allows using normal nix-managed services that register sockets for
lazy activation. Also brings in some red errors about starting services
that we don't have, but that will be fixed
2025-12-05 04:07:04 -03:00
Val Packett
fd771dd95f munix: make the default command overridable via env
This is convenient for app launch wrappers that would want to set their
own default command but still allow overriding it
2025-12-05 04:06:05 -03:00
Val Packett
2a98ae83af nix: update libkrun (aarch64 fixes) 2025-12-05 00:31:09 -03:00
Val Packett
d2070a1bec nix: set system.switch.enable = false
Will be required by the upstreamed static userborn, and shouldn've been
part of the initial research anyway :)
2025-12-05 00:09:03 -03:00
Val Packett
5f1783b9bb nix: update flake comment to mention --override-input 2025-12-05 00:08:30 -03:00
Jörg Thalheim
c7ec9872ca fix: disable systemd-resolved (not needed, DNS comes from host via passt) 2025-12-04 23:59:42 -03:00
Jörg Thalheim
57b8ae3424 switch from git submodules to flake inputs 2025-12-04 23:59:33 -03:00
Val Packett
6e8e4b9fda nix: replace pkgs.system with pkgs.stdenv.hostPlatform.system
evaluation warning: ‘system’ has been renamed to/replaced by ‘stdenv.hostPlatform.system’
2025-12-04 23:52:14 -03:00
Val Packett
7aa9f614da systemd: get udevd out of the critical chain
We don't need to wait for it at all
2025-12-04 23:48:17 -03:00
Val Packett
9a6cf18cc1 micro-activate: x86_64 type fix 2025-12-04 07:24:41 -03:00
Val Packett
0bd986f97f Introduce micro-activate (RIIR activate script + tiny bit of tmpfiles)
Instead of interpreting all that shell and running actual tmpfiles, use
a tiny stage before systemd that mounts a tmpfs at /run (preventing
systemd from doing the same), populates it with NixOS symlinks and
preserved resolv.conf, and mounts the immutable /etc overlay before
passing control over to systemd.
2025-12-04 07:17:31 -03:00
Val Packett
3d2f6c4732 systemd: disable generate-shutdown-ramfs 2025-12-04 06:59:40 -03:00
Val Packett
a93ab32aea Pass MESA_LOADER_DRIVER_OVERRIDE to muvm-remote
The Zink override for Venus in muvm was being eaten by systemd.. until now
2025-11-28 03:55:50 -03:00
Val Packett
f831c9d958 Update nixpkgs / dependencies (mesa-25.3.0)
Almost no need to touch mesa anymore.. except for the radeon virtio flag
2025-11-28 02:13:20 -03:00
Val Packett
9b23ae8094 gitignore testvm 2025-11-28 02:13:20 -03:00
Val Packett
93ba3d8fc4 Switch from sysusers to new "baked" userborn usage
Avoid spending ~100ms of userspace boot time on unnecessary regeneration
of entirely static /etc/{passwd,group,shadow}. This will be proposed to nixpkgs.
2025-11-28 02:13:20 -03:00
Val Packett
bd6307ded5 Symlink resolv.conf and machine-id instead of mounting
Works fine after all. Let's avoid mount calls to boot faster.
2025-11-28 02:13:20 -03:00
Val Packett
83db4d6074 Remove leftover debug env invocation 2025-11-28 02:13:20 -03:00
Val Packett
87691a5747 Do not wait for udev to settle
Relying on udev to assign permissions to the virtgpu was very "proper"
but really excessive. We have a simple static configuration, built into
the kernel, so there are no dynamic shenanigans, we can always reliably
just chmod/chown the permissions. Let's go back to faster booting.
2025-11-28 02:13:20 -03:00
Val Packett
73fdfca030 Update muvm fork (proper pwbridge fix) 2025-11-28 02:13:20 -03:00
Val Packett
23b91f1d6d Update libkrun and libkrunfw to latest upstream (merged MS_SHARED and EROFS) 2025-11-28 02:13:20 -03:00
Val Packett
59137223a9 Add fallback to our mesa package for non-NixOS hosts 2025-11-28 02:13:20 -03:00
Val Packett
c164db06c1 Fix msm driver name to msm_dpu 2025-11-27 21:58:53 -03:00
Val Packett
77028b04da Add error messages for missing muvm/passt 2025-11-27 20:33:30 -03:00
Val Packett
9f0f835fce Add sidebus integration 2025-11-14 06:20:44 -03:00
Val Packett
030503f23b Unhardcode x86_64 in virtwl package 2025-11-14 06:17:34 -03:00
Val Packett
8d2596d7cc testvm: add a couple more test tools 2025-11-14 02:11:11 -03:00
Val Packett
8d178b21ef Temporarily fork libkrun to add systemd mount propagation fix 2025-11-14 02:10:19 -03:00
Val Packett
d8d531aa00 Fix tmpfiles entry for runtime dir 2025-11-14 02:09:04 -03:00
Val Packett
7c0825b46c Update muvm with PipeWire fix
See https://github.com/AsahiLinux/muvm/pull/204
2025-11-07 06:11:10 -03:00
33 changed files with 1074 additions and 482 deletions

3
.gitignore vendored
View file

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

9
.gitmodules vendored
View file

@ -1,9 +0,0 @@
[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,6 +21,19 @@ 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)
@ -55,5 +68,3 @@ 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,16 +1,49 @@
{ mkShell, lib, systemd, cargo, rust-analyzer, rustfmt, passt, bubblewrap, libkrun, muvm }:
{
mkShell,
lib,
systemd,
cargo,
rust-analyzer,
rustfmt,
passt,
bubblewrap,
libkrun,
muvm,
sidebus-broker,
wl-cross-domain-proxy,
wl-backdrop,
pkgs,
}:
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
];
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
}

148
flake.lock generated
View file

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

200
flake.nix
View file

@ -5,76 +5,142 @@
};
inputs = {
self.submodules = true;
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
# 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";
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;
# };
};
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; }
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"
];
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 +0,0 @@
Subproject commit 72b8b0870c3ecad0388f52150a68f79083fcc86f

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

155
micro-activate.rs Normal file
View file

@ -0,0 +1,155 @@
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,19 +2,46 @@
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;;
@ -22,15 +49,19 @@ 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;;
--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;;
-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;;
--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;;
*)
@ -48,11 +79,36 @@ 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=("bash")
MICROVM_COMMAND=("$MICROVM_DEFAULT_COMMAND")
fi
if [ "$GPU" -eq 1 ]; then
@ -73,7 +129,7 @@ if [ "$GPU" -eq 1 ]; then
driver_mod="$(readlink "$driver_link")"
driver_name="${driver_mod##*/}"
case "$driver_name" in
amdgpu|msm) # TODO: i915
amdgpu|msm_dpu) # TODO: i915
echo "munix: ${card##*/} gpu driver is '$driver_name', using vdrm" >&2;
GPU_MODE=drm
break;;
@ -102,7 +158,15 @@ if [ "$WAYLAND" -eq 1 ]; then
"--bind" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY"
"--setenv" "WAYLAND_DISPLAY" "$WAYLAND_DISPLAY"
)
MUVM_ARGS+=("-e" "WAYLAND_DISPLAY=wayland-1") # the proxy is managed by us, not muvm
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
fi
if [ "$PIPEWIRE" -eq 1 ]; then
@ -120,6 +184,11 @@ 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
@ -131,43 +200,87 @@ else
unset DISPLAY XAUTHORITY
fi
exec bwrap --unshare-all --share-net \
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 \
--uid $MICROVM_UID --gid $MICROVM_GID \
--tmpfs / \
--dir /run --dir /var --symlink /run /var/run --dir /tmp \
--dir /run --dir /var --symlink /run /var/run --dir /tmp --dir /mnt --dir /bin --dir /usr/bin \
--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 \
--symlink "$MICROVM_CLOSURE/etc/systemd" /etc/systemd \
--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 \
--ro-bind /nix/store /nix/store \
--ro-bind /run/systemd/resolve /run/systemd/resolve \
--file 11 /etc/passwd \
--file 12 /etc/group \
--file 13 /etc/resolv.conf \
--file 13 /run/resolv.conf \
--file 14 /run/localtime \
--dir "$XDG_RUNTIME_DIR" \
--setenv PATH "/run/munix/muvm:/run/munix/passt:$MICROVM_CLOSURE/sw/bin" \
"${BWRAP_ARGS[@]}" \
muvm \
--custom-init-cmdline "$MICROVM_CLOSURE/sw/sbin/init --log-target=console" \
--custom-init-cmdline "/opt/bin/micro-activate $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" \
-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
-e BOOT_TIME_OFFSET="$BOOT_TIME_OFFSET" \
-i -t -- "${MICROVM_COMMAND[@]}" \
13< /etc/resolv.conf \
14< /etc/localtime

1
muvm

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

View file

@ -1,209 +1,204 @@
{ self, virtwl }:
{ pkgs, lib, utils, config, ... }: let
useTTY = {
TTYPath = "/dev/hvc0";
StandardOutput = "tty";
StandardInput = "tty";
StandardError = "tty";
};
{
self,
}:
{
pkgs,
lib,
utils,
config,
...
}:
let
runtimeDir = "/run/vm-user";
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
'';
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.";
};
# 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 -"];
config = {
boot.isContainer = true;
fileSystems."/".device = lib.mkDefault "/dev/sda"; # dummy
# Configure services
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.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";
# 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;
};
};
systemd.services.muvm-pwbridge = {
enable = true;
description = "PipeWire cross-domain proxy";
requires = ["muvm-pwbridge.socket"];
serviceConfig.Type = "exec";
serviceConfig.ExecStart = "/opt/bin/muvm-pwbridge";
};
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;
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"
# 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.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";
};
};
# 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"
];
};
};
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";
};
};
users.groups.appvm.gid = 1337;
users.allowNoPasswordLogin = true;
hardware.graphics.enable = true;
hardware.graphics.package = self.packages.${pkgs.system}.mesa;
# 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}
'';
};
};
}

View file

@ -1,12 +1,12 @@
{ virtwl }:
{ }:
{ pkgs, ... }: {
system.stateVersion = "25.11";
system.stateVersion = "26.05";
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 # build broken: Program 'spirv-remap' not found
pkgs.veloren
pkgs.vkquake
# pkgs.veloren
pkgs.kdePackages.kate
pkgs.adwaita-icon-theme
pkgs.amberol
@ -27,8 +27,18 @@
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,19 +1,32 @@
{ libkrun, libkrunfw, rustPlatform, variant ? null, ... }:
{
libkrun,
libkrunfw,
libkrun-src,
rustPlatform,
libcap_ng,
variant ? null,
...
}:
let
libkrunfw' = libkrunfw.override { inherit variant; };
in
(libkrun.override {
withBlk = true;
withGpu = true;
withSound = true;
withNet = true;
inherit variant;
libkrunfw = libkrunfw';
}).overrideAttrs (old: {
src = ../../libkrun;
cargoDeps = rustPlatform.importCargoLock {
lockFile = ../../libkrun/Cargo.lock;
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';
};
# mesonFlags = [ (lib.mesonOption "decoders" "gles,vulkan,composer") ]; # no magma(?)
in
# libkrun'
libkrun'.overrideAttrs (old: {
src = libkrun-src;
cargoDeps = rustPlatform.importCargoLock {
lockFile = "${libkrun-src}/Cargo.lock";
};
buildInputs = old.buildInputs ++ [ libcap_ng ]; # new dep
})

View file

@ -1,19 +1,22 @@
{ libkrunfw, fetchurl, variant ? null, ... }:
{
libkrunfw,
# libkrunfw-src,
# fetchurl,
variant ? null,
...
}:
(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=";
let
libkrunfw' = libkrunfw.override {
inherit variant;
};
# buildInputs = old.buildInputs;
})
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=";
# };
# })

View file

@ -1,16 +1,10 @@
{ mesa, lib }:
{ mesa, lib, stdenv }:
(mesa.override {
vulkanDrivers = [
"amd"
"intel"
"microsoft-experimental" # removing this breaks the build
"nouveau"
"swrast"
"virtio"
"gfxstream" # probably not going to use this though
];
# nothing currently
}).overrideAttrs (new: old: {
mesonFlags = old.mesonFlags ++ [ (lib.mesonBool "amdgpu-virtio" true) ];
patches = old.patches ++ [ ../../radvmmio.patch ]; # already merged to git
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 ++ [ ];
})

View file

@ -1,14 +1,36 @@
{ writeScriptBin, symlinkJoin, makeWrapper, muvm, passt, bubblewrap }:
{ stdenv, writeScriptBin, symlinkJoin, makeWrapper, muvm, passt, bubblewrap, sidebus-broker, wl-cross-domain-proxy, wl-backdrop, 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;
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 muvm passt bubblewrap ];
paths = [ munixScript microActivate muvm passt bubblewrap sidebus-broker wl-cross-domain-proxy wl-backdrop ];
buildInputs = [ makeWrapper ];
postBuild = ''
wrapProgram $out/bin/munix --prefix PATH : $out/bin
wrapProgram $out/bin/munix --prefix PATH : $out/bin --set FALLBACK_OPENGL_DRIVER ${mesa} --set MUNIX_SYSTEMD_UNITS ${munixSystemd}
'';
}

View file

@ -1,12 +1,12 @@
{ muvm, libkrun, systemd, rustPlatform }:
{ muvm, libkrun, muvm-src, 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 = muvm-src;
cargoDeps = rustPlatform.importCargoLock {
lockFile = ../../muvm/Cargo.lock;
lockFile = "${muvm-src}/Cargo.lock";
};
})

View file

@ -0,0 +1,44 @@
{
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 ];
};
}

View file

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

@ -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

View file

@ -0,0 +1,71 @@
{
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;
});
};
}