diff --git a/README.md b/README.md index 5beb1c9..3231431 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,149 @@ and manages WiFi/provisioning on a headless Linux kiosk. **Audio:** SDL3_mixer **Target:** Linux kiosk (Wayland); developed on macOS +--- +## Version Deployment +> How to release a new version of RadiantWave, and what happens on the kiosks when you do. + +### Two channels, two update paths + +| Channel | App input | Attic cache | Manifest | +|---|---|---|---| +| `dev` | `radiantwave-dev` (`?ref=dev`) | `attic.../radiantwave-dev` | `files.../radiantwave-dev/manifest.json` | +| `main` | `radiantwave` (`?ref=refs/tags/1.8`) | `attic.../radiantwave` | `files.../radiantwave/manifest.json` | + +A kiosk gets RadiantWave by two independent paths: + +- **Live app update** -- CI publishes a manifest; the kiosk's + `radiantwave-updater.timer` fetches the closure from Attic and swaps + `/var/lib/radiantwave/current`. Takes effect in ~2 min, no rebuild. +- **NixOS rebuild** -- `system.autoUpgrade` runs every 10 min against the + `KioskConfig` repo with `--refresh` and rebuilds the whole system closure. + +`KioskConfig` enables `services.radiantwaveUpdater` on all four configs on +**both** branches, so the live path is always available. + +**An app-only feature only needs the live app update path. You do not commit, +push, or rebuild `../KioskConfig`.** Its `flake.lock` pin of `radiantwave-dev` +only determines the closure baked into the ISO; the updater overwrites that +pointer as soon as it sees a newer manifest. + +### Test a feature on the dev kiosk -- manual steps + +**1. Build and test locally.** + +```sh +nix develop --command bash -c ' + cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug + cmake --build build + ./build/test_assets +' +``` + +Run each `./build/test_` binary you touched -- there is no ctest +integration and no aggregate target. If you added a test file, register it as +a new `add_executable` in `CMakeLists.txt` first. + +**2. Land it on `dev`.** + +```sh +git switch dev && git pull +git switch -c feature/ +# ... commit your work ... +git push -u origin feature/ +``` + +Open a PR against `dev` and merge. The kiosk only ever builds `dev`. + +**3. Dispatch the build workflow** on the RadiantWave repo in Gitea Actions. + +**4. Verify.** + +The Home footer's Version row shows the new short commit hash, and: + +```sh +ssh kiosk@ 'readlink /var/lib/radiantwave/current' +curl -fsSL https://files.radiantwavetech.com/radiantwave-dev/manifest.json | jq .storePath +``` + +Both must match. + +### Test a feature on the dev kiosk -- automated by CI + +> Everything between dispatching `build.yml` and the kiosk running the new +> binary. No human step is required for any of it. +> +> **Trigger.** `workflow_dispatch` force-clones the `dev` branch +> (`REF=dev`, hardcoded -- you cannot pick a branch). A tag push clones the tag. +> Either way `APP_VERSION` becomes the short commit hash on dispatch, or +> `git describe --tags --always` on a tag. +> +> **Build.** Native `x86_64-linux` package, then the offline suite via +> `checks..default`, then the live portal tests from `nix develop` +> (`RWT_LIVE_*` secrets), then the `aarch64-linux` package and checks. The +> aarch64 build is emulated via binfmt/qemu-user and is the long pole. +> +> **Publish.** `attic push radiantwave:radiantwave-dev` with both +> `result-pkg` and `result-pkg-aarch64`, using `ATTIC_TOKEN_DEV`. Then it +> writes `{"version","storePath"}` from `readlink -f` of each result into +> `manifest.json` (x86) and `aarch64-manifest.json` (arm), and `scp`s both to +> `157.245.124.103:/mnt/infrastructure_data/files/radiantwave-dev/`, which is +> served as `https://files.radiantwavetech.com/radiantwave-dev/`. +> +> **Kiosk side.** `radiantwave-updater.timer` (`OnBootSec=2min`, +> `OnUnitActiveSec=2min`, `Persistent`) runs the oneshot +> `radiantwave-updater.service`, which curls the manifest, `jq`s `.storePath`, +> and compares it against `readlink /var/lib/radiantwave/current`. On a new +> path it fetches the closure, never building: +> +> ```sh +> nix-store -r "$NEW_PATH" \ +> --add-root /var/lib/radiantwave/.current-root \ +> --option extra-substituters " https://cache.nixos.org" +> ``` +> +> Then it swaps atomically with `ln -sfn` plus `mv -T`. Both Attic caches and +> their public keys are already trusted in `configuration.nix`, so the kiosk +> needs no credentials. +> +> **Failure mode.** The Attic push and manifest publish are ordinary steps, not +> `always()`, so **any failing test blocks publication** and the kiosk keeps +> running the previous build. Check the run's log before assuming a deploy +> landed. + +### Gotchas + +- **Dispatch hardcodes `dev`.** `build.yml` sets `REF=dev` on + `workflow_dispatch`; you cannot select a branch. +- **Push before you dispatch.** CI clones `dev` at dispatch time, so dispatching + first publishes the previous commit and the feature appears not to deploy. +- **Reboot briefly reverts the app.** `radiantwave-updater.nix` seeds the + pointer with tmpfiles type `L`, which re-points `current` at the ISO's baked + baseline on every boot. The 2-minute timer restores the live build shortly + after, so the window is brief but real. +- **A reboot does not lose kiosk data.** `radiantwave-backup.service` rsyncs + `~/.local/radiantwave` (minus `audio/`) before every upgrade attempt. +- **Don't bump `channel.appVersion` for an app-only deploy.** It only labels + the baked ISO baseline. Bump it when rebuilding a dev ISO so it advertises + the new fingerprint. +- **No local Linux builder on macOS.** `extra-platforms = x86_64-darwin` only, + so `nix build .#packages.x86_64-linux.default` and + `nixos-rebuild switch --target-host` are not available from a Mac. The + `orion-lxc` runner is the only `x86_64-linux` builder. + +### Releasing to production + +1. Merge `dev` -> `main` in `radiantwave`. +2. Tag and push: `git tag 1.9 && git push origin 1.9`. The tag push triggers + `build.yml`, which publishes to the `radiantwave` Attic cache and + `radiantwave/manifest.json` using `ATTIC_TOKEN_PROD`. +3. In `KioskConfig`, on `main`: set `radiantwave.url` to + `?ref=refs/tags/1.9`, set `channel.appVersion = "1.9"`, and run + `nix flake update radiantwave`. Commit both -- the lock and the channel + block must agree. +4. Its CI fails fast unless the baked baseline exactly matches the published + manifest, so this step cannot drift silently. + --- ## Project Layout @@ -564,34 +707,6 @@ status to decide which page to show. --- -## Rebuild Progress - -| Component | Status | -| ----------- | -------- | -| Entry / Event Loop | Done | -| Window + Renderer | Done | -| Page System | Done | -| PageManager (stack) | Done | -| KeybindManager | Done | -| Home Page | Done | -| Settings Page | Done | -| Provisioning FSM | — | -| Pages (11 types) | — | -| OpenGL Context | — | -| Virtual Framebuffer | — | -| Shader Manager | — | -| Font Manager | — | -| Line Patterns | — | -| Color Patterns | — | -| Mixer | — | -| Network / WiFi | — | -| Database (SQLite) | — | -| HTTP Client | — | -| Logging | — | -| Config / App State | — | - ---- - ## Common Pitfalls - Do not commit audio assets (`.wav` files) -- downloaded at runtime.