fix: Fehlerbehandlung und Quoting der Recovery-Station nach Review

- 'on' prueft nach dem Start wirklich, ob der Dienst aktiv ist (Type=simple
  meldet sonst schon Erfolg, wenn der Prozess nur gestartet wurde), und faehrt
  den Zugangspunkt bei einem toten Server wieder herunter statt ihn offen mit
  bekanntem Passwort ohne Server dahinter stehen zu lassen.
- 'off' faengt einen fehlschlagenden Wechsel zurueck ins Heim-WLAN ab statt
  unter set -eu mit nmclis Rohausgabe abzubrechen.
- Fehlende Schluessel in der Konfiguration werden nach dem Sourcen explizit
  geprueft und mit einem Verweis auf setup_pi.sh gemeldet statt mit
  "parameter not set" abzubrechen.
- station.conf.example verwendet einfache statt doppelter Anfuehrungszeichen:
  POSIX ". datei" wertet $ und ` auch in doppelten Anfuehrungszeichen aus,
  ein Passwort mit "$1" waere sonst lautlos durch ein Shell-Argument ersetzt
  worden. systemds EnvironmentFile= fuehrt ohnehin nie eine Shell aus und
  entfernt einfache wie doppelte Anfuehrungszeichen gleichermassen.
- ninlight-station.service: BindsTo=NetworkManager.service entfernt (ein
  dadurch ausgeloester Stop gilt fuer systemd als sauber und wird von keiner
  Restart=-Politik erneut gestartet - ein NetworkManager-Neustart haette den
  Server sonst still und dauerhaft mitgerissen); stattdessen nur
  After=NetworkManager.service fuer die Startreihenfolge und Restart=always,
  damit der Prozess nach einem Absturz (z.B. Bind-Fehler) automatisch
  wiederkommt.
- Trailer-Reader im 'status'-Zweig nutzt jetzt errors="replace" wie
  trailer_version() in server.py, statt bei nicht-ASCII-Bytes im
  Versionsfeld abzustuerzen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Michael Sauer
2026-09-12 12:14:49 +02:00
co-authored by Claude Sonnet 5
parent d41c385375
commit 10cc67d490
3 changed files with 47 additions and 13 deletions
+26 -4
View File
@@ -13,6 +13,14 @@ CONF=/etc/ninlight-station.conf
# shellcheck disable=SC1090 # shellcheck disable=SC1090
. "$CONF" . "$CONF"
for _key in SSID PSK IMAGE_DIR HOME_CONNECTION AP_CONNECTION; do
eval "test -n \"\${$_key-}\"" || {
echo "Fehler: $CONF: Schluessel $_key fehlt oder ist leer - tools/station/setup_pi.sh ausfuehren" >&2
exit 1
}
done
unset _key
need_profile() { need_profile() {
nmcli -t -f NAME con show | grep -qx "$AP_CONNECTION" || { nmcli -t -f NAME con show | grep -qx "$AP_CONNECTION" || {
echo "Fehler: NetworkManager-Profil '$AP_CONNECTION' fehlt - tools/station/setup_pi.sh ausfuehren" >&2 echo "Fehler: NetworkManager-Profil '$AP_CONNECTION' fehlt - tools/station/setup_pi.sh ausfuehren" >&2
@@ -31,14 +39,28 @@ on)
[ "$n" -gt 0 ] || echo "Warnung: $IMAGE_DIR enthaelt kein Image - die Station antwortet auf alles mit 404" >&2 [ "$n" -gt 0 ] || echo "Warnung: $IMAGE_DIR enthaelt kein Image - die Station antwortet auf alles mit 404" >&2
sudo nmcli con up "$AP_CONNECTION" >/dev/null sudo nmcli con up "$AP_CONNECTION" >/dev/null
sudo systemctl start ninlight-station sudo systemctl start ninlight-station
echo "Station an: SSID $SSID auf 192.168.8.1, $n Image(s)" # "systemctl start" meldet bei Type=simple schon Erfolg, sobald der Prozess
echo "Nicht vergessen: 'ninlight-station off', wenn du fertig bist." # gestartet wurde - nicht, sobald er ueberlebt hat. Kurz warten und wirklich
# nachsehen, sonst bleibt ein offener AP ohne Server dahinter stehen.
sleep 1
if systemctl is-active --quiet ninlight-station; then
echo "Station an: SSID $SSID auf 192.168.8.1, $n Image(s)"
echo "Nicht vergessen: 'ninlight-station off', wenn du fertig bist."
else
sudo nmcli con down "$AP_CONNECTION" >/dev/null 2>&1 || true
echo "Fehler: ninlight-station ist nach dem Start nicht aktiv - Zugangspunkt wieder abgeschaltet. Details: journalctl -u ninlight-station" >&2
exit 1
fi
;; ;;
off) off)
sudo systemctl stop ninlight-station 2>/dev/null || true sudo systemctl stop ninlight-station 2>/dev/null || true
sudo nmcli con down "$AP_CONNECTION" >/dev/null 2>&1 || true sudo nmcli con down "$AP_CONNECTION" >/dev/null 2>&1 || true
if nmcli -t -f NAME con show | grep -qx "$HOME_CONNECTION"; then if nmcli -t -f NAME con show | grep -qx "$HOME_CONNECTION"; then
sudo nmcli con up "$HOME_CONNECTION" >/dev/null && echo "Station aus, wlan0 zurueck im Heim-WLAN ($HOME_CONNECTION)" if sudo nmcli con up "$HOME_CONNECTION" >/dev/null; then
echo "Station aus, wlan0 zurueck im Heim-WLAN ($HOME_CONNECTION)"
else
echo "Station aus. Zurueckschalten zu '$HOME_CONNECTION' fehlgeschlagen - wlan0-Status pruefen." >&2
fi
else else
echo "Station aus. Profil '$HOME_CONNECTION' fehlt, wlan0 bleibt ungenutzt - der Zugang ueber Kabel ist davon nicht betroffen." >&2 echo "Station aus. Profil '$HOME_CONNECTION' fehlt, wlan0 bleibt ungenutzt - der Zugang ueber Kabel ist davon nicht betroffen." >&2
fi fi
@@ -53,7 +75,7 @@ status)
"$(python3 - "$f" <<'PY' "$(python3 - "$f" <<'PY'
import pathlib, sys import pathlib, sys
t = pathlib.Path(sys.argv[1]).read_bytes()[-512:] t = pathlib.Path(sys.argv[1]).read_bytes()[-512:]
print(t[21:37].rstrip(b"\x00").decode() if t[:4] == b"NINL" else "kein Trailer") print(t[21:37].rstrip(b"\x00").decode("ascii", "replace") if t[:4] == b"NINL" else "kein Trailer")
PY PY
)" )"
done done
+9 -4
View File
@@ -1,14 +1,19 @@
[Unit] [Unit]
Description=ninlight recovery station (HTTP image server) Description=ninlight recovery station (HTTP image server)
After=network.target # Ohne den Zugangspunkt hat der Server beim Start keine Adresse zum Binden.
# Ohne den Zugangspunkt hat der Server keine Adresse zum Binden. # Bewusst kein BindsTo=NetworkManager.service: ein dadurch ausgeloester Stop
BindsTo=NetworkManager.service # gilt fuer systemd als sauber und wuerde von KEINER Restart=-Politik erneut
# gestartet - ein NetworkManager-Neustart wuerde den Server dann still und
# dauerhaft mitreissen. Stattdessen nur Start-Reihenfolge vorgeben und den
# Prozess bei jedem Absturz (z.B. Bind-Fehler nach einem NM-Neustart) per
# Restart=always in kurzen Abstaenden neu versuchen lassen.
After=network.target NetworkManager.service
[Service] [Service]
Type=simple Type=simple
EnvironmentFile=/etc/ninlight-station.conf EnvironmentFile=/etc/ninlight-station.conf
ExecStart=/usr/bin/python3 /opt/ninlight-station/server.py --image-dir ${IMAGE_DIR} ExecStart=/usr/bin/python3 /opt/ninlight-station/server.py --image-dir ${IMAGE_DIR}
Restart=on-failure Restart=always
RestartSec=2 RestartSec=2
# Laeuft als root: Port 80 und - fuer 4c - die DHCP-Leases in # Laeuft als root: Port 80 und - fuer 4c - die DHCP-Leases in
+12 -5
View File
@@ -2,18 +2,25 @@
# Muss zu den Build-Parametern der Firmware passen (NINLIGHT_RECOVERY_SSID, # Muss zu den Build-Parametern der Firmware passen (NINLIGHT_RECOVERY_SSID,
# NINLIGHT_RECOVERY_PASSWORD in config.local.cmake). Weicht etwas ab, findet # NINLIGHT_RECOVERY_PASSWORD in config.local.cmake). Weicht etwas ab, findet
# die Lampe die Station nicht. # die Lampe die Station nicht.
#
# Werte immer in einfachen Anführungszeichen: ninlight-station liest diese
# Datei per POSIX ". datei" ein, und dabei wertet die Shell auch in doppelten
# Anführungszeichen $ und ` aus - ein Passwort mit "$1" wuerde so lautlos
# durch ein Shell-Argument ersetzt. Einfache Anführungszeichen unterbinden
# das; systemds EnvironmentFile= entfernt sie ebenso wie doppelte und fuehrt
# ohnehin nie eine Shell aus.
SSID="ninlight-recovery" SSID='ninlight-recovery'
# Oeffentlicher Default. Fuer den echten Einsatz hier UND in config.local.cmake # Oeffentlicher Default. Fuer den echten Einsatz hier UND in config.local.cmake
# einen eigenen Wert setzen - wer das Passwort kennt und in Funkreichweite ist, # einen eigenen Wert setzen - wer das Passwort kennt und in Funkreichweite ist,
# kann jeder Lampe beim naechsten Boot beliebige Firmware unterschieben. # kann jeder Lampe beim naechsten Boot beliebige Firmware unterschieben.
PSK="ninlight-recovery" PSK='ninlight-recovery'
IMAGE_DIR="/opt/ninlight-station/images" IMAGE_DIR='/opt/ninlight-station/images'
# NetworkManager-Profil, in das "ninlight-station off" zurueckschaltet. # NetworkManager-Profil, in das "ninlight-station off" zurueckschaltet.
HOME_CONNECTION="preconfigured" HOME_CONNECTION='preconfigured'
# NetworkManager-Profil der Station selbst. # NetworkManager-Profil der Station selbst.
AP_CONNECTION="ninlight-recovery" AP_CONNECTION='ninlight-recovery'