feat: Konfiguration, systemd-Unit und CLI der Recovery-Station
Die Station ist bewusst nicht dauerhaft an - 'ninlight-station on' schaltet Zugangspunkt und Dienst gemeinsam ein, 'off' schaltet wlan0 zurueck ins Heim-WLAN. Der Dienst laeuft als root (Port 80 und die DHCP-Leases fuer 4c) und bekommt dafuer systemd-Einschraenkungen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a2e87b8855
commit
d41c385375
Executable
+66
@@ -0,0 +1,66 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Schaltet die ninlight-Recovery-Station an und aus.
|
||||||
|
#
|
||||||
|
# Die Station ist absichtlich nicht dauerhaft an: Solange sie laeuft, kann jeder
|
||||||
|
# in Funkreichweite mit dem Passwort einer Lampe beim naechsten Boot beliebige
|
||||||
|
# Firmware unterschieben, und der Werksreset per fuenf Stromzyklen funktioniert
|
||||||
|
# bei keiner Lampe in Reichweite mehr (die Firmware loescht den Zaehler vor
|
||||||
|
# jedem Recovery-Versuch).
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
CONF=/etc/ninlight-station.conf
|
||||||
|
[ -r "$CONF" ] || { echo "Fehler: $CONF fehlt - zuerst tools/station/setup_pi.sh ausfuehren" >&2; exit 1; }
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
. "$CONF"
|
||||||
|
|
||||||
|
need_profile() {
|
||||||
|
nmcli -t -f NAME con show | grep -qx "$AP_CONNECTION" || {
|
||||||
|
echo "Fehler: NetworkManager-Profil '$AP_CONNECTION' fehlt - tools/station/setup_pi.sh ausfuehren" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
images() {
|
||||||
|
find "$IMAGE_DIR" -maxdepth 1 -name 'ninlight_*_ota.img' 2>/dev/null | sort
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
on)
|
||||||
|
need_profile
|
||||||
|
n=$(images | wc -l | tr -d ' ')
|
||||||
|
[ "$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 systemctl start ninlight-station
|
||||||
|
echo "Station an: SSID $SSID auf 192.168.8.1, $n Image(s)"
|
||||||
|
echo "Nicht vergessen: 'ninlight-station off', wenn du fertig bist."
|
||||||
|
;;
|
||||||
|
off)
|
||||||
|
sudo systemctl stop ninlight-station 2>/dev/null || true
|
||||||
|
sudo nmcli con down "$AP_CONNECTION" >/dev/null 2>&1 || true
|
||||||
|
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)"
|
||||||
|
else
|
||||||
|
echo "Station aus. Profil '$HOME_CONNECTION' fehlt, wlan0 bleibt ungenutzt - der Zugang ueber Kabel ist davon nicht betroffen." >&2
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
printf 'Dienst : %s\n' "$(systemctl is-active ninlight-station 2>/dev/null || echo inaktiv)"
|
||||||
|
printf 'wlan0 : %s\n' "$(nmcli -t -f GENERAL.CONNECTION device show wlan0 2>/dev/null | cut -d: -f2)"
|
||||||
|
printf 'Adresse: %s\n' "$(ip -4 -br addr show wlan0 2>/dev/null | awk '{print $3}')"
|
||||||
|
echo "Images :"
|
||||||
|
images | while read -r f; do
|
||||||
|
printf ' %-34s %s\n' "$(basename "$f")" \
|
||||||
|
"$(python3 - "$f" <<'PY'
|
||||||
|
import pathlib, sys
|
||||||
|
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")
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
done
|
||||||
|
[ -r /run/ninlight-station/status.json ] && { echo "Zuletzt ausgeliefert:"; sed 's/^/ /' /run/ninlight-station/status.json; }
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Aufruf: ninlight-station on|off|status" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=ninlight recovery station (HTTP image server)
|
||||||
|
After=network.target
|
||||||
|
# Ohne den Zugangspunkt hat der Server keine Adresse zum Binden.
|
||||||
|
BindsTo=NetworkManager.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
EnvironmentFile=/etc/ninlight-station.conf
|
||||||
|
ExecStart=/usr/bin/python3 /opt/ninlight-station/server.py --image-dir ${IMAGE_DIR}
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=2
|
||||||
|
|
||||||
|
# Laeuft als root: Port 80 und - fuer 4c - die DHCP-Leases in
|
||||||
|
# /var/lib/NetworkManager (Verzeichnis 0700 root:root) sind sonst nicht erreichbar.
|
||||||
|
# Gegengewicht:
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=yes
|
||||||
|
PrivateTmp=yes
|
||||||
|
RuntimeDirectory=ninlight-station
|
||||||
|
ReadWritePaths=/run/ninlight-station
|
||||||
|
RestrictAddressFamilies=AF_INET AF_UNIX
|
||||||
|
MemoryMax=128M
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# /etc/ninlight-station.conf - von tools/station/setup_pi.sh angelegt.
|
||||||
|
# Muss zu den Build-Parametern der Firmware passen (NINLIGHT_RECOVERY_SSID,
|
||||||
|
# NINLIGHT_RECOVERY_PASSWORD in config.local.cmake). Weicht etwas ab, findet
|
||||||
|
# die Lampe die Station nicht.
|
||||||
|
|
||||||
|
SSID="ninlight-recovery"
|
||||||
|
|
||||||
|
# Oeffentlicher Default. Fuer den echten Einsatz hier UND in config.local.cmake
|
||||||
|
# einen eigenen Wert setzen - wer das Passwort kennt und in Funkreichweite ist,
|
||||||
|
# kann jeder Lampe beim naechsten Boot beliebige Firmware unterschieben.
|
||||||
|
PSK="ninlight-recovery"
|
||||||
|
|
||||||
|
IMAGE_DIR="/opt/ninlight-station/images"
|
||||||
|
|
||||||
|
# NetworkManager-Profil, in das "ninlight-station off" zurueckschaltet.
|
||||||
|
HOME_CONNECTION="preconfigured"
|
||||||
|
|
||||||
|
# NetworkManager-Profil der Station selbst.
|
||||||
|
AP_CONNECTION="ninlight-recovery"
|
||||||
Reference in New Issue
Block a user