135 Commits

Author SHA1 Message Date
Micha f77a69a0b2 Record audit baseline tag 2026-05-26 19:44:50 +02:00
Micha f73cf48e41 Document external recovery dependencies 2026-05-26 19:44:14 +02:00
Micha eea2697ca1 Triage policy check warnings 2026-05-26 19:42:01 +02:00
Micha a3d77d7529 Document hardware capacity baseline 2026-05-26 19:39:42 +02:00
Micha 02a50e1a58 Record Komodo metadata fix 2026-05-26 19:32:26 +02:00
Micha 267e76059a Clean up Komodo webhook drift 2026-05-26 19:29:51 +02:00
Micha 9d4fee02ca Record next audit handoff 2026-05-26 15:39:41 +02:00
Micha 24ebcaa3c7 Document Nextcloud webhook refresh 2026-05-26 15:34:43 +02:00
Micha 45bae13aa0 Remove legacy monitoring stacks 2026-05-26 15:27:37 +02:00
Micha ff5991cec8 Document AdGuard webhook diagnosis 2026-05-26 15:15:34 +02:00
Micha 5b6e7b8b66 Record AdGuard Tailscale validation 2026-05-26 15:00:35 +02:00
Micha 5cb401797d Bind AdGuard admin to Tailscale 2026-05-26 14:55:49 +02:00
Micha 1d0cba92bd Record Unraid flash backup live evidence 2026-05-25 19:49:38 +02:00
Micha 9353a9fc44 Fix Borg preflight freshness dump path 2026-05-25 19:44:22 +02:00
Micha d50b11784d Add Unraid flash config to Borg preflight 2026-05-25 19:36:16 +02:00
Micha 09eeac51e1 Record legacy grafana hook disablement 2026-05-25 16:51:14 +02:00
Micha 565940b9ef Record monitoring migration completion 2026-05-25 16:47:03 +02:00
Micha b6bbca43ad Replace Uptime Kuma with monitoring checks 2026-05-25 16:37:46 +02:00
Micha 388e57e385 Document AdGuard LAN admin decision 2026-05-25 16:27:03 +02:00
Micha 0c2bb8484a Record Homepage live removal evidence 2026-05-25 14:52:18 +02:00
Micha a7797fd02e Consolidate dashboard on Glance 2026-05-25 14:44:46 +02:00
Micha bac927bbcc Record Jellyfin live removal evidence 2026-05-25 12:15:08 +02:00
Micha add8b71ea9 Remove Jellyfin from homelab target state 2026-05-25 11:57:00 +02:00
Micha e21e89e51b Document Borg passphrase host secret 2026-05-25 11:38:03 +02:00
Micha 4e4684b616 Document external GitHub mirror 2026-05-25 11:27:28 +02:00
Micha 84030956ac Fix Gitea external DNS for GitHub mirror 2026-05-25 11:17:31 +02:00
Micha 17fe8073bb Allow GitHub mirror target for Gitea 2026-05-25 10:56:04 +02:00
Micha 9f32ba72c1 Make audit final runtime wording stable 2026-05-25 07:37:28 +02:00
Micha e9a7f79025 Clarify audit doc-only deployment state 2026-05-25 07:36:03 +02:00
Micha 43727151df Refresh final audit live status 2026-05-25 07:34:08 +02:00
Micha 66ee10cb55 Clarify Disk1 parity follow-up 2026-05-25 06:17:13 +02:00
Micha ab68900216 Complete Disk1 phase 2 migration 2026-05-25 06:13:50 +02:00
Micha 8f56c6edcd Document Disk1 phase 2 backup readiness 2026-05-24 13:07:45 +02:00
Micha 8e400fb3c3 Finalize homelab audit end state 2026-05-23 11:29:08 +02:00
Micha cd650b19ac Close Gitea signup, dedup posture-check alerts, extend Borg scope
Operational hardening across several services after live incident
analysis between 2026-05-18 and 2026-05-20:

- Gitea: disable public registration and OpenID signup/signin to
  stop the external POST / 5xx bursts that triggered availability
  alerts. New repo-wide policy requires every productive
  Micha/homelab-infra Komodo stack to ship with an active
  Gitea->Komodo webhook on the current stack ID (documented in
  CLAUDE.md, AI_CONTEXT.md, WORKFLOW.md).
- posture-check: extract the Disk1 fstype check into its own
  function so the documented Disk1 NTFS exception no longer raises
  ntfy warnings, skip POSIX inode checks on NTFS, and dedup ntfy
  alerts via a fingerprint state file with ALERT_REPEAT_SECONDS
  (default 24h). Repeat-spam on the same cause now suppressed.
- docker-critical-events: parse the event JSON for container name,
  action, exit code and signal; drop `die exit=0` events (clean
  stops); ship a structured ntfy message instead of the raw event
  line.
- Borg UI: mount /mnt/user/services into the backup container as
  /local/services:ro and include homelab-infra, stacks and
  posture-check in all-important-sources.txt. RESTORE_MATRIX and
  DISASTER_RECOVERY updated accordingly.
- Unraid user scripts: document the new
  homelab-operations-report-daily cron job and the SMTP password
  file it expects on the host.
- MIGRATION_LOG: capture the four live events from this window -
  Gitea 5xx burst + signup closure, Komodo webhook reconciliation,
  posture-check host-version verification, Borg scope extension,
  and Traefik 5xx alert detuning.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 11:05:35 +02:00
Codex af231dd4e8 Fix zero-count noise pattern handling 2026-05-23 11:03:02 +02:00
Micha 428223d2e4 Mark posture report scripts executable 2026-05-23 11:00:40 +02:00
Micha b6d3ed4832 Tune homelab availability alerts 2026-05-23 10:58:12 +02:00
Micha 9e7bebbd3c Add daily operations report with hardened log-noise filtering
Brings the previously untracked daily-status-report.sh and
send-operations-report-mail.sh into the repo, plus a refactor of the
log-noise pipeline:

- New helper services/posture-check/lib/normalize-noise-patterns.sh
  strips comments, empty lines and trailing whitespace from
  log-noise.patterns before grep -f sees it. A stray empty line in
  the pattern file would otherwise have made grep -Eaif match every
  hit and silently wipe the log highlights.
- log-noise.patterns is now documented per-pattern (Why / Re-check).
  The Vaultwarden pattern is split: token/session noise stays as
  noise; DNS/Connect/Resolve/reqwest/hyper errors are removed from
  the noise set so real network signals stay visible.
- collect_log_highlights now reports a per-container and per-pattern
  noise breakdown (Top N) and an escalation flag when any pattern
  exceeds NOISE_ESCALATION_THRESHOLD (default 500). The flag is fed
  into derive_report_status and the management summary.
- New shell tests under services/posture-check/tests/ verify the
  normalize helper handles comments, empty lines, whitespace-only
  lines, and that unknown error lines remain in the attention set.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 10:41:33 +02:00
Micha b7cbbe51de Fix Jellyfin external DNS 2026-05-18 20:29:18 +02:00
Micha 71ac18b21c Fix Jellyfin native auth routing 2026-05-18 13:43:41 +02:00
Micha 90f270be96 Fix Jellyfin config permissions 2026-05-18 13:21:30 +02:00
Micha e28f8dabec Add Jellyfin media server stack 2026-05-18 13:09:32 +02:00
Micha edfec5b66d Add Plex media server stack 2026-05-18 13:09:27 +02:00
Micha 59bec9ac77 Fix Glance live widget data sources 2026-05-18 09:35:53 +02:00
Micha 9f86da708a Add Glance live network widgets 2026-05-18 08:31:57 +02:00
Micha d6170211c4 Refine Glance network widgets 2026-05-18 08:13:13 +02:00
Micha fb681086f3 Restyle Glance dashboard layout 2026-05-18 08:03:59 +02:00
Micha 5b101f3b3d Keep only verified Glance community widget 2026-05-17 18:20:56 +02:00
Micha 669efbd57e Fix Glance Speedtest subrequest headers 2026-05-17 18:18:14 +02:00
Micha 2dd5590a2a Polish Glance community widgets 2026-05-17 18:16:05 +02:00
Micha 175cd6951f Add Glance community homelab widgets 2026-05-17 18:07:57 +02:00
Micha aeb7573b03 Remove noisy Glance dashboard widgets 2026-05-17 17:08:10 +02:00
Micha 215f44b962 Fix Glance monitor health checks 2026-05-17 17:05:23 +02:00
Micha 6ce625f77a Fix Glance socket proxy image tag 2026-05-17 16:59:29 +02:00
Micha c3c8060ddf Add Glance homelab dashboard stack 2026-05-17 16:51:43 +02:00
Micha 29eaf8001f Normalize ntfy alert routing 2026-05-17 14:57:45 +02:00
Micha db7dc3f2af Add ntfy alert delivery for monitoring 2026-05-17 11:34:19 +02:00
Micha c748236886 Prune monitoring dashboard imports 2026-05-17 11:30:00 +02:00
Micha 8aa850df40 Set Grafana DNS resolvers 2026-05-17 11:26:27 +02:00
Micha 2c4854f628 Accept protected HTTP checks in blackbox 2026-05-17 11:25:35 +02:00
Micha b7050812d4 Fix blackbox DNS resolution 2026-05-17 11:24:20 +02:00
Micha c95fa601f0 Add monitoring replacement baseline 2026-05-17 11:22:38 +02:00
Micha 0c308ff352 Preserve InfluxDB data in monitoring stack 2026-05-17 10:47:57 +02:00
Micha 53216e50c1 Fix monitoring InfluxDB volume permissions 2026-05-17 10:45:32 +02:00
Micha b7dfdad621 Consolidate monitoring target stack 2026-05-17 10:41:29 +02:00
Micha 61625a7a1c ops: keep monitoring importer running for komodo 2026-05-16 22:39:09 +02:00
Micha 6e28ea94d2 ops: wire monitoring stack to traefik metrics 2026-05-16 22:10:43 +02:00
Micha 58eb53a6a8 ops: add monitoring compose stack 2026-05-16 21:59:20 +02:00
Micha d345d770c2 docs: add homelab audit report 2026-05-16 21:51:48 +02:00
Micha 2e136d9060 Update current homelab rest list 2026-05-16 20:30:31 +02:00
Micha 6ca829ec45 Document Unraid automation schedules 2026-05-16 20:11:19 +02:00
Micha ef3b546d30 Align documentation consistency fixes 2026-05-16 20:04:46 +02:00
Micha 6f684fb4e3 Document Unraid native log rotation 2026-05-16 19:31:54 +02:00
Micha 0adddb6533 Add Unraid automation script templates 2026-05-16 14:34:35 +02:00
Micha 162421e537 Harden Gitea webhooks and Docker log limits 2026-05-16 13:34:45 +02:00
Micha bf30240217 Remove Loki image-internal healthcheck 2026-05-16 13:32:02 +02:00
Micha 5f7940aa01 Tune Loki host bootstrap settings 2026-05-16 13:31:08 +02:00
Micha a5add937f8 Add Loki Alloy logging baseline 2026-05-16 13:26:49 +02:00
Micha 5ada1ad153 Treat Filebrowser state as file-backed dump 2026-05-16 13:16:01 +02:00
Micha ead7e1e17d Fallback SQLite dumps to host paths 2026-05-16 13:14:10 +02:00
Micha 14e9c0963d Allow posture warnings before Borg 2026-05-16 13:12:47 +02:00
Micha 23262cd7b9 Allow Disk1 NTFS posture transition 2026-05-16 13:12:19 +02:00
Micha 878ad2d5f1 Harden backup and posture checks 2026-05-16 13:04:22 +02:00
Micha 12a87ad342 Clean up SQLite dump temp files on failure 2026-05-16 12:03:10 +02:00
Micha 0e7e639df4 Correct Filebrowser backup state 2026-05-16 11:59:57 +02:00
Micha 18df2d155d Add consistent Borg database dumps 2026-05-16 11:49:36 +02:00
Micha fa177155e6 Document final restore service secrets 2026-05-16 10:55:42 +02:00
Micha 11c863c8aa Set explicit DNS for Grafana 2026-05-16 10:41:06 +02:00
Micha dd9f677779 Use LSIO file secret for code-server password 2026-05-16 10:37:25 +02:00
Micha a9e62ee8e5 Document restore exceptions and layout fixes 2026-05-16 07:43:46 +02:00
Micha 4e7d313a20 Pin restored auth stack image tags 2026-05-15 18:18:32 +02:00
Micha 57ea7507a7 Remove Backrest and WD backup references 2026-05-15 16:57:42 +02:00
Micha 1e3e019f28 Update STORAGE_LAYOUT.draft.md
docs: storage-layout v1.3 als bindendes Architektur-Dokument

Erstfassung des verbindlichen Storage-Layouts nach NTFS-Cache-Vorfall
2026-05-11. Definiert Pool-/Share-Konfiguration, Appdata-Struktur,
Backup-Architektur (Borg only, kein Backrest, kein WD-MyBookLive),
Posture-Check-Pflichten, Hard Rules und Migrationspfade.

Operator-Entscheidungen v1.0 -> v1.3 eingearbeitet. Sechs der elf
Open Items entschieden, eines deferred (Disk-Groessen via Posture-Check),
drei als Folge-Aufgaben (Retention, Schwellen-Kalibrierung,
RESTORE_MATRIX-Klassifikation).

Refs: Incident 2026-05-11 NTFS-Cache-Korruption
2026-05-15 16:10:55 +02:00
Micha 54fd0c3347 diverse Änderungen 2026-05-15 16:05:34 +02:00
Micha d7e1eb33ba Improve restore job ntfy timeout and output 2026-05-07 11:34:50 +02:00
Micha 008ab9bc4a Add ntfy wrapper for restore jobs 2026-05-07 11:26:15 +02:00
Micha 7ff7284f6b Add host-ready restore automation scripts 2026-05-07 11:20:03 +02:00
Micha d20b687211 Add restore handbook and Unraid job guide 2026-05-07 11:11:36 +02:00
Micha 16416d964f Add restore test automation scaffolding 2026-05-07 11:07:46 +02:00
Micha 2cc39c73f6 Add validated Paperless restore test pattern 2026-05-07 11:01:27 +02:00
Micha d351b1cac8 Add validated Gitea restore test pattern 2026-05-07 10:00:58 +02:00
Micha df4d335907 Document validated Vaultwarden restore pattern 2026-05-07 09:39:29 +02:00
Micha 7161da00b3 hermes infos
hermes infos
2026-05-06 20:26:29 +02:00
Micha aded9a9cbc update
update
2026-05-06 20:22:14 +02:00
Micha 5cc0a4dadb update
update
2026-05-06 20:18:25 +02:00
Micha 84020346bc hermes update
hermes next level
2026-05-06 20:13:48 +02:00
Micha 1dc1c1ef17 Add restore test scaffolding for Vaultwarden 2026-05-06 20:13:30 +02:00
Micha 7c50e69b44 Add manual repo policy checks
Add manual repo policy checks
2026-05-06 19:36:01 +02:00
Micha 0aa8138bdd hermes update
hermes update
2026-05-06 19:13:52 +02:00
Micha bdef0afcb9 Add AI handoff summary 2026-05-06 19:07:45 +02:00
Micha e0e12f1173 Document stale Komodo webhook cleanup 2026-05-06 18:55:56 +02:00
Micha 403b5fa77c Clarify Komodo webhook secret handling 2026-05-06 18:53:47 +02:00
Micha 9b4d37ca81 Split Komodo webhook secret 2026-05-06 18:50:14 +02:00
Micha 014e51fd67 Configure Authelia GMX SMTP notifier 2026-05-06 18:41:24 +02:00
Micha f94a55e093 Protect mail archiver and document Hermes restore 2026-05-06 18:23:01 +02:00
Micha 8f3c03f396 Fix invalid image digest pins 2026-05-05 21:02:24 +02:00
Micha bdba76cebc Clean up compose metadata and placeholders 2026-05-05 20:16:48 +02:00
Micha 78b9a6e362 Pin versioned app image digests 2026-05-05 20:13:49 +02:00
Micha 374a3198ed Document ops exceptions and Hermes route 2026-05-05 19:42:42 +02:00
Micha 986d8dd3f5 Pin stateful service image digests 2026-05-05 19:33:28 +02:00
Micha 1acd4c6830 docs(borg): backup scope mit nextcloud, grafana, influxdb, hermes, backrest, bentopdf abgeglichen; portainer altlast entfernt; offene decisions explizit gemacht
docs(borg): backup scope mit nextcloud, grafana, influxdb, hermes, backrest, bentopdf abgeglichen; portainer altlast entfernt; offene decisions explizit gemacht
2026-05-04 20:43:48 +02:00
Micha d6e686ae80 Document Authelia host ACL merge 2026-05-04 20:27:07 +02:00
Micha b1fbb310fb Document Komodo self-stack drift recovery 2026-05-04 20:21:23 +02:00
Micha f858da484b Clarify Authelia config source and ACLs
Clarify Authelia config source and ACLs
2026-05-04 19:57:45 +02:00
Micha 197454931f Ignore local env files and template Hermes stack env
Ignore local env files and template Hermes stack env
2026-05-04 19:54:28 +02:00
Micha bcb2bf81a8 Document Authelia without Redis session backend
Document Authelia without Redis session backend
2026-05-04 19:51:44 +02:00
Micha b45c406975 Claude ready
Claude ready
2026-05-04 15:42:48 +02:00
Micha 821fe99807 Document GitOps drift recovery and InfluxDB LAN access 2026-05-04 15:23:49 +02:00
Micha 7268f4ce1e Fix InfluxDB LAN port publishing 2026-05-04 14:59:42 +02:00
Micha 86f1a582c9 Fix Komodo periphery stack workspace access 2026-05-04 14:49:47 +02:00
Micha e3f7ed15cf Revert "Update docker-compose.yml"
This reverts commit 2b1f95ed09.
2026-05-04 13:54:38 +02:00
Micha 2b1f95ed09 Update docker-compose.yml 2026-05-04 13:47:51 +02:00
Micha ebea1789a6 Update docker-compose.yml 2026-05-04 13:40:23 +02:00
Micha 2f7b1a0aa2 Prepare Home Assistant weather export to InfluxDB
Prepare Home Assistant weather export to InfluxDB
2026-05-04 13:29:06 +02:00
170 changed files with 15655 additions and 511 deletions
+30
View File
@@ -0,0 +1,30 @@
# Local environment and stack values
.env
*.env
!*.env.example
**/stack.env
!**/stack.env.example
# Secrets and certificate material
**/secrets/
**/letsencrypt/
**/acme.json
**/*.key
**/*.pem
**/*.crt
# Backup, dump, and archive artifacts
**/*.dump
**/*.sql.gz
**/*.archive.gz
**/*.tar
**/*.tar.gz
**/*.tgz
**/*.zip
# Local/editor noise
.DS_Store
Thumbs.db
*.tmp
*.log
.serena/
+129
View File
@@ -0,0 +1,129 @@
# Claude Code Context - Homelab Infra
Stand: 2026-05-04
Dieses Repository ist die GitOps-Quelle fuer das KalliLab CORE Homelab auf einem Unraid-Host. Es verwaltet Docker-Compose-Stacks fuer Core-Dienste, Security, Infrastruktur, Apps, Operations-Tools, Host-nahe Dienste und Traefik. Gitea Online ist die operative Quelle der Wahrheit; Komodo konsumiert den Git-Stand und deployed daraus.
## Vor jeder Aenderung lesen
Claude muss vor jeder fachlichen oder technischen Aenderung mindestens diese Dateien lesen:
1. `HOMELAB_ARCHITECTURE_MASTER_V2.md`
2. `docs/WORKFLOW.md`
3. `docs/REPO_MAP.md`
4. `docs/SERVICE_CATALOG.md`
5. die betroffene `docker-compose.yml`
Zusaetzlich je nach Thema:
- Restore / Host-Ausfall: `docs/DISASTER_RECOVERY.md` und `docs/RESTORE_MATRIX.md`
- Rollback: `docs/ROLLBACK.md`
- Secrets: `docs/SECRETS_MAP.md`
- GitOps-/Komodo-/Runtime-Drift: `docs/GITOPS_DRIFT_RUNBOOK.md`
- Gesamtbild fuer KI-Agenten: `docs/AI_CONTEXT.md`
- Home Assistant / Ecowitt / InfluxDB: `docs/HOME_ASSISTANT_INFLUXDB_ECOWITT.md`
## Projektbeschreibung
- Host: Unraid, Hostname `Kallilabcore`
- Domain: `kaleschke.info`
- Reverse Proxy: Traefik v3
- DNS: AdGuard Home + Unbound
- VPN/Remote: Tailscale
- Git: Gitea unter `git.kaleschke.info`
- Deployment: Komodo als primaerer und einziger produktiver Stack-Manager
- Lokale Arbeitskopie: Windows/GitHub Desktop
- Persistenz: ueberwiegend `/mnt/user/appdata`, Nutzdaten in `/mnt/user/documents`, `/mnt/user/photos`, GitOps/Gitea in `/mnt/user/services`
## Architekturprinzipien
- Traefik ist der einzige oeffentliche HTTP(S)-Einstiegspunkt.
- Service-Routing erfolgt per Docker-Labels, nicht ueber neue Traefik File-Provider-Service-Routen.
- `frontend_net` ist das Web-/Proxy-Netz.
- `backend_net` ist `internal: true` fuer Datenbanken, Redis und interne Backends.
- App-interne Netze sind erlaubt, wenn sie eine App und ihre Datenbank/Worker sauber isolieren.
- Datenbanken und Caches duerfen nicht im `frontend_net` liegen.
- Direkte Host-Ports sind nur mit dokumentierter Ausnahme erlaubt.
- Komodo bleibt bewusst ohne pauschale zentrale Authelia-ForwardAuth-Middleware.
- Secrets gehoeren niemals ins Git-Repository.
- Produktive Container sollen als Compose/Git-Stack verwaltet werden.
## GitOps-Regeln
Quelle der Wahrheit:
1. Gitea `origin/master`
2. lokaler Clone
3. Komodo Stack Workspace
4. laufende Docker-Container
5. Host-Zustand
Standard-Workflow:
1. Lokal synchronisieren (`Fetch`, ggf. `Pull`)
2. Betroffene Docs und Compose-Datei lesen
3. Zielzustand und Rollback klaeren
4. Lokal minimal aendern
5. Validieren
6. Commit und Push durch den Benutzer oder nach expliziter Freigabe
7. Komodo-Deploy/Runtime pruefen
8. Dokumentation nachziehen
Neue produktive Komodo-Stacks aus `Micha/homelab-infra` brauchen verpflichtend einen aktiven Gitea->Komodo-Webhook auf die aktuelle Stack-ID. Ausnahmen muessen im selben Aenderungsblock dokumentiert werden.
Wenn Drift vermutet wird, nicht raten. Erst die Pflichtmatrix in `docs/GITOPS_DRIFT_RUNBOOK.md` abarbeiten.
## Sicherheitsregeln
- Secret-Werte niemals ausgeben. Wenn Werte auftauchen: redakten.
- Nur Secret-Namen, Env-Key-Namen und Pfade dokumentieren.
- Keine produktiven `.env`- oder Stack-Env-Werte zitieren.
- Keine Compose-Aenderung ohne vorherigen Architektur-/Workflow-Abgleich.
- Keine Deployments, Host-Hotfixes oder Docker-Schreibbefehle ohne ausdrueckliche Anweisung.
- Keine direkten Host-Ports fuer Web-UIs, ausser dokumentierte Ausnahmen.
- `privileged: true`, Docker-Socket und Host-Netz nur als dokumentierte Ausnahme.
- Traefik dynamic config unter `traefik/dynamic/` wird nicht automatisch von Komodo deployed und muss bei Aenderungen manuell auf den Host synchronisiert werden.
## Bekannte dokumentierte Ausnahmen
- `traefik`: Host-Ports 80/443
- `gitea`: SSH-Port 222
- `AdGuard Home`: DNS-Port 53 und LAN-Admin-Port 8082
- `tailscale`: `network_mode: host`
- `Plex-Media-Server`: historischer Host-Netz-Sonderfall, nicht als Repo-Stack enthalten
- `scrutiny`: `privileged: true` fuer SMART/Laufwerkszugriff
- `Komodo`: Docker-Socket und native Auth ohne pauschale ForwardAuth
- `traefik/dynamic/*`: manuelle Host-Sync-Ausnahme
- `influxdb3-core`: LAN-only Host-Port 8181 fuer Home Assistant Writer, keine Traefik-Route, nicht im `frontend_net`
## No-Go-Regeln
- Keine produktiven Aenderungen direkt in Komodo.
- Keine `docker run`-Ad-hoc-Container als Dauerzustand.
- Keine Compose-Dateien, Secrets oder Host-Konfigurationen stillschweigend aendern.
- Keine Deployments ausloesen, wenn der Benutzer nur Analyse oder Dokumentation verlangt.
- Kein `push --force` auf `master` als Standard-Rollback.
- Keine History-Rewrites ohne ausdrueckliche Freigabe.
- Keine Loesch-, Reset- oder Migrationsbefehle ohne klaren Zielzustand und Rollback.
- Keine neuen `latest`-/mutable Image-Tags ohne bewusste Versionsentscheidung.
## Rollback-Erwartungen
Jede Aenderung braucht vor dem Deploy eine klare Rueckkehrstrategie:
- letzter bekannter funktionierender Git-Stand
- betroffener Stack und Persistenzpfade
- ob Datenpfade unveraendert bleiben
- ob Secrets/ENV betroffen sind
- konkrete Smoke-Tests nach Rollback
Standard-Rollback ist ein Ruecknahme-Commit oder gezielte Rueckaenderung mit Push nach Gitea. Produktive Datenpfade unter `/mnt/user/appdata`, `/mnt/user/documents`, `/mnt/user/photos`, `/mnt/user/services` und `/mnt/user/backups` nicht blind loeschen.
## Arbeitsweise fuer Claude
- Erst lesen, dann handeln.
- Bei Unsicherheit Zustand messen, nicht erraten.
- Aenderungen klein halten und nur den betroffenen Bereich anfassen.
- Bestehende Doku und Repo-Konventionen bevorzugen.
- Bei Secret-/Runtime-/Komodo-Fragen besonders konservativ sein.
- Wenn zwei Reparaturversuche nicht funktionieren: stoppen, Pflichtmatrix ausfuellen, eine abweichende Ebene benennen.
+80 -39
View File
@@ -3,7 +3,7 @@
> **Single Source of Truth** für Docker-Netzwerkarchitektur, Sicherheitsregeln, Zielbild und Migration des Kallilabcore-Homelabs. > **Single Source of Truth** für Docker-Netzwerkarchitektur, Sicherheitsregeln, Zielbild und Migration des Kallilabcore-Homelabs.
> **Arbeitsregel für KI-Assistenten:** Dieses Dokument immer zuerst lesen, bevor Fragen zu Containern, Netzwerken, Traefik, Tailscale, Migration oder Security beantwortet werden. > **Arbeitsregel für KI-Assistenten:** Dieses Dokument immer zuerst lesen, bevor Fragen zu Containern, Netzwerken, Traefik, Tailscale, Migration oder Security beantwortet werden.
**Stand:** 2026-04-17 | **Aktueller Schwerpunkt:** GitOps / Doku-Synchronisierung / Reproduzierbare Deployments **Stand:** 2026-05-23 | **Aktueller Schwerpunkt:** GitOps / Doku-Synchronisierung / Reproduzierbare Deployments
--- ---
@@ -47,20 +47,20 @@
## 2. Architektur-Prinzipien ## 2. Architektur-Prinzipien
### P1 — Traefik ist der einzige öffentliche HTTP(S)-Einstiegspunkt ### P1 — Traefik ist der einzige öffentliche HTTP(S)-Einstiegspunkt
Kein Webdienst veröffentlicht finale direkte Host-Ports außer `traefik` selbst. Begründete Ausnahmen: `gitea`-SSH (Port 222), `AdGuard Home` (Port 53/DNS + 8082/Admin), `Tailscale`, `Plex-Media-Server`. Kein Webdienst veröffentlicht finale direkte Host-Ports außer `traefik` selbst. Begründete Ausnahmen: `gitea`-SSH (Port 222), `AdGuard Home` (Port 53/DNS direkt; Admin 8082 nur auf Tailscale-IP `100.80.98.33`), `Tailscale`, `Plex-Media-Server` und `monitoring-influxdb3-core` Port 8181 als LAN-only Writer-Endpunkt fuer Home Assistant.
### P2 — Das Setup bleibt bewusst einfach: `frontend_net` + `backend_net` + app-interne Netze ### P2 — Das Setup bleibt bewusst einfach: `frontend_net` + `backend_net` + app-interne Netze
- `frontend_net` = Proxy-/Web-Netz - `frontend_net` = Proxy-/Web-Netz
- `backend_net` = intern für DB/Cache/App-Kommunikation - `backend_net` = intern für DB/Cache/App-Kommunikation
- zusätzliche Netze nur app-intern, wenn technisch nötig (`mealie_mealie_internal`, `immich_default`, `dns_net`) - zusätzliche Netze nur app-intern, wenn technisch nötig (`mealie_internal`, `immich_default`, `dns_net`)
Es gibt **keine künstlichen globalen Zusatznetze** wie `admin_net`, `monitoring_net` oder `media_net`. Es gibt **keine künstlichen globalen Zusatznetze** wie `admin_net` oder `media_net`. `monitoring_net` ist die dokumentierte Ausnahme fuer den zentralen Observability-Stack.
### P3 — Datenbanken gehören nie ins `frontend_net` ### P3 — Datenbanken gehören nie ins `frontend_net`
Postgres, Redis und ähnliche Dienste laufen ausschließlich in `backend_net` oder einem eigenen internen Compose-Netz. Postgres, Redis und ähnliche Dienste laufen ausschließlich in `backend_net` oder einem eigenen internen Compose-Netz.
### P4 — Admin-UIs sind nicht öffentlich ### P4 — Admin-UIs sind nicht öffentlich
filebrowser, scrutiny, UptimeKuma, code-server, Traefik-Dashboard, backrest und borg-ui sind standardmaessig **Tailscale-only** oder hinter Traefik **mit zentraler Middleware** abgesichert. `Komodo` ist die dokumentierte Ausnahme und bleibt bewusst bei nativer Authentifizierung ohne pauschal vorgeschaltete ForwardAuth-Middleware. filebrowser, scrutiny, code-server, Traefik-Dashboard und borg-ui sind standardmaessig **Tailscale-only** oder hinter Traefik **mit zentraler Middleware** abgesichert. `Komodo` ist die dokumentierte Ausnahme und bleibt bewusst bei nativer Authentifizierung ohne pauschal vorgeschaltete ForwardAuth-Middleware.
### P5 — Compose-first ### P5 — Compose-first
Alle produktiven Container werden als Compose verwaltet. Bestehende Dockerman-/Ad-hoc-Container werden schrittweise migriert. Alle produktiven Container werden als Compose verwaltet. Bestehende Dockerman-/Ad-hoc-Container werden schrittweise migriert.
@@ -87,9 +87,12 @@ Jeder produktive Container nutzt `restart: unless-stopped`, außer eine Ausnahme
| `frontend_net` | bridge, external | einziges Traefik-/Web-Netz | Standard | | `frontend_net` | bridge, external | einziges Traefik-/Web-Netz | Standard |
| `backend_net` | bridge, `internal: true` | interne App-/DB-/Cache-Kommunikation | Standard | | `backend_net` | bridge, `internal: true` | interne App-/DB-/Cache-Kommunikation | Standard |
| `dns_net` | bridge | Resolver-Schicht: AdGuard Home + Unbound | bleibt | | `dns_net` | bridge | Resolver-Schicht: AdGuard Home + Unbound | bleibt |
| `mealie_mealie_internal` | bridge, `internal: true` | internes Netz nur für `mealie` + `mealie-postgres` | ✅ umgesetzt | | `mealie_internal` | bridge, `internal: true` | internes Netz nur für `mealie` + `mealie-postgres` | ✅ umgesetzt |
| `immich_default` | Compose-intern, `internal: true` | internes Immich-Netz | ✅ umgesetzt | | `immich_default` | Compose-intern, `internal: true` | internes Immich-Netz | ✅ umgesetzt |
| `nextcloud_internal` | bridge, `internal: true` | internes Netz nur fuer `nextcloud` + `nextcloud-postgres` + `nextcloud-redis` | ✅ vorbereitet | | `nextcloud_internal` | bridge, `internal: true` | internes Netz nur fuer `nextcloud` + `nextcloud-postgres` + `nextcloud-redis` | ✅ vorbereitet |
| `monitoring_net` | Compose-intern, bridge | zentraler Observability-Stack fuer Prometheus, Loki, Grafana, Promtail, Exporter und InfluxDB | Zielzustand |
| `monitoring_influx_lan` | Compose-intern, bridge | nicht-oeffentliches Zusatznetz nur fuer Docker Host-Port-Publishing von InfluxDB 8181 | Zielzustand |
| `glance_socket_net` | Compose-intern, `internal: true` | interner Zugriff von Glance auf den Docker-Socket-Proxy | umgesetzt |
| `host` | host | nur für echte Sonderfälle | begründet | | `host` | host | nur für echte Sonderfälle | begründet |
### 3.2 Finales Diagramm (vereinfacht) ### 3.2 Finales Diagramm (vereinfacht)
@@ -101,7 +104,7 @@ traefik (80/443)
└── frontend_net └── frontend_net
├── öffentliche Apps (vaultwarden, mealie, paperless, immich, gitea, ntfy, mail-archiver, nextcloud) ├── öffentliche Apps (vaultwarden, mealie, paperless, immich, gitea, ntfy, mail-archiver, nextcloud)
├── geschützte UIs mit Middleware (homepage, paperless-gpt, uptime-kuma, filebrowser, scrutiny, code-server, backrest, borg-ui, glances, speedtest, bentopdf, grafana) ├── geschützte UIs mit Middleware (glance, paperless-gpt, filebrowser, scrutiny, code-server, borg-ui, glances, speedtest, bentopdf, monitoring-grafana)
├── Admin-UI mit nativer Auth (komodo) ├── Admin-UI mit nativer Auth (komodo)
└── Dienste mit Internetbedarf ohne öffentliche UI (ddns-updater) └── Dienste mit Internetbedarf ohne öffentliche UI (ddns-updater)
@@ -116,9 +119,11 @@ dns_net
└── unbound └── unbound
App-interne Netze App-interne Netze
├── mealie_mealie_internal (internal: true) ✅ ├── mealie_internal (internal: true) ✅
├── immich_default (internal: true) ✅ ├── immich_default (internal: true) ✅
── nextcloud_internal (internal: true) ✅ ── nextcloud_internal (internal: true) ✅
├── monitoring_net (zentraler Observability-Stack)
└── monitoring_influx_lan (Bridge fuer LAN-Port-Publishing, keine Traefik-Route)
Host-Sonderfälle Host-Sonderfälle
├── tailscale ├── tailscale
@@ -139,27 +144,26 @@ Diese Dienste sind über echte `*.kaleschke.info`-Domains erreichbar:
- `ntfy` — ntfy.kaleschke.info - `ntfy` — ntfy.kaleschke.info
- `gitea` (Web) — git.kaleschke.info - `gitea` (Web) — git.kaleschke.info
- `immich_server` — immich.kaleschke.info - `immich_server` — immich.kaleschke.info
- `mail-archiver` — mail.kaleschke.info
- `nextcloud` — cloud.kaleschke.info - `nextcloud` — cloud.kaleschke.info
### 4.2 Nicht öffentlich / nur Tailscale oder Traefik + Middleware ### 4.2 Nicht öffentlich / nur Tailscale oder Traefik + Middleware
Diese Dienste sind **keine Public Apps**: Diese Dienste sind **keine Public Apps**:
- `Komodo` — komodo.kaleschke.info (Traefik, aber bewusst ohne zentrale Middleware; native Auth bleibt aktiv) - `Komodo` — komodo.kaleschke.info (Traefik, aber bewusst ohne zentrale Middleware; native Auth bleibt aktiv)
- `UptimeKuma` — uptime.kaleschke.info (Middleware)
- `filebrowser` — files.kaleschke.info (Middleware) - `filebrowser` — files.kaleschke.info (Middleware)
- `scrutiny` — scrutiny.kaleschke.info (Middleware) - `scrutiny` — scrutiny.kaleschke.info (Middleware)
- `code-server` — Traefik + Middleware - `code-server` — Traefik + Middleware
- `backrest` — Traefik + Middleware
- `borg-ui` — borg.kaleschke.info (Middleware) - `borg-ui` — borg.kaleschke.info (Middleware)
- `homepage`home.kaleschke.info (Middleware) - `glance`glance.kaleschke.info (Middleware)
- `paperless-gpt` — paperless-gpt.kaleschke.info (Middleware) - `paperless-gpt` — paperless-gpt.kaleschke.info (Middleware)
- `mail-archiver` — mail.kaleschke.info (Middleware + App-Auth)
- `glances` — glances.kaleschke.info (Middleware) - `glances` — glances.kaleschke.info (Middleware)
- `speedtest-tracker` — speedtest.kaleschke.info (Middleware) - `speedtest-tracker` — speedtest.kaleschke.info (Middleware)
- `bentopdf` — pdf.kaleschke.info (Middleware) - `bentopdf` — pdf.kaleschke.info (Middleware)
- `grafana`grafana.kaleschke.info (Middleware) - `monitoring-grafana`monitoring.kaleschke.info (Middleware)
- `hermes-dashboard` — hermes.kaleschke.info (Middleware)
- `Traefik-Dashboard` - `Traefik-Dashboard`
- `AdGuard Home`Port 8082 direkt auf die Admin-UI (`80` im Container), kein Traefik, nur LAN-Zugang - `AdGuard Home`Admin-UI auf Port 8082 (`80` im Container), kein Traefik, nur Tailscale-IP `100.80.98.33`; 2026-05-26 bewusst keine 2FA-/Traefik-Umstellung
### 4.3 Regel ### 4.3 Regel
Wenn ein Dienst im `frontend_net` hängt, heißt das **nicht automatisch öffentlich**. Admin-Dienste dürfen im `frontend_net` liegen, wenn: Wenn ein Dienst im `frontend_net` hängt, heißt das **nicht automatisch öffentlich**. Admin-Dienste dürfen im `frontend_net` liegen, wenn:
@@ -231,19 +235,17 @@ Legende Status:
| Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte | | Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| `traefik` | ✅ | `frontend_net`, `backend_net` | öffentlich 80/443 | zentraler Ingress, Service-Routing via Docker-Labels | — | | `traefik` | ✅ | `frontend_net`, `backend_net` | öffentlich 80/443 | zentraler Ingress, Service-Routing via Docker-Labels | — |
| `AdGuard Home` | ✅ | `dns_net` (172.23.0.3), `frontend_net` | Port 53 DNS direkt, Port 8082 Admin (LAN) | DNS-Server + Upstream zu unbound; kein Traefik (DNS-Sonderfall) | Admin-Port per Traefik + Middleware absichern (Block F) | | `AdGuard Home` | ✅ | `dns_net` (172.23.0.3), `frontend_net` | Port 53 DNS direkt, Port 8082 Admin nur auf Tailscale-IP `100.80.98.33` | DNS-Server + Upstream zu unbound; kein Traefik fuer Admin-UI | Admin-Port bleibt bewusst ohne Traefik/2FA, aber nicht mehr auf allen LAN-Interfaces |
| `unbound` | ✅ | `dns_net` | intern | Upstream-Resolver für AdGuard, isoliert | — | | `unbound` | ✅ | `dns_net` | intern | Upstream-Resolver für AdGuard, isoliert | — |
| `ddns-updater` | ✅ | `frontend_net` | intern | Cloudflare DNS API; bleibt in `frontend_net` | Dokumentierte Ausnahme | | `ddns-updater` | ✅ | `frontend_net` | intern | Cloudflare DNS API; bleibt in `frontend_net` | Dokumentierte Ausnahme |
| `tailscale` | ✅ | `host` | VPN-Zugang | Git-Stack (`host-services/tailscale/`) | `TS_USERSPACE`/`privileged` später prüfen | | `tailscale` | ✅ | `host` | VPN-Zugang | Git-Stack (`host-services/tailscale/`) | nutzt `NET_ADMIN`, `NET_RAW` und `/dev/net/tun` als dokumentierte VPN-Ausnahme |
| `backrest` | ✅ | `frontend_net`, `backend_net` | Traefik + Middleware | produktiver Backup-Admin-Dienst | breite Mounts bewusst dokumentieren |
| `homepage` | ✅ | `frontend_net` | Traefik + Middleware | geschuetztes Start-Dashboard via `home.kaleschke.info` | — |
### 7.2 Sicherheit / Identity ### 7.2 Sicherheit / Identity
| Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte | | Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| `vaultwarden` | ✅ | `frontend_net` | Traefik | kein Host-Port, `ADMIN_TOKEN_FILE` | — | | `vaultwarden` | ✅ | `frontend_net` | Traefik | kein Host-Port, `ADMIN_TOKEN_FILE` | — |
| `authelia` | ✅ | `frontend_net`, `backend_net` | Traefik via `auth.kaleschke.info` | aktiver ForwardAuth-Provider, Secrets via `_FILE`, PostgreSQL + Redis Shared | — | | `authelia` | ✅ | `frontend_net`, `backend_net` | Traefik via `auth.kaleschke.info` | aktiver ForwardAuth-Provider, Secrets via `_FILE`, PostgreSQL Storage; bewusst ohne Redis-Session-Backend | — |
### 7.3 Datenbanken / Caches ### 7.3 Datenbanken / Caches
@@ -251,24 +253,25 @@ Legende Status:
|---|---|---|---|---|---| |---|---|---|---|---|---|
| `postgresql17` | ✅ | `backend_net` | intern | kein Host-Port, `POSTGRES_PASSWORD_FILE` | — | | `postgresql17` | ✅ | `backend_net` | intern | kein Host-Port, `POSTGRES_PASSWORD_FILE` | — |
| `Redis` | ✅ | `backend_net` | intern | intern-only Cache | optional named volume | | `Redis` | ✅ | `backend_net` | intern | intern-only Cache | optional named volume |
| `mealie-postgres` | ✅ | `mealie_mealie_internal` | intern | isoliert, nie `frontend_net` | — | | `mealie-postgres` | ✅ | `mealie_internal` | intern | isoliert, nie `frontend_net` | — |
| `immich_postgres` | ✅ | `immich_default` | intern | intern-only | — | | `immich_postgres` | ✅ | `immich_default` | intern | intern-only | — |
| `immich_redis` | ⏳ | `immich_default` | intern | intern-only | anonymes Volume → named volume | | `immich_redis` | ⏳ | `immich_default` | intern | intern-only | anonymes Volume → named volume |
| `nextcloud-postgres` | ✅ | `nextcloud_internal` | intern | app-eigene Nextcloud-Datenbank mit `_FILE`-Secret | — | | `nextcloud-postgres` | ✅ | `nextcloud_internal` | intern | app-eigene Nextcloud-Datenbank mit `_FILE`-Secret | — |
| `nextcloud-redis` | ✅ | `nextcloud_internal` | intern | app-eigener Cache fuer File Locking / Sessions | — | | `nextcloud-redis` | ✅ | `nextcloud_internal` | intern | app-eigener Cache fuer File Locking / Sessions | — |
### 7.4 Öffentliche Apps ### 7.4 Produktive Apps
| Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte | | Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| `paperless-ngx` | ✅ | `frontend_net`, `backend_net` | Traefik | aktiv via `paperless.kaleschke.info` | — | | `paperless-ngx` | ✅ | `frontend_net`, `backend_net` | Traefik | aktiv via `paperless.kaleschke.info` | — |
| `mail-archiver` | ✅ | `frontend_net`, `backend_net` | Traefik | aktiv via `mail.kaleschke.info`; IMAP-Abruf + DB-Zugang | — | | `mail-archiver` | ✅ | `frontend_net`, `backend_net` | Traefik + Middleware | aktiv via `mail.kaleschke.info`; IMAP-Abruf + DB-Zugang; App-eigene Auth bleibt zusaetzliche Schutzschicht | — |
| `mealie` | ✅ | `frontend_net`, `mealie_mealie_internal` | Traefik | sauber getrennte App/DB-Struktur | — | | `mealie` | ✅ | `frontend_net`, `mealie_internal` | Traefik | sauber getrennte App/DB-Struktur | — |
| `ntfy` | ✅ | `frontend_net` | Traefik | aktiv via `ntfy.kaleschke.info`, Git-Stack | — | | `ntfy` | ✅ | `frontend_net` | Traefik | aktiv via `ntfy.kaleschke.info`, Git-Stack | — |
| `gitea` | ✅ | `frontend_net` | Traefik + SSH-Port 222 | Web via Traefik, SSH direkt gebunden | — | | `gitea` | ✅ | `frontend_net` | Traefik + SSH-Port 222 | Web via Traefik, SSH direkt gebunden | — |
| `immich_server` | ✅ | `immich_default`, `frontend_net` | Traefik | aktiv via `immich.kaleschke.info` | — | | `immich_server` | ✅ | `immich_default`, `frontend_net` | Traefik | aktiv via `immich.kaleschke.info` | — |
| `immich_machine_learning` | ✅ | `immich_default` | intern | bleibt intern | — | | `immich_machine_learning` | ✅ | `immich_default` | intern | bleibt intern | — |
| `nextcloud` | ✅ | `frontend_net`, `nextcloud_internal` | Traefik | aktiv via `cloud.kaleschke.info`, nativer Nextcloud-Login, WebDAV/CardDAV faehig | CalDAV/CardDAV-Redirect via Traefik-Labels | | `nextcloud` | ✅ | `frontend_net`, `nextcloud_internal` | Traefik | aktiv via `cloud.kaleschke.info`, nativer Nextcloud-Login, WebDAV/CardDAV faehig | CalDAV/CardDAV-Redirect via Traefik-Labels |
| `plex` | ✅ | `host` | Plex native / Host-Netz | Compose-Stack unter `host-services/plex/`; Host-Netz bleibt fuer Discovery / Plex GDM dokumentierte Ausnahme | — |
### 7.5 Admin / Operations ### 7.5 Admin / Operations
@@ -277,27 +280,31 @@ Legende Status:
| `komodo` | ✅ | `frontend_net` | Traefik, native Auth | primaerer GitOps-Stack-Manager | bewusste Ausnahme: keine pauschale ForwardAuth-Middleware vor UI/API/Webhooks/Periphery | | `komodo` | ✅ | `frontend_net` | Traefik, native Auth | primaerer GitOps-Stack-Manager | bewusste Ausnahme: keine pauschale ForwardAuth-Middleware vor UI/API/Webhooks/Periphery |
| `code-server` | ✅ | `frontend_net` | Traefik + Middleware | `PASSWORD_FILE` aktiv | — | | `code-server` | ✅ | `frontend_net` | Traefik + Middleware | `PASSWORD_FILE` aktiv | — |
| `PortainerCE` | ❌ entfernt | - | - | 2026-03-29 abgeschaltet | historisch; nicht mehr deployen | | `PortainerCE` | ❌ entfernt | - | - | 2026-03-29 abgeschaltet | historisch; nicht mehr deployen |
| `filebrowser` | ✅ | `frontend_net` | Traefik + Middleware | aktiv via `files.kaleschke.info` | Mounts einschränken (Block F) | | `filebrowser` | ✅ | `frontend_net` | Traefik + Middleware | aktiv via `files.kaleschke.info` | Appdata-Breitmount entfernt; nur Documents/Photos/Projekte plus eigener App-State |
| `borg-ui` | ✅ | `frontend_net` | Traefik + Middleware | produktiver Borg-/Restore-Dienst; `/local/secrets` ist bewusst Teil des Restore-Scopes | BorgBase-Repo und Key laufend pflegen | | `borg-ui` | ✅ | `frontend_net` | Traefik + Middleware | produktiver Borg-/Restore-Dienst; `/local/secrets` ist bewusst Teil des Restore-Scopes | BorgBase-Repo und Key laufend pflegen |
| `paperless-gpt` | ✅ | `frontend_net` | Traefik + Middleware | aktiv via `paperless-gpt.kaleschke.info` | — | | `paperless-gpt` | ✅ | `frontend_net` | Traefik + Middleware | aktiv via `paperless-gpt.kaleschke.info` | — |
| `bentopdf` | ✅ vorbereitet | `frontend_net` | Traefik + Middleware | PDF-Tooling via `pdf.kaleschke.info`; browserseitige Verarbeitung, COOP/COEP fuer Office-Konvertierung | Deploy und fachliche Abnahme offen | | `bentopdf` | ✅ vorbereitet | `frontend_net` | Traefik + Middleware | PDF-Tooling via `pdf.kaleschke.info`; browserseitige Verarbeitung, COOP/COEP fuer Office-Konvertierung | Deploy und fachliche Abnahme offen |
| `hermes-dashboard` | ✅ | `frontend_net`, `hermes_net` | Traefik + Middleware | aktiv via `hermes.kaleschke.info`; Dashboard bindet intern mit `--insecure` auf `0.0.0.0`, externe Absicherung ueber Authelia | — |
### 7.6 Monitoring / Status ### 7.6 Monitoring / Status
| Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte | | Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| `UptimeKuma` | ✅ | `frontend_net` | Traefik + Middleware | aktiv via `uptime.kaleschke.info` | — | | `glance` | ✅ | `frontend_net`, `glance_socket_net` | Traefik + Middleware | einziges Homelab-Dashboard via `glance.kaleschke.info`; Docker-Status nur ueber internen Socket-Proxy | — |
| `glances` | ✅ | `frontend_net` | Traefik + Middleware | aktiv via `glances.kaleschke.info` | — | | `glances` | ✅ | `frontend_net` | Traefik + Middleware | aktiv via `glances.kaleschke.info` | — |
| `scrutiny` | ✅ | `frontend_net` | Traefik + Middleware | aktiv via `scrutiny.kaleschke.info`, Git-Stack | `privileged` später prüfen | | `scrutiny` | ✅ | `frontend_net` | Traefik + Middleware | aktiv via `scrutiny.kaleschke.info`, Git-Stack | `privileged` später prüfen |
| `speedtest-tracker` | ✅ | `frontend_net` | Traefik + Middleware | aktiv via `speedtest.kaleschke.info` | — | | `speedtest-tracker` | ✅ | `frontend_net` | Traefik + Middleware | aktiv via `speedtest.kaleschke.info` | — |
| `grafana` | ✅ vorbereitet | `frontend_net`, `grafana_influx_internal` | Traefik + Middleware | vorbereitet via `grafana.kaleschke.info`, InfluxDB-Datenquelle provisioniert | Secrets anlegen, Deploy offen | | `monitoring-grafana` | ✅ | `frontend_net`, `monitoring_net` | Traefik + Middleware | zentrale UI via `monitoring.kaleschke.info`; Datasources fuer Prometheus, Loki und InfluxDB | — |
| `influxdb3-core` | ✅ vorbereitet | `grafana_influx_internal` | intern | InfluxDB 3 Core fuer Metriken; keine direkte Host-/Traefik-Freigabe | Token und Datenbank `homelab` anlegen, Deploy offen | | `monitoring-influxdb3-core` | ✅ | `monitoring_net`, `monitoring_influx_lan` + LAN-Bind | LAN-Port nur fuer interne Writer | InfluxDB 3 Core fuer Home-Assistant-/Ecowitt-Langzeitdaten; keine Traefik-/Public-Freigabe; Port 8181 nur via `INFLUXDB_BIND_IP` | HA-Write-Token und Sensor-Export finalisieren |
| `monitoring-loki` | ✅ | `monitoring_net` | intern | interner Container-Logspeicher ohne Public Route; Monitoring-Grafana greift ueber Loki-Datasource zu | Retention/Storage beobachten |
| `monitoring-promtail` | ✅ | `monitoring_net` | intern | Docker-Log-Collector mit read-only Docker-Socket-Ausnahme; schreibt nach Loki | Socket-Ausnahme regelmaessig pruefen |
| `grafana` / `influxdb3-core` / `loki` / `alloy` | entfernt | - | abgeloest | alte Docker-Runtime frei von Altcontainern; Compose-Pfade am 2026-05-26 aus aktivem Repo entfernt | Rollback nur ueber Git-Historie |
### 7.7 Noch offene Sonderfälle ### 7.7 Noch offene Sonderfälle
| Container | Status | Ziel | | Container | Status | Ziel |
|---|---|---| |---|---|---|
| `Plex-Media-Server` | ⏳ Dockerman | Compose-Migration, `host`-Netz bleibt (Discovery) | | — | — | Plex ist nicht mehr offen: der Dienst ist als Repo-Compose-Stack unter `host-services/plex/` dokumentiert; `host`-Netz bleibt als Discovery-Ausnahme. |
### 7.8 Entfernte Container ### 7.8 Entfernte Container
@@ -313,11 +320,15 @@ Legende Status:
| `dashdot` | 2026-03-28 | nicht mehr aktiv | | `dashdot` | 2026-03-28 | nicht mehr aktiv |
| `netdata` | 2026-03-28 | nicht mehr aktiv | | `netdata` | 2026-03-28 | nicht mehr aktiv |
| `netalertx` | 2026-03-28 | nicht mehr aktiv | | `netalertx` | 2026-03-28 | nicht mehr aktiv |
| `luckyBackup` | 2026-03-28 | nicht mehr aktiv; Backup via backrest | | `luckyBackup` | 2026-03-28 | nicht mehr aktiv; Backup via Borg |
| `backrest` | 2026-05-15 | entfernt; Borg ist die alleinige Backup-Technologie, WD MyBookLive ist kein Backup-Ziel mehr |
| `Stash` | 2026-03-28 | nicht mehr aktiv | | `Stash` | 2026-03-28 | nicht mehr aktiv |
| `PortainerCE` | 2026-03-29 | abgeschaltet; Komodo ist alleiniger Stack-Manager | | `PortainerCE` | 2026-03-29 | abgeschaltet; Komodo ist alleiniger Stack-Manager |
| `beszel` | nicht dokumentiert | bereits entfernt; nicht mehr Teil des Zielbilds | | `beszel` | nicht dokumentiert | bereits entfernt; nicht mehr Teil des Zielbilds |
| `beszel-agent` | nicht dokumentiert | bereits entfernt; nicht mehr Teil des Zielbilds | | `beszel-agent` | nicht dokumentiert | bereits entfernt; nicht mehr Teil des Zielbilds |
| `jellyfin` | 2026-05-25 | doppelter Medienserver neben Plex; Plex bleibt einziger Medienserver |
| `homepage` | 2026-05-25 | doppeltes Dashboard neben Glance; Glance bleibt einziges Homelab-Dashboard |
| `uptime-kuma` | 2026-05-25 | durch `monitoring-blackbox-exporter`, Prometheus-Alerts und `monitoring-grafana` ersetzt |
--- ---
@@ -377,17 +388,20 @@ Für den laufenden Betrieb gilt stattdessen:
| Container | Ausnahme | Begründung | | Container | Ausnahme | Begründung |
|---|---|---| |---|---|---|
| `traefik` | Host-Ports 80/443 | zentraler Reverse Proxy | | `traefik` | Host-Ports 80/443 | zentraler Reverse Proxy |
| `tailscale` | `host` | VPN-Zugang; Umstellung nur kontrolliert möglich | | `tailscale` | `host`, `NET_ADMIN`, `NET_RAW`, `/dev/net/tun` | VPN-Zugang benoetigt Kernel-Netzwerkfunktionen; Umstellung nur kontrolliert moeglich |
| `AdGuard Home` | Port 53 (TCP/UDP) direkt + Port 8082 auf Container-Port 80 | DNS benötigt direkten Port 53; kein HTTP-Proxy für DNS möglich | | `AdGuard Home` | Port 53 (TCP/UDP) direkt + `100.80.98.33:8082` auf Container-Port 80 | DNS benoetigt direkten Port 53; Admin-Port 8082 bleibt bewusst ohne Traefik/2FA, aber nur via Tailscale |
| `Plex-Media-Server` | `host` | Discovery / mDNS / Plex GDM | | `Plex-Media-Server` | `host` | Discovery / mDNS / Plex GDM |
| `scrutiny` | `privileged: true` | SMART-Datenzugriff auf Laufwerke | | `scrutiny` | `privileged: true` | SMART-Datenzugriff auf Laufwerke |
| `Komodo` | Docker-Socket Zugriff | Stack-Deployments benötigen Socket | | `Komodo` | Docker-Socket Zugriff | Stack-Deployments benötigen Socket |
| `glance-docker-socket-proxy` | Docker-Socket read-only | Glance benoetigt Containerstatus; Zugriff wird ueber einen internen Socket-Proxy auf lesende Docker-API-Endpunkte begrenzt und nicht ins `frontend_net` gelegt |
| `Komodo` | keine pauschale zentrale Middleware | Webhooks (`/listener`), API und Periphery-WebSocket (`/ws/periphery`) sollen nicht durch vorgeschaltete ForwardAuth gebrochen werden | | `Komodo` | keine pauschale zentrale Middleware | Webhooks (`/listener`), API und Periphery-WebSocket (`/ws/periphery`) sollen nicht durch vorgeschaltete ForwardAuth gebrochen werden |
| `gitea` | SSH-Port 222 direkt gebunden | Git-SSH-Zugang; kein HTTP-Proxy für SSH möglich | | `gitea` | SSH-Port 222 direkt gebunden | Git-SSH-Zugang; kein HTTP-Proxy für SSH möglich |
| `ddns-updater` | bleibt in `frontend_net` statt `backend_net` | braucht Cloudflare-API-Zugang; `backend_net` ist `internal: true` | | `ddns-updater` | bleibt in `frontend_net` statt `backend_net` | braucht Cloudflare-API-Zugang; `backend_net` ist `internal: true` |
| `mail-archiver` | `frontend_net` + `backend_net` | braucht Internetzugang für IMAP-Abruf (GMX, Gmail) und DB-Zugang | | `mail-archiver` | `frontend_net` + `backend_net` | braucht Internetzugang für IMAP-Abruf (GMX, Gmail) und DB-Zugang |
| `traefik/dynamic/*` | manueller Host-Sync trotz GitOps | File-Provider bleibt bewusst fuer `middlewares.yml`, `tls.yml` und `dashboards.yml`; Komodo deployed diese Dateien nicht automatisch | | `traefik/dynamic/*` | manueller Host-Sync trotz GitOps | File-Provider bleibt bewusst fuer `middlewares.yml`, `tls.yml` und `dashboards.yml`; Komodo deployed diese Dateien nicht automatisch |
| `nextcloud` | keine zentrale ForwardAuth-Middleware | Nextcloud bringt eigene Auth, Clients und WebDAV/CardDAV-Endpunkte mit; Traefik bleibt Reverse Proxy, Auth bleibt app-nativ | | `nextcloud` | keine zentrale ForwardAuth-Middleware | Nextcloud bringt eigene Auth, Clients und WebDAV/CardDAV-Endpunkte mit; Traefik bleibt Reverse Proxy, Auth bleibt app-nativ |
| `monitoring-influxdb3-core` | Host-Port 8181 auf LAN-IP; `user: "0"` | Home Assistant laeuft in einer VM ausserhalb des Compose-Netzes und muss Metriken schreiben koennen; keine Traefik-Route, kein `frontend_net`, Zugriff nur ueber Token und LAN-IP `INFLUXDB_BIND_IP`; InfluxDB 3 Core benoetigt im aktuellen Container-Setup Root-Rechte fuer den lokalen Object-Store-Pfad im named volume |
| `monitoring-promtail` | Docker-Socket read-only | Docker-Log-Discovery fuer Loki; keine Schreibrechte, keine Appdaten-Persistenz ueber den Socket |
--- ---
@@ -465,12 +479,19 @@ Komodo ist nun der primäre GitOps-Stack-Manager:
**Zugangsregel:** Komodo bleibt bewusst bei nativer Authentifizierung ohne pauschal vorgeschaltete ForwardAuth-Middleware vor dem gesamten Router. Hintergrund sind die gemischten UI-, API-, Webhook- und Periphery-Endpunkte unter derselben Domain. **Zugangsregel:** Komodo bleibt bewusst bei nativer Authentifizierung ohne pauschal vorgeschaltete ForwardAuth-Middleware vor dem gesamten Router. Hintergrund sind die gemischten UI-, API-, Webhook- und Periphery-Endpunkte unter derselben Domain.
### Komodo Self-Stack Drift-Recovery (2026-05-04)
- Befund: `komodo-core` und `komodo-periphery` liefen aus temporaeren `/tmp/*repair.yml`-Dateien, waehrend `komodo-mongo` auf den fehlenden persistenten Pfad `/mnt/user/services/stacks/komodo/compose.yaml` verwies.
- Recovery: Repair-YAMLs und Runtime-ENV wurden unter `/mnt/user/appdata/komodo/_drift_backup_2026-05-04/` gesichert; eine zusaetzliche Recovery-ENV liegt unter `/mnt/user/appdata/secrets/_komodo_stack_env_recovery_2026-05-04.env` und ist als temporaeres Tier-1-Secret-Material zu behandeln.
- Der persistente Self-Stack wurde unter `/mnt/user/services/stacks/komodo/compose.yaml` aus `ops/komodo/docker-compose.yml` wiederhergestellt. Die hostseitige `.env` bleibt ausserhalb von Git.
- Reconcile-Regel: Bei Self-Stack-Drift keinen pauschalen `docker compose up -d` ausfuehren, wenn der Dry-run `komodo-mongo` recreaten wuerde. Core und Periphery koennen gezielt mit `--no-deps` neu erstellt werden, Mongo bleibt dabei unangetastet.
- Ergebnis: Alle drei Komodo-Container zeigen wieder auf `/mnt/user/services/stacks/komodo/compose.yaml`; Mongo blieb waehrend der Rueckfuehrung healthy.
### AdGuard Home — Ablösung von Pi-hole (2026-03-28) ### AdGuard Home — Ablösung von Pi-hole (2026-03-28)
`binhex-official-pihole` wurde entfernt und durch `AdGuard Home` + `unbound` ersetzt: `binhex-official-pihole` wurde entfernt und durch `AdGuard Home` + `unbound` ersetzt:
- AdGuard läuft als Git-Stack (`host-services/Adguard/docker-compose.yml`) - AdGuard läuft als Git-Stack (`host-services/Adguard/docker-compose.yml`)
- Netzwerke: `dns_net` (feste IP 172.23.0.3) + `frontend_net` - Netzwerke: `dns_net` (feste IP 172.23.0.3) + `frontend_net`
- Port 53 (DNS) direkt gebunden — dokumentierte Ausnahme - Port 53 (DNS) direkt gebunden — dokumentierte Ausnahme
- Admin-UI direkt gebunden via Host-Port 8082 auf Container-Port 80 — Traefik-Absicherung ausstehend (Block F) - Admin-UI direkt gebunden via Tailscale-IP `100.80.98.33:8082` auf Container-Port 80 — 2026-05-26 bewusst als einfache Operator-Entscheidung ohne Traefik-/2FA-Umstellung
- `unbound` läuft weiterhin als Upstream-Resolver in `dns_net` - `unbound` läuft weiterhin als Upstream-Resolver in `dns_net`
### diun — Entfernung (2026-03-28) ### diun — Entfernung (2026-03-28)
@@ -520,29 +541,49 @@ Host-Pfade in `env_file` (z.B. `/mnt/...`) sind in Git-Stacks nicht verfügbar.
### Reproduzierbare Deployments (2026-04-17) ### Reproduzierbare Deployments (2026-04-17)
Mutable Tags wie `latest`, `stable`, `release` oder reine Major-Tags wurden auf die **aktuell laufenden Digests** eingefroren. Das ist bewusst **kein Upgrade-Mechanismus**, sondern dient dazu, den heute funktionierenden Laufzeitstand exakt im Repo festzuhalten. Echte Versions-Upgrades bleiben ein eigener, geplanter Schritt. Mutable Tags wie `latest`, `stable`, `release` oder reine Major-Tags wurden auf die **aktuell laufenden Digests** eingefroren. Das ist bewusst **kein Upgrade-Mechanismus**, sondern dient dazu, den heute funktionierenden Laufzeitstand exakt im Repo festzuhalten. Echte Versions-Upgrades bleiben ein eigener, geplanter Schritt.
### Stateful Digest-Pinning (2026-05-05, ergaenzt 2026-05-16)
- Tier-1/stateful Basisdienste werden bevorzugt mit sprechendem Minor-/Patch-Tag plus Digest gepinnt, z. B. `postgres:17.9@sha256:...` oder `mongo:7.0.32@sha256:...`.
- Redis-Caches sind seit dem Hardening-Sprint 2026-05-16 auf `redis:7.4-alpine@sha256:...` vereinheitlicht. Updates erfolgen bewusst stackweise mit Smoke-Test.
- Bereits versionierte Apps koennen optional spaeter ebenfalls Digests erhalten; dieser Schritt ist getrennt vom Datenhalter-Pinning.
### Nextcloud und Stirling-PDF (2026-04-19) ### Nextcloud und Stirling-PDF (2026-04-19)
- `nextcloud` wird bewusst **nicht** als AIO-Stack gebaut, sondern als klassischer Docker-Microservice-Stack mit eigenem PostgreSQL und eigenem Redis. Das passt besser zum bestehenden GitOps-/Compose-Modell des Repos. - `nextcloud` wird bewusst **nicht** als AIO-Stack gebaut, sondern als klassischer Docker-Microservice-Stack mit eigenem PostgreSQL und eigenem Redis. Das passt besser zum bestehenden GitOps-/Compose-Modell des Repos.
- `nextcloud` bleibt bei nativer App-Authentifizierung ohne zentrale ForwardAuth-Middleware vor dem Router, damit Browser-Login, Desktop-/Mobile-Clients sowie WebDAV/CardDAV sauber funktionieren. - `nextcloud` bleibt bei nativer App-Authentifizierung ohne zentrale ForwardAuth-Middleware vor dem Router, damit Browser-Login, Desktop-/Mobile-Clients sowie WebDAV/CardDAV sauber funktionieren.
- `stirling-pdf` wird als geschuetzter Tool-Stack hinter `authelia@file,secure-headers@file` betrieben; die interne Stirling-Login-Funktion bleibt deaktiviert, um Doppel-Login zu vermeiden. - `stirling-pdf` wird als geschuetzter Tool-Stack hinter `authelia@file,secure-headers@file` betrieben; die interne Stirling-Login-Funktion bleibt deaktiviert, um Doppel-Login zu vermeiden.
### BentoPDF und Grafana/InfluxDB vorbereitet (2026-04-30) ### BentoPDF und Monitoring-Zielstack (2026-04-30, aktualisiert 2026-05-17)
- `bentopdf` ersetzt repo-seitig `stirling-pdf` auf der bestehenden Domain `pdf.kaleschke.info`, bleibt aber bis zum bewussten Komodo-Deploy nur vorbereitet. - `bentopdf` ersetzt repo-seitig `stirling-pdf` auf der bestehenden Domain `pdf.kaleschke.info`, bleibt aber bis zum bewussten Komodo-Deploy nur vorbereitet.
- BentoPDF benoetigt fuer Office-Konvertierung die Cross-Origin-Isolation-Header `Cross-Origin-Opener-Policy: same-origin` und `Cross-Origin-Embedder-Policy: require-corp`; diese werden per Traefik-Docker-Middleware gesetzt. - BentoPDF benoetigt fuer Office-Konvertierung die Cross-Origin-Isolation-Header `Cross-Origin-Opener-Policy: same-origin` und `Cross-Origin-Embedder-Policy: require-corp`; diese werden per Traefik-Docker-Middleware gesetzt.
- `grafana` wird als geschuetztes Monitoring-UI unter `grafana.kaleschke.info` vorbereitet. - `monitoring/` ist der zentrale Zielstack fuer Prometheus, Loki, Promtail, Grafana, node-exporter, cAdvisor und InfluxDB 3 Core.
- `influxdb3-core` bleibt als interne Datenbank ohne direkten Host-Port im Compose-internen Netz `grafana_influx_internal`. - `monitoring-grafana` wird als geschuetztes Monitoring-UI unter `monitoring.kaleschke.info` betrieben.
- `monitoring-influxdb3-core` bleibt ohne Traefik-/Public-Route; fuer interne Writer wie Home Assistant kann Port `8181` per `INFLUXDB_BIND_IP` auf eine LAN-Adresse gebunden werden.
- Fuer dieses Port-Publishing nutzt `monitoring-influxdb3-core` zusaetzlich `monitoring_influx_lan`. Das ist keine Public-App-Freigabe und ersetzt nicht die Token-Authentifizierung.
- InfluxDB 3 Core nutzt einen festen Versionstag statt `latest`, weil der InfluxDB-`latest`-Tag versionsstrategisch im Umbruch ist. - InfluxDB 3 Core nutzt einen festen Versionstag statt `latest`, weil der InfluxDB-`latest`-Tag versionsstrategisch im Umbruch ist.
- Die alten Pfade `ops/grafana-influxdb` und `ops/loki` wurden am 2026-05-26 aus dem aktiven Repo entfernt; `monitoring/` ist der einzige Observability-Zielstack.
- Uptime Kuma wurde nach erfolgreichem Blackbox-/Grafana-Smoke-Test entfernt; `monitoring/` ist die Quelle fuer HTTP-Erreichbarkeit und Alerts.
### Monitoring-Logging-Baseline (2026-05-17)
- `monitoring-loki` laeuft intern auf `monitoring_net`, ohne Traefik-Route und ohne Host-Port.
- `monitoring-promtail` sammelt Docker-Logs ueber `/var/run/docker.sock:ro` und `/var/lib/docker/containers:ro` und schreibt sie an Loki.
- `monitoring-grafana` bekommt provisionierte Datasources fuer Prometheus, Loki und InfluxDB 3 Core.
- Loki-Logdaten sind Diagnosematerial mit begrenzter Retention, keine primaere Restore-Quelle.
### Authelia ohne Redis-Session-Backend (2026-05-04)
- Authelia nutzt PostgreSQL fuer persistente Storage-Daten, aber bewusst kein Redis-Session-Backend.
- Das haelt den Tier-1-Auth-Pfad einfacher; nach einem Authelia-Restart muessen aktive Sessions neu aufgebaut werden.
- `infra/redis` bleibt shared Cache fuer Dienste wie Paperless, ist aber keine Authelia-Abhaengigkeit.
### ddns-updater — Netz-Ausnahme ### ddns-updater — Netz-Ausnahme
Bleibt bewusst in `frontend_net` statt `backend_net`, weil `backend_net` `internal: true` ist und ddns-updater die Cloudflare-API erreichen muss. Bleibt bewusst in `frontend_net` statt `backend_net`, weil `backend_net` `internal: true` ist und ddns-updater die Cloudflare-API erreichen muss.
### mail-archiver — Hybrid-Dienst ### mail-archiver — Hybrid-Dienst
Benötigt `backend_net` (PostgreSQL) + `frontend_net` (IMAP-Abruf von GMX/Gmail). Kein reiner Backend-Dienst. Aktuell via Traefik unter `mail.kaleschke.info` geroutet. Benötigt `backend_net` (PostgreSQL) + `frontend_net` (IMAP-Abruf von GMX/Gmail). Kein reiner Backend-Dienst. Die Web-UI ist via Traefik unter `mail.kaleschke.info` erreichbar und wird durch `authelia@file,secure-headers@file` plus App-eigene Auth geschuetzt.
### Netzwerk-Standard für Apps mit Datenbanken ### Netzwerk-Standard für Apps mit Datenbanken
- App → `frontend_net` + internes Netzwerk - App → `frontend_net` + internes Netzwerk
- Datenbank → nur internes Netzwerk (`internal: true`) - Datenbank → nur internes Netzwerk (`internal: true`)
Beispiel (Mealie): `mealie``frontend_net` + `mealie_mealie_internal`, `mealie-postgres` → nur `mealie_mealie_internal`. Beispiel (Mealie): `mealie``frontend_net` + `mealie_internal`, `mealie-postgres` → nur `mealie_internal`.
--- ---
@@ -551,4 +592,4 @@ Beispiel (Mealie): `mealie` → `frontend_net` + `mealie_mealie_internal`, `meal
Dieses Dokument ist keine lose Notiz, sondern das **operative Masterdokument** für die Docker- und Zugriffsarchitektur des Homelabs. Dieses Dokument ist keine lose Notiz, sondern das **operative Masterdokument** für die Docker- und Zugriffsarchitektur des Homelabs.
**Zielbild in einem Satz:** **Zielbild in einem Satz:**
`frontend_net` für Web-UIs und Dienste mit Internetbedarf, `backend_net` für interne Backends, app-interne Netze nur wenn technisch nötig, Tailscale für Remote-Admin-Zugriff, Traefik als einziger Web-Einstieg (Service-Routing via Docker-Labels, File-Provider nur für zentrale Dynamic-Config), Komodo als GitOps-Stack-Manager, AdGuard Home + Unbound für DNS, keine produktiven `bridge`-Container mehr. `frontend_net` für Web-UIs und Dienste mit Internetbedarf, `backend_net` für interne Backends, app-interne Netze nur wenn technisch nötig, Tailscale für Remote-Admin-Zugriff, Traefik als einziger Web-Einstieg (Service-Routing via Docker-Labels, File-Provider nur für zentrale Dynamic-Config), Komodo als GitOps-Stack-Manager, AdGuard Home + Unbound für DNS, keine produktiven Container im Docker-Default-`bridge`.
+194
View File
@@ -0,0 +1,194 @@
%PDF-1.4
%“Œ‹ž ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 6 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/Contents 15 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
5 0 obj
<<
/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
6 0 obj
<<
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
7 0 obj
<<
/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
12 0 obj
<<
/PageMode /UseNone /Pages 14 0 R /Type /Catalog
>>
endobj
13 0 obj
<<
/Author (Claude Sonnet - Read-Only Audit) /CreationDate (D:20260505184207+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260505184207+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (KalliLab CORE GitOps & Design Review) /Title (Homelab Audit 2026-05-05) /Trapped /False
>>
endobj
14 0 obj
<<
/Count 7 /Kids [ 4 0 R 5 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R ] /Type /Pages
>>
endobj
15 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2097
>>
stream
Gau0D?#SK;&q0MXR&@eM6CQ)Y-0_n7c:=A":+[7c/cl`O30H1o89me\Y]50j4\LnOR7a`2j]I9!jmLEa3BK#Y6U;2GUA6lT'>;,WZaYO>9@n4t^?sDB7s]6)EcY[t8&S<PmQ5I%fHn3jag!kuHs;8q90jQMDAYnW:2#DL#,I[L5G-S'&4N`t[VN=^A:($B6db@H,<N]bDLZ=]Od*`OpI>kBU">9Pi3Q_H'40>YGBOoKan)>^gPr=,TcFu>imo56nB;F9hpVEZiik_UZingCYo=(.mFW/VV!Bn/IRpb<I8$O1bN77nZKQ_Ajp]./FfHp3*EQE>JDV/brKXO82VNm0"^lY-3taP^<AB?HN^#dS[mdk[$5nKW!$oe>#8Tk`0/1l;1UbAu7cW0+Qe^_bh8h9jg)=DuRa6,^2._GQ^H+RtW#KI@N#'gN:ZG9gkit_,^E]EK-_^!.Fh&sc-bA56/TSG^>K(e^JE-G-3.$qX!GUK2NOue3',O5?YKmI:8H5O.XP!#&J0_+:R:l8=oY)`SF)%A#[oi`Eqf;OGXqR(LH1;$=,.*%GbC:M.B89n8GSEP3^Yn6/mml/Y^9]]R_Vb_Df<<58i?C;aT\AcRqMoQ]m/_)R)EY<FRRqbo4"c_>2<?pN<Lke_rne!DLP0n\%$T\QF.OQjckurs,ht4Em\MZE4Z`^u6U;;Ta7ct5R"a`cXSM6+4'1n)KX)q3`c`'8&I'+I%dNcmo"W*<51]la?GhV9I.'G)+6L\_;e"&`ignY_kb'*HaSiVim_e;DG^KhLhA!=j.OpR<.-3<t>Wl&V[qUh%c?NSqR@5_,gsiF'$!cSQ3lt?dE)i2=Ws*8Y4L(J*9?k"A(Q-sd[OtPTTJi4\8X6u;V]/pc\L^aCF-aM8'^IE0<P@P-8h)s"gEOg5.*,>pTK5k_<TGGO?icPqq<u**O'A`E'I'JLl7)&$U^>@(=4[9GKjqj</m?bih((GJ(:mn+4f:4gJ)G,Q*DE;]VIlAZUP?V6<3r=i*l(gA._u0[XZ<Fi2,2rBEWZUe(L1!P]<S43'T(dK>qJ'8$-nV2_k<+_Q]tSCHN\`rTZ8oAoYRWY@.)3rX[Q/hCW[<^/K;c4SFS!Wmcc6R:1\!X9K@O(Zcma=bD\=(idpD!*o>ks;KF2"EGc8s"FIU#GbM"Hj5?)$bu*(QlaoPdCt,PnC+RB>PM,LZBs_N@&bZOiDg5P&>4\bg"<`Tt*%T9:+W&WNQe%"".:`VZ$%D7H>r;SCB_B>0rc-?^]$#2,c1&7V-ZqCfOj9X&.9jm_8H=]PY4%PBiZa1i9U=E\p/j@aKO*J_GIq*mhl]/>Rl(70rHlX1P`JHIr;T9m\(B'5A?jah7jLTO%35b@H`_T^Y.rT4O^.YX2RU#'[&m$r?&Kjo1t-6(XH4,jU>0*WHD+iD?M4Uc-Di71XEL&/SRV,F@Rmk9?Kp,UjlreF2/9F:\fF?AIOmWkW#)2[E9'i4Ho<GSrY9Yiq&K]&X-(*`X\V.cmnQf'$soa-Xd&Ob7/?GJ!6)nf_qf)jMJ!Wc>:Hb>frEn2E0)U*JFGYdMUe5h+*XpY.)&+l<Pr:@]]lGakJuCo4GN=7$][G1$_=)0r8_osc'HqVdRhEVF.OCtYO`UJ_a-DZ,V,um8Y.Kc0`UKi_rlisTF6h(C!$30R4:294m;lEj9kmg7fCU'!:P&b7f`:ppcuN`TM3f?`*%*'qVHOfn5:i7h`X3e^"-\dG7jB^l:s/P#&%5*Dbs`!0#$L7I39eRRYk%jf+U6?m4ZUUgI!FD'kQ^e`GUF-hX&G4DSd8SFp8<Z9+D4_-/<I^h$Gm&gL//4q^IbB_^Q#k.9'lr.1H)&De+nHp/>E'_[O/VBS7S`JWM)bEX0E&+n5G]Z"%"mM>ds<_9`]RQ4CV=&>h1*_SbgEK]t2E"[Ag%&7VTrfH(-TT/%n:$19+X7/hE='mW9^'CU3*+N!*L!Vc,."64Fjr7fNj6f3j3^%o)E9P`.:$[sMNGb3@fDPhfMj8_t9RTQr$F'[jOR/WX4gA#3bYnY1rNMfX01)\@%Y"aCop'Z1A[XKfdXLY9:[U&0*.SFE2:9@0\cZ=2;BM#ttLJIgRls+?t~>endstream
endobj
16 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1616
>>
stream
Gatn%l#59H'YqKHYB]?1[)*<]Yq]Bq-,PeM3QN*tlE2V1!M7Oeiu'm!QR:ENIc"O"U$7])8e-6m]]8ahnXrH<hY-u^!4YSYD!6H56^iSXEa+W3_]V,<GiCXAaDZjlH@Db\mp3[CGX39jINiKIlih:Z$3CJpThF\qn7+Cj7BHhP6MBPP/(o]d)d:X[SQn)^lj*7AOo9*mdKXW&>o:+La7"6aqglAER20af`J!1$C^.Rm1&qR9WWE@[G,kd*i&CVUBnl9W)Ua*XI:Lgph##6M6VSV)+,'WuV?q9qi#`7$$'").0g)f#AJeUq(?m.*3c<"4r7\R^ftnV"nZ_W^?b!3p,e-n#6j`BcQ%mE"Ja%Y^0V9U[rXN`Yqecs%gQVj5qEQG,[B1K'Ybp+[G-'Z*fCV+dGOQV3K8r_3>?Z*-`p**cOfnqTk?I'!lg<8%QjuX<"6Y[^K3H,]6(otKI(jF4Pq(9M_8NSR$3n!6^dJJ:[AjRuL81Ud0Vl5)k^<P6a<a$\G'YMQQNfYnVm`6$?>Mna^s0FK\Ps8q:BZB5TNpt+._1(pdMc3,4[SdX8>5e$+SBqB0uK8-agDPLECpG!PYujSdnQ%RqZc[_PO9\:,h$;N)rD0h^D_&O9#l.>Bg3IEdqSC@p0p.uC-Zu"m>Z&5_>CUEV_]29pS6SQg?YWllg\NgDFTsa2][lrVfYSn_lC"1S`<2u;VGeL<Y918$kk77J^/M"RMhrm?)>4;RG[pbaEW`AEUL?$AC!&86Ye9M#/XQE7C=oK'?WA"9)*@/]*C.Id"Sa]jsX4q0q_N02IBq(9nuqFTXdnLikt)W>hOa7<-l:&nPR^5(D$l4c7^D]aj#'93e'Aq6rcDQ5];d9oD;Gud8_:?69RTUWYtWXMm^?u6DASM4>RieK7`qh;TY>).pt0-9[V`MW?lu7+V^>Cf-]cVM-Qh#'4P)PK?$Sc3+(c<WO()-WN!-#h3r(]3_n-MpL@BpDZ3]Ng:FMa`a(WlRKC*Ka>q69)7:+?Q0.4;Wn-;PUiI_uS@.LO\3M6XDTF'D(o9o["OZ5'a(QO=Dj2d7EJ&RK1eLomS17"2CdD:@HfpmLoH+VMgMXYOi6brbd%Wa<`.W-s[qN-NVeF!OmASS/+M$9eQi^Gga,T+S9K*#"hc8SV.eoUfj&+9Y^s]]j/Ta#]6l,acKQ+"dV3*H&7BnN/b7,34cNE!t+Wc+pcs_#u#Mr*7!E*D\W>2*_A@U\\9`bEeJs9@!BO3MDTl"/8X+,\SD.j(de4Z`#'P`=s8s8IGQeMj.>H?1KAsXTnf622.Ai,/6-'7(`cq[<G+UWs88V02_gCZ?6nj7f1bfVMCfHo<*B2@EZ/u[<&>PEP\BB7HRW\%O40r*oPYQ&hoCcbZ^EA!J2V9s,HI\#>DP$='Ao2j1<c35-2EE5/JWEI4d3uc:8<1j%(>Eb)pblmU*WJr-=HR!LD["Gk=F/o)<bQO[g.%P_7epb;!m6[G10Y'dDS."TujAW1g>F?cEc5_>d"Uif+kb2diV\XqScrl(l1<Qk=MU>;`:bRfT%G*(:0S@#=EC88G\7m/&P*u/;!7\pNBS"MF9GccJXj(@K+AMQKp24_lJT+U(m-@s]-iX57.N;C~>endstream
endobj
17 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2759
>>
stream
GauHLCNJ5g(B*Z.EMq7rDT8LJIUm&7<jq)AlSs&6[@Zbr`WM'j`/4[t5-3DkrU5(5;DOLeQC?(L:+Ut+^jVc#EBo]rr0lp>1a>%#e*2QXVFBO49Lj;?r14,QUYh)R,Od8A-/Gpp#P5OgfV\%70fp"M*142&3jqb#kj"9$a9m%DnJmSs:@Xeu[Bi<9m`MZLG$j(5jUZ2l4o(jr`<*BRcLl94ZpKrXbLY48j,B#%=dg]+fn@XhLBGug1=(3&@BY]SMCc[91rI;I+)(R&NSd9LXa?ZXdFDdEHJ>j922uiYEO%YFm>6SfH5QWh_-pbk$jV)e>c<FDBf3=.3K(7KOZp1%s%X[$JqeZ@CSO!dTBidl,3kQp,EfUodBhQBqM(mQ[c%;kEZ3N2+S8J)klu_#^d#^H/bj&WT&Z:sI+S4_OlM.A&;1&g.?1S1:'I6H<Uci%\,A2*Q7be^R'm'l-f]>s>pNEJ:?S-"&eQF!<c5IW>JD,!p%to9)t_<aLIe0foD)trTi,eXp#hd#./++@:b?9::TJ$GBlr%7>Yj\GHW_A"OLm5\[t)"(1".gD%i0(I!u\[7>&6#BS#sf@10GLLRRV?ViI[d,G\!c\j*ScL>c:(ZAKq.:M2ElaN*fi>WLls&]V%iY9]m-bG`n1sHo`o2luuO/l04:"I$s+mC\NlG+He+(B6:uQ.2K7H:;')M/Q59aMBY0pbje+%[BOR1YBfeGS^<6A.7L>V_^U4%:0/<cOY\V)#gb%M+r!2A;!YM">U$k[2QCi/FiK)6"oZl`[HY?R%nMf@mnStHqgRb'e!]0'OAc$35b[taDgF$M5^_#i+<EjFi+tS]ER3Ak/q7(;a8&Xq6K5Bng\)UDIAoO/G&-`lGN>jjLW*aYjkGc;/JhSOOD/pWcgtt@QX&j_MC6"Pflj;UBYH-X)GKRbG%?s5Jc:Z$TNEB3]^!QW8pK`lq7:@EeS`=jC<k:]mI>$<?h.ojG4]mf??dBf.ab,Qe,!"/G0c.L)pb)afX@X2T&m\"pFQ/p"4CG%S`>`aK?3_t4$'u:pX*,I&Q-MCHc!+OA]nY1.Mr!t@G,>elu*T(QQ<gXQCsM1jMafn^B!^s@+Nad_U>U^.j>XSlbEY8iprS5$mtq!U^,EI"&I9\E/D",AhQ:Q,=r\X=@E&AfQqAHaE8bODhFY7RD<K!JPOmd^h>l^2V=G5W0mqH2Hs).B!BZnS#.6oVOCe?J!g^bC:f5LbcV3<oT*??CF&:pPmk0Wh*#Q?Q6q(S\P380ZC<D9?J+"(h/p3:V!Im,Rt.-bo^R1T9dl=*[J9M!LlSRn7ZN1d^k8#b,uuIo(<fCtG/Gcd.CfE-F[G]m82rd=p&C5T3_ibms2J#VP,\TLq(kFh6-neo%kJgc,1JOF%-'%!HD;m'`fa;2`H7\6JdUU\_/VtY'!GU%o#-LJ=M%/!.-2:djK.F:9I;2VQck!oL6c5^Q7t.gY!WHb=P)<L@mO:p=8INFOt//pSqoAXL3X5[3+isP6;bmkEmR.9:Kk[7+*!J7Nk:skM/nj3]G(3EEaCrg`J-q?oWI'FL?Fn)57`JZ]q<<2%sP_7kl,bX567=/1?bAM)=*lOFVF$&Q+AfP!0?VOnkMc$6(3;c`@R:.6-j\.X.#d!)e^4P.Q1RsC=cctK"73o03V<#heVsX)GQ>THLk8R8GbL1Jj71i>3-mU=;`Vq/]kA5(k3p`/DFhX(Q?DVU'Y=\3.cHV3XrBkXBXH[hJS,<7'Rf9c#NL=7B)jjSAMjM'Y@i2r6mDqSjEBra,k`U2U=:'mPr?*FiHk8TK'jjADD@Q4XdZ(Dc&D.b%=p(A6ni7%saGYYk"p-TNo:[nd5&]lpO-t7Q9A>"0Ct@oM,Gbs,%`JIFjZ`T-FLcMKsS2^=.ga#2g58!gu0E4'%ZJ]Vt>4jKa'/9LQsjoFE"6:3.!pZW!P;T<]rg7B*1FmXJ<J72>/f;W?Q#Wb^DuBRd!o72!We3_bKi(ppV1<qj(I*9aC@:n/9r$=MHQjfOXI)!VG6Tga=)f2Y5h@oeqS4DR4a2"k&s%_^]aV<Y61%?iPMj:p3?4i3h&O[h8]Z&`-H<HqU+O%CTiWL!\K!j4]7JtXs\&Z9TZcF2M;`[A/$b_d4a5p3ReI;.PUKjP3c)^)gK%Ed^uMmmeVHZRrVICLAh?/FFE5E#g0_nl+dhuC<:hM`PZ#QOYH!A:n@*Z$BfRj%Gc0+,F,3lH]Tl4VcM5,gIQd_ehLBG&VPq(:FoaZRlcg5&N4TM$p$h-FaHr2FsqJOGFY"bsI>(dJGDOLUF",1Gk;"sR7%!nO#aj[)_:.1:Z*^+?g]_\N$G"ECJ4Veaf`nlQ[V-&P6:b&Y?D<lE8)YgHY:.3PNhYMY_oN%\%Rpk$rqkf;)'mBC;rj\`2R33Xb<[]RakcS/Y1N8uLSLhChL.s-#]r^8Wh*?]i\=>.(@4ZPF0]@#6Ie,`8#'eEsKWVg"X33>F<cd6WGR:M`Z\s0>i46*sOAt))ja)'N-$>;Ko/nghr!!k,B<".$IkaGc;3O$;1@ap;`d"dEW9Y9=RL8.q[9Bp_OgYuF\)FXq#UCbI]#F6<'g7XtA-#_++8R@/7"%aV99Yi'p8IO%UEBW-r'PDSha]E^"g&N]TPbD:8Qe1&<X\^fqKZ,k-m>?ph+6r5eF1=)<UFf8ln)Nq6=8gTl=+0H?(OEI.V@M1;a'\p-7o[r4-#l4e0L&_VM5]VM2ZoR,neRmt?JiKj>FT(M'@_q#q&ZpKOB9a5^t?$L%Nait^R7$&]`~>endstream
endobj
18 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2447
>>
stream
Gatm<?$"c/&q0MXfU4oKm?f`F-+QgWA#F5BF_.tkF'h%k#YBhC5UA^sf(@#?"-B7Qe;6#aS2cNT&D4=9cImo!o'j]/s,M/pM]nWsqr@u=S<+i'&Ii[CC3]<iM<f;JTG*OT#JCE3]0R/-@>Ba)M8[;;5BPp6$n:V`0%M<_FPSWCU4`kKPFkV:c2i(CfPKAZp3#EBHr9bs0jf**4Q&0G`JL)VJt>co+E["B_joS5@Tb$qfn_7&#$9m40.Q-&IdL49ckL^jicm3Wr^ap[2#PH8f>.85M!^CR6m'FbmR^2ZBMQt+7XMH"I6S0jMr:fr6sbOf&R<ZL"b':g2W--/*b=uui;SZ&X+CP'Tq!KqAZMi\9Vu$frs-MJn!F5P@Zt-XL8!3"#u%J55pqP<El/fdpJi=WL$!2Mc^lFT?uWcnF(--h$SQO,dY,q%9eQbQ!H=Ha.1&RuX/@,@omG@5%W;8=Pt^G[^q!96Qb$Q4kDB49EOjd0pDX-gD8:'CLnefT^?>5'+DQCL]s&=N*LBh0o,D)2"ZE]KS12p7a^/i6btBK9B-YBQPY-L.(PPJ^\J?i#UNt7gEp#"6j`i]HY)j=YdZ('#X246`QWMM"rF,a"i?PV/>_@M*:9*iCU_qFbbo&1B!dj@&0#XCN-tgM(n3W`[)rV2_PFP`dSj;8FOBb[9Ur[O(cH4V,4%!iC>R?6,gG5S;BM[A7M+aJOPj9M'\]D"bg]56XT2dV<A8:@6E!4*L-&E8Ne-3n<l[YPH#toIZi#Y_HBO>%tT%e,Jl9e'7/G^*=N4[mgJeCddTuaI%6lhm(*udZ2'IA"2g[!jcN1R`u6/:3Yi*4rb;\4[=+AQC&F"_cALaf5XqP;9(CB5>m\cu5hS<j'$&U43@@/Rt/F:h4%Ydb@b;=?2YhE*p>q(s]R/``sRP;@mbU1DM)B?m<92S'n$S"cXrX"E610m:-FN,L?TG;B#uUc=p)$Wj8_:o[?'b$WH:#1@c$H%P0.QXWpZBF#.s.AU<A87;W!Yu"g&>Z1hYb_2[i_;-N*@BGuIBm4DCqn>5@d,!moCVb0VF2sq)I.Q@a)VT/p"%nSEVL0P_1cilR7<A&k-$,b;cUP!dWWo13F4KK!`._30hOaMs+pB7TBi/8f8?h(e/l,&u/@*bho:h+B2+;^E-_&0ecrdq,Tskj5a(&DZBaM)n/#6IqXYX6^,"k_tZo"2f)U$;++F&5mB=316cDa6Xf(_C[PPQh1j#Jc_Z-!l_jR-fT2:tA&dlEC&Yl(KH?)TqgWM36u$sK,=)gS9-Of;D]:p..Pf%alV\Z@`?X\Z/Na(SE:T5E+oh&6LKrLr0#RTF/.i"R4\/W#?c`.YU3GRLdD9K1IK`.)jIV\qB9B>\_V27S>5!RoY*Q8DZg%`kO):l,VkTN;irTiVpKO'X!n-1W$0SdV-IZ>PGYX=T\F+e)ML?b'f0:[*(g<VZ[fMS_OK$@BggQ]:mTe+@3NfUr6K/@fH6FKLZ,UQIgP!Nf5D2C=cn/&#p$7K)79)Qt+OZ?.^ekrWA7/],"<]^B%B<h+N>=2a5O^<lADXDoL/k=V&W&4(p^g/@I'C%J,0[5L9+T`BXb,L*0timO[8Ru`4$nE/^/)7*%a-=Xh]44Te#U+fm^LIBWm5)--8.hm1$O=]`<O(8hn-DAJ"VQ08N$sh0g*4E(3[HsnnKpFO;\/8pXPK?;ChZ8l+UKJ</".K%V[qPK78Th&q<@_e9d8$f[+`\pK`4/2u)SE:JUp[tIFFc&U8hV&(cO;V`lJA]XjBPYEm!S(,TKZ*n$a4a.QQbhRqN*%J6EZVtr]jg2("L74fjX8!%"g]G`'sB%<%p6NMt3pBhPdnjko%eMF8D64brG52KHB0"cm[&X(5'7jdgS/Abk-.[&c)Mf\%;."$\E8D/@IO0lZJ?jGo85#*E01XbkHWq`[V$1G#78,WW#._#._DWSC/*X1su!tP8,P)<:/D:CT^As13::)raOiR<48XtC_0YpFoT@5KU],Z(i1$e)\F"V6qjW_)^oFtnIf^FnsOP`LiRB\;m'f*0k4;jO:$4+4ir^G7jD"8;N2N*$CO(NDLCL\M/_#emIW7g>,,igo1bE-(OUPfKp[[npE+o7NqQif\js)rS%Oc$.<%<(#%nn["P@f<)#qNR`Sf)q4hr.3dS-"I@dJVS*nb700GSgCAeif5ANm4$,$]URjK3PMCH7AiiI3$;EPP7U=-!m%mlp/aiFWME2st9ASM6CC7(gU&Bt]f'_g(82Z1u-+NlIh(Ggr2/^![0#A*VP!nBl2eCQpnd1C8mYnRY]%.pY_`r)]\ZSF0p"5>&]Bq*%FNX)@iRQt;7b?&uB>/0XY05ENk<+G)C'o9"`DF:JK+I9N;Z_h[42ca\SE]jCe%GAbs\-K:J*R3Ck-ei^CP>Q',Aq'aL8fMg\@lF_Zm9s1SpR-$;:R%b+"LS&Be3h?0j5J-1m~>endstream
endobj
19 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1691
>>
stream
Gau0DgN)%,&:N/3m,Xk(Ak\!\8<@q+3ht>AOuc?S99Y.F_'19^i^MhF:,DkD/gq;W>rddBg%O')/\$-;(4-%5JrSZpq)qLS?I*$W#6i7X('/Q6"1&O-AiBKH,.9.lk5q>.E6qCJ_^Y>Q=H#"_fVb7L%d2aS0#;N/Qqt4:*!(bFkgcVgbePI+%NTBG&sqo;0RnfUC80\iFGnEZ6j,'6i%n,sR'RK5?H=CK5KI%gqfME6/9mMZ.RA,RLN*LAB#:iZr:q(>?XWS"Vq]XH!2?*B,'r[_@A^#].g>l:HY11pnML.K_9Yi.@lp%o_$))lP\mY\-lH3\ZK1I+msj8*?,2G'kLMIcP4*NraXldE^W'`k&I$oaXChq#F5+T$T\YFl*XDY#4C-iW%e?rO^lTr<K]>q>H+W1+Jg1?Vqgjet!`KNB1P,&l#p'I9jD+kL'_Sc*]NL=_ce$@dWedGFH`RRUMCK5R`XD_5Wm4Y1)DE0siPb<hQNIVE&rL;B4U2jN`1t0Q$iQPMP!IZkO&%q6gHL<[g@5k8(3G\LI7Ro'h]&!Zidf*Dlsr/9m-Nf@0/D;(F:aiE4*g4M9YHujLQUC[.S0C7$;u1@"o\B''Sk2aq2+\@R+#*1rJ``"4lE"rHGG6:h'bB[]+)0U*QB+\:`&5u)a1<6<L:DXFu-SO9(ulXr\i&tY*LPo-/>Nt\h#AQQ^L1N$fge:36tY`aD@R$&m]prVQSKae5b"@P^oIallk2i0dn[FXGk=pA_"VUW-HC^dHn2%MrFo]UkI@*`6XQOLpU((V$6>_U^W?C6l(m,@eCkiPB\7FD)f3LYN8*10Cgq1F(fi&PHteN3@]9-2(V^sTm.ZM<<iW4_(%)F[5shAa0rh*,-KooQ=Jqtn:ca7[=!^tF9t1l(60?Db&E8#(r"`_iB(&8WWW_i?P/RZJb=A&&G8bplU2bcNQJA"->aZWdq6Q@b_l3`<p-bnhWD*L@9qr&]rL'RW3bu=];J$d5/X6l<!HE$'$lnER*Q;/4(atRh"8lk-':(8Q%D`na?"p\.^gPNA<l-Dfbke.FnjUJ5)CKVgBP@Pnl2g0$G.`)cS2:diI-U+;$(?.AS%R.a'!T`l?+3r=?=5"csI,qVm:qX$"d;qT#/0iDa:G7e!8(^c%1"1mFaH:#fkrcR.aP;Wd0cZU"OfL^!`O6MTIu[b=jP;8%6@5&Ya-g2TH$f9]G]/_ZRl'Zga"$aJ<^2'B8_0EYM87gSgHJI-HVDrDV@iiX*h>S<S)_W"XD[a@Ko`r'$%%qV1g]W(U-\^9CraSZ'AWZ8a;q9C.:2L$J\2F%/6UV.VV0^sg\Y<NiOh4C<:cJOem31B.:eTA0)$7r)!mKiGYIVl_kIScDnB[6B.0C!"."H2Qs\[UckN&`q;Zf[EU%D51Fs/!3$-RV%F$gmiO-.Z\[rD=a>@Tnh@Q]ZQ.)ijl[O]Desgd3DXRK?TaM$EO8ujlnfZYu`aJQ?TjuM"REN^,?c`'Y]N^&"B2\[j/cKhHX0Yh.rT?osIJ;>I/J)K9,EV^tt/C!pT$1^'dZqikq[=QUbCgoNc.=/q%6WF(s+5`.3n,NQW3Td&(ff!=WhR/q)3qF(s+5]E&FX)OEO9ThXS,!uT`K/inNVc.:te;"u%:0u+nm#gNF$e!l-O1plL-DP,/Tdhn%kf(&UARl]^/KJVTQl`oafN;a"A"#*F~>endstream
endobj
20 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2670
>>
stream
Gau0DD,]4L')p1[kh<TYA1MgS^RM8#5[Xj;+[]kSak]ss-o70%Zt%:cZ5j_Kp:kN=IV4EE$*sa9)G0nuW;BH$#T3>tW;dYO^tTFWS.QkjKF&2o[ip,0PORM>"5WHPdrRc%AmjZ3DV>l24:o[<NgkH+l&KU`huo(#lWc,lh6L>@"[O=-q*7M0_:'DR3b[U>N[;H5qsDNZ,o&A#`]D/jh7sF)pnjl<R:-;TQVj3WpX(R_9P5$ci&o>!MSnI(AgNh!l"Ys*hle1#'uHT>]#MER7LV-d[,,iIZ)0Nm,mAK8BEoHq[pf&i"j_h;2Jgm$`W^Zr'!o#`7\a(To(9Zdqu?/Z&hp)M>asnjqG7o<`8cF5+7V$hM6>M3lPZkn>D@o!+K*Vu4'_D2o[E>Ydmj?G.ko@HbB%N]_!G@eB*Pd`ke8/63(RJ<ijUCV]qL%U`/adR^lg=mYT];>86=en)!2b[#(30)Q50W='jjiK)*37!=>!\nd;n=;a<F:O]YLef`fk\67FG&^2UPAtpPA-cL@b(Pl@E_q543ah]A7$:gNI"S5pr'CEo/O<Gj]IkNr:2cXf\1T-'PH[m?$[QF`\n]h<NN#\b9&_7rljmqp]SK?RMZq^W4"FTa3N8)(1&QbUMp/Jm"l^ZX8(p'a9Y!);W$EncR_@4EIae1+Mr?ds-nK(^s*[9*$f./#h]/Wh=d=]<RD>):#-mdoeYlA#PZpZfG?9p24t-068<#>*>P\rKjh/MK8*K/SS&tOAo1%q33CuZKO181:`0ta)IN+V,f=,WWN!0abitM=oaU!Ws;*k_CeB*m?lA(=KI7]pnh[c#`D[rYjpLi,ei*oA0Od8Q$>h&g;]F3"KN>$^C%/%aCBllJt[V699q7ONKAa7c4dXf&/9]fp#XO_4,+1sQtTP59>A-n!DfofMNoJ^pAtiqFT[Z3?Z8'B)>^4!@^0BLWAs1id&pIjVt;UYe>-OknH[!#5o?F%Y,[Zkid>$CPa;``>l]".pBZjtZiJbb:b?of&9X_nWQ6bj(l-dT*b',1F*/JJ0)giuP]2L6aCpuMmcB%8+"NeWoJOkW6I<$R5I,Z2[1K6[LJXmhNsLl.-_5Z1e;=1diq1L-Mc9"*&i&L,04DR,N0e[A]b=6Un`\4$:X\P&e$k-6Kk8-K:W?1\Z'/##.GkS0VepqA"<C"EHcDqHHpru=\BM9AcJ^Rm?01lcX()7Gjr#.G?YGeu*_OWuXo_2G9lDJ@fe!q#;<da%j)I:5*:;0GF()U]6XuZEY3gC!jE:+]m2;WnEgo*uIsla&WjKE;dA635lNin<`Te)q$FpMqX@+l;4VZDFr4lts7NUJN?5GDX5lqk]nk@(6Sr8c\n[+?*rDi]Ti7#eY=b\m5NM&!VNY%)B\B@=?>t9,TU1%X0q^,kbZ7WpqCZ)uh>d<r.hXFLfYp\3*:T&3in5p](#CD*$P<!!+Wau$6]?$]meW?ob'i.Fq"`1S7?GuL%QWLC''ht-Y=Y:Bd]Af^:[;TG6DT3$4?bj$(];tit'fJR!AB6;"Tn5_%=0]K"0WPti2:WR?EeTN'QeE?l#;BGGS/3&k>SI+6>#k^>,!eTpNI@gE@5$C/E*;''aJ!EPq`_HV0Ld-'K8"q(:C*+(hf-a)Kulf&0I;M1'R"Q8.3*M0fYe5sE7dlf,CedYRj'(5jKP3G7ooDu&am^E[l;,E=^]tdB?._E'1#I8Kf#M%]>r"`c2&jMF>mmr5<eah?kZ>F"-^fDIJX)`dXu/JhsuA-Gs_`gHbm3ikr6pK;X')/43EH:%i2$*ndsR63q`0]#^iCci6>[p/4Xilq`)"2KOJKe$Lo?Me4N35"=j)?+7GIbJ_[GmL5*:s^'"T3[&(k9C3hqSq)iqL5+Q$:bU4dE8bgV]r3,q/*oC3trptn35[(#f9.].bB'@/icli\`i:%FB8A'AR/&!V:MZR:SHL&F[8i<R4rjekL<Io4:Jcq%5kH\G0/'?"DUGc_H=<lF^MLLFF9n?@IQ8G@q(K%'$P%'lf7q*ms?&\t!?d^3_)XaQ*(j*:+N>.DP4AU51jsqGF=6(6:>Xl30md"*'%Su1Lb4l)4d4eq/YfSIsZ`G5=1hh)XK[(;&4U]m2f+=]X=Q:I<-G5:+[90I5FB/44>0_pV<qoO-rScp1P\=$I@,T'lS#:N\oe,JUNOXR;LLY-Ed'6^GU1]G*33QMI=<a>FKd``[:uE%fMJcK-j\*&#U6:-;kGj#B4C%&lEN*?ECJer#!X240W3R^A/0?qF[R>SfifD9!9\MP6gHZjDS7t3KchiFNq7N8JP)E!Iq1ACXP?_W2=1=V%Ig[&)J#M::>VsP\8\ReO;nC=iUQI`78t@92rt7tVSq@O9H%KTSQ;M#\5)Q[)ISMV$oD;C_nWZVteIG9!P+]fA&[/XBTq@e9?Ms4;qh_Fn?YZE2YJ(c74NXp(?;U#G.Oi7]<6.1ar6k?37p&86eu3gl+`0))L"*M=S<pa`2GTAQ>-/G,N8j)Z\rfRZ.bUD6O$U.S%`I`BLpO:;+$;@<l*i#nTHu4mDXO-<p:e#FU]6HX4<6o;>Oo2\N_0WKTXI"+7CRu^SEd8WMZe<`%Jg5jamK<@@qIomIC_D+gmf@>a7#qp<%&d5:<kN0a[u9NCa.!7'\Jm;kFd7`gZONFm-0m-]f[?XF8s;hHMdNT_4R4A,6%~>endstream
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1010
>>
stream
Gatm8?$"IS'Re<2\AM6RY\Hc/jK0`a['[W3Qs#0c%&:bmM85:dZJ;b0rqO0Mghpf:^r&3.A3&)KkFQau3/$q__sc:ZS/-)A)[A!gW.7AhK&R-_MTMJ((1rCo@piR&&cL$D,e6UbD'kQ,B3_`%OqQb:B!a/kOYI_";$%6$H8ZY&"f*^;U%8k_`Nm8:ISBTR5c"p;A/?UEa[OJu'9WflKn6!l<S.(>Xl/<qq&'<ShAE>.HIA)4K*XmYKJD:YU%f&8ZTd03h?+DfOZ9V4Z1pN2!jtNhpOI^e7egI2%N,,4$rIj=>U#N@>]?t.2FgaODf,I)R.oM-VK4H3lR#^jFoAFX]s?`@oljs/*F^3^@?XA=.DqC-T_Fn14ARs74(b21`</0n9m[4q55A#.Z;9ZI*1an]["\>/U*+A<]fmn-(EYnbQ,TjkbM`?<oQYO6%F14hd;$:EW^@"IV^c9.nV++1md'KZQ:G5=m_tCZ$)$r+3fA1WPeh,VhPG>$6%dn"b(-2`:XM8R.frYMG.k"8[OjrG:4UA28E5%'*a]JVq\k0.QALEfn"l]bOD,?(r'2L;P=UWFM*[jdOoc`8np!ijs)5WqEJ(YrUkdeL<ClEpLI"4990+#/96s"\_MkL#6"`jc?!X5+Ba9a:7b+.@]^(gRoOud$H],Jk)01V'hV`]Q&E_i<2u(q,l_Z1bpXc79*kHM8;Wc<!=i$>9ZlVE+s5q-GX"0VC2lbH*7^l8*FR+Fq3$E`[>Y*-IOo8ZRaKfddW>rf-Xa)d4)=6eeD!Ym=;3J7:b;5U:YkTkDE>n3Hb-@uhN!Hd[rMe-_-gqNNc3N[4<5hha]+/2IQLs^s:@2><f6Y-tgC$V1#JEpqc<`&gK<(VNer@Z1Ek=C`")TQdHN,V>@(=WD(jt[.:05sGGM]_+3F"J>.2uqmFSF'US+RS>_7[*\)r0gN32*'9f@^U:\7lolN>_S@JSU$)pk*f9gie+pK-eqeW$WrP4.u<sD5^'.LCFWTIfM[j]L)~>endstream
endobj
xref
0 22
0000000000 65535 f
0000000061 00000 n
0000000112 00000 n
0000000219 00000 n
0000000331 00000 n
0000000536 00000 n
0000000741 00000 n
0000000856 00000 n
0000001061 00000 n
0000001266 00000 n
0000001471 00000 n
0000001677 00000 n
0000001883 00000 n
0000001953 00000 n
0000002284 00000 n
0000002382 00000 n
0000004571 00000 n
0000006279 00000 n
0000009130 00000 n
0000011669 00000 n
0000013452 00000 n
0000016214 00000 n
trailer
<<
/ID
[<d46a1b7909dbc491a240e4a50a35fd17><d46a1b7909dbc491a240e4a50a35fd17>]
% ReportLab generated PDF document -- digest (opensource)
/Info 13 0 R
/Root 12 0 R
/Size 22
>>
startxref
17316
%%EOF
+14 -3
View File
@@ -13,6 +13,14 @@ Bei Restore-, Host-Ausfall- oder Wiederanlauf-Fragen zusaetzlich:
3. `docs/DISASTER_RECOVERY.md` 3. `docs/DISASTER_RECOVERY.md`
4. `docs/RESTORE_MATRIX.md` 4. `docs/RESTORE_MATRIX.md`
5. `docs/SERVICES_RECOVERY.md`
Bei Hardware-, Netzwerk-, Provider- oder Kapazitaetsfragen zusaetzlich:
6. `docs/HARDWARE_INVENTORY.md`
7. `docs/NETWORK_INVENTORY.md`
8. `docs/EXTERNAL_DEPENDENCIES.md`
9. `docs/CAPACITY_AND_LIFECYCLE.md`
## Architektur ## Architektur
@@ -38,7 +46,8 @@ Bei Restore-, Host-Ausfall- oder Wiederanlauf-Fragen zusaetzlich:
- `security/` -> sicherheitskritische Dienste - `security/` -> sicherheitskritische Dienste
- `infra/` -> Datenbanken und technische Services - `infra/` -> Datenbanken und technische Services
- `apps/` -> Anwendungen - `apps/` -> Anwendungen
- `ops/` -> Monitoring und Tools - `ops/` -> operative Tools
- `monitoring/` -> zentraler Observability-Stack
- `host-services/` -> Dienste mit Host-Netz - `host-services/` -> Dienste mit Host-Netz
- `traefik/` -> Reverse Proxy Konfiguration - `traefik/` -> Reverse Proxy Konfiguration
- `docs/` -> Dokumentation und Prozesse - `docs/` -> Dokumentation und Prozesse
@@ -59,9 +68,11 @@ Bei Restore-, Host-Ausfall- oder Wiederanlauf-Fragen zusaetzlich:
- Komodo ist der primaere und einzige produktive Stack-Manager. - Komodo ist der primaere und einzige produktive Stack-Manager.
- Komodo bleibt bewusst bei nativer Authentifizierung; zentrale Traefik-Auth wird dort nicht pauschal vorgeschaltet. - Komodo bleibt bewusst bei nativer Authentifizierung; zentrale Traefik-Auth wird dort nicht pauschal vorgeschaltet.
- Portainer CE ist abgeschaltet und kein Teil des aktiven Betriebs mehr. - Portainer CE ist abgeschaltet und kein Teil des aktiven Betriebs mehr.
- Homepage ist das aktive produktive Frontend / Start-Dashboard. - Glance ist das aktive produktive Homelab-Dashboard.
- Traefik `dynamic/` bleibt eine dokumentierte manuelle Host-Sync-Ausnahme ausserhalb des normalen Komodo-Deployments. - Traefik `dynamic/` bleibt eine dokumentierte manuelle Host-Sync-Ausnahme ausserhalb des normalen Komodo-Deployments.
- Mutable Image-Tags sind auf die aktuell laufenden Digests eingefroren; echte Versions-Upgrades erfolgen bewusst separat. - Mutable Image-Tags sind auf die aktuell laufenden Digests eingefroren; echte Versions-Upgrades erfolgen bewusst separat.
- Disaster-Recovery und dienstspezifische Restore-Quellen sind in `docs/DISASTER_RECOVERY.md` und `docs/RESTORE_MATRIX.md` beschrieben. - Disaster-Recovery und dienstspezifische Restore-Quellen sind in `docs/DISASTER_RECOVERY.md` und `docs/RESTORE_MATRIX.md` beschrieben.
- Recovery-kritische Services-Pfade wie Gitea-Repositories, Komodo-Workspaces und Host-Automation sind in `docs/SERVICES_RECOVERY.md` beschrieben.
- Hardware-, Netzwerk-, Provider- und Capacity-Inventare sind als operative Audit-Dokumente unter `docs/HARDWARE_INVENTORY.md`, `docs/NETWORK_INVENTORY.md`, `docs/EXTERNAL_DEPENDENCIES.md` und `docs/CAPACITY_AND_LIFECYCLE.md` vorbereitet.
- Der verbindliche Detailablauf steht in `docs/WORKFLOW.md`. - Der verbindliche Detailablauf steht in `docs/WORKFLOW.md`.
- `nextcloud`, `bentopdf` und `grafana-influxdb` sind repo-seitig vorbereitet und folgen dem dokumentierten Netz-/Secret-/Traefik-Modell. - `nextcloud`, `bentopdf` und `monitoring` folgen dem dokumentierten Netz-/Secret-/Traefik-Modell; der zentrale Monitoring-Stack buendelt Prometheus, Loki, Promtail, Grafana und InfluxDB 3 Core.
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
bentopdf: bentopdf:
image: bentopdfteam/bentopdf:2.8.4 image: bentopdfteam/bentopdf:2.8.4@sha256:f54b9ed9c56b767e0098b525468206689b666323c2b500b9686c3cf41cdfa348
container_name: bentopdf container_name: bentopdf
restart: unless-stopped restart: unless-stopped
tmpfs: tmpfs:
-42
View File
@@ -1,42 +0,0 @@
services:
homepage:
image: ghcr.io/gethomepage/homepage:latest@sha256:cc84f2f5eb3c7734353701ccbaa24ed02dacb0d119114e50e4251e2005f3990a
container_name: homepage
restart: unless-stopped
environment:
HOMEPAGE_ALLOWED_HOSTS: home.kaleschke.info
HOMEPAGE_VAR_GITEA_TOKEN: ${HOMEPAGE_VAR_GITEA_TOKEN}
HOMEPAGE_VAR_ADGUARD_USERNAME: ${HOMEPAGE_VAR_ADGUARD_USERNAME}
HOMEPAGE_VAR_ADGUARD_PASSWORD: ${HOMEPAGE_VAR_ADGUARD_PASSWORD}
HOMEPAGE_VAR_KOMODO_API_KEY: ${HOMEPAGE_VAR_KOMODO_API_KEY}
HOMEPAGE_VAR_KOMODO_API_SECRET: ${HOMEPAGE_VAR_KOMODO_API_SECRET}
HOMEPAGE_VAR_BACKREST_USERNAME: ${HOMEPAGE_VAR_BACKREST_USERNAME}
HOMEPAGE_VAR_BACKREST_PASSWORD: ${HOMEPAGE_VAR_BACKREST_PASSWORD}
HOMEPAGE_VAR_SPEEDTEST_API_KEY: ${HOMEPAGE_VAR_SPEEDTEST_API_KEY}
HOMEPAGE_VAR_PAPERLESS_TOKEN: ${HOMEPAGE_VAR_PAPERLESS_TOKEN}
HOMEPAGE_VAR_FILEBROWSER_USERNAME: ${HOMEPAGE_VAR_FILEBROWSER_USERNAME}
HOMEPAGE_VAR_FILEBROWSER_PASSWORD: ${HOMEPAGE_VAR_FILEBROWSER_PASSWORD}
HOMEPAGE_VAR_IMMICH_API_KEY: ${HOMEPAGE_VAR_IMMICH_API_KEY}
HOMEPAGE_VAR_MEALIE_TOKEN: ${HOMEPAGE_VAR_MEALIE_TOKEN}
HOMEPAGE_VAR_UPTIME_SLUG: ${HOMEPAGE_VAR_UPTIME_SLUG}
volumes:
- /mnt/user/appdata/homepage:/app/config
- /mnt/user/appdata/homepage/images:/app/public/images
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- frontend_net
labels:
- traefik.enable=true
- traefik.docker.network=frontend_net
- traefik.http.routers.homepage.rule=Host(`home.kaleschke.info`)
- traefik.http.routers.homepage.entrypoints=websecure
- traefik.http.routers.homepage.tls=true
- traefik.http.routers.homepage.tls.certresolver=le
- traefik.http.routers.homepage.middlewares=authelia@file,secure-headers@file
- traefik.http.services.homepage.loadbalancer.server.port=3000
security_opt:
- no-new-privileges:true
networks:
frontend_net:
external: true
View File
+3 -5
View File
@@ -1,5 +1,3 @@
version: "3.9"
services: services:
immich-server: immich-server:
container_name: immich_server container_name: immich_server
@@ -14,10 +12,10 @@ services:
DB_PASSWORD: ${IMMICH_DB_PASSWORD} DB_PASSWORD: ${IMMICH_DB_PASSWORD}
DB_DATABASE_NAME: immich DB_DATABASE_NAME: immich
REDIS_HOSTNAME: redis REDIS_HOSTNAME: redis
TZ: Europe/Berlin
volumes: volumes:
- /mnt/user/photos/immich:/usr/src/app/upload - /mnt/user/photos/immich:/usr/src/app/upload
- /mnt/user/photos/family_archive:/usr/src/app/external - /mnt/user/photos/family_archive:/usr/src/app/external
- /etc/localtime:/etc/localtime:ro
networks: networks:
- immich_default - immich_default
- frontend_net - frontend_net
@@ -45,7 +43,7 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: redis:7 image: redis:7.4-alpine@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99
restart: unless-stopped restart: unless-stopped
networks: networks:
- immich_default - immich_default
@@ -54,7 +52,7 @@ services:
database: database:
container_name: immich_postgres container_name: immich_postgres
image: tensorchord/pgvecto-rs:pg14-v0.2.0 image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:739cdd626151ff1f796dc95a6591b55a714f341c737e27f045019ceabf8e8c52
restart: unless-stopped restart: unless-stopped
environment: environment:
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
View File
+1 -1
View File
@@ -1,4 +1,3 @@
version: "3.9"
services: services:
mail-archiver: mail-archiver:
image: s1t5/mailarchiver@sha256:94d7525db56b13154a14203f8fb7b53fac034f28a914c32da9d2e426b49328ed image: s1t5/mailarchiver@sha256:94d7525db56b13154a14203f8fb7b53fac034f28a914c32da9d2e426b49328ed
@@ -25,6 +24,7 @@ services:
- "traefik.http.routers.mail-archiver.entrypoints=websecure" - "traefik.http.routers.mail-archiver.entrypoints=websecure"
- "traefik.http.routers.mail-archiver.tls=true" - "traefik.http.routers.mail-archiver.tls=true"
- "traefik.http.routers.mail-archiver.tls.certresolver=le" - "traefik.http.routers.mail-archiver.tls.certresolver=le"
- "traefik.http.routers.mail-archiver.middlewares=authelia@file,secure-headers@file"
- "traefik.http.services.mail-archiver.loadbalancer.server.port=5000" - "traefik.http.services.mail-archiver.loadbalancer.server.port=5000"
networks: networks:
backend_net: backend_net:
View File
+3 -4
View File
@@ -1,6 +1,6 @@
services: services:
mealie: mealie:
image: ghcr.io/mealie-recipes/mealie:v3.12.0 image: ghcr.io/mealie-recipes/mealie:v3.12.0@sha256:8d962f611390a1cca667eed32a29e9467e9c01c523e2db3ad00f667372067f9d
container_name: mealie container_name: mealie
restart: unless-stopped restart: unless-stopped
@@ -14,13 +14,12 @@ services:
POSTGRES_SERVER: mealie-postgres POSTGRES_SERVER: mealie-postgres
POSTGRES_DB: mealie POSTGRES_DB: mealie
POSTGRES_USER: mealie POSTGRES_USER: mealie
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password POSTGRES_PASSWORD: ${MEALIE_POSTGRES_PASSWORD}
BASE_URL: https://mealie.kaleschke.info BASE_URL: https://mealie.kaleschke.info
volumes: volumes:
- /mnt/user/appdata/mealie/data:/app/data - /mnt/user/appdata/mealie/data:/app/data
- /mnt/user/appdata/secrets/mealie_postgres_password.txt:/run/secrets/postgres_password:ro
networks: networks:
- frontend_net - frontend_net
@@ -39,7 +38,7 @@ services:
- traefik.http.services.mealie.loadbalancer.server.port=9000 - traefik.http.services.mealie.loadbalancer.server.port=9000
mealie-postgres: mealie-postgres:
image: postgres:17 image: postgres:17.9@sha256:5b96f1a16bd9768b060dd2ffe55cb6225c4d9ef4d214a8b21eb08134869a97e4
container_name: mealie-postgres container_name: mealie-postgres
restart: unless-stopped restart: unless-stopped
+3 -3
View File
@@ -1,6 +1,6 @@
services: services:
nextcloud: nextcloud:
image: nextcloud:33.0.2-apache image: nextcloud:33.0.2-apache@sha256:39b2ba219271a22851f8409a7b1295d5892aba1696d9193500311c02e60591a4
container_name: nextcloud container_name: nextcloud
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
@@ -46,7 +46,7 @@ services:
- "traefik.http.services.nextcloud.loadbalancer.server.port=80" - "traefik.http.services.nextcloud.loadbalancer.server.port=80"
nextcloud-postgres: nextcloud-postgres:
image: postgres:17 image: postgres:17.9@sha256:5b96f1a16bd9768b060dd2ffe55cb6225c4d9ef4d214a8b21eb08134869a97e4
container_name: nextcloud-postgres container_name: nextcloud-postgres
restart: unless-stopped restart: unless-stopped
environment: environment:
@@ -64,7 +64,7 @@ services:
- no-new-privileges:true - no-new-privileges:true
nextcloud-redis: nextcloud-redis:
image: redis:7.4-alpine image: redis:7.4-alpine@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99
container_name: nextcloud-redis container_name: nextcloud-redis
restart: unless-stopped restart: unless-stopped
command: redis-server --save 60 1 --loglevel warning command: redis-server --save 60 1 --loglevel warning
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
ntfy: ntfy:
image: binwiederhier/ntfy:latest@sha256:2b9e12d56a538f4402da53128eeca02696c4b207ab7fbe031c27eca92ca9b86 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.24.0 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:
View File
+1 -3
View File
@@ -1,8 +1,6 @@
version: "3.9"
services: services:
paperless: paperless:
image: ghcr.io/paperless-ngx/paperless-ngx:2.20.10 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:latest@sha256:0a163e92e55698ddc45c0265884a86c7363da245ab4a909a8e5cb0f541aeeb4d image: shaanmajid/unbound:1.24.2@sha256:d278b71c592b2555cc802911bb0757a6a24f4a8ad7f5848720296c04876eeb63
container_name: unbound container_name: unbound
restart: unless-stopped restart: unless-stopped
volumes: volumes:
View File
+10 -2
View File
@@ -1,6 +1,6 @@
services: services:
gitea: gitea:
image: docker.gitea.com/gitea:1.25.4 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:
@@ -11,9 +11,17 @@ services:
- GITEA__server__DOMAIN=git.kaleschke.info - GITEA__server__DOMAIN=git.kaleschke.info
- GITEA__server__ROOT_URL=https://git.kaleschke.info/ - GITEA__server__ROOT_URL=https://git.kaleschke.info/
- GITEA__database__DB_TYPE=sqlite3 - GITEA__database__DB_TYPE=sqlite3
- GITEA__webhook__ALLOWED_HOST_LIST=* - GITEA__service__DISABLE_REGISTRATION=true
- GITEA__service__REGISTER_EMAIL_CONFIRM=true
- GITEA__openid__ENABLE_OPENID_SIGNIN=false
- GITEA__openid__ENABLE_OPENID_SIGNUP=false
- GITEA__migrations__ALLOWED_DOMAINS=github.com
- GITEA__webhook__ALLOWED_HOST_LIST=komodo-core,localhost,127.0.0.1,192.168.178.0/24
volumes: volumes:
- /mnt/user/services/gitea/data:/data - /mnt/user/services/gitea/data:/data
dns:
- 1.1.1.1
- 8.8.8.8
ports: ports:
- "222:22" - "222:22"
networks: networks:
+213
View File
@@ -0,0 +1,213 @@
# AI Context
Stand: 2026-05-04
Diese Datei ist fuer KI-Agenten gedacht, die das Homelab-Repo schnell verstehen muessen. Sie ersetzt nicht die Detaildokumente, sondern fasst Zielbild, Betriebsmodell und Risiken zusammen.
## Kurzfassung
Dieses Repository beschreibt ein Unraid-basiertes Homelab namens `Kallilabcore`. Der Betrieb folgt GitOps: Gitea `origin/master` ist die Quelle der Wahrheit, der lokale Clone ist Arbeitskopie, Komodo deployed aus Gitea, Docker Runtime und Host sind Ergebnis, nicht Bearbeitungsort.
Traefik ist der zentrale Web-Einstieg fuer HTTP(S). Admin-/Ops-UIs liegen entweder hinter Authelia/secure headers oder sind als Ausnahme dokumentiert. Secrets liegen ausserhalb des Repos auf dem Host, meistens unter `/mnt/user/appdata/secrets/`.
## Zielbild des Homelabs
- stabile Compose-first Infrastruktur
- keine produktiven Dockerman-/Ad-hoc-Container als Dauerzustand
- Traefik als einziger oeffentlicher Web-Eingang
- GitOps ueber Gitea + Komodo
- klare Trennung von Web-Netz, Backend-Netz und app-internen Netzen
- saubere Backup-/Restore-Faehigkeit ueber Borg, Dumps und dokumentierte Pfade
- keine stillen Host-Hotfixes ohne Repo-/Doku-Abgleich
## Architekturblocke
### Ingress / Netzwerk
- `traefik` nimmt 80/443 entgegen.
- Docker-Labels definieren Service-Routing.
- `traefik/dynamic/*` bleibt nur fuer Middlewares, TLS und Dashboards.
- Neue Service-Routen gehoeren nicht in den File-Provider.
### DNS / Remote
- AdGuard Home beantwortet LAN-DNS und nutzt Unbound als Upstream.
- Tailscale stellt Remote-Zugang bereit und nutzt `network_mode: host`.
### GitOps
- Gitea hostet das Repo unter `git.kaleschke.info`.
- Komodo ist Stack-Manager und Deploy-Consumer.
- Komodo Periphery braucht Docker-Socket und `/mnt/user/services` Mount, um Stacks reproduzierbar zu deployen.
- Neue produktive Komodo-Stacks aus `Micha/homelab-infra` muessen einen aktiven Gitea->Komodo-Webhook auf die aktuelle Stack-ID haben; Ausnahmen wie deaktivierte/pausierte Stacks muessen dokumentiert werden.
- Der `komodo`-Self-Stack ist eine dokumentierte Ausnahme ohne aktiven Gitea-Webhook; Bootstrap/Recovery laeuft ueber `docs/SERVICES_RECOVERY.md`.
### Identity / Security
- Authelia stellt ForwardAuth fuer viele Admin-UIs bereit.
- Authelia nutzt GMX SMTP fuer Identity-/2FA-Benachrichtigungen; Passwort liegt als Host-Secret `authelia_smtp_password.txt`.
- Vaultwarden ist ein separater Passwort-Tresor.
- Komodo ist bewusst nicht pauschal hinter Authelia, weil UI, API, Webhooks und Periphery-WebSocket sonst leicht gebrochen werden koennen.
- Komodo-Compose, Komodo-Secrets und Komodo-Runtime nur gemeinsam mit dem Betreiber aendern; `KOMODO_WEBHOOK_SECRET` ist bewusst getrennt von `KOMODO_SECRET_KEY`. Gitea-Webhooks nicht pauschal vereinheitlichen: einzelne Komodo-Stacks koennen eigene per-Stack-Webhook-Secrets haben.
### Apps
Wichtige Apps sind Paperless, Immich, Mealie, Mail Archiver, Nextcloud, ntfy, Vaultwarden und Gitea. Admin-/Ops-Tools sind u. a. Glance, Komodo, Borg UI, Filebrowser, code-server, Glances, Scrutiny, Speedtest, Monitoring Grafana und Hermes Agent.
### Hermes Agent — Architektur und Ops-Monitor
Hermes laeuft nach Model C (siehe `ops/hermes-agent/README.md`):
- `hermes-gateway` als Docker-Container auf dem Unraid-Host, intern auf `hermes_net:8642`
- Terminal-Befehle werden via SSH auf eine dedizierte Linux-VM ausgefuehrt
- VM-IP: `192.168.178.143`, SSH-User: `hermes`
- Repo-Clone auf der VM: `/srv/hermes-workspace/homelab-infra/`
**Fuer KI-Agenten wichtig:** Das Hermes-Terminal laeuft auf der VM, nicht auf dem Unraid-Host.
`/mnt/user/...`-Pfade sind von der VM aus nicht direkt erreichbar.
Docker-CLI ist auf der VM nicht installiert — fuer Homelab-Checks wird `check_health.py` verwendet.
**Ops-Monitor (homelab-ops-monitor):**
- Skill: `ops/hermes-agent/skills/homelab-ops-monitor.md`
- Script: `ops/hermes-agent/scripts/check_health.py` — prueft Services via HTTP, keine externen Deps
- Wissensbasis: `ops/hermes-agent/services.json` — maschinenlesbare Ableitung aus `docs/SERVICE_CATALOG.md`
- Check-Strategie: HTTP GET fuer URL-basierte Services, interne Services (DBs, Redis) als `"internal"` markiert
- ntfy-Topic fuer Alerts: `homelab-alerts` auf `https://ntfy.kaleschke.info`
Nach Aenderungen an `services.json` oder `check_health.py`: `git pull` auf der VM ausfuehren.
- Mail Archiver ist ein Hybrid-Dienst mit `frontend_net` fuer IMAP/Traefik und `backend_net` fuer PostgreSQL; die Web-UI liegt hinter Authelia und behaelt zusaetzlich App-eigene Auth.
### Monitoring / Metriken
- Zielzustand ist ein zentraler Stack `monitoring/` unter `https://monitoring.kaleschke.info`.
- `monitoring-grafana` ist die zentrale UI fuer Prometheus, Loki und InfluxDB 3 Core.
- `monitoring-prometheus` sammelt Infrastruktur-Metriken; `monitoring-loki` + `monitoring-promtail` sammeln Docker-Logs.
- `monitoring-influxdb3-core` ist nicht public und nicht im `frontend_net`.
- Home Assistant schreibt ueber LAN-only Port 8181 nach InfluxDB, gebunden ueber `INFLUXDB_BIND_IP`.
- Ein `401 Unauthorized` von InfluxDB ohne Token ist beim Reachability-Test ein Erfolgssignal.
- Die frueheren Altstaende `ops/loki` und `ops/grafana-influxdb` wurden aus dem aktiven Repo entfernt; fuer Monitoring immer `monitoring/` verwenden, Rollback nur ueber Git-Historie.
- Uptime Kuma ist entfernt; HTTP-Verfuegbarkeit laeuft ueber Blackbox Exporter, Prometheus-Alerts und `Homelab / Availability` in Monitoring Grafana.
## Deployment-Logik
Normalfall:
1. lokaler Clone synchronisieren
2. betroffene Dokumente und Compose-Datei lesen
3. minimal aendern
4. lokal validieren
5. committen und pushen
6. Komodo-Webhook / Deploy beobachten
7. Runtime testen
8. Doku aktualisieren
Wichtig: Komodo-Web-Editor ist nicht der Bearbeitungsort. Wenn Komodo und Git voneinander abweichen, zuerst Git und Komodo Workspace pruefen, nicht live herumprobieren.
Beim Anlegen neuer produktiver Stacks ist der Gitea->Komodo-Webhook Pflicht. Nach dem Anlegen muss ein Test-Push oder Test-Delivery zeigen, dass Gitea die aktuelle Komodo-Stack-ID erreicht.
## Netzwerkmodell
| Netzwerk | Bedeutung |
|---|---|
| `frontend_net` | Web-/Proxy-Netz fuer Traefik-geroutete Dienste und Dienste mit Internetbedarf |
| `backend_net` | internes Netz fuer shared PostgreSQL, Redis und Backends |
| `dns_net` | AdGuard + Unbound |
| app-interne Netze | Isolation von App + DB/Cache, z. B. Immich, Mealie, Nextcloud, Monitoring |
| `host` | nur dokumentierte Sonderfaelle wie Tailscale/Plex |
Regeln:
- Datenbanken nie ins `frontend_net`.
- Admin-UIs nur mit Traefik + Middleware oder dokumentierter Ausnahme.
- Direkte Host-Ports sind Ausnahme, nicht Default.
- Runtime-Netznamen koennen Compose-Projektpraefixe bekommen, z. B. `monitoring_monitoring_influx_lan`.
## Security-Modell
- Secrets nie ins Git.
- Werte niemals zitieren, auch nicht aus `.env`, Stack ENV, Logs oder Screenshots.
- Secret-Dateien bevorzugt unter `/mnt/user/appdata/secrets/`.
- `_FILE`-Varianten bevorzugen, falls Image sie unterstuetzt.
- Wenn `_FILE` nicht unterstuetzt wird, Komodo Stack Environment Variables verwenden.
- Docker-Socket, `privileged: true`, Host-Netz und breite Mounts sind nur mit dokumentierter Begruendung akzeptabel.
Bekannte Ausnahmen:
- Traefik: 80/443
- Gitea: SSH 222
- AdGuard: DNS 53 direkt; Admin 8082 ist bewusst ohne Traefik/2FA, aber auf Tailscale-IP `100.80.98.33` begrenzt
- Tailscale: Host-Netz, `NET_ADMIN`, `NET_RAW`, `/dev/net/tun`
- Scrutiny: privileged
- Komodo: Docker-Socket, native Auth
- InfluxDB: LAN-only 8181 fuer Home Assistant Writer
- `monitoring-influxdb3-core`: `user: "0"` als dokumentierte Host-Appdata-Permissions-Ausnahme
- Traefik dynamic config: manueller Host-Sync
## Backup- und Restore-Modell
Borg sichert kritische Appdaten, Secrets, Traefik-State und Dump-Artefakte. Datenbank-Restore soll bevorzugt ueber Dumps laufen, nicht ueber rohe Live-DB-Verzeichnisse.
Wichtige Pfade:
- `/mnt/user/appdata`
- `/mnt/user/appdata/secrets`
- `/mnt/user/services`
- `/mnt/user/documents`
- `/mnt/user/photos`
- `/mnt/user/backups/borg/dumps/latest`
Dump-Skript:
- `ops/borg-ui/scripts/pre-backup-dumps.sh`
- soll auf dem Unraid Host laufen
- soll nicht als Borg-UI Inline-Hook behandelt werden, solange die Architektur nicht bewusst geaendert wird
Disaster Recovery folgt einer Bootstrap-Reihenfolge:
1. Traefik, AdGuard, Tailscale
2. PostgreSQL, Authelia, Redis, Gitea
3. Komodo
4. kritische Apps
5. restliche Apps/Ops inklusive Hermes Agent
## Typische Arbeitsweise im Repo
- Fuer Fragen zuerst `HOMELAB_ARCHITECTURE_MASTER_V2.md` lesen.
- Fuer operative Aenderungen `docs/WORKFLOW.md` lesen.
- Fuer Service-Details `docs/SERVICE_CATALOG.md` und die Compose-Datei lesen.
- Fuer Drift `docs/GITOPS_DRIFT_RUNBOOK.md` nutzen.
- Fuer Rollback `docs/ROLLBACK.md` nutzen.
- Fuer Restore `docs/DISASTER_RECOVERY.md` und `docs/RESTORE_MATRIX.md` nutzen.
KI-Agenten sollen konservativ arbeiten: keine indirekten Live-Aenderungen, keine Deployments, keine Commits, keine Host-Schreibbefehle, wenn der Benutzer nur Analyse oder Doku verlangt.
## Bekannte Risiken und Altlasten
- Traefik dynamic config muss manuell auf den Host synchronisiert werden; Komodo deployed diese Dateien nicht automatisch.
- `backend_net` und app-interne Netze muessen bei Runtime-Problemen live geprueft werden, weil Compose-Projektpraefixe Netznamen veraendern koennen.
- Authelia `configuration.yml` ist Repo-Baseline fuer nicht geheime Einstellungen, wird aber nicht automatisch von Komodo auf den Host kopiert; die produktive Host-Datei kann OIDC-/Secret-Konfiguration enthalten. Bei Auth-Aenderungen Repo-Baseline, Host-Config und Compose-Middlewares pruefen und nicht blind ueberschreiben.
- Authelia nutzt PostgreSQL, aber bewusst kein Redis-Session-Backend; Redis ist kein Authelia-Bootstrap-Blocker.
- Authelia-Notifier ist SMTP; bei Auth-Aenderungen Host-Config backupen, `authelia validate-config` ausfuehren und erst danach neu starten.
- `paperless-ngx` nutzt fuer DB/Redis bewusst Stack ENV statt `_FILE`.
- `glance-docker-socket-proxy`, `glances` und `komodo-periphery` nutzen Docker-/Socket-Zugriff; Zugriff bewusst behandeln.
- `borg-ui` und `filebrowser` haben breite Mounts; bei Hardening nicht ad hoc, sondern gezielt vorgehen.
- `scrutiny` ist privilegiert und hat Device-Mounts.
- `Plex-Media-Server` ist im Architekturziel als Host-Sonderfall dokumentiert, aber nicht als Repo-Compose-Stack enthalten.
- Echte `stack.env`- und `.env`-Dateien gehoeren nicht ins Repo; fuer Hermes liegt nur `ops/hermes-agent/stack.env.example` im Git.
- Einige Images nutzen mutable Tag plus Digest. Das friert den aktuellen Digest ein, ist aber kein automatisches Upgrade-Modell.
- Stateful Images werden bevorzugt als Minor-/Patch-Tag plus Digest gepinnt; Redis-Caches bleiben bewusst ungedigestet.
## Arbeitsregel bei Unsicherheit
Nicht raten. Erst diese Reihenfolge:
1. Repo-Doku lesen
2. Compose-Datei lesen
3. Git-Stand pruefen
4. Komodo Workspace pruefen
5. Docker Runtime pruefen
6. Host-Listener / echten Request pruefen
7. genau eine abweichende Ebene benennen
Wenn zwei Reparaturversuche scheitern: keine weiteren Schreibbefehle, Pflichtmatrix aus `docs/GITOPS_DRIFT_RUNBOOK.md` ausfuellen.
+80
View File
@@ -0,0 +1,80 @@
# AI Handoff 2026-05-06
Kompakte Quelle fuer einen neuen Chat. Ziel: nicht das ganze Repo neu auditieren, sondern mit dem bekannten Stand weiterarbeiten.
## Aktueller Stand
- Repo: `G:\Gitea_Clone\homelab-infra`
- Remote: `https://git.kaleschke.info/Micha/homelab-infra.git`
- Branch: `master`
- Letzter bekannter Commit: `e0e12f1 Document stale Komodo webhook cleanup`
- Unraid-Host: `ssh root@192.168.178.58`
- Push-Befehl, der zuverlaessig funktioniert: `git -C "G:\Gitea_Clone\homelab-infra" push origin master`
- Nicht anfassen ohne explizite Freigabe: untracked `Homelab_Audit_2026-05-05.pdf` und untracked `ops/hermes-agent/services.yaml`.
## Audit-Arbeit Erledigt
- K1: ungueltige Digests fuer Authelia, ntfy und borg-ui korrigiert und smoke-getestet.
- K2: Authelia nutzt bewusst kein Redis; Doku entsprechend korrigiert.
- K3/M1/M2 alt: Authelia Repo-Baseline geklaert, Homepage/Komodo ACL-Drift bereinigt.
- M3a/M3b: Digest-Pinning fuer stateful/Tier-1 und weitere versionierte Apps umgesetzt; Redis-Caches bewusst ohne Digest, Nextcloud bewusst offen.
- M5/N5: `.gitignore` eingefuehrt, Hermes `stack.env` zu `stack.env.example`.
- M6/M7/M8: Hermes-Domain, Grafana/Influx `user: "0"` und Tailscale-Capabilities dokumentiert.
- M9: Backup Scope / Restore Matrix erledigt.
- N-Aufraeumen: alte Compose-`version:` Felder, leere Env-Beispiele und `.keep`-Platzhalter bereinigt.
- Mail Archiver: `mail.kaleschke.info` liegt hinter `authelia@file,secure-headers@file`; Smoke-Test war 302 zu Authelia.
- Hermes: Restore/DR-Doku ergaenzt.
- Authelia SMTP: GMX SMTP eingerichtet, validiert, deployed und smoke-getestet.
- M10: `KOMODO_WEBHOOK_SECRET` ist von `KOMODO_SECRET_KEY` getrennt.
## Wichtige Runtime-Details
### Authelia SMTP
- Adresse: `submission://mail.gmx.net:587`
- Mailkonto: `michideheld@gmx.de`
- SMTP-Passwort liegt nur auf dem Host: `/mnt/user/appdata/secrets/authelia_smtp_password.txt`
- Host-Config wurde vor Umstellung gesichert: `/mnt/user/appdata/authelia/config/configuration.yml.bak-20260506-smtp`
- Authelia-Compose nutzt explizite DNS-Server, weil der SMTP-Startup-Check externe Namen aufloesen muss.
- Nach Deploy war `authelia` healthy; `auth.kaleschke.info` antwortete 200, geschuetzte Routen 302 zu Authelia.
### Komodo / M10
- Komodo-Runtime nur gemeinsam mit dem Betreiber aendern.
- `KOMODO_SECRET_KEY` wurde nicht geaendert.
- `KOMODO_WEBHOOK_SECRET` wurde geaendert und ist jetzt eigener 64-Zeichen-Wert.
- Neuer Wert liegt nur auf dem Host in `/mnt/user/services/stacks/komodo/.env`.
- Komodo Compose auf Host: `/mnt/user/services/stacks/komodo/compose.yaml`.
- Backups vom M10-Sprint:
- `/mnt/user/appdata/komodo/_m10_backup_20260506-184838`
- `/mnt/user/services/gitea/data/gitea/_m10_backup_20260506-184838/gitea.db.bak`
- `komodo-core` wurde gezielt recreated.
- `komodo-mongo` wurde nicht neu gestartet.
- `komodo-periphery` lief durch und meldete sich wieder am Core-Websocket an.
- Gitea-Komodo-Webhooks: 29 aktive Hooks, 29 zuletzt erfolgreich, 0 aktiv fehlgeschlagen.
- Ein stale Gitea-Webhook auf eine nicht mehr existierende Komodo-Stack-ID wurde deaktiviert, nicht geloescht.
- Eine Warnung `request branch does not match expected` ist ein Branch-Filter-Skip, kein Secret-/Auth-Fehler.
- Fuer neue Gitea-Webhooks im Standardfall den globalen `KOMODO_WEBHOOK_SECRET` aus der Komodo-Host-`.env` nutzen, ausser Komodo zeigt fuer den Stack explizit ein eigenes per-Stack-Secret.
## Sicherheitsregeln Fuer Weitere Arbeit
- Keine Secret-Werte im Chat oder Git ausgeben.
- Bei Host-Pruefungen nur SET/MISSING, Laengen und Pfade zeigen.
- Komodo-Compose, Komodo-Secrets und Komodo-Runtime nur bewusst und kleinschrittig aendern.
- Bei jedem Deploy pro Stack smoke-testen; nicht mehrere kritische Stacks parallel veraendern.
- Untracked Dateien nicht automatisch committen.
- Bei Authelia-Aenderungen: Host-Config sichern, `authelia validate-config` ausfuehren, dann erst neu starten.
- Bei Komodo-Aenderungen: Gitea-Webhooks und Komodo-Core-Secret-Seite zusammen betrachten.
## Naechste Sinnvolle Next-Level-Themen
- Grafana/Influx rootless betreiben statt `user: "0"`; eigener Sprint wegen Volume-Rechten.
- Restore-Test fuer Vaultwarden und Paperless dokumentiert durchfuehren.
- Komodo Periphery von Legacy-Passkey auf Public-Key-Modell haerten.
- Monitoring/Alerting reifer machen: externe Alarme, Restore-Test-Reminder, Backup-Erfolg sichtbar.
- Gitea/Komodo Webhook-Landschaft weiter aufraeumen und per-Stack-Secret-Strategie dokumentieren.
- DR-Test fuer `backend_net`/externe Docker-Netze explizit aufnehmen.
## Startprompt Fuer Neuen Chat
Bitte zuerst `docs/AI_HANDOFF_2026-05-06.md` lesen und als aktuelle Arbeitsquelle verwenden. Nicht das ganze Repo neu auditieren, ausser ich fordere es an. Beachte besonders: Komodo nur gemeinsam und kleinschrittig aendern, keine Secret-Werte ausgeben, untracked PDF und `ops/hermes-agent/services.yaml` nicht anfassen. Wir starten jetzt mit Next-Level-Hardening.
+31
View File
@@ -0,0 +1,31 @@
# Alerting Map
Stand: 2026-05-23
Ziel: Alle problemrelevanten Homelab-Meldungen landen auf einem Handy-Topic.
## ntfy Topics
| Topic | Zweck |
|---|---|
| `homelab-alerts` | Alles, was Aufmerksamkeit braucht: Prometheus, Docker-Events, Posture, Zertifikate/Token, Compose-Drift, Borg-Pre-Hook-Fehler und Restore-Fehler |
| `homelab-info` | Optionale Erfolgsmeldungen, z. B. erfolgreiche Restore-Testlaeufe |
## Sender
| Sender | Pfad | Problem-Topic | Hinweis |
|---|---|---|---|
| Prometheus / Alertmanager | `monitoring/alertmanager/alertmanager.yml`, `monitoring/alertmanager-ntfy-bridge/bridge.py` | `homelab-alerts` | Zentrale Monitoring-Alerts via Bridge |
| Posture Check | `services/posture-check/posture-check.sh` | `homelab-alerts` | Warning und Critical gehen auf dasselbe Handy-Topic |
| Cert / Token Check | `services/posture-check/cert-token-check.sh` | `homelab-alerts` | Prueft produktive HTTPS-Domains und Cloudflare Token |
| Compose Runtime Drift | `services/posture-check/compose-runtime-drift.sh` | `homelab-alerts` | Meldet Abweichungen zwischen Repo-Compose und Runtime-Image |
| Docker Critical Events | `services/posture-check/docker-critical-events.sh` | `homelab-alerts` | Meldet Docker `die`, `oom` und `kill` Events |
| Borg Pre-Hook | `ops/borg-ui/scripts/pre-borg.sh` | `homelab-alerts` | Meldet Fehler vor Borg, z. B. Posture-, Dump- oder Restore-Freshness-Fehler |
| Restore Jobs | `ops/restore-tests/run-restore-job-with-ntfy.sh` | `homelab-alerts` | Erfolg geht an `homelab-info`, Fehler immer an `homelab-alerts` |
## Konvention
- `NTFY_BASE_URL` zeigt standardmaessig auf `https://ntfy.kaleschke.info`.
- Neue Problem-Alerts sollen `homelab-alerts` nutzen.
- Erfolgsmeldungen sind optional und sollen nicht in `homelab-alerts` landen, ausser sie sind bewusst als Lebenszeichen gewuenscht.
- Blackbox-Endpoint-Alerts sollen bekannte WAN-/Provider-Sammelausfaelle zusammenfassen, damit kurze DSL-Reconnects keine ntfy-Flut pro Domain erzeugen.
+369
View File
@@ -0,0 +1,369 @@
# Homelab Audit - 2026-05-23
Stand: 2026-05-23, repo-basiert. Erstellt nach `docs/WORKFLOW.md` und `docs/GITOPS_DRIFT_RUNBOOK.md`. Quellebasis: `origin/master` plus lokaler Clone, ohne Schreibbefehle, ohne Deploy.
Dieser Audit ist eine punktuelle Sollzustands-Bewertung, kein Live-Status. Die Live-Verifikations-Schritte stehen am Ende in Abschnitt 9; alle dortigen Outputs ersetzen Vermutungen durch Messwerte.
## 0. Executive Summary
Ampel-Bewertung pro Bereich:
| Bereich | Ampel | Kernaussage |
|---|---|---|
| GitOps-Konsistenz (lokal/Gitea) | 🟡 | Lokaler Clone ist **1 Commit voraus** auf `master` (`cd650b1`, Haertungs-Commit). Bis zum Push existiert dieser Stand nur lokal, nicht in Gitea — bei einem Clone-Verlust ist er weg. |
| GitOps-Konsistenz (Working Tree) | 🟢* | Keine echten Inhaltsaenderungen offen. Die 47 "modified files" aus `git status` im Linux-Mount sind voraussichtlich CRLF/LF-Mount-Artefakte (durch `git diff -w --stat` auf Stichprobe bestaetigt leer). Bitte am Windows-Host gegenpruefen. |
| Hardening-Sprint (Mai 2026) | 🟢 | Alle vier Post-Restore-Sprint-Items sind im Repo umgesetzt (Filebrowser-Mounts, Authelia Argon2id, Gitea Webhook-Allowlist, Backup-Dump-Konsistenz). |
| Backup/Restore-Readiness | 🟢 | `pre-backup-dumps.sh` deckt alle relevanten SQLite/PostgreSQL/Mongo-Quellen ab. Borg-UI-Scope umfasst `/mnt/user/services`, `homelab-infra`, `stacks`, `posture-check`. Live-Frische ist offen (Abschnitt 9). |
| Monitoring-Migration | 🟡 | `monitoring/` Stack im Repo komplett, aber Live-Deploy laut `docs/NEXT_SPRINT_TODO_2026-05-16.md` noch ausstehend. Alte Stacks `ops/grafana-influxdb` und `ops/loki` sollen erst nach Live-Smoke-Test gestoppt werden. |
| Doku-Drift Repo vs. Master-Doku | 🟠 | `apps/jellyfin/`, `host-services/plex/` und einige andere existieren produktiv als Compose-Stacks, sind aber in `HOMELAB_ARCHITECTURE_MASTER_V2.md`, `docs/SERVICE_CATALOG.md` und `docs/REPO_MAP.md` **nicht aufgefuehrt**. Authelia-ACL kennt `jellyfin.kaleschke.info` als bypass, im Masterdoku-Hostkatalog steht es nicht. |
| Repo-Hygiene | 🟡 | 8 leere Verzeichnisse im Working Tree (siehe 4.3). `.serena/` ist untracked und nicht in `.gitignore`. Drei `ops/windows-reinstall/*.ps1` sind untracked. |
| Bekannte Ausnahmen | 🟢 | Alle Ausnahmen aus `HOMELAB_ARCHITECTURE_MASTER_V2.md` Abschnitt 10 sind weiterhin dokumentiert und durch den Policy-Check abgedeckt (0 Critical, 4 Warnings, 9 Info alles dokumentierte Ausnahmen). |
**Kernfazit:** Das Homelab ist sehr nah an der "Endstufe". Es gibt keine kritischen Befunde. Die einzige Pflichtaktion vor dem naechsten geplanten Schritt ist der **Push des lokalen Commits `cd650b1` nach Gitea**, damit `origin/master` wieder die Quelle der Wahrheit ist. Danach sind nur noch zwei priorisierte Pakete offen: Monitoring-Stack live finalisieren und Doku auf den Stand der neuen Stacks (Jellyfin/Plex/...) nachziehen.
---
## 1. Methodik und Quellen
Diese Audit-Quellen wurden gelesen (repo-seitig):
- `HOMELAB_ARCHITECTURE_MASTER_V2.md`
- `docs/WORKFLOW.md`
- `docs/REPO_MAP.md`
- `docs/SERVICE_CATALOG.md`
- `docs/RESTORE_MATRIX.md`
- `docs/GITOPS_DRIFT_RUNBOOK.md`
- `docs/NEXT_SPRINT_TODO_2026-05-16.md`
- `ops/borg-ui/scripts/pre-backup-dumps.sh`
- `ops/borg-ui/docker-compose.yml`
- `ops/borg-ui/all-important-sources.txt`
- `ops/filebrowser/docker-compose.yml`
- `security/authelia/configuration.yml`
- `core/gitea/docker-compose.yml`
- `apps/jellyfin/docker-compose.yml`
- `host-services/plex/docker-compose.yml`
- `ops/policy-checks/last-report.md`
Schreibbefehle: keine. Deploys: keine. Containerlaufzeit, Komodo-Webhook-Status, Borg-Lauf-Frische und Host-Listener wurden bewusst nicht angetastet — dafuer steht der Live-Daten-Block in Abschnitt 9.
---
## 2. Schicht A — GitOps und Konsistenz
### 2.1 Lokaler Clone vs. `origin/master`
```
## master...origin/master [ahead 1]
HEAD = cd650b19ac057a1b74ac63503e5dba50eaf5b8ea
origin/master = af231dd4e835b19005cc0842509199d480af00d9
```
- **Befund:** Lokaler Clone ist 1 Commit voraus.
- **Commit:** `cd650b1 Close Gitea signup, dedup posture-check alerts, extend Borg scope` (Sat May 23 11:01:24 2026 +0200).
- **Inhalt** (laut Commit-Message und betroffenen Dateien):
- Gitea: `DISABLE_REGISTRATION=true`, `ENABLE_OPENID_SIGNIN=false`, `ENABLE_OPENID_SIGNUP=false`
- Repo-Pflicht-Doku ergaenzt: Komodo-Stack-Webhook-Pflicht in `CLAUDE.md`, `AI_CONTEXT.md`, `WORKFLOW.md`
- `posture-check.sh`: Disk1-NTFS-Funktion ausgelagert, Inode-Check auf NTFS uebersprungen, ntfy-Dedup via Fingerprint-State + `ALERT_REPEAT_SECONDS`
- `docker-critical-events.sh`: JSON-Parsing, `die exit=0` gefiltert, strukturierte ntfy-Message
- `borg-ui`: `/mnt/user/services` als `/local/services:ro` gemountet, `all-important-sources.txt` ergaenzt
- Unraid User Scripts dokumentiert (daily report)
- `MIGRATION_LOG.md`, `RESTORE_MATRIX.md`, `DISASTER_RECOVERY.md` aktualisiert
- **Risiko:** Bei Verlust des Windows-Clones (Reinstall, Diskcrash) ist dieser Stand verloren, weil er nicht in Gitea liegt. Komodo deployt ausserdem aus Gitea und kennt diese Aenderungen noch nicht.
- **Empfohlene Aktion (Pflicht vor weiterer Arbeit):** In GitHub Desktop `Push origin` ausfuehren. Danach Komodo-Reaktion fuer die betroffenen Stacks (`gitea`, `borg-ui`) pruefen und Smoke-Tests laufen lassen.
### 2.2 Working-Tree-Status
```
$ git status --short
M CLAUDE.md
M HOMELAB_ARCHITECTURE_MASTER_V2.md
M apps/homepage/docker-compose.yml
...
(47 Dateien als modified gemeldet, plus 4 Untracked)
```
- **Bewertung:** Die 47 "modified files" sind mit hoher Wahrscheinlichkeit **Mount-Artefakte** durch CRLF/LF zwischen Windows-Clone und Linux-Mount. Stichprobe `git diff -w --stat CLAUDE.md HOMELAB_ARCHITECTURE_MASTER_V2.md` lieferte leer — d. h. keine inhaltlichen Diffs.
- **Aktion:** Bitte am Windows-Host in GitHub Desktop `git status --short` ausfuehren. Wenn dort der Tree leer ist (nur die 4 Untracked), gibt es keinen echten Working-Tree-Drift. Wenn dort echte Diffs erscheinen, hier bitte zurueckmelden — dann ist das ein eigener Befund.
- **Optional (nicht Pflicht):** `.gitattributes` mit `* text=auto eol=lf` haerten, damit dieser Mount-Effekt fuer KI-Audits aus dem Weg geht. Das ist ein eigener kleiner Commit, kein Audit-Output.
### 2.3 Untracked Files
```
?? .serena/
?? ops/windows-reinstall/backup-delta-after-2026-05-07.ps1
?? ops/windows-reinstall/cleanup-dualboot-bcd.ps1
?? ops/windows-reinstall/repair-disk0-boot-to-new-windows.ps1
```
- `.serena/` ist das Working-Directory des Serena Code-Search-Tools. Hat eigene `.gitignore` intern, aber das `.serena/`-Verzeichnis selbst ist nicht in der Repo-`.gitignore`.
- **Aktion (klein):** `.serena/` in `.gitignore` aufnehmen, damit es nicht versehentlich committet wird.
- Die drei PowerShell-Scripts unter `ops/windows-reinstall/` sind Windows-Reinstall-Helfer. Entscheidung offen: ins Repo aufnehmen (mit Kontextkommentar warum sie dort liegen) oder lokal halten und in `.gitignore` aufnehmen. Vorschlag: aufnehmen, weil `ops/` der dokumentierte Ort fuer Ops-Skripte ist.
### 2.4 Letzte Commit-Historie (Top 10)
```
cd650b1 Close Gitea signup, dedup posture-check alerts, extend Borg scope [LOKAL, NICHT GEPUSHT]
af231dd Fix zero-count noise pattern handling
428223d Mark posture report scripts executable
b6d3ed4 Tune homelab availability alerts
9e7bebb Add daily operations report with hardened log-noise filtering
b7cbbe5 Fix Jellyfin external DNS
71ac18b Fix Jellyfin native auth routing
90f270b Fix Jellyfin config permissions
e28f8da Add Jellyfin media server stack
edfec5b Add Plex media server stack
```
- Die letzten Tage waren sichtbar: Jellyfin/Plex hinzugefuegt, Availability-Alerts feinjustiert, Posture-Check-Skripte produktiv gemacht, dann der grosse Haertungs-Commit gestern (2026-05-23 11:01).
### 2.5 Compose-Inventar vs. Doku
Repo hat folgende Compose-Stacks, die in den Doku-Quellen (`HOMELAB_ARCHITECTURE_MASTER_V2.md`, `docs/SERVICE_CATALOG.md`, `docs/REPO_MAP.md`) **nicht oder nur teilweise** aufgefuehrt sind:
| Stack | Status im Repo | Status in Master-Doku |
|---|---|---|
| `apps/jellyfin/docker-compose.yml` | produktiv vorhanden, gepinnt `jellyfin:10.11.8@sha256:...`, Traefik `jellyfin.kaleschke.info`, `secure-headers@file`, native Auth, `/mnt/user/media:ro` + `/mnt/user/photos:ro` | **fehlt** in 7.4 Apps; Authelia-ACL kennt aber bereits `jellyfin.kaleschke.info` als bypass — Doku hinkt hinterher |
| `host-services/plex/docker-compose.yml` | produktiv vorhanden, gepinnt `plexinc/pms-docker:1.43.1.10611-1e34174b1@sha256:...`, `network_mode: host`, `/mnt/user/media:ro` + `/mnt/user/photos:ro` | Master-Doku sagt explizit "Plex-Media-Server ist historischer Host-Sonderfall, nicht als Repo-Compose-Stack enthalten" — **das stimmt nicht mehr**, Plex ist jetzt ein Repo-Compose-Stack |
| `host-services/docker/` | leeres Verzeichnis | nicht erwaehnt |
| `infra/dns/` | leeres Verzeichnis | nicht erwaehnt |
| `ops/Semaphore/` | Skripten/Playbooks aber kein Compose | nicht erwaehnt |
| `ops/backrest/` | leeres Verzeichnis (Stack laut Master-Doku am 2026-05-15 entfernt) | korrekt als entfernt dokumentiert; Verzeichnis sollte leer bleiben oder weg |
| `apps/firefly/`, `apps/firefly-fints/` | leere Verzeichnisse | nicht erwaehnt |
| `apps/stirling-pdf/` | leeres Verzeichnis (durch `bentopdf` abgeloest) | korrekt als abgeloest dokumentiert |
- **Aktion (Doku-Synchronisierung):** `HOMELAB_ARCHITECTURE_MASTER_V2.md` Abschnitt 7 (Container-Zielbild), `docs/SERVICE_CATALOG.md` und `docs/REPO_MAP.md` um Jellyfin und Plex erweitern. Plex-Doku im Master umschreiben: nicht mehr "historisch ausserhalb Repo", sondern "Compose-Stack mit `network_mode: host` als VPN-Discovery-Ausnahme".
- **Aktion (Repo-Hygiene):** Die leeren Verzeichnisse `apps/firefly`, `apps/firefly-fints`, `apps/stirling-pdf`, `host-services/docker`, `infra/dns`, `ops/backrest`, `ops/grafana-influxdb/scripts`, `ops/Semaphore/playbooks`, `ops/Semaphore/Scripts` aufraeumen — Master-Doku sagt: "Leere `.keep`-Platzhalter wurden entfernt; neue Verzeichnisse sollen erst mit konkretem Inhalt ins Repo." Diese Verzeichnisse verletzen diese Regel passiv.
### 2.6 Image-Pinning
Lt. `docs/NEXT_SPRINT_TODO_2026-05-16.md` sind diese Stacks noch nicht voll versioniert gepinnt:
- `ddns-updater``latest...@sha256`
- `glances``latest-full@sha256`
- `scrutiny``latest-omnibus@sha256`
Das ist bewusst dokumentiert und kein Audit-Befund.
---
## 3. Schicht B — Hardening-Sprint 2026-05 (Sitrep)
Dies war der Sprint, der nach dem 2026-05 Restore explizit gesetzt wurde. Stand im Repo:
| Sprint-Item | Stand 2026-05-16 (Plan) | Stand 2026-05-23 (Repo) | Beleg |
|---|---|---|---|
| **(1) Backup-Konsistenz** — `dump_sqlite_container` fuer Gitea/Vaultwarden/Uptime-Kuma/Speedtest/Filebrowser + `pg_dump` Nextcloud | offen | ✅ erledigt | `ops/borg-ui/scripts/pre-backup-dumps.sh` Z. 97139 (`dump_sqlite_container`), Z. 253258 (Nextcloud `pg_dump`), Z. 261264 (alle SQLite-Container mit Host-Fallback), Z. 267 (Filebrowser BoltDB). Borg-Scope erweitert um `/mnt/user/services` (Borg-UI Compose Z. 26 + `all-important-sources.txt` Z. 2325). |
| **(2) Filebrowser entschaerfen** — `/mnt/user/appdata:/srv/appdata` weg, gezielte RW-Subpfade | offen | ✅ erledigt | `ops/filebrowser/docker-compose.yml` Z. 1116. Keine Appdata-Mounts mehr. Nur noch `/mnt/user/documents`, `/mnt/user/photos`, `/mnt/user/projekte` als Datenmounts plus eigener `/database` und `/config`. |
| **(3) Authelia Argon2id haerten** — iterations 3, memory 65536, parallelism 4 | offen | ✅ erledigt | `security/authelia/configuration.yml` Z. 1725. Exakt die geplanten Parameter sind aktiv. |
| **(4) Gitea Webhook-Allowlist** — `ALLOWED_HOST_LIST=*` einschraenken | offen | ✅ erledigt | `core/gitea/docker-compose.yml` Z. 18: `GITEA__webhook__ALLOWED_HOST_LIST=komodo-core,localhost,127.0.0.1,192.168.178.0/24`. Zusatz aus heutigem Commit: Public Registration und OpenID-Signup/Signin sind deaktiviert. |
Alle vier Items sind **im Repo abgeschlossen**. Live-Wirksamkeit haengt am Komodo-Deploy aus Gitea — und genau da haengt aktuell der ungepushte Commit `cd650b1` davor (siehe 2.1). Solange er nicht in Gitea ist, ist insbesondere die Gitea-Signup-Schliessung im Live-Stand nicht garantiert.
**Bewusst nicht angefasste Liste (Operator-Entscheidung 2026-05-16) ist weiterhin gueltig:**
- Hermes — bleibt VM-seitig offen, NAS-Stack bewusst nicht starten
- Disk1 NTFS — Phase-2-Migration nach Plan
- Komodo native Auth ohne ForwardAuth
- Grafana/Influxdb3-core `user: "0"`
- Image-Pinning-Vereinheitlichung (`:latest@sha256:`) fuer ddns-updater/glances/scrutiny
---
## 4. Schicht C — "Endstufe?"-Bewertung
### 4.1 Backup/Restore-Readiness
- **Dump-Coverage:** `pre-backup-dumps.sh` deckt 14 Quellen ab: PostgreSQL-Globals + 3 Shared-DBs + 3 dedizierte Postgres (mealie, immich, nextcloud) + 4 SQLite (gitea, vaultwarden, uptime-kuma, speedtest-tracker) + Filebrowser-BoltDB + Borg-UI + Grafana + Komodo-Mongo. Deckt 1:1 die Restore-Matrix-Eintraege ab.
- **Borg-Scope:** `all-important-sources.txt` enthaelt 27 Eintraege inkl. neuer `services/homelab-infra`, `services/stacks`, `services/posture-check` und `secrets`.
- **Restore-Validierungen:** Laut `docs/RESTORE_MATRIX.md` sind am 2026-05-07 Mini-Restores fuer `gitea`, `vaultwarden` und `paperless` validiert worden — dokumentierter Stand.
- **Live offen:** Wann lief der letzte Borg-Lauf? Sind alle Dumps unter `/mnt/user/backups/borg/dumps/latest` frischer als 24h? Siehe Live-Checkliste 9.4.
### 4.2 Monitoring-Migration
- Repo-Zielzustand `monitoring/docker-compose.yml` (337 Zeilen Compose) existiert mit Prometheus, Alertmanager, ntfy-Bridge, Blackbox-Exporter, Loki, Promtail, Grafana, node-exporter, cAdvisor, InfluxDB3 Core.
- Provisioning unter `monitoring/grafana/provisioning/` und `monitoring/prometheus/`, `monitoring/loki/`, `monitoring/promtail/`, `monitoring/alertmanager/`, `monitoring/blackbox/` vollstaendig vorhanden.
- Alte Stacks `ops/grafana-influxdb/` und `ops/loki/` bewusst noch im Repo (dokumentierter Altstand, Rollback-Referenz).
- **Live offen:** Ist `monitoring` schon als Komodo-Stack deployed? Laufen die Container? Sind die Secret-Dateien `monitoring_grafana_admin_password.txt`, `monitoring_grafana_influxdb_token.txt`, `influxdb3_admin_token.json` auf dem Host? Siehe Live-Checkliste 9.5.
### 4.3 Repo-Hygiene
| Befund | Schwere | Aktion |
|---|---|---|
| 8 leere Verzeichnisse (`apps/firefly`, `apps/firefly-fints`, `apps/stirling-pdf`, `host-services/docker`, `infra/dns`, `ops/backrest`, `ops/grafana-influxdb/scripts`, `ops/Semaphore/playbooks`, `ops/Semaphore/Scripts`) | klein | Aufraeumen, danach committen |
| `.serena/` untracked, nicht in `.gitignore` | klein | `.serena/` zu `.gitignore` hinzufuegen |
| 3 `ops/windows-reinstall/*.ps1` untracked | klein | Entscheidung treffen: ins Repo oder ignorieren |
### 4.4 Bekannte dokumentierte Ausnahmen
Aus `HOMELAB_ARCHITECTURE_MASTER_V2.md` Abschnitt 10 — alle weiterhin gueltig und durch den Policy-Check abgedeckt (`ops/policy-checks/last-report.md` 0 Critical):
- Traefik 80/443
- Tailscale Host-Netz + Capabilities
- AdGuard Port 53 + 8082 (Admin-Port LAN-only, dokumentiert; **offener Punkt im Master:** "Traefik-Absicherung ausstehend (Block F)" — bewusst spaeter)
- Plex Host-Netz (aber Master-Doku-Eintrag jetzt falsch, siehe 2.5)
- Scrutiny `privileged: true`
- Komodo Docker-Socket + keine pauschale Middleware
- glance-docker-socket-proxy Read-only Socket
- Gitea SSH 222
- ddns-updater `frontend_net`
- mail-archiver Hybrid-Netze
- `traefik/dynamic/*` manueller Host-Sync
- nextcloud native Auth
- monitoring-influxdb3-core LAN 8181 + `user: "0"`
- monitoring-promtail Docker-Socket read-only
Keine ungeplanten neuen Ausnahmen.
### 4.5 Endstufen-Definition
"Endstufe" ist erreicht, wenn alle folgenden Punkte gruen sind:
1. **Gitea = Quelle der Wahrheit** — kein lokaler Commit ohne Push 🟡 (heute: `cd650b1` ungepusht)
2. **Hardening-Sprint im Repo abgeschlossen** 🟢
3. **Backup-Konsistenz live verifiziert (Borg laeuft, Dumps frisch)** ❓ Live
4. **Monitoring-Stack live, alte Altstaende gestoppt** 🟡
5. **Doku synchron mit Repo (Jellyfin/Plex, leere Verzeichnisse, ...)** 🟠
6. **Policy-Check 0 Critical** 🟢 (4 Warnings sind dokumentierte Ausnahmen)
7. **Restore-Lab gepflegt (`mail-archiver` als naechste Uebung empfohlen)** 🟡 dokumentiert offen
Sechs der sieben Punkte sind in Reichweite ohne neue Architekturentscheidungen. Punkt 3 und 4 brauchen Live-Daten (Abschnitt 9). Punkt 1 ist 1 Push entfernt.
---
## 5. Priorisierte Restliste
| Prio | Aktion | Begruendung | Aufwand |
|---|---|---|---|
| **P0** | `cd650b1` nach Gitea pushen | GitOps-Quelle-der-Wahrheit, Voraussetzung fuer alles weitere | 30 Sekunden |
| **P0** | Live-Daten aus Abschnitt 9 einholen | Ohne Live-Frische ist Endstufen-Bewertung unvollstaendig | 5 Minuten |
| **P1** | Monitoring-Stack live finalisieren (Secrets pruefen, deployen, Smoke-Test, alte Altstaende stoppen) | Aus `docs/NEXT_SPRINT_TODO_2026-05-16.md` der naechste produktive Schritt | 12 Stunden mit Tests |
| **P2** | Doku-Drift schliessen: Jellyfin und Plex in `HOMELAB_ARCHITECTURE_MASTER_V2.md` 7.4 + 7.1, `docs/SERVICE_CATALOG.md`, `docs/REPO_MAP.md` ergaenzen; Plex-Eintrag in Abschnitt 7.7 "noch offene Sonderfaelle" entfernen (ist umgesetzt) | Doku ist Source of Truth fuer KI-Audits und Nachfolge | 30 Minuten |
| **P2** | Home Assistant -> InfluxDB final testen, HA-Dashboard in `monitoring-grafana` anlegen | aus NEXT_SPRINT_TODO | 12 Stunden |
| **P3** | Repo-Hygiene: 8 leere Verzeichnisse loeschen, `.serena/` in `.gitignore`, Entscheidung zu `ops/windows-reinstall/*.ps1` | minor, aber dokumentiert | 15 Minuten |
| **P3** | Naechster Restore-Lab-Lauf: `mail-archiver` (empfohlen in `RESTORE_MATRIX.md`) | Restore-Routine ueben, bevor sie gebraucht wird | 1 Stunde |
| **P4** | `.gitattributes` mit `* text=auto eol=lf` hinzufuegen, um CRLF/LF-Mount-Effekte bei KI-Audits zu vermeiden | klein, kosmetisch fuer kuenftige Audits | 5 Minuten |
| **bleibt** | Hermes VM-Seite, Disk1-NTFS Phase 2, AdGuard Admin-Port hinter Traefik (Block F), Image-Pinning ddns/glances/scrutiny | bewusste Operator-Entscheidung, kein Audit-Beduerfnis | nicht jetzt |
---
## 6. Was bewusst NICHT angetastet wurde (Audit-Verzicht)
Konsistent mit der bekannten Nicht-Anfassen-Liste:
- Hermes (VM-seitig offen)
- Disk1 NTFS (Phase-2-Migration nach Plan)
- Komodo native Auth ohne ForwardAuth
- Grafana / influxdb3-core `user: "0"` Uebergangsausnahme
- Image-Pinning-Vereinheitlichung fuer ddns-updater/glances/scrutiny
---
## 7. Risiken und Drift-Indikatoren
| Risiko | Wahrscheinlichkeit | Wirkung | Migitation |
|---|---|---|---|
| Lokaler Clone-Verlust (Disk, Reinstall) bevor `cd650b1` gepusht wurde | gering, aber real (Du bist im Reinstall-Kontext, siehe `ops/windows-reinstall/`!) | Verlust von Gitea-Signup-Closure und Posture-Check-Verbesserungen | **Sofort pushen** |
| Komodo deployt aus Gitea, `gitea` und `borg-ui` laufen aktuell ohne die heutigen Verbesserungen | mittel | Gitea Signup steht noch offen, Borg-Scope umfasst `/mnt/user/services` noch nicht | Push + Komodo-Reaktion pruefen |
| 47 vermeintlich modified files koennten doch echte Diffs sein, wenn der Windows-Host etwas anderes zeigt | gering | falsche Audit-Aussage | Punkt 9.1 auf dem Windows-Host pruefen |
| Doku-Drift wird groesser, wenn weitere Stacks ohne Doku-Update hinzukommen | mittel | KI-Audits und Onboarding leiden | P2-Doku-Sync nicht aufschieben |
| Monitoring-Stack-Migration unfertig, alter und neuer Stack koennten parallel werden | mittel | Doppelte Metric-/Log-Pipeline, Verwirrung bei Diagnose | Live-Status klaeren bevor Deploy |
---
## 8. Sources of Truth — Schnellzugriff
- Operative Quelle der Wahrheit: Gitea `origin/master` (https://git.kaleschke.info/Micha/homelab-infra)
- Architektur-Master: `HOMELAB_ARCHITECTURE_MASTER_V2.md`
- Workflow / GitOps-Regeln: `docs/WORKFLOW.md`
- Drift-Runbook: `docs/GITOPS_DRIFT_RUNBOOK.md`
- Restore-Quellen: `docs/RESTORE_MATRIX.md`, `docs/DISASTER_RECOVERY.md`
- Letzter Policy-Check: `ops/policy-checks/last-report.md` (0 Critical)
- Letzte Sprint-Restliste: `docs/NEXT_SPRINT_TODO_2026-05-16.md`
---
## 9. Live-Daten-Checkliste — bitte ausfuehren und zurueckspielen
Fuehre die folgenden Bloecke am Unraid-Host (per SSH oder Web-Terminal) und am Windows-Host (Git Bash / PowerShell in `G:\Gitea_Clone\homelab-infra`) aus und pastiere die Outputs zurueck. Ich integriere sie dann in diesen Report.
### 9.1 Windows-Host: Echter Working-Tree-Status
```powershell
cd G:\Gitea_Clone\homelab-infra
git status --short
git log origin/master..HEAD --oneline
```
Erwartet: Working tree leer (oder nur die 4 Untracked). `cd650b1` als einziger lokaler Commit.
### 9.2 Unraid-Host: Gitea online
```bash
curl -sI --max-time 5 https://git.kaleschke.info/ | head -5
docker exec gitea sh -lc 'gitea --version'
```
Erwartet: `HTTP/2 200` (oder ein Auth-Code, der den Erreichbarkeitstest erfuellt). Gitea-Version stimmt mit Image-Tag `1.25.4` ueberein.
### 9.3 Unraid-Host: Komodo-Webhook-Status
In Komodo UI fuer jeden produktiven Stack aus `Micha/homelab-infra` pruefen:
- `webhook_enabled: true`
- Gitea-Hook auf `http://komodo-core:9120/listener/github/stack/<stack-id>/deploy` aktiv
- `last_status` der letzten Webhook-Delivery in Gitea (Repository -> Settings -> Webhooks)
Pflicht-Stacks zum Pruefen: `traefik`, `gitea`, `authelia`, `vaultwarden`, `postgresql17`, `redis`, `paperless-ngx`, `immich`, `nextcloud`, `mealie`, `mail-archiver`, `ntfy`, `homepage`, `paperless-gpt`, `borg-ui`, `filebrowser`, `code-server`, `uptime-kuma`, `glance`, `glances`, `scrutiny`, `speedtest-tracker`, `bentopdf`, `ddns-updater`, `komodo`, `jellyfin`, `plex`, `adguard`, `tailscale`, `monitoring`, `hermes-agent` (sofern produktiv).
Bei Stacks **ohne** aktiven Webhook bitte den Grund vermerken (dokumentierte Ausnahme oder Nachholbedarf).
### 9.4 Unraid-Host: Borg-Lauf-Frische und Dump-Coverage
```bash
ls -lah /mnt/user/backups/borg/dumps/latest/
stat -c '%y %n' /mnt/user/backups/borg/dumps/latest/*.dump /mnt/user/backups/borg/dumps/latest/*.sql /mnt/user/backups/borg/dumps/latest/*.archive.gz /mnt/user/backups/borg/dumps/latest/*.sqlite 2>/dev/null | sort
docker exec borg-ui borg list --short 2>&1 | tail -10
```
Erwartet: Alle 14 Artefakte aus 4.1 sind vorhanden, mtime juenger als 24h. Borg-Archive-Liste zeigt regelmaessige Laeufe.
### 9.5 Unraid-Host: Monitoring-Stack live?
```bash
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" | grep monitoring
ls -la /mnt/user/appdata/secrets/ | grep -E 'monitoring|influxdb3'
curl -sI --max-time 5 https://monitoring.kaleschke.info/ | head -5
ss -ltnp 2>/dev/null | grep -E ':8181|:9090|:3100' | head -10
```
Erwartet: Entweder alle `monitoring-*` Container laufen (dann ist 4.2 🟢) oder gar nicht (dann ist 4.2 🟡 wie aktuell bewertet). Halb-Zustand ist ein Audit-Befund.
### 9.6 Unraid-Host: GitOps-Pflichtmatrix Spot-Check fuer einen Stack
Beispiel `gitea` (weil der heutige Commit ihn betrifft):
```bash
cd /mnt/user/services/stacks/gitea
git rev-parse --short HEAD
git status -sb
docker inspect gitea --format '{{.Image}}'
docker exec gitea sh -lc 'env | grep -E "ALLOWED_HOST_LIST|DISABLE_REGISTRATION|ENABLE_OPENID"'
```
Erwartet: Komodo-Workspace `HEAD` zeigt entweder auf `cd650b1` (wenn Push schon erfolgt + Komodo deployed) oder auf `af231dd` (vor dem Push). ENV-Vars in der Live-Runtime spiegeln den Commit, der Komodo zuletzt deployed hat.
### 9.7 Unraid-Host: Host-Listener-Spot-Check
```bash
ss -ltnp 2>/dev/null | grep -E ':80|:443|:53|:222|:8082|:8181' | sort
```
Erwartet exakt die dokumentierten Ausnahmen aus `HOMELAB_ARCHITECTURE_MASTER_V2.md` Abschnitt 10. Andere Listener = Befund.
---
## 10. Nachhalte-Vorschlag
Wenn Du moechtest, halte ich diesen Audit in zwei Schritten zu Ende:
1. Du fuehrst Abschnitt 9 aus und pastierst die Outputs zurueck.
2. Ich aktualisiere diesen Report mit den Live-Ergebnissen, ergaenze die Ampel und schliesse die offenen 🟡/🟠 Punkte oder benenne sie als echte Restliste.
Bis dahin gilt der Stand dieses Reports als Repo-Audit, nicht als Endstufen-Zertifikat.
+22
View File
@@ -0,0 +1,22 @@
# Homelab Audit Final - 2026-05-23
Stand: 2026-05-25 07:33 CEST. Ergebnis nach Push, Live-Messung, Doku-Sync, Repo-Hygiene und erneuter Live-Nachmessung.
| Punkt | Ampel | Beleg |
|---|---|---|
| P0 `cd650b1` nach Gitea pushen | gruen | Push `af231dd..cd650b1 master -> master`; produktiver Runtime-Stand `66ee10c` enthaelt `cd650b1`. Die abschliessenden Audit-Doku-Commits liegen in Gitea; der runtime-relevante Stack-Inhalt fuer `gitea`, `borg-ui` und `monitoring` ist seit `66ee10c` unveraendert. |
| P0 Live-Daten ablegen | gruen | `docs/AUDIT_2026-05-23_LIVE.md` angelegt, keine Secret-Werte dokumentiert. |
| P1 Monitoring live / Altstaende down | gruen | 10 `monitoring-*` Container laufen, `0` unhealthy, `0` starting, `0` stopped; alte `grafana`/`influxdb3-core`/`loki`/`alloy` Container sind nicht vorhanden. `monitoring.kaleschke.info` liefert 302 zu Authelia, Prometheus ist bereit, Loki `/ready` liefert `ready`. |
| P1 Jellyfin/Plex Doku | gruen | `HOMELAB_ARCHITECTURE_MASTER_V2.md`, `docs/SERVICE_CATALOG.md` und `docs/REPO_MAP.md` ergaenzt; Plex ist als Repo-Compose-Stack dokumentiert, nicht mehr als nicht migrierter Dockerman-Sonderfall. |
| P2 Borg-Frische | gruen | Borg-UI DB: letzter Backup-Job `completed`, Archiv `Taegliche-Sicherung-2026-05-25T05:52:44.157`, `nfiles=100221`; 15 kanonische Dump-/Archive-Artefakte von 2026-05-25 06:09/06:10 CEST und damit juenger als 24 h. |
| P3 Repo-Hygiene | gruen | `.serena/` in `.gitignore`; leere Verzeichnisse entfernt; `ops/windows-reinstall/*.ps1` bewusst ins Repo aufgenommen. |
| Policy-Check | gruen | `ops/policy-checks/check_repo.ps1`: 0 Critical, dokumentierte Warnings zu Plex Host-Netz, InfluxDB/Grafana `user: "0"` und bekannten mutable-tag-Ausnahmen. |
## Bewusst offen
- Keine offenen Punkte aus der Audit-Restliste.
- Nicht Teil des Audits: Nach Disk1 Phase 2 laeuft seit 2026-05-25 eine nicht korrigierende Parity-Pruefung (`NOCORRECT`) im Hintergrund; Zwischenstand der Nachmessung: `mdResyncAction=check P`, `mdResyncCorr=0`.
## Schlussbewertung
Das Homelab ist fuer die Audit-Restliste in der Endstufe. Es gibt keine kritischen Repo-, GitOps- oder Live-Befunde aus diesem Audit. Monitoring ist produktiv und ready, alte Altstaende sind down, Backups und Dumps sind frisch.
+88
View File
@@ -0,0 +1,88 @@
# Homelab Audit Live-Daten - 2026-05-23
Stand: 2026-05-23 11:27 CEST. Quelle: lokaler Windows-Clone und SSH auf `Kallilabcore`. Secret-Werte wurden nicht ausgelesen oder redaktiert; dokumentiert sind nur Status, Dateinamen, Modi, Env-Key-Namen und nicht geheime Bool-/Host-Werte.
## 9.1 Windows-Host / Git
- `git status --short` nach dem initialen Push: keine tracked Modifikationen, untracked waren `.serena/`, `docs/AUDIT_2026-05-23.md`, `docs/CODEX_ENDSTUFE_PROMPT_2026-05-23.md` und drei `ops/windows-reinstall/*.ps1`.
- `cd650b1` wurde nach `origin/master` gepusht: `af231dd..cd650b1 master -> master`.
## 9.2 Gitea online
- `curl -sI https://git.kaleschke.info/`: `HTTP/2 200`.
- `docker exec gitea gitea --version`: `1.25.4`.
- Signup-Smoke: `/user/sign_up` meldet Registration disabled.
## 9.3 Komodo / Workspace-Reaktion
| Stack | Workspace HEAD | Status | Live-Beleg |
|---|---:|---|---|
| `gitea` | `cd650b1` | `## master...origin/master` | Container neu gestartet, Env-Keys aktiv |
| `borg-ui` | `cd650b1` | `## master...origin/master` | Container healthy, `/mnt/user/services -> /local/services` gemountet |
| `monitoring` | `cd650b1` | `## master...origin/master`, untracked Host-Backup `monitoring/prometheus/alerts.yml.bak-20260520-incident` | Stack laeuft seit 4 Tagen |
Gitea Live-Env ohne Secrets:
```text
GITEA__service__DISABLE_REGISTRATION=true
GITEA__openid__ENABLE_OPENID_SIGNIN=false
GITEA__openid__ENABLE_OPENID_SIGNUP=false
GITEA__webhook__ALLOWED_HOST_LIST=komodo-core,localhost,127.0.0.1,192.168.178.0/24
```
## 9.4 Borg-Lauf-Frische und Dump-Coverage
- Borg UI DB: Repository `appdata-critical`, `last_backup=2026-05-23 02:30:12 UTC`, `archive_count=30`, letzter Job `completed`, `nfiles=100056`.
- Schedule: `Taegliche Sicherung` enabled, letzter Lauf `2026-05-23 02:30:11 UTC`, naechster Lauf `2026-05-24 02:30:00 UTC`.
- Hinweis: `docker exec borg-ui borg list --short` funktioniert ohne Repository-Parameter/Env nicht (`Invalid location format: ""`). Der Borg-UI-Status wurde deshalb ueber die lokale Borg-UI-Datenbank ohne Secret-Spalten ausgewertet.
Frische Artefakte in `/mnt/user/backups/borg/dumps/latest`:
```text
2026-05-23 04:00 postgresql17-globals.sql
2026-05-23 04:00 postgresql17-mailarchiver.dump
2026-05-23 04:00 postgresql17-paperless.dump
2026-05-23 04:01 postgresql17-authelia.dump
2026-05-23 04:01 mealie.dump
2026-05-23 04:01 gitea.sqlite.dump
2026-05-23 04:01 immich.dump
2026-05-23 04:01 nextcloud.dump
2026-05-23 04:01 uptime-kuma.sqlite.dump
2026-05-23 04:01 vaultwarden.sqlite.dump
2026-05-23 04:01 speedtest-tracker.sqlite.dump
2026-05-23 04:01 filebrowser.bolt.dump
2026-05-23 04:01 borg-ui.sqlite
2026-05-23 04:01 grafana.sqlite
2026-05-23 04:01 komodo-mongo.archive.gz
```
Bewertung: 15 aktuelle Dump-/Archive-Artefakte sind juenger als 24 h. Aeltere nackte `.sqlite`-Dateien vom 2026-05-16 liegen noch im Verzeichnis, sind aber Legacy-Kopien ohne `.dump`-Suffix und nicht Teil der aktuellen Dump-Serie.
## 9.5 Monitoring-Stack
- Aktive Container: `monitoring-grafana`, `monitoring-promtail`, `monitoring-prometheus`, `monitoring-cadvisor`, `monitoring-influxdb3-core`, `monitoring-loki`, `monitoring-blackbox-exporter`, `monitoring-alertmanager-ntfy-bridge`, `monitoring-alertmanager`, `monitoring-node-exporter`.
- Alte Altcontainer `grafana`, `influxdb3-core`, `loki`, `alloy`: nicht vorhanden in `docker ps -a`.
- Secret-Dateien vorhanden und mode `600`: `monitoring_grafana_admin_password.txt`, `monitoring_grafana_influxdb_token.txt`, `influxdb3_admin_token.json`.
- `https://monitoring.kaleschke.info/`: `HTTP/2 302` zu Authelia, wie erwartet.
- Host-Listener fuer Monitoring: nur `127.0.0.1:8181`; keine Host-Listener fuer `:9090` oder `:3100`.
- Prometheus readiness: `Prometheus Server is Ready.`
- Grafana health: `database=ok`, Version `12.4.3`.
- Loki: Container laeuft und API/metrics liefert `loki_build_info`; `/ready` liefert aktuell `503 Service Unavailable`, waehrend Query-/Metrics-Endpunkte 200-Zaehler zeigen. Kein Reparaturversuch gestartet, weil der produktive Logpfad nachweislich Daten annimmt und die Stop-Regel keine blinden Eingriffe erlaubt.
## 9.6 GitOps Spot-Check Gitea
- `/mnt/user/services/stacks/gitea`: `cd650b1`, `## master...origin/master`.
- Docker-Image: `docker.gitea.com/gitea:1.25.4`.
- Live-ENV spiegelt `cd650b1` fuer Registrierung, OpenID und Webhook-Allowlist.
## 9.7 Host-Listener
Dokumentierte Listener gefunden:
- `:80`, `:443` Traefik
- `:53` AdGuard DNS
- `:222` Gitea SSH
- `:8082` AdGuard Admin
- `127.0.0.1:8181` Monitoring InfluxDB
Zusaetzlich sichtbar: mehrere `wsdd2` Listener auf `:5355` je Interface. Das ist Host-/Unraid-Service-Discovery, kein Compose-Webdienst und kein GitOps-Stack-Port.
+895
View File
@@ -0,0 +1,895 @@
# Homelab-Audit KalliLab CORE
Stand: 2026-05-25
Methode: Repo-basierter Audit auf `master` (lokaler Clone). Keine Live-Messung gegen den Host. Querverweise auf Audit-Live-Daten aus `docs/AUDIT_2026-05-23_LIVE.md`, wo verfuegbar.
Auftrag: externer, kritischer Audit-Blick zusaetzlich zur internen `docs/STRATEGISCHE_BEWERTUNG_2026-05-23.md`.
## Wichtige Vorbemerkung
Es gibt seit dem 23.05. eine fundierte interne Bewertung (`docs/STRATEGISCHE_BEWERTUNG_2026-05-23.md`) und eine konsolidierte Hausaufgabenliste (`docs/CODEX_KONSOLIDIERUNG_2026-05-23.md`). Davon wurden seit dem 25.05. bereits umgesetzt:
- Jellyfin entfernt (MASTER 7.8)
- Homepage entfernt (MASTER 7.8)
- Uptime-Kuma entfernt (MASTER 7.8, SECRETS_MAP 29)
- Monitoring-Stack produktiv (AUDIT_FINAL 9), Altstaende-Container down
- Disk1 NTFS -> XFS Phase 2 abgeschlossen am 2026-05-25 (STORAGE_LAYOUT 3)
- Unraid-Flash-Backup live (`unraid-flash-config.tar.gz` im Borg-Lauf)
- GitHub-Push-Mirror `michaelkaleschke-spec/homelab-infra` aktiv (DR 10, MASTER 7.1)
Diese geloesten Punkte werden hier nicht wiederholt. Dieser Audit konzentriert sich auf das, was nach dem 23.05.-Sprint **noch offen ist** und auf das, was die strategische Bewertung **nicht oder nur kurz angerissen** hat.
---
# Phase 1: Repo-Inventar
## Ordnerstruktur (Ist-Zustand)
```
homelab-infra/
├── apps/ 9 Stacks (bentopdf, immich, mail-archiver, mealie, nextcloud, ntfy, paperless, paperless-gpt, unbound)
├── core/ 1 Stack (gitea)
├── docs/ 28 Markdown-Dokumente
├── env/ 2 *.example
├── host-services/ 3 Stacks (Adguard, plex, tailscale)
├── infra/ 3 Stacks (ddns-updater, postgresql17, redis)
├── monitoring/ 1 Compose mit 10 Services + Provisioning
├── ops/ 17 Verzeichnisse (Semaphore, borg-ui, code-server, filebrowser, glance, glances, grafana-influxdb [Altstand], hermes-agent, komodo, loki [Altstand], policy-checks, restore-tests, scrutiny, speedtest, uptime-kuma [Altrest], windows-reinstall)
├── security/ 2 Stacks (authelia, vaultwarden)
├── services/ 1 posture-check (Host-Skripte)
└── traefik/ 1 Compose + dynamic/ (3 Files)
```
**Inventar-Befund:**
- ~30 Compose-Dateien, 1 zentraler Compose-Multi-Service (`monitoring/`).
- 29 Composes wurden vom Policy-Checker validiert (`ops/policy-checks/last-report.md`): **0 Critical, 4 Warnings, 9 Info**.
- Doku-Dichte ist hoch (REPO_MAP, SERVICE_CATALOG, RESTORE_MATRIX, DISASTER_RECOVERY, SECRETS_MAP, WORKFLOW, STORAGE_LAYOUT, GITOPS_DRIFT_RUNBOOK, ALERTING_MAP).
- Restore-Tests sind als echte Scripts versioniert (`ops/restore-tests/`). Ueberdurchschnittlich.
## Gut dokumentierte Bereiche (Belegt)
| Bereich | Quelle |
|---|---|
| Architektur, Netze, Ausnahmen | `HOMELAB_ARCHITECTURE_MASTER_V2.md` |
| GitOps-Workflow, Drift | `docs/WORKFLOW.md`, `docs/GITOPS_DRIFT_RUNBOOK.md` |
| Backup-Scope, Restore-Wege, Tier-Modell | `docs/RESTORE_MATRIX.md`, `docs/DISASTER_RECOVERY.md`, `ops/borg-ui/BACKUP_SCOPE.md` |
| Storage / Cache-Policy / FS / Posture | `docs/STORAGE_LAYOUT.draft.md` |
| Secret-Inventur | `docs/SECRETS_MAP.md` |
| Alert-Pfade | `docs/ALERTING_MAP.md` |
## Luecken / Unklar (vermutet bzw. Rueckfrage noetig)
| Luecke | Warum es ein Audit-Loch ist |
|---|---|
| `docs/STORAGE_LAYOUT.draft.md` ist `Draft 1.3`, nicht `Active` | Mehrere Hard Rules (12 Constitution) gelten formal noch nicht. Hard Rule 11 (kein Stack ohne Restore-Pfad in RESTORE_MATRIX) wird heute schon eingehalten — also nur Formal-Luecke. |
| `docs/SERVICES_RECOVERY.md` ist als verbindlich angekuendigt (STORAGE_LAYOUT 4), aber nicht im Repo | Konkrete Mirror-Mechanik fuer `services/gitea/git/repositories/` ≤ 6 h ist nirgends spezifiziert. |
| Hardware-Inventar: kein zentrales Dokument | Keine Stelle im Repo nennt CPU-Modell, RAM-Groesse, NIC-Speed, Mainboard, Parity-Disk-Groessen — nur "Samsung 970 EVO Plus 2 TB" steht in STORAGE_LAYOUT 3. |
| USV: keine Erwaehnung | Keine Datei nennt eine USV. Unklar, ob vorhanden. |
| Familien-/User-Onboarding-Doku | Keine Doku, die deine Frau/Kinder lesen muessten ("So loggst du dich in Nextcloud ein"). Aktuell ist alles Operator-Doku. |
## Fuer den Audit besonders wichtige Dateien (verwendet)
- `HOMELAB_ARCHITECTURE_MASTER_V2.md` — komplett
- `docs/WORKFLOW.md`, `docs/REPO_MAP.md`, `docs/SERVICE_CATALOG.md` — komplett
- `docs/DISASTER_RECOVERY.md`, `docs/RESTORE_MATRIX.md`, `docs/SECRETS_MAP.md` — komplett
- `docs/STORAGE_LAYOUT.draft.md`, `docs/STRATEGISCHE_BEWERTUNG_2026-05-23.md` — komplett
- `docs/AUDIT_2026-05-23_LIVE.md`, `docs/AUDIT_2026-05-23_FINAL.md`
- `ops/policy-checks/last-report.md`
- `monitoring/docker-compose.yml`, `monitoring/prometheus/alerts.yml`
- `traefik/docker-compose.yml`, `traefik/dynamic/middlewares.yml`
- `security/authelia/configuration.yml`, `security/authelia/docker-compose.yml`
- `apps/paperless/docker-compose.yml`, `apps/immich/docker-compose.yml`, `apps/nextcloud/docker-compose.yml`
- `host-services/Adguard/docker-compose.yml`
---
# A. Executive Summary
**Was schon stark ist:**
- GitOps-Disziplin: Gitea als Sollzustand, Komodo als Consumer, dokumentierter Drift-Runbook, Stop-Regel ("zwei Fehlversuche -> Pflichtmatrix"). Seltene Reife.
- Backup-Architektur: Pre-Backup-Dumps + Borg + Restore-Tests mit echtem Smoke-Test-Kriterium ("Container startet ≠ Erfolg"). 15 frische Dumps < 24 h alt (`AUDIT_LIVE 9.4`).
- Architektur-Klarheit: `frontend_net` / `backend_net` / app-interne Netze, keine Sammelnetze, dokumentierte Ausnahmen.
- Image-Pinning: Tier-1-Stateful mit `<minor>@sha256:...`. Konsequent durchgezogen.
- Secrets-Hygiene: Keine Secret-Werte im Repo, `_FILE`-Mounts + Komodo Stack ENV, explizit dokumentierte Ausnahmen.
- Policy-as-Code: `check_repo.ps1` mit 0 Critical und sauber dokumentierten Exceptions.
**Was kritisch ist:**
- **AdGuard Admin-Port 8082 ohne Authelia/2FA am LAN gebunden** (`host-services/Adguard/docker-compose.yml:16`) — dokumentierte "Operator-Entscheidung" 2026-05-25. Im Heim-LAN tolerierbar, mit IoT/Gaeste-WLAN potenziell ein Pfad zur DNS-Manipulation. Niedrigster Aufwand: Bind nur auf Tailscale-Interface.
- **Authelia ACL: 2FA nur fuer `files.kaleschke.info` und `scrutiny.kaleschke.info`** (`security/authelia/configuration.yml:44-48`). Borg-UI, Code-Server, Filebrowser, Glance — alles nur `one_factor`. Bei Pwd-Kompromittierung des Operator-Accounts ist Borg-UI + Code-Server der direkteste Pfad zur Datenexfiltration.
- **Authelia-Repo-Baseline ↔ Host-Config-Drift "by design"** (`docs/REPO_MAP.md:48`, `SERVICE_CATALOG 23`). User-DB, OIDC-Clients und Secrets sind hostseitig, Manual-Merge-Pflicht. Stelle, an der Drift mit Anlauf passiert.
- **Komodo Self-Bootstrap-Problem ist nur dokumentiert, nicht geloest** (MASTER 13: Self-Stack Drift-Recovery 2026-05-04). Bei Recovery vom kalten Host musst du Komodo aus `compose.yaml` neu erzeugen — dafuer brauchst du die `.env` mit `KOMODO_*`-Secrets, die nur auf Host und ggf. Vaultwarden liegen.
- **Backup-Off-Site-Diversitaet:** BorgBase Hetzner ist Single-Provider; Borg-Passphrase analog gesichert ist als TODO markiert (`docs/DISASTER_RECOVERY.md:64,401`). Wenn Hetzner-Account verloren geht, ist das halbe DR-Versprechen weg.
**Was unnoetig komplex ist:**
- Drei dokumentierte Monitoring-/Logging-Pfade gleichzeitig im Repo: `ops/grafana-influxdb` (Altstand), `ops/loki` (Altstand), `monitoring/` (Ziel). Die Altstaende sind als Container down, aber **Verzeichnisse noch im Repo** — Doppelpflege-Risiko. Der versprochene Repo-Cleanup (`git rm`) fehlt.
- Hermes-Agent: NAS-Stack bewusst deaktiviert ("VM-seitig offen"), aber Stack-Verzeichnis und Compose mit Dashboard-Domain bleiben im Repo. Mehr "Schwebezustand" als operativer Wert.
- BentoPDF: "vorbereitet", noch nie produktiv abgenommen (`SERVICE_CATALOG 52`, `MASTER 7.5`).
- `infra/redis` ist als shared Cache deklariert, wird de facto nur von Paperless genutzt (Authelia nicht, Immich/Nextcloud/Mealie haben eigene Redis). Das "shared" stimmt im Repo nicht mit der Realitaet ueberein.
**Groesster Hebel:**
**Authelia OIDC-Provider aktivieren** — wenn Nextcloud, Immich, Grafana (und perspektivisch Mealie via OIDC-Bridge) per SSO laufen, gewinnst du gleichzeitig:
- (a) Familien-Onboarding-Komfort (ein Login),
- (b) zentrale Brute-Force-Regulation und Audit,
- (c) Voraussetzung fuer sinnvolles CrowdSec/Fail2Ban,
- (d) zentrale 2FA-Pflicht statt App-by-App.
Das ist ein Sprint, nicht ein Quartal, und macht aus deinem "Admin-Authelia" ein echtes Identity-System.
**Was ein erfahrener Homelabber sofort aendern wuerde:**
1. AdGuard-Admin-Port nur auf Tailscale-Interface binden (5 Min, Compose-Edit).
2. Borg-Passphrase auf Papier in Bankschliessfach (15 Min, off-system).
3. `scrutiny` und `ddns-updater` `no-new-privileges` Warning aufraeumen (10 Min) — kosmetisch, aber Policy-Check sollte clean sein.
4. Altstaende `ops/grafana-influxdb/` und `ops/loki/` aus Repo entfernen (Backup-Branch dann `git rm`).
5. Renovate-Bot gegen Gitea einrichten — beendet manuelle Digest-Pflicht.
---
# B. Scorecard (1 = exzellent, 10 = ungenuegend)
| Bereich | Note | Begruendung |
|---|---:|---|
| Hardware | **nicht bewertbar** | Keine Inventar-Doku im Repo. Nur Cache-NVMe genannt. Siehe Phase H. |
| Ordnerstruktur | **2** | Klare Trennung apps/infra/ops/security/core; konsistente Namenskonvention (mit Migrationsplan in STORAGE_LAYOUT 6). Kleinerer Haenger: `host-services/Adguard/` mit Grossbuchstabe. |
| Storage | **3** (Repo-Stand) / **2** (mit STORAGE_LAYOUT Active) | Cache-XFS, Disk1-XFS jetzt erreicht. Pfad-Disziplin via `/mnt/user/...`. Posture-Check etabliert. Note durch Draft-Status und fehlendes `SERVICES_RECOVERY.md` gedrueckt. |
| Docker-Architektur | **2** | Netzmodell klar, Healthchecks fehlen grossflaechig, `latest@sha256` als bewusster Kompromiss bei einigen Images dokumentiert. Keine Memory-Limits. |
| Reverse Proxy / Zugriff | **2** | DNS-Challenge, Wildcard-faehig, Authelia ForwardAuth, dynamic config sauber getrennt. Manuelle Host-Sync-Ausnahme ist pragmatisch. |
| Security | **3** | Solides Fundament (Authelia Argon2id, no-new-privileges-Standard, Webhook-Allowlist, `cloudflare_dns_api_token` als Docker Secret), aber: nur 2 Domains mit 2FA, AdGuard-Admin direkt am LAN, kein WAF/Bouncer, Authelia Regulation 5-Min-Ban ist gentil. |
| Netzwerk / DNS | **2** | AdGuard + Unbound + Tailscale-Trias ist Best-of-Class-Homelab. FritzBox als Router nicht im Repo dokumentiert, daher Note nicht 1. |
| Backup | **2** | Borg, Pre-Dumps, Tier-Modell, dokumentierter Scope. Punkt-Abzug: Single-Provider Off-Site, Passphrase nicht analog, Komodo-Mongo-Dump-Verifikation nicht im Auto-Cron. |
| Restore-Faehigkeit | **2** | RESTORE_MATRIX mit Smoke-Test je Dienst, Restore-Test-Schedule + Validierungen fuer Vaultwarden/Gitea/Paperless dokumentiert. Punkt-Abzug: Immich-Restore noch nie geuebt — groesster Datentopf. |
| GitOps | **1-2** | Webhook-Pflicht fuer neue Stacks, Source-of-Truth-Hierarchie, Drift-Runbook. Self-Bootstrap-Problem von Komodo zieht von 1 auf 2. |
| Monitoring | **3** | Stack produktiv, aber Altstaende noch im Repo, Family-View-Dashboard fehlt, Alerts (`alerts.yml`) sehr knapp (5 Regeln), keine Cert-Expiry-Alert auf Prometheus-Ebene (Cert-Token-Check laeuft separat). |
| Dokumentation | **1** | Aussergewoehnlich. SERVICE_CATALOG ist Gold. Einziger Punkt: kein End-User-/Familien-Onboarding. |
| Automatisierung | **3** | Borg-Dumps automatisiert, posture-check Host-Cron, Alert->ntfy-Pipe. Aber: keine CI gegen Repo, kein Renovate, kein automatisches Image-Update-Tracking. |
| Wartbarkeit | **2** | Doku traegt; Sprintpflege-Disziplin sichtbar (MIGRATION_LOG, AUDIT_FINAL). Risiko: Authelia-Drift, Hermes-Schwebezustand. |
| Nerd-Faktor | **2-** | Komodo + Borg-UI + ntfy-Bridge + Posture-Check + Restore-Lab + Hermes-Experiment + Push-Mirror + Digest-Pinning. Liegt zwischen "Solider Senior" und "Spielwiese halten lernen". |
**Gesamteindruck: 2 (gut).** Strukturell weit ueber durchschnittlichem Homelab; konkrete Luecken sind klar benennbar und nicht systemisch.
---
# C. Top-20 Findings
> Format: priorisiert nach Risiko-zu-Aufwand-Hebel. Jeder Eintrag hat Fundstelle, Empfehlung und Prio.
### F-01 · AdGuard-Admin am LAN ohne Auth
- **Kategorie:** Security / Zugriff
- **Fundstelle:** `host-services/Adguard/docker-compose.yml:16`, `MASTER 10`, `docs/SERVICE_CATALOG.md:14`
- **Beobachtung:** Port `8082:80` direkt auf alle Interfaces. Bewusste Operator-Entscheidung 2026-05-25.
- **Risiko:** Jedes Geraet im LAN kann DNS-Filterregeln, Upstream und Logging manipulieren. IoT-Kompromittierung oder Gast-WLAN -> DNS-Hijack moeglich.
- **Best Practice:** Admin-UIs nicht im LAN ohne Auth. Entweder hinter Traefik+Authelia mit `two_factor` oder Bind auf Tailscale-Interface (z. B. `100.x.y.z:8082:80`).
- **Empfehlung:** Schritt 1 — Bind auf Tailscale-IP (S, 5 Min). Schritt 2 — optional spaeter Traefik-Route hinter Authelia.
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** S
- **Validierung:** `ss -ltnp | grep :8082` zeigt nur Tailscale-IP; LAN-Browser-Zugriff schlaegt fehl.
- **Rollback:** Compose-Diff zurueck, Komodo redeploy.
### F-02 · Borg-Passphrase nicht analog gesichert
- **Kategorie:** Backup / DR
- **Fundstelle:** `docs/DISASTER_RECOVERY.md:64,401`, `docs/SECRETS_MAP.md:48`
- **Beobachtung:** `borg_repo_passphrase.txt` liegt im Host-Filesystem unter `/mnt/user/appdata/secrets/`. Doku weist explizit darauf hin, dass eine externe analoge Sicherung Operator-Aufgabe ist.
- **Risiko:** Wenn Unraid-Host und ggf. Vaultwarden gleichzeitig defekt sind, ist das verschluesselte Borg-Repo bei Hetzner nutzlos.
- **Empfehlung:** Auf Papier ausdrucken, in Bankschliessfach oder bei vertrauter Person versiegelt. Zusaetzlich in Vaultwarden hinterlegen (aber Vaultwarden hilft nicht, wenn es selbst restauriert werden muss).
- **Prioritaet:** Muss sofort
- **Aufwand:** S
- **Validierung:** Du kannst den Wert ohne Host wiederherstellen.
### F-03 · Single-Provider Off-Site Backup
- **Kategorie:** Backup
- **Fundstelle:** `ops/borg-ui/BACKUP_SCOPE.md`, `docs/RESTORE_MATRIX.md:77-96`, `STORAGE_LAYOUT 8.1`
- **Beobachtung:** Hetzner Storage Box als alleiniges Off-Site-Borg-Ziel. STORAGE_LAYOUT 8.1 sieht zusaetzlich lokales Borg-Repo auf `/mnt/user/backups/borg/` vor (gleicher Host) und eine externe Wechselplatte (manuell rotiert).
- **Risiko:** Hetzner-Account-Verlust (Payment-Issue, Account-Hack, Provider-Outage) = halbes 3-2-1.
- **Best Practice:** Zweites Off-Site-Ziel mit unterschiedlichem Provider oder Cold-Wechselplatte mit fester Rotationskadenz.
- **Empfehlung:** (a) Wechselplatten-Rotation in fester Kadenz dokumentieren (zwei Platten, monatlicher Tausch). Oder (b) zweites Borg-Repo bei rsync.net / BorgBase EU2 / privatem 2. Standort.
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** M
- **Validierung:** `borg list` gegen beide Repos, beide < 7 Tage alt.
### F-04 · Authelia 2FA-Pflicht zu schmal
- **Kategorie:** Security
- **Fundstelle:** `security/authelia/configuration.yml:44-53`
- **Beobachtung:** Nur `files.kaleschke.info` und `scrutiny.kaleschke.info` sind `two_factor`. Tier-1-Operator-UIs wie Borg-UI, Code-Server, Filebrowser (zweite Route?), Komodo (eigene Auth), Glance, Grafana laufen mit `one_factor`.
- **Risiko:** Operator-Passwort-Kompromittierung (Phishing, Keylogger, Browser-Save-Leak) gibt ohne 2FA Vollzugriff auf Code-Server (Repo + Workspace), Borg-UI (Restore-Pfade), Filebrowser (Documents/Photos).
- **Empfehlung:** ACL erweitern: `two_factor` fuer `borg.kaleschke.info`, `code.kaleschke.info`, `files.kaleschke.info` (schon), `glance.kaleschke.info` (debattierbar), `traefik.kaleschke.info`. Komodo bleibt Ausnahme.
- **Prioritaet:** Muss sofort
- **Aufwand:** S
- **Validierung:** Nach Login auf `borg.kaleschke.info` wird 2FA-Prompt erzwungen.
- **Rollback:** ACL-Block zurueck.
### F-05 · Repo-Altstaende `ops/grafana-influxdb/` und `ops/loki/` nicht entfernt
- **Kategorie:** Wartbarkeit / GitOps
- **Fundstelle:** Repo-Wurzeln `ops/grafana-influxdb/`, `ops/loki/`; `MASTER 7.6`, `SERVICE_CATALOG 68-70,80-81`, `AUDIT_FINAL 9`
- **Beobachtung:** Container down, aber Compose-Dateien + Provisioning bleiben im Repo. Doku referenziert beide gleichzeitig. Risiko: jemand (zukuenftiges Ich, KI) deployt versehentlich den Altstand.
- **Empfehlung:** `git rm` der beiden Verzeichnisse, Tag `pre-monitoring-cleanup` fuer Rollback, MIGRATION_LOG-Eintrag.
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** S
- **Validierung:** `policy-checks` laeuft clean, Repo enthaelt nur noch `monitoring/`.
### F-06 · Hermes-Agent im Schwebezustand
- **Kategorie:** App-Landschaft / Wartbarkeit
- **Fundstelle:** `ops/hermes-agent/docker-compose.yml`, `MASTER 7.5`, `SERVICE_CATALOG 82-83`
- **Beobachtung:** "NAS-Stack bewusst deaktiviert" wegen offener VM-Seite. Dashboard-Domain (`hermes.kaleschke.info`) + Authelia-ACL + Secret-Pfade dokumentiert.
- **Risiko:** Schleichender Verfall — in 6 Monaten verstehst du Model-C nicht mehr ohne `ops/hermes-agent/README.md`. Bei jeder Authelia-/Compose-Aenderung musst du Hermes mitpruefen, obwohl es nichts tut.
- **Empfehlung:** Operator-Entscheidung mit 60-Tage-Deadline ehrlich treffen. Wenn nicht produktiv bis 2026-07-25: `git rm ops/hermes-agent/`, Domain aus DNS, ACL-Eintrag raus.
- **Prioritaet:** Sollte zeitnah (Entscheidung)
- **Aufwand:** S (Entfernen) / L (echte Produktiv-Aktivierung)
- **Validierung:** Entweder Smoke-Test auf `hermes.kaleschke.info` mit funktionalem Use-Case-Beleg, oder Repo-clean.
### F-07 · Monitoring-Stack ohne Digest-Pin
- **Kategorie:** Reproduzierbarkeit / GitOps
- **Fundstelle:** `monitoring/docker-compose.yml:3,28,66,84,100,118,276,296`
- **Beobachtung:** Prometheus, Alertmanager, Blackbox, Loki, Promtail, Grafana, node-exporter, cAdvisor sind alle nur per Tag gepinnt (`prom/prometheus:v3.7.3`, `grafana/grafana:12.4.3`, ...). Nur `influxdb3-core` hat `@sha256:`. Das widerspricht der Image-Versionierungs-Disziplin der Tier-1-Stateful-Dienste.
- **Risiko:** Wenn upstream einen Tag erneut pushed (Versionsdrift, Supply Chain), wird beim Rebuild ein anderer Container deployed — gerade Monitoring sollte stabil sein.
- **Empfehlung:** Beim naechsten Komodo-Redeploy aktuellen Digest auslesen und einpinnen. Vorbereitung fuer Renovate (F-12).
- **Prioritaet:** Nice to have
- **Aufwand:** S
- **Validierung:** `grep '@sha256' monitoring/docker-compose.yml` listet alle 10 Services.
### F-08 · Alert-Regeln zu duenn
- **Kategorie:** Monitoring
- **Fundstelle:** `monitoring/prometheus/alerts.yml`
- **Beobachtung:** Exakt 5 Regeln: ExternalConnectivityDown, EndpointDown, EndpointSlow, DiskAlmostFull, MemoryHighUsage, Traefik5xx. Es fehlen:
- Borg-Lauf-Frische (ueber `node_exporter` textfile collector oder Pushgateway).
- Zertifikatslaufzeit (Blackbox kann `probe_ssl_earliest_cert_expiry`, aber keine Alert-Regel dafuer).
- Container-down-Alert (cAdvisor liefert `container_last_seen`).
- PostgreSQL-Connection-Saturation.
- Loki ingestion-rate / log-volume spike.
- InfluxDB-Disk-Pressure.
- Backup-Job-Failure.
- **Risiko:** Du siehst Probleme nicht, bevor sie weh tun. Cert-Expiry und Borg-Stale sind die schmerzhaftesten Blind-Spots.
- **Empfehlung:** Mindestens zwei Regeln nachziehen: `BorgArchiveStale` (>30 h, Pushgateway oder textfile) und `TLSCertExpiryNear` (<14 Tage). Rest als Folge-Sprint.
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** M
- **Validierung:** Alerts feuern in Test-Bedingung (Borg-Dump-File touch -d backwards).
### F-09 · Komodo-Self-Bootstrap-Problem
- **Kategorie:** GitOps / DR
- **Fundstelle:** `MASTER 13: Komodo Self-Stack Drift-Recovery 2026-05-04`
- **Beobachtung:** Du hattest schon einen Drift-Vorfall (Komodo-Core ran aus `/tmp/*repair.yml`, Mongo-Pfad fehlte). Recovery-ENV liegt als "temporaeres Tier-1-Secret-Material" unter `/mnt/user/appdata/secrets/_komodo_stack_env_recovery_2026-05-04.env` (Doku-Stand).
- **Risiko:** Bei Totalausfall musst du Komodo aus Compose-Datei wiederbeleben, dafuer brauchst du die Stack-ENV mit `KOMODO_SECRET_KEY`, `KOMODO_MONGO_PASSWORD`, `KOMODO_PERIPHERY_PASSKEY` etc., die nur als Komodo Stack ENV existieren. Klassisches Henne-Ei.
- **Empfehlung:** Komodo-Self-Stack aus Komodos eigener Verwaltung herausnehmen und als handgepflegten `docker compose`-Service in `services/komodo-bootstrap/` halten. Stack-ENV als versiegelte Datei unter `/mnt/user/appdata/secrets/` mit deterministischem Restore-Pfad in RESTORE_MATRIX.
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** M
- **Validierung:** Komodo-Stack-Datei lebt im Repo unter `services/`, nicht in Komodos eigener Workspace-Sicht.
### F-10 · Authelia Repo↔Host Drift "by design"
- **Kategorie:** GitOps / Security
- **Fundstelle:** `docs/REPO_MAP.md:48`, `security/authelia/configuration.yml`, `SERVICE_CATALOG 23`
- **Beobachtung:** Repo enthaelt Baseline ohne Secrets, OIDC, Users-DB. Manuelles Merge auf den Host noetig. Es gibt keine automatische Konsistenz-Pruefung.
- **Risiko:** Repo-Aenderung (z. B. neue ACL-Regel) wird gepusht, aber nie auf den Host gemerged -> Drift, Authelia hinkt der Wahrheit hinterher.
- **Empfehlung:** Symmetrisch zum Traefik-Dynamic-Workflow (manueller Sync explizit als Pflicht in WORKFLOW.md). Zusaetzlich ein einfaches Diff-Script `services/authelia-diff.sh`, das `diff` zwischen Repo-Baseline und Host-Datei zeigt, und das im posture-check als Warning auftaucht, wenn die ACL-Sektion differiert.
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** S
- **Validierung:** Script laeuft, posture-check kennt einen neuen Check `authelia_config_drift`.
### F-11 · Immich-Restore noch nie geuebt
- **Kategorie:** Backup / Restore
- **Fundstelle:** `docs/RESTORE_MATRIX.md:49`, Restore-Test-Schedule
- **Beobachtung:** Vaultwarden / Gitea / Paperless haben Mini-Restore-Tests (2026-05-07). Immich nicht. Immich ist der groesste Datentopf (Familien-Fotos).
- **Risiko:** Silent Corruption in Postgres-pgvecto-rs-Daten bemerkst du erst beim Restore-Versuch.
- **Empfehlung:** Eigener Sprint: Immich-Restore-Test gegen `/mnt/user/backups/restore-lab/immich/` mit Sub-Set der `immich.dump` und einem Foto-Sample. Smoke-Test = "10 Fotos im Browser sichtbar nach Restore".
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** M
- **Validierung:** Report unter `/mnt/user/backups/restore-reports/immich-<datum>.json`.
### F-12 · Keine Image-Update-Automatik (Renovate o. ae.)
- **Kategorie:** Wartbarkeit
- **Fundstelle:** Repo-weit; `docs/WORKFLOW.md:282-288` (Image-Versionierung)
- **Beobachtung:** Digest-Pinning ist konsequent, aber rein manuell. Bei ~30 Images bedeutet das, du musst monatlich fuer Patch-Updates manuell Digests auslesen — oder es bleibt liegen.
- **Risiko:** CVE-Patches werden nicht eingespielt, weil "der laufende Stand ist stabil".
- **Empfehlung:** Renovate Bot (self-hosted, gegen Gitea), Gitea-Actions-Runner. Renovate oeffnet PRs fuer Patch-/Minor-Updates; Major-Updates werden mit Labels separiert.
- **Prioritaet:** Sollte zeitnah (oder Nice to have, je nach Schmerz)
- **Aufwand:** M (Initial-Setup ist substantiell)
- **Validierung:** Renovate hat erste PRs in Gitea geoeffnet, du mergst eines davon kontrolliert.
### F-13 · Keine OIDC-SSO fuer User-Apps
- **Kategorie:** Security / UX
- **Fundstelle:** `security/authelia/configuration.yml`, `docs/SECRETS_MAP.md`
- **Beobachtung:** Authelia kann OIDC, ist aber nur als ForwardAuth konfiguriert. Nextcloud, Immich, Grafana, Mealie laufen mit eigenen User-DBs.
- **Risiko:** N getrennte Passwortspeicher, N getrennte 2FA-Setups, keine zentrale Sperrung bei Account-Kompromittierung. Familie hat keinen einfachen Onboarding-Pfad.
- **Empfehlung:** OIDC-Provider in Authelia aktivieren, Nextcloud (via Plugin), Immich (nativer OIDC-Support), Grafana (nativer OIDC-Support) als Clients konfigurieren. Vaultwarden via OIDC-Bridge nur, wenn der Aufwand klar mehrwertig ist — sonst bewusst auslassen.
- **Prioritaet:** Sollte zeitnah (groesster Hebel laut Executive)
- **Aufwand:** L
- **Validierung:** Familienkonto kann sich mit einem Login bei Nextcloud + Immich + Grafana anmelden.
### F-14 · Kein WAF / Bouncer vor oeffentlichen Apps
- **Kategorie:** Security
- **Fundstelle:** `traefik/docker-compose.yml`, oeffentliche Hosts in `docs/REPO_MAP.md:127-152`
- **Beobachtung:** Sechs oeffentliche Apps mit nativer Auth (vault, paperless, mealie, ntfy, git, immich, cloud) ohne IP-Bouncer. Authelia-Regulation greift nur fuer die ForwardAuth-Pfade; Apps mit eigener Auth bekommen den vollen Traffic.
- **Risiko:** Credential-Stuffing-Bot-Wellen treffen die App selbst (Nextcloud, Immich) — Logs sind im Loki, aber kein Sperr-Mechanismus.
- **Empfehlung:** CrowdSec als Bouncer fuer Traefik (`crowdsecurity/traefik-bouncer`). Nutzt Loki/Logs fuer Erkennung, sperrt IPs auf Traefik-Ebene, bevor sie die Apps treffen.
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** M
- **Validierung:** CrowdSec-Dashboard zeigt erste Sperren; Test-Brute-Force gegen `nextcloud.kaleschke.info` wird bei N Versuchen geblockt.
### F-15 · Healthchecks fehlen grossflaechig
- **Kategorie:** Docker / Operations
- **Fundstelle:** Spot-checks: `apps/paperless/docker-compose.yml`, `apps/immich/docker-compose.yml`, `security/authelia/docker-compose.yml`, `traefik/docker-compose.yml` — keiner hat `healthcheck:`-Block.
- **Beobachtung:** Restart-Policy ist ueberall `unless-stopped`, aber ohne Healthcheck kann Docker keinen Crash-Loop bei "Container laeuft, aber App tot" erkennen.
- **Risiko:** Bei Soft-Failure (Postgres-Connection-Pool tot, Authelia haengt im Storage-Connect) merkst du nichts, weil Container "running" bleibt.
- **Empfehlung:** Fuer Tier-1 (Traefik `wget /ping`, Authelia `/api/health`, PostgreSQL `pg_isready`, Komodo `wget /api/healthcheck`) Healthchecks ergaenzen. Fuer Tier-2 schrittweise.
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** M (pro Stack 515 Min)
- **Validierung:** `docker ps` zeigt `(healthy)` neben den Tier-1-Containern.
### F-16 · `infra/redis` als "shared" deklariert, faktisch nur Paperless
- **Kategorie:** Architektur-Konsistenz
- **Fundstelle:** `infra/redis/docker-compose.yml`, `docs/SERVICE_CATALOG.md:31` ("shared Redis Cache")
- **Beobachtung:** Immich, Nextcloud, Mealie haben jeweils eigene Redis-Instanzen. Authelia nutzt bewusst kein Redis (MASTER 13). Paperless nutzt es laut Compose. Effektiv "Paperless-Redis im Frack des shared-Caches".
- **Risiko:** Niedrig. Aber: Wenn du `infra/redis` fuer etwas anderes wegnimmst, denkst du, es kostet Paperless was — und das waere der Fall.
- **Empfehlung:** Doku-Update: SERVICE_CATALOG 31 praezisieren ("dediziertes Redis fuer Paperless; andere Stacks haben eigene Redis-Instanzen"). Architektur bleibt, nur Etikett ehrlich machen. Alternativ: in `apps/paperless/` als App-internes Netz konsolidieren wie Mealie.
- **Prioritaet:** Nice to have
- **Aufwand:** S (Doku) / M (Architektur)
### F-17 · Plex bleibt als Host-Net-Stack
- **Kategorie:** Security / Architektur
- **Fundstelle:** `host-services/plex/docker-compose.yml`, `MASTER 7.4`
- **Beobachtung:** Plex laeuft als Host-Net wegen Discovery/GDM. Dokumentierte Ausnahme.
- **Risiko:** Plex hat hoehere Angriffsoberflaeche als Apps mit Traefik. Plex-Login wurde mehrfach Ziel von Account-Uebernahmen (Plex.tv-Auth-Issues 2024/25). Bei Plex.tv-Kompromittierung greift Authelia nicht — Plex authentifiziert gegen Plex.tv.
- **Empfehlung:** Plex bewusst beibehalten (Doku stuetzt), aber: (a) "Remote Access" in Plex-UI deaktivieren, wenn nur lokal/Tailscale genutzt. (b) Plex-Server nicht in `frontend_net` (waere schaedlich) — bleibt Host-Net, korrekt.
- **Prioritaet:** Nice to have
- **Aufwand:** S
- **Validierung:** Plex `Remote Access` UI zeigt "disabled".
### F-18 · Nextcloud ohne ForwardAuth, ohne dedizierte Brute-Force-Doku
- **Kategorie:** Security
- **Fundstelle:** `apps/nextcloud/docker-compose.yml`, `MASTER 13: Nextcloud-Entscheidung`
- **Beobachtung:** Bewusste Ausnahme (WebDAV/CardDAV). In Nextcloud selbst sind Brute-Force-Schutz, 2FA-Pflicht und App-Passwords konfigurierbar, aber nicht im Repo dokumentiert.
- **Risiko:** Familien-Konto mit schwachem Passwort + Nextcloud oeffentlich = direkter Pfad zu Dokumenten/Fotos.
- **Empfehlung:** `apps/nextcloud/POST_INSTALL.md` mit Pflicht-Checkliste: Brute-Force-Plugin aktiv, 2FA-Provider TOTP installiert, Admin-Account hat 2FA, "Enforce 2FA for admin group" gesetzt. Optional: `OCC`-Befehle als Skript in `services/nextcloud-policy/`.
- **Prioritaet:** Sollte zeitnah
- **Aufwand:** S
- **Validierung:** Test-Login ohne 2FA als Admin schlaegt fehl.
### F-19 · Keine Container-Memory-Limits
- **Kategorie:** Docker / Hardware-Schutz
- **Fundstelle:** Spot-checks aller Composes
- **Beobachtung:** Keine `mem_limit:` oder `deploy.resources.limits` Sektion in Tier-1- oder Tier-2-Stacks.
- **Risiko:** Bei Image-Bug oder Memory-Leak (z. B. Immich-ML, Paperless-OCR-Loop) kann ein Container den Host in OOM treiben. Posture-Check + Docker-Critical-Events sehen das nachher, aber praeventiver waere Container-Limit + Docker-OOM-Kill fuer den richtigen Prozess.
- **Empfehlung:** Fuer Tier-1 (Postgres, Authelia, Traefik, Komodo) sanfte Limits setzen (z. B. Postgres 2 GB, Authelia 256 MB, Traefik 256 MB). Fuer Immich-ML-Container ein hartes Limit, das Verhungern verhindert.
- **Prioritaet:** Nice to have
- **Aufwand:** M
- **Validierung:** `docker stats` zeigt `MEM USAGE / LIMIT` ungleich `unlimited`.
### F-20 · Paperless-DBPass weiter als Stack-ENV (dokumentierte Ausnahme)
- **Kategorie:** Secrets
- **Fundstelle:** `MASTER 13: Secrets in Komodo Stacks`, `docs/SECRETS_MAP.md:25`
- **Beobachtung:** Paperless unterstuetzt `_FILE` nicht fuer DB-Pass. Bewusste Ausnahme.
- **Risiko:** Stack-ENV liegt in Komodo-DB (Mongo), nicht im Repo. Bei Komodo-Mongo-Backup-Luecke fehlt das Passwort beim Restore.
- **Empfehlung:** Erweiterung der Disaster-Recovery-Doku: explizite Liste aller "Stack-ENV-only"-Secrets mit Zeiger, dass `komodo-mongo.archive.gz` fuer Restore zwingend ist, oder die ENV manuell vorgehalten werden muss (in Vaultwarden + externer Notiz).
- **Prioritaet:** Nice to have
- **Aufwand:** S
- **Validierung:** DR-Doc Abschnitt "Stack-ENV-Werte" referenziert konkrete Restore-Pfade.
---
# D. Risiko-Matrix
| Risiko | Bereich | Wahrscheinlichkeit | Auswirkung | Prioritaet | Massnahme |
|---|---|---|---|---|---|
| Borg-Passphrase weg -> Restore unmoeglich | Backup | niedrig | katastrophal | P0 | F-02 analoge Sicherung |
| Hetzner-Account-Verlust -> halbes 3-2-1 | Backup | niedrig-mittel | hoch | P0/P1 | F-03 Zweitziel |
| AdGuard-Admin-Manipulation aus LAN | Security | niedrig | hoch (DNS-Hijack) | P0 | F-01 Bind auf Tailscale |
| Operator-Pwd-Leak -> 2FA fehlt fuer Borg-UI/Code-Server | Security | mittel | hoch | P0 | F-04 2FA-ACL erweitern |
| Komodo-Self-Bootstrap-Failure nach Totalausfall | DR | niedrig | hoch | P1 | F-09 Bootstrap-Datei in `services/` |
| Authelia Repo↔Host Drift unbemerkt | GitOps/Security | mittel | mittel | P1 | F-10 Diff-Check |
| Immich Silent Corruption -> kein Restore-Test belegt | Backup | niedrig | sehr hoch (Familien-Fotos) | P1 | F-11 Restore-Test |
| Cert-Expiry unbemerkt -> Public Apps down | Operations | niedrig | mittel | P1 | F-08 Alert-Regel |
| Nextcloud Brute-Force ohne Bouncer | Security | mittel | mittel-hoch | P1 | F-14 CrowdSec / F-18 Nextcloud-Haerten |
| Image-Update-Stillstand -> CVE bleibt | Security | mittel | mittel | P1 | F-12 Renovate |
| Hermes-Wartungsschuld | Wartbarkeit | hoch | niedrig | P1 | F-06 Entscheidung |
| Repo-Altstaende ueberleben -> Doppel-Deploy | GitOps | mittel | niedrig | P1 | F-05 Cleanup |
| OOM durch unlimitierte Container | Hardware | niedrig | mittel | P2 | F-19 mem_limit |
| Healthcheck-Luecke -> Soft-Failure stumm | Operations | mittel | niedrig | P2 | F-15 Healthchecks |
| Monitoring-Stack ohne Digest-Pin | Reproduzierbarkeit | niedrig | niedrig | P2 | F-07 Digests + Renovate |
| Hardware-SPOF (kein zweiter Host) | Hardware | niedrig | sehr hoch | P3 | Cold-Standby / 2. Host |
---
# E. Zielarchitektur (realistisch fuer privates Homelab)
**Hardware**
- 1× Unraid-Host (bestehend) als Production. CPU mit AVX2/AVX-512 fuer Immich-ML. ≥ 32 GB RAM (fuer 2× Postgres + Immich-ML + Loki/Prometheus + 2 VMs).
- 2× NVMe als BTRFS-RAID1-Cache, sobald Cache-Auslastung > 70 % (STORAGE_LAYOUT 15.3).
- Parity-Disk ≥ groesste Daten-Disk.
- USV mit USB-Steuerung (NUT-faehig: APC Back-UPS RS 700+, Eaton 3S, CyberPower CP1500EPFCLCD). Direkter Shutdown bei Power-Loss.
- Optional: zweiter alter Mini-PC oder NUC als Cold-Standby mit Tailscale, der den letzten Komodo-Bootstrap + Gitea-Mirror tragen kann.
**Netzwerk**
- FritzBox (bestehend) als Router + NAT.
- VLANs nur wenn IoT-WLAN dazukommt (FritzBox-Gast-WLAN reicht fuer Anfang).
- DNS: AdGuard -> Unbound (bestehend). Admin-UI nur Tailscale.
- Tailscale (bestehend): Operator-Pfad. Subnet-Router optional fuer LAN-Devices ueber Tailscale.
**Storage**
- Cache `only` fuer `appdata`, `system`, `domains` (bestehend STORAGE_LAYOUT 4).
- Disk1 (XFS) fuer `services`, `documents`, `photos`, `backups`, `media`, `finance`, `projekte`.
- Externe Wechselplatte (XFS) fuer Cold-Off-Site mit fester monatlicher Rotation.
**Ordnerstruktur (Repo)**
- Beibehalten. Nur Cleanup von Altstaenden (F-05). Naming `kebab-case`-Migration aus STORAGE_LAYOUT 6 schrittweise.
**Docker**
- Compose-only via Komodo (bestehend).
- Digest-Pin fuer alle Images (F-07).
- Healthchecks fuer Tier-1 (F-15).
- Mem-Limits fuer Tier-1 + Immich-ML (F-19).
- App-interne Netze fuer Stack-Isolation (bestehend).
**Reverse Proxy**
- Traefik v3 (bestehend), DNS-Challenge, Wildcard.
- Dynamic Config nur fuer Middlewares, TLS, Dashboard (bestehend).
- CrowdSec-Bouncer (F-14) fuer oeffentliche Apps.
**Auth**
- Authelia als ForwardAuth **und** OIDC-Provider (F-13).
- Nextcloud, Immich, Grafana via OIDC.
- 2FA-Pflicht fuer alle Operator-Dienste (F-04).
- Komodo bewusste Ausnahme (bestehend).
**Backup**
- Borg lokal (`/mnt/user/backups/borg/`) + Borg Hetzner + Wechselplatte.
- Pre-Dump-Hooks (bestehend).
- Borg-Passphrase off-system analog (F-02).
- Restore-Tests automatisiert (F-11 Immich, dann andere via CI).
**Monitoring**
- `monitoring/`-Stack als alleinige Quelle. Altstaende raus (F-05).
- Family-View-Dashboard in Grafana (alles gruen, Backup-Frische, Cert-Tage).
- Alerts ausgebaut (F-08).
- Posture-Check + Docker-Critical-Events -> ntfy `homelab-alerts` (bestehend).
**Dokumentation**
- Aktuelle Doku-Tiefe halten.
- `SERVICES_RECOVERY.md` und `STORAGE_LAYOUT.md` (Active) finalisieren.
- Familien-/User-Onboarding-Doku als eigenes kleines Dokument.
**GitOps**
- Gitea + Komodo (bestehend).
- GitHub-Push-Mirror (umgesetzt, bestaetigt durch MASTER 7.1).
- Renovate-Bot gegen Gitea (F-12).
- Optional: Staging-Branch + zweites Komodo-Ziel in Tailscale-VM (Phase 3).
**Restore**
- RESTORE_MATRIX bleibt fuehrend.
- Restore-Lab unter `/mnt/user/backups/restore-lab/` (bestehend).
- Immich-Restore als Luecke schliessen (F-11).
- Komodo-Self-Bootstrap raus aus Komodo (F-09).
---
# F. Priorisierte Massnahmenliste
| # | Aufgabe | Warum | Kategorie | Prio | Aufwand | Risiko (bei Nicht-Tun) | Mehrwert | Abhaengigkeiten | Validierung |
|---|---|---|---|---|---|---|---|---|---|
| 1 | Borg-Passphrase analog sichern | DR-SPOF schliessen | Backup | P0 | S | katastrophal | DR-Sicherheit | — | Wert ohne Host abrufbar |
| 2 | AdGuard-Admin auf Tailscale-IP binden | LAN-Angriffsflaeche | Security | P0 | S | hoch | LAN-IoT-Haertung | — | `ss -ltnp` zeigt nur Tailscale |
| 3 | 2FA-ACL erweitern (borg, code, files, traefik) | Operator-Pwd-Leak | Security | P0 | S | hoch | 2FA-Coverage | Authelia-TOTP-Setup | Login erzwingt 2FA |
| 4 | Altstaende `ops/grafana-influxdb`+`ops/loki` `git rm` | Repo-Hygiene, kein Re-Deploy | GitOps | P0 | S | niedrig | Klarheit | Tag setzen | Policy-Check clean |
| 5 | Hermes 60-Tage-Deadline | Wartungsschuld | App | P1 | S/L | mittel | Komplexitaet raus | Operator-Entscheidung | Entweder produktiv oder weg |
| 6 | Immich-Restore-Test einrichten | Groesster Datentopf ungeprueft | Backup | P1 | M | hoch | Restore-Vertrauen | Restore-Lab-Pfad | Smoke-Test-Report |
| 7 | Renovate-Bot in Gitea | manuelle Digest-Pflege | Wartung | P1 | M | mittel | Update-Hygiene | Gitea-Runner | erste PR offen |
| 8 | Alert-Regeln (Borg-Frische, Cert-Expiry) | Blind-Spot Operations | Monitoring | P1 | M | mittel | echte Alerts | Pushgateway o. textfile | Alert in Test |
| 9 | Family-View-Dashboard Grafana | Morgens 30 s Check | Monitoring | P1 | M | niedrig | Uebersicht | Datasources stehen | Dashboard funktioniert |
| 10 | Komodo-Self-Bootstrap als `services/komodo-bootstrap/` | Henne-Ei-Problem | GitOps/DR | P1 | M | mittel | sauberer Recovery-Pfad | Komodo-Stack-Doku | Bootstrap aus Repo allein moeglich |
| 11 | Authelia-Drift-Diff-Check in posture-check | Repo↔Host Drift | GitOps | P1 | S | mittel | Drift-Detektion | posture-check-Erweiterung | neuer Check sichtbar |
| 12 | Healthchecks Tier-1 | Soft-Failure-Erkennung | Docker | P1 | M | niedrig | Self-Healing-Trigger | — | `docker ps` zeigt `healthy` |
| 13 | CrowdSec-Bouncer vor Traefik | oeffentliche Apps schuetzen | Security | P1 | M | mittel | Brute-Force-Stop | Traefik-Middleware | Test-IP wird geblockt |
| 14 | Nextcloud-Haertung dokumentieren | Public App + native Auth | Security | P1 | S | mittel | App-Haertung | Plugin-Install | 2FA-erzwingt |
| 15 | Authelia OIDC-Provider + Nextcloud/Immich/Grafana | SSO, Familien-Onboarding | Security/UX | P2 | L | niedrig | hoher Mehrwert | Authelia-OIDC-Setup | SSO-Login funktioniert |
| 16 | Immich-Smartphone-Auto-Backup fuer Familie | Killer-App fuer Familie | App | P2 | S | niedrig | hoher Mehrwert | — | Familien-Foto in Immich |
| 17 | Monitoring-Stack Digests + Renovate-Pin | Reproduzierbarkeit | GitOps | P2 | S | niedrig | konsistent | Renovate optional | `@sha256` an allen Images |
| 18 | Mem-Limits Tier-1 + Immich-ML | OOM-Schutz | Hardware/Docker | P2 | M | niedrig | Schutz | — | `docker stats` zeigt Limits |
| 19 | Off-Site-Zweitziel (rsync.net o. Wechselplatte) | Single-Provider | Backup | P2 | M | mittel | 3-2-1 echt | Borg-Config | beide Repos < 7d |
| 20 | Staging-Branch + 2. Komodo-Ziel | Risiko-Aenderung testbar | GitOps | P3 | L | niedrig | Reife | 2. VM/Host | Deploy auf staging klappt |
---
# G. Refactoring-Plan (Sprints)
## Sprint 0 — Sicherheitsnetz und Ist-Zustand sichern (1 Tag)
- **Ziel:** Du kannst danach im schlimmsten Fall alles, was du jetzt aenderst, sicher zurueckrollen.
- **Aufgaben:**
- Git-Tag `audit-2026-05-25-baseline` auf `master` setzen und nach Gitea + GitHub-Mirror pushen.
- Borg-Lauf manuell ausloesen ("freshen up"), Erfolg im Log dokumentieren.
- Aktuellen Komodo-Mongo-Dump verifizieren (`mongorestore --dry-run`).
- `docs/MIGRATION_LOG.md` Eintrag "Audit-Sprint-Start".
- **Erfolgskriterium:** Tag pushed, Borg-Lauf gruen, Mongo-Dump verifiziert.
- **Validierung:** `git fetch && git tag | grep audit-2026-05-25-baseline` und `ls /mnt/user/backups/borg/dumps/latest/` zeigt fresh.
- **Rollback:** N/A (rein additiv).
- **Risiko bei Nichtumsetzung:** Keine Notbremse fuer Sprint 1.
## Sprint 1 — Offensichtliche Risiken entschaerfen (1 Woche)
- **Ziel:** P0-Risiken weg, Repo-Hygiene wieder gruen.
- **Aufgaben (in dieser Reihenfolge):**
1. F-02 Borg-Passphrase analog sichern (off-system, kein Code-Change).
2. F-01 AdGuard-Admin-Port auf Tailscale-IP — Edit `host-services/Adguard/docker-compose.yml:16`.
3. F-04 Authelia ACL erweitern (`two_factor` fuer borg, code, files, traefik) — Edit `security/authelia/configuration.yml` + Host-Sync.
4. F-05 Altstaende `ops/grafana-influxdb/`, `ops/loki/` entfernen — `git rm`, MIGRATION_LOG.
5. Policy-Check-Warnings `SEC001` (ddns-updater, scrutiny) aufraeumen.
- **Erfolgskriterium:** Policy-Check 0 Warnings fuer SEC001, AdGuard-Admin nur via Tailscale, 2FA-Login auf borg.kaleschke.info.
- **Validierung:** Policy-Check-Report; Browser-Test mit/ohne 2FA-Cookie.
- **Rollback:** Commit-Revert pro Block.
## Sprint 2 — GitOps-Robustheit (12 Wochen)
- **Ziel:** Self-Bootstrap-Problem entschaerft, Drift-Detektion automatisiert.
- **Aufgaben:**
1. F-09 Komodo-Bootstrap-Compose nach `services/komodo-bootstrap/` extrahieren + dokumentierter Standalone-Restore-Pfad.
2. F-10 Authelia-Drift-Diff in posture-check ergaenzen.
3. F-11 Immich-Restore-Test einrichten (analog zu vaultwarden/gitea/paperless).
4. F-06 Hermes-Entscheidung mit 60-Tage-Deadline schriftlich.
- **Erfolgskriterium:** Komodo laesst sich aus Repo allein wiederherstellen. Posture-Check zeigt `authelia_config_drift: false`. Immich-Restore-Report unter `/mnt/user/backups/restore-reports/`.
- **Validierung:** Trockenversuch (Komodo-Container stoppen, `docker compose up -d` aus `services/komodo-bootstrap/`).
- **Rollback:** Bootstrap-Verzeichnis loeschen, Komodo-Self-Stack wie vorher.
## Sprint 3 — Backup & Restore belastbar machen (23 Wochen)
- **Ziel:** 3-2-1 echt, Restore-Tests breiter, Stack-ENV im DR-Pfad.
- **Aufgaben:**
1. F-03 Zweitziel: Wechselplatten-Rotation dokumentieren ODER zweites Borg-Repo (rsync.net / BorgBase EU2).
2. F-20 Stack-ENV-Liste in DR-Doc explizit machen (Restore-Reihenfolge).
3. Borg-Verifikation Cron fuer `borg check --repository-only` weekly (STORAGE_LAYOUT 8.4).
4. Quartalsweise End-to-End-Restore-Drill in Schedule aufnehmen.
- **Erfolgskriterium:** Beide Off-Site-Ziele < 7 Tage alt; DR-Doc enthaelt "ENV-Restore-Reihenfolge".
- **Validierung:** `borg list` gegen beide Repos.
## Sprint 4 — Monitoring & Alerting ausbauen (2 Wochen)
- **Ziel:** Sichtbarkeit auf das, was wirklich weh tut.
- **Aufgaben:**
1. F-08 Alert-Regeln: `BorgArchiveStale`, `TLSCertExpiryNear`, `ContainerDown`, `PostgresConnSaturation`.
2. F-15 Healthchecks fuer Traefik, Authelia, Postgres, Komodo, Gitea.
3. F-07 Digest-Pin in `monitoring/docker-compose.yml`.
4. Family-View-Dashboard in Grafana (1 Panel: Service-Up, 1 Panel: Backup-Frische, 1 Panel: Cert-Tage, 1 Panel: Disk-Fuellung).
- **Erfolgskriterium:** Family-View zeigt alles gruen; Cert-Alert feuert in Test (Datum vorgespult).
- **Validierung:** Dashboard sichtbar unter `monitoring.kaleschke.info/d/family-view`.
## Sprint 5 — Auth-Konsolidierung & Frontdoor-Haertung (34 Wochen)
- **Ziel:** SSO fuer die Familie, Brute-Force-Bouncer vor oeffentlichen Apps.
- **Aufgaben:**
1. F-13 Authelia OIDC-Provider aktivieren.
2. Nextcloud OIDC-Plugin + Test-Login.
3. Immich OIDC + Test-Login.
4. Grafana OIDC + Test-Login.
5. F-14 CrowdSec-Bouncer vor Traefik.
6. F-18 Nextcloud-Haertung-Dokument + 2FA-Pflicht.
- **Erfolgskriterium:** Familien-Konto loggt sich mit einem Login bei drei Apps ein; CrowdSec sperrt Test-IP nach N fehlerhaften Versuchen.
- **Validierung:** OIDC-Sequenz im Browser ohne Eingabe-Wiederholung; CrowdSec-Dashboard zeigt Sperre.
## Sprint 6 — Automatisierung und Nerd-Level (laufend)
- **Ziel:** Image-Update-Pipeline, optional Staging.
- **Aufgaben:**
1. F-12 Renovate-Bot gegen Gitea.
2. F-19 Mem-Limits Tier-1.
3. Restore-Test-CI via Gitea Actions (P3).
4. Optional: Staging-Branch + zweites Komodo-Ziel in Tailscale-VM (P3).
5. Optional: Firefly III / Actual Budget fuer `/mnt/user/finance`.
- **Erfolgskriterium:** Renovate-PRs erscheinen woechentlich; mindestens ein automatisches Patch-Update gemerged.
---
# H. Fehlende Informationen
> Nur, was den Audit konkret schaerfer machen wuerde.
| Frage | Warum | Bereich | Kommando / Datei |
|---|---|---|---|
| CPU-Modell, RAM-Groesse, Mainboard | Hardware-Bewertung, OOM-Risiko, Immich-ML-Eignung, AVX-Verfuegbarkeit | Hardware | `cat /proc/cpuinfo \| grep -E 'model name\|flags'`, `free -h`, `dmidecode -t baseboard \| head -20` |
| USV vorhanden? Modell? | DR-Beurteilung Power-Loss, Shutdown-Pfad | Hardware/DR | physische Sichtpruefung; `apcaccess` falls APC mit NUT |
| Stromverbrauch idle / Last | Betriebskosten, Sizing | Hardware | Smartmeter / Tibber-API |
| NIC-Speed (1 GbE? 2.5 GbE?) | Backup-Durchsatz, Plex-Streaming | Netzwerk | `ip -br link`, `ethtool eth0` |
| Disk-Inventar (Anzahl, Modelle, Alter) | Storage-Health, Replacement-Plan | Storage | `lsblk -o NAME,SIZE,MODEL,SERIAL`, Scrutiny-UI |
| Aktueller Cache-Fuellstand | Wann zweite NVMe? | Storage | `df -h /mnt/cache` |
| FritzBox-Modell + Firmware | Net-Sicherheit, VLAN-Faehigkeit | Netzwerk | FritzBox-UI / `fritzconnection` |
| Tatsaechlich genutzte Plex- vs. ungenutzte App | Konsolidierungs-Belege | App-Landschaft | Plex-Server-Logs, ggf. Glances-Container-CPU pro Stack |
| Existiert `/mnt/user/finance/`-Share schon? | Ist Firefly-Vorbereitung trivial? | Storage | `ls /mnt/user/finance/` |
| Authelia Live-User-DB-Tiefe (Anzahl User, 2FA-Status) | 2FA-Coverage-Bewertung | Security | `cat /mnt/user/appdata/authelia/config/users_database.yml` (nur Strukturansicht, keine Hashes hier zitieren) |
| Komodo-Mongo-Dump letzter Integrity-Check | F-09-Vorbereitung | Backup | `mongorestore --dry-run --archive=komodo-mongo.archive.gz --gzip` |
| Aktuelle Cert-Restlaufzeit | F-08-Test-Vorbereitung | Operations | `openssl s_client -connect git.kaleschke.info:443 -servername git.kaleschke.info < /dev/null \| openssl x509 -noout -dates` |
---
# I. Pruefkommandos (Linux / Unraid / Docker / Windows)
> Strukturiert nach Bereich. Sicher zum Ausfuehren am Host.
### Hardware
```bash
# CPU + Flags (AVX fuer Immich-ML)
cat /proc/cpuinfo | awk '/model name|flags/ {print; if(/flags/) exit}'
# RAM
free -h
dmidecode -t memory | grep -E "Size|Speed" | head -20
# Mainboard
dmidecode -t baseboard | head -20
# PCI / SATA / NVMe
lspci
nvme list
lsblk -o NAME,SIZE,MODEL,SERIAL,FSTYPE,MOUNTPOINT,VENDOR
# SMART
smartctl -a /dev/nvme0n1 | head -40
smartctl -a /dev/sdb | head -40
# Stromverbrauch (Unraid Plugin oder ipmitool falls IPMI)
sensors | head -30
```
### Filesystem / Storage / Mounts
```bash
# Filesystem-Typen (Hard Rule 12.1)
findmnt -no FSTYPE /mnt/cache /mnt/disk1 /boot
mount | grep -E "ntfs3|fuseblk" # darf leer sein
# Share-Settings
ls -la /boot/config/shares/
# Cache-Fuellstand
df -h /mnt/cache /mnt/disk1 /mnt/user
du -sh /mnt/user/appdata/* | sort -hr | head -20
```
### Docker
```bash
# Container-Inventur
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" | sort
docker ps -a --filter "status=exited" --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"
# Netzwerke
docker network ls
docker network inspect frontend_net | jq '.[0].Containers | keys'
docker network inspect backend_net | jq '.[0].Internal'
# Volumes ohne Container (Waisen)
docker volume ls -qf dangling=true
# Effektive Ports
ss -ltnp | sort -k4
# Healthchecks
docker ps --format "{{.Names}}\t{{.Status}}" | grep -E "healthy|unhealthy|starting"
```
### Security
```bash
# Privileged-Container und Docker-Socket-Mounts
for c in $(docker ps -q); do
docker inspect "$c" --format '{{.Name}}: priv={{.HostConfig.Privileged}}; sock={{range .HostConfig.Binds}}{{println .}}{{end}}'
done | grep -E "priv=true|docker.sock"
# Direkte Host-Ports
docker ps --format "{{.Names}}: {{.Ports}}" | grep -v "^[^:]*: $"
# Secret-Datei-Rechte
ls -la /mnt/user/appdata/secrets/
stat -c "%a %n" /mnt/user/appdata/secrets/*.txt
```
### Backup / Restore
```bash
# Borg-Frische
ls -lat /mnt/user/backups/borg/dumps/latest/ | head
find /mnt/user/backups/borg/dumps/latest -mmin +1440 -type f # aelter 24h
# Borg-Repo (Passphrase per File)
export BORG_PASSPHRASE=$(cat /mnt/user/appdata/secrets/borg_repo_passphrase.txt)
borg list ssh://... --short | tail -5
borg info ssh://... ::Taegliche-Sicherung-2026-05-25T05:52:44.157
# Posture-Check
cat /mnt/user/services/posture-check/last.json | jq '.warning_count, .critical_count'
```
### Netzwerk / DNS
```bash
# Tailscale
tailscale status
# DNS auf AdGuard testen
dig @127.0.0.1 git.kaleschke.info
dig @127.0.0.1 example.com # Unbound-Recursion
# Cert-Restlaufzeit
for h in vault git immich cloud paperless mealie ntfy; do
echo -n "$h.kaleschke.info: "
openssl s_client -connect ${h}.kaleschke.info:443 -servername ${h}.kaleschke.info </dev/null 2>/dev/null \
| openssl x509 -noout -dates 2>/dev/null
done
```
### GitOps-Konsistenz
```bash
# Komodo Stack-Workspace vs. Repo
cd /mnt/user/services/stacks/<stackname>
git rev-parse HEAD
git status --short
# Webhook-Liveness
docker logs komodo-core 2>&1 | grep -i "webhook\|deploy" | tail -20
```
### Windows (lokal)
```powershell
# Repo-Status
git status --short
git fetch origin
git log origin/master..HEAD --oneline # ungepushte Commits
git log HEAD..origin/master --oneline # ungepullte Commits
# Policy-Check
.\ops\policy-checks\check_repo.ps1
# Restore-Freshness
.\ops\restore-tests\check-restore-freshness.ps1
```
---
# J. Community- / Best-Practice-Abgleich
> Nur fuer die Architekturentscheidungen, bei denen der Markt eindeutig ist oder Gegenpositionen relevant sind.
| Entscheidung | Markt-Best-Practice | Stuetzende Quellen | Bewertung |
|---|---|---|---|
| Traefik mit Docker-Labels statt File-Provider | Standard in Selfhosted (siehe `awesome-selfhosted-docker`, Smarthome-Beginner Templates) | Traefik-Doc v3 docs.traefik.io/providers/docker | passt zu MASTER 13 (Wechsel 2026-03-28) |
| DNS-Challenge mit Cloudflare statt HTTP-01 | Standard fuer Wildcard und reduzierte Angriffsflaeche | acme.sh / lego docs | passt, korrekt |
| Authelia ForwardAuth statt Authentik | Authelia ist leichtgewichtiger, Authentik maechtiger; beide valide | r/selfhosted-Konsens 2024-25 | Authelia richtig fuer Single-Family-Setup |
| Authelia ohne Redis-Session-Backend | Markt-Standard ist mit Redis; deine Vereinfachung ist begruendet (MASTER 13 2026-05-04) | Authelia-Doc | Trade-off klar; Bewertung: vertretbar fuer Homelab |
| Komodo statt Portainer/Dockge | Komodo ist neuer (2024), Dockge etabliert, Portainer kommerziell | Selfh.st 2025 Comparison | Komodo legitim, mehr GitOps-nativ als Dockge |
| Borg statt Restic/Kopia | Borg ist klassische Wahl fuer Linux-Backup mit Deduplikation; Kopia/Restic gewinnen mit Multi-Backend | r/datahoarder, ServeTheHome 2024 | Borg passt zu Unraid-Stack; bewusste Vereinfachung |
| Glance statt Homepage als Single-Dashboard | beide auf Augenhoehe; Homepage etablierter, Glance moderner, schneller, mit Live-Widgets | github.com/glanceapp/glance vs. github.com/gethomepage/homepage | Glance legitim; Bewertung: deine Wahl ist verteidigbar |
| Immich nicht hinter Authelia ForwardAuth | offizielle Immich-Doku raet bei nativer App-Auth davon ab, weil Sync-Clients OIDC oder direkte Auth brauchen | immich.app/docs/administration/reverse-proxy | korrekt; OIDC spaeter (F-13) ist der Weg |
| Nextcloud klassisch statt AIO | NC-AIO ist offizielle Empfehlung fuer Neuaufbau, klassisch hat mehr Flexibilitaet fuer GitOps | NC-Blog 2024 | bewusste Ausnahme MASTER 13; vertretbar, da GitOps-Anbindung wichtiger |
| Single-Host + Borg statt Proxmox-Cluster + ZFS-Send | fuer Familien-Homelab ist Cluster Overkill | r/homelab, LTT-Forum | Single-Host korrekt; Cold-Standby (Sprint 6) ist die richtige naechste Stufe |
| AdGuard + Unbound statt Pi-hole | aequivalent; AdGuard hat moderne UI, Unbound recursion | gowri/networkchuck Tutorials 2024 | passt |
| Posture-Check als Skript statt Goss/InSpec | fuer Single-Host pragmatisch | Goss ist maechtiger, aber Overkill | Skript-Loesung legitim |
| Renovate gegen Gitea | mehrere Erfahrungsberichte in Gitea-Issues + Renovate-Docs | docs.renovatebot.com/modules/platform/gitea/ | Standard-Pfad |
| CrowdSec vor Traefik | starker Trend 2024-25 in Selfhosted-Community | crowdsec.net/blog, Marius-Hosting-Tutorials | sinnvolle Haertung |
**Gegenpositionen, die du kennen solltest:**
- **"Authelia OIDC ist kompliziert, lieber Authentik."** Korrekt, wenn du auch B2B-SAML brauchst. Fuer reine Familien-OIDC ist Authelia leichter wartbar.
- **"CrowdSec laedt zentrale Reputation-Listen -> Privacy-Bedenken."** Stimmt, du kannst Local-Only-Mode fahren. Fuer Homelab egal.
- **"Renovate-Bot erzeugt Laerm."** Mit Group/Schedule-Rules zaehmbar. Wert > Laerm.
- **"Komodo ist zu jung."** Gegenargument: du benutzt es seit Q1/2026 produktiv, Major-Inzidenz war beherrschbar. Der Wechsel zurueck zu Portainer/Dockge waere hoehere Kosten als der Reifegrad-Nachteil.
---
# K. Endziel — "Nerd-Level Homelab"
So sieht dein Homelab aus, wenn es wirklich auf Senior-Level ist:
**Betrieb im Alltag**
- Morgens 30 Sekunden auf `monitoring.kaleschke.info`: Family-View zeigt 7 Tier-1-Services gruen, Backup-Job in der Nacht hat 100 % Files erfasst, alle Certs > 30 Tage, Disk < 80 %.
- Push-Benachrichtigung auf dem Handy nur, wenn wirklich etwas brennt (Posture-Check critical, Borg > 30 h, Endpoint down ≥ 8 Min, Cert < 14 Tage).
- Familienmitglieder loggen sich mit einem Login bei Nextcloud, Immich, Mealie ein. 2FA per TOTP-App, kein App-by-App-Passwortzettel mehr.
**Updates**
- Renovate oeffnet woechentlich 510 Pull-Requests in Gitea fuer Patch-Versionen. Du siehst sie im Web-UI, pruefst Release Notes, klickst Merge. Komodo deployt automatisch via Webhook. Smoke-Test in der naechsten Glance-Seite.
- Major-Updates kommen separat mit Label `major`, behandelst du in einem geplanten Slot mit Restore-Snapshot davor.
**Backups**
- Borg lokal alle 6 h, Borg Hetzner taeglich, Wechselplatte monatlich. Borg-Passphrase auf Papier im Bankschliessfach. Alle drei Ziele juenger als 36 h Alert-Schwelle.
- Pre-Dump-Hooks erzeugen 15 konsistente Dump-Artefakte pro Lauf, automatische Posture-Check-Vor-Hook bricht Backup ab, wenn FS oder Mount sich veraendert haben.
**Restore**
- Monatlicher Mini-Restore-Test fuer Vaultwarden/Gitea/Paperless/Immich nach `/mnt/user/backups/restore-lab/<dienst>/` laeuft automatisiert per Gitea Actions, Erfolgs-Report landet als Datei + ntfy-Info.
- Quartalsweise End-to-End-Drill: ein kompletter Stack restauriert, App startet, Smoke-Test passt, Doku validiert. Komodo-Bootstrap-Pfad ist getestet.
- Im Ernstfall folgst du `docs/DISASTER_RECOVERY.md` Phase 05 und bist nach < 8 h wieder im Vollbetrieb. Repo-Bootstrap aus GitHub-Mirror, Stacks in Stufen 15, Verifikation pro Stufe.
**Monitoring**
- Prometheus + Loki + Grafana + Alertmanager-ntfy-Bridge. ~15 Alert-Regeln, alle in `alerts.yml` versioniert.
- Family-View, Host-Overview, Containers+Logs, Traefik-Standalone, Backup-Frische, Cert-Tage. Sechs Dashboards mehr braucht keine Familie.
- Loki sammelt 30 Tage Logs aus allen Containern via Promtail. cAdvisor + node-exporter liefern Container- und Host-Metriken. Blackbox testet oeffentliche Endpoints alle 60 s.
**Security**
- Authelia OIDC fuer Nextcloud, Immich, Grafana, Mealie. ForwardAuth fuer Operator-UIs mit 2FA-Pflicht ab Tier-2.
- CrowdSec sperrt Brute-Force-IPs auf Traefik-Ebene bevor sie die Apps treffen.
- AdGuard-Admin nur via Tailscale. Operator-Pfad ausschliesslich Tailscale.
- Authelia-Repo-Baseline und Host-Config sind per Diff-Check im posture-check abgesichert.
- Secrets-Mounts mode 600, Borg-Passphrase analog.
**Dokumentation**
- SERVICE_CATALOG, RESTORE_MATRIX, DISASTER_RECOVERY, STORAGE_LAYOUT, SECRETS_MAP, WORKFLOW, GITOPS_DRIFT_RUNBOOK, ALERTING_MAP, HOMELAB_ARCHITECTURE_MASTER bleiben aktuelle Single-Source-of-Truth.
- `services/komodo-bootstrap/` loest das Henne-Ei. `SERVICES_RECOVERY.md` ist final, nicht Draft.
- Familien-Onboarding-Doku als Markdown: "So nutzt du Nextcloud-Web", "So aktivierst du Immich-Foto-Backup auf dem Handy", "So loggst du dich neu per 2FA ein".
**Taegliche Nutzung**
- Familie scannt Briefe per ASN-Barcode in `scans_inbox/`, Paperless tagged via paperless-gpt automatisch, alles durchsuchbar.
- Immich erfasst Smartphone-Fotos aller Familienmitglieder automatisch, Familie blaettert per Web/App, Tagging per ML.
- Nextcloud traegt Kalender, Kontakte, geteilte Familienordner per WebDAV/CardDAV — kein Google/Apple-Lock-In.
- Mealie speichert Rezepte, Einkaufsliste auf dem Handy.
- Vaultwarden ist der einzige Passwort-Tresor der Familie; Familien-Organisation aktiv.
- Plex streamt Heim-Medien an alle Endgeraete.
- ntfy schickt dir Vorfaelle aufs Handy — und sonst nichts.
- Optional: Firefly III fuer die Familien-Finanzen, Ecowitt-Wetter-Dashboard, Home-Assistant-Automationen fuer Strom-Eigenverbrauch.
**Was bewusst weggelassen ist**
- Kein Kubernetes. Komodo + Compose reicht.
- Kein zweiter Medienserver neben Plex (Jellyfin-Entscheidung 2026-05-25).
- Kein zweites Dashboard neben Glance (Homepage-Entscheidung 2026-05-25).
- Kein Uptime-Kuma neben Blackbox (Entscheidung 2026-05-25).
- Kein Hermes-Agent, wenn er bis 2026-07-25 keinen klaren Alltagsnutzen liefert.
- Kein BentoPDF/paperless-gpt 24/7, wenn nicht aktiv genutzt.
- Kein Self-Stack-Komodo (durch `services/komodo-bootstrap/` ersetzt).
---
## Schlussbemerkung
Das Setup ist naeher an Senior-Reife als an Bastel-Niveau. Der groesste Hebel der naechsten drei Monate ist **Konsolidieren statt erweitern** (Hermes-Entscheidung, Altstaende raus, Auth-SSO, Off-Site-Diversitaet), kombiniert mit der einen Aktivierung, die das Setup vom Operator-Tool zum **Familien-Tool** macht: Immich-Smartphone-Backup fuer alle.
Das vorhandene 2026-05-23-Audit hat die richtigen Sprintziele bereits identifiziert. Diese externe Audit-Sicht ergaenzt:
- **2FA-Pflicht auf Tier-1-Operator-UIs** (F-04) — fehlt in der Bewertung 2026-05-23 in dieser Klarheit.
- **Healthcheck-Luecke** (F-15) und **fehlende Mem-Limits** (F-19) — operative Detail-Findings, die in der strategischen Bewertung nicht auftauchen.
- **Komodo-Self-Bootstrap als konkreter Code-Vorschlag** (F-09) statt nur als Risiko-Erwaehnung.
- **Authelia-Drift-Detection automatisieren** (F-10) statt nur "manuell merge".
- **Monitoring-Stack ohne Digest-Pin** (F-07) — Inkonsistenz mit der eigenen Image-Pinning-Disziplin.
- **`infra/redis` ist faktisch nicht shared** (F-16) — Etikett-Realitaet-Drift.
- **Alert-Regeln deutlich zu duenn** (F-08) — Sichtbarkeitsluecken bei Cert/Borg/Container-Down.
Wenn Sprint 13 in 46 Wochen sitzen, bist du auf einer 1-Note. Wenn dann Sprint 45 in weiteren 68 Wochen kommen, hat die Familie ein echtes Self-Hosting-System, kein "Container-Sammlung im Keller". Das ist der Unterschied, den der Audit-Auftrag adressiert.
+95
View File
@@ -0,0 +1,95 @@
# Audit TODO 2026-05-25
Quelle: `docs/AUDIT_2026-05-25.md`
Status: Arbeitsliste fuer die Umsetzung. Authelia-2FA/OIDC bleibt bewusst spaet, weil die Ziel-Policy noch nicht final entschieden ist.
## Leitplanken
- Keine Authelia-2FA-ACL-Aenderungen in den ersten Sprints.
- Keine Live-riskanten Bind-/Port-Aenderungen ohne vorher erfasste Host-Werte, insbesondere Tailscale-IP.
- Erst Inventar und Baseline, dann Aenderungen.
- Jede produktive Aenderung bekommt Validierung und Rollback-Hinweis.
## Naechster Startpunkt 2026-05-26
Kontext bewusst gesichert, bevor weitere Live-Aenderungen passieren:
1. USV-Entscheidung treffen: aktuell ist keine funktionierende USV-Abschaltung nachgewiesen.
2. Gitea-Bundle-/Mirror-Mechanik und Borg-Passphrase-Offsite-Sicherung entscheiden.
3. Authelia 2FA/OIDC weiterhin nicht anfassen; das bleibt bewusst der letzte Block.
## Sprint 0 - Inventar und Baseline
| Status | Aufgabe | Ergebnis |
|---|---|---|
| erledigt | Hardware-Inventar ausfuellen | CPU, RAM, Mainboard, BIOS, NIC, Controller, Disks, SMART und Capacity-Baseline erfasst; USV ist als nicht validiert dokumentiert |
| in Arbeit | Netzwerk-Inventar ausfuellen | Host-IP, Gateway, Tailscale-IP und AdGuard-Bind erfasst; Router-/VLAN-Details offen |
| erledigt (Baseline) | Externe Abhaengigkeiten dokumentieren | `docs/EXTERNAL_DEPENDENCIES.md` enthaelt bekannte Provider, Kritikalitaet, Ausfallplaene; Account-Recovery-Codes/Zahlungswege bleiben Off-Repo-Operatorcheck |
| erledigt (Baseline) | Services-Recovery-Pfade beschreiben | `docs/SERVICES_RECOVERY.md` enthaelt Gitea-/Komodo-/Secrets-Sonderpfade; Gitea-Bundle-/Mirror-Mechanik bleibt als Umsetzungsentscheidung offen |
| erledigt | Baseline-Tag setzen | `audit-2026-05-25-baseline` ist lokal und remote vorhanden |
| erledigt | Policy-Check neu ausfuehren | SEC001-Warnings aus altem Report sind nicht mehr aktuell |
## Sprint 1 - Nicht-kontroverse Sicherheits- und Repo-Hygiene
| Status | Aufgabe | Ergebnis |
|---|---|---|
| offen | Borg-Passphrase analog sichern | Passphrase ist ohne Host/Vaultwarden wiederherstellbar |
| erledigt (repo) | AdGuard Admin-Bind vorbereiten | Tailscale-IP `100.80.98.33` erfasst, Compose-Soll geaendert |
| erledigt | AdGuard Admin-Port auf Tailscale-IP binden | Live validiert: `ss -ltnp` zeigt `100.80.98.33:8082`, DNS auf Port 53 funktioniert, LAN-Zugriff auf `192.168.178.58:8082` schlaegt fehl |
| erledigt | Alte Monitoring-Verzeichnisse entfernen | `ops/grafana-influxdb/` und `ops/loki/` sind aus dem aktiven Repo entfernt; Rollback erfolgt ueber Git-Historie |
| erledigt | Komodo/Gitea-Restdrift bereinigen | alter Komodo-Stack `grafana` ist inert und ohne Repo-Pfad/Webhook; Gitea-Hook `35` und `komodo`-Self-Hook `11` sind inaktiv; aktive Gitea-Hooks haben keine Fehlstatus |
| erledigt | Policy-Warnings triagieren | Plex Host-Netz und digest-gepinnte mutable Tags sind dokumentierte Info-Ausnahmen; `monitoring-influxdb3-core` als Root-Ausnahme bleibt bewusst als Warning sichtbar |
## Sprint 2 - Storage und Recovery verbindlich machen
| Status | Aufgabe | Ergebnis |
|---|---|---|
| offen | `docs/STORAGE_LAYOUT.draft.md` finalisieren | Datei wird als `docs/STORAGE_LAYOUT.md` Active gefuehrt |
| offen | Disk- und Share-TBDs eintragen | Modelle, Groessen, Seriennummern, Filesysteme und Cache-Settings sind dokumentiert |
| offen | Gitea-Repo-Mirror-Mechanik definieren | Mirror fuer `/mnt/user/services/gitea/git/repositories/` mit Frequenz <= 6 h ist spezifiziert |
| offen | Komodo-Bootstrap-Pfad beschreiben | Kaltstart ohne laufendes Komodo ist dokumentiert |
| offen | Immich-Restore-Test planen | Testumfang, Datenpfade und Smoke-Test-Kriterium stehen fest |
## Sprint 3 - Restore und Monitoring
| Status | Aufgabe | Ergebnis |
|---|---|---|
| offen | Immich-Restore-Test implementieren | Restore-Report landet unter `/mnt/user/backups/restore-reports/` |
| offen | Borg-Stale-Alert bauen | Alarm feuert, wenn Borg-Archiv zu alt ist |
| offen | TLS-Cert-Expiry-Alert bauen | Alarm feuert bei Restlaufzeit unter Schwellwert |
| offen | Container-Down-Alert bauen | Unerwartet fehlende Container werden sichtbar |
| offen | Family-View Dashboard definieren | Uptime, Backup-Frische, Cert-Tage, Disk-Fuellung auf einer Seite |
## Sprint 4 - Familien- und Betriebsdoku
| Status | Aufgabe | Ergebnis |
|---|---|---|
| offen | Familien-Onboarding schreiben | Nextcloud, Immich, Vaultwarden, 2FA-Verlust, Ausfallverhalten kurz erklaert |
| erledigt (Baseline) | Capacity-/Lifecycle-Review erstellen | Cache 6 %, Array/User-Shares 33 %, lokale Backups 2.2G; externe Backup-/Cold-Storage-Groessen bleiben offen |
| offen | USV-Test oder USV-Entscheidung | Power-Loss-Verhalten ist bekannt und dokumentiert |
## Sprint 5 - Auth und Frontdoor, bewusst zuletzt
| Status | Aufgabe | Ergebnis |
|---|---|---|
| geparkt | Authelia 2FA fuer Operator-UIs erweitern | Erst nach finaler Policy-Entscheidung |
| geparkt | Authelia OIDC fuer Apps pruefen | Erst nach Familien-/Client-Auswirkungsanalyse |
| geparkt | CrowdSec vor Traefik pruefen | Nach stabiler Auth-/Monitoring-Basis |
## Offene Host-Werte
Diese Werte muessen am Unraid-Host erhoben werden, bevor die entsprechenden Aenderungen sauber umgesetzt werden:
```bash
hostname
cat /proc/cpuinfo | awk '/model name|flags/ {print; if(/flags/) exit}'
free -h
dmidecode -t baseboard | head -30
ip -br link
tailscale ip -4
lsblk -o NAME,SIZE,MODEL,SERIAL,FSTYPE,MOUNTPOINT,VENDOR
df -h /mnt/cache /mnt/disk1 /mnt/user
smartctl -a /dev/nvme0n1 | head -80
smartctl -a /dev/sdb | head -80
```
+80
View File
@@ -0,0 +1,80 @@
# Audit Report - KalliLab CORE
Datum: 2026-05-16
Gepruefte Compose-Dateien: 30
## Kritische Befunde
Keine kritischen Befunde im Repo-Sollzustand gefunden.
- Keine Datenbank und kein Cache haengt in `frontend_net`.
- Keine produktive Compose-Datei ohne explizites `networks:`-Feld gefunden.
- Keine produktive `.env`- oder `stack.env`-Datei ohne `.example`-Suffix im Repository gefunden.
- Keine Klartext-Passwoerter, Tokens oder API-Keys in Compose-`environment:`-Bloecken gefunden.
## Mittlere Befunde
- Docker-Socket ausserhalb der im Audit-Prompt genannten Allowlist: `traefik` mountet `/var/run/docker.sock:/var/run/docker.sock:ro` in `traefik/docker-compose.yml:34`. Der Socket ist fuer den Docker-Provider fachlich nachvollziehbar, aber in `HOMELAB_ARCHITECTURE_MASTER_V2.md` Abschnitt 10 nicht explizit als Docker-Socket-Ausnahme aufgefuehrt.
- Docker-Socket ausserhalb der im Audit-Prompt genannten Allowlist: `alloy` mountet `/var/run/docker.sock:/var/run/docker.sock:ro` in `ops/loki/docker-compose.yml:26`. [AUSNAHME - dokumentiert] in `HOMELAB_ARCHITECTURE_MASTER_V2.md` Abschnitt 10 als `alloy` Docker-Socket read-only, aber nicht in der Prompt-Allowlist genannt.
- `komodo-periphery` haengt ohne Web-UI im `frontend_net` in `ops/komodo/docker-compose.yml:92-94`. Die Compose-Datei dokumentiert an `ops/komodo/docker-compose.yml:77-79` den Grund als Git-Zugriff auf internes Gitea und Docker-Agent-Sonderfall. [AUSNAHME - lokal in Compose dokumentiert]
- `hermes_net` ist ein app-internes Netz ohne `internal: true`: `ops/hermes-agent/docker-compose.yml:91-93`. Das ist nicht Teil der explizit geforderten `internal: true`-Liste, aber strukturell auffaellig, weil Gateway und Dashboard intern ueber dieses Netz sprechen.
- `grafana` nutzt zusaetzlich `backend_net` in `ops/grafana-influxdb/docker-compose.yml:27-30`, obwohl die Architektur das Grafana/Influx-Paar primaer als `frontend_net` + `grafana_influx_internal` beschreibt. Grund ist vermutlich Loki-Datasource-Zugriff; `docs/REPO_MAP.md` nennt `backend_net` fuer Grafana-Loki-Datasource. [AUSNAHME - dokumentiert]
- `paperless-gpt` verwendet `PAPERLESS_API_TOKEN` als Stack-ENV in `apps/paperless-gpt/docker-compose.yml:15`, taucht aber in `docs/SECRETS_MAP.md` nicht als eigener aktiver Secret-Eintrag auf.
## Hinweise
- Direkte Host-Ports sind nur bei dokumentierten Ausnahmen vorhanden:
- [AUSNAHME - dokumentiert] `traefik`: `80:80`, `443:443` in `traefik/docker-compose.yml:30-32`.
- [AUSNAHME - dokumentiert] `gitea`: `222:22` in `core/gitea/docker-compose.yml:17-18`.
- [AUSNAHME - dokumentiert] `adguard`: `53:53/tcp`, `53:53/udp`, `8082:80` in `host-services/Adguard/docker-compose.yml:13-16`.
- [AUSNAHME - dokumentiert] `influxdb3-core`: `${INFLUXDB_BIND_IP:-127.0.0.1}:8181:8181` in `ops/grafana-influxdb/docker-compose.yml:54-55`.
- `backend_net` wird in allen Compose-Dateien als `external: true` referenziert, z. B. `infra/postgresql17/docker-compose.yml:25-26`, `infra/redis/docker-compose.yml:22-23`, `traefik/docker-compose.yml:59-60`. Ob das externe Docker-Netz live wirklich `internal: true` ist, ist aus dem Repo allein nicht verifizierbar.
- `grafana_influx_lan` hat bewusst kein `internal: true`: `ops/grafana-influxdb/docker-compose.yml:88-89`. [AUSNAHME - dokumentiert]
- Mutable Tags sind mit Digests gepinnt, aber semantisch weiterhin mutable:
- `immich-server`: `release@sha256` in `apps/immich/docker-compose.yml:4`.
- `immich-machine-learning`: `release@sha256` in `apps/immich/docker-compose.yml:35`.
- `tailscale`: `stable@sha256` in `host-services/tailscale/docker-compose.yml:3`.
- `ddns-updater`: `latest@sha256` in `infra/ddns-updater/docker-compose.yml:3`.
- `komodo-core`: Major-Tag `2@sha256` in `ops/komodo/docker-compose.yml:36`.
- `komodo-periphery`: Major-Tag `2@sha256` in `ops/komodo/docker-compose.yml:82`.
- `uptime-kuma`: Major-Tag `1@sha256` in `ops/uptime-kuma/docker-compose.yml:3`.
- Reines `image: name:latest` ohne Digest wurde in produktiven Compose-Dateien nicht gefunden.
- Alle Services haben `restart: unless-stopped`.
- Alle Services haben `security_opt: no-new-privileges:true`.
- [AUSNAHME - dokumentiert] `scrutiny` nutzt `privileged: true` in `ops/scrutiny/docker-compose.yml:6`.
- [AUSNAHME - dokumentiert] `tailscale` nutzt `network_mode: host` in `host-services/tailscale/docker-compose.yml:6`.
## Dokumentations-Abweichungen
- Service-Namen in Compose vs. `docs/SERVICE_CATALOG.md` weichen bei Immich ab: Compose nutzt `immich-server`, `immich-machine-learning`, `database`, `redis` in `apps/immich/docker-compose.yml:2`, `:33`, `:44`, `:53`; der Katalog dokumentiert `immich_server`, `immich_machine_learning`, `immich_postgres`, `immich_redis`.
- Service-Name in Compose vs. `docs/SERVICE_CATALOG.md` weicht bei Paperless ab: Compose nutzt `paperless` in `apps/paperless/docker-compose.yml:2`; der Katalog dokumentiert `paperless-ngx`.
- Service-Name in Compose vs. `docs/SERVICE_CATALOG.md` weicht bei Redis ab: Compose nutzt `redis` in `infra/redis/docker-compose.yml:2`; der Katalog dokumentiert `Redis`.
- Traefik-Host bei Hermes ist im Compose variabel: `Host(`${HERMES_DASHBOARD_HOST}`)` in `ops/hermes-agent/docker-compose.yml:81`; `docs/REPO_MAP.md` dokumentiert konkret `hermes.kaleschke.info`. Das ist plausibel, aber maschinell nicht eindeutig abgleichbar.
- `docs/REPO_MAP.md` Volumes-Tabelle ist fuer mehrere Mounts nur zusammenfassend, nicht pfadgenau. Beispiele mit Compose-Pfaden, die dort nicht wortgleich auftauchen:
- `/mnt/user/appdata/unbound/config` in `apps/unbound/docker-compose.yml:7`.
- `/mnt/user/appdata/ddns-updater` in `infra/ddns-updater/docker-compose.yml:17`.
- `/mnt/user/appdata/filebrowser/database` und `/mnt/user/appdata/filebrowser/config` in `ops/filebrowser/docker-compose.yml:15-16`.
- `/mnt/user/appdata/borg-ui/restore` in `ops/borg-ui/docker-compose.yml:27`.
- Netzwerke in Compose sind alle in `docs/REPO_MAP.md` aufgefuehrt.
- Traefik-Hosts aus Compose sind in `docs/REPO_MAP.md` aufgefuehrt; einzige Besonderheit ist der variable Hermes-Host.
- `.example`-Dateien haben passende Gegenspieler in `.gitignore`: `.env`, `*.env`, `!*.env.example`, `**/stack.env`, `!**/stack.env.example`.
- Keine `.keep`-Platzhalter-Dateien gefunden.
## Offene Architektur-Punkte: aktueller Stand
- `immich_redis`: weiterhin kein named volume. Compose-Service `redis` in `apps/immich/docker-compose.yml:44-50` hat aktuell gar keinen `volumes:`-Block. Architekturpunkt bleibt offen.
- `AdGuard Home`: Admin-Port ist weiterhin direkt veroeffentlicht: `8082:80` in `host-services/Adguard/docker-compose.yml:15`; keine Traefik-Labels vorhanden. Block F bleibt offen.
- `filebrowser`: Appdata-Breitmount ist entfernt; aktuelle Mounts sind `/mnt/user/documents`, `/mnt/user/photos`, `/mnt/user/projekte` plus eigene DB/Config in `ops/filebrowser/docker-compose.yml:12-16`. Langfristiges Hardening bleibt moeglich, aber der groesste alte Breitmount ist erledigt.
- `bentopdf`: Compose ist vorhanden und Traefik-abgesichert in `apps/bentopdf/docker-compose.yml:2-27`. Runtime-Deploy und fachliche Abnahme koennen aus dem Repo allein nicht bestaetigt werden; Architekturpunkt bleibt als Live-Pruefung offen.
- `grafana` und `influxdb3-core`: laufen weiterhin als `user: "0"` in `ops/grafana-influxdb/docker-compose.yml:6` und `ops/grafana-influxdb/docker-compose.yml:53`. [AUSNAHME - dokumentiert]
- `plex-media-server`: keine Compose-Datei im Repo gefunden; bleibt Dockerman-/Host-Sonderfall laut Architektur.
## Bestanden
- 30 produktive `docker-compose.yml` wurden geprueft.
- Keine Datenbanken oder Caches im `frontend_net`: `postgresql17` nur `backend_net` in `infra/postgresql17/docker-compose.yml:18`; shared `redis` nur `backend_net` in `infra/redis/docker-compose.yml:15`; `mealie-postgres` nur `mealie_internal` in `apps/mealie/docker-compose.yml:56`; Immich `database` und `redis` nur `immich_default` in `apps/immich/docker-compose.yml:48` und `:64`; Nextcloud DB/Redis nur `nextcloud_internal` in `apps/nextcloud/docker-compose.yml:61` und `:73`; `komodo-mongo` nur `komodo_net` in `ops/komodo/docker-compose.yml:16`.
- App-interne Pflichtnetze sind korrekt `internal: true`: `immich_default` in `apps/immich/docker-compose.yml:73-75`, `mealie_internal` in `apps/mealie/docker-compose.yml:66-68`, `nextcloud_internal` in `apps/nextcloud/docker-compose.yml:82-84`, `grafana_influx_internal` in `ops/grafana-influxdb/docker-compose.yml:90-91`.
- `frontend_net` und `backend_net` werden in Compose-Dateien als `external: true` referenziert.
- Alle Services mit `traefik.enable=true` setzen explizit `traefik.docker.network=frontend_net`, `entrypoints=websecure`, `tls=true` und `tls.certresolver=le`.
- Kein Traefik-Label mit `yourdomain.tld` gefunden.
- Admin-/Ops-Router haben Middleware `authelia@file,secure-headers@file`, u. a. `homepage` in `apps/homepage/docker-compose.yml:31`, `filebrowser` in `ops/filebrowser/docker-compose.yml:27`, `scrutiny` in `ops/scrutiny/docker-compose.yml:32`, `grafana` in `ops/grafana-influxdb/docker-compose.yml:46`. [AUSNAHME - dokumentiert] `komodo-core` hat keine ForwardAuth-Middleware in `ops/komodo/docker-compose.yml:64-71`; [AUSNAHME - dokumentiert] `nextcloud` nutzt native Auth und nur Redirect-Middleware in `apps/nextcloud/docker-compose.yml:42-46`.
- Web-UIs ohne Traefik-Labels wurden nur als dokumentierte Sonderfaelle gefunden: `adguard` mit LAN-Admin-Port in `host-services/Adguard/docker-compose.yml:13-16`; `ddns-updater` hat keine Web-UI und braucht Internet in `infra/ddns-updater/docker-compose.yml:8`; `komodo-periphery` ist Agent ohne Traefik-Route in `ops/komodo/docker-compose.yml:81-103`.
+65
View File
@@ -0,0 +1,65 @@
# Capacity and Lifecycle - KalliLab CORE
Status: Initiale Capacity-Baseline 2026-05-26; externe Backup-/Cold-Storage-Groessen offen.
## Zweck
Dieses Dokument haelt Wachstum, Schwellenwerte und Upgrade-Trigger fest. Es verhindert, dass Storage-, RAM- oder Backup-Entscheidungen erst dann getroffen werden, wenn der Host bereits unter Druck steht.
## Aktuelle Kapazitaet
| Bereich | Groesse | Belegt | Frei | Schwellwert | Bewertung |
|---|---:|---:|---:|---:|---|
| Cache | 1.9T | 97G | 1.8T | 70 % Planung / 85 % Aktion | gruen, 6 % belegt |
| Disk1 / Array | 5.5T | 1.8T | 3.7T | 80 % Planung / 90 % Aktion | gruen, 33 % belegt |
| User Shares gesamt | 5.5T | 1.8T | 3.7T | 80 % Planung / 90 % Aktion | gruen, entspricht aktuell Disk1 |
| Backups lokal | 5.5T geteilter Array-Space | 2.2G unter `/mnt/user/backups` | 3.7T Share-frei | Review bei Borg-/Dump-Wachstum | lokal nicht unabhaengig vom Array |
| Hetzner Borg | TBD | TBD | TBD | TBD | TBD |
| Externe Cold-Platte | TBD | TBD | TBD | TBD | TBD |
Pruefkommando:
```bash
df -h /mnt/cache /mnt/disk1 /mnt/user
du -sh /mnt/user/appdata/* | sort -hr | head -30
du -sh /mnt/user/documents /mnt/user/photos /mnt/user/media /mnt/user/backups 2>/dev/null
```
## Wachstumsbereiche
| Bereich | Erwartetes Wachstum | Risiko | Naechste Aktion |
|---|---|---|---|
| Medien | aktuell ca. 1.7T | groesster Speicherblock | Array-Erweiterung vor 80 % planen |
| Immich Fotos/Videos | aktuell ca. 23G | hoechster privater Datentopf | Restore-Test priorisieren |
| Paperless/Dokumente | aktuell ca. 199M im Documents-Share | wichtig, moderates Wachstum | Restore-Test existiert, Share-Wachstum beobachten |
| Nextcloud | TBD | Familiennutzung kann stark wachsen | Quota/Backup pruefen |
| Monitoring/Loki | begrenzt durch Retention | Retention kann Disk fuellen | Retention und Volume-Groesse bei Reviews pruefen |
| Borg Dumps | aktuell ca. 2.2G lokale Backups | Retention und Excludes pruefen | Borg-Stale + Groessenprofil |
## Upgrade-Trigger
| Trigger | Massnahme |
|---|---|
| Cache dauerhaft >70 % | Zweite NVMe oder Appdata-Verteilung planen |
| Cache >85 % | Sofortmassnahme, keine grossen Deployments |
| Disk1 >80 % | Array-Erweiterung planen |
| Disk1 >90 % | Keine neuen grossen Datenimporte, Erweiterung priorisieren |
| RAM >90 % ueber 10 Minuten regelmaessig | RAM-Ausbau oder Service-Limits pruefen |
| Borg-Laufzeit deutlich steigend | Scope, Netzwerk und Ziel pruefen |
| SMART-Warnung | Ersatz planen, Restore-/Backup-Frische pruefen |
| Keine USV-Abschaltung | USV anschaffen/anschliessen oder Power-Loss-Risiko bewusst akzeptieren |
## Restore-Zeitziele
| Tier | Beispiel | Zielzeit | Status |
|---|---|---:|---|
| Tier 0 | Repo, Secrets, Traefik, DNS | TBD | offen |
| Tier 1 | Gitea, Vaultwarden, Paperless, Immich | TBD | offen |
| Tier 2 | Nextcloud, Mealie, Monitoring | TBD | offen |
| Tier 3 | Komfort-/Ops-Tools | TBD | offen |
## Review-Log
| Datum | Befund | Entscheidung |
|---|---|---|
| 2026-05-26 | Cache 6 %, Array/User-Shares 33 %, lokale Backups 2.2G; keine validierte USV-Abschaltung | Capacity gruen; naechste operative Risiken sind USV-Entscheidung und externe Backup-/Cold-Storage-Groessen |
+32
View File
@@ -0,0 +1,32 @@
# Codex-Prompt: KalliLab Endstufe
Du hast Vollzugriff auf `G:\Gitea_Clone\homelab-infra`, Gitea-Push, Komodo, und SSH auf Unraid `Kallilabcore`.
## Lies zuerst
1. `CLAUDE.md`
2. `docs/AUDIT_2026-05-23.md` — dort steht die komplette Restliste
## Auftrag
Den Audit von oben verifizieren und die offenen Punkte abarbeiten, bis das Homelab in der Endstufe ist. Reihenfolge:
1. **P0** — Lokalen Commit `cd650b1` nach Gitea pushen, danach Komodo-Reaktion fuer `gitea` und `borg-ui` pruefen.
2. **P0** — Live-Daten aus Audit-Abschnitt 9 messen und in `docs/AUDIT_2026-05-23_LIVE.md` ablegen (Secrets redacten).
3. **P1** — Monitoring-Stack (`monitoring/`) live deployen, alte `ops/grafana-influxdb` und `ops/loki` `down` (nicht loeschen).
4. **P1** — Jellyfin und Plex in `HOMELAB_ARCHITECTURE_MASTER_V2.md`, `docs/SERVICE_CATALOG.md`, `docs/REPO_MAP.md` nachtragen. Plex-Eintrag "nicht als Repo-Stack enthalten" korrigieren.
5. **P2** — Borg-Lauf-Frische pruefen, ggf. neuen Lauf ausloesen, alle 14 Dump-Artefakte juenger als 24 h.
6. **P3** — Repo-Hygiene: 8 leere Verzeichnisse weg, `.serena/` in `.gitignore`, Entscheidung zu `ops/windows-reinstall/*.ps1`.
## Regeln (aus CLAUDE.md, nicht verhandelbar)
- Secrets nie im Klartext ausgeben.
- Keine Aenderungen direkt in Komodo, nur ueber Git → Push → Komodo.
- Kein `push --force`, kein blindes Loeschen von `/mnt/user/{appdata,documents,photos,services,backups}`.
- Working-Tree-Status nur aus `git status --short` ableiten, nie aus `git diff` ueber Linux-Mount.
- Traefik dynamic config wird nicht von Komodo deployed — Aenderungen dort manuell auf `/mnt/user/appdata/traefik/dynamic/` syncen.
- Nicht anfassen: Hermes, Disk1 NTFS Phase 2, Komodo-Auth, Grafana/InfluxDB `user: "0"`, Image-Pinning ddns/glances/scrutiny.
- Wenn zwei Reparaturversuche scheitern: stoppen, Drift-Runbook Pflichtmatrix, Operator fragen.
## Arbeitsmodus pro Block
Lesen → minimal aendern → `ops/policy-checks/check_repo.ps1` lokal → Commit → Push → Komodo-Reaktion + Smoke-Test → eine Zeile in `docs/MIGRATION_LOG.md`.
## Fertig
Wenn alles abgearbeitet ist (oder ein Punkt bewusst offen bleibt): `docs/AUDIT_2026-05-23_FINAL.md` schreiben mit Ampel + konkretem Beleg pro Punkt, committen, pushen, kurz an mich melden.
+73 -30
View File
@@ -8,6 +8,9 @@ Verwandte Dokumente:
- `docs/ROLLBACK.md` - Rueckweg bei Fehlern im laufenden GitOps-Betrieb - `docs/ROLLBACK.md` - Rueckweg bei Fehlern im laufenden GitOps-Betrieb
- `docs/RESTORE_MATRIX.md` - Restore-Quellen und Verifikationsregeln pro Dienst - `docs/RESTORE_MATRIX.md` - Restore-Quellen und Verifikationsregeln pro Dienst
- `docs/RESTORE_HANDBOOK.md` - praktische Restore-Betriebsanleitung
- `docs/SERVICES_RECOVERY.md` - Recovery-kritische `/mnt/user/services`-Pfade, Gitea-Mirror und Komodo-Bootstrap
- `docs/EXTERNAL_DEPENDENCIES.md` - externe Provider/Konten und Ausfall-Szenarien
- `ops/borg-ui/BACKUP_SCOPE.md` - Zielbild des Borg-Scopes - `ops/borg-ui/BACKUP_SCOPE.md` - Zielbild des Borg-Scopes
--- ---
@@ -59,12 +62,14 @@ Diese Punkte sollten **vor** einem echten Ausfall geklaert sein:
| Thema | Sollzustand | | Thema | Sollzustand |
|---|---| |---|---|
| Repo-Zugang ausserhalb von Gitea | externer Mirror oder lokaler aktueller Clone vorhanden | | Repo-Zugang ausserhalb von Gitea | privater GitHub-Push-Mirror `michaelkaleschke-spec/homelab-infra` und lokaler aktueller Clone vorhanden |
| Unraid USB-/Flash-Backup | eingerichtet und wiederherstellbar | | Unraid USB-/Flash-Backup | `unraid-flash-config.tar.gz` wird vor Borg unter `/mnt/user/backups/borg/dumps/latest` erzeugt und nach Hetzner/Borg gesichert; Unraid-Connect-Cloud-Backup optional zusaetzlich |
| Borg-Ziel | nicht nur lokal auf demselben Ausfallpfad | | Borg-Ziel | nicht nur lokal auf demselben Ausfallpfad |
| Borg-Passphrase | extern sicher hinterlegt | | Borg-Passphrase | Host-Secret-Datei vorhanden und fuer Borg-Zugriff verifiziert; externe analoge Hinterlegung bleibt Operator-Aufgabe |
| Secrets-Dateien | ueber Borg bzw. Restore-Quellen abgedeckt | | Secrets-Dateien | ueber Borg bzw. Restore-Quellen abgedeckt |
| Komodo Stack ENV-Werte | extern dokumentiert, z. B. Vaultwarden | | Komodo Stack ENV-Werte | extern dokumentiert, z. B. Vaultwarden |
| Services-Recovery | `docs/SERVICES_RECOVERY.md` gepflegt, insbesondere Gitea-Repo-Mirror und Komodo-Bootstrap |
| Hardware-/Netzwerkdaten | `docs/HARDWARE_INVENTORY.md` und `docs/NETWORK_INVENTORY.md` mit echten Werten gefuellt |
| Restore-Smoke-Tests | fuer mindestens 1-2 kritische Dienste nachgewiesen | | Restore-Smoke-Tests | fuer mindestens 1-2 kritische Dienste nachgewiesen |
**Wichtig:** Dieses Dokument ist nur so gut wie die Vorbereitung ausserhalb des Repos. **Wichtig:** Dieses Dokument ist nur so gut wie die Vorbereitung ausserhalb des Repos.
@@ -80,13 +85,13 @@ Deshalb gilt:
1. Wenn moeglich, Repo ueber einen externen Mirror oder einen lokalen aktuellen Clone holen. 1. Wenn moeglich, Repo ueber einen externen Mirror oder einen lokalen aktuellen Clone holen.
2. Nur wenn `gitea` bereits wieder verfuegbar ist, direkt aus `git.kaleschke.info` klonen. 2. Nur wenn `gitea` bereits wieder verfuegbar ist, direkt aus `git.kaleschke.info` klonen.
Empfohlene Wege: Verfuegbare Wege:
- externer Push-Mirror - externer Push-Mirror: `https://github.com/michaelkaleschke-spec/homelab-infra`
- lokaler Bare-Clone auf dem PC - lokaler Bare-Clone auf dem PC
- normaler lokaler Arbeits-Clone auf dem PC - normaler lokaler Arbeits-Clone auf dem PC
Wenn **kein externer Repo-Zugang** besteht, ist `services/gitea/data` selbst ein kritischer Restore-Pfad. Wenn **weder GitHub-Mirror noch lokaler Repo-Clone** verfuegbar sind, ist `services/gitea/data` selbst ein kritischer Restore-Pfad.
--- ---
@@ -99,6 +104,8 @@ Wenn **kein externer Repo-Zugang** besteht, ist `services/gitea/data` selbst ein
3. Array wieder zuweisen und starten 3. Array wieder zuweisen und starten
4. Grundlegende Shares pruefen 4. Grundlegende Shares pruefen
Primaere lokale/off-site Restore-Quelle fuer die bestehende Flash-Konfiguration ist das Borg-Artefakt `unraid-flash-config.tar.gz` aus `/mnt/user/backups/borg/dumps/latest`. Dieses Archiv enthaelt `/boot/config` und muss wie Secret-Material behandelt werden.
### 5.2 Erwartete Shares / Pfade ### 5.2 Erwartete Shares / Pfade
Mindestens diese Pfade muessen wieder verfuegbar sein: Mindestens diese Pfade muessen wieder verfuegbar sein:
@@ -129,6 +136,7 @@ Erwartete Basis unter `/mnt/user/appdata/secrets/`:
- `authelia_jwt_secret.txt` - `authelia_jwt_secret.txt`
- `authelia_postgres_password.txt` - `authelia_postgres_password.txt`
- `authelia_session_secret.txt` - `authelia_session_secret.txt`
- `authelia_smtp_password.txt`
- `authelia_storage_encryption_key.txt` - `authelia_storage_encryption_key.txt`
- `immich_postgres_password.txt` - `immich_postgres_password.txt`
- `komodo_mongo_password.txt` - `komodo_mongo_password.txt`
@@ -138,7 +146,9 @@ Erwartete Basis unter `/mnt/user/appdata/secrets/`:
- `nextcloud_postgres_password.txt` - `nextcloud_postgres_password.txt`
- `postgres_password.txt` - `postgres_password.txt`
- `redis_password.txt` - `redis_password.txt`
- `borg_repo_passphrase.txt`
- `vaultwarden_admin_token.txt` - `vaultwarden_admin_token.txt`
- `hermes_runner_id_ed25519`
Weitere relevante Secret-Pfade: Weitere relevante Secret-Pfade:
@@ -154,11 +164,13 @@ Diese Werte sind vor dem Start der betroffenen Dienste zu pruefen bzw. wieder in
- `IMMICH_DB_PASSWORD` - `IMMICH_DB_PASSWORD`
- `MAILARCHIVER_DB_CONNECTION` - `MAILARCHIVER_DB_CONNECTION`
- `MAILARCHIVER_AUTH_PASSWORD` - `MAILARCHIVER_AUTH_PASSWORD`
- `HERMES_DASHBOARD_HOST`
- Hermes Host-`.env` fuer Provider-/API-/Home-Assistant-Tokens
- `KOMODO_SECRET_KEY` - `KOMODO_SECRET_KEY`
- `KOMODO_WEBHOOK_SECRET`
- `KOMODO_JWT_SECRET` - `KOMODO_JWT_SECRET`
- `KOMODO_MONGO_PASSWORD` - `KOMODO_MONGO_PASSWORD`
- `KOMODO_PERIPHERY_PASSKEY` - `KOMODO_PERIPHERY_PASSKEY`
- relevante `HOMEPAGE_VAR_*`
- `APP_KEY` und `ADMIN_PASSWORD` fuer `speedtest-tracker` - `APP_KEY` und `ADMIN_PASSWORD` fuer `speedtest-tracker`
### 6.3 Rechte ### 6.3 Rechte
@@ -193,11 +205,16 @@ Besonders kritisch:
- `/mnt/user/appdata/secrets` - `/mnt/user/appdata/secrets`
- `/mnt/user/appdata/traefik` - `/mnt/user/appdata/traefik`
- `/mnt/user/services/homelab-infra`
- `/mnt/user/services/stacks`
- `/mnt/user/services/posture-check`
- Details zu `/mnt/user/services/` und Komodo/Gitea-Bootstrap stehen in `docs/SERVICES_RECOVERY.md`
- `/mnt/user/services/gitea/data` - `/mnt/user/services/gitea/data`
- `/mnt/user/appdata/authelia/config` - `/mnt/user/appdata/authelia/config`
- `/mnt/user/appdata/komodo/core` - `/mnt/user/appdata/komodo/core`
- `/mnt/user/appdata/komodo/periphery` - `/mnt/user/appdata/komodo/periphery`
- `/mnt/user/backups/borg/dumps/latest` - `/mnt/user/backups/borg/dumps/latest`
- `/mnt/user/backups/borg/dumps/latest/unraid-flash-config.tar.gz`
- dienstspezifische App- und Nutzdatenpfade - dienstspezifische App- und Nutzdatenpfade
**Nicht blind alles extrahieren**, wenn nur einzelne Pfade oder Dienste betroffen sind. **Nicht blind alles extrahieren**, wenn nur einzelne Pfade oder Dienste betroffen sind.
@@ -223,14 +240,16 @@ Ziel:
### Stufe 2 - Gemeinsame Backends und Identity ### Stufe 2 - Gemeinsame Backends und Identity
4. `infra/postgresql17/` 4. `infra/postgresql17/`
5. `infra/redis/` 5. `security/authelia/`
6. `security/authelia/` 6. `infra/redis/`
7. `core/gitea/` 7. `core/gitea/`
Ziel: Ziel:
- gemeinsame DB / Redis verfuegbar - gemeinsame DB verfuegbar
- zentrale Auth laeuft - zentrale Auth laeuft; Authelia nutzt bewusst kein Redis-Session-Backend
- Authelia SMTP-Notifier kann GMX erreichen
- Redis als shared Cache fuer abhaengige Apps verfuegbar
- Git-Zugriff wiederhergestellt - Git-Zugriff wiederhergestellt
### Stufe 3 - Deploy-System ### Stufe 3 - Deploy-System
@@ -254,19 +273,18 @@ Ziel:
### Stufe 5 - Restliche Apps und Ops ### Stufe 5 - Restliche Apps und Ops
15. `apps/homepage/` 15. `apps/ntfy/`
16. `apps/ntfy/` 16. `apps/paperless-gpt/`
17. `apps/paperless-gpt/` 17. `apps/bentopdf/`
18. `apps/bentopdf/` 18. `ops/glance/`
19. `ops/uptime-kuma/` 19. `ops/borg-ui/`
20. `ops/borg-ui/` 20. `ops/filebrowser/`
21. `ops/backrest/` 21. `ops/glances/`
22. `ops/filebrowser/` 22. `ops/scrutiny/`
23. `ops/glances/` 23. `ops/speedtest/`
24. `ops/scrutiny/` 24. `monitoring/`
25. `ops/speedtest/` 25. `ops/hermes-agent/`
26. `ops/grafana-influxdb/` 26. `infra/ddns-updater/`
27. `infra/ddns-updater/`
**Regel:** Nach jeder Stufe kurz pruefen, bevor die naechste beginnt. **Regel:** Nach jeder Stufe kurz pruefen, bevor die naechste beginnt.
@@ -301,7 +319,8 @@ Ziel:
- Borg UI startet und kennt sein Repo noch - Borg UI startet und kennt sein Repo noch
- aktuelle Dump-Artefakte sind vorhanden - aktuelle Dump-Artefakte sind vorhanden
- Uptime Kuma / Homepage / ntfy sind wieder da - Glance / Monitoring / ntfy sind wieder da
- Hermes Gateway und Dashboard starten; `hermes.kaleschke.info` leitet anonym zu Authelia weiter
--- ---
@@ -326,6 +345,17 @@ Das bedeutet:
Nach einem Komodo-Neuaufbau muessen diese Werte vor dem Start des Stacks wieder gesetzt sein. Nach einem Komodo-Neuaufbau muessen diese Werte vor dem Start des Stacks wieder gesetzt sein.
### `authelia`
Authelia nutzt GMX SMTP fuer Identity-/2FA-Benachrichtigungen.
Vor dem Start muessen vorhanden sein:
- `/mnt/user/appdata/secrets/authelia_smtp_password.txt`
- SMTP-Zugang fuer `michideheld@gmx.de`
Beim Smoke-Test muss `authelia validate-config` erfolgreich sein; der SMTP-Startup-Check darf den Start nicht blockieren.
### `nextcloud` ### `nextcloud`
`nextcloud` ist bewusst kein AIO-Stack, sondern ein klassischer App-/PostgreSQL-/Redis-Stack. `nextcloud` ist bewusst kein AIO-Stack, sondern ein klassischer App-/PostgreSQL-/Redis-Stack.
@@ -346,20 +376,33 @@ Relevant:
- Dump-Ziel: `/mnt/user/backups/borg/dumps/latest` - Dump-Ziel: `/mnt/user/backups/borg/dumps/latest`
- Skript: `ops/borg-ui/scripts/pre-backup-dumps.sh` - Skript: `ops/borg-ui/scripts/pre-backup-dumps.sh`
- Unraid-Flash-Artefakt: `unraid-flash-config.tar.gz` plus `.sha256` und Manifest im selben Zielpfad
### Hermes Agent
Hermes nutzt einen lokalen Build und hostseitige Runtime-Daten.
Vor dem Start muessen vorhanden sein:
- `/mnt/user/appdata/hermes-agent/data`
- `/mnt/user/appdata/hermes-agent/ssh`
- `/mnt/user/appdata/secrets/hermes_runner_id_ed25519`
- die hostseitige Hermes `.env` mit Provider-/API-/Home-Assistant-Tokens
Smoke-Test: `hermes-gateway` healthcheck ist gruen, `hermes.kaleschke.info` leitet fuer anonyme Requests zu Authelia weiter.
### Gitea ### Gitea
Wenn weder externer Mirror noch lokaler Clone verfuegbar sind, ist `services/gitea/data` selbst Teil des kritischen Wiederanlaufs. `Micha/homelab-infra` wird als privater GitHub-Push-Mirror gespiegelt. Dieser Mirror ist der bevorzugte Repo-Bootstrap, falls Gitea selbst nach einem Ausfall noch nicht laeuft. Wenn weder GitHub-Mirror noch lokaler Clone verfuegbar sind, ist `services/gitea/data` selbst Teil des kritischen Wiederanlaufs.
--- ---
## 11. Offene Vorbereitungs-To-dos ## 11. Offene Vorbereitungs-To-dos
- externer Repo-Zugang fuer den Ernstfall absichern - Unraid-USB-/Flash-Backup regelmaessig ueber `unraid-flash-config.tar.gz` und optional Unraid Connect pruefen
- Unraid-USB-/Flash-Backup pruefen - Borg-Passphrase aus `/mnt/user/appdata/secrets/borg_repo_passphrase.txt` extern analog sicher hinterlegen
- Borg-Passphrase extern sicher hinterlegen
- Komodo Stack-ENV-Werte zentral ausserhalb von Komodo dokumentieren - Komodo Stack-ENV-Werte zentral ausserhalb von Komodo dokumentieren
- Restore-Smoke-Test fuer mindestens einen weiteren kritischen Dienst dokumentieren - regelmaessige automatisierte Restore-Smoke-Tests fuer Vaultwarden, Gitea und Paperless etablieren
- `komodo-mongo`-Dump nach Major-Upgrades gezielt kontrollieren - `komodo-mongo`-Dump nach Major-Upgrades gezielt kontrollieren
--- ---
+77
View File
@@ -0,0 +1,77 @@
# Disk1 Phase 2 - NTFS to XFS Migration
Stand: 2026-05-25 06:15 CEST. Ziel erreicht: `/mnt/disk1` wurde von `ntfs3` auf XFS migriert, ohne produktive Compose-Pfade zu aendern. Container nutzen weiter `/mnt/user/...`.
## Preflight
| Check | Ergebnis |
|---|---|
| Disk1 Mount | `/dev/md1p1` auf `/mnt/disk1`, `ntfs3`, 5.5T, 1.7T genutzt, 3.8T frei |
| Cache Mount | `/dev/nvme0n1p1` auf `/mnt/cache`, `xfs`, 1.9T, 100G genutzt |
| H: Backup-Ziel | `H:\`, Label `Externe HDD`, NTFS, 8T, 5.96T frei, healthy |
| Compose-Binds | Keine Treffer fuer direkte `/mnt/disk1`, `/mnt/cache`, `/mnt/disks`, `/mnt/remotes` Binds |
| SMB-Zugriff | `\\Kallilabcore\services`, `documents`, `photos`, `media` erreichbar |
## Zu sichernde Shares
| Share | Groesse laut Preflight |
|---|---:|
| `services` | 451M |
| `documents` | 196M |
| `photos` | 23G |
| `backups` | 2.2G |
| `media` | 1.7T |
| `finance` | 0 |
| `projekte` | 92K |
Zusaetzliche Disk1-Top-Level-Pfade: `scripts` 3.3M, `isos` 0, `System Volume Information` 0.
## Backup-Strategie
- Zielroot: `H:\kallilab-recovery\disk1-phase2-2026-05-23`.
- Kritischer Linux-/GitOps-Pfad `services` wird zusaetzlich als Tar-Archiv ueber SSH gesichert, damit Linux-Metadaten erhalten bleiben.
- User-Shares werden per SMB/Robocopy kopiert, mit Logs und anschliessender Zaehler-/Groessenverifikation.
- Keine produktiven Datenpfade werden geloescht.
## Gates
1. Backup komplett und verifiziert.
2. Dienste-Freeze vorbereitet und letzte Dumps frisch.
3. Direkt vor Format/Array-Prozedur: Operator-Bestaetigung im konkreten Moment.
## Backup-Ergebnis
Stand: 2026-05-24 13:07 CEST.
| Bereich | Sicherungsart | Ergebnis |
|---|---|---|
| `media` | Robocopy/SMB nach `H:\kallilab-recovery\disk1-phase2-2026-05-23\shares\media` | 2722 Dateien, 1677.11 GiB, Manifestvergleich: 0 missing, 0 size mismatch, 0 extra |
| `services` | Host-Tar auf Unraid Cache, danach binaer per `scp` nach H: | `services.tar`, 0.441 GiB, `tar -tf` gueltig |
| `documents` | Host-Tar auf Unraid Cache, danach binaer per `scp` nach H: | `documents.tar`, 0.192 GiB, `tar -tf` gueltig |
| `photos` | Host-Tar auf Unraid Cache, danach binaer per `scp` nach H: | `photos.tar`, 22.876 GiB, `tar -tf` gueltig |
| `backups` | Host-Tar auf Unraid Cache, danach binaer per `scp` nach H: | `backups.tar`, 2.099 GiB, `tar -tf` gueltig |
| `finance` | Host-Tar auf Unraid Cache, danach binaer per `scp` nach H: | `finance.tar`, leerer Share, `tar -tf` gueltig |
| `projekte` | Host-Tar auf Unraid Cache, danach binaer per `scp` nach H: | `projekte.tar`, klein, `tar -tf` gueltig |
| Disk1-Extras `scripts`, `isos` | Host-Tar auf Unraid Cache, danach binaer per `scp` nach H: | `disk1-extra.tar`, 0.003 GiB, `tar -tf` gueltig |
Hinweis: Erste Tar-Versuche per PowerShell-Redirect wurden verworfen, weil PowerShell den binaeren SSH-Stream als UTF-16 geschrieben hatte. Die ungueltige `media.tar` und unvollstaendige SMB-Teilkopien fuer `services`/`documents` wurden vom Backup-Ziel entfernt, damit nur verwertbare Sicherungen uebrig bleiben.
## Abschluss
Stand: 2026-05-25 06:15 CEST.
| Check | Ergebnis |
|---|---|
| Freeze-Dumps | `pre-backup-dumps.sh` vor Format ausgefuehrt; nach Wiederanlauf erneut erfolgreich, 15 kanonische Dump-Artefakte juenger als 24 h |
| Disk1 Filesystem | `/dev/md1p1` auf `/mnt/disk1`, `xfs`, 5.5T, 1.8T genutzt, 3.7T frei |
| Restore `media` | 2722 Dateien, 1,800,782,188,226 Bytes; finaler Manifestvergleich: 0 missing, 0 size mismatch, 0 extra |
| Restore Tar-Shares | `services`, `documents`, `photos`, `backups`, `finance`, `projekte` und Disk1-Extras aus H:-Freeze-Archiven nach `/mnt/disk1` extrahiert |
| Docker/Services | 49 Container laufend, 0 stopped, 0 unhealthy, 0 starting |
| Smoke-Tests | `git.kaleschke.info` 200, `komodo.kaleschke.info` 200, `borg.kaleschke.info` 302, `jellyfin.kaleschke.info` 302, `monitoring.kaleschke.info` 302 |
| Service-Mounts | Gitea SSH `:222` offen; Jellyfin und Plex sehen `/media`; Prometheus readiness ok |
| Backup-Lauf | Borg-UI Repository `appdata-critical`, letzter Job `completed`, Archiv `Taegliche-Sicherung-2026-05-25T05:52:44.157`, `nfiles=100221` |
| Temp-Cleanup | `/mnt/cache/disk1-phase2-tmp/*.tar` nach H:-Verifikation geloescht; Cache weiter XFS mit ca. 1.7T frei |
Hinweis zum Docker-Wiederanlauf: Nach dem manuellen Docker-Start liefen die Container, aber Healthcheck-Execs scheiterten wegen `dockerd` mit `XDG_RUNTIME_DIR=/run/user/0`. Ein gezielter Docker-Neustart mit unsetztem `XDG_RUNTIME_DIR` behob den Runtime-Fehler; danach wurden alle Healthchecks gruen. `monitoring-prometheus` war durch den geplanten Docker-Stop sauber beendet und wurde als bestehender Container wieder gestartet.
Offener Nachlauf: Die Array-Parity-Anzeige zeigte nach Abschluss weiter `mdNumDisabled=1`, `mdNumInvalid=1` und `mdResyncAction=check P`, waehrend beide beteiligten Devices `rdevStatus=DISK_OK` und `rdevNumErrors=0` melden. Parity-Zustand separat in Unraid pruefen und keinen Parity-/Disk-Slot ohne Operator-Entscheid aendern.
+68
View File
@@ -0,0 +1,68 @@
# External Dependencies - KalliLab CORE
Status: Initiale Betreiber-Baseline 2026-05-26; konkrete Account-Recovery-Codes und Besitznachweise muessen ausserhalb des Repos bestaetigt werden.
## Zweck
Dieses Dokument beschreibt externe Anbieter und Konten, von denen Betrieb, Recovery oder Zugriff abhaengen. Ziel ist, im Ausfallfall nicht erst suchen zu muessen, welcher Provider welches Teilproblem verursacht.
## Abhaengigkeiten
| Anbieter / System | Zweck | Kritikalitaet | Recovery-Auswirkung | Zugang / Besitz | Notfallplan |
|---|---|---:|---|---|---|
| Domain-Registrar | Besitz `kaleschke.info` | hoch | Ohne Domain brechen Public URLs/TLS-Erneuerung | Operator-Konto ausserhalb Repo, konkreten Registrar im Account pruefen | Registrar-Zugang, 2FA-Recovery und Zahlungsweg analog/off-system sichern |
| Cloudflare DNS | Authoritative DNS, ACME DNS-Challenge, DDNS | hoch | Neue Zertifikate/DNS-Aenderungen blockiert | Cloudflare-Konto; API-Token liegt als Host-Secret | API-Token rotierbar halten, Account-Recovery und Zone-Besitz pruefen |
| Hetzner Storage Box | Off-site Borg Backup | kritisch | Restore aus Off-site ggf. nicht moeglich | Hetzner-Konto / Storage-Box-Zugang ausserhalb Repo | Zweites Off-site-Ziel oder Cold-Platte etablieren; Borg-Passphrase extern sichern |
| GitHub Mirror | Externer Repo-Mirror `michaelkaleschke-spec/homelab-infra` | mittel/hoch | Gitea-Verlust abfederbar, Repo-Bootstrap bleibt moeglich | GitHub-Konto; PAT liegt in Gitea-Mirror-Settings, nicht im Repo | Mirror-Status regelmaessig pruefen; lokalen Clone als zweite Kopie behalten |
| Tailscale | Remote-/Operator-Zugang | hoch | Remote-Zugriff erschwert, lokale Bedienung bleibt | Tailnet-Konto; Node `Kallilabcore`, IPv4 `100.80.98.33` | Break-glass per LAN und physischem Zugriff; Tailnet-Recovery-Codes sichern |
| GMX SMTP | Authelia Notifier | mittel | Mail-Notifier faellt aus, Login selbst nicht zwingend | GMX-Konto; SMTP-Secret liegt hostseitig | ntfy/zweiter SMTP als Fallback pruefen |
| Let's Encrypt | TLS-Zertifikate | hoch | Cert-Erneuerung faellt aus | automatisch via Traefik und Cloudflare DNS-Challenge | Cert-Expiry Alert einrichten; Cloudflare-Token und Traefik-Storage pruefen |
| Container Registries | Image Pulls von Docker Hub, GHCR, LSCR, Gitea Registry u. a. | mittel | Redeploy/Update blockiert | ueberwiegend oeffentlich; keine produktiven Registry-Tokens im Repo | Gepinnte Digests und lokale Runtime helfen kurzfristig; Updates geplant und einzeln deployen |
| Plex Konto/Remote Access | Plex native Auth, ggf. Remote Access und Claim | mittel | Plex-Clients/Remote-Funktionen koennen ausfallen | Plex-Konto ausserhalb Repo; `PLEX_CLAIM` nur fuer Setup | LAN-Medienpfade bleiben lokal; Konto-Recovery separat sichern |
| Mobile Push | ntfy und ggf. mobile Plattform-Pushes | niedrig/mittel | Alerts erreichen Mobilgeraete ggf. nicht | App-/Device-seitig | Kritische Alerts zusaetzlich in Grafana/Glance sichtbar halten |
## Kritische Secrets ausserhalb des Repos
Authoritativ ist `docs/SECRETS_MAP.md`. Diese Liste markiert nur externe Abhaengigkeiten.
| Secret | Zweck | Recovery-Hinweis |
|---|---|---|
| Borg Passphrase | Entschluesselung Borg-Repos | Muss analog/off-system vorhanden sein |
| Cloudflare DNS API Token | ACME DNS-Challenge | Token-Rotation und Scope pruefen |
| GitHub Mirror Token | Push-Mirror | In Gitea/GitHub verwaltet, nicht im Repo |
| Tailscale Account Recovery | Tailnet-Zugang | Account-2FA/Recovery Codes sichern |
| SMTP Passwort | Authelia Mail | In Host-Secret, Fallback pruefen |
| Domain-Registrar Recovery | Domain-Besitz und Zahlung | Account, 2FA und Zahlungsweg ausserhalb des Homelabs sichern |
| Hetzner Storage Box Zugang | Off-site Backup-Ziel | SSH-/Web-Zugang und Zahlungsweg extern sichern |
## Ausfall-Szenarien
### Hetzner Storage Box nicht erreichbar
- Lokales Borg-Repo und aktuelle Dumps pruefen.
- Keine destruktiven Host-Aenderungen starten, solange Off-site unklar ist.
- Zweites Off-site-Ziel oder Cold-Platte als Folgeaufgabe umsetzen.
### Cloudflare Account/DNS gestoert
- Bestehende Zertifikate laufen bis Ablauf weiter.
- Keine Domain-/ACME-Aenderungen moeglich.
- Tailscale/LAN-Zugang als Break-glass nutzen.
### Tailscale gestoert
- Lokalen LAN-Zugang nutzen.
- Direkte Admin-Ports nur gemaess dokumentierten Ausnahmen verwenden.
- AdGuard-Admin-Bind muss so geplant werden, dass ein lokaler Break-glass-Weg bekannt ist.
- Seit 2026-05-26 ist AdGuard Admin nur ueber `100.80.98.33:8082` gebunden; bei Tailnet-Ausfall ist lokaler Host-/Compose-Zugriff der Break-glass-Weg.
### Domain verloren oder Registrar-Zugriff verloren
- Gitea/GitHub Mirror und lokale IP/Tailscale-Pfade fuer Recovery nutzen.
- Neue Domain waere separater Migrationsfall fuer Traefik, Authelia, App-URLs und Clients.
## Review
| Datum | Ergebnis | Naechste Aktion |
|---|---|---|
| 2026-05-26 | Bekannte externe Abhaengigkeiten aus Repo-/Betriebsdoku dokumentiert; keine Secret-Werte aufgenommen | Account-Besitz, 2FA-Recovery-Codes, Zahlungswege und Borg-Passphrase extern bestaetigen |
+38
View File
@@ -0,0 +1,38 @@
# Family Onboarding - KalliLab CORE
Status: Entwurf. Zielgruppe sind Familienmitglieder, nicht Operatoren.
## Zweck
Diese Datei soll spaeter kurz und alltagstauglich erklaeren, wie die wichtigsten Dienste genutzt werden und was bei Problemen zu tun ist. Keine Restore-Matrix, keine Docker-Begriffe.
## Dienste
| Dienst | URL | Zweck | Konto / Login | Notiz |
|---|---|---|---|---|
| Nextcloud | `https://cloud.kaleschke.info` | Dateien, Kalender, Kontakte | TBD | Mobile App/WebDAV/CardDAV |
| Immich | `https://immich.kaleschke.info` | Fotos und Smartphone-Backup | TBD | Backup-App pro Handy |
| Vaultwarden | `https://vault.kaleschke.info` | Passwoerter | TBD | Familien-Organisation pruefen |
| Mealie | `https://mealie.kaleschke.info` | Rezepte und Einkauf | TBD | TBD |
| Paperless | `https://paperless.kaleschke.info` | Dokumente | TBD | Scan-/Inbox-Prozess beschreiben |
| Plex | intern/App | Medien | TBD | TBD |
## Was tun bei Problemen?
| Situation | Verhalten |
|---|---|
| Webseite nicht erreichbar | 10 Minuten warten, dann Operator informieren |
| Passwort vergessen | Operator informieren, nicht selbst neue Konten anlegen |
| Handy-Foto-Backup stoppt | App oeffnen, WLAN/Batteriesparmodus pruefen, Operator informieren |
| 2FA verloren | Operator informieren; Recovery-Prozess wird separat festgelegt |
| Warnmeldung vom Browser | Nicht weiterklicken, Screenshot machen, Operator informieren |
## Offene Inhalte
| Status | Aufgabe |
|---|---|
| offen | Pro Dienst kurze Schritt-fuer-Schritt-Anleitung schreiben |
| offen | Konto-/2FA-Policy final entscheiden |
| offen | Immich Mobile Backup fuer alle Geraete testen |
| offen | Vaultwarden Familienorganisation pruefen |
+127
View File
@@ -0,0 +1,127 @@
# GitOps Drift Runbook
Dieses Runbook ist fuer Faelle, in denen Gitea, lokaler Clone, Komodo Workspace und Docker Runtime nicht sichtbar denselben Stand haben.
## Ziel
Vor jeder Reparatur muss klar sein, welche Ebene vom Sollzustand abweicht:
1. Lokaler Clone
2. Gitea `origin/master`
3. Komodo Stack Workspace auf dem Host
4. Laufender Docker-Container
5. Host-Netzwerklistener
Nicht mehrere Ebenen gleichzeitig reparieren. Erst messen, dann genau eine Abweichung beheben.
## Pflichtmatrix
### 1. Lokaler Clone
```bash
git status -sb
git rev-parse HEAD
git rev-parse origin/master
git ls-remote https://git.kaleschke.info/Micha/homelab-infra.git refs/heads/master
```
Alle Hashes muessen gleich sein, bevor Komodo oder Runtime bewertet werden.
### 2. Komodo Workspace
Auf dem Unraid-Host im Stack-Workspace:
```bash
cd /mnt/user/services/stacks/<stack-name>
git rev-parse --short HEAD
git status -sb
```
Bei Drift:
```bash
git fetch --all --prune
git reset --hard origin/master
```
Erst danach deployen.
### 3. Docker Runtime
```bash
docker inspect <container> --format '{{.Created}}'
docker inspect <container> --format '{{json .NetworkSettings.Networks}}'
docker inspect <container> --format '{{json .NetworkSettings.Ports}}'
docker inspect <container> --format '{{json .HostConfig.PortBindings}}'
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep <container-or-service>
```
Wichtig: `HostConfig.PortBindings` ist nur die Container-Spezifikation. Entscheidend fuer einen aktiven Host-Port sind `NetworkSettings.Ports`, `docker ps` und ein echter Listener.
### 4. Host-Port / Listener
```bash
ss -ltnp | grep <port>
curl -i --max-time 5 http://<host-ip>:<port>/
```
Ein `401 Unauthorized` ist bei geschuetzten APIs oft ein Erfolg: Dienst ist erreichbar, Auth fehlt nur beim Testrequest.
## Komodo/Periphery Checks
Wenn Komodo Stacks nicht aus Gitea deployen kann:
```bash
docker inspect komodo-periphery --format '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}'
docker inspect komodo-periphery --format '{{range $k,$v := .NetworkSettings.Networks}}{{println $k}}{{end}}'
docker exec komodo-periphery sh -lc 'getent hosts git.kaleschke.info'
docker exec komodo-periphery sh -lc 'wget -S -O- -T 5 --no-check-certificate https://git.kaleschke.info 2>&1 | head -40'
```
Sollzustand:
- `/mnt/user/services -> /mnt/user/services` ist gemountet.
- `komodo_net` und `frontend_net` sind verbunden.
- `git.kaleschke.info` loest auf `192.168.178.58` auf.
- HTTPS zu Gitea antwortet.
## InfluxDB LAN-Port Beispiel
Soll fuer Home Assistant:
```bash
cd /mnt/user/services/stacks/monitoring
git fetch --all --prune
git reset --hard origin/master
docker compose --env-file .env -p monitoring -f monitoring/docker-compose.yml up -d --force-recreate --no-deps influxdb3-core
```
Danach pruefen:
```bash
docker network ls | grep -E "monitoring|influx"
docker inspect monitoring-influxdb3-core --format '{{json .NetworkSettings.Networks}}'
docker inspect monitoring-influxdb3-core --format '{{json .NetworkSettings.Ports}}'
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep influx
ss -ltnp | grep 8181
curl -i --max-time 5 http://192.168.178.58:8181/
```
Erwartung:
- Komodo Workspace `HEAD` entspricht `origin/master`.
- `monitoring-influxdb3-core` haengt an `monitoring_monitoring_net` und `monitoring_monitoring_influx_lan`.
- Docker zeigt `192.168.178.58:8181->8181/tcp`.
- `ss` zeigt `docker-proxy` auf `192.168.178.58:8181`.
- `curl` bekommt `401 Unauthorized` von InfluxDB.
Hinweis: Im Compose-File heissen die Netze `monitoring_net` und `monitoring_influx_lan`. Durch den Compose-Projektnamen `monitoring` koennen daraus zur Laufzeit Docker-Netze mit Projektpraefix werden.
## Stop-Regel
Wenn zwei Reparaturversuche nicht zum erwarteten Ergebnis fuehren:
1. Keine weiteren Schreibbefehle.
2. Pflichtmatrix ausfuellen.
3. Genau eine abweichende Ebene benennen.
4. Erst danach einen neuen Fix ausfuehren.
+187
View File
@@ -0,0 +1,187 @@
# Hardware Inventory - KalliLab CORE
Status: Hardware-Baseline erfasst; USV/Power-Loss bleibt offene Betreiberentscheidung.
Host: `Kallilabcore`
Letzte Pruefung: 2026-05-26
Naechster Review: 2026-08-26
## Zweck
Dieses Dokument beschreibt die physische Basis des Homelabs. Es ist die Grundlage fuer Capacity Planning, Restore-Zeit, Ersatzteilplanung, USV-Verhalten und Entscheidungen wie Immich-ML, Plex-Transcoding oder Storage-Erweiterung.
## Host
| Feld | Wert |
|---|---|
| Hostname | Kallilabcore |
| Standort | Heim-LAN, physischer Standort TBD |
| Betriebssystem | Unraid |
| Unraid-Version | 7.2.4 |
| Rolle | Single-Host Homelab, Docker Compose via Komodo |
| Boot-Medium | Samsung Flash Drive, 59.8G, FAT32 |
| Flash-Backup | In Borg-Scope aufgenommen, siehe `docs/MIGRATION_LOG.md` |
## CPU
| Feld | Wert |
|---|---|
| Modell | 12th Gen Intel(R) Core(TM) i5-12400F |
| Kerne / Threads | 6 Kerne / 12 Threads |
| Architektur | x86_64 |
| Relevante Flags | AVX, AVX2, FMA, AES, VT-x vorhanden; kein AVX-512 |
| iGPU / Quick Sync | Nein, `F`-CPU ohne iGPU |
Pruefkommando:
```bash
cat /proc/cpuinfo | awk '/model name|flags/ {print; if(/flags/) exit}'
lscpu
```
## RAM
| Feld | Wert |
|---|---|
| Gesamt | 31 GiB |
| Belegt im Normalbetrieb | ca. 7.9 GiB genutzt, ca. 23 GiB verfuegbar |
| Slots / Ausbau | 4x 8 GB DDR4 belegt, gemischte Module |
| Module | Crucial CT8G4DFS8266.C8FE, Crucial CT8G4DFS8213.C8FDD1, 2x G.Skill F4-3600C17-8GVK |
| Konfigurierter Takt | 2133 MT/s |
| ECC | Nein |
Pruefkommando:
```bash
free -h
dmidecode -t memory | grep -E "Size|Speed|Locator|Type" | head -40
```
## Mainboard und Controller
| Feld | Wert |
|---|---|
| Mainboard | Gigabyte Technology Co., Ltd. B760M DS3H DDR4 |
| BIOS/Firmware | American Megatrends International F21, Release 2025-06-19 |
| SATA/HBA Controller | Intel Raptor Lake SATA AHCI Controller, onboard |
| NVMe Controller | Samsung SM981/PM981/PM983 NVMe Controller |
| NVMe Slots | mindestens 1 belegt |
Pruefkommando:
```bash
dmidecode -t baseboard | head -30
lspci
```
## Netzwerk-Hardware
| Interface | Speed | Rolle | Bemerkung |
|---|---:|---|---|
| eth0 / bond0 / br0 | 1 Gbit/s full duplex | LAN | Realtek RTL8125 2.5GbE Controller, Link aktuell 1G; Host-IP `192.168.178.58/24`, Gateway `192.168.178.1` |
| tailscale1 | virtuell | VPN | Tailscale IPv4 `100.80.98.33` |
Pruefkommando:
```bash
ip -br link
ethtool <interface>
tailscale ip -4
```
## Storage
| Slot | Device | Modell | Seriennummer | Groesse | Filesystem | Rolle | Health |
|---|---|---|---|---:|---|---|---|
| Cache | `nvme0n1p1` | Samsung SSD 970 EVO Plus 2TB | `S4J4NM0W609649H` | 1.8T | XFS | Appdata/system/domains | SMART passed |
| Disk1 | `md1p1` / physisch `sdc` | WDC WD60EFAX-68JH4N1 | `WD-WX32D90PC0V0` | 5.5T | XFS auf md1p1 | Array-Daten | SMART passed |
| Parity | physisch `sdb` | TOSHIBA HDWG480 | `2460A03VFA3H` | 7.3T | n/a | Parity | SMART passed |
| Boot | `sda1` | Samsung Flash Drive | `0375125090000587` | 59.8G | FAT32 | Unraid Boot | aktiv |
| Cold Backup | TBD | TBD | TBD | TBD | TBD | Externe Rotation | offen |
Pruefkommando:
```bash
lsblk -o NAME,SIZE,MODEL,SERIAL,FSTYPE,MOUNTPOINT,VENDOR
findmnt -no FSTYPE /mnt/cache /mnt/disk1 /boot
df -h /mnt/cache /mnt/disk1 /mnt/user
```
## SMART / Health
| Device | Letzter Check | Kritische Werte | Bewertung |
|---|---|---|---|
| /dev/nvme0n1 | 2026-05-26 | Critical Warning `0x00`, Percentage Used `0%`, Media Errors `0`, Power On Hours `370`, Written `5.87 TB` | gut |
| /dev/sdb | 2026-05-26 | Reallocated `0`, Pending `0`, Uncorrectable `0`, CRC `1`, Power On Hours `8971` | gut, CRC-Wert beobachten |
| /dev/sdc | 2026-05-26 | Reallocated `0`, Pending `0`, Uncorrectable `0`, CRC `0`, Power On Hours `14174` | gut |
Pruefkommando:
```bash
smartctl -a /dev/nvme0n1
smartctl -a /dev/sdb
smartctl -a /dev/sdc
```
## USV / Power Loss
| Feld | Wert |
|---|---|
| USV vorhanden | Nicht validiert / keine erkannte USV |
| Modell | Kein APC/Eaton/CyberPower-Geraet per `lsusb` erkannt |
| Verbindung | `apcupsd` ist auf USB vorkonfiguriert, aber kein passendes USB-USV-Geraet sichtbar |
| Software | `apcaccess` vorhanden; `apcupsd` laeuft nicht, `localhost:3551` liefert Connection refused |
| Konfigurierte Schwellen | `BATTERYLEVEL 5`, `MINUTES 3`, `TIMEOUT 0`, aber inaktiv solange `apcupsd` nicht laeuft |
| Laufzeit im Idle | Nicht messbar |
| Letzter Shutdown-Test | Nicht durchgefuehrt |
Bewertung:
- Aktueller Befund 2026-05-26: keine funktionierende USV-Absicherung nachgewiesen.
- `apcupsd` ist zwar auf dem System vorhanden, aber nicht aktiv.
- Power-Loss bleibt damit ein bewusst offenes Risiko fuer Docker-/DB-State und laufende Writes.
- Naechste Entscheidung: echte USV anschliessen und Shutdown testen oder Risiko bewusst akzeptieren und dokumentieren.
## Stromverbrauch
| Zustand | Verbrauch | Messmethode | Datum |
|---|---:|---|---|
| Idle | TBD | externes Messgeraet erforderlich | TBD |
| Normalbetrieb | TBD | externes Messgeraet erforderlich | TBD |
| Backup-Lauf | TBD | externes Messgeraet erforderlich | TBD |
| Last | TBD | externes Messgeraet erforderlich | TBD |
## Ersatzteil- und Lifecycle-Plan
| Komponente | Trigger | Massnahme |
|---|---|---|
| Cache-NVMe | >70 % Fuellstand oder SMART-Warnung | Zweite NVMe / Pool-Entscheidung; aktuell 6 % belegt |
| Disk1 | >80 % Fuellstand oder SMART-Warnung | Array-Erweiterung / Ersatz; aktuell 33 % belegt |
| Parity | Kleiner als neue groesste Datenplatte | Parity-Upgrade vor Datenplatten-Upgrade |
| Boot-USB | Lesefehler oder Alter TBD | Flash-Backup verifizieren, Ersatzstick vorbereiten |
| RAM | Swap/OOM oder Immich/Nextcloud-Druck | Ausbau planen |
| USV | keine funktionierende USV-Abschaltung | USV anschaffen/anschliessen oder Risiko schriftlich akzeptieren |
## Audit-Kommandos
```bash
hostname
uname -a
cat /etc/unraid-version 2>/dev/null || true
lscpu
free -h
dmidecode -t baseboard | head -30
dmidecode -t bios -t system -t baseboard
dmidecode -t memory | grep -E "Size|Speed|Locator|Type" | head -40
lspci | egrep -i 'sata|ahci|raid|nvme|ethernet|network'
ip -br link
ethtool eth0
tailscale ip -4
lsblk -o NAME,SIZE,MODEL,SERIAL,FSTYPE,MOUNTPOINT,VENDOR
df -Th /mnt/cache /mnt/disk1 /mnt/user /boot
smartctl -a /dev/nvme0n1 | head -100
smartctl -a /dev/sdb | head -100
smartctl -a /dev/sdc | head -100
apcaccess status
/etc/rc.d/rc.apcupsd status
lsusb
```
+138
View File
@@ -0,0 +1,138 @@
# Home Assistant -> InfluxDB 3 -> Grafana
Ziel: Home Assistant schreibt ausgewaehlte Ecowitt- und Energiesensoren nach InfluxDB 3 Core. Grafana bleibt das Langzeit-Dashboard, Home Assistant bleibt die Automationszentrale.
## Live-Stand 2026-05-04
- Home Assistant ist per SSH unter `192.168.178.50:22222` erreichbar.
- `ha core check` ist erfolgreich.
- InfluxDB 3 Core ist von Home Assistant aus unter `http://192.168.178.58:8181/` erreichbar; `401 Unauthorized` ohne Token ist der erwartete Reachability-Test.
- In `/homeassistant/configuration.yaml` ist noch kein `influxdb:`-Block aktiv.
- In `/homeassistant/secrets.yaml` ist noch kein `influxdb3_homeassistant_token` eingetragen.
- In der Home-Assistant-Entity-Registry ist noch keine Ecowitt-Integration sichtbar; aktuell existiert nur `weather.forecast_home`.
## 1. InfluxDB fuer Home Assistant erreichbar machen
Der Stack haelt InfluxDB bewusst ohne Traefik-Route. Fuer Home Assistant wird nur der HTTP-Port `8181` auf einer internen LAN-Adresse veroeffentlicht.
Im Zielzustand in Komodo/Stack-Environment fuer `monitoring` setzen:
```env
INFLUXDB_BIND_IP=192.168.178.58
```
`192.168.178.58` ist die LAN-IP des Docker-Hosts, auf dem `monitoring-influxdb3-core` laeuft. Nicht `0.0.0.0` verwenden, wenn es nicht notwendig ist.
Danach den Stack neu deployen und von Home Assistant aus pruefen:
```bash
curl -i --max-time 5 http://192.168.178.58:8181/
```
Erwartetes Ergebnis ohne Token: `401 Unauthorized`. Das bestaetigt, dass der LAN-Port erreichbar ist und Authentifizierung aktiv bleibt.
## 2. Token fuer Home Assistant
InfluxDB 3 Core unterstuetzt aktuell Admin- und Named-Admin-Tokens. Einen eigenen Named-Admin-Token fuer Home Assistant verwenden, damit der Token getrennt vom initialen Operator-/Admin-Token rotiert werden kann.
Den Token in Home Assistant eintragen:
```yaml
# /homeassistant/secrets.yaml
influxdb3_homeassistant_token: "apiv3_REPLACE_WITH_HOME_ASSISTANT_TOKEN"
```
Token niemals ins Git-Repository schreiben.
## 3. Ecowitt in Home Assistant anbinden
Die offizielle Ecowitt-Integration ist eine lokale Push-Integration. Home Assistant erzeugt einen HTTP-Webhook; die Wetterstation sendet danach ihre Daten aktiv an Home Assistant.
1. In Home Assistant `Einstellungen -> Geraete & Dienste -> Integration hinzufuegen -> Ecowitt` oeffnen.
2. Die angezeigten Werte fuer Server/IP, Pfad und Port notieren.
3. Im Ecowitt-Gateway entweder per App oder Web-UI unter `Weather Services -> Customized` eintragen:
- Customized: `Enable`
- Protocol Type Same As: `Ecowitt`
- Server/IP, Path und Port exakt wie in Home Assistant angezeigt
4. Wichtig: Ecowitt kann kein HTTPS. Den lokalen HTTP-Endpunkt von Home Assistant verwenden, nicht eine reine HTTPS-/Traefik-URL.
5. Nach dem ersten Push pruefen, ob neue `sensor.*`- und ggf. `binary_sensor.*`-Entities in Home Assistant auftauchen.
## 4. Home Assistant fuer InfluxDB konfigurieren
Minimaler Startblock fuer `/homeassistant/configuration.yaml`:
```yaml
influxdb:
api_version: 2
ssl: false
host: 192.168.178.58
port: 8181
token: !secret influxdb3_homeassistant_token
organization: homelab
bucket: homelab
max_retries: 3
default_measurement: state
include:
entity_globs:
- sensor.*ecowitt*
- sensor.*gw*
- sensor.netzbezug_live
- sensor.ins_netz_live
- sensor.ins_haus_live
- sensor.pv_ueberschuss_live
- sensor.batterie_soc_live
- sensor.wallbox_leistung_live
- sensor.pv_produktion_heute
- sensor.wallbox_energie_heute
```
Die `sensor.*ecowitt*`- und `sensor.*gw*`-Regeln sind nur Startpunkte. Nach dem ersten Ecowitt-Push die echten Entity-IDs aus Home Assistant auslesen und die Liste enger machen.
Nach dem Edit:
```bash
ha core check
ha core restart
```
## 5. Grafana Smoke-Test
In `https://monitoring.kaleschke.info` mit der bestehenden Datenquelle `InfluxDB 3 Core` eine SQL-Abfrage testen:
```sql
SHOW TABLES
```
Danach eine konkrete Tabelle pruefen. Home Assistant schreibt Measurements je nach Konfiguration und Entity-Typ; typische Tabellen sind `state`, `degC`, `%`, `W`, `kWh` oder aehnliche Measurement-Namen.
Beispiel fuer die ersten Werte:
```sql
SELECT *
FROM state
ORDER BY time DESC
LIMIT 20
```
## 6. Dashboard-Kandidaten
Sinnvolle Panels fuer das erste Wetter-Dashboard:
- Aussentemperatur und Luftfeuchte
- Luftdruck
- Windgeschwindigkeit und Boeen
- Regenrate und Regen heute
- UV und Solarstrahlung
- Batterielevel der Ecowitt-Sensoren
- PV-Ueberschuss vs. Wetter
- Wallbox-Leistung vs. PV-Ueberschuss
## 7. Erste Automationen
Home Assistant bleibt fuer Reaktionen zustaendig:
- Regen startet -> Dachfenster/Fenster pruefen
- Windboee ueber Grenzwert -> Markise/Rolllaeden schuetzen
- Frostwarnung -> Garten/Wasser/Auto-Hinweis
- Genug Regen heute -> Bewaesserung ueberspringen
- Ecowitt-Batterie niedrig -> ntfy/HA-Benachrichtigung
+274 -5
View File
@@ -8,6 +8,7 @@ Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ab
- Komodo ist der einzige produktive Stack-Manager. - Komodo ist der einzige produktive Stack-Manager.
- Portainer CE ist entfernt. - Portainer CE ist entfernt.
- Firefly, Firefly-Fints und Semaphore sind entfernt. - Firefly, Firefly-Fints und Semaphore sind entfernt.
- `monitoring/` ist der einzige aktive Observability-Stack; alte Repo-Pfade `ops/grafana-influxdb` und `ops/loki` sind entfernt.
- Borg UI ist produktiv, Dump-Automatisierung laeuft host-seitig und ein Restore-Smoke-Test wurde erfolgreich durchgefuehrt. - Borg UI ist produktiv, Dump-Automatisierung laeuft host-seitig und ein Restore-Smoke-Test wurde erfolgreich durchgefuehrt.
- GitHub Desktop ist der bevorzugte lokale Workflow fuer `Fetch`, `Pull`, `Commit` und `Push`. - GitHub Desktop ist der bevorzugte lokale Workflow fuer `Fetch`, `Pull`, `Commit` und `Push`.
- Mutable Image-Tags sind auf die aktuell laufenden Digests eingefroren. - Mutable Image-Tags sind auf die aktuell laufenden Digests eingefroren.
@@ -16,6 +17,274 @@ Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ab
## Historische Meilensteine ## Historische Meilensteine
### 2026-05-26 - Audit-Baseline-Tag gesetzt
- Der Stand nach Hardware-/Capacity-Baseline, Policy-Triage und Recovery-Doku wurde als `audit-2026-05-25-baseline` markiert und nach Gitea gepusht.
### 2026-05-26 - Externe Abhaengigkeiten und Services-Recovery baseline dokumentiert
- `docs/EXTERNAL_DEPENDENCIES.md` von Template auf Betreiber-Baseline angehoben: Domain, Cloudflare, Hetzner, GitHub-Mirror, Tailscale, GMX, Let's Encrypt, Registries, Plex und mobile Push-Pfade sind mit Ausfallwirkung und Notfallplan dokumentiert.
- `docs/SERVICES_RECOVERY.md` finalisiert den Komodo-Bootstrap-Anker: `ops/komodo/docker-compose.yml` bleibt verbindlich; der `komodo`-Self-Stack hat keinen aktiven Gitea-Webhook und ist nicht der Recovery-Anker.
- Offene Off-Repo-Betreiberchecks bleiben Account-Besitz, 2FA-Recovery-Codes, Zahlungswege, Borg-Passphrase-Hinterlegung und Gitea-Bundle-/Mirror-Mechanik.
### 2026-05-26 - Policy-Warnings triagiert
- Plex `network_mode: host` wurde in den Policy-Ausnahmen als dokumentierte Discovery-Ausnahme erfasst.
- Mutable Tags bei `ddns-updater`, `glances` und `scrutiny` bleiben wegen vorhandener SHA256-Digests reproduzierbar gepinnt und werden im Policy-Report als Info-Ausnahmen sichtbar gehalten.
- `monitoring-influxdb3-core` bleibt als dokumentierte `user: "0"`-Ausnahme bewusst eine Warning, damit der Hardening-Punkt nicht aus dem Blick faellt.
### 2026-05-26 - Hardware-/Capacity-Baseline abgeschlossen
- Hardware-Inventar auf Host-Befund aktualisiert: BIOS AMI F21 vom 2025-06-19, Intel Raptor Lake SATA AHCI, Samsung NVMe Controller und Realtek RTL8125 2.5GbE mit aktuellem 1G-Link.
- RAM-Baseline dokumentiert: 4x 8 GB DDR4 ohne ECC, gemischte Module, aktuell 2133 MT/s konfiguriert.
- Capacity-Baseline dokumentiert: Cache 1.9T mit 97G genutzt (6 %), Disk1/User-Shares 5.5T mit 1.8T genutzt (33 %), lokale Backups 2.2G unter `/mnt/user/backups`.
- USV-Befund dokumentiert: `apcupsd` ist vorhanden und auf USB vorkonfiguriert, laeuft aber nicht; `apcaccess status` liefert Connection refused und `lsusb` zeigt keine erkannte USV. Power-Loss bleibt damit eine offene Betreiberentscheidung.
### 2026-05-26 - Komodo/Gitea-Restdrift bereinigt
- Der alte Komodo-Stack `grafana` wurde als historischer Altstand inert gemacht: keine Repo-Dateipfade, kein Webhook, keine alte Stack-ENV, keine `missing_files`/`remote_errors`. Rollback bleibt Git-Historie, nicht der alte Komodo-Stack.
- Der Gitea-Hook `35` fuer den alten `grafana`-Stack bleibt inaktiv. Der nicht sinnvolle `komodo`-Self-Hook `11` wurde deaktiviert, weil Komodo selbst nicht per Gitea-Webhook auf `master` deployed wird.
- Ein kurz sichtbarer Komodo-DB-Typfehler durch `updated_at` als Float wurde im selben Kontrollfenster auf nativen Mongo `Long` korrigiert; danach traten keine neuen `invalid type`-Fehler mehr auf.
- Nach der Bereinigung: aktive Gitea-Komodo-Hooks haben `0` Fehlstatus; `komodo-core`, `komodo-periphery`, `komodo-mongo`, `nextcloud` und die aktuellen `monitoring-*` Container laufen weiter.
### 2026-05-26 - Monitoring-Altstaende aus aktivem Repo entfernt
- Die abgeloesten Pfade `ops/grafana-influxdb` und `ops/loki` wurden per `git rm` aus dem aktiven Repo entfernt. `monitoring/` bleibt der einzige Observability-Zielstack.
- Live-Check vor dem Cleanup: nur `monitoring-grafana`, `monitoring-promtail`, `monitoring-influxdb3-core` und `monitoring-loki` laufen; alte Container `grafana`, `influxdb3-core`, `loki` und `alloy` sind nicht vorhanden.
- Rollback erfolgt bei Bedarf ueber Git-Historie, nicht ueber parallel gepflegte Compose-Verzeichnisse.
- Im selben GitOps-Kontrollfenster wurde Gitea-Webhook `35` fuer den alten `grafana`-Rollback-Stack als inaktiv bestaetigt. Der aktive Nextcloud-Hook `36` hatte einen Signaturfehler; sein Secret wurde ohne Ausgabe des Werts aus der Komodo-Stack-Konfiguration zurueck nach Gitea synchronisiert.
### 2026-05-26 - AdGuard Admin-Port auf Tailscale-Soll begrenzt
- Host-Audit per SSH gegen `Kallilabcore` durchgefuehrt: Tailscale IPv4 ist `100.80.98.33`, LAN-IP ist `192.168.178.58/24`, Gateway `192.168.178.1`.
- Repo-Soll fuer `host-services/Adguard/docker-compose.yml` geaendert: DNS `53/tcp+udp` bleibt unveraendert, die Admin-UI bindet nun auf `100.80.98.33:8082:80`.
- Architektur, Service-Katalog, Repo-Map, Netzwerk-Inventar und AI-Kontext wurden an das neue Modell angepasst: keine Traefik-/Authelia-2FA-Umstellung, aber keine LAN-weite Admin-Bindung mehr.
- Live-Deploy wurde nach Fast-Forward des AdGuard-Workspaces auf `5cb4017` mit `docker compose -p adguard ... up -d` ausgefuehrt. Validierung erfolgreich: `ss -ltnp` zeigt `100.80.98.33:8082`, DNS via `@127.0.0.1` und `@192.168.178.58` funktioniert, `http://100.80.98.33:8082/` liefert HTTP 302, `http://192.168.178.58:8082/` ist nicht mehr erreichbar.
- Nachpruefung des GitOps-Pfads: Gitea-Hook `1` zeigt auf Komodo-Stack `69c7b9e26b77cd827811b9d0` und lieferte HTTP 200. Komodo-Deploys fuer AdGuard scheiterten zunaechst im `Git pull` einmal an `.git/index.lock` und danach an `fatal: Cannot rebase onto multiple branches`; der Workspace wurde aufgeraeumt und steht sauber auf `origin/master`.
### 2026-05-26 - Audit-Umsetzung vorbereitet
- Aus `docs/AUDIT_2026-05-25.md` wurde `docs/AUDIT_2026-05-25_TODO.md` als operative Arbeitsliste abgeleitet. Authelia-2FA/OIDC bleibt bewusst geparkt und wird erst nach finaler Policy-Entscheidung umgesetzt.
- Neue Inventar- und Betriebsdokumente angelegt: `docs/HARDWARE_INVENTORY.md`, `docs/NETWORK_INVENTORY.md`, `docs/EXTERNAL_DEPENDENCIES.md`, `docs/CAPACITY_AND_LIFECYCLE.md` und `docs/FAMILY_ONBOARDING.md`.
- `docs/SERVICES_RECOVERY.md` beschreibt initial die recovery-kritischen `/mnt/user/services`-Pfade, Gitea-Repo-Mirror-Optionen, Komodo-Bootstrap und Secret-Recovery-Reihenfolge.
- Policy-Check lokal erneut ausgefuehrt: die alten SEC001-Warnings fuer `ddns-updater` und `scrutiny` sind nicht mehr aktuell; verbleibende Warnings betreffen Host-Netz-/User-/Image-Tag-Themen und Altstaende.
### 2026-05-25 - Unraid Flash-Backup in Borg-Scope aufgenommen
- `pre-backup-dumps.sh` erzeugt zusaetzlich zu den DB-Dumps ein sensibles `unraid-flash-config.tar.gz` aus `/boot/config` inklusive SHA256 und Manifest unter `/mnt/user/backups/borg/dumps/latest`.
- Da `/local/borg-dumps` bereits Teil des Borg-Scopes ist, wird das Flash-Konfigurationsartefakt mit dem bestehenden Hetzner/Borg-Backup historisiert. Downloadbare Plugin-Paketarchive unter `/boot/config/plugins/*/` werden aus dem Artefakt ausgeschlossen; Restore-relevante Konfiguration bleibt enthalten.
- Live-Erstlauf erfolgreich: `pre-borg.sh` lieferte `critical_count=0`, Freshness `Critical: 0`, `unraid-flash-config.tar.gz` ist 297 KiB gross, `0600 root:root`, SHA256-Pruefung `OK`, 356 Archiv-Eintraege, Manifest fuer Unraid `7.2.4`. Der Borg-UI-Job `Taegliche Sicherung` ist aktiv und umfasst `/local/borg-dumps`; der naechste planmaessige Hetzner-Lauf nimmt das neue Flash-Artefakt mit. Der Host-Repo-Clone unter `/mnt/user/services/homelab-infra` wurde wegen eines fehlgeschlagenen Fast-Forward-Checkouts frisch geklont; der vorherige Stand liegt archiviert unter `/mnt/user/services/_archive/homelab-infra-pre-refresh-20260525-194209`.
### 2026-05-25 - Monitoring-Zielstack finalisiert und Uptime Kuma entfernt
- `monitoring` und `glance` wurden auf Commit `b6bbca4` deployed; Komodo zeigt fuer beide `latest_hash` = `deployed_hash` = `b6bbca4` ohne `remote_errors`. Die zehn `monitoring-*` Container laufen, `monitoring.kaleschke.info` und `glance.kaleschke.info` leiten anonym zu Authelia, Prometheus ist ready und Loki `/ready` liefert `ready`.
- Alte Monitoring-Altcontainer `grafana`, `influxdb3-core`, `loki` und `alloy` sind in Docker nicht vorhanden; `ops/grafana-influxdb` und `ops/loki` bleiben nur als Rollback-/Migrationsreferenz im Repo. Der noch aktive Gitea-Hook `35` des alten `grafana`-Rollback-Stacks wurde deaktiviert, damit zukuenftige Pushes den Altstand nicht reaktivieren.
- Uptime Kuma wurde durch Blackbox/Prometheus/Grafana ersetzt: aktive Blackbox-Zielliste enthaelt 19 HTTPS-Ziele, `uptime.kaleschke.info` ist dort nicht mehr enthalten und liefert nach Stack-Removal 404. Der Komodo-Stack `uptime-kuma` wurde gestoppt/destroyed/geloescht, Gitea-Webhook `23` deaktiviert, Appdata nach `/mnt/user/appdata/_archive/uptime-kuma-removed-2026-05-25` und der alte Stack-Workspace nach `/mnt/user/services/stacks/_archive/uptime-kuma-removed-2026-05-25` verschoben.
- Authelia-Hostconfig wurde mit Backup `configuration.yml.pre-uptime-removal-20260525-164343.bak` um den toten `uptime.kaleschke.info`-Eintrag bereinigt, validiert und neu gestartet. Prometheus wurde wegen eines `Stale NFS file handle` auf der gebundenen Konfigurationsdatei per Komodo-Restart neu gemountet.
### 2026-05-25 - AdGuard Admin-Port bewusst LAN-direkt belassen
- Strategische Option `adguard.kaleschke.info` hinter Traefik/Authelia-2FA wurde bewertet, aber vom Operator bewusst verworfen, weil der Betriebsweg einfach bleiben soll. AdGuard bleibt als dokumentierte Ausnahme mit DNS `53/tcp+udp` und Admin `8082:80` LAN-direkt; keine Live-Aenderung an AdGuard, Authelia oder Traefik wurde vorgenommen.
### 2026-05-25 - Borg-Passphrase Host-Secret verifiziert
- Erwartete Host-Secret-Datei `/mnt/user/appdata/secrets/borg_repo_passphrase.txt` aus der bestehenden Borg-UI-Repo-Konfiguration erzeugt, mit `root:root` und Modus `600` gesichert und per `borg info` gegen das Hetzner-Borg-Repo verifiziert. Analoge Offline-Hinterlegung bleibt bewusste Operator-Aufgabe; Secret-Wert wurde nicht ausgegeben oder dokumentiert.
### 2026-05-25 - Dashboard auf Glance konsolidiert
- Glance bleibt das einzige Homelab-Dashboard; Homepage wurde aus dem Zielbild entfernt. Authelia-Default-Redirect, Monitoring-Blackbox-Ziele, Cert-Check-Domains und Glance-Konfiguration zeigen nicht mehr auf `home.kaleschke.info`; Homepage wurde via Komodo-API gestoppt/destroyed, der Komodo-Stack geloescht, der alte Gitea-Webhook deaktiviert und Appdata nach `/mnt/user/appdata/_archive/homepage-removed-2026-05-25` verschoben.
### 2026-05-25 - Jellyfin aus Zielbild entfernt
- Plex-Smoke-Test erfolgreich (`/identity` HTTP 200, Container healthy, `/data/movies` und `/photos` sichtbar). Jellyfin wurde repo-seitig entfernt, aus Authelia-Baseline und Zielbild-Doku ausgetragen, via Komodo-API gestoppt/destroyed, der Komodo-Stack geloescht und Appdata nach `/mnt/user/appdata/_archive/jellyfin-removed-2026-05-25` verschoben; Plex bleibt einziger Medienserver.
### 2026-05-25 - Externer Repo-Mirror eingerichtet
- Gitea erlaubt fuer Repo-Migrationen und Mirror-Targets gezielt `github.com` und nutzt explizite externe DNS-Resolver. `Micha/homelab-infra` spiegelt nun als privater GitHub-Push-Mirror nach `michaelkaleschke-spec/homelab-infra`; erster manueller Sync erfolgreich, Gitea `push_mirror.last_error` leer. Token-Werte bleiben ausschliesslich in Gitea/GitHub und werden nicht dokumentiert.
### 2026-05-25 - Audit-Final nachgemessen
- Audit-Restliste erneut live geprueft: runtime-relevanter Stack-Inhalt fuer `gitea`, `borg-ui` und `monitoring` seit `66ee10c` unveraendert; abschliessende Audit-Doku-Commits liegen in Gitea; Monitoring inklusive Loki `/ready` gruen; Borg-Job und 15 kanonische Dump-Artefakte frisch; `docs/AUDIT_2026-05-23_FINAL.md` auf den Live-Stand aktualisiert.
### 2026-05-25 - Disk1 Phase 2 abgeschlossen
- Disk1 wurde nach H:-Freeze-Backup und finalem Service-Freeze von NTFS/`ntfs3` auf XFS migriert.
- Restore verifiziert: `media` final 2722 Dateien und 1,800,782,188,226 Bytes mit 0 missing/extra/size mismatch; Tar-Shares und Disk1-Extras aus den H:-Freeze-Archiven wiederhergestellt.
- Docker/Services nach XDG-Runtime-Fix wieder stabil: 49 Container laufend, 0 stopped, 0 unhealthy, 0 starting; Gitea, Komodo, Borg, Jellyfin und Monitoring per Smoke-Test erreichbar.
- Borg-UI meldet den letzten Backup-Job `completed`; `pre-backup-dumps.sh` wurde nach Wiederanlauf erneut ausgefuehrt und 15 kanonische Dump-Artefakte sind juenger als 24 h.
- `posture-check` erwartet Disk1 nun standardmaessig als XFS (`ALLOW_DISK1_NTFS=0`).
### 2026-05-23 - Audit-Endstufe verifiziert
- Lokalen Hardening-Commit `cd650b1` nach Gitea gepusht; Komodo-Workspaces fuer `gitea`, `borg-ui` und `monitoring` stehen auf `cd650b1`.
- Live-Audit in `docs/AUDIT_2026-05-23_LIVE.md` dokumentiert: Gitea-Registration geschlossen, Borg-Dumps frisch, Monitoring-Stack aktiv, alte Grafana/Loki-Altcontainer nicht mehr vorhanden.
- Jellyfin und Plex in Architektur, Service-Katalog und Repo-Map nachgetragen; Plex ist jetzt als Repo-Compose-Stack mit dokumentierter Host-Netz-Ausnahme gefuehrt.
- Repo-Hygiene abgeschlossen: `.serena/` ignoriert, leere Verzeichnisse entfernt, Windows-Reinstall-Helfer unter `ops/windows-reinstall/` bewusst versioniert.
### 2026-05-20 - Gitea 5xx-Bursts untersucht und Signup geschlossen
- Live-Befund zu `HomelabTraefik5xx`: kurze externe `POST /`-Bursts auf `gitea@docker` von `103.153.183.69` und `103.153.183.73`, jeweils HTTP 500 in unter 10 ms; normale Gitea-Checks und Git-Reads liefen parallel mit HTTP 200.
- Keine Hinweise auf erfolgreichen Zugriff: Gitea-Container ohne Restart/OOM, nur User `micha`, keine neuen User der letzten 30 Tage, keine neuen Repos, SSH-Keys oder Access-Tokens im Untersuchungsfenster.
- Live-Prometheus lief noch mit der alten Regel `rate(...[5m]) > 0`; die bereits im Repo vorbereitete Regel `increase(...[5m]) >= 5` wurde auf den Live-Mount kopiert und per Prometheus-Reload aktiviert.
- Gitea-Registrierung und OpenID-Signup wurden geschlossen: `DISABLE_REGISTRATION=true`, `REGISTER_EMAIL_CONFIRM=true`, `ENABLE_OPENID_SIGNIN=false`, `ENABLE_OPENID_SIGNUP=false`; Signup-Seite zeigt danach "Registration is disabled", OpenID-Login liefert 403.
### 2026-05-18 - Komodo Webhooks vollstaendig abgeglichen
- Live-Befund auf `Kallilabcore`: Komodo hatte fuer mehrere aktuelle Stacks `webhook_enabled: true`, aber Gitea enthielt noch nicht fuer alle aktuellen Stack-IDs aktive Webhooks.
- In der Gitea-Datenbank wurden aktive Webhooks fuer `monitoring` (`6a08d5297707b0930ab95c72`), `glance` (`6a09d7347707b0930ab96eae`), `grafana` (`69f31ecdf65eb72b757c497d`) und `nextcloud` (`69e519085fd5e8bc51f121f0`) nach dem bestehenden Komodo-Hook-Muster angelegt.
- Stale aktive Gitea-Hooks auf nicht mehr vorhandene bzw. alte Komodo-Stack-IDs wurden deaktiviert.
- Abgleich danach: 30 aktive Gitea-Komodo-Hooks fuer 30 Komodo-Stacks mit aktiviertem Webhook; `hermes` bleibt in Komodo bewusst `webhook_enabled: false`.
- Netzwerkpfad aus dem `gitea`-Container zu `komodo-core:9120` wurde erfolgreich verifiziert; `last_status=0` fuer neue Hooks bleibt bis zum ersten Push erwartbar.
### 2026-05-19 - Posture-Check Host-Version verifiziert
- Ursache fuer wiederholte ntfy-Warnings war nicht mehr die Repo-Logik allein, sondern dass auf dem Unraid-Host noch die alte Skriptversion unter `/mnt/user/services/homelab-infra/services/posture-check/posture-check.sh` ausgefuehrt wurde.
- Host-Skript wurde mit Backup ersetzt und mit `SEND_NTFY=0` direkt auf dem Host verifiziert.
- Ergebnis des echten Host-Laufs: `status: ok`, `critical_count: 0`, `warning_count: 0`.
- Betriebsregel daraus: Bei Host-User-Scripts nach Repo-Aenderungen immer den tatsaechlich ausgefuehrten Host-Pfad und den Live-Output pruefen.
### 2026-05-19 - Borg-Scope fuer GitOps Host Automation erweitert
- Nach den Gitea-/Komodo-Webhook- und Posture-Check-Aenderungen wurde der Backup-Scope um Host-GitOps-Pfade erweitert.
- Borg UI mountet kuenftig `/mnt/user/services` read-only als `/local/services`.
- In `all-important-sources.txt` wurden `/local/services/homelab-infra`, `/local/services/stacks` und `/local/services/posture-check` aufgenommen.
- `pre-backup-dumps.sh` wurde auf dem Host ausgefuehrt; frische Dumps fuer `gitea.sqlite.dump` und `komodo-mongo.archive.gz` liegen unter `/mnt/user/backups/borg/dumps/latest`.
- Wirksam wird der neue `/local/services`-Mount nach Redeploy/Recreate des `borg-ui`-Stacks.
### 2026-05-19 - Traefik-5xx Alert entstoert
- `HomelabTraefik5xx` hatte auf einzelne 5xx-Antworten reagiert, weil die Regel `rate(...[5m]) > 0` nutzte.
- Live-Befund fuer `gitea@docker`: zwei kurze `POST /` mit HTTP 500 von einer externen IP, danach durchgehend erfolgreiche Gitea-Checks; kein Container-Restart.
- Prometheus-Regel auf `increase(...[5m]) >= 5` geaendert, damit einzelne externe Fehlrequests keinen ntfy-Alarm ausloesen.
### 2026-05-17 - Glance Homelab-Dashboard vorbereitet
- `ops/glance` als geschuetztes Homelab-Dashboard unter `glance.kaleschke.info` vorbereitet.
- Glance zeigt HTTP-Monitore fuer Core, Apps und Ops, Docker-Containergruppen, Host-Snapshot und Bookmarks.
- Docker-Status laeuft nicht ueber einen direkten Socket-Mount in Glance, sondern ueber `glance-docker-socket-proxy` auf einem internen `glance_socket_net`.
- Die HTTP-Monitore nutzen oeffentliche URLs als Klickziel und interne `check-url`-Endpunkte auf `frontend_net`, damit Glance nicht vom externen Hairpin-/Auth-Pfad abhaengt.
- Das Immich Community-Widget wurde ergaenzt. Der API-Zugriff nutzt eine interne Service-URL und ein Stack-ENV-Token. Paperless, Scrutiny und Speedtest bleiben Kandidaten fuer einen spaeteren Widget-Pass, sobald die konkrete API-Ausgabe im Glance-Kontext sauber verifiziert ist.
- Das Dashboard-Layout wurde an `ginesjunior11/glance-dashboard-config` angelehnt: dunkleres blaues Theme, Zeitfortschrittsgruppe, farbige Dashboard-Icons, dichter `Homelab Status`, Server-Stats im Hauptbereich und eine zweite Seite `Infrastructure and Media`. Die rechte Home-Spalte zeigt WAN-Infos aus Speedtest Tracker, Speedtest-Livewerte, AdGuard-DNS-Stats, DNS/Ingress-Monitore und eine separate Netzwerk-Containergruppe.
### 2026-05-17 - Monitoring-Zielstack konsolidiert
- `monitoring/` als zentraler Observability-Zielstack fuer Prometheus, Loki, Promtail, Grafana, node-exporter, cAdvisor und InfluxDB 3 Core vorbereitet.
- `monitoring-grafana` nutzt den Repo-Standard `authelia@file,secure-headers@file` und Secrets per Datei statt Klartext-Stack-ENV.
- `monitoring-influxdb3-core` uebernimmt den LAN-only Writer-Endpunkt fuer Home Assistant (`8181` via `INFLUXDB_BIND_IP`).
- `ops/loki` und `ops/grafana-influxdb` sind abgeloeste Altstaende und bleiben nur als Rollback-/Migrationsreferenz im Repo.
### 2026-05-07 - Vaultwarden Restore-Test praktisch verifiziert
- Erster echter Vaultwarden-Mini-Restore gegen das produktive Borg-Repo `hetzner_borg_appdata_critical` erfolgreich durchgefuehrt.
- Restore lief isoliert nach `/mnt/user/backups/restore-lab/vaultwarden`, nicht gegen produktive Pfade.
- Testinstanz `restoretest-vaultwarden` wurde lokal auf `127.0.0.1:18080` gestartet; HTTP 200 und Login-Seite wurden erfolgreich bestaetigt.
- Report wurde unter `/mnt/user/backups/restore-reports/vaultwarden-2026-05-07.md` geschrieben.
- Fuer den praktischen Restore-Pfad wurden zwei hostseitige Voraussetzungen sichtbar und umgesetzt:
- `known_hosts` fuer das Hetzner-Ziel im `borg-ui`-Container
- Host-Secret-Datei `/mnt/user/appdata/secrets/borg_repo_passphrase.txt` fuer kuenftige Restore-Tests
- Testdaten unter `/mnt/user/backups/restore-lab/vaultwarden/data` wurden nach erfolgreichem Lauf wieder bereinigt.
### 2026-05-07 - Gitea Restore-Test praktisch verifiziert
- Erster echter Gitea-Mini-Restore gegen das produktive Borg-Repo `hetzner_borg_appdata_critical` erfolgreich durchgefuehrt.
- Restore lief isoliert nach `/mnt/user/backups/restore-lab/gitea`, nicht gegen produktive Pfade.
- Testinstanz `restoretest-gitea` wurde lokal auf `127.0.0.1:13000` und `127.0.0.1:12222` gestartet.
- HTTP 200, HTML-Titel und lokaler SSH-Port wurden erfolgreich bestaetigt.
- Report wurde unter `/mnt/user/backups/restore-reports/gitea-2026-05-07.md` geschrieben.
- Testdaten unter `/mnt/user/backups/restore-lab/gitea/data` wurden nach erfolgreichem Lauf wieder bereinigt.
### 2026-05-07 - Paperless Restore-Test praktisch verifiziert
- Erster echter Paperless-Mini-Restore gegen das produktive Borg-Repo `hetzner_borg_appdata_critical` erfolgreich durchgefuehrt.
- Restore umfasste sowohl die Dateipfade als auch `postgresql17-paperless.dump` aus dem Borg-Archiv.
- Testinstanzen `restoretest-paperless`, `restoretest-paperless-postgres` und `restoretest-paperless-redis` liefen isoliert ohne Traefik.
- Login-Seite war lokal auf `127.0.0.1:18120` erreichbar.
- Der Dump-Import in Test-Postgres war erfolgreich; die Test-Datenbank enthielt `25` Dokumente.
- Report wurde unter `/mnt/user/backups/restore-reports/paperless-2026-05-07.md` geschrieben.
- Testdaten unter `/mnt/user/backups/restore-lab/paperless` wurden nach erfolgreichem Lauf wieder bereinigt.
### 2026-05-06 - Komodo Webhook Secret getrennt
- `KOMODO_WEBHOOK_SECRET` von `KOMODO_SECRET_KEY` getrennt und als eigene Stack-ENV-Variable dokumentiert.
- Gitea-Komodo-Webhooks mit bisherigem Core-Secret wurden auf den neuen `KOMODO_WEBHOOK_SECRET` umgestellt; bereits individuelle per-Stack-Webhook-Secrets wurden beibehalten.
- Host-`.env`, persistente Komodo-Compose und Gitea-Webhooks wurden als ein gemeinsamer Runtime-Schritt behandelt, damit Auto-Deploys nicht auseinanderlaufen.
- Ein stale Gitea-Webhook auf eine nicht mehr vorhandene Komodo-Stack-ID wurde deaktiviert, nicht geloescht.
### 2026-05-06 - Authelia GMX SMTP Notifier
- Authelia-Notifier von Filesystem-Log auf GMX SMTP (`submission://mail.gmx.net:587`) umgestellt.
- SMTP-Passwort bleibt ausserhalb des Repos unter `/mnt/user/appdata/secrets/authelia_smtp_password.txt`.
- Authelia-Compose erhaelt explizite DNS-Server, weil der SMTP-Startup-Check externe Namen wie `mail.gmx.net` aufloesen muss.
- Repo-Baseline und Host-Config muessen bei Auth-Aenderungen weiter bewusst gemerged und vor Restart validiert werden.
### 2026-05-06 - Hermes DR und Mail-Archiver Authelia
- Hermes Agent in `docs/RESTORE_MATRIX.md` und `docs/DISASTER_RECOVERY.md` mit Restore-Pfaden, Secret-/ENV-Hinweisen und Smoke-Test ergaenzt.
- Mail-Archiver Web-UI hinter `authelia@file,secure-headers@file` gelegt; App-eigene Auth bleibt als zweite Schutzschicht bestehen.
- M10/Komodo blieb unveraendert.
### 2026-05-05 - N-Aufraeum-Sprint
- Obsolete Compose-Top-Level-Felder `version: "3.9"` aus Immich, Mail Archiver und Paperless entfernt.
- Leere `env/domains.env.example` und `env/global.env.example` mit nicht geheimen Beispielwerten gefuellt.
- Veraltete `.keep`-Platzhalter aus Verzeichnissen mit echten Compose-/Repo-Inhalten sowie zwei reine Geister-Verzeichnisse (`host-services/plex`, `infra/dns`) entfernt.
### 2026-05-16 - Backup-Konsistenz und erster Hardening-Schnitt
- SQLite-Dumps fuer Gitea, Vaultwarden, Speedtest Tracker und Filebrowser werden containerseitig als `*.sqlite.dump` erzeugt und per Freshness-Check geprueft; Uptime Kuma wurde am 2026-05-25 aus dem aktiven Dump-Scope entfernt.
- `nextcloud.dump` und die Nextcloud-Userdaten sind als Option A im Borg-Scope dokumentiert.
- Filebrowser mountet keine breite `/mnt/user/appdata`-Flaeche mehr, sondern nur noch Documents, Photos, Projekte sowie eigenen App-State.
- Authelia Argon2id-Parameter in der Repo-Baseline auf `iterations: 3`, `memory: 65536`, `parallelism: 4` gesetzt; produktive Host-Config muss kontrolliert gemerged und mit Test-User validiert werden.
- Redis-Caches wurden auf `redis:7.4-alpine@sha256:...` vereinheitlicht; Nextcloud wurde mit Registry-validiertem Digest gepinnt.
- Eindeutig aufloesbare `latest@sha256`-Images wurden auf konkrete Tags umgestellt: Homepage `v1.12.3`, code-server `4.116.0`, Filebrowser `v2.63.2`, Speedtest Tracker `1.13.12`.
### 2026-05-05 - M3b versionierte App-Images digest-gepinnt
- Versionierte Nicht-Komodo-Images fuer BentoPDF, Mealie, Paperless, Paperless-GPT, AdGuard Home, Grafana, InfluxDB 3 Core und Traefik auf die am Host laufenden, manifest-validierten Digests gepinnt.
- `nextcloud:33.0.2-apache` wurde bewusst nicht in diesem Schritt gepinnt, weil der lokal gelistete Digest nicht als Registry-Manifest fuer `tag@sha256` validierbar war.
- Redis-Caches und Komodo/M10 blieben unveraendert.
### 2026-05-05 - M6/M7/M8 Doku-Konsolidierung
- `hermes.kaleschke.info` als produktive Hermes-Dashboard-Route hinter Traefik + Authelia in Architektur, Repo-Map und Service-Katalog ergaenzt.
- `grafana` und `influxdb3-core` laufen weiterhin als `user: "0"`; das wurde als Host-Appdata-Permissions-Ausnahme dokumentiert und nicht nebenbei geaendert.
- Tailscale-Ausnahme um `NET_ADMIN`, `NET_RAW` und `/dev/net/tun` ergaenzt.
- Komodo-Secret-/Webhook-Themen wurden bewusst nicht geaendert; Komodo-Aenderungen erfolgen nur gemeinsam mit dem Betreiber.
### 2026-05-05 - M3a stateful Digest-Pinning
- PostgreSQL 17 Datenhalter auf `postgres:17.9@sha256:5b96f1a16bd9768b060dd2ffe55cb6225c4d9ef4d214a8b21eb08134869a97e4` gepinnt (`postgresql17`, `mealie-postgres`, `nextcloud-postgres`).
- Immich pgvector-Postgres auf `tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:739cdd626151ff1f796dc95a6591b55a714f341c737e27f045019ceabf8e8c52` gepinnt.
- Komodo Mongo auf `mongo:7.0.32@sha256:32979a1189dfdc44da3f5ed40d910495f5ad8f6f7f77556646f890a30b2d3f56` sowie Komodo Core/Periphery und Gitea auf die am Host laufenden Digests gepinnt.
- Redis-Caches wurden am 2026-05-16 auf `redis:7.4-alpine@sha256:...` vereinheitlicht; Redeploys erfolgen stackweise mit Smoke-Test, nicht parallel.
### 2026-05-04 - Komodo Self-Stack Drift auf persistenten Pfad zurueckgefuehrt
- Drift-Befund: `komodo-core` und `komodo-periphery` liefen aus `/tmp/komodo-core-repair.yml` bzw. `/tmp/komodo-periphery-repair.yml`; `komodo-mongo` verwies auf `/mnt/user/services/stacks/komodo/compose.yaml`, obwohl dieser Pfad fehlte.
- Vor Eingriff wurden die Repair-Dateien und zugehoerigen `/tmp/*.env`-Dateien unter `/mnt/user/appdata/komodo/_drift_backup_2026-05-04/` gesichert.
- Zusaetzlich wurde eine geschuetzte Recovery-ENV unter `/mnt/user/appdata/secrets/_komodo_stack_env_recovery_2026-05-04.env` abgelegt; diese Datei enthaelt Tier-1-Secret-Material und ist kein Dauerzustand.
- Vor dem Reconcile wurde das host-seitige Dump-Skript ausgefuehrt; `komodo-mongo.archive.gz` wurde frisch unter `/mnt/user/backups/borg/dumps/latest/` erzeugt.
- Persistenter Self-Stack wurde unter `/mnt/user/services/stacks/komodo/compose.yaml` aus `ops/komodo/docker-compose.yml` wiederhergestellt; `.env` wurde hostseitig aus der bestehenden Runtime-ENV abgeleitet.
- Der vollstaendige Dry-run haette auch `komodo-mongo` recreated und wurde daher nicht ausgefuehrt. Stattdessen wurden nur `komodo-core` und `komodo-periphery` gezielt mit `--no-deps --force-recreate` aus dem persistenten Pfad neu erstellt; `komodo-mongo` blieb unveraendert healthy.
- Smoke-Tests: `docker compose ls` zeigt fuer `komodo` nur noch `/mnt/user/services/stacks/komodo/compose.yaml`, Mongo pingt `{ ok: 1 }`, `https://komodo.kaleschke.info` liefert HTTP 200, und Periphery meldet sich am Core an.
- Die `/tmp/*repair.yml`-Dateien bleiben vorerst als Altlast erhalten und duerfen erst nach stabiler Laufzeit bewusst entfernt oder ins Drift-Backup verschoben werden.
### 2026-05-04 - Authelia ACL-Drift hostseitig gemerged
- Die produktive Authelia-Config ist groesser als die Repo-Datei, weil sie hostseitige OIDC-/Secret-Konfiguration enthaelt. Die Repo-Datei wurde daher als nicht geheime Baseline eingeordnet und nicht blind auf den Host kopiert.
- Host-Backup vor Aenderung: `/mnt/user/appdata/authelia/config/configuration.yml.bak-20260504-acl-sync`.
- Minimaler Host-Merge: `homepage.kaleschke.info` wurde aus der bypass-Liste entfernt, `komodo.kaleschke.info` aus der 2FA-Liste entfernt, und `default_redirection_url` wurde auf `https://home.kaleschke.info` gesetzt.
- `authelia validate-config` war erfolgreich; Authelia wurde neu gestartet und war danach healthy.
- Smoke-Tests: `home.kaleschke.info` liefert fuer anonyme Requests eine Authelia-Weiterleitung, `komodo.kaleschke.info` bleibt ueber native Komodo-Auth erreichbar.
### 2026-05-04 - Home Assistant InfluxDB LAN-Port und Drift-Runbook
- `influxdb3-core` fuer Home-Assistant-Writer auf LAN-Port `8181` vorbereitet und deployed.
- InfluxDB bleibt ohne Traefik-/Public-Route und haengt nicht im `frontend_net`.
- Fuer aktives Docker Host-Port-Publishing wurde zusaetzlich zum internen `grafana_influx_internal` das Compose-Netz `grafana_influx_lan` ergaenzt.
- Komodo Periphery dauerhaft um `/mnt/user/services:/mnt/user/services` und `frontend_net` ergaenzt, damit Stack-Workspaces und Gitea-Zugriff reproduzierbar funktionieren.
- `docs/GITOPS_DRIFT_RUNBOOK.md` angelegt, um lokale Git-Kopie, Gitea, Komodo Workspace, Docker Runtime und Host-Listener getrennt zu pruefen.
### 2026-03-28 - GitOps-Konsolidierung ### 2026-03-28 - GitOps-Konsolidierung
- Komodo als primaeren Stack-Manager eingefuehrt. - Komodo als primaeren Stack-Manager eingefuehrt.
@@ -34,7 +303,7 @@ Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ab
- Pre-Backup-Dumps host-seitig ueber Unraid User Scripts etabliert. - Pre-Backup-Dumps host-seitig ueber Unraid User Scripts etabliert.
- Dump-Zielpfad auf `/mnt/user/backups/borg/dumps` umgestellt. - Dump-Zielpfad auf `/mnt/user/backups/borg/dumps` umgestellt.
- Restore-Smoke-Test fuer `postgresql17-globals.sql` und `gitea.db` erfolgreich nachgewiesen. - Restore-Smoke-Test fuer `postgresql17-globals.sql` und `gitea.db` erfolgreich nachgewiesen.
- Monitoring fuer Borg ueber `ntfy` und Uptime Kuma eingerichtet. - Monitoring fuer Borg war historisch ueber `ntfy` und Uptime Kuma eingerichtet; seit 2026-05-25 ersetzt durch `ntfy`, Blackbox/Prometheus und Monitoring Grafana.
### 2026-04-15 - Repo- und Betriebsbereinigung ### 2026-04-15 - Repo- und Betriebsbereinigung
@@ -71,10 +340,10 @@ Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ab
- `stirling-pdf` repo-seitig durch `bentopdf` ersetzt; Domain `pdf.kaleschke.info` bleibt erhalten. - `stirling-pdf` repo-seitig durch `bentopdf` ersetzt; Domain `pdf.kaleschke.info` bleibt erhalten.
- BentoPDF laeuft als geschuetztes browserseitiges PDF-Tool hinter `authelia@file,secure-headers@file` und setzt zusaetzlich COOP/COEP-Header fuer SharedArrayBuffer-basierte Office-Konvertierung. - BentoPDF laeuft als geschuetztes browserseitiges PDF-Tool hinter `authelia@file,secure-headers@file` und setzt zusaetzlich COOP/COEP-Header fuer SharedArrayBuffer-basierte Office-Konvertierung.
- `ops/grafana-influxdb` als neuer Monitoring-Stack vorbereitet, aber noch nicht deployed. - `ops/grafana-influxdb` als neuer Monitoring-Stack vorbereitet und spaeter in Betrieb genommen.
- Grafana wird hinter Traefik + Authelia unter `grafana.kaleschke.info` geplant. - Grafana laeuft hinter Traefik + Authelia unter `grafana.kaleschke.info`.
- InfluxDB 3 Core bleibt intern im Compose-Netz und wird ueber eine provisionierte Grafana-Datenquelle angebunden. - InfluxDB 3 Core bleibt ohne Public Route und wird ueber eine provisionierte Grafana-Datenquelle angebunden.
- Secrets fuer Grafana-Admin-Passwort, InfluxDB-Admin-Token und Grafana-Datasource-Token sind als Host-Dateien unter `/mnt/user/appdata/secrets/` dokumentiert und muessen vor dem ersten Deploy angelegt werden. - Secrets fuer Grafana-Admin-Passwort, InfluxDB-Admin-Token und Grafana-Datasource-Token sind als Host-Dateien unter `/mnt/user/appdata/secrets/` dokumentiert.
--- ---
+106
View File
@@ -0,0 +1,106 @@
# Network Inventory - KalliLab CORE
Status: Initialer Host-Audit erfasst, Router-/VLAN-Details offen.
Letzte Pruefung: 2026-05-26
## Zweck
Dieses Dokument beschreibt Router, DNS, Tailscale, Portfreigaben und Netztrennung. Es ergaenzt das Architektur-Zielbild in `HOMELAB_ARCHITECTURE_MASTER_V2.md` um konkrete Hardware- und Betriebswerte.
## Internet und Router
| Feld | Wert |
|---|---|
| Anschluss / Provider | TBD |
| Router-Modell | TBD |
| Firmware | TBD |
| Router-IP | 192.168.178.1 |
| DHCP-Server | vermutlich Router, zu pruefen |
| Lokales Subnetz | 192.168.178.0/24 |
| IPv6 aktiv | TBD |
| DynDNS / DDNS | Cloudflare via `ddns-updater`, Details TBD |
## DNS
| Komponente | Rolle | Adresse | Bemerkung |
|---|---|---|---|
| AdGuard Home | LAN DNS / Filter | Host `192.168.178.58`, Docker `172.23.0.3` | DNS auf Port 53; Admin soll nur via Tailscale-IP `100.80.98.33:8082` erreichbar sein |
| Unbound | Rekursiver Resolver | Docker `dns_net` | Upstream fuer AdGuard |
| Cloudflare | Authoritative DNS | extern | DNS-Challenge fuer TLS |
| Router | DHCP DNS-Verteilung | TBD | Muss auf AdGuard zeigen, falls so betrieben |
## Tailscale
| Feld | Wert |
|---|---|
| Node-Name | Kallilabcore |
| Tailscale IPv4 | 100.80.98.33 |
| Tailscale IPv6 | TBD |
| Exit Node | TBD |
| Subnet Router | TBD |
| ACL-Policy extern dokumentiert | TBD |
Pruefkommando:
```bash
tailscale status
tailscale ip -4
tailscale ip -6
```
## Portfreigaben und Exposure
| Port | Ziel | Zweck | Bewertung |
|---:|---|---|---|
| 80/tcp | Traefik | HTTP->HTTPS / ACME | erwartet |
| 443/tcp | Traefik | HTTPS | erwartet |
| 222/tcp | Gitea SSH | Git SSH | dokumentierte Ausnahme |
| 53/tcp+udp | AdGuard | DNS | dokumentierte Ausnahme |
| 8082/tcp | AdGuard Admin | Admin UI | Repo-Soll: nur `100.80.98.33:8082`, DNS-Port 53 unveraendert |
| 8181/tcp | InfluxDB 3 Core | LAN Writer fuer Home Assistant | LAN-only, Bind-IP pruefen |
Pruefkommando:
```bash
ss -ltnp | sort -k4
docker ps --format "{{.Names}}: {{.Ports}}" | sort
```
## Netztrennung
| Netz | Status | Bemerkung |
|---|---|---|
| LAN | 192.168.178.0/24 | Hauptnetz, Host `192.168.178.58` |
| Gast-WLAN | TBD | Zugriff auf AdGuard Admin muss ausgeschlossen sein |
| IoT-Netz | TBD | Zugriff auf AdGuard Admin muss ausgeschlossen sein |
| Tailscale | aktiv | Operator-Zugang, Host-IP `100.80.98.33` |
| VLANs | TBD | Router-/Switch-Faehigkeit pruefen |
## Docker-Netze
Authoritativ ist `HOMELAB_ARCHITECTURE_MASTER_V2.md`. Dieses Inventar haelt nur den Laufzeit-Snapshot fest.
| Docker-Netz | Zweck | Erwartung |
|---|---|---|
| frontend_net | Traefik/Web | external bridge |
| backend_net | DB/Cache intern | internal bridge |
| dns_net | AdGuard/Unbound | bridge |
| monitoring_net | Observability | compose-intern |
| app-interne Netze | Stack-isoliert | nur wenn technisch noetig |
Pruefkommando:
```bash
docker network ls
docker network inspect frontend_net | jq '.[0].Containers | keys'
docker network inspect backend_net | jq '.[0].Internal'
```
## Offene Entscheidungen
| Thema | Status | Naechster Schritt |
|---|---|---|
| AdGuard Admin nur via Tailscale | live validiert 2026-05-26 | Compose bindet Admin-Port auf `100.80.98.33:8082`; DNS auf Port 53 funktioniert, LAN-Zugriff auf `192.168.178.58:8082` schlaegt fehl |
| Gast-/IoT-Zugriff auf Admin-Ports | offen | Router-Regeln pruefen |
| IPv6 Exposure | offen | Router und Traefik/Cloudflare pruefen |
| Home Assistant InfluxDB Bind | offen | Effektive Listener-Adresse pruefen |
+41
View File
@@ -0,0 +1,41 @@
# Aktuelle Restliste - KalliLab CORE
Stand: 2026-05-17
Diese Datei ersetzt die alte Sprint-Liste vom 2026-05-16. Die damaligen Backup-, Posture-, Logging- und Hardening-Bloecke sind weitgehend erledigt oder dokumentiert. Sie bleibt nur als kurze Restliste fuer die naechsten bewussten Arbeitspakete bestehen.
## Erledigt / nicht mehr offen
- Filebrowser-Hardening: breiter Appdata-Mount ist entfernt; Filebrowser mountet nur noch Documents, Photos, Projekte und eigenen App-State.
- Authelia Argon2id-Haertung: `iterations: 3`, `memory: 65536`, `parallelism: 4`, `key_length: 32`, `salt_length: 16` sind gesetzt.
- Gitea Webhook-Allowlist: `GITEA__webhook__ALLOWED_HOST_LIST` ist auf `komodo-core,localhost,127.0.0.1,192.168.178.0/24` eingeschraenkt.
- Backup-Konsistenz: SQLite-/Nextcloud-Dumps, Borg-Scope fuer Nextcloud-Daten, Restore-Matrix und Freshness-Checks sind umgesetzt.
- Posture-/Cert-/Drift-Checks: Skripte und Unraid User Scripts sind vorhanden und geplant.
- Monitoring-Zielstack: `monitoring/` buendelt Prometheus, Loki, Promtail, Grafana, node-exporter, cAdvisor und InfluxDB 3 Core im Repo-Zielzustand.
- Docker-Log-Rotation: Unraid-native Rotation ist dokumentiert; keine separate `/etc/docker/daemon.json` setzen.
- Disk1-NTFS-Migration Phase 2: am 2026-05-25 abgeschlossen; Disk1 ist XFS, `posture-check` akzeptiert NTFS nicht mehr als Standard.
## Morgen / bewusst spaeter
- Monitoring live finalisieren:
- Secrets `monitoring_grafana_admin_password.txt`, `monitoring_grafana_influxdb_token.txt`, `influxdb3_admin_token.json` auf dem Host pruefen/anlegen
- `monitoring` in Komodo deployen
- alte Stacks `ops/loki` und `ops/grafana-influxdb` nach erfolgreichem Smoke-Test stoppen
- `https://monitoring.kaleschke.info`, Prometheus Targets, Loki-Logs und InfluxDB-Datasource pruefen
- Home Assistant -> InfluxDB finalisieren:
- HA-Token/Writer final pruefen
- erste Messwerte in InfluxDB verifizieren
- Grafana-HA-/Wetter-Dashboard in `monitoring-grafana` aufbauen
- Hermes VM-Seite:
- Runner-VM, echte `.env`, SSH-Key und Dashboard/Gateway final zusammenfuehren
- NAS-Stack erst starten, wenn VM-Seite bereit ist
## Verbleibende bekannte Warnings
- `ddns-updater`, `glances`, `scrutiny`: nutzen noch `latest...@sha256`; spaeter durch konkrete Versionstags ersetzen, sofern upstream sinnvoll versioniert.
- `ops/grafana-influxdb` und `ops/loki`: bleiben nur noch als Rollback-/Migrationsreferenz im Repo, nach Live-Migration nicht parallel betreiben.
- `scrutiny`: bleibt `privileged: true`; dokumentierte SMART-Ausnahme, spaeter erneut pruefen.
## Regel
Neue Arbeit erst starten, wenn klar ist, ob sie eines der drei Morgen-Themen betrifft oder eine der bekannten Warnings bewusst abbaut.
+89
View File
@@ -0,0 +1,89 @@
# Recovery Handoff - KalliLab CORE - 2026-05-15
Zweck: Startpunkt fuer einen neuen Chat, ohne das komplette Repo erneut zu lesen.
## Kontext
- Incident: NTFS-Cache-Vorfall ab 2026-05-11.
- Host: Unraid `Kallilabcore`, SSH `root@192.168.178.58`.
- Root Cause: Cache war NTFS/ntfs3; Disk1 ist noch NTFS/ntfs3 und wird spaeter separat migriert.
- Recovery-Prinzip: `docs/STORAGE_LAYOUT.draft.md` ist fuer diesen Restore bindend, obwohl die Datei noch `.draft` heisst.
- Keine Stacks starten, wenn ein Pfad/Setting gegen Storage Layout, Restore Matrix oder Architecture Master verstoesst.
## Host-Zustand
- Cache wurde erfolgreich von NTFS auf XFS neu formatiert.
- Verifiziert: `/mnt/cache` ist XFS auf `/dev/nvme0n1p1`.
- Disk1 bleibt vorerst NTFS auf `/mnt/disk1`; Migration ist Phase 2 nach stabilem Cache-Betrieb.
- Docker und Libvirt wurden nach dem Format wieder gestoppt.
- `/mnt/user/appdata` ist leer bzw. nur Basisverzeichnis; produktive Appdaten sind noch nicht restored.
- Share-Settings wurden nach Storage Layout korrigiert:
- `appdata`, `system`, `domains`: cache `only`
- `services`, `documents`, `photos`, `backups`, `media`, `finance`, `projekte`: cache `no`, include `disk1`
- `isos`: cache `yes`
- Backup alter Share-Configs: `/boot/config/shares.bak-20260515-pre-storage-layout`
## Image und Backups
- Full NVMe image liegt auf Windows `H:\kallilab-recovery\2026-05-14\nvme0n1-full-20260514.img`.
- `dd` exit code war `0`; Image-Groesse/Padding geprueft; Source-Raw-Hash war fertig.
- Image-Data-Hash wurde aus Zeitgruenden bewusst abgebrochen. Risiko wurde als ca. 1-3 Prozent eingeschaetzt.
- Hetzner-Borg-Archiv `Taegliche-Sicherung-2026-05-10T04:30:52.050` wurde als lesbare Recovery-Quelle verifiziert.
- Verifiziert wurden u. a. Vaultwarden SQLite, Gitea SQLite, Postgres-Dumps und Komodo Mongo-Archiv-Header.
- Lokaler Verify-Auszug liegt unter `H:\kallilab-recovery\2026-05-14\borg-verify-may10`.
## Entscheidungen seit dem Cache-Rebuild
- WD MyBookLive Duo wird komplett aus dem Setup entfernt.
- Backrest wird komplett aus dem aktiven Setup entfernt.
- Borg ist alleinige Backup-Technologie.
- Appdata Backup Plugin bleibt deaktiviert; WD-Ziele wurden aus aktiver Host-Konfiguration geleert.
- Unassigned Devices SMB-Remote fuer `//MYBOOKLIVEDUO/Public` wurde aus aktiver Host-Konfiguration entfernt.
- Backrest User Script `check_backrest_hetzner` wurde aus Schedule/Cron entfernt.
- Host-Konfig-Backup fuer diese Bereinigung: `/boot/config/cleanup-backup-20260515-remove-wd-backrest`
## Repo-Aenderungen im aktuellen Arbeitsbaum
Backrest wurde aus dem aktiven Zielbild entfernt:
- `ops/backrest/docker-compose.yml` geloescht
- `HOMELAB_ARCHITECTURE_MASTER_V2.md` aktualisiert
- `docs/REPO_MAP.md` aktualisiert
- `docs/SERVICE_CATALOG.md` aktualisiert
- `docs/RESTORE_MATRIX.md` aktualisiert
- `docs/AI_CONTEXT.md` aktualisiert
- `docs/DISASTER_RECOVERY.md` aktualisiert
- `ops/borg-ui/BACKUP_SCOPE.md` aktualisiert
- `ops/hermes-agent/services.json` aktualisiert
- `ops/hermes-agent/services.yaml` aktualisiert
- `ops/policy-checks/last-report.md` aktualisiert
Verifikation:
- `rg "/mnt/(cache|disk1|disks|remotes)" -g docker-compose.yml -g compose.yaml -g *.yml -g *.yaml` findet keine aktiven Compose/YAML-Treffer.
- `rg "ops/backrest|backrest.kaleschke|/mnt/user/appdata/backrest|192.168.178.86|MYBOOKLIVEDUO|WD-DUO"` findet nur historische/gewollte Hinweise.
- `python -m json.tool ops/hermes-agent/services.json` ok.
- `ops/hermes-agent/services.yaml` YAML ok.
- `ops/policy-checks/check_repo.ps1` ok: 29 Compose-Dateien, 0 Critical, 4 Warnings.
## Wichtigste Stop-Regeln
- Keine Container starten, solange Core-Pfade oder Share-Settings nicht gegen Storage Layout geprueft sind.
- Keine Backrest-/WD-Referenzen reaktivieren.
- Keine Bind-Mounts auf `/mnt/cache`, `/mnt/disk1`, `/mnt/disks`, `/mnt/remotes`.
- Keine Schreibaktionen auf Disk1 ausser bewusst noetig; Disk1 ist noch NTFS.
- Komodo nur gemeinsam und explizit anfassen.
- Erst Daten/Secrets restoren, dann Stacks einzeln starten und smoke-testen.
## Naechster sinnvoller Schritt
1. Repo-Aenderungen kurz reviewen und committen/pushen, bevor Komodo wieder produktiv wird.
2. DNS-Basis wiederherstellen:
- AdGuard: `/mnt/user/appdata/adguard/conf` aus Borg oder Image restoren; `work` kann frisch sein.
- Unbound: `/mnt/user/appdata/unbound/config` aus Borg oder Image restoren.
- Danach nur AdGuard + Unbound starten und DNS testen.
3. Danach Traefik + Authelia + Gitea/Vaultwarden in kleinen Schritten.
## Startprompt fuer neuen Chat
Lies zuerst `docs/RECOVERY_HANDOFF_2026-05-15.md`, dann `docs/STORAGE_LAYOUT.draft.md`, `docs/RESTORE_MATRIX.md` und nur die Compose-Dateien des naechsten betroffenen Stacks. Fuehre den KalliLab-CORE-Restore token-sparend fort. Nichts erfinden, keine Container starten, wenn etwas gegen Storage Layout verstoesst. Backrest und WD MyBookLive Duo sind entfernt und duerfen nicht wieder ins Setup.
+246
View File
@@ -0,0 +1,246 @@
# Repository Map
Stand: 2026-05-23
Diese Datei ist eine technische Landkarte des Repositories. Sie wurde aus Markdown-Dokumenten, `docker-compose.yml`-Dateien, Env-Beispielen, Traefik-Dynamic-Configs, Komodo/Periphery-Dateien und Skripten abgeleitet. Sie beschreibt den Repo-Sollzustand, nicht zwingend den Live-Zustand auf dem Host.
Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennamen, Secret-Namen und Pfade.
## Ordnerstruktur
| Pfad | Zweck |
|---|---|
| `apps/` | Produktive Anwendungen und vorbereitete App-Stacks |
| `core/` | Basisdienste, aktuell Gitea |
| `docs/` | Betriebsdokumentation, Restore, Rollback, GitOps-Regeln |
| `env/` | globale nicht geheime Beispiel-Env-Dateien |
| `host-services/` | Host-nahe Dienste mit direkten Ports oder Host-Netz |
| `infra/` | technische Infrastruktur wie PostgreSQL, Redis, DDNS |
| `ops/` | Operations-, Backup-, Monitoring- und Admin-Tools |
| `services/` | Host-seitige Betriebsskripte und Recovery-kritische Service-Hilfen |
| `security/` | Identity/Security-Dienste wie Authelia und Vaultwarden |
| `traefik/` | Reverse Proxy Compose und dynamic File-Provider-Konfiguration |
## Wichtige Dokumente
| Datei | Bedeutung |
|---|---|
| `README.md` | Einstieg und Kurzueberblick |
| `HOMELAB_ARCHITECTURE_MASTER_V2.md` | operative Architektur-Quelle fuer Netzwerk, Zugriff und Ausnahmen |
| `docs/WORKFLOW.md` | GitOps-/No-Drift-Arbeitsregeln |
| `docs/GITOPS_DRIFT_RUNBOOK.md` | Pflichtmatrix fuer Git/Gitea/Komodo/Docker/Host-Drift |
| `docs/DISASTER_RECOVERY.md` | Wiederanlauf nach Host-/Systemausfall |
| `docs/RESTORE_MATRIX.md` | Restore-Quellen, Dump-Artefakte und Smoke-Tests je Dienst |
| `docs/SERVICES_RECOVERY.md` | Recovery-kritische `/mnt/user/services`-Pfade, Gitea-Mirror und Komodo-Bootstrap |
| `docs/HARDWARE_INVENTORY.md` | Hardware-, Disk-, SMART-, USV- und Strom-Inventar |
| `docs/NETWORK_INVENTORY.md` | Router, DNS, Tailscale, Portfreigaben und Netztrennung |
| `docs/EXTERNAL_DEPENDENCIES.md` | Externe Provider, Konten, Ausfall-Szenarien und kritische Off-Repo-Abhaengigkeiten |
| `docs/CAPACITY_AND_LIFECYCLE.md` | Capacity-Schwellen, Wachstum, Upgrade-Trigger und Restore-Zeitziele |
| `docs/FAMILY_ONBOARDING.md` | Familienorientierte Nutzungsdoku ohne Operator-Details |
| `docs/AUDIT_2026-05-25_TODO.md` | Operative Arbeitsliste aus dem Audit vom 2026-05-25; Authelia-2FA bewusst geparkt |
| `docs/ALERTING_MAP.md` | ntfy Topic-Konvention und Sender-Mapping fuer Homelab-Alerts |
| `docs/ROLLBACK.md` | Rueckweg bei Fehlern im GitOps-Betrieb |
| `docs/SECRETS_MAP.md` | Secret-Namen, Pfade und Einbindungsarten ohne Werte |
| `docs/HOME_ASSISTANT_INFLUXDB_ECOWITT.md` | Home Assistant -> InfluxDB 3 -> Grafana Ablauf |
| `docs/AI_CONTEXT.md` | Gesamtverstaendnis fuer KI-Agenten |
| `docs/SERVICE_CATALOG.md` | produktiver Service-Katalog |
## Relevante Nicht-Compose-Dateien
| Datei | Zweck / Hinweis |
|---|---|
| `traefik/dynamic/middlewares.yml` | zentrale `secure-headers` und `authelia` ForwardAuth Middleware; manuelle Host-Sync-Ausnahme |
| `traefik/dynamic/dashboards.yml` | leer; File-Provider-Platzhalter |
| `traefik/dynamic/tls.yml` | leer; File-Provider-Platzhalter |
| `security/authelia/configuration.yml` | versionierte Authelia-Baseline fuer nicht geheime ACL-/Session-/Storage-Einstellungen; manuelle Host-Merge-Pflicht, User-Daten, OIDC-Client-Konfiguration und Secret-Werte bleiben ausserhalb von Git |
| `monitoring/prometheus/prometheus.yml` | Prometheus Scrape-Konfiguration fuer dedizierten Monitoring-Stack |
| `monitoring/loki/loki-config.yml` | Loki Filesystem/Retention-Konfiguration fuer dedizierten Monitoring-Stack |
| `monitoring/promtail/promtail-config.yml` | Promtail Docker-Socket-Discovery fuer dedizierten Monitoring-Stack |
| `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/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 und ntfy-Alarmierung |
| `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 Schreibweise aus `STORAGE_LAYOUT.draft.md` |
| `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/stack.env.example` | Beispiel fuer Hermes Stack-ENV; echte `stack.env` bleibt host-/komodoseitig und ist per `.gitignore` ausgeschlossen |
| `monitoring/stack.env.example` | `INFLUXDB_BIND_IP` Default `127.0.0.1`; im Zielzustand fuer Home Assistant auf LAN-IP setzen |
| `ops/komodo/stack.env.example` | Komodo Stack-ENV-Beispiel, Secret-Werte nicht enthalten |
## Stack-Inventar
### Apps
| Stack | Compose | Services / Images | Traefik Hosts | Networks | Ports | Abhaengigkeiten |
|---|---|---|---|---|---|---|
| BentoPDF | `apps/bentopdf/docker-compose.yml` | `bentopdf` -> `bentopdfteam/bentopdf:2.8.4` | `pdf.kaleschke.info` | `frontend_net` | keine | Traefik + Authelia; COOP/COEP Middleware |
| Immich | `apps/immich/docker-compose.yml` | `immich-server`, `immich-machine-learning`, `database`, `redis` | `immich.kaleschke.info` | `frontend_net`, `immich_default` | keine | `immich-server` depends on `database`, `redis` |
| Mail Archiver | `apps/mail-archiver/docker-compose.yml` | `mail-archiver` -> `s1t5/mailarchiver@sha256:...` | `mail.kaleschke.info` | `frontend_net`, `backend_net` | keine | shared PostgreSQL via env connection string; Internet fuer IMAP |
| Mealie | `apps/mealie/docker-compose.yml` | `mealie`, `mealie-postgres` | `mealie.kaleschke.info` | `frontend_net`, `mealie_internal` | keine | eigene PostgreSQL im internen Netz |
| Nextcloud | `apps/nextcloud/docker-compose.yml` | `nextcloud`, `nextcloud-postgres`, `nextcloud-redis` | `cloud.kaleschke.info` | `frontend_net`, `nextcloud_internal` | keine | native Nextcloud-Auth; eigene DB und Redis |
| ntfy | `apps/ntfy/docker-compose.yml` | `ntfy` -> `binwiederhier/ntfy:latest@sha256:...` | `ntfy.kaleschke.info` | `frontend_net` | keine | mobile Push via upstream `ntfy.sh` |
| Paperless-ngx | `apps/paperless/docker-compose.yml` | `paperless` -> `ghcr.io/paperless-ngx/paperless-ngx:2.20.10` | `paperless.kaleschke.info` | `frontend_net`, `backend_net` | keine | shared PostgreSQL + Redis; DB/Redis via Stack ENV |
| Paperless-GPT | `apps/paperless-gpt/docker-compose.yml` | `paperless-gpt` -> `icereed/paperless-gpt:v0.24.0` | `paperless-gpt.kaleschke.info` | `frontend_net` | keine | Paperless API, Ollama/LLM config |
| Unbound | `apps/unbound/docker-compose.yml` | `unbound` -> `shaanmajid/unbound:latest@sha256:...` | keine | `dns_net` | keine | Upstream Resolver fuer AdGuard |
### Core / Security / Infra
| Stack | Compose | Services / Images | Traefik Hosts | Networks | Ports | Abhaengigkeiten |
|---|---|---|---|---|---|---|
| Gitea | `core/gitea/docker-compose.yml` | `gitea` -> `docker.gitea.com/gitea:1.25.4@sha256:...` | `git.kaleschke.info` | `frontend_net` | `222:22/tcp` | SQLite in `/data`; SSH-Port ist dokumentierte Ausnahme; `github.com` ist als Mirror-Ziel erlaubt und externe DNS-Resolver sind gesetzt |
| Authelia | `security/authelia/docker-compose.yml` | `authelia` -> `authelia/authelia:latest@sha256:...` | `auth.kaleschke.info` | `frontend_net`, `backend_net` | keine | PostgreSQL 17 Storage, Traefik ForwardAuth; bewusst ohne Redis-Session-Backend |
| Vaultwarden | `security/vaultwarden/docker-compose.yml` | `vaultwarden` -> `vaultwarden/server:latest@sha256:...` | `vault.kaleschke.info` | `frontend_net` | keine | Datei-Persistenz, `ADMIN_TOKEN_FILE` |
| ddns-updater | `infra/ddns-updater/docker-compose.yml` | `ddns-updater` -> `ghcr.io/qdm12/ddns-updater:latest@sha256:...` | keine | `frontend_net` | keine | Cloudflare/API-Internetbedarf |
| PostgreSQL 17 | `infra/postgresql17/docker-compose.yml` | `postgresql17` -> `postgres:17.9@sha256:...` | keine | `backend_net` | keine | shared DB-Cluster |
| Redis | `infra/redis/docker-compose.yml` | `Redis` -> `redis:7.4-alpine@sha256:...` | keine | `backend_net` | keine | shared Cache, Passwort-Datei |
### Host Services
| Stack | Compose | Services / Images | Hosts | Networks | Ports / Mode | Abhaengigkeiten |
|---|---|---|---|---|---|---|
| AdGuard Home | `host-services/Adguard/docker-compose.yml` | `adguard` -> `adguard/adguardhome:v0.107.52` | keine Traefik-Route | `dns_net`, `frontend_net` | `53/tcp`, `53/udp`, `100.80.98.33:8082:80/tcp` | Unbound in `dns_net`; DNS-Port 53 ist direkte Ausnahme; Admin 8082 ist auf Tailscale-IP begrenzt |
| Plex | `host-services/plex/docker-compose.yml` | `plex` -> `plexinc/pms-docker:1.43.1.10611-1e34174b1@sha256:...` | keine Traefik-Route | `network_mode: host` | host network | Medienserver; Host-Netz bleibt fuer Discovery / Plex GDM dokumentierte Ausnahme |
| Tailscale | `host-services/tailscale/docker-compose.yml` | `Tailscale-Docker` -> `tailscale/tailscale:stable@sha256:...` | keine | `network_mode: host` | host network | VPN/Remote-Zugang |
### Operations
| Stack | Compose | Services / Images | Traefik Hosts | Networks | Ports | Abhaengigkeiten |
|---|---|---|---|---|---|---|
| Borg UI | `ops/borg-ui/docker-compose.yml` | `borg-ui` -> `ainullcode/borg-ui:latest@sha256:...` | `borg.kaleschke.info` | `frontend_net` | keine | Borg repo, Dump-Scope, Restore-Ziel |
| code-server | `ops/code-server/docker-compose.yml` | `code-server` -> `lscr.io/linuxserver/code-server:4.116.0@sha256:...` | `code.kaleschke.info` | `frontend_net` | keine | Passwort-Datei, Workspace-Mounts |
| Filebrowser | `ops/filebrowser/docker-compose.yml` | `filebrowser` -> `filebrowser/filebrowser:v2.63.2@sha256:...` | `files.kaleschke.info` | `frontend_net` | keine | Documents/Photos/Projekte-Mounts, Admin-UI hinter Authelia |
| Glance | `ops/glance/docker-compose.yml` | `glance` -> `glanceapp/glance:v0.8.4`, `glance-docker-socket-proxy` -> `tecnativa/docker-socket-proxy:v0.4.2` | `glance.kaleschke.info` | `frontend_net`, `glance_socket_net` | keine | Homelab-Dashboard mit Home- und Infrastructure-Seite, Monitor-, Community-, Docker-, Internet-/DNS-/VPN- und Server-Stats-Widgets; aktives Community-Widget: Immich; Docker-API nur ueber internen Socket-Proxy |
| Glances | `ops/glances/docker-compose.yml` | `glances` -> `nicolargo/glances:latest-full@sha256:...` | `glances.kaleschke.info` | `frontend_net` | keine | Rootfs/Docker-Socket fuer Monitoring |
| Monitoring | `monitoring/docker-compose.yml` | `monitoring-prometheus`, `monitoring-alertmanager`, `monitoring-alertmanager-ntfy-bridge`, `monitoring-blackbox-exporter`, `monitoring-loki`, `monitoring-promtail`, `monitoring-grafana`, `monitoring-node-exporter`, `monitoring-cadvisor`, `monitoring-influxdb3-core`, optional `monitoring-grafana-dashboard-importer` | `monitoring.kaleschke.info` | `frontend_net`, `monitoring_net`, `monitoring_influx_lan` | `monitoring-influxdb3-core`: `${INFLUXDB_BIND_IP:-127.0.0.1}:8181:8181` | zentraler Zielstack fuer Prometheus/Loki/Grafana/InfluxDB; Alertmanager sendet via ntfy-Bridge nach `homelab-alerts`; Blackbox ersetzt Uptime-Kuma-Checks nach Parallelphase; Promtail nutzt Docker socket read-only; Dashboard-Importer nur via `bootstrap`-Profil |
| Hermes Agent | `ops/hermes-agent/docker-compose.yml` | `hermes-gateway`, `hermes-dashboard` -> local build from Dockerfile | `hermes.kaleschke.info` via `${HERMES_DASHBOARD_HOST}` | `hermes_net`, dashboard zusaetzlich `frontend_net` | `8642` nur expose intern | SSH runner, Home Assistant optional, LLM provider env; Dashboard hinter Authelia |
| Komodo | `ops/komodo/docker-compose.yml` | `komodo-core`, `komodo-mongo`, `komodo-periphery` | `komodo.kaleschke.info` | `frontend_net`, `komodo_net` | keine | Mongo, Docker socket, `/mnt/user/services` workspace mount, Gitea DNS override |
| Scrutiny | `ops/scrutiny/docker-compose.yml` | `scrutiny` -> `ghcr.io/starosdev/scrutiny:latest-omnibus@sha256:...` | `scrutiny.kaleschke.info` | `frontend_net` | keine | `privileged: true`, device mounts fuer SMART |
| Speedtest Tracker | `ops/speedtest/docker-compose.yml` | `speedtest-tracker` -> `lscr.io/linuxserver/speedtest-tracker:1.13.12@sha256:...` | `speedtest.kaleschke.info` | `frontend_net` | keine | App key/admin env, SQLite/config path |
### Traefik
| Stack | Compose | Service / Image | Hosts | Networks | Ports | Abhaengigkeiten |
|---|---|---|---|---|---|---|
| Traefik | `traefik/docker-compose.yml` | `traefik` -> `traefik:v3.6` | `traefik.kaleschke.info` | `frontend_net`, `backend_net` | `80:80/tcp`, `443:443/tcp` | Docker provider, Cloudflare DNS token secret, dynamic config |
## Traefik Hosts
| Host | Service | Zugriff |
|---|---|---|
| `auth.kaleschke.info` | Authelia | Auth provider / bypass fuer eigene Domain |
| `borg.kaleschke.info` | Borg UI | Traefik + Authelia |
| `cloud.kaleschke.info` | Nextcloud | Traefik, native App-Auth |
| `code.kaleschke.info` | code-server | Traefik + Authelia |
| `files.kaleschke.info` | Filebrowser | Traefik + Authelia |
| `git.kaleschke.info` | Gitea Web | Traefik |
| `glance.kaleschke.info` | Glance | Traefik + Authelia |
| `glances.kaleschke.info` | Glances | Traefik + Authelia |
| `hermes.kaleschke.info` | Hermes Dashboard | Traefik + Authelia |
| `immich.kaleschke.info` | Immich | Traefik, native App-Auth |
| `komodo.kaleschke.info` | Komodo | Traefik, native Komodo-Auth; keine pauschale ForwardAuth |
| `mail.kaleschke.info` | Mail Archiver | Traefik + Authelia + App-Auth |
| `mealie.kaleschke.info` | Mealie | Traefik |
| `monitoring.kaleschke.info` | Monitoring Grafana | Traefik + Authelia |
| `ntfy.kaleschke.info` | ntfy | Traefik |
| `paperless.kaleschke.info` | Paperless-ngx | Traefik |
| `paperless-gpt.kaleschke.info` | Paperless-GPT | Traefik + Authelia |
| `pdf.kaleschke.info` | BentoPDF | Traefik + Authelia + COOP/COEP |
| `scrutiny.kaleschke.info` | Scrutiny | Traefik + Authelia |
| `speedtest.kaleschke.info` | Speedtest Tracker | Traefik + Authelia |
| `traefik.kaleschke.info` | Traefik Dashboard | Traefik + Authelia |
| `vault.kaleschke.info` | Vaultwarden | Traefik |
## Networks
| Network | Typ / Status | Nutzer |
|---|---|---|
| `frontend_net` | external bridge | Web-/Proxy-Netz fuer Traefik und alle gerouteten UIs |
| `backend_net` | external/internal laut Architektur | PostgreSQL 17, Redis, Authelia, Paperless, Mail Archiver, Traefik |
| `dns_net` | App-/Host-Netz | AdGuard Home und Unbound |
| `immich_default` | Compose-intern, `internal: true` | Immich Server, ML, Postgres, Redis |
| `mealie_internal` | Compose-intern; Laufzeitname mit Compose-Projektpraefix typischerweise `mealie_mealie_internal` | Mealie und Mealie Postgres |
| `nextcloud_internal` | Compose-intern | Nextcloud, Nextcloud Postgres, Nextcloud Redis |
| `monitoring_net` | Compose-/Stack-Netz bridge | Prometheus, Loki, Promtail, Monitoring-Grafana, node-exporter, cAdvisor; Traefik fuer Metrics-Scrape |
| `monitoring_influx_lan` | Compose-intern bridge | InfluxDB Host-Port-Publishing fuer LAN Writer im zentralen Monitoring-Stack |
| `glance_socket_net` | Compose-intern, `internal: true` | Glance und `glance-docker-socket-proxy`; keine Traefik-Anbindung |
| `komodo_net` | Compose-intern, `internal: true` | Komodo Core, Mongo, Periphery |
| `hermes_net` | Compose-intern bridge | Hermes Gateway/Dashboard |
| `host` | Host-Netz | Tailscale; Plex als Repo-Compose-Stack unter `host-services/plex/` |
## Volumes und Datenpfade
| Bereich | Wichtige Pfade |
|---|---|
| Traefik | `/mnt/user/appdata/traefik/dynamic`, `/mnt/user/appdata/traefik/letsencrypt`, Cloudflare Secret |
| Gitea | `/mnt/user/services/gitea/data` |
| Authelia | `/mnt/user/appdata/authelia/config`, Authelia Secret-Dateien |
| Vaultwarden | `/mnt/user/appdata/vaultwarden`, Admin-Token-Datei |
| PostgreSQL 17 | `/mnt/user/appdata/postgresql17`, `postgres_password.txt` |
| Redis | `/mnt/user/appdata/redis`, `redis_password.txt` |
| Paperless | `/mnt/user/appdata/paperless-ngx/data`, `/mnt/user/documents/paperless`, `/mnt/user/documents/scans_inbox` |
| Immich | `/mnt/user/photos/immich`, `/mnt/user/photos/family_archive`, `/mnt/user/appdata/immich_postgres`, `model-cache` |
| Mealie | `/mnt/user/appdata/mealie/data`, `/mnt/user/appdata/mealie/postgres` |
| Mail Archiver | `/mnt/user/appdata/mailarchiver/data-protection-keys` |
| Nextcloud | `/mnt/user/appdata/nextcloud/html`, `/mnt/user/documents/nextcloud-data`, `/mnt/user/appdata/nextcloud/postgres`, `/mnt/user/appdata/nextcloud/redis` |
| Plex | `/mnt/user/appdata/plex/config`, `/mnt/user/appdata/plex/transcode`, `/mnt/user/media`, `/mnt/user/photos` |
| ntfy | `/mnt/user/appdata/ntfy` |
| Paperless-GPT | `/mnt/user/appdata/paperless-gpt/data`, `/mnt/user/appdata/paperless-gpt/prompts` |
| AdGuard | `/mnt/user/appdata/adguard/work`, `/mnt/user/appdata/adguard/conf` |
| Tailscale | `/mnt/user/appdata/tailscale` |
| Borg UI | `/mnt/user/appdata/borg-ui/data`, `/mnt/user/appdata/borg-ui/cache`, `/mnt/user/backups/borg/dumps`, selected restore/source mounts |
| code-server | `/mnt/user/appdata/code-server`, `/mnt/user/services/dev` |
| Filebrowser | `/mnt/user/documents`, `/mnt/user/photos`, `/mnt/user/projekte`, Filebrowser database/config paths |
| Glance | Repo-Konfiguration unter `ops/glance/config/glance.yml`; keine produktive Datenpersistenz; Docker-Socket nur am internen Proxy |
| Glances | `/`, Docker socket, `/etc/os-release` |
| Scrutiny | `/mnt/user/appdata/scrutiny/*`, `/run/udev`, selected `/dev/...` disks |
| Speedtest | `/mnt/user/appdata/speedtest-tracker/config` |
| Monitoring | named volumes `prometheus_data`, `loki_data`, `promtail_positions`, `grafana_data`; InfluxDB-Persistenz unter `/mnt/user/appdata/influxdb3/data` und `/mnt/user/appdata/influxdb3/plugins`; Provisioning im Repo unter `monitoring/grafana/provisioning`; Dashboards unter `monitoring/grafana/dashboards`; historische Altstandsdaten unter `/mnt/user/appdata/grafana`, `/mnt/user/appdata/loki` und `/mnt/user/appdata/alloy` nicht blind loeschen |
| Hermes Agent | `/mnt/user/appdata/hermes-agent/data`, `/mnt/user/appdata/hermes-agent/ssh`, SSH private key path |
| Komodo | `komodo_keys`, `/mnt/user/appdata/komodo/core`, `/mnt/user/appdata/komodo/mongo`, `/mnt/user/appdata/komodo/periphery`, `/mnt/user/services` |
## Secrets und Env-Hinweise
| Dienst / Stack | Secret-/Env-Hinweise ohne Werte |
|---|---|
| Traefik | `cloudflare_dns_api_token` als Docker Secret / `CF_DNS_API_TOKEN_FILE` |
| Authelia | JWT, Session, Storage Encryption, Postgres Password via `_FILE` |
| Vaultwarden | `ADMIN_TOKEN_FILE` |
| PostgreSQL 17 | `POSTGRES_PASSWORD_FILE` |
| Redis | Passwort-Datei und Startkommando |
| Paperless | `PAPERLESS_DBPASS`, `PAPERLESS_REDIS` als Komodo Stack ENV |
| Immich | `IMMICH_DB_PASSWORD` Stack ENV; `immich_postgres_password.txt` fuer Postgres |
| Mail Archiver | `MAILARCHIVER_DB_CONNECTION`, `MAILARCHIVER_AUTH_PASSWORD` als Stack ENV |
| Glance | `GLANCE_IMMICH_API_KEY`, `GLANCE_ADGUARD_USERNAME`, `GLANCE_ADGUARD_PASSWORD`, `GLANCE_SPEEDTEST_API_KEY` als Stack ENV fuer Community-/Live-Widgets |
| Speedtest | `APP_KEY`, `ADMIN_PASSWORD` als Stack ENV |
| Nextcloud | Admin User, Admin Password, Postgres Password via Secret-Dateien |
| Komodo | `KOMODO_SECRET_KEY`, `KOMODO_WEBHOOK_SECRET`, `KOMODO_JWT_SECRET`, `KOMODO_MONGO_PASSWORD`, `KOMODO_PERIPHERY_PASSKEY`; Mongo Passwort-Datei |
| Borg UI | Borg Credentials, Admin Login, SSH Keys in persistentem Appdata, nicht im Git |
| Hermes Agent | provider keys, API server key, messaging tokens, Home Assistant token in Host `.env`; SSH private key als Host-Secret |
| Monitoring | `monitoring_grafana_admin_password.txt`, `monitoring_grafana_influxdb_token.txt`, `influxdb3_admin_token.json` fuer zentrale Grafana-/InfluxDB-Funktionen; ersetzt Uptime Kuma fuer HTTP-Verfuegbarkeit |
## Skripte
| Skript | Ausfuehrungsort | Zweck |
|---|---|---|
| `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/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.
## Unsicherheiten / TODOs aus Repo-Sicht
- Echte `stack.env`- und `.env`-Dateien sind per `.gitignore` ausgeschlossen; nur `*.example`-Dateien gehoeren ins Repo.
- Hardware-, Netzwerk- und Provider-Inventare sind initiale Templates und muessen nach einem Host-Audit mit echten Werten gefuellt werden.
- Authelia-2FA-/OIDC-Aenderungen sind nach Audit 2026-05-25 bewusst geparkt und werden nicht als Sofortmassnahme behandelt.
- Authelia `configuration.yml` ist Repo-Baseline fuer nicht geheime Einstellungen, wird aber nicht automatisch von Komodo auf den Host kopiert. Die produktive Host-Datei kann zusaetzliche OIDC-/Secret-Konfiguration enthalten; Aenderungen muessen manuell gemerged und validiert werden.
- `backend_net` ist in der Architektur als `internal: true` beschrieben; einzelne Compose-Dateien referenzieren es external. Live-Netz-Attribute bei Drift-Fragen pruefen.
- Einige Images bleiben trotz Digest-Pin semantisch auf mutable Tags (`latest@sha256`, `release@sha256`). Das ist bewusst dokumentiert, aber bei Updates gesondert pruefen.
- Stateful Datenhalter sind seit 2026-05-05 bevorzugt mit Minor-/Patch-Tag plus Digest gepinnt; Redis-Caches wurden im Hardening-Sprint 2026-05-16 auf `redis:7.4-alpine@sha256:...` vereinheitlicht.
- `scrutiny` bleibt `privileged: true`; dokumentierte Ausnahme, aber weiterhin pruefenswert.
- `tailscale` nutzt Host-Netz, `NET_ADMIN`, `NET_RAW` und `/dev/net/tun` als dokumentierte VPN-Ausnahme.
- `monitoring-influxdb3-core` laeuft aktuell als `user: "0"`; UID/GID-Hardening nur als eigener Sprint.
- Leere `.keep`-Platzhalter wurden entfernt; neue Verzeichnisse sollen erst mit konkretem Inhalt ins Repo.
- `plex` ist als Repo-Compose-Stack unter `host-services/plex/` enthalten; `network_mode: host` bleibt die dokumentierte Discovery-Ausnahme.
- BentoPDF kann je nach Live-Stand vorbereitet statt produktiv sein; Hermes Dashboard ist produktiv unter `hermes.kaleschke.info`.
+206
View File
@@ -0,0 +1,206 @@
# Restore Handbook - KalliLab CORE
Stand: 2026-05-07
Dieses Handbuch ist die praktische Betriebsanleitung fuer Restore-Checks und Restore-Lab in KalliLab CORE.
Es ergaenzt:
- `docs/RESTORE_MATRIX.md`
- `docs/DISASTER_RECOVERY.md`
- `ops/restore-tests/*`
---
## 1. Ziel
Dieses Handbuch beantwortet vier Fragen:
1. Was ist die Restore-Quelle?
2. Wo wird getestet?
3. Wie pruefen wir Erfolg?
4. Wie machen wir das regelmaessig mit wenig Handarbeit?
---
## 2. Grundmuster
Alle validierten Restore-Tests folgen demselben Muster:
- Quelle bleibt das produktive Borg-Repo bei Hetzner
- Borg-Zugriff laeuft ueber den vorhandenen `borg-ui`-Container
- Passphrase kommt aus `/mnt/user/appdata/secrets/borg_repo_passphrase.txt`
- Testdaten landen unter `/mnt/user/backups/restore-lab/<dienst>`
- Reports landen unter `/mnt/user/backups/restore-reports`
- Testinstanzen laufen lokal ohne Traefik und ohne produktive Domain
- nach Erfolg werden Testcontainer und Testdaten wieder entfernt
---
## 3. Bereits praktisch verifiziert
### Vaultwarden
- Report: `/mnt/user/backups/restore-reports/vaultwarden-2026-05-07.md`
- Nachweis:
- Borg-Restore erfolgreich
- Testcontainer startete
- Login-Seite war erreichbar
### Gitea
- Report: `/mnt/user/backups/restore-reports/gitea-2026-05-07.md`
- Nachweis:
- Borg-Restore erfolgreich
- Web-UI antwortete
- SSH-Port reagierte
### Paperless
- Report: `/mnt/user/backups/restore-reports/paperless-2026-05-07.md`
- Nachweis:
- Borg-Datei-Restore erfolgreich
- Paperless-Dump aus Borg importiert
- Login-Seite war erreichbar
- Test-DB enthielt `25` Dokumente
---
## 4. Verzeichnisstruktur
### Produktiv
- `/mnt/user/appdata`
- `/mnt/user/services`
- `/mnt/user/documents`
- `/mnt/user/backups/borg/dumps/latest`
### Restore-Lab
- `/mnt/user/backups/restore-lab/vaultwarden`
- `/mnt/user/backups/restore-lab/gitea`
- `/mnt/user/backups/restore-lab/paperless`
### Reports
- `/mnt/user/backups/restore-reports`
---
## 5. Restore-Frequenz
- jeden Montag, 06:30:
- Frische-Check fuer Dumps und Reports
- 1. Samstag im Monat, 07:00:
- Vaultwarden
- 3. Samstag im Monat, 07:00:
- Gitea
- jeder 2. Monat, 2. Samstag, 08:00:
- Paperless
---
## 6. Betriebsmodi
### V1
- validierte Bash-Host-Jobs
- Host-Job-Definitionen liegen im Repo
- Scheduler kann bereits echte Frische- und Restore-Checks fahren
- `ntfy` und Hermes-Auswertung folgen danach
### V2
- `ntfy` bei Erfolg/Fehler
- Hermes liest Reports und baut Uebersichten
- zusaetzliche Rotation, Sammelreports und weitere Dienste
---
## 7. User Script Jobs auf Unraid
Die Vorlagen stehen in:
- `ops/restore-tests/unraid-user-scripts.md`
Host-Repo-Pfad:
```text
/mnt/user/services/homelab
```
V1-Jobs:
1. `restore-freshness-weekly`
2. `restore-vaultwarden-monthly`
3. `restore-gitea-monthly`
4. `restore-paperless-bimonthly`
---
## 8. Erfolgskriterien
Ein Restore-Test gilt nur dann als erfolgreich, wenn:
- Restore-Quelle lesbar war
- Daten im Restore-Lab ankamen
- Testcontainer startete
- Smoke-Test erfolgreich war
- Report geschrieben wurde
Nur `Container laeuft` reicht nicht.
---
## 9. Sicherheitsregeln
- keine produktiven Pfade beschreiben
- keine produktiven Container fuer Restore-Tests verwenden
- keine produktiven Domains fuer Testinstanzen verwenden
- keine Secrets im Repo
- keine Restore-Automatik fuer neue Dienste ohne bewusste Freigabe
---
## 10. Schnellstart
### Frische-Check
Auf dem Unraid-Host:
```bash
bash /mnt/user/services/homelab/ops/restore-tests/run-restore-checks.sh freshness
```
### Vaultwarden Restore-Check
```bash
bash /mnt/user/services/homelab/ops/restore-tests/run-restore-checks.sh vaultwarden
```
### Gitea Restore-Check
```bash
bash /mnt/user/services/homelab/ops/restore-tests/run-restore-checks.sh gitea
```
### Paperless Restore-Check
```bash
bash /mnt/user/services/homelab/ops/restore-tests/run-restore-checks.sh paperless
```
### Optional mit `ntfy`
```bash
bash /mnt/user/services/homelab/ops/restore-tests/run-restore-job-with-ntfy.sh freshness homelab-info
```
---
## 11. Naechste Ausbaustufen
1. Vollautomatik fuer Vaultwarden, Gitea und Paperless
2. `ntfy`-Meldungen fuer Erfolg/Fehler
3. Hermes-Zusammenfassung ueber vorhandene Reports
4. naechster Referenz-Restore fuer `mail-archiver` oder `mealie`
+39 -17
View File
@@ -26,15 +26,17 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
| Dienst | Fuehrende Quelle | Datei-Restore | Dump / DB | Secrets / ENV | Abhaengigkeiten | Smoke-Test | | Dienst | Fuehrende Quelle | Datei-Restore | Dump / DB | Secrets / ENV | Abhaengigkeiten | Smoke-Test |
|---|---|---|---|---|---|---| |---|---|---|---|---|---|---|
| Unraid OS Flash | Borg-Artefakt + optional Unraid Connect | `/boot/config` aus `unraid-flash-config.tar.gz` | `unraid-flash-config.tar.gz`, `.sha256`, Manifest | enthaelt sensible Host-Konfiguration, wie Secret-Material behandeln | Unraid USB Flash Creator / neuer Boot-Stick | Unraid bootet, Array-Zuordnung und Shares sind sichtbar |
| Traefik | Share / Borg | `/mnt/user/appdata/traefik`, besonders `dynamic/`, `letsencrypt`, `secrets` | keine eigene DB | `cloudflare_dns_api_token` | `frontend_net`, `backend_net` | `https://traefik.kaleschke.info` erreichbar, Dashboard ueber Authelia | | Traefik | Share / Borg | `/mnt/user/appdata/traefik`, besonders `dynamic/`, `letsencrypt`, `secrets` | keine eigene DB | `cloudflare_dns_api_token` | `frontend_net`, `backend_net` | `https://traefik.kaleschke.info` erreichbar, Dashboard ueber Authelia |
| AdGuard Home | Share / Borg | `/mnt/user/appdata/adguard/conf` | keine | keine zusaetzlichen Repo-Secrets dokumentiert | `dns_net`, `frontend_net` | DNS-Aufloesung funktioniert | | AdGuard Home | Share / Borg | `/mnt/user/appdata/adguard/conf` | keine | keine zusaetzlichen Repo-Secrets dokumentiert | `dns_net`, `frontend_net` | DNS-Aufloesung funktioniert |
| Tailscale | Share / Borg | `/mnt/user/appdata/tailscale` | keine | Tailscale-State im Pfad | Host-Netz | Tailscale verbunden | | Tailscale | Share / Borg | `/mnt/user/appdata/tailscale` | keine | Tailscale-State im Pfad | Host-Netz | Tailscale verbunden |
| PostgreSQL 17 | Share + Dumps | `/mnt/user/appdata/postgresql17` | `postgresql17-globals.sql`, `postgresql17-mailarchiver.dump`, `postgresql17-paperless.dump`, optional `postgresql17-authelia.dump` | `postgres_password.txt` | `backend_net` | DB startet, Ziel-Datenbanken vorhanden | | PostgreSQL 17 | Share + Dumps | `/mnt/user/appdata/postgresql17` | `postgresql17-globals.sql`, `postgresql17-mailarchiver.dump`, `postgresql17-paperless.dump`, optional `postgresql17-authelia.dump` | `postgres_password.txt` | `backend_net` | DB startet, Ziel-Datenbanken vorhanden |
| Redis | Share / Host | `/mnt/user/appdata/redis` | keine | `redis_password.txt` | `backend_net` | Redis startet, Apps verbinden sich | | Redis | Share / Host | `/mnt/user/appdata/redis` | keine | `redis_password.txt` | `backend_net` | Redis startet, Apps verbinden sich |
| Authelia | Borg | `/mnt/user/appdata/authelia/config`, `/mnt/user/appdata/secrets/*authelia*` | Shared PostgreSQL, optional Dump `postgresql17-authelia.dump` | JWT/Session/Storage/Postgres-Secret-Dateien | PostgreSQL 17, Redis, Traefik | Login-Seite und ForwardAuth funktionieren | | Authelia | Borg | `/mnt/user/appdata/authelia/config`, `/mnt/user/appdata/secrets/*authelia*` | Shared PostgreSQL, optional Dump `postgresql17-authelia.dump` | JWT/Session/Storage/Postgres-/SMTP-Secret-Dateien | PostgreSQL 17, Traefik, GMX SMTP | Login-Seite und ForwardAuth funktionieren; SMTP-Notifier startet; aktive Sessions werden nach Restart neu aufgebaut |
| Gitea | Borg / Share | `/mnt/user/services/gitea/data` | SQLite in `/data` | keine separaten Secret-Dateien dokumentiert | Traefik | Web-UI erreichbar, Repo sichtbar, SSH-Port reagiert | | Gitea | GitHub-Mirror fuer Repo-Bootstrap, Borg + Dump fuer Gitea-Appstate | `/mnt/user/services/gitea/data` | `gitea.sqlite.dump` | `borg_repo_passphrase.txt` fuer Restore-Tests; GitHub-Push-Mirror-PAT liegt nur in Gitea-Mirror-Settings | Traefik | Web-UI erreichbar, Repo sichtbar, SSH-Port reagiert; GitHub-Push-Mirror synchronisiert ohne `last_error`; Mini-Restore nach `/mnt/user/backups/restore-lab/gitea` am 2026-05-07 erfolgreich validiert |
| Komodo | Borg / Share | `/mnt/user/appdata/komodo/core`, `/mnt/user/appdata/komodo/periphery` | `komodo-mongo.archive.gz` falls verifiziert | `komodo_mongo_password.txt`, `KOMODO_*` Stack ENV | Traefik, Mongo, Gitea | UI erreichbar, Periphery verbunden | | Komodo | Borg / Share | `/mnt/user/appdata/komodo/core`, `/mnt/user/appdata/komodo/periphery`, `/mnt/user/services/stacks` | `komodo-mongo.archive.gz` falls verifiziert | `komodo_mongo_password.txt`, `KOMODO_*` Stack ENV | Traefik, Mongo, Gitea | UI erreichbar, Periphery verbunden |
| Vaultwarden | Borg / Share | `/mnt/user/appdata/vaultwarden` | dateibasiert | `vaultwarden_admin_token.txt` | Traefik | Login-Seite erreichbar, Tresor-Daten sichtbar | | GitOps Host Automation | Borg / Git | `/mnt/user/services/homelab-infra`, `/mnt/user/services/posture-check` | keine eigene DB | keine | Gitea, Komodo, Unraid User Scripts | `posture-check` laeuft vom Host-Pfad und liefert `warning_count: 0` im bekannten Uebergangszustand |
| Vaultwarden | Borg + Dump | `/mnt/user/appdata/vaultwarden` | `vaultwarden.sqlite.dump` | `vaultwarden_admin_token.txt`, `borg_repo_passphrase.txt` fuer Restore-Tests | Traefik | Login-Seite erreichbar, Tresor-Daten sichtbar; Mini-Restore nach `/mnt/user/backups/restore-lab/vaultwarden` am 2026-05-07 erfolgreich validiert |
--- ---
@@ -42,12 +44,12 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
| Dienst | Fuehrende Quelle | Datei-Restore | Dump / DB | Secrets / ENV | Abhaengigkeiten | Smoke-Test | | Dienst | Fuehrende Quelle | Datei-Restore | Dump / DB | Secrets / ENV | Abhaengigkeiten | Smoke-Test |
|---|---|---|---|---|---|---| |---|---|---|---|---|---|---|
| Paperless-ngx | Borg + Dumps | `/mnt/user/appdata/paperless-ngx/data`, `/mnt/user/documents/paperless`, `/mnt/user/documents/paperless/export`, `/mnt/user/documents/scans_inbox` | `postgresql17-paperless.dump` | `PAPERLESS_DBPASS`, `PAPERLESS_REDIS` | PostgreSQL 17, Redis, Traefik | Web-UI startet, Dokumente vorhanden | | Paperless-ngx | Borg + Dumps | `/mnt/user/appdata/paperless-ngx/data`, `/mnt/user/documents/paperless`, `/mnt/user/documents/paperless/export`, `/mnt/user/documents/scans_inbox` | `postgresql17-paperless.dump` | `PAPERLESS_DBPASS`, `PAPERLESS_REDIS`, `borg_repo_passphrase.txt` fuer Restore-Tests | PostgreSQL 17, Redis, Traefik | Web-UI startet, Dokumente vorhanden; Mini-Restore nach `/mnt/user/backups/restore-lab/paperless` am 2026-05-07 erfolgreich validiert |
| Mealie | Borg + Dump | `/mnt/user/appdata/mealie/data`, optional `/mnt/user/appdata/mealie/postgres` bei lokalem Share-Weiterbetrieb | `mealie.dump` | `mealie_postgres_password.txt` | `mealie-postgres`, Traefik | UI startet, Rezepte vorhanden | | Mealie | Borg + Dump | `/mnt/user/appdata/mealie/data`, optional `/mnt/user/appdata/mealie/postgres` bei lokalem Share-Weiterbetrieb | `mealie.dump` | `mealie_postgres_password.txt` | `mealie-postgres`, Traefik | UI startet, Rezepte vorhanden |
| Immich | Borg + Dump | `/mnt/user/photos/immich`, `/mnt/user/photos/family_archive` | `immich.dump` | `IMMICH_DB_PASSWORD`, `immich_postgres_password.txt` | `immich_postgres`, `immich_redis`, Traefik | UI startet, Medienbibliothek sichtbar | | Immich | Borg + Dump | `/mnt/user/photos/immich`, `/mnt/user/photos/family_archive` | `immich.dump` | `IMMICH_DB_PASSWORD`, `immich_postgres_password.txt` | `immich_postgres`, `immich_redis`, Traefik | UI startet, Medienbibliothek sichtbar |
| Mail-Archiver | Borg + Shared Dump | `/mnt/user/appdata/mailarchiver/data-protection-keys` | `postgresql17-mailarchiver.dump` | `MAILARCHIVER_DB_CONNECTION`, `MAILARCHIVER_AUTH_PASSWORD` | PostgreSQL 17, Traefik | Web-UI startet, Archiv laesst sich oeffnen | | Mail-Archiver | Borg + Shared Dump | `/mnt/user/appdata/mailarchiver/data-protection-keys` | `postgresql17-mailarchiver.dump` | `MAILARCHIVER_DB_CONNECTION`, `MAILARCHIVER_AUTH_PASSWORD` | PostgreSQL 17, Traefik, Authelia | Authelia-Weiterleitung greift; nach Login startet die Web-UI und das Archiv laesst sich oeffnen |
| Nextcloud | Borg + Share | `/mnt/user/appdata/nextcloud/html`, `/mnt/user/appdata/nextcloud/postgres`, `/mnt/user/appdata/nextcloud/redis`, `/mnt/user/documents/nextcloud-data` | app-eigene PostgreSQL unter `/mnt/user/appdata/nextcloud/postgres` | `nextcloud_admin_user.txt`, `nextcloud_admin_password.txt`, `nextcloud_postgres_password.txt` | `nextcloud-postgres`, `nextcloud-redis`, Traefik | Web-UI startet, Login funktioniert, Dateien sichtbar | | Nextcloud | Borg + Dump | `/mnt/user/appdata/nextcloud/html`, `/mnt/user/documents/nextcloud-data` | `nextcloud.dump` | `nextcloud_admin_user.txt`, `nextcloud_admin_password.txt`, `nextcloud_postgres_password.txt` | `nextcloud-postgres`, `nextcloud-redis`, Traefik | Web-UI startet, Login funktioniert, Dateien sichtbar |
| Homepage | Borg / Share | `/mnt/user/appdata/homepage` | keine | `HOMEPAGE_VAR_*` | Traefik, Authelia | Dashboard startet, Widgets laden | | Glance | Git / Borg-Repo | Repo-Konfiguration unter `ops/glance/config/glance.yml`; keine kritische Datenpersistenz | keine | `GLANCE_IMMICH_API_KEY`, `GLANCE_ADGUARD_USERNAME`, `GLANCE_ADGUARD_PASSWORD`, `GLANCE_SPEEDTEST_API_KEY` | Traefik, Authelia, optional interne API-Ziele | Dashboard startet, Widgets laden, Docker-Status laeuft nur ueber `glance-docker-socket-proxy` |
| ntfy | Borg / Share | `/mnt/user/appdata/ntfy` | keine | keine besonderen Secret-Dateien dokumentiert | Traefik | UI und Push-Endpunkt erreichbar | | ntfy | Borg / Share | `/mnt/user/appdata/ntfy` | keine | keine besonderen Secret-Dateien dokumentiert | Traefik | UI und Push-Endpunkt erreichbar |
| Paperless-GPT | Borg / Share | `/mnt/user/appdata/paperless-gpt` | keine eigene DB | `PAPERLESS_API_TOKEN` | Traefik, Paperless | UI startet, Konfiguration vorhanden | | Paperless-GPT | Borg / Share | `/mnt/user/appdata/paperless-gpt` | keine eigene DB | `PAPERLESS_API_TOKEN` | Traefik, Paperless | UI startet, Konfiguration vorhanden |
@@ -57,16 +59,17 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
| Dienst | Fuehrende Quelle | Datei-Restore | Dump / DB | Secrets / ENV | Abhaengigkeiten | Smoke-Test | | Dienst | Fuehrende Quelle | Datei-Restore | Dump / DB | Secrets / ENV | Abhaengigkeiten | Smoke-Test |
|---|---|---|---|---|---|---| |---|---|---|---|---|---|---|
| Borg UI | Borg / Share | `/mnt/user/appdata/borg-ui/data` | keine eigene DB | Borg-Repo-Creds in `/data` | Traefik | UI startet, Repo-Verbindung bekannt | | Borg UI | Borg + Dump | `/mnt/user/appdata/borg-ui/data` | `borg-ui.sqlite` | Borg-Repo-Creds in `/data` | Traefik | UI startet, Repo-Verbindung bekannt |
| Backrest | Share | `/mnt/user/appdata/backrest/*` | keine | SSH-/Repo-Creds im Mount | Traefik | UI startet | | Filebrowser | Share / Fresh + Dump | `/mnt/user/appdata/filebrowser` | `filebrowser.bolt.dump` | `filebrowser_admin_password.txt` bei Fresh-Rebuild | Traefik, Authelia | UI startet, Admin-User vorhanden |
| Uptime Kuma | Share | `/mnt/user/appdata/uptime-kuma` | keine | keine besonderen Secret-Dateien dokumentiert | Traefik, Authelia | Monitore vorhanden |
| Filebrowser | Share | `/mnt/user/appdata/filebrowser` | keine | keine separaten Secret-Dateien dokumentiert | Traefik, Authelia | UI startet |
| Glances | Rebuildbar | kein kritischer Zustand | keine | keine | Traefik, Authelia | UI startet | | Glances | Rebuildbar | kein kritischer Zustand | keine | keine | Traefik, Authelia | UI startet |
| Scrutiny | Teilweise rebuildbar | `/mnt/user/appdata/scrutiny` falls gewuenscht | InfluxDB bewusst nicht Teil des Critical-Scope | keine | Traefik, Authelia | UI startet, Laufwerke sichtbar | | Scrutiny | Teilweise rebuildbar | `/mnt/user/appdata/scrutiny` falls gewuenscht | InfluxDB bewusst nicht Teil des Critical-Scope | keine | Traefik, Authelia | UI startet, Laufwerke sichtbar |
| Speedtest Tracker | Share | `/mnt/user/appdata/speedtest-tracker/config` | SQLite im App-Pfad | `APP_KEY`, `ADMIN_PASSWORD` | Traefik, Authelia | UI startet | | Speedtest Tracker | Share + Dump | `/mnt/user/appdata/speedtest-tracker/config` | `speedtest-tracker.sqlite.dump` | `APP_KEY`, `ADMIN_PASSWORD` | Traefik, Authelia | UI startet |
| BentoPDF | Rebuildbar | keine kritische Persistenz; alte Stirling-PDF-Daten unter `/mnt/user/appdata/stirling-pdf` bis zur Abnahme behalten | keine | keine separaten Secret-Dateien dokumentiert | Traefik, Authelia | UI startet, PDF-Tools verfuegbar, Office-Konvertierung ueber HTTPS funktioniert | | BentoPDF | Rebuildbar | keine kritische Persistenz; alte Stirling-PDF-Daten unter `/mnt/user/appdata/stirling-pdf` bis zur Abnahme behalten | keine | keine separaten Secret-Dateien dokumentiert | Traefik, Authelia | UI startet, PDF-Tools verfuegbar, Office-Konvertierung ueber HTTPS funktioniert |
| Grafana | Share | `/mnt/user/appdata/grafana`, inklusive `provisioning/datasources/influxdb.yml` | SQLite im App-Pfad | `grafana_admin_password.txt`, `grafana_influxdb_token.txt` | Traefik, Authelia, InfluxDB 3 Core | UI startet, InfluxDB-Datenquelle testet erfolgreich | | Grafana | historischer Altstand | `/mnt/user/appdata/grafana` | `grafana.sqlite` | `grafana_admin_password.txt`, `grafana_influxdb_token.txt` | nicht aktiv | Compose-Pfad aus aktivem Repo entfernt; nur ueber Git-Historie wiederherstellen, falls ein Rollback wirklich noetig ist |
| InfluxDB 3 Core | Share | `/mnt/user/appdata/influxdb3/data`, `/mnt/user/appdata/influxdb3/plugins` | dateibasierter Object Store | `influxdb3_admin_token.json` | internes `grafana_influx_internal` Netz | `homelab`-Datenbank vorhanden, Grafana kann SQL-Abfrage ausfuehren | | InfluxDB 3 Core | historischer Altstand / Datenuebernahme | `/mnt/user/appdata/influxdb3/data`, `/mnt/user/appdata/influxdb3/plugins` | dateibasierter Object Store | `influxdb3_admin_token.json` | `monitoring-influxdb3-core` | Datenpfad wird vom Monitoring-Zielstack weitergenutzt und darf nicht blind geloescht werden |
| Loki / Alloy | historischer Altstand | `/mnt/user/appdata/loki/config`, `/mnt/user/appdata/loki/data`, `/mnt/user/appdata/alloy/config` | keine primaere DB; Loki-Dateispeicher war transient | keine zusaetzlichen Secrets | nicht aktiv | Compose-Pfad aus aktivem Repo entfernt; aktuelle Logsammlung laeuft ueber `monitoring-loki`/`monitoring-promtail` |
| Monitoring Stack | Rebuild + named volumes + InfluxDB-Appdata | `prometheus_data`, `loki_data`, `promtail_positions`, `grafana_data`; InfluxDB unter `/mnt/user/appdata/influxdb3/data` und `/mnt/user/appdata/influxdb3/plugins`; Provisioning aus `monitoring/grafana/provisioning` | Prometheus-TSDB, Loki-Dateispeicher und InfluxDB-Dateistore; Diagnose-/Langzeitdaten, keine Tier-1-Restore-Quelle | `monitoring_grafana_admin_password.txt`, `monitoring_grafana_influxdb_token.txt`, `influxdb3_admin_token.json` | `monitoring_net`, `monitoring_influx_lan`, `frontend_net`, Traefik, Authelia, Docker socket read-only fuer Promtail, Host-Mounts fuer node-exporter/cAdvisor | `https://monitoring.kaleschke.info` leitet zu Authelia; Prometheus Targets sind up; Grafana-Datasources `Prometheus`, `Loki` und `InfluxDB 3 Core` funktionieren |
| Hermes Agent | VM-seitig offen | `/mnt/user/appdata/hermes-agent/data`, `/mnt/user/appdata/hermes-agent/ssh` | keine eigene DB | Host-`.env` fuer Provider-/API-/Home-Assistant-Tokens, `hermes_runner_id_ed25519`, `HERMES_DASHBOARD_HOST` | separate Hermes-VM/Runner, Traefik, Authelia, `hermes_net` | NAS-Stack nicht starten, solange Runner-VM und echte `.env` fehlen |
| ddns-updater | Rebuildbar | geringe Persistenzrelevanz | keine | Provider-Zugang ueber Stack ENV | Internetzugang | Update-Job laeuft | | ddns-updater | Rebuildbar | geringe Persistenzrelevanz | keine | Provider-Zugang ueber Stack ENV | Internetzugang | Update-Job laeuft |
--- ---
@@ -81,6 +84,15 @@ Aktuell relevante Dump-Artefakte unter `/mnt/user/backups/borg/dumps/latest`:
- `postgresql17-authelia.dump` (optional / wenn vorhanden) - `postgresql17-authelia.dump` (optional / wenn vorhanden)
- `mealie.dump` - `mealie.dump`
- `immich.dump` - `immich.dump`
- `nextcloud.dump`
- `gitea.sqlite.dump`
- `vaultwarden.sqlite.dump`
- `speedtest-tracker.sqlite.dump`
- `filebrowser.bolt.dump`
- `borg-ui.sqlite`
- `grafana.sqlite`
- `unraid-flash-config.tar.gz` plus `unraid-flash-config.tar.gz.sha256` und Manifest
- Monitoring-Stack: keine verpflichtenden Dump-Artefakte; Prometheus/Loki/Grafana named volumes sind Diagnose-/Dashboard-Zustand, keine primaere Restore-Quelle.
- `komodo-mongo.archive.gz` (noch gesondert verifizieren) - `komodo-mongo.archive.gz` (noch gesondert verifizieren)
Die Dump-Erzeugung ist host-seitig ueber `ops/borg-ui/scripts/pre-backup-dumps.sh` vorgesehen. Die Dump-Erzeugung ist host-seitig ueber `ops/borg-ui/scripts/pre-backup-dumps.sh` vorgesehen.
@@ -94,14 +106,24 @@ Die Dump-Erzeugung ist host-seitig ueber `ops/borg-ui/scripts/pre-backup-dumps.s
- Bei Unsicherheit zuerst in Testpfade oder Testinstanzen pruefen. - Bei Unsicherheit zuerst in Testpfade oder Testinstanzen pruefen.
- Der minimale Erfolg ist **nicht** "Container startet", sondern "fachlicher Smoke-Test gelingt". - Der minimale Erfolg ist **nicht** "Container startet", sondern "fachlicher Smoke-Test gelingt".
### Validiertes Restore-Lab-Muster
- Restore-Quelle bleibt das produktive Borg-Repo
- Testziel liegt unter `/mnt/user/backups/restore-lab/<dienst>`
- Reports liegen unter `/mnt/user/backups/restore-reports`
- Borg-Zugriff kann ueber den vorhandenen `borg-ui`-Container laufen
- Borg-Passphrasen fuer Restore-Tests sollten aus Host-Secret-Dateien kommen, nicht aus UI-Interna
- Testinstanzen bekommen keine produktive Domain und keine Traefik-Route
--- ---
## Erste sinnvolle Referenz-Restores ## Erste sinnvolle Referenz-Restores
Wenn weitere Restore-Uebungen dokumentiert werden sollen, sind diese Dienste besonders geeignet: Wenn weitere Restore-Uebungen dokumentiert werden sollen, sind diese Dienste besonders geeignet:
1. `gitea` 1. `mail-archiver`
2. `paperless-ngx` 2. `paperless-ngx`
3. `vaultwarden` 3. `gitea`
4. `vaultwarden`
Sie liefern hohen Erkenntnisgewinn ohne den kompletten Homelab-Neuaufbau zu brauchen. Sie liefern hohen Erkenntnisgewinn ohne den kompletten Homelab-Neuaufbau zu brauchen.
+33 -1
View File
@@ -89,11 +89,43 @@ Vor dem ersten produktiven Einsatz reicht es, den vorbereiteten Stack nicht zu d
Nach einem Deploy: Nach einem Deploy:
1. `ops/grafana-influxdb` in Komodo stoppen oder den letzten Git-Stand ohne diesen Stack deployen 1. alten Grafana/InfluxDB-Stack in Komodo gestoppt lassen; der fruehere Compose-Pfad `ops/grafana-influxdb` ist seit 2026-05-26 nicht mehr im aktiven Repo
2. Persistenz unter `/mnt/user/appdata/grafana` und `/mnt/user/appdata/influxdb3` unangetastet lassen 2. Persistenz unter `/mnt/user/appdata/grafana` und `/mnt/user/appdata/influxdb3` unangetastet lassen
3. Secrets unter `/mnt/user/appdata/secrets/grafana_admin_password.txt`, `/mnt/user/appdata/secrets/grafana_influxdb_token.txt` und `/mnt/user/appdata/secrets/influxdb3_admin_token.json` nur nach bewusstem Entscheid entfernen 3. Secrets unter `/mnt/user/appdata/secrets/grafana_admin_password.txt`, `/mnt/user/appdata/secrets/grafana_influxdb_token.txt` und `/mnt/user/appdata/secrets/influxdb3_admin_token.json` nur nach bewusstem Entscheid entfernen
4. Grafana-Domain und InfluxDB-Zugriff testen, bis klar ist, dass keine produktiven Dashboards oder Writer mehr davon abhaengen 4. Grafana-Domain und InfluxDB-Zugriff testen, bis klar ist, dass keine produktiven Dashboards oder Writer mehr davon abhaengen
## Monitoring-Zielstack Rollback
Der Zielzustand ist `monitoring/` als einziger Observability-Stack. Bei Problemen nach der Migration:
1. `monitoring` in Komodo stoppen oder auf den letzten funktionierenden Commit zurueckgehen
2. nur im echten Notfall die abgeloesten Altstaende aus der Git-Historie vor dem Repo-Cleanup wiederherstellen, z. B. aus Commit `ff5991c`; nicht dauerhaft parallel zum Zielstack betreiben
3. named volumes `prometheus_data`, `loki_data`, `promtail_positions`, `grafana_data` sowie `/mnt/user/appdata/influxdb3` nicht blind loeschen
4. Secrets `monitoring_grafana_admin_password.txt`, `monitoring_grafana_influxdb_token.txt` und `influxdb3_admin_token.json` nur nach bewusstem Entscheid entfernen
5. Home Assistant Writer erst wieder umstellen, wenn `curl -i http://192.168.178.58:8181/` erwartbar `401 Unauthorized` liefert
6. Grafana-Datasources `Prometheus`, `Loki` und `InfluxDB 3 Core` testen
## Uptime Kuma Removal Rollback
Falls die Blackbox-/Grafana-Ablösung unerwartet nicht ausreicht:
1. per Ruecknahme-Commit `ops/uptime-kuma/docker-compose.yml`, die Blackbox-/Glance-/Authelia-Referenzen und die Restore-Freshness-Pruefung auf den letzten Uptime-Kuma-Stand zurueckbringen
2. nach Gitea pushen und den Uptime-Kuma-Stack in Komodo neu anlegen oder aus dem letzten Stack-Backup wiederherstellen
3. `/mnt/user/appdata/_archive/uptime-kuma-removed-2026-05-25` nach `/mnt/user/appdata/uptime-kuma` zurueckverschieben, falls die Archivierung bereits erfolgt ist
4. `https://uptime.kaleschke.info` und die Monitore pruefen
5. erst danach den Blackbox-/Grafana-Zielzustand erneut bewerten
## Glance Dashboard Rollback
Vor dem ersten produktiven Einsatz reicht es, den vorbereiteten Stack `ops/glance` nicht zu deployen oder per Ruecknahme-Commit aus dem Repo zu entfernen.
Nach einem Deploy:
1. `glance` in Komodo stoppen oder auf den letzten funktionierenden Commit zurueckgehen
2. keine Produktivdaten loeschen; Glance nutzt nur Repo-Konfiguration und Stack-ENV
3. pruefen, ob `https://glance.kaleschke.info` nicht mehr geroutet wird oder wieder den erwarteten Stand zeigt
4. der `glance-docker-socket-proxy` darf nicht separat als Dauercontainer laufen bleiben
--- ---
## Daten-Rollback ## Daten-Rollback
+24 -11
View File
@@ -20,32 +20,38 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
| Traefik | Cloudflare DNS API Token | `/mnt/user/appdata/traefik/secrets/cloudflare_dns_api_token` -> Docker Secret `cloudflare_dns_api_token` | aktiv | | Traefik | Cloudflare DNS API Token | `/mnt/user/appdata/traefik/secrets/cloudflare_dns_api_token` -> Docker Secret `cloudflare_dns_api_token` | aktiv |
| PostgreSQL 17 | DB Password | `/mnt/user/appdata/secrets/postgres_password.txt` -> `POSTGRES_PASSWORD_FILE` | aktiv | | PostgreSQL 17 | DB Password | `/mnt/user/appdata/secrets/postgres_password.txt` -> `POSTGRES_PASSWORD_FILE` | aktiv |
| Redis | Passwort | `/mnt/user/appdata/secrets/redis_password.txt` -> Datei-Mount + Startkommando in `infra/redis/docker-compose.yml` | aktiv | | Redis | Passwort | `/mnt/user/appdata/secrets/redis_password.txt` -> Datei-Mount + Startkommando in `infra/redis/docker-compose.yml` | aktiv |
| Mealie | DB Password | `/mnt/user/appdata/secrets/mealie_postgres_password.txt` -> `POSTGRES_PASSWORD_FILE` | aktiv | | Mealie | DB Password | `/mnt/user/appdata/secrets/mealie_postgres_password.txt` -> nicht versionierte Stack-`.env` `${MEALIE_POSTGRES_PASSWORD}` -> `POSTGRES_PASSWORD` | aktiv |
| mealie-postgres | DB Password | `/mnt/user/appdata/secrets/mealie_postgres_password.txt` -> `POSTGRES_PASSWORD_FILE` | aktiv | | mealie-postgres | DB Password | `/mnt/user/appdata/secrets/mealie_postgres_password.txt` -> `POSTGRES_PASSWORD_FILE` | aktiv |
| Paperless-ngx | DB Password | Stack ENV `${PAPERLESS_DBPASS}` | aktiv | | Paperless-ngx | DB Password | Stack ENV `${PAPERLESS_DBPASS}` | aktiv |
| Paperless-ngx | Redis URL | Stack ENV `${PAPERLESS_REDIS}` | aktiv | | Paperless-ngx | Redis URL | Stack ENV `${PAPERLESS_REDIS}` | aktiv |
| code-server | Passwort | `/mnt/user/appdata/code-server/secrets/password` -> `PASSWORD_FILE` | aktiv | | code-server | Passwort | `/mnt/user/appdata/code-server/secrets/password` -> `FILE__PASSWORD` | aktiv |
| Filebrowser | Admin Password | `/mnt/user/appdata/secrets/filebrowser_admin_password.txt` -> initialisierte SQLite-DB | aktiv |
| Immich (server) | DB Password | Stack ENV `${IMMICH_DB_PASSWORD}` | aktiv | | Immich (server) | DB Password | Stack ENV `${IMMICH_DB_PASSWORD}` | aktiv |
| immich-postgres | DB Password | `/mnt/user/appdata/secrets/immich_postgres_password.txt` -> `POSTGRES_PASSWORD_FILE` | aktiv | | immich-postgres | DB Password | `/mnt/user/appdata/secrets/immich_postgres_password.txt` -> `POSTGRES_PASSWORD_FILE` | aktiv |
| mail-archiver | DB Connection | Stack ENV `${MAILARCHIVER_DB_CONNECTION}` | aktiv | | mail-archiver | DB Connection | Stack ENV `${MAILARCHIVER_DB_CONNECTION}` | aktiv |
| mail-archiver | Auth Password | Stack ENV `${MAILARCHIVER_AUTH_PASSWORD}` | aktiv | | mail-archiver | Auth Password | Stack ENV `${MAILARCHIVER_AUTH_PASSWORD}` | aktiv |
| Authelia | JWT Secret | `/mnt/user/appdata/secrets/authelia_jwt_secret.txt` -> `AUTHELIA_JWT_SECRET_FILE` | aktiv | | Authelia | JWT Secret | `/mnt/user/appdata/secrets/authelia_jwt_secret.txt` -> `AUTHELIA_JWT_SECRET_FILE` | aktiv |
| Authelia | Session Secret | `/mnt/user/appdata/secrets/authelia_session_secret.txt` -> `AUTHELIA_SESSION_SECRET_FILE` | aktiv | | Authelia | Session Secret | `/mnt/user/appdata/secrets/authelia_session_secret.txt` -> `AUTHELIA_SESSION_SECRET_FILE` | aktiv |
| Authelia | SMTP Password | `/mnt/user/appdata/secrets/authelia_smtp_password.txt` -> Host-Secret fuer SMTP-Notifier | aktiv |
| Authelia | Storage Encryption Key | `/mnt/user/appdata/secrets/authelia_storage_encryption_key.txt` -> `AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE` | aktiv | | Authelia | Storage Encryption Key | `/mnt/user/appdata/secrets/authelia_storage_encryption_key.txt` -> `AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE` | aktiv |
| Authelia | Postgres Password | `/mnt/user/appdata/secrets/authelia_postgres_password.txt` -> `AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE` | aktiv | | Authelia | Postgres Password | `/mnt/user/appdata/secrets/authelia_postgres_password.txt` -> `AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE` | aktiv |
| Komodo Mongo | Root Password | `/mnt/user/appdata/secrets/komodo_mongo_password.txt` -> `MONGO_INITDB_ROOT_PASSWORD_FILE` | aktiv | | Komodo Mongo | Root Password | `/mnt/user/appdata/secrets/komodo_mongo_password.txt` -> `MONGO_INITDB_ROOT_PASSWORD_FILE` | aktiv |
| Komodo Core | App Secrets | Stack ENV `${KOMODO_SECRET_KEY}`, `${KOMODO_JWT_SECRET}`, `${KOMODO_MONGO_PASSWORD}`, `${KOMODO_PERIPHERY_PASSKEY}` | aktiv | | Komodo Core | App Secrets | Stack ENV `${KOMODO_SECRET_KEY}`, `${KOMODO_WEBHOOK_SECRET}`, `${KOMODO_JWT_SECRET}`, `${KOMODO_MONGO_PASSWORD}`, `${KOMODO_PERIPHERY_PASSKEY}` | aktiv |
| Homepage | API Tokens / Zugangsdaten | Stack ENV `HOMEPAGE_VAR_*` | aktiv | | Gitea Push Mirror | GitHub fine-grained PAT fuer `michaelkaleschke-spec/homelab-infra` | Gitea Repository Mirror Settings, persistent in `/mnt/user/services/gitea/data`; kein Datei-Secret im Repo | aktiv |
| Glance | Community Widget API Tokens | Stack ENV `${GLANCE_IMMICH_API_KEY}`, `${GLANCE_ADGUARD_USERNAME}`, `${GLANCE_ADGUARD_PASSWORD}`, `${GLANCE_SPEEDTEST_API_KEY}` | aktiv |
| speedtest-tracker | App Key / Admin-Zugang | Stack ENV `${APP_KEY}`, `${ADMIN_PASSWORD}` | aktiv | | speedtest-tracker | App Key / Admin-Zugang | Stack ENV `${APP_KEY}`, `${ADMIN_PASSWORD}` | aktiv |
| Nextcloud | Admin User | `/mnt/user/appdata/secrets/nextcloud_admin_user.txt` -> `NEXTCLOUD_ADMIN_USER_FILE` | neu | | Nextcloud | Admin User | `/mnt/user/appdata/secrets/nextcloud_admin_user.txt` -> `NEXTCLOUD_ADMIN_USER_FILE` | neu |
| Nextcloud | Admin Password | `/mnt/user/appdata/secrets/nextcloud_admin_password.txt` -> `NEXTCLOUD_ADMIN_PASSWORD_FILE` | neu | | Nextcloud | Admin Password | `/mnt/user/appdata/secrets/nextcloud_admin_password.txt` -> `NEXTCLOUD_ADMIN_PASSWORD_FILE` | neu |
| nextcloud-postgres | DB Password | `/mnt/user/appdata/secrets/nextcloud_postgres_password.txt` -> `POSTGRES_PASSWORD_FILE` | neu | | nextcloud-postgres | DB Password | `/mnt/user/appdata/secrets/nextcloud_postgres_password.txt` -> `POSTGRES_PASSWORD_FILE` | neu |
| Borg UI / Borg | Admin-Login, `SECRET_KEY`, SSH-Keys, Repo-Credentials | persistent unter `/mnt/user/appdata/borg-ui/data/` | aktiv | | Borg UI / Borg | Admin-Login, `SECRET_KEY`, SSH-Keys, Repo-Credentials | persistent unter `/mnt/user/appdata/borg-ui/data/` | aktiv |
| Hermes Agent | Provider-Keys, Bot-Tokens, API-Server-Key | `/mnt/user/appdata/hermes-agent/data/.env` | neu | | Borg Repo | Borg-Passphrase fuer Restore-Tests und Notfallzugriff | `/mnt/user/appdata/secrets/borg_repo_passphrase.txt` -> Host-Secret-Datei, nicht im Repo | aktiv |
| Hermes Agent | SSH-Runner Private Key | `/mnt/user/appdata/secrets/hermes_runner_id_ed25519` -> `/root/.ssh/id_ed25519` | neu | | Unraid Flash Backup | Boot-/Array-/Share-/Plugin-Konfiguration, ggf. Hashes/Keys/Templates | `/mnt/user/backups/borg/dumps/latest/unraid-flash-config.tar.gz`, via Borg/Hetzner gesichert | aktiv; wie Secret-Material behandeln |
| Grafana | Admin Password | `/mnt/user/appdata/secrets/grafana_admin_password.txt` -> `GF_SECURITY_ADMIN_PASSWORD__FILE` | vorbereitet | | Hermes Agent | Provider-Keys, Bot-Tokens, API-Server-Key | `/mnt/user/appdata/hermes-agent/data/.env` | VM-seitig offen |
| InfluxDB 3 Core | Admin Token JSON | `/mnt/user/appdata/secrets/influxdb3_admin_token.json` -> Docker Secret `/run/secrets/influxdb3_admin_token` | vorbereitet | | Hermes Agent | SSH-Runner Private Key | `/mnt/user/appdata/secrets/hermes_runner_id_ed25519` -> `/root/.ssh/id_ed25519` | VM-seitig offen |
| Grafana -> InfluxDB | Datasource Token | `/mnt/user/appdata/secrets/grafana_influxdb_token.txt` -> Docker Secret `/run/secrets/grafana_influxdb_token` | vorbereitet | | InfluxDB 3 Core | Admin Token JSON | `/mnt/user/appdata/secrets/influxdb3_admin_token.json` -> Docker Secret `/run/secrets/influxdb3_admin_token` | aktiv |
| Monitoring Grafana | Admin Password | `/mnt/user/appdata/secrets/monitoring_grafana_admin_password.txt` -> Docker Secret `/run/secrets/monitoring_grafana_admin_password` -> `GF_SECURITY_ADMIN_PASSWORD__FILE` | aktiv |
| Monitoring Grafana -> InfluxDB | Datasource Token | `/mnt/user/appdata/secrets/monitoring_grafana_influxdb_token.txt` -> Docker Secret `/run/secrets/monitoring_grafana_influxdb_token` | aktiv |
| Home Assistant -> InfluxDB | HA InfluxDB Token | `/homeassistant/secrets.yaml` -> `influxdb3_homeassistant_token` | geplant |
--- ---
@@ -55,6 +61,8 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
|---|---|---| |---|---|---|
| Gotify | `gotify_password.txt` / `GOTIFY_DEFAULTUSER_PASS_FILE` | Dienst nicht mehr aktiv | | Gotify | `gotify_password.txt` / `GOTIFY_DEFAULTUSER_PASS_FILE` | Dienst nicht mehr aktiv |
| diun | Stack ENV | Container entfernt | | diun | Stack ENV | Container entfernt |
| Uptime Kuma | `uptime_kuma_admin_password.txt` | Dienst am 2026-05-25 entfernt; nur fuer Alt-Appdata/Archiv behalten |
| Grafana Altstand | `grafana_admin_password.txt`, `grafana_influxdb_token.txt` | Compose-Pfad aus aktivem Repo entfernt; nur fuer Git-Historie-/Rollback-Fall behalten |
--- ---
@@ -65,18 +73,21 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
|-- authelia_jwt_secret.txt |-- authelia_jwt_secret.txt
|-- authelia_postgres_password.txt |-- authelia_postgres_password.txt
|-- authelia_session_secret.txt |-- authelia_session_secret.txt
|-- authelia_smtp_password.txt
|-- authelia_storage_encryption_key.txt |-- authelia_storage_encryption_key.txt
|-- immich_postgres_password.txt |-- immich_postgres_password.txt
|-- komodo_mongo_password.txt |-- komodo_mongo_password.txt
|-- mealie_postgres_password.txt |-- mealie_postgres_password.txt
|-- monitoring_grafana_admin_password.txt
|-- monitoring_grafana_influxdb_token.txt
|-- nextcloud_admin_password.txt |-- nextcloud_admin_password.txt
|-- nextcloud_admin_user.txt |-- nextcloud_admin_user.txt
|-- nextcloud_postgres_password.txt |-- nextcloud_postgres_password.txt
|-- postgres_password.txt |-- postgres_password.txt
|-- redis_password.txt |-- redis_password.txt
|-- grafana_admin_password.txt |-- borg_repo_passphrase.txt
|-- grafana_influxdb_token.txt
|-- influxdb3_admin_token.json |-- influxdb3_admin_token.json
|-- filebrowser_admin_password.txt
`-- vaultwarden_admin_token.txt `-- vaultwarden_admin_token.txt
``` ```
@@ -86,6 +97,8 @@ Weitere dokumentierte Secret-Pfade:
- `/mnt/user/appdata/secrets/hermes_runner_id_ed25519` - `/mnt/user/appdata/secrets/hermes_runner_id_ed25519`
- `/mnt/user/appdata/traefik/secrets/cloudflare_dns_api_token` - `/mnt/user/appdata/traefik/secrets/cloudflare_dns_api_token`
- Borg UI verwaltet Session-Secret, Admin-Login, SSH-Keys und Repo-Credentials in seiner persistenten `/data`-Struktur. Diese Daten liegen nicht im Git, muessen aber gesichert werden. - Borg UI verwaltet Session-Secret, Admin-Login, SSH-Keys und Repo-Credentials in seiner persistenten `/data`-Struktur. Diese Daten liegen nicht im Git, muessen aber gesichert werden.
- Die Borg-Repo-Passphrase liegt zusaetzlich als Host-Secret-Datei fuer Restore-Tests und Notfallzugriff vor; der Wert muss ausserhalb des Homelabs analog gesichert werden.
- Gitea verwaltet den GitHub-Push-Mirror-PAT in den Repository-Mirror-Settings. Der Wert wird nicht dokumentiert und nicht in Dateien unter `docs/` oder `core/gitea/` geschrieben.
- `paperless-ngx` ist eine bewusste Ausnahme: DB-Passwort und Redis-URL bleiben aktuell als Komodo Stack Environment Variables hinterlegt, um den stabil laufenden Produktionsstand nicht fuer eine reine Secret-Mechanik-Migration zu riskieren. - `paperless-ngx` ist eine bewusste Ausnahme: DB-Passwort und Redis-URL bleiben aktuell als Komodo Stack Environment Variables hinterlegt, um den stabil laufenden Produktionsstand nicht fuer eine reine Secret-Mechanik-Migration zu riskieren.
--- ---
+100
View File
@@ -0,0 +1,100 @@
# Services Recovery - KalliLab CORE
Status: Initiale Recovery-Baseline 2026-05-26, aus dem Audit 2026-05-25 abgeleitet.
Verwandte Docs: `docs/DISASTER_RECOVERY.md`, `docs/RESTORE_MATRIX.md`, `docs/STORAGE_LAYOUT.draft.md`, `docs/SECRETS_MAP.md`
## Zweck
Der Share `/mnt/user/services/` ist recovery-kritisch, weil dort GitOps- und Host-Automation-Pfade liegen. Dieses Dokument beschreibt, welche Services-Pfade gesichert werden muessen und wie ein Kaltstart ohne laufendes Komodo gedacht ist.
## Kritische Pfade
| Pfad | Zweck | Kritikalitaet | Backup-Anforderung |
|---|---|---:|---|
| `/mnt/user/services/homelab-infra` | Host-Repo-Clone fuer Automation/Posture | hoch | Borg + GitHub/Gitea Mirror |
| `/mnt/user/services/stacks` | Komodo Stack Workspaces | hoch | Borg, vor strukturellen Aenderungen extra sichern |
| `/mnt/user/services/gitea/git/repositories` | Gitea Repository-Inhalte | kritisch | Borg + separater Mirror <= 6 h |
| `/mnt/user/services/posture-check` | Hostseitig ausgefuehrte Checks | hoch | Borg + Repo-Abgleich |
| `/mnt/user/appdata/secrets` | Runtime Secrets | kritisch | Borg + ausgewählte analoge/off-system Kopien |
## Gitea Repository Mirror
Ziel: Verlustfenster fuer `/mnt/user/services/gitea/git/repositories/` auf maximal 6 Stunden begrenzen.
Optionen:
| Option | Bewertung |
|---|---|
| `git bundle` je Repository auf zweites Medium | Sehr gut fuer Git-Recovery, transparent, gut pruefbar |
| `rsync` auf externe Platte oder zweiten Host | Einfach, aber Ziel muss regelmaessig erreichbar und geprueft sein |
| Separates Borg-Repo mit kurzem Schedule | Konsistent zum bestehenden Backup, aber wieder Borg-/Passphrase-abhaengig |
Empfohlener Start:
1. `git bundle`-Job fuer alle Gitea-Repositories definieren.
2. Ziel auf zweitem physischen Medium oder separatem Off-site-Ziel ablegen.
3. Job alle 6 Stunden ausfuehren.
4. Stichprobe: ein Bundle in Wegwerfpfad klonen.
Erfolgskriterium:
```bash
git clone /path/to/repo.bundle /tmp/repo-restore-test
git -C /tmp/repo-restore-test fsck
```
## Komodo Bootstrap
Problem: Komodo verwaltet Stacks, ist aber selbst Teil des Recovery-Pfads. Ein kalter Host darf nicht voraussetzen, dass Komodo schon laeuft.
Komodo ist deshalb bewusst kein normaler Auto-Deploy-Stack: der `komodo`-Self-Stack hat keinen aktiven Gitea-Webhook. Recovery und Aenderungen laufen ueber den dokumentierten Bootstrap-Pfad und muessen nach dem Start in Komodo validiert werden.
Minimaler Wiederanlauf:
1. Docker und externe Netze herstellen (`frontend_net`, `backend_net`, ggf. weitere dokumentierte Netze).
2. Repo aus Gitea/GitHub Mirror klonen.
3. Komodo Compose aus `ops/komodo/docker-compose.yml` oder einem spaeteren Bootstrap-Pfad starten.
4. Erforderliche `.env`/Secrets aus Host-Secret-Backup wiederherstellen.
5. Komodo-Core, Periphery und Mongo starten.
6. Web-UI und Periphery-Verbindung pruefen.
Offene Aufgabe:
- Entscheidung 2026-05-26: `ops/komodo/docker-compose.yml` bleibt die verbindliche Bootstrap-Quelle. Der `komodo`-Self-Stack hat keinen aktiven Gitea-Webhook und ist nicht der Recovery-Anker.
- Offen bleibt nur ein spaeterer Trockenlauf, bei dem die Komodo-Startkommandos gegen echte Restore-Pfade getestet werden.
Validierung:
```bash
docker compose -f ops/komodo/docker-compose.yml config
docker compose -f ops/komodo/docker-compose.yml up -d
docker ps --filter "name=komodo"
```
## Secrets Recovery Reihenfolge
Authoritativ ist `docs/SECRETS_MAP.md`. Fuer den Kaltstart ist diese Reihenfolge praktisch:
1. Borg-Passphrase analog/off-system beschaffen.
2. Borg-Repo-Zugang/SSH-Key wiederherstellen.
3. `/mnt/user/appdata/secrets/` aus Borg wiederherstellen.
4. Komodo Stack ENV / Recovery ENV wiederherstellen.
5. Gitea Secrets und SSH-Material wiederherstellen.
6. Traefik/Cloudflare Secret wiederherstellen.
7. App-spezifische Secrets nach Tier-Reihenfolge wiederherstellen.
## Break-glass Regeln
- Keine Secret-Werte in Git oder Tickets kopieren.
- Restore-Tests laufen in Wegwerfpfaden, nie direkt gegen produktive Pfade.
- Wenn Gitea und Komodo beide down sind, gewinnt der externe GitHub-Mirror als Repo-Quelle.
- Wenn Borg ohne Passphrase nicht entschluesselbar ist, ist Recovery blockiert. Deshalb ist die analoge Passphrase-Sicherung P0.
## Naechste Aufgaben
| Status | Aufgabe |
|---|---|
| offen | Gitea-Bundle- oder Mirror-Mechanik final entscheiden |
| erledigt | Komodo-Bootstrap-Quelle finalisieren |
| offen | Restore-Kommandos nach erstem Trockenlauf mit echten Pfaden ergaenzen |
| erledigt | Services-Recovery in `docs/DISASTER_RECOVERY.md` verlinken |
+109
View File
@@ -0,0 +1,109 @@
# Service Catalog
Stand: 2026-05-23
Dieser Katalog beschreibt produktive und repo-vorbereitete Dienste aus Sicht von Betrieb, Restore und KI-Kontext. Er basiert auf dem Repo-Sollzustand. Vor produktiven Eingriffen immer den Live-Zustand in Komodo/Docker pruefen.
Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und Pfade genannt.
## Core / Ingress / DNS
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|---|---|---|---|---|---|---|---|---|
| `traefik` | zentraler Reverse Proxy, TLS, Docker-Label-Routing | `traefik/docker-compose.yml`, `traefik/dynamic/*` | `https://traefik.kaleschke.info` | Docker socket, Cloudflare DNS API, `frontend_net`, `backend_net` | `/mnt/user/appdata/traefik/dynamic`, `/mnt/user/appdata/traefik/letsencrypt` | Tier 1, Share/Borg | ja, eigene Dashboard-Route mit Authelia | Host-Ports 80/443 sind zentrale Ausnahme; dynamic configs werden nicht automatisch von Komodo deployed |
| `adguard` | DNS-Server / LAN DNS | `host-services/Adguard/docker-compose.yml` | LAN-Port `53`, Admin `100.80.98.33:8082` | `dns_net`, `frontend_net`, Unbound | `/mnt/user/appdata/adguard/conf`, `/mnt/user/appdata/adguard/work` | Tier 1, config relevant | nein | Direkter DNS-Port 53 bleibt; Admin-Port ist bewusst ohne Traefik/2FA, aber auf Tailscale-IP begrenzt (Operator-Entscheidung 2026-05-26) |
| `unbound` | Upstream DNS Resolver fuer AdGuard | `apps/unbound/docker-compose.yml` | intern | `dns_net` | `/mnt/user/appdata/unbound/config` | rebuildbar / config relevant | nein | intern isoliert |
| `tailscale` | VPN/Remote-Zugang | `host-services/tailscale/docker-compose.yml` | Tailscale | Host-Netz | `/mnt/user/appdata/tailscale` | Tier 1, State relevant | nein | `network_mode: host`, `NET_ADMIN`, `NET_RAW` und `/dev/net/tun` sind dokumentierte VPN-Ausnahmen |
| `gitea` | Git-Server / origin fuer GitOps | `core/gitea/docker-compose.yml` | `https://git.kaleschke.info`, SSH `222` | Traefik, `frontend_net`, externe DNS-Resolver fuer GitHub-Push-Mirror | `/mnt/user/services/gitea/data` | Tier 1, `gitea.sqlite.dump` + Share; privater GitHub-Push-Mirror fuer Repo-Bootstrap | ja | SSH-Port 222 direkte Host-Port-Ausnahme; Push-Mirror nach `michaelkaleschke-spec/homelab-infra` reduziert das DR-Bootstrap-Risiko |
## Security / Identity
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|---|---|---|---|---|---|---|---|---|
| `authelia` | ForwardAuth / zentrale Auth fuer Admin-UIs | `security/authelia/docker-compose.yml`, `security/authelia/configuration.yml` | `https://auth.kaleschke.info` | PostgreSQL 17, Traefik, GMX SMTP | `/mnt/user/appdata/authelia/config`, Authelia Secret-Dateien | Tier 1, config + DB + secrets | ja | Bewusst ohne Redis-Session-Backend; SMTP-Notifier via GMX und `authelia_smtp_password.txt`; explizite DNS-Server fuer SMTP/NTP; Repo-Baseline muss manuell in die Host-Config gemerged werden, OIDC/Secrets bleiben hostseitig; Access-Control und Compose-Middleware bei Aenderungen abgleichen |
| `vaultwarden` | Passwort-Tresor | `security/vaultwarden/docker-compose.yml` | `https://vault.kaleschke.info` | Traefik, `frontend_net` | `/mnt/user/appdata/vaultwarden` | Tier 1, `vaultwarden.sqlite.dump` + Share | ja | `ADMIN_TOKEN_FILE`; keine direkten Ports |
## Shared Infrastructure
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|---|---|---|---|---|---|---|---|---|
| `postgresql17` | shared PostgreSQL Cluster | `infra/postgresql17/docker-compose.yml` | intern | `backend_net` | `/mnt/user/appdata/postgresql17`, `postgres_password.txt` | Tier 1; Dumps unter `/mnt/user/backups/borg/dumps/latest` | nein | keine Host-Ports; raw DB nicht primaerer Restore-Weg |
| `Redis` | shared Redis Cache | `infra/redis/docker-compose.yml` | intern | `backend_net` | `/mnt/user/appdata/redis`, `redis_password.txt` | transiente Daten, bewusst nicht kritisch | nein | Passwort-Datei; optional named volume offen |
| `ddns-updater` | Cloudflare/DDNS Aktualisierung | `infra/ddns-updater/docker-compose.yml` | intern | Internetzugang, `frontend_net` | `/mnt/user/appdata/ddns-updater` | rebuildbar | nein | bleibt bewusst in `frontend_net`, weil `backend_net` internal ist |
## Public / User Apps
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|---|---|---|---|---|---|---|---|---|
| `paperless-ngx` | Dokumentenmanagement | `apps/paperless/docker-compose.yml` | `https://paperless.kaleschke.info` | PostgreSQL 17, Redis, Traefik | `/mnt/user/appdata/paperless-ngx/data`, `/mnt/user/documents/paperless`, `/mnt/user/documents/scans_inbox` | Tier 2, Borg + `postgresql17-paperless.dump` | ja | DB/Redis Secrets bleiben bewusst Stack ENV |
| `paperless-gpt` | KI-Ergaenzung fuer Paperless | `apps/paperless-gpt/docker-compose.yml` | `https://paperless-gpt.kaleschke.info` | Paperless API, LLM/Ollama, Traefik | `/mnt/user/appdata/paperless-gpt/data`, `/mnt/user/appdata/paperless-gpt/prompts` | Tier 2 | ja + Authelia | API Token als Stack ENV; OCR/LLM-Konfig bei Aenderungen pruefen |
| `immich_server` | Foto-/Video-App | `apps/immich/docker-compose.yml` | `https://immich.kaleschke.info` | Immich Postgres, Immich Redis, ML, Traefik | `/mnt/user/photos/immich`, `/mnt/user/photos/family_archive` | Tier 2, Borg + `immich.dump` | ja | native App-Auth; externes Fotoarchiv gemountet |
| `immich_postgres` | Immich-Datenbank | `apps/immich/docker-compose.yml` | intern | `immich_default` | `/mnt/user/appdata/immich_postgres`, `immich_postgres_password.txt` | Dump `immich.dump` | nein | nie ins `frontend_net` |
| `immich_redis` | Immich Cache | `apps/immich/docker-compose.yml` | intern | `immich_default` | kein kritischer Pfad dokumentiert | rebuildbar | nein | Architektur nennt anonymes Volume -> named volume als offenes Thema |
| `immich_machine_learning` | Immich ML | `apps/immich/docker-compose.yml` | intern | `immich_default` | `model-cache` | rebuildbar | nein | intern-only |
| `mealie` | Rezeptverwaltung | `apps/mealie/docker-compose.yml` | `https://mealie.kaleschke.info` | `mealie-postgres`, Traefik | `/mnt/user/appdata/mealie/data` | Tier 2, Borg + `mealie.dump` | ja | App + DB in internem Netz getrennt |
| `mealie-postgres` | Mealie-Datenbank | `apps/mealie/docker-compose.yml` | intern | `mealie_internal` | `/mnt/user/appdata/mealie/postgres`, `mealie_postgres_password.txt` | Dump `mealie.dump` | nein | interne DB |
| `mail-archiver` | Mail-Archivierung | `apps/mail-archiver/docker-compose.yml` | `https://mail.kaleschke.info` | PostgreSQL 17, Internet/IMAP, Traefik, Authelia | `/mnt/user/appdata/mailarchiver/data-protection-keys` | Tier 2, `postgresql17-mailarchiver.dump` | ja + Authelia | Hybrid-Dienst: `frontend_net` fuer Internet, `backend_net` fuer DB; App-eigene Auth bleibt zusaetzliche Schutzschicht |
| `nextcloud` | Datei-/Cloud-Dienst | `apps/nextcloud/docker-compose.yml` | `https://cloud.kaleschke.info` | eigene PostgreSQL, eigene Redis, Traefik | `/mnt/user/appdata/nextcloud/html`, `/mnt/user/documents/nextcloud-data` | Tier 2, `nextcloud.dump` + Share | ja | native App-Auth ohne zentrale ForwardAuth; WebDAV/CardDAV beachten |
| `nextcloud-postgres` | Nextcloud-Datenbank | `apps/nextcloud/docker-compose.yml` | intern | `nextcloud_internal` | `/mnt/user/appdata/nextcloud/postgres`, `nextcloud_postgres_password.txt` | `nextcloud.dump`, raw DB nicht primaerer Restore-Weg | nein | interne DB |
| `nextcloud-redis` | Nextcloud Cache/Locking | `apps/nextcloud/docker-compose.yml` | intern | `nextcloud_internal` | `/mnt/user/appdata/nextcloud/redis` | Teil von Nextcloud-Restore | nein | interne Redis |
| `plex` | Medienserver mit LAN-/Client-Discovery | `host-services/plex/docker-compose.yml` | Plex native / LAN / Remote je Plex-Konfiguration | Host-Netz | `/mnt/user/appdata/plex/config`, `/mnt/user/appdata/plex/transcode`, `/mnt/user/media`, `/mnt/user/photos` | Tier 2, Appdata + Medienpfade im Borg-/Share-Scope | nein | Repo-Compose-Stack; `network_mode: host` bleibt dokumentierte Discovery-Ausnahme, kein Traefik-Stack |
| `ntfy` | Push-Benachrichtigungen | `apps/ntfy/docker-compose.yml` | `https://ntfy.kaleschke.info` | Traefik, upstream mobile push | `/mnt/user/appdata/ntfy` | Tier 2 | ja | `NTFY_BEHIND_PROXY=true`; Problem-Alerts gehen gebuendelt an `homelab-alerts`, optionale Erfolgsmeldungen an `homelab-info` |
| `bentopdf` | PDF-Tooling / Ersatz fuer Stirling-PDF | `apps/bentopdf/docker-compose.yml` | `https://pdf.kaleschke.info` | Traefik + Authelia | keine kritische Persistenz im Compose | Tier 3, rebuildbar | ja + Authelia | COOP/COEP per Middleware; fachliche Abnahme/Live-Status pruefen |
## Operations / Monitoring / Admin
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|---|---|---|---|---|---|---|---|---|
| `glance` | einziges Homelab-Uebersichts-/Status-Dashboard | `ops/glance/docker-compose.yml`, `ops/glance/config/glance.yml` | `https://glance.kaleschke.info` | Traefik + Authelia, interne HTTP-Checks, Immich API, AdGuard API, Speedtest API, interner Docker-Socket-Proxy | Repo-Konfiguration; keine kritische Persistenz | Tier 3, rebuildbar | ja + Authelia | Zeigt aktive Dienste, Immich-Community-Widget, Internet-/DNS-/VPN-Monitore, AdGuard-DNS-Stats, Speedtest-Livewerte, Docker-Containergruppen, Server-Stats, Zeitfortschritt, Bookmarks und eine Infrastruktur-Seite; Docker-API nur ueber `glance-docker-socket-proxy` auf internem Netz |
| `komodo-core` | GitOps UI/API/Stack-Manager | `ops/komodo/docker-compose.yml` | `https://komodo.kaleschke.info` | Mongo, Gitea, Traefik | `/mnt/user/appdata/komodo/core`, `komodo_keys` | Tier 1 | ja, native Auth | keine pauschale Authelia-ForwardAuth; Gitea DNS override |
| `komodo-mongo` | Komodo Datenbank | `ops/komodo/docker-compose.yml` | intern | `komodo_net` | `/mnt/user/appdata/komodo/mongo`, `komodo_mongo_password.txt` | Tier 1, `komodo-mongo.archive.gz` | nein | Dump am 2026-05-04 bestaetigt; nach Major-Upgrades pruefen |
| `komodo-periphery` | Komodo Host-Agent | `ops/komodo/docker-compose.yml` | intern Core -> Periphery | Docker socket, `/mnt/user/services`, `frontend_net`, `komodo_net` | `/mnt/user/appdata/komodo/periphery`, `komodo_keys` | Tier 1 | nein | Docker-Socket-Ausnahme; `/mnt/user/services` Mount fuer Stack-Workspaces |
| `borg-ui` | Borg Backup-/Restore UI | `ops/borg-ui/docker-compose.yml` | `https://borg.kaleschke.info` | Traefik + Authelia, Borg repo credentials | `/mnt/user/appdata/borg-ui/data`, `/mnt/user/backups/borg/dumps`, Restore-Ziel | Tier 3 / Backup kritisch, `borg-ui.sqlite` | ja + Authelia | breite Mounts bewusst; `/local/secrets` im DR-Scope; Nextcloud-Daten werden read-only nach `/local/nextcloud/data` eingebunden |
| `glances` | System-/Container-Monitoring | `ops/glances/docker-compose.yml` | `https://glances.kaleschke.info` | Docker socket, rootfs, Traefik + Authelia | kein kritischer Zustand | Tier 3, rebuildbar | ja + Authelia | Dokumentierte Host-Observability-Ausnahme: `pid: host`, `/:/rootfs:ro`, `/var/run/docker.sock:/var/run/docker.sock:ro`, `/etc/os-release:/etc/os-release:ro`; keine Appdaten ausserhalb `/mnt/user/...` |
| `scrutiny` | Laufwerks-/SMART-Monitoring | `ops/scrutiny/docker-compose.yml` | `https://scrutiny.kaleschke.info` | Device mounts, Traefik + Authelia | `/mnt/user/appdata/scrutiny/config`, `/mnt/user/appdata/scrutiny/influxdb` | Tier 3, Metrics nicht kritisch | ja + Authelia | Dokumentierte Host-Observability-Ausnahme: `privileged: true`, `/run/udev:/run/udev:ro`, `/dev/sdb:/dev/sdb`, `/dev/sdc:/dev/sdc`, `/dev/nvme0n1:/dev/nvme0n1`; keine Appdaten ausserhalb `/mnt/user/...` |
| `speedtest-tracker` | Speedtest-Monitoring | `ops/speedtest/docker-compose.yml` | `https://speedtest.kaleschke.info` | Traefik + Authelia | `/mnt/user/appdata/speedtest-tracker/config` | Tier 3, `speedtest-tracker.sqlite.dump` | ja + Authelia | `APP_KEY`, `ADMIN_PASSWORD` Stack ENV |
| `filebrowser` | Datei-Browser fuer Documents/Photos/Projekte | `ops/filebrowser/docker-compose.yml` | `https://files.kaleschke.info` | Traefik + Authelia | `/mnt/user/appdata/filebrowser/*`, `/mnt/user/documents`, `/mnt/user/photos`, `/mnt/user/projekte` | Tier 3, `filebrowser.bolt.dump` + Share | ja + Authelia | Breiter Appdata-Mount entfernt; Secrets und Traefik-Dynamic-Config sind nicht mehr ueber Filebrowser gemountet |
| `code-server` | Web-Editor / Operations Workspace | `ops/code-server/docker-compose.yml` | `https://code.kaleschke.info` | Traefik + Authelia | `/mnt/user/appdata/code-server`, `/mnt/user/services/dev` | Tier 3 | ja + Authelia | Passwort ueber LSIO `FILE__PASSWORD`; Workspaces beachten |
| `monitoring-grafana` | zentrale Observability-UI fuer Metriken, Logs und InfluxDB | `monitoring/docker-compose.yml` | `https://monitoring.kaleschke.info` | Traefik + Authelia, Prometheus, Loki, InfluxDB 3 Core | named volume `grafana_data`, Provisioning unter `monitoring/grafana/provisioning`, Dashboards unter `monitoring/grafana/dashboards` | Tier 3, named volume | ja + Authelia | Admin-Passwort ueber `monitoring_grafana_admin_password.txt`; Zielbestand: `Homelab / Availability`, `Homelab / Host Overview`, `Homelab / Containers + Logs`, `Traefik Official Standalone Dashboard`; Dashboard-Importer ist optionales `bootstrap`-Profil fuer Traefik |
| `monitoring-prometheus` | Metrik-Speicher fuer Homelab-Monitoring | `monitoring/docker-compose.yml`, `monitoring/prometheus/prometheus.yml`, `monitoring/prometheus/alerts.yml` | intern `http://prometheus:9090` | `monitoring_net`, node-exporter, cAdvisor, Traefik-Metrics, Blackbox Exporter, Alertmanager | named volume `prometheus_data` | Tier 3, transiente Metriken mit 30 Tagen Retention | nein | Scrapes: Prometheus, node-exporter, cAdvisor, Traefik `:8082`, `blackbox-http`; Prometheus-Regeln senden an Alertmanager und von dort nach ntfy |
| `monitoring-alertmanager` | Alert-Routing fuer Prometheus-Regeln | `monitoring/docker-compose.yml`, `monitoring/alertmanager/alertmanager.yml` | intern `:9093` | Prometheus, ntfy Bridge | named volume `alertmanager_data` | Tier 3 | nein | sendet firing und resolved Alerts an `monitoring-alertmanager-ntfy-bridge` |
| `monitoring-alertmanager-ntfy-bridge` | Alertmanager-Webhook nach ntfy Push | `monitoring/docker-compose.yml`, `monitoring/alertmanager-ntfy-bridge/bridge.py` | intern `:8080` | Alertmanager, `https://ntfy.kaleschke.info/homelab-alerts` | kein kritischer Zustand | rebuildbar | nein | formatiert Alertmanager JSON als ntfy Titel, Nachricht, Priority und Tags; keine Secrets |
| `monitoring-blackbox-exporter` | HTTP-Erreichbarkeitspruefungen als Uptime-Kuma-Ersatz | `monitoring/docker-compose.yml`, `monitoring/blackbox/blackbox.yml` | intern `:9115` | Prometheus, externe HTTPS-Ziele | kein kritischer Zustand | rebuildbar | nein | Uptime Kuma wurde 2026-05-25 nach erfolgreichem Blackbox-/Grafana-Smoke-Test entfernt |
| `monitoring-loki` | Logspeicher fuer Monitoring-Stack | `monitoring/docker-compose.yml`, `monitoring/loki/loki-config.yml` | intern `http://loki:3100` | `monitoring_net`, Promtail, Grafana | named volume `loki_data` | Tier 3, transiente Logs mit 30 Tagen Retention | nein | Ersetzt den alten `ops/loki`-Stack; kein paralleler Altcontainer aktiv |
| `monitoring-promtail` | Docker-Log-Collector fuer Monitoring-Loki | `monitoring/docker-compose.yml`, `monitoring/promtail/promtail-config.yml` | intern | Docker socket read-only, Docker json-file Logs, Loki | named volume `promtail_positions` | rebuildbar | nein | Dokumentierte Host-Observability-Ausnahme: `/var/run/docker.sock:/var/run/docker.sock:ro` und `/var/lib/docker/containers:ro`; keine Appdaten, nur Log-Discovery |
| `monitoring-node-exporter` | Host-Metriken fuer Prometheus | `monitoring/docker-compose.yml` | intern `:9100` | Host `/proc`, `/sys`, `/` read-only, Prometheus | kein kritischer Zustand | rebuildbar | nein | Host-Observability-Ausnahme mit read-only Rootfs/Proc/Sys-Mounts |
| `monitoring-cadvisor` | Container-Metriken fuer Prometheus | `monitoring/docker-compose.yml` | intern `:8080` | Docker/Host read-only Mounts, Prometheus | kein kritischer Zustand | rebuildbar | nein | Host-Observability-Ausnahme fuer Container-Metriken; keine direkten Ports |
| `monitoring-influxdb3-core` | InfluxDB 3 Core fuer Home-Assistant-/Ecowitt-Langzeitdaten | `monitoring/docker-compose.yml` | LAN `8181` je `INFLUXDB_BIND_IP`, keine Public URL | Monitoring-Grafana, Home Assistant Writer | `/mnt/user/appdata/influxdb3/data`, `/mnt/user/appdata/influxdb3/plugins` | Tier 3 | nein | LAN-only Host-Port-Ausnahme; `user: "0"` ist fuer den lokalen Object-Store-Pfad dokumentiert; uebernimmt den bisherigen InfluxDB-Daten-/Token-Katalog; `401 Unauthorized` beim Curl ohne Token ist erwarteter Reachability-Test |
| `hermes-gateway` | Hermes Agent Gateway/API intern | `ops/hermes-agent/docker-compose.yml` | intern `8642` auf `hermes_net` | SSH Runner (VM 192.168.178.143), LLM Provider, optional Home Assistant | `/mnt/user/appdata/hermes-agent/data`, SSH key path | Tier 3, Borg/Share | nein | NAS-Stack bleibt deaktiviert, solange die separate Hermes-VM/Runner-Seite nicht wiederhergestellt ist; kein Docker-Socket |
| `hermes-dashboard` | Hermes Dashboard | `ops/hermes-agent/docker-compose.yml` | `https://hermes.kaleschke.info` via `${HERMES_DASHBOARD_HOST}` | `hermes-gateway`, Traefik + Authelia | shared read-only data mount | Tier 3, Borg/Share | ja + Authelia | Compose-Profil `dashboard`; aktuell VM-seitig offen, nicht Teil des NAS-Finalstarts |
## Host Operations
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|---|---|---|---|---|---|---|---|---|
| `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` |
## Backup- und Restore-Hinweise
- Tier-1-Dienste stehen in `docs/RESTORE_MATRIX.md` und `docs/DISASTER_RECOVERY.md`.
- Dump-Erzeugung erfolgt host-seitig ueber `ops/borg-ui/scripts/pre-backup-dumps.sh`.
- Dump-Ziel: `/mnt/user/backups/borg/dumps/latest`.
- Raw Live-DB-Pfade sind meist nicht der primaere Restore-Weg; bevorzugt werden Dumps plus Appdaten.
- Borg UI nimmt bewusst `/local/secrets` in den DR-Scope auf.
## Logging-Resilienz
- Docker-Rohlogs bleiben die erste Fallback-Ebene: `docker logs <container>` funktioniert unabhaengig von Loki/Grafana.
- Loki-Chunks liegen unter `/mnt/user/appdata/loki/data` und koennen durch Neustart des internen Loki-Stacks wieder genutzt werden.
- Host-Logs wie `/var/log/syslog` und `dmesg` bleiben die Quelle fuer Kernel-, OOM- und Docker-Daemon-Ereignisse.
- Critical-Events werden zusaetzlich ueber ntfy extern sichtbar, wenn `services/posture-check/docker-critical-events.sh` als Host-Watcher laeuft.
- Docker `json-file` Logs werden auf Unraid nativ ueber `/boot/config/docker.cfg` begrenzt. Aktiver Host-Stand: `DOCKER_LOG_ROTATION="yes"`, `DOCKER_LOG_SIZE="50m"`, `DOCKER_LOG_FILES="1"`. Keine zusaetzliche `/etc/docker/daemon.json` setzen, weil sie mit Unraids Docker-Startflags kollidieren kann.
## Bekannte offene Fragen
- Authelia Repo-Baseline, Host-Config und Compose-Middlewares sollten bei Auth-Aenderungen explizit abgeglichen werden.
- Filebrowser-Mounts sind breit und bewusst, aber bei zukuenftigen Hardening-Sprints Kandidaten.
- Scrutiny bleibt privilegiert; nur mit klarer Begruendung aendern.
- BentoPDF ersetzt Stirling-PDF produktiv. Hermes ist VM-seitig offen und auf dem NAS bewusst nicht gestartet.
+415
View File
@@ -0,0 +1,415 @@
# Storage Layout — KalliLab CORE
| Feld | Wert |
|------|------|
| Version | 1.3 |
| Status | **Active** — bindend, commit-reif als `docs/STORAGE_LAYOUT.md` |
| Datum | 2026-05-15 |
| Geltungsbereich | Unraid-Host `Kallilabcore`, alle Pools, Array-Disks, User-Shares, Appdata-Strukturen |
| Verbindlichkeit | Bindend ab Inkraftsetzung. Abweichungen nur dokumentiert als Ausnahme (siehe Abschnitt 12) |
| Vorgänger | keiner; entstanden aus Incident 2026-05-11 (NTFS-Cache-Korruption) |
| Verwandte Docs | `HOMELAB_ARCHITECTURE_MASTER_V2.md`, `docs/REPO_MAP.md`, `docs/SERVICE_CATALOG.md`, `docs/RESTORE_MATRIX.md`, `docs/SECRETS_MAP.md`, `docs/DISASTER_RECOVERY.md` |
---
## 1. Zweck
Dieses Dokument definiert die verbindliche Storage-Architektur für den KalliLab-CORE-Homelab-Stack. Es beantwortet:
- Welche physikalischen Disks gibt es, mit welchem Filesystem.
- Welche User-Shares existieren, mit welchen Cache- und Allokations-Settings.
- Wo liegen Container-Daten und in welcher Sub-Struktur.
- Welche Pfade dürfen in Compose-Dateien als Bind-Mount auftauchen, welche nie.
- Welche Backup- und Posture-Check-Pflichten daraus folgen.
Es ist **vor** jeder Storage- oder Compose-Änderung zu lesen. Wenn ein neuer Stack, ein neuer Pfad, eine neue Disk, ein neuer Share gebraucht wird und hier nicht spezifiziert ist, wird dieses Dokument **erst** ergänzt, dann implementiert.
## 2. Leitprinzipien
1. **Filesystem-Semantik ist nicht verhandelbar.** Datenbanken, Container-Persistenz und Array-Daten brauchen Linux-natives Filesystem (XFS, BTRFS, ZFS). NTFS, exFAT, FAT32 sind ausschließlich für Boot-USB und externe Austauschmedien zulässig, niemals für Cache, Array oder Pool.
2. **Recovery > Convenience.** Eine Konvention, die Restore vereinfacht, schlägt jede Konvention, die nur die Erstinstallation vereinfacht.
3. **Posture-Check als First-Class-Concern.** Jede Annahme über den Host-Zustand (FS-Typ, Mount, Permissions) muss automatisiert prüfbar sein. „Wir wissen schon, dass das stimmt" ist keine zulässige Begründung.
4. **GitOps-Konformität.** Alles, was Compose-, Share-, oder Storage-Verhalten beeinflusst, ist im Repo dokumentiert oder versioniert. Was nur im Host lebt, existiert für Recovery nicht.
5. **Pfad-Stabilität.** Pfade in Compose-Bind-Mounts bleiben stabil über Filesystem-Migrationen, Disk-Wechsel und Pool-Umzüge hinweg. Konkret: Container kennen `/mnt/user/...`-Pfade, nie `/mnt/cache/...` oder `/mnt/disk1/...`.
6. **Keine Sammelordner.** Eine Datei hat genau einen logischen Eigentümer (Stack, Share, Nutzkategorie). Mischungen erzeugen Drift und brechen Backup-Excludes.
7. **Bewusste Heterogenität nur mit Begründung.** Wenn ein Stack vom Standard abweicht (z. B. Host-Network, Privileged, Direct-Cache-Mount), ist die Abweichung in Abschnitt 12 dokumentiert mit Begründung und Risiko-Akzeptanz.
## 3. Physikalisches Layout (Soll-Zustand)
| Slot | Device | Filesystem | Größe | Zweck | Status nach Recovery |
|------|--------|------------|-------|-------|----------------------|
| Cache (Pool) | Samsung 970 EVO Plus, NVMe | **XFS** | 2 TB | Appdata, system, domains | Reformat von NTFS auf XFS, Phase 1 |
| Disk1 (Array) | HDD (Modell TBD) | **XFS** | TBD | Nutzdaten, Backups, Services | NTFS-zu-XFS-Migration Phase 2 abgeschlossen am 2026-05-25 |
| Parity | HDD (Modell TBD) | — (keine FS) | TBD | Redundanz für Array | Unverändert |
| Boot | USB-Stick | FAT32 | klein | Unraid-OS, Konfiguration | Unverändert, regelmäßig per Flash-Backup gesichert |
| Externe Backup-Platte | Wechselplatte (Modell TBD) | XFS oder ext4 | ~8 TB | Ausgelagertes Backup-Ziel, Recovery-Material | NEU, ersetzt WD MyBookLive |
**TBD-Felder werden nachgetragen** sobald `lsblk -o NAME,SIZE,MODEL,SERIAL,VENDOR /dev/sd?` und `smartctl -i` für jeden Slot einmal ausgeführt wurden. Vorschlag: dieser Detection-Lauf ist Teil der Posture-Check-Erstinstallation (§11) und das Ergebnis wird automatisch in dieses Dokument eingetragen. Kein Blocker für die Inkraftsetzung — die Festsetzung „XFS auf Cache, XFS auf Disk1" steht unabhängig von Modell/Größe.
**Begründung Filesystem-Wahl:**
- **XFS für Cache und Disk1:** Unraid-Standard, robuste fsck, gute Performance für Datenbank-Workloads, niedrige Komplexität, einheitlicher Stack vereinfacht Operations.
- **BTRFS bewusst nicht** für Single-Disk-Cache: Mehrwerte (RAID1, Snapshots) werden ohne zweite NVMe nicht realisiert; zusätzlicher COW-Overhead und Komplexität ohne Gegenwert.
- **ZFS bewusst nicht** für aktuellen Bedarf: höherer RAM-Verbrauch, in der bestehenden Topologie kein Mehrwert; spätere Migration eines dedizierten Pools auf ZFS bleibt offen (siehe Abschnitt 13).
**Eskalationspfad bei zweiter Cache-NVMe:** Migration zu BTRFS-Pool (RAID1) als bewusste Architektur-Entscheidung mit eigenem Migrations-Plan. Bis dahin: Single-XFS.
## 4. Logisches Layout — User-Shares
Verbindliche Share-Definition. Jeder Share hat genau einen primären Datenträger.
| Share | Cache-Setting | Primary Storage | Allocation | Split Level | Zweck |
|-------|---------------|-----------------|------------|-------------|-------|
| `appdata` | **only** | Cache | High Water | 1 | Container-Persistenz, niemals auf Array |
| `system` | **only** | Cache | High Water | — | Docker-Image bzw. Folder-Mode-Verzeichnis, libvirt — bewusst `only`, weil Mover-Migration der Docker-Image-Datei oder von libvirt-State zwischen FS-Typen riskant ist |
| `domains` | **only** | Cache | High Water | 1 | VM-Disk-Images — Performance und Stabilität für produktive VMs (HAOS, hermes-runner). Wenn ältere/seltene VM-Images aufs Array dürfen, bewusst auf `prefer` herabsetzen, dokumentiert pro VM |
| `isos` | yes | Cache → Array | High Water | 2 | VM-Installations-ISOs, optional |
| `services` | no | Disk1 | High Water | 2 | Gitea, Komodo-State, secrets-Pfad — **recovery-kritisch**, siehe Hinweis unter Tabelle |
| `documents` | no | Disk1 | High Water | 2 | Persönliche Dokumente |
| `photos` | no | Disk1 | High Water | 2 | Persönliche Fotos |
| `backups` | no | Disk1 | High Water | 2 | Lokale Borg-Repos, Appdata-Backup-Archive |
| `media` | no | Disk1 | High Water | 2 | Optional, Media-Files |
| `finance` | no | Disk1 | High Water | 2 | Persönlich |
| `projekte` | no | Disk1 | High Water | 2 | Persönlich |
**Erklärung der Cache-Settings:**
- `only`: Daten landen ausschließlich auf Cache, Mover ignoriert sie. Schutz vor Mover-induzierter Daten-Migration zwischen Filesystemen.
- `prefer`: Neue Daten landen auf Cache, Mover schiebt sie bei Cache-Druck auf Array.
- `yes`: Neue Daten landen auf Cache, Mover schiebt sie regelmäßig auf Array (klassisches Cache-Schreibverhalten).
- `no`: Daten umgehen Cache, gehen direkt auf Array.
**Wahl `only` für `appdata`, `system`, `domains`** ist die wichtigste Setting in diesem Dokument. Sie verhindert genau die Klasse von Vorfällen, die im Mai 2026 passiert ist: dass Container-Daten, Docker-Image-State oder VM-Disks unbemerkt zwischen Filesystemen wandern. Wenn der Cache voll wird, ist das ein Kapazitätsproblem, kein Filesystem-Problem — und wird durch zweite NVMe oder Cache-Tuning gelöst, nicht durch Mover-Migration.
**`services` ist recovery-kritisch.** Gitea (operative Source of Truth des GitOps-Stacks) und Komodo-State (Deployment-Runtime) liegen dort. Ohne `services` ist Self-Recovery nicht mehr möglich. Daraus folgt:
- `services` wird vor jedem strukturellen Eingriff am Array (Disk1-Migration Phase 2, Disk-Tausch, Pool-Umzug) **vollständig** auf zweites Medium gesichert
- `services` darf nicht auf einem als instabil bekannten Datenträger leben — Warnung in `posture-check`, Eskalation bei Disk1-FS-Anomalien
- Änderungen an Gitea-Repos, Komodo-Stacks oder secrets-Pfad nur nach erfolgreichem letzten Backup-Check (siehe §11)
- **Mirror-Backup für Gitea-Repo-Inhalte (Operator-Entscheidung 2026-05-15, verbindlich):** zusätzlich zum Standard-Borg-Lauf wird `services/gitea/git/repositories/` per separatem Mirror-Mechanismus auf ein zweites physisches Medium außerhalb Disk1 gesichert, mit Frequenz ≤ 6 h. Konkrete Implementierung (Optionen: `git bundle`-Bundles in separates Repo auf Cache, `rsync` auf externe Platte, separates Borg-Repo mit kürzerem Schedule) wird in `docs/SERVICES_RECOVERY.md` (zu erstellen) detailliert. Begründung: Gitea ist operative Source of Truth des GitOps; Verlust seit letztem Standard-Backup (bis zu 24 h) wäre für Recovery-Pfad unzumutbar.
**Erklärung Allocation-Method:** „High Water" verteilt neue Daten auf der am wenigsten gefüllten Disk bis zu einer Wasserlinie, dann auf die nächste. Praktisch unauffällig bei Single-Array-Disk; wird relevant bei Erweiterung.
**Split Level** bestimmt, wie tief Verzeichnisse über Disks aufgeteilt werden dürfen. Für `appdata` zwingend `1` — ein Stack-Verzeichnis bleibt auf einer Disk, wird nie aufgespalten.
## 5. Appdata-Sub-Struktur
Verbindliches Layout pro Stack:
```
/mnt/user/appdata/<stackname>/
├── config/ # Statische Konfiguration (yaml, conf, env-Dateien)
├── data/ # Live-Datenverzeichnis (DB-Datendir, Container-Persistenz)
├── dumps/ # Pre-Backup-Hooks schreiben hier (pg_dump, mongodump, sqlite .backup)
├── logs/ # Container-Logs, falls nicht via journald gelöst
└── README.md # Pflicht: Kurzbeschreibung, Restore-Zeiger, Sondereigenheiten
```
**Regeln:**
- Ein Verzeichnis pro Stack, kein Mischen mehrerer Stacks
- Stack-Namen sind lowercase, Bindestriche statt Unterstriche (z. B. `mail-archiver`, nicht `Mail_Archiver`)
- `data/` enthält Live-State. **Behandlung pro Stack klassifiziert in `RESTORE_MATRIX.md`:**
- **Reines DB-Datenverzeichnis** (z. B. raw Postgres-`data/`, raw Mongo-`data/`): wird **nicht** direkt gesichert, ist durch `dumps/` abgedeckt
- **Echte Nutzdaten** (z. B. Immich-Uploads, Paperless-Dokumente, Gitea-Repo-Inhalte, Vaultwarden-Attachments, Mealie-Bilder, Grafana-Dashboards/Provisioning): **werden direkt gesichert** oder über expliziten Export-Mechanismus erfasst
- **Mischformen** (z. B. ein Stack mit DB-Files und Nutzdaten im selben `data/`): pro Stack im Detail spezifiziert, mit getrennten Sub-Pfaden wo möglich
- `dumps/` enthält von Pre-Hooks erzeugte konsistente Dumps; **wird immer von Borg gesichert**
- `config/` ist die einzige Quelle der Container-Konfiguration; idealerweise versioniert oder aus Git deploybar
- `README.md` pro Stack: drei Zeilen genügen (was, warum, Restore-Zeiger)
**Ausnahmen, wenn Container das Layout nicht zulassen:** dokumentiert in `docs/SERVICE_CATALOG.md` pro Stack, mit Verweis hier.
## 6. Naming-Konventionen
| Element | Konvention | Beispiel |
|---------|------------|----------|
| Stack-Name | `kebab-case`, lowercase, keine Sonderzeichen | `vaultwarden`, `mail-archiver`, `immich-server` |
| Container-Name | identisch zu Stack-Name (oder `<stack>-<role>`), kebab-case | `postgresql-17`, `immich-postgres`, `komodo-mongo` |
| Volume-Pfad in Compose | `/mnt/user/appdata/<stack>/<sub>` | `/mnt/user/appdata/vaultwarden/data:/data` |
| Netz-Name | `frontend_net`, `backend_net`, oder `<stack>_internal` | `frontend_net`, `immich_internal` |
| Env-Datei | `<stack>.env`, im Komodo-Stack-Workspace, **nicht** im Repo | — |
**Begründung Bindestriche:** vermeiden Verwechslung mit Shell-Variablen (Unterstriche werden in env vars verwendet), passen besser zu Docker-Compose-Konventionen, bleiben URL-safe.
**Migration Bestand → kebab-case (Operator-Entscheidung 2026-05-15):** existierende Stacks mit Unterstrichen oder gemischten Konventionen (`postgresql17`, `immich_postgres`, `komodo-mongo` etc.) werden im Rahmen der Recovery-Phase (Cache-Restore Phase 1, Disk1-Migration Phase 2) auf kebab-case migriert. Pro Stack: alter Name → neuer Name in `docs/RESTORE_MATRIX.md` dokumentiert. Container-Renames erfolgen sauber per Compose-Down → Rename → Compose-Up mit neuer Identität, **nicht** im laufenden Betrieb. Netzwerk-Referenzen, Healthchecks und Inter-Stack-Abhängigkeiten werden im selben Schritt mit angepasst. Nicht-migrierte Stacks am Ende der Recovery-Phase werden in §20 als bewusste Ausnahme dokumentiert oder auf einen Folge-Termin verschoben.
## 7. Permissions-Modell
| Anwendungsfall | UID:GID | Begründung |
|----------------|---------|------------|
| Standard-Files in `appdata/` | `99:100` (`nobody:users`) | Unraid-Standard, von shfs/Mover sauber behandelt |
| Container-spezifischer User | individuell, dokumentiert | manche Images erwarten zwingend `1000:1000` o. ä. |
| `secrets/`-Verzeichnis | `0:0`, mode `0700` oder `0750` | Schutz vor versehentlichem Read |
| `logs/` | `99:100`, mode `0755` | Lesbar für Debugging, nicht weltschreibbar |
| `dumps/` | `99:100`, mode `0750` | Backup-Hook schreibt, Borg liest |
**Regel:** Permission-Setzung beim Stack-Restore ist explizit per Skript oder Init-Container, nicht „erstmal `chown -R root:root` und gucken was kaputtgeht". Dokumentiert pro Stack.
## 8. Backup-Architektur
### 8.0 Aktueller Ist-Zustand vs. Ziel-Zustand
**Operator-Entscheidung 2026-05-15:** Backrest wird abgeschaltet. **Borg ist die alleinige Backup-Technologie.** Restic-Repos werden nicht weiterentwickelt; vorhandene Restic-Daten verbleiben als historisches Material bis zur expliziten Entsorgung.
**Ist-Zustand zum Zeitpunkt der Inkraftsetzung:**
- Borg via Hetzner Storagebox: produktiv, im Vorfall 2026-05-11 als Restore-Quelle verifiziert
- Backrest/restic: **wird abgeschaltet** — Container nicht mehr starten, Repo-Daten archivieren oder löschen, Stack-Eintrag aus Compose entfernen
- Appdata-Backup-Plugin (CIFS auf WD MyBookLive): **abgeschaltet, Ziel komplett aus Setup entfernt**
- Lokales Borg-Repo auf `/mnt/user/backups/borg/`: einzurichten falls noch nicht vorhanden, als zweites Borg-Ziel neben Hetzner
**Ziel-Zustand (was dieses Dokument definiert):**
- Borg ist die einzige produktive Backup-Technologie (lokal + remote)
- Backup-Pfade, Pre-Hooks und Verifikation einheitlich über Borg
- Migrationspfad Backrest-Abschaltung pro Stack in `docs/RESTORE_MATRIX.md`
Dieses Dokument definiert die Zielarchitektur. Abweichungen zwischen Ist und Soll sind dokumentiert in `docs/RESTORE_MATRIX.md`.
### 8.1 Backup-Ziele
| Ziel | Typ | Zweck | Retention |
|------|-----|-------|-----------|
| `/mnt/user/backups/borg/` (lokal) | Borg-Repo auf Disk1 | Schneller lokaler Restore | 30 Tage täglich, 12 Monate monatlich |
| Hetzner Storagebox | Borg-Repo via SSH | Off-Site, Disaster Recovery | 90 Tage täglich, 24 Monate monatlich |
| Externe Wechselplatte | Cold Storage, manuell | Air-Gap, Recovery-Material | rotiert manuell, kein festes Schema |
**WD MyBookLive ist explizit kein Backup-Ziel mehr.** Eintrag in `docs/SECRETS_MAP.md` und allen Backup-Konfigs entfernt.
### 8.2 Backup-Inhalt
**Pflicht-Backup-Scope (Borg, beide Repos):**
- `/mnt/user/appdata/*/config/`
- `/mnt/user/appdata/*/dumps/`
- `/mnt/user/appdata/*/README.md`
- `/mnt/user/appdata/*/data/` für Stacks mit Nutzdaten in `data/` (siehe §5 und Klassifikation in `RESTORE_MATRIX.md`)
- `/mnt/user/services/`
- `/boot/config/` (Unraid-Konfiguration)
- **persönliche Daten vollständig im Scope** (Operator-Entscheidung 2026-05-15):
- `/mnt/user/documents/`
- `/mnt/user/photos/`
- `/mnt/user/finance/`
- `/mnt/user/projekte/`
- `/mnt/user/media/` (falls behalten)
Borg dedupliziert über Läufe, der erste Vollbackup ist groß, Folge-Backups inkrementell. Retention nach §8.1.
**Pflicht-Excludes (mit Vorsicht — siehe §5):**
- `**/data/` innerhalb `appdata/*/` **nur dann**, wenn `data/` ausschließlich rohes DB-Datenverzeichnis ist (durch `dumps/` abgedeckt). Stacks mit echten Nutzdaten in `data/` (Immich, Paperless, Gitea, Vaultwarden, Mealie, Grafana etc.) sind aus diesem Exclude **explizit ausgenommen** — pro Stack klassifiziert in `docs/RESTORE_MATRIX.md`. Unklassifizierte Stacks gelten als „nicht ausschließbar" — lieber zuviel sichern als versehentlich zu wenig.
- `**/cache/`, `**/tmp/`, `**/lost+found/` (jeweils generisch unbedenklich)
- Container-Image-Layer (`/var/lib/docker/`)
- VM-Disk-Images (`/mnt/user/domains/`) außer auf Operator-Entscheidung pro VM
### 8.3 Pre-Backup-Hooks pro DB-Stack
Verbindlich. Jeder Stack mit eigener DB hat einen Pre-Hook, der einen konsistenten Dump nach `appdata/<stack>/dumps/` schreibt:
| Stack-Typ | Befehl | Ziel |
|-----------|--------|------|
| Postgres | `pg_dump -Fc` oder `pg_dumpall --globals-only` | `appdata/<stack>/dumps/<dbname>.dump` |
| MariaDB/MySQL | `mariadb-dump --single-transaction` | `appdata/<stack>/dumps/<dbname>.sql` |
| MongoDB | `mongodump --archive=...` | `appdata/<stack>/dumps/mongo.archive.gz` |
| SQLite (Vaultwarden, Gitea) | `sqlite3 db .backup target.db` | `appdata/<stack>/dumps/<dbname>.sqlite` |
| Redis | `redis-cli BGSAVE` plus Kopie der `dump.rdb` | `appdata/<stack>/dumps/dump.rdb` |
**Dumps sind nur dann wertvoll, wenn sie regelmäßig gegen Restore-Test verifiziert werden.** Siehe Abschnitt 11.
### 8.4 Backup-Verifikation
| Frequenz | Prüfung | Pass-Kriterium |
|----------|---------|----------------|
| Pro Backup-Lauf | Exit-Code, Dauer im erwarteten Korridor, Archivgröße im erwarteten Korridor | alle drei grün, sonst Alarm |
| Wöchentlich | `borg check --repository-only` auf beiden Repos | fehlerfrei |
| Monatlich | Probe-Restore eines zufällig ausgewählten Stacks aus Hetzner-Repo in Wegwerf-Pfad | restored Files lesbar, DB-Dump per `pg_restore --list` lesbar |
| Quartalsweise | Voll-Restore-Drill einer kompletten App-Klasse (z. B. Gitea allein) auf Test-Pfad | App startet, Login funktioniert |
Backup-Verifikations-Ergebnisse landen in `/mnt/user/services/backup-verify/` als Log mit Zeitstempel.
## 9. Mover-Policy
- Mover läuft nicht auf einem Schedule, der parallel zu Backup-Jobs liegt
- Mover wird **niemals** für Shares mit `cache: only` aktiv
- Mover wird vor planmäßigen Reboots manuell ausgelöst und abgewartet
- Mover-Logs werden vom Posture-Check auf Anomalien geprüft
## 10. Network-Bezug zu Storage
Reine Verweis-Sektion. Authoritative Definition in `HOMELAB_ARCHITECTURE_MASTER_V2.md`.
- Datenbanken, die Storage-intensiv sind, hängen ausschließlich an `backend_net` (`internal: true`), niemals an `frontend_net`
- Backup-Container hängen entweder an `backend_net` (für DB-Connect) oder eigenem internen Netz, nicht an `frontend_net`
- Es gibt keine Storage-Pfade, die über `frontend_net`-Container exponiert werden
## 11. Posture-Check (Pflicht-Audit)
Automatisierter Check, der mindestens täglich läuft (z. B. via User-Script oder Cron im posture-check-Stack), und bei jedem Fail einen Alarm produziert.
**Zusätzlich zwingend ausgelöst:**
- **Bei jedem Boot** des Hosts (Unraid User-Script `at start`, oder systemd-äquivalent) — fängt sofort ab, wenn nach Reboot ein Mount falsch ist
- **Vor jedem Backup-Lauf** als Pre-Hook — wenn Posture-Check fehlschlägt, **wird das Backup abgebrochen und alarmiert**, statt fragwürdige Daten zu sichern und so die Backup-Historie zu kontaminieren
- **Nach jedem Mover-Lauf** als Post-Hook — verifiziert, dass Mover keine `cache: only`-Shares angefasst hat
- **Vor jeder strukturellen Änderung** (Disk-Tausch, Pool-Umzug, Format-Aktion) manuell — Soll-Ist-Abgleich vor destruktiven Aktionen
**Alarmziel (Operator-Entscheidung 2026-05-15, normalisiert 2026-05-17): ntfy.** Problem-Alerts gehen an den selbst gehosteten ntfy-Server `https://ntfy.kaleschke.info` mit dem Topic `homelab-alerts`. Optionale Erfolgsmeldungen gehen an `homelab-info`. Push-Empfänger sind die Mobil-Geräte des Operators. Optional als Folge-Erweiterung: E-Mail-Fallback über mail-archiver-Stack als persistente Trail für historische Auswertung — nicht jetzt entscheiden, kann später dazukommen.
**Pflichtprüfungen:**
| Check | Erwartung | Bei Fail |
|-------|-----------|----------|
| `findmnt -no FSTYPE /mnt/cache` | `xfs` | Sofortalarm, kritisch |
| `findmnt -no FSTYPE /mnt/disk1` | `xfs` (nach Phase 2) | Sofortalarm, kritisch |
| `mount` enthält keinen `ntfs3`- oder `fuseblk`-Eintrag auf Cache/Array | wahr | Sofortalarm |
| `nvme smart-log /dev/nvme0n1` Critical Warning | `0x00` | Sofortalarm |
| `nvme smart-log` Media Errors | `0` oder stabil zur Vorwoche | Warnung |
| D-State-Prozesse > 60s | keine | Warnung, ab 5 Min Alarm |
| iowait über 5-Min-Mittel | < 30 % | Warnung ab 50 %, Alarm ab 80 % |
| Letztes erfolgreiches Borg-Archiv (lokal) | < 26 h alt | Warnung ab 30 h, Alarm ab 48 h |
| Letztes erfolgreiches Borg-Archiv (Hetzner) | < 26 h alt | Warnung ab 30 h, Alarm ab 48 h |
| Erwartete Mindestgröße pro Borg-Archiv | matcht Profil pro Stack | Warnung |
| CIFS/NFS-Mount-Liveness (für jeden konfigurierten Remote-Mount) | `stat` mit 10s-Timeout erfolgreich | Sofortalarm |
| Anzahl laufender Container | matcht Soll aus Repo | Warnung |
**Implementierung:** Skript unter `services/posture-check/posture-check.sh` plus Kompatibilitaets-Wrapper `services/posture-check/posture_check.sh`, ausgegeben als JSON nach `/mnt/user/services/posture-check/last.json`, konsumiert von ntfy.
## 12. Hard Rules — Constitution
Diese Regeln sind nicht optional. Verstoß ist Incident, kein Feature-Request.
1. **Kein NTFS, exFAT, FAT32 auf Cache, Pool, Array.** Nur XFS, BTRFS, ZFS.
2. **Keine Compose-Bind-Mounts auf `/mnt/cache/...` oder `/mnt/disk1/...`.** Immer `/mnt/user/...`.
3. **Keine Container schreibt nach `/mnt/disks/...` (Unassigned Devices) im Dauerbetrieb.**
4. **Keine `latest`-Image-Tags in Production-Compose.** Versionspin oder bewusster `:stable`/`:lts`-Alias.
5. **Keine Secrets im Repo.** Niemals.
6. **Kein Backup-Ziel über CIFS mit Hard-Mount ohne Liveness-Check.**
7. **Keine produktive Stack-Änderung ohne Compose-Commit im Repo.**
8. **Keine Disk- oder Pool-Änderung ohne Update dieses Dokuments im selben Commit.**
9. **Kein neuer Stack ohne Eintrag in `STORAGE_LAYOUT.md` (Share/Pfad), `SERVICE_CATALOG.md` (Funktion), `RESTORE_MATRIX.md` (Restore-Pfad).**
10. **Kein Pre-Backup-Hook, der DBs nicht konsistent dumped, für Stacks mit eigener DB.**
11. **Kein produktiver Stack ohne dokumentierten Restore-Pfad in `docs/RESTORE_MATRIX.md`.** Idealerweise mit dokumentiertem Restore-Test (≤ 90 Tage alt); bei fehlendem Test mindestens schriftliche Restore-Schritte und Backup-Quelle. Stacks ohne diesen Eintrag laufen nicht produktiv — entweder dokumentieren oder abschalten.
12. **Kein Backup-Lauf ohne vorgeschalteten Posture-Check (siehe §11).** Backup auf kompromittiertem Filesystem überschreibt unter Umständen den letzten guten Stand und kontaminiert die Backup-Historie.
**Dokumentierte Host-Observability-Ausnahmen (Operator-Entscheidung 2026-05-16):**
`glances`, `scrutiny`, `monitoring-promtail`, `monitoring-node-exporter` und `monitoring-cadvisor` duerfen gezielt Host-/Device-Bind-Mounts ausserhalb `/mnt/user/...` nutzen, weil ihre Kernfunktion sonst nicht erfuellbar ist. Erlaubt sind nur die in `docs/SERVICE_CATALOG.md` pro Dienst genannten Binds. Diese Ausnahmen sind keine Datenpersistenz-Pfade und duerfen nicht fuer Appdaten, Backups oder normale Service-Konfiguration erweitert werden. Neue oder geaenderte Host-Binds brauchen eine explizite Doku-Aenderung im selben Commit.
## 13. Soft Rules — Konventionen
Erwartet, aber begründbare Abweichungen sind dokumentiert.
- Stack-Verzeichnisse werden mit der Konvention aus Abschnitt 5 angelegt
- Ein Stack hat einen `README.md` in seinem `appdata/`-Verzeichnis
- Image-Tags werden quartalsweise auf Updates geprüft, dokumentiert in einem Update-Log
- Container ohne klare Eigentümerschaft werden quartalsweise reviewed (rauswerfen oder in Doku aufnehmen)
- Neue Shares werden grundsätzlich mit Cache-Setting `no` angelegt, Promotion auf `prefer`/`only` ist bewusste Entscheidung
- Compose-Dateien sind kommentiert, wenn die Konfiguration nicht selbsterklärend ist
## 14. Anti-Patterns mit Begründung
| Anti-Pattern | Warum Verboten |
|--------------|----------------|
| Direkter `/mnt/cache/X`-Bind-Mount in Compose | Pfad verschwindet, wenn Share-Setting auf Array migriert wird; bricht beim ersten Mover-Lauf |
| `chown -R 1000:1000 /mnt/user/appdata` blanket | Bricht alle Stacks, die andere UIDs erwarten; bei Mover-Lauf werden Permissions ggf. überschrieben |
| Backup direkt von Live-DB-Datendir | Inkonsistent unter Last; WAL-Divergenz nach Restore möglich |
| `latest`-Tags in Production | Reproduzierbarkeit weg; Rollback nur per Glück möglich |
| Mehrere Stacks im selben Appdata-Verzeichnis | Backup-Excludes greifen falsch; Restore betrifft Nachbar-Stack |
| Cache-Filesystem ohne automatisierten Posture-Check | Genau der Vorfall vom 2026-05-11 |
| Hard-mounted CIFS auf wackelige Geräte | D-State-Prozesse können den ganzen Host blockieren |
| Secret in Compose-Env (statt `env_file`) | Secret im Repo lesbar; bei `git log -p` für immer drin |
| Stack ohne dokumentierten Restore-Pfad | Bei Recovery muss improvisiert werden; das war der Auslöser dieses Dokuments |
## 15. Migrations- und Wachstumspfade
### 15.1 NTFS-zu-XFS-Migration (Cache, Phase 1)
Behandelt in `docs/DISASTER_RECOVERY.md`, Sektion „Cache Recovery 2026-05".
### 15.2 NTFS-zu-XFS-Migration (Disk1, Phase 2)
Folgeprojekt nach mindestens 7 Tagen stabilem Cache-Betrieb. Eigener Plan in `docs/DISASTER_RECOVERY.md`. Grobschritte:
1. Selektive Sicherung aller Disk1-Inhalte auf zweite Disk oder Hetzner
2. Hash-Verifikation der Sicherung
3. Disk1 entfernen aus Array (oder leer, wenn Daten temporär auf zweiter Disk)
4. Disk1 als XFS in Unraid-GUI neu formatieren
5. Daten zurückspielen
6. Parity neu bauen
7. Posture-Check grün
### 15.3 Erweiterung um zweite Cache-NVMe
Trigger: Cache-Auslastung > 70 %, oder Wunsch nach RAID1 für Appdata.
Pfad: BTRFS-Pool aus zwei NVMe (RAID1) als Ersatz für Single-XFS-Cache. Migration über Pool-Wechsel mit zwischengeschalteter Image-Sicherung. Eigenes Migrations-Dokument vor Beginn.
### 15.4 Erweiterung um zweite Array-Disk
Trigger: Disk1 > 80 % voll, oder Performance-Bedarf.
Pfad: zweite Daten-Disk dem Array hinzufügen, Allocation-Method überprüfen, Split-Level pro Share validieren. Parity-Disk muss ≥ größte Daten-Disk bleiben.
### 15.5 Multi-Host (Hermes-Skalierung)
Wenn Hermes-Worker auf weiteren Hosts skaliert: dieser Storage-Layout-Plan gilt zunächst nur für `Kallilabcore`. Weitere Hosts brauchen eigenes Storage-Layout-Dokument. Gemeinsamer Storage (NFS, iSCSI, S3-API) ist ein eigener Architektur-Schritt mit eigenem Dokument.
## 16. Glossar
| Begriff | Bedeutung |
|---------|-----------|
| **Cache (Pool)** | Schneller Pool (NVMe), nicht Teil des Arrays, ohne Parity-Schutz, für Performance-kritische Daten |
| **Array** | Klassische Unraid-Daten-Disks plus Parity, paritätsgeschützt, langsamer |
| **shfs** | Unraid-User-Share-FUSE-Filesystem; vereinigt Cache und Array unter `/mnt/user/` |
| **Mover** | Unraid-Service, der Daten zwischen Cache und Array gemäß Cache-Setting verschiebt |
| **Cache-Setting** | Pro Share: `only`, `prefer`, `yes`, `no` — steuert Mover-Verhalten |
| **Allocation Method** | High Water / Most Free / Fill Up — wie Daten auf Array-Disks verteilt werden |
| **Split Level** | Maximale Verzeichnistiefe, in der Unraid Inhalte über Disks aufteilen darf |
| **Pre-Backup-Hook** | Skript, das vor jedem Backup einen konsistenten DB-Dump erzeugt |
| **Posture-Check** | Automatisierter Audit, der Host-Realität gegen Soll-Zustand prüft |
| **Constitution** | Hard Rules in Abschnitt 12, nicht verhandelbar |
## 17. Referenzen
- `HOMELAB_ARCHITECTURE_MASTER_V2.md` — Gesamtarchitektur, Netze, Traefik, GitOps
- `docs/REPO_MAP.md` — Repo-Struktur, wo welcher Stack lebt
- `docs/SERVICE_CATALOG.md` — Pro Stack: Funktion, Abhängigkeiten, Eigenheiten
- `docs/RESTORE_MATRIX.md` — Pro Stack: Restore-Quelle und -Verfahren
- `docs/SECRETS_MAP.md` — Pro Secret: Speicherort, Rotation, Recovery
- `docs/DISASTER_RECOVERY.md` — Konkrete Recovery-Pläne, inkl. NTFS-Migration
- `docs/GITOPS_DRIFT_RUNBOOK.md` — Drift-Erkennung und -Behebung
- `docs/AI_CONTEXT.md` — Kontext für AI-Assistenten
## 18. Changelog
| Version | Datum | Änderung | Autor |
|---------|-------|----------|-------|
| 1.0 (Draft) | 2026-05-15 | Initialfassung nach Incident 2026-05-11. Ursprung: NTFS-Cache-Korruption, fehlende Posture-Checks, ungeplante Backup-Strategie. | Operator + AI-Assistenten |
| 1.1 (Draft) | 2026-05-15 | Operator-Review-Feedback eingearbeitet: `system`/`domains` auf `only`, `services` als recovery-kritisch markiert, `data/`-Behandlung pro Stack klassifiziert (statt blanket Exclude), Backup-Tooling Ist/Soll explizit getrennt, Posture-Check zusätzlich bei Boot/vor Backup/nach Mover, Hard Rules 11+12 ergänzt (Restore-Pfad-Pflicht, Posture-vor-Backup), Alarmziel-Optionen benannt, Review-Items in eigene Sektion §20 verschoben. | Operator + AI-Assistenten |
| 1.2 (Draft) | 2026-05-15 | Operator-Entscheidungen #3, #4, #6, #9, #11 eingearbeitet: Backrest abgeschaltet (Borg alleinige Backup-Technologie), persönliche Daten vollständig im Pflicht-Backup-Scope, ntfy als Alarmziel verbindlich, kebab-case-Migration im Rahmen der Recovery-Phase, Mirror-Backup für Gitea-Repo-Inhalte als verbindliche Spec (Implementierung in `SERVICES_RECOVERY.md` zu detaillieren). Offen: Items #1, #2, #5, #7, #8, #10. | Operator + AI-Assistenten |
| 1.3 (Draft) | 2026-05-15 | Operator-Entscheidungen #1, #7, #8 eingearbeitet: Disk-Größen/-Modelle als Deferred via Posture-Check-Detection (kein Blocker), optionale Stacks (Filebrowser, code-server, Speedtest, Scrutiny, Uptime-Kuma) bleiben im Layout und sind produktiv, Network-Verweis auf MASTER_V2 bestätigt. Damit alle akuten Items entschieden. Verbleibend: Items #2, #5, #10 (Retention, Schwellen-Kalibrierung, RESTORE_MATRIX-Klassifikation) — alle als Folge-Aufgaben über Inkraftsetzung hinaus, kein Commit-Blocker. | Operator + AI-Assistenten |
## 19. Inkraftsetzung
Dieses Dokument tritt in Kraft mit dem Commit der finalen Fassung in `master`-Branch des Repos `homelab-infra`. Ab Inkraftsetzung gelten alle Hard Rules; Soft Rules werden im Rahmen der laufenden Recovery-Arbeit etabliert; Posture-Check muss innerhalb von 14 Tagen nach Inkraftsetzung produktiv laufen.
Erste Audit-Review dieses Dokuments: spätestens 90 Tage nach Inkraftsetzung. Danach jährlich oder bei jeder strukturellen Änderung des Storage-Layouts.
## 20. Open Review Items (vor finalem Commit zu entscheiden)
Diese Sektion dokumentiert offene Operator-Entscheidungen und Lücken. **Vor Statuswechsel von Draft auf Active ist jeder Punkt entweder eingearbeitet oder bewusst als „bleibt offen" mit Verweis auf Folge-Issue/-Doc markiert.**
| Nr. | Item | Status | Verantwortung |
|-----|------|--------|---------------|
| 1 | Disk-Größen und Modelle in §3 (Disk1, Parity, externe Backup-Platte) | **DEFERRED 2026-05-15** — wird automatisch via Posture-Check-Detection-Lauf ergänzt; Disk1-Filesystem ist seit 2026-05-25 XFS | Operator + Posture-Check |
| 2 | Retention-Werte in §8.1 (Borg-Repos lokal/remote) — abhängig von tatsächlicher Storage-Kapazität | Vorschlag steht, anzupassen | Operator |
| 3 | Backup-Tooling: Backrest abschalten, Borg alleinige Backup-Technologie | **ENTSCHIEDEN 2026-05-15** | erledigt (siehe §8.0) |
| 4 | Backup-Scope für persönliche Daten: `documents`, `photos`, `finance`, `projekte` (und `media` falls behalten) **vollständig** im Pflicht-Scope | **ENTSCHIEDEN 2026-05-15** | erledigt (siehe §8.2) |
| 5 | Posture-Check-Schwellen in §11 — Vorschlag konservativ, nach erstem Monitoring kalibrieren | Vorschlag steht | Operator nach 30 Tagen |
| 6 | Alarmziel: ntfy als primärer Push-Kanal, Mail-Fallback als Folge-Erweiterung optional | **ENTSCHIEDEN 2026-05-15** | erledigt (siehe §11) |
| 7 | Optionale Stacks (Filebrowser, code-server, Speedtest, Scrutiny, Uptime-Kuma) — bleiben im Layout, sind produktiv im Scope, folgen den Standard-Konventionen aus §5 und Backup-Pflichten aus §8.2 | **ENTSCHIEDEN 2026-05-15** | erledigt |
| 8 | Verweis auf `HOMELAB_ARCHITECTURE_MASTER_V2.md` für Network-Architektur (§10) — Net-Architektur steht dort authoritativ, Verweis ist ausreichend | **ENTSCHIEDEN 2026-05-15** | erledigt (siehe §10) |
| 9 | Naming-Konvention: kebab-case durchziehen, Migration im Rahmen der Recovery-Phase | **ENTSCHIEDEN 2026-05-15** | erledigt (siehe §6); pro Stack in RESTORE_MATRIX.md zu dokumentieren |
| 10 | Pro-Stack-Klassifizierung in `RESTORE_MATRIX.md` (DB-Typ, Nutzdaten in `data/`, Dump-Verfahren, letzter Restore-Test, kebab-case-Migrationsname) — als Folge-Aufgabe aus Hard Rule §12.11 | Folge-Aufgabe | Operator + Recovery-Phase |
| 11 | Mirror-Backup für `services/gitea/git/repositories/` auf zweites Medium, ≤ 6 h Frequenz, konkrete Implementierung in `docs/SERVICES_RECOVERY.md` zu erstellen | **ENTSCHIEDEN 2026-05-15**, Implementierungs-Doc offen | Operator + Folge-Doc |
Wenn alle 11 Punkte bearbeitet sind und der Operator die Datei reviewed hat, wird sie als `docs/STORAGE_LAYOUT.md` (ohne `.draft`) committed und Status auf `Active` gesetzt.
+40 -18
View File
@@ -76,6 +76,9 @@ Vor lokaler Arbeit:
Nach lokaler Arbeit: Nach lokaler Arbeit:
1. Aenderungen pruefen 1. Aenderungen pruefen
2. bei Compose-/Backup-/Restore-Aenderungen relevante manuelle Repo-Checks ausfuehren
- `ops/policy-checks/check_repo.ps1`
- `ops/restore-tests/check-restore-freshness.ps1` oder gezielte Restore-Checks
2. Commit mit sauberer Nachricht 2. Commit mit sauberer Nachricht
3. `Push origin` 3. `Push origin`
4. Komodo-Webhook im Hinterkopf behalten 4. Komodo-Webhook im Hinterkopf behalten
@@ -112,6 +115,23 @@ Komodo ist in diesem Setup:
- Pushes koennen automatisch einen Komodo-Deploy ausloesen - Pushes koennen automatisch einen Komodo-Deploy ausloesen
- wenn Komodo und Git voneinander abweichen, gewinnt Git - wenn Komodo und Git voneinander abweichen, gewinnt Git
### Pflicht bei neuen Komodo-Stacks
Jeder neue produktive Komodo-Stack, der aus `Micha/homelab-infra` deployed wird, braucht einen aktiven Gitea-Webhook auf die aktuelle Komodo-Stack-ID.
Pflichtschritte beim Anlegen:
1. Stack in Komodo aus Gitea anlegen
2. `webhook_enabled` in Komodo aktivieren
3. passenden Gitea-Webhook fuer die aktuelle Stack-ID anlegen
4. Gitea-Hook gegen `http://komodo-core:9120/listener/github/stack/<stack-id>/deploy` pruefen
5. einen Push oder Test-Delivery ausloesen und `last_status`/Komodo-Deploy pruefen
6. Ausnahmen explizit dokumentieren
**Regel:** Kein neuer produktiver GitOps-Stack ohne funktionierenden Gitea->Komodo-Webhook. Bewusste Ausnahmen muessen im selben Aenderungsblock dokumentiert werden, inklusive Grund und Alternativ-Deploy-Weg.
Der Standardfall nutzt den globalen `KOMODO_WEBHOOK_SECRET` aus der Komodo-Host-`.env`, ausser Komodo zeigt fuer den Stack explizit ein eigenes per-Stack-Secret.
### Ausnahme: Komodo-Zugangsmodell ### Ausnahme: Komodo-Zugangsmodell
Komodo bleibt **bewusst** ohne zentrale Traefik-ForwardAuth-Middleware. Komodo bleibt **bewusst** ohne zentrale Traefik-ForwardAuth-Middleware.
@@ -184,6 +204,22 @@ Wenn Drift erkannt wird, gilt:
- oder Realitaet an Repo zurueckfuehren - oder Realitaet an Repo zurueckfuehren
4. erst danach weiterarbeiten 4. erst danach weiterarbeiten
### Pflichtcheck bei Drift-Verdacht
Vor jedem Reparaturversuch muessen die Ebenen getrennt geprueft werden:
1. lokaler Clone
2. Gitea `origin/master`
3. Komodo Stack Workspace
4. Docker Runtime
5. Host-Netzwerklistener
Das detaillierte Runbook steht in `docs/GITOPS_DRIFT_RUNBOOK.md`.
**Regel:** `HostConfig.PortBindings` allein beweist keinen aktiven Host-Port. Entscheidend sind `NetworkSettings.Ports`, `docker ps`, `ss -ltnp` und ein echter `curl` gegen den Host-Port.
**Stop-Regel:** Wenn zwei Reparaturversuche nicht zum erwarteten Ergebnis fuehren, keine weiteren Schreibbefehle ausfuehren. Erst die Pflichtmatrix aus dem Runbook ausfuellen.
--- ---
## Ausnahmefall: Hotfix auf dem Host ## Ausnahmefall: Hotfix auf dem Host
@@ -282,25 +318,11 @@ Nach jeder erfolgreichen Migration oder relevanten Aenderung muessen diese Datei
- `docs/MIGRATION_LOG.md` - `docs/MIGRATION_LOG.md`
- `docs/SECRETS_MAP.md` - `docs/SECRETS_MAP.md`
- `docs/ROLLBACK.md` - `docs/ROLLBACK.md`
- `docs/SERVICES_RECOVERY.md` falls `/mnt/user/services`, Gitea, Komodo oder Host-Automation betroffen sind
- `docs/HARDWARE_INVENTORY.md` und `docs/CAPACITY_AND_LIFECYCLE.md` falls Hardware, Disks, Cache, RAM oder USV betroffen sind
- `docs/NETWORK_INVENTORY.md` und `docs/EXTERNAL_DEPENDENCIES.md` falls Router, DNS, Tailscale, Portfreigaben oder Provider betroffen sind
- `HOMELAB_ARCHITECTURE_MASTER_V2.md` falls Architektur betroffen ist - `HOMELAB_ARCHITECTURE_MASTER_V2.md` falls Architektur betroffen ist
- `docs/GITOPS_DRIFT_RUNBOOK.md` falls GitOps-/Komodo-/Runtime-Drift betroffen ist
---
## Sprint-Regel
Jede Migration wird als Sprint behandelt. Ein Sprint umfasst immer:
1. Ist-Zustand pruefen
2. Zielzustand definieren
3. Compose-Datei im Repo anpassen
4. lokal synchronisieren und aendern
5. Commit + Push
6. Deploy ueber Komodo
7. Test
8. Doku aktualisieren
9. Sprint abschliessen
> Nie mehrere kritische Dienste gleichzeitig aendern.
--- ---
+103
View File
@@ -0,0 +1,103 @@
# Post-Install: Als Erstes Codex wieder startklar machen
Ziel: Nach der frischen Windows-Installation zuerst wieder mit Codex/ChatGPT weiterarbeiten koennen, bevor der restliche Wiederaufbau startet.
## 1. Internet herstellen
1. Windows starten.
2. LAN/WLAN verbinden.
3. Falls kein Internet vorhanden ist:
- externe Backup-HDD anschliessen
- Treiber aus `H:\Windows-Neuaufsetzen-Backup\13_Treiber_Windows` installieren
- besonders LAN/WLAN/Chipsatz pruefen
## 2. Browser starten
1. Microsoft Edge oeffnen.
2. Bei Microsoft/ChatGPT/Codex anmelden, je nachdem welche Variante genutzt wird.
3. Diese Datei auf H: oeffnen:
```text
H:\Windows-Neuaufsetzen-Backup\POSTINSTALL_ERSTES_ZIEL_CODEX.md
```
## 3. Basiswerkzeuge installieren
PowerShell als normaler Benutzer oeffnen.
```powershell
winget install --exact --id Git.Git --source winget --accept-package-agreements --accept-source-agreements
winget install --exact --id Microsoft.VisualStudioCode --source winget --accept-package-agreements --accept-source-agreements
winget install --exact --id OpenJS.NodeJS.LTS --source winget --accept-package-agreements --accept-source-agreements
```
Optional danach UniGetUI:
```powershell
winget install --exact --id Devolutions.UniGetUI --source winget --accept-package-agreements --accept-source-agreements
```
## 4. SSH und Git-Konfiguration zurueckholen
Backup-HDD muss angeschlossen sein.
```powershell
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.ssh" | Out-Null
Copy-Item -Path "H:\Windows-Neuaufsetzen-Backup\09_Programme_Settings_Lizenzen\ssh\*" -Destination "$env:USERPROFILE\.ssh" -Force
Copy-Item -Path "H:\Windows-Neuaufsetzen-Backup\09_Programme_Settings_Lizenzen\git\.gitconfig" -Destination "$env:USERPROFILE\.gitconfig" -Force
```
SSH-Key-Rechte pruefen:
```powershell
ssh -T git@github.com
```
Falls Gitea genutzt wird, stattdessen oder zusaetzlich den Gitea-Host testen.
## 5. Homelab-Repo wieder verfuegbar machen
Wenn `G:\Gitea_Clone\homelab-infra` noch existiert:
```powershell
cd /d G:\Gitea_Clone\homelab-infra
git status
```
Falls das Repo neu geklont werden muss:
```powershell
New-Item -ItemType Directory -Force -Path G:\Gitea_Clone | Out-Null
cd /d G:\Gitea_Clone
git clone <DEIN_GITEA_ODER_GITHUB_REPO_URL> homelab-infra
cd homelab-infra
git status
```
## 6. Codex-Kontext wieder aufnehmen
Wichtige Dateien:
```text
H:\Windows-Neuaufsetzen-Backup\12_Exportierte_Listen\installierte_programme_lesbar.md
H:\Windows-Neuaufsetzen-Backup\12_Exportierte_Listen\kritische_programme_lizenz_check.md
H:\Windows-Neuaufsetzen-Backup\09_Programme_Settings_Lizenzen\keys_exporte\banking4_license_private.txt
G:\Gitea_Clone\homelab-infra\docs\windows-neuaufsetzen-masterplan.md
```
Dann Codex/ChatGPT sagen:
```text
Windows ist frisch installiert. Bitte hilf mir mit dem Post-Install-Wiederaufbau anhand von H:\Windows-Neuaufsetzen-Backup und dem Masterplan.
```
## 7. Danach erst Programme wiederherstellen
Reihenfolge:
1. Banking4 installieren und Lizenz aus `banking4_license_private.txt` nutzen.
2. Banking4-Datentresor aus `H:\Windows-Neuaufsetzen-Backup\07_Banking_Finanzen\Banking4_Datentresor_explizit` oeffnen.
3. Microsoft 365 ueber Microsoft-Konto installieren.
4. WISO Steuer installieren und Steuerdateien aus `WISO_Steuer_Dokumente` pruefen.
5. Restliche Programme mit UniGetUI/WinGet/Installern wieder aufbauen.
+550
View File
@@ -0,0 +1,550 @@
# Windows neu aufsetzen: Masterplan ohne Datenverlust
Stand: 2026-05-07
Ziel: Windows sauber neu installieren, die Datenträgerstruktur bereinigen und wichtige Daten sicher erhalten.
Grundregel: Vor dem Löschen, Formatieren oder Neuinstallieren müssen mindestens zwei geprüfte Kopien der wichtigen Daten existieren.
## Aktueller Arbeitsstand
Stand: 2026-05-07, 15:00 Uhr
Erledigt:
- Backup-Ziel `H:\Windows-Neuaufsetzen-Backup` auf externer 8-TB-HDD erstellt.
- Inventarlisten exportiert nach `H:\Windows-Neuaufsetzen-Backup\12_Exportierte_Listen`.
- Installierte Programme inventarisiert: 161 Eintraege.
- Aktuelle Benutzerordner von `C:\Users\michi` gesichert: Desktop, Documents, Pictures, Videos, Downloads, Music.
- `C:\Users\michi\.ssh` und `C:\Users\michi\.gitconfig` gesichert.
- Alte Standardordner aus `D:\Users\Baerchen` gesichert, soweit vorhanden.
- Persoenliche/auffaellige Ordner von `F:` gesichert: `BMW Leasing`, `Marina Handy 2025`, `Marina Handy Backup`.
- Relevante Ordner von `G:` gesichert: `Gitea_Clone`, `open-webui`, `Treiber`.
- WSL-Distributionen exportiert: `Ubuntu.tar`, `docker-desktop.tar`.
- Browserprofile gesichert: Chrome und Edge.
- Kritische Programmdaten zusaetzlich gesichert:
- Banking4/Subsembly: `C:\Users\michi\AppData\Local\Subsembly`
- WISO/Buhl: `C:\Users\michi\AppData\Local\Buhl`, `C:\Users\michi\AppData\Local\Buhl Data Service GmbH`, `C:\ProgramData\Buhl Data Service GmbH`
- WISO-Steuerdateien: `C:\Users\michi\Documents\steuer`
- Banking-Exporte vom Desktop: `C:\Users\michi\Desktop\Banking`
- Registry-Exports fuer Subsembly und Microsoft Office erstellt; Buhl-Registry-Suchlisten erstellt.
- Banking4-Lizenzdaten separat gesichert: `H:\Windows-Neuaufsetzen-Backup\09_Programme_Settings_Lizenzen\keys_exporte\banking4_license_private.txt`.
- Aktueller Banking4-Datentresor separat gesichert: `H:\Windows-Neuaufsetzen-Backup\07_Banking_Finanzen\Banking4_Datentresor_explizit\Mein Datentresor.sub`.
- Office-Aktivierungsstatus exportiert: lokal als Microsoft 365/Office16 O365 Home Premium Grace/Notifications sichtbar, daher Microsoft-Konto/Abonnement manuell pruefen.
- Lesbare Programmlisten erstellt:
- `installierte_programme_lesbar.md`
- `kritische_programme_lizenz_check.md`
- UniGetUI/Keyfinder-Empfehlungen dokumentiert: `keyfinder_tools_recommendation.md`.
- Robocopy-Summenzeilen geprueft: keine Kopierfehler in den bekannten Backup-Jobs.
- Verifikationslisten erstellt:
- `backup_verification_known_data.csv`
- `backup_verification_browser_profiles.csv`
Noch offen:
- Manuelle Screenshots in `H:\Windows-Neuaufsetzen-Backup\14_Screenshots` ablegen.
- BitLocker-Status mit Adminrechten pruefen.
- Passwortmanager, 2FA-Recovery-Codes und Browser-Sync manuell pruefen.
- Banking4-Speicherort explizit pruefen.
- Banking4 im Programm selbst oeffnen und aktuellen Datentresor/Backup-Export bestaetigen. Der Key und der Datentresor sind bereits lokal auf H: gesichert.
- WISO Steuer 2026 oeffnen und Lizenz/Buhl-Konto sowie Speicherorte der Steuerdateien bestaetigen.
- Microsoft-Konto fuer M365 pruefen: Office-Webkonto/Abonnement, Installationsrecht, OneDrive-Sync.
- Optional Keyfinder-Lauf durchfuehren und Ergebnisse lokal auf H: speichern.
- `G:\Ollama` bewusst entscheiden: nicht gesichert, ca. 40,9 GB lokale Modell-/Cache-Daten.
- D:, F: und G: vor dem spaeteren Loeschen noch einmal in Ruhe final bestaetigen.
## Zielentscheidung: Neues Windows auf Datentraeger 0
Entscheidung vom 2026-05-07: Das neue Windows soll auf `Datentraeger 0` installiert werden.
Aktueller Zustand laut Datentraegerverwaltung:
| Datentraeger | Aktuelles Laufwerk | Groesse | Inhalt/Zweck |
|---|---:|---:|---|
| Datentraeger 0 | D: | ca. 167 GB | Alte Windows SSD |
| Datentraeger 1 | E: | ca. 167 GB | Blizzard Games |
| Datentraeger 2 | C: und F: | ca. 931 GB | aktuelles Windows + 980SSD-Partition |
| Datentraeger 3 | G: | ca. 931 GB | M2 SSD / Daten |
| Datentraeger 4 | H: | ca. 7,45 TB | externe Backup-HDD |
Bewertung:
- Machbar, wenn `Datentraeger 0` als reines Windows-/Programme-Laufwerk genutzt wird.
- Nicht ideal fuer sehr viele Programme/Games, weil nur ca. 167 GB vorhanden sind.
- Vorteil: Die aktuelle Windows-SSD auf `Datentraeger 2` bleibt waehrend der Migration zunaechst erhalten.
- Wichtig: Bei der Installation duerfen ausschliesslich Partitionen auf `Datentraeger 0` geloescht werden.
Empfohlenes Installationsverhalten:
1. Externe Backup-HDD `H:` vor der Windows-Installation abziehen.
2. Wenn praktisch moeglich: andere interne Datentraeger fuer die Installation abziehen oder im UEFI deaktivieren.
3. Im Windows-Setup `Benutzerdefiniert` waehlen.
4. `Datentraeger 0` anhand der Groesse ca. 167 GB identifizieren.
5. Nur auf `Datentraeger 0` alle Partitionen loeschen:
- 499 MB Wiederherstellung
- 100 MB nicht zugeordnet bleibt egal
- D: Alte Windows SSD
- 640 MB Wiederherstellung
6. Den dadurch komplett nicht zugeordneten Speicher auf `Datentraeger 0` auswaehlen.
7. Windows installieren lassen.
Nicht loeschen:
- `Datentraeger 1` / E: Blizzard Games
- `Datentraeger 2` / C: und F:
- `Datentraeger 3` / G:
- `Datentraeger 4` / H:
Nach der Installation:
- Bootreihenfolge im UEFI auf die neue Windows-Installation auf `Datentraeger 0` setzen.
- Altes Windows auf `Datentraeger 2` erst loeschen, wenn das neue System mehrere Tage stabil laeuft.
## UniGetUI fuer den Wiederaufbau
UniGetUI ist fuer den Wiederaufbau sinnvoll, aber nicht fuer Lizenz-Keys.
Nutzen:
- Programme ueber WinGet/Scoop/Chocolatey/Pip/NPM suchen und installieren.
- Updates zentral verwalten.
- Paketlisten importieren/exportieren.
Grenzen:
- Banking4, WISO Steuer und Microsoft 365 wurden im `winget export` nicht als sauber wiederinstallierbare Pakete abgedeckt.
- Lizenzkeys werden durch UniGetUI nicht gesichert.
Vorhanden:
- WinGet-Export: `H:\Windows-Neuaufsetzen-Backup\12_Exportierte_Listen\winget-export.json`
Nach Neuinstallation:
```powershell
winget install --exact --id Devolutions.UniGetUI --source winget
```
Danach kann die WinGet-Liste optional importiert werden:
```powershell
winget import --import-file "H:\Windows-Neuaufsetzen-Backup\12_Exportierte_Listen\winget-export.json" --accept-package-agreements --accept-source-agreements
```
Empfehlung: Nicht alles blind importieren. Erst Basisprogramme installieren, dann die exportierte Liste als Orientierung nutzen.
## Laufwerksannahmen
Diese Zuordnung muss vor dem Start geprüft werden.
| Laufwerk | Vermutung | Behandlung |
|---|---|---|
| C: | aktuelles Windows | sichern, danach neu aufsetzen |
| D: | alte Windows-SSD oder Altbestand | erst analysieren, nicht blind löschen |
| E: | Blizzard / Games | wahrscheinlich neu ladbar, Saves prüfen |
| F: | 980SSD, fast leer | liegt auf derselben physischen Samsung 980 PRO wie C: |
| G: | M.2 SSD, stark belegt | erst analysieren, wichtige Daten sichern |
| H: | externe 8-TB-HDD | Backup-Ziel |
Wichtige Erkenntnis aus dem Inventar vom 2026-05-07: `C:` und `F:` sind Partitionen auf derselben Samsung SSD 980 PRO 1TB. Wenn diese SSD als Ziel fuer die Neuinstallation genutzt wird, muss besonders sauber entschieden werden, welche Partitionen geloescht werden. `F:` ist kein eigener physischer Datentraeger.
## Phase 1: Backup-Struktur auf H: anlegen
Zielordner:
```text
H:\Windows-Neuaufsetzen-Backup\
|-- 01_Desktop
|-- 02_Dokumente
|-- 03_Bilder
|-- 04_Videos
|-- 05_Downloads_wichtig
|-- 06_Projekte
|-- 07_Banking_Finanzen
|-- 08_Browser_Lesezeichen_Profile
|-- 09_Programme_Settings_Lizenzen
|-- 10_Games_Savegames
|-- 11_Homelab_NAS_Doku
|-- 12_Exportierte_Listen
|-- 13_Treiber_Windows
|-- 14_Screenshots
|-- 15_Musik
`-- 99_Unsortiert_von_D_F_G
```
PowerShell:
```powershell
$BackupRoot = "H:\Windows-Neuaufsetzen-Backup"
$Folders = @(
"01_Desktop",
"02_Dokumente",
"03_Bilder",
"04_Videos",
"05_Downloads_wichtig",
"06_Projekte",
"07_Banking_Finanzen",
"08_Browser_Lesezeichen_Profile",
"09_Programme_Settings_Lizenzen",
"10_Games_Savegames",
"11_Homelab_NAS_Doku",
"12_Exportierte_Listen",
"13_Treiber_Windows",
"14_Screenshots",
"15_Musik",
"99_Unsortiert_von_D_F_G"
)
New-Item -ItemType Directory -Force -Path $BackupRoot | Out-Null
$Folders | ForEach-Object {
New-Item -ItemType Directory -Force -Path (Join-Path $BackupRoot $_) | Out-Null
}
```
Stop-Punkt: H: ist sichtbar, beschreibbar und hat genug freien Speicher.
## Phase 2: Inventar exportieren
### Installierte Programme
```powershell
$BackupRoot = "H:\Windows-Neuaufsetzen-Backup"
Get-ItemProperty `
HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*, `
HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* |
Select-Object DisplayName, DisplayVersion, Publisher, InstallDate |
Where-Object { $_.DisplayName } |
Sort-Object DisplayName |
Export-Csv "$BackupRoot\12_Exportierte_Listen\installierte_programme.csv" -NoTypeInformation -Encoding UTF8
```
Optional zusätzlich:
```powershell
winget list > "H:\Windows-Neuaufsetzen-Backup\12_Exportierte_Listen\winget-list.txt"
```
### Laufwerksübersicht
```powershell
Get-Volume |
Sort-Object DriveLetter |
Select-Object DriveLetter, FileSystemLabel, FileSystem, Size, SizeRemaining |
Export-Csv "H:\Windows-Neuaufsetzen-Backup\12_Exportierte_Listen\laufwerke.csv" -NoTypeInformation -Encoding UTF8
Get-Disk |
Select-Object Number, FriendlyName, SerialNumber, HealthStatus, Size, PartitionStyle |
Export-Csv "H:\Windows-Neuaufsetzen-Backup\12_Exportierte_Listen\datentraeger.csv" -NoTypeInformation -Encoding UTF8
```
### Windows-Aktivierung
```powershell
wmic path softwarelicensingservice get OA3xOriginalProductKey > "H:\Windows-Neuaufsetzen-Backup\12_Exportierte_Listen\windows_oem_key.txt"
```
Zusätzlich Screenshot speichern:
- Windows-Aktivierung
- Datenträgerverwaltung
- Apps & Features
- Gerätemanager
- Netzwerkadapter
Stop-Punkt: Programmliste, Laufwerkslisten und wichtige Screenshots liegen auf H:.
## Phase 3: Muss-Daten sichern
Die folgenden Daten haben Priorität.
### Benutzerordner
Passe `<Benutzername>` an.
```powershell
$User = "C:\Users\<Benutzername>"
$BackupRoot = "H:\Windows-Neuaufsetzen-Backup"
robocopy "$User\Desktop" "$BackupRoot\01_Desktop" /E /COPY:DAT /DCOPY:DAT /R:2 /W:2 /XJ /TEE /LOG:"$BackupRoot\12_Exportierte_Listen\backup_desktop.log"
robocopy "$User\Documents" "$BackupRoot\02_Dokumente" /E /COPY:DAT /DCOPY:DAT /R:2 /W:2 /XJ /TEE /LOG:"$BackupRoot\12_Exportierte_Listen\backup_dokumente.log"
robocopy "$User\Pictures" "$BackupRoot\03_Bilder" /E /COPY:DAT /DCOPY:DAT /R:2 /W:2 /XJ /TEE /LOG:"$BackupRoot\12_Exportierte_Listen\backup_bilder.log"
robocopy "$User\Videos" "$BackupRoot\04_Videos" /E /COPY:DAT /DCOPY:DAT /R:2 /W:2 /XJ /TEE /LOG:"$BackupRoot\12_Exportierte_Listen\backup_videos.log"
robocopy "$User\Music" "$BackupRoot\15_Musik" /E /COPY:DAT /DCOPY:DAT /R:2 /W:2 /XJ /TEE /LOG:"$BackupRoot\12_Exportierte_Listen\backup_music.log"
```
Downloads nicht blind komplett übernehmen. Erst wichtige Installer, PDFs, ZIPs, Rechnungen, Exporte und persönliche Dateien aussortieren.
### Kritische versteckte Daten
Prüfen und bei Bedarf sichern:
| Pfad | Warum |
|---|---|
| `C:\Users\<Benutzername>\.ssh` | SSH Keys |
| `C:\Users\<Benutzername>\.gitconfig` | Git-Konfiguration |
| `C:\Users\<Benutzername>\AppData\Roaming` | wichtige App-Einstellungen |
| `C:\Users\<Benutzername>\AppData\Local` | Browserprofile, App-Daten |
| `C:\ProgramData` | gemeinsame App-Daten, Lizenzen |
| `C:\Users\<Benutzername>\Documents\Outlook-Dateien` | PST/Outlook-Archive |
Empfehlung: AppData nur sichern, später aber nicht komplett zurückkopieren.
## Phase 4: Spezialdaten prüfen
Diese Daten sind leicht zu übersehen.
- Banking4-Daten, Exporte, Tresore
- Passwortmanager-Backups oder lokale Datenbanken
- 2FA-Recovery-Codes
- Browser-Lesezeichen-Export
- lokale Spielstände ohne Cloud-Sync
- Steuerunterlagen
- Verträge und Rechnungen
- GPG/PGP Keys
- Zertifikate
- VPN-Profile
- API-Keys und `.env` Dateien
- Homelab-, NAS- und Router-Dokumentation
- Docker Desktop Daten
- WSL-Distributionen
- virtuelle Maschinen
### WSL exportieren, falls genutzt
```powershell
wsl --list --verbose > "H:\Windows-Neuaufsetzen-Backup\12_Exportierte_Listen\wsl-distros.txt"
wsl --export <DistroName> "H:\Windows-Neuaufsetzen-Backup\09_Programme_Settings_Lizenzen\<DistroName>.tar"
```
## Phase 5: D:, F: und G: analysieren
Nicht löschen. Erst suchen.
PowerShell:
```powershell
$BackupRoot = "H:\Windows-Neuaufsetzen-Backup"
$SearchRoots = @("D:\", "F:\", "G:\")
$Patterns = @(
"Users",
"Windows.old",
"Dokumente",
"Documents",
"Bilder",
"Pictures",
"Desktop",
"Downloads",
"Projekte",
"Projects",
"Backup",
"NAS",
"Git",
"Python",
"Banking",
"Steuern",
"Vertraege",
"Verträge"
)
foreach ($Root in $SearchRoots) {
if (Test-Path $Root) {
Get-ChildItem -Path $Root -Directory -ErrorAction SilentlyContinue |
Select-Object FullName, LastWriteTime |
Export-Csv "$BackupRoot\12_Exportierte_Listen\top_level_$($Root[0]).csv" -NoTypeInformation -Encoding UTF8
foreach ($Pattern in $Patterns) {
Get-ChildItem -Path $Root -Recurse -Directory -ErrorAction SilentlyContinue -Filter "*$Pattern*" |
Select-Object FullName, LastWriteTime |
Export-Csv "$BackupRoot\12_Exportierte_Listen\fundstellen_$($Root[0])_$Pattern.csv" -NoTypeInformation -Encoding UTF8
}
}
}
```
Alles Unklare zuerst nach `H:\Windows-Neuaufsetzen-Backup\99_Unsortiert_von_D_F_G\` sichern.
Stop-Punkt: Für D:, F: und G: ist klar, was wichtig ist, was neu installiert werden kann und was später gelöscht werden darf.
## Phase 6: Treiber und Installationsmedien vorbereiten
Vorher herunterladen und auf H: speichern:
- Windows 11 Media Creation Tool
- Mainboard-Chipsatztreiber
- LAN-Treiber
- WLAN-Treiber
- GPU-Treiber
- Audio-Treiber
- Bluetooth-Treiber, falls relevant
- Drucker-/Scanner-Treiber, falls relevant
Minimum: LAN/WLAN-Treiber müssen offline verfügbar sein.
Stop-Punkt: Windows-USB-Stick funktioniert und Netzwerk-Treiber liegen auf H:.
## Phase 7: Backup prüfen
Pflichtprüfung:
- H: abziehen und wieder anschließen
- mehrere Bilder öffnen
- mehrere PDFs öffnen
- Office-Dateien öffnen
- Projektordner prüfen
- Banking-/Finanzdaten prüfen
- SSH-Key-Ordner prüfen
- Programmliste öffnen
- Robocopy-Logs auf Fehler prüfen
Optional Ordnergrößen vergleichen:
```powershell
Get-ChildItem "C:\Users\<Benutzername>\Documents" -Recurse -Force -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum
Get-ChildItem "H:\Windows-Neuaufsetzen-Backup\02_Dokumente" -Recurse -Force -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum
```
Go/No-Go:
- Go: wichtige Daten sind auf H: lesbar und mindestens die kritischsten Daten existieren zusätzlich auf NAS, Cloud oder zweiter Platte.
- No-Go: unbekannte Daten auf D:, F: oder G:, fehlende Browser-/Passwort-/2FA-Sicherung, unklarer Banking4-Speicherort, kein funktionierender Netzwerk-Treiber.
## Phase 8: Ziel-SSD für Windows festlegen
Empfehlung:
- Eine schnelle, zuverlässige SSD als neues C:
- Games, Daten und Projekte getrennt halten
- Alte Windows-SSD erst später löschen
Vermutlicher Kandidat:
- Samsung SSD 980 PRO 1TB, falls sie bewusst komplett als neue System-SSD neu partitioniert werden soll
- WDC WDS100T2B0C 1TB, falls die aktuelle M.2-Datenplatte nach vollstaendiger Sicherung als neues Systemlaufwerk dienen soll
- nicht einfach `F:` auswaehlen, ohne die physische SSD-Struktur zu beachten, da `F:` und `C:` auf derselben SSD liegen
Vor der Installation ideal:
- Nur Ziel-SSD angeschlossen lassen
- Backup-HDD H: abziehen
- andere interne Laufwerke abziehen, falls praktisch möglich
Das verhindert, dass Windows Bootpartitionen auf dem falschen Datenträger ablegt.
## Phase 9: Windows neu installieren
Installation:
1. Vom Windows-USB-Stick booten.
2. Benutzerdefinierte Installation wählen.
3. Ziel-SSD eindeutig identifizieren.
4. Nur auf der Ziel-SSD alte Partitionen löschen.
5. Nicht zugeordneten Speicher auf der Ziel-SSD auswählen.
6. Windows installieren.
Nicht anfassen:
- externe Backup-HDD
- Datenlaufwerke
- alte Windows-SSD, solange sie nicht final geprüft wurde
## Phase 10: Ersteinrichtung
Direkt nach der Installation:
- Windows Update vollständig laufen lassen
- Chipsatztreiber installieren
- GPU-Treiber installieren
- LAN/WLAN prüfen
- Windows-Aktivierung prüfen
- Laufwerksbuchstaben sauber vergeben
- Windows Defender und Firewall prüfen
- BitLocker bewusst aktivieren oder deaktiviert lassen
- Wiederherstellungspunkt erstellen
Basisprogramme:
- Browser
- Passwortmanager
- 7-Zip
- Office oder LibreOffice
- Banking4
- Git
- VS Code / Codex / Dev-Tools
- Docker Desktop / WSL, falls benötigt
- Trading-/Finanztools
- Drucker/Scanner
- Steam / Battle.net
## Phase 11: Daten kontrolliert zurückholen
Zuerst:
- Dokumente
- Bilder
- Projekte
- Finanzen
- Desktop
- wichtige Downloads
- SSH Keys
- Browser-Lesezeichen
Danach gezielt:
- einzelne App-Konfigurationen
- Spielstände
- WSL-Distributionen
- Docker-Daten
- Outlook/PST
Nicht tun:
- `AppData` komplett zurückkopieren
- alte Windows-Ordner zurückmischen
- Programme aus alten Ordnern starten statt neu installieren
## Phase 12: Alte Datenträger bereinigen
Erst nach mehreren Tagen stabiler Nutzung:
- D: alte Windows-SSD final prüfen
- alte Benutzerordner gezielt archivieren oder löschen
- alte Windows-/Recovery-/EFI-Partitionen nur löschen, wenn sicher nicht davon gebootet wird
- Games-Laufwerke neu strukturieren
- Datenlaufwerke sinnvoll benennen
Zielstruktur:
| Laufwerk | Zweck |
|---|---|
| C: | Windows + Programme |
| D: | Daten / Projekte |
| E: | Games |
| F: | Arbeits-SSD / schnelle Daten |
| H: | Backup extern |
## Finale Checkliste vor dem Löschen
- [ ] Backup-Struktur auf H: erstellt
- [ ] Programmliste exportiert
- [ ] Laufwerksliste exportiert
- [ ] Windows-Aktivierung dokumentiert
- [ ] Benutzerordner gesichert
- [ ] Browser-Lesezeichen exportiert oder Sync geprüft
- [ ] Passwortmanager geprüft
- [ ] 2FA-Recovery-Codes gesichert
- [ ] SSH/API/GPG/Zertifikate gesichert
- [ ] Banking4-Speicherort geprüft und gesichert
- [ ] Homelab-/NAS-Doku gesichert
- [ ] D:, F: und G: analysiert
- [ ] Unklare Daten nach `99_Unsortiert_von_D_F_G` kopiert
- [ ] LAN/WLAN-Treiber auf H: gespeichert
- [ ] Windows-USB-Stick erstellt
- [ ] Backup-Dateien stichprobenartig geöffnet
- [ ] Kritische Daten zusätzlich auf NAS, Cloud oder zweiter Platte gesichert
- [ ] Ziel-SSD eindeutig festgelegt
Erst wenn alle Punkte erledigt sind, ist die Neuinstallation freigegeben.
+4
View File
@@ -0,0 +1,4 @@
BASE_DOMAIN=kaleschke.info
TRAEFIK_DOMAIN=traefik.kaleschke.info
AUTH_DOMAIN=auth.kaleschke.info
GLANCE_DOMAIN=glance.kaleschke.info
+3
View File
@@ -0,0 +1,3 @@
TZ=Europe/Berlin
PUID=99
PGID=100
+2 -2
View File
@@ -1,6 +1,6 @@
services: services:
adguard: adguard:
image: adguard/adguardhome:v0.107.52 image: adguard/adguardhome:v0.107.52@sha256:d16cc7517ab96f843e7f8bf8826402dba98f5e6b175858920296243332391589
container_name: adguard container_name: adguard
restart: unless-stopped restart: unless-stopped
volumes: volumes:
@@ -13,7 +13,7 @@ services:
ports: ports:
- "53:53/tcp" - "53:53/tcp"
- "53:53/udp" - "53:53/udp"
- "8082:80" - "100.80.98.33:8082:80"
security_opt: security_opt:
- no-new-privileges:true - no-new-privileges:true
View File
+19
View File
@@ -0,0 +1,19 @@
services:
plex:
image: plexinc/pms-docker:1.43.1.10611-1e34174b1@sha256:8b5bcdf7b506fe051aa1a0a0d464efdb3ad8c0fb1f8a4dfb27a8c489b609920c
container_name: plex
restart: unless-stopped
network_mode: host
environment:
TZ: Europe/Berlin
PLEX_UID: "99"
PLEX_GID: "100"
CHANGE_CONFIG_DIR_OWNERSHIP: "true"
PLEX_CLAIM: ${PLEX_CLAIM:-}
volumes:
- /mnt/user/appdata/plex/config:/config
- /mnt/user/appdata/plex/transcode:/transcode
- /mnt/user/media:/data:ro
- /mnt/user/photos:/photos:ro
security_opt:
- no-new-privileges:true
View File
+2
View File
@@ -3,6 +3,8 @@ services:
image: ghcr.io/qdm12/ddns-updater:latest@sha256:ee16ab4f6203bf9e5b0925d38a0b4ebf2d9f23771f933cfb2f5a2dbd5f9a2f88 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:
- no-new-privileges:true
networks: networks:
- frontend_net - frontend_net
environment: environment:
View File
View File
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
postgresql17: postgresql17:
image: postgres:17 image: postgres:17.9@sha256:5b96f1a16bd9768b060dd2ffe55cb6225c4d9ef4d214a8b21eb08134869a97e4
container_name: postgresql17 container_name: postgresql17
restart: unless-stopped restart: unless-stopped
View File
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
redis: redis:
image: redis:7-alpine image: redis:7.4-alpine@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99
container_name: Redis container_name: Redis
restart: unless-stopped restart: unless-stopped
command: command:
+91
View File
@@ -0,0 +1,91 @@
# Monitoring Stack
Zielzustand: ein zentraler Observability-Stack fuer KalliLab CORE.
## Enthaltene Dienste
- `monitoring-grafana`: zentrale UI unter `https://monitoring.kaleschke.info`
- `monitoring-prometheus`: Metriken mit 30 Tagen Retention
- `monitoring-alertmanager`: Alert-Routing fuer Prometheus-Regeln
- `monitoring-alertmanager-ntfy-bridge`: uebersetzt Alertmanager-Webhooks zu ntfy-Pushes
- `monitoring-loki`: Container-Logs mit 30 Tagen Retention
- `monitoring-promtail`: Docker-Log-Discovery ueber read-only Docker-Socket
- `monitoring-node-exporter`: Host-Metriken
- `monitoring-cadvisor`: Container-Metriken
- `monitoring-blackbox-exporter`: externe HTTP-Erreichbarkeit als Uptime-Kuma-Ersatz
- `monitoring-influxdb3-core`: InfluxDB 3 Core fuer Home-Assistant-/Ecowitt-Langzeitdaten
Die alten Pfade `ops/loki` und `ops/grafana-influxdb` wurden am 2026-05-26 aus dem aktiven Repo entfernt. Rollback erfolgt bei Bedarf ueber Git-Historie, nicht ueber parallel gepflegte Compose-Verzeichnisse.
Live-Stand 2026-05-25: die zehn `monitoring-*` Container laufen produktiv, die alten Container `grafana`, `influxdb3-core`, `loki` und `alloy` sind in Docker nicht mehr vorhanden. Uptime Kuma ist durch Blackbox Exporter, Prometheus-Alerts und das Dashboard `Homelab / Availability` abgeloest.
## Secrets
Vor dem Deploy muessen diese Host-Dateien existieren:
```text
/mnt/user/appdata/secrets/monitoring_grafana_admin_password.txt
/mnt/user/appdata/secrets/monitoring_grafana_influxdb_token.txt
/mnt/user/appdata/secrets/influxdb3_admin_token.json
```
Alle Dateien mit Rechten `600` anlegen. Werte niemals ins Git schreiben.
`monitoring-influxdb3-core` uebernimmt bewusst `/mnt/user/appdata/influxdb3/data` und `/mnt/user/appdata/influxdb3/plugins` vom bisherigen Grafana/Influx-Stack, damit Home-Assistant-/Ecowitt-Historie und Token-Katalog erhalten bleiben.
## Stack Environment
Default ist sicher lokal:
```env
INFLUXDB_BIND_IP=127.0.0.1
```
Wenn Home Assistant aus der VM schreiben soll, in Komodo fuer den `monitoring`-Stack setzen:
```env
INFLUXDB_BIND_IP=192.168.178.58
```
## Migration
1. Secrets anlegen. Erledigt.
2. Alten `ops/loki`-Stack stoppen, wenn `monitoring-loki` und `monitoring-promtail` live gehen. Erledigt.
3. Alten `ops/grafana-influxdb`-Stack stoppen, bevor `monitoring-influxdb3-core` den LAN-Port `192.168.178.58:8181` uebernimmt. Erledigt.
4. `monitoring` via Komodo deployen und `INFLUXDB_BIND_IP=192.168.178.58` erst setzen, wenn der Altcontainer den Port freigegeben hat. Erledigt.
5. Alte Repo-Verzeichnisse `ops/loki` und `ops/grafana-influxdb` entfernen. Erledigt.
6. Optionales Dashboard-Bootstrap-Profil einmalig ausfuehren.
7. Home Assistant Writer gegen `http://192.168.178.58:8181/` pruefen; `401 Unauthorized` ohne Token ist erwartbar.
## Smoke-Tests
- `https://monitoring.kaleschke.info` leitet zu Authelia.
- Grafana-Datasources `Prometheus`, `Loki` und `InfluxDB 3 Core` testen erfolgreich.
- Prometheus Targets: `prometheus`, `node-exporter`, `cadvisor`, `traefik`, `blackbox-http`.
- Alertmanager ist erreichbar und sendet ueber `monitoring-alertmanager-ntfy-bridge` nach `https://ntfy.kaleschke.info/homelab-alerts`.
- Loki zeigt Container-Logs mit Labels `container`, `compose_project`, `compose_service`.
- InfluxDB 3 Core enthaelt die Datenbank `homelab`.
## Abloesestand
- Dozzle bleibt abgeloest: `Homelab / Containers + Logs` ersetzt Live-Logs und Error-Rate.
- Glances erst stoppen, wenn `Homelab / Host Overview` und `Homelab / Containers + Logs` fuer CPU, RAM, Disk, Network, Container-CPU und Container-RAM passen.
- Uptime Kuma ist entfernt; `Homelab / Availability`, Blackbox Exporter und Prometheus-Alerts sind der Zielzustand fuer HTTP-Verfuegbarkeit.
- Dashboard-Zielbestand: `Homelab / Availability`, `Homelab / Containers + Logs`, `Homelab / Host Overview`, `Traefik Official Standalone Dashboard`.
## Alerting
Prometheus wertet `monitoring/prometheus/alerts.yml` aus und sendet an `monitoring-alertmanager`.
Alertmanager routet alle Alerts an den ntfy-Bridge-Container.
Der Bridge-Container postet nach `https://ntfy.kaleschke.info/homelab-alerts`.
Blackbox-HTTP-Alerts unterscheiden zwischen einem einzelnen kaputten Endpoint und einem externen Connectivity-Problem:
- `HomelabExternalConnectivityDown` feuert, wenn mindestens 5 Public-Endpoints gleichzeitig fuer 8 Minuten nicht erreichbar sind. Das deckt WAN-, DNS- oder Provider-Ausfaelle ab, inklusive laengerer DSL-Reconnects.
- `HomelabEndpointDown` feuert fuer einzelne Endpoints erst nach 8 Minuten und wird unterdrueckt, solange der Sammelalert aktiv ist. Dadurch erzeugt ein Telekom-24h-Reconnect keine ntfy-Flut pro Domain.
Test:
```bash
curl -fsS http://alertmanager-ntfy-bridge:8080/healthz
```
@@ -0,0 +1,112 @@
import json
import os
import sys
import urllib.error
import urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
NTFY_URL = os.environ.get("NTFY_URL", "https://ntfy.kaleschke.info/homelab-alerts")
def priority_for(status, severity):
if status == "resolved":
return "2"
if severity == "critical":
return "5"
if severity == "warning":
return "4"
return "3"
def tags_for(status, severity):
if status == "resolved":
return "white_check_mark"
if severity == "critical":
return "rotating_light"
if severity == "warning":
return "warning"
return "information_source"
def alert_message(alert):
labels = alert.get("labels", {})
annotations = alert.get("annotations", {})
status = alert.get("status", "firing")
severity = labels.get("severity", "info")
alertname = labels.get("alertname", "Alert")
target = labels.get("instance") or labels.get("service") or labels.get("mountpoint") or "homelab"
summary = annotations.get("summary") or alertname
description = annotations.get("description") or ""
title = f"{status.upper()} {severity}: {alertname}"
lines = [
summary,
f"Target: {target}",
]
if description and description != summary:
lines.append(description)
return title, "\n".join(lines), priority_for(status, severity), tags_for(status, severity)
def send_ntfy(title, message, priority, tags):
req = urllib.request.Request(
NTFY_URL,
data=message.encode("utf-8"),
headers={
"Title": title,
"Priority": priority,
"Tags": tags,
},
method="POST",
)
with urllib.request.urlopen(req, timeout=15) as response:
response.read()
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/healthz":
self.send_response(200)
self.end_headers()
self.wfile.write(b"ok\n")
return
self.send_response(404)
self.end_headers()
def do_POST(self):
if self.path != "/alertmanager":
self.send_response(404)
self.end_headers()
return
length = int(self.headers.get("Content-Length", "0"))
payload = json.loads(self.rfile.read(length) or b"{}")
alerts = payload.get("alerts", [])
sent = 0
for alert in alerts:
title, message, priority, tags = alert_message(alert)
try:
send_ntfy(title, message, priority, tags)
sent += 1
except urllib.error.URLError as exc:
print(f"ntfy send failed: {exc}", file=sys.stderr, flush=True)
self.send_response(502)
self.end_headers()
self.wfile.write(b"ntfy send failed\n")
return
print(f"sent {sent} ntfy notifications", flush=True)
self.send_response(200)
self.end_headers()
self.wfile.write(f"sent {sent}\n".encode("utf-8"))
def log_message(self, fmt, *args):
print(fmt % args, flush=True)
if __name__ == "__main__":
server = ThreadingHTTPServer(("0.0.0.0", 8080), Handler)
print(f"alertmanager ntfy bridge listening on :8080 -> {NTFY_URL}", flush=True)
server.serve_forever()
+25
View File
@@ -0,0 +1,25 @@
global:
resolve_timeout: 5m
route:
receiver: ntfy-homelab
group_by:
- alertname
- severity
group_wait: 20s
group_interval: 5m
repeat_interval: 4h
routes:
- receiver: ntfy-homelab
matchers:
- severity="critical"
group_wait: 10s
group_interval: 2m
repeat_interval: 30m
receivers:
- name: ntfy-homelab
webhook_configs:
- url: http://alertmanager-ntfy-bridge:8080/alertmanager
send_resolved: true
max_alerts: 10
+24
View File
@@ -0,0 +1,24 @@
modules:
http_reachable:
prober: http
timeout: 10s
http:
valid_status_codes:
- 200
- 204
- 301
- 302
- 303
- 307
- 308
- 401
- 403
valid_http_versions:
- HTTP/1.1
- HTTP/2.0
follow_redirects: true
preferred_ip_protocol: ip4
tcp_connect:
prober: tcp
timeout: 5s
+364
View File
@@ -0,0 +1,364 @@
services:
prometheus:
image: prom/prometheus:v3.7.3
container_name: monitoring-prometheus
restart: unless-stopped
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=30d
- --web.enable-lifecycle
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./prometheus/alerts.yml:/etc/prometheus/alerts.yml:ro
- prometheus_data:/prometheus
networks:
- monitoring_net
expose:
- "9090"
security_opt:
- no-new-privileges:true
depends_on:
- alertmanager
- blackbox-exporter
- node-exporter
- cadvisor
alertmanager:
image: prom/alertmanager:v0.28.1
container_name: monitoring-alertmanager
restart: unless-stopped
command:
- --config.file=/etc/alertmanager/alertmanager.yml
- --storage.path=/alertmanager
volumes:
- ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
- alertmanager_data:/alertmanager
networks:
- monitoring_net
expose:
- "9093"
security_opt:
- no-new-privileges:true
alertmanager-ntfy-bridge:
image: python:3.13-alpine
container_name: monitoring-alertmanager-ntfy-bridge
restart: unless-stopped
dns:
- 1.1.1.1
- 8.8.8.8
environment:
NTFY_URL: https://ntfy.kaleschke.info/homelab-alerts
command:
- python
- /app/bridge.py
volumes:
- ./alertmanager-ntfy-bridge/bridge.py:/app/bridge.py:ro
networks:
- monitoring_net
expose:
- "8080"
security_opt:
- no-new-privileges:true
blackbox-exporter:
image: prom/blackbox-exporter:v0.27.0
container_name: monitoring-blackbox-exporter
restart: unless-stopped
dns:
- 1.1.1.1
- 8.8.8.8
command:
- --config.file=/etc/blackbox_exporter/blackbox.yml
volumes:
- ./blackbox/blackbox.yml:/etc/blackbox_exporter/blackbox.yml:ro
networks:
- monitoring_net
expose:
- "9115"
security_opt:
- no-new-privileges:true
loki:
image: grafana/loki:3.7.2
container_name: monitoring-loki
restart: unless-stopped
command:
- -config.file=/etc/loki/loki-config.yml
volumes:
- ./loki/loki-config.yml:/etc/loki/loki-config.yml:ro
- loki_data:/loki
networks:
- monitoring_net
expose:
- "3100"
security_opt:
- no-new-privileges:true
promtail:
image: grafana/promtail:3.6.10
container_name: monitoring-promtail
restart: unless-stopped
command:
- -config.file=/etc/promtail/promtail-config.yml
volumes:
- ./promtail/promtail-config.yml:/etc/promtail/promtail-config.yml:ro
- promtail_positions:/positions
- /var/run/docker.sock:/var/run/docker.sock:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
networks:
- monitoring_net
security_opt:
- no-new-privileges:true
depends_on:
- loki
grafana:
image: grafana/grafana:12.4.3
container_name: monitoring-grafana
restart: unless-stopped
dns:
- 1.1.1.1
- 8.8.8.8
environment:
GF_SERVER_ROOT_URL: https://monitoring.kaleschke.info/
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD__FILE: /run/secrets/monitoring_grafana_admin_password
GF_USERS_ALLOW_SIGN_UP: "false"
GF_AUTH_ANONYMOUS_ENABLED: "false"
entrypoint:
- /bin/sh
- -c
- |
export GRAFANA_INFLUXDB_TOKEN="$$(cat /run/secrets/monitoring_grafana_influxdb_token)"
exec /run.sh
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
networks:
- monitoring_net
- frontend_net
secrets:
- monitoring_grafana_admin_password
- monitoring_grafana_influxdb_token
expose:
- "3000"
security_opt:
- no-new-privileges:true
depends_on:
- prometheus
- loki
- influxdb3-core
labels:
- traefik.enable=true
- traefik.docker.network=frontend_net
- traefik.http.routers.monitoring-grafana.rule=Host(`monitoring.kaleschke.info`)
- traefik.http.routers.monitoring-grafana.entrypoints=websecure
- traefik.http.routers.monitoring-grafana.tls=true
- traefik.http.routers.monitoring-grafana.tls.certresolver=le
- traefik.http.routers.monitoring-grafana.middlewares=authelia@file,secure-headers@file
- traefik.http.services.monitoring-grafana.loadbalancer.server.port=3000
grafana-dashboard-importer:
image: python:3.13-alpine
container_name: monitoring-grafana-dashboard-importer
restart: "no"
profiles:
- bootstrap
dns:
- 1.1.1.1
- 8.8.8.8
networks:
- monitoring_net
- frontend_net
security_opt:
- no-new-privileges:true
depends_on:
- grafana
secrets:
- monitoring_grafana_admin_password
command:
- /bin/sh
- -c
- |
python - <<'PY'
import base64
import json
import time
import urllib.error
import urllib.request
grafana_url = "http://grafana:3000"
with open("/run/secrets/monitoring_grafana_admin_password", encoding="utf-8") as secret:
password = secret.read().strip()
auth = base64.b64encode(f"admin:{password}".encode()).decode()
headers = {
"Authorization": f"Basic {auth}",
"Content-Type": "application/json",
}
def request(path, payload=None, timeout=20):
data = None if payload is None else json.dumps(payload).encode()
req = urllib.request.Request(f"{grafana_url}{path}", data=data, headers=headers)
if payload is not None:
req.method = "POST"
with urllib.request.urlopen(req, timeout=timeout) as response:
body = response.read()
return json.loads(body.decode() or "{}") if body else {}
def import_dashboard(payload, dashboard_id):
for attempt in range(1, 7):
try:
return request("/api/dashboards/import", payload)
except urllib.error.HTTPError as exc:
body = exc.read().decode(errors="replace")[:300]
if attempt == 6:
raise RuntimeError(f"Dashboard {dashboard_id} import failed: {exc.code} {body}") from exc
print(f"Dashboard {dashboard_id} import attempt {attempt} failed: HTTP {exc.code} {body}")
time.sleep(5)
except Exception as exc:
if attempt == 6:
raise
print(f"Dashboard {dashboard_id} import attempt {attempt} failed: {exc}")
time.sleep(5)
for _ in range(60):
try:
request("/api/health", timeout=5)
break
except Exception:
time.sleep(2)
else:
raise SystemExit("Grafana did not become ready in time")
dashboards = [
(17346, "Prometheus"),
]
def fetch_dashboard(dashboard_id):
url = f"https://grafana.com/api/dashboards/{dashboard_id}/revisions/latest/download"
for attempt in range(1, 7):
try:
with urllib.request.urlopen(url, timeout=30) as response:
return json.loads(response.read().decode())
except Exception as exc:
if attempt == 6:
raise
print(f"Dashboard {dashboard_id} download attempt {attempt} failed: {exc}")
time.sleep(5)
for dashboard_id, default_datasource in dashboards:
dashboard = fetch_dashboard(dashboard_id)
inputs = []
for item in dashboard.get("__inputs", []):
plugin_id = item.get("pluginId", "").lower()
value = "Loki" if plugin_id == "loki" or default_datasource == "Loki" else "Prometheus"
inputs.append({
"name": item.get("name"),
"type": item.get("type", "datasource"),
"pluginId": item.get("pluginId", "prometheus"),
"value": value,
})
import_dashboard({
"dashboard": dashboard,
"overwrite": True,
"inputs": inputs,
}, dashboard_id)
print(f"Imported Grafana dashboard {dashboard_id}")
PY
echo "Dashboard import complete."
node-exporter:
image: prom/node-exporter:v1.9.1
container_name: monitoring-node-exporter
restart: unless-stopped
command:
- --path.procfs=/host/proc
- --path.sysfs=/host/sys
- --path.rootfs=/rootfs
- --collector.filesystem.mount-points-exclude=^/(dev|proc|sys|run|var/lib/docker/.+|var/lib/containers/storage/.+)($|/)
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
networks:
- monitoring_net
expose:
- "9100"
security_opt:
- no-new-privileges:true
cadvisor:
image: ghcr.io/google/cadvisor:v0.53.0
container_name: monitoring-cadvisor
restart: unless-stopped
command:
- --docker_only=true
- --housekeeping_interval=30s
- --store_container_labels=false
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker:/var/lib/docker:ro
- /dev/disk:/dev/disk:ro
networks:
- monitoring_net
expose:
- "8080"
security_opt:
- no-new-privileges:true
influxdb3-core:
image: influxdb:3.9.1-core@sha256:1d58c8b9ac90153ae3a020ede2810c8284933dda50ac71e7573389ab6f012128
container_name: monitoring-influxdb3-core
user: "0"
restart: unless-stopped
ports:
- "${INFLUXDB_BIND_IP:-127.0.0.1}:8181:8181"
command:
- influxdb3
- serve
- --node-id=kallilabcore
- --object-store=file
- --data-dir=/var/lib/influxdb3/data
- --plugin-dir=/var/lib/influxdb3/plugins
- --admin-token-file=/run/secrets/influxdb3_admin_token
volumes:
- /mnt/user/appdata/influxdb3/data:/var/lib/influxdb3/data
- /mnt/user/appdata/influxdb3/plugins:/var/lib/influxdb3/plugins
secrets:
- influxdb3_admin_token
networks:
- monitoring_net
- monitoring_influx_lan
security_opt:
- no-new-privileges:true
networks:
monitoring_net:
name: monitoring_net
driver: bridge
monitoring_influx_lan:
driver: bridge
frontend_net:
external: true
volumes:
prometheus_data:
alertmanager_data:
loki_data:
promtail_positions:
grafana_data:
secrets:
monitoring_grafana_admin_password:
file: /mnt/user/appdata/secrets/monitoring_grafana_admin_password.txt
monitoring_grafana_influxdb_token:
file: /mnt/user/appdata/secrets/monitoring_grafana_influxdb_token.txt
influxdb3_admin_token:
file: /mnt/user/appdata/secrets/influxdb3_admin_token.json
@@ -0,0 +1,145 @@
{
"uid": "homelab-availability",
"title": "Homelab / Availability",
"tags": ["homelab", "blackbox", "uptime"],
"timezone": "browser",
"schemaVersion": 39,
"version": 1,
"refresh": "30s",
"time": {
"from": "now-6h",
"to": "now"
},
"templating": {
"list": [
{
"name": "target",
"label": "Target",
"type": "query",
"datasource": "Prometheus",
"query": "label_values(probe_success{job=\"blackbox-http\"}, instance)",
"refresh": 1,
"includeAll": true,
"multi": true,
"allValue": ".+",
"current": {
"selected": true,
"text": "All",
"value": "$__all"
}
}
]
},
"panels": [
{
"id": 1,
"type": "stat",
"title": "Endpoints up",
"datasource": "Prometheus",
"gridPos": {"h": 5, "w": 6, "x": 0, "y": 0},
"targets": [
{
"refId": "A",
"expr": "sum(probe_success{job=\"blackbox-http\", instance=~\"${target:regex}\"})"
}
],
"fieldConfig": {
"defaults": {
"unit": "short",
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "red", "value": null},
{"color": "green", "value": 1}
]
}
},
"overrides": []
},
"options": {"reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false}}
},
{
"id": 2,
"type": "stat",
"title": "Endpoints down",
"datasource": "Prometheus",
"gridPos": {"h": 5, "w": 6, "x": 6, "y": 0},
"targets": [
{
"refId": "A",
"expr": "count(probe_success{job=\"blackbox-http\", instance=~\"${target:regex}\"}) - sum(probe_success{job=\"blackbox-http\", instance=~\"${target:regex}\"})"
}
],
"fieldConfig": {
"defaults": {
"unit": "short",
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "green", "value": null},
{"color": "red", "value": 1}
]
}
},
"overrides": []
},
"options": {"reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false}}
},
{
"id": 3,
"type": "timeseries",
"title": "Probe duration",
"datasource": "Prometheus",
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0},
"targets": [
{
"refId": "A",
"expr": "probe_duration_seconds{job=\"blackbox-http\", instance=~\"${target:regex}\"}",
"legendFormat": "{{instance}}"
}
],
"fieldConfig": {"defaults": {"unit": "s"}, "overrides": []},
"options": {"legend": {"displayMode": "list", "placement": "bottom"}}
},
{
"id": 4,
"type": "timeseries",
"title": "Availability",
"datasource": "Prometheus",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 5},
"targets": [
{
"refId": "A",
"expr": "probe_success{job=\"blackbox-http\", instance=~\"${target:regex}\"}",
"legendFormat": "{{instance}}"
}
],
"fieldConfig": {"defaults": {"unit": "bool"}, "overrides": []},
"options": {"legend": {"displayMode": "list", "placement": "bottom"}}
},
{
"id": 5,
"type": "table",
"title": "HTTP status",
"datasource": "Prometheus",
"gridPos": {"h": 9, "w": 24, "x": 0, "y": 13},
"targets": [
{
"refId": "A",
"expr": "probe_http_status_code{job=\"blackbox-http\", instance=~\"${target:regex}\"}",
"format": "table",
"instant": true
}
],
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {"Time": true, "__name__": true, "job": true},
"renameByName": {"Value": "status_code", "instance": "target"}
}
}
]
}
]
}
@@ -0,0 +1,109 @@
{
"uid": "homelab-containers-logs",
"title": "Homelab / Containers + Logs",
"tags": ["homelab", "cadvisor", "loki", "dozzle-replacement"],
"timezone": "browser",
"schemaVersion": 39,
"version": 1,
"refresh": "30s",
"time": {
"from": "now-1h",
"to": "now"
},
"templating": {
"list": [
{
"name": "service",
"label": "Service",
"type": "query",
"datasource": "Loki",
"query": "label_values(compose_service)",
"refresh": 1,
"includeAll": true,
"multi": true,
"allValue": ".+",
"current": {
"selected": true,
"text": "All",
"value": "$__all"
}
},
{
"name": "search",
"label": "Regex Match",
"type": "textbox",
"query": ".+",
"current": {
"selected": true,
"text": ".+",
"value": ".+"
}
}
]
},
"panels": [
{
"id": 1,
"type": "timeseries",
"title": "Container CPU",
"datasource": "Prometheus",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
"targets": [
{
"refId": "A",
"expr": "sum by (name) (rate(container_cpu_usage_seconds_total{name!=\"\"}[5m]))",
"legendFormat": "{{name}}"
}
],
"fieldConfig": {"defaults": {"unit": "cores"}, "overrides": []}
},
{
"id": 2,
"type": "timeseries",
"title": "Container memory",
"datasource": "Prometheus",
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0},
"targets": [
{
"refId": "A",
"expr": "sum by (name) (container_memory_working_set_bytes{name!=\"\"})",
"legendFormat": "{{name}}"
}
],
"fieldConfig": {"defaults": {"unit": "bytes"}, "overrides": []}
},
{
"id": 3,
"type": "timeseries",
"title": "Log error rate",
"datasource": "Loki",
"gridPos": {"h": 8, "w": 24, "x": 0, "y": 8},
"targets": [
{
"refId": "A",
"expr": "sum by (compose_service) (count_over_time({compose_service=~\"${service:regex}\", container=~\".+\"} |~ \"(?i)error|exception|panic|fatal|traceback|oom|killed\" [$__interval]))",
"legendFormat": "{{compose_service}}"
}
]
},
{
"id": 4,
"type": "logs",
"title": "Live logs",
"datasource": "Loki",
"gridPos": {"h": 14, "w": 24, "x": 0, "y": 16},
"targets": [
{
"refId": "A",
"expr": "{compose_service=~\"${service:regex}\", container=~\".+\"} |~ \"$search\""
}
],
"options": {
"showLabels": false,
"showTime": true,
"sortOrder": "Descending",
"wrapLogMessage": false
}
}
]
}
@@ -0,0 +1,102 @@
{
"uid": "homelab-host-overview",
"title": "Homelab / Host Overview",
"tags": ["homelab", "node-exporter", "glances-replacement"],
"timezone": "browser",
"schemaVersion": 39,
"version": 1,
"refresh": "30s",
"time": {
"from": "now-6h",
"to": "now"
},
"panels": [
{
"id": 1,
"type": "timeseries",
"title": "CPU usage",
"datasource": "Prometheus",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
"targets": [
{
"refId": "A",
"expr": "100 - (avg(rate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)",
"legendFormat": "CPU"
}
],
"fieldConfig": {"defaults": {"unit": "percent", "min": 0, "max": 100}, "overrides": []}
},
{
"id": 2,
"type": "timeseries",
"title": "Memory usage",
"datasource": "Prometheus",
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0},
"targets": [
{
"refId": "A",
"expr": "100 * (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes))",
"legendFormat": "Memory"
}
],
"fieldConfig": {"defaults": {"unit": "percent", "min": 0, "max": 100}, "overrides": []}
},
{
"id": 3,
"type": "timeseries",
"title": "Filesystem usage",
"datasource": "Prometheus",
"gridPos": {"h": 9, "w": 12, "x": 0, "y": 8},
"targets": [
{
"refId": "A",
"expr": "100 * (1 - node_filesystem_avail_bytes{fstype!~\"tmpfs|overlay\"} / node_filesystem_size_bytes{fstype!~\"tmpfs|overlay\"})",
"legendFormat": "{{mountpoint}}"
}
],
"fieldConfig": {"defaults": {"unit": "percent", "min": 0, "max": 100}, "overrides": []},
"options": {"legend": {"displayMode": "list", "placement": "bottom"}}
},
{
"id": 4,
"type": "timeseries",
"title": "Network throughput",
"datasource": "Prometheus",
"gridPos": {"h": 9, "w": 12, "x": 12, "y": 8},
"targets": [
{
"refId": "A",
"expr": "rate(node_network_receive_bytes_total{device!~\"lo|veth.*|br-.*|docker.*\"}[5m])",
"legendFormat": "{{device}} RX"
},
{
"refId": "B",
"expr": "rate(node_network_transmit_bytes_total{device!~\"lo|veth.*|br-.*|docker.*\"}[5m])",
"legendFormat": "{{device}} TX"
}
],
"fieldConfig": {"defaults": {"unit": "Bps"}, "overrides": []},
"options": {"legend": {"displayMode": "list", "placement": "bottom"}}
},
{
"id": 5,
"type": "timeseries",
"title": "Disk IO",
"datasource": "Prometheus",
"gridPos": {"h": 8, "w": 24, "x": 0, "y": 17},
"targets": [
{
"refId": "A",
"expr": "rate(node_disk_read_bytes_total[5m])",
"legendFormat": "{{device}} read"
},
{
"refId": "B",
"expr": "rate(node_disk_written_bytes_total[5m])",
"legendFormat": "{{device}} write"
}
],
"fieldConfig": {"defaults": {"unit": "Bps"}, "overrides": []}
}
]
}
@@ -0,0 +1,13 @@
apiVersion: 1
providers:
- name: Homelab Imports
orgId: 1
folder: Homelab
type: file
disableDeletion: false
updateIntervalSeconds: 300
allowUiUpdates: true
options:
# Grafana.com dashboard IDs are imported by the compose one-shot importer.
path: /var/lib/grafana/dashboards
@@ -0,0 +1,33 @@
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: false
jsonData:
timeInterval: 15s
- name: Loki
type: loki
access: proxy
url: http://loki:3100
editable: false
jsonData:
maxLines: 1000
- name: InfluxDB 3 Core
uid: monitoring-influxdb3-core
type: influxdb
access: proxy
url: http://influxdb3-core:8181
editable: false
jsonData:
version: SQL
dbName: homelab
httpMode: POST
insecureGrpc: true
secureJsonData:
token: $GRAFANA_INFLUXDB_TOKEN
+47
View File
@@ -0,0 +1,47 @@
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
common:
instance_addr: 127.0.0.1
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
schema_config:
configs:
- from: 2026-05-16
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
limits_config:
retention_period: 720h
allow_structured_metadata: true
ingestion_rate_mb: 16
ingestion_burst_size_mb: 32
compactor:
working_directory: /loki/compactor
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
delete_request_store: filesystem
+58
View File
@@ -0,0 +1,58 @@
groups:
- name: homelab-availability
rules:
- alert: HomelabExternalConnectivityDown
expr: sum(probe_success{job="blackbox-http"} == 0) >= 5
for: 8m
labels:
severity: warning
annotations:
summary: "External connectivity appears down"
description: "At least 5 public homelab endpoints are unreachable. Likely WAN, DNS, or provider issue."
- alert: HomelabEndpointDown
expr: (probe_success{job="blackbox-http"} == 0) unless on() (sum(probe_success{job="blackbox-http"} == 0) >= 5)
for: 8m
labels:
severity: critical
annotations:
summary: "{{ $labels.instance }} is not reachable"
description: "Blackbox probe failed for {{ $labels.instance }}."
- alert: HomelabEndpointSlow
expr: probe_duration_seconds{job="blackbox-http"} > 5
for: 5m
labels:
severity: warning
annotations:
summary: "{{ $labels.instance }} is slow"
description: "Blackbox probe duration is above 5 seconds for {{ $labels.instance }}."
- name: homelab-host
rules:
- alert: HomelabDiskAlmostFull
expr: 100 * (1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) > 85
for: 10m
labels:
severity: warning
annotations:
summary: "Disk usage high on {{ $labels.mountpoint }}"
description: "{{ $labels.mountpoint }} is above 85% used."
- alert: HomelabHighMemoryUsage
expr: 100 * (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) > 90
for: 10m
labels:
severity: warning
annotations:
summary: "Memory usage high"
description: "Host memory usage is above 90%."
- alert: HomelabTraefik5xx
expr: sum(increase(traefik_service_requests_total{code=~"5.."}[5m])) by (service) >= 5
for: 2m
labels:
severity: warning
annotations:
summary: "Traefik 5xx responses for {{ $labels.service }}"
description: "Traefik reports at least 5 5xx responses for {{ $labels.service }} within 5 minutes."
+73
View File
@@ -0,0 +1,73 @@
global:
scrape_interval: 15s
evaluation_interval: 15s
external_labels:
site: kallilabcore
rule_files:
- /etc/prometheus/alerts.yml
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
scrape_configs:
- job_name: prometheus
static_configs:
- targets:
- prometheus:9090
- job_name: node-exporter
static_configs:
- targets:
- node-exporter:9100
- job_name: cadvisor
static_configs:
- targets:
- cadvisor:8080
- job_name: traefik
metrics_path: /metrics
static_configs:
# Traefik exposes Prometheus metrics internally on its metrics entrypoint.
- targets:
- traefik:8082
- job_name: blackbox-http
metrics_path: /probe
params:
module:
- http_reachable
static_configs:
- targets:
- https://monitoring.kaleschke.info
- https://auth.kaleschke.info
- https://git.kaleschke.info
- https://komodo.kaleschke.info
- https://glance.kaleschke.info
- https://paperless.kaleschke.info
- https://paperless-gpt.kaleschke.info
- https://immich.kaleschke.info
- https://mealie.kaleschke.info
- https://vault.kaleschke.info
- https://cloud.kaleschke.info
- https://ntfy.kaleschke.info
- https://borg.kaleschke.info
- https://files.kaleschke.info
- https://code.kaleschke.info
- https://glances.kaleschke.info
- https://scrutiny.kaleschke.info
- https://speedtest.kaleschke.info
- https://pdf.kaleschke.info
relabel_configs:
- source_labels:
- __address__
target_label: __param_target
- source_labels:
- __param_target
target_label: instance
- target_label: __address__
replacement: blackbox-exporter:9115
+34
View File
@@ -0,0 +1,34 @@
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /positions/positions.yml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: docker
docker_sd_configs:
- host: unix:///var/run/docker.sock
refresh_interval: 10s
relabel_configs:
- source_labels:
- __meta_docker_container_name
regex: /(.+)
target_label: container
- source_labels:
- __meta_docker_container_log_stream
target_label: stream
- source_labels:
- __meta_docker_container_label_com_docker_compose_project
target_label: compose_project
- source_labels:
- __meta_docker_container_label_com_docker_compose_service
target_label: compose_service
# Docker json-file logs live under /var/lib/docker/containers/<id>/<id>-json.log.
- source_labels:
- __meta_docker_container_id
target_label: __path__
replacement: /var/lib/docker/containers/$1/$1-json.log
+3
View File
@@ -0,0 +1,3 @@
# Safe default: local host only.
# Set this to the Unraid LAN IP, for example 192.168.178.58, when a VM such as Home Assistant must write to InfluxDB.
INFLUXDB_BIND_IP=127.0.0.1
-48
View File
@@ -1,48 +0,0 @@
services:
backrest:
image: ghcr.io/garethgeorge/backrest:latest@sha256:f4d34bd6fa985d13bdb6c01c5d8727e07708899afa9567d800808357d77b9fb0
container_name: backrest
restart: unless-stopped
environment:
- TZ=Europe/Berlin
- BACKREST_DATA=/data
- BACKREST_CONFIG=/config/config.json
- XDG_CACHE_HOME=/cache
- TMPDIR=/tmp
volumes:
- /mnt/user/appdata/backrest/data:/data
- /mnt/user/appdata/backrest/config:/config
- /mnt/user/appdata/backrest/cache:/cache
- /mnt/user/appdata/backrest/tmp:/tmp
- /mnt/user/appdata/backrest/ssh:/root/.ssh
- /mnt/user/appdata:/source/appdata:ro
- /mnt/remotes/192.168.178.86/Public/backrest-repos:/repos/wd
- /mnt/user/documents:/source/user/documents:ro
- /mnt/user/finance:/source/user/finance:ro
- /mnt/user/photos:/source/user/photos:ro
- /mnt/user/services:/source/user/services:ro
dns:
- 1.1.1.1
- 8.8.8.8
networks:
- backend_net
- frontend_net
labels:
- traefik.enable=true
- traefik.docker.network=frontend_net
- traefik.http.routers.backrest.rule=Host(`backrest.kaleschke.info`)
- traefik.http.routers.backrest.entrypoints=websecure
- traefik.http.routers.backrest.tls=true
- traefik.http.routers.backrest.tls.certresolver=le
- traefik.http.routers.backrest.middlewares=authelia@file,secure-headers@file
- traefik.http.services.backrest.loadbalancer.server.port=9898
networks:
backend_net:
external: true
frontend_net:
external: true
+1 -3
View File
@@ -57,7 +57,6 @@ Der technische Scope für `critical_infra` ist in `all-important-sources.txt` fe
- `/local/secrets` - `/local/secrets`
- `/local/appdata/authelia/config` - `/local/appdata/authelia/config`
- `/local/appdata/traefik` - `/local/appdata/traefik`
- `/local/appdata/homepage`
- `/local/appdata/ntfy` - `/local/appdata/ntfy`
- `/local/appdata/paperless-gpt` - `/local/appdata/paperless-gpt`
- `/local/appdata/tailscale` - `/local/appdata/tailscale`
@@ -78,7 +77,6 @@ Der technische Scope für `critical_infra` ist in `all-important-sources.txt` fe
| Mail-archiver | DataProtection-Keys | Shared PostgreSQL Dump vorhanden | Ja | gut | | Mail-archiver | DataProtection-Keys | Shared PostgreSQL Dump vorhanden | Ja | gut |
| Authelia | Config + Secrets | Shared PostgreSQL Dump vorgesehen / erzeugt | Ja | gut | | Authelia | Config + Secrets | Shared PostgreSQL Dump vorgesehen / erzeugt | Ja | gut |
| Traefik | dynamische Config + Let's Encrypt | keine separate DB | Ja | gut | | Traefik | dynamische Config + Let's Encrypt | keine separate DB | Ja | gut |
| Homepage | Config + Bilder | keine separate DB | Ja | gut |
| ntfy | Datei-Daten | keine separate DB | Ja | gut | | ntfy | Datei-Daten | keine separate DB | Ja | gut |
| Paperless-GPT | lokale Daten / Prompts | keine separate DB | Ja | gut | | Paperless-GPT | lokale Daten / Prompts | keine separate DB | Ja | gut |
| Tailscale | State-Verzeichnis | keine separate DB | Ja | gut | | Tailscale | State-Verzeichnis | keine separate DB | Ja | gut |
@@ -143,7 +141,7 @@ Seit dem ersten erfolgreichen Lauf wurde der Zielzustand weiter konkret umgesetz
- Ein Restore-Smoke-Test war erfolgreich: - Ein Restore-Smoke-Test war erfolgreich:
- `postgresql17-globals.sql` wurde wiederhergestellt - `postgresql17-globals.sql` wurde wiederhergestellt
- `gitea.db` wurde wiederhergestellt - `gitea.db` wurde wiederhergestellt
- `ntfy` und Uptime Kuma sind für Borg-Monitoring eingerichtet. - `ntfy` bleibt fuer Borg-Alerts eingerichtet; Uptime Kuma wurde 2026-05-25 durch Blackbox/Prometheus/Grafana abgeloest.
- `Firefly`, `Firefly-Fints` und `Semaphore` wurden aus Git und vom Homelab entfernt. - `Firefly`, `Firefly-Fints` und `Semaphore` wurden aus Git und vom Homelab entfernt.
## Was aktuell bewusst nicht als Problem gewertet wird ## Was aktuell bewusst nicht als Problem gewertet wird
+38 -9
View File
@@ -1,5 +1,7 @@
# Borg Backup Scope for KalliLabcore # Borg Backup Scope for KalliLabcore
Stand: 2026-05-16
This file defines the target state for replacing Backrest with Borg in this homelab. This file defines the target state for replacing Backrest with Borg in this homelab.
## Goal ## Goal
@@ -9,36 +11,61 @@ Use Borg as the single backup system for:
- critical file-backed application data - critical file-backed application data
- secrets, keys, and reverse-proxy state - secrets, keys, and reverse-proxy state
- database dumps generated before each Borg backup - database dumps generated before each Borg backup
- Unraid flash configuration artifacts generated before each Borg backup
Do not back up raw live database storage directories as the primary recovery artifact. Do not back up raw live database storage directories as the primary recovery artifact.
## Strategy ## Strategy
1. A pre-backup dump script runs on the host and writes fresh dumps to `/mnt/user/backups/borg/dumps/latest`. 1. A pre-backup dump script runs on the host and writes fresh dumps plus `unraid-flash-config.tar.gz` to `/mnt/user/backups/borg/dumps/latest`.
2. Borg backs up `/local/borg-dumps` plus the critical mounted paths below. 2. Borg backs up `/local/borg-dumps` plus the critical mounted paths below.
3. Borg retention handles history; the dump directory itself keeps only the latest artifacts. 3. Borg retention handles history; the dump directory itself keeps only the latest artifacts.
The inclusion of `/local/secrets` is intentional: Borg is expected to cover disaster recovery for selected secret material as part of the current homelab restore strategy. The inclusion of `/local/secrets` is intentional: Borg is expected to cover disaster recovery for selected secret material as part of the current homelab restore strategy.
The Unraid flash configuration archive is intentional as well and must be treated as secret backup material.
## Service Inventory ## Service Inventory
| Service | Recovery Method | What Borg Should Capture | | Service | Recovery Method | What Borg Should Capture |
| --- | --- | --- | | --- | --- | --- |
| Vaultwarden | file data | `/local/appdata/vaultwarden` | | Vaultwarden | SQLite dump + file data | `/local/borg-dumps`, `/local/appdata/vaultwarden` |
| Paperless | DB dump + file data | `/local/borg-dumps`, `/local/appdata/paperless-ngx/data`, `/local/paperless/media`, `/local/paperless/export`, `/local/paperless/consume` | | Paperless | DB dump + file data | `/local/borg-dumps`, `/local/appdata/paperless-ngx/data`, `/local/paperless/media`, `/local/paperless/export`, `/local/paperless/consume` |
| Immich | DB dump + file data | `/local/borg-dumps`, `/local/immich/upload`, `/local/immich/external` | | Immich | DB dump + file data | `/local/borg-dumps`, `/local/immich/upload`, `/local/immich/external` |
| Gitea | file data (SQLite inside `/data`) | `/local/gitea/data` | | Gitea | SQLite dump + file data | `/local/borg-dumps`, `/local/gitea/data` |
| Mealie | DB dump + file data | `/local/borg-dumps`, `/local/appdata/mealie/data` | | Mealie | DB dump + file data | `/local/borg-dumps`, `/local/appdata/mealie/data` |
| Mail-archiver | shared Postgres dump + data protection keys | `/local/borg-dumps`, `/local/appdata/mailarchiver/data-protection-keys` | | Mail-archiver | shared Postgres dump + data protection keys | `/local/borg-dumps`, `/local/appdata/mailarchiver/data-protection-keys` |
| Authelia | shared Postgres dump + config + secrets | `/local/borg-dumps`, `/local/appdata/authelia/config`, `/local/secrets` | | Authelia | shared Postgres dump + config + secrets | `/local/borg-dumps`, `/local/appdata/authelia/config`, `/local/secrets` |
| Traefik | file data | `/local/appdata/traefik` | | Traefik | file data | `/local/appdata/traefik` |
| Homepage | file data | `/local/appdata/homepage` |
| ntfy | file data | `/local/appdata/ntfy` | | ntfy | file data | `/local/appdata/ntfy` |
| Paperless-GPT | file data | `/local/appdata/paperless-gpt` | | Paperless-GPT | file data | `/local/appdata/paperless-gpt` |
| Tailscale | file data | `/local/appdata/tailscale` | | Tailscale | file data | `/local/appdata/tailscale` |
| AdGuard | config only | `/local/appdata/adguard/conf` | | AdGuard | config only | `/local/appdata/adguard/conf` |
| Borg UI | self-backup | `/local/appdata/borg-ui/data` | | Borg UI | SQLite dump + self-backup | `/local/borg-dumps`, `/local/appdata/borg-ui/data` |
| Komodo | config/cache only, optional | `/local/appdata/komodo/periphery`, `/local/appdata/komodo/core` | | Komodo | config + Mongo dump | `/local/borg-dumps`, `/local/appdata/komodo/periphery`, `/local/appdata/komodo/core` |
| GitOps host automation | repo clone + Komodo workspaces + host-check state | `/local/services/homelab-infra`, `/local/services/stacks`, `/local/services/posture-check` |
| Unraid OS flash | generated config archive | `/local/borg-dumps/unraid-flash-config.tar.gz` plus checksum and manifest |
| Nextcloud | DB dump + file data | `/local/borg-dumps`, `/local/appdata/nextcloud/html`, `/local/nextcloud/data` |
| Grafana | SQLite dump + file data | `/local/borg-dumps`, `/local/appdata/grafana` |
| Filebrowser | file-backed state dump + file data | `/local/borg-dumps`, `/local/appdata/filebrowser` |
| InfluxDB 3 Core | file data | `/local/appdata/influxdb3/data`, `/local/appdata/influxdb3/plugins` |
| Hermes Agent | file data + SSH key | `/local/appdata/hermes-agent/data`, `/local/secrets/hermes_runner_id_ed25519` |
| BentoPDF | rebuildable | no critical persistence in compose |
## Open Decisions and Coverage Gaps
These are deviations from the standard "DB dump first, file path second" strategy. Decide deliberately, do not silently extend.
### Nextcloud
Option A umgesetzt: `pre-backup-dumps.sh` writes `nextcloud.dump` from `nextcloud-postgres`. Borg UI also mounts `/mnt/user/documents/nextcloud-data` read-only as `/local/nextcloud/data`, so database and user files are both inside scope after the Borg UI stack is recreated.
### Komodo Mongo dump
`komodo-mongo.archive.gz` was produced and verified on 2026-05-04 (`gzip -t` ok). The dump function is in place in `pre-backup-dumps.sh`. Re-verify after any Komodo or Mongo major upgrade.
### GitOps host automation
The live Unraid User Scripts execute repo scripts from `/mnt/user/services/homelab-infra`, while Komodo keeps stack workspaces below `/mnt/user/services/stacks`. These paths are now mounted into Borg UI as `/local/services/...` and included explicitly so host-side script hotfixes, stack workspace state, and posture-check state are recoverable.
## Database Dumps Required ## Database Dumps Required
@@ -52,16 +79,21 @@ The inclusion of `/local/secrets` is intentional: Borg is expected to cover disa
- `mealie` - `mealie`
- `immich` - `immich`
- `nextcloud`
### Other Databases ### Other Databases
- Komodo MongoDB - Komodo MongoDB
- SQLite: `gitea`, `vaultwarden`, `speedtest-tracker`, `borg-ui`, `grafana`
- File-backed state: `filebrowser.bolt.dump`
- Unraid flash config: `unraid-flash-config.tar.gz` plus `unraid-flash-config.tar.gz.sha256`
## Explicitly Not Backed Up as Raw Live DB Files ## Explicitly Not Backed Up as Raw Live DB Files
- `/mnt/user/appdata/postgresql17` - `/mnt/user/appdata/postgresql17`
- `/mnt/user/appdata/mealie/postgres` - `/mnt/user/appdata/mealie/postgres`
- `/mnt/user/appdata/immich_postgres` - `/mnt/user/appdata/immich_postgres`
- `/mnt/user/appdata/nextcloud/postgres`
- `/mnt/user/appdata/komodo/mongo` - `/mnt/user/appdata/komodo/mongo`
- `/mnt/user/appdata/redis` - `/mnt/user/appdata/redis`
- `/mnt/user/appdata/scrutiny/influxdb` - `/mnt/user/appdata/scrutiny/influxdb`
@@ -73,11 +105,8 @@ These are not part of the first-class Borg scope:
- Plex metadata and cache - Plex metadata and cache
- AdGuard query log - AdGuard query log
- code-server extensions cache - code-server extensions cache
- uptime-kuma
- scrutiny metrics history - scrutiny metrics history
- dozzle, glances, speedtest - dozzle, glances, speedtest
- filebrowser app state
- portainer
## Suggested Retention ## Suggested Retention
+3 -1
View File
@@ -12,7 +12,6 @@
/local/secrets /local/secrets
/local/appdata/authelia/config /local/appdata/authelia/config
/local/appdata/traefik /local/appdata/traefik
/local/appdata/homepage
/local/appdata/ntfy /local/appdata/ntfy
/local/appdata/paperless-gpt /local/appdata/paperless-gpt
/local/appdata/tailscale /local/appdata/tailscale
@@ -20,3 +19,6 @@
/local/appdata/borg-ui/data /local/appdata/borg-ui/data
/local/appdata/komodo/periphery /local/appdata/komodo/periphery
/local/appdata/komodo/core /local/appdata/komodo/core
/local/services/homelab-infra
/local/services/stacks
/local/services/posture-check
+3 -1
View File
@@ -1,6 +1,6 @@
services: services:
borg-ui: borg-ui:
image: ainullcode/borg-ui:latest@sha256:867c73983e5bef5491dce1c34acf85fe8a9fe4f6ad5a9381e7ca2c382359ce6 image: ainullcode/borg-ui@sha256:867c73983e5bef5491cdee1c34acf85fe8a9fe4f6ad5a9381e7ca2c382359ce6
container_name: borg-ui container_name: borg-ui
restart: unless-stopped restart: unless-stopped
security_opt: security_opt:
@@ -20,8 +20,10 @@ services:
- /mnt/user/documents/scans_inbox:/local/paperless/consume:ro - /mnt/user/documents/scans_inbox:/local/paperless/consume:ro
- /mnt/user/documents/paperless:/local/paperless/media:ro - /mnt/user/documents/paperless:/local/paperless/media:ro
- /mnt/user/documents/paperless/export:/local/paperless/export:ro - /mnt/user/documents/paperless/export:/local/paperless/export:ro
- /mnt/user/documents/nextcloud-data:/local/nextcloud/data:ro
- /mnt/user/photos/immich:/local/immich/upload:ro - /mnt/user/photos/immich:/local/immich/upload:ro
- /mnt/user/photos/family_archive:/local/immich/external:ro - /mnt/user/photos/family_archive:/local/immich/external:ro
- /mnt/user/services:/local/services:ro
- /mnt/user/services/gitea/data:/local/gitea/data:ro - /mnt/user/services/gitea/data:/local/gitea/data:ro
- /mnt/user/appdata/borg-ui/restore:/restore - /mnt/user/appdata/borg-ui/restore:/restore
dns: dns:
+4
View File
@@ -14,6 +14,10 @@ Fresh dump artifacts are written to:
Borg UI should include `/local/borg-dumps` as a backup source. Borg UI should include `/local/borg-dumps` as a backup source.
The dump set also includes `unraid-flash-config.tar.gz`, a host-generated
archive of `/boot/config` plus checksum and manifest. Treat this archive as
secret backup material.
## Notes ## Notes
- The script is written for host execution where `docker` is available. - The script is written for host execution where `docker` is available.
+33 -15
View File
@@ -1,10 +1,10 @@
# Unraid User Scripts Setup # Unraid User Scripts Setup
This document describes the intended automation path for `pre-backup-dumps.sh`. This document describes the intended automation path for the Borg pre-flight scripts.
## Decision ## Decision
The pre-backup dump refresh should run: The Borg pre-flight should run:
- on the Unraid host - on the Unraid host
- through the User Scripts plugin or host cron - through the User Scripts plugin or host cron
@@ -14,7 +14,13 @@ It should **not** be implemented as a Borg UI inline hook in the current design.
## Why host-side ## Why host-side
`pre-backup-dumps.sh` currently assumes: `pre-borg.sh` currently chains the host-side checks:
- `services/posture-check/posture-check.sh`
- `ops/borg-ui/scripts/pre-backup-dumps.sh` including the Unraid flash config archive
- `ops/restore-tests/check-restore-freshness.sh`
The dump step assumes:
- access to the host Docker daemon via `docker exec` - access to the host Docker daemon via `docker exec`
- access to host paths under `/mnt/user/...` - access to host paths under `/mnt/user/...`
@@ -24,24 +30,36 @@ That makes host execution simpler, more transparent, and lower-risk than giving
## Recommended rollout ## Recommended rollout
1. Store the script on the host, for example at: 1. Use the repo clone on the host:
- `/mnt/user/appdata/borg-ui/scripts/pre-backup-dumps.sh` - `/mnt/user/services/homelab-infra`
2. Make it executable: 2. Make the scripts executable:
- `chmod +x /mnt/user/appdata/borg-ui/scripts/pre-backup-dumps.sh` - `chmod +x /mnt/user/services/homelab-infra/ops/borg-ui/scripts/pre-borg.sh`
3. Create a User Scripts entry such as: - `chmod +x /mnt/user/services/homelab-infra/ops/borg-ui/scripts/pre-backup-dumps.sh`
3. Create a User Scripts entry:
- `borg-pre-backup-dumps` - `borg-pre-backup-dumps`
4. Let that entry run: 4. Script body:
- on a fixed schedule before the expected Borg backup window
- or manually before ad hoc Borg runs ```bash
5. Keep Borg UI focused on backing up `/local/borg-dumps`, not on generating the dumps itself. #!/bin/bash
REPO_ROOT=/mnt/user/services/homelab-infra \
DUMP_ROOT=/mnt/user/backups/borg/dumps \
ALLOW_POSTURE_WARNING=1 \
bash /mnt/user/services/homelab-infra/ops/borg-ui/scripts/pre-borg.sh
```
5. Schedule: daily at `04:00`, before the expected Borg backup window.
6. Keep Borg UI focused on backing up `/local/borg-dumps`, not on generating the dumps itself.
## Operational model ## Operational model
The intended sequence is: The intended sequence is:
1. Host script refreshes `latest` dump artifacts. 1. Host wrapper checks posture.
2. Borg UI backs up `/local/borg-dumps` together with the rest of `critical_infra`. 2. Host script refreshes `latest` dump artifacts.
3. Borg history preserves dump history, so the host only needs to keep the most recent dump set. 3. Host script writes `unraid-flash-config.tar.gz` plus checksum and manifest into the same dump set.
4. Freshness check verifies expected dumps and the flash config archive.
5. Borg UI backs up `/local/borg-dumps` together with the rest of `critical_infra`.
6. Borg history preserves dump history, so the host only needs to keep the most recent dump set.
## Current dump target ## Current dump target
+163
View File
@@ -68,6 +68,143 @@ dump_pg_globals() {
atomic_write "$output" "$tmp" atomic_write "$output" "$tmp"
} }
dump_sqlite_file() {
source="$1"
output="$2"
label="$3"
if [ ! -f "$source" ]; then
warn "Skipping missing SQLite database for $label: $source"
return 0
fi
tmp="$TMP_DIR/$(basename "$output").tmp"
log "Dumping SQLite database '$label' from $source"
rm -f "$tmp"
if ! sqlite3 "$source" ".backup $tmp"; then
warn "SQLite backup failed for $label"
rm -f "$tmp"
return 1
fi
if [ "$(sqlite3 "$tmp" 'PRAGMA quick_check;')" != "ok" ]; then
warn "SQLite quick_check failed for $label"
rm -f "$tmp"
return 1
fi
atomic_write "$output" "$tmp"
}
dump_sqlite_container() {
container="$1"
db_path="$2"
output="$3"
host_source="${4:-}"
if ! need_container "$container"; then
warn "Skipping missing container: $container"
return 0
fi
if ! docker exec "$container" sh -lc 'command -v sqlite3 >/dev/null 2>&1'; then
if [ -n "$host_source" ]; then
warn "Container $container has no sqlite3; using host-side SQLite backup for $host_source"
dump_sqlite_file "$host_source" "$output" "$container"
return
fi
warn "Skipping SQLite backup for $container because sqlite3 is missing in the container and no host fallback is configured"
return 1
fi
container_tmp="/tmp/$(basename "$output").bak"
tmp="$TMP_DIR/$(basename "$output").tmp"
log "Dumping SQLite database '$db_path' from $container"
rm -f "$tmp"
docker exec "$container" rm -f "$container_tmp" >/dev/null 2>&1 || true
if ! docker exec "$container" sqlite3 "$db_path" ".backup $container_tmp"; then
warn "SQLite backup failed for $container:$db_path"
docker exec "$container" rm -f "$container_tmp" >/dev/null 2>&1 || true
rm -f "$tmp"
return 1
fi
docker cp "$container:$container_tmp" "$tmp"
docker exec "$container" rm -f "$container_tmp" >/dev/null 2>&1 || true
if [ "$(sqlite3 "$tmp" 'PRAGMA quick_check;')" != "ok" ]; then
warn "SQLite quick_check failed for $container:$db_path"
rm -f "$tmp"
return 1
fi
atomic_write "$output" "$tmp"
}
dump_file_copy() {
source="$1"
output="$2"
label="$3"
if [ ! -f "$source" ]; then
warn "Skipping missing file dump for $label: $source"
return 0
fi
tmp="$TMP_DIR/$(basename "$output").tmp"
log "Copying file-backed state '$label' from $source"
rm -f "$tmp"
cp "$source" "$tmp"
atomic_write "$output" "$tmp"
}
backup_unraid_flash_config() {
output="$LATEST_DIR/unraid-flash-config.tar.gz"
checksum="$LATEST_DIR/unraid-flash-config.tar.gz.sha256"
manifest="$LATEST_DIR/unraid-flash-config.manifest.txt"
tmp="$TMP_DIR/unraid-flash-config.tar.gz.tmp"
tmp_checksum="$TMP_DIR/unraid-flash-config.tar.gz.sha256.tmp"
tmp_manifest="$TMP_DIR/unraid-flash-config.manifest.txt.tmp"
if [ ! -d /boot/config ]; then
warn "Skipping Unraid flash config backup because /boot/config is missing"
return 1
fi
log "Backing up Unraid flash configuration from /boot/config"
rm -f "$tmp" "$tmp_checksum" "$tmp_manifest"
tar -C /boot \
--exclude='config/plugins/*/*.txz' \
--exclude='config/plugins/*/*.tgz' \
--exclude='config/plugins/*/*.tar' \
--exclude='config/plugins/*/*.tar.*' \
--exclude='config/plugins/*/*.zip' \
--exclude='config/plugins/*/*.md5' \
-czf "$tmp" config
chmod 600 "$tmp"
atomic_write "$output" "$tmp"
(
cd "$LATEST_DIR"
sha256sum "$(basename "$output")"
) > "$tmp_checksum"
chmod 600 "$tmp_checksum"
atomic_write "$checksum" "$tmp_checksum"
{
printf 'created_utc=%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
printf 'host=%s\n' "$(hostname)"
if [ -f /etc/unraid-version ]; then
sed 's/^/unraid_/' /etc/unraid-version
fi
printf 'source=/boot/config\n'
printf 'archive=%s\n' "$(basename "$output")"
printf 'checksum=%s\n' "$(basename "$checksum")"
printf 'note=%s\n' 'Contains Unraid configuration and must be treated as secret backup material.'
printf 'excluded=%s\n' 'downloadable plugin package archives under /boot/config/plugins/*/'
} > "$tmp_manifest"
chmod 600 "$tmp_manifest"
atomic_write "$manifest" "$tmp_manifest"
}
dump_optional_pg_db() { dump_optional_pg_db() {
container="$1" container="$1"
password="$2" password="$2"
@@ -131,6 +268,9 @@ dump_mongo_container() {
main() { main() {
need_cmd docker need_cmd docker
need_cmd sqlite3
need_cmd tar
need_cmd sha256sum
ensure_dirs ensure_dirs
# Shared PostgreSQL 17 # Shared PostgreSQL 17
@@ -162,9 +302,32 @@ main() {
warn "Skipping missing container: immich_postgres" warn "Skipping missing container: immich_postgres"
fi fi
if need_container "nextcloud-postgres"; then
nextcloud_password="$(cat /mnt/user/appdata/secrets/nextcloud_postgres_password.txt)"
dump_pg_db "nextcloud-postgres" "$nextcloud_password" "nextcloud" "nextcloud" "$LATEST_DIR/nextcloud.dump"
else
warn "Skipping missing container: nextcloud-postgres"
fi
# SQLite databases
dump_sqlite_container "gitea" "/data/gitea/gitea.db" "$LATEST_DIR/gitea.sqlite.dump" "/mnt/user/services/gitea/data/gitea/gitea.db"
dump_sqlite_container "vaultwarden" "/data/db.sqlite3" "$LATEST_DIR/vaultwarden.sqlite.dump" "/mnt/user/appdata/vaultwarden/db.sqlite3"
dump_sqlite_container "speedtest-tracker" "/config/database.sqlite" "$LATEST_DIR/speedtest-tracker.sqlite.dump" "/mnt/user/appdata/speedtest-tracker/config/database.sqlite"
# Filebrowser uses file-backed app state, but this installation is not SQLite.
dump_file_copy "/mnt/user/appdata/filebrowser/database/filebrowser.db" "$LATEST_DIR/filebrowser.bolt.dump" "filebrowser"
# Additional host-side SQLite dumps for admin tooling with appdata files.
dump_sqlite_file "/mnt/user/appdata/borg-ui/data/borg.db" "$LATEST_DIR/borg-ui.sqlite" "borg-ui"
dump_sqlite_file "/mnt/user/appdata/grafana/grafana.db" "$LATEST_DIR/grafana.sqlite" "grafana"
# MongoDB # MongoDB
dump_mongo_container "komodo-mongo" "$LATEST_DIR/komodo-mongo.archive.gz" dump_mongo_container "komodo-mongo" "$LATEST_DIR/komodo-mongo.archive.gz"
# Unraid USB flash configuration. This is generated into the existing dump
# set so Borg carries it off-site together with the database artifacts.
backup_unraid_flash_config
log "Finished refreshing dump set in $LATEST_DIR" log "Finished refreshing dump set in $LATEST_DIR"
} }
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="${REPO_ROOT:-$(cd "$SCRIPT_DIR/../../.." && pwd)}"
POSTURE_CHECK="${POSTURE_CHECK:-$REPO_ROOT/services/posture-check/posture-check.sh}"
FRESHNESS_CHECK="${FRESHNESS_CHECK:-$REPO_ROOT/ops/restore-tests/check-restore-freshness.sh}"
PRE_BACKUP_DUMPS="${PRE_BACKUP_DUMPS:-$SCRIPT_DIR/pre-backup-dumps.sh}"
NTFY_SCRIPT="${NTFY_SCRIPT:-$REPO_ROOT/ops/restore-tests/send-ntfy.sh}"
NTFY_TOPIC="${NTFY_TOPIC:-homelab-alerts}"
ALLOW_POSTURE_WARNING="${ALLOW_POSTURE_WARNING:-1}"
case "${DUMP_ROOT:-}" in
*/latest)
FRESHNESS_DUMP_ROOT="${FRESHNESS_DUMP_ROOT:-$DUMP_ROOT}"
;;
"")
FRESHNESS_DUMP_ROOT="${FRESHNESS_DUMP_ROOT:-/mnt/user/backups/borg/dumps/latest}"
;;
*)
FRESHNESS_DUMP_ROOT="${FRESHNESS_DUMP_ROOT:-$DUMP_ROOT/latest}"
;;
esac
notify_failure() {
local step="$1"
local message="$2"
if [ -x "$NTFY_SCRIPT" ]; then
"$NTFY_SCRIPT" "$NTFY_TOPIC" "Borg pre-hook failed: $step" "$message" high || true
fi
}
run_step() {
local step="$1"
shift
echo "[pre-borg] Running $step"
if "$@"; then
echo "[pre-borg] OK: $step"
else
rc=$?
notify_failure "$step" "Command failed with exit code $rc: $*"
exit "$rc"
fi
}
echo "[pre-borg] Running posture-check"
if "$POSTURE_CHECK"; then
echo "[pre-borg] OK: posture-check"
else
rc=$?
if [ "$rc" -eq 1 ] && [ "$ALLOW_POSTURE_WARNING" = "1" ]; then
echo "[pre-borg] WARNING: posture-check returned warnings; continuing because ALLOW_POSTURE_WARNING=1"
else
notify_failure "posture-check" "Command failed with exit code $rc: $POSTURE_CHECK"
exit "$rc"
fi
fi
run_step "pre-backup-dumps" "$PRE_BACKUP_DUMPS"
run_step "restore-freshness" env DUMP_ROOT="$FRESHNESS_DUMP_ROOT" "$FRESHNESS_CHECK"
echo "[pre-borg] All pre-flight checks passed"
View File
+2 -3
View File
@@ -1,6 +1,6 @@
services: services:
code-server: code-server:
image: lscr.io/linuxserver/code-server:latest@sha256:4620adace18935dd6ca79d77e3bc1c379e21875392192f970cf5d6b0fb4aefcd 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:
@@ -10,7 +10,7 @@ services:
- PUID=1000 - PUID=1000
- PGID=1000 - PGID=1000
- TZ=Europe/Berlin - TZ=Europe/Berlin
- PASSWORD_FILE=/run/secrets/password - FILE__PASSWORD=/run/secrets/password
- DEFAULT_WORKSPACE=/workspace - DEFAULT_WORKSPACE=/workspace
- PWA_APPNAME=KalliLab Code - PWA_APPNAME=KalliLab Code
@@ -18,7 +18,6 @@ services:
volumes: volumes:
- /mnt/user/appdata/code-server:/config - /mnt/user/appdata/code-server:/config
- /mnt/user/services/dev:/workspace - /mnt/user/services/dev:/workspace
- /mnt/user/appdata/homepage:/prod/homepage
- /mnt/user/appdata/code-server/secrets/password:/run/secrets/password:ro - /mnt/user/appdata/code-server/secrets/password:/run/secrets/password:ro
networks: networks:
View File
+4 -2
View File
@@ -1,6 +1,6 @@
services: services:
filebrowser: filebrowser:
image: filebrowser/filebrowser:latest@sha256:4dce87308b9f9cfbcf8d0a284fc9565d2b515530a6bae2d920b388161e093f26 image: filebrowser/filebrowser:v2.63.2@sha256:4dce87308b9f9cfbcf8d0a284fc9565d2b515530a6bae2d920b388161e093f26
container_name: filebrowser container_name: filebrowser
restart: unless-stopped restart: unless-stopped
security_opt: security_opt:
@@ -9,7 +9,9 @@ services:
- PUID=99 - PUID=99
- PGID=100 - PGID=100
volumes: volumes:
- /mnt/user/appdata:/srv/appdata - /mnt/user/documents:/srv/documents
- /mnt/user/photos:/srv/photos
- /mnt/user/projekte:/srv/projekte
- /mnt/user/appdata/filebrowser/database:/database - /mnt/user/appdata/filebrowser/database:/database
- /mnt/user/appdata/filebrowser/config:/config - /mnt/user/appdata/filebrowser/config:/config
command: ["--database", "/database/filebrowser.db"] command: ["--database", "/database/filebrowser.db"]
+879
View File
@@ -0,0 +1,879 @@
server:
proxied: true
branding:
app-name: KalliLab Dashboard
logo-text: KL
hide-footer: true
theme:
background-color: 210 20 13
primary-color: 212 100 50
positive-color: 140 70 40
negative-color: 4 78 57
contrast-multiplier: 1.25
text-saturation-multiplier: 0.9
disable-picker: false
pages:
- name: Home
slug: home
width: wide
head-widgets:
- type: search
search-engine: duckduckgo
new-tab: true
autofocus: true
placeholder: Suche im Web oder springe per Bang...
bangs:
- title: Gitea
shortcut: "!git"
url: https://git.kaleschke.info/explore/repos?q={QUERY}
- title: Paperless
shortcut: "!doc"
url: https://paperless.kaleschke.info/documents?query={QUERY}
- title: Nextcloud
shortcut: "!cloud"
url: https://cloud.kaleschke.info/apps/files/?dir=/{QUERY}
- title: Komodo
shortcut: "!komodo"
url: https://komodo.kaleschke.info
columns:
- size: small
widgets:
- type: group
widgets:
- type: custom-api
title: Day
body-type: string
skip-json-validation: true
cache: 1s
template: |
{{ $localTime := now }}
{{ $elapsedSeconds := add (mul $localTime.Hour 3600) (mul $localTime.Minute 60) | add $localTime.Second }}
{{ $dayProgress := div (mul $elapsedSeconds 100.0) 86400.0 }}
{{ $gradient := "#70a1ff" }}
{{ if gt $dayProgress 25.0 }}{{ $gradient = "#ff6b6b, #70a1ff" }}{{ end }}
{{ if gt $dayProgress 50.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df" }}{{ end }}
{{ if gt $dayProgress 75.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df, #70a1ff" }}{{ end }}
<div style="text-align: center;">
<div style="width: 100%; height: 12px; background: #23262f; border: 1px solid color-mix(in srgb, var(--color-text-subdue) 55%, transparent); border-radius: 10px; overflow: hidden;">
<div style="height: 100%; width: {{ $dayProgress }}%; background: linear-gradient(90deg, {{ $gradient }});"></div>
</div>
<div class="size-h1" style="margin-top: 6px;">{{ printf "%.2f" $dayProgress }}% des Tages sind vorbei</div>
</div>
- type: custom-api
title: Month
body-type: string
skip-json-validation: true
cache: 1s
template: |
{{ $localTime := now }}
{{ $month := $localTime.Month }}
{{ $daysInMonth := 31 }}
{{ if eq $month 2 }}{{ $daysInMonth = 28 }}{{ end }}
{{ if or (eq $month 4) (eq $month 6) (eq $month 9) (eq $month 11) }}{{ $daysInMonth = 30 }}{{ end }}
{{ $secondsToday := add (mul $localTime.Hour 3600) (mul $localTime.Minute 60) | add $localTime.Second }}
{{ $daysElapsed := add (sub $localTime.Day 1) (div $secondsToday 86400.0) }}
{{ $monthProgress := mul (div $daysElapsed $daysInMonth) 100.0 }}
{{ $gradient := "#70a1ff" }}
{{ if gt $monthProgress 25.0 }}{{ $gradient = "#ff6b6b, #70a1ff" }}{{ end }}
{{ if gt $monthProgress 50.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df" }}{{ end }}
{{ if gt $monthProgress 75.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df, #70a1ff" }}{{ end }}
<div style="text-align: center;">
<div style="width: 100%; height: 12px; background: #23262f; border: 1px solid color-mix(in srgb, var(--color-text-subdue) 55%, transparent); border-radius: 10px; overflow: hidden;">
<div style="height: 100%; width: {{ $monthProgress }}%; background: linear-gradient(90deg, {{ $gradient }});"></div>
</div>
<div class="size-h1" style="margin-top: 6px;">{{ printf "%.2f" $monthProgress }}% des Monats sind vorbei</div>
</div>
- type: custom-api
title: Year
body-type: string
skip-json-validation: true
cache: 1s
template: |
{{ $localTime := now }}
{{ $secondsToday := add (mul $localTime.Hour 3600) (mul $localTime.Minute 60) | add $localTime.Second }}
{{ $secondsElapsed := add (mul (sub $localTime.YearDay 1) 86400) $secondsToday }}
{{ $yearProgress := div (mul $secondsElapsed 100.0) (mul 365 86400) }}
{{ $gradient := "#70a1ff" }}
{{ if gt $yearProgress 25.0 }}{{ $gradient = "#ff6b6b, #70a1ff" }}{{ end }}
{{ if gt $yearProgress 50.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df" }}{{ end }}
{{ if gt $yearProgress 75.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df, #70a1ff" }}{{ end }}
<div style="text-align: center;">
<div style="width: 100%; height: 12px; background: #23262f; border: 1px solid color-mix(in srgb, var(--color-text-subdue) 55%, transparent); border-radius: 10px; overflow: hidden;">
<div style="height: 100%; width: {{ $yearProgress }}%; background: linear-gradient(90deg, {{ $gradient }});"></div>
</div>
<div class="size-h1" style="margin-top: 6px;">{{ printf "%.2f" $yearProgress }}% des Jahres sind vorbei</div>
</div>
- type: clock
hour-format: 24h
show-progress: true
timezones:
- timezone: Europe/Berlin
label: Berlin
- timezone: UTC
label: UTC
- type: calendar
first-day-of-week: monday
- type: bookmarks
title: Direkte Einstiege
groups:
- title: Core
color: 212 100 50
links:
- title: Komodo
url: https://komodo.kaleschke.info
icon: sh:komodo
- title: Gitea
url: https://git.kaleschke.info
icon: si:gitea
- title: Monitoring
url: https://monitoring.kaleschke.info
icon: si:grafana
- title: Ops
color: 45 70 55
links:
- title: Borg
url: https://borg.kaleschke.info
icon: mdi:archive
- title: Glances
url: https://glances.kaleschke.info
icon: sh:glances
- title: Scrutiny
url: https://scrutiny.kaleschke.info
icon: sh:scrutiny
- size: full
widgets:
- type: server-stats
title: Server Stats
servers:
- type: local
name: Kallilabcore
hide-mountpoints-by-default: false
- type: group
widgets:
- type: custom-api
title: Immich
title-url: https://immich.kaleschke.info
cache: 10m
url: http://immich_server:2283/api/server/statistics
headers:
x-api-key: ${GLANCE_IMMICH_API_KEY}
subrequests:
storage:
url: http://immich_server:2283/api/server/storage
headers:
x-api-key: ${GLANCE_IMMICH_API_KEY}
template: |
{{ $photos := .JSON.Int "photos" }}
{{ $videos := .JSON.Int "videos" }}
{{ $usageGiB := div (toFloat (.JSON.Int "usage")) 1073741824.0 }}
{{ $storage := .Subrequest "storage" }}
{{ $storageOK := and (ge $storage.Response.StatusCode 200) (le $storage.Response.StatusCode 299) }}
{{ $percentage := 0.0 }}
{{ if $storageOK }}{{ $percentage = $storage.JSON.Float "diskUsagePercentage" }}{{ end }}
<div class="flex justify-between text-center">
<div>
<div class="color-highlight size-h3">{{ $photos | formatNumber }}</div>
<div class="size-h6 uppercase">Fotos</div>
</div>
<div>
<div class="color-highlight size-h3">{{ $videos | formatNumber }}</div>
<div class="size-h6 uppercase">Videos</div>
</div>
<div>
<div class="color-highlight size-h3">{{ printf "%.0f" $usageGiB }} GiB</div>
<div class="size-h6 uppercase">Medien</div>
</div>
</div>
<div style="height: 8px; margin-top: 14px; border-radius: 999px; overflow: hidden; background: color-mix(in srgb, var(--color-text-subdue) 22%, transparent);">
<div style="height: 100%; width: {{ if $storageOK }}{{ printf "%.1f" $percentage }}%{{ else }}0%{{ end }}; border-radius: 999px; background: var(--color-primary);"></div>
</div>
<div class="size-h6 color-subdue" style="margin-top: 8px;">{{ if $storageOK }}{{ printf "%.1f" $percentage }}% Speicher belegt{{ else }}Speicher API nicht verfuegbar{{ end }}</div>
- type: monitor
title: Homelab Status
cache: 1m
sites:
- title: AdGuard Home
url: http://192.168.178.58:8082
check-url: http://adguard
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/adguard-home.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Authelia
url: https://auth.kaleschke.info
check-url: http://authelia:9091/api/health
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authelia.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Gitea
url: https://git.kaleschke.info
check-url: http://gitea:3000/api/healthz
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Traefik
url: https://traefik.kaleschke.info
check-url: http://traefik:8082/metrics
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Vaultwarden
url: https://vault.kaleschke.info
check-url: http://vaultwarden/alive
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vaultwarden.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Komodo
url: https://komodo.kaleschke.info
check-url: http://komodo-core:9120
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/komodo.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Paperless-ngx
url: https://paperless.kaleschke.info
check-url: http://paperless-ngx:8000
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/paperless-ngx.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Paperless-GPT
url: https://paperless-gpt.kaleschke.info
check-url: http://paperless-gpt:8080
icon: mdi:robot
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Immich
url: https://immich.kaleschke.info
check-url: http://immich_server:2283/api/server/ping
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Mealie
url: https://mealie.kaleschke.info
check-url: http://mealie:9000
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mealie.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Nextcloud
url: https://cloud.kaleschke.info
check-url: http://nextcloud/status.php
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: ntfy
url: https://ntfy.kaleschke.info
check-url: http://ntfy/v1/health
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/ntfy.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Mail Archiver
url: https://mail.kaleschke.info
check-url: http://mail-archiver:5000
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mailcow.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: BentoPDF
url: https://pdf.kaleschke.info
check-url: http://bentopdf:8080
icon: mdi:file-pdf-box
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Glance
url: https://glance.kaleschke.info
check-url: http://glance:8080
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glance.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Monitoring Grafana
url: https://monitoring.kaleschke.info
check-url: http://monitoring-grafana:3000/api/health
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/grafana.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Glances
url: https://glances.kaleschke.info
check-url: http://glances:61208
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glances.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Scrutiny
url: https://scrutiny.kaleschke.info
check-url: http://scrutiny:8080
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/scrutiny.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Speedtest Tracker
url: https://speedtest.kaleschke.info
check-url: http://speedtest-tracker
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/speedtest-tracker.png
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Filebrowser
url: https://files.kaleschke.info
check-url: http://filebrowser
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/filebrowser.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: code-server
url: https://code.kaleschke.info
check-url: http://code-server:8443
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vscode.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Borg UI
url: https://borg.kaleschke.info
check-url: http://borg-ui:8081
icon: mdi:archive-sync
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- size: small
widgets:
- type: custom-api
title: Internet
title-url: https://speedtest.kaleschke.info
cache: 1h
url: http://speedtest-tracker/api/v1/results/latest
headers:
Authorization: Bearer ${GLANCE_SPEEDTEST_API_KEY}
Accept: application/json
template: |
{{ $ip := .JSON.String "external_ip" }}
{{ if eq $ip "" }}{{ $ip = .JSON.String "data.interface.externalIp" }}{{ end }}
{{ $isp := .JSON.String "isp" }}
{{ if eq $isp "" }}{{ $isp = .JSON.String "data.isp" }}{{ end }}
{{ $server := .JSON.String "server_name" }}
{{ if eq $server "" }}{{ $server = .JSON.String "data.server_name" }}{{ end }}
<div style="display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center;">
<div class="color-primary size-h2" style="font-weight: 700;">{{ if ne $ip "" }}{{ $ip }}{{ else }}WAN online{{ end }}</div>
<div class="size-h5 color-highlight">Speedtest Tracker</div>
<div class="size-h6 color-subdue" style="font-style: italic;">{{ if ne $isp "" }}{{ $isp }}{{ else }}{{ $server }}{{ end }}</div>
</div>
- type: custom-api
title: Internet Speed
title-url: https://speedtest.kaleschke.info
cache: 1h
url: http://speedtest-tracker/api/v1/results/latest
headers:
Authorization: Bearer ${GLANCE_SPEEDTEST_API_KEY}
Accept: application/json
subrequests:
stats:
url: http://speedtest-tracker/api/v1/stats
headers:
Authorization: Bearer ${GLANCE_SPEEDTEST_API_KEY}
Accept: application/json
template: |
{{ $stats := .Subrequest "stats" }}
{{ $download := .JSON.Float "download" }}
{{ if eq $download 0.0 }}{{ $download = .JSON.Float "data.download" }}{{ end }}
{{ if eq $download 0.0 }}{{ $download = div (.JSON.Float "download_bits") 1000000.0 }}{{ end }}
{{ if eq $download 0.0 }}{{ $download = div (.JSON.Float "data.download_bits") 1000000.0 }}{{ end }}
{{ $upload := .JSON.Float "upload" }}
{{ if eq $upload 0.0 }}{{ $upload = .JSON.Float "data.upload" }}{{ end }}
{{ if eq $upload 0.0 }}{{ $upload = div (.JSON.Float "upload_bits") 1000000.0 }}{{ end }}
{{ if eq $upload 0.0 }}{{ $upload = div (.JSON.Float "data.upload_bits") 1000000.0 }}{{ end }}
{{ $ping := .JSON.Float "ping" }}
{{ if eq $ping 0.0 }}{{ $ping = .JSON.Float "data.ping" }}{{ end }}
{{ $downloadAvg := $stats.JSON.Float "avg_download" }}
{{ if eq $downloadAvg 0.0 }}{{ $downloadAvg = $stats.JSON.Float "data.download.avg" }}{{ end }}
{{ if eq $downloadAvg 0.0 }}{{ $downloadAvg = div ($stats.JSON.Float "data.download.avg_bits") 1000000.0 }}{{ end }}
{{ $uploadAvg := $stats.JSON.Float "avg_upload" }}
{{ if eq $uploadAvg 0.0 }}{{ $uploadAvg = $stats.JSON.Float "data.upload.avg" }}{{ end }}
{{ if eq $uploadAvg 0.0 }}{{ $uploadAvg = div ($stats.JSON.Float "data.upload.avg_bits") 1000000.0 }}{{ end }}
{{ $pingAvg := $stats.JSON.Float "avg_ping" }}
{{ if eq $pingAvg 0.0 }}{{ $pingAvg = $stats.JSON.Float "data.ping.avg" }}{{ end }}
{{ $downloadChange := percentChange $downloadAvg $download }}
{{ $uploadChange := percentChange $uploadAvg $upload }}
{{ $pingChange := percentChange $pingAvg $ping }}
<div class="flex justify-between text-center margin-block-3">
<div>
<div class="size-small {{ if lt $downloadChange 0.0 }}color-negative{{ else }}color-positive{{ end }}">{{ printf "%+.1f%%" $downloadChange }}</div>
<div class="color-highlight size-h3">{{ printf "%.1f" $download }}</div>
<div class="size-h6 color-subdue">DOWNLOAD</div>
</div>
<div>
<div class="size-small {{ if lt $uploadChange 0.0 }}color-negative{{ else }}color-positive{{ end }}">{{ printf "%+.1f%%" $uploadChange }}</div>
<div class="color-highlight size-h3">{{ printf "%.1f" $upload }}</div>
<div class="size-h6 color-subdue">UPLOAD</div>
</div>
<div>
<div class="size-small {{ if gt $pingChange 0.0 }}color-negative{{ else }}color-positive{{ end }}">{{ printf "%+.1f%%" $pingChange }}</div>
<div class="color-highlight size-h3">{{ printf "%.0f ms" $ping }}</div>
<div class="size-h6 color-subdue">PING</div>
</div>
</div>
- type: dns-stats
title: DNS Stats
service: adguard
url: http://adguard
username: ${GLANCE_ADGUARD_USERNAME}
password: ${GLANCE_ADGUARD_PASSWORD}
- type: monitor
title: DNS und VPN
cache: 1m
sites:
- title: AdGuard Home
url: http://192.168.178.58:8082
check-url: http://adguard
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/adguard-home.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Traefik
url: https://traefik.kaleschke.info
check-url: http://traefik:8082/metrics
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- type: docker-containers
title: Network Container
category: network
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers: &containers
traefik:
name: Traefik
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
url: https://traefik.kaleschke.info
description: Reverse Proxy
category: core
hide: false
gitea:
name: Gitea
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
url: https://git.kaleschke.info
description: GitOps Origin
category: core
hide: false
authelia:
name: Authelia
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authelia.svg
url: https://auth.kaleschke.info
description: ForwardAuth
category: core
hide: false
vaultwarden:
name: Vaultwarden
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vaultwarden.svg
url: https://vault.kaleschke.info
description: Password Vault
category: core
hide: false
postgresql17:
name: PostgreSQL 17
icon: si:postgresql
description: Shared DB
category: core
hide: false
Redis:
name: Redis
icon: si:redis
description: Shared Cache
category: core
hide: false
adguard:
name: AdGuard
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/adguard-home.svg
url: http://192.168.178.58:8082
description: DNS Filter
category: network
hide: false
unbound:
name: Unbound
icon: mdi:dns
description: Upstream Resolver
category: network
hide: false
Tailscale-Docker:
name: Tailscale
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/tailscale.svg
description: VPN
category: network
hide: false
ddns-updater:
name: DDNS Updater
icon: mdi:cloud-sync
description: Cloudflare DNS
category: network
hide: false
paperless-ngx:
name: Paperless-ngx
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/paperless-ngx.svg
url: https://paperless.kaleschke.info
description: Dokumente
category: apps
hide: false
paperless-gpt:
name: Paperless-GPT
icon: mdi:robot
url: https://paperless-gpt.kaleschke.info
description: Dokumenten-KI
category: apps
hide: false
immich_server:
name: Immich
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
url: https://immich.kaleschke.info
description: Fotos und Videos
category: apps
id: immich
hide: false
immich_postgres:
name: DB
parent: immich
category: apps
hide: false
immich_redis:
name: Redis
parent: immich
category: apps
hide: false
immich_machine_learning:
name: ML
parent: immich
category: apps
hide: false
mealie:
name: Mealie
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mealie.svg
url: https://mealie.kaleschke.info
description: Rezepte
category: apps
id: mealie
hide: false
mealie-postgres:
name: DB
parent: mealie
category: apps
hide: false
nextcloud:
name: Nextcloud
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg
url: https://cloud.kaleschke.info
description: Dateien und Sync
category: apps
id: nextcloud
hide: false
nextcloud-postgres:
name: DB
parent: nextcloud
category: apps
hide: false
nextcloud-redis:
name: Redis
parent: nextcloud
category: apps
hide: false
mail-archiver:
name: Mail Archiver
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mailcow.svg
url: https://mail.kaleschke.info
description: Mail-Archiv
category: apps
hide: false
ntfy:
name: ntfy
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/ntfy.svg
url: https://ntfy.kaleschke.info
description: Push Alerts
category: apps
hide: false
bentopdf:
name: BentoPDF
icon: mdi:file-pdf-box
url: https://pdf.kaleschke.info
description: PDF Tools
category: apps
hide: false
glance:
name: Glance
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glance.svg
url: https://glance.kaleschke.info
description: Homelab Uebersicht
category: ops
hide: false
glance-docker-socket-proxy:
name: Glance Socket Proxy
icon: si:docker
description: Read-only Docker API
category: ops
hide: false
monitoring-grafana:
name: Monitoring Grafana
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/grafana.svg
url: https://monitoring.kaleschke.info
description: Observability UI
category: ops
id: monitoring
hide: false
monitoring-prometheus:
name: Prometheus
parent: monitoring
category: ops
hide: false
monitoring-loki:
name: Loki
parent: monitoring
category: ops
hide: false
monitoring-promtail:
name: Promtail
parent: monitoring
category: ops
hide: false
monitoring-alertmanager:
name: Alertmanager
parent: monitoring
category: ops
hide: false
monitoring-alertmanager-ntfy-bridge:
name: ntfy Bridge
parent: monitoring
category: ops
hide: false
monitoring-blackbox-exporter:
name: Blackbox
parent: monitoring
category: ops
hide: false
monitoring-node-exporter:
name: Node Exporter
parent: monitoring
category: ops
hide: false
monitoring-cadvisor:
name: cAdvisor
parent: monitoring
category: ops
hide: false
monitoring-influxdb3-core:
name: InfluxDB 3
parent: monitoring
category: ops
hide: false
glances:
name: Glances
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glances.svg
url: https://glances.kaleschke.info
description: Host-Monitoring
category: ops
hide: false
scrutiny:
name: Scrutiny
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/scrutiny.svg
url: https://scrutiny.kaleschke.info
description: SMART
category: ops
hide: false
speedtest-tracker:
name: Speedtest
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/speedtest-tracker.png
url: https://speedtest.kaleschke.info
description: WAN-Messung
category: ops
hide: false
filebrowser:
name: Filebrowser
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/filebrowser.svg
url: https://files.kaleschke.info
description: Dateizugriff
category: ops
hide: false
code-server:
name: code-server
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vscode.svg
url: https://code.kaleschke.info
description: Web IDE
category: ops
hide: false
borg-ui:
name: Borg UI
icon: mdi:archive-sync
url: https://borg.kaleschke.info
description: Backup und Restore
category: ops
hide: false
hermes-dashboard:
name: Hermes
icon: mdi:shield-sparkles
url: https://hermes.kaleschke.info
description: Ops Agent UI
category: ops
id: hermes
hide: false
hermes-gateway:
name: Gateway
parent: hermes
category: ops
hide: false
komodo-core:
name: Komodo
icon: sh:komodo
url: https://komodo.kaleschke.info
description: Stack Manager
category: ops
id: komodo
hide: false
komodo-mongo:
name: Mongo
parent: komodo
category: ops
hide: false
komodo-periphery:
name: Periphery
parent: komodo
category: ops
hide: false
- type: docker-containers
title: App Container
category: apps
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers: *containers
- type: docker-containers
title: Ops Container
category: ops
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers: *containers
- name: Infrastructure and Media
slug: infrastructure
width: wide
columns:
- size: small
widgets:
- type: bookmarks
title: Core
groups:
- title: Control Plane
color: 212 100 50
links:
- title: Komodo
url: https://komodo.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/komodo.svg
- title: Gitea
url: https://git.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
- title: Traefik
url: https://traefik.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
- title: Authelia
url: https://auth.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authelia.svg
- type: bookmarks
title: Media und Apps
groups:
- title: Apps
color: 140 70 40
links:
- title: Immich
url: https://immich.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
- title: Paperless
url: https://paperless.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/paperless-ngx.svg
- title: Nextcloud
url: https://cloud.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg
- title: Mealie
url: https://mealie.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mealie.svg
- size: full
widgets:
- type: monitor
title: Platform Checks
cache: 1m
sites:
- title: Gitea
url: https://git.kaleschke.info
check-url: http://gitea:3000/api/healthz
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Monitoring Grafana
url: https://monitoring.kaleschke.info
check-url: http://monitoring-grafana:3000/api/health
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/grafana.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Glance
url: https://glance.kaleschke.info
check-url: http://glance:8080
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glance.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Immich
url: https://immich.kaleschke.info
check-url: http://immich_server:2283/api/server/ping
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Paperless-ngx
url: https://paperless.kaleschke.info
check-url: http://paperless-ngx:8000
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/paperless-ngx.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Nextcloud
url: https://cloud.kaleschke.info
check-url: http://nextcloud/status.php
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- type: docker-containers
title: Core Container
category: core
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers: *containers
- type: docker-containers
title: App Container
category: apps
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers: *containers
- type: docker-containers
title: Ops Container
category: ops
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers: *containers
- size: small
widgets:
- type: bookmarks
title: Ops
groups:
- title: Tools
color: 4 78 57
links:
- title: Glances
url: https://glances.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glances.svg
- title: Scrutiny
url: https://scrutiny.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/scrutiny.svg
- title: Speedtest
url: https://speedtest.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/speedtest-tracker.png
+56
View File
@@ -0,0 +1,56 @@
services:
glance:
image: glanceapp/glance:v0.8.4
container_name: glance
restart: unless-stopped
environment:
TZ: Europe/Berlin
GLANCE_IMMICH_API_KEY: ${GLANCE_IMMICH_API_KEY:-}
GLANCE_ADGUARD_USERNAME: ${GLANCE_ADGUARD_USERNAME:-}
GLANCE_ADGUARD_PASSWORD: ${GLANCE_ADGUARD_PASSWORD:-}
GLANCE_SPEEDTEST_API_KEY: ${GLANCE_SPEEDTEST_API_KEY:-}
volumes:
- ./config:/app/config:ro
networks:
- frontend_net
- glance_socket_net
depends_on:
- glance-docker-socket-proxy
labels:
- traefik.enable=true
- traefik.docker.network=frontend_net
- traefik.http.routers.glance.rule=Host(`glance.kaleschke.info`)
- traefik.http.routers.glance.entrypoints=websecure
- traefik.http.routers.glance.tls=true
- traefik.http.routers.glance.tls.certresolver=le
- traefik.http.routers.glance.middlewares=authelia@file,secure-headers@file
- traefik.http.services.glance.loadbalancer.server.port=8080
security_opt:
- no-new-privileges:true
glance-docker-socket-proxy:
image: tecnativa/docker-socket-proxy:v0.4.2
container_name: glance-docker-socket-proxy
restart: unless-stopped
environment:
LOG_LEVEL: warning
POST: "0"
CONTAINERS: "1"
INFO: "1"
VERSION: "1"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- glance_socket_net
expose:
- "2375"
security_opt:
- no-new-privileges:true
networks:
frontend_net:
external: true
glance_socket_net:
name: glance_socket_net
internal: true
driver: bridge
-63
View File
@@ -1,63 +0,0 @@
# Grafana + InfluxDB 3 Core
Vorbereiteter Monitoring-Stack. Noch nicht deployen, bis die Secrets und der erste InfluxDB-Token sauber angelegt sind.
## Quellen / Entscheidungen
- Grafana nutzt das offizielle OSS-Image `grafana/grafana:12.4.3`.
- InfluxDB nutzt `influxdb:3.9.1-core`, nicht `latest`, weil `latest` bei InfluxDB aktiv in Richtung InfluxDB 3 umgestellt wird.
- Grafana wird ueber Traefik + `authelia@file,secure-headers@file` unter `grafana.kaleschke.info` veroeffentlicht.
- InfluxDB bleibt ohne direkten Host-Port und ohne Traefik-Route im internen Compose-Netz `grafana_influx_internal`.
- Grafana provisioning legt eine SQL-Datenquelle fuer InfluxDB 3 Core mit der Datenbank `homelab` an.
- Der Grafana-Datasource-Token liegt als Secret-Datei auf dem Host und wird beim Containerstart nur containerintern in die fuer Grafana-Provisioning noetige Environment-Variable geladen.
## Vor dem ersten Deploy
1. Secret fuer Grafana anlegen:
```bash
install -m 600 /dev/null /mnt/user/appdata/secrets/grafana_admin_password.txt
```
2. Offline-Admin-Token fuer InfluxDB 3 als JSON anlegen:
```json
{
"token": "apiv3_REPLACE_WITH_STRONG_RANDOM_TOKEN",
"name": "admin",
"description": "Admin token for KalliLab InfluxDB 3 Core"
}
```
Pfad: `/mnt/user/appdata/secrets/influxdb3_admin_token.json`, Rechte `600`.
3. Grafana-Datasource-Token anlegen. Fuer den ersten Start kann der Token aus `influxdb3_admin_token.json` verwendet werden; sobald ein eingeschraenkter Read-Token existiert, diesen hier eintragen:
```bash
install -m 600 /dev/null /mnt/user/appdata/secrets/grafana_influxdb_token.txt
```
4. Provisioning-Datei aus dem Git-Checkout auf den Host-Appdata-Pfad kopieren:
```bash
mkdir -p /mnt/user/appdata/grafana/provisioning/datasources
cp /mnt/user/appdata/komodo/core/repos/homelab-infra/ops/grafana-influxdb/provisioning/datasources/influxdb.yml /mnt/user/appdata/grafana/provisioning/datasources/influxdb.yml
chmod 644 /mnt/user/appdata/grafana/provisioning/datasources/influxdb.yml
```
5. Nach dem ersten Start die Datenbank anlegen:
```bash
docker exec influxdb3-core influxdb3 create database homelab --token "$INFLUXDB3_AUTH_TOKEN"
```
## Smoke-Test nach Deploy
- `https://grafana.kaleschke.info` oeffnet nach Authelia die Grafana-Loginseite.
- Grafana `Connections -> Data sources -> InfluxDB 3 Core -> Save & test` ist erfolgreich.
- InfluxDB bleibt von aussen nicht direkt erreichbar.
## Rollback
- Stack in Komodo stoppen oder Git auf den letzten Stand ohne `ops/grafana-influxdb` zuruecknehmen.
- Persistente Daten liegen unter `/mnt/user/appdata/grafana` und `/mnt/user/appdata/influxdb3`; nicht automatisch loeschen.
-80
View File
@@ -1,80 +0,0 @@
services:
grafana:
image: grafana/grafana:12.4.3
container_name: grafana
restart: unless-stopped
user: "0"
environment:
GF_SERVER_ROOT_URL: https://grafana.kaleschke.info/
GF_SECURITY_ADMIN_PASSWORD__FILE: /run/secrets/grafana_admin_password
GF_USERS_ALLOW_SIGN_UP: "false"
GF_AUTH_ANONYMOUS_ENABLED: "false"
entrypoint:
- /bin/sh
- -c
- |
export GRAFANA_INFLUXDB_TOKEN="$$(cat /run/secrets/grafana_influxdb_token)"
exec /run.sh
volumes:
- /mnt/user/appdata/grafana:/var/lib/grafana
- /mnt/user/appdata/grafana/provisioning:/etc/grafana/provisioning:ro
secrets:
- grafana_admin_password
- grafana_influxdb_token
networks:
- frontend_net
- grafana_influx_internal
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
labels:
- traefik.enable=true
- traefik.docker.network=frontend_net
- traefik.http.routers.grafana.rule=Host(`grafana.kaleschke.info`)
- traefik.http.routers.grafana.entrypoints=websecure
- traefik.http.routers.grafana.tls=true
- traefik.http.routers.grafana.tls.certresolver=le
- traefik.http.routers.grafana.middlewares=authelia@file,secure-headers@file
- traefik.http.services.grafana.loadbalancer.server.port=3000
influxdb3-core:
image: influxdb:3.9.1-core
container_name: influxdb3-core
restart: unless-stopped
user: "0"
command:
- influxdb3
- serve
- --node-id=kallilabcore
- --object-store=file
- --data-dir=/var/lib/influxdb3/data
- --plugin-dir=/var/lib/influxdb3/plugins
- --admin-token-file=/run/secrets/influxdb3_admin_token
volumes:
- /mnt/user/appdata/influxdb3/data:/var/lib/influxdb3/data
- /mnt/user/appdata/influxdb3/plugins:/var/lib/influxdb3/plugins
secrets:
- influxdb3_admin_token
networks:
- grafana_influx_internal
security_opt:
- no-new-privileges:true
secrets:
grafana_admin_password:
file: /mnt/user/appdata/secrets/grafana_admin_password.txt
influxdb3_admin_token:
file: /mnt/user/appdata/secrets/influxdb3_admin_token.json
grafana_influxdb_token:
file: /mnt/user/appdata/secrets/grafana_influxdb_token.txt
networks:
frontend_net:
external: true
grafana_influx_internal:
internal: true
@@ -1,18 +0,0 @@
apiVersion: 1
prune: true
datasources:
- name: InfluxDB 3 Core
uid: influxdb3-core
type: influxdb
access: proxy
url: http://influxdb3-core:8181
isDefault: true
jsonData:
version: SQL
dbName: homelab
httpMode: POST
insecureGrpc: true
secureJsonData:
token: $GRAFANA_INFLUXDB_TOKEN
+6
View File
@@ -5,3 +5,9 @@ USER root
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y --no-install-recommends openssh-client && \ apt-get install -y --no-install-recommends openssh-client && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
RUN cd /opt/hermes/web && \
npm install && \
npm run build
RUN python3 -c "from pathlib import Path; p = Path('/opt/hermes/hermes_cli/main.py'); s = p.read_text(); old = ' if not (web_dir / \"package.json\").exists():\n return True\n'; new = ' if not (web_dir / \"package.json\").exists():\n return True\n prebuilt_dist = web_dir.parent / \"hermes_cli\" / \"web_dist\" / \"index.html\"\n if prebuilt_dist.exists():\n return True\n'; p.write_text(s.replace(old, new))"

Some files were not shown because too many files have changed in this diff Show More