From 65a6b256f9413bada4b3302023f529df57800fac Mon Sep 17 00:00:00 2001 From: NeilClack Date: Sun, 23 Aug 2026 09:58:46 -0500 Subject: [PATCH] Fix dev/prod split and dev ISOs; consolidate zero-touch installer - Wire -dev variants to the radiantwave-dev input (?ref=dev); prod keeps main - Fix android-dev upgradeFlakeRef (was upgrading into production config) - Parameterize one installer module (zero-touch-installer.nix); replace the four near-duplicate ISO configs; mkIso pairs each ISO with its closure + disk layout, fixing commercial-dev/android-dev ISO eval errors - Add missing packages outputs (commercial-dev, android-dev, rpi, rpi-dev) - Set networking.hostName + system.configurationRevision per variant - Remove gitea access token and dead netrc/JWT config (repos are public) - Drop flake.lock update step from CI (lock updates are manual) - Refresh AGENTS.md: variants/ISO tables, facter report procedure, gotchas --- .gitea/workflows/build-dev.yml | 8 +- AGENTS.md | 94 +++++--- android-iso.nix | 133 ----------- commercial-iso-config.nix | 128 ---------- configuration.nix | 2 - dev-iso.nix | 133 ----------- flake.nix | 266 +++++++++------------ iso-config.nix => zero-touch-installer.nix | 53 ++-- 8 files changed, 200 insertions(+), 617 deletions(-) delete mode 100644 android-iso.nix delete mode 100644 commercial-iso-config.nix delete mode 100644 dev-iso.nix rename iso-config.nix => zero-touch-installer.nix (63%) diff --git a/.gitea/workflows/build-dev.yml b/.gitea/workflows/build-dev.yml index 1411f04..5fb76e5 100644 --- a/.gitea/workflows/build-dev.yml +++ b/.gitea/workflows/build-dev.yml @@ -4,17 +4,15 @@ on: workflow_dispatch: jobs: - update-and-build: + build-dev-iso: runs-on: build-server steps: - name: Checkout run: git clone --depth 1 ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/KioskConfig.git . - - name: Update radiantwave-dev - run: | - . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh - nix flake update radiantwave-dev + # flake.lock is updated manually (committing an updated lock is the + # release trigger); CI only builds what is checked in. - name: Prepare output directory run: mkdir -p /home/user/radiantwave diff --git a/AGENTS.md b/AGENTS.md index 99a0fad..b4b741f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,10 +2,19 @@ ## What this repo is -NixOS flake that defines kiosk systems running the RadiantWave app. Not application code — this is the OS image and deployment configuration. The actual app lives in the `radiantwave` flake input (from gitea.radiantwavetech.com). +NixOS flake that defines kiosk systems running the RadiantWave app. Not application code — this is the OS image and deployment configuration. The actual app lives in the `radiantwave` flake inputs (from gitea.radiantwavetech.com). Hosted on Gitea: `https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig` +## Prod/dev split + +Single branch (`main`) for this repo. The production vs. dev split is at the **flake input + upgrade ref** level: + +- Production variants consume the `radiantwave` input (`?ref=main`) and auto-upgrade from `KioskConfig.git#` on `main`. +- `-dev` variants consume the `radiantwave-dev` input (`?ref=dev`) and auto-upgrade from `KioskConfig.git?ref=dev#-dev`. + +Both gitea repos are public: all git access is plain HTTPS (`git+https://...`), no SSH keys or access tokens on the kiosks. Don't reintroduce `git+ssh` URLs in flake/upgrade refs. + ## Key commands ```bash @@ -18,7 +27,7 @@ nix build .#nixosConfigurations..config.system.build.isoImage # Deploy to a remote kiosk over SSH nixos-rebuild switch --flake .# --target-host root@ -# Push build result to Attic cache +# Push build result to Attic cache (not currently wired into any config) attic push kiosk ./result # Evaluate available config names @@ -27,48 +36,79 @@ nix eval .#nixosConfigurations --apply builtins.attrNames ## Config variants -| Name | Target hardware | Disk device | Notes | +| Name | Target hardware | Disk device | App input | |---|---|---|---| -| `radiantwavekiosk` | Intel NUC 14 (NVMe) | `/dev/nvme0n1` | Production, uses `radiantwave-legacy` | -| `radiantwavekiosk-dev` | Intel NUC 14 (NVMe) | `/dev/nvme0n1` | Dev, uses `radiantwave` `?ref=dev` | -| `radiantwavekiosk-commercial` | Commercial HW (SATA) | `/dev/sda` | Production, uses `radiantwave-legacy` | -| `radiantwavekiosk-commercial-dev` | Commercial HW (SATA) | `/dev/sda` | Dev, uses `radiantwave` `?ref=dev` | -| `radiantwavekiosk-android` | Intel NUC 14 | `/dev/nvme0n1` | + rotated display (portrait), uses `radiantwave-legacy` | -| `radiantwavekiosk-rpi` | Raspberry Pi 5 | — | Uses `nixos-raspberrypi`, uses `radiantwave-legacy` | -| `radiantwavekiosk-rpi-dev` | Raspberry Pi 5 | — | Dev, uses `radiantwave` `?ref=dev` | +| `radiantwavekiosk` | Intel NUC 14 (NVMe) | `/dev/nvme0n1` | `radiantwave` (main) | +| `radiantwavekiosk-dev` | Intel NUC 14 (NVMe) | `/dev/nvme0n1` | `radiantwave-dev` (dev) | +| `radiantwavekiosk-commercial` | Commercial HW (SATA) | `/dev/sda` | `radiantwave` (main) | +| `radiantwavekiosk-commercial-dev` | Commercial HW (SATA) | `/dev/sda` | `radiantwave-dev` (dev) | +| `radiantwavekiosk-android` | Intel NUC 14, rotated display | `/dev/nvme0n1` | `radiantwave` (main) | +| `radiantwavekiosk-android-dev` | Intel NUC 14, rotated display | `/dev/nvme0n1` | `radiantwave-dev` (dev) | +| `radiantwavekiosk-rpi` | Raspberry Pi 5 (SD image) | — | `radiantwave` (main) | +| `radiantwavekiosk-rpi-dev` | Raspberry Pi 5 (SD image) | — | `radiantwave-dev` (dev) | -ISOs: `usb-iso`, `commercial-usb-iso`, `android-iso`, `dev-iso` — each bakes in the corresponding system closure for zero-touch install. +Every variant sets its own `networking.hostName` (same as the config name) and `system.configurationRevision`, so `tools/find_systems.zsh` can identify fleet members. -## Branching model +### USB images (zero-touch installers) -Single branch (`main`). Production vs. dev split is at the **flake input** level (`radiantwave-legacy` vs `radiantwave` `?ref=dev`), not at the branch level. +Each ISO installs exactly one closure using that closure's disk layout (`mkIso` in `flake.nix` derives both from the target config name): + +| ISO | Installs | Disk layout | +|---|---|---| +| `kiosk-iso` | `radiantwavekiosk` | NVMe (`disk-config.nix`) | +| `dev-iso` | `radiantwavekiosk-dev` | NVMe | +| `commercial-iso` | `radiantwavekiosk-commercial` | SATA (`commercial-disk-config.nix`) | +| `commercial-dev-iso` | `radiantwavekiosk-commercial-dev` | SATA | +| `android-iso` | `radiantwavekiosk-android` | NVMe | +| `android-dev-iso` | `radiantwavekiosk-android-dev` | NVMe | + +The installer lives once in `zero-touch-installer.nix` and is parameterized by `targetConfig`. Adding a new variant = add the `nixosConfigurations.*` entry, a matching `packages.x86_64-linux.*` line, and an ISO if needed. + +Raspberry Pi systems are built as SD images, not ISOs: +`nix build .#nixosConfigurations.radiantwavekiosk-rpi.config.system.build.sdImage` (see `build-rpi.sh`). + +## Hardware facter reports + +`facter.json` is a hardware report captured on a **NUC 14**. It is currently applied to **every x86 variant**, including the commercial SATA hardware — acceptable until per-model reports exist. + +To generate a report for a hardware model: + +1. Boot that hardware from any NixOS live image (the kiosk installers work). +2. With real peripherals attached (display(s), audio), run: + ```bash + sudo nix shell nixpkgs#nixos-facter -c nixos-facter -o facter-.json + ``` +3. Commit `facter-.json` to the repo root. +4. Wire it up at the marked spot in `flake.nix` (`facter.reportPath` inside `mkKioskSystem`) by selecting the right file per variant. + +Regenerate when major hardware changes, not on a schedule — reports are snapshots. RPi configs don't use the facter module, so no report is needed there. ## CI (Gitea) -`.gitea/workflows/build-dev.yml` — triggered by `workflow_dispatch`. Updates `radiantwave` input (dev branch), builds dev closure + ISO, commits updated `flake.lock`. Runs on `build-server`. +`.gitea/workflows/build-*.yml` — all triggered by `workflow_dispatch` on the `build-server` runner. They only **build and publish** what is committed. `flake.lock` is updated manually: commit an updated lock (e.g. after `nix flake update radiantwave-dev`) and dispatching the workflow is the release trigger. ## Important gotchas -- **Disk device differs by variant**: `disk-config.nix` uses `/dev/nvme0n1`, `commercial-disk-config.nix` uses `/dev/sda`. Don't mix them up. -- **ISO configs reference specific system closures**: `iso-config.nix` hardcodes `radiantwavekiosk`, `dev-iso.nix` hardcodes `radiantwavekiosk-dev`, `commercial-iso-config.nix` hardcodes `radiantwavekiosk-commercial`, `android-iso.nix` hardcodes `radiantwavekiosk-android`. Changing a config name in `flake.nix` requires updating the corresponding ISO config. -- **Attic cache token**: ISO configs embed a JWT token for Attic in `/etc/nix/netrc`. If builds fail with auth errors, the token may have expired (expiry in the JWT payload). -- **`rotated_display.nix` uses `mkForce`** on `loginShellInit` — it replaces the entire kiosk startup script from `configuration.nix`, don't edit both. -- **SOPS**: `.sops.yaml` configures age-based encryption for `secrets.yaml`. You'll need the private key matching `age1t2wpzv...` to decrypt. -- **Flake inputs**: `radiantwave` tracks main, `radiantwave-dev` tracks `?ref=dev`. Updating them independently is intentional. +- **Disk device differs by variant**: `disk-config.nix` uses `/dev/nvme0n1`, `commercial-disk-config.nix` uses `/dev/sda`. `mkIso` pairs each ISO with the correct layout automatically — keep new ISOs paired correctly. +- **Changing a config name** requires updating that config's `upgradeFlakeRef` in `flake.nix` (it embeds the name). +- **`rotated_display.nix` uses `mkForce`** on `loginShellInit` — it replaces the entire kiosk startup script from `configuration.nix`; don't edit both. +- **Auto-update**: `system.autoUpgrade` runs every 10 min (`*:00/10`) with `--refresh`, operation `switch`, no auto-reboot. A `radiantwave-backup` rsync runs before each attempt. The health-check/rollback service is written but commented out in `configuration.nix`. +- **No binary cache**: kiosks fetch from cache.nixos.org only and compile RadiantWave itself from source during upgrades. Attic integration was planned (see `PLAN-update-system.md`) but never wired; the old netrc/JWT setup has been removed. +- **Secrets**: none in-tree anymore (gitea repos are public). If either repo goes private, re-add auth via `nix.settings.access-tokens` in `configuration.nix`. +- **Flake inputs**: `radiantwave` tracks main, `radiantwave-dev` tracks `?ref=dev`; prod and `-dev` configs consume them respectively. Updating them independently is intentional. +- **SSH keys on machines are inbound-only** (`authorizedKeys` for admin access); outbound git is HTTPS. The root key in `zero-touch-installer.nix` must stay in sync with `myKey` in `configuration.nix`. ## File map -- `flake.nix` — all system definitions, entry point +- `flake.nix` — entry point: inputs, builders (`mkKioskSystem`, `mkRpiKioskSystem`, `mkIso`), all system definitions, facter report wiring - `configuration.nix` — shared NixOS config (packages, services, kiosk user, auto-upgrade, health check, VM variant) +- `zero-touch-installer.nix` — parameterized USB installer module used by all six ISOs - `disk-config.nix` — NVMe disk layout (NUC) - `commercial-disk-config.nix` — SATA disk layout (commercial HW) -- `iso-config.nix` — production ISO + zero-touch installer -- `dev-iso.nix` — dev ISO + zero-touch installer -- `commercial-iso-config.nix` — commercial ISO + zero-touch installer -- `android-iso.nix` — android ISO + zero-touch installer - `rotated_display.nix` — portrait display override for Android variant - `facter.json` — hardware facter report (NUC 14) -- `14rvk.json`, `pn54.json` — additional hardware facter reports - `assets/` — logo and splash image - `plymouth-theme/` — boot splash theme -- `commands.md` — quick-reference for build/deploy commands +- `tools/find_systems.zsh` — subnet sweep for fleet inventory over SSH +- `build-rpi.sh` — containerized RPi SD-image build helper +- `PLAN-*.md`, `report.md`, `timeline.csv`, `kiosk-url-changes.csv` — planning/history notes, not configuration diff --git a/android-iso.nix b/android-iso.nix deleted file mode 100644 index c294d7c..0000000 --- a/android-iso.nix +++ /dev/null @@ -1,133 +0,0 @@ -{ - pkgs, - inputs, - self, - ... -}: -let - targetSystem = - self.nixosConfigurations.radiantwavekiosk-android.config.system.build.toplevel; - - diskoScript = - self.nixosConfigurations.radiantwavekiosk-android.config.system.build.diskoScript; -in -{ - ############################################################ - # System configuration - ############################################################ - - nix.settings = { - experimental-features = [ - "nix-command" - "flakes" - ]; - - substituters = [ - "https://cache.nixos.org" - ]; - - flake-registry = ""; - - connect-timeout = 0; - }; - - nix.registry.nixpkgs = pkgs.lib.mkForce { - to = { - type = "path"; - path = "${inputs.nixpkgs}"; - }; - }; - - nix.extraOptions = '' - netrc-file = /etc/nix/netrc - ''; - - environment.variables.KIOSK_FLAKE = "${self}"; - - environment.etc."nix/netrc" = { - text = '' - password eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIwODgzODk2ODEsIm5iZiI6MTc3MjgxMzY4MSwic3ViIjoia2lvc2siLCJodHRwczovL2p3dC5hdHRpYy5ycy92MSI6eyJjYWNoZXMiOnsia2lvc2siOnsiciI6MX19fX0.N6Ktrmmu1LSy2PP8uNF25VHRP1dxPZJ4M0MR1kUpVi0 - ''; - mode = "0400"; - }; - - networking.networkmanager.enable = true; - - ############################################################ - # User configuration - ############################################################ - - users.users.root.openssh.authorizedKeys.keys = [ - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL4qWNZnYU5JaqR9c2qh6oFZktzVNBOx42PfDaGofCL6 cnclack@pm.me macbook" - ]; - - ############################################################ - # Service configuration - ############################################################ - - services.openssh.enable = true; - - systemd.services.NetworkManager-wait-online.enable = false; - - systemd.services.zero-touch-install = { - description = "Zero-Touch Kiosk Installation"; - - wantedBy = [ "multi-user.target" ]; - - # Make sure we own tty1 exclusively - getty@tty1 will otherwise - # try to spawn a login prompt on the same console. - conflicts = [ "getty@tty1.service" ]; - after = [ "systemd-udev-settle.service" "getty@tty1.service" ]; - - path = with pkgs; [ - nix - git - util-linux - nixos-install-tools - curl - networkmanager - mount - umount - disko - ]; - - environment = { - NIX_PATH = "nixpkgs=${inputs.nixpkgs}"; - KIOSK_FLAKE = "${self}"; - DISKO_SCRIPT = "${diskoScript}"; - }; - - serviceConfig = { - StandardInput = "tty"; - StandardOutput = "tty"; - StandardError = "tty"; - TTYPath = "/dev/tty1"; - TTYReset = true; - TTYVHangup = true; - TTYVTDisallocate = true; - }; - - script = '' - set -euo pipefail - - echo "Running disko script..." - echo "Script: $DISKO_SCRIPT" - - ${diskoScript} - - echo "Installing RadiantWave" - echo "Closure Path: ${targetSystem}" - - nixos-install \ - --system "${targetSystem}" \ - --no-root-passwd \ - --no-channel-copy - - echo "============================================" - echo " Installation complete!" - echo " Please shut down and remove the flash drive before restarting" - echo "============================================" - sleep 10000000 - ''; - }; -} diff --git a/commercial-iso-config.nix b/commercial-iso-config.nix deleted file mode 100644 index 944bfb8..0000000 --- a/commercial-iso-config.nix +++ /dev/null @@ -1,128 +0,0 @@ -{ - pkgs, - inputs, - self, - ... -}: -let - # TODO: Parameterize this to combine with iso-config.nix - # Currently hardcoded to radiantwavekiosk-commercial; should accept target config as param - targetSystem = - self.nixosConfigurations.radiantwavekiosk-commercial.config.system.build.toplevel; - - diskoScript = - self.nixosConfigurations.radiantwavekiosk-commercial.config.system.build.diskoScript; -in -{ - nix.settings = { - experimental-features = [ - "nix-command" - "flakes" - ]; - - substituters = [ - "https://cache.nixos.org" - ]; - - flake-registry = ""; - }; - - nix.registry.nixpkgs = pkgs.lib.mkForce { - to = { - type = "path"; - path = "${inputs.nixpkgs}"; - }; - }; - - environment.variables.KIOSK_FLAKE = "${self}"; - - virtualisation.vmVariant = { - virtualisation.memorySize = 8096; - virtualisation.diskSize = 100000; - virtualisation.cores = 4; - virtualisation.qemu.options = [ - # "-nic none" - ]; - virtualisation.forwardPorts = [ - { from = "host"; host.port = 2222; guest.port = 22; } - ]; - }; - - environment.etc."nix/netrc" = { - text = '' - password eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIwODgzODk2ODEsIm5iZiI6MTc3MjgxMzY4MSwic3ViIjoia2lvc2siLCJodHRwczovL2p3dC5hdHRpYy5ycy92MSI6eyJjYWNoZXMiOnsia2lvc2siOnsiciI6MX19fX0.N6Ktrmmu1LSy2PP8uNF25VHRP1dxPZJ4M0MR1kUpVi0 - ''; - mode = "0400"; - }; - - nix.extraOptions = '' - netrc-file = /etc/nix/netrc - ''; - - networking.networkmanager.enable = true; - - systemd.services.NetworkManager-wait-online.enable = false; - - nix.settings.connect-timeout = 0; - - services.openssh.enable = true; - - users.users.root.openssh.authorizedKeys.keys = [ - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB4vXx7sOOVAyxdS/N1LEI+L8wYICf+gbzs6Kz7K8Je5 cnclack@pm.me:Thinkpad" - ]; - - systemd.services.zero-touch-install = { - description = "Zero-Touch Kiosk Installation"; - - wantedBy = [ "multi-user.target" ]; - - wants = [ ]; - after = [ "systemd-udev-settle.service" ]; - - path = with pkgs; [ - nix - git - util-linux - nixos-install-tools - curl - networkmanager - mount - umount - disko - ]; - - environment = { - NIX_PATH = "nixpkgs=${inputs.nixpkgs}"; - KIOSK_FLAKE = "${self}"; - DISKO_SCRIPT = "${diskoScript}"; - }; - - script = '' - set -euo pipefail - - echo "Running disko script..." - echo "Script: $DISKO_SCRIPT" - - ${diskoScript} - - echo "Installing RadiantWave" - echo "Closure Path: ${targetSystem}" - - nixos-install \ - --system "${targetSystem}" \ - --no-root-passwd \ - --no-channel-copy - - # Redirect to console and show completion message - exec > /dev/tty1 2>&1 - echo "" - echo "=============================================" - echo " Installation complete!" - echo "" - echo " Please remove the USB drive and reboot." - echo "=============================================" - - # Wait indefinitely for user action - sleep infinity -''; }; -} diff --git a/configuration.nix b/configuration.nix index 8c2a9de..89908d8 100644 --- a/configuration.nix +++ b/configuration.nix @@ -61,8 +61,6 @@ in networking.networkmanager.enable = true; - nix.settings.access-tokens = "gitea.radiantwavetech.com=ff524968d31acc5c4da350d0884a87264d0e8881"; - system.autoUpgrade = { enable = true; flake = upgradeFlakeRef; diff --git a/dev-iso.nix b/dev-iso.nix deleted file mode 100644 index 41eb0a6..0000000 --- a/dev-iso.nix +++ /dev/null @@ -1,133 +0,0 @@ -{ - pkgs, - inputs, - self, - ... -}: -let - targetSystem = - self.nixosConfigurations.radiantwavekiosk-dev.config.system.build.toplevel; - - diskoScript = - self.nixosConfigurations.radiantwavekiosk-dev.config.system.build.diskoScript; -in -{ - ############################################################ - # System configuration - ############################################################ - - nix.settings = { - experimental-features = [ - "nix-command" - "flakes" - ]; - - substituters = [ - "https://cache.nixos.org" - ]; - - flake-registry = ""; - - connect-timeout = 0; - }; - - nix.registry.nixpkgs = pkgs.lib.mkForce { - to = { - type = "path"; - path = "${inputs.nixpkgs}"; - }; - }; - - nix.extraOptions = '' - netrc-file = /etc/nix/netrc - ''; - - environment.variables.KIOSK_FLAKE = "${self}"; - - environment.etc."nix/netrc" = { - text = '' - password eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIwODgzODk2ODEsIm5iZiI6MTc3MjgxMzY4MSwic3ViIjoia2lvc2siLCJodHRwczovL2p3dC5hdHRpYy5ycy92MSI6eyJjYWNoZXMiOnsia2lvc2siOnsiciI6MX19fX0.N6Ktrmmu1LSy2PP8uNF25VHRP1dxPZJ4M0MR1kUpVi0 - ''; - mode = "0400"; - }; - - networking.networkmanager.enable = true; - - ############################################################ - # User configuration - ############################################################ - - users.users.root.openssh.authorizedKeys.keys = [ - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL4qWNZnYU5JaqR9c2qh6oFZktzVNBOx42PfDaGofCL6 cnclack@pm.me macbook" - ]; - - ############################################################ - # Service configuration - ############################################################ - - services.openssh.enable = true; - - systemd.services.NetworkManager-wait-online.enable = false; - - systemd.services.zero-touch-install = { - description = "Zero-Touch Kiosk Installation"; - - wantedBy = [ "multi-user.target" ]; - - # Make sure we own tty1 exclusively - getty@tty1 will otherwise - # try to spawn a login prompt on the same console. - conflicts = [ "getty@tty1.service" ]; - after = [ "systemd-udev-settle.service" "getty@tty1.service" ]; - - path = with pkgs; [ - nix - git - util-linux - nixos-install-tools - curl - networkmanager - mount - umount - disko - ]; - - environment = { - NIX_PATH = "nixpkgs=${inputs.nixpkgs}"; - KIOSK_FLAKE = "${self}"; - DISKO_SCRIPT = "${diskoScript}"; - }; - - serviceConfig = { - StandardInput = "tty"; - StandardOutput = "tty"; - StandardError = "tty"; - TTYPath = "/dev/tty1"; - TTYReset = true; - TTYVHangup = true; - TTYVTDisallocate = true; - }; - - script = '' - set -euo pipefail - - echo "Running disko script..." - echo "Script: $DISKO_SCRIPT" - - ${diskoScript} - - echo "Installing RadiantWave" - echo "Closure Path: ${targetSystem}" - - nixos-install \ - --system "${targetSystem}" \ - --no-root-passwd \ - --no-channel-copy - - echo "============================================" - echo " Installation complete!" - echo " Please shut down and remove the flash drive before restarting" - echo "============================================" - sleep 10000000 - ''; - }; -} \ No newline at end of file diff --git a/flake.nix b/flake.nix index 52819c8..8d082ab 100644 --- a/flake.nix +++ b/flake.nix @@ -53,7 +53,16 @@ ''; }; - mkKioskSystem = { system, radiantwave, upgradeFlakeRef, extraModules ? [], diskModule, extraSpecialArgs ? {}}: + revision = self.rev or self.dirtyRev or null; + + mkKioskSystem = { + system, + radiantwave, + upgradeFlakeRef, + hostName, + diskModule, + extraModules ? [], + }: nixpkgs.lib.nixosSystem { inherit system; @@ -61,20 +70,35 @@ inherit radiantwave; inherit upgradeFlakeRef; inherit radiantwavePlymouth; - } // extraSpecialArgs; + }; modules = [ { nixpkgs.config.allowUnfree = true; } + { + networking.hostName = hostName; + system.configurationRevision = revision; + } ./configuration.nix disko.nixosModules.disko - diskModule + 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; } radiantwave.nixosModules.default ] ++ extraModules; }; - mkRpiKioskSystem = { radiantwave, upgradeFlakeRef, extraModules ? [] }: + mkRpiKioskSystem = { + radiantwave, + upgradeFlakeRef, + hostName, + extraModules ? [], + }: nixos-raspberrypi.lib.nixosSystem { specialArgs = { inherit radiantwave upgradeFlakeRef; @@ -94,6 +118,9 @@ ./configuration.nix radiantwave.nixosModules.default { + 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"; @@ -107,211 +134,138 @@ } ] ++ 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 { packages.x86_64-linux.radiantwavekiosk = self.nixosConfigurations.radiantwavekiosk.config.system.build.toplevel; packages.x86_64-linux.radiantwavekiosk-dev = self.nixosConfigurations.radiantwavekiosk-dev.config.system.build.toplevel; packages.x86_64-linux.radiantwavekiosk-commercial = self.nixosConfigurations.radiantwavekiosk-commercial.config.system.build.toplevel; + packages.x86_64-linux.radiantwavekiosk-commercial-dev = self.nixosConfigurations.radiantwavekiosk-commercial-dev.config.system.build.toplevel; packages.x86_64-linux.radiantwavekiosk-android = self.nixosConfigurations.radiantwavekiosk-android.config.system.build.toplevel; - # Kiosk + packages.x86_64-linux.radiantwavekiosk-android-dev = self.nixosConfigurations.radiantwavekiosk-android-dev.config.system.build.toplevel; + packages.x86_64-linux.radiantwavekiosk-rpi = self.nixosConfigurations.radiantwavekiosk-rpi.config.system.build.toplevel; + packages.x86_64-linux.radiantwavekiosk-rpi-dev = self.nixosConfigurations.radiantwavekiosk-rpi-dev.config.system.build.toplevel; + + # Kiosk systems. + # + # Production variants consume the `radiantwave` input (?ref=main); + # `-dev` variants consume `radiantwave-dev` (?ref=dev) and track the + # dev branch of this repo via their autoUpgrade flake refs. nixosConfigurations = { radiantwavekiosk = mkKioskSystem { system = "x86_64-linux"; radiantwave = radiantwave; + hostName = "radiantwavekiosk"; diskModule = ./disk-config.nix; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git#radiantwavekiosk"; - extraModules = []; }; - # Kiosk-dev radiantwavekiosk-dev = mkKioskSystem { system = "x86_64-linux"; - radiantwave = radiantwave; + radiantwave = radiantwave-dev; + hostName = "radiantwavekiosk-dev"; diskModule = ./disk-config.nix; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git?ref=dev#radiantwavekiosk-dev"; - extraModules = []; }; - # Commercial radiantwavekiosk-commercial = mkKioskSystem { system = "x86_64-linux"; radiantwave = radiantwave; + hostName = "radiantwavekiosk-commercial"; diskModule = ./commercial-disk-config.nix; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git#radiantwavekiosk-commercial"; - extraModules = []; }; - # Commercial-dev radiantwavekiosk-commercial-dev = mkKioskSystem { system = "x86_64-linux"; - radiantwave = radiantwave; + radiantwave = radiantwave-dev; + hostName = "radiantwavekiosk-commercial-dev"; diskModule = ./commercial-disk-config.nix; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git?ref=dev#radiantwavekiosk-commercial-dev"; - extraModules = []; }; - # Kiosk - rotated screen for android displays + # Rotated screen for android-style displays. radiantwavekiosk-android = mkKioskSystem { system = "x86_64-linux"; radiantwave = radiantwave; + hostName = "radiantwavekiosk-android"; diskModule = ./disk-config.nix; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git#radiantwavekiosk-android"; - extraModules = [./rotated_display.nix ]; + extraModules = [./rotated_display.nix]; }; - # Kiosk-dev - rotated screen for android displays radiantwavekiosk-android-dev = mkKioskSystem { system = "x86_64-linux"; - radiantwave = radiantwave; + radiantwave = radiantwave-dev; + hostName = "radiantwavekiosk-android-dev"; diskModule = ./disk-config.nix; - upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git#radiantwavekiosk-android"; - extraModules = [./rotated_display.nix ]; + upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git?ref=dev#radiantwavekiosk-android-dev"; + extraModules = [./rotated_display.nix]; }; - # Raspberry pi 5 + # Raspberry Pi 5. radiantwavekiosk-rpi = mkRpiKioskSystem { radiantwave = radiantwave; + hostName = "radiantwavekiosk-rpi"; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git#radiantwavekiosk-rpi"; }; - # Raspberry pi 5 dev radiantwavekiosk-rpi-dev = mkRpiKioskSystem { - radiantwave = radiantwave; + radiantwave = radiantwave-dev; + hostName = "radiantwavekiosk-rpi-dev"; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git?ref=dev#radiantwavekiosk-rpi-dev"; }; + # === USB Images === - # Kiosk ISO - kiosk-iso = 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 - ./iso-config.nix - ./disk-config.nix - ({ pkgs, ... }: { - environment.systemPackages = [ - disko.packages.${system}.default - ]; - isoImage.storeContents = [ - self.packages.x86_64-linux.radiantwavekiosk - ]; - environment.etc."disko-config.nix".source = ./disk-config.nix; - }) - ]; + # 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. + kiosk-iso = mkIso { + targetConfig = "radiantwavekiosk"; + diskModule = ./disk-config.nix; }; - # Commercial iso - commercial-iso = 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 - ./commercial-iso-config.nix - ./commercial-disk-config.nix - ({ pkgs, ... }: { - environment.systemPackages = [ - disko.packages.${system}.default - ]; - isoImage.storeContents = [ - self.packages.x86_64-linux.radiantwavekiosk-commercial - ]; - environment.etc."disko-config.nix".source = ./commercial-disk-config.nix; - }) - ]; + commercial-iso = mkIso { + targetConfig = "radiantwavekiosk-commercial"; + diskModule = ./commercial-disk-config.nix; }; - # kiosk rotated screen ISO - android-iso = 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 - ./android-iso.nix - ./disk-config.nix - ({ pkgs, ... }: { - environment.systemPackages = [ - disko.packages.${system}.default - ]; - isoImage.storeContents = [ - self.packages.x86_64-linux.radiantwavekiosk-android - ]; - environment.etc."disko-config.nix".source = ./disk-config.nix; - }) - ]; + android-iso = mkIso { + targetConfig = "radiantwavekiosk-android"; + diskModule = ./disk-config.nix; }; - # Kiosk iso development branch - dev-iso = 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 - ./dev-iso.nix - ./disk-config.nix - ({ pkgs, ... }: { - environment.systemPackages = [ - disko.packages.${system}.default - ]; - isoImage.storeContents = [ - self.packages.x86_64-linux.radiantwavekiosk-dev - ]; - environment.etc."disko-config.nix".source = ./disk-config.nix; - }) - ]; + dev-iso = mkIso { + targetConfig = "radiantwavekiosk-dev"; + diskModule = ./disk-config.nix; }; - # Kiosk iso development branch - commercial-dev-iso = 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 - ./dev-iso.nix - ./disk-config.nix - ({ pkgs, ... }: { - environment.systemPackages = [ - disko.packages.${system}.default - ]; - isoImage.storeContents = [ - self.packages.x86_64-linux.radiantwavekiosk-commercial-dev - ]; - environment.etc."disko-config.nix".source = ./commercial-disk-config.nix; - }) - ]; + commercial-dev-iso = mkIso { + targetConfig = "radiantwavekiosk-commercial-dev"; + diskModule = ./commercial-disk-config.nix; }; - # Kiosk-dev rotated display iso - android-dev-iso = 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 - ./dev-iso.nix - ./disk-config.nix - ({ pkgs, ... }: { - environment.systemPackages = [ - disko.packages.${system}.default - ]; - isoImage.storeContents = [ - self.packages.x86_64-linux.radiantwavekiosk-android-dev - ]; - environment.etc."disko-config.nix".source = ./disk-config.nix; - }) - ]; + android-dev-iso = mkIso { + targetConfig = "radiantwavekiosk-android-dev"; + diskModule = ./disk-config.nix; }; - }; }; } diff --git a/iso-config.nix b/zero-touch-installer.nix similarity index 63% rename from iso-config.nix rename to zero-touch-installer.nix index cd65196..42df4c7 100644 --- a/iso-config.nix +++ b/zero-touch-installer.nix @@ -1,3 +1,13 @@ +# Parameterized zero-touch installer for kiosk USB images. +# +# Usage in flake.nix: +# (import ./zero-touch-installer.nix { targetConfig = "radiantwavekiosk"; }) +# +# `targetConfig` must name an entry in self.nixosConfigurations. The target +# closure and its disko partitioning script are derived from that config, so +# an image can never install a mismatched system or format the wrong disk +# layout as long as flake.nix pairs each ISO with its disk-config.nix. +{ targetConfig }: { pkgs, inputs, @@ -5,17 +15,15 @@ ... }: let - targetSystem = - self.nixosConfigurations.radiantwavekiosk.config.system.build.toplevel; + cfg = self.nixosConfigurations.${targetConfig}; + targetSystem = cfg.config.system.build.toplevel; + diskoScript = cfg.config.system.build.diskoScript; - diskoScript = - self.nixosConfigurations.radiantwavekiosk.config.system.build.diskoScript; + # Keep in sync with myKey in configuration.nix. + adminKey = + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL4qWNZnYU5JaqR9c2qh6oFZktzVNBOx42PfDaGofCL6 cnclack@pm.me macbook"; in { - ############################################################ - # System configuration - ############################################################ - nix.settings = { experimental-features = [ "nix-command" @@ -38,32 +46,11 @@ in }; }; - nix.extraOptions = '' - netrc-file = /etc/nix/netrc - ''; - environment.variables.KIOSK_FLAKE = "${self}"; - environment.etc."nix/netrc" = { - text = '' - password eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIwODgzODk2ODEsIm5iZiI6MTc3MjgxMzY4MSwic3ViIjoia2lvc2siLCJodHRwczovL2p3dC5hdHRpYy5ycy92MSI6eyJjYWNoZXMiOnsia2lvc2siOnsiciI6MX19fX0.N6Ktrmmu1LSy2PP8uNF25VHRP1dxPZJ4M0MR1kUpVi0 - ''; - mode = "0400"; - }; - networking.networkmanager.enable = true; - ############################################################ - # User configuration - ############################################################ - - users.users.root.openssh.authorizedKeys.keys = [ - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL4qWNZnYU5JaqR9c2qh6oFZktzVNBOx42PfDaGofCL6 cnclack@pm.me macbook" - ]; - - ############################################################ - # Service configuration - ############################################################ + users.users.root.openssh.authorizedKeys.keys = [ adminKey ]; services.openssh.enable = true; @@ -115,7 +102,7 @@ in ${diskoScript} - echo "Installing RadiantWave" + echo "Installing RadiantWave (${targetConfig})" echo "Closure Path: ${targetSystem}" nixos-install \ @@ -127,7 +114,7 @@ in echo " Installation complete!" echo " Please shut down and remove the flash drive before restarting" echo "============================================" - sleep 10000000 + sleep infinity ''; }; -} \ No newline at end of file +}