#!/usr/bin/env bash # Stub-Tests fuer setup_pi.sh - ohne Pi ausfuehrbar. # # ssh/scp werden im PATH durch Stubs ersetzt (Muster wie tests/test_deploy.sh): # ssh fuehrt das per Heredoc uebergebene Remote-Skript lokal aus, scp uebersetzt # "host:pfad"-Ziele auf den blossen lokalen Pfad und ruft dann "cp" auf. Die # System-Zielpfade (/etc/ninlight-station.conf, /opt/ninlight-station, ...) sind # per Umgebungsvariable auf ein Sandbox-Verzeichnis umgebogen (dasselbe Prinzip # wie NINLIGHT_STATION_ROOT/_BIN/_UNIT in deploy.sh) - setup_pi.sh fasst damit # nichts auf dem Testrechner selbst an ausser dem transienten /tmp, das schon # deploy.sh fuer denselben Zweck benutzt. # # Schwerpunkt: Fixrunde 1, I4 - eine bestehende Konfiguration muss um fehlende # Schluessel ergaenzt werden, ohne vorhandene Werte anzutasten. # # Aufruf: tools/station/tests/test_setup_pi.sh set -euo pipefail HERE=$(cd "$(dirname "$0")" && pwd) STATION_DIR=$(cd "$HERE/.." && pwd) TMPROOT=$(mktemp -d) trap 'rm -rf "$TMPROOT"' EXIT FAILED=0 pass() { echo "ok - $1"; } fail() { echo "FEHLER - $1" >&2; FAILED=1; } file_mode() { # $1 = Pfad. GNU-stat (Linux/Pi) muss zuerst probiert werden, nicht BSD-stat # (macOS): "stat -f '%Lp' datei" deutet auf GNU-coreutils "-f" als # "Dateisystemstatus" und '%Lp' als zweites Dateiargument, scheitert dabei # zwar (rc != 0), schreibt aber vorher schon mehrere Zeilen Dateisystem-Infos # auf stdout - der "||"-Zweig haengt danach zusaetzlich "600"/"644" an, und # der Aufrufer sieht Muell statt der reinen Zahl (Fixrunde 2, N1). BSD-"stat # -c" bricht dagegen sofort mit einem Fehler auf stderr ab, ganz ohne # Ausgabe auf stdout. stat -c '%a' "$1" 2>/dev/null || stat -f '%Lp' "$1" 2>/dev/null } setup_stubbin() { local bindir=$1 log=$2 mkdir -p "$bindir" cat >"$bindir/ssh" <>"$log" host=\$1; shift "\$@" EOF cat >"$bindir/scp" <>"$log" args=() for a in "\$@"; do case "\$a" in -*) continue ;; *:*) args+=("\${a#*:}") ;; *) args+=("\$a") ;; esac done exec cp "\${args[@]}" EOF # "sudo install -o root -g root ..." (im NetworkManager-Keyfile-Schritt) braucht # echtes Root, um den Eigentuemer zu setzen - hat der Testrechner (anders als der # Pi) nicht. Der Stub streicht -o/-g nur fuer "install", der Rest laeuft wie auf # dem Pi ueber ein exec des ungestubbten Kommandos. cat >"$bindir/sudo" <>"$log" if [ "\$1" = "install" ]; then args=() skip=0 for a in "\$@"; do if [ "\$skip" -eq 1 ]; then skip=0; continue; fi case "\$a" in -o|-g) skip=1 ;; *) args+=("\$a") ;; esac done exec "\${args[@]}" fi exec "\$@" EOF cat >"$bindir/systemctl" <>"$log" case "\$1" in is-active) exit "\${FAKE_SYSTEMCTL_ACTIVE_EXIT:-0}" ;; *) exit 0 ;; esac EOF cat >"$bindir/nmcli" <>"$log" if [ "\$1" = "-t" ] && [ "\$2" = "-f" ] && [ "\$3" = "DEVICE,STATE" ]; then echo "wlan0:connected" exit 0 fi exit 0 EOF chmod +x "$bindir"/ssh "$bindir"/scp "$bindir"/sudo "$bindir"/systemctl "$bindir"/nmcli } # $1 = Sandbox-Wurzel fuer die "Systempfade" setup_state() { local state=$1 rm -rf "$state" mkdir -p "$state/etc" "$state/opt" "$state/usr/local/bin" "$state/etc/systemd/system" \ "$state/etc/NetworkManager/system-connections" "$state/tmp" } # Fuehrt setup_pi.sh mit gestubbtem PATH und auf die Sandbox umgebogenen # Systempfaden aus. $1 = state-Verzeichnis, $2 = stub-bin-Verzeichnis. # # NINLIGHT_STAGE_DIR biegt auch die per scp "uebertragenen" Zwischendateien auf # die Sandbox um (Fixrunde 2, N4): ohne das wuerden scp/ssh-Stub sie unter dem # echten /tmp des Entwicklungsrechners ablegen und am Ende per "rm -f" wieder # loeschen - mit denselben, leicht kollidierenden Namen wie # "ninlight-station.conf.example" oder "server.py". run_setup() { local state=$1 stubbin=$2 PATH="$stubbin:$PATH" \ NINLIGHT_SSH_BIN="$stubbin/ssh" \ NINLIGHT_SCP_BIN="$stubbin/scp" \ NINLIGHT_STAGE_DIR="$state/tmp" \ NINLIGHT_STATION_CONF="$state/etc/ninlight-station.conf" \ NINLIGHT_STATION_ROOT="$state/opt/ninlight-station" \ NINLIGHT_STATION_BIN="$state/usr/local/bin/ninlight-station" \ NINLIGHT_STATION_UNIT="$state/etc/systemd/system/ninlight-station.service" \ NINLIGHT_NM_DIR="$state/etc/NetworkManager/system-connections" \ "$STATION_DIR/setup_pi.sh" pi-test } echo "=== Szenario 1: frischer Pi ohne Konfiguration -> Vorlage wird komplett angelegt ===" STATE="$TMPROOT/s1-state"; STUBBIN="$TMPROOT/bin1"; LOG="$TMPROOT/s1.log" setup_state "$STATE" setup_stubbin "$STUBBIN" "$LOG" : >"$LOG" set +e OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1) RC=$? set -e [ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT" if diff -q "$STATION_DIR/station.conf.example" "$STATE/etc/ninlight-station.conf" >/dev/null 2>&1; then pass "Konfiguration wurde 1:1 aus der Vorlage angelegt" else fail "Konfiguration weicht von der Vorlage ab oder fehlt" fi [ -x "$STATE/opt/ninlight-station/server.py" ] && pass "server.py installiert" || fail "server.py fehlt" [ -f "$STATE/opt/ninlight-station/products.py" ] && pass "products.py installiert" || fail "products.py fehlt (arm braucht es)" [ -x "$STATE/usr/local/bin/ninlight-station" ] && pass "CLI installiert" || fail "ninlight-station CLI fehlt" [ -f "$STATE/etc/systemd/system/ninlight-station.service" ] && pass "Unit installiert" || fail "Unit fehlt" [ -d "$STATE/opt/ninlight-station/images" ] && pass "images/-Verzeichnis angelegt" || fail "images/-Verzeichnis fehlt" echo echo "=== Szenario 2 (Fixrunde 1, I4): bestehende Konfiguration ohne die neuen Schluessel wird ergaenzt, nicht ersetzt ===" STATE="$TMPROOT/s2-state"; STUBBIN="$TMPROOT/bin2"; LOG="$TMPROOT/s2.log" setup_state "$STATE" setup_stubbin "$STUBBIN" "$LOG" cat >"$STATE/etc/ninlight-station.conf" <<'CONF' SSID='eigene-ssid' PSK='eigenes-passwort' IMAGE_DIR='/opt/ninlight-station/images' HOME_CONNECTION='preconfigured' AP_CONNECTION='ninlight-recovery' CONF chmod 0600 "$STATE/etc/ninlight-station.conf" : >"$LOG" set +e OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1) RC=$? set -e [ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT" echo "$OUT" | grep -q "ergaenzt" && pass "Ausgabe kuendigt das Ergaenzen fehlender Schluessel an" || fail "Ausgabe erwaehnt 'ergaenzt' nicht: $OUT" CONF_OUT="$STATE/etc/ninlight-station.conf" if grep -Fqx "SSID='eigene-ssid'" "$CONF_OUT" && grep -Fqx "PSK='eigenes-passwort'" "$CONF_OUT"; then pass "vorhandene Werte (SSID/PSK) blieben unveraendert" else fail "vorhandene Werte wurden ueberschrieben: $(cat "$CONF_OUT")" fi for KEY in MIGRATE_AP_CONNECTION MIGRATE_SSID MIGRATE_PSK EPD_REVISION; do if grep -Eq "^${KEY}=" "$CONF_OUT"; then pass "fehlender Schluessel $KEY wurde ergaenzt" else fail "Schluessel $KEY fehlt weiterhin in der Konfiguration" fi done MODE=$(file_mode "$CONF_OUT") [ "$MODE" = "600" ] && pass "Konfigurationsdatei bleibt 0600" || fail "Konfigurationsdatei hat falsche Rechte: $MODE" echo echo "=== Szenario 3: erneuter Lauf auf einer bereits vollstaendigen Konfiguration ist ein echtes No-Op ===" : >"$LOG" set +e OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1) RC=$? set -e [ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT" echo "$OUT" | grep -q "alle Schluessel vorhanden" && pass "meldet 'alle Schluessel vorhanden' statt erneut zu ergaenzen" || fail "Ausgabe: $OUT" DUP=$(grep -c '^EPD_REVISION=' "$CONF_OUT") [ "$DUP" -eq 1 ] && pass "EPD_REVISION steht nur einmal in der Datei (kein doppeltes Anhaengen)" || fail "EPD_REVISION steht $DUP mal in der Datei" echo echo "=== Szenario 4 (Fixrunde 2, N3): ein eingerueckter Bestandswert gilt nicht als fehlend ===" STATE="$TMPROOT/s4-state"; STUBBIN="$TMPROOT/bin4"; LOG="$TMPROOT/s4.log" setup_state "$STATE" setup_stubbin "$STUBBIN" "$LOG" # Fuehrendes Leerzeichen vor PSK= - gueltiges ". datei"-Shell, aber eine rein # zeilenverankerte Erkennung ("^PSK=") wuerde das als "fehlt" werten und die # Vorlagenzeile anhaengen. Da das CLI die Datei per ". $CONF" sourct, gewinnt # dann die LETZTE Zuweisung - das eigene Passwort waere durch den oeffentlichen # Default aus der Vorlage ersetzt, ohne dass die Datei das irgendwo anzeigt. cat >"$STATE/etc/ninlight-station.conf" <<'CONF' SSID='eigene-ssid' PSK='eigenes-eingeruecktes-passwort' IMAGE_DIR='/opt/ninlight-station/images' HOME_CONNECTION='preconfigured' AP_CONNECTION='ninlight-recovery' CONF chmod 0600 "$STATE/etc/ninlight-station.conf" : >"$LOG" set +e OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1) RC=$? set -e [ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT" CONF_OUT4="$STATE/etc/ninlight-station.conf" PSK_LINES=$(grep -cE '^[[:space:]]*PSK=' "$CONF_OUT4") [ "$PSK_LINES" -eq 1 ] && pass "PSK steht weiterhin nur einmal in der Datei (nicht dupliziert)" || fail "PSK steht $PSK_LINES mal in der Datei: $(cat "$CONF_OUT4")" if grep -Fq "eigenes-eingeruecktes-passwort" "$CONF_OUT4"; then pass "der eingerueckte Bestandswert von PSK blieb erhalten" else fail "der eingerueckte Bestandswert von PSK wurde ersetzt: $(cat "$CONF_OUT4")" fi if grep -Fqx "PSK='ninlight-recovery'" "$CONF_OUT4"; then fail "der oeffentliche Vorlagen-Default fuer PSK wurde zusaetzlich angehaengt - eigenes Passwort waere beim Sourcen ueberschrieben worden" else pass "der oeffentliche Vorlagen-Default fuer PSK wurde NICHT angehaengt" fi for KEY in MIGRATE_AP_CONNECTION MIGRATE_SSID MIGRATE_PSK EPD_REVISION; do grep -Eq "^${KEY}=" "$CONF_OUT4" && pass "fehlender Schluessel $KEY wurde trotzdem ergaenzt" || fail "Schluessel $KEY fehlt weiterhin" done echo if [ "$FAILED" -eq 0 ]; then echo "Alle Tests bestanden." else echo "Mindestens ein Test ist fehlgeschlagen." >&2 fi exit "$FAILED"