Files
KioskConfig/radiantwave-updater.nix
T
NeilClack 7d8cc56ba0
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
Fetch updater closures from cache.nixos.org as well as Attic
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

91 lines
3.5 KiB
Nix

{ 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).";
};
baselinePackage = lib.mkOption {
type = lib.types.package;
description = "Copy of RadiantWave baked into the system closure, 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 baseline copy already baked into the system closure
# (so the ISO carries it with no duplication); the updater service later
# re-points it atomically to newer closures fetched from Attic.
systemd.tmpfiles.rules = [
"L /var/lib/radiantwave/current - - - - ${cfg.baselinePackage.outPath}"
];
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";
RemainAfterExit = true;
};
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
echo "radiantwave-updater: already current (''${CURRENT})"
exit 0
fi
# Realize the closure via the machine's substituters. The app itself
# only exists in the dev Attic cache, but nixpkgs deps (e.g. sqlite)
# are signed by cache.nixos.org and Attic intentionally skips them to
# avoid re-uploading the world. Fetch those from cache.nixos.org so
# the whole closure resolves without ever building from source.
echo "radiantwave-updater: acquiring ''${NEW_PATH}"
${pkgs.nix}/bin/nix-store -r "$NEW_PATH" \
--option extra-substituters "https://attic.radiantwavetech.com/radiantwave-dev https://cache.nixos.org" \
--option extra-trusted-public-keys "radiantwave-dev:B8FhLu3MQ9JfeEIHIKNrKxnP+3akzzkqMbTZR4hRfj0= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" || {
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}"
'';
};
systemd.timers.radiantwave-updater = {
description = "Check for RadiantWave app updates";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "2min";
OnUnitActiveSec = "2min";
Persistent = true;
Unit = "radiantwave-updater.service";
};
};
};
}