Compare commits
29
Commits
v1
..
e50391ba9e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e50391ba9e | ||
|
|
8796feddd9 | ||
|
|
b6de748f8f | ||
|
|
cac2e455ec | ||
|
|
85788ce32d | ||
|
|
e6138d398d | ||
|
|
c18e158b69 | ||
|
|
b136d1751b | ||
|
|
05bb98b107 | ||
|
|
ca89c234ab | ||
|
|
9a8a258228 | ||
|
|
7d8cc56ba0 | ||
|
|
e44626feec | ||
|
|
48c31d0f0c | ||
|
|
d661162676 | ||
|
|
b8d0f59c0a | ||
|
|
4e36113636 | ||
|
|
2bf762acac | ||
|
|
46a61a8397 | ||
|
|
c1ad4a9767 | ||
|
|
47dc7b8964 | ||
|
|
dfa7320a37 | ||
|
|
87fca854f4 | ||
|
|
c8298a128f | ||
|
|
036c437430 | ||
|
|
8775fcfb24 | ||
|
|
f900585702 | ||
|
|
16b8e3aeb9 | ||
|
|
45b4d7aa95 |
@@ -1,51 +0,0 @@
|
|||||||
name: Build Dev ISO
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
|
|
||||||
env:
|
|
||||||
NIX_CONFIG: |
|
|
||||||
extra-substituters = https://attic.radiantwavetech.com/radiantwave-dev
|
|
||||||
extra-trusted-public-keys = radiantwave-dev:B8FhLu3MQ9JfeEIHIKNrKxnP+3akzzkqMbTZR4hRfj0=
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-dev-iso:
|
|
||||||
runs-on: build-server
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Clone Repository
|
|
||||||
run: git clone --depth 1 --no-single-branch ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/KioskConfig.git .
|
|
||||||
|
|
||||||
- name: Checkout dev branch
|
|
||||||
run: git checkout 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: Move old dev images to archive
|
|
||||||
run: tools/archive-images.sh nclack@157.245.124.103 /mnt/infrastructure_data/files/images /mnt/infrastructure_data/files/images/archives 'radiantwave-kiosk-dev*'
|
|
||||||
|
|
||||||
- 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}"
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
NIX_CONFIG: |
|
|
||||||
extra-substituters = https://attic.radiantwavetech.com/radiantwave
|
|
||||||
extra-trusted-public-keys = radiantwave:QADrjpBWJssTZ3KmiZtcWEf4uAm66oOlHmtQI3jkrgA=
|
|
||||||
|
|
||||||
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: Move old android images to archive
|
|
||||||
# run: |
|
|
||||||
# ssh nclack@157.245.124.103 'mv /mnt/infrastructure_data/files/images/radiantwave-android-iso* /mnt/infrastructure_data/files/images/archives/'
|
|
||||||
|
|
||||||
- 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,43 +0,0 @@
|
|||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
NIX_CONFIG: |
|
|
||||||
extra-substituters = https://attic.radiantwavetech.com/radiantwave
|
|
||||||
extra-trusted-public-keys = radiantwave:QADrjpBWJssTZ3KmiZtcWEf4uAm66oOlHmtQI3jkrgA=
|
|
||||||
|
|
||||||
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: Move old commercial images to archive
|
|
||||||
# run: |
|
|
||||||
# ssh nclack@157.245.124.103 'mv /mnt/infrastructure_data/files/images/radiantwave-commercial-iso* /mnt/infrastructure_data/files/images/archives/'
|
|
||||||
|
|
||||||
- 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}"
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
NIX_CONFIG: |
|
|
||||||
extra-substituters = https://attic.radiantwavetech.com/radiantwave
|
|
||||||
extra-trusted-public-keys = radiantwave:QADrjpBWJssTZ3KmiZtcWEf4uAm66oOlHmtQI3jkrgA=
|
|
||||||
|
|
||||||
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-home-${ISO_DATE}.iso"
|
|
||||||
cp -L result/iso/*.iso "/home/user/radiantwave/${FILENAME}"
|
|
||||||
echo "KIOSK_ISO_FILENAME=${FILENAME}" >> "$GITHUB_ENV"
|
|
||||||
|
|
||||||
# - name: Move old kiosk images to archive
|
|
||||||
# run: |
|
|
||||||
# ssh nclack@157.245.124.103 'mv /mnt/infrastructure_data/files/images/radiantwave-home* /mnt/infrastructure_data/files/images/archives/'
|
|
||||||
|
|
||||||
- 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}"
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
NIX_CONFIG: |
|
|
||||||
extra-substituters = https://attic.radiantwavetech.com/radiantwave https://nixos-raspberrypi.cachix.org
|
|
||||||
extra-trusted-public-keys = radiantwave:QADrjpBWJssTZ3KmiZtcWEf4uAm66oOlHmtQI3jkrgA= nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI=
|
|
||||||
|
|
||||||
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: Move old Raspberry Pi images to archive
|
|
||||||
run: tools/archive-images.sh nclack@157.245.124.103 /mnt/infrastructure_data/files/images /mnt/infrastructure_data/files/images/archives 'radiantwave-rpi*'
|
|
||||||
|
|
||||||
- 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}"
|
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
# One build workflow for every branch — do not add per-branch copies.
|
||||||
|
#
|
||||||
|
# The branch/ref selects the channel, nothing else does: the flake's `channel`
|
||||||
|
# block picks the app input, Attic repo, and upgrade ref, so the same config
|
||||||
|
# names build dev images on `dev` and prod images on `main`. This file itself
|
||||||
|
# is identical on both branches.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
#
|
||||||
|
# Hardware variants come from an include-matrix:
|
||||||
|
# ISO variants -> .config.system.build.isoImage (storeContents pulls in the closure)
|
||||||
|
# Raspberry Pi -> .config.system.build.sdImage
|
||||||
|
# `orion-lxc` emulates aarch64-linux, so the Pi build runs alongside the rest.
|
||||||
|
name: Build Kiosk Images
|
||||||
|
|
||||||
|
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.
|
||||||
|
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=
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: ${{ matrix.label }}
|
||||||
|
runs-on: orion-lxc
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- label: kiosk
|
||||||
|
system: kiosk-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: android
|
||||||
|
system: android-iso
|
||||||
|
target: isoImage
|
||||||
|
outdir: iso
|
||||||
|
ext: iso
|
||||||
|
arch: x86_64-linux
|
||||||
|
manifest: manifest.json
|
||||||
|
- 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)"
|
||||||
|
|
||||||
|
# Production only: fail fast unless the baked closure is exactly what
|
||||||
|
# RadiantWave CI published to Attic. The dev channel churns on every
|
||||||
|
# dispatch, so this is skipped there.
|
||||||
|
- name: Verify baked baseline matches the published channel
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
||||||
|
|
||||||
|
if [ "$CHANNEL" != "radiantwave" ]; then
|
||||||
|
echo "channel '$CHANNEL' is not production; skipping baseline preflight"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION=$(nix eval --impure --raw .#channelVersion)
|
||||||
|
ATTIC=$(nix eval --impure --raw .#channelAtticUrl)
|
||||||
|
MANIFEST_URL="$(nix eval --impure --raw .#channelManifestDir)/${{ matrix.manifest }}"
|
||||||
|
BASELINE=$(nix eval --impure --raw .#baselineOutPaths.${{ matrix.arch }})
|
||||||
|
|
||||||
|
MANIFEST=$(curl -fsSL "$MANIFEST_URL")
|
||||||
|
PUB_VERSION=$(MANIFEST="$MANIFEST" nix eval --impure --raw --expr '(builtins.fromJSON (builtins.getEnv "MANIFEST")).version')
|
||||||
|
PUB_PATH=$(MANIFEST="$MANIFEST" nix eval --impure --raw --expr '(builtins.fromJSON (builtins.getEnv "MANIFEST")).storePath')
|
||||||
|
|
||||||
|
echo "channel version : $VERSION"
|
||||||
|
echo "manifest version: $PUB_VERSION"
|
||||||
|
echo "baseline path : $BASELINE"
|
||||||
|
echo "manifest path : $PUB_PATH"
|
||||||
|
|
||||||
|
if [ "$VERSION" != "$PUB_VERSION" ]; then
|
||||||
|
echo "ERROR: channel.appVersion '$VERSION' != published manifest version '$PUB_VERSION'."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ "$BASELINE" != "$PUB_PATH" ]; then
|
||||||
|
echo "ERROR: baseline '$BASELINE' is not the published closure '$PUB_PATH'."
|
||||||
|
echo " Run 'nix flake update radiantwave' so the pinned input resolves to the released commit."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
HASH=$(basename "$BASELINE" | cut -d- -f1)
|
||||||
|
if ! curl -fsS -o /dev/null "$ATTIC/${HASH}.narinfo"; then
|
||||||
|
echo "ERROR: $BASELINE 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}"
|
||||||
@@ -8,10 +8,14 @@ Hosted on Gitea: `https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig`
|
|||||||
|
|
||||||
## Prod/dev split
|
## Prod/dev split
|
||||||
|
|
||||||
Single branch (`main`) for this repo. The production vs. dev split is at the **flake input + upgrade ref** level:
|
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`:
|
||||||
|
|
||||||
- Production variants consume the `radiantwave` input (`?ref=main`) and auto-upgrade from `KioskConfig.git#<config>` on `main`.
|
- `main`: `channel.appInput = radiantwave` (pinned to tag `1.8`), `atticRepo = "radiantwave"`, `gitRef = ""` (default branch), `appVersion = "1.8"`.
|
||||||
- `-dev` variants consume the `radiantwave-dev` input (`?ref=dev`) and auto-upgrade from `KioskConfig.git?ref=dev#<config>-dev`.
|
- `dev`: `channel.appInput = radiantwave-dev`, `atticRepo = "radiantwave-dev"`, `gitRef = "?ref=dev"`, `appVersion = <dev fingerprint>`.
|
||||||
|
|
||||||
|
Everything downstream (the configs, ISOs, updater module, autoUpgrade refs) reads from `channel`, so a `dev`→`main` merge carries your changes automatically — no copying between sibling configs. To flip a channel, change only those four `channel` values. When merging, reconcile just this one block.
|
||||||
|
|
||||||
|
`channel.appVersion` is applied to the baked app via `appPackage = system: channel.appInput.packages.${system}.default.override { version = channel.appVersion; }`. RadiantWave's own flake only injects its version from the ambient `APP_VERSION` env var on impure evals, so pure evals (local `nix build`, `build-rpi.sh`) fall back to `0.1.0`. Declaring the version here keeps local and CI builds correct without `--impure`, and the derivation matches the Attic closure when RadiantWave CI built it with the same version string. Bump `appVersion` whenever the pinned input changes (release tag on `main`, short commit hash on `dev`).
|
||||||
|
|
||||||
Both gitea repos are public: all git access is plain HTTPS (`git+https://...`), no SSH keys or access tokens on the kiosks. Don't reintroduce `git+ssh` URLs in flake/upgrade refs.
|
Both gitea repos are public: all git access is plain HTTPS (`git+https://...`), no SSH keys or access tokens on the kiosks. Don't reintroduce `git+ssh` URLs in flake/upgrade refs.
|
||||||
|
|
||||||
@@ -36,16 +40,16 @@ nix eval .#nixosConfigurations --apply builtins.attrNames
|
|||||||
|
|
||||||
## Config variants
|
## Config variants
|
||||||
|
|
||||||
|
One config per hardware target. Dev/prod is the branch, not the config — see `channel` in `flake.nix`:
|
||||||
|
|
||||||
| Name | Target hardware | Disk device | App input |
|
| Name | Target hardware | Disk device | App input |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `radiantwavekiosk` | Intel NUC 14 (NVMe) | `/dev/nvme0n1` | `radiantwave` (main) |
|
| `radiantwavekiosk` | Intel NUC 14 (NVMe) | `/dev/nvme0n1` | `radiantwave` (tag 1.8) |
|
||||||
| `radiantwavekiosk-dev` | Intel NUC 14 (NVMe) | `/dev/nvme0n1` | `radiantwave-dev` (dev) |
|
| `radiantwavekiosk-commercial` | Commercial HW (SATA) | `/dev/sda` | `radiantwave` (tag 1.8) |
|
||||||
| `radiantwavekiosk-commercial` | Commercial HW (SATA) | `/dev/sda` | `radiantwave` (main) |
|
| `radiantwavekiosk-android` | Intel NUC 14, rotated display | `/dev/nvme0n1` | `radiantwave` (tag 1.8) |
|
||||||
| `radiantwavekiosk-commercial-dev` | Commercial HW (SATA) | `/dev/sda` | `radiantwave-dev` (dev) |
|
| `radiantwavekiosk-rpi` | Raspberry Pi 5 (SD image) | — | `radiantwave` (tag 1.8) |
|
||||||
| `radiantwavekiosk-android` | Intel NUC 14, rotated display | `/dev/nvme0n1` | `radiantwave` (main) |
|
|
||||||
| `radiantwavekiosk-android-dev` | Intel NUC 14, rotated display | `/dev/nvme0n1` | `radiantwave-dev` (dev) |
|
On the `dev` branch, the same four configs use `radiantwave-dev` and track `?ref=dev`.
|
||||||
| `radiantwavekiosk-rpi` | Raspberry Pi 5 (SD image) | — | `radiantwave` (main) |
|
|
||||||
| `radiantwavekiosk-rpi-dev` | Raspberry Pi 5 (SD image) | — | `radiantwave-dev` (dev) |
|
|
||||||
|
|
||||||
Every variant sets its own `networking.hostName` (same as the config name) and `system.configurationRevision`, so `tools/find_systems.zsh` can identify fleet members.
|
Every variant sets its own `networking.hostName` (same as the config name) and `system.configurationRevision`, so `tools/find_systems.zsh` can identify fleet members.
|
||||||
|
|
||||||
@@ -56,11 +60,10 @@ Each ISO installs exactly one closure using that closure's disk layout (`mkIso`
|
|||||||
| ISO | Installs | Disk layout |
|
| ISO | Installs | Disk layout |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `kiosk-iso` | `radiantwavekiosk` | NVMe (`disk-config.nix`) |
|
| `kiosk-iso` | `radiantwavekiosk` | NVMe (`disk-config.nix`) |
|
||||||
| `dev-iso` | `radiantwavekiosk-dev` | NVMe |
|
|
||||||
| `commercial-iso` | `radiantwavekiosk-commercial` | SATA (`commercial-disk-config.nix`) |
|
| `commercial-iso` | `radiantwavekiosk-commercial` | SATA (`commercial-disk-config.nix`) |
|
||||||
| `commercial-dev-iso` | `radiantwavekiosk-commercial-dev` | SATA |
|
|
||||||
| `android-iso` | `radiantwavekiosk-android` | NVMe |
|
| `android-iso` | `radiantwavekiosk-android` | NVMe |
|
||||||
| `android-dev-iso` | `radiantwavekiosk-android-dev` | NVMe |
|
|
||||||
|
These same names build **dev-flavored** images when the flake is checked out on the `dev` branch (the channel block selects the dev app, attic repo, and upgrade ref), so there are no separate `-dev` ISOs.
|
||||||
|
|
||||||
The installer lives once in `zero-touch-installer.nix` and is parameterized by `targetConfig`. Adding a new variant = add the `nixosConfigurations.*` entry, a matching `packages.x86_64-linux.*` line, and an ISO if needed.
|
The installer lives once in `zero-touch-installer.nix` and is parameterized by `targetConfig`. Adding a new variant = add the `nixosConfigurations.*` entry, a matching `packages.x86_64-linux.*` line, and an ISO if needed.
|
||||||
|
|
||||||
@@ -91,18 +94,20 @@ Regenerate when major hardware changes, not on a schedule — reports are snapsh
|
|||||||
|
|
||||||
- **Disk device differs by variant**: `disk-config.nix` uses `/dev/nvme0n1`, `commercial-disk-config.nix` uses `/dev/sda`. `mkIso` pairs each ISO with the correct layout automatically — keep new ISOs paired correctly.
|
- **Disk device differs by variant**: `disk-config.nix` uses `/dev/nvme0n1`, `commercial-disk-config.nix` uses `/dev/sda`. `mkIso` pairs each ISO with the correct layout automatically — keep new ISOs paired correctly.
|
||||||
- **Changing a config name** requires updating that config's `upgradeFlakeRef` in `flake.nix` (it embeds the name).
|
- **Changing a config name** requires updating that config's `upgradeFlakeRef` in `flake.nix` (it embeds the name).
|
||||||
|
- **The `channel` block is the dev/prod toggle** in `flake.nix` — `appInput`, `atticRepo`, `gitRef`, `appVersion`. When merging `dev`→`main`, keep this block's values for the target branch; only this block differs between branches.
|
||||||
- **`rotated_display.nix` uses `mkForce`** on `loginShellInit` — it replaces the entire kiosk startup script from `configuration.nix`; don't edit both.
|
- **`rotated_display.nix` uses `mkForce`** on `loginShellInit` — it replaces the entire kiosk startup script from `configuration.nix`; don't edit both.
|
||||||
- **Auto-update**: `system.autoUpgrade` runs every 10 min (`*:00/10`) with `--refresh`, operation `switch`, no auto-reboot. A `radiantwave-backup` rsync runs before each attempt. The health-check/rollback service is written but commented out in `configuration.nix`.
|
- **Auto-update**: `system.autoUpgrade` runs every 10 min (`*:00/10`) with `--refresh`, operation `switch`, no auto-reboot. A `radiantwave-backup` rsync runs before each attempt. The health-check/rollback service is written but commented out in `configuration.nix`.
|
||||||
|
- **Live app updater**: each config's `radiantwaveUpdaterModule` points at a per-arch manifest — x86 uses `<repo>/manifest.json`, RPi uses `<repo>/aarch64-manifest.json` (via the `manifestFile` arg). A 2-min timer polls it and fetches the announced closure from the channel's Attic repo. RPi live updates require RadiantWave CI to publish both an aarch64 closure and `aarch64-manifest.json`; until then the RPi stays on the baked baseline.
|
||||||
- **No binary cache**: kiosks fetch from cache.nixos.org only and compile RadiantWave itself from source during upgrades. Attic integration was planned (see `PLAN-update-system.md`) but never wired; the old netrc/JWT setup has been removed.
|
- **No binary cache**: kiosks fetch from cache.nixos.org only and compile RadiantWave itself from source during upgrades. Attic integration was planned (see `PLAN-update-system.md`) but never wired; the old netrc/JWT setup has been removed.
|
||||||
- **Secrets**: none in-tree anymore (gitea repos are public). If either repo goes private, re-add auth via `nix.settings.access-tokens` in `configuration.nix`.
|
- **Secrets**: none in-tree anymore (gitea repos are public). If either repo goes private, re-add auth via `nix.settings.access-tokens` in `configuration.nix`.
|
||||||
- **Flake inputs**: `radiantwave` tracks main, `radiantwave-dev` tracks `?ref=dev`; prod and `-dev` configs consume them respectively. Updating them independently is intentional.
|
- **Flake inputs**: `radiantwave` is pinned to tag `1.8` (`?ref=refs/tags/1.8`); `radiantwave-dev` tracks `?ref=dev`. The `channel` block picks which one the configs consume. Bump the pinned tag and `channel.appVersion` together.
|
||||||
- **SSH keys on machines are inbound-only** (`authorizedKeys` for admin access); outbound git is HTTPS. The root key in `zero-touch-installer.nix` must stay in sync with `myKey` in `configuration.nix`.
|
- **SSH keys on machines are inbound-only** (`authorizedKeys` for admin access); outbound git is HTTPS. The root key in `zero-touch-installer.nix` must stay in sync with `myKey` in `configuration.nix`.
|
||||||
|
|
||||||
## File map
|
## File map
|
||||||
|
|
||||||
- `flake.nix` — entry point: inputs, builders (`mkKioskSystem`, `mkRpiKioskSystem`, `mkIso`), all system definitions, facter report wiring
|
- `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 six ISOs
|
- `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)
|
||||||
- `rotated_display.nix` — portrait display override for Android variant
|
- `rotated_display.nix` — portrait display override for Android variant
|
||||||
|
|||||||
+19
@@ -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.
+6
-3
@@ -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,8 +26,8 @@ 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 "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=" >> /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)..."
|
||||||
@@ -34,7 +37,7 @@ podman run --rm \
|
|||||||
--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}'
|
||||||
'
|
'
|
||||||
|
|||||||
+12
-32
@@ -1,7 +1,15 @@
|
|||||||
{ pkgs, radiantwave, upgradeFlakeRef, radiantwavePlymouth, ... }:
|
{ config, pkgs, radiantwaveApp, upgradeFlakeRef, radiantwavePlymouth, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
radiantApp = radiantwave.packages.${pkgs.system}.default;
|
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" ];
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
@@ -27,7 +35,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.
|
||||||
@@ -159,7 +167,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
|
||||||
'';
|
'';
|
||||||
|
|
||||||
@@ -255,32 +263,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)"
|
|
||||||
# '';
|
|
||||||
# };
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
+10
-10
@@ -220,16 +220,16 @@
|
|||||||
"nixpkgs": "nixpkgs_3"
|
"nixpkgs": "nixpkgs_3"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1787522765,
|
"lastModified": 1790803867,
|
||||||
"narHash": "sha256-D3KOrGcAH07lvKYgyiwejOQpDW91js7zI6l87WdabLk=",
|
"narHash": "sha256-HRGQQlmn/kCsZIq1wBMFaW6q1xfSvwPhUS5CpG1mm+Q=",
|
||||||
"ref": "main",
|
"ref": "refs/tags/1.8",
|
||||||
"rev": "63265cc22f3055a782a0114e92c57a36bef723b2",
|
"rev": "34c72b51ddc3da8d46d994e787ffd1f8e8dda77c",
|
||||||
"revCount": 40,
|
"revCount": 85,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git"
|
"url": "https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"ref": "main",
|
"ref": "refs/tags/1.8",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git"
|
"url": "https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git"
|
||||||
}
|
}
|
||||||
@@ -240,11 +240,11 @@
|
|||||||
"nixpkgs": "nixpkgs_4"
|
"nixpkgs": "nixpkgs_4"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1787522765,
|
"lastModified": 1787943320,
|
||||||
"narHash": "sha256-D3KOrGcAH07lvKYgyiwejOQpDW91js7zI6l87WdabLk=",
|
"narHash": "sha256-zoHqtwO0DV3z/N311IxiMLrbyQHrw7HIoL1FQW5pGDk=",
|
||||||
"ref": "dev",
|
"ref": "dev",
|
||||||
"rev": "63265cc22f3055a782a0114e92c57a36bef723b2",
|
"rev": "2917d33866f052eca1e9daea175d95382a9087f2",
|
||||||
"revCount": 40,
|
"revCount": 50,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git"
|
"url": "https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
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";
|
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";
|
radiantwave-dev.url = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git?ref=dev";
|
||||||
nixos-raspberrypi.url = "github:nvmd/nixos-raspberrypi/main";
|
nixos-raspberrypi.url = "github:nvmd/nixos-raspberrypi/main";
|
||||||
};
|
};
|
||||||
@@ -28,33 +28,87 @@
|
|||||||
# 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=1280 \
|
--width=1280 \
|
||||||
--height=720 \
|
--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;
|
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-dev; # dev
|
||||||
|
atticRepo = "radiantwave-dev"; # dev
|
||||||
|
gitRef = "?ref=dev"; # dev
|
||||||
|
appVersion = "2917d33"; # dev
|
||||||
|
};
|
||||||
|
|
||||||
|
# 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";
|
||||||
|
|
||||||
|
# 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 = {
|
mkKioskSystem = {
|
||||||
system,
|
system,
|
||||||
radiantwave,
|
radiantwave,
|
||||||
@@ -67,9 +121,9 @@
|
|||||||
inherit system;
|
inherit system;
|
||||||
|
|
||||||
specialArgs = {
|
specialArgs = {
|
||||||
inherit radiantwave;
|
inherit radiantwave upgradeFlakeRef;
|
||||||
inherit upgradeFlakeRef;
|
radiantwaveApp = appPackage system;
|
||||||
inherit radiantwavePlymouth;
|
radiantwavePlymouth = mkRadiantwavePlymouth;
|
||||||
};
|
};
|
||||||
|
|
||||||
modules = [
|
modules = [
|
||||||
@@ -79,6 +133,7 @@
|
|||||||
system.configurationRevision = revision;
|
system.configurationRevision = revision;
|
||||||
}
|
}
|
||||||
./configuration.nix
|
./configuration.nix
|
||||||
|
./radiantwave-updater.nix
|
||||||
disko.nixosModules.disko
|
disko.nixosModules.disko
|
||||||
diskModule
|
diskModule
|
||||||
nixos-facter-modules.nixosModules.facter
|
nixos-facter-modules.nixosModules.facter
|
||||||
@@ -103,10 +158,12 @@
|
|||||||
specialArgs = {
|
specialArgs = {
|
||||||
inherit radiantwave upgradeFlakeRef;
|
inherit radiantwave upgradeFlakeRef;
|
||||||
inherit nixos-raspberrypi;
|
inherit nixos-raspberrypi;
|
||||||
inherit radiantwavePlymouth;
|
radiantwaveApp = appPackage "aarch64-linux";
|
||||||
|
radiantwavePlymouth = mkRadiantwavePlymouth;
|
||||||
};
|
};
|
||||||
|
|
||||||
modules = [
|
modules = [
|
||||||
|
{ nixpkgs.config.allowUnfree = true; }
|
||||||
({ ... }: {
|
({ ... }: {
|
||||||
imports = with nixos-raspberrypi.nixosModules; [
|
imports = with nixos-raspberrypi.nixosModules; [
|
||||||
sd-image
|
sd-image
|
||||||
@@ -116,6 +173,7 @@
|
|||||||
];
|
];
|
||||||
})
|
})
|
||||||
./configuration.nix
|
./configuration.nix
|
||||||
|
./radiantwave-updater.nix
|
||||||
radiantwave.nixosModules.default
|
radiantwave.nixosModules.default
|
||||||
{
|
{
|
||||||
networking.hostName = hostName;
|
networking.hostName = hostName;
|
||||||
@@ -166,82 +224,76 @@
|
|||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
packages.x86_64-linux.radiantwavekiosk = self.nixosConfigurations.radiantwavekiosk.config.system.build.toplevel;
|
inherit channelName;
|
||||||
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-dev = self.nixosConfigurations.radiantwavekiosk-commercial-dev.config.system.build.toplevel;
|
|
||||||
packages.x86_64-linux.radiantwavekiosk-android = self.nixosConfigurations.radiantwavekiosk-android.config.system.build.toplevel;
|
|
||||||
packages.x86_64-linux.radiantwavekiosk-android-dev = self.nixosConfigurations.radiantwavekiosk-android-dev.config.system.build.toplevel;
|
|
||||||
packages.x86_64-linux.radiantwavekiosk-rpi = self.nixosConfigurations.radiantwavekiosk-rpi.config.system.build.toplevel;
|
|
||||||
packages.x86_64-linux.radiantwavekiosk-rpi-dev = self.nixosConfigurations.radiantwavekiosk-rpi-dev.config.system.build.toplevel;
|
|
||||||
|
|
||||||
# Kiosk systems.
|
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.
|
||||||
#
|
#
|
||||||
# Production variants consume the `radiantwave` input (?ref=main);
|
# Every config consumes the channel selected at the top of this file
|
||||||
# `-dev` variants consume `radiantwave-dev` (?ref=dev) and track the
|
# (see `channel`): the RadiantWave app input, the Attic repo/manifest
|
||||||
# dev branch of this repo via their autoUpgrade flake refs.
|
# 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;
|
radiantwave = channel.appInput;
|
||||||
hostName = "radiantwavekiosk";
|
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 = [
|
||||||
radiantwavekiosk-dev = mkKioskSystem {
|
(radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = appPackage "x86_64-linux"; })
|
||||||
system = "x86_64-linux";
|
];
|
||||||
radiantwave = radiantwave-dev;
|
|
||||||
hostName = "radiantwavekiosk-dev";
|
|
||||||
diskModule = ./disk-config.nix;
|
|
||||||
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git?ref=dev#radiantwavekiosk-dev";
|
|
||||||
};
|
};
|
||||||
radiantwavekiosk-commercial = mkKioskSystem {
|
radiantwavekiosk-commercial = mkKioskSystem {
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
radiantwave = radiantwave;
|
radiantwave = channel.appInput;
|
||||||
hostName = "radiantwavekiosk-commercial";
|
hostName = "radiantwavekiosk-commercial";
|
||||||
diskModule = ./commercial-disk-config.nix;
|
diskModule = ./commercial-disk-config.nix;
|
||||||
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git#radiantwavekiosk-commercial";
|
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-commercial";
|
||||||
};
|
extraModules = [
|
||||||
radiantwavekiosk-commercial-dev = mkKioskSystem {
|
(radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = appPackage "x86_64-linux"; })
|
||||||
system = "x86_64-linux";
|
];
|
||||||
radiantwave = radiantwave-dev;
|
|
||||||
hostName = "radiantwavekiosk-commercial-dev";
|
|
||||||
diskModule = ./commercial-disk-config.nix;
|
|
||||||
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git?ref=dev#radiantwavekiosk-commercial-dev";
|
|
||||||
};
|
};
|
||||||
# Rotated screen for android-style displays.
|
# Rotated screen for android-style displays.
|
||||||
radiantwavekiosk-android = mkKioskSystem {
|
radiantwavekiosk-android = mkKioskSystem {
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
radiantwave = radiantwave;
|
radiantwave = channel.appInput;
|
||||||
hostName = "radiantwavekiosk-android";
|
hostName = "radiantwavekiosk-android";
|
||||||
diskModule = ./disk-config.nix;
|
diskModule = ./disk-config.nix;
|
||||||
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git#radiantwavekiosk-android";
|
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-android";
|
||||||
extraModules = [./rotated_display.nix];
|
extraModules = [
|
||||||
};
|
./rotated_display.nix
|
||||||
radiantwavekiosk-android-dev = mkKioskSystem {
|
(radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = appPackage "x86_64-linux"; })
|
||||||
system = "x86_64-linux";
|
];
|
||||||
radiantwave = radiantwave-dev;
|
|
||||||
hostName = "radiantwavekiosk-android-dev";
|
|
||||||
diskModule = ./disk-config.nix;
|
|
||||||
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git?ref=dev#radiantwavekiosk-android-dev";
|
|
||||||
extraModules = [./rotated_display.nix];
|
|
||||||
};
|
};
|
||||||
# Raspberry Pi 5.
|
# Raspberry Pi 5.
|
||||||
radiantwavekiosk-rpi = mkRpiKioskSystem {
|
radiantwavekiosk-rpi = mkRpiKioskSystem {
|
||||||
radiantwave = radiantwave;
|
radiantwave = channel.appInput;
|
||||||
hostName = "radiantwavekiosk-rpi";
|
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 = [
|
||||||
radiantwavekiosk-rpi-dev = mkRpiKioskSystem {
|
(radiantwaveUpdaterModule {
|
||||||
radiantwave = radiantwave-dev;
|
repo = channel.atticRepo;
|
||||||
hostName = "radiantwavekiosk-rpi-dev";
|
baseline = appPackage "aarch64-linux";
|
||||||
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git?ref=dev#radiantwavekiosk-rpi-dev";
|
manifestFile = "aarch64-manifest.json";
|
||||||
|
})
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
# === USB Images ===
|
# === USB Images ===
|
||||||
# Each ISO installs exactly the named system closure using that
|
# Each ISO installs exactly the named system closure using that
|
||||||
# system's disk layout. NVMe layouts pair with ./disk-config.nix,
|
# system's disk layout. NVMe layouts pair with ./disk-config.nix,
|
||||||
# SATA with ./commercial-disk-config.nix — don't cross them.
|
# SATA with ./commercial-disk-config.nix — don't cross them.
|
||||||
|
# The same names build dev-flavored images when the flake is checked
|
||||||
|
# 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 {
|
kiosk-iso = mkIso {
|
||||||
targetConfig = "radiantwavekiosk";
|
targetConfig = "radiantwavekiosk";
|
||||||
diskModule = ./disk-config.nix;
|
diskModule = ./disk-config.nix;
|
||||||
@@ -254,18 +306,6 @@
|
|||||||
targetConfig = "radiantwavekiosk-android";
|
targetConfig = "radiantwavekiosk-android";
|
||||||
diskModule = ./disk-config.nix;
|
diskModule = ./disk-config.nix;
|
||||||
};
|
};
|
||||||
dev-iso = mkIso {
|
|
||||||
targetConfig = "radiantwavekiosk-dev";
|
|
||||||
diskModule = ./disk-config.nix;
|
|
||||||
};
|
|
||||||
commercial-dev-iso = mkIso {
|
|
||||||
targetConfig = "radiantwavekiosk-commercial-dev";
|
|
||||||
diskModule = ./commercial-disk-config.nix;
|
|
||||||
};
|
|
||||||
android-dev-iso = mkIso {
|
|
||||||
targetConfig = "radiantwavekiosk-android-dev";
|
|
||||||
diskModule = ./disk-config.nix;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.")
|
||||||
@@ -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).";
|
||||||
|
};
|
||||||
|
|
||||||
|
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";
|
||||||
|
# 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}"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
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
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
set -uo pipefail # no -e: we want to keep going past unreachable hosts
|
set -uo pipefail # no -e: we want to keep going past unreachable hosts
|
||||||
|
|
||||||
SUBNET="${1:-192.168.4.0/24}"
|
SUBNET="${1:-192.168.1.0/24}"
|
||||||
SSH_USER="kiosk"
|
SSH_USER="kiosk"
|
||||||
SSH_OPTS=(-o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new)
|
SSH_OPTS=(-o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user