8 Commits
Author SHA1 Message Date
Michael Sauer 2fe2f7d9f4 feat(tools): Auslieferungsarchiv ohne WLAN-Zugangsdaten
Baut alle acht Ziele ueber den wiederverwendbaren Bauweg tools/build_nocreds.sh
(config.local.cmake liegt per trap beiseite, damit sie nicht als set() jedes -D
schlaegt) und packt ota/, migrate/ (prod+dev signiert), flash/, bruno/,
onboard.sh, README.md und SHA256SUMS nach dist/ninlight-<version>.tar.gz.

Vor dem Packen wird jede Datei binaer gegen SSID, WLAN-Passwort und Token aus
config.local.cmake geprueft; ein Fund bricht den Lauf ab.
2026-09-15 01:07:44 +02:00
Michael Sauer 47532a6820 fix(station): ehrliche Abbruchmeldung, apt-update darf scheitern
Fixrunde 2 zu Task 6b.

- Die Vorbedingung "display.py liegt in der Ablage" steht jetzt VOR dem
  ersten sudo install. Vorher stand sie dahinter, und die Meldung "Nichts
  installiert" war schlicht unwahr: server.py, products.py, das CLI und
  die Stations-Unit lagen dann schon auf dem Pi. Ausgerechnet an dem
  Werkbank-Moment, fuer den die Pruefung gedacht ist, log das Skript den
  Bediener an. Die Ablage raeumt ein EXIT-Trap jetzt auf beiden Wegen aus
  dem Block ab, nicht nur im Erfolgsfall.
- "apt-get update" ist keine Voraussetzung mehr fuer "apt-get install":
  update meldet schon dann einen Fehler, wenn eine einzige Quelle nicht
  erreichbar ist, und ein Pi, auf dem das Paket aus dem Cache gekommen
  waere, bekam so still kein Pillow.
- Die Signalhandler beenden jetzt selbst (130/143/129). Ein Handler ohne
  exit laesst bash weiterlaufen - ein Strg-C konnte in "fertig, Exit 0"
  muenden. Die Begruendung im Kommentar war falsch und ist korrigiert:
  zum Aufraeumen haette der EXIT-Trap allein genuegt.
- test_host.sh ruft die Shell-Suiten aus einer Liste auf, und der
  Coverage-Test prueft diese Liste gegen das Dateisystem statt im
  Skripttext nach Dateinamen zu suchen - ein nachgestellter Kommentar
  hebelte die alte Regel aus. Er deckt jetzt ganz tools/ ab, nicht nur
  station/tests.
- Neue Szenarien: Wahrheitsgehalt der Abbruchmeldung, apt-update-Fall,
  und ein Strg-C, das aus den Trap-Zeilen des Skripts selbst geprueft
  wird.
2026-09-14 00:45:04 +02:00
Michael Sauer 7fdccbce72 fix(station): Anzeige-Installation absichern, apt darf nur warnen
Fixrunde 1 zu Task 6b.

- setup_pi.sh raeumt die Ablage vor dem scp ab und bricht ab, wenn
  display.py dort fehlt. Ein Reststand aus einem abgebrochenen Lauf liess
  "scp -r" ins vorhandene Ziel hineinkopieren; die Installationsschleife
  ueberspringt Verzeichnisse und fand nichts - der Lauf meldete Erfolg,
  aktivierte den Anzeigedienst und hinterliess ein leeres display/:
  203/EXEC im 5-Sekunden-Takt bei schwarzem Panel.
- Der Paketschritt steht jetzt hinter der Installation und warnt nur.
  Eine Migrationsstation steht typischerweise nicht am Netz; unter set -e
  davor hiess ein gescheitertes apt: gar keine Station statt einer Station
  ohne Panel. "apt-get update" kommt vor dem install, der Anzeigedienst
  startet nach dem Paketschritt.
- setup_pi.sh raeumt display/ auf dem Pi wie deploy.sh auf (im Repo
  geloeschte Dateien verschwinden), der Trap haengt auch an INT/TERM/HUP,
  und mktemp bekommt eine Vorlage: ohne sie beachtet BSD-mktemp TMPDIR
  nicht, und die Temp-Datei landete je nach Plattform woanders.
- test_host.sh ruft test_deploy.sh auf - 95 Zusagen liefen nur bei
  Handaufruf, darunter die Deploy-Haelfte der Anzeige-Auslieferung. Der
  Coverage-Test deckt jetzt auch die Shell-Suiten ab und zaehlt dabei nur
  nicht auskommentierte Zeilen.
- Neue Szenarien fuer alle vier Punkte, darunter die Zusage, dass beim
  gescheiterten Schreiben eines Profils keine Datei mit dem WLAN-Passwort
  liegenbleibt.
2026-09-14 00:03:49 +02:00
Michael Sauer cc398ef57a feat(station): Anzeige ausliefern, Migrationsprofil, display-Befehle
Schritt 8 des Plans: die fertige E-Paper-Anzeige wird endlich ausgeliefert
und verdrahtet, dazu zwei liegengebliebene Dinge.

- deploy.sh/setup_pi.sh uebertragen display/ (mit Panel-Treibern und
  HERKUNFT.md) und ninlight-display.service; setup_pi.sh installiert und
  aktiviert die Unit und zieht python3-pil nach - ohne Pillow beendet sich
  der Anzeigedienst still mit 0, das faellt sonst nirgends auf.
- setup_pi.sh legt auch das Profil fuer den Migrations-Zugangspunkt an
  (qconnex-firmware, von der alten Klyqa-Firmware fest vorgegeben, gleiche
  Adresse wie das Rettungsprofil). Ohne das scheiterte "migrate on" auf
  einem frisch eingerichteten Pi an need_profile. Bestehende Profile
  bleiben unangetastet.
- "ninlight-station display test|clear|off"; der Hilfetext erklaert die
  Revisionsbestimmung, denn EPD_REVISION laesst sich nur am Testbild
  bestimmen.
- "off" nimmt die Anzeige mit: erst Dienst stoppen, dann Panel leeren
  (sonst schreibt der Daemon binnen 5 s sein altes Bild zurueck) und beides
  vor ap_switch, das bei haengendem NetworkManager abbricht. Ein
  stehengebliebenes "MIGRATION" wuerde einen Betriebszustand behaupten, den
  es nicht mehr gibt.
- test_host.sh sammelt tools/mkimage als Ganzes ein: die 19 Tests in
  test_nl_mkimage.py liefen in keiner Suite mehr mit, darunter die Wache
  fuer die Python-3.8-Randbedingung des Build-Containers. Ein neuer Test
  haelt die Regel fest, dass keine Testdatei mehr herausfallen kann.
- Nebenbei: die Temp-Datei mit dem WLAN-Passwort wird wieder zuverlaessig
  aufgeraeumt, und leere Konfigurationswerte legen kein kaputtes Profil an.
2026-09-13 23:19:14 +02:00
Michael Sauer 1c077a15d0 fix(station): RuntimeDirectory-Race, Statusphase, off/ap_switch-Nachpruefung, Pi-Bestandskonfiguration
Fixrunde 1 zu Task 4:

- C1 (kritisch): die Unit leerte /run/ninlight-station bei jedem
  systemctl restart, weil RuntimeDirectoryPreserve fehlte - genau der
  Neustart, mit dem migrate/recovery on den Dienst hochfahren, wischte
  die eben geschriebene Modus-/Steuerdatei wieder weg, und der Server
  fiel still auf den Recovery-Default zurueck. Unit bekommt
  RuntimeDirectoryPreserve=restart, und start_and_verify() prueft nach
  dem Neustart zusaetzlich ausdruecklich nach, dass Modus und Phase noch
  stimmen.
- I1: status las die Phase aus status.json, die arm/listen (die den
  Dienst bewusst nicht neu starten) nie aktualisieren - jetzt kommt sie
  aus control.json.
- I2: off pruefte nach dem Herunterfahren nichts nach und konnte
  "Station aus" melden, waehrend ein Profil wegen haengendem
  NetworkManager weiterhin aktiv war - ap_switch() verifiziert das jetzt
  auch im Aus-Fall.
- I3: der Fehlerzweig von ap_switch (Mismatch nach dem Umschalten) war
  im bisherigen Test nie erreichbar; der nmcli-Stub kann das jetzt
  erzwingen.
- I4: neue Pflichtschluessel blockierten jeden Unterbefehl auf einem
  bereits eingerichteten Pi. setup_pi.sh ergaenzt fehlende Schluessel in
  einer bestehenden Konfiguration jetzt einzeln, statt sie unangetastet
  zu lassen; dafuer musste das Skript per bash-s-Heredocs und
  ueberschreibbaren Pfadvariablen erst testbar gemacht werden (neuer
  test_setup_pi.sh).

Dazu vier kleinere Punkte aus derselben Review: grep -Fqx statt grep -qx
fuer Profilnamen, install-Stub durch tee+chmod ersetzt (POSIX, kein
Sonderfall mehr fuer macOS), Rechtepruefung auf control.json in den
arm/listen-Testfaellen, stderr sauber von stdout getrennt in den
Warnungs-/Fehlerfaellen.
2026-09-13 19:04:23 +02:00
Michael Sauer 1d3627bacd feat(station): Unterbefehle fuer Migrationsmodus und Freigabe
ninlight-station bekommt recovery/migrate/arm/listen als Unterbefehle statt nur
on/off/status; "on" bleibt Kurzform fuer "recovery on". Eine gemeinsame
ap_switch()-Funktion faehrt vor jedem Wechsel erst jedes andere Stationsprofil
herunter und prueft per nmcli nach, dass auf wlan0 wirklich nur eines aktiv
ist - die beiden Zugangspunkte (Rettung/Migration) schliessen sich damit
gegenseitig aus. Der Modus wandert per /run/ninlight-station/mode an den
(ohnehin neu gestarteten) Dienst statt ueber die Konfigurationsdatei; arm/listen
schreiben die Steuerdatei aus Task 3 mit demselben atomaren 0644-Muster wie
server.py selbst. station.conf.example und die Pflichtschluesselpruefung
bekommen die neuen Migrations- und Anzeige-Schluessel dazu.
2026-09-13 18:39:48 +02:00
Michael Sauer e50659d954 feat(tools): Signierwerkzeug fuer Migrationsimages
nl_sign.py signiert/prueft W600-Images im RSA-4096-PKCS#1-v1.5-Format der
alten Klyqa-Firmware (Check_OTA_Image() in qc_ssl_layer.c): SHA256 ueber die
gesamte Datei, angehaengt als exakt 512 Byte - derselbe Block wie der
ninlight-Trailer, daher die Ablehnung bereits getrailerter Eingaben.
Unterstuetzt sowohl cryptography als auch pycryptodome/pycryptodomex als
Backend. Gegen das echte Klyqa-Schluesselpaar aus ~/workspace/fw-sign
bitgleich verifiziert.

tools/test_host.sh haengt jetzt die Python-Tests aus tools/mkimage/tests
und tools/station/tests an ctest an.
2026-09-13 03:15:08 +02:00
Michael Sauer 4e7ff78fc6 test: Host-Testumgebung mit Unity, SHA256 und CRC 2026-09-11 12:23:04 +02:00