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.
176 lines
10 KiB
Markdown
176 lines
10 KiB
Markdown
# ninlight-Recovery-Station
|
|
|
|
Ein Raspberry Pi spannt den Zugangspunkt `ninlight-recovery` auf und liefert unter
|
|
`http://192.168.8.1/ninlight_<target>_ota.img` Firmware-Images aus.
|
|
|
|
Jede ninlight-Lampe sucht beim Booten bis zu drei Sekunden nach dieser Station. Findet sie
|
|
sie, lädt sie das Image für ihren Gerätetyp und flasht es — ohne dass die Lampe im WLAN
|
|
erreichbar sein muss. Das ist der Rettungsweg für Lampen, an die man sonst nur noch mit dem
|
|
Lötkolben käme.
|
|
|
|
Trägt das angebotene Image dieselbe Version, die bereits läuft, lehnt die Lampe es ab und
|
|
bootet normal weiter. Eine angelassene Station flasht also nicht in einer Endlosschleife.
|
|
|
|
## Voraussetzung
|
|
|
|
Der SSH-Benutzer auf dem Pi braucht passwortloses `sudo` — beide Skripte und die CLI
|
|
(`ninlight-station on|off|status`) rufen `sudo` nicht-interaktiv auf; ein Passwort-Prompt
|
|
würde dort nur mit einer Fehlermeldung enden.
|
|
|
|
## Bedienung
|
|
|
|
tools/station/setup_pi.sh <pi-host> einmalig einrichten
|
|
tools/station/deploy.sh <pi-host> [target] Images ausliefern
|
|
ssh <pi-host> ninlight-station on einschalten
|
|
ssh <pi-host> ninlight-station status Zustand und letzte Auslieferungen
|
|
ssh <pi-host> ninlight-station bildsatz welcher Migrations-Bildsatz gilt
|
|
ssh <pi-host> ninlight-station off ausschalten
|
|
|
|
## Bildsatz: mit oder ohne WLAN-Zugangsdaten
|
|
|
|
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 — **für beide
|
|
Betriebsarten**, Migration **und Rettung**:
|
|
|
|
| Umschalterstellung | Migration | Rettung | gebaut mit | die Lampe … |
|
|
|---|---|---|---|---|
|
|
| `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/` | `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
|
|
(Menüeintrag „Umschalten: …"). Das ist ein **Umschalter, keine Abfrage je Migration/
|
|
Rettung**: der Zustand steht in `/var/lib/ninlight-station/imageset`, überlebt Neustarts
|
|
und bleibt, bis ihn jemand ändert. Genau deshalb steht er auch **dauerhaft in der
|
|
Fußzeile des Panels** — in jedem Modus, auch bei ausgeschalteter Station: man soll vor
|
|
jedem Einschalten sehen, was gilt, statt zwanzig Lampen mit den eigenen Zugangsdaten zu
|
|
flashen, weil die Einstellung von vorletzter Woche noch stand.
|
|
|
|
**Warum `ohne-wlan` die Vorgabe ist** (und warum jeder Zweifelsfall — Datei fehlt,
|
|
unlesbar, unbekannter Inhalt — dorthin führt): im Automatikmodus wird jede alte
|
|
Klyqa-Lampe in Funkreichweite migriert, auch fremde. Mit dem Abzug ohne Zugangsdaten ist
|
|
ein Fehlgriff ärgerlich, aber harmlos — die Lampe steht im Onboarding-AP und wartet. Mit
|
|
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.
|
|
|
|
**Der Umschalter galt bisher nur für die Migration — jetzt auch für die Rettung, und das
|
|
hat eine spürbare Folge.** Bis zu dieser Erweiterung bekam eine Rettung *immer* ein
|
|
Abbild **mit** Zugangsdaten (der flache Bestand aus `build/`). Seitdem gilt für die
|
|
Rettung dieselbe Vorgabe `ohne-wlan` wie für die Migration: **eine gerettete Lampe steht
|
|
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.
|
|
|
|
**Fehlt der gewählte Satz** (nie ausgeliefert, oder `tools/build_nocreds.sh` nie
|
|
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
|
|
|
|
Sobald die Station läuft, wird `wlan0` zum Zugangspunkt und **jede SSH-Sitzung reißt ab**.
|
|
Für genau diesen Zustand gibt es einen Taster auf **GPIO23**; die Rückmeldung kommt aufs
|
|
E-Paper. Ein Taster, zwei Zusammenhänge:
|
|
|
|
| | kurz (unter 1 s) | gehalten |
|
|
|----------------------|------------------|-------------------------------------------------------|
|
|
| Ruhe (Statusbild) | Menü öffnen | 3 s → Abbruch (Station aus), weiter 10 s → Herunterfahren |
|
|
| Menü offen | nächster Eintrag | 1,5 s → Eintrag auswählen |
|
|
| Rückfrage | abbrechen | 1,5 s → bestätigen |
|
|
|
|
Der Abbruch feuert **bei 3 s**, nicht erst beim Loslassen — danach läuft auf dem Panel ein
|
|
Countdown bis zum Herunterfahren, den Loslassen jederzeit abbricht. Das Menü schließt nach
|
|
20 s ohne Eingabe, eine Rückfrage verfällt nach 10 s als Abbruch. Die Einträge hängen vom
|
|
Zustand ab (Station aus / Rettung läuft / Migration läuft); jede verändernde Auswahl
|
|
bekommt eine Rückfrage.
|
|
|
|
**Die beiden Migrationsarten sind getrennte Einträge.** „Migration: horchen"
|
|
(`migrate on --listen`) steht zuerst und ist der naheliegende: der Zugangspunkt steht,
|
|
aber es geht nichts raus, bis du eine erkannte Lampe freigibst. „Migration: AUTOMATIK"
|
|
(`migrate on`) folgt danach und bekommt eine **andere, deutlichere Rückfrage** — der
|
|
Migrations-Zugangspunkt hat ein öffentlich bekanntes Passwort (es steht in der alten
|
|
Klyqa-Firmware), und im Automatikmodus wird jede alte Klyqa-Lampe in Funkreichweite
|
|
ungefragt migriert, auch fremde. Die Rückfrage benennt genau das, statt nur „Wirklich?"
|
|
zu fragen.
|
|
|
|
Der Taster ist nirgends Voraussetzung: fehlt `gpiozero` oder der Taster, protokolliert der
|
|
Dienst eine Zeile und beendet sich ohne Fehler. Er fasst den SPI-Bus **nicht** an (zwei
|
|
Prozesse auf denselben Leitungen ergeben `GPIO busy`), sondern schreibt seinen
|
|
Bedienzustand nach `/run/ninlight-button/ui.json`; der Anzeigedienst liest ihn und
|
|
zeichnet. Ausgeführt wird ausschließlich über das CLI.
|
|
|
|
Dienst und Unit liegen in `tools/station/button/` bzw.
|
|
`tools/station/ninlight-button.service`. `setup_pi.sh` installiert, aktiviert und startet
|
|
den Tasterdienst wie den Anzeigedienst; `deploy.sh` liefert `button/` bei jedem Deploy mit
|
|
aus. Ein fehlgeschlagenes Aktivieren/Starten ist dabei nur eine Warnung — eine Station
|
|
ohne Taster muss vollständig benutzbar bleiben.
|
|
|
|
## Abschlussbildschirm beim Herunterfahren und beim Neustart
|
|
|
|
E-Paper behält sein Bild **dauerhaft**, auch stromlos. Fährt der Pi herunter, wird der
|
|
Anzeigedienst mit abgeräumt, und das zuletzt gezeichnete Bild bleibt für immer stehen —
|
|
bei der Abnahme stand dort die **Rückfrage** des Tastermenüs, und der ausgeschaltete Pi
|
|
sah aus, als warte er noch auf eine Eingabe.
|
|
|
|
Dafür gibt es zwei systemd-Units, `ninlight-poweroff.service` (`WantedBy=poweroff.target`)
|
|
und `ninlight-reboot.service` (`WantedBy=reboot.target`). Sie hängen am **System**, nicht
|
|
am Tasterdienst: so greifen sie bei jedem Herunterfahren — über das Menü, über `poweroff`
|
|
per SSH, über `reboot` — und nicht nur auf einem von drei Wegen.
|
|
|
|
**Die Texte unterscheiden sich, und das ist der Punkt:**
|
|
|
|
| | Panel |
|
|
|---|---|
|
|
| Herunterfahren | „AUSGESCHALTET — Strom kann getrennt werden" |
|
|
| Neustart | „NEUSTART — Strom NICHT trennen!" plus der Hinweis, dass es rund 2 min dauert |
|
|
|
|
Ein Zero W braucht gut anderthalb Minuten, bis der Anzeigedienst wieder zeichnet. Stünde
|
|
dort währenddessen „Strom kann getrennt werden", wäre das eine Aufforderung zu genau dem
|
|
Griff, der an diesem Pi schon eine SD-Karte gekostet hat.
|
|
|
|
**Reihenfolge:** der Anzeigedienst hält die GPIO-/SPI-Leitungen; zwei Prozesse darauf
|
|
ergeben `lgpio.error: 'GPIO busy'`. Die Units ordnen sich deshalb per
|
|
`After=ninlight-display.service` hinter dessen Stop ein, und `ninlight-station display
|
|
final <poweroff|reboot>` wartet zusätzlich, bis `systemctl is-active ninlight-display`
|
|
nicht mehr anschlägt — dasselbe Muster wie `display_handover()` bei `display test`, nur
|
|
ohne Wiederanfahren. Gibt der Dienst die Leitungen nicht frei, wird **nicht** gezeichnet,
|
|
sondern eine Zeile ins Journal geschrieben: ein Fehler an der Anzeige darf das
|
|
Herunterfahren nie aufhalten.
|
|
|
|
SSID und Passwort stehen in `/etc/ninlight-station.conf` auf dem Pi und müssen zu den
|
|
Build-Parametern `NINLIGHT_RECOVERY_SSID` und `NINLIGHT_RECOVERY_PASSWORD` aus
|
|
`config.local.cmake` passen. Weicht eines ab, findet die Lampe die Station nicht.
|
|
|
|
## Vertrauensgrenze
|
|
|
|
Der Trailer eines Images prüft Gerätetyp und SHA256. Er ist **keine** kryptografische
|
|
Signatur, und der Download läuft über `http://` ohne TLS. Die Station schützt allein ein
|
|
WPA2-Passwort, dessen Default im Repo steht. Wer in Funkreichweite ist und es kennt, kann
|
|
jeder Lampe beim nächsten Boot beliebige Firmware unterschieben.
|
|
|
|
Das ist gewollt: Die Signaturprüfung der alten Firmware auszuhebeln ist der Zweck dieses
|
|
Projekts. Daraus folgen zwei Regeln.
|
|
|
|
**Setz ein eigenes Passwort.** In `config.local.cmake` und in
|
|
`/etc/ninlight-station.conf` — an beiden Stellen denselben Wert.
|
|
|
|
**Lass die Station nur an, solange du sie brauchst.** Nicht nur wegen des Fremdzugriffs:
|
|
Die Firmware löscht den Power-Cycle-Zähler, bevor sie einen Recovery-Versuch startet.
|
|
Solange eine Station in Reichweite ist, funktioniert der Werksreset per fünf Stromzyklen
|
|
bei **keiner** Lampe in Reichweite mehr.
|