Idea is to be able to easy declare persistent NixOS VM's which are also perfectly safe if they get deleted for isolation purposes.
Quickstory:
The root originated from me wishing to isolate some potential bloatware like Spotify desktop app when having requirements as follows: use only Windows as host, be easy to [re-]install, look like as much seamlessly to host as possible. Nested VM sounded like too much messing around, Windows Sandbox is disposable by design so that I had to relogin everty reboot which is inconvenient. I reluctantly continued using Spotify in browser.
Once upon a time, not so late after that idea came to my mind, I was doomscrolling GitHub and starring cool repos as usual, all went well, and here was no sign of trouble ahead until I got to see nixos-generators...
That looked inspiring and challenging - and felt really good! - which is rare for me due to free time issues. So, my tiny bit of experience in Nix(OS), some freshy motivation, and it all start interfering, that turned into spotibox.
Name Qubix is a Qubes OS + Nix which is so obvious but fancy that I couldn't resist naming it like this, although the project neither affiliated nor tries to compete with Qubes OS in any way. More like, it only serves me as a fun thingy to try and it's been choosen after the mind lightning effect after I dug into Qubes OS compartmentalization philosophy. Might call it a tribute if you want. And unfortunately it looks like the only way QOS intended to work is to have it as your bare-metal host.
Qubix is a small declarative appliance factory for Windows-hosted, Hyper-V-based
NixOS VMs. The MVP builds one appliance, spotibox, as a minimal Spotify VM with
Openbox, xrdp and the PulseAudio audio path that worked in the prototype.
The goal is a fast local loop:
Nix modules
-> nixos-generators Hyper-V VHDX
-> PowerShell recreates the Hyper-V VM
-> mstsc connects to the appliance
- A modular NixOS configuration for
spotibox. - A Hyper-V Generation 2 VHDX built through
nixos-generators. - A Nix-generated JSON manifest consumed by PowerShell.
- A Windows PowerShell controller,
tools/qubixctl.ps1. - A stable xrdp audio baseline using PulseAudio, not PipeWire.
- Separate
userandrdpaccounts to avoid session cross-contamination. - A basic NixOS smoke test for users, xrdp, Spotify, Openbox and Avahi.
flake.nix
machines/
spotibox.nix
spotibox-debug.nix
profiles/
audio/pulseaudio-xrdp.nix
apps/spotify.nix
gui/openbox.nix
kernel/default.nix
modes/debug.nix
modes/prod.nix
security/minimal.nix
tools/
qubixctl.ps1
tests/
spotibox-basic.nix
From Linux/WSL:
nix build .#spotibox-vhdxThe default package is also spotibox-vhdx, so this works too:
nix buildThe generated manifest is available as a build artifact:
nix build .#qubix-manifest-json
cat resultresult is a symlink to the JSON file in the Nix store. The PowerShell
controller builds and reads the same artifact at runtime — you do not need to
build it manually.
Run PowerShell from Windows. Mutating Hyper-V commands must be run from an elevated PowerShell session.
Set-Location $HOME
$Qubix = "\\wsl.localhost\NixOS\home\nixos\Documents\repos\qubix"
& "$Qubix\tools\qubixctl.cmd" -Command recreate -Machine spotiboxrecreate builds the Nix image and recreates the VM in one step.
The .cmd wrapper runs the PowerShell script with process-scoped
-ExecutionPolicy Bypass. This avoids changing your global execution policy and
works around Windows treating scripts under \\wsl.localhost\... as unsigned
remote files. Keep the PowerShell working directory on a normal Windows path
when invoking the wrapper: cmd.exe cannot use a UNC path as its current
directory and otherwise falls back to C:\Windows. In other words, assign the
UNC path to $Qubix, but do not cd into it.
The controller defaults to:
- WSL distro:
NixOS - Linux repo path:
/home/nixos/Documents/repos/qubix - Hyper-V switch:
Default Switch - VM name:
qubix-spotibox - VM root:
C:\HyperV\Qubix
Override them when needed:
& "$Qubix\tools\qubixctl.cmd" `
-Command recreate `
-Machine spotibox `
-WslDistro NixOS `
-RepoLinuxPath /home/nixos/Documents/repos/qubix `
-VmRoot C:\HyperV\Qubix `
-SwitchName "Default Switch"Other commands:
& "$Qubix\tools\qubixctl.cmd" -Command build # run nix build only, skip VM
& "$Qubix\tools\qubixctl.cmd" -Command status
& "$Qubix\tools\qubixctl.cmd" -Command start
& "$Qubix\tools\qubixctl.cmd" -Command stop
& "$Qubix\tools\qubixctl.cmd" -Command destroy
& "$Qubix\tools\qubixctl.cmd" -Command mstscmstsc defaults to spotibox.local. If name resolution fails, pass an address:
& "$Qubix\tools\qubixctl.cmd" -Command mstsc -Address 192.168.192.121The old prototype used an ISO that booted, partitioned /dev/sda, installed
NixOS and rebooted. That works, but it keeps the slowest and most fragile part of
the loop: installing an OS inside a VM every time.
Qubix MVP builds the final Hyper-V VHDX directly from Nix. Hyper-V then only has to boot a ready disk.
The prototype found a very specific failure mode: Hyper-V enhanced sessions and
mstsc sessions under the same Unix user can mix DISPLAY,
DBUS_SESSION_BUS_ADDRESS and PulseAudio state. In that broken state,
pavucontrol launched from mstsc may open in the Hyper-V session.
The stable baseline is:
Hyper-V console -> user
mstsc/xrdp -> rdp
xrdp audio -> PulseAudio xrdp modules
PipeWire -> disabled
EasyEffects is intentionally not the active DSP baseline here. It is PipeWire-oriented, while this xrdp audio path expects PulseAudio.
The default manifest uses Hyper-V Default Switch with DHCP. The IP address
may change after reboot, but Avahi mDNS lets you connect by name:
.\tools\qubixctl.cmd -Command mstsc # resolves spotibox.localIf spotibox.local does not resolve, use status or Hyper-V Manager to find the
current IP and pass it with -Address.
For a stable address that survives reboots, declare a static IP in both the NixOS machine config and the flake manifest.
Edit machines/spotibox.nix only — the manifest derives the values automatically:
qubix.network = {
staticIp = "192.168.250.10";
gateway = "192.168.250.1";
};Then recreate as usual:
.\tools\qubixctl.cmd -Command recreate -Machine spotiboxqubixctl automatically creates an Internal Hyper-V switch (spotibox-nat),
assigns the gateway IP to the host vEthernet adapter, and creates a NetNat
rule so the VM can reach the internet. All three steps are idempotent — safe
to re-run on every recreate.
Connect by fixed IP:
.\tools\qubixctl.cmd -Command mstsc -Address 192.168.250.10Note: the NixOS image and the manifest must declare the same address. Qubix does not enforce this automatically — if they diverge the VM will boot with the wrong IP for the switch it is attached to.
Evaluate the flake:
nix flake check --no-buildRun the full smoke test when you are ready to build test VMs:
nix build .#checks.x86_64-linux.spotibox-basicManual acceptance:
.\tools\qubixctl.cmd -Command recreate -Machine spotibox- Confirm Hyper-V has a running
qubix-spotibox. - Connect with mstsc as
rdp/1234. - Start
pavucontrolin the RDP session and confirm the window appears there. - Start Spotify manually and confirm audio routes through RDP.
Nix is the source of truth for the manifest. qubixctl builds the
qubix-manifest-json derivation at startup, reads the resulting store path,
and parses the JSON — no manually maintained config file.
YAML can be generated later as a human-facing artifact using the same pattern:
describe structured data in Nix, emit JSON with builtins.toJSON, then convert
JSON to YAML with a tool such as yj.
- Spotify Openbox autostart.
- Spotify network lockdown via nftables, proxy or DNS allowlist.
- Stable custom Hyper-V NAT switch.
- PipeWire + EasyEffects experiment once xrdp audio is understood.
- Hardening profile, possibly inspired by nix-mineral, applied carefully.
- Production image with fewer debug tools.
- Kernel profile experiments: default/latest/hardened first, custom tiny kernel later.
- Hyper-V differencing disks for disposable runtime clones.
- Optional Nix-generated YAML artifact for human-facing config/docs.
backend.microvmfor headless disposable sandboxes.backend.nspawnfor trusted services.
- nixos-generators: https://github.com/nix-community/nixos-generators
- Generating YAML files with Nix: https://kokada.dev/blog/generating-yaml-files-with-nix/