diff --git a/tools/station/ninlight-station b/tools/station/ninlight-station new file mode 100755 index 0000000..3ad216c --- /dev/null +++ b/tools/station/ninlight-station @@ -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 diff --git a/tools/station/ninlight-station.service b/tools/station/ninlight-station.service new file mode 100644 index 0000000..274cf95 --- /dev/null +++ b/tools/station/ninlight-station.service @@ -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 diff --git a/tools/station/station.conf.example b/tools/station/station.conf.example new file mode 100644 index 0000000..f98bb73 --- /dev/null +++ b/tools/station/station.conf.example @@ -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"