{ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; disko.url = "github:nix-community/disko"; disko.inputs.nixpkgs.follows = "nixpkgs"; nixos-facter-modules.url = "github:numtide/nixos-facter-modules"; nixos-raspberrypi.url = "github:nvmd/nixos-raspberrypi/main"; }; outputs = { self, nixpkgs, disko, nixos-facter-modules, nixos-raspberrypi, ... }@inputs: let system = "x86_64-linux"; pkgs = nixpkgs.legacyPackages.${system}; # Plymouth theme: renders the RadiantWave SVG to a full-HD splash.png # at build time (librsvg), so the theme is hermetic and resolution- # independent. The .plymouth file's /usr paths are rewritten to $out # (the NixOS module's initrd fixup rewrites store paths again). mkRadiantwavePlymouth = targetPkgs: targetPkgs.stdenv.mkDerivation { pname = "radiantwave-plymouth"; version = "1.0.0"; nativeBuildInputs = [ targetPkgs.librsvg ]; src = ./assets/RWT-Circular-Color-ForOnDark.svg; dontUnpack = true; buildPhase = '' mkdir -p $out/share/plymouth/themes/radiantwave rsvg-convert \ --keep-aspect-ratio \ --background-color=#111318 \ --width=1280 \ --height=720 \ --output=$out/share/plymouth/themes/radiantwave/splash.png \ $src sed 's,/usr/,'"$out"'/,g' ${./plymouth-theme/radiantwave.plymouth} \ > $out/share/plymouth/themes/radiantwave/radiantwave.plymouth cp ${./plymouth-theme/radiantwave.script} \ $out/share/plymouth/themes/radiantwave/radiantwave.script ''; }; revision = self.rev or self.dirtyRev or null; # DEV/PROD CHANNEL SELECTOR — the single block that differs between the # `main` and `dev` branches. Everything downstream (the four configs, the # ISOs, the updater module) is identical on both branches and pulls from # this channel, so a `dev`-to-`main` merge carries your changes with it # automatically. There are no more `-dev` sibling configs to copy. # # main: atticRepo = "radiantwave", gitRef = "" # dev: atticRepo = "radiantwave-dev", gitRef = "?ref=dev" # # To flip branches, change ONLY these two values (then push/merge). # # Note there is deliberately NO app version here. The RadiantWave app is # not built or pinned by this repo; each channel's published manifest is # the single source of truth (see `publishedApp` below). channel = { atticRepo = "radiantwave-dev"; # dev gitRef = "?ref=dev"; # dev }; # Exposed so CI (see .gitea/workflows/build.yml) and humans can verify # which channel this checkout resolves to. On `main` this is # "radiantwave"; on `dev` it is "radiantwave-dev". channelName = channel.atticRepo; # Channel metadata shared by the configs and the CI preflight. channelAtticUrl = "https://attic.radiantwavetech.com/${channel.atticRepo}"; channelManifestDir = "https://files.radiantwavetech.com/${channel.atticRepo}"; # The baked RadiantWave app is whatever the channel's published manifest # currently announces. RadiantWave CI builds the app, pushes the closure # to the channel's Attic cache, and writes {version, storePath} to the # manifest. We fetch that manifest at eval time and use its storePath # verbatim, so an image always carries exactly the published closure and # never compiles RadiantWave from source. This is an *impure* fetch (no # hash), so every eval that reaches the app needs `--impure`: CI, the # kiosk's system.autoUpgrade, and local `build-rpi.sh` all pass it. manifestFor = manifestFile: builtins.fromJSON (builtins.readFile (builtins.fetchurl { url = "${channelManifestDir}/${manifestFile}"; })); # The published closure as a plain store-path string. configuration.nix # wraps it in a symlinkJoin so it can live in environment.systemPackages # (a bare string there would be coerced with builtins.storePath, which # requires the closure to already be local at eval time). publishedApp = manifestFile: (manifestFor manifestFile).storePath; # Live app-update side channel. The manifest is served by the # fileserver; closures are fetched (never built) from the channel's # Attic cache. `repo` selects the channel — "radiantwave" (main) or # "radiantwave-dev" (dev) — and `manifestFile` selects the per-arch # manifest (default "manifest.json"; Raspberry Pi uses # "aarch64-manifest.json"). All variants follow this same update # procedure; only the channel/arch differ. radiantwaveUpdaterModule = { repo, baseline, manifestFile ? "manifest.json" }: { services.radiantwaveUpdater = { enable = true; baselinePath = baseline; manifestUrl = "https://files.radiantwavetech.com/${repo}/${manifestFile}"; atticUrl = "https://attic.radiantwavetech.com/${repo}"; }; }; mkKioskSystem = { system, upgradeFlakeRef, hostName, diskModule, extraModules ? [], }: nixpkgs.lib.nixosSystem { inherit system; specialArgs = { inherit upgradeFlakeRef; radiantwaveApp = publishedApp "manifest.json"; radiantwavePlymouth = mkRadiantwavePlymouth; }; modules = [ { nixpkgs.config.allowUnfree = true; } { networking.hostName = hostName; system.configurationRevision = revision; } ./configuration.nix ./radiantwave-updater.nix disko.nixosModules.disko diskModule nixos-facter-modules.nixosModules.facter # Hardware facter report. This is a NUC 14 report currently # applied to every x86 variant (including commercial SATA # hardware). To use per-model reports: generate them on each # hardware target (procedure in AGENTS.md), commit them as # facter-.json, and select per variant here, e.g.: # { facter.reportPath = ./facter-commercial.json; } { facter.reportPath = ./facter.json; } ] ++ extraModules; }; mkRpiKioskSystem = { upgradeFlakeRef, hostName, extraModules ? [], }: nixos-raspberrypi.lib.nixosSystem { specialArgs = { inherit upgradeFlakeRef; inherit nixos-raspberrypi; radiantwaveApp = publishedApp "aarch64-manifest.json"; radiantwavePlymouth = mkRadiantwavePlymouth; }; modules = [ { nixpkgs.config.allowUnfree = true; } ({ ... }: { imports = with nixos-raspberrypi.nixosModules; [ sd-image raspberry-pi-5.base raspberry-pi-5.page-size-16k raspberry-pi-5.display-vc4 ]; }) ./configuration.nix ./radiantwave-updater.nix { networking.hostName = hostName; system.configurationRevision = revision; boot.loader.systemd-boot.enable = nixpkgs.lib.mkForce false; boot.loader.efi.canTouchEfiVariables = nixpkgs.lib.mkForce false; boot.loader.raspberry-pi.bootloader = "kernel"; nix.settings.extra-substituters = [ "https://nixos-raspberrypi.cachix.org" ]; nix.settings.extra-trusted-public-keys = [ "nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI=" ]; } ] ++ extraModules; }; # Zero-touch USB installer image. `targetConfig` names the kiosk system # to install: the closure comes from the packages output below and the # partitioning script from the paired disk module, so the ISO always # installs the matching system for its hardware. mkIso = { targetConfig, diskModule }: nixpkgs.lib.nixosSystem { system = "x86_64-linux"; specialArgs = { inherit inputs; inherit self; }; modules = [ "${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix" disko.nixosModules.disko (import ./zero-touch-installer.nix { inherit targetConfig; }) diskModule ({ pkgs, ... }: { environment.systemPackages = [ disko.packages.${system}.default ]; isoImage.storeContents = [ self.packages.x86_64-linux.${targetConfig} ]; environment.etc."disko-config.nix".source = diskModule; }) ]; }; in { inherit channelName channelAtticUrl channelManifestDir; packages.x86_64-linux.radiantwavekiosk = self.nixosConfigurations.radiantwavekiosk.config.system.build.toplevel; packages.x86_64-linux.radiantwavekiosk-commercial = self.nixosConfigurations.radiantwavekiosk-commercial.config.system.build.toplevel; packages.x86_64-linux.radiantwavekiosk-android = self.nixosConfigurations.radiantwavekiosk-android.config.system.build.toplevel; packages.x86_64-linux.radiantwavekiosk-rpi = self.nixosConfigurations.radiantwavekiosk-rpi.config.system.build.toplevel; # Kiosk systems — one per hardware target, no dev/prod siblings. # # Every config consumes the channel selected at the top of this file # (see `channel`): the published manifest whose closure is baked as the # app, the Attic repo/manifest it fetches live updates from, and the # KioskConfig git ref it auto-upgrades from. On `main` the channel is # prod; on `dev` it is the dev channel. Because there is exactly one # config per hardware type, changes made on `dev` reach prod via a plain # merge — no copying. nixosConfigurations = { radiantwavekiosk = mkKioskSystem { system = "x86_64-linux"; hostName = "radiantwavekiosk"; diskModule = ./disk-config.nix; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk"; extraModules = [ (radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = publishedApp "manifest.json"; }) ]; }; radiantwavekiosk-commercial = mkKioskSystem { system = "x86_64-linux"; hostName = "radiantwavekiosk-commercial"; diskModule = ./commercial-disk-config.nix; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-commercial"; extraModules = [ (radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = publishedApp "manifest.json"; }) ]; }; # Rotated screen for android-style displays. radiantwavekiosk-android = mkKioskSystem { system = "x86_64-linux"; hostName = "radiantwavekiosk-android"; diskModule = ./disk-config.nix; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-android"; extraModules = [ ./rotated_display.nix (radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = publishedApp "manifest.json"; }) ]; }; # Raspberry Pi 5. radiantwavekiosk-rpi = mkRpiKioskSystem { hostName = "radiantwavekiosk-rpi"; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-rpi"; extraModules = [ (radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = publishedApp "aarch64-manifest.json"; manifestFile = "aarch64-manifest.json"; }) ]; }; # === USB Images === # Each ISO installs exactly the named system closure using that # system's disk layout. NVMe layouts pair with ./disk-config.nix, # SATA with ./commercial-disk-config.nix — don't cross them. # The same names build dev-flavored images when the flake is checked # out on the `dev` branch (the channel block above selects the dev # manifest, attic repo, and upgrade ref), so there are no separate # -dev ISOs. kiosk-iso = mkIso { targetConfig = "radiantwavekiosk"; diskModule = ./disk-config.nix; }; commercial-iso = mkIso { targetConfig = "radiantwavekiosk-commercial"; diskModule = ./commercial-disk-config.nix; }; android-iso = mkIso { targetConfig = "radiantwavekiosk-android"; diskModule = ./disk-config.nix; }; }; }; }