2 Commits
Author SHA1 Message Date
NeilClack 1ec9fd81e5 Pin moved 1.8 tag and guard the baked baseline against the published channel
build-prod-commercial.yml / build-commercial (push) Canceled after 0s
build-prod-android.yml / build-android (push) Successful in 3m44s
build-prod-kiosk.yml / build-kiosk (push) Successful in 3m39s
The radiantwave 1.8 tag was force-moved from 34c72b5 to a5a6b5b while
flake.lock still pinned the old commit, so the ISO baked a baseline that
was not the closure RadiantWave CI published to Attic. With version 1.8
overridden this could still compile RadiantWave from source instead of
substituting it.

- Update flake.lock so radiantwave?ref=refs/tags/1.8 resolves to a5a6b5b.
- Expose channelVersion/channelAtticUrl/channelManifestDir/baselineOutPaths
  from flake.nix for CI verification.
- Prod workflows: fail fast unless channel.appVersion equals the channel
  manifest version, baselineOutPaths.<system> equals the manifest storePath,
  and that store path exists in the channel's Attic cache.
2026-10-04 15:50:48 -05:00
NeilClack 6fa3c0552b 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.
2026-10-03 12:07:07 -05:00
9 changed files with 248 additions and 38 deletions
+41
View File
@@ -28,6 +28,47 @@ jobs:
exit 1 exit 1
fi fi
# Fail fast unless the closure baked into the ISO is exactly what
# RadiantWave CI published to Attic. Otherwise the kiosk compiles
# RadiantWave from source (slow) and the live updater immediately swaps
# to the announced closure on first boot. See `channel` in flake.nix.
- name: Verify baked baseline matches the published channel
run: |
set -euo pipefail
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
VERSION=$(nix eval --impure --raw .#channelVersion)
ATTIC=$(nix eval --impure --raw .#channelAtticUrl)
MANIFEST_URL="$(nix eval --impure --raw .#channelManifestDir)/manifest.json"
BASELINE=$(nix eval --impure --raw .#baselineOutPaths.x86_64-linux)
MANIFEST=$(curl -fsSL "$MANIFEST_URL")
PUB_VERSION=$(MANIFEST="$MANIFEST" nix eval --impure --raw --expr '(builtins.fromJSON (builtins.getEnv "MANIFEST")).version')
PUB_PATH=$(MANIFEST="$MANIFEST" nix eval --impure --raw --expr '(builtins.fromJSON (builtins.getEnv "MANIFEST")).storePath')
echo "channel version : $VERSION"
echo "manifest version: $PUB_VERSION"
echo "baseline path : $BASELINE"
echo "manifest path : $PUB_PATH"
if [ "$VERSION" != "$PUB_VERSION" ]; then
echo "ERROR: channel.appVersion '$VERSION' != published manifest version '$PUB_VERSION'."
echo " Bump channel.appVersion (and the pinned radiantwave input) to match the release."
exit 1
fi
if [ "$BASELINE" != "$PUB_PATH" ]; then
echo "ERROR: baseline '$BASELINE' is not the published closure '$PUB_PATH'."
echo " Run 'nix flake update radiantwave' so the pinned input resolves to the release the manifest was built from."
exit 1
fi
HASH=$(basename "$BASELINE" | cut -d- -f1)
if ! curl -fsS -o /dev/null "$ATTIC/${HASH}.narinfo"; then
echo "ERROR: $BASELINE is not in $ATTIC; the ISO would compile RadiantWave from source."
exit 1
fi
- name: Prepare output directory - name: Prepare output directory
run: mkdir -p /var/lib/gitea-runner/radiantwave run: mkdir -p /var/lib/gitea-runner/radiantwave
@@ -28,6 +28,47 @@ jobs:
exit 1 exit 1
fi fi
# Fail fast unless the closure baked into the ISO is exactly what
# RadiantWave CI published to Attic. Otherwise the kiosk compiles
# RadiantWave from source (slow) and the live updater immediately swaps
# to the announced closure on first boot. See `channel` in flake.nix.
- name: Verify baked baseline matches the published channel
run: |
set -euo pipefail
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
VERSION=$(nix eval --impure --raw .#channelVersion)
ATTIC=$(nix eval --impure --raw .#channelAtticUrl)
MANIFEST_URL="$(nix eval --impure --raw .#channelManifestDir)/manifest.json"
BASELINE=$(nix eval --impure --raw .#baselineOutPaths.x86_64-linux)
MANIFEST=$(curl -fsSL "$MANIFEST_URL")
PUB_VERSION=$(MANIFEST="$MANIFEST" nix eval --impure --raw --expr '(builtins.fromJSON (builtins.getEnv "MANIFEST")).version')
PUB_PATH=$(MANIFEST="$MANIFEST" nix eval --impure --raw --expr '(builtins.fromJSON (builtins.getEnv "MANIFEST")).storePath')
echo "channel version : $VERSION"
echo "manifest version: $PUB_VERSION"
echo "baseline path : $BASELINE"
echo "manifest path : $PUB_PATH"
if [ "$VERSION" != "$PUB_VERSION" ]; then
echo "ERROR: channel.appVersion '$VERSION' != published manifest version '$PUB_VERSION'."
echo " Bump channel.appVersion (and the pinned radiantwave input) to match the release."
exit 1
fi
if [ "$BASELINE" != "$PUB_PATH" ]; then
echo "ERROR: baseline '$BASELINE' is not the published closure '$PUB_PATH'."
echo " Run 'nix flake update radiantwave' so the pinned input resolves to the release the manifest was built from."
exit 1
fi
HASH=$(basename "$BASELINE" | cut -d- -f1)
if ! curl -fsS -o /dev/null "$ATTIC/${HASH}.narinfo"; then
echo "ERROR: $BASELINE is not in $ATTIC; the ISO would compile RadiantWave from source."
exit 1
fi
- name: Prepare output directory - name: Prepare output directory
run: mkdir -p /var/lib/gitea-runner/radiantwave run: mkdir -p /var/lib/gitea-runner/radiantwave
+41
View File
@@ -28,6 +28,47 @@ jobs:
exit 1 exit 1
fi fi
# Fail fast unless the closure baked into the ISO is exactly what
# RadiantWave CI published to Attic. Otherwise the kiosk compiles
# RadiantWave from source (slow) and the live updater immediately swaps
# to the announced closure on first boot. See `channel` in flake.nix.
- name: Verify baked baseline matches the published channel
run: |
set -euo pipefail
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
VERSION=$(nix eval --impure --raw .#channelVersion)
ATTIC=$(nix eval --impure --raw .#channelAtticUrl)
MANIFEST_URL="$(nix eval --impure --raw .#channelManifestDir)/manifest.json"
BASELINE=$(nix eval --impure --raw .#baselineOutPaths.x86_64-linux)
MANIFEST=$(curl -fsSL "$MANIFEST_URL")
PUB_VERSION=$(MANIFEST="$MANIFEST" nix eval --impure --raw --expr '(builtins.fromJSON (builtins.getEnv "MANIFEST")).version')
PUB_PATH=$(MANIFEST="$MANIFEST" nix eval --impure --raw --expr '(builtins.fromJSON (builtins.getEnv "MANIFEST")).storePath')
echo "channel version : $VERSION"
echo "manifest version: $PUB_VERSION"
echo "baseline path : $BASELINE"
echo "manifest path : $PUB_PATH"
if [ "$VERSION" != "$PUB_VERSION" ]; then
echo "ERROR: channel.appVersion '$VERSION' != published manifest version '$PUB_VERSION'."
echo " Bump channel.appVersion (and the pinned radiantwave input) to match the release."
exit 1
fi
if [ "$BASELINE" != "$PUB_PATH" ]; then
echo "ERROR: baseline '$BASELINE' is not the published closure '$PUB_PATH'."
echo " Run 'nix flake update radiantwave' so the pinned input resolves to the release the manifest was built from."
exit 1
fi
HASH=$(basename "$BASELINE" | cut -d- -f1)
if ! curl -fsS -o /dev/null "$ATTIC/${HASH}.narinfo"; then
echo "ERROR: $BASELINE is not in $ATTIC; the ISO would compile RadiantWave from source."
exit 1
fi
- name: Prepare output directory - name: Prepare output directory
run: mkdir -p /var/lib/gitea-runner/radiantwave run: mkdir -p /var/lib/gitea-runner/radiantwave
+41
View File
@@ -25,6 +25,47 @@ jobs:
exit 1 exit 1
fi fi
# Fail fast unless the closure baked into the image is exactly what
# RadiantWave CI published to Attic (aarch64). Otherwise the Pi compiles
# RadiantWave from source (slow) unless RadiantWave CI has published both
# the aarch64 closure and aarch64-manifest.json. See `channel` in flake.nix.
- name: Verify baked baseline matches the published channel
run: |
set -euo pipefail
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
VERSION=$(nix eval --impure --raw .#channelVersion)
ATTIC=$(nix eval --impure --raw .#channelAtticUrl)
MANIFEST_URL="$(nix eval --impure --raw .#channelManifestDir)/aarch64-manifest.json"
BASELINE=$(nix eval --impure --raw .#baselineOutPaths.aarch64-linux)
MANIFEST=$(curl -fsSL "$MANIFEST_URL")
PUB_VERSION=$(MANIFEST="$MANIFEST" nix eval --impure --raw --expr '(builtins.fromJSON (builtins.getEnv "MANIFEST")).version')
PUB_PATH=$(MANIFEST="$MANIFEST" nix eval --impure --raw --expr '(builtins.fromJSON (builtins.getEnv "MANIFEST")).storePath')
echo "channel version : $VERSION"
echo "manifest version: $PUB_VERSION"
echo "baseline path : $BASELINE"
echo "manifest path : $PUB_PATH"
if [ "$VERSION" != "$PUB_VERSION" ]; then
echo "ERROR: channel.appVersion '$VERSION' != published manifest version '$PUB_VERSION'."
echo " Bump channel.appVersion (and the pinned radiantwave input) to match the release."
exit 1
fi
if [ "$BASELINE" != "$PUB_PATH" ]; then
echo "ERROR: baseline '$BASELINE' is not the published closure '$PUB_PATH'."
echo " Run 'nix flake update radiantwave' so the pinned input resolves to the release the manifest was built from."
exit 1
fi
HASH=$(basename "$BASELINE" | cut -d- -f1)
if ! curl -fsS -o /dev/null "$ATTIC/${HASH}.narinfo"; then
echo "ERROR: $BASELINE is not in $ATTIC; the image would compile RadiantWave from source."
exit 1
fi
- name: Prepare output directory - name: Prepare output directory
run: mkdir -p /var/lib/gitea-runner/radiantwave run: mkdir -p /var/lib/gitea-runner/radiantwave
+15 -10
View File
@@ -10,10 +10,14 @@ 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`: 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). - `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"`. - `dev`: `channel.appInput = radiantwave-dev`, `atticRepo = "radiantwave-dev"`, `gitRef = "?ref=dev"`, `appVersion = <dev fingerprint>`.
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`. Bump `appVersion` whenever the pinned input changes (release tag on `main`, short commit hash on `dev`).
The baked baseline must be **byte-identical to the closure RadiantWave CI publishes to Attic** — that is what lets the ISO/`sdImage` substitute RadiantWave instead of compiling it from source, and makes the first live update a no-op. The prod workflows assert this in "Verify baked baseline matches the published channel": it compares `channelVersion` to the manifest's `version`, `baselineOutPaths.<system>` to the manifest's `storePath`, and confirms that store path exists in the channel's Attic cache. If it fails, the usual cause is a **moved release tag**: `radiantwave` is pinned by tag ref in `flake.lock`, so re-tagging `1.8` (as happened when it moved from `34c72b5` to `a5a6b5b`) leaves the lock on the old commit until you run `nix flake update radiantwave`. The RadiantWave flake also includes `.gitea/` in the app source, so its CI workflow changes alter the derivation hash.
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. 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 +46,10 @@ One config per hardware target. Dev/prod is the branch, not the config — see `
| Name | Target hardware | Disk device | App input | | Name | Target hardware | Disk device | App input |
|---|---|---|---| |---|---|---|---|
| `radiantwavekiosk` | Intel NUC 14 (NVMe) | `/dev/nvme0n1` | `radiantwave` (main) | | `radiantwavekiosk` | Intel NUC 14 (NVMe) | `/dev/nvme0n1` | `radiantwave` (tag 1.8) |
| `radiantwavekiosk-commercial` | Commercial HW (SATA) | `/dev/sda` | `radiantwave` (main) | | `radiantwavekiosk-commercial` | Commercial HW (SATA) | `/dev/sda` | `radiantwave` (tag 1.8) |
| `radiantwavekiosk-android` | Intel NUC 14, rotated display | `/dev/nvme0n1` | `radiantwave` (main) | | `radiantwavekiosk-android` | Intel NUC 14, rotated display | `/dev/nvme0n1` | `radiantwave` (tag 1.8) |
| `radiantwavekiosk-rpi` | Raspberry Pi 5 (SD image) | — | `radiantwave` (main) | | `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`. On the `dev` branch, the same four configs use `radiantwave-dev` and track `?ref=dev`.
@@ -86,18 +90,19 @@ Regenerate when major hardware changes, not on a schedule — reports are snapsh
## CI (Gitea) ## CI (Gitea)
`.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. `.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. The prod workflows run a "Verify baked baseline matches the published channel" preflight that fails the build if the baked closure diverges from the channel manifest or is missing from Attic (see the `channel` block and `baselineOutPaths` in `flake.nix`).
## Important gotchas ## Important gotchas
- **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. - **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). - **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. - **`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`. - **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 `<repo>/manifest.json`, RPi uses `<repo>/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. - **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`. - **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. **Tag refs are live**: if RadiantWave force-moves a tag, the lock keeps the old commit until `nix flake update radiantwave`, so the baked baseline silently diverges from the published channel (the CI preflight catches this).
- **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`. - **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 ## File map
+2 -2
View File
@@ -1,7 +1,7 @@
{ config, pkgs, radiantwave, upgradeFlakeRef, radiantwavePlymouth, ... }: { config, pkgs, radiantwaveApp, upgradeFlakeRef, radiantwavePlymouth, ... }:
let let
radiantApp = radiantwave.packages.${pkgs.system}.default; radiantApp = radiantwaveApp;
# When the live-updater module is enabled, RadiantWave launches through the # When the live-updater module is enabled, RadiantWave launches through the
# mutable /var/lib/radiantwave/current # mutable /var/lib/radiantwave/current
# pointer so it can be swapped without a full nixos-rebuild. Otherwise it # pointer so it can be swapped without a full nixos-rebuild. Otherwise it
Generated
+6 -6
View File
@@ -220,16 +220,16 @@
"nixpkgs": "nixpkgs_3" "nixpkgs": "nixpkgs_3"
}, },
"locked": { "locked": {
"lastModified": 1789157302, "lastModified": 1791050336,
"narHash": "sha256-9wOHFEkFd7Lz1cJoUWBksgzTB+RP6sdtiF3hpyOrnQY=", "narHash": "sha256-sb/cDFS55qodbU6GEydhyRyW8VBTGg110ejSXVNd2OY=",
"ref": "main", "ref": "refs/tags/1.8",
"rev": "a043ec84d8c421f6fc2abb064da2836449010868", "rev": "a5a6b5bef07fffb5ec9597e577e6fb8a4b2a0108",
"revCount": 64, "revCount": 87,
"type": "git", "type": "git",
"url": "https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git" "url": "https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git"
}, },
"original": { "original": {
"ref": "main", "ref": "refs/tags/1.8",
"type": "git", "type": "git",
"url": "https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git" "url": "https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git"
} }
+51 -17
View File
@@ -4,7 +4,7 @@
disko.url = "github:nix-community/disko"; disko.url = "github:nix-community/disko";
disko.inputs.nixpkgs.follows = "nixpkgs"; disko.inputs.nixpkgs.follows = "nixpkgs";
nixos-facter-modules.url = "github:numtide/nixos-facter-modules"; 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"; radiantwave-dev.url = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git?ref=dev";
nixos-raspberrypi.url = "github:nvmd/nixos-raspberrypi/main"; 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 # this channel, so a `dev`-to-`main` merge carries your changes with it
# automatically. There are no more `-dev` sibling configs to copy. # automatically. There are no more `-dev` sibling configs to copy.
# #
# main: appInput = radiantwave (main), atticRepo = "radiantwave", # main: appInput = radiantwave (tag 1.8), atticRepo = "radiantwave",
# gitRef = "" (default branch) # gitRef = "", appVersion = "1.8"
# dev: appInput = radiantwave-dev, atticRepo = "radiantwave-dev", # dev: appInput = radiantwave-dev, atticRepo = "radiantwave-dev",
# gitRef = "?ref=dev" # gitRef = "?ref=dev", appVersion = <dev fingerprint>
# #
# To flip branches, change ONLY these three values (then push/merge). # To flip branches, change ONLY these four values (then push/merge).
channel = { channel = {
appInput = radiantwave; # main appInput = radiantwave; # main
atticRepo = "radiantwave"; # main atticRepo = "radiantwave"; # main
gitRef = ""; # main gitRef = ""; # main
appVersion = "1.8"; # main
}; };
# Exposed so CI (see .gitea/workflows/build-prod-*.yml) and humans can # Exposed so CI (see .gitea/workflows/build-prod-*.yml) and humans can
@@ -82,16 +83,43 @@
# instead of silently shipping dev builds as prod ISOs. # instead of silently shipping dev builds as prod ISOs.
channelName = if channel.appInput == radiantwave then "radiantwave" else "radiantwave-dev"; channelName = if channel.appInput == radiantwave then "radiantwave" else "radiantwave-dev";
# Channel metadata exposed for CI preflight (see .gitea/workflows/build-*).
# `channelVersion` must equal the version RadiantWave CI publishes in the
# channel manifest; `baselineOutPaths` must equal the manifest's
# `storePath`. When they do, the ISO bakes exactly the closure already in
# Attic, so the app is substituted (never compiled locally) and the first
# live update is a no-op. A mismatch means either the pinned input is
# stale (as when the release tag moves) or channel.appVersion drifted.
channelVersion = channel.appVersion;
channelAtticUrl = "https://attic.radiantwavetech.com/${channel.atticRepo}";
channelManifestDir = "https://files.radiantwavetech.com/${channel.atticRepo}";
baselineOutPaths = {
x86_64-linux = (appPackage "x86_64-linux").outPath;
aarch64-linux = (appPackage "aarch64-linux").outPath;
};
# 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 # Live app-update side channel. The manifest is served by the
# fileserver; closures are fetched (never built) from the channel's # fileserver; closures are fetched (never built) from the channel's
# Attic cache. `repo` selects the channel — "radiantwave" (main) or # Attic cache. `repo` selects the channel — "radiantwave" (main) or
# "radiantwave-dev" (dev). All variants follow this same update # "radiantwave-dev" (dev) — and `manifestFile` selects the per-arch
# procedure; only the channel differs. # manifest (default "manifest.json"; Raspberry Pi uses
radiantwaveUpdaterModule = { repo, baseline }: { # "aarch64-manifest.json"). All variants follow this same update
# procedure; only the channel/arch differ.
radiantwaveUpdaterModule = { repo, baseline, manifestFile ? "manifest.json" }: {
services.radiantwaveUpdater = { services.radiantwaveUpdater = {
enable = true; enable = true;
baselinePackage = baseline; baselinePackage = baseline;
manifestUrl = "https://files.radiantwavetech.com/${repo}/manifest.json"; manifestUrl = "https://files.radiantwavetech.com/${repo}/${manifestFile}";
atticUrl = "https://attic.radiantwavetech.com/${repo}"; atticUrl = "https://attic.radiantwavetech.com/${repo}";
}; };
}; };
@@ -108,8 +136,8 @@
inherit system; inherit system;
specialArgs = { specialArgs = {
inherit radiantwave; inherit radiantwave upgradeFlakeRef;
inherit upgradeFlakeRef; radiantwaveApp = appPackage system;
radiantwavePlymouth = mkRadiantwavePlymouth; radiantwavePlymouth = mkRadiantwavePlymouth;
}; };
@@ -145,10 +173,12 @@
specialArgs = { specialArgs = {
inherit radiantwave upgradeFlakeRef; inherit radiantwave upgradeFlakeRef;
inherit nixos-raspberrypi; inherit nixos-raspberrypi;
radiantwaveApp = appPackage "aarch64-linux";
radiantwavePlymouth = mkRadiantwavePlymouth; radiantwavePlymouth = mkRadiantwavePlymouth;
}; };
modules = [ modules = [
{ nixpkgs.config.allowUnfree = true; }
({ ... }: { ({ ... }: {
imports = with nixos-raspberrypi.nixosModules; [ imports = with nixos-raspberrypi.nixosModules; [
sd-image sd-image
@@ -209,7 +239,7 @@
}; };
in in
{ {
inherit channelName; inherit channelName channelVersion channelAtticUrl channelManifestDir baselineOutPaths;
packages.x86_64-linux.radiantwavekiosk = self.nixosConfigurations.radiantwavekiosk.config.system.build.toplevel; 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-commercial = self.nixosConfigurations.radiantwavekiosk-commercial.config.system.build.toplevel;
@@ -232,7 +262,7 @@
diskModule = ./disk-config.nix; diskModule = ./disk-config.nix;
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk"; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk";
extraModules = [ 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 { radiantwavekiosk-commercial = mkKioskSystem {
@@ -242,7 +272,7 @@
diskModule = ./commercial-disk-config.nix; diskModule = ./commercial-disk-config.nix;
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-commercial"; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-commercial";
extraModules = [ 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. # Rotated screen for android-style displays.
@@ -254,7 +284,7 @@
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-android"; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-android";
extraModules = [ extraModules = [
./rotated_display.nix ./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. # Raspberry Pi 5.
@@ -263,7 +293,11 @@
hostName = "radiantwavekiosk-rpi"; hostName = "radiantwavekiosk-rpi";
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-rpi"; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-rpi";
extraModules = [ extraModules = [
(radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = channel.appInput.packages.aarch64-linux.default; }) (radiantwaveUpdaterModule {
repo = channel.atticRepo;
baseline = appPackage "aarch64-linux";
manifestFile = "aarch64-manifest.json";
})
]; ];
}; };
+10 -3
View File
@@ -1,6 +1,13 @@
{ pkgs, radiantwave, ...}: { config, pkgs, radiantwaveApp, ...}:
let 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" '' kioskscript = pkgs.writeShellScript "kiosk-start" ''
# Wait for the Wayland display to be ready # Wait for the Wayland display to be ready
for i in {1..10}; do for i in {1..10}; do
@@ -13,7 +20,7 @@ let
OUTPUT=$(${pkgs.wlr-randr}/bin/wlr-randr | grep -m1 '^\S' | awk '{print $1}') OUTPUT=$(${pkgs.wlr-randr}/bin/wlr-randr | grep -m1 '^\S' | awk '{print $1}')
echo "Rotating output: $OUTPUT" >> /tmp/kiosk.log 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 ${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 in
{ {