The radiantwave 1.8 tag was force-moved from 34c72b5 to a5a6b5b while flake.lock still pinned the old commit, so the ISO baked a baseline that was not the closure RadiantWave CI published to Attic. With version 1.8 overridden this could still compile RadiantWave from source instead of substituting it. - Update flake.lock so radiantwave?ref=refs/tags/1.8 resolves to a5a6b5b. - Expose channelVersion/channelAtticUrl/channelManifestDir/baselineOutPaths from flake.nix for CI verification. - Prod workflows: fail fast unless channel.appVersion equals the channel manifest version, baselineOutPaths.<system> equals the manifest storePath, and that store path exists in the channel's Attic cache.
327 lines
14 KiB
Nix
327 lines
14 KiB
Nix
{
|
|
inputs = {
|
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
|
disko.url = "github:nix-community/disko";
|
|
disko.inputs.nixpkgs.follows = "nixpkgs";
|
|
nixos-facter-modules.url = "github:numtide/nixos-facter-modules";
|
|
radiantwave.url = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git?ref=refs/tags/1.8";
|
|
radiantwave-dev.url = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git?ref=dev";
|
|
nixos-raspberrypi.url = "github:nvmd/nixos-raspberrypi/main";
|
|
};
|
|
|
|
outputs =
|
|
{
|
|
self,
|
|
nixpkgs,
|
|
disko,
|
|
nixos-facter-modules,
|
|
radiantwave,
|
|
radiantwave-dev,
|
|
nixos-raspberrypi,
|
|
...
|
|
}@inputs:
|
|
let
|
|
system = "x86_64-linux";
|
|
pkgs = nixpkgs.legacyPackages.${system};
|
|
|
|
# Plymouth theme: renders the RadiantWave SVG to a full-HD splash.png
|
|
# at build time (librsvg), so the theme is hermetic and resolution-
|
|
# independent. The .plymouth file's /usr paths are rewritten to $out
|
|
# (the NixOS module's initrd fixup rewrites store paths again).
|
|
mkRadiantwavePlymouth = targetPkgs:
|
|
targetPkgs.stdenv.mkDerivation {
|
|
pname = "radiantwave-plymouth";
|
|
version = "1.0.0";
|
|
|
|
nativeBuildInputs = [ targetPkgs.librsvg ];
|
|
|
|
src = ./assets/RWT-Circular-Color-ForOnDark.svg;
|
|
dontUnpack = true;
|
|
|
|
buildPhase = ''
|
|
mkdir -p $out/share/plymouth/themes/radiantwave
|
|
rsvg-convert \
|
|
--keep-aspect-ratio \
|
|
--background-color=#111318 \
|
|
--width=1280 \
|
|
--height=720 \
|
|
--output=$out/share/plymouth/themes/radiantwave/splash.png \
|
|
$src
|
|
sed 's,/usr/,'"$out"'/,g' ${./plymouth-theme/radiantwave.plymouth} \
|
|
> $out/share/plymouth/themes/radiantwave/radiantwave.plymouth
|
|
cp ${./plymouth-theme/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: appInput = radiantwave (tag 1.8), atticRepo = "radiantwave",
|
|
# gitRef = "", appVersion = "1.8"
|
|
# dev: appInput = radiantwave-dev, atticRepo = "radiantwave-dev",
|
|
# gitRef = "?ref=dev", appVersion = <dev fingerprint>
|
|
#
|
|
# To flip branches, change ONLY these four values (then push/merge).
|
|
channel = {
|
|
appInput = radiantwave; # main
|
|
atticRepo = "radiantwave"; # main
|
|
gitRef = ""; # main
|
|
appVersion = "1.8"; # main
|
|
};
|
|
|
|
# Exposed so CI (see .gitea/workflows/build-prod-*.yml) and humans can
|
|
# verify which channel this checkout resolves to. On `main` this must be
|
|
# "radiantwave"; on `dev` it must be "radiantwave-dev". The prod
|
|
# workflows fail fast if this isn't the production channel, so a merge
|
|
# that brings a dev-pointing channel block into `main` breaks the build
|
|
# instead of silently shipping dev builds as prod ISOs.
|
|
channelName = if channel.appInput == radiantwave then "radiantwave" else "radiantwave-dev";
|
|
|
|
# Channel metadata exposed for CI preflight (see .gitea/workflows/build-*).
|
|
# `channelVersion` must equal the version RadiantWave CI publishes in the
|
|
# channel manifest; `baselineOutPaths` must equal the manifest's
|
|
# `storePath`. When they do, the ISO bakes exactly the closure already in
|
|
# Attic, so the app is substituted (never compiled locally) and the first
|
|
# live update is a no-op. A mismatch means either the pinned input is
|
|
# stale (as when the release tag moves) or channel.appVersion drifted.
|
|
channelVersion = channel.appVersion;
|
|
channelAtticUrl = "https://attic.radiantwavetech.com/${channel.atticRepo}";
|
|
channelManifestDir = "https://files.radiantwavetech.com/${channel.atticRepo}";
|
|
baselineOutPaths = {
|
|
x86_64-linux = (appPackage "x86_64-linux").outPath;
|
|
aarch64-linux = (appPackage "aarch64-linux").outPath;
|
|
};
|
|
|
|
# The App package, pinned to the channel's declared version. RadiantWave's
|
|
# own flake only injects its version from the ambient APP_VERSION env var
|
|
# on impure evals; pure evals (local `nix build`, build-rpi.sh) fall back
|
|
# to its 0.1.0 default. Overriding the version here makes the baked
|
|
# baseline correct regardless of eval purity, and matches the Attic
|
|
# closure when RadiantWave CI built it with the same version string.
|
|
appPackage = system: channel.appInput.packages.${system}.default.override {
|
|
version = channel.appVersion;
|
|
};
|
|
|
|
# Live app-update side channel. The manifest is served by the
|
|
# 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;
|
|
baselinePackage = baseline;
|
|
manifestUrl = "https://files.radiantwavetech.com/${repo}/${manifestFile}";
|
|
atticUrl = "https://attic.radiantwavetech.com/${repo}";
|
|
};
|
|
};
|
|
|
|
mkKioskSystem = {
|
|
system,
|
|
radiantwave,
|
|
upgradeFlakeRef,
|
|
hostName,
|
|
diskModule,
|
|
extraModules ? [],
|
|
}:
|
|
nixpkgs.lib.nixosSystem {
|
|
inherit system;
|
|
|
|
specialArgs = {
|
|
inherit radiantwave upgradeFlakeRef;
|
|
radiantwaveApp = appPackage system;
|
|
radiantwavePlymouth = mkRadiantwavePlymouth;
|
|
};
|
|
|
|
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; }
|
|
radiantwave.nixosModules.default
|
|
] ++ extraModules;
|
|
};
|
|
|
|
mkRpiKioskSystem = {
|
|
radiantwave,
|
|
upgradeFlakeRef,
|
|
hostName,
|
|
extraModules ? [],
|
|
}:
|
|
nixos-raspberrypi.lib.nixosSystem {
|
|
specialArgs = {
|
|
inherit radiantwave upgradeFlakeRef;
|
|
inherit nixos-raspberrypi;
|
|
radiantwaveApp = appPackage "aarch64-linux";
|
|
radiantwavePlymouth = mkRadiantwavePlymouth;
|
|
};
|
|
|
|
modules = [
|
|
{ nixpkgs.config.allowUnfree = true; }
|
|
({ ... }: {
|
|
imports = with nixos-raspberrypi.nixosModules; [
|
|
sd-image
|
|
raspberry-pi-5.base
|
|
raspberry-pi-5.page-size-16k
|
|
raspberry-pi-5.display-vc4
|
|
];
|
|
})
|
|
./configuration.nix
|
|
./radiantwave-updater.nix
|
|
radiantwave.nixosModules.default
|
|
{
|
|
networking.hostName = hostName;
|
|
system.configurationRevision = revision;
|
|
|
|
boot.loader.systemd-boot.enable = nixpkgs.lib.mkForce false;
|
|
boot.loader.efi.canTouchEfiVariables = nixpkgs.lib.mkForce false;
|
|
boot.loader.raspberry-pi.bootloader = "kernel";
|
|
|
|
nix.settings.extra-substituters = [
|
|
"https://nixos-raspberrypi.cachix.org"
|
|
];
|
|
nix.settings.extra-trusted-public-keys = [
|
|
"nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI="
|
|
];
|
|
}
|
|
] ++ 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
|
|
{
|
|
inherit channelName channelVersion channelAtticUrl channelManifestDir baselineOutPaths;
|
|
|
|
packages.x86_64-linux.radiantwavekiosk = self.nixosConfigurations.radiantwavekiosk.config.system.build.toplevel;
|
|
packages.x86_64-linux.radiantwavekiosk-commercial = self.nixosConfigurations.radiantwavekiosk-commercial.config.system.build.toplevel;
|
|
packages.x86_64-linux.radiantwavekiosk-android = self.nixosConfigurations.radiantwavekiosk-android.config.system.build.toplevel;
|
|
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 RadiantWave app input, 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 = {
|
|
radiantwavekiosk = mkKioskSystem {
|
|
system = "x86_64-linux";
|
|
radiantwave = channel.appInput;
|
|
hostName = "radiantwavekiosk";
|
|
diskModule = ./disk-config.nix;
|
|
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk";
|
|
extraModules = [
|
|
(radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = appPackage "x86_64-linux"; })
|
|
];
|
|
};
|
|
radiantwavekiosk-commercial = mkKioskSystem {
|
|
system = "x86_64-linux";
|
|
radiantwave = channel.appInput;
|
|
hostName = "radiantwavekiosk-commercial";
|
|
diskModule = ./commercial-disk-config.nix;
|
|
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-commercial";
|
|
extraModules = [
|
|
(radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = appPackage "x86_64-linux"; })
|
|
];
|
|
};
|
|
# Rotated screen for android-style displays.
|
|
radiantwavekiosk-android = mkKioskSystem {
|
|
system = "x86_64-linux";
|
|
radiantwave = channel.appInput;
|
|
hostName = "radiantwavekiosk-android";
|
|
diskModule = ./disk-config.nix;
|
|
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-android";
|
|
extraModules = [
|
|
./rotated_display.nix
|
|
(radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = appPackage "x86_64-linux"; })
|
|
];
|
|
};
|
|
# Raspberry Pi 5.
|
|
radiantwavekiosk-rpi = mkRpiKioskSystem {
|
|
radiantwave = channel.appInput;
|
|
hostName = "radiantwavekiosk-rpi";
|
|
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-rpi";
|
|
extraModules = [
|
|
(radiantwaveUpdaterModule {
|
|
repo = channel.atticRepo;
|
|
baseline = appPackage "aarch64-linux";
|
|
manifestFile = "aarch64-manifest.json";
|
|
})
|
|
];
|
|
};
|
|
|
|
# === USB Images ===
|
|
# Each ISO installs exactly the named system closure using that
|
|
# system's disk layout. NVMe layouts pair with ./disk-config.nix,
|
|
# SATA with ./commercial-disk-config.nix — don't cross them.
|
|
# The same names build dev-flavored images when the flake is checked
|
|
# out on the `dev` branch (the channel block above selects the dev
|
|
# app, attic repo, and upgrade ref), so there are no separate -dev
|
|
# ISOs.
|
|
kiosk-iso = mkIso {
|
|
targetConfig = "radiantwavekiosk";
|
|
diskModule = ./disk-config.nix;
|
|
};
|
|
commercial-iso = mkIso {
|
|
targetConfig = "radiantwavekiosk-commercial";
|
|
diskModule = ./commercial-disk-config.nix;
|
|
};
|
|
android-iso = mkIso {
|
|
targetConfig = "radiantwavekiosk-android";
|
|
diskModule = ./disk-config.nix;
|
|
};
|
|
};
|
|
};
|
|
}
|