From d464bb167ed8e5a88334cc06f031ead8a192a845 Mon Sep 17 00:00:00 2001 From: Michael Sauer Date: Sun, 13 Sep 2026 01:29:25 +0200 Subject: [PATCH] fix: Voll-Deploy der Rettungsstation gegen doppelte Images und stillen Loeschverlust absichern Zwei Build-Verzeichnisse mit demselben Image-Dateinamen (z.B. build/evk und build/evk-onb, letzteres ohne WLAN-Zugangsdaten) landeten flach im selben Staging-Namen - die Sortierreihenfolge entschied zufaellig, welches gewinnt, und die Trailer-Pruefung merkt nichts, weil in beiden derselbe Geraetetyp steht. deploy.sh bricht jetzt ab und nennt beide Fundorte, statt zu raten. Der Voll-Deploy (deploy.sh ohne target - der in README.md gezeigte Normalfall) loeschte per rsync --delete zudem die Images aller gerade nicht gebauten Geraetetypen, ohne je zu sagen, welche. Vor dem Loeschen wird jetzt per Namensvergleich ermittelt und ausgegeben, welche Dateien verschwinden - sichtbar statt still, ohne den Ablauf fuer Skripte zu blockieren. Neue Testszenarien 7-9 in tools/station/tests/test_deploy.sh decken beide Faelle ab. Co-Authored-By: Claude Sonnet 5 --- tools/station/deploy.sh | 35 ++++++++++++- tools/station/tests/test_deploy.sh | 81 ++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/tools/station/deploy.sh b/tools/station/deploy.sh index b0ab712..b98e345 100755 --- a/tools/station/deploy.sh +++ b/tools/station/deploy.sh @@ -27,7 +27,23 @@ if [ $# -gt 0 ]; then done else FULL_SYNC=1 - while IFS= read -r f; do FILES+=("$f"); done < <(find "$ROOT/build" -maxdepth 2 -name 'ninlight_*_ota.img' | sort) + # Zwei Build-Verzeichnisse koennen denselben Image-Dateinamen liefern (z.B. + # build/evk/ und ein Nebenschauplatz wie build/evk-onb/, das ohne WLAN- + # Zugangsdaten gebaut wurde). Landen beide flach im selben STAGE/$base, + # entscheidet allein die Sortierreihenfolge, welches gewinnt - und die + # Trailer-Pruefung merkt nichts, weil in beiden derselbe Geraetetyp steht. + # Ein zweiter Fundort fuer denselben Dateinamen ist deshalb ein Abbruch, + # kein Zufall. + declare -A SEEN_AT=() + while IFS= read -r f; do + base=$(basename "$f") + if [ -n "${SEEN_AT[$base]:-}" ]; then + echo "Fehler: $base mehrfach gefunden - ${SEEN_AT[$base]} und $f. Voller Deploy abgebrochen, welches Bild echt ist, ist nicht entscheidbar. Nicht benoetigtes Build-Verzeichnis loeschen oder gezielt deployen." >&2 + exit 1 + fi + SEEN_AT[$base]=$f + FILES+=("$f") + done < <(find "$ROOT/build" -maxdepth 2 -name 'ninlight_*_ota.img' | sort) [ ${#FILES[@]} -gt 0 ] || { echo "Fehler: keine Images unter $ROOT/build gefunden" >&2; exit 1; } fi @@ -73,7 +89,22 @@ UNIT_PATH=${NINLIGHT_STATION_UNIT:-/etc/systemd/system/ninlight-station.service} sudo install -d -m 0755 "$ROOT_DIR/images" if [ "$FULL_SYNC" -eq 1 ]; then # Vollabgleich: alle Images ausserhalb der aktuellen Auslieferung entfernen. - echo " voller Abgleich - Images ausserhalb der Auswahl werden entfernt" + # Vorher zeigen, WELCHE das sind - sonst faellt ein geloeschtes Image erst + # auf, wenn jemand mit einer toten Lampe vor der Station steht. Ermittelt + # per einfachem Namensvergleich (portabel, kein Verlass auf rsyncs + # "deleting"-Ausgabe, die ohne -v gar nicht erscheint). + OLD_LIST=$(find "$ROOT_DIR/images" -maxdepth 1 -name 'ninlight_*_ota.img' -exec basename {} \; 2>/dev/null | sort) + NEW_LIST=$(find /tmp/ninlight-images -maxdepth 1 -name 'ninlight_*_ota.img' -exec basename {} \; | sort) + REMOVED="" + if [ -n "$OLD_LIST" ]; then + REMOVED=$(comm -23 <(printf '%s\n' "$OLD_LIST") <(printf '%s\n' "$NEW_LIST")) + fi + if [ -n "$REMOVED" ]; then + echo " voller Abgleich - folgende Images werden entfernt:" + echo "$REMOVED" | sed 's/^/ /' + else + echo " voller Abgleich - keine Images werden entfernt" + fi sudo rsync -a -c --delete /tmp/ninlight-images/ "$ROOT_DIR/images/" else # Gezielter Deploy: nur die uebertragenen Dateien ersetzen/ergaenzen, alle diff --git a/tools/station/tests/test_deploy.sh b/tools/station/tests/test_deploy.sh index 5b63823..347b973 100755 --- a/tools/station/tests/test_deploy.sh +++ b/tools/station/tests/test_deploy.sh @@ -275,6 +275,87 @@ else fail "setup_pi.sh: Default fuer SCP_BIN ist nicht mehr /usr/bin/scp" fi +echo +echo "=== Szenario 7: Voll-Deploy mit zwei Build-Verzeichnissen fuer denselben Dateinamen -> Abbruch (B1) ===" +SANDBOX="$TMPROOT/s7"; STUBBIN="$TMPROOT/bin7"; LOG="$TMPROOT/s7.log"; SANDBOX_STATE="$TMPROOT/s7-state" +setup_sandbox "$SANDBOX" +setup_stubbin "$STUBBIN" "$LOG" +mkdir -p "$SANDBOX/build/evk" "$SANDBOX/build/evk-onb" +echo "evk-mit-wlan" >"$SANDBOX/build/evk/ninlight_evk_ota.img" +echo "evk-ohne-wlan" >"$SANDBOX/build/evk-onb/ninlight_evk_ota.img" +: >"$LOG" +set +e +OUT=$(run_deploy "$SANDBOX" pi-test 2>&1) +RC=$? +set -e +if [ "$RC" -ne 0 ]; then pass "Exit-Code != 0 ($RC)"; else fail "Exit-Code war 0, obwohl zwei Fundorte denselben Dateinamen liefern"; fi +if echo "$OUT" | grep -q "ninlight_evk_ota.img mehrfach gefunden"; then + pass "Fehlermeldung nennt den doppelten Dateinamen" +else + fail "Fehlermeldung zum doppelten Dateinamen fehlt: $OUT" +fi +if echo "$OUT" | grep -q "build/evk/ninlight_evk_ota.img" && echo "$OUT" | grep -q "build/evk-onb/ninlight_evk_ota.img"; then + pass "Fehlermeldung nennt beide Fundorte" +else + fail "Fehlermeldung nennt nicht beide Fundorte: $OUT" +fi +if ! grep -q "STUB" "$LOG" 2>/dev/null; then + pass "kein Transfer-Kommando aufgerufen - nichts wurde stillschweigend uebertragen" +else + fail "ein Transfer-Kommando wurde trotz doppeltem Dateinamen aufgerufen: $(cat "$LOG")" +fi + +echo +echo "=== Szenario 8: Voll-Deploy zeigt vor dem Loeschen, welche Images verschwinden (B2) ===" +SANDBOX="$TMPROOT/s8"; STUBBIN="$TMPROOT/bin8"; LOG="$TMPROOT/s8.log"; SANDBOX_STATE="$TMPROOT/s8-state" +setup_sandbox "$SANDBOX" +setup_stubbin "$STUBBIN" "$LOG" +mkdir -p "$SANDBOX/build/foo" +echo "foo-image-neu" >"$SANDBOX/build/foo/ninlight_foo_ota.img" +mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images" +echo "veraltet" >"$SANDBOX_STATE/opt/ninlight-station/images/ninlight_veraltet_ota.img" +echo "auch-veraltet" >"$SANDBOX_STATE/opt/ninlight-station/images/ninlight_e14_cw_ota.img" +: >"$LOG" +set +e +OUT=$(run_deploy "$SANDBOX" pi-test 2>&1) +RC=$? +set -e +if [ "$RC" -eq 0 ]; then pass "Exit-Code 0"; else fail "Exit-Code $RC, Ausgabe: $OUT"; fi +if echo "$OUT" | grep -q "folgende Images werden entfernt"; then + pass "Ausgabe kuendigt die zu entfernenden Images an, bevor geloescht wird" +else + fail "Ausgabe nennt die zu entfernenden Images nicht: $OUT" +fi +if echo "$OUT" | grep -q "ninlight_veraltet_ota.img" && echo "$OUT" | grep -q "ninlight_e14_cw_ota.img"; then + pass "beide zu entfernenden Dateien werden namentlich genannt" +else + fail "nicht alle zu entfernenden Dateien werden genannt: $OUT" +fi +if [ ! -f "$SANDBOX_STATE/opt/ninlight-station/images/ninlight_veraltet_ota.img" ]; then + pass "veraltetes Image wurde nach der Ankuendigung tatsaechlich entfernt" +else + fail "veraltetes Image wurde trotz Ankuendigung nicht entfernt" +fi + +echo +echo "=== Szenario 9: Voll-Deploy ohne verwaiste Images meldet das statt zu schweigen (B2) ===" +SANDBOX="$TMPROOT/s9"; STUBBIN="$TMPROOT/bin9"; LOG="$TMPROOT/s9.log"; SANDBOX_STATE="$TMPROOT/s9-state" +setup_sandbox "$SANDBOX" +setup_stubbin "$STUBBIN" "$LOG" +mkdir -p "$SANDBOX/build/foo" +echo "foo-image" >"$SANDBOX/build/foo/ninlight_foo_ota.img" +: >"$LOG" +set +e +OUT=$(run_deploy "$SANDBOX" pi-test 2>&1) +RC=$? +set -e +if [ "$RC" -eq 0 ]; then pass "Exit-Code 0"; else fail "Exit-Code $RC, Ausgabe: $OUT"; fi +if echo "$OUT" | grep -q "keine Images werden entfernt"; then + pass "Ausgabe meldet explizit, dass nichts entfernt wird" +else + fail "Ausgabe meldet nicht, dass nichts entfernt wird: $OUT" +fi + echo if [ "$FAILED" -eq 0 ]; then echo "Alle Tests bestanden."