Betriebs-Luecken: Restore-Probe, Venv-Audit, Disk-Waechter, PC-Watch
Scope-Entscheid 17.07.: MC2 bleibt Box-Zentrale (KEINE Homelab-Zentrale) - Infra-Radar (Proxmox/PBS/QNAP/Gitea) geht als eigenes Projekt an Lucys Queue. - restore-probe.sh (Cron 1. d. M. 06:40, no-agent): stellt WIRKLICH wieder her - PBS-Canary aus hermes.pxar (selektiv via --pattern, sudo-frei ueber die User-Unit-Zugaenge) + Tarball-Probe + Frische-Check <36h; Erstlauf-Fallbacks (llamaswap.pxar / Kern-Datei) bis der Canary in den Sicherungen ankommt. - venv-audit.sh (Cron 2. d. M. 06:40, no-agent): pip outdated + pip-audit-CVEs fuer mem0-/voice-venv ueber eigenes Audit-venv; Funde -> Kanban-Karte je Monat, Update bleibt Commander-Entscheid. - self-smoke: Disk-Waechter (Check 7, Schwelle 85%, nennt die groessten Brocken). - Trend-Radar-Watchlist: neue Typen github_release_major + pypi; Eintraege electron-major (PC-Shell auf 33!) und pocket-tts (PyPI-Versionswatch). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+6
-1
@@ -121,6 +121,11 @@ cp "$SRC/deploy/karten-gutachter.sh" "$HOME/.hermes/scripts/karten-gutachter.sh"
|
||||
# Harness + Suiten liegen im Repo ($SRC/deploy/pruefstand/), nur der Runner wird kopiert.
|
||||
cp "$SRC/deploy/trend-radar-feed.sh" "$HOME/.hermes/scripts/trend-radar-feed.sh"
|
||||
cp "$SRC/deploy/pruefstand/pruefstand.sh" "$HOME/.hermes/scripts/pruefstand.sh"
|
||||
# Restore-Probe + Venv-Audit (17.07.2026, monatliche --no-agent-Crons): echte
|
||||
# Wiederherstellungs-Drills (PBS-Canary + Tarball) und CVE-/Outdated-Audit der
|
||||
# Python-Nebendienst-venvs (mem0, voice) — melden nur, ändern nichts.
|
||||
cp "$SRC/deploy/restore-probe.sh" "$HOME/.hermes/scripts/restore-probe.sh"
|
||||
cp "$SRC/deploy/venv-audit.sh" "$HOME/.hermes/scripts/venv-audit.sh"
|
||||
# Werkstatt-Kanban-Profil (Ideen-Queue, 10.07.2026): Leitplanken des Worker-Profils nachziehen
|
||||
# (Profil selbst wurde einmalig per `hermes profile create werkstatt --clone` angelegt).
|
||||
[ -d "$HOME/.hermes/profiles/werkstatt" ] && cp "$SRC/deploy/werkstatt-SOUL.md" "$HOME/.hermes/profiles/werkstatt/SOUL.md"
|
||||
@@ -128,7 +133,7 @@ cp "$SRC/deploy/pruefstand/pruefstand.sh" "$HOME/.hermes/scripts/pruefstand.sh"
|
||||
# statt Lucy-default. Profil selbst wird einmalig per `hermes profile create betrieb
|
||||
# --clone-from werkstatt` angelegt (model coder→hermes, Description fürs Specifier-Routing).
|
||||
[ -d "$HOME/.hermes/profiles/betrieb" ] && cp "$SRC/deploy/betrieb-SOUL.md" "$HOME/.hermes/profiles/betrieb/SOUL.md"
|
||||
chmod +x "$HOME/.hermes/scripts/fremdblick.sh" "$HOME/.hermes/scripts/dreaming-feed.sh" "$HOME/.hermes/scripts/worker.sh" "$HOME/.hermes/scripts/chef-gutachter-feed.sh" "$HOME/.hermes/scripts/hermes-release-radar-feed.sh" "$HOME/.hermes/scripts/radar-selbstkritik-wrapper.sh" "$HOME/.hermes/scripts/idle-radar-feed.sh" "$HOME/.hermes/scripts/selbst-inventur.sh" "$HOME/.hermes/scripts/karten-gutachter.sh" "$HOME/.hermes/scripts/trend-radar-feed.sh" "$HOME/.hermes/scripts/pruefstand.sh"
|
||||
chmod +x "$HOME/.hermes/scripts/fremdblick.sh" "$HOME/.hermes/scripts/dreaming-feed.sh" "$HOME/.hermes/scripts/worker.sh" "$HOME/.hermes/scripts/chef-gutachter-feed.sh" "$HOME/.hermes/scripts/hermes-release-radar-feed.sh" "$HOME/.hermes/scripts/radar-selbstkritik-wrapper.sh" "$HOME/.hermes/scripts/idle-radar-feed.sh" "$HOME/.hermes/scripts/selbst-inventur.sh" "$HOME/.hermes/scripts/karten-gutachter.sh" "$HOME/.hermes/scripts/trend-radar-feed.sh" "$HOME/.hermes/scripts/pruefstand.sh" "$HOME/.hermes/scripts/restore-probe.sh" "$HOME/.hermes/scripts/venv-audit.sh"
|
||||
# Werkstatt-Skill (Autonomie E6): Selbstwartungs-Kreislauf für Hermes.
|
||||
mkdir -p "$HOME/.hermes/skills/wartung"
|
||||
cp "$SRC/deploy/skills/wartung/SKILL.md" "$HOME/.hermes/skills/wartung/SKILL.md"
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env bash
|
||||
# Restore-Probe (monatlich, 17.07.2026): „Backups sind Hoffnung, Restores sind Wissen."
|
||||
# Spielt ECHTE Wiederherstellungen durch, statt nur Sicherungs-Erfolge zu zählen:
|
||||
# 1) PBS: jüngsten host/aibox-Snapshot ermitteln (Alter < 36 h!), daraus den
|
||||
# Kanarienvogel (state/backup-canary.txt aus hermes.pxar, selektiv via --pattern —
|
||||
# ~/.hermes ist ~8 GB, wir ziehen nur die eine Datei) nach /tmp restaurieren und
|
||||
# den Inhalt EXAKT vergleichen. Erstlauf-Fallback (Canary noch in keinem
|
||||
# Snapshot): das winzige llamaswap.pxar komplett restaurieren und config.yaml
|
||||
# auf Substanz prüfen.
|
||||
# 2) Tarball: jüngstes mc2-state-*.tar.gz (Alter < 36 h) listen und den Canary
|
||||
# (bzw. beim Erstlauf eine Kern-Datei) herausziehen und prüfen.
|
||||
# 3) Canary für künftige Läufe sicherstellen (fester Inhalt → exakter Vergleich).
|
||||
# Läuft als Hermes-Cron --no-agent (stdout = Telegram): monatlicher Herzschlag, bei
|
||||
# Problemen ⚠️-Alarmtext. Komplett ohne sudo (PBS-Env + Client liegen im User-Raum).
|
||||
set -uo pipefail
|
||||
|
||||
PBS_ENV="$HOME/.config/pbs-backup/env"
|
||||
PBS_BIN="$HOME/bin/proxmox-backup-client"
|
||||
CANARY="$HOME/.hermes/state/backup-canary.txt"
|
||||
CANARY_SOLL="MC2-Backup-Kanarienvogel v1 — dieser Inhalt ist konstant und wird nach dem Restore exakt verglichen."
|
||||
TARBALL_DIR="/srv/models/mc2-backups"
|
||||
MAX_ALTER_H=36
|
||||
|
||||
TMP="$(mktemp -d /tmp/restore-probe.XXXXXX)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
FEHLER=()
|
||||
BEFUND=()
|
||||
|
||||
briefkasten() { # $1=Text (stiller Chronik-Spiegel)
|
||||
curl -sf -m 5 -X POST "${MC_ANNOUNCE_URL:-http://127.0.0.1:9001/api/voice/announce}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data "$(python3 - "$1" <<'PY'
|
||||
import json, sys
|
||||
print(json.dumps({"text": sys.argv[1], "subject": "Restore-Probe",
|
||||
"source": "restore-probe", "priority": "silent"}))
|
||||
PY
|
||||
)" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
# ---------- 0) Canary sicherstellen (für DIESEN Lauf evtl. noch nicht im Backup) ----------
|
||||
if [ ! -f "$CANARY" ] || [ "$(cat "$CANARY" 2>/dev/null)" != "$CANARY_SOLL" ]; then
|
||||
printf '%s' "$CANARY_SOLL" > "$CANARY"
|
||||
fi
|
||||
|
||||
# ---------- 1) PBS-Restore-Probe ----------
|
||||
if [ ! -f "$PBS_ENV" ] || [ ! -x "$PBS_BIN" ]; then
|
||||
FEHLER+=("PBS-Probe unmöglich: Env ($PBS_ENV) oder Client ($PBS_BIN) fehlt")
|
||||
else
|
||||
set -a; source "$PBS_ENV"; set +a
|
||||
SNAP_JSON="$("$PBS_BIN" snapshot list host/aibox --output-format json 2>/dev/null \
|
||||
|| "$PBS_BIN" snapshots --output-format json 2>/dev/null || true)"
|
||||
SNAP_INFO="$(printf '%s' "$SNAP_JSON" | python3 -c '
|
||||
import json, sys, time
|
||||
try:
|
||||
snaps = json.load(sys.stdin)
|
||||
except Exception:
|
||||
print("KEINE"); raise SystemExit
|
||||
snaps = [s for s in snaps if isinstance(s, dict)
|
||||
and s.get("backup-type") == "host" and s.get("backup-id") == "aibox"]
|
||||
if not snaps:
|
||||
print("KEINE"); raise SystemExit
|
||||
s = max(snaps, key=lambda x: x.get("backup-time") or 0)
|
||||
t = s.get("backup-time") or 0
|
||||
alter_h = (time.time() - t) / 3600
|
||||
stamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(t))
|
||||
print(f"host/aibox/{stamp}")
|
||||
print(round(alter_h, 1))
|
||||
' 2>/dev/null || echo KEINE)"
|
||||
SNAP="$(printf '%s\n' "$SNAP_INFO" | sed -n 1p)"
|
||||
ALTER_H="$(printf '%s\n' "$SNAP_INFO" | sed -n 2p)"
|
||||
if [ "$SNAP" = "KEINE" ] || [ -z "$SNAP" ]; then
|
||||
FEHLER+=("PBS: kein host/aibox-Snapshot auffindbar (Sicherung steht evtl. STILL)")
|
||||
else
|
||||
if python3 -c "import sys; sys.exit(0 if float('$ALTER_H') <= $MAX_ALTER_H else 1)" 2>/dev/null; then
|
||||
BEFUND+=("PBS-Snapshot $SNAP ist ${ALTER_H} h alt (frisch)")
|
||||
else
|
||||
FEHLER+=("PBS: jüngster Snapshot ist ${ALTER_H} h alt (> ${MAX_ALTER_H} h) — Sicherung steht STILL")
|
||||
fi
|
||||
# Selektives Canary-Restore aus hermes.pxar
|
||||
mkdir -p "$TMP/pbs"
|
||||
if "$PBS_BIN" restore "$SNAP" hermes.pxar "$TMP/pbs" \
|
||||
--pattern "state/backup-canary.txt" >/dev/null 2>&1 \
|
||||
&& [ -f "$TMP/pbs/state/backup-canary.txt" ]; then
|
||||
if [ "$(cat "$TMP/pbs/state/backup-canary.txt")" = "$CANARY_SOLL" ]; then
|
||||
BEFUND+=("PBS-Restore: Canary aus hermes.pxar wiederhergestellt, Inhalt EXAKT korrekt ✅")
|
||||
else
|
||||
FEHLER+=("PBS-Restore: Canary wiederhergestellt, aber Inhalt WEICHT AB (Datenkorruption?)")
|
||||
fi
|
||||
else
|
||||
# Erstlauf-Fallback: winziges llamaswap.pxar komplett
|
||||
mkdir -p "$TMP/ls"
|
||||
if "$PBS_BIN" restore "$SNAP" llamaswap.pxar "$TMP/ls" >/dev/null 2>&1 \
|
||||
&& [ -s "$TMP/ls/config.yaml" ] && grep -q 'models:' "$TMP/ls/config.yaml"; then
|
||||
BEFUND+=("PBS-Restore: llamaswap.pxar wiederhergestellt, config.yaml plausibel ✅ (Canary war noch nicht im Snapshot — ab dem nächsten dabei)")
|
||||
else
|
||||
FEHLER+=("PBS-Restore FEHLGESCHLAGEN: weder Canary noch llamaswap.pxar sauber wiederherstellbar")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------- 2) Tarball-Probe ----------
|
||||
TARBALL="$(ls -1t "$TARBALL_DIR"/mc2-state-*.tar.gz 2>/dev/null | head -1 || true)"
|
||||
if [ -z "$TARBALL" ]; then
|
||||
FEHLER+=("Tarball: keine mc2-state-Sicherung unter $TARBALL_DIR gefunden")
|
||||
else
|
||||
T_ALTER_H="$(python3 -c "import os, time; print(round((time.time()-os.path.getmtime('$TARBALL'))/3600, 1))")"
|
||||
if python3 -c "import sys; sys.exit(0 if float('$T_ALTER_H') <= $MAX_ALTER_H else 1)"; then
|
||||
BEFUND+=("Tarball $(basename "$TARBALL") ist ${T_ALTER_H} h alt (frisch)")
|
||||
else
|
||||
FEHLER+=("Tarball: jüngste Sicherung ist ${T_ALTER_H} h alt (> ${MAX_ALTER_H} h) — mc2-backup steht STILL")
|
||||
fi
|
||||
CANARY_PFAD="$(tar -tzf "$TARBALL" 2>/dev/null | grep 'backup-canary\.txt$' | head -1 || true)"
|
||||
if [ -n "$CANARY_PFAD" ]; then
|
||||
if [ "$(tar -xzf "$TARBALL" -O "$CANARY_PFAD" 2>/dev/null)" = "$CANARY_SOLL" ]; then
|
||||
BEFUND+=("Tarball-Restore: Canary herausgezogen, Inhalt EXAKT korrekt ✅")
|
||||
else
|
||||
FEHLER+=("Tarball-Restore: Canary im Archiv, aber Inhalt WEICHT AB")
|
||||
fi
|
||||
else
|
||||
KERN="$(tar -tzf "$TARBALL" 2>/dev/null | grep -E 'cron/|state/' | head -1 || true)"
|
||||
if [ -n "$KERN" ] && [ -n "$(tar -xzf "$TARBALL" -O "$KERN" 2>/dev/null | head -c 1)" ]; then
|
||||
BEFUND+=("Tarball-Restore: Kern-Datei ($KERN) herausgezogen, nicht leer ✅ (Canary ab der nächsten Sicherung dabei)")
|
||||
else
|
||||
FEHLER+=("Tarball-Restore FEHLGESCHLAGEN: Archiv listet keine Kern-Dateien oder Extraktion leer")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------- Bericht (stdout = Telegram via --no-agent) ----------
|
||||
if [ "${#FEHLER[@]}" -eq 0 ]; then
|
||||
echo "Commander, monatliche Restore-Probe: beide Wiederherstellungs-Wege funktionieren wirklich."
|
||||
printf '• %s\n' "${BEFUND[@]}"
|
||||
briefkasten "Restore-Probe grün: $(printf '%s · ' "${BEFUND[@]}")"
|
||||
else
|
||||
echo "⚠️ Commander, die monatliche Restore-Probe hat ein ECHTES Problem gefunden:"
|
||||
printf '• %s\n' "${FEHLER[@]}"
|
||||
if [ "${#BEFUND[@]}" -gt 0 ]; then
|
||||
echo "Was funktioniert hat:"
|
||||
printf '• %s\n' "${BEFUND[@]}"
|
||||
fi
|
||||
echo "Bitte zeitnah draufschauen — ein Backup, das nicht zurückkommt, ist keins."
|
||||
briefkasten "⚠️ Restore-Probe ROT: $(printf '%s · ' "${FEHLER[@]}")"
|
||||
exit 1
|
||||
fi
|
||||
@@ -158,6 +158,21 @@ else
|
||||
FAILED+=("Browser-Tool (agent-browser fehlt — npm install -g agent-browser && agent-browser install --with-deps)")
|
||||
fi
|
||||
|
||||
# ── 7) Disk-Wächter (17.07.2026): /srv/models sammelt Modelle, Backups + Vault wachsen ──
|
||||
# Schwelle 85 % — meldet mit den größten Brocken, damit die Aufräum-Entscheidung leicht fällt.
|
||||
DISK_SCHWELLE="${DISK_SCHWELLE:-85}"
|
||||
while read -r FS NUTZUNG ZIEL; do
|
||||
PCT="${NUTZUNG%\%}"
|
||||
if [ "${PCT:-0}" -ge "$DISK_SCHWELLE" ]; then
|
||||
BROCKEN="$(du -s --block-size=1G /srv/models/*/ 2>/dev/null | sort -rn | head -3 \
|
||||
| awk '{printf "%s GB %s · ", $1, $2}' || true)"
|
||||
say "FAIL · Disk: $ZIEL ist zu $NUTZUNG voll (Schwelle ${DISK_SCHWELLE} %)"
|
||||
FAILED+=("Disk ($ZIEL zu $NUTZUNG voll — größte Brocken: ${BROCKEN:-unbekannt}Kandidat: rollenlose Fallback-Modelle prüfen)")
|
||||
else
|
||||
say "OK · Disk: $ZIEL bei $NUTZUNG"
|
||||
fi
|
||||
done < <(df --output=source,pcent,target / /srv/models 2>/dev/null | tail -n +2 | sort -u -k1,1)
|
||||
|
||||
# ── Selbstheilungen nur melden, nicht alarmieren ─────────────────────────────
|
||||
if [ "${#HEALED[@]}" -gt 0 ]; then
|
||||
HEAL_MSG="Commander, mein Selbst-Test hat etwas gefunden und direkt selbst repariert:
|
||||
|
||||
@@ -239,7 +239,7 @@ echo
|
||||
# =====================================================================
|
||||
echo "### 3. WATCH-LISTE (mechanisch geprüft)"
|
||||
python3 - "$WATCHLIST" "$STATE" "$TMPD/watch.txt" <<'PY'
|
||||
import json, sys, urllib.request
|
||||
import json, re, sys, urllib.request
|
||||
|
||||
def hole(url, timeout=30):
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "mc2-trend-radar"})
|
||||
@@ -272,6 +272,22 @@ for e in eintraege:
|
||||
neu = " ⚠️ NEU seit letzter Woche!" if tag != gesehen.get(key) else ""
|
||||
zeilen.append(f"- {key}: neuester Release {tag}{neu} ({e['warum']})")
|
||||
gesehen[key] = tag
|
||||
elif typ == "github_release_major":
|
||||
# PC-Watch (17.07.): nur MAJOR-Sprünge melden — Electron released wöchentlich,
|
||||
# relevant ist der Chromium-Jahrgang, nicht jeder Patch.
|
||||
d = json.loads(hole(f"https://api.github.com/repos/{e['repo']}/releases?per_page=1"))
|
||||
tag = (d[0].get("tag_name") if isinstance(d, list) and d else "?") or "?"
|
||||
m = re.search(r"(\d+)", tag)
|
||||
major = m.group(1) if m else "?"
|
||||
neu = " ⚠️ NEUE MAJOR-VERSION!" if major != gesehen.get(key, major) and major != "?" else ""
|
||||
zeilen.append(f"- {key}: neuester Release {tag} (Major {major}){neu} ({e['warum']})")
|
||||
gesehen[key] = major
|
||||
elif typ == "pypi":
|
||||
d = json.loads(hole(f"https://pypi.org/pypi/{e['paket']}/json"))
|
||||
ver = ((d.get("info") or {}).get("version")) or "?"
|
||||
neu = " ⚠️ NEU seit letzter Woche!" if ver != gesehen.get(key, ver) and ver != "?" else ""
|
||||
zeilen.append(f"- {key}: PyPI-Version {ver}{neu} ({e['warum']})")
|
||||
gesehen[key] = ver
|
||||
elif typ == "url_zeilen":
|
||||
text = hole(e["url"], timeout=45)
|
||||
passend = [z.strip() for z in text.splitlines() if e.get("muster", "") in z]
|
||||
|
||||
@@ -16,6 +16,20 @@
|
||||
"bedingung": "Neuer ROCm-Release seit letzter Meldung",
|
||||
"warum": "Seit 7.14 ist TheRock der Produktions-Stack und AMD optimiert offiziell für die Halo-Familie — ROCm könnte Vulkan bei der Token-Erzeugung einholen. Bei neuem Release: Community-Grid-Zeilen unten gegenlesen."
|
||||
},
|
||||
{
|
||||
"key": "electron-major",
|
||||
"typ": "github_release_major",
|
||||
"repo": "electron/electron",
|
||||
"bedingung": "Neue MAJOR-Version seit letzter Meldung",
|
||||
"warum": "Lucys Desktop-Shell am PC steht auf Electron 33 — jede Major dazwischen ist ein Jahrgang Chromium-Sicherheitspatches. Update = Rebuild-Dance am PC, bleibt Handarbeit; der Radar erinnert nur."
|
||||
},
|
||||
{
|
||||
"key": "pocket-tts",
|
||||
"typ": "pypi",
|
||||
"paket": "pocket-tts",
|
||||
"bedingung": "Neue Version auf PyPI",
|
||||
"warum": "Lucys Stimme am PC (installiert 2.1.0, german_24l) — neue Versionen können Stimm-/Pausen-Fixes bringen; Update bleibt Handarbeit am PC (pocket = DIE Stimme, Verdikt 16.07.)."
|
||||
},
|
||||
{
|
||||
"key": "llamacpp-rocm-diskussion",
|
||||
"typ": "url_zeilen",
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env bash
|
||||
# Venv-Audit (monatlich, 17.07.2026): die Python-Nebendienste (Mem0, Voice) altern
|
||||
# eingefroren — kein Auto-Update-Kanal deckt sie ab. Dieser Audit MELDET nur
|
||||
# (bewusst kein Auto-Update: Python-Deps brechen gern):
|
||||
# je venv: veraltete Pakete zählen (pip list --outdated) + bekannte
|
||||
# Sicherheitslücken prüfen (pip-audit gegen den eingefrorenen Stand).
|
||||
# Werkzeug: eigenes Audit-venv (~/.venv-audit, wird beim Erstlauf angelegt) — die
|
||||
# Dienst-venvs selbst werden NICHT angefasst. CVE-Funde → Kanban-Karte (triage,
|
||||
# idempotent je Monat). Läuft als Hermes-Cron --no-agent (stdout = Telegram).
|
||||
set -uo pipefail
|
||||
|
||||
VENVS=("$HOME/.mem0/venv:mem0" "$HOME/.voice/venv:voice")
|
||||
AUDIT_VENV="$HOME/.venv-audit"
|
||||
HERMES="$HOME/.local/bin/hermes"
|
||||
MONAT="$(date '+%Y-%m')"
|
||||
TMP="$(mktemp -d /tmp/venv-audit.XXXXXX)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
briefkasten() {
|
||||
curl -sf -m 5 -X POST "${MC_ANNOUNCE_URL:-http://127.0.0.1:9001/api/voice/announce}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data "$(python3 - "$1" <<'PY'
|
||||
import json, sys
|
||||
print(json.dumps({"text": sys.argv[1], "subject": "Venv-Audit",
|
||||
"source": "venv-audit", "priority": "silent"}))
|
||||
PY
|
||||
)" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
# ---------- Werkzeug-venv sicherstellen (einmalig) ----------
|
||||
if [ ! -x "$AUDIT_VENV/bin/pip-audit" ]; then
|
||||
python3 -m venv "$AUDIT_VENV" >/dev/null 2>&1 || true
|
||||
"$AUDIT_VENV/bin/pip" install -q --upgrade pip pip-audit >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
ZEILEN=()
|
||||
CVE_DETAILS=""
|
||||
CVES_GESAMT=0
|
||||
|
||||
for EINTRAG in "${VENVS[@]}"; do
|
||||
VENV="${EINTRAG%%:*}"; NAME="${EINTRAG##*:}"
|
||||
if [ ! -x "$VENV/bin/pip" ]; then
|
||||
ZEILEN+=("$NAME: venv fehlt unter $VENV — bitte prüfen")
|
||||
continue
|
||||
fi
|
||||
# Veraltete Pakete (Anzahl reicht fürs Lagebild)
|
||||
ALT_N="$("$VENV/bin/pip" list --outdated 2>/dev/null | tail -n +3 | grep -c '' || echo 0)"
|
||||
# CVE-Prüfung gegen den eingefrorenen Stand (lokale Pfade/Editables ausfiltern)
|
||||
"$VENV/bin/pip" freeze 2>/dev/null | grep -vE ' @ |^-e |^#' > "$TMP/$NAME.txt" || true
|
||||
CVE_N=0
|
||||
if [ -x "$AUDIT_VENV/bin/pip-audit" ] && [ -s "$TMP/$NAME.txt" ]; then
|
||||
AUDIT_OUT="$("$AUDIT_VENV/bin/pip-audit" -r "$TMP/$NAME.txt" --progress-spinner off 2>&1 || true)"
|
||||
if printf '%s' "$AUDIT_OUT" | grep -qi 'No known vulnerabilities'; then
|
||||
CVE_N=0
|
||||
else
|
||||
CVE_N="$(printf '%s\n' "$AUDIT_OUT" | grep -cE '^[a-zA-Z0-9_.-]+ +[0-9][^ ]* +(GHSA|PYSEC|CVE)' || echo 0)"
|
||||
if [ "${CVE_N:-0}" -gt 0 ]; then
|
||||
CVE_DETAILS="${CVE_DETAILS}
|
||||
== ${NAME} ==
|
||||
$(printf '%s\n' "$AUDIT_OUT" | grep -E '^[a-zA-Z0-9_.-]+ +[0-9][^ ]* +(GHSA|PYSEC|CVE)' | head -10)"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
ZEILEN+=("$NAME: pip-audit nicht verfügbar (Audit-venv kaputt oder offline?) — nur Outdated-Zählung")
|
||||
fi
|
||||
CVES_GESAMT=$(( CVES_GESAMT + ${CVE_N:-0} ))
|
||||
ZEILEN+=("$NAME: ${ALT_N} Pakete veraltet · ${CVE_N:-0} bekannte Sicherheitslücken")
|
||||
done
|
||||
|
||||
# ---------- Bericht ----------
|
||||
if [ "$CVES_GESAMT" -eq 0 ]; then
|
||||
echo "Commander, monatlicher Venv-Audit der Python-Nebendienste — keine bekannten Sicherheitslücken:"
|
||||
printf '• %s\n' "${ZEILEN[@]}"
|
||||
echo "(Veraltete Pakete sind nur ein Lagebild; aktualisiert wird bewusst nur mit Anlass.)"
|
||||
briefkasten "Venv-Audit grün: $(printf '%s · ' "${ZEILEN[@]}")"
|
||||
else
|
||||
echo "⚠️ Commander, der monatliche Venv-Audit hat ${CVES_GESAMT} bekannte Sicherheitslücken gefunden:"
|
||||
printf '• %s\n' "${ZEILEN[@]}"
|
||||
echo "Details liegen als Karte in der Ideen-Queue — Update bleibt dein Entscheid."
|
||||
if [ -x "$HERMES" ]; then
|
||||
"$HERMES" kanban create "Venv-Audit ${MONAT}: ${CVES_GESAMT} Sicherheitslücken in Python-Diensten" \
|
||||
--body "Automatischer monatlicher Audit (restore-/venv-Kreislauf 17.07.). Funde (pip-audit):
|
||||
${CVE_DETAILS}
|
||||
|
||||
Empfehlung: betroffene Pakete gezielt aktualisieren (venv-weise, mit Dienst-Restart + Postcheck) — KEIN pauschales Upgrade." \
|
||||
--triage --created-by venv-audit --idempotency-key "venv-audit-${MONAT}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
briefkasten "⚠️ Venv-Audit: ${CVES_GESAMT} Sicherheitslücken — Karte liegt in der Queue."
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user