Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f77a69a0b2 | |||
| f73cf48e41 | |||
| eea2697ca1 | |||
| a3d77d7529 | |||
| 02a50e1a58 | |||
| 267e76059a | |||
| 9d4fee02ca | |||
| 24ebcaa3c7 | |||
| 45bae13aa0 | |||
| ff5991cec8 | |||
| 5b6e7b8b66 | |||
| 5cb401797d | |||
| 1d0cba92bd | |||
| 9353a9fc44 | |||
| d50b11784d | |||
| 09eeac51e1 | |||
| 565940b9ef | |||
| b6bbca43ad | |||
| 388e57e385 | |||
| 0c2bb8484a | |||
| a7797fd02e | |||
| bac927bbcc | |||
| add8b71ea9 | |||
| e21e89e51b | |||
| 4e4684b616 | |||
| 84030956ac | |||
| 17fe8073bb | |||
| 9f32ba72c1 | |||
| e9a7f79025 | |||
| 43727151df | |||
| 66ee10cb55 | |||
| ab68900216 | |||
| 8f56c6edcd | |||
| 8e400fb3c3 | |||
| cd650b19ac | |||
| af231dd4e8 | |||
| 428223d2e4 | |||
| b6d3ed4832 | |||
| 9e7bebbd3c | |||
| b7cbbe51de | |||
| 71ac18b21c | |||
| 90f270be96 | |||
| e28f8dabec | |||
| edfec5b66d | |||
| 59bec9ac77 | |||
| 9f86da708a | |||
| d6170211c4 | |||
| fb681086f3 | |||
| 5b101f3b3d | |||
| 669efbd57e | |||
| 2dd5590a2a | |||
| 175cd6951f | |||
| aeb7573b03 | |||
| 215f44b962 | |||
| 6ce625f77a | |||
| c3c8060ddf | |||
| 29eaf8001f | |||
| db7dc3f2af | |||
| c748236886 | |||
| 8aa850df40 | |||
| 2c4854f628 | |||
| b7050812d4 | |||
| c95fa601f0 | |||
| 0c308ff352 | |||
| 53216e50c1 | |||
| b7dfdad621 | |||
| 61625a7a1c | |||
| 6e28ea94d2 | |||
| 58eb53a6a8 | |||
| d345d770c2 | |||
| 2e136d9060 | |||
| 6ca829ec45 | |||
| ef3b546d30 | |||
| 6f684fb4e3 | |||
| 0adddb6533 | |||
| 162421e537 | |||
| bf30240217 | |||
| 5f7940aa01 | |||
| a5add937f8 | |||
| 5ada1ad153 | |||
| ead7e1e17d | |||
| 14e9c0963d | |||
| 23262cd7b9 | |||
| 878ad2d5f1 | |||
| 12a87ad342 | |||
| 0e7e639df4 | |||
| 18df2d155d | |||
| fa177155e6 | |||
| 11c863c8aa | |||
| dd9f677779 | |||
| a9e62ee8e5 | |||
| 4e7d313a20 | |||
| 57ea7507a7 | |||
| 1e3e019f28 | |||
| 54fd0c3347 |
@@ -27,3 +27,4 @@
|
||||
Thumbs.db
|
||||
*.tmp
|
||||
*.log
|
||||
.serena/
|
||||
|
||||
@@ -69,6 +69,8 @@ Standard-Workflow:
|
||||
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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
> **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.
|
||||
|
||||
**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
|
||||
|
||||
### 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` und `influxdb3-core` Port 8181 als LAN-only Writer-Endpunkt fuer Home Assistant.
|
||||
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
|
||||
- `frontend_net` = Proxy-/Web-Netz
|
||||
- `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`
|
||||
Postgres, Redis und ähnliche Dienste laufen ausschließlich in `backend_net` oder einem eigenen internen Compose-Netz.
|
||||
|
||||
### 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
|
||||
Alle produktiven Container werden als Compose verwaltet. Bestehende Dockerman-/Ad-hoc-Container werden schrittweise migriert.
|
||||
@@ -87,11 +87,12 @@ Jeder produktive Container nutzt `restart: unless-stopped`, außer eine Ausnahme
|
||||
| `frontend_net` | bridge, external | einziges Traefik-/Web-Netz | Standard |
|
||||
| `backend_net` | bridge, `internal: true` | interne App-/DB-/Cache-Kommunikation | Standard |
|
||||
| `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 |
|
||||
| `nextcloud_internal` | bridge, `internal: true` | internes Netz nur fuer `nextcloud` + `nextcloud-postgres` + `nextcloud-redis` | ✅ vorbereitet |
|
||||
| `grafana_influx_internal` | Compose-intern, `internal: true` | interne Grafana-zu-InfluxDB-Kommunikation | ✅ umgesetzt |
|
||||
| `grafana_influx_lan` | Compose-intern, bridge | nicht-oeffentliches Zusatznetz nur fuer Docker Host-Port-Publishing von InfluxDB 8181 | ✅ umgesetzt |
|
||||
| `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 |
|
||||
|
||||
### 3.2 Finales Diagramm (vereinfacht)
|
||||
@@ -103,7 +104,7 @@ traefik (80/443)
|
||||
│
|
||||
└── frontend_net
|
||||
├── ö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)
|
||||
└── Dienste mit Internetbedarf ohne öffentliche UI (ddns-updater)
|
||||
|
||||
@@ -118,11 +119,11 @@ dns_net
|
||||
└── unbound
|
||||
|
||||
App-interne Netze
|
||||
├── mealie_mealie_internal (internal: true) ✅
|
||||
├── mealie_internal (internal: true) ✅
|
||||
├── immich_default (internal: true) ✅
|
||||
├── nextcloud_internal (internal: true) ✅
|
||||
├── grafana_influx_internal (internal: true)
|
||||
└── grafana_influx_lan (Bridge fuer LAN-Port-Publishing, keine Traefik-Route)
|
||||
├── monitoring_net (zentraler Observability-Stack)
|
||||
└── monitoring_influx_lan (Bridge fuer LAN-Port-Publishing, keine Traefik-Route)
|
||||
|
||||
Host-Sonderfälle
|
||||
├── tailscale
|
||||
@@ -149,22 +150,20 @@ Diese Dienste sind über echte `*.kaleschke.info`-Domains erreichbar:
|
||||
Diese Dienste sind **keine Public Apps**:
|
||||
|
||||
- `Komodo` — komodo.kaleschke.info (Traefik, aber bewusst ohne zentrale Middleware; native Auth bleibt aktiv)
|
||||
- `UptimeKuma` — uptime.kaleschke.info (Middleware)
|
||||
- `filebrowser` — files.kaleschke.info (Middleware)
|
||||
- `scrutiny` — scrutiny.kaleschke.info (Middleware)
|
||||
- `code-server` — Traefik + Middleware
|
||||
- `backrest` — Traefik + Middleware
|
||||
- `borg-ui` — borg.kaleschke.info (Middleware)
|
||||
- `homepage` — home.kaleschke.info (Middleware)
|
||||
- `glance` — glance.kaleschke.info (Middleware)
|
||||
- `paperless-gpt` — paperless-gpt.kaleschke.info (Middleware)
|
||||
- `mail-archiver` — mail.kaleschke.info (Middleware + App-Auth)
|
||||
- `glances` — glances.kaleschke.info (Middleware)
|
||||
- `speedtest-tracker` — speedtest.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`
|
||||
- `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
|
||||
Wenn ein Dienst im `frontend_net` hängt, heißt das **nicht automatisch öffentlich**. Admin-Dienste dürfen im `frontend_net` liegen, wenn:
|
||||
@@ -236,12 +235,10 @@ Legende Status:
|
||||
| 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 | — |
|
||||
| `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 | — |
|
||||
| `ddns-updater` | ✅ | `frontend_net` | intern | Cloudflare DNS API; bleibt in `frontend_net` | Dokumentierte Ausnahme |
|
||||
| `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
|
||||
|
||||
@@ -256,7 +253,7 @@ Legende Status:
|
||||
|---|---|---|---|---|---|
|
||||
| `postgresql17` | ✅ | `backend_net` | intern | kein Host-Port, `POSTGRES_PASSWORD_FILE` | — |
|
||||
| `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_redis` | ⏳ | `immich_default` | intern | intern-only | anonymes Volume → named volume |
|
||||
| `nextcloud-postgres` | ✅ | `nextcloud_internal` | intern | app-eigene Nextcloud-Datenbank mit `_FILE`-Secret | — |
|
||||
@@ -268,12 +265,13 @@ Legende Status:
|
||||
|---|---|---|---|---|---|
|
||||
| `paperless-ngx` | ✅ | `frontend_net`, `backend_net` | Traefik | aktiv via `paperless.kaleschke.info` | — |
|
||||
| `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 | — |
|
||||
| `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_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 |
|
||||
| `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
|
||||
|
||||
@@ -282,7 +280,7 @@ Legende Status:
|
||||
| `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 | — |
|
||||
| `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 |
|
||||
| `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 |
|
||||
@@ -292,18 +290,21 @@ Legende Status:
|
||||
|
||||
| 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` | — |
|
||||
| `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` | — |
|
||||
| `grafana` | ✅ | `frontend_net`, `grafana_influx_internal` | Traefik + Middleware | aktiv via `grafana.kaleschke.info`, InfluxDB-Datenquelle provisioniert; laeuft aktuell als `user: "0"` wegen Host-Appdata-Permissions | Wetter-/HA-Dashboard aufbauen; UID/GID-Hardening spaeter pruefen |
|
||||
| `influxdb3-core` | ✅ | `grafana_influx_internal`, `grafana_influx_lan` + LAN-Bind | LAN-Port nur fuer interne Writer | InfluxDB 3 Core fuer Metriken; keine Traefik-/Public-Freigabe; Port 8181 nur via `INFLUXDB_BIND_IP`; laeuft aktuell als `user: "0"` wegen Host-Appdata-Permissions | HA-Write-Token und Sensor-Export finalisieren; UID/GID-Hardening spaeter pruefen |
|
||||
| `monitoring-grafana` | ✅ | `frontend_net`, `monitoring_net` | Traefik + Middleware | zentrale UI via `monitoring.kaleschke.info`; Datasources fuer Prometheus, Loki und InfluxDB | — |
|
||||
| `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
|
||||
|
||||
| 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
|
||||
|
||||
@@ -319,11 +320,15 @@ Legende Status:
|
||||
| `dashdot` | 2026-03-28 | nicht mehr aktiv |
|
||||
| `netdata` | 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 |
|
||||
| `PortainerCE` | 2026-03-29 | abgeschaltet; Komodo ist alleiniger Stack-Manager |
|
||||
| `beszel` | 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 |
|
||||
|
||||
---
|
||||
|
||||
@@ -384,18 +389,19 @@ Für den laufenden Betrieb gilt stattdessen:
|
||||
|---|---|---|
|
||||
| `traefik` | Host-Ports 80/443 | zentraler Reverse Proxy |
|
||||
| `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 |
|
||||
| `scrutiny` | `privileged: true` | SMART-Datenzugriff auf Laufwerke |
|
||||
| `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 |
|
||||
| `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` |
|
||||
| `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 |
|
||||
| `nextcloud` | keine zentrale ForwardAuth-Middleware | Nextcloud bringt eigene Auth, Clients und WebDAV/CardDAV-Endpunkte mit; Traefik bleibt Reverse Proxy, Auth bleibt app-nativ |
|
||||
| `influxdb3-core` | Host-Port 8181 auf LAN-IP | 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` |
|
||||
| `grafana`, `influxdb3-core` | `user: "0"` | aktueller Live-Stand fuer Host-Appdata-/Plugin-Permissions; als Ausnahme dokumentiert, spaeter separat mit UID/GID-Test haerten |
|
||||
| `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 |
|
||||
|
||||
---
|
||||
|
||||
@@ -485,7 +491,7 @@ Komodo ist nun der primäre GitOps-Stack-Manager:
|
||||
- AdGuard läuft als Git-Stack (`host-services/Adguard/docker-compose.yml`)
|
||||
- Netzwerke: `dns_net` (feste IP 172.23.0.3) + `frontend_net`
|
||||
- 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`
|
||||
|
||||
### diun — Entfernung (2026-03-28)
|
||||
@@ -535,9 +541,9 @@ Host-Pfade in `env_file` (z.B. `/mnt/...`) sind in Git-Stacks nicht verfügbar.
|
||||
### 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.
|
||||
|
||||
### Stateful Digest-Pinning (2026-05-05)
|
||||
### 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 bleiben bewusst ohne Digest-Pin. Cache-Verlust ist akzeptabel, und Sicherheits-Patches sollen dort ohne eigenen Datenbank-Upgrade-Sprint fliessen koennen.
|
||||
- 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)
|
||||
@@ -545,14 +551,22 @@ Mutable Tags wie `latest`, `stable`, `release` oder reine Major-Tags wurden auf
|
||||
- `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.
|
||||
|
||||
### 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 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` betrieben.
|
||||
- `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 `influxdb3-core` zusaetzlich zum internen Grafana-Netz `grafana_influx_lan`. Das ist keine Public-App-Freigabe und ersetzt nicht die Token-Authentifizierung.
|
||||
- `monitoring/` ist der zentrale Zielstack fuer Prometheus, Loki, Promtail, Grafana, node-exporter, cAdvisor und InfluxDB 3 Core.
|
||||
- `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.
|
||||
- `grafana` und `influxdb3-core` laufen aktuell als `user: "0"`; das ist als Host-Appdata-Permissions-Ausnahme dokumentiert und wird nicht nebenbei geaendert.
|
||||
- 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.
|
||||
@@ -569,7 +583,7 @@ Benötigt `backend_net` (PostgreSQL) + `frontend_net` (IMAP-Abruf von GMX/Gmail)
|
||||
- App → `frontend_net` + internes Netzwerk
|
||||
- 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`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -13,6 +13,14 @@ Bei Restore-, Host-Ausfall- oder Wiederanlauf-Fragen zusaetzlich:
|
||||
|
||||
3. `docs/DISASTER_RECOVERY.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
|
||||
|
||||
@@ -38,7 +46,8 @@ Bei Restore-, Host-Ausfall- oder Wiederanlauf-Fragen zusaetzlich:
|
||||
- `security/` -> sicherheitskritische Dienste
|
||||
- `infra/` -> Datenbanken und technische Services
|
||||
- `apps/` -> Anwendungen
|
||||
- `ops/` -> Monitoring und Tools
|
||||
- `ops/` -> operative Tools
|
||||
- `monitoring/` -> zentraler Observability-Stack
|
||||
- `host-services/` -> Dienste mit Host-Netz
|
||||
- `traefik/` -> Reverse Proxy Konfiguration
|
||||
- `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 bleibt bewusst bei nativer Authentifizierung; zentrale Traefik-Auth wird dort nicht pauschal vorgeschaltet.
|
||||
- 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.
|
||||
- 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.
|
||||
- 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`.
|
||||
- `nextcloud`, `bentopdf` und `grafana-influxdb` folgen dem dokumentierten Netz-/Secret-/Traefik-Modell; Grafana/InfluxDB ist fuer Home-Assistant-Wetterdaten vorbereitet.
|
||||
- `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,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
|
||||
@@ -12,10 +12,10 @@ services:
|
||||
DB_PASSWORD: ${IMMICH_DB_PASSWORD}
|
||||
DB_DATABASE_NAME: immich
|
||||
REDIS_HOSTNAME: redis
|
||||
TZ: Europe/Berlin
|
||||
volumes:
|
||||
- /mnt/user/photos/immich:/usr/src/app/upload
|
||||
- /mnt/user/photos/family_archive:/usr/src/app/external
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
networks:
|
||||
- immich_default
|
||||
- frontend_net
|
||||
@@ -43,7 +43,7 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: redis:7
|
||||
image: redis:7.4-alpine@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- immich_default
|
||||
|
||||
@@ -14,13 +14,12 @@ services:
|
||||
POSTGRES_SERVER: mealie-postgres
|
||||
POSTGRES_DB: mealie
|
||||
POSTGRES_USER: mealie
|
||||
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
|
||||
POSTGRES_PASSWORD: ${MEALIE_POSTGRES_PASSWORD}
|
||||
|
||||
BASE_URL: https://mealie.kaleschke.info
|
||||
|
||||
volumes:
|
||||
- /mnt/user/appdata/mealie/data:/app/data
|
||||
- /mnt/user/appdata/secrets/mealie_postgres_password.txt:/run/secrets/postgres_password:ro
|
||||
|
||||
networks:
|
||||
- frontend_net
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
nextcloud:
|
||||
image: nextcloud:33.0.2-apache
|
||||
image: nextcloud:33.0.2-apache@sha256:39b2ba219271a22851f8409a7b1295d5892aba1696d9193500311c02e60591a4
|
||||
container_name: nextcloud
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
@@ -64,7 +64,7 @@ services:
|
||||
- no-new-privileges:true
|
||||
|
||||
nextcloud-redis:
|
||||
image: redis:7.4-alpine
|
||||
image: redis:7.4-alpine@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99
|
||||
container_name: nextcloud-redis
|
||||
restart: unless-stopped
|
||||
command: redis-server --save 60 1 --loglevel warning
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
unbound:
|
||||
image: shaanmajid/unbound:latest@sha256:0a163e92e55698ddc45c0265884a86c7363da245ab4a909a8e5cb0f541aeeb4d
|
||||
image: shaanmajid/unbound:1.24.2@sha256:d278b71c592b2555cc802911bb0757a6a24f4a8ad7f5848720296c04876eeb63
|
||||
container_name: unbound
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
|
||||
@@ -11,9 +11,17 @@ services:
|
||||
- GITEA__server__DOMAIN=git.kaleschke.info
|
||||
- GITEA__server__ROOT_URL=https://git.kaleschke.info/
|
||||
- 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:
|
||||
- /mnt/user/services/gitea/data:/data
|
||||
dns:
|
||||
- 1.1.1.1
|
||||
- 8.8.8.8
|
||||
ports:
|
||||
- "222:22"
|
||||
networks:
|
||||
|
||||
+17
-10
@@ -39,6 +39,8 @@ Traefik ist der zentrale Web-Einstieg fuer HTTP(S). Admin-/Ops-UIs liegen entwed
|
||||
- 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
|
||||
|
||||
@@ -50,7 +52,7 @@ Traefik ist der zentrale Web-Einstieg fuer HTTP(S). Admin-/Ops-UIs liegen entwed
|
||||
|
||||
### Apps
|
||||
|
||||
Wichtige Apps sind Paperless, Immich, Mealie, Mail Archiver, Nextcloud, ntfy, Vaultwarden und Gitea. Admin-/Ops-Tools sind u. a. Homepage, Komodo, Borg UI, Backrest, Uptime Kuma, Filebrowser, code-server, Glances, Scrutiny, Speedtest, Grafana und Hermes Agent.
|
||||
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
|
||||
|
||||
@@ -78,11 +80,14 @@ Nach Aenderungen an `services.json` oder `check_health.py`: `git pull` auf der V
|
||||
|
||||
### Monitoring / Metriken
|
||||
|
||||
- Grafana laeuft unter `grafana.kaleschke.info` hinter Traefik + Authelia.
|
||||
- InfluxDB 3 Core ist nicht public und nicht im `frontend_net`.
|
||||
- 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.
|
||||
- Grafana und InfluxDB laufen aktuell als `user: "0"`; nicht ad hoc aendern, sondern als eigenen UID/GID-Hardening-Sprint behandeln.
|
||||
- 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
|
||||
|
||||
@@ -99,6 +104,8 @@ Normalfall:
|
||||
|
||||
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 |
|
||||
@@ -106,7 +113,7 @@ Wichtig: Komodo-Web-Editor ist nicht der Bearbeitungsort. Wenn Komodo und Git vo
|
||||
| `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, Grafana/Influx |
|
||||
| app-interne Netze | Isolation von App + DB/Cache, z. B. Immich, Mealie, Nextcloud, Monitoring |
|
||||
| `host` | nur dokumentierte Sonderfaelle wie Tailscale/Plex |
|
||||
|
||||
Regeln:
|
||||
@@ -114,7 +121,7 @@ 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. `grafana_grafana_influx_lan`.
|
||||
- Runtime-Netznamen koennen Compose-Projektpraefixe bekommen, z. B. `monitoring_monitoring_influx_lan`.
|
||||
|
||||
## Security-Modell
|
||||
|
||||
@@ -129,12 +136,12 @@ Bekannte Ausnahmen:
|
||||
|
||||
- Traefik: 80/443
|
||||
- Gitea: SSH 222
|
||||
- AdGuard: DNS 53 und Admin 8082
|
||||
- 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
|
||||
- Grafana/InfluxDB: `user: "0"` als dokumentierte Host-Appdata-Permissions-Ausnahme
|
||||
- `monitoring-influxdb3-core`: `user: "0"` als dokumentierte Host-Appdata-Permissions-Ausnahme
|
||||
- Traefik dynamic config: manueller Host-Sync
|
||||
|
||||
## Backup- und Restore-Modell
|
||||
@@ -183,8 +190,8 @@ KI-Agenten sollen konservativ arbeiten: keine indirekten Live-Aenderungen, keine
|
||||
- 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`.
|
||||
- `homepage`, `glances` und `komodo-periphery` nutzen Docker-Socket-Mounts; Zugriff bewusst behandeln.
|
||||
- `backrest`, `borg-ui` und `filebrowser` haben breite Mounts; bei Hardening nicht ad hoc, sondern gezielt vorgehen.
|
||||
- `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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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. 97–139 (`dump_sqlite_container`), Z. 253–258 (Nextcloud `pg_dump`), Z. 261–264 (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. 23–25). |
|
||||
| **(2) Filebrowser entschaerfen** — `/mnt/user/appdata:/srv/appdata` weg, gezielte RW-Subpfade | offen | ✅ erledigt | `ops/filebrowser/docker-compose.yml` Z. 11–16. 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. 17–25. 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 | 1–2 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 | 1–2 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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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 5–15 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 (1–2 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 (2–3 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 (3–4 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 5–10 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 0–5 und bist nach < 8 h wieder im Vollbetrieb. Repo-Bootstrap aus GitHub-Mirror, Stacks in Stufen 1–5, 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 1–3 in 4–6 Wochen sitzen, bist du auf einer 1-Note. Wenn dann Sprint 4–5 in weiteren 6–8 Wochen kommen, hat die Familie ein echtes Self-Hosting-System, kein "Container-Sammlung im Keller". Das ist der Unterschied, den der Audit-Auftrag adressiert.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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`.
|
||||
@@ -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 |
|
||||
@@ -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.
|
||||
+34
-26
@@ -9,6 +9,8 @@ Verwandte Dokumente:
|
||||
- `docs/ROLLBACK.md` - Rueckweg bei Fehlern im laufenden GitOps-Betrieb
|
||||
- `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
|
||||
|
||||
---
|
||||
@@ -60,12 +62,14 @@ Diese Punkte sollten **vor** einem echten Ausfall geklaert sein:
|
||||
|
||||
| Thema | Sollzustand |
|
||||
|---|---|
|
||||
| Repo-Zugang ausserhalb von Gitea | externer Mirror oder lokaler aktueller Clone vorhanden |
|
||||
| Unraid USB-/Flash-Backup | eingerichtet und wiederherstellbar |
|
||||
| Repo-Zugang ausserhalb von Gitea | privater GitHub-Push-Mirror `michaelkaleschke-spec/homelab-infra` und lokaler aktueller Clone vorhanden |
|
||||
| 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-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 |
|
||||
| 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 |
|
||||
|
||||
**Wichtig:** Dieses Dokument ist nur so gut wie die Vorbereitung ausserhalb des Repos.
|
||||
@@ -81,13 +85,13 @@ Deshalb gilt:
|
||||
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.
|
||||
|
||||
Empfohlene Wege:
|
||||
Verfuegbare Wege:
|
||||
|
||||
- externer Push-Mirror
|
||||
- externer Push-Mirror: `https://github.com/michaelkaleschke-spec/homelab-infra`
|
||||
- lokaler Bare-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.
|
||||
|
||||
---
|
||||
|
||||
@@ -100,6 +104,8 @@ Wenn **kein externer Repo-Zugang** besteht, ist `services/gitea/data` selbst ein
|
||||
3. Array wieder zuweisen und starten
|
||||
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
|
||||
|
||||
Mindestens diese Pfade muessen wieder verfuegbar sein:
|
||||
@@ -165,7 +171,6 @@ Diese Werte sind vor dem Start der betroffenen Dienste zu pruefen bzw. wieder in
|
||||
- `KOMODO_JWT_SECRET`
|
||||
- `KOMODO_MONGO_PASSWORD`
|
||||
- `KOMODO_PERIPHERY_PASSKEY`
|
||||
- relevante `HOMEPAGE_VAR_*`
|
||||
- `APP_KEY` und `ADMIN_PASSWORD` fuer `speedtest-tracker`
|
||||
|
||||
### 6.3 Rechte
|
||||
@@ -200,11 +205,16 @@ Besonders kritisch:
|
||||
|
||||
- `/mnt/user/appdata/secrets`
|
||||
- `/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/appdata/authelia/config`
|
||||
- `/mnt/user/appdata/komodo/core`
|
||||
- `/mnt/user/appdata/komodo/periphery`
|
||||
- `/mnt/user/backups/borg/dumps/latest`
|
||||
- `/mnt/user/backups/borg/dumps/latest/unraid-flash-config.tar.gz`
|
||||
- dienstspezifische App- und Nutzdatenpfade
|
||||
|
||||
**Nicht blind alles extrahieren**, wenn nur einzelne Pfade oder Dienste betroffen sind.
|
||||
@@ -263,20 +273,18 @@ Ziel:
|
||||
|
||||
### Stufe 5 - Restliche Apps und Ops
|
||||
|
||||
15. `apps/homepage/`
|
||||
16. `apps/ntfy/`
|
||||
17. `apps/paperless-gpt/`
|
||||
18. `apps/bentopdf/`
|
||||
19. `ops/uptime-kuma/`
|
||||
20. `ops/borg-ui/`
|
||||
21. `ops/backrest/`
|
||||
22. `ops/filebrowser/`
|
||||
23. `ops/glances/`
|
||||
24. `ops/scrutiny/`
|
||||
25. `ops/speedtest/`
|
||||
26. `ops/grafana-influxdb/`
|
||||
27. `ops/hermes-agent/`
|
||||
28. `infra/ddns-updater/`
|
||||
15. `apps/ntfy/`
|
||||
16. `apps/paperless-gpt/`
|
||||
17. `apps/bentopdf/`
|
||||
18. `ops/glance/`
|
||||
19. `ops/borg-ui/`
|
||||
20. `ops/filebrowser/`
|
||||
21. `ops/glances/`
|
||||
22. `ops/scrutiny/`
|
||||
23. `ops/speedtest/`
|
||||
24. `monitoring/`
|
||||
25. `ops/hermes-agent/`
|
||||
26. `infra/ddns-updater/`
|
||||
|
||||
**Regel:** Nach jeder Stufe kurz pruefen, bevor die naechste beginnt.
|
||||
|
||||
@@ -311,7 +319,7 @@ Ziel:
|
||||
|
||||
- Borg UI startet und kennt sein Repo noch
|
||||
- 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
|
||||
|
||||
---
|
||||
@@ -368,6 +376,7 @@ Relevant:
|
||||
|
||||
- Dump-Ziel: `/mnt/user/backups/borg/dumps/latest`
|
||||
- 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
|
||||
|
||||
@@ -384,15 +393,14 @@ Smoke-Test: `hermes-gateway` healthcheck ist gruen, `hermes.kaleschke.info` leit
|
||||
|
||||
### 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
|
||||
|
||||
- externer Repo-Zugang fuer den Ernstfall absichern
|
||||
- Unraid-USB-/Flash-Backup pruefen
|
||||
- Borg-Passphrase extern sicher hinterlegen
|
||||
- Unraid-USB-/Flash-Backup regelmaessig ueber `unraid-flash-config.tar.gz` und optional Unraid Connect pruefen
|
||||
- Borg-Passphrase aus `/mnt/user/appdata/secrets/borg_repo_passphrase.txt` extern analog sicher hinterlegen
|
||||
- Komodo Stack-ENV-Werte zentral ausserhalb von Komodo dokumentieren
|
||||
- regelmaessige automatisierte Restore-Smoke-Tests fuer Vaultwarden, Gitea und Paperless etablieren
|
||||
- `komodo-mongo`-Dump nach Major-Upgrades gezielt kontrollieren
|
||||
|
||||
@@ -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.
|
||||
@@ -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 |
|
||||
@@ -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 |
|
||||
|
||||
@@ -90,18 +90,18 @@ Sollzustand:
|
||||
Soll fuer Home Assistant:
|
||||
|
||||
```bash
|
||||
cd /mnt/user/services/stacks/grafana
|
||||
cd /mnt/user/services/stacks/monitoring
|
||||
git fetch --all --prune
|
||||
git reset --hard origin/master
|
||||
docker compose --env-file .env -p grafana -f ops/grafana-influxdb/docker-compose.yml up -d --force-recreate --no-deps influxdb3-core
|
||||
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 "grafana|influx"
|
||||
docker inspect influxdb3-core --format '{{json .NetworkSettings.Networks}}'
|
||||
docker inspect influxdb3-core --format '{{json .NetworkSettings.Ports}}'
|
||||
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/
|
||||
@@ -110,12 +110,12 @@ curl -i --max-time 5 http://192.168.178.58:8181/
|
||||
Erwartung:
|
||||
|
||||
- Komodo Workspace `HEAD` entspricht `origin/master`.
|
||||
- `influxdb3-core` haengt an `grafana_grafana_influx_internal` und `grafana_grafana_influx_lan`.
|
||||
- `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 `grafana_influx_internal` und `grafana_influx_lan`. Durch den Compose-Projektnamen `grafana` werden daraus zur Laufzeit die Docker-Netze `grafana_grafana_influx_internal` und `grafana_grafana_influx_lan`.
|
||||
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
|
||||
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -15,13 +15,13 @@ Ziel: Home Assistant schreibt ausgewaehlte Ecowitt- und Energiesensoren nach Inf
|
||||
|
||||
Der Stack haelt InfluxDB bewusst ohne Traefik-Route. Fuer Home Assistant wird nur der HTTP-Port `8181` auf einer internen LAN-Adresse veroeffentlicht.
|
||||
|
||||
In Komodo/Stack-Environment fuer `ops/grafana-influxdb` setzen:
|
||||
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 `influxdb3-core` laeuft. Nicht `0.0.0.0` verwenden, wenn es nicht notwendig ist.
|
||||
`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:
|
||||
|
||||
@@ -97,7 +97,7 @@ ha core restart
|
||||
|
||||
## 5. Grafana Smoke-Test
|
||||
|
||||
In Grafana mit der bestehenden Datenquelle `InfluxDB 3 Core` eine SQL-Abfrage testen:
|
||||
In `https://monitoring.kaleschke.info` mit der bestehenden Datenquelle `InfluxDB 3 Core` eine SQL-Abfrage testen:
|
||||
|
||||
```sql
|
||||
SHOW TABLES
|
||||
|
||||
+168
-2
@@ -8,6 +8,7 @@ Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ab
|
||||
- Komodo ist der einzige produktive Stack-Manager.
|
||||
- Portainer CE ist 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.
|
||||
- GitHub Desktop ist der bevorzugte lokale Workflow fuer `Fetch`, `Pull`, `Commit` und `Push`.
|
||||
- Mutable Image-Tags sind auf die aktuell laufenden Digests eingefroren.
|
||||
@@ -16,6 +17,162 @@ Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ab
|
||||
|
||||
## 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.
|
||||
@@ -72,6 +229,15 @@ Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ab
|
||||
- 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.
|
||||
@@ -90,7 +256,7 @@ Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ab
|
||||
- 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 bleiben bewusst ohne Digest-Pin; Redeploys erfolgen stackweise mit Smoke-Test, nicht parallel.
|
||||
- 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
|
||||
|
||||
@@ -137,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.
|
||||
- Dump-Zielpfad auf `/mnt/user/backups/borg/dumps` umgestellt.
|
||||
- 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
|
||||
|
||||
|
||||
@@ -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 |
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
+52
-36
@@ -1,6 +1,6 @@
|
||||
# Repository Map
|
||||
|
||||
Stand: 2026-05-04
|
||||
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.
|
||||
|
||||
@@ -17,6 +17,7 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
|
||||
| `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 |
|
||||
|
||||
@@ -29,8 +30,16 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
|
||||
| `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 und Smoke-Tests je Dienst |
|
||||
| `docs/ROLLBACK.md` | Rueckweg im GitOps-Betrieb |
|
||||
| `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 |
|
||||
@@ -44,12 +53,19 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
|
||||
| `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 |
|
||||
| `ops/grafana-influxdb/provisioning/datasources/influxdb.yml` | Grafana Datasource Provisioning fuer InfluxDB 3 Core |
|
||||
| `ops/borg-ui/scripts/pre-backup-dumps.sh` | Host-seitiges Dump-Skript fuer PostgreSQL, Mealie, Immich und Komodo Mongo |
|
||||
| `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 |
|
||||
| `ops/grafana-influxdb/stack.env.example` | `INFLUXDB_BIND_IP` Default `127.0.0.1`; auf Unraid fuer Home Assistant auf LAN-IP setzen |
|
||||
| `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
|
||||
@@ -59,7 +75,6 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
|
||||
| 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 |
|
||||
| Homepage | `apps/homepage/docker-compose.yml` | `homepage` -> `ghcr.io/gethomepage/homepage:latest@sha256:...` | `home.kaleschke.info` | `frontend_net` | keine | Docker-Socket read-only fuer Widgets; viele `HOMEPAGE_VAR_*` Env-Keys |
|
||||
| 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 |
|
||||
@@ -73,35 +88,35 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
|
||||
|
||||
| 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 |
|
||||
| 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-alpine` | keine | `backend_net` | keine | shared Cache, Passwort-Datei |
|
||||
| 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`, `8082:80/tcp` | Unbound in `dns_net`; direkte Ports sind dokumentierte Ausnahme |
|
||||
| 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 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Backrest | `ops/backrest/docker-compose.yml` | `backrest` -> `ghcr.io/garethgeorge/backrest:latest@sha256:...` | `backrest.kaleschke.info` | `frontend_net`, `backend_net` | keine | breite Backup-Mounts, SSH/Repo-Creds im Appdata |
|
||||
| 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:latest@sha256:...` | `code.kaleschke.info` | `frontend_net` | keine | Passwort-Datei, Workspace-Mounts |
|
||||
| Filebrowser | `ops/filebrowser/docker-compose.yml` | `filebrowser` -> `filebrowser/filebrowser:latest@sha256:...` | `files.kaleschke.info` | `frontend_net` | keine | Appdata-Mount, Admin-UI hinter Authelia |
|
||||
| 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 |
|
||||
| Grafana/InfluxDB | `ops/grafana-influxdb/docker-compose.yml` | `grafana`, `influxdb3-core` | `grafana.kaleschke.info` | `frontend_net`, `grafana_influx_internal`, `grafana_influx_lan` | `influxdb3-core`: `${INFLUXDB_BIND_IP:-127.0.0.1}:8181:8181` | InfluxDB LAN-only fuer Home Assistant; Grafana datasource token; beide Container laufen aktuell als `user: "0"` |
|
||||
| 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:latest@sha256:...` | `speedtest.kaleschke.info` | `frontend_net` | keine | App key/admin env, SQLite/config path |
|
||||
| Uptime Kuma | `ops/uptime-kuma/docker-compose.yml` | `UptimeKuma` -> `louislam/uptime-kuma:1@sha256:...` | `uptime.kaleschke.info` | `frontend_net` | keine | Monitor-State in Appdata |
|
||||
| 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
|
||||
|
||||
@@ -114,20 +129,19 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
|
||||
| Host | Service | Zugriff |
|
||||
|---|---|---|
|
||||
| `auth.kaleschke.info` | Authelia | Auth provider / bypass fuer eigene Domain |
|
||||
| `backrest.kaleschke.info` | Backrest | Traefik + Authelia |
|
||||
| `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 |
|
||||
| `grafana.kaleschke.info` | Grafana | Traefik + Authelia |
|
||||
| `hermes.kaleschke.info` | Hermes Dashboard | Traefik + Authelia |
|
||||
| `home.kaleschke.info` | Homepage | Traefik + Authelia; faellt in Authelia unter die 1FA-Wildcard-Regel |
|
||||
| `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 |
|
||||
@@ -135,7 +149,6 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
|
||||
| `scrutiny.kaleschke.info` | Scrutiny | Traefik + Authelia |
|
||||
| `speedtest.kaleschke.info` | Speedtest Tracker | Traefik + Authelia |
|
||||
| `traefik.kaleschke.info` | Traefik Dashboard | Traefik + Authelia |
|
||||
| `uptime.kaleschke.info` | Uptime Kuma | Traefik + Authelia |
|
||||
| `vault.kaleschke.info` | Vaultwarden | Traefik |
|
||||
|
||||
## Networks
|
||||
@@ -146,13 +159,14 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
|
||||
| `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 | Mealie und Mealie Postgres |
|
||||
| `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 |
|
||||
| `grafana_influx_internal` | Compose-intern, `internal: true` | Grafana und InfluxDB |
|
||||
| `grafana_influx_lan` | Compose-intern bridge | InfluxDB Host-Port-Publishing fuer LAN Writer |
|
||||
| `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 historisch ausserhalb Repo |
|
||||
| `host` | Host-Netz | Tailscale; Plex als Repo-Compose-Stack unter `host-services/plex/` |
|
||||
|
||||
## Volumes und Datenpfade
|
||||
|
||||
@@ -169,20 +183,19 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
|
||||
| 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` |
|
||||
| Homepage | `/mnt/user/appdata/homepage`, `/mnt/user/appdata/homepage/images`, Docker socket read-only |
|
||||
| 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` |
|
||||
| Backrest | `/mnt/user/appdata/backrest/*`, broad `/mnt/user/...` source mounts, remote repo mount |
|
||||
| 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`, Homepage production mount |
|
||||
| Filebrowser | `/mnt/user/appdata`, Filebrowser database/config paths |
|
||||
| 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` |
|
||||
| Uptime Kuma | `/mnt/user/appdata/uptime-kuma` |
|
||||
| Grafana/InfluxDB | `/mnt/user/appdata/grafana`, Grafana provisioning, `/mnt/user/appdata/influxdb3/data`, `/mnt/user/appdata/influxdb3/plugins` |
|
||||
| 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` |
|
||||
|
||||
@@ -198,33 +211,36 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
|
||||
| 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 |
|
||||
| Homepage | viele `HOMEPAGE_VAR_*` Stack ENV Keys fuer Tokens/Logins |
|
||||
| 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 |
|
||||
| Grafana/InfluxDB | Grafana Admin Password, InfluxDB Admin Token JSON, Grafana Datasource Token |
|
||||
| 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.
|
||||
- `apps/mealie` nutzt in Compose `mealie_internal`; Architektur-Doku nennt teils `mealie_mealie_internal`. Laufzeitnamen koennen durch Compose-Projektpraefixe abweichen.
|
||||
- `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 bleiben bewusst ohne Digest-Pin.
|
||||
- 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.
|
||||
- `grafana` und `influxdb3-core` laufen aktuell als `user: "0"`; UID/GID-Hardening nur als eigener Sprint.
|
||||
- `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-Media-Server` ist als historischer Host-Sonderfall dokumentiert, aber nicht als Repo-Compose-Stack enthalten.
|
||||
- `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`.
|
||||
|
||||
@@ -193,7 +193,7 @@ bash /mnt/user/services/homelab/ops/restore-tests/run-restore-checks.sh paperles
|
||||
### Optional mit `ntfy`
|
||||
|
||||
```bash
|
||||
bash /mnt/user/services/homelab/ops/restore-tests/run-restore-job-with-ntfy.sh freshness homelab-restore
|
||||
bash /mnt/user/services/homelab/ops/restore-tests/run-restore-job-with-ntfy.sh freshness homelab-info
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
+24
-13
@@ -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 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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` | `borg_repo_passphrase.txt` fuer Restore-Tests | Traefik | Web-UI erreichbar, Repo sichtbar, SSH-Port reagiert; 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 |
|
||||
| Vaultwarden | Borg / Share | `/mnt/user/appdata/vaultwarden` | dateibasiert | `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 |
|
||||
| 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`, `/mnt/user/services/stacks` | `komodo-mongo.archive.gz` falls verifiziert | `komodo_mongo_password.txt`, `KOMODO_*` Stack ENV | Traefik, Mongo, Gitea | UI erreichbar, Periphery verbunden |
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
@@ -46,8 +48,8 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
|
||||
| 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 |
|
||||
| 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 |
|
||||
| Homepage | Borg / Share | `/mnt/user/appdata/homepage` | keine | `HOMEPAGE_VAR_*` | Traefik, Authelia | Dashboard startet, Widgets laden |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| Paperless-GPT | Borg / Share | `/mnt/user/appdata/paperless-gpt` | keine eigene DB | `PAPERLESS_API_TOKEN` | Traefik, Paperless | UI startet, Konfiguration vorhanden |
|
||||
|
||||
@@ -57,17 +59,17 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
|
||||
|
||||
| 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 |
|
||||
| Backrest | Share | `/mnt/user/appdata/backrest/*` | keine | SSH-/Repo-Creds im Mount | Traefik | UI startet |
|
||||
| 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 |
|
||||
| Borg UI | Borg + Dump | `/mnt/user/appdata/borg-ui/data` | `borg-ui.sqlite` | Borg-Repo-Creds in `/data` | Traefik | UI startet, Repo-Verbindung bekannt |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| Hermes Agent | Borg / Share | `/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` | Traefik, Authelia, `hermes_net` | Gateway-Health ist ok, Dashboard leitet anonym zu Authelia weiter, SSH-Runner-Key ist vorhanden |
|
||||
| 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 | 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 |
|
||||
|
||||
---
|
||||
@@ -82,6 +84,15 @@ Aktuell relevante Dump-Artefakte unter `/mnt/user/backups/borg/dumps/latest`:
|
||||
- `postgresql17-authelia.dump` (optional / wenn vorhanden)
|
||||
- `mealie.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)
|
||||
|
||||
Die Dump-Erzeugung ist host-seitig ueber `ops/borg-ui/scripts/pre-backup-dumps.sh` vorgesehen.
|
||||
|
||||
+33
-1
@@ -89,11 +89,43 @@ Vor dem ersten produktiven Einsatz reicht es, den vorbereiteten Stack nicht zu d
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
## 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
|
||||
|
||||
+22
-10
@@ -20,32 +20,37 @@ 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| Paperless-ngx | DB Password | Stack ENV `${PAPERLESS_DBPASS}` | 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-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 | 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 | 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 | 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 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 |
|
||||
| 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-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 |
|
||||
| Hermes Agent | Provider-Keys, Bot-Tokens, API-Server-Key | `/mnt/user/appdata/hermes-agent/data/.env` | neu |
|
||||
| Hermes Agent | SSH-Runner Private Key | `/mnt/user/appdata/secrets/hermes_runner_id_ed25519` -> `/root/.ssh/id_ed25519` | neu |
|
||||
| Grafana | Admin Password | `/mnt/user/appdata/secrets/grafana_admin_password.txt` -> `GF_SECURITY_ADMIN_PASSWORD__FILE` | vorbereitet |
|
||||
| InfluxDB 3 Core | Admin Token JSON | `/mnt/user/appdata/secrets/influxdb3_admin_token.json` -> Docker Secret `/run/secrets/influxdb3_admin_token` | vorbereitet |
|
||||
| Grafana -> InfluxDB | Datasource Token | `/mnt/user/appdata/secrets/grafana_influxdb_token.txt` -> Docker Secret `/run/secrets/grafana_influxdb_token` | vorbereitet |
|
||||
| Borg Repo | Borg-Passphrase fuer Restore-Tests und Notfallzugriff | `/mnt/user/appdata/secrets/borg_repo_passphrase.txt` -> Host-Secret-Datei, nicht im Repo | aktiv |
|
||||
| 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 |
|
||||
| Hermes Agent | Provider-Keys, Bot-Tokens, API-Server-Key | `/mnt/user/appdata/hermes-agent/data/.env` | VM-seitig offen |
|
||||
| Hermes Agent | SSH-Runner Private Key | `/mnt/user/appdata/secrets/hermes_runner_id_ed25519` -> `/root/.ssh/id_ed25519` | VM-seitig offen |
|
||||
| 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 |
|
||||
|
||||
---
|
||||
@@ -56,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 |
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
@@ -66,18 +73,21 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
|
||||
|-- authelia_jwt_secret.txt
|
||||
|-- authelia_postgres_password.txt
|
||||
|-- authelia_session_secret.txt
|
||||
|-- authelia_smtp_password.txt
|
||||
|-- authelia_storage_encryption_key.txt
|
||||
|-- immich_postgres_password.txt
|
||||
|-- komodo_mongo_password.txt
|
||||
|-- mealie_postgres_password.txt
|
||||
|-- monitoring_grafana_admin_password.txt
|
||||
|-- monitoring_grafana_influxdb_token.txt
|
||||
|-- nextcloud_admin_password.txt
|
||||
|-- nextcloud_admin_user.txt
|
||||
|-- nextcloud_postgres_password.txt
|
||||
|-- postgres_password.txt
|
||||
|-- redis_password.txt
|
||||
|-- grafana_admin_password.txt
|
||||
|-- grafana_influxdb_token.txt
|
||||
|-- borg_repo_passphrase.txt
|
||||
|-- influxdb3_admin_token.json
|
||||
|-- filebrowser_admin_password.txt
|
||||
`-- vaultwarden_admin_token.txt
|
||||
```
|
||||
|
||||
@@ -87,6 +97,8 @@ Weitere dokumentierte Secret-Pfade:
|
||||
- `/mnt/user/appdata/secrets/hermes_runner_id_ed25519`
|
||||
- `/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.
|
||||
- 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.
|
||||
|
||||
---
|
||||
|
||||
@@ -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 |
|
||||
+44
-22
@@ -1,6 +1,6 @@
|
||||
# Service Catalog
|
||||
|
||||
Stand: 2026-05-04
|
||||
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.
|
||||
|
||||
@@ -11,17 +11,17 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und
|
||||
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `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 `8082` | `dns_net`, `frontend_net`, Unbound | `/mnt/user/appdata/adguard/conf`, `/mnt/user/appdata/adguard/work` | Tier 1, config relevant | nein | Direkte Ports 53 und 8082 dokumentierte Ausnahme; Admin-Port spaeter ggf. absichern |
|
||||
| `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` | `/mnt/user/services/gitea/data` | Tier 1, SQLite in `/data` | ja | SSH-Port 222 direkte Host-Port-Ausnahme; ohne externen Mirror im DR kritisch |
|
||||
| `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 | ja | `ADMIN_TOKEN_FILE`; keine direkten Ports |
|
||||
| `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
|
||||
|
||||
@@ -44,32 +44,46 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und
|
||||
| `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, Share + app-eigene DB | 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` | Teil von Nextcloud-Restore | nein | interne DB |
|
||||
| `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 |
|
||||
| `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`; Monitoring/Borg-Benachrichtigungen |
|
||||
| `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 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `homepage` | Start-Dashboard | `apps/homepage/docker-compose.yml` | `https://home.kaleschke.info` | Traefik, Docker socket read-only, viele API Tokens | `/mnt/user/appdata/homepage`, `/mnt/user/appdata/homepage/images` | Tier 2 | ja + Authelia laut Compose | Authelia schuetzt die Domain ueber die 1FA-Wildcard-Regel |
|
||||
| `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 | ja + Authelia | breite Mounts bewusst; `/local/secrets` im DR-Scope |
|
||||
| `backrest` | Backup-Admin-Dienst / Legacy-Backup-Ebene | `ops/backrest/docker-compose.yml` | `https://backrest.kaleschke.info` | Traefik + Authelia, Repo/SSH-Mounts | `/mnt/user/appdata/backrest/*`, broad source mounts | Tier 3 | ja + Authelia | breite Mounts bewusst dokumentieren |
|
||||
| `uptime-kuma` | Monitoring / Uptime Checks | `ops/uptime-kuma/docker-compose.yml` | `https://uptime.kaleschke.info` | Traefik + Authelia | `/mnt/user/appdata/uptime-kuma` | Tier 3 | ja + Authelia | Monitore nach Restore pruefen |
|
||||
| `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 | Rootfs und Docker-Socket Mounts |
|
||||
| `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 | `privileged: true` dokumentierte Ausnahme |
|
||||
| `speedtest-tracker` | Speedtest-Monitoring | `ops/speedtest/docker-compose.yml` | `https://speedtest.kaleschke.info` | Traefik + Authelia | `/mnt/user/appdata/speedtest-tracker/config` | Tier 3 | ja + Authelia | `APP_KEY`, `ADMIN_PASSWORD` Stack ENV |
|
||||
| `filebrowser` | Datei-Browser fuer Appdata | `ops/filebrowser/docker-compose.yml` | `https://files.kaleschke.info` | Traefik + Authelia | `/mnt/user/appdata/filebrowser/*`, breiter `/mnt/user/appdata` Mount | Tier 3 | ja + Authelia | Mounts langfristig einschraenken |
|
||||
| `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 | `PASSWORD_FILE`; Workspaces beachten |
|
||||
| `grafana` | Metrik-Dashboard | `ops/grafana-influxdb/docker-compose.yml` | `https://grafana.kaleschke.info` | Traefik + Authelia, InfluxDB 3 Core | `/mnt/user/appdata/grafana`, Grafana provisioning | Tier 3 | ja + Authelia | Datasource wird provisioniert, Token ueber Secret; laeuft aktuell als `user: "0"` wegen Host-Appdata-Permissions |
|
||||
| `influxdb3-core` | Zeitreihen-/Metrikdaten fuer Grafana und Home Assistant | `ops/grafana-influxdb/docker-compose.yml` | LAN `8181` je `INFLUXDB_BIND_IP`, keine Public URL | Grafana, Home Assistant Writer | `/mnt/user/appdata/influxdb3/data`, `/mnt/user/appdata/influxdb3/plugins` | Tier 3 | nein | LAN-only Host-Port-Ausnahme; `401 Unauthorized` beim Curl ohne Token ist erwarteter Reachability-Test; laeuft aktuell als `user: "0"` wegen Host-Appdata-Permissions |
|
||||
| `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 | kein Docker-Socket; terminal backend `ssh` auf dedizierte VM (192.168.178.143, User `hermes`, Repo unter `/srv/hermes-workspace/homelab-infra/`); Ops-Monitor-Skill `homelab-ops-monitor` aktiv; echte `.env` auf Host-Appdata |
|
||||
| `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`; bindet intern mit `--insecure` auf `0.0.0.0`, externe Absicherung bleibt Authelia |
|
||||
| `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
|
||||
|
||||
@@ -79,9 +93,17 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und
|
||||
- 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- und Backrest-Mounts sind breit und bewusst, aber bei zukuenftigen Hardening-Sprints Kandidaten.
|
||||
- Filebrowser-Mounts sind breit und bewusst, aber bei zukuenftigen Hardening-Sprints Kandidaten.
|
||||
- Scrutiny bleibt privilegiert; nur mit klarer Begruendung aendern.
|
||||
- BentoPDF kann je nach Live-Stand vorbereitet statt produktiv sein. Hermes Dashboard ist produktiv unter `hermes.kaleschke.info`, aber noch nicht vollstaendig in Restore Matrix / DR Bootstrap eingeordnet.
|
||||
- BentoPDF ersetzt Stirling-PDF produktiv. Hermes ist VM-seitig offen und auf dem NAS bewusst nicht gestartet.
|
||||
|
||||
@@ -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.
|
||||
+20
-18
@@ -115,6 +115,23 @@ Komodo ist in diesem Setup:
|
||||
- Pushes koennen automatisch einen Komodo-Deploy ausloesen
|
||||
- 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
|
||||
|
||||
Komodo bleibt **bewusst** ohne zentrale Traefik-ForwardAuth-Middleware.
|
||||
@@ -301,29 +318,14 @@ Nach jeder erfolgreichen Migration oder relevanten Aenderung muessen diese Datei
|
||||
- `docs/MIGRATION_LOG.md`
|
||||
- `docs/SECRETS_MAP.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
|
||||
- `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.
|
||||
|
||||
---
|
||||
|
||||
## Rollback-Regel
|
||||
|
||||
Jede Aenderung muss rueckrollbar sein. Vor jedem Deploy muss klar sein:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
BASE_DOMAIN=kaleschke.info
|
||||
TRAEFIK_DOMAIN=traefik.kaleschke.info
|
||||
AUTH_DOMAIN=auth.kaleschke.info
|
||||
HOME_DOMAIN=home.kaleschke.info
|
||||
GLANCE_DOMAIN=glance.kaleschke.info
|
||||
|
||||
@@ -13,7 +13,7 @@ services:
|
||||
ports:
|
||||
- "53:53/tcp"
|
||||
- "53:53/udp"
|
||||
- "8082:80"
|
||||
- "100.80.98.33:8082:80"
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -3,6 +3,8 @@ services:
|
||||
image: ghcr.io/qdm12/ddns-updater:latest@sha256:ee16ab4f6203bf9e5b0925d38a0b4ebf2d9f23771f933cfb2f5a2dbd5f9a2f88
|
||||
container_name: ddns-updater
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
networks:
|
||||
- frontend_net
|
||||
environment:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
image: redis:7.4-alpine@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99
|
||||
container_name: Redis
|
||||
restart: unless-stopped
|
||||
command:
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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."
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -57,7 +57,6 @@ Der technische Scope für `critical_infra` ist in `all-important-sources.txt` fe
|
||||
- `/local/secrets`
|
||||
- `/local/appdata/authelia/config`
|
||||
- `/local/appdata/traefik`
|
||||
- `/local/appdata/homepage`
|
||||
- `/local/appdata/ntfy`
|
||||
- `/local/appdata/paperless-gpt`
|
||||
- `/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 |
|
||||
| Authelia | Config + Secrets | Shared PostgreSQL Dump vorgesehen / erzeugt | 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 |
|
||||
| Paperless-GPT | lokale Daten / Prompts | 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:
|
||||
- `postgresql17-globals.sql` 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.
|
||||
|
||||
## Was aktuell bewusst nicht als Problem gewertet wird
|
||||
|
||||
+23
-24
@@ -1,6 +1,6 @@
|
||||
# Borg Backup Scope for KalliLabcore
|
||||
|
||||
Stand: 2026-05-04
|
||||
Stand: 2026-05-16
|
||||
|
||||
This file defines the target state for replacing Backrest with Borg in this homelab.
|
||||
|
||||
@@ -11,66 +11,62 @@ Use Borg as the single backup system for:
|
||||
- critical file-backed application data
|
||||
- secrets, keys, and reverse-proxy state
|
||||
- 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.
|
||||
|
||||
## 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.
|
||||
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 Unraid flash configuration archive is intentional as well and must be treated as secret backup material.
|
||||
|
||||
## Service Inventory
|
||||
|
||||
| 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` |
|
||||
| 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` |
|
||||
| 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` |
|
||||
| Traefik | file data | `/local/appdata/traefik` |
|
||||
| Homepage | file data | `/local/appdata/homepage` |
|
||||
| ntfy | file data | `/local/appdata/ntfy` |
|
||||
| Paperless-GPT | file data | `/local/appdata/paperless-gpt` |
|
||||
| Tailscale | file data | `/local/appdata/tailscale` |
|
||||
| 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 + Mongo dump | `/local/borg-dumps`, `/local/appdata/komodo/periphery`, `/local/appdata/komodo/core` |
|
||||
| Nextcloud | raw DB path + file data | `/local/appdata/nextcloud/html`, `/local/appdata/nextcloud/postgres`, `/local/appdata/nextcloud/redis`; user data path see gap below |
|
||||
| Grafana | file data | `/local/appdata/grafana` |
|
||||
| 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` |
|
||||
| Backrest | file data | `/local/appdata/backrest/data`, `/local/appdata/backrest/config` |
|
||||
| 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 database
|
||||
### Nextcloud
|
||||
|
||||
Recovery currently relies on the raw live DB path `/local/appdata/nextcloud/postgres`. This is inconsistent with the policy "Do not back up raw live database storage directories as the primary recovery artifact" stated below.
|
||||
|
||||
Open decision:
|
||||
|
||||
- Option A: extend `ops/borg-ui/scripts/pre-backup-dumps.sh` with a `nextcloud-postgres` dump and treat the raw path as transient.
|
||||
- Option B: accept the raw path as a documented Nextcloud-specific exception.
|
||||
|
||||
Until decided, the raw path is what Borg sees today and is the only Nextcloud DB recovery surface.
|
||||
|
||||
### Nextcloud user data path is outside the borg-ui mount set
|
||||
|
||||
`/mnt/user/documents/nextcloud-data` is not mounted into `borg-ui` in `ops/borg-ui/docker-compose.yml`. Nextcloud user files are therefore not in the current Borg scope. Resolution requires a separate Compose change (add a read-only mount) and is not silently fixed in this scope document.
|
||||
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
|
||||
|
||||
### Shared PostgreSQL (`postgresql17`)
|
||||
@@ -83,16 +79,21 @@ Until decided, the raw path is what Borg sees today and is the only Nextcloud DB
|
||||
|
||||
- `mealie`
|
||||
- `immich`
|
||||
- `nextcloud`
|
||||
|
||||
### Other Databases
|
||||
|
||||
- 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
|
||||
|
||||
- `/mnt/user/appdata/postgresql17`
|
||||
- `/mnt/user/appdata/mealie/postgres`
|
||||
- `/mnt/user/appdata/immich_postgres`
|
||||
- `/mnt/user/appdata/nextcloud/postgres`
|
||||
- `/mnt/user/appdata/komodo/mongo`
|
||||
- `/mnt/user/appdata/redis`
|
||||
- `/mnt/user/appdata/scrutiny/influxdb`
|
||||
@@ -104,10 +105,8 @@ These are not part of the first-class Borg scope:
|
||||
- Plex metadata and cache
|
||||
- AdGuard query log
|
||||
- code-server extensions cache
|
||||
- uptime-kuma
|
||||
- scrutiny metrics history
|
||||
- dozzle, glances, speedtest
|
||||
- filebrowser app state
|
||||
|
||||
## Suggested Retention
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
/local/secrets
|
||||
/local/appdata/authelia/config
|
||||
/local/appdata/traefik
|
||||
/local/appdata/homepage
|
||||
/local/appdata/ntfy
|
||||
/local/appdata/paperless-gpt
|
||||
/local/appdata/tailscale
|
||||
@@ -20,3 +19,6 @@
|
||||
/local/appdata/borg-ui/data
|
||||
/local/appdata/komodo/periphery
|
||||
/local/appdata/komodo/core
|
||||
/local/services/homelab-infra
|
||||
/local/services/stacks
|
||||
/local/services/posture-check
|
||||
|
||||
@@ -20,8 +20,10 @@ services:
|
||||
- /mnt/user/documents/scans_inbox:/local/paperless/consume:ro
|
||||
- /mnt/user/documents/paperless:/local/paperless/media: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/family_archive:/local/immich/external:ro
|
||||
- /mnt/user/services:/local/services:ro
|
||||
- /mnt/user/services/gitea/data:/local/gitea/data:ro
|
||||
- /mnt/user/appdata/borg-ui/restore:/restore
|
||||
dns:
|
||||
|
||||
@@ -14,6 +14,10 @@ Fresh dump artifacts are written to:
|
||||
|
||||
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
|
||||
|
||||
- The script is written for host execution where `docker` is available.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 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
|
||||
|
||||
The pre-backup dump refresh should run:
|
||||
The Borg pre-flight should run:
|
||||
|
||||
- on the Unraid host
|
||||
- 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
|
||||
|
||||
`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 host paths under `/mnt/user/...`
|
||||
@@ -24,24 +30,36 @@ That makes host execution simpler, more transparent, and lower-risk than giving
|
||||
|
||||
## Recommended rollout
|
||||
|
||||
1. Store the script on the host, for example at:
|
||||
- `/mnt/user/appdata/borg-ui/scripts/pre-backup-dumps.sh`
|
||||
2. Make it executable:
|
||||
- `chmod +x /mnt/user/appdata/borg-ui/scripts/pre-backup-dumps.sh`
|
||||
3. Create a User Scripts entry such as:
|
||||
1. Use the repo clone on the host:
|
||||
- `/mnt/user/services/homelab-infra`
|
||||
2. Make the scripts executable:
|
||||
- `chmod +x /mnt/user/services/homelab-infra/ops/borg-ui/scripts/pre-borg.sh`
|
||||
- `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`
|
||||
4. Let that entry run:
|
||||
- on a fixed schedule before the expected Borg backup window
|
||||
- or manually before ad hoc Borg runs
|
||||
5. Keep Borg UI focused on backing up `/local/borg-dumps`, not on generating the dumps itself.
|
||||
4. Script body:
|
||||
|
||||
```bash
|
||||
#!/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
|
||||
|
||||
The intended sequence is:
|
||||
|
||||
1. Host script refreshes `latest` dump artifacts.
|
||||
2. Borg UI backs up `/local/borg-dumps` together with the rest of `critical_infra`.
|
||||
3. Borg history preserves dump history, so the host only needs to keep the most recent dump set.
|
||||
1. Host wrapper checks posture.
|
||||
2. Host script refreshes `latest` dump artifacts.
|
||||
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
|
||||
|
||||
|
||||
Regular → Executable
+163
@@ -68,6 +68,143 @@ dump_pg_globals() {
|
||||
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() {
|
||||
container="$1"
|
||||
password="$2"
|
||||
@@ -131,6 +268,9 @@ dump_mongo_container() {
|
||||
|
||||
main() {
|
||||
need_cmd docker
|
||||
need_cmd sqlite3
|
||||
need_cmd tar
|
||||
need_cmd sha256sum
|
||||
ensure_dirs
|
||||
|
||||
# Shared PostgreSQL 17
|
||||
@@ -162,9 +302,32 @@ main() {
|
||||
warn "Skipping missing container: immich_postgres"
|
||||
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
|
||||
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"
|
||||
}
|
||||
|
||||
|
||||
Executable
+62
@@ -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"
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
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
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
@@ -10,7 +10,7 @@ services:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Europe/Berlin
|
||||
- PASSWORD_FILE=/run/secrets/password
|
||||
- FILE__PASSWORD=/run/secrets/password
|
||||
- DEFAULT_WORKSPACE=/workspace
|
||||
- PWA_APPNAME=KalliLab Code
|
||||
|
||||
@@ -18,7 +18,6 @@ services:
|
||||
volumes:
|
||||
- /mnt/user/appdata/code-server:/config
|
||||
- /mnt/user/services/dev:/workspace
|
||||
- /mnt/user/appdata/homepage:/prod/homepage
|
||||
- /mnt/user/appdata/code-server/secrets/password:/run/secrets/password:ro
|
||||
|
||||
networks:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
filebrowser:
|
||||
image: filebrowser/filebrowser:latest@sha256:4dce87308b9f9cfbcf8d0a284fc9565d2b515530a6bae2d920b388161e093f26
|
||||
image: filebrowser/filebrowser:v2.63.2@sha256:4dce87308b9f9cfbcf8d0a284fc9565d2b515530a6bae2d920b388161e093f26
|
||||
container_name: filebrowser
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
@@ -9,7 +9,9 @@ services:
|
||||
- PUID=99
|
||||
- PGID=100
|
||||
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/config:/config
|
||||
command: ["--database", "/database/filebrowser.db"]
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,81 +0,0 @@
|
||||
# Grafana + InfluxDB 3 Core
|
||||
|
||||
Monitoring-Stack fuer Grafana + InfluxDB 3 Core. InfluxDB bleibt ohne Public Route; interne Writer wie Home Assistant koennen ueber einen gezielt gebundenen LAN-Port schreiben.
|
||||
|
||||
## 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 Traefik-Route. Der HTTP-Port `8181` kann fuer interne Writer wie Home Assistant ueber `INFLUXDB_BIND_IP` auf eine LAN-Adresse gebunden werden; Default ist `127.0.0.1`.
|
||||
- InfluxDB haengt an zwei Compose-Netzen: `grafana_influx_internal` fuer Grafana und `grafana_influx_lan` fuer das Docker Host-Port-Publishing. Im laufenden Komodo-Stack heissen sie durch den Compose-Projektpraefix `grafana_grafana_influx_internal` und `grafana_grafana_influx_lan`. InfluxDB haengt bewusst nicht im `frontend_net`.
|
||||
- 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.
|
||||
- Home Assistant schreibt mit der InfluxDB-v2-API-Kompatibilitaet nach InfluxDB 3; Details: `docs/HOME_ASSISTANT_INFLUXDB_ECOWITT.md`.
|
||||
|
||||
## Initiale Einrichtung
|
||||
|
||||
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 InfluxDB 3 Core aktuell einen eigenen Named-Admin-Token verwenden, damit der Grafana-Zugang getrennt vom initialen Operator-/Admin-Token rotiert werden kann:
|
||||
|
||||
```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 ohne Public Route. Falls `INFLUXDB_BIND_IP` auf die LAN-IP gesetzt ist, ist Port `8181` nur im internen Netz fuer Writer wie Home Assistant erreichbar.
|
||||
- `docker ps` zeigt fuer `influxdb3-core` `192.168.178.58:8181->8181/tcp` oder den per `INFLUXDB_BIND_IP` gesetzten Host.
|
||||
- `ss -ltnp | grep 8181` zeigt einen Listener auf der gebundenen Host-IP.
|
||||
- `curl -i http://192.168.178.58:8181/` liefert ohne Token erwartbar `401 Unauthorized`.
|
||||
|
||||
## Drift-Check
|
||||
|
||||
Wenn Komodo, Gitea und Runtime nicht zusammenpassen, zuerst `docs/GITOPS_DRIFT_RUNBOOK.md` verwenden. Besonders wichtig:
|
||||
|
||||
```bash
|
||||
cd /mnt/user/services/stacks/grafana
|
||||
git rev-parse --short HEAD
|
||||
grep -nE "ports:|grafana_influx_lan|grafana_influx_internal" -A4 -B2 ops/grafana-influxdb/docker-compose.yml
|
||||
docker inspect influxdb3-core --format '{{json .NetworkSettings.Ports}}'
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep influx
|
||||
ss -ltnp | grep 8181
|
||||
```
|
||||
|
||||
## 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.
|
||||
@@ -1,85 +0,0 @@
|
||||
services:
|
||||
grafana:
|
||||
image: grafana/grafana:12.4.3@sha256:2e986801428cd689c2358605289c90ab37d2b39e24808874971f54c99bcdc412
|
||||
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@sha256:1d58c8b9ac90153ae3a020ede2810c8284933dda50ac71e7573389ab6f012128
|
||||
container_name: influxdb3-core
|
||||
restart: unless-stopped
|
||||
user: "0"
|
||||
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:
|
||||
- grafana_influx_lan
|
||||
- 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_lan:
|
||||
driver: bridge
|
||||
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
|
||||
@@ -315,48 +315,41 @@
|
||||
"first_check": "HTTPS erreichbar? NTFY_BEHIND_PROXY=true gesetzt? Traefik healthy?",
|
||||
"notes": "KRITISCH: Ausfall bedeutet keine anderen Alerts ankommen"
|
||||
},
|
||||
"homepage": {
|
||||
"description": "Start-Dashboard",
|
||||
"glance": {
|
||||
"description": "Homelab-Dashboard",
|
||||
"tier": 3,
|
||||
"category": "ops",
|
||||
"container_name": "homepage",
|
||||
"container_name": "glance",
|
||||
"dependencies": ["traefik"],
|
||||
"url": "https://home.kaleschke.info",
|
||||
"url": "https://glance.kaleschke.info",
|
||||
"dump_file": null,
|
||||
"data_paths": ["/mnt/user/appdata/homepage"],
|
||||
"first_check": "Traefik erreichbar? Docker-Socket read-only lesbar? API-Tokens gueltig?",
|
||||
"notes": "Docker socket read-only; viele API Tokens in Config"
|
||||
"data_paths": [],
|
||||
"first_check": "Traefik erreichbar? Docker-Socket-Proxy intern erreichbar? API-Tokens fuer Widgets gueltig?",
|
||||
"notes": "aktives Homelab-Dashboard; Homepage wurde entfernt"
|
||||
},
|
||||
"uptime-kuma": {
|
||||
"description": "Monitoring / Uptime Checks",
|
||||
"monitoring-grafana": {
|
||||
"description": "Zentrale Observability-UI",
|
||||
"tier": 3,
|
||||
"category": "ops",
|
||||
"container_name": "UptimeKuma",
|
||||
"dependencies": ["traefik"],
|
||||
"url": "https://uptime.kaleschke.info",
|
||||
"container_name": "monitoring-grafana",
|
||||
"dependencies": [
|
||||
"monitoring-prometheus",
|
||||
"monitoring-loki",
|
||||
"monitoring-influxdb3-core",
|
||||
"traefik"
|
||||
],
|
||||
"url": "https://monitoring.kaleschke.info",
|
||||
"dump_file": null,
|
||||
"data_paths": ["/mnt/user/appdata/uptime-kuma"],
|
||||
"first_check": "Datenbank-Volume intakt? Traefik erreichbar?",
|
||||
"notes": "Monitore nach Restore manuell pruefen"
|
||||
"data_paths": ["grafana_data"],
|
||||
"first_check": "Authelia-Redirect? Datasources Prometheus, Loki und InfluxDB 3 Core gruen?",
|
||||
"notes": "ersetzt alten Grafana-Altstand und Uptime-Kuma-Views"
|
||||
},
|
||||
"grafana": {
|
||||
"description": "Metrik-Dashboard",
|
||||
"monitoring-influxdb3-core": {
|
||||
"description": "Zeitreihen- / Metrikdaten fuer Monitoring und Home Assistant",
|
||||
"tier": 3,
|
||||
"category": "ops",
|
||||
"container_name": "grafana",
|
||||
"dependencies": ["influxdb3-core", "traefik"],
|
||||
"url": "https://grafana.kaleschke.info",
|
||||
"dump_file": null,
|
||||
"data_paths": ["/mnt/user/appdata/grafana"],
|
||||
"first_check": "influxdb3-core healthy? Datasource-Token gesetzt? Provisioning-Konfig vorhanden?",
|
||||
"notes": "laeuft als user 0; Datasource wird provisioniert"
|
||||
},
|
||||
"influxdb3-core": {
|
||||
"description": "Zeitreihen- / Metrikdaten fuer Grafana und Home Assistant",
|
||||
"tier": 3,
|
||||
"category": "ops",
|
||||
"container_name": "influxdb3-core",
|
||||
"dependencies": [],
|
||||
"container_name": "monitoring-influxdb3-core",
|
||||
"dependencies": ["monitoring-grafana"],
|
||||
"url": null,
|
||||
"dump_file": null,
|
||||
"data_paths": [
|
||||
@@ -408,18 +401,6 @@
|
||||
"first_check": "Borg-Repo-Credentials vorhanden? Backup-Mounts erreichbar? Traefik healthy?",
|
||||
"notes": "breite Mounts bewusst dokumentiert; /local/secrets im DR-Scope"
|
||||
},
|
||||
"backrest": {
|
||||
"description": "Backup-Admin-Dienst",
|
||||
"tier": 3,
|
||||
"category": "ops",
|
||||
"container_name": "backrest",
|
||||
"dependencies": ["traefik"],
|
||||
"url": "https://backrest.kaleschke.info",
|
||||
"dump_file": null,
|
||||
"data_paths": ["/mnt/user/appdata/backrest"],
|
||||
"first_check": "Repo/SSH-Mounts erreichbar? Traefik healthy?",
|
||||
"notes": "breite Mounts bewusst dokumentiert"
|
||||
},
|
||||
"hermes-gateway": {
|
||||
"description": "Hermes Agent Gateway / AI Ops Assistant",
|
||||
"tier": 3,
|
||||
|
||||
@@ -395,55 +395,43 @@ services:
|
||||
# TIER 3 — Ops / Tools (Ausfall schmerzt, blockiert nichts Kritisches)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
homepage:
|
||||
description: Start-Dashboard
|
||||
glance:
|
||||
description: Homelab-Dashboard
|
||||
tier: 3
|
||||
category: ops
|
||||
container_name: homepage
|
||||
container_name: glance
|
||||
dependencies:
|
||||
- traefik
|
||||
url: https://home.kaleschke.info
|
||||
url: https://glance.kaleschke.info
|
||||
dump_file: null
|
||||
data_paths:
|
||||
- /mnt/user/appdata/homepage
|
||||
first_check: "Traefik erreichbar? Docker-Socket read-only lesbar? API-Tokens gueltig?"
|
||||
notes: "Docker socket read-only; viele API Tokens in Config"
|
||||
data_paths: []
|
||||
first_check: "Traefik erreichbar? Docker-Socket-Proxy intern erreichbar? API-Tokens fuer Widgets gueltig?"
|
||||
notes: "aktives Homelab-Dashboard; Homepage wurde entfernt"
|
||||
|
||||
uptime-kuma:
|
||||
description: Monitoring / Uptime Checks
|
||||
monitoring-grafana:
|
||||
description: Zentrale Observability-UI
|
||||
tier: 3
|
||||
category: ops
|
||||
container_name: UptimeKuma
|
||||
container_name: monitoring-grafana
|
||||
dependencies:
|
||||
- monitoring-prometheus
|
||||
- monitoring-loki
|
||||
- monitoring-influxdb3-core
|
||||
- traefik
|
||||
url: https://uptime.kaleschke.info
|
||||
url: https://monitoring.kaleschke.info
|
||||
dump_file: null
|
||||
data_paths:
|
||||
- /mnt/user/appdata/uptime-kuma
|
||||
first_check: "Datenbank-Volume intakt? Traefik erreichbar?"
|
||||
notes: "Monitore nach Restore manuell pruefen"
|
||||
- grafana_data
|
||||
first_check: "Authelia-Redirect? Datasources Prometheus, Loki und InfluxDB 3 Core gruen?"
|
||||
notes: "ersetzt alten Grafana-Altstand und Uptime-Kuma-Views"
|
||||
|
||||
grafana:
|
||||
description: Metrik-Dashboard
|
||||
monitoring-influxdb3-core:
|
||||
description: Zeitreihen- / Metrikdaten fuer Monitoring und Home Assistant
|
||||
tier: 3
|
||||
category: ops
|
||||
container_name: grafana
|
||||
container_name: monitoring-influxdb3-core
|
||||
dependencies:
|
||||
- influxdb3-core
|
||||
- traefik
|
||||
url: https://grafana.kaleschke.info
|
||||
dump_file: null
|
||||
data_paths:
|
||||
- /mnt/user/appdata/grafana
|
||||
first_check: "influxdb3-core healthy? Datasource-Token in Secret gesetzt? Provisioning-Konfig vorhanden?"
|
||||
notes: "laeuft als user 0 wegen Host-Appdata-Permissions (dokumentiert); Datasource wird provisioniert"
|
||||
|
||||
influxdb3-core:
|
||||
description: Zeitreihen- / Metrikdaten fuer Grafana und Home Assistant
|
||||
tier: 3
|
||||
category: ops
|
||||
container_name: influxdb3-core
|
||||
dependencies: []
|
||||
- monitoring-grafana
|
||||
url: null
|
||||
dump_file: null
|
||||
data_paths:
|
||||
@@ -495,20 +483,6 @@ services:
|
||||
first_check: "Borg-Repo-Credentials vorhanden? Backup-Mounts erreichbar? Traefik healthy?"
|
||||
notes: "breite Mounts bewusst dokumentiert; /local/secrets im DR-Scope"
|
||||
|
||||
backrest:
|
||||
description: Backup-Admin-Dienst (Legacy-Backup-Ebene)
|
||||
tier: 3
|
||||
category: ops
|
||||
container_name: backrest
|
||||
dependencies:
|
||||
- traefik
|
||||
url: https://backrest.kaleschke.info
|
||||
dump_file: null
|
||||
data_paths:
|
||||
- /mnt/user/appdata/backrest
|
||||
first_check: "Repo/SSH-Mounts erreichbar? Traefik healthy?"
|
||||
notes: "breite Mounts bewusst dokumentiert"
|
||||
|
||||
hermes-gateway:
|
||||
description: Hermes Agent Gateway / AI Ops Assistant
|
||||
tier: 3
|
||||
|
||||
@@ -32,6 +32,7 @@ powershell -ExecutionPolicy Bypass -File .\ops\policy-checks\check_repo.ps1 -Rep
|
||||
- Host-Port-Mappings
|
||||
- Traefik-Router mit `Host(...)` und Middleware-Standard fuer geschuetzte Admin-/Ops-Dienste
|
||||
- sichtbare Report-Punkte fuer dokumentierte Ausnahmen wie `user: "0"`, `privileged: true` oder `network_mode: host`
|
||||
- digest-gepinnte mutable Tags bleiben sichtbar; nur explizit dokumentierte Ausnahmen werden als Info statt Warning gewertet
|
||||
|
||||
## Wichtige Betriebsregel
|
||||
|
||||
|
||||
@@ -252,6 +252,14 @@ function Test-ServicePolicies {
|
||||
}
|
||||
}
|
||||
|
||||
if ($service.Image -match ':[Ll]atest(?:[-@]|$)') {
|
||||
if (($service.Image -match '@sha256:') -and (Test-IdentityMatch -Service $service -Candidates $Exceptions.allowed_mutable_tag_identities)) {
|
||||
Add-Finding -Findings $Findings -Severity 'info' -Code 'IMAGE002' -Target $targetBase -Message 'Image uses a latest tag but is digest-pinned and documented as an exception.'
|
||||
} else {
|
||||
Add-Finding -Findings $Findings -Severity 'warning' -Code 'IMAGE001' -Target $targetBase -Message 'Image uses a latest tag. Prefer a concrete version tag, even when a digest is present.'
|
||||
}
|
||||
}
|
||||
|
||||
$isDataService = $false
|
||||
$identityText = ($service.ServiceName + ' ' + $service.ContainerName + ' ' + $service.Image).ToLowerInvariant()
|
||||
foreach ($needle in @('postgres', 'redis', 'mongo', 'mysql', 'mariadb', 'influxdb')) {
|
||||
@@ -358,6 +366,7 @@ $exceptionsRaw = Get-Content -LiteralPath $exceptionsPath -Raw | ConvertFrom-Jso
|
||||
$exceptions = @{
|
||||
middleware_exempt_identities = @($exceptionsRaw.middleware_exempt_identities)
|
||||
allowed_root_identities = @($exceptionsRaw.allowed_root_identities)
|
||||
allowed_mutable_tag_identities = @($exceptionsRaw.allowed_mutable_tag_identities)
|
||||
allowed_privileged_identities = @($exceptionsRaw.allowed_privileged_identities)
|
||||
allowed_host_network_identities = @($exceptionsRaw.allowed_host_network_identities)
|
||||
allowed_host_port_identities = @{}
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
"adguard": [
|
||||
"53:53/tcp",
|
||||
"53:53/udp",
|
||||
"8082:80"
|
||||
"100.80.98.33:8082:80"
|
||||
],
|
||||
"gitea": [
|
||||
"222:22"
|
||||
],
|
||||
"influxdb3-core": [
|
||||
"monitoring-influxdb3-core": [
|
||||
"${INFLUXDB_BIND_IP:-127.0.0.1}:8181:8181"
|
||||
],
|
||||
"traefik": [
|
||||
@@ -30,13 +30,18 @@
|
||||
]
|
||||
},
|
||||
"allowed_root_identities": [
|
||||
"grafana",
|
||||
"influxdb3-core"
|
||||
"monitoring-influxdb3-core"
|
||||
],
|
||||
"allowed_mutable_tag_identities": [
|
||||
"ddns-updater",
|
||||
"glances",
|
||||
"scrutiny"
|
||||
],
|
||||
"allowed_privileged_identities": [
|
||||
"scrutiny"
|
||||
],
|
||||
"allowed_host_network_identities": [
|
||||
"plex",
|
||||
"tailscale",
|
||||
"Tailscale-Docker"
|
||||
]
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
# Policy Check Report
|
||||
|
||||
## Summary
|
||||
- Compose files checked: 30
|
||||
- Compose files checked: 29
|
||||
- Critical findings: 0
|
||||
- Warnings: 5
|
||||
- Info findings: 9
|
||||
- Warnings: 1
|
||||
- Info findings: 13
|
||||
|
||||
## Critical
|
||||
- none
|
||||
|
||||
## Warnings
|
||||
- [SEC001] infra\ddns-updater\docker-compose.yml :: ddns-updater: Missing security_opt no-new-privileges:true.
|
||||
- [SEC001] ops\backrest\docker-compose.yml :: backrest: Missing security_opt no-new-privileges:true.
|
||||
- [USER001] ops\grafana-influxdb\docker-compose.yml :: grafana: Runs as user 0. Documented exception, keep visible for hardening.
|
||||
- [USER001] ops\grafana-influxdb\docker-compose.yml :: influxdb3-core: Runs as user 0. Documented exception, keep visible for hardening.
|
||||
- [SEC001] ops\scrutiny\docker-compose.yml :: scrutiny: Missing security_opt no-new-privileges:true.
|
||||
- [USER001] monitoring\docker-compose.yml :: influxdb3-core: Runs as user 0. Documented exception, keep visible for hardening.
|
||||
|
||||
## Info
|
||||
- [PORT001] core\gitea\docker-compose.yml :: gitea: Allowed host port mapping: 222:22
|
||||
- [PORT001] host-services\Adguard\docker-compose.yml :: adguard: Allowed host port mapping: 53:53/tcp
|
||||
- [PORT001] host-services\Adguard\docker-compose.yml :: adguard: Allowed host port mapping: 53:53/udp
|
||||
- [PORT001] host-services\Adguard\docker-compose.yml :: adguard: Allowed host port mapping: 8082:80
|
||||
- [PORT001] host-services\Adguard\docker-compose.yml :: adguard: Allowed host port mapping: 100.80.98.33:8082:80
|
||||
- [HOSTNET001] host-services\plex\docker-compose.yml :: plex: network_mode: host is a documented exception.
|
||||
- [HOSTNET001] host-services\tailscale\docker-compose.yml :: tailscale: network_mode: host is a documented exception.
|
||||
- [PORT001] ops\grafana-influxdb\docker-compose.yml :: influxdb3-core: Allowed host port mapping: ${INFLUXDB_BIND_IP:-127.0.0.1}:8181:8181
|
||||
- [IMAGE002] infra\ddns-updater\docker-compose.yml :: ddns-updater: Image uses a latest tag but is digest-pinned and documented as an exception.
|
||||
- [PORT001] monitoring\docker-compose.yml :: influxdb3-core: Allowed host port mapping: ${INFLUXDB_BIND_IP:-127.0.0.1}:8181:8181
|
||||
- [IMAGE002] ops\glances\docker-compose.yml :: glances: Image uses a latest tag but is digest-pinned and documented as an exception.
|
||||
- [IMAGE002] ops\scrutiny\docker-compose.yml :: scrutiny: Image uses a latest tag but is digest-pinned and documented as an exception.
|
||||
- [PRIV001] ops\scrutiny\docker-compose.yml :: scrutiny: Privileged mode is a documented exception.
|
||||
- [PORT001] traefik\docker-compose.yml :: traefik: Allowed host port mapping: 80:80
|
||||
- [PORT001] traefik\docker-compose.yml :: traefik: Allowed host port mapping: 443:443
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
param(
|
||||
[string]$DumpRoot = "/mnt/user/backups/borg/dumps/latest",
|
||||
[string]$ReportRoot = "/mnt/user/backups/restore-reports",
|
||||
[int]$MaxDumpAgeHours = 36,
|
||||
[int]$MaxDumpAgeHours = 26,
|
||||
[int]$MaxReportAgeDays = 45
|
||||
)
|
||||
|
||||
@@ -9,7 +9,13 @@ $checks = @(
|
||||
@{ Name = "postgresql17-paperless.dump"; Path = Join-Path $DumpRoot "postgresql17-paperless.dump" },
|
||||
@{ Name = "postgresql17-mailarchiver.dump"; Path = Join-Path $DumpRoot "postgresql17-mailarchiver.dump" },
|
||||
@{ Name = "mealie.dump"; Path = Join-Path $DumpRoot "mealie.dump" },
|
||||
@{ Name = "immich.dump"; Path = Join-Path $DumpRoot "immich.dump" }
|
||||
@{ Name = "immich.dump"; Path = Join-Path $DumpRoot "immich.dump" },
|
||||
@{ Name = "nextcloud.dump"; Path = Join-Path $DumpRoot "nextcloud.dump" },
|
||||
@{ Name = "gitea.sqlite.dump"; Path = Join-Path $DumpRoot "gitea.sqlite.dump" },
|
||||
@{ Name = "vaultwarden.sqlite.dump"; Path = Join-Path $DumpRoot "vaultwarden.sqlite.dump" },
|
||||
@{ Name = "speedtest-tracker.sqlite.dump"; Path = Join-Path $DumpRoot "speedtest-tracker.sqlite.dump" },
|
||||
@{ Name = "filebrowser.bolt.dump"; Path = Join-Path $DumpRoot "filebrowser.bolt.dump" },
|
||||
@{ Name = "unraid-flash-config.tar.gz"; Path = Join-Path $DumpRoot "unraid-flash-config.tar.gz" }
|
||||
)
|
||||
|
||||
$reportChecks = @(
|
||||
@@ -30,15 +36,25 @@ foreach ($check in $checks) {
|
||||
}
|
||||
|
||||
$item = Get-Item $check.Path
|
||||
if ($item.Length -le 0) {
|
||||
$critical.Add("DUMP_EMPTY $($check.Name)")
|
||||
continue
|
||||
}
|
||||
|
||||
$ageHours = ($now - $item.LastWriteTime).TotalHours
|
||||
if ($ageHours -gt $MaxDumpAgeHours) {
|
||||
$warnings.Add(("DUMP_STALE {0} age={1:N1}h" -f $check.Name, $ageHours))
|
||||
$critical.Add(("DUMP_STALE {0} age={1:N1}h" -f $check.Name, $ageHours))
|
||||
} else {
|
||||
$info.Add(("DUMP_OK {0} age={1:N1}h" -f $check.Name, $ageHours))
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($check in $reportChecks) {
|
||||
if (-not (Test-Path $ReportRoot)) {
|
||||
$warnings.Add("REPORT_ROOT_MISSING $ReportRoot")
|
||||
break
|
||||
}
|
||||
|
||||
$latest = Get-ChildItem -Path $ReportRoot -Filter ([System.IO.Path]::GetFileName($check.Path)) -ErrorAction SilentlyContinue |
|
||||
Sort-Object LastWriteTime -Descending |
|
||||
Select-Object -First 1
|
||||
|
||||
Regular → Executable
+22
-3
@@ -3,7 +3,7 @@ set -euo pipefail
|
||||
|
||||
DUMP_ROOT="${DUMP_ROOT:-/mnt/user/backups/borg/dumps/latest}"
|
||||
REPORT_ROOT="${REPORT_ROOT:-/mnt/user/backups/restore-reports}"
|
||||
MAX_DUMP_AGE_HOURS="${MAX_DUMP_AGE_HOURS:-36}"
|
||||
MAX_DUMP_AGE_HOURS="${MAX_DUMP_AGE_HOURS:-26}"
|
||||
MAX_REPORT_AGE_DAYS="${MAX_REPORT_AGE_DAYS:-45}"
|
||||
|
||||
now_epoch="$(date +%s)"
|
||||
@@ -25,21 +25,40 @@ check_file_age_days() {
|
||||
echo $(( (now_epoch - mtime) / 86400 ))
|
||||
}
|
||||
|
||||
for dump in postgresql17-paperless.dump postgresql17-mailarchiver.dump mealie.dump immich.dump; do
|
||||
for dump in \
|
||||
postgresql17-paperless.dump \
|
||||
postgresql17-mailarchiver.dump \
|
||||
mealie.dump \
|
||||
immich.dump \
|
||||
nextcloud.dump \
|
||||
gitea.sqlite.dump \
|
||||
vaultwarden.sqlite.dump \
|
||||
speedtest-tracker.sqlite.dump \
|
||||
filebrowser.bolt.dump \
|
||||
unraid-flash-config.tar.gz; do
|
||||
path="$DUMP_ROOT/$dump"
|
||||
if [ ! -f "$path" ]; then
|
||||
critical+=("DUMP_MISSING $dump")
|
||||
continue
|
||||
fi
|
||||
if [ ! -s "$path" ]; then
|
||||
critical+=("DUMP_EMPTY $dump")
|
||||
continue
|
||||
fi
|
||||
age="$(check_file_age_hours "$path")"
|
||||
if [ "$age" -gt "$MAX_DUMP_AGE_HOURS" ]; then
|
||||
warnings+=("DUMP_STALE $dump age=${age}h")
|
||||
critical+=("DUMP_STALE $dump age=${age}h")
|
||||
else
|
||||
info+=("DUMP_OK $dump age=${age}h")
|
||||
fi
|
||||
done
|
||||
|
||||
for service in vaultwarden gitea paperless; do
|
||||
if [ ! -d "$REPORT_ROOT" ]; then
|
||||
warnings+=("REPORT_ROOT_MISSING $REPORT_ROOT")
|
||||
break
|
||||
fi
|
||||
|
||||
latest="$(find "$REPORT_ROOT" -maxdepth 1 -type f -name "$service-*.md" | sort | tail -n 1 || true)"
|
||||
if [ -z "$latest" ]; then
|
||||
warnings+=("REPORT_MISSING $service")
|
||||
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TOPIC="${TOPIC:-homelab-info}"
|
||||
TESTS="${TESTS:-vaultwarden gitea paperless}"
|
||||
|
||||
pick_random() {
|
||||
printf '%s\n' $TESTS | awk 'BEGIN { srand() } { items[++count] = $0 } END { print items[int(rand() * count) + 1] }'
|
||||
}
|
||||
|
||||
selected="$(pick_random)"
|
||||
echo "Selected monthly restore test: $selected"
|
||||
|
||||
exec "$SCRIPT_DIR/run-restore-job-with-ntfy.sh" "$selected" "$TOPIC"
|
||||
@@ -3,10 +3,11 @@ set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
MODE="${1:-}"
|
||||
TOPIC="${2:-homelab-restore}"
|
||||
SUCCESS_TOPIC="${2:-${RESTORE_SUCCESS_TOPIC:-homelab-info}}"
|
||||
FAILURE_TOPIC="${RESTORE_FAILURE_TOPIC:-homelab-alerts}"
|
||||
|
||||
if [ -z "$MODE" ]; then
|
||||
echo "Usage: $0 <freshness|vaultwarden|gitea|paperless> [topic]" >&2
|
||||
echo "Usage: $0 <freshness|vaultwarden|gitea|paperless> [success_topic]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -20,10 +21,10 @@ echo "Report target: $REPORT_FILE"
|
||||
|
||||
if "$SCRIPT_DIR/run-restore-checks.sh" "$MODE" > "$REPORT_FILE"; then
|
||||
echo "Restore job succeeded, sending ntfy..."
|
||||
"$SCRIPT_DIR/send-ntfy.sh" "$TOPIC" "Restore job ok: $MODE" "Restore job succeeded. Report: $REPORT_FILE" default || true
|
||||
"$SCRIPT_DIR/send-ntfy.sh" "$SUCCESS_TOPIC" "Restore job ok: $MODE" "Restore job succeeded. Report: $REPORT_FILE" default || true
|
||||
echo "Done"
|
||||
else
|
||||
echo "Restore job failed, sending ntfy..."
|
||||
"$SCRIPT_DIR/send-ntfy.sh" "$TOPIC" "Restore job failed: $MODE" "Restore job failed. Report: $REPORT_FILE" high || true
|
||||
"$SCRIPT_DIR/send-ntfy.sh" "$FAILURE_TOPIC" "Restore job failed: $MODE" "Restore job failed. Report: $REPORT_FILE" high || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -45,11 +45,23 @@ Spaeter:
|
||||
- `vaultwarden`
|
||||
- Jeden 3. Samstag im Monat, 07:00:
|
||||
- `gitea`
|
||||
- Jeden 2. Monat am 2. Samstag, 08:00:
|
||||
- Jeden 2. Samstag in ungeraden Monaten, 08:00:
|
||||
- `paperless`
|
||||
- Jeden 1. des Monats, 09:00:
|
||||
- `monthly-random-restore.sh`
|
||||
- Quartalsweise am 1. Werktag des Quartals:
|
||||
- DR-/Restore-Sanity-Check
|
||||
|
||||
## Unraid User Scripts Cron
|
||||
|
||||
| Script | Cron | Bedeutung |
|
||||
|---|---|---|
|
||||
| `restore-freshness-weekly` | `30 6 * * 1` | jeden Montag 06:30 |
|
||||
| `restore-vaultwarden-monthly` | `0 7 1-7 * 6` | erster Samstag im Monat 07:00 |
|
||||
| `restore-gitea-monthly` | `15 7 15-21 * 6` | dritter Samstag im Monat 07:15 |
|
||||
| `restore-paperless-bimonthly` | `0 8 8-14 1,3,5,7,9,11 *` | zweiter Samstag in ungeraden Monaten 08:00 |
|
||||
| `monthly-random-restore` | `0 9 1 * *` | erster Kalendertag im Monat 09:00 |
|
||||
|
||||
## Betriebsmodus
|
||||
|
||||
- V1:
|
||||
|
||||
@@ -7,7 +7,7 @@ Diese Vorlagen binden die validierten Restore-Checks in Unraid User Scripts ein.
|
||||
Host-Repo-Pfad:
|
||||
|
||||
```text
|
||||
/mnt/user/services/homelab
|
||||
/mnt/user/services/homelab-infra
|
||||
```
|
||||
|
||||
## Script 1 - `restore-freshness-weekly`
|
||||
@@ -20,7 +20,7 @@ Inhalt:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
bash /mnt/user/services/homelab/ops/restore-tests/run-restore-checks.sh freshness \
|
||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh freshness \
|
||||
> /mnt/user/backups/restore-reports/freshness-$(date +%F).md
|
||||
```
|
||||
|
||||
@@ -40,7 +40,7 @@ V1-Inhalt:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
bash /mnt/user/services/homelab/ops/restore-tests/run-restore-checks.sh vaultwarden \
|
||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh vaultwarden \
|
||||
> /mnt/user/backups/restore-reports/vaultwarden-$(date +%F).md
|
||||
```
|
||||
|
||||
@@ -54,7 +54,7 @@ V1-Inhalt:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
bash /mnt/user/services/homelab/ops/restore-tests/run-restore-checks.sh gitea \
|
||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh gitea \
|
||||
> /mnt/user/backups/restore-reports/gitea-$(date +%F).md
|
||||
```
|
||||
|
||||
@@ -68,7 +68,7 @@ V1-Inhalt:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
bash /mnt/user/services/homelab/ops/restore-tests/run-restore-checks.sh paperless \
|
||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh paperless \
|
||||
> /mnt/user/backups/restore-reports/paperless-$(date +%F).md
|
||||
```
|
||||
|
||||
@@ -97,9 +97,11 @@ Beispiel:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
bash /mnt/user/services/homelab/ops/restore-tests/run-restore-job-with-ntfy.sh freshness homelab-restore
|
||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-job-with-ntfy.sh freshness homelab-info
|
||||
```
|
||||
|
||||
Fehler gehen unabhaengig vom Erfolgstopic nach `homelab-alerts`, damit Restore-Probleme auf dem gleichen Handy-Topic landen wie Prometheus-, Docker-, Borg- und Posture-Alarme.
|
||||
|
||||
Verwendete Hilfsskripte:
|
||||
|
||||
- `ops/restore-tests/send-ntfy.sh`
|
||||
|
||||
@@ -18,6 +18,8 @@ services:
|
||||
- /dev/sdb:/dev/sdb
|
||||
- /dev/sdc:/dev/sdc
|
||||
- /dev/nvme0n1:/dev/nvme0n1
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
networks:
|
||||
- frontend_net
|
||||
labels:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
speedtest-tracker:
|
||||
image: lscr.io/linuxserver/speedtest-tracker:latest@sha256:eb3d249f16177964daa4fff7f6a90bbf6645f4e23158d92f5cddb133728d0804
|
||||
image: lscr.io/linuxserver/speedtest-tracker:1.13.12@sha256:eb3d249f16177964daa4fff7f6a90bbf6645f4e23158d92f5cddb133728d0804
|
||||
container_name: speedtest-tracker
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
services:
|
||||
uptime-kuma:
|
||||
image: louislam/uptime-kuma:1@sha256:3d632903e6af34139a37f18055c4f1bfd9b7205ae1138f1e5e8940ddc1d176f9
|
||||
container_name: UptimeKuma
|
||||
restart: unless-stopped
|
||||
dns:
|
||||
- 8.8.8.8
|
||||
- 1.1.1.1
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
volumes:
|
||||
- /mnt/user/appdata/uptime-kuma:/app/data
|
||||
networks:
|
||||
- frontend_net
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=frontend_net"
|
||||
- "traefik.http.routers.uptime-kuma.rule=Host(`uptime.kaleschke.info`)"
|
||||
- "traefik.http.routers.uptime-kuma.entrypoints=websecure"
|
||||
- "traefik.http.routers.uptime-kuma.tls=true"
|
||||
- "traefik.http.routers.uptime-kuma.tls.certresolver=le"
|
||||
- "traefik.http.routers.uptime-kuma.middlewares=authelia@file,secure-headers@file"
|
||||
- "traefik.http.services.uptime-kuma.loadbalancer.server.port=3001"
|
||||
|
||||
networks:
|
||||
frontend_net:
|
||||
external: true
|
||||
@@ -0,0 +1,9 @@
|
||||
# Windows Reinstall Helpers
|
||||
|
||||
Diese Skripte sind bewusst versionierte Operator-Hilfen fuer den Windows-Neuaufsetzen-/Dual-Boot-Kontext vom Mai 2026.
|
||||
|
||||
- `backup-delta-after-2026-05-07.ps1` kopiert nach einem definierten Cutoff lokale Nutzdaten in ein Backup-Ziel.
|
||||
- `repair-disk0-boot-to-new-windows.ps1` repariert EFI/Bootdateien fuer das neue Windows auf der Intel-SSD und verlangt Adminrechte.
|
||||
- `cleanup-dualboot-bcd.ps1` bereinigt BCD-Bootmenueeintraege und verlangt eine explizite Textbestaetigung.
|
||||
|
||||
Die Skripte enthalten keine Secrets, arbeiten aber mit lokalen Windows-Datentraegern und duerfen nur interaktiv und mit vorheriger Sichtpruefung ausgefuehrt werden.
|
||||
@@ -0,0 +1,118 @@
|
||||
param(
|
||||
[datetime]$Cutoff = "2026-05-07T15:30:00",
|
||||
[string]$BackupRoot = "H:\Windows-Neuaufsetzen-Backup",
|
||||
[string]$UserProfilePath = "C:\Users\michi"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Invoke-RobocopyDelta {
|
||||
param(
|
||||
[string]$Source,
|
||||
[string]$Destination,
|
||||
[string]$LogName
|
||||
)
|
||||
|
||||
if (-not (Test-Path $Source)) {
|
||||
Write-Host "SKIP missing source: $Source"
|
||||
return
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $Destination | Out-Null
|
||||
$LogPath = Join-Path $DeltaLogDir $LogName
|
||||
|
||||
Write-Host "DELTA COPY $Source"
|
||||
Write-Host " -> $Destination"
|
||||
|
||||
robocopy $Source $Destination /E /COPY:DAT /DCOPY:DAT /R:2 /W:2 /XJ /MAXAGE:$CutoffString /TEE /LOG:$LogPath
|
||||
$ExitCode = $LASTEXITCODE
|
||||
if ($ExitCode -gt 7) {
|
||||
throw "Robocopy failed with exit code $ExitCode for source: $Source"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path $BackupRoot)) {
|
||||
throw "BackupRoot does not exist: $BackupRoot"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $UserProfilePath)) {
|
||||
throw "UserProfilePath does not exist: $UserProfilePath"
|
||||
}
|
||||
|
||||
$DeltaRoot = Join-Path $BackupRoot "_Delta_2026-05-19"
|
||||
$DeltaLogDir = Join-Path $DeltaRoot "00_Logs"
|
||||
New-Item -ItemType Directory -Force -Path $DeltaLogDir | Out-Null
|
||||
|
||||
$CutoffString = $Cutoff.ToString("yyyyMMdd")
|
||||
|
||||
$Manifest = [ordered]@{
|
||||
CreatedAt = (Get-Date).ToString("s")
|
||||
Cutoff = $Cutoff.ToString("s")
|
||||
CutoffNote = "Robocopy /MAXAGE uses date granularity, so files changed on or after the cutoff day are included."
|
||||
RunningSystemDrive = $env:SystemDrive
|
||||
RunningWinDir = $env:windir
|
||||
BackupRoot = $BackupRoot
|
||||
DeltaRoot = $DeltaRoot
|
||||
}
|
||||
|
||||
$Manifest.GetEnumerator() |
|
||||
ForEach-Object { "$($_.Key)=$($_.Value)" } |
|
||||
Out-File (Join-Path $DeltaRoot "delta_manifest.txt") -Encoding UTF8
|
||||
|
||||
$Jobs = @(
|
||||
@{ Source = Join-Path $UserProfilePath "Desktop"; Destination = Join-Path $DeltaRoot "01_Desktop"; Log = "delta_current_desktop.log" },
|
||||
@{ Source = Join-Path $UserProfilePath "Documents"; Destination = Join-Path $DeltaRoot "02_Dokumente"; Log = "delta_current_documents.log" },
|
||||
@{ Source = Join-Path $UserProfilePath "Pictures"; Destination = Join-Path $DeltaRoot "03_Bilder"; Log = "delta_current_pictures.log" },
|
||||
@{ Source = Join-Path $UserProfilePath "Videos"; Destination = Join-Path $DeltaRoot "04_Videos"; Log = "delta_current_videos.log" },
|
||||
@{ Source = Join-Path $UserProfilePath "Downloads"; Destination = Join-Path $DeltaRoot "05_Downloads"; Log = "delta_current_downloads.log" },
|
||||
@{ Source = Join-Path $UserProfilePath ".ssh"; Destination = Join-Path $DeltaRoot "09_Programme_Settings_Lizenzen\ssh"; Log = "delta_current_ssh.log" },
|
||||
@{ Source = Join-Path $UserProfilePath "AppData\Local\Subsembly"; Destination = Join-Path $DeltaRoot "09_Programme_Settings_Lizenzen\Subsembly_Local"; Log = "delta_subsembly_local.log" },
|
||||
@{ Source = Join-Path $UserProfilePath "AppData\Local\Buhl"; Destination = Join-Path $DeltaRoot "09_Programme_Settings_Lizenzen\Buhl_Local"; Log = "delta_buhl_local.log" },
|
||||
@{ Source = Join-Path $UserProfilePath "AppData\Local\Buhl Data Service GmbH"; Destination = Join-Path $DeltaRoot "09_Programme_Settings_Lizenzen\Buhl_Data_Service_Local"; Log = "delta_buhl_data_service_local.log" },
|
||||
@{ Source = "C:\ProgramData\Buhl Data Service GmbH"; Destination = Join-Path $DeltaRoot "09_Programme_Settings_Lizenzen\Buhl_Data_Service_ProgramData"; Log = "delta_buhl_data_service_programdata.log" },
|
||||
@{ Source = "G:\Gitea_Clone"; Destination = Join-Path $DeltaRoot "06_Projekte\Gitea_Clone"; Log = "delta_g_gitea_clone.log" },
|
||||
@{ Source = "G:\open-webui"; Destination = Join-Path $DeltaRoot "09_Programme_Settings_Lizenzen\open-webui"; Log = "delta_g_open_webui.log" },
|
||||
@{ Source = "G:\Treiber"; Destination = Join-Path $DeltaRoot "13_Treiber_Windows\G_Treiber"; Log = "delta_g_treiber.log" },
|
||||
@{ Source = "F:\BMW Leasing"; Destination = Join-Path $DeltaRoot "07_Banking_Finanzen\BMW Leasing"; Log = "delta_f_bmw_leasing.log" },
|
||||
@{ Source = "F:\Marina Handy 2025"; Destination = Join-Path $DeltaRoot "03_Bilder\Marina Handy 2025"; Log = "delta_f_marina_handy_2025.log" }
|
||||
)
|
||||
|
||||
foreach ($Job in $Jobs) {
|
||||
Invoke-RobocopyDelta -Source $Job.Source -Destination $Job.Destination -LogName $Job.Log
|
||||
}
|
||||
|
||||
$ExplicitBankingDir = Join-Path $DeltaRoot "07_Banking_Finanzen\Banking4_Datentresor_explizit"
|
||||
New-Item -ItemType Directory -Force -Path $ExplicitBankingDir | Out-Null
|
||||
|
||||
$BankingFiles = @(
|
||||
(Join-Path $UserProfilePath "Documents\Mein Datentresor.sub"),
|
||||
(Join-Path $UserProfilePath "Documents\.Mein Datentresor.sub.att")
|
||||
)
|
||||
|
||||
foreach ($File in $BankingFiles) {
|
||||
if (Test-Path $File) {
|
||||
Copy-Item -LiteralPath $File -Destination (Join-Path $ExplicitBankingDir (Split-Path $File -Leaf)) -Force
|
||||
}
|
||||
}
|
||||
|
||||
$SummaryRoots = Get-ChildItem $DeltaRoot -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne "00_Logs" }
|
||||
$Summary = foreach ($Root in $SummaryRoots) {
|
||||
$Files = @(Get-ChildItem $Root.FullName -Recurse -Force -File -ErrorAction SilentlyContinue)
|
||||
$Measure = $Files | Measure-Object Length -Sum
|
||||
[pscustomobject]@{
|
||||
Path = $Root.FullName
|
||||
Files = $Files.Count
|
||||
SizeGB = [math]::Round(($Measure.Sum / 1GB), 4)
|
||||
Newest = ($Files | Sort-Object LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty LastWriteTime)
|
||||
}
|
||||
}
|
||||
|
||||
$Summary | Export-Csv (Join-Path $DeltaLogDir "delta_summary.csv") -NoTypeInformation -Encoding UTF8
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Delta backup finished:"
|
||||
Write-Host " $DeltaRoot"
|
||||
Write-Host ""
|
||||
Write-Host "Summary:"
|
||||
$Summary | Format-Table -AutoSize
|
||||
@@ -0,0 +1,105 @@
|
||||
param(
|
||||
[string]$BackupRoot = "H:\Windows-Neuaufsetzen-Backup",
|
||||
[string]$UserProfilePath = $env:USERPROFILE,
|
||||
[switch]$IncludeOllama
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Invoke-RobocopyBackup {
|
||||
param(
|
||||
[string]$Source,
|
||||
[string]$Destination,
|
||||
[string]$LogName
|
||||
)
|
||||
|
||||
if (-not (Test-Path $Source)) {
|
||||
Write-Host "SKIP missing source: $Source"
|
||||
return
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $Destination | Out-Null
|
||||
|
||||
$LogDir = Join-Path $BackupRoot "12_Exportierte_Listen"
|
||||
New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
|
||||
$LogPath = Join-Path $LogDir $LogName
|
||||
|
||||
Write-Host "COPY $Source"
|
||||
Write-Host " -> $Destination"
|
||||
|
||||
robocopy $Source $Destination /E /COPY:DAT /DCOPY:DAT /R:2 /W:2 /XJ /TEE /LOG:$LogPath
|
||||
$ExitCode = $LASTEXITCODE
|
||||
|
||||
if ($ExitCode -gt 7) {
|
||||
throw "Robocopy failed with exit code $ExitCode for source: $Source"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path $BackupRoot)) {
|
||||
throw "BackupRoot does not exist: $BackupRoot"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $UserProfilePath)) {
|
||||
throw "UserProfilePath does not exist: $UserProfilePath"
|
||||
}
|
||||
|
||||
$CurrentUserJobs = @(
|
||||
@{ Source = Join-Path $UserProfilePath "Desktop"; Destination = Join-Path $BackupRoot "01_Desktop"; Log = "backup_current_desktop.log" },
|
||||
@{ Source = Join-Path $UserProfilePath "Documents"; Destination = Join-Path $BackupRoot "02_Dokumente"; Log = "backup_current_documents.log" },
|
||||
@{ Source = Join-Path $UserProfilePath "Pictures"; Destination = Join-Path $BackupRoot "03_Bilder"; Log = "backup_current_pictures.log" },
|
||||
@{ Source = Join-Path $UserProfilePath "Videos"; Destination = Join-Path $BackupRoot "04_Videos"; Log = "backup_current_videos.log" },
|
||||
@{ Source = Join-Path $UserProfilePath "Downloads"; Destination = Join-Path $BackupRoot "05_Downloads_wichtig\_current_downloads_all"; Log = "backup_current_downloads.log" },
|
||||
@{ Source = Join-Path $UserProfilePath "Music"; Destination = Join-Path $BackupRoot "15_Musik"; Log = "backup_current_music.log" },
|
||||
@{ Source = Join-Path $UserProfilePath ".ssh"; Destination = Join-Path $BackupRoot "09_Programme_Settings_Lizenzen\ssh"; Log = "backup_current_ssh.log" }
|
||||
)
|
||||
|
||||
foreach ($Job in $CurrentUserJobs) {
|
||||
Invoke-RobocopyBackup -Source $Job.Source -Destination $Job.Destination -LogName $Job.Log
|
||||
}
|
||||
|
||||
$GitConfig = Join-Path $UserProfilePath ".gitconfig"
|
||||
if (Test-Path $GitConfig) {
|
||||
$GitDest = Join-Path $BackupRoot "09_Programme_Settings_Lizenzen\git"
|
||||
New-Item -ItemType Directory -Force -Path $GitDest | Out-Null
|
||||
Copy-Item -LiteralPath $GitConfig -Destination (Join-Path $GitDest ".gitconfig") -Force
|
||||
}
|
||||
|
||||
$OldUserRoot = "D:\Users\Baerchen"
|
||||
$OldUserJobs = @(
|
||||
@{ Source = Join-Path $OldUserRoot "Desktop"; Destination = Join-Path $BackupRoot "99_Unsortiert_von_D_F_G\D_Users_Baerchen\Desktop"; Log = "backup_old_baerchen_desktop.log" },
|
||||
@{ Source = Join-Path $OldUserRoot "Documents"; Destination = Join-Path $BackupRoot "99_Unsortiert_von_D_F_G\D_Users_Baerchen\Documents"; Log = "backup_old_baerchen_documents.log" },
|
||||
@{ Source = Join-Path $OldUserRoot "Pictures"; Destination = Join-Path $BackupRoot "99_Unsortiert_von_D_F_G\D_Users_Baerchen\Pictures"; Log = "backup_old_baerchen_pictures.log" },
|
||||
@{ Source = Join-Path $OldUserRoot "Videos"; Destination = Join-Path $BackupRoot "99_Unsortiert_von_D_F_G\D_Users_Baerchen\Videos"; Log = "backup_old_baerchen_videos.log" },
|
||||
@{ Source = Join-Path $OldUserRoot "Downloads"; Destination = Join-Path $BackupRoot "99_Unsortiert_von_D_F_G\D_Users_Baerchen\Downloads"; Log = "backup_old_baerchen_downloads.log" },
|
||||
@{ Source = Join-Path $OldUserRoot "Music"; Destination = Join-Path $BackupRoot "99_Unsortiert_von_D_F_G\D_Users_Baerchen\Music"; Log = "backup_old_baerchen_music.log" }
|
||||
)
|
||||
|
||||
foreach ($Job in $OldUserJobs) {
|
||||
Invoke-RobocopyBackup -Source $Job.Source -Destination $Job.Destination -LogName $Job.Log
|
||||
}
|
||||
|
||||
$ExtraJobs = @(
|
||||
@{ Source = "F:\BMW Leasing"; Destination = Join-Path $BackupRoot "07_Banking_Finanzen\BMW Leasing"; Log = "backup_f_bmw_leasing.log" },
|
||||
@{ Source = "F:\Marina Handy 2025"; Destination = Join-Path $BackupRoot "03_Bilder\Marina Handy 2025"; Log = "backup_f_marina_handy_2025.log" },
|
||||
@{ Source = "F:\Marina Handy Backup"; Destination = Join-Path $BackupRoot "03_Bilder\Marina Handy Backup"; Log = "backup_f_marina_handy_backup.log" },
|
||||
@{ Source = "G:\Gitea_Clone"; Destination = Join-Path $BackupRoot "06_Projekte\Gitea_Clone"; Log = "backup_g_gitea_clone.log" },
|
||||
@{ Source = "G:\open-webui"; Destination = Join-Path $BackupRoot "09_Programme_Settings_Lizenzen\open-webui"; Log = "backup_g_open_webui.log" },
|
||||
@{ Source = "G:\Treiber"; Destination = Join-Path $BackupRoot "13_Treiber_Windows\G_Treiber"; Log = "backup_g_treiber.log" }
|
||||
)
|
||||
|
||||
if ($IncludeOllama) {
|
||||
$ExtraJobs += @{ Source = "G:\Ollama"; Destination = Join-Path $BackupRoot "09_Programme_Settings_Lizenzen\Ollama"; Log = "backup_g_ollama.log" }
|
||||
}
|
||||
|
||||
foreach ($Job in $ExtraJobs) {
|
||||
Invoke-RobocopyBackup -Source $Job.Source -Destination $Job.Destination -LogName $Job.Log
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Known-data backup finished."
|
||||
Write-Host "Backup root: $BackupRoot"
|
||||
Write-Host "Logs: $BackupRoot\12_Exportierte_Listen"
|
||||
if (-not $IncludeOllama) {
|
||||
Write-Host "Note: G:\Ollama was not copied. Re-run with -IncludeOllama if you want to keep local models too."
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$BackupDir = "H:\Windows-Neuaufsetzen-Backup\12_Exportierte_Listen"
|
||||
$BeforeFile = Join-Path $BackupDir "bcdedit_enum_before_dualboot_cleanup.txt"
|
||||
$AfterFile = Join-Path $BackupDir "bcdedit_enum_after_dualboot_cleanup.txt"
|
||||
|
||||
function Assert-Admin {
|
||||
$Identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$Principal = [Security.Principal.WindowsPrincipal]::new($Identity)
|
||||
if (-not $Principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
throw "Dieses Skript muss in PowerShell als Administrator ausgefuehrt werden."
|
||||
}
|
||||
}
|
||||
|
||||
function Get-BcdOsLoaderEntries {
|
||||
$Text = & bcdedit /enum osloader /v
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "bcdedit /enum osloader /v ist fehlgeschlagen."
|
||||
}
|
||||
|
||||
$Entries = @()
|
||||
$Current = $null
|
||||
|
||||
foreach ($Line in $Text) {
|
||||
if ($Line -match "^-{5,}$") {
|
||||
if ($Current -and $Current.Identifier) {
|
||||
$Entries += [pscustomobject]$Current
|
||||
}
|
||||
$Current = @{
|
||||
Identifier = $null
|
||||
Device = $null
|
||||
OsDevice = $null
|
||||
Path = $null
|
||||
Description = $null
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (-not $Current) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ($Line -match "^\s*(identifier|Bezeichner)\s+(.+)$") { $Current.Identifier = $Matches[2].Trim(); continue }
|
||||
if ($Line -match "^\s*device\s+(.+)$") { $Current.Device = $Matches[1].Trim(); continue }
|
||||
if ($Line -match "^\s*osdevice\s+(.+)$") { $Current.OsDevice = $Matches[1].Trim(); continue }
|
||||
if ($Line -match "^\s*path\s+(.+)$") { $Current.Path = $Matches[1].Trim(); continue }
|
||||
if ($Line -match "^\s*description\s+(.+)$") { $Current.Description = $Matches[1].Trim(); continue }
|
||||
}
|
||||
|
||||
if ($Current -and $Current.Identifier) {
|
||||
$Entries += [pscustomobject]$Current
|
||||
}
|
||||
|
||||
return $Entries
|
||||
}
|
||||
|
||||
Assert-Admin
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $BackupDir | Out-Null
|
||||
|
||||
& bcdedit /enum all /v | Out-File $BeforeFile -Encoding UTF8
|
||||
|
||||
$Entries = Get-BcdOsLoaderEntries
|
||||
|
||||
Write-Host "Gefundene Windows-Boot-Eintraege:"
|
||||
$Entries | Select-Object Identifier, Description, Device, OsDevice, Path | Format-Table -AutoSize
|
||||
|
||||
$NewWindows = $Entries | Where-Object {
|
||||
$_.OsDevice -eq "partition=D:" -and
|
||||
$_.Path -match "winload\.efi$" -and
|
||||
(Test-Path "D:\Windows\System32\winload.efi")
|
||||
} | Select-Object -First 1
|
||||
|
||||
$OldWindows = $Entries | Where-Object {
|
||||
$_.OsDevice -eq "partition=C:" -and
|
||||
$_.Path -match "winload\.efi$" -and
|
||||
(Test-Path "C:\Windows\System32\winload.efi")
|
||||
} | Select-Object -First 1
|
||||
|
||||
if (-not $NewWindows) {
|
||||
throw "Abbruch: Neuer Windows-Eintrag auf D:\Windows wurde nicht eindeutig gefunden."
|
||||
}
|
||||
|
||||
if (-not $OldWindows) {
|
||||
throw "Abbruch: Alter Windows-Eintrag auf C:\Windows wurde nicht eindeutig gefunden."
|
||||
}
|
||||
|
||||
$KeepIds = @($NewWindows.Identifier, $OldWindows.Identifier)
|
||||
$DeleteEntries = $Entries | Where-Object { $KeepIds -notcontains $_.Identifier }
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Bleibt erhalten:"
|
||||
Write-Host " NEU: $($NewWindows.Identifier) -> $($NewWindows.OsDevice) -> D:\Windows"
|
||||
Write-Host " ALT: $($OldWindows.Identifier) -> $($OldWindows.OsDevice) -> C:\Windows"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Wird aus dem Bootmenue entfernt:"
|
||||
if ($DeleteEntries) {
|
||||
$DeleteEntries | Select-Object Identifier, Description, Device, OsDevice, Path | Format-Table -AutoSize
|
||||
} else {
|
||||
Write-Host " Keine ueberfluessigen Eintraege gefunden."
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Es werden nur Bootmenue-Eintraege geloescht, keine Partitionen und keine Windows-Ordner."
|
||||
$Confirmation = Read-Host "Tippe exakt BOOTCLEAN um fortzufahren"
|
||||
if ($Confirmation -ne "BOOTCLEAN") {
|
||||
throw "Abbruch: Bestaetigung wurde nicht eingegeben."
|
||||
}
|
||||
|
||||
& bcdedit /set $NewWindows.Identifier description "Windows 11 Neu"
|
||||
if ($LASTEXITCODE -ne 0) { throw "Konnte neuen Windows-Eintrag nicht umbenennen." }
|
||||
|
||||
& bcdedit /set $OldWindows.Identifier description "Windows 11 Alt"
|
||||
if ($LASTEXITCODE -ne 0) { throw "Konnte alten Windows-Eintrag nicht umbenennen." }
|
||||
|
||||
& bcdedit /default $NewWindows.Identifier
|
||||
if ($LASTEXITCODE -ne 0) { throw "Konnte Standard-Boot-Eintrag nicht setzen." }
|
||||
|
||||
& bcdedit /timeout 5
|
||||
if ($LASTEXITCODE -ne 0) { throw "Konnte Timeout nicht setzen." }
|
||||
|
||||
foreach ($Entry in $DeleteEntries) {
|
||||
Write-Host "Loesche Boot-Eintrag $($Entry.Identifier) ($($Entry.Description))"
|
||||
& bcdedit /delete $Entry.Identifier /f
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Konnte Boot-Eintrag $($Entry.Identifier) nicht loeschen."
|
||||
}
|
||||
}
|
||||
|
||||
& bcdedit /enum all /v | Out-File $AfterFile -Encoding UTF8
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Bootmenue bereinigt."
|
||||
Write-Host "Vorher gesichert: $BeforeFile"
|
||||
Write-Host "Nachher gesichert: $AfterFile"
|
||||
Write-Host ""
|
||||
Write-Host "Bitte neu starten. Es sollten nur noch zwei Eintraege erscheinen:"
|
||||
Write-Host " Windows 11 Neu"
|
||||
Write-Host " Windows 11 Alt"
|
||||
@@ -0,0 +1,69 @@
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$ExpectedDiskNumber = 0
|
||||
$ExpectedFriendlyName = "INTEL SSDSC2BW180A3L"
|
||||
$ExpectedSerialNumber = "CVCV3105053K180EGN"
|
||||
$MinSizeGB = 160
|
||||
$MaxSizeGB = 180
|
||||
|
||||
function Assert-Admin {
|
||||
$Identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$Principal = [Security.Principal.WindowsPrincipal]::new($Identity)
|
||||
if (-not $Principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
throw "Dieses Skript muss in PowerShell als Administrator ausgefuehrt werden."
|
||||
}
|
||||
}
|
||||
|
||||
Assert-Admin
|
||||
|
||||
$Disk = Get-Disk -Number $ExpectedDiskNumber
|
||||
$SizeGB = [math]::Round($Disk.Size / 1GB, 2)
|
||||
|
||||
Write-Host "Zieldatentraeger:"
|
||||
$Disk | Select-Object Number, FriendlyName, SerialNumber, HealthStatus, OperationalStatus, @{Name="SizeGB";Expression={[math]::Round($_.Size/1GB,2)}}, PartitionStyle | Format-Table -AutoSize
|
||||
|
||||
Write-Host "Aktuelle Partitionen auf Datentraeger ${ExpectedDiskNumber}:"
|
||||
Get-Partition -DiskNumber $ExpectedDiskNumber | Sort-Object PartitionNumber | Select-Object DiskNumber, PartitionNumber, DriveLetter, Type, @{Name="SizeGB";Expression={[math]::Round($_.Size/1GB,3)}} | Format-Table -AutoSize
|
||||
|
||||
if ($Disk.FriendlyName -ne $ExpectedFriendlyName) {
|
||||
throw "Abbruch: FriendlyName passt nicht. Erwartet '$ExpectedFriendlyName', gefunden '$($Disk.FriendlyName)'."
|
||||
}
|
||||
|
||||
if ($Disk.SerialNumber -ne $ExpectedSerialNumber) {
|
||||
throw "Abbruch: SerialNumber passt nicht. Erwartet '$ExpectedSerialNumber', gefunden '$($Disk.SerialNumber)'."
|
||||
}
|
||||
|
||||
if ($SizeGB -lt $MinSizeGB -or $SizeGB -gt $MaxSizeGB) {
|
||||
throw "Abbruch: Groesse passt nicht. Erwartet ca. 167 GB, gefunden $SizeGB GB."
|
||||
}
|
||||
|
||||
$SystemPartitions = Get-Partition -DiskNumber $ExpectedDiskNumber | Where-Object {
|
||||
$_.IsBoot -or $_.IsSystem -or $_.IsActive
|
||||
}
|
||||
|
||||
if ($SystemPartitions) {
|
||||
throw "Abbruch: Datentraeger $ExpectedDiskNumber enthaelt Boot/System/Aktiv-Partitionen."
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "WARNUNG: Datentraeger 0 wird jetzt komplett geleert."
|
||||
Write-Host "Das betrifft die alte Windows-SSD mit aktuell D: und Recovery-Partitionen."
|
||||
Write-Host "Andere Datentraeger werden nicht angefasst."
|
||||
Write-Host ""
|
||||
|
||||
$Confirmation = Read-Host "Tippe exakt LEEREN um fortzufahren"
|
||||
if ($Confirmation -ne "LEEREN") {
|
||||
throw "Abbruch: Bestaetigung wurde nicht eingegeben."
|
||||
}
|
||||
|
||||
Clear-Disk -Number $ExpectedDiskNumber -RemoveData -RemoveOEM -Confirm:$false
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Datentraeger $ExpectedDiskNumber wurde geleert."
|
||||
Write-Host "Ergebnis:"
|
||||
Get-Disk -Number $ExpectedDiskNumber | Select-Object Number, FriendlyName, HealthStatus, OperationalStatus, @{Name="SizeGB";Expression={[math]::Round($_.Size/1GB,2)}}, PartitionStyle | Format-Table -AutoSize
|
||||
Get-Partition -DiskNumber $ExpectedDiskNumber -ErrorAction SilentlyContinue | Format-Table -AutoSize
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Naechster Schritt: Vom Windows-USB-Stick booten und Windows auf den nicht zugeordneten Speicher von Datentraeger 0 installieren."
|
||||
@@ -0,0 +1,34 @@
|
||||
param(
|
||||
[string]$BackupRoot = "H:\Windows-Neuaufsetzen-Backup",
|
||||
[switch]$ImportWingetList
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
|
||||
throw "winget is not available. Install or update App Installer from Microsoft Store first."
|
||||
}
|
||||
|
||||
Write-Host "Installing UniGetUI..."
|
||||
winget install --exact --id Devolutions.UniGetUI --source winget --accept-package-agreements --accept-source-agreements
|
||||
|
||||
$WingetExport = Join-Path $BackupRoot "12_Exportierte_Listen\winget-export.json"
|
||||
|
||||
if ($ImportWingetList) {
|
||||
if (-not (Test-Path $WingetExport)) {
|
||||
throw "WinGet export file not found: $WingetExport"
|
||||
}
|
||||
|
||||
Write-Host "Importing WinGet package list..."
|
||||
Write-Host "Source: $WingetExport"
|
||||
winget import --import-file $WingetExport --accept-package-agreements --accept-source-agreements
|
||||
} else {
|
||||
Write-Host ""
|
||||
Write-Host "UniGetUI installed."
|
||||
Write-Host "WinGet export is available here:"
|
||||
Write-Host " $WingetExport"
|
||||
Write-Host ""
|
||||
Write-Host "Review the list before importing. To import later, run:"
|
||||
Write-Host " .\postinstall-unigetui.ps1 -ImportWingetList"
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
param(
|
||||
[string]$BackupRoot = "H:\Windows-Neuaufsetzen-Backup",
|
||||
[string]$UserProfilePath = $env:USERPROFILE
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function New-BackupFolder {
|
||||
param([string]$Name)
|
||||
|
||||
$Path = Join-Path $BackupRoot $Name
|
||||
New-Item -ItemType Directory -Force -Path $Path | Out-Null
|
||||
return $Path
|
||||
}
|
||||
|
||||
function Export-CsvSafe {
|
||||
param(
|
||||
[Parameter(ValueFromPipeline = $true)]$InputObject,
|
||||
[string]$Path
|
||||
)
|
||||
|
||||
begin {
|
||||
$Items = @()
|
||||
}
|
||||
|
||||
process {
|
||||
$Items += $InputObject
|
||||
}
|
||||
|
||||
end {
|
||||
$Items | Export-Csv -Path $Path -NoTypeInformation -Encoding UTF8
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path $UserProfilePath)) {
|
||||
throw "UserProfilePath does not exist: $UserProfilePath"
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $BackupRoot | Out-Null
|
||||
|
||||
$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"
|
||||
)
|
||||
|
||||
$Folders | ForEach-Object { New-BackupFolder $_ | Out-Null }
|
||||
$ExportDir = Join-Path $BackupRoot "12_Exportierte_Listen"
|
||||
|
||||
Get-ItemProperty `
|
||||
HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*, `
|
||||
HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* |
|
||||
Where-Object {
|
||||
$_.PSObject.Properties.Name -contains "DisplayName" -and
|
||||
-not [string]::IsNullOrWhiteSpace($_.DisplayName)
|
||||
} |
|
||||
Select-Object DisplayName, DisplayVersion, Publisher, InstallDate, InstallLocation |
|
||||
Sort-Object DisplayName |
|
||||
Export-CsvSafe -Path (Join-Path $ExportDir "installierte_programme.csv")
|
||||
|
||||
if (Get-Command winget -ErrorAction SilentlyContinue) {
|
||||
winget list | Out-File -FilePath (Join-Path $ExportDir "winget-list.txt") -Encoding UTF8
|
||||
}
|
||||
|
||||
Get-Volume |
|
||||
Sort-Object DriveLetter |
|
||||
Select-Object DriveLetter, FileSystemLabel, FileSystem, Size, SizeRemaining, HealthStatus |
|
||||
Export-CsvSafe -Path (Join-Path $ExportDir "laufwerke.csv")
|
||||
|
||||
Get-Disk |
|
||||
Select-Object Number, FriendlyName, SerialNumber, HealthStatus, OperationalStatus, Size, PartitionStyle |
|
||||
Export-CsvSafe -Path (Join-Path $ExportDir "datentraeger.csv")
|
||||
|
||||
Get-Partition |
|
||||
Select-Object DiskNumber, PartitionNumber, DriveLetter, Type, Size, GptType |
|
||||
Sort-Object DiskNumber, PartitionNumber |
|
||||
Export-CsvSafe -Path (Join-Path $ExportDir "partitionen.csv")
|
||||
|
||||
try {
|
||||
wmic path softwarelicensingservice get OA3xOriginalProductKey |
|
||||
Out-File -FilePath (Join-Path $ExportDir "windows_oem_key.txt") -Encoding UTF8
|
||||
} catch {
|
||||
"Could not read OEM key: $($_.Exception.Message)" |
|
||||
Out-File -FilePath (Join-Path $ExportDir "windows_oem_key.txt") -Encoding UTF8
|
||||
}
|
||||
|
||||
if (Get-Command manage-bde -ErrorAction SilentlyContinue) {
|
||||
manage-bde -status | Out-File -FilePath (Join-Path $ExportDir "bitlocker_status.txt") -Encoding UTF8
|
||||
}
|
||||
|
||||
if (Get-Command wsl -ErrorAction SilentlyContinue) {
|
||||
wsl --list --verbose | Out-File -FilePath (Join-Path $ExportDir "wsl-distros.txt") -Encoding UTF8
|
||||
}
|
||||
|
||||
$KnownUserPaths = @(
|
||||
"Desktop",
|
||||
"Documents",
|
||||
"Pictures",
|
||||
"Videos",
|
||||
"Downloads",
|
||||
"Music",
|
||||
".ssh",
|
||||
".gitconfig"
|
||||
)
|
||||
|
||||
$KnownUserPaths |
|
||||
ForEach-Object {
|
||||
$Path = Join-Path $UserProfilePath $_
|
||||
[pscustomobject]@{
|
||||
Name = $_
|
||||
Path = $Path
|
||||
Exists = Test-Path $Path
|
||||
LastWriteTime = if (Test-Path $Path) { (Get-Item $Path -Force).LastWriteTime } else { $null }
|
||||
}
|
||||
} |
|
||||
Export-CsvSafe -Path (Join-Path $ExportDir "benutzerpfade.csv")
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Backup preparation inventory created:"
|
||||
Write-Host " $BackupRoot"
|
||||
Write-Host ""
|
||||
Write-Host "Next manual steps:"
|
||||
Write-Host " 1. Save screenshots into: $BackupRoot\14_Screenshots"
|
||||
Write-Host " 2. Review CSV files in: $ExportDir"
|
||||
Write-Host " 3. Start data backup only after confirming H: is the correct external drive."
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user