Fixrunde 2 zu Task 6b. - Die Vorbedingung "display.py liegt in der Ablage" steht jetzt VOR dem ersten sudo install. Vorher stand sie dahinter, und die Meldung "Nichts installiert" war schlicht unwahr: server.py, products.py, das CLI und die Stations-Unit lagen dann schon auf dem Pi. Ausgerechnet an dem Werkbank-Moment, fuer den die Pruefung gedacht ist, log das Skript den Bediener an. Die Ablage raeumt ein EXIT-Trap jetzt auf beiden Wegen aus dem Block ab, nicht nur im Erfolgsfall. - "apt-get update" ist keine Voraussetzung mehr fuer "apt-get install": update meldet schon dann einen Fehler, wenn eine einzige Quelle nicht erreichbar ist, und ein Pi, auf dem das Paket aus dem Cache gekommen waere, bekam so still kein Pillow. - Die Signalhandler beenden jetzt selbst (130/143/129). Ein Handler ohne exit laesst bash weiterlaufen - ein Strg-C konnte in "fertig, Exit 0" muenden. Die Begruendung im Kommentar war falsch und ist korrigiert: zum Aufraeumen haette der EXIT-Trap allein genuegt. - test_host.sh ruft die Shell-Suiten aus einer Liste auf, und der Coverage-Test prueft diese Liste gegen das Dateisystem statt im Skripttext nach Dateinamen zu suchen - ein nachgestellter Kommentar hebelte die alte Regel aus. Er deckt jetzt ganz tools/ ab, nicht nur station/tests. - Neue Szenarien: Wahrheitsgehalt der Abbruchmeldung, apt-update-Fall, und ein Strg-C, das aus den Trap-Zeilen des Skripts selbst geprueft wird.
370 lines
20 KiB
Bash
Executable File
370 lines
20 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Richtet einen Raspberry Pi als ninlight-Station ein. Idempotent: mehrfaches
|
|
# Ausfuehren aendert nichts und schadet nicht - auch nicht auf einem Pi, auf dem
|
|
# schon eine aeltere Version dieses Skripts gelaufen ist (siehe "Konfiguration"
|
|
# unten: fehlende Schluessel werden ergaenzt, vorhandene bleiben unangetastet).
|
|
#
|
|
# Aufruf: tools/station/setup_pi.sh <pi-host>
|
|
set -euo pipefail
|
|
HOST=${1:?Aufruf: tools/station/setup_pi.sh <pi-host>}
|
|
HERE=$(cd "$(dirname "$0")" && pwd)
|
|
# /usr/bin/ssh und /usr/bin/scp statt Varianten im PATH, die sich anders verhalten koennen.
|
|
# Ueberschreibbar (wie NINLIGHT_STATION_ROOT unten und in deploy.sh), damit ein Test gegen
|
|
# Stubs statt einen echten Pi laufen kann.
|
|
SSH_BIN=${NINLIGHT_SSH_BIN:-/usr/bin/ssh}
|
|
SCP_BIN=${NINLIGHT_SCP_BIN:-/usr/bin/scp}
|
|
# Wo die per scp uebertragenen Dateien auf dem Zielsystem zwischenlanden, bevor
|
|
# sie an ihren endgueltigen Platz installiert werden. Ueberschreibbar wie die
|
|
# anderen NINLIGHT_*-Pfade: auf dem Pi bleibt es bei /tmp, ein Test kann hier in
|
|
# eine Sandbox statt in das echte /tmp des Entwicklungsrechners umleiten
|
|
# (Fixrunde 2, N4) - sowohl fuer die lokale scp-Zielangabe unten als auch fuer
|
|
# die REMOTE-Bloecke, die dieselbe Variable (mit demselben Default) referenzieren.
|
|
STAGE_DIR=${NINLIGHT_STAGE_DIR:-/tmp}
|
|
|
|
echo "== Voraussetzungen auf $HOST pruefen =="
|
|
"$SSH_BIN" "$HOST" bash -s <<'REMOTE'
|
|
set -e
|
|
systemctl is-active --quiet NetworkManager || { echo "NetworkManager laeuft nicht - dieser Plan setzt ihn voraus" >&2; exit 1; }
|
|
nmcli -t -f DEVICE,STATE device | grep -q "^wlan0:" || { echo "wlan0 wird nicht von NetworkManager verwaltet" >&2; exit 1; }
|
|
python3 -c "import sys; sys.exit(0 if sys.version_info >= (3,11) else 1)" || { echo "Python 3.11+ noetig" >&2; exit 1; }
|
|
echo " ok"
|
|
REMOTE
|
|
|
|
echo "== Konfiguration =="
|
|
"$SCP_BIN" -q "$HERE/station.conf.example" "$HOST:$STAGE_DIR/ninlight-station.conf.example"
|
|
# CONF ueberschreibbar wie ROOT_DIR/BIN_PATH/UNIT_PATH unten: auf dem Pi bleibt es
|
|
# beim Default, ein Test kann hier gegen eine Sandbox-Datei statt gegen echte
|
|
# Systempfade laufen.
|
|
"$SSH_BIN" "$HOST" bash -s <<'REMOTE'
|
|
set -e
|
|
CONF=${NINLIGHT_STATION_CONF:-/etc/ninlight-station.conf}
|
|
TEMPLATE=${NINLIGHT_STAGE_DIR:-/tmp}/ninlight-station.conf.example
|
|
if [ -f "$CONF" ]; then
|
|
# Eine bestehende Konfiguration bleibt bei ihren Werten - nur Schluessel, die
|
|
# eine neuere Version des CLI zusaetzlich verlangt (z.B. die Migrations- und
|
|
# Anzeige-Schluessel aus Task 4), werden aus der Vorlage ergaenzt. Ohne das
|
|
# wuerde das Ausrollen eines neuen ninlight-station auf einen bereits
|
|
# eingerichteten Pi jeden Unterbefehl - "off" eingeschlossen - mit "Schluessel
|
|
# ... fehlt" blockieren, und ein bereits laufender Zugangspunkt bliebe an, bis
|
|
# jemand die 0600-Datei von Hand ergaenzt (Fixrunde 1, I4).
|
|
ADDED=""
|
|
for KEY in SSID PSK IMAGE_DIR HOME_CONNECTION AP_CONNECTION \
|
|
MIGRATE_AP_CONNECTION MIGRATE_SSID MIGRATE_PSK EPD_REVISION; do
|
|
# Nicht nur "^KEY=": eine vorhandene Zuweisung mit fuehrendem Leerraum oder
|
|
# einem "export" davor (beides gueltiges ". datei"-Shell) wuerde sonst als
|
|
# fehlend gelten, die Vorlagenzeile wird angehaengt, und da das CLI die
|
|
# Datei per ". $CONF" sourct, gewinnt die LETZTE Zuweisung - ein eigenes
|
|
# Passwort wuerde so still durch den oeffentlichen Vorlagenwert ersetzt
|
|
# (Fixrunde 2, N3: genau das "vorhandene Werte nicht ueberschreiben" aus I4,
|
|
# nur eine Formatierungsvariante weiter).
|
|
if ! sudo grep -Eq "^[[:space:]]*(export[[:space:]]+)?${KEY}=" "$CONF"; then
|
|
LINE=$(grep -E "^${KEY}=" "$TEMPLATE" || true)
|
|
if [ -n "$LINE" ]; then
|
|
printf '%s\n' "$LINE" | sudo tee -a "$CONF" >/dev/null
|
|
ADDED="$ADDED $KEY"
|
|
fi
|
|
fi
|
|
done
|
|
if [ -n "$ADDED" ]; then
|
|
echo " $CONF: fehlende Schluessel ergaenzt:$ADDED (vorhandene Werte unveraendert)"
|
|
else
|
|
echo " $CONF existiert bereits, alle Schluessel vorhanden"
|
|
fi
|
|
else
|
|
sudo install -m 0600 "$TEMPLATE" "$CONF"
|
|
echo " $CONF aus der Vorlage angelegt"
|
|
fi
|
|
rm -f "$TEMPLATE"
|
|
REMOTE
|
|
|
|
echo "== Server, CLI und Units installieren =="
|
|
# Reststand der Ablage wegraeumen, BEVOR das scp laeuft (Task 6b F1, I1). "scp -r quelle
|
|
# ziel" kopiert das Quellverzeichnis IN ein bereits vorhandenes Ziel hinein - aus
|
|
# einem liegengebliebenen "…/ninlight-display/" wird dann "…/ninlight-display/display/",
|
|
# die Installationsschleife unten ueberspringt Verzeichnisse und findet gar nichts mehr.
|
|
# Ergebnis waere ein Lauf, der Erfolg meldet, den Anzeigedienst aktiviert und ein leeres
|
|
# $ROOT_DIR/display hinterlaesst: 203/EXEC im 5-Sekunden-Takt, Panel schwarz. Genau so
|
|
# ein Reststand entsteht, wenn ein frueherer Lauf nach dem scp abbricht (sudo-Timeout,
|
|
# Platte voll, Strg-C) - die Aufraeumzeile unten laeuft dann nicht mehr. Idempotent
|
|
# heisst, dass auch dieser Fall glattgeht (siehe Kopf dieser Datei).
|
|
"$SSH_BIN" "$HOST" bash -s <<'REMOTE'
|
|
set -e
|
|
STAGE_DIR=${NINLIGHT_STAGE_DIR:-/tmp}
|
|
rm -rf "$STAGE_DIR/ninlight-display"
|
|
REMOTE
|
|
"$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.
|
|
"$SSH_BIN" "$HOST" bash -s <<'REMOTE'
|
|
set -e
|
|
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}
|
|
DISPLAY_UNIT_PATH=${NINLIGHT_DISPLAY_UNIT:-/etc/systemd/system/ninlight-display.service}
|
|
|
|
# Die Ablage wird in JEDEM Fall abgeraeumt, auch wenn dieser Block vorzeitig aussteigt
|
|
# (Task 6b F2, N1). Vorher stand das "rm -rf" nur am Blockende und wurde beim Abbruch
|
|
# uebersprungen - die uebertragenen Dateien blieben unter /tmp liegen und waren beim
|
|
# naechsten Lauf genau der Reststand, gegen den der Block eigentlich absichert.
|
|
cleanup_stage() {
|
|
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"
|
|
}
|
|
trap cleanup_stage EXIT
|
|
|
|
rm -rf "$STAGE_DIR/ninlight-display/__pycache__"
|
|
# Harte Vorbedingung, und zwar VOR dem ersten "sudo install" (Task 6b F2, N1): ohne
|
|
# display.py in der Ablage hat das scp nicht geliefert, was es sollte - abgebrochen,
|
|
# falsch verschachtelt oder gar nicht gelaufen. Die Installationsschleife weiter unten
|
|
# wuerde das stillschweigend als "nichts zu tun" verbuchen, der Lauf meldete Erfolg und
|
|
# der aktivierte Anzeigedienst liefe gegen eine nicht existierende Datei.
|
|
#
|
|
# Die Pruefung stand zuerst NACH den vier "sudo install" darunter - die Meldung "Nichts
|
|
# installiert" war damit schlicht unwahr: server.py, products.py, das CLI und die
|
|
# Stations-Unit lagen dann bereits auf dem Pi. Ausgerechnet an dem Werkbank-Moment, fuer
|
|
# den diese Pruefung gedacht ist, log das Skript den Bediener an. Jetzt stimmt der Satz.
|
|
if [ ! -f "$STAGE_DIR/ninlight-display/display.py" ]; then
|
|
echo "Fehler: $STAGE_DIR/ninlight-display/display.py fehlt - die Uebertragung des Anzeigeverzeichnisses ist fehlgeschlagen." >&2
|
|
echo " Nichts installiert: $ROOT_DIR, $BIN_PATH und beide Units sind unveraendert. Ablage aufgeraeumt, Lauf einfach wiederholen." >&2
|
|
exit 1
|
|
fi
|
|
|
|
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"
|
|
# 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.
|
|
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
|
|
# Was im Repo geloescht wurde, muss auch hier verschwinden - dasselbe Argument wie in
|
|
# deploy.sh (dort per "rsync --delete"): display/ ist reiner Code aus dem Repo, es gibt
|
|
# keinen Bestand, der einen Lauf ueberleben muesste, und ein zurueckgebliebenes altes
|
|
# Modul wuerde weiter mit importiert. rsync steht hier nicht zur Verfuegung (setup_pi.sh
|
|
# setzt ausdruecklich nur ssh/scp voraus), also von Hand.
|
|
for f in "$ROOT_DIR"/display/*; do
|
|
[ -e "$f" ] || continue
|
|
if [ ! -e "$STAGE_DIR/ninlight-display/$(basename "$f")" ]; then
|
|
sudo rm -rf "$f"
|
|
echo " veraltete Datei entfernt: display/$(basename "$f")"
|
|
fi
|
|
done
|
|
sudo install -m 0644 "$STAGE_DIR/ninlight-display.service" "$DISPLAY_UNIT_PATH"
|
|
# Das Aufraeumen der Ablage macht der EXIT-Trap oben - auf beiden Wegen aus diesem Block.
|
|
sudo systemctl daemon-reload
|
|
echo " installiert"
|
|
REMOTE
|
|
|
|
echo "== Pakete =="
|
|
# Pillow kommt auf dem Pi bewusst aus dem Distributionspaket (python3-pil), nicht per
|
|
# pip: tools/station/ laeuft dort unter dem System-Python 3.13, und Debian 13 laesst ein
|
|
# "pip install" ins System-Python gar nicht mehr zu (PEP 668). Ohne Pillow zeichnet die
|
|
# Anzeige nicht - display.py faengt den ImportError ab und beendet sich still mit 0
|
|
# (Absicht, damit ein fehlendes Panel nie eine Migration verhindert). Genau deshalb muss
|
|
# das Paket ueberhaupt hier kommen: sonst faellt es nirgends auf.
|
|
#
|
|
# Aber NACH der Installation und nur als Warnung (Task 6b F1, I4): eine Migrationsstation
|
|
# steht typischerweise nicht am Netz, und mit veralteten Paketlisten oder ohne Internet
|
|
# scheitert apt. Unter "set -e" vor der Installation haette das den Lauf abgebrochen,
|
|
# bevor server.py, das CLI, beide Units und beide NetworkManager-Profile installiert sind
|
|
# - man haette dann GAR KEINE Station statt einer Station ohne Panel. Das waere die
|
|
# einzige harte Stelle in einer sonst durchgehend weichen Kette gewesen (display.py
|
|
# beendet sich mit 0, deploy.sh warnt nur, enable/restart unten warnen nur).
|
|
"$SSH_BIN" "$HOST" bash -s <<'REMOTE'
|
|
set -e
|
|
if python3 -c 'import PIL' 2>/dev/null; then
|
|
echo " python3-pil ist bereits vorhanden"
|
|
else
|
|
# "update" zuerst: auf einem laenger nicht gepflegten Pi zeigen die Paketlisten auf
|
|
# Versionen, die es im Archiv nicht mehr gibt, und "install" scheitert mit 404.
|
|
#
|
|
# Aber "|| true", und "install" laeuft UNABHAENGIG davon (Task 6b F2, N2): "apt-get
|
|
# update" liefert schon dann einen Fehler, wenn eine einzige Quelle nicht erreichbar
|
|
# ist - eine veraltete Drittanbieter-Liste, teilweises Netz. Mit "update && install"
|
|
# bekaeme ein Pi, auf dem "install" aus dem Paketcache oder den erreichbaren Quellen
|
|
# muehelos durchgelaufen waere, still kein Pillow. Das Auffrischen ist eine
|
|
# Verbesserung der Chancen, keine Voraussetzung.
|
|
sudo apt-get update >/dev/null 2>&1 || true
|
|
if sudo apt-get install -y python3-pil; then
|
|
echo " python3-pil installiert"
|
|
else
|
|
echo " Warnung: python3-pil liess sich nicht installieren (kein Netz? veraltete Paketlisten?)." >&2
|
|
echo " Die Station ist vollstaendig eingerichtet, das E-Paper bleibt aber leer," >&2
|
|
echo " bis 'sudo apt install python3-pil' durchlaeuft und der Anzeigedienst neu startet." >&2
|
|
fi
|
|
fi
|
|
REMOTE
|
|
|
|
echo "== Anzeigedienst aktivieren =="
|
|
# Die Anzeige wird aktiviert (startet also beim Booten mit) UND jetzt gestartet - anders
|
|
# als die Station selbst, die absichtlich aus bleibt: die Anzeige sendet nichts, sie
|
|
# zeigt nur an, und "Station aus" ist genau das, was nach dem Einrichten auf dem Panel
|
|
# stehen soll. Erst hier, nach dem Paketschritt: so startet der Dienst bereits mit
|
|
# vorhandenem Pillow, statt sich beim ersten Zeichnen still mit 0 zu beenden und bis zum
|
|
# naechsten manuellen Neustart nichts anzuzeigen.
|
|
#
|
|
# Fehlt Panel oder Pillow, beendet sich der Dienst still mit 0 (siehe display.py) -
|
|
# deshalb nur Warnungen: ein fehlendes Panel darf setup_pi.sh nicht abbrechen.
|
|
"$SSH_BIN" "$HOST" bash -s <<'REMOTE'
|
|
set -e
|
|
sudo systemctl enable ninlight-display.service >/dev/null 2>&1 || \
|
|
echo " Warnung: 'systemctl enable ninlight-display' fehlgeschlagen - die Anzeige startet nach einem Neustart des Pi nicht von allein mit" >&2
|
|
sudo systemctl restart ninlight-display.service >/dev/null 2>&1 || \
|
|
echo " Warnung: 'systemctl restart ninlight-display' fehlgeschlagen - journalctl -u ninlight-display" >&2
|
|
echo " Anzeigedienst aktiviert"
|
|
REMOTE
|
|
|
|
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
|
|
# ". /etc/ninlight-station.conf" wuerde also mit "Permission denied"
|
|
# abbrechen. "sudo cat" + eval liest sie dennoch ein, ohne die Rechte der
|
|
# Datei aufzuweichen.
|
|
# - "nmcli ... wifi-sec.psk $PSK" wuerde das Passwort als Kommandozeilen-
|
|
# argument an einen Prozess uebergeben - fuer jeden lokalen Benutzer per
|
|
# "ps"/"/proc/<pid>/cmdline" sichtbar. Deshalb wird das Verbindungsprofil
|
|
# stattdessen direkt als NetworkManager-Keyfile (0600 root:root)
|
|
# geschrieben; das Passwort landet nur in einer Root-Datei, nie in einer
|
|
# Prozessliste.
|
|
"$SSH_BIN" "$HOST" bash -s <<'REMOTE'
|
|
set -e
|
|
CONF=${NINLIGHT_STATION_CONF:-/etc/ninlight-station.conf}
|
|
NM_DIR=${NINLIGHT_NM_DIR:-/etc/NetworkManager/system-connections}
|
|
eval "$(sudo cat "$CONF")"
|
|
|
|
# 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).
|
|
#
|
|
# Zur Reichweite des Traps (Task 6b F2, N3 - die Begruendung hier war vorher falsch):
|
|
# der EXIT-Trap allein WUERDE bei einem Strg-C schon reichen, bash fuehrt ihn auch beim
|
|
# Abbruch durch ein nicht abgefangenes SIGINT aus (Exit 130). Der Grund fuer die
|
|
# Signalhandler ist ein anderer: sie sollen den Exit-Code ehrlich halten. Ein Handler
|
|
# fuer INT/TERM, der nicht selbst beendet, laesst bash naemlich NACH dem Handler
|
|
# weiterlaufen - ein Strg-C mitten im Schreiben der Profile koennte dann in "fertig,
|
|
# Exit 0" muenden, obwohl der Lauf abgebrochen wurde. Deshalb raeumt jeder Handler auf
|
|
# und beendet danach ausdruecklich mit dem ueblichen Signal-Code (128+n).
|
|
#
|
|
# Mit ausdruecklicher Vorlage unter ${TMPDIR:-/tmp}, nicht als blankes "mktemp -d":
|
|
# BSD-mktemp (macOS, wo diese Suite auch laeuft) beachtet TMPDIR ohne Vorlage gar nicht
|
|
# und legt unabhaengig davon unter /var/folders/... an, GNU-mktemp (der Pi) dagegen
|
|
# schon. Ohne Vorlage landet das Verzeichnis also je nach Plattform woanders - ein Test,
|
|
# der nachsieht, ob wirklich nichts liegengeblieben ist, sucht dann auf macOS an der
|
|
# falschen Stelle und ist gruen, ohne etwas geprueft zu haben.
|
|
PROFILE_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/ninlight-profile.XXXXXX")
|
|
trap 'rm -rf "$PROFILE_TMPDIR"' EXIT
|
|
trap 'rm -rf "$PROFILE_TMPDIR"; exit 130' INT
|
|
trap 'rm -rf "$PROFILE_TMPDIR"; exit 143' TERM
|
|
trap 'rm -rf "$PROFILE_TMPDIR"; exit 129' HUP
|
|
|
|
# $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'
|
|
|
|
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)
|
|
EOF
|