feat(station): Rettungsabbilder folgen dem Bildsatz-Umschalter beim Deploy

deploy.sh legt Rettungsabzuege jetzt in images/rescue-wlan/ (aus build/) und
images/rescue-nocreds/ (aus build-nocreds/) ab, statt sie flach in images/
abzulegen - dasselbe Muster wie migrate-wlan/migrate-nocreds, inklusive
derselben --delete-Vorsicht (nur bei vollstaendigem Satz). Alter flacher
Bestand wird bei jedem Deploy aufgeraeumt, auch bei einem gezielten.

README beschreibt den Umschalter jetzt fuer beide Betriebsarten.
This commit is contained in:
Michael Sauer
2026-09-23 21:22:06 +02:00
parent 9ee1983dff
commit eec9fa5a21
3 changed files with 333 additions and 58 deletions
+39 -18
View File
@@ -29,20 +29,21 @@ würde dort nur mit einer Fehlermeldung enden.
## Bildsatz: mit oder ohne WLAN-Zugangsdaten ## Bildsatz: mit oder ohne WLAN-Zugangsdaten
Die WLAN-Zugangsdaten stecken **zur Bauzeit** in der Firmware (`config.local.cmake`). Es Die WLAN-Zugangsdaten stecken **zur Bauzeit** in der Firmware (`config.local.cmake`). Es
gibt deshalb zwei Abzüge je Ziel, und die Station hält beide vor: gibt deshalb zwei Abzüge je Ziel, und die Station hält beide vor — **für beide
Betriebsarten**, Migration **und Rettung**:
| Umschalterstellung | Verzeichnis auf dem Pi | gebaut mit | die migrierte Lampe … | | Umschalterstellung | Migration | Rettung | gebaut mit | die Lampe … |
|---|---|---|---| |---|---|---|---|---|
| `ohne-wlan` (**Vorgabe**) | `images/migrate-nocreds/` | `tools/build_nocreds.sh` | startet im eigenen Onboarding-Zugangspunkt und wartet auf ein WLAN | | `ohne-wlan` (**Vorgabe**) | `images/migrate-nocreds/` | `images/rescue-nocreds/` | `tools/build_nocreds.sh` | startet im eigenen Onboarding-Zugangspunkt und wartet auf ein WLAN |
| `mit-wlan` | `images/migrate-wlan/` | `build/` | hängt sofort im WLAN aus `config.local.cmake` | | `mit-wlan` | `images/migrate-wlan/` | `images/rescue-wlan/` | `build/` | hängt sofort im WLAN aus `config.local.cmake` |
Umgeschaltet wird per `ninlight-station bildsatz ohne-wlan|mit-wlan` oder am Taster Umgeschaltet wird per `ninlight-station bildsatz ohne-wlan|mit-wlan` oder am Taster
(Menüeintrag „Umschalten: …"). Das ist ein **Umschalter, keine Abfrage je Migration**: der (Menüeintrag „Umschalten: …"). Das ist ein **Umschalter, keine Abfrage je Migration/
Zustand steht in `/var/lib/ninlight-station/imageset`, überlebt Neustarts und bleibt, bis Rettung**: der Zustand steht in `/var/lib/ninlight-station/imageset`, überlebt Neustarts
ihn jemand ändert. Genau deshalb steht er auch **dauerhaft in der Fußzeile des Panels** — und bleibt, bis ihn jemand ändert. Genau deshalb steht er auch **dauerhaft in der
in jedem Modus, auch bei ausgeschalteter Station: man soll vor jedem Einschalten sehen, Fußzeile des Panels** — in jedem Modus, auch bei ausgeschalteter Station: man soll vor
was gilt, statt zwanzig Lampen mit den eigenen Zugangsdaten zu flashen, weil die jedem Einschalten sehen, was gilt, statt zwanzig Lampen mit den eigenen Zugangsdaten zu
Einstellung von vorletzter Woche noch stand. flashen, weil die Einstellung von vorletzter Woche noch stand.
**Warum `ohne-wlan` die Vorgabe ist** (und warum jeder Zweifelsfall — Datei fehlt, **Warum `ohne-wlan` die Vorgabe ist** (und warum jeder Zweifelsfall — Datei fehlt,
unlesbar, unbekannter Inhalt — dorthin führt): im Automatikmodus wird jede alte unlesbar, unbekannter Inhalt — dorthin führt): im Automatikmodus wird jede alte
@@ -51,14 +52,34 @@ ein Fehlgriff ärgerlich, aber harmlos — die Lampe steht im Onboarding-AP und
dem Abzug mit Zugangsdaten steht das eigene WLAN-Passwort unwiderruflich in einem fremden dem Abzug mit Zugangsdaten steht das eigene WLAN-Passwort unwiderruflich in einem fremden
Gerät. Der teurere Fehler darf nie der sein, der ohne Zutun passiert. Gerät. Der teurere Fehler darf nie der sein, der ohne Zutun passiert.
**Fehlt der gewählte Satz** (nie ausgeliefert, oder `tools/build_nocreds.sh` nie gelaufen), **Der Umschalter galt bisher nur für die Migration — jetzt auch für die Rettung, und das
wird **nichts** ausgeliefert — kein stilles Ausweichen auf den anderen Satz. Der Server hat eine spürbare Folge.** Bis zu dieser Erweiterung bekam eine Rettung *immer* ein
antwortet mit 404 und `status: noimageset`, `ninlight-station status` sagt „FEHLT", und auf Abbild **mit** Zugangsdaten (der flache Bestand aus `build/`). Seitdem gilt für die
dem Panel steht „SATZ FEHLT". „Vorhanden" heißt dabei überall dasselbe: Verzeichnis da Rettung dieselbe Vorgabe `ohne-wlan` wie für die Migration: **eine gerettete Lampe steht
**und** mindestens ein Image darin. danach im eigenen Onboarding-Zugangspunkt statt im eigenen WLAN**, bis jemand ihr eines
nennt. Das ist gewollt (dieselbe Abwägung wie oben — der teure Fehler darf nie der sein,
der ohne Zutun passiert), soll aber niemanden überraschen: die Fußzeile des Panels zeigt
den Bildsatz deshalb ausdrücklich auch im Rettungsmodus, und stellt man bewusst auf
`mit-wlan` um, gilt das für beide Betriebsarten gemeinsam.
`deploy.sh` liefert beide Sätze aus. Fehlt `build-nocreds/`, bleibt der Satz `ohne-wlan` **Fehlt der gewählte Satz** (nie ausgeliefert, oder `tools/build_nocreds.sh` nie
auf dem Pi unangetastet (statt geleert zu werden) und der Lauf meldet, womit er entsteht. gelaufen), wird **nichts** ausgeliefert — kein stilles Ausweichen auf den anderen Satz,
und auch nicht auf einen alten, flachen Bestand direkt in `images/` (siehe unten). Der
Server antwortet mit 404 (Migration zusätzlich mit `status: noimageset`), und auf dem
Panel steht „SATZ FEHLT" — in JEDEM Modus. „Vorhanden" heißt dabei überall dasselbe:
Verzeichnis da **und** mindestens ein Image darin.
`deploy.sh` liefert alle vier Sätze aus (migrate-wlan/migrate-nocreds für die Migration,
rescue-wlan/rescue-nocreds für die Rettung). Fehlt `build-nocreds/` oder ist es für ein
Ziel unvollständig, bleibt der jeweils betroffene Satz `ohne-wlan` auf dem Pi unangetastet
(statt geleert zu werden) und der Lauf meldet, womit er entsteht.
**Rückwärtskompatibilität bewusst nicht gegeben:** vor dieser Erweiterung lagen
Rettungsabbilder flach in `images/` (immer mit Zugangsdaten). Ein Pi mit diesem alten
Bestand muss **einmal neu per `deploy.sh` bestückt werden** — der Deploy räumt den
flachen Altbestand dabei automatisch weg. Bis dahin liefert die Rettung in der Vorgabe
`ohne-wlan` sichtbar nichts aus (statt den alten Bestand mit Zugangsdaten stillschweigend
weiter zu bedienen, was den ganzen Sinn dieser Erweiterung unterlaufen würde).
## Taster am Gerät ## Taster am Gerät
+145 -31
View File
@@ -13,8 +13,15 @@ MKIMAGE="$ROOT/tools/mkimage/nl_mkimage.py"
# Abnahmebericht). Ueberschreibbar, damit tests/test_deploy.sh gegen einen Stub statt # Abnahmebericht). Ueberschreibbar, damit tests/test_deploy.sh gegen einen Stub statt
# einen echten Pi laufen kann - dasselbe Prinzip wie bei NINLIGHT_STATION_ROOT unten. # einen echten Pi laufen kann - dasselbe Prinzip wie bei NINLIGHT_STATION_ROOT unten.
SSH_BIN=${NINLIGHT_SSH_BIN:-/usr/bin/ssh} SSH_BIN=${NINLIGHT_SSH_BIN:-/usr/bin/ssh}
# Wo die Abzuege OHNE einkompilierte WLAN-Zugangsdaten liegen (tools/build_nocreds.sh,
# dieselbe Variable wie dort und in tools/release.sh). Hier schon gebraucht, nicht erst
# beim Signieren der Migrationsimages weiter unten: seit der Bildsatz-Umschalter auch die
# RETTUNG betrifft, braucht dieser Baum auch fuer die Rettungsabzuege (_ota.img statt
# _gz.img) einen eigenen Fundlauf, parallel zu dem fuer build/ unten.
NOCREDS_BUILD_REL=${NINLIGHT_NOCREDS_BUILD_REL:-build-nocreds}
declare -a FILES=() declare -a FILES=()
declare -a RESCUE_NOCREDS_FILES=()
if [ $# -gt 0 ]; then if [ $# -gt 0 ]; then
# Gezielter Deploy: nur die genannten Geraetetypen werden ersetzt, alle # Gezielter Deploy: nur die genannten Geraetetypen werden ersetzt, alle
# anderen Images im Zielverzeichnis bleiben unangetastet (siehe FULL_SYNC # anderen Images im Zielverzeichnis bleiben unangetastet (siehe FULL_SYNC
@@ -24,6 +31,17 @@ if [ $# -gt 0 ]; then
f="$ROOT/build/$t/ninlight_${t}_ota.img" f="$ROOT/build/$t/ninlight_${t}_ota.img"
[ -f "$f" ] || { echo "Fehler: $f fehlt - erst bauen" >&2; exit 1; } [ -f "$f" ] || { echo "Fehler: $f fehlt - erst bauen" >&2; exit 1; }
FILES+=("$f") FILES+=("$f")
# build-nocreds/ ist wie beim Signieren unten OPTIONAL/nachsichtig: ein Deploy darf
# nicht daran scheitern, dass tools/build_nocreds.sh fuer dieses Ziel noch nicht
# gelaufen ist. Fehlt die Datei, bleibt der Rettungs-Bildsatz "ohne-wlan" fuer GENAU
# dieses Ziel auf dem Pi unangetastet (sync_image_set unten arbeitet ohne --delete,
# wenn diese Liste nicht jedes Ziel aus FILES abdeckt).
nf="$ROOT/$NOCREDS_BUILD_REL/$t/ninlight_${t}_ota.img"
if [ -f "$nf" ]; then
RESCUE_NOCREDS_FILES+=("$nf")
else
echo "Hinweis: $nf fehlt - $t bleibt im Rettungs-Bildsatz 'ohne-wlan' unveraendert" >&2
fi
done done
else else
FULL_SYNC=1 FULL_SYNC=1
@@ -45,9 +63,24 @@ else
FILES+=("$f") FILES+=("$f")
done < <(find "$ROOT/build" -maxdepth 2 -name 'ninlight_*_ota.img' | sort) done < <(find "$ROOT/build" -maxdepth 2 -name 'ninlight_*_ota.img' | sort)
[ ${#FILES[@]} -gt 0 ] || { echo "Fehler: keine Images unter $ROOT/build gefunden" >&2; exit 1; } [ ${#FILES[@]} -gt 0 ] || { echo "Fehler: keine Images unter $ROOT/build gefunden" >&2; exit 1; }
# Derselbe Fundlauf fuer build-nocreds/ - genauso nachsichtig wie beim gezielten Deploy
# oben: fehlt der Baum komplett (tools/build_nocreds.sh nie gelaufen), bleibt
# RESCUE_NOCREDS_FILES einfach leer, kein Abbruch.
if [ -d "$ROOT/$NOCREDS_BUILD_REL" ]; then
declare -A SEEN_AT_NOCREDS=()
while IFS= read -r f; do
base=$(basename "$f")
if [ -n "${SEEN_AT_NOCREDS[$base]:-}" ]; then
echo "Fehler: $base mehrfach unter $NOCREDS_BUILD_REL gefunden - ${SEEN_AT_NOCREDS[$base]} und $f. Voller Deploy abgebrochen, welches Bild echt ist, ist nicht entscheidbar." >&2
exit 1
fi
SEEN_AT_NOCREDS[$base]=$f
RESCUE_NOCREDS_FILES+=("$f")
done < <(find "$ROOT/$NOCREDS_BUILD_REL" -maxdepth 2 -name 'ninlight_*_ota.img' | sort)
fi
fi fi
echo "== Images pruefen ==" echo "== Rettungsabzuege pruefen (build/, mit Zugangsdaten) =="
STAGE=$(mktemp -d) STAGE=$(mktemp -d)
trap 'rm -rf "$STAGE"' EXIT trap 'rm -rf "$STAGE"' EXIT
printf '%-26s %-10s %s\n' GERAET VERSION BYTES printf '%-26s %-10s %s\n' GERAET VERSION BYTES
@@ -63,9 +96,54 @@ for f in "${FILES[@]}"; do
set -- $out set -- $out
printf '%-26s %-10s %s\n' "$1" "$2" "$3" printf '%-26s %-10s %s\n' "$1" "$2" "$3"
NINL_VERSION[$target]="$2" NINL_VERSION[$target]="$2"
cp "$f" "$STAGE/$base" # Landet jetzt in einem eigenen Unterverzeichnis (rescue-wlan/) statt flach in STAGE -
# server.py:image_path() folgt seit diesem Umschalter demselben Bildsatz-Umschalter
# wie migrate_dir(), und "mit-wlan" ist genau dieser Satz (siehe RESCUE_SET_DIRS dort).
mkdir -p "$STAGE/rescue-wlan"
cp "$f" "$STAGE/rescue-wlan/$base"
done done
# Rettungsabzuege OHNE Zugangsdaten (build-nocreds/) - unabhaengig von den signierten
# Migrationsimages weiter unten: die Rettung braucht keine Signatur, nur den
# gewoehnlichen ninlight-Trailer, den nl_mkimage.py schon beim Bauen einsetzt. Ist eine
# Datei da, MUSS sie die Pruefung bestehen (Abbruch bei Fehlschlag, wie oben) - fehlt sie,
# ist das der lax behandelte Regelfall "tools/build_nocreds.sh noch nicht/nicht fuer
# jedes Ziel gelaufen" (siehe oben).
if [ ${#RESCUE_NOCREDS_FILES[@]} -gt 0 ]; then
echo "== Rettungsabzuege pruefen ($NOCREDS_BUILD_REL/, ohne Zugangsdaten) =="
for f in "${RESCUE_NOCREDS_FILES[@]}"; do
base=$(basename "$f")
target=${base#ninlight_}; target=${target%_ota.img}
out=$(python3 "$MKIMAGE" --verify "$f" --expect-device "$target") || {
echo "Fehler: $base (ohne Zugangsdaten) besteht die Trailer-Pruefung nicht, nichts uebertragen" >&2; exit 1; }
# shellcheck disable=SC2086
set -- $out
printf '%-26s %-10s %s\n' "$1" "$2" "$3"
mkdir -p "$STAGE/rescue-nocreds"
cp "$f" "$STAGE/rescue-nocreds/$base"
done
else
echo "Hinweis: keine Abzuege unter $ROOT/$NOCREDS_BUILD_REL - der Rettungs-Bildsatz 'ohne-wlan' bleibt auf dem Pi unveraendert. Erzeugen mit tools/build_nocreds.sh." >&2
fi
# "Vollstaendig" heisst hier: dieser Lauf kennt fuer JEDES Ziel aus FILES (dem Satz
# 'mit-wlan') auch ein Gegenstueck ohne Zugangsdaten - erst dann darf der Abgleich auf
# dem Pi mit --delete arbeiten (dieselbe Ueberlegung wie bei NOCREDS_COMPLETE fuer die
# Migrationsimages weiter unten, nur ohne Signierschritt dazwischen).
RESCUE_NOCREDS_COMPLETE=1
if [ ${#RESCUE_NOCREDS_FILES[@]} -eq 0 ]; then
RESCUE_NOCREDS_COMPLETE=0
else
declare -A RESCUE_NOCREDS_TARGETS=()
for f in "${RESCUE_NOCREDS_FILES[@]}"; do
base=$(basename "$f"); t=${base#ninlight_}; t=${t%_ota.img}
RESCUE_NOCREDS_TARGETS[$t]=1
done
for f in "${FILES[@]}"; do
base=$(basename "$f"); t=${base#ninlight_}; t=${t%_ota.img}
[ -n "${RESCUE_NOCREDS_TARGETS[$t]:-}" ] || RESCUE_NOCREDS_COMPLETE=0
done
fi
echo "== Migrationsimages signieren ==" echo "== Migrationsimages signieren =="
# Die Signierschluessel gehoeren nie ins Repo und nie auf den Pi - sie werden hier nur # Die Signierschluessel gehoeren nie ins Repo und nie auf den Pi - sie werden hier nur
# gelesen, unter $NINLIGHT_SIGN_DIR (Vorgabe ~/workspace/fw-sign auf DIESEM Rechner). # gelesen, unter $NINLIGHT_SIGN_DIR (Vorgabe ~/workspace/fw-sign auf DIESEM Rechner).
@@ -89,9 +167,7 @@ MIGRATE_COMPLETE=0
# zusaetzlich, dass fuer JEDES Ziel ein Abzug aus build-nocreds/ vorlag - fehlt auch nur # zusaetzlich, dass fuer JEDES Ziel ein Abzug aus build-nocreds/ vorlag - fehlt auch nur
# einer, darf --delete auf dem Pi nicht laufen. # einer, darf --delete auf dem Pi nicht laufen.
NOCREDS_COMPLETE=0 NOCREDS_COMPLETE=0
# Wo die Abzuege OHNE einkompilierte WLAN-Zugangsdaten liegen (tools/build_nocreds.sh, # NOCREDS_BUILD_REL ist schon oben gesetzt (dort auch fuer die Rettungsabzuege gebraucht).
# dieselbe Variable wie dort und in tools/release.sh).
NOCREDS_BUILD_REL=${NINLIGHT_NOCREDS_BUILD_REL:-build-nocreds}
# Signiert einen kompletten Satz Migrationsimages und schreibt sein manifest.json. # Signiert einen kompletten Satz Migrationsimages und schreibt sein manifest.json.
# $1 = Quellverzeichnis der Abzuege (build bzw. build-nocreds), absolut # $1 = Quellverzeichnis der Abzuege (build bzw. build-nocreds), absolut
@@ -326,11 +402,12 @@ rsync -a -c --delete --exclude=__pycache__ -e "$SSH_BIN" "$HERE/display/" "$HOST
rsync -a -c --delete --exclude=__pycache__ -e "$SSH_BIN" "$HERE/button/" "$HOST:/tmp/ninlight-button/" rsync -a -c --delete --exclude=__pycache__ -e "$SSH_BIN" "$HERE/button/" "$HOST:/tmp/ninlight-button/"
# Pfade als Variablen mit Default: so kann ein Test die Installation gegen # Pfade als Variablen mit Default: so kann ein Test die Installation gegen
# ein Sandbox-Verzeichnis statt gegen echte Systempfade laufen lassen. # ein Sandbox-Verzeichnis statt gegen echte Systempfade laufen lassen.
"$SSH_BIN" "$HOST" bash -s -- "$FULL_SYNC" "$MIGRATE_COMPLETE" "$NOCREDS_COMPLETE" <<'REMOTE' "$SSH_BIN" "$HOST" bash -s -- "$FULL_SYNC" "$MIGRATE_COMPLETE" "$NOCREDS_COMPLETE" "$RESCUE_NOCREDS_COMPLETE" <<'REMOTE'
set -e set -e
FULL_SYNC=$1 FULL_SYNC=$1
MIGRATE_COMPLETE=$2 MIGRATE_COMPLETE=$2
NOCREDS_COMPLETE=$3 NOCREDS_COMPLETE=$3
RESCUE_NOCREDS_COMPLETE=$4
ROOT_DIR=${NINLIGHT_STATION_ROOT:-/opt/ninlight-station} ROOT_DIR=${NINLIGHT_STATION_ROOT:-/opt/ninlight-station}
BIN_PATH=${NINLIGHT_STATION_BIN:-/usr/local/bin/ninlight-station} BIN_PATH=${NINLIGHT_STATION_BIN:-/usr/local/bin/ninlight-station}
UNIT_PATH=${NINLIGHT_STATION_UNIT:-/etc/systemd/system/ninlight-station.service} UNIT_PATH=${NINLIGHT_STATION_UNIT:-/etc/systemd/system/ninlight-station.service}
@@ -358,27 +435,41 @@ if [ "$FULL_SYNC" -eq 1 ]; then
else else
echo " voller Abgleich - keine Images werden entfernt" echo " voller Abgleich - keine Images werden entfernt"
fi fi
# migrate/ wird gleich in einem eigenen Schritt behandelt (siehe unten) - hier # migrate-*/rescue-* werden gleich in einem eigenen Schritt behandelt (siehe unten) -
# ausklammern, sonst wuerde --delete es leeren, wenn dieser Deploy ohne # hier ausklammern, sonst wuerde --delete sie leeren, wenn dieser Deploy ohne
# Schluesselverzeichnis lief und /tmp/ninlight-images/migrate deshalb gar nicht # Schluesselverzeichnis bzw. ohne build-nocreds/ lief und die jeweilige Unterablage
# existiert. # unter /tmp/ninlight-images deshalb gar nicht existiert.
sudo rsync -a -c --delete --exclude=/migrate-wlan --exclude=/migrate-nocreds /tmp/ninlight-images/ "$ROOT_DIR/images/" #
# Seit diesem Umschalter landet HIER (flach in images/) nichts Neues mehr: die
# Rettungsabzuege ziehen mit rescue-wlan/rescue-nocreds nach demselben Muster wie die
# Migrationsabzuege um. Ein alter, noch flacher Bestand aus der Zeit vor diesem
# Umschalter verschwindet dadurch bei einem vollen Deploy automatisch (die Quelle
# unter /tmp/ninlight-images hat dort ja nichts mehr) - kein separater Aufraeumschritt
# noetig, dieselbe Mechanik, die weiter unten "images/migrate" entsorgt.
sudo rsync -a -c --delete --exclude=/migrate-wlan --exclude=/migrate-nocreds \
--exclude=/rescue-wlan --exclude=/rescue-nocreds /tmp/ninlight-images/ "$ROOT_DIR/images/"
else else
# Gezielter Deploy: nur die uebertragenen Dateien ersetzen/ergaenzen, alle # Gezielter Deploy: nur die uebertragenen Dateien ersetzen/ergaenzen, alle
# anderen Geraetetypen im Zielverzeichnis bleiben erhalten (kein --delete). # anderen Geraetetypen im Zielverzeichnis bleiben erhalten (kein --delete).
echo " gezielter Deploy - andere Geraetetypen im Zielverzeichnis bleiben erhalten" echo " gezielter Deploy - andere Geraetetypen im Zielverzeichnis bleiben erhalten"
sudo rsync -a -c --exclude=/migrate-wlan --exclude=/migrate-nocreds /tmp/ninlight-images/ "$ROOT_DIR/images/" sudo rsync -a -c --exclude=/migrate-wlan --exclude=/migrate-nocreds \
--exclude=/rescue-wlan --exclude=/rescue-nocreds /tmp/ninlight-images/ "$ROOT_DIR/images/"
fi fi
# $1 = Name des Bildsatzverzeichnisses, $2 = 1, wenn dieser Lauf den Satz VOLLSTAENDIG # $1 = Name des Bildsatzverzeichnisses, $2 = 1, wenn dieser Lauf den Satz VOLLSTAENDIG
# beschreibt (beide Schluessel, alle Ziele) und --delete deshalb erlaubt ist. # beschreibt (bei den Migrationssaetzen: beide Schluessel, alle Ziele; bei den
# Rettungssaetzen: jedes Ziel aus FILES vorhanden) und --delete deshalb erlaubt ist.
# #
# Migrationsimages getrennt behandeln: fehlt migrate/ in der Uebertragung (weil auf # Fuer alle vier Bildsatz-Verzeichnisse gleichermassen (migrate-wlan/migrate-nocreds UND
# dem Entwicklungsrechner kein Schluesselverzeichnis oder gar kein Schluessel # seit diesem Umschalter auch rescue-wlan/rescue-nocreds, daher der generische Name):
# vorhanden war), darf ein vorhandenes migrate/ auf dem Pi NICHT geleert werden - # fehlt ein Satz in der Uebertragung (kein Schluesselverzeichnis, kein
# sonst zerstoert ein Deploy ohne Schluesselzugriff die Bestueckung frueherer Deploys # tools/build_nocreds.sh gelaufen, ...), darf ein vorhandener Satz auf dem Pi NICHT
# (derselbe Fehler wie frueher bei den _ota.img, siehe oben, nur diesmal beim Fehlen # geleert werden - sonst zerstoert ein unvollstaendiger Deploy die Bestueckung
# statt beim Ersetzen). # frueherer Deploys (derselbe Fehler wie frueher bei den _ota.img, siehe oben, nur
sync_migrate_set() { # diesmal beim Fehlen statt beim Ersetzen). Die Manifest-Zusammenfuehrung weiter unten
# betrifft dabei nur Migrationssaetze - ein Rettungssatz hat kein manifest.json (die
# Version steckt im ninlight-Trailer jedes einzelnen Images), das Zusammenfuehren
# greift dort also einfach nicht.
sync_image_set() {
set=$1 set=$1
complete=$2 complete=$2
if [ -d "/tmp/ninlight-images/$set" ]; then if [ -d "/tmp/ninlight-images/$set" ]; then
@@ -455,20 +546,28 @@ MERGE
fi fi
fi fi
else else
# Weder "kein Schluesselverzeichnis" noch "Verzeichnis vorhanden, aber leer" laesst # Weder "kein Schluesselverzeichnis"/"build-nocreds/ fehlt" noch "Verzeichnis
# sich von hier aus unterscheiden - beide Faelle liefern ein leeres migrate/ von # vorhanden, aber leer" laesst sich von hier aus unterscheiden - beide Faelle liefern
# diesem Rechner. Die lokale Meldung (deploy.sh, "Hinweis: ...") ist bereits genau; # ein leeres $set/ von diesem Rechner. Die lokale Meldung (deploy.sh, "Hinweis: ...")
# diese hier behauptet absichtlich keinen bestimmten Grund (Fixrunde 2, Kleinigkeit). # ist bereits genau; diese hier behauptet absichtlich keinen bestimmten Grund
echo " keine Migrationsimages fuer $set von diesem Rechner - $set/ auf dem Pi bleibt unangetastet" # (Fixrunde 2, Kleinigkeit).
echo " keine Abzuege fuer $set von diesem Rechner - $set/ auf dem Pi bleibt unangetastet"
fi fi
} }
# Beide Bildsaetze. Sie sind voneinander unabhaengig: liegt nur einer von diesem Rechner # Alle vier Bildsaetze. Sie sind voneinander unabhaengig: liegt nur einer von diesem
# vor (z.B. weil tools/build_nocreds.sh noch nicht gelaufen ist), bleibt der andere auf # Rechner vor (z.B. weil tools/build_nocreds.sh noch nicht gelaufen ist), bleibt der
# dem Pi unveraendert - der Umschalter zeigt dann eben auf einen Satz, der fehlt, und die # andere auf dem Pi unveraendert - der Umschalter zeigt dann eben auf einen Satz, der
# Station liefert in dieser Stellung sichtbar nichts aus. # fehlt, und die Station liefert in dieser Stellung sichtbar nichts aus.
sync_migrate_set migrate-wlan "$MIGRATE_COMPLETE" sync_image_set migrate-wlan "$MIGRATE_COMPLETE"
sync_migrate_set migrate-nocreds "$NOCREDS_COMPLETE" sync_image_set migrate-nocreds "$NOCREDS_COMPLETE"
# rescue-wlan ist, wenn es ueberhaupt uebertragen wurde, IMMER vollstaendig: es entsteht
# 1:1 aus FILES (dem verifizierten Satz aus build/), es gibt keinen Zwischenschritt wie
# das Signieren, der es unvollstaendig lassen koennte. "1" ist hier deshalb kein
# Versehen, sondern dieselbe Bedeutung wie MIGRATE_COMPLETE=1 bei zwei vorhandenen
# Schluesseln.
sync_image_set rescue-wlan 1
sync_image_set rescue-nocreds "$RESCUE_NOCREDS_COMPLETE"
# Altbestand: bis zum Umschalter hiess das einzige Verzeichnis "migrate" und enthielt die # Altbestand: bis zum Umschalter hiess das einzige Verzeichnis "migrate" und enthielt die
# Abzuege MIT WLAN-Zugangsdaten. Es wird von niemandem mehr gelesen; liegen bleiben darf # Abzuege MIT WLAN-Zugangsdaten. Es wird von niemandem mehr gelesen; liegen bleiben darf
# es trotzdem nicht, sonst glaubt der naechste Mensch am Pi, es sei der gueltige Satz. # es trotzdem nicht, sonst glaubt der naechste Mensch am Pi, es sei der gueltige Satz.
@@ -476,6 +575,21 @@ if [ -d "$ROOT_DIR/images/migrate" ]; then
sudo rm -rf "$ROOT_DIR/images/migrate" sudo rm -rf "$ROOT_DIR/images/migrate"
echo " Altbestand entfernt: images/migrate (heisst jetzt images/migrate-wlan)" echo " Altbestand entfernt: images/migrate (heisst jetzt images/migrate-wlan)"
fi fi
# Derselbe Altbestand-Fall fuer die Rettung: vor diesem Umschalter lagen Rettungsabzuege
# FLACH in images/ (immer MIT Zugangsdaten, aus build/) - server.py:image_path() liest
# dort seit diesem Umschalter nicht mehr (siehe rescue_dir() dort fuer die Begruendung,
# warum bewusst KEIN Fallback). Ein voller Deploy raeumt sie oben bereits über die
# normale --delete-Mechanik weg (die Quelle unter /tmp/ninlight-images hat dort ja
# nichts mehr); ein GEZIELTER Deploy laesst sie aber, wie jeden anderen nicht
# uebertragenen Inhalt von images/, unangetastet liegen (kein --delete). Dieser
# Aufraeumschritt laeuft deshalb IMMER, unabhaengig von FULL_SYNC - er ist rein additiv
# (loescht hoechstens toten, nie wieder gelesenen Ballast) und macht so auch einen
# gezielten Deploy sofort sauber, statt erst auf den naechsten vollen zu warten.
OLD_FLAT_RESCUE=$(find "$ROOT_DIR/images" -maxdepth 1 -name 'ninlight_*_ota.img' 2>/dev/null)
if [ -n "$OLD_FLAT_RESCUE" ]; then
echo "$OLD_FLAT_RESCUE" | xargs -r sudo rm -f
echo " Altbestand entfernt: Rettungsabbilder, die noch flach in images/ lagen (liegen jetzt in images/rescue-wlan bzw. images/rescue-nocreds)"
fi
sudo install -m 0755 /tmp/server.py "$ROOT_DIR/server.py" sudo install -m 0755 /tmp/server.py "$ROOT_DIR/server.py"
sudo install -m 0644 /tmp/products.py "$ROOT_DIR/products.py" sudo install -m 0644 /tmp/products.py "$ROOT_DIR/products.py"
sudo install -m 0755 /tmp/ninlight-station "$BIN_PATH" sudo install -m 0755 /tmp/ninlight-station "$BIN_PATH"
+149 -9
View File
@@ -355,10 +355,12 @@ setup_stubbin "$STUBBIN" "$LOG"
mkdir -p "$SANDBOX/build/foo" "$SANDBOX/build/bar" mkdir -p "$SANDBOX/build/foo" "$SANDBOX/build/bar"
echo "foo-image-neu" >"$SANDBOX/build/foo/ninlight_foo_ota.img" echo "foo-image-neu" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
echo "bar-image" >"$SANDBOX/build/bar/ninlight_bar_ota.img" echo "bar-image" >"$SANDBOX/build/bar/ninlight_bar_ota.img"
# Zielverzeichnis simuliert einen vorherigen Voll-Deploy: Images fuer foo UND bar liegen schon da. # Zielverzeichnis simuliert einen vorherigen Voll-Deploy MIT dieser Fassung von
mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images" # deploy.sh: Rettungsabbilder fuer foo UND bar liegen schon in images/rescue-wlan/ (dem
echo "foo-image-alt" >"$SANDBOX_STATE/opt/ninlight-station/images/ninlight_foo_ota.img" # "mit-wlan"-Bildsatz aus build/, siehe server.py:RESCUE_SET_DIRS).
echo "bar-image" >"$SANDBOX_STATE/opt/ninlight-station/images/ninlight_bar_ota.img" mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan"
echo "foo-image-alt" >"$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_foo_ota.img"
echo "bar-image" >"$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_bar_ota.img"
: >"$LOG" : >"$LOG"
set +e set +e
OUT=$(run_deploy "$SANDBOX" pi-test foo 2>&1) OUT=$(run_deploy "$SANDBOX" pi-test foo 2>&1)
@@ -370,13 +372,13 @@ if echo "$OUT" | grep -q "andere Geraetetypen im Zielverzeichnis bleiben erhalte
else else
fail "Ausgabe erwaehnt den gezielten Deploy nicht" fail "Ausgabe erwaehnt den gezielten Deploy nicht"
fi fi
if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/ninlight_bar_ota.img" ]; then if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_bar_ota.img" ]; then
pass "Image des NICHT ausgewaehlten Geraetetyps (bar) blieb erhalten" pass "Image des NICHT ausgewaehlten Geraetetyps (bar) blieb erhalten"
else else
fail "Image des nicht ausgewaehlten Geraetetyps (bar) wurde geloescht - Critical 2 nicht behoben" fail "Image des nicht ausgewaehlten Geraetetyps (bar) wurde geloescht - Critical 2 nicht behoben"
fi fi
if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/ninlight_foo_ota.img" ] \ if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_foo_ota.img" ] \
&& [ "$(cat "$SANDBOX_STATE/opt/ninlight-station/images/ninlight_foo_ota.img")" = "foo-image-neu" ]; then && [ "$(cat "$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_foo_ota.img")" = "foo-image-neu" ]; then
pass "Image des ausgewaehlten Geraetetyps (foo) wurde aktualisiert" pass "Image des ausgewaehlten Geraetetyps (foo) wurde aktualisiert"
else else
fail "Image des ausgewaehlten Geraetetyps (foo) wurde nicht aktualisiert" fail "Image des ausgewaehlten Geraetetyps (foo) wurde nicht aktualisiert"
@@ -646,7 +648,7 @@ OUT=$(run_deploy "$SANDBOX" pi-test foo 2>&1)
RC=$? RC=$?
set -e set -e
if [ "$RC" -eq 0 ]; then pass "Exit-Code 0"; else fail "Exit-Code $RC, Ausgabe: $OUT"; fi if [ "$RC" -eq 0 ]; then pass "Exit-Code 0"; else fail "Exit-Code $RC, Ausgabe: $OUT"; fi
if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/ninlight_foo_ota.img" ]; then if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_foo_ota.img" ]; then
pass "Rettungsimage (_ota.img) wurde trotzdem ausgeliefert" pass "Rettungsimage (_ota.img) wurde trotzdem ausgeliefert"
else else
fail "Rettungsimage fehlt: $OUT" fail "Rettungsimage fehlt: $OUT"
@@ -901,7 +903,7 @@ OUT=$(TEST_SIGN_DIR="$SIGN_EMPTY" run_deploy "$SANDBOX" pi-test 2>&1)
RC=$? RC=$?
set -e set -e
if [ "$RC" -eq 0 ]; then pass "Exit-Code 0"; else fail "Exit-Code $RC, Ausgabe: $OUT"; fi if [ "$RC" -eq 0 ]; then pass "Exit-Code 0"; else fail "Exit-Code $RC, Ausgabe: $OUT"; fi
if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/ninlight_foo_ota.img" ]; then if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_foo_ota.img" ]; then
pass "Rettungsimage (_ota.img) wurde trotzdem ausgeliefert" pass "Rettungsimage (_ota.img) wurde trotzdem ausgeliefert"
else else
fail "Rettungsimage fehlt: $OUT" fail "Rettungsimage fehlt: $OUT"
@@ -1419,6 +1421,144 @@ else
fail "der Satz 'ohne-wlan' auf dem Pi enthaelt Zugangsdaten" fail "der Satz 'ohne-wlan' auf dem Pi enthaelt Zugangsdaten"
fi fi
echo
echo "=== Szenario 35: der Bildsatz-Umschalter gilt jetzt auch fuer die Rettung - beide Rettungssaetze werden ausgeliefert ==="
# Analog zu Szenario 28 (Migration), aber fuer die Rettungsabbilder: rescue-wlan/ kommt
# aus build/ (dasselbe _ota.img, das ohnehin schon fuer die Trailer-Pruefung gebraucht
# wird), rescue-nocreds/ aus build-nocreds/ - ein eigener, unsignierter Fund.
SANDBOX="$TMPROOT/s35"; STUBBIN="$TMPROOT/bin35"; LOG="$TMPROOT/s35.log"; SANDBOX_STATE="$TMPROOT/s35-state"
setup_sandbox "$SANDBOX"
setup_stubbin "$STUBBIN" "$LOG"
mkdir -p "$SANDBOX/build/foo" "$SANDBOX/build-nocreds/foo"
make_ota_file "$SANDBOX/build/foo/ninlight_foo_ota.img"
python3 -c "
import sys
path = sys.argv[1]
body = b'N' * 2048
trailer = bytearray(b'\xff' * 512)
trailer[0:4] = b'NINL'
with open(path, 'wb') as fh:
fh.write(body + bytes(trailer))
" "$SANDBOX/build-nocreds/foo/ninlight_foo_ota.img"
: >"$LOG"
set +e
OUT=$(run_deploy "$SANDBOX" pi-test 2>&1)
RC=$?
set -e
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC: $OUT"
RESCUE_WLAN="$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_foo_ota.img"
RESCUE_NOCREDS="$SANDBOX_STATE/opt/ninlight-station/images/rescue-nocreds/ninlight_foo_ota.img"
if [ -f "$RESCUE_WLAN" ] && [ -f "$RESCUE_NOCREDS" ]; then
pass "beide Rettungssaetze liegen getrennt auf dem Pi"
else
fail "mindestens ein Rettungssatz fehlt: $OUT"
fi
if head -c 1 "$RESCUE_WLAN" | grep -q "B"; then
pass "rescue-wlan/ kommt wirklich aus build/"
else
fail "rescue-wlan/ hat nicht den erwarteten Inhalt aus build/"
fi
if head -c 1 "$RESCUE_NOCREDS" | grep -q "N"; then
pass "rescue-nocreds/ kommt wirklich aus build-nocreds/"
else
fail "rescue-nocreds/ hat nicht den erwarteten Inhalt aus build-nocreds/"
fi
echo
echo "=== Szenario 36: ohne build-nocreds/ bleibt der Rettungssatz 'ohne-wlan' auf dem Pi unangetastet ==="
SANDBOX="$TMPROOT/s36"; STUBBIN="$TMPROOT/bin36"; LOG="$TMPROOT/s36.log"; SANDBOX_STATE="$TMPROOT/s36-state"
setup_sandbox "$SANDBOX"
setup_stubbin "$STUBBIN" "$LOG"
mkdir -p "$SANDBOX/build/foo"
make_ota_file "$SANDBOX/build/foo/ninlight_foo_ota.img"
mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images/rescue-nocreds"
echo "alter-rescue-nocreds-abzug" >"$SANDBOX_STATE/opt/ninlight-station/images/rescue-nocreds/ninlight_foo_ota.img"
: >"$LOG"
set +e
OUT=$(run_deploy "$SANDBOX" pi-test 2>&1)
RC=$?
set -e
[ "$RC" -eq 0 ] && pass "Exit-Code 0 trotz fehlendem build-nocreds/" || fail "Exit-Code $RC: $OUT"
if [ "$(cat "$SANDBOX_STATE/opt/ninlight-station/images/rescue-nocreds/ninlight_foo_ota.img" 2>/dev/null)" \
= "alter-rescue-nocreds-abzug" ]; then
pass "vorhandener Rettungssatz 'ohne-wlan' blieb unangetastet"
else
fail "der Rettungssatz 'ohne-wlan' wurde geleert oder ueberschrieben, obwohl build-nocreds/ fehlte"
fi
if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_foo_ota.img" ]; then
pass "der Rettungssatz 'mit-wlan' wurde trotzdem ausgeliefert"
else
fail "der Rettungssatz 'mit-wlan' fehlt: $OUT"
fi
if echo "$OUT" | grep -q "build-nocreds"; then
pass "der Hinweis nennt, womit der fehlende Rettungssatz entsteht"
else
fail "kein Hinweis auf build-nocreds/ in der Ausgabe: $OUT"
fi
echo
echo "=== Szenario 37: gezielter Deploy ohne build-nocreds/<ziel> bricht nicht ab, sondern laesst 'ohne-wlan' fuer dieses Ziel unangetastet ==="
SANDBOX="$TMPROOT/s37"; STUBBIN="$TMPROOT/bin37"; LOG="$TMPROOT/s37.log"; SANDBOX_STATE="$TMPROOT/s37-state"
setup_sandbox "$SANDBOX"
setup_stubbin "$STUBBIN" "$LOG"
mkdir -p "$SANDBOX/build/foo"
make_ota_file "$SANDBOX/build/foo/ninlight_foo_ota.img"
: >"$LOG"
set +e
OUT=$(run_deploy "$SANDBOX" pi-test foo 2>&1)
RC=$?
set -e
[ "$RC" -eq 0 ] && pass "Exit-Code 0 trotz fehlendem build-nocreds/foo" || fail "Exit-Code $RC: $OUT"
if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_foo_ota.img" ]; then
pass "der Rettungssatz 'mit-wlan' fuer foo wurde trotzdem ausgeliefert"
else
fail "der Rettungssatz 'mit-wlan' fuer foo fehlt: $OUT"
fi
if [ ! -e "$SANDBOX_STATE/opt/ninlight-station/images/rescue-nocreds/ninlight_foo_ota.img" ]; then
pass "kein Rettungsimage 'ohne-wlan' fuer foo entstanden - build-nocreds/foo fehlte"
else
fail "es entstand ein Rettungsimage 'ohne-wlan' fuer foo, obwohl build-nocreds/foo fehlte"
fi
if echo "$OUT" | grep -q "bleibt im Rettungs-Bildsatz 'ohne-wlan' unveraendert"; then
pass "Hinweis auf das fehlende build-nocreds/foo vorhanden"
else
fail "kein Hinweis auf das fehlende build-nocreds/foo: $OUT"
fi
echo
echo "=== Szenario 38: Altbestand - vor diesem Umschalter flach in images/ liegende Rettungsabbilder werden auch bei einem gezielten Deploy aufgeraeumt ==="
# Anders als bei den Migrationsabbildern (die ein eigenes, benanntes Verzeichnis erst
# seit einem frueheren Umschalter haben) gab es fuer die Rettung nie eine Uebergangsphase
# mit --delete: der flache Bestand wird von server.py seit diesem Umschalter nicht mehr
# gelesen (siehe rescue_dir() dort) und ist deshalb toter Ballast, sobald IRGENDEIN
# Deploy mit dieser Fassung laeuft - auch ein gezielter, der images/ sonst nicht mit
# --delete abgleicht.
SANDBOX="$TMPROOT/s38"; STUBBIN="$TMPROOT/bin38"; LOG="$TMPROOT/s38.log"; SANDBOX_STATE="$TMPROOT/s38-state"
setup_sandbox "$SANDBOX"
setup_stubbin "$STUBBIN" "$LOG"
mkdir -p "$SANDBOX/build/foo"
make_ota_file "$SANDBOX/build/foo/ninlight_foo_ota.img"
mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images"
echo "alter-flacher-bestand" >"$SANDBOX_STATE/opt/ninlight-station/images/ninlight_foo_ota.img"
echo "alter-flacher-bestand-bar" >"$SANDBOX_STATE/opt/ninlight-station/images/ninlight_bar_ota.img"
: >"$LOG"
set +e
OUT=$(run_deploy "$SANDBOX" pi-test foo 2>&1)
RC=$?
set -e
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC: $OUT"
if [ ! -e "$SANDBOX_STATE/opt/ninlight-station/images/ninlight_foo_ota.img" ] \
&& [ ! -e "$SANDBOX_STATE/opt/ninlight-station/images/ninlight_bar_ota.img" ]; then
pass "der flache Altbestand wurde auch bei einem gezielten Deploy entfernt"
else
fail "flacher Altbestand liegt noch da - er wuerde von niemandem mehr gelesen, aber am Pi einen gueltigen Satz vortaeuschen"
fi
if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_foo_ota.img" ]; then
pass "das neue Rettungsimage liegt an der richtigen Stelle (images/rescue-wlan/)"
else
fail "das neue Rettungsimage fehlt: $OUT"
fi
echo echo
if [ "$FAILED" -eq 0 ]; then if [ "$FAILED" -eq 0 ]; then
echo "Alle Tests bestanden." echo "Alle Tests bestanden."