feat(station): Anzeige ausliefern, Migrationsprofil, display-Befehle
Schritt 8 des Plans: die fertige E-Paper-Anzeige wird endlich ausgeliefert und verdrahtet, dazu zwei liegengebliebene Dinge. - deploy.sh/setup_pi.sh uebertragen display/ (mit Panel-Treibern und HERKUNFT.md) und ninlight-display.service; setup_pi.sh installiert und aktiviert die Unit und zieht python3-pil nach - ohne Pillow beendet sich der Anzeigedienst still mit 0, das faellt sonst nirgends auf. - setup_pi.sh legt auch das Profil fuer den Migrations-Zugangspunkt an (qconnex-firmware, von der alten Klyqa-Firmware fest vorgegeben, gleiche Adresse wie das Rettungsprofil). Ohne das scheiterte "migrate on" auf einem frisch eingerichteten Pi an need_profile. Bestehende Profile bleiben unangetastet. - "ninlight-station display test|clear|off"; der Hilfetext erklaert die Revisionsbestimmung, denn EPD_REVISION laesst sich nur am Testbild bestimmen. - "off" nimmt die Anzeige mit: erst Dienst stoppen, dann Panel leeren (sonst schreibt der Daemon binnen 5 s sein altes Bild zurueck) und beides vor ap_switch, das bei haengendem NetworkManager abbricht. Ein stehengebliebenes "MIGRATION" wuerde einen Betriebszustand behaupten, den es nicht mehr gibt. - test_host.sh sammelt tools/mkimage als Ganzes ein: die 19 Tests in test_nl_mkimage.py liefen in keiner Suite mehr mit, darunter die Wache fuer die Python-3.8-Randbedingung des Build-Containers. Ein neuer Test haelt die Regel fest, dass keine Testdatei mehr herausfallen kann. - Nebenbei: die Temp-Datei mit dem WLAN-Passwort wird wieder zuverlaessig aufgeraeumt, und leere Konfigurationswerte legen kein kaputtes Profil an.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
"""Wache dagegen, dass eine Testdatei aus allen Suiten herausfaellt.
|
||||
|
||||
Genau das war schon einmal passiert: tools/test_host.sh rief pytest auf
|
||||
tools/mkimage/tests und tools/station/tests auf - tools/mkimage/test_nl_mkimage.py
|
||||
liegt aber DIREKT in tools/mkimage/ und lief damit in keiner Suite mehr mit. 19 Tests
|
||||
waren still verschwunden, darunter test_stays_python38_compatible, die Wache fuer die
|
||||
bindende 3.8-Randbedingung des Docker-Build-Containers (`str | None` in nl_mkimage.py
|
||||
hatte den Build schon einmal gebrochen).
|
||||
|
||||
Eine stumm ausgefallene Testdatei ist schlimmer als eine fehlende: die Suite bleibt
|
||||
gruen und behauptet damit eine Deckung, die es nicht gibt. Deshalb wird hier nicht ein
|
||||
einzelner Pfad festgenagelt, sondern die Regel selbst - JEDE test_*.py unter tools/
|
||||
muss von einem der Pfade erfasst sein, die tools/test_host.sh an pytest uebergibt.
|
||||
Eine neue Testdatei an einer neuen Stelle faellt damit sofort auf, statt erst dann,
|
||||
wenn jemand sie vermisst.
|
||||
"""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
TOOLS_DIR = Path(__file__).resolve().parents[2]
|
||||
REPO_ROOT = TOOLS_DIR.parent
|
||||
TEST_HOST = TOOLS_DIR / "test_host.sh"
|
||||
|
||||
# Verzeichnisse, die keine Tests DIESES Projekts enthalten: virtuelle Umgebungen und
|
||||
# installierte Fremdpakete bringen eigene test_*.py mit, die hier nichts zu suchen haben.
|
||||
IGNORED_PARTS = (".venv", "__pycache__", "site-packages", "node_modules", ".git")
|
||||
|
||||
|
||||
def pytest_paths():
|
||||
"""Die Pfade, die tools/test_host.sh an pytest uebergibt, als aufgeloeste Pfade."""
|
||||
text = TEST_HOST.read_text()
|
||||
match = re.search(r"^\s*python3 -m pytest (.+?)(?:\s+-\S+)*\s*$", text, re.M)
|
||||
assert match, "in tools/test_host.sh ist kein 'python3 -m pytest'-Aufruf zu finden"
|
||||
paths = []
|
||||
for raw in match.group(1).split():
|
||||
arg = raw.strip('"')
|
||||
if arg.startswith("-"):
|
||||
continue
|
||||
paths.append(Path(arg.replace("$ROOT", str(REPO_ROOT))).resolve())
|
||||
assert paths, "der pytest-Aufruf in tools/test_host.sh uebergibt keine Pfade"
|
||||
return paths
|
||||
|
||||
|
||||
def own_test_files():
|
||||
return sorted(
|
||||
p.resolve()
|
||||
for p in TOOLS_DIR.rglob("test_*.py")
|
||||
if not any(part in IGNORED_PARTS for part in p.parts)
|
||||
)
|
||||
|
||||
|
||||
def test_every_test_file_is_reached_by_test_host():
|
||||
roots = pytest_paths()
|
||||
orphans = []
|
||||
for test_file in own_test_files():
|
||||
if not any(test_file == r or r in test_file.parents for r in roots):
|
||||
orphans.append(test_file.relative_to(REPO_ROOT))
|
||||
assert not orphans, (
|
||||
"diese Testdateien laufen in keiner Suite mit - tools/test_host.sh sammelt sie "
|
||||
"nicht ein: {}. Entweder den Pfad in test_host.sh erweitern oder die Datei "
|
||||
"dorthin verschieben, wo sie eingesammelt wird.".format(
|
||||
", ".join(str(o) for o in orphans)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_python38_guard_is_among_them():
|
||||
"""Die konkrete Datei, deren Ausfall den Build-Bruch damals durchgelassen haette."""
|
||||
guard = (TOOLS_DIR / "mkimage" / "test_nl_mkimage.py").resolve()
|
||||
assert guard.exists(), "tools/mkimage/test_nl_mkimage.py fehlt"
|
||||
roots = pytest_paths()
|
||||
assert any(guard == r or r in guard.parents for r in roots), (
|
||||
"tools/mkimage/test_nl_mkimage.py wird von tools/test_host.sh nicht eingesammelt "
|
||||
"- darin steht test_stays_python38_compatible, die einzige Wache gegen erneute "
|
||||
"3.10-Syntax in nl_mkimage.py (der Docker-Build-Container hat Python 3.8.10)"
|
||||
)
|
||||
|
||||
|
||||
def test_vermin_is_installed():
|
||||
"""vermin fehlt -> harter Fehlschlag, kein Skip.
|
||||
|
||||
Eine Wache, die sich beim Fehlen ihres Werkzeugs stillschweigend abschaltet, haette
|
||||
den Build-Bruch von damals genauso durchgelassen wie gar kein Test. Nachinstallieren:
|
||||
pip install vermin (oder pip3 install --user vermin).
|
||||
"""
|
||||
import vermin # noqa: F401
|
||||
+31
-2
@@ -202,7 +202,14 @@ echo "== uebertragen =="
|
||||
# ausgelieferte Verzeichnis.
|
||||
# -e: rsync soll fuer den Transport zum Pi denselben ssh wie oben benutzen.
|
||||
rsync -a -c --delete -e "$SSH_BIN" "$STAGE/" "$HOST:/tmp/ninlight-images/"
|
||||
rsync -a -e "$SSH_BIN" "$HERE/server.py" "$HERE/products.py" "$HERE/ninlight-station" "$HERE/ninlight-station.service" "$HOST:/tmp/"
|
||||
rsync -a -e "$SSH_BIN" "$HERE/server.py" "$HERE/products.py" "$HERE/ninlight-station" \
|
||||
"$HERE/ninlight-station.service" "$HERE/ninlight-display.service" "$HOST:/tmp/"
|
||||
# Das Anzeigeverzeichnis als Ganzes (render.py, display.py, epdconfig.py, die beiden
|
||||
# uebernommenen Panel-Treiber und HERKUNFT.md - der Herkunftsnachweis gehoert mit auf
|
||||
# den Pi, nicht nur ins Repo). --delete gilt hier nur fuer die fluechtige Ablage unter
|
||||
# /tmp; __pycache__ bleibt draussen, sonst landen die .pyc dieses Rechners (andere
|
||||
# Python-Version als auf dem Pi) im Zielverzeichnis und bleiben dort liegen.
|
||||
rsync -a -c --delete --exclude=__pycache__ -e "$SSH_BIN" "$HERE/display/" "$HOST:/tmp/ninlight-display/"
|
||||
# Pfade als Variablen mit Default: so kann ein Test die Installation gegen
|
||||
# ein Sandbox-Verzeichnis statt gegen echte Systempfade laufen lassen.
|
||||
"$SSH_BIN" "$HOST" bash -s -- "$FULL_SYNC" "$MIGRATE_COMPLETE" <<'REMOTE'
|
||||
@@ -212,6 +219,7 @@ MIGRATE_COMPLETE=$2
|
||||
ROOT_DIR=${NINLIGHT_STATION_ROOT:-/opt/ninlight-station}
|
||||
BIN_PATH=${NINLIGHT_STATION_BIN:-/usr/local/bin/ninlight-station}
|
||||
UNIT_PATH=${NINLIGHT_STATION_UNIT:-/etc/systemd/system/ninlight-station.service}
|
||||
DISPLAY_UNIT_PATH=${NINLIGHT_DISPLAY_UNIT:-/etc/systemd/system/ninlight-display.service}
|
||||
|
||||
sudo install -d -m 0755 "$ROOT_DIR/images"
|
||||
if [ "$FULL_SYNC" -eq 1 ]; then
|
||||
@@ -333,7 +341,14 @@ sudo install -m 0755 /tmp/server.py "$ROOT_DIR/server.py"
|
||||
sudo install -m 0644 /tmp/products.py "$ROOT_DIR/products.py"
|
||||
sudo install -m 0755 /tmp/ninlight-station "$BIN_PATH"
|
||||
sudo install -m 0644 /tmp/ninlight-station.service "$UNIT_PATH"
|
||||
rm -rf /tmp/ninlight-images /tmp/server.py /tmp/products.py /tmp/ninlight-station /tmp/ninlight-station.service
|
||||
# Anzeige: Verzeichnis und Unit. --delete ist hier richtig (anders als bei den Images):
|
||||
# display/ ist reiner Code aus dem Repo, es gibt keinen Bestand, der einen Deploy
|
||||
# ueberleben muesste - eine im Repo geloeschte Datei muss auch hier verschwinden.
|
||||
sudo install -d -m 0755 "$ROOT_DIR/display"
|
||||
sudo rsync -a -c --delete /tmp/ninlight-display/ "$ROOT_DIR/display/"
|
||||
sudo install -m 0644 /tmp/ninlight-display.service "$DISPLAY_UNIT_PATH"
|
||||
rm -rf /tmp/ninlight-images /tmp/server.py /tmp/products.py /tmp/ninlight-station /tmp/ninlight-station.service \
|
||||
/tmp/ninlight-display /tmp/ninlight-display.service
|
||||
sudo systemctl daemon-reload
|
||||
if systemctl is-active --quiet ninlight-station; then
|
||||
if sudo systemctl restart ninlight-station; then
|
||||
@@ -345,6 +360,20 @@ if systemctl is-active --quiet ninlight-station; then
|
||||
else
|
||||
echo " Dienst laeuft nicht (Station ist aus)"
|
||||
fi
|
||||
# Die Anzeige laeuft mit dem gerade ersetzten Code weiter, bis sie neu gestartet wird.
|
||||
# Ein Fehlschlag ist hier aber ausdruecklich KEIN Deploy-Fehler (anders als oben beim
|
||||
# Hauptdienst): die Anzeige ist nirgends Voraussetzung fuer eine Migration, und ein
|
||||
# Deploy, der wegen des Panels rot wird, verleitet nur dazu, den echten Fehler oben zu
|
||||
# uebersehen.
|
||||
if systemctl is-active --quiet ninlight-display; then
|
||||
if sudo systemctl restart ninlight-display; then
|
||||
echo " Anzeigedienst neu gestartet"
|
||||
else
|
||||
echo " Warnung: Anzeigedienst lief, Neustart ist fehlgeschlagen - journalctl -u ninlight-display" >&2
|
||||
fi
|
||||
else
|
||||
echo " Anzeigedienst laeuft nicht"
|
||||
fi
|
||||
REMOTE
|
||||
|
||||
echo "fertig."
|
||||
|
||||
@@ -257,8 +257,41 @@ migrate_on() {
|
||||
echo "Nicht vergessen: 'ninlight-station off', wenn du fertig bist."
|
||||
}
|
||||
|
||||
# Ruft display.py mit einem Schalter auf. EPD_REVISION steht in $CONF und wird von
|
||||
# ". $CONF" nur als Shell-Variable gesetzt, nicht exportiert - "env VAR=..." reicht sie
|
||||
# an den Python-Prozess durch. "sudo", weil /dev/spidev0.0 und die GPIO-Leitungen root
|
||||
# gehoeren (der Anzeigedienst laeuft aus demselben Grund als root).
|
||||
display_run() {
|
||||
sudo env EPD_REVISION="$EPD_REVISION" python3 "$STATION_LIB/display/display.py" "$1"
|
||||
}
|
||||
|
||||
# Nimmt die Anzeige mit aus. Reihenfolge: erst den Dienst stoppen, dann das Panel leeren -
|
||||
# umgekehrt zeichnet der noch laufende Daemon binnen 5 s sein altes Bild wieder darauf.
|
||||
#
|
||||
# Warum leeren und nicht "Station aus" stehen lassen: der Anzeigedienst erkennt den
|
||||
# ausgeschalteten Zustand zwar selbst (systemctl is-active), aber ein laufender
|
||||
# Anzeigedienst nach "off" bedeutet, dass "off" eben NICHT alles abgeschaltet hat. Ein
|
||||
# leeres Panel behauptet dagegen gar nichts - und das ist bei einer Station, die Lampen
|
||||
# ungefragt Firmware unterschieben kann, die einzig ungefaehrliche Richtung: ein
|
||||
# stehengebliebenes "MIGRATION"-Bild wuerde einen Betriebszustand behaupten, den es nicht
|
||||
# mehr gibt, waehrend ein leeres Panel hoechstens nach "aus" aussieht - was es ist.
|
||||
display_off() {
|
||||
sudo systemctl stop ninlight-display 2>/dev/null || true
|
||||
# Kein Abbruch, wenn das Panel fehlt: display.py beendet sich dann selbst mit 0, und
|
||||
# ein Fehler an der Anzeige darf das Ausschalten der Station nie aufhalten.
|
||||
display_run --clear || echo "Warnung: Panel konnte nicht geleert werden - Anzeige pruefen" >&2
|
||||
}
|
||||
|
||||
full_off() {
|
||||
sudo systemctl stop ninlight-station 2>/dev/null || true
|
||||
# VOR ap_switch, nicht danach: ap_switch "" bricht bei einem haengenden
|
||||
# NetworkManager mit Fehler ab, und unter "set -e" kaeme danach nichts mehr. Genau
|
||||
# dann - Zugangspunkt laesst sich nicht abschalten - waere die Anzeige sonst der
|
||||
# letzte Rest von "off", der nie ausgefuehrt wird: Daemon laeuft weiter, Panel zeigt
|
||||
# unveraendert "MIGRATION". Ein leeres Panel behauptet dagegen nichts; die Wahrheit
|
||||
# ueber den noch offenen Zugangspunkt steht in ap_switch's Fehlermeldung, wo man sie
|
||||
# liest, statt auf einem Panel, das man auch fuer aktuell halten koennte.
|
||||
display_off
|
||||
ap_switch ""
|
||||
if nmcli -t -f NAME con show | grep -Fqx "$HOME_CONNECTION"; then
|
||||
if sudo nmcli con up "$HOME_CONNECTION" >/dev/null; then
|
||||
@@ -439,8 +472,27 @@ PY
|
||||
exit 0
|
||||
}
|
||||
|
||||
display_cmd() {
|
||||
case "${1:-}" in
|
||||
test) display_run --test ;;
|
||||
clear) display_run --clear ;;
|
||||
off) display_off ;;
|
||||
*) usage; exit 2 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo "Aufruf: ninlight-station on|recovery {on|off}|migrate {on [--listen]|off}|arm [<target>]|listen|off|status" >&2
|
||||
echo "Aufruf: ninlight-station on|recovery {on|off}|migrate {on [--listen]|off}|arm [<target>]|listen|display {test|clear|off}|off|status" >&2
|
||||
echo "" >&2
|
||||
echo " display test Testbild aufs Panel zeichnen. Das ist der erste Handgriff bei der" >&2
|
||||
echo " Hardware-Abnahme und die EINZIGE Moeglichkeit, die Panel-Revision" >&2
|
||||
echo " zu bestimmen: die steht am Geraet nirgends. Bleibt das Bild leer," >&2
|
||||
echo " verzerrt oder verschoben, ist EPD_REVISION in" >&2
|
||||
echo " /etc/ninlight-station.conf falsch - auf die andere Revision" >&2
|
||||
echo " umstellen (V2 statt V3 oder umgekehrt), das Panel kurz" >&2
|
||||
echo " stromlos machen und erneut 'display test'." >&2
|
||||
echo " display clear Panel leeren." >&2
|
||||
echo " display off Anzeigedienst stoppen und das Panel leeren (macht 'off' mit)." >&2
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
@@ -467,6 +519,9 @@ case "${1:-}" in
|
||||
listen)
|
||||
listen_cmd
|
||||
;;
|
||||
display)
|
||||
display_cmd "${2:-}"
|
||||
;;
|
||||
off)
|
||||
full_off
|
||||
;;
|
||||
|
||||
+142
-47
@@ -77,8 +77,31 @@ fi
|
||||
rm -f "$TEMPLATE"
|
||||
REMOTE
|
||||
|
||||
echo "== Server, CLI und Unit installieren =="
|
||||
"$SCP_BIN" -q "$HERE/server.py" "$HERE/products.py" "$HERE/ninlight-station" "$HERE/ninlight-station.service" "$HOST:$STAGE_DIR/"
|
||||
echo "== Pakete =="
|
||||
# Pillow kommt auf dem Pi bewusst aus dem Distributionspaket (python3-pil), nicht per
|
||||
# pip: tools/station/ laeuft dort unter dem System-Python 3.13, und Debian 13 laesst ein
|
||||
# "pip install" ins System-Python gar nicht mehr zu (PEP 668). Ohne Pillow zeichnet die
|
||||
# Anzeige nicht - display.py faengt den ImportError ab und beendet sich still mit 0
|
||||
# (Absicht, damit ein fehlendes Panel nie eine Migration verhindert). Genau deshalb muss
|
||||
# das Paket HIER kommen: sonst faellt es nirgends auf.
|
||||
"$SSH_BIN" "$HOST" bash -s <<'REMOTE'
|
||||
set -e
|
||||
if python3 -c 'import PIL' 2>/dev/null; then
|
||||
echo " python3-pil ist bereits vorhanden"
|
||||
else
|
||||
sudo apt-get install -y python3-pil
|
||||
echo " python3-pil installiert"
|
||||
fi
|
||||
REMOTE
|
||||
|
||||
echo "== Server, CLI und Units installieren =="
|
||||
"$SCP_BIN" -q "$HERE/server.py" "$HERE/products.py" "$HERE/ninlight-station" \
|
||||
"$HERE/ninlight-station.service" "$HERE/ninlight-display.service" "$HOST:$STAGE_DIR/"
|
||||
# Das Anzeigeverzeichnis als Ganzes: render.py, display.py, epdconfig.py, die beiden
|
||||
# uebernommenen Panel-Treiber und HERKUNFT.md (der Herkunftsnachweis gehoert mit auf den
|
||||
# Pi). "scp -r" statt rsync, weil setup_pi.sh - anders als deploy.sh - ausdruecklich nur
|
||||
# ssh/scp voraussetzt; __pycache__ wird auf der Gegenseite weggeraeumt (unten).
|
||||
"$SCP_BIN" -q -r "$HERE/display" "$HOST:$STAGE_DIR/ninlight-display"
|
||||
# Pfade als Variablen mit Default (wie in deploy.sh): so kann ein Test die
|
||||
# Installation gegen ein Sandbox-Verzeichnis statt gegen echte Systempfade laufen
|
||||
# lassen.
|
||||
@@ -88,17 +111,40 @@ STAGE_DIR=${NINLIGHT_STAGE_DIR:-/tmp}
|
||||
ROOT_DIR=${NINLIGHT_STATION_ROOT:-/opt/ninlight-station}
|
||||
BIN_PATH=${NINLIGHT_STATION_BIN:-/usr/local/bin/ninlight-station}
|
||||
UNIT_PATH=${NINLIGHT_STATION_UNIT:-/etc/systemd/system/ninlight-station.service}
|
||||
sudo install -d -m 0755 "$ROOT_DIR" "$ROOT_DIR/images"
|
||||
DISPLAY_UNIT_PATH=${NINLIGHT_DISPLAY_UNIT:-/etc/systemd/system/ninlight-display.service}
|
||||
sudo install -d -m 0755 "$ROOT_DIR" "$ROOT_DIR/images" "$ROOT_DIR/display"
|
||||
sudo install -m 0755 "$STAGE_DIR/server.py" "$ROOT_DIR/server.py"
|
||||
sudo install -m 0644 "$STAGE_DIR/products.py" "$ROOT_DIR/products.py"
|
||||
sudo install -m 0755 "$STAGE_DIR/ninlight-station" "$BIN_PATH"
|
||||
sudo install -m 0644 "$STAGE_DIR/ninlight-station.service" "$UNIT_PATH"
|
||||
rm -f "$STAGE_DIR/server.py" "$STAGE_DIR/products.py" "$STAGE_DIR/ninlight-station" "$STAGE_DIR/ninlight-station.service"
|
||||
# Anzeige: display.py ist ausfuehrbar (die Unit ruft es zwar ueber /usr/bin/python3 auf,
|
||||
# "ninlight-station display test" aber auch von Hand), der Rest ist importierter Code.
|
||||
rm -rf "$STAGE_DIR/ninlight-display/__pycache__"
|
||||
for f in "$STAGE_DIR"/ninlight-display/*; do
|
||||
[ -f "$f" ] || continue
|
||||
case "$(basename "$f")" in
|
||||
display.py) sudo install -m 0755 "$f" "$ROOT_DIR/display/$(basename "$f")" ;;
|
||||
*) sudo install -m 0644 "$f" "$ROOT_DIR/display/$(basename "$f")" ;;
|
||||
esac
|
||||
done
|
||||
sudo install -m 0644 "$STAGE_DIR/ninlight-display.service" "$DISPLAY_UNIT_PATH"
|
||||
rm -rf "$STAGE_DIR/server.py" "$STAGE_DIR/products.py" "$STAGE_DIR/ninlight-station" \
|
||||
"$STAGE_DIR/ninlight-station.service" "$STAGE_DIR/ninlight-display.service" \
|
||||
"$STAGE_DIR/ninlight-display"
|
||||
sudo systemctl daemon-reload
|
||||
echo " installiert"
|
||||
# Die Anzeige wird aktiviert (startet also beim Booten mit) UND jetzt gestartet - anders
|
||||
# als die Station selbst, die absichtlich aus bleibt: die Anzeige sendet nichts, sie
|
||||
# zeigt nur an, und "Station aus" ist genau das, was nach dem Einrichten auf dem Panel
|
||||
# stehen soll. Fehlt Panel oder Pillow, beendet sich der Dienst still mit 0 (siehe
|
||||
# display.py) - deshalb "|| true": ein fehlendes Panel darf setup_pi.sh nicht abbrechen.
|
||||
sudo systemctl enable ninlight-display.service >/dev/null 2>&1 || \
|
||||
echo " Warnung: 'systemctl enable ninlight-display' fehlgeschlagen" >&2
|
||||
sudo systemctl restart ninlight-display.service >/dev/null 2>&1 || \
|
||||
echo " Warnung: 'systemctl restart ninlight-display' fehlgeschlagen" >&2
|
||||
echo " installiert (Anzeigedienst aktiviert)"
|
||||
REMOTE
|
||||
|
||||
echo "== NetworkManager-Profil =="
|
||||
echo "== NetworkManager-Profile =="
|
||||
# Zwei Dinge, die man an einer echten Lampenstation nicht falsch machen darf:
|
||||
# - /etc/ninlight-station.conf ist 0600 root:root (das Passwort soll nicht
|
||||
# jeder lesen koennen). Der SSH-Benutzer ist aber nicht root, direktes
|
||||
@@ -116,47 +162,91 @@ set -e
|
||||
CONF=${NINLIGHT_STATION_CONF:-/etc/ninlight-station.conf}
|
||||
NM_DIR=${NINLIGHT_NM_DIR:-/etc/NetworkManager/system-connections}
|
||||
eval "$(sudo cat "$CONF")"
|
||||
CONN_FILE="$NM_DIR/${AP_CONNECTION}.nmconnection"
|
||||
if sudo test -f "$CONN_FILE"; then
|
||||
UUID=$(sudo sed -n 's/^uuid=//p' "$CONN_FILE" | head -n1)
|
||||
ACTION="aktualisiert"
|
||||
else
|
||||
# /proc/sys/kernel/random/uuid gibt es nur unter Linux (dem Pi) - der Fallback
|
||||
# auf uuidgen ist es, was einen Testlauf gegen den lokalen Stub (macOS) ueberhaupt
|
||||
# ermoeglicht, aendert aber nichts am Verhalten auf dem Pi selbst.
|
||||
UUID=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen | tr '[:upper:]' '[:lower:]')
|
||||
ACTION="angelegt"
|
||||
fi
|
||||
TMP=$(mktemp)
|
||||
trap 'rm -f "$TMP"' EXIT
|
||||
{
|
||||
printf '%s\n' '[connection]'
|
||||
printf 'id=%s\n' "$AP_CONNECTION"
|
||||
printf 'uuid=%s\n' "$UUID"
|
||||
printf '%s\n' 'type=wifi'
|
||||
printf '%s\n' 'interface-name=wlan0'
|
||||
printf '%s\n' 'autoconnect=false'
|
||||
printf '\n%s\n' '[wifi]'
|
||||
printf '%s\n' 'mode=ap'
|
||||
printf 'ssid=%s\n' "$SSID"
|
||||
printf '%s\n' 'band=bg'
|
||||
printf '%s\n' 'channel=6'
|
||||
printf '\n%s\n' '[wifi-security]'
|
||||
printf '%s\n' 'key-mgmt=wpa-psk'
|
||||
printf '%s\n' 'proto=rsn'
|
||||
printf '%s\n' 'pairwise=ccmp;'
|
||||
printf '%s\n' 'group=ccmp;'
|
||||
printf 'psk=%s\n' "$PSK"
|
||||
printf '\n%s\n' '[ipv4]'
|
||||
printf '%s\n' 'method=shared'
|
||||
printf '%s\n' 'address1=192.168.8.1/24'
|
||||
printf '\n%s\n' '[ipv6]'
|
||||
printf '%s\n' 'method=disabled'
|
||||
} >"$TMP"
|
||||
sudo install -o root -g root -m 0600 "$TMP" "$CONN_FILE"
|
||||
rm -f "$TMP"
|
||||
sudo nmcli connection load "$CONN_FILE" >/dev/null
|
||||
echo " Profil $AP_CONNECTION $ACTION"
|
||||
|
||||
# Die Keyfiles werden erst in einer Temp-Datei aufgebaut und dann per "install"
|
||||
# an ihren Platz gelegt - dazwischen liegt das WLAN-Passwort in einer Datei, die
|
||||
# noch nicht 0600 root:root ist. Ein Abbruch (set -e) genau dort darf sie nicht
|
||||
# liegen lassen. Ein EXIT-Trap in write_profile() selbst waere dafuer untauglich:
|
||||
# die Funktion laeuft zweimal, der zweite trap ueberschriebe den ersten, und
|
||||
# "$tmp" ist "local" - beim Auswerten des Traps am Skriptende gaebe es die
|
||||
# Variable gar nicht mehr. Deshalb ein gemeinsames Verzeichnis mit genau einem
|
||||
# Trap darum herum (0700, mktemp -d legt es so an).
|
||||
PROFILE_TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$PROFILE_TMPDIR"' EXIT
|
||||
|
||||
# $1 = Profilname, $2 = SSID, $3 = PSK, $4 = "update" (bestehendes Profil neu schreiben,
|
||||
# UUID behalten) oder "keep" (bestehendes Profil unangetastet lassen).
|
||||
#
|
||||
# Beide Profile bekommen dieselbe Adresse 192.168.8.1/24 auf wlan0 - genau deshalb
|
||||
# schliessen sie sich gegenseitig aus, und genau deshalb prueft ninlight-station's
|
||||
# ap_switch() nach jedem Umschalten nach, dass wirklich nur eines aktiv ist.
|
||||
write_profile() {
|
||||
local id=$1 ssid=$2 psk=$3 mode=$4
|
||||
# Ein leerer Profilname wuerde eine Datei ".nmconnection" anlegen, eine leere SSID
|
||||
# oder ein leeres PSK ein Profil, das NetworkManager beim Hochfahren stumm ablehnt -
|
||||
# in beiden Faellen faellt es erst auf, wenn jemand mit einer toten Lampe vor der
|
||||
# Station steht. Der Konfigurationsschritt oben ergaenzt fehlende Schluessel aus der
|
||||
# Vorlage; leer sein koennen sie trotzdem (von Hand geleert).
|
||||
if [ -z "$id" ] || [ -z "$ssid" ] || [ -z "$psk" ]; then
|
||||
echo "Fehler: Profilname, SSID oder PSK ist leer - $CONF pruefen (Profil '$id')" >&2
|
||||
return 1
|
||||
fi
|
||||
local conn_file="$NM_DIR/${id}.nmconnection"
|
||||
local uuid action tmp
|
||||
if sudo test -f "$conn_file"; then
|
||||
if [ "$mode" = "keep" ]; then
|
||||
# Der Migrations-AP ist auf manchen Pis von Hand angelegt worden (SSID/PSK sind
|
||||
# von der alten Klyqa-Firmware fest vorgegeben, es gibt also nichts zu variieren).
|
||||
# Ein bestehendes Profil wird deshalb nicht ueberschrieben - es koennte
|
||||
# handjustierte Felder (Kanal, Band, Regulierungsdomaene) tragen, die dieses
|
||||
# Skript gar nicht kennt.
|
||||
echo " Profil $id existiert bereits - unveraendert gelassen"
|
||||
return 0
|
||||
fi
|
||||
uuid=$(sudo sed -n 's/^uuid=//p' "$conn_file" | head -n1)
|
||||
action="aktualisiert"
|
||||
else
|
||||
# /proc/sys/kernel/random/uuid gibt es nur unter Linux (dem Pi) - der Fallback
|
||||
# auf uuidgen ist es, was einen Testlauf gegen den lokalen Stub (macOS) ueberhaupt
|
||||
# ermoeglicht, aendert aber nichts am Verhalten auf dem Pi selbst.
|
||||
uuid=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen | tr '[:upper:]' '[:lower:]')
|
||||
action="angelegt"
|
||||
fi
|
||||
tmp=$(mktemp "$PROFILE_TMPDIR/profile.XXXXXX")
|
||||
{
|
||||
printf '%s\n' '[connection]'
|
||||
printf 'id=%s\n' "$id"
|
||||
printf 'uuid=%s\n' "$uuid"
|
||||
printf '%s\n' 'type=wifi'
|
||||
printf '%s\n' 'interface-name=wlan0'
|
||||
printf '%s\n' 'autoconnect=false'
|
||||
printf '\n%s\n' '[wifi]'
|
||||
printf '%s\n' 'mode=ap'
|
||||
printf 'ssid=%s\n' "$ssid"
|
||||
printf '%s\n' 'band=bg'
|
||||
printf '%s\n' 'channel=6'
|
||||
printf '\n%s\n' '[wifi-security]'
|
||||
printf '%s\n' 'key-mgmt=wpa-psk'
|
||||
printf '%s\n' 'proto=rsn'
|
||||
printf '%s\n' 'pairwise=ccmp;'
|
||||
printf '%s\n' 'group=ccmp;'
|
||||
printf 'psk=%s\n' "$psk"
|
||||
printf '\n%s\n' '[ipv4]'
|
||||
printf '%s\n' 'method=shared'
|
||||
printf '%s\n' 'address1=192.168.8.1/24'
|
||||
printf '\n%s\n' '[ipv6]'
|
||||
printf '%s\n' 'method=disabled'
|
||||
} >"$tmp"
|
||||
sudo install -o root -g root -m 0600 "$tmp" "$conn_file"
|
||||
rm -f "$tmp"
|
||||
sudo nmcli connection load "$conn_file" >/dev/null
|
||||
echo " Profil $id $action"
|
||||
}
|
||||
|
||||
write_profile "$AP_CONNECTION" "$SSID" "$PSK" update
|
||||
# Ohne dieses zweite Profil scheitert "ninlight-station migrate on" auf einem frisch
|
||||
# eingerichteten Pi an need_profile "$MIGRATE_AP_CONNECTION".
|
||||
write_profile "$MIGRATE_AP_CONNECTION" "$MIGRATE_SSID" "$MIGRATE_PSK" keep
|
||||
REMOTE
|
||||
|
||||
cat <<'EOF'
|
||||
@@ -164,6 +254,11 @@ cat <<'EOF'
|
||||
Fertig. Die Station ist absichtlich noch AUS.
|
||||
|
||||
Naechste Schritte:
|
||||
ssh <pi-host> ninlight-station display test
|
||||
Testbild aufs E-Paper - bleibt es leer oder
|
||||
verzerrt, steht EPD_REVISION in
|
||||
/etc/ninlight-station.conf auf der falschen
|
||||
Panel-Revision (V2 statt V3 oder umgekehrt)
|
||||
tools/station/deploy.sh <pi-host> Images ausliefern
|
||||
ssh <pi-host> ninlight-station on Station einschalten
|
||||
ssh <pi-host> ninlight-station off wieder ausschalten (nicht vergessen)
|
||||
|
||||
@@ -164,9 +164,31 @@ EOF
|
||||
setup_sandbox() {
|
||||
local sandbox=$1 unitfile=${2:-$REAL_UNIT}
|
||||
rm -rf "$sandbox"
|
||||
mkdir -p "$sandbox/run" "$sandbox/images/migrate" "$sandbox/bin" "$sandbox/nmstate"
|
||||
mkdir -p "$sandbox/run" "$sandbox/images/migrate" "$sandbox/bin" "$sandbox/nmstate" \
|
||||
"$sandbox/lib/display"
|
||||
echo "dummy" >"$sandbox/images/ninlight_foo_ota.img"
|
||||
|
||||
# NINLIGHT_STATION_LIB zeigt auf diese Sandbox statt auf tools/station selbst.
|
||||
# products.py wird gebraucht (arm prueft das Ziel dagegen) und deshalb echt kopiert;
|
||||
# display/display.py ist ein Rekorder, der seine Argumente in DIESELBE cmd.log
|
||||
# schreibt wie die nmcli-/systemctl-Stubs. Nur so laesst sich die REIHENFOLGE
|
||||
# "erst Dienst stoppen, dann Panel leeren" ueberhaupt pruefen - mit zwei getrennten
|
||||
# Logs waere sie nicht rekonstruierbar.
|
||||
#
|
||||
# Der Rekorder ersetzt das echte display.py absichtlich: geprueft wird hier, WAS
|
||||
# ninlight-station aufruft (Pfad, Schalter, Reihenfolge, Umgang mit dem Rueckgabe-
|
||||
# wert), nicht was display.py daraus macht - das deckt tests/test_render.py ab.
|
||||
cp "$STATION_DIR/products.py" "$sandbox/lib/products.py"
|
||||
cat >"$sandbox/lib/display/display.py" <<EOF
|
||||
import os
|
||||
import sys
|
||||
|
||||
with open("$sandbox/cmd.log", "a") as fh:
|
||||
fh.write("STUB display.py %s (EPD_REVISION=%s)\n"
|
||||
% (" ".join(sys.argv[1:]), os.environ.get("EPD_REVISION", "<unset>")))
|
||||
sys.exit(int(os.environ.get("FAKE_DISPLAY_EXIT", "0")))
|
||||
EOF
|
||||
|
||||
cat >"$sandbox/station.conf" <<EOF
|
||||
SSID='test-recovery'
|
||||
PSK='test-psk'
|
||||
@@ -192,7 +214,7 @@ run_cli() {
|
||||
PATH="$sandbox/bin:$PATH" \
|
||||
NINLIGHT_STATION_CONF="$sandbox/station.conf" \
|
||||
NINLIGHT_RUN_DIR="$sandbox/run" \
|
||||
NINLIGHT_STATION_LIB="$STATION_DIR" \
|
||||
NINLIGHT_STATION_LIB="$sandbox/lib" \
|
||||
"$CLI" "$@"
|
||||
}
|
||||
|
||||
@@ -549,6 +571,157 @@ echo "$OUT" | grep -qE "Modus[[:space:]]*: -" && pass "Modus zeigt '-' (keine st
|
||||
echo "$OUT" | grep -qi "armed" && fail "status zeigt 'armed', obwohl der Modus gar nicht bekannt ist: $OUT" || pass "keine 'armed'-Phase bei unbekanntem Modus"
|
||||
echo "$OUT" | grep -qE "Phase[[:space:]]*: -" && pass "Phase zeigt '-' statt einer Vermutung" || fail "Phase zeigt keinen neutralen Platzhalter: $OUT"
|
||||
|
||||
echo
|
||||
echo "=== Fall D1 (Task 6b, Punkt 3): 'display test' ruft display.py mit --test und der konfigurierten Revision auf ==="
|
||||
SBX="$TMPROOT/tD1"
|
||||
setup_sandbox "$SBX"
|
||||
: >"$SBX/cmd.log"
|
||||
set +e
|
||||
OUT=$(run_cli "$SBX" display test 2>&1)
|
||||
RC=$?
|
||||
set -e
|
||||
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
||||
grep -Fq "STUB display.py --test" "$SBX/cmd.log" && pass "display.py wurde mit --test aufgerufen" || fail "kein '--test'-Aufruf im Log: $(cat "$SBX/cmd.log")"
|
||||
# Der Testbild-Aufruf ist die einzige Moeglichkeit, die Panel-Revision zu bestimmen -
|
||||
# er ist wertlos, wenn er nicht die Revision aus der Konfiguration durchreicht:
|
||||
# EPD_REVISION wird per ". $CONF" nur als Shell-Variable gesetzt, nicht exportiert.
|
||||
grep -Fq "STUB display.py --test (EPD_REVISION=V3)" "$SBX/cmd.log" && pass "EPD_REVISION aus der Konfiguration erreicht den Python-Prozess" || fail "EPD_REVISION kam nicht durch: $(cat "$SBX/cmd.log")"
|
||||
# Nicht gleichzeitig den Anzeigedienst umbringen: 'display test' ist ein Handgriff bei
|
||||
# der Abnahme, kein Ausschalten.
|
||||
grep -Fq "systemctl stop ninlight-display" "$SBX/cmd.log" && fail "'display test' stoppt den Anzeigedienst - das soll nur 'display off'/'off'" || pass "'display test' laesst den Anzeigedienst in Ruhe"
|
||||
|
||||
echo
|
||||
echo "=== Fall D2: 'display clear' ruft display.py mit --clear auf ==="
|
||||
SBX="$TMPROOT/tD2"
|
||||
setup_sandbox "$SBX"
|
||||
: >"$SBX/cmd.log"
|
||||
set +e
|
||||
OUT=$(run_cli "$SBX" display clear 2>&1)
|
||||
RC=$?
|
||||
set -e
|
||||
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
||||
grep -Fq "STUB display.py --clear" "$SBX/cmd.log" && pass "display.py wurde mit --clear aufgerufen" || fail "kein '--clear'-Aufruf im Log: $(cat "$SBX/cmd.log")"
|
||||
grep -Fq "STUB display.py --test" "$SBX/cmd.log" && fail "'display clear' hat --test aufgerufen" || pass "kein --test bei 'display clear'"
|
||||
|
||||
echo
|
||||
echo "=== Fall D3 (Task 6b, Punkt 4): 'display off' stoppt den Dienst VOR dem Leeren des Panels ==="
|
||||
# Umgekehrte Reihenfolge waere wirkungslos: der Daemon fragt alle 5 s nach und
|
||||
# schriebe sein altes Bild binnen dieser Zeit wieder aufs Panel.
|
||||
SBX="$TMPROOT/tD3"
|
||||
setup_sandbox "$SBX"
|
||||
: >"$SBX/cmd.log"
|
||||
set +e
|
||||
OUT=$(run_cli "$SBX" display off 2>&1)
|
||||
RC=$?
|
||||
set -e
|
||||
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
||||
STOP_LINE=$(grep -n "STUB systemctl stop ninlight-display" "$SBX/cmd.log" | head -n1 | cut -d: -f1)
|
||||
CLEAR_LINE=$(grep -n "STUB display.py --clear" "$SBX/cmd.log" | head -n1 | cut -d: -f1)
|
||||
if [ -n "$STOP_LINE" ] && [ -n "$CLEAR_LINE" ] && [ "$STOP_LINE" -lt "$CLEAR_LINE" ]; then
|
||||
pass "Anzeigedienst wird gestoppt, BEVOR das Panel geleert wird (stop=$STOP_LINE, clear=$CLEAR_LINE)"
|
||||
else
|
||||
fail "Reihenfolge falsch oder Aufruf fehlt (stop=$STOP_LINE, clear=$CLEAR_LINE): $(cat "$SBX/cmd.log")"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "=== Fall D4 (Task 6b, Punkt 4): 'off' nimmt die Anzeige mit - Dienst gestoppt, Panel geleert ==="
|
||||
# Ohne das behauptet das stehengebliebene Bild einen Betriebszustand ("MIGRATION"),
|
||||
# den es nicht mehr gibt - bei einer Station, die Lampen ungefragt Firmware
|
||||
# unterschieben kann, die gefaehrliche Richtung.
|
||||
SBX="$TMPROOT/tD4"
|
||||
setup_sandbox "$SBX"
|
||||
echo "dummy" >"$SBX/images/migrate/gu10_rgb_prod.img"
|
||||
run_cli "$SBX" migrate on >/dev/null 2>&1
|
||||
: >"$SBX/cmd.log"
|
||||
set +e
|
||||
OUT=$(run_cli "$SBX" off 2>&1)
|
||||
RC=$?
|
||||
set -e
|
||||
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
||||
grep -Fq "STUB systemctl stop ninlight-display" "$SBX/cmd.log" && pass "'off' stoppt den Anzeigedienst" || fail "'off' stoppt den Anzeigedienst nicht: $(cat "$SBX/cmd.log")"
|
||||
grep -Fq "STUB display.py --clear" "$SBX/cmd.log" && pass "'off' leert das Panel" || fail "'off' leert das Panel nicht: $(cat "$SBX/cmd.log")"
|
||||
STOP_LINE=$(grep -n "STUB systemctl stop ninlight-display" "$SBX/cmd.log" | head -n1 | cut -d: -f1)
|
||||
CLEAR_LINE=$(grep -n "STUB display.py --clear" "$SBX/cmd.log" | head -n1 | cut -d: -f1)
|
||||
if [ -n "$STOP_LINE" ] && [ -n "$CLEAR_LINE" ] && [ "$STOP_LINE" -lt "$CLEAR_LINE" ]; then
|
||||
pass "auch bei 'off': erst stoppen, dann leeren"
|
||||
else
|
||||
fail "Reihenfolge bei 'off' falsch (stop=$STOP_LINE, clear=$CLEAR_LINE): $(cat "$SBX/cmd.log")"
|
||||
fi
|
||||
# Das Panel muss geleert sein, BEVOR ap_switch das Ergebnis von "off" verkuenden kann -
|
||||
# und vor allem, bevor ap_switch bei haengendem NetworkManager abbricht (Fall D6).
|
||||
UP_LINE=$(grep -n "nmcli con up home-wifi" "$SBX/cmd.log" | head -n1 | cut -d: -f1)
|
||||
if [ -n "$UP_LINE" ] && [ "$CLEAR_LINE" -lt "$UP_LINE" ]; then
|
||||
pass "Panel ist geleert, bevor wlan0 ins Heim-WLAN zurueckgeschaltet wird"
|
||||
else
|
||||
fail "Panel wurde erst nach dem Zurueckschalten geleert (clear=$CLEAR_LINE, up=$UP_LINE)"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "=== Fall D5: ein Fehler beim Leeren des Panels haelt 'off' nicht auf ==="
|
||||
# Ein fehlendes/kaputtes Panel darf das Ausschalten der Station nie blockieren -
|
||||
# sonst bliebe ausgerechnet der Zugangspunkt an, weil die Anzeige klemmt.
|
||||
SBX="$TMPROOT/tD5"
|
||||
setup_sandbox "$SBX"
|
||||
: >"$SBX/cmd.log"
|
||||
set +e
|
||||
OUT=$(FAKE_DISPLAY_EXIT=1 run_cli "$SBX" off 2>"$SBX/stderr.log")
|
||||
RC=$?
|
||||
set -e
|
||||
ERR=$(cat "$SBX/stderr.log")
|
||||
[ "$RC" -eq 0 ] && pass "Exit-Code 0 trotz fehlgeschlagenem Leeren" || fail "Exit-Code $RC, stderr: $ERR"
|
||||
echo "$ERR" | grep -q "Warnung" && pass "Warnung ueber das nicht geleerte Panel steht auf stderr" || fail "keine Warnung auf stderr: $ERR"
|
||||
grep -Fq "nmcli con up home-wifi" "$SBX/cmd.log" && pass "'off' hat trotzdem bis zum Zurueckschalten ins Heim-WLAN durchgezogen" || fail "'off' ist am Anzeigefehler haengengeblieben: $(cat "$SBX/cmd.log")"
|
||||
|
||||
echo
|
||||
echo "=== Fall D6: haengender NetworkManager - das Panel wird trotzdem noch geleert ==="
|
||||
# ap_switch "" bricht hier mit Fehler ab; unter "set -e" laeuft danach nichts mehr.
|
||||
# Genau deshalb steht display_off VOR ap_switch: sonst bliebe ein "MIGRATION"-Bild
|
||||
# stehen, waehrend der Zugangspunkt noch sendet und niemand mehr etwas abschaltet.
|
||||
SBX="$TMPROOT/tD6"
|
||||
setup_sandbox "$SBX"
|
||||
echo "dummy" >"$SBX/images/migrate/gu10_rgb_prod.img"
|
||||
run_cli "$SBX" migrate on >/dev/null 2>&1
|
||||
: >"$SBX/cmd.log"
|
||||
set +e
|
||||
OUT=$(FAKE_NMCLI_STUCK=1 run_cli "$SBX" off 2>"$SBX/stderr.log")
|
||||
RC=$?
|
||||
set -e
|
||||
ERR=$(cat "$SBX/stderr.log")
|
||||
[ "$RC" -ne 0 ] && pass "Exit-Code != 0 (der Zugangspunkt liess sich nicht abschalten)" || fail "Exit-Code 0, obwohl der Zugangspunkt noch aktiv ist: $OUT"
|
||||
grep -Fq "STUB display.py --clear" "$SBX/cmd.log" && pass "Panel wurde trotz haengendem NetworkManager geleert" || fail "Panel blieb stehen, waehrend der Zugangspunkt noch sendet: $(cat "$SBX/cmd.log")"
|
||||
|
||||
echo
|
||||
echo "=== Fall D7: 'display' ohne/mit unbekanntem Unterbefehl -> Hilfetext, Exit 2 ==="
|
||||
SBX="$TMPROOT/tD7"
|
||||
setup_sandbox "$SBX"
|
||||
# Anlegen, bevor geprueft wird: eine fehlende Logdatei wuerde die "kein Aufruf"-Zusage
|
||||
# unten sonst durch ein grep-Versagen erfuellen statt durch das Verhalten des Skripts.
|
||||
: >"$SBX/cmd.log"
|
||||
for ARG in "" "quatsch"; do
|
||||
set +e
|
||||
# shellcheck disable=SC2086
|
||||
OUT=$(run_cli "$SBX" display $ARG 2>&1)
|
||||
RC=$?
|
||||
set -e
|
||||
[ "$RC" -eq 2 ] && pass "Exit-Code 2 fuer 'display ${ARG:-<leer>}'" || fail "Exit-Code $RC fuer 'display ${ARG:-<leer>}': $OUT"
|
||||
echo "$OUT" | grep -q "display {test|clear|off}" && pass "Hilfetext nennt die Unterbefehle ('display ${ARG:-<leer>}')" || fail "Hilfetext fehlt: $OUT"
|
||||
done
|
||||
grep -Fq "STUB display.py" "$SBX/cmd.log" && fail "ein unbekannter Unterbefehl hat display.py trotzdem aufgerufen" || pass "kein display.py-Aufruf bei unbekanntem Unterbefehl"
|
||||
|
||||
echo
|
||||
echo "=== Fall D8 (Task 6b, Punkt 3): der Hilfetext erklaert die Revisionsbestimmung ==="
|
||||
# Die Panel-Revision steht am Geraet nirgends; 'display test' ist die einzige
|
||||
# Moeglichkeit, sie zu bestimmen, und das muss dort stehen, wo man beim ersten
|
||||
# Handgriff der Hardware-Abnahme hinsieht.
|
||||
SBX="$TMPROOT/tD8"
|
||||
setup_sandbox "$SBX"
|
||||
set +e
|
||||
HELP=$(run_cli "$SBX" quatsch 2>&1)
|
||||
set -e
|
||||
echo "$HELP" | grep -q "EPD_REVISION" && pass "Hilfetext nennt EPD_REVISION" || fail "Hilfetext nennt EPD_REVISION nicht: $HELP"
|
||||
echo "$HELP" | grep -q "ninlight-station.conf" && pass "Hilfetext nennt die Datei, in der die Revision steht" || fail "Hilfetext nennt /etc/ninlight-station.conf nicht: $HELP"
|
||||
echo "$HELP" | grep -qE "V2|V3" && pass "Hilfetext nennt die beiden moeglichen Revisionen" || fail "Hilfetext nennt V2/V3 nicht: $HELP"
|
||||
|
||||
echo
|
||||
if [ "$FAILED" -eq 0 ]; then
|
||||
echo "Alle Tests bestanden."
|
||||
|
||||
@@ -51,6 +51,17 @@ setup_sandbox() {
|
||||
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"
|
||||
# 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"
|
||||
|
||||
# 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
|
||||
@@ -111,9 +122,22 @@ 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
|
||||
;;
|
||||
esac
|
||||
case "\$1" in
|
||||
is-active) exit "\${FAKE_SYSTEMCTL_ACTIVE_EXIT:-1}" ;;
|
||||
restart) exit "\${FAKE_SYSTEMCTL_RESTART_EXIT:-0}" ;;
|
||||
@@ -146,6 +170,7 @@ run_deploy() {
|
||||
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_SIGN_DIR="$sign_dir" \
|
||||
"$sandbox/tools/station/deploy.sh" "$@"
|
||||
}
|
||||
@@ -948,6 +973,89 @@ for label in truncated empty array null string null-entry; do
|
||||
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
|
||||
if [ "$FAILED" -eq 0 ]; then
|
||||
echo "Alle Tests bestanden."
|
||||
|
||||
@@ -55,12 +55,41 @@ echo "STUB scp \$*" >>"$log"
|
||||
args=()
|
||||
for a in "\$@"; do
|
||||
case "\$a" in
|
||||
# "-r" muss durchgereicht werden (als cp -R): setup_pi.sh uebertraegt das
|
||||
# Anzeigeverzeichnis als Ganzes, und ein Stub, der das Flag verschluckt, laesst
|
||||
# "cp" an einem Verzeichnis scheitern - der Test wuerde dann den Stub anzeigen,
|
||||
# nicht das Skript. Alle anderen Optionen (-q) bleiben weiterhin draussen.
|
||||
-r|-R) args+=("-R") ;;
|
||||
-*) continue ;;
|
||||
*:*) args+=("\${a#*:}") ;;
|
||||
*) args+=("\$a") ;;
|
||||
esac
|
||||
done
|
||||
exec cp "\${args[@]}"
|
||||
EOF
|
||||
|
||||
# apt-get gibt es auf dem Entwicklungsrechner nicht - und selbst auf einem Debian
|
||||
# duerfte ein Test es nie wirklich aufrufen. Der Stub protokolliert nur.
|
||||
cat >"$bindir/apt-get" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
echo "STUB apt-get \$*" >>"$log"
|
||||
exit "\${FAKE_APT_EXIT:-0}"
|
||||
EOF
|
||||
|
||||
# Steht fuer das python3 DES PI, nicht fuer das des Testrechners: nur die
|
||||
# Pillow-Probe wird beantwortet (steuerbar per FAKE_PIL_MISSING - auf diesem Rechner
|
||||
# ist Pillow sonst je nach Installation zufaellig vorhanden oder nicht, und der
|
||||
# Paketschritt waere damit gar nicht pruefbar). Jeder andere Aufruf laeuft durch das
|
||||
# echte python3.
|
||||
cat >"$bindir/python3" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
echo "STUB python3 \$*" >>"$log"
|
||||
for a in "\$@"; do
|
||||
case "\$a" in
|
||||
*"import PIL"*) exit "\${FAKE_PIL_MISSING:-0}" ;;
|
||||
esac
|
||||
done
|
||||
exec "$(command -v python3)" "\$@"
|
||||
EOF
|
||||
|
||||
# "sudo install -o root -g root ..." (im NetworkManager-Keyfile-Schritt) braucht
|
||||
@@ -104,7 +133,8 @@ fi
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
chmod +x "$bindir"/ssh "$bindir"/scp "$bindir"/sudo "$bindir"/systemctl "$bindir"/nmcli
|
||||
chmod +x "$bindir"/ssh "$bindir"/scp "$bindir"/sudo "$bindir"/systemctl "$bindir"/nmcli \
|
||||
"$bindir"/apt-get "$bindir"/python3
|
||||
}
|
||||
|
||||
# $1 = Sandbox-Wurzel fuer die "Systempfade"
|
||||
@@ -133,6 +163,7 @@ run_setup() {
|
||||
NINLIGHT_STATION_ROOT="$state/opt/ninlight-station" \
|
||||
NINLIGHT_STATION_BIN="$state/usr/local/bin/ninlight-station" \
|
||||
NINLIGHT_STATION_UNIT="$state/etc/systemd/system/ninlight-station.service" \
|
||||
NINLIGHT_DISPLAY_UNIT="$state/etc/systemd/system/ninlight-display.service" \
|
||||
NINLIGHT_NM_DIR="$state/etc/NetworkManager/system-connections" \
|
||||
"$STATION_DIR/setup_pi.sh" pi-test
|
||||
}
|
||||
@@ -247,6 +278,158 @@ for KEY in MIGRATE_AP_CONNECTION MIGRATE_SSID MIGRATE_PSK EPD_REVISION; do
|
||||
grep -Eq "^${KEY}=" "$CONF_OUT4" && pass "fehlender Schluessel $KEY wurde trotzdem ergaenzt" || fail "Schluessel $KEY fehlt weiterhin"
|
||||
done
|
||||
|
||||
echo
|
||||
echo "=== Szenario 5 (Task 6b, Punkt 1): Anzeige wird ausgeliefert, installiert und aktiviert ==="
|
||||
STATE="$TMPROOT/s5-state"; STUBBIN="$TMPROOT/bin5"; LOG="$TMPROOT/s5.log"
|
||||
setup_state "$STATE"
|
||||
setup_stubbin "$STUBBIN" "$LOG"
|
||||
: >"$LOG"
|
||||
set +e
|
||||
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
||||
RC=$?
|
||||
set -e
|
||||
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
||||
DISP="$STATE/opt/ninlight-station/display"
|
||||
# Die Panel-Treiber und der Herkunftsnachweis gehoeren mit auf den Pi, nicht nur ins
|
||||
# Repo: HERKUNFT.md ist der Nachweis, woher der uebernommene Waveshare-Code stammt.
|
||||
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
|
||||
[ -x "$DISP/display.py" ] && pass "display.py ist ausfuehrbar (Handbetrieb 'display test')" || fail "display.py ist nicht ausfuehrbar"
|
||||
MODE=$(file_mode "$DISP/render.py")
|
||||
[ "$MODE" = "644" ] && pass "importierter Code ist 0644" || fail "render.py hat Rechte $MODE statt 644"
|
||||
[ ! -d "$DISP/__pycache__" ] && pass "kein __pycache__ auf dem Pi gelandet" || fail "__pycache__ wurde mitkopiert"
|
||||
[ -f "$STATE/etc/systemd/system/ninlight-display.service" ] && pass "ninlight-display.service installiert" || fail "Anzeige-Unit fehlt - die Unit waere weiter toter Text im Repo"
|
||||
# Ohne "enable" startet die Anzeige nach einem Stromausfall nicht wieder mit.
|
||||
grep -Fq "systemctl enable ninlight-display.service" "$LOG" && pass "Anzeigedienst wurde aktiviert" || fail "kein 'systemctl enable ninlight-display' im Log"
|
||||
grep -Fq "systemctl restart ninlight-display.service" "$LOG" && pass "Anzeigedienst wurde gestartet" || fail "kein 'systemctl restart ninlight-display' im Log"
|
||||
# Die Station selbst bleibt nach dem Einrichten absichtlich AUS - nur die Anzeige
|
||||
# laeuft. Ein versehentliches "enable ninlight-station" waere ein Sicherheitsfehler:
|
||||
# der Zugangspunkt kaeme nach jedem Boot von allein hoch.
|
||||
grep -Fq "systemctl enable ninlight-station" "$LOG" && fail "setup_pi.sh aktiviert den Stationsdienst - der Zugangspunkt kaeme nach jedem Boot von allein hoch" || pass "Stationsdienst bleibt deaktiviert (Station ist nach dem Einrichten aus)"
|
||||
|
||||
echo
|
||||
echo "=== Szenario 6 (Task 6b, Punkt 1): python3-pil wird nachgezogen, wenn Pillow fehlt ==="
|
||||
# Ohne Pillow beendet sich der Anzeigedienst still mit 0 (Absicht) - das faellt
|
||||
# nirgends auf, also muss das Paket hier kommen.
|
||||
STATE="$TMPROOT/s6-state"; STUBBIN="$TMPROOT/bin6"; LOG="$TMPROOT/s6.log"
|
||||
setup_state "$STATE"
|
||||
setup_stubbin "$STUBBIN" "$LOG"
|
||||
: >"$LOG"
|
||||
set +e
|
||||
OUT=$(FAKE_PIL_MISSING=1 run_setup "$STATE" "$STUBBIN" 2>&1)
|
||||
RC=$?
|
||||
set -e
|
||||
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
||||
grep -Fq "apt-get install -y python3-pil" "$LOG" && pass "python3-pil wird per apt nachinstalliert" || fail "kein 'apt-get install -y python3-pil' im Log"
|
||||
|
||||
echo
|
||||
echo "=== Szenario 7: ist Pillow schon da, wird apt gar nicht erst bemueht ==="
|
||||
STATE="$TMPROOT/s7-state"; STUBBIN="$TMPROOT/bin7"; LOG="$TMPROOT/s7.log"
|
||||
setup_state "$STATE"
|
||||
setup_stubbin "$STUBBIN" "$LOG"
|
||||
: >"$LOG"
|
||||
set +e
|
||||
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
||||
RC=$?
|
||||
set -e
|
||||
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
||||
grep -Fq "apt-get" "$LOG" && fail "apt-get wurde trotz vorhandenem Pillow aufgerufen" || pass "kein apt-Aufruf, wenn Pillow schon vorhanden ist"
|
||||
|
||||
echo
|
||||
echo "=== Szenario 8 (Task 6b, Punkt 2): das Profil fuer den Migrations-Zugangspunkt wird angelegt ==="
|
||||
# Ohne dieses zweite Profil scheitert "ninlight-station migrate on" auf einem frisch
|
||||
# eingerichteten Pi an need_profile "$MIGRATE_AP_CONNECTION".
|
||||
#
|
||||
# Geprueft wird der Zustand aus Szenario 7 (ein vollstaendiger Lauf auf einem frischen
|
||||
# Pi, $STATE/$LOG zeigen noch dorthin) - deshalb hier kein eigener Lauf.
|
||||
NMDIR="$STATE/etc/NetworkManager/system-connections"
|
||||
MIGRATE_FILE="$NMDIR/qconnex-firmware.nmconnection"
|
||||
[ -f "$MIGRATE_FILE" ] && pass "qconnex-firmware.nmconnection angelegt" || fail "Migrationsprofil fehlt in $NMDIR: $(ls "$NMDIR")"
|
||||
[ -f "$NMDIR/ninlight-recovery.nmconnection" ] && pass "Rettungsprofil weiterhin angelegt" || fail "Rettungsprofil fehlt"
|
||||
# SSID und PSK sind von der alten Klyqa-Firmware fest vorgegeben - weicht eines ab,
|
||||
# findet keine alte Lampe die Migrationsstation.
|
||||
grep -Fqx "ssid=qconnex-firmware" "$MIGRATE_FILE" && pass "SSID ist die von der Klyqa-Firmware vorgegebene" || fail "falsche SSID: $(grep '^ssid=' "$MIGRATE_FILE")"
|
||||
grep -Fqx "psk=qconnex-firmware" "$MIGRATE_FILE" && pass "PSK ist der von der Klyqa-Firmware vorgegebene" || fail "falscher PSK"
|
||||
grep -Fqx "mode=ap" "$MIGRATE_FILE" && pass "Profil ist ein Zugangspunkt" || fail "mode=ap fehlt"
|
||||
grep -Fqx "method=shared" "$MIGRATE_FILE" && pass "ipv4.method ist 'shared'" || fail "method=shared fehlt"
|
||||
# Dieselbe Adresse wie das Rettungsprofil - genau deshalb schliessen sich beide aus,
|
||||
# und genau darauf verlaesst sich ap_switch() in ninlight-station.
|
||||
grep -Fqx "address1=192.168.8.1/24" "$MIGRATE_FILE" && pass "Adresse 192.168.8.1/24 (wie das Rettungsprofil)" || fail "falsche Adresse: $(grep '^address1=' "$MIGRATE_FILE")"
|
||||
grep -Fqx "autoconnect=false" "$MIGRATE_FILE" && pass "kein Selbststart beim Booten" || fail "autoconnect ist nicht false - der Migrations-AP kaeme nach jedem Boot von allein hoch"
|
||||
MODE=$(file_mode "$MIGRATE_FILE")
|
||||
[ "$MODE" = "600" ] && pass "Keyfile ist 0600 (das Passwort steht darin)" || fail "Keyfile hat Rechte $MODE statt 600"
|
||||
# Das Passwort darf nie als Kommandozeilenargument an nmcli gehen (in "ps" sichtbar).
|
||||
grep -Fq "nmcli connection load" "$LOG" && pass "Profil wird per 'connection load' eingelesen" || fail "kein 'nmcli connection load' im Log"
|
||||
grep -Eq "STUB nmcli .*(psk|wifi-sec)" "$LOG" && fail "das Passwort wurde als nmcli-Argument uebergeben - in 'ps' fuer jeden lokalen Benutzer sichtbar" || pass "kein Passwort in einer nmcli-Kommandozeile"
|
||||
|
||||
echo
|
||||
echo "=== Szenario 9 (Task 6b, Punkt 2): ein bestehendes Migrationsprofil bleibt unangetastet ==="
|
||||
STATE="$TMPROOT/s9-state"; STUBBIN="$TMPROOT/bin9"; LOG="$TMPROOT/s9.log"
|
||||
setup_state "$STATE"
|
||||
setup_stubbin "$STUBBIN" "$LOG"
|
||||
NMDIR9="$STATE/etc/NetworkManager/system-connections"
|
||||
# Von Hand angelegt, mit handjustierten Feldern (anderer Kanal), die dieses Skript
|
||||
# gar nicht kennt.
|
||||
cat >"$NMDIR9/qconnex-firmware.nmconnection" <<'NM'
|
||||
[connection]
|
||||
id=qconnex-firmware
|
||||
uuid=11111111-2222-3333-4444-555555555555
|
||||
type=wifi
|
||||
[wifi]
|
||||
mode=ap
|
||||
ssid=qconnex-firmware
|
||||
channel=11
|
||||
NM
|
||||
chmod 0600 "$NMDIR9/qconnex-firmware.nmconnection"
|
||||
BEFORE=$(cat "$NMDIR9/qconnex-firmware.nmconnection")
|
||||
: >"$LOG"
|
||||
set +e
|
||||
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
||||
RC=$?
|
||||
set -e
|
||||
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
||||
if [ "$BEFORE" = "$(cat "$NMDIR9/qconnex-firmware.nmconnection")" ]; then
|
||||
pass "bestehendes Migrationsprofil wurde Byte fuer Byte unveraendert gelassen"
|
||||
else
|
||||
fail "bestehendes Migrationsprofil wurde ueberschrieben: $(cat "$NMDIR9/qconnex-firmware.nmconnection")"
|
||||
fi
|
||||
echo "$OUT" | grep -q "unveraendert gelassen" && pass "Ausgabe sagt, dass das Profil unveraendert blieb" || fail "Ausgabe erwaehnt das nicht: $OUT"
|
||||
# Das Rettungsprofil dagegen wird bewusst neu geschrieben (SSID/PSK koennen sich
|
||||
# geaendert haben) - aber unter Beibehaltung der UUID.
|
||||
[ -f "$NMDIR9/ninlight-recovery.nmconnection" ] && pass "Rettungsprofil trotzdem angelegt" || fail "Rettungsprofil fehlt"
|
||||
|
||||
echo
|
||||
echo "=== Szenario 10: ein leerer Konfigurationswert legt kein kaputtes Profil an ==="
|
||||
STATE="$TMPROOT/s10-state"; STUBBIN="$TMPROOT/bin10"; LOG="$TMPROOT/s10.log"
|
||||
setup_state "$STATE"
|
||||
setup_stubbin "$STUBBIN" "$LOG"
|
||||
cat >"$STATE/etc/ninlight-station.conf" <<'CONF'
|
||||
SSID='eigene-ssid'
|
||||
PSK='eigenes-passwort'
|
||||
IMAGE_DIR='/opt/ninlight-station/images'
|
||||
HOME_CONNECTION='preconfigured'
|
||||
AP_CONNECTION='ninlight-recovery'
|
||||
MIGRATE_SSID='qconnex-firmware'
|
||||
MIGRATE_PSK=''
|
||||
MIGRATE_AP_CONNECTION='qconnex-firmware'
|
||||
EPD_REVISION='V3'
|
||||
CONF
|
||||
chmod 0600 "$STATE/etc/ninlight-station.conf"
|
||||
: >"$LOG"
|
||||
set +e
|
||||
OUT=$(run_setup "$STATE" "$STUBBIN" 2>&1)
|
||||
RC=$?
|
||||
set -e
|
||||
[ "$RC" -ne 0 ] && pass "Exit-Code != 0 bei leerem PSK" || fail "Exit-Code 0 trotz leerem Migrations-PSK: $OUT"
|
||||
echo "$OUT" | grep -qi "leer" && pass "Fehlermeldung nennt den leeren Wert" || fail "Fehlermeldung fehlt: $OUT"
|
||||
NMDIR10="$STATE/etc/NetworkManager/system-connections"
|
||||
if [ -f "$NMDIR10/qconnex-firmware.nmconnection" ]; then
|
||||
fail "trotz leerem PSK wurde ein Migrationsprofil geschrieben"
|
||||
else
|
||||
pass "kein Migrationsprofil mit leerem PSK angelegt"
|
||||
fi
|
||||
|
||||
echo
|
||||
if [ "$FAILED" -eq 0 ]; then
|
||||
echo "Alle Tests bestanden."
|
||||
|
||||
+13
-1
@@ -7,9 +7,21 @@ cmake --build "$ROOT/build/host"
|
||||
ctest --test-dir "$ROOT/build/host" --output-on-failure "$@"
|
||||
|
||||
if python3 -c 'import pytest' 2>/dev/null; then
|
||||
python3 -m pytest "$ROOT/tools/mkimage/tests" "$ROOT/tools/station/tests" -q
|
||||
# tools/mkimage als GANZES Verzeichnis, nicht nur tools/mkimage/tests: die 19 Tests in
|
||||
# tools/mkimage/test_nl_mkimage.py liegen direkt daneben und liefen sonst in keiner
|
||||
# Suite mit - darunter test_stays_python38_compatible, die Wache fuer die bindende
|
||||
# 3.8-Randbedingung des Docker-Build-Containers.
|
||||
#
|
||||
# Diese Wache braucht das Paket "vermin" und schlaegt ohne es HART fehl (kein Skip):
|
||||
# eine Wache, die sich beim Fehlen ihres Werkzeugs stillschweigend abschaltet, haette
|
||||
# den Build-Bruch von damals genauso durchgelassen wie gar kein Test. Fehlt vermin,
|
||||
# nachinstallieren:
|
||||
# pip install vermin (oder: pip3 install --user vermin)
|
||||
python3 -m pytest "$ROOT/tools/mkimage" "$ROOT/tools/station/tests" -q
|
||||
else
|
||||
echo "Hinweis: pytest fehlt - Python-Tests uebersprungen" >&2
|
||||
echo " Installation: pip install pytest vermin (vermin braucht" >&2
|
||||
echo " tools/mkimage/test_nl_mkimage.py::test_stays_python38_compatible)" >&2
|
||||
fi
|
||||
|
||||
"$ROOT/tools/station/tests/test_cli.sh"
|
||||
|
||||
Reference in New Issue
Block a user