Compare commits
105
Commits
2abdfa2b9b
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0f4caa00b | ||
|
|
c3d4898d84 | ||
|
|
a3e6da7053 | ||
|
|
48bab43a21 | ||
|
|
f74d8cb708 | ||
|
|
d54637d249 | ||
|
|
a33a586485 | ||
|
|
7aa88ee174 | ||
|
|
31bc806d01 | ||
|
|
eafa4b6e26 | ||
|
|
44d402b179 | ||
|
|
1e996d4c05 | ||
|
|
d17def994f | ||
|
|
e50391ba9e | ||
|
|
1ec9fd81e5 | ||
|
|
6fa3c0552b | ||
|
|
8796feddd9 | ||
|
|
c684387f60 | ||
|
|
b6de748f8f | ||
|
|
cac2e455ec | ||
|
|
85788ce32d | ||
|
|
e6138d398d | ||
|
|
c720c67975 | ||
|
|
6e446893f3 | ||
|
|
0860dea6c7 | ||
|
|
fc7edc0d22 | ||
|
|
187581b52a | ||
|
|
46cc6ef5f5 | ||
|
|
c18e158b69 | ||
|
|
b136d1751b | ||
|
|
05bb98b107 | ||
|
|
ca89c234ab | ||
|
|
9a8a258228 | ||
|
|
7d8cc56ba0 | ||
|
|
e44626feec | ||
|
|
48c31d0f0c | ||
|
|
d661162676 | ||
|
|
b8d0f59c0a | ||
|
|
4e36113636 | ||
|
|
2bf762acac | ||
|
|
46a61a8397 | ||
|
|
c1ad4a9767 | ||
|
|
47dc7b8964 | ||
|
|
dfa7320a37 | ||
|
|
a8f78b282d | ||
|
|
87fca854f4 | ||
|
|
cf7129b888 | ||
|
|
c8298a128f | ||
|
|
0be0b01d73 | ||
|
|
036c437430 | ||
|
|
2a6df4bbc4 | ||
|
|
8775fcfb24 | ||
|
|
f166b25364 | ||
|
|
f900585702 | ||
|
|
16b8e3aeb9 | ||
|
|
7f9566e52c | ||
|
|
45b4d7aa95 | ||
|
|
6d3136c9cb | ||
|
|
bd9146e641 | ||
|
|
9b0036b4da | ||
|
|
d26cec2a53 | ||
|
|
2e862c13a8 | ||
|
|
84ec13b1f9 | ||
|
|
588f0e4aa1 | ||
|
|
46384e6cf7 | ||
|
|
6951d35310 | ||
|
|
7778da3867 | ||
|
|
59e9f7ce29 | ||
|
|
3ec72c8479 | ||
|
|
20d91df9eb | ||
|
|
65a6b256f9 | ||
|
|
64b2a9cfc4 | ||
|
|
875f3da03d | ||
|
|
9965e30152 | ||
|
|
a01c3d5521 | ||
|
|
33c9ceefb0 | ||
|
|
c9743de7bb | ||
|
|
6ddf004e95 | ||
|
|
439e646170 | ||
|
|
f8cb1746af | ||
|
|
a4f4d17d6e | ||
|
|
e7a5ffa290 | ||
|
|
553b06ea92 | ||
|
|
8e8da490e4 | ||
|
|
75cce42de5 | ||
|
|
572ab76100 | ||
|
|
d37c794be4 | ||
|
|
b26081046e | ||
|
|
87367dab04 | ||
|
|
0a35c97379 | ||
|
|
2fb99175f7 | ||
|
|
02cde0fabb | ||
|
|
143119477f | ||
|
|
b584016db2 | ||
|
|
e09758a70e | ||
|
|
14463ad99b | ||
|
|
027d1a628f | ||
|
|
fb2f861258 | ||
|
|
974a610518 | ||
|
|
c71b5ebc84 | ||
|
|
6656c44de4 | ||
|
|
2788d27fdc | ||
|
|
72a564c9ef | ||
|
|
2185792cf4 | ||
|
|
3b9c501aa8 |
@@ -1,50 +0,0 @@
|
|||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update-and-build:
|
|
||||||
runs-on: build-server
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Update radiantwave-dev
|
|
||||||
run: |
|
|
||||||
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
|
||||||
nix flake update radiantwave-dev
|
|
||||||
|
|
||||||
- name: Prepare output directory
|
|
||||||
run: mkdir -p /home/user/radiantwave
|
|
||||||
|
|
||||||
# --- Kiosk ISO ---
|
|
||||||
- name: Build kiosk ISO
|
|
||||||
run: |
|
|
||||||
export NIXPKGS_ALLOW_UNFREE=1
|
|
||||||
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
|
||||||
nix build -L --impure .#nixosConfigurations.dev-iso.config.system.build.isoImage
|
|
||||||
|
|
||||||
- name: Rename kiosk ISO
|
|
||||||
run: |
|
|
||||||
ISO_DATE=$(date +%Y-%m-%d_%H-%M-%S)
|
|
||||||
FILENAME="radiantwave-kiosk-dev-iso-${ISO_DATE}.iso"
|
|
||||||
cp -L result/iso/*.iso "/home/user/radiantwave/${FILENAME}"
|
|
||||||
echo "KIOSK_ISO_FILENAME=${FILENAME}" >> "$GITHUB_ENV"
|
|
||||||
|
|
||||||
- name: Copy kiosk ISO to fileserver
|
|
||||||
run: |
|
|
||||||
scp "/home/user/radiantwave/${KIOSK_ISO_FILENAME}" \
|
|
||||||
radiantwave-infra:/mnt/infrastructure_data/files/images/
|
|
||||||
|
|
||||||
- name: Cleanup kiosk ISO
|
|
||||||
run: rm -f "/home/user/radiantwave/${KIOSK_ISO_FILENAME}"
|
|
||||||
|
|
||||||
- name: Commit updated flake.lock
|
|
||||||
run: |
|
|
||||||
git config user.email "ci@radiantwavetech.com"
|
|
||||||
git config user.name "RadiantWave CI"
|
|
||||||
git remote set-url origin https://oauth2:${{ secrets.GIT_TOKEN }}@gitea.radiantwavetech.com/RadiantWaveTech/kioskconfig.git
|
|
||||||
|
|
||||||
git add flake.lock
|
|
||||||
git commit -m "chore: update radiantwave-dev to latest" || echo "Nothing to commit"
|
|
||||||
git push
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update-and-build:
|
|
||||||
runs-on: build-server
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Update radiantwave
|
|
||||||
run: |
|
|
||||||
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
|
||||||
nix flake update radiantwave
|
|
||||||
|
|
||||||
- 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.kiosk-iso.config.system.build.isoImage
|
|
||||||
|
|
||||||
- name: Rename kiosk ISO
|
|
||||||
run: |
|
|
||||||
ISO_DATE=$(date +%Y-%m-%d_%H-%M-%S)
|
|
||||||
FILENAME="radiantwave-iso-${ISO_DATE}.iso"
|
|
||||||
cp -L result/iso/*.iso "/home/user/radiantwave/${FILENAME}"
|
|
||||||
echo "KIOSK_ISO_FILENAME=${FILENAME}" >> "$GITHUB_ENV"
|
|
||||||
|
|
||||||
- name: Copy kiosk ISO to fileserver
|
|
||||||
run: |
|
|
||||||
scp "/home/user/radiantwave/${KIOSK_ISO_FILENAME}" \
|
|
||||||
radiantwave-infra:/mnt/infrastructure_data/files/images/
|
|
||||||
|
|
||||||
- name: Cleanup kiosk ISO
|
|
||||||
run: rm -f "/home/user/radiantwave/${KIOSK_ISO_FILENAME}"
|
|
||||||
|
|
||||||
# --- Android Kiosk ISO ---
|
|
||||||
- name: Build android kiosk ISO
|
|
||||||
run: |
|
|
||||||
export NIXPKGS_ALLOW_UNFREE=1
|
|
||||||
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
|
||||||
nix build -L --impure .#nixosConfigurations.android-iso.config.system.build.isoImage
|
|
||||||
|
|
||||||
- name: Rename android kiosk ISO
|
|
||||||
run: |
|
|
||||||
ISO_DATE=$(date +%Y-%m-%d_%H-%M-%S)
|
|
||||||
FILENAME="radiantwave-android-iso-${ISO_DATE}.iso"
|
|
||||||
cp -L result/iso/*.iso "/home/user/radiantwave/${FILENAME}"
|
|
||||||
echo "ANDROID_ISO_FILENAME=${FILENAME}" >> "$GITHUB_ENV"
|
|
||||||
|
|
||||||
- name: Copy android kiosk ISO to fileserver
|
|
||||||
run: |
|
|
||||||
scp "/home/user/radiantwave/${ANDROID_ISO_FILENAME}" \
|
|
||||||
radiantwave-infra:/mnt/infrastructure_data/files/images/
|
|
||||||
|
|
||||||
- name: Cleanup android kiosk ISO
|
|
||||||
run: rm -f "/home/user/radiantwave/${ANDROID_ISO_FILENAME}"
|
|
||||||
|
|
||||||
# --- Commercial Kiosk ISO ---
|
|
||||||
- name: Build commercial kiosk ISO
|
|
||||||
run: |
|
|
||||||
export NIXPKGS_ALLOW_UNFREE=1
|
|
||||||
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
|
||||||
nix build -L --impure .#nixosConfigurations.commercial-iso.config.system.build.isoImage
|
|
||||||
|
|
||||||
- name: Rename commercial kiosk ISO
|
|
||||||
run: |
|
|
||||||
ISO_DATE=$(date +%Y-%m-%d_%H-%M-%S)
|
|
||||||
FILENAME="radiantwave-commercial-iso-${ISO_DATE}.iso"
|
|
||||||
cp -L result/iso/*.iso "/home/user/radiantwave/${FILENAME}"
|
|
||||||
echo "COMMERCIAL_ISO_FILENAME=${FILENAME}" >> "$GITHUB_ENV"
|
|
||||||
|
|
||||||
- name: Copy commercial kiosk ISO to fileserver
|
|
||||||
run: |
|
|
||||||
scp "/home/user/radiantwave/${COMMERCIAL_ISO_FILENAME}" \
|
|
||||||
radiantwave-infra:/mnt/infrastructure_data/files/images/
|
|
||||||
|
|
||||||
- name: Cleanup commercial kiosk ISO
|
|
||||||
run: rm -f "/home/user/radiantwave/${COMMERCIAL_ISO_FILENAME}"
|
|
||||||
|
|
||||||
# --- Raspberry Pi image (disabled for speed, for now) ---
|
|
||||||
# - name: Build Raspberry Pi image
|
|
||||||
# run: |
|
|
||||||
# export NIXPKGS_ALLOW_UNFREE=1
|
|
||||||
# . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
|
||||||
# nix build -L --impure .#nixosConfigurations.radiantwavekiosk-rpi.config.system.build.sdImage
|
|
||||||
#
|
|
||||||
# - name: Rename Raspberry Pi image
|
|
||||||
# run: |
|
|
||||||
# ISO_DATE=$(date +%Y-%m-%d_%H-%M-%S)
|
|
||||||
# FILENAME="radiantwave-rpi-${ISO_DATE}.img.zst"
|
|
||||||
# cp -L result/sd-image/*.img.zst "/home/user/radiantwave/${FILENAME}"
|
|
||||||
# echo "RPI_IMAGE_FILENAME=${FILENAME}" >> "$GITHUB_ENV"
|
|
||||||
#
|
|
||||||
# - name: Copy Raspberry Pi image to fileserver
|
|
||||||
# run: |
|
|
||||||
# scp "/home/user/radiantwave/${RPI_IMAGE_FILENAME}" \
|
|
||||||
# radiantwave-infra:/mnt/infrastructure_data/files/images/
|
|
||||||
#
|
|
||||||
# - name: Cleanup Raspberry Pi image
|
|
||||||
# run: rm -f "/home/user/radiantwave/${RPI_IMAGE_FILENAME}"
|
|
||||||
|
|
||||||
- name: Commit updated flake.lock
|
|
||||||
run: |
|
|
||||||
git config user.email "ci@radiantwavetech.com"
|
|
||||||
git config user.name "RadiantWave CI"
|
|
||||||
git remote set-url origin https://oauth2:${{ secrets.GIT_TOKEN }}@gitea.radiantwavetech.com/RadiantWaveTech/kioskconfig.git
|
|
||||||
|
|
||||||
git add flake.lock
|
|
||||||
git commit -m "chore: update radiantwave-dev to latest" || echo "Nothing to commit"
|
|
||||||
git push
|
|
||||||
|
|
||||||
# - name: Notify
|
|
||||||
# if: always()
|
|
||||||
# uses: https://github.com/dawidd6/action-send-mail@v3
|
|
||||||
# with:
|
|
||||||
# server_address: smtp.gmail.com
|
|
||||||
# server_port: 587
|
|
||||||
# username: ${{ secrets.MAIL_USERNAME }}
|
|
||||||
# password: ${{ secrets.MAIL_PASSWORD }}
|
|
||||||
# subject: "${{ job.status == 'success' && '✅' || '❌' }} KioskConfig main build ${{ job.status }}"
|
|
||||||
# to: nclack@radiantwavetech.com
|
|
||||||
# from: ci@radiantwavetech.com
|
|
||||||
# body: "KioskConfig main build ${{ job.status }} — radiantwave updated and artifacts copied."
|
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
# One build workflow per hardware family, identical on every branch — do not
|
||||||
|
# add per-branch copies. build-x86.yaml is the same file with a different matrix.
|
||||||
|
#
|
||||||
|
# The branch/ref selects the channel, nothing else does: the flake's `channel`
|
||||||
|
# block picks the channel manifest whose closure is baked as the app, the Attic
|
||||||
|
# repo, and the upgrade ref, so the same config names build dev images on `dev`
|
||||||
|
# and prod images on `main`.
|
||||||
|
#
|
||||||
|
# Trigger model:
|
||||||
|
# * push to `dev` -> build every development image
|
||||||
|
# * new git tag -> build every production image (tags live on main)
|
||||||
|
# * manual dispatch -> build the channel of the selected branch
|
||||||
|
#
|
||||||
|
# This file builds the Raspberry Pi variant:
|
||||||
|
# Raspberry Pi -> .config.system.build.sdImage
|
||||||
|
# `orion-lxc` emulates aarch64-linux, so this runs on the same runner.
|
||||||
|
name: Build Kiosk Image (Raspberry Pi)
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Both channel caches plus the Raspberry Pi cachix, so a build can substitute
|
||||||
|
# from whichever channel the checked-out ref selects. tarball-ttl=0 keeps the
|
||||||
|
# eval-time manifest fetch (builtins.fetchurl in flake.nix) from reusing a
|
||||||
|
# stale copy cached by an earlier build on this persistent runner.
|
||||||
|
NIX_CONFIG: |
|
||||||
|
extra-substituters = https://attic.radiantwavetech.com/radiantwave https://attic.radiantwavetech.com/radiantwave-dev https://nixos-raspberrypi.cachix.org
|
||||||
|
extra-trusted-public-keys = radiantwave:QADrjpBWJssTZ3KmiZtcWEf4uAm66oOlHmtQI3jkrgA= radiantwave-dev:B8FhLu3MQ9JfeEIHIKNrKxnP+3akzzkqMbTZR4hRfj0= nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI=
|
||||||
|
tarball-ttl = 0
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: ${{ matrix.label }}
|
||||||
|
runs-on: orion-lxc
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- label: rpi
|
||||||
|
system: radiantwavekiosk-rpi
|
||||||
|
target: sdImage
|
||||||
|
outdir: sd-image
|
||||||
|
ext: img.zst
|
||||||
|
arch: aarch64-linux
|
||||||
|
manifest: aarch64-manifest.json
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Clone repository
|
||||||
|
run: |
|
||||||
|
rm -rf ./* ./.[!.]* 2>/dev/null || true
|
||||||
|
ref="${GITHUB_REF#refs/heads/}"
|
||||||
|
ref="${ref#refs/tags/}"
|
||||||
|
git clone --depth 1 --branch "$ref" \
|
||||||
|
ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/KioskConfig.git .
|
||||||
|
git log --oneline -1
|
||||||
|
|
||||||
|
- name: Determine channel
|
||||||
|
run: |
|
||||||
|
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
||||||
|
CHANNEL=$(nix eval --impure --raw .#channelName)
|
||||||
|
if [ "$CHANNEL" = "radiantwave" ]; then LABEL=prod; else LABEL=dev; fi
|
||||||
|
echo "CHANNEL=$CHANNEL" >> "$GITHUB_ENV"
|
||||||
|
echo "CHANNEL_LABEL=$LABEL" >> "$GITHUB_ENV"
|
||||||
|
echo "Building the '$CHANNEL' channel ($LABEL)"
|
||||||
|
|
||||||
|
# Fail fast if the closure the channel manifest announces is not in the
|
||||||
|
# channel's Attic cache. We bake that exact storePath, so without it the
|
||||||
|
# image would try to compile RadiantWave from source (which never works:
|
||||||
|
# this repo has no app source). Runs for both channels; RadiantWave CI
|
||||||
|
# publishes the manifest only after the matching Attic push succeeds.
|
||||||
|
- name: Verify published app closure is in the channel cache
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
||||||
|
|
||||||
|
ATTIC=$(nix eval --impure --raw .#channelAtticUrl)
|
||||||
|
MANIFEST_URL="$(nix eval --impure --raw .#channelManifestDir)/${{ matrix.manifest }}"
|
||||||
|
MANIFEST=$(curl -fsSL "$MANIFEST_URL")
|
||||||
|
PUB_PATH=$(MANIFEST="$MANIFEST" nix eval --impure --raw --expr '(builtins.fromJSON (builtins.getEnv "MANIFEST")).storePath')
|
||||||
|
|
||||||
|
echo "channel : $CHANNEL"
|
||||||
|
echo "attic : $ATTIC"
|
||||||
|
echo "manifest: $MANIFEST_URL"
|
||||||
|
echo "closure : $PUB_PATH"
|
||||||
|
|
||||||
|
HASH=$(basename "$PUB_PATH" | cut -d- -f1)
|
||||||
|
if ! curl -fsS -o /dev/null "$ATTIC/${HASH}.narinfo"; then
|
||||||
|
echo "ERROR: $PUB_PATH is not in $ATTIC; the image would compile RadiantWave from source."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Prepare output directory
|
||||||
|
run: mkdir -p /var/lib/gitea-runner/radiantwave
|
||||||
|
|
||||||
|
- name: Build ${{ matrix.label }}
|
||||||
|
run: |
|
||||||
|
export NIXPKGS_ALLOW_UNFREE=1
|
||||||
|
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
||||||
|
nix build -L --impure \
|
||||||
|
".#nixosConfigurations.${{ matrix.system }}.config.system.build.${{ matrix.target }}"
|
||||||
|
|
||||||
|
- name: Publish ${{ matrix.label }}
|
||||||
|
run: |
|
||||||
|
ISO_DATE=$(date +%Y-%m-%d_%H-%M-%S)
|
||||||
|
FILENAME="radiantwave-${{ matrix.label }}-${CHANNEL_LABEL}-${ISO_DATE}.${{ matrix.ext }}"
|
||||||
|
cp -L result/${{ matrix.outdir }}/*.${{ matrix.ext }} \
|
||||||
|
"/var/lib/gitea-runner/radiantwave/${FILENAME}"
|
||||||
|
scp "/var/lib/gitea-runner/radiantwave/${FILENAME}" \
|
||||||
|
157.245.124.103:/mnt/infrastructure_data/files/images/
|
||||||
|
rm -f "/var/lib/gitea-runner/radiantwave/${FILENAME}"
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
# One build workflow per hardware family, identical on every branch — do not
|
||||||
|
# add per-branch copies. build-rpi.yaml is the same file with a different matrix.
|
||||||
|
#
|
||||||
|
# The branch/ref selects the channel, nothing else does: the flake's `channel`
|
||||||
|
# block picks the channel manifest whose closure is baked as the app, the Attic
|
||||||
|
# repo, and the upgrade ref, so the same config names build dev images on `dev`
|
||||||
|
# and prod images on `main`.
|
||||||
|
#
|
||||||
|
# Trigger model:
|
||||||
|
# * push to `dev` -> build every development image
|
||||||
|
# * new git tag -> build every production image (tags live on main)
|
||||||
|
# * manual dispatch -> build the channel of the selected branch
|
||||||
|
#
|
||||||
|
# This file builds the x86_64 ISO variants:
|
||||||
|
# ISO variants -> .config.system.build.isoImage (storeContents pulls in the closure)
|
||||||
|
name: Build Kiosk Images (x86)
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Both channel caches plus the Raspberry Pi cachix, so a build can substitute
|
||||||
|
# from whichever channel the checked-out ref selects. tarball-ttl=0 keeps the
|
||||||
|
# eval-time manifest fetch (builtins.fetchurl in flake.nix) from reusing a
|
||||||
|
# stale copy cached by an earlier build on this persistent runner.
|
||||||
|
NIX_CONFIG: |
|
||||||
|
extra-substituters = https://attic.radiantwavetech.com/radiantwave https://attic.radiantwavetech.com/radiantwave-dev https://nixos-raspberrypi.cachix.org
|
||||||
|
extra-trusted-public-keys = radiantwave:QADrjpBWJssTZ3KmiZtcWEf4uAm66oOlHmtQI3jkrgA= radiantwave-dev:B8FhLu3MQ9JfeEIHIKNrKxnP+3akzzkqMbTZR4hRfj0= nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI=
|
||||||
|
tarball-ttl = 0
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: ${{ matrix.label }}
|
||||||
|
runs-on: orion-lxc
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
# NOTE: Gitea schedules matrix jobs in reverse definition order on
|
||||||
|
# the single orion-lxc runner, so the list is reversed on purpose:
|
||||||
|
# android -> commercial -> kiosk means kiosk builds FIRST.
|
||||||
|
- label: android
|
||||||
|
system: android-iso
|
||||||
|
target: isoImage
|
||||||
|
outdir: iso
|
||||||
|
ext: iso
|
||||||
|
arch: x86_64-linux
|
||||||
|
manifest: manifest.json
|
||||||
|
- label: commercial
|
||||||
|
system: commercial-iso
|
||||||
|
target: isoImage
|
||||||
|
outdir: iso
|
||||||
|
ext: iso
|
||||||
|
arch: x86_64-linux
|
||||||
|
manifest: manifest.json
|
||||||
|
- label: kiosk
|
||||||
|
system: kiosk-iso
|
||||||
|
target: isoImage
|
||||||
|
outdir: iso
|
||||||
|
ext: iso
|
||||||
|
arch: x86_64-linux
|
||||||
|
manifest: manifest.json
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Clone repository
|
||||||
|
run: |
|
||||||
|
rm -rf ./* ./.[!.]* 2>/dev/null || true
|
||||||
|
ref="${GITHUB_REF#refs/heads/}"
|
||||||
|
ref="${ref#refs/tags/}"
|
||||||
|
git clone --depth 1 --branch "$ref" \
|
||||||
|
ssh://git@gitea.radiantwavetech.com:2222/RadiantWaveTech/KioskConfig.git .
|
||||||
|
git log --oneline -1
|
||||||
|
|
||||||
|
- name: Determine channel
|
||||||
|
run: |
|
||||||
|
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
||||||
|
CHANNEL=$(nix eval --impure --raw .#channelName)
|
||||||
|
if [ "$CHANNEL" = "radiantwave" ]; then LABEL=prod; else LABEL=dev; fi
|
||||||
|
echo "CHANNEL=$CHANNEL" >> "$GITHUB_ENV"
|
||||||
|
echo "CHANNEL_LABEL=$LABEL" >> "$GITHUB_ENV"
|
||||||
|
echo "Building the '$CHANNEL' channel ($LABEL)"
|
||||||
|
|
||||||
|
# Fail fast if the closure the channel manifest announces is not in the
|
||||||
|
# channel's Attic cache. We bake that exact storePath, so without it the
|
||||||
|
# image would try to compile RadiantWave from source (which never works:
|
||||||
|
# this repo has no app source). Runs for both channels; RadiantWave CI
|
||||||
|
# publishes the manifest only after the matching Attic push succeeds.
|
||||||
|
- name: Verify published app closure is in the channel cache
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
||||||
|
|
||||||
|
ATTIC=$(nix eval --impure --raw .#channelAtticUrl)
|
||||||
|
MANIFEST_URL="$(nix eval --impure --raw .#channelManifestDir)/${{ matrix.manifest }}"
|
||||||
|
MANIFEST=$(curl -fsSL "$MANIFEST_URL")
|
||||||
|
PUB_PATH=$(MANIFEST="$MANIFEST" nix eval --impure --raw --expr '(builtins.fromJSON (builtins.getEnv "MANIFEST")).storePath')
|
||||||
|
|
||||||
|
echo "channel : $CHANNEL"
|
||||||
|
echo "attic : $ATTIC"
|
||||||
|
echo "manifest: $MANIFEST_URL"
|
||||||
|
echo "closure : $PUB_PATH"
|
||||||
|
|
||||||
|
HASH=$(basename "$PUB_PATH" | cut -d- -f1)
|
||||||
|
if ! curl -fsS -o /dev/null "$ATTIC/${HASH}.narinfo"; then
|
||||||
|
echo "ERROR: $PUB_PATH is not in $ATTIC; the image would compile RadiantWave from source."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Prepare output directory
|
||||||
|
run: mkdir -p /var/lib/gitea-runner/radiantwave
|
||||||
|
|
||||||
|
- name: Build ${{ matrix.label }}
|
||||||
|
run: |
|
||||||
|
export NIXPKGS_ALLOW_UNFREE=1
|
||||||
|
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
||||||
|
nix build -L --impure \
|
||||||
|
".#nixosConfigurations.${{ matrix.system }}.config.system.build.${{ matrix.target }}"
|
||||||
|
|
||||||
|
- name: Publish ${{ matrix.label }}
|
||||||
|
run: |
|
||||||
|
ISO_DATE=$(date +%Y-%m-%d_%H-%M-%S)
|
||||||
|
FILENAME="radiantwave-${{ matrix.label }}-${CHANNEL_LABEL}-${ISO_DATE}.${{ matrix.ext }}"
|
||||||
|
cp -L result/${{ matrix.outdir }}/*.${{ matrix.ext }} \
|
||||||
|
"/var/lib/gitea-runner/radiantwave/${FILENAME}"
|
||||||
|
scp "/var/lib/gitea-runner/radiantwave/${FILENAME}" \
|
||||||
|
157.245.124.103:/mnt/infrastructure_data/files/images/
|
||||||
|
rm -f "/var/lib/gitea-runner/radiantwave/${FILENAME}"
|
||||||
@@ -4,3 +4,5 @@ build/out/
|
|||||||
.direnv
|
.direnv
|
||||||
wifi-psk.txt
|
wifi-psk.txt
|
||||||
*.qcow2
|
*.qcow2
|
||||||
|
VERIFY-DEV.md
|
||||||
|
VERIFY-PROD.md
|
||||||
|
|||||||
@@ -2,72 +2,120 @@
|
|||||||
|
|
||||||
## What this repo is
|
## What this repo is
|
||||||
|
|
||||||
NixOS flake that defines kiosk systems running the RadiantWave app. Not application code — this is the OS image and deployment configuration. The actual app lives in the `radiantwave` flake input (from gitea.radiantwavetech.com).
|
NixOS flake that defines kiosk systems running the RadiantWave app. Not application code — this is the OS image and deployment configuration. The app itself is not pinned or built here: each channel's **published manifest** (served from `files.radiantwavetech.com`, pushed by RadiantWave CI to Attic) is the single source of truth for the baked closure.
|
||||||
|
|
||||||
Hosted on Gitea: `https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig`
|
Hosted on Gitea: `https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig`
|
||||||
|
|
||||||
|
## Prod/dev split
|
||||||
|
|
||||||
|
Two branches — `main` (prod) and `dev`. There are **no `-dev` configs**: each hardware target has exactly one config, and the branch selects its channel via the single `channel` block at the top of `flake.nix`:
|
||||||
|
|
||||||
|
- `main`: `atticRepo = "radiantwave"`, `gitRef = ""` (default branch).
|
||||||
|
- `dev`: `atticRepo = "radiantwave-dev"`, `gitRef = "?ref=dev"`.
|
||||||
|
|
||||||
|
Everything downstream (the configs, ISOs, updater module, autoUpgrade refs) reads from `channel`, so a `dev`→`main` merge carries your changes automatically — no copying between sibling configs. To flip a channel, change only those two `channel` values. When merging, reconcile just this one block.
|
||||||
|
|
||||||
|
**No app version is pinned anywhere.** At eval time `flake.nix` fetches the channel's published manifest (`https://files.radiantwavetech.com/<atticRepo>/<manifestFile>`) with `builtins.fetchurl` and bakes its `storePath` verbatim as the app closure. Nix substitutes that exact closure from the channel's Attic cache, so images always match what RadiantWave published and never compile the app from source. Because that fetch is impure (no hash), **every eval that reaches the app needs `--impure`**: CI, local `build-rpi.sh`, and the kiosk's `system.autoUpgrade` (which passes `--impure`). `tarball-ttl = 0` is set in CI and on the kiosks so a rebuild never reuses a stale manifest.
|
||||||
|
|
||||||
|
Both gitea repos are public: all git access is plain HTTPS (`git+https://...`), no SSH keys or access tokens on the kiosks. Don't reintroduce `git+ssh` URLs in flake/upgrade refs.
|
||||||
|
|
||||||
## Key commands
|
## Key commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build a system closure (any config name from flake.nix)
|
# Build a system closure (any config name from flake.nix).
|
||||||
nix build .#nixosConfigurations.<name>.config.system.build.toplevel
|
# --impure is required: the app closure comes from the channel manifest,
|
||||||
|
# fetched at eval time (see the prod/dev split above).
|
||||||
|
nix build --impure .#nixosConfigurations.<name>.config.system.build.toplevel
|
||||||
|
|
||||||
# Build a bootable ISO
|
# Build a bootable ISO
|
||||||
nix build .#nixosConfigurations.<name>.config.system.build.isoImage
|
nix build --impure .#nixosConfigurations.<name>.config.system.build.isoImage
|
||||||
|
|
||||||
# Deploy to a remote kiosk over SSH
|
# Deploy to a remote kiosk over SSH
|
||||||
nixos-rebuild switch --flake .#<name> --target-host root@<host>
|
nixos-rebuild switch --impure --flake .#<name> --target-host root@<host>
|
||||||
|
|
||||||
# Push build result to Attic cache
|
# Push build result to Attic cache (not currently wired into any config)
|
||||||
attic push kiosk ./result
|
attic push kiosk ./result
|
||||||
|
|
||||||
# Evaluate available config names
|
# Evaluate available config names (pure-safe: does not force the app fetch)
|
||||||
nix eval .#nixosConfigurations --apply builtins.attrNames
|
nix eval .#nixosConfigurations --apply builtins.attrNames
|
||||||
```
|
```
|
||||||
|
|
||||||
## Config variants
|
## Config variants
|
||||||
|
|
||||||
| Name | Target hardware | Disk device | Notes |
|
One config per hardware target. Dev/prod is the branch, not the config — see `channel` in `flake.nix`:
|
||||||
|
|
||||||
|
| Name | Target hardware | Disk device | App closure |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `radiantwavekiosk` | Intel NUC 14 (NVMe) | `/dev/nvme0n1` | Production, uses `radiantwave` main |
|
| `radiantwavekiosk` | Intel NUC 14 (NVMe) | `/dev/nvme0n1` | channel manifest `manifest.json` |
|
||||||
| `radiantwavekiosk-dev` | Intel NUC 14 (NVMe) | `/dev/nvme0n1` | Dev, uses `radiantwave-dev` ref |
|
| `radiantwavekiosk-commercial` | Commercial HW (SATA) | `/dev/sda` | channel manifest `manifest.json` |
|
||||||
| `radiantwavekiosk-commercial` | Commercial HW (SATA) | `/dev/sda` | Production |
|
| `radiantwavekiosk-android` | Intel NUC 14, rotated display | `/dev/nvme0n1` | channel manifest `manifest.json` |
|
||||||
| `radiantwavekiosk-commercial-dev` | Commercial HW (SATA) | `/dev/sda` | Dev |
|
| `radiantwavekiosk-rpi` | Raspberry Pi 5 (SD image) | — | channel manifest `aarch64-manifest.json` |
|
||||||
| `radiantwavekiosk-android` | Intel NUC 14 | `/dev/nvme0n1` | + rotated display (portrait) |
|
|
||||||
| `radiantwavekiosk-rpi` | Raspberry Pi 5 | — | Uses `nixos-raspberrypi` |
|
|
||||||
| `radiantwavekiosk-rpi-dev` | Raspberry Pi 5 | — | Dev, uses `radiantwave-dev` |
|
|
||||||
|
|
||||||
ISOs: `usb-iso`, `commercial-usb-iso`, `android-iso`, `dev-iso` — each bakes in the corresponding system closure for zero-touch install.
|
On the `dev` branch, the same four configs read the `radiantwave-dev` manifests (`?ref=dev`).
|
||||||
|
|
||||||
## Branching model
|
Every variant sets its own `networking.hostName` (same as the config name) and `system.configurationRevision`, so `tools/find_systems.zsh` can identify fleet members.
|
||||||
|
|
||||||
Single branch (`main`). Production vs. dev split is at the **flake input** level (`radiantwave` vs `radiantwave-dev`), not at the branch level.
|
### USB images (zero-touch installers)
|
||||||
|
|
||||||
## CI (Forgejo)
|
Each ISO installs exactly one closure using that closure's disk layout (`mkIso` in `flake.nix` derives both from the target config name):
|
||||||
|
|
||||||
`.forgejo/workflows/build-dev.yml` — triggered by `repository_dispatch` (`radiantwave-dev-built`) from the RadiantWave repo. Updates `radiantwave-dev` input, builds dev closure + ISO, commits updated `flake.lock`. Runs on `neils-server-runner`.
|
| ISO | Installs | Disk layout |
|
||||||
|
|---|---|---|
|
||||||
|
| `kiosk-iso` | `radiantwavekiosk` | NVMe (`disk-config.nix`) |
|
||||||
|
| `commercial-iso` | `radiantwavekiosk-commercial` | SATA (`commercial-disk-config.nix`) |
|
||||||
|
| `android-iso` | `radiantwavekiosk-android` | NVMe |
|
||||||
|
|
||||||
|
These same names build **dev-flavored** images when the flake is checked out on the `dev` branch (the channel block selects the dev manifest, attic repo, and upgrade ref), so there are no separate `-dev` ISOs.
|
||||||
|
|
||||||
|
The installer lives once in `zero-touch-installer.nix` and is parameterized by `targetConfig`. Adding a new variant = add the `nixosConfigurations.*` entry, a matching `packages.x86_64-linux.*` line, and an ISO if needed.
|
||||||
|
|
||||||
|
Raspberry Pi systems are built as SD images, not ISOs:
|
||||||
|
`nix build --impure .#nixosConfigurations.radiantwavekiosk-rpi.config.system.build.sdImage` (see `build-rpi.sh`).
|
||||||
|
|
||||||
|
## Hardware facter reports
|
||||||
|
|
||||||
|
`facter.json` is a hardware report captured on a **NUC 14**. It is currently applied to **every x86 variant**, including the commercial SATA hardware — acceptable until per-model reports exist.
|
||||||
|
|
||||||
|
To generate a report for a hardware model:
|
||||||
|
|
||||||
|
1. Boot that hardware from any NixOS live image (the kiosk installers work).
|
||||||
|
2. With real peripherals attached (display(s), audio), run:
|
||||||
|
```bash
|
||||||
|
sudo nix shell nixpkgs#nixos-facter -c nixos-facter -o facter-<model>.json
|
||||||
|
```
|
||||||
|
3. Commit `facter-<model>.json` to the repo root.
|
||||||
|
4. Wire it up at the marked spot in `flake.nix` (`facter.reportPath` inside `mkKioskSystem`) by selecting the right file per variant.
|
||||||
|
|
||||||
|
Regenerate when major hardware changes, not on a schedule — reports are snapshots. RPi configs don't use the facter module, so no report is needed there.
|
||||||
|
|
||||||
|
## CI (Gitea)
|
||||||
|
|
||||||
|
`.gitea/workflows/build.yml` and `build-rpi.yml` — triggered on pushes to `dev`, on new tags (production), and by `workflow_dispatch`, all on the `orion-lxc` runner. They build and publish exactly the closure the channel manifest announces, so a release is published by RadiantWave CI alone; there is no `flake.lock` bump to make here. Both workflows set `tarball-ttl = 0` and fail fast if the announced closure is not in the channel's Attic cache.
|
||||||
|
|
||||||
## Important gotchas
|
## Important gotchas
|
||||||
|
|
||||||
- **Disk device differs by variant**: `disk-config.nix` uses `/dev/nvme0n1`, `commercial-disk-config.nix` uses `/dev/sda`. Don't mix them up.
|
- **Disk device differs by variant**: `disk-config.nix` uses `/dev/nvme0n1`, `commercial-disk-config.nix` uses `/dev/sda`. `mkIso` pairs each ISO with the correct layout automatically — keep new ISOs paired correctly.
|
||||||
- **ISO configs reference specific system closures**: `iso-config.nix` hardcodes `radiantwavekiosk`, `dev-iso.nix` hardcodes `radiantwavekiosk-dev`, `commercial-iso-config.nix` hardcodes `radiantwavekiosk-commercial`. Changing a config name in `flake.nix` requires updating the corresponding ISO config.
|
- **Changing a config name** requires updating that config's `upgradeFlakeRef` in `flake.nix` (it embeds the name).
|
||||||
- **Attic cache token**: ISO configs embed a JWT token for Attic in `/etc/nix/netrc`. If builds fail with auth errors, the token may have expired (expiry in the JWT payload).
|
- **The `channel` block is the dev/prod toggle** in `flake.nix` — `atticRepo` and `gitRef`. When merging `dev`→`main`, keep this block's values for the target branch; only this block differs between branches.
|
||||||
- **`rotated_display.nix` uses `mkForce`** on `loginShellInit` — it replaces the entire kiosk startup script from `configuration.nix`, don't edit both.
|
- **`rotated_display.nix` uses `mkForce`** on `loginShellInit` — it replaces the entire kiosk startup script from `configuration.nix`; don't edit both.
|
||||||
- **SOPS**: `.sops.yaml` configures age-based encryption for `secrets.yaml`. You'll need the private key matching `age1t2wpzv...` to decrypt.
|
- **Auto-update**: `system.autoUpgrade` runs every 10 min (`*:00/10`) with `--refresh --impure`, operation `switch`, no auto-reboot. A `radiantwave-backup` rsync runs before each attempt. The health-check/rollback service is written but commented out in `configuration.nix`.
|
||||||
- **Flake inputs**: `radiantwave` tracks main, `radiantwave-dev` tracks `?ref=dev`. Updating them independently is intentional.
|
- **Live app updater**: each config's `radiantwaveUpdaterModule` points at a per-arch manifest — x86 uses `<repo>/manifest.json`, RPi uses `<repo>/aarch64-manifest.json` (via the `manifestFile` arg). A 2-min timer polls it and fetches the announced closure from the channel's Attic repo. RPi live updates require RadiantWave CI to publish both an aarch64 closure and `aarch64-manifest.json`; until then the RPi stays on the baked baseline.
|
||||||
|
- **App closure comes from Attic**: both channels (`radiantwave`, `radiantwave-dev`) are configured as trusted substituters in `configuration.nix`, so the baked app and its updates are substituted, never compiled, on the kiosks. nixpkgs deps still come from cache.nixos.org.
|
||||||
|
- **Secrets**: none in-tree anymore (gitea repos are public). If either repo goes private, re-add auth via `nix.settings.access-tokens` in `configuration.nix`.
|
||||||
|
- **App inputs**: there are none. Do not re-add a pinned `radiantwave` flake input or an `appVersion`; that is exactly what drifted from the published releases. The manifest is the source of truth.
|
||||||
|
- **SSH keys on machines are inbound-only** (`authorizedKeys` for admin access); outbound git is HTTPS. The root key in `zero-touch-installer.nix` must stay in sync with `myKey` in `configuration.nix`.
|
||||||
|
|
||||||
## File map
|
## File map
|
||||||
|
|
||||||
- `flake.nix` — all system definitions, entry point
|
- `flake.nix` — entry point: inputs, the `channel` dev/prod selector, builders (`mkKioskSystem`, `mkRpiKioskSystem`, `mkIso`), all system definitions, facter report wiring
|
||||||
- `configuration.nix` — shared NixOS config (packages, services, kiosk user, auto-upgrade, health check, VM variant)
|
- `configuration.nix` — shared NixOS config (packages, services, kiosk user, auto-upgrade, health check, VM variant)
|
||||||
|
- `zero-touch-installer.nix` — parameterized USB installer module used by all three ISOs
|
||||||
- `disk-config.nix` — NVMe disk layout (NUC)
|
- `disk-config.nix` — NVMe disk layout (NUC)
|
||||||
- `commercial-disk-config.nix` — SATA disk layout (commercial HW)
|
- `commercial-disk-config.nix` — SATA disk layout (commercial HW)
|
||||||
- `iso-config.nix` — production ISO + zero-touch installer
|
|
||||||
- `dev-iso.nix` — dev ISO + zero-touch installer
|
|
||||||
- `commercial-iso-config.nix` — commercial ISO + zero-touch installer
|
|
||||||
- `rotated_display.nix` — portrait display override for Android variant
|
- `rotated_display.nix` — portrait display override for Android variant
|
||||||
- `facter.json` — hardware facter report (NUC 14)
|
- `facter.json` — hardware facter report (NUC 14)
|
||||||
- `14rvk.json`, `pn54.json` — additional hardware facter reports
|
|
||||||
- `assets/` — logo and splash image
|
- `assets/` — logo and splash image
|
||||||
- `plymouth-theme/` — boot splash theme
|
- `plymouth-theme/` — boot splash theme
|
||||||
- `commands.md` — quick-reference for build/deploy commands
|
- `tools/find_systems.zsh` — subnet sweep for fleet inventory over SSH
|
||||||
|
- `build-rpi.sh` — containerized RPi SD-image build helper
|
||||||
|
- `PLAN-*.md`, `report.md`, `timeline.csv`, `kiosk-url-changes.csv` — planning/history notes, not configuration
|
||||||
|
|||||||
@@ -0,0 +1,632 @@
|
|||||||
|
# RadiantWave Kiosk Update System Plan
|
||||||
|
|
||||||
|
**Goal**: Bulletproof automatic update system for RadiantWave kiosk
|
||||||
|
**Deadline**: Production in 2 days
|
||||||
|
**Architecture**: Manifest-based app updates + Flake lock system updates + systemd service supervision
|
||||||
|
**Timezone**: UTC (all kiosks run in UTC)
|
||||||
|
**Variants**: All variants (production + dev) auto-update
|
||||||
|
**Notifications**: Logs only (no email/Slack)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
This plan implements a three-part update system:
|
||||||
|
|
||||||
|
1. **Kiosk App Supervision**: Convert the kiosk app from exec'd login shell to a supervised systemd user service with automatic restart
|
||||||
|
2. **RadiantWave App Updates**: Hourly manifest checks trigger updates via Attic binary cache
|
||||||
|
3. **System Updates**: NixOS updates on boot and nightly at 8PM UTC
|
||||||
|
|
||||||
|
All updates show Plymouth splash during transition and auto-rollback on failure.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ BOOT SEQUENCE │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ 1. Plymouth splash (held indefinitely) │
|
||||||
|
│ 2. getty autologin as kiosk on tty1 │
|
||||||
|
│ 3. loginShellInit runs: │
|
||||||
|
│ a. Set environment variables │
|
||||||
|
│ b. Wait for network (up to 10s) │
|
||||||
|
│ c. Start radiantwave-audio-init user service │
|
||||||
|
│ d. Start kiosk-app.service (systemd user service) │
|
||||||
|
│ e. Quit Plymouth │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ KIOSK APP SERVICE │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ kiosk-app.service (systemd user service) │
|
||||||
|
│ ├─ Runs: dbus-run-session cage -- radiantwave -d -s │
|
||||||
|
│ ├─ Restart=always, RestartSec=5 │
|
||||||
|
│ ├─ Logs to /tmp/kiosk.log │
|
||||||
|
│ └─ On failure: auto-restart (3 attempts) │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ UPDATE CHECKERS │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ radiantwave-update-checker.service (hourly) │
|
||||||
|
│ ├─ Checks: https://attic.radiantwavetech.com/manifest.json │
|
||||||
|
│ ├─ If new version: trigger radiantwave-upgrade.service │
|
||||||
|
│ └─ Runs as: root (system service) │
|
||||||
|
│ │
|
||||||
|
│ system-update-checker.service (on boot + 8PM UTC) │
|
||||||
|
│ ├─ Triggers: nixos-upgrade.service │
|
||||||
|
│ └─ Runs as: root (system service) │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ UPGRADE PROCESS │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ radiantwave-upgrade.service (triggered by update checker) │
|
||||||
|
│ ├─ 1. Show Plymouth splash │
|
||||||
|
│ ├─ 2. Stop kiosk-app.service │
|
||||||
|
│ ├─ 3. Update flake.lock (git pull) │
|
||||||
|
│ ├─ 4. Run nixos-rebuild switch │
|
||||||
|
│ ├─ 5. Restart kiosk-app.service │
|
||||||
|
│ ├─ 6. Quit Plymouth │
|
||||||
|
│ └─ 7. Health check (if fails: auto-rollback) │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Component Details
|
||||||
|
|
||||||
|
### 1. Kiosk App Systemd Service
|
||||||
|
|
||||||
|
**File**: `kiosk-app.nix` (new module)
|
||||||
|
|
||||||
|
**Purpose**: Supervise the cage/radiantwave process with automatic restart
|
||||||
|
|
||||||
|
**Key Configuration**:
|
||||||
|
```nix
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
# System service (runs as root, starts user service)
|
||||||
|
systemd.services.kiosk-app = {
|
||||||
|
description = "RadiantWave Kiosk Application";
|
||||||
|
after = [ "graphical.target" ];
|
||||||
|
wantedBy = [ "graphical.target" ];
|
||||||
|
|
||||||
|
# Start the user service
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = "${pkgs.systemd}/bin/systemctl --user start kiosk-app-session.service";
|
||||||
|
ExecStop = "${pkgs.systemd}/bin/systemctl --user stop kiosk-app-session.service";
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# User service (actual cage/radiantwave process)
|
||||||
|
systemd.user.services.kiosk-app-session = {
|
||||||
|
description = "RadiantWave Kiosk Session";
|
||||||
|
after = [ "graphical-session.target" ];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = "${pkgs.dbus}/bin/dbus-run-session ${pkgs.cage}/bin/cage -- ${config.radiantApp}/bin/radiantwave -d -s";
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = 5;
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
StandardOutput = "journal";
|
||||||
|
StandardError = "journal";
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
Environment = [
|
||||||
|
"GODEBUG=cgocheck=0"
|
||||||
|
"WAYLAND_DISPLAY=wayland-0"
|
||||||
|
"XDG_RUNTIME_DIR=/run/user/1000"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Restart policy
|
||||||
|
unitConfig = {
|
||||||
|
StartLimitIntervalSec = 60;
|
||||||
|
StartLimitBurst = 3;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Changes to `configuration.nix`**:
|
||||||
|
- Remove the `exec` line from `environment.loginShellInit`
|
||||||
|
- Add `systemctl --user start kiosk-app-session.service` instead
|
||||||
|
- Keep Plymouth quit after service start
|
||||||
|
|
||||||
|
**Updated `loginShellInit`**:
|
||||||
|
```nix
|
||||||
|
environment.loginShellInit = ''
|
||||||
|
# ... existing env setup and network wait ...
|
||||||
|
|
||||||
|
# Start audio init
|
||||||
|
systemctl --user start radiantwave-audio-init.service
|
||||||
|
|
||||||
|
# Start kiosk app service
|
||||||
|
systemctl --user start kiosk-app-session.service
|
||||||
|
|
||||||
|
# Wait a moment for cage to start
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Quit Plymouth
|
||||||
|
sudo -n plymouth quit --wait
|
||||||
|
|
||||||
|
# Keep shell alive (don't exec)
|
||||||
|
# The systemd service manages the kiosk app
|
||||||
|
'';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Attic Cache Configuration
|
||||||
|
|
||||||
|
**File**: `attic-cache.nix` (new module)
|
||||||
|
|
||||||
|
**Purpose**: Configure Nix to use Attic as a binary cache
|
||||||
|
|
||||||
|
**Key Configuration**:
|
||||||
|
```nix
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
# Add Attic to trusted caches
|
||||||
|
nix.settings = {
|
||||||
|
trusted-caches = [
|
||||||
|
"https://attic.radiantwavetech.com/radiantwave"
|
||||||
|
];
|
||||||
|
|
||||||
|
# Extra substituters (for pulling)
|
||||||
|
extra-substituters = [
|
||||||
|
"https://attic.radiantwavetech.com/radiantwave"
|
||||||
|
];
|
||||||
|
|
||||||
|
# Public keys (Attic uses Ed25519)
|
||||||
|
trusted-public-keys = [
|
||||||
|
"radiantwave:PLACEHOLDER_PUBLIC_KEY"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Ensure nix can use the cache without authentication
|
||||||
|
# (Attic is configured as public/open)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: Replace `PLACEHOLDER_PUBLIC_KEY` with the actual Attic public key after server setup.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. RadiantWave Update Checker
|
||||||
|
|
||||||
|
**File**: `radiantwave-update-checker.nix` (new module)
|
||||||
|
|
||||||
|
**Purpose**: Check Attic manifest hourly for new RadiantWave versions
|
||||||
|
|
||||||
|
**Key Configuration**:
|
||||||
|
```nix
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
systemd.services.radiantwave-update-checker = {
|
||||||
|
description = "Check for RadiantWave updates";
|
||||||
|
|
||||||
|
# Run hourly
|
||||||
|
startAt = "hourly";
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
|
||||||
|
# Check manifest and trigger upgrade if needed
|
||||||
|
ExecStart = pkgs.writeScript "check-radiantwave-update" ''
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MANIFEST_URL="https://attic.radiantwavetech.com/manifest.json"
|
||||||
|
CURRENT_VERSION_FILE="/etc/radiantwave-version"
|
||||||
|
|
||||||
|
# Fetch manifest
|
||||||
|
MANIFEST=$(curl -sf "$MANIFEST_URL" || exit 0)
|
||||||
|
|
||||||
|
# Extract version
|
||||||
|
NEW_VERSION=$(echo "$MANIFEST" | ${pkgs.jq}/bin/jq -r '.version')
|
||||||
|
|
||||||
|
# Get current version
|
||||||
|
if [ -f "$CURRENT_VERSION_FILE" ]; then
|
||||||
|
CURRENT_VERSION=$(cat "$CURRENT_VERSION_FILE")
|
||||||
|
else
|
||||||
|
CURRENT_VERSION="0.0.0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Compare versions
|
||||||
|
if [ "$NEW_VERSION" != "$CURRENT_VERSION" ]; then
|
||||||
|
echo "Update available: $CURRENT_VERSION -> $NEW_VERSION"
|
||||||
|
# Trigger upgrade
|
||||||
|
systemctl start radiantwave-upgrade.service
|
||||||
|
else
|
||||||
|
echo "Already up to date: $CURRENT_VERSION"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Upgrade service (triggered by checker)
|
||||||
|
systemd.services.radiantwave-upgrade = {
|
||||||
|
description = "Upgrade RadiantWave";
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
|
||||||
|
# Show Plymouth during upgrade
|
||||||
|
ExecStartPre = "${pkgs.plymouth}/bin/plymouth show-splash || true";
|
||||||
|
|
||||||
|
# Stop kiosk
|
||||||
|
ExecStart = "${pkgs.systemd}/bin/systemctl stop kiosk-app.service";
|
||||||
|
|
||||||
|
# Update flake.lock and rebuild
|
||||||
|
ExecStart = pkgs.writeScript "upgrade-radiantwave" ''
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Update flake.lock
|
||||||
|
cd ${config.system.build.toplevel} # or git repo path
|
||||||
|
git pull origin main
|
||||||
|
|
||||||
|
# Update radiantwave input
|
||||||
|
nix flake update radiantwave
|
||||||
|
|
||||||
|
# Rebuild and switch
|
||||||
|
nixos-rebuild switch --flake .#${config.networking.hostName}
|
||||||
|
|
||||||
|
# Update version file
|
||||||
|
NEW_VERSION=$(curl -sf https://attic.radiantwavetech.com/manifest.json | ${pkgs.jq}/bin/jq -r '.version')
|
||||||
|
echo "$NEW_VERSION" > /etc/radiantwave-version
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Restart kiosk
|
||||||
|
ExecStart = "${pkgs.systemd}/bin/systemctl start kiosk-app.service";
|
||||||
|
|
||||||
|
# Quit Plymouth
|
||||||
|
ExecStartPost = "${pkgs.plymouth}/bin/plymouth quit --wait || true";
|
||||||
|
|
||||||
|
# Rollback on failure
|
||||||
|
FailureAction = "none"; # Handled by healthcheck
|
||||||
|
|
||||||
|
# Timeout
|
||||||
|
TimeoutStartSec = 300; # 5 minutes for rebuild
|
||||||
|
};
|
||||||
|
|
||||||
|
# Run after update checker
|
||||||
|
after = [ "radiantwave-update-checker.service" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. System Update Configuration
|
||||||
|
|
||||||
|
**File**: `system-updates.nix` (modify existing)
|
||||||
|
|
||||||
|
**Purpose**: Configure NixOS system updates on boot and nightly at 8PM UTC
|
||||||
|
|
||||||
|
**Changes to `configuration.nix`**:
|
||||||
|
```nix
|
||||||
|
# Replace existing system.autoUpgrade
|
||||||
|
system.autoUpgrade = {
|
||||||
|
enable = true;
|
||||||
|
flake = upgradeFlakeRef; # e.g., "git+https://gitea...KioskConfig.git#radiantwavekiosk"
|
||||||
|
|
||||||
|
# Run on boot and nightly at 8PM UTC (kiosks are in UTC)
|
||||||
|
dates = "20:00"; # 8PM UTC daily
|
||||||
|
randomizedDelaySec = "10min"; # Avoid thundering herd
|
||||||
|
|
||||||
|
flags = [ "--refresh" "-L" ];
|
||||||
|
operation = "switch";
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: The existing health check service (`radiantwave-healthcheck`) already runs after `nixos-upgrade.service` and handles rollback. Keep this as-is.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Update Rollback Mechanism
|
||||||
|
|
||||||
|
**File**: `update-rollback.nix` (modify existing health check)
|
||||||
|
|
||||||
|
**Purpose**: Auto-rollback on failed updates
|
||||||
|
|
||||||
|
**Existing mechanism** (already in `configuration.nix`):
|
||||||
|
```nix
|
||||||
|
systemd.services.radiantwave-healthcheck = {
|
||||||
|
description = "Health check after upgrade";
|
||||||
|
after = [ "nixos-upgrade.service" ];
|
||||||
|
wantedBy = [ "nixos-upgrade.service" ];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStartPre = "sleep 90"; # Wait for cage to start
|
||||||
|
ExecStart = pkgs.writeScript "healthcheck" ''
|
||||||
|
#!/bin/bash
|
||||||
|
if ! pgrep -f ".radiantwave-unwrapped"; then
|
||||||
|
echo "Health check failed, rolling back..."
|
||||||
|
nixos-rebuild switch --rollback
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Enhancement**: Add version file tracking and logging:
|
||||||
|
```nix
|
||||||
|
ExecStart = pkgs.writeScript "healthcheck" ''
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
LOG_FILE="/var/log/radiantwave-healthcheck.log"
|
||||||
|
|
||||||
|
echo "$(date): Starting health check" >> "$LOG_FILE"
|
||||||
|
|
||||||
|
# Wait for cage to start
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
# Check if radiantwave is running
|
||||||
|
if pgrep -f ".radiantwave-unwrapped" > /dev/null; then
|
||||||
|
echo "$(date): Health check passed" >> "$LOG_FILE"
|
||||||
|
# Update version file on success
|
||||||
|
if [ -f "/tmp/pending-version" ]; then
|
||||||
|
cp /tmp/pending-version /etc/radiantwave-version
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "$(date): Health check FAILED, rolling back..." >> "$LOG_FILE"
|
||||||
|
nixos-rebuild switch --rollback 2>> "$LOG_FILE"
|
||||||
|
echo "$(date): Rollback complete" >> "$LOG_FILE"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. CI Changes to Push to Attic
|
||||||
|
|
||||||
|
**File**: `.gitea/workflows/build-prod.yml` (modify)
|
||||||
|
|
||||||
|
**Purpose**: Push built closures to Attic cache
|
||||||
|
|
||||||
|
**Add to workflow**:
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
# ... existing build steps ...
|
||||||
|
|
||||||
|
# Push to Attic
|
||||||
|
- name: Push to Attic cache
|
||||||
|
run: |
|
||||||
|
# Install attic-client
|
||||||
|
nix profile install github:zedant/attic#attic-client
|
||||||
|
|
||||||
|
# Login to Attic (token from secrets)
|
||||||
|
attic login production https://attic.radiantwavetech.com production "${{ secrets.ATTIC_TOKEN }}"
|
||||||
|
|
||||||
|
# Push closures
|
||||||
|
attic push production ./result-kiosk
|
||||||
|
attic push production ./result-android
|
||||||
|
attic push production ./result-commercial
|
||||||
|
|
||||||
|
# Update manifest
|
||||||
|
- name: Update manifest
|
||||||
|
run: |
|
||||||
|
VERSION=$(date +%Y.%m.%d-%H%M)
|
||||||
|
STORE_PATH=$(readlink -f ./result-kiosk)
|
||||||
|
|
||||||
|
# Create manifest
|
||||||
|
cat > manifest.json << EOF
|
||||||
|
{
|
||||||
|
"version": "$VERSION",
|
||||||
|
"storePath": "$STORE_PATH",
|
||||||
|
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||||
|
"commit": "${{ github.sha }}"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Upload manifest to Attic or fileserver
|
||||||
|
# (Attic doesn't support arbitrary file uploads, so use a fileserver or git)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: Attic doesn't support arbitrary file uploads. Options for manifest:
|
||||||
|
1. Host on a simple fileserver (nginx, S3, etc.)
|
||||||
|
2. Commit to a git repo and serve via GitHub/Gitea Pages
|
||||||
|
3. Use Attic's labels feature (if available)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
### Phase 1: Attic Server Setup (User's Responsibility)
|
||||||
|
- [ ] Set up Attic server at `attic.radiantwavetech.com`
|
||||||
|
- [ ] Configure public/open access (no authentication)
|
||||||
|
- [ ] Create cache named `radiantwave`
|
||||||
|
- [ ] Note the public key for Nix configuration
|
||||||
|
- [ ] Test pushing a closure: `attic push radiantwave /nix/store/...`
|
||||||
|
|
||||||
|
### Phase 2: Manifest Hosting (User's Responsibility)
|
||||||
|
- [ ] Choose manifest hosting location (fileserver, git, S3)
|
||||||
|
- [ ] Create initial manifest with current version
|
||||||
|
- [ ] Ensure manifest is accessible via HTTPS
|
||||||
|
|
||||||
|
### Phase 3: Kiosk Configuration Changes
|
||||||
|
- [ ] Create `kiosk-app.nix` module with systemd service
|
||||||
|
- [ ] Create `attic-cache.nix` module for Attic configuration
|
||||||
|
- [ ] Create `radiantwave-update-checker.nix` module
|
||||||
|
- [ ] Modify `configuration.nix`:
|
||||||
|
- Remove `exec` from `loginShellInit`
|
||||||
|
- Add `systemctl --user start kiosk-app-session.service`
|
||||||
|
- Update `system.autoUpgrade` schedule
|
||||||
|
- [ ] Modify `rotated_display.nix` to match changes
|
||||||
|
- [ ] Test locally with `nixos-rebuild build`
|
||||||
|
|
||||||
|
### Phase 4: CI Changes
|
||||||
|
- [ ] Add Attic push step to `build-prod.yml`
|
||||||
|
- [ ] Add manifest update step
|
||||||
|
- [ ] Test CI pipeline with manual trigger
|
||||||
|
|
||||||
|
### Phase 5: Testing
|
||||||
|
- [ ] Build ISO with changes
|
||||||
|
- [ ] Test on VM or physical hardware
|
||||||
|
- [ ] Verify kiosk starts automatically
|
||||||
|
- [ ] Test manual update trigger
|
||||||
|
- [ ] Test automatic update detection
|
||||||
|
- [ ] Test rollback on failure
|
||||||
|
- [ ] Test Plymouth splash during updates
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
### Kiosk App Service
|
||||||
|
- [ ] Kiosk starts automatically on boot
|
||||||
|
- [ ] Kiosk restarts if crashed (kill -9 radiantwave)
|
||||||
|
- [ ] Logs appear in `/tmp/kiosk.log` and `journalctl`
|
||||||
|
- [ ] Audio init completes before kiosk starts
|
||||||
|
|
||||||
|
### Update Detection
|
||||||
|
- [ ] Manifest check runs hourly
|
||||||
|
- [ ] System update check runs on boot
|
||||||
|
- [ ] System update check runs at 8PM UTC
|
||||||
|
- [ ] Checker logs show current vs available version
|
||||||
|
|
||||||
|
### Update Process
|
||||||
|
- [ ] Plymouth splash shows during update
|
||||||
|
- [ ] Kiosk stops before rebuild
|
||||||
|
- [ ] Rebuild completes successfully
|
||||||
|
- [ ] Kiosk restarts after rebuild
|
||||||
|
- [ ] Plymouth quits after kiosk starts
|
||||||
|
- [ ] Version file updates on success
|
||||||
|
|
||||||
|
### Rollback
|
||||||
|
- [ ] Health check runs after upgrade
|
||||||
|
- [ ] Rollback triggers if radiantwave not running
|
||||||
|
- [ ] Rollback completes successfully
|
||||||
|
- [ ] Logs show rollback reason
|
||||||
|
|
||||||
|
### Network Failure Handling
|
||||||
|
- [ ] Update checker continues if manifest unreachable
|
||||||
|
- [ ] Rebuild continues if Attic unreachable (uses local cache)
|
||||||
|
- [ ] Kiosk restarts even if update fails
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Map (New/Modified)
|
||||||
|
|
||||||
|
### New Files
|
||||||
|
- `kiosk-app.nix` - Systemd service for kiosk app
|
||||||
|
- `attic-cache.nix` - Attic binary cache configuration
|
||||||
|
- `radiantwave-update-checker.nix` - Hourly update checker
|
||||||
|
- `system-updates.nix` - System update schedule (or modify configuration.nix)
|
||||||
|
|
||||||
|
### Modified Files
|
||||||
|
- `configuration.nix` - Remove exec, add service start, update autoUpgrade schedule
|
||||||
|
- `rotated_display.nix` - Match loginShellInit changes
|
||||||
|
- `.gitea/workflows/build-prod.yml` - Add Attic push step
|
||||||
|
- `.gitea/workflows/build-dev.yml` - Add Attic push step (optional)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rollback Strategy
|
||||||
|
|
||||||
|
If the update system itself fails:
|
||||||
|
|
||||||
|
1. **Manual rollback**: SSH into kiosk, run `nixos-rebuild switch --rollback`
|
||||||
|
2. **Boot previous generation**: Select previous generation in systemd-boot (if timeout enabled)
|
||||||
|
3. **Reinstall from ISO**: Last resort, use zero-touch installer ISO
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Monitoring and Debugging
|
||||||
|
|
||||||
|
### Logs to Check
|
||||||
|
- `/tmp/kiosk.log` - Kiosk app output
|
||||||
|
- `journalctl -u kiosk-app.service` - Systemd service logs
|
||||||
|
- `journalctl -u radiantwave-update-checker.service` - Update checker logs
|
||||||
|
- `journalctl -u radiantwave-upgrade.service` - Upgrade process logs
|
||||||
|
- `/var/log/radiantwave-healthcheck.log` - Health check results
|
||||||
|
|
||||||
|
### Manual Commands
|
||||||
|
```bash
|
||||||
|
# Check current version
|
||||||
|
cat /etc/radiantwave-version
|
||||||
|
|
||||||
|
# Check manifest manually
|
||||||
|
curl -sf https://attic.radiantwavetech.com/manifest.json | jq
|
||||||
|
|
||||||
|
# Trigger manual update
|
||||||
|
systemctl start radiantwave-upgrade.service
|
||||||
|
|
||||||
|
# Check kiosk service status
|
||||||
|
systemctl --user status kiosk-app-session.service
|
||||||
|
|
||||||
|
# View kiosk logs
|
||||||
|
journalctl -u kiosk-app.service -f
|
||||||
|
|
||||||
|
# Manual rollback
|
||||||
|
nixos-rebuild switch --rollback
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Risk Assessment
|
||||||
|
|
||||||
|
| Risk | Impact | Mitigation |
|
||||||
|
|------|--------|------------|
|
||||||
|
| Attic server unavailable | Updates fail, kiosk continues running | Update checker continues hourly, kiosk unaffected |
|
||||||
|
| Manifest corrupted | False update trigger | Version comparison logic, checksums |
|
||||||
|
| Rebuild fails | Kiosk stays on old version | Automatic rollback via health check |
|
||||||
|
| Kiosk crashes during update | Plymouth stays on screen | Timeout mechanisms, manual intervention |
|
||||||
|
| Network partition | No updates possible | Kiosk continues on current version |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
- [ ] Kiosk starts automatically on boot (no manual intervention)
|
||||||
|
- [ ] Kiosk restarts automatically if crashed
|
||||||
|
- [ ] RadiantWave updates check every hour
|
||||||
|
- [ ] System updates check on boot and 8PM UTC
|
||||||
|
- [ ] Updates show Plymouth splash during transition
|
||||||
|
- [ ] Failed updates auto-rollback within 2 minutes
|
||||||
|
- [ ] All operations logged for debugging
|
||||||
|
- [ ] Zero manual intervention required for routine updates
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Timeline
|
||||||
|
|
||||||
|
**Day 1 (Today)**:
|
||||||
|
- [ ] Set up Attic server
|
||||||
|
- [ ] Create manifest hosting
|
||||||
|
- [ ] Implement kiosk-app.nix
|
||||||
|
- [ ] Implement attic-cache.nix
|
||||||
|
- [ ] Test kiosk service locally
|
||||||
|
|
||||||
|
**Day 2 (Tomorrow)**:
|
||||||
|
- [ ] Implement update checker modules
|
||||||
|
- [ ] Update CI workflows
|
||||||
|
- [ ] Build test ISO
|
||||||
|
- [ ] Test on hardware
|
||||||
|
- [ ] Deploy to production
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Questions for Clarification
|
||||||
|
|
||||||
|
1. ~~Where will the manifest file be hosted?~~ → **Fileserver (nginx/Caddy)**
|
||||||
|
2. ~~What timezone is the kiosk in?~~ → **UTC (all kiosks)**
|
||||||
|
3. ~~Should dev variants also auto-update?~~ → **Yes, all variants**
|
||||||
|
4. Do you want a manual "check for updates" button in the kiosk UI?
|
||||||
|
5. ~~Should we notify admins of failed updates?~~ → **Logs only**
|
||||||
+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.
+8
-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,18 +26,20 @@ podman run --rm \
|
|||||||
mkdir -p /etc/nix
|
mkdir -p /etc/nix
|
||||||
echo "experimental-features = nix-command flakes" > /etc/nix/nix.conf
|
echo "experimental-features = nix-command flakes" > /etc/nix/nix.conf
|
||||||
echo "accept-flake-config = true" >> /etc/nix/nix.conf
|
echo "accept-flake-config = true" >> /etc/nix/nix.conf
|
||||||
echo "extra-substituters = https://cache.nixos.org https://nixos-raspberrypi.cachix.org" >> /etc/nix/nix.conf
|
echo "tarball-ttl = 0" >> /etc/nix/nix.conf
|
||||||
echo "extra-trusted-public-keys = nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI=" >> /etc/nix/nix.conf
|
echo "extra-substituters = https://cache.nixos.org https://nixos-raspberrypi.cachix.org https://attic.radiantwavetech.com/radiantwave https://attic.radiantwavetech.com/radiantwave-dev" >> /etc/nix/nix.conf
|
||||||
|
echo "extra-trusted-public-keys = nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI= radiantwave:QADrjpBWJssTZ3KmiZtcWEf4uAm66oOlHmtQI3jkrgA= radiantwave-dev:B8FhLu3MQ9JfeEIHIKNrKxnP+3akzzkqMbTZR4hRfj0=" >> /etc/nix/nix.conf
|
||||||
echo "max-jobs = 4" >> /etc/nix/nix.conf
|
echo "max-jobs = 4" >> /etc/nix/nix.conf
|
||||||
|
|
||||||
echo "==> Building sdImage (first run slow, cached after)..."
|
echo "==> Building sdImage (first run slow, cached after)..."
|
||||||
nix --accept-flake-config build \
|
nix --accept-flake-config build \
|
||||||
.#nixosConfigurations.'${CONFIG}'.config.system.build.sdImage \
|
.#nixosConfigurations.'${CONFIG}'.config.system.build.sdImage \
|
||||||
|
--impure \
|
||||||
--fallback \
|
--fallback \
|
||||||
--print-build-logs
|
--print-build-logs
|
||||||
|
|
||||||
echo "==> Build complete, copying..."
|
echo "==> Build complete, copying..."
|
||||||
cp result/sd-image/*.img* /repo/'${FILENAME}'
|
cp result/sd-image/*.img.zst /repo/'${FILENAME}'
|
||||||
echo "==> Done: ${FILENAME}"
|
echo "==> Done: ${FILENAME}"
|
||||||
ls -lh /repo/'${FILENAME}'
|
ls -lh /repo/'${FILENAME}'
|
||||||
'
|
'
|
||||||
|
|||||||
@@ -1,128 +0,0 @@
|
|||||||
{
|
|
||||||
pkgs,
|
|
||||||
inputs,
|
|
||||||
self,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
# TODO: Parameterize this to combine with iso-config.nix
|
|
||||||
# Currently hardcoded to radiantwavekiosk-commercial; should accept target config as param
|
|
||||||
targetSystem =
|
|
||||||
self.nixosConfigurations.radiantwavekiosk-commercial.config.system.build.toplevel;
|
|
||||||
|
|
||||||
diskoScript =
|
|
||||||
self.nixosConfigurations.radiantwavekiosk-commercial.config.system.build.diskoScript;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
nix.settings = {
|
|
||||||
experimental-features = [
|
|
||||||
"nix-command"
|
|
||||||
"flakes"
|
|
||||||
];
|
|
||||||
|
|
||||||
substituters = [
|
|
||||||
"https://cache.nixos.org"
|
|
||||||
];
|
|
||||||
|
|
||||||
flake-registry = "";
|
|
||||||
};
|
|
||||||
|
|
||||||
nix.registry.nixpkgs = pkgs.lib.mkForce {
|
|
||||||
to = {
|
|
||||||
type = "path";
|
|
||||||
path = "${inputs.nixpkgs}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.variables.KIOSK_FLAKE = "${self}";
|
|
||||||
|
|
||||||
virtualisation.vmVariant = {
|
|
||||||
virtualisation.memorySize = 8096;
|
|
||||||
virtualisation.diskSize = 100000;
|
|
||||||
virtualisation.cores = 4;
|
|
||||||
virtualisation.qemu.options = [
|
|
||||||
# "-nic none"
|
|
||||||
];
|
|
||||||
virtualisation.forwardPorts = [
|
|
||||||
{ from = "host"; host.port = 2222; guest.port = 22; }
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.etc."nix/netrc" = {
|
|
||||||
text = ''
|
|
||||||
password eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIwODgzODk2ODEsIm5iZiI6MTc3MjgxMzY4MSwic3ViIjoia2lvc2siLCJodHRwczovL2p3dC5hdHRpYy5ycy92MSI6eyJjYWNoZXMiOnsia2lvc2siOnsiciI6MX19fX0.N6Ktrmmu1LSy2PP8uNF25VHRP1dxPZJ4M0MR1kUpVi0
|
|
||||||
'';
|
|
||||||
mode = "0400";
|
|
||||||
};
|
|
||||||
|
|
||||||
nix.extraOptions = ''
|
|
||||||
netrc-file = /etc/nix/netrc
|
|
||||||
'';
|
|
||||||
|
|
||||||
networking.networkmanager.enable = true;
|
|
||||||
|
|
||||||
systemd.services.NetworkManager-wait-online.enable = false;
|
|
||||||
|
|
||||||
nix.settings.connect-timeout = 0;
|
|
||||||
|
|
||||||
services.openssh.enable = true;
|
|
||||||
|
|
||||||
users.users.root.openssh.authorizedKeys.keys = [
|
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB4vXx7sOOVAyxdS/N1LEI+L8wYICf+gbzs6Kz7K8Je5 cnclack@pm.me:Thinkpad"
|
|
||||||
];
|
|
||||||
|
|
||||||
systemd.services.zero-touch-install = {
|
|
||||||
description = "Zero-Touch Kiosk Installation";
|
|
||||||
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
|
|
||||||
wants = [ ];
|
|
||||||
after = [ "systemd-udev-settle.service" ];
|
|
||||||
|
|
||||||
path = with pkgs; [
|
|
||||||
nix
|
|
||||||
git
|
|
||||||
util-linux
|
|
||||||
nixos-install-tools
|
|
||||||
curl
|
|
||||||
networkmanager
|
|
||||||
mount
|
|
||||||
umount
|
|
||||||
disko
|
|
||||||
];
|
|
||||||
|
|
||||||
environment = {
|
|
||||||
NIX_PATH = "nixpkgs=${inputs.nixpkgs}";
|
|
||||||
KIOSK_FLAKE = "${self}";
|
|
||||||
DISKO_SCRIPT = "${diskoScript}";
|
|
||||||
};
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
echo "Running disko script..."
|
|
||||||
echo "Script: $DISKO_SCRIPT"
|
|
||||||
|
|
||||||
${diskoScript}
|
|
||||||
|
|
||||||
echo "Installing RadiantWave"
|
|
||||||
echo "Closure Path: ${targetSystem}"
|
|
||||||
|
|
||||||
nixos-install \
|
|
||||||
--system "${targetSystem}" \
|
|
||||||
--no-root-passwd \
|
|
||||||
--no-channel-copy
|
|
||||||
|
|
||||||
# Redirect to console and show completion message
|
|
||||||
exec > /dev/tty1 2>&1
|
|
||||||
echo ""
|
|
||||||
echo "============================================="
|
|
||||||
echo " Installation complete!"
|
|
||||||
echo ""
|
|
||||||
echo " Please remove the USB drive and reboot."
|
|
||||||
echo "============================================="
|
|
||||||
|
|
||||||
# Wait indefinitely for user action
|
|
||||||
sleep infinity
|
|
||||||
''; };
|
|
||||||
}
|
|
||||||
+179
-39
@@ -1,8 +1,31 @@
|
|||||||
{ pkgs, radiantwave, upgradeFlakeRef, radiantwavePlymouth, ... }:
|
{ config, pkgs, radiantwaveApp, upgradeFlakeRef, radiantwavePlymouth, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
radiantApp = radiantwave.packages.${pkgs.system}.default;
|
# The published app is a store path with context (builtins.storePath in
|
||||||
|
# flake.nix), so Nix substitutes it from the channel's Attic cache and the
|
||||||
|
# system closure depends on it.
|
||||||
|
radiantApp = radiantwaveApp;
|
||||||
|
# When the live-updater module is enabled, RadiantWave launches through the
|
||||||
|
# mutable /var/lib/radiantwave/current
|
||||||
|
# pointer so it can be swapped without a full nixos-rebuild. Otherwise it
|
||||||
|
# launches straight from the immutable system closure as before.
|
||||||
|
launchBin =
|
||||||
|
if config.services.radiantwaveUpdater.enable or false
|
||||||
|
then "/var/lib/radiantwave/current/bin/radiantwave"
|
||||||
|
else "/run/current-system/sw/bin/radiantwave";
|
||||||
myKey = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL4qWNZnYU5JaqR9c2qh6oFZktzVNBOx42PfDaGofCL6 cnclack@pm.me macbook" ];
|
myKey = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL4qWNZnYU5JaqR9c2qh6oFZktzVNBOx42PfDaGofCL6 cnclack@pm.me macbook" ];
|
||||||
|
|
||||||
|
# Headscale preauth key. Paste/replace here.
|
||||||
|
#
|
||||||
|
# This MUST be a *reusable* key with a long expiry (1 year is good), created
|
||||||
|
# with:
|
||||||
|
# headscale preauthkeys create --user <user-id> --reusable --expiration 8760h
|
||||||
|
#
|
||||||
|
# A single-use key only ever enrolls one node, which would mean a fresh key
|
||||||
|
# plus a rebuild for every kiosk that joins the fleet. A reusable key lets any
|
||||||
|
# number of kiosks enroll unattended, and the timer below retries the join so
|
||||||
|
# a node that misses its window at boot heals on its own.
|
||||||
|
headscaleAuthKey = "hskey-auth-UfU_eS_sdOu6-CjrqrVdDJFRFsPnaTtK5sFuCiVlffJAS7uQ1wX1_65MNk205WGsF4clOiBD3ZOV-";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
############################################################
|
############################################################
|
||||||
@@ -27,7 +50,7 @@ in
|
|||||||
# --- RadiantWave splash: Plymouth from boot until the app's first frame ---
|
# --- RadiantWave splash: Plymouth from boot until the app's first frame ---
|
||||||
boot.plymouth.enable = true;
|
boot.plymouth.enable = true;
|
||||||
boot.plymouth.theme = "radiantwave";
|
boot.plymouth.theme = "radiantwave";
|
||||||
boot.plymouth.themePackages = [ radiantwavePlymouth ];
|
boot.plymouth.themePackages = [ (radiantwavePlymouth pkgs) ];
|
||||||
|
|
||||||
# Silence the kernel/initrd output behind the splash. The module adds
|
# Silence the kernel/initrd output behind the splash. The module adds
|
||||||
# "splash" itself; we keep "quiet" plus show_status=false and loglevel 0.
|
# "splash" itself; we keep "quiet" plus show_status=false and loglevel 0.
|
||||||
@@ -55,26 +78,159 @@ in
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# Let the kiosk app apply pending updates by restarting its own TTY
|
||||||
|
# session (re-autologin -> fresh cage + RadiantWave). Scoped to exactly
|
||||||
|
# one unit and verb; everything else keeps stock (denied) policy.
|
||||||
|
security.polkit.enable = true;
|
||||||
|
environment.etc."polkit-1/rules.d/10-kiosk-getty.rules".text = ''
|
||||||
|
polkit.addRule(function(action, subject) {
|
||||||
|
if (subject.user !== "kiosk") return null;
|
||||||
|
if (action.id !== "org.freedesktop.systemd1.manage-units") return null;
|
||||||
|
var unit = action.lookup("unit");
|
||||||
|
var verb = action.lookup("verb");
|
||||||
|
if ((unit === "getty@tty1.service" || unit === "getty@tty1") && verb === "restart") {
|
||||||
|
return polkit.Result.YES;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
'';
|
||||||
|
|
||||||
hardware.graphics.enable = true;
|
hardware.graphics.enable = true;
|
||||||
|
|
||||||
time.timeZone = "America/Chicago";
|
time.timeZone = "America/Chicago";
|
||||||
|
|
||||||
networking.networkmanager.enable = true;
|
networking.networkmanager.enable = true;
|
||||||
|
|
||||||
nix.settings.access-tokens = "gitea.radiantwavetech.com=ff524968d31acc5c4da350d0884a87264d0e8881";
|
############################################################
|
||||||
|
# Tailscale (self-hosted Headscale control plane)
|
||||||
|
############################################################
|
||||||
|
# The node joins the RadiantWave Headscale server at
|
||||||
|
# hs.radiantwavetech.com using the *hardware serial* as its Tailscale
|
||||||
|
# node name (not networking.hostName), so a kiosk is always reachable at
|
||||||
|
# <serial> even though every variant shares one hostName per branch.
|
||||||
|
#
|
||||||
|
# The preauth key is set in `headscaleAuthKey` in the let block at the
|
||||||
|
# top of this file. NOTE: it ends up in the world-readable /nix/store, so
|
||||||
|
# keep this file out of public repos while a live key is in it.
|
||||||
|
services.tailscale = {
|
||||||
|
enable = true;
|
||||||
|
useRoutingFeatures = "client";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Reach the kiosk's services (SSH, etc.) over the tailnet without extra
|
||||||
|
# per-port rules. Membership is gated by the preauth key.
|
||||||
|
networking.firewall.trustedInterfaces = [ "tailscale0" ];
|
||||||
|
|
||||||
|
systemd.services.radiantwave-tailscale-auth = {
|
||||||
|
description = "Join the RadiantWave tailnet using the hardware serial as node name";
|
||||||
|
after = [ "tailscaled.service" "network-online.target" ];
|
||||||
|
wants = [ "tailscaled.service" "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
# Deliberately no RemainAfterExit: the unit has to fall back to inactive
|
||||||
|
# so the timer below can trigger it again. The script is idempotent, so
|
||||||
|
# re-running it on an already-joined kiosk is a cheap no-op.
|
||||||
|
};
|
||||||
|
path = [ pkgs.tailscale pkgs.coreutils pkgs.gnused pkgs.gnugrep ];
|
||||||
|
script = ''
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# Hardware serial. x86 exposes it via DMI; Raspberry Pi exposes it in
|
||||||
|
# the device tree (null-terminated), mirrored under /proc/device-tree.
|
||||||
|
serial=""
|
||||||
|
for f in \
|
||||||
|
/sys/class/dmi/id/product_serial \
|
||||||
|
/sys/class/dmi/id/board_serial \
|
||||||
|
/sys/firmware/devicetree/base/serial-number \
|
||||||
|
/proc/device-tree/serial-number
|
||||||
|
do
|
||||||
|
if [ -r "$f" ]; then
|
||||||
|
serial="$(tr -d '\000' < "$f" | tr -d '\n\r')"
|
||||||
|
[ -n "$serial" ] && break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Tailscale node names are RFC 1035 labels: lowercase, [a-z0-9-], <=63.
|
||||||
|
serial="$(printf '%s' "$serial" \
|
||||||
|
| tr '[:upper:]' '[:lower:]' \
|
||||||
|
| tr -c 'a-z0-9-' '-' \
|
||||||
|
| sed -e 's/-\{1,\}/-/g' -e 's/^-//' -e 's/-$//' \
|
||||||
|
| cut -c1-63)"
|
||||||
|
[ -n "$serial" ] || serial="radiantwave-unknown"
|
||||||
|
|
||||||
|
# Already joined? Bail out early. This is what makes the periodic
|
||||||
|
# re-invocation from the timer below cheap and safe to run forever.
|
||||||
|
# The JSON is pretty-printed with a space, so allow optional whitespace.
|
||||||
|
state="$(tailscale status --json 2>/dev/null \
|
||||||
|
| sed -n 's/.*"BackendState": *"\([A-Za-z]*\)".*/\1/p')"
|
||||||
|
if [ "$state" = "Running" ]; then
|
||||||
|
echo "radiantwave-tailscale-auth: already joined as '$serial'"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "radiantwave-tailscale-auth: joining as '$serial' (state: ''${state:-unknown})"
|
||||||
|
for _ in $(seq 1 30); do
|
||||||
|
if tailscale up \
|
||||||
|
--auth-key=${pkgs.lib.escapeShellArg headscaleAuthKey} \
|
||||||
|
--login-server=https://hs.radiantwavetech.com \
|
||||||
|
--hostname="$serial" \
|
||||||
|
--accept-dns=false
|
||||||
|
then
|
||||||
|
echo "radiantwave-tailscale-auth: connected"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
# Best-effort by design: do NOT exit non-zero here.
|
||||||
|
#
|
||||||
|
# This unit is part of the new generation that switch-to-configuration
|
||||||
|
# starts, and *any* failing unit among those aborts the switch with
|
||||||
|
# status 4/NOPERMISSION. While the preauth key was expired that is exactly
|
||||||
|
# what happened: this unit failed, which failed `switch-to-configuration`,
|
||||||
|
# which failed nixos-upgrade.service -- so the tailscale key silently
|
||||||
|
# stopped *all* system upgrades on the fleet every 10 minutes.
|
||||||
|
#
|
||||||
|
# The retry timer below is what actually recovers the join, so a failed
|
||||||
|
# attempt here must never take down the system switch with it.
|
||||||
|
echo "radiantwave-tailscale-auth: could not join tailnet after retries;" >&2
|
||||||
|
echo "radiantwave-tailscale-auth: leaving the join to the retry timer" >&2
|
||||||
|
exit 0
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# The join only needs to happen once per node, but it fails for reasons
|
||||||
|
# outside our control: the network may not be usable yet at boot, tailscaled
|
||||||
|
# may be restarted later, or the preauth key may be rotated out from under
|
||||||
|
# the fleet. A timer re-runs the (idempotent) join so a kiosk recovers on its
|
||||||
|
# own instead of needing a manual `systemctl restart`.
|
||||||
|
#
|
||||||
|
# This replaces the old behavior where the one-shot unit failed once at boot
|
||||||
|
# and stayed failed forever (Restart=no, NRestarts=0).
|
||||||
|
systemd.timers.radiantwave-tailscale-auth = {
|
||||||
|
description = "Periodically ensure the kiosk is joined to the tailnet";
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
# The join is attempted at boot via multi-user.target above; this is the
|
||||||
|
# first retry in case that one lost the race with the network.
|
||||||
|
OnBootSec = "5min";
|
||||||
|
# Measured from when the unit last went inactive, so a slow join doesn't
|
||||||
|
# immediately retrigger.
|
||||||
|
OnUnitInactiveSec = "10min";
|
||||||
|
Unit = "radiantwave-tailscale-auth.service";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
system.autoUpgrade = {
|
system.autoUpgrade = {
|
||||||
enable = true;
|
enable = true;
|
||||||
flake = upgradeFlakeRef;
|
flake = upgradeFlakeRef;
|
||||||
dates = "*:00/10";
|
dates = "*:00/10";
|
||||||
flags = [ "--refresh" "-L" ];
|
# --impure: the flake reads the channel's published manifest at eval time
|
||||||
|
# (builtins.fetchurl) to pick the app store path for the next generation.
|
||||||
|
flags = [ "--refresh" "--impure" "-L" ];
|
||||||
operation = "switch";
|
operation = "switch";
|
||||||
};
|
};
|
||||||
|
|
||||||
system.activationScripts.cleanRadiantFiles = ''
|
|
||||||
rm -rf /home/kiosk/.local/radiantwave/audio
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Virtual machine declaration
|
# Virtual machine declaration
|
||||||
virtualisation.vmVariant = {
|
virtualisation.vmVariant = {
|
||||||
boot.loader.systemd-boot.enable = pkgs.lib.mkForce false;
|
boot.loader.systemd-boot.enable = pkgs.lib.mkForce false;
|
||||||
@@ -98,6 +254,19 @@ in
|
|||||||
# NixOS settings.
|
# NixOS settings.
|
||||||
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||||
nix.settings.accept-flake-config = true;
|
nix.settings.accept-flake-config = true;
|
||||||
|
# The app baked into each generation comes from the channel's published
|
||||||
|
# manifest, fetched at eval time with `builtins.fetchurl` (see flake.nix).
|
||||||
|
# Disable the tarball cache so a rebuild during autoUpgrade always reads the
|
||||||
|
# current manifest instead of a copy up to an hour old.
|
||||||
|
nix.settings.tarball-ttl = 0;
|
||||||
|
nix.settings.extra-substituters = [
|
||||||
|
"https://attic.radiantwavetech.com/radiantwave"
|
||||||
|
"https://attic.radiantwavetech.com/radiantwave-dev"
|
||||||
|
];
|
||||||
|
nix.settings.extra-trusted-public-keys = [
|
||||||
|
"radiantwave:QADrjpBWJssTZ3KmiZtcWEf4uAm66oOlHmtQI3jkrgA="
|
||||||
|
"radiantwave-dev:B8FhLu3MQ9JfeEIHIKNrKxnP+3akzzkqMbTZR4hRfj0="
|
||||||
|
];
|
||||||
system.stateVersion = "24.05";
|
system.stateVersion = "24.05";
|
||||||
|
|
||||||
############################################################
|
############################################################
|
||||||
@@ -110,7 +279,6 @@ in
|
|||||||
uid = 1000;
|
uid = 1000;
|
||||||
extraGroups = ["audio" "video" "input" "networkmanager" "wheel" "seat"];
|
extraGroups = ["audio" "video" "input" "networkmanager" "wheel" "seat"];
|
||||||
openssh.authorizedKeys.keys = myKey;
|
openssh.authorizedKeys.keys = myKey;
|
||||||
password = "Fuckp@ssw0rds";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
services.getty.autologinUser = "kiosk";
|
services.getty.autologinUser = "kiosk";
|
||||||
@@ -142,7 +310,7 @@ in
|
|||||||
# Release the splash screen right before the compositor takes over the display
|
# Release the splash screen right before the compositor takes over the display
|
||||||
sudo -n plymouth quit --wait || true
|
sudo -n plymouth quit --wait || true
|
||||||
|
|
||||||
exec dbus-run-session cage -- /run/current-system/sw/bin/radiantwave -d -s > /tmp/kiosk.log 2>&1
|
exec dbus-run-session cage -- ${launchBin} -d -s > /tmp/kiosk.log 2>&1
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
|
|
||||||
@@ -238,32 +406,4 @@ in
|
|||||||
echo "Backup completed at $(date)" > "$BACKUP_DIR/.backup-timestamp"
|
echo "Backup completed at $(date)" > "$BACKUP_DIR/.backup-timestamp"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
# --- Health check service: runs after nixos-upgrade ---
|
|
||||||
systemd.services.radiantwave-healthcheck = {
|
|
||||||
description = "Verify RadiantWave is running after upgrade, rollback if not";
|
|
||||||
after = [ "nixos-upgrade.service" ];
|
|
||||||
wantedBy = [ "nixos-upgrade.service" ];
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
# Give cage time to start radiantwave after the switch
|
|
||||||
ExecStartPre = "${pkgs.coreutils}/bin/sleep 90";
|
|
||||||
};
|
|
||||||
script = ''
|
|
||||||
# The installed `radiantwave` is a makeWrapper script that execs the real
|
|
||||||
# binary (`.radiantwave-unwrapped`), so the process comm is not exactly
|
|
||||||
# `radiantwave`; match the exec'd binary's command line instead.
|
|
||||||
if ${pkgs.procps}/bin/pgrep -f '\.radiantwave-unwrapped' > /dev/null; then
|
|
||||||
echo "RadiantWave is running, upgrade successful"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "RadiantWave not found after upgrade, rolling back..."
|
|
||||||
|
|
||||||
# Roll back to previous NixOS generation
|
|
||||||
/run/current-system/sw/bin/nixos-rebuild switch --rollback
|
|
||||||
|
|
||||||
echo "Rollback completed at $(date)"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
-133
@@ -1,133 +0,0 @@
|
|||||||
{
|
|
||||||
pkgs,
|
|
||||||
inputs,
|
|
||||||
self,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
targetSystem =
|
|
||||||
self.nixosConfigurations.radiantwavekiosk-dev.config.system.build.toplevel;
|
|
||||||
|
|
||||||
diskoScript =
|
|
||||||
self.nixosConfigurations.radiantwavekiosk-dev.config.system.build.diskoScript;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
############################################################
|
|
||||||
# System configuration
|
|
||||||
############################################################
|
|
||||||
|
|
||||||
nix.settings = {
|
|
||||||
experimental-features = [
|
|
||||||
"nix-command"
|
|
||||||
"flakes"
|
|
||||||
];
|
|
||||||
|
|
||||||
substituters = [
|
|
||||||
"https://cache.nixos.org"
|
|
||||||
];
|
|
||||||
|
|
||||||
flake-registry = "";
|
|
||||||
|
|
||||||
connect-timeout = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
nix.registry.nixpkgs = pkgs.lib.mkForce {
|
|
||||||
to = {
|
|
||||||
type = "path";
|
|
||||||
path = "${inputs.nixpkgs}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
nix.extraOptions = ''
|
|
||||||
netrc-file = /etc/nix/netrc
|
|
||||||
'';
|
|
||||||
|
|
||||||
environment.variables.KIOSK_FLAKE = "${self}";
|
|
||||||
|
|
||||||
environment.etc."nix/netrc" = {
|
|
||||||
text = ''
|
|
||||||
password eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIwODgzODk2ODEsIm5iZiI6MTc3MjgxMzY4MSwic3ViIjoia2lvc2siLCJodHRwczovL2p3dC5hdHRpYy5ycy92MSI6eyJjYWNoZXMiOnsia2lvc2siOnsiciI6MX19fX0.N6Ktrmmu1LSy2PP8uNF25VHRP1dxPZJ4M0MR1kUpVi0
|
|
||||||
'';
|
|
||||||
mode = "0400";
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.networkmanager.enable = true;
|
|
||||||
|
|
||||||
############################################################
|
|
||||||
# User configuration
|
|
||||||
############################################################
|
|
||||||
|
|
||||||
users.users.root.openssh.authorizedKeys.keys = [
|
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL4qWNZnYU5JaqR9c2qh6oFZktzVNBOx42PfDaGofCL6 cnclack@pm.me macbook"
|
|
||||||
];
|
|
||||||
|
|
||||||
############################################################
|
|
||||||
# Service configuration
|
|
||||||
############################################################
|
|
||||||
|
|
||||||
services.openssh.enable = true;
|
|
||||||
|
|
||||||
systemd.services.NetworkManager-wait-online.enable = false;
|
|
||||||
|
|
||||||
systemd.services.zero-touch-install = {
|
|
||||||
description = "Zero-Touch Kiosk Installation";
|
|
||||||
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
|
|
||||||
# Make sure we own tty1 exclusively - getty@tty1 will otherwise
|
|
||||||
# try to spawn a login prompt on the same console.
|
|
||||||
conflicts = [ "getty@tty1.service" ];
|
|
||||||
after = [ "systemd-udev-settle.service" "getty@tty1.service" ];
|
|
||||||
|
|
||||||
path = with pkgs; [
|
|
||||||
nix
|
|
||||||
git
|
|
||||||
util-linux
|
|
||||||
nixos-install-tools
|
|
||||||
curl
|
|
||||||
networkmanager
|
|
||||||
mount
|
|
||||||
umount
|
|
||||||
disko
|
|
||||||
];
|
|
||||||
|
|
||||||
environment = {
|
|
||||||
NIX_PATH = "nixpkgs=${inputs.nixpkgs}";
|
|
||||||
KIOSK_FLAKE = "${self}";
|
|
||||||
DISKO_SCRIPT = "${diskoScript}";
|
|
||||||
};
|
|
||||||
|
|
||||||
serviceConfig = {
|
|
||||||
StandardInput = "tty";
|
|
||||||
StandardOutput = "tty";
|
|
||||||
StandardError = "tty";
|
|
||||||
TTYPath = "/dev/tty1";
|
|
||||||
TTYReset = true;
|
|
||||||
TTYVHangup = true;
|
|
||||||
TTYVTDisallocate = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
echo "Running disko script..."
|
|
||||||
echo "Script: $DISKO_SCRIPT"
|
|
||||||
|
|
||||||
${diskoScript}
|
|
||||||
|
|
||||||
echo "Installing RadiantWave"
|
|
||||||
echo "Closure Path: ${targetSystem}"
|
|
||||||
|
|
||||||
nixos-install \
|
|
||||||
--system "${targetSystem}" \
|
|
||||||
--no-root-passwd \
|
|
||||||
--no-channel-copy
|
|
||||||
|
|
||||||
echo "============================================"
|
|
||||||
echo " Installation complete!"
|
|
||||||
echo " Please shut down and remove the flash drive before restarting"
|
|
||||||
echo "============================================"
|
|
||||||
sleep 10000000
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.kioskEdid;
|
||||||
|
|
||||||
|
overrideModule = types.submodule {
|
||||||
|
options = {
|
||||||
|
connector = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
example = "HDMI-A-1";
|
||||||
|
description = ''
|
||||||
|
DRM connector name to apply the EDID override to.
|
||||||
|
Check `ls /sys/class/drm/` on target hardware to confirm the
|
||||||
|
exact name (e.g. HDMI-A-1, HDMI-A-2).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
edidFile = mkOption {
|
||||||
|
type = types.path;
|
||||||
|
description = ''
|
||||||
|
Path to the binary EDID file to force for this connector.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
forceConnected = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Also force this connector to be treated as connected
|
||||||
|
(video=<connector>:e), in case the real link reports
|
||||||
|
disconnected/garbled state through a splitter or wireless kit.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.kioskEdid = {
|
||||||
|
enable = mkEnableOption "forcing custom EDIDs on one or more display connectors";
|
||||||
|
|
||||||
|
overrides = mkOption {
|
||||||
|
type = types.listOf overrideModule;
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
List of per-connector EDID overrides to apply. Each entry gets its
|
||||||
|
own firmware file and its own drm.edid_firmware kernel param.
|
||||||
|
'';
|
||||||
|
example = literalExpression ''
|
||||||
|
[
|
||||||
|
{ connector = "HDMI-A-1"; edidFile = ./android-4k30-edid.bin; }
|
||||||
|
{ connector = "HDMI-A-2"; edidFile = ./android-4k30-edid.bin; }
|
||||||
|
]
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
hardware.firmware = map
|
||||||
|
(o: pkgs.runCommand "kiosk-edid-firmware-${o.connector}" { } ''
|
||||||
|
mkdir -p $out/lib/firmware/edid
|
||||||
|
cp ${o.edidFile} $out/lib/firmware/edid/${o.connector}.bin
|
||||||
|
'')
|
||||||
|
cfg.overrides;
|
||||||
|
|
||||||
|
boot.kernelParams = flatten (map
|
||||||
|
(o:
|
||||||
|
[ "drm.edid_firmware=${o.connector}:edid/${o.connector}.bin" ]
|
||||||
|
++ optional o.forceConnected "video=${o.connector}:e"
|
||||||
|
)
|
||||||
|
cfg.overrides);
|
||||||
|
};
|
||||||
|
}
|
||||||
Generated
+1
-141
@@ -51,42 +51,6 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-utils": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1731533236,
|
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils_2": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems_2"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1731533236,
|
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixos-facter-modules": {
|
"nixos-facter-modules": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1773858690,
|
"lastModified": 1773858690,
|
||||||
@@ -182,116 +146,12 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_3": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1776877367,
|
|
||||||
"narHash": "sha256-EHq1/OX139R1RvBzOJ0aMRT3xnWyqtHBRUBuO1gFzjI=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "0726a0ecb6d4e08f6adced58726b95db924cef57",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_4": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1776877367,
|
|
||||||
"narHash": "sha256-EHq1/OX139R1RvBzOJ0aMRT3xnWyqtHBRUBuO1gFzjI=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "0726a0ecb6d4e08f6adced58726b95db924cef57",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"radiantwave": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"nixpkgs": "nixpkgs_3"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1786170011,
|
|
||||||
"narHash": "sha256-Vx/3kwIQFRYDZQtVdcdGgJPnNa2Y44HRYZyJAeug6OI=",
|
|
||||||
"ref": "main",
|
|
||||||
"rev": "312f5e2a90e03f32ce61d6db54f46b9adac60933",
|
|
||||||
"revCount": 250,
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"ref": "main",
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"radiantwave-legacy": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils_2",
|
|
||||||
"nixpkgs": "nixpkgs_4"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1786170011,
|
|
||||||
"narHash": "sha256-Vx/3kwIQFRYDZQtVdcdGgJPnNa2Y44HRYZyJAeug6OI=",
|
|
||||||
"ref": "main",
|
|
||||||
"rev": "312f5e2a90e03f32ce61d6db54f46b9adac60933",
|
|
||||||
"revCount": 250,
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://fb62d6c0cb4e2ca3b7ed6e08b4942c29ed071cef@gitea.radiantwavetech.com/RadiantWaveTech/radiantwave-legacy.git"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"ref": "main",
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://fb62d6c0cb4e2ca3b7ed6e08b4942c29ed071cef@gitea.radiantwavetech.com/RadiantWaveTech/radiantwave-legacy.git"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"disko": "disko",
|
"disko": "disko",
|
||||||
"nixos-facter-modules": "nixos-facter-modules",
|
"nixos-facter-modules": "nixos-facter-modules",
|
||||||
"nixos-raspberrypi": "nixos-raspberrypi",
|
"nixos-raspberrypi": "nixos-raspberrypi",
|
||||||
"nixpkgs": "nixpkgs_2",
|
"nixpkgs": "nixpkgs_2"
|
||||||
"radiantwave": "radiantwave",
|
|
||||||
"radiantwave-legacy": "radiantwave-legacy"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
disko.url = "github:nix-community/disko";
|
disko.url = "github:nix-community/disko";
|
||||||
disko.inputs.nixpkgs.follows = "nixpkgs";
|
disko.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
nixos-facter-modules.url = "github:numtide/nixos-facter-modules";
|
nixos-facter-modules.url = "github:numtide/nixos-facter-modules";
|
||||||
radiantwave.url = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/radiantwave.git?ref=main";
|
|
||||||
radiantwave-legacy.url = "git+https://fb62d6c0cb4e2ca3b7ed6e08b4942c29ed071cef@gitea.radiantwavetech.com/RadiantWaveTech/radiantwave-legacy.git?ref=main";
|
|
||||||
nixos-raspberrypi.url = "github:nvmd/nixos-raspberrypi/main";
|
nixos-raspberrypi.url = "github:nvmd/nixos-raspberrypi/main";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -15,8 +13,6 @@
|
|||||||
nixpkgs,
|
nixpkgs,
|
||||||
disko,
|
disko,
|
||||||
nixos-facter-modules,
|
nixos-facter-modules,
|
||||||
radiantwave,
|
|
||||||
radiantwave-legacy,
|
|
||||||
nixos-raspberrypi,
|
nixos-raspberrypi,
|
||||||
...
|
...
|
||||||
}@inputs:
|
}@inputs:
|
||||||
@@ -28,11 +24,12 @@
|
|||||||
# 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:
|
||||||
|
targetPkgs.stdenv.mkDerivation {
|
||||||
pname = "radiantwave-plymouth";
|
pname = "radiantwave-plymouth";
|
||||||
version = "1.0.0";
|
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;
|
||||||
@@ -42,8 +39,8 @@
|
|||||||
rsvg-convert \
|
rsvg-convert \
|
||||||
--keep-aspect-ratio \
|
--keep-aspect-ratio \
|
||||||
--background-color=#111318 \
|
--background-color=#111318 \
|
||||||
--width=3840 \
|
--width=1280 \
|
||||||
--height=2160 \
|
--height=720 \
|
||||||
--output=$out/share/plymouth/themes/radiantwave/splash.png \
|
--output=$out/share/plymouth/themes/radiantwave/splash.png \
|
||||||
$src
|
$src
|
||||||
sed 's,/usr/,'"$out"'/,g' ${./plymouth-theme/radiantwave.plymouth} \
|
sed 's,/usr/,'"$out"'/,g' ${./plymouth-theme/radiantwave.plymouth} \
|
||||||
@@ -53,36 +50,129 @@
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
mkKioskSystem = { system, radiantwave, upgradeFlakeRef, extraModules ? [], diskModule, extraSpecialArgs ? {}}:
|
revision = self.rev or self.dirtyRev or null;
|
||||||
|
|
||||||
|
# DEV/PROD CHANNEL SELECTOR — the single block that differs between the
|
||||||
|
# `main` and `dev` branches. Everything downstream (the four configs, the
|
||||||
|
# ISOs, the updater module) is identical on both branches and pulls from
|
||||||
|
# this channel, so a `dev`-to-`main` merge carries your changes with it
|
||||||
|
# automatically. There are no more `-dev` sibling configs to copy.
|
||||||
|
#
|
||||||
|
# main: atticRepo = "radiantwave", gitRef = ""
|
||||||
|
# dev: atticRepo = "radiantwave-dev", gitRef = "?ref=dev"
|
||||||
|
#
|
||||||
|
# To flip branches, change ONLY these two values (then push/merge).
|
||||||
|
#
|
||||||
|
# Note there is deliberately NO app version here. The RadiantWave app is
|
||||||
|
# not built or pinned by this repo; each channel's published manifest is
|
||||||
|
# the single source of truth (see `publishedApp` below).
|
||||||
|
channel = {
|
||||||
|
atticRepo = "radiantwave"; # main
|
||||||
|
gitRef = ""; # main
|
||||||
|
};
|
||||||
|
|
||||||
|
# Exposed so CI (see .gitea/workflows/build.yml) and humans can verify
|
||||||
|
# which channel this checkout resolves to. On `main` this is
|
||||||
|
# "radiantwave"; on `dev` it is "radiantwave-dev".
|
||||||
|
channelName = channel.atticRepo;
|
||||||
|
|
||||||
|
# Channel metadata shared by the configs and the CI preflight.
|
||||||
|
channelAtticUrl = "https://attic.radiantwavetech.com/${channel.atticRepo}";
|
||||||
|
channelManifestDir = "https://files.radiantwavetech.com/${channel.atticRepo}";
|
||||||
|
|
||||||
|
# The baked RadiantWave app is whatever the channel's published manifest
|
||||||
|
# currently announces. RadiantWave CI builds the app, pushes the closure
|
||||||
|
# to the channel's Attic cache, and writes {version, storePath} to the
|
||||||
|
# manifest. We fetch that manifest at eval time and use its storePath
|
||||||
|
# verbatim, so an image always carries exactly the published closure and
|
||||||
|
# never compiles RadiantWave from source. This is an *impure* fetch (no
|
||||||
|
# hash), so every eval that reaches the app needs `--impure`: CI, the
|
||||||
|
# kiosk's system.autoUpgrade, and local `build-rpi.sh` all pass it.
|
||||||
|
manifestFor = manifestFile:
|
||||||
|
builtins.fromJSON (builtins.readFile (builtins.fetchurl {
|
||||||
|
url = "${channelManifestDir}/${manifestFile}";
|
||||||
|
}));
|
||||||
|
|
||||||
|
# The published closure as a plain store-path string. Used for the
|
||||||
|
# updater's runtime baseline pointer (a plain string; the closure itself
|
||||||
|
# is pulled in via publishedApp below).
|
||||||
|
publishedPath = manifestFile: (manifestFor manifestFile).storePath;
|
||||||
|
|
||||||
|
# The published closure as a real store-path dependency.
|
||||||
|
# builtins.storePath makes the derivation depend on this exact path and
|
||||||
|
# substitute it from the channel's Attic cache (configured as a trusted
|
||||||
|
# substituter), so the app is never compiled from source. The closure
|
||||||
|
# itself is not fetched by this repository; RadiantWave CI publishes it.
|
||||||
|
publishedApp = manifestFile: builtins.storePath (publishedPath manifestFile);
|
||||||
|
|
||||||
|
# Live app-update side channel. The manifest is served by the
|
||||||
|
# fileserver; closures are fetched (never built) from the channel's
|
||||||
|
# Attic cache. `repo` selects the channel — "radiantwave" (main) or
|
||||||
|
# "radiantwave-dev" (dev) — and `manifestFile` selects the per-arch
|
||||||
|
# manifest (default "manifest.json"; Raspberry Pi uses
|
||||||
|
# "aarch64-manifest.json"). All variants follow this same update
|
||||||
|
# procedure; only the channel/arch differ.
|
||||||
|
radiantwaveUpdaterModule = { repo, baseline, manifestFile ? "manifest.json" }: {
|
||||||
|
services.radiantwaveUpdater = {
|
||||||
|
enable = true;
|
||||||
|
baselinePath = baseline;
|
||||||
|
manifestUrl = "https://files.radiantwavetech.com/${repo}/${manifestFile}";
|
||||||
|
atticUrl = "https://attic.radiantwavetech.com/${repo}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
mkKioskSystem = {
|
||||||
|
system,
|
||||||
|
upgradeFlakeRef,
|
||||||
|
hostName,
|
||||||
|
diskModule,
|
||||||
|
extraModules ? [],
|
||||||
|
}:
|
||||||
nixpkgs.lib.nixosSystem {
|
nixpkgs.lib.nixosSystem {
|
||||||
inherit system;
|
inherit system;
|
||||||
|
|
||||||
specialArgs = {
|
specialArgs = {
|
||||||
inherit radiantwave;
|
|
||||||
inherit upgradeFlakeRef;
|
inherit upgradeFlakeRef;
|
||||||
inherit radiantwavePlymouth;
|
radiantwaveApp = publishedApp "manifest.json";
|
||||||
} // extraSpecialArgs;
|
radiantwavePlymouth = mkRadiantwavePlymouth;
|
||||||
|
};
|
||||||
|
|
||||||
modules = [
|
modules = [
|
||||||
{ nixpkgs.config.allowUnfree = true; }
|
{ nixpkgs.config.allowUnfree = true; }
|
||||||
|
{
|
||||||
|
networking.hostName = hostName;
|
||||||
|
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
|
||||||
|
# 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; }
|
{ facter.reportPath = ./facter.json; }
|
||||||
radiantwave.nixosModules.default
|
|
||||||
] ++ extraModules;
|
] ++ extraModules;
|
||||||
};
|
};
|
||||||
|
|
||||||
mkRpiKioskSystem = { radiantwave, upgradeFlakeRef, extraModules ? [] }:
|
mkRpiKioskSystem = {
|
||||||
|
upgradeFlakeRef,
|
||||||
|
hostName,
|
||||||
|
extraModules ? [],
|
||||||
|
}:
|
||||||
nixos-raspberrypi.lib.nixosSystem {
|
nixos-raspberrypi.lib.nixosSystem {
|
||||||
specialArgs = {
|
specialArgs = {
|
||||||
inherit radiantwave upgradeFlakeRef;
|
inherit upgradeFlakeRef;
|
||||||
inherit nixos-raspberrypi;
|
inherit nixos-raspberrypi;
|
||||||
inherit radiantwavePlymouth;
|
radiantwaveApp = publishedApp "aarch64-manifest.json";
|
||||||
|
radiantwavePlymouth = mkRadiantwavePlymouth;
|
||||||
};
|
};
|
||||||
|
|
||||||
modules = [
|
modules = [
|
||||||
|
{ nixpkgs.config.allowUnfree = true; }
|
||||||
({ ... }: {
|
({ ... }: {
|
||||||
imports = with nixos-raspberrypi.nixosModules; [
|
imports = with nixos-raspberrypi.nixosModules; [
|
||||||
sd-image
|
sd-image
|
||||||
@@ -92,8 +182,11 @@
|
|||||||
];
|
];
|
||||||
})
|
})
|
||||||
./configuration.nix
|
./configuration.nix
|
||||||
radiantwave.nixosModules.default
|
./radiantwave-updater.nix
|
||||||
{
|
{
|
||||||
|
networking.hostName = hostName;
|
||||||
|
system.configurationRevision = revision;
|
||||||
|
|
||||||
boot.loader.systemd-boot.enable = nixpkgs.lib.mkForce false;
|
boot.loader.systemd-boot.enable = nixpkgs.lib.mkForce false;
|
||||||
boot.loader.efi.canTouchEfiVariables = nixpkgs.lib.mkForce false;
|
boot.loader.efi.canTouchEfiVariables = nixpkgs.lib.mkForce false;
|
||||||
boot.loader.raspberry-pi.bootloader = "kernel";
|
boot.loader.raspberry-pi.bootloader = "kernel";
|
||||||
@@ -107,155 +200,116 @@
|
|||||||
}
|
}
|
||||||
] ++ extraModules;
|
] ++ extraModules;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Zero-touch USB installer image. `targetConfig` names the kiosk system
|
||||||
|
# to install: the closure comes from the packages output below and the
|
||||||
|
# partitioning script from the paired disk module, so the ISO always
|
||||||
|
# installs the matching system for its hardware.
|
||||||
|
mkIso = { targetConfig, diskModule }:
|
||||||
|
nixpkgs.lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
|
||||||
|
specialArgs = {
|
||||||
|
inherit inputs;
|
||||||
|
inherit self;
|
||||||
|
};
|
||||||
|
|
||||||
|
modules = [
|
||||||
|
"${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix"
|
||||||
|
disko.nixosModules.disko
|
||||||
|
(import ./zero-touch-installer.nix { inherit targetConfig; })
|
||||||
|
diskModule
|
||||||
|
({ pkgs, ... }: {
|
||||||
|
environment.systemPackages = [
|
||||||
|
disko.packages.${system}.default
|
||||||
|
];
|
||||||
|
isoImage.storeContents = [
|
||||||
|
self.packages.x86_64-linux.${targetConfig}
|
||||||
|
];
|
||||||
|
environment.etc."disko-config.nix".source = diskModule;
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
inherit channelName channelAtticUrl channelManifestDir;
|
||||||
|
|
||||||
packages.x86_64-linux.radiantwavekiosk = self.nixosConfigurations.radiantwavekiosk.config.system.build.toplevel;
|
packages.x86_64-linux.radiantwavekiosk = self.nixosConfigurations.radiantwavekiosk.config.system.build.toplevel;
|
||||||
packages.x86_64-linux.radiantwavekiosk-dev = self.nixosConfigurations.radiantwavekiosk-dev.config.system.build.toplevel;
|
|
||||||
packages.x86_64-linux.radiantwavekiosk-commercial = self.nixosConfigurations.radiantwavekiosk-commercial.config.system.build.toplevel;
|
packages.x86_64-linux.radiantwavekiosk-commercial = self.nixosConfigurations.radiantwavekiosk-commercial.config.system.build.toplevel;
|
||||||
packages.x86_64-linux.radiantwavekiosk-android = self.nixosConfigurations.radiantwavekiosk-android.config.system.build.toplevel;
|
packages.x86_64-linux.radiantwavekiosk-android = self.nixosConfigurations.radiantwavekiosk-android.config.system.build.toplevel;
|
||||||
# Kiosk
|
packages.x86_64-linux.radiantwavekiosk-rpi = self.nixosConfigurations.radiantwavekiosk-rpi.config.system.build.toplevel;
|
||||||
|
|
||||||
|
# Kiosk systems — one per hardware target, no dev/prod siblings.
|
||||||
|
#
|
||||||
|
# Every config consumes the channel selected at the top of this file
|
||||||
|
# (see `channel`): the published manifest whose closure is baked as the
|
||||||
|
# app, the Attic repo/manifest it fetches live updates from, and the
|
||||||
|
# KioskConfig git ref it auto-upgrades from. On `main` the channel is
|
||||||
|
# prod; on `dev` it is the dev channel. Because there is exactly one
|
||||||
|
# config per hardware type, changes made on `dev` reach prod via a plain
|
||||||
|
# merge — no copying.
|
||||||
nixosConfigurations = {
|
nixosConfigurations = {
|
||||||
radiantwavekiosk = mkKioskSystem {
|
radiantwavekiosk = mkKioskSystem {
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
radiantwave = radiantwave-legacy;
|
hostName = "radiantwavekiosk";
|
||||||
diskModule = ./disk-config.nix;
|
diskModule = ./disk-config.nix;
|
||||||
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git#radiantwavekiosk";
|
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk";
|
||||||
extraModules = [];
|
extraModules = [
|
||||||
|
(radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = publishedPath "manifest.json"; })
|
||||||
|
];
|
||||||
};
|
};
|
||||||
# Kiosk-dev
|
|
||||||
radiantwavekiosk-dev = mkKioskSystem {
|
|
||||||
system = "x86_64-linux";
|
|
||||||
radiantwave = radiantwave;
|
|
||||||
diskModule = ./disk-config.nix;
|
|
||||||
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git?ref=dev#radiantwavekiosk-dev";
|
|
||||||
extraModules = [];
|
|
||||||
};
|
|
||||||
# Commercial
|
|
||||||
radiantwavekiosk-commercial = mkKioskSystem {
|
radiantwavekiosk-commercial = mkKioskSystem {
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
radiantwave = radiantwave-legacy;
|
hostName = "radiantwavekiosk-commercial";
|
||||||
diskModule = ./commercial-disk-config.nix;
|
diskModule = ./commercial-disk-config.nix;
|
||||||
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git#radiantwavekiosk-commercial";
|
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-commercial";
|
||||||
extraModules = [];
|
extraModules = [
|
||||||
|
(radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = publishedPath "manifest.json"; })
|
||||||
|
];
|
||||||
};
|
};
|
||||||
# Commercial-dev
|
# Rotated screen for android-style displays.
|
||||||
radiantwavekiosk-commercial-dev = mkKioskSystem {
|
|
||||||
system = "x86_64-linux";
|
|
||||||
radiantwave = radiantwave;
|
|
||||||
diskModule = ./commercial-disk-config.nix;
|
|
||||||
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git?ref=dev#radiantwavekiosk-commercial";
|
|
||||||
extraModules = [];
|
|
||||||
};
|
|
||||||
# Kiosk - rotated screen for android displays
|
|
||||||
radiantwavekiosk-android = mkKioskSystem {
|
radiantwavekiosk-android = mkKioskSystem {
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
radiantwave = radiantwave-legacy;
|
hostName = "radiantwavekiosk-android";
|
||||||
diskModule = ./disk-config.nix;
|
diskModule = ./disk-config.nix;
|
||||||
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git#radiantwavekiosk-android";
|
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-android";
|
||||||
extraModules = [./rotated_display.nix ];
|
extraModules = [
|
||||||
|
./rotated_display.nix
|
||||||
|
(radiantwaveUpdaterModule { repo = channel.atticRepo; baseline = publishedPath "manifest.json"; })
|
||||||
|
];
|
||||||
};
|
};
|
||||||
# Raspberry pi 5
|
# Raspberry Pi 5.
|
||||||
radiantwavekiosk-rpi = mkRpiKioskSystem {
|
radiantwavekiosk-rpi = mkRpiKioskSystem {
|
||||||
radiantwave = radiantwave-legacy;
|
hostName = "radiantwavekiosk-rpi";
|
||||||
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git#radiantwavekiosk-rpi";
|
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git${channel.gitRef}#radiantwavekiosk-rpi";
|
||||||
};
|
extraModules = [
|
||||||
# Raspberry pi 5 dev
|
(radiantwaveUpdaterModule {
|
||||||
radiantwavekiosk-rpi-dev = mkRpiKioskSystem {
|
repo = channel.atticRepo;
|
||||||
radiantwave = radiantwave;
|
baseline = publishedPath "aarch64-manifest.json";
|
||||||
upgradeFlakeRef = "git+https://gitea.radiantwavetech.com/RadiantWaveTech/KioskConfig.git?ref=dev#radiantwavekiosk-rpi-dev";
|
manifestFile = "aarch64-manifest.json";
|
||||||
|
})
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
# === USB Images ===
|
# === USB Images ===
|
||||||
# Kiosk ISO
|
# Each ISO installs exactly the named system closure using that
|
||||||
kiosk-iso = nixpkgs.lib.nixosSystem {
|
# system's disk layout. NVMe layouts pair with ./disk-config.nix,
|
||||||
system = "x86_64-linux";
|
# SATA with ./commercial-disk-config.nix — don't cross them.
|
||||||
specialArgs = {
|
# The same names build dev-flavored images when the flake is checked
|
||||||
inherit inputs;
|
# out on the `dev` branch (the channel block above selects the dev
|
||||||
inherit self;
|
# manifest, attic repo, and upgrade ref), so there are no separate
|
||||||
|
# -dev ISOs.
|
||||||
|
kiosk-iso = mkIso {
|
||||||
|
targetConfig = "radiantwavekiosk";
|
||||||
|
diskModule = ./disk-config.nix;
|
||||||
};
|
};
|
||||||
modules = [
|
commercial-iso = mkIso {
|
||||||
"${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix"
|
targetConfig = "radiantwavekiosk-commercial";
|
||||||
disko.nixosModules.disko
|
diskModule = ./commercial-disk-config.nix;
|
||||||
./iso-config.nix
|
|
||||||
./disk-config.nix
|
|
||||||
({ pkgs, ... }: {
|
|
||||||
environment.systemPackages = [
|
|
||||||
disko.packages.${system}.default
|
|
||||||
];
|
|
||||||
isoImage.storeContents = [
|
|
||||||
self.packages.x86_64-linux.radiantwavekiosk
|
|
||||||
];
|
|
||||||
environment.etc."disko-config.nix".source = ./disk-config.nix;
|
|
||||||
})
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
# Commercial iso
|
android-iso = mkIso {
|
||||||
commercial-iso = nixpkgs.lib.nixosSystem {
|
targetConfig = "radiantwavekiosk-android";
|
||||||
system = "x86_64-linux";
|
diskModule = ./disk-config.nix;
|
||||||
specialArgs = {
|
|
||||||
inherit inputs;
|
|
||||||
inherit self;
|
|
||||||
};
|
|
||||||
modules = [
|
|
||||||
"${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix"
|
|
||||||
disko.nixosModules.disko
|
|
||||||
./commercial-iso-config.nix
|
|
||||||
./commercial-disk-config.nix
|
|
||||||
({ pkgs, ... }: {
|
|
||||||
environment.systemPackages = [
|
|
||||||
disko.packages.${system}.default
|
|
||||||
];
|
|
||||||
isoImage.storeContents = [
|
|
||||||
self.packages.x86_64-linux.radiantwavekiosk-commercial
|
|
||||||
];
|
|
||||||
environment.etc."disko-config.nix".source = ./commercial-disk-config.nix;
|
|
||||||
})
|
|
||||||
];
|
|
||||||
};
|
|
||||||
# kiosk rotated screen ISO
|
|
||||||
android-iso = 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
|
|
||||||
./iso-config.nix
|
|
||||||
./disk-config.nix
|
|
||||||
({ pkgs, ... }: {
|
|
||||||
environment.systemPackages = [
|
|
||||||
disko.packages.${system}.default
|
|
||||||
];
|
|
||||||
isoImage.storeContents = [
|
|
||||||
self.packages.x86_64-linux.radiantwavekiosk-android
|
|
||||||
];
|
|
||||||
environment.etc."disko-config.nix".source = ./disk-config.nix;
|
|
||||||
})
|
|
||||||
];
|
|
||||||
};
|
|
||||||
# Kiosk iso development branch
|
|
||||||
dev-iso = nixpkgs.lib.nixosSystem {
|
|
||||||
system = "x86_64-linux";
|
|
||||||
specialArgs = {
|
|
||||||
inherit inputs;
|
|
||||||
inherit self;
|
|
||||||
};
|
|
||||||
modules = [
|
|
||||||
"${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix"
|
|
||||||
disko.nixosModules.disko
|
|
||||||
./dev-iso.nix
|
|
||||||
./disk-config.nix
|
|
||||||
({ pkgs, ... }: {
|
|
||||||
environment.systemPackages = [
|
|
||||||
disko.packages.${system}.default
|
|
||||||
];
|
|
||||||
isoImage.storeContents = [
|
|
||||||
self.packages.x86_64-linux.radiantwavekiosk-dev
|
|
||||||
];
|
|
||||||
environment.etc."disko-config.nix".source = ./disk-config.nix;
|
|
||||||
})
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,15 @@
|
|||||||
|
KioskConfig Commit,Date (CDT),URL Change,Pinned App Revision,App Revision Date,App Version on Home Screen
|
||||||
|
c06a3f7,2026-07-27 14:13,"codeberg.org/RhemaWave-Technologies/RhemaWave.git",9f8212cf550b,2026-06-27,2.3.5.8
|
||||||
|
5a6031a,2026-07-27 14:23,Codeberg → gitea.radiantwavetech.com/.../RhemaWave.git,9f8212cf550b,2026-06-27,2.3.5.8
|
||||||
|
7f2f19f,2026-07-27 14:24,RhemaWave.git → radiantwave.git,9f8212cf550b,2026-06-27,2.3.5.8
|
||||||
|
4af16d9,2026-07-28 10:43,Add ?ref=main,6ad6516a7461,2026-07-02,Coming Soon
|
||||||
|
cbb3e05,2026-08-07 17:13,Dev → radiantwave-cpp.git?ref=dev,b54dbca2d5e7,2026-08-03,07292026_1618
|
||||||
|
37d18d3,2026-08-07 17:18,Fix: dropped git+ prefix on dev URL,b54dbca2d5e7,2026-08-03,07292026_1618
|
||||||
|
50a1f3e,2026-08-07 17:19,Fix: restored git+ prefix on dev URL,b54dbca2d5e7,2026-08-03,07292026_1618
|
||||||
|
e3120f2,2026-08-08 00:02,Dev back to radiantwave.git?ref=dev,b54dbca2d5e7,2026-08-03,07292026_1618
|
||||||
|
50176d8,2026-08-17 10:51,New radiantwave-legacy input added,312f5e2a90e0,2026-08-08,08082026_0120
|
||||||
|
0cb1337,2026-08-17 10:57,Auth token added to legacy URL,312f5e2a90e0,2026-08-08,08082026_0120
|
||||||
|
fb2f861,2026-08-17 12:48,Prod pointed to radiantwave-legacy.git,312f5e2a90e0,2026-08-08,08082026_0120
|
||||||
|
027d1a6,2026-08-17 12:50,Token rotated (fb62d6c → 794ef1d),312f5e2a90e0,2026-08-08,08082026_0120
|
||||||
|
14463ad,2026-08-17 12:52,Token removed (repo made public),312f5e2a90e0,2026-08-08,08082026_0120
|
||||||
|
e09758a,2026-08-17 12:54,HTTPS → SSH (:2222),312f5e2a90e0,2026-08-08,08082026_0120
|
||||||
|
@@ -0,0 +1,98 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.radiantwaveUpdater;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.radiantwaveUpdater = {
|
||||||
|
enable = lib.mkEnableOption "live RadiantWave app updates via a polled manifest";
|
||||||
|
|
||||||
|
manifestUrl = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "HTTP(S) URL of the JSON manifest announcing the current app store path.";
|
||||||
|
};
|
||||||
|
|
||||||
|
atticUrl = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Attic cache URL to fetch the app closure from (fetch-only, never builds).";
|
||||||
|
};
|
||||||
|
|
||||||
|
baselinePath = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Store path of the RadiantWave closure baked into the system closure (from the channel manifest), used to seed the runtime pointer before the first update.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
# Runtime pointer the login shell launches RadiantWave from. Seeded on
|
||||||
|
# first boot to the exact closure the channel manifest announced (the same
|
||||||
|
# path the updater would fetch), so no spurious first update is triggered.
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"L /var/lib/radiantwave/current - - - - ${cfg.baselinePath}"
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd.services.radiantwave-updater = {
|
||||||
|
description = "Poll the RadiantWave manifest and swap the live app pointer";
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
# NOTE: no RemainAfterExit here. Keeping the oneshot "active" after
|
||||||
|
# it finishes breaks the timer's OnUnitActiveSec re-arming, so the
|
||||||
|
# 2-min cadence stops after the first run (NEXT goes empty, Trigger
|
||||||
|
# becomes n/a). Let the service return to dead so the timer refires.
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
set -u
|
||||||
|
|
||||||
|
NEW_PATH=$(${pkgs.curl}/bin/curl -fsSL "${cfg.manifestUrl}" \
|
||||||
|
| ${pkgs.jq}/bin/jq -er .storePath) || {
|
||||||
|
echo "radiantwave-updater: failed to fetch/parse manifest from ${cfg.manifestUrl}"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
CURRENT=$(${pkgs.coreutils}/bin/readlink /var/lib/radiantwave/current 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ -z "$NEW_PATH" ] || [ "$NEW_PATH" = "$CURRENT" ]; then
|
||||||
|
# Already current; make sure the running closure is GC-rooted.
|
||||||
|
${pkgs.coreutils}/bin/ln -sfn "$CURRENT" /var/lib/radiantwave/.current-root 2>/dev/null || true
|
||||||
|
echo "radiantwave-updater: already current (''${CURRENT})"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Realize the closure via the variant's Attic cache plus cache.nixos.org.
|
||||||
|
# nixpkgs deps (e.g. sqlite) are signed by cache.nixos.org and Attic
|
||||||
|
# intentionally skips them to avoid re-uploading the world, so fetch
|
||||||
|
# them from cache.nixos.org and the whole closure resolves without ever
|
||||||
|
# building from source. Trusted public keys come from the system config
|
||||||
|
# (both Attic repos are trusted in configuration.nix). --add-root pins
|
||||||
|
# the fetched closure so GC can't collect it while the swap below makes
|
||||||
|
# it live.
|
||||||
|
echo "radiantwave-updater: acquiring ''${NEW_PATH}"
|
||||||
|
${pkgs.nix}/bin/nix-store -r "$NEW_PATH" \
|
||||||
|
--add-root /var/lib/radiantwave/.current-root \
|
||||||
|
--option extra-substituters "${cfg.atticUrl} https://cache.nixos.org" || {
|
||||||
|
echo "radiantwave-updater: nix copy failed for ''${NEW_PATH}"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Atomic swap: the app never observes a half-updated pointer.
|
||||||
|
${pkgs.coreutils}/bin/ln -sfn "$NEW_PATH" /var/lib/radiantwave/current.tmp
|
||||||
|
${pkgs.coreutils}/bin/mv -T /var/lib/radiantwave/current.tmp /var/lib/radiantwave/current
|
||||||
|
echo "radiantwave-updater: swapped ''${CURRENT} -> ''${NEW_PATH}"
|
||||||
|
loginctl terminate-user kiosk
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers.radiantwave-updater = {
|
||||||
|
description = "Check for RadiantWave app updates";
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnBootSec = "2min";
|
||||||
|
OnUnitActiveSec = "2min";
|
||||||
|
Persistent = true;
|
||||||
|
Unit = "radiantwave-updater.service";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
+20
-4
@@ -1,10 +1,26 @@
|
|||||||
{ 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
|
||||||
|
for i in {1..10}; do
|
||||||
|
if ${pkgs.wlr-randr}/bin/wlr-randr &>/dev/null; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
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}')
|
||||||
${pkgs.wlr-randr}/bin/wlr-randr --output "$OUTPUT" --transform 90
|
echo "Rotating output: $OUTPUT" >> /tmp/kiosk.log
|
||||||
exec ${radiantApp}/bin/radiantwave -d -s
|
${pkgs.wlr-randr}/bin/wlr-randr --output "$OUTPUT" --transform 90 >> /tmp/kiosk.log 2>&1 || echo "wlr-randr failed" >> /tmp/kiosk.log
|
||||||
|
exec ${launchBin} -d -s
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
Date (CDT),KioskConfig Hash,RadiantWave Hash,App Version,Summary
|
||||||
|
2026-03-12 08:05:10 -0500,,403ec4db89b5,dev,Initial release: RadiantWave v1.0
|
||||||
|
2026-03-13 19:57:06 -0500,,ee2b3383a8da,dev,Fix WiFi password entry
|
||||||
|
2026-03-13 20:14:01 -0500,,c68757a63439,dev,Update version
|
||||||
|
2026-03-13 20:28:41 -0500,,91e5c04a3800,dev,Update version
|
||||||
|
2026-03-13 20:58:28 -0500,,5fef01107b03,dev,Attempt to fix wifi connectivity
|
||||||
|
2026-03-13 20:59:02 -0500,,a116e01146e9,dev,update version
|
||||||
|
2026-03-13 21:13:21 -0500,,5f254e94cd55,dev,Fix WiFi page update
|
||||||
|
2026-03-13 21:16:54 -0500,,f8fbfb721d3d,dev,Update version
|
||||||
|
2026-03-23 15:02:07 -0500,,17c088cc2fc2,dev,Update Database
|
||||||
|
2026-03-23 17:35:52 -0500,,8d2384139116,dev,Pull Proclamations & Categories from API and store them in the database
|
||||||
|
2026-03-24 11:19:50 -0500,,9b6bae9511ab,dev,List proclamations inside of categories
|
||||||
|
2026-03-25 11:27:16 -0500,,678d679b536d,dev,Fix bug loading 0 proclamations
|
||||||
|
2026-03-25 18:09:02 -0500,,465928915c2c,dev,Update version
|
||||||
|
2026-03-25 19:56:41 -0500,,c1c077dc698d,dev,Update asset locations and permissions
|
||||||
|
2026-03-25 20:33:58 -0500,,4aeaaf10b7d2,dev,Simplify asset handling
|
||||||
|
2026-03-29 23:41:16 -0500,,6e28b90ee860,dev,"Add wifi, license and email checks"
|
||||||
|
2026-03-30 00:08:52 -0500,,aea646f78868,dev,Set default volume to MAX
|
||||||
|
2026-03-30 00:10:21 -0500,,331a8255fd5d,v1.0.0,Set version to 1.0.0 statically
|
||||||
|
2026-03-30 00:43:01 -0500,,120772175d42,v1.0.0,Update Proclamation selection
|
||||||
|
2026-04-13 14:00:02 -0500,,105d8900d58d,v1.0.0,Replace audio devices with audio toggle
|
||||||
|
2026-04-13 14:09:26 -0500,,76fd6057778b,v1.0.0,Update changelog
|
||||||
|
2026-04-15 11:01:28 -0500,,b6bf0f1873b7,v1.0.0,Sync proclamations when entering proclamation list
|
||||||
|
2026-04-15 11:58:44 -0500,,10ba5df13261,v1.0.0,Force re-initialization of audio device when Play() is called
|
||||||
|
2026-04-15 13:56:53 -0500,,b94279be691a,v1.0.0,Update changelog
|
||||||
|
2026-04-17 11:43:59 -0500,,504095caa826,v1.0.0,Sync instead of download.
|
||||||
|
2026-04-23 16:44:09 -0500,,dd069e934508,1.0,WIP: Stagger proclamations
|
||||||
|
2026-04-24 11:06:50 -0500,,7f344ae5c806,1.0,Optimize proclamation loading
|
||||||
|
2026-05-06 10:16:19 -0500,,3cf3e4cf23f3,1.0,Fix database syncronization
|
||||||
|
2026-05-10 21:19:07 -0500,,87d484353fb0,1.0,Fixed WiFi password bug. Ready for testing and merge
|
||||||
|
2026-05-11 12:55:16 -0500,,1f9da7ee780a,1.0,Attempt to fix proclamation sync
|
||||||
|
2026-05-14 09:57:13 -0500,,3ea32e0eaa36,1.0,Add volume changing logic
|
||||||
|
2026-05-14 10:21:11 -0500,,5188c90350d8,1.0,Add working volume bar in audioDevices page
|
||||||
|
2026-05-26 14:12:27 -0500,,f7ef42c84ab5,1.0,Rename application to Radiant Wave
|
||||||
|
2026-05-31 12:08:19 -0500,,0ebc3adf4ae5,1.0,Proclamations change colors!
|
||||||
|
2026-05-31 12:15:10 -0500,,8268022d5128,1.0,Organize proclamations alphabetically
|
||||||
|
2026-06-03 11:21:28 -0500,,ddab8e446c6b,1.0.0-beta,Update version for visual preference during beta phase
|
||||||
|
2026-06-11 19:44:20 -0500,,4b7d2c13d5ce,1.0.0-beta,Rename audio devices to audio options
|
||||||
|
2026-06-18 08:02:21 -0500,,6b4867953756,1.0.0-beta,Add database logic
|
||||||
|
2026-06-18 11:29:45 -0500,,fe5543d50dea,1.0.0-beta,Audio downloads when necessary
|
||||||
|
2026-06-18 15:39:30 -0500,,a5f9780f410b,1.0.0-beta,Add download package
|
||||||
|
2026-06-18 15:49:05 -0500,,1a67b9780400,1.0.0-beta,un-mask wifi password
|
||||||
|
2026-06-18 15:49:29 -0500,,b11afe22f09e,1.0.0-beta,remove file downloads
|
||||||
|
2026-06-18 21:28:16 -0500,,7ecb90b63b61,1.0,Downloads are working and the goroutine is reporting updates appropriately.
|
||||||
|
2026-06-18 21:52:29 -0500,,2af1aae88580,1.0,Audio files download on application start. Home screen shows status message
|
||||||
|
2026-06-18 22:43:47 -0500,,ac86864c7286,1.0,Add downloading message to home screen.
|
||||||
|
2026-06-18 22:51:17 -0500,,1c814f4abb6b,1.0,Reset default volume to MAX
|
||||||
|
2026-06-19 00:13:25 -0500,,4cd24375cdac,1.0,Add audio features
|
||||||
|
2026-06-23 11:22:21 -0500,,30dd5ffdcb94,1.0,Create objects for tracking audio devices
|
||||||
|
2026-06-24 14:17:06 -0500,,0a9a33b87b90,1.0,Fix download URL mismatch
|
||||||
|
2026-06-24 14:50:13 -0500,,970b8ebf09af,1.0,Add verbose logging to download.DownloadAudioFiles
|
||||||
|
2026-06-24 14:57:42 -0500,,cc2887d2a06d,b018a79,Update version text and add more download logs
|
||||||
|
2026-06-24 18:29:59 -0500,,953d0d8bc93d,b018a79,Rewrite entire network package and update wifi page
|
||||||
|
2026-06-24 22:41:48 -0500,,a4eff17b77a1,b018a79,"fix(network): fix wifi page never scanning, never exiting, and UI freezes on connect"
|
||||||
|
2026-06-24 23:18:32 -0500,,57500308b00d,b018a79,Handle locked database cases
|
||||||
|
2026-06-24 23:20:57 -0500,,f8e75194e845,cd79a6f,Update version hash for visual verification
|
||||||
|
2026-06-26 18:07:12 -0500,,4dd78b5ade8a,9beeeb1,Update version number for visual validation
|
||||||
|
2026-06-27 00:37:07 -0500,,2fac3ab6a8aa,9beeeb1,Replace textInputSupported flag
|
||||||
|
2026-06-27 01:47:06 -0500,,ca18000c8ef4,2.3.5.8,Update version string for validation
|
||||||
|
2026-06-27 12:57:29 -0500,,18c57c9101bf,2.3.5.8,Remove calls to RefreshConfig
|
||||||
|
2026-06-27 13:59:22 -0500,,f00cb9efb6a0,2.3.5.8,Fix download progress and prevent re-downloading existing audio.
|
||||||
|
2026-06-27 14:25:25 -0500,,2c88b7ffc515,2.3.5.8,Add creation of audio directory and remove unused function in download package
|
||||||
|
2026-06-27 14:25:51 -0500,,0b43a982985b,2.3.5.8,Remove unused function in download package
|
||||||
|
2026-06-27 14:50:50 -0500,,8ec1cbbc480b,2.3.5.8,Log the app config in Debug
|
||||||
|
2026-06-27 15:09:51 -0500,,ba39ed85a943,2.3.5.8,Remove wal and shm deletion
|
||||||
|
2026-06-27 16:01:33 -0500,,7e598f191f72,2.3.5.8,Update Network package
|
||||||
|
2026-06-27 16:51:18 -0500,,b0947bd59447,2.3.5.8,Fix bugs in WiFi page
|
||||||
|
2026-06-28 01:56:35 -0500,,193ebbcb2714,2.3.5.8,Fix network connection timeouts and security detection
|
||||||
|
2026-06-28 15:11:35 -0500,,6ad8a19f2ad9,2.3.5.8,Increase startup volume to 1.0
|
||||||
|
2026-06-28 15:18:27 -0500,,c232d417ba31,Coming Soon,Update version string
|
||||||
|
2026-07-10 13:56:06 -0500,,2bcb36669fd2,Coming Soon,fix: restore SDL volume to MAX after device switch
|
||||||
|
2026-07-10 13:56:12 -0500,,9bc0fd6820bb,Coming Soon,fix: restore SDL volume to MAX after device switch
|
||||||
|
2026-07-10 14:00:48 -0500,,68b3680e32bc,Coming Soon,fix: make wayland/X11 buildInputs Linux-only for macOS dev shell
|
||||||
|
2026-07-10 14:18:47 -0500,,779bc77c5b45,Coming Soon,fix: use recursiveUpdate instead of // to preserve packages.default
|
||||||
|
2026-07-10 16:02:02 -0500,,313db8ccd18a,Coming Soon,fix: rewrite cmd_diagnose to use script upload instead of nested SSH quoting
|
||||||
|
2026-07-10 17:12:48 -0500,,fca635482901,Coming Soon,Create Docker build pipeline for MacOS
|
||||||
|
2026-07-11 09:01:05 -0500,,141613f571ed,Audio-Bug-Fix 0.1,Update version string to indicate audio-bug-fixes
|
||||||
|
2026-07-11 09:45:12 -0500,,9dc1ce77aefc,Audio-Bug-Fix 0.1,WIP on dev: 141613f Update version string to indicate audio-bug-fixes
|
||||||
|
2026-07-11 09:45:12 -0500,,bdbc525b6fb7,Audio-Bug-Fix 0.1,index on dev: 141613f Update version string to indicate audio-bug-fixes
|
||||||
|
2026-07-11 10:16:15 -0500,,72910e700605,Audio-Bug-Fix 0.1,fix: prevent Play() deadlock when device is not open
|
||||||
|
2026-07-11 11:39:58 -0500,,bb132ceeaf86,Audio-Bug-Fix 0.5,fix: resolve 4 critical audio mixer bugs (v0.5)
|
||||||
|
2026-07-11 13:43:05 -0500,,3f8144a708bb,Audio-Bug-Fix 0.6,Gate READY on download completion; move audio to ~/.radiantwave/audio
|
||||||
|
2026-07-11 15:27:01 -0500,,336f407499f4,Audio-Bug-Fix 0.6,Allow F2/F3 during DOWNLOADING_ASSETS state
|
||||||
|
2026-07-11 16:36:15 -0500,,a93e47589984,Audio-Bug-Fix 0.6,Fix double-close panic on DownloadDone channel
|
||||||
|
2026-07-11 16:43:16 -0500,,2ef826fe635b,Audio-Bug-Fix 0.6,Fix double-close panic on DownloadDone: skip re-navigation between HOME-mapped states
|
||||||
|
2026-07-11 17:10:59 -0500,,f29d1d1af51e,07112026-1710,Bump version to 07112026-1710; move completed items to release section
|
||||||
|
2026-07-12 18:15:41 -0500,,c98b84a3a8f3,07112026-1710,fix: active device highlight incorrect when using default device
|
||||||
|
2026-07-12 18:16:18 -0500,,e090870b4dfd,07112026-1710,fix: SwitchDevice no longer passes 'system default' to SDL
|
||||||
|
2026-07-12 18:17:22 -0500,,5854c57f6f01,07112026-1710,"changelog: update fixed issues list, remove known issues"
|
||||||
|
2026-07-27 13:14:37 -0500,,b1a5a670ab0b,07112026-1710,Add audio list
|
||||||
|
2026-07-27 14:13:58 -0500,c06a3f7f92de,,07112026-1710,init new repo
|
||||||
|
2026-07-27 14:23:31 -0500,5a6031a3fe5a,,07112026-1710,refactor: migrate from Codeberg to Gitea and rename rhemawave to radiantwave
|
||||||
|
2026-07-27 14:24:25 -0500,7f2f19fe41d9,,07112026-1710,fix: use correct lowercase repo name radiantwave.git
|
||||||
|
2026-07-27 14:29:03 -0500,26aca21e963d,,07112026-1710,Update flake.lock
|
||||||
|
2026-07-28 14:10:44 +0000,16545a77fd7e,,07112026-1710,chore: update radiantwave-dev to latest dev
|
||||||
|
2026-07-28 10:43:06 -0500,4af16d9df7c4,,07112026-1710,Specify main branch
|
||||||
|
2026-07-29 14:46:11 -0500,,6dda46288473,07292026_1445,Add audio selection
|
||||||
|
2026-07-29 14:57:32 -0500,,7a3b6a66b72f,07292026_1445,"Remove wpctl parsing tests (TestParseSinkLine, TestParseSinks)"
|
||||||
|
2026-07-29 14:58:15 -0500,,3480e7594a3b,07292026_1445,Replace wpctl volume control with SDL_mixer VolumeMusic
|
||||||
|
2026-07-29 16:19:15 -0500,,91711eef9328,07292026_1618,"Replace AudioFile with AudioTrack, fix download+orphan+MD5 sync, always play original audio fallback"
|
||||||
|
2026-07-29 19:40:04 -0500,09bc293af31e,,07292026_1618,Chore: Update RadiantWave
|
||||||
|
2026-08-02 00:56:34 -0500,ab6488028120,,07292026_1618,Organize flakes and cleanup things.
|
||||||
|
2026-08-03 01:54:27 -0500,,11b25227d789,07292026_1618,"Rename app to radiantwave: embed and provision assets, drop proclamations"
|
||||||
|
2026-08-03 01:54:52 -0500,ce6856b37a09,,07292026_1618,"Fix kiosk startup: radiantwave binary, drop stale rsync, fix GODEBUG typo"
|
||||||
|
2026-08-03 07:26:16 +0000,8ef25f756076,,07292026_1618,chore: update radiantwave-dev to latest
|
||||||
|
2026-08-03 02:40:51 -0500,,b54dbca2d5e7,07292026_1618,Fix asset provisioning to strip embedded share/radiantwave root
|
||||||
|
2026-08-06 10:13:40 -0500,0f5aa7670cf9,,07292026_1618,Chore: Update Radiantwave
|
||||||
|
2026-08-06 21:55:40 -0500,c3f65f78976f,,07292026_1618,Fix audio: pin system volume to 100% before RadiantWave launches
|
||||||
|
2026-08-07 00:38:25 -0500,2d4aca230d04,,07292026_1618,Update configuration and dev-iso
|
||||||
|
2026-08-07 17:13:59 -0500,cbb3e0585970,,07292026_1618,"Update to radiantave-cpp, enable plymouth boot image"
|
||||||
|
2026-08-07 17:18:32 -0500,37d18d3f474b,,07292026_1618,Update repository location
|
||||||
|
2026-08-07 17:19:04 -0500,50a1f3e99dfc,,07292026_1618,Update repository location
|
||||||
|
2026-08-07 17:39:26 -0500,06f02d49b910,,07292026_1618,update radiantwave to radiatnwave-dev
|
||||||
|
2026-08-07 17:47:04 -0500,2e3d88b57dce,,07292026_1618,Update Plymount dontUnpack = true;
|
||||||
|
2026-08-07 22:17:52 -0500,4580319f19f5,,07292026_1618,Update radiantwave-dev
|
||||||
|
2026-08-07 23:58:25 -0500,8abb028c8260,,07292026_1618,fix wpctl volume pinning
|
||||||
|
2026-08-08 00:02:09 -0500,e3120f27c20b,,07292026_1618,Pull back from radiantwave-cpp-dev to radiantwave-dev
|
||||||
|
2026-08-08 00:12:57 -0500,,2212cc9b7805,07292026_1618,Add audio device switching UI
|
||||||
|
2026-08-08 01:20:11 -0500,,312f5e2a90e0,08082026_0120,Rename rhemawave to radiantwave throughout
|
||||||
|
2026-08-08 06:25:05 +0000,1f3e194aa558,,08082026_0120,Chore: Update Radiantwave-dev
|
||||||
|
2026-08-08 01:44:03 -0500,240be2540f3f,,08082026_0120,Update Radiantwave
|
||||||
|
2026-08-17 10:44:36 -0500,06c2a7cd0a1e,,08082026_0120,Add plan to migrate to DRM
|
||||||
|
2026-08-17 10:51:05 -0500,50176d8fd5e4,,08082026_0120,"repoint production to radiantwave-legacy, dev to radiantwave (ref=main)"
|
||||||
|
2026-08-17 10:57:16 -0500,0cb1337f18fd,,08082026_0120,"add radiantwave-legacy input with auth token, update flake.lock"
|
||||||
|
2026-08-17 11:02:19 -0500,2abdfa2b9b28,,08082026_0120,revert upgradeFlakeRef URLs to KioskConfig to match renamed repo
|
||||||
|
2026-08-17 12:43:57 -0500,974a61051883,,08082026_0120,Update dev images to new radiantwave
|
||||||
|
2026-08-17 12:48:22 -0500,fb2f86125804,,08082026_0120,Revert back to Legacy Radiant Wave
|
||||||
|
2026-08-17 12:50:20 -0500,027d1a628feb,,08082026_0120,Update token
|
||||||
|
2026-08-17 12:52:11 -0500,14463ad99b08,,08082026_0120,Just make RadiantWave-Legacy public
|
||||||
|
2026-08-17 12:54:06 -0500,e09758a70ece,,08082026_0120,fix: switch radiantwave inputs to SSH for legacy Go repo
|
||||||
|
Executable
+56
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Archive old build images on the fileserver before publishing new ones.
|
||||||
|
#
|
||||||
|
# Usage: tools/archive-images.sh <ssh-target> <images-dir> <archive-dir> <pattern>...
|
||||||
|
#
|
||||||
|
# Zero matching files is fine (nothing to archive yet); a missing directory
|
||||||
|
# or a failed move aborts non-zero so CI stops before publishing.
|
||||||
|
# The remote half is piped to `bash -s` over stdin so there is no nested
|
||||||
|
# shell quoting to get wrong.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
die() { echo "error: $*" >&2; exit 1; }
|
||||||
|
|
||||||
|
(( $# >= 4 )) || die "usage: $0 <ssh-target> <images-dir> <archive-dir> <pattern>..."
|
||||||
|
|
||||||
|
target=$1 images_dir=$2 archive_dir=$3
|
||||||
|
shift 3
|
||||||
|
patterns=("$@")
|
||||||
|
|
||||||
|
for v in "$images_dir" "$archive_dir" "${patterns[@]}"; do
|
||||||
|
case $v in
|
||||||
|
*[[:space:]\'\"]*) die "unsafe argument (whitespace/quotes): $v" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Archiving previous images on $target:"
|
||||||
|
printf ' pattern: %s\n' "${patterns[@]}"
|
||||||
|
|
||||||
|
# Assignments prefixed to `bash -s` are parsed by the remote shell as
|
||||||
|
# environment variables for the script below; the quoted heredoc marker
|
||||||
|
# keeps every byte of the payload literal until bash executes it.
|
||||||
|
ssh "$target" "IMAGES_DIR=$images_dir ARCHIVE_DIR=$archive_dir bash -s" <<'REMOTE'
|
||||||
|
set -euo pipefail
|
||||||
|
shopt -s nullglob
|
||||||
|
|
||||||
|
if [ ! -d "$IMAGES_DIR" ]; then
|
||||||
|
echo "error: images dir not found: $IMAGES_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -d "$ARCHIVE_DIR" ]; then
|
||||||
|
echo "error: archive dir not found: $ARCHIVE_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
moved=0
|
||||||
|
for pat in "${PATTERNS[@]}"; do
|
||||||
|
for f in "$IMAGES_DIR"/$pat; do
|
||||||
|
echo " archiving $(basename "$f")"
|
||||||
|
mv "$f" "$ARCHIVE_DIR/"
|
||||||
|
moved=$((moved + 1))
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Archived $moved file(s)."
|
||||||
|
REMOTE
|
||||||
Executable
+41
@@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env zsh
|
||||||
|
#
|
||||||
|
# find_systems.zsh
|
||||||
|
# Sweeps a subnet for hosts with SSH open, then queries each as `kiosk`
|
||||||
|
# for hostname + NixOS configuration revision.
|
||||||
|
#
|
||||||
|
# Usage: ./find_systems.zsh [subnet]
|
||||||
|
# subnet defaults to 192.168.4.0/24
|
||||||
|
|
||||||
|
set -uo pipefail # no -e: we want to keep going past unreachable hosts
|
||||||
|
|
||||||
|
SUBNET="${1:-192.168.1.0/24}"
|
||||||
|
SSH_USER="kiosk"
|
||||||
|
SSH_OPTS=(-o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new)
|
||||||
|
|
||||||
|
echo "Scanning $SUBNET for open SSH (22)..."
|
||||||
|
ips=("${(@f)$(nmap -p 22 --open -Pn "$SUBNET" -oG - | awk '/22\/open/ {print $2}')}")
|
||||||
|
|
||||||
|
if [[ ${#ips[@]} -eq 0 ]]; then
|
||||||
|
echo "No hosts with open SSH found on $SUBNET."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Found ${#ips[@]} host(s). Querying..."
|
||||||
|
echo
|
||||||
|
|
||||||
|
fail_count=0
|
||||||
|
|
||||||
|
for ip in "${ips[@]}"; do
|
||||||
|
echo "== $ip =="
|
||||||
|
if ssh "${SSH_OPTS[@]}" "${SSH_USER}@${ip}" \
|
||||||
|
'echo "host: $(hostname)"; echo "rev: $(nixos-version --configuration-revision)"'; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
echo " (unreachable or auth failed)"
|
||||||
|
((fail_count++))
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Done. ${fail_count} of ${#ips[@]} host(s) failed to respond."
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
# Parameterized zero-touch installer for kiosk USB images.
|
||||||
|
#
|
||||||
|
# Usage in flake.nix:
|
||||||
|
# (import ./zero-touch-installer.nix { targetConfig = "radiantwavekiosk"; })
|
||||||
|
#
|
||||||
|
# `targetConfig` must name an entry in self.nixosConfigurations. The target
|
||||||
|
# closure and its disko partitioning script are derived from that config, so
|
||||||
|
# an image can never install a mismatched system or format the wrong disk
|
||||||
|
# layout as long as flake.nix pairs each ISO with its disk-config.nix.
|
||||||
|
{ targetConfig }:
|
||||||
{
|
{
|
||||||
pkgs,
|
pkgs,
|
||||||
inputs,
|
inputs,
|
||||||
@@ -5,17 +15,15 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
targetSystem =
|
cfg = self.nixosConfigurations.${targetConfig};
|
||||||
self.nixosConfigurations.radiantwavekiosk.config.system.build.toplevel;
|
targetSystem = cfg.config.system.build.toplevel;
|
||||||
|
diskoScript = cfg.config.system.build.diskoScript;
|
||||||
|
|
||||||
diskoScript =
|
# Keep in sync with myKey in configuration.nix.
|
||||||
self.nixosConfigurations.radiantwavekiosk.config.system.build.diskoScript;
|
adminKey =
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL4qWNZnYU5JaqR9c2qh6oFZktzVNBOx42PfDaGofCL6 cnclack@pm.me macbook";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
############################################################
|
|
||||||
# System configuration
|
|
||||||
############################################################
|
|
||||||
|
|
||||||
nix.settings = {
|
nix.settings = {
|
||||||
experimental-features = [
|
experimental-features = [
|
||||||
"nix-command"
|
"nix-command"
|
||||||
@@ -38,32 +46,11 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
nix.extraOptions = ''
|
|
||||||
netrc-file = /etc/nix/netrc
|
|
||||||
'';
|
|
||||||
|
|
||||||
environment.variables.KIOSK_FLAKE = "${self}";
|
environment.variables.KIOSK_FLAKE = "${self}";
|
||||||
|
|
||||||
environment.etc."nix/netrc" = {
|
|
||||||
text = ''
|
|
||||||
password eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIwODgzODk2ODEsIm5iZiI6MTc3MjgxMzY4MSwic3ViIjoia2lvc2siLCJodHRwczovL2p3dC5hdHRpYy5ycy92MSI6eyJjYWNoZXMiOnsia2lvc2siOnsiciI6MX19fX0.N6Ktrmmu1LSy2PP8uNF25VHRP1dxPZJ4M0MR1kUpVi0
|
|
||||||
'';
|
|
||||||
mode = "0400";
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.networkmanager.enable = true;
|
networking.networkmanager.enable = true;
|
||||||
|
|
||||||
############################################################
|
users.users.root.openssh.authorizedKeys.keys = [ adminKey ];
|
||||||
# User configuration
|
|
||||||
############################################################
|
|
||||||
|
|
||||||
users.users.root.openssh.authorizedKeys.keys = [
|
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL4qWNZnYU5JaqR9c2qh6oFZktzVNBOx42PfDaGofCL6 cnclack@pm.me macbook"
|
|
||||||
];
|
|
||||||
|
|
||||||
############################################################
|
|
||||||
# Service configuration
|
|
||||||
############################################################
|
|
||||||
|
|
||||||
services.openssh.enable = true;
|
services.openssh.enable = true;
|
||||||
|
|
||||||
@@ -115,7 +102,7 @@ in
|
|||||||
|
|
||||||
${diskoScript}
|
${diskoScript}
|
||||||
|
|
||||||
echo "Installing RadiantWave"
|
echo "Installing RadiantWave (${targetConfig})"
|
||||||
echo "Closure Path: ${targetSystem}"
|
echo "Closure Path: ${targetSystem}"
|
||||||
|
|
||||||
nixos-install \
|
nixos-install \
|
||||||
@@ -127,7 +114,7 @@ in
|
|||||||
echo " Installation complete!"
|
echo " Installation complete!"
|
||||||
echo " Please shut down and remove the flash drive before restarting"
|
echo " Please shut down and remove the flash drive before restarting"
|
||||||
echo "============================================"
|
echo "============================================"
|
||||||
sleep 10000000
|
sleep infinity
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user