Lesender Selbsttest (Version, Services, Hosts, Downtimes) beim Start und täglich 04:00, Seite /selftest, Teilcheck checkmk_api in /health, Chat-Meldung bei Wechsel. Toleranter Antwort-Parser (value/members, state_type int/String), Fallbacks für Host-Abfrage und Downtime-Liste, Fehlerhinweise je HTTP-Status. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
31 KiB
31 KiB
checkbot
Checkmk-Bot für Debian-LXC (Proxmox). Überwacht Checkmk per REST-API, lässt Alarme von externem Ollama analysieren, meldet in Telegram und/oder Element (Matrix). Konfiguration komplett über Web-UI.
Vollständiger Plan: /home/sysops/.claude/plans/ja-alle-bernehmen-eager-lark.md
Arbeitsregeln
- Auf diesem Rechner wird nichts installiert (kein venv, kein pip, keine Pakete). Code nur schreiben, Ausführung und Tests passieren im Ziel-LXC.
- Stand: Phase 1 bis 6, 6b, 6c und 6d als Code, Phase 7 als Dateien geschrieben, nichts ausgeführt oder getestet (siehe README-Statustabelle).
- Code-Kommentare nur bei echtem WHY.
Festgelegte Entscheidungen
- Plattform: Debian-LXC, unprivileged, 1 Core, 1 GB RAM, Python 3, venv im LXC, systemd, Caddy (HTTPS, interne CA). Kein Docker.
- Checkmk 2.5 ist die Zielversion (Live-System). Alle REST-Annahmen (Spaltennamen für Services/Hosts, Downtime-Endpunkt, Antwortformat) wurden nach dem Stand von 2.3 formuliert und müssen im ersten Lauf gegen 2.5 geprüft werden (OpenAPI-Doku der Instanz unter
/<site>/check_mk/api/1.0/ui/, Integrationstests Phase 8). Nur REST-API (Automation-User, Lese-Rolle). Kein Webhook, kein Notification-Script, kein eingehender Alarm-Port. - Poller: 30 s (UI einstellbar), nur Nicht-OK, Zustandsvergleich in SQLite, nur Hard-States, Verzögerung einstellbar (Default 15 min, Regeln überschreiben, Phase 6b), Flapping gebündelt.
- Ollama extern
office.sysops.de:11434. Zwei Modell-Einstellungen (Alarmtext, Agent), Startqwen3, Hermes zum Vergleich. - Chat: Adapter-Interface. Telegram (Long-Polling) und Matrix (direkt über die Client-Server-API mit
httpx, bewusst nichtmatrix-nio: kein libolm/E2EE nötig; eigener Homeserver, unverschlüsselter Raum, Bot-User mit Access-Token). Parallel, einzeln abschaltbar, Ziel pro Routing-Regel. - Alarm-Flow: Rohalarm sofort, KI-Analyse per Edit nachgereicht (Telegram editMessage, Matrix m.replace). Max 8 Zeilen DE. Timeout 60 s, 2 Retries, sonst "KI nicht verfügbar". Bündelung pro Host 60 s, max 10 Prompts/min, Recovery ohne KI. Downtime: kein Push, Verlauf "unterdrückt". UI-Stummschaltung mit Ablauf.
- Befehle: gemeinsamer Handler,
/in Telegram,!in Matrix, Allowlist pro Backend. - Agent-Loop nur für
/ask,/explain, Tiefenanalyse. 5 feste Lese-Tools (get_service_status,get_host_services,get_state_history,get_downtimes,get_problems), max 6 Schritte, 90 s, Tool-Output untrusted, alle Aufrufe im Audit-Log. - Bot liest Checkmk standardmäßig nur (Automation-User mit Lese-Rolle). Einzige Schreibaktion: Downtime setzen, siehe nächster Punkt.
- Ruhigstellen per Chat (
/muteund/downtimein Telegram,!muteund!downtimein Matrix):- A,
mute: Bot-lokale Stummschaltung mit Dauer, Checkmk bleibt unberührt. Standard. Alarme bieten Buttons "2 h stumm" (Telegram Inline-Buttons, Matrix:!mute 2hals Antwort auf die Alarmnachricht). - B,
downtime: echte Checkmk-Downtime per REST (POST /domain-types/downtime/collections/service, Endpunkt noch gegen echte Instanz prüfen). Eigener Checkmk-User nur mit Downtime-Recht, Secret getrennt vom Lese-User. In der UI abschaltbar, standardmäßig aus, maximale Dauer einstellbar (z.B. 24 h). - B nur durch Allowlist-User nach Bestätigung (Host, Service, Dauer, Kommentar anzeigen, dann Button bzw.
ja). Agent und Modell haben keine Schreib-Tools und lösen B nie aus. - Jede Aktion im Audit-Log: User, Backend, Host, Service, Dauer.
- A,
- UI: FastAPI, Jinja2, HTMX, deutsch, hell/dunkel. Lokale Nutzer (Argon2, optional TOTP), Rollen admin/viewer,
checkbot create-admin, Setup-Assistent. - Secrets in SQLite mit AES-GCM. Nur
master.key, Session-Secret, Bind-Adresse in/etc/checkbot/(chmod 600). UI zeigt Secrets nie an. - Audit-Log ohne Secret-Werte. Prompts versioniert mit Rollback, Testbereich.
- Retention: Verlauf/KI 90 Tage, Audit 1 Jahr.
/healthplus Checkmk-Agent im LXC. - Plugin-Output ist untrusted Input (Prompt Injection), KI löst keine Aktionen aus.
Phasen
- Gerüst: Schema,
crypto.py, Checkmk- und Ollama-Client,/health - UI-Basis: Login, Rollen, Setup-Assistent, Verbindungstests, Audit
- Chat-Interface, Telegram, Poller, Alarm-Flow
- Matrix-Adapter, Routing, Stummschaltung (UI)
- Command-Handler (inkl.
mute,downtimemit Bestätigung), Agent-Loop - Prompt-Versionierung, Testbereich, Verlauf, Retention
- Deployment (
setup-lxc.sh, Caddy, systemd, Checkmk-Agent) - Tests
Geplante Struktur
app/ main.py fileconfig.py db.py models.py crypto.py checkmk.py poller.py
ollama.py pipeline.py agent.py tools.py commands.py audit.py
retention.py health.py chat/{base,telegram,matrix}.py web/
deploy/ setup-lxc.sh systemd/checkbot.service Caddyfile
tests/
Fortschritt Phase 5b (ungetestet)
- Neu:
app/listeners/(Telegram-getUpdates, Matrix-/sync, Backoff, Offset/Since in Tabellelistener_state),app/downtime/(Client, Bestätigungs-Store, Service),app/agent_runs/(Recorder, Tabelleagent_runs, AgentService),app/commands/{mutecmd,downtimecmd,callbacks,...},app/alerts/extras.py(Buttons/Hinweis),app/chatops.py(Verdrahtung), UI/agentund Sectioncheckmk_dt. - Annahmen Telegram:
getUpdatesmitallowed_updates=[message, callback_query], Long-Poll 30 s; erster Start überspringt den Rückstand peroffset=-1;editMessageTextohnereply_markupwürde die Buttons entfernen, daher wird es beim Edit mitgegeben;callback_datamax. 64 Byte (mute:<alert_id>:<min>,deep:<id>,dt:<id>:<min>,dty:<token>,dtn:<token>). - Annahmen Matrix:
/syncmit Filter (nur Raum, nurm.room.message);next_batchwird vor der Verarbeitung gespeichert (höchstens einmal); Edits (m.replace) undm.noticewerden ignoriert; Reply perm.relates_to.m.in_reply_to. - Annahmen Checkmk:
POST /domain-types/downtime/collections/servicemitdowntime_type=service,host_name,service_descriptions,start_time/end_time(ISO 8601 UTC,Z),comment; Erfolg ist 204. Noch gegen eine echte Instanz prüfen. - Alarm-Buttons nur bei Einzelalarmen (nicht Recovery, nicht gebündelt); Reply-Auflösung bei mehreren Services in einer Nachricht lehnt ab und nennt die explizite Form.
Phase 7 Deployment (Dateien unter deploy/, ungetestet, Details in deploy/README.md)
- Genau ein uvicorn-Worker: Rate-Limiter, TOTP-Einrichtung und Downtime-Bestätigungen liegen im Prozessspeicher, außerdem liefe der Poller doppelt. Bind-Adresse und Port kommen aus
checkbot.env(Unit nutzt${CHECKBOT_BIND_*}), Caddy-Upstream wird daraus gerendert. - Caddy-Modi:
internal(Caddy-CA, Root-Zertifikat muss verteilt werden) oderfiles(Zertifikat der Unternehmens-CA nach/etc/caddy/certs). Zugriffslog ohne Query, Authorization, Cookie. /healthüber Caddy nur von 127.0.0.1 undCHECKBOT_MONITOR_IP(Detailtexte intern), sonst 403./healthliefert 503, solange ein Teilcheck nicht ok ist, auch bei "nicht konfiguriert".- Local Check: "nicht konfiguriert" ergibt WARN, "deaktiviert" (Telegram/Matrix fehlen im JSON) OK, Fehler/Bot nicht erreichbar CRIT.
master.keywird nie mit der DB gesichert. Schlüssel erzeugtsetup-lxc.shals root und übergibt sie ancheckbot(das Konfigurationsverzeichnis ist fürcheckbotnur lesbar).- Unprivilegierte LXCs: Drop-In
checkbot-lxc-relaxed.conf(setup-lxc.sh --lxc-relaxed).
Phase 6 Prompts und Retention (Code geschrieben, ungetestet)
- Migration Index 5 (sechster Eintrag in
app/db.py): Tabellenprompt_versions(UNIQUE name+version),prompt_active,retention_state(Einzelzeile id=1: ts, origin, counts als JSON) und Indexstate_log_ts. Phase 6b/6c hängen ihre Migrationen dahinter an. app/prompts/: Registry (alert,agent) mit Standardtext, Schutzhinweis undcompose; die Standardtexte bleiben inapp/alerts/prompts.pybzw.app/agent/prompts.py(dort nur Agenten-Prompt in Standardtext +DATA_NOTICEgetrennt,SYSTEM_PROMPTbleibt als Zusammensetzung). Version 1 entsteht beim ersten Zugriff aus dem Standard oder dem Altsettingprompt.alert_system(ungültiger Altwert führt zum Standardtext, das Setting wird danach nie wieder gelesen, aber nicht gelöscht). Validierung: NFKC-Normalisierung, Steuer- und Zero-Width-Zeichen weg, nicht leer, max. 4000 Zeichen,<daten>-Begrenzer werden abgelehnt (nicht entfernt, damit nichts neu zusammenwächst).- Verdrahtung über Provider:
AlertAnalyzer(..., prompt_provider),AgentService(..., prompt_provider),Agent(..., system_prompt); Runtime liefertPromptService.effective_text, das nie wirft (Fallback Standardtext). Kein Cache, jeder Aufruf liest frisch (ein indizierter Select). - Audit:
prompt.init(system),prompt.create,prompt.activate(Name,vN, Nutzer), nie Textinhalt oder Notiz. - Testbereich:
PromptTester(Rate-Limit 6/min pro Nutzer, im Speicher). Alarm-Test nutztbuild_messagesundalert_modelausapp/alerts. Agent-Test nutztAgentService.trial_ask: kein Eintrag inagent_runs, Tool-Aufrufe im Audit untertest:<nutzer>. Fehlertexte werden mit den konfigurierten Secrets maskiert. - Retention-Settings (Min 1, Max 3650, Mutes 365):
retention.alerts_days=90,retention.agent_runs_days=90,retention.state_log_days=30,retention.audit_days=365,retention.mutes_days=7. TageslaufRetentionRunnerals Task in der Runtime: 60 s nach Start, danach Prüfung alle 15 min, Lauf einmal pro Kalendertag ab 03:00; ein Start-Lauf nur, wenn heute noch keiner stattfand; ein fehlgeschlagener Lauf wird am selben Tag nicht wiederholt. Der Lauf löscht in Batches zu 1000 perid IN (SELECT ... LIMIT), danachwal_checkpoint(TRUNCATE)(Fehler nur geloggt), ein Audit-Eintragretention.run. - UI:
/prompts/{name}(alle),/prompts/{name}/edit|draft|versions/{n}/activate|test(Admin),/settings/retention(generische Settings-Section mitextra_template) undPOST /retention/run. - Offene Annahmen: Ollama-Retries im Testbereich wie im Betrieb (Request kann bis zu 3 x Timeout dauern); der Setup-Assistent zeigt Aufbewahrung wie Alarmierung als "erledigt", weil kein Pflichtfeld existiert.
Phase 6b Regeln, Verzögerung, Delta, Erinnerungen (Code geschrieben, ungetestet)
- Migration Index 6:
service_states+problem_since,reported_at(Zeitpunkt der letzten Meldung, bei Altzeilen = Migrationszeit),reported_since(Beginn des gemeldeten Zustands),last_reminder_ts(wird vom Poller-Upsert nie überschrieben);routes+delay_minutes(NULL = Standard),output_contains,ai_enabled,ai_hint,reminder_hours(6b: NULL/0 = keine, ab 6c NULL = Standard, 0 = aus),loud,use_default_targets; neue Tabelleapp_state(key/value, Markerseed.rule.zfs_zpool). Bis Index 6 belegt, 6c nimmt Index 7. - Settings:
alerts.default_delay_minutes(15, 0-1440),alerts.recovery_enabled(an),alerts.reminder_hours(0, 0-168).poll.min_durationist entfernt, der Altwert insettingsbleibt unbeachtet liegen. app/routing/:rules.py(Route,matches,matching_rulesmit stop_on_match,resolve_targetsinkl. use_default_targets),decisions.py(resolve_delay,delay_provider,ai_allowed,resolve_loud,ai_hintsmax. 1200,reminder_interval),seed.py(Zpool-Regel einmalig, Fehler nur geloggt, Aufruf im Lifespan),limits.py. Erinnerungen matchen wie Probleme (Artremindernutztproblemder Regel).- Poller:
compute_events(previous, current, now, delay_seconds)mit Callable pro (Host, Service, Status, Output); Regeln pro Zyklus frisch (Poller(..., rules=routes.enabled_routes)).Eventhatprevious_state,previous_duration,since_ts,problem_since_ts,ai_enabled,ai_hint; Artreminder. - Pipeline:
alerts/gate.py(ReportGate: merkt pro Service, ob das Problem gesendet wurde, Fallback ausalertsperreport_counts; Recovery-Schalter und "Recovery nur nach gemeldetem Problem", Gründerecovery_aus,nicht_gemeldet), Gruppenschlüssel (Host, Ziele, leise); Adaptersend/edit(..., silent=False),Delivererübergibtsilentnur wenn gesetzt.alerts/delta.py(Dauerformat, Delta-Texte),alerts/report_status.py(Dashboard). app/reminders/:due_reminders(rein) undReminderWorker(60 s, Zeitpunkt wird vor dem Senden gespeichert, im Lifespan gestartet).
Phase 6c Betriebsrobustheit (Code geschrieben, ungetestet)
- Migrationen: Index 7 (
service_states+ack_stateINTEGER NULL = nicht bestätigt, sonst Status bei Beginn der Bestätigung; +host_held= Service-Alarm wegen Host down zurückgehalten, noch nicht als gemeldet gezählt; setzt Markerbaseline.doneinapp_state, wennservice_statesschon Zeilen hat), Index 8 (routes.reminder_hours0 wird NULL). Die Liste liegt jetzt inapp/migrations.py, der Runner (app/migrate.py:split_statements,MigrationError, je MigrationBEGIN IMMEDIATE...PRAGMA user_version...COMMIT, sonstROLLBACK) wird vonDatabase(path, migrations=None)benutzt; der Autocommit-Modus gilt nur während der Migration. - Erinnerung je Regel:
resolve_reminder_hours(leer = Standard, 0 = aus, mehrere Treffer: eine 0 gewinnt, sonst kleinster Wert > 0, sonst Standard). Bestätigte Services bekommen keine Erinnerung. - Poller:
Poller(..., notifier, baseline, tracker). Host-Zustände kommen alsServiceStatemit ServiceHost(HostState.as_service) durchcompute_events; Host-Verzögerungalerts.host_delay_minutesüberdelay_provider(..., host_minutes).build_row/baseline_rowsinpoller/events.py/poller/baseline.py;BaselineGuard(Markerbaseline.done, Anforderungbaseline.requestedinapp_state),OutageTracker(Schwelle 3,unreachable= Transport/Timeout/5xx,unexpected= 4xx/Parse),CheckmkError.kind/.brief(brief nie mit Antwortinhalt). Ein Fehler beim Host-Abruf zählt als Fehlzyklus des ganzen Abrufs (sonst entstünden falsche Host-Recoveries). - Pipeline/Alerts:
alerts/sendlimit.py(SendLimiter, proTarget, Sammelmeldung per Ticker_flush_overflow),alerts/content.py(Modus-/Limit-/Basis-URL-Provider,alert_link),alerts/system.py+system_text.py(SystemNotifierfür Baseline und Ausfall, Artsystem),alerts/followup.py(KI-Edit), Gruppenschlüssel(host, targets, silent, modes).ReportGate.resetnach Baseline. Neue Suppress-Gründebestaetigt,host_down,sendelimit. - Settings:
alerts.host_delay_minutes(0, 0-1440),alerts.send_limit_per_minute(15, 1-60),ui.base_url,telegram.content_mode,matrix.content_mode(voll/kurz). UI: Feldtypselect,Field.hint/transform, AktionPOST /settings/alerting/baseline(Admin, CSRF, Auditbaseline.request), Telegram-ButtonDetailsperButton.url(Fallback ohne Link-Button bei abgelehnter URL). - Annahmen: Host-Abruf
POST /domain-types/host/collections/allmit Querystate != 0und Spaltenname,state,plugin_output,last_state_change,scheduled_downtime_depth,acknowledged; Service-Spalteacknowledged; Host-State 0/1/2 = UP/DOWN/UNREACH (intern 0/2/3); Hosts gelten immer als hart. Alles ungeprüft gegen 2.5 (Phase 6d prüft). - Abweichung: Datenschutzmodus gilt je Backend, nicht je einzelnem Ziel (Ziele stehen in Regeln als freie Zeilen). Ein vom Sendelimit verworfenes Problem zählt im Speicher des Gates als gemeldet (die Recovery kommt).
Phase 6d API-Selbsttest (Code geschrieben, ungetestet)
- Migration Index 9 (zehnter Eintrag in
app/migrations.py): Tabelleselftest_state(Einzelzeile id=1: ts, version, checks als JSON-Liste{name, status, text}), analog zuretention_state. - Neues Modul
app/selftest/:constants.py(Statusok/abweichend/fehler, Teilcheck-Namenversion/services/hosts/downtimes, Zeitplan-Konstanten),model.py(CheckResult,SelftestRunmitoverall_status/ok),repository.py(SelftestRepository, liest/schreibt die Einzelzeile),checks.py(die vier Teilchecks, rein lesend, unterscheidennicht erreichbarvonAntwort unerwartetüberCheckmkUnreachable/CheckmkError, prüfen Pflicht- und optionale Spalten im rohen JSON vor dem Parsen),service.py(SelftestService.run()ruft die vier Teilchecks auf, vergleicht die Version mit dem letzten Lauf, speichert),notify.py(message_for: Text nur bei Statuswechsel, Rückkehr zu ok oder Versionswechsel, sonstNone),runner.py(SelftestRunner: Zeitplan wieRetentionRunner, aber Start-Verzögerung 20 s statt 60 s und täglich ab 04:00 statt 03:00; manueller Lauf überrun_manualmit Auditselftest.run, Rate-Limit 1 pro 30 s überSlidingWindowLimiter). app/checkmk.py:_items/extract_extensionsakzeptierenvalueodermembers;_is_hardüber die benannte KonstanteHARD_STATE_TYPES("1"/"hard", alles andere inkl.Nonegilt als soft, stürzt nicht ab);CheckmkErrorbekommt zusätzlich.hint(lesbarer Hinweis je HTTP-Status 401/403/404/422/5xx überstatus_hint),.briefbleibt wortgleich wie bisher, damit bestehende Tests und Chat-Texte unverändert bleiben. Neue, nur für den Selbsttest gedachte Zusatzpfade, die das reguläre Verhalten nicht verändern:problem_hosts_with_fallback/selftest_hosts(POST scheitert → GET mitcolumns=und clientseitigem Filterstate != 0, der funktionierende Weg wird in_host_query_modegemerkt und vonproblem_hosts()beim nächsten Aufruf zuerst versucht),selftest_services(rohe Zeilen plus best-effort Parse, Parsefehler werden abgefangen statt den Selbsttest abzubrechen),downtimes_with_fallback(scheitertdowntime_type→ GET mitqueryals JSON-String,is_service0/1),downtimes_from_states(reine Funktion, leitet Downtimes ausscheduled_downtime_depthder schon gelesenen Service-/Host-Listen ab, letzter Ausweg).app/health.py: neuer Teilcheckcheckmk_api, liest nur den letzten gespeicherten Selbsttest-Lauf (kein eigener API-Aufruf). Checkmk nicht konfiguriert oder keinselftest_repoübergeben →NotConfigured; noch kein Lauf →NotConfigured("noch nicht gelaufen")(gleiche Behandlung wie "nicht konfiguriert", dafür liestrun_checksjetzt den Text aus der Exception statt ihn fest zu verdrahten); Statusabweichendoderfehler→ Fehler (503 in/health).build_checks(store, poller_status=None, selftest_repo=None)ist rückwärtskompatibel (fehlenderselftest_repoergibt stets "nicht konfiguriert").app/runtime.py:Runtime.selftest_repo/.selftest/.selftest_runner, Task im Lifespan wieretention_task(Cancel infinally).app/alerts/system.pybekommtSystemNotifier.selftest(ok, text)(eigener Service-NameSelbsttestinsystem_text.py, Artsystem, wie die Checkmk-Ausfallmeldung).- Settings
selftest.enabled(Default an, schaltet nur den automatischen Zeitplan ab, ein manueller Lauf funktioniert immer) undselftest.notify(Default an) in neuer Section "Selbsttest" (app/web/selftest_section.py,/settings/selftest). - UI
/selftest(app/web/selftest_pages.py+selftest.html): Tabelle der vier Teilchecks mit Ampel-Badge, Checkmk-Version, Zeitpunkt des letzten Laufs; Fallback-Hinweise stehen im Text des jeweiligen Teilchecks. Lesbar für alle angemeldeten Rollen, Button "Selbsttest jetzt ausführen" nur für Admins (CSRF über die globale FastAPI-Dependency, Rate-Limit 1 pro 30 s). Menüeinträge/selftest(alle) und/settings/selftest(Admins). deploy/checkmk/checkbot-local-check: neuer ServiceCheckbot_checkmk_api, Detailtextnoch nicht gelaufenwird wienicht konfiguriertals WARN gewertet (WARN_DETAILS).- Annahmen/Abweichungen: "sofort bei Versionswechsel" löst keinen zusätzlichen Sofort-Lauf aus, sondern markiert nur den laufenden Lauf als Versionswechsel für die Chat-Meldung (im Auftrag so vorgesehen: "sofort erneut laufen lassen bzw. den aktuellen Lauf kennzeichnen"). Fehlt der Parser-Vergleich an Spalten, weil aktuell keine Problem-Services/-Hosts vorliegen (leere Ergebnisliste), kann der jeweilige Teilcheck die Spalten nicht prüfen und meldet
ok, auch wenn eine Spalte in Wahrheit fehlen würde – eine Grenze des Ansatzes, der nur die echten Problem-Listen liest statt eines eigenen Schema-Endpunkts..hintanCheckmkErrorist zusätzlich zu.brief, nicht statt dessen, damit bestehende Tests mit exaktembrief-Text (z. B."HTTP 401") unverändert bleiben.
Weiterer Weg (entschieden)
Reihenfolge: Testbasis, erster Lauf im Test-LXC (pytest), Korrekturrunde, Phase 6, Phase 6b, Phase 6c, Phase 8, Abnahme, Parallelbetrieb, Tag v0.1.0. Verfeinerungen passieren am Live-System, nicht vorab.
- Testbasis:
pytestmanuell im Test-LXC (Dev-venv mitrequirements-dev.txt), dazu.gitea/workflows/test.ymlfür einen späteren Gitea-Actions-Runner. Logging als Zeilenlog nach journald, Level überCHECKBOT_LOG_LEVEL(Default INFO), nie Secrets in Logs (Test für die Adapter). Zeitzone überCHECKBOT_TZim Setup-Skript, DefaultEurope/Berlin. - Phase 6: Alarm- und Agenten-Prompt versioniert mit Rollback und Testbereich; der Schutzhinweis gegen Prompt Injection wird immer fest angehängt und ist nicht editierbar. Retention einstellbar: Alarmverlauf/Zustellungen 90 Tage, Agenten-Läufe 90 Tage,
state_log30 Tage, Audit 1 Jahr, abgelaufene Mutes 7 Tage, tägliches Aufräumen mit Log der gelöschten Zeilen. Erinnerungen für offene CRIT-Probleme, Standard aus, nicht bei Downtime/Mute. - Phase 6b (Trigger-Regeln, Routing-Seite wird "Regeln"): Zusatzfelder pro Regel: KI-Hinweistext (nur Zusatzkontext hinter dem festen Schutzhinweis), KI an/aus, Erinnerungsintervall (nur CRIT), laut/leise (Telegram
disable_notification). Zusatzbedingungen: Plugin-Output enthält Text (Teilstring, keine Regex wegen ReDoS) und Mindestdauer in Minuten. Keine Checkmk-Labels/Tags. Beispiel: Service*zpool*, ab WARN, eigener Raum, eigener KI-Hinweis. - Phase 6c (Betriebsrobustheit, nach 6b, vor 8):
- Baseline beim Erstlauf: Zustand still merken, eine Zusammenfassung ohne KI (Anzahl, 10 kritischste); Button in der UI setzt die Baseline neu.
- Bestätigte Probleme (Acknowledged in Checkmk) werden mit Grund "bestätigt" unterdrückt, bei Verschlechterung wieder gemeldet. Regel für alle Unterdrückungen: Recovery nur, wenn das Problem vorher gemeldet wurde.
- Host-Alarme: Hosts mitabfragen, "Host DOWN" als eigener Alarm, Service-Alarme des Hosts mit Grund "Host down" unterdrücken, Recovery bei Rückkehr.
- Checkmk nicht erreichbar: nach 3 Fehlzyklen eine Meldung "Checkmk nicht erreichbar seit HH:MM" an die Standardziele, bei Rückkehr eine zweite; währenddessen keine Recoveries.
- Sendelimit 15 Nachrichten pro Minute und Ziel, Überschuss als Sammelmeldung pro Minute, alles bleibt im Verlauf.
- Datenschutz pro Ziel: Inhalt voll oder kurz (nur Host, Service, Status, ohne Plugin-Output und KI-Text), Standard voll, Entscheidung am Live-System.
- Optionale
ui.base_url: Alarme enthalten Link "Details" auf den Verlauf (Telegram-Button, Matrix-Text), nur wenn gesetzt.
- Standardfall ist der ZFS-Zpool-Alarm:
- Vorinstallierte, aktive Regel "ZFS Zpool" (kommt mit Phase 6b): Service-Glob
*zpool*(Groß/Klein egal), ab WARN, Standardziele, KI-Hinweis mit Prüfschritten (zpool status -v, Scrub-Stand, Fehlerzähler, SMART der betroffenen Platte, läuft ein Resilver), Erinnerung bei CRIT alle 4 h. In der UI editier- und löschbar. Echter Service-Name und Beispiel-Output stehen noch aus, Glob und Texte danach anpassen. - Haupt-Testszenario in E2E und Abnahme:
checkbot-testbekommt einen lokalen Check, der einen Zpool simuliert und per Datei zwischen ONLINE, DEGRADED und FAULTED schaltet (DEGRADED ergibt WARN, FAULTED ergibt CRIT, ONLINE ergibt Recovery). Dazu ein Zpool-Beispielalarm im Prompt-Testbereich. - Das Checkmk-Plugin bzw. der lokale Check für Zpool kann später angepasst werden, ohne Bot-Änderung: stabiler Service-Name (
Zpool <pool>), Schlüsselwort und wichtigste Details in der ersten Ausgabezeile (nur diese erreicht Chat und KI). Optional später:long_plugin_outputmitlesen (kleine Änderung an der Spaltenliste inapp/checkmk.pyund Kürzung im Prompt); Perfdata als Trigger-Bedingung nur, wenn sich der Bedarf am Live-System zeigt.
- Vorinstallierte, aktive Regel "ZFS Zpool" (kommt mit Phase 6b): Service-Glob
- Zpool-Referenz aus dem Live-System: Service heißt
zpool status(Host z.B.kosext100.krs.local), WARN-Output beginnt mit "RaidZ: One or more devices has experienced an unrecoverable error. ..." (ca. 270 Zeichen, ohne Geräte- und Fehlerzahlen). Der Glob*zpool*passt. Dieser Output dient als Vorlage für den simulierten Testcheck und den Beispielalarm im Prompt-Testbereich. - Phase 9 (nach der Abnahme, optional, in der UI abschaltbar): Statusberichte nach Zeitplan. Das Modell schreibt aus dem aktuellen Zustand und den Änderungen seit dem letzten Bericht: Zähler (DOWN-Hosts, CRIT, WARN, UNKNOWN), neu, weiter offen, erholt, bestätigt, in Downtime. Der Zeitplan ist in der UI einstellbar: mehrere Uhrzeiten, Wochentage, Zeitzone des LXC, Ziele, Inhalt voll oder kurz. Dazu ein Button "Bericht jetzt senden" und der Befehl
/report. Der Bericht merkt sich den Stand des letzten Berichts (Vergleich "seit 18:05"). Verfeinerung am Live-System. - Delta in der Alarmnachricht (Phase 6b):
Eventbekommt Vorzustand und Dauer. Alarmzeile zeigt Übergang und Dauer (WARN → CRIT, seit 18:05 (vorher WARN seit 2 h)), Recovery die Gesamtdauer (wieder OK nach 3 h 12 min (zuletzt CRIT)), Erinnerungenweiter CRIT seit 4 h. - Recovery-Meldungen sind in der UI schaltbar: globaler Schalter
alerts.recovery_enabled(Default an) in der Section "Alarmierung" und pro Regel über die Ereignisarten (kindsenthältrecoveryoder nicht). Greift zusätzlich zur Regel "Recovery nur, wenn das Problem vorher gemeldet wurde". Ist Recovery aus, bleibt sie im Verlauf sichtbar, wird aber nicht gesendet. - Verzögerung (Phase 6b): Section "Alarmierung" bekommt
alerts.default_delay_minutes(Werkseinstellung 15; ersetztpoll.min_duration). Jede Regel kann die Verzögerung überschreiben, auch mit 0. Passen mehrere Regeln, gilt die kleinste. Sie zählt ab der letzten Statusänderung, eine Verschlechterung startet sie neu; wird das Problem vorher OK, gibt es weder Alarm noch Recovery. Das Dashboard zeigt wartende Probleme ("wartet noch 8 min"). Mute und Downtime wirken auch während der Wartezeit. Host-Alarme und der Checkmk-Ausfall (Phase 6c) kommen immer sofort, Host-Verzögerungalerts.host_delay_minutesStandard 0. Die vorinstallierte Regel "ZFS Zpool" (*zpool*) hat Verzögerung 0, damit gilt ab Werk: Zpool sofort, alles andere nach 15 min. - Phase 6d (nach 6c): API-Selbsttest. Lesend beim Start, täglich 04:00 und sofort bei Checkmk-Versionswechsel:
/version, Service- und Host-Abfrage (alle benötigten Spalten in der Antwort), Downtime-Abruf (GET). Ergebnis auf einer UI-Seite, Teilcheckcheckmk_apiin/health, Chat-Meldung bei Fehlschlag oder Versionswechsel (und bei Rückkehr zu ok). Unterscheidet "nicht erreichbar" von "Antwort unerwartet". Erledigt (Code geschrieben, ungetestet), Details und Annahmen siehe Abschnitt "Phase 6d API-Selbsttest" oben.- Aufgenommen nach Prüfung des Referenzrepos
jlk/checkmk-llm-server(MIT, nur als Referenz; es belegt weder Host-Live-Status noch die Downtime-Liste): (a) toleranter Antwort-Parser, dervalueodermembersakzeptiert undstate_typeals int oder String (hard/soft) zulässt; (b) Downtime-Fallback: scheitertGET /domain-types/downtime/collections/all, ermittelt der Bot Downtimes ausscheduled_downtime_depth > 0der Service- und Host-Abfrage und meldet das im Selbsttest als Abweichung; (c) lesbare Fehlerhinweise je Status (401, 403, 404, 422, 5xx) statt nurHTTP <code>. Die Spaltenlast_state_change,is_flapping,state_typeund der Host-Endpunkt bleiben bis zum Selbsttest gegen 2.5 ungeprüft. (umgesetzt:extract_extensions,_is_hard/HARD_STATE_TYPES,downtimes_from_states,status_hintinapp/checkmk.py.) - Zweite Referenz
chexma/vibeMK(GPL-3.0, nur Wissen, kein Code): Host-Live-Status dort per GET/domain-types/host/collections/allmitcolumns=name,state,acknowledged,scheduled_downtime_depth, State 0 clientseitig verworfen, Host-State 0/1/2 = UP/DOWN/UNREACH (bestätigt); Downtime-Liste per GET/domain-types/downtime/collections/allmitquery(host_name,is_service),downtime_typeals Listenfilter dort nicht belegt;state_typeint 0 = soft, 1 = hard. Fallback im Selbsttest: scheitert der POST der Host-Abfrage, GET mitcolumnsund clientseitigem Filter; scheitert der Downtime-Abruf mitdowntime_type, GET mitqueryundis_service. (umgesetzt:problem_hosts_with_fallback/selftest_hosts,downtimes_with_fallback/_downtimes_by_queryinapp/checkmk.py.)
- Aufgenommen nach Prüfung des Referenzrepos
- Entschieden nach Phase 6b: Erinnerung pro Regel: leer = Standard, 0 = aus, >0 = Stunden. Keine Recovery ohne vorherigen Alarm bleibt so. Migrationen laufen künftig in einer Transaktion pro Migration mit Rollback.
deploy/first-run.shrichtet das Dev-venv ein, lässtpytestlaufen und packt Log, Python-Version, Paketliste und Git-Stand ohne Secrets in ein Archiv. Zugriff auf den Test-LXC per SSH nur dort, eigener Benutzer ohne Root, jeder Befehl mit Freigabe. Nach dem ersten grünenpytestund vor der Abnahme:docs/THREAT_MODEL.md(STRIDE) und ein einmaliger Sicherheits-Review des gesamten Codes. - Vorgemerkt, noch NICHT entschieden (Phase 10, Idee): Chat mit dem Agenten ("Hermes") in der UI. Heute ist der Agent nur per Telegram/Matrix (
/ask,/explain, Tiefenanalyse) und als Einzelfrage-Testlauf unter/promptserreichbar. Vorschläge zur späteren Entscheidung: nur Admins (Schalterui.chat_viewers_allowed, Standard aus); 6 Runden Kontext, Gespräche mit Gesprächs-ID inagent_runs, Nutzer sehen eigene Gespräche, Admins alle unter/agent, Aufbewahrung 90 Tage, Rate-Limit 5 pro Minute; Antwort nach Abschluss ohne Streaming ("läuft …" mit Abbrechen, max. 90 s), Tool-Schritte aufklappbar. Das Agentenmodell ist in der Section "Ollama" einstellbar (auch ein Hermes-Modell). Erst nach der Abnahme angehen. - Aktionen: Der Bot führt nie Shell-Befehle aus (weder lokal noch per SSH). Webhook pro Regel (POST JSON an Allowlist-URLs, Audit) kommt erst nach der Abnahme.
- Phase 8: Integrationstests mit Marker
integration(pytest -m integration), Zugangsdaten per Umgebung, nur lesend; Downtime nur mit ausdrücklichem Flag und gegen einen Testhost. Testhostcheckbot-testin Checkmk mit schaltbarem lokalem Check plus halbautomatischestests/e2e/run.py. - Abnahme (
docs/ACCEPTANCE.md): CRIT in Telegram und Element, KI-Analyse binnen 90 s, Recovery ohne KI, Downtime undmuteunterdrücken, Ollama aus ergibt Rohalarm mit "KI nicht verfügbar", Neustart ohne Doppelalarme, Backup und Restore real getestet,/healthim Checkmk grün, Baseline-Zusammenfassung beim Erstlauf, Host-down unterdrückt Service-Alarme, Meldung bei Checkmk-Ausfall, 24 h Dauerlauf ohne Fehler im Journal. - Einführung: erste Woche nur Testziele (separate Gruppe/Raum) per Routing-Regel, danach auf Produktivziele umschalten. Release: Tag
v0.1.0und kurzeCHANGELOG.md. - Bewusst nicht enthalten: Ack setzen (Bestätigungen aus Checkmk werden nur gelesen, Phase 6c), mehrere Checkmk-Sites, weitere Kanäle (Mail/Slack), E2EE in Matrix.