Compare commits

...

81 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
Jörg Thalheim
f295e7b32a README: document pipewire not working 2025-11-03 18:05:16 +01:00
Jörg Thalheim
181e675f02 munix: run realpath on nixos closure before running
this is more convinent when using `nix build`.
2025-11-03 17:57:14 +01:00
Jörg Thalheim
c32be2f8c8 README: add usage information 2025-11-03 17:56:25 +01:00
Jörg Thalheim
b70a166e8b add binary cache 2025-11-03 17:36:02 +01:00
Jörg Thalheim
505e85b9c5 flake.nix: extract packages/devshells/modules into smaller files 2025-11-03 17:22:41 +01:00
Jörg Thalheim
6f7f3f2461 Restrict flake to Linux systems and enable allowUnfree for nixosConfigurations
This project is Linux-specific (microVMs, libkrun, systemd, etc.), so
the flake now only exports packages and checks for x86_64-linux and
aarch64-linux systems. This prevents Darwin build failures.

Additionally, nixosConfigurations now set allowUnfree = true to allow
packages like zerotierone that have unfree licenses.
2025-11-03 16:24:04 +01:00
Jörg Thalheim
46edb4b7e9 Add flake checks for CI/CD validation
This adds a checks attribute to enable automated validation of all
build outputs. Checks are included for all packages, devShells, and
nixosConfigurations, with appropriate prefixes (package-, devShell-,
nixos-) for clarity and organization.
2025-11-03 15:45:54 +01:00
Jörg Thalheim
348cedab1a Refactor testvm into reusable nixosModule for multiple architectures
This extracts the testvm configuration into a shared nixosModules.testvm
module that can be reused across different architectures. The module is
now used by both testvm-x86_64 and testvm-aarch64 nixosConfigurations,
eliminating code duplication.

Additionally, nixosConfigurations have been moved to the top-level flake
outputs to follow Nix conventions, rather than being nested inside
eachDefaultSystem. The virtwl proxy reference now uses ${pkgs.system}
instead of hardcoded x86_64-linux to support both architectures.
2025-11-03 15:45:47 +01:00
Val Packett
790dd0d1f4 Integrate systemd
Not fully optimized yet, but shouldn't have any regressions
2025-10-31 04:53:19 -03:00
Val Packett
c4d462ea20 Update muvm 2025-10-03 01:37:22 -03:00
Val Packett
146430d8cd Add '--' arg separator 2025-09-26 06:09:41 -03:00
34 changed files with 1210 additions and 354 deletions

3
.gitignore vendored
View file

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

6
.gitmodules vendored
View file

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

View file

@ -2,23 +2,69 @@
WIP: A microVM runner for NixOS systems with desktop integration, powered by muvm/libkrun.
## Quick Start
**1. Build a test VM:**
```bash
nix build '.#nixosConfigurations.testvm-x86_64.config.system.build.toplevel' -o testvm
```
**2. Run the VM:**
```bash
nix run '.#munix' -- testvm
```
This will start an interactive bash session inside the microVM.
**Run a specific command:**
```bash
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)
- `--gid GID`, `-g GID` - Set microVM GID (default: 1337)
- `--no-gpu` - Disable GPU acceleration
- `--no-wayland` - Disable Wayland support
- `--no-pipewire` - Disable PipeWire audio
- `--x11` - Enable X11 support
- `--bind SRC DST` - Bind mount SRC to DST in the VM
- `--ro-bind SRC DST` - Read-only bind mount
- `--expose PATH` - Expose PATH in the VM at the same location
- `--ro-expose PATH` - Expose PATH read-only
Example with options:
```bash
nix run '.#munix' -- --no-gpu --ro-expose /home/user/data testvm htop
```
## Development
Building an example closure:
```
nix build '.#nixosConfigurations.x86_64-linux.testvm.config.system.build.toplevel'
```
Running the nix build:
```
nix run '.#packages.x86_64-linux.munix' $(readlink result)
```
Working on muvm & munix locally (not built into the nix store):
```
```bash
cd muvm && cargo build --locked --release
PATH=$PWD/muvm/target/release:$PATH ./munix $(readlink result)
PATH=$PWD/muvm/target/release:$PATH ./munix testvm
```
## Requirements
- Linux system with KVM support (`/dev/kvm`)
- For GPU acceleration: Kernel 6.13+ with compatible drivers (amdgpu, msm)
- For Wayland: `XDG_RUNTIME_DIR` and `WAYLAND_DISPLAY` set
## Known Issues

49
devShells/default.nix Normal file
View file

@ -0,0 +1,49 @@
{
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 {
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
}

165
flake.lock generated
View file

@ -1,120 +1,133 @@
{
"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": 1758078198,
"narHash": "sha256-60ojlXp42UZYTOAgsiKPZUab1Aa8pXTIo+rz0zoaynI=",
"ref": "val/tsvwswkqrrsr",
"rev": "1f0fc70eb049852a18a5203af2204bd9f5729f29",
"shallow": true,
"submodules": true,
"type": "git",
"url": "https://github.com/valpackett/nixpkgs"
"lastModified": 1770197578,
"narHash": "sha256-AYqlWrX09+HvGs8zM6ebZ1pwUqjkfpnv8mewYwAo+iM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "00c21e4c93d963c50d4c0c89bfa84ed6e0694df2",
"type": "github"
},
"original": {
"ref": "val/tsvwswkqrrsr",
"shallow": true,
"submodules": true,
"type": "git",
"url": "https://github.com/valpackett/nixpkgs"
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"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"
}
}
},

281
flake.nix
View file

@ -1,167 +1,146 @@
{
inputs = {
self.submodules = true;
# nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
# https://github.com/NixOS/nixpkgs/pull/444133
nixpkgs.url = "git+https://github.com/valpackett/nixpkgs?shallow=1&submodules=1&ref=val/tsvwswkqrrsr";
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";
nixConfig = {
extra-substituters = [ "https://cache.clan.lol" ];
extra-trusted-public-keys = [ "cache.clan.lol-1:3KztgSAB5R1M+Dz7vzkBGzXdodizbgLXGXKXlcQLA28=" ];
};
outputs = {self, nixpkgs, flake-utils, virtwl, ...}:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
};
in {
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
packages.libkrunfw = pkgs.libkrunfw;
# packages.libkrunfw = (pkgs.libkrunfw.overrideAttrs {
# version = "4.10.0";
# src = pkgs.fetchFromGitHub {
# owner = "containers";
# repo = "libkrunfw";
# tag = "v4.10.0";
# hash = "sha256-mq2gw0+xL6qUZE/fk0vLT3PEpzPV8p+iwRFJHXVOMnk=";
# };
# kernelSrc = pkgs.fetchurl {
# url = "mirror://kernel/linux/kernel/v6.x/linux-6.12.34.tar.xz";
# hash = "sha256-p/P+OB9n7KQXLptj77YaFL1/nhJ44DYD0P9ak/Jwwk0=";
# };
# cargoHash = "";
# });
flake-parts.url = "github:hercules-ci/flake-parts";
flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs";
packages.libkrun = (pkgs.libkrun.override {
withBlk = true;
withGpu = true;
withSound = true;
withNet = true;
libkrunfw = self.packages.${system}.libkrunfw;
}).overrideAttrs (old: {
src = ./libkrun;
cargoDeps = pkgs.rustPlatform.importCargoLock {
lockFile = ./libkrun/Cargo.lock;
};
# mesonFlags = [ (pkgs.lib.mesonOption "decoders" "gles,vulkan,composer") ]; # no magma(?)
});
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";
packages.muvm = (pkgs.muvm.override {
libkrun = self.packages.${system}.libkrun;
}).overrideAttrs (old: {
postPatch = ""; # no more sysctl; udevd now takes the var anyway; XXX: fex
MUVM_UDEVD_PATH = "${pkgs.systemd}/lib/systemd/systemd-udevd";
src = ./muvm;
cargoDeps = pkgs.rustPlatform.importCargoLock {
lockFile = ./muvm/Cargo.lock;
};
});
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";
packages.munix = let
munixScript = (pkgs.writeScriptBin "munix" (builtins.readFile ./munix)).overrideAttrs(old: {
buildCommand = "${old.buildCommand}\n patchShebangs $out";
});
munixInitRootHook = (pkgs.writeScriptBin "munix-init-root" (builtins.readFile ./munix-init-root)).overrideAttrs(old: {
buildCommand = "${old.buildCommand}\n patchShebangs $out";
});
munixInitUserHook = (pkgs.writeScriptBin "munix-init-user" (builtins.readFile ./munix-init-user)).overrideAttrs(old: {
buildCommand = "${old.buildCommand}\n patchShebangs $out";
});
in pkgs.symlinkJoin {
name = "munix";
paths = [ munixScript munixInitRootHook munixInitUserHook self.packages.${system}.muvm pkgs.passt pkgs.bubblewrap ];
buildInputs = [ pkgs.makeWrapper ];
postBuild = ''
wrapProgram $out/bin/munix --prefix PATH : $out/bin
'';
# 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 =
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;
};
devShells.default = let
projects = with self.packages.${system}; [ libkrun muvm ];
in pkgs.mkShell {
MUVM_UDEVD_PATH = "${pkgs.systemd}/lib/systemd/systemd-udevd";
nativeBuildInputs = pkgs.lib.concatMap (pkg: pkg.nativeBuildInputs) projects;
buildInputs = (pkgs.lib.concatMap (pkg: pkg.buildInputs) projects) ++ (with self.packages.${system}; [
# virglrenderer
]) ++ (with pkgs; [
cargo
rust-analyzer
rustfmt
passt
bubblewrap
]);
templates.musictest = {
description = "Music player demo VM with MPD and Euphonica";
path = ./templates/musictest;
};
packages.mesa = (pkgs.mesa.override {
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 ++ [ (pkgs.lib.mesonBool "amdgpu-virtio" true) ];
patches = old.patches ++ [ ./radvmmio.patch ]; # already merged to git
});
nixosConfigurations.testvm = nixpkgs.lib.nixosSystem {
inherit system;
nixosConfigurations.testvm-x86_64 = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
{
system.stateVersion = "25.11";
fileSystems."/".device = pkgs.lib.mkDefault "/dev/sda";
boot.isContainer = true;
users.mutableUsers = false;
users.users.appvm = {
uid = 1337;
isNormalUser = true;
home = "/home/appvm";
description = "microVM User";
extraGroups = [ "wheel" "video" "input" ];
};
users.groups.appvm.gid = 1337;
users.allowNoPasswordLogin = true;
hardware.graphics.enable = true;
hardware.graphics.package = self.packages.${system}.mesa;
system.replaceDependencies.replacements = [
{
original = pkgs.mesa;
replacement = self.packages.${system}.mesa;
}
];
environment.systemPackages = [
pkgs.fastfetch
pkgs.htop
pkgs.radeontop
virtwl.packages.x86_64-linux.proxy
pkgs.wayland-utils
pkgs.weston
pkgs.waycheck
pkgs.vulkan-tools
pkgs.glxinfo
pkgs.glmark2
pkgs.mesa-demos
pkgs.xorg.xeyes
pkgs.xterm
pkgs.vkquake
pkgs.kdePackages.kate
pkgs.adwaita-fonts
pkgs.adwaita-icon-theme
pkgs.gnome-text-editor
pkgs.firefox
pkgs.ffmpeg-full
pkgs.mpv
pkgs.libva-utils
];
}
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;
};
};
};
}

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

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())
}

184
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,20 @@ 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;;
*)
if [ "$MICROVM_CLOSURE" = "" ]; then
@ -47,8 +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
@ -69,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;;
@ -84,9 +144,6 @@ if [ "$GPU" -eq 1 ]; then
GPU_MODE=software
fi
MUVM_ARGS+=("--gpu-mode=$GPU_MODE")
if [ "$GPU_MODE" = "venus" ]; then
MUVM_ARGS+=("-e" "MESA_LOADER_DRIVER_OVERRIDE=zink")
fi
else
BWRAP_ARGS+=("--dir" "/dev/dri")
MUVM_ARGS+=("--gpu-mode=software")
@ -101,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
@ -119,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
@ -130,37 +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 "$MICROVM_CLOSURE/sw/bin/env" /usr/bin/env \
--ro-bind "$SCRIPT_PATH/munix-init-root" /usr/bin/munix-init-root \
--ro-bind "$SCRIPT_PATH/munix-init-user" /usr/bin/munix-init-user \
--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 \
--ro-bind /nix/store /nix/store \
--ro-bind /run/systemd/resolve /run/systemd/resolve \
--ro-bind /etc/resolv.conf /etc/resolv.conf \
--file 11 /etc/passwd \
--file 12 /etc/group \
--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 \
-x /usr/bin/munix-init-root -X /usr/bin/munix-init-user --udevd-path="$MICROVM_CLOSURE/sw/bin/true" \
--custom-init-cmdline "/opt/bin/micro-activate $MICROVM_CLOSURE/sw/sbin/init --log-target=console" \
"${MUVM_ARGS[@]}" \
-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
)
-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

View file

@ -1,27 +0,0 @@
#!/usr/bin/env bash
unset LANGUAGE LC_ALL LC_CTYPE LC_NUMERIC LC_COLLATE LC_TIME LC_MESSAGES LC_MONETARY LC_ADDRESS LC_IDENTIFICATION LC_MEASUREMENT LC_PAPER LC_TELEPHONE LC_NAME LANG 2>/dev/null # perl, calm down
PATH=$MICROVM_CLOSURE/sw/bin
echo "=> Initializing microVM environment"
mkdir /run/log
# Prepare mounts for paths that activation writes to
mount -t tmpfs tmpfs /etc
cp /run/muvm-host/etc/resolv.conf /etc/resolv.conf
mount -t tmpfs tmpfs /usr/bin
cp /run/muvm-host/usr/bin/munix-init-user /usr/bin/munix-init-user
mount -t tmpfs tmpfs /dev/shm # tries to remount, fails on virtiofs due to unexpected opts
$MICROVM_CLOSURE/activate >/run/log/activate.spam 2>&1
umount /dev/shm # restore the original virtiofs dax shm though
chown $MICROVM_UID:$MICROVM_GID /run /dev # avoid "Detected unsafe path transition"
systemd-tmpfiles --create >/run/log/tmpfiles.spam 2>&1
systemd-machine-id-setup >/dev/null 2>&1
$MICROVM_CLOSURE/sw/lib/systemd/systemd-udevd >/run/log/udevd.spam 2>&1 &
echo "=> microVM environment ready!"

View file

@ -1,4 +0,0 @@
#!/usr/bin/env bash
PATH=$MICROVM_CLOSURE/sw/bin
mkdir -p ~/.var/log
wayland-proxy-virtwl --virtio-gpu >~/.var/log/wayland-proxy.log 2>&1 &

1
muvm

@ -1 +0,0 @@
Subproject commit 53d416fb3f96b04a5bd3dd40f94fa4d8fbf1d7f1

204
nixosModules/default.nix Normal file
View file

@ -0,0 +1,204 @@
{
self,
}:
{
pkgs,
lib,
utils,
config,
...
}:
let
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.";
};
config = {
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;
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"
];
};
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}
'';
};
};
}

44
nixosModules/testvm.nix Normal file
View file

@ -0,0 +1,44 @@
{ }:
{ pkgs, ... }: {
system.stateVersion = "26.05";
fonts.packages = [ pkgs.adwaita-fonts pkgs.dejavu_fonts ];
programs.dconf.enable = true;
environment.systemPackages = [
pkgs.fastfetch
pkgs.htop
pkgs.wayland-utils
pkgs.weston
pkgs.waycheck
pkgs.vulkan-tools
pkgs.glmark2
pkgs.mesa-demos
pkgs.xorg.xeyes
pkgs.xterm
pkgs.vkquake
# pkgs.veloren
pkgs.kdePackages.kate
pkgs.adwaita-icon-theme
pkgs.amberol
pkgs.bustle
pkgs.d-spy
pkgs.gnome-text-editor
pkgs.firefox
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

@ -0,0 +1,32 @@
{
libkrun,
libkrunfw,
libkrun-src,
rustPlatform,
libcap_ng,
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;
cargoDeps = rustPlatform.importCargoLock {
lockFile = "${libkrun-src}/Cargo.lock";
};
buildInputs = old.buildInputs ++ [ libcap_ng ]; # new dep
})

View file

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

10
packages/mesa/default.nix Normal file
View file

@ -0,0 +1,10 @@
{ mesa, lib, stdenv }:
(mesa.override {
# nothing currently
}).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 ++ [ ];
})

View file

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

12
packages/muvm/default.nix Normal file
View file

@ -0,0 +1,12 @@
{ 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;
cargoDeps = rustPlatform.importCargoLock {
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;
});
};
}