Compare commits

..
2 Commits
7 changed files with 63 additions and 8 deletions

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "tesm"
version = "2.8.37"
version = "2.8.39"
description = "TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten und Aruba-Switchen"
requires-python = ">=3.11"
dependencies = [
+24 -3
View File
@@ -95,7 +95,19 @@ traegt() {
}
bestuecken() {
"$PY" -m pip install --quiet --upgrade pip setuptools wheel
# **Jeder pip-Lauf wird einzeln geprueft.**
#
# "set -e" hilft hier nicht: diese Funktion wird in einer Bedingung
# aufgerufen ("if ! bestuecken"), und darin ist errexit fuer den **ganzen**
# Rumpf abgeschaltet. Ein gescheitertes pip lief deshalb einfach weiter bis
# zum "return 0" -- der Lauf meldete "Fertig", und im venv lagen weiter die
# alten Pakete.
#
# Real passiert: der Lizenzserver blieb ueber drei Auslieferungen hinweg auf
# 2.8.16 stehen, weil eine Abhaengigkeit nicht aufloesbar war. Gesehen hat es
# niemand, denn die Fehlerzeile von pip ging im Lauf unter und der
# Ausstiegscode war 0.
"$PY" -m pip install --quiet --upgrade pip setuptools wheel || return 1
local pakete=("$ROOT/packages/tesm-licensing" "$ROOT/packages/tesm-core")
if [[ "$APP" == "tesm-license" ]]; then
[[ -d "$ROOT/packages/tesm-licensing-server" ]] || {
@@ -104,9 +116,11 @@ bestuecken() {
}
pakete+=("$ROOT/packages/tesm-licensing-server")
fi
"$PY" -m pip install --quiet "${pakete[@]}" "$ROOT/apps/$APP" gunicorn
"$PY" -m pip install --quiet "${pakete[@]}" "$ROOT/apps/$APP" gunicorn || return 1
# Die WebSocket-Bruecke braucht nur TESM (Terminal und Bildschirm).
[[ "$APP" == "tesm" ]] && "$PY" -m pip install --quiet flask-sock
if [[ "$APP" == "tesm" ]]; then
"$PY" -m pip install --quiet flask-sock || return 1
fi
return 0
}
@@ -147,6 +161,13 @@ fi
# und in einer Sekunde durch.
chmod -R a+rX "$VENV" 2>/dev/null || true
# Vertrauen ist gut: nach dem Einspielen muss die Anwendung auch importierbar
# sein. Ohne diese Probe hiesse "Fertig" nur, dass pip nicht geschimpft hat.
if ! "$PY" -c "import $MODUL" >/dev/null 2>&1; then
melden "FEHLER" "Die Pakete fuer $NAME wurden eingespielt, aber '$MODUL' laesst sich nicht importieren."
exit 1
fi
[[ "$MODUS" == "immer" ]] || melden "INFO" "Python-Umgebung von $NAME ist wieder vollstaendig."
rm -f "$MERKER" 2>/dev/null || true
exit 0
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "tesm-core"
version = "2.8.19"
version = "2.8.20"
description = "Gemeinsames Anwendungsgeruest (Auth, RBAC, Navigation, Design-System, Migrationen) fuer TESM und TESM-Lizenzserver"
requires-python = ">=3.11"
dependencies = [
@@ -193,7 +193,18 @@ def zustand_als_auskunft(state: "LicenseState") -> dict[str, Any]:
"days_left": (status.days_left if status and not status.lifetime else None),
"expiring_soon": bool(status.expiring_soon) if status else False,
"grace_active": bool(status.grace_active) if status else False,
"modules": sorted(state.modules_in_use),
# Drei Angaben, weil zwei davon verschieden sein koennen und die
# Verwechslung teuer ist: "modules" ist, was die **Lizenz** freigibt --
# dieselbe Liste, die der Lizenzserver zeigt. Was davon vor Ort
# abgeschaltet wurde, steht daneben.
#
# Vorher stand hier allein das Laufende. Beim Kunden fehlte darin
# "dhcp", waehrend die Lizenzseite es auswies -- es war lediglich unter
# Verwaltung -> System abgeschaltet. Aus der Antwort ging das nicht
# hervor, und damit sah es nach einem Lizenzproblem aus.
"modules": sorted(state.modules),
"modules_active": sorted(state.modules_in_use),
"modules_off": sorted(state.modules_off),
"customer": state.customer_name,
"last_heartbeat_at": state.last_heartbeat_at,
"error": state.error,
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "tesm-licensing"
version = "2.7.7"
version = "2.7.8"
description = "Ed25519-basiertes Lizenz- und Aktivierungsprotokoll fuer TESM und TESM-Lizenzserver"
requires-python = ">=3.11"
dependencies = ["cryptography>=42"]
@@ -46,6 +46,7 @@ from .license import (
key_id,
license_status,
parse_iso,
tage_bis,
split_bundle,
utcnow,
verify_license,
@@ -103,6 +104,7 @@ __all__ = [
"machine_identity",
"new_nonce",
"parse_iso",
"tage_bis",
"sign",
"split_bundle",
"system_fingerprint",
@@ -221,6 +221,27 @@ def verify_license(
)
def tage_bis(expires_at: datetime, *, now: datetime | None = None) -> int:
"""Restlaufzeit in **angefangenen** Tagen.
Aufgerundet, und das ist eine Entscheidung: eine Lizenz, die in zwei
Stunden ablaeuft, hat "1 Tag" und nicht "0". Null bedeutet hier
ausschliesslich *abgelaufen* -- eine gueltige Lizenz, die mit "0 Tage"
dasteht, liest sich wie eine tote.
**Es gibt genau diese eine Rechnung.** Der Lizenzserver hatte eine zweite,
abgerundete: dieselbe Lizenz zeigte in seiner Oberflaeche "21 Tage" und im
Abruf beim Kunden "22". Zwei Zahlen fuer dieselbe Frage sind schlimmer als
eine ungenaue -- sie kosten jemanden einen Nachmittag mit der Suche nach
einem Fehler, den es nicht gibt.
"""
moment = now or utcnow()
sekunden = int((expires_at - moment).total_seconds())
if sekunden <= 0:
return 0
return -(-sekunden // 86400)
def license_status(
document: Mapping[str, Any],
*,
@@ -278,7 +299,7 @@ def license_status(
days_left = 0
days_since_expiry = int((moment - expires_at).total_seconds() // 86400)
else:
days_left = -(-int(delta.total_seconds()) // 86400)
days_left = tage_bis(expires_at, now=moment)
days_since_expiry = None
grace_active = bool(