fix(station): Anzeige-Installation absichern, apt darf nur warnen
Fixrunde 1 zu Task 6b. - setup_pi.sh raeumt die Ablage vor dem scp ab und bricht ab, wenn display.py dort fehlt. Ein Reststand aus einem abgebrochenen Lauf liess "scp -r" ins vorhandene Ziel hineinkopieren; die Installationsschleife ueberspringt Verzeichnisse und fand nichts - der Lauf meldete Erfolg, aktivierte den Anzeigedienst und hinterliess ein leeres display/: 203/EXEC im 5-Sekunden-Takt bei schwarzem Panel. - Der Paketschritt steht jetzt hinter der Installation und warnt nur. Eine Migrationsstation steht typischerweise nicht am Netz; unter set -e davor hiess ein gescheitertes apt: gar keine Station statt einer Station ohne Panel. "apt-get update" kommt vor dem install, der Anzeigedienst startet nach dem Paketschritt. - setup_pi.sh raeumt display/ auf dem Pi wie deploy.sh auf (im Repo geloeschte Dateien verschwinden), der Trap haengt auch an INT/TERM/HUP, und mktemp bekommt eine Vorlage: ohne sie beachtet BSD-mktemp TMPDIR nicht, und die Temp-Datei landete je nach Plattform woanders. - test_host.sh ruft test_deploy.sh auf - 95 Zusagen liefen nur bei Handaufruf, darunter die Deploy-Haelfte der Anzeige-Auslieferung. Der Coverage-Test deckt jetzt auch die Shell-Suiten ab und zaehlt dabei nur nicht auskommentierte Zeilen. - Neue Szenarien fuer alle vier Punkte, darunter die Zusage, dass beim gescheiterten Schreiben eines Profils keine Datei mit dem WLAN-Passwort liegenbleibt.
This commit is contained in:
+95
-22
@@ -77,24 +77,21 @@ fi
|
||||
rm -f "$TEMPLATE"
|
||||
REMOTE
|
||||
|
||||
echo "== Pakete =="
|
||||
# Pillow kommt auf dem Pi bewusst aus dem Distributionspaket (python3-pil), nicht per
|
||||
# pip: tools/station/ laeuft dort unter dem System-Python 3.13, und Debian 13 laesst ein
|
||||
# "pip install" ins System-Python gar nicht mehr zu (PEP 668). Ohne Pillow zeichnet die
|
||||
# Anzeige nicht - display.py faengt den ImportError ab und beendet sich still mit 0
|
||||
# (Absicht, damit ein fehlendes Panel nie eine Migration verhindert). Genau deshalb muss
|
||||
# das Paket HIER kommen: sonst faellt es nirgends auf.
|
||||
echo "== Server, CLI und Units installieren =="
|
||||
# Reststand der Ablage wegraeumen, BEVOR das scp laeuft (Task 6b F1, I1). "scp -r quelle
|
||||
# ziel" kopiert das Quellverzeichnis IN ein bereits vorhandenes Ziel hinein - aus
|
||||
# einem liegengebliebenen "…/ninlight-display/" wird dann "…/ninlight-display/display/",
|
||||
# die Installationsschleife unten ueberspringt Verzeichnisse und findet gar nichts mehr.
|
||||
# Ergebnis waere ein Lauf, der Erfolg meldet, den Anzeigedienst aktiviert und ein leeres
|
||||
# $ROOT_DIR/display hinterlaesst: 203/EXEC im 5-Sekunden-Takt, Panel schwarz. Genau so
|
||||
# ein Reststand entsteht, wenn ein frueherer Lauf nach dem scp abbricht (sudo-Timeout,
|
||||
# Platte voll, Strg-C) - die Aufraeumzeile unten laeuft dann nicht mehr. Idempotent
|
||||
# heisst, dass auch dieser Fall glattgeht (siehe Kopf dieser Datei).
|
||||
"$SSH_BIN" "$HOST" bash -s <<'REMOTE'
|
||||
set -e
|
||||
if python3 -c 'import PIL' 2>/dev/null; then
|
||||
echo " python3-pil ist bereits vorhanden"
|
||||
else
|
||||
sudo apt-get install -y python3-pil
|
||||
echo " python3-pil installiert"
|
||||
fi
|
||||
STAGE_DIR=${NINLIGHT_STAGE_DIR:-/tmp}
|
||||
rm -rf "$STAGE_DIR/ninlight-display"
|
||||
REMOTE
|
||||
|
||||
echo "== Server, CLI und Units installieren =="
|
||||
"$SCP_BIN" -q "$HERE/server.py" "$HERE/products.py" "$HERE/ninlight-station" \
|
||||
"$HERE/ninlight-station.service" "$HERE/ninlight-display.service" "$HOST:$STAGE_DIR/"
|
||||
# Das Anzeigeverzeichnis als Ganzes: render.py, display.py, epdconfig.py, die beiden
|
||||
@@ -120,6 +117,15 @@ sudo install -m 0644 "$STAGE_DIR/ninlight-station.service" "$UNIT_PATH"
|
||||
# Anzeige: display.py ist ausfuehrbar (die Unit ruft es zwar ueber /usr/bin/python3 auf,
|
||||
# "ninlight-station display test" aber auch von Hand), der Rest ist importierter Code.
|
||||
rm -rf "$STAGE_DIR/ninlight-display/__pycache__"
|
||||
# Harte Vorbedingung (Task 6b F1, I1): ohne display.py in der Ablage hat das scp nicht
|
||||
# geliefert, was es sollte - abgebrochen, falsch verschachtelt oder gar nicht gelaufen.
|
||||
# Die Schleife darunter wuerde das stillschweigend als "nichts zu tun" verbuchen, der
|
||||
# Lauf meldete Erfolg und der aktivierte Anzeigedienst liefe gegen eine nicht
|
||||
# existierende Datei. Lieber hier laut abbrechen.
|
||||
if [ ! -f "$STAGE_DIR/ninlight-display/display.py" ]; then
|
||||
echo "Fehler: $STAGE_DIR/ninlight-display/display.py fehlt - die Uebertragung des Anzeigeverzeichnisses ist fehlgeschlagen. Nichts installiert." >&2
|
||||
exit 1
|
||||
fi
|
||||
for f in "$STAGE_DIR"/ninlight-display/*; do
|
||||
[ -f "$f" ] || continue
|
||||
case "$(basename "$f")" in
|
||||
@@ -127,21 +133,76 @@ for f in "$STAGE_DIR"/ninlight-display/*; do
|
||||
*) sudo install -m 0644 "$f" "$ROOT_DIR/display/$(basename "$f")" ;;
|
||||
esac
|
||||
done
|
||||
# Was im Repo geloescht wurde, muss auch hier verschwinden - dasselbe Argument wie in
|
||||
# deploy.sh (dort per "rsync --delete"): display/ ist reiner Code aus dem Repo, es gibt
|
||||
# keinen Bestand, der einen Lauf ueberleben muesste, und ein zurueckgebliebenes altes
|
||||
# Modul wuerde weiter mit importiert. rsync steht hier nicht zur Verfuegung (setup_pi.sh
|
||||
# setzt ausdruecklich nur ssh/scp voraus), also von Hand.
|
||||
for f in "$ROOT_DIR"/display/*; do
|
||||
[ -e "$f" ] || continue
|
||||
if [ ! -e "$STAGE_DIR/ninlight-display/$(basename "$f")" ]; then
|
||||
sudo rm -rf "$f"
|
||||
echo " veraltete Datei entfernt: display/$(basename "$f")"
|
||||
fi
|
||||
done
|
||||
sudo install -m 0644 "$STAGE_DIR/ninlight-display.service" "$DISPLAY_UNIT_PATH"
|
||||
rm -rf "$STAGE_DIR/server.py" "$STAGE_DIR/products.py" "$STAGE_DIR/ninlight-station" \
|
||||
"$STAGE_DIR/ninlight-station.service" "$STAGE_DIR/ninlight-display.service" \
|
||||
"$STAGE_DIR/ninlight-display"
|
||||
sudo systemctl daemon-reload
|
||||
echo " installiert"
|
||||
REMOTE
|
||||
|
||||
echo "== Pakete =="
|
||||
# Pillow kommt auf dem Pi bewusst aus dem Distributionspaket (python3-pil), nicht per
|
||||
# pip: tools/station/ laeuft dort unter dem System-Python 3.13, und Debian 13 laesst ein
|
||||
# "pip install" ins System-Python gar nicht mehr zu (PEP 668). Ohne Pillow zeichnet die
|
||||
# Anzeige nicht - display.py faengt den ImportError ab und beendet sich still mit 0
|
||||
# (Absicht, damit ein fehlendes Panel nie eine Migration verhindert). Genau deshalb muss
|
||||
# das Paket ueberhaupt hier kommen: sonst faellt es nirgends auf.
|
||||
#
|
||||
# Aber NACH der Installation und nur als Warnung (Task 6b F1, I4): eine Migrationsstation
|
||||
# steht typischerweise nicht am Netz, und mit veralteten Paketlisten oder ohne Internet
|
||||
# scheitert apt. Unter "set -e" vor der Installation haette das den Lauf abgebrochen,
|
||||
# bevor server.py, das CLI, beide Units und beide NetworkManager-Profile installiert sind
|
||||
# - man haette dann GAR KEINE Station statt einer Station ohne Panel. Das waere die
|
||||
# einzige harte Stelle in einer sonst durchgehend weichen Kette gewesen (display.py
|
||||
# beendet sich mit 0, deploy.sh warnt nur, enable/restart unten warnen nur).
|
||||
"$SSH_BIN" "$HOST" bash -s <<'REMOTE'
|
||||
set -e
|
||||
if python3 -c 'import PIL' 2>/dev/null; then
|
||||
echo " python3-pil ist bereits vorhanden"
|
||||
else
|
||||
# "update" zuerst: auf einem laenger nicht gepflegten Pi zeigen die Paketlisten auf
|
||||
# Versionen, die es im Archiv nicht mehr gibt, und "install" scheitert mit 404.
|
||||
# Beide Aufrufe duerfen nur warnen - siehe oben.
|
||||
if sudo apt-get update >/dev/null 2>&1 && sudo apt-get install -y python3-pil; then
|
||||
echo " python3-pil installiert"
|
||||
else
|
||||
echo " Warnung: python3-pil liess sich nicht installieren (kein Netz? veraltete Paketlisten?)." >&2
|
||||
echo " Die Station ist vollstaendig eingerichtet, das E-Paper bleibt aber leer," >&2
|
||||
echo " bis 'sudo apt install python3-pil' durchlaeuft und der Anzeigedienst neu startet." >&2
|
||||
fi
|
||||
fi
|
||||
REMOTE
|
||||
|
||||
echo "== Anzeigedienst aktivieren =="
|
||||
# Die Anzeige wird aktiviert (startet also beim Booten mit) UND jetzt gestartet - anders
|
||||
# als die Station selbst, die absichtlich aus bleibt: die Anzeige sendet nichts, sie
|
||||
# zeigt nur an, und "Station aus" ist genau das, was nach dem Einrichten auf dem Panel
|
||||
# stehen soll. Fehlt Panel oder Pillow, beendet sich der Dienst still mit 0 (siehe
|
||||
# display.py) - deshalb "|| true": ein fehlendes Panel darf setup_pi.sh nicht abbrechen.
|
||||
# stehen soll. Erst hier, nach dem Paketschritt: so startet der Dienst bereits mit
|
||||
# vorhandenem Pillow, statt sich beim ersten Zeichnen still mit 0 zu beenden und bis zum
|
||||
# naechsten manuellen Neustart nichts anzuzeigen.
|
||||
#
|
||||
# Fehlt Panel oder Pillow, beendet sich der Dienst still mit 0 (siehe display.py) -
|
||||
# deshalb nur Warnungen: ein fehlendes Panel darf setup_pi.sh nicht abbrechen.
|
||||
"$SSH_BIN" "$HOST" bash -s <<'REMOTE'
|
||||
set -e
|
||||
sudo systemctl enable ninlight-display.service >/dev/null 2>&1 || \
|
||||
echo " Warnung: 'systemctl enable ninlight-display' fehlgeschlagen" >&2
|
||||
echo " Warnung: 'systemctl enable ninlight-display' fehlgeschlagen - die Anzeige startet nach einem Neustart des Pi nicht von allein mit" >&2
|
||||
sudo systemctl restart ninlight-display.service >/dev/null 2>&1 || \
|
||||
echo " Warnung: 'systemctl restart ninlight-display' fehlgeschlagen" >&2
|
||||
echo " installiert (Anzeigedienst aktiviert)"
|
||||
echo " Warnung: 'systemctl restart ninlight-display' fehlgeschlagen - journalctl -u ninlight-display" >&2
|
||||
echo " Anzeigedienst aktiviert"
|
||||
REMOTE
|
||||
|
||||
echo "== NetworkManager-Profile =="
|
||||
@@ -171,8 +232,20 @@ eval "$(sudo cat "$CONF")"
|
||||
# "$tmp" ist "local" - beim Auswerten des Traps am Skriptende gaebe es die
|
||||
# Variable gar nicht mehr. Deshalb ein gemeinsames Verzeichnis mit genau einem
|
||||
# Trap darum herum (0700, mktemp -d legt es so an).
|
||||
PROFILE_TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$PROFILE_TMPDIR"' EXIT
|
||||
#
|
||||
# Auch auf INT/TERM/HUP, nicht nur EXIT: bash fuehrt den EXIT-Trap zwar nach einem
|
||||
# abgefangenen Signal aus, aber nur, wenn das Signal ueberhaupt abgefangen wird - ohne
|
||||
# eigenen Handler beendet ein Strg-C (INT) oder ein abbrechendes ssh (HUP) die Shell
|
||||
# sofort, und der EXIT-Trap laeuft nie. Genau dann bliebe die Datei liegen.
|
||||
#
|
||||
# Mit ausdruecklicher Vorlage unter ${TMPDIR:-/tmp}, nicht als blankes "mktemp -d":
|
||||
# BSD-mktemp (macOS, wo diese Suite auch laeuft) beachtet TMPDIR ohne Vorlage gar nicht
|
||||
# und legt unabhaengig davon unter /var/folders/... an, GNU-mktemp (der Pi) dagegen
|
||||
# schon. Ohne Vorlage landet das Verzeichnis also je nach Plattform woanders - ein Test,
|
||||
# der nachsieht, ob wirklich nichts liegengeblieben ist, sucht dann auf macOS an der
|
||||
# falschen Stelle und ist gruen, ohne etwas geprueft zu haben.
|
||||
PROFILE_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/ninlight-profile.XXXXXX")
|
||||
trap 'rm -rf "$PROFILE_TMPDIR"' EXIT INT TERM HUP
|
||||
|
||||
# $1 = Profilname, $2 = SSID, $3 = PSK, $4 = "update" (bestehendes Profil neu schreiben,
|
||||
# UUID behalten) oder "keep" (bestehendes Profil unangetastet lassen).
|
||||
|
||||
@@ -136,6 +136,15 @@ case "\$1" in
|
||||
# RuntimeDirectoryPreserve=restart in der Unit leert systemd das
|
||||
# RuntimeDirectory bei JEDEM Stop/Restart, nicht nur beim allerersten Start,
|
||||
# und legt es leer wieder an.
|
||||
#
|
||||
# ACHTUNG, Stub-Erfindung (Task 6b F1): die Regel wird hier auf JEDE
|
||||
# Unit angewandt, also auch auf ninlight-display - die traegt aber gar kein
|
||||
# RuntimeDirectory=, ein "stop ninlight-display" raeumt in Wirklichkeit nichts
|
||||
# weg. \$UNIT_FILE ist immer ninlight-station.service, unabhaengig davon, welche
|
||||
# Unit im Aufruf steht. Heute folgenlos (kein Testfall stoppt die Anzeige und
|
||||
# erwartet danach noch Dateien in RUN_DIR), aber niemand darf sich darauf
|
||||
# verlassen: wer kuenftig prueft, was ein "display off" mit RUN_DIR macht, muss
|
||||
# den Stub vorher nach Unit unterscheiden lassen.
|
||||
if ! grep -q '^RuntimeDirectoryPreserve=restart' "\$UNIT_FILE" 2>/dev/null; then
|
||||
rm -rf "\$RUN_DIR"
|
||||
mkdir -p "\$RUN_DIR"
|
||||
|
||||
@@ -52,6 +52,14 @@ EOF
|
||||
cat >"$bindir/scp" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
echo "STUB scp \$*" >>"$log"
|
||||
# Testschalter (Task 6b F1, I1): laesst die Uebertragung des Anzeigeverzeichnisses
|
||||
# scheitern, ohne dass scp selbst einen Fehler meldet - genau das Bild eines
|
||||
# abgebrochenen oder falsch verschachtelten "scp -r".
|
||||
for a in "\$@"; do
|
||||
case "\$a" in
|
||||
*ninlight-display) [ -n "\${FAKE_SCP_SKIP_DIR:-}" ] && exit 0 ;;
|
||||
esac
|
||||
done
|
||||
args=()
|
||||
for a in "\$@"; do
|
||||
case "\$a" in
|
||||
@@ -73,7 +81,14 @@ EOF
|
||||
cat >"$bindir/apt-get" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
echo "STUB apt-get \$*" >>"$log"
|
||||
exit "\${FAKE_APT_EXIT:-0}"
|
||||
# Getrennt schaltbar: der Fall, den I4 beschreibt, ist "Paketlisten lassen sich
|
||||
# auffrischen, das Paket ist trotzdem nicht zu holen" (404). Ein Stub, der schon
|
||||
# "update" scheitern laesst, kaeme bei "install" nie an.
|
||||
case "\$1" in
|
||||
update) exit "\${FAKE_APT_UPDATE_EXIT:-0}" ;;
|
||||
install) exit "\${FAKE_APT_EXIT:-0}" ;;
|
||||
esac
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
# Steht fuer das python3 DES PI, nicht fuer das des Testrechners: nur die
|
||||
@@ -99,6 +114,14 @@ EOF
|
||||
cat >"$bindir/sudo" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
echo "STUB sudo \$*" >>"$log"
|
||||
# Testschalter (Task 6b F1, I2): laesst ein bestimmtes "sudo install" scheitern -
|
||||
# gebraucht, um den Abbruchpfad in write_profile() zu erreichen, auf dem die
|
||||
# Temp-Datei mit dem WLAN-Passwort sonst liegenbliebe.
|
||||
if [ -n "\${FAKE_SUDO_INSTALL_FAIL:-}" ]; then
|
||||
case "\$*" in
|
||||
install*"\$FAKE_SUDO_INSTALL_FAIL"*) echo "install: simulierter Fehler" >&2; exit 1 ;;
|
||||
esac
|
||||
fi
|
||||
if [ "\$1" = "install" ]; then
|
||||
args=()
|
||||
skip=0
|
||||
@@ -117,7 +140,11 @@ EOF
|
||||
cat >"$bindir/systemctl" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
echo "STUB systemctl \$*" >>"$log"
|
||||
# Testschalter: laesst "enable"/"restart" scheitern (beides darf setup_pi.sh nur
|
||||
# warnen lassen, nie abbrechen).
|
||||
case "\$1" in
|
||||
enable) exit "\${FAKE_SYSTEMCTL_ENABLE_EXIT:-0}" ;;
|
||||
restart) exit "\${FAKE_SYSTEMCTL_RESTART_EXIT:-0}" ;;
|
||||
is-active) exit "\${FAKE_SYSTEMCTL_ACTIVE_EXIT:-0}" ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
@@ -155,7 +182,13 @@ setup_state() {
|
||||
# "ninlight-station.conf.example" oder "server.py".
|
||||
run_setup() {
|
||||
local state=$1 stubbin=$2
|
||||
# TMPDIR in die Sandbox: write_profile() legt seine Temp-Dateien (mit dem WLAN-
|
||||
# Passwort darin) per "mktemp -d" unter TMPDIR an. Nur so kann Szenario 12 danach
|
||||
# nachsehen, ob wirklich nichts liegengeblieben ist - und nebenbei fasst der Test
|
||||
# damit auch dafuer nicht das echte /tmp des Entwicklungsrechners an.
|
||||
mkdir -p "$state/tmpdir"
|
||||
PATH="$stubbin:$PATH" \
|
||||
TMPDIR="$state/tmpdir" \
|
||||
NINLIGHT_SSH_BIN="$stubbin/ssh" \
|
||||
NINLIGHT_SCP_BIN="$stubbin/scp" \
|
||||
NINLIGHT_STAGE_DIR="$state/tmp" \
|
||||
@@ -430,6 +463,156 @@ else
|
||||
pass "kein Migrationsprofil mit leerem PSK angelegt"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "=== Szenario 11 (Task 6b F1, I1): Reststand in der Ablage fuehrt nicht zu einer stillen Nullinstallation ==="
|
||||
# Ein frueherer Lauf ist nach dem scp abgebrochen (sudo-Timeout, Platte voll, Strg-C),
|
||||
# die Aufraeumzeile lief nicht. "scp -r quelle ziel" kopiert dann IN das vorhandene Ziel
|
||||
# hinein (…/ninlight-display/display/), die Installationsschleife ueberspringt
|
||||
# Verzeichnisse und findet nichts - der Lauf meldete frueher Erfolg und aktivierte einen
|
||||
# Dienst, dessen ExecStart-Datei gar nicht existiert (203/EXEC im 5-Sekunden-Takt).
|
||||
STATE="$TMPROOT/s11-state"; STUBBIN="$TMPROOT/bin11"; LOG="$TMPROOT/s11.log"
|
||||
setup_state "$STATE"
|
||||
setup_stubbin "$STUBBIN" "$LOG"
|
||||
mkdir -p "$STATE/tmp/ninlight-display"
|
||||
echo "rest" >"$STATE/tmp/ninlight-display/uebrig.py"
|
||||
: >"$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"
|
||||
DISP11="$STATE/opt/ninlight-station/display"
|
||||
[ -f "$DISP11/display.py" ] && pass "display.py trotz Reststand installiert" || fail "Nullinstallation: $DISP11 enthaelt $(ls -A "$DISP11" 2>/dev/null | tr '\n' ' ')"
|
||||
[ -f "$DISP11/epd2in13_V3.py" ] && pass "Panel-Treiber trotz Reststand installiert" || fail "Panel-Treiber fehlt"
|
||||
[ ! -d "$DISP11/display" ] && pass "kein verschachteltes display/display/ entstanden" || fail "verschachteltes Verzeichnis angelegt"
|
||||
[ ! -f "$DISP11/uebrig.py" ] && pass "der Reststand des Vorlaufs wurde nicht mitinstalliert" || fail "uebrig.py aus dem Reststand landete auf dem Pi"
|
||||
|
||||
echo
|
||||
echo "=== Szenario 12 (Task 6b F1, I1): scheitert die Uebertragung, wird laut abgebrochen statt still 'installiert' gemeldet ==="
|
||||
STATE="$TMPROOT/s12-state"; STUBBIN="$TMPROOT/bin12"; LOG="$TMPROOT/s12.log"
|
||||
setup_state "$STATE"
|
||||
setup_stubbin "$STUBBIN" "$LOG"
|
||||
: >"$LOG"
|
||||
set +e
|
||||
OUT=$(FAKE_SCP_SKIP_DIR=1 run_setup "$STATE" "$STUBBIN" 2>&1)
|
||||
RC=$?
|
||||
set -e
|
||||
[ "$RC" -ne 0 ] && pass "Exit-Code != 0" || fail "Exit-Code 0 trotz fehlgeschlagener Uebertragung: $OUT"
|
||||
echo "$OUT" | grep -q "display.py fehlt" && pass "Fehlermeldung nennt die fehlende Datei" || fail "Fehlermeldung fehlt: $OUT"
|
||||
# Das Schlimmste waere ein aktivierter Dienst ohne ExecStart-Datei.
|
||||
grep -Fq "systemctl enable ninlight-display" "$LOG" && fail "der Anzeigedienst wurde trotz fehlender Dateien aktiviert - 203/EXEC im Neustart-Takt" || pass "kein aktivierter Dienst ohne Dateien"
|
||||
|
||||
echo
|
||||
echo "=== Szenario 13 (Task 6b F1, I1): eine im Repo geloeschte Datei verschwindet auch auf dem Pi ==="
|
||||
# Dasselbe Argument wie in deploy.sh: display/ ist reiner Code aus dem Repo, ein
|
||||
# zurueckgebliebenes altes Modul wuerde weiter mit importiert.
|
||||
STATE="$TMPROOT/s13-state"; STUBBIN="$TMPROOT/bin13"; LOG="$TMPROOT/s13.log"
|
||||
setup_state "$STATE"
|
||||
setup_stubbin "$STUBBIN" "$LOG"
|
||||
mkdir -p "$STATE/opt/ninlight-station/display"
|
||||
echo "alt" >"$STATE/opt/ninlight-station/display/veraltet.py"
|
||||
: >"$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"
|
||||
[ ! -f "$STATE/opt/ninlight-station/display/veraltet.py" ] && pass "veraltete Datei entfernt" || fail "veraltet.py blieb liegen"
|
||||
[ -f "$STATE/opt/ninlight-station/display/render.py" ] && pass "der aktuelle Code ist trotzdem da" || fail "render.py fehlt"
|
||||
|
||||
echo
|
||||
echo "=== Szenario 14 (Task 6b F1, I2): scheitert das Schreiben eines Profils, bleibt keine Temp-Datei mit dem Passwort liegen ==="
|
||||
# Zwischen "mktemp" und "install" steht das WLAN-Passwort in einer Datei unter TMPDIR.
|
||||
# Ein Abbruch genau dort (set -e) darf sie nicht zuruecklassen - dafuer gibt es den Trap
|
||||
# um das gemeinsame mktemp-Verzeichnis. Ohne ihn faellt das sonst nirgends auf.
|
||||
STATE="$TMPROOT/s14-state"; STUBBIN="$TMPROOT/bin14"; LOG="$TMPROOT/s14.log"
|
||||
setup_state "$STATE"
|
||||
setup_stubbin "$STUBBIN" "$LOG"
|
||||
cat >"$STATE/etc/ninlight-station.conf" <<'CONF'
|
||||
SSID='eigene-ssid'
|
||||
PSK='streng-geheimes-passwort'
|
||||
IMAGE_DIR='/opt/ninlight-station/images'
|
||||
HOME_CONNECTION='preconfigured'
|
||||
AP_CONNECTION='ninlight-recovery'
|
||||
MIGRATE_SSID='qconnex-firmware'
|
||||
MIGRATE_PSK='qconnex-firmware'
|
||||
MIGRATE_AP_CONNECTION='qconnex-firmware'
|
||||
EPD_REVISION='V3'
|
||||
CONF
|
||||
chmod 0600 "$STATE/etc/ninlight-station.conf"
|
||||
: >"$LOG"
|
||||
set +e
|
||||
OUT=$(FAKE_SUDO_INSTALL_FAIL=.nmconnection run_setup "$STATE" "$STUBBIN" 2>&1)
|
||||
RC=$?
|
||||
set -e
|
||||
[ "$RC" -ne 0 ] && pass "Exit-Code != 0 (das Profil liess sich nicht schreiben)" || fail "Exit-Code 0 trotz fehlgeschlagenem install: $OUT"
|
||||
LEFTOVER=$(find "$STATE/tmpdir" -type f 2>/dev/null | head -n5)
|
||||
[ -z "$LEFTOVER" ] && pass "keine Temp-Datei unter TMPDIR zurueckgeblieben" || fail "liegengeblieben: $LEFTOVER"
|
||||
# Der eigentliche Schaden waere das Passwort in einer Datei, die niemand mehr aufraeumt.
|
||||
if grep -rlq "streng-geheimes-passwort" "$STATE/tmpdir" 2>/dev/null; then
|
||||
fail "das WLAN-Passwort steht in einer liegengebliebenen Datei unter $STATE/tmpdir"
|
||||
else
|
||||
pass "das WLAN-Passwort steht in keiner liegengebliebenen Datei"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "=== Szenario 15 (Task 6b F1, I4): scheitert apt, ist die Station trotzdem vollstaendig eingerichtet ==="
|
||||
# Eine Migrationsstation steht typischerweise nicht am Netz. Ein harter Abbruch hier
|
||||
# hiesse: gar keine Station statt einer Station ohne Panel.
|
||||
STATE="$TMPROOT/s15-state"; STUBBIN="$TMPROOT/bin15"; LOG="$TMPROOT/s15.log"
|
||||
setup_state "$STATE"
|
||||
setup_stubbin "$STUBBIN" "$LOG"
|
||||
: >"$LOG"
|
||||
set +e
|
||||
OUT=$(FAKE_PIL_MISSING=1 FAKE_APT_EXIT=100 run_setup "$STATE" "$STUBBIN" 2>"$TMPROOT/s15.err")
|
||||
RC=$?
|
||||
set -e
|
||||
ERR=$(cat "$TMPROOT/s15.err")
|
||||
[ "$RC" -eq 0 ] && pass "Exit-Code 0 trotz gescheitertem apt" || fail "Exit-Code $RC - kein Netz haette die ganze Einrichtung verhindert: $ERR"
|
||||
echo "$ERR" | grep -q "Warnung" && pass "Warnung (keine Fehlermeldung) auf stderr" || fail "keine Warnung auf stderr: $ERR"
|
||||
[ -x "$STATE/opt/ninlight-station/server.py" ] && pass "server.py installiert" || fail "server.py fehlt"
|
||||
[ -x "$STATE/usr/local/bin/ninlight-station" ] && pass "CLI installiert" || fail "CLI fehlt"
|
||||
[ -f "$STATE/etc/systemd/system/ninlight-station.service" ] && pass "Stations-Unit installiert" || fail "Stations-Unit fehlt"
|
||||
[ -f "$STATE/etc/systemd/system/ninlight-display.service" ] && pass "Anzeige-Unit installiert" || fail "Anzeige-Unit fehlt"
|
||||
[ -f "$STATE/opt/ninlight-station/display/display.py" ] && pass "Anzeigecode installiert" || fail "Anzeigecode fehlt"
|
||||
NMDIR15="$STATE/etc/NetworkManager/system-connections"
|
||||
[ -f "$NMDIR15/ninlight-recovery.nmconnection" ] && pass "Rettungsprofil angelegt" || fail "Rettungsprofil fehlt"
|
||||
[ -f "$NMDIR15/qconnex-firmware.nmconnection" ] && pass "Migrationsprofil angelegt" || fail "Migrationsprofil fehlt"
|
||||
# Reihenfolge: apt kommt NACH der Installation, sonst haette ein Fehlschlag alles davor
|
||||
# mitgerissen.
|
||||
INST_LINE=$({ grep -n "install .*ninlight-station.service" "$LOG" | head -n1 | cut -d: -f1; } || true)
|
||||
APT_LINE=$({ grep -n "apt-get install" "$LOG" | head -n1 | cut -d: -f1; } || true)
|
||||
if [ -n "$INST_LINE" ] && [ -n "$APT_LINE" ] && [ "$INST_LINE" -lt "$APT_LINE" ]; then
|
||||
pass "der Paketschritt laeuft nach der Installation (inst=$INST_LINE, apt=$APT_LINE)"
|
||||
else
|
||||
fail "der Paketschritt laeuft weiterhin vor der Installation (inst=$INST_LINE, apt=$APT_LINE)"
|
||||
fi
|
||||
# Und der Anzeigedienst startet erst nach dem Paketschritt - sonst liefe er beim
|
||||
# allerersten Zeichnen ohne Pillow und beendete sich still mit 0.
|
||||
RESTART_LINE=$({ grep -n "systemctl restart ninlight-display" "$LOG" | head -n1 | cut -d: -f1; } || true)
|
||||
if [ -n "$RESTART_LINE" ] && [ "$APT_LINE" -lt "$RESTART_LINE" ]; then
|
||||
pass "der Anzeigedienst startet nach dem Paketschritt (apt=$APT_LINE, restart=$RESTART_LINE)"
|
||||
else
|
||||
fail "der Anzeigedienst startet vor dem Paketschritt (apt=$APT_LINE, restart=$RESTART_LINE)"
|
||||
fi
|
||||
grep -Fq "apt-get update" "$LOG" && pass "die Paketlisten werden vorher aufgefrischt" || fail "kein 'apt-get update' vor dem install"
|
||||
|
||||
echo
|
||||
echo "=== Szenario 16: ein fehlgeschlagenes enable/restart des Anzeigedienstes warnt nur ==="
|
||||
STATE="$TMPROOT/s16-state"; STUBBIN="$TMPROOT/bin16"; LOG="$TMPROOT/s16.log"
|
||||
setup_state "$STATE"
|
||||
setup_stubbin "$STUBBIN" "$LOG"
|
||||
: >"$LOG"
|
||||
set +e
|
||||
OUT=$(FAKE_SYSTEMCTL_ENABLE_EXIT=1 FAKE_SYSTEMCTL_RESTART_EXIT=1 run_setup "$STATE" "$STUBBIN" 2>"$TMPROOT/s16.err")
|
||||
RC=$?
|
||||
set -e
|
||||
ERR=$(cat "$TMPROOT/s16.err")
|
||||
[ "$RC" -eq 0 ] && pass "Exit-Code 0 trotz fehlgeschlagenem enable/restart" || fail "Exit-Code $RC: $ERR"
|
||||
echo "$ERR" | grep -q "systemctl enable ninlight-display" && pass "Warnung zu 'enable' auf stderr" || fail "keine enable-Warnung: $ERR"
|
||||
echo "$ERR" | grep -q "systemctl restart ninlight-display" && pass "Warnung zu 'restart' auf stderr" || fail "keine restart-Warnung: $ERR"
|
||||
[ -f "$STATE/etc/NetworkManager/system-connections/qconnex-firmware.nmconnection" ] && pass "der Lauf ging danach normal weiter (Profile angelegt)" || fail "der Lauf brach nach der Warnung ab"
|
||||
|
||||
echo
|
||||
if [ "$FAILED" -eq 0 ]; then
|
||||
echo "Alle Tests bestanden."
|
||||
|
||||
Reference in New Issue
Block a user