From 6fa3c0552b5eaacc6e79b6e79306ee89a2678b57 Mon Sep 17 00:00:00 2001 From: NeilClack Date: Sat, 3 Oct 2026 12:07:07 -0500 Subject: [PATCH] Declare app version per channel and fix Android live updates - Pin the radiantwave input to tag 1.8 and add channel.appVersion; override the package version so pure builds bake 1.8 instead of RadiantWave's 0.1.0 default. - radiantwaveUpdaterModule gains a manifestFile arg; Raspberry Pi polls aarch64-manifest.json. - Launch the rotated/Android kiosk through /var/lib/radiantwave/current so the live updater actually applies. - Allow unfree in the RPi builder to match x86 and drop the impure NIXPKGS_ALLOW_UNFREE dependency. --- AGENTS.md | 21 +++++++++++-------- configuration.nix | 4 ++-- flake.lock | 12 +++++------ flake.nix | 51 +++++++++++++++++++++++++++++++-------------- rotated_display.nix | 13 +++++++++--- 5 files changed, 65 insertions(+), 36 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 589965f..691c991 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,10 +10,12 @@ Hosted on Gitea: `https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig` Two branches — `main` (prod) and `dev`. There are **no `-dev` configs**: each hardware target has exactly one config, and the branch selects its channel via the single `channel` block at the top of `flake.nix`: -- `main`: `channel.appInput = radiantwave` (main), `atticRepo = "radiantwave"`, `gitRef = ""` (default branch). -- `dev`: `channel.appInput = radiantwave-dev`, `atticRepo = "radiantwave-dev"`, `gitRef = "?ref=dev"`. +- `main`: `channel.appInput = radiantwave` (pinned to tag `1.8`), `atticRepo = "radiantwave"`, `gitRef = ""` (default branch), `appVersion = "1.8"`. +- `dev`: `channel.appInput = radiantwave-dev`, `atticRepo = "radiantwave-dev"`, `gitRef = "?ref=dev"`, `appVersion = `. -Everything downstream (the configs, ISOs, updater module, autoUpgrade refs) reads from `channel`, so a `dev`→`main` merge carries your changes automatically — no copying between sibling configs. To flip a channel, change only those three `channel` values. When merging, reconcile just this one block. +Everything downstream (the configs, ISOs, updater module, autoUpgrade refs) reads from `channel`, so a `dev`→`main` merge carries your changes automatically — no copying between sibling configs. To flip a channel, change only those four `channel` values. When merging, reconcile just this one block. + +`channel.appVersion` is applied to the baked app via `appPackage = system: channel.appInput.packages.${system}.default.override { version = channel.appVersion; }`. RadiantWave's own flake only injects its version from the ambient `APP_VERSION` env var on impure evals, so pure evals (local `nix build`, `build-rpi.sh`) fall back to `0.1.0`. Declaring the version here keeps local and CI builds correct without `--impure`, and the derivation matches the Attic closure when RadiantWave CI built it with the same version string. Bump `appVersion` whenever the pinned input changes (release tag on `main`, short commit hash on `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. @@ -42,10 +44,10 @@ One config per hardware target. Dev/prod is the branch, not the config — see ` | Name | Target hardware | Disk device | App input | |---|---|---|---| -| `radiantwavekiosk` | Intel NUC 14 (NVMe) | `/dev/nvme0n1` | `radiantwave` (main) | -| `radiantwavekiosk-commercial` | Commercial HW (SATA) | `/dev/sda` | `radiantwave` (main) | -| `radiantwavekiosk-android` | Intel NUC 14, rotated display | `/dev/nvme0n1` | `radiantwave` (main) | -| `radiantwavekiosk-rpi` | Raspberry Pi 5 (SD image) | — | `radiantwave` (main) | +| `radiantwavekiosk` | Intel NUC 14 (NVMe) | `/dev/nvme0n1` | `radiantwave` (tag 1.8) | +| `radiantwavekiosk-commercial` | Commercial HW (SATA) | `/dev/sda` | `radiantwave` (tag 1.8) | +| `radiantwavekiosk-android` | Intel NUC 14, rotated display | `/dev/nvme0n1` | `radiantwave` (tag 1.8) | +| `radiantwavekiosk-rpi` | Raspberry Pi 5 (SD image) | — | `radiantwave` (tag 1.8) | On the `dev` branch, the same four configs use `radiantwave-dev` and track `?ref=dev`. @@ -92,12 +94,13 @@ Regenerate when major hardware changes, not on a schedule — reports are snapsh - **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). -- **The `channel` block is the dev/prod toggle** in `flake.nix` — `appInput`, `atticRepo`, `gitRef`. When merging `dev`→`main`, keep this block's values for the target branch; only this block differs between branches. +- **The `channel` block is the dev/prod toggle** in `flake.nix` — `appInput`, `atticRepo`, `gitRef`, `appVersion`. When merging `dev`→`main`, keep this block's values for the target branch; only this block differs between branches. - **`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`. +- **Live app updater**: each config's `radiantwaveUpdaterModule` points at a per-arch manifest — x86 uses `/manifest.json`, RPi uses `/aarch64-manifest.json` (via the `manifestFile` arg). A 2-min timer polls it and fetches the announced closure from the channel's Attic repo. RPi live updates require RadiantWave CI to publish both an aarch64 closure and `aarch64-manifest.json`; until then the RPi stays on the baked baseline. - **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`. The `channel` block picks which one the configs consume. Updating them independently is intentional. +- **Flake inputs**: `radiantwave` is pinned to tag `1.8` (`?ref=refs/tags/1.8`); `radiantwave-dev` tracks `?ref=dev`. The `channel` block picks which one the configs consume. Bump the pinned tag and `channel.appVersion` together. - **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 diff --git a/configuration.nix b/configuration.nix index ca58231..58678a8 100644 --- a/configuration.nix +++ b/configuration.nix @@ -1,7 +1,7 @@ -{ config, pkgs, radiantwave, upgradeFlakeRef, radiantwavePlymouth, ... }: +{ config, pkgs, radiantwaveApp, upgradeFlakeRef, radiantwavePlymouth, ... }: let - radiantApp = radiantwave.packages.${pkgs.system}.default; + radiantApp = radiantwaveApp; # When the live-updater module is enabled, RadiantWave launches through the # mutable /var/lib/radiantwave/current # pointer so it can be swapped without a full nixos-rebuild. Otherwise it diff --git a/flake.lock b/flake.lock index 83d88a0..316d65b 100644 --- a/flake.lock +++ b/flake.lock @@ -220,16 +220,16 @@ "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1789157302, - "narHash": "sha256-9wOHFEkFd7Lz1cJoUWBksgzTB+RP6sdtiF3hpyOrnQY=", - "ref": "main", - "rev": "a043ec84d8c421f6fc2abb064da2836449010868", - "revCount": 64, + "lastModified": 1790803867, + "narHash": "sha256-HRGQQlmn/kCsZIq1wBMFaW6q1xfSvwPhUS5CpG1mm+Q=", + "ref": "refs/tags/1.8", + "rev": "34c72b51ddc3da8d46d994e787ffd1f8e8dda77c", + "revCount": 85, "type": "git", "url": "https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git" }, "original": { - "ref": "main", + "ref": "refs/tags/1.8", "type": "git", "url": "https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git" } diff --git a/flake.nix b/flake.nix index 56d80e6..defc28e 100644 --- a/flake.nix +++ b/flake.nix @@ -4,7 +4,7 @@ disko.url = "github:nix-community/disko"; disko.inputs.nixpkgs.follows = "nixpkgs"; nixos-facter-modules.url = "github:numtide/nixos-facter-modules"; - radiantwave.url = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git?ref=main"; + radiantwave.url = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git?ref=refs/tags/1.8"; radiantwave-dev.url = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git?ref=dev"; nixos-raspberrypi.url = "github:nvmd/nixos-raspberrypi/main"; }; @@ -62,16 +62,17 @@ # this channel, so a `dev`-to-`main` merge carries your changes with it # automatically. There are no more `-dev` sibling configs to copy. # - # main: appInput = radiantwave (main), atticRepo = "radiantwave", - # gitRef = "" (default branch) - # dev: appInput = radiantwave-dev, atticRepo = "radiantwave-dev", - # gitRef = "?ref=dev" + # main: appInput = radiantwave (tag 1.8), atticRepo = "radiantwave", + # gitRef = "", appVersion = "1.8" + # dev: appInput = radiantwave-dev, atticRepo = "radiantwave-dev", + # gitRef = "?ref=dev", appVersion = # - # To flip branches, change ONLY these three values (then push/merge). + # To flip branches, change ONLY these four values (then push/merge). channel = { appInput = radiantwave; # main atticRepo = "radiantwave"; # main gitRef = ""; # main + appVersion = "1.8"; # main }; # Exposed so CI (see .gitea/workflows/build-prod-*.yml) and humans can @@ -82,16 +83,28 @@ # instead of silently shipping dev builds as prod ISOs. channelName = if channel.appInput == radiantwave then "radiantwave" else "radiantwave-dev"; + # The App package, pinned to the channel's declared version. RadiantWave's + # own flake only injects its version from the ambient APP_VERSION env var + # on impure evals; pure evals (local `nix build`, build-rpi.sh) fall back + # to its 0.1.0 default. Overriding the version here makes the baked + # baseline correct regardless of eval purity, and matches the Attic + # closure when RadiantWave CI built it with the same version string. + appPackage = system: channel.appInput.packages.${system}.default.override { + version = channel.appVersion; + }; + # 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). All variants follow this same update - # procedure; only the channel differs. - radiantwaveUpdaterModule = { repo, baseline }: { + # "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; baselinePackage = baseline; - manifestUrl = "https://files.radiantwavetech.com/${repo}/manifest.json"; + manifestUrl = "https://files.radiantwavetech.com/${repo}/${manifestFile}"; atticUrl = "https://attic.radiantwavetech.com/${repo}"; }; }; @@ -108,8 +121,8 @@ inherit system; specialArgs = { - inherit radiantwave; - inherit upgradeFlakeRef; + inherit radiantwave upgradeFlakeRef; + radiantwaveApp = appPackage system; radiantwavePlymouth = mkRadiantwavePlymouth; }; @@ -145,10 +158,12 @@ specialArgs = { inherit radiantwave upgradeFlakeRef; inherit nixos-raspberrypi; + radiantwaveApp = appPackage "aarch64-linux"; radiantwavePlymouth = mkRadiantwavePlymouth; }; modules = [ + { nixpkgs.config.allowUnfree = true; } ({ ... }: { imports = with nixos-raspberrypi.nixosModules; [ sd-image @@ -232,7 +247,7 @@ diskModule = ./disk-config.nix; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk"; extraModules = [ - (radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = channel.appInput.packages.x86_64-linux.default; }) + (radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = appPackage "x86_64-linux"; }) ]; }; radiantwavekiosk-commercial = mkKioskSystem { @@ -242,7 +257,7 @@ diskModule = ./commercial-disk-config.nix; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-commercial"; extraModules = [ - (radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = channel.appInput.packages.x86_64-linux.default; }) + (radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = appPackage "x86_64-linux"; }) ]; }; # Rotated screen for android-style displays. @@ -254,7 +269,7 @@ upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-android"; extraModules = [ ./rotated_display.nix - (radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = channel.appInput.packages.x86_64-linux.default; }) + (radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = appPackage "x86_64-linux"; }) ]; }; # Raspberry Pi 5. @@ -263,7 +278,11 @@ hostName = "radiantwavekiosk-rpi"; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-rpi"; extraModules = [ - (radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = channel.appInput.packages.aarch64-linux.default; }) + (radiantwaveUpdaterModule { + repo = channel.atticRepo; + baseline = appPackage "aarch64-linux"; + manifestFile = "aarch64-manifest.json"; + }) ]; }; diff --git a/rotated_display.nix b/rotated_display.nix index b9c696f..65af47c 100644 --- a/rotated_display.nix +++ b/rotated_display.nix @@ -1,6 +1,13 @@ -{ pkgs, radiantwave, ...}: +{ config, pkgs, radiantwaveApp, ...}: let - radiantApp = radiantwave.packages.${pkgs.system}.default; + radiantApp = radiantwaveApp; + # Match configuration.nix: when the live updater is enabled, launch through + # the mutable /var/lib/radiantwave/current pointer so Android kiosks receive + # app updates instead of always running the baked baseline. + launchBin = + if config.services.radiantwaveUpdater.enable or false + then "/var/lib/radiantwave/current/bin/radiantwave" + else "${radiantApp}/bin/radiantwave"; kioskscript = pkgs.writeShellScript "kiosk-start" '' # Wait for the Wayland display to be ready for i in {1..10}; do @@ -13,7 +20,7 @@ let OUTPUT=$(${pkgs.wlr-randr}/bin/wlr-randr | grep -m1 '^\S' | awk '{print $1}') echo "Rotating output: $OUTPUT" >> /tmp/kiosk.log ${pkgs.wlr-randr}/bin/wlr-randr --output "$OUTPUT" --transform 90 >> /tmp/kiosk.log 2>&1 || echo "wlr-randr failed" >> /tmp/kiosk.log - exec ${radiantApp}/bin/radiantwave -d -s + exec ${launchBin} -d -s ''; in {