homelab: alte Kernel per Knopf entfernen; Systemplatte des Proxmox-Hosts im Blick
User-Wunsch 25.09.: Auf dem Host lagen 25 Kernel (~23 GB), 20 davon hält apt für entbehrlich (19,7 GB). - Ausführer: host.kernel_alt/kernel_bleiben aus apt-get -s autoremove (nur Kernel, nie laufender, nächster oder festgepinnter), host.systemplatte; neue Aktion kernel_aufraeumen bestimmt die Liste selbst und purgt. - Homelab-Teil: Lauf „kernel“ (Ergebnis aufgeraeumt), POST /api/homelab/ziele/pve/kernel-aufraeumen, Vorschlag mit Knopf, Wächter für die Systemplatte (80/90 %). - Oberfläche: Kachel Systemplatte, Knopf „Alte Kernel entfernen“ mit Rückfrage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
28888890ec
commit
7a456c8efa
@@ -637,12 +637,70 @@ def _host() -> dict:
|
||||
host["fehler"] = str(exc)[:200]
|
||||
code, kernel = _laufen(["uname", "-r"], 5)
|
||||
host["kernel"] = kernel if code == 0 else None
|
||||
vorhanden = [os.path.basename(p)[len("vmlinuz-"):] for p in glob.glob("/boot/vmlinuz-*")]
|
||||
host["kernel_neu"] = neuer_kernel(host["kernel"], vorhanden, _lesen("/etc/kernel/proxmox-boot-pin").strip() or None)
|
||||
pin = _lesen("/etc/kernel/proxmox-boot-pin").strip() or None
|
||||
host["kernel_neu"] = neuer_kernel(host["kernel"], _kernel_vorhanden(), pin)
|
||||
host["neustart_noetig"] = host["neustart_noetig"] or bool(host["kernel_neu"])
|
||||
host["systemplatte"] = _platte("/")
|
||||
try:
|
||||
host["kernel_alt"], host["kernel_bleiben"] = _kernel_lage(host["kernel"], host["kernel_neu"] or host["kernel"], pin)
|
||||
except Exception:
|
||||
host["kernel_alt"], host["kernel_bleiben"] = [], []
|
||||
return host
|
||||
|
||||
|
||||
def _platte(pfad: str) -> dict | None:
|
||||
"""Belegung wie df sie zeigt: belegt + was Nutzer noch schreiben dürfen (ohne die Reserve für root)."""
|
||||
try:
|
||||
s = os.statvfs(pfad)
|
||||
except (OSError, AttributeError): # AttributeError: Windows kennt statvfs nicht (Tests am PC)
|
||||
return None
|
||||
belegt = (s.f_blocks - s.f_bfree) * s.f_frsize
|
||||
return {"belegt": belegt, "gesamt": belegt + s.f_bavail * s.f_frsize}
|
||||
|
||||
|
||||
def _kernel_vorhanden() -> list[str]:
|
||||
return [os.path.basename(p)[len("vmlinuz-"):] for p in glob.glob("/boot/vmlinuz-*")]
|
||||
|
||||
|
||||
KERNEL_PAKET = re.compile(r"^(?:proxmox|pve)-kernel-(\d+\.\d+\.\d+-\d+-pve)(?:-signed)?$")
|
||||
|
||||
|
||||
def alte_kernel(simulation: str, behalten: set[str]) -> list[str]:
|
||||
"""Kernel-Pakete, die `apt-get -s autoremove` entfernen würde — nur Kernel und nie einer aus `behalten` (der
|
||||
laufende, der beim nächsten Start kommt, ein festgepinnter). Was Proxmox selbst hält (je Serie den jüngsten, den
|
||||
bei der Installation), steht gar nicht erst in der Simulation."""
|
||||
pakete = []
|
||||
for zeile in simulation.splitlines():
|
||||
m = re.match(r"^Remv (\S+)", zeile)
|
||||
k = KERNEL_PAKET.match(m.group(1)) if m else None
|
||||
if k and k.group(1) not in behalten:
|
||||
pakete.append(m.group(1))
|
||||
return pakete
|
||||
|
||||
|
||||
def _kernel_lage(laufend: str | None, naechster: str | None, pin: str | None) -> tuple[list[dict], list[str]]:
|
||||
"""(entbehrliche Kernel mit Paket, Version und belegtem Platz samt initrd; die bleibenden Versionen)."""
|
||||
code, simulation = _laufen(["apt-get", "-s", "autoremove"], 120)
|
||||
if code != 0 or not laufend:
|
||||
return [], []
|
||||
pakete = alte_kernel(simulation, {k for k in (laufend, naechster, pin) if k})
|
||||
groessen: dict[str, int] = {}
|
||||
if pakete:
|
||||
code, aus = _laufen(["dpkg-query", "-W", "-f=${Package}\t${Installed-Size}\n", *pakete], 30)
|
||||
for zeile in aus.splitlines() if code == 0 else []:
|
||||
name, _, kib = zeile.partition("\t")
|
||||
if kib.strip().isdigit():
|
||||
groessen[name] = int(kib) * 1024
|
||||
weg = []
|
||||
for paket in pakete:
|
||||
version = KERNEL_PAKET.match(paket).group(1)
|
||||
initrd = f"/boot/initrd.img-{version}"
|
||||
weg.append({"paket": paket, "version": version,
|
||||
"groesse": groessen.get(paket, 0) + (os.path.getsize(initrd) if os.path.exists(initrd) else 0)})
|
||||
entbehrlich = {w["version"] for w in weg}
|
||||
return weg, sorted((v for v in _kernel_vorhanden() if v not in entbehrlich), key=_versionsschluessel)
|
||||
|
||||
|
||||
def _versionsschluessel(version: str) -> tuple[int, ...]:
|
||||
return tuple(int(x) for x in re.findall(r"\d+", version))
|
||||
|
||||
@@ -843,11 +901,33 @@ def a_bericht(p: dict) -> tuple[int, str]:
|
||||
return 0, json.dumps(bericht(), ensure_ascii=False)
|
||||
|
||||
|
||||
def a_kernel_aufraeumen(p: dict) -> tuple[int, str]:
|
||||
"""Alte Kernel entfernen (seit 25.09.2026, nur auf Knopfdruck). Welche, bestimmt dieser Host selbst, jetzt und wie
|
||||
im Bericht — der Server kann nur den Anstoß geben. Am Ende: entfernt=<Anzahl>, frei=<Bytes>."""
|
||||
code, laufend = _laufen(["uname", "-r"], 5)
|
||||
if code != 0 or not laufend.strip():
|
||||
raise ValueError("der laufende Kernel ließ sich nicht lesen")
|
||||
laufend = laufend.strip()
|
||||
pin = _lesen("/etc/kernel/proxmox-boot-pin").strip() or None
|
||||
naechster = neuer_kernel(laufend, _kernel_vorhanden(), pin) or laufend
|
||||
weg, _ = _kernel_lage(laufend, naechster, pin)
|
||||
pakete = [w["paket"] for w in weg if w["version"] not in {laufend, naechster, pin}]
|
||||
if not pakete:
|
||||
return 0, "entfernt=0\nfrei=0\nKein alter Kernel zu entfernen."
|
||||
vorher = _platte("/") or {}
|
||||
code, text = _laufen(["env", "DEBIAN_FRONTEND=noninteractive", "apt-get", "-y", "purge", *pakete], 30 * 60,
|
||||
sanft=True)
|
||||
nachher = _platte("/") or {}
|
||||
frei = max(0, int(vorher.get("belegt") or 0) - int(nachher.get("belegt") or 0))
|
||||
return code, f"entfernt={len(pakete) if code == 0 else 0}\nfrei={frei}\n{text[-1500:]}"
|
||||
|
||||
|
||||
AKTIONEN = {"snapshot": a_snapshot, "update": a_update, "os_update": a_os_update, "suchen": a_suchen,
|
||||
"zurueck": a_zurueck,
|
||||
"snapshot_loeschen": a_snapshot_loeschen, "host_update": a_host_update,
|
||||
"host_neustart": a_host_neustart, "bericht": a_bericht,
|
||||
"sichern": a_sichern, "sicherung_zurueck": a_sicherung_zurueck, "sicherung_loeschen": a_sicherung_loeschen}
|
||||
"sichern": a_sichern, "sicherung_zurueck": a_sicherung_zurueck, "sicherung_loeschen": a_sicherung_loeschen,
|
||||
"kernel_aufraeumen": a_kernel_aufraeumen}
|
||||
|
||||
|
||||
def ausfuehren(auftrag: dict) -> dict:
|
||||
|
||||
Reference in New Issue
Block a user