# NixOS test VM for RadiantWave — mirrors the production kiosk setup. # # Build & run: # nixos-rebuild build-vm --flake .#test-vm # ./result/bin/run-test-vm-vm # # - cage as the Wayland compositor (fullscreen kiosk) # - PipeWire audio (with a virtual sink for device-switch testing) # - NetworkManager for the WiFi provisioning page # - Auto-login as the kiosk user, app started via loginShellInit { config, lib, pkgs, ... }: let radiantWaveApp = config.services.radiantwave.package; in { # ── Hostname ────────────────────────────────────────────────────────────── networking.hostName = "radiantwave-test"; # ── RadiantWave module ──────────────────────────────────────────────────── services.radiantwave.enable = true; # ── Boot ────────────────────────────────────────────────────────────────── boot.loader.systemd-boot.enable = lib.mkForce false; boot.loader.efi.canTouchEfiVariables = lib.mkForce false; # ── Packages ────────────────────────────────────────────────────────────── environment.systemPackages = [ pkgs.cage pkgs.rsync pkgs.dbus pkgs.mesa pkgs.git pkgs.sqlite ]; # ── Audio (PipeWire + virtual sink for device-switch testing) ───────────── services.pipewire = { enable = true; alsa.enable = true; pulse.enable = true; extraConfig.pipewire."30-virtual-sink" = { "context.modules" = [ { name = "libpipewire-module-loopback"; args = { "node.name" = "virtual-speaker"; "node.description" = "Virtual Speaker"; "audio.position" = [ "FL" "FR" ]; }; } ]; }; }; # ── Seat ────────────────────────────────────────────────────────────────── services.seatd.enable = true; hardware.graphics.enable = true; # ── Networking ──────────────────────────────────────────────────────────── services.resolved.enable = false; networking.networkmanager.enable = true; systemd.services.NetworkManager-wait-online.enable = false; systemd.network.wait-online.enable = false; # ── SSH ─────────────────────────────────────────────────────────────────── services.openssh = { enable = true; settings = { PermitRootLogin = "prohibit-password"; PasswordAuthentication = true; }; }; # ── User ────────────────────────────────────────────────────────────────── users.users.kiosk = { isNormalUser = true; uid = 1000; extraGroups = [ "audio" "video" "input" "networkmanager" "wheel" ]; password = "kiosk"; }; services.getty.autologinUser = "kiosk"; # ── Data directories (matches the app's first-run paths) ────────────────── systemd.tmpfiles.rules = [ "d /home/kiosk/.local/radiantwave 0770 kiosk kiosk -" "d /home/kiosk/.local/radiantwave/audio 0755 kiosk kiosk -" ]; # ── RadiantWave service (via loginShellInit, same as production) ────────── environment.loginShellInit = '' if [ "$(tty)" = "/dev/tty1" ]; then export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt # Smart Network Wait for i in {1..10}; do if ${pkgs.iputils}/bin/ping -c 1 8.8.8.8 &> /dev/null; then break fi sleep 1 done # Graphics fixes (matches production) export SDL_VIDEODRIVER=wayland export XDG_SESSION_TYPE=wayland export WLR_RENDERER=pixman export WLR_NO_HARDWARE_CURSORS=1 # Prefer the PipeWire audio backend so SDL enumerates every sink # (built-in analog, HDMI, and the loopback virtual speaker) instead of # silently falling back to an ALSA-only device view that hides the # PipeWire sinks and makes the device selector show a single entry. export SDL_AUDIODRIVER=pipewire if [ -n "$${XDG_RUNTIME_DIR:-}" ]; then for i in {1..20}; do [ -S "$XDG_RUNTIME_DIR/pipewire-0" ] && break sleep 0.5 done fi cage -- ${radiantWaveApp}/bin/radiantwave > /tmp/kiosk.log 2>&1 fi ''; # ── VM settings ─────────────────────────────────────────────────────────── virtualisation = { memorySize = 4096; cores = 4; graphics = true; forwardPorts = [ { from = "host"; host.port = 2222; guest.port = 22; } ]; qemu.options = [ "-device intel-hda" "-device hda-duplex" "-vga virtio" ]; }; services.logrotate.enable = false; # ── Nix settings ────────────────────────────────────────────────────────── nix.settings.experimental-features = [ "nix-command" "flakes" ]; nix.settings.accept-flake-config = true; # ── Firewall (open for local dev) ───────────────────────────────────────── networking.firewall.enable = false; system.stateVersion = "24.11"; }