[Unit] Description=ninlight E-Paper-Anzeige (Status der Migrations-/Rettungsstation) # Nur eine Anzeige fuer den Hauptdienst - After=, NICHT Requires=/BindsTo=/Wants=: ein # fehlendes oder abgestuerztes Panel darf server.py niemals mitreissen, und server.py # darf umgekehrt starten, auch wenn dieser Dienst fehlt oder deaktiviert ist. Siehe # display.py's Modul-Docstring: die Anzeige ist nirgends Voraussetzung fuer die # Migration. # # KEIN Wants=ninlight-station.service (stand hier zuerst, Fund der Hardware-Abnahme auf # Pi 2): Wants= ist keine reine Reihenfolgeangabe, sondern AKTIVIERT die genannte Unit # beim Start dieser Unit, unabhaengig davon, ob sie enabled ist - "ninlight-station" ist # absichtlich disabled (siehe deren Kommentar: die Station ist per Vorgabe aus), wurde # aber trotzdem bei jedem Boot mitgestartet, weil dieser (enabled) Anzeigedienst sie ueber # Wants= mitzog. Ohne Zugangspunkt kann server.py 192.168.8.1 nicht binden, stirbt, wird # von Restart=always dort mehrfach neu versucht und bleibt auf "failed" haengen - das # flutet das Journal bei jedem Boot und laesst "ninlight-station status" faelschlich # einen Fehler ("failed" statt "inactive") vermuten, wo absichtlich nichts laeuft. # After= allein ordnet nur die Startreihenfolge, FALLS beide Units ohnehin gestartet # werden (z.B. wenn jemand die Station von Hand einschaltet) - und genau das war hier # gemeint, nicht "zieh die Station mit hoch". Nicht aus falscher Vorsicht wieder # einbauen: der Nebeneffekt ist genau der oben beschriebene, kein Sicherheitsnetz. After=ninlight-station.service # Gehoeren seit systemd 229 ins [Unit], nicht ins [Service] (dort standen sie zuerst - # Fixrunde 1, I1: systemd ignoriert sie dort mit "Unknown key name", die Vorgabe (5 # Starts/10s) greift dann bei RestartSec=5 nie, und genau das sollte die Deckelung unten # eigentlich verhindern). ninlight-station.service (dieselbe Sektion) macht es richtig. StartLimitIntervalSec=60 StartLimitBurst=5 [Service] Type=simple # Reihenfolge wichtig: systemd wendet Environment=/EnvironmentFile= in der Reihenfolge # an, in der sie hier stehen, und eine spaetere Zuweisung ueberschreibt eine gleichnamige # fruehere. EPD_REVISION=V3 hier ist nur der Vorgabewert, falls die Konfigurationsdatei # (unten, per EnvironmentFile=) den Schluessel (noch) nicht enthaelt - z.B. auf einem Pi, # der vor Task 6 aufgesetzt wurde. Steht er dort, gewinnt er. Environment=EPD_REVISION=V3 Environment=NINLIGHT_RUN_DIR=/run/ninlight-station Environment=NINLIGHT_STATION_CONF=/etc/ninlight-station.conf # "-"-Praefix VOR dem Dateipfad, nicht vor dem Direktivennamen (Fixrunde 3, F1 - das war # in Fixrunde 2 falsch herum: "-EnvironmentFile=..." ist kein gueltiger Schluesselname, # systemd verwirft die Zeile komplett mit "Unknown key name", und die Konfigurationsdatei # wurde dadurch ueberhaupt nicht mehr gelesen - Environment=EPD_REVISION=V3 oben haette # IMMER gewonnen, unabhaengig davon, was in /etc/ninlight-station.conf steht). Mit dem # Praefix am richtigen Platz gilt: ohne die Konfigurationsdatei startet die Anzeige # trotzdem (mit den Environment=-Vorgaben oben) statt mit einem harten Fehler - "die # Anzeige darf nirgends Voraussetzung sein" gilt auch fuer ihre eigene Konfiguration. EnvironmentFile=-/etc/ninlight-station.conf # RuntimeDirectory=/WorkingDirectory=/Environment=HOME= zusammen beheben einen Fund aus # der Hardware-Abnahme: lgpio (gpiozero's bevorzugte Pin-Factory) legt beim Start eine # Benachrichtigungs-Pipe unter "$HOME/.lgd-nfy0" an. Unter systemd ist HOME per Vorgabe # LEER und das Arbeitsverzeichnis "/" - der Pfad wird "//.lgd-nfy0", und ProtectSystem= # strict (unten) macht "/" schreibgeschuetzt. lgpio scheitert dann mit "Can't set # permissions", gpiozero faellt ueber rpigpio/pigpio bis zur experimentellen NativeFactory # durch, und DIE scheitert ihrerseits mit [Errno 22] Invalid argument - display.py faengt # das korrekt als DisplayUnavailable ab und beendet sich mit 0 (siehe Kommentar zu # Restart=on-failure unten), aber das Panel bleibt schwarz und das Journal sieht aus wie # "kein Panel angeschlossen", obwohl die Hardware in Ordnung ist. Am Geraet nachgemessen # (systemd-run mit denselben zwei Property=, gegen ein Shell-echo von $HOME und pwd). # # RuntimeDirectory=ninlight-display ist ABSICHTLICH ein eigenes Verzeichnis, nicht das # RuntimeDirectory=ninlight-station der Hauptunit: dieser Dienst liest von dort nur # (status.json/control.json ueber NINLIGHT_RUN_DIR, s.o.), er soll dort nichts anlegen # koennen. Schriebe die Anzeige in fremden Laufzeitzustand, waere das eine unnoetige # Kopplung an dessen Lebenszyklus (siehe die Begruendung zu ReadOnlyPaths= weiter unten: # server.py's RuntimeDirectory wird beim Stop/Neustart geleert). systemd legt # /run/ninlight-display beim Start dieser Unit selbst an (Vorgabe 0755, vom User dieses # Dienstes - hier root, s.u. - beschreibbar) und raeumt es beim Stop wieder auf. RuntimeDirectory=ninlight-display WorkingDirectory=/run/ninlight-display Environment=HOME=/run/ninlight-display ExecStart=/usr/bin/python3 /opt/ninlight-station/display/display.py # display.py faengt ein fehlendes/abgestuerztes Panel als DisplayUnavailable ab, # protokolliert eine Zeile und beendet sich mit sys.exit(0) - das ist also nie ein # "Fehlschlag" im systemd-Sinne und loest kein Restart= aus. Restart=on-failure (nicht # always) greift nur fuer echte Abstuerze (z.B. ein Python-Traceback aus einem # Programmierfehler); ohne den Deckel wuerde ein dauerhaft fehlendes Panel sonst NICHT # staendig neu versucht (gut so), waehrend ein echter Bug endlos neu starten wuerde und # so das Journal fluten koennte. Restart=on-failure RestartSec=5 NoNewPrivileges=yes ProtectSystem=strict ProtectHome=yes PrivateTmp=yes # KEIN ReadOnlyPaths=/run/ninlight-station hier (stand hier zuerst, Fixrunde 1, C3): # server.py's Unit traegt RuntimeDirectory=ninlight-station, und "ninlight-station off" # stoppt den Hauptdienst regulaer - dabei raeumt systemd dessen RuntimeDirectory auf, # sofern nicht gerade ein Neustart lief (RuntimeDirectoryPreserve=restart deckt nur den # Neustart-Fall ab, nicht den regulaeren Stop). Ein nicht mit "-" prefigierter Pfad in # ReadOnlyPaths=/ReadWritePaths=/BindPaths=, der beim Start fehlt, laesst systemd die # Namensraum-Einrichtung mit 226/NAMESPACE abbrechen - ausgerechnet dann, wenn die # Anzeige "Station aus" zeigen soll, waere sie selbst nicht mehr startbar gewesen. Die # Zeile war obendrein wirkungslos: ProtectSystem=strict macht "/" (und damit "/run", # soweit nicht per ReadWritePaths= explizit freigegeben) bereits schreibgeschuetzt. RestrictAddressFamilies=AF_UNIX AF_NETLINK MemoryMax=64M # Laeuft als root (wie der Hauptdienst) fuer /dev/spidev0.0 und die GPIO-Zeichenleitung - # kein SupplementaryGroups=spi gpio hier (stand hier zuerst, Fixrunde 1, I2): auf reinem # Debian (im Unterschied zu Raspberry Pi OS) gibt es diese Gruppen oft gar nicht, und # eine fehlende Gruppe ist fuer systemd ein harter Startfehler (216/GROUP) - ausgerechnet # in der Unit, die nie stoeren soll, wenn etwas an der Anzeige fehlt. Als root sind die # Gruppen fuer den Geraetezugriff ohnehin nicht noetig. [Install] WantedBy=multi-user.target