{ pkgs, radiantwave, upgradeFlakeRef, radiantwavePlymouth, ... }: let radiantApp = radiantwave.packages.${pkgs.system}.default; myKey = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL4qWNZnYU5JaqR9c2qh6oFZktzVNBOx42PfDaGofCL6 cnclack@pm.me macbook" ]; in { ############################################################ # System configuration ############################################################ # Included packages environment.systemPackages = [ pkgs.cage pkgs.rsync pkgs.dbus pkgs.mesa pkgs.git pkgs.tailscale pkgs.sqlite pkgs.wireplumber radiantApp ]; # This populates the EFI partition and tells the NUC how to start NixOS. boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; # Boot straight into the splash screen without waiting on the bootloader menu. boot.loader.timeout = 0; # Hardware-specific: NUC 14 needs these for the NVMe and Graphics boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" "i915" "drm" "drm_kms_helper" ]; # --- RadiantWave splash: Plymouth from boot until the app's first frame --- boot.plymouth.enable = true; boot.plymouth.theme = "radiantwave"; boot.plymouth.themePackages = [ radiantwavePlymouth ]; # Silence the kernel/initrd output behind the splash. The module adds # "splash" itself; we keep "quiet" plus show_status=false and loglevel 0. boot.consoleLogLevel = 0; boot.initrd.verbose = false; boot.kernelParams = [ "quiet" "splash" "systemd.show_status=false" ]; # Hold the splash until the app is ready to take over the display, instead of # quitting at multi-user.target (which would flash a black/console screen # during the login shell's network wait). The kiosk login shell runs # `sudo -n plymouth quit --wait` immediately before exec'ing cage. systemd.services.plymouth-quit.wantedBy = pkgs.lib.mkForce [ ]; systemd.services.plymouth-quit-wait.wantedBy = pkgs.lib.mkForce [ ]; # Allow the kiosk user to quit Plymouth (NOPASSWD, command-scoped). security.sudo.extraRules = [ { users = [ "kiosk" ]; commands = [ { command = "/run/current-system/sw/bin/plymouth"; options = [ "NOPASSWD" ]; } ]; } ]; hardware.graphics.enable = true; time.timeZone = "America/Chicago"; networking.networkmanager.enable = true; nix.settings.access-tokens = { "gitea.radiantwavetech.com" = "ff524968d31acc5c4da350d0884a87264d0e8881"; }; system.autoUpgrade = { enable = true; flake = upgradeFlakeRef; dates = "*:00/10"; flags = [ "--refresh" "-L" ]; operation = "switch"; }; system.activationScripts.cleanRadiantFiles = '' rm -rf /home/kiosk/.local/share/radiantwave/audio ''; # Virtual machine declaration virtualisation.vmVariant = { boot.loader.systemd-boot.enable = pkgs.lib.mkForce false; boot.loader.efi.canTouchEfiVariables = pkgs.lib.mkForce false; virtualisation.forwardPorts = [ { from = "host"; host.port = 2222; guest.port = 22; } ]; virtualisation.qemu.options = [ "-device intel-hda" "-device hda-duplex" "-vga virtio" ]; virtualisation = { cores = 8; memorySize = 8192; }; services.logrotate.enable = false; }; virtualisation.diskSize = 10485760; # NixOS settings. nix.settings.experimental-features = [ "nix-command" "flakes" ]; nix.settings.accept-flake-config = true; system.stateVersion = "24.05"; ############################################################ # User configuration ############################################################ users.users.root.openssh.authorizedKeys.keys = myKey; users.users.kiosk = { isNormalUser = true; uid = 1000; extraGroups = ["audio" "video" "input" "networkmanager" "wheel"]; openssh.authorizedKeys.keys = myKey; password = "Fuckp@ssw0rds"; }; services.getty.autologinUser = "kiosk"; # --- The Kiosk Startup Script --- environment.loginShellInit = '' if [ "$(tty)" = "/dev/tty1" ]; then export GODEBUG=netdns=cgo 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 export SDL_VIDEODRIVER=wayland export QT_QPA_PLATFORM=wayland export XDG_SESSION_TYPE=wayland export WLR_RENDERER=pixman export WLR_NO_HARDWARE_CURSORS=1 # Pin system audio to 100% and unmute before launching RadiantWave ${pkgs.systemd}/bin/systemctl --user start radiantwave-audio-init.service || true # Release the splash screen right before the compositor takes over the display sudo -n plymouth quit --wait || true exec dbus-run-session cage -- /run/current-system/sw/bin/radiantwave -d -s > /tmp/kiosk.log 2>&1 fi ''; ############################################################ # Service configuration ############################################################ services.pipewire = { enable = true; alsa.enable = true; pulse.enable = true; }; # New sinks with no stored volume default to ~40% in WirePlumber 0.5+. # Force every newly-created sink to 100% so switching the default sink # never changes the volume. services.pipewire.wireplumber.extraConfig."99-kiosk-audio" = { "wireplumber.settings"."device.routes.default-sink-volume" = 1.0; }; # --- Pin all system audio sinks to 100% before RadiantWave launches --- # User unit (PipeWire runs per-user in the kiosk session), owned by this # system config. Ordered after PipeWire/WirePlumber; the retry loop covers # sink readiness at boot. Pins every connected sink (not just the default) # and unmutes them, so switching the default sink never changes the volume. # RadiantWave's loginShellInit waits on this before exec'ing cage. systemd.user.services.radiantwave-audio-init = { description = "Pin all system audio sinks to 100% and unmute"; after = [ "pipewire.service" "wireplumber.service" ]; wants = [ "pipewire.service" "wireplumber.service" ]; serviceConfig = { Type = "oneshot"; }; script = '' # Wait until WirePlumber exposes at least one sink sinks="" for i in {1..30}; do sinks=$(${pkgs.wireplumber}/bin/wpctl list audio sinks 2>/dev/null \ | ${pkgs.gnused}/bin/sed -nE 's/^.*[^0-9]([0-9]+)\. .*$/\1/p') if [ -n "$sinks" ]; then break fi sleep 1 done # Pin and unmute every connected sink for sink_id in $sinks; do ${pkgs.wireplumber}/bin/wpctl set-volume "$sink_id" 1.0 2>/dev/null || true ${pkgs.wireplumber}/bin/wpctl set-mute "$sink_id" 0 2>/dev/null || true done # Fallback in case the sink list came up empty ${pkgs.wireplumber}/bin/wpctl set-volume @DEFAULT_AUDIO_SINK@ 1.0 2>/dev/null || true ${pkgs.wireplumber}/bin/wpctl set-mute @DEFAULT_AUDIO_SINK@ 0 2>/dev/null || true ''; }; services.seatd.enable = true; services.openssh.enable = true; services.openssh.settings.PermitRootLogin = "prohibit-password"; # Networking Network-Manager services.resolved.enable = false; systemd.services.NetworkManager-wait-online.enable = false; systemd.network.wait-online.enable = false; # --- Set ownership of Radiantwave files --- systemd.tmpfiles.rules = [ "d /home/kiosk/.local/share/radiantwave 0770 kiosk kiosk -" ]; # --- Backup service: runs before nixos-upgrade --- systemd.services.radiantwave-backup = { description = "Backup RadiantWave user data before upgrade"; before = [ "nixos-upgrade.service" ]; wantedBy = [ "nixos-upgrade.service" ]; serviceConfig = { Type = "oneshot"; User = "kiosk"; }; script = '' BACKUP_DIR="/home/kiosk/.local/share/radiantwave-backup" SOURCE_DIR="/home/kiosk/.local/share/radiantwave" mkdir -p "$BACKUP_DIR" ${pkgs.rsync}/bin/rsync -a \ --exclude='audio/' \ "$SOURCE_DIR/" "$BACKUP_DIR/" echo "Backup completed at $(date)" > "$BACKUP_DIR/.backup-timestamp" ''; }; # --- Health check service: runs after nixos-upgrade --- systemd.services.radiantwave-healthcheck = { description = "Verify RadiantWave is running after upgrade, rollback if not"; after = [ "nixos-upgrade.service" ]; wantedBy = [ "nixos-upgrade.service" ]; serviceConfig = { Type = "oneshot"; # Give cage time to start radiantwave after the switch ExecStartPre = "${pkgs.coreutils}/bin/sleep 90"; }; script = '' if ${pkgs.procps}/bin/pgrep -x radiantwave > /dev/null; then echo "RadiantWave is running, upgrade successful" exit 0 fi echo "RadiantWave not found after upgrade, rolling back..." # Roll back to previous NixOS generation /run/current-system/sw/bin/nixos-rebuild switch --rollback echo "Rollback completed at $(date)" ''; }; }