Fixrunde 2 zu Task 4:
- N1 (wichtig): file_mode() in test_cli.sh/test_setup_pi.sh probierte
"stat -f '%Lp'" vor "stat -c '%a'" - auf GNU-coreutils (dem Pi) deutet
"-f" dort "Dateisystemstatus" und liefert Muell statt der Rechte-Zahl,
faellt aber trotzdem in den "||"-Zweig durch, der zusaetzlich "644"
anhaengt. Reihenfolge umgedreht und mit einem per gstat erzwungenen
GNU-Testlauf zusaetzlich abgesichert.
- N2 (wichtig): status zeigte nach einem Wechsel zurueck in den
Rettungsmodus weiterhin die letzte Migrationsphase aus einer liegen
gebliebenen control.json (moeglich seit RuntimeDirectoryPreserve=
restart aus Fixrunde 1). Phase wird jetzt nur noch angezeigt, wenn
Modus tatsaechlich "migration" ist.
- N3 (klein): die Schluesselerkennung in setup_pi.sh war rein
zeilenverankert ("^KEY=") und haette einen eingerueckten Bestandswert
als fehlend gewertet - beim naechsten Sourcen der Konfiguration haette
die zusaetzlich angehaengte Vorlagenzeile das eigene Passwort
ueberschrieben. Muster auf fuehrenden Leerraum/export gehaertet.
- N4 (klein): test_setup_pi.sh liess setup_pi.sh ueber den scp-Stub echte
Dateien im /tmp des Entwicklungsrechners anlegen und wieder loeschen.
Neue Variable NINLIGHT_STAGE_DIR biegt das auf eine Sandbox um.
Alle vier Punkte mit Regressionstests, N2 und N3 zusaetzlich per
Mutationsprobe (Fix zurueckgenommen, Test wird rot) verifiziert.
257 lines
9.7 KiB
Bash
Executable File
257 lines
9.7 KiB
Bash
Executable File
#!/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" <<EOF
|
|
#!/usr/bin/env bash
|
|
echo "STUB ssh \$*" >>"$log"
|
|
host=\$1; shift
|
|
"\$@"
|
|
EOF
|
|
|
|
cat >"$bindir/scp" <<EOF
|
|
#!/usr/bin/env bash
|
|
echo "STUB 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" <<EOF
|
|
#!/usr/bin/env bash
|
|
echo "STUB 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" <<EOF
|
|
#!/usr/bin/env bash
|
|
echo "STUB systemctl \$*" >>"$log"
|
|
case "\$1" in
|
|
is-active) exit "\${FAKE_SYSTEMCTL_ACTIVE_EXIT:-0}" ;;
|
|
*) exit 0 ;;
|
|
esac
|
|
EOF
|
|
|
|
cat >"$bindir/nmcli" <<EOF
|
|
#!/usr/bin/env bash
|
|
echo "STUB 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"
|