homelab: eigener Reiter „Snapshots“ mit Verwaltung (anlegen, löschen, zurücksetzen)
Die Seite /homelab/snapshots (in der Seitenleiste nach „Updates“: Snapshots sind der Rückweg der Updates) zeigt je Gerät die Snapshots mit Zeitpunkt, Alter, Herkunft (vor Update / per Knopf / von Hand) und Beschreibung, beim PBS die Sicherungen des Orchestrators mit Größe. Knöpfe „Snapshot anlegen“, „Zurücksetzen“ und „Löschen“ gibt es nur für freigegebene Geräte und nur für mc2-Snapshots bzw. eigene Sicherungen; von Hand angelegte bleiben reine Ansicht. Die Rückfragen kommen vom Homelab-Teil (wie im Ziel-Modell); Zurücksetzen sagt vorher, was verloren geht und dass das Gerät neu startet. Am Handy (320–400 px) untereinander, ab md Knöpfe rechts, ab xl fünf Spalten. Entscheide: - Ausführer: kein neuer Befehl. Der Bericht bringt zusätzlich snapshot_details (snaptime, Beschreibung, eigen) und sicherung_details (ctime, Größe); snapshots und sicherungen bleiben unverändert. Keine Belegung je Snapshot — die kennt Proxmox bei LVM-Thin nicht, die Seite sagt das so. „snapshot“ nimmt einen Anlass (update oder knopf) und wählt damit eine feste Beschreibung, damit ein Snapshot auf Knopfdruck nicht „Vor einem Update“ heißt. Bis der neue Ausführer eingespielt ist, liest der Homelab-Teil den Zeitpunkt aus dem Namen. - Aktionen sind Läufe in homelab-laeufe.json (Mechanik aus updates.py): gleiche Sperren (START_SPERRE, je Gerät einer, SAMMELLAUF_SPERRE), Protokoll-Art „snapshot“ mit eigenen Titeln, der Wächter lässt ein Gerät im Zurücksetzen in Ruhe. Zusätzlich keine Aktion, solange irgendwo ein Update läuft: Der Ausführer arbeitet einen Auftrag nach dem anderen ab — hinter einem Update liefe die Aktion in ihr Zeitlimit und käme danach unbeobachtet doch noch dran. - Zurücksetzen prüft danach wie ein Update und meldet immer ([Homelab-Snapshot], bei Rot als Alarm); anlegen und löschen melden nur ihr Scheitern, das Ergebnis steht beim Gerät. Nicht im Update-Verlauf, nicht unter „Zuletzt eingespielt“. - Per Knopf angelegte Snapshots räumt kein grünes Update weg (updates._aufraeumen fragt snapshots.knopf_namen). - speicher.py: alle Snapshots in einem Vorschlag mit Verweis auf die Seite statt einer je Snapshot. - Nebenbei: Der Update-Verlauf stürzte bei einem Ergebnis ohne Eintrag ab (etwa „offen“ nach einem Docker-Probelauf); die Protokoll-Seite stürzte im Entwicklungsmodus ab (Konstante „Symbol“ gegen das Symbol.for des React Compilers); Auftragstitel „Alter Snapshot gelöscht“ heißt jetzt „Snapshot gelöscht“ (gelöscht wird auch von Hand). Tests: pytest test_homelab_snapshots.py (Ausführer-Details, Liste, Läufe mit nachgespieltem Ausführer, Sperren, Aufräumen, Schnittstelle), test_homelab_speicher.py; Vitest SnapshotListe, lib/snapshots, Speicher. Prüftor grün. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2784c0ea32
commit
450c782ed3
@@ -4,7 +4,7 @@ Alles aus dem Bericht des Ausführers (alle 10 Minuten), dazu eine eigene Prüfu
|
||||
• Speicherpool (Thin-LVM, heute local-lvm): Läuft er voll, stehen alle Gäste darauf still. Gelb ab 80 %, rot ab
|
||||
90 % — also bei 10 % frei (User-Vorgabe) —, für Daten wie Metadaten. Dazu Empfehlungen, was Platz bringt,
|
||||
der größte Gewinn zuerst: Platz, den ein Gast längst freigegeben hat (TRIM), ungenutzte und verwaiste Platten,
|
||||
Snapshots.
|
||||
Snapshots (seit 25.09.2026 in einem Vorschlag, verwaltet auf der Seite Homelab → Snapshots).
|
||||
• NAS: jedes Netzlaufwerk des Proxmox-Hosts (heute das QNAP per NFS, Ziel der nächtlichen Sicherungen): antwortet,
|
||||
ist eingebunden, hängt nicht, hat Platz (80/90 % wie oben).
|
||||
• Sicherungen: Für jeden Gast, den ein Sicherungsauftrag erfasst, liegt auf dessen Ziel eine frische Sicherung
|
||||
@@ -23,6 +23,7 @@ from datetime import datetime
|
||||
from kern.zeit import LOCAL_TZ
|
||||
|
||||
from services.homelab import apps, kanal
|
||||
from services.homelab.inventar import EIGENES_ETIKETT
|
||||
|
||||
GELB, ROT = 0.80, 0.90
|
||||
EMPFEHLUNG_AB = 1_000_000_000 # kleinere Gewinne sind keine eigene Empfehlung wert
|
||||
@@ -135,20 +136,50 @@ def empfehlungen(bericht: dict) -> list[dict]:
|
||||
if kernel := _kernel_vorschlag(bericht.get("host") or {}):
|
||||
liste.append(kernel)
|
||||
liste.sort(key=lambda e: -(e["platz"] or 0))
|
||||
for g in gaeste:
|
||||
for s in g.get("snapshots") or []:
|
||||
m = re.fullmatch(r"mc2-(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})\d{2}", s)
|
||||
wann = f"vom {m.group(3)}.{m.group(2)}. {m.group(4)}:{m.group(5)}" if m else "von Hand angelegt"
|
||||
liste.append({
|
||||
"art": "snapshot", "platz": None, "titel": f"{_name(g)}: Snapshot {s}", "kurz": f"Snapshot bei {_name(g)}",
|
||||
"ort": "pool",
|
||||
"text": ((f"Rückweg des Updates {wann}. Läuft {_name(g)} seitdem gut, kann er weg" if m else
|
||||
f"Snapshot {wann}. Wird er nicht mehr gebraucht, kann er weg")
|
||||
+ f": in Proxmox {_wo(g)} → Snapshots → {s} → Entfernen. Wie viel er belegt, sagt Proxmox "
|
||||
"nicht — bei Containern meist wenig, er wächst mit jeder Änderung seitdem.")})
|
||||
if snapshots := _snapshot_vorschlag(gaeste):
|
||||
liste.append(snapshots)
|
||||
return liste
|
||||
|
||||
|
||||
def _geraete_text(namen: list[str]) -> str:
|
||||
"""„Gitea (2), NetBird und AdGuard Home“ — je Gerät einmal, mit der Zahl, wo es mehrere sind."""
|
||||
teile = [f"{n} ({namen.count(n)})" if namen.count(n) > 1 else n for n in dict.fromkeys(namen)]
|
||||
return teile[0] if len(teile) == 1 else ", ".join(teile[:-1]) + " und " + teile[-1]
|
||||
|
||||
|
||||
def _snapshot_vorschlag(gaeste: list[dict]) -> dict | None:
|
||||
"""Alle Snapshots in EINEM Vorschlag (seit 25.09.2026; vorher einer je Snapshot): Verwalten lassen sie sich auf der
|
||||
Seite Homelab → Snapshots, mit Zeitpunkt und Knöpfen. Der Container des Orchestrators selbst fehlt dort wie hier.
|
||||
Wie viel sie belegen, sagt Proxmox nicht — deshalb ohne Größe und zuletzt."""
|
||||
eigene: list[str] = []
|
||||
von_hand: list[str] = []
|
||||
for g in gaeste:
|
||||
if EIGENES_ETIKETT in (g.get("etiketten") or []):
|
||||
continue
|
||||
for s in g.get("snapshots") or []:
|
||||
(eigene if re.fullmatch(r"mc2-\d{8}-\d{6}", str(s)) else von_hand).append(_name(g))
|
||||
anzahl = len(eigene) + len(von_hand)
|
||||
if not anzahl:
|
||||
return None
|
||||
if eigene:
|
||||
titel = (f"{len(eigene)} Snapshot{'s' if len(eigene) != 1 else ''} des Orchestrators — verwalten unter "
|
||||
"Homelab → Snapshots")
|
||||
text = (f"Bei {_geraete_text(eigene)}. Läuft ein Gerät seit seinem Snapshot gut, kann der weg — auf der Seite "
|
||||
"Snapshots per Knopf, dort auch mit Zeitpunkt und dem Weg zurück.")
|
||||
else:
|
||||
titel = (f"{len(von_hand)} von Hand angelegte{'r' if len(von_hand) == 1 else ''} "
|
||||
f"Snapshot{'s' if len(von_hand) != 1 else ''} — ansehen unter Homelab → Snapshots")
|
||||
text = f"Bei {_geraete_text(von_hand)}."
|
||||
if eigene and von_hand:
|
||||
text += f" Dazu {len(von_hand)} von Hand in Proxmox angelegt ({_geraete_text(von_hand)})."
|
||||
if von_hand:
|
||||
text += " Von Hand angelegte löscht der Orchestrator nicht; das geht in Proxmox."
|
||||
text += (" Wie viel ein Snapshot belegt, sagt Proxmox nicht — bei Containern meist wenig, er wächst mit jeder "
|
||||
"Änderung seitdem.")
|
||||
return {"art": "snapshot", "platz": None, "ort": "pool", "titel": titel, "text": text,
|
||||
"kurz": f"{anzahl} Snapshot{'s' if anzahl != 1 else ''} (Homelab → Snapshots)"}
|
||||
|
||||
|
||||
def _kernel_vorschlag(host: dict) -> dict | None:
|
||||
"""Alte Kernel auf der Systemplatte des Hosts — mit Knopf. Die Liste stammt vom Ausführer (was apt für entbehrlich
|
||||
hält, nie der laufende oder der nächste); beim Klick bestimmt er sie noch einmal selbst."""
|
||||
|
||||
Reference in New Issue
Block a user