Fixrunde 1 zu Task 4: - C1 (kritisch): die Unit leerte /run/ninlight-station bei jedem systemctl restart, weil RuntimeDirectoryPreserve fehlte - genau der Neustart, mit dem migrate/recovery on den Dienst hochfahren, wischte die eben geschriebene Modus-/Steuerdatei wieder weg, und der Server fiel still auf den Recovery-Default zurueck. Unit bekommt RuntimeDirectoryPreserve=restart, und start_and_verify() prueft nach dem Neustart zusaetzlich ausdruecklich nach, dass Modus und Phase noch stimmen. - I1: status las die Phase aus status.json, die arm/listen (die den Dienst bewusst nicht neu starten) nie aktualisieren - jetzt kommt sie aus control.json. - I2: off pruefte nach dem Herunterfahren nichts nach und konnte "Station aus" melden, waehrend ein Profil wegen haengendem NetworkManager weiterhin aktiv war - ap_switch() verifiziert das jetzt auch im Aus-Fall. - I3: der Fehlerzweig von ap_switch (Mismatch nach dem Umschalten) war im bisherigen Test nie erreichbar; der nmcli-Stub kann das jetzt erzwingen. - I4: neue Pflichtschluessel blockierten jeden Unterbefehl auf einem bereits eingerichteten Pi. setup_pi.sh ergaenzt fehlende Schluessel in einer bestehenden Konfiguration jetzt einzeln, statt sie unangetastet zu lassen; dafuer musste das Skript per bash-s-Heredocs und ueberschreibbaren Pfadvariablen erst testbar gemacht werden (neuer test_setup_pi.sh). Dazu vier kleinere Punkte aus derselben Review: grep -Fqx statt grep -qx fuer Profilnamen, install-Stub durch tee+chmod ersetzt (POSIX, kein Sonderfall mehr fuer macOS), Rechtepruefung auf control.json in den arm/listen-Testfaellen, stderr sauber von stdout getrennt in den Warnungs-/Fehlerfaellen.
156 lines
6.6 KiB
Bash
Executable File
156 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Richtet einen Raspberry Pi als ninlight-Station ein. Idempotent: mehrfaches
|
|
# Ausfuehren aendert nichts und schadet nicht - auch nicht auf einem Pi, auf dem
|
|
# schon eine aeltere Version dieses Skripts gelaufen ist (siehe "Konfiguration"
|
|
# unten: fehlende Schluessel werden ergaenzt, vorhandene bleiben unangetastet).
|
|
#
|
|
# Aufruf: tools/station/setup_pi.sh <pi-host>
|
|
set -euo pipefail
|
|
HOST=${1:?Aufruf: tools/station/setup_pi.sh <pi-host>}
|
|
HERE=$(cd "$(dirname "$0")" && pwd)
|
|
# /usr/bin/ssh und /usr/bin/scp statt Varianten im PATH, die sich anders verhalten koennen.
|
|
# Ueberschreibbar (wie NINLIGHT_STATION_ROOT unten und in deploy.sh), damit ein Test gegen
|
|
# Stubs statt einen echten Pi laufen kann.
|
|
SSH_BIN=${NINLIGHT_SSH_BIN:-/usr/bin/ssh}
|
|
SCP_BIN=${NINLIGHT_SCP_BIN:-/usr/bin/scp}
|
|
|
|
echo "== Voraussetzungen auf $HOST pruefen =="
|
|
"$SSH_BIN" "$HOST" bash -s <<'REMOTE'
|
|
set -e
|
|
systemctl is-active --quiet NetworkManager || { echo "NetworkManager laeuft nicht - dieser Plan setzt ihn voraus" >&2; exit 1; }
|
|
nmcli -t -f DEVICE,STATE device | grep -q "^wlan0:" || { echo "wlan0 wird nicht von NetworkManager verwaltet" >&2; exit 1; }
|
|
python3 -c "import sys; sys.exit(0 if sys.version_info >= (3,11) else 1)" || { echo "Python 3.11+ noetig" >&2; exit 1; }
|
|
echo " ok"
|
|
REMOTE
|
|
|
|
echo "== Konfiguration =="
|
|
"$SCP_BIN" -q "$HERE/station.conf.example" "$HOST:/tmp/ninlight-station.conf.example"
|
|
# CONF ueberschreibbar wie ROOT_DIR/BIN_PATH/UNIT_PATH unten: auf dem Pi bleibt es
|
|
# beim Default, ein Test kann hier gegen eine Sandbox-Datei statt gegen echte
|
|
# Systempfade laufen.
|
|
"$SSH_BIN" "$HOST" bash -s <<'REMOTE'
|
|
set -e
|
|
CONF=${NINLIGHT_STATION_CONF:-/etc/ninlight-station.conf}
|
|
TEMPLATE=/tmp/ninlight-station.conf.example
|
|
if [ -f "$CONF" ]; then
|
|
# Eine bestehende Konfiguration bleibt bei ihren Werten - nur Schluessel, die
|
|
# eine neuere Version des CLI zusaetzlich verlangt (z.B. die Migrations- und
|
|
# Anzeige-Schluessel aus Task 4), werden aus der Vorlage ergaenzt. Ohne das
|
|
# wuerde das Ausrollen eines neuen ninlight-station auf einen bereits
|
|
# eingerichteten Pi jeden Unterbefehl - "off" eingeschlossen - mit "Schluessel
|
|
# ... fehlt" blockieren, und ein bereits laufender Zugangspunkt bliebe an, bis
|
|
# jemand die 0600-Datei von Hand ergaenzt (Fixrunde 1, I4).
|
|
ADDED=""
|
|
for KEY in SSID PSK IMAGE_DIR HOME_CONNECTION AP_CONNECTION \
|
|
MIGRATE_AP_CONNECTION MIGRATE_SSID MIGRATE_PSK EPD_REVISION; do
|
|
if ! sudo grep -Eq "^${KEY}=" "$CONF"; then
|
|
LINE=$(grep -E "^${KEY}=" "$TEMPLATE" || true)
|
|
if [ -n "$LINE" ]; then
|
|
printf '%s\n' "$LINE" | sudo tee -a "$CONF" >/dev/null
|
|
ADDED="$ADDED $KEY"
|
|
fi
|
|
fi
|
|
done
|
|
if [ -n "$ADDED" ]; then
|
|
echo " $CONF: fehlende Schluessel ergaenzt:$ADDED (vorhandene Werte unveraendert)"
|
|
else
|
|
echo " $CONF existiert bereits, alle Schluessel vorhanden"
|
|
fi
|
|
else
|
|
sudo install -m 0600 "$TEMPLATE" "$CONF"
|
|
echo " $CONF aus der Vorlage angelegt"
|
|
fi
|
|
rm -f "$TEMPLATE"
|
|
REMOTE
|
|
|
|
echo "== Server, CLI und Unit installieren =="
|
|
"$SCP_BIN" -q "$HERE/server.py" "$HERE/products.py" "$HERE/ninlight-station" "$HERE/ninlight-station.service" "$HOST:/tmp/"
|
|
# Pfade als Variablen mit Default (wie in deploy.sh): so kann ein Test die
|
|
# Installation gegen ein Sandbox-Verzeichnis statt gegen echte Systempfade laufen
|
|
# lassen.
|
|
"$SSH_BIN" "$HOST" bash -s <<'REMOTE'
|
|
set -e
|
|
ROOT_DIR=${NINLIGHT_STATION_ROOT:-/opt/ninlight-station}
|
|
BIN_PATH=${NINLIGHT_STATION_BIN:-/usr/local/bin/ninlight-station}
|
|
UNIT_PATH=${NINLIGHT_STATION_UNIT:-/etc/systemd/system/ninlight-station.service}
|
|
sudo install -d -m 0755 "$ROOT_DIR" "$ROOT_DIR/images"
|
|
sudo install -m 0755 /tmp/server.py "$ROOT_DIR/server.py"
|
|
sudo install -m 0644 /tmp/products.py "$ROOT_DIR/products.py"
|
|
sudo install -m 0755 /tmp/ninlight-station "$BIN_PATH"
|
|
sudo install -m 0644 /tmp/ninlight-station.service "$UNIT_PATH"
|
|
rm -f /tmp/server.py /tmp/products.py /tmp/ninlight-station /tmp/ninlight-station.service
|
|
sudo systemctl daemon-reload
|
|
echo " installiert"
|
|
REMOTE
|
|
|
|
echo "== NetworkManager-Profil =="
|
|
# Zwei Dinge, die man an einer echten Lampenstation nicht falsch machen darf:
|
|
# - /etc/ninlight-station.conf ist 0600 root:root (das Passwort soll nicht
|
|
# jeder lesen koennen). Der SSH-Benutzer ist aber nicht root, direktes
|
|
# ". /etc/ninlight-station.conf" wuerde also mit "Permission denied"
|
|
# abbrechen. "sudo cat" + eval liest sie dennoch ein, ohne die Rechte der
|
|
# Datei aufzuweichen.
|
|
# - "nmcli ... wifi-sec.psk $PSK" wuerde das Passwort als Kommandozeilen-
|
|
# argument an einen Prozess uebergeben - fuer jeden lokalen Benutzer per
|
|
# "ps"/"/proc/<pid>/cmdline" sichtbar. Deshalb wird das Verbindungsprofil
|
|
# stattdessen direkt als NetworkManager-Keyfile (0600 root:root)
|
|
# geschrieben; das Passwort landet nur in einer Root-Datei, nie in einer
|
|
# Prozessliste.
|
|
"$SSH_BIN" "$HOST" bash -s <<'REMOTE'
|
|
set -e
|
|
CONF=${NINLIGHT_STATION_CONF:-/etc/ninlight-station.conf}
|
|
NM_DIR=${NINLIGHT_NM_DIR:-/etc/NetworkManager/system-connections}
|
|
eval "$(sudo cat "$CONF")"
|
|
CONN_FILE="$NM_DIR/${AP_CONNECTION}.nmconnection"
|
|
if sudo test -f "$CONN_FILE"; then
|
|
UUID=$(sudo sed -n 's/^uuid=//p' "$CONN_FILE" | head -n1)
|
|
ACTION="aktualisiert"
|
|
else
|
|
# /proc/sys/kernel/random/uuid gibt es nur unter Linux (dem Pi) - der Fallback
|
|
# auf uuidgen ist es, was einen Testlauf gegen den lokalen Stub (macOS) ueberhaupt
|
|
# ermoeglicht, aendert aber nichts am Verhalten auf dem Pi selbst.
|
|
UUID=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen | tr '[:upper:]' '[:lower:]')
|
|
ACTION="angelegt"
|
|
fi
|
|
TMP=$(mktemp)
|
|
trap 'rm -f "$TMP"' EXIT
|
|
{
|
|
printf '%s\n' '[connection]'
|
|
printf 'id=%s\n' "$AP_CONNECTION"
|
|
printf 'uuid=%s\n' "$UUID"
|
|
printf '%s\n' 'type=wifi'
|
|
printf '%s\n' 'interface-name=wlan0'
|
|
printf '%s\n' 'autoconnect=false'
|
|
printf '\n%s\n' '[wifi]'
|
|
printf '%s\n' 'mode=ap'
|
|
printf 'ssid=%s\n' "$SSID"
|
|
printf '%s\n' 'band=bg'
|
|
printf '%s\n' 'channel=6'
|
|
printf '\n%s\n' '[wifi-security]'
|
|
printf '%s\n' 'key-mgmt=wpa-psk'
|
|
printf '%s\n' 'proto=rsn'
|
|
printf '%s\n' 'pairwise=ccmp;'
|
|
printf '%s\n' 'group=ccmp;'
|
|
printf 'psk=%s\n' "$PSK"
|
|
printf '\n%s\n' '[ipv4]'
|
|
printf '%s\n' 'method=shared'
|
|
printf '%s\n' 'address1=192.168.8.1/24'
|
|
printf '\n%s\n' '[ipv6]'
|
|
printf '%s\n' 'method=disabled'
|
|
} >"$TMP"
|
|
sudo install -o root -g root -m 0600 "$TMP" "$CONN_FILE"
|
|
rm -f "$TMP"
|
|
sudo nmcli connection load "$CONN_FILE" >/dev/null
|
|
echo " Profil $AP_CONNECTION $ACTION"
|
|
REMOTE
|
|
|
|
cat <<'EOF'
|
|
|
|
Fertig. Die Station ist absichtlich noch AUS.
|
|
|
|
Naechste Schritte:
|
|
tools/station/deploy.sh <pi-host> Images ausliefern
|
|
ssh <pi-host> ninlight-station on Station einschalten
|
|
ssh <pi-host> ninlight-station off wieder ausschalten (nicht vergessen)
|
|
EOF
|