Welle 2 · Sicherheits-Markierung: Debian-Sicherheitsupdates, Alpine über secdb, Lücken-Scans aus Arcane

Der Ausführer zählt je Gast, wie viele offene Updates aus einer Sicherheits-Suite kommen („-security“ in apt list),
beim Proxmox-Host ebenso; für Alpine (NPMplus) schickt er die Paketliste, und der Homelab-Teil gleicht sie mit Alpines
Sicherheitsdatenbank ab (im Hintergrund geladen, zwölf Stunden gemerkt, Alpine-Versionsvergleich). Die Update-Tabelle,
die Kacheln und die Übersicht zeigen „· 3 Sicherheit“, die Rückfrage nennt es. Je Docker-Container steht, was Arcanes
Lücken-Scan (Trivy, sonntags 02:00) im Image fand, kritische und hohe. Der Ausführer braucht ausfuehrer-einrichten.sh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-25 18:02:33 +02:00
co-authored by Claude Opus 5.5
parent 995a0f34ee
commit 1c66daad0b
13 changed files with 375 additions and 17 deletions
@@ -31,14 +31,26 @@ function zustandText(c: DockerContainer): string {
/** Aus „ghcr.io/getarcaneapp/manager:latest“ wird „manager“ — für das Logo. */
const imageName = (image: string | null) => (image ?? "").split("/").at(-1)?.split(":")[0] ?? ""
/** „2 kritische, 5 hohe Lücken“ aus Arcanes Scan (seit 25.09.2026) — nur, wenn es kritische oder hohe gibt. */
function lueckenText(c: DockerContainer): string | null {
const l = c.luecken
if (!l || (!l.kritisch && !l.hoch)) return null
const teile = [l.kritisch ? `${l.kritisch} kritische` : null, l.hoch ? `${l.hoch} hohe` : null].filter(Boolean)
return `${teile.join(", ")} Lücken`
}
function Zeile({ c }: { c: DockerContainer }) {
const u = UPDATE[c.update] ?? UPDATE.unbekannt
const luecken = lueckenText(c)
return (
<li className="flex min-w-0 items-center gap-3 border-t border-white/[0.06] py-2.5 first:border-t-0">
<Geraetesymbol bild={geraetebild({ art: "container", name: c.dienst ?? imageName(c.image) })} groesse="sm" punkt={punkt(c)} />
<span className="flex min-w-0 flex-1 flex-col">
<span className="truncate text-[14.5px] font-medium">{c.name}</span>
<span className="truncate text-[12.5px] text-text-3">{c.image}</span>
{luecken && (
<span className={cn("truncate text-[12.5px]", c.luecken?.kritisch ? "text-rot-text" : "text-bernstein")}>{luecken}</span>
)}
</span>
<span className="flex shrink-0 flex-col items-end text-right">
<span className={cn("text-[13px]", punkt(c) === "rot" ? "text-rot-text" : "text-text-2")}>{zustandText(c)}</span>
@@ -42,7 +42,14 @@ function PaketZelle({ b, onSuchen, gesperrt }: { b: BausteinStand | null; onSuch
if (!b) return <span className="text-sm text-text-3">–</span>
const docker = b.id === "docker"
let text
if (b.zustand === "neu") text = <span className="text-cyan-text">{b.kurz || "Update bereit"}</span>
if (b.zustand === "neu") {
text = (
<span className="text-cyan-text">
{b.kurz || "Update bereit"}
{!!b.sicherheit && <span className="text-rot-text"> · {b.sicherheit} Sicherheit</span>}
</span>
)
}
else if (b.zustand === "aktuell") text = <span className="text-gruen-text">{docker ? "Images aktuell" : "aktuell"}</span>
else {
const datum = listenAlter(b.grund)
+10 -1
View File
@@ -1,4 +1,4 @@
import { kennzahlText, lage, lampeFuer, listenAlter, offeneUpdates, rueckwegText } from "@/lib/homelab"
import { kennzahlText, lage, lampeFuer, listenAlter, offeneUpdates, rueckwegText, sicherheitText } from "@/lib/homelab"
import type { BausteinStand, Hinweis, HomelabStand, ZielStand } from "@/lib/typen"
const baustein = (b: Partial<BausteinStand> & Pick<BausteinStand, "id" | "zustand">): BausteinStand => ({
@@ -110,3 +110,12 @@ describe("kennzahlText (seit 25.09.2026)", () => {
expect(kennzahlText(undefined)).toBeNull()
})
})
describe("Sicherheits-Markierung (seit 25.09.2026)", () => {
it("hängt die Zahl der Sicherheitsupdates an", () => {
const pakete = baustein({ id: "os", zustand: "neu", kurz: "12 Pakete", sicherheit: 3 })
expect(sicherheitText(pakete)).toBe(" · 3 Sicherheit")
expect(lampeFuer(ziel({ id: "ct-104", name: "Gitea", bausteine: [pakete] }), false).wert).toBe("12 Pakete · 3 Sicherheit")
expect(sicherheitText(baustein({ id: "os", zustand: "neu", kurz: "2 Pakete", sicherheit: 0 }))).toBe("")
})
})
+6 -1
View File
@@ -95,7 +95,12 @@ export const versionText = (v: string) => v.replace(/^Image vom /, "")
/** Was die Versionsspalte „Neu“ zeigt: die neue Version einer App, sonst „58 Pakete“. */
export function neuText(b: BausteinStand): string {
if (istApp(b) && b.verfuegbar) return b.verfuegbar
return b.kurz || b.verfuegbar || "neu"
return `${b.kurz || b.verfuegbar || "neu"}${sicherheitText(b)}`
}
/** „ · 3 Sicherheit“ — wenn Updates eine Sicherheitslücke schließen (seit 25.09.2026), sonst leer. */
export function sicherheitText(b: BausteinStand): string {
return b.sicherheit ? ` · ${b.sicherheit} Sicherheit` : ""
}
/** Anteil der belegten Platte (0…1) oder null, wo unbekannt. Ab 80 % gelb, ab 90 % rot — wie der Wächter (ext4 hält 5 % zurück). */
+4
View File
@@ -306,6 +306,8 @@ export interface BausteinStand {
kurz: string
grund: string | null
aktion: ZielAktion | null
/** Davon Sicherheitsupdates (seit 25.09.2026; null/fehlt = unbekannt). */
sicherheit?: number | null
}
/** Ein Gerät (KI-Box, Proxmox-Host, Container, VM) mit seinen Bausteinen. */
@@ -697,6 +699,8 @@ export interface DockerContainer {
update: "neu" | "lokal" | "aktuell" | "unbekannt"
aktuell: string | null
neu: string | null
/** Arcanes Lücken-Scan des Images (seit 25.09.2026); null = noch nicht gescannt. */
luecken?: { kritisch: number; hoch: number; mittel: number; gesamt: number; zeit: string } | null
}
export interface DockerLage {
+2 -2
View File
@@ -1,4 +1,4 @@
import { systemBaustein, appBaustein } from "@/lib/homelab"
import { appBaustein, sicherheitText, systemBaustein } from "@/lib/homelab"
import type { BausteinStand, ZielStand } from "@/lib/typen"
// Die Kurzform für die Übersicht: wer hat wo welche Updates — in einer Zeile je Gerät.
@@ -32,7 +32,7 @@ export function kurzform(ziel: ZielStand): KurzZeile | null {
teile.push(`App → ${app.verfuegbar ?? "neu"}${w ? ` (${w})` : ""}`)
}
const sys = systemBaustein(ziel)
if (sys?.zustand === "neu") teile.push(sys.kurz || (sys.id === "docker" ? "Images" : "Pakete"))
if (sys?.zustand === "neu") teile.push(`${sys.kurz || (sys.id === "docker" ? "Images" : "Pakete")}${sicherheitText(sys)}`)
// KI-Box: Bausteine heißen selbst (Betriebssystem, Hermes, Motor …).
for (const b of neu) {
if (b === app || b === sys) continue