1 Commits

Author SHA1 Message Date
renovate ca78990449 chore(deps): update postgres docker tag to v18 2026-05-29 18:31:29 +00:00
47 changed files with 65 additions and 1621 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
bentopdf: bentopdf:
image: bentopdfteam/bentopdf:2.8.5@sha256:2d867aacb8ab5b196d00ee86944b1899d09d72df355384c5e15cf974737963a0 image: bentopdfteam/bentopdf:2.8.4@sha256:f54b9ed9c56b767e0098b525468206689b666323c2b500b9686c3cf41cdfa348
container_name: bentopdf container_name: bentopdf
restart: unless-stopped restart: unless-stopped
tmpfs: tmpfs:
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
mail-archiver: mail-archiver:
image: s1t5/mailarchiver@sha256:ea7fd8c2e3e0ef0941e8dd9e726e35a8de33296f5c7b9ed811df5168ae6a9714 image: s1t5/mailarchiver@sha256:94d7525db56b13154a14203f8fb7b53fac034f28a914c32da9d2e426b49328ed
container_name: mail-archiver container_name: mail-archiver
restart: unless-stopped restart: unless-stopped
environment: environment:
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
mealie: mealie:
image: ghcr.io/mealie-recipes/mealie:v3.19.2@sha256:f68e959bf66f4f458893ea58facac71690fe6f2ac7a31466b5cecb41b4e99c02 image: ghcr.io/mealie-recipes/mealie:v3.12.0@sha256:8d962f611390a1cca667eed32a29e9467e9c01c523e2db3ad00f667372067f9d
container_name: mealie container_name: mealie
restart: unless-stopped restart: unless-stopped
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
nextcloud: nextcloud:
image: nextcloud:33.0.4-apache@sha256:caa40b8beaf0057ac213d8dfc515c36ce64f7a8f0825b6a287e6f7cf2f4a095d image: nextcloud:33.0.2-apache@sha256:39b2ba219271a22851f8409a7b1295d5892aba1696d9193500311c02e60591a4
container_name: nextcloud container_name: nextcloud
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
ntfy: ntfy:
image: binwiederhier/ntfy@sha256:b32b4221a64ec2e7c000f0782b2feef24022e1a09a24e531640f4cbba6cfa1e6 image: binwiederhier/ntfy@sha256:2b9e12d56a538f4402da51328eeca02696c4b207ab7fbe031c27e51a22ca9b86
container_name: ntfy container_name: ntfy
restart: unless-stopped restart: unless-stopped
dns: dns:
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
paperless-gpt: paperless-gpt:
image: icereed/paperless-gpt:v0.25.1@sha256:c0ce6186028911101a2cfe68353f14a9dbb2653596f3f1cff94de4b6db3114ff image: icereed/paperless-gpt:v0.24.0@sha256:15bad5d455b98f21bb7b5d6615f56871ff67a8bb379dc0dd7ba411f4633071a6
container_name: paperless-gpt container_name: paperless-gpt
restart: unless-stopped restart: unless-stopped
security_opt: security_opt:
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
paperless: paperless:
image: ghcr.io/paperless-ngx/paperless-ngx:2.20.15@sha256:6c86cad803970ea782683a8e80e7403444c5bf3cf70de63b4d3c8e87500db92f image: ghcr.io/paperless-ngx/paperless-ngx:2.20.10@sha256:07a0b4ba01ce377c82a0636e16c0c3d931fde5b7e9304de6601986cc42d9b6e6
container_name: paperless-ngx container_name: paperless-ngx
restart: unless-stopped restart: unless-stopped
security_opt: security_opt:
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
unbound: unbound:
image: shaanmajid/unbound:1.25.1@sha256:96809ff052e8bd79bba30e067d8b27ed9a2f069b6b2a3484fe1d0eb45aba07c5 image: shaanmajid/unbound:1.24.2@sha256:d278b71c592b2555cc802911bb0757a6a24f4a8ad7f5848720296c04876eeb63
container_name: unbound container_name: unbound
restart: unless-stopped restart: unless-stopped
volumes: volumes:
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
gitea: gitea:
image: docker.gitea.com/gitea:1.26.2@sha256:7d13848af12645600a5f9d93ee2560daa9c6fa6b5b859b7bff3a5e1c0b661031 image: docker.gitea.com/gitea:1.25.4@sha256:17d18218be2dad1f8ed402a4f906989505c90ab8b66ee9befcecfb5d470133e7
container_name: gitea container_name: gitea
restart: unless-stopped restart: unless-stopped
security_opt: security_opt:
-3
View File
@@ -4,9 +4,6 @@ Stand: 2026-05-23
Ziel: Alle problemrelevanten Homelab-Meldungen landen auf einem Handy-Topic. Ziel: Alle problemrelevanten Homelab-Meldungen landen auf einem Handy-Topic.
> Die Prometheus-Alarmregeln im Detail (Trigger, Schwellen, Severity,
> Handlungshinweis, Luecken-Analyse) stehen in `docs/ALERT_RULES.md`.
## ntfy Topics ## ntfy Topics
| Topic | Zweck | | Topic | Zweck |
-129
View File
@@ -1,129 +0,0 @@
# Alert Rules
Stand: 2026-05-30
Zentrale Nachschlagetabelle aller Prometheus-Alarmregeln plus Bewertung, ob die
Abdeckung sinnvoll und vollstaendig ist.
- **Authoritative Quelle der Regeln:** `monitoring/prometheus/alerts.yml`
- **Topic-/Sender-Konvention:** `docs/ALERTING_MAP.md`
- Alle Prometheus-Alarme laufen ueber Alertmanager →
`monitoring/alertmanager-ntfy-bridge/bridge.py` → ntfy-Topic `homelab-alerts`.
> Diese Datei ist **Doku**, nicht die Konfiguration. Wer eine Regel aendert,
> aendert `monitoring/prometheus/alerts.yml`, pusht nach Gitea und laesst Komodo
> deployen bzw. Prometheus neu laden. Danach diese Tabelle nachziehen.
## Zwei Alarm-Pfade nebeneinander
Nicht jeder Homelab-Alarm kommt aus Prometheus. Wer "fehlt da was?" beantworten
will, muss beide Pfade zusammen lesen:
| Pfad | Quelle | Beispiele |
|---|---|---|
| **Prometheus / Alertmanager** | `monitoring/prometheus/alerts.yml` | Erreichbarkeit, Zertifikate, Disk/RAM, Borg-Metriken, Critical-Container |
| **Posture-Check / ntfy-direkt** | `services/posture-check/*` | NVMe-SMART, Cert/Token-Check, Compose-Runtime-Drift, Docker `die`/`oom`/`kill`, Authelia-Drift, Borg-Pre-Hook, Restore-Jobs |
Beide enden auf `homelab-alerts`. Der Posture-Pfad ist in `docs/ALERTING_MAP.md`
tabelliert; er wird hier nur referenziert, nicht dupliziert.
## Prometheus-Regeltabelle
Severity-Routing der Bridge: `critical` und `warning` gehen beide auf
`homelab-alerts` (kein eigenes Topic je Severity).
### Gruppe `homelab-availability`
| Alarm | Trigger (PromQL, gekuerzt) | Schwelle / `for` | Severity | Was tun |
|---|---|---|---|---|
| `HomelabExternalConnectivityDown` | `sum(probe_success{blackbox-http}==0) >= 5` | ≥5 Endpunkte / 8m | warning | WAN/DNS/Provider pruefen, nicht pro Domain jagen — Sammelausfall |
| `HomelabEndpointDown` | `probe_success==0` (einzeln, nicht im Sammelausfall) | 1 Endpunkt / 8m | critical | Betroffenen Dienst/Traefik-Route pruefen |
| `HomelabEndpointSlow` | `probe_duration_seconds > 5` | >5s / 5m | warning | Dienst-/Backend-Last pruefen, oft transient |
| `HomelabCertificateExpiresSoon` | Restlaufzeit 721 Tage | <21d & >7d / 30m | warning | ACME/Traefik-Renewal beobachten |
| `HomelabCertificateExpiresCritical` | Restlaufzeit ≤7 Tage (oder abgelaufen) | ≤7d / 15m | critical | Renewal sofort erzwingen/pruefen |
### Gruppe `homelab-host`
| Alarm | Trigger (PromQL, gekuerzt) | Schwelle / `for` | Severity | Was tun |
|---|---|---|---|---|
| `HomelabDiskAlmostFull` | `100*(1-avail/size) > 85` (ohne tmpfs/overlay) | >85% / 10m | warning | Mountpoint aufraeumen / erweitern |
| `HomelabDiskCritical` | `100*(1-avail/size) > 95` (ohne tmpfs/overlay) | >95% / 5m | critical | Sofort Platz schaffen — Writes drohen zu scheitern (DB, appdata, Cache) |
| `HomelabHighMemoryUsage` | `100*(1-MemAvailable/MemTotal) > 90` | >90% / 10m | warning | Speicherfresser identifizieren, ggf. Container-Limit (F-19) |
| `HomelabTraefik5xx` | `increase(traefik_service_requests_total{5..}[5m]) >= 5` je Service | ≥5 / 2m | warning | Backend des betroffenen Service pruefen |
### Gruppe `homelab-backup-and-containers`
| Alarm | Trigger (PromQL, gekuerzt) | Schwelle / `for` | Severity | Was tun |
|---|---|---|---|---|
| `HomelabTextfileExporterStale` | `time()-last_run > 2h` | >2h / 15m | warning | `export-prometheus-textfile.sh`-Cron auf Host pruefen |
| `HomelabBorgMetricsMissing` | `absent(borg_last_completed_ts)` | fehlt / 15m | critical | Textfile-Export oder borg-ui pruefen |
| `HomelabBorgBackupStale` | `time()-borg_last_completed_ts > 30h` | >30h / 15m | warning | Letztes Borg-Backup nachholen/pruefen |
| `HomelabBorgLastJobFailed` | `borg_last_success != 1` | ≠1 / 15m | critical | Borg-UI-Job-Log pruefen, Backup wiederholen |
| `HomelabBorgLastJobCompletedWithWarnings` | `borg_last_job_warning == 1` | =1 / 15m | warning | Warnung im Borg-UI-Job lesen |
| `HomelabCriticalContainerDown` | `homelab_critical_container_running == 0` | =0 / 5m | critical | Container neu starten / Komodo-Stack pruefen (`name`-Label) |
Die Liste der ueberwachten Critical-Container steht in
`services/posture-check/export-prometheus-textfile.sh` (`CRITICAL_CONTAINERS`).
### Gruppe `homelab-meta`
| Alarm | Trigger (PromQL, gekuerzt) | Schwelle / `for` | Severity | Was tun |
|---|---|---|---|---|
| `HomelabPrometheusTargetDown` | `up == 0` | =0 / 5m | critical | Scrape-Ziel (node-exporter/cadvisor/blackbox/traefik) pruefen — Metriken sind sonst still |
## Bewertung: Sind die Alarme sinnvoll?
Insgesamt solide. Die Erreichbarkeits-Gruppe ist gut entworfen — der
Sammelausfall-Trick (`>=5` Endpunkte als ein Warning, Einzelausfall als
Critical) verhindert eine ntfy-Flut bei kurzen DSL-Reconnects. Borg ist mit vier
Regeln (fehlende Metrik, veraltet, fehlgeschlagen, mit Warnungen) gut
abgedeckt.
Anmerkungen / Feinschliff (kein Handlungsdruck):
- **`HomelabDiskAlmostFull` ohne Array-Filter.** Der `fstype!~"tmpfs|overlay"`-
Filter schliesst keine bewusst vollen Unraid-Array-Disks aus. Eine
Datengrab-Disk, die dauerhaft bei 90 % liegt, erzeugt einen Dauer-Warning.
Bei Bedarf per `mountpoint`-Filter auf die wirklich kritischen Pfade
(`/`, appdata-/services-Cache) eingrenzen.
- **`HomelabEndpointSlow` >5s** ist grosszuegig und damit eher ruhig — okay als
bewusste Wahl, faengt aber keine schleichende 34s-Degradierung.
- **`HomelabHighMemoryUsage` 90 %** ist auf einem Host mit ZFS/Unraid-Cache
schnell erreicht (Cache zaehlt nicht als „available" je nach Messung); die
Verwendung von `MemAvailable` ist hier korrekt und mildert das.
## Bewertung: Fehlt etwas? (Luecken, priorisiert)
### Hoch — erledigt 2026-05-30
1. ~~Kein `up == 0` auf Scrape-Targets~~**`HomelabPrometheusTargetDown`**
umgesetzt (Gruppe `homelab-meta`). Faellt node-exporter/cadvisor/blackbox/
traefik aus, feuert jetzt nach 5 Minuten ein Critical.
2. ~~Kein Disk-Critical-Tier~~**`HomelabDiskCritical`** bei >95 % umgesetzt
(Gruppe `homelab-host`), zusaetzlich zum bestehenden Warning bei >85 %.
### Mittel — sinnvoll, aber kein Notstand
3. **Dead-Man's-Switch.** Faellt Prometheus oder die ntfy-Bridge selbst aus,
feuert gar kein Alarm — strukturell blind. Eine immer feuernde
Watchdog-Regel plus externer „Heartbeat fehlt"-Waechter (z. B. Uptime-Kuma
Push-Monitor oder Healthchecks.io) schliesst die Luecke. Bewusst leichtes
Gewicht, weil Posture-Check/Borg-Pre-Hook teilweise unabhaengig laufen.
4. **Inode-Erschoepfung.** Paperless/Immich erzeugen viele kleine Dateien;
`node_filesystem_files_free` kann vor dem Byte-Limit knapp werden. Niedrige
Wahrscheinlichkeit, billiger Alarm.
### Bewusst nicht in Prometheus (anderer Pfad deckt ab)
- **NVMe-SMART-Verschleiss** → `check_nvme_smart` im Posture-Check (ntfy direkt).
- **Compose-Runtime-Drift / Authelia-Drift** → Posture-Check (ntfy direkt).
- **Docker `oom`/`die`/`kill`** → `docker-critical-events.sh` (ntfy direkt) —
dies ist auch der Detektionspfad fuer den ersten echten OOM-Vorfall, der F-19
(Container-Memory-Limits) ausloesen wuerde.
- **Cert/Token-Health jenseits TLS-Ablauf** → `cert-token-check.sh`.
## Stand
Die zwei Hoch-Luecken sind seit 2026-05-30 in `alerts.yml` umgesetzt. Naechster
optionaler Schritt waere der Dead-Man's-Switch ueber einen externen Heartbeat-
Waechter; ohne familienkritischen Anlass aber nicht eilig.
+3 -4
View File
@@ -54,7 +54,7 @@ Kontext bewusst gesichert, bevor weitere Live-Aenderungen passieren:
| erledigt | `docs/STORAGE_LAYOUT.draft.md` finalisieren | Datei als `docs/STORAGE_LAYOUT.md` Active v1.4 gefuehrt; Draft-Blocker entfernt | | erledigt | `docs/STORAGE_LAYOUT.draft.md` finalisieren | Datei als `docs/STORAGE_LAYOUT.md` Active v1.4 gefuehrt; Draft-Blocker entfernt |
| erledigt (Baseline) | Disk- und Share-TBDs eintragen | Disk-Modelle, Seriennummern, Groessen, Filesysteme und Share-Cache-Settings aus `docs/HARDWARE_INVENTORY.md` und Host-Readout 2026-05-27 uebernommen; Retention-/Schwellen-Kalibrierung bleibt Folgeaufgabe | | erledigt (Baseline) | Disk- und Share-TBDs eintragen | Disk-Modelle, Seriennummern, Groessen, Filesysteme und Share-Cache-Settings aus `docs/HARDWARE_INVENTORY.md` und Host-Readout 2026-05-27 uebernommen; Retention-/Schwellen-Kalibrierung bleibt Folgeaufgabe |
| erledigt | Gitea-Repo-Mirror-Mechanik definieren | `ops/borg-ui/scripts/gitea-bundle-mirror.sh` erzeugt verifizierte Bundles unter `/mnt/user/backups/git-bundles/gitea`; Host-Erstlauf 2026-05-26: 4 Bundles, Checksums OK, `homelab-infra.bundle` klonbar und `git fsck` sauber. Schedule live seit 2026-05-27 ueber User-Script `gitea-bundle-mirror-6h` (`10 */6 * * *`); Bundles werden mit `chmod 644` geschrieben damit der Nearline-Pull sie greift. | | erledigt | Gitea-Repo-Mirror-Mechanik definieren | `ops/borg-ui/scripts/gitea-bundle-mirror.sh` erzeugt verifizierte Bundles unter `/mnt/user/backups/git-bundles/gitea`; Host-Erstlauf 2026-05-26: 4 Bundles, Checksums OK, `homelab-infra.bundle` klonbar und `git fsck` sauber. Schedule live seit 2026-05-27 ueber User-Script `gitea-bundle-mirror-6h` (`10 */6 * * *`); Bundles werden mit `chmod 644` geschrieben damit der Nearline-Pull sie greift. |
| erledigt (Doku + Skript + Erstlauf) | Komodo-Bootstrap-Pfad beschreiben | `docs/SERVICES_RECOVERY.md` enthaelt linearen Bootstrap in Stufen A-F mit Recovery-Anker `ops/komodo/docker-compose.yml`, expliziter Abgrenzung zum Self-Stack, Secret-Reihenfolge und Validierungs-Kommandos; `docs/DISASTER_RECOVERY.md` Stufe 3 verlinkt auf Bootstrap-Pfad. Trockenlauf-Skript unter `ops/restore-tests/komodo-bootstrap-*` seit 2026-05-29 vorhanden, Erstlauf 2026-05-30 erfolgreich (siehe Sprint 8 Eintrag). | | erledigt (Doku) | Komodo-Bootstrap-Pfad beschreiben | `docs/SERVICES_RECOVERY.md` enthaelt linearen Bootstrap in Stufen A-F mit Recovery-Anker `ops/komodo/docker-compose.yml`, expliziter Abgrenzung zum Self-Stack, Secret-Reihenfolge und Validierungs-Kommandos; `docs/DISASTER_RECOVERY.md` Stufe 3 verlinkt auf Bootstrap-Pfad. Trockenlauf-Skript bleibt als offene Folgeaufgabe. |
| erledigt | Immich-Restore-Test planen | Testumfang, Datenpfade und Smoke-Test-Kriterium sind in `docs/IMMICH_RESTORE_TEST.md`, `ops/restore-tests/immich-plan.md` und `ops/restore-tests/immich-runbook.md` festgehalten; erster Host-Lauf am 2026-05-27 erfolgreich | | erledigt | Immich-Restore-Test planen | Testumfang, Datenpfade und Smoke-Test-Kriterium sind in `docs/IMMICH_RESTORE_TEST.md`, `ops/restore-tests/immich-plan.md` und `ops/restore-tests/immich-runbook.md` festgehalten; erster Host-Lauf am 2026-05-27 erfolgreich |
## Sprint 3 - Restore und Monitoring ## Sprint 3 - Restore und Monitoring
@@ -105,9 +105,8 @@ In diesem Audit-Zyklus werden diese Punkte **nicht** umgesetzt. Sie sind dokumen
|---|---|---| |---|---|---|
| erledigt 2026-05-29 | Healthchecks fuer Tier-1 (F-15) | postgresql17 (`pg_isready`), Redis (`redis-cli ping` mit Auth), Vaultwarden (`curl /alive`), Gitea (`wget /api/healthz`), Traefik (`traefik healthcheck --ping`, `--ping=true` in CLI), Authelia (`wget /api/health`, weil v4.39 `helper health-check` entfernt hat); komodo-mongo war bereits gepinnt healthy. Live-Smoke: alle 6 healthy nach Recreate. Postgres- und Gitea-Stack-Workspace waren Komodo-seitig zurueckgeblieben (124 bzw. 52 commits behind); manuell per `cp` + `docker compose up -d` synchronisiert. | | erledigt 2026-05-29 | Healthchecks fuer Tier-1 (F-15) | postgresql17 (`pg_isready`), Redis (`redis-cli ping` mit Auth), Vaultwarden (`curl /alive`), Gitea (`wget /api/healthz`), Traefik (`traefik healthcheck --ping`, `--ping=true` in CLI), Authelia (`wget /api/health`, weil v4.39 `helper health-check` entfernt hat); komodo-mongo war bereits gepinnt healthy. Live-Smoke: alle 6 healthy nach Recreate. Postgres- und Gitea-Stack-Workspace waren Komodo-seitig zurueckgeblieben (124 bzw. 52 commits behind); manuell per `cp` + `docker compose up -d` synchronisiert. |
| erledigt 2026-05-29 | Monitoring-Stack Digest-Pinning (F-07) | 9 Container in `monitoring/docker-compose.yml` per Tag@sha256 gepinnt: prometheus, alertmanager, alertmanager-ntfy-bridge (python:3.13-alpine), blackbox-exporter, loki, promtail, grafana, node-exporter, cadvisor. Digests aus dem aktuell laufenden Container ausgelesen, damit der Pin den Live-Stand reflektiert. influxdb3-core war bereits gepinnt. | | erledigt 2026-05-29 | Monitoring-Stack Digest-Pinning (F-07) | 9 Container in `monitoring/docker-compose.yml` per Tag@sha256 gepinnt: prometheus, alertmanager, alertmanager-ntfy-bridge (python:3.13-alpine), blackbox-exporter, loki, promtail, grafana, node-exporter, cadvisor. Digests aus dem aktuell laufenden Container ausgelesen, damit der Pin den Live-Stand reflektiert. influxdb3-core war bereits gepinnt. |
| erledigt 2026-05-29 (Skript) / 2026-05-30 (Erstlauf) | Komodo-Bootstrap-Trockenlauf-Skript (F-09 Rest) | `ops/restore-tests/komodo-bootstrap-{compose.test.yml,test.sh,plan.md,runbook.md}` analog zum Immich-Restore-Test angelegt. Test-Compose nutzt dieselben Image-Digests wie Produktion, isoliert unter Project `restoretest-komodo`, Test-Periphery ohne docker.sock-Mount, Test-Port nur `127.0.0.1:19120`. Wegwerf-Secrets im Compose. **Erstlauf 2026-05-30 erfolgreich**: Result `SUCCESS`, alle 5 Checks gruen — compose config valid, Test-Mongo healthy (6s), Mongo authenticated ping ok, Komodo Core HTTP `200`, Test-Periphery container state `running`. Report unter `/mnt/user/backups/restore-reports/komodo-bootstrap-2026-05-30.md`. Produktive Komodo-Container, Mongo-Datadir und Secrets nicht beruehrt. Damit ist `ops/komodo/docker-compose.yml` als Recovery-Anker belegt tauglich (nicht mehr nur angenommen). | | erledigt 2026-05-29 | Komodo-Bootstrap-Trockenlauf-Skript (F-09 Rest) | `ops/restore-tests/komodo-bootstrap-{compose.test.yml,test.sh,plan.md,runbook.md}` analog zum Immich-Restore-Test angelegt. Test-Compose nutzt dieselben Image-Digests wie Produktion, isoliert unter Project `restoretest-komodo`, Test-Periphery ohne docker.sock-Mount, Test-Port nur `127.0.0.1:19120`. Wegwerf-Secrets im Compose. Erster Lauf manuell durch Operator. |
| erledigt 2026-05-29 | Renovate-Bot gegen Gitea (F-12) | Live: Service-Account `renovate` (uid 2, kein Admin) angelegt, Collaborator Write auf `Micha/homelab-infra`, PAT in `/mnt/user/appdata/secrets/renovate_token.txt` (chmod 600). Cron `renovate-six-hourly` (`20 */6 * * *`) live in `/etc/cron.d/root`. Erstlauf 2026-05-29 erfolgreich: 5 PRs (mongo digest+minor, postgres digest+minor, minor-and-patch-updates gruppiert), 1 Dependency-Dashboard-Issue, 8 Branches. Komodo-Major durch packageRule deaktiviert wie erwartet. Architektur-Detail: Repo-Config in `renovate.json`, Bot-Config in `ops/renovate/bot-config.js` (Renovate liest die im Repo nur als Repo-Config, Bot-Settings dort triggern "forbidden/disabled"). | | vorbereitet 2026-05-29 (Setup-Schritte Operator-Aufgabe) | Renovate-Bot gegen Gitea (F-12) | `renovate.json` im Repo-Root mit Group-Rules (Major getrennt, Minor/Patch/Digest gruppiert, Tier-1-Datenhalter einzeln, Komodo-Major deaktiviert). `ops/renovate/run-renovate.sh` als One-Shot-Wrapper. `docs/RENOVATE.md` mit Setup-Anleitung: Gitea-Service-Account `renovate` anlegen, PAT erzeugen, in `/mnt/user/appdata/secrets/renovate_token.txt` ablegen, User-Script `renovate-six-hourly` (`20 */6 * * *`) aktivieren. Kein Auto-Merge, jede PR braucht Operator-Sichtpruefung. |
| erledigt 2026-05-30 | Authelia Repo<->Host Drift-Check (F-10) | `services/authelia-diff.sh` vergleicht die `access_control:`-Sektion zwischen Repo-Baseline und Host-Datei (Default; per env `AUTHELIA_DIFF_SECTIONS` erweiterbar). OIDC-Clients/Identity-Provider und Secret-Werte bleiben bewusst aussen vor. Exit-Codes: 0 = ok, 1 = Drift, 2 = Datei fehlt, 3 = Sektion fehlt, 4 = Werkzeug fehlt. Posture-Check ruft das Skript als Check `authelia_config_drift` auf (`SKIP_AUTHELIA_DRIFT=1` skippt, `AUTHELIA_DIFF_SCRIPT` ueberschreibt den Pfad); Drift wird als Warning gemeldet, nicht Critical. Smoke-Test lokal: identische Files -> rc=0, ACL-Drift im Domain-Eintrag -> rc=1 mit unified diff. WORKFLOW.md hat jetzt eine eigene Pflicht-Sektion "Ausnahme: Authelia configuration.yml" analog zur Traefik-Dynamic-Sektion. Pflicht-Setup auf dem Host: Repo-Spiegel unter `/mnt/user/services/homelab-infra/`. |
## Sprint 7 - Off-site und 3-2-1 (offen) ## Sprint 7 - Off-site und 3-2-1 (offen)
-202
View File
@@ -1,202 +0,0 @@
# Codex-Prompt: Jellyfin entfernen, Plex bleibt
> **Status (Stand 2026-05-30):** Auftrag ausgefuehrt. Jellyfin wurde 2026-05-25 aus Repo, Komodo, Traefik-Routing, Authelia-ACL und Appdata-Live-Pfad entfernt (Service-Removal-Checkliste in `docs/WORKFLOW.md`, MIGRATION_LOG-Eintrag dort). Datei bleibt im Repo als **Codex-Removal-Pattern** fuer kuenftige Stack-Removals (z.B. Hermes nach Review-Deadline 2026-07-25 oder bei BentoPDF/paperless-gpt-Folgeentscheidung). Inhaltlich nicht mehr aendern — als Vorlage referenzieren und pro Anwendung neu instanzieren.
Stand: 2026-05-23
Ausloeser: Operator-Entscheidung "Plex bleibt, Jellyfin weg".
Bezug: `docs/STRATEGISCHE_BEWERTUNG_2026-05-23.md` Block 9 Quick Wins.
Du hast Vollzugriff auf `G:\Gitea_Clone\homelab-infra`, Gitea-Push, Komodo, und SSH auf Unraid `Kallilabcore`.
## Lies zuerst
1. `CLAUDE.md`
2. `HOMELAB_ARCHITECTURE_MASTER_V2.md` Abschnitt 7.4 und 7.8
3. `docs/WORKFLOW.md`
4. `docs/ROLLBACK.md`
5. `apps/jellyfin/docker-compose.yml`
6. `security/authelia/configuration.yml` (Access-Control-Block)
7. `docs/SERVICE_CATALOG.md`, `docs/REPO_MAP.md`, `docs/MIGRATION_LOG.md`
## Ziel
Jellyfin ist vollstaendig aus dem Repo, Komodo, Traefik-Routing und Authelia-ACL entfernt. Plex laeuft unveraendert weiter. Medien-/Foto-Mounts unter `/mnt/user/media` und `/mnt/user/photos` bleiben unberuehrt. Domain `jellyfin.kaleschke.info` antwortet nicht mehr ueber Traefik.
## Wichtige Vorabinformation
- `/mnt/user/appdata/jellyfin/{config,cache}` ist **nicht** im Borg-Scope (`ops/borg-ui/all-important-sources.txt`). Watch-History, User-Settings und Metadaten-Cache sind danach weg. Das ist akzeptabel, weil Plex die Nutzungsdaten ohnehin separat fuehrt.
- Plex teilt sich `/mnt/user/media:ro` und `/mnt/user/photos:ro` mit Jellyfin — Datenpfad bleibt unangetastet.
- Authelia-Eintrag `jellyfin.kaleschke.info` steht unter `policy: bypass` in `security/authelia/configuration.yml` Zeile 42. Die `configuration.yml` ist Repo-Baseline und muss laut `docs/AI_CONTEXT.md` manuell auf den Host gemerged werden.
- Jellyfin hat **keinen** Eintrag in `monitoring/blackbox/blackbox.yml`, `ops/glance/config/glance.yml`, `apps/homepage/docker-compose.yml`. Die Homepage-Service-Cards liegen hostseitig unter `/mnt/user/appdata/homepage/config/services.yaml` — dort moeglicherweise ein Eintrag, vor Ort pruefen.
## Reihenfolge
### P0 — Plex-Tauglichkeit verifizieren (vor jeglichem Loeschen)
Smoke-Test auf dem Host:
```bash
curl -fsS -o /dev/null -w "%{http_code}\n" http://192.168.178.58:32400/identity
docker exec plex test -d /data/Filme || echo "Filme nicht erreichbar"
docker exec plex test -d /photos || echo "Photos nicht erreichbar"
```
Akzeptanzkriterium: Plex antwortet mit `200`, beide Medien-Pfade sind im Plex-Container sichtbar, Plex zeigt in seiner Web-UI alle erwarteten Bibliotheken. **Wenn nicht erfuellt: abbrechen und Operator fragen.**
### P1 — Jellyfin Stack in Komodo stoppen (nicht loeschen)
In Komodo Web-UI Stack `jellyfin` `Stop` ausfuehren (nicht `Destroy`). Damit ist der Container weg, Stack-Definition und Workspace bleiben — Rollback per `Start` moeglich.
Akzeptanzkriterium:
```bash
docker ps -a --filter name=jellyfin --format "{{.Names}}\t{{.Status}}"
```
zeigt entweder keine Zeile oder `Exited`.
### P2 — Authelia ACL um Jellyfin-Bypass bereinigen
Datei: `security/authelia/configuration.yml`
```diff
- domain:
- immich.kaleschke.info
- paperless.kaleschke.info
- mealie.kaleschke.info
- vault.kaleschke.info
- ntfy.kaleschke.info
- git.kaleschke.info
- - jellyfin.kaleschke.info
policy: bypass
```
Kein anderer Authelia-Eintrag referenziert Jellyfin. Wildcard `*.kaleschke.info` mit `policy: one_factor` greift fuer geloeschte Domains nicht, weil Traefik die Route nicht mehr kennt.
**Manueller Host-Sync danach Pflicht** (`docs/AI_CONTEXT.md`, Workflow): die geaenderte `configuration.yml` muss auf `/mnt/user/appdata/authelia/config/configuration.yml` gemerged werden (OIDC-/Secret-Block hostseitig erhalten). Danach `docker exec authelia authelia validate-config -c /config/configuration.yml` und Stack-Restart.
### P3 — Compose-Stack aus Repo entfernen
```bash
git rm -r apps/jellyfin/
```
Akzeptanzkriterium: `apps/jellyfin/` existiert nicht mehr; `git status --short` zeigt `D apps/jellyfin/docker-compose.yml`.
### P4 — Doku synchronisieren
**`HOMELAB_ARCHITECTURE_MASTER_V2.md`:**
- Abschnitt 3.2 Diagramm: `jellyfin` aus oeffentliche-Apps-Zeile entfernen.
- Abschnitt 4.1 "Oeffentlich ueber Traefik": Zeile `- jellyfin — jellyfin.kaleschke.info` entfernen.
- Abschnitt 7.4 Tabelle "Produktive Apps": Zeile `jellyfin` entfernen.
- Abschnitt 7.8 "Entfernte Container": neue Zeile
```text
| `jellyfin` | 2026-05-23 | doppelter Medienserver neben Plex; Plex bleibt einziger Medienserver |
```
**`docs/SERVICE_CATALOG.md`:**
- Block "Public / User Apps": Jellyfin-Zeile entfernen.
**`docs/REPO_MAP.md`:**
- Abschnitt "Apps"-Tabelle: Jellyfin-Zeile entfernen.
- Abschnitt "Traefik Hosts": `jellyfin.kaleschke.info`-Zeile entfernen.
- Abschnitt "Volumes und Datenpfade": Jellyfin-Zeile entfernen.
**`docs/MIGRATION_LOG.md`:** neuen Eintrag anhaengen
```text
### Jellyfin entfernt (2026-05-23)
- Operator-Entscheidung: Plex bleibt einziger Medienserver.
- Compose-Stack `apps/jellyfin/` aus Repo entfernt.
- Authelia-ACL bereinigt (`jellyfin.kaleschke.info` aus `bypass`-Liste raus), Host-Config gemerged.
- Komodo-Stack `jellyfin` gestoppt; Stack-Eintrag und Webhook bei Abschluss von Schritt P7 entfernt.
- Appdata unter `/mnt/user/appdata/jellyfin/` nach `/mnt/user/appdata/_archive/jellyfin-removed-2026-05-23/` verschoben (siehe Schritt P5).
- DNS-Eintrag `jellyfin.kaleschke.info` in Cloudflare belassen, kann beim naechsten DNS-Cleanup mit entfernt werden.
```
### P5 — Appdata archivieren statt loeschen
Auf dem Host:
```bash
mkdir -p /mnt/user/appdata/_archive
mv /mnt/user/appdata/jellyfin /mnt/user/appdata/_archive/jellyfin-removed-2026-05-23
```
Akzeptanzkriterium: `/mnt/user/appdata/jellyfin` existiert nicht mehr, `/mnt/user/appdata/_archive/jellyfin-removed-2026-05-23` enthaelt `config/` und `cache/`. **Nicht endgueltig loeschen** vor mindestens 14 Tagen Plex-Stabilitaet.
### P6 — Policy-Check + Commit
```powershell
pwsh ops/policy-checks/check_repo.ps1
```
Akzeptanzkriterium: 0 Critical, keine neuen Warnings (vorher: 4 dokumentierte Warnings, soll danach gleich bleiben).
Commit:
```bash
git add -A
git commit -m "Remove Jellyfin stack; Plex remains sole media server"
git push origin master
```
### P7 — Komodo-Stack-Eintrag und Webhook entfernen
Nach erfolgreichem Push und gruener Komodo-Reaktion auf restliche Stacks:
- In Komodo: Stack `jellyfin` `Destroy` (Workspace `/mnt/user/services/stacks/jellyfin/` entfernen).
- In Gitea: Webhook-Eintrag fuer Komodo-Stack-ID `jellyfin` entfernen.
Akzeptanzkriterium: `ls /mnt/user/services/stacks/ | grep jellyfin` ist leer; Gitea-Webhook-Liste fuer `Micha/homelab-infra` enthaelt keinen Jellyfin-Eintrag mehr.
### P8 — Smoke-Test final
```bash
curl -fsS -o /dev/null -w "%{http_code}\n" https://jellyfin.kaleschke.info/
docker ps --filter name=jellyfin --format "{{.Names}}"
ss -ltnp | grep 8096 || echo "Port 8096 frei"
```
Erwartung:
- `https://jellyfin.kaleschke.info/` antwortet `404` (Traefik kennt die Route nicht mehr) oder Cert-Fehler je nach Cache.
- Kein laufender `jellyfin`-Container.
- Port `8096` nicht belegt (Jellyfin nutzte nur Container-Port, sollte sowieso frei sein).
- Authelia-Login fuer Admin-Domains (`uptime`, `files`, `scrutiny`) funktioniert weiterhin — Bypass-Liste-Aenderung darf 2FA-Domains nicht angreifen.
## Rollback (bis Schritt P5 einschliesslich)
- Git: `git revert <commit-sha>` und push. Komodo deployt Jellyfin neu, sobald Stack in Komodo wieder existiert.
- Appdata: `mv /mnt/user/appdata/_archive/jellyfin-removed-2026-05-23 /mnt/user/appdata/jellyfin`.
- Authelia-ACL: Bypass-Eintrag wieder rein, Host-Sync, Authelia-Restart.
Ab Schritt P7 (Komodo Destroy + Webhook weg) ist Rollback nur per Neuanlegen des Komodo-Stacks moeglich.
## Regeln (aus CLAUDE.md, nicht verhandelbar)
- Secrets nie im Klartext ausgeben.
- Keine Aenderungen direkt in Komodo, nur ueber Git → Push → Komodo. **Ausnahme:** Schritt P1 (`Stop`) und P7 (`Destroy`) sind explizit Komodo-Aktionen nach erfolgreichem Repo-Stand.
- Kein `push --force`, kein blindes Loeschen unter `/mnt/user/{appdata,documents,photos,services,backups}` — Appdata wird in `_archive/` verschoben, nicht entfernt.
- Working-Tree-Status nur aus `git status --short` ableiten, nie aus `git diff` ueber Linux-Mount.
- Traefik dynamic config wird nicht von Komodo deployed — fuer diesen Auftrag nicht relevant, weil Jellyfin nur per Docker-Labels gerouteted war.
- Nicht anfassen: Plex-Stack, Medien-/Foto-Mounts, alle anderen Apps.
- Wenn zwei Reparaturversuche scheitern: stoppen, `docs/GITOPS_DRIFT_RUNBOOK.md` Pflichtmatrix, Operator fragen.
## Arbeitsmodus pro Schritt
Lesen → minimal aendern → `ops/policy-checks/check_repo.ps1` lokal (nur P6) → Commit → Push → Komodo-Reaktion + Smoke-Test → Eintrag in `docs/MIGRATION_LOG.md`.
## Fertig
Wenn alle 8 Schritte abgehakt sind: kurze Erfolgsmeldung an Operator mit:
- Commit-SHA des Removal-Commits
- Bestaetigung Plex weiterhin gruen
- Bestaetigung Authelia validate-config gruen
- Bestaetigung Komodo-Stack und Webhook entfernt
- Pfad zum Appdata-Archiv und Erinnerung, dass `_archive/jellyfin-removed-2026-05-23/` nach 14 Tagen Plex-Stabilitaet entfernt werden darf
-123
View File
@@ -1,123 +0,0 @@
# Codex-Prompt: Komodo 5xx-Spam Root-Cause
Stand: 2026-05-31
Auftraggeber: Operator
Vorarbeit: Claude (auto-mode), siehe Ermittlungsstand unten.
## Auftrag
`HomelabTraefik5xx` feuert dauerhaft fuer `service="komodo@docker"`. Quelle
finden, fixen, dokumentieren. Bitte einmal **bis zum Ende** durchziehen, nicht
nur eine Hypothese pruefen.
## Vor Arbeitsbeginn lesen
- `CLAUDE.md`
- `docs/WORKFLOW.md`
- `monitoring/prometheus/alerts.yml`
- `docs/ALERT_RULES.md`
- `ops/komodo/docker-compose.yml`
- `traefik/docker-compose.yml`
- `monitoring/prometheus/prometheus.yml` (Blackbox-Targets)
- `monitoring/blackbox/blackbox.yml`
- `ops/glance/config/glance.yml` (5 Komodo-URL-Stellen, **NICHT** die Quelle — siehe Ermittlung)
## Ermittlungsstand (bereits geklaert)
### Was gemessen wurde
- Traefik-Access-Log: Source-IP ist **eure WAN-IP `217.249.121.39`** (Hairpin
aus dem Heimnetz). User-Agent leer (`"-"`).
- Muster: `GET /` 200 **alle 15s** + `GET /user` **500** alle 30s, plus
gelegentlich `POST /auth/login/GetLoginOptions` 200 und
`POST /read/GetCoreInfo` 500.
- Prometheus `sum by (code) (increase(traefik_service_requests_total{service="komodo@docker"}[5m]))`:
`200`=22, `500`=14 (Werte vom 2026-05-31 08:11 UTC).
- `docker logs komodo-core` ist still — keine internen Errors, nur normale
Execute-Requests. Komodo wirft den 500 also vermutlich auf Auth-Pfad
(`/user` ohne gueltige Session sollte `401` sein, nicht `500`). Das ist ein
Komodo-Bug-on-Top, **aber nicht die Frage**.
### Ausgeschlossene Kandidaten (durch Test)
- **Browser-Tabs** — User hat alle Komodo-Tabs zugemacht, Polling laeuft
weiter.
- **PWA auf Handy** — User hat keine.
- **Uptime-Kuma** — Container existiert nicht mehr.
- **Homepage** — entfernt.
- **Glance** — Test 2026-05-31 ~08:35 UTC: 130s gestoppt, 5xx-Rate
unveraendert (2/60s Baseline → 4/130s waehrend Stop). Trotz 5 Komodo-URL-
Eintraegen in `ops/glance/config/glance.yml` (search-shortcut Zeile 40,
bookmark Zeilen 131/768, monitor-Widget Zeile 237 mit `check-url:
http://komodo-core:9120`, docker-containers-Widget Zeile 725). Glance ist
raus.
### Noch nicht getestete Kandidaten
- **Posture-Check / cert-token-check.sh** (`services/posture-check/`) — koennte
periodisch Komodo-HTTPS pingen. 15s-/30s-Kadenz waere ungewoehnlich fuer
einen Cron-Job, aber pruefen.
- **Blackbox-Exporter** — pollt laut `monitoring/prometheus/prometheus.yml`
`https://komodo.kaleschke.info` alle 15s. Das erklaert den `GET / 200`-
Anteil sauber. Erklaert aber NICHT den `GET /user 500` 30s-Takt.
- **Komodo Periphery** — auf `komodo_net` und `frontend_net`. Sollte mit
Core via internes Netz reden, koennte aber per Misconfig die Public-URL
treffen. Logs noch nicht eingesehen.
- **Komodo Core selbst** mit `KOMODO_HOST=https://komodo.kaleschke.info`
evtl. Self-Check via Public-URL.
- **Ein Gerat im LAN**, das wir noch nicht auf dem Schirm haben (zweiter
Rechner mit altem Tab, Smart-TV, etc.).
### Was nicht geht
- `tcpdump` fehlt auf dem Host.
- `conntrack` zeigt die Hairpin-Pakete nicht (NAT-Pre-Routing).
## Naechste Schritte (Vorschlag)
1. **Blackbox-Exporter ausschliessen**: Targets in `prometheus.yml` zeigen,
dass Blackbox NUR `https://komodo.kaleschke.info` pollt (also `/`, kein
`/user`). Bestaetigen.
2. **Posture-Check pruefen**: `services/posture-check/cert-token-check.sh`
lesen, Kadenz und Endpunkte protokollieren. Falls dort `/user` oder ein
30s-Loop drin ist → Treffer.
3. **Periphery isolieren**: Periphery 2 min stoppen, Traefik-Log gegen-
checken. `docker stop komodo-periphery; sleep 130; <log-check>; docker
start komodo-periphery`. Vorsicht: Periphery-Down heisst Komodo-Deploy
funktioniert nicht — also nur kurz, kein Deploy in dem Fenster.
4. **Komodo-Core isolieren**: Wenn 1-3 nichts ergeben, Komodo-Core selbst 2 min
stoppen. Wenn Polling weiterlaeuft, ist der Client ausserhalb der Komodo-
Stack (LAN-Geraet). Wenn es aufhoert, polled Komodo Core sich selbst.
5. **LAN-Aufnahme via Komodo-Container**: Falls Container-Stack ausgeschlossen,
im komodo-core-Container per `ss -tnp state syn-recv` waehrend einer
typischen Polling-Sekunde mitschauen. Source-IP/Port der eingehenden
Connection liefert den Hairpin-Origin am genauesten.
## Fix-Erwartung
Sobald Quelle bekannt:
- **Wenn Container im Stack**: Config so anpassen, dass die Anfrage intern
laeuft (kein Public-Hostname), inkl. Doku.
- **Wenn LAN-Geraet**: User informieren, was es ist; wenn moeglich Geraet
reparieren (Tab schliessen, App deinstallieren). Kein Repo-Change noetig.
- **Wenn nicht abstellbar**: separate Frage, ob `HomelabTraefik5xx` fuer
`service="komodo@docker"` mit einem Exclude versehen werden soll — aber nur
als letzter Ausweg. Default ist: Quelle fixen.
## Doku am Ende
- Eintrag in `docs/MIGRATION_LOG.md`: Datum, Symptom, Root-Cause, Fix,
Smoke-Test.
- Falls eine Glance-/Periphery-/sonstige Config-Aenderung noetig wird:
Standard-Loop (Commit → Push → Komodo-Deploy → Smoke), Co-Authored-By-Tag
mitgeben.
## Regeln (nicht verhandelbar)
- Git → Push → Komodo. Keine direkten Komodo-Edits.
- Stop/Start-Tests sind okay, aber nur kurz (≤ 3 min) und mit
Wiederanlauf-Schritt im selben Block.
- Secrets nicht ausgeben.
- Bei zwei gescheiterten Versuchen: stop, Pflichtmatrix aus
`docs/GITOPS_DRIFT_RUNBOOK.md`, Operator fragen.
-83
View File
@@ -1,83 +0,0 @@
# Codex-Prompt: KalliLab Konsolidierung (Bewertungs-Followup)
> **Status (Stand 2026-05-30):** Erstprompt fuer den Audit-Zyklus 2026-05-25, Stand weitgehend abgearbeitet. Verbleibende Punkte sind in `docs/AUDIT_2026-05-25_TODO.md` weiter gefuehrt (offen, geparkt, bewusst nicht umgesetzt). Datei bleibt im Repo als **Codex-Prompt-Vorlage** fuer kuenftige Konsolidierungs-Sweeps; inhaltlich nicht mehr aendern.
Stand: 2026-05-23
Auftraggeber: Operator
Quelle: `docs/STRATEGISCHE_BEWERTUNG_2026-05-23.md`
## Schritt 0 — Reviewe die Bewertung kritisch
Lies `docs/STRATEGISCHE_BEWERTUNG_2026-05-23.md` komplett. Bevor du irgendetwas anfasst, sag dem Operator ehrlich:
- Wo ist Claudes Befund richtig?
- Wo liegt Claude daneben oder hat etwas Wichtiges uebersehen?
- Welche Hausaufgaben unten wuerdest du anders priorisieren oder weglassen?
Erst nach Operator-Freigabe weitermachen.
## Lies vor jedem Block
`CLAUDE.md`, `docs/WORKFLOW.md`, betroffene Compose-Datei. Bei DR/Backup zusaetzlich `docs/DISASTER_RECOVERY.md` und `docs/RESTORE_MATRIX.md`.
## Hausaufgaben
### P0 — Quick Wins (≤ 1 Woche, hoher Nutzen)
1. **Externer Repo-Mirror** einrichten (GitHub privat oder zweites Gitea); Push-Mirror in Gitea aktivieren. Schliesst das groesste DR-Loch.
2. **Borg-Passphrase analog sichern** (Schliessfach oder Familienmitglied).
3. **Jellyfin entfernen, Plex bleibt.** Detail-Schritte in `docs/CODEX_JELLYFIN_REMOVAL_2026-05-23.md`. Kurzfassung: Plex-Smoke-Test → Komodo-Stop → Authelia-Bypass raus + Host-Sync → `git rm apps/jellyfin/` → Doku (MASTER 3.2/4.1/7.4/7.8, SERVICE_CATALOG, REPO_MAP, MIGRATION_LOG) → Appdata nach `_archive/` → Policy-Check → Push → Komodo-Destroy + Webhook weg.
4. **Glance oder Homepage** als einziges Dashboard waehlen, das andere stoppen und aus Repo entfernen.
5. **AdGuard Admin-Port 8082** hinter Authelia oder nur via Tailscale (Block F aus MASTER 10).
6. **Authelia 2FA-Pflicht** fuer alle aktiven User verifizieren bzw. aktivieren.
7. **Disk1 NTFS → XFS Phase 2** abschliessen, anschliessend `ALLOW_DISK1_NTFS=0` in posture-check.
### P1 — Stabilitaet und Ordnung (24 Wochen)
8. **Monitoring-Migration abschliessen**: `monitoring/` produktiv, `ops/grafana-influxdb` + `ops/loki` `down` + aus Repo entfernen.
9. **Uptime-Kuma abloesen** durch Blackbox + Grafana-Alerts (nach 7 Tagen Parallelbetrieb mit Paritaet, wie in SERVICE_CATALOG vorgesehen).
10. **Hermes-Agent Entscheidung**: produktiv mit klarem Alltagsnutzen oder vollstaendig entfernen. Kein weiteres Quartal "halb da".
11. **paperless-gpt und BentoPDF**: gleiche Frage. Produktiv im Workflow oder weg.
12. **Unraid USB-Flash-Backup** einrichten (eingebauter Mechanismus).
13. **Family-View-Dashboard** in Grafana: alles-gruen-Uebersicht fuer den Morgen-Check.
### P2 — Automatisierung und Transparenz (412 Wochen)
14. **Authelia OIDC-Provider** aktivieren; Nextcloud + Immich + Grafana als OIDC-Clients.
15. **Renovate Bot gegen Gitea** fuer kontrollierte Image-Update-PRs (loest die manuelle Digest-Pflege ab).
16. **Restore-Test fuer Immich** als eigener Sprint einplanen (groesster Datentopf ohne Mini-Restore).
17. **Immich Smartphone-Auto-Backup** fuer alle Familien-Geraete aktivieren — der eigentliche Familien-Nutzen.
18. **CrowdSec vor Traefik** als Bouncer fuer oeffentlich erreichbare Apps.
### P3 — Advanced (36 Monate)
19. Staging-Branch + zweites Komodo-Ziel in Tailscale-VM.
20. Restore-Test-Automatisierung als CI (Gitea Actions oder Drone).
21. Off-Site-Backup zu zweitem Ziel (zweites BorgBase-Repo oder Hetzner Storage Box).
22. Cold-Standby-Konzept dokumentieren.
23. Komodo-Self-Stack aus Komodo-Management herausnehmen, als handgepflegter `docker compose`-Service in `services/`.
### P4 — Nice-to-have / Spielwiese
24. Firefly III oder Actual Budget fuer `/mnt/user/finance`.
25. Wandtablet im Flur mit Family-Dashboard.
26. Home Assistant tiefer in ntfy-Workflows verzahnen (Frostwarnung, PV-Ueberschuss, Briefkasten).
27. Ecowitt-Wetter-Dashboard, sobald HA→InfluxDB-Pipeline aus `docs/HOME_ASSISTANT_INFLUXDB_ECOWITT.md` laeuft.
## Regeln (aus CLAUDE.md, nicht verhandelbar)
- Git → Push → Komodo. Keine direkten Komodo-Edits, kein `push --force`.
- Secrets nie ins Repo, nie loggen.
- Appdata-Pfade nicht blind loeschen — vor Removal nach `/mnt/user/appdata/_archive/<ding>-removed-<datum>/` verschieben, 14 Tage warten.
- Traefik dynamic config manueller Host-Sync.
- Working-Tree-Status nur aus `git status --short`, nie aus `git diff` ueber Linux-Mount.
- Nicht anfassen: Komodo native Auth (dokumentierte Ausnahme), Grafana/InfluxDB `user: "0"`, Image-Pinning ddns/glances/scrutiny.
- Bei zwei gescheiterten Versuchen: stop, `docs/GITOPS_DRIFT_RUNBOOK.md` Pflichtmatrix, Operator fragen.
## Arbeitsmodus pro Block
Lesen → minimal aendern → `ops/policy-checks/check_repo.ps1` → Commit → Push → Komodo + Smoke-Test → eine Zeile in `docs/MIGRATION_LOG.md`.
## Fertig pro Block
Kurz an Operator: Commit-SHA, Smoke-Test-Beleg, ggf. neuer Watchpoint.
+6 -94
View File
@@ -17,87 +17,6 @@ Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ab
## Historische Meilensteine ## Historische Meilensteine
### 2026-05-31 - Komodo Deploy-Drift strukturell abgesichert
Nach dem Renovate-Block wurde die Ursache fuer den kurzzeitigen `nextcloud-postgres`-Drift nachgezogen: Komodo hatte den `nextcloud`-Stack beim Postgres-17.10-Deploy gestartet, aber `docker compose up -d` scheiterte zunaechst an einem Docker-Recreate-Namenskonflikt (`nextcloud-postgres` Ersatzcontainer). Der Workspace war dadurch bereits auf dem neuen Commit, waehrend der laufende DB-Container noch das alte Image nutzte. Der spaetere gezielte `docker compose up -d` aus dem aktualisierten Workspace hat den Zwischenzustand sauber aufgeloest; es blieben keine exited/dead Containerreste.
- Komodo-Update-Historie bestaetigt: ein fehlgeschlagener `DeployStack` fuer `nextcloud` mit Compose-Up-Konflikt, danach ein erfolgreicher `DeployStack`.
- Aktueller Runtime-Stand nach Pruefung: `nextcloud`, `nextcloud-postgres`, `nextcloud-redis`, `postgresql17`, `mealie-postgres`, `gitea`, `bentopdf`, `ddns-updater` und Komodo-Self-Stack laufen ohne `unhealthy`-Status; die erwarteten Images stimmen mit den Compose-Dateien ueberein.
- `services/posture-check/export-prometheus-textfile.sh` exportiert jetzt `homelab_gitops_runtime_image_match{name,project,service}` fuer laufende Compose-Container. Die Metrik vergleicht das Image aus `docker compose config --format json` gegen `docker inspect .Config.Image` des laufenden Containers und faengt damit genau den Zustand "Workspace/Compose neu, Runtime alt" ab.
- Neue Prometheus-Regel `HomelabGitOpsRuntimeImageDrift`: feuert als Warning, wenn ein laufender Compose-Container laenger als 10 Minuten nicht dem Compose-Image entspricht.
- Smoke: Exporter-Test in `/tmp/kallilab-textfile-test/homelab.prom` lieferte fuer alle erkannten Compose-Container `homelab_gitops_runtime_image_match = 1`; `promtool check rules` meldete `SUCCESS: 17 rules found`.
- Beim Live-Reload zeigte Prometheus nach dem Git-Pull einen `stale file handle` auf die bind-gemountete `alerts.yml`. Fix: nur `monitoring-prometheus` aus dem aktuellen Monitoring-Workspace per `docker compose up -d --force-recreate --no-deps prometheus` neu erstellt. Danach: `promtool check rules` erfolgreich, Lifecycle-Reload erfolgreich, Regel `HomelabGitOpsRuntimeImageDrift` geladen und `inactive`.
### 2026-05-31 - Renovate PRs #1 bis #5 gemerged und deployed
Die ersten fuenf Renovate-PRs wurden einzeln in `master` uebernommen, mit Policy-Check und Live-Smoke nach den Datenhalter-Aenderungen. Major-Branches wurden bewusst nicht gemerged.
- #1 `renovate/mongo-7.0.32`: Mongo-Digest fuer Komodo uebernommen, Merge-Commit `b8b0af9`.
- #2 `renovate/postgres-17.9`: Postgres-17.9-Digest fuer `postgresql17`, `mealie-postgres` und `nextcloud-postgres` uebernommen, Merge-Commit `db1fa7c`.
- #3 `renovate/minor-patch-updates`: gruppierte Minor-/Patch-Updates uebernommen, Merge-Commit `dde4419`. Danach wurden die Komodo-Workspaces `gitea`, `bentopdf` und `ddns-updater` wegen Drift gezielt gesichert, auf `origin/master` synchronisiert und neu deployed. Backups liegen unter `/mnt/user/appdata/komodo/_workspace_backups/*-workspace-before-renovate-pr3-resync-*.tar.gz`.
- #4 `renovate/mongo-7.x`: Komodo-Mongo von `7.0.32` auf `7.0.34` gehoben, Merge-Commit `076676d`. Da der Komodo-Self-Stack nicht ueber einen normalen Git-Webhook redeployed, wurde vorher ein Compose-Backup (`/mnt/user/appdata/komodo/_workspace_backups/komodo-compose-before-renovate-20260531-125102.yaml`) und ein frischer Mongo-Dump (`/mnt/user/backups/borg/dumps/latest/komodo-mongo-pre-renovate-20260531-125102.archive.gz`) erstellt, danach der Self-Stack kontrolliert aktualisiert.
- #5 `renovate/postgres-17.x`: Postgres von `17.9` auf `17.10` fuer `postgresql17`, `mealie-postgres` und `nextcloud-postgres` gehoben, Merge-Commit `96fcacc`. `postgresql17` und `mealie-postgres` wurden durch Komodo recreated; `nextcloud-postgres` musste aus dem bereits aktualisierten Workspace `/mnt/user/services/stacks/nextcloud/apps/nextcloud` einmal gezielt mit `docker compose up -d` nachgezogen werden.
- `ops/policy-checks/check_repo.ps1` blieb nach den Merge-Commits ohne Criticals; einzige Warning ist weiterhin die dokumentierte InfluxDB-root-Ausnahme.
- Smoke-Beleg nach Settle: `postgresql17` healthy auf `postgres:17.10@sha256:0027bef...`, `mealie-postgres` und `nextcloud-postgres` laufen auf demselben `17.10`-Digest, `mealie.kaleschke.info` HTTP `200`, `cloud.kaleschke.info` HTTP `302`, `git.kaleschke.info` HTTP `200`, `komodo.kaleschke.info` HTTP `200`, keine `unhealthy` Container.
- Gitea listete #1 bis #5 nach den lokalen Merge-Commits noch als offen. Jeder PR bekam per API einen Kommentar mit dem manuellen Merge-Commit und wurde geschlossen, damit Renovate keinen offenen Altstand behaelt.
- Watchpoint: Renovate-Branches `mongo-8.x`, `postgres-18.x`, `redis-8.x` und `major-major-updates` bleiben bewusst ungemerged und brauchen separate Operator-Entscheidung.
### 2026-05-31 - Gitea Komodo-Workspace-Drift bereinigt
Der Komodo-Workspace fuer den `gitea`-Stack unter `/mnt/user/services/stacks/gitea` war auf `1d0cba9` stehengeblieben, 70 Commits hinter `origin/master`, mit 23 untracked Pfaden. Dadurch war die von Docker genutzte Compose-Datei nicht identisch mit dem aktuellen Repo-Stand (`core/gitea/docker-compose.yml`), obwohl der Gitea-Komodo-Webhook aktiv war.
- Vor der Bereinigung wurde der komplette Workspace gesichert: `/mnt/user/appdata/komodo/_workspace_backups/gitea-workspace-before-resync-20260531-122515.tar.gz`.
- Die untracked Pfade wurden einzeln gegen `origin/master` geprueft. 18 Eintraege waren byte-identisch, vier Doku-Dateien waren aeltere Zwischenstaende, und `ops/h-drive-nearline/pull-critical-backups.ps1` war ebenfalls identisch. Es gab keinen nur-im-Workspace-gueltigen neueren Arbeitsstand.
- Danach wurden nur die bekannten untracked Konfliktpfade aus dem Workspace entfernt und `git pull --ff-only origin master` ausgefuehrt. Ergebnis: `stacks/gitea` steht sauber auf `e6a0e9f`, `## master...origin/master`, ohne Dirty-State.
- `docker compose up -d` aus `/mnt/user/services/stacks/gitea/core/gitea` lief ohne Recreate-Zwang; der laufende Container nutzt weiterhin den Stack-Workspace als Compose-Quelle und ist `healthy`.
- Smoke-Test: `docker exec gitea wget -qO- http://localhost:3000/api/healthz` liefert `status: pass`, `https://git.kaleschke.info` liefert HTTP `200`, und die lokale Gitea-API fuer `Micha/homelab-infra` antwortet mit `default_branch: master`.
- Watchpoint: Gitea ist ein Henne-Ei-Stack, weil der Dienst sein eigenes Git-Origin bereitstellt. Webhook und `auto_pull` sind aktiv, aber Workspace-Drift muss bei kuenftigen Gitea-Aenderungen besonders bewusst geprueft werden; kein pauschales `git clean -fd` ohne vorherige Sicherung und Vergleich.
### 2026-05-31 - Komodo 5xx-Spam eingegrenzt: LAN-Client statt Stack-Fehler
`HomelabTraefik5xx` feuerte fuer `service="komodo@docker"`, weil wiederkehrende Komodo-UI-API-Requests ohne gueltige Session (`GET /user`, zeitweise `POST /read/GetCoreInfo`) von Traefik als 500 gezaehlt wurden. Komodo Core selbst loggte keine internen Fehler; die 500-Antwort ist ein Komodo-Auth-Pfad-Bug-on-top, aber nicht die primaere Betriebsstoerung.
- Bestaetigt: Blackbox-Exporter erklaert nur `GET /` alle 15s. Waehrend `monitoring-blackbox-exporter` gestoppt war, verschwanden die `/`-200-Probes, `/user`-500 lief aber weiter.
- Ausgeschlossen: `cert-token-check.sh` prueft keine Komodo-Domain; Komodo Periphery war nach 130s Stop nicht die Quelle; Glance war bereits vorab durch Stop-Test ausgeschlossen.
- Core-Isolation: Bei gestopptem `komodo-core` liefen die Client-Requests weiter, aber Traefik loggte sie als 404 ohne `komodo@docker`-Service. Nach Core-Start wurden dieselben Requests wieder zu `komodo@docker`-500. Damit ist die Quelle ein LAN-/Client-Geraet, nicht Komodo Core als Self-Poll.
- Lokale Client-Suche: Auf dem Windows-Operator-PC `192.168.178.103` bestanden HTTPS-Verbindungen zur WAN-IP `217.249.121.39`. Brave war zunaechst plausibel, weil die Brave-Session alte Komodo-Tabs enthielt; ein Brave-Schluss beendete den 5xx-Takt jedoch nicht. Danach blieb als lokaler Kandidat nur `Codex.exe` mit Verbindung zur WAN-IP. Der in-app Browser zeigte keine offene Seite, daher ist der operative Fix: Codex-App/Thread nach Abschluss schliessen bzw. neu starten; falls der Takt danach wider Erwarten weiterlaeuft, naechster Schritt ist LAN-Geraetesuche am Router/Switch statt Repo-Aenderung.
- Kein Repo-/Komodo-Fix umgesetzt: Monitoring-Regel und Komodo-Compose bleiben unveraendert. Ein Alert-Exclude fuer `komodo@docker` waere nur ein letzter Ausweg und wurde nicht gesetzt.
- Smoke-Beleg waehrend der Eingrenzung: `traefik`, `komodo-core`, `komodo-periphery`, `komodo-mongo` und `monitoring-blackbox-exporter` liefen nach den Stop/Start-Tests wieder; `komodo-mongo` und `traefik` waren healthy.
### 2026-05-30 - Komodo-Bootstrap-Trockenlauf Erstlauf (F-09 Rest abgeschlossen)
Skript ist seit 2026-05-29 vorbereitet, heute erster echter Lauf auf dem Host.
- Aufruf: `bash /mnt/user/services/homelab-infra/ops/restore-tests/komodo-bootstrap-test.sh --keep-data`
- Vorlauf: `--what-if` zur Plan-Verifikation, danach echter Lauf, beides ohne Eingriff in den produktiven Komodo-Stack.
- Ergebnis: `SUCCESS`, alle 5 Smoke-Checks gruen.
- `docker compose config valid: ok`
- `Test-Mongo healthy: ok` (Mongo healthy in ~6 s)
- `Mongo authenticated ping (Test-Creds): ok`
- `Komodo Core HTTP status: 200` (Login-Seite ausgeliefert)
- `Test-Periphery container state: running`
- Report: `/mnt/user/backups/restore-reports/komodo-bootstrap-2026-05-30.md`
- Isolation hielt wie geplant: produktive Container `komodo-mongo`, `komodo-core`, `komodo-periphery` unter Project `komodo` blieben unangetastet, ebenso `/mnt/user/appdata/komodo/{mongo,core,periphery}` und die produktiven `KOMODO_*`-Secrets. Test lief unter Project `restoretest-komodo` mit Wegwerf-Datadir `/mnt/user/backups/restore-lab/komodo/`, Wegwerf-Secrets im Test-Compose und Test-Port nur auf `127.0.0.1:19120`.
- Operator-Klick bewusst nicht von Claude uebernommen: `ssh root@kallilabcore` ist eine Aktionsklasse, die in CLAUDE.md ausdruecklich Operator-Anweisung verlangt. Der Auto-Mode-Classifier hat einen nicht-destruktiven SSH-Probe entsprechend blockiert. Der Operator hat den Befehl im Unraid-Webterminal selbst gestartet.
- Bedeutung: `ops/komodo/docker-compose.yml` ist als Recovery-Anker fuer die Bootstrap-Stufen A-F in `docs/SERVICES_RECOVERY.md` jetzt **belegt** tauglich, nicht mehr nur angenommen tauglich. Image-Digests (mongo:7.0.32, komodo-core:2, komodo-periphery:2) und Mongo-Auth-Schema sind verifiziert.
- Lab-Daten unter `/mnt/user/backups/restore-lab/komodo/` bleiben mit `--keep-data` erhalten, Test-Container wurden im EXIT-Trap sauber abgeraeumt. Operator entscheidet, ob das Lab-Verzeichnis (~300 MB) entfernt wird.
Folgeschritt fuer `docs/RESTORE_DRILL_ROUTINE.md`: Komodo-Bootstrap-Trockenlauf passt zum quartalsweisen DR-Sanity-Check (Q4) oder als wiederholbarer Standalone-Drill. Aktuell kein Host-Schedule, Aufruf bleibt manuell.
### 2026-05-30 - F-10: Authelia Repo<->Host Drift-Check
Der dokumentierte "by-design"-Drift zwischen `security/authelia/configuration.yml` (Repo-Baseline) und `/mnt/user/appdata/authelia/config/configuration.yml` (Host) wird jetzt automatisch ueberwacht. Vorher: Manueller Merge auf den Host war Pflicht, aber keine Pruefung. Eine vergessene ACL-Synchronisation waere erst bei einem Login-Fehler aufgefallen.
- Neues Skript `services/authelia-diff.sh`: extrahiert die `access_control:`-Sektion aus beiden YAMLs per awk-Block-Extractor (Top-Level-Key bis zum naechsten Top-Level-Key), normalisiert Kommentar- und Leerzeilen, vergleicht via `diff -u`. Default-Sektion ist `access_control`, weil das laut F-10 der primaere Drift-Vektor ist; per env `AUTHELIA_DIFF_SECTIONS` koennen weitere Top-Level-Sektionen (`session`, `regulation`, `totp`, ...) ergaenzt werden. OIDC-Clients, Identity-Provider und Secret-Werte bleiben bewusst aussen vor.
- Exit-Code-Schema: 0 = ok, 1 = Drift (Diff auf stdout), 2 = Datei fehlt, 3 = Sektion fehlt, 4 = Werkzeug fehlt. Macht das Skript auch standalone nutzbar (`ssh kallilab "bash /mnt/user/services/homelab-infra/services/authelia-diff.sh"`).
- `services/posture-check/posture-check.sh` ruft das Skript am Ende des Checks-Blocks auf (`check_authelia_config_drift`). Drift wird als **Warning** gemeldet, nicht Critical, weil die produktive Authelia trotz Drift weiter laeuft und die ACL fuer schon angemeldete Sessions weiter wirkt. Skip-Mechanismus: `SKIP_AUTHELIA_DRIFT=1`. Pfad-Override: `AUTHELIA_DIFF_SCRIPT`.
- Pflicht-Setup auf dem Host: Repo-Spiegel unter `/mnt/user/services/homelab-infra/` als read-only-Clone von Gitea `Micha/homelab-infra` mit regelmaessigem `git pull --ff-only`. Default-Pfade des Skripts setzen das voraus. Ohne Repo-Spiegel meldet der Check Warning, weil die Baseline-Datei fehlt - keine stille Inaktivierung.
- Lokaler Smoke-Test 2026-05-30 erfolgreich: identische Files -> rc=0; ACL-Drift im Domain-Eintrag `scrutiny.kaleschke.info -> scrutiny-renamed.kaleschke.info` -> rc=1 mit unified diff, ACL-Block korrekt extrahiert, Kommentar- und Leerzeilen rausgefiltert. False-Positive auf `session.default_redirection_url`-Aenderung korrekt vermieden (gehoert nicht zu `access_control`).
- `docs/WORKFLOW.md` hat jetzt eine eigene Sektion "Ausnahme: Authelia configuration.yml" analog zur Traefik-Dynamic-Sektion. Pflicht-Workflow: 1. Repo-Aenderung + Commit + Push, 2. manueller Merge in die Host-Datei mit Erhalt der OIDC-Sektionen, 3. `docker restart authelia` + Login-Smoke-Test, 4. `services/authelia-diff.sh` muss `exit 0` liefern.
- `docs/REPO_MAP.md` und `docs/SERVICE_CATALOG.md` zeigen das Skript und den neuen Posture-Check-Eintrag.
Operator-Folgeschritt (klein, nicht heute): Repo-Spiegel `/mnt/user/services/homelab-infra/` auf dem Host einrichten und in den vorhandenen `gitea-bundle-mirror-6h`-Plan oder einen eigenen 6h-Cron einbinden, damit das Skript einen aktuellen Vergleichsstand findet.
### 2026-05-29 - Stack-Hygiene Sprint: Healthchecks, Monitoring-Digests, Komodo-Bootstrap-Skript, Renovate-Vorbereitung ### 2026-05-29 - Stack-Hygiene Sprint: Healthchecks, Monitoring-Digests, Komodo-Bootstrap-Skript, Renovate-Vorbereitung
Vier Audit-Punkte am Stueck abgearbeitet. Pro Block: Live-Verifikation am Host, Doku im Repo. Vier Audit-Punkte am Stueck abgearbeitet. Pro Block: Live-Verifikation am Host, Doku im Repo.
@@ -123,20 +42,13 @@ Vier Audit-Punkte am Stueck abgearbeitet. Pro Block: Live-Verifikation am Host,
- Smoke-Test-Kriterien: docker compose config valid, Mongo healthy, Mongo Auth-Ping ok, Core HTTP 200/302/303/401, Periphery container `running`. - Smoke-Test-Kriterien: docker compose config valid, Mongo healthy, Mongo Auth-Ping ok, Core HTTP 200/302/303/401, Periphery container `running`.
- Erster Lauf bleibt manueller Operator-Schritt. - Erster Lauf bleibt manueller Operator-Schritt.
**F-12 Renovate-Bot (live)** **F-12 Renovate-Bot (vorbereitet)**
- Repo-Config in `renovate.json` (Repo-Root): nur extends, packageRules, ignorePaths, manager file patterns, labels, rangeStrategy. Bot-Config separat in `ops/renovate/bot-config.js`: platform, endpoint, autodiscover=false, repositories=["Micha/homelab-infra"], gitAuthor, Concurrent-Limits. Trennung war noetig: Renovate liest die `renovate.json` im Repo als REPO-Config; Bot-Felder darin wurden als "this repo is disabled" fehlinterpretiert (Repository result: forbidden, status: disabled). - `renovate.json` im Repo-Root mit Homelab-tauglichen Group-Rules: Major-Updates getrennt, Minor/Patch/Digest fuer Docker-Compose und Dockerfile gruppiert, Tier-1-Datenhalter (Postgres, Mongo, Redis, pgvecto-rs) einzeln ohne Group, Komodo-Major-Updates explizit deaktiviert.
- `ops/renovate/run-renovate.sh` als One-Shot-Container-Wrapper. Wichtige Haertungen waehrend des Setups: - `ops/renovate/run-renovate.sh` als One-Shot-Container-Wrapper: liest Gitea-PAT aus Host-Secret-Datei, startet `renovate/renovate:41` einmalig, schreibt Log unter `/mnt/user/services/renovate/logs/`.
- `--add-host git.kaleschke.info:192.168.178.58`: Renovate-Container kann den Hostname sonst nicht aufloesen (`EAI_AGAIN`). Analog zur `extra_hosts`-Loesung in der Komodo-Compose. - `docs/RENOVATE.md` mit kompletter Operator-Setup-Anleitung (5 Schritte): Gitea-Service-Account `renovate`, Access-Token, Token-Datei, Erstlauf, User-Script `renovate-six-hourly` (`20 */6 * * *`).
- `--env-file` statt `-e RENOVATE_TOKEN=...`: Token war sonst in `ps` und `docker inspect` sichtbar. - Bewusst KEIN Auto-Merge: jede PR braucht Operator-Sichtpruefung.
- `chmod 0777` auf `/mnt/user/services/renovate/state`: Renovate-Image laeuft als uid 12021 (ubuntu), kann root-owned Mount sonst nicht beschreiben. - Setup-Schritte (Gitea-User, PAT, Token-Datei, User-Script-Aktivierung) bleiben Operator-Aufgabe; Repo-seitig alles vorbereitet.
- Live-Setup am Host:
- Service-Account `renovate` (uid 2, **kein Admin**) ueber `gitea admin user create` angelegt.
- Collaborator-Status mit Write-Permission auf `homelab-infra` (initialer DB-Insert hat den Gitea-Permissions-Cache nicht aktualisiert; Renovate sah `permissions.push=false` und brach mit "Repository does not permit pull or push" ab; saubere Loesung war Operator-UI-Klick "Entfernen + neu hinzufuegen", was den Cache konsistent aktualisiert; Befund-Bestaetigung via Doku-Studium `lib/modules/platform/gitea/index.ts`: die Push-Check ist hardcoded, kein Bypass moeglich).
- Personal-Access-Token mit Scopes `read:user,write:repository,write:issue`, in `/mnt/user/appdata/secrets/renovate_token.txt` (chmod 600). Token wurde einmal rotiert, weil der Wert beim ersten Erzeugen im SSH-Output sichtbar war.
- User-Script `renovate-six-hourly` mit Cron `20 */6 * * *` live in `/etc/cron.d/root`.
- Erstlauf 2026-05-29 erfolgreich: 5 PRs (mongo digest, mongo 7.0.32->7.0.34, postgres digest, postgres 17.9->17.10, minor-and-patch-updates gruppiert), 1 Issue "Renovate Dependency Dashboard", 8 Branches (drei Major-Branches warten auf naechsten Lauf wegen prConcurrentLimit=5). Komodo-Major-Updates wurden korrekt durch packageRule unterdrueckt.
- `docs/RENOVATE.md` zeigt die ursprueglichen 5 Operator-Schritte fuer Neuaufsetzen bzw. Disaster Recovery.
### 2026-05-29 - Borg-Source `/local/appdata/homepage` verspaetet entfernt + Removal-Checkliste in WORKFLOW ### 2026-05-29 - Borg-Source `/local/appdata/homepage` verspaetet entfernt + Removal-Checkliste in WORKFLOW
+1 -3
View File
@@ -27,9 +27,7 @@ Bewusst kein Auto-Merge: jede PR braucht eine Operator-Sichtpruefung und einen M
- Username: `renovate` - Username: `renovate`
- E-Mail: ein gueltiges Postfach (Renovate sendet keine Mails, aber Gitea braucht eine Adresse) - E-Mail: ein gueltiges Postfach (Renovate sendet keine Mails, aber Gitea braucht eine Adresse)
- Passwort: zufaellig, in Vaultwarden speichern - Passwort: zufaellig, in Vaultwarden speichern
3. Diesem User Schreibrechte fuer das Repo geben, das Renovate scannen soll: Repo `homelab-infra` -> Einstellungen -> Mitarbeiter -> `renovate` mit Permission `Schreibrechte` hinzufuegen. 3. Diesem User Schreibrechte fuer die Repos geben, die Renovate scannen soll. Einfachster Weg: dem User direkt Maintainer-Recht in jedem Repo unter `Micha/` geben (Settings -> Collaborators -> Add Collaborator -> `renovate` -> Permission `Write`).
**Wichtig:** Den Collaborator immer ueber die Gitea-UI/API hinzufuegen, nicht ueber direkten SQL-Insert. Die UI/API loest einen Permissions-Cache-Refresh aus; ein DB-Insert tut das nicht und fuehrt dazu, dass Renovate spaeter "Repository does not permit pull or push" meldet, obwohl die DB den Write-Mode kennt (Befund am 2026-05-29).
### Schritt 2 - Access-Token erzeugen ### Schritt 2 - Access-Token erzeugen
+1 -8
View File
@@ -45,10 +45,6 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
| `docs/FRITZBOX_PORT_CORRECTION_PLAN.md` | Vorbereitungs-Doku fuer FRITZ!Box-Portfreigaben-Korrektur (kein Router-Eingriff) | | `docs/FRITZBOX_PORT_CORRECTION_PLAN.md` | Vorbereitungs-Doku fuer FRITZ!Box-Portfreigaben-Korrektur (kein Router-Eingriff) |
| `docs/OFFSITE_BACKUP_OPTIONS.md` | Entscheidungsvorlage zweites Offsite-Backup-Ziel (rsync.net vs. BorgBase EU2 vs. Cold-Platte) | | `docs/OFFSITE_BACKUP_OPTIONS.md` | Entscheidungsvorlage zweites Offsite-Backup-Ziel (rsync.net vs. BorgBase EU2 vs. Cold-Platte) |
| `docs/AUDIT_2026-05-25_TODO.md` | Operative Arbeitsliste aus dem Audit vom 2026-05-25; Authelia-2FA bewusst geparkt | | `docs/AUDIT_2026-05-25_TODO.md` | Operative Arbeitsliste aus dem Audit vom 2026-05-25; Authelia-2FA bewusst geparkt |
| `docs/STRATEGISCHE_BEWERTUNG_2026-05-23.md` | Historische Baseline-Bewertung; hat den Audit-Zyklus 2026-05-25 angestossen |
| `docs/CODEX_KONSOLIDIERUNG_2026-05-23.md` | Codex-Erstprompt zum Audit-Zyklus, abgearbeitet; Vorlage fuer kuenftige Sweeps |
| `docs/CODEX_JELLYFIN_REMOVAL_2026-05-23.md` | Codex-Removal-Pattern, Jellyfin-Removal 2026-05-25 ausgefuehrt; Vorlage fuer kuenftige Stack-Removals |
| `ops/policy-checks/mem-limits-baseline.md` | F-19 Vorbereitungs-Plan fuer Container-Mem-Limits; bewusst nicht vor 7 Tagen Peak-Beobachtung |
| `docs/ALERTING_MAP.md` | ntfy Topic-Konvention und Sender-Mapping fuer Homelab-Alerts | | `docs/ALERTING_MAP.md` | ntfy Topic-Konvention und Sender-Mapping fuer Homelab-Alerts |
| `docs/ROLLBACK.md` | Rueckweg bei Fehlern im GitOps-Betrieb | | `docs/ROLLBACK.md` | Rueckweg bei Fehlern im GitOps-Betrieb |
| `docs/SECRETS_MAP.md` | Secret-Namen, Pfade und Einbindungsarten ohne Werte | | `docs/SECRETS_MAP.md` | Secret-Namen, Pfade und Einbindungsarten ohne Werte |
@@ -70,11 +66,9 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
| `monitoring/grafana/provisioning/*` | Grafana Datasource-/Dashboard-Provisioning fuer Prometheus und Loki | | `monitoring/grafana/provisioning/*` | Grafana Datasource-/Dashboard-Provisioning fuer Prometheus und Loki |
| `ops/glance/config/glance.yml` | Glance Dashboard-Konfiguration fuer Homelab-Monitore, Internet-/DNS-/VPN-Widgets, Community-Widgets, Docker-Containergruppen, Zeitfortschritt, Host-Snapshot, Bookmarks und zweite Infrastruktur-Seite | | `ops/glance/config/glance.yml` | Glance Dashboard-Konfiguration fuer Homelab-Monitore, Internet-/DNS-/VPN-Widgets, Community-Widgets, Docker-Containergruppen, Zeitfortschritt, Host-Snapshot, Bookmarks und zweite Infrastruktur-Seite |
| `ops/borg-ui/scripts/pre-backup-dumps.sh` | Host-seitiges Dump-Skript fuer PostgreSQL, SQLite-Container-Dumps und Komodo Mongo | | `ops/borg-ui/scripts/pre-backup-dumps.sh` | Host-seitiges Dump-Skript fuer PostgreSQL, SQLite-Container-Dumps und Komodo Mongo |
| `services/posture-check/posture-check.sh` | Host-seitiger Posture-Check fuer Filesystem, Mover-Drift, NVMe-SMART, Fuellstand, Authelia-Repo<->Host-Drift und ntfy-Alarmierung | | `services/posture-check/posture-check.sh` | Host-seitiger Posture-Check fuer Filesystem, Mover-Drift, NVMe-SMART, Fuellstand und ntfy-Alarmierung |
| `services/posture-check/export-prometheus-textfile.sh` | Host-seitiger Textfile-Exporter fuer Borg-, Critical-Container- und GitOps-Runtime-Image-Drift-Metriken |
| `services/posture-check/docker-critical-events.sh` | Host-seitiger Docker-Event-Watcher fuer kritische ntfy-Alarme | | `services/posture-check/docker-critical-events.sh` | Host-seitiger Docker-Event-Watcher fuer kritische ntfy-Alarme |
| `services/posture-check/posture_check.sh` | Kompatibilitaets-Wrapper fuer die historische Schreibweise aus `STORAGE_LAYOUT.draft.md` | | `services/posture-check/posture_check.sh` | Kompatibilitaets-Wrapper fuer die historische Schreibweise aus `STORAGE_LAYOUT.draft.md` |
| `services/authelia-diff.sh` | Vergleicht `access_control:`-Sektion zwischen Repo-Baseline und Host-Datei; wird vom Posture-Check als Check `authelia_config_drift` aufgerufen |
| `ops/hermes-agent/config/hermes/config.yaml` | Hermes Agent Konfiguration mit Env-Platzhaltern | | `ops/hermes-agent/config/hermes/config.yaml` | Hermes Agent Konfiguration mit Env-Platzhaltern |
| `ops/hermes-agent/hermes.env.example` | Beispiel fuer Hermes `.env`; echte Datei liegt auf Host-Appdata | | `ops/hermes-agent/hermes.env.example` | Beispiel fuer Hermes `.env`; echte Datei liegt auf Host-Appdata |
| `ops/hermes-agent/stack.env.example` | Beispiel fuer Hermes Stack-ENV; echte `stack.env` bleibt host-/komodoseitig und ist per `.gitignore` ausgeschlossen | | `ops/hermes-agent/stack.env.example` | Beispiel fuer Hermes Stack-ENV; echte `stack.env` bleibt host-/komodoseitig und ist per `.gitignore` ausgeschlossen |
@@ -238,7 +232,6 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
|---|---|---| |---|---|---|
| `ops/borg-ui/scripts/pre-backup-dumps.sh` | Unraid Host, nicht Borg-UI Inline-Hook | erzeugt aktuelle Dumps unter `/mnt/user/backups/borg/dumps/latest` | | `ops/borg-ui/scripts/pre-backup-dumps.sh` | Unraid Host, nicht Borg-UI Inline-Hook | erzeugt aktuelle Dumps unter `/mnt/user/backups/borg/dumps/latest` |
| `services/posture-check/posture-check.sh` | Unraid Host | schreibt `/mnt/user/services/posture-check/last.json` und alarmiert via ntfy bei Warning/Critical | | `services/posture-check/posture-check.sh` | Unraid Host | schreibt `/mnt/user/services/posture-check/last.json` und alarmiert via ntfy bei Warning/Critical |
| `services/posture-check/export-prometheus-textfile.sh` | Unraid Host, Cron/Textfile-Collector | schreibt Borg-, Critical-Container- und GitOps-Runtime-Image-Drift-Metriken fuer Prometheus |
| `services/posture-check/docker-critical-events.sh` | Unraid Host | beobachtet Docker `die`/`oom`/`kill` Events und alarmiert via `homelab-alerts` | | `services/posture-check/docker-critical-events.sh` | Unraid Host | beobachtet Docker `die`/`oom`/`kill` Events und alarmiert via `homelab-alerts` |
Das Skript liest Secret-Dateien auf dem Host und schreibt Dump-Artefakte. Bei Analyse niemals Secret-Inhalte ausgeben. Das Skript liest Secret-Dateien auf dem Host und schreibt Dump-Artefakte. Bei Analyse niemals Secret-Inhalte ausgeben.
+1 -4
View File
@@ -47,10 +47,7 @@ Diese Liste ist bewusst auf Tier-2 und Tier-1-Dienste fokussiert. Tier-3-Dienste
### Q2 2026 - Konkrete Belegung ### Q2 2026 - Konkrete Belegung
- Mini-Restore: **Immich (erledigt 2026-05-27)**. - Mini-Restore: **Immich (erledigt 2026-05-27)**.
- DR-Sanity-Check (teilweise erledigt, Rest vor Quartalsende 2026-06-30): - DR-Sanity-Check (offen, vor Quartalsende 2026-06-30): Komodo-Bootstrap-Pfad gegen `docs/SERVICES_RECOVERY.md` durchgehen, Gitea-Bundles ueber `ops/borg-ui/scripts/gitea-bundle-mirror.sh` auf Frische und Bundle-Klonbarkeit pruefen, Secrets-Inventur gegen `docs/SECRETS_MAP.md` abgleichen.
- Komodo-Bootstrap-Pfad: **erledigt 2026-05-30** durch echten Trockenlauf via `ops/restore-tests/komodo-bootstrap-test.sh --keep-data`, Report `/mnt/user/backups/restore-reports/komodo-bootstrap-2026-05-30.md`, `ops/komodo/docker-compose.yml` als Recovery-Anker belegt.
- Gitea-Bundles ueber `ops/borg-ui/scripts/gitea-bundle-mirror.sh` auf Frische und Bundle-Klonbarkeit pruefen: offen.
- Secrets-Inventur gegen `docs/SECRETS_MAP.md` abgleichen: offen.
### Wer schiebt das an? ### Wer schiebt das an?
+11 -20
View File
@@ -138,28 +138,19 @@ Erfolgskriterium: Komodo-UI laedt, Periphery `Online`, mindestens ein Stack aus
Erst nach erfolgreichem Komodo-Bootstrap werden produktive Stacks ueber den dokumentierten Stufenpfad in `docs/DISASTER_RECOVERY.md` Phase 4 hochgefahren (Traefik, AdGuard, Tailscale, dann PostgreSQL, Authelia, Redis, Gitea, dann Apps). Erst nach erfolgreichem Komodo-Bootstrap werden produktive Stacks ueber den dokumentierten Stufenpfad in `docs/DISASTER_RECOVERY.md` Phase 4 hochgefahren (Traefik, AdGuard, Tailscale, dann PostgreSQL, Authelia, Redis, Gitea, dann Apps).
### Trockenlauf (als Repo-Skript, bestaetigt) ### Trockenlauf-Idee (Doku-only, nicht ausgefuehrt)
Trockenlauf gegen Wegwerf-Pfade ist seit 2026-05-29 als Repo-Skript abgelegt: `ops/restore-tests/komodo-bootstrap-{compose.test.yml,test.sh,plan.md,runbook.md}`. Aufruf: Ein bewusster Trockenlauf des Komodo-Bootstraps gegen Wegwerf-Pfade ist die naechste sinnvolle Reife-Stufe. Vorschlag:
```bash | Schritt | Inhalt |
bash /mnt/user/services/homelab-infra/ops/restore-tests/komodo-bootstrap-test.sh --what-if # nur Plan
bash /mnt/user/services/homelab-infra/ops/restore-tests/komodo-bootstrap-test.sh --keep-data # echter Lauf
```
Erstlauf 2026-05-30 erfolgreich: `SUCCESS`, alle 5 Checks gruen (compose config valid, Mongo healthy, Mongo authenticated ping ok, Komodo Core HTTP `200`, Test-Periphery `running`). Report unter `/mnt/user/backups/restore-reports/komodo-bootstrap-2026-05-30.md`. Produktive Komodo-Container, Mongo-Datadir und Secrets wurden nicht beruehrt.
Test-Isolation:
| Bereich | Wegwerf-Wert |
|---|---| |---|---|
| Compose-Project | `restoretest-komodo` (isoliert von Produktions-Project `komodo`) | | 1 | Test-Compose aus `ops/komodo/docker-compose.yml` in `/mnt/user/backups/restore-lab/komodo/` kopieren |
| Test-Mongo-Datadir | `/mnt/user/backups/restore-lab/komodo/mongo` | | 2 | Test-`.env` mit Wegwerf-Secrets erzeugen (nicht produktive Werte!) |
| Test-Port | `127.0.0.1:19120` (kein LAN, kein Traefik) | | 3 | `docker compose -f .../restore-lab/komodo/docker-compose.yml -p restoretest-komodo up -d` |
| Test-Periphery | ohne `docker.sock`-Mount, ohne `/mnt/user/services`-Mount | | 4 | Smoke: Mongo healthy, Core antwortet auf `http://127.0.0.1:<test-port>/api/health`, Periphery verbindet |
| `KOMODO_*`-Secrets | Wegwerf-Werte im Test-Compose, niemals produktive Werte | | 5 | `docker compose -p restoretest-komodo down -v` und Restore-Lab bereinigen |
Damit ist `ops/komodo/docker-compose.yml` als Recovery-Anker fuer Stufen A-F **belegt** tauglich, nicht nur angenommen tauglich. Der Trockenlauf ist **noch nicht** als Repo-Skript abgelegt. Er bleibt als Folgeschritt analog zum Immich-Restore-Test geplant.
### Validierungs-Kommandos (Snapshot) ### Validierungs-Kommandos (Snapshot)
@@ -206,6 +197,6 @@ Authoritativ ist `docs/SECRETS_MAP.md`. Fuer den Kaltstart ist diese Reihenfolge
| erledigt (Skript + Host-Test) | Gitea-Bundle- oder Mirror-Mechanik final entscheiden | | erledigt (Skript + Host-Test) | Gitea-Bundle- oder Mirror-Mechanik final entscheiden |
| erledigt | Komodo-Bootstrap-Quelle finalisieren | | erledigt | Komodo-Bootstrap-Quelle finalisieren |
| erledigt (Doku) | Komodo-Kaltstart in linearen Stufen A-F dokumentieren | | erledigt (Doku) | Komodo-Kaltstart in linearen Stufen A-F dokumentieren |
| erledigt 2026-05-29 | Komodo-Trockenlauf-Skript in `ops/restore-tests/` analog zu Immich vorbereiten | | offen | Komodo-Trockenlauf-Skript in `ops/restore-tests/` analog zu Immich vorbereiten |
| erledigt 2026-05-30 | Restore-Kommandos nach erstem Trockenlauf mit echten Pfaden ergaenzen | | offen | Restore-Kommandos nach erstem Trockenlauf mit echten Pfaden ergaenzen |
| erledigt | Services-Recovery in `docs/DISASTER_RECOVERY.md` verlinken | | erledigt | Services-Recovery in `docs/DISASTER_RECOVERY.md` verlinken |
+1 -1
View File
@@ -82,7 +82,7 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs | | Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|---|---|---|---|---|---|---|---|---| |---|---|---|---|---|---|---|---|---|
| `posture-check` | Host-Posture-Audit fuer Filesystem, Mover-Drift, NVMe-SMART, Fuellstand und Authelia-Repo<->Host-Drift | `services/posture-check/posture-check.sh` | Unraid User-Script / Cron / Borg Pre-Hook | `findmnt`, `df`, `nvme`, optional `curl` fuer ntfy; ruft `services/authelia-diff.sh` fuer `authelia_config_drift` auf | `/mnt/user/services/posture-check/last.json` | Repo-Skript + letzter JSON-Status | nein | Muss auf dem Unraid-Host bei Boot, stuendlich und vor Borg laufen; Disk1-NTFS ist nach Disk1 Phase 2 nicht mehr erlaubt (`ALLOW_DISK1_NTFS=0` Standard); Warning/Critical alarmieren via ntfy nur bei neuer Ursache oder nach `ALERT_REPEAT_SECONDS`. Authelia-Drift-Check braucht einen Repo-Spiegel unter `/mnt/user/services/homelab-infra/` (siehe `docs/WORKFLOW.md` Sektion "Ausnahme: Authelia configuration.yml") | | `posture-check` | Host-Posture-Audit fuer Filesystem, Mover-Drift, NVMe-SMART und Fuellstand | `services/posture-check/posture-check.sh` | Unraid User-Script / Cron / Borg Pre-Hook | `findmnt`, `df`, `nvme`, optional `curl` fuer ntfy | `/mnt/user/services/posture-check/last.json` | Repo-Skript + letzter JSON-Status | nein | Muss auf dem Unraid-Host bei Boot, stuendlich und vor Borg laufen; Disk1-NTFS ist nach Disk1 Phase 2 nicht mehr erlaubt (`ALLOW_DISK1_NTFS=0` Standard); Warning/Critical alarmieren via ntfy nur bei neuer Ursache oder nach `ALERT_REPEAT_SECONDS` |
| `docker-critical-events` | Live-Alarmierung fuer Docker `die`/`oom`/`kill` Events | `services/posture-check/docker-critical-events.sh` | Unraid User-Script / Hintergrundprozess | Docker CLI, ntfy | `/mnt/user/services/posture-check/docker-critical-events-last.log` | Repo-Skript + letzter Event-Log | nein | Optional als Unraid User-Script `at array start` starten; sendet nach `homelab-alerts` | | `docker-critical-events` | Live-Alarmierung fuer Docker `die`/`oom`/`kill` Events | `services/posture-check/docker-critical-events.sh` | Unraid User-Script / Hintergrundprozess | Docker CLI, ntfy | `/mnt/user/services/posture-check/docker-critical-events-last.log` | Repo-Skript + letzter Event-Log | nein | Optional als Unraid User-Script `at array start` starten; sendet nach `homelab-alerts` |
## Backup- und Restore-Hinweise ## Backup- und Restore-Hinweise
-522
View File
@@ -1,522 +0,0 @@
# Strategische Bewertung KalliLab CORE
> **Status (Stand 2026-05-30): Historischer Snapshot vom 2026-05-23, inhaltlich grossteils ueberholt.**
>
> Dieses Dokument bleibt im Repo als Audit-Anker und als "wo standen wir am 2026-05-23". Die konkreten Befunde, Top-5-Listen und Mehrwert-Fahrplaene sind durch den Audit-Zyklus 2026-05-25 zu einem grossen Teil **abgearbeitet, bewusst nicht umgesetzt oder explizit geparkt**.
>
> - **Nicht als TODO-Liste lesen.** Aktuelle Arbeitsliste: `docs/AUDIT_2026-05-25_TODO.md`.
> - **Originaltext nicht aendern.** Statt Inline-Annotationen steht der pro-Punkt-Status in einer Tabelle am Ende des Dokuments (Abschnitt "Status-Anhang 2026-05-30").
> - **Schulnote 2- gilt nicht mehr.** Mit den Konsolidierungen seit 2026-05-25 sind die meisten Notenabzieher behoben; eine neue Note wuerde hier eher bei 1- bis 2 landen, ist aber kein Selbstzweck.
Stand: 2026-05-23
Bewertet von: externer Blick auf den Repo-Sollzustand
> Diese Bewertung ist bewusst kein Sicherheits- oder Konfigurations-Audit, sondern eine ganzheitliche Einordnung: was das Setup heute leistet, wo es stark ist, wo es zu komplex ist, und wo der nächste echte Mehrwert liegt.
---
## Vorbemerkung und Methode
Bewertet wurde der Repo-Stand auf `master`, nicht der Live-Zustand auf dem Host. Grundlage waren `HOMELAB_ARCHITECTURE_MASTER_V2.md`, `docs/WORKFLOW.md`, `docs/SERVICE_CATALOG.md`, `docs/REPO_MAP.md`, `docs/DISASTER_RECOVERY.md`, `docs/RESTORE_MATRIX.md`, `docs/SECRETS_MAP.md`, `docs/AI_CONTEXT.md`, `docs/GITOPS_DRIFT_RUNBOOK.md`, `docs/HOME_ASSISTANT_INFLUXDB_ECOWITT.md`, `docs/ALERTING_MAP.md`, `ops/borg-ui/BACKUP_SCOPE.md`, `ops/policy-checks/last-report.md`, `ops/restore-tests/schedule.md`, repräsentative Compose-Dateien (Paperless, Monitoring) sowie die Memory-Notiz zum Post-Restore-Sprint.
Ehrliche Einschätzung in einem Satz: Das ist kein Bastel-Homelab mehr. Das ist eine kleine private Plattform mit dokumentationsbasierter Disziplin, die ein paar Lasten mitschleppt, die man jetzt bewusst loswerden sollte, bevor noch mehr dazukommt.
---
## 1. Architektur und Grundidee
Der Aufbau ist nicht gewachsen, er ist gestaltet. Das sieht man sofort an drei Stellen:
Erstens trennt das Repo Verantwortlichkeiten konsequent über die Top-Level-Ordner `core/`, `security/`, `infra/`, `apps/`, `ops/`, `host-services/`, `monitoring/`, `traefik/` und `services/`. Das ist keine willkürliche Kosmetik, das spiegelt die Tier-Hierarchie aus DR und Restore wider. Ein neuer Dienst weiß durch das Einordnungsschema in `HOMELAB_ARCHITECTURE_MASTER_V2.md` Abschnitt 6 sofort, wo er hin gehört und in welche Netze er kommt. Das ist überdurchschnittlich.
Zweitens ist das Netzmodell schlicht und gleichzeitig diszipliniert: `frontend_net` für alles mit Web-UI oder Internetbedarf, `backend_net` `internal: true` für DB/Redis, und app-interne Netze (`mealie_internal`, `immich_default`, `nextcloud_internal`, `monitoring_net`) für Stack-Isolation. Es gibt keine Kunstnetze wie `admin_net` oder `media_net` aus reiner Symmetriesucht. Das ist genau die Linie, die viele Homelabs verfehlen, weil sie entweder alles in ein Bridge-Netz werfen oder fünfzehn semantische Netze erfinden, ohne dass sie was tun.
Drittens ist die Source-of-Truth-Hierarchie explizit (Gitea Online → lokaler Clone → Komodo → Docker → Host) und es gibt ein Drift-Runbook (`docs/GITOPS_DRIFT_RUNBOOK.md`) mit echter Messreihenfolge. Das schlägt 95% der "Selfhoster mit Portainer und Glück".
**Wo es trotzdem hakt:** Die Trennung Monitoring/Spielerei ist noch nicht sauber. Es gibt `ops/grafana-influxdb` (Altstand), `ops/loki` (Altstand) und `monitoring/` (Zielstack) gleichzeitig im Repo. Solange die Migration nicht abgeschlossen und die Altstände nicht entfernt sind, ist das echte Doppelpflege-Risiko — und genau so entstehen die Bugs, die man nachher zwei Wochen sucht. Die Doku sagt klar "nicht parallel betreiben", aber das Repo macht es trotzdem möglich. Das ist eine offene Baustelle, kein Architekturproblem.
**Note für diesen Block: 1-2.**
---
## 2. Nutzen und Mehrwert
Hier wird es ehrlicher: Das Setup hat sehr viel Substanz, aber auch klare Spielereien.
Echter Alltagsnutzen, der den Aufwand rechtfertigt:
- **Vaultwarden** als Passwort-Tresor — der Klassiker, der wirklich täglich genutzt wird.
- **Paperless-ngx** mit Scan-Inbox, Barcode/ASN-Workflow und Tika aus — das ist klassische Familien-Dokumentenverwaltung mit echtem Wert, sobald man Briefe digitalisiert. Die Barcode-Konfiguration (`PAPERLESS_CONSUMER_BARCODE_DPI=600`, `PAPERLESS_CONSUMER_ENABLE_ASN_BARCODE=1`) zeigt, dass der Workflow durchdacht ist.
- **Immich** für Fotos, mit `family_archive`-Mount — das ersetzt sinnvoll Google Photos für eine Familie.
- **Nextcloud** für Dateifreigabe und WebDAV/CardDAV — wenn das wirklich genutzt wird, ist es ein echtes Google-Drive/iCloud-Replacement.
- **Mealie** für Rezepte — nett, aber das ist ein Lebensmittel-Ding, nicht Infrastruktur.
- **Mail-Archiver** — sehr persönlicher Mehrwert, wenn IMAP-Archive aus GMX/Gmail langfristig durchsuchbar bleiben sollen.
- **ntfy** als Push-Backbone für `homelab-alerts` und `homelab-info` — operativ unverzichtbar.
- **Gitea** — primär als GitOps-Quelle. Für andere Projekte ein Bonus.
- **AdGuard Home + Unbound** — ja, das hat Alltagsnutzen für alle Geräte im LAN.
- **Tailscale** — Remote-Zugang ohne Port-Freigabe nach außen, klar wertvoll.
Spielerei oder Overengineering, das ehrlich auf den Prüfstand gehört:
- **Plex zusätzlich zu Jellyfin**: Beide sind Medienserver mit derselben Bibliothek (`/mnt/user/media`, `/mnt/user/photos`). Plex bringt zwar Remote-Streaming und bessere Clients, Jellyfin bringt native Open-Source-Auth und keine Lizenz. Einen davon kann man weglassen — die ehrlichste Antwort ist, das nach 30 Tagen Nutzungs-Tracking zu entscheiden.
- **Glance + Homepage + Komodo-UI als drei parallele Dashboards**: Homepage als Startseite, Glance als zweites Dashboard mit Widgets, Komodo als Stack-Sicht. Hier ist mindestens eines redundant. Glance ist erst seit kurzem live und wirkt eher wie "weil cool" als "weil nötig".
- **paperless-gpt**: Coole Idee (LLM für Tagging), aber wenn das nicht aktiv genutzt wird, ist es nur ein Container, der idle Ressourcen frisst. Frage an dich: Wann hast du das letzte Mal eine GPT-Vorschlags-Tag-Liste angenommen?
- **BentoPDF**: Ist als "vorbereitet" markiert, Fachabnahme offen. Wenn du in zwei Monaten noch keine PDFs verarbeitet hast: weglassen.
- **Hermes-Agent**: Das ist die eindeutigste Spielerei. Ein LLM-Agent über SSH-Runner zu einer separaten VM, mit eigenem Dashboard, dessen NAS-Seite bewusst deaktiviert ist, weil die VM-Seite "offen" ist. Komplexes Modell C, abhängig von einer dedizierten Linux-VM, mit Provider-Keys und Dashboard-Domain. Das ist klassisches Nerd-Lieblingsprojekt-ohne-klaren-Alltagsnutzen-Symptom. Solange du nicht ehrlich beschreiben kannst, was Hermes für dich täglich tut, ist es Reifegrad "Experiment".
- **Speedtest-Tracker**: Nett für Monitoring der ISP-Qualität, aber ein einziger Speedtest-Container für eine private Leitung ist eher "ich messe gerne" als "ich brauche das wirklich".
- **code-server**: Web-IDE im Browser. Sinnvoll, wenn du wirklich vom iPad aus arbeitest. Sonst: VSCode lokal reicht.
Use Cases, die echten Mehrwert hätten und fehlen:
- **Finanzen**: Im DR-Doc steht `/mnt/user/finance` als Share, aber kein App-Stack. Firefly III oder Actual Budget würden hier sofort spürbaren Alltagsnutzen liefern — Konten konsolidieren, Budgets verfolgen, Steuer-Vorbereitung.
- **Familien-SSO**: Du hast Authelia, aber Authelia ist primär für Admin-UIs konfiguriert. Wenn deine Familie Nextcloud, Immich, Mealie und Vaultwarden mit einem Login nutzen könnte (Authelia OIDC-Provider), wäre das ein echter Mehrwert für andere als dich.
- **Smartphone-Foto-Auto-Backup**: Immich kann das nativ. Wenn das nicht eingerichtet ist und alle Familien-Smartphones automatisch in `immich` landen würden, wäre das die Killer-App für deine Frau und Kinder, nicht für dich.
- **Tagliche Familien-Übersicht auf einem Wandtablet**: Homepage oder Glance auf einem alten Tablet im Flur, mit Kalender (Nextcloud), Wetter (Ecowitt), und ntfy-Notifications.
- **Kalender/Aufgaben/CardDAV-Nutzung**: Nextcloud kann das, aber ich sehe in der Doku keinen Hinweis, dass die Familie das tatsächlich nutzt. Wenn nicht: Migration weg von Google Calendar/iCloud wäre ein echter Souveränitäts-Gewinn.
**Note für diesen Block: 2-3.** Die infrastrukturelle Substanz ist top, aber der Anteil "Container läuft, weil ich ihn ausprobieren wollte" ist höher als nötig.
---
## 3. Best Practices
Was richtig gut ist gemessen an dem, was professionelle Setups machen würden:
- **Image-Pinning mit Tag und Digest** für Stateful Services (Postgres 17.9, Redis 7.4-alpine, Mongo 7.0.32, alle mit `@sha256:...`). Das machen die wenigsten Homelabs. Echt vorbildlich.
- **Secrets via Docker `_FILE`-Mounts oder Komodo Stack ENV, niemals im Git** — und das ist konsequent durchgezogen, inklusive `.gitignore` für `.env`-Dateien und expliziter Doku in `docs/SECRETS_MAP.md`.
- **`no-new-privileges:true`** als Standard, mit dokumentierten Ausnahmen für Scrutiny (SMART) und Glances (Host-Observability) statt versteckter Lockerungen.
- **Policy-as-Code light** über `ops/policy-checks/check_repo.ps1` — der letzte Report zeigt 0 Critical und 4 dokumentierte Warnings. Das ist Tooling-Disziplin, die viele Firmen nicht haben.
- **Restore-Tests mit Schedule** (`ops/restore-tests/schedule.md`): wöchentliche Freshness-Checks, monatliche Mini-Restores für Vaultwarden und Gitea, alle zwei Monate Paperless. Erfolg ist explizit als "Smoke-Test passt", nicht "Container startet" definiert. Das ist seltene Reife.
- **Pre-Backup-Dumps statt rohe Live-DB-Verzeichnisse als primärer Restore-Pfad** — das ist die Lehre, die viele erst nach dem ersten kaputten Restore lernen.
- **Posture-Check + Docker-Critical-Events → ntfy** als Live-Alarmierung, bereits mit Wiederholungsschutz (`ALERT_REPEAT_SECONDS=86400`) und Dedup. Das ist Operations-Reife.
- **Cloudflare DNS Challenge für ACME** statt HTTP-01, ermöglicht Wildcard-Zertifikate und keine Port-80-Abhängigkeit für Erneuerung.
- **GitOps mit Webhook-Pflicht für neue Stacks** (`docs/WORKFLOW.md`, Abschnitt "Pflicht bei neuen Komodo-Stacks"). Das verhindert "deployed-once-then-forgotten"-Stacks.
Was Standard wäre und du sinnvoll davon abweichst:
- **Komodo bewusst ohne pauschale ForwardAuth-Middleware** — richtig, weil Webhooks, API und Periphery sonst brechen. Die meisten würden hier blind Authelia davor schalten und dann zwei Tage debuggen.
- **Authelia ohne Redis-Session-Backend** — bewusste Vereinfachung. Du bezahlst dafür mit Re-Login nach Authelia-Restart, gewinnst dafür weniger Tier-1-Abhängigkeiten. Vertretbarer Trade-off.
- **Traefik dynamic config als manuelle Host-Sync-Ausnahme** — pragmatisch dokumentiert, statt eines komplexen Auto-Sync-Workarounds.
Wo du gefährlich von Best Practice abweichst:
- **Externer Repo-Mirror als DR-Voraussetzung ist offenes TODO** (`docs/DISASTER_RECOVERY.md`, Abschnitt 11). Wenn Gitea ausfällt — und Gitea hängt auch noch an Traefik und PostgreSQL — kannst du Komodo nicht aus Git deployen, kannst die Repo-Doku nicht lesen, und je nach Schaden hast du den lokalen Clone als einzigen Pfad. Das ist ein echter Single Point of Failure. Ein Push-Mirror nach GitHub/GitLab (privat) oder zumindest ein versionierter Sync nach BorgBase würde das in 30 Minuten lösen.
- **Unraid USB-Flash-Backup ist offenes TODO**. Wenn der USB-Stick stirbt, ist das nicht das Ende der Welt (Daten leben), aber es kostet einen vollen Wiederaufsetzungs-Tag. Unraid hat dafür einen eingebauten Backup-Mechanismus.
- **Komodo Self-Stack Drift Mai 2026**: Du hattest schon einen Vorfall, wo Komodo selbst nicht mehr sauber managebar war ("Recovery-ENV als Tier-1-Secret-Material"). Das Bootstrap-Problem — Komodo verwaltet Komodo — ist nicht gelöst, nur dokumentiert. Eine echte Lösung wäre: Komodo-Self-Stack explizit aus Komodo herauslassen und nur als `docker compose`-Script in `services/` halten.
- **Kein Fail2Ban / CrowdSec vor Traefik**. Vaultwarden und Nextcloud sind im Internet erreichbar mit eigener Auth. Die meisten Anti-Brute-Force-Maßnahmen liegen in den Apps selbst, nicht auf Layer 7. Bei einer ernsten Bot-Welle würde Authelia die Last tragen, ohne IP-Bans auszusprechen. CrowdSec als Bouncer für Traefik wäre eine sinnvolle Härtung mit überschaubarem Aufwand.
**Note für diesen Block: 2.**
---
## 4. Nerd-Level / Advanced Homelab
Was sehr erfahrene Selfhoster mit so einem Repo zusätzlich machen würden:
- **Renovate Bot oder ein vergleichbares Image-Update-Tracking**: Du pinnst Digests, was richtig ist, aber damit hast du dich auch in die manuelle Update-Pflicht begeben. Renovate gegen Gitea würde wöchentliche PRs für neue Patch-Versionen auf master öffnen, die du mergen oder ignorieren kannst. Das ist deutlich besser als "irgendwann manuell den Digest aktualisieren".
- **Staging-Path**: Aktuell hast du master und das ist gleichzeitig produktiv. Ein zweiter Branch (`staging`) der gegen einen zweiten Komodo-Server (in einer Tailscale-VM oder einem zweiten Unraid-Share) deployed, würde Risiko-Aenderungen testbar machen. Das ist viel Aufwand für ein Homelab, aber wenn dich die Stabilität ernsthaft kümmert, ist es der nächste Reifegrad.
- **OIDC-Provider statt nur ForwardAuth**: Authelia kann OIDC. Wenn Nextcloud, Immich, Grafana, Komodo (theoretisch), Vaultwarden (via OIDC-Bridge) per SSO laufen, ist das die "echte" Konsolidierung. Heute hast du ForwardAuth für Admin-Dienste, aber Apps mit eigener Auth (Nextcloud, Immich, Jellyfin) sind Eigeninseln.
- **Restore-Tests automatisiert in CI**: Du hast Skripts und Cron-Slots, aber kein CI gegen das Repo, das die Restore-Test-Skripte syntaktisch und semantisch prüft. Ein Gitea Actions oder Drone-Setup auf dem Host könnte das gegen jeden Commit laufen lassen.
- **Backup-Test-Härtung: Restore in eine echte Test-Domain mit Traefik-Route hinter Authelia** (heute bewusst ohne Domain — siehe `docs/RESTORE_MATRIX.md`). Das ist eine bewusste Entscheidung, würde aber einen "End-to-End restore drill" möglich machen, der einmal pro Quartal komplett durchläuft.
- **Disk1 NTFS → XFS Phase 2**: Im Repo dokumentiert, im posture-check temporär toleriert mit `ALLOW_DISK1_NTFS=1`. Das ist die offensichtlichste offene Hardening-Baustelle.
- **Loki-Retention und Log-Volume mal anschauen**: 30 Tage Retention ist gut, aber im aktuellen Stand wirst du irgendwann Storage-Probleme bekommen, wenn du nicht weißt, wie viel der Stack pro Tag produziert.
Was sie bewusst weglassen würden:
- **Hermes-Agent**: Genau dieses "ich baue mir einen Agenten der über SSH meine VM bedient und ein Dashboard hat" ist das, wovor erfahrene Leute nach dem dritten Homelab warnen. Es bringt Komplexität, Wartungslast und keine messbare Reduktion deiner manuellen Arbeit. Wenn Hermes nicht in den nächsten 60 Tagen produktiv und unverzichtbar wird: entfernen.
- **Drei Dashboard-Tools**: Sie würden eines wählen (vermutlich Homepage), die anderen rauswerfen.
- **Zwei Medienserver**: Plex und Jellyfin parallel ist Tool-Sammlerei.
- **Eigenes paperless-gpt-Container** wenn nicht aktiv im Workflow: lieber das LLM ein-zwei mal manuell auf eine PDF werfen als einen Container 24/7 idle laufen lassen.
**Note für diesen Block: 2-3.** Sehr nahe am nächsten Reifegrad, aber an drei, vier Stellen würde erfahrene Hand jetzt entrümpeln statt erweitern.
---
## 5. Betrieb und Wartbarkeit
Hier ist die Bewertung am eindeutigsten positiv. Dieses Setup ist langfristig wartbar.
Die Dokumentation ist auf einem Niveau, das ich selten sehe. `docs/SERVICE_CATALOG.md` ist ein vollständiger Dienst-Katalog mit Restore-Quelle, Smoke-Test und Besonderheiten pro Dienst. `docs/REPO_MAP.md` ist eine technische Landkarte. `docs/RESTORE_MATRIX.md` ist nicht nur "wo ist das Backup", sondern "was ist die führende Quelle", "welche Dumps", "welche Secrets müssen vor Start da sein", "was ist der Smoke-Test". Das ist Doku, die in sechs Monaten noch funktioniert.
Der Workflow ist klar (`docs/WORKFLOW.md`): Fetch → Pull → ändern → Commit → Push → Komodo. Es gibt eine explizite Stop-Regel ("wenn zwei Reparaturversuche nicht funktionieren, Pflichtmatrix ausfüllen"), die viele Selfhoster nicht haben und stattdessen in Mut-Spiralen rutschen.
Drei Stellen, wo Wartbarkeit gefährdet ist:
- **Hermes-Agent**: Spätestens nach sechs Monaten ohne aktive Pflege verstehst du die Model-C-Architektur nicht mehr ohne `ops/hermes-agent/README.md` zu lesen — und dann ist die Frage, warum überhaupt.
- **Doppelter Monitoring-Stack (`ops/grafana-influxdb`, `ops/loki`, `monitoring/`)**: Solange beide Welten im Repo sind, vergisst du in einem halben Jahr, welche live ist. Die Migration muss abgeschlossen und die Altstände müssen entfernt werden.
- **Authelia Repo-Baseline vs. Host-Config**: Du dokumentierst selbst, dass die Repo-`configuration.yml` "manuell auf den Host gemerged" werden muss, mit OIDC und Secrets hostseitig. Das ist Drift-Risiko per Design. Ein zweiter Mechanismus (z. B. das manuelle Pendant zum Traefik-dynamic-Sync) oder mindestens ein expliziter Diff-Check vor jeder Auth-Änderung wäre Pflicht.
**Note für diesen Block: 1-2.**
---
## 6. Sicherheit und Zugriff
Du hast eine durchdachte Schichtung:
- Authelia für Admin-UIs (`uptime`, `borg`, `files`, `code`, `grafana`, `monitoring`, `pdf`, `glance`, `glances`, `scrutiny`, `paperless-gpt`, `speedtest`, `hermes`, `traefik`-Dashboard, `homepage`).
- Native App-Auth für User-Apps (`vaultwarden`, `nextcloud`, `immich`, `jellyfin`, `paperless`).
- Komodo mit eigener Auth ohne ForwardAuth (bewusste Ausnahme).
- Tailscale für Admin-Zugriff von außen.
Was wirklich gut ist:
- Authelia mit Argon2id, `iterations=3`, `memory=65536`, `parallelism=4`, `key_length=32`, `salt_length=16` — das ist solide Konfiguration, nicht Default-Müll.
- Secrets durchgängig per File-Mount oder Komodo Stack ENV, nie im Compose im Klartext.
- Gitea Webhook-Allowlist (`GITEA__webhook__ALLOWED_HOST_LIST=komodo-core,localhost,127.0.0.1,192.168.178.0/24`) und Registrierung deaktiviert — das schließt Webhook-SSRF-Vektoren.
- `cloudflare_dns_api_token` als Docker Secret, nicht als ENV.
Wo du härter trennen solltest:
- **AdGuard Admin-Port 8082 ist direkt am LAN gebunden ohne Authelia**. Das ist im Architekturdokument als offenes TODO ("Block F") markiert. Im Home-LAN ist das verschmerzbar, aber wenn du eines Tages einen Gast im WLAN hast oder ein IoT-Gerät kompromittiert wird, ist das ein direkter Pfad in die DNS-Konfiguration.
- **Nextcloud läuft ohne ForwardAuth** (bewusst wegen WebDAV/CardDAV). Wenn deine Familie schwache Passwörter setzt, ist Nextcloud im Internet das primäre Angriffsziel. Nextcloud-eigene Maßnahmen (Brute-Force-Protection, 2FA-Pflicht für Admin) sollten dokumentiert aktiv sein.
- **2FA-Pflicht in Authelia**: In der Doku nicht klar erwähnt. Wenn 2FA nur "optional" ist, läuft die Härtung ins Leere.
Wo Komfort wichtiger ist und das sinnvoll so bleibt:
- Komodo ohne ForwardAuth — richtig.
- Authelia ohne Redis-Session-Backend — vertretbar.
- Plex/Jellyfin mit nativer Auth — sinnvoll, weil die Clients eigene Auth machen.
**Note für diesen Block: 2.**
---
## 7. Backup und Disaster Recovery
Das ist eine der stärksten Säulen.
Was du richtig machst:
- Borg statt Backrest — dokumentierte Entscheidung, eine Backup-Technologie statt zwei.
- **Pre-Backup-Dumps als kanonische Restore-Quelle**, nicht rohe Live-DB-Verzeichnisse. Explizit dokumentiert in `ops/borg-ui/BACKUP_SCOPE.md` ("Do not back up raw live database storage directories as the primary recovery artifact").
- **Restore-Tests mit Schedule** und expliziter Erfolgsregel ("Container läuft reicht nicht — Smoke-Test muss greifen").
- Dump-Skript für SQLite-Container (Gitea, Vaultwarden, Uptime-Kuma, Speedtest-Tracker), BoltDB-Snapshot für Filebrowser, `pg_dump` für die einzelnen Postgres-Datenbanken, `mongodump` für Komodo-Mongo.
- Borg-Scope erweitert um `/mnt/user/services` für GitOps-Recovery (Repo, Stack-Workspaces, Posture-Check-State).
- Tier-Modell in `docs/RESTORE_MATRIX.md` mit klarer Reihenfolge.
- Dokumentierte Restore-Lab-Praxis: Testpfad `/mnt/user/backups/restore-lab/<dienst>`, Reports unter `/mnt/user/backups/restore-reports`, ohne Traefik-Route — keine Vermischung von Test und Produktion.
Was wirklich offen ist:
- **Externer Backup-Mirror oder Off-Site-Ziel**: BorgBase ist erwähnt (`ops/borg-ui` als Borg-UI auf BorgBase-Repo), aber die Frage "was passiert, wenn der Unraid-Host und BorgBase gleichzeitig down sind" hat keine dokumentierte Antwort. Zwei Repos (z. B. BorgBase + ein zweites lokales NAS oder ein Hetzner Storage Box) wären die Standardlösung.
- **Externer Repo-Mirror als DR-Voraussetzung** — in DR.md als TODO markiert. Wenn Gitea nicht aufsteht, ist das Repo nur über deinen lokalen Clone erreichbar.
- **Unraid USB-Flash-Backup** — in DR.md als TODO markiert.
- **Borg-Passphrase extern sicher hinterlegt** — als TODO markiert. Das ist die typische "wenn das Haus brennt"-Frage. Vaultwarden hilft dir nicht, wenn Vaultwarden gerade restauriert werden soll. Eine zweite Kopie der Passphrase (verschlüsselt auf einem USB-Stick im Bankschließfach, oder bei einem Familienmitglied) ist Standard.
- **Komodo-Mongo Dump nach Major-Upgrades verifizieren** — als Watchpoint dokumentiert, aber nicht im automatischen Restore-Test-Cron.
Restore-Tests sind monatlich für Vaultwarden und Gitea, alle zwei Monate für Paperless, "später" für Immich. Das ist gut, aber Immich-Restore-Tests sind die kritischsten, weil dort die größten Datenmengen liegen und ein silent corruption am schmerzhaftesten wäre.
**Note für diesen Block: 1-2.** Wenn die offenen DR-Vorbereitungs-TODOs abgehakt wären, klare 1.
---
## 8. Monitoring und Transparenz
Das ist der Bereich mit dem größten Übergang. Du hast viele Tools, mit Überschneidungen, und der zentrale Monitoring-Stack ist im Aufbau.
Was du heute hast:
- **Uptime-Kuma**: HTTP/TCP-Uptime-Checks mit Web-UI.
- **Glances**: Live-System-Sicht (CPU/RAM/Disk pro Host).
- **Scrutiny**: SMART-Monitoring für Laufwerke.
- **Speedtest-Tracker**: Periodische Speedtests gegen den ISP.
- **Glance**: Status-Dashboard mit Widgets (Immich, AdGuard, Speedtest, Docker-Container).
- **Homepage**: Start-Dashboard mit Service-Cards.
- **Posture-Check**: Host-Filesystem, NVMe-SMART, Mover, Füllstand → ntfy.
- **Docker-Critical-Events**: `die`/`oom`/`kill` → ntfy.
- **`monitoring/`-Zielstack**: Prometheus, Alertmanager, ntfy-Bridge, Blackbox, Loki, Promtail, Grafana, node-exporter, cAdvisor, InfluxDB 3 Core.
Was richtig gut ist:
- **Ein zentraler Alert-Pfad**: alle problemrelevanten Meldungen landen auf `homelab-alerts` per ntfy. Das ist die wichtigste Disziplin und du hast sie. `docs/ALERTING_MAP.md` listet alle Sender.
- Prometheus-Stack mit Alertmanager + Bridge zu ntfy, also nicht "Grafana sendet Email" sondern "Alertmanager-Pflicht-Pfad".
- Blackbox-Exporter ersetzt mittelfristig Uptime-Kuma (richtige Strategie).
- 30 Tage Retention für Prometheus und Loki — sinnvoll für Diagnose-Daten, kein Backup-Surrogat.
- node-exporter + cAdvisor + Traefik-Metrics → wirklich vollständige Infrastruktur-Telemetrie.
Was fehlt oder zu viel ist:
- **Doppelte Beobachtungs-Tools nebeneinander**: Uptime-Kuma vs. Blackbox, Glances vs. node-exporter+cAdvisor, Glance vs. Homepage. Du weißt das, die Migration ist im Gang. Bis sie fertig ist, gibt es Verwirrung darüber, welches die "Wahrheit" ist.
- **Smoke-Test-Dashboards**: In `monitoring/grafana/dashboards/` sind ein paar Dashboards, aber die "Family-View" — "alles grün, alles erreichbar, Backup heute Nacht durchgelaufen" — fehlt als explizites Dashboard. Das wäre der Wert für dich selbst: morgens kurz draufschauen und wissen, ob etwas die Aufmerksamkeit braucht.
- **Alert-Regeln explizit listen**: `monitoring/prometheus/alerts.yml` existiert, aber eine kurze Doku, welche Regeln wann feuern (Disk > 90%, Borg älter 36h, Cert läuft in 14 Tagen ab, etc.), würde die Operations-Reife komplettieren.
- **Cert-Token-Check** läuft (laut ALERTING_MAP) — gut, das ist die einzige sinnvolle Methode, "TLS-Cert läuft ab" früh genug zu sehen.
**Note für diesen Block: 3.** Bricht ab, sobald die Monitoring-Migration sauber abgeschlossen ist und die Altstände entfernt sind — dann eine 2.
---
## 9. Konkreter Mehrwert-Fahrplan
### Quick Wins (≤ eine Woche, hoher Nutzen)
- **Externer Push-Mirror für das Repo nach GitHub privat** einrichten. Das ist ein Webhook in Gitea + ein leerer GitHub-Privat-Repo. 30 Minuten. Löst das wichtigste DR-Risiko.
- **Borg-Passphrase auf einen USB-Stick im Bankschließfach** oder in eine versiegelte Umschlag-Box. Eine analoge Sicherung gegen das digitale Worst-Case-Szenario.
- **Plex oder Jellyfin entscheiden**: einen davon weg. 14 Tage Nutzungs-Tracking via Server-Logs oder einfach beobachten, wer welchen Client öffnet. Dann den ungenutzten Stack archivieren.
- **Glance ODER Homepage** als einziges Dashboard wählen. Heute laufen beide. Es gibt keinen technischen Grund für zwei.
- **Authelia 2FA-Pflicht für alle aktiven User** verifizieren — wenn nicht gesetzt, jetzt setzen.
- **Disk1 NTFS → XFS Phase 2 abschließen** — das ist im Repo dokumentiert und im posture-check als Übergangsausnahme markiert. Loswerden.
### Phase 1 (zwei bis vier Wochen, Stabilität und Ordnung)
- **Monitoring-Migration abschließen und Altstände entfernen**: `monitoring/` produktiv, dann `ops/grafana-influxdb` und `ops/loki` aus dem Repo löschen (mit Backup-Branch fürs Gewissen).
- **Uptime-Kuma ablösen durch Blackbox + Grafana-Alerts**: nach den sieben Tagen Parallelbetrieb, die in `docs/SERVICE_CATALOG.md` als Pflicht stehen.
- **Hermes-Agent ehrliche Entscheidung**: produktiv machen mit klarem Alltagsnutzen, oder entfernen. Kein "halb da, halb deaktiviert"-Zustand für ein weiteres Quartal.
- **paperless-gpt und BentoPDF Status**: gleiche Frage. Produktiv oder weg.
- **Unraid USB-Flash-Backup** einrichten (Unraid hat einen eingebauten Mechanismus).
- **Ein Family-View-Dashboard in Grafana** bauen: alles-grün-Übersicht für den Morgen-Check.
### Phase 2 (vier bis zwölf Wochen, Automatisierung und Transparenz)
- **Authelia OIDC-Provider aktivieren** und Nextcloud, Immich, Grafana als OIDC-Clients konfigurieren. Echtes SSO für die Familie.
- **Renovate Bot gegen Gitea** für kontrollierte Image-Updates (PRs statt manuelle Digest-Pflege).
- **Restore-Test für Immich** als eigener Sprint einplanen — der größte Datentopf und der einzige Tier-2-Dienst ohne Mini-Restore-Test.
- **Familie onboarden**: Smartphone-Auto-Backup zu Immich für alle Familien-Geräte. Das ist der Schritt vom "ich betreibe Container" zum "meine Familie benutzt aktiv was Selbstgebautes".
- **CrowdSec vor Traefik** als Bouncer für die öffentlich erreichbaren Apps (Vaultwarden, Nextcloud, Immich, Gitea).
### Phase 3 (drei bis sechs Monate, Advanced Nerd-Level)
- **Staging-Branch + zweites Komodo-Ziel** in einer Tailscale-VM, für Risiko-Änderungen.
- **Restore-Test-Automatisierung als CI** (Gitea Actions oder Drone).
- **Off-Site-Backup zu einem zweiten Ziel** (zweites BorgBase-Repo, Hetzner Storage Box, oder zweites NAS bei einem Familienmitglied).
- **Cold-Standby-Konzept** dokumentieren: was passiert, wenn der Unraid-Host stirbt und du erst in zwei Wochen Ersatz hast?
- **Komodo-Self-Stack rausnehmen** und als handgepflegten `docker compose`-Service in `services/` halten — löst das Bootstrap-Problem.
### Phase 4 (Spielwiese, nice-to-have)
- Firefly III oder Actual Budget für Finanz-Übersicht.
- Wandtablet-Setup im Flur mit Family-Dashboard.
- Smart-Home-Automatisierungen über Home Assistant tiefer mit ntfy verzahnen (Frost-Warnung, PV-Überschuss-Hinweis, Briefkasten-Sensor).
- Ein eigenes kleines Dashboard für Ecowitt-Wetterdaten (sobald die Pipeline aus `docs/HOME_ASSISTANT_INFLUXDB_ECOWITT.md` läuft).
---
## Schulnote, Top-5-Listen, klare Empfehlung
### Schulnote
**2- (gut bis befriedigend, eher 2).**
Auf der Skala "durchschnittliches Homelab" wäre das eine 1. Die strukturelle Disziplin, GitOps-Konsequenz, Doku-Qualität, Backup-Reife und Architektur-Klarheit liegen weit über dem, was die meisten Selfhoster jemals erreichen. Was die Note von einer 1 runterzieht: drei bis fünf offene Baustellen, die nicht trivial sind (externer Repo-Mirror, Monitoring-Migration unfertig, Hermes-Agent im Schwebezustand, zwei Medienserver parallel, AdGuard Admin-Port ohne ForwardAuth). Wenn du Phase 1 abschließt, bist du klar bei einer 1.
### Top 5 sofort verbessern
1. **Externer Repo-Mirror** (GitHub privat, BorgBase, oder zweites Gitea). 30 Minuten Aufwand, schließt das wichtigste DR-Loch.
2. **Borg-Passphrase analog außerhalb des Systems sichern** (Bankschließfach, Familienmitglied, Tresor).
3. **Plex oder Jellyfin entscheiden**, einen davon entfernen.
4. **Glance oder Homepage** als einziges Dashboard wählen.
5. **AdGuard Admin-Port hinter Authelia** oder mindestens nur via Tailscale erreichbar (heute LAN-direkt).
### Top 5 mit dem größten zusätzlichen Mehrwert
1. **Smartphone-Auto-Backup zu Immich für die ganze Familie**: macht aus deinem Foto-Server eine echte Killer-App für andere als dich.
2. **Authelia OIDC-Provider aktivieren** und Nextcloud + Immich + Grafana per SSO: ein Login für alle wichtigen Apps.
3. **Renovate Bot gegen Gitea**: automatisierte Update-PRs für deine Digest-pinnten Images.
4. **Family-View-Dashboard in Grafana**: morgens 30 Sekunden draufschauen statt Tools-Tour.
5. **Finanz-App (Firefly III oder Actual Budget)**: füllt den `/mnt/user/finance`-Share mit echtem Alltagsnutzen.
### Top 5 lieber NICHT machen
1. **Hermes-Agent ausbauen statt loswerden**. Wenn du in 60 Tagen nicht ehrlich sagen kannst, was Hermes dir täglich abnimmt: weg damit. Komplexität ohne Gegenwert ist das größte Anti-Pattern in jedem Homelab.
2. **Noch mehr Dashboards einbauen**. Du hast bereits Homepage, Glance, Komodo-UI und kommst gleich noch mit Grafana-Family-View. Mehr wäre Sammlerei.
3. **Pauschale Authelia-ForwardAuth vor Komodo** schalten. Dokumentierte Ausnahme aus gutem Grund. Webhooks, API und Periphery würden brechen.
4. **Backend_net auf `external: true` statt `internal: true`** umstellen, weil "ist ja einfacher". Genau das ist die Mauer, die viele Apps vor öffentlichem Zugriff schützt.
5. **Komodo Self-Stack komplett über Komodo managen lassen**. Du hattest schon einen Drift-Vorfall. Komodo verwaltet Komodo ist ein Bootstrap-Problem ohne befriedigende Lösung.
### Klare Empfehlung
**Vereinfachen und konsolidieren, NICHT weiter ausbauen.**
Du bist an einem Punkt, an dem das Setup mehr Substanz hat, als aktiv genutzt wird. Die nächsten sechs Monate sollten weniger neue Dienste sehen und mehr Entrümpelung (Plex vs. Jellyfin, Glance vs. Homepage, Hermes-Entscheidung, Monitoring-Altstände raus, paperless-gpt/BentoPDF-Entscheidung), mehr Familien-Aktivierung (Immich-Smartphone-Backup, OIDC-SSO, Family-Dashboard), und mehr DR-Resilienz (externer Mirror, Off-Site-Backup-Ziel, Borg-Passphrase analog gesichert).
Wenn du das in den nächsten drei Monaten machst, hast du eine private Plattform mit echtem Alltagsnutzen, klarer Wartbarkeit, und einer Wiederherstellbarkeit, die seriöser ist als das, was viele Mittelstandsfirmen für ihre Office-IT haben. Das ist das Ziel, nicht "noch ein Container".
Wenn du dann weiterausbauen willst, bist du in der Position, das aus einer Stärke heraus zu tun, nicht aus dem "ich muss noch das hier probieren"-Reflex.
---
## Status-Anhang 2026-05-30
Dieser Anhang ist nicht Teil der Originalbewertung vom 2026-05-23. Er ordnet jedem konkret handelbaren Befund den tatsaechlichen Stand nach den Audit-Sprints zu, damit das Dokument selbststaendig lesbar bleibt.
### Block 1 - Architektur
| Originalbefund | Stand 2026-05-30 |
|---|---|
| ops/grafana-influxdb + ops/loki + monitoring/ parallel im Repo | **erledigt** 2026-05-26: Altstaende entfernt, monitoring/ einziger aktiver Observability-Stack |
### Block 2 - Nutzen und Mehrwert
| Originalbefund | Stand 2026-05-30 |
|---|---|
| Plex zusaetzlich zu Jellyfin | **erledigt** 2026-05-25: Jellyfin entfernt, Plex bleibt einziger Medienserver |
| Glance + Homepage + Komodo-UI als drei parallele Dashboards | **erledigt** 2026-05-25: Homepage entfernt, Glance bleibt einziges Dashboard |
| paperless-gpt — produktiv oder weg? | **entschieden** 2026-05-28: behalten bis Paperless-NGX 3.0 native KI-Features, dann neu bewerten |
| BentoPDF — produktiv oder weg? | **entschieden** 2026-05-28: behalten als situatives Tool (~4 MB RAM-Footprint) |
| Hermes-Agent als Spielerei, Review-Deadline gesetzt | **geparkt** mit Review-Deadline 2026-07-25 |
| Speedtest-Tracker als "ich messe gerne" | unveraendert, keine Operator-Entscheidung getroffen |
| code-server — sinnvoll oder weg? | unveraendert, keine Operator-Entscheidung getroffen |
| Finanz-App (Firefly III / Actual Budget) als fehlender Mehrwert | offen, nice-to-have ohne aktiven Termin |
| Familien-SSO ueber Authelia OIDC | **geparkt** im Auth-Block (F-13) |
| Smartphone-Auto-Backup zu Immich | offen, Anwendungsentscheidung pro Familienmitglied |
| Wandtablet im Flur mit Family-Dashboard | offen, Spielwiese |
| Kalender/Aufgaben/CardDAV-Nutzung dokumentieren | offen, Operator-Frage |
### Block 3 - Best Practices
| Originalbefund | Stand 2026-05-30 |
|---|---|
| Externer Repo-Mirror als DR-Voraussetzung offen | **erledigt**: GitHub-Push-Mirror `michaelkaleschke-spec/homelab-infra` aktiv |
| Unraid USB-Flash-Backup offen | **erledigt** 2026-05-25: `unraid-flash-config.tar.gz` im Borg-Scope |
| Komodo Self-Stack Drift Mai 2026 nur dokumentiert, nicht geloest | **teilweise erledigt** 2026-05-29/30: Trockenlauf-Skript + erfolgreicher Erstlauf belegen `ops/komodo/docker-compose.yml` als Recovery-Anker; Self-Stack-Entkopplung selbst bleibt offen |
| Kein Fail2Ban/CrowdSec vor Traefik | **geparkt** im Auth-Block (F-14) |
| Renovate Bot fehlt | **erledigt** 2026-05-29: live, erster Lauf erfolgreich, 5 PRs in Gitea |
### Block 4 - Nerd-Level
| Originalbefund | Stand 2026-05-30 |
|---|---|
| Renovate Bot oder vergleichbares Update-Tracking | **erledigt** 2026-05-29 |
| Staging-Path mit zweitem Komodo-Ziel | offen, Phase 3 nice-to-have |
| OIDC-Provider statt nur ForwardAuth | **geparkt** im Auth-Block (F-13) |
| Restore-Tests automatisiert in CI (Gitea Actions / Drone) | offen, Phase 3 |
| End-to-End restore drill mit Test-Domain hinter Traefik | offen, bewusst nicht (Test-Lab bleibt ohne Domain) |
| Disk1 NTFS -> XFS Phase 2 | **erledigt** (`ALLOW_DISK1_NTFS=0` als Default im posture-check, XFS-Erwartung aktiv) |
| Loki-Retention und Log-Volume bewerten | offen, Detail-Sweep |
| Hermes-Agent loswerden | **geparkt** mit Review 2026-07-25 |
| Drei Dashboard-Tools auf eines reduzieren | **erledigt**: Glance bleibt als einziges |
| Zwei Medienserver | **erledigt**: Jellyfin entfernt |
### Block 5 - Betrieb und Wartbarkeit
| Originalbefund | Stand 2026-05-30 |
|---|---|
| Doppelter Monitoring-Stack als Wartbarkeits-Risiko | **erledigt** 2026-05-26 |
| Authelia Repo-Baseline vs. Host-Config Drift "by design" | **erledigt** 2026-05-30 (F-10): `services/authelia-diff.sh` + Posture-Check ueberwacht ACL-Drift automatisch, WORKFLOW.md hat eigene Pflicht-Sektion |
| Hermes-Agent verstaendnis-kritisch nach 6 Monaten | **geparkt** mit Review 2026-07-25 |
### Block 6 - Sicherheit und Zugriff
| Originalbefund | Stand 2026-05-30 |
|---|---|
| AdGuard Admin-Port 8082 LAN-direkt ohne Authelia | **erledigt** 2026-05-26: auf Tailscale-IP `100.80.98.33:8082` gebunden, LAN-Zugriff blockiert |
| Nextcloud ohne ForwardAuth, Brute-Force-Doku offen | **geparkt** im Auth-Block (F-18) |
| Authelia 2FA-Pflicht nicht klar dokumentiert | **geparkt** im Auth-Block (F-04) |
### Block 7 - Backup und Disaster Recovery
| Originalbefund | Stand 2026-05-30 |
|---|---|
| Externer Backup-Mirror / zweites Off-Site-Ziel | **entschieden** 2026-05-28: kein zweites Off-Site; 3-2-1 mit Live + lokalem Borg + Hetzner + H:/-Nearline erfuellt; Hetzner-Haertungen als Folge-TODOs |
| Externer Repo-Mirror | **erledigt**: GitHub-Push-Mirror aktiv |
| Unraid USB-Flash-Backup | **erledigt** 2026-05-25 |
| Borg-Passphrase analog gesichert | **erledigt** 2026-05-26: Operator bestaetigt, offline gesichert |
| Komodo-Mongo Dump-Verifikation nach Major-Upgrades | offen, Watchpoint dokumentiert, nicht im automatischen Cron |
| Restore-Tests Immich (groesster Datentopf ohne Mini-Restore) | **erledigt** 2026-05-27: erster Host-Lauf erfolgreich (`SUCCESS`, 11977 Assets) |
### Block 8 - Monitoring und Transparenz
| Originalbefund | Stand 2026-05-30 |
|---|---|
| Doppelte Tools (Uptime-Kuma vs. Blackbox, Glance vs. Homepage) | **erledigt** 2026-05-25: Uptime-Kuma und Homepage entfernt |
| Family-View Dashboard fehlt als Morgens-Check | **Spec da, JSON offen**: `docs/FAMILY_VIEW_DASHBOARD.md` definiert Layout/Queries/Thresholds; JSON wird gebaut, sobald Metriken 7+ Tage stabil sind |
| Alert-Regeln explizit listen | **teilweise**: `monitoring/prometheus/alerts.yml` enthaelt Regeln (Borg-Stale, Cert-Expiry, Container-Down), `docs/ALERTING_MAP.md` mappt Sender — eine Doku-Zusammenfassung "welche Regel feuert wann" ist noch nicht zentralisiert |
### Block 9 - Konkreter Mehrwert-Fahrplan
#### Quick Wins (≤ 1 Woche)
| Original-Punkt | Stand 2026-05-30 |
|---|---|
| Externer Push-Mirror GitHub privat | **erledigt** |
| Borg-Passphrase analog sichern | **erledigt** 2026-05-26 |
| Plex oder Jellyfin entscheiden | **erledigt** 2026-05-25: Jellyfin weg |
| Glance oder Homepage waehlen | **erledigt** 2026-05-25: Homepage weg |
| Authelia 2FA-Pflicht aktivieren | **geparkt** (F-04) |
| Disk1 NTFS -> XFS Phase 2 | **erledigt** |
| AdGuard Admin Tailscale-only | **erledigt** 2026-05-26 |
#### Phase 1 (2-4 Wochen)
| Original-Punkt | Stand 2026-05-30 |
|---|---|
| Monitoring-Migration abschliessen, Altstaende entfernen | **erledigt** 2026-05-26 |
| Uptime-Kuma abloesen durch Blackbox + Grafana | **erledigt** 2026-05-25 |
| Hermes-Agent Entscheidung | **geparkt** mit Review 2026-07-25 |
| paperless-gpt / BentoPDF Entscheidung | **entschieden** 2026-05-28: beide behalten mit Begruendung |
| Unraid USB-Flash-Backup | **erledigt** 2026-05-25 |
| Family-View-Dashboard | Spec da, JSON wartet |
#### Phase 2 (4-12 Wochen)
| Original-Punkt | Stand 2026-05-30 |
|---|---|
| Authelia OIDC fuer Nextcloud/Immich/Grafana | **geparkt** (F-13) |
| Renovate Bot gegen Gitea | **erledigt** 2026-05-29 |
| Restore-Test fuer Immich | **erledigt** 2026-05-27 |
| Familien-Smartphone-Auto-Backup zu Immich | offen, Operator-Anwendungsentscheidung |
| CrowdSec vor Traefik | **geparkt** (F-14) |
#### Phase 3 (3-6 Monate)
| Original-Punkt | Stand 2026-05-30 |
|---|---|
| Staging-Branch + zweites Komodo-Ziel | offen |
| Restore-Test-Automatisierung als CI | offen |
| Off-Site-Backup zu zweitem Ziel | **entschieden** 2026-05-28: bewusst nicht |
| Cold-Standby-Konzept dokumentieren | offen |
| Komodo-Self-Stack rausnehmen | teilweise erledigt: Bootstrap-Anker und Trockenlauf-Skript da, Entkopplung selbst noch nicht |
#### Phase 4 (Spielwiese)
| Original-Punkt | Stand 2026-05-30 |
|---|---|
| Firefly III / Actual Budget | offen |
| Wandtablet mit Family-Dashboard | offen |
| Home Assistant + ntfy enger verzahnen | offen |
| Ecowitt-Wetter-Dashboard | offen |
### Top-5-Listen vom 2026-05-23
#### Top 5 sofort verbessern
| Original-Top-5 | Stand 2026-05-30 |
|---|---|
| 1. Externer Repo-Mirror | **erledigt** |
| 2. Borg-Passphrase analog sichern | **erledigt** |
| 3. Plex oder Jellyfin entscheiden | **erledigt** |
| 4. Glance oder Homepage waehlen | **erledigt** |
| 5. AdGuard Admin-Port haerten | **erledigt** |
**Alle 5 erledigt.**
#### Top 5 mit groesstem zusaetzlichen Mehrwert
| Original-Top-5 | Stand 2026-05-30 |
|---|---|
| 1. Smartphone-Auto-Backup zu Immich | offen, Anwendungsentscheidung |
| 2. Authelia OIDC fuer SSO | **geparkt** |
| 3. Renovate Bot gegen Gitea | **erledigt** |
| 4. Family-View-Dashboard | Spec da, JSON wartet |
| 5. Finanz-App | offen |
#### Top 5 lieber NICHT machen
| Original-Anti-Top-5 | Stand 2026-05-30 |
|---|---|
| 1. Hermes-Agent ausbauen statt loswerden | gehalten — Agent geparkt mit Review, nicht ausgebaut |
| 2. Noch mehr Dashboards einbauen | gehalten — Homepage entfernt, Glance bleibt einziges |
| 3. Pauschale Authelia vor Komodo | gehalten — Komodo bleibt ohne ForwardAuth |
| 4. backend_net auf external statt internal | gehalten — backend_net bleibt internal |
| 5. Komodo Self-Stack komplett via Komodo | teilweise gehalten — Trockenlauf-Skript als Gegenmaszahme, vollstaendige Entkopplung offen |
### Zusammenfassung des Status-Anhangs
- **Top 5 sofort**: 5/5 erledigt.
- **Quick Wins (7)**: 6 erledigt, 1 geparkt.
- **Phase 1 (6)**: 4 erledigt, 1 geparkt, 1 wartend.
- **Phase 2 (5)**: 2 erledigt, 2 geparkt, 1 offen.
- **Phase 3 (5)**: 1 entschieden (nicht umgesetzt), 1 teilweise, 3 offen.
- **Phase 4 (Spielwiese)**: alle offen, bewusst niedrige Prioritaet.
- **Auth-Block (F-04/13/14/18)**: vollstaendig geparkt nach Operator-Entscheidung 2026-05-26, gebuendelte Bearbeitung ausserhalb des aktuellen Zyklus.
Wer hier weiterarbeiten will, schaut auf `docs/AUDIT_2026-05-25_TODO.md` — dort ist der operative Stand gepflegt.
-36
View File
@@ -269,42 +269,6 @@ Diese Ausnahme bleibt bewusst bestehen. Der File-Provider wird weiterhin nur fue
--- ---
## Ausnahme: Authelia configuration.yml
> **Diese Datei wird von Komodo nicht automatisch deployed.**
`security/authelia/configuration.yml` ist die Repo-Baseline fuer nicht geheime Einstellungen (Access-Control, Session, Storage-Struktur, Notifier, TOTP). Die produktive Host-Datei darf zusaetzlich OIDC-Clients und hostseitige Identity-Provider-Konfiguration enthalten. Secret-Werte und die User-Datenbank bleiben grundsaetzlich ausserhalb von Git.
| Git-Pfad | Host-Pfad (NAS) |
|---|---|
| `security/authelia/configuration.yml` | `/mnt/user/appdata/authelia/config/configuration.yml` |
### Pflicht-Workflow bei Aenderungen an `configuration.yml`
1. Datei im Git-Repo (`security/authelia/`) aendern.
2. Commit + Push.
3. Aenderung manuell in die Host-Datei mergen, OIDC-/Identity-Provider-Sektionen erhalten.
4. `docker restart authelia` und Login-Smoke-Test auf einer ACL-betroffenen Domain.
5. `services/authelia-diff.sh` (Default-Aufruf) muss `exit 0` liefern.
### Automatische Drift-Erkennung
`services/authelia-diff.sh` vergleicht die `access_control:`-Sektion zwischen Repo-Baseline und Host-Datei. Der Posture-Check (`services/posture-check/posture-check.sh`) ruft das Skript als Check `authelia_config_drift` auf und meldet Drift als Warning via ntfy.
Konfigurierbare Variablen (Defaults sind das produktive Zielbild):
- `AUTHELIA_REPO_BASELINE` — Pfad zur Repo-Datei auf dem Host, Default `/mnt/user/services/homelab-infra/security/authelia/configuration.yml`
- `AUTHELIA_HOST_CONFIG` — Pfad zur produktiven Host-Datei, Default `/mnt/user/appdata/authelia/config/configuration.yml`
- `AUTHELIA_DIFF_SECTIONS` — Komma-Liste der zu vergleichenden Top-Level-Sektionen, Default `access_control`
- `AUTHELIA_DIFF_SCRIPT` — Pfad zum Diff-Skript fuer den Posture-Check, Default `/mnt/user/services/homelab-infra/services/authelia-diff.sh`
- `SKIP_AUTHELIA_DRIFT=1` — Check im Posture-Check ueberspringen
Pflicht-Setup auf dem Host: Repo-Spiegel unter `/mnt/user/services/homelab-infra/` (Read-only-Clone von Gitea `Micha/homelab-infra`, regelmaessig `git pull --ff-only`). Ohne Repo-Spiegel meldet der Check Warning, weil die Baseline-Datei fehlt — Critical wird der Check bewusst nicht.
> **Merksatz:** Push allein reicht hier nicht. Ohne den manuellen Merge ins Host-Configfile wirkt die Aenderung nicht, und der Drift-Check wuerde Warning melden.
---
## Secrets-Regeln ## Secrets-Regeln
- Secrets liegen niemals im Repository - Secrets liegen niemals im Repository
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
adguard: adguard:
image: adguard/adguardhome:v0.107.76@sha256:7157eb1dc3b26c7af1d6898759a7b3f7d0fa09891fbd2d3caa6abc1057a9179b image: adguard/adguardhome:v0.107.52@sha256:d16cc7517ab96f843e7f8bf8826402dba98f5e6b175858920296243332391589
container_name: adguard container_name: adguard
restart: unless-stopped restart: unless-stopped
volumes: volumes:
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
tailscale: tailscale:
image: tailscale/tailscale:stable@sha256:25cde9ad76020b0e29229136d0c38b5962e9a0e1774ffac9b0df68e4a37d6cf0 image: tailscale/tailscale:stable@sha256:dbeff02d2337344b351afac203427218c4d0a06c43fc10a865184063498472a6
container_name: Tailscale-Docker container_name: Tailscale-Docker
restart: unless-stopped restart: unless-stopped
network_mode: host network_mode: host
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
ddns-updater: ddns-updater:
image: ghcr.io/qdm12/ddns-updater:latest@sha256:9313e1c31f366c89dc0819e5eff85576cb23821424c0c267fa66cfa39aabde83 image: ghcr.io/qdm12/ddns-updater:latest@sha256:ee16ab4f6203bf9e5b0925d38a0b4ebf2d9f23771f933cfb2f5a2dbd5f9a2f88
container_name: ddns-updater container_name: ddns-updater
restart: unless-stopped restart: unless-stopped
security_opt: security_opt:
+9 -9
View File
@@ -1,6 +1,6 @@
services: services:
prometheus: prometheus:
image: prom/prometheus:v3.12.0@sha256:69f5241418838263316593f7274a304b095c40bcf22e57272865da91bd60a8ac image: prom/prometheus:v3.7.3@sha256:49214755b6153f90a597adcbff0252cc61069f8ab69ce8411285cd4a560e8038
container_name: monitoring-prometheus container_name: monitoring-prometheus
restart: unless-stopped restart: unless-stopped
command: command:
@@ -25,7 +25,7 @@ services:
- cadvisor - cadvisor
alertmanager: alertmanager:
image: prom/alertmanager:v0.32.1@sha256:51a825c2a40acc3e338fdd00d622e01ec090f72be2b3ea46be0839cd47a4d286 image: prom/alertmanager:v0.28.1@sha256:27c475db5fb156cab31d5c18a4251ac7ed567746a2483ff264516437a39b15ba
container_name: monitoring-alertmanager container_name: monitoring-alertmanager
restart: unless-stopped restart: unless-stopped
command: command:
@@ -42,7 +42,7 @@ services:
- no-new-privileges:true - no-new-privileges:true
alertmanager-ntfy-bridge: alertmanager-ntfy-bridge:
image: python:3.14-alpine@sha256:5a824eb82cc75361f98611f3cfc5091ea33f10a6ccea4d4ebdabbc523b9a1614 image: python:3.13-alpine@sha256:420cd0bf0f3998275875e02ecd5808168cf0843cbb4d3c536432f729247b2acc
container_name: monitoring-alertmanager-ntfy-bridge container_name: monitoring-alertmanager-ntfy-bridge
restart: unless-stopped restart: unless-stopped
dns: dns:
@@ -63,7 +63,7 @@ services:
- no-new-privileges:true - no-new-privileges:true
blackbox-exporter: blackbox-exporter:
image: prom/blackbox-exporter:v0.28.0@sha256:e753ff9f3fc458d02cca5eddab5a77e1c175eee484a8925ac7d524f04366c2fc image: prom/blackbox-exporter:v0.27.0@sha256:a50c4c0eda297baa1678cd4dc4712a67fdea713b832d43ce7fcc5f9bea05094d
container_name: monitoring-blackbox-exporter container_name: monitoring-blackbox-exporter
restart: unless-stopped restart: unless-stopped
dns: dns:
@@ -97,7 +97,7 @@ services:
- no-new-privileges:true - no-new-privileges:true
promtail: promtail:
image: grafana/promtail:3.6.11@sha256:a761cb834cfaeee29745440d4884d6748f0a08d8f68928db1d707018c1dcfbe9 image: grafana/promtail:3.6.10@sha256:2a0f5e3e160ee5d549c585f6cc4f4e1c566ff783324a424bd75bc16503fc660e
container_name: monitoring-promtail container_name: monitoring-promtail
restart: unless-stopped restart: unless-stopped
command: command:
@@ -162,7 +162,7 @@ services:
- traefik.http.services.monitoring-grafana.loadbalancer.server.port=3000 - traefik.http.services.monitoring-grafana.loadbalancer.server.port=3000
grafana-dashboard-importer: grafana-dashboard-importer:
image: python:3.14-alpine image: python:3.13-alpine
container_name: monitoring-grafana-dashboard-importer container_name: monitoring-grafana-dashboard-importer
restart: "no" restart: "no"
profiles: profiles:
@@ -273,7 +273,7 @@ services:
echo "Dashboard import complete." echo "Dashboard import complete."
node-exporter: node-exporter:
image: prom/node-exporter:v1.11.1@sha256:e9cff4fc67b1818f8c97adb115b9f12c9a54b533de86765d4a0effc01b357205 image: prom/node-exporter:v1.9.1@sha256:d00a542e409ee618a4edc67da14dd48c5da66726bbd5537ab2af9c1dfc442c8a
container_name: monitoring-node-exporter container_name: monitoring-node-exporter
restart: unless-stopped restart: unless-stopped
command: command:
@@ -295,7 +295,7 @@ services:
- no-new-privileges:true - no-new-privileges:true
cadvisor: cadvisor:
image: ghcr.io/google/cadvisor:v0.57.0@sha256:e75bdb03b74b0b6995f208f166fead2e6e555dde73e44200113bb26f41b1981d image: ghcr.io/google/cadvisor:v0.53.0@sha256:c3770bd6fc6c6a9cb2b47143e6b3cc3fdd9d20a8453dffbb7e09a145e7e0c4e4
container_name: monitoring-cadvisor container_name: monitoring-cadvisor
restart: unless-stopped restart: unless-stopped
command: command:
@@ -316,7 +316,7 @@ services:
- no-new-privileges:true - no-new-privileges:true
influxdb3-core: influxdb3-core:
image: influxdb:3.9.2-core@sha256:31ad94df2248134989b2cf73d965e51dd5f35dfae22d7ed8f4776b12e6f69f4e image: influxdb:3.9.1-core@sha256:1d58c8b9ac90153ae3a020ede2810c8284933dda50ac71e7573389ab6f012128
container_name: monitoring-influxdb3-core container_name: monitoring-influxdb3-core
user: "0" user: "0"
restart: unless-stopped restart: unless-stopped
-29
View File
@@ -57,15 +57,6 @@ groups:
summary: "Disk usage high on {{ $labels.mountpoint }}" summary: "Disk usage high on {{ $labels.mountpoint }}"
description: "{{ $labels.mountpoint }} is above 85% used." description: "{{ $labels.mountpoint }} is above 85% used."
- alert: HomelabDiskCritical
expr: 100 * (1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) > 95
for: 5m
labels:
severity: critical
annotations:
summary: "Disk critically full on {{ $labels.mountpoint }}"
description: "{{ $labels.mountpoint }} is above 95% used. Writes may start to fail (DB, appdata, cache)."
- alert: HomelabHighMemoryUsage - alert: HomelabHighMemoryUsage
expr: 100 * (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) > 90 expr: 100 * (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) > 90
for: 10m for: 10m
@@ -139,23 +130,3 @@ groups:
annotations: annotations:
summary: "Critical container is down: {{ $labels.name }}" summary: "Critical container is down: {{ $labels.name }}"
description: "The host textfile exporter reports that critical container {{ $labels.name }} is not running." description: "The host textfile exporter reports that critical container {{ $labels.name }} is not running."
- alert: HomelabGitOpsRuntimeImageDrift
expr: homelab_gitops_runtime_image_match == 0
for: 10m
labels:
severity: warning
annotations:
summary: "Runtime image drift: {{ $labels.name }}"
description: "Container {{ $labels.name }} is not running the image declared by its Compose config in project {{ $labels.project }}."
- name: homelab-meta
rules:
- alert: HomelabPrometheusTargetDown
expr: up == 0
for: 5m
labels:
severity: critical
annotations:
summary: "Prometheus target down: {{ $labels.job }} / {{ $labels.instance }}"
description: "Scrape target {{ $labels.instance }} (job {{ $labels.job }}) is unreachable. Metrics from this target are silent — alerts built on them will not fire."
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
borg-ui: borg-ui:
image: ainullcode/borg-ui@sha256:b44c0a92b650d80f215a986dadda5c2604c61eb28a7571e19c046eff41d761e7 image: ainullcode/borg-ui@sha256:867c73983e5bef5491cdee1c34acf85fe8a9fe4f6ad5a9381e7ca2c382359ce6
container_name: borg-ui container_name: borg-ui
restart: unless-stopped restart: unless-stopped
security_opt: security_opt:
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
code-server: code-server:
image: lscr.io/linuxserver/code-server:4.122.0@sha256:0caf1b65ebec84b94397108b56da6c33f124c5390f5832da94e75f4609c0e2ad image: lscr.io/linuxserver/code-server:4.116.0@sha256:4620adace18935dd6ca79d77e3bc1c379e21875392192f970cf5d6b0fb4aefcd
container_name: code-server container_name: code-server
restart: unless-stopped restart: unless-stopped
security_opt: security_opt:
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
filebrowser: filebrowser:
image: filebrowser/filebrowser:v2.63.5@sha256:aefb0c20de10ef8b617995ca5522479ad40d41e6386bd01946a345c6026ff31c image: filebrowser/filebrowser:v2.63.2@sha256:4dce87308b9f9cfbcf8d0a284fc9565d2b515530a6bae2d920b388161e093f26
container_name: filebrowser container_name: filebrowser
restart: unless-stopped restart: unless-stopped
security_opt: security_opt:
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
glance: glance:
image: glanceapp/glance:v0.8.5 image: glanceapp/glance:v0.8.4
container_name: glance container_name: glance
restart: unless-stopped restart: unless-stopped
environment: environment:
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
glances: glances:
image: nicolargo/glances:latest-full@sha256:60872a1af0e40a3150975617c7e811ad7ad48f95bc45d033fb0c1737a037e4d2 image: nicolargo/glances:latest-full@sha256:b4b0f059fa8064a0e8dae5530ce9334834ab07205269cfbf405d16b4d40c0c66
container_name: glances container_name: glances
restart: unless-stopped restart: unless-stopped
pid: host pid: host
+1 -1
View File
@@ -1,4 +1,4 @@
FROM nousresearch/hermes-agent:v2026.5.29 FROM nousresearch/hermes-agent:v2026.4.16
USER root USER root
+3 -3
View File
@@ -4,7 +4,7 @@ services:
# Netz: komodo_net (internal: true) niemals frontend_net # Netz: komodo_net (internal: true) niemals frontend_net
# ────────────────────────────────────────────────────────────────── # ──────────────────────────────────────────────────────────────────
komodo-mongo: komodo-mongo:
image: mongo:7.0.34@sha256:4b5bf3c2bb7516164f6dcb44acce4fdcb428abfe5771a1128304a0f34ab9ff7c image: mongo:7.0.32@sha256:32979a1189dfdc44da3f5ed40d910495f5ad8f6f7f77556646f890a30b2d3f56
container_name: komodo-mongo container_name: komodo-mongo
labels: labels:
komodo.skip: komodo.skip:
@@ -33,7 +33,7 @@ services:
# Admin-Dienst: bewusst ohne pauschale ForwardAuth-Middleware; dokumentierte Ausnahme # Admin-Dienst: bewusst ohne pauschale ForwardAuth-Middleware; dokumentierte Ausnahme
# ────────────────────────────────────────────────────────────────── # ──────────────────────────────────────────────────────────────────
komodo-core: komodo-core:
image: ghcr.io/moghtech/komodo-core:2@sha256:7afbcfa99674bf3f51539ec3aa7235795e9b994af9b7099a6c4c654d5d8a5b6b image: ghcr.io/moghtech/komodo-core:2@sha256:8a7dbba232e4e49797bb412be5f78207c89fcf22cc2727b38631ae30f7518a4c
container_name: komodo-core container_name: komodo-core
init: true init: true
restart: unless-stopped restart: unless-stopped
@@ -79,7 +79,7 @@ services:
# Ausnahme: Docker-Socket ohne :ro (Periphery startet/stoppt Container) # Ausnahme: Docker-Socket ohne :ro (Periphery startet/stoppt Container)
# ────────────────────────────────────────────────────────────────── # ──────────────────────────────────────────────────────────────────
komodo-periphery: komodo-periphery:
image: ghcr.io/moghtech/komodo-periphery:2@sha256:7fb1a4807d125ce036a17d37c940b4001402afcaf342a2c720c98d096b1b54da image: ghcr.io/moghtech/komodo-periphery:2@sha256:8ac9f2ef9c1461b95c862d445da00253005e7094d1e30f5b7b04b8d60ca7a3d6
container_name: komodo-periphery container_name: komodo-periphery
init: true init: true
restart: unless-stopped restart: unless-stopped
-107
View File
@@ -1,107 +0,0 @@
# Memory-Limits Baseline - Vorbereitung F-19
Status: **Vorbereitung**. Echte `mem_limit`-Werte werden erst gesetzt, wenn mindestens 7 Tage realer Peak-Werte vorliegen.
Bezug: `docs/AUDIT_2026-05-25.md` F-19 "Keine Container-Memory-Limits".
## Warum nicht heute
Audit-TODO 2026-05-30: F-19 ist nicht akut. Im `docs/MIGRATION_LOG.md` ist **kein einziger** OOM-/Memory-Vorfall dokumentiert. `services/posture-check/docker-critical-events.sh` ueberwacht `die`/`oom`/`kill`-Events und alarmiert via ntfy — der Detektions-Pfad ist da, der Daten-Befund fehlt. Limits ohne Peak-Daten zu setzen bedeutet entweder zu eng (Flapping) oder so weit weg vom Realwert, dass die Schutzwirkung gegen Null geht.
Familien-Einladung verschiebt die Risiko-Bilanz nach oben: Ein OOM in Authelia/Postgres bei Familien-Nutzung kostet Vertrauen, nicht nur Operator-Zeit. Sobald die Einladung raus ist, wird F-19 ein "should" statt "nice".
## Plan
### Phase 1 - Peak-Beobachtung (7 Tage)
Auf dem Host stuendlich `docker stats --no-stream` snappen und in eine Textfile pro Container schreiben. Beispiel-Snippet fuer das Cron-Skript:
```bash
#!/usr/bin/env bash
# /boot/config/plugins/user.scripts/scripts/docker-stats-snapshot/script
set -euo pipefail
OUT="/mnt/user/services/policy-checks/docker-stats-$(date +%Y%m%d).log"
mkdir -p "$(dirname "$OUT")"
{
echo "=== $(date -Iseconds) ==="
docker stats --no-stream --format 'table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.CPUPerc}}'
} >> "$OUT"
```
Cron: stuendlich (`0 * * * *`), 7 Tage laufen lassen.
### Phase 2 - Peak-Auswertung
Pro Tier-1-Container das Maximum `MemUsage` aus dem 7-Tage-Log ableiten:
```bash
grep -E '^postgresql17|^authelia|^Redis|^vaultwarden|^gitea|^traefik|^komodo-mongo' \
/mnt/user/services/policy-checks/docker-stats-*.log \
| awk -F'\t' '{print $1, $2}' \
| sort -u
```
Erwartete Groessenordnungen (zur Plausibilitaetspruefung, nicht zur Festlegung):
| Container | Erwartung |
|---|---|
| postgresql17 | 200-600 MB |
| Redis | 30-80 MB |
| authelia | 50-150 MB |
| vaultwarden | 100-300 MB |
| gitea | 200-500 MB |
| traefik | 80-200 MB |
| komodo-mongo | 300-800 MB |
### Phase 3 - Limit-Setting
Pro Tier-1-Container:
```yaml
deploy:
resources:
limits:
memory: <peak * 1.5, mindestens floor>
```
Floor-Werte:
- postgresql17: 1G (Cache-Verhalten leidet bei weniger)
- komodo-mongo: 1G (WiredTiger braucht Working-Set)
- Redis: 256M (Paperless-Cache)
- vaultwarden: 256M
- gitea: 512M
- traefik: 256M
- authelia: 256M
`mem_reservation` bewusst nicht setzen — auf einem Single-Host-Setup ist Reservation Theater.
### Phase 4 - Rollout-Reihenfolge
1. Redis und authelia zuerst (kleinste Risiko-Container, klares Memory-Profil).
2. Wenn nach 48 h kein Flapping: traefik, vaultwarden, gitea.
3. Zuletzt postgresql17 und komodo-mongo, weil DB-Limits bei zu engem Setting Performance kippen.
Jede Stufe einzeln committen und 24 h beobachten.
### Phase 5 - Tier-2 (optional)
Tier-2 (Immich, Nextcloud, Paperless, Mealie, Mail-Archiver) bewusst spaeter, nur wenn ein konkreter Vorfall das rechtfertigt. Immich-ML ist der wahrscheinlichste Kandidat fuer den ersten echten OOM-Vorfall, deshalb dort zuerst beobachten, dann limitieren.
## Stop-Regel
Falls in Phase 3 ein Container nach Limit-Setzung haeufiger restartet als vor dem Limit: Limit raus, kein zweiter Versuch ohne dazwischenliegende Peak-Reanalyse. Doku-Eintrag in `docs/MIGRATION_LOG.md`, F-19 weiter offen.
## Was nicht ins Skript gehoert
- Mem-Limits sind kein Tuning, kein Performance-Hebel. Wer sich Performance erhofft, hat das falsche Werkzeug.
- Postgres-`shared_buffers` und `effective_cache_size` muessen zur Limit-Groesse passen. Setzen ohne Postgres-internes Tuning macht die DB langsamer, nicht stabiler.
- Komodo-Mongo waechst mit Stack-/Update-Historie. Limit fuer naechste 12 Monate planen, nicht fuer den heutigen Stand.
## Naechster Trigger
- Familien-Einladung raus, 4 Wochen stabile Nutzung, **oder**
- erster echter OOM-Vorfall im `docker-critical-events.sh`-Log, **oder**
- ein Immich/Nextcloud-Last-Sprung (z.B. grosses Foto-Backup), bei dem Host-Swap sichtbar wird.
Bei einem dieser Trigger: Phase 1 starten.
+1 -1
View File
@@ -2,7 +2,7 @@
## Status ## Status
Skript und Test-Compose sind vorbereitet. **Erstlauf 2026-05-27 erfolgreich** (`SUCCESS`, HTTP `200`, `11977` Assets im Test-DB-Check). Report: `/mnt/user/backups/restore-reports/immich-2026-05-27.md`. Folgelaeufe je Quartal gemaess `docs/RESTORE_DRILL_ROUTINE.md` (Q2 = Immich). Skript und Test-Compose sind vorbereitet. **Erster echter Lauf steht noch aus.**
Vor dem ersten Lauf muss Operator entscheiden: Vor dem ersten Lauf muss Operator entscheiden:
+4 -10
View File
@@ -75,14 +75,8 @@ Optional spaeter:
| Volume-Reste | unterbrochener Lauf laesst Wegwerf-Datadir liegen | Skript loescht Restore-Lab vor jedem Lauf; `--keep-data` ueberschreibt das bewusst | | Volume-Reste | unterbrochener Lauf laesst Wegwerf-Datadir liegen | Skript loescht Restore-Lab vor jedem Lauf; `--keep-data` ueberschreibt das bewusst |
| Periphery-Erreichbarkeit | Core sucht Periphery initial nicht aktiv | Test prueft nur Periphery `State.Status=running`; voller Handshake ist optional | | Periphery-Erreichbarkeit | Core sucht Periphery initial nicht aktiv | Test prueft nur Periphery `State.Status=running`; voller Handshake ist optional |
## Bestaetigte Laeufe ## Noch offen vor dem ersten echten Lauf
| Datum | Mode | Ergebnis | Report | - Erstlauf mit `--what-if` zur Plan-Verifikation
|---|---|---|---| - Erstlauf mit `--keep-data` zur Zeitmessung
| 2026-05-30 | `--what-if` | Plan-Ausgabe wie erwartet | (kein Report, nur stdout) | - Bei Erfolg `docs/RESTORE_DRILL_ROUTINE.md` Quartals-Belegung pruefen (Q2 ist bereits Immich; Komodo passt eher zu Q4 oder zum quartalsweisen DR-Sanity-Check)
| 2026-05-30 | `--keep-data` | `SUCCESS`, 5/5 Checks gruen, Core HTTP `200`, Mongo healthy in ~6 s | `/mnt/user/backups/restore-reports/komodo-bootstrap-2026-05-30.md` |
## Folgeschritte
- Quartals-Belegung: Komodo-Bootstrap passt zum DR-Sanity-Check (`docs/RESTORE_DRILL_ROUTINE.md` Q2/Q4) und kann ohne Borg-Archiv jederzeit wiederholt werden.
- Optional fuer kuenftige Laeufe: echtes Restore aus `komodo-mongo.archive.gz` in die Test-Mongo, danach Schreiben einer Wegwerf-Resource ueber die API.
@@ -2,7 +2,7 @@
## Status ## Status
Skript und Test-Compose sind vorbereitet. **Erstlauf 2026-05-30 erfolgreich** (`SUCCESS`, alle 5 Checks gruen, Komodo Core HTTP `200`). Report: `/mnt/user/backups/restore-reports/komodo-bootstrap-2026-05-30.md`. Folgelaeufe quartalsweise empfohlen als Teil des DR-Sanity-Checks (`docs/RESTORE_DRILL_ROUTINE.md`). Skript und Test-Compose sind vorbereitet. Erster echter Lauf steht noch aus.
## Vorbedingungen ## Vorbedingungen
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
scrutiny: scrutiny:
image: ghcr.io/starosdev/scrutiny:latest-omnibus@sha256:a79cd67878a797bc9412e9a9a3e330cd1062f78bd98dc28e2654e655196b8743 image: ghcr.io/starosdev/scrutiny:latest-omnibus@sha256:9f77acf1a567802bbefe0f0e7510cb2ecc20d319276cf183512c7e843214abd8
container_name: scrutiny container_name: scrutiny
restart: unless-stopped restart: unless-stopped
privileged: true privileged: true
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
speedtest-tracker: speedtest-tracker:
image: lscr.io/linuxserver/speedtest-tracker:1.14.3@sha256:79c00631575dec6d91c10ed904c211224f00813013a305c2284324e195a538bb image: lscr.io/linuxserver/speedtest-tracker:1.13.12@sha256:eb3d249f16177964daa4fff7f6a90bbf6645f4e23158d92f5cddb133728d0804
container_name: speedtest-tracker container_name: speedtest-tracker
restart: unless-stopped restart: unless-stopped
security_opt: security_opt:
+1 -1
View File
@@ -2,7 +2,7 @@ name: authelia
services: services:
authelia: authelia:
container_name: authelia container_name: authelia
image: authelia/authelia:4.39.20@sha256:1b363e9279e742397966333f364e0876ae02bf5c876de73e83af6d48c57ff51b image: authelia/authelia:4.39.19@sha256:0c824dcab1ae97c56bf673c5e77fe8cc6bcd400564555140cc8002a12c6b6463
restart: unless-stopped restart: unless-stopped
environment: environment:
AUTHELIA_JWT_SECRET_FILE: /secrets/jwt_secret.txt AUTHELIA_JWT_SECRET_FILE: /secrets/jwt_secret.txt
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
vaultwarden: vaultwarden:
image: vaultwarden/server:1.36.0@sha256:d626d04934cd1192ad8ced1adb975099fca78cec33ab467d2d3c923cde7f3b0c image: vaultwarden/server:1.35.7@sha256:9a8eec71f4a52411cc43edc7a50f33e9b6f62b5baca0dd95f0c6e7fd60f1a341
container_name: vaultwarden container_name: vaultwarden
restart: unless-stopped restart: unless-stopped
-121
View File
@@ -1,121 +0,0 @@
#!/usr/bin/env bash
# Vergleicht die Repo-Baseline der Authelia-Konfiguration gegen die produktive
# Host-Datei. Bewusst nur fuer Sektionen, die laut Repo-Konvention auf Host
# und Repo identisch sein muessen (Default: access_control). OIDC-Clients,
# identity_providers und Secret-Werte bleiben hostseitig und werden nicht
# verglichen.
#
# Aufruf-Defaults siehe Variablen unten. Aufruf typischerweise:
# bash services/authelia-diff.sh
#
# Exit-Codes:
# 0 alle verglichenen Sektionen identisch
# 1 Drift festgestellt (Diff wird auf stdout ausgegeben)
# 2 Repo-Baseline oder Host-Datei fehlt
# 3 Sektion in mindestens einer Datei nicht gefunden
# 4 internes Werkzeug fehlt (awk/diff)
set -uo pipefail
AUTHELIA_REPO_BASELINE="${AUTHELIA_REPO_BASELINE:-/mnt/user/services/homelab-infra/security/authelia/configuration.yml}"
AUTHELIA_HOST_CONFIG="${AUTHELIA_HOST_CONFIG:-/mnt/user/appdata/authelia/config/configuration.yml}"
AUTHELIA_DIFF_SECTIONS="${AUTHELIA_DIFF_SECTIONS:-access_control}"
for cmd in awk diff; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "authelia-diff: missing required command '$cmd'" >&2
exit 4
fi
done
if [ ! -f "$AUTHELIA_REPO_BASELINE" ]; then
echo "authelia-diff: repo baseline not found: $AUTHELIA_REPO_BASELINE" >&2
exit 2
fi
if [ ! -f "$AUTHELIA_HOST_CONFIG" ]; then
echo "authelia-diff: host config not found: $AUTHELIA_HOST_CONFIG" >&2
exit 2
fi
# Extrahiert einen Top-Level-Block aus einer YAML-Datei.
# Block-Anfang: Zeile, die mit "<section>:" beginnt (kein Whitespace davor).
# Block-Ende: naechste Top-Level-Key-Zeile (`^[A-Za-z_][A-Za-z0-9_]*:`).
# Eingaberauschen wird entfernt: reine Kommentarzeilen, trailing whitespace,
# Leerzeilen.
extract_section() {
local file="$1"
local section="$2"
awk -v section="$section" '
BEGIN { inside = 0; found = 0 }
{
line = $0
sub(/[[:space:]]+$/, "", line)
}
# Top-Level-Key entdeckt
/^[A-Za-z_][A-Za-z0-9_]*:/ {
key = line
sub(/:.*$/, "", key)
if (key == section) {
inside = 1
found = 1
print line
next
} else if (inside == 1) {
inside = 0
}
}
inside == 1 {
# Kommentar- und Leerzeilen ignorieren
if (line ~ /^[[:space:]]*#/) next
if (line ~ /^[[:space:]]*$/) next
print line
}
END {
if (!found) exit 10
}
' "$file"
}
tmpdir="$(mktemp -d -t authelia-diff.XXXXXX)"
trap 'rm -rf "$tmpdir"' EXIT
overall_status=0
diff_output=""
missing_sections=""
IFS=',' read -r -a sections <<< "$AUTHELIA_DIFF_SECTIONS"
for section in "${sections[@]}"; do
section="${section// /}"
[ -z "$section" ] && continue
repo_file="$tmpdir/repo.$section"
host_file="$tmpdir/host.$section"
if ! extract_section "$AUTHELIA_REPO_BASELINE" "$section" > "$repo_file" 2>/dev/null; then
missing_sections="${missing_sections}${missing_sections:+, }$section (repo)"
continue
fi
if ! extract_section "$AUTHELIA_HOST_CONFIG" "$section" > "$host_file" 2>/dev/null; then
missing_sections="${missing_sections}${missing_sections:+, }$section (host)"
continue
fi
if ! diff_chunk="$(diff -u \
--label "repo:$section" "$repo_file" \
--label "host:$section" "$host_file")"; then
overall_status=1
diff_output="${diff_output}${diff_chunk}"$'\n'
fi
done
if [ -n "$missing_sections" ] && [ "$overall_status" -eq 0 ]; then
echo "authelia-diff: sections missing: $missing_sections" >&2
exit 3
fi
if [ "$overall_status" -ne 0 ]; then
printf '%s' "$diff_output"
exit 1
fi
exit 0
@@ -15,51 +15,6 @@ trap cleanup EXIT
now="$(date +%s)" now="$(date +%s)"
emit_gitops_runtime_image_metrics() {
cat <<'EOF'
# HELP homelab_gitops_runtime_image_match Whether a running Compose container uses the image currently declared by its Compose config.
# TYPE homelab_gitops_runtime_image_match gauge
EOF
if ! command -v jq >/dev/null 2>&1; then
return
fi
docker ps \
--filter label=com.docker.compose.project \
--format '{{.Names}}\t{{.Label "com.docker.compose.project"}}\t{{.Label "com.docker.compose.service"}}\t{{.Label "com.docker.compose.project.config_files"}}\t{{.Label "com.docker.compose.project.environment_file"}}' |
while IFS="$(printf '\t')" read -r container project service config_files env_file; do
[ -n "$container" ] || continue
[ -n "$service" ] || continue
[ -n "$config_files" ] || continue
config_file="${config_files%%,*}"
[ -f "$config_file" ] || continue
compose_args=(-f "$config_file")
if [ -n "$env_file" ] && [ -f "$env_file" ]; then
compose_args+=(--env-file "$env_file")
fi
expected="$(
docker compose "${compose_args[@]}" config --format json 2>/dev/null |
jq -r --arg service "$service" '.services[$service].image // empty' 2>/dev/null || true
)"
[ -n "$expected" ] || continue
running="$(docker inspect -f '{{.Config.Image}}' "$container" 2>/dev/null || true)"
[ -n "$running" ] || continue
match="0"
if [ "$running" = "$expected" ]; then
match="1"
fi
printf 'homelab_gitops_runtime_image_match{name="%s",project="%s",service="%s"} %s\n' \
"$container" "$project" "$service" "$match"
done
}
{ {
cat <<'EOF' cat <<'EOF'
# HELP homelab_textfile_exporter_last_run_timestamp_seconds Unix timestamp of the last successful homelab textfile exporter run. # HELP homelab_textfile_exporter_last_run_timestamp_seconds Unix timestamp of the last successful homelab textfile exporter run.
@@ -79,8 +34,6 @@ EOF
printf 'homelab_critical_container_running{name="%s"} %s\n' "$container" "$running" printf 'homelab_critical_container_running{name="%s"} %s\n' "$container" "$running"
done done
emit_gitops_runtime_image_metrics
cat <<'EOF' cat <<'EOF'
# HELP homelab_borg_last_completed_timestamp_seconds Unix timestamp of the most recent completed Borg backup job known to Borg UI. # HELP homelab_borg_last_completed_timestamp_seconds Unix timestamp of the most recent completed Borg backup job known to Borg UI.
# TYPE homelab_borg_last_completed_timestamp_seconds gauge # TYPE homelab_borg_last_completed_timestamp_seconds gauge
-38
View File
@@ -10,8 +10,6 @@ TMP_DIR="${TMP_DIR:-/tmp/kallilab-posture-check}"
ALLOW_DISK1_NTFS="${ALLOW_DISK1_NTFS:-0}" ALLOW_DISK1_NTFS="${ALLOW_DISK1_NTFS:-0}"
ALERT_STATE_PATH="${ALERT_STATE_PATH:-/mnt/user/services/posture-check/last-alert.state}" ALERT_STATE_PATH="${ALERT_STATE_PATH:-/mnt/user/services/posture-check/last-alert.state}"
ALERT_REPEAT_SECONDS="${ALERT_REPEAT_SECONDS:-86400}" ALERT_REPEAT_SECONDS="${ALERT_REPEAT_SECONDS:-86400}"
SKIP_AUTHELIA_DRIFT="${SKIP_AUTHELIA_DRIFT:-0}"
AUTHELIA_DIFF_SCRIPT="${AUTHELIA_DIFF_SCRIPT:-/mnt/user/services/homelab-infra/services/authelia-diff.sh}"
mkdir -p "$TMP_DIR" mkdir -p "$TMP_DIR"
RESULTS_FILE="$TMP_DIR/results.$$" RESULTS_FILE="$TMP_DIR/results.$$"
@@ -221,41 +219,6 @@ check_nvme_smart() {
fi fi
} }
check_authelia_config_drift() {
if [ "$SKIP_AUTHELIA_DRIFT" = "1" ]; then
add_result "ok" "authelia_config_drift" "Authelia drift check skipped via SKIP_AUTHELIA_DRIFT=1"
return
fi
if [ ! -x "$AUTHELIA_DIFF_SCRIPT" ] && [ ! -f "$AUTHELIA_DIFF_SCRIPT" ]; then
add_result "warning" "authelia_config_drift" "Authelia diff script missing: $AUTHELIA_DIFF_SCRIPT"
return
fi
local output
local rc
output="$(bash "$AUTHELIA_DIFF_SCRIPT" 2>&1)"
rc=$?
case "$rc" in
0)
add_result "ok" "authelia_config_drift" "Authelia repo baseline matches host config (access_control)"
;;
1)
add_result "warning" "authelia_config_drift" "Authelia repo<->host drift in access_control; run authelia-diff.sh for details"
;;
2)
add_result "warning" "authelia_config_drift" "Authelia diff aborted: $output"
;;
3)
add_result "warning" "authelia_config_drift" "Authelia diff: section missing in repo or host: $output"
;;
*)
add_result "warning" "authelia_config_drift" "Authelia diff returned unexpected rc=$rc: $output"
;;
esac
}
send_ntfy() { send_ntfy() {
local severity="$1" local severity="$1"
local topic="$2" local topic="$2"
@@ -425,7 +388,6 @@ main() {
done done
check_nvme_smart check_nvme_smart
check_authelia_config_drift
write_json write_json
} }
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
traefik: traefik:
image: traefik:v3.7@sha256:6b9cbca6fac42ab0075f5437d8dc1685cfd188626d8d515839ea94f8b6271c42 image: traefik:v3.6@sha256:8cb20d16e01a53d8d7f7696ac2f1af7d200d5c9984d226ce2299731d9eab6d6c
container_name: traefik container_name: traefik
restart: unless-stopped restart: unless-stopped
security_opt: security_opt: