Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb54dac066 | ||
|
|
456058956c | ||
|
|
3032178247 | ||
|
|
4a8a5d658f |
@@ -4,7 +4,19 @@
|
|||||||
Layout (512 bytes): "NINL" | version(1) | device[16] | fw_version[16] | sha256[32] | 0xFF padding.
|
Layout (512 bytes): "NINL" | version(1) | device[16] | fw_version[16] | sha256[32] | 0xFF padding.
|
||||||
The hash covers the complete `_gz.img` (T_BOOTER header + payload), which is exactly what the
|
The hash covers the complete `_gz.img` (T_BOOTER header + payload), which is exactly what the
|
||||||
firmware hashes while writing the image to flash.
|
firmware hashes while writing the image to flash.
|
||||||
|
|
||||||
|
Must run under Python 3.8.10: CMake invokes this script via plain `python3` inside the
|
||||||
|
x86_64 build container (see docker/Dockerfile, cmake/run_mkimage.cmake) on every firmware
|
||||||
|
build, and that container has not been upgraded past 3.8. This is independent of the
|
||||||
|
"Python >= 3.11" requirement elsewhere in this repo, which applies only to the tooling that
|
||||||
|
runs on the Mac and the Pi (tools/station, tools/mkimage's own pytest suite), never to this
|
||||||
|
file. Keep `from __future__ import annotations` below so that PEP 604 unions (`str | None`)
|
||||||
|
in annotations stay text and are never evaluated - without it, that syntax raises
|
||||||
|
`TypeError: unsupported operand type(s) for |: 'type' and 'NoneType'` on 3.8/3.9. See
|
||||||
|
test_nl_mkimage.py::test_stays_python38_compatible, which fails the build if this drifts.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import hashlib
|
import hashlib
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ version = "0.1.0"
|
|||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = ["pytest>=8"]
|
dev = ["pytest>=8", "vermin>=1.6"]
|
||||||
|
|||||||
@@ -208,3 +208,71 @@ def test_verify_with_build_arguments_is_an_error(tmp_path):
|
|||||||
)
|
)
|
||||||
assert r.returncode != 0
|
assert r.returncode != 0
|
||||||
assert "cannot be combined" in r.stderr
|
assert "cannot be combined" in r.stderr
|
||||||
|
|
||||||
|
|
||||||
|
# --- Python 3.8 compatibility -------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# CMake calls this script via plain `python3` inside the build container on every firmware
|
||||||
|
# build (see docker/Dockerfile, cmake/run_mkimage.cmake), and that container is stuck on
|
||||||
|
# Python 3.8.10. This is the opposite constraint from the rest of the repo's tooling (which
|
||||||
|
# needs Python >= 3.11) - see the module docstring in nl_mkimage.py for the full story, and
|
||||||
|
# git history around the "unsupported operand type(s) for |" break for what happens when it
|
||||||
|
# regresses.
|
||||||
|
#
|
||||||
|
# Two checks, because neither alone covers the failure mode we actually hit:
|
||||||
|
# 1. `from __future__ import annotations` must be the first statement after the module
|
||||||
|
# docstring. As long as it's there, PEP 604 unions in annotations (`str | None`) are
|
||||||
|
# never evaluated at runtime, so they can't raise on 3.8/3.9 no matter how many more of
|
||||||
|
# them get added later.
|
||||||
|
# 2. vermin's *default* analysis (annotations not evaluated - matching point 1) catches
|
||||||
|
# everything else 3.9+/3.10+/3.11+ that could sneak in: walrus in odd positions, match
|
||||||
|
# statements, dict `|=`, new stdlib members, etc. We deliberately do NOT enable vermin's
|
||||||
|
# `union-types` feature: it flags `str | None` as needing 3.10 unconditionally, without
|
||||||
|
# accounting for `from __future__ import annotations` deferring evaluation - which would
|
||||||
|
# make this test fail on the very fix that makes the file 3.8-safe. Check 1 above is
|
||||||
|
# what actually guards against that pattern.
|
||||||
|
|
||||||
|
|
||||||
|
def test_future_annotations_import_is_present():
|
||||||
|
"""Guards the PEP 604 union pattern that broke the Docker build (see module docstring)."""
|
||||||
|
import ast
|
||||||
|
|
||||||
|
tree = ast.parse(SCRIPT.read_text())
|
||||||
|
body = tree.body
|
||||||
|
# Skip a leading module docstring, if any.
|
||||||
|
if body and isinstance(body[0], ast.Expr) and isinstance(body[0].value, ast.Constant) \
|
||||||
|
and isinstance(body[0].value.value, str):
|
||||||
|
body = body[1:]
|
||||||
|
assert body, "nl_mkimage.py is unexpectedly empty"
|
||||||
|
first = body[0]
|
||||||
|
assert isinstance(first, ast.ImportFrom) and first.module == "__future__" \
|
||||||
|
and any(alias.name == "annotations" for alias in first.names), (
|
||||||
|
"nl_mkimage.py must start with `from __future__ import annotations` right after its "
|
||||||
|
"module docstring - without it, PEP 604 unions like `str | None` in annotations are "
|
||||||
|
"evaluated eagerly and crash under the Python 3.8.10 in the Docker build container "
|
||||||
|
"(TypeError: unsupported operand type(s) for |: 'type' and 'NoneType')"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_stays_python38_compatible():
|
||||||
|
"""Static check that nl_mkimage.py needs nothing newer than Python 3.8 to run.
|
||||||
|
|
||||||
|
Runs without a Python 3.8 interpreter installed: vermin infers the minimum required
|
||||||
|
version from syntax/stdlib usage instead of actually executing the file.
|
||||||
|
"""
|
||||||
|
vermin = __import__("vermin")
|
||||||
|
|
||||||
|
config = vermin.Config()
|
||||||
|
config.set_quiet(True)
|
||||||
|
# Match nl_mkimage.py's own strategy: with `from __future__ import annotations` present
|
||||||
|
# (checked separately above), annotations are never evaluated, so vermin shouldn't assume
|
||||||
|
# they need runtime support either. This is vermin's default, spelled out here so it's
|
||||||
|
# obvious why `union-types` (see the block comment above) is intentionally not enabled.
|
||||||
|
config.set_eval_annotations(False)
|
||||||
|
|
||||||
|
source = SCRIPT.read_text()
|
||||||
|
py2_min, py3_min = vermin.detect(source, config, str(SCRIPT))
|
||||||
|
assert py3_min is not None and py3_min <= (3, 8), (
|
||||||
|
f"nl_mkimage.py now needs Python {py3_min} - it must keep working on the "
|
||||||
|
"Python 3.8.10 in the Docker build container (see module docstring)"
|
||||||
|
)
|
||||||
|
|||||||
Generated
+14
-1
@@ -28,12 +28,16 @@ source = { virtual = "." }
|
|||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
|
{ name = "vermin" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [{ name = "pytest", specifier = ">=8" }]
|
dev = [
|
||||||
|
{ name = "pytest", specifier = ">=8" },
|
||||||
|
{ name = "vermin", specifier = ">=1.6" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
@@ -77,3 +81,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa
|
|||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
|
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vermin"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/24/7b/2c1b403f2a844e1acb36694fc336e323df742f7f752edb4188311ad43f9e/vermin-1.8.0.tar.gz", hash = "sha256:3621955ac2a2950175c5b4a9b2fc3bd24bd416da0388893c9eb6971264e4ca1f", size = 99913, upload-time = "2025-11-20T17:29:31.509Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/fd/6d2167767488e162efd01300a679c7f7d913dbb65baa0b15760ebb369854/vermin-1.8.0-py3-none-any.whl", hash = "sha256:6f2b98ad697a16ed3442e81a0f0baabfc38c185b78aaab1b1fecd87bdd6640b8", size = 98030, upload-time = "2025-11-20T17:29:29.931Z" },
|
||||||
|
]
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ shift || true
|
|||||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||||
ROOT=$(cd "$HERE/../.." && pwd)
|
ROOT=$(cd "$HERE/../.." && pwd)
|
||||||
MKIMAGE="$ROOT/tools/mkimage/nl_mkimage.py"
|
MKIMAGE="$ROOT/tools/mkimage/nl_mkimage.py"
|
||||||
|
# /usr/bin/ssh statt einem ssh im PATH, das sich anders verhaelt (siehe A3-Notiz im
|
||||||
|
# Abnahmebericht). Ueberschreibbar, damit tests/test_deploy.sh gegen einen Stub statt
|
||||||
|
# einen echten Pi laufen kann - dasselbe Prinzip wie bei NINLIGHT_STATION_ROOT unten.
|
||||||
|
SSH_BIN=${NINLIGHT_SSH_BIN:-/usr/bin/ssh}
|
||||||
|
|
||||||
declare -a FILES=()
|
declare -a FILES=()
|
||||||
if [ $# -gt 0 ]; then
|
if [ $# -gt 0 ]; then
|
||||||
@@ -54,11 +58,12 @@ echo "== uebertragen =="
|
|||||||
# /tmp/ninlight-images auf dem Pi ist nur eine fluechtige Ablage fuer diesen
|
# /tmp/ninlight-images auf dem Pi ist nur eine fluechtige Ablage fuer diesen
|
||||||
# Lauf - hier darf --delete unbedingt ran, das betrifft nicht das live
|
# Lauf - hier darf --delete unbedingt ran, das betrifft nicht das live
|
||||||
# ausgelieferte Verzeichnis.
|
# ausgelieferte Verzeichnis.
|
||||||
rsync -a -c --delete "$STAGE/" "$HOST:/tmp/ninlight-images/"
|
# -e: rsync soll fuer den Transport zum Pi denselben ssh wie oben benutzen.
|
||||||
rsync -a "$HERE/server.py" "$HERE/ninlight-station" "$HERE/ninlight-station.service" "$HOST:/tmp/"
|
rsync -a -c --delete -e "$SSH_BIN" "$STAGE/" "$HOST:/tmp/ninlight-images/"
|
||||||
|
rsync -a -e "$SSH_BIN" "$HERE/server.py" "$HERE/ninlight-station" "$HERE/ninlight-station.service" "$HOST:/tmp/"
|
||||||
# Pfade als Variablen mit Default: so kann ein Test die Installation gegen
|
# Pfade als Variablen mit Default: so kann ein Test die Installation gegen
|
||||||
# ein Sandbox-Verzeichnis statt gegen echte Systempfade laufen lassen.
|
# ein Sandbox-Verzeichnis statt gegen echte Systempfade laufen lassen.
|
||||||
ssh "$HOST" bash -s -- "$FULL_SYNC" <<'REMOTE'
|
"$SSH_BIN" "$HOST" bash -s -- "$FULL_SYNC" <<'REMOTE'
|
||||||
set -e
|
set -e
|
||||||
FULL_SYNC=$1
|
FULL_SYNC=$1
|
||||||
ROOT_DIR=${NINLIGHT_STATION_ROOT:-/opt/ninlight-station}
|
ROOT_DIR=${NINLIGHT_STATION_ROOT:-/opt/ninlight-station}
|
||||||
|
|||||||
@@ -91,7 +91,13 @@ off)
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
status)
|
status)
|
||||||
printf 'Dienst : %s\n' "$(systemctl is-active ninlight-station 2>/dev/null || echo inaktiv)"
|
# "systemctl is-active" schreibt den Zustand ("inactive", "failed", ...) auf stdout und
|
||||||
|
# liefert dabei einen Rueckgabewert ungleich 0, sobald der Dienst nicht "active" ist -
|
||||||
|
# ein "|| echo inaktiv" wuerde also zusaetzlich zur ersten Zeile ausgeloest und beide
|
||||||
|
# landen in der Ausgabe. Der Zustand selbst ist die Auskunft; "status" ist damit
|
||||||
|
# erfolgreich, sobald es ihn ermitteln konnte, unabhaengig vom Dienstzustand - deshalb
|
||||||
|
# am Ende explizit exit 0.
|
||||||
|
printf 'Dienst : %s\n' "$(systemctl is-active ninlight-station 2>/dev/null || true)"
|
||||||
printf 'wlan0 : %s\n' "$(nmcli -t -f GENERAL.CONNECTION device show wlan0 2>/dev/null | cut -d: -f2)"
|
printf 'wlan0 : %s\n' "$(nmcli -t -f GENERAL.CONNECTION device show wlan0 2>/dev/null | cut -d: -f2)"
|
||||||
printf 'Adresse: %s\n' "$(ip -4 -br addr show wlan0 2>/dev/null | awk '{print $3}')"
|
printf 'Adresse: %s\n' "$(ip -4 -br addr show wlan0 2>/dev/null | awk '{print $3}')"
|
||||||
echo "Images :"
|
echo "Images :"
|
||||||
@@ -104,7 +110,11 @@ print(t[21:37].rstrip(b"\x00").decode("ascii", "replace") if t[:4] == b"NINL" el
|
|||||||
PY
|
PY
|
||||||
)"
|
)"
|
||||||
done
|
done
|
||||||
|
# "status" soll mit 0 enden, sobald es seine Auskunft geben konnte, auch wenn noch nie
|
||||||
|
# ausgeliefert wurde - ohne das "|| true" waere der Exit-Code des Skripts hier der des
|
||||||
|
# fehlgeschlagenen Tests (1), weil das die letzte Anweisung im Zweig ist.
|
||||||
[ -r /run/ninlight-station/status.json ] && { echo "Zuletzt ausgeliefert:"; sed 's/^/ /' /run/ninlight-station/status.json; }
|
[ -r /run/ninlight-station/status.json ] && { echo "Zuletzt ausgeliefert:"; sed 's/^/ /' /run/ninlight-station/status.json; }
|
||||||
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Aufruf: ninlight-station on|off|status" >&2
|
echo "Aufruf: ninlight-station on|off|status" >&2
|
||||||
|
|||||||
@@ -6,17 +6,22 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
HOST=${1:?Aufruf: tools/station/setup_pi.sh <pi-host>}
|
HOST=${1:?Aufruf: tools/station/setup_pi.sh <pi-host>}
|
||||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
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 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}
|
||||||
|
|
||||||
echo "== Voraussetzungen auf $HOST pruefen =="
|
echo "== Voraussetzungen auf $HOST pruefen =="
|
||||||
ssh "$HOST" 'set -e
|
"$SSH_BIN" "$HOST" 'set -e
|
||||||
systemctl is-active --quiet NetworkManager || { echo "NetworkManager laeuft nicht - dieser Plan setzt ihn voraus" >&2; exit 1; }
|
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; }
|
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; }
|
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"'
|
echo " ok"'
|
||||||
|
|
||||||
echo "== Konfiguration =="
|
echo "== Konfiguration =="
|
||||||
scp -q "$HERE/station.conf.example" "$HOST:/tmp/ninlight-station.conf.example"
|
"$SCP_BIN" -q "$HERE/station.conf.example" "$HOST:/tmp/ninlight-station.conf.example"
|
||||||
ssh "$HOST" 'set -e
|
"$SSH_BIN" "$HOST" 'set -e
|
||||||
if [ -f /etc/ninlight-station.conf ]; then
|
if [ -f /etc/ninlight-station.conf ]; then
|
||||||
echo " /etc/ninlight-station.conf existiert bereits, bleibt unveraendert"
|
echo " /etc/ninlight-station.conf existiert bereits, bleibt unveraendert"
|
||||||
else
|
else
|
||||||
@@ -26,8 +31,8 @@ ssh "$HOST" 'set -e
|
|||||||
rm -f /tmp/ninlight-station.conf.example'
|
rm -f /tmp/ninlight-station.conf.example'
|
||||||
|
|
||||||
echo "== Server, CLI und Unit installieren =="
|
echo "== Server, CLI und Unit installieren =="
|
||||||
scp -q "$HERE/server.py" "$HERE/ninlight-station" "$HERE/ninlight-station.service" "$HOST:/tmp/"
|
"$SCP_BIN" -q "$HERE/server.py" "$HERE/ninlight-station" "$HERE/ninlight-station.service" "$HOST:/tmp/"
|
||||||
ssh "$HOST" 'set -e
|
"$SSH_BIN" "$HOST" 'set -e
|
||||||
sudo install -d -m 0755 /opt/ninlight-station /opt/ninlight-station/images
|
sudo install -d -m 0755 /opt/ninlight-station /opt/ninlight-station/images
|
||||||
sudo install -m 0755 /tmp/server.py /opt/ninlight-station/server.py
|
sudo install -m 0755 /tmp/server.py /opt/ninlight-station/server.py
|
||||||
sudo install -m 0755 /tmp/ninlight-station /usr/local/bin/ninlight-station
|
sudo install -m 0755 /tmp/ninlight-station /usr/local/bin/ninlight-station
|
||||||
@@ -49,7 +54,7 @@ echo "== NetworkManager-Profil =="
|
|||||||
# stattdessen direkt als NetworkManager-Keyfile (0600 root:root)
|
# stattdessen direkt als NetworkManager-Keyfile (0600 root:root)
|
||||||
# geschrieben; das Passwort landet nur in einer Root-Datei, nie in einer
|
# geschrieben; das Passwort landet nur in einer Root-Datei, nie in einer
|
||||||
# Prozessliste.
|
# Prozessliste.
|
||||||
ssh "$HOST" bash -s <<'REMOTE'
|
"$SSH_BIN" "$HOST" bash -s <<'REMOTE'
|
||||||
set -e
|
set -e
|
||||||
eval "$(sudo cat /etc/ninlight-station.conf)"
|
eval "$(sudo cat /etc/ninlight-station.conf)"
|
||||||
CONN_FILE="/etc/NetworkManager/system-connections/${AP_CONNECTION}.nmconnection"
|
CONN_FILE="/etc/NetworkManager/system-connections/${AP_CONNECTION}.nmconnection"
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ run_deploy() {
|
|||||||
# in der Sandbox muessen sie fuer "install" eigens angelegt werden.
|
# in der Sandbox muessen sie fuer "install" eigens angelegt werden.
|
||||||
mkdir -p "$SANDBOX_STATE/usr/local/bin" "$SANDBOX_STATE/etc/systemd/system"
|
mkdir -p "$SANDBOX_STATE/usr/local/bin" "$SANDBOX_STATE/etc/systemd/system"
|
||||||
PATH="$STUBBIN:$PATH" \
|
PATH="$STUBBIN:$PATH" \
|
||||||
|
NINLIGHT_SSH_BIN="$STUBBIN/ssh" \
|
||||||
NINLIGHT_STATION_ROOT="$SANDBOX_STATE/opt/ninlight-station" \
|
NINLIGHT_STATION_ROOT="$SANDBOX_STATE/opt/ninlight-station" \
|
||||||
NINLIGHT_STATION_BIN="$SANDBOX_STATE/usr/local/bin/ninlight-station" \
|
NINLIGHT_STATION_BIN="$SANDBOX_STATE/usr/local/bin/ninlight-station" \
|
||||||
NINLIGHT_STATION_UNIT="$SANDBOX_STATE/etc/systemd/system/ninlight-station.service" \
|
NINLIGHT_STATION_UNIT="$SANDBOX_STATE/etc/systemd/system/ninlight-station.service" \
|
||||||
@@ -232,6 +233,7 @@ echo "foo-image" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
|
|||||||
set +e
|
set +e
|
||||||
OUT=$(FAKE_SYSTEMCTL_ACTIVE_EXIT=0 FAKE_SYSTEMCTL_RESTART_EXIT=1 \
|
OUT=$(FAKE_SYSTEMCTL_ACTIVE_EXIT=0 FAKE_SYSTEMCTL_RESTART_EXIT=1 \
|
||||||
PATH="$STUBBIN:$PATH" \
|
PATH="$STUBBIN:$PATH" \
|
||||||
|
NINLIGHT_SSH_BIN="$STUBBIN/ssh" \
|
||||||
NINLIGHT_STATION_ROOT="$SANDBOX_STATE/opt/ninlight-station" \
|
NINLIGHT_STATION_ROOT="$SANDBOX_STATE/opt/ninlight-station" \
|
||||||
NINLIGHT_STATION_BIN="$SANDBOX_STATE/usr/local/bin/ninlight-station" \
|
NINLIGHT_STATION_BIN="$SANDBOX_STATE/usr/local/bin/ninlight-station" \
|
||||||
NINLIGHT_STATION_UNIT="$SANDBOX_STATE/etc/systemd/system/ninlight-station.service" \
|
NINLIGHT_STATION_UNIT="$SANDBOX_STATE/etc/systemd/system/ninlight-station.service" \
|
||||||
@@ -250,6 +252,29 @@ else
|
|||||||
pass "'Dienst laeuft nicht' wird nicht faelschlich gemeldet"
|
pass "'Dienst laeuft nicht' wird nicht faelschlich gemeldet"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Szenario 6: deploy.sh/setup_pi.sh nutzen ohne Override /usr/bin/ssh (bzw. scp) ==="
|
||||||
|
# Die anderen Szenarien laufen mit NINLIGHT_SSH_BIN auf einen Stub umgebogen - das prueft die
|
||||||
|
# Logik, aber nicht mehr, WELCHEN ssh das Skript ohne Override tatsaechlich waehlt. Der Nutzer
|
||||||
|
# verlangt ausdruecklich /usr/bin/ssh und /usr/bin/scp statt einer Variante im PATH; das hier
|
||||||
|
# ist die Absicherung dagegen, dass der Default beim naechsten Umbau stillschweigend zurueck
|
||||||
|
# auf ein PATH-"ssh" rutscht.
|
||||||
|
if grep -q 'SSH_BIN=\${NINLIGHT_SSH_BIN:-/usr/bin/ssh}' "$STATION_DIR/deploy.sh"; then
|
||||||
|
pass "deploy.sh: Default fuer SSH_BIN ist /usr/bin/ssh"
|
||||||
|
else
|
||||||
|
fail "deploy.sh: Default fuer SSH_BIN ist nicht mehr /usr/bin/ssh"
|
||||||
|
fi
|
||||||
|
if grep -q 'SSH_BIN=\${NINLIGHT_SSH_BIN:-/usr/bin/ssh}' "$STATION_DIR/setup_pi.sh"; then
|
||||||
|
pass "setup_pi.sh: Default fuer SSH_BIN ist /usr/bin/ssh"
|
||||||
|
else
|
||||||
|
fail "setup_pi.sh: Default fuer SSH_BIN ist nicht mehr /usr/bin/ssh"
|
||||||
|
fi
|
||||||
|
if grep -q 'SCP_BIN=\${NINLIGHT_SCP_BIN:-/usr/bin/scp}' "$STATION_DIR/setup_pi.sh"; then
|
||||||
|
pass "setup_pi.sh: Default fuer SCP_BIN ist /usr/bin/scp"
|
||||||
|
else
|
||||||
|
fail "setup_pi.sh: Default fuer SCP_BIN ist nicht mehr /usr/bin/scp"
|
||||||
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
if [ "$FAILED" -eq 0 ]; then
|
if [ "$FAILED" -eq 0 ]; then
|
||||||
echo "Alle Tests bestanden."
|
echo "Alle Tests bestanden."
|
||||||
|
|||||||
Generated
+79
@@ -0,0 +1,79 @@
|
|||||||
|
version = 1
|
||||||
|
revision = 3
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iniconfig"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ninlight-station"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
|
||||||
|
[package.dev-dependencies]
|
||||||
|
dev = [
|
||||||
|
{ name = "pytest" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
|
||||||
|
[package.metadata.requires-dev]
|
||||||
|
dev = [{ name = "pytest", specifier = ">=8" }]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "26.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pluggy"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pygments"
|
||||||
|
version = "2.21.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest"
|
||||||
|
version = "9.1.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
{ name = "iniconfig" },
|
||||||
|
{ name = "packaging" },
|
||||||
|
{ name = "pluggy" },
|
||||||
|
{ name = "pygments" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user