Compare commits
2
Commits
31bc806d01
...
a33a586485
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a33a586485 | ||
|
|
7aa88ee174 |
@@ -2,8 +2,9 @@
|
|||||||
# add per-branch copies. build-x86.yaml is the same file with a different matrix.
|
# add per-branch copies. build-x86.yaml is the same file with a different matrix.
|
||||||
#
|
#
|
||||||
# The branch/ref selects the channel, nothing else does: the flake's `channel`
|
# The branch/ref selects the channel, nothing else does: the flake's `channel`
|
||||||
# block picks the app input, Attic repo, and upgrade ref, so the same config
|
# block picks the channel manifest whose closure is baked as the app, the Attic
|
||||||
# names build dev images on `dev` and prod images on `main`.
|
# repo, and the upgrade ref, so the same config names build dev images on `dev`
|
||||||
|
# and prod images on `main`.
|
||||||
#
|
#
|
||||||
# Trigger model:
|
# Trigger model:
|
||||||
# * push to `dev` -> build every development image
|
# * push to `dev` -> build every development image
|
||||||
@@ -25,10 +26,13 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
# Both channel caches plus the Raspberry Pi cachix, so a build can substitute
|
# Both channel caches plus the Raspberry Pi cachix, so a build can substitute
|
||||||
# from whichever channel the checked-out ref selects.
|
# from whichever channel the checked-out ref selects. tarball-ttl=0 keeps the
|
||||||
|
# eval-time manifest fetch (builtins.fetchurl in flake.nix) from reusing a
|
||||||
|
# stale copy cached by an earlier build on this persistent runner.
|
||||||
NIX_CONFIG: |
|
NIX_CONFIG: |
|
||||||
extra-substituters = https://attic.radiantwavetech.com/radiantwave https://attic.radiantwavetech.com/radiantwave-dev https://nixos-raspberrypi.cachix.org
|
extra-substituters = https://attic.radiantwavetech.com/radiantwave https://attic.radiantwavetech.com/radiantwave-dev https://nixos-raspberrypi.cachix.org
|
||||||
extra-trusted-public-keys = radiantwave:QADrjpBWJssTZ3KmiZtcWEf4uAm66oOlHmtQI3jkrgA= radiantwave-dev:B8FhLu3MQ9JfeEIHIKNrKxnP+3akzzkqMbTZR4hRfj0= nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI=
|
extra-trusted-public-keys = radiantwave:QADrjpBWJssTZ3KmiZtcWEf4uAm66oOlHmtQI3jkrgA= radiantwave-dev:B8FhLu3MQ9JfeEIHIKNrKxnP+3akzzkqMbTZR4hRfj0= nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI=
|
||||||
|
tarball-ttl = 0
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -65,45 +69,29 @@ jobs:
|
|||||||
echo "CHANNEL_LABEL=$LABEL" >> "$GITHUB_ENV"
|
echo "CHANNEL_LABEL=$LABEL" >> "$GITHUB_ENV"
|
||||||
echo "Building the '$CHANNEL' channel ($LABEL)"
|
echo "Building the '$CHANNEL' channel ($LABEL)"
|
||||||
|
|
||||||
# Production only: fail fast unless the baked closure is exactly what
|
# Fail fast if the closure the channel manifest announces is not in the
|
||||||
# RadiantWave CI published to Attic. The dev channel churns on every
|
# channel's Attic cache. We bake that exact storePath, so without it the
|
||||||
# dispatch, so this is skipped there.
|
# image would try to compile RadiantWave from source (which never works:
|
||||||
- name: Verify baked baseline matches the published channel
|
# this repo has no app source). Runs for both channels; RadiantWave CI
|
||||||
|
# publishes the manifest only after the matching Attic push succeeds.
|
||||||
|
- name: Verify published app closure is in the channel cache
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
||||||
|
|
||||||
if [ "$CHANNEL" != "radiantwave" ]; then
|
|
||||||
echo "channel '$CHANNEL' is not production; skipping baseline preflight"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
VERSION=$(nix eval --impure --raw .#channelVersion)
|
|
||||||
ATTIC=$(nix eval --impure --raw .#channelAtticUrl)
|
ATTIC=$(nix eval --impure --raw .#channelAtticUrl)
|
||||||
MANIFEST_URL="$(nix eval --impure --raw .#channelManifestDir)/${{ matrix.manifest }}"
|
MANIFEST_URL="$(nix eval --impure --raw .#channelManifestDir)/${{ matrix.manifest }}"
|
||||||
BASELINE=$(nix eval --impure --raw .#baselineOutPaths.${{ matrix.arch }})
|
|
||||||
|
|
||||||
MANIFEST=$(curl -fsSL "$MANIFEST_URL")
|
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')
|
PUB_PATH=$(MANIFEST="$MANIFEST" nix eval --impure --raw --expr '(builtins.fromJSON (builtins.getEnv "MANIFEST")).storePath')
|
||||||
|
|
||||||
echo "channel version : $VERSION"
|
echo "channel : $CHANNEL"
|
||||||
echo "manifest version: $PUB_VERSION"
|
echo "attic : $ATTIC"
|
||||||
echo "baseline path : $BASELINE"
|
echo "manifest: $MANIFEST_URL"
|
||||||
echo "manifest path : $PUB_PATH"
|
echo "closure : $PUB_PATH"
|
||||||
|
|
||||||
if [ "$VERSION" != "$PUB_VERSION" ]; then
|
HASH=$(basename "$PUB_PATH" | cut -d- -f1)
|
||||||
echo "ERROR: channel.appVersion '$VERSION' != published manifest version '$PUB_VERSION'."
|
|
||||||
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 released commit."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
HASH=$(basename "$BASELINE" | cut -d- -f1)
|
|
||||||
if ! curl -fsS -o /dev/null "$ATTIC/${HASH}.narinfo"; then
|
if ! curl -fsS -o /dev/null "$ATTIC/${HASH}.narinfo"; then
|
||||||
echo "ERROR: $BASELINE is not in $ATTIC; the image would compile RadiantWave from source."
|
echo "ERROR: $PUB_PATH is not in $ATTIC; the image would compile RadiantWave from source."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+19
-31
@@ -2,8 +2,9 @@
|
|||||||
# add per-branch copies. build-rpi.yaml is the same file with a different matrix.
|
# add per-branch copies. build-rpi.yaml is the same file with a different matrix.
|
||||||
#
|
#
|
||||||
# The branch/ref selects the channel, nothing else does: the flake's `channel`
|
# The branch/ref selects the channel, nothing else does: the flake's `channel`
|
||||||
# block picks the app input, Attic repo, and upgrade ref, so the same config
|
# block picks the channel manifest whose closure is baked as the app, the Attic
|
||||||
# names build dev images on `dev` and prod images on `main`.
|
# repo, and the upgrade ref, so the same config names build dev images on `dev`
|
||||||
|
# and prod images on `main`.
|
||||||
#
|
#
|
||||||
# Trigger model:
|
# Trigger model:
|
||||||
# * push to `dev` -> build every development image
|
# * push to `dev` -> build every development image
|
||||||
@@ -24,10 +25,13 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
# Both channel caches plus the Raspberry Pi cachix, so a build can substitute
|
# Both channel caches plus the Raspberry Pi cachix, so a build can substitute
|
||||||
# from whichever channel the checked-out ref selects.
|
# from whichever channel the checked-out ref selects. tarball-ttl=0 keeps the
|
||||||
|
# eval-time manifest fetch (builtins.fetchurl in flake.nix) from reusing a
|
||||||
|
# stale copy cached by an earlier build on this persistent runner.
|
||||||
NIX_CONFIG: |
|
NIX_CONFIG: |
|
||||||
extra-substituters = https://attic.radiantwavetech.com/radiantwave https://attic.radiantwavetech.com/radiantwave-dev https://nixos-raspberrypi.cachix.org
|
extra-substituters = https://attic.radiantwavetech.com/radiantwave https://attic.radiantwavetech.com/radiantwave-dev https://nixos-raspberrypi.cachix.org
|
||||||
extra-trusted-public-keys = radiantwave:QADrjpBWJssTZ3KmiZtcWEf4uAm66oOlHmtQI3jkrgA= radiantwave-dev:B8FhLu3MQ9JfeEIHIKNrKxnP+3akzzkqMbTZR4hRfj0= nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI=
|
extra-trusted-public-keys = radiantwave:QADrjpBWJssTZ3KmiZtcWEf4uAm66oOlHmtQI3jkrgA= radiantwave-dev:B8FhLu3MQ9JfeEIHIKNrKxnP+3akzzkqMbTZR4hRfj0= nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI=
|
||||||
|
tarball-ttl = 0
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -81,45 +85,29 @@ jobs:
|
|||||||
echo "CHANNEL_LABEL=$LABEL" >> "$GITHUB_ENV"
|
echo "CHANNEL_LABEL=$LABEL" >> "$GITHUB_ENV"
|
||||||
echo "Building the '$CHANNEL' channel ($LABEL)"
|
echo "Building the '$CHANNEL' channel ($LABEL)"
|
||||||
|
|
||||||
# Production only: fail fast unless the baked closure is exactly what
|
# Fail fast if the closure the channel manifest announces is not in the
|
||||||
# RadiantWave CI published to Attic. The dev channel churns on every
|
# channel's Attic cache. We bake that exact storePath, so without it the
|
||||||
# dispatch, so this is skipped there.
|
# image would try to compile RadiantWave from source (which never works:
|
||||||
- name: Verify baked baseline matches the published channel
|
# this repo has no app source). Runs for both channels; RadiantWave CI
|
||||||
|
# publishes the manifest only after the matching Attic push succeeds.
|
||||||
|
- name: Verify published app closure is in the channel cache
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
||||||
|
|
||||||
if [ "$CHANNEL" != "radiantwave" ]; then
|
|
||||||
echo "channel '$CHANNEL' is not production; skipping baseline preflight"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
VERSION=$(nix eval --impure --raw .#channelVersion)
|
|
||||||
ATTIC=$(nix eval --impure --raw .#channelAtticUrl)
|
ATTIC=$(nix eval --impure --raw .#channelAtticUrl)
|
||||||
MANIFEST_URL="$(nix eval --impure --raw .#channelManifestDir)/${{ matrix.manifest }}"
|
MANIFEST_URL="$(nix eval --impure --raw .#channelManifestDir)/${{ matrix.manifest }}"
|
||||||
BASELINE=$(nix eval --impure --raw .#baselineOutPaths.${{ matrix.arch }})
|
|
||||||
|
|
||||||
MANIFEST=$(curl -fsSL "$MANIFEST_URL")
|
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')
|
PUB_PATH=$(MANIFEST="$MANIFEST" nix eval --impure --raw --expr '(builtins.fromJSON (builtins.getEnv "MANIFEST")).storePath')
|
||||||
|
|
||||||
echo "channel version : $VERSION"
|
echo "channel : $CHANNEL"
|
||||||
echo "manifest version: $PUB_VERSION"
|
echo "attic : $ATTIC"
|
||||||
echo "baseline path : $BASELINE"
|
echo "manifest: $MANIFEST_URL"
|
||||||
echo "manifest path : $PUB_PATH"
|
echo "closure : $PUB_PATH"
|
||||||
|
|
||||||
if [ "$VERSION" != "$PUB_VERSION" ]; then
|
HASH=$(basename "$PUB_PATH" | cut -d- -f1)
|
||||||
echo "ERROR: channel.appVersion '$VERSION' != published manifest version '$PUB_VERSION'."
|
|
||||||
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 released commit."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
HASH=$(basename "$BASELINE" | cut -d- -f1)
|
|
||||||
if ! curl -fsS -o /dev/null "$ATTIC/${HASH}.narinfo"; then
|
if ! curl -fsS -o /dev/null "$ATTIC/${HASH}.narinfo"; then
|
||||||
echo "ERROR: $BASELINE is not in $ATTIC; the image would compile RadiantWave from source."
|
echo "ERROR: $PUB_PATH is not in $ATTIC; the image would compile RadiantWave from source."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## What this repo is
|
## 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 inputs (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 app itself is not pinned or built here: each channel's **published manifest** (served from `files.radiantwavetech.com`, pushed by RadiantWave CI to Attic) is the single source of truth for the baked closure.
|
||||||
|
|
||||||
Hosted on Gitea: `https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig`
|
Hosted on Gitea: `https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig`
|
||||||
|
|
||||||
@@ -10,31 +10,33 @@ 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` (pinned to tag `1.8`), `atticRepo = "radiantwave"`, `gitRef = ""` (default branch), `appVersion = "1.8"`.
|
- `main`: `atticRepo = "radiantwave"`, `gitRef = ""` (default branch).
|
||||||
- `dev`: `channel.appInput = radiantwave-dev`, `atticRepo = "radiantwave-dev"`, `gitRef = "?ref=dev"`, `appVersion = <dev fingerprint>`.
|
- `dev`: `atticRepo = "radiantwave-dev"`, `gitRef = "?ref=dev"`.
|
||||||
|
|
||||||
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.
|
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 two `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`).
|
**No app version is pinned anywhere.** At eval time `flake.nix` fetches the channel's published manifest (`https://files.radiantwavetech.com/<atticRepo>/<manifestFile>`) with `builtins.fetchurl` and bakes its `storePath` verbatim as the app closure. Nix substitutes that exact closure from the channel's Attic cache, so images always match what RadiantWave published and never compile the app from source. Because that fetch is impure (no hash), **every eval that reaches the app needs `--impure`**: CI, local `build-rpi.sh`, and the kiosk's `system.autoUpgrade` (which passes `--impure`). `tarball-ttl = 0` is set in CI and on the kiosks so a rebuild never reuses a stale manifest.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
## Key commands
|
## Key commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build a system closure (any config name from flake.nix)
|
# Build a system closure (any config name from flake.nix).
|
||||||
nix build .#nixosConfigurations.<name>.config.system.build.toplevel
|
# --impure is required: the app closure comes from the channel manifest,
|
||||||
|
# fetched at eval time (see the prod/dev split above).
|
||||||
|
nix build --impure .#nixosConfigurations.<name>.config.system.build.toplevel
|
||||||
|
|
||||||
# Build a bootable ISO
|
# Build a bootable ISO
|
||||||
nix build .#nixosConfigurations.<name>.config.system.build.isoImage
|
nix build --impure .#nixosConfigurations.<name>.config.system.build.isoImage
|
||||||
|
|
||||||
# Deploy to a remote kiosk over SSH
|
# Deploy to a remote kiosk over SSH
|
||||||
nixos-rebuild switch --flake .#<name> --target-host root@<host>
|
nixos-rebuild switch --impure --flake .#<name> --target-host root@<host>
|
||||||
|
|
||||||
# Push build result to Attic cache (not currently wired into any config)
|
# Push build result to Attic cache (not currently wired into any config)
|
||||||
attic push kiosk ./result
|
attic push kiosk ./result
|
||||||
|
|
||||||
# Evaluate available config names
|
# Evaluate available config names (pure-safe: does not force the app fetch)
|
||||||
nix eval .#nixosConfigurations --apply builtins.attrNames
|
nix eval .#nixosConfigurations --apply builtins.attrNames
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -42,14 +44,14 @@ nix eval .#nixosConfigurations --apply builtins.attrNames
|
|||||||
|
|
||||||
One config per hardware target. Dev/prod is the branch, not the config — see `channel` in `flake.nix`:
|
One config per hardware target. Dev/prod is the branch, not the config — see `channel` in `flake.nix`:
|
||||||
|
|
||||||
| Name | Target hardware | Disk device | App input |
|
| Name | Target hardware | Disk device | App closure |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `radiantwavekiosk` | Intel NUC 14 (NVMe) | `/dev/nvme0n1` | `radiantwave` (tag 1.8) |
|
| `radiantwavekiosk` | Intel NUC 14 (NVMe) | `/dev/nvme0n1` | channel manifest `manifest.json` |
|
||||||
| `radiantwavekiosk-commercial` | Commercial HW (SATA) | `/dev/sda` | `radiantwave` (tag 1.8) |
|
| `radiantwavekiosk-commercial` | Commercial HW (SATA) | `/dev/sda` | channel manifest `manifest.json` |
|
||||||
| `radiantwavekiosk-android` | Intel NUC 14, rotated display | `/dev/nvme0n1` | `radiantwave` (tag 1.8) |
|
| `radiantwavekiosk-android` | Intel NUC 14, rotated display | `/dev/nvme0n1` | channel manifest `manifest.json` |
|
||||||
| `radiantwavekiosk-rpi` | Raspberry Pi 5 (SD image) | — | `radiantwave` (tag 1.8) |
|
| `radiantwavekiosk-rpi` | Raspberry Pi 5 (SD image) | — | channel manifest `aarch64-manifest.json` |
|
||||||
|
|
||||||
On the `dev` branch, the same four configs use `radiantwave-dev` and track `?ref=dev`.
|
On the `dev` branch, the same four configs read the `radiantwave-dev` manifests (`?ref=dev`).
|
||||||
|
|
||||||
Every variant sets its own `networking.hostName` (same as the config name) and `system.configurationRevision`, so `tools/find_systems.zsh` can identify fleet members.
|
Every variant sets its own `networking.hostName` (same as the config name) and `system.configurationRevision`, so `tools/find_systems.zsh` can identify fleet members.
|
||||||
|
|
||||||
@@ -63,12 +65,12 @@ Each ISO installs exactly one closure using that closure's disk layout (`mkIso`
|
|||||||
| `commercial-iso` | `radiantwavekiosk-commercial` | SATA (`commercial-disk-config.nix`) |
|
| `commercial-iso` | `radiantwavekiosk-commercial` | SATA (`commercial-disk-config.nix`) |
|
||||||
| `android-iso` | `radiantwavekiosk-android` | NVMe |
|
| `android-iso` | `radiantwavekiosk-android` | NVMe |
|
||||||
|
|
||||||
These same names build **dev-flavored** images when the flake is checked out on the `dev` branch (the channel block selects the dev app, attic repo, and upgrade ref), so there are no separate `-dev` ISOs.
|
These same names build **dev-flavored** images when the flake is checked out on the `dev` branch (the channel block selects the dev manifest, attic repo, and upgrade ref), so there are no separate `-dev` ISOs.
|
||||||
|
|
||||||
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.
|
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:
|
Raspberry Pi systems are built as SD images, not ISOs:
|
||||||
`nix build .#nixosConfigurations.radiantwavekiosk-rpi.config.system.build.sdImage` (see `build-rpi.sh`).
|
`nix build --impure .#nixosConfigurations.radiantwavekiosk-rpi.config.system.build.sdImage` (see `build-rpi.sh`).
|
||||||
|
|
||||||
## Hardware facter reports
|
## Hardware facter reports
|
||||||
|
|
||||||
@@ -88,19 +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` and `build-rpi.yml` — triggered on pushes to `dev`, on new tags (production), and by `workflow_dispatch`, all on the `orion-lxc` runner. They build and publish exactly the closure the channel manifest announces, so a release is published by RadiantWave CI alone; there is no `flake.lock` bump to make here. Both workflows set `tarball-ttl = 0` and fail fast if the announced closure is not in the channel's Attic cache.
|
||||||
|
|
||||||
## 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`, `appVersion`. 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` — `atticRepo` and `gitRef`. 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 --impure`, 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.
|
- **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.
|
- **App closure comes from Attic**: both channels (`radiantwave`, `radiantwave-dev`) are configured as trusted substituters in `configuration.nix`, so the baked app and its updates are substituted, never compiled, on the kiosks. nixpkgs deps still come from cache.nixos.org.
|
||||||
- **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` 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.
|
- **App inputs**: there are none. Do not re-add a pinned `radiantwave` flake input or an `appVersion`; that is exactly what drifted from the published releases. The manifest is the source of truth.
|
||||||
- **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
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ podman run --rm \
|
|||||||
mkdir -p /etc/nix
|
mkdir -p /etc/nix
|
||||||
echo "experimental-features = nix-command flakes" > /etc/nix/nix.conf
|
echo "experimental-features = nix-command flakes" > /etc/nix/nix.conf
|
||||||
echo "accept-flake-config = true" >> /etc/nix/nix.conf
|
echo "accept-flake-config = true" >> /etc/nix/nix.conf
|
||||||
|
echo "tarball-ttl = 0" >> /etc/nix/nix.conf
|
||||||
echo "extra-substituters = https://cache.nixos.org https://nixos-raspberrypi.cachix.org https://attic.radiantwavetech.com/radiantwave https://attic.radiantwavetech.com/radiantwave-dev" >> /etc/nix/nix.conf
|
echo "extra-substituters = https://cache.nixos.org https://nixos-raspberrypi.cachix.org https://attic.radiantwavetech.com/radiantwave https://attic.radiantwavetech.com/radiantwave-dev" >> /etc/nix/nix.conf
|
||||||
echo "extra-trusted-public-keys = nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI= radiantwave:QADrjpBWJssTZ3KmiZtcWEf4uAm66oOlHmtQI3jkrgA= radiantwave-dev:B8FhLu3MQ9JfeEIHIKNrKxnP+3akzzkqMbTZR4hRfj0=" >> /etc/nix/nix.conf
|
echo "extra-trusted-public-keys = nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI= radiantwave:QADrjpBWJssTZ3KmiZtcWEf4uAm66oOlHmtQI3jkrgA= radiantwave-dev:B8FhLu3MQ9JfeEIHIKNrKxnP+3akzzkqMbTZR4hRfj0=" >> /etc/nix/nix.conf
|
||||||
echo "max-jobs = 4" >> /etc/nix/nix.conf
|
echo "max-jobs = 4" >> /etc/nix/nix.conf
|
||||||
@@ -33,6 +34,7 @@ podman run --rm \
|
|||||||
echo "==> Building sdImage (first run slow, cached after)..."
|
echo "==> Building sdImage (first run slow, cached after)..."
|
||||||
nix --accept-flake-config build \
|
nix --accept-flake-config build \
|
||||||
.#nixosConfigurations.'${CONFIG}'.config.system.build.sdImage \
|
.#nixosConfigurations.'${CONFIG}'.config.system.build.sdImage \
|
||||||
|
--impure \
|
||||||
--fallback \
|
--fallback \
|
||||||
--print-build-logs
|
--print-build-logs
|
||||||
|
|
||||||
|
|||||||
+17
-2
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
let
|
let
|
||||||
radiantApp = radiantwaveApp;
|
radiantApp = radiantwaveApp;
|
||||||
|
# The published app arrives as a bare store-path string (from the channel
|
||||||
|
# manifest, see flake.nix). Wrap it in a derivation built with this system's
|
||||||
|
# pkgs so it can live in environment.systemPackages; the wrapper is a symlink
|
||||||
|
# farm and the real closure is substituted from the channel's Attic cache.
|
||||||
|
radiantAppPackage = pkgs.symlinkJoin {
|
||||||
|
name = "radiantwave-published";
|
||||||
|
paths = [ radiantApp ];
|
||||||
|
};
|
||||||
# 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
|
||||||
@@ -32,7 +40,7 @@ in
|
|||||||
# Included packages
|
# Included packages
|
||||||
environment.systemPackages = [
|
environment.systemPackages = [
|
||||||
pkgs.cage pkgs.rsync pkgs.dbus pkgs.mesa pkgs.git pkgs.tailscale pkgs.sqlite pkgs.wireplumber
|
pkgs.cage pkgs.rsync pkgs.dbus pkgs.mesa pkgs.git pkgs.tailscale pkgs.sqlite pkgs.wireplumber
|
||||||
radiantApp
|
radiantAppPackage
|
||||||
];
|
];
|
||||||
|
|
||||||
# This populates the EFI partition and tells the NUC how to start NixOS.
|
# This populates the EFI partition and tells the NUC how to start NixOS.
|
||||||
@@ -222,7 +230,9 @@ in
|
|||||||
enable = true;
|
enable = true;
|
||||||
flake = upgradeFlakeRef;
|
flake = upgradeFlakeRef;
|
||||||
dates = "*:00/10";
|
dates = "*:00/10";
|
||||||
flags = [ "--refresh" "-L" ];
|
# --impure: the flake reads the channel's published manifest at eval time
|
||||||
|
# (builtins.fetchurl) to pick the app store path for the next generation.
|
||||||
|
flags = [ "--refresh" "--impure" "-L" ];
|
||||||
operation = "switch";
|
operation = "switch";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -249,6 +259,11 @@ in
|
|||||||
# NixOS settings.
|
# NixOS settings.
|
||||||
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||||
nix.settings.accept-flake-config = true;
|
nix.settings.accept-flake-config = true;
|
||||||
|
# The app baked into each generation comes from the channel's published
|
||||||
|
# manifest, fetched at eval time with `builtins.fetchurl` (see flake.nix).
|
||||||
|
# Disable the tarball cache so a rebuild during autoUpgrade always reads the
|
||||||
|
# current manifest instead of a copy up to an hour old.
|
||||||
|
nix.settings.tarball-ttl = 0;
|
||||||
nix.settings.extra-substituters = [
|
nix.settings.extra-substituters = [
|
||||||
"https://attic.radiantwavetech.com/radiantwave"
|
"https://attic.radiantwavetech.com/radiantwave"
|
||||||
"https://attic.radiantwavetech.com/radiantwave-dev"
|
"https://attic.radiantwavetech.com/radiantwave-dev"
|
||||||
|
|||||||
Generated
+1
-141
@@ -51,42 +51,6 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-utils": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1731533236,
|
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils_2": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems_2"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1731533236,
|
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixos-facter-modules": {
|
"nixos-facter-modules": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1773858690,
|
"lastModified": 1773858690,
|
||||||
@@ -182,116 +146,12 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_3": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1785967620,
|
|
||||||
"narHash": "sha256-IItrdb7Puk05RqOBWZYFC5X6Wl1sJmCfh5MWVHw5iMM=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "b7c2ada94fe99c15b0dbcf4d11fd7850b957a436",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_4": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1785967620,
|
|
||||||
"narHash": "sha256-IItrdb7Puk05RqOBWZYFC5X6Wl1sJmCfh5MWVHw5iMM=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "b7c2ada94fe99c15b0dbcf4d11fd7850b957a436",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"radiantwave": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"nixpkgs": "nixpkgs_3"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"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": "refs/tags/1.8",
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"radiantwave-dev": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils_2",
|
|
||||||
"nixpkgs": "nixpkgs_4"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1787943320,
|
|
||||||
"narHash": "sha256-zoHqtwO0DV3z/N311IxiMLrbyQHrw7HIoL1FQW5pGDk=",
|
|
||||||
"ref": "dev",
|
|
||||||
"rev": "2917d33866f052eca1e9daea175d95382a9087f2",
|
|
||||||
"revCount": 50,
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"ref": "dev",
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"disko": "disko",
|
"disko": "disko",
|
||||||
"nixos-facter-modules": "nixos-facter-modules",
|
"nixos-facter-modules": "nixos-facter-modules",
|
||||||
"nixos-raspberrypi": "nixos-raspberrypi",
|
"nixos-raspberrypi": "nixos-raspberrypi",
|
||||||
"nixpkgs": "nixpkgs_2",
|
"nixpkgs": "nixpkgs_2"
|
||||||
"radiantwave": "radiantwave",
|
|
||||||
"radiantwave-dev": "radiantwave-dev"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
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=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";
|
nixos-raspberrypi.url = "github:nvmd/nixos-raspberrypi/main";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -15,8 +13,6 @@
|
|||||||
nixpkgs,
|
nixpkgs,
|
||||||
disko,
|
disko,
|
||||||
nixos-facter-modules,
|
nixos-facter-modules,
|
||||||
radiantwave,
|
|
||||||
radiantwave-dev,
|
|
||||||
nixos-raspberrypi,
|
nixos-raspberrypi,
|
||||||
...
|
...
|
||||||
}@inputs:
|
}@inputs:
|
||||||
@@ -62,36 +58,46 @@
|
|||||||
# 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 (tag 1.8), atticRepo = "radiantwave",
|
# main: atticRepo = "radiantwave", gitRef = ""
|
||||||
# gitRef = "", appVersion = "1.8"
|
# dev: atticRepo = "radiantwave-dev", gitRef = "?ref=dev"
|
||||||
# dev: appInput = radiantwave-dev, atticRepo = "radiantwave-dev",
|
|
||||||
# gitRef = "?ref=dev", appVersion = <dev fingerprint>
|
|
||||||
#
|
#
|
||||||
# To flip branches, change ONLY these four values (then push/merge).
|
# To flip branches, change ONLY these two values (then push/merge).
|
||||||
|
#
|
||||||
|
# Note there is deliberately NO app version here. The RadiantWave app is
|
||||||
|
# not built or pinned by this repo; each channel's published manifest is
|
||||||
|
# the single source of truth (see `publishedApp` below).
|
||||||
channel = {
|
channel = {
|
||||||
appInput = radiantwave-dev; # dev
|
|
||||||
atticRepo = "radiantwave-dev"; # dev
|
atticRepo = "radiantwave-dev"; # dev
|
||||||
gitRef = "?ref=dev"; # dev
|
gitRef = "?ref=dev"; # dev
|
||||||
appVersion = "2917d33"; # dev
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# Exposed so CI (see .gitea/workflows/build-prod-*.yml) and humans can
|
# Exposed so CI (see .gitea/workflows/build.yml) and humans can verify
|
||||||
# verify which channel this checkout resolves to. On `main` this must be
|
# which channel this checkout resolves to. On `main` this is
|
||||||
# "radiantwave"; on `dev` it must be "radiantwave-dev". The prod
|
# "radiantwave"; on `dev` it is "radiantwave-dev".
|
||||||
# workflows fail fast if this isn't the production channel, so a merge
|
channelName = channel.atticRepo;
|
||||||
# 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";
|
|
||||||
|
|
||||||
# The App package, pinned to the channel's declared version. RadiantWave's
|
# Channel metadata shared by the configs and the CI preflight.
|
||||||
# own flake only injects its version from the ambient APP_VERSION env var
|
channelAtticUrl = "https://attic.radiantwavetech.com/${channel.atticRepo}";
|
||||||
# on impure evals; pure evals (local `nix build`, build-rpi.sh) fall back
|
channelManifestDir = "https://files.radiantwavetech.com/${channel.atticRepo}";
|
||||||
# to its 0.1.0 default. Overriding the version here makes the baked
|
|
||||||
# baseline correct regardless of eval purity, and matches the Attic
|
# The baked RadiantWave app is whatever the channel's published manifest
|
||||||
# closure when RadiantWave CI built it with the same version string.
|
# currently announces. RadiantWave CI builds the app, pushes the closure
|
||||||
appPackage = system: channel.appInput.packages.${system}.default.override {
|
# to the channel's Attic cache, and writes {version, storePath} to the
|
||||||
version = channel.appVersion;
|
# manifest. We fetch that manifest at eval time and use its storePath
|
||||||
};
|
# verbatim, so an image always carries exactly the published closure and
|
||||||
|
# never compiles RadiantWave from source. This is an *impure* fetch (no
|
||||||
|
# hash), so every eval that reaches the app needs `--impure`: CI, the
|
||||||
|
# kiosk's system.autoUpgrade, and local `build-rpi.sh` all pass it.
|
||||||
|
manifestFor = manifestFile:
|
||||||
|
builtins.fromJSON (builtins.readFile (builtins.fetchurl {
|
||||||
|
url = "${channelManifestDir}/${manifestFile}";
|
||||||
|
}));
|
||||||
|
|
||||||
|
# The published closure as a plain store-path string. configuration.nix
|
||||||
|
# wraps it in a symlinkJoin so it can live in environment.systemPackages
|
||||||
|
# (a bare string there would be coerced with builtins.storePath, which
|
||||||
|
# requires the closure to already be local at eval time).
|
||||||
|
publishedApp = manifestFile: (manifestFor manifestFile).storePath;
|
||||||
|
|
||||||
# Live app-update side channel. The manifest is served by the
|
# 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
|
||||||
@@ -103,7 +109,7 @@
|
|||||||
radiantwaveUpdaterModule = { repo, baseline, manifestFile ? "manifest.json" }: {
|
radiantwaveUpdaterModule = { repo, baseline, manifestFile ? "manifest.json" }: {
|
||||||
services.radiantwaveUpdater = {
|
services.radiantwaveUpdater = {
|
||||||
enable = true;
|
enable = true;
|
||||||
baselinePackage = baseline;
|
baselinePath = baseline;
|
||||||
manifestUrl = "https://files.radiantwavetech.com/${repo}/${manifestFile}";
|
manifestUrl = "https://files.radiantwavetech.com/${repo}/${manifestFile}";
|
||||||
atticUrl = "https://attic.radiantwavetech.com/${repo}";
|
atticUrl = "https://attic.radiantwavetech.com/${repo}";
|
||||||
};
|
};
|
||||||
@@ -111,7 +117,6 @@
|
|||||||
|
|
||||||
mkKioskSystem = {
|
mkKioskSystem = {
|
||||||
system,
|
system,
|
||||||
radiantwave,
|
|
||||||
upgradeFlakeRef,
|
upgradeFlakeRef,
|
||||||
hostName,
|
hostName,
|
||||||
diskModule,
|
diskModule,
|
||||||
@@ -121,8 +126,8 @@
|
|||||||
inherit system;
|
inherit system;
|
||||||
|
|
||||||
specialArgs = {
|
specialArgs = {
|
||||||
inherit radiantwave upgradeFlakeRef;
|
inherit upgradeFlakeRef;
|
||||||
radiantwaveApp = appPackage system;
|
radiantwaveApp = publishedApp "manifest.json";
|
||||||
radiantwavePlymouth = mkRadiantwavePlymouth;
|
radiantwavePlymouth = mkRadiantwavePlymouth;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -144,21 +149,19 @@
|
|||||||
# facter-<model>.json, and select per variant here, e.g.:
|
# facter-<model>.json, and select per variant here, e.g.:
|
||||||
# { facter.reportPath = ./facter-commercial.json; }
|
# { facter.reportPath = ./facter-commercial.json; }
|
||||||
{ facter.reportPath = ./facter.json; }
|
{ facter.reportPath = ./facter.json; }
|
||||||
radiantwave.nixosModules.default
|
|
||||||
] ++ extraModules;
|
] ++ extraModules;
|
||||||
};
|
};
|
||||||
|
|
||||||
mkRpiKioskSystem = {
|
mkRpiKioskSystem = {
|
||||||
radiantwave,
|
|
||||||
upgradeFlakeRef,
|
upgradeFlakeRef,
|
||||||
hostName,
|
hostName,
|
||||||
extraModules ? [],
|
extraModules ? [],
|
||||||
}:
|
}:
|
||||||
nixos-raspberrypi.lib.nixosSystem {
|
nixos-raspberrypi.lib.nixosSystem {
|
||||||
specialArgs = {
|
specialArgs = {
|
||||||
inherit radiantwave upgradeFlakeRef;
|
inherit upgradeFlakeRef;
|
||||||
inherit nixos-raspberrypi;
|
inherit nixos-raspberrypi;
|
||||||
radiantwaveApp = appPackage "aarch64-linux";
|
radiantwaveApp = publishedApp "aarch64-manifest.json";
|
||||||
radiantwavePlymouth = mkRadiantwavePlymouth;
|
radiantwavePlymouth = mkRadiantwavePlymouth;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -174,7 +177,6 @@
|
|||||||
})
|
})
|
||||||
./configuration.nix
|
./configuration.nix
|
||||||
./radiantwave-updater.nix
|
./radiantwave-updater.nix
|
||||||
radiantwave.nixosModules.default
|
|
||||||
{
|
{
|
||||||
networking.hostName = hostName;
|
networking.hostName = hostName;
|
||||||
system.configurationRevision = revision;
|
system.configurationRevision = revision;
|
||||||
@@ -224,7 +226,7 @@
|
|||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit channelName;
|
inherit channelName channelAtticUrl channelManifestDir;
|
||||||
|
|
||||||
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;
|
||||||
@@ -234,53 +236,50 @@
|
|||||||
# Kiosk systems — one per hardware target, no dev/prod siblings.
|
# Kiosk systems — one per hardware target, no dev/prod siblings.
|
||||||
#
|
#
|
||||||
# Every config consumes the channel selected at the top of this file
|
# Every config consumes the channel selected at the top of this file
|
||||||
# (see `channel`): the RadiantWave app input, the Attic repo/manifest
|
# (see `channel`): the published manifest whose closure is baked as the
|
||||||
# it fetches live updates from, and the KioskConfig git ref it
|
# app, the Attic repo/manifest it fetches live updates from, and the
|
||||||
# auto-upgrades from. On `main` the channel is prod; on `dev` it is the
|
# KioskConfig git ref it auto-upgrades from. On `main` the channel is
|
||||||
# dev channel. Because there is exactly one config per hardware type,
|
# prod; on `dev` it is the dev channel. Because there is exactly one
|
||||||
# changes made on `dev` reach prod via a plain merge — no copying.
|
# config per hardware type, changes made on `dev` reach prod via a plain
|
||||||
|
# merge — no copying.
|
||||||
nixosConfigurations = {
|
nixosConfigurations = {
|
||||||
radiantwavekiosk = mkKioskSystem {
|
radiantwavekiosk = mkKioskSystem {
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
radiantwave = channel.appInput;
|
|
||||||
hostName = "radiantwavekiosk";
|
hostName = "radiantwavekiosk";
|
||||||
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 = appPackage "x86_64-linux"; })
|
(radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = publishedApp "manifest.json"; })
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
radiantwavekiosk-commercial = mkKioskSystem {
|
radiantwavekiosk-commercial = mkKioskSystem {
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
radiantwave = channel.appInput;
|
|
||||||
hostName = "radiantwavekiosk-commercial";
|
hostName = "radiantwavekiosk-commercial";
|
||||||
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 = appPackage "x86_64-linux"; })
|
(radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = publishedApp "manifest.json"; })
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
# Rotated screen for android-style displays.
|
# Rotated screen for android-style displays.
|
||||||
radiantwavekiosk-android = mkKioskSystem {
|
radiantwavekiosk-android = mkKioskSystem {
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
radiantwave = channel.appInput;
|
|
||||||
hostName = "radiantwavekiosk-android";
|
hostName = "radiantwavekiosk-android";
|
||||||
diskModule = ./disk-config.nix;
|
diskModule = ./disk-config.nix;
|
||||||
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 = appPackage "x86_64-linux"; })
|
(radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = publishedApp "manifest.json"; })
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
# Raspberry Pi 5.
|
# Raspberry Pi 5.
|
||||||
radiantwavekiosk-rpi = mkRpiKioskSystem {
|
radiantwavekiosk-rpi = mkRpiKioskSystem {
|
||||||
radiantwave = channel.appInput;
|
|
||||||
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 {
|
(radiantwaveUpdaterModule {
|
||||||
repo = channel.atticRepo;
|
repo = channel.atticRepo;
|
||||||
baseline = appPackage "aarch64-linux";
|
baseline = publishedApp "aarch64-manifest.json";
|
||||||
manifestFile = "aarch64-manifest.json";
|
manifestFile = "aarch64-manifest.json";
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
@@ -292,8 +291,8 @@
|
|||||||
# SATA with ./commercial-disk-config.nix — don't cross them.
|
# SATA with ./commercial-disk-config.nix — don't cross them.
|
||||||
# The same names build dev-flavored images when the flake is checked
|
# The same names build dev-flavored images when the flake is checked
|
||||||
# out on the `dev` branch (the channel block above selects the dev
|
# out on the `dev` branch (the channel block above selects the dev
|
||||||
# app, attic repo, and upgrade ref), so there are no separate -dev
|
# manifest, attic repo, and upgrade ref), so there are no separate
|
||||||
# ISOs.
|
# -dev ISOs.
|
||||||
kiosk-iso = mkIso {
|
kiosk-iso = mkIso {
|
||||||
targetConfig = "radiantwavekiosk";
|
targetConfig = "radiantwavekiosk";
|
||||||
diskModule = ./disk-config.nix;
|
diskModule = ./disk-config.nix;
|
||||||
|
|||||||
@@ -17,19 +17,18 @@ in
|
|||||||
description = "Attic cache URL to fetch the app closure from (fetch-only, never builds).";
|
description = "Attic cache URL to fetch the app closure from (fetch-only, never builds).";
|
||||||
};
|
};
|
||||||
|
|
||||||
baselinePackage = lib.mkOption {
|
baselinePath = lib.mkOption {
|
||||||
type = lib.types.package;
|
type = lib.types.str;
|
||||||
description = "Copy of RadiantWave baked into the system closure, used to seed the runtime pointer before the first update.";
|
description = "Store path of the RadiantWave closure baked into the system closure (from the channel manifest), used to seed the runtime pointer before the first update.";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
# Runtime pointer the login shell launches RadiantWave from. Seeded on
|
# Runtime pointer the login shell launches RadiantWave from. Seeded on
|
||||||
# first boot to the baseline copy already baked into the system closure
|
# first boot to the exact closure the channel manifest announced (the same
|
||||||
# (so the ISO carries it with no duplication); the updater service later
|
# path the updater would fetch), so no spurious first update is triggered.
|
||||||
# re-points it atomically to newer closures fetched from Attic.
|
|
||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"L /var/lib/radiantwave/current - - - - ${cfg.baselinePackage.outPath}"
|
"L /var/lib/radiantwave/current - - - - ${cfg.baselinePath}"
|
||||||
];
|
];
|
||||||
|
|
||||||
systemd.services.radiantwave-updater = {
|
systemd.services.radiantwave-updater = {
|
||||||
@@ -81,6 +80,7 @@ in
|
|||||||
${pkgs.coreutils}/bin/ln -sfn "$NEW_PATH" /var/lib/radiantwave/current.tmp
|
${pkgs.coreutils}/bin/ln -sfn "$NEW_PATH" /var/lib/radiantwave/current.tmp
|
||||||
${pkgs.coreutils}/bin/mv -T /var/lib/radiantwave/current.tmp /var/lib/radiantwave/current
|
${pkgs.coreutils}/bin/mv -T /var/lib/radiantwave/current.tmp /var/lib/radiantwave/current
|
||||||
echo "radiantwave-updater: swapped ''${CURRENT} -> ''${NEW_PATH}"
|
echo "radiantwave-updater: swapped ''${CURRENT} -> ''${NEW_PATH}"
|
||||||
|
loginctl terminate-user kiosk
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user