deploy.sh legt Rettungsabzuege jetzt in images/rescue-wlan/ (aus build/) und images/rescue-nocreds/ (aus build-nocreds/) ab, statt sie flach in images/ abzulegen - dasselbe Muster wie migrate-wlan/migrate-nocreds, inklusive derselben --delete-Vorsicht (nur bei vollstaendigem Satz). Alter flacher Bestand wird bei jedem Deploy aufgeraeumt, auch bei einem gezielten. README beschreibt den Umschalter jetzt fuer beide Betriebsarten.
1569 lines
72 KiB
Bash
Executable File
1569 lines
72 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Stub-Tests fuer deploy.sh - ohne Pi ausfuehrbar.
|
|
#
|
|
# ssh/rsync werden im PATH durch Stubs ersetzt: ssh fuehrt das per Heredoc
|
|
# uebergebene Remote-Skript lokal aus (statt es an einen echten Pi zu
|
|
# schicken), rsync uebersetzt "host:pfad"-Ziele auf den blossen lokalen Pfad
|
|
# und ruft dann den echten rsync auf. So laeuft die komplette Logik von
|
|
# deploy.sh - inklusive des Installationsschritts, der normalerweise per SSH
|
|
# auf dem Pi passiert - tatsaechlich, aber vollstaendig gegen ein Sandbox-
|
|
# Verzeichnis (NINLIGHT_STATION_ROOT/…) statt gegen echte Systempfade.
|
|
#
|
|
# Aufruf: tools/station/tests/test_deploy.sh
|
|
set -euo pipefail
|
|
|
|
HERE=$(cd "$(dirname "$0")" && pwd)
|
|
STATION_DIR=$(cd "$HERE/.." && pwd)
|
|
MKIMAGE_DIR=$(cd "$STATION_DIR/../mkimage" && pwd)
|
|
REAL_RSYNC=$(command -v rsync)
|
|
|
|
# Sicherheitsnetz (Fixrunde 1, C4): NINLIGHT_SIGN_DIR ist genau die Variable, mit der
|
|
# man auf ~/workspace/fw-sign zeigt - und auf diesem Entwicklungsrechner liegen dort
|
|
# ECHTE Signierschluessel. Ist sie im aufrufenden Profil oder fuer einen frueheren
|
|
# manuellen Deploy exportiert, darf sie unter keinen Umstaenden in diese Suite
|
|
# durchsickern. Deshalb: hier explizit loeschen, und run_deploy() unten liest sie NIE
|
|
# wieder - jedes Szenario, das echte Testschluessel braucht, uebergibt sie ueber die
|
|
# separate Variable TEST_SIGN_DIR (die deploy.sh selbst gar nicht kennt und die daher
|
|
# auch nicht zufaellig aus der Umgebung vorbelegt sein kann).
|
|
unset NINLIGHT_SIGN_DIR
|
|
unset TEST_SIGN_DIR
|
|
|
|
TMPROOT=$(mktemp -d)
|
|
trap 'rm -rf "$TMPROOT"' EXIT
|
|
|
|
FAILED=0
|
|
pass() { echo "ok - $1"; }
|
|
fail() { echo "FEHLER - $1" >&2; FAILED=1; }
|
|
|
|
# --- Sandbox-Repo aufbauen: tools/station/deploy.sh + tools/mkimage/nl_mkimage.py + build/ ---
|
|
setup_sandbox() {
|
|
local sandbox=$1
|
|
rm -rf "$sandbox"
|
|
mkdir -p "$sandbox/tools/station" "$sandbox/tools/mkimage" "$sandbox/build"
|
|
cp "$STATION_DIR/deploy.sh" "$sandbox/tools/station/deploy.sh"
|
|
chmod +x "$sandbox/tools/station/deploy.sh"
|
|
# Das ECHTE Signierwerkzeug wird gebraucht (nicht gestubbt): die Migrations-Szenarien
|
|
# pruefen echtes Signieren/die echten Formatchecks (Mindestlaenge, kein Trailer, 4096
|
|
# Bit). Nur die Schluessel selbst sind Wegwerf-Testschluessel, nie echte.
|
|
cp "$MKIMAGE_DIR/nl_sign.py" "$sandbox/tools/mkimage/nl_sign.py"
|
|
# Inhalt irrelevant, die Dateien muessen nur existieren (werden "uebertragen").
|
|
echo "# dummy" >"$sandbox/tools/station/server.py"
|
|
echo "# dummy" >"$sandbox/tools/station/products.py"
|
|
echo "# dummy" >"$sandbox/tools/station/ninlight-station"
|
|
echo "# dummy" >"$sandbox/tools/station/ninlight-station.service"
|
|
echo "# dummy" >"$sandbox/tools/station/ninlight-display.service"
|
|
echo "# dummy" >"$sandbox/tools/station/ninlight-button.service"
|
|
echo "# dummy" >"$sandbox/tools/station/ninlight-poweroff.service"
|
|
echo "# dummy" >"$sandbox/tools/station/ninlight-reboot.service"
|
|
# display/ wird ECHT kopiert, nicht durch Attrappen ersetzt: die Zusage aus Task 6b
|
|
# ist, dass die beiden uebernommenen Panel-Treiber UND der Herkunftsnachweis
|
|
# HERKUNFT.md mit auf den Pi gehen. Mit selbst erfundenen Dateinamen im Sandbox-Repo
|
|
# wuerde der Test genau das nicht mehr zeigen - er pruefte nur noch sich selbst.
|
|
cp -R "$STATION_DIR/display" "$sandbox/tools/station/display"
|
|
rm -rf "$sandbox/tools/station/display/__pycache__"
|
|
# Ein __pycache__ mit .pyc dieses Rechners (andere Python-Version als der Pi) - es
|
|
# darf nicht mit uebertragen werden.
|
|
mkdir -p "$sandbox/tools/station/display/__pycache__"
|
|
echo "pyc-muell" >"$sandbox/tools/station/display/__pycache__/render.cpython-999.pyc"
|
|
# button/ ebenso echt kopiert, gleiches Argument wie bei display/ oben.
|
|
cp -R "$STATION_DIR/button" "$sandbox/tools/station/button"
|
|
rm -rf "$sandbox/tools/station/button/__pycache__"
|
|
mkdir -p "$sandbox/tools/station/button/__pycache__"
|
|
echo "pyc-muell" >"$sandbox/tools/station/button/__pycache__/button.cpython-999.pyc"
|
|
|
|
# Fake nl_mkimage.py --verify: verhaelt sich wie das echte Tool (eine Zeile
|
|
# "<geraet> <version> <bytes>" auf stdout bei Erfolg, Exit 0; eine Zeile auf
|
|
# stderr bei Fehlschlag, Exit 1), aber steuerbar per Umgebungsvariable
|
|
# MKIMAGE_FAIL_SUBSTR: enthaelt der zu pruefende Dateiname diesen Teilstring,
|
|
# schlaegt die Pruefung fehl.
|
|
cat >"$sandbox/tools/mkimage/nl_mkimage.py" <<'PY'
|
|
import os
|
|
import sys
|
|
|
|
args = sys.argv[1:]
|
|
f = args[args.index("--verify") + 1]
|
|
target = args[args.index("--expect-device") + 1]
|
|
fail_substr = os.environ.get("MKIMAGE_FAIL_SUBSTR", "")
|
|
if fail_substr and fail_substr in f:
|
|
print(f"Fehler: {f} besteht die Trailer-Pruefung nicht (Test-Stub)", file=sys.stderr)
|
|
sys.exit(1)
|
|
size = os.path.getsize(f)
|
|
print(f"{target} 1.0.0 {size}")
|
|
PY
|
|
}
|
|
|
|
# --- Stub-Kommandos fuer ssh/rsync im PATH ---
|
|
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/rsync" <<EOF
|
|
#!/usr/bin/env bash
|
|
echo "STUB rsync \$*" >>"$log"
|
|
args=()
|
|
for a in "\$@"; do
|
|
case "\$a" in
|
|
-*) args+=("\$a") ;;
|
|
*:*) args+=("\${a#*:}") ;;
|
|
*) args+=("\$a") ;;
|
|
esac
|
|
done
|
|
exec "$REAL_RSYNC" "\${args[@]}"
|
|
EOF
|
|
|
|
cat >"$bindir/scp" <<EOF
|
|
#!/usr/bin/env bash
|
|
echo "STUB scp \$* - deploy.sh sollte scp nicht aufrufen" >>"$log"
|
|
exit 1
|
|
EOF
|
|
|
|
cat >"$bindir/sudo" <<EOF
|
|
#!/usr/bin/env bash
|
|
echo "STUB sudo \$*" >>"$log"
|
|
exec "\$@"
|
|
EOF
|
|
|
|
# Der Stub unterscheidet nach Unit: deploy.sh behandelt den Anzeigedienst
|
|
# ausdruecklich anders als den Hauptdienst (ein fehlgeschlagener Neustart der
|
|
# Anzeige ist KEIN Deploy-Fehler). Mit einem Schalter fuer beide Units zusammen
|
|
# liesse sich genau dieser Unterschied nicht pruefen.
|
|
cat >"$bindir/systemctl" <<EOF
|
|
#!/usr/bin/env bash
|
|
echo "STUB systemctl \$*" >>"$log"
|
|
case "\$*" in
|
|
*ninlight-display*)
|
|
case "\$1" in
|
|
is-active) exit "\${FAKE_DISPLAY_ACTIVE_EXIT:-1}" ;;
|
|
restart) exit "\${FAKE_DISPLAY_RESTART_EXIT:-0}" ;;
|
|
*) exit 0 ;;
|
|
esac
|
|
;;
|
|
*ninlight-button*)
|
|
case "\$1" in
|
|
is-active) exit "\${FAKE_BUTTON_ACTIVE_EXIT:-1}" ;;
|
|
restart) exit "\${FAKE_BUTTON_RESTART_EXIT:-0}" ;;
|
|
*) exit 0 ;;
|
|
esac
|
|
;;
|
|
esac
|
|
case "\$1" in
|
|
is-active) exit "\${FAKE_SYSTEMCTL_ACTIVE_EXIT:-1}" ;;
|
|
restart) exit "\${FAKE_SYSTEMCTL_RESTART_EXIT:-0}" ;;
|
|
*) exit 0 ;;
|
|
esac
|
|
EOF
|
|
|
|
chmod +x "$bindir"/ssh "$bindir"/rsync "$bindir"/scp "$bindir"/sudo "$bindir"/systemctl
|
|
}
|
|
|
|
run_deploy() {
|
|
# Fuehrt deploy.sh mit stubbtem PATH aus; Ausgabe landet in $OUT, Exit-Code in $?.
|
|
local sandbox=$1; shift
|
|
# Auf dem echten Pi existieren /usr/local/bin und /etc/systemd/system schon;
|
|
# in der Sandbox muessen sie fuer "install" eigens angelegt werden.
|
|
mkdir -p "$SANDBOX_STATE/usr/local/bin" "$SANDBOX_STATE/etc/systemd/system"
|
|
# NINLIGHT_SIGN_DIR wird hier HART auf einen lokalen Wert gesetzt, niemals ueber
|
|
# "${NINLIGHT_SIGN_DIR:-default}" aus der geerbten Umgebung uebernommen (das war der
|
|
# C4-Fehler aus Fixrunde 1: ein Fallback greift nur, wenn die Variable NICHT gesetzt
|
|
# ist - war sie es, z.B. weil ein Profil sie dauerhaft auf ~/workspace/fw-sign zeigen
|
|
# laesst, haette der Fallback sie unveraendert durchgereicht). Die einzige Variable,
|
|
# die ein Szenario mit echten Testschluesseln setzen darf, ist TEST_SIGN_DIR - eine
|
|
# Variable, die deploy.sh selbst gar nicht kennt und die deshalb nie zufaellig von
|
|
# aussen vorbelegt sein kann wie NINLIGHT_SIGN_DIR es auf einem Entwicklungsrechner
|
|
# ist. Ohne TEST_SIGN_DIR zeigt sie auf einen Pfad, der in der Sandbox garantiert
|
|
# nicht existiert.
|
|
local sign_dir="${TEST_SIGN_DIR:-$sandbox/no-such-sign-dir}"
|
|
PATH="$STUBBIN:$PATH" \
|
|
NINLIGHT_SSH_BIN="$STUBBIN/ssh" \
|
|
NINLIGHT_STATION_ROOT="$SANDBOX_STATE/opt/ninlight-station" \
|
|
NINLIGHT_STATION_BIN="$SANDBOX_STATE/usr/local/bin/ninlight-station" \
|
|
NINLIGHT_STATION_UNIT="$SANDBOX_STATE/etc/systemd/system/ninlight-station.service" \
|
|
NINLIGHT_DISPLAY_UNIT="$SANDBOX_STATE/etc/systemd/system/ninlight-display.service" \
|
|
NINLIGHT_BUTTON_UNIT="$SANDBOX_STATE/etc/systemd/system/ninlight-button.service" \
|
|
NINLIGHT_POWEROFF_UNIT="$SANDBOX_STATE/etc/systemd/system/ninlight-poweroff.service" \
|
|
NINLIGHT_REBOOT_UNIT="$SANDBOX_STATE/etc/systemd/system/ninlight-reboot.service" \
|
|
NINLIGHT_SIGN_DIR="$sign_dir" \
|
|
"$sandbox/tools/station/deploy.sh" "$@"
|
|
}
|
|
|
|
# --- Hilfsmittel fuer die Migrations-Szenarien ---
|
|
|
|
# Erzeugt Wegwerf-Testschluessel (nl_sign.generate_test_key(), nie echte Schluessel,
|
|
# nie geschrieben ausser in dieses Sandbox-Verzeichnis) unter $keydir. with_prod/
|
|
# with_dev steuern, welche der beiden Dateien entsteht.
|
|
make_test_keys() {
|
|
local keydir=$1 with_prod=$2 with_dev=$3
|
|
mkdir -p "$keydir/QCX_Device_CA"
|
|
PYTHONPATH="$MKIMAGE_DIR" python3 - "$keydir" "$with_prod" "$with_dev" <<'PY'
|
|
import sys
|
|
import nl_sign
|
|
|
|
keydir, with_prod, with_dev = sys.argv[1], sys.argv[2] == "1", sys.argv[3] == "1"
|
|
if with_prod:
|
|
k = nl_sign.generate_test_key()
|
|
with open(keydir + "/QCX_Device_CA/qcx_firmware_signing_key.pem", "wb") as fh:
|
|
fh.write(k.private_pem)
|
|
if with_dev:
|
|
k = nl_sign.generate_test_key()
|
|
with open(keydir + "/fw_sign.pem", "wb") as fh:
|
|
fh.write(k.private_pem)
|
|
PY
|
|
}
|
|
|
|
# Erzeugt eine gueltige rohe _gz.img-Attrappe: >= 1024 Byte, durch 4 teilbar, ohne
|
|
# NINL-Trailer - genau das, was nl_sign.py als Eingabe akzeptiert.
|
|
make_gz_file() {
|
|
local path=$1 fill=${2:-A}
|
|
python3 -c "
|
|
import sys
|
|
path, fill = sys.argv[1], sys.argv[2].encode()
|
|
data = bytearray((fill * 2048)[:2048])
|
|
data[-512:-508] = b'\\x00\\x00\\x00\\x00' # nie zufaellig wie 'NINL' aussehen
|
|
with open(path, 'wb') as fh:
|
|
fh.write(bytes(data))
|
|
" "$path" "$fill"
|
|
}
|
|
|
|
# Erzeugt eine _ota.img-Attrappe mit ANDEREM Inhalt als das zugehoerige _gz.img und
|
|
# einem echten NINL-Trailer - damit ein versehentliches Signieren des _ota.img statt
|
|
# des _gz.img (Fall 7) zweifelsfrei auffliegt: weder Inhalt noch Groesse passen dann.
|
|
make_ota_file() {
|
|
local path=$1
|
|
python3 -c "
|
|
import sys
|
|
path = sys.argv[1]
|
|
body = b'B' * 2048
|
|
trailer = bytearray(b'\\xff' * 512)
|
|
trailer[0:4] = b'NINL'
|
|
with open(path, 'wb') as fh:
|
|
fh.write(body + bytes(trailer))
|
|
" "$path"
|
|
}
|
|
|
|
# Wegwerf-Zugangsdaten fuer die Pruefung des Bildsatzes "ohne-wlan" - niemals die
|
|
# echten aus dem Repo. Dieselbe Rolle wie SECRET_* in tools/tests/test_release.sh.
|
|
SECRET_SSID="TestWLAN-9c2e-geheim"
|
|
SECRET_PASS="testpasswort-9c2e"
|
|
SECRET_TOKEN="0badc0de9c2e0badc0de9c2e"
|
|
|
|
# Legt config.local.cmake im Sandbox-Repo an - genau die Datei, gegen die deploy.sh den
|
|
# Bildsatz "ohne-wlan" prueft.
|
|
write_local_cmake() {
|
|
local sandbox=$1
|
|
cat >"$sandbox/config.local.cmake" <<EOF
|
|
set(NINLIGHT_WIFI_SSID "$SECRET_SSID")
|
|
set(NINLIGHT_WIFI_PASSWORD "$SECRET_PASS")
|
|
set(NINLIGHT_TOKEN "$SECRET_TOKEN")
|
|
EOF
|
|
}
|
|
|
|
# Wie make_gz_file, aber mit einkompilierten WLAN-Zugangsdaten - das, was ein Abzug aus
|
|
# einem falsch konfigurierten build-nocreds/ (oder aus NINLIGHT_NOCREDS_BUILD_REL=build)
|
|
# wirklich enthielte. Der "Compiler" dieser Suite, analog zum Docker-Stub in
|
|
# test_release.sh: nur weil er die Werte tatsaechlich einbaut, sagt ein bestandener
|
|
# Pruefdurchlauf ueberhaupt etwas.
|
|
make_gz_file_with_creds() {
|
|
local path=$1 fill=${2:-A}
|
|
SSID="$SECRET_SSID" PASS="$SECRET_PASS" TOKEN="$SECRET_TOKEN" python3 -c "
|
|
import os, sys
|
|
path, fill = sys.argv[1], sys.argv[2].encode()
|
|
creds = (os.environ['SSID'] + '\\0' + os.environ['PASS'] + '\\0'
|
|
+ os.environ['TOKEN'] + '\\0').encode()
|
|
data = bytearray((fill * 2048)[:2048])
|
|
data[64:64 + len(creds)] = creds
|
|
data[-512:-508] = b'\\x00\\x00\\x00\\x00' # nie zufaellig wie 'NINL' aussehen
|
|
with open(path, 'wb') as fh:
|
|
fh.write(bytes(data))
|
|
" "$path" "$fill"
|
|
}
|
|
|
|
# Sucht eine Zeichenkette BINAER in jeder Datei unterhalb eines Verzeichnisses.
|
|
# Rueckgabe 0 = gefunden. Wie contains_secrets() in test_release.sh.
|
|
tree_contains() {
|
|
local dir=$1 needle=$2
|
|
NEEDLE="$needle" python3 - "$dir" <<'PY'
|
|
import os
|
|
import sys
|
|
|
|
needle = os.environ["NEEDLE"].encode()
|
|
for root, _dirs, files in os.walk(sys.argv[1]):
|
|
for fn in files:
|
|
with open(os.path.join(root, fn), "rb") as fh:
|
|
if needle in fh.read():
|
|
sys.exit(0)
|
|
sys.exit(1)
|
|
PY
|
|
}
|
|
|
|
echo "=== Szenario 1: Image besteht die Trailer-Pruefung nicht -> nichts uebertragen ==="
|
|
SANDBOX="$TMPROOT/s1"; STUBBIN="$TMPROOT/bin1"; LOG="$TMPROOT/s1.log"; SANDBOX_STATE="$TMPROOT/s1-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo"
|
|
echo "dummy" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(MKIMAGE_FAIL_SUBSTR=foo run_deploy "$SANDBOX" pi-test foo 2>&1)
|
|
RC=$?
|
|
set -e
|
|
if [ "$RC" -ne 0 ]; then pass "Exit-Code != 0 ($RC)"; else fail "Exit-Code war 0"; fi
|
|
if echo "$OUT" | grep -q "besteht die Trailer-Pruefung nicht"; then
|
|
pass "Fehlermeldung zur Trailer-Pruefung vorhanden"
|
|
else
|
|
fail "Fehlermeldung zur Trailer-Pruefung fehlt"
|
|
fi
|
|
if ! grep -q "STUB ssh\|STUB rsync\|STUB scp" "$LOG"; then
|
|
pass "kein ssh/rsync/scp aufgerufen - nichts wurde uebertragen"
|
|
else
|
|
fail "ssh/rsync/scp wurde trotz fehlgeschlagener Pruefung aufgerufen: $(cat "$LOG")"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 2: keine Images unter build/ gefunden ==="
|
|
SANDBOX="$TMPROOT/s2"; STUBBIN="$TMPROOT/bin2"; LOG="$TMPROOT/s2.log"; SANDBOX_STATE="$TMPROOT/s2-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
: >"$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"; fi
|
|
if echo "$OUT" | grep -q "keine Images unter"; then
|
|
pass "Fehlermeldung zu fehlenden Images vorhanden"
|
|
else
|
|
fail "Fehlermeldung zu fehlenden Images fehlt"
|
|
fi
|
|
if ! grep -q "STUB" "$LOG" 2>/dev/null; then
|
|
pass "kein Transfer-Kommando aufgerufen"
|
|
else
|
|
fail "ein Transfer-Kommando wurde trotz fehlender Images aufgerufen: $(cat "$LOG")"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 3: gezielter Deploy eines Geraetetyps laesst andere Images auf dem Ziel unangetastet (Critical 2) ==="
|
|
SANDBOX="$TMPROOT/s3"; STUBBIN="$TMPROOT/bin3"; LOG="$TMPROOT/s3.log"; SANDBOX_STATE="$TMPROOT/s3-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo" "$SANDBOX/build/bar"
|
|
echo "foo-image-neu" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
echo "bar-image" >"$SANDBOX/build/bar/ninlight_bar_ota.img"
|
|
# Zielverzeichnis simuliert einen vorherigen Voll-Deploy MIT dieser Fassung von
|
|
# deploy.sh: Rettungsabbilder fuer foo UND bar liegen schon in images/rescue-wlan/ (dem
|
|
# "mit-wlan"-Bildsatz aus build/, siehe server.py:RESCUE_SET_DIRS).
|
|
mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan"
|
|
echo "foo-image-alt" >"$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_foo_ota.img"
|
|
echo "bar-image" >"$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_bar_ota.img"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_deploy "$SANDBOX" pi-test foo 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 "andere Geraetetypen im Zielverzeichnis bleiben erhalten"; then
|
|
pass "Ausgabe kuendigt gezielten Deploy ohne --delete an"
|
|
else
|
|
fail "Ausgabe erwaehnt den gezielten Deploy nicht"
|
|
fi
|
|
if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_bar_ota.img" ]; then
|
|
pass "Image des NICHT ausgewaehlten Geraetetyps (bar) blieb erhalten"
|
|
else
|
|
fail "Image des nicht ausgewaehlten Geraetetyps (bar) wurde geloescht - Critical 2 nicht behoben"
|
|
fi
|
|
if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_foo_ota.img" ] \
|
|
&& [ "$(cat "$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_foo_ota.img")" = "foo-image-neu" ]; then
|
|
pass "Image des ausgewaehlten Geraetetyps (foo) wurde aktualisiert"
|
|
else
|
|
fail "Image des ausgewaehlten Geraetetyps (foo) wurde nicht aktualisiert"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 4: Voll-Deploy (kein target) entfernt veraltete Images - zur Abgrenzung von Szenario 3 ==="
|
|
SANDBOX="$TMPROOT/s4"; STUBBIN="$TMPROOT/bin4"; LOG="$TMPROOT/s4.log"; SANDBOX_STATE="$TMPROOT/s4-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"
|
|
: >"$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 [ ! -f "$SANDBOX_STATE/opt/ninlight-station/images/ninlight_veraltet_ota.img" ]; then
|
|
pass "veraltetes Image wurde beim Voll-Deploy entfernt (--delete wirkt weiterhin, wenn gewollt)"
|
|
else
|
|
fail "veraltetes Image wurde beim Voll-Deploy NICHT entfernt"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 5: Dienst laeuft, Neustart nach dem Deploy schlaegt fehl -> Exit != 0 (Minor) ==="
|
|
SANDBOX="$TMPROOT/s5"; STUBBIN="$TMPROOT/bin5"; LOG="$TMPROOT/s5.log"; SANDBOX_STATE="$TMPROOT/s5-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo" "$SANDBOX_STATE/usr/local/bin" "$SANDBOX_STATE/etc/systemd/system"
|
|
echo "foo-image" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
: >"$LOG"
|
|
set +e
|
|
# Ueber run_deploy() statt eines eigenen Aufrufs (Fixrunde 1: der vorherige direkte
|
|
# Aufruf hier setzte NINLIGHT_SIGN_DIR zwar auch hart, ging aber am gemeinsamen Muster
|
|
# vorbei - ein Praezedenzfall, den ein spaeterer Umbau leicht wieder falsch abschreiben
|
|
# koennte). FAKE_SYSTEMCTL_* werden als gewoehnliche vorangestellte Umgebungsvariablen
|
|
# durchgereicht, run_deploy() ist eine Funktion und sieht sie genauso wie jeder andere
|
|
# Befehl.
|
|
OUT=$(FAKE_SYSTEMCTL_ACTIVE_EXIT=0 FAKE_SYSTEMCTL_RESTART_EXIT=1 run_deploy "$SANDBOX" pi-test foo 2>&1)
|
|
RC=$?
|
|
set -e
|
|
if [ "$RC" -ne 0 ]; then pass "Exit-Code != 0, wenn der Neustart fehlschlaegt ($RC)"; else fail "Exit-Code war 0, obwohl der Neustart fehlschlug"; fi
|
|
if echo "$OUT" | grep -q "Neustart nach dem Deploy ist aber fehlgeschlagen"; then
|
|
pass "Fehlermeldung unterscheidet Neustart-Fehlschlag von 'Dienst laeuft nicht'"
|
|
else
|
|
fail "Fehlermeldung fehlt oder ist mehrdeutig"
|
|
fi
|
|
if echo "$OUT" | grep -q "Dienst laeuft nicht"; then
|
|
fail "faelschlich 'Dienst laeuft nicht' gemeldet, obwohl der Dienst lief"
|
|
else
|
|
pass "'Dienst laeuft nicht' wird nicht faelschlich gemeldet"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 6: deploy.sh/setup_pi.sh nutzen ohne Override /usr/bin/ssh (bzw. scp) ==="
|
|
# Die anderen Szenarien laufen mit NINLIGHT_SSH_BIN auf einen Stub umgebogen - das prueft die
|
|
# Logik, aber nicht mehr, WELCHEN ssh das Skript ohne Override tatsaechlich waehlt. Der Nutzer
|
|
# verlangt ausdruecklich /usr/bin/ssh und /usr/bin/scp statt einer Variante im PATH; das hier
|
|
# ist die Absicherung dagegen, dass der Default beim naechsten Umbau stillschweigend zurueck
|
|
# auf ein PATH-"ssh" rutscht.
|
|
if grep -q 'SSH_BIN=\${NINLIGHT_SSH_BIN:-/usr/bin/ssh}' "$STATION_DIR/deploy.sh"; then
|
|
pass "deploy.sh: Default fuer SSH_BIN ist /usr/bin/ssh"
|
|
else
|
|
fail "deploy.sh: Default fuer SSH_BIN ist nicht mehr /usr/bin/ssh"
|
|
fi
|
|
if grep -q 'SSH_BIN=\${NINLIGHT_SSH_BIN:-/usr/bin/ssh}' "$STATION_DIR/setup_pi.sh"; then
|
|
pass "setup_pi.sh: Default fuer SSH_BIN ist /usr/bin/ssh"
|
|
else
|
|
fail "setup_pi.sh: Default fuer SSH_BIN ist nicht mehr /usr/bin/ssh"
|
|
fi
|
|
if grep -q 'SCP_BIN=\${NINLIGHT_SCP_BIN:-/usr/bin/scp}' "$STATION_DIR/setup_pi.sh"; then
|
|
pass "setup_pi.sh: Default fuer SCP_BIN ist /usr/bin/scp"
|
|
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
|
|
echo "=== Vorbereitung: Wegwerf-Testschluessel fuer die Migrations-Szenarien ==="
|
|
# Nur ueber nl_sign.generate_test_key() erzeugt, nie ausserhalb dieses Testlaufs
|
|
# verwendet und nie in ein Repository- oder Log-File jenseits dieser TMPROOT-Sandbox
|
|
# geschrieben.
|
|
KEYDIR_FULL="$TMPROOT/keys-full"
|
|
make_test_keys "$KEYDIR_FULL" 1 1
|
|
KEYDIR_DEV_ONLY="$TMPROOT/keys-dev-only"
|
|
make_test_keys "$KEYDIR_DEV_ONLY" 0 1
|
|
pass "Testschluessel erzeugt"
|
|
|
|
echo
|
|
echo "=== Szenario 10: Mit vorhandenem Schluesselverzeichnis entstehen zwei Migrationsimages je Ziel (Fall 1) ==="
|
|
SANDBOX="$TMPROOT/s10"; STUBBIN="$TMPROOT/bin10"; LOG="$TMPROOT/s10.log"; SANDBOX_STATE="$TMPROOT/s10-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo"
|
|
make_gz_file "$SANDBOX/build/foo/ninlight_foo_gz.img"
|
|
echo "foo-ota" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(TEST_SIGN_DIR="$KEYDIR_FULL" run_deploy "$SANDBOX" pi-test foo 2>&1)
|
|
RC=$?
|
|
set -e
|
|
if [ "$RC" -eq 0 ]; then pass "Exit-Code 0"; else fail "Exit-Code $RC, Ausgabe: $OUT"; fi
|
|
GZ_SIZE=$(wc -c <"$SANDBOX/build/foo/ninlight_foo_gz.img" | tr -d ' ')
|
|
PROD_IMG="$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_prod.img"
|
|
DEV_IMG="$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_dev.img"
|
|
if [ -f "$PROD_IMG" ] && [ -f "$DEV_IMG" ]; then
|
|
pass "beide Migrationsimages (prod, dev) wurden angelegt"
|
|
else
|
|
fail "mindestens ein Migrationsimage fehlt: $OUT"
|
|
fi
|
|
for img in "$PROD_IMG" "$DEV_IMG"; do
|
|
[ -f "$img" ] || continue
|
|
size=$(wc -c <"$img" | tr -d ' ')
|
|
expected=$((GZ_SIZE + 512))
|
|
if [ "$size" -eq "$expected" ]; then
|
|
pass "$(basename "$img") ist genau 512 Byte groesser als das _gz.img ($size Byte)"
|
|
else
|
|
fail "$(basename "$img") hat $size Byte, erwartet $expected"
|
|
fi
|
|
done
|
|
|
|
echo
|
|
echo "=== Szenario 11: manifest.json listet jedes ausgelieferte Migrationsimage (Fall 2) ==="
|
|
SANDBOX="$TMPROOT/s11"; STUBBIN="$TMPROOT/bin11"; LOG="$TMPROOT/s11.log"; SANDBOX_STATE="$TMPROOT/s11-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo"
|
|
make_gz_file "$SANDBOX/build/foo/ninlight_foo_gz.img"
|
|
echo "foo-ota" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(TEST_SIGN_DIR="$KEYDIR_FULL" run_deploy "$SANDBOX" pi-test foo 2>&1)
|
|
RC=$?
|
|
set -e
|
|
MANIFEST="$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/manifest.json"
|
|
if [ "$RC" -eq 0 ] && [ -f "$MANIFEST" ]; then
|
|
pass "manifest.json wurde angelegt"
|
|
else
|
|
fail "manifest.json fehlt oder Exit-Code $RC: $OUT"
|
|
fi
|
|
set +e
|
|
# Form haelt sich an die Spec (docs/superpowers/spec Abschnitt 4) und an das, was
|
|
# server.py tatsaechlich liest. Seit Fixrunde 2 (N2) traegt "ninlight_version" PRO
|
|
# EINTRAG die Version - hier stimmen beide Eintraege ueberein, deshalb steht das
|
|
# manifestweite Feld zusaetzlich als Bequemlichkeit oben. Ein reines json.load haette
|
|
# eine falsche Verschachtelung nicht gemerkt (Fixrunde 1, C1); die naechste Pruefung
|
|
# (Szenario 20) schickt dieselbe Datei zusaetzlich durch den echten Server-Leser.
|
|
CHECK=$(python3 -c "
|
|
import json
|
|
with open('$MANIFEST') as fh:
|
|
data = json.load(fh)
|
|
entries = data.get('images', [])
|
|
by_variant = {e.get('variant'): e for e in entries if e.get('target') == 'foo'}
|
|
ok = (
|
|
isinstance(data.get('built'), str) and len(data['built']) > 0
|
|
and data.get('ninlight_version') == '1.0.0'
|
|
and len(entries) == 2
|
|
and set(by_variant) == {'prod', 'dev'}
|
|
and all(e.get('file') in ('foo_prod.img', 'foo_dev.img') for e in entries)
|
|
and all(isinstance(e.get('bytes'), int) and e['bytes'] > 0 for e in entries)
|
|
and all(isinstance(e.get('sha256'), str) and len(e['sha256']) == 64 for e in entries)
|
|
and all(e.get('ninlight_version') == '1.0.0' for e in entries)
|
|
)
|
|
print('OK' if ok else 'FAIL: ' + json.dumps(data))
|
|
" 2>&1)
|
|
set -e
|
|
if [ "$CHECK" = "OK" ]; then
|
|
pass "manifest.json traegt built/ninlight_version oben (Bequemlichkeit) und target/variant/file/bytes/sha256/ninlight_version je Eintrag"
|
|
else
|
|
fail "manifest.json-Inhalt unerwartet: $CHECK"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 12: Ohne NINLIGHT_SIGN_DIR entfallen die Migrationsimages, das Rettungsimage kommt trotzdem an (Fall 3) ==="
|
|
SANDBOX="$TMPROOT/s12"; STUBBIN="$TMPROOT/bin12"; LOG="$TMPROOT/s12.log"; SANDBOX_STATE="$TMPROOT/s12-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo"
|
|
make_gz_file "$SANDBOX/build/foo/ninlight_foo_gz.img"
|
|
echo "foo-ota" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_deploy "$SANDBOX" pi-test foo 2>&1)
|
|
RC=$?
|
|
set -e
|
|
if [ "$RC" -eq 0 ]; then pass "Exit-Code 0"; else fail "Exit-Code $RC, Ausgabe: $OUT"; fi
|
|
if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_foo_ota.img" ]; then
|
|
pass "Rettungsimage (_ota.img) wurde trotzdem ausgeliefert"
|
|
else
|
|
fail "Rettungsimage fehlt: $OUT"
|
|
fi
|
|
if [ ! -d "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan" ] \
|
|
|| [ -z "$(ls -A "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan" 2>/dev/null)" ]; then
|
|
pass "keine Migrationsimages entstanden"
|
|
else
|
|
fail "Migrationsimages entstanden, obwohl kein Schluesselverzeichnis vorhanden war"
|
|
fi
|
|
if echo "$OUT" | grep -q "Schluesselverzeichnis"; then
|
|
pass "Meldung ueber fehlendes Schluesselverzeichnis vorhanden"
|
|
else
|
|
fail "keine Meldung ueber das fehlende Schluesselverzeichnis: $OUT"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 13: Schluesselverzeichnis ohne Produktionsschluessel - nur dev-Images entstehen (Fall 4) ==="
|
|
SANDBOX="$TMPROOT/s13"; STUBBIN="$TMPROOT/bin13"; LOG="$TMPROOT/s13.log"; SANDBOX_STATE="$TMPROOT/s13-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo"
|
|
make_gz_file "$SANDBOX/build/foo/ninlight_foo_gz.img"
|
|
echo "foo-ota" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(TEST_SIGN_DIR="$KEYDIR_DEV_ONLY" run_deploy "$SANDBOX" pi-test foo 2>&1)
|
|
RC=$?
|
|
set -e
|
|
if [ "$RC" -eq 0 ]; then pass "Exit-Code 0"; else fail "Exit-Code $RC, Ausgabe: $OUT"; fi
|
|
if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_dev.img" ]; then
|
|
pass "dev-Migrationsimage wurde angelegt"
|
|
else
|
|
fail "dev-Migrationsimage fehlt: $OUT"
|
|
fi
|
|
if [ ! -f "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_prod.img" ]; then
|
|
pass "prod-Migrationsimage entfiel wie erwartet"
|
|
else
|
|
fail "prod-Migrationsimage wurde trotz fehlendem Produktionsschluessel angelegt"
|
|
fi
|
|
if echo "$OUT" | grep -q "Produktionsschluessel fehlt"; then
|
|
pass "Meldung ueber fehlenden Produktionsschluessel vorhanden"
|
|
else
|
|
fail "keine Meldung ueber den fehlenden Produktionsschluessel: $OUT"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 14: gezielter Deploy erzeugt nur die Migrationsimages des angegebenen Ziels (Fall 5, 4a-Fehler fuer migrate/) ==="
|
|
SANDBOX="$TMPROOT/s14"; STUBBIN="$TMPROOT/bin14"; LOG="$TMPROOT/s14.log"; SANDBOX_STATE="$TMPROOT/s14-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo" "$SANDBOX/build/bar"
|
|
make_gz_file "$SANDBOX/build/foo/ninlight_foo_gz.img" A
|
|
make_gz_file "$SANDBOX/build/bar/ninlight_bar_gz.img" C
|
|
echo "foo-ota" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
echo "bar-ota" >"$SANDBOX/build/bar/ninlight_bar_ota.img"
|
|
mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan"
|
|
echo "vorhandenes-bar-prod" >"$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/bar_prod.img"
|
|
echo "vorhandenes-bar-dev" >"$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/bar_dev.img"
|
|
# Manifest eines frueheren Deploys, das bar beschrieben hat - genau das darf ein
|
|
# gezielter Deploy von foo nicht einfach wegschreiben (Fixrunde 1, I1). bar traegt
|
|
# absichtlich eine ANDERE Firmware-Version als das, was dieser Lauf fuer foo signiert
|
|
# (nl_mkimage-Stub liefert immer "1.0.0") - das Manifest nach dem Merge muss beide
|
|
# Versionen unveraendert nebeneinander stehen lassen (Fixrunde 2, N2).
|
|
cat >"$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/manifest.json" <<'JSON'
|
|
{"built": "2026-01-01T00:00:00Z", "ninlight_version": "0.9.0",
|
|
"images": [
|
|
{"target": "bar", "variant": "prod", "file": "bar_prod.img", "bytes": 20, "sha256": "x",
|
|
"ninlight_version": "0.9.0"},
|
|
{"target": "bar", "variant": "dev", "file": "bar_dev.img", "bytes": 19, "sha256": "y",
|
|
"ninlight_version": "0.9.0"}
|
|
]}
|
|
JSON
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(TEST_SIGN_DIR="$KEYDIR_FULL" run_deploy "$SANDBOX" pi-test foo 2>&1)
|
|
RC=$?
|
|
set -e
|
|
if [ "$RC" -eq 0 ]; then pass "Exit-Code 0"; else fail "Exit-Code $RC, Ausgabe: $OUT"; fi
|
|
if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_prod.img" ] \
|
|
&& [ -f "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_dev.img" ]; then
|
|
pass "Migrationsimages fuer das angegebene Ziel (foo) wurden angelegt"
|
|
else
|
|
fail "Migrationsimages fuer foo fehlen: $OUT"
|
|
fi
|
|
if [ "$(cat "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/bar_prod.img" 2>/dev/null)" = "vorhandenes-bar-prod" ] \
|
|
&& [ "$(cat "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/bar_dev.img" 2>/dev/null)" = "vorhandenes-bar-dev" ]; then
|
|
pass "Migrationsimages des NICHT ausgewaehlten Geraetetyps (bar) blieben unangetastet"
|
|
else
|
|
fail "Migrationsimages von bar wurden veraendert oder geloescht - 4a-Fehler fuer migrate/"
|
|
fi
|
|
set +e
|
|
MANIFEST_CHECK=$(python3 -c "
|
|
import json
|
|
with open('$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/manifest.json') as fh:
|
|
data = json.load(fh)
|
|
entries = data.get('images', [])
|
|
keys = {(e.get('target'), e.get('variant')) for e in entries}
|
|
expected = {('foo', 'prod'), ('foo', 'dev'), ('bar', 'prod'), ('bar', 'dev')}
|
|
by_key = {(e.get('target'), e.get('variant')): e for e in entries}
|
|
ok = (
|
|
keys == expected
|
|
# foo wurde in DIESEM Lauf signiert -> Stub-Version 1.0.0 (Fixrunde 2, N2: Version
|
|
# steht pro Eintrag).
|
|
and by_key[('foo', 'prod')].get('ninlight_version') == '1.0.0'
|
|
and by_key[('foo', 'dev')].get('ninlight_version') == '1.0.0'
|
|
# bar wurde NICHT neu signiert -> seine alte Version (0.9.0) bleibt unangetastet,
|
|
# auch wenn sie von foos Version abweicht.
|
|
and by_key[('bar', 'prod')].get('ninlight_version') == '0.9.0'
|
|
and by_key[('bar', 'dev')].get('ninlight_version') == '0.9.0'
|
|
# Da foo (1.0.0) und bar (0.9.0) nach dem Merge NICHT uebereinstimmen, darf das
|
|
# manifestweite Feld nicht mehr die Version dieses Laufs behaupten - es muss ganz
|
|
# fehlen (Fixrunde 2, N2: das war der eigentliche Fehler - vorher stand hier
|
|
# weiterhin 1.0.0, obwohl bars Bilder unveraendert auf 0.9.0 blieben).
|
|
and 'ninlight_version' not in data
|
|
)
|
|
print('OK' if ok else 'FAIL: ' + json.dumps(data))
|
|
" 2>&1)
|
|
set -e
|
|
if [ "$MANIFEST_CHECK" = "OK" ]; then
|
|
pass "manifest.json beschreibt nach dem gezielten Deploy weiterhin BEIDE Geraetetypen mit je eigener Version, ohne falsches manifestweites Feld"
|
|
else
|
|
fail "manifest.json-Inhalt nach dem gezielten Deploy unerwartet: $MANIFEST_CHECK"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 15: Kein privater Schluessel und kein Schluesselpfad-Inhalt in der Ausgabe (Fall 6) ==="
|
|
SANDBOX="$TMPROOT/s15"; STUBBIN="$TMPROOT/bin15"; LOG="$TMPROOT/s15.log"; SANDBOX_STATE="$TMPROOT/s15-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo"
|
|
make_gz_file "$SANDBOX/build/foo/ninlight_foo_gz.img"
|
|
echo "foo-ota" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(TEST_SIGN_DIR="$KEYDIR_FULL" run_deploy "$SANDBOX" pi-test foo 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 -- "-----BEGIN"; then
|
|
fail "PEM-Header (Schluesselinhalt) taucht in der Ausgabe auf"
|
|
else
|
|
pass "kein PEM-Header in der Ausgabe"
|
|
fi
|
|
# ALLE Zeilen BEIDER privater Schluessel pruefen (nicht nur eine einzelne Zeile des
|
|
# Produktionsschluessels) und dabei auch das ssh/rsync/sudo-Log durchsuchen, nicht nur
|
|
# die Kommandozeilenausgabe von deploy.sh selbst (Fixrunde 1: die vorherige Version
|
|
# haette einen Leak ueber eine andere Zeile oder ueber $LOG glatt uebersehen).
|
|
LEAK=""
|
|
while IFS= read -r line; do
|
|
[ -n "$line" ] || continue
|
|
if echo "$OUT" | grep -qF -- "$line"; then
|
|
LEAK="Ausgabe enthaelt: $line"
|
|
break
|
|
fi
|
|
if grep -qF -- "$line" "$LOG"; then
|
|
LEAK="Log enthaelt: $line"
|
|
break
|
|
fi
|
|
done < <(cat "$KEYDIR_FULL/QCX_Device_CA/qcx_firmware_signing_key.pem" "$KEYDIR_FULL/fw_sign.pem")
|
|
if [ -z "$LEAK" ]; then
|
|
pass "keine Zeile eines privaten Schluessels taucht in Ausgabe oder Log auf"
|
|
else
|
|
fail "Schluesselinhalt gefunden - $LEAK"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 16: Signiert wird das _gz.img, niemals das _ota.img (Fall 7, gefaehrlichster Fehler) ==="
|
|
SANDBOX="$TMPROOT/s16"; STUBBIN="$TMPROOT/bin16"; LOG="$TMPROOT/s16.log"; SANDBOX_STATE="$TMPROOT/s16-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo"
|
|
make_gz_file "$SANDBOX/build/foo/ninlight_foo_gz.img" A
|
|
make_ota_file "$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(TEST_SIGN_DIR="$KEYDIR_FULL" run_deploy "$SANDBOX" pi-test foo 2>&1)
|
|
RC=$?
|
|
set -e
|
|
if [ "$RC" -eq 0 ]; then pass "Exit-Code 0"; else fail "Exit-Code $RC, Ausgabe: $OUT"; fi
|
|
DEV_IMG="$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_dev.img"
|
|
if [ -f "$DEV_IMG" ]; then
|
|
set +e
|
|
python3 -c "
|
|
import sys
|
|
with open(sys.argv[1], 'rb') as fh:
|
|
signed = fh.read()
|
|
with open(sys.argv[2], 'rb') as fh:
|
|
gz = fh.read()
|
|
sys.exit(0 if signed[:-512] == gz else 1)
|
|
" "$DEV_IMG" "$SANDBOX/build/foo/ninlight_foo_gz.img"
|
|
PAYLOAD_MATCH=$?
|
|
set -e
|
|
if [ "$PAYLOAD_MATCH" -eq 0 ]; then
|
|
pass "die signierte Nutzlast ist byteidentisch mit dem _gz.img"
|
|
else
|
|
fail "die signierte Nutzlast entspricht NICHT dem _gz.img - das _ota.img wurde signiert!"
|
|
fi
|
|
else
|
|
fail "dev-Migrationsimage wurde nicht angelegt: $OUT"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 17: Ohne Schluesselverzeichnis bleibt ein vorhandenes migrate/ beim Voll-Deploy unangetastet ==="
|
|
SANDBOX="$TMPROOT/s17"; STUBBIN="$TMPROOT/bin17"; LOG="$TMPROOT/s17.log"; SANDBOX_STATE="$TMPROOT/s17-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo"
|
|
echo "foo-ota" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan"
|
|
echo "vorhandenes-foo-prod" >"$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_prod.img"
|
|
echo "vorhandenes-foo-dev" >"$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_dev.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 [ "$(cat "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_prod.img" 2>/dev/null)" = "vorhandenes-foo-prod" ] \
|
|
&& [ "$(cat "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_dev.img" 2>/dev/null)" = "vorhandenes-foo-dev" ]; then
|
|
pass "vorhandenes migrate/ blieb beim Voll-Deploy ohne Schluesselverzeichnis unangetastet"
|
|
else
|
|
fail "migrate/ wurde geleert, obwohl kein Schluesselverzeichnis vorhanden war - zerstoert die Bestueckung"
|
|
fi
|
|
if echo "$OUT" | grep -q "migrate-wlan/ auf dem Pi bleibt unangetastet"; then
|
|
pass "Ausgabe kuendigt an, dass migrate/ unangetastet bleibt"
|
|
else
|
|
fail "Ausgabe erwaehnt nicht, dass migrate/ unangetastet bleibt: $OUT"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 18 (Fixrunde 1, C2): Schluesselverzeichnis vorhanden, aber leer - migrate/ bleibt unangetastet, Rettungsimage kommt trotzdem ==="
|
|
# Der eigentliche C2-Repro: anders als Szenario 12 (Verzeichnis fehlt komplett) EXISTIERT
|
|
# $SIGN_DIR hier, enthaelt aber keinen der beiden Schluessel. Vorher fuehrte das trotzdem
|
|
# zu einem (leeren) $STAGE/migrate mit Manifest, das dann per --delete ein vorhandenes
|
|
# migrate/ auf dem Pi geleert hat.
|
|
SIGN_EMPTY="$TMPROOT/keys-empty"
|
|
mkdir -p "$SIGN_EMPTY"
|
|
SANDBOX="$TMPROOT/s18"; STUBBIN="$TMPROOT/bin18"; LOG="$TMPROOT/s18.log"; SANDBOX_STATE="$TMPROOT/s18-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo"
|
|
echo "foo-ota" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan"
|
|
echo "vorhandenes-foo-prod" >"$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_prod.img"
|
|
echo "vorhandenes-foo-dev" >"$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_dev.img"
|
|
echo '{"built": "2026-01-01T00:00:00Z", "ninlight_version": "0.0.1", "images": []}' \
|
|
>"$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/manifest.json"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(TEST_SIGN_DIR="$SIGN_EMPTY" 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 [ -f "$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_foo_ota.img" ]; then
|
|
pass "Rettungsimage (_ota.img) wurde trotzdem ausgeliefert"
|
|
else
|
|
fail "Rettungsimage fehlt: $OUT"
|
|
fi
|
|
if [ "$(cat "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_prod.img" 2>/dev/null)" = "vorhandenes-foo-prod" ] \
|
|
&& [ "$(cat "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_dev.img" 2>/dev/null)" = "vorhandenes-foo-dev" ] \
|
|
&& grep -q '"images": \[\]' "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/manifest.json"; then
|
|
pass "vorhandenes migrate/ (Bilder UND manifest.json) blieb unangetastet - Verzeichnis war da, aber leer"
|
|
else
|
|
fail "migrate/ wurde trotz vorhandenem, aber leerem Schluesselverzeichnis veraendert/geleert - C2 nicht behoben"
|
|
fi
|
|
if echo "$OUT" | grep -q "enthaelt weder Produktions- noch Entwicklungsschluessel"; then
|
|
pass "Meldung nennt das leere Schluesselverzeichnis"
|
|
else
|
|
fail "keine Meldung ueber das leere Schluesselverzeichnis: $OUT"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 19 (Fixrunde 1, C2): Voll-Deploy mit nur dev-Schluessel raeumt keine vorhandenen _prod.img weg ==="
|
|
SANDBOX="$TMPROOT/s19"; STUBBIN="$TMPROOT/bin19"; LOG="$TMPROOT/s19.log"; SANDBOX_STATE="$TMPROOT/s19-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo"
|
|
make_gz_file "$SANDBOX/build/foo/ninlight_foo_gz.img"
|
|
echo "foo-ota" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan"
|
|
echo "echtes-altes-prod-image" >"$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_prod.img"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(TEST_SIGN_DIR="$KEYDIR_DEV_ONLY" run_deploy "$SANDBOX" pi-test 2>&1) # Voll-Deploy, kein Ziel-Argument
|
|
RC=$?
|
|
set -e
|
|
if [ "$RC" -eq 0 ]; then pass "Exit-Code 0"; else fail "Exit-Code $RC, Ausgabe: $OUT"; fi
|
|
if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_dev.img" ]; then
|
|
pass "neues dev-Migrationsimage wurde signiert und ausgeliefert"
|
|
else
|
|
fail "dev-Migrationsimage fehlt: $OUT"
|
|
fi
|
|
if [ "$(cat "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_prod.img" 2>/dev/null)" = "echtes-altes-prod-image" ]; then
|
|
pass "das vorhandene _prod.img blieb unangetastet, obwohl dieser Voll-Deploy nur den dev-Schluessel hatte"
|
|
else
|
|
fail "das vorhandene _prod.img wurde beim Voll-Deploy mit nur dev-Schluessel entfernt - C2 (Teilfall) nicht behoben"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 20 (Fixrunde 1, C1): manifest.json wird vom ECHTEN Server-Leser korrekt interpretiert ==="
|
|
# json.load allein (wie in Szenario 11) haette die falsche Verschachtelung von
|
|
# ninlight_version nicht gemerkt, weil es nur die Feldnamen innerhalb der Eintraege
|
|
# prueft. Hier laeuft dieselbe Datei stattdessen durch server.py's eigene
|
|
# Station.image_version(target, variant) - genau die Funktion, die im echten Betrieb
|
|
# entscheidet, welche Version protokolliert und angezeigt wird (seit Fixrunde 2, N2:
|
|
# pro ausgeliefertem Eintrag, nicht mehr nur manifestweit - deshalb jetzt mit
|
|
# target/variant statt ohne Argumente).
|
|
SANDBOX="$TMPROOT/s20"; STUBBIN="$TMPROOT/bin20"; LOG="$TMPROOT/s20.log"; SANDBOX_STATE="$TMPROOT/s20-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo"
|
|
make_gz_file "$SANDBOX/build/foo/ninlight_foo_gz.img"
|
|
echo "foo-ota" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(TEST_SIGN_DIR="$KEYDIR_FULL" run_deploy "$SANDBOX" pi-test foo 2>&1)
|
|
RC=$?
|
|
set -e
|
|
MANIFEST="$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/manifest.json"
|
|
if [ "$RC" -eq 0 ] && [ -f "$MANIFEST" ]; then
|
|
pass "manifest.json wurde angelegt"
|
|
else
|
|
fail "manifest.json fehlt oder Exit-Code $RC: $OUT"
|
|
fi
|
|
printf 'mit-wlan\n' >"$TMPROOT/s20-imageset"
|
|
set +e
|
|
VERSION=$(python3 -c "
|
|
import importlib.util
|
|
from pathlib import Path
|
|
|
|
station_dir = Path('$STATION_DIR')
|
|
spec = importlib.util.spec_from_file_location('ninlight_station_server_c1', station_dir / 'server.py')
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
|
|
st = module.Station(
|
|
image_dir=Path('$MANIFEST').resolve().parent.parent,
|
|
state_path=Path('$TMPROOT/s20-status.json'),
|
|
stall_after=None,
|
|
mode='migration',
|
|
control_path=Path('$TMPROOT/s20-control.json'),
|
|
# Das Manifest dieses Szenarios gehoert zum Satz MIT WLAN-Zugangsdaten - der
|
|
# Umschalter muss also darauf stehen, sonst liest der Server im anderen Satz nach.
|
|
imageset_path=Path('$TMPROOT/s20-imageset'),
|
|
)
|
|
print(st.image_version('foo', 'dev'))
|
|
" 2>&1)
|
|
RC2=$?
|
|
set -e
|
|
if [ "$RC2" -eq 0 ] && [ "$VERSION" = "1.0.0" ]; then
|
|
pass "Station.image_version() (der echte Server-Code) liest die Version korrekt, nicht 'unbekannt'"
|
|
else
|
|
fail "Station.image_version() liefert '$VERSION' statt '1.0.0' (RC=$RC2) - Manifest-Form stimmt nicht mit dem Server ueberein"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 21 (Fixrunde 2, N1): kaputtes/falsch geformtes Bestandsmanifest darf den Deploy nicht mitten drin abbrechen ==="
|
|
# Der Merge steht in deploy.sh VOR der Installation von server.py/products.py/CLI/Unit
|
|
# und vor dem Neustart - ein Abbruch hier liesse den Pi mit neuen Images, aber altem
|
|
# Stationscode zurueck. "truncated" und "empty" liefen schon vorher durch (json.load
|
|
# wirft ValueError, das war immer abgefangen); "array"/"null"/"string"/"null-entry"
|
|
# sind gueltiges JSON mit falscher FORM und haben vorher mit AttributeError abgebrochen
|
|
# ("... has no attribute 'get'"), weil nur auf Lese-, nicht auf Parse-Fehler geprueft
|
|
# wurde. Alle vier Zeilen aus der Review-Tabelle.
|
|
mkdir -p "$TMPROOT/bad-manifests"
|
|
printf '%s' '{ kaputt' >"$TMPROOT/bad-manifests/truncated.json"
|
|
: >"$TMPROOT/bad-manifests/empty.json"
|
|
printf '%s' '[]' >"$TMPROOT/bad-manifests/array.json"
|
|
printf '%s' 'null' >"$TMPROOT/bad-manifests/null.json"
|
|
printf '%s' '"text"' >"$TMPROOT/bad-manifests/string.json"
|
|
printf '%s' '{"images": [null]}' >"$TMPROOT/bad-manifests/null-entry.json"
|
|
|
|
for label in truncated empty array null string null-entry; do
|
|
SANDBOX="$TMPROOT/s21-$label"; STUBBIN="$TMPROOT/bin21-$label"
|
|
LOG="$TMPROOT/s21-$label.log"; SANDBOX_STATE="$TMPROOT/s21-$label-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo"
|
|
make_gz_file "$SANDBOX/build/foo/ninlight_foo_gz.img"
|
|
echo "foo-ota" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan"
|
|
cp "$TMPROOT/bad-manifests/$label.json" "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/manifest.json"
|
|
: >"$LOG"
|
|
set +e
|
|
# Nur EIN Schluessel (dev) -> MIGRATE_COMPLETE=0 -> der Merge-Zweig laeuft, nicht der
|
|
# --delete-Zweig, der das kaputte Bestandsmanifest ohnehin ueberschreiben wuerde.
|
|
OUT=$(TEST_SIGN_DIR="$KEYDIR_DEV_ONLY" run_deploy "$SANDBOX" pi-test foo 2>&1)
|
|
RC=$?
|
|
set -e
|
|
if [ "$RC" -eq 0 ]; then
|
|
pass "Bestandsmanifest '$label': Exit-Code 0, Deploy bricht nicht mitten drin ab"
|
|
else
|
|
fail "Bestandsmanifest '$label': Exit-Code $RC - Deploy abgebrochen. Ausgabe: $OUT"
|
|
fi
|
|
if [ -f "$SANDBOX_STATE/opt/ninlight-station/server.py" ] \
|
|
&& [ -f "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_dev.img" ]; then
|
|
pass "Bestandsmanifest '$label': Deploy lief vollstaendig durch (server.py installiert, foo_dev.img da)"
|
|
else
|
|
fail "Bestandsmanifest '$label': Deploy blieb auf halbem Weg stehen (server.py oder foo_dev.img fehlt)"
|
|
fi
|
|
done
|
|
|
|
echo
|
|
echo "=== Szenario 22 (Task 6b, Punkt 1): deploy.sh liefert Anzeigeverzeichnis und Anzeige-Unit mit aus ==="
|
|
SANDBOX="$TMPROOT/s22"; STUBBIN="$TMPROOT/bin22"; LOG="$TMPROOT/s22.log"; SANDBOX_STATE="$TMPROOT/s22-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo"
|
|
echo "foo-image" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
# Bestand auf dem "Pi": eine Datei, die es im Repo nicht mehr gibt. display/ ist reiner
|
|
# Code aus dem Repo - was dort verschwindet, muss auch hier verschwinden, sonst laedt
|
|
# ein zurueckgebliebenes altes Modul weiter mit.
|
|
mkdir -p "$SANDBOX_STATE/opt/ninlight-station/display"
|
|
echo "alt" >"$SANDBOX_STATE/opt/ninlight-station/display/veraltet.py"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_deploy "$SANDBOX" pi-test 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
DISP="$SANDBOX_STATE/opt/ninlight-station/display"
|
|
for F in display.py render.py epdconfig.py epd2in13_V2.py epd2in13_V3.py HERKUNFT.md; do
|
|
[ -f "$DISP/$F" ] && pass "display/$F ausgeliefert" || fail "display/$F fehlt unter $DISP"
|
|
done
|
|
[ -f "$SANDBOX_STATE/etc/systemd/system/ninlight-display.service" ] && pass "ninlight-display.service ausgeliefert" || fail "Anzeige-Unit fehlt - sie waere weiter toter Text im Repo"
|
|
[ ! -f "$DISP/veraltet.py" ] && pass "im Repo geloeschte Datei verschwindet auch auf dem Pi (--delete)" || fail "veraltet.py blieb im Zielverzeichnis liegen"
|
|
[ ! -d "$DISP/__pycache__" ] && pass "kein __pycache__ dieses Rechners auf dem Pi gelandet" || fail "__pycache__ wurde mit uebertragen: $(ls "$DISP/__pycache__")"
|
|
# Die Anzeige lief nicht (Stub-Default) - dann wird sie auch nicht neu gestartet, aber
|
|
# der Deploy sagt es.
|
|
echo "$OUT" | grep -q "Anzeigedienst laeuft nicht" && pass "Deploy meldet, dass der Anzeigedienst nicht laeuft" || fail "Ausgabe erwaehnt den Anzeigedienst nicht: $OUT"
|
|
# Die fluechtige Ablage wird wieder aufgeraeumt (sie liegt, wie bei server.py & Co.,
|
|
# im echten /tmp - deploy.sh's REMOTE-Block kennt dafuer keine Sandbox-Variable).
|
|
[ ! -d /tmp/ninlight-display ] && pass "die fluechtige Ablage /tmp/ninlight-display wurde aufgeraeumt" || fail "/tmp/ninlight-display blieb liegen"
|
|
|
|
echo
|
|
echo "=== Szenario 23: laeuft die Anzeige, wird sie nach dem Deploy neu gestartet ==="
|
|
# Ohne Neustart liefe sie mit dem gerade ersetzten Code weiter.
|
|
SANDBOX="$TMPROOT/s23"; STUBBIN="$TMPROOT/bin23"; LOG="$TMPROOT/s23.log"; SANDBOX_STATE="$TMPROOT/s23-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=$(FAKE_DISPLAY_ACTIVE_EXIT=0 run_deploy "$SANDBOX" pi-test 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
grep -Fq "systemctl restart ninlight-display" "$LOG" && pass "Anzeigedienst wurde neu gestartet" || fail "kein 'systemctl restart ninlight-display' im Log: $(cat "$LOG")"
|
|
echo "$OUT" | grep -q "Anzeigedienst neu gestartet" && pass "Ausgabe meldet den Neustart" || fail "Ausgabe meldet den Neustart nicht: $OUT"
|
|
|
|
echo
|
|
echo "=== Szenario 24: ein fehlgeschlagener Neustart der ANZEIGE ist kein Deploy-Fehler ==="
|
|
# Die Anzeige ist nirgends Voraussetzung fuer eine Migration. Ein Deploy, der wegen des
|
|
# Panels rot wird, verleitet nur dazu, einen echten Fehler zu uebersehen. Beim
|
|
# HAUPTdienst ist es umgekehrt - das prueft das Szenario darunter zur Abgrenzung.
|
|
SANDBOX="$TMPROOT/s24"; STUBBIN="$TMPROOT/bin24"; LOG="$TMPROOT/s24.log"; SANDBOX_STATE="$TMPROOT/s24-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=$(FAKE_DISPLAY_ACTIVE_EXIT=0 FAKE_DISPLAY_RESTART_EXIT=1 run_deploy "$SANDBOX" pi-test 2>"$TMPROOT/s24.err")
|
|
RC=$?
|
|
set -e
|
|
ERR=$(cat "$TMPROOT/s24.err")
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0 trotz fehlgeschlagenem Anzeige-Neustart" || fail "Exit-Code $RC - der Deploy wurde wegen der Anzeige rot: $ERR"
|
|
echo "$ERR" | grep -q "Warnung" && pass "Warnung (keine Fehlermeldung) auf stderr" || fail "keine Warnung auf stderr: $ERR"
|
|
[ -f "$SANDBOX_STATE/opt/ninlight-station/server.py" ] && pass "der Deploy selbst lief vollstaendig durch" || fail "server.py fehlt - der Deploy brach ab"
|
|
|
|
echo
|
|
echo "=== Szenario 25 (Abgrenzung): ein fehlgeschlagener Neustart des HAUPTdienstes ist sehr wohl ein Deploy-Fehler ==="
|
|
SANDBOX="$TMPROOT/s25"; STUBBIN="$TMPROOT/bin25"; LOG="$TMPROOT/s25.log"; SANDBOX_STATE="$TMPROOT/s25-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=$(FAKE_SYSTEMCTL_ACTIVE_EXIT=0 FAKE_SYSTEMCTL_RESTART_EXIT=1 run_deploy "$SANDBOX" pi-test 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -ne 0 ] && pass "Exit-Code != 0 beim Hauptdienst" || fail "Exit-Code 0 - ein toter Hauptdienst ginge unbemerkt durch: $OUT"
|
|
|
|
echo
|
|
echo "=== Szenario 26: deploy.sh liefert das Tasterverzeichnis und die Taster-Unit mit aus ==="
|
|
SANDBOX="$TMPROOT/s26"; STUBBIN="$TMPROOT/bin26"; LOG="$TMPROOT/s26.log"; SANDBOX_STATE="$TMPROOT/s26-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo"
|
|
echo "foo-image" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
# Bestand auf dem "Pi": eine Datei, die es im Repo nicht mehr gibt - muss beim Deploy
|
|
# verschwinden (dasselbe Argument wie bei display/ in Szenario 22).
|
|
mkdir -p "$SANDBOX_STATE/opt/ninlight-station/button"
|
|
echo "alt" >"$SANDBOX_STATE/opt/ninlight-station/button/veraltet.py"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_deploy "$SANDBOX" pi-test 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
BTN="$SANDBOX_STATE/opt/ninlight-station/button"
|
|
[ -f "$BTN/button.py" ] && pass "button/button.py ausgeliefert" || fail "button/button.py fehlt unter $BTN"
|
|
[ -f "$SANDBOX_STATE/etc/systemd/system/ninlight-button.service" ] && pass "ninlight-button.service ausgeliefert" || fail "Taster-Unit fehlt - sie waere weiter toter Text im Repo"
|
|
[ ! -f "$BTN/veraltet.py" ] && pass "im Repo geloeschte Datei verschwindet auch auf dem Pi (--delete)" || fail "veraltet.py blieb im Zielverzeichnis liegen"
|
|
[ ! -d "$BTN/__pycache__" ] && pass "kein __pycache__ dieses Rechners auf dem Pi gelandet" || fail "__pycache__ wurde mit uebertragen: $(ls "$BTN/__pycache__")"
|
|
echo "$OUT" | grep -q "Tasterdienst laeuft nicht" && pass "Deploy meldet, dass der Tasterdienst nicht laeuft" || fail "Ausgabe erwaehnt den Tasterdienst nicht: $OUT"
|
|
[ ! -d /tmp/ninlight-button ] && pass "die fluechtige Ablage /tmp/ninlight-button wurde aufgeraeumt" || fail "/tmp/ninlight-button blieb liegen"
|
|
|
|
echo
|
|
echo "=== Szenario 27: ein fehlgeschlagener Neustart des TASTERdienstes ist kein Deploy-Fehler ==="
|
|
SANDBOX="$TMPROOT/s27"; STUBBIN="$TMPROOT/bin27"; LOG="$TMPROOT/s27.log"; SANDBOX_STATE="$TMPROOT/s27-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=$(FAKE_BUTTON_ACTIVE_EXIT=0 FAKE_BUTTON_RESTART_EXIT=1 run_deploy "$SANDBOX" pi-test 2>"$TMPROOT/s27.err")
|
|
RC=$?
|
|
set -e
|
|
ERR=$(cat "$TMPROOT/s27.err")
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0 trotz fehlgeschlagenem Taster-Neustart" || fail "Exit-Code $RC - der Deploy wurde wegen des Tasters rot: $ERR"
|
|
echo "$ERR" | grep -q "Warnung" && pass "Warnung (keine Fehlermeldung) auf stderr" || fail "keine Warnung auf stderr: $ERR"
|
|
[ -f "$SANDBOX_STATE/opt/ninlight-station/server.py" ] && pass "der Deploy selbst lief vollstaendig durch" || fail "server.py fehlt - der Deploy brach ab"
|
|
|
|
echo
|
|
echo "=== Szenario 28: beide Bildsaetze werden ausgeliefert, und zwar aus verschiedenen Bauten ==="
|
|
# Der Umschalter auf der Station ist nur so gut wie die Bestueckung: liegen beide Saetze
|
|
# nicht getrennt auf dem Pi, kann er nichts umschalten. Geprueft wird am INHALT, nicht am
|
|
# Verzeichnisnamen - die Fuellzeichen der beiden _gz.img sind verschieden, ein
|
|
# vertauschter Quellbaum faellt also auf.
|
|
SANDBOX="$TMPROOT/s28"; STUBBIN="$TMPROOT/bin28"; LOG="$TMPROOT/s28.log"; SANDBOX_STATE="$TMPROOT/s28-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo" "$SANDBOX/build-nocreds/foo"
|
|
make_gz_file "$SANDBOX/build/foo/ninlight_foo_gz.img" W
|
|
echo "foo-ota" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
make_gz_file "$SANDBOX/build-nocreds/foo/ninlight_foo_gz.img" N
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(TEST_SIGN_DIR="$KEYDIR_FULL" run_deploy "$SANDBOX" pi-test foo 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
|
WLAN_IMG="$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_dev.img"
|
|
NOCR_IMG="$SANDBOX_STATE/opt/ninlight-station/images/migrate-nocreds/foo_dev.img"
|
|
if [ -f "$WLAN_IMG" ] && [ -f "$NOCR_IMG" ]; then
|
|
pass "beide Bildsaetze liegen getrennt auf dem Pi"
|
|
else
|
|
fail "mindestens ein Bildsatz fehlt: $OUT"
|
|
fi
|
|
if [ -f "$WLAN_IMG" ] && [ -f "$NOCR_IMG" ]; then
|
|
# Die ersten Bytes sind das Fuellzeichen des jeweiligen _gz.img.
|
|
W_HEAD=$(head -c 8 "$WLAN_IMG")
|
|
N_HEAD=$(head -c 8 "$NOCR_IMG")
|
|
[ "$W_HEAD" = "WWWWWWWW" ] && pass "migrate-wlan/ kommt wirklich aus build/" || fail "migrate-wlan/ enthaelt '$W_HEAD' statt der build/-Bytes"
|
|
[ "$N_HEAD" = "NNNNNNNN" ] && pass "migrate-nocreds/ kommt wirklich aus build-nocreds/" || fail "migrate-nocreds/ enthaelt '$N_HEAD' statt der build-nocreds/-Bytes"
|
|
fi
|
|
for m in migrate-wlan migrate-nocreds; do
|
|
if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/$m/manifest.json" ]; then
|
|
pass "$m/manifest.json wurde angelegt"
|
|
else
|
|
fail "$m/manifest.json fehlt"
|
|
fi
|
|
done
|
|
|
|
echo
|
|
echo "=== Szenario 29: ohne build-nocreds/ bleibt der Satz 'ohne-wlan' auf dem Pi unangetastet ==="
|
|
# tools/build_nocreds.sh ist ein eigener, optionaler Bau. Ein Deploy darf nicht daran
|
|
# scheitern, dass er nicht gelaufen ist - und er darf die Bestueckung frueherer Deploys
|
|
# nicht wegraeumen (derselbe Fehler wie frueher bei migrate/, siehe Szenario 17).
|
|
SANDBOX="$TMPROOT/s29"; STUBBIN="$TMPROOT/bin29"; LOG="$TMPROOT/s29.log"; SANDBOX_STATE="$TMPROOT/s29-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo"
|
|
make_gz_file "$SANDBOX/build/foo/ninlight_foo_gz.img"
|
|
echo "foo-ota" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images/migrate-nocreds"
|
|
echo "alter-nocreds-abzug" >"$SANDBOX_STATE/opt/ninlight-station/images/migrate-nocreds/foo_dev.img"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(TEST_SIGN_DIR="$KEYDIR_FULL" run_deploy "$SANDBOX" pi-test 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0 trotz fehlendem build-nocreds/" || fail "Exit-Code $RC: $OUT"
|
|
if [ "$(cat "$SANDBOX_STATE/opt/ninlight-station/images/migrate-nocreds/foo_dev.img" 2>/dev/null)" = "alter-nocreds-abzug" ]; then
|
|
pass "vorhandener Satz 'ohne-wlan' blieb unangetastet"
|
|
else
|
|
fail "der Satz 'ohne-wlan' wurde geleert oder ueberschrieben, obwohl build-nocreds/ fehlte"
|
|
fi
|
|
[ -f "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_dev.img" ] \
|
|
&& pass "der Satz 'mit-wlan' wurde trotzdem ausgeliefert" \
|
|
|| fail "der Satz 'mit-wlan' fehlt: $OUT"
|
|
echo "$OUT" | grep -q "build_nocreds.sh" \
|
|
&& pass "der Hinweis nennt, womit der fehlende Satz entsteht" \
|
|
|| fail "kein Hinweis auf tools/build_nocreds.sh: $OUT"
|
|
|
|
echo
|
|
echo "=== Szenario 30: ein unvollstaendiger Satz 'ohne-wlan' raeumt nicht mit --delete auf ==="
|
|
# build-nocreds/ hat nur eines von zwei Zielen - dieser Lauf beschreibt den Sollzustand
|
|
# also NICHT vollstaendig, und --delete wuerde das zweite Ziel des vorigen Deploys
|
|
# wegraeumen.
|
|
SANDBOX="$TMPROOT/s30"; STUBBIN="$TMPROOT/bin30"; LOG="$TMPROOT/s30.log"; SANDBOX_STATE="$TMPROOT/s30-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo" "$SANDBOX/build/bar" "$SANDBOX/build-nocreds/foo"
|
|
make_gz_file "$SANDBOX/build/foo/ninlight_foo_gz.img"
|
|
make_gz_file "$SANDBOX/build/bar/ninlight_bar_gz.img"
|
|
echo "foo-ota" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
echo "bar-ota" >"$SANDBOX/build/bar/ninlight_bar_ota.img"
|
|
make_gz_file "$SANDBOX/build-nocreds/foo/ninlight_foo_gz.img" N
|
|
mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images/migrate-nocreds"
|
|
echo "alter-bar-abzug" >"$SANDBOX_STATE/opt/ninlight-station/images/migrate-nocreds/bar_dev.img"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(TEST_SIGN_DIR="$KEYDIR_FULL" run_deploy "$SANDBOX" pi-test 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC: $OUT"
|
|
if [ "$(cat "$SANDBOX_STATE/opt/ninlight-station/images/migrate-nocreds/bar_dev.img" 2>/dev/null)" = "alter-bar-abzug" ]; then
|
|
pass "das nicht neu gebaute Ziel blieb im Satz 'ohne-wlan' erhalten"
|
|
else
|
|
fail "das nicht neu gebaute Ziel wurde aus dem Satz 'ohne-wlan' entfernt - --delete lief trotz unvollstaendigem Satz"
|
|
fi
|
|
[ -f "$SANDBOX_STATE/opt/ninlight-station/images/migrate-nocreds/foo_dev.img" ] \
|
|
&& pass "das gebaute Ziel kam dazu" || fail "das gebaute Ziel fehlt im Satz 'ohne-wlan'"
|
|
|
|
echo
|
|
echo "=== Szenario 31: das alte, namenlose migrate/ wird beim Deploy entfernt ==="
|
|
# Bis zum Umschalter hiess das einzige Verzeichnis "migrate" und enthielt die Abzuege MIT
|
|
# Zugangsdaten. Es wird von niemandem mehr gelesen - bliebe es liegen, haelt der naechste
|
|
# Mensch am Pi es fuer den gueltigen Satz.
|
|
SANDBOX="$TMPROOT/s31"; STUBBIN="$TMPROOT/bin31"; LOG="$TMPROOT/s31.log"; SANDBOX_STATE="$TMPROOT/s31-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo"
|
|
echo "foo-image" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images/migrate"
|
|
echo "altbestand" >"$SANDBOX_STATE/opt/ninlight-station/images/migrate/foo_dev.img"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_deploy "$SANDBOX" pi-test 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC: $OUT"
|
|
[ ! -e "$SANDBOX_STATE/opt/ninlight-station/images/migrate" ] \
|
|
&& pass "der Altbestand images/migrate wurde entfernt" \
|
|
|| fail "images/migrate liegt noch da"
|
|
|
|
echo
|
|
echo "=== Szenario 32: die beiden Abschlussbild-Units werden mit ausgeliefert ==="
|
|
# Ohne sie bliebe auf dem E-Paper nach dem Herunterfahren fuer immer das letzte Bild
|
|
# stehen - bei der Abnahme war das die Rueckfrage des Tastermenues.
|
|
SANDBOX="$TMPROOT/s32"; STUBBIN="$TMPROOT/bin32"; LOG="$TMPROOT/s32.log"; SANDBOX_STATE="$TMPROOT/s32-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
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC: $OUT"
|
|
for u in ninlight-poweroff ninlight-reboot; do
|
|
[ -f "$SANDBOX_STATE/etc/systemd/system/$u.service" ] \
|
|
&& pass "$u.service installiert" || fail "$u.service fehlt - sie waere toter Text im Repo"
|
|
done
|
|
# Ein Deploy darf keine Unit EINSCHALTEN, die vorher aus war - das macht setup_pi.sh.
|
|
grep -Fq "systemctl enable ninlight-poweroff" "$LOG" \
|
|
&& fail "der Deploy aktiviert die Abschlussbild-Unit" \
|
|
|| pass "der Deploy aktiviert sie nicht (das ist setup_pi.sh's Aufgabe)"
|
|
# Und die Ablage ist hinterher leer.
|
|
[ ! -e /tmp/ninlight-poweroff.service ] && [ ! -e /tmp/ninlight-reboot.service ] \
|
|
&& pass "die Ablage wurde aufgeraeumt" || fail "Unit-Dateien blieben unter /tmp liegen"
|
|
|
|
echo
|
|
echo "=== Szenario 33 (Abschlussreview, I3): Zugangsdaten im Bildsatz 'ohne-wlan' brechen den Deploy ab ==="
|
|
# Der Satz "ohne-wlan" ist die SICHERE VORGABE - im Automatikmodus geht er an jede alte
|
|
# Klyqa-Lampe in Funkreichweite, auch an fremde, und das Flashen ist nicht umkehrbar.
|
|
# release.sh prueft sein Archiv binaer gegen config.local.cmake; deploy.sh signierte
|
|
# bisher ungeprueft, was in build-nocreds/ lag. Ein von Hand konfiguriertes
|
|
# build-nocreds/<ziel> genuegt, damit das Panel "ohne WLAN" anzeigt, waehrend das
|
|
# WLAN-Passwort des Bedieners unwiderruflich in fremde Lampen wandert.
|
|
SANDBOX="$TMPROOT/s33"; STUBBIN="$TMPROOT/bin33"; LOG="$TMPROOT/s33.log"; SANDBOX_STATE="$TMPROOT/s33-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
write_local_cmake "$SANDBOX"
|
|
mkdir -p "$SANDBOX/build/foo" "$SANDBOX/build-nocreds/foo"
|
|
make_gz_file "$SANDBOX/build/foo/ninlight_foo_gz.img" W
|
|
echo "foo-ota" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
# Der undichte Abzug: enthaelt SSID, Passwort und Token.
|
|
make_gz_file_with_creds "$SANDBOX/build-nocreds/foo/ninlight_foo_gz.img" N
|
|
# GEGENPROBE (ohne sie waere dieses Szenario wertlos): der "Compiler" dieser Suite muss
|
|
# die Zugangsdaten wirklich einbauen. Genau diese Gegenprobe gibt es in test_release.sh
|
|
# fuer den Docker-Stub - dort hat sie belegt, dass Fall 5 nicht nur deshalb gruen ist,
|
|
# weil nie jemand etwas einbaut.
|
|
if tree_contains "$SANDBOX/build-nocreds" "$SECRET_PASS"; then
|
|
pass "Gegenprobe: der Abzug in build-nocreds/ enthaelt das WLAN-Passwort wirklich"
|
|
else
|
|
fail "Gegenprobe fehlgeschlagen - der Abzug enthaelt gar keine Zugangsdaten, Szenario 33 waere wertlos"
|
|
fi
|
|
# Der Pi hat schon einen (sauberen) Bestand aus einem frueheren Deploy - er darf durch
|
|
# den Abbruch weder ersetzt noch geloescht werden.
|
|
mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images/migrate-nocreds"
|
|
echo "alter-sauberer-abzug" >"$SANDBOX_STATE/opt/ninlight-station/images/migrate-nocreds/foo_dev.img"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(TEST_SIGN_DIR="$KEYDIR_FULL" run_deploy "$SANDBOX" pi-test foo 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -ne 0 ] && pass "Exit-Code != 0 ($RC)" || fail "Exit-Code war 0 - der undichte Bildsatz waere ausgeliefert worden: $OUT"
|
|
echo "$OUT" | grep -q "Zugangsdaten" \
|
|
&& pass "Fehlermeldung nennt die gefundenen Zugangsdaten" \
|
|
|| fail "keine Fehlermeldung zu Zugangsdaten: $OUT"
|
|
echo "$OUT" | grep -q "NINLIGHT_WIFI_PASSWORD" \
|
|
&& pass "Fehlermeldung nennt den betroffenen Schluessel" \
|
|
|| fail "Fehlermeldung nennt den Schluessel nicht: $OUT"
|
|
if ! grep -q "STUB ssh\|STUB rsync\|STUB scp" "$LOG"; then
|
|
pass "kein ssh/rsync/scp aufgerufen - nichts wurde uebertragen"
|
|
else
|
|
fail "es wurde trotz gefundener Zugangsdaten uebertragen: $(cat "$LOG")"
|
|
fi
|
|
[ "$(cat "$SANDBOX_STATE/opt/ninlight-station/images/migrate-nocreds/foo_dev.img")" = "alter-sauberer-abzug" ] \
|
|
&& pass "der Bestand auf dem Pi blieb unangetastet" \
|
|
|| fail "der Bestand auf dem Pi wurde veraendert"
|
|
if ! tree_contains "$SANDBOX_STATE" "$SECRET_PASS"; then
|
|
pass "kein Zugangsdatum hat es in die Sandbox-Zielablage geschafft"
|
|
else
|
|
fail "Zugangsdaten sind trotz Abbruch beim Ziel gelandet"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 34 (Abgrenzung zu 33): ein sauberer Satz 'ohne-wlan' geht raus, migrate-wlan DARF die Zugangsdaten tragen ==="
|
|
# Die Pruefung muss genau einen Satz betreffen. migrate-wlan/ enthaelt die Zugangsdaten
|
|
# absichtlich - das ist sein ganzer Zweck ("Lampe haengt sofort im eigenen WLAN"). Eine
|
|
# Pruefung, die auch ihn abweist, waere unbenutzbar und wuerde nach kurzer Zeit
|
|
# abgeschaltet.
|
|
SANDBOX="$TMPROOT/s34"; STUBBIN="$TMPROOT/bin34"; LOG="$TMPROOT/s34.log"; SANDBOX_STATE="$TMPROOT/s34-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
write_local_cmake "$SANDBOX"
|
|
mkdir -p "$SANDBOX/build/foo" "$SANDBOX/build-nocreds/foo"
|
|
# build/ MIT Zugangsdaten (wie im echten Betrieb), build-nocreds/ ohne.
|
|
make_gz_file_with_creds "$SANDBOX/build/foo/ninlight_foo_gz.img" W
|
|
echo "foo-ota" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
make_gz_file "$SANDBOX/build-nocreds/foo/ninlight_foo_gz.img" N
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(TEST_SIGN_DIR="$KEYDIR_FULL" run_deploy "$SANDBOX" pi-test foo 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC - die Pruefung weist einen sauberen Satz ab: $OUT"
|
|
[ -f "$SANDBOX_STATE/opt/ninlight-station/images/migrate-nocreds/foo_dev.img" ] \
|
|
&& pass "der saubere Satz 'ohne-wlan' kam an" || fail "der saubere Satz 'ohne-wlan' fehlt: $OUT"
|
|
[ -f "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan/foo_dev.img" ] \
|
|
&& pass "migrate-wlan kam trotz einkompilierter Zugangsdaten an" \
|
|
|| fail "migrate-wlan wurde faelschlich mit abgewiesen: $OUT"
|
|
if tree_contains "$SANDBOX_STATE/opt/ninlight-station/images/migrate-wlan" "$SECRET_PASS"; then
|
|
pass "Gegenprobe: migrate-wlan traegt die Zugangsdaten wirklich - die Pruefung haette anschlagen KOENNEN"
|
|
else
|
|
fail "Gegenprobe fehlgeschlagen - migrate-wlan traegt gar keine Zugangsdaten, die Abgrenzung waere wertlos"
|
|
fi
|
|
if ! tree_contains "$SANDBOX_STATE/opt/ninlight-station/images/migrate-nocreds" "$SECRET_PASS"; then
|
|
pass "und der Satz 'ohne-wlan' auf dem Pi ist frei davon"
|
|
else
|
|
fail "der Satz 'ohne-wlan' auf dem Pi enthaelt Zugangsdaten"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 35: der Bildsatz-Umschalter gilt jetzt auch fuer die Rettung - beide Rettungssaetze werden ausgeliefert ==="
|
|
# Analog zu Szenario 28 (Migration), aber fuer die Rettungsabbilder: rescue-wlan/ kommt
|
|
# aus build/ (dasselbe _ota.img, das ohnehin schon fuer die Trailer-Pruefung gebraucht
|
|
# wird), rescue-nocreds/ aus build-nocreds/ - ein eigener, unsignierter Fund.
|
|
SANDBOX="$TMPROOT/s35"; STUBBIN="$TMPROOT/bin35"; LOG="$TMPROOT/s35.log"; SANDBOX_STATE="$TMPROOT/s35-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo" "$SANDBOX/build-nocreds/foo"
|
|
make_ota_file "$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
python3 -c "
|
|
import sys
|
|
path = sys.argv[1]
|
|
body = b'N' * 2048
|
|
trailer = bytearray(b'\xff' * 512)
|
|
trailer[0:4] = b'NINL'
|
|
with open(path, 'wb') as fh:
|
|
fh.write(body + bytes(trailer))
|
|
" "$SANDBOX/build-nocreds/foo/ninlight_foo_ota.img"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_deploy "$SANDBOX" pi-test 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC: $OUT"
|
|
RESCUE_WLAN="$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_foo_ota.img"
|
|
RESCUE_NOCREDS="$SANDBOX_STATE/opt/ninlight-station/images/rescue-nocreds/ninlight_foo_ota.img"
|
|
if [ -f "$RESCUE_WLAN" ] && [ -f "$RESCUE_NOCREDS" ]; then
|
|
pass "beide Rettungssaetze liegen getrennt auf dem Pi"
|
|
else
|
|
fail "mindestens ein Rettungssatz fehlt: $OUT"
|
|
fi
|
|
if head -c 1 "$RESCUE_WLAN" | grep -q "B"; then
|
|
pass "rescue-wlan/ kommt wirklich aus build/"
|
|
else
|
|
fail "rescue-wlan/ hat nicht den erwarteten Inhalt aus build/"
|
|
fi
|
|
if head -c 1 "$RESCUE_NOCREDS" | grep -q "N"; then
|
|
pass "rescue-nocreds/ kommt wirklich aus build-nocreds/"
|
|
else
|
|
fail "rescue-nocreds/ hat nicht den erwarteten Inhalt aus build-nocreds/"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 36: ohne build-nocreds/ bleibt der Rettungssatz 'ohne-wlan' auf dem Pi unangetastet ==="
|
|
SANDBOX="$TMPROOT/s36"; STUBBIN="$TMPROOT/bin36"; LOG="$TMPROOT/s36.log"; SANDBOX_STATE="$TMPROOT/s36-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo"
|
|
make_ota_file "$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images/rescue-nocreds"
|
|
echo "alter-rescue-nocreds-abzug" >"$SANDBOX_STATE/opt/ninlight-station/images/rescue-nocreds/ninlight_foo_ota.img"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_deploy "$SANDBOX" pi-test 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0 trotz fehlendem build-nocreds/" || fail "Exit-Code $RC: $OUT"
|
|
if [ "$(cat "$SANDBOX_STATE/opt/ninlight-station/images/rescue-nocreds/ninlight_foo_ota.img" 2>/dev/null)" \
|
|
= "alter-rescue-nocreds-abzug" ]; then
|
|
pass "vorhandener Rettungssatz 'ohne-wlan' blieb unangetastet"
|
|
else
|
|
fail "der Rettungssatz 'ohne-wlan' wurde geleert oder ueberschrieben, obwohl build-nocreds/ fehlte"
|
|
fi
|
|
if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_foo_ota.img" ]; then
|
|
pass "der Rettungssatz 'mit-wlan' wurde trotzdem ausgeliefert"
|
|
else
|
|
fail "der Rettungssatz 'mit-wlan' fehlt: $OUT"
|
|
fi
|
|
if echo "$OUT" | grep -q "build-nocreds"; then
|
|
pass "der Hinweis nennt, womit der fehlende Rettungssatz entsteht"
|
|
else
|
|
fail "kein Hinweis auf build-nocreds/ in der Ausgabe: $OUT"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 37: gezielter Deploy ohne build-nocreds/<ziel> bricht nicht ab, sondern laesst 'ohne-wlan' fuer dieses Ziel unangetastet ==="
|
|
SANDBOX="$TMPROOT/s37"; STUBBIN="$TMPROOT/bin37"; LOG="$TMPROOT/s37.log"; SANDBOX_STATE="$TMPROOT/s37-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo"
|
|
make_ota_file "$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_deploy "$SANDBOX" pi-test foo 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0 trotz fehlendem build-nocreds/foo" || fail "Exit-Code $RC: $OUT"
|
|
if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_foo_ota.img" ]; then
|
|
pass "der Rettungssatz 'mit-wlan' fuer foo wurde trotzdem ausgeliefert"
|
|
else
|
|
fail "der Rettungssatz 'mit-wlan' fuer foo fehlt: $OUT"
|
|
fi
|
|
if [ ! -e "$SANDBOX_STATE/opt/ninlight-station/images/rescue-nocreds/ninlight_foo_ota.img" ]; then
|
|
pass "kein Rettungsimage 'ohne-wlan' fuer foo entstanden - build-nocreds/foo fehlte"
|
|
else
|
|
fail "es entstand ein Rettungsimage 'ohne-wlan' fuer foo, obwohl build-nocreds/foo fehlte"
|
|
fi
|
|
if echo "$OUT" | grep -q "bleibt im Rettungs-Bildsatz 'ohne-wlan' unveraendert"; then
|
|
pass "Hinweis auf das fehlende build-nocreds/foo vorhanden"
|
|
else
|
|
fail "kein Hinweis auf das fehlende build-nocreds/foo: $OUT"
|
|
fi
|
|
|
|
echo
|
|
echo "=== Szenario 38: Altbestand - vor diesem Umschalter flach in images/ liegende Rettungsabbilder werden auch bei einem gezielten Deploy aufgeraeumt ==="
|
|
# Anders als bei den Migrationsabbildern (die ein eigenes, benanntes Verzeichnis erst
|
|
# seit einem frueheren Umschalter haben) gab es fuer die Rettung nie eine Uebergangsphase
|
|
# mit --delete: der flache Bestand wird von server.py seit diesem Umschalter nicht mehr
|
|
# gelesen (siehe rescue_dir() dort) und ist deshalb toter Ballast, sobald IRGENDEIN
|
|
# Deploy mit dieser Fassung laeuft - auch ein gezielter, der images/ sonst nicht mit
|
|
# --delete abgleicht.
|
|
SANDBOX="$TMPROOT/s38"; STUBBIN="$TMPROOT/bin38"; LOG="$TMPROOT/s38.log"; SANDBOX_STATE="$TMPROOT/s38-state"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stubbin "$STUBBIN" "$LOG"
|
|
mkdir -p "$SANDBOX/build/foo"
|
|
make_ota_file "$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|
mkdir -p "$SANDBOX_STATE/opt/ninlight-station/images"
|
|
echo "alter-flacher-bestand" >"$SANDBOX_STATE/opt/ninlight-station/images/ninlight_foo_ota.img"
|
|
echo "alter-flacher-bestand-bar" >"$SANDBOX_STATE/opt/ninlight-station/images/ninlight_bar_ota.img"
|
|
: >"$LOG"
|
|
set +e
|
|
OUT=$(run_deploy "$SANDBOX" pi-test foo 2>&1)
|
|
RC=$?
|
|
set -e
|
|
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC: $OUT"
|
|
if [ ! -e "$SANDBOX_STATE/opt/ninlight-station/images/ninlight_foo_ota.img" ] \
|
|
&& [ ! -e "$SANDBOX_STATE/opt/ninlight-station/images/ninlight_bar_ota.img" ]; then
|
|
pass "der flache Altbestand wurde auch bei einem gezielten Deploy entfernt"
|
|
else
|
|
fail "flacher Altbestand liegt noch da - er wuerde von niemandem mehr gelesen, aber am Pi einen gueltigen Satz vortaeuschen"
|
|
fi
|
|
if [ -f "$SANDBOX_STATE/opt/ninlight-station/images/rescue-wlan/ninlight_foo_ota.img" ]; then
|
|
pass "das neue Rettungsimage liegt an der richtigen Stelle (images/rescue-wlan/)"
|
|
else
|
|
fail "das neue Rettungsimage fehlt: $OUT"
|
|
fi
|
|
|
|
echo
|
|
if [ "$FAILED" -eq 0 ]; then
|
|
echo "Alle Tests bestanden."
|
|
else
|
|
echo "Mindestens ein Test ist fehlgeschlagen." >&2
|
|
fi
|
|
exit "$FAILED"
|