70 Commits
Author SHA1 Message Date
NeilClack a3e6da7053 Ignore VERIFY-DEV.md and VERIFY-PROD.md
Build Kiosk Image (Raspberry Pi) / rpi (push) Canceled after 2h22m14s
Build Kiosk Images (x86) / commercial (push) Canceled after 0s
Build Kiosk Images (x86) / kiosk (push) Canceled after 0s
Build Kiosk Images (x86) / android (push) Canceled after 0s
Local verification checklists kept alongside the checkout; not configuration.
2026-10-07 11:09:01 -05:00
NeilClack f74d8cb708 Make the baked app a real store-path dependency via builtins.storePath
Build Kiosk Image (Raspberry Pi) / rpi (push) Canceled after 9s
Build Kiosk Images (x86) / android (push) Successful in 3m56s
Build Kiosk Images (x86) / commercial (push) Successful in 3m32s
Build Kiosk Images (x86) / kiosk (push) Successful in 4m7s
The previous commit wrapped the manifest's storePath string in a
symlinkJoin. symlinkJoin passes its paths as a file, so a bare store-path
string registered no dependency: Nix never substituted the closure and the
symlinkJoin failed with 'radiantwave-9b57e48: No such file or directory'.

builtins.storePath turns the published path into a real store-path
dependency that Nix substitutes from the channel's Attic cache, so the app
is baked and never compiled. Keep the raw string (publishedPath) for the
updater's plain-string baseline pointer.
2026-10-07 10:48:15 -05:00
NeilClack a33a586485 Bake the channel manifest's closure instead of a pinned app version
Build Kiosk Image (Raspberry Pi) / rpi (push) Canceled after 9s
Build Kiosk Images (x86) / android (push) Failing after 19s
Build Kiosk Images (x86) / commercial (push) Failing after 18s
Build Kiosk Images (x86) / kiosk (push) Failing after 18s
Production builds failed CI's baseline preflight because flake.nix pinned
the app to tag 1.8 (channel.appVersion = "1.8") while the published
manifest had moved on to 1.9; dev was likewise pinned to a stale commit.

Stop pinning the app anywhere. flake.nix now fetches the channel's
published manifest at eval time (builtins.fetchurl) and bakes its
storePath verbatim, so every image carries exactly the closure RadiantWave
published to Attic and never compiles the app from source.

- flake.nix: drop the radiantwave/radiantwave-dev inputs, appInput,
  appVersion, channelVersion and baselineOutPaths; add channelAtticUrl,
  channelManifestDir and publishedApp; builders consume the published path.
- configuration.nix: wrap the published store path in a symlinkJoin so it
  can live in environment.systemPackages; add --impure to autoUpgrade
  flags and set tarball-ttl = 0 so eval reads the current manifest.
- radiantwave-updater.nix: baselinePackage (package) -> baselinePath (str),
  seeded from the exact manifest path.
- CI (build.yml, build-rpi.yml): replace the version-equality preflight with
  a fail-fast check that the announced closure is in the channel's Attic;
  set tarball-ttl = 0.
- build-rpi.sh: pass --impure.
- AGENTS.md: document the manifest-driven baseline.
2026-10-07 10:26:17 -05:00
NeilClack 7aa88ee174 Update radiantwave-updater script to auto-restart tty1 2026-10-05 21:50:15 -05:00
NeilClack 31bc806d01 Update preauthkey and join script
Build Kiosk Image (Raspberry Pi) / rpi (push) Canceled after 0s
Build Kiosk Images (x86) / android (push) Successful in 3m44s
Build Kiosk Images (x86) / commercial (push) Successful in 3m41s
Build Kiosk Images (x86) / kiosk (push) Successful in 3m45s
2026-10-04 20:12:25 -05:00
NeilClack eafa4b6e26 Split raspberry pi build into a separate workflow
Build Kiosk Image (Raspberry Pi) / rpi (push) Canceled after 9s
Build Kiosk Images (x86) / android (push) Successful in 4m4s
Build Kiosk Images (x86) / commercial (push) Successful in 3m47s
Build Kiosk Images (x86) / kiosk (push) Successful in 3m41s
2026-10-04 18:34:31 -05:00
NeilClack 44d402b179 ci: define kiosk last so it builds first
Build Kiosk Images / android (push) Canceled after 0s
Build Kiosk Images / commercial (push) Canceled after 0s
Build Kiosk Images / kiosk (push) Canceled after 0s
Build Kiosk Images / rpi (push) Canceled after 0s
Gitea schedules matrix jobs in reverse definition order on the single
orion-lxc runner, so kiosk (previously defined first) built last. Reverse
the include list so kiosk builds first; order is rpi, android, commercial,
kiosk in the file.
2026-10-04 18:16:12 -05:00
NeilClack 1e996d4c05 Add tailscale
Build Kiosk Images / android (push) Canceled after 0s
Build Kiosk Images / commercial (push) Canceled after 0s
Build Kiosk Images / kiosk (push) Canceled after 0s
Build Kiosk Images / rpi (push) Canceled after 21m40s
2026-10-04 18:12:47 -05:00
NeilClack e50391ba9e ci: unify all kiosk build jobs into one branch-aware workflow
Build Kiosk Images / android (push) Canceled after 0s
Build Kiosk Images / commercial (push) Canceled after 0s
Build Kiosk Images / kiosk (push) Canceled after 0s
Build Kiosk Images / rpi (push) Canceled after 8m21s
Single .gitea/workflows/build.yml shared by both branches; the checked-out
ref selects the channel, so no per-branch copies.

- dev push -> builds all development images (kiosk, commercial, android, rpi)
- new tag  -> builds all production images; manual dispatch also supported
- Raspberry Pi sdImage built on orion-lxc via aarch64 emulation
- production-only baseline preflight (skipped on the dev channel)
- artifact names standardized to radiantwave-<variant>-<prod|dev>-<timestamp>
2026-10-04 18:04:35 -05:00
NeilClack 8796feddd9 Declare app version per channel and fix Android live updates
- Add channel.appVersion and an appPackage override so pure builds bake the pinned version instead of RadiantWave's 0.1.0 default.

- Pin the radiantwave input to tag 1.8 and allow unfree in the RPi builder to match x86.

- 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.
2026-10-03 11:59:29 -05:00
NeilClack b6de748f8f Replace radiantwave-infra with IP address
Build Dev ISOs / build-dev-kiosk (push) Successful in 1m20s
Build Dev ISOs / build-dev-android (push) Successful in 1m36s
Build Dev ISOs / build-dev-commercial (push) Successful in 3m47s
2026-09-30 14:25:42 -05:00
NeilClack cac2e455ec Update output file path
Build Dev ISOs / build-dev-kiosk (push) Failing after 13s
Build Dev ISOs / build-dev-android (push) Failing after 12s
Build Dev ISOs / build-dev-commercial (push) Failing after 11s
2026-09-30 12:27:04 -05:00
NeilClack 85788ce32d Update action runner to orion-lxc
Build Dev ISOs / build-dev-kiosk (push) Failing after 1s
Build Dev ISOs / build-dev-android (push) Failing after 1s
Build Dev ISOs / build-dev-commercial (push) Failing after 1s
2026-09-30 12:04:51 -05:00
NeilClack e6138d398d Update raspberry pi builds 2026-09-30 12:04:37 -05:00
NeilClack c18e158b69 Point dev branch at the dev RadiantWave channel
Build Dev ISOs / build-dev-kiosk (push) Successful in 4m29s
Build Dev ISOs / build-dev-android (push) Successful in 4m31s
Build Dev ISOs / build-dev-commercial (push) Successful in 4m36s
Fixes b136d17 which left the channel block on main values, so the dev
branch was building/tracking prod instead of radiantwave-dev.
2026-08-28 20:30:51 -05:00
NeilClack b136d1751b Merge -dev closures.
Build Dev ISOs / build-dev-kiosk (push) Successful in 4m55s
Build Dev ISOs / build-dev-android (push) Successful in 4m52s
Build Dev ISOs / build-dev-commercial (push) Successful in 4m53s
Consolidates 8 closures to only 4 by using a git branch selector that sets
the values for -dev variants of each closure.

Reference the 'Verifying KioskConfig builds are in the right branch' note
in Obsidian for more reference.
2026-08-28 20:15:32 -05:00
NeilClack 05bb98b107 Enable live app updater on production variants
Build Dev ISOs / build-dev-android (push) Canceled after 0s
Build Dev ISOs / build-dev-commercial (push) Canceled after 0s
Build Dev ISOs / build-dev-kiosk (push) Canceled after 1m10s
All variants now follow the same update procedure as the dev flakes;
prod variants pull the RadiantWave binary, Attic repo, and manifest
from the radiantwave channel instead of radiantwave-dev.
2026-08-28 16:37:13 -05:00
NeilClack ca89c234ab Fix updater timer not re-arming after first run
Build Dev ISOs / build-dev-kiosk (push) Successful in 4m32s
Build Dev ISOs / build-dev-commercial (push) Successful in 5m4s
Build Dev ISOs / build-dev-android (push) Successful in 5m28s
RemainAfterExit=true kept the oneshot 'active' forever, which broke the
timer's OnUnitActiveSec re-arming: the timer fired once (NEXT emptied,
Trigger n/a) and never polled again. Remove it so the service returns to
dead and the 2-min cadence keeps firing.
2026-08-28 15:59:23 -05:00
NeilClack 9a8a258228 GC-root the fetched radiantwave closure
Build Dev ISOs / build-dev-kiosk (push) Canceled after 0s
Build Dev ISOs / build-dev-android (push) Canceled after 0s
Build Dev ISOs / build-dev-commercial (push) Canceled after 0s
nix-store -r was fetching the closure without --add-root, so the swapped-in
app pointer wasn't a GC root and the running closure could be collected.
Pin it with --add-root .current-root, and root the already-current closure
on the no-op path too.
2026-08-28 15:30:20 -05:00
NeilClack 7d8cc56ba0 Fetch updater closures from cache.nixos.org as well as Attic
Build Dev ISOs / build-dev-kiosk (push) Successful in 5m1s
Build Dev ISOs / build-dev-android (push) Successful in 4m44s
Build Dev ISOs / build-dev-commercial (push) Canceled after 2m31s
nix copy --from <attic> only pulls from that single store, so nixpkgs
deps (e.g. sqlite) that Attic skips and leaves on cache.nixos.org were
missing -> 'path is not valid'. Realize the closure through the machine's
substituters so the app comes from the dev Attic and its upstream deps
resolve from cache.nixos.org, without building from source.
2026-08-28 15:25:04 -05:00
NeilClack e44626feec Update RadiantWave a9087f2
Build Dev ISOs / build-dev-kiosk (push) Successful in 5m38s
Build Dev ISOs / build-dev-android (push) Successful in 5m21s
Build Dev ISOs / build-dev-commercial (push) Successful in 5m38s
2026-08-28 13:55:59 -05:00
NeilClack 48c31d0f0c Move home systems to first build slot
Build Dev ISOs / build-dev-kiosk (push) Successful in 4m56s
Build Dev ISOs / build-dev-android (push) Successful in 4m45s
Build Dev ISOs / build-dev-commercial (push) Successful in 5m10s
2026-08-27 21:26:45 -05:00
NeilClack d661162676 Add live RadiantWave updater (dev variants)
Build Dev ISOs / build-dev-android (push) Successful in 1m48s
Build Dev ISOs / build-dev-kiosk (push) Successful in 1m37s
Build Dev ISOs / build-dev-commercial (push) Successful in 1m46s
Add a radiantwave-updater NixOS module that polls a manifest on the
fileserver and swaps the app pointer /var/lib/radiantwave/current to new
closures fetched from the dev Attic cache. Closures are fetched with
'nix copy --from', never built from source; the swap is atomic via
mv -T.

Enable it on the -dev variants only for now: the login shell launches
from /var/lib/radiantwave/current/bin/radiantwave when enabled, and from
the immutable system path otherwise, so prod behavior is unchanged. The
pointer is seeded on first boot to the baseline copy already baked into
the closure (no duplication).
2026-08-27 20:58:07 -05:00
NeilClack b8d0f59c0a Remove EDID overwrite
Build Dev ISOs / build-dev-android (push) Successful in 4m50s
Build Dev ISOs / build-dev-kiosk (push) Successful in 4m49s
Build Dev ISOs / build-dev-commercial (push) Successful in 4m45s
2026-08-26 12:30:36 -05:00
NeilClack 4e36113636 Disable EDID Override
Build Dev ISOs / build-dev-android (push) Successful in 4m53s
Build Dev ISOs / build-dev-kiosk (push) Successful in 4m51s
Build Dev ISOs / build-dev-commercial (push) Successful in 4m45s
2026-08-26 11:38:17 -05:00
NeilClack 2bf762acac Add actual EDID from Android Kiosk
Build Dev ISOs / build-dev-android (push) Successful in 5m5s
Build Dev ISOs / build-dev-kiosk (push) Successful in 4m56s
Build Dev ISOs / build-dev-commercial (push) Successful in 4m53s
2026-08-26 10:12:45 -05:00
NeilClack 46a61a8397 Remove HDMI-A-1
Build Dev ISOs / build-dev-commercial (push) Successful in 4m17s
Build Dev ISOs / build-dev-android (push) Successful in 4m23s
Build Dev ISOs / build-dev-kiosk (push) Successful in 4m56s
HDMI-A-1 refers to the HDMI port closest to the power cable.
In all models, we end up connecting HDMI to port 2.
HDMI-A-1 was likely taking over, becuase it suddenly had a valid
EDID assigned, indicating 'a monitor is connected here'
2026-08-25 22:26:00 -05:00
NeilClack c1ad4a9767 Remove archive steps...
Build Dev ISOs / build-dev-android (push) Successful in 4m41s
Build Dev ISOs / build-dev-kiosk (push) Successful in 4m34s
Build Dev ISOs / build-dev-commercial (push) Successful in 5m6s
2026-08-25 21:49:09 -05:00
NeilClack 47dc7b8964 Update dev builds to build each -dev variant
Build Dev ISOs / build-dev-kiosk (push) Failing after 3m18s
Build Dev ISOs / build-dev-android (push) Canceled after 0s
Build Dev ISOs / build-dev-commercial (push) Canceled after 2m20s
2026-08-25 21:43:20 -05:00
NeilClack dfa7320a37 Test 1 - EDID Spoofing for Android 4K
Build Dev ISO / build-dev-iso (push) Canceled after 2m40s
2026-08-25 21:40:56 -05:00
NeilClack 87fca854f4 Update radiantwave - rotated
Build Dev ISO / build-dev-iso (push) Successful in 4m13s
2026-08-25 11:31:56 -05:00
NeilClack c8298a128f Update Radiantwave 1.3
Build Dev ISO / build-dev-iso (push) Successful in 4m38s
Stretch scroller screen on vertical displays. Test 1
2026-08-25 11:00:28 -05:00
NeilClack 036c437430 Update RadiantWave 1.2
Build Dev ISO / build-dev-iso (push) Successful in 4m31s
2026-08-25 10:33:59 -05:00
NeilClack 8775fcfb24 Update workflows to run automatically on push to main
Build Dev ISO / build-dev-iso (push) Canceled after 0s
2026-08-25 08:09:26 -05:00
NeilClack f900585702 Update Radiantwave 1.2
Build Dev ISO / build-dev-iso (push) Successful in 4m51s
2026-08-25 08:01:13 -05:00
NeilClack 16b8e3aeb9 Update Radiantwave 1.1
Build Dev ISO / build-dev-iso (push) Failing after 3m37s
2026-08-25 06:59:10 -05:00
NeilClack 45b4d7aa95 Update radiantwave
Build Dev ISO / build-dev-iso (push) Successful in 4m21s
2026-08-23 21:20:38 -05:00
NeilClack bd9146e641 Allow kiosk to restart getty@tty1 via scoped polkit rule
Build Dev ISO / build-dev-iso (push) Successful in 4m23s
The RadiantWave app can now apply pending updates by restarting its own
TTY session (plain systemctl, no sudo): getty re-autologins kiosk and
the login shell relaunches cage + app from the new generation.

Also drop the cleanRadiantFiles activation script.
2026-08-23 17:54:01 -05:00
NeilClack 9b0036b4da Update RadiantWave ef723b2
Build Dev ISO / build-dev-iso (push) Successful in 4m48s
2026-08-23 17:33:57 -05:00
NeilClack d26cec2a53 fix: broken whitespace guard in archive-images.sh
Build Dev ISO / build-dev-iso (push) Successful in 4m19s
The case bracket expression [\ space\'\"] listed the letters s,p,a,c,e
as a character set instead of matching whitespace, rejecting every normal
path. Use the POSIX [[:space:]] class.
2026-08-23 15:55:05 -05:00
NeilClack 2e862c13a8 fix: replace fragile ssh archive one-liners with tools/archive-images.sh
Build Dev ISO / build-dev-iso (push) Failing after 3m18s
The inline find -exec broke through the double shell hop (the remote
shell consumed the ; separator). The script pipes its payload to remote
bash via stdin, tolerates zero matches, and fails loudly on missing
directories or failed moves.
2026-08-23 15:45:11 -05:00
NeilClack 84ec13b1f9 ci: restore dev ISO workflow, auto-build on dev push, use Attic
Build Dev ISO / build-dev-iso (push) Failing after 3m19s
2026-08-23 15:22:34 -05:00
NeilClack 588f0e4aa1 ci: pull closures from Attic caches in prod builds 2026-08-23 15:22:34 -05:00
NeilClack 46384e6cf7 Fix attic upload
build-dev / nix-build (push) Failing after 1s
2026-08-23 15:07:22 -05:00
NeilClack 6951d35310 fix: make manual dev checkout work (fetch all branch tips, drop bad cd) 2026-08-23 14:53:32 -05:00
NeilClack 7778da3867 fix: tolerate empty archive globs; clone dispatched ref in dev workflow 2026-08-23 14:49:44 -05:00
NeilClack 59e9f7ce29 Update RadiantWave and add Attic cache 2026-08-23 14:18:48 -05:00
NeilClack 3ec72c8479 Bring radiantwave up to date + dev 2026-08-23 11:24:47 -05:00
NeilClack 20d91df9eb Revert RadiantWave for testing the new update configuration 2026-08-23 11:23:21 -05:00
NeilClack 65a6b256f9 Fix dev/prod split and dev ISOs; consolidate zero-touch installer
- Wire -dev variants to the radiantwave-dev input (?ref=dev); prod keeps main
- Fix android-dev upgradeFlakeRef (was upgrading into production config)
- Parameterize one installer module (zero-touch-installer.nix); replace the
  four near-duplicate ISO configs; mkIso pairs each ISO with its closure +
  disk layout, fixing commercial-dev/android-dev ISO eval errors
- Add missing packages outputs (commercial-dev, android-dev, rpi, rpi-dev)
- Set networking.hostName + system.configurationRevision per variant
- Remove gitea access token and dead netrc/JWT config (repos are public)
- Drop flake.lock update step from CI (lock updates are manual)
- Refresh AGENTS.md: variants/ISO tables, facter report procedure, gotchas
2026-08-23 09:58:46 -05:00
NeilClack 64b2a9cfc4 Shrink splash screen 2026-08-23 08:20:10 -05:00
NeilClack 875f3da03d Add tools 2026-08-22 17:07:54 -05:00
NeilClack 9965e30152 Fix git urls 2026-08-22 17:07:47 -05:00
NeilClack a01c3d5521 Update RadiantWave 2026-08-22 16:29:56 -05:00
NeilClack 33c9ceefb0 Update RadiantWave
Enables the naming of audio files by editing audio file names in new_checksums.md5
2026-08-21 10:04:40 -05:00
NeilClack c9743de7bb Update RadiantWave 2026-08-21 09:54:11 -05:00
NeilClack 6ddf004e95 Update RadiantWave to remove WiFi Gate 2026-08-18 23:39:22 -04:00
NeilClack 439e646170 Remove archive command 2026-08-18 23:02:32 -04:00
NeilClack f8cb1746af Release RadiantWave (C++) 2026-08-18 04:44:58 -05:00
NeilClack a4f4d17d6e Add archive step to prod workflows; rename radiantwave-iso to radiantwave-home 2026-08-17 22:51:11 -05:00
NeilClack e7a5ffa290 Update Radiantwave to ec6b766 2026-08-17 22:47:16 -05:00
NeilClack 553b06ea92 Fix closure naming' 2026-08-17 22:45:18 -05:00
NeilClack 8e8da490e4 Update radiantwave reference and add dev variants to all images 2026-08-17 22:44:14 -05:00
NeilClack 75cce42de5 Add cleanup command for fileserver 2026-08-17 22:23:37 -05:00
NeilClack 572ab76100 Add name 2026-08-17 22:13:01 -05:00
NeilClack d37c794be4 Fix repository pointers 2026-08-17 22:08:47 -05:00
NeilClack b26081046e Update RadiantWave
build-dev.yml / update-and-build (push) Canceled after 0s
2026-08-17 21:35:20 -05:00
NeilClack 87367dab04 Temporarily remove healthcheck
build-dev.yml / update-and-build (push) Canceled after 1m40s
Healthcheck may be failing for reasons not related to a bad binary, preventing a good binary from booting.
2026-08-17 19:53:06 -05:00
NeilClack 0a35c97379 Remove user password 2026-08-17 19:49:15 -05:00
NeilClack 2fb99175f7 Re-size Plymouth image to 1920x1080 2026-08-17 19:49:01 -05:00
35 changed files with 1337 additions and 877 deletions
-54
View File
@@ -1,54 +0,0 @@
on:
push:
branches:
- main
workflow_dispatch:
jobs:
update-and-build:
runs-on: build-server
steps:
- name: Checkout
run: git clone --depth 1 ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/KioskConfig.git .
- name: Update radiantwave-dev
run: |
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
nix flake update radiantwave-dev
- name: Prepare output directory
run: mkdir -p /home/user/radiantwave
# --- Kiosk ISO ---
- name: Build kiosk ISO
run: |
export NIXPKGS_ALLOW_UNFREE=1
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
nix build -L --impure .#nixosConfigurations.dev-iso.config.system.build.isoImage
- name: Rename kiosk ISO
run: |
ISO_DATE=$(date +%Y-%m-%d_%H-%M-%S)
FILENAME="radiantwave-kiosk-dev-iso-${ISO_DATE}.iso"
cp -L result/iso/*.iso "/home/user/radiantwave/${FILENAME}"
echo "KIOSK_ISO_FILENAME=${FILENAME}" >> "$GITHUB_ENV"
- name: Copy kiosk ISO to fileserver
run: |
scp "/home/user/radiantwave/${KIOSK_ISO_FILENAME}" \
radiantwave-infra:/mnt/infrastructure_data/files/images/
- name: Cleanup kiosk ISO
run: rm -f "/home/user/radiantwave/${KIOSK_ISO_FILENAME}"
- name: Commit updated flake.lock
run: |
git config user.email "ci@radiantwavetech.com"
git config user.name "RadiantWave CI"
git remote set-url origin https://oauth2:${{ secrets.GIT_TOKEN }}@gitea.radiantwavetech.com/RadiantWaveTech/kioskconfig.git
git add flake.lock
git commit -m "chore: update radiantwave-dev to latest" || echo "Nothing to commit"
git push
-34
View File
@@ -1,34 +0,0 @@
on:
workflow_dispatch:
jobs:
build-android:
runs-on: build-server
steps:
- name: Checkout
run: git clone --depth 1 ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/KioskConfig.git .
- name: Prepare output directory
run: mkdir -p /home/user/radiantwave
- name: Build android kiosk ISO
run: |
export NIXPKGS_ALLOW_UNFREE=1
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
nix build -L --impure .#nixosConfigurations.android-iso.config.system.build.isoImage
- name: Rename android kiosk ISO
run: |
ISO_DATE=$(date +%Y-%m-%d_%H-%M-%S)
FILENAME="radiantwave-android-iso-${ISO_DATE}.iso"
cp -L result/iso/*.iso "/home/user/radiantwave/${FILENAME}"
echo "ANDROID_ISO_FILENAME=${FILENAME}" >> "$GITHUB_ENV"
- name: Copy android kiosk ISO to fileserver
run: |
scp "/home/user/radiantwave/${ANDROID_ISO_FILENAME}" \
radiantwave-infra:/mnt/infrastructure_data/files/images/
- name: Cleanup android kiosk ISO
run: rm -f "/home/user/radiantwave/${ANDROID_ISO_FILENAME}"
@@ -1,34 +0,0 @@
on:
workflow_dispatch:
jobs:
build-commercial:
runs-on: build-server
steps:
- name: Checkout
run: git clone --depth 1 ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/KioskConfig.git .
- name: Prepare output directory
run: mkdir -p /home/user/radiantwave
- name: Build commercial kiosk ISO
run: |
export NIXPKGS_ALLOW_UNFREE=1
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
nix build -L --impure .#nixosConfigurations.commercial-iso.config.system.build.isoImage
- name: Rename commercial kiosk ISO
run: |
ISO_DATE=$(date +%Y-%m-%d_%H-%M-%S)
FILENAME="radiantwave-commercial-iso-${ISO_DATE}.iso"
cp -L result/iso/*.iso "/home/user/radiantwave/${FILENAME}"
echo "COMMERCIAL_ISO_FILENAME=${FILENAME}" >> "$GITHUB_ENV"
- name: Copy commercial kiosk ISO to fileserver
run: |
scp "/home/user/radiantwave/${COMMERCIAL_ISO_FILENAME}" \
radiantwave-infra:/mnt/infrastructure_data/files/images/
- name: Cleanup commercial kiosk ISO
run: rm -f "/home/user/radiantwave/${COMMERCIAL_ISO_FILENAME}"
-34
View File
@@ -1,34 +0,0 @@
on:
workflow_dispatch:
jobs:
build-kiosk:
runs-on: build-server
steps:
- name: Checkout
run: git clone --depth 1 ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/KioskConfig.git .
- name: Prepare output directory
run: mkdir -p /home/user/radiantwave
- name: Build kiosk ISO
run: |
export NIXPKGS_ALLOW_UNFREE=1
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
nix build -L --impure .#nixosConfigurations.kiosk-iso.config.system.build.isoImage
- name: Rename kiosk ISO
run: |
ISO_DATE=$(date +%Y-%m-%d_%H-%M-%S)
FILENAME="radiantwave-iso-${ISO_DATE}.iso"
cp -L result/iso/*.iso "/home/user/radiantwave/${FILENAME}"
echo "KIOSK_ISO_FILENAME=${FILENAME}" >> "$GITHUB_ENV"
- name: Copy kiosk ISO to fileserver
run: |
scp "/home/user/radiantwave/${KIOSK_ISO_FILENAME}" \
radiantwave-infra:/mnt/infrastructure_data/files/images/
- name: Cleanup kiosk ISO
run: rm -f "/home/user/radiantwave/${KIOSK_ISO_FILENAME}"
-34
View File
@@ -1,34 +0,0 @@
on:
workflow_dispatch:
jobs:
build-rpi:
runs-on: build-server
steps:
- name: Checkout
run: git clone --depth 1 ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/KioskConfig.git .
- name: Prepare output directory
run: mkdir -p /home/user/radiantwave
- name: Build Raspberry Pi image
run: |
export NIXPKGS_ALLOW_UNFREE=1
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
nix build -L --impure .#nixosConfigurations.radiantwavekiosk-rpi.config.system.build.sdImage
- name: Rename Raspberry Pi image
run: |
ISO_DATE=$(date +%Y-%m-%d_%H-%M-%S)
FILENAME="radiantwave-rpi-${ISO_DATE}.img.zst"
cp -L result/sd-image/*.img.zst "/home/user/radiantwave/${FILENAME}"
echo "RPI_IMAGE_FILENAME=${FILENAME}" >> "$GITHUB_ENV"
- name: Copy Raspberry Pi image to fileserver
run: |
scp "/home/user/radiantwave/${RPI_IMAGE_FILENAME}" \
radiantwave-infra:/mnt/infrastructure_data/files/images/
- name: Cleanup Raspberry Pi image
run: rm -f "/home/user/radiantwave/${RPI_IMAGE_FILENAME}"
+116
View File
@@ -0,0 +1,116 @@
# One build workflow per hardware family, identical on every branch — do not
# 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`
# block picks the channel manifest whose closure is baked as the app, the Attic
# repo, and the upgrade ref, so the same config names build dev images on `dev`
# and prod images on `main`.
#
# Trigger model:
# * push to `dev` -> build every development image
# * new git tag -> build every production image (tags live on main)
# * manual dispatch -> build the channel of the selected branch
#
# This file builds the Raspberry Pi variant:
# Raspberry Pi -> .config.system.build.sdImage
# `orion-lxc` emulates aarch64-linux, so this runs on the same runner.
name: Build Kiosk Image (Raspberry Pi)
on:
workflow_dispatch:
push:
branches:
- dev
tags:
- "*"
env:
# Both channel caches plus the Raspberry Pi cachix, so a build can substitute
# 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: |
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=
tarball-ttl = 0
jobs:
build:
name: ${{ matrix.label }}
runs-on: orion-lxc
strategy:
fail-fast: false
matrix:
include:
- label: rpi
system: radiantwavekiosk-rpi
target: sdImage
outdir: sd-image
ext: img.zst
arch: aarch64-linux
manifest: aarch64-manifest.json
steps:
- name: Clone repository
run: |
rm -rf ./* ./.[!.]* 2>/dev/null || true
ref="${GITHUB_REF#refs/heads/}"
ref="${ref#refs/tags/}"
git clone --depth 1 --branch "$ref" \
ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/KioskConfig.git .
git log --oneline -1
- name: Determine channel
run: |
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
CHANNEL=$(nix eval --impure --raw .#channelName)
if [ "$CHANNEL" = "radiantwave" ]; then LABEL=prod; else LABEL=dev; fi
echo "CHANNEL=$CHANNEL" >> "$GITHUB_ENV"
echo "CHANNEL_LABEL=$LABEL" >> "$GITHUB_ENV"
echo "Building the '$CHANNEL' channel ($LABEL)"
# Fail fast if the closure the channel manifest announces is not in the
# channel's Attic cache. We bake that exact storePath, so without it the
# image would try to compile RadiantWave from source (which never works:
# 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: |
set -euo pipefail
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
ATTIC=$(nix eval --impure --raw .#channelAtticUrl)
MANIFEST_URL="$(nix eval --impure --raw .#channelManifestDir)/${{ matrix.manifest }}"
MANIFEST=$(curl -fsSL "$MANIFEST_URL")
PUB_PATH=$(MANIFEST="$MANIFEST" nix eval --impure --raw --expr '(builtins.fromJSON (builtins.getEnv "MANIFEST")).storePath')
echo "channel : $CHANNEL"
echo "attic : $ATTIC"
echo "manifest: $MANIFEST_URL"
echo "closure : $PUB_PATH"
HASH=$(basename "$PUB_PATH" | cut -d- -f1)
if ! curl -fsS -o /dev/null "$ATTIC/${HASH}.narinfo"; then
echo "ERROR: $PUB_PATH is not in $ATTIC; the image would compile RadiantWave from source."
exit 1
fi
- name: Prepare output directory
run: mkdir -p /var/lib/gitea-runner/radiantwave
- name: Build ${{ matrix.label }}
run: |
export NIXPKGS_ALLOW_UNFREE=1
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
nix build -L --impure \
".#nixosConfigurations.${{ matrix.system }}.config.system.build.${{ matrix.target }}"
- name: Publish ${{ matrix.label }}
run: |
ISO_DATE=$(date +%Y-%m-%d_%H-%M-%S)
FILENAME="radiantwave-${{ matrix.label }}-${CHANNEL_LABEL}-${ISO_DATE}.${{ matrix.ext }}"
cp -L result/${{ matrix.outdir }}/*.${{ matrix.ext }} \
"/var/lib/gitea-runner/radiantwave/${FILENAME}"
scp "/var/lib/gitea-runner/radiantwave/${FILENAME}" \
157.245.124.103:/mnt/infrastructure_data/files/images/
rm -f "/var/lib/gitea-runner/radiantwave/${FILENAME}"
+132
View File
@@ -0,0 +1,132 @@
# One build workflow per hardware family, identical on every branch — do not
# 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`
# block picks the channel manifest whose closure is baked as the app, the Attic
# repo, and the upgrade ref, so the same config names build dev images on `dev`
# and prod images on `main`.
#
# Trigger model:
# * push to `dev` -> build every development image
# * new git tag -> build every production image (tags live on main)
# * manual dispatch -> build the channel of the selected branch
#
# This file builds the x86_64 ISO variants:
# ISO variants -> .config.system.build.isoImage (storeContents pulls in the closure)
name: Build Kiosk Images (x86)
on:
workflow_dispatch:
push:
branches:
- dev
tags:
- "*"
env:
# Both channel caches plus the Raspberry Pi cachix, so a build can substitute
# 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: |
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=
tarball-ttl = 0
jobs:
build:
name: ${{ matrix.label }}
runs-on: orion-lxc
strategy:
fail-fast: false
matrix:
include:
# NOTE: Gitea schedules matrix jobs in reverse definition order on
# the single orion-lxc runner, so the list is reversed on purpose:
# android -> commercial -> kiosk means kiosk builds FIRST.
- label: android
system: android-iso
target: isoImage
outdir: iso
ext: iso
arch: x86_64-linux
manifest: manifest.json
- label: commercial
system: commercial-iso
target: isoImage
outdir: iso
ext: iso
arch: x86_64-linux
manifest: manifest.json
- label: kiosk
system: kiosk-iso
target: isoImage
outdir: iso
ext: iso
arch: x86_64-linux
manifest: manifest.json
steps:
- name: Clone repository
run: |
rm -rf ./* ./.[!.]* 2>/dev/null || true
ref="${GITHUB_REF#refs/heads/}"
ref="${ref#refs/tags/}"
git clone --depth 1 --branch "$ref" \
ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/KioskConfig.git .
git log --oneline -1
- name: Determine channel
run: |
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
CHANNEL=$(nix eval --impure --raw .#channelName)
if [ "$CHANNEL" = "radiantwave" ]; then LABEL=prod; else LABEL=dev; fi
echo "CHANNEL=$CHANNEL" >> "$GITHUB_ENV"
echo "CHANNEL_LABEL=$LABEL" >> "$GITHUB_ENV"
echo "Building the '$CHANNEL' channel ($LABEL)"
# Fail fast if the closure the channel manifest announces is not in the
# channel's Attic cache. We bake that exact storePath, so without it the
# image would try to compile RadiantWave from source (which never works:
# 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: |
set -euo pipefail
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
ATTIC=$(nix eval --impure --raw .#channelAtticUrl)
MANIFEST_URL="$(nix eval --impure --raw .#channelManifestDir)/${{ matrix.manifest }}"
MANIFEST=$(curl -fsSL "$MANIFEST_URL")
PUB_PATH=$(MANIFEST="$MANIFEST" nix eval --impure --raw --expr '(builtins.fromJSON (builtins.getEnv "MANIFEST")).storePath')
echo "channel : $CHANNEL"
echo "attic : $ATTIC"
echo "manifest: $MANIFEST_URL"
echo "closure : $PUB_PATH"
HASH=$(basename "$PUB_PATH" | cut -d- -f1)
if ! curl -fsS -o /dev/null "$ATTIC/${HASH}.narinfo"; then
echo "ERROR: $PUB_PATH is not in $ATTIC; the image would compile RadiantWave from source."
exit 1
fi
- name: Prepare output directory
run: mkdir -p /var/lib/gitea-runner/radiantwave
- name: Build ${{ matrix.label }}
run: |
export NIXPKGS_ALLOW_UNFREE=1
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
nix build -L --impure \
".#nixosConfigurations.${{ matrix.system }}.config.system.build.${{ matrix.target }}"
- name: Publish ${{ matrix.label }}
run: |
ISO_DATE=$(date +%Y-%m-%d_%H-%M-%S)
FILENAME="radiantwave-${{ matrix.label }}-${CHANNEL_LABEL}-${ISO_DATE}.${{ matrix.ext }}"
cp -L result/${{ matrix.outdir }}/*.${{ matrix.ext }} \
"/var/lib/gitea-runner/radiantwave/${FILENAME}"
scp "/var/lib/gitea-runner/radiantwave/${FILENAME}" \
157.245.124.103:/mnt/infrastructure_data/files/images/
rm -f "/var/lib/gitea-runner/radiantwave/${FILENAME}"
+2
View File
@@ -4,3 +4,5 @@ build/out/
.direnv .direnv
wifi-psk.txt wifi-psk.txt
*.qcow2 *.qcow2
VERIFY-DEV.md
VERIFY-PROD.md
+80 -32
View File
@@ -2,72 +2,120 @@
## 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 input (from gitea.radiantwavetech.com). NixOS flake that defines kiosk systems running the RadiantWave app. Not application code — this is the OS image and deployment configuration. The 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`
## Prod/dev split
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`: `atticRepo = "radiantwave"`, `gitRef = ""` (default branch).
- `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 two `channel` values. When merging, reconcile just this one block.
**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.
## 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 # 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
``` ```
## Config variants ## Config variants
| Name | Target hardware | Disk device | Notes | One config per hardware target. Dev/prod is the branch, not the config — see `channel` in `flake.nix`:
| Name | Target hardware | Disk device | App closure |
|---|---|---|---| |---|---|---|---|
| `radiantwavekiosk` | Intel NUC 14 (NVMe) | `/dev/nvme0n1` | Production, uses `radiantwave` main | | `radiantwavekiosk` | Intel NUC 14 (NVMe) | `/dev/nvme0n1` | channel manifest `manifest.json` |
| `radiantwavekiosk-dev` | Intel NUC 14 (NVMe) | `/dev/nvme0n1` | Dev, uses `radiantwave-dev` ref | | `radiantwavekiosk-commercial` | Commercial HW (SATA) | `/dev/sda` | channel manifest `manifest.json` |
| `radiantwavekiosk-commercial` | Commercial HW (SATA) | `/dev/sda` | Production | | `radiantwavekiosk-android` | Intel NUC 14, rotated display | `/dev/nvme0n1` | channel manifest `manifest.json` |
| `radiantwavekiosk-commercial-dev` | Commercial HW (SATA) | `/dev/sda` | Dev | | `radiantwavekiosk-rpi` | Raspberry Pi 5 (SD image) | — | channel manifest `aarch64-manifest.json` |
| `radiantwavekiosk-android` | Intel NUC 14 | `/dev/nvme0n1` | + rotated display (portrait) |
| `radiantwavekiosk-rpi` | Raspberry Pi 5 | — | Uses `nixos-raspberrypi` |
| `radiantwavekiosk-rpi-dev` | Raspberry Pi 5 | — | Dev, uses `radiantwave-dev` |
ISOs: `usb-iso`, `commercial-usb-iso`, `android-iso`, `dev-iso` — each bakes in the corresponding system closure for zero-touch install. On the `dev` branch, the same four configs read the `radiantwave-dev` manifests (`?ref=dev`).
## Branching model Every variant sets its own `networking.hostName` (same as the config name) and `system.configurationRevision`, so `tools/find_systems.zsh` can identify fleet members.
Single branch (`main`). Production vs. dev split is at the **flake input** level (`radiantwave` vs `radiantwave-dev`), not at the branch level. ### USB images (zero-touch installers)
## CI (Forgejo) Each ISO installs exactly one closure using that closure's disk layout (`mkIso` in `flake.nix` derives both from the target config name):
`.forgejo/workflows/build-dev.yml` — triggered by `repository_dispatch` (`radiantwave-dev-built`) from the RadiantWave repo. Updates `radiantwave-dev` input, builds dev closure + ISO, commits updated `flake.lock`. Runs on `neils-server-runner`. | ISO | Installs | Disk layout |
|---|---|---|
| `kiosk-iso` | `radiantwavekiosk` | NVMe (`disk-config.nix`) |
| `commercial-iso` | `radiantwavekiosk-commercial` | SATA (`commercial-disk-config.nix`) |
| `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 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.
Raspberry Pi systems are built as SD images, not ISOs:
`nix build --impure .#nixosConfigurations.radiantwavekiosk-rpi.config.system.build.sdImage` (see `build-rpi.sh`).
## Hardware facter reports
`facter.json` is a hardware report captured on a **NUC 14**. It is currently applied to **every x86 variant**, including the commercial SATA hardware — acceptable until per-model reports exist.
To generate a report for a hardware model:
1. Boot that hardware from any NixOS live image (the kiosk installers work).
2. With real peripherals attached (display(s), audio), run:
```bash
sudo nix shell nixpkgs#nixos-facter -c nixos-facter -o facter-<model>.json
```
3. Commit `facter-<model>.json` to the repo root.
4. Wire it up at the marked spot in `flake.nix` (`facter.reportPath` inside `mkKioskSystem`) by selecting the right file per variant.
Regenerate when major hardware changes, not on a schedule — reports are snapshots. RPi configs don't use the facter module, so no report is needed there.
## CI (Gitea)
`.gitea/workflows/build.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`. Don't mix them up. - **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.
- **ISO configs reference specific system closures**: `iso-config.nix` hardcodes `radiantwavekiosk`, `dev-iso.nix` hardcodes `radiantwavekiosk-dev`, `commercial-iso-config.nix` hardcodes `radiantwavekiosk-commercial`. Changing a config name in `flake.nix` requires updating the corresponding ISO config. - **Changing a config name** requires updating that config's `upgradeFlakeRef` in `flake.nix` (it embeds the name).
- **Attic cache token**: ISO configs embed a JWT token for Attic in `/etc/nix/netrc`. If builds fail with auth errors, the token may have expired (expiry in the JWT payload). - **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.
- **SOPS**: `.sops.yaml` configures age-based encryption for `secrets.yaml`. You'll need the private key matching `age1t2wpzv...` to decrypt. - **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`.
- **Flake inputs**: `radiantwave` tracks main, `radiantwave-dev` tracks `?ref=dev`. Updating them independently is intentional. - **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.
- **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`.
- **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`.
## File map ## File map
- `flake.nix` — all system definitions, entry point - `flake.nix` — entry point: inputs, the `channel` dev/prod selector, builders (`mkKioskSystem`, `mkRpiKioskSystem`, `mkIso`), all system definitions, facter report wiring
- `configuration.nix` — shared NixOS config (packages, services, kiosk user, auto-upgrade, health check, VM variant) - `configuration.nix` — shared NixOS config (packages, services, kiosk user, auto-upgrade, health check, VM variant)
- `zero-touch-installer.nix` — parameterized USB installer module used by all three ISOs
- `disk-config.nix` — NVMe disk layout (NUC) - `disk-config.nix` — NVMe disk layout (NUC)
- `commercial-disk-config.nix` — SATA disk layout (commercial HW) - `commercial-disk-config.nix` — SATA disk layout (commercial HW)
- `iso-config.nix` — production ISO + zero-touch installer
- `dev-iso.nix` — dev ISO + zero-touch installer
- `commercial-iso-config.nix` — commercial ISO + zero-touch installer
- `rotated_display.nix` — portrait display override for Android variant - `rotated_display.nix` — portrait display override for Android variant
- `facter.json` — hardware facter report (NUC 14) - `facter.json` — hardware facter report (NUC 14)
- `14rvk.json`, `pn54.json` — additional hardware facter reports
- `assets/` — logo and splash image - `assets/` — logo and splash image
- `plymouth-theme/` — boot splash theme - `plymouth-theme/` — boot splash theme
- `commands.md` — quick-reference for build/deploy commands - `tools/find_systems.zsh` — subnet sweep for fleet inventory over SSH
- `build-rpi.sh` — containerized RPi SD-image build helper
- `PLAN-*.md`, `report.md`, `timeline.csv`, `kiosk-url-changes.csv` — planning/history notes, not configuration
+19
View File
@@ -0,0 +1,19 @@
Implementation complete and verified. Here's what was done:
Implemented (both repos on dev)
Checkpoints (return points if something fails):
- radiantwave → tag pre-live-update @ d84307b
- KioskConfig → tag pre-live-update @ b8d0f59
radiantwave — .gitea/workflows/dev.yml: Added a "Publish manifest" step after the Attic push. It writes {"version","storePath"} using readlink -f ./result-pkg (the exact closure pushed to the dev Attic), then scps it to 157.245.124.103:/mnt/infrastructure_data/files/radiantwave-dev/manifest.json. main.yml untouched.
KioskConfig:
- New radiantwave-updater.nix module: tmpfiles seed (L /var/lib/radiantwave/current → <baked-in baseline>), a systemd oneshot service (curl manifest → nix copy --from dev Attic, never builds → atomic mv -T swap), and a 2-min persistent timer.
- flake.nix: imports the module in both builders and enables it on all 4 -dev variants (radiantwavekiosk-dev, -commercial-dev, -android-dev, -rpi-dev) pointing at the dev manifest/attic.
- configuration.nix: launches via /var/lib/radiantwave/current/bin/radiantwave when the updater is enabled; prod keeps /run/current-system/sw/bin/radiantwave.
Verified
- All dev configs evaluate with enable=true; prod enable=false, no updater service/timer, login path unchanged.
- Rendered service script correct (escaped shell vars, resoled Nix ínterps); dev login line + tmpfiles seed rule confirmed.
- All 14 nixosConfigurations evaluate; toplevel dry-run of radiantwavekiosk-dev plans cleanly.
- dev.yml parses as valid YAML with the new step.
Before this actually works end-to-end, you need to
1. Create /mnt/infrastructure_data/files/radiantwave-dev/ on the infra host and confirm it's served as https://files.radiantwavetech.com/radiantwave-dev/manifest.json.
2. Commit + push these changes (I staged radiantwave-updater.nix so nix can see it; nothing is committed yet) — push dev.yml change to radiantwave dev, and the KioskConfig changes to dev. The dev-iso/etc. workflows will then bake the seed in.
Note: git add radiantwave-updater.nix was needed for flake evaluation; the KioskConfig changes are staged but uncommitted. Want me to commit them?
Binary file not shown.
+8 -3
View File
@@ -6,16 +6,19 @@ TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
FILENAME="radiantwave-raspberrypi-${TIMESTAMP}.img.zst" FILENAME="radiantwave-raspberrypi-${TIMESTAMP}.img.zst"
REPO="$(pwd)" REPO="$(pwd)"
NIX_STORE="kiosk-nix-store" NIX_STORE="kiosk-nix-store"
NIX_STATE="kiosk-nix-state"
echo "==> Building ${CONFIG}..." echo "==> Building ${CONFIG}..."
echo "==> Output: ${REPO}/${FILENAME}" echo "==> Output: ${REPO}/${FILENAME}"
echo "" echo ""
podman volume create "${NIX_STORE}" 2>/dev/null || true podman volume create "${NIX_STORE}" 2>/dev/null || true
podman volume create "${NIX_STATE}" 2>/dev/null || true
podman run --rm \ podman run --rm \
-v "${REPO}:/repo" \ -v "${REPO}:/repo" \
-v "${NIX_STORE}:/nix/store" \ -v "${NIX_STORE}:/nix/store" \
-v "${NIX_STATE}:/nix/var/nix" \
-w /repo \ -w /repo \
nixos/nix:latest \ nixos/nix:latest \
sh -c ' sh -c '
@@ -23,18 +26,20 @@ 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 "extra-substituters = https://cache.nixos.org https://nixos-raspberrypi.cachix.org" >> /etc/nix/nix.conf echo "tarball-ttl = 0" >> /etc/nix/nix.conf
echo "extra-trusted-public-keys = nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI=" >> /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 "max-jobs = 4" >> /etc/nix/nix.conf echo "max-jobs = 4" >> /etc/nix/nix.conf
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
echo "==> Build complete, copying..." echo "==> Build complete, copying..."
cp result/sd-image/*.img* /repo/'${FILENAME}' cp result/sd-image/*.img.zst /repo/'${FILENAME}'
echo "==> Done: ${FILENAME}" echo "==> Done: ${FILENAME}"
ls -lh /repo/'${FILENAME}' ls -lh /repo/'${FILENAME}'
' '
-128
View File
@@ -1,128 +0,0 @@
{
pkgs,
inputs,
self,
...
}:
let
# TODO: Parameterize this to combine with iso-config.nix
# Currently hardcoded to radiantwavekiosk-commercial; should accept target config as param
targetSystem =
self.nixosConfigurations.radiantwavekiosk-commercial.config.system.build.toplevel;
diskoScript =
self.nixosConfigurations.radiantwavekiosk-commercial.config.system.build.diskoScript;
in
{
nix.settings = {
experimental-features = [
"nix-command"
"flakes"
];
substituters = [
"https://cache.nixos.org"
];
flake-registry = "";
};
nix.registry.nixpkgs = pkgs.lib.mkForce {
to = {
type = "path";
path = "${inputs.nixpkgs}";
};
};
environment.variables.KIOSK_FLAKE = "${self}";
virtualisation.vmVariant = {
virtualisation.memorySize = 8096;
virtualisation.diskSize = 100000;
virtualisation.cores = 4;
virtualisation.qemu.options = [
# "-nic none"
];
virtualisation.forwardPorts = [
{ from = "host"; host.port = 2222; guest.port = 22; }
];
};
environment.etc."nix/netrc" = {
text = ''
password eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIwODgzODk2ODEsIm5iZiI6MTc3MjgxMzY4MSwic3ViIjoia2lvc2siLCJodHRwczovL2p3dC5hdHRpYy5ycy92MSI6eyJjYWNoZXMiOnsia2lvc2siOnsiciI6MX19fX0.N6Ktrmmu1LSy2PP8uNF25VHRP1dxPZJ4M0MR1kUpVi0
'';
mode = "0400";
};
nix.extraOptions = ''
netrc-file = /etc/nix/netrc
'';
networking.networkmanager.enable = true;
systemd.services.NetworkManager-wait-online.enable = false;
nix.settings.connect-timeout = 0;
services.openssh.enable = true;
users.users.root.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB4vXx7sOOVAyxdS/N1LEI+L8wYICf+gbzs6Kz7K8Je5 cnclack@pm.me:Thinkpad"
];
systemd.services.zero-touch-install = {
description = "Zero-Touch Kiosk Installation";
wantedBy = [ "multi-user.target" ];
wants = [ ];
after = [ "systemd-udev-settle.service" ];
path = with pkgs; [
nix
git
util-linux
nixos-install-tools
curl
networkmanager
mount
umount
disko
];
environment = {
NIX_PATH = "nixpkgs=${inputs.nixpkgs}";
KIOSK_FLAKE = "${self}";
DISKO_SCRIPT = "${diskoScript}";
};
script = ''
set -euo pipefail
echo "Running disko script..."
echo "Script: $DISKO_SCRIPT"
${diskoScript}
echo "Installing RadiantWave"
echo "Closure Path: ${targetSystem}"
nixos-install \
--system "${targetSystem}" \
--no-root-passwd \
--no-channel-copy
# Redirect to console and show completion message
exec > /dev/tty1 2>&1
echo ""
echo "============================================="
echo " Installation complete!"
echo ""
echo " Please remove the USB drive and reboot."
echo "============================================="
# Wait indefinitely for user action
sleep infinity
''; };
}
+180 -40
View File
@@ -1,8 +1,31 @@
{ pkgs, radiantwave, upgradeFlakeRef, radiantwavePlymouth, ... }: { config, pkgs, radiantwaveApp, upgradeFlakeRef, radiantwavePlymouth, ... }:
let let
radiantApp = radiantwave.packages.${pkgs.system}.default; # The published app is a store path with context (builtins.storePath in
# flake.nix), so Nix substitutes it from the channel's Attic cache and the
# system closure depends on it.
radiantApp = radiantwaveApp;
# When the live-updater module is enabled, RadiantWave launches through the
# mutable /var/lib/radiantwave/current
# pointer so it can be swapped without a full nixos-rebuild. Otherwise it
# launches straight from the immutable system closure as before.
launchBin =
if config.services.radiantwaveUpdater.enable or false
then "/var/lib/radiantwave/current/bin/radiantwave"
else "/run/current-system/sw/bin/radiantwave";
myKey = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL4qWNZnYU5JaqR9c2qh6oFZktzVNBOx42PfDaGofCL6 cnclack@pm.me macbook" ]; myKey = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL4qWNZnYU5JaqR9c2qh6oFZktzVNBOx42PfDaGofCL6 cnclack@pm.me macbook" ];
# Headscale preauth key. Paste/replace here.
#
# This MUST be a *reusable* key with a long expiry (1 year is good), created
# with:
# headscale preauthkeys create --user <user-id> --reusable --expiration 8760h
#
# A single-use key only ever enrolls one node, which would mean a fresh key
# plus a rebuild for every kiosk that joins the fleet. A reusable key lets any
# number of kiosks enroll unattended, and the timer below retries the join so
# a node that misses its window at boot heals on its own.
headscaleAuthKey = "hskey-auth-UfU_eS_sdOu6-CjrqrVdDJFRFsPnaTtK5sFuCiVlffJAS7uQ1wX1_65MNk205WGsF4clOiBD3ZOV-";
in in
{ {
############################################################ ############################################################
@@ -27,7 +50,7 @@ in
# --- RadiantWave splash: Plymouth from boot until the app's first frame --- # --- RadiantWave splash: Plymouth from boot until the app's first frame ---
boot.plymouth.enable = true; boot.plymouth.enable = true;
boot.plymouth.theme = "radiantwave"; boot.plymouth.theme = "radiantwave";
boot.plymouth.themePackages = [ radiantwavePlymouth ]; boot.plymouth.themePackages = [ (radiantwavePlymouth pkgs) ];
# Silence the kernel/initrd output behind the splash. The module adds # Silence the kernel/initrd output behind the splash. The module adds
# "splash" itself; we keep "quiet" plus show_status=false and loglevel 0. # "splash" itself; we keep "quiet" plus show_status=false and loglevel 0.
@@ -55,26 +78,159 @@ in
} }
]; ];
# Let the kiosk app apply pending updates by restarting its own TTY
# session (re-autologin -> fresh cage + RadiantWave). Scoped to exactly
# one unit and verb; everything else keeps stock (denied) policy.
security.polkit.enable = true;
environment.etc."polkit-1/rules.d/10-kiosk-getty.rules".text = ''
polkit.addRule(function(action, subject) {
if (subject.user !== "kiosk") return null;
if (action.id !== "org.freedesktop.systemd1.manage-units") return null;
var unit = action.lookup("unit");
var verb = action.lookup("verb");
if ((unit === "getty@tty1.service" || unit === "getty@tty1") && verb === "restart") {
return polkit.Result.YES;
}
});
'';
hardware.graphics.enable = true; hardware.graphics.enable = true;
time.timeZone = "America/Chicago"; time.timeZone = "America/Chicago";
networking.networkmanager.enable = true; networking.networkmanager.enable = true;
nix.settings.access-tokens = "gitea.radiantwavetech.com=ff524968d31acc5c4da350d0884a87264d0e8881"; ############################################################
# Tailscale (self-hosted Headscale control plane)
############################################################
# The node joins the RadiantWave Headscale server at
# hs.radiantwavetech.com using the *hardware serial* as its Tailscale
# node name (not networking.hostName), so a kiosk is always reachable at
# <serial> even though every variant shares one hostName per branch.
#
# The preauth key is set in `headscaleAuthKey` in the let block at the
# top of this file. NOTE: it ends up in the world-readable /nix/store, so
# keep this file out of public repos while a live key is in it.
services.tailscale = {
enable = true;
useRoutingFeatures = "client";
};
# Reach the kiosk's services (SSH, etc.) over the tailnet without extra
# per-port rules. Membership is gated by the preauth key.
networking.firewall.trustedInterfaces = [ "tailscale0" ];
systemd.services.radiantwave-tailscale-auth = {
description = "Join the RadiantWave tailnet using the hardware serial as node name";
after = [ "tailscaled.service" "network-online.target" ];
wants = [ "tailscaled.service" "network-online.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
# Deliberately no RemainAfterExit: the unit has to fall back to inactive
# so the timer below can trigger it again. The script is idempotent, so
# re-running it on an already-joined kiosk is a cheap no-op.
};
path = [ pkgs.tailscale pkgs.coreutils pkgs.gnused pkgs.gnugrep ];
script = ''
set -eu
# Hardware serial. x86 exposes it via DMI; Raspberry Pi exposes it in
# the device tree (null-terminated), mirrored under /proc/device-tree.
serial=""
for f in \
/sys/class/dmi/id/product_serial \
/sys/class/dmi/id/board_serial \
/sys/firmware/devicetree/base/serial-number \
/proc/device-tree/serial-number
do
if [ -r "$f" ]; then
serial="$(tr -d '\000' < "$f" | tr -d '\n\r')"
[ -n "$serial" ] && break
fi
done
# Tailscale node names are RFC 1035 labels: lowercase, [a-z0-9-], <=63.
serial="$(printf '%s' "$serial" \
| tr '[:upper:]' '[:lower:]' \
| tr -c 'a-z0-9-' '-' \
| sed -e 's/-\{1,\}/-/g' -e 's/^-//' -e 's/-$//' \
| cut -c1-63)"
[ -n "$serial" ] || serial="radiantwave-unknown"
# Already joined? Bail out early. This is what makes the periodic
# re-invocation from the timer below cheap and safe to run forever.
# The JSON is pretty-printed with a space, so allow optional whitespace.
state="$(tailscale status --json 2>/dev/null \
| sed -n 's/.*"BackendState": *"\([A-Za-z]*\)".*/\1/p')"
if [ "$state" = "Running" ]; then
echo "radiantwave-tailscale-auth: already joined as '$serial'"
exit 0
fi
echo "radiantwave-tailscale-auth: joining as '$serial' (state: ''${state:-unknown})"
for _ in $(seq 1 30); do
if tailscale up \
--auth-key=${pkgs.lib.escapeShellArg headscaleAuthKey} \
--login-server=https://hs.radiantwavetech.com \
--hostname="$serial" \
--accept-dns=false
then
echo "radiantwave-tailscale-auth: connected"
exit 0
fi
sleep 2
done
# Best-effort by design: do NOT exit non-zero here.
#
# This unit is part of the new generation that switch-to-configuration
# starts, and *any* failing unit among those aborts the switch with
# status 4/NOPERMISSION. While the preauth key was expired that is exactly
# what happened: this unit failed, which failed `switch-to-configuration`,
# which failed nixos-upgrade.service -- so the tailscale key silently
# stopped *all* system upgrades on the fleet every 10 minutes.
#
# The retry timer below is what actually recovers the join, so a failed
# attempt here must never take down the system switch with it.
echo "radiantwave-tailscale-auth: could not join tailnet after retries;" >&2
echo "radiantwave-tailscale-auth: leaving the join to the retry timer" >&2
exit 0
'';
};
# The join only needs to happen once per node, but it fails for reasons
# outside our control: the network may not be usable yet at boot, tailscaled
# may be restarted later, or the preauth key may be rotated out from under
# the fleet. A timer re-runs the (idempotent) join so a kiosk recovers on its
# own instead of needing a manual `systemctl restart`.
#
# This replaces the old behavior where the one-shot unit failed once at boot
# and stayed failed forever (Restart=no, NRestarts=0).
systemd.timers.radiantwave-tailscale-auth = {
description = "Periodically ensure the kiosk is joined to the tailnet";
wantedBy = [ "timers.target" ];
timerConfig = {
# The join is attempted at boot via multi-user.target above; this is the
# first retry in case that one lost the race with the network.
OnBootSec = "5min";
# Measured from when the unit last went inactive, so a slow join doesn't
# immediately retrigger.
OnUnitInactiveSec = "10min";
Unit = "radiantwave-tailscale-auth.service";
};
};
system.autoUpgrade = { system.autoUpgrade = {
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";
}; };
system.activationScripts.cleanRadiantFiles = ''
rm -rf /home/kiosk/.local/radiantwave/audio
'';
# Virtual machine declaration # Virtual machine declaration
virtualisation.vmVariant = { virtualisation.vmVariant = {
boot.loader.systemd-boot.enable = pkgs.lib.mkForce false; boot.loader.systemd-boot.enable = pkgs.lib.mkForce false;
@@ -98,6 +254,19 @@ 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 = [
"https://attic.radiantwavetech.com/radiantwave"
"https://attic.radiantwavetech.com/radiantwave-dev"
];
nix.settings.extra-trusted-public-keys = [
"radiantwave:QADrjpBWJssTZ3KmiZtcWEf4uAm66oOlHmtQI3jkrgA="
"radiantwave-dev:B8FhLu3MQ9JfeEIHIKNrKxnP+3akzzkqMbTZR4hRfj0="
];
system.stateVersion = "24.05"; system.stateVersion = "24.05";
############################################################ ############################################################
@@ -110,7 +279,6 @@ in
uid = 1000; uid = 1000;
extraGroups = ["audio" "video" "input" "networkmanager" "wheel" "seat"]; extraGroups = ["audio" "video" "input" "networkmanager" "wheel" "seat"];
openssh.authorizedKeys.keys = myKey; openssh.authorizedKeys.keys = myKey;
password = "Fuckp@ssw0rds";
}; };
services.getty.autologinUser = "kiosk"; services.getty.autologinUser = "kiosk";
@@ -142,7 +310,7 @@ in
# Release the splash screen right before the compositor takes over the display # Release the splash screen right before the compositor takes over the display
sudo -n plymouth quit --wait || true sudo -n plymouth quit --wait || true
exec dbus-run-session cage -- /run/current-system/sw/bin/radiantwave -d -s > /tmp/kiosk.log 2>&1 exec dbus-run-session cage -- ${launchBin} -d -s > /tmp/kiosk.log 2>&1
fi fi
''; '';
@@ -238,32 +406,4 @@ in
echo "Backup completed at $(date)" > "$BACKUP_DIR/.backup-timestamp" echo "Backup completed at $(date)" > "$BACKUP_DIR/.backup-timestamp"
''; '';
}; };
}
# --- Health check service: runs after nixos-upgrade ---
systemd.services.radiantwave-healthcheck = {
description = "Verify RadiantWave is running after upgrade, rollback if not";
after = [ "nixos-upgrade.service" ];
wantedBy = [ "nixos-upgrade.service" ];
serviceConfig = {
Type = "oneshot";
# Give cage time to start radiantwave after the switch
ExecStartPre = "${pkgs.coreutils}/bin/sleep 90";
};
script = ''
# The installed `radiantwave` is a makeWrapper script that execs the real
# binary (`.radiantwave-unwrapped`), so the process comm is not exactly
# `radiantwave`; match the exec'd binary's command line instead.
if ${pkgs.procps}/bin/pgrep -f '\.radiantwave-unwrapped' > /dev/null; then
echo "RadiantWave is running, upgrade successful"
exit 0
fi
echo "RadiantWave not found after upgrade, rolling back..."
# Roll back to previous NixOS generation
/run/current-system/sw/bin/nixos-rebuild switch --rollback
echo "Rollback completed at $(date)"
'';
};
}
-133
View File
@@ -1,133 +0,0 @@
{
pkgs,
inputs,
self,
...
}:
let
targetSystem =
self.nixosConfigurations.radiantwavekiosk-dev.config.system.build.toplevel;
diskoScript =
self.nixosConfigurations.radiantwavekiosk-dev.config.system.build.diskoScript;
in
{
############################################################
# System configuration
############################################################
nix.settings = {
experimental-features = [
"nix-command"
"flakes"
];
substituters = [
"https://cache.nixos.org"
];
flake-registry = "";
connect-timeout = 0;
};
nix.registry.nixpkgs = pkgs.lib.mkForce {
to = {
type = "path";
path = "${inputs.nixpkgs}";
};
};
nix.extraOptions = ''
netrc-file = /etc/nix/netrc
'';
environment.variables.KIOSK_FLAKE = "${self}";
environment.etc."nix/netrc" = {
text = ''
password eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIwODgzODk2ODEsIm5iZiI6MTc3MjgxMzY4MSwic3ViIjoia2lvc2siLCJodHRwczovL2p3dC5hdHRpYy5ycy92MSI6eyJjYWNoZXMiOnsia2lvc2siOnsiciI6MX19fX0.N6Ktrmmu1LSy2PP8uNF25VHRP1dxPZJ4M0MR1kUpVi0
'';
mode = "0400";
};
networking.networkmanager.enable = true;
############################################################
# User configuration
############################################################
users.users.root.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL4qWNZnYU5JaqR9c2qh6oFZktzVNBOx42PfDaGofCL6 cnclack@pm.me macbook"
];
############################################################
# Service configuration
############################################################
services.openssh.enable = true;
systemd.services.NetworkManager-wait-online.enable = false;
systemd.services.zero-touch-install = {
description = "Zero-Touch Kiosk Installation";
wantedBy = [ "multi-user.target" ];
# Make sure we own tty1 exclusively - getty@tty1 will otherwise
# try to spawn a login prompt on the same console.
conflicts = [ "getty@tty1.service" ];
after = [ "systemd-udev-settle.service" "getty@tty1.service" ];
path = with pkgs; [
nix
git
util-linux
nixos-install-tools
curl
networkmanager
mount
umount
disko
];
environment = {
NIX_PATH = "nixpkgs=${inputs.nixpkgs}";
KIOSK_FLAKE = "${self}";
DISKO_SCRIPT = "${diskoScript}";
};
serviceConfig = {
StandardInput = "tty";
StandardOutput = "tty";
StandardError = "tty";
TTYPath = "/dev/tty1";
TTYReset = true;
TTYVHangup = true;
TTYVTDisallocate = true;
};
script = ''
set -euo pipefail
echo "Running disko script..."
echo "Script: $DISKO_SCRIPT"
${diskoScript}
echo "Installing RadiantWave"
echo "Closure Path: ${targetSystem}"
nixos-install \
--system "${targetSystem}" \
--no-root-passwd \
--no-channel-copy
echo "============================================"
echo " Installation complete!"
echo " Please shut down and remove the flash drive before restarting"
echo "============================================"
sleep 10000000
'';
};
}
+74
View File
@@ -0,0 +1,74 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.kioskEdid;
overrideModule = types.submodule {
options = {
connector = mkOption {
type = types.str;
example = "HDMI-A-1";
description = ''
DRM connector name to apply the EDID override to.
Check `ls /sys/class/drm/` on target hardware to confirm the
exact name (e.g. HDMI-A-1, HDMI-A-2).
'';
};
edidFile = mkOption {
type = types.path;
description = ''
Path to the binary EDID file to force for this connector.
'';
};
forceConnected = mkOption {
type = types.bool;
default = true;
description = ''
Also force this connector to be treated as connected
(video=<connector>:e), in case the real link reports
disconnected/garbled state through a splitter or wireless kit.
'';
};
};
};
in
{
options.services.kioskEdid = {
enable = mkEnableOption "forcing custom EDIDs on one or more display connectors";
overrides = mkOption {
type = types.listOf overrideModule;
default = [ ];
description = ''
List of per-connector EDID overrides to apply. Each entry gets its
own firmware file and its own drm.edid_firmware kernel param.
'';
example = literalExpression ''
[
{ connector = "HDMI-A-1"; edidFile = ./android-4k30-edid.bin; }
{ connector = "HDMI-A-2"; edidFile = ./android-4k30-edid.bin; }
]
'';
};
};
config = mkIf cfg.enable {
hardware.firmware = map
(o: pkgs.runCommand "kiosk-edid-firmware-${o.connector}" { } ''
mkdir -p $out/lib/firmware/edid
cp ${o.edidFile} $out/lib/firmware/edid/${o.connector}.bin
'')
cfg.overrides;
boot.kernelParams = flatten (map
(o:
[ "drm.edid_firmware=${o.connector}:edid/${o.connector}.bin" ]
++ optional o.forceConnected "video=${o.connector}:e"
)
cfg.overrides);
};
}
Generated
+1 -141
View File
@@ -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": 1776877367,
"narHash": "sha256-EHq1/OX139R1RvBzOJ0aMRT3xnWyqtHBRUBuO1gFzjI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0726a0ecb6d4e08f6adced58726b95db924cef57",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1776877367,
"narHash": "sha256-EHq1/OX139R1RvBzOJ0aMRT3xnWyqtHBRUBuO1gFzjI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0726a0ecb6d4e08f6adced58726b95db924cef57",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"radiantwave": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1786170011,
"narHash": "sha256-Vx/3kwIQFRYDZQtVdcdGgJPnNa2Y44HRYZyJAeug6OI=",
"ref": "main",
"rev": "312f5e2a90e03f32ce61d6db54f46b9adac60933",
"revCount": 250,
"type": "git",
"url": "ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/radiantwave-legacy.git"
},
"original": {
"ref": "main",
"type": "git",
"url": "ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/radiantwave-legacy.git"
}
},
"radiantwave-legacy": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_4"
},
"locked": {
"lastModified": 1786170011,
"narHash": "sha256-Vx/3kwIQFRYDZQtVdcdGgJPnNa2Y44HRYZyJAeug6OI=",
"ref": "main",
"rev": "312f5e2a90e03f32ce61d6db54f46b9adac60933",
"revCount": 250,
"type": "git",
"url": "ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/radiantwave-legacy.git"
},
"original": {
"ref": "main",
"type": "git",
"url": "ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/radiantwave-legacy.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-legacy": "radiantwave-legacy"
}
},
"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"
} }
} }
}, },
+227 -174
View File
@@ -4,9 +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=main"; # This is the NEW RadiantWave
radiantwave.url = "git+ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/radiantwave-legacy.git?ref=main";
radiantwave-legacy.url = "git+ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/radiantwave-legacy.git?ref=main";
nixos-raspberrypi.url = "github:nvmd/nixos-raspberrypi/main"; nixos-raspberrypi.url = "github:nvmd/nixos-raspberrypi/main";
}; };
@@ -16,8 +13,6 @@
nixpkgs, nixpkgs,
disko, disko,
nixos-facter-modules, nixos-facter-modules,
radiantwave,
radiantwave-legacy,
nixos-raspberrypi, nixos-raspberrypi,
... ...
}@inputs: }@inputs:
@@ -29,61 +24,155 @@
# at build time (librsvg), so the theme is hermetic and resolution- # at build time (librsvg), so the theme is hermetic and resolution-
# independent. The .plymouth file's /usr paths are rewritten to $out # independent. The .plymouth file's /usr paths are rewritten to $out
# (the NixOS module's initrd fixup rewrites store paths again). # (the NixOS module's initrd fixup rewrites store paths again).
radiantwavePlymouth = pkgs.stdenv.mkDerivation { mkRadiantwavePlymouth = targetPkgs:
pname = "radiantwave-plymouth"; targetPkgs.stdenv.mkDerivation {
version = "1.0.0"; pname = "radiantwave-plymouth";
version = "1.0.0";
nativeBuildInputs = [ pkgs.librsvg ]; nativeBuildInputs = [ targetPkgs.librsvg ];
src = ./assets/RWT-Circular-Color-ForOnDark.svg; src = ./assets/RWT-Circular-Color-ForOnDark.svg;
dontUnpack = true; dontUnpack = true;
buildPhase = '' buildPhase = ''
mkdir -p $out/share/plymouth/themes/radiantwave mkdir -p $out/share/plymouth/themes/radiantwave
rsvg-convert \ rsvg-convert \
--keep-aspect-ratio \ --keep-aspect-ratio \
--background-color=#111318 \ --background-color=#111318 \
--width=3840 \ --width=1280 \
--height=2160 \ --height=720 \
--output=$out/share/plymouth/themes/radiantwave/splash.png \ --output=$out/share/plymouth/themes/radiantwave/splash.png \
$src $src
sed 's,/usr/,'"$out"'/,g' ${./plymouth-theme/radiantwave.plymouth} \ sed 's,/usr/,'"$out"'/,g' ${./plymouth-theme/radiantwave.plymouth} \
> $out/share/plymouth/themes/radiantwave/radiantwave.plymouth > $out/share/plymouth/themes/radiantwave/radiantwave.plymouth
cp ${./plymouth-theme/radiantwave.script} \ cp ${./plymouth-theme/radiantwave.script} \
$out/share/plymouth/themes/radiantwave/radiantwave.script $out/share/plymouth/themes/radiantwave/radiantwave.script
''; '';
};
revision = self.rev or self.dirtyRev or null;
# DEV/PROD CHANNEL SELECTOR — the single block that differs between the
# `main` and `dev` branches. Everything downstream (the four configs, the
# ISOs, the updater module) is identical on both branches and pulls from
# this channel, so a `dev`-to-`main` merge carries your changes with it
# automatically. There are no more `-dev` sibling configs to copy.
#
# main: atticRepo = "radiantwave", gitRef = ""
# dev: atticRepo = "radiantwave-dev", gitRef = "?ref=dev"
#
# 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 = {
atticRepo = "radiantwave-dev"; # dev
gitRef = "?ref=dev"; # dev
}; };
mkKioskSystem = { system, radiantwave, upgradeFlakeRef, extraModules ? [], diskModule, extraSpecialArgs ? {}}: # Exposed so CI (see .gitea/workflows/build.yml) and humans can verify
# which channel this checkout resolves to. On `main` this is
# "radiantwave"; on `dev` it is "radiantwave-dev".
channelName = channel.atticRepo;
# Channel metadata shared by the configs and the CI preflight.
channelAtticUrl = "https://attic.radiantwavetech.com/${channel.atticRepo}";
channelManifestDir = "https://files.radiantwavetech.com/${channel.atticRepo}";
# The baked RadiantWave app is whatever the channel's published manifest
# currently announces. RadiantWave CI builds the app, pushes the closure
# to the channel's Attic cache, and writes {version, storePath} to the
# 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. Used for the
# updater's runtime baseline pointer (a plain string; the closure itself
# is pulled in via publishedApp below).
publishedPath = manifestFile: (manifestFor manifestFile).storePath;
# The published closure as a real store-path dependency.
# builtins.storePath makes the derivation depend on this exact path and
# substitute it from the channel's Attic cache (configured as a trusted
# substituter), so the app is never compiled from source. The closure
# itself is not fetched by this repository; RadiantWave CI publishes it.
publishedApp = manifestFile: builtins.storePath (publishedPath manifestFile);
# Live app-update side channel. The manifest is served by the
# fileserver; closures are fetched (never built) from the channel's
# Attic cache. `repo` selects the channel — "radiantwave" (main) or
# "radiantwave-dev" (dev) — and `manifestFile` selects the per-arch
# manifest (default "manifest.json"; Raspberry Pi uses
# "aarch64-manifest.json"). All variants follow this same update
# procedure; only the channel/arch differ.
radiantwaveUpdaterModule = { repo, baseline, manifestFile ? "manifest.json" }: {
services.radiantwaveUpdater = {
enable = true;
baselinePath = baseline;
manifestUrl = "https://files.radiantwavetech.com/${repo}/${manifestFile}";
atticUrl = "https://attic.radiantwavetech.com/${repo}";
};
};
mkKioskSystem = {
system,
upgradeFlakeRef,
hostName,
diskModule,
extraModules ? [],
}:
nixpkgs.lib.nixosSystem { nixpkgs.lib.nixosSystem {
inherit system; inherit system;
specialArgs = { specialArgs = {
inherit radiantwave;
inherit upgradeFlakeRef; inherit upgradeFlakeRef;
inherit radiantwavePlymouth; radiantwaveApp = publishedApp "manifest.json";
} // extraSpecialArgs; radiantwavePlymouth = mkRadiantwavePlymouth;
modules = [
{ nixpkgs.config.allowUnfree = true; }
./configuration.nix
disko.nixosModules.disko
diskModule
nixos-facter-modules.nixosModules.facter
{ facter.reportPath = ./facter.json; }
radiantwave.nixosModules.default
] ++ extraModules;
};
mkRpiKioskSystem = { radiantwave, upgradeFlakeRef, extraModules ? [] }:
nixos-raspberrypi.lib.nixosSystem {
specialArgs = {
inherit radiantwave upgradeFlakeRef;
inherit nixos-raspberrypi;
inherit radiantwavePlymouth;
}; };
modules = [ modules = [
{ nixpkgs.config.allowUnfree = true; }
{
networking.hostName = hostName;
system.configurationRevision = revision;
}
./configuration.nix
./radiantwave-updater.nix
disko.nixosModules.disko
diskModule
nixos-facter-modules.nixosModules.facter
# Hardware facter report. This is a NUC 14 report currently
# applied to every x86 variant (including commercial SATA
# hardware). To use per-model reports: generate them on each
# hardware target (procedure in AGENTS.md), commit them as
# facter-<model>.json, and select per variant here, e.g.:
# { facter.reportPath = ./facter-commercial.json; }
{ facter.reportPath = ./facter.json; }
] ++ extraModules;
};
mkRpiKioskSystem = {
upgradeFlakeRef,
hostName,
extraModules ? [],
}:
nixos-raspberrypi.lib.nixosSystem {
specialArgs = {
inherit upgradeFlakeRef;
inherit nixos-raspberrypi;
radiantwaveApp = publishedApp "aarch64-manifest.json";
radiantwavePlymouth = mkRadiantwavePlymouth;
};
modules = [
{ nixpkgs.config.allowUnfree = true; }
({ ... }: { ({ ... }: {
imports = with nixos-raspberrypi.nixosModules; [ imports = with nixos-raspberrypi.nixosModules; [
sd-image sd-image
@@ -93,8 +182,11 @@
]; ];
}) })
./configuration.nix ./configuration.nix
radiantwave.nixosModules.default ./radiantwave-updater.nix
{ {
networking.hostName = hostName;
system.configurationRevision = revision;
boot.loader.systemd-boot.enable = nixpkgs.lib.mkForce false; boot.loader.systemd-boot.enable = nixpkgs.lib.mkForce false;
boot.loader.efi.canTouchEfiVariables = nixpkgs.lib.mkForce false; boot.loader.efi.canTouchEfiVariables = nixpkgs.lib.mkForce false;
boot.loader.raspberry-pi.bootloader = "kernel"; boot.loader.raspberry-pi.bootloader = "kernel";
@@ -108,155 +200,116 @@
} }
] ++ extraModules; ] ++ extraModules;
}; };
# Zero-touch USB installer image. `targetConfig` names the kiosk system
# to install: the closure comes from the packages output below and the
# partitioning script from the paired disk module, so the ISO always
# installs the matching system for its hardware.
mkIso = { targetConfig, diskModule }:
nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = {
inherit inputs;
inherit self;
};
modules = [
"${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix"
disko.nixosModules.disko
(import ./zero-touch-installer.nix { inherit targetConfig; })
diskModule
({ pkgs, ... }: {
environment.systemPackages = [
disko.packages.${system}.default
];
isoImage.storeContents = [
self.packages.x86_64-linux.${targetConfig}
];
environment.etc."disko-config.nix".source = diskModule;
})
];
};
in in
{ {
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-dev = self.nixosConfigurations.radiantwavekiosk-dev.config.system.build.toplevel;
packages.x86_64-linux.radiantwavekiosk-commercial = self.nixosConfigurations.radiantwavekiosk-commercial.config.system.build.toplevel; packages.x86_64-linux.radiantwavekiosk-commercial = 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;
# Kiosk packages.x86_64-linux.radiantwavekiosk-rpi = self.nixosConfigurations.radiantwavekiosk-rpi.config.system.build.toplevel;
# Kiosk systems — one per hardware target, no dev/prod siblings.
#
# Every config consumes the channel selected at the top of this file
# (see `channel`): the published manifest whose closure is baked as the
# app, the Attic repo/manifest it fetches live updates from, and the
# KioskConfig git ref it auto-upgrades from. On `main` the channel is
# prod; on `dev` it is the dev channel. Because there is exactly one
# 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 = radiantwave-legacy; hostName = "radiantwavekiosk";
diskModule = ./disk-config.nix; diskModule = ./disk-config.nix;
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git#radiantwavekiosk"; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk";
extraModules = []; extraModules = [
(radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = publishedPath "manifest.json"; })
];
}; };
# Kiosk-dev
radiantwavekiosk-dev = mkKioskSystem {
system = "x86_64-linux";
radiantwave = radiantwave;
diskModule = ./disk-config.nix;
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git?ref=dev#radiantwavekiosk-dev";
extraModules = [];
};
# Commercial
radiantwavekiosk-commercial = mkKioskSystem { radiantwavekiosk-commercial = mkKioskSystem {
system = "x86_64-linux"; system = "x86_64-linux";
radiantwave = radiantwave-legacy; hostName = "radiantwavekiosk-commercial";
diskModule = ./commercial-disk-config.nix; diskModule = ./commercial-disk-config.nix;
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git#radiantwavekiosk-commercial"; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-commercial";
extraModules = []; extraModules = [
(radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = publishedPath "manifest.json"; })
];
}; };
# Commercial-dev # Rotated screen for android-style displays.
radiantwavekiosk-commercial-dev = mkKioskSystem {
system = "x86_64-linux";
radiantwave = radiantwave;
diskModule = ./commercial-disk-config.nix;
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git?ref=dev#radiantwavekiosk-commercial";
extraModules = [];
};
# Kiosk - rotated screen for android displays
radiantwavekiosk-android = mkKioskSystem { radiantwavekiosk-android = mkKioskSystem {
system = "x86_64-linux"; system = "x86_64-linux";
radiantwave = radiantwave-legacy; hostName = "radiantwavekiosk-android";
diskModule = ./disk-config.nix; diskModule = ./disk-config.nix;
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git#radiantwavekiosk-android"; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-android";
extraModules = [./rotated_display.nix ]; extraModules = [
./rotated_display.nix
(radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = publishedPath "manifest.json"; })
];
}; };
# Raspberry pi 5 # Raspberry Pi 5.
radiantwavekiosk-rpi = mkRpiKioskSystem { radiantwavekiosk-rpi = mkRpiKioskSystem {
radiantwave = radiantwave-legacy; hostName = "radiantwavekiosk-rpi";
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git#radiantwavekiosk-rpi"; upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-rpi";
}; extraModules = [
# Raspberry pi 5 dev (radiantwaveUpdaterModule {
radiantwavekiosk-rpi-dev = mkRpiKioskSystem { repo = channel.atticRepo;
radiantwave = radiantwave; baseline = publishedPath "aarch64-manifest.json";
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git?ref=dev#radiantwavekiosk-rpi-dev"; manifestFile = "aarch64-manifest.json";
})
];
}; };
# === USB Images === # === USB Images ===
# Kiosk ISO # Each ISO installs exactly the named system closure using that
kiosk-iso = nixpkgs.lib.nixosSystem { # system's disk layout. NVMe layouts pair with ./disk-config.nix,
system = "x86_64-linux"; # SATA with ./commercial-disk-config.nix — don't cross them.
specialArgs = { # The same names build dev-flavored images when the flake is checked
inherit inputs; # out on the `dev` branch (the channel block above selects the dev
inherit self; # manifest, attic repo, and upgrade ref), so there are no separate
}; # -dev ISOs.
modules = [ kiosk-iso = mkIso {
"${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix" targetConfig = "radiantwavekiosk";
disko.nixosModules.disko diskModule = ./disk-config.nix;
./iso-config.nix
./disk-config.nix
({ pkgs, ... }: {
environment.systemPackages = [
disko.packages.${system}.default
];
isoImage.storeContents = [
self.packages.x86_64-linux.radiantwavekiosk
];
environment.etc."disko-config.nix".source = ./disk-config.nix;
})
];
}; };
# Commercial iso commercial-iso = mkIso {
commercial-iso = nixpkgs.lib.nixosSystem { targetConfig = "radiantwavekiosk-commercial";
system = "x86_64-linux"; diskModule = ./commercial-disk-config.nix;
specialArgs = {
inherit inputs;
inherit self;
};
modules = [
"${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix"
disko.nixosModules.disko
./commercial-iso-config.nix
./commercial-disk-config.nix
({ pkgs, ... }: {
environment.systemPackages = [
disko.packages.${system}.default
];
isoImage.storeContents = [
self.packages.x86_64-linux.radiantwavekiosk-commercial
];
environment.etc."disko-config.nix".source = ./commercial-disk-config.nix;
})
];
}; };
# kiosk rotated screen ISO android-iso = mkIso {
android-iso = nixpkgs.lib.nixosSystem { targetConfig = "radiantwavekiosk-android";
system = "x86_64-linux"; diskModule = ./disk-config.nix;
specialArgs = {
inherit inputs;
inherit self;
};
modules = [
"${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix"
disko.nixosModules.disko
./iso-config.nix
./disk-config.nix
({ pkgs, ... }: {
environment.systemPackages = [
disko.packages.${system}.default
];
isoImage.storeContents = [
self.packages.x86_64-linux.radiantwavekiosk-android
];
environment.etc."disko-config.nix".source = ./disk-config.nix;
})
];
};
# Kiosk iso development branch
dev-iso = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = {
inherit inputs;
inherit self;
};
modules = [
"${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix"
disko.nixosModules.disko
./dev-iso.nix
./disk-config.nix
({ pkgs, ... }: {
environment.systemPackages = [
disko.packages.${system}.default
];
isoImage.storeContents = [
self.packages.x86_64-linux.radiantwavekiosk-dev
];
environment.etc."disko-config.nix".source = ./disk-config.nix;
})
];
}; };
}; };
}; };
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""
Forces 2160x3840 portrait @ 30Hz as the ONLY Detailed Timing Descriptor
(DTD slot 1 / preferred timing) and strips the Video Data Block from the
CTA-861 extension so no other video mode is advertised in any way.
Usage:
python3 force_4k30_portrait_only.py donor.bin fixed.bin
"""
import sys
def build_dtd(pixel_clock_khz, h_active, h_blank, h_sync_offset, h_sync_width,
v_active, v_blank, v_sync_offset, v_sync_width,
h_size_mm=0, v_size_mm=0, h_border=0, v_border=0,
interlaced=False, hsync_pos=True, vsync_pos=True):
pc = round(pixel_clock_khz / 10) # EDID stores pixel clock in 10kHz units
dtd = bytearray(18)
dtd[0] = pc & 0xFF
dtd[1] = (pc >> 8) & 0xFF
dtd[2] = h_active & 0xFF
dtd[3] = h_blank & 0xFF
dtd[4] = ((h_active >> 8) << 4) | (h_blank >> 8)
dtd[5] = v_active & 0xFF
dtd[6] = v_blank & 0xFF
dtd[7] = ((v_active >> 8) << 4) | (v_blank >> 8)
dtd[8] = h_sync_offset & 0xFF
dtd[9] = h_sync_width & 0xFF
dtd[10] = ((v_sync_offset & 0xF) << 4) | (v_sync_width & 0xF)
dtd[11] = (((h_sync_offset >> 8) & 0x3) << 6) | \
(((h_sync_width >> 8) & 0x3) << 4) | \
(((v_sync_offset >> 4) & 0x3) << 2) | \
((v_sync_width >> 4) & 0x3)
dtd[12] = h_size_mm & 0xFF
dtd[13] = v_size_mm & 0xFF
dtd[14] = ((h_size_mm >> 8) << 4) | (v_size_mm >> 8)
dtd[15] = h_border
dtd[16] = v_border
flags = 0x18 # digital separate sync
if interlaced:
flags |= 0x80
if hsync_pos:
flags |= 0x02
if vsync_pos:
flags |= 0x04
dtd[17] = flags
return dtd
TAG_VIDEO = 2
def strip_video_data_block(ext):
"""Remove all Video Data Blocks from a CTA-861 extension block."""
dtd_offset = ext[2]
pos = 4 # skip 4-byte CTA header
blocks = []
while pos < dtd_offset:
tag = (ext[pos] >> 5) & 0x07
length = ext[pos] & 0x1F
block_data = bytes(ext[pos + 1:pos + 1 + length])
blocks.append((tag, length, block_data))
pos += 1 + length
blocks = [(t, l, d) for t, l, d in blocks if t != TAG_VIDEO]
new_db = bytearray()
for tag, length, data in blocks:
new_db.append((tag << 5) | length)
new_db.extend(data)
new_ext = bytearray(128)
new_ext[0] = ext[0] # CTA tag
new_ext[1] = ext[1] # revision
new_ext[2] = 4 + len(new_db) # new DTD offset
new_ext[3] = ext[3] # flags (audio, YCbCr, etc.)
new_ext[4:4 + len(new_db)] = new_db
new_ext[0x7F] = 0
new_ext[0x7F] = (-sum(new_ext)) % 256
return new_ext
if __name__ == "__main__":
infile, outfile = sys.argv[1], sys.argv[2]
data = bytearray(open(infile, "rb").read())
# --- 2160x3840 portrait @ 30Hz ---
# H blanking uses CVT-RB-style fixed values (front 48 / sync 32 / back 80)
# V blanking computed to satisfy CVT-RB min vertical blank time (~460us)
# Pixel clock ~= htotal * vtotal * refresh
dtd_4k30 = build_dtd(
pixel_clock_khz=271020,
h_active=2160, h_blank=160, h_sync_offset=48, h_sync_width=32,
v_active=3840, v_blank=54, v_sync_offset=3, v_sync_width=5,
h_size_mm=685, v_size_mm=1218, # swapped panel physical size for portrait
)
# Slot 1 = bytes 54-71 (was 1920x1080@60, the preferred timing)
data[54:72] = dtd_4k30
# Slot 2 = bytes 72-89 (was 1280x720@60) - blank out to a dummy
# descriptor so no second timing is advertised at all.
# Dummy Descriptor format: 00 00 00 10 00, then 13 zero-padding bytes.
dummy = bytearray(18)
dummy[3] = 0x10 # tag byte for "Dummy Descriptor"
data[72:90] = dummy
# Unknown Display Descriptor slot and Product Name descriptor are
# left as-is.
# Recompute base block checksum (byte 127)
data[127] = (256 - sum(data[0:127]) % 256) % 256
# Strip Video Data Block from CTA-861 extension (bytes 128-255)
data[128:256] = strip_video_data_block(data[128:256])
open(outfile, "wb").write(data)
print(f"Wrote {outfile}")
print("Run `edid-decode` on the output to verify before flashing.")
+15
View File
@@ -0,0 +1,15 @@
KioskConfig Commit,Date (CDT),URL Change,Pinned App Revision,App Revision Date,App Version on Home Screen
c06a3f7,2026-07-27 14:13,"codeberg.org/RhemaWave-Technologies/RhemaWave.git",9f8212cf550b,2026-06-27,2.3.5.8
5a6031a,2026-07-27 14:23,Codeberg → gitea.radiantwavetech.com/.../RhemaWave.git,9f8212cf550b,2026-06-27,2.3.5.8
7f2f19f,2026-07-27 14:24,RhemaWave.git → radiantwave.git,9f8212cf550b,2026-06-27,2.3.5.8
4af16d9,2026-07-28 10:43,Add ?ref=main,6ad6516a7461,2026-07-02,Coming Soon
cbb3e05,2026-08-07 17:13,Dev → radiantwave-cpp.git?ref=dev,b54dbca2d5e7,2026-08-03,07292026_1618
37d18d3,2026-08-07 17:18,Fix: dropped git+ prefix on dev URL,b54dbca2d5e7,2026-08-03,07292026_1618
50a1f3e,2026-08-07 17:19,Fix: restored git+ prefix on dev URL,b54dbca2d5e7,2026-08-03,07292026_1618
e3120f2,2026-08-08 00:02,Dev back to radiantwave.git?ref=dev,b54dbca2d5e7,2026-08-03,07292026_1618
50176d8,2026-08-17 10:51,New radiantwave-legacy input added,312f5e2a90e0,2026-08-08,08082026_0120
0cb1337,2026-08-17 10:57,Auth token added to legacy URL,312f5e2a90e0,2026-08-08,08082026_0120
fb2f861,2026-08-17 12:48,Prod pointed to radiantwave-legacy.git,312f5e2a90e0,2026-08-08,08082026_0120
027d1a6,2026-08-17 12:50,Token rotated (fb62d6c → 794ef1d),312f5e2a90e0,2026-08-08,08082026_0120
14463ad,2026-08-17 12:52,Token removed (repo made public),312f5e2a90e0,2026-08-08,08082026_0120
e09758a,2026-08-17 12:54,HTTPS → SSH (:2222),312f5e2a90e0,2026-08-08,08082026_0120
1 KioskConfig Commit Date (CDT) URL Change Pinned App Revision App Revision Date App Version on Home Screen
2 c06a3f7 2026-07-27 14:13 codeberg.org/RhemaWave-Technologies/RhemaWave.git 9f8212cf550b 2026-06-27 2.3.5.8
3 5a6031a 2026-07-27 14:23 Codeberg → gitea.radiantwavetech.com/.../RhemaWave.git 9f8212cf550b 2026-06-27 2.3.5.8
4 7f2f19f 2026-07-27 14:24 RhemaWave.git → radiantwave.git 9f8212cf550b 2026-06-27 2.3.5.8
5 4af16d9 2026-07-28 10:43 Add ?ref=main 6ad6516a7461 2026-07-02 Coming Soon
6 cbb3e05 2026-08-07 17:13 Dev → radiantwave-cpp.git?ref=dev b54dbca2d5e7 2026-08-03 07292026_1618
7 37d18d3 2026-08-07 17:18 Fix: dropped git+ prefix on dev URL b54dbca2d5e7 2026-08-03 07292026_1618
8 50a1f3e 2026-08-07 17:19 Fix: restored git+ prefix on dev URL b54dbca2d5e7 2026-08-03 07292026_1618
9 e3120f2 2026-08-08 00:02 Dev back to radiantwave.git?ref=dev b54dbca2d5e7 2026-08-03 07292026_1618
10 50176d8 2026-08-17 10:51 New radiantwave-legacy input added 312f5e2a90e0 2026-08-08 08082026_0120
11 0cb1337 2026-08-17 10:57 Auth token added to legacy URL 312f5e2a90e0 2026-08-08 08082026_0120
12 fb2f861 2026-08-17 12:48 Prod pointed to radiantwave-legacy.git 312f5e2a90e0 2026-08-08 08082026_0120
13 027d1a6 2026-08-17 12:50 Token rotated (fb62d6c → 794ef1d) 312f5e2a90e0 2026-08-08 08082026_0120
14 14463ad 2026-08-17 12:52 Token removed (repo made public) 312f5e2a90e0 2026-08-08 08082026_0120
15 e09758a 2026-08-17 12:54 HTTPS → SSH (:2222) 312f5e2a90e0 2026-08-08 08082026_0120
+98
View File
@@ -0,0 +1,98 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.radiantwaveUpdater;
in
{
options.services.radiantwaveUpdater = {
enable = lib.mkEnableOption "live RadiantWave app updates via a polled manifest";
manifestUrl = lib.mkOption {
type = lib.types.str;
description = "HTTP(S) URL of the JSON manifest announcing the current app store path.";
};
atticUrl = lib.mkOption {
type = lib.types.str;
description = "Attic cache URL to fetch the app closure from (fetch-only, never builds).";
};
baselinePath = lib.mkOption {
type = lib.types.str;
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 {
# Runtime pointer the login shell launches RadiantWave from. Seeded on
# first boot to the exact closure the channel manifest announced (the same
# path the updater would fetch), so no spurious first update is triggered.
systemd.tmpfiles.rules = [
"L /var/lib/radiantwave/current - - - - ${cfg.baselinePath}"
];
systemd.services.radiantwave-updater = {
description = "Poll the RadiantWave manifest and swap the live app pointer";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
serviceConfig = {
Type = "oneshot";
# NOTE: no RemainAfterExit here. Keeping the oneshot "active" after
# it finishes breaks the timer's OnUnitActiveSec re-arming, so the
# 2-min cadence stops after the first run (NEXT goes empty, Trigger
# becomes n/a). Let the service return to dead so the timer refires.
};
script = ''
set -u
NEW_PATH=$(${pkgs.curl}/bin/curl -fsSL "${cfg.manifestUrl}" \
| ${pkgs.jq}/bin/jq -er .storePath) || {
echo "radiantwave-updater: failed to fetch/parse manifest from ${cfg.manifestUrl}"
exit 0
}
CURRENT=$(${pkgs.coreutils}/bin/readlink /var/lib/radiantwave/current 2>/dev/null || true)
if [ -z "$NEW_PATH" ] || [ "$NEW_PATH" = "$CURRENT" ]; then
# Already current; make sure the running closure is GC-rooted.
${pkgs.coreutils}/bin/ln -sfn "$CURRENT" /var/lib/radiantwave/.current-root 2>/dev/null || true
echo "radiantwave-updater: already current (''${CURRENT})"
exit 0
fi
# Realize the closure via the variant's Attic cache plus cache.nixos.org.
# nixpkgs deps (e.g. sqlite) are signed by cache.nixos.org and Attic
# intentionally skips them to avoid re-uploading the world, so fetch
# them from cache.nixos.org and the whole closure resolves without ever
# building from source. Trusted public keys come from the system config
# (both Attic repos are trusted in configuration.nix). --add-root pins
# the fetched closure so GC can't collect it while the swap below makes
# it live.
echo "radiantwave-updater: acquiring ''${NEW_PATH}"
${pkgs.nix}/bin/nix-store -r "$NEW_PATH" \
--add-root /var/lib/radiantwave/.current-root \
--option extra-substituters "${cfg.atticUrl} https://cache.nixos.org" || {
echo "radiantwave-updater: nix copy failed for ''${NEW_PATH}"
exit 1
}
# Atomic swap: the app never observes a half-updated pointer.
${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
echo "radiantwave-updater: swapped ''${CURRENT} -> ''${NEW_PATH}"
loginctl terminate-user kiosk
'';
};
systemd.timers.radiantwave-updater = {
description = "Check for RadiantWave app updates";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "2min";
OnUnitActiveSec = "2min";
Persistent = true;
Unit = "radiantwave-updater.service";
};
};
};
}
+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
+130
View File
@@ -0,0 +1,130 @@
Date (CDT),KioskConfig Hash,RadiantWave Hash,App Version,Summary
2026-03-12 08:05:10 -0500,,403ec4db89b5,dev,Initial release: RadiantWave v1.0
2026-03-13 19:57:06 -0500,,ee2b3383a8da,dev,Fix WiFi password entry
2026-03-13 20:14:01 -0500,,c68757a63439,dev,Update version
2026-03-13 20:28:41 -0500,,91e5c04a3800,dev,Update version
2026-03-13 20:58:28 -0500,,5fef01107b03,dev,Attempt to fix wifi connectivity
2026-03-13 20:59:02 -0500,,a116e01146e9,dev,update version
2026-03-13 21:13:21 -0500,,5f254e94cd55,dev,Fix WiFi page update
2026-03-13 21:16:54 -0500,,f8fbfb721d3d,dev,Update version
2026-03-23 15:02:07 -0500,,17c088cc2fc2,dev,Update Database
2026-03-23 17:35:52 -0500,,8d2384139116,dev,Pull Proclamations & Categories from API and store them in the database
2026-03-24 11:19:50 -0500,,9b6bae9511ab,dev,List proclamations inside of categories
2026-03-25 11:27:16 -0500,,678d679b536d,dev,Fix bug loading 0 proclamations
2026-03-25 18:09:02 -0500,,465928915c2c,dev,Update version
2026-03-25 19:56:41 -0500,,c1c077dc698d,dev,Update asset locations and permissions
2026-03-25 20:33:58 -0500,,4aeaaf10b7d2,dev,Simplify asset handling
2026-03-29 23:41:16 -0500,,6e28b90ee860,dev,"Add wifi, license and email checks"
2026-03-30 00:08:52 -0500,,aea646f78868,dev,Set default volume to MAX
2026-03-30 00:10:21 -0500,,331a8255fd5d,v1.0.0,Set version to 1.0.0 statically
2026-03-30 00:43:01 -0500,,120772175d42,v1.0.0,Update Proclamation selection
2026-04-13 14:00:02 -0500,,105d8900d58d,v1.0.0,Replace audio devices with audio toggle
2026-04-13 14:09:26 -0500,,76fd6057778b,v1.0.0,Update changelog
2026-04-15 11:01:28 -0500,,b6bf0f1873b7,v1.0.0,Sync proclamations when entering proclamation list
2026-04-15 11:58:44 -0500,,10ba5df13261,v1.0.0,Force re-initialization of audio device when Play() is called
2026-04-15 13:56:53 -0500,,b94279be691a,v1.0.0,Update changelog
2026-04-17 11:43:59 -0500,,504095caa826,v1.0.0,Sync instead of download.
2026-04-23 16:44:09 -0500,,dd069e934508,1.0,WIP: Stagger proclamations
2026-04-24 11:06:50 -0500,,7f344ae5c806,1.0,Optimize proclamation loading
2026-05-06 10:16:19 -0500,,3cf3e4cf23f3,1.0,Fix database syncronization
2026-05-10 21:19:07 -0500,,87d484353fb0,1.0,Fixed WiFi password bug. Ready for testing and merge
2026-05-11 12:55:16 -0500,,1f9da7ee780a,1.0,Attempt to fix proclamation sync
2026-05-14 09:57:13 -0500,,3ea32e0eaa36,1.0,Add volume changing logic
2026-05-14 10:21:11 -0500,,5188c90350d8,1.0,Add working volume bar in audioDevices page
2026-05-26 14:12:27 -0500,,f7ef42c84ab5,1.0,Rename application to Radiant Wave
2026-05-31 12:08:19 -0500,,0ebc3adf4ae5,1.0,Proclamations change colors!
2026-05-31 12:15:10 -0500,,8268022d5128,1.0,Organize proclamations alphabetically
2026-06-03 11:21:28 -0500,,ddab8e446c6b,1.0.0-beta,Update version for visual preference during beta phase
2026-06-11 19:44:20 -0500,,4b7d2c13d5ce,1.0.0-beta,Rename audio devices to audio options
2026-06-18 08:02:21 -0500,,6b4867953756,1.0.0-beta,Add database logic
2026-06-18 11:29:45 -0500,,fe5543d50dea,1.0.0-beta,Audio downloads when necessary
2026-06-18 15:39:30 -0500,,a5f9780f410b,1.0.0-beta,Add download package
2026-06-18 15:49:05 -0500,,1a67b9780400,1.0.0-beta,un-mask wifi password
2026-06-18 15:49:29 -0500,,b11afe22f09e,1.0.0-beta,remove file downloads
2026-06-18 21:28:16 -0500,,7ecb90b63b61,1.0,Downloads are working and the goroutine is reporting updates appropriately.
2026-06-18 21:52:29 -0500,,2af1aae88580,1.0,Audio files download on application start. Home screen shows status message
2026-06-18 22:43:47 -0500,,ac86864c7286,1.0,Add downloading message to home screen.
2026-06-18 22:51:17 -0500,,1c814f4abb6b,1.0,Reset default volume to MAX
2026-06-19 00:13:25 -0500,,4cd24375cdac,1.0,Add audio features
2026-06-23 11:22:21 -0500,,30dd5ffdcb94,1.0,Create objects for tracking audio devices
2026-06-24 14:17:06 -0500,,0a9a33b87b90,1.0,Fix download URL mismatch
2026-06-24 14:50:13 -0500,,970b8ebf09af,1.0,Add verbose logging to download.DownloadAudioFiles
2026-06-24 14:57:42 -0500,,cc2887d2a06d,b018a79,Update version text and add more download logs
2026-06-24 18:29:59 -0500,,953d0d8bc93d,b018a79,Rewrite entire network package and update wifi page
2026-06-24 22:41:48 -0500,,a4eff17b77a1,b018a79,"fix(network): fix wifi page never scanning, never exiting, and UI freezes on connect"
2026-06-24 23:18:32 -0500,,57500308b00d,b018a79,Handle locked database cases
2026-06-24 23:20:57 -0500,,f8e75194e845,cd79a6f,Update version hash for visual verification
2026-06-26 18:07:12 -0500,,4dd78b5ade8a,9beeeb1,Update version number for visual validation
2026-06-27 00:37:07 -0500,,2fac3ab6a8aa,9beeeb1,Replace textInputSupported flag
2026-06-27 01:47:06 -0500,,ca18000c8ef4,2.3.5.8,Update version string for validation
2026-06-27 12:57:29 -0500,,18c57c9101bf,2.3.5.8,Remove calls to RefreshConfig
2026-06-27 13:59:22 -0500,,f00cb9efb6a0,2.3.5.8,Fix download progress and prevent re-downloading existing audio.
2026-06-27 14:25:25 -0500,,2c88b7ffc515,2.3.5.8,Add creation of audio directory and remove unused function in download package
2026-06-27 14:25:51 -0500,,0b43a982985b,2.3.5.8,Remove unused function in download package
2026-06-27 14:50:50 -0500,,8ec1cbbc480b,2.3.5.8,Log the app config in Debug
2026-06-27 15:09:51 -0500,,ba39ed85a943,2.3.5.8,Remove wal and shm deletion
2026-06-27 16:01:33 -0500,,7e598f191f72,2.3.5.8,Update Network package
2026-06-27 16:51:18 -0500,,b0947bd59447,2.3.5.8,Fix bugs in WiFi page
2026-06-28 01:56:35 -0500,,193ebbcb2714,2.3.5.8,Fix network connection timeouts and security detection
2026-06-28 15:11:35 -0500,,6ad8a19f2ad9,2.3.5.8,Increase startup volume to 1.0
2026-06-28 15:18:27 -0500,,c232d417ba31,Coming Soon,Update version string
2026-07-10 13:56:06 -0500,,2bcb36669fd2,Coming Soon,fix: restore SDL volume to MAX after device switch
2026-07-10 13:56:12 -0500,,9bc0fd6820bb,Coming Soon,fix: restore SDL volume to MAX after device switch
2026-07-10 14:00:48 -0500,,68b3680e32bc,Coming Soon,fix: make wayland/X11 buildInputs Linux-only for macOS dev shell
2026-07-10 14:18:47 -0500,,779bc77c5b45,Coming Soon,fix: use recursiveUpdate instead of // to preserve packages.default
2026-07-10 16:02:02 -0500,,313db8ccd18a,Coming Soon,fix: rewrite cmd_diagnose to use script upload instead of nested SSH quoting
2026-07-10 17:12:48 -0500,,fca635482901,Coming Soon,Create Docker build pipeline for MacOS
2026-07-11 09:01:05 -0500,,141613f571ed,Audio-Bug-Fix 0.1,Update version string to indicate audio-bug-fixes
2026-07-11 09:45:12 -0500,,9dc1ce77aefc,Audio-Bug-Fix 0.1,WIP on dev: 141613f Update version string to indicate audio-bug-fixes
2026-07-11 09:45:12 -0500,,bdbc525b6fb7,Audio-Bug-Fix 0.1,index on dev: 141613f Update version string to indicate audio-bug-fixes
2026-07-11 10:16:15 -0500,,72910e700605,Audio-Bug-Fix 0.1,fix: prevent Play() deadlock when device is not open
2026-07-11 11:39:58 -0500,,bb132ceeaf86,Audio-Bug-Fix 0.5,fix: resolve 4 critical audio mixer bugs (v0.5)
2026-07-11 13:43:05 -0500,,3f8144a708bb,Audio-Bug-Fix 0.6,Gate READY on download completion; move audio to ~/.radiantwave/audio
2026-07-11 15:27:01 -0500,,336f407499f4,Audio-Bug-Fix 0.6,Allow F2/F3 during DOWNLOADING_ASSETS state
2026-07-11 16:36:15 -0500,,a93e47589984,Audio-Bug-Fix 0.6,Fix double-close panic on DownloadDone channel
2026-07-11 16:43:16 -0500,,2ef826fe635b,Audio-Bug-Fix 0.6,Fix double-close panic on DownloadDone: skip re-navigation between HOME-mapped states
2026-07-11 17:10:59 -0500,,f29d1d1af51e,07112026-1710,Bump version to 07112026-1710; move completed items to release section
2026-07-12 18:15:41 -0500,,c98b84a3a8f3,07112026-1710,fix: active device highlight incorrect when using default device
2026-07-12 18:16:18 -0500,,e090870b4dfd,07112026-1710,fix: SwitchDevice no longer passes 'system default' to SDL
2026-07-12 18:17:22 -0500,,5854c57f6f01,07112026-1710,"changelog: update fixed issues list, remove known issues"
2026-07-27 13:14:37 -0500,,b1a5a670ab0b,07112026-1710,Add audio list
2026-07-27 14:13:58 -0500,c06a3f7f92de,,07112026-1710,init new repo
2026-07-27 14:23:31 -0500,5a6031a3fe5a,,07112026-1710,refactor: migrate from Codeberg to Gitea and rename rhemawave to radiantwave
2026-07-27 14:24:25 -0500,7f2f19fe41d9,,07112026-1710,fix: use correct lowercase repo name radiantwave.git
2026-07-27 14:29:03 -0500,26aca21e963d,,07112026-1710,Update flake.lock
2026-07-28 14:10:44 +0000,16545a77fd7e,,07112026-1710,chore: update radiantwave-dev to latest dev
2026-07-28 10:43:06 -0500,4af16d9df7c4,,07112026-1710,Specify main branch
2026-07-29 14:46:11 -0500,,6dda46288473,07292026_1445,Add audio selection
2026-07-29 14:57:32 -0500,,7a3b6a66b72f,07292026_1445,"Remove wpctl parsing tests (TestParseSinkLine, TestParseSinks)"
2026-07-29 14:58:15 -0500,,3480e7594a3b,07292026_1445,Replace wpctl volume control with SDL_mixer VolumeMusic
2026-07-29 16:19:15 -0500,,91711eef9328,07292026_1618,"Replace AudioFile with AudioTrack, fix download+orphan+MD5 sync, always play original audio fallback"
2026-07-29 19:40:04 -0500,09bc293af31e,,07292026_1618,Chore: Update RadiantWave
2026-08-02 00:56:34 -0500,ab6488028120,,07292026_1618,Organize flakes and cleanup things.
2026-08-03 01:54:27 -0500,,11b25227d789,07292026_1618,"Rename app to radiantwave: embed and provision assets, drop proclamations"
2026-08-03 01:54:52 -0500,ce6856b37a09,,07292026_1618,"Fix kiosk startup: radiantwave binary, drop stale rsync, fix GODEBUG typo"
2026-08-03 07:26:16 +0000,8ef25f756076,,07292026_1618,chore: update radiantwave-dev to latest
2026-08-03 02:40:51 -0500,,b54dbca2d5e7,07292026_1618,Fix asset provisioning to strip embedded share/radiantwave root
2026-08-06 10:13:40 -0500,0f5aa7670cf9,,07292026_1618,Chore: Update Radiantwave
2026-08-06 21:55:40 -0500,c3f65f78976f,,07292026_1618,Fix audio: pin system volume to 100% before RadiantWave launches
2026-08-07 00:38:25 -0500,2d4aca230d04,,07292026_1618,Update configuration and dev-iso
2026-08-07 17:13:59 -0500,cbb3e0585970,,07292026_1618,"Update to radiantave-cpp, enable plymouth boot image"
2026-08-07 17:18:32 -0500,37d18d3f474b,,07292026_1618,Update repository location
2026-08-07 17:19:04 -0500,50a1f3e99dfc,,07292026_1618,Update repository location
2026-08-07 17:39:26 -0500,06f02d49b910,,07292026_1618,update radiantwave to radiatnwave-dev
2026-08-07 17:47:04 -0500,2e3d88b57dce,,07292026_1618,Update Plymount dontUnpack = true;
2026-08-07 22:17:52 -0500,4580319f19f5,,07292026_1618,Update radiantwave-dev
2026-08-07 23:58:25 -0500,8abb028c8260,,07292026_1618,fix wpctl volume pinning
2026-08-08 00:02:09 -0500,e3120f27c20b,,07292026_1618,Pull back from radiantwave-cpp-dev to radiantwave-dev
2026-08-08 00:12:57 -0500,,2212cc9b7805,07292026_1618,Add audio device switching UI
2026-08-08 01:20:11 -0500,,312f5e2a90e0,08082026_0120,Rename rhemawave to radiantwave throughout
2026-08-08 06:25:05 +0000,1f3e194aa558,,08082026_0120,Chore: Update Radiantwave-dev
2026-08-08 01:44:03 -0500,240be2540f3f,,08082026_0120,Update Radiantwave
2026-08-17 10:44:36 -0500,06c2a7cd0a1e,,08082026_0120,Add plan to migrate to DRM
2026-08-17 10:51:05 -0500,50176d8fd5e4,,08082026_0120,"repoint production to radiantwave-legacy, dev to radiantwave (ref=main)"
2026-08-17 10:57:16 -0500,0cb1337f18fd,,08082026_0120,"add radiantwave-legacy input with auth token, update flake.lock"
2026-08-17 11:02:19 -0500,2abdfa2b9b28,,08082026_0120,revert upgradeFlakeRef URLs to KioskConfig to match renamed repo
2026-08-17 12:43:57 -0500,974a61051883,,08082026_0120,Update dev images to new radiantwave
2026-08-17 12:48:22 -0500,fb2f86125804,,08082026_0120,Revert back to Legacy Radiant Wave
2026-08-17 12:50:20 -0500,027d1a628feb,,08082026_0120,Update token
2026-08-17 12:52:11 -0500,14463ad99b08,,08082026_0120,Just make RadiantWave-Legacy public
2026-08-17 12:54:06 -0500,e09758a70ece,,08082026_0120,fix: switch radiantwave inputs to SSH for legacy Go repo
1 Date (CDT) KioskConfig Hash RadiantWave Hash App Version Summary
2 2026-03-12 08:05:10 -0500 403ec4db89b5 dev Initial release: RadiantWave v1.0
3 2026-03-13 19:57:06 -0500 ee2b3383a8da dev Fix WiFi password entry
4 2026-03-13 20:14:01 -0500 c68757a63439 dev Update version
5 2026-03-13 20:28:41 -0500 91e5c04a3800 dev Update version
6 2026-03-13 20:58:28 -0500 5fef01107b03 dev Attempt to fix wifi connectivity
7 2026-03-13 20:59:02 -0500 a116e01146e9 dev update version
8 2026-03-13 21:13:21 -0500 5f254e94cd55 dev Fix WiFi page update
9 2026-03-13 21:16:54 -0500 f8fbfb721d3d dev Update version
10 2026-03-23 15:02:07 -0500 17c088cc2fc2 dev Update Database
11 2026-03-23 17:35:52 -0500 8d2384139116 dev Pull Proclamations & Categories from API and store them in the database
12 2026-03-24 11:19:50 -0500 9b6bae9511ab dev List proclamations inside of categories
13 2026-03-25 11:27:16 -0500 678d679b536d dev Fix bug loading 0 proclamations
14 2026-03-25 18:09:02 -0500 465928915c2c dev Update version
15 2026-03-25 19:56:41 -0500 c1c077dc698d dev Update asset locations and permissions
16 2026-03-25 20:33:58 -0500 4aeaaf10b7d2 dev Simplify asset handling
17 2026-03-29 23:41:16 -0500 6e28b90ee860 dev Add wifi, license and email checks
18 2026-03-30 00:08:52 -0500 aea646f78868 dev Set default volume to MAX
19 2026-03-30 00:10:21 -0500 331a8255fd5d v1.0.0 Set version to 1.0.0 statically
20 2026-03-30 00:43:01 -0500 120772175d42 v1.0.0 Update Proclamation selection
21 2026-04-13 14:00:02 -0500 105d8900d58d v1.0.0 Replace audio devices with audio toggle
22 2026-04-13 14:09:26 -0500 76fd6057778b v1.0.0 Update changelog
23 2026-04-15 11:01:28 -0500 b6bf0f1873b7 v1.0.0 Sync proclamations when entering proclamation list
24 2026-04-15 11:58:44 -0500 10ba5df13261 v1.0.0 Force re-initialization of audio device when Play() is called
25 2026-04-15 13:56:53 -0500 b94279be691a v1.0.0 Update changelog
26 2026-04-17 11:43:59 -0500 504095caa826 v1.0.0 Sync instead of download.
27 2026-04-23 16:44:09 -0500 dd069e934508 1.0 WIP: Stagger proclamations
28 2026-04-24 11:06:50 -0500 7f344ae5c806 1.0 Optimize proclamation loading
29 2026-05-06 10:16:19 -0500 3cf3e4cf23f3 1.0 Fix database syncronization
30 2026-05-10 21:19:07 -0500 87d484353fb0 1.0 Fixed WiFi password bug. Ready for testing and merge
31 2026-05-11 12:55:16 -0500 1f9da7ee780a 1.0 Attempt to fix proclamation sync
32 2026-05-14 09:57:13 -0500 3ea32e0eaa36 1.0 Add volume changing logic
33 2026-05-14 10:21:11 -0500 5188c90350d8 1.0 Add working volume bar in audioDevices page
34 2026-05-26 14:12:27 -0500 f7ef42c84ab5 1.0 Rename application to Radiant Wave
35 2026-05-31 12:08:19 -0500 0ebc3adf4ae5 1.0 Proclamations change colors!
36 2026-05-31 12:15:10 -0500 8268022d5128 1.0 Organize proclamations alphabetically
37 2026-06-03 11:21:28 -0500 ddab8e446c6b 1.0.0-beta Update version for visual preference during beta phase
38 2026-06-11 19:44:20 -0500 4b7d2c13d5ce 1.0.0-beta Rename audio devices to audio options
39 2026-06-18 08:02:21 -0500 6b4867953756 1.0.0-beta Add database logic
40 2026-06-18 11:29:45 -0500 fe5543d50dea 1.0.0-beta Audio downloads when necessary
41 2026-06-18 15:39:30 -0500 a5f9780f410b 1.0.0-beta Add download package
42 2026-06-18 15:49:05 -0500 1a67b9780400 1.0.0-beta un-mask wifi password
43 2026-06-18 15:49:29 -0500 b11afe22f09e 1.0.0-beta remove file downloads
44 2026-06-18 21:28:16 -0500 7ecb90b63b61 1.0 Downloads are working and the goroutine is reporting updates appropriately.
45 2026-06-18 21:52:29 -0500 2af1aae88580 1.0 Audio files download on application start. Home screen shows status message
46 2026-06-18 22:43:47 -0500 ac86864c7286 1.0 Add downloading message to home screen.
47 2026-06-18 22:51:17 -0500 1c814f4abb6b 1.0 Reset default volume to MAX
48 2026-06-19 00:13:25 -0500 4cd24375cdac 1.0 Add audio features
49 2026-06-23 11:22:21 -0500 30dd5ffdcb94 1.0 Create objects for tracking audio devices
50 2026-06-24 14:17:06 -0500 0a9a33b87b90 1.0 Fix download URL mismatch
51 2026-06-24 14:50:13 -0500 970b8ebf09af 1.0 Add verbose logging to download.DownloadAudioFiles
52 2026-06-24 14:57:42 -0500 cc2887d2a06d b018a79 Update version text and add more download logs
53 2026-06-24 18:29:59 -0500 953d0d8bc93d b018a79 Rewrite entire network package and update wifi page
54 2026-06-24 22:41:48 -0500 a4eff17b77a1 b018a79 fix(network): fix wifi page never scanning, never exiting, and UI freezes on connect
55 2026-06-24 23:18:32 -0500 57500308b00d b018a79 Handle locked database cases
56 2026-06-24 23:20:57 -0500 f8e75194e845 cd79a6f Update version hash for visual verification
57 2026-06-26 18:07:12 -0500 4dd78b5ade8a 9beeeb1 Update version number for visual validation
58 2026-06-27 00:37:07 -0500 2fac3ab6a8aa 9beeeb1 Replace textInputSupported flag
59 2026-06-27 01:47:06 -0500 ca18000c8ef4 2.3.5.8 Update version string for validation
60 2026-06-27 12:57:29 -0500 18c57c9101bf 2.3.5.8 Remove calls to RefreshConfig
61 2026-06-27 13:59:22 -0500 f00cb9efb6a0 2.3.5.8 Fix download progress and prevent re-downloading existing audio.
62 2026-06-27 14:25:25 -0500 2c88b7ffc515 2.3.5.8 Add creation of audio directory and remove unused function in download package
63 2026-06-27 14:25:51 -0500 0b43a982985b 2.3.5.8 Remove unused function in download package
64 2026-06-27 14:50:50 -0500 8ec1cbbc480b 2.3.5.8 Log the app config in Debug
65 2026-06-27 15:09:51 -0500 ba39ed85a943 2.3.5.8 Remove wal and shm deletion
66 2026-06-27 16:01:33 -0500 7e598f191f72 2.3.5.8 Update Network package
67 2026-06-27 16:51:18 -0500 b0947bd59447 2.3.5.8 Fix bugs in WiFi page
68 2026-06-28 01:56:35 -0500 193ebbcb2714 2.3.5.8 Fix network connection timeouts and security detection
69 2026-06-28 15:11:35 -0500 6ad8a19f2ad9 2.3.5.8 Increase startup volume to 1.0
70 2026-06-28 15:18:27 -0500 c232d417ba31 Coming Soon Update version string
71 2026-07-10 13:56:06 -0500 2bcb36669fd2 Coming Soon fix: restore SDL volume to MAX after device switch
72 2026-07-10 13:56:12 -0500 9bc0fd6820bb Coming Soon fix: restore SDL volume to MAX after device switch
73 2026-07-10 14:00:48 -0500 68b3680e32bc Coming Soon fix: make wayland/X11 buildInputs Linux-only for macOS dev shell
74 2026-07-10 14:18:47 -0500 779bc77c5b45 Coming Soon fix: use recursiveUpdate instead of // to preserve packages.default
75 2026-07-10 16:02:02 -0500 313db8ccd18a Coming Soon fix: rewrite cmd_diagnose to use script upload instead of nested SSH quoting
76 2026-07-10 17:12:48 -0500 fca635482901 Coming Soon Create Docker build pipeline for MacOS
77 2026-07-11 09:01:05 -0500 141613f571ed Audio-Bug-Fix 0.1 Update version string to indicate audio-bug-fixes
78 2026-07-11 09:45:12 -0500 9dc1ce77aefc Audio-Bug-Fix 0.1 WIP on dev: 141613f Update version string to indicate audio-bug-fixes
79 2026-07-11 09:45:12 -0500 bdbc525b6fb7 Audio-Bug-Fix 0.1 index on dev: 141613f Update version string to indicate audio-bug-fixes
80 2026-07-11 10:16:15 -0500 72910e700605 Audio-Bug-Fix 0.1 fix: prevent Play() deadlock when device is not open
81 2026-07-11 11:39:58 -0500 bb132ceeaf86 Audio-Bug-Fix 0.5 fix: resolve 4 critical audio mixer bugs (v0.5)
82 2026-07-11 13:43:05 -0500 3f8144a708bb Audio-Bug-Fix 0.6 Gate READY on download completion; move audio to ~/.radiantwave/audio
83 2026-07-11 15:27:01 -0500 336f407499f4 Audio-Bug-Fix 0.6 Allow F2/F3 during DOWNLOADING_ASSETS state
84 2026-07-11 16:36:15 -0500 a93e47589984 Audio-Bug-Fix 0.6 Fix double-close panic on DownloadDone channel
85 2026-07-11 16:43:16 -0500 2ef826fe635b Audio-Bug-Fix 0.6 Fix double-close panic on DownloadDone: skip re-navigation between HOME-mapped states
86 2026-07-11 17:10:59 -0500 f29d1d1af51e 07112026-1710 Bump version to 07112026-1710; move completed items to release section
87 2026-07-12 18:15:41 -0500 c98b84a3a8f3 07112026-1710 fix: active device highlight incorrect when using default device
88 2026-07-12 18:16:18 -0500 e090870b4dfd 07112026-1710 fix: SwitchDevice no longer passes 'system default' to SDL
89 2026-07-12 18:17:22 -0500 5854c57f6f01 07112026-1710 changelog: update fixed issues list, remove known issues
90 2026-07-27 13:14:37 -0500 b1a5a670ab0b 07112026-1710 Add audio list
91 2026-07-27 14:13:58 -0500 c06a3f7f92de 07112026-1710 init new repo
92 2026-07-27 14:23:31 -0500 5a6031a3fe5a 07112026-1710 refactor: migrate from Codeberg to Gitea and rename rhemawave to radiantwave
93 2026-07-27 14:24:25 -0500 7f2f19fe41d9 07112026-1710 fix: use correct lowercase repo name radiantwave.git
94 2026-07-27 14:29:03 -0500 26aca21e963d 07112026-1710 Update flake.lock
95 2026-07-28 14:10:44 +0000 16545a77fd7e 07112026-1710 chore: update radiantwave-dev to latest dev
96 2026-07-28 10:43:06 -0500 4af16d9df7c4 07112026-1710 Specify main branch
97 2026-07-29 14:46:11 -0500 6dda46288473 07292026_1445 Add audio selection
98 2026-07-29 14:57:32 -0500 7a3b6a66b72f 07292026_1445 Remove wpctl parsing tests (TestParseSinkLine, TestParseSinks)
99 2026-07-29 14:58:15 -0500 3480e7594a3b 07292026_1445 Replace wpctl volume control with SDL_mixer VolumeMusic
100 2026-07-29 16:19:15 -0500 91711eef9328 07292026_1618 Replace AudioFile with AudioTrack, fix download+orphan+MD5 sync, always play original audio fallback
101 2026-07-29 19:40:04 -0500 09bc293af31e 07292026_1618 Chore: Update RadiantWave
102 2026-08-02 00:56:34 -0500 ab6488028120 07292026_1618 Organize flakes and cleanup things.
103 2026-08-03 01:54:27 -0500 11b25227d789 07292026_1618 Rename app to radiantwave: embed and provision assets, drop proclamations
104 2026-08-03 01:54:52 -0500 ce6856b37a09 07292026_1618 Fix kiosk startup: radiantwave binary, drop stale rsync, fix GODEBUG typo
105 2026-08-03 07:26:16 +0000 8ef25f756076 07292026_1618 chore: update radiantwave-dev to latest
106 2026-08-03 02:40:51 -0500 b54dbca2d5e7 07292026_1618 Fix asset provisioning to strip embedded share/radiantwave root
107 2026-08-06 10:13:40 -0500 0f5aa7670cf9 07292026_1618 Chore: Update Radiantwave
108 2026-08-06 21:55:40 -0500 c3f65f78976f 07292026_1618 Fix audio: pin system volume to 100% before RadiantWave launches
109 2026-08-07 00:38:25 -0500 2d4aca230d04 07292026_1618 Update configuration and dev-iso
110 2026-08-07 17:13:59 -0500 cbb3e0585970 07292026_1618 Update to radiantave-cpp, enable plymouth boot image
111 2026-08-07 17:18:32 -0500 37d18d3f474b 07292026_1618 Update repository location
112 2026-08-07 17:19:04 -0500 50a1f3e99dfc 07292026_1618 Update repository location
113 2026-08-07 17:39:26 -0500 06f02d49b910 07292026_1618 update radiantwave to radiatnwave-dev
114 2026-08-07 17:47:04 -0500 2e3d88b57dce 07292026_1618 Update Plymount dontUnpack = true;
115 2026-08-07 22:17:52 -0500 4580319f19f5 07292026_1618 Update radiantwave-dev
116 2026-08-07 23:58:25 -0500 8abb028c8260 07292026_1618 fix wpctl volume pinning
117 2026-08-08 00:02:09 -0500 e3120f27c20b 07292026_1618 Pull back from radiantwave-cpp-dev to radiantwave-dev
118 2026-08-08 00:12:57 -0500 2212cc9b7805 07292026_1618 Add audio device switching UI
119 2026-08-08 01:20:11 -0500 312f5e2a90e0 08082026_0120 Rename rhemawave to radiantwave throughout
120 2026-08-08 06:25:05 +0000 1f3e194aa558 08082026_0120 Chore: Update Radiantwave-dev
121 2026-08-08 01:44:03 -0500 240be2540f3f 08082026_0120 Update Radiantwave
122 2026-08-17 10:44:36 -0500 06c2a7cd0a1e 08082026_0120 Add plan to migrate to DRM
123 2026-08-17 10:51:05 -0500 50176d8fd5e4 08082026_0120 repoint production to radiantwave-legacy, dev to radiantwave (ref=main)
124 2026-08-17 10:57:16 -0500 0cb1337f18fd 08082026_0120 add radiantwave-legacy input with auth token, update flake.lock
125 2026-08-17 11:02:19 -0500 2abdfa2b9b28 08082026_0120 revert upgradeFlakeRef URLs to KioskConfig to match renamed repo
126 2026-08-17 12:43:57 -0500 974a61051883 08082026_0120 Update dev images to new radiantwave
127 2026-08-17 12:48:22 -0500 fb2f86125804 08082026_0120 Revert back to Legacy Radiant Wave
128 2026-08-17 12:50:20 -0500 027d1a628feb 08082026_0120 Update token
129 2026-08-17 12:52:11 -0500 14463ad99b08 08082026_0120 Just make RadiantWave-Legacy public
130 2026-08-17 12:54:06 -0500 e09758a70ece 08082026_0120 fix: switch radiantwave inputs to SSH for legacy Go repo
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# Archive old build images on the fileserver before publishing new ones.
#
# Usage: tools/archive-images.sh <ssh-target> <images-dir> <archive-dir> <pattern>...
#
# Zero matching files is fine (nothing to archive yet); a missing directory
# or a failed move aborts non-zero so CI stops before publishing.
# The remote half is piped to `bash -s` over stdin so there is no nested
# shell quoting to get wrong.
set -euo pipefail
die() { echo "error: $*" >&2; exit 1; }
(( $# >= 4 )) || die "usage: $0 <ssh-target> <images-dir> <archive-dir> <pattern>..."
target=$1 images_dir=$2 archive_dir=$3
shift 3
patterns=("$@")
for v in "$images_dir" "$archive_dir" "${patterns[@]}"; do
case $v in
*[[:space:]\'\"]*) die "unsafe argument (whitespace/quotes): $v" ;;
esac
done
echo "Archiving previous images on $target:"
printf ' pattern: %s\n' "${patterns[@]}"
# Assignments prefixed to `bash -s` are parsed by the remote shell as
# environment variables for the script below; the quoted heredoc marker
# keeps every byte of the payload literal until bash executes it.
ssh "$target" "IMAGES_DIR=$images_dir ARCHIVE_DIR=$archive_dir bash -s" <<'REMOTE'
set -euo pipefail
shopt -s nullglob
if [ ! -d "$IMAGES_DIR" ]; then
echo "error: images dir not found: $IMAGES_DIR" >&2
exit 1
fi
if [ ! -d "$ARCHIVE_DIR" ]; then
echo "error: archive dir not found: $ARCHIVE_DIR" >&2
exit 1
fi
moved=0
for pat in "${PATTERNS[@]}"; do
for f in "$IMAGES_DIR"/$pat; do
echo " archiving $(basename "$f")"
mv "$f" "$ARCHIVE_DIR/"
moved=$((moved + 1))
done
done
echo "Archived $moved file(s)."
REMOTE
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env zsh
#
# find_systems.zsh
# Sweeps a subnet for hosts with SSH open, then queries each as `kiosk`
# for hostname + NixOS configuration revision.
#
# Usage: ./find_systems.zsh [subnet]
# subnet defaults to 192.168.4.0/24
set -uo pipefail # no -e: we want to keep going past unreachable hosts
SUBNET="${1:-192.168.1.0/24}"
SSH_USER="kiosk"
SSH_OPTS=(-o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new)
echo "Scanning $SUBNET for open SSH (22)..."
ips=("${(@f)$(nmap -p 22 --open -Pn "$SUBNET" -oG - | awk '/22\/open/ {print $2}')}")
if [[ ${#ips[@]} -eq 0 ]]; then
echo "No hosts with open SSH found on $SUBNET."
exit 0
fi
echo "Found ${#ips[@]} host(s). Querying..."
echo
fail_count=0
for ip in "${ips[@]}"; do
echo "== $ip =="
if ssh "${SSH_OPTS[@]}" "${SSH_USER}@${ip}" \
'echo "host: $(hostname)"; echo "rev: $(nixos-version --configuration-revision)"'; then
:
else
echo " (unreachable or auth failed)"
((fail_count++))
fi
echo
done
echo "Done. ${fail_count} of ${#ips[@]} host(s) failed to respond."
+20 -33
View File
@@ -1,3 +1,13 @@
# Parameterized zero-touch installer for kiosk USB images.
#
# Usage in flake.nix:
# (import ./zero-touch-installer.nix { targetConfig = "radiantwavekiosk"; })
#
# `targetConfig` must name an entry in self.nixosConfigurations. The target
# closure and its disko partitioning script are derived from that config, so
# an image can never install a mismatched system or format the wrong disk
# layout as long as flake.nix pairs each ISO with its disk-config.nix.
{ targetConfig }:
{ {
pkgs, pkgs,
inputs, inputs,
@@ -5,17 +15,15 @@
... ...
}: }:
let let
targetSystem = cfg = self.nixosConfigurations.${targetConfig};
self.nixosConfigurations.radiantwavekiosk.config.system.build.toplevel; targetSystem = cfg.config.system.build.toplevel;
diskoScript = cfg.config.system.build.diskoScript;
diskoScript = # Keep in sync with myKey in configuration.nix.
self.nixosConfigurations.radiantwavekiosk.config.system.build.diskoScript; adminKey =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL4qWNZnYU5JaqR9c2qh6oFZktzVNBOx42PfDaGofCL6 cnclack@pm.me macbook";
in in
{ {
############################################################
# System configuration
############################################################
nix.settings = { nix.settings = {
experimental-features = [ experimental-features = [
"nix-command" "nix-command"
@@ -38,32 +46,11 @@ in
}; };
}; };
nix.extraOptions = ''
netrc-file = /etc/nix/netrc
'';
environment.variables.KIOSK_FLAKE = "${self}"; environment.variables.KIOSK_FLAKE = "${self}";
environment.etc."nix/netrc" = {
text = ''
password eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIwODgzODk2ODEsIm5iZiI6MTc3MjgxMzY4MSwic3ViIjoia2lvc2siLCJodHRwczovL2p3dC5hdHRpYy5ycy92MSI6eyJjYWNoZXMiOnsia2lvc2siOnsiciI6MX19fX0.N6Ktrmmu1LSy2PP8uNF25VHRP1dxPZJ4M0MR1kUpVi0
'';
mode = "0400";
};
networking.networkmanager.enable = true; networking.networkmanager.enable = true;
############################################################ users.users.root.openssh.authorizedKeys.keys = [ adminKey ];
# User configuration
############################################################
users.users.root.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL4qWNZnYU5JaqR9c2qh6oFZktzVNBOx42PfDaGofCL6 cnclack@pm.me macbook"
];
############################################################
# Service configuration
############################################################
services.openssh.enable = true; services.openssh.enable = true;
@@ -115,7 +102,7 @@ in
${diskoScript} ${diskoScript}
echo "Installing RadiantWave" echo "Installing RadiantWave (${targetConfig})"
echo "Closure Path: ${targetSystem}" echo "Closure Path: ${targetSystem}"
nixos-install \ nixos-install \
@@ -127,7 +114,7 @@ in
echo " Installation complete!" echo " Installation complete!"
echo " Please shut down and remove the flash drive before restarting" echo " Please shut down and remove the flash drive before restarting"
echo "============================================" echo "============================================"
sleep 10000000 sleep infinity
''; '';
}; };
} }