Viertes Analog-Profil neben den SM2135EH-Lampen: PWM-Kanaele R=PB13 (ch1), G=PB16 (ch2), B=PB15 (ch3) bei 800 Hz, PIN-belegt aus der Original-Firmware (qcl_build_config_lamp.h, HARDWARE_MOBL_LED_SHIELD). Kein CW/WW-Zweig: ct_min == ct_max ist das Erkennungszeichen fuer 'weiss = RGB-Mischung', nl_color.c gibt in diesem Fall alle drei Farbkanalgleich. Neues Bauziel in allen Skripten; test_release zaehlt Dateizahlen jetzt aus TARGETS statt hart 8/16.
497 lines
20 KiB
Bash
Executable File
497 lines
20 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Stub-Tests fuer tools/release.sh (und tools/build_nocreds.sh) - ohne Docker, ohne
|
|
# echten Firmwarebau ausfuehrbar.
|
|
#
|
|
# Gestubbt wird nur die UNTERSTE Ebene: NINLIGHT_DOCKER zeigt auf ein Skript, das sich
|
|
# wie der Bau im Container verhaelt (relative Pfade ab Repo-Wurzel, legt _gz.img und
|
|
# _ota.img ab). Alles darueber - das Beiseitelegen von config.local.cmake, das
|
|
# Signieren, README/SHA256SUMS, das Packen - laeuft echt. Wichtig dabei: der Stub
|
|
# verhaelt sich wie ein Compiler und SCHREIBT die Werte aus config.local.cmake in die
|
|
# Images, wenn die Datei zum Bauzeitpunkt noch da ist. Nur so pruefen die Faelle 5/6
|
|
# wirklich das Skript und nicht den Stub: vergisst release.sh/build_nocreds.sh das
|
|
# Beiseitelegen, landen die Zugangsdaten tatsaechlich im Abzug.
|
|
#
|
|
# Aufruf: tools/tests/test_release.sh
|
|
set -euo pipefail
|
|
|
|
HERE=$(cd "$(dirname "$0")" && pwd)
|
|
TOOLS_DIR=$(cd "$HERE/.." && pwd)
|
|
REPO=$(cd "$TOOLS_DIR/.." && pwd)
|
|
|
|
# Sicherheitsnetz (wie in tools/station/tests/test_deploy.sh): NINLIGHT_SIGN_DIR zeigt
|
|
# auf diesem Rechner auf ECHTE Signierschluessel. Sie darf nicht aus der Umgebung in
|
|
# diese Suite durchsickern - jedes Szenario setzt sie hart, Testschluessel kommen ueber
|
|
# die separate Variable TEST_SIGN_DIR.
|
|
unset NINLIGHT_SIGN_DIR
|
|
unset TEST_SIGN_DIR
|
|
unset NINLIGHT_NOCREDS_BUILD_REL
|
|
unset NINLIGHT_DOCKER
|
|
|
|
# GNU vor BSD: auf dem Pi/Debian gibt es sha256sum, auf dem Mac nur shasum.
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
SHA_CHECK=(sha256sum -c)
|
|
else
|
|
SHA_CHECK=(shasum -a 256 -c)
|
|
fi
|
|
|
|
TMPROOT=$(mktemp -d)
|
|
trap 'rm -rf "$TMPROOT"' EXIT
|
|
|
|
FAILED=0
|
|
pass() { echo "ok - $1"; }
|
|
fail() { echo "FEHLER - $1" >&2; FAILED=1; }
|
|
|
|
TARGETS=(evk e27_rgb e14_rgb gu10_rgb e27_cw e14_cw gu10_cw g95_cw ledshield_rgb)
|
|
|
|
# Wegwerf-Zugangsdaten - niemals die echten aus dem Repo.
|
|
SECRET_SSID="TestWLAN-4b7f-geheim"
|
|
SECRET_PASS="testpasswort-4b7f"
|
|
SECRET_TOKEN="0badc0de4b7f0badc0de4b7f"
|
|
|
|
# --- Sandbox-Repo: release.sh, build_nocreds.sh, nl_sign.py, bruno/, config.local.cmake ---
|
|
setup_sandbox() {
|
|
local sandbox=$1
|
|
rm -rf "$sandbox"
|
|
mkdir -p "$sandbox/tools/mkimage" "$sandbox/bruno" "$sandbox/dist"
|
|
cp "$TOOLS_DIR/release.sh" "$sandbox/tools/release.sh"
|
|
cp "$TOOLS_DIR/build_nocreds.sh" "$sandbox/tools/build_nocreds.sh"
|
|
chmod +x "$sandbox/tools/release.sh" "$sandbox/tools/build_nocreds.sh"
|
|
# Das ECHTE Signierwerkzeug, nur die Schluessel sind Wegwerf-Testschluessel.
|
|
cp "$TOOLS_DIR/mkimage/nl_sign.py" "$sandbox/tools/mkimage/nl_sign.py"
|
|
# Die ECHTE Bruno-Collection: die Zusage ist, dass sie im Archiv liegt - mit einer
|
|
# erfundenen Attrappe wuerde der Test nur sich selbst pruefen.
|
|
cp "$REPO/bruno/ninlight.yml" "$sandbox/bruno/ninlight.yml"
|
|
cat >"$sandbox/config.local.cmake" <<EOF
|
|
set(NINLIGHT_WIFI_SSID "$SECRET_SSID")
|
|
set(NINLIGHT_WIFI_PASSWORD "$SECRET_PASS")
|
|
set(NINLIGHT_TOKEN "$SECRET_TOKEN")
|
|
EOF
|
|
}
|
|
|
|
# --- Docker-Stub: verhaelt sich wie der Bau im Container ---
|
|
# Steuerbar ueber:
|
|
# STUB_FAIL_TARGET bricht beim Bau dieses Ziels mit Exit 1 ab
|
|
# STUB_TERM_TARGET schickt beim Bau dieses Ziels SIGTERM an den Aufrufer
|
|
# (build_nocreds.sh) - erzwungener Abbruch mitten im Lauf
|
|
# STUB_LEAK=1 schreibt die Zugangsdaten IMMER ins Image, auch wenn
|
|
# config.local.cmake beiseite liegt (prueft den doppelten Boden)
|
|
setup_stub_docker() {
|
|
local stub=$1 sandbox=$2
|
|
cat >"$stub" <<'STUB'
|
|
#!/usr/bin/env bash
|
|
# Erwartet "cmake -S . -B <rel>/<ziel> ..." bzw. "cmake --build <rel>/<ziel>".
|
|
set -euo pipefail
|
|
cd "$NINLIGHT_STUB_ROOT"
|
|
mode=""
|
|
outdir=""
|
|
args=("$@")
|
|
for ((i = 0; i < ${#args[@]}; i++)); do
|
|
case "${args[$i]}" in
|
|
--build) mode=build; outdir=${args[$((i + 1))]} ;;
|
|
-B) mode=${mode:-configure}; outdir=${args[$((i + 1))]} ;;
|
|
esac
|
|
done
|
|
[ -n "$outdir" ] || { echo "Stub: kein Build-Verzeichnis in '$*'" >&2; exit 1; }
|
|
target=$(basename "$outdir")
|
|
mkdir -p "$outdir"
|
|
[ "$mode" = "build" ] || exit 0
|
|
|
|
if [ "${STUB_TERM_TARGET:-}" = "$target" ]; then
|
|
kill -TERM "$PPID"
|
|
sleep 5
|
|
exit 1
|
|
fi
|
|
if [ "${STUB_FAIL_TARGET:-}" = "$target" ]; then
|
|
echo "Stub: Bau von $target fehlgeschlagen" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# "Compiler": die Zugangsdaten aus config.local.cmake landen im Abzug - genau wie beim
|
|
# echten Bau, wo CMakeLists.txt sie per set() einkompiliert.
|
|
creds=""
|
|
if [ -f config.local.cmake ]; then
|
|
creds=$(sed -n 's/.*set(NINLIGHT_[A-Z_]* "\(.*\)").*/\1/p' config.local.cmake | tr '\n' ' ')
|
|
fi
|
|
if [ "${STUB_LEAK:-}" = "1" ]; then
|
|
creds="$STUB_LEAK_VALUE"
|
|
fi
|
|
CREDS="$creds" TARGET="$target" OUTDIR="$outdir" python3 - <<'PY'
|
|
import os
|
|
|
|
target, outdir, creds = os.environ["TARGET"], os.environ["OUTDIR"], os.environ["CREDS"]
|
|
body = bytearray(("firmware-%s-" % target).encode() * 200)
|
|
body = body[:4096] + b"\0" * (4096 - len(body[:4096]))
|
|
blob = creds.encode()
|
|
if blob:
|
|
body[100:100 + len(blob)] = blob
|
|
gz = bytes(body)
|
|
with open(os.path.join(outdir, "ninlight_%s_gz.img" % target), "wb") as fh:
|
|
fh.write(gz) # roh, ohne NINL-Trailer - das signiert nl_sign.py
|
|
trailer = bytearray(b"\xff" * 512)
|
|
trailer[0:4] = b"NINL"
|
|
with open(os.path.join(outdir, "ninlight_%s_ota.img" % target), "wb") as fh:
|
|
fh.write(gz + bytes(trailer))
|
|
PY
|
|
STUB
|
|
chmod +x "$stub"
|
|
}
|
|
|
|
# Wegwerf-Testschluessel ueber nl_sign.generate_test_key() - nie echte Schluessel.
|
|
make_test_keys() {
|
|
local keydir=$1 with_prod=$2 with_dev=$3
|
|
mkdir -p "$keydir/QCX_Device_CA"
|
|
PYTHONPATH="$TOOLS_DIR/mkimage" 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)
|
|
with open(keydir + "/prod_pub.der", "wb") as fh:
|
|
fh.write(k.public_der)
|
|
if with_dev:
|
|
k = nl_sign.generate_test_key()
|
|
with open(keydir + "/fw_sign.pem", "wb") as fh:
|
|
fh.write(k.private_pem)
|
|
with open(keydir + "/dev_pub.der", "wb") as fh:
|
|
fh.write(k.public_der)
|
|
PY
|
|
}
|
|
|
|
# Ruft release.sh in der Sandbox auf. NINLIGHT_SIGN_DIR wird hier HART gesetzt, nie aus
|
|
# der geerbten Umgebung uebernommen (siehe Sicherheitsnetz oben).
|
|
run_release() {
|
|
local sandbox=$1 version=$2
|
|
local sign_dir="${TEST_SIGN_DIR:-$sandbox/kein-schluesselverzeichnis}"
|
|
env NINLIGHT_SIGN_DIR="$sign_dir" \
|
|
NINLIGHT_DOCKER="$sandbox/stub-docker" \
|
|
NINLIGHT_STUB_ROOT="$sandbox" \
|
|
STUB_FAIL_TARGET="${STUB_FAIL_TARGET:-}" \
|
|
STUB_TERM_TARGET="${STUB_TERM_TARGET:-}" \
|
|
STUB_LEAK="${STUB_LEAK:-}" \
|
|
STUB_LEAK_VALUE="$SECRET_SSID" \
|
|
"$sandbox/tools/release.sh" "$version"
|
|
}
|
|
|
|
# Sucht die Zugangsdaten BINAER in jeder Datei unterhalb eines Verzeichnisses.
|
|
grep_secrets() {
|
|
local dir=$1
|
|
SSID="$SECRET_SSID" PASS="$SECRET_PASS" TOKEN="$SECRET_TOKEN" python3 - "$dir" <<'PY'
|
|
import os
|
|
import sys
|
|
|
|
root = sys.argv[1]
|
|
needles = {k: os.environ[k].encode() for k in ("SSID", "PASS", "TOKEN")}
|
|
for dirpath, _dirs, files in os.walk(root):
|
|
for fn in sorted(files):
|
|
p = os.path.join(dirpath, fn)
|
|
if os.path.islink(p):
|
|
continue
|
|
with open(p, "rb") as fh:
|
|
data = fh.read()
|
|
for name, needle in needles.items():
|
|
if needle in data:
|
|
print("%s in %s" % (name, os.path.relpath(p, root)))
|
|
PY
|
|
}
|
|
|
|
sha_of() { python3 -c "
|
|
import hashlib,sys
|
|
print(hashlib.sha256(open(sys.argv[1],'rb').read()).hexdigest())" "$1"; }
|
|
|
|
echo "=== Szenario 1: Erfolgslauf mit beiden Schluesseln ==="
|
|
SANDBOX="$TMPROOT/s1"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stub_docker "$SANDBOX/stub-docker" "$SANDBOX"
|
|
KEYS_FULL="$TMPROOT/keys-full"
|
|
make_test_keys "$KEYS_FULL" 1 1
|
|
CONF_BEFORE=$(sha_of "$SANDBOX/config.local.cmake")
|
|
set +e
|
|
OUT=$(TEST_SIGN_DIR="$KEYS_FULL" run_release "$SANDBOX" 0.9.9 2>&1)
|
|
RC=$?
|
|
set -e
|
|
if [ "$RC" -eq 0 ]; then pass "Exit-Code 0"; else fail "Exit-Code $RC: $OUT"; fi
|
|
TGZ="$SANDBOX/dist/ninlight-0.9.9.tar.gz"
|
|
if [ -f "$TGZ" ]; then pass "Archiv entstanden"; else fail "kein Archiv unter $TGZ"; fi
|
|
|
|
EXTRACT="$TMPROOT/x1"
|
|
mkdir -p "$EXTRACT"
|
|
tar xzf "$TGZ" -C "$EXTRACT"
|
|
A="$EXTRACT/ninlight-0.9.9"
|
|
if [ -d "$A" ]; then pass "Archiv entpackt nach ninlight-0.9.9/"; else fail "Verzeichnis ninlight-0.9.9/ fehlt"; fi
|
|
|
|
echo "--- Fall 1/2/3: Verzeichnisse und Dateizahlen ---"
|
|
for d in ota migrate flash bruno; do
|
|
if [ -d "$A/$d" ]; then pass "$d/ vorhanden"; else fail "$d/ fehlt"; fi
|
|
done
|
|
for f in SHA256SUMS README.md onboard.sh bruno/ninlight.yml; do
|
|
if [ -f "$A/$f" ]; then pass "$f vorhanden"; else fail "$f fehlt"; fi
|
|
done
|
|
if [ -x "$A/onboard.sh" ]; then pass "onboard.sh ist ausfuehrbar"; else fail "onboard.sh ist nicht ausfuehrbar"; fi
|
|
N_OTA=$(find "$A/ota" -type f | wc -l | tr -d ' ')
|
|
N_FLASH=$(find "$A/flash" -type f | wc -l | tr -d ' ')
|
|
N_MIG=$(find "$A/migrate" -type f | wc -l | tr -d ' ')
|
|
[ "$N_OTA" = "${#TARGETS[@]}" ] && pass "ota/ enthaelt ${#TARGETS[@]} Dateien" || fail "ota/ enthaelt $N_OTA statt ${#TARGETS[@]}"
|
|
[ "$N_FLASH" = "${#TARGETS[@]}" ] && pass "flash/ enthaelt ${#TARGETS[@]} Dateien" || fail "flash/ enthaelt $N_FLASH statt ${#TARGETS[@]}"
|
|
[ "$N_MIG" = "$(( 2 * ${#TARGETS[@]} ))" ] && pass "migrate/ enthaelt $(( 2 * ${#TARGETS[@]} )) Dateien" || fail "migrate/ enthaelt $N_MIG statt $(( 2 * ${#TARGETS[@]} ))"
|
|
MISS=""
|
|
for t in "${TARGETS[@]}"; do
|
|
[ -f "$A/ota/ninlight_${t}_ota.img" ] || MISS="$MISS ota/$t"
|
|
[ -f "$A/flash/ninlight_${t}_gz.img" ] || MISS="$MISS flash/$t"
|
|
[ -f "$A/migrate/${t}_prod.img" ] || MISS="$MISS migrate/${t}_prod"
|
|
[ -f "$A/migrate/${t}_dev.img" ] || MISS="$MISS migrate/${t}_dev"
|
|
done
|
|
[ -z "$MISS" ] && pass "je Ziel genau die erwarteten Dateinamen" || fail "fehlende Dateien:$MISS"
|
|
|
|
echo "--- Migrationsimages sind echt signiert (und zwar das _gz.img) ---"
|
|
if python3 "$TOOLS_DIR/mkimage/nl_sign.py" verify --pub "$KEYS_FULL/prod_pub.der" \
|
|
--in "$A/migrate/evk_prod.img" >/dev/null 2>&1; then
|
|
pass "evk_prod.img traegt eine gueltige Produktionssignatur"
|
|
else
|
|
fail "evk_prod.img besteht die Signaturpruefung nicht"
|
|
fi
|
|
if python3 "$TOOLS_DIR/mkimage/nl_sign.py" verify --pub "$KEYS_FULL/dev_pub.der" \
|
|
--in "$A/migrate/g95_cw_dev.img" >/dev/null 2>&1; then
|
|
pass "g95_cw_dev.img traegt eine gueltige Entwicklungssignatur"
|
|
else
|
|
fail "g95_cw_dev.img besteht die Signaturpruefung nicht"
|
|
fi
|
|
# Die Signatur belegt denselben 512-Byte-Block wie der Trailer: signiert werden muss das
|
|
# rohe _gz.img. Ein versehentlich signiertes _ota.img waere 512 Byte laenger.
|
|
GZ_SIZE=$(python3 -c "import os,sys;print(os.path.getsize(sys.argv[1]))" "$A/flash/ninlight_evk_gz.img")
|
|
MIG_SIZE=$(python3 -c "import os,sys;print(os.path.getsize(sys.argv[1]))" "$A/migrate/evk_prod.img")
|
|
if [ "$MIG_SIZE" -eq $((GZ_SIZE + 512)) ]; then
|
|
pass "Migrationsimage = _gz.img + 512 Byte Signatur (nicht das _ota.img signiert)"
|
|
else
|
|
fail "Migrationsimage hat $MIG_SIZE Byte, erwartet $((GZ_SIZE + 512))"
|
|
fi
|
|
|
|
echo "--- Fall 4: SHA256SUMS deckt jede Datei ab und stimmt ---"
|
|
LISTED=$(awk '{print $2}' "$A/SHA256SUMS" | sed 's|^\*||' | sort)
|
|
PRESENT=$( (cd "$A" && find . -type f ! -name SHA256SUMS | sed 's|^\./||' | sort) )
|
|
if [ "$LISTED" = "$PRESENT" ]; then
|
|
pass "SHA256SUMS listet genau die Dateien des Archivs ($(echo "$PRESENT" | wc -l | tr -d ' '))"
|
|
else
|
|
fail "SHA256SUMS und Archivinhalt weichen ab:
|
|
$(diff <(echo "$LISTED") <(echo "$PRESENT") || true)"
|
|
fi
|
|
if (cd "$A" && "${SHA_CHECK[@]}" SHA256SUMS >/dev/null 2>&1); then
|
|
pass "Pruefsummen stimmen"
|
|
else
|
|
fail "Pruefsummenpruefung fehlgeschlagen"
|
|
fi
|
|
|
|
echo "--- Fall 5: keine Zugangsdaten im Archiv (binaer gesucht) ---"
|
|
HITS=$(grep_secrets "$A")
|
|
if [ -z "$HITS" ]; then
|
|
pass "weder SSID noch Passwort noch Token in irgendeiner Datei des Archivs"
|
|
else
|
|
fail "Zugangsdaten im Archiv gefunden:
|
|
$HITS"
|
|
fi
|
|
# Auch im gepackten Archiv selbst (tar speichert Inhalte ungefiltert, gzip ist keine
|
|
# Verschleierung - aber eine unkomprimierbare Kopie wuerde hier auffallen).
|
|
if python3 -c "
|
|
import gzip,sys
|
|
data = gzip.open(sys.argv[1],'rb').read()
|
|
sys.exit(0 if not any(s.encode() in data for s in sys.argv[2:]) else 1)" \
|
|
"$TGZ" "$SECRET_SSID" "$SECRET_PASS" "$SECRET_TOKEN"; then
|
|
pass "auch im entpackten tar-Strom keine Zugangsdaten"
|
|
else
|
|
fail "Zugangsdaten im tar-Strom des Archivs"
|
|
fi
|
|
# Der Stub verhaelt sich nur dann wie ein Compiler, wenn er die Werte ueberhaupt
|
|
# einbauen KANN - sonst waere Fall 5 wertlos. Gegenprobe: mit vorhandener
|
|
# config.local.cmake landen sie im Abzug.
|
|
mkdir -p "$TMPROOT/probe"
|
|
env NINLIGHT_STUB_ROOT="$SANDBOX" "$SANDBOX/stub-docker" cmake --build "$TMPROOT/probe/evk" >/dev/null
|
|
if grep_secrets "$TMPROOT/probe" | grep -q SSID; then
|
|
pass "Gegenprobe: der Stub baut die Zugangsdaten ein, solange config.local.cmake da ist"
|
|
else
|
|
fail "Gegenprobe fehlgeschlagen - der Stub baut nie Zugangsdaten ein, Fall 5 waere wertlos"
|
|
fi
|
|
|
|
echo "--- Fall 6a: config.local.cmake nach dem Erfolgslauf unveraendert ---"
|
|
if [ -f "$SANDBOX/config.local.cmake" ] && [ "$(sha_of "$SANDBOX/config.local.cmake")" = "$CONF_BEFORE" ]; then
|
|
pass "config.local.cmake liegt unveraendert an seinem Platz"
|
|
else
|
|
fail "config.local.cmake fehlt oder wurde veraendert"
|
|
fi
|
|
|
|
echo "--- README nennt die Vertrauensgrenze und die Verzeichnisse ---"
|
|
for phrase in "kein kryptografisches Signaturverfahren" "ohne TLS" "onboard.sh" "migrate/" "flash/"; do
|
|
if grep -qF "$phrase" "$A/README.md"; then pass "README nennt '$phrase'"; else fail "README nennt '$phrase' nicht"; fi
|
|
done
|
|
|
|
echo "=== Szenario 2: Bau bricht mittendrin ab (Fall 6) ==="
|
|
SANDBOX="$TMPROOT/s2"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stub_docker "$SANDBOX/stub-docker" "$SANDBOX"
|
|
CONF_BEFORE=$(sha_of "$SANDBOX/config.local.cmake")
|
|
set +e
|
|
OUT=$(STUB_FAIL_TARGET=e27_cw TEST_SIGN_DIR="$KEYS_FULL" run_release "$SANDBOX" 0.9.9 2>&1)
|
|
RC=$?
|
|
set -e
|
|
if [ "$RC" -ne 0 ]; then pass "Exit-Code != 0 ($RC)"; else fail "Exit-Code war 0"; fi
|
|
if [ ! -e "$SANDBOX/dist/ninlight-0.9.9.tar.gz" ]; then pass "kein halbes Archiv zurueckgelassen"; else fail "Archiv trotz Abbruch entstanden"; fi
|
|
if [ -f "$SANDBOX/config.local.cmake" ] && [ "$(sha_of "$SANDBOX/config.local.cmake")" = "$CONF_BEFORE" ]; then
|
|
pass "config.local.cmake nach dem Abbruch unveraendert an seinem Platz"
|
|
else
|
|
fail "config.local.cmake nach dem Abbruch verschwunden oder veraendert"
|
|
fi
|
|
|
|
echo "=== Szenario 3: Abbruch per Signal (SIGTERM mitten im Bau) ==="
|
|
SANDBOX="$TMPROOT/s3"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stub_docker "$SANDBOX/stub-docker" "$SANDBOX"
|
|
CONF_BEFORE=$(sha_of "$SANDBOX/config.local.cmake")
|
|
set +e
|
|
OUT=$(STUB_TERM_TARGET=gu10_rgb TEST_SIGN_DIR="$KEYS_FULL" run_release "$SANDBOX" 0.9.9 2>&1)
|
|
RC=$?
|
|
set -e
|
|
if [ "$RC" -ne 0 ]; then pass "Exit-Code != 0 ($RC)"; else fail "Exit-Code war 0"; fi
|
|
if [ -f "$SANDBOX/config.local.cmake" ] && [ "$(sha_of "$SANDBOX/config.local.cmake")" = "$CONF_BEFORE" ]; then
|
|
pass "config.local.cmake nach dem Signalabbruch unveraendert an seinem Platz"
|
|
else
|
|
fail "config.local.cmake nach dem Signalabbruch verschwunden oder veraendert"
|
|
fi
|
|
if [ ! -e "$SANDBOX/dist/ninlight-0.9.9.tar.gz" ]; then pass "kein Archiv nach dem Signalabbruch"; else fail "Archiv trotz Signalabbruch entstanden"; fi
|
|
|
|
echo "=== Szenario 4: ohne Schluesselverzeichnis (Fall 3) ==="
|
|
SANDBOX="$TMPROOT/s4"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stub_docker "$SANDBOX/stub-docker" "$SANDBOX"
|
|
set +e
|
|
OUT=$(run_release "$SANDBOX" 0.9.9 2>&1) # kein TEST_SIGN_DIR -> Verzeichnis gibt es nicht
|
|
RC=$?
|
|
set -e
|
|
if [ "$RC" -eq 0 ]; then pass "Exit-Code 0 - das Archiv entsteht trotzdem"; else fail "Exit-Code $RC: $OUT"; fi
|
|
if echo "$OUT" | grep -q "Schluesselverzeichnis"; then pass "deutliche Meldung zum fehlenden Schluesselverzeichnis"; else fail "keine Meldung zum fehlenden Schluesselverzeichnis"; fi
|
|
EXTRACT="$TMPROOT/x4"; mkdir -p "$EXTRACT"
|
|
tar xzf "$SANDBOX/dist/ninlight-0.9.9.tar.gz" -C "$EXTRACT"
|
|
A4="$EXTRACT/ninlight-0.9.9"
|
|
if [ ! -d "$A4/migrate" ]; then pass "migrate/ liegt nicht vor"; else fail "migrate/ trotzdem vorhanden"; fi
|
|
if [ -d "$A4/ota" ] && [ "$(find "$A4/ota" -type f | wc -l | tr -d ' ')" = "${#TARGETS[@]}" ]; then
|
|
pass "ota/ trotzdem vollstaendig"
|
|
else
|
|
fail "ota/ unvollstaendig"
|
|
fi
|
|
if grep -q "migrate/\` fehlt in diesem Archiv" "$A4/README.md"; then
|
|
pass "README.md sagt, warum migrate/ fehlt"
|
|
else
|
|
fail "README.md erklaert das fehlende migrate/ nicht"
|
|
fi
|
|
if (cd "$A4" && "${SHA_CHECK[@]}" SHA256SUMS >/dev/null 2>&1); then
|
|
pass "SHA256SUMS stimmt auch ohne migrate/"
|
|
else
|
|
fail "SHA256SUMS stimmt ohne migrate/ nicht"
|
|
fi
|
|
|
|
echo "=== Szenario 5: zweiter Lauf mit derselben Version bricht ab (Fall 7) ==="
|
|
BEFORE=$(sha_of "$SANDBOX/dist/ninlight-0.9.9.tar.gz")
|
|
set +e
|
|
OUT=$(run_release "$SANDBOX" 0.9.9 2>&1)
|
|
RC=$?
|
|
set -e
|
|
if [ "$RC" -ne 0 ]; then pass "Exit-Code != 0 ($RC)"; else fail "zweiter Lauf war erfolgreich"; fi
|
|
if echo "$OUT" | grep -q "existiert bereits"; then pass "Meldung 'existiert bereits'"; else fail "keine Meldung zum vorhandenen Archiv: $OUT"; fi
|
|
if [ "$(sha_of "$SANDBOX/dist/ninlight-0.9.9.tar.gz")" = "$BEFORE" ]; then
|
|
pass "vorhandenes Archiv unveraendert"
|
|
else
|
|
fail "vorhandenes Archiv wurde ueberschrieben"
|
|
fi
|
|
# Und eine andere Version laeuft weiterhin durch.
|
|
set +e
|
|
OUT=$(run_release "$SANDBOX" 0.9.10 2>&1)
|
|
RC=$?
|
|
set -e
|
|
if [ "$RC" -eq 0 ] && [ -f "$SANDBOX/dist/ninlight-0.9.10.tar.gz" ]; then
|
|
pass "andere Version laeuft normal durch"
|
|
else
|
|
fail "andere Version schlaegt fehl: $OUT"
|
|
fi
|
|
|
|
echo "=== Szenario 6: doppelter Boden - Zugangsdaten im Abzug brechen den Lauf ab ==="
|
|
SANDBOX="$TMPROOT/s6"
|
|
setup_sandbox "$SANDBOX"
|
|
setup_stub_docker "$SANDBOX/stub-docker" "$SANDBOX"
|
|
set +e
|
|
OUT=$(STUB_LEAK=1 TEST_SIGN_DIR="$KEYS_FULL" run_release "$SANDBOX" 0.9.9 2>&1)
|
|
RC=$?
|
|
set -e
|
|
if [ "$RC" -ne 0 ]; then pass "Exit-Code != 0 ($RC)"; else fail "Lauf war trotz Zugangsdaten im Abzug erfolgreich"; fi
|
|
if echo "$OUT" | grep -q "Zugangsdaten aus config.local.cmake im Archiv gefunden"; then
|
|
pass "Meldung benennt den Fund"
|
|
else
|
|
fail "keine Meldung zum Fund: $OUT"
|
|
fi
|
|
if echo "$OUT" | grep -qF "$SECRET_SSID"; then
|
|
fail "die Meldung druckt den geheimen Wert selbst"
|
|
else
|
|
pass "die Meldung druckt den Wert nicht, nur den Namen"
|
|
fi
|
|
if [ ! -e "$SANDBOX/dist/ninlight-0.9.9.tar.gz" ]; then pass "kein Archiv gepackt"; else fail "Archiv trotz Fund gepackt"; fi
|
|
|
|
echo "=== Szenario 7: onboard.sh spricht die richtige Schnittstelle an ==="
|
|
ONB="$TMPROOT/x1/ninlight-0.9.9/onboard.sh"
|
|
STUBBIN="$TMPROOT/onb-bin"
|
|
mkdir -p "$STUBBIN"
|
|
CURLLOG="$TMPROOT/curl.log"
|
|
cat >"$STUBBIN/curl" <<'CURL'
|
|
#!/usr/bin/env bash
|
|
{ for a in "$@"; do printf '%s\n' "$a"; done; echo "--"; } >>"$CURL_LOG"
|
|
echo '{}'
|
|
CURL
|
|
chmod +x "$STUBBIN/curl"
|
|
: >"$CURLLOG"
|
|
CURL_LOG="$CURLLOG" PATH="$STUBBIN:$PATH" NINLIGHT_ONBOARD_WAIT=0 "$ONB" "MeinWLAN" "geheim123" >/dev/null
|
|
if grep -q "^http://192.168.4.1:3333/api/v1/system/settings$" "$CURLLOG"; then
|
|
pass "PUT geht an system/settings im Onboarding-AP"
|
|
else
|
|
fail "falsche URL: $(cat "$CURLLOG")"
|
|
fi
|
|
if grep -q '^PUT$' "$CURLLOG"; then pass "Methode PUT"; else fail "keine PUT-Methode"; fi
|
|
if grep -qF '{"type": "wifi", "ssid": "MeinWLAN", "password": "geheim123"}' "$CURLLOG"; then
|
|
pass "Rumpf entspricht der Bruno-Anfrage 'Set WiFi'"
|
|
else
|
|
fail "falscher Rumpf: $(cat "$CURLLOG")"
|
|
fi
|
|
if grep -q "^http://192.168.4.1:3333/api/v1/system/info$" "$CURLLOG"; then
|
|
pass "danach wird system/info abgefragt"
|
|
else
|
|
fail "system/info wird nicht abgefragt"
|
|
fi
|
|
if grep -q "^Authorization:" "$CURLLOG"; then
|
|
fail "ohne --token wird trotzdem ein Authorization-Header geschickt"
|
|
else
|
|
pass "ohne --token kein Authorization-Header (offener Onboarding-AP)"
|
|
fi
|
|
: >"$CURLLOG"
|
|
CURL_LOG="$CURLLOG" PATH="$STUBBIN:$PATH" NINLIGHT_ONBOARD_WAIT=0 "$ONB" --token abc12345 "MeinWLAN" "geheim123" >/dev/null
|
|
if grep -q "^Authorization: abc12345$" "$CURLLOG"; then
|
|
pass "mit --token wird der Header geschickt (Fallback-AP)"
|
|
else
|
|
fail "--token kommt nicht als Authorization-Header an"
|
|
fi
|
|
HELP=$("$ONB" --help)
|
|
if echo "$HELP" | grep -qi "von hand" && echo "$HELP" | grep -q "ninlight-<mac4>"; then
|
|
pass "Hilfetext nennt die manuellen Schritte und den AP-Namen"
|
|
else
|
|
fail "Hilfetext erklaert die manuellen Schritte nicht: $HELP"
|
|
fi
|
|
set +e
|
|
"$ONB" >/dev/null 2>&1
|
|
RC=$?
|
|
set -e
|
|
if [ "$RC" -ne 0 ]; then pass "ohne SSID bricht onboard.sh ab"; else fail "ohne SSID lief onboard.sh durch"; fi
|
|
|
|
echo
|
|
if [ "$FAILED" -eq 0 ]; then
|
|
echo "alle Zusagen erfuellt"
|
|
else
|
|
echo "es gab Fehler" >&2
|
|
fi
|
|
exit "$FAILED"
|