16 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
NeilClack c684387f60 Merge pull request 'Dev' (#9) from dev into main
build-prod-android.yml / build-android (push) Failing after 2s
build-prod-commercial.yml / build-commercial (push) Failing after 1s
build-prod-kiosk.yml / build-kiosk (push) Failing after 2s
Reviewed-on: #9
2026-10-01 22:28:47 +00:00
NeilClack c720c67975 Update Radiantwave 1.6
build-prod-android.yml / build-android (push) Failing after 1s
build-prod-commercial.yml / build-commercial (push) Failing after 1s
build-prod-kiosk.yml / build-kiosk (push) Failing after 2s
2026-09-11 16:03:48 -05:00
NeilClack 6e446893f3 Point main at the production RadiantWave channel and guard it in CI
build-prod-android.yml / build-android (push) Successful in 5m3s
build-prod-commercial.yml / build-commercial (push) Failing after 4m3s
build-prod-kiosk.yml / build-kiosk (push) Failing after 3m23s
The dev->main merge left the channel block in flake.nix pointing at
radiantwave-dev, so prod ISOs built from main shipped the dev app (which
versions itself by short git hash) and the updater polled the dev Attic
manifest. Restore the channel block to main values, re-pin flake.lock to
the latest radiantwave main revision, and expose channelName so the prod
build workflows can fail fast if main ever resolves to the dev channel.
2026-09-01 09:25:03 -05:00
NeilClack 0860dea6c7 Merge remote-tracking branch 'origin/main' 2026-09-01 09:24:58 -05:00
NeilClack fc7edc0d22 Merge pull request 'Dev' (#8) from dev into main
build-prod-android.yml / build-android (push) Successful in 4m27s
build-prod-kiosk.yml / build-kiosk (push) Successful in 4m32s
build-prod-commercial.yml / build-commercial (push) Successful in 4m39s
Reviewed-on: #8
2026-08-29 02:01:44 +00:00
NeilClack 187581b52a Merge branch 'dev' 2026-08-28 21:00:43 -05:00
NeilClack 46cc6ef5f5 Remove temp files 2026-08-28 21:00:32 -05:00
NeilClack a8f78b282d Merge pull request 'Update radiantwave - rotated' (#7) from dev into main
build-prod-commercial.yml / build-commercial (push) Successful in 4m21s
build-prod-android.yml / build-android (push) Successful in 4m42s
build-prod-kiosk.yml / build-kiosk (push) Successful in 4m14s
Reviewed-on: #7
2026-08-25 16:33:12 +00:00
NeilClack cf7129b888 Merge pull request 'Update Radiantwave 1.3' (#6) from dev into main
build-prod-android.yml / build-android (push) Successful in 4m16s
build-prod-commercial.yml / build-commercial (push) Successful in 4m20s
build-prod-kiosk.yml / build-kiosk (push) Successful in 4m16s
Reviewed-on: #6
2026-08-25 16:00:49 +00:00
NeilClack 0be0b01d73 Merge pull request 'Update RadiantWave 1.2' (#5) from dev into main
build-prod-android.yml / build-android (push) Failing after 12s
build-prod-commercial.yml / build-commercial (push) Failing after 9s
build-prod-kiosk.yml / build-kiosk (push) Failing after 9s
Reviewed-on: #5
2026-08-25 15:34:57 +00:00
NeilClack 2a6df4bbc4 Merge pull request 'Update workflows to run automatically on push to main' (#4) from dev into main
build-prod-android.yml / build-android (push) Successful in 4m25s
build-prod-commercial.yml / build-commercial (push) Successful in 4m27s
build-prod-kiosk.yml / build-kiosk (push) Successful in 4m19s
Reviewed-on: #4
2026-08-25 13:11:49 +00:00
NeilClack f166b25364 Merge pull request 'Dev' (#3) from dev into main
Reviewed-on: #3
2026-08-25 13:05:37 +00:00
NeilClack 7f9566e52c Merge pull request 'Update radiantwave' (#2) from dev into main
Reviewed-on: #2
2026-08-24 02:21:07 +00:00
NeilClack 6d3136c9cb Merge pull request 'Initial Release' (#1) from dev into main
Reviewed-on: #1
2026-08-24 01:36:17 +00:00
18 changed files with 304 additions and 40 deletions
+52
View File
@@ -17,6 +17,58 @@ jobs:
- name: Checkout - name: Checkout
run: git clone --depth 1 ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/KioskConfig.git . run: git clone --depth 1 ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/KioskConfig.git .
- name: Guard main branch is on the production channel
run: |
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
NAME=$(nix eval --impure --raw .#channelName)
if [ "$NAME" != "radiantwave" ]; then
echo "ERROR: main branch resolves to the '$NAME' channel, expected 'radiantwave'."
echo " A dev->main merge brought a dev-pointing channel block into flake.nix."
echo " Set channel.appInput/atticRepo/gitRef back to main values before building prod ISOs."
exit 1
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
@@ -17,6 +17,58 @@ jobs:
- name: Checkout - name: Checkout
run: git clone --depth 1 ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/KioskConfig.git . run: git clone --depth 1 ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/KioskConfig.git .
- name: Guard main branch is on the production channel
run: |
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
NAME=$(nix eval --impure --raw .#channelName)
if [ "$NAME" != "radiantwave" ]; then
echo "ERROR: main branch resolves to the '$NAME' channel, expected 'radiantwave'."
echo " A dev->main merge brought a dev-pointing channel block into flake.nix."
echo " Set channel.appInput/atticRepo/gitRef back to main values before building prod ISOs."
exit 1
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
+52
View File
@@ -17,6 +17,58 @@ jobs:
- name: Checkout - name: Checkout
run: git clone --depth 1 ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/KioskConfig.git . run: git clone --depth 1 ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/KioskConfig.git .
- name: Guard main branch is on the production channel
run: |
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
NAME=$(nix eval --impure --raw .#channelName)
if [ "$NAME" != "radiantwave" ]; then
echo "ERROR: main branch resolves to the '$NAME' channel, expected 'radiantwave'."
echo " A dev->main merge brought a dev-pointing channel block into flake.nix."
echo " Set channel.appInput/atticRepo/gitRef back to main values before building prod ISOs."
exit 1
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
+52
View File
@@ -14,6 +14,58 @@ jobs:
- name: Checkout - name: Checkout
run: git clone --depth 1 ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/KioskConfig.git . run: git clone --depth 1 ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/KioskConfig.git .
- name: Guard main branch is on the production channel
run: |
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
NAME=$(nix eval --impure --raw .#channelName)
if [ "$NAME" != "radiantwave" ]; then
echo "ERROR: main branch resolves to the '$NAME' channel, expected 'radiantwave'."
echo " A dev->main merge brought a dev-pointing channel block into flake.nix."
echo " Set channel.appInput/atticRepo/gitRef back to main values before building prod images."
exit 1
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": 1787675420, "lastModified": 1791050336,
"narHash": "sha256-2Hq9IcgdDXQc2uQ4yC/5NZVjcZaFCcVS78vj+ee+y/Q=", "narHash": "sha256-sb/cDFS55qodbU6GEydhyRyW8VBTGg110ejSXVNd2OY=",
"ref": "main", "ref": "refs/tags/1.8",
"rev": "43cd93bbd7dde69c55c00a28c1691e1a192eda39", "rev": "a5a6b5bef07fffb5ec9597e577e6fb8a4b2a0108",
"revCount": 47, "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"
} }
+63 -19
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,28 +62,64 @@
# 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-dev; # dev appInput = radiantwave; # main
atticRepo = "radiantwave-dev"; # dev atticRepo = "radiantwave"; # main
gitRef = "?ref=dev"; # dev gitRef = ""; # main
appVersion = "1.8"; # main
};
# Exposed so CI (see .gitea/workflows/build-prod-*.yml) and humans can
# verify which channel this checkout resolves to. On `main` this must be
# "radiantwave"; on `dev` it must be "radiantwave-dev". The prod
# workflows fail fast if this isn't the production channel, so a merge
# that brings a dev-pointing channel block into `main` breaks the build
# instead of silently shipping dev builds as prod ISOs.
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}";
}; };
}; };
@@ -100,8 +136,8 @@
inherit system; inherit system;
specialArgs = { specialArgs = {
inherit radiantwave; inherit radiantwave upgradeFlakeRef;
inherit upgradeFlakeRef; radiantwaveApp = appPackage system;
radiantwavePlymouth = mkRadiantwavePlymouth; radiantwavePlymouth = mkRadiantwavePlymouth;
}; };
@@ -137,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
@@ -201,6 +239,8 @@
}; };
in in
{ {
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;
packages.x86_64-linux.radiantwavekiosk-android = self.nixosConfigurations.radiantwavekiosk-android.config.system.build.toplevel; packages.x86_64-linux.radiantwavekiosk-android = self.nixosConfigurations.radiantwavekiosk-android.config.system.build.toplevel;
@@ -222,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 {
@@ -232,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.
@@ -244,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.
@@ -253,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
{ {
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
View File