homelab: „Alle aktualisieren“, Protokoll und Arcane-Schlüssel über die Oberfläche
- Sammellauf (services/homelab/sammellauf.py): alle Updates mit Knopf nacheinander über updates.starten, Gäste nach VMID, dann NPMplus und AdGuard, zuletzt die Host-Pakete (nie der Neustart). Rot stoppt den Rest (übersprungen, abgebrochen, dringende Meldung); grün gibt eine Sammelmeldung, die Erfolgsmeldungen der Schritte bleiben so lange aus. Nur einer gleichzeitig, der einzelne Knopf lehnt solange ab (START_SPERRE). Ein Neustart mitten im Lauf macht ihn beim Start zu „abgebrochen“, „unterbrochen (Neustart)“. - Protokoll (services/homelab/protokoll.py): Aufträge, Läufe, Sammelläufe, Wächter-Verlauf, Melde-Log und wöchentliches Suchen, neueste zuerst, ohne Geheimnisse. Der Kanal hebt 500 erledigte Aufträge auf und vermerkt eingehende Berichte; die Pflege führt eine kurze Ereignisliste, der Wächter-Verlauf die Stufe. - Einstellungen (services/homelab/einstellungen.py): Arcane-Schlüssel erst gegen Arcane prüfen, dann in arcane.key (0600) speichern, nie zurückgeben; echte Docker-Updates per Schalter. Die Umgebung geht vor. - Schnittstellen als eigener Block in routers/homelab.py vor den Ausführer-Endpunkten. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
bf1153cb66
commit
8ec261b8b2
@@ -8,6 +8,10 @@ für den Dienst lesbar); die Einrichtung auf dem Proxmox-Host kopiert es in /etc
|
||||
Aufträge liegen in <Datenordner>/ausfuehrer-auftraege.json und überleben so einen Neustart dieses Teils.
|
||||
Zwei Prozesse schreiben hinein: die Oberfläche (Knöpfe, Abholen durch den Ausführer) und seit 24.09.2026 der
|
||||
Steward (wöchentliches Suchen, pflege.py). Lesen, ändern, schreiben geschieht deshalb unter einer Dateisperre.
|
||||
|
||||
Erledigte Aufträge bleiben (seit 24.09.2026) bis zu BEHALTEN Stück stehen — das Protokoll der Oberfläche
|
||||
(protokoll.py) liest sie. Die jüngsten VOLLTEXT behalten ihre ganze Ausgabe, ältere nur deren Ende (TEXT_KURZ
|
||||
Zeichen, so viel zeigt das Protokoll): Die Datei wird bei jedem Auftrag gelesen und geschrieben.
|
||||
"""
|
||||
|
||||
import hmac
|
||||
@@ -31,7 +35,10 @@ except ImportError: # Windows (Entwicklung): nur die Thread-Sperre
|
||||
AKTIONEN = {"bericht", "snapshot", "update", "os_update", "suchen", "zurueck", "snapshot_loeschen",
|
||||
"host_update", "host_neustart", "sichern", "sicherung_zurueck", "sicherung_loeschen"}
|
||||
VERLOREN_S = 3 * 3600 # abgeholt, aber nie beantwortet (Ausführer abgestürzt, Host neu gestartet)
|
||||
BEHALTEN = 60 # so viele erledigte Aufträge bleiben sichtbar
|
||||
BEHALTEN = 500 # so viele erledigte Aufträge bleiben sichtbar (Protokoll; bis 24.09.2026: 60)
|
||||
VOLLTEXT = 60 # die jüngsten erledigten behalten ihre ganze Ausgabe …
|
||||
TEXT_KURZ = 4000 # … ältere nur deren letzte Zeichen
|
||||
BERICHTE_BEHALTEN = 150 # Eingänge der Berichte, die der Ausführer von sich aus schickt (alle 10 min: gut ein Tag)
|
||||
|
||||
_lock = threading.Lock()
|
||||
_kontakt: dict = {"zuletzt": None}
|
||||
@@ -110,9 +117,31 @@ def zuletzt() -> float | None:
|
||||
|
||||
# --- Bericht ------------------------------------------------------------------------------
|
||||
|
||||
def bericht_speichern(daten: dict) -> None:
|
||||
def bericht_speichern(daten: dict, aus_auftrag: bool = False) -> None:
|
||||
"""Den neuesten Bericht ablegen. Schickt ihn der Ausführer von sich aus (alle 10 min und nach jedem Auftrag),
|
||||
wird sein Eingang kurz vermerkt — das Protokoll zeigt ihn auf Wunsch. aus_auftrag: ein Lauf hat ihn als Auftrag
|
||||
„bericht“ geholt (updates.py); dann steht er dort schon."""
|
||||
jetzt = time.time()
|
||||
with _lock:
|
||||
_json_schreiben(_dir() / "pve-bericht.json", {"empfangen": time.time(), "bericht": daten})
|
||||
_json_schreiben(_dir() / "pve-bericht.json", {"empfangen": jetzt, "bericht": daten})
|
||||
if aus_auftrag:
|
||||
return
|
||||
host = daten.get("host") if isinstance(daten.get("host"), dict) else {}
|
||||
gaeste = [g for g in daten.get("gaeste") or [] if isinstance(g, dict)]
|
||||
eingang = {"empfangen": jetzt, "host_version": host.get("version"), "gaeste": len(gaeste),
|
||||
"laufend": sum(1 for g in gaeste if g.get("status") == "running"),
|
||||
"host_updates": len(host["updates"]) if isinstance(host.get("updates"), list) else None}
|
||||
pfad = _dir() / "ausfuehrer-berichte.json"
|
||||
eingaenge = _json_lesen(pfad, [])
|
||||
eingaenge = eingaenge if isinstance(eingaenge, list) else []
|
||||
_json_schreiben(pfad, (eingaenge + [eingang])[-BERICHTE_BEHALTEN:])
|
||||
|
||||
|
||||
def berichte() -> list[dict]:
|
||||
"""Die vermerkten Eingänge von Berichten (älteste zuerst): Zeit, Host-Version, Zahl der Gäste und Host-Updates."""
|
||||
with _lock:
|
||||
daten = _json_lesen(_dir() / "ausfuehrer-berichte.json", [])
|
||||
return [e for e in daten if isinstance(e, dict)] if isinstance(daten, list) else []
|
||||
|
||||
|
||||
def bericht() -> dict | None:
|
||||
@@ -136,6 +165,10 @@ def _alle() -> list[dict]:
|
||||
def _merken(auftraege: list[dict]) -> None:
|
||||
offen = [a for a in auftraege if a["status"] in ("wartet", "laeuft")]
|
||||
fertig = [a for a in auftraege if a["status"] not in ("wartet", "laeuft")][-BEHALTEN:]
|
||||
for a in fertig[:-VOLLTEXT]:
|
||||
text = a.get("text")
|
||||
if isinstance(text, str) and len(text) > TEXT_KURZ:
|
||||
a["text"] = "…" + text[-(TEXT_KURZ - 1):]
|
||||
_json_schreiben(_pfad(), sorted(offen + fertig, key=lambda a: a["erstellt"]))
|
||||
|
||||
|
||||
@@ -154,17 +187,22 @@ def anlegen(aktion: str, parameter: dict | None = None) -> str:
|
||||
|
||||
def naechster() -> dict | None:
|
||||
"""Für den Ausführer: der älteste wartende Auftrag, ab jetzt „läuft“. Hängengebliebene werden
|
||||
vorher als verloren abgeschlossen."""
|
||||
vorher als verloren abgeschlossen. Geschrieben wird nur, wenn sich etwas ändert: Der Ausführer fragt alle
|
||||
fünf Sekunden."""
|
||||
jetzt = time.time()
|
||||
with _sperre():
|
||||
auftraege = _alle()
|
||||
geaendert = False
|
||||
for a in auftraege:
|
||||
if a["status"] == "laeuft" and jetzt - (a["abgeholt"] or jetzt) > VERLOREN_S:
|
||||
a.update(status="verloren", fertig=jetzt, text="Der Ausführer hat nie geantwortet.")
|
||||
geaendert = True
|
||||
auftrag = next((a for a in auftraege if a["status"] == "wartet"), None)
|
||||
if auftrag:
|
||||
auftrag.update(status="laeuft", abgeholt=jetzt)
|
||||
_merken(auftraege)
|
||||
geaendert = True
|
||||
if geaendert:
|
||||
_merken(auftraege)
|
||||
return {k: auftrag[k] for k in ("id", "aktion", "parameter")} if auftrag else None
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user