Add self-hosted Healthchecks stack for internal job monitoring (hybrid)

Self-hosted Healthchecks (ops/healthchecks/) as the hub for internal
cron/job heartbeats. The three host-down/backup watchdogs (Borg pre-hook,
baerchen nearline pull, monitoring watchdog #8) deliberately stay on
healthchecks.io cloud, since an on-host watcher cannot report a host outage.

- frontend_net + dedicated PostgreSQL 18 in healthchecks_internal
- native Healthchecks auth; ping/API exempt from Authelia (n8n/Komodo pattern)
- registered as middleware_exempt in ops/policy-checks/exceptions.json
- docs: DECISIONS, ARCHITECTURE (3.1/4.2/7.6/10), SERVICE_CATALOG,
  SECRETS_MAP, MASTER_TODO, README index

docker compose config validated (exit 0). Not yet deployed: host secret file,
appdata dir, Komodo stack + ENV and Gitea webhook remain operator steps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 20:09:56 +02:00
parent ee0d450a27
commit cbfbb8ca4f
9 changed files with 291 additions and 0 deletions
+111
View File
@@ -0,0 +1,111 @@
Typ: Runbook · Stand: 2026-06-23 · Status: vorbereitet (noch nicht deployed)
# Healthchecks (self-hosted) — Cron-/Job-Heartbeat-Monitor
Self-gehostete Instanz von [Healthchecks](https://github.com/healthchecks/healthchecks)
als zentraler **Dead-Man's-Switch** fuer die internen Jobs und Scripte des
Homelabs: ein Job pingt beim erfolgreichen Lauf eine URL; bleibt der Ping aus,
alarmiert Healthchecks. Damit werden stille Job-Ausfaelle sichtbar, die Docker
("Up"), Prometheus-Blackbox (nur HTTP-Routen) und der Critical-Events-Watcher
(nur `die`/`oom`) nicht sehen.
## Scope-Entscheidung (wichtig)
Dieser Stack ist bewusst der Hub fuer **interne** Checks auf einem **laufenden**
Host — Frage: "Lief Job X heute?". Beispiele:
- `services/posture-check/posture-check.sh` (stuendlich / pre-borg)
- `ops/restore-tests/run-restore-checks.sh` (Kadenz aus `schedule.md`)
- `ops/borg-ui/scripts/pre-backup-dumps.sh` (Dump-Erzeugung)
- `ops/borg-ui/scripts/gitea-bundle-mirror.sh`
**Nicht hier:** die host-down-/backup-still-Waechter bleiben **extern** auf
healthchecks.io-Cloud (Free-Tier):
| Check | Quelle | Endpoint |
|---|---|---|
| Borg-Pre-Hook | `ops/borg-ui/scripts/pre-borg.sh` | healthchecks.io-Cloud |
| baerchen Nearline-Pull | `ops/h-drive-nearline/pull-critical-backups.ps1` | healthchecks.io-Cloud |
| Monitoring-Watchdog (#8) | `monitoring/prometheus/alerts.yml` (geplant) | healthchecks.io-Cloud |
**Begruendung:** Ein Waechter, der auf demselben Unraid-Host laeuft, den er
ueberwacht, kann einen Host-Ausfall nicht melden — er ist dann selbst tot, und
Stille ist nicht von "alles gut" unterscheidbar. Genau diese drei Checks
existieren fuer den Host-/Backup-Stillstand, deshalb muessen sie extern bleiben.
Die Skripte sind endpoint-agnostisch (siehe `docs/SECRETS_MAP.md`), eine
spaetere Umstellung waere reine URL-Frage — die Architektur-Empfehlung bleibt
aber extern.
## Architektur
- `healthchecks` — Web-UI + Ping-Listener, `frontend_net`, Traefik via
`https://hc.kaleschke.info`, **native Healthchecks-Auth ohne pauschale
Authelia** (analog n8n/Komodo): die Ping-Endpunkte `/ping/*` und die API
muessen ohne ForwardAuth erreichbar sein, sonst koennen Jobs nicht melden.
- `healthchecks-postgres` — dedizierte PostgreSQL 18, nur `healthchecks_internal`
(`internal: true`), nie `frontend_net`.
- SMTP ist bewusst **nicht** konfiguriert: Login laeuft ueber das
Superuser-Passwort, Benachrichtigung ueber die ntfy-Integration. SMTP (GMX)
kann spaeter additiv ergaenzt werden, falls E-Mail-Alerts gewuenscht sind.
## Secrets (siehe `docs/SECRETS_MAP.md`)
| Secret | Mechanik |
|---|---|
| `HEALTHCHECKS_SECRET_KEY` | Komodo Stack-ENV (Django Secret Key) |
| `HEALTHCHECKS_DB_PASSWORD` | Komodo Stack-ENV (gleicher Wert wie Datei-Secret) |
| `HEALTHCHECKS_SUPERUSER_EMAIL` | Komodo Stack-ENV (Login-Mail des Erst-Admins) |
| `HEALTHCHECKS_SUPERUSER_PASSWORD` | Komodo Stack-ENV (Login-Passwort des Erst-Admins) |
| `healthchecks_postgres_password.txt` | Datei-Secret `/mnt/user/appdata/secrets/``POSTGRES_PASSWORD_FILE` |
`SECRET_KEY` und `DB_PASSWORD` unterstuetzt das Image nicht als `_FILE` → Stack-ENV
(Regel aus `docs/SECRETS_MAP.md`). Das Postgres-Passwort liegt zusaetzlich als
Datei-Secret vor; beide Werte muessen identisch sein.
## Pre-Deploy (einmalig, Operator)
1. Appdata anlegen: `/mnt/user/appdata/healthchecks/postgres18/`.
2. Datei-Secret erzeugen: `/mnt/user/appdata/secrets/healthchecks_postgres_password.txt`
(`chmod 600`), Wert = `HEALTHCHECKS_DB_PASSWORD`.
3. In Komodo die vier Stack-ENV-Variablen setzen (`SECRET_KEY` z. B. via
`python -c "import secrets;print(secrets.token_urlsafe(64))"`).
## Deploy + Pflicht-Webhook
1. Stack in Komodo aus Gitea `Micha/homelab-infra` anlegen, `webhook_enabled` an.
2. Gitea-Webhook auf die neue Stack-ID anlegen
(`http://komodo-core:9120/listener/github/stack/<stack-id>/deploy`),
Branch-Filter `master`. **Pflicht fuer jeden neuen produktiven Stack**
(`docs/WORKFLOW.md`).
3. Test-Delivery ausloesen, `last_status`/Komodo-Deploy pruefen.
## Post-Deploy
1. Login auf `https://hc.kaleschke.info` mit Superuser-Mail/-Passwort.
2. Pro Job einen Check anlegen (Period + Grace passend zur Job-Kadenz, gern als
Cron-Ausdruck). Ping-URL kopieren.
3. **ntfy-Integration**: im Check unter Integrations ntfy hinzufuegen,
Server `https://ntfy.kaleschke.info`, Topic `homelab-alerts` (Problem-Alerts)
— konsistent mit der bestehenden Alert-Schiene.
4. Im Job am Ende des erfolgreichen Laufs pingen, z. B.:
```bash
curl -fsS -m 10 --retry 3 "https://hc.kaleschke.info/ping/<uuid>" >/dev/null || true
```
Optional `/start` am Anfang (misst Laufzeit) und `/<uuid>/fail` im Trap.
## Rollback
- Letzter stabiler Git-Stand: Stack existiert noch nicht — Rollback = Stack in
Komodo stoppen/destroyen, Repo-Pfad `ops/healthchecks/` per `git rm`
zuruecknehmen, Gitea-Webhook deaktivieren.
- Datenpfad `/mnt/user/appdata/healthchecks/postgres18` bleibt unberuehrt und ist
jederzeit loeschbar (reine Check-Metadaten, kein kritischer Datentopf — die
Pings selbst sind in den Jobs definiert).
- Secrets/ENV: bei Abbau die vier Stack-ENV + die Datei-Secret entfernen.
## Image-Pinning
`healthchecks/healthchecks:v4.2@sha256:6b5f59` ist auf den am 2026-06-23 ueber
die Docker-Hub-API ermittelten Manifest-Digest gepinnt. Beim ersten Pull den
real laufenden Digest gegenpruefen und bei Abweichung im Repo nachziehen
(`docs/WORKFLOW.md` Abschnitt Image-Versionierung).