Compare commits
131 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8379657446 | |||
| c39ae5cdfa | |||
| 80385c4560 | |||
| 7587ee4e77 | |||
| a3c5610934 | |||
| bc9ace315a | |||
| 5171059dd1 | |||
| 0ecb2aceca | |||
| 1160f50663 | |||
| 88c48faab1 | |||
| ec8e915a56 | |||
| 861f70da58 | |||
| fc9e4aad8e | |||
| 15b351fa25 | |||
| e8cde1e2e0 | |||
| f236bfec00 | |||
| fd1b7001f6 | |||
| d45a49d648 | |||
| 1255863a4e | |||
| 26fc96a7af | |||
| e18720d1f8 | |||
| a1e6a03f79 | |||
| 8200697258 | |||
| 05b12c4802 | |||
| 8d01c3537a | |||
| 230e0cc9dc | |||
| c9bd4af2a8 | |||
| 5927b478fa | |||
| ee69bbf730 | |||
| d908d967d4 | |||
| 606779d342 | |||
| 0fabed4d1a | |||
| 76b9ffa140 | |||
| 170a7dcc1f | |||
| 0f5045ea8e | |||
| dfa3acc21e | |||
| 2eb8da1cd4 | |||
| 2acbc1adde | |||
| 342d0a0a27 | |||
| 4ab6dcefd2 | |||
| c24b792808 | |||
| 25a4ada891 | |||
| 6e6005aefd | |||
| ad438a07b3 | |||
| ce6f5c72dd | |||
| 630ee8dd90 | |||
| b1ca9ef19c | |||
| 1c949d3fcc | |||
| cfa6c01768 | |||
| 3474d53ce5 | |||
| ca81b959cc | |||
| 23764dff38 | |||
| 3c4a48d7e5 | |||
| c0a39f5dfc | |||
| a1d7b6e433 | |||
| 45f43da659 | |||
| 290cb8949e | |||
| d933d3cee8 | |||
| baedf9f932 | |||
| b387757e87 | |||
| 3eedbcbe16 | |||
| 9033724b15 | |||
| aae176f1b7 | |||
| c7590e6603 | |||
| 3e486b95f6 | |||
| 08b4be7a5d | |||
| a4f4696b0d | |||
| 1fcdb68221 | |||
| 489a429316 | |||
| 513f41b852 | |||
| c80b51f585 | |||
| 42ed59a4d7 | |||
| 58c3324557 | |||
| d48d473942 | |||
| e80e5dd49f | |||
| 3c339474a7 | |||
| c79afdfab0 | |||
| 8172793c68 | |||
| 8e46440944 | |||
| dfe1dc1c99 | |||
| 4007da3302 | |||
| 9836ea3c4f | |||
| 803f84b3af | |||
| d05ca63545 | |||
| 9847baf327 | |||
| 8ec5bc55d9 | |||
| 9c844074e0 | |||
| c126b71852 | |||
| e89b88a513 | |||
| 8bb250220b | |||
| 2f64aee109 | |||
| ed55b88ec1 | |||
| ce747f687f | |||
| cf11b4d75b | |||
| 796901ec6b | |||
| de7b714b4d | |||
| 8045e22873 | |||
| 52f8c2adcb | |||
| 0ddae675a8 | |||
| 7ce8e948cd | |||
| 2a87220862 | |||
| f2d4cad566 | |||
| e7370e4820 | |||
| dc26eb313c | |||
| dc7cbfa6cd | |||
| cf9ca59eb1 | |||
| d2a9c3b8cb | |||
| 0177350e64 | |||
| 2f3a029098 | |||
| a4c79d9d81 | |||
| 18a90fbb4b | |||
| 30f076c85a | |||
| 6e65f81503 | |||
| 6123584a02 | |||
| c33e29016b | |||
| 2628a0c795 | |||
| c7eed6bdad | |||
| 6c61ad3860 | |||
| 2d1b541847 | |||
| c3491eb382 | |||
| 023ee63687 | |||
| 3a263a4846 | |||
| 68d3ace598 | |||
| 0ef98a23e1 | |||
| 6353da47c5 | |||
| 207f49f001 | |||
| a687d9b73e | |||
| e3459c76d0 | |||
| 254eb81496 | |||
| 9a6d7123ce | |||
| 151d253aff |
@@ -22,6 +22,9 @@
|
|||||||
**/*.tgz
|
**/*.tgz
|
||||||
**/*.zip
|
**/*.zip
|
||||||
|
|
||||||
|
# Generated reports
|
||||||
|
ops/policy-checks/last-report.md
|
||||||
|
|
||||||
# Local/editor noise
|
# Local/editor noise
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Agent Context - Homelab Infra
|
||||||
|
|
||||||
|
Typ: Einstieg/Index · Stand: 2026-06-11 · Status: aktiv
|
||||||
|
|
||||||
|
Einstiegspunkt fuer KI-Agenten (Codex, Gemini u. a.; Claude nutzt zusaetzlich
|
||||||
|
`CLAUDE.md`). Kein eigener Inhalt - nur Pflichtpfade.
|
||||||
|
|
||||||
|
## Vor jeder Arbeit lesen
|
||||||
|
|
||||||
|
1. `docs/AI_CONTEXT.md` - Systembild, harte Regeln, Ausnahmen-Kurzliste
|
||||||
|
2. `HOMELAB_ARCHITECTURE_MASTER_V2.md` - Architektur-Zielbild
|
||||||
|
3. `docs/WORKFLOW.md` - verbindlicher GitOps-/No-Drift-Ablauf
|
||||||
|
4. die betroffene `docker-compose.yml` bzw. das betroffene Runbook (Index: `docs/README.md`)
|
||||||
|
|
||||||
|
## Nicht verhandelbar
|
||||||
|
|
||||||
|
- Keine Secret-Werte lesen, zitieren oder schreiben - nur Namen und Pfade.
|
||||||
|
- Keine Deployments, Host-Hotfixes oder Docker-Schreibbefehle ohne ausdrueckliche Anweisung.
|
||||||
|
- Doku-Regeln aus `docs/REPO_MAP.md` einhalten: ein Fakt, ein Zuhause. Status nur in `docs/MASTER_TODO.md`, Entscheidungen nur in `docs/DECISIONS.md`.
|
||||||
|
- Bei Drift oder zwei fehlgeschlagenen Reparaturversuchen: stoppen, `docs/GITOPS_DRIFT_RUNBOOK.md`.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Claude Code Context - Homelab Infra
|
# Claude Code Context - Homelab Infra
|
||||||
|
|
||||||
Stand: 2026-05-04
|
Stand: 2026-06-11
|
||||||
|
|
||||||
Dieses Repository ist die GitOps-Quelle fuer das KalliLab CORE Homelab auf einem Unraid-Host. Es verwaltet Docker-Compose-Stacks fuer Core-Dienste, Security, Infrastruktur, Apps, Operations-Tools, Host-nahe Dienste und Traefik. Gitea Online ist die operative Quelle der Wahrheit; Komodo konsumiert den Git-Stand und deployed daraus.
|
Dieses Repository ist die GitOps-Quelle fuer das KalliLab CORE Homelab auf einem Unraid-Host. Es verwaltet Docker-Compose-Stacks fuer Core-Dienste, Security, Infrastruktur, Apps, Operations-Tools, Host-nahe Dienste und Traefik. Gitea Online ist die operative Quelle der Wahrheit; Komodo konsumiert den Git-Stand und deployed daraus.
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ Zusaetzlich je nach Thema:
|
|||||||
- Secrets: `docs/SECRETS_MAP.md`
|
- Secrets: `docs/SECRETS_MAP.md`
|
||||||
- GitOps-/Komodo-/Runtime-Drift: `docs/GITOPS_DRIFT_RUNBOOK.md`
|
- GitOps-/Komodo-/Runtime-Drift: `docs/GITOPS_DRIFT_RUNBOOK.md`
|
||||||
- Gesamtbild fuer KI-Agenten: `docs/AI_CONTEXT.md`
|
- Gesamtbild fuer KI-Agenten: `docs/AI_CONTEXT.md`
|
||||||
- Home Assistant / Ecowitt / InfluxDB: `docs/HOME_ASSISTANT_INFLUXDB_ECOWITT.md`
|
- Architektur-/Betriebsentscheidungen mit Begruendung: `docs/DECISIONS.md`
|
||||||
|
|
||||||
## Projektbeschreibung
|
## Projektbeschreibung
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ Wenn Drift vermutet wird, nicht raten. Erst die Pflichtmatrix in `docs/GITOPS_DR
|
|||||||
- `traefik`: Host-Ports 80/443
|
- `traefik`: Host-Ports 80/443
|
||||||
- `gitea`: SSH-Port 222
|
- `gitea`: SSH-Port 222
|
||||||
- `AdGuard Home`: DNS-Port 53 und LAN-Admin-Port 8082
|
- `AdGuard Home`: DNS-Port 53 und LAN-Admin-Port 8082
|
||||||
- `tailscale`: `network_mode: host`
|
- `tailscale`: natives Unraid-Plugin (`tailscale.plg`, Interface `tailscale1`), Subnet-Router fuers LAN; nicht repo-/Komodo-verwaltet. Der frueher repo-verwaltete userspace-Docker-Stack `host-services/tailscale/` wurde am 2026-06-06 entfernt.
|
||||||
- `Plex-Media-Server`: historischer Host-Netz-Sonderfall, nicht als Repo-Stack enthalten
|
- `Plex-Media-Server`: historischer Host-Netz-Sonderfall, nicht als Repo-Stack enthalten
|
||||||
- `scrutiny`: `privileged: true` fuer SMART/Laufwerkszugriff
|
- `scrutiny`: `privileged: true` fuer SMART/Laufwerkszugriff
|
||||||
- `Komodo`: Docker-Socket und native Auth ohne pauschale ForwardAuth
|
- `Komodo`: Docker-Socket und native Auth ohne pauschale ForwardAuth
|
||||||
@@ -123,6 +123,7 @@ Standard-Rollback ist ein Ruecknahme-Commit oder gezielte Rueckaenderung mit Pus
|
|||||||
## Arbeitsweise fuer Claude
|
## Arbeitsweise fuer Claude
|
||||||
|
|
||||||
- Erst lesen, dann handeln.
|
- Erst lesen, dann handeln.
|
||||||
|
- Doku-Regeln aus `docs/REPO_MAP.md` einhalten: ein Fakt, ein Zuhause. Status nur in `docs/MASTER_TODO.md`, Entscheidungen nur in `docs/DECISIONS.md`, Erledigtes verlaesst die Arbeitskopie.
|
||||||
- Bei Unsicherheit Zustand messen, nicht erraten.
|
- Bei Unsicherheit Zustand messen, nicht erraten.
|
||||||
- Aenderungen klein halten und nur den betroffenen Bereich anfassen.
|
- Aenderungen klein halten und nur den betroffenen Bereich anfassen.
|
||||||
- Bestehende Doku und Repo-Konventionen bevorzugen.
|
- Bestehende Doku und Repo-Konventionen bevorzugen.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
> **Single Source of Truth** für Docker-Netzwerkarchitektur, Sicherheitsregeln, Zielbild und Migration des Kallilabcore-Homelabs.
|
> **Single Source of Truth** für Docker-Netzwerkarchitektur, Sicherheitsregeln, Zielbild und Migration des Kallilabcore-Homelabs.
|
||||||
> **Arbeitsregel für KI-Assistenten:** Dieses Dokument immer zuerst lesen, bevor Fragen zu Containern, Netzwerken, Traefik, Tailscale, Migration oder Security beantwortet werden.
|
> **Arbeitsregel für KI-Assistenten:** Dieses Dokument immer zuerst lesen, bevor Fragen zu Containern, Netzwerken, Traefik, Tailscale, Migration oder Security beantwortet werden.
|
||||||
|
|
||||||
**Stand:** 2026-06-02 | **Aktueller Schwerpunkt:** GitOps / Doku-Synchronisierung / Reproduzierbare Deployments
|
**Stand:** 2026-06-13 | **Aktueller Schwerpunkt:** Home Assistant Tibber / Energie-Kosten
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
10. [Bekannte Ausnahmen und Begründungen](#10-bekannte-ausnahmen-und-begründungen)
|
10. [Bekannte Ausnahmen und Begründungen](#10-bekannte-ausnahmen-und-begründungen)
|
||||||
11. [Projektorganisation und Arbeitsmodus](#11-projektorganisation-und-arbeitsmodus)
|
11. [Projektorganisation und Arbeitsmodus](#11-projektorganisation-und-arbeitsmodus)
|
||||||
12. [Nutzung mit KI / Kontext-Regel](#12-nutzung-mit-ki--kontext-regel)
|
12. [Nutzung mit KI / Kontext-Regel](#12-nutzung-mit-ki--kontext-regel)
|
||||||
13. [Betriebserfahrungen und Entscheidungs-Log](#13-betriebserfahrungen-und-entscheidungs-log)
|
13. [Betriebserfahrungen und Entscheidungs-Log (ausgelagert)](#13-betriebserfahrungen-und-entscheidungs-log-ausgelagert)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -88,11 +88,13 @@ Jeder produktive Container nutzt `restart: unless-stopped`, außer eine Ausnahme
|
|||||||
| `backend_net` | bridge, `internal: true` | interne App-/DB-/Cache-Kommunikation | Standard |
|
| `backend_net` | bridge, `internal: true` | interne App-/DB-/Cache-Kommunikation | Standard |
|
||||||
| `dns_net` | bridge | Resolver-Schicht: AdGuard Home + Unbound | bleibt |
|
| `dns_net` | bridge | Resolver-Schicht: AdGuard Home + Unbound | bleibt |
|
||||||
| `mealie_internal` | bridge, `internal: true` | internes Netz nur für `mealie` + `mealie-postgres` | ✅ umgesetzt |
|
| `mealie_internal` | bridge, `internal: true` | internes Netz nur für `mealie` + `mealie-postgres` | ✅ umgesetzt |
|
||||||
| `immich_default` | Compose-intern, `internal: true` | internes Immich-Netz | ✅ umgesetzt |
|
| `immich_default` | Compose-intern, `internal: true` | internes Immich-Netz (Server, Postgres, Redis, ML) | ✅ umgesetzt |
|
||||||
|
| `immich_egress` | Compose-intern, bridge (nicht `internal`) | Outbound-only fuer `immich_machine_learning` (Modell-Download huggingface) | ✅ umgesetzt |
|
||||||
| `nextcloud_internal` | bridge, `internal: true` | internes Netz nur fuer `nextcloud` + `nextcloud-postgres` + `nextcloud-redis` | ✅ vorbereitet |
|
| `nextcloud_internal` | bridge, `internal: true` | internes Netz nur fuer `nextcloud` + `nextcloud-postgres` + `nextcloud-redis` | ✅ vorbereitet |
|
||||||
| `monitoring_net` | Compose-intern, bridge | zentraler Observability-Stack fuer Prometheus, Loki, Grafana, Promtail, Exporter und InfluxDB | Zielzustand |
|
| `monitoring_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 |
|
| `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 |
|
| `glance_socket_net` | Compose-intern, `internal: true` | interner Zugriff von Glance auf den Docker-Socket-Proxy | umgesetzt |
|
||||||
|
| `smarthome_net` | bridge, `internal: true` | interne Smart-Home-Kommunikation zwischen Home Assistant, Mosquitto, spaeter Zigbee2MQTT/ESPHome | vorbereitet |
|
||||||
| `host` | host | nur für echte Sonderfälle | begründet |
|
| `host` | host | nur für echte Sonderfälle | begründet |
|
||||||
|
|
||||||
### 3.2 Finales Diagramm (vereinfacht)
|
### 3.2 Finales Diagramm (vereinfacht)
|
||||||
@@ -123,7 +125,8 @@ App-interne Netze
|
|||||||
├── immich_default (internal: true) ✅
|
├── immich_default (internal: true) ✅
|
||||||
├── nextcloud_internal (internal: true) ✅
|
├── nextcloud_internal (internal: true) ✅
|
||||||
├── monitoring_net (zentraler Observability-Stack)
|
├── monitoring_net (zentraler Observability-Stack)
|
||||||
└── monitoring_influx_lan (Bridge fuer LAN-Port-Publishing, keine Traefik-Route)
|
├── monitoring_influx_lan (Bridge fuer LAN-Port-Publishing, keine Traefik-Route)
|
||||||
|
└── smarthome_net (HA, Mosquitto, spaeter Zigbee2MQTT/ESPHome)
|
||||||
|
|
||||||
Host-Sonderfälle
|
Host-Sonderfälle
|
||||||
├── tailscale
|
├── tailscale
|
||||||
@@ -145,6 +148,8 @@ Diese Dienste sind über echte `*.kaleschke.info`-Domains erreichbar:
|
|||||||
- `gitea` (Web) — git.kaleschke.info
|
- `gitea` (Web) — git.kaleschke.info
|
||||||
- `immich_server` — immich.kaleschke.info
|
- `immich_server` — immich.kaleschke.info
|
||||||
- `nextcloud` — cloud.kaleschke.info
|
- `nextcloud` — cloud.kaleschke.info
|
||||||
|
- `plex` — plex.kaleschke.info (Traefik, native Plex-Auth; Plex Remote Access/Port 32400 bleibt aus)
|
||||||
|
- `homeassistant` — home.kaleschke.info (Traefik, native Home-Assistant-Auth)
|
||||||
|
|
||||||
### 4.2 Nicht öffentlich / nur Tailscale oder Traefik + Middleware
|
### 4.2 Nicht öffentlich / nur Tailscale oder Traefik + Middleware
|
||||||
Diese Dienste sind **keine Public Apps**:
|
Diese Dienste sind **keine Public Apps**:
|
||||||
@@ -240,7 +245,7 @@ Legende Status:
|
|||||||
| `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 |
|
| `AdGuard Home` | ✅ | `dns_net` (172.23.0.3), `frontend_net` | Port 53 DNS direkt, Port 8082 Admin nur auf Tailscale-IP `100.80.98.33` | DNS-Server + Upstream zu unbound; kein Traefik fuer Admin-UI | Admin-Port bleibt bewusst ohne Traefik/2FA, aber nicht mehr auf allen LAN-Interfaces |
|
||||||
| `unbound` | ✅ | `dns_net` | intern | Upstream-Resolver für AdGuard, isoliert | — |
|
| `unbound` | ✅ | `dns_net` | intern | Upstream-Resolver für AdGuard, isoliert | — |
|
||||||
| `ddns-updater` | ✅ | `frontend_net` | intern | Cloudflare DNS API; bleibt in `frontend_net` | Dokumentierte Ausnahme |
|
| `ddns-updater` | ✅ | `frontend_net` | intern | Cloudflare DNS API; bleibt in `frontend_net` | Dokumentierte Ausnahme |
|
||||||
| `tailscale` | ✅ | `host` | VPN-Zugang | Git-Stack (`host-services/tailscale/`) | nutzt `NET_ADMIN`, `NET_RAW` und `/dev/net/tun` als dokumentierte VPN-Ausnahme |
|
| `tailscale` | ✅ | `host` | VPN-Zugang / Subnet-Router | **Natives Unraid-Plugin** (`tailscale.plg`, Interface `tailscale1`, State `/boot/config/plugins/tailscale/state`) — **nicht** repo-/Komodo-verwaltet | Subnet-Router fuer `192.168.178.0/24`; der redundante userspace-Docker-Stack `host-services/tailscale/` wurde am 2026-06-06 entfernt |
|
||||||
|
|
||||||
### 7.2 Sicherheit / Identity
|
### 7.2 Sicherheit / Identity
|
||||||
|
|
||||||
@@ -260,6 +265,7 @@ Legende Status:
|
|||||||
| `immich_redis` | ⏳ | `immich_default` | intern | intern-only | anonymes Volume → named volume |
|
| `immich_redis` | ⏳ | `immich_default` | intern | intern-only | anonymes Volume → named volume |
|
||||||
| `nextcloud-postgres` | ✅ | `nextcloud_internal` | intern | app-eigene Nextcloud-Datenbank mit `_FILE`-Secret | — |
|
| `nextcloud-postgres` | ✅ | `nextcloud_internal` | intern | app-eigene Nextcloud-Datenbank mit `_FILE`-Secret | — |
|
||||||
| `nextcloud-redis` | ✅ | `nextcloud_internal` | intern | app-eigener Cache fuer File Locking / Sessions | — |
|
| `nextcloud-redis` | ✅ | `nextcloud_internal` | intern | app-eigener Cache fuer File Locking / Sessions | — |
|
||||||
|
| `smarthome-mosquitto` | ✅ | `smarthome_net` | intern `1883`, kein Host-Port in Phase 1 | MQTT-Datenbus fuer Home Assistant, spaeter ESPHome und Zigbee2MQTT; Passwortdatei und ACLs in `/mnt/user/appdata/mosquitto/config`; MQTT-Smoke und HA-MQTT-Integration am 2026-06-13 erfolgreich | LAN-Port erst in ESPHome-Phase mit ACLs/per-Device-Usern |
|
||||||
|
|
||||||
### 7.4 Produktive Apps
|
### 7.4 Produktive Apps
|
||||||
|
|
||||||
@@ -271,9 +277,10 @@ Legende Status:
|
|||||||
| `ntfy` | ✅ | `frontend_net` | Traefik | aktiv via `ntfy.kaleschke.info`, Git-Stack | — |
|
| `ntfy` | ✅ | `frontend_net` | Traefik | aktiv via `ntfy.kaleschke.info`, Git-Stack | — |
|
||||||
| `gitea` | ✅ | `frontend_net` | Traefik + SSH-Port 222 | Web via Traefik, SSH direkt gebunden | — |
|
| `gitea` | ✅ | `frontend_net` | Traefik + SSH-Port 222 | Web via Traefik, SSH direkt gebunden | — |
|
||||||
| `immich_server` | ✅ | `immich_default`, `frontend_net` | Traefik | aktiv via `immich.kaleschke.info` | — |
|
| `immich_server` | ✅ | `immich_default`, `frontend_net` | Traefik | aktiv via `immich.kaleschke.info` | — |
|
||||||
| `immich_machine_learning` | ✅ | `immich_default` | intern | bleibt intern | — |
|
| `immich_machine_learning` | ✅ | `immich_default`, `immich_egress` | intern (keine Traefik-Route) | `immich_default` fuer Server-Erreichbarkeit + dediziertes `immich_egress` (nicht-internal) fuer einmaligen Modell-Download (CLIP/buffalo_l) nach `model-cache`; bewusst nicht `frontend_net`, da unauth. ML-API | — |
|
||||||
| `nextcloud` | ✅ | `frontend_net`, `nextcloud_internal` | Traefik | aktiv via `cloud.kaleschke.info`, nativer Nextcloud-Login, WebDAV/CardDAV faehig | CalDAV/CardDAV-Redirect via Traefik-Labels |
|
| `nextcloud` | ✅ | `frontend_net`, `nextcloud_internal` | Traefik | aktiv via `cloud.kaleschke.info`, nativer Nextcloud-Login, WebDAV/CardDAV faehig | CalDAV/CardDAV-Redirect via Traefik-Labels |
|
||||||
| `plex` | ✅ | `host` | Plex native, **LAN/Tailscale-only** (Remote Access aus seit 2026-05-28) | Compose-Stack unter `host-services/plex/`; Host-Netz bleibt fuer Discovery / Plex GDM dokumentierte Ausnahme; Server geclaimt von `Xeridos`; Smart-TVs (Schlafzimmer, Wohnzimmer) ueber WLAN-LAN per mDNS | — |
|
| `homeassistant` | ✅ | `frontend_net`, `smarthome_net` | Traefik via `home.kaleschke.info`, native HA-Auth | Home Assistant Container im GitOps-Stack `smart-home/`; kein HAOS, kein Supervised; Fach-YAML kommt aus `smart-home-kalli`, `.storage` bleibt in `/mnt/user/appdata/homeassistant`; Komodo-Stack und Gitea-Webhook aktiv; HA-native Backup-Erzeugung, Restore-Probe, HA-MQTT-Integration, SolarEdge Local und Energy Dashboard am 2026-06-13 erfolgreich | Tibber, Energie-Kosten, spaeter Energie-Automationen |
|
||||||
|
| `plex` | ✅ | `host` | Traefik via `plex.kaleschke.info` + Plex native Auth; LAN direkt `:32400` | Compose-Stack unter `host-services/plex/`; Host-Netz bleibt fuer Discovery / Plex GDM dokumentierte Ausnahme; Traefik routet per File-Provider-Ausnahme auf `http://192.168.178.58:32400`, weil Docker-Labels Host-Netz-Container aus Traefik heraus auf `127.0.0.1` routen wuerden; kein direkter WAN-Port 32400 und Plex Remote Access bleibt aus; Server geclaimt von `Xeridos`; Smart-TVs (Schlafzimmer, Wohnzimmer) ueber WLAN-LAN per mDNS | — |
|
||||||
| `super-productivity` | ✅ vorbereitet | `frontend_net` | Traefik + Middleware | Persoenliche Task-PWA des Operators; Issues kommen aus Gitea `Micha/mails` via n8n-Mail-Workflow | Deploy + Webhook + DNS-Eintrag offen |
|
| `super-productivity` | ✅ vorbereitet | `frontend_net` | Traefik + Middleware | Persoenliche Task-PWA des Operators; Issues kommen aus Gitea `Micha/mails` via n8n-Mail-Workflow | Deploy + Webhook + DNS-Eintrag offen |
|
||||||
| `n8n` | ✅ vorbereitet | `frontend_net` | Traefik, native Auth (keine pauschale Authelia) | Workflow-Automation; erster Workflow: GMX-Mail -> OpenAI-Extraktion -> Gitea-Issue in `Micha/mails`; `N8N_ENCRYPTION_KEY` ist Stack-ENV-Pflichtsecret | Deploy + Webhook + Owner-Setup offen |
|
| `n8n` | ✅ vorbereitet | `frontend_net` | Traefik, native Auth (keine pauschale Authelia) | Workflow-Automation; erster Workflow: GMX-Mail -> OpenAI-Extraktion -> Gitea-Issue in `Micha/mails`; `N8N_ENCRYPTION_KEY` ist Stack-ENV-Pflichtsecret | Deploy + Webhook + Owner-Setup offen |
|
||||||
|
|
||||||
@@ -308,7 +315,7 @@ Legende Status:
|
|||||||
|
|
||||||
| Container | Status | Ziel |
|
| Container | Status | Ziel |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| — | — | Plex ist nicht mehr offen: der Dienst ist als Repo-Compose-Stack unter `host-services/plex/` dokumentiert; `host`-Netz bleibt als Discovery-Ausnahme. |
|
| — | — | Plex ist nicht mehr direkt offen: der Dienst ist als Repo-Compose-Stack unter `host-services/plex/` dokumentiert; `host`-Netz bleibt als Discovery-Ausnahme. Externer Zugriff laeuft ausschliesslich ueber Traefik/443 auf `plex.kaleschke.info`; keine direkte 32400-WAN-Freigabe. Technisch nutzt Plex als einzige Host-Netz-Route `traefik/dynamic/plex.yml`, weil Docker-Labels fuer `network_mode: host` in Traefik auf `127.0.0.1:32400` zeigen. |
|
||||||
|
|
||||||
### 7.8 Entfernte Container
|
### 7.8 Entfernte Container
|
||||||
|
|
||||||
@@ -370,23 +377,7 @@ labels:
|
|||||||
|
|
||||||
## 9. Historische Migration (abgeschlossen)
|
## 9. Historische Migration (abgeschlossen)
|
||||||
|
|
||||||
Die frühere Blockmigration aus der Portainer-/Dockerman-Phase ist fachlich abgeschlossen.
|
Die Blockmigration aus der Portainer-/Dockerman-Phase ist abgeschlossen: Traefik laeuft labelbasiert ohne File-Provider-Service-Routen, Komodo ist alleiniger Stack-Manager, Portainer CE ist entfernt, Borg/Dumps/Restore-Tests sind produktiv. Entscheidungen und Hintergruende stehen in `docs/DECISIONS.md`; die Sprint-Historie liegt in Git.
|
||||||
|
|
||||||
Dieser Abschnitt dient nur noch als **historischer Vermerk**:
|
|
||||||
|
|
||||||
- Traefik läuft labelbasiert ohne Service-Routen im File-Provider.
|
|
||||||
- Komodo ist der einzige aktive Stack-Manager.
|
|
||||||
- Portainer CE ist entfernt.
|
|
||||||
- Borg/Borg UI, Dump-Automatisierung und Restore-Test sind produktiv eingeführt.
|
|
||||||
- Frühere Sprint-/Block-Checklisten werden hier **nicht mehr operativ gepflegt**.
|
|
||||||
|
|
||||||
Für den laufenden Betrieb gilt stattdessen:
|
|
||||||
|
|
||||||
- Zielbild und Architektur in diesem Dokument
|
|
||||||
- Git-/Komodo-Ablauf in `docs/WORKFLOW.md`
|
|
||||||
- fachliche Änderungen in der jeweils betroffenen Stack-Doku
|
|
||||||
- Entscheidungen und besondere Umstellungen im Entscheidungs-Log unten
|
|
||||||
|
|
||||||
## 10. Bekannte Ausnahmen und Begründungen
|
## 10. Bekannte Ausnahmen und Begründungen
|
||||||
|
|
||||||
| Container | Ausnahme | Begründung |
|
| Container | Ausnahme | Begründung |
|
||||||
@@ -404,9 +395,13 @@ Für den laufenden Betrieb gilt stattdessen:
|
|||||||
| `mail-archiver` | `frontend_net` + `backend_net` | braucht Internetzugang für IMAP-Abruf (GMX, Gmail) und DB-Zugang |
|
| `mail-archiver` | `frontend_net` + `backend_net` | braucht Internetzugang für IMAP-Abruf (GMX, Gmail) und DB-Zugang |
|
||||||
| `traefik/dynamic/*` | manueller Host-Sync trotz GitOps | File-Provider bleibt bewusst fuer `middlewares.yml`, `tls.yml` und `dashboards.yml`; Komodo deployed diese Dateien nicht automatisch |
|
| `traefik/dynamic/*` | manueller Host-Sync trotz GitOps | File-Provider bleibt bewusst fuer `middlewares.yml`, `tls.yml` und `dashboards.yml`; Komodo deployed diese Dateien nicht automatisch |
|
||||||
| `nextcloud` | keine zentrale ForwardAuth-Middleware | Nextcloud bringt eigene Auth, Clients und WebDAV/CardDAV-Endpunkte mit; Traefik bleibt Reverse Proxy, Auth bleibt app-nativ |
|
| `nextcloud` | keine zentrale ForwardAuth-Middleware | Nextcloud bringt eigene Auth, Clients und WebDAV/CardDAV-Endpunkte mit; Traefik bleibt Reverse Proxy, Auth bleibt app-nativ |
|
||||||
| `monitoring-influxdb3-core` | Host-Port 8181 auf LAN-IP; `user: "0"` | Home Assistant laeuft in einer VM ausserhalb des Compose-Netzes und muss Metriken schreiben koennen; keine Traefik-Route, kein `frontend_net`, Zugriff nur ueber Token und LAN-IP `INFLUXDB_BIND_IP`; InfluxDB 3 Core benoetigt im aktuellen Container-Setup Root-Rechte fuer den lokalen Object-Store-Pfad im named volume |
|
| `monitoring-influxdb3-core` | Host-Port 8181 auf LAN-IP; `user: "0"` | Home Assistant schreibt spaeter Langzeitdaten. Nach der HA-Container-Entscheidung muss der Writer-Pfad in der Influx-Phase explizit gewaehlt werden: entweder LAN-Bind via `INFLUXDB_BIND_IP` oder gezieltes gemeinsames internes Netz. Keine Traefik-Route, Zugriff nur ueber Token; 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 |
|
| `monitoring-promtail` | Docker-Socket read-only | Docker-Log-Discovery fuer Loki; keine Schreibrechte, keine Appdaten-Persistenz ueber den Socket |
|
||||||
| `n8n` | keine pauschale Authelia-Middleware | Webhook-Endpunkte (`/webhook/*`, `/webhook-test/*`) muessen ohne ForwardAuth erreichbar bleiben; n8n bringt eigene Owner-/Login-Auth mit (analog Komodo/Nextcloud) |
|
| `n8n` | keine pauschale Authelia-Middleware | Webhook-Endpunkte (`/webhook/*`, `/webhook-test/*`) muessen ohne ForwardAuth erreichbar bleiben; n8n bringt eigene Owner-/Login-Auth mit (analog Komodo/Nextcloud) |
|
||||||
|
| `plex` | Traefik ohne Authelia, File-Provider-Ausnahme trotz Host-Netz | Plex bringt native Konto-/Client-Auth mit; vorgeschaltete ForwardAuth wuerde Plex Web, Apps und Client-Flows stoeren. Docker-Labels sind fuer diesen Host-Netz-Container ungeeignet, weil Traefik sonst `127.0.0.1:32400` nutzt; daher `traefik/dynamic/plex.yml` mit Ziel `192.168.178.58:32400`. Route nur ueber Traefik/443 (`plex.kaleschke.info`), direkter Plex-WAN-Port 32400 und Plex Remote Access bleiben deaktiviert. |
|
||||||
|
| `homeassistant` | Traefik ohne Authelia, Fach-YAML aus separatem Repo | Home Assistant bringt eigene Auth, mobile Apps, Webhooks und Integrationsfluesse mit. Der Container haengt in `frontend_net` fuer Traefik und in `smarthome_net` fuer MQTT/Zigbee2MQTT/ESPHome. `.storage` und Secrets bleiben in Appdata und werden per Borg gesichert, nicht versioniert. |
|
||||||
|
| `homeassistant` (Ecowitt) | LAN-only Host-Port `8123` auf `192.168.178.58` | Ecowitt-GW3000 kann kein HTTPS und pusht per HTTP an den HA-Webhook. HA bekommt einen Host-Bind nur auf der LAN-IP (`192.168.178.58:8123:8123`, nicht `0.0.0.0`/WAN), analog InfluxDB 8181. Kein Traefik-Umbau des globalen HTTP-Redirects noetig, da Ecowitt rein im LAN pusht. Webhook nicht `local_only`, geschuetzt durch 128-bit-Zufalls-ID. Siehe `docs/DECISIONS.md` (2026-06-13). |
|
||||||
|
| `homeassistant` (SolarEdge Local) | HACS/Custom-Integration `solaredge_modbus_multi` | Lokaler SolarEdge-Zugriff laeuft ueber Modbus TCP `192.168.178.111:1502`, Device-ID `1`. Das ist bewusst lokal statt Cloud-API, weil kein SolarEdge-API-Key verfuegbar ist und der Wechselrichter Modbus-Daten fuer Inverter, Smart Meter und Batterie liefert. Custom-Integration-Warnungen bei HA-Core-Upgrades beachten. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -462,175 +457,15 @@ Damit ist sofort klar:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 13. Betriebserfahrungen und Entscheidungs-Log
|
## 13. Betriebserfahrungen und Entscheidungs-Log (ausgelagert)
|
||||||
|
|
||||||
### Fix Common Problems Plugin entfernt (2026-06-03)
|
Architektur- und Betriebsentscheidungen werden seit 2026-06-11 zentral in
|
||||||
|
`docs/DECISIONS.md` gefuehrt (ADR-light: Entscheidung, Kontext, Review-Trigger).
|
||||||
Befund: Drei `grep -R ... /usr/local/emhttp`-Prozesse liefen seit ~7 Tagen durchgehend mit je 100 % CPU (TIME+ 177-179 h). Status `R`, von PID 1 adoptierte Zombies einer laengst beendeten Fix-Common-Problems-(FCP)-Scan-Session. Folge: konstante Load 14.6 auf 12 Cores, IOWAIT-Peaks bis 55 %, USB-Flash unter Dauer-IO.
|
Dieses Dokument haelt nur noch das Zielbild. Neue Entscheidungen werden dort
|
||||||
|
eingetragen; hier aendert sich nur etwas, wenn das Zielbild selbst betroffen
|
||||||
Ursache: Unraids `/usr/local/emhttp` enthaelt Symlinks `mnt -> /mnt` (mehrere TB Array) und `boot -> /boot` (USB-Flash). GNU `grep -R` dereferenziert Symlinks rekursiv. Ein FCP-Scan-Schritt (`/etc/cron.daily/fix.common.problems.sh -> scripts/scan.php`) hat dadurch effektiv die gesamte Array-Struktur gegrept und ist beim ersten Treffer-Loop haengen geblieben. Der Lock `/tmp/fix.common.problems/scanRunning` war vom 2026-06-03 04:40 - jeder weitere Daily-Cron-Run wuerde dasselbe Verhalten reproduzieren.
|
ist (Netze, Zugangsmodell, Ausnahmen in Sektion 10).
|
||||||
|
|
||||||
Massnahme: FCP-Plugin per `plugin remove fix.common.problems.plg` deinstalliert. Cron-Eintrag, Plugin-Verzeichnis und `/tmp`-Reste sauber. Load fiel innerhalb Minuten auf 1.08 (1-min).
|
|
||||||
|
|
||||||
Entscheidung: FCP wird bewusst **nicht** wieder installiert. Begruendung:
|
|
||||||
|
|
||||||
- Restliche Risiken werden bereits ueber andere Wege abgedeckt: Scrutiny (Laufwerks-SMART), Monitoring-Stack (Container-Health, Prometheus-Alerts, Blackbox), Posture-Check (Filesystem-/Drift-/Authelia-Audit), Critical-Events-Watcher (`services/posture-check/docker-critical-events.sh`).
|
|
||||||
- FCP ist ein externes Community-Plugin und nicht Teil der Repo-managed GitOps-Welt; Verhalten haengt von einer Online-Templates-Datei ab.
|
|
||||||
- Ein einmaliges Hang-up reicht, um die Flash-Drive 7 Tage lang zu thrashen - das Verhaeltnis Nutzen/Risiko ist negativ.
|
|
||||||
|
|
||||||
Folgen fuer Doku: Eintrag in `docs/AUDIT_2026-05-25_TODO.md` unter "Zuletzt geschlossen"; FCP taucht nicht mehr als Voraussetzung in DR/Monitoring-Pfaden auf, da es nie produktiv referenziert war.
|
|
||||||
|
|
||||||
### Plex Server Reclaim und LAN-only-Profil (2026-05-28)
|
|
||||||
|
|
||||||
Befund: Die `Preferences.xml` des Plex-Servers war seit dem 18.05.2026 13:18 jungfraeulich (391 Bytes, ohne `PlexOnlineMail`/`PlexOnlineUsername`/`PlexOnlineToken`). Der Server war damit nicht mit einem Plex.tv-Account geclaimt, obwohl die Smart-TVs ueber LAN-Discovery (mDNS/Plex-GDM) weiter funktionierten. Beim Login als `Xeridos` ueber `app.plex.tv` meldete der Server "Keine Berechtigung", weil kein Owner registriert war. Zusaetzlich war die `library_sections`-Konfiguration leer (Backups vom 19./22./28.05. ebenfalls ~370 KB statt MBs/GBs); die Bibliotheks-Konfiguration war seit dem 18.05. weg, die Filmdateien unter `/mnt/user/media/*` blieben aber intakt (~833 Verzeichnisse, davon `movies/` 1.4 TB und `Heimatfilme/` 300 GB).
|
|
||||||
|
|
||||||
Reclaim:
|
|
||||||
|
|
||||||
- Operator-Claim-Token via `https://www.plex.tv/claim` als `Xeridos` erzeugt.
|
|
||||||
- Plex-Container per `PLEX_CLAIM=claim-... docker compose up -d --force-recreate plex` am Host-Pfad `/mnt/user/services/stacks/plex/host-services/plex` neu erstellt. Token wurde **nur** als Shell-Inline-ENV mitgegeben, **nicht** in eine `.env`-Datei, **nicht** in die Compose, **nicht** in die Komodo-Stack-ENV geschrieben.
|
|
||||||
- Nach Erfolg: zweiter `docker compose up -d --force-recreate plex` ohne `PLEX_CLAIM`, damit der verbrauchte Token nicht im `docker inspect`-ENV-Snapshot persistiert.
|
|
||||||
- Bash-History defensiv geleert.
|
|
||||||
|
|
||||||
Endstand:
|
|
||||||
|
|
||||||
- `PlexOnlineUsername="Xeridos"`, `PlexOnlineMail="michideheld@gmx.de"`, `PlexOnlineHome="1"`.
|
|
||||||
- Bibliotheken neu angelegt via Plex-Web → Verwalte Mediatheken → `/data/movies`, `/data/Heimatfilme` etc.
|
|
||||||
- `PublishServerOnPlexOnlineKey="0"` (Remote Access deaktiviert), Plex-Relay aus → Plex bleibt strikt LAN/Tailscale-only, konsistent zum Tailscale-First-Operator-Modell.
|
|
||||||
|
|
||||||
Konsequenzen fuer Doku/Betrieb:
|
|
||||||
|
|
||||||
- Plex-Home-Familien-Profil ("Familie") muss bei Bedarf neu eingeladen werden; war ohnehin nicht aktiv genutzt.
|
|
||||||
- Watch-State aus der Zeit vor dem 18.05. ist nicht recoverbar; Filme/Serien laufen bei Wiederaufruf bei 00:00 los.
|
|
||||||
- `host-services/plex/docker-compose.yml` enthaelt weiter `PLEX_CLAIM: ${PLEX_CLAIM:-}`, damit ein zukuenftiger Reclaim ohne Repo-Aenderung moeglich ist.
|
|
||||||
|
|
||||||
### Traefik — Wechsel zu reinen Docker-Labels (2026-03-28)
|
|
||||||
Die statischen File-Provider-Konfigurationen in `/mnt/user/appdata/traefik/dynamic/` wurden vollständig bereinigt:
|
|
||||||
- **Gelöscht:** `immich.yml`, `gitea.yml`, `mealie.yml`, `scrutiny.yml`, `vaultwarden.yml.bak`
|
|
||||||
- **Verbleibend (notwendig):** `middlewares.yml`, `tls.yml`, `dashboards.yml`
|
|
||||||
|
|
||||||
**Hintergrund:** Die alten File-Provider-Configs haben `@file`-Routen mit `@docker`-Routen konkurrieren lassen. In Traefik v3 gewinnt der File-Provider und hat z.B. Immich auf die falsche IP geroutet (Bad Gateway). Nach Löschung läuft Traefik ausschließlich auf Docker-Labels.
|
|
||||||
|
|
||||||
**Regel:** Neue Dienste ausschließlich via Docker Compose Labels konfigurieren. Keine neuen `.yml`-Dateien im `dynamic/`-Verzeichnis für Service-Routen anlegen.
|
|
||||||
|
|
||||||
### Komodo — Ablösung von Portainer als Stack-Manager (2026-03-28)
|
|
||||||
Komodo ist nun der primäre GitOps-Stack-Manager:
|
|
||||||
- **Komodo Core** läuft als Docker-Stack (`ops/komodo/docker-compose.yml`)
|
|
||||||
- **Komodo Periphery** läuft auf dem Unraid-Host für direktes Server-Management
|
|
||||||
- Stacks werden via Gitea synchronisiert und über Komodo deployed
|
|
||||||
- Portainer CE ist abgeschaltet; Komodo ist der alleinige aktive Stack-Manager
|
|
||||||
|
|
||||||
**Betriebsregel:** Alle Stack-Änderungen laufen über Git; Komodo konsumiert nur den Stand aus Gitea.
|
|
||||||
|
|
||||||
**Zugangsregel:** Komodo bleibt bewusst bei nativer Authentifizierung ohne pauschal vorgeschaltete ForwardAuth-Middleware vor dem gesamten Router. Hintergrund sind die gemischten UI-, API-, Webhook- und Periphery-Endpunkte unter derselben Domain.
|
|
||||||
|
|
||||||
### Komodo Self-Stack Drift-Recovery (2026-05-04)
|
|
||||||
- Befund: `komodo-core` und `komodo-periphery` liefen aus temporaeren `/tmp/*repair.yml`-Dateien, waehrend `komodo-mongo` auf den fehlenden persistenten Pfad `/mnt/user/services/stacks/komodo/compose.yaml` verwies.
|
|
||||||
- Recovery: Repair-YAMLs und Runtime-ENV wurden unter `/mnt/user/appdata/komodo/_drift_backup_2026-05-04/` gesichert; eine zusaetzliche Recovery-ENV liegt unter `/mnt/user/appdata/secrets/_komodo_stack_env_recovery_2026-05-04.env` und ist als temporaeres Tier-1-Secret-Material zu behandeln.
|
|
||||||
- Der persistente Self-Stack wurde unter `/mnt/user/services/stacks/komodo/compose.yaml` aus `ops/komodo/docker-compose.yml` wiederhergestellt. Die hostseitige `.env` bleibt ausserhalb von Git.
|
|
||||||
- Reconcile-Regel: Bei Self-Stack-Drift keinen pauschalen `docker compose up -d` ausfuehren, wenn der Dry-run `komodo-mongo` recreaten wuerde. Core und Periphery koennen gezielt mit `--no-deps` neu erstellt werden, Mongo bleibt dabei unangetastet.
|
|
||||||
- Ergebnis: Alle drei Komodo-Container zeigen wieder auf `/mnt/user/services/stacks/komodo/compose.yaml`; Mongo blieb waehrend der Rueckfuehrung healthy.
|
|
||||||
|
|
||||||
### AdGuard Home — Ablösung von Pi-hole (2026-03-28)
|
|
||||||
`binhex-official-pihole` wurde entfernt und durch `AdGuard Home` + `unbound` ersetzt:
|
|
||||||
- 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 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)
|
|
||||||
`diun` (Docker Image Update Notifier) wurde deinstalliert:
|
|
||||||
- Stack gelöscht
|
|
||||||
- Orphan-Netzwerk `diun_diun_default` bereinigt
|
|
||||||
- Repo-Eintrag `infra/diun/` aus Git entfernt
|
|
||||||
|
|
||||||
Update-Monitoring kann über Komodo's eingebaute Update-Notifications abgedeckt werden.
|
|
||||||
|
|
||||||
### ntfy — Push-Notifications (Git-Stack)
|
|
||||||
`ntfy` läuft als Git-Stack (`apps/ntfy/docker-compose.yml`):
|
|
||||||
- `ntfy.kaleschke.info` via Traefik
|
|
||||||
- `NTFY_UPSTREAM_BASE_URL: https://ntfy.sh` für mobile Push-Notifications
|
|
||||||
- `NTFY_BEHIND_PROXY: true` korrekt gesetzt
|
|
||||||
|
|
||||||
### immich_default — internal: true gesetzt (2026-03-29)
|
|
||||||
`immich_default` wurde von `external: true` auf ein Compose-verwaltetes internes Netz umgestellt:
|
|
||||||
- **Vorher:** `external: true` (manuell erstellt, falsche Labels `com.docker.compose.network=default`)
|
|
||||||
- **Nachher:** Compose-managed, `internal: true`, `driver: bridge`, korrekte Labels
|
|
||||||
- Durchgeführt via: manuelles `docker stop` der Containers → `docker network rm immich_default` → Komodo Redeploy
|
|
||||||
- Ergebnis: alle Immich-Container (`immich_postgres`, `immich_redis`, `immich_machine_learning`) sind jetzt vom Internet isoliert; nur `immich_server` hat zusätzlich `frontend_net` für Traefik
|
|
||||||
|
|
||||||
### Secrets in Komodo Stacks
|
|
||||||
Host-Pfade in `env_file` (z.B. `/mnt/...`) sind in Git-Stacks nicht verfügbar. Standardlösung: Stack Environment Variables + `${VARIABLE_NAME}` in der Compose.
|
|
||||||
|
|
||||||
**Regel:** Wenn `_FILE` nicht unterstützt wird → Stack Environment Variable. Kein Secret im Git.
|
|
||||||
|
|
||||||
**Bewusste Ausnahme:** `paperless-ngx` bleibt fuer `PAPERLESS_DBPASS` und `PAPERLESS_REDIS` vorerst bei Stack Environment Variables. Eine Umstellung auf `_FILE` ist fachlich denkbar, wird aber nicht gegen den aktuell stabilen Produktionsstand erzwungen.
|
|
||||||
|
|
||||||
### Borg UI / BorgBase (2026-04-12)
|
|
||||||
- `borg-ui` läuft als Admin-Dienst in `ops/borg-ui/docker-compose.yml`
|
|
||||||
- nur `frontend_net`, weil Web-UI + externer SSH-Zugang zu BorgBase benötigt werden
|
|
||||||
- keine direkten Host-Ports; Zugriff ausschließlich via Traefik + Middleware über `borg.kaleschke.info`
|
|
||||||
- breite Restore-/Backup-Mounts bewusst gesetzt; inklusive `/local/secrets` fuer Disaster Recovery, separates Restore-Ziel unter `/mnt/user/appdata/borg-ui/restore`
|
|
||||||
- kein separater Borg-CLI-Container nötig, da Borg UI die Borg-CLI bereits im Container mitbringt
|
|
||||||
|
|
||||||
| Container | `_FILE` Support |
|
|
||||||
|---|---|
|
|
||||||
| Vaultwarden | ✅ ja |
|
|
||||||
| PostgreSQL | ✅ ja |
|
|
||||||
| code-server | ✅ ja (`PASSWORD_FILE`) |
|
|
||||||
| Immich Postgres | ✅ ja (`POSTGRES_PASSWORD_FILE`) |
|
|
||||||
| Mealie | ✅ ja (`POSTGRES_PASSWORD_FILE`) |
|
|
||||||
| paperless-ngx | ❌ nein für DB-Pass → Stack ENV |
|
|
||||||
|
|
||||||
### 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, ergaenzt 2026-05-16)
|
|
||||||
- Tier-1/stateful Basisdienste werden bevorzugt mit sprechendem Minor-/Patch-Tag plus Digest gepinnt, z. B. `postgres:17.9@sha256:...` oder `mongo:7.0.32@sha256:...`.
|
|
||||||
- Redis-Caches sind seit dem Hardening-Sprint 2026-05-16 auf `redis:7.4-alpine@sha256:...` vereinheitlicht. Updates erfolgen bewusst stackweise mit Smoke-Test.
|
|
||||||
- Bereits versionierte Apps koennen optional spaeter ebenfalls Digests erhalten; dieser Schritt ist getrennt vom Datenhalter-Pinning.
|
|
||||||
|
|
||||||
### Nextcloud und Stirling-PDF (2026-04-19)
|
|
||||||
- `nextcloud` wird bewusst **nicht** als AIO-Stack gebaut, sondern als klassischer Docker-Microservice-Stack mit eigenem PostgreSQL und eigenem Redis. Das passt besser zum bestehenden GitOps-/Compose-Modell des Repos.
|
|
||||||
- `nextcloud` bleibt bei nativer App-Authentifizierung ohne zentrale ForwardAuth-Middleware vor dem Router, damit Browser-Login, Desktop-/Mobile-Clients sowie WebDAV/CardDAV sauber funktionieren.
|
|
||||||
- `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 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.
|
|
||||||
- `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.
|
|
||||||
- Die alten Pfade `ops/grafana-influxdb` und `ops/loki` wurden am 2026-05-26 aus dem aktiven Repo entfernt; `monitoring/` ist der einzige Observability-Zielstack.
|
|
||||||
- Uptime Kuma wurde nach erfolgreichem Blackbox-/Grafana-Smoke-Test entfernt; `monitoring/` ist die Quelle fuer HTTP-Erreichbarkeit und Alerts.
|
|
||||||
|
|
||||||
### Monitoring-Logging-Baseline (2026-05-17)
|
|
||||||
- `monitoring-loki` laeuft intern auf `monitoring_net`, ohne Traefik-Route und ohne Host-Port.
|
|
||||||
- `monitoring-promtail` sammelt Docker-Logs ueber `/var/run/docker.sock:ro` und `/var/lib/docker/containers:ro` und schreibt sie an Loki.
|
|
||||||
- `monitoring-grafana` bekommt provisionierte Datasources fuer Prometheus, Loki und InfluxDB 3 Core.
|
|
||||||
- Loki-Logdaten sind Diagnosematerial mit begrenzter Retention, keine primaere Restore-Quelle.
|
|
||||||
|
|
||||||
### Authelia ohne Redis-Session-Backend (2026-05-04)
|
|
||||||
- Authelia nutzt PostgreSQL fuer persistente Storage-Daten, aber bewusst kein Redis-Session-Backend.
|
|
||||||
- Das haelt den Tier-1-Auth-Pfad einfacher; nach einem Authelia-Restart muessen aktive Sessions neu aufgebaut werden.
|
|
||||||
- `infra/redis` ist historisch als "shared Cache" angelegt, wird aber faktisch nur von Paperless als App-Cache genutzt. Immich, Nextcloud und Mealie betreiben jeweils eigene Redis-Instanzen in ihren App-internen Netzen; Authelia laeuft bewusst ohne Redis. Eine spaetere Konsolidierung in `apps/paperless/` (analog zu Mealie/Immich/Nextcloud) bleibt fachlich denkbar, ist aber kein priorisierter Schritt.
|
|
||||||
|
|
||||||
### ddns-updater — Netz-Ausnahme
|
|
||||||
Bleibt bewusst in `frontend_net` statt `backend_net`, weil `backend_net` `internal: true` ist und ddns-updater die Cloudflare-API erreichen muss.
|
|
||||||
|
|
||||||
### mail-archiver — Hybrid-Dienst
|
|
||||||
Benötigt `backend_net` (PostgreSQL) + `frontend_net` (IMAP-Abruf von GMX/Gmail). Kein reiner Backend-Dienst. Die Web-UI ist via Traefik unter `mail.kaleschke.info` erreichbar und wird durch `authelia@file,secure-headers@file` plus App-eigene Auth geschuetzt.
|
|
||||||
|
|
||||||
### Netzwerk-Standard für Apps mit Datenbanken
|
|
||||||
- App → `frontend_net` + internes Netzwerk
|
|
||||||
- Datenbank → nur internes Netzwerk (`internal: true`)
|
|
||||||
|
|
||||||
Beispiel (Mealie): `mealie` → `frontend_net` + `mealie_internal`, `mealie-postgres` → nur `mealie_internal`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Schlussformel
|
## Schlussformel
|
||||||
|
|
||||||
Dieses Dokument ist keine lose Notiz, sondern das **operative Masterdokument** für die Docker- und Zugriffsarchitektur des Homelabs.
|
Dieses Dokument ist keine lose Notiz, sondern das **operative Masterdokument** für die Docker- und Zugriffsarchitektur des Homelabs.
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ Bei Hardware-, Netzwerk-, Provider- oder Kapazitaetsfragen zusaetzlich:
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
|
- Offene Punkte stehen ausschliesslich in `docs/MASTER_TODO.md`; Entscheidungen mit Begruendung in `docs/DECISIONS.md`.
|
||||||
- Komodo ist der primaere und einzige produktive Stack-Manager.
|
- Komodo ist der primaere und einzige produktive Stack-Manager.
|
||||||
- Komodo bleibt bewusst bei nativer Authentifizierung; zentrale Traefik-Auth wird dort nicht pauschal vorgeschaltet.
|
- Komodo bleibt bewusst bei nativer Authentifizierung; zentrale Traefik-Auth wird dort nicht pauschal vorgeschaltet.
|
||||||
- Portainer CE ist abgeschaltet und kein Teil des aktiven Betriebs mehr.
|
- Portainer CE ist abgeschaltet und kein Teil des aktiven Betriebs mehr.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
immich-server:
|
immich-server:
|
||||||
container_name: immich_server
|
container_name: immich_server
|
||||||
image: ghcr.io/immich-app/immich-server:release@sha256:c15bff75068effb03f4355997d03dc7e0fc58720c2b54ad6f7f10d1bc57efaa5
|
image: ghcr.io/immich-app/immich-server:v2.7.5@sha256:c15bff75068effb03f4355997d03dc7e0fc58720c2b54ad6f7f10d1bc57efaa5
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
@@ -32,12 +32,34 @@ services:
|
|||||||
|
|
||||||
immich-machine-learning:
|
immich-machine-learning:
|
||||||
container_name: immich_machine_learning
|
container_name: immich_machine_learning
|
||||||
image: ghcr.io/immich-app/immich-machine-learning:release@sha256:a2501141440f10516d329fdfba2c68082e19eb9ba6016c061ac80d23beadf7f3
|
image: ghcr.io/immich-app/immich-machine-learning:v2.7.5@sha256:a2501141440f10516d329fdfba2c68082e19eb9ba6016c061ac80d23beadf7f3
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
# Workaround fuer gunicorn-25.1.0-Control-Socket-Bug: der Worker haengt
|
||||||
|
# nach "Control socket listening at /usr/src/gunicorn.ctl" und erreicht
|
||||||
|
# nie "Application startup complete" -> Container bleibt dauerhaft
|
||||||
|
# unhealthy, ML (Gesichtserkennung/CLIP/Smart-Search) ist tot.
|
||||||
|
# --no-control-socket deaktiviert das fehlerhafte Feature. immich-ml
|
||||||
|
# startet gunicorn als Subprozess, der GUNICORN_CMD_ARGS aus der Env
|
||||||
|
# liest und anhaengt. Bestaetigte Upstream-Regression seit Immich 2.6
|
||||||
|
# (immich#27228, gunicorn#3510). Re-check: bei Immich-Update, das
|
||||||
|
# gunicorn auf >25.1.0/<25.1.0 mit Fix bringt, wieder entfernen.
|
||||||
|
GUNICORN_CMD_ARGS: "--no-control-socket"
|
||||||
volumes:
|
volumes:
|
||||||
- model-cache:/cache
|
- model-cache:/cache
|
||||||
networks:
|
networks:
|
||||||
|
# immich_default (internal) = Erreichbarkeit durch immich-server.
|
||||||
|
# immich_egress (nicht-internal) = Outbound zu huggingface, damit ML die
|
||||||
|
# Modelle (CLIP ViT-B-32, buffalo_l) einmalig nach model-cache laedt.
|
||||||
|
# Ohne dieses Netz scheitert der Modell-Download an der DNS-Aufloesung
|
||||||
|
# (immich_default ist internal: true) -> Smart Search/Gesichtserkennung tot.
|
||||||
- immich_default
|
- immich_default
|
||||||
|
- immich_egress
|
||||||
|
dns:
|
||||||
|
# Egress-Netz braucht externe Aufloesung (huggingface.co); explizit nach
|
||||||
|
# docs/WORKFLOW.md "DNS-Regeln fuer Container", analog traefik/ddns-updater.
|
||||||
|
- 1.1.1.1
|
||||||
|
- 8.8.8.8
|
||||||
security_opt:
|
security_opt:
|
||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
|
|
||||||
@@ -75,5 +97,10 @@ networks:
|
|||||||
name: immich_default
|
name: immich_default
|
||||||
internal: true
|
internal: true
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
immich_egress:
|
||||||
|
# Bewusst NICHT internal: nur fuer den ML-Modell-Download (Outbound).
|
||||||
|
# Nur immich_machine_learning haengt hier; DB/Redis bleiben in immich_default.
|
||||||
|
name: immich_egress
|
||||||
|
driver: bridge
|
||||||
frontend_net:
|
frontend_net:
|
||||||
external: true
|
external: true
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
mail-archiver:
|
mail-archiver:
|
||||||
image: s1t5/mailarchiver@sha256:ea7fd8c2e3e0ef0941e8dd9e726e35a8de33296f5c7b9ed811df5168ae6a9714
|
image: s1t5/mailarchiver@sha256:4ea7ecc47ad1dd2c523b85c3967574b61e39def1b6fd26edf874e21733c4018c
|
||||||
container_name: mail-archiver
|
container_name: mail-archiver
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ services:
|
|||||||
container_name: mealie
|
container_name: mealie
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# OIDC: Authelia ueber Host-LAN-IP -> Traefik erreichbar (Container-DNS loest
|
||||||
|
# auth.kaleschke.info sonst nicht; gleiches Muster wie Komodo. SNI bleibt der
|
||||||
|
# Hostname, Let's-Encrypt-Cert validiert weiter.
|
||||||
|
extra_hosts:
|
||||||
|
- "auth.kaleschke.info:192.168.178.58"
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
TZ: Europe/Berlin
|
TZ: Europe/Berlin
|
||||||
ALLOW_SIGNUP: "false"
|
ALLOW_SIGNUP: "false"
|
||||||
@@ -18,6 +24,16 @@ services:
|
|||||||
|
|
||||||
BASE_URL: https://mealie.kaleschke.info
|
BASE_URL: https://mealie.kaleschke.info
|
||||||
|
|
||||||
|
# --- Authelia OIDC SSO (additiv, 2026-06-06; lokaler Login bleibt) ---
|
||||||
|
OIDC_AUTH_ENABLED: "true"
|
||||||
|
OIDC_PROVIDER_NAME: Authelia
|
||||||
|
OIDC_CONFIGURATION_URL: https://auth.kaleschke.info/.well-known/openid-configuration
|
||||||
|
OIDC_CLIENT_ID: mealie
|
||||||
|
OIDC_CLIENT_SECRET: ${MEALIE_OIDC_CLIENT_SECRET}
|
||||||
|
OIDC_SIGNUP_ENABLED: "true"
|
||||||
|
OIDC_AUTO_REDIRECT: "false"
|
||||||
|
OIDC_REMEMBER_ME: "true"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- /mnt/user/appdata/mealie/data:/app/data
|
- /mnt/user/appdata/mealie/data:/app/data
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
n8n:
|
n8n:
|
||||||
image: docker.n8n.io/n8nio/n8n:2.25.5@sha256:08862289f9e9b387d91eab66a74d40d307c0c9b74d2504866f8fe61e9063c838
|
image: docker.n8n.io/n8nio/n8n:2.27.3@sha256:a772d24e6b4f9b3848be5a57c5e45437eed1965bbbcefa2f9a93f4835b6639fa
|
||||||
container_name: n8n
|
container_name: n8n
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
nextcloud:
|
nextcloud:
|
||||||
image: nextcloud:33.0.5-apache@sha256:96f8b6ad4adf4044ac6d3cbc10ef99b4897c90792782b5b60a5700e5b1b97b84
|
image: nextcloud:33.0.5-apache@sha256:56bdc45109067500fd0832fa64832b7c77a167d9394cbf5f0f4b59740b94194d
|
||||||
container_name: nextcloud
|
container_name: nextcloud
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ services:
|
|||||||
image: ghcr.io/paperless-ngx/paperless-ngx:2.20.15@sha256:6c86cad803970ea782683a8e80e7403444c5bf3cf70de63b4d3c8e87500db92f
|
image: ghcr.io/paperless-ngx/paperless-ngx:2.20.15@sha256:6c86cad803970ea782683a8e80e7403444c5bf3cf70de63b4d3c8e87500db92f
|
||||||
container_name: paperless-ngx
|
container_name: paperless-ngx
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
# OIDC: Authelia ueber Host-LAN-IP -> Traefik erreichbar (Container-DNS sonst nicht)
|
||||||
|
extra_hosts:
|
||||||
|
- "auth.kaleschke.info:192.168.178.58"
|
||||||
security_opt:
|
security_opt:
|
||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
environment:
|
environment:
|
||||||
@@ -17,6 +20,11 @@ services:
|
|||||||
- PAPERLESS_OCR_LANGUAGE=deu+eng
|
- PAPERLESS_OCR_LANGUAGE=deu+eng
|
||||||
- PAPERLESS_URL=https://paperless.kaleschke.info
|
- PAPERLESS_URL=https://paperless.kaleschke.info
|
||||||
|
|
||||||
|
# --- Authelia OIDC SSO (additiv, 2026-06-06; lokaler Login bleibt) ---
|
||||||
|
- PAPERLESS_APPS=allauth.socialaccount.providers.openid_connect
|
||||||
|
- PAPERLESS_SOCIAL_AUTO_SIGNUP=true
|
||||||
|
- 'PAPERLESS_SOCIALACCOUNT_PROVIDERS={"openid_connect":{"OAUTH_PKCE_ENABLED":true,"APPS":[{"provider_id":"authelia","name":"Authelia","client_id":"paperless","secret":"${PAPERLESS_OIDC_SECRET}","settings":{"server_url":"https://auth.kaleschke.info"}}]}}'
|
||||||
|
|
||||||
# Barcode / ASN
|
# Barcode / ASN
|
||||||
- PAPERLESS_CONSUMER_ENABLE_BARCODES=1
|
- PAPERLESS_CONSUMER_ENABLE_BARCODES=1
|
||||||
- PAPERLESS_CONSUMER_ENABLE_ASN_BARCODE=1
|
- PAPERLESS_CONSUMER_ENABLE_ASN_BARCODE=1
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
unbound:
|
unbound:
|
||||||
image: shaanmajid/unbound:1.25.1@sha256:96809ff052e8bd79bba30e067d8b27ed9a2f069b6b2a3484fe1d0eb45aba07c5
|
image: shaanmajid/unbound:1.25.1@sha256:f140db02a005904802bf5840093e95e675321aa060a00426fdffc2a3ac2eeb6b
|
||||||
container_name: unbound
|
container_name: unbound
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
+13
-40
@@ -1,8 +1,10 @@
|
|||||||
# AI Context
|
# AI Context
|
||||||
|
|
||||||
Stand: 2026-06-05
|
Typ: Einstieg/Index · Stand: 2026-06-11 · Status: aktiv
|
||||||
|
|
||||||
Kurzer Kontext fuer KI-Agenten. Nicht als Ersatz fuer die echten Runbooks lesen.
|
Kurzer Kontext fuer KI-Agenten. Nicht als Ersatz fuer die echten Runbooks lesen.
|
||||||
|
Diese Datei enthaelt bewusst **keinen** Arbeitsstand mehr — Status nur in
|
||||||
|
`docs/MASTER_TODO.md`, Entscheidungen nur in `docs/DECISIONS.md`.
|
||||||
|
|
||||||
## Systembild
|
## Systembild
|
||||||
|
|
||||||
@@ -20,6 +22,7 @@ Kurzer Kontext fuer KI-Agenten. Nicht als Ersatz fuer die echten Runbooks lesen.
|
|||||||
3. betroffene Compose-Datei
|
3. betroffene Compose-Datei
|
||||||
4. bei Service-Fragen `docs/SERVICE_CATALOG.md`
|
4. bei Service-Fragen `docs/SERVICE_CATALOG.md`
|
||||||
5. bei Restore/DR `docs/DISASTER_RECOVERY.md` und `docs/RESTORE_MATRIX.md`
|
5. bei Restore/DR `docs/DISASTER_RECOVERY.md` und `docs/RESTORE_MATRIX.md`
|
||||||
|
6. bei "warum ist das so?"-Fragen `docs/DECISIONS.md`
|
||||||
|
|
||||||
## Harte Regeln
|
## Harte Regeln
|
||||||
|
|
||||||
@@ -30,51 +33,21 @@ Kurzer Kontext fuer KI-Agenten. Nicht als Ersatz fuer die echten Runbooks lesen.
|
|||||||
- Traefik dynamic config und Authelia Host-Config sind manuelle Sync-Ausnahmen.
|
- Traefik dynamic config und Authelia Host-Config sind manuelle Sync-Ausnahmen.
|
||||||
- Bei Drift zuerst Git, Gitea, Komodo Workspace, Docker Runtime und Host getrennt pruefen.
|
- Bei Drift zuerst Git, Gitea, Komodo Workspace, Docker Runtime und Host getrennt pruefen.
|
||||||
- Nach zwei fehlgeschlagenen Reparaturversuchen stoppen und `docs/GITOPS_DRIFT_RUNBOOK.md` nutzen.
|
- Nach zwei fehlgeschlagenen Reparaturversuchen stoppen und `docs/GITOPS_DRIFT_RUNBOOK.md` nutzen.
|
||||||
|
- Doku-Regel: ein Fakt hat genau ein Zuhause; verlinken statt kopieren (`docs/REPO_MAP.md`).
|
||||||
|
|
||||||
## Bekannte Ausnahmen
|
## Bekannte Ausnahmen
|
||||||
|
|
||||||
|
Autoritativ: `HOMELAB_ARCHITECTURE_MASTER_V2.md` §10. Kurzliste:
|
||||||
|
|
||||||
- Traefik: Host-Ports 80/443, WAN-Freigabe nur 443
|
- Traefik: Host-Ports 80/443, WAN-Freigabe nur 443
|
||||||
- Gitea: SSH auf Host-Port 222, keine WAN-Freigabe
|
- Gitea: SSH auf Host-Port 222, keine WAN-Freigabe
|
||||||
- AdGuard: DNS 53 direkt; Admin nur auf Tailscale-IP `100.80.98.33:8082`
|
- AdGuard: DNS 53 direkt; Admin nur auf Tailscale-IP `100.80.98.33:8082`
|
||||||
- Tailscale und Plex: Host-Netz
|
- Tailscale: natives Unraid-Plugin (nicht repo-verwaltet); Plex: Host-Netz
|
||||||
- Scrutiny: privileged
|
- Scrutiny: privileged; Komodo/Periphery: Docker-Socket
|
||||||
- Komodo/Periphery: Docker-Socket-Zugriff
|
|
||||||
- InfluxDB 3 Core: `127.0.0.1:8181`, Root-User-Ausnahme dokumentiert
|
- InfluxDB 3 Core: `127.0.0.1:8181`, Root-User-Ausnahme dokumentiert
|
||||||
|
|
||||||
## Aktuelle Restpunkte
|
## Arbeitsstand
|
||||||
|
|
||||||
Authoritativ: `docs/MASTER_TODO.md`.
|
- Offene Punkte: `docs/MASTER_TODO.md` (einzige Statusliste)
|
||||||
|
- Entscheidungen und Begruendungen: `docs/DECISIONS.md`
|
||||||
Kurzfassung:
|
- Belege/Reports: `/mnt/user/backups/restore-reports/` auf dem Host
|
||||||
|
|
||||||
- Auth-/OIDC-/CrowdSec-/Hermes-Themen bewusst geparkt
|
|
||||||
- Wochenend-Sprint 2026-06-05: `docs/WEEKEND_EXECUTION_PLAN_2026-06-05.md`
|
|
||||||
und `docs/WEEKEND_STATUS_2026-06-05.md`
|
|
||||||
|
|
||||||
Letzte Bestaetigung:
|
|
||||||
|
|
||||||
- Windows-Image `baerchen`: Veeam Agent Free Job `baerchen-c-image` auf
|
|
||||||
`\\kallilabcore\backups\windows-images\baerchen`, erster Full-Backup-Lauf
|
|
||||||
2026-06-05 erfolgreich, GUI-Wert 53,8 GB, Dauer 0:11:31. Recovery-USB ist
|
|
||||||
erstellt; Boot-/SMB-/Restore-Point-Test ohne Restore ist noch offen.
|
|
||||||
- Veeam Storage Encryption ist beim ersten Full-Lauf nicht aktiv
|
|
||||||
(`StorageEncryptionEnabled=False`); nachtraegliche Aktivierung ist eine
|
|
||||||
Operator-Entscheidung, weil sie Passwort- und Restore-Prozess aendert.
|
|
||||||
- BitLocker fuer `baerchen` ist bewusst nicht aktiviert und bleibt
|
|
||||||
Operator-Entscheidung.
|
|
||||||
- Tailscale-Inventar 2026-06-05 real gemessen: `Kallilabcore`
|
|
||||||
`100.80.98.33`, IPv6 `fd7a:115c:a1e0::2c01:62b2`, kein Exit Node, aber
|
|
||||||
aktiver Subnet Router fuer `192.168.178.0/24`. Dadurch ist die Tailnet-ACL
|
|
||||||
sicherheitsrelevant; Entscheidung Default-Allow vs tag-basierte ACL offen.
|
|
||||||
- Unraid-Flash-Backup-Artefaktpruefung: `ops/maintenance/check-unraid-flash-backup.sh`
|
|
||||||
prueft Artefakt, SHA256, Alter und Kern-Configs. Test 2026-06-05 gegen Host
|
|
||||||
erfolgreich laut `docs/MASTER_TODO.md`.
|
|
||||||
- Borg-Nachlauf 2026-06-01 erfolgreich: Archiv `Taegliche-Sicherung-2026-06-01T04:30:26.913`, Freshness Critical 0 / Warnings 0.
|
|
||||||
- H:/ Nearline-Pull 2026-06-01 repariert: Borg-Dumps werden kuratiert kopiert, Gitea-Bundles aktuell.
|
|
||||||
- Family-Status-Dashboard liegt als `monitoring/grafana/dashboards/family-status.json` im Repo.
|
|
||||||
- Alt-Volumes nach PG18/VectorChord-Burn-in sind seit 2026-06-02 reversibel archiviert unter `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602`; die alten Originalpfade sind nicht mehr aktiv gemountet.
|
|
||||||
- Family-Onboarding ist auf drei Nutzungsziele fokussiert: Vaultwarden, Immich und Mealie; praktischer Ablauf in `docs/FAMILY_ONBOARDING.md`.
|
|
||||||
- Externer Betreibercheck: `ops/maintenance/check-external-operator.sh`; FRITZ!Box 7590 meldet FRITZ!OS `154.08.25`, DNS fuer Public Apps hat keine AAAA-Records, Host hat keine globale Provider-IPv6.
|
|
||||||
- FRITZ!Box-UI 2026-06-01: Remote-HTTPS auf FRITZ!Box-UI aus, FTP/FTPS auf Speichermedien aus, WAN-Freigabe nur `443/tcp`, keine aktive IPv6-Freigabe sichtbar, UPnP-Selbstfreigaben aus.
|
|
||||||
- FRITZ!Box-Konfig-Backup 2026-06-01 extern/off-system in Vaultwarden abgelegt; Datei und Kennwort bleiben ausserhalb des Repos.
|
|
||||||
- Hetzner-Account-Hygiene 2026-06-01 erledigt: 2FA aktiv, Recovery Key offline gedruckt, Zahlung ok; Storage Box SSH-only, Maintenance-Key in Vaultwarden. Append-only forced-command brach Key-Auth und wurde per Passwort-Recovery zurueckgesetzt; Operator-Entscheidung: fuer dieses Homelab bewusst nicht umsetzen.
|
|
||||||
|
|||||||
+9
-1
@@ -1,6 +1,6 @@
|
|||||||
# Alert Rules
|
# Alert Rules
|
||||||
|
|
||||||
Stand: 2026-06-05
|
Stand: 2026-06-18
|
||||||
|
|
||||||
Diese Datei beschreibt die produktiven Alarmwege und wichtigsten Regeln. Die
|
Diese Datei beschreibt die produktiven Alarmwege und wichtigsten Regeln. Die
|
||||||
Konfiguration selbst liegt in `monitoring/prometheus/alerts.yml` und in den
|
Konfiguration selbst liegt in `monitoring/prometheus/alerts.yml` und in den
|
||||||
@@ -36,6 +36,14 @@ Skripten unter `services/posture-check/`.
|
|||||||
| `HomelabBorgBackupStale` | letztes Borg-Backup >30h | warning | Backup-Lauf nachholen/pruefen |
|
| `HomelabBorgBackupStale` | letztes Borg-Backup >30h | warning | Backup-Lauf nachholen/pruefen |
|
||||||
| `HomelabBorgLastJobFailed` | letzter Borg-Job fehlgeschlagen | critical | Borg-UI-Job-Log pruefen |
|
| `HomelabBorgLastJobFailed` | letzter Borg-Job fehlgeschlagen | critical | Borg-UI-Job-Log pruefen |
|
||||||
| `HomelabBorgLastJobCompletedWithWarnings` | letzter Borg-Job mit Warnungen | warning | Warnung im Borg-UI-Job lesen |
|
| `HomelabBorgLastJobCompletedWithWarnings` | letzter Borg-Job mit Warnungen | warning | Warnung im Borg-UI-Job lesen |
|
||||||
|
| `HomelabBorgDumpMissing` | erwartetes Dump-Artefakt fehlt im aktuellen Dump-Set | critical | `pre-backup-dumps.sh`/User-Script pruefen |
|
||||||
|
| `HomelabBorgDumpStale` | Dump-Artefakt >30h alt (Borg laeuft, Dumps eingefroren) | critical | `pre-backup-dumps.sh`/User-Script pruefen, nicht nur den Borg-Job |
|
||||||
|
| `HomelabBorgScopeSourceListMissing` | Repo-Quellliste fuer Borg-Drift-Check fehlt | critical | Borg-UI-Mount `/local/services/homelab-infra` und Repo-Pfad pruefen |
|
||||||
|
| `HomelabBorgScopeMissingSources` | Borg UI enthaelt nicht alle Pfade aus `ops/borg-ui/all-important-sources.txt` | critical | Live-Borg-Scope an Repo-Quelle angleichen |
|
||||||
|
| `HomelabBorgScopeExtraSources` | Borg UI enthaelt Pfade ausserhalb der Repo-Quellliste | warning | Doku oder Live-Scope bereinigen |
|
||||||
|
| `HomelabBorgRepositoryCheckStale` | letzter Borg-Check >14 Tage alt | warning | Borg-Repository-Check ausfuehren oder Scheduler pruefen |
|
||||||
|
| `HomelabBorgRetentionDisabled` | Scheduled Job fuehrt kein Prune aus | warning | Retention-Einstellung in Borg UI pruefen |
|
||||||
|
| `HomelabBorgCompactDisabled` | Scheduled Job fuehrt kein Compact aus | warning | Compact-Einstellung in Borg UI pruefen |
|
||||||
| `HomelabCriticalContainerDown` | kritischer Container fehlt | critical | Komodo/Docker-Status pruefen |
|
| `HomelabCriticalContainerDown` | kritischer Container fehlt | critical | Komodo/Docker-Status pruefen |
|
||||||
| `HomelabPrometheusTargetDown` | Scrape-Ziel down | critical | node-exporter/cadvisor/blackbox/traefik pruefen |
|
| `HomelabPrometheusTargetDown` | Scrape-Ziel down | critical | node-exporter/cadvisor/blackbox/traefik pruefen |
|
||||||
|
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
# Audit-Restliste 2026-05-25
|
|
||||||
|
|
||||||
Status: **kompakte Restliste**. Die erledigten Sprint-Tabellen und langen
|
|
||||||
Audit-Snapshots wurden aus der Arbeitskopie entfernt; Detailhistorie liegt in Git.
|
|
||||||
|
|
||||||
Letzter Sync mit `docs/MASTER_TODO.md`: 2026-06-05. Offene Punkte sind deckungsgleich;
|
|
||||||
neue Restore-Runbook-Stubs (Unraid Flash / AdGuard / Tailscale / Redis 8) wurden
|
|
||||||
in `docs/RESTORE_MATRIX.md` ergaenzt.
|
|
||||||
|
|
||||||
## Aktuell offene Punkte
|
|
||||||
|
|
||||||
| Prioritaet | Punkt | Naechster Schritt |
|
|
||||||
|---|---|---|
|
|
||||||
| P1 | DR-Workstation Bare-Metal-Kit: WSL2 + Borg-Client installieren | Hetzner-DR-SSH-Key ist 2026-06-03 erledigt und offline gesichert. Verbleibend: WSL2 auf dem Gaming-PC einrichten (`wsl --install -d Ubuntu`), `sudo apt install borgbackup` und ein erster Smoke `borg list ssh://u565255@u565255.your-storagebox.de/./hetzner_borg_appdata_critical` mit dem offline gesicherten Key + Passphrase. Bestandteile dokumentiert in `docs/EXTERNAL_DEPENDENCIES.md` Abschnitt "DR-Workstation Bare-Metal-Kit" |
|
|
||||||
| P2 | Family-Onboarding praktisch starten | Fokus: Vaultwarden als Passwortbasis, Immich-Mobile-Backup auf jedem Handy, Mealie mit erstem Rezept/Einkaufsliste; Ablauf steht in `docs/FAMILY_ONBOARDING.md` |
|
|
||||||
|
|
||||||
## Restore-Audit Backlog (Stand 2026-06-03)
|
|
||||||
|
|
||||||
Ergebnis des Restore-Skills-Audits (Session 2026-06-02/03). Die kritischen Bugfixes (Cron-OR-Semantik, ntfy-Race, Cleanup-Trap, Pfad-Inkonsistenz, Vaultwarden-Token, Paperless-Retry, Header-Validierung, Authelia-Test) sind erledigt und committed. Die folgenden Punkte sind bewusst offener Backlog:
|
|
||||||
|
|
||||||
| Prioritaet | Punkt | Status | Naechster Schritt |
|
|
||||||
|---|---|---|---|
|
|
||||||
| P1 | Nextcloud-Restore-Test | **erledigt 2026-06-03** | Borg-Extract + pg_restore (126 Tabellen) + HTTP 200 + `occ status maintenance:false`. Quelle: `hetzner_borg_appdata_critical`, Archiv `Taegliche-Sicherung-2026-06-03T04:30:41.432`. Zwei Skript-Bugs im Zuge des Laufs gefixt (`check_data_directory_permissions: false` patchen, `.ncdata`-Marker anlegen). Report `/mnt/user/backups/restore-reports/nextcloud-2026-06-03.md`. |
|
|
||||||
| P1 | Shared PostgreSQL 18 Cluster Restore Drill | **erledigt 2026-06-03** | Globals + 5 DBs (paperless 72t, mailarchiver 1t, authelia 25t, nextcloud 126t, mealie 66t), `data_checksums=on`, Report `/mnt/user/backups/restore-reports/shared-pg-cluster-2026-06-03.md` |
|
|
||||||
| P1 | Komodo-Mongo-Daten-Restore | **erledigt 2026-06-03** | 86904 Dokumente erfolgreich restored, Report `/mnt/user/backups/restore-reports/komodo-mongo-restore-2026-06-03.md`. Nebenbefund: Dump von Mongo 8.0.23, Test auf 7.0.32 — Cross-Version-Warning, fuer Lesetest harmlos |
|
|
||||||
| P2 | Mailarchiver-Restore-Test | **erledigt 2026-06-03** | Data-Protection-Keys + 645M pg_restore + HTTP 200. Report `/mnt/user/backups/restore-reports/mailarchiver-2026-06-03.md` |
|
|
||||||
| P2 | Mealie-Restore-Test | **erledigt 2026-06-03** | Borg-Data + pg_restore + HTTP 200, 3 Rezepte. Report `/mnt/user/backups/restore-reports/mealie-2026-06-03.md` |
|
|
||||||
| P2 | Traefik-Restore-Test | **erledigt 2026-06-03** | dynamic/ + letsencrypt/ aus Borg, File-Provider + Ping 200. CF-Token bewusst nicht im Smoke. Report `/mnt/user/backups/restore-reports/traefik-2026-06-03.md` |
|
|
||||||
| P3 | Negativ-Test fuer Frische-Check | offen | Einmal pro Quartal bewusst kaputten Dump einfuettern und pruefen ob `homelab-alerts` feuert |
|
|
||||||
| P3 | End-to-end-DR-Drill | offen | Komplett-Bootstrap Phase 1-5 auf einem Wegwerf-Host; realistisch nur mit zweiter Hardware |
|
|
||||||
|
|
||||||
## Bewusst geparkt
|
|
||||||
|
|
||||||
| Punkt | Entscheidung |
|
|
||||||
|---|---|
|
|
||||||
| Authelia 2FA fuer Operator-UIs (Rest) | Tier-1-Operator-UIs sind 2026-06-03 auf `two_factor` gehoben (`files`, `scrutiny`, `borg`, `code`). Restliche Admin-UIs (`monitoring`, `glances`, `glance`, `speedtest`, `paperless-gpt`, `pdf`, `mail`, `hermes`, `sp`) bleiben bewusst auf `one_factor`, bis die finale Auth-Policy steht. |
|
|
||||||
| Authelia OIDC fuer Apps | Geparkt bis klare Familien-/SSO-Entscheidung |
|
|
||||||
| CrowdSec vor Traefik | Bewusst nicht umgesetzt: einzige WAN-Tuer ist `443/tcp`, Operator-Pfad ist Tailscale, Authelia-`regulation:` deckt Auth-Brute-Force ab. Neu bewerten bei breiterer Attack Surface. |
|
|
||||||
| Nextcloud 2FA/Brute-Force-Haertung | UI-Schritt fuer Operator-Account (`twofactor_totp` aktivieren) bleibt offen. App-weite Familien-Policy gemeinsam mit OIDC entscheiden. |
|
|
||||||
| Hermes-Agent | NAS-Stack bleibt deaktiviert; Review-Deadline 2026-07-25 |
|
|
||||||
| USV | Anschaffung verschoben; Power-Loss-Risiko bewusst akzeptiert |
|
|
||||||
| Zweites Off-site-Ziel | Bewusst nicht umgesetzt; neu bewerten bei Hetzner-Problemen, stark wachsendem Datenwert oder geaenderter Betreiber-Praeferenz |
|
|
||||||
| Borg `append-only` auf Hetzner | Operator-Entscheidung 2026-06-01: nicht umgesetzt. Der forced-command-Test auf der Storage Box brach Key-Auth und wurde per Passwort-Recovery zurueckgesetzt; Nutzen steht fuer dieses Homelab nicht im Verhaeltnis zum Betriebsrisiko. |
|
|
||||||
|
|
||||||
## Zuletzt geschlossen
|
|
||||||
|
|
||||||
- Nextcloud-Restore-Test 2026-06-03 erfolgreich (Tier-2 damit komplett belegt). Drei Laeufe noetig: Lauf 1 schlug an `chmod()` der data-Dir auf shfs fehl (`OC_Util.php:486`), Lauf 2 an fehlender `.ncdata`-Marker-Datei, Lauf 3 sauber durch. Beide Bug-Fixes ins Skript `ops/restore-tests/nextcloud-restore-test.sh` integriert. Endresultat: HTTP 200 auf `/status.php`, `occ status` ok, 126 Tabellen in der DB. Source: `hetzner_borg_appdata_critical`, Archiv `Taegliche-Sicherung-2026-06-03T04:30:41.432`. Report unter `/mnt/user/backups/restore-reports/nextcloud-2026-06-03.md`.
|
|
||||||
- Hetzner Storage Box DR-SSH-Key `dr-hetzner-2026-06-03` (ed25519, Passphrase-frei) angelegt: Pubkey via `install-ssh-key` auf der Storage Box autorisiert, passwortloser Login erfolgreich (Borg-Repos `backup`, `backup2`, `hetzner_borg_appdata`, `hetzner_borg_appdata_critical` sichtbar), Private-Key offline neben KOMODO_*-Notiz und GitHub-Deploy-Key abgelegt, Arbeitsplatz-Kopie geloescht. Damit ist Bare-Metal-Borg-Zugang von der DR-Workstation moeglich, sobald WSL2+Borg installiert sind.
|
|
||||||
- Fix Common Problems Plugin (FCP) 2026-06-03 deinstalliert. Befund: drei `grep -R ... /usr/local/emhttp`-Prozesse aus einem FCP-Daily-Scan hingen seit ~7 Tagen in einem Symlink-Loop (`/usr/local/emhttp/mnt -> /mnt`, gesamte Array). 3 Cores dauerhaft 100 %, IOWAIT bis 55 %, USB-Flash unter Dauer-IO. Plugin via `plugin remove` entfernt, Cron + /tmp-Reste sauber, Load von 14.6 auf 1.08 gefallen. FCP wird bewusst nicht wieder installiert (Begruendung siehe `HOMELAB_ARCHITECTURE_MASTER_V2.md` Sektion 13). Bekannte Risiken decken Scrutiny, Monitoring, Posture-Check und Critical-Events-Watcher bereits ab.
|
|
||||||
- GitHub-Mirror Read-Only Deploy-Key `DR Read-Only 2026-06-03` (ed25519, Passphrase-frei) angelegt: GitHub Repo Settings -> Deploy Keys ohne Write-Access, Smoke `git ls-remote` erfolgreich (HEAD `d947c7f` = master), Private-Key offline neben der KOMODO_*-Notiz abgelegt, Arbeitsplatz-Kopie nach USB-Transfer geloescht. Damit ist der DR-Read-Pfad zum privaten Mirror ohne Operator-Browser-Login moeglich.
|
|
||||||
- KOMODO_*-Notiz offline gesichert (Operator-Bestaetigung 2026-06-03). Quelle bleibt host-seitige `.env` unter `/mnt/user/services/stacks/komodo/.env` bzw. die Drift-Recovery-Kopie unter `/mnt/user/appdata/secrets/_komodo_stack_env_recovery_2026-05-04.env`. Damit ist der Bare-Metal-Komodo-Bootstrap ohne Vaultwarden moeglich. Eintrag in `docs/EXTERNAL_DEPENDENCIES.md` Reviews und Pflichtbestandteil im DR-Workstation-Kit nachgezogen.
|
|
||||||
- DR-Tabletop 2026-06-03 durchgelaufen, Findings in `docs/DR_DRILL_2026-06-03.md` (23 Befunde: 1 CRITICAL, 11 HIGH, 8 MED, 3 LOW). Reine Doku-Fixes in DR.md (Phase 0 Mirror-Klarstellung, neue Phase 4 Stufe 0 Docker-Netze, LE-Staging-Hinweis, Komodo-Stolperfallen, App-DB-Verify in Phase 5) und in `EXTERNAL_DEPENDENCIES.md` (DR-Workstation-Kit, KOMODO_*-Notiz und GitHub-Read-PAT als offene Bootstrap-Bloecke) sind im selben Aenderungsblock erledigt. Operator-Aufgaben (Notiz/PAT/WSL-Setup) wandern als P1 in die offenen Punkte.
|
|
||||||
- Authelia ACL: `borg.kaleschke.info` und `code.kaleschke.info` 2026-06-03 in den `two_factor`-Block der Repo-Baseline aufgenommen. Beide UIs haben effektiv Host-/Backup-Zugriff (Borg-Restore-Scope inkl. `/local/secrets`, code-server mit Workspaces). Wirkung erst nach manuellem Merge in `/mnt/user/appdata/authelia/config/configuration.yml`, `docker restart authelia` und Smoke-Test auf einer der vier 2FA-Domains; `services/authelia-diff.sh` muss `exit 0` liefern. TOTP-Enrollment des Operator-Accounts ist Voraussetzung, sonst Login-Sperre.
|
|
||||||
- Alt-Volumes nach Burn-in freigegeben und reversibel archiviert: Shared PG17, Mealie PG17, Nextcloud PG17 und Immich pgvecto.rs liegen seit 2026-06-02 unter `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602`; Manifest auf dem Host: `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/MANIFEST.txt`. Keine harte Loeschung, keine aktiven Container-Mounts auf die alten Pfade.
|
|
||||||
- Externer Betreibercheck vorbereitet: `docs/EXTERNAL_OPERATOR_RUNBOOK.md` und `ops/maintenance/check-external-operator.sh`; Live-Baseline am 2026-06-01: FRITZ!OS `154.08.25`, keine Public-AAAA-Records fuer `*.kaleschke.info`, Host ohne globale Provider-IPv6, WAN `443/tcp` offen und `80/tcp`/`222/tcp` geschlossen.
|
|
||||||
- FRITZ!Box-Servicefenster UI-seitig abgeschlossen: FRITZ!Box-Dienste aus dem Internet sind aus (HTTPS auf FRITZ!Box-UI, FTP/FTPS auf Speichermedien), aktive WAN-Freigabe bleibt nur `443/tcp -> 192.168.178.58`, keine aktive IPv6-Freigabe sichtbar, UPnP-Selbstfreigaben aus.
|
|
||||||
- FRITZ!Box-Konfig-Backup exportiert und extern/off-system in Vaultwarden abgelegt: `Einstellungen_FRITZ.Box_7590_154.08.25_01.06.26_1318.export`; Kennwort und Datei bleiben ausserhalb des Repos.
|
|
||||||
- Hetzner-Account-Hygiene erledigt: externe Kontakt-/Rechnungs-Mail bestaetigt, Zahlung ok, 2FA mit Google Authenticator aktiv, Recovery Key offline ausgedruckt.
|
|
||||||
- Hetzner Storage Box geprueft: `storage-box-1`, `u565255.your-storagebox.de`, SSH-Port `23`, SSH aktiv, SMB/WebDAV aus, 64,94 GB / 1 TB belegt; Borg-UI-Key und separater Maintenance-Key funktionieren wieder nach Passwort-Recovery. Borg `append-only` ist bewusst nicht umgesetzt.
|
|
||||||
- Family-View Dashboard ist repo-seitig gebaut: `monitoring/grafana/dashboards/family-status.json` zeigt Family-App-Uptime, Backup-Alter, TLS-Restlaufzeit, Critical-Container und Image-Drift.
|
|
||||||
- Borg-Nachlauf nach dem 2026-05-31-Sprint ist belegt: Archiv `Taegliche-Sicherung-2026-06-01T04:30:26.913`, 101669 Dateien, `rc=0`; Freshness-Check am 2026-06-01: Critical 0, Warnings 0.
|
|
||||||
- H:/ Nearline-Pull am 2026-06-01 repariert und manuell validiert: kuratierte Borg-Dumps Exit 0, Gitea-Bundles Exit 1 (Robocopy-Erfolg mit Kopien), Report `nearline-pull-2026-06-01-082553.md`.
|
|
||||||
- Immich-, Paperless-, Gitea- und Vaultwarden-Restore-Pfade sind belegt.
|
|
||||||
- H:/ Nearline-Pull laeuft seit 2026-05-28 als Windows Scheduled Task.
|
|
||||||
- FRITZ!Box-Portfreigaben sind bereinigt: WAN-seitig bleibt `443/tcp`.
|
|
||||||
- InfluxDB 3 Core ist effektiv nur auf `127.0.0.1:8181` gebunden.
|
|
||||||
- Renovate ist produktiv, Major-Updates werden bewusst manuell entschieden.
|
|
||||||
- Policy-Check bleibt ohne Criticals; bekannte Root-Ausnahmen sind dokumentiert.
|
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
# Authelia OIDC fuer Apps - Plan & Runbook
|
||||||
|
|
||||||
|
Stand: 2026-06-17. Authelia-Version: **v4.39.20**.
|
||||||
|
|
||||||
|
Ziel: App-uebergreifendes Single-Sign-On ueber Authelia als OpenID-Connect-Provider
|
||||||
|
(`https://auth.kaleschke.info`). Statt pro App eigener Logins meldet man sich einmal
|
||||||
|
bei Authelia an (inkl. 2FA) und wird per OIDC an die App durchgereicht.
|
||||||
|
|
||||||
|
> **Status:** aktives Runbook. Grafana und Mealie sind seit 2026-06-06 live
|
||||||
|
> und per Login-Smoke verifiziert. Paperless ist seit 2026-06-17 technisch
|
||||||
|
> verdrahtet (Authelia-Client + Stack-ENV-Secret + Service-Smoke gruen);
|
||||||
|
> finaler Browser-Login mit Operator-Account bleibt offen. Der Rollout bleibt
|
||||||
|
> additiv: lokale App-Logins bleiben als Fallback aktiv.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Grundregeln (wichtig)
|
||||||
|
|
||||||
|
- **Secrets gehoeren nie ins Repo.** OIDC-Client-Secrets (Klartext und pbkdf2-Hash)
|
||||||
|
liegen ausschliesslich in der Host-Config `/mnt/user/appdata/authelia/config/configuration.yml`
|
||||||
|
(Hash) und im jeweiligen App-Stack (Klartext, via Komodo Stack-ENV / Secret-Datei),
|
||||||
|
plus optional Vaultwarden. Dieses Dokument enthaelt nur Schema und Variablennamen.
|
||||||
|
- **OIDC-Clients leben host-seitig**, wie der bestehende `beszel`-Client. Die Repo-Baseline
|
||||||
|
`security/authelia/configuration.yml` haelt nur die nicht-geheime Struktur
|
||||||
|
(`access_control` etc.); `services/authelia-diff.sh` vergleicht standardmaessig nur
|
||||||
|
`access_control`, OIDC-Clients auf dem Host loesen also keinen Drift-Alarm aus.
|
||||||
|
- **Issuer/Endpoints** (Authelia OIDC):
|
||||||
|
- Issuer: `https://auth.kaleschke.info`
|
||||||
|
- Authorization: `https://auth.kaleschke.info/api/oidc/authorization`
|
||||||
|
- Token: `https://auth.kaleschke.info/api/oidc/token`
|
||||||
|
- Userinfo: `https://auth.kaleschke.info/api/oidc/userinfo`
|
||||||
|
- JWKS: `https://auth.kaleschke.info/jwks.json`
|
||||||
|
- Discovery: `https://auth.kaleschke.info/.well-known/openid-configuration`
|
||||||
|
- **PKCE an, wo moeglich** (`require_pkce: true`, `S256`), wie beim Beszel-Client.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Client-Schema (Authelia v4.39, gespiegelt vom bestehenden `beszel`-Client)
|
||||||
|
|
||||||
|
Pro App ein Block unter `identity_providers.oidc.clients` in der **Host-Config**:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
identity_providers:
|
||||||
|
oidc:
|
||||||
|
clients:
|
||||||
|
- client_id: '<app>'
|
||||||
|
client_name: '<App-Name>'
|
||||||
|
client_secret: '<pbkdf2-sha512-Hash - NUR auf dem Host>'
|
||||||
|
public: false
|
||||||
|
authorization_policy: 'two_factor' # admin-Apps: two_factor; Familien-Apps: s.u.
|
||||||
|
require_pkce: true
|
||||||
|
pkce_challenge_method: 'S256'
|
||||||
|
redirect_uris:
|
||||||
|
- 'https://<app>.kaleschke.info/<oidc-callback-pfad>'
|
||||||
|
scopes:
|
||||||
|
- 'openid'
|
||||||
|
- 'profile'
|
||||||
|
- 'email'
|
||||||
|
- 'groups'
|
||||||
|
response_types:
|
||||||
|
- 'code'
|
||||||
|
grant_types:
|
||||||
|
- 'authorization_code'
|
||||||
|
token_endpoint_auth_method: 'client_secret_basic'
|
||||||
|
userinfo_signed_response_alg: 'none'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Client-Secret erzeugen (auf dem Host)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec authelia authelia crypto hash generate pbkdf2 \
|
||||||
|
--variant sha512 --random --random.length 72 --random.charset rfc3986
|
||||||
|
```
|
||||||
|
|
||||||
|
- Ausgabe: **Random Password** (Klartext) + **Digest** (pbkdf2-Hash).
|
||||||
|
- **Hash** -> Host-Config `client_secret`.
|
||||||
|
- **Klartext** -> App-Stack (Komodo Stack-ENV/Secret) + optional Vaultwarden.
|
||||||
|
- Klartext **nicht** ins Repo, nicht in Logs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reihenfolge / Rollout
|
||||||
|
|
||||||
|
| Stufe | App | Domain | OIDC-Support | Policy | Risiko | Begruendung |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| **1 (Proof) ERLEDIGT 2026-06-06** | Grafana (monitoring) | `monitoring.kaleschke.info` | nativ (`generic_oauth`) | `two_factor` | niedrig | **Live + Login verifiziert.** Authelia-Client `grafana` (host), Secret als Datei `/mnt/user/appdata/secrets/grafana_oidc_client_secret` via `__FILE`, ForwardAuth-Middleware durch OIDC ersetzt, lokaler Admin bleibt Fallback |
|
||||||
|
| 2 | Immich | `immich.kaleschke.info` | nativ (Admin-UI/Config-File) | s. u. (Familie) | mittel | **GEPARKT bis Onboarding (Entscheidung 2026-06-06):** nur `micha` hat Authelia-Account, Familien-SSO-Nutzen entsteht erst mit Familien-Accounts; Immich ist mobil-lastig (hoechste Stoeranfaelligkeit) und braucht UI/Config-File. Erst nach Onboarding gezielt. Runbook bereit. |
|
||||||
|
| 3 | Nextcloud | `cloud.kaleschke.info` | App `user_oidc` (+occ) | s. u. | mittel | **GEPARKT bis Onboarding (Entscheidung 2026-06-06):** wie Immich; braucht `user_oidc`-App-Install + `occ`. Lokaler Login bleibt. Erst nach Onboarding. Runbook bereit. |
|
||||||
|
| **4 ERLEDIGT 2026-06-06** | Mealie | `mealie.kaleschke.info` | nativ | `one_factor` | niedrig | **Live + Login verifiziert.** OIDC-Env additiv (lokaler Login bleibt), Secret als Stack-ENV `${MEALIE_OIDC_CLIENT_SECRET}`, `extra_hosts` noetig (s. Gotchas) |
|
||||||
|
| **5 TEILWEISE ERLEDIGT 2026-06-17** | Paperless-ngx | `paperless.kaleschke.info` | `django-allauth` (Umgebungsvariablen) | `one_factor` (hostseitiger Ist-Stand; `two_factor` spaeter moeglich) | mittel | **Authelia-Client + `${PAPERLESS_OIDC_SECRET}` in Stack-ENV gesetzt, Authelia-Config validiert, Paperless HTTP-Smoke `200`.** Lokaler Login bleibt Fallback; finaler Browser-Login mit Operator-Account offen. |
|
||||||
|
|
||||||
|
**Nicht OIDC:** Vaultwarden hat kein Standard-Endnutzer-OIDC (SSO ist Enterprise/Bitwarden-Feature) -> bleibt eigener Login. ntfy bleibt wie gehabt.
|
||||||
|
|
||||||
|
### Policy Familien-Apps
|
||||||
|
|
||||||
|
- Admin-Apps (Grafana, Paperless): `authorization_policy: two_factor`.
|
||||||
|
- Familien-Apps (Immich, Nextcloud, Mealie): Start mit `one_factor` und lokalen
|
||||||
|
App-Logins als Fallback. 2FA fuer Familie erst spaeter, sobald TOTP-Enrollment
|
||||||
|
pro Person eingerichtet ist; sonst entsteht unnoetiges Lockout-Risiko.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stufe 1 konkret: Grafana (empfohlener Erststart)
|
||||||
|
|
||||||
|
### A) Authelia (Host) - Client anlegen
|
||||||
|
1. Secret erzeugen (Befehl oben). Klartext + Hash notieren.
|
||||||
|
2. In `/mnt/user/appdata/authelia/config/configuration.yml` unter
|
||||||
|
`identity_providers.oidc.clients` neuen Block einfuegen:
|
||||||
|
```yaml
|
||||||
|
- client_id: 'grafana'
|
||||||
|
client_name: 'Grafana'
|
||||||
|
client_secret: '<HASH>'
|
||||||
|
public: false
|
||||||
|
authorization_policy: 'two_factor'
|
||||||
|
require_pkce: true
|
||||||
|
pkce_challenge_method: 'S256'
|
||||||
|
redirect_uris:
|
||||||
|
- 'https://monitoring.kaleschke.info/login/generic_oauth'
|
||||||
|
scopes: ['openid', 'profile', 'email', 'groups']
|
||||||
|
response_types: ['code']
|
||||||
|
grant_types: ['authorization_code']
|
||||||
|
token_endpoint_auth_method: 'client_secret_basic'
|
||||||
|
userinfo_signed_response_alg: 'none'
|
||||||
|
```
|
||||||
|
3. `docker restart authelia`, Health + Log pruefen (`Startup complete`, keine Fehler).
|
||||||
|
|
||||||
|
### B) Grafana (Komodo Stack-ENV) - generic_oauth
|
||||||
|
Im `monitoring`-Stack (Grafana) setzen (Klartext-Secret aus Schritt A):
|
||||||
|
```
|
||||||
|
GF_AUTH_GENERIC_OAUTH_ENABLED=true
|
||||||
|
GF_AUTH_GENERIC_OAUTH_NAME=Authelia
|
||||||
|
GF_AUTH_GENERIC_OAUTH_CLIENT_ID=grafana
|
||||||
|
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET=<KLARTEXT-SECRET>
|
||||||
|
GF_AUTH_GENERIC_OAUTH_SCOPES=openid profile email groups
|
||||||
|
GF_AUTH_GENERIC_OAUTH_AUTH_URL=https://auth.kaleschke.info/api/oidc/authorization
|
||||||
|
GF_AUTH_GENERIC_OAUTH_TOKEN_URL=https://auth.kaleschke.info/api/oidc/token
|
||||||
|
GF_AUTH_GENERIC_OAUTH_API_URL=https://auth.kaleschke.info/api/oidc/userinfo
|
||||||
|
GF_AUTH_GENERIC_OAUTH_USE_PKCE=true
|
||||||
|
GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP=true
|
||||||
|
# optional Rollen-Mapping ueber groups:
|
||||||
|
# GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH=contains(groups[*], 'admins') && 'Admin' || 'Viewer'
|
||||||
|
```
|
||||||
|
- `GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET` als Stack-ENV-only (kein `_FILE`-Support) -> in
|
||||||
|
`docs/SECRETS_MAP.md` als `grafana_oidc_client_secret` (Stack-ENV) nachziehen.
|
||||||
|
|
||||||
|
### C) Test + Rollback
|
||||||
|
- Test: `monitoring.kaleschke.info` -> "Sign in with Authelia" -> Authelia-Login (2FA) -> zurueck in Grafana, eingeloggt.
|
||||||
|
- **Fallback bleibt:** lokaler Grafana-Admin-Login (`/login`) ist weiter aktiv -> kein Lockout.
|
||||||
|
- Rollback: `GF_AUTH_GENERIC_OAUTH_ENABLED=false` (Grafana redeploy) und/oder Client-Block in Authelia entfernen + `docker restart authelia`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Doku-Nachzug bei jedem neuen Client
|
||||||
|
|
||||||
|
- `docs/SECRETS_MAP.md`: pro App `<app>_oidc_client_secret` (Stack-ENV) + Hinweis "Hash in Authelia-Host-Config".
|
||||||
|
- `docs/SERVICE_CATALOG.md`: App-Zeile um "OIDC via Authelia" ergaenzen.
|
||||||
|
- Dieses Dokument: Rollout-Tabelle abhaken.
|
||||||
|
- `docs/MASTER_TODO.md`: Fortschritt im OIDC-Punkt nachziehen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gotchas (aus dem realen Rollout 2026-06-06)
|
||||||
|
|
||||||
|
- **`extra_hosts` ist Pflicht fuer App-Container, die selbst zu Authelia connecten**
|
||||||
|
(OIDC-Discovery/Token sind Server-zu-Server): Der App-Container loest
|
||||||
|
`auth.kaleschke.info` per Docker-DNS oft nicht auf -> `httpx.ConnectTimeout` /
|
||||||
|
500 beim OAuth-Start. Fix wie Komodo:
|
||||||
|
```yaml
|
||||||
|
extra_hosts:
|
||||||
|
- "auth.kaleschke.info:192.168.178.58"
|
||||||
|
```
|
||||||
|
Cert validiert weiter (SNI/Hostname bleibt gleich, nur die IP wird gemappt).
|
||||||
|
Gilt fuer Mealie (bestaetigt) und sehr wahrscheinlich Paperless/Immich/Nextcloud.
|
||||||
|
- **Additiv heisst additiv:** OIDC als zusaetzlichen Login aktivieren, lokalen
|
||||||
|
Login NICHT abschalten, `AUTO_REDIRECT`/Force-OIDC aus -> kein Lockout.
|
||||||
|
- **Account-Linking per E-Mail:** Apps verknuepfen den OIDC-User i. d. R. per
|
||||||
|
E-Mail-Claim. Stimmt die Authelia-E-Mail mit dem App-Account, wird verknuepft;
|
||||||
|
sonst legt die App (bei aktivem Signup) einen neuen User an.
|
||||||
|
- **Secret-Mechanik je App verschieden:** Grafana `__FILE` (Docker-Secret),
|
||||||
|
Mealie Stack-ENV `${MEALIE_OIDC_CLIENT_SECRET}`, Paperless Stack-ENV
|
||||||
|
`${PAPERLESS_OIDC_SECRET}`. Hash immer in der Authelia-Host-Config, Klartext nie ins Repo.
|
||||||
|
|
||||||
|
## Spaetere Feinschliffe vor breitem Rollout
|
||||||
|
|
||||||
|
1. Gruppen/Rollen-Mapping: braucht es Authelia-Gruppen (z. B. `admins`, `family`) fuer
|
||||||
|
App-Rollen (Grafana Admin/Viewer, Nextcloud-Gruppen)? Wenn ja, in der Authelia
|
||||||
|
User-Datenbank Gruppen pflegen.
|
||||||
|
2. Familien-2FA spaeter neu bewerten, nachdem echte Familien-Accounts in Authelia
|
||||||
|
angelegt und TOTP pro Person verstanden ist.
|
||||||
@@ -59,7 +59,7 @@ du -sh /mnt/user/documents /mnt/user/photos /mnt/user/media /mnt/user/backups 2>
|
|||||||
| Pull der Gitea-Bundles aus `/mnt/user/backups/git-bundles/gitea` | identisch | Bundles sind klein und schnell synchronisiert |
|
| Pull der Gitea-Bundles aus `/mnt/user/backups/git-bundles/gitea` | identisch | Bundles sind klein und schnell synchronisiert |
|
||||||
| Pull des Unraid-Flash-Artefakts `unraid-flash-config.tar.gz` | bewusst nicht im H:/ Scope | Restore-Quelle bleibt Hetzner-Borg; Flash-Config wie Secret behandeln |
|
| Pull des Unraid-Flash-Artefakts `unraid-flash-config.tar.gz` | bewusst nicht im H:/ Scope | Restore-Quelle bleibt Hetzner-Borg; Flash-Config wie Secret behandeln |
|
||||||
|
|
||||||
Der konkrete Pull-Pfad ist in `docs/H_DRIVE_NEARLINE_PULL.md` und `ops/h-drive-nearline/pull-critical-backups.ps1` produktiv. Der Windows Scheduled Task `KalliLab H Drive Nearline Pull` laeuft seit 2026-05-28 taeglich 05:30.
|
Der konkrete Pull-Pfad ist in `ops/h-drive-nearline/README.md` und `ops/h-drive-nearline/pull-critical-backups.ps1` produktiv. Der Windows Scheduled Task `KalliLab H Drive Nearline Pull` laeuft seit 2026-05-28 taeglich 05:30.
|
||||||
|
|
||||||
| Abgrenzung | Bewertung | Begruendung |
|
| Abgrenzung | Bewertung | Begruendung |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|||||||
@@ -0,0 +1,307 @@
|
|||||||
|
# Entscheidungs-Register (ADR-light)
|
||||||
|
|
||||||
|
Typ: Entscheidung · Stand: 2026-06-11 · Status: aktiv
|
||||||
|
|
||||||
|
Zentrales Register fuer Architektur- und Betriebsentscheidungen. Neueste oben.
|
||||||
|
Jeder Eintrag: Entscheidung, Kontext, ggf. Alternativen und Review-Trigger.
|
||||||
|
Lange Incident-Erzaehlungen gehoeren nicht hierher, sondern in den Commit bzw.
|
||||||
|
Host-Report; hier steht das Destillat. Vorher lebten diese Eintraege verstreut
|
||||||
|
in `HOMELAB_ARCHITECTURE_MASTER_V2.md` §13, `docs/MASTER_TODO.md` (Geparkt),
|
||||||
|
`docs/HARDWARE_INVENTORY.md` und der Audit-Restliste.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-06-16 - Immich ML bekommt dediziertes Egress-Netz (Modell-Download)
|
||||||
|
|
||||||
|
**Entscheidung:** `immich_machine_learning` haengt zusaetzlich zu `immich_default`
|
||||||
|
(`internal: true`) in einem neuen, bewusst **nicht**-internen Compose-Netz
|
||||||
|
`immich_egress` und bekommt explizit `dns: 1.1.1.1/8.8.8.8`. Damit kann ML die
|
||||||
|
Modelle (CLIP `ViT-B-32__openai`, Gesichtserkennung `buffalo_l`) einmalig von
|
||||||
|
huggingface nach `model-cache` laden. DB und Redis bleiben unveraendert in
|
||||||
|
`immich_default` isoliert.
|
||||||
|
|
||||||
|
**Kontext:** Am 2026-06-16 live gemessen: ML lief zwar `healthy`, aber
|
||||||
|
`/cache` war 0 B und die Logs zeigten in Schleife
|
||||||
|
`NameResolutionError: Failed to resolve 'huggingface.co'`. Ursache: ML hing nur
|
||||||
|
im `internal: true`-Netz `immich_default` ohne Egress/DNS. Folge: Smart Search
|
||||||
|
und Gesichtserkennung waren faktisch tot, trotz gesundem Container (Healthcheck
|
||||||
|
prueft nur den HTTP-Endpoint, nicht die Modellverfuegbarkeit). Das Zielbild §7.4
|
||||||
|
sagte vorher "bleibt intern" und widersprach damit dem eigenen Einordnungsschema
|
||||||
|
§6 Schritt 2 ("braucht Internet -> nicht-internal").
|
||||||
|
|
||||||
|
**Alternativen:** (a) ML an `frontend_net` haengen — verworfen, weil die
|
||||||
|
unauthentifizierte ML-API dann im geteilten Web-Netz erreichbar waere.
|
||||||
|
(b) `immich_default` auf nicht-internal umstellen — verworfen, weil dann auch
|
||||||
|
Postgres/Redis Outbound-Internet bekaemen (Least-Privilege-Verlust). Das
|
||||||
|
dediziertes Egress-Netz isoliert den Internetbedarf auf genau ML.
|
||||||
|
|
||||||
|
**Review-Trigger:** Immich-Update, das ML-Modelle anders bezieht; Wunsch nach
|
||||||
|
vollstaendigem Air-Gap (dann Modelle offline vorseeden statt Egress); oder wenn
|
||||||
|
ML weitere Modelle braucht (Egress bleibt dafuer noetig).
|
||||||
|
|
||||||
|
## 2026-06-13 - Wetter-/Langzeitarchiv: HA schreibt nach InfluxDB 3 Core
|
||||||
|
|
||||||
|
**Entscheidung:** Home Assistant schreibt die Ecowitt-Sensoren
|
||||||
|
(`sensor.gw3000a_*`) dauerhaft nach InfluxDB 3 Core (DB `homeassistant`),
|
||||||
|
visualisiert im Grafana-Dashboard `ha-weather-archive` ("Wetterarchiv
|
||||||
|
KalliHome"). HA wurde dem bestehenden `monitoring_net` als zusaetzliches Netz
|
||||||
|
hinzugefuegt und schreibt intern an `monitoring-influxdb3-core:8181`
|
||||||
|
(v2-Write-API) - kein Host-Port, keine LAN-Exposition. Das war die im
|
||||||
|
Ecowitt-Eintrag offen gelassene Reachability-Entscheidung (Alternative:
|
||||||
|
LAN-Bind 8181).
|
||||||
|
|
||||||
|
**Kontext:** Gewuenscht war ein echtes Langzeit-Wetterarchiv unabhaengig von
|
||||||
|
HAs kurzer SQLite-Historie. HAs eingebaute Langzeit-Statistiken decken den Fall
|
||||||
|
stuendlich bereits ab; InfluxDB liefert volle Aufloesung und eigene Grafana-
|
||||||
|
Dashboards. InfluxDB 3 Core kennt nur Admin-Tokens (keine feingranularen
|
||||||
|
Scopes), daher hat der HA-Schreibtoken vollen Admin-Zugriff auf die
|
||||||
|
Monitoring-InfluxDB - bewusst akzeptiert (Operator-Freigabe), unabhaengig
|
||||||
|
widerrufbar, Token nur in Appdata-Secrets (`ha_influxdb_token` + HA
|
||||||
|
`secrets.yaml`).
|
||||||
|
|
||||||
|
**Betriebsstand 2026-06-13:** HA im `monitoring_net`, Writer aktiv (Daten in
|
||||||
|
Measurements `°C`, `%`, `hPa`, `km/h`, `W/m²`, `mm`, `lx`, `°`), zweite
|
||||||
|
Grafana-Datasource `ha-weather-influx` (DB `homeassistant`) und Dashboard
|
||||||
|
provisioniert. Glance zeigt zusaetzlich eine Live-Wetterkachel direkt aus der
|
||||||
|
HA-API (`GLANCE_HA_TOKEN`).
|
||||||
|
|
||||||
|
**Review-Trigger:** InfluxDB-3-Enterprise mit Token-Scopes (dann HA-Token
|
||||||
|
einschraenken), Wegfall des Monitoring-Stacks, oder Neubewertung der
|
||||||
|
HA-Internet-Exposition (HA haengt jetzt auch im Observability-Netz).
|
||||||
|
|
||||||
|
## 2026-06-13 - SolarEdge lokal ueber Modbus TCP angebunden
|
||||||
|
|
||||||
|
**Entscheidung:** SolarEdge wird in Home Assistant lokal ueber
|
||||||
|
`solaredge_modbus_multi` angebunden, nicht ueber die SolarEdge-Cloud-API. Der
|
||||||
|
Wechselrichter ist im LAN als `192.168.178.111` erreichbar, MAC-OUI
|
||||||
|
`84:D6:C5` gehoert zu SolarEdge, Modbus TCP laeuft auf Port `1502`, Device-ID
|
||||||
|
`1`. Die Integration liefert Inverter-, Smart-Meter- und Batterie-Entitaeten.
|
||||||
|
|
||||||
|
**Kontext:** Der Operator kann im SolarEdge-Portal keinen API-Key erzeugen; das
|
||||||
|
fruehere Setup lief bereits lokal. Der alte in der Doku genannte VONETS-Adapter
|
||||||
|
`192.168.178.71` ist nicht erreichbar und bleibt kein verlaesslicher Zielpfad.
|
||||||
|
Die native HA-Core-Integration `solaredge` waere Cloud-Polling mit Site-ID/API-
|
||||||
|
Key; `solaredge_local` erwartet dagegen die lokale HTTP-SetApp-API unter
|
||||||
|
`/web/v1/status`, die am Wechselrichter nicht offen ist. Der vorhandene
|
||||||
|
HACS-/Custom-Component-Pfad `solaredge_modbus_multi` v3.2.5 passt zur realen
|
||||||
|
Schnittstelle und wurde ohne neue Downloads wiederverwendet.
|
||||||
|
|
||||||
|
**Betriebsstand 2026-06-13:** Config-Entry `SolarEdge Local` ist `loaded`,
|
||||||
|
Polling alle 60 Sekunden, Meter- und Batterie-Erkennung aktiv, Extras und
|
||||||
|
Power-Control-Schreibfunktionen deaktiviert. Relevante Energy-Dashboard-
|
||||||
|
Kandidaten:
|
||||||
|
`sensor.solaredge_local_i1_ac_energy`,
|
||||||
|
`sensor.solaredge_local_i1_m1_ac_energy_imported`,
|
||||||
|
`sensor.solaredge_local_i1_m1_ac_energy_exported`,
|
||||||
|
`sensor.solaredge_local_i1_b1_energy_import`,
|
||||||
|
`sensor.solaredge_local_i1_b1_energy_export`. Nach der Integration wurde ein
|
||||||
|
HA-native Backup erzeugt:
|
||||||
|
`/mnt/user/appdata/homeassistant/backups/Custom_backup_2026.6.1_2026-06-13_14.59_48645373.tar`.
|
||||||
|
Das HA Energy Dashboard wurde anschliessend mit Netz, PV und Speicher aus
|
||||||
|
SolarEdge Local konfiguriert und per `energy/validate` ohne Issues geprueft.
|
||||||
|
Kosten/Preise bleiben bis zur Tibber-Anbindung leer. Nach dieser UI-State-
|
||||||
|
Aenderung wurde ein weiteres HA-native Backup erzeugt:
|
||||||
|
`/mnt/user/appdata/homeassistant/backups/Custom_backup_2026.6.1_2026-06-13_15.59_25670583.tar`.
|
||||||
|
|
||||||
|
**Trade-off:** Die lokale Modbus-Integration passt zum Prinzip "lokal vor
|
||||||
|
Cloud" und liefert deutlich bessere Betriebsdaten als die Cloud-API, ist aber
|
||||||
|
eine HACS-/Custom-Integration und damit nicht durch HA-Core getestet. Bei
|
||||||
|
Problemen zuerst Integration deaktivieren oder auf HA-Core-Cloud-Polling
|
||||||
|
zurueckfallen, sobald Site-ID/API-Key verfuegbar sind.
|
||||||
|
|
||||||
|
**Review-Trigger:** HA-Core-Upgrade mit Custom-Integration-Warnungen,
|
||||||
|
Ausfaelle von `192.168.178.111:1502`, Wechselrichtertausch/IP-Aenderung,
|
||||||
|
oder wenn Energie-Automationen schreibende Power-Control-Funktionen brauchen.
|
||||||
|
|
||||||
|
## 2026-06-13 - Ecowitt-Ingress: LAN-only Host-Bind 8123 umgesetzt
|
||||||
|
|
||||||
|
**Entscheidung:** Home Assistant bekommt einen LAN-only Host-Bind
|
||||||
|
`192.168.178.58:8123:8123` (nur LAN-IP, nicht `0.0.0.0`/WAN). Das Ecowitt-GW3000
|
||||||
|
pusht per HTTP direkt an den HA-Webhook. Damit ist die offene
|
||||||
|
Phase-2-Entscheidung (Eintrag 2026-06-12) zugunsten des LAN-Bind-Fallbacks
|
||||||
|
entschieden; ein Umbau des globalen Traefik HTTP-zu-HTTPS-Redirects entfaellt,
|
||||||
|
weil Ecowitt rein im LAN pusht und Traefik gar nicht braucht.
|
||||||
|
|
||||||
|
**Kontext:** Der globale `web`->`websecure`-Redirect auf EntryPoint-Ebene laesst
|
||||||
|
sich nicht sauber selektiv aushebeln. Der LAN-Bind ist analog zur dokumentierten
|
||||||
|
InfluxDB-8181-Ausnahme, WAN-sicher (FRITZ!Box forwardet nur 443 auf Traefik) und
|
||||||
|
ohne Traefik-Umbau. Der HA-Webhook ist nicht `local_only`; Schutz ist die
|
||||||
|
128-bit-Zufalls-Webhook-ID. Restrisiko: der Pfad ist theoretisch auch ueber
|
||||||
|
Traefik/443 erreichbar, praktisch aber unratbar.
|
||||||
|
|
||||||
|
**Review-Trigger:** Wenn der Webhook haerter abgesichert werden soll
|
||||||
|
(Traefik-IPAllowList auf `/api/webhook/` oder `local_only`), oder bei Ausbau
|
||||||
|
auf Ecowitt-Langzeitspeicherung in InfluxDB.
|
||||||
|
|
||||||
|
## 2026-06-12 - Home Assistant als Container im GitOps-Stack
|
||||||
|
|
||||||
|
**Entscheidung:** Home Assistant laeuft neu als `homeassistant` Container im
|
||||||
|
Stack `smart-home/`, nicht als HAOS-VM und nicht als Supervised-Installation.
|
||||||
|
Mosquitto laeuft als eigener Container im selben Stack; Zigbee2MQTT und ESPHome
|
||||||
|
werden spaeter ebenfalls als eigenstaendige Container ergaenzt. HA haengt in
|
||||||
|
`frontend_net` fuer Traefik und in `smarthome_net` fuer MQTT/Zigbee2MQTT/ESPHome.
|
||||||
|
Das Fachrepo `smart-home-kalli` liefert versionierte HA-YAML-Dateien read-only;
|
||||||
|
`.storage`, `secrets.yaml` und Integrations-State bleiben in
|
||||||
|
`/mnt/user/appdata/homeassistant`.
|
||||||
|
|
||||||
|
**Kontext:** Das fruehere HAOS-VM-Setup ging bei einem Crash ohne brauchbares
|
||||||
|
Backup verloren. Das Homelab betreibt produktive Dienste inzwischen ueber
|
||||||
|
Gitea, Komodo, Compose, Renovate und Borg. HA Container passt in dieses
|
||||||
|
Betriebsmodell und vermeidet eine zweite Update-/Backup-Welt. Supervised ist
|
||||||
|
kein Zielpfad mehr; HAOS bleibt die Alternative, falls Add-on-Komfort,
|
||||||
|
Matter/Thread/HomeKit-Discovery oder Host-nahe HA-Funktionen wichtiger werden
|
||||||
|
als GitOps-Konformitaet.
|
||||||
|
|
||||||
|
**Betriebsstand 2026-06-13:** Owner-Onboarding ist abgeschlossen, die
|
||||||
|
temporaere Authelia-Onboarding-Guard-Middleware ist entfernt, `smart-home`
|
||||||
|
existiert als Komodo-Stack mit Gitea-Webhook, HA-native `backup.create` erzeugt
|
||||||
|
ein lesbares Backup-Artefakt, und der Mosquitto-Broker besteht einen
|
||||||
|
authentifizierten Publish/Subscribe-Smoke. Die Restore-Probe wurde am
|
||||||
|
2026-06-13 erfolgreich abgeschlossen: HA-native Backup + Mosquitto-Appdata +
|
||||||
|
Fachrepo-Clone wurden isoliert gestartet, HA HTTP/API/check_config waren gruen,
|
||||||
|
MQTT Publish/Subscribe und retained Topic nach Broker-Restart waren gruen.
|
||||||
|
Report: `/mnt/user/backups/restore-reports/homeassistant-2026-06-13.md`.
|
||||||
|
Die HA-MQTT-Integration wurde anschliessend am 2026-06-13 ueber den
|
||||||
|
Home-Assistant-Config-Flow verbunden; Config-Entry `smarthome-mosquitto` ist
|
||||||
|
`loaded`, Mosquitto sieht den HA-Client mit User `homeassistant`, und
|
||||||
|
`check_config` ist gruen. Damit ist die Foundation abgeschlossen. Naechster
|
||||||
|
Produktivschritt ist Tibber, danach SolarEdge mit bewusster Entscheidung
|
||||||
|
zwischen schneller Cloud-Integration und lokalem Modbus-TCP.
|
||||||
|
|
||||||
|
**Review-Trigger:** Viele mDNS-/SSDP-abhaengige lokale Integrationen
|
||||||
|
(HomeKit, Cast, Matter/Thread), Bedarf an HA-Add-ons als Betriebsstandard,
|
||||||
|
oder wiederholte Probleme durch Bridge-Netzwerkbetrieb.
|
||||||
|
|
||||||
|
## 2026-06-12 - Ecowitt-Ingress bleibt bewusste Phase-2-Entscheidung
|
||||||
|
|
||||||
|
**Entscheidung:** In Phase 1 wird kein Host-Port `8123` fuer Home Assistant
|
||||||
|
veroeffentlicht. Ecowitt wird spaeter entweder ueber eine gezielte
|
||||||
|
Traefik-HTTP-Ausnahme fuer den Webhook-Pfad angebunden oder, falls der globale
|
||||||
|
HTTP-zu-HTTPS-EntryPoint-Redirect nicht sauber selektiv abloesbar ist, ueber
|
||||||
|
einen dokumentierten LAN-only Host-Port `8123`.
|
||||||
|
|
||||||
|
**Kontext:** Ecowitt kann nur HTTP und kein HTTPS. Traefik hat aktuell einen
|
||||||
|
globalen `web` -> `websecure` Redirect auf EntryPoint-Ebene. Ein normaler
|
||||||
|
HTTP-Router kann diese Regel voraussichtlich nicht umgehen, ohne Traefik selbst
|
||||||
|
umzubauen. Deshalb wird die Entscheidung nicht vorgezogen.
|
||||||
|
|
||||||
|
**Review-Trigger:** Start der Ecowitt-/InfluxDB-Phase oder Umbau der Traefik
|
||||||
|
HTTP-Redirect-Architektur.
|
||||||
|
|
||||||
|
## 2026-06-11 — Host-DNS-Fallback aktiv (AdGuard-SPOF entschaerft)
|
||||||
|
|
||||||
|
**Entscheidung:** Unraid-Host nutzt `eth0` DNS server 1 = `192.168.178.58` (AdGuard) und **DNS server 2 = `192.168.178.1`** (FRITZ!Box) als Failover.
|
||||||
|
**Kontext:** AdGuard war einziger LAN-Resolver; ein Recreate hat 2026-06 einen Bulk-Deploy zerlegt, weil Docker-Pulls am eigenen DNS-Container scheiterten. Der Fallback bleibt nur passiv aktiv (Go-Resolver springt erst bei Socket-Fehler weiter), der Filter wirkt im Normalbetrieb unveraendert. `options rotate` ist nicht gesetzt. Umsetzung der Empfehlung 3a aus dem Optimierungs-Assessment vom 2026-06-10. Runbook: `docs/runbooks/komodo-bulk-deploy-dns.md`.
|
||||||
|
**Review-Trigger:** Wenn AdGuard durch eine andere Filter-Loesung ersetzt wird oder ein zweiter Host-Resolver verfuegbar ist.
|
||||||
|
|
||||||
|
## 2026-06-11 — Hetzner Storage Box: automatische Snapshots aktiv
|
||||||
|
|
||||||
|
**Entscheidung:** Automatische Snapshots auf der Hetzner Storage Box (BX11, `u565255.your-storagebox.de`) sind aktiv: taeglich um 05:30 UTC (nach dem Borg-Lauf 04:30 lokal), Retention 7 Tage, Snapshot-Verzeichnis sichtbar fuer Einzeldatei-Restore via `.zfs/snapshot/`.
|
||||||
|
**Kontext:** Borg `append-only` ist bewusst nicht umgesetzt (siehe Eintrag 2026-06-01); damit war ein kompromittierter Host bisher in der Lage, auch das Off-site-Backup zu loeschen. Storage-Box-Snapshots sind host-seitig nicht loeschbar und im BX11-Tarif inklusive. Kosten: 0 EUR zusaetzlich. Umsetzung der Empfehlung 2 aus dem Optimierungs-Assessment vom 2026-06-10.
|
||||||
|
**Review-Trigger:** Hetzner-Quota-Druck (aktuell 65 GB / 1 TB - viel Luft) oder Aenderung der Backup-Strategie.
|
||||||
|
|
||||||
|
## 2026-06-11 — Doku-Konsolidierung: ein Fakt, ein Zuhause
|
||||||
|
|
||||||
|
**Entscheidung:** Die Dokumentation wird nach `docs/archive/2026/homelab-doku-optimierung-2026-06-11.md` konsolidiert: `MASTER_TODO.md` ist die einzige Statusliste, dieses Register die einzige Entscheidungssammlung, `docs/archive/` nimmt abgeschlossene Snapshots auf, Erledigtes verlaesst die Arbeitskopie. Keine Ordner-Restruktur des Bestands.
|
||||||
|
**Kontext:** 74 Markdown-Dateien / ~9.400 Zeilen; einzelne Sachverhalte waren an 6–9 Stellen dokumentiert; vier parallele Statuslisten.
|
||||||
|
**Review-Trigger:** Quartals-Gaertnern (siehe `docs/REPO_MAP.md` Doku-Regeln).
|
||||||
|
|
||||||
|
## 2026-06-06 — baerchen: BitLocker und Veeam Storage Encryption bewusst aus
|
||||||
|
|
||||||
|
**Entscheidung:** BitLocker bleibt auf allen Laufwerken deaktiviert; Veeam Storage Encryption bleibt aus (`StorageEncryptionEnabled=False`).
|
||||||
|
**Kontext:** Recovery laeuft ueber das Veeam-Image auf dem lokalen SMB-Share; kein Key-Management-Aufwand, Restrisiko physischer Diebstahl akzeptiert.
|
||||||
|
**Review-Trigger:** Off-host-Auslagerung des Windows-Images oder geaendertes Risikoprofil. Runbook: `ops/windows-reinstall/docs/windows-image-backup-baseline.md`.
|
||||||
|
|
||||||
|
## 2026-06-06 — Tailscale: natives Unraid-Plugin kanonisch, restriktive ACL
|
||||||
|
|
||||||
|
**Entscheidung:** Tailscale laeuft ausschliesslich als natives Unraid-Plugin (`tailscale.plg`, Subnet-Router, State im Flash-Backup); der redundante userspace-Docker-Stack `host-services/tailscale/` wurde entfernt. Tailnet-ACL ist tag-basiert restriktiv (`tag:server`/`tag:operator`, `tag:family` schlafend), Default-Allow entfernt.
|
||||||
|
**Kontext:** Zwei parallele `tailscaled`-Instanzen; nur die Plugin-Instanz routet. Details: `docs/NETWORK_INVENTORY.md`.
|
||||||
|
**Review-Trigger:** Erstes reales Familiengeraet (Familien-Dienste in ACL konkretisieren).
|
||||||
|
|
||||||
|
## 2026-06-06 — Authelia: 2FA-Catch-all aktiv, OIDC-Rollout gestaffelt
|
||||||
|
|
||||||
|
**Entscheidung:** Catch-all `*.kaleschke.info` -> `two_factor` in Repo- und Host-Config. OIDC-SSO wird app-weise ausgerollt (live: Grafana, Mealie; deployed: Paperless). Immich- und Nextcloud-OIDC sowie Nextcloud-Operator-TOTP sind geparkt, bis Familien-Accounts existieren.
|
||||||
|
**Kontext:** Nur der Operator hat aktuell einen Authelia-Account; Familien-SSO-Nutzen entsteht erst mit dem Onboarding. Runbook: `docs/AUTHELIA_OIDC_PLAN.md`.
|
||||||
|
**Review-Trigger:** Family-Onboarding erreicht die App-Login-Ebene.
|
||||||
|
|
||||||
|
## 2026-06-05 — USV geparkt, Cold-Backup Hetzner-only, kein Strom-Monitoring
|
||||||
|
|
||||||
|
**Entscheidung:** Keine USV-Anschaffung dieses Quartal (Power-Loss bewusst akzeptiert). Off-site bleibt allein Hetzner-Borg, keine zweite rotierende Cold-Kopie. Stromverbrauch wird nicht gemessen (kein Messgeraet, kein Beschaffungs-Todo).
|
||||||
|
**Review-Trigger:** USV: Q3-Review ab 2026-07-01, Hardware-Upgrade oder realer Stromausfall mit Datenfolge. Cold-Backup: Hetzner-Probleme oder stark wachsender Datenwert. Strom: nur bei Anschaffung eines Messgeraets.
|
||||||
|
|
||||||
|
## 2026-06-03 — Fix Common Problems Plugin entfernt, keine Neuinstallation
|
||||||
|
|
||||||
|
**Entscheidung:** FCP wurde deinstalliert und wird bewusst nicht wieder installiert.
|
||||||
|
**Kontext:** Ein FCP-Scan hing 7 Tage in einem `grep -R`-Symlink-Loop ueber das gesamte Array (3 Cores 100 %, IOWAIT bis 55 %, Load 14.6 -> 1.08 nach Entfernung). Die abgedeckten Risiken uebernehmen Scrutiny, Monitoring-Stack, Posture-Check und Critical-Events-Watcher.
|
||||||
|
**Review-Trigger:** keiner; Entscheidung ist final.
|
||||||
|
|
||||||
|
## 2026-06-01 — Borg append-only auf Hetzner nicht umgesetzt
|
||||||
|
|
||||||
|
**Entscheidung:** Kein append-only/forced-command auf der Storage Box.
|
||||||
|
**Kontext:** Der forced-command-Test brach die Key-Auth und musste per Passwort-Recovery zurueckgesetzt werden; Nutzen/Betriebsrisiko-Verhaeltnis unguenstig. Kompensation (Storage-Box-Snapshots) siehe `docs/homelab-optimierung.md` Empfehlung 2.
|
||||||
|
**Review-Trigger:** Hetzner bietet robusteren Mechanismus, oder Ransomware-Risikoprofil aendert sich.
|
||||||
|
|
||||||
|
## 2026-05-28 — Plex: Reclaim, Traefik-Route ohne ForwardAuth, kein Remote Access
|
||||||
|
|
||||||
|
**Entscheidung:** Plex-Server ist als Operator-Konto geclaimt; externer Zugriff laeuft ausschliesslich ueber Traefik/443 (`plex.kaleschke.info`, File-Provider-Ausnahme wegen Host-Netz), Plex Remote Access und WAN-Port 32400 bleiben aus, keine Authelia-ForwardAuth (native Plex-Auth).
|
||||||
|
**Kontext:** Preferences waren nach dem Mai-Crash jungfraeulich; Claim-Token wurde nur als Shell-Inline-ENV genutzt, nie persistiert. Details: `docs/SERVICE_CATALOG.md`, `HOMELAB_ARCHITECTURE_MASTER_V2.md` §10.
|
||||||
|
|
||||||
|
## 2026-05-28 — Gitea-SSH (222) bleibt ohne WAN-Freigabe
|
||||||
|
|
||||||
|
**Entscheidung:** Port 222 wird nicht in der FRITZ!Box freigegeben.
|
||||||
|
**Kontext:** Tailscale ist der Operator-Pfad, der GitHub-Mirror deckt DR-Bootstrap ab, SSH-Brute-Force-Vektor extern vermeiden.
|
||||||
|
|
||||||
|
## 2026-05-28 — paperless-gpt und BentoPDF bleiben aktiv
|
||||||
|
|
||||||
|
**Entscheidung:** Beide Container bleiben trotz geringer Nutzung. paperless-gpt-Abloese wird erst mit Paperless-NGX 3.0 (eigene KI-Features) neu bewertet; BentoPDF ist situatives Tool mit vernachlaessigbarem Footprint und ersetzt Stirling-PDF.
|
||||||
|
**Review-Trigger:** Paperless-NGX-3.0-Release.
|
||||||
|
|
||||||
|
## 2026-05-26 — AdGuard-Admin nur auf Tailscale-IP, ohne Traefik/2FA
|
||||||
|
|
||||||
|
**Entscheidung:** Admin-UI bleibt auf `100.80.98.33:8082` (Tailscale-only) gebunden; bewusst keine Traefik-/2FA-Umstellung. DNS-Port 53 bleibt direkte Host-Port-Ausnahme.
|
||||||
|
**Review-Trigger:** Aenderung des Tailnet-Zugangsmodells.
|
||||||
|
|
||||||
|
## 2026-05-25 — Ein Dienst pro Funktion: Jellyfin, Homepage, Uptime-Kuma entfernt
|
||||||
|
|
||||||
|
**Entscheidung:** Plex ist der einzige Medienserver, Glance das einzige Dashboard, Blackbox-Exporter + Prometheus-Alerts + Grafana ersetzen Uptime-Kuma.
|
||||||
|
**Kontext:** Doppelte Dienste = doppelte Pflege/Attack-Surface. Removal-Checkliste: `docs/WORKFLOW.md`.
|
||||||
|
|
||||||
|
## 2026-05-17 — Monitoring-/Logging-Baseline
|
||||||
|
|
||||||
|
**Entscheidung:** `monitoring/` ist der einzige Observability-Stack (Prometheus, Loki, Promtail, Grafana, Exporter, InfluxDB 3 Core). Loki intern ohne Route, Promtail mit read-only Docker-Socket, Loki-Daten sind Diagnosematerial mit Retention, keine Restore-Quelle. Alte Pfade `ops/loki`/`ops/grafana-influxdb` sind entfernt (Rollback nur via Git-Historie).
|
||||||
|
|
||||||
|
## 2026-05-05 — Stateful Digest-Pinning und Versionspolitik
|
||||||
|
|
||||||
|
**Entscheidung:** Tier-1-/stateful Dienste laufen mit sprechendem Versions-Tag plus Digest (z. B. `postgres:17.x@sha256:...`); mutable Tags wurden 2026-04-17 auf laufende Digests eingefroren. Digest-Pinning ist Reproduzierbarkeit, kein Upgrade-Mechanismus; echte Upgrades sind eigene Aenderungsbloecke. Renovate (live seit 2026-05-29) liefert PRs, kein Auto-Merge.
|
||||||
|
**Review-Trigger:** Mutable-Tag-Restbestand siehe `docs/homelab-optimierung.md` Empfehlung 1.
|
||||||
|
|
||||||
|
## 2026-05-04 — Authelia ohne Redis-Session-Backend
|
||||||
|
|
||||||
|
**Entscheidung:** Authelia nutzt PostgreSQL fuer Storage, aber kein Redis-Session-Backend; nach Restart werden Sessions neu aufgebaut.
|
||||||
|
**Kontext:** Haelt den Tier-1-Auth-Pfad einfach. `infra/redis` ist faktisch nur Paperless-Cache; Konsolidierung nach `apps/paperless/` bleibt denkbar, unpriorisiert.
|
||||||
|
|
||||||
|
## 2026-05-04 — Komodo-Self-Stack: Reconcile-Regel nach Drift
|
||||||
|
|
||||||
|
**Entscheidung:** Der Komodo-Self-Stack laeuft aus `/mnt/user/services/stacks/komodo/compose.yaml` (Quelle: `ops/komodo/docker-compose.yml`). Bei Self-Stack-Drift kein pauschales `docker compose up -d`, wenn der Dry-run `komodo-mongo` recreaten wuerde; Core/Periphery gezielt mit `--no-deps` neu erstellen, Mongo unangetastet lassen.
|
||||||
|
**Kontext:** Drift-Recovery 2026-05-04 (Repair-YAMLs aus `/tmp`); Sicherungen unter `/mnt/user/appdata/komodo/_drift_backup_2026-05-04/`.
|
||||||
|
|
||||||
|
## 2026-04-19 — Nextcloud als klassischer Stack, nicht AIO; native Auth
|
||||||
|
|
||||||
|
**Entscheidung:** Nextcloud laeuft als App + eigene PostgreSQL + eigene Redis (kein AIO), ohne zentrale ForwardAuth (Browser-/Client-/WebDAV-Flows brauchen native Auth).
|
||||||
|
|
||||||
|
## 2026-04-12 — Borg-Scope enthaelt bewusst /local/secrets
|
||||||
|
|
||||||
|
**Entscheidung:** Borg sichert ausgewaehltes Secret-Material (`/local/secrets`) als Teil der DR-Strategie; `borg-ui` hat dafuer breite, bewusste Mounts. Dumps statt Raw-DB-Pfade sind der primaere Restore-Weg.
|
||||||
|
**Kontext:** `ops/borg-ui/BACKUP_SCOPE.md`.
|
||||||
|
|
||||||
|
## 2026-03-28/29 — GitOps-Fundament
|
||||||
|
|
||||||
|
**Entscheidung:** Komodo ersetzt Portainer als alleiniger Stack-Manager (Docker-Socket-Ausnahme, native Auth ohne pauschale ForwardAuth wegen Webhooks/`/ws/periphery`). Traefik routet ausschliesslich ueber Docker-Labels; File-Provider nur fuer `middlewares.yml`, `tls.yml`, `dashboards.yml` (+ dokumentierte `plex.yml`-Ausnahme). AdGuard Home + Unbound ersetzen Pi-hole.
|
||||||
|
**Kontext:** Konkurrierende `@file`-/`@docker`-Router hatten Fehlrouting verursacht; Regel: keine neuen Service-Routen im File-Provider.
|
||||||
|
|
||||||
|
## Aelteres / Sonderfaelle
|
||||||
|
|
||||||
|
- **Paperless Stack-ENV-Ausnahme:** `PAPERLESS_DBPASS`/`PAPERLESS_REDIS` bleiben Komodo-Stack-ENV (kein `_FILE`-Support im Image); Konsequenzen fuer DR siehe `docs/DISASTER_RECOVERY.md` Phase 2.
|
||||||
|
- **ddns-updater in `frontend_net`:** braucht Cloudflare-API; `backend_net` ist internal.
|
||||||
|
- **mail-archiver Hybrid:** `frontend_net` (IMAP) + `backend_net` (DB), App-Auth zusaetzlich zu Authelia.
|
||||||
|
- Vollstaendige technische Ausnahmen-Liste mit Begruendung: `HOMELAB_ARCHITECTURE_MASTER_V2.md` §10 (bleibt dort autoritativ).
|
||||||
@@ -8,7 +8,7 @@ Verwandte Dokumente:
|
|||||||
|
|
||||||
- `docs/ROLLBACK.md` - Rueckweg bei Fehlern im laufenden GitOps-Betrieb
|
- `docs/ROLLBACK.md` - Rueckweg bei Fehlern im laufenden GitOps-Betrieb
|
||||||
- `docs/RESTORE_MATRIX.md` - Restore-Quellen und Verifikationsregeln pro Dienst
|
- `docs/RESTORE_MATRIX.md` - Restore-Quellen und Verifikationsregeln pro Dienst
|
||||||
- `docs/RESTORE_HANDBOOK.md` - praktische Restore-Betriebsanleitung
|
- `ops/restore-tests/README.md` - Restore-Test-Betrieb und Werkzeuge
|
||||||
- `docs/SERVICES_RECOVERY.md` - Recovery-kritische `/mnt/user/services`-Pfade, Gitea-Mirror und Komodo-Bootstrap
|
- `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
|
- `docs/EXTERNAL_DEPENDENCIES.md` - externe Provider/Konten und Ausfall-Szenarien
|
||||||
- `ops/borg-ui/BACKUP_SCOPE.md` - Zielbild des Borg-Scopes
|
- `ops/borg-ui/BACKUP_SCOPE.md` - Zielbild des Borg-Scopes
|
||||||
@@ -290,7 +290,14 @@ Erfolgskriterium: `docker network ls` zeigt `frontend_net`, `backend_net`, `moni
|
|||||||
|
|
||||||
1. `traefik/`
|
1. `traefik/`
|
||||||
2. `host-services/Adguard/`
|
2. `host-services/Adguard/`
|
||||||
3. `host-services/tailscale/`
|
|
||||||
|
> **Tailscale-Hinweis:** Tailscale laeuft als **natives Unraid-Plugin**
|
||||||
|
> (`tailscale.plg`, Interface `tailscale1`, State `/boot/config/plugins/tailscale/state`,
|
||||||
|
> im Flash-Backup gesichert) und ist der Subnet-Router fuer `192.168.178.0/24`.
|
||||||
|
> Es ist **kein** Compose-/Komodo-Stack mehr und kommt mit dem Host hoch — daher
|
||||||
|
> nicht in dieser Bootstrap-Liste. Der frueher hier gelistete Docker-Stack
|
||||||
|
> `host-services/tailscale/` (userspace-only, redundant) wurde am 2026-06-06
|
||||||
|
> entfernt (siehe `docs/NETWORK_INVENTORY.md`).
|
||||||
|
|
||||||
**LE-Rate-Limit-Vorsicht:** Wenn `/mnt/user/appdata/traefik/letsencrypt/acme.json` verloren oder unklar ist, zuerst gegen Let's Encrypt Staging ausstellen lassen (`--certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory`). Erst nach gruenem Smoke wieder auf Production-CA. Hintergrund: 50 Zertifikate pro Domain pro Woche reicht bei einem hektischen Wiederanlauf nicht, wenn man die Sub-Domains mehrfach hochzieht.
|
**LE-Rate-Limit-Vorsicht:** Wenn `/mnt/user/appdata/traefik/letsencrypt/acme.json` verloren oder unklar ist, zuerst gegen Let's Encrypt Staging ausstellen lassen (`--certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory`). Erst nach gruenem Smoke wieder auf Production-CA. Hintergrund: 50 Zertifikate pro Domain pro Woche reicht bei einem hektischen Wiederanlauf nicht, wenn man die Sub-Domains mehrfach hochzieht.
|
||||||
|
|
||||||
@@ -558,15 +565,14 @@ und physisch ausserhalb des Rechners abgelegt sein.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 11. Offene Vorbereitungs-To-dos
|
## 11. Laufende Vorbereitung
|
||||||
|
|
||||||
- Unraid-USB-/Flash-Backup regelmaessig ueber `unraid-flash-config.tar.gz` und optional Unraid Connect pruefen
|
Offene Punkte werden in `docs/MASTER_TODO.md` gefuehrt. Daueraufgaben:
|
||||||
- Borg-Passphrase ist laut Operator-Bestaetigung vom 2026-05-26 extern/offline hinterlegt; bei Reviews nur Existenz/Lesbarkeit der Offline-Kopie pruefen, nie den Wert dokumentieren
|
|
||||||
- Komodo Stack-ENV-Werte zentral ausserhalb von Komodo dokumentieren
|
- Unraid-Flash-Artefakt regelmaessig pruefen (`ops/maintenance/check-unraid-flash-backup.sh`)
|
||||||
- regelmaessige automatisierte Restore-Smoke-Tests fuer Vaultwarden, Gitea und Paperless etablieren
|
- Offline-Kopien (Borg-Passphrase, KOMODO_*-Notiz, DR-Keys) bei Reviews nur auf Auffindbarkeit pruefen, nie Werte dokumentieren
|
||||||
- `komodo-mongo`-Dump nach Major-Upgrades gezielt kontrollieren
|
- `komodo-mongo`-Dump nach Major-Upgrades gezielt kontrollieren
|
||||||
- `baerchen` Recovery-USB-Boot-/SMB-Test nach erfolgreichem erstem Full-Lauf
|
- Restore-Drills nach Kadenz aus `ops/restore-tests/schedule.md` rotieren
|
||||||
verifizieren
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -115,6 +115,15 @@ Erwartet: HEAD und mindestens ein `refs/heads/master`-Eintrag.
|
|||||||
|
|
||||||
Damit der "ich pruefe das vierteljaehrlich"-Schritt zur Routine wird, ein kleines Skript ins WSL-Home:
|
Damit der "ich pruefe das vierteljaehrlich"-Schritt zur Routine wird, ein kleines Skript ins WSL-Home:
|
||||||
|
|
||||||
|
Stand 2026-06-06: Das Skript liegt zusaetzlich versioniert unter
|
||||||
|
`ops/maintenance/dr-workstation-smoke.sh` und wurde auf `baerchen` bereits nach
|
||||||
|
`~/dr-smoke.sh` in die Ubuntu-WSL kopiert. Borg 1.2.8 ist installiert, die
|
||||||
|
DR-Key-Arbeitskopien liegen unter `~/.ssh/dr-readonly` und
|
||||||
|
`~/.ssh/dr-hetzner`, GitHub-Read-Smoke und Hetzner-SSH-Smoke sind erfolgreich.
|
||||||
|
Der finale Borg-Smoke via `bash ~/dr-smoke.sh` wurde am 2026-06-06 ebenfalls
|
||||||
|
erfolgreich gefahren (`DR-Smoke OK (2026-06-06 10:05:30)`). Die Borg-Passphrase
|
||||||
|
wurde nur interaktiv eingegeben und nicht gespeichert.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cat > ~/dr-smoke.sh <<'EOF'
|
cat > ~/dr-smoke.sh <<'EOF'
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
@@ -158,9 +167,7 @@ Nach erfolgreicher Einrichtung im Repo dokumentieren. In `docs/EXTERNAL_DEPENDEN
|
|||||||
| 2026-06-XX | DR-Workstation produktiv: WSL2 Ubuntu auf Gaming-PC, borgbackup installiert, Hetzner-DR-Key und GitHub-Deploy-Key in ~/.ssh, Quartals-Smoke-Skript ~/dr-smoke.sh. Bare-Metal-DR-Pillars sind damit alle vier produktionsreif. | Quartalsweise Smoke laufen lassen |
|
| 2026-06-XX | DR-Workstation produktiv: WSL2 Ubuntu auf Gaming-PC, borgbackup installiert, Hetzner-DR-Key und GitHub-Deploy-Key in ~/.ssh, Quartals-Smoke-Skript ~/dr-smoke.sh. Bare-Metal-DR-Pillars sind damit alle vier produktionsreif. | Quartalsweise Smoke laufen lassen |
|
||||||
```
|
```
|
||||||
|
|
||||||
Audit-Restliste analog: in `docs/AUDIT_2026-05-25_TODO.md` den P1 "DR-Workstation Bare-Metal-Kit: WSL2 + Borg-Client installieren" auf erledigt setzen und unter "Zuletzt geschlossen" einen Eintrag mit Smoke-Ergebnis machen.
|
Falls der Punkt noch als offen in `docs/MASTER_TODO.md` steht, dort in den Kurzlog uebernehmen.
|
||||||
|
|
||||||
Wenn ich (Claude) am Tag der Einrichtung mit SSH-Zugang dabei bin, ziehe ich das nach. Sonst per `git add docs/EXTERNAL_DEPENDENCIES.md docs/AUDIT_2026-05-25_TODO.md && git commit && git push`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ Dieses Dokument beschreibt externe Anbieter und Konten, von denen Betrieb, Recov
|
|||||||
| OpenAI API | Paperless-GPT LLM und Vision-OCR | mittel | Automatische Dokument-Titel, Tags, Korrespondenten und LLM-OCR fallen aus; Paperless selbst laeuft weiter | OpenAI-Projekt/API-Key ausserhalb Repo | Key in Vaultwarden/Komodo sichern, bei Offenlegung rotieren; Kosten/Usage im OpenAI-Projekt beobachten |
|
| OpenAI API | Paperless-GPT LLM und Vision-OCR | mittel | Automatische Dokument-Titel, Tags, Korrespondenten und LLM-OCR fallen aus; Paperless selbst laeuft weiter | OpenAI-Projekt/API-Key ausserhalb Repo | Key in Vaultwarden/Komodo sichern, bei Offenlegung rotieren; Kosten/Usage im OpenAI-Projekt beobachten |
|
||||||
| 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 |
|
| 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 |
|
| 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 |
|
| Plex Konto | Plex native Auth, Claim und Client-Zugriff ueber `plex.kaleschke.info` | mittel | Plex-Web/App-Login und Clients koennen ausfallen; LAN-Medienpfade bleiben lokal | Plex-Konto ausserhalb Repo; `PLEX_CLAIM` nur fuer Setup | Plex Remote Access bleibt aus; externer Zugriff laeuft ueber Traefik/443. 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 |
|
| 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 |
|
||||||
| Operator-DR-Workstation | Bare-Metal-Recovery-Arbeitsplatz (Gaming-PC Windows, lokaler Repo-Clone `G:\Gitea_Clone\homelab-infra`) | kritisch | Ohne Workstation kein Borg-Extract, kein Hetzner-Zugriff, kein Repo-Bootstrap; der Unraid-Host ist im Bare-Metal-Fall gerade weg | Operator-PC, WSL2 + Borg-Client, SSH-Key fuer Hetzner Storage Box, Offline-Kopie der Borg-Passphrase | Setup als bewusste DR-Vorbedingung pflegen (siehe Abschnitt "DR-Workstation Bare-Metal-Kit") |
|
| Operator-DR-Workstation | Bare-Metal-Recovery-Arbeitsplatz (Gaming-PC Windows, lokaler Repo-Clone `G:\Gitea_Clone\homelab-infra`) | kritisch | Ohne Workstation kein Borg-Extract, kein Hetzner-Zugriff, kein Repo-Bootstrap; der Unraid-Host ist im Bare-Metal-Fall gerade weg | Operator-PC, WSL2 + Borg-Client, SSH-Key fuer Hetzner Storage Box, Offline-Kopie der Borg-Passphrase | Setup als bewusste DR-Vorbedingung pflegen (siehe Abschnitt "DR-Workstation Bare-Metal-Kit") |
|
||||||
|
|
||||||
@@ -96,14 +96,6 @@ Operative Regel: Die DR-Workstation wird nicht als Test-/Spiel-PC betrachtet. WS
|
|||||||
|
|
||||||
| Datum | Ergebnis | Naechste Aktion |
|
| Datum | Ergebnis | Naechste Aktion |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 2026-05-26 | Bekannte externe Abhaengigkeiten aus Repo-/Betriebsdoku dokumentiert; keine Secret-Werte aufgenommen. Borg-Passphrase ist laut Operator offline gesichert. | Account-Besitz, 2FA-Recovery-Codes und Zahlungswege extern bestaetigen |
|
| 2026-05-26 bis 2026-06-03 | Baseline und Haertung abgeschlossen: externe Abhaengigkeiten dokumentiert; FRITZ!Box-WAN auf 443/tcp bereinigt, Remote-Dienste aus, Konfig-Backup in Vaultwarden; Hetzner-Account-Hygiene (2FA, Recovery Key offline); KOMODO_*-Notiz und GitHub-Read-Deploy-Key offline gesichert. Detailhistorie in Git. | Keine Folgeaktion |
|
||||||
| 2026-05-26 | Telekom-DSL und FRITZ!Box 7590 (damals FRITZ!OS 8.21) als WAN-/Router-Abhaengigkeit aufgenommen; Ausfallschutz nicht eingerichtet | FRITZ!OS-Update am 2026-06-01 als `154.08.25` beobachtet |
|
|
||||||
| 2026-05-28 | FRITZ!Box-Portfreigaben bereinigt: aktiv bleibt nur `443/tcp`; `80/tcp` entfernt, `222/tcp` bewusst nicht angelegt; UPnP-Recht fuer VONETS-Bridge deaktiviert | IPv6-/Dienste-Review am 2026-06-01 nachgezogen |
|
|
||||||
| 2026-06-01 | Externer Betreibercheck vorbereitet: `docs/EXTERNAL_OPERATOR_RUNBOOK.md` und `ops/maintenance/check-external-operator.sh`; FRITZ!Box meldet per TR-064 FRITZ!OS `154.08.25`, Public DNS hat keine AAAA-Records, Host hat keine globale Provider-IPv6 | Account-Hygiene am 2026-06-01 nachgezogen |
|
|
||||||
| 2026-06-01 | FRITZ!Box-UI gegengeprueft und Konfig-Backup extern/off-system in Vaultwarden abgelegt; Remote-HTTPS auf FRITZ!Box-UI aus, FTP/FTPS auf Speichermedien aus, nur `443/tcp -> 192.168.178.58`, keine aktive IPv6-Freigabe sichtbar, UPnP-Selbstfreigaben aus | Bei naechstem Router-Update erneut exportieren |
|
|
||||||
| 2026-06-01 | Hetzner-Account-Hygiene erledigt: externe Mail ok, Zahlung ok, 2FA aktiv, Recovery Key offline gedruckt. Storage Box: SSH aktiv, SMB/WebDAV aus, Maintenance-Key in Vaultwarden, Borg-Repo-Zugriff nach Recovery geprueft. Borg `append-only` wird bewusst nicht umgesetzt. | Keine Folgeaktion |
|
|
||||||
| 2026-06-03 | Hetzner Storage Box Maintenance-Key zusaetzlich offline gesichert bestaetigt (Operator-Antwort im DR-Tabletop 2026-06-03). Damit ist der Hetzner-Zugang im Bare-Metal-Fall ohne Vaultwarden moeglich. | Keine Folgeaktion |
|
|
||||||
| 2026-06-03 | DR-Tabletop ergibt drei offene Bootstrap-Bloecke: KOMODO_*-Notiz nicht offline, GitHub-Mirror-Read-PAT/Deploy-Key nicht angelegt, DR-Workstation nicht als DR-Kit konfiguriert. Details in `docs/DR_DRILL_2026-06-03.md` und Folge-Tasks in `docs/AUDIT_2026-05-25_TODO.md`. | KOMODO_*-Notiz erzeugen, Read-PAT erzeugen, WSL2+Borg auf Gaming-PC einrichten |
|
|
||||||
| 2026-06-03 | KOMODO_*-Notiz offline gesichert (Operator-Bestaetigung im DR-Tabletop-Followup). Quelle bleibt host-seitige `.env` (`/mnt/user/services/stacks/komodo/.env`) bzw. Drift-Recovery-Kopie vom 2026-05-04. Bare-Metal-Komodo-Bootstrap ist damit ohne Vaultwarden moeglich. | Restliche P1-Operator-Aufgaben: GitHub-Read-PAT, DR-Workstation-Setup, Nextcloud-Restore-Test |
|
|
||||||
| 2026-06-03 | GitHub-Mirror Read-Only Deploy-Key `DR Read-Only 2026-06-03` (ed25519, Passphrase-frei) erzeugt, in GitHub Repo Settings ohne Write-Access hinterlegt, Smoke `git ls-remote` erfolgreich (`d947c7f` matched master HEAD), Private-Key offline neben KOMODO_*-Notiz abgelegt, Arbeitsplatz-Kopie geloescht. | Restliche P1-Operator-Aufgaben: DR-Workstation-Setup, Nextcloud-Restore-Test |
|
|
||||||
| 2026-06-03 | Hetzner Storage Box DR-SSH-Key `dr-hetzner-2026-06-03` (ed25519, Passphrase-frei) erzeugt, via `install-ssh-key` auf Storage Box `u565255.your-storagebox.de:23` autorisiert, passwortloser Login erfolgreich (Borg-Repos sichtbar), Private-Key offline neben KOMODO_*-Notiz und GitHub-Deploy-Key abgelegt, Arbeitsplatz-Kopie geloescht. Bare-Metal-Borg-Restore von der DR-Workstation ist damit moeglich, sobald WSL2 + Borg-Client installiert sind. | Restliche P1-Operator-Aufgaben: WSL2 + Borg-Client auf DR-Workstation installieren, Nextcloud-Restore-Test |
|
| 2026-06-03 | Hetzner Storage Box DR-SSH-Key `dr-hetzner-2026-06-03` (ed25519, Passphrase-frei) erzeugt, via `install-ssh-key` auf Storage Box `u565255.your-storagebox.de:23` autorisiert, passwortloser Login erfolgreich (Borg-Repos sichtbar), Private-Key offline neben KOMODO_*-Notiz und GitHub-Deploy-Key abgelegt, Arbeitsplatz-Kopie geloescht. Bare-Metal-Borg-Restore von der DR-Workstation ist damit moeglich, sobald WSL2 + Borg-Client installiert sind. | Restliche P1-Operator-Aufgaben: WSL2 + Borg-Client auf DR-Workstation installieren, Nextcloud-Restore-Test |
|
||||||
|
| 2026-06-06 | DR-Workstation produktiv: WSL2 Ubuntu 24.04 vorhanden, SSH/Git und Borg 1.2.8 in WSL vorhanden, DR-Key-Arbeitskopien unter `~/.ssh/dr-readonly` und `~/.ssh/dr-hetzner`, GitHub-Read-Smoke und Hetzner-SSH-Smoke erfolgreich, `ops/maintenance/dr-workstation-smoke.sh` nach `~/dr-smoke.sh` kopiert. Finaler Operator-Smoke erfolgreich: GitHub HEAD `3a263a4...`, Hetzner Storage Box Repos sichtbar, Borg-Repo `hetzner_borg_appdata_critical` gelesen, Repository ID `5dd9b949...`, encrypted `Yes (repokey)`, `DR-Smoke OK (2026-06-06 10:05:30)`. | Quartalsweise `bash ~/dr-smoke.sh`; Borg-Passphrase weiterhin nur interaktiv eingeben und nicht speichern |
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ Nach Aenderung:
|
|||||||
|
|
||||||
1. Einen regulaeren Borg-Lauf abwarten oder manuell starten.
|
1. Einen regulaeren Borg-Lauf abwarten oder manuell starten.
|
||||||
2. `check-external-operator.sh` ausfuehren.
|
2. `check-external-operator.sh` ausfuehren.
|
||||||
3. In `docs/AUDIT_2026-05-25_TODO.md` nur das Ergebnis dokumentieren.
|
3. Nur das Ergebnis dokumentieren: Datum/Befund im Review-Log von `docs/EXTERNAL_DEPENDENCIES.md`.
|
||||||
|
|
||||||
## 4. FRITZ!Box-Servicefenster
|
## 4. FRITZ!Box-Servicefenster
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ Nachteile, ehrlich gesagt: Wenn der Server zuhause aus ist, sind die Apps weg, b
|
|||||||
| **Vaultwarden** | Passwoerter sicher speichern und auf jedem Geraet nachschauen | Bitwarden-App (kostenlos), beim ersten Start Server-URL auf `vault.kaleschke.info` aendern lassen |
|
| **Vaultwarden** | Passwoerter sicher speichern und auf jedem Geraet nachschauen | Bitwarden-App (kostenlos), beim ersten Start Server-URL auf `vault.kaleschke.info` aendern lassen |
|
||||||
| **Mealie** | Rezepte sammeln, Wochenplan, Einkaufsliste | Web `mealie.kaleschke.info` oder Mealie-App |
|
| **Mealie** | Rezepte sammeln, Wochenplan, Einkaufsliste | Web `mealie.kaleschke.info` oder Mealie-App |
|
||||||
| **Paperless** | Briefe und wichtige Dokumente scannen, durchsuchen, ablegen | Web `paperless.kaleschke.info`; Scan-Workflow erklaert Michi |
|
| **Paperless** | Briefe und wichtige Dokumente scannen, durchsuchen, ablegen | Web `paperless.kaleschke.info`; Scan-Workflow erklaert Michi |
|
||||||
| **Plex** | Filme und Musik auf Fernseher, Handy und Tablet | Plex-App auf dem Geraet, mit Konto anmelden |
|
| **Plex** | Filme und Musik auf Fernseher, Handy und Tablet | Web `https://plex.kaleschke.info` oder Plex-App auf dem Geraet, mit Konto anmelden |
|
||||||
|
|
||||||
> Wenn du eine App auf dem Handy installierst und sie fragt nach einer Server-URL, ist das immer eine `...kaleschke.info`-Adresse. Wenn du dir nicht sicher bist, frag bevor du etwas eintippst.
|
> Wenn du eine App auf dem Handy installierst und sie fragt nach einer Server-URL, ist das immer eine `...kaleschke.info`-Adresse. Wenn du dir nicht sicher bist, frag bevor du etwas eintippst.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
# Guest / IoT Network Runbook
|
||||||
|
|
||||||
|
Stand: 2026-06-06
|
||||||
|
|
||||||
|
Dieses Runbook beschreibt den sicheren Weg, das FRITZ!Box-Gastnetz zu aktivieren,
|
||||||
|
ohne versehentlich Homelab-Admin-Ports aus dem Gastsegment erreichbar zu machen.
|
||||||
|
|
||||||
|
## Zielbild
|
||||||
|
|
||||||
|
- Normales LAN bleibt `192.168.178.0/24`.
|
||||||
|
- Kallilabcore bleibt im normalen LAN unter `192.168.178.58`.
|
||||||
|
- FRITZ!Box-Gast-WLAN darf Internetzugang haben, aber keinen Zugriff auf
|
||||||
|
`192.168.178.0/24`.
|
||||||
|
- Homelab-Admin-Pfade bleiben Operator-only:
|
||||||
|
- Tailscale fuer Admin-Zugriff
|
||||||
|
- Authelia/2FA fuer geschuetzte Web-UIs
|
||||||
|
- keine LAN-Admin-Ports aus dem Gastnetz
|
||||||
|
|
||||||
|
## Vorbedingungen
|
||||||
|
|
||||||
|
Vor dem Einschalten des Gast-WLANs muessen diese Preflights gruen sein:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
G:\Gitea_Clone\homelab-infra\ops\maintenance\check-guest-iot-isolation.ps1 -Mode LanPreflight
|
||||||
|
```
|
||||||
|
|
||||||
|
Erwartung im normalen LAN:
|
||||||
|
|
||||||
|
- `192.168.178.58:8082` ist blockiert (AdGuard Admin nur Tailscale).
|
||||||
|
- `192.168.178.58:8181` ist blockiert (InfluxDB nicht LAN-exponiert).
|
||||||
|
- `192.168.178.58:80`, `443`, `222` koennen im normalen LAN erreichbar sein.
|
||||||
|
|
||||||
|
Auf Unraid zusaetzlich:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/mnt/user/services/homelab-infra/ops/maintenance/check-guest-iot-preflight.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Validierung 2026-06-06: Host-Preflight erfolgreich, Report
|
||||||
|
`/mnt/user/backups/restore-reports/guest-iot-preflight-2026-06-06-131316.md`.
|
||||||
|
Ergebnis: FRITZ!Box 7590 per TR-064 erreichbar, `192.168.178.58:8082`
|
||||||
|
blockiert, `100.80.98.33:8082` erreichbar, `192.168.178.58:8181` blockiert.
|
||||||
|
|
||||||
|
Gast-WLAN-Smoke 2026-06-06: Operator hat ein iPhone mit `Fritzi Gastzugang`
|
||||||
|
verbunden und folgende Ziele getestet; alle waren aus dem Gast-WLAN nicht
|
||||||
|
erreichbar:
|
||||||
|
|
||||||
|
- `http://192.168.178.58:8082`
|
||||||
|
- `http://192.168.178.58:8181`
|
||||||
|
- `http://192.168.178.58:222`
|
||||||
|
- `https://192.168.178.58`
|
||||||
|
- `http://192.168.178.1`
|
||||||
|
|
||||||
|
Damit ist die Gastnetz-Isolation fuer die getesteten Homelab-/Router-Adminpfade
|
||||||
|
validiert.
|
||||||
|
|
||||||
|
## FRITZ!Box Schritte
|
||||||
|
|
||||||
|
In der FRITZ!Box UI:
|
||||||
|
|
||||||
|
1. `WLAN -> Gastzugang` oeffnen.
|
||||||
|
2. `Gastzugang aktiv` einschalten.
|
||||||
|
3. WPA2/WPA3-Verschluesselung aktiv lassen.
|
||||||
|
4. Eigenen Gast-SSID-Namen setzen, z. B. `Fritzi-Gast`.
|
||||||
|
5. Starkes Passwort setzen und in Vaultwarden ablegen.
|
||||||
|
6. Option `Geraete im Gastnetz duerfen miteinander kommunizieren` deaktiviert
|
||||||
|
lassen, sofern nicht bewusst gebraucht.
|
||||||
|
7. Option fuer Zugriff auf das Heimnetz / private Netzwerk deaktiviert lassen.
|
||||||
|
8. Gastzugang speichern.
|
||||||
|
|
||||||
|
Wichtig: Die genaue FRITZ!OS-8.25-UI-Beschriftung kann leicht variieren. Der
|
||||||
|
entscheidende Punkt ist: Gastgeraete duerfen keinen Zugriff auf das Heimnetz
|
||||||
|
haben.
|
||||||
|
|
||||||
|
## Verifikation
|
||||||
|
|
||||||
|
Ein Handy oder Laptop mit dem Gast-WLAN verbinden, dann auf diesem Geraet testen:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
G:\Gitea_Clone\homelab-infra\ops\maintenance\check-guest-iot-isolation.ps1 -Mode Guest
|
||||||
|
```
|
||||||
|
|
||||||
|
Erwartung aus dem Gast-WLAN:
|
||||||
|
|
||||||
|
- `192.168.178.58:80` blockiert
|
||||||
|
- `192.168.178.58:443` blockiert
|
||||||
|
- `192.168.178.58:222` blockiert
|
||||||
|
- `192.168.178.58:8082` blockiert
|
||||||
|
- `192.168.178.58:8181` blockiert
|
||||||
|
- `192.168.178.1:80` blockiert oder nur Gast-Gateway-Ansicht
|
||||||
|
|
||||||
|
Wenn der Test `Risk count: 0` meldet, ist die Isolation fuer die getesteten
|
||||||
|
Homelab-Admin-Pfade ausreichend.
|
||||||
|
|
||||||
|
## Betrieb
|
||||||
|
|
||||||
|
- Familien-/Gaestegeraete kommen ins Gast-WLAN, wenn sie keinen direkten Zugriff
|
||||||
|
auf LAN-Geraete brauchen.
|
||||||
|
- Homelab-Apps fuer Familie laufen perspektivisch ueber HTTPS/OIDC, nicht ueber
|
||||||
|
direkten LAN-Zugriff.
|
||||||
|
- Geraete, die lokale Discovery brauchen (z. B. manche Smart-TV/Plex-Szenarien),
|
||||||
|
bleiben im normalen LAN oder bekommen eine separate bewusste Entscheidung.
|
||||||
|
|
||||||
|
## Rollback
|
||||||
|
|
||||||
|
Wenn nach Aktivierung etwas Unerwartetes passiert:
|
||||||
|
|
||||||
|
1. FRITZ!Box: `WLAN -> Gastzugang` oeffnen.
|
||||||
|
2. Gastzugang deaktivieren.
|
||||||
|
3. Speichern.
|
||||||
|
4. Normalen LAN-Zugriff pruefen:
|
||||||
|
```powershell
|
||||||
|
G:\Gitea_Clone\homelab-infra\ops\maintenance\check-guest-iot-isolation.ps1 -Mode LanPreflight
|
||||||
|
```
|
||||||
|
|
||||||
|
Es werden durch dieses Runbook keine Docker-Stacks, Secrets oder produktiven
|
||||||
|
Appdaten veraendert.
|
||||||
@@ -15,7 +15,7 @@ Entscheidungen festgehalten. Details in den jeweiligen Abschnitten unten.
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| USV / Power Loss | **Bewusst auf Q3/2026 geparkt.** Keine Anschaffung dieses Quartal; Power-Loss bleibt akzeptiertes Risiko. | Naechstes Hardware-Upgrade, erneuter realer Stromausfall mit Datenfolge, oder Q3-Review (ab 2026-07-01) |
|
| USV / Power Loss | **Bewusst auf Q3/2026 geparkt.** Keine Anschaffung dieses Quartal; Power-Loss bleibt akzeptiertes Risiko. | Naechstes Hardware-Upgrade, erneuter realer Stromausfall mit Datenfolge, oder Q3-Review (ab 2026-07-01) |
|
||||||
| Cold-Backup-Rotation | **Bewusst Hetzner-only.** Off-site bleibt allein das Hetzner-Borg-Repo; keine zweite rotierende Cold-Kopie. | Stark wachsender Datenwert, wiederholte Hetzner-Probleme, oder geaenderte Betreiber-Praeferenz |
|
| Cold-Backup-Rotation | **Bewusst Hetzner-only.** Off-site bleibt allein das Hetzner-Borg-Repo; keine zweite rotierende Cold-Kopie. | Stark wachsender Datenwert, wiederholte Hetzner-Probleme, oder geaenderte Betreiber-Praeferenz |
|
||||||
| Stromverbrauch messen | **Operator-Entscheidung offen: Messgeraet beschaffen.** Aktuell kein Smart-Plug/Messgeraet vorhanden, daher keine Messwerte. | Beschaffung eines USB-/Smart-Plug-Messgeraets (z. B. schaltbare Mess-Steckdose) |
|
| Stromverbrauch messen | **Bewusst ohne Messung (Entscheidung 2026-06-06).** Kein Messgeraet; Werte bleiben dauerhaft offen, kein Beschaffungs-Todo. | Nur falls spaeter doch ein Messgeraet angeschafft wird oder Strom-/Kostenfrage relevant wird |
|
||||||
|
|
||||||
## Zweck
|
## Zweck
|
||||||
|
|
||||||
@@ -157,10 +157,10 @@ Bewertung:
|
|||||||
|
|
||||||
## Stromverbrauch
|
## Stromverbrauch
|
||||||
|
|
||||||
**Operator-Entscheidung offen: Messgeraet beschaffen.** Stand 2026-06-05 ist kein
|
**Bewusst ohne Messung (Operator-Entscheidung 2026-06-06).** Es wird kein
|
||||||
Smart-Plug/Messgeraet vorhanden, daher liegen keine Messwerte vor. Die Werte
|
Messgeraet beschafft; Idle/Normal/Backup/Last bleiben dauerhaft offen. Kein
|
||||||
bleiben bewusst offen, bis ein messfaehiges Geraet beschafft ist. Erst danach
|
offener Todo. Falls spaeter doch eine Mess-Steckdose angeschafft wird, reicht
|
||||||
werden Idle/Normal/Backup/Last in einem Durchlauf erfasst.
|
ein einziger Messdurchlauf, um die Tabelle zu fuellen.
|
||||||
|
|
||||||
| Zustand | Verbrauch | Messmethode | Datum |
|
| Zustand | Verbrauch | Messmethode | Datum |
|
||||||
|---|---:|---|---|
|
|---|---:|---|---|
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
# H:/ Nearline Pull
|
|
||||||
|
|
||||||
Status: **produktiv** (2026-05-28). Erster echter Lauf 2026-05-27 20:45 erfolgreich. Windows Scheduled Task `KalliLab H Drive Nearline Pull` taeglich 05:30 ist seit 2026-05-28 aktiv.
|
|
||||||
|
|
||||||
## Erstlauf-Befund 2026-05-27
|
|
||||||
|
|
||||||
- Erster `-WhatIf`-loser Lauf: 18 Borg-Dump-Files erfolgreich gepullt, 4 unraid-flash-config-Files und 10 Gitea-Bundle-Files blockiert (`Zugriff verweigert`).
|
|
||||||
- Ursache: Bundles wurden mit `chmod 600` geschrieben, Flash-Config bewusst `0600 root:root`, Filebrowser-Dump erbte 0640. Der SMB-Read-Share auf dem Operator-PC liest mit unprivilegierten Rechten, kein root.
|
|
||||||
- Fixes im selben Sprint:
|
|
||||||
- `ops/borg-ui/scripts/gitea-bundle-mirror.sh` schreibt Bundles und Sidecars jetzt 0644 (Bundle-Inhalt = Git-Historie, ohne Secrets durch `.gitignore`).
|
|
||||||
- `ops/borg-ui/scripts/pre-backup-dumps.sh` setzt alle Dumps via `atomic_write` per Default auf 0644; `unraid-flash-config.*` bleibt explizit 0600.
|
|
||||||
- `ops/h-drive-nearline/pull-critical-backups.ps1` excluded die `unraid-flash-config.*`-Familie ueber `/XF`, damit Flash-Config bewusst nicht in den Nearline-Scope kommt.
|
|
||||||
- Zweiter Lauf (nach Fixes): beide Robocopy-Jobs Exit-Code 1, **19 Borg-Dumps + 10 Gitea-Bundle-Files** auf H:/.
|
|
||||||
|
|
||||||
## Befund 2026-06-01
|
|
||||||
|
|
||||||
- Der Scheduled Task um 05:30 kopierte die aktuellen Dumps, brach aber mit Robocopy Exit-Code 8 ab, weil im Dump-Root alte `*-pre-*` Dateien und Migration-/Cutover-Verzeichnisse mit restriktiven Rechten lagen.
|
|
||||||
- Fix: `ops/h-drive-nearline/pull-critical-backups.ps1` kopiert fuer `borg-dumps-latest` nur noch die kuratierte Pflichtdatei-Liste und schliesst Migration-/Cutover-Verzeichnisse aus.
|
|
||||||
- Manueller Kontrolllauf 2026-06-01 08:25 erfolgreich: `borg-dumps-latest` Exit-Code 0, `gitea-bundles` Exit-Code 1 (Robocopy-Erfolg mit Kopien), Report `H:\kallilab-nearline-backups\_reports\nearline-pull-2026-06-01-082553.md`.
|
|
||||||
|
|
||||||
## Zweck
|
|
||||||
|
|
||||||
`H:/` ist eine zweite lokale Nearline-Kopie fuer die wichtigsten Restore-Artefakte. Es ersetzt weder Hetzner/Borg noch ein echtes Off-site-/Airgap-Ziel, reduziert aber das Risiko, dass ein lokaler Restore nur vom Unraid-Array abhaengt.
|
|
||||||
|
|
||||||
## Quelle und Ziel
|
|
||||||
|
|
||||||
| Zweck | Quelle | Ziel |
|
|
||||||
|---|---|---|
|
|
||||||
| Aktuelle kuratierte Dumps ohne Flash-Backup | `\\192.168.178.58\backups\borg\dumps\latest` | `H:\kallilab-nearline-backups\borg-dumps\latest` |
|
|
||||||
| Gitea-Bundles | `\\192.168.178.58\backups\git-bundles\gitea` | `H:\kallilab-nearline-backups\git-bundles\gitea` |
|
|
||||||
|
|
||||||
Das Skript kopiert bewusst **nicht** mit `/MIR` und loescht keine Dateien auf `H:/`. Alte Artefakte duerfen dort erst nach manueller Sichtpruefung geloescht werden.
|
|
||||||
|
|
||||||
Der Borg-Dumps-Job ist eine Whitelist der aktuellen Nearline-Pflichtartefakte. Einmalige Migrations-Sicherungen, Pre-Major-Snapshots und Redis-Cutover-Verzeichnisse bleiben ueber Borg/Hetzner abgedeckt, sind aber kein H:/-Nearline-Pflichtbestand.
|
|
||||||
|
|
||||||
## Skript
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File G:\Gitea_Clone\homelab-infra\ops\h-drive-nearline\pull-critical-backups.ps1 -WhatIf
|
|
||||||
```
|
|
||||||
|
|
||||||
Echter Lauf:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File G:\Gitea_Clone\homelab-infra\ops\h-drive-nearline\pull-critical-backups.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
Reports landen unter:
|
|
||||||
|
|
||||||
```text
|
|
||||||
H:\kallilab-nearline-backups\_reports
|
|
||||||
```
|
|
||||||
|
|
||||||
Robocopy-Logs landen unter:
|
|
||||||
|
|
||||||
```text
|
|
||||||
H:\kallilab-nearline-backups\_logs
|
|
||||||
```
|
|
||||||
|
|
||||||
## Geplanter Schedule
|
|
||||||
|
|
||||||
Empfohlen: taeglich 05:30 Uhr, nach dem Borg-Dump-Fenster um ca. 04:00 Uhr.
|
|
||||||
|
|
||||||
Aktiv seit 2026-05-28. Tatsaechlicher Register-Befehl (RunLevel-Enum-Wert ist `Limited`, nicht `LeastPrivilege`):
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$Action = New-ScheduledTaskAction `
|
|
||||||
-Execute "powershell.exe" `
|
|
||||||
-Argument "-NoProfile -ExecutionPolicy Bypass -File `"G:\Gitea_Clone\homelab-infra\ops\h-drive-nearline\pull-critical-backups.ps1`""
|
|
||||||
|
|
||||||
$Trigger = New-ScheduledTaskTrigger -Daily -At 05:30
|
|
||||||
|
|
||||||
$Settings = New-ScheduledTaskSettingsSet `
|
|
||||||
-AllowStartIfOnBatteries `
|
|
||||||
-DontStopIfGoingOnBatteries `
|
|
||||||
-StartWhenAvailable `
|
|
||||||
-ExecutionTimeLimit (New-TimeSpan -Hours 2)
|
|
||||||
|
|
||||||
Register-ScheduledTask `
|
|
||||||
-TaskName "KalliLab H Drive Nearline Pull" `
|
|
||||||
-Action $Action `
|
|
||||||
-Trigger $Trigger `
|
|
||||||
-Settings $Settings `
|
|
||||||
-Description "Copies critical KalliLab restore artifacts from Unraid SMB backup share to H:/ nearline disk." `
|
|
||||||
-RunLevel Limited
|
|
||||||
```
|
|
||||||
|
|
||||||
Status pruefen:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
Get-ScheduledTask -TaskName "KalliLab H Drive Nearline Pull" | Format-List TaskName, State
|
|
||||||
Get-ScheduledTaskInfo -TaskName "KalliLab H Drive Nearline Pull" | Format-List LastRunTime, LastTaskResult, NextRunTime, NumberOfMissedRuns
|
|
||||||
```
|
|
||||||
|
|
||||||
Manueller Trigger zum Testen:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
Start-ScheduledTask -TaskName "KalliLab H Drive Nearline Pull"
|
|
||||||
```
|
|
||||||
|
|
||||||
Verhalten:
|
|
||||||
|
|
||||||
- Laeuft als angemeldeter User (`RunLevel Limited`); wenn der PC abgemeldet ist, wartet der Task bis zur naechsten Anmeldung (`StartWhenAvailable`).
|
|
||||||
- Akku-Modus blockiert nicht (`AllowStartIfOnBatteries`).
|
|
||||||
- Maximale Laufzeit 2 h, danach wird der Task abgebrochen.
|
|
||||||
|
|
||||||
## Erfolgscheck
|
|
||||||
|
|
||||||
Nach einem echten Lauf muessen mindestens diese Artefakte unter `H:\kallilab-nearline-backups` liegen:
|
|
||||||
|
|
||||||
- `borg-dumps\latest\immich.dump`
|
|
||||||
- `borg-dumps\latest\komodo-mongo.archive.gz`
|
|
||||||
- `borg-dumps\latest\postgresql17-paperless.dump`
|
|
||||||
- `borg-dumps\latest\postgresql17-mailarchiver.dump`
|
|
||||||
- `borg-dumps\latest\nextcloud.dump`
|
|
||||||
- `borg-dumps\latest\mealie.dump`
|
|
||||||
- `borg-dumps\latest\gitea.sqlite.dump`
|
|
||||||
- `borg-dumps\latest\vaultwarden.sqlite.dump`
|
|
||||||
- `git-bundles\gitea\latest-report.md`
|
|
||||||
- `git-bundles\gitea\micha\*.bundle`
|
|
||||||
|
|
||||||
Bewusst **nicht** im Nearline-Scope:
|
|
||||||
|
|
||||||
- `unraid-flash-config.tar.gz` (hostseitig 0600 root:root; Restore-Quelle bleibt das Hetzner-Borg-Repo, siehe `docs/RESTORE_MATRIX.md` Tier 1 Unraid OS Flash).
|
|
||||||
|
|
||||||
## Schutzregeln
|
|
||||||
|
|
||||||
- Kein CIFS-/SMB-Hard-Mount von `H:/` auf Unraid.
|
|
||||||
- Kein Borg-Repo direkt auf `H:/` ueber SMB.
|
|
||||||
- Kein `/MIR` und kein automatisches Loeschen auf `H:/`.
|
|
||||||
- Flash-Backup wie Secret behandeln; `H:/` bleibt lokaler Operator-Datentraeger.
|
|
||||||
+47
-67
@@ -1,110 +1,90 @@
|
|||||||
# Master To-do - KalliLab CORE
|
# Master To-do - KalliLab CORE
|
||||||
|
|
||||||
Stand: 2026-06-05 (Wochenend-Sprint, nach Status-Kategorien sortiert)
|
Typ: Status/To-do · Stand: 2026-06-18 · Status: aktiv
|
||||||
|
|
||||||
Diese Liste ist die zentrale Arbeitsliste fuer offene operative Punkte im
|
Diese Liste ist die **einzige** Arbeitsliste fuer offene operative Punkte im
|
||||||
Homelab. Detailentscheidungen bleiben in den verlinkten Runbooks; diese Datei
|
Homelab. Detailablaeufe stehen in den verlinkten Runbooks; Entscheidungen mit
|
||||||
haelt Status, naechsten konkreten Schritt und Quelle zusammen.
|
Begruendung stehen in `docs/DECISIONS.md`; Belege fuer Erledigtes liegen in
|
||||||
|
Host-Reports (`/mnt/user/backups/restore-reports/`) und in der Git-Historie.
|
||||||
|
|
||||||
## Status-Kategorien
|
## Status-Kategorien
|
||||||
|
|
||||||
- **Aktiv dieses Wochenende** - soll jetzt vorankommen (Claude, Codex oder Operator); konkreter naechster Schritt steht.
|
- **Aktiv** - soll vorankommen; konkreter naechster Schritt steht.
|
||||||
- **Operator-Entscheidung** - wartet auf eine bewusste Entscheidung des Betreibers (ja/nein/welche Option).
|
- **Operator-Entscheidung** - wartet auf eine bewusste Entscheidung (ja/nein/Option).
|
||||||
- **Geparkt** - bewusst nicht jetzt, mit klarem Review-Trigger.
|
- **Geparkt** - bewusst nicht jetzt, mit klarem Review-Trigger.
|
||||||
- **Extern blockiert** - wartet auf ein externes Ereignis oder eine Abhaengigkeit (Nachtlauf, zweite Hardware, Geraetebeschaffung).
|
- **Extern blockiert** - wartet auf ein externes Ereignis oder eine Abhaengigkeit.
|
||||||
|
|
||||||
Owner-Aufteilung fuer das Wochenende: `baerchen`/Veeam/Backup-Verifikation liegt
|
|
||||||
bei **Codex**; Doku-/Inventar-/Onboarding-Arbeit liegt bei **Claude**;
|
|
||||||
Host-/Entscheidungsaufgaben beim **Operator**.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Aktiv dieses Wochenende
|
## Aktiv
|
||||||
|
|
||||||
| Thema | Owner | Naechster konkreter Schritt | Quelle |
|
| Thema | Owner | Naechster konkreter Schritt | Quelle |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| DR-Workstation Bare-Metal-Kit | Operator | Auf dem Gaming-PC `wsl --install -d Ubuntu`, dann `sudo apt install borgbackup`, dann Smoke `borg list ssh://u565255@u565255.your-storagebox.de/./hetzner_borg_appdata_critical` mit offline gesichertem Key + Passphrase | `docs/AUDIT_2026-05-25_TODO.md`, `docs/DR_WORKSTATION_SETUP.md`, `docs/EXTERNAL_DEPENDENCIES.md` |
|
| Family-Onboarding erster Termin | Operator | Checkliste ist fertig (`docs/FAMILY_ONBOARDING.md` Abschnitt "Erster Onboarding-Termin"). Personen/Geraete festlegen, Reihenfolge Vaultwarden -> Immich -> Mealie pro Person abarbeiten | `docs/FAMILY_ONBOARDING.md` |
|
||||||
| Family-Onboarding erster Termin | Operator | Checkliste ist fertig (`docs/FAMILY_ONBOARDING.md` Abschnitt "Erster Onboarding-Termin"). Operator legt fest, welche Personen/Geraete real verfuegbar sind, und arbeitet die Reihenfolge Vaultwarden -> Immich -> Mealie pro Person ab | `docs/FAMILY_ONBOARDING.md`, `docs/AUDIT_2026-05-25_TODO.md` |
|
| Restore-Test Unraid OS Flash (Stick-Boot) | Operator | Artefakt-Validierung 2026-06-05 erledigt (`ops/maintenance/check-unraid-flash-backup.sh`). **Verbleibt:** physischer Ersatzstick-Boot-Test, wenn ein Wegwerf-Stick bereitliegt | `ops/restore-tests/unraid-flash-runbook.md` |
|
||||||
| Home Assistant -> InfluxDB Token | Operator | `influxdb3_homeassistant_token` in InfluxDB 3 erzeugen, in HA `secrets.yaml` ablegen, einen Write-Pfad-Test fahren; nur Variablennamen/Pfade dokumentieren, kein Wert ins Repo | `docs/SECRETS_MAP.md`, `docs/HOME_ASSISTANT_INFLUXDB_ECOWITT.md` |
|
| Restore-Test Tailscale | Operator | State-Validierung + Reconnect nur auf Wegwerf-Host/VM, danach Geraet in Tailscale-Admin entfernen | `ops/restore-tests/tailscale-runbook.md` |
|
||||||
| Restore-Test Unraid OS Flash (Stick-Boot) | Operator | Artefakt-Validierung am 2026-06-05 erledigt (`ops/maintenance/check-unraid-flash-backup.sh`, sha256 OK, 8 Kern-Configs). **Verbleibt:** physischer Ersatzstick-Boot-Test, wenn ein Wegwerf-Stick bereitliegt | `docs/RESTORE_MATRIX.md` Abschnitt "Unraid OS Flash" |
|
| Authelia OIDC fuer Apps | Operator/Codex | Live: Grafana + Mealie login-verifiziert; Paperless Secret verdrahtet und Service-Smoke am 2026-06-17 gruen, finaler Browser-Login mit Operator-Account offen. Immich + Nextcloud bewusst geparkt bis Family-Onboarding (siehe `docs/DECISIONS.md` 2026-06-06) | `docs/AUTHELIA_OIDC_PLAN.md` |
|
||||||
| Restore-Test AdGuard Home | Operator | Runbook-Stub abarbeiten: Config nach `/mnt/user/backups/restore-lab/adguard` extrahieren, Testcontainer auf Port `5353`/`3001`, DNS-Smoke | `docs/RESTORE_MATRIX.md` Abschnitt "AdGuard Home" |
|
| Home Assistant Tibber | Operator/Codex | Tibber per HA-UI-Config-Flow verbinden. Danach Energy-Dashboard um echte Kosten/Preisquelle ergaenzen; SolarEdge-PV, Netz und Speicher sind bereits konfiguriert und validiert | `docs/runbooks/smart-home-bootstrap.md`, `docs/DECISIONS.md` |
|
||||||
| Restore-Test Tailscale | Operator | Runbook-Stub abarbeiten: State-Validierung + Reconnect nur auf Wegwerf-Host/VM, danach Geraet in Tailscale-Admin entfernen | `docs/RESTORE_MATRIX.md` Abschnitt "Tailscale" |
|
| Nearline-Pull Dead-Man's-Switch | Operator | H:-Pull war ~2026-06-04 bis 2026-06-18 still gestoppt (Task fehlte, kein Alarm). Lauf nachgeholt + Scheduled Task `KalliLab H Drive Nearline Pull` neu registriert (State Ready). **Verbleibt:** externer Dead-Man's-Switch (Healthchecks.io-Ping am Ende von `pull-critical-backups.ps1` und `ops/borg-ui/scripts/pre-borg.sh`), da Prometheus auf Unraid den baerchen-Pull nicht sieht | `ops/h-drive-nearline/README.md` |
|
||||||
| Restore-Test Redis 8 | Operator | Runbook-Stub abarbeiten: Pre-Cutover-Backup in isolierte Instanz auf Port `16379`, `PING` + `INFO server` (8.x) + `DBSIZE` pruefen | `docs/RESTORE_MATRIX.md` Abschnitt "Redis 8 (Shared)" |
|
| Monitoring Single-File-Bind-Mount Hardening | Operator/Claude | Prometheus am 2026-06-19 auf stabilen Directory-Mount (`./prometheus:/etc/prometheus/config:ro`) umgestellt + recreated -> Stale-Handle-Footgun dort beseitigt, Reload reicht wieder. **Verbleibt:** gleiches Einzeldatei-Muster bei alertmanager/blackbox/loki/promtail/grafana-provisioning praeventiv auf Directory-Mount umstellen | `monitoring/docker-compose.yml` |
|
||||||
| Manuelle App-/Lizenzchecks `baerchen` | Codex/Operator | Passwortmanager/2FA-Recovery-Codes, Banking4, WISO, Microsoft/M365/OneDrive im laufenden System bestaetigen | `ops/windows-reinstall/docs/windows-neuaufsetzen-masterplan.md` |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Operator-Entscheidung
|
## Operator-Entscheidung
|
||||||
|
|
||||||
| Thema | Entscheidungsfrage | Quelle |
|
Getroffene Entscheidungen mit Begruendung und Review-Trigger: `docs/DECISIONS.md`.
|
||||||
|
|
||||||
|
| Thema | Entscheidung noetig | Quelle |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| BitLocker-Entscheidung `baerchen` | C: (und ggf. D:) aktivieren oder bewusst deaktiviert lassen? Bei Ja: Recovery-Key vorher nach `D:\30_Finanzen\...`, Vaultwarden und physisch sichern. (Claude fasst BitLocker bewusst nicht an.) | `docs/SECRETS_MAP.md`, `ops/windows-reinstall/docs/laufwerks-neustruktur-2026-06-04.md` |
|
| `/mnt/user/projekte` Backup-Scope | Filebrowser serviert `projekte` (und ganze `documents`/`photos`), aber nur App-Unterordner sind im Borg-Scope. Entscheiden: `projekte` als read-only Borg-UI-Mount + Quelllisten-Eintrag aufnehmen, oder bewusst als "nur lokal, nicht DR-relevant" bestaetigen | `ops/borg-ui/BACKUP_SCOPE.md` Abschnitt "User-Daten-Shares ausserhalb des App-Scope" |
|
||||||
| Veeam Storage Encryption `baerchen` | Reicht der erste unverschluesselte Full-Lauf, oder soll Veeam Storage Encryption aktiviert werden? Bei Ja: Passwort in Vaultwarden anlegen, Job umstellen und neues Full-Backup erzeugen | `ops/windows-reinstall/docs/windows-image-backup-baseline.md`, `docs/SECRETS_MAP.md` |
|
|
||||||
| Stromverbrauch messen | Messgeraet/Smart-Plug beschaffen? Ohne Geraet bleiben Idle/Normal/Backup/Last bewusst offen. **Status 2026-06-05: kein Geraet vorhanden.** | `docs/HARDWARE_INVENTORY.md` Abschnitt "Stromverbrauch" |
|
|
||||||
| Tailscale ACL-Policy | **Entwurf abgestimmt 2026-06-05, noch nicht angewendet.** Richtung: Tag-basiert (`tag:server`/`tag:operator`/`tag:family`), Allow-all spaeter ersetzen; `baerchen-1`+`iphone-14` = operator, `kallilabcore` = server, Familie nur gezielte Dienste. Heute bewusst nur Sichtung, kein Tagging. **Offen vor Umsetzung:** (1) aktuellen ACL-JSON read-only sichten, (2) konkrete Familien-Dienste/Ports festlegen, (3) lockout-sichere Tagging-Reihenfolge + Smoke-Tests mit Operator durchfuehren | `docs/NETWORK_INVENTORY.md` Abschnitt "ACL-Policy — Entwurf und Rollout-Plan" |
|
|
||||||
| Nextcloud 2FA / Brute-Force-Haertung | Operator-TOTP (`twofactor_totp`) jetzt aktivieren? Familien-/OIDC-weite Policy separat | `docs/AUDIT_2026-05-25_TODO.md` |
|
|
||||||
| Authelia Rest-2FA | Weitere Admin-UIs (`monitoring`, `glances`, `glance`, `speedtest`, `pdf`, `mail`, `sp` ...) von `one_factor` auf `two_factor` heben oder bewusst belassen? | `docs/AUDIT_2026-05-25_TODO.md` |
|
|
||||||
| Authelia OIDC fuer Apps | App-uebergreifendes SSO einfuehren - haengt an Familien-/SSO-Grundsatzentscheidung | `docs/AUDIT_2026-05-25_TODO.md` |
|
|
||||||
| Gast-/IoT-Netz | Bewusst kein Gast-WLAN/IoT-Netz aktivieren, solange nicht gebraucht. Vorbedingung bei spaeterer Aktivierung: LAN-Admin-Ports vorher per FRITZ!Box-Filter gegen das Gastsegment sperren | `docs/NETWORK_INVENTORY.md` |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Geparkt
|
## Geparkt
|
||||||
|
|
||||||
Bewusst nicht jetzt - mit Review-Trigger.
|
Bewusst nicht jetzt - Begruendungen in `docs/DECISIONS.md`, hier nur Thema und Trigger.
|
||||||
|
|
||||||
| Thema | Entscheidung / Trigger | Quelle |
|
| Thema | Review-Trigger | Quelle |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| USV-Anschaffung | **Auf Q3/2026 geparkt** (2026-06-05). Power-Loss bleibt akzeptiertes Risiko. Trigger: Hardware-Upgrade, realer Stromausfall mit Datenfolge, oder Q3-Review ab 2026-07-01 | `docs/HARDWARE_INVENTORY.md` |
|
| USV-Anschaffung | Q3-Review ab 2026-07-01, Hardware-Upgrade oder realer Stromausfall mit Datenfolge | `docs/DECISIONS.md` |
|
||||||
| Cold-Backup-Rotation | **Bewusst Hetzner-only** (2026-06-05). Keine zweite rotierende Cold-Kopie. Trigger: stark wachsender Datenwert, wiederholte Hetzner-Probleme, geaenderte Praeferenz | `docs/HARDWARE_INVENTORY.md` |
|
| Cold-Backup-Rotation (zweites Off-site-Ziel) | Hetzner-Probleme, stark wachsender Datenwert oder geaenderte Praeferenz | `docs/DECISIONS.md` |
|
||||||
| WAN-Ausfallschutz | **Spaeter evaluieren** (2026-06-05). Mobilfunk-Failover inaktiv; lokale Apps laufen bei WAN-Ausfall weiter. Trigger: haeufigere/laengere DSL-Ausfaelle oder kritischer Remote-Zugang | `docs/NETWORK_INVENTORY.md` |
|
| WAN-Ausfallschutz | haeufigere/laengere DSL-Ausfaelle oder kritischer Remote-Zugang | `docs/NETWORK_INVENTORY.md` |
|
||||||
| Home Assistant InfluxDB Bind | Aktuell `127.0.0.1:8181`, validiert. Nur wenn HA nicht lokal auf den Host schreibt, bewusste Bind-Aenderung planen | `docs/NETWORK_INVENTORY.md` |
|
| Borg `append-only` auf Hetzner | robusterer Hetzner-Mechanismus oder geaendertes Ransomware-Risikoprofil | `docs/DECISIONS.md` |
|
||||||
| Docker Critical Events Watcher | **Aktiviert 2026-06-05:** Unraid User Script `docker-critical-events-at-start` nutzt den Supervisor und steht in `schedule.json` auf `frequency: start`; Watcher manuell gestartet, Status `running`. Optionaler ntfy-Smoke wurde nachts bewusst nicht gesendet und kann spaeter mit `docker-critical-events-supervisor.sh smoke` nachgeholt werden | `docs/SERVICE_CATALOG.md`, `services/posture-check/docker-critical-events.sh`, `services/posture-check/unraid-user-scripts.md` |
|
| CrowdSec vor Traefik | breitere Attack Surface als nur `443/tcp` | `docs/DECISIONS.md` |
|
||||||
| Negativ-Test Backup-Frische | Quartalsweise: bewusst kaputten/fehlenden Dump in Testpfad simulieren, pruefen ob `homelab-alerts` feuert | `docs/AUDIT_2026-05-25_TODO.md` |
|
| Nextcloud 2FA (Operator-TOTP) | OIDC-/SSO-Block erreicht die App-Login-Ebene | `docs/DECISIONS.md` |
|
||||||
| End-to-end-DR-Drill | Komplett-Bootstrap Phase 1-5 auf Wegwerf-Host; realistisch erst mit zweiter Hardware (siehe auch Extern blockiert) | `docs/AUDIT_2026-05-25_TODO.md`, `docs/DISASTER_RECOVERY.md` |
|
| Hermes-Agent | Review-Deadline 2026-07-25; NAS-Stack bleibt deaktiviert | `docs/SERVICE_CATALOG.md` |
|
||||||
| Wiederkehrende Restore-Drills | Vaultwarden, Gitea, Authelia, Komodo, Paperless, Immich, Traefik, PostgreSQL, Mongo, Nextcloud, Mealie, Mail-Archiver nach Matrix-Intervallen rotieren | `docs/RESTORE_MATRIX.md`, `docs/RESTORE_HANDBOOK.md` |
|
| Dedizierter SMB-User `veeam-baerchen` | nur wenn Unraid-User-/Share-Rechte bewusst angefasst werden | `ops/windows-reinstall/docs/windows-image-backup-baseline.md` |
|
||||||
| Dedizierter SMB-User `veeam-baerchen` | Optional spaeter, nur wenn Unraid-User-/Share-Rechte bewusst angefasst werden | `ops/windows-reinstall/docs/windows-image-backup-baseline.md` |
|
| Filebrowser-Mount-Scope | naechster Hardening-Sprint | `docs/SERVICE_CATALOG.md` |
|
||||||
| CrowdSec vor Traefik | Bewusst nicht umgesetzt; einzige WAN-Tuer ist `443/tcp`, Authelia `regulation:` deckt Brute-Force ab. Neu bewerten bei breiterer Attack Surface | `docs/AUDIT_2026-05-25_TODO.md` |
|
| Scrutiny Privileged-Ausnahme | nur mit klarer Begruendung aendern | `docs/SERVICE_CATALOG.md` |
|
||||||
| Hermes-Agent | NAS-Stack bleibt deaktiviert; Review-Deadline 2026-07-25 | `docs/AUDIT_2026-05-25_TODO.md`, `docs/SERVICE_CATALOG.md` |
|
| Immich Redis named volume | passende Wartung am Immich-Stack | `docs/SERVICE_CATALOG.md` |
|
||||||
| Filebrowser-Mounts | Bei zukuenftigem Hardening-Sprint Mount-Scope reduzieren | `docs/SERVICE_CATALOG.md` |
|
| Komodo keys named volume | gemeinsames Wartungsfenster mit Operator | Live-Volume `komodo_komodo_keys` nach `/mnt/user/appdata/komodo/keys` migrieren, Compose anpassen, Periphery-Reconnect pruefen, dann in Borg-Scope aufnehmen |
|
||||||
| Scrutiny Privileged-Ausnahme | Nur mit klarer Begruendung aendern; sonst dokumentierte Ausnahme beibehalten | `docs/SERVICE_CATALOG.md` |
|
| Storage-Wachstum (zweite NVMe, zweite Array-Disk, ZFS/BTRFS) | Trigger aus Capacity-Doku | `docs/STORAGE_LAYOUT.md`, `docs/CAPACITY_AND_LIFECYCLE.md` |
|
||||||
| Immich Redis named volume | Anonymes Volume bei passender Wartung auf named volume umstellen oder Ausnahme dokumentieren | `docs/SERVICE_CATALOG.md` |
|
| Wiederkehrende Restore-Drills | laufend nach Kadenz, inkl. quartalsweisem Frische-Negativtest (`run-restore-checks.sh freshness-negative`) | `docs/RESTORE_MATRIX.md`, `ops/restore-tests/schedule.md` |
|
||||||
| Storage-Wachstum | Zweite NVMe, ZFS/BTRFS-Optionen, zweite Array-Disk nur bei Triggern aus Capacity-Doku | `docs/STORAGE_LAYOUT.md`, `docs/CAPACITY_AND_LIFECYCLE.md` |
|
| Doku-Quartals-Gaertnern (~15 min) | quartalsweise, erster Lauf mit Q3-Review ab 2026-07-01: Datiertes archivieren, Done-/Review-Logs kuerzen, tote Links pruefen | `docs/REPO_MAP.md` Doku-Regeln |
|
||||||
| Zweites Off-site-Ziel | Bewusst nicht umgesetzt; neu bewerten bei Hetzner-Problemen oder wachsendem Datenwert | `docs/AUDIT_2026-05-25_TODO.md` |
|
|
||||||
| Borg `append-only` auf Hetzner | Operator-Entscheidung 2026-06-01: nicht umgesetzt (forced-command brach Key-Auth, Nutzen/Risiko unguenstig) | `docs/AUDIT_2026-05-25_TODO.md` |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Extern blockiert
|
## Extern blockiert
|
||||||
|
|
||||||
Wartet auf ein externes Ereignis oder eine Abhaengigkeit.
|
|
||||||
|
|
||||||
| Thema | Blockiert durch | Naechster Schritt sobald entblockt | Quelle |
|
| Thema | Blockiert durch | Naechster Schritt sobald entblockt | Quelle |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `baerchen` Recovery-Test ohne Restore | Haengt am physischen USB-Boot-Test | Von USB `VEEAMRE` booten, SMB-Ziel mounten, Restore Point anzeigen, vor echtem Restore abbrechen. **Owner: Codex/Operator** | `ops/windows-reinstall/docs/windows-image-backup-baseline.md`, `docs/RESTORE_MATRIX.md` |
|
| End-to-end-DR-Drill | Keine zweite Wegwerf-Hardware verfuegbar | Komplett-Bootstrap Phase 1-5 fahren | `docs/DISASTER_RECOVERY.md` |
|
||||||
| End-to-end-DR-Drill (Hardware-Teil) | Keine zweite Wegwerf-Hardware verfuegbar | Sobald zweite Hardware da ist: Komplett-Bootstrap Phase 1-5 fahren | `docs/DISASTER_RECOVERY.md` |
|
|
||||||
| Stromverbrauch-Messwerte | Kein Messgeraet beschafft | Nach Beschaffung einer schaltbaren Mess-Steckdose einen Messdurchlauf Idle/Normal/Backup/Last fahren | `docs/HARDWARE_INVENTORY.md` |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Erledigt im Wochenend-Sprint (2026-06-05)
|
## Zuletzt erledigt (Kurzlog, max. 5 Eintraege)
|
||||||
|
|
||||||
- Restore-Matrix "Naechste Restore-Test-Kandidaten" bereinigt: 5 am 2026-06-03 abgeschlossene Kandidaten entfernt, durch die 4 real offenen Pfade ersetzt; Stand-Datum aktualisiert.
|
- **2026-06-17** Offene TODOs gegen Live-Stand abgeglichen: Paperless-OIDC-Secret verdrahtet und Service-Smoke gruen; alter Tailscale-Docker-State nach `_archive/tailscale-removed-2026-06-06/` verschoben; Tailnet-Restpunkt geschlossen.
|
||||||
- Restore-Test-Runbook-Stubs fuer Unraid Flash / AdGuard / Tailscale / Redis 8 in `docs/RESTORE_MATRIX.md` ergaenzt.
|
- **2026-06-17** Repo-Hygiene abgeschlossen: Glance-Widget-Tokens sind in Runtime gesetzt, Audit-PDF liegt extern unter `H:\kallilab-recovery\audits`, Worktree clean.
|
||||||
- Alte Windows-Doku bereinigt: WinRE-/Admin-Check-To-dos in `boot-cleanup-plan-2026-06-04.md` und `laufwerks-neustruktur-2026-06-04.md` als erledigt markiert.
|
- **2026-06-17** Komodo/Gitea-Webhooks normalisiert: aktive Komodo-Hooks fuer `Micha/homelab-infra` nutzen Branch-Filter `master`; DB-Backup vor Host-Hotfix erstellt. Workflow-Regel nachgezogen.
|
||||||
- `docs/HARDWARE_INVENTORY.md`: USV (Q3-Park), Cold-Backup (Hetzner-only) und Stromverbrauch (Operator-Entscheidung offen) von diffusen TBDs auf bewusste Entscheidungen mit Review-Triggern gehoben.
|
- **2026-06-19** Backup-Hardening live verifiziert: Borg-Scope-Drift 0 (alle 33 Quellen konfiguriert), Dumps frisch (11/11 present), neue Dump-Alerts aktiv (25 Regeln, 0 feuern). Prometheus-`alerts.yml`-Stale-Handle (FUSE-Einzeldatei-Mount) per `--force-recreate` behoben und anschliessend dauerhaft auf Directory-Mount umgestellt (recreated, 25 Regeln aktiv).
|
||||||
- `docs/NETWORK_INVENTORY.md`: Tailscale-Inventar am 2026-06-05 **real per read-only SSH gemessen** und eingetragen: IPv6 `fd7a:115c:a1e0::2c01:62b2`, Exit Node `nein`, **Subnet-Router fuer `192.168.178.0/24` aktiv** (widerlegt fruehere Vermutung), Tailnet `taild9fcf2.ts.net`, Geraete-Snapshot + Dubletten-Hinweis. WAN-Failover und Gast-/IoT geschaerft. `zu messen`-Platzhalter entfernt. **`Tailscale-Inventar messen` damit geschlossen.**
|
- **2026-06-18** Backup-Audit-Hardening: Dump-Frische-Metriken + Alerts `HomelabBorgDumpMissing/Stale`, Freshness-Checks + Nearline-Pull um `n8n`/`globals` ergaenzt, 4 Tier-2-Container in Critical-Watch, Scope-Doku fuer `projekte`/Hermes praezisiert. H:-Nearline (still seit 2026-06-04) nachgeholt + Task neu registriert.
|
||||||
- `ops/maintenance/check-unraid-flash-backup.sh` neu: read-only Validierung des Flash-Artefakts (sha256, Frische, Kern-Configs, keine Extraktion). Am 2026-06-05 gegen den Host getestet: Exit 0, sha256 OK, 390 Eintraege, 8/8 Kern-Configs. `docs/RESTORE_MATRIX.md` mit Testdatum/Ergebnis aktualisiert. **Artefakt-Validierung des Unraid-Flash-Backups damit erledigt; nur Stick-Boot-Test offen.**
|
|
||||||
- `docs/FAMILY_ONBOARDING.md`: Michi-Checkliste in eine echte Erste-Termin-Checkliste (Vorbereitung, Reihenfolge, Erfolgskriterium, bewusst spaeter) umgebaut.
|
|
||||||
- `docs/MASTER_TODO.md` in vier Status-Kategorien (Aktiv / Operator-Entscheidung / Geparkt / Extern blockiert) umstrukturiert.
|
|
||||||
- `baerchen` Veeam-Erstbackup: erster Full-Lauf 2026-06-05 erfolgreich geschrieben (Veeam-GUI 53,8 GB, Dauer 0:11:31, MetaCheck 0 Fehler/0 Warnungen, VSS `job: success`). Beleg in `ops/windows-reinstall/docs/windows-image-backup-baseline.md`; Veeam Storage Encryption war im ersten Lauf nicht aktiv und ist als Operator-Entscheidung nachgezogen.
|
|
||||||
- Docker Critical Events Watcher auf Unraid aktiviert: Host-Clone auf Commit `2f3d184` aktualisiert, User Script `/boot/config/plugins/user.scripts/scripts/docker-critical-events-at-start/script` auf den Supervisor umgestellt, altes Script als `script.bak-20260605-232621` gesichert, `schedule.json` zeigt `frequency: start`, Watcher laeuft mit PID `1681168`. ntfy-Smoke bewusst nicht nachts gesendet.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Pflege-Regel
|
## Pflege-Regel
|
||||||
|
|
||||||
- Neue operative To-dos zuerst hier eintragen oder aus Detaildokumenten hierher uebernehmen, immer mit Status-Kategorie.
|
- Neue operative To-dos zuerst hier eintragen, immer mit Status-Kategorie.
|
||||||
- Wenn ein Punkt erledigt ist, in der Detaildoku den Beleg/Report eintragen und diese Liste aktualisieren.
|
- Erledigt: Beleg liegt im Host-Report bzw. Commit; hier nur ein Kurzlog-Eintrag (max. 3 Zeilen), aelteste Eintraege fliegen raus, sobald mehr als 5.
|
||||||
|
- Entscheidungen (auch "bewusst nein") gehoeren mit Begruendung nach `docs/DECISIONS.md`, hier nur Thema + Trigger.
|
||||||
- Keine vagen "pruefen"-Eintraege ohne Kommando oder Entscheidung.
|
- Keine vagen "pruefen"-Eintraege ohne Kommando oder Entscheidung.
|
||||||
- Historische Drill-Reports bleiben Belegmaterial, aber nicht die fuehrende Arbeitsliste.
|
|
||||||
|
|||||||
+220
-64
@@ -1,7 +1,7 @@
|
|||||||
# Network Inventory - KalliLab CORE
|
# Network Inventory - KalliLab CORE
|
||||||
|
|
||||||
Status: Host-Audit erfasst; Router-Baseline und Portfreigaben-UI bereinigt; FRITZ!Box-Remote-Dienste aus; IPv6-Exposure technisch und per UI entschaerft; Tailscale-Inventar am 2026-06-05 real gemessen.
|
Status: Host-Audit erfasst; Router-Baseline und Portfreigaben-UI bereinigt; FRITZ!Box-Remote-Dienste aus; IPv6-Exposure technisch und per UI entschaerft; Tailscale-Inventar am 2026-06-17 real gemessen.
|
||||||
Letzte Pruefung: 2026-06-05 (Tailscale-Inventar), 2026-06-01 (Router/Ports)
|
Letzte Pruefung: 2026-06-17 (Tailscale-Inventar), 2026-06-01 (Router/Ports)
|
||||||
|
|
||||||
## Zweck
|
## Zweck
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ Dieses Dokument beschreibt Router, DNS, Tailscale, Portfreigaben und Netztrennun
|
|||||||
| Komponente | Rolle | Adresse | Bemerkung |
|
| 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 |
|
| 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 |
|
| Unbound | DNSSEC-validierender Forwarding-Resolver | Docker `dns_net` | Upstream fuer AdGuard; forwardet per DoT zu Cloudflare, keine Root-Rekursion |
|
||||||
| Cloudflare | Authoritative DNS | extern | DNS-Challenge fuer TLS |
|
| Cloudflare | Authoritative DNS | extern | DNS-Challenge fuer TLS |
|
||||||
| Router | DHCP DNS-Verteilung | TBD | Muss auf AdGuard zeigen, falls so betrieben |
|
| Router | DHCP DNS-Verteilung | TBD | Muss auf AdGuard zeigen, falls so betrieben |
|
||||||
|
|
||||||
@@ -55,23 +55,46 @@ Gemessen am 2026-06-05 per read-only SSH auf den Host (`tailscale status`,
|
|||||||
| Tailscale IPv6 | `fd7a:115c:a1e0::2c01:62b2` (gemessen 2026-06-05) |
|
| Tailscale IPv6 | `fd7a:115c:a1e0::2c01:62b2` (gemessen 2026-06-05) |
|
||||||
| Exit Node | **Nein.** `Self.ExitNodeOption: false` und `Self.ExitNode: false` — Host bietet keinen Exit Node an und nutzt keinen. Entspricht dem Ziel (Operator-Zugang ist eingehend, nicht als Internet-Ausgang). |
|
| Exit Node | **Nein.** `Self.ExitNodeOption: false` und `Self.ExitNode: false` — Host bietet keinen Exit Node an und nutzt keinen. Entspricht dem Ziel (Operator-Zugang ist eingehend, nicht als Internet-Ausgang). |
|
||||||
| Subnet Router | **Ja, aktiv.** Host advertised und ist Primary fuer `192.168.178.0/24` (`Self.PrimaryRoutes: ["192.168.178.0/24"]`, ebenfalls in `AllowedIPs`). Das LAN ist also fuer das gesamte Tailnet ueber diesen Subnet-Router erreichbar — bewusst gemessener Ist-Zustand, **kein** "keine Route" wie zuvor vermutet. |
|
| Subnet Router | **Ja, aktiv.** Host advertised und ist Primary fuer `192.168.178.0/24` (`Self.PrimaryRoutes: ["192.168.178.0/24"]`, ebenfalls in `AllowedIPs`). Das LAN ist also fuer das gesamte Tailnet ueber diesen Subnet-Router erreichbar — bewusst gemessener Ist-Zustand, **kein** "keine Route" wie zuvor vermutet. |
|
||||||
| ACL-Policy extern dokumentiert | **Operator-Entscheidung offen** — siehe eigener Block unten; durch den aktiven Subnet-Router ist die ACL-Frage sicherheitsrelevanter als zuvor angenommen. |
|
| ACL-Policy extern dokumentiert | **Angewendet 2026-06-06** — restriktive Tag-basierte `grants`-Policy live (`tag:server`/`tag:operator`, `tag:family` schlafend). Default-Allow entfernt, verifiziert. Details im Block unten. |
|
||||||
|
|
||||||
### Tailnet-Geraete (Snapshot 2026-06-05)
|
### Tailnet-Geraete (Snapshot 2026-06-17)
|
||||||
|
|
||||||
| Tailscale-IP | Node | OS | Status |
|
| Tailscale-IP | Node | OS | Status |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `100.80.98.33` | kallilabcore | linux | aktiv (Host, Subnet-Router) |
|
| `100.80.98.33` | kallilabcore | linux | aktiv (Host, Subnet-Router) |
|
||||||
| `100.78.133.37` | baerchen-1 | windows | aktiv (aktuelle Operator-Workstation, direct) |
|
| `100.78.133.37` | baerchen-1 | windows | aktiv (aktuelle Operator-Workstation, direct) |
|
||||||
| `100.105.203.21` | baerchen | windows | offline, zuletzt vor ~1 Tag gesehen (Alt-Node) |
|
| `100.73.83.55` | iphone-14 | iOS | bekannt, aktuell offline |
|
||||||
| `100.73.83.55` | iphone-14 | iOS | bekannt |
|
|
||||||
| `100.112.0.90` | kallilab-core | linux | gelistet, kein aktiver Verkehr — **moeglicher Alt-/Dubletten-Node**, separat pruefen |
|
|
||||||
|
|
||||||
> Hygiene-Hinweis (kein Secret): Es existieren zwei linux-Nodes mit aehnlichem
|
> **Historischer Befund 2026-06-06 (read-only auf dem Host ermittelt):** Der Host
|
||||||
> Namen (`kallilabcore` und `kallilab-core`) sowie zwei `baerchen`-Nodes
|
> hatte damals **zwei** `tailscaled`-Prozesse:
|
||||||
> (`baerchen-1` aktiv, `baerchen` offline). Bei Gelegenheit in der
|
>
|
||||||
> Tailscale-Admin-Konsole pruefen, ob die inaktiven Eintraege stillgelegt werden
|
> 1. **Native Unraid-Plugin** = `kallilabcore` (100.80.98.33). Prozess
|
||||||
> koennen. Das ist Aufraeumarbeit, kein akutes Risiko.
|
> `/usr/local/sbin/tailscaled -statedir /boot/config/plugins/tailscale/state
|
||||||
|
> -tun tailscale1`. **Echtes TUN-Interface `tailscale1`, ist der Subnet-Router
|
||||||
|
> fuer `192.168.178.0/24`**, laeuft seit 24. Mai, installiert via
|
||||||
|
> `tailscale.plg` + `unraid-tailscale-utils`. State unter
|
||||||
|
> `/boot/config/plugins/tailscale/state` → ueber das **Flash-Backup** gesichert.
|
||||||
|
> Im ACL-Rollout `tag:server`. **Das ist die funktionale, kanonische Instanz.**
|
||||||
|
> 2. **Docker-Stack** = `kallilab-core` (100.112.0.90), `host-services/tailscale/`.
|
||||||
|
> Prozess `tailscaled --tun=userspace-networking` → **nur Userspace, kann
|
||||||
|
> technisch nicht routen / kein Subnet-Router/Exit-Node sein**, advertised
|
||||||
|
> nichts, kein Container teilt seinen Namespace, seit 31. Mai. State unter
|
||||||
|
> `/mnt/user/appdata/tailscale`. Im ACL-Rollout untagged → isoliert.
|
||||||
|
> **Hochwahrscheinlich redundant.**
|
||||||
|
>
|
||||||
|
> **Umgesetzt 2026-06-06:** Der redundante Docker-Stack `host-services/tailscale/`
|
||||||
|
> wurde sauber per GitOps abgebaut — Komodo-Stack `tailscale` gestoppt+destroyed
|
||||||
|
> (Operator), `git rm host-services/tailscale/`, Glance-Widget entfernt, und
|
||||||
|
> Architektur-/Service-Catalog-/DR-/CLAUDE-Doku auf "natives Plugin" nachgezogen.
|
||||||
|
> Read-only verifiziert: Container weg, nur noch der native `tailscaled` mit
|
||||||
|
> `tailscale1`, Subnet-Route + Operator-Zugriff intakt. Nachpruefung 2026-06-17:
|
||||||
|
> `tailscale status --self=false` zeigt nur noch `baerchen-1` und `iphone-14`;
|
||||||
|
> der alte State-Pfad `/mnt/user/appdata/tailscale` ist weg und liegt archiviert
|
||||||
|
> unter `/mnt/user/appdata/_archive/tailscale-removed-2026-06-06/`.
|
||||||
|
>
|
||||||
|
> **Doku-Korrektur erledigt:** `docs/RESTORE_MATRIX.md` zeigt jetzt auf den
|
||||||
|
> funktionalen State `/boot/config/plugins/tailscale/state` (im Flash-Backup)
|
||||||
|
> statt auf den entfernten userspace-Docker-Pfad.
|
||||||
|
|
||||||
### Subnet-Router-Konsequenz
|
### Subnet-Router-Konsequenz
|
||||||
|
|
||||||
@@ -91,34 +114,22 @@ tailscale ip -4
|
|||||||
tailscale ip -6
|
tailscale ip -6
|
||||||
```
|
```
|
||||||
|
|
||||||
### ACL-Policy — Entwurf und Rollout-Plan (Stand 2026-06-05, NICHT angewendet)
|
### ACL-Policy — ANGEWENDET 2026-06-06 (restriktive Tag-basierte grants)
|
||||||
|
|
||||||
Die Tailnet-ACL wird in der Tailscale-Admin-Konsole unter `Access controls`
|
**Status: live und verifiziert.** Die restriktive Policy wurde am 2026-06-06
|
||||||
verwaltet (kein Wert/Secret gehoert ins Repo). Aktueller Live-Stand ist
|
gemeinsam mit dem Operator in der lockout-sicheren Reihenfolge ausgerollt und
|
||||||
Default-Allow (`src: ["*"] -> dst: ["*:*"]`), d. h. jedes Tailnet-Geraet darf
|
read-only verifiziert (siehe "Rollout-Protokoll" unten). Ausgangspunkt war die
|
||||||
alles inklusive der LAN-Subnet-Route.
|
**unveraenderte Default-Policy** im **`grants`-Schema** (eine Allow-all-Regel,
|
||||||
|
keine Groups/Tags/`autoApprovers`); es gab also keinen eigenen Bestand zu
|
||||||
|
erhalten.
|
||||||
|
|
||||||
**Abgestimmte Richtung (Operator-Entscheidungen 2026-06-05):**
|
> **Schema-Hinweis:** Dieses Tailnet nutzt das `grants`-Modell
|
||||||
|
> (`{"src","dst","ip"}`), nicht das aeltere `acls`/`action:accept`-Modell.
|
||||||
|
> Normaler SSH-Zugriff (`ssh kallilabcore` ueber OpenSSH Port 22) wird ueber
|
||||||
|
> `grants` geregelt, nicht ueber den `ssh`-Block; letzterer betrifft nur die
|
||||||
|
> Tailscale-SSH-Funktion.
|
||||||
|
|
||||||
- Ziel ist eine restriktivere, Tag-basierte ACL.
|
**Angewendete Policy (live, kein Secret):**
|
||||||
- Single-User-Realitaet: aktuell gehoeren alle Nodes demselben User
|
|
||||||
`michaelkaleschke@`. Eine Differenzierung Operator/Familie ist nur ueber
|
|
||||||
**Tags** moeglich. Tagging aendert Ownership/Key-Expiry und erfordert je Geraet
|
|
||||||
Re-Auth — deshalb bewusst ein eigener, spaeterer Schritt.
|
|
||||||
- **Heute bewusst nur Sichtung + Entwurf, kein Tagging, keine Anwendung.**
|
|
||||||
- Familiengeraete brauchen Tailnet-Zugriff auf **bestimmte** Dienste (welche
|
|
||||||
genau, ist noch zu konkretisieren) — `tag:family` bekommt gezielte `dst`-Regeln.
|
|
||||||
- `iphone-14` ist ein Operator-Geraet und faellt unter `tag:operator`.
|
|
||||||
|
|
||||||
**Geraete -> Tag (fuer den spaeteren Tagging-Schritt):**
|
|
||||||
|
|
||||||
| Tag | Geraete |
|
|
||||||
|---|---|
|
|
||||||
| `tag:server` | `kallilabcore` (Host, Subnet-Router) |
|
|
||||||
| `tag:operator` | `baerchen-1`, `iphone-14` |
|
|
||||||
| `tag:family` | kuenftige Familiengeraete |
|
|
||||||
|
|
||||||
**Entwurf (Vorschlag, noch nicht in der Konsole gespeichert):**
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -130,42 +141,64 @@ alles inklusive der LAN-Subnet-Route.
|
|||||||
"autoApprovers": {
|
"autoApprovers": {
|
||||||
"routes": { "192.168.178.0/24": ["tag:server"] }
|
"routes": { "192.168.178.0/24": ["tag:server"] }
|
||||||
},
|
},
|
||||||
"acls": [
|
"grants": [
|
||||||
{ "action": "accept", "src": ["tag:operator"], "dst": ["*:*"] },
|
{"src": ["tag:operator"], "dst": ["*"], "ip": ["*"]},
|
||||||
{ "action": "accept", "src": ["tag:server"], "dst": ["tag:operator:*"] },
|
{"src": ["tag:server"], "dst": ["tag:operator"], "ip": ["*"]},
|
||||||
{ "action": "accept", "src": ["tag:family"], "dst": ["100.80.98.33:443"] }
|
{"src": ["tag:family"], "dst": ["tag:server"], "ip": ["tcp:443"]}
|
||||||
],
|
],
|
||||||
"ssh": [
|
"ssh": [
|
||||||
{ "action": "accept", "src": ["tag:operator"], "dst": ["tag:server"],
|
{"action": "check", "src": ["autogroup:member"], "dst": ["autogroup:self"],
|
||||||
"users": ["root", "autogroup:nonroot"] }
|
"users": ["autogroup:nonroot", "root"]}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
> Die `tag:family`-`dst` `100.80.98.33:443` ist ein **Platzhalter** und wird
|
**Geraete-Tags (live):** `kallilabcore` = `tag:server`; `baerchen-1` + `iphone-14`
|
||||||
> durch die real benoetigten Familien-Dienste ersetzt, sobald diese feststehen.
|
= `tag:operator`. Alte Nodes `kallilab-core` und `baerchen` sind nicht mehr im
|
||||||
|
aktuellen Tailnet-Status sichtbar.
|
||||||
|
|
||||||
**Lockout-sichere Reihenfolge (wenn die Umsetzung freigegeben wird):**
|
**Rollout-Protokoll 2026-06-06 (lockout-sicher, je Schritt read-only verifiziert):**
|
||||||
|
|
||||||
1. `tagOwners` + `autoApprovers` + neue ACL-Regeln speichern, **Allow-all-Regel
|
1. Policy additiv erweitert (Tags/grants definiert, Allow-all noch drin) -> alle Peers unveraendert verbunden, Route approved.
|
||||||
zunaechst behalten**.
|
2. `baerchen-1` getaggt `tag:operator` -> online, verifiziert.
|
||||||
2. Geraete taggen (`baerchen-1`, `iphone-14` -> operator; `kallilabcore` ->
|
3. `iphone-14` getaggt `tag:operator` -> verifiziert.
|
||||||
server) und je Geraet die Verbindung verifizieren.
|
4. `kallilab-core` faktisch geprueft (Docker-Sidecar, keine Abhaengigen) -> bewusst untagged gelassen.
|
||||||
3. Subnet-Route bleibt approved (jetzt via `autoApprovers`/`tag:server`).
|
5. Host `kallilabcore` getaggt `tag:server` -> Route blieb via `autoApprovers` automatisch approved, SSH ok.
|
||||||
4. **Erst zuletzt** die Allow-all-Regel entfernen -> restriktiv schalten.
|
6. Allow-all entfernt -> restriktiv. Smoke-Tests gruen: Operator-SSH ok, AdGuard-Admin ueber Tailnet `HTTP 302`, Ping 0% Verlust, Route weiter approved; Host sieht nur noch die zwei Operator-Peers (untagged Nodes isoliert). LAN-Rueckweg durchgehend verfuegbar.
|
||||||
5. Sofort Smoke-Tests fahren (siehe unten).
|
|
||||||
|
|
||||||
**Smoke-Tests nach Anwendung:**
|
**Schema-/Erhaltungs-Hinweis fuer spaeter:** Die LAN-Subnet-Route
|
||||||
|
`192.168.178.0/24` wird jetzt ueber `autoApprovers`/`tag:server` approved
|
||||||
|
(vorher manuell). Es gibt keinen eigenen Bestand zu erhalten; die Policy oben
|
||||||
|
ist die vollstaendige Wahrheit.
|
||||||
|
|
||||||
- `baerchen-1` erreicht Host: `ping 100.80.98.33`, `ssh kallilabcore hostname` (read-only).
|
**Hintergrund / Designentscheidungen (2026-06-05/06):**
|
||||||
- `baerchen-1` erreicht LAN ueber Route: z. B. AdGuard-Admin `http://100.80.98.33:8082`, `curl -kI https://192.168.178.58`.
|
|
||||||
- Falls testbar: ein nicht-Operator-Geraet erreicht Route/Admin-Ports **nicht** mehr.
|
|
||||||
- Host-Gegencheck: `tailscale status` zeigt alle Soll-Peers weiter verbunden.
|
|
||||||
|
|
||||||
**Noch offene Eingaben vor Finalisierung:**
|
- Single-User-Realitaet: alle Nodes gehoeren demselben User `michaelkaleschke@`.
|
||||||
|
Eine Differenzierung Operator/Familie ist nur ueber **Tags** moeglich, deshalb
|
||||||
|
der Tag-Ansatz statt user-/gruppenbasiert.
|
||||||
|
- Erster Rollout bewusst klein: nur `tag:server` + `tag:operator`.
|
||||||
|
- **`tag:family` ist vorbereitet, aber schlafend:** Tag und eine konservative
|
||||||
|
Minimal-Regel (`dst: tag:server`, `ip: tcp:443`) sind definiert, aber **kein
|
||||||
|
Geraet traegt den Tag**, daher null Wirkung. Sobald ein echtes Familiengeraet
|
||||||
|
dazukommt, wird es einmal mit `tag:family` getaggt und die Regel greift sofort
|
||||||
|
— ohne Policy-Umbau. Vor dem ersten realen Familiengeraet die Regel auf die
|
||||||
|
dann benoetigten Dienste/Ports pruefen.
|
||||||
|
- Der `ssh`-Block bleibt der Default (Tailscale-SSH Check-Modus); normaler
|
||||||
|
OpenSSH-Zugriff laeuft ueber die `grants` (Port 22, fuer `tag:operator` ueber
|
||||||
|
`ip: ["*"]` abgedeckt).
|
||||||
|
|
||||||
1. Aktueller ACL-JSON aus der Admin-Konsole (read-only gesichtet, nur Struktur dokumentiert).
|
**Offene Folgepunkte (kein Risiko, Hygiene/spaeter):**
|
||||||
2. Konkrete Liste der Dienste/Ports, die Familiengeraete ueber Tailscale brauchen.
|
|
||||||
|
- Familien-Dienste/Ports konkretisieren — erst wenn ein reales Familiengeraet dazukommt.
|
||||||
|
- **Zwei-Tailscale-Konsolidierung: ERLEDIGT 2026-06-06** — redundanter Docker-Stack
|
||||||
|
abgebaut, nur noch die native Plugin-Instanz `kallilabcore` (Subnet-Router) aktiv.
|
||||||
|
- **Tailnet-Konsole/Altstate aufraeumen: ERLEDIGT 2026-06-17** — Node-Eintraege
|
||||||
|
`kallilab-core` und alter Offline-`baerchen` sind im aktuellen Tailnet-Status
|
||||||
|
nicht mehr sichtbar; State-Pfad `/mnt/user/appdata/tailscale` vom entfernten
|
||||||
|
Docker-Stack liegt unter `_archive/tailscale-removed-2026-06-06/`.
|
||||||
|
- Optionaler Off-LAN-Routentest: von einem Operator-Geraet im Mobilfunk
|
||||||
|
(nicht im Heim-LAN) ein LAN-Ziel ueber `192.168.178.0/24` erreichen, um die
|
||||||
|
Subnet-Route end-to-end zu bestaetigen (im Heim-LAN nicht sauber isolierbar).
|
||||||
|
|
||||||
## Portfreigaben und Exposure
|
## Portfreigaben und Exposure
|
||||||
|
|
||||||
@@ -183,6 +216,7 @@ Bewusst **nicht** freigegeben:
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `80/tcp` | Cloudflare-DNS-Challenge ersetzt HTTP-01; Traefik macht HTTP->HTTPS-Redirect nur LAN-seitig; WAN-`80` waere zusaetzliche Angriffsflaeche ohne Funktionsnutzen. **2026-05-28 in FRITZ!Box-UI entfernt**, Validierung: Mobilfunk-Test ergibt Timeout auf `http://vault.kaleschke.info`, `https://...` weiter erreichbar. |
|
| `80/tcp` | Cloudflare-DNS-Challenge ersetzt HTTP-01; Traefik macht HTTP->HTTPS-Redirect nur LAN-seitig; WAN-`80` waere zusaetzliche Angriffsflaeche ohne Funktionsnutzen. **2026-05-28 in FRITZ!Box-UI entfernt**, Validierung: Mobilfunk-Test ergibt Timeout auf `http://vault.kaleschke.info`, `https://...` weiter erreichbar. |
|
||||||
| `222/tcp` (Gitea SSH) | bewusst Tailscale-only: Operator-Pfad ist Tailscale, GitHub-Mirror deckt DR-Bootstrap ab, Gitea-Bundles sind off-host. Externe SSH-Brute-Force-Vektoren vermeiden. |
|
| `222/tcp` (Gitea SSH) | bewusst Tailscale-only: Operator-Pfad ist Tailscale, GitHub-Mirror deckt DR-Bootstrap ab, Gitea-Bundles sind off-host. Externe SSH-Brute-Force-Vektoren vermeiden. |
|
||||||
|
| `32400/tcp` (Plex) | Plex wird extern ausschliesslich ueber `https://plex.kaleschke.info` via Traefik/443 erreicht. Kein direkter WAN-Port fuer Plex, Plex Remote Access bleibt aus. |
|
||||||
|
|
||||||
### UPnP / Selbstständige Portfreigaben
|
### UPnP / Selbstständige Portfreigaben
|
||||||
|
|
||||||
@@ -211,6 +245,7 @@ Historischer UI-Befund vor Bereinigung vom 2026-05-27 (`Internet -> Freigaben ->
|
|||||||
| 443/tcp | Traefik | HTTPS | WAN-Freigabe in FRITZ!Box erwartet |
|
| 443/tcp | Traefik | HTTPS | WAN-Freigabe in FRITZ!Box erwartet |
|
||||||
| 222/tcp | Gitea SSH | Git SSH | nur LAN/Tailscale; keine WAN-Freigabe |
|
| 222/tcp | Gitea SSH | Git SSH | nur LAN/Tailscale; keine WAN-Freigabe |
|
||||||
| 53/tcp+udp | AdGuard | DNS | LAN-only, dokumentierte Ausnahme |
|
| 53/tcp+udp | AdGuard | DNS | LAN-only, dokumentierte Ausnahme |
|
||||||
|
| 32400/tcp | Plex | Medienserver / Plex Web lokal | LAN/Tailscale direkt; extern nur via Traefik `https://plex.kaleschke.info`, keine WAN-Freigabe fuer 32400 |
|
||||||
| 8082/tcp | AdGuard Admin | Admin UI | Bind nur `100.80.98.33:8082` (Tailscale), nicht im LAN exponiert |
|
| 8082/tcp | AdGuard Admin | Admin UI | Bind nur `100.80.98.33:8082` (Tailscale), nicht im LAN exponiert |
|
||||||
| 8181/tcp | InfluxDB 3 Core | Home Assistant / Ecowitt Writer | 2026-05-31 effektiv nur `127.0.0.1:8181`, nicht LAN-exponiert |
|
| 8181/tcp | InfluxDB 3 Core | Home Assistant / Ecowitt Writer | 2026-05-31 effektiv nur `127.0.0.1:8181`, nicht LAN-exponiert |
|
||||||
|
|
||||||
@@ -227,7 +262,7 @@ docker ps --format "{{.Names}}: {{.Ports}}" | sort
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| LAN | 192.168.178.0/24 | Hauptnetz, Host `192.168.178.58`, FRITZ!Box meldet 35 aktive Geraete |
|
| LAN | 192.168.178.0/24 | Hauptnetz, Host `192.168.178.58`, FRITZ!Box meldet 35 aktive Geraete |
|
||||||
| WLAN 2,4 / 5 GHz | aktiv, SSID `Fritzi` | Standard-WLAN, im LAN-Adressbereich, kein eigener Adressraum |
|
| WLAN 2,4 / 5 GHz | aktiv, SSID `Fritzi` | Standard-WLAN, im LAN-Adressbereich, kein eigener Adressraum |
|
||||||
| Gast-WLAN | **inaktiv** (FRITZ!Box-UI) | Solange inaktiv: kein Gast-Pfad zu LAN-Diensten; AdGuard-Admin-Trennung primaer ueber Tailscale-Bind statt Netzsegmentierung |
|
| Gast-WLAN | aktiv, SSID `Fritzi Gastzugang` | FRITZ!Box-Gastnetz ist vom Heimnetz getrennt; Smoke 2026-06-06 vom iPhone bestaetigt keine Erreichbarkeit der getesteten LAN-/Admin-Ziele |
|
||||||
| IoT-Netz | nicht existent | Keine VLAN-Trennung dokumentiert |
|
| IoT-Netz | nicht existent | Keine VLAN-Trennung dokumentiert |
|
||||||
| Tailscale | aktiv | Operator-Zugang, Host-IP `100.80.98.33` |
|
| Tailscale | aktiv | Operator-Zugang, Host-IP `100.80.98.33` |
|
||||||
| VLANs | nicht in Nutzung | FRITZ!Box 7590 kann VLAN-Tagging an einzelnen LAN-Ports; aktuell nicht konfiguriert |
|
| VLANs | nicht in Nutzung | FRITZ!Box 7590 kann VLAN-Tagging an einzelnen LAN-Ports; aktuell nicht konfiguriert |
|
||||||
@@ -252,6 +287,126 @@ docker network inspect frontend_net | jq '.[0].Containers | keys'
|
|||||||
docker network inspect backend_net | jq '.[0].Internal'
|
docker network inspect backend_net | jq '.[0].Internal'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## SSH-Konfiguration Host
|
||||||
|
|
||||||
|
Geprueft 2026-06-06 (read-only), **gehaertet 2026-06-07** via `ssh root@192.168.178.58`.
|
||||||
|
|
||||||
|
| Parameter | Ist-Wert (effektiv via `sshd -T`, Stand 2026-06-07) | Soll | Status |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `Port` | `22` | 22 | ok |
|
||||||
|
| `PermitRootLogin` | `prohibit-password` | `prohibit-password` | **gehaertet 2026-06-07** |
|
||||||
|
| `PasswordAuthentication` | `no` | `no` | **gehaertet 2026-06-07** |
|
||||||
|
| `KbdInteractiveAuthentication` | `no` | `no` | **gehaertet 2026-06-07** (noetig wegen `UsePAM yes`) |
|
||||||
|
| `PubkeyAuthentication` | `yes` | `yes` | ok |
|
||||||
|
| `PermitEmptyPasswords` | `no` | `no` | ok |
|
||||||
|
| `AuthorizedKeysFile` | `.ssh/authorized_keys` | `.ssh/authorized_keys` | ok |
|
||||||
|
|
||||||
|
**Hinterlegte SSH-Keys (root):** 3 Keys vorhanden (persistiert unter `/boot/config/ssh/root/authorized_keys`):
|
||||||
|
- `root@Kallilabcore` (Host-eigener Key)
|
||||||
|
- `michi@Baerchen` (Operator-Workstation)
|
||||||
|
- `hetzner-storagebox-maintenance-2026-06-01` (Hetzner-Maintenance-Key)
|
||||||
|
|
||||||
|
**Durchgefuehrte Haertung (2026-06-07):** Root-Login ist jetzt key-only,
|
||||||
|
Passwort- und Keyboard-Interactive-Auth sind serverseitig abgeschaltet.
|
||||||
|
Verifiziert: frischer Key-Login `OK`; `ssh -o PreferredAuthentications=none`
|
||||||
|
meldet `Authentications that can continue: publickey`; reiner Passwort-Versuch
|
||||||
|
`Permission denied (publickey)`.
|
||||||
|
|
||||||
|
**Wichtig — Unraid-Persistenz:** `/etc/ssh/sshd_config` wird beim Boot aus dem
|
||||||
|
OS-Image regeneriert (`rc.sshd`: `cp -f /boot/config/ssh/* /etc/ssh/`, danach
|
||||||
|
`sshd_build`, das nur `Port`/`ListenAddress`/`AddressFamily` setzt). Die
|
||||||
|
Unraid-GUI (**Settings → Management Access → SSH**) bietet nur `Use SSH`/`SSH port`
|
||||||
|
an — **`PermitRootLogin`/`PasswordAuthentication` sind dort nicht einstellbar.**
|
||||||
|
Persistiert wird daher **upgrade-sicher** ueber einen idempotenten Hook:
|
||||||
|
|
||||||
|
- `/boot/config/ssh-harden.sh` — setzt die drei Direktiven idempotent (bestehende
|
||||||
|
aktive Zeile entfernen, genau einmal global vor dem ersten `Match`-Block einfuegen),
|
||||||
|
`sshd -t`-Validierung, Reload nur per `kill -HUP` des Host-`sshd` bei valider Config.
|
||||||
|
Idempotenz belegt: nach mehreren Laeufen je Direktive exakt 1 aktive Zeile, alte
|
||||||
|
`PermitRootLogin yes` entfernt.
|
||||||
|
- `/boot/config/go` — ruft `/bin/bash /boot/config/ssh-harden.sh` bei jedem Boot auf.
|
||||||
|
|
||||||
|
**Selbst-Verifikation (Syslog, rein informativ, keine Reparatur):** Das Skript
|
||||||
|
schreibt nach jedem Lauf die effektiven Auth-Werte (`sshd -T`) nach syslog, z. B.
|
||||||
|
`ssh-harden: VERIFY permitrootlogin prohibit-password pubkeyauthentication yes
|
||||||
|
passwordauthentication no kbdinteractiveauthentication no`. Damit ist nach jedem
|
||||||
|
Boot/Upgrade nachweisbar, ob die Haertung gegriffen hat.
|
||||||
|
|
||||||
|
**Post-Upgrade-/Reboot-Check** (manuell, einmal nach jedem Unraid-Upgrade):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# A) Effektive Werte direkt abfragen (Soll: prohibit-password / no / no / yes)
|
||||||
|
ssh root@192.168.178.58 "sshd -T | grep -Ei 'permitroot|passwordauth|kbdinteractive|pubkey'"
|
||||||
|
# B) Oder die automatische VERIFY-Zeile im Syslog lesen (Unraid nutzt rsyslog -> /var/log/syslog, nicht logread)
|
||||||
|
ssh root@192.168.178.58 "grep 'ssh-harden' /var/log/syslog | tail -3"
|
||||||
|
```
|
||||||
|
|
||||||
|
Dieser Weg editiert die **jeweils aktuelle** von Unraid generierte Config nach und
|
||||||
|
ueberlebt damit Unraid-Upgrades; findet er die Stock-Zeile nicht (z. B. weil eine
|
||||||
|
neue Version schon `prohibit-password` ausliefert), macht der `sed` nichts und
|
||||||
|
bricht den Boot nicht (fail-safe Richtung offen, nicht ausgesperrt). Bewusst
|
||||||
|
**nicht** der oft empfohlene Weg einer kompletten `/boot/config/ssh/sshd_config`
|
||||||
|
auf Flash — der wuerde die Stock-Config einfrieren und beim Upgrade neue Defaults
|
||||||
|
verschlucken.
|
||||||
|
|
||||||
|
**Rollback:** `go`-Block + `/boot/config/ssh-harden.sh` entfernen, dann
|
||||||
|
`cp /boot/config/ssh-harden.sshd_config.bak-20260607 /etc/ssh/sshd_config` und
|
||||||
|
`kill -HUP $(cat /var/run/sshd.pid)`. Notzugang ueber Unraid-Konsole/GUI bleibt.
|
||||||
|
|
||||||
|
**Abgrenzung:** Ein zweiter `sshd` (`-D -e`) laeuft in einem Docker-Container
|
||||||
|
(s6-overlay, moby-Namespace) und bindet **nicht** den Host-`:22`; eigene Config
|
||||||
|
im Container, von dieser Haertung unberuehrt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Post-Upgrade Posture-Recheck — Unraid 7.3.1 (2026-06-07)
|
||||||
|
|
||||||
|
Nach dem Major-Upgrade **7.2.4 → 7.3.1** read-only die Host-Listener-Landschaft
|
||||||
|
(`ss -tlnp`) gegen die dokumentierten Annahmen geprueft.
|
||||||
|
|
||||||
|
**Dokumentierte Ausnahmen verifiziert (alle weiterhin gueltig):**
|
||||||
|
|
||||||
|
| Dienst | Soll | Ist nach 7.3.1 | Status |
|
||||||
|
|---|---|---|---|
|
||||||
|
| InfluxDB 3 | nur `127.0.0.1:8181` | `127.0.0.1:8181` | ✅ |
|
||||||
|
| AdGuard-Admin | nur Tailscale `100.80.98.33:8082` | `100.80.98.33:8082` | ✅ |
|
||||||
|
| Gitea-SSH `222` | LAN/Tailscale, keine WAN-Freigabe | `0.0.0.0:222` (LAN/TS), WAN am Router zu | ✅ |
|
||||||
|
| Traefik `80/443` | einziger Owner | docker-proxy (Traefik) allein | ✅ |
|
||||||
|
| libvirt `:53` | darf nicht existieren | **weg** (Fix vom 2026-06-07 haelt) | ✅ |
|
||||||
|
|
||||||
|
**Docker-Socket (`/var/run/docker.sock`) — C-3-Kontext:**
|
||||||
|
|
||||||
|
| Container | Mount | Bewertung |
|
||||||
|
|---|---|---|
|
||||||
|
| komodo-periphery | **RW** | dokumentierte Ausnahme (Periphery startet/stoppt Container) |
|
||||||
|
| traefik | ro | C-3: Direkt-Mount (ro), nicht ueber Socket-Proxy — offener Audit-Punkt, kein Regress |
|
||||||
|
| glances / monitoring-promtail / glance-docker-socket-proxy | ro | unkritisch |
|
||||||
|
|
||||||
|
Keine neue RW-Socket-Exposure durch das Upgrade.
|
||||||
|
|
||||||
|
**Vorfall-Notiz AdGuard/DNS (Boot-Race, behoben 2026-06-07):** Das Upgrade hatte das
|
||||||
|
ungenutzte **libvirt-Default-Netz** auf Autostart gebracht; dessen `dnsmasq` belegte
|
||||||
|
beim Boot Port `53` **vor** AdGuard → AdGuards erster Start scheiterte am Bind und
|
||||||
|
liess den Container ohne Netz-Anbindung (`Networks={}`, keine Ports) zurueck. Fix:
|
||||||
|
`virsh net-autostart default --disable` + `virsh net-destroy default` (kein VM
|
||||||
|
betroffen, Liste leer) + AdGuard-Container aus der Compose `--force-recreate`
|
||||||
|
(re-attach `dns_net`, `:53` neu veroeffentlicht). DNS danach verifiziert aufloesend.
|
||||||
|
`libvirtd` laeuft weiter nur auf `127.0.0.1:16509`.
|
||||||
|
|
||||||
|
**Empfehlung (Dauerfix):** Da keine VMs genutzt werden, **Unraid VM Manager → Enable
|
||||||
|
VMs = No** — dann startet `libvirtd` gar nicht und der `:53`-Konflikt kann prinzipiell
|
||||||
|
nicht wiederkehren. Bis dahin verhindert der abgeschaltete Autostart die Wiederkehr.
|
||||||
|
|
||||||
|
**Beobachtungen (kein Regress, Inventar):** SMB (`:445/:139`) und Plex (`*:32400`)
|
||||||
|
lauschen auch auf der Tailscale-IP; durch die seit 2026-06-06 tag-restriktive
|
||||||
|
Tailnet-ACL akzeptabel.
|
||||||
|
|
||||||
|
**SSH-Haertung nach Upgrade:** key-only root unveraendert aktiv und verifiziert
|
||||||
|
(`prohibit-password`/`password no`/`kbd no`), go-Hook genau 1× gefeuert — siehe
|
||||||
|
Abschnitt „SSH-Konfiguration Host".
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Offene Entscheidungen
|
## Offene Entscheidungen
|
||||||
|
|
||||||
| Thema | Status | Naechster Schritt |
|
| Thema | Status | Naechster Schritt |
|
||||||
@@ -260,7 +415,8 @@ docker network inspect backend_net | jq '.[0].Internal'
|
|||||||
| FRITZ!Box-Portfreigaben mit Repo-Soll abgleichen | **erledigt 2026-06-01** | Bereinigt: `80/tcp` entfernt (Cloudflare-DNS-Challenge ersetzt HTTP-01; Mobilfunk-Test bestaetigt Timeout auf `http://`, `https://` weiter ok). `222/tcp` bleibt bewusst nicht eingerichtet (Tailscale-only-Linie). UPnP-Selbstfreigaben sind aus. Aktiver Soll-Stand: ausschliesslich `443/tcp -> 192.168.178.58`. |
|
| FRITZ!Box-Portfreigaben mit Repo-Soll abgleichen | **erledigt 2026-06-01** | Bereinigt: `80/tcp` entfernt (Cloudflare-DNS-Challenge ersetzt HTTP-01; Mobilfunk-Test bestaetigt Timeout auf `http://`, `https://` weiter ok). `222/tcp` bleibt bewusst nicht eingerichtet (Tailscale-only-Linie). UPnP-Selbstfreigaben sind aus. Aktiver Soll-Stand: ausschliesslich `443/tcp -> 192.168.178.58`. |
|
||||||
| FRITZ!Box-Dienste aus dem Internet | **erledigt 2026-06-01** | `Internet -> Freigaben -> FRITZ!Box-Dienste`: HTTPS-Zugriff auf die FRITZ!Box aus dem Internet aus; FTP/FTPS auf Speichermedien aus. |
|
| FRITZ!Box-Dienste aus dem Internet | **erledigt 2026-06-01** | `Internet -> Freigaben -> FRITZ!Box-Dienste`: HTTPS-Zugriff auf die FRITZ!Box aus dem Internet aus; FTP/FTPS auf Speichermedien aus. |
|
||||||
| FRITZ!OS Update und Konfig-Backup | **erledigt 2026-06-01** | TR-064 meldet `154.08.25`; Konfig-Export liegt extern/off-system in Vaultwarden, Kennwort und Datei bleiben ausserhalb des Repos. |
|
| FRITZ!OS Update und Konfig-Backup | **erledigt 2026-06-01** | TR-064 meldet `154.08.25`; Konfig-Export liegt extern/off-system in Vaultwarden, Kennwort und Datei bleiben ausserhalb des Repos. |
|
||||||
| Gast-/IoT-Zugriff auf Admin-Ports | **Entscheidungspunkt: kein Gast-/IoT-Netz aktivieren, solange nicht gebraucht** | Aktuell entschaerft, weil Gast-WLAN inaktiv ist und kein IoT-VLAN existiert. Risiko entsteht erst bei Aktivierung. Harte Vorbedingung fuer eine spaetere Aktivierung: **vor** dem Einschalten von Gast-WLAN/IoT muessen `192.168.178.58:8082` (AdGuard-Admin, ohnehin Tailscale-gebunden), `192.168.178.58:8181` (InfluxDB, bereits `127.0.0.1`-bound) und alle weiteren LAN-Admin-Ports per FRITZ!Box-Netzwerkfilter/Kindersicherung gegen das Gastsegment gesperrt sein. Bis dahin bewusst kein Gastnetz. |
|
| Gast-/IoT-Zugriff auf Admin-Ports | **validiert 2026-06-06** | Runbook `docs/GUEST_IOT_NETWORK.md` und Checks `ops/maintenance/check-guest-iot-isolation.ps1` sowie `ops/maintenance/check-guest-iot-preflight.sh` vorhanden. LAN-Preflight von `baerchen` gruen: `192.168.178.58:8082` und `:8181` blockiert. Host-Preflight auf Unraid gruen, Report `/mnt/user/backups/restore-reports/guest-iot-preflight-2026-06-06-131316.md`. Gast-WLAN-Smoke per iPhone: `192.168.178.58:8082`, `:8181`, `:222`, `https://192.168.178.58` und `192.168.178.1` nicht erreichbar. |
|
||||||
| IPv6 Exposure | technisch und per UI entschaerft | Public DNS liefert keine AAAA-Records fuer `*.kaleschke.info`; Host hat keine globale Provider-IPv6. TR-064 meldet IPv6-Firewall aktiv und Pinholes grundsaetzlich erlaubt; FRITZ!Box-UI zeigt keine aktiven IPv6-Freigaben, keine Admin-/SSH-Freigaben. |
|
| IPv6 Exposure | technisch und per UI entschaerft | Public DNS liefert keine AAAA-Records fuer `*.kaleschke.info`; Host hat keine globale Provider-IPv6. TR-064 meldet IPv6-Firewall aktiv und Pinholes grundsaetzlich erlaubt; FRITZ!Box-UI zeigt keine aktiven IPv6-Freigaben, keine Admin-/SSH-Freigaben. |
|
||||||
| WAN-Ausfallschutz | **geparkt: spaeter evaluieren** (Operator-Entscheidung 2026-06-05) | Mobilfunk-Stick-Failover an FRITZ!Box bleibt vorerst inaktiv. Folgen sind bewusst akzeptiert: Internet-Ausfall = ACME/DDNS pausieren, lokale Apps laufen weiter. Review-Trigger: haeufigere oder laengere DSL-Ausfaelle, oder wenn externer Remote-Zugang (statt nur lokalem Betrieb) geschaeftskritisch wird. Erst dann Mobilfunk-Failover technisch bewerten. |
|
| WAN-Ausfallschutz | **geparkt: spaeter evaluieren** (Operator-Entscheidung 2026-06-05) | Mobilfunk-Stick-Failover an FRITZ!Box bleibt vorerst inaktiv. Folgen sind bewusst akzeptiert: Internet-Ausfall = ACME/DDNS pausieren, lokale Apps laufen weiter. Review-Trigger: haeufigere oder laengere DSL-Ausfaelle, oder wenn externer Remote-Zugang (statt nur lokalem Betrieb) geschaeftskritisch wird. Erst dann Mobilfunk-Failover technisch bewerten. |
|
||||||
| Home Assistant InfluxDB Bind | validiert 2026-05-31 | `docker-proxy` bindet `127.0.0.1:8181`; keine LAN-Exposure. Wenn Home Assistant nicht lokal auf dem Host schreibt, braucht das eine bewusste Bind-Aenderung. |
|
| Home Assistant InfluxDB Bind | validiert 2026-05-31 | `docker-proxy` bindet `127.0.0.1:8181`; keine LAN-Exposure. Wenn Home Assistant nicht lokal auf dem Host schreibt, braucht das eine bewusste Bind-Aenderung. |
|
||||||
|
| SSH-Haertung Host | **erledigt 2026-06-07** | Root-Login key-only: `PermitRootLogin prohibit-password`, `PasswordAuthentication no`, `KbdInteractiveAuthentication no`. Live gesetzt + verifiziert (Key-Login ok, Passwort-Auth abgelehnt). Persistenz upgrade-sicher ueber `/boot/config/ssh-harden.sh` (idempotent, `sshd -t` vor Reload) aufgerufen aus `/boot/config/go`. GUI bietet diese Optionen nicht. Details im Abschnitt „SSH-Konfiguration Host". |
|
||||||
|
|||||||
+30
-17
@@ -1,29 +1,38 @@
|
|||||||
# Documentation Index
|
# Documentation Index
|
||||||
|
|
||||||
Stand: 2026-06-05
|
Typ: Einstieg/Index · Stand: 2026-06-11 · Status: aktiv
|
||||||
|
|
||||||
Diese Datei trennt aktive Betriebsdokumentation von historischer Arbeitsdoku. Neue operative Dokumente duerfen nur in `docs/` liegen, wenn sie heute als Einstieg, Runbook, Inventar oder offene Arbeitsliste gebraucht werden. Erledigte Audits, Chat-Handoffs, Prompt-Dateien und abgeschlossene Plaene bleiben in der Git-Historie, aber nicht als dauerhafte Arbeitskopie.
|
Diese Datei trennt aktive Betriebsdokumentation von historischer Arbeitsdoku.
|
||||||
|
Neue operative Dokumente duerfen nur in `docs/` liegen, wenn sie heute als
|
||||||
|
Einstieg, Runbook, Inventar, Entscheidung oder Statusliste gebraucht werden.
|
||||||
|
Abgeschlossene Audits, Drills und Plaene wandern nach `archive/` oder werden
|
||||||
|
geloescht (Git-Historie ist das Archiv). Verbindliche Doku-Regeln:
|
||||||
|
`REPO_MAP.md` Abschnitt "Doku-Regeln".
|
||||||
|
|
||||||
## Pflicht-Einstieg
|
## Pflicht-Einstieg
|
||||||
|
|
||||||
| Datei | Zweck |
|
| Datei | Zweck |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `../README.md` | kurzer Repo-Einstieg |
|
| `../README.md` | kurzer Repo-Einstieg |
|
||||||
|
| `../AGENTS.md` | Einstiegspunkt fuer KI-Agenten (Codex u. a.) |
|
||||||
| `../HOMELAB_ARCHITECTURE_MASTER_V2.md` | Architektur-Quelle fuer Netz, Zugriff und Ausnahmen |
|
| `../HOMELAB_ARCHITECTURE_MASTER_V2.md` | Architektur-Quelle fuer Netz, Zugriff und Ausnahmen |
|
||||||
| `WORKFLOW.md` | verbindlicher GitOps-/No-Drift-Ablauf |
|
| `WORKFLOW.md` | verbindlicher GitOps-/No-Drift-Ablauf |
|
||||||
| `REPO_MAP.md` | technische Landkarte des Repositories |
|
| `REPO_MAP.md` | technische Landkarte des Repositories + Doku-Regeln |
|
||||||
| `SERVICE_CATALOG.md` | produktiver Service-Katalog |
|
| `SERVICE_CATALOG.md` | produktiver Service-Katalog |
|
||||||
|
| `DECISIONS.md` | Entscheidungs-Register (ADR-light) |
|
||||||
|
| `MASTER_TODO.md` | einzige operative Statusliste |
|
||||||
|
|
||||||
## Betrieb und Recovery
|
## Betrieb und Recovery
|
||||||
|
|
||||||
| Datei | Zweck |
|
| Datei | Zweck |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `DISASTER_RECOVERY.md` | Wiederanlauf nach Host-/Systemausfall |
|
| `DISASTER_RECOVERY.md` | Wiederanlauf nach Host-/Systemausfall |
|
||||||
| `RESTORE_MATRIX.md` | Restore-Quellen, Dumps, Secrets und Smoke-Tests je Dienst |
|
| `RESTORE_MATRIX.md` | Restore-Quellen, Dumps, Secrets, Smoke-Tests und Test-Reifegrad je Dienst |
|
||||||
| `RESTORE_HANDBOOK.md` | praktische Restore-Anleitung |
|
|
||||||
| `SERVICES_RECOVERY.md` | Gitea-/Komodo-/Services-Bootstrap |
|
| `SERVICES_RECOVERY.md` | Gitea-/Komodo-/Services-Bootstrap |
|
||||||
| `ROLLBACK.md` | Rueckweg bei GitOps-/Deploy-Fehlern |
|
| `ROLLBACK.md` | Rueckweg bei GitOps-/Deploy-Fehlern |
|
||||||
| `GITOPS_DRIFT_RUNBOOK.md` | Pflichtmatrix bei Drift zwischen Git, Komodo, Docker und Host |
|
| `GITOPS_DRIFT_RUNBOOK.md` | Pflichtmatrix bei Drift zwischen Git, Komodo, Docker und Host |
|
||||||
|
| `DR_WORKSTATION_SETUP.md` | DR-Gaming-PC einrichten (WSL2 + Borg-Client + SSH-Keys) |
|
||||||
|
| `../ops/restore-tests/README.md` | Restore-Test-Betrieb, Skripte und Kadenz |
|
||||||
|
|
||||||
## Inventare und Policies
|
## Inventare und Policies
|
||||||
|
|
||||||
@@ -31,11 +40,13 @@ Diese Datei trennt aktive Betriebsdokumentation von historischer Arbeitsdoku. Ne
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `STORAGE_LAYOUT.md` | verbindliche Storage-/Share-/Pfad-Regeln |
|
| `STORAGE_LAYOUT.md` | verbindliche Storage-/Share-/Pfad-Regeln |
|
||||||
| `SECRETS_MAP.md` | Secret-Namen, Speicherorte und Einbindungsarten ohne Werte |
|
| `SECRETS_MAP.md` | Secret-Namen, Speicherorte und Einbindungsarten ohne Werte |
|
||||||
| `HARDWARE_INVENTORY.md` | Host-, Disk-, SMART-, USV- und Power-Baseline |
|
| `AUTHELIA_OIDC_PLAN.md` | Plan & Runbook fuer app-uebergreifendes SSO via Authelia OIDC |
|
||||||
|
| `HARDWARE_INVENTORY.md` | Host-, Disk-, SMART- und Power-Baseline |
|
||||||
| `NETWORK_INVENTORY.md` | Router, DNS, Tailscale, Portfreigaben und Netzthemen |
|
| `NETWORK_INVENTORY.md` | Router, DNS, Tailscale, Portfreigaben und Netzthemen |
|
||||||
| `EXTERNAL_DEPENDENCIES.md` | Provider, Konten und externe Abhaengigkeiten |
|
| `GUEST_IOT_NETWORK.md` | Sicherer Ablauf fuer FRITZ!Box-Gastnetz / IoT-Isolation |
|
||||||
|
| `EXTERNAL_DEPENDENCIES.md` | Provider, Konten, DR-Workstation-Kit und externe Abhaengigkeiten |
|
||||||
| `EXTERNAL_OPERATOR_RUNBOOK.md` | Hetzner-/Borg-/FRITZ!Box-Betreibercheck |
|
| `EXTERNAL_OPERATOR_RUNBOOK.md` | Hetzner-/Borg-/FRITZ!Box-Betreibercheck |
|
||||||
| `CAPACITY_AND_LIFECYCLE.md` | Kapazitaet, Wachstum und Upgrade-Trigger |
|
| `CAPACITY_AND_LIFECYCLE.md` | Kapazitaet, Wachstum, Upgrade-Trigger, H:/-Nearline-Einordnung |
|
||||||
|
|
||||||
## Monitoring und Automatisierung
|
## Monitoring und Automatisierung
|
||||||
|
|
||||||
@@ -43,18 +54,20 @@ Diese Datei trennt aktive Betriebsdokumentation von historischer Arbeitsdoku. Ne
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `ALERT_RULES.md` | Prometheus-/ntfy-Regeln und Handlungslogik |
|
| `ALERT_RULES.md` | Prometheus-/ntfy-Regeln und Handlungslogik |
|
||||||
| `RENOVATE.md` | Self-hosted Renovate gegen Gitea |
|
| `RENOVATE.md` | Self-hosted Renovate gegen Gitea |
|
||||||
| `HOME_ASSISTANT_INFLUXDB_ECOWITT.md` | Home Assistant -> InfluxDB 3 -> Grafana |
|
| `runbooks/komodo-bulk-deploy-dns.md` | Bulk-Deploy-Pulls scheitern an DNS bei AdGuard-Recreate |
|
||||||
| `H_DRIVE_NEARLINE_PULL.md` | Windows-H:/ Nearline-Pull fuer kritische Restore-Artefakte |
|
| `../ops/h-drive-nearline/README.md` | Windows-H:/ Nearline-Pull fuer kritische Restore-Artefakte |
|
||||||
|
|
||||||
## Nutzer- und Planungsdoku
|
## Nutzer- und Statusdoku
|
||||||
|
|
||||||
| Datei | Zweck |
|
| Datei | Zweck |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `FAMILY_ONBOARDING.md` | familienverstaendliche Nutzungsdoku |
|
| `FAMILY_ONBOARDING.md` | familienverstaendliche Nutzungsdoku |
|
||||||
| `AUDIT_2026-05-25_TODO.md` | kompakte Restliste aus dem Audit-Zyklus |
|
| `AI_CONTEXT.md` | kompakter Kontext fuer KI-Agenten (Regeln + Pointer, kein Status) |
|
||||||
| `MASTER_TODO.md` | zentrale operative Master-To-do-Liste ueber alle Bereiche |
|
| `homelab-optimierung.md` | technisches Optimierungs-Assessment 2026-06-10 (offene Empfehlungen) |
|
||||||
| `WEEKEND_EXECUTION_PLAN_2026-06-05.md` | Owner-Aufteilung und Wochenendplan fuer Todo-Abschluss |
|
|
||||||
| `WEEKEND_STATUS_2026-06-05.md` | kurzlebiges Arbeitsboard fuer den laufenden Wochenend-Sprint |
|
|
||||||
| `AI_CONTEXT.md` | kompakter Kontext fuer KI-Agenten |
|
|
||||||
|
|
||||||
Windows-Neuaufsetzen-Dokumente liegen nicht mehr in `docs/`, sondern im fachlich passenden Ordner `../ops/windows-reinstall/docs/`.
|
## Archiv
|
||||||
|
|
||||||
|
Abgeschlossene Snapshots, Drills und Audits: `archive/README.md`.
|
||||||
|
Windows-Neuaufsetzen-Doku (Projekt abgeschlossen) liegt ebenfalls dort;
|
||||||
|
aktiv geblieben sind nur Veeam-Baseline und Laufwerksstruktur unter
|
||||||
|
`../ops/windows-reinstall/`.
|
||||||
|
|||||||
+9
-1
@@ -93,7 +93,15 @@ Script: bash /mnt/user/services/homelab-infra/ops/renovate/run-renovate.sh
|
|||||||
| Schedule | `extends ["schedule:weekly"]` | Renovate-Engine prueft, aber PRs/Updates folgen Wochen-Profilen wo sinnvoll |
|
| Schedule | `extends ["schedule:weekly"]` | Renovate-Engine prueft, aber PRs/Updates folgen Wochen-Profilen wo sinnvoll |
|
||||||
| Dependency Dashboard | aktiv | Gitea-Issue, die alle ausstehenden Updates auflistet |
|
| Dependency Dashboard | aktiv | Gitea-Issue, die alle ausstehenden Updates auflistet |
|
||||||
| Onboarding-PR | `onboarding: false` | Keine `Configure Renovate`-Onboarding-PR; wir nutzen die Repo-`renovate.json` direkt |
|
| Onboarding-PR | `onboarding: false` | Keine `Configure Renovate`-Onboarding-PR; wir nutzen die Repo-`renovate.json` direkt |
|
||||||
| Ignore-Pfade | `_archive`, `ops/grafana-influxdb`, `ops/loki` | Renovate scant alte/abgeloeste Stacks nicht |
|
| Ignore-Pfade | `_archive`, `ops/grafana-influxdb`, `ops/loki`, `ops/komodo` | Renovate scant alte/abgeloeste Stacks nicht; `ops/komodo` ist bewusst raus (siehe unten) |
|
||||||
|
|
||||||
|
## Ausnahme: komodo-Stack ist inline-verwaltet, nicht git-deployed
|
||||||
|
|
||||||
|
Der `komodo`-Stack (Komodo-Core/Mongo/Periphery, Datei `ops/komodo/docker-compose.yml`) wird **nicht aus diesem Repo deployed**. In Komodo ist der Stack als **inline `file_contents`** (UI-defined) gespeichert (`repo` leer, `files_on_host=false`, `has_inline_file_contents=true`) und hat bewusst `webhook_enabled=false`, damit Komodo sich nicht selbst per Webhook recreated (Bootstrap-/Henne-Ei-Fall).
|
||||||
|
|
||||||
|
Konsequenz: Ein Renovate-PR auf `ops/komodo/docker-compose.yml` wirkt zur Laufzeit **nicht** (Komodo deployt aus seiner Inline-Definition) und erzeugt nur Git↔Komodo-Scheinsicherheit. Deshalb steht `ops/komodo/**` in `ignorePaths`. Die Repo-Datei bleibt als Doku/Spiegel und traegt den aktuell real laufenden Digest.
|
||||||
|
|
||||||
|
Befund-Datum 2026-06-10: Renovate-PR #13 (mongo-8.0.23 Digest-Refresh) wurde gemergt, wirkte aber nicht; der Digest wurde im Repo auf den laufenden Stand zurueckgesetzt und der Pfad ausgenommen. Echte Updates des komodo-Stacks laufen bis auf Weiteres manuell ueber Komodo (Inline-Compose anpassen) bzw. spaeter via Migration auf git-backed (eigener Aenderungsblock).
|
||||||
|
|
||||||
## Aktueller Betriebsstand
|
## Aktueller Betriebsstand
|
||||||
|
|
||||||
|
|||||||
+12
-5
@@ -33,8 +33,10 @@ Details gilt immer die betroffene Compose-Datei oder das jeweilige Runbook.
|
|||||||
| `docs/RESTORE_MATRIX.md` | Restore-Quelle je Dienst |
|
| `docs/RESTORE_MATRIX.md` | Restore-Quelle je Dienst |
|
||||||
| `docs/SECRETS_MAP.md` | Secret-Namen und Pfade ohne Werte |
|
| `docs/SECRETS_MAP.md` | Secret-Namen und Pfade ohne Werte |
|
||||||
| `docs/GITOPS_DRIFT_RUNBOOK.md` | Git/Gitea/Komodo/Docker/Host-Drift |
|
| `docs/GITOPS_DRIFT_RUNBOOK.md` | Git/Gitea/Komodo/Docker/Host-Drift |
|
||||||
| `docs/AUDIT_2026-05-25_TODO.md` | aktuelle Restliste |
|
| `docs/MASTER_TODO.md` | einzige operative Statusliste |
|
||||||
|
| `docs/DECISIONS.md` | Entscheidungs-Register (ADR-light) |
|
||||||
| `docs/DR_WORKSTATION_SETUP.md` | Schritt-fuer-Schritt-Runbook fuer den DR-Gaming-PC (WSL2 + Borg-Client + SSH-Keys) |
|
| `docs/DR_WORKSTATION_SETUP.md` | Schritt-fuer-Schritt-Runbook fuer den DR-Gaming-PC (WSL2 + Borg-Client + SSH-Keys) |
|
||||||
|
| `docs/runbooks/komodo-bulk-deploy-dns.md` | Bulk-Deploy-Pulls scheitern an DNS, wenn AdGuard im selben Batch recreated wird |
|
||||||
|
|
||||||
## Wichtige Skripte
|
## Wichtige Skripte
|
||||||
|
|
||||||
@@ -49,8 +51,13 @@ Details gilt immer die betroffene Compose-Datei oder das jeweilige Runbook.
|
|||||||
| `services/authelia-diff.sh` | Authelia ACL Repo-zu-Host-Vergleich |
|
| `services/authelia-diff.sh` | Authelia ACL Repo-zu-Host-Vergleich |
|
||||||
| `ops/h-drive-nearline/pull-critical-backups.ps1` | H:/ Nearline-Pull |
|
| `ops/h-drive-nearline/pull-critical-backups.ps1` | H:/ Nearline-Pull |
|
||||||
|
|
||||||
## Arbeitsregel
|
## Doku-Regeln
|
||||||
|
|
||||||
Neue Doku nur anlegen, wenn sie dauerhaft als Runbook, Inventar oder Restliste
|
1. **Ein Fakt, ein Zuhause.** Status -> `docs/MASTER_TODO.md`; Entscheidungen -> `docs/DECISIONS.md`; Zielbild -> `HOMELAB_ARCHITECTURE_MASTER_V2.md`/Inventare/`SERVICE_CATALOG`; Ablauf -> genau ein Runbook; Beleg -> Host-Report (`/mnt/user/backups/restore-reports/`) oder Git-Commit. Alle anderen Stellen verlinken statt kopieren.
|
||||||
gebraucht wird. Einmalige Audits, Prompt-Kopien und lange Verlaufsprotokolle
|
2. **Erledigt = raus aus der Arbeitskopie.** Abgeschlossene Plaene, Sprints, Audits und Drills nach `docs/archive/` (Belege mit Referenzwert) oder loeschen (Sprint-Boards, erledigte Listen) - Git ist das Archiv.
|
||||||
gehoeren in Git-Commits, nicht als neue Markdown-Dateien.
|
3. **Neue Datei nur mit klarem Typ:** Einstieg/Index, Architektur, Inventar/Referenz, Runbook, Entscheidung, Status oder befristeter Snapshot. Sonst ist es ein Eintrag in einer bestehenden Datei.
|
||||||
|
4. **Done-Eintraege max. 3 Zeilen**, Details in Commit/Report; Kurzlog in `MASTER_TODO` max. 5 Eintraege.
|
||||||
|
5. **Datum im Dateinamen nur fuer Snapshots**; datierte Dateien im `docs/`-Root sind per Definition Aufraeum-Kandidaten.
|
||||||
|
6. **Index-Pflicht:** jede neue/geloeschte Doku-Datei aktualisiert `docs/README.md` im selben Commit.
|
||||||
|
7. **Quartals-Gaertnern (~15 min):** Datiertes archivieren, Done-/Review-Logs kuerzen, tote Links pruefen.
|
||||||
|
8. **Kopfzeile je Dokument:** `Typ: ... · Stand: YYYY-MM-DD · Status: ...`. Bestandsnamen (SCREAMING_SNAKE) bleiben; neue Dateien in Unterordnern in kebab-case.
|
||||||
|
|||||||
@@ -1,250 +0,0 @@
|
|||||||
# Restore Handbook - KalliLab CORE
|
|
||||||
|
|
||||||
Stand: 2026-06-03
|
|
||||||
|
|
||||||
Dieses Handbuch ist die praktische Betriebsanleitung fuer Restore-Checks und Restore-Lab in KalliLab CORE.
|
|
||||||
|
|
||||||
Es ergaenzt:
|
|
||||||
|
|
||||||
- `docs/RESTORE_MATRIX.md`
|
|
||||||
- `docs/DISASTER_RECOVERY.md`
|
|
||||||
- `ops/restore-tests/*`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Ziel
|
|
||||||
|
|
||||||
Dieses Handbuch beantwortet vier Fragen:
|
|
||||||
|
|
||||||
1. Was ist die Restore-Quelle?
|
|
||||||
2. Wo wird getestet?
|
|
||||||
3. Wie pruefen wir Erfolg?
|
|
||||||
4. Wie machen wir das regelmaessig mit wenig Handarbeit?
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Grundmuster
|
|
||||||
|
|
||||||
Alle validierten Restore-Tests folgen demselben Muster:
|
|
||||||
|
|
||||||
- Quelle bleibt das produktive Borg-Repo bei Hetzner
|
|
||||||
- Borg-Zugriff laeuft ueber den vorhandenen `borg-ui`-Container
|
|
||||||
- Passphrase kommt aus `/mnt/user/appdata/secrets/borg_repo_passphrase.txt`
|
|
||||||
- Testdaten landen unter `/mnt/user/backups/restore-lab/<dienst>`
|
|
||||||
- Reports landen unter `/mnt/user/backups/restore-reports`
|
|
||||||
- Testinstanzen laufen lokal ohne Traefik und ohne produktive Domain
|
|
||||||
- nach Erfolg werden Testcontainer und Testdaten wieder entfernt
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Bereits praktisch verifiziert
|
|
||||||
|
|
||||||
### Vaultwarden
|
|
||||||
|
|
||||||
- Erstlauf: 2026-05-07
|
|
||||||
- Nachweis: Borg-Restore, Testcontainer, Login-Seite erreichbar
|
|
||||||
|
|
||||||
### Gitea
|
|
||||||
|
|
||||||
- Erstlauf: 2026-05-07
|
|
||||||
- Nachweis: Borg-Restore, Web-UI, SSH-TCP-Port
|
|
||||||
|
|
||||||
### Paperless
|
|
||||||
|
|
||||||
- Erstlauf: 2026-05-07, Folgelauf: 2026-05-31
|
|
||||||
- Nachweis: Borg-Datei-Restore, Dump-Import in Test-Postgres, Login-Seite, Doc-Count
|
|
||||||
|
|
||||||
### Immich
|
|
||||||
|
|
||||||
- Erstlauf: 2026-05-27
|
|
||||||
- Nachweis: DB-Dump-Restore in VectorChord-Test-Postgres, HTTP-Smoke, Asset-Count
|
|
||||||
- Hinweis: Foto-Dateien-Restore ist bewusst nicht Teil des Smokes
|
|
||||||
|
|
||||||
### Authelia
|
|
||||||
|
|
||||||
- Erstlauf: 2026-06-03
|
|
||||||
- Nachweis: Config-Borg-Restore, `authelia config validate`, HTTP-Health `/api/health`
|
|
||||||
- Hinweis: Daten-Restore des produktiven Dumps ist bewusst nicht Teil des Smokes (Storage-Encryption-Key-Kopplung)
|
|
||||||
|
|
||||||
### Komodo Bootstrap
|
|
||||||
|
|
||||||
- Erstlauf: 2026-05-30
|
|
||||||
- Nachweis: Compose-Validierung, Mongo healthy, Core HTTP, Periphery running
|
|
||||||
- Hinweis: Daten-Restore aus `komodo-mongo.archive.gz` ist noch nicht getestet
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Verzeichnisstruktur
|
|
||||||
|
|
||||||
### Produktiv
|
|
||||||
|
|
||||||
- `/mnt/user/appdata`
|
|
||||||
- `/mnt/user/services`
|
|
||||||
- `/mnt/user/documents`
|
|
||||||
- `/mnt/user/backups/borg/dumps/latest`
|
|
||||||
|
|
||||||
### Restore-Lab
|
|
||||||
|
|
||||||
- `/mnt/user/backups/restore-lab/vaultwarden`
|
|
||||||
- `/mnt/user/backups/restore-lab/gitea`
|
|
||||||
- `/mnt/user/backups/restore-lab/paperless`
|
|
||||||
- `/mnt/user/backups/restore-lab/immich`
|
|
||||||
- `/mnt/user/backups/restore-lab/authelia`
|
|
||||||
- `/mnt/user/backups/restore-lab/komodo`
|
|
||||||
- `/mnt/user/backups/restore-lab/_failed` (Diagnose-Material bei Fehllaeufen)
|
|
||||||
|
|
||||||
### Reports
|
|
||||||
|
|
||||||
- `/mnt/user/backups/restore-reports`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Restore-Frequenz
|
|
||||||
|
|
||||||
- jeden Montag, 06:30: Frische-Check fuer Dumps und Reports
|
|
||||||
- 1. Samstag im Monat, 07:00: Vaultwarden
|
|
||||||
- 3. Samstag im Monat, 07:15: Gitea
|
|
||||||
- 2. Samstag in ungeraden Monaten, 08:00: Paperless
|
|
||||||
- 2. Sonntag in Feb/Mai/Aug/Nov, 08:30: Immich
|
|
||||||
- 2. Samstag in geraden Monaten, 07:30: Authelia
|
|
||||||
- 1. Kalendertag im Monat, 09:00: Zufaelliger Restore aus Pool
|
|
||||||
|
|
||||||
Vollstaendiger Kalender mit Cron-Ausdruecken und Shell-Guards steht in `ops/restore-tests/schedule.md`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Betriebsmodus
|
|
||||||
|
|
||||||
Stand 2026-06-03 ist der Betrieb auf V1+ (V1 mit ntfy):
|
|
||||||
|
|
||||||
- validierte Bash-Host-Jobs fuer Vaultwarden, Gitea, Paperless, Immich, Authelia, Komodo-Bootstrap
|
|
||||||
- Host-Job-Definitionen und Cron-Vorlagen liegen im Repo (`ops/restore-tests/unraid-user-scripts.md`)
|
|
||||||
- `ntfy`-Wrapper sendet Erfolg an `homelab-info`, Fehler an `homelab-alerts`
|
|
||||||
- Frische-Check prueft zusaetzlich pg-Custom-Format-Dumps per `pg_restore --list` Header-Validierung
|
|
||||||
- bei Fehlschlag wird das Restore-Lab nach `_failed/` verschoben statt geloescht
|
|
||||||
|
|
||||||
Noch geplant fuer V2:
|
|
||||||
|
|
||||||
- Hermes-Zusammenfassung ueber vorhandene Reports
|
|
||||||
- Sammelreports und Report-Rotation
|
|
||||||
- weitere Dienste (Nextcloud, Mailarchiver, Mealie)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. User Script Jobs auf Unraid
|
|
||||||
|
|
||||||
Die Vorlagen stehen in:
|
|
||||||
|
|
||||||
- `ops/restore-tests/unraid-user-scripts.md`
|
|
||||||
|
|
||||||
Host-Repo-Pfad:
|
|
||||||
|
|
||||||
```text
|
|
||||||
/mnt/user/services/homelab-infra
|
|
||||||
```
|
|
||||||
|
|
||||||
Jobs:
|
|
||||||
|
|
||||||
1. `restore-freshness-weekly`
|
|
||||||
2. `restore-vaultwarden-monthly`
|
|
||||||
3. `restore-gitea-monthly`
|
|
||||||
4. `restore-paperless-bimonthly`
|
|
||||||
5. `restore-immich-quarterly`
|
|
||||||
6. `restore-authelia-bimonthly`
|
|
||||||
7. `monthly-random-restore`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Erfolgskriterien
|
|
||||||
|
|
||||||
Ein Restore-Test gilt nur dann als erfolgreich, wenn:
|
|
||||||
|
|
||||||
- Restore-Quelle lesbar war
|
|
||||||
- Daten im Restore-Lab ankamen
|
|
||||||
- Testcontainer startete
|
|
||||||
- Smoke-Test erfolgreich war
|
|
||||||
- Report geschrieben wurde
|
|
||||||
|
|
||||||
Nur `Container laeuft` reicht nicht.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. Sicherheitsregeln
|
|
||||||
|
|
||||||
- keine produktiven Pfade beschreiben
|
|
||||||
- keine produktiven Container fuer Restore-Tests verwenden
|
|
||||||
- keine produktiven Domains fuer Testinstanzen verwenden
|
|
||||||
- keine Secrets im Repo
|
|
||||||
- keine Restore-Automatik fuer neue Dienste ohne bewusste Freigabe
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. Schnellstart
|
|
||||||
|
|
||||||
### Frische-Check
|
|
||||||
|
|
||||||
Auf dem Unraid-Host:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh freshness
|
|
||||||
```
|
|
||||||
|
|
||||||
### Vaultwarden Restore-Check
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh vaultwarden
|
|
||||||
```
|
|
||||||
|
|
||||||
### Gitea Restore-Check
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh gitea
|
|
||||||
```
|
|
||||||
|
|
||||||
### Paperless Restore-Check
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh paperless
|
|
||||||
```
|
|
||||||
|
|
||||||
### Immich Restore-Check
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh immich
|
|
||||||
```
|
|
||||||
|
|
||||||
### Authelia Restore-Check
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh authelia
|
|
||||||
```
|
|
||||||
|
|
||||||
### Komodo Bootstrap Trockenlauf
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh komodo-bootstrap
|
|
||||||
```
|
|
||||||
|
|
||||||
### Optional mit `ntfy`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-job-with-ntfy.sh freshness homelab-info
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 11. Naechste Ausbaustufen
|
|
||||||
|
|
||||||
1. Nextcloud-Restore-Test (mit `occ maintenance:mode`-Choreographie)
|
|
||||||
2. Mailarchiver-Restore-Test
|
|
||||||
3. Mealie-Restore-Test
|
|
||||||
4. Komodo-Mongo-Daten-Restore (echtes `mongorestore` statt reinem Bootstrap)
|
|
||||||
5. Shared-PostgreSQL-18-Cluster-Restore-Drill (globals + per-DB-Dumps)
|
|
||||||
6. Traefik-Restore-Test (mit `dynamic/` und LE-State)
|
|
||||||
7. Hermes-Zusammenfassung ueber vorhandene Reports
|
|
||||||
8. Report-Rotation (archivieren nach 12 Monaten)
|
|
||||||
9. Negativ-Test: bewusst kaputten Dump in den Frische-Check einfuettern
|
|
||||||
|
|
||||||
## 12. Report-Aufbewahrung
|
|
||||||
|
|
||||||
Reports unter `/mnt/user/backups/restore-reports` werden dauerhaft aufbewahrt. Bei wachsender Anzahl (ca. 50-60 pro Jahr) empfiehlt sich eine jaehrliche Archivierung alter Reports in einen Unterordner `_archive/YYYY/`. Der Frische-Check warnt bei `MAX_REPORT_AGE_DAYS=45`, loescht aber bewusst nicht automatisch.
|
|
||||||
+25
-149
@@ -28,10 +28,10 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
|
|||||||
|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|
|
||||||
| 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 |
|
| Unraid OS Flash | Borg-Artefakt + optional Unraid Connect | `/boot/config` aus `unraid-flash-config.tar.gz` | `unraid-flash-config.tar.gz`, `.sha256`, Manifest | enthaelt sensible Host-Konfiguration, wie Secret-Material behandeln | Unraid USB Flash Creator / neuer Boot-Stick | Unraid bootet, Array-Zuordnung und Shares sind sichtbar |
|
||||||
| Traefik | Share / Borg | `/mnt/user/appdata/traefik`, besonders `dynamic/`, `letsencrypt`, `secrets` | keine eigene DB | `cloudflare_dns_api_token` | `frontend_net`, `backend_net` | `https://traefik.kaleschke.info` erreichbar, Dashboard ueber Authelia |
|
| Traefik | Share / Borg | `/mnt/user/appdata/traefik`, besonders `dynamic/`, `letsencrypt`, `secrets` | keine eigene DB | `cloudflare_dns_api_token` | `frontend_net`, `backend_net` | `https://traefik.kaleschke.info` erreichbar, Dashboard ueber Authelia |
|
||||||
| AdGuard Home | Share / Borg | `/mnt/user/appdata/adguard/conf` | keine | keine zusaetzlichen Repo-Secrets dokumentiert | `dns_net`, `frontend_net` | DNS-Aufloesung funktioniert |
|
| AdGuard Home | Share / Borg | `/mnt/user/appdata/adguard/conf` | keine | keine zusaetzlichen Repo-Secrets dokumentiert | `dns_net`, `frontend_net` | DNS-Aufloesung funktioniert; Restore-Smoke am 2026-06-06 erfolgreich |
|
||||||
| Tailscale | Share / Borg | `/mnt/user/appdata/tailscale` | keine | Tailscale-State im Pfad | Host-Netz | Tailscale verbunden |
|
| Tailscale | Flash-Backup (funktional) | **Funktional: `/boot/config/plugins/tailscale/state`** (native Unraid-Plugin-Instanz `kallilabcore`, Subnet-Router, im Flash-Backup gesichert). Der frueher genannte Pfad `/mnt/user/appdata/tailscale` gehoerte zum entfernten userspace-only Docker-Stack `kallilab-core` und ist seit 2026-06-17 nach `/mnt/user/appdata/_archive/tailscale-removed-2026-06-06/` verschoben; nicht mehr als aktive Restore-Quelle behandeln | keine | Tailscale-State im Flash-Backup; Archivpfad nur fuer Altanalyse | Host-Netz | Tailscale verbunden, Subnet-Route `192.168.178.0/24` aktiv |
|
||||||
| PostgreSQL 18 | Share + Dumps | `/mnt/user/appdata/postgresql18` (archivierter Rollback-Altstand: `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/postgresql17`) | `postgresql17-globals.sql`, `postgresql17-mailarchiver.dump`, `postgresql17-paperless.dump`, optional `postgresql17-authelia.dump` | `postgres_password.txt`, App-Rollen-Passwoerter aus den jeweiligen Stack-ENV/Secret-Dateien | `backend_net` | DB startet, Ziel-Datenbanken vorhanden; `SHOW data_checksums` ist `on` |
|
| PostgreSQL 18 | Share + Dumps | `/mnt/user/appdata/postgresql18` (archivierter Rollback-Altstand: `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/postgresql17`) | `postgresql17-globals.sql`, `postgresql17-mailarchiver.dump`, `postgresql17-paperless.dump`, optional `postgresql17-authelia.dump` | `postgres_password.txt`, App-Rollen-Passwoerter aus den jeweiligen Stack-ENV/Secret-Dateien | `backend_net` | DB startet, Ziel-Datenbanken vorhanden; `SHOW data_checksums` ist `on` |
|
||||||
| Redis 8 | Share / Host | `/mnt/user/appdata/redis`; Rollback-Backup unter `/mnt/user/backups/borg/dumps/latest/shared-redis-pre-redis8-<ts>` | RDB/AOF-Dateien im Datenpfad | `redis_password.txt` | `backend_net` | Redis startet, `redis_version` ist 8.x, Apps verbinden sich |
|
| Redis 8 | Share / Host | `/mnt/user/appdata/redis`; Rollback-Backup unter `/mnt/user/backups/borg/dumps/latest/shared-redis-pre-redis8-<ts>` | RDB/AOF-Dateien im Datenpfad | `redis_password.txt` | `backend_net` | Redis startet, `redis_version` ist 8.x, Apps verbinden sich; Restore-Smoke am 2026-06-06 erfolgreich |
|
||||||
| Authelia | Borg | `/mnt/user/appdata/authelia/config`, `/mnt/user/appdata/secrets/*authelia*` | Shared PostgreSQL 18, optional Dump `postgresql17-authelia.dump` | JWT/Session/Storage/Postgres-/SMTP-Secret-Dateien | PostgreSQL 18, Traefik, GMX SMTP | Login-Seite und ForwardAuth funktionieren; SMTP-Notifier startet; aktive Sessions werden nach Restart neu aufgebaut; Restore-Smoke am 2026-06-03 erfolgreich: Config aus Borg, minimale Test-Config, frisches Test-Postgres, HTTP `/api/health` 200, Report `/mnt/user/backups/restore-reports/authelia-2026-06-03.md` |
|
| Authelia | Borg | `/mnt/user/appdata/authelia/config`, `/mnt/user/appdata/secrets/*authelia*` | Shared PostgreSQL 18, optional Dump `postgresql17-authelia.dump` | JWT/Session/Storage/Postgres-/SMTP-Secret-Dateien | PostgreSQL 18, Traefik, GMX SMTP | Login-Seite und ForwardAuth funktionieren; SMTP-Notifier startet; aktive Sessions werden nach Restart neu aufgebaut; Restore-Smoke am 2026-06-03 erfolgreich: Config aus Borg, minimale Test-Config, frisches Test-Postgres, HTTP `/api/health` 200, Report `/mnt/user/backups/restore-reports/authelia-2026-06-03.md` |
|
||||||
| Gitea | GitHub-Mirror + Gitea-Bundles fuer Repo-Bootstrap, Borg + Dump fuer Gitea-Appstate | `/mnt/user/services/gitea/data`, `/mnt/user/backups/git-bundles/gitea` | `gitea.sqlite.dump`, Bundle-Report `latest-report.md` | `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; Bundle laesst sich klonen und `git fsck` ist sauber; GitHub-Push-Mirror synchronisiert ohne `last_error`; Mini-Restore nach `/mnt/user/backups/restore-lab/gitea` am 2026-05-07 erfolgreich validiert |
|
| Gitea | GitHub-Mirror + Gitea-Bundles fuer Repo-Bootstrap, Borg + Dump fuer Gitea-Appstate | `/mnt/user/services/gitea/data`, `/mnt/user/backups/git-bundles/gitea` | `gitea.sqlite.dump`, Bundle-Report `latest-report.md` | `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; Bundle laesst sich klonen und `git fsck` ist sauber; 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 |
|
| 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 |
|
||||||
@@ -44,7 +44,7 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
|
|||||||
|
|
||||||
| System | Fuehrende Quelle | Datei-Restore | Dump / DB | Secrets / ENV | Abhaengigkeiten | Smoke-Test |
|
| System | Fuehrende Quelle | Datei-Restore | Dump / DB | Secrets / ENV | Abhaengigkeiten | Smoke-Test |
|
||||||
|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|
|
||||||
| `baerchen` Windows 11 | Veeam Agent Image auf Unraid-SMB | `/mnt/user/backups/windows-images/baerchen/` bzw. `\\kallilabcore\backups\windows-images\baerchen` | Veeam Restore Points im Zielordner; erster Full-Lauf 2026-06-05, GUI-Groesse 53,8 GB, Dauer 0:11:31, MetaCheck 0 Fehler/0 Warnungen | SMB-User `micha`; Veeam Job Encryption Password nur noetig, falls Storage Encryption spaeter aktiviert wird; BitLocker-Recovery-Key erst noetig, wenn BitLocker spaeter aktiviert wird | Veeam Recovery USB `VEEAMRE`, SMB auf `kallilabcore`, AdGuard/DNS oder direkte IP | Von `VEEAMRE` booten, SMB-Ziel oeffnen, Restore Point anzeigen, vor echtem Restore abbrechen; Runbook `ops/windows-reinstall/docs/windows-image-backup-baseline.md` |
|
| `baerchen` Windows 11 | Veeam Agent Image auf Unraid-SMB | `/mnt/user/backups/windows-images/baerchen/` bzw. `\\kallilabcore\backups\windows-images\baerchen` | Veeam Restore Points im Zielordner; erster Full-Lauf 2026-06-05, GUI-Groesse 53,8 GB, Dauer 0:11:31, MetaCheck 0 Fehler/0 Warnungen | SMB-User `micha`; Veeam Job Encryption Password nur noetig, falls Storage Encryption spaeter aktiviert wird; BitLocker-Recovery-Key erst noetig, wenn BitLocker spaeter aktiviert wird | Veeam Recovery USB `VEEAMRE`, SMB auf `kallilabcore`, AdGuard/DNS oder direkte IP | Recovery-Test am 2026-06-06 erfolgreich: USB-Boot, SMB-Ziel erreichbar, Restore Point sichtbar, vor echtem Restore abgebrochen; Runbook `ops/windows-reinstall/docs/windows-image-backup-baseline.md` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
|
|||||||
|
|
||||||
| Dienst | Fuehrende Quelle | Datei-Restore | Dump / DB | Secrets / ENV | Abhaengigkeiten | Smoke-Test |
|
| Dienst | Fuehrende Quelle | Datei-Restore | Dump / DB | Secrets / ENV | Abhaengigkeiten | Smoke-Test |
|
||||||
|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|
|
||||||
| Paperless-ngx | Borg + Dumps | `/mnt/user/appdata/paperless-ngx/data`, `/mnt/user/documents/paperless`, `/mnt/user/documents/paperless/export`, `/mnt/user/documents/scans_inbox` | `postgresql17-paperless.dump` | `PAPERLESS_DBPASS`, `PAPERLESS_REDIS`, `borg_repo_passphrase.txt` fuer Restore-Tests | PostgreSQL 18, Redis, Traefik | Web-UI startet, Dokumente vorhanden; Restore-Test am 2026-05-31 erfolgreich: Borg-Archiv `Tägliche-Sicherung-2026-05-31T04:30:13.181`, isolierter PostgreSQL-18-/Redis-8-Testpfad, HTTP `200`, `32` Dokumente im Test-DB-Check, Report `/mnt/user/backups/restore-reports/paperless-2026-05-31.md` |
|
| Paperless-ngx | Borg + Dumps | `/mnt/user/appdata/paperless-ngx/data`, `/mnt/user/documents/paperless`, `/mnt/user/documents/paperless/export`, `/mnt/user/documents/scans_inbox` | `postgresql17-paperless.dump` | `PAPERLESS_DBPASS`, `PAPERLESS_REDIS`, `PAPERLESS_OIDC_SECRET`, `borg_repo_passphrase.txt` fuer Restore-Tests | PostgreSQL 18, Redis, Traefik, Authelia OIDC | Web-UI startet, Dokumente vorhanden; Restore-Test am 2026-05-31 erfolgreich: Borg-Archiv `Tägliche-Sicherung-2026-05-31T04:30:13.181`, isolierter PostgreSQL-18-/Redis-8-Testpfad, HTTP `200`, `32` Dokumente im Test-DB-Check, Report `/mnt/user/backups/restore-reports/paperless-2026-05-31.md`; OIDC-Secret am 2026-06-17 verdrahtet, lokaler Login bleibt Fallback |
|
||||||
| Mealie | Borg + Dump | `/mnt/user/appdata/mealie/data`, `/mnt/user/appdata/mealie/postgres18` (archivierter Rollback-Altstand: `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/mealie-postgres17`) | `mealie.dump` | `mealie_postgres_password.txt` | `mealie-postgres`, Traefik | UI startet, Rezepte vorhanden |
|
| Mealie | Borg + Dump | `/mnt/user/appdata/mealie/data`, `/mnt/user/appdata/mealie/postgres18` (archivierter Rollback-Altstand: `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/mealie-postgres17`) | `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`, `/mnt/user/appdata/immich_postgres_vectorchord`; archivierter Rollback-Altstand: `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/immich-postgres-pgvecto-rs` | `immich.dump`; nach VectorChord braucht ein Restore ein Postgres-Image mit VectorChord | `IMMICH_DB_PASSWORD`, `immich_postgres_password.txt`, `borg_repo_passphrase.txt` fuer Restore-Tests | `immich_postgres`, `immich_redis`, Traefik | DB- und UI-Smoke gegen produktives Borg-Archiv am 2026-05-27 erfolgreich validiert; VectorChord-Migration am 2026-05-31: `11977` Assets, `11107` Smart-Search-Zeilen, `7092` Face-Search-Zeilen, `vchord 0.4.3`, `vector 0.8.1`, HTTP/API-Smoke 200. Voll-Restore der Foto-Dateien bleibt separater DR-Drill |
|
| Immich | Borg + Dump | `/mnt/user/photos/immich`, `/mnt/user/photos/family_archive`, `/mnt/user/appdata/immich_postgres_vectorchord`; archivierter Rollback-Altstand: `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/immich-postgres-pgvecto-rs` | `immich.dump`; nach VectorChord braucht ein Restore ein Postgres-Image mit VectorChord | `IMMICH_DB_PASSWORD`, `immich_postgres_password.txt`, `borg_repo_passphrase.txt` fuer Restore-Tests | `immich_postgres`, `immich_redis`, Traefik | DB- und UI-Smoke gegen produktives Borg-Archiv am 2026-05-27 erfolgreich validiert; VectorChord-Migration am 2026-05-31: `11977` Assets, `11107` Smart-Search-Zeilen, `7092` Face-Search-Zeilen, `vchord 0.4.3`, `vector 0.8.1`, HTTP/API-Smoke 200. Voll-Restore der Foto-Dateien bleibt separater DR-Drill |
|
||||||
| Mail-Archiver | Borg + Shared Dump | `/mnt/user/appdata/mailarchiver/data-protection-keys` | `postgresql17-mailarchiver.dump` | `MAILARCHIVER_DB_CONNECTION`, `MAILARCHIVER_AUTH_PASSWORD` | PostgreSQL 18, Traefik, Authelia | Authelia-Weiterleitung greift; nach Login startet die Web-UI und das Archiv laesst sich oeffnen |
|
| Mail-Archiver | Borg + Shared Dump | `/mnt/user/appdata/mailarchiver/data-protection-keys` | `postgresql17-mailarchiver.dump` | `MAILARCHIVER_DB_CONNECTION`, `MAILARCHIVER_AUTH_PASSWORD` | PostgreSQL 18, Traefik, Authelia | Authelia-Weiterleitung greift; nach Login startet die Web-UI und das Archiv laesst sich oeffnen |
|
||||||
@@ -60,6 +60,10 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
|
|||||||
| 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` |
|
| Glance | Git / Borg-Repo | Repo-Konfiguration unter `ops/glance/config/glance.yml`; keine kritische Datenpersistenz | keine | `GLANCE_IMMICH_API_KEY`, `GLANCE_ADGUARD_USERNAME`, `GLANCE_ADGUARD_PASSWORD`, `GLANCE_SPEEDTEST_API_KEY` | Traefik, Authelia, optional interne API-Ziele | Dashboard startet, Widgets laden, Docker-Status laeuft nur ueber `glance-docker-socket-proxy` |
|
||||||
| ntfy | Borg / Share | `/mnt/user/appdata/ntfy` | keine | keine besonderen Secret-Dateien dokumentiert | Traefik | UI und Push-Endpunkt erreichbar |
|
| ntfy | Borg / Share | `/mnt/user/appdata/ntfy` | keine | keine besonderen Secret-Dateien dokumentiert | Traefik | UI und Push-Endpunkt erreichbar |
|
||||||
| Paperless-GPT | Borg / Share | `/mnt/user/appdata/paperless-gpt` | keine eigene DB | `PAPERLESS_API_TOKEN`, `OPENAI_API_KEY` | Traefik, Paperless, OpenAI API | UI startet, Konfiguration vorhanden; LLM-Provider zeigt `openai` / `gpt-5.4-mini` |
|
| Paperless-GPT | Borg / Share | `/mnt/user/appdata/paperless-gpt` | keine eigene DB | `PAPERLESS_API_TOKEN`, `OPENAI_API_KEY` | Traefik, Paperless, OpenAI API | UI startet, Konfiguration vorhanden; LLM-Provider zeigt `openai` / `gpt-5.4-mini` |
|
||||||
|
| n8n | Borg + Dump | `/mnt/user/appdata/n8n/data` | `n8n.sqlite.dump`; Credentials sind nur mit dem passenden `N8N_ENCRYPTION_KEY` entschluesselbar | `N8N_ENCRYPTION_KEY`, GMX/OpenAI/Gitea-Credentials in n8n | Traefik, GMX IMAP, OpenAI API, Gitea API | UI startet, Owner-Login funktioniert, kritischer Mail->LLM->Gitea-Workflow ist vorhanden und deaktiviert/aktiv wie vor Restore |
|
||||||
|
| Home Assistant | Borg + HA-native Backups + Fachrepo | `/mnt/user/appdata/homeassistant` inkl. `.storage`, `secrets.yaml`, `trusted_proxies.yaml`, `custom_components` (HACS, `solaredge_modbus_multi`); Fach-YAML aus `/mnt/user/services/smart-home-kalli/home-assistant` | HA-native Backup-Artefakte unter `/mnt/user/appdata/homeassistant/backups`; erstes Artefakt 2026-06-13 erzeugt und tar-lesbar (`backup.json`, `homeassistant.tar.gz`); Backup nach SolarEdge-Integration: `Custom_backup_2026.6.1_2026-06-13_14.59_48645373.tar`; Backup nach Energy-Dashboard-Konfiguration: `Custom_backup_2026.6.1_2026-06-13_15.59_25670583.tar`; keine externe DB in Phase 1 | HA-Secrets in `secrets.yaml`, Integrations-Tokens in `.storage`, MQTT-Credentials, Agent-API-Tokens als Host-Secrets `ha_token_codex`/`ha_token_claude` (nur mit erhaltenem `.storage`-Auth-State nutzbar), spaeter Tibber/InfluxDB-Tokens | Traefik, `frontend_net`, `smarthome_net`, Mosquitto, Fachrepo-Clone, SolarEdge-Wechselrichter `192.168.178.111:1502` | Restore-Test am 2026-06-13 erfolgreich: HA-native Backup + Mosquitto-Appdata + Fachrepo-Clone isoliert gestartet, HA HTTP/API/check_config gruen; produktiv danach HA-MQTT-Config-Entry `smarthome-mosquitto` geladen, SolarEdge Local `solaredge_modbus_multi` loaded mit 68 Entitaeten und Energy Dashboard fuer Netz/PV/Speicher per `energy/validate` ohne Issues; Report `/mnt/user/backups/restore-reports/homeassistant-2026-06-13.md` |
|
||||||
|
| Smart-Home MQTT / Mosquitto | Borg / Share | `/mnt/user/appdata/mosquitto/config`, `/mnt/user/appdata/mosquitto/data`, `/mnt/user/appdata/mosquitto/log` | Mosquitto persistiert retained messages/subscriptions dateibasiert | `passwordfile`, `aclfile`, spaeter per-Device-User | `smarthome_net`, Home Assistant, spaeter ESPHome/Zigbee2MQTT | Restore-Test am 2026-06-13 erfolgreich: authentifizierter Publish/Subscribe-Smoke mit `homeassistant`-User und retained Topic nach Broker-Restart gruen; produktiv verbindet sich HA als User `homeassistant` |
|
||||||
|
| Smart-Home Fachrepo | Gitea + Borg-Repo-Clone | `/mnt/user/services/smart-home-kalli` | keine | keine echten Secrets im Repo; `secrets-template/` nur Beispiele | Gitea, Home Assistant Mounts | `git status` sauber, HA liest `configuration.yaml` und `packages/` aus dem Clone |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -77,6 +81,8 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
|
|||||||
| 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 |
|
| 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` |
|
| 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 |
|
| 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 |
|
||||||
|
| Zigbee2MQTT (geplant) | Borg + Fachrepo | `/mnt/user/appdata/zigbee2mqtt` inkl. `configuration.yaml`, `database.db`, `coordinator_backup.json`, `state.json`; Fach-Doku im Repo `smart-home-kalli` | keine externe DB | `network_key`, MQTT-Credentials, LAN-Koordinator-IP/Firmwarestand | Mosquitto, LAN-PoE-Koordinator, `smarthome_net` | Z2M startet, Coordinator verbindet sich, geraete bleiben gepairt, Testgeraet sendet MQTT-State |
|
||||||
|
| ESPHome (geplant) | Fachrepo + Borg fuer Build-/Runtime-State | `/mnt/user/appdata/esphome` falls Dashboard/Build-Cache genutzt wird; YAML unter `/mnt/user/services/smart-home-kalli/esphome` | keine | ESPHome-Secrets ausserhalb Git, API-/OTA-Keys | WLAN/LAN, Mosquitto falls MQTT genutzt wird | Dashboard startet, ein Testgeraet kompiliert/validiert, OTA/API-Verbindung funktioniert |
|
||||||
| 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 |
|
| Hermes Agent | VM-seitig offen | `/mnt/user/appdata/hermes-agent/data`, `/mnt/user/appdata/hermes-agent/ssh` | keine eigene DB | Host-`.env` fuer Provider-/API-/Home-Assistant-Tokens, `hermes_runner_id_ed25519`, `HERMES_DASHBOARD_HOST` | separate Hermes-VM/Runner, Traefik, Authelia, `hermes_net` | NAS-Stack nicht starten, solange Runner-VM und echte `.env` fehlen |
|
||||||
| ddns-updater | Rebuildbar | geringe Persistenzrelevanz | keine | Provider-Zugang ueber Stack ENV | Internetzugang | Update-Job laeuft |
|
| ddns-updater | Rebuildbar | geringe Persistenzrelevanz | keine | Provider-Zugang ueber Stack ENV | Internetzugang | Update-Job laeuft |
|
||||||
|
|
||||||
@@ -99,6 +105,7 @@ Aktuell relevante Dump-Artefakte unter `/mnt/user/backups/borg/dumps/latest`:
|
|||||||
- `filebrowser.bolt.dump`
|
- `filebrowser.bolt.dump`
|
||||||
- `borg-ui.sqlite`
|
- `borg-ui.sqlite`
|
||||||
- `grafana.sqlite`
|
- `grafana.sqlite`
|
||||||
|
- `n8n.sqlite.dump`
|
||||||
- `unraid-flash-config.tar.gz` plus `unraid-flash-config.tar.gz.sha256` und Manifest
|
- `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.
|
- Monitoring-Stack: keine verpflichtenden Dump-Artefakte; Prometheus/Loki/Grafana named volumes sind Diagnose-/Dashboard-Zustand, keine primaere Restore-Quelle.
|
||||||
- `komodo-mongo.archive.gz` (noch gesondert verifizieren)
|
- `komodo-mongo.archive.gz` (noch gesondert verifizieren)
|
||||||
@@ -135,7 +142,7 @@ Die Dump-Erzeugung ist host-seitig ueber `ops/borg-ui/scripts/pre-backup-dumps.s
|
|||||||
|
|
||||||
## Restore-Test-Reifegrad
|
## Restore-Test-Reifegrad
|
||||||
|
|
||||||
Stand 2026-06-05. Pro Dienst auf einen Blick: Wurde der Restore schon einmal real getestet?
|
Stand 2026-06-06. Pro Dienst auf einen Blick: Wurde der Restore schon einmal real getestet?
|
||||||
|
|
||||||
| Dienst | Tier | Letzter Restore-Test | Typ | Naechster Lauf |
|
| Dienst | Tier | Letzter Restore-Test | Typ | Naechster Lauf |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
@@ -147,10 +154,10 @@ Stand 2026-06-05. Pro Dienst auf einen Blick: Wurde der Restore schon einmal rea
|
|||||||
| Immich | 2 | 2026-05-27 | Dump + Container + HTTP + Asset-Count | quartalsweise (2. So Feb/Mai/Aug/Nov) |
|
| Immich | 2 | 2026-05-27 | Dump + Container + HTTP + Asset-Count | quartalsweise (2. So Feb/Mai/Aug/Nov) |
|
||||||
| Unraid OS Flash | 1 | 2026-06-05 (Artefakt-Validierung) | sha256 OK + 390 Eintraege + 8 Kern-Configs vorhanden (`ops/maintenance/check-unraid-flash-backup.sh`); **physischer Ersatzstick-Boot-Test weiter offen** | Stick-Boot-Test nach Bedarf |
|
| Unraid OS Flash | 1 | 2026-06-05 (Artefakt-Validierung) | sha256 OK + 390 Eintraege + 8 Kern-Configs vorhanden (`ops/maintenance/check-unraid-flash-backup.sh`); **physischer Ersatzstick-Boot-Test weiter offen** | Stick-Boot-Test nach Bedarf |
|
||||||
| Traefik | 1 | 2026-06-03 | Config + LE-State + File-Provider + Ping 200 | quartalsweise |
|
| Traefik | 1 | 2026-06-03 | Config + LE-State + File-Provider + Ping 200 | quartalsweise |
|
||||||
| AdGuard Home | 1 | - | noch kein Test | - |
|
| AdGuard Home | 1 | 2026-06-06 | Config + Container + HTTP 401 + DNS + Filter-Count | quartalsweise oder nach DNS-Aenderungen |
|
||||||
| Tailscale | 1 | - | noch kein Test | - |
|
| Tailscale | 1 | - | noch kein Test | - |
|
||||||
| PostgreSQL 18 Cluster | 1 | 2026-06-03 | globals + 5 per-DB dumps, 290 Tabellen gesamt | quartalsweise |
|
| PostgreSQL 18 Cluster | 1 | 2026-06-03 | globals + 5 per-DB dumps, 290 Tabellen gesamt | quartalsweise |
|
||||||
| Redis 8 | 1 | - | noch kein Test | - |
|
| Redis 8 | 1 | 2026-06-06 | Pre-Cutover-Artefakt + Container + PING + INFO + DBSIZE | quartalsweise oder vor/nach Redis-Major-Aenderungen |
|
||||||
| Komodo Mongo Daten | 1 | 2026-06-03 | mongorestore --archive --gzip, 86904 docs | quartalsweise |
|
| Komodo Mongo Daten | 1 | 2026-06-03 | mongorestore --archive --gzip, 86904 docs | quartalsweise |
|
||||||
| Nextcloud | 2 | 2026-06-03 | File + Dump + Container + HTTP 200 + occ status + Table-Count (126) | quartalsweise |
|
| Nextcloud | 2 | 2026-06-03 | File + Dump + Container + HTTP 200 + occ status + Table-Count (126) | quartalsweise |
|
||||||
| Mealie | 2 | 2026-06-03 | File + Dump + Container + HTTP + Recipe-Count (3) | quartalsweise |
|
| Mealie | 2 | 2026-06-03 | File + Dump + Container + HTTP + Recipe-Count (3) | quartalsweise |
|
||||||
@@ -159,157 +166,26 @@ Stand 2026-06-05. Pro Dienst auf einen Blick: Wurde der Restore schon einmal rea
|
|||||||
| ntfy | 2 | - | rebuildbar, kein Test noetig | - |
|
| ntfy | 2 | - | rebuildbar, kein Test noetig | - |
|
||||||
| Borg UI | 3 | - | rebuildbar | - |
|
| Borg UI | 3 | - | rebuildbar | - |
|
||||||
| Filebrowser | 3 | - | rebuildbar | - |
|
| Filebrowser | 3 | - | rebuildbar | - |
|
||||||
| baerchen Windows Image | Workstation | 2026-06-05 | Full-Backup geschrieben; Recovery USB + SMB-Mount noch offen | Recovery-USB-Test |
|
| baerchen Windows Image | Workstation | 2026-06-06 | Full-Backup geschrieben; Recovery-USB-Boot, SMB-Mount und Restore-Point-Sichtpruefung erfolgreich; vor echtem Restore abgebrochen | nach Image-Aenderungen oder quartalsweise |
|
||||||
|
| Home Assistant + Mosquitto | 2 | 2026-06-13 | HA-native Backup + Mosquitto-Appdata + Fachrepo-Clone, isolierte Testcontainer, HA HTTP/API/check_config, MQTT Publish/Subscribe + retained Topic nach Broker-Restart | vor groesseren Smart-Home-Aenderungen oder nach relevanten HA/Mosquitto-Architekturaenderungen |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Naechste Restore-Test-Kandidaten (priorisiert)
|
## Naechste Restore-Test-Kandidaten (priorisiert)
|
||||||
|
|
||||||
Stand 2026-06-05. Die frueheren Kandidaten (Shared PG18, Komodo Mongo, Mailarchiver, Mealie, Traefik)
|
Stand 2026-06-06. Die frueheren Kandidaten (Shared PG18, Komodo Mongo, Mailarchiver, Mealie, Traefik)
|
||||||
wurden alle am 2026-06-03 abgeschlossen und sind in der Reifegrad-Tabelle belegt.
|
wurden alle am 2026-06-03 abgeschlossen und sind in der Reifegrad-Tabelle belegt.
|
||||||
|
|
||||||
Verbleibende offene Restore-Pfade ohne vollstaendigen Test:
|
Verbleibende offene Restore-Pfade ohne vollstaendigen Test:
|
||||||
|
|
||||||
1. **Unraid OS Flash** - Artefakt-Validierung am 2026-06-05 erfolgreich (siehe Reifegrad-Tabelle und Runbook unten); offen bleibt nur der **physische Ersatzstick-Boot-Test**.
|
1. **Unraid OS Flash** - Artefakt-Validierung am 2026-06-05 erfolgreich (siehe Reifegrad-Tabelle und `ops/restore-tests/unraid-flash-runbook.md`); offen bleibt nur der **physische Ersatzstick-Boot-Test**.
|
||||||
2. **AdGuard Home** - Config-Restore in Testpfad oder Wegwerf-Instanz pruefen
|
2. **Tailscale** - State-/Reconnect-Pfad dokumentiert testen (`ops/restore-tests/tailscale-runbook.md`)
|
||||||
3. **Tailscale** - State-/Reconnect-Pfad dokumentiert testen
|
|
||||||
4. **Redis 8 (Shared)** - Restore aus Datenpfad oder Pre-Cutover-Backup in isolierter Testinstanz pruefen
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Restore-Test-Runbooks (Entwurf)
|
## Restore-Test-Runbooks
|
||||||
|
|
||||||
Diese Abschnitte sind vorbereitete Checklisten fuer die noch untesteten Restore-Pfade.
|
Die Ablaeufe je Dienst liegen als Runbooks und automatisierte Skripte unter
|
||||||
Sie sind **nicht** als produktive Anleitungen zu verwenden, bevor ein erster Testlauf
|
`ops/restore-tests/` (Einstieg: `ops/restore-tests/README.md`). Fuer die noch
|
||||||
die konkreten Artefaktnamen und Pfade bestaetigt hat.
|
offenen Pfade: `ops/restore-tests/unraid-flash-runbook.md` und
|
||||||
|
`ops/restore-tests/tailscale-runbook.md`.
|
||||||
### Unraid OS Flash
|
|
||||||
|
|
||||||
**Voraussetzungen:**
|
|
||||||
- Borg-Artefakt `unraid-flash-config.tar.gz` und `unraid-flash-config.tar.gz.sha256` unter `/mnt/user/backups/borg/dumps/latest` oder im Hetzner-Borg-Repo verfuegbar
|
|
||||||
- Neuer leerer USB-Stick (Empfehlung: 16 GB, USB 2.0 kompatibel)
|
|
||||||
- Unraid USB Flash Creator oder manueller Restore-Pfad
|
|
||||||
- Offline-gesicherte Borg-Passphrase verfuegbar
|
|
||||||
|
|
||||||
**Checkliste Artefakt-Validierung (ohne produktiven Stick):**
|
|
||||||
|
|
||||||
Automatisiert via Repo-Skript `ops/maintenance/check-unraid-flash-backup.sh`
|
|
||||||
(read-only, keine Extraktion). Manuelle Einzelschritte:
|
|
||||||
|
|
||||||
1. SHA256-Pruefung: `sha256sum -c unraid-flash-config.tar.gz.sha256`
|
|
||||||
2. Artefakt-Inhalt pruefen: `tar -tzf unraid-flash-config.tar.gz | head -40` — erwartet `config/` als Prefix
|
|
||||||
3. Kern-Configs vorhanden: `super.dat`, `disk.cfg`, `ident.cfg`, `share.cfg`, `network.cfg`, `docker.cfg`, `go`, `domain.cfg`
|
|
||||||
4. Keine produktiven Konfigurationspfade (z. B. `config/ssh/`) ausserhalb des Test-Environments extrahieren
|
|
||||||
5. Manifest-Datei auf Vollstaendigkeit pruefen
|
|
||||||
|
|
||||||
**Validierungsergebnis 2026-06-05 (read-only per SSH):** Artefakt frisch
|
|
||||||
(2026-06-05 04:00, ~16 h alt beim Test), `sha256sum -c` = OK, 390 Eintraege,
|
|
||||||
alle 8 Kern-Configs vorhanden. Das Archiv enthaelt erwartungsgemaess
|
|
||||||
Secret-Material (SSH-Host-Keys, Tailscale-State, `passwd`/`shadow`/`smbpasswd`,
|
|
||||||
`Trial.key`) und ist wie Secret-Backup zu behandeln. Es wurde nichts extrahiert,
|
|
||||||
nur Eintragsnamen gelistet. Offen bleibt der physische Ersatzstick-Boot-Test.
|
|
||||||
|
|
||||||
**Checkliste vollstaendiger Restore-Test (auf Wegwerf-Stick):**
|
|
||||||
|
|
||||||
1. Neuen USB-Stick mit Unraid USB Flash Creator formatieren und Basis-Unraid draufspielen
|
|
||||||
2. `config/`-Verzeichnis aus `unraid-flash-config.tar.gz` in den `/boot/config`-Pfad des neuen Sticks extrahieren
|
|
||||||
3. Im Testrahmen booten (kein Array starten, keine Shares mounten)
|
|
||||||
4. Pruefen: Unraid-Grundkonfiguration (Shares, Hostname, Netzwerk) ist sichtbar
|
|
||||||
5. Array-Zuordnung lesbar, ohne Drive-Assigns zu bestaetigen
|
|
||||||
|
|
||||||
**Smoke-Test-Kriterium:** Unraid bootet, Hostname ist `Kallilabcore`, Share-Konfiguration ist sichtbar, kein Array gestartet.
|
|
||||||
|
|
||||||
**Sonderregel:** Das Artefakt enthaelt Host-Konfiguration und SSH-Keys und ist wie Secret-Material zu behandeln. Nicht auf oeffentlichen oder unverschluesselten Testzielen extrahieren.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### AdGuard Home
|
|
||||||
|
|
||||||
**Voraussetzungen:**
|
|
||||||
- Borg-Archiv mit `/mnt/user/appdata/adguard/conf` zugaenglich (produktives Repo oder Teststand)
|
|
||||||
- Testpfad unter `/mnt/user/backups/restore-lab/adguard` vorbereitet
|
|
||||||
- Docker-Faehigkeit auf dem Testhost oder in der Restore-Lab-Umgebung
|
|
||||||
|
|
||||||
**Checkliste:**
|
|
||||||
|
|
||||||
1. Borg-Extract des letzten Archivs nach `/mnt/user/backups/restore-lab/adguard/conf`:
|
|
||||||
```
|
|
||||||
borg extract ::ARCHIV /mnt/user/appdata/adguard/conf
|
|
||||||
```
|
|
||||||
2. Konfigurationsdatei `AdGuardHome.yaml` auf Vollstaendigkeit pruefen (YAML-Syntax valide)
|
|
||||||
3. Testcontainer starten (kein produktiver DNS-Port 53, stattdessen z. B. `5353`):
|
|
||||||
```yaml
|
|
||||||
ports:
|
|
||||||
- "5353:53/udp"
|
|
||||||
- "3001:3000/tcp"
|
|
||||||
volumes:
|
|
||||||
- /mnt/user/backups/restore-lab/adguard/conf:/opt/adguardhome/conf
|
|
||||||
```
|
|
||||||
4. `http://localhost:3001` erreichbar, Login moeglich
|
|
||||||
5. DNS-Aufloesung: `dig @127.0.0.1 -p 5353 git.kaleschke.info` gibt plausible Antwort
|
|
||||||
6. Testcontainer stoppen und Testpfad aufraeumen
|
|
||||||
|
|
||||||
**Smoke-Test-Kriterium:** AdGuard-Web-UI laeuft, DNS-Aufloesung antwortet, Filterlisten sind geladen.
|
|
||||||
|
|
||||||
**Keine Secrets:** AdGuard Home verwendet keine dokumentierten Repo-Secrets; Login-Credentials liegen in der `AdGuardHome.yaml` im Borg-Archiv.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Tailscale
|
|
||||||
|
|
||||||
**Voraussetzungen:**
|
|
||||||
- Borg-Archiv mit `/mnt/user/appdata/tailscale` zugaenglich
|
|
||||||
- Testpfad unter `/mnt/user/backups/restore-lab/tailscale` vorbereitet
|
|
||||||
- Achtung: Der Tailscale-State ist maschinenspezifisch. Ein Restore auf denselben produktiven Host wuerde die laufende Verbindung verdraengen. Nur auf einem Wegwerf- oder Offline-Host testen.
|
|
||||||
|
|
||||||
**Checkliste Artefakt-Validierung (ohne produktiven Host):**
|
|
||||||
|
|
||||||
1. Borg-Extract nach `/mnt/user/backups/restore-lab/tailscale`
|
|
||||||
2. State-Verzeichnis auf erwartete Dateien pruefen: `tailscaled.state` vorhanden
|
|
||||||
3. Dateisystem-Rechte pruefen: `tailscaled.state` muss fuer `root` zugaenglich sein
|
|
||||||
|
|
||||||
**Checkliste Reconnect-Test (auf Wegwerf-Host oder VM):**
|
|
||||||
|
|
||||||
1. Tailscale-Container mit dem gemounteten State-Pfad starten
|
|
||||||
2. `tailscale status` zeigt `Connected` oder den erwarteten Hostnamen
|
|
||||||
3. Tailscale-Admin-Konsole (`login.tailscale.com`) zeigt Geraet als `Online`
|
|
||||||
4. SSH ueber Tailscale-IP auf den Testhost moeglich
|
|
||||||
5. Testcontainer stoppen; Wegwerf-Geraet in der Tailscale-Admin-Konsole entfernen
|
|
||||||
|
|
||||||
**Smoke-Test-Kriterium:** Container verbindet sich mit bestehendem Tailscale-Account (kein neues Re-Auth noetig), Tailscale-IP ist erreichbar.
|
|
||||||
|
|
||||||
**Hinweis:** Falls der State veraltet ist (Key expired), wird Tailscale einen Re-Auth anfordern. Das ist ein valides Testergebnis und belegt, wie lang der Reconnect-Pfad bei abgelaufenem Key ist.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Redis 8 (Shared)
|
|
||||||
|
|
||||||
**Voraussetzungen:**
|
|
||||||
- Pre-Cutover-Backup unter `/mnt/user/backups/borg/dumps/latest/shared-redis-pre-redis8-<ts>` vorhanden, oder Borg-Archiv mit `/mnt/user/appdata/redis`
|
|
||||||
- Secret-Datei `redis_password.txt` fuer Testinstanz verfuegbar (aus Borg, nicht als Wert dokumentieren)
|
|
||||||
- Testpfad unter `/mnt/user/backups/restore-lab/redis` vorbereitet
|
|
||||||
|
|
||||||
**Checkliste:**
|
|
||||||
|
|
||||||
1. RDB/AOF-Datei aus dem Backup in den Testpfad kopieren:
|
|
||||||
```
|
|
||||||
cp /mnt/user/backups/borg/dumps/latest/shared-redis-pre-redis8-<ts>/dump.rdb \
|
|
||||||
/mnt/user/backups/restore-lab/redis/
|
|
||||||
```
|
|
||||||
(oder Borg-Extract aus dem Appdata-Archiv)
|
|
||||||
2. Testcontainer starten (kein produktiver Port 6379, stattdessen z. B. `16379`):
|
|
||||||
```yaml
|
|
||||||
ports:
|
|
||||||
- "127.0.0.1:16379:6379"
|
|
||||||
volumes:
|
|
||||||
- /mnt/user/backups/restore-lab/redis:/data
|
|
||||||
command: redis-server --requirepass <aus Secret> --appendonly yes
|
|
||||||
```
|
|
||||||
3. Verbindungstest: `redis-cli -p 16379 -a <pass> PING` antwortet `PONG`
|
|
||||||
4. Redis-Version pruefen: `redis-cli -p 16379 -a <pass> INFO server | grep redis_version` zeigt `8.x`
|
|
||||||
5. Stichprobe Key-Bestand: `redis-cli -p 16379 -a <pass> DBSIZE` zeigt plausible Zahl (nicht 0)
|
|
||||||
6. Testcontainer stoppen und Testpfad aufraeumen
|
|
||||||
|
|
||||||
**Smoke-Test-Kriterium:** Redis 8 startet mit dem Restore-Datenpfad, `PING` antwortet, `DBSIZE` ist nicht 0.
|
|
||||||
|
|
||||||
**Shared Redis Besonderheit:** Shared Redis wird produktiv nur von Paperless genutzt (AOF aktiv). Bei einem echten Restore nach App-Absturz: Erst Redis aus Backup hochziehen, dann Paperless. Nextcloud hat eigene Redis-Instanz ohne Passwort.
|
|
||||||
|
|||||||
+14
-63
@@ -1,6 +1,10 @@
|
|||||||
# Rollback Guide - Homelab
|
# Rollback Guide - Homelab
|
||||||
|
|
||||||
|
Typ: Runbook · Stand: 2026-06-11 · Status: aktiv
|
||||||
|
|
||||||
Dieses Dokument beschreibt den sicheren Rueckweg im aktuellen GitOps-Betrieb.
|
Dieses Dokument beschreibt den sicheren Rueckweg im aktuellen GitOps-Betrieb.
|
||||||
|
Rollback-Anleitungen fuer bereits entfernte Dienste (Uptime-Kuma, Grafana-/
|
||||||
|
InfluxDB-Altstack, Stirling-PDF) liegen in der Git-Historie, nicht mehr hier.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -72,59 +76,14 @@ Bei Problemen mit Borg UI oder Dump-Automatisierung:
|
|||||||
3. Persistenz unter `/mnt/user/appdata/borg-ui/` und `/mnt/user/backups/borg/dumps/` nicht blind loeschen
|
3. Persistenz unter `/mnt/user/appdata/borg-ui/` und `/mnt/user/backups/borg/dumps/` nicht blind loeschen
|
||||||
4. Restore zuerst in einen Testpfad schreiben, nicht direkt in Produktivpfade
|
4. Restore zuerst in einen Testpfad schreiben, nicht direkt in Produktivpfade
|
||||||
|
|
||||||
## BentoPDF / Stirling-PDF Rollback
|
## Monitoring-Stack Rollback
|
||||||
|
|
||||||
Bei Problemen mit BentoPDF:
|
`monitoring/` ist der einzige Observability-Stack. Bei Problemen:
|
||||||
|
|
||||||
1. Git-Stand auf die letzte funktionierende Stirling-PDF-Compose zuruecknehmen oder gezielt `apps/bentopdf` wieder durch `apps/stirling-pdf` ersetzen
|
|
||||||
2. Commit + Push nach Gitea
|
|
||||||
3. betroffenen Stack in Komodo redeployen
|
|
||||||
4. `https://pdf.kaleschke.info` pruefen
|
|
||||||
|
|
||||||
Die alte Stirling-PDF-Persistenz unter `/mnt/user/appdata/stirling-pdf` nicht loeschen, solange der BentoPDF-Ersatz nicht fachlich abgenommen ist.
|
|
||||||
|
|
||||||
## Grafana / InfluxDB Rollback
|
|
||||||
|
|
||||||
Vor dem ersten produktiven Einsatz reicht es, den vorbereiteten Stack nicht zu deployen oder per Ruecknahme-Commit aus dem Repo zu entfernen.
|
|
||||||
|
|
||||||
Nach einem Deploy:
|
|
||||||
|
|
||||||
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
|
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
|
2. named volumes `prometheus_data`, `loki_data`, `promtail_positions`, `grafana_data` sowie `/mnt/user/appdata/influxdb3` nicht blind loeschen
|
||||||
3. named volumes `prometheus_data`, `loki_data`, `promtail_positions`, `grafana_data` sowie `/mnt/user/appdata/influxdb3` nicht blind loeschen
|
3. Secrets (`monitoring_grafana_admin_password.txt`, `monitoring_grafana_influxdb_token.txt`, `influxdb3_admin_token.json`) nur nach bewusstem Entscheid entfernen
|
||||||
4. Secrets `monitoring_grafana_admin_password.txt`, `monitoring_grafana_influxdb_token.txt` und `influxdb3_admin_token.json` nur nach bewusstem Entscheid entfernen
|
4. Grafana-Datasources `Prometheus`, `Loki` und `InfluxDB 3 Core` testen
|
||||||
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
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -132,19 +91,11 @@ Nach einem Deploy:
|
|||||||
|
|
||||||
Bevorzugte Quellen:
|
Bevorzugte Quellen:
|
||||||
|
|
||||||
- Borg-Restore
|
- Borg-Restore (zuerst in Testpfade unter `/mnt/user/backups/restore-lab/`)
|
||||||
- erzeugte PostgreSQL-/MariaDB-Dumps
|
- erzeugte Dumps unter `/mnt/user/backups/borg/dumps/latest`
|
||||||
- bekannte Appdata-Snapshots
|
- bekannte Appdata-Archivstaende unter `/mnt/user/appdata/_archive/`
|
||||||
|
|
||||||
Beispiele:
|
Dienst-spezifische Restore-Quellen, Dumps und Smoke-Tests stehen in `docs/RESTORE_MATRIX.md`.
|
||||||
|
|
||||||
```bash
|
|
||||||
cp -r /mnt/user/appdata/<service> /mnt/user/backup/
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pg_dumpall > /mnt/user/backup/pg_dump_$(date +%Y%m%d).sql
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+13
-6
@@ -25,6 +25,7 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
|
|||||||
| mealie-postgres | DB Password | `/mnt/user/appdata/secrets/mealie_postgres_password.txt` -> `POSTGRES_PASSWORD_FILE` | aktiv |
|
| mealie-postgres | DB Password | `/mnt/user/appdata/secrets/mealie_postgres_password.txt` -> `POSTGRES_PASSWORD_FILE` | aktiv |
|
||||||
| Paperless-ngx | DB Password | Stack ENV `${PAPERLESS_DBPASS}` | aktiv |
|
| Paperless-ngx | DB Password | Stack ENV `${PAPERLESS_DBPASS}` | aktiv |
|
||||||
| Paperless-ngx | Redis URL | Stack ENV `${PAPERLESS_REDIS}` | aktiv |
|
| Paperless-ngx | Redis URL | Stack ENV `${PAPERLESS_REDIS}` | aktiv |
|
||||||
|
| Paperless OIDC (Authelia) | Client Secret | Stack ENV `${PAPERLESS_OIDC_SECRET}` in `/mnt/user/services/stacks/paperless/apps/paperless/.env` (Komodo-Stack-ENV); pbkdf2-Hash im Authelia-Host-Config-Client `paperless` (kein Wert im Repo) | aktiv (2026-06-17) |
|
||||||
| Paperless-GPT | OpenAI API Key | Stack ENV `${OPENAI_API_KEY}`; nicht im Repo, nicht in Logs | aktiv |
|
| Paperless-GPT | OpenAI API Key | Stack ENV `${OPENAI_API_KEY}`; nicht im Repo, nicht in Logs | aktiv |
|
||||||
| code-server | Passwort | `/mnt/user/appdata/code-server/secrets/password` -> `FILE__PASSWORD` | 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 |
|
| Filebrowser | Admin Password | `/mnt/user/appdata/secrets/filebrowser_admin_password.txt` -> initialisierte SQLite-DB | aktiv |
|
||||||
@@ -40,7 +41,7 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
|
|||||||
| Komodo Mongo | Root Password | `/mnt/user/appdata/secrets/komodo_mongo_password.txt` -> `MONGO_INITDB_ROOT_PASSWORD_FILE` | aktiv |
|
| Komodo Mongo | Root Password | `/mnt/user/appdata/secrets/komodo_mongo_password.txt` -> `MONGO_INITDB_ROOT_PASSWORD_FILE` | aktiv |
|
||||||
| Komodo Core | App Secrets | Stack ENV `${KOMODO_SECRET_KEY}`, `${KOMODO_WEBHOOK_SECRET}`, `${KOMODO_JWT_SECRET}`, `${KOMODO_MONGO_PASSWORD}`, `${KOMODO_PERIPHERY_PASSKEY}` | aktiv |
|
| Komodo Core | App Secrets | Stack ENV `${KOMODO_SECRET_KEY}`, `${KOMODO_WEBHOOK_SECRET}`, `${KOMODO_JWT_SECRET}`, `${KOMODO_MONGO_PASSWORD}`, `${KOMODO_PERIPHERY_PASSKEY}` | aktiv |
|
||||||
| 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 |
|
| 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 |
|
| Glance | Community Widget API Tokens | Stack ENV `${GLANCE_IMMICH_API_KEY}`, `${GLANCE_ADGUARD_USERNAME}`, `${GLANCE_ADGUARD_PASSWORD}`, `${GLANCE_SPEEDTEST_API_KEY}`, `${GLANCE_KOMODO_API_KEY}`, `${GLANCE_KOMODO_API_SECRET}`, `${GLANCE_GITEA_TOKEN}`, `${GLANCE_PAPERLESS_TOKEN}`, `${GLANCE_MEALIE_TOKEN}` (alle read-only anlegen), `${GLANCE_HA_TOKEN}` (HA Long-Lived Access Token; Glance nutzt nur `GET /api/states`) | aktiv |
|
||||||
| speedtest-tracker | App Key / Admin-Zugang | Stack ENV `${APP_KEY}`, `${ADMIN_PASSWORD}` | aktiv |
|
| speedtest-tracker | App Key / Admin-Zugang | Stack ENV `${APP_KEY}`, `${ADMIN_PASSWORD}` | aktiv |
|
||||||
| Nextcloud | Admin User | `/mnt/user/appdata/secrets/nextcloud_admin_user.txt` -> `NEXTCLOUD_ADMIN_USER_FILE` | neu |
|
| Nextcloud | Admin User | `/mnt/user/appdata/secrets/nextcloud_admin_user.txt` -> `NEXTCLOUD_ADMIN_USER_FILE` | neu |
|
||||||
| Nextcloud | Admin Password | `/mnt/user/appdata/secrets/nextcloud_admin_password.txt` -> `NEXTCLOUD_ADMIN_PASSWORD_FILE` | neu |
|
| Nextcloud | Admin Password | `/mnt/user/appdata/secrets/nextcloud_admin_password.txt` -> `NEXTCLOUD_ADMIN_PASSWORD_FILE` | neu |
|
||||||
@@ -51,9 +52,12 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
|
|||||||
| Hermes Agent | Provider-Keys, Bot-Tokens, API-Server-Key | `/mnt/user/appdata/hermes-agent/data/.env` | VM-seitig offen |
|
| 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 |
|
| 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 |
|
| InfluxDB 3 Core | Admin Token JSON | `/mnt/user/appdata/secrets/influxdb3_admin_token.json` -> Docker Secret `/run/secrets/influxdb3_admin_token` | aktiv |
|
||||||
|
| Home Assistant -> InfluxDB | Write Token (Wetterarchiv) | `/mnt/user/appdata/secrets/ha_influxdb_token` + HA `/config/secrets.yaml` Key `influxdb_ha_token`; InfluxDB-3-Core Named-Admin-Token (voller Zugriff, da Core keine Scopes kennt) | aktiv |
|
||||||
|
| Home Assistant | Agent API Tokens | `/mnt/user/appdata/secrets/ha_token_claude`, `ha_token_codex` (Long-Lived Access Tokens fuer read-only API-Zugriff durch KI-Agenten) | 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 | 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 |
|
| 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 |
|
| Grafana OIDC (Authelia) | Client Secret | `/mnt/user/appdata/secrets/grafana_oidc_client_secret` (Klartext, chmod 600) -> Docker Secret `/run/secrets/grafana_oidc_client_secret` -> `GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET__FILE`. Zugehoeriger pbkdf2-Hash liegt im Authelia-Host-Config-Client `grafana` (kein Wert im Repo) | aktiv (2026-06-06) |
|
||||||
|
| Mealie OIDC (Authelia) | Client Secret | Stack-ENV `${MEALIE_OIDC_CLIENT_SECRET}` in `/mnt/user/services/stacks/mealie/apps/mealie/.env` (Komodo-Stack-ENV); pbkdf2-Hash im Authelia-Host-Config-Client `mealie` (kein Wert im Repo) | aktiv (2026-06-06) |
|
||||||
| Renovate Bot | Gitea Service-Account PAT | `/mnt/user/appdata/secrets/renovate_token.txt` -> Host-Datei (chmod 600), gelesen von `ops/renovate/run-renovate.sh` und an Renovate-Container als `RENOVATE_TOKEN` weitergegeben | aktiv nach Operator-Setup (siehe `docs/RENOVATE.md`) |
|
| Renovate Bot | Gitea Service-Account PAT | `/mnt/user/appdata/secrets/renovate_token.txt` -> Host-Datei (chmod 600), gelesen von `ops/renovate/run-renovate.sh` und an Renovate-Container als `RENOVATE_TOKEN` weitergegeben | aktiv nach Operator-Setup (siehe `docs/RENOVATE.md`) |
|
||||||
| n8n | Encryption Key fuer interne Credential-Verschluesselung | `/mnt/user/appdata/secrets/n8n_encryption_key.txt` (chmod 600) -> Komodo Stack ENV `${N8N_ENCRYPTION_KEY}`; kein `_FILE`-Support im Upstream-Image | aktiv |
|
| n8n | Encryption Key fuer interne Credential-Verschluesselung | `/mnt/user/appdata/secrets/n8n_encryption_key.txt` (chmod 600) -> Komodo Stack ENV `${N8N_ENCRYPTION_KEY}`; kein `_FILE`-Support im Upstream-Image | aktiv |
|
||||||
| n8n | GMX IMAP Login (Mail-Trigger Workflow) | n8n Credentials Store (Typ `imap`), nur in `/mnt/user/appdata/n8n/data` mit `N8N_ENCRYPTION_KEY` verschluesselt | aktiv |
|
| n8n | GMX IMAP Login (Mail-Trigger Workflow) | n8n Credentials Store (Typ `imap`), nur in `/mnt/user/appdata/n8n/data` mit `N8N_ENCRYPTION_KEY` verschluesselt | aktiv |
|
||||||
@@ -61,7 +65,7 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
|
|||||||
| n8n | Gitea PAT fuer `n8n-bot` (Issue-Erstellung Workflow) | n8n Credentials Store (Typ `httpHeaderAuth`, Header `Authorization: token ...`); separater Bot-User mit Scope `write:issue` auf `Micha/mails` | aktiv |
|
| n8n | Gitea PAT fuer `n8n-bot` (Issue-Erstellung Workflow) | n8n Credentials Store (Typ `httpHeaderAuth`, Header `Authorization: token ...`); separater Bot-User mit Scope `write:issue` auf `Micha/mails` | aktiv |
|
||||||
| baerchen Veeam | Veeam Job Encryption Password | Vaultwarden Secure Note `Veeam baerchen backup encryption password`; kein Datei-Secret im Repo | geplant, nur noetig falls Veeam Storage Encryption aktiviert wird |
|
| baerchen Veeam | Veeam Job Encryption Password | Vaultwarden Secure Note `Veeam baerchen backup encryption password`; kein Datei-Secret im Repo | geplant, nur noetig falls Veeam Storage Encryption aktiviert wird |
|
||||||
| baerchen SMB Backup Target | SMB Credential fuer User `micha` | bestehender Unraid-/Vaultwarden-Zugang fuer Share `backups`; wird im Veeam-Job gespeichert, Wert nie dokumentieren | aktiv |
|
| baerchen SMB Backup Target | SMB Credential fuer User `micha` | bestehender Unraid-/Vaultwarden-Zugang fuer Share `backups`; wird im Veeam-Job gespeichert, Wert nie dokumentieren | aktiv |
|
||||||
| baerchen BitLocker | BitLocker Recovery Key C: | geplant: `D:\30_Finanzen\BitLocker-RecoveryKey-baerchen-<DATUM>.txt` + Vaultwarden Secure Note + physischer Ausdruck; aktuell BitLocker noch nicht aktiv | geplant |
|
| baerchen BitLocker | BitLocker Recovery Key C: | **bewusst deaktiviert (Entscheidung 2026-06-06):** kein BitLocker, kein Recovery-Key noetig. Falls spaeter aktiviert: Key nach `D:\30_Finanzen\BitLocker-RecoveryKey-baerchen-<DATUM>.txt` + Vaultwarden Secure Note + physischer Ausdruck | nicht aktiv (bewusst) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -97,6 +101,9 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
|
|||||||
|-- redis_password.txt
|
|-- redis_password.txt
|
||||||
|-- borg_repo_passphrase.txt
|
|-- borg_repo_passphrase.txt
|
||||||
|-- influxdb3_admin_token.json
|
|-- influxdb3_admin_token.json
|
||||||
|
|-- ha_influxdb_token
|
||||||
|
|-- ha_token_claude
|
||||||
|
|-- ha_token_codex
|
||||||
|-- filebrowser_admin_password.txt
|
|-- filebrowser_admin_password.txt
|
||||||
|-- homelab_smtp_password.txt
|
|-- homelab_smtp_password.txt
|
||||||
`-- vaultwarden_admin_token.txt
|
`-- vaultwarden_admin_token.txt
|
||||||
@@ -110,7 +117,7 @@ Weitere dokumentierte Secret-Pfade:
|
|||||||
- Borg UI verwaltet Session-Secret, Admin-Login, SSH-Keys und Repo-Credentials in seiner persistenten `/data`-Struktur. Diese Daten liegen nicht im Git, muessen aber gesichert werden.
|
- Borg UI verwaltet Session-Secret, Admin-Login, SSH-Keys und Repo-Credentials in seiner persistenten `/data`-Struktur. Diese Daten liegen nicht im Git, muessen aber gesichert werden.
|
||||||
- Die Borg-Repo-Passphrase liegt zusaetzlich als Host-Secret-Datei fuer Restore-Tests und Notfallzugriff vor. Der Wert ist laut Operator-Bestaetigung vom 2026-05-26 offline gesichert; Ablageort und Wert werden nicht im Repo dokumentiert.
|
- Die Borg-Repo-Passphrase liegt zusaetzlich als Host-Secret-Datei fuer Restore-Tests und Notfallzugriff vor. Der Wert ist laut Operator-Bestaetigung vom 2026-05-26 offline gesichert; Ablageort und Wert werden nicht im Repo dokumentiert.
|
||||||
- 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.
|
- Gitea verwaltet den GitHub-Push-Mirror-PAT in den Repository-Mirror-Settings. Der Wert wird nicht dokumentiert und nicht in Dateien unter `docs/` oder `core/gitea/` geschrieben.
|
||||||
- `paperless-ngx` ist eine bewusste Ausnahme: DB-Passwort und Redis-URL bleiben aktuell als Komodo Stack Environment Variables hinterlegt, um den stabil laufenden Produktionsstand nicht fuer eine reine Secret-Mechanik-Migration zu riskieren.
|
- `paperless-ngx` ist eine bewusste Ausnahme: DB-Passwort, Redis-URL und OIDC-Client-Secret bleiben aktuell als Komodo Stack Environment Variables hinterlegt, um den stabil laufenden Produktionsstand nicht fuer eine reine Secret-Mechanik-Migration zu riskieren.
|
||||||
- `baerchen` nutzt fuer das Veeam-Backup aktuell den bestehenden SMB-User
|
- `baerchen` nutzt fuer das Veeam-Backup aktuell den bestehenden SMB-User
|
||||||
`micha`. Ein dedizierter SMB-User `veeam-baerchen` ist nur ein spaeteres
|
`micha`. Ein dedizierter SMB-User `veeam-baerchen` ist nur ein spaeteres
|
||||||
Hardening-Ziel, solange keine Unraid-User-/Share-Aenderungen gewuenscht sind.
|
Hardening-Ziel, solange keine Unraid-User-/Share-Aenderungen gewuenscht sind.
|
||||||
@@ -133,14 +140,14 @@ Einige Secrets liegen bewusst nur als Komodo Stack Environment Variables vor, we
|
|||||||
|
|
||||||
| Stack | Stack-ENV-Variablen | Restore-Quelle (Reihenfolge) | Folgen bei Verlust aller Quellen |
|
| Stack | Stack-ENV-Variablen | Restore-Quelle (Reihenfolge) | Folgen bei Verlust aller Quellen |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `paperless-ngx` | `PAPERLESS_DBPASS`, `PAPERLESS_REDIS` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | App-DB ist im Postgres-Cluster, Passwort muss in Postgres und Stack-ENV synchron neu gesetzt werden; Redis-URL ist deterministisch rekonstruierbar (Host, Port, Passwort), wenn Redis-Passwort-Datei vorliegt |
|
| `paperless-ngx` | `PAPERLESS_DBPASS`, `PAPERLESS_REDIS`, `PAPERLESS_OIDC_SECRET` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | App-DB ist im Postgres-Cluster, Passwort muss in Postgres und Stack-ENV synchron neu gesetzt werden; Redis-URL ist deterministisch rekonstruierbar (Host, Port, Passwort), wenn Redis-Passwort-Datei vorliegt; OIDC-Client-Secret kann mit passendem Authelia-Client neu rotiert werden |
|
||||||
| `paperless-gpt` | `PAPERLESS_API_TOKEN`, `OPENAI_API_KEY` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | Paperless-Token kann in Paperless neu erzeugt werden; OpenAI-Key muss im OpenAI-Projekt rotiert/neu erstellt werden |
|
| `paperless-gpt` | `PAPERLESS_API_TOKEN`, `OPENAI_API_KEY` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | Paperless-Token kann in Paperless neu erzeugt werden; OpenAI-Key muss im OpenAI-Projekt rotiert/neu erstellt werden |
|
||||||
| `immich-server` | `IMMICH_DB_PASSWORD` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | analog Paperless: Postgres-User-Passwort in `immich_postgres` und Stack-ENV gemeinsam zuruecksetzen |
|
| `immich-server` | `IMMICH_DB_PASSWORD` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | analog Paperless: Postgres-User-Passwort in `immich_postgres` und Stack-ENV gemeinsam zuruecksetzen |
|
||||||
| `mail-archiver` | `MAILARCHIVER_DB_CONNECTION`, `MAILARCHIVER_AUTH_PASSWORD` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | DB-Connection-String enthaelt Postgres-Pass; App-Auth-Password fuer Web-UI |
|
| `mail-archiver` | `MAILARCHIVER_DB_CONNECTION`, `MAILARCHIVER_AUTH_PASSWORD` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | DB-Connection-String enthaelt Postgres-Pass; App-Auth-Password fuer Web-UI |
|
||||||
| `speedtest-tracker` | `APP_KEY`, `ADMIN_PASSWORD` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | `APP_KEY` ist verschluesselungsrelevant; bei echtem Verlust App-State frisch initialisieren |
|
| `speedtest-tracker` | `APP_KEY`, `ADMIN_PASSWORD` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | `APP_KEY` ist verschluesselungsrelevant; bei echtem Verlust App-State frisch initialisieren |
|
||||||
| `komodo-core` | `KOMODO_SECRET_KEY`, `KOMODO_WEBHOOK_SECRET`, `KOMODO_JWT_SECRET`, `KOMODO_MONGO_PASSWORD`, `KOMODO_PERIPHERY_PASSKEY` | Vaultwarden -> externe Notiz (Henne-Ei: Komodo-Mongo-Dump ist hier **nicht** Restore-Quelle, weil Komodo dafuer schon laufen muesste) | siehe `docs/SERVICES_RECOVERY.md` Komodo-Bootstrap; ohne diese Werte ist der Self-Stack nicht reproduzierbar |
|
| `komodo-core` | `KOMODO_SECRET_KEY`, `KOMODO_WEBHOOK_SECRET`, `KOMODO_JWT_SECRET`, `KOMODO_MONGO_PASSWORD`, `KOMODO_PERIPHERY_PASSKEY` | Vaultwarden -> externe Notiz (Henne-Ei: Komodo-Mongo-Dump ist hier **nicht** Restore-Quelle, weil Komodo dafuer schon laufen muesste) | siehe `docs/SERVICES_RECOVERY.md` Komodo-Bootstrap; ohne diese Werte ist der Self-Stack nicht reproduzierbar |
|
||||||
| `hermes-agent` | `HERMES_DASHBOARD_HOST` plus Provider-/API-/Home-Assistant-Tokens in Host-`.env` | Vaultwarden -> externe Notiz | Stack ist aktuell geparkt (Review 2026-07-25); ohne Werte bleibt der Stack deaktiviert, kein Schaden am Rest |
|
| `hermes-agent` | `HERMES_DASHBOARD_HOST` plus Provider-/API-/Home-Assistant-Tokens in Host-`.env` | Vaultwarden -> externe Notiz | Stack ist aktuell geparkt (Review 2026-07-25); ohne Werte bleibt der Stack deaktiviert, kein Schaden am Rest |
|
||||||
| `glance` | `GLANCE_IMMICH_API_KEY`, `GLANCE_ADGUARD_USERNAME`, `GLANCE_ADGUARD_PASSWORD`, `GLANCE_SPEEDTEST_API_KEY` | Provider-UIs (Immich, AdGuard, Speedtest-Tracker) neu erzeugen | rebuildbar; Widgets bleiben leer bis Tokens neu erzeugt sind, kein kritischer Datentopf |
|
| `glance` | `GLANCE_IMMICH_API_KEY`, `GLANCE_ADGUARD_USERNAME`, `GLANCE_ADGUARD_PASSWORD`, `GLANCE_SPEEDTEST_API_KEY`, `GLANCE_KOMODO_API_KEY`, `GLANCE_KOMODO_API_SECRET`, `GLANCE_GITEA_TOKEN`, `GLANCE_PAPERLESS_TOKEN`, `GLANCE_MEALIE_TOKEN`, `GLANCE_HA_TOKEN` | Provider-UIs (Immich, AdGuard, Speedtest-Tracker, Komodo, Gitea, Paperless, Mealie, Home Assistant) neu erzeugen | rebuildbar; Widgets bleiben leer bis Tokens neu erzeugt sind, kein kritischer Datentopf; `GLANCE_HA_TOKEN` muss zusaetzlich in `ops/glance/docker-compose.yml` durchgereicht werden |
|
||||||
| `n8n` | `N8N_ENCRYPTION_KEY` | Host-Secret-Datei `/mnt/user/appdata/secrets/n8n_encryption_key.txt` -> Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | Bei Verlust aller Quellen: n8n startet, aber **alle gespeicherten Credentials sind unbrauchbar** (Re-Eingabe noetig: GMX IMAP, OpenAI, Gitea PAT). Workflows bleiben strukturell erhalten. |
|
| `n8n` | `N8N_ENCRYPTION_KEY` | Host-Secret-Datei `/mnt/user/appdata/secrets/n8n_encryption_key.txt` -> Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | Bei Verlust aller Quellen: n8n startet, aber **alle gespeicherten Credentials sind unbrauchbar** (Re-Eingabe noetig: GMX IMAP, OpenAI, Gitea PAT). Workflows bleiben strukturell erhalten. |
|
||||||
|
|
||||||
### Komodo-Sonderfall
|
### Komodo-Sonderfall
|
||||||
|
|||||||
@@ -142,8 +142,7 @@ Erst nach erfolgreichem Komodo-Bootstrap werden produktive Stacks ueber den doku
|
|||||||
|
|
||||||
Trockenlauf gegen Wegwerf-Pfade ist seit 2026-05-29 als Repo-Skript abgelegt:
|
Trockenlauf gegen Wegwerf-Pfade ist seit 2026-05-29 als Repo-Skript abgelegt:
|
||||||
`ops/restore-tests/komodo-bootstrap-compose.test.yml`,
|
`ops/restore-tests/komodo-bootstrap-compose.test.yml`,
|
||||||
`ops/restore-tests/komodo-bootstrap-test.sh`,
|
`ops/restore-tests/komodo-bootstrap-test.sh` und
|
||||||
`ops/restore-tests/komodo-bootstrap-plan.md` und
|
|
||||||
`ops/restore-tests/komodo-bootstrap-runbook.md`. Aufruf:
|
`ops/restore-tests/komodo-bootstrap-runbook.md`. Aufruf:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -203,13 +202,4 @@ Authoritativ ist `docs/SECRETS_MAP.md`. Fuer den Kaltstart ist diese Reihenfolge
|
|||||||
- Wenn Gitea und Komodo beide down sind, gewinnt der externe GitHub-Mirror als Repo-Quelle.
|
- 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. Die Offline-Sicherung wurde am 2026-05-26 vom Operator bestaetigt; bei Reviews nur pruefen, dass sie weiterhin auffindbar und lesbar ist.
|
- Wenn Borg ohne Passphrase nicht entschluesselbar ist, ist Recovery blockiert. Die Offline-Sicherung wurde am 2026-05-26 vom Operator bestaetigt; bei Reviews nur pruefen, dass sie weiterhin auffindbar und lesbar ist.
|
||||||
|
|
||||||
## Naechste Aufgaben
|
Offene Folgepunkte werden in `docs/MASTER_TODO.md` gefuehrt.
|
||||||
|
|
||||||
| Status | Aufgabe |
|
|
||||||
|---|---|
|
|
||||||
| erledigt (Skript + Host-Test) | Gitea-Bundle- oder Mirror-Mechanik final entscheiden |
|
|
||||||
| erledigt | Komodo-Bootstrap-Quelle finalisieren |
|
|
||||||
| erledigt (Doku) | Komodo-Kaltstart in linearen Stufen A-F dokumentieren |
|
|
||||||
| erledigt 2026-05-29 | Komodo-Trockenlauf-Skript in `ops/restore-tests/` analog zu Immich vorbereiten |
|
|
||||||
| erledigt 2026-05-30 | Restore-Kommandos nach erstem Trockenlauf mit echten Pfaden ergaenzen |
|
|
||||||
| erledigt | Services-Recovery in `docs/DISASTER_RECOVERY.md` verlinken |
|
|
||||||
|
|||||||
+14
-7
@@ -1,6 +1,6 @@
|
|||||||
# Service Catalog
|
# Service Catalog
|
||||||
|
|
||||||
Stand: 2026-06-02
|
Stand: 2026-06-13
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
@@ -12,8 +12,8 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und
|
|||||||
|---|---|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|---|---|
|
||||||
| `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 |
|
| `traefik` | zentraler Reverse Proxy, TLS, Docker-Label-Routing | `traefik/docker-compose.yml`, `traefik/dynamic/*` | `https://traefik.kaleschke.info` | Docker socket, Cloudflare DNS API, `frontend_net`, `backend_net` | `/mnt/user/appdata/traefik/dynamic`, `/mnt/user/appdata/traefik/letsencrypt` | Tier 1, Share/Borg | ja, eigene Dashboard-Route mit Authelia | Host-Ports 80/443 sind zentrale Ausnahme; dynamic configs werden nicht automatisch von Komodo deployed |
|
||||||
| `adguard` | DNS-Server / LAN DNS | `host-services/Adguard/docker-compose.yml` | LAN-Port `53`, Admin `100.80.98.33:8082` | `dns_net`, `frontend_net`, Unbound | `/mnt/user/appdata/adguard/conf`, `/mnt/user/appdata/adguard/work` | Tier 1, config relevant | nein | Direkter DNS-Port 53 bleibt; Admin-Port ist bewusst ohne Traefik/2FA, aber auf Tailscale-IP begrenzt (Operator-Entscheidung 2026-05-26) |
|
| `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 |
|
| `unbound` | DNSSEC-validierender Forwarding-Resolver fuer AdGuard | `apps/unbound/docker-compose.yml` | intern | `dns_net` | `/mnt/user/appdata/unbound/config` | rebuildbar / config relevant | nein | intern isoliert; forwardet per DoT zu Cloudflare, keine Root-Rekursion |
|
||||||
| `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 |
|
| `tailscale` | VPN/Remote-Zugang, Subnet-Router | **Natives Unraid-Plugin** `tailscale.plg` (nicht repo-/Komodo-verwaltet) | Tailscale | Host-Netz (`tailscale1`) | `/boot/config/plugins/tailscale/state` (im Flash-Backup) | Tier 1, State relevant | nein | Subnet-Router `192.168.178.0/24`; redundanter Docker-Stack `host-services/tailscale/` am 2026-06-06 entfernt |
|
||||||
| `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 |
|
| `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
|
## Security / Identity
|
||||||
@@ -35,19 +35,19 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und
|
|||||||
|
|
||||||
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|
||||||
|---|---|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|---|---|
|
||||||
| `paperless-ngx` | Dokumentenmanagement | `apps/paperless/docker-compose.yml` | `https://paperless.kaleschke.info` | PostgreSQL 18, Redis 8, Traefik | `/mnt/user/appdata/paperless-ngx/data`, `/mnt/user/documents/paperless`, `/mnt/user/documents/scans_inbox` | Tier 2, Borg + `postgresql17-paperless.dump` | ja | DB/Redis Secrets bleiben bewusst Stack ENV; Dump-Dateiname behaelt den historischen Cluster-Namen |
|
| `paperless-ngx` | Dokumentenmanagement | `apps/paperless/docker-compose.yml` | `https://paperless.kaleschke.info` | PostgreSQL 18, Redis 8, Traefik, Authelia OIDC | `/mnt/user/appdata/paperless-ngx/data`, `/mnt/user/documents/paperless`, `/mnt/user/documents/scans_inbox` | Tier 2, Borg + `postgresql17-paperless.dump` | ja + Authelia | DB/Redis/OIDC Secrets bleiben bewusst Stack ENV; OIDC ist additiv via Authelia konfiguriert, lokaler Login bleibt Fallback; Dump-Dateiname behaelt den historischen Cluster-Namen |
|
||||||
| `paperless-gpt` | KI-Ergaenzung fuer Paperless | `apps/paperless-gpt/docker-compose.yml` | `https://paperless-gpt.kaleschke.info` | Paperless API, OpenAI API, Traefik | `/mnt/user/appdata/paperless-gpt/data`, `/mnt/user/appdata/paperless-gpt/prompts` | Tier 2 | ja + Authelia | `PAPERLESS_API_TOKEN` und `OPENAI_API_KEY` als Stack ENV; LLM und Vision-OCR laufen ueber `gpt-5.4-mini`, kein Zugriff mehr auf lokale Ollama-VM. **Behalten-Entscheidung 2026-05-28:** Container bleibt aktiv, auch wenn aktuell keine Traefik-Zugriffe in der Woche; Ablouseplanung erst mit Paperless-NGX 3.0 (eigene KI-Features erwartet) - dann neu bewerten. |
|
| `paperless-gpt` | KI-Ergaenzung fuer Paperless | `apps/paperless-gpt/docker-compose.yml` | `https://paperless-gpt.kaleschke.info` | Paperless API, OpenAI API, Traefik | `/mnt/user/appdata/paperless-gpt/data`, `/mnt/user/appdata/paperless-gpt/prompts` | Tier 2 | ja + Authelia | `PAPERLESS_API_TOKEN` und `OPENAI_API_KEY` als Stack ENV; LLM und Vision-OCR laufen ueber `gpt-5.4-mini`, kein Zugriff mehr auf lokale Ollama-VM. **Behalten-Entscheidung 2026-05-28:** Container bleibt aktiv, auch wenn aktuell keine Traefik-Zugriffe in der Woche; Ablouseplanung erst mit Paperless-NGX 3.0 (eigene KI-Features erwartet) - dann neu bewerten. |
|
||||||
| `immich_server` | Foto-/Video-App | `apps/immich/docker-compose.yml` | `https://immich.kaleschke.info` | Immich Postgres, Immich Redis, ML, Traefik | `/mnt/user/photos/immich`, `/mnt/user/photos/family_archive` | Tier 2, Borg + `immich.dump` | ja | native App-Auth; externes Fotoarchiv gemountet |
|
| `immich_server` | Foto-/Video-App | `apps/immich/docker-compose.yml` | `https://immich.kaleschke.info` | Immich Postgres, Immich Redis, ML, Traefik | `/mnt/user/photos/immich`, `/mnt/user/photos/family_archive` | Tier 2, Borg + `immich.dump` | ja | native App-Auth; externes Fotoarchiv gemountet |
|
||||||
| `immich_postgres` | Immich-Datenbank | `apps/immich/docker-compose.yml` | intern | `immich_default` | `/mnt/user/appdata/immich_postgres_vectorchord`, archivierter Rollback-Altstand `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/immich-postgres-pgvecto-rs`, `immich_postgres_password.txt` | Dump `immich.dump`; Restore braucht ein Image mit VectorChord/pgvector | nein | PG14 bleibt bewusst; Immich-DB-Image `ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0`; nie ins `frontend_net` |
|
| `immich_postgres` | Immich-Datenbank | `apps/immich/docker-compose.yml` | intern | `immich_default` | `/mnt/user/appdata/immich_postgres_vectorchord`, archivierter Rollback-Altstand `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/immich-postgres-pgvecto-rs`, `immich_postgres_password.txt` | Dump `immich.dump`; Restore braucht ein Image mit VectorChord/pgvector | nein | PG14 bleibt bewusst; Immich-DB-Image `ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0`; nie ins `frontend_net` |
|
||||||
| `immich_redis` | Immich Cache | `apps/immich/docker-compose.yml` | intern | `immich_default` | kein kritischer Pfad dokumentiert | rebuildbar | nein | Redis 8.8; Architektur nennt anonymes Volume -> named volume als offenes Thema |
|
| `immich_redis` | Immich Cache | `apps/immich/docker-compose.yml` | intern | `immich_default` | kein kritischer Pfad dokumentiert | rebuildbar | nein | Redis 8.8; Architektur nennt anonymes Volume -> named volume als offenes Thema |
|
||||||
| `immich_machine_learning` | Immich ML | `apps/immich/docker-compose.yml` | intern | `immich_default` | `model-cache` | rebuildbar | nein | intern-only |
|
| `immich_machine_learning` | Immich ML | `apps/immich/docker-compose.yml` | intern | `immich_default`, `immich_egress` | `model-cache` | rebuildbar | nein | keine Traefik-Route; `immich_egress` (nicht-internal) nur fuer Modell-Download zu huggingface, sonst scheitert Smart Search/Gesichtserkennung an DNS |
|
||||||
| `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` | 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/postgres18`, archivierter Rollback-Altstand `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/mealie-postgres17`, `mealie_postgres_password.txt` | Dump `mealie.dump` | nein | interne DB; PostgreSQL 18 |
|
| `mealie-postgres` | Mealie-Datenbank | `apps/mealie/docker-compose.yml` | intern | `mealie_internal` | `/mnt/user/appdata/mealie/postgres18`, archivierter Rollback-Altstand `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/mealie-postgres17`, `mealie_postgres_password.txt` | Dump `mealie.dump` | nein | interne DB; PostgreSQL 18 |
|
||||||
| `mail-archiver` | Mail-Archivierung | `apps/mail-archiver/docker-compose.yml` | `https://mail.kaleschke.info` | PostgreSQL 18, 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; Dump-Dateiname behaelt den historischen Cluster-Namen |
|
| `mail-archiver` | Mail-Archivierung | `apps/mail-archiver/docker-compose.yml` | `https://mail.kaleschke.info` | PostgreSQL 18, 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; Dump-Dateiname behaelt den historischen Cluster-Namen |
|
||||||
| `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` | 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/postgres18`, archivierter Rollback-Altstand `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/nextcloud-postgres17`, `nextcloud_postgres_password.txt` | `nextcloud.dump`, raw DB nicht primaerer Restore-Weg | nein | interne DB; PostgreSQL 18 |
|
| `nextcloud-postgres` | Nextcloud-Datenbank | `apps/nextcloud/docker-compose.yml` | intern | `nextcloud_internal` | `/mnt/user/appdata/nextcloud/postgres18`, archivierter Rollback-Altstand `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/nextcloud-postgres17`, `nextcloud_postgres_password.txt` | `nextcloud.dump`, raw DB nicht primaerer Restore-Weg | nein | interne DB; PostgreSQL 18 |
|
||||||
| `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 8.8 |
|
| `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 8.8 |
|
||||||
| `plex` | Medienserver mit LAN-/Client-Discovery | `host-services/plex/docker-compose.yml` | Plex native, **LAN/Tailscale-only**, Remote Access deaktiviert | 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. Server geclaimt von `Xeridos` (Reclaim 2026-05-28 nach Preferences-Reset vom 18.05.). Smart-TVs greifen ueber WLAN-LAN per mDNS/Plex-GDM direkt zu. `PublishServerOnPlexOnlineKey=0` (Remote Access aus), `RelayEnabled` ebenfalls aus. |
|
| `plex` | Medienserver mit LAN-/Client-Discovery | `host-services/plex/docker-compose.yml`, `traefik/dynamic/plex.yml` | `https://plex.kaleschke.info`, LAN `http://192.168.178.58:32400/web`, Remote Access deaktiviert | Host-Netz, Traefik File provider | `/mnt/user/appdata/plex/config`, `/mnt/user/appdata/plex/transcode`, `/mnt/user/media`, `/mnt/user/photos` | Tier 2, Appdata + Medienpfade im Borg-/Share-Scope | ja, native Plex-Auth | Repo-Compose-Stack; `network_mode: host` bleibt dokumentierte Discovery-Ausnahme. Traefik routet via File-Provider-Ausnahme auf `http://192.168.178.58:32400`, weil Docker-Labels Host-Netz-Container aus Traefik heraus auf `127.0.0.1` routen wuerden. Keine FRITZ!Box-Freigabe fuer `32400`. Keine Authelia-ForwardAuth, weil Plex Web/App-Clients native Plex-Auth und eigene Flows nutzen. Server geclaimt von `Xeridos`; Smart-TVs greifen weiter ueber WLAN-LAN per mDNS/Plex-GDM direkt zu. `PublishServerOnPlexOnlineKey=0` (Plex Remote Access aus), `RelayEnabled` ebenfalls aus. |
|
||||||
| `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` |
|
| `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. **Behalten-Entscheidung 2026-05-28:** Container bleibt aktiv als situatives Tool, auch wenn aktuell keine Traefik-Zugriffe in der Woche. Resource-Footprint vernachlaessigbar (~4 MB RAM). |
|
| `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. **Behalten-Entscheidung 2026-05-28:** Container bleibt aktiv als situatives Tool, auch wenn aktuell keine Traefik-Zugriffe in der Woche. Resource-Footprint vernachlaessigbar (~4 MB RAM). |
|
||||||
| `super-productivity` | Persoenliche Produktivitaets-/Task-PWA (Operator), konsumiert Gitea-Issues aus `Micha/mails` | `apps/super-productivity/docker-compose.yml` | `https://sp.kaleschke.info` | Traefik + Authelia, Gitea `Micha/mails` (Polling vom Client) | statisches Frontend, kein Server-State; Browser-IndexedDB plus optionaler WebDAV-Sync gegen Nextcloud | Tier 3, rebuildbar | ja + Authelia | Reine Static-PWA; SP synchronisiert client-seitig ueber Gitea-API (Scope `assigned`, Repo `Micha/mails`, User `Micha`). |
|
| `super-productivity` | Persoenliche Produktivitaets-/Task-PWA (Operator), konsumiert Gitea-Issues aus `Micha/mails` | `apps/super-productivity/docker-compose.yml` | `https://sp.kaleschke.info` | Traefik + Authelia, Gitea `Micha/mails` (Polling vom Client) | statisches Frontend, kein Server-State; Browser-IndexedDB plus optionaler WebDAV-Sync gegen Nextcloud | Tier 3, rebuildbar | ja + Authelia | Reine Static-PWA; SP synchronisiert client-seitig ueber Gitea-API (Scope `assigned`, Repo `Micha/mails`, User `Micha`). |
|
||||||
@@ -75,11 +75,18 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und
|
|||||||
| `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-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-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-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` | Host-Port `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 | 2026-05-31 effektiv auf `127.0.0.1:8181` gebunden, also nicht LAN-exponiert; `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 |
|
| `monitoring-influxdb3-core` | InfluxDB 3 Core fuer Home-Assistant-/Ecowitt-Langzeitdaten | `monitoring/docker-compose.yml` | Host-Port `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 | 2026-05-31 effektiv auf `127.0.0.1:8181` gebunden, also nicht LAN-exponiert; vor dem HA-Writer muss entschieden werden, ob `INFLUXDB_BIND_IP` auf eine LAN-IP geht oder HA gezielt ein gemeinsames internes Netz mit InfluxDB bekommt. `user: "0"` ist fuer den lokalen Object-Store-Pfad dokumentiert; `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-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 |
|
| `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 |
|
||||||
| `n8n` | Workflow-Automation; aktuell genutzt fuer Mail->LLM->Gitea-Issue (Inbox `Micha/mails`) | `apps/n8n/docker-compose.yml`, `apps/n8n/workflows/*.json` | `https://n8n.kaleschke.info` | Traefik (ohne pauschale Authelia, analog Komodo/Nextcloud), GMX IMAP, OpenAI API, Gitea API | `/mnt/user/appdata/n8n/data` (SQLite, Credentials, Workflows) | Tier 2, Borg + `n8n-data` (Credentials sind nur mit `N8N_ENCRYPTION_KEY` entschluesselbar) | ja, native Auth | Wegen Webhook-Endpunkten (`/webhook/*`) bewusst ohne `authelia@file`; eigene Login-/Owner-Auth bleibt Pflicht; `N8N_ENCRYPTION_KEY` ist Stack-ENV-Pflichtsecret, Verlust macht Credentials unbrauchbar. |
|
| `n8n` | Workflow-Automation; aktuell genutzt fuer Mail->LLM->Gitea-Issue (Inbox `Micha/mails`) | `apps/n8n/docker-compose.yml`, `apps/n8n/workflows/*.json` | `https://n8n.kaleschke.info` | Traefik (ohne pauschale Authelia, analog Komodo/Nextcloud), GMX IMAP, OpenAI API, Gitea API | `/mnt/user/appdata/n8n/data` (SQLite, Credentials, Workflows) | Tier 2, Borg + `n8n-data` (Credentials sind nur mit `N8N_ENCRYPTION_KEY` entschluesselbar) | ja, native Auth | Wegen Webhook-Endpunkten (`/webhook/*`) bewusst ohne `authelia@file`; eigene Login-/Owner-Auth bleibt Pflicht; `N8N_ENCRYPTION_KEY` ist Stack-ENV-Pflichtsecret, Verlust macht Credentials unbrauchbar. |
|
||||||
|
|
||||||
|
## Smart Home
|
||||||
|
|
||||||
|
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|
||||||
|
|---|---|---|---|---|---|---|---|---|
|
||||||
|
| `homeassistant` | Zentrale Smart-Home-Steuerung, Energy Dashboard, Integrations-Hub | Runtime: `smart-home/docker-compose.yml`; Fachkonfiguration: Repo `smart-home-kalli` | `https://home.kaleschke.info`; zusaetzlich LAN-only Host-Bind `192.168.178.58:8123` nur fuer den Ecowitt-HTTP-Push | Traefik, `frontend_net`, `smarthome_net`, `smarthome-mosquitto`, SolarEdge-Wechselrichter `192.168.178.111:1502`, Fachrepo unter `/mnt/user/services/smart-home-kalli` | `/mnt/user/appdata/homeassistant` inkl. `.storage`, `secrets.yaml`, `trusted_proxies.yaml` und `custom_components` (HACS, `solaredge_modbus_multi`); YAML-Fachdateien read-only aus `/mnt/user/services/smart-home-kalli/home-assistant`; Agent-API-Tokens als Host-Secrets `ha_token_codex`/`ha_token_claude` | Tier 2, Borg + HA-native Backups; erstes HA-Backup am 2026-06-13 erzeugt/geprueft; Restore-Probe am 2026-06-13 erfolgreich, Report `/mnt/user/backups/restore-reports/homeassistant-2026-06-13.md`; Backup nach SolarEdge-Integration: `/mnt/user/appdata/homeassistant/backups/Custom_backup_2026.6.1_2026-06-13_14.59_48645373.tar`; Backup nach Energy-Dashboard-Konfiguration: `/mnt/user/appdata/homeassistant/backups/Custom_backup_2026.6.1_2026-06-13_15.59_25670583.tar` | ja, native HA-Auth | HA Container statt HAOS-VM; keine Add-ons, keine Supervised-Installation. `configuration.yaml` kommt aus dem Fachrepo, `.storage` wird nicht versioniert. `http.use_x_forwarded_for`, `trusted_proxies` und `ip_ban_enabled` sind aktiv. HA-MQTT-Integration `smarthome-mosquitto` ist seit 2026-06-13 geladen. SolarEdge ist seit 2026-06-13 lokal ueber `solaredge_modbus_multi` v3.2.5 angebunden: `SolarEdge Local`, `192.168.178.111:1502`, Device-ID `1`, Meter+Batterie-Erkennung an, Power-Control aus. Energy Dashboard ist fuer Netz, PV und Speicher konfiguriert; Kosten folgen mit Tibber. Komodo-Stack und Gitea-Webhook sind aktiv. Ecowitt-Ingress seit 2026-06-13 ueber LAN-only Host-Bind `192.168.178.58:8123` geloest; offen ist nur die GW3000-Customized-Server-Konfiguration. Naechster Produktivschritt: Tibber. |
|
||||||
|
| `smarthome-mosquitto` | MQTT-Broker fuer HA, spaeter ESPHome und Zigbee2MQTT | `smart-home/docker-compose.yml`, `smart-home/mosquitto/config/mosquitto.conf` | intern `smarthome_net:1883`; kein LAN-Port in Phase 1 | `smarthome_net`, Passwort-/ACL-Dateien in Appdata | `/mnt/user/appdata/mosquitto/config`, `/mnt/user/appdata/mosquitto/data`, `/mnt/user/appdata/mosquitto/log` | Tier 2, Borg; Passwortdatei, ACLs und persistente Broker-Daten relevant; Restore-Probe am 2026-06-13 erfolgreich | nein | Authentifizierter Publish/Subscribe-Smoke und retained Topic nach Broker-Restart am 2026-06-13 erfolgreich. Home Assistant verbindet sich als User `homeassistant`. LAN-Port `1883` erst in ESPHome-Phase mit ACLs und per-Device-Usern. |
|
||||||
|
|
||||||
## Host Operations
|
## Host Operations
|
||||||
|
|
||||||
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ Es ist **vor** jeder Storage- oder Compose-Änderung zu lesen. Wenn ein neuer St
|
|||||||
| Disk1 (Array) | WDC WD60EFAX-68JH4N1 (`WD-WX32D90PC0V0`) | **XFS** auf `md1p1` | 5.5T nutzbar | Nutzdaten, Backups, Services | NTFS-zu-XFS-Migration Phase 2 abgeschlossen am 2026-05-25 |
|
| Disk1 (Array) | WDC WD60EFAX-68JH4N1 (`WD-WX32D90PC0V0`) | **XFS** auf `md1p1` | 5.5T nutzbar | Nutzdaten, Backups, Services | NTFS-zu-XFS-Migration Phase 2 abgeschlossen am 2026-05-25 |
|
||||||
| Parity | TOSHIBA HDWG480 (`2460A03VFA3H`) | — (keine FS) | 7.3T | Redundanz für Array | Unverändert |
|
| Parity | TOSHIBA HDWG480 (`2460A03VFA3H`) | — (keine FS) | 7.3T | Redundanz für Array | Unverändert |
|
||||||
| Boot | Samsung Flash Drive (`0375125090000587`) | FAT32 | 59.8G | Unraid-OS, Konfiguration | Regelmäßig per Flash-Backup gesichert |
|
| Boot | Samsung Flash Drive (`0375125090000587`) | FAT32 | 59.8G | Unraid-OS, Konfiguration | Regelmäßig per Flash-Backup gesichert |
|
||||||
| Externe Backup-Platte | H:/ `Externe HDD` am Windows-PC | NTFS | 8.0T | Nearline-Pull-Ziel für kritische Restore-Artefakte | Kein Off-site-/Airgap-Ersatz; Pull-Workflow in `docs/H_DRIVE_NEARLINE_PULL.md` |
|
| Externe Backup-Platte | H:/ `Externe HDD` am Windows-PC | NTFS | 8.0T | Nearline-Pull-Ziel für kritische Restore-Artefakte | Kein Off-site-/Airgap-Ersatz; Pull-Workflow in `ops/h-drive-nearline/README.md` |
|
||||||
|
|
||||||
Physikalische Basisdaten sind aus `docs/HARDWARE_INVENTORY.md` und dem Host-Readout vom 2026-05-27 übernommen. Detailwerte zu SMART/Health bleiben dort die autoritative Quelle; dieses Dokument hält die Storage-Policy.
|
Physikalische Basisdaten sind aus `docs/HARDWARE_INVENTORY.md` und dem Host-Readout vom 2026-05-27 übernommen. Detailwerte zu SMART/Health bleiben dort die autoritative Quelle; dieses Dokument hält die Storage-Policy.
|
||||||
|
|
||||||
@@ -384,4 +384,4 @@ Wenn Hermes-Worker auf weiteren Hosts skaliert: dieser Storage-Layout-Plan gilt
|
|||||||
|
|
||||||
Status: **Active v1.4 seit 2026-05-27**.
|
Status: **Active v1.4 seit 2026-05-27**.
|
||||||
|
|
||||||
Detailhistorie und alte Review-Tabellen liegen in der Git-Historie. Aktuelle Folgepunkte stehen nicht mehr hier, sondern in `docs/AUDIT_2026-05-25_TODO.md`.
|
Detailhistorie und alte Review-Tabellen liegen in der Git-Historie. Aktuelle Folgepunkte stehen nicht mehr hier, sondern in `docs/MASTER_TODO.md`.
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
# Weekend Execution Plan - 2026-06-05 bis 2026-06-07
|
|
||||||
|
|
||||||
Ziel: Bis Ende des Wochenendes alle offenen To-dos aus `docs/MASTER_TODO.md`
|
|
||||||
entweder erledigen, verifiziert schliessen, oder bewusst als geparkt/extern
|
|
||||||
blockiert markieren. Nicht jeder Punkt ist realistisch "fertig" im Sinne von
|
|
||||||
technisch umgesetzt: Family-Onboarding, zweite Hardware, USV und WAN-Failover
|
|
||||||
brauchen Operator- oder Hardware-Entscheidungen.
|
|
||||||
|
|
||||||
## Arbeitsregeln
|
|
||||||
|
|
||||||
- Secrets niemals in Chat, Logs oder Repo schreiben.
|
|
||||||
- Homelab-Aenderungen nur via GitOps, keine direkten Komodo-/Docker-Hotfixes.
|
|
||||||
- Destruktive Windows- oder Host-Schritte nur nach expliziter Freigabe.
|
|
||||||
- Ergebnis jedes abgeschlossenen Punkts in der Detaildoku und in
|
|
||||||
`docs/MASTER_TODO.md` nachziehen.
|
|
||||||
- Am Ende: ein sauberer Commit-Block; Push erst nach Freigabe.
|
|
||||||
|
|
||||||
## Owner-Aufteilung
|
|
||||||
|
|
||||||
| Owner | Fokus | Ergebnis |
|
|
||||||
|---|---|---|
|
|
||||||
| Codex | `baerchen` Veeam, Doku-Konsolidierung, lokale Checks, Commit-Vorbereitung | Veeam-Erstbackup geprueft, Recovery-Test dokumentiert, Masterliste aktualisiert |
|
|
||||||
| Claude | Family-Onboarding-Paket, Network-/Tailscale-Entscheidungen, Hardware-/Todo-Konsolidierung, nicht-destruktive Runbooks | Konkrete Doku-Patches, ausfuehrbare Checklisten, klare Operator-Fragen statt diffuser TBDs |
|
|
||||||
| Operator | Physische/GUI-Schritte, Secrets, Familie, Hardwareentscheidungen | Recovery-USB booten, Passwoerter/Keys bereitstellen, Family-Onboarding starten/entscheiden |
|
|
||||||
|
|
||||||
## Codex-Aufgaben
|
|
||||||
|
|
||||||
| Prioritaet | Aufgabe | Abschlusskriterium |
|
|
||||||
|---|---|---|
|
|
||||||
| P1 | Veeam-Erstbackup `baerchen-c-image` pruefen | **erledigt 2026-06-05:** Full-Lauf geschrieben, Veeam-GUI 53,8 GB, Dauer 0:11:31, MetaCheck 0 Fehler/0 Warnungen; Storage Encryption war nicht aktiv und ist als Operator-Entscheidung dokumentiert |
|
|
||||||
| P1 | Recovery-USB-Test begleiten | `VEEAMRE` bootet, SMB-Ziel sichtbar, Restore Point sichtbar, vor Restore abgebrochen |
|
|
||||||
| P1 | `windows-image-backup-baseline.md` finalisieren | Erster Lauf und Teststatus mit Datum eingetragen |
|
|
||||||
| P1 | `docs/MASTER_TODO.md` nach jedem Abschluss aktualisieren | erledigte Punkte entfernt oder in "geschlossen" vermerkt |
|
|
||||||
| P2 | Alte Windows-Reinstall-Doku bereinigen | ueberholte WinRE-/Admin-To-dos als erledigt/ueberholt markiert |
|
|
||||||
| P2 | Git-Status sortieren | Eigene Aenderungen klar von vorhandenen User-Aenderungen getrennt |
|
|
||||||
| P2 | Commit vorbereiten | Commit-Message-Vorschlag und Datei-Liste bereit; kein Push ohne Freigabe |
|
|
||||||
|
|
||||||
## Claude-Aufgaben
|
|
||||||
|
|
||||||
Claude soll parallel nur repo-seitig arbeiten und keine produktiven Host-Aenderungen
|
|
||||||
ausfuehren. Die Aufgaben sind bewusst als echte Doku-/Planungsarbeit formuliert,
|
|
||||||
nicht nur als Pruefaufgaben:
|
|
||||||
|
|
||||||
| Prioritaet | Aufgabe | Abschlusskriterium |
|
|
||||||
|---|---|---|
|
|
||||||
| P1 | `docs/MASTER_TODO.md` gegen Detaildokus gegenpruefen | **erledigt 2026-06-05:** Sync-Notiz in `docs/AUDIT_2026-05-25_TODO.md`, Masterliste aktualisiert |
|
|
||||||
| P1 | Restore-Backlog aktualisieren | **erledigt 2026-06-05:** erledigte Kandidaten aus `docs/RESTORE_MATRIX.md` bereinigt |
|
|
||||||
| P1 | Family-Onboarding in ein ausfuehrbares Session-Paket umwandeln | **erledigt 2026-06-05:** `docs/FAMILY_ONBOARDING.md` enthaelt Vorbereitungs-, Termin- und Erfolgskriterien ohne Secret-Werte |
|
|
||||||
| P1 | `docs/NETWORK_INVENTORY.md` TBDs in Entscheidungen oder konkrete Operator-Fragen verwandeln | **erledigt 2026-06-05:** Tailscale IPv6/Exit Node/Subnet Router/ACL-Policy sind als Messaufgabe/Operator-Entscheidung formuliert; Gast-/WAN-Pfade sind geparkt oder mit Vorbedingungen versehen |
|
|
||||||
| P2 | Nicht-destruktive Runbooks fuer offene Restore-Tests vorbereiten | **erledigt 2026-06-05:** Runbook-Stubs fuer Unraid Flash, AdGuard, Tailscale, Redis 8 in `docs/RESTORE_MATRIX.md` |
|
|
||||||
| P2 | `docs/AUDIT_2026-05-25_TODO.md` und `MASTER_TODO.md` synchronisieren | **erledigt 2026-06-05:** keine doppelten oder widerspruechlichen P1/P2-Punkte |
|
|
||||||
| P2 | Windows-Reinstall-Altdoku auf ueberholte To-dos pruefen | **erledigt 2026-06-05:** WinRE/Admin-Check-Altlasten als erledigt/ueberholt markiert |
|
|
||||||
| P2 | Hardware-/Betriebsentscheidungen konsolidieren | **teilweise erledigt 2026-06-05:** USV und Cold-Backup-Rotation sind entschieden/geparkt; Masterliste fuehrt sie nicht mehr als aktive Umsetzungsaufgaben |
|
|
||||||
| P3 | Geparkte Punkte klassifizieren | Family/USV/WAN/CrowdSec/OIDC klar als Entscheidung statt Umsetzungsarbeit markiert |
|
|
||||||
|
|
||||||
## Operator-Aufgaben
|
|
||||||
|
|
||||||
| Prioritaet | Aufgabe | Abschlusskriterium |
|
|
||||||
|---|---|---|
|
|
||||||
| P1 | Veeam-Encryption-Entscheidung treffen | Fuer den ersten Full-Lauf ist kein Veeam-Encryption-Passwort noetig; falls Storage Encryption aktiviert wird, Passwort in Vaultwarden anlegen und neues Full erzeugen |
|
|
||||||
| P1 | Recovery-USB physisch booten | Boot ins Veeam-Recovery-System gelingt |
|
|
||||||
| P1 | Keine echten Restore-Ziele bestaetigen | Restore-Test wird vor destruktiver Datentraegerauswahl abgebrochen |
|
|
||||||
| P2 | BitLocker-Entscheidung treffen | `aktivieren`, `spaeter`, oder `bewusst aus` dokumentiert |
|
|
||||||
| P2 | Family-Onboarding real starten oder terminieren | konkreter Termin/Personenkreis statt offenem Wunsch |
|
|
||||||
| P3 | Hardware-Entscheidungen | USV/Cold-Rotation/WAN-Failover als kaufen, spaeter, oder bewusst nein markieren |
|
|
||||||
|
|
||||||
## Realistische Wochenend-Ziele
|
|
||||||
|
|
||||||
Bis Sonntagabend realistisch fertig:
|
|
||||||
|
|
||||||
- `baerchen` Veeam-Erstbackup verifiziert.
|
|
||||||
- `baerchen` Recovery-USB-Test ohne Restore verifiziert.
|
|
||||||
- Veeam-/BitLocker-Doku bereinigt.
|
|
||||||
- Master-To-do-Liste bereinigt.
|
|
||||||
- Restore-Backlog sortiert.
|
|
||||||
- Alte/ueberholte To-dos als erledigt/ueberholt markiert.
|
|
||||||
- Blockierte Punkte explizit als Betreiber-/Hardware-/Familienentscheidung markiert.
|
|
||||||
|
|
||||||
Nicht realistisch ohne externe Voraussetzungen:
|
|
||||||
|
|
||||||
- End-to-end-DR-Drill ohne zweite Hardware.
|
|
||||||
- Family-Onboarding ohne Familie/Geraete.
|
|
||||||
- USV erledigen ohne Kauf.
|
|
||||||
- WAN-Failover erledigen ohne Mobilfunk-/Router-Entscheidung.
|
|
||||||
- Dedizierter SMB-User ohne bewusste Unraid-User-/Share-Aenderung.
|
|
||||||
|
|
||||||
## Prompt fuer Claude
|
|
||||||
|
|
||||||
```text
|
|
||||||
Du bist Claude im KalliLab CORE Homelab-Repo.
|
|
||||||
|
|
||||||
Arbeitsziel fuer dieses Wochenende:
|
|
||||||
Hilf, alle offenen To-dos aus `docs/MASTER_TODO.md` bis Sonntagabend entweder
|
|
||||||
zu erledigen, sauber zu dokumentieren, oder bewusst als geparkt/blockiert zu
|
|
||||||
klassifizieren. Arbeite repo-seitig, keine produktiven Host-Aenderungen.
|
|
||||||
|
|
||||||
Pflichtregeln:
|
|
||||||
- Lies zuerst `CLAUDE.md`.
|
|
||||||
- Lies danach `HOMELAB_ARCHITECTURE_MASTER_V2.md`, `docs/WORKFLOW.md`,
|
|
||||||
`docs/README.md`, `docs/REPO_MAP.md`, `docs/MASTER_TODO.md`,
|
|
||||||
`docs/RESTORE_MATRIX.md`, `docs/DISASTER_RECOVERY.md`,
|
|
||||||
`docs/SECRETS_MAP.md` und `ops/windows-reinstall/docs/windows-image-backup-baseline.md`.
|
|
||||||
- Keine Secrets ins Repo. Nur Secret-Namen, Pfade und Ablageorte dokumentieren.
|
|
||||||
- Keine Komodo-/Docker-/Host-Hotfixes. Keine produktiven Schreibbefehle auf dem Homelab.
|
|
||||||
- Keine destruktiven Aktionen.
|
|
||||||
- Beachte vorhandene uncommitted Aenderungen; nichts revertieren, was du nicht selbst gemacht hast.
|
|
||||||
|
|
||||||
Konkrete Aufgaben:
|
|
||||||
1. Wandle `docs/FAMILY_ONBOARDING.md` von einer guten Erklaerseite in ein
|
|
||||||
ausfuehrbares Session-Paket um:
|
|
||||||
- 30-Minuten-Ablauf fuer das erste echte Onboarding
|
|
||||||
- Checkliste pro Geraet/Person ohne Namen oder Secret-Werte
|
|
||||||
- klare Abschlusskriterien fuer Vaultwarden, Immich und Mealie
|
|
||||||
- Liste der Operator-Fragen, falls Konten/Startpasswoerter fehlen
|
|
||||||
2. Bereinige `docs/NETWORK_INVENTORY.md`:
|
|
||||||
- Tailscale IPv6, Exit Node, Subnet Router und ACL-Policy nicht als
|
|
||||||
unerklaerte `TBD` stehen lassen
|
|
||||||
- wenn nicht verifizierbar: als konkrete Operator-Frage oder bewusst offene
|
|
||||||
Entscheidung formulieren
|
|
||||||
- Gast-/IoT-Zugriff als Entscheidungspfad dokumentieren, nicht als vage
|
|
||||||
Altlast
|
|
||||||
3. Ziehe `docs/MASTER_TODO.md` nach deinen Edits nach:
|
|
||||||
- echte naechste Schritte in P1/P2
|
|
||||||
- geparkte Entscheidungen nur im geparkten/geschlossenen Bereich
|
|
||||||
- keine Duplikate zu `docs/AUDIT_2026-05-25_TODO.md`
|
|
||||||
4. Falls du weitere diffuse TBDs in Hardware/Network/Family findest: nicht nur
|
|
||||||
melden, sondern in konkrete Entscheidung, geparkten Punkt oder naechsten
|
|
||||||
Operator-Schritt umformulieren.
|
|
||||||
5. Schon erledigte Restore-/Windows-Doku-Aufgaben nicht erneut bearbeiten,
|
|
||||||
ausser du findest einen klaren Widerspruch.
|
|
||||||
6. Am Ende liefere:
|
|
||||||
- geaenderte Dateien
|
|
||||||
- welche Punkte geschlossen wurden
|
|
||||||
- welche Punkte blockiert/geparkt bleiben und warum
|
|
||||||
- welche Operator-Schritte noch noetig sind
|
|
||||||
|
|
||||||
Nicht tun:
|
|
||||||
- Keine Secrets anzeigen oder erfinden.
|
|
||||||
- Kein Push.
|
|
||||||
- Kein `docker`, `ssh` oder Host-Schreibzugriff.
|
|
||||||
- Kein BitLocker, keine Veeam-Aenderung, keine Unraid-User-/Share-Aenderung.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Abschlusskriterien fuer Sonntag
|
|
||||||
|
|
||||||
- `docs/MASTER_TODO.md` ist die fuehrende Liste.
|
|
||||||
- Alle erledigten Punkte haben Beleg in der Detaildoku.
|
|
||||||
- Alle nicht erledigbaren Punkte sind als blockiert/geparkt mit Grund markiert.
|
|
||||||
- `git status` ist verstanden: eigene Doku-Aenderungen vs. bestehende
|
|
||||||
User-Aenderungen sind getrennt.
|
|
||||||
- Commit ist vorbereitet, Push erfolgt nur nach Operator-Freigabe.
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Weekend Status - 2026-06-05
|
|
||||||
|
|
||||||
Kurzlebiges Arbeitsboard fuer den Wochenend-Sprint. Fuehrende Liste bleibt
|
|
||||||
`docs/MASTER_TODO.md`; dieses Board haelt nur den aktuellen Arbeitsstand fest.
|
|
||||||
|
|
||||||
## Jetzt laufend
|
|
||||||
|
|
||||||
| Owner | Aufgabe | Status | Naechster Schritt |
|
|
||||||
|---|---|---|---|
|
|
||||||
| Codex | Veeam-Erstbackup `baerchen-c-image` | erledigt | Erster Full-Lauf 2026-06-05 geschrieben; Recovery-Test bleibt offen |
|
|
||||||
| Codex | Veeam-Verifikationshilfe | erledigt | Hilfsskript bleibt fuer spaetere Checks verfuegbar |
|
|
||||||
| Claude | Restore-/Altdoku-Bereinigung | erledigt | Keine weitere Arbeit an Veeam/Windows/Restore-Matrix ohne neuen Widerspruch |
|
|
||||||
| Claude | Family-/Network-Ausfuehrungspaket | erledigt | Masterliste und Weekend-Plan sind nachgezogen |
|
|
||||||
|
|
||||||
## Naechste Operator-Schritte
|
|
||||||
|
|
||||||
| Zeitpunkt | Aufgabe | Ergebnis, das dokumentiert wird |
|
|
||||||
|---|---|---|
|
|
||||||
| Erledigt | Veeam-Erstbackup `baerchen-c-image` pruefen | 2026-06-05 19:46, Full-Lauf erfolgreich, Veeam-GUI 53,8 GB, Dauer 0:11:31 |
|
|
||||||
| Als naechstes | Recovery-USB `VEEAMRE` booten | Boot OK, Netzwerk OK, SMB-Ziel sichtbar |
|
|
||||||
| Im Recovery-Test | Restore Point anzeigen; falls spaeter verschluesselt: Passwort testen | Restore Point sichtbar; vor echtem Restore abgebrochen |
|
|
||||||
| Spaeter | BitLocker-Entscheidung treffen | `aktivieren`, `spaeter`, oder `bewusst aus` in `docs/MASTER_TODO.md`/Baseline nachziehen |
|
|
||||||
|
|
||||||
## Heute bereits geschlossen
|
|
||||||
|
|
||||||
| Thema | Ergebnis |
|
|
||||||
|---|---|
|
|
||||||
| WinRE/Admin-Altlasten | In Windows-Reinstall-Doku als erledigt/ueberholt markiert |
|
|
||||||
| Restore-Test-Kandidaten | Erledigte Kandidaten aus der aktiven Liste entfernt; Stubs fuer offene Kandidaten ergaenzt |
|
|
||||||
| Family-Onboarding | Aus der Familien-Doku wurde ein konkreter 30-45-Minuten-Terminablauf mit Vorbereitung und Erfolgskriterien |
|
|
||||||
| Network-TBDs | Tailscale-/Gastnetz-/WAN-Failover-Punkte wurden in Messaufgaben, Vorbedingungen oder geparkte Entscheidungen umgewandelt |
|
|
||||||
| Veeam-Erstbackup | Full-Lauf 2026-06-05 erfolgreich geschrieben: Veeam-GUI 53,8 GB, Dauer 0:11:31, MetaCheck 0 Fehler/0 Warnungen, VSS success; Veeam Storage Encryption war nicht aktiv |
|
|
||||||
| Cold-Backup-Rotation | Bewusst Hetzner-only; kein aktives Todo mehr |
|
|
||||||
| USV | Bewusst auf Q3/2026 geparkt; Power-Loss bleibt akzeptiertes Risiko |
|
|
||||||
|
|
||||||
## Nicht ohne neue Freigabe anfassen
|
|
||||||
|
|
||||||
- Keine BitLocker-Aktivierung.
|
|
||||||
- Keine Aenderung am Veeam-Job oder Encryption-Status.
|
|
||||||
- Keine Unraid-User-/Share-Aenderung.
|
|
||||||
- Keine produktiven Host- oder Docker-Schreibbefehle.
|
|
||||||
+16
-4
@@ -124,14 +124,20 @@ Pflichtschritte beim Anlegen:
|
|||||||
1. Stack in Komodo aus Gitea anlegen
|
1. Stack in Komodo aus Gitea anlegen
|
||||||
2. `webhook_enabled` in Komodo aktivieren
|
2. `webhook_enabled` in Komodo aktivieren
|
||||||
3. passenden Gitea-Webhook fuer die aktuelle Stack-ID anlegen
|
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
|
4. Branch-Filter im Gitea-Hook auf den produktiven Branch setzen, aktuell `master`
|
||||||
5. einen Push oder Test-Delivery ausloesen und `last_status`/Komodo-Deploy pruefen
|
5. Gitea-Hook gegen `http://komodo-core:9120/listener/github/stack/<stack-id>/deploy` pruefen
|
||||||
6. Ausnahmen explizit dokumentieren
|
6. einen Push oder Test-Delivery ausloesen und `last_status`/Komodo-Deploy pruefen
|
||||||
|
7. 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.
|
**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.
|
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.
|
||||||
|
|
||||||
|
Der Gitea-Branch-Filter darf nicht leer oder `*` bleiben, solange der Komodo-Stack
|
||||||
|
einen konkreten Repo-Branch erwartet. Sonst triggern Feature-/Arbeitsbranches alle
|
||||||
|
Stack-Listener, Komodo verwirft sie mit `request branch does not match expected`
|
||||||
|
und der Operations-Report bekommt unnuetzes Komodo-/Traefik-Rauschen.
|
||||||
|
|
||||||
### Ausnahme: Komodo-Zugangsmodell
|
### Ausnahme: Komodo-Zugangsmodell
|
||||||
|
|
||||||
Komodo bleibt **bewusst** ohne zentrale Traefik-ForwardAuth-Middleware.
|
Komodo bleibt **bewusst** ohne zentrale Traefik-ForwardAuth-Middleware.
|
||||||
@@ -369,7 +375,13 @@ Wenn ein Stack `webhook_enabled` in Komodo hatte, zusaetzlich pruefen, ob der zu
|
|||||||
|
|
||||||
## Dokumentationspflicht
|
## Dokumentationspflicht
|
||||||
|
|
||||||
Nach jeder erfolgreichen Migration oder relevanten Aenderung muessen diese Dateien geprueft werden:
|
Es gilt "ein Fakt, ein Zuhause" (`docs/REPO_MAP.md` Doku-Regeln): aktualisiert
|
||||||
|
wird das jeweils zustaendige Dokument plus `docs/README.md`-Index, nicht
|
||||||
|
mehrere Kopien. Nach jeder relevanten Aenderung pruefen, **welche** dieser
|
||||||
|
Zuhause betroffen sind:
|
||||||
|
|
||||||
|
- `docs/DECISIONS.md` falls eine bewusste Entscheidung getroffen oder revidiert wurde
|
||||||
|
- `docs/MASTER_TODO.md` falls sich der Status offener Punkte aendert
|
||||||
|
|
||||||
- `docs/SECRETS_MAP.md`
|
- `docs/SECRETS_MAP.md`
|
||||||
- `docs/ROLLBACK.md`
|
- `docs/ROLLBACK.md`
|
||||||
|
|||||||
+8
-1
@@ -1,8 +1,15 @@
|
|||||||
# Home Assistant -> InfluxDB 3 -> Grafana
|
# Home Assistant -> InfluxDB 3 -> Grafana
|
||||||
|
|
||||||
|
**Status 2026-06-06: archiviert / nicht aktiv.** Home Assistant existiert seit
|
||||||
|
dem Crash aktuell nicht mehr. Dieses Dokument ist nur noch ein historischer
|
||||||
|
Zielbild-Entwurf fuer einen spaeteren Neuaufbau. Das fruehere TODO
|
||||||
|
`influxdb3_homeassistant_token` wurde aus der aktiven Master-Liste gestrichen;
|
||||||
|
vor Token-, InfluxDB-Writer- oder Ecowitt-Arbeiten muss Home Assistant zuerst
|
||||||
|
neu aufgesetzt und neu inventarisiert werden.
|
||||||
|
|
||||||
Ziel: Home Assistant schreibt ausgewaehlte Ecowitt- und Energiesensoren nach InfluxDB 3 Core. Grafana bleibt das Langzeit-Dashboard, Home Assistant bleibt die Automationszentrale.
|
Ziel: Home Assistant schreibt ausgewaehlte Ecowitt- und Energiesensoren nach InfluxDB 3 Core. Grafana bleibt das Langzeit-Dashboard, Home Assistant bleibt die Automationszentrale.
|
||||||
|
|
||||||
## Live-Stand 2026-05-04
|
## Historischer Live-Stand 2026-05-04
|
||||||
|
|
||||||
- Home Assistant ist per SSH unter `192.168.178.50:22222` erreichbar.
|
- Home Assistant ist per SSH unter `192.168.178.50:22222` erreichbar.
|
||||||
- `ha core check` ist erfolgreich.
|
- `ha core check` ist erfolgreich.
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# baerchen App-/Lizenz-Readiness - 2026-06-06
|
||||||
|
|
||||||
|
Automatisch erzeugter lokaler Check. Keine Lizenzkeys, Passwoerter, Tokens oder Recovery-Code-Werte wurden ausgelesen oder ins Repo geschrieben.
|
||||||
|
|
||||||
|
## Ergebnis
|
||||||
|
|
||||||
|
- Technische Inventarisierung: erledigt
|
||||||
|
- Manuelle Konto-/Recovery-Bestaetigung: erledigt laut Operator-Bestaetigung 2026-06-06 ("alle Dienste laufen")
|
||||||
|
|
||||||
|
## Installierte Programme
|
||||||
|
|
||||||
|
### Passwortmanager / Browser
|
||||||
|
|
||||||
|
| DisplayName | DisplayVersion | Publisher | InstallDate |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Brave | 149.1.91.168 | Die Brave-Autoren | 20260604 |
|
||||||
|
| Google Chrome | 149.0.7827.54 | Google LLC | 20260604 |
|
||||||
|
| Microsoft Edge | 148.0.3967.96 | Microsoft Corporation | 20260604 |
|
||||||
|
| Microsoft Edge WebView2-Laufzeit | 148.0.3967.96 | Microsoft Corporation | 20260604 |
|
||||||
|
|
||||||
|
### Banking4 / Subsembly
|
||||||
|
|
||||||
|
| DisplayName | DisplayVersion | Publisher | InstallDate |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Banking4 Home | | Subsembly GmbH | |
|
||||||
|
|
||||||
|
### WISO / Buhl
|
||||||
|
|
||||||
|
| DisplayName | DisplayVersion | Publisher | InstallDate |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| WISO Steuer 2026 | 33.07.3410 | Buhl Data Service GmbH | 20260604 |
|
||||||
|
|
||||||
|
### Microsoft 365 / Office / OneDrive
|
||||||
|
|
||||||
|
| DisplayName | DisplayVersion | Publisher | InstallDate |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Microsoft 365 - de-de | 16.0.20026.20140 | Microsoft Corporation | |
|
||||||
|
| Microsoft 365 - en-us | 16.0.20026.20140 | Microsoft Corporation | |
|
||||||
|
| Microsoft OneDrive | 23.038.0219.0001 | Microsoft Corporation | |
|
||||||
|
| Office 16 Click-to-Run Extensibility Component | 16.0.20026.20076 | Microsoft Corporation | 20260604 |
|
||||||
|
| Office 16 Click-to-Run Localization Component | 16.0.20026.20140 | Microsoft Corporation | 20260604 |
|
||||||
|
|
||||||
|
## Relevante Datenpfade
|
||||||
|
|
||||||
|
| Path | Exists | Type | LastWriteTime | Bytes |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| C:\Users\michi\AppData\Local\Subsembly | True | Directory | 2026-06-04T12:23:43 | 43360359 |
|
||||||
|
| C:\Users\michi\AppData\Local\Buhl | True | Directory | 2026-06-04T12:55:57 | 680833 |
|
||||||
|
| C:\Users\michi\AppData\Local\Buhl Data Service GmbH | False | | | |
|
||||||
|
| C:\ProgramData\Buhl Data Service GmbH | True | Directory | 2026-06-04T12:57:08 | 6037194 |
|
||||||
|
| C:\Users\michi\Documents\steuer | True | Directory | 2026-01-26T11:21:44 | 13069132 |
|
||||||
|
| C:\Users\michi\Desktop\Banking | False | | | |
|
||||||
|
| C:\Users\michi\OneDrive | True | Directory | 2026-06-04T12:39:24 | 39370265 |
|
||||||
|
| D:\30_Finanzen | True | Directory | 2026-06-04T20:14:26 | 128994854 |
|
||||||
|
| D:\30_Finanzen\Recovery-Codes | False | | | |
|
||||||
|
| D:\30_Finanzen\BitLocker-RecoveryKey-baerchen-2026-06-06.txt | False | | | |
|
||||||
|
|
||||||
|
## OneDrive / Microsoft 365 Indikatoren
|
||||||
|
|
||||||
|
### OneDrive Prozess
|
||||||
|
|
||||||
|
_Keine Treffer._
|
||||||
|
|
||||||
|
### OneDrive Accounts Registry
|
||||||
|
|
||||||
|
| PSChildName |
|
||||||
|
| --- |
|
||||||
|
| Business1 |
|
||||||
|
| Personal |
|
||||||
|
|
||||||
|
### Office Aktivierungsindikatoren
|
||||||
|
|
||||||
|
_Keine Office-OSPP-Aktivierungsdaten gefunden oder Office nicht klassisch installiert._
|
||||||
|
|
||||||
|
## Manuell noch zu bestaetigen
|
||||||
|
|
||||||
|
- [x] Passwortmanager laesst sich oeffnen und enthaelt Homelab-/Banking-/Provider-Eintraege.
|
||||||
|
- [x] 2FA-Recovery-Codes fuer Microsoft, Hetzner, Cloudflare, Tailscale, Gitea/GitHub und Banken sind offline oder in Vaultwarden auffindbar.
|
||||||
|
- [x] Banking4 oeffnet den aktuellen Datentresor; ein frischer Backup-/Exportpfad ist bekannt.
|
||||||
|
- [x] WISO Steuer 2026 oeffnet, Buhl-Konto/Lizenz ist aktiv, Steuerdateien unter `C:\Users\michi\Documents\steuer` bzw. neuem Zielpfad sind sichtbar.
|
||||||
|
- [x] Microsoft-Konto zeigt aktives M365/Office-Installationsrecht.
|
||||||
|
- [x] OneDrive-Sync ist angemeldet und synchronisiert die erwarteten Ordner.
|
||||||
|
|
||||||
|
## Bewertung
|
||||||
|
|
||||||
|
Dieses Dokument belegt die technische Inventarisierung und die Operator-Bestaetigung vom 2026-06-06. Secret-Werte, Lizenzkeys und Recovery-Code-Werte wurden nicht dokumentiert.
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
# DR-Workstation Readiness - 2026-06-06
|
||||||
|
|
||||||
|
Automatisch erzeugter lokaler Readiness-Check fuer den Operator-PC. Es wurden keine Secret-Werte, Passphrases oder Private-Key-Inhalte ausgegeben.
|
||||||
|
|
||||||
|
## Zusammenfassung
|
||||||
|
|
||||||
|
| Check | Ergebnis |
|
||||||
|
|---|---|
|
||||||
|
| WSL2 Ubuntu | vorhanden (`Ubuntu 24.04`, WSL Version 2) |
|
||||||
|
| SSH/Git in WSL | vorhanden |
|
||||||
|
| GitHub-Read-Smoke mit DR-Key | ok |
|
||||||
|
| Borg Client | installiert |
|
||||||
|
| Hetzner Storage Box mit DR-Key | ok |
|
||||||
|
| `~/dr-smoke.sh` | vorhanden |
|
||||||
|
| Finaler Borg-Smoke | ok, Operator-Bestaetigung 2026-06-06 10:05:30 |
|
||||||
|
| WSL sudo ohne Passwortprompt | nein, Operator muss Passwort eingeben |
|
||||||
|
|
||||||
|
## Bewertung
|
||||||
|
|
||||||
|
- Der lokale WSL2-/Ubuntu-Unterbau ist vorhanden.
|
||||||
|
- Die DR-Key-Arbeitskopien liegen in WSL unter `~/.ssh/dr-readonly` und `~/.ssh/dr-hetzner`.
|
||||||
|
- GitHub-Read-Smoke und Hetzner-SSH-Smoke sind erfolgreich.
|
||||||
|
- `borgbackup` ist installiert.
|
||||||
|
- Der vollstaendige Bare-Metal-DR-Smoke ist erfolgreich abgeschlossen.
|
||||||
|
|
||||||
|
## Finaler Borg-Smoke
|
||||||
|
|
||||||
|
Operator-Bestaetigung vom 2026-06-06:
|
||||||
|
|
||||||
|
- Befehl: `bash ~/dr-smoke.sh`
|
||||||
|
- GitHub Deploy-Key: HEAD `3a263a4...`
|
||||||
|
- Hetzner SSH-Login: Repos `backup`, `backup2`, `hetzner_borg_appdata`, `hetzner_borg_appdata_critical` sichtbar
|
||||||
|
- Borg-Repo: `ssh://u565255@u565255.your-storagebox.de/./hetzner_borg_appdata_critical`
|
||||||
|
- Repository ID: `5dd9b949...`
|
||||||
|
- Encryption: `Yes (repokey)`
|
||||||
|
- Borg-Statistik: `Original size 1.16 TB`, `Compressed size 1.13 TB`, `Deduplicated size 35.92 GB`
|
||||||
|
- Ergebnis: `DR-Smoke OK (2026-06-06 10:05:30)`
|
||||||
|
|
||||||
|
Die Borg-Passphrase wurde nur interaktiv eingegeben und nicht dauerhaft auf `baerchen` gespeichert.
|
||||||
|
|
||||||
|
## Rohchecks
|
||||||
|
|
||||||
|
### wsl_status
|
||||||
|
|
||||||
|
- ExitCode: `0`
|
||||||
|
|
||||||
|
```text
|
||||||
|
Standarddistribution: Ubuntu
|
||||||
|
|
||||||
|
Standardversion: 2
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### wsl_list
|
||||||
|
|
||||||
|
- ExitCode: `0`
|
||||||
|
|
||||||
|
```text
|
||||||
|
NAME STATE VERSION
|
||||||
|
|
||||||
|
* Ubuntu Stopped 2
|
||||||
|
|
||||||
|
docker-desktop Stopped 2
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### ubuntu_os
|
||||||
|
|
||||||
|
- ExitCode: `0`
|
||||||
|
|
||||||
|
```text
|
||||||
|
Distributor ID: Ubuntu
|
||||||
|
Description: Ubuntu 24.04.4 LTS
|
||||||
|
Release: 24.04
|
||||||
|
Codename: noble
|
||||||
|
6.6.114.1-microsoft-standard-WSL2
|
||||||
|
```
|
||||||
|
|
||||||
|
### tools
|
||||||
|
|
||||||
|
- ExitCode: `0`
|
||||||
|
|
||||||
|
```text
|
||||||
|
/usr/bin/borg
|
||||||
|
borg 1.2.8
|
||||||
|
/usr/bin/ssh
|
||||||
|
OpenSSH_9.6p1 Ubuntu-3ubuntu13.16, OpenSSL 3.0.13 30 Jan 2024
|
||||||
|
/usr/bin/git
|
||||||
|
git version 2.43.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### sudo
|
||||||
|
|
||||||
|
- ExitCode: `0`
|
||||||
|
|
||||||
|
```text
|
||||||
|
sudo-password-needed
|
||||||
|
```
|
||||||
|
|
||||||
|
### wsl_ssh_files
|
||||||
|
|
||||||
|
- ExitCode: `0`
|
||||||
|
|
||||||
|
```text
|
||||||
|
total 40
|
||||||
|
drwx------ 2 michi michi 4096 Jun 6 09:14 .
|
||||||
|
drwxr-x--- 5 michi michi 4096 Jun 6 08:37 ..
|
||||||
|
-rw------- 1 michi michi 411 Jun 6 09:14 dr-hetzner
|
||||||
|
-rw------- 1 michi michi 419 Jun 6 09:14 dr-readonly
|
||||||
|
-rw------- 1 michi michi 411 Apr 4 19:29 id_ed25519
|
||||||
|
-rw-r--r-- 1 michi michi 97 Apr 4 19:29 id_ed25519.pub
|
||||||
|
-rw------- 1 michi michi 6358 Jun 6 09:14 known_hosts
|
||||||
|
-rw------- 1 michi michi 3013 Apr 20 20:13 known_hosts.old
|
||||||
|
-rw------- 1 michi michi 3858 Apr 24 08:27 known_hosts.pre-port222-20260604-122031.bak
|
||||||
|
-rwxr-xr-x 1 michi michi 1311 Jun 6 08:37 /home/michi/dr-smoke.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### github_dr_key_smoke
|
||||||
|
|
||||||
|
- ExitCode: `0`
|
||||||
|
|
||||||
|
```text
|
||||||
|
68d3ace598ee4d1cdad3ed94b63ae5046ac187fb HEAD
|
||||||
|
```
|
||||||
|
|
||||||
|
### hetzner_dr_key_smoke
|
||||||
|
|
||||||
|
- ExitCode: `0`
|
||||||
|
|
||||||
|
```text
|
||||||
|
backup
|
||||||
|
backup2
|
||||||
|
hetzner_borg_appdata
|
||||||
|
hetzner_borg_appdata_critical
|
||||||
|
```
|
||||||
@@ -0,0 +1,401 @@
|
|||||||
|
# Homelab-Doku-Optimierung — Analyse und Vorschlag 2026-06-11
|
||||||
|
|
||||||
|
Typ: Analyse / Optimierungsvorschlag · Stand: 2026-06-11 · Status: **umgesetzt am 2026-06-11** (archiviert; siehe `docs/DECISIONS.md` Eintrag 2026-06-11). Nicht umgesetzt blieben nur: Hermes-README-Kuerzung (beim Review 2026-07-25), PDF-Ablage extern (Operator), optionale Projekte aus Abschnitt 13.
|
||||||
|
|
||||||
|
Read-only-Analyse der gesamten Markdown-Dokumentation (Stand `master`, lokale
|
||||||
|
Arbeitskopie 2026-06-11). Es wurde nichts gelöscht, verschoben oder verändert;
|
||||||
|
dieses Dokument ist der einzige neue Inhalt. Abgrenzung: `docs/homelab-optimierung.md`
|
||||||
|
(2026-06-10) bewertet die **technische** Betriebsebene; dieses Dokument bewertet
|
||||||
|
ausschließlich die **Dokumentation und ihre Regeln**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Executive Summary
|
||||||
|
|
||||||
|
Die Doku ist inhaltlich exzellent und ungewöhnlich diszipliniert gepflegt —
|
||||||
|
das Problem ist nicht Qualität oder Veralterung, sondern **Volumen, Mehrfachpflege
|
||||||
|
und fehlende Lebenszyklus-Regeln**. Kennzahlen:
|
||||||
|
|
||||||
|
- **74 versionierte Markdown-Dateien, ~9.400 Zeilen** (davon `docs/`: 35 Dateien / ~5.050 Zeilen, `ops/`: 34 Dateien).
|
||||||
|
- Praktisch alle Dateien wurden in den letzten 4 Wochen angefasst — es gibt **kein Stale-Problem, aber ein Pflegelast-Problem**.
|
||||||
|
- Ein einzelner Sachverhalt wird heute an **6–9 Stellen** dokumentiert (Beispiele in Abschnitt 3.1). Jede Änderung erzeugt dadurch eine Update-Kaskade über viele Dateien.
|
||||||
|
- Vier parallele Status-/To-do-Listen plus Done-Logs in fast jedem Dokument.
|
||||||
|
- Abgeschlossene Sprints, Audits und Pläne bleiben als aktive Dateien liegen, obwohl `docs/README.md` (Zeile 5) genau das verbietet — die Policy existiert, wird aber nicht durchgesetzt.
|
||||||
|
|
||||||
|
Kernempfehlung in einem Satz: **Nicht umstrukturieren, sondern konsolidieren** —
|
||||||
|
jeder Fakt bekommt genau ein Zuhause, Erledigtes verlässt die Arbeitskopie,
|
||||||
|
und ein neues Entscheidungs-Register (`docs/DECISIONS.md`) ersetzt die heute
|
||||||
|
über fünf Dateien verteilten Entscheidungs-Logs. Realistisches Ziel: **docs/ von
|
||||||
|
35 auf ~22 aktive Dateien, Gesamtbestand von ~9.400 auf ~6.500 Zeilen**, ohne
|
||||||
|
Wissensverlust (Git-Historie bleibt vollständig).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Aktueller Eindruck
|
||||||
|
|
||||||
|
### 2.1 Bestandsaufnahme
|
||||||
|
|
||||||
|
| Bereich | Dateien | Charakter |
|
||||||
|
|---|---:|---|
|
||||||
|
| Root (`README.md`, `CLAUDE.md`, `HOMELAB_ARCHITECTURE_MASTER_V2.md`) | 3 | Einstieg, KI-Regeln, Architektur-Master (502 Zeilen) |
|
||||||
|
| `docs/` flach | 31 | Runbooks, Inventare, Statuslisten, Pläne, Snapshots — gemischt |
|
||||||
|
| `docs/audit/` | 2 | Audit-Snapshots (Workstation-Audit, DR-Readiness) |
|
||||||
|
| `docs/runbooks/` | 1 | neue Konvention, erst ein Dokument (`komodo-bulk-deploy-dns.md`) |
|
||||||
|
| `ops/restore-tests/` | 14 | README, schedule, 6× plan.md, 4× runbook.md, Hilfsdoku |
|
||||||
|
| `ops/windows-reinstall/docs/` | 8 | Workstation-Neuaufsetzen-Projekt vom Mai 2026, weitgehend abgeschlossen |
|
||||||
|
| `ops/borg-ui/`, `ops/policy-checks/`, übrige `ops/` | 12 | Tool-Doku, teils mit historischen Audits und generierten Reports |
|
||||||
|
| `monitoring/`, `services/` | 2 | Stack-/Skript-README |
|
||||||
|
|
||||||
|
`memory/` und `.serena/` sind gitignored (Tool-Caches) und nicht Teil des Korpus.
|
||||||
|
|
||||||
|
### 2.2 Stärken (bewusst erhalten)
|
||||||
|
|
||||||
|
- `docs/README.md` als gepflegter Index mit expliziter Aktiv-vs.-Historie-Policy.
|
||||||
|
- `docs/REPO_MAP.md` enthält bereits eine Anti-Wildwuchs-Arbeitsregel ("Neue Doku nur, wenn dauerhaft als Runbook, Inventar oder Restliste gebraucht").
|
||||||
|
- `docs/MASTER_TODO.md` hat Status-Kategorien (Aktiv/Entscheidung/Geparkt/Blockiert) mit Review-Triggern — das ist Best Practice.
|
||||||
|
- Runbooks sind hochwertig: konkrete Kommandos, Erfolgskriterien, Rollback (z. B. `docs/GITOPS_DRIFT_RUNBOOK.md`, `docs/GUEST_IOT_NETWORK.md`).
|
||||||
|
- Inventare trennen sauber Ist-Werte von Entscheidungen (`docs/HARDWARE_INVENTORY.md` "Betreiber-Entscheidungen").
|
||||||
|
- Secret-Hygiene ist durchgängig: nur Namen/Pfade, nie Werte.
|
||||||
|
- Konsistente Verweis-Kultur ("Verwandte Dokumente"-Blöcke).
|
||||||
|
|
||||||
|
Das eigentliche Asset — die Doku-Disziplin — soll erhalten bleiben. Die Optimierung
|
||||||
|
zielt darauf, dass dieselbe Disziplin **weniger Schreibarbeit pro Ereignis** kostet.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Wichtigste Probleme
|
||||||
|
|
||||||
|
### 3.1 P1 — Mehrfachpflege: ein Fakt, viele Heimaten (Hauptproblem)
|
||||||
|
|
||||||
|
Gemessene Beispiele aus dem aktuellen Bestand:
|
||||||
|
|
||||||
|
| Sachverhalt | Anzahl Stellen | Fundorte |
|
||||||
|
|---|---:|---|
|
||||||
|
| Tailscale-Docker-Stack-Abbau (2026-06-06) | **9** | `CLAUDE.md` (Ausnahmen), `HOMELAB_ARCHITECTURE_MASTER_V2.md` (§7.1 + §10), `docs/SERVICE_CATALOG.md`, `docs/RESTORE_MATRIX.md`, `docs/DISASTER_RECOVERY.md` (Phase-4-Hinweis), `docs/NETWORK_INVENTORY.md`, `docs/MASTER_TODO.md` (Done-Log), `docs/AI_CONTEXT.md` |
|
||||||
|
| Veeam-Erstbackup `baerchen` (53,8 GB / 0:11:31) | **8** | `docs/AI_CONTEXT.md`, `docs/MASTER_TODO.md` (2×), `docs/WEEKEND_STATUS_2026-06-05.md` (2×), `docs/WEEKEND_EXECUTION_PLAN_2026-06-05.md`, `docs/RESTORE_MATRIX.md`, `docs/DISASTER_RECOVERY.md` §10, `ops/windows-reinstall/docs/windows-image-backup-baseline.md` |
|
||||||
|
| Leseliste / GitOps-Hierarchie | **7** | `README.md`, `CLAUDE.md`, `docs/AI_CONTEXT.md`, `docs/WORKFLOW.md`, `HOMELAB_ARCHITECTURE_MASTER_V2.md` (§11.4 + §12), `docs/README.md`, `docs/REPO_MAP.md` |
|
||||||
|
| DR-Workstation-Smoke (2026-06-06) | **6** | `docs/EXTERNAL_DEPENDENCIES.md` (Review-Log), `docs/AUDIT_2026-05-25_TODO.md`, `docs/MASTER_TODO.md`, `docs/AI_CONTEXT.md`, `docs/audit/dr-workstation-readiness-2026-06-06.md`, `docs/DR_WORKSTATION_SETUP.md` (Einschub Schritt 6) |
|
||||||
|
| Liste der dokumentierten Ausnahmen | **5** | `CLAUDE.md`, `docs/AI_CONTEXT.md`, `HOMELAB_ARCHITECTURE_MASTER_V2.md` §10 (autoritativ), `docs/SERVICE_CATALOG.md` (Spalten), `ops/policy-checks/` (kodiert) |
|
||||||
|
| Restore-Test-Status je Dienst | **4–5** | `docs/RESTORE_MATRIX.md` (Reifegrad-Tabelle), `docs/RESTORE_HANDBOOK.md` §3, `ops/restore-tests/README.md` (Status), Done-Logs in `MASTER_TODO`/`AUDIT_2026-05-25_TODO` |
|
||||||
|
| Komodo-Kaltstart | **3–4** | `docs/DISASTER_RECOVERY.md` Phase 4 Stufe 3, `docs/SERVICES_RECOVERY.md` Stufen A–F, `ops/restore-tests/komodo-bootstrap-runbook.md` (+ `-plan.md`) |
|
||||||
|
|
||||||
|
Ursache ist eine "Beleg-Kultur": jedes erledigte Ereignis wird als Nachweis in
|
||||||
|
alle thematisch berührten Dokumente kopiert, statt einmal dokumentiert und
|
||||||
|
verlinkt. Die Folge ist genau die Update-Kaskade, die `docs/WORKFLOW.md`
|
||||||
|
("Dokumentationspflicht": 7 Dateien prüfen pro Änderung) institutionalisiert.
|
||||||
|
|
||||||
|
### 3.2 P2 — Vier parallele Statuslisten plus verteilte Done-Logs
|
||||||
|
|
||||||
|
- `docs/MASTER_TODO.md` erklärt sich selbst zur führenden Liste — richtig.
|
||||||
|
- `docs/AUDIT_2026-05-25_TODO.md` bestätigt selbst, nur noch deckungsgleiche Restliste zu sein (1 offener Punkt); existiert faktisch nur als historische Hülle.
|
||||||
|
- `docs/WEEKEND_EXECUTION_PLAN_2026-06-05.md` + `docs/WEEKEND_STATUS_2026-06-05.md`: Sprint ist seit 2026-06-07 vorbei, alle Punkte erledigt; `WEEKEND_STATUS` nennt sich selbst "kurzlebig".
|
||||||
|
- `docs/AI_CONTEXT.md` führt einen eigenen Status-Block ("Aktuelle Restpunkte", "Letzte Bestaetigung", Zeilen 44–84), der `MASTER_TODO` dupliziert und bei jedem Ereignis mitgepflegt werden muss.
|
||||||
|
- Dazu eigene To-do-/Backlog-Abschnitte in `docs/DISASTER_RECOVERY.md` (§11), `docs/RESTORE_HANDBOOK.md` (§11), `docs/SERVICES_RECOVERY.md` ("Naechste Aufgaben" — alle erledigt), `docs/SERVICE_CATALOG.md` ("Bekannte offene Fragen").
|
||||||
|
- Done-Logs wachsen unbegrenzt: `MASTER_TODO` besteht zu ~60 % aus dem Erledigt-Block; `docs/EXTERNAL_DEPENDENCIES.md` trägt 11 Review-Zeilen, die dieselben Ereignisse erneut erzählen.
|
||||||
|
|
||||||
|
### 3.3 P3 — Restore-/DR-Wissen auf zu viele Schichten verteilt
|
||||||
|
|
||||||
|
Sechs `docs/`-Dateien (`DISASTER_RECOVERY`, `RESTORE_MATRIX`, `RESTORE_HANDBOOK`,
|
||||||
|
`SERVICES_RECOVERY`, `ROLLBACK`, `GITOPS_DRIFT_RUNBOOK`) plus 14 Dateien unter
|
||||||
|
`ops/restore-tests/`. Konkrete Überschneidungen:
|
||||||
|
|
||||||
|
- `docs/RESTORE_MATRIX.md` enthält ab Zeile 178 **eingebettete Runbook-Entwürfe** (Unraid-Flash, AdGuard, Tailscale, Redis) — dasselbe Genre, das unter `ops/restore-tests/*-runbook.md` bereits ein Zuhause hat. AdGuard und Redis sind dort inzwischen sogar als Skript automatisiert und validiert; die Matrix-Abschnitte sind damit doppelt.
|
||||||
|
- `docs/RESTORE_HANDBOOK.md` und `ops/restore-tests/README.md` beantworten zu ~80 % dieselben Fragen (Grundmuster, Verzeichnisse, Status je Dienst, Schnellstart) — zwei Pflegeorte für einen Prozess.
|
||||||
|
- Die `*-plan.md`-Dateien (6 Stück) waren Vor-Erstlauf-Planung; nach erfolgreichem Erstlauf sind Runbook + Skript die Wahrheit, die Pläne sind Historie (z. B. `gitea-plan.md` "Noch offen vor dem ersten echten Lauf" — der Lauf war am 2026-05-07).
|
||||||
|
- Restore-Kadenz steht dreifach: `RESTORE_HANDBOOK` §5, `ops/restore-tests/schedule.md`, `ops/restore-tests/unraid-user-scripts.md`.
|
||||||
|
|
||||||
|
### 3.4 P4 — Historische Snapshots leben als aktive Doku weiter
|
||||||
|
|
||||||
|
Trotz klarer Policy in `docs/README.md` ("Erledigte Audits, Chat-Handoffs ...
|
||||||
|
bleiben in der Git-Historie, aber nicht als dauerhafte Arbeitskopie"):
|
||||||
|
|
||||||
|
- `docs/DR_DRILL_2026-06-03.md` (392 Zeilen): Findings sind laut `AUDIT_2026-05-25_TODO` vollständig in DR.md/EXTERNAL_DEPENDENCIES eingearbeitet — reines Belegmaterial.
|
||||||
|
- `docs/audit/system-audit-2026-06-05.md` (229 Zeilen): Windows-Workstation-Audit, thematisch nicht einmal Homelab-Betrieb.
|
||||||
|
- `docs/audit/dr-workstation-readiness-2026-06-06.md`: automatisch erzeugter Check-Output inkl. Rohblöcken.
|
||||||
|
- `docs/WEEKEND_*_2026-06-05.md` (2 Dateien): abgeschlossener Sprint.
|
||||||
|
- `docs/KalliLab_CORE_Audit_2026-06-06.pdf` (untracked): Binär-Report im `docs/`-Ordner.
|
||||||
|
- `ops/borg-ui/BACKUP_AUDIT_STATUS_QUO.md` (Stand 2026-04-15): Vor-Migrations-Ist-Aufnahme, von `BACKUP_SCOPE.md` abgelöst.
|
||||||
|
- `ops/policy-checks/last-report.md`: **generierter** Report, eingecheckt — bei jedem Lauf entsteht Diff-Rauschen.
|
||||||
|
- `ops/windows-reinstall/docs/` (8 Dateien, ~1.400 Zeilen): Projekt Mai 2026 ist abgeschlossen; aktiv gebraucht wird davon im Betrieb nur `windows-image-backup-baseline.md` (Veeam-Restore-Runbook, von `RESTORE_MATRIX` referenziert) und ggf. `laufwerks-neustruktur-2026-06-04.md` als Soll-Referenz.
|
||||||
|
|
||||||
|
Es fehlt eine gelebte Archiv-Konvention — entweder konsequentes Löschen (Policy
|
||||||
|
existiert) oder ein sichtbares `docs/archive/`.
|
||||||
|
|
||||||
|
### 3.5 P5 — Architektur-Master vermischt Zielbild und Entscheidungs-Log
|
||||||
|
|
||||||
|
`HOMELAB_ARCHITECTURE_MASTER_V2.md` (502 Zeilen) ist Pflichtlektüre Nr. 1, trägt
|
||||||
|
aber in §13 ein unbegrenzt wachsendes Betriebs-/Entscheidungs-Log (FCP-Incident,
|
||||||
|
Plex-Reclaim-Erzählung, Digest-Pinning-Historie ...). Entscheidungen liegen
|
||||||
|
zusätzlich in `MASTER_TODO` (Geparkt-Tabelle mit Triggern),
|
||||||
|
`HARDWARE_INVENTORY` (Betreiber-Entscheidungen), `AUDIT_2026-05-25_TODO`
|
||||||
|
("Bewusst geparkt") und den Review-Logs der Inventare. Ein zentrales,
|
||||||
|
chronologisches Entscheidungs-Register (ADR-light) fehlt —
|
||||||
|
`docs/runbooks/komodo-bulk-deploy-dns.md` nennt sich bereits selbst
|
||||||
|
"Runbook / ADR-light" und zeigt den Bedarf.
|
||||||
|
|
||||||
|
### 3.6 P6 — Einstiegs-Redundanz
|
||||||
|
|
||||||
|
`README.md`, `CLAUDE.md`, `docs/AI_CONTEXT.md`, `docs/README.md`,
|
||||||
|
`docs/REPO_MAP.md`, `docs/WORKFLOW.md` (KI-Arbeitsregel) und
|
||||||
|
`HOMELAB_ARCHITECTURE_MASTER_V2.md` (§11/§12) wiederholen alle dieselben
|
||||||
|
Grundregeln (Quelle der Wahrheit, Leselisten, Ausnahmen) in leicht
|
||||||
|
unterschiedlichen Fassungen. Bei Regeländerungen müssen bis zu 7 Dateien
|
||||||
|
angefasst werden; die Leselisten weichen bereits leicht voneinander ab.
|
||||||
|
|
||||||
|
### 3.7 P7 — Flacher Namensraum mit gemischten Typen und Zielgruppen
|
||||||
|
|
||||||
|
In `docs/` liegen 31 Dateien flach nebeneinander: Familien-Doku
|
||||||
|
(`FAMILY_ONBOARDING.md`) neben Bare-Metal-DR, Statuslisten neben Inventaren,
|
||||||
|
Snapshots neben Dauer-Runbooks. Die begonnene Untergliederung
|
||||||
|
(`docs/runbooks/` mit 1 Datei, `docs/audit/` mit 2) ist inkonsistent: ~10
|
||||||
|
Runbook-artige Dokumente liegen weiter flach. Namensstile mischen sich
|
||||||
|
(`SCREAMING_SNAKE.md` vs. `homelab-optimierung.md` vs. `komodo-bulk-deploy-dns.md`).
|
||||||
|
|
||||||
|
### 3.8 P8 — Punktuelle Doppel-Dokumente
|
||||||
|
|
||||||
|
- `docs/H_DRIVE_NEARLINE_PULL.md` (Pull-Workflow + Befund-Historie) vs. neues, untracked `ops/h-drive-nearline/README.md` (Struktur + Betrieb + Aufräum-Historie) vs. H:/-Abschnitt in `docs/CAPACITY_AND_LIFECYCLE.md` — drei Orte für ein Thema.
|
||||||
|
- `ops/restore-tests/README.md` pflegt eine manuelle Datei-Auflistung des eigenen Verzeichnisses ("Geplante Struktur", ~35 Zeilen) — das Verzeichnis listet sich selbst.
|
||||||
|
- `ops/hermes-agent/README.md` (367 Zeilen) ist überwiegend "Phase 1 Documentation Analysis" für einen Dienst, der bis mindestens 2026-07-25 deaktiviert geparkt ist.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Best-Practice-Abgleich (Kurzfassung)
|
||||||
|
|
||||||
|
| Prinzip | Heute | Lücke |
|
||||||
|
|---|---|---|
|
||||||
|
| Single Source of Truth pro Fakt | Git als SSoT für Konfig ✅; für Doku-Fakten ❌ (6–9 Kopien) | Regel "ein Fakt, ein Zuhause" fehlt |
|
||||||
|
| Trennung Architektur / Runbook / Entscheidung / Status | teilweise; Mischformen wie `RESTORE_MATRIX` (Referenz + Runbooks + Status) und Master §13 | Dokumenttypen nicht explizit definiert |
|
||||||
|
| README als Einstieg | ✅ vorhanden und gut | nur Redundanz mit 6 weiteren Einstiegen |
|
||||||
|
| ADRs für Entscheidungen | verteilt auf 5 Orte | zentrales Register fehlt |
|
||||||
|
| Runbooks für Wiederholbares | ✅ stark | doppelt gepflegt (Matrix-Einbettungen, Handbook vs. README) |
|
||||||
|
| Kurze Dokumente statt Sammeldateien | gemischt; Master 502 Z., DR 400 Z., Matrix 261 Z. | Status-/Historien-Anteile aufblähen Kerndokumente |
|
||||||
|
| Archivierung Veralteter Inhalte | Policy existiert (`docs/README.md`) | wird nicht durchgesetzt; kein `archive/` |
|
||||||
|
| Namenskonventionen | de facto SCREAMING_SNAKE | nicht dokumentiert, neue Dateien weichen ab |
|
||||||
|
| Ownership / Aktualisierungsrhythmus | Ein-Operator-Modell, Review-Trigger teils vorhanden | kein definierter Doku-Review-Rhythmus |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Konkrete Verschlankungsvorschläge
|
||||||
|
|
||||||
|
Bewertungslegende: Mehrwert (niedrig/mittel/hoch/sehr hoch) · Aufwand
|
||||||
|
(klein/mittel/groß) · Risiko (niedrig/mittel/hoch) · Ü = Wirkung Übersichtlichkeit,
|
||||||
|
W = Wirkung Wartbarkeit (–/+/++/+++).
|
||||||
|
|
||||||
|
### 5.1 Statuslisten auf genau eine reduzieren
|
||||||
|
|
||||||
|
| Maßnahme | Mehrwert | Aufwand | Risiko | Ü | W |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| `WEEKEND_EXECUTION_PLAN_2026-06-05.md` + `WEEKEND_STATUS_2026-06-05.md` löschen/archivieren (Inhalt vollständig in `MASTER_TODO` Done-Log) | hoch | klein | niedrig | ++ | + |
|
||||||
|
| `AUDIT_2026-05-25_TODO.md` auflösen: den 1 offenen Punkt + "Bewusst geparkt" in `MASTER_TODO` übernehmen, Datei löschen | hoch | klein | niedrig | ++ | ++ |
|
||||||
|
| `AI_CONTEXT.md` Status-Block (Z. 44–84) streichen; nur Pointer "Authoritativ: `docs/MASTER_TODO.md`" behalten → Datei schrumpft auf ~35 Zeilen reine Regeln/Pointer | hoch | klein | niedrig | + | +++ |
|
||||||
|
| `MASTER_TODO` Done-Log auf die letzten ~5 Einträge begrenzen; ältere Einträge ersatzlos streichen (Git-Historie + Host-Reports sind der Beleg) | hoch | klein | niedrig | ++ | +++ |
|
||||||
|
| To-do-Restabschnitte in Detail-Dokumenten entfernen: `SERVICES_RECOVERY` "Naechste Aufgaben" (alles erledigt), `RESTORE_HANDBOOK` §11 → als Einzeiler nach `MASTER_TODO` | mittel | klein | niedrig | + | ++ |
|
||||||
|
|
||||||
|
### 5.2 Restore-/DR-Cluster konsolidieren
|
||||||
|
|
||||||
|
| Maßnahme | Mehrwert | Aufwand | Risiko | Ü | W |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| `RESTORE_MATRIX.md` auf Referenz reduzieren: eingebettete Runbook-Entwürfe (Z. 178–343) nach `ops/restore-tests/` verschieben bzw. löschen, wo Skript + Runbook schon existieren (AdGuard, Redis); Matrix behält nur Tier-Tabellen + Reifegrad | hoch | mittel | niedrig | ++ | ++ |
|
||||||
|
| `RESTORE_HANDBOOK.md` und `ops/restore-tests/README.md` zu **einem** Betriebsdokument zusammenführen (Empfehlung: `ops/restore-tests/README.md` als Zuhause, da Skripte dort liegen; `docs/README.md`-Index verlinkt) | hoch | mittel | niedrig | ++ | ++ |
|
||||||
|
| Die 6 `*-plan.md` unter `ops/restore-tests/` archivieren/löschen — Runbook + Skript sind seit den Erstläufen die Wahrheit | mittel | klein | niedrig | + | + |
|
||||||
|
| Restore-Status nur noch in der Reifegrad-Tabelle der `RESTORE_MATRIX` führen; `ops/restore-tests/README.md` "Status"-Abschnitt durch Link ersetzen | mittel | klein | niedrig | + | ++ |
|
||||||
|
| Komodo-Kaltstart: `SERVICES_RECOVERY.md` bleibt kanonisch (Stufen A–F); `DISASTER_RECOVERY.md` Phase 4 Stufe 3 auf Verweis + 3 Kern-Stolperfallen kürzen | mittel | klein | niedrig | + | ++ |
|
||||||
|
| `ROLLBACK.md`: abgeschlossene Service-Rollbacks (Uptime-Kuma, Grafana/InfluxDB-Altstack, BentoPDF/Stirling) streichen — Rollback-Pfade für entfernte Dienste gehören in die Git-Historie | mittel | klein | niedrig | + | + |
|
||||||
|
|
||||||
|
### 5.3 Entscheidungs-Register einführen (wichtigste strukturelle Maßnahme)
|
||||||
|
|
||||||
|
| Maßnahme | Mehrwert | Aufwand | Risiko | Ü | W |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| Neues `docs/DECISIONS.md` (ADR-light, eine Datei, neueste oben): Datum, Entscheidung, Kontext, Alternativen, Review-Trigger — je Eintrag 5–15 Zeilen | sehr hoch | mittel | niedrig | ++ | +++ |
|
||||||
|
| `HOMELAB_ARCHITECTURE_MASTER_V2.md` §13 dorthin migrieren; §9 (historische Migration) auf 3 Zeilen kürzen → Master schrumpft von 502 auf ~300 Zeilen reines Zielbild | sehr hoch | mittel | mittel* | +++ | +++ |
|
||||||
|
| Künftige Entscheidungen **nur noch** dort; `MASTER_TODO` "Geparkt" verlinkt auf DECISIONS-Einträge statt sie zu wiederholen | hoch | klein | niedrig | ++ | +++ |
|
||||||
|
|
||||||
|
*Risiko "mittel" nur, weil der Master Pflichtlektüre für alle Agenten ist —
|
||||||
|
Migration als ein sauberer Commit mit Verweis im Master ("Entscheidungs-Log:
|
||||||
|
siehe `docs/DECISIONS.md`") entschärft das vollständig.
|
||||||
|
|
||||||
|
### 5.4 Historisches archivieren
|
||||||
|
|
||||||
|
| Maßnahme | Mehrwert | Aufwand | Risiko | Ü | W |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| `docs/archive/` anlegen (oder konsequent löschen — Operator-Frage 1); dorthin: `DR_DRILL_2026-06-03.md`, `docs/audit/*` (beide), `HOME_ASSISTANT_INFLUXDB_ECOWITT.md` (selbst als archiviert markiert), Weekend-Dateien | hoch | klein | niedrig | +++ | ++ |
|
||||||
|
| `ops/windows-reinstall/docs/`: nur `windows-image-backup-baseline.md` (aktives Veeam-DR-Runbook) und `laufwerks-neustruktur-2026-06-04.md` (Soll-Referenz) bleiben aktiv; die übrigen 6 Dateien archivieren | mittel | klein | niedrig | ++ | + |
|
||||||
|
| `ops/borg-ui/BACKUP_AUDIT_STATUS_QUO.md` archivieren (`BACKUP_SCOPE.md` ist das aktive Zielbild) | mittel | klein | niedrig | + | + |
|
||||||
|
| `ops/policy-checks/last-report.md` aus Git entfernen und in `.gitignore` aufnehmen (generiertes Artefakt) | mittel | klein | niedrig | + | ++ |
|
||||||
|
| `docs/KalliLab_CORE_Audit_2026-06-06.pdf` nicht committen; Ablage auf Share/H: statt im GitOps-Repo | mittel | klein | niedrig | + | + |
|
||||||
|
|
||||||
|
### 5.5 Punktuelle Zusammenführungen
|
||||||
|
|
||||||
|
| Maßnahme | Mehrwert | Aufwand | Risiko | Ü | W |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| H:/-Thema: `ops/h-drive-nearline/README.md` (neu, derzeit untracked) committen und zur einzigen H:/-Doku machen; `docs/H_DRIVE_NEARLINE_PULL.md` auf Kurzverweis reduzieren oder auflösen; Befund-Historie 2026-05/06 → `DECISIONS.md` oder Git | mittel | klein | niedrig | + | ++ |
|
||||||
|
| `ops/restore-tests/README.md`: manuelle Datei-Auflistung ("Geplante Struktur") auf die 5 Einstiegs-Skripte kürzen | niedrig–mittel | klein | niedrig | + | + |
|
||||||
|
| `ops/hermes-agent/README.md` beim Hermes-Review (Deadline 2026-07-25) von 367 auf ~60 Zeilen Betriebs-README kürzen oder mit dem Stack entfernen | niedrig | klein | niedrig | + | + |
|
||||||
|
| Leselisten vereinheitlichen: `README.md` und `CLAUDE.md` behalten je **eine** Leseliste; `WORKFLOW`/`Master §12`/`AI_CONTEXT` verweisen nur noch darauf | mittel | klein | niedrig | + | ++ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Vorgeschlagene Zielstruktur
|
||||||
|
|
||||||
|
Bewusst **keine** Big-Bang-Umordnung: Massen-Verschiebungen brechen die
|
||||||
|
Querverweise in ~30 Dokumenten, die Pflicht-Leselisten in `CLAUDE.md` und die
|
||||||
|
Pfade im Host-Spiegel. Die Struktur bleibt erkennbar, wird aber dünner und
|
||||||
|
bekommt drei neue Sammelpunkte:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/ (unverändert)
|
||||||
|
├── README.md Einstieg, eine Leseliste
|
||||||
|
├── CLAUDE.md KI-Arbeitsregeln (verweist statt wiederholt)
|
||||||
|
├── HOMELAB_ARCHITECTURE_MASTER_V2.md nur noch Zielbild (~300 Z.)
|
||||||
|
├── docs/
|
||||||
|
│ ├── README.md Index (Pflicht, wie heute)
|
||||||
|
│ ├── MASTER_TODO.md EINZIGE Statusliste
|
||||||
|
│ ├── DECISIONS.md NEU: Entscheidungs-Register (ADR-light)
|
||||||
|
│ ├── AI_CONTEXT.md verschlankt: Regeln + Pointer, kein Status
|
||||||
|
│ ├── WORKFLOW.md / REPO_MAP.md unverändert
|
||||||
|
│ ├── SERVICE_CATALOG.md Referenz (unverändert)
|
||||||
|
│ ├── Inventare (6): HARDWARE_, NETWORK_, STORAGE_LAYOUT,
|
||||||
|
│ │ EXTERNAL_DEPENDENCIES, CAPACITY_, SECRETS_MAP
|
||||||
|
│ ├── Runbooks (flach, Bestand): DISASTER_RECOVERY, RESTORE_MATRIX (schlank),
|
||||||
|
│ │ SERVICES_RECOVERY, ROLLBACK, GITOPS_DRIFT_RUNBOOK,
|
||||||
|
│ │ GUEST_IOT_NETWORK, EXTERNAL_OPERATOR_RUNBOOK,
|
||||||
|
│ │ DR_WORKSTATION_SETUP, AUTHELIA_OIDC_PLAN,
|
||||||
|
│ │ FAMILY_ONBOARDING, RENOVATE, ALERT_RULES
|
||||||
|
│ ├── runbooks/ NEUE themenspezifische Runbooks (kebab-case),
|
||||||
|
│ │ Bestand bleibt wo er ist
|
||||||
|
│ └── archive/ NEU: abgeschlossene Snapshots/Drills/Audits
|
||||||
|
└── ops/<tool>/ Tool-Doku bleibt beim Tool (README + Runbook)
|
||||||
|
```
|
||||||
|
|
||||||
|
Netto-Effekt: `docs/` aktiv 35 → ~22 Dateien; Gesamtbestand ~74 → ~50 aktive
|
||||||
|
Dateien; geschätzt ~2.900 Zeilen weniger Pflegefläche.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Empfohlene Dokumenttypen
|
||||||
|
|
||||||
|
Jede Datei bekommt genau einen Typ (im Kopf deklariert):
|
||||||
|
|
||||||
|
| Typ | Zweck | Beispiele (Bestand) | Lebenszyklus |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **Einstieg/Index** | Navigation, Regeln | `README.md`, `docs/README.md`, `CLAUDE.md` | dauerhaft, klein halten |
|
||||||
|
| **Architektur/Zielbild** | Soll-Zustand, Prinzipien, Ausnahmen | `HOMELAB_ARCHITECTURE_MASTER_V2.md` | dauerhaft; Änderungen via DECISIONS begründet |
|
||||||
|
| **Inventar/Referenz** | Ist-Werte, Kataloge, Matrizen | `SERVICE_CATALOG`, `NETWORK_INVENTORY`, `RESTORE_MATRIX` | dauerhaft; nur Ist-Stand, keine Verlaufserzählung |
|
||||||
|
| **Runbook** | wiederholbare Abläufe mit Erfolgskriterium + Rollback | `GITOPS_DRIFT_RUNBOOK`, `DR_WORKSTATION_SETUP`, `ops/restore-tests/*-runbook.md` | dauerhaft; bei Ablösung archivieren |
|
||||||
|
| **Entscheidung (ADR-light)** | Was, warum, Alternativen, Review-Trigger | NEU: `docs/DECISIONS.md` | append-only, neueste oben |
|
||||||
|
| **Status/To-do** | offene Arbeit | `MASTER_TODO.md` (einzige Instanz) | lebend; Done-Einträge max. ~5 |
|
||||||
|
| **Snapshot/Beleg** | Audits, Drills, Sprint-Boards, Messungen | `DR_DRILL_*`, `audit/*`, `WEEKEND_*`, `mem-limits-baseline` | **befristet**: nach Einarbeitung → `archive/` oder löschen |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Merge-/Archivierungs-Kandidaten (Gesamtliste, priorisiert)
|
||||||
|
|
||||||
|
| # | Kandidat | Aktion | Prio |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | `docs/WEEKEND_EXECUTION_PLAN_2026-06-05.md`, `docs/WEEKEND_STATUS_2026-06-05.md` | löschen/archivieren | sofort |
|
||||||
|
| 2 | `docs/AUDIT_2026-05-25_TODO.md` | Rest in `MASTER_TODO` mergen, löschen | sofort |
|
||||||
|
| 3 | `docs/AI_CONTEXT.md` Z. 44–84 | streichen (Pointer auf MASTER_TODO) | sofort |
|
||||||
|
| 4 | `ops/policy-checks/last-report.md` | entgitten + `.gitignore` | sofort |
|
||||||
|
| 5 | `docs/KalliLab_CORE_Audit_2026-06-06.pdf` (untracked) | nicht committen, extern ablegen | sofort |
|
||||||
|
| 6 | `docs/DR_DRILL_2026-06-03.md`, `docs/audit/*` (2), `docs/HOME_ASSISTANT_INFLUXDB_ECOWITT.md` | → `docs/archive/` | Woche 1 |
|
||||||
|
| 7 | `ops/h-drive-nearline/README.md` + `docs/H_DRIVE_NEARLINE_PULL.md` | committen + zu einem Dokument | Woche 1 |
|
||||||
|
| 8 | `HOMELAB_ARCHITECTURE_MASTER_V2.md` §13 (+§9 kürzen) | → neues `docs/DECISIONS.md` | Woche 2 |
|
||||||
|
| 9 | `docs/ROLLBACK.md` historische Service-Abschnitte | streichen | Woche 2 |
|
||||||
|
| 10 | `docs/RESTORE_HANDBOOK.md` + `ops/restore-tests/README.md` | zu einem Dokument | Woche 3 |
|
||||||
|
| 11 | `docs/RESTORE_MATRIX.md` eingebettete Runbooks (Z. 178–343) | ausgliedern/löschen | Woche 3 |
|
||||||
|
| 12 | `ops/restore-tests/*-plan.md` (6) | archivieren/löschen | Woche 3 |
|
||||||
|
| 13 | `docs/SERVICES_RECOVERY.md` Done-Tabelle; `RESTORE_HANDBOOK` §11-Backlog | streichen / nach MASTER_TODO | Woche 3 |
|
||||||
|
| 14 | `ops/windows-reinstall/docs/` (6 von 8 Dateien) | archivieren | Woche 4 |
|
||||||
|
| 15 | `ops/borg-ui/BACKUP_AUDIT_STATUS_QUO.md` | archivieren | Woche 4 |
|
||||||
|
| 16 | `MASTER_TODO` Done-Log, `EXTERNAL_DEPENDENCIES` Review-Log | auf jüngste Einträge kürzen | Woche 4 |
|
||||||
|
| 17 | `ops/hermes-agent/README.md` | beim Hermes-Review 2026-07-25 kürzen/entfernen | später |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Empfohlene Namenskonventionen
|
||||||
|
|
||||||
|
1. **Bestand nicht umbenennen.** `SCREAMING_SNAKE.md` bleibt für die etablierte Kern-Doku in `docs/` — Renames erzeugen nur Link-Brüche ohne Informationsgewinn.
|
||||||
|
2. **Neue Dateien in Unterordnern in `kebab-case.md`** (so wie `docs/runbooks/komodo-bulk-deploy-dns.md` es bereits vormacht).
|
||||||
|
3. **Datum im Dateinamen nur für Snapshots** (`YYYY-MM-DD`), und Snapshots gehören nach `docs/archive/YYYY/`. Eine datierte Datei im `docs/`-Root ist künftig per Definition ein Aufräum-Kandidat.
|
||||||
|
4. **Kopfzeilen-Konvention** (3 Felder, eine Zeile, wie in diesem Dokument): `Typ: … · Stand: YYYY-MM-DD · Status: aktiv | geparkt (Trigger: …) | archiviert`. Viele Dokumente haben "Stand:" bereits — nur Typ/Status ergänzen.
|
||||||
|
5. **Archiv-Pfad:** `docs/archive/YYYY/<datum>-<thema>.md`, oben ein Einzeiler "Archiviert am …, abgelöst durch …".
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Minimale Doku-Regeln für die Zukunft
|
||||||
|
|
||||||
|
Vorschlag als Ersatz/Ergänzung der bestehenden Arbeitsregel in `docs/REPO_MAP.md`
|
||||||
|
(und Kurzfassung in `CLAUDE.md`):
|
||||||
|
|
||||||
|
1. **Ein Fakt, ein Zuhause.** Status → `MASTER_TODO`. Entscheidung → `DECISIONS`. Zielbild → Architektur/Inventar/Katalog. Ablauf → genau ein Runbook. Beleg → Host-Report (`/mnt/user/backups/restore-reports/`) oder Git-Commit. Alle anderen Stellen **verlinken**.
|
||||||
|
2. **Erledigt = raus aus der Arbeitskopie.** Abgeschlossene Pläne, Sprints, Audits und Drill-Reports wandern nach `docs/archive/` oder werden gelöscht — Git ist das Archiv (bestehende Policy aus `docs/README.md`, jetzt durchgesetzt).
|
||||||
|
3. **Neue Datei nur, wenn sie einem der 7 Typen aus Abschnitt 7 entspricht** — sonst ist es ein Eintrag in einer bestehenden Datei.
|
||||||
|
4. **Done-Einträge maximal 3 Zeilen.** Wer mehr Beleg braucht, verlinkt Commit oder Report. Done-Logs werden bei >5 Einträgen gekürzt.
|
||||||
|
5. **Snapshot-Dateien tragen ihr Ablaufdatum** ("Status: befristet bis …") und werden danach archiviert.
|
||||||
|
6. **Index-Pflicht bleibt:** jede neue/gelöschte Datei aktualisiert `docs/README.md` im selben Commit.
|
||||||
|
7. **Quartals-Gärtnern (15 min):** datierte Dateien im `docs/`-Root archivieren, Done-Logs kürzen, tote Links prüfen — passt zum bestehenden Quartals-Rhythmus (DR-Smoke, Restore-Drills).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 30-Tage-Plan
|
||||||
|
|
||||||
|
**Woche 1 — Quick Wins + Archiv-Fundament** (alles klein, risikolos):
|
||||||
|
Uncommitted Arbeitskopie klären (6 modifizierte Dateien, 2 untracked — deckt
|
||||||
|
sich mit `docs/homelab-optimierung.md` Empfehlung 9) · Kandidaten #1–#7 aus
|
||||||
|
Abschnitt 8 · `docs/archive/` anlegen.
|
||||||
|
|
||||||
|
**Woche 2 — Entscheidungs-Register:**
|
||||||
|
`docs/DECISIONS.md` anlegen (Vorlage: 5 Felder) · Master §13 migrieren, §9
|
||||||
|
kürzen, Verweis im Master setzen · `ROLLBACK.md` entschlacken · verstreute
|
||||||
|
"Bewusst geparkt"-Entscheidungen als DECISIONS-Einträge mit Review-Trigger
|
||||||
|
zusammenziehen.
|
||||||
|
|
||||||
|
**Woche 3 — Restore-Cluster:**
|
||||||
|
`RESTORE_HANDBOOK` ↔ `ops/restore-tests/README.md` zusammenführen ·
|
||||||
|
`RESTORE_MATRIX` auf Tabellen reduzieren, Runbook-Entwürfe ausgliedern ·
|
||||||
|
`*-plan.md` archivieren · Restore-Status auf einen Ort (Reifegrad-Tabelle).
|
||||||
|
|
||||||
|
**Woche 4 — Regeln verankern + Abschluss:**
|
||||||
|
Regeln aus Abschnitt 10 in `REPO_MAP.md`/`CLAUDE.md` einarbeiten · Leselisten
|
||||||
|
vereinheitlichen · `windows-reinstall`-Doku abschließen/archivieren ·
|
||||||
|
Done-/Review-Logs kürzen · `docs/README.md`-Index final neu aufbauen ·
|
||||||
|
dieses Dokument selbst nach `docs/archive/` verschieben (Regel 2 gilt auch hier).
|
||||||
|
|
||||||
|
Jeder Schritt ist ein eigener kleiner Commit → Rollback ist immer ein
|
||||||
|
`git revert`; keine produktiven Pfade, keine Compose-Dateien betroffen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Quick Wins unter 30 Minuten
|
||||||
|
|
||||||
|
| Quick Win | Wirkung |
|
||||||
|
|---|---|
|
||||||
|
| Weekend-Dateien (2) löschen | −161 Zeilen, eine Statusliste weniger |
|
||||||
|
| `AUDIT_2026-05-25_TODO.md` in `MASTER_TODO` auflösen | −57 Zeilen, Sync-Pflicht entfällt dauerhaft |
|
||||||
|
| `AI_CONTEXT` Status-Block streichen | KI-Kontext wird wartungsfrei |
|
||||||
|
| `last-report.md` entgitten + `.gitignore` | kein Diff-Rauschen pro Policy-Lauf |
|
||||||
|
| `docs/archive/` anlegen + 5 Snapshots verschieben | `docs/`-Root zeigt nur noch Aktives |
|
||||||
|
| `ops/h-drive-nearline/README.md` committen, `H_DRIVE_NEARLINE_PULL` zum Pointer machen | H:/-Thema hat ein Zuhause |
|
||||||
|
| PDF aus `docs/` entfernen (extern ablegen) | keine Binärdateien im GitOps-Repo |
|
||||||
|
| `MASTER_TODO` Done-Log auf 5 Einträge kürzen | −60 Zeilen in der führenden Liste |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Größere Aufräumprojekte (später, bewusst optional)
|
||||||
|
|
||||||
|
1. **Ordner-Restruktur `docs/{runbooks,inventory}/`** für den Bestand: nur angehen, wenn der flache Namensraum nach der Konsolidierung noch stört. Aufwand groß (Link-Churn in ~30 Dateien, `CLAUDE.md`-Leselisten, Host-Spiegel), Mehrwert nach der Verschlankung nur noch mittel, Risiko mittel.
|
||||||
|
2. **Doku-Linter im Policy-Check:** `ops/policy-checks/check_repo.ps1` um DOC-Checks erweitern — tote relative Links, datierte Dateien im `docs/`-Root, fehlende Typ/Stand-Kopfzeile. Passt zur bestehenden Check-Kultur; Aufwand mittel, Mehrwert hoch für die Dauerhaftigkeit der Regeln.
|
||||||
|
3. **Index-Generierung:** `docs/README.md`-Tabellen aus den Kopfzeilen generieren statt manuell pflegen. Nice-to-have für ein Ein-Personen-Lab; erst nach 2.
|
||||||
|
4. **Workstation-Doku entflechten:** prüfen, ob `baerchen`-Lifecycle-Doku (windows-reinstall, System-Audits) langfristig in ein eigenes Repo gehört; im Homelab-Repo bleibt nur das DR-relevante Veeam-Runbook. Mehrwert mittel, Aufwand mittel.
|
||||||
|
5. **Master-Diät Stufe 2:** Spalten-Überlappung zwischen Master §7-Tabellen und `SERVICE_CATALOG` reduzieren (Status/Netze doppelt). Vorsichtig angehen — beide sind Pflichtlektüre; erst nachdem DECISIONS etabliert ist.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Offene Fragen an den Operator
|
||||||
|
|
||||||
|
1. **Archivieren oder löschen?** `docs/archive/` macht Historie sichtbar, widerspricht aber der bestehenden "Git-Historie reicht"-Policy. Präferenz? (Empfehlung: `archive/` für Drill-/Audit-Belege mit Referenzwert, Löschen für Sprint-Boards und erledigte Pläne.)
|
||||||
|
2. **Wer konsumiert `docs/AI_CONTEXT.md`** außer Claude (Codex? Hermes? Gemini-Sessions)? Wenn nur Claude: mit `CLAUDE.md` zusammenlegen und eine Datei einsparen. Wenn mehrere: schlank behalten wie vorgeschlagen.
|
||||||
|
3. **`docs/audit/` als dauerhafte Konvention?** Sollen künftige Audit-Snapshots überhaupt ins Repo, oder reichen Host-Reports unter `/mnt/user/backups/restore-reports/` plus ein DECISIONS-/TODO-Eintrag?
|
||||||
|
4. **Folder-Restruktur (Projekt 13.1) gewünscht oder bewusst nie?** Eine klare Nein-Entscheidung wäre auch ein legitimer DECISIONS-Eintrag und beendet das Thema.
|
||||||
|
5. **Die 6 uncommitteten Doku-Änderungen** in der Arbeitskopie (u. a. `AI_CONTEXT`, `AUDIT_2026-05-25_TODO`, `WEEKEND_STATUS`, windows-reinstall-Dateien): committen oder verwerfen? Das sollte vor Umsetzung der Wochen-1-Schritte geklärt sein, damit Merges sauber bleiben.
|
||||||
|
6. **Soll `docs/WORKFLOW.md` "Dokumentationspflicht"** (7 Dateien pro Änderung prüfen) nach Einführung von Regel 1 ("ein Fakt, ein Zuhause") entsprechend verkürzt werden? Empfehlung: ja — die Prüfliste schrumpft auf "betroffenes Zuhause + Index".
|
||||||
+18
-12
@@ -45,12 +45,18 @@ Noch offen:
|
|||||||
- Manuelle Screenshots in `H:\Windows-Neuaufsetzen-Backup\14_Screenshots` ablegen.
|
- Manuelle Screenshots in `H:\Windows-Neuaufsetzen-Backup\14_Screenshots` ablegen.
|
||||||
- BitLocker-Status mit Adminrechten pruefen. **Nachlauf 2026-06-05:** Status
|
- BitLocker-Status mit Adminrechten pruefen. **Nachlauf 2026-06-05:** Status
|
||||||
wurde geprueft; C:/D:/E:/G:/H: sind `FullyDecrypted`, Protection `Off`.
|
wurde geprueft; C:/D:/E:/G:/H: sind `FullyDecrypted`, Protection `Off`.
|
||||||
Offen bleibt nur die bewusste BitLocker-Entscheidung.
|
**Entscheidung 2026-06-06:** BitLocker bleibt bewusst deaktiviert; Recovery
|
||||||
- Passwortmanager, 2FA-Recovery-Codes und Browser-Sync manuell pruefen.
|
laeuft ueber Veeam-Image, kein BitLocker-Key-Management.
|
||||||
- Banking4-Speicherort explizit pruefen.
|
- Passwortmanager, 2FA-Recovery-Codes und Browser-Sync manuell pruefen. **Erledigt 2026-06-06 laut Operator-Bestaetigung.**
|
||||||
- Banking4 im Programm selbst oeffnen und aktuellen Datentresor/Backup-Export bestaetigen. Der Key und der Datentresor sind bereits lokal auf H: gesichert.
|
- Banking4-Speicherort explizit pruefen. **Erledigt 2026-06-06 laut Operator-Bestaetigung.**
|
||||||
- WISO Steuer 2026 oeffnen und Lizenz/Buhl-Konto sowie Speicherorte der Steuerdateien bestaetigen.
|
- Banking4 im Programm selbst oeffnen und aktuellen Datentresor/Backup-Export bestaetigen. Der Key und der Datentresor sind bereits lokal auf H: gesichert. **Erledigt 2026-06-06 laut Operator-Bestaetigung.**
|
||||||
- Microsoft-Konto fuer M365 pruefen: Office-Webkonto/Abonnement, Installationsrecht, OneDrive-Sync.
|
- WISO Steuer 2026 oeffnen und Lizenz/Buhl-Konto sowie Speicherorte der Steuerdateien bestaetigen. **Erledigt 2026-06-06 laut Operator-Bestaetigung.**
|
||||||
|
- Microsoft-Konto fuer M365 pruefen: Office-Webkonto/Abonnement, Installationsrecht, OneDrive-Sync. **Erledigt 2026-06-06 laut Operator-Bestaetigung.**
|
||||||
|
- Technisches Nachinstallations-Inventar 2026-06-06 erledigt:
|
||||||
|
`baerchen-app-license-readiness-2026-06-06.md`. Banking4, WISO Steuer
|
||||||
|
2026, Microsoft 365/OneDrive und relevante Datenpfade sind lokal sichtbar;
|
||||||
|
Konto-/App-Oeffnen-Checks wurden am 2026-06-06 durch den Operator
|
||||||
|
bestaetigt ("alle Dienste laufen").
|
||||||
- Optional Keyfinder-Lauf durchfuehren und Ergebnisse lokal auf H: speichern.
|
- Optional Keyfinder-Lauf durchfuehren und Ergebnisse lokal auf H: speichern.
|
||||||
- `G:\Ollama` bewusst entscheiden: nicht gesichert, ca. 40,9 GB lokale Modell-/Cache-Daten.
|
- `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.
|
- D:, F: und G: vor dem spaeteren Loeschen noch einmal in Ruhe final bestaetigen.
|
||||||
@@ -464,7 +470,7 @@ Direkt nach der Installation:
|
|||||||
- Windows-Aktivierung prüfen
|
- Windows-Aktivierung prüfen
|
||||||
- Laufwerksbuchstaben sauber vergeben
|
- Laufwerksbuchstaben sauber vergeben
|
||||||
- Windows Defender und Firewall prüfen
|
- Windows Defender und Firewall prüfen
|
||||||
- BitLocker bewusst aktivieren oder deaktiviert lassen
|
- BitLocker bewusst deaktiviert lassen (Entscheidung 2026-06-06)
|
||||||
- Wiederherstellungspunkt erstellen
|
- Wiederherstellungspunkt erstellen
|
||||||
|
|
||||||
Basisprogramme:
|
Basisprogramme:
|
||||||
@@ -533,8 +539,8 @@ Zielstruktur:
|
|||||||
Status 2026-06-05: Diese Checkliste ist historisch fuer die Freigabe der
|
Status 2026-06-05: Diese Checkliste ist historisch fuer die Freigabe der
|
||||||
Neuinstallation. Die technische Neuinstallation, Laufwerksbereinigung,
|
Neuinstallation. Die technische Neuinstallation, Laufwerksbereinigung,
|
||||||
WinRE-Pruefung und Veeam-Baseline sind in neueren Dokumenten nachgezogen.
|
WinRE-Pruefung und Veeam-Baseline sind in neueren Dokumenten nachgezogen.
|
||||||
Als offene manuelle Pruefungen bleiben vor allem Passwortmanager/2FA,
|
Status 2026-06-06: Passwortmanager/2FA, Banking4, WISO und Microsoft/M365
|
||||||
Banking4, WISO und Microsoft/M365.
|
wurden durch den Operator bestaetigt ("alle Dienste laufen").
|
||||||
|
|
||||||
- [ ] Backup-Struktur auf H: erstellt
|
- [ ] Backup-Struktur auf H: erstellt
|
||||||
- [ ] Programmliste exportiert
|
- [ ] Programmliste exportiert
|
||||||
@@ -542,10 +548,10 @@ Banking4, WISO und Microsoft/M365.
|
|||||||
- [ ] Windows-Aktivierung dokumentiert
|
- [ ] Windows-Aktivierung dokumentiert
|
||||||
- [ ] Benutzerordner gesichert
|
- [ ] Benutzerordner gesichert
|
||||||
- [ ] Browser-Lesezeichen exportiert oder Sync geprüft
|
- [ ] Browser-Lesezeichen exportiert oder Sync geprüft
|
||||||
- [ ] Passwortmanager geprüft
|
- [x] Passwortmanager geprüft
|
||||||
- [ ] 2FA-Recovery-Codes gesichert
|
- [x] 2FA-Recovery-Codes gesichert
|
||||||
- [ ] SSH/API/GPG/Zertifikate gesichert
|
- [ ] SSH/API/GPG/Zertifikate gesichert
|
||||||
- [ ] Banking4-Speicherort geprüft und gesichert
|
- [x] Banking4-Speicherort geprüft und gesichert
|
||||||
- [ ] Homelab-/NAS-Doku gesichert
|
- [ ] Homelab-/NAS-Doku gesichert
|
||||||
- [ ] D:, F: und G: analysiert
|
- [ ] D:, F: und G: analysiert
|
||||||
- [ ] Unklare Daten nach `99_Unsortiert_von_D_F_G` kopiert
|
- [ ] Unklare Daten nach `99_Unsortiert_von_D_F_G` kopiert
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Archiv
|
||||||
|
|
||||||
|
Typ: Index · Stand: 2026-06-11 · Status: aktiv
|
||||||
|
|
||||||
|
Abgeschlossene Snapshots, Drills, Audits und abgeloeste Plaene mit Referenzwert.
|
||||||
|
Inhalte hier werden nicht mehr gepflegt; die fuehrenden Quellen stehen in der
|
||||||
|
Spalte "Abgeloest durch". Sprint-Boards und erledigte Arbeitslisten werden nicht
|
||||||
|
archiviert, sondern geloescht (Git-Historie ist das Archiv).
|
||||||
|
|
||||||
|
## 2026
|
||||||
|
|
||||||
|
| Datei | Was es war | Abgeloest durch / Ergebnis eingearbeitet in |
|
||||||
|
|---|---|---|
|
||||||
|
| `2026/BACKUP_AUDIT_STATUS_QUO_2026-04-15.md` | Ist-Aufnahme Backup vor der Borg-Migration | `ops/borg-ui/BACKUP_SCOPE.md` |
|
||||||
|
| `2026/DR_DRILL_2026-06-03.md` | DR-Tabletop-Drill, 23 Befunde | Doku-Fixes in `docs/DISASTER_RECOVERY.md` und `docs/EXTERNAL_DEPENDENCIES.md` |
|
||||||
|
| `2026/system-audit-baerchen-2026-06-05.md` | Read-only-Audit der Windows-Workstation | Befunde abgearbeitet bzw. Operator-Entscheidungen in `docs/DECISIONS.md` |
|
||||||
|
| `2026/dr-workstation-readiness-2026-06-06.md` | Automatischer Readiness-Check DR-Workstation | `docs/EXTERNAL_DEPENDENCIES.md` Abschnitt "DR-Workstation Bare-Metal-Kit" |
|
||||||
|
| `2026/HOME_ASSISTANT_INFLUXDB_ECOWITT.md` | Zielbild-Entwurf HA -> InfluxDB 3; HA existiert seit Crash nicht mehr | Neuaufbau braucht neue Inventur; Entwurf nur Referenz |
|
||||||
|
| `2026/windows-neuaufsetzen-masterplan.md` | Masterplan Windows-Neuaufsetzen Mai 2026 (abgeschlossen) | Aktiv bleibt nur `ops/windows-reinstall/` (Skripte, Veeam-Baseline, Laufwerksstruktur) |
|
||||||
|
| `2026/postdelta-2026-06-04.md` | PostDelta-Datenstand nach Neuinstallation | Projekt abgeschlossen |
|
||||||
|
| `2026/programme-entscheidung-2026-06-04.md` | Programm-Reinstall-Entscheidungen | Projekt abgeschlossen |
|
||||||
|
| `2026/boot-cleanup-plan-2026-06-04.md` | BCD-/Boot-Bereinigungsplan | Umgesetzt; Endzustand im System-Audit belegt |
|
||||||
|
| `2026/postinstall-erstes-ziel-codex.md` | Postinstall-Arbeitsauftrag | Projekt abgeschlossen |
|
||||||
|
| `2026/baerchen-app-license-readiness-2026-06-06.md` | App-/Lizenz-Readiness-Check | Projekt abgeschlossen |
|
||||||
|
| `2026/homelab-doku-optimierung-2026-06-11.md` | Analyse + Vorschlag zur Doku-Konsolidierung | umgesetzt 2026-06-11; Regeln in `docs/REPO_MAP.md`, Entscheidung in `docs/DECISIONS.md` |
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
# Homelab-Optimierung — Assessment 2026-06-10
|
||||||
|
|
||||||
|
Read-only-Analyse des Repos (Stand `master`, lokale Arbeitskopie 2026-06-10).
|
||||||
|
Keine produktiven Änderungen durchgeführt. Alle Empfehlungen sind Vorschläge
|
||||||
|
mit Rollback-Plan; nichts wurde deployed.
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
Das KalliLab-CORE-Homelab ist für ein Ein-Host-Setup ungewöhnlich reif:
|
||||||
|
GitOps mit Gitea+Komodo, sauberes Netzmodell (frontend/backend/app-intern),
|
||||||
|
Authelia mit 2FA-Catch-all, belegte Restore-Drills für alle Tier-1/2-Dienste,
|
||||||
|
Off-site-Borg nach Hetzner, DR-Workstation-Kit, Monitoring mit Prometheus/
|
||||||
|
Loki/Grafana/Alertmanager→ntfy. Die Doku-Disziplin ist das eigentliche Asset.
|
||||||
|
|
||||||
|
Die größten realen Lücken liegen nicht in der Architektur, sondern in der
|
||||||
|
**Container-Betriebsebene**: 20 von 30 Stacks haben keinen Healthcheck, kein
|
||||||
|
einziger Container hat Memory-/CPU-Limits, und mehrere Images laufen auf
|
||||||
|
mutablen Tags (`release`, `latest`, `:2`), bei denen Renovate-Digest-Bumps
|
||||||
|
faktisch unkontrollierte Versionssprünge sind — am kritischsten bei Immich.
|
||||||
|
Dazu kommen zwei strukturelle Risiken: **AdGuard ist DNS-SPOF ohne Fallback**
|
||||||
|
(hat bereits einen Teil-Deploy-Ausfall verursacht) und **Borg-Backups sind
|
||||||
|
vom Host aus löschbar** (append-only bewusst abgelehnt, aber die kostenlose
|
||||||
|
Kompensation — Hetzner-Storage-Box-Snapshots — ist nicht aktiviert).
|
||||||
|
|
||||||
|
## Gesamtbewertung
|
||||||
|
|
||||||
|
| Bereich | Note | Begründung |
|
||||||
|
|---|---|---|
|
||||||
|
| Architektur | **sehr gut** | klares Netzmodell, dokumentierte Ausnahmen, ein Ingress, Compose-first konsequent |
|
||||||
|
| Netzwerk/DNS/Proxy | **gut, ein SPOF** | Traefik v3 labelbasiert sauber; AdGuard+Unbound ohne zweiten Resolver — bekannter Vorfall (Bulk-Deploy-DNS-Ausfall, `docs/runbooks/komodo-bulk-deploy-dns.md`) |
|
||||||
|
| Container-Betrieb | **mittel** | 10/30 Stacks mit Healthcheck, 0 Ressourcen-Limits, mutable Tags hinter Digests versteckt |
|
||||||
|
| Storage/Backups | **sehr gut** | Borg→Hetzner, Dumps, H:/-Nearline, Restore-Drills mit Reports belegt; offen: Backup-Löschschutz |
|
||||||
|
| Security/Secrets | **gut** | `_FILE`/Stack-ENV konsequent, 2FA-Catch-all, WAN nur 443/tcp; `no-new-privileges` nur in 10/30 Stacks trotz P8-Pflichtregel |
|
||||||
|
| Monitoring/Alerting | **gut** | Prometheus/Blackbox/Loki/ntfy-Kette steht; Monitoring-Stack selbst hat keine Healthchecks und überwacht sich nicht selbst |
|
||||||
|
| Automatisierung/IaC | **sehr gut** | Komodo-Webhooks, Renovate, Posture-Check, Critical-Events-Watcher; manuelle Sync-Ausnahmen (traefik/dynamic, Authelia-Config) sind dokumentiert, aber fehleranfällig |
|
||||||
|
| Ausfallsicherheit | **bewusst begrenzt** | Ein Host, keine USV (geparkt Q3/2026), kein WAN-Failover — als akzeptiertes Risiko dokumentiert, das ist legitim |
|
||||||
|
| Strom/Kosten | **keine Daten** | keine Verbrauchsmessung im Repo sichtbar — siehe offene Fragen |
|
||||||
|
|
||||||
|
## Top 10 Verbesserungen nach Mehrwert
|
||||||
|
|
||||||
|
### 1. Immich vom `release`-Tag auf Versions-Tag pinnen
|
||||||
|
- **Beobachtung:** `apps/immich/docker-compose.yml:4` nutzt `immich-server:release@sha256:...` (ebenso ML). Renovate aktualisiert Digests — beim `release`-Tag ist ein "Digest-Update" in Wahrheit ein Major-/Minor-Versionssprung, ohne dass es im PR-Titel sichtbar wird. Immich ist berüchtigt für Breaking Changes zwischen Minors.
|
||||||
|
- **Warum relevant:** Ein gemergter "harmloser" Digest-PR kann Immich unangekündigt auf eine inkompatible Version heben (DB-Migrationen, ML-Modell-Wechsel).
|
||||||
|
- **Änderung:** Tag auf die konkret laufende Version umstellen (z. B. `immich-server:v2.x.y@sha256:<aktueller Digest>`), gleiche Vorgehensweise wie bei Mealie/Paperless. Laufende Version ermitteln: `docker exec immich_server cat /usr/src/app/package.json | grep version` oder Immich-UI → Version.
|
||||||
|
- **Verifikation:** Renovate erzeugt danach Versions-PRs statt stiller Digest-PRs; `docker inspect immich_server --format '{{.Config.Image}}'` zeigt den Versionstag.
|
||||||
|
- **Rollback:** Commit revert; Digest bleibt identisch, kein Redeploy-Zwang.
|
||||||
|
- **Nebenwirkungen:** keine zur Laufzeit (Digest unverändert). | Nutzen: **hoch** | Risiko: niedrig | Aufwand: klein | sofort
|
||||||
|
- Gleiches Muster prüfen für: `komodo:2`, `ddns-updater:latest`, `scrutiny:latest-omnibus`, `glances:latest-full` sowie tag-lose digest-only Images (`mail-archiver`, `borg-ui`, `ntfy` — Version im Compose unsichtbar).
|
||||||
|
|
||||||
|
### 2. Hetzner-Storage-Box-Snapshots als Ransomware-/Fehlbedienungsschutz aktivieren
|
||||||
|
- **Beobachtung:** Borg `append-only` wurde am 2026-06-01 bewusst verworfen (forced-command brach Key-Auth). Damit kann jeder mit dem Borg-Key (Host, borg-ui-Container mit `/local/secrets`-Mount) Archive **löschen** — ein kompromittierter Host vernichtet auch das Off-site-Backup.
|
||||||
|
- **Warum relevant:** Das ist die einzige verbliebene Lücke in einer sonst sehr guten Backup-Kette.
|
||||||
|
- **Änderung:** In der Hetzner-Robot-Konsole automatische Snapshots der Storage Box aktivieren (z. B. täglich, 7–14 Tage Retention). Snapshots sind host-seitig nicht löschbar und im Storage-Box-Preis enthalten.
|
||||||
|
- **Verifikation:** Robot-Konsole zeigt Snapshot-Liste; nach 2 Tagen: zwei Snapshots vorhanden. Restore-Probe: einzelne Datei aus Snapshot über das Snapshot-Verzeichnis lesen.
|
||||||
|
- **Rollback:** Snapshots deaktivieren — rein additiv, keine Auswirkung auf Borg.
|
||||||
|
- **Nebenwirkungen:** Snapshots zählen ggf. anteilig aufs Quota (aktuell 65 GB / 1 TB — viel Luft). | Nutzen: **sehr hoch** | Risiko: niedrig | Aufwand: klein (<30 min) | sofort
|
||||||
|
|
||||||
|
### 3. DNS-Fallback gegen den AdGuard-SPOF
|
||||||
|
- **Beobachtung:** AdGuard ist einziger LAN-Resolver. Der dokumentierte Vorfall (Bulk-Deploy: AdGuard-Recreate → Host ohne DNS → Komodo-Pulls scheitern) ist genau dieses Muster; das Runbook behandelt nur das Symptom.
|
||||||
|
- **Warum relevant:** Jeder AdGuard-Ausfall (Update, OOM, Disk) nimmt LAN + Host-DNS gleichzeitig mit — auch die Reparaturfähigkeit (Image-Pulls!) hängt daran.
|
||||||
|
- **Änderung (gestuft):**
|
||||||
|
- a) Host-Ebene: zweiten Nameserver (z. B. `1.1.1.1`) in der Unraid-Netzwerkkonfig als Fallback hinter `192.168.178.58` eintragen. Damit kann der Host immer Images pullen.
|
||||||
|
- b) LAN-Ebene: in der FRITZ!Box als zweiten lokalen DNS die FRITZ!Box selbst (oder einen Public DNS) hinterlegen — bewusster Trade-off: bei AdGuard-Down kein Ad-Blocking statt kein Internet.
|
||||||
|
- **Verifikation:** `docker stop adguard` im Wartungsfenster → `nslookup gitea.com` auf dem Host funktioniert weiterhin; danach `docker start adguard`.
|
||||||
|
- **Rollback:** Nameserver-Eintrag entfernen.
|
||||||
|
- **Nebenwirkungen:** DNS-Anfragen können am Filter vorbeilaufen, solange AdGuard down ist (gewollt); Fallback-Resolver sieht dann Anfragen (Privacy-Abwägung). | Nutzen: **hoch** | Risiko: niedrig | Aufwand: klein | diese Woche
|
||||||
|
|
||||||
|
### 4. Healthchecks für die App-Stacks nachrüsten
|
||||||
|
- **Beobachtung:** Nur 10 von 30 Compose-Dateien definieren Healthchecks (traefik, gitea, vaultwarden, authelia, postgresql17, redis, komodo, bentopdf, glances, hermes). **Ohne:** Nextcloud (App+DB+Redis), Immich (alle 4), Paperless, Mealie, Mail-Archiver, n8n, AdGuard, Unbound und der komplette Monitoring-Stack (11 Services).
|
||||||
|
- **Warum relevant:** Ohne Healthcheck meldet Docker "Up", auch wenn die App hängt; der Critical-Events-Watcher sieht nur `die`/`oom`, keine Hänger. Prometheus-Blackbox prüft nur HTTP-Routen von außen.
|
||||||
|
- **Änderung:** Pro Stack einen minimalen Healthcheck ergänzen, priorisiert: Nextcloud (`curl -f http://localhost/status.php`), Paperless, Mealie, n8n, Unbound (`drill @127.0.0.1 cloudflare.com` bzw. `unbound-control status`), AdGuard. Stackweise deployen, nicht als Bulk (siehe DNS-Runbook!).
|
||||||
|
- **Verifikation:** `docker ps --format '{{.Names}} {{.Status}}'` zeigt `(healthy)`; cAdvisor/Glance zeigen Health-Status.
|
||||||
|
- **Rollback:** Healthcheck-Block entfernen, Redeploy — kein Datenrisiko.
|
||||||
|
- **Nebenwirkungen:** Falsch kalibrierte Checks (zu kurze `start_period`) können Flapping erzeugen; konservativ starten (`interval: 60s`, `retries: 5`). | Nutzen: **hoch** | Risiko: niedrig | Aufwand: mittel | diesen Monat
|
||||||
|
|
||||||
|
### 5. Memory-Limits für die größten Verbraucher
|
||||||
|
- **Beobachtung:** Kein einziger Service hat `mem_limit`/`deploy.resources`. Auf einem Ein-Host-System konkurrieren ~50 Container; ein Speicherleck (Immich-ML, Nextcloud-PHP, Loki) kann den Host-OOM-Killer auslösen, der dann beliebige Tier-1-Container trifft (Postgres!).
|
||||||
|
- **Warum relevant:** Der OOM-Killer wählt nach Score, nicht nach Wichtigkeit. Limits machen den Blast-Radius deterministisch: die fehlerhafte App stirbt, nicht die Datenbank.
|
||||||
|
- **Änderung:** Erst messen: `docker stats --no-stream --format '{{.Name}}\t{{.MemUsage}}'` über ein paar Tage (oder cAdvisor-Dashboard `container_memory_working_set_bytes`). Dann Limits = Peak × 1,5 für die Top-5-Verbraucher (typisch: immich-ml, nextcloud, paperless, plex, prometheus) setzen.
|
||||||
|
- **Verifikation:** `docker inspect <c> --format '{{.HostConfig.Memory}}'`; Grafana-Panel Memory vs. Limit; keine neuen `oom`-Events im Critical-Events-Log.
|
||||||
|
- **Rollback:** Limit-Zeilen entfernen, Redeploy.
|
||||||
|
- **Nebenwirkungen:** Zu knappe Limits OOM-killen die App selbst — deshalb messen statt raten, und Limits nur bei unkritischen Apps zuerst. | Nutzen: **hoch** | Risiko: mittel | Aufwand: mittel | diesen Monat
|
||||||
|
|
||||||
|
### 6. `no-new-privileges` flächendeckend gemäß P8
|
||||||
|
- **Beobachtung:** Architektur-Regel P8 verlangt `no-new-privileges:true` standardmäßig; gesetzt ist es nur in 10 von 30 Stacks. Es fehlt u. a. bei allen Apps mit WAN-Exposition (Nextcloud, Immich, Paperless, Mealie, ntfy, n8n).
|
||||||
|
- **Warum relevant:** Billige Defense-in-Depth gegen Privilege-Escalation nach App-Kompromittierung — genau bei den exponierten Diensten am wertvollsten. Aktuell: dokumentierte Regel ≠ gelebter Stand (Policy-Drift).
|
||||||
|
- **Änderung:** `security_opt: ["no-new-privileges:true"]` in die fehlenden Stacks, stackweise mit Smoke-Test. Vorsicht bei Images mit s6/sudo-Setup (LSIO-Images wie speedtest/code-server haben es teils schon — prüfen) und bei Plex (Host-Netz, zuerst testen).
|
||||||
|
- **Verifikation:** `docker inspect <c> --format '{{.HostConfig.SecurityOpt}}'`; Posture-/Policy-Check erweitern, damit Drift künftig alarmiert.
|
||||||
|
- **Rollback:** Zeile entfernen, Redeploy.
|
||||||
|
- **Nebenwirkungen:** Container, die intern setuid brauchen (selten: einige Init-Systeme), starten nicht — fällt im Smoke-Test sofort auf. | Nutzen: mittel | Risiko: niedrig | Aufwand: mittel | diesen Monat
|
||||||
|
|
||||||
|
### 7. traefik/dynamic-Sync automatisieren statt manuell
|
||||||
|
- **Beobachtung:** `traefik/dynamic/*` (middlewares, tls, dashboards, plex) wird laut dokumentierter Ausnahme **manuell** auf den Host synchronisiert. Das ist die klassische Quelle für "Repo sagt A, Host macht B" — besonders heikel, weil hier Auth-Middlewares definiert sind.
|
||||||
|
- **Warum relevant:** Ein vergessener Sync nach einer Middleware-Änderung kann unbemerkt eine Schutzschicht im Live-Zustand alt lassen; auffallen würde es erst beim Audit.
|
||||||
|
- **Änderung:** Kleines Sync-Skript analog `services/authelia-diff.sh`: Repo-Spiegel `/mnt/user/services/homelab-infra/traefik/dynamic/` per `rsync --checksum --dry-run` gegen `/mnt/user/appdata/traefik/dynamic/` diffen; Diff ≠ leer → ntfy-Warnung über den bestehenden Posture-Check. (Stufe 2 optional: automatisch syncen; erst nur alarmieren.)
|
||||||
|
- **Verifikation:** Testweise eine Whitespace-Änderung im Repo-Spiegel → Posture-Check meldet `traefik_dynamic_drift`.
|
||||||
|
- **Rollback:** Check aus dem Posture-Skript entfernen; rein lesend, kein Produktionsrisiko.
|
||||||
|
- **Nebenwirkungen:** keine (read-only Check). | Nutzen: mittel | Risiko: niedrig | Aufwand: klein | diese Woche
|
||||||
|
|
||||||
|
### 8. Watchdog für den Monitoring-Stack selbst (Dead-Man's-Switch)
|
||||||
|
- **Beobachtung:** Die Alert-Kette ist Prometheus → Alertmanager → Bridge → ntfy. Fällt ein Glied (oder der ganze Monitoring-Stack) aus, kommen schlicht **keine** Alerts mehr — Stille ist nicht von "alles gut" unterscheidbar. Kein Healthcheck im Monitoring-Compose.
|
||||||
|
- **Warum relevant:** Das Monitoring überwacht alles außer sich selbst.
|
||||||
|
- **Änderung:** Dauerhaft feuernde `Watchdog`-Alert-Rule in `monitoring/prometheus/alerts.yml` + externen Heartbeat-Empfänger: einfachste Variante ist healthchecks.io (free) — Alertmanager-Route schickt den Watchdog alle 5 min an die Heartbeat-URL; bleibt er aus, alarmiert healthchecks.io per Mail/Push von außen.
|
||||||
|
- **Verifikation:** `docker stop monitoring-prometheus` im Wartungsfenster → externe Benachrichtigung nach ~10 min; danach Start.
|
||||||
|
- **Rollback:** Rule + Route entfernen.
|
||||||
|
- **Nebenwirkungen:** neue (kleine) externe Abhängigkeit — in `docs/EXTERNAL_DEPENDENCIES.md` eintragen. | Nutzen: **hoch** | Risiko: niedrig | Aufwand: klein | diese Woche
|
||||||
|
|
||||||
|
### 9. Lokale Arbeitskopie sauber halten (GitOps-Hygiene)
|
||||||
|
- **Beobachtung:** Die lokale Arbeitskopie hat aktuell 6 modifizierte Dateien und 2 untracked Artefakte (u. a. `docs/KalliLab_CORE_Audit_2026-06-06.pdf`, `ops/h-drive-nearline/README.md`), die nicht committed sind. Bei "Gitea = Quelle der Wahrheit" ist eine dauerhaft schmutzige Arbeitskopie ein Drift-Risiko (Änderungen gehen bei Pull-Konflikten verloren oder landen versehentlich in fremden Commits).
|
||||||
|
- **Warum relevant:** Genau die Drift-Klasse, vor der `docs/GITOPS_DRIFT_RUNBOOK.md` warnt — nur auf Ebene 2 (lokaler Clone) statt Ebene 4.
|
||||||
|
- **Änderung:** Modifizierte Doku-Dateien reviewen und committen oder verwerfen; PDF entweder committen (wenn es Referenz ist) oder in `.gitignore`/außerhalb des Repos ablegen; `ops/h-drive-nearline/README.md` committen.
|
||||||
|
- **Verifikation:** `git status` zeigt clean tree (bis auf bewusste Arbeit).
|
||||||
|
- **Rollback:** n/a (Aufräumarbeit). | Nutzen: mittel | Risiko: niedrig | Aufwand: klein (<30 min) | sofort
|
||||||
|
|
||||||
|
### 10. Doku-Drift-Fixes (klein, aber Vertrauensbasis)
|
||||||
|
- **Beobachtung:** `HOMELAB_ARCHITECTURE_MASTER_V2.md` nennt "Redis-Caches auf `redis:7.4-alpine` vereinheitlicht" — real laufen alle auf `redis:8.8.0-alpine`. Ebenso "PostgreSQL 17"-Pfade/Servicenamen bei PG 18 (letzteres ist dokumentiert bewusst, ersteres nicht).
|
||||||
|
- **Warum relevant:** Das Masterdokument ist laut eigener Regel die erste Lesepflicht für jeden (auch KI-)Eingriff; veraltete Fakten dort erzeugen falsche Entscheidungen.
|
||||||
|
- **Änderung:** Redis-Abschnitt in Sektion 13 auf 8.8 aktualisieren; bei Gelegenheit einen Mini-Check ins Posture-/Audit-Ritual: "stimmen Versionsangaben im Master noch?"
|
||||||
|
- **Verifikation:** `grep -n "7.4-alpine" HOMELAB_ARCHITECTURE_MASTER_V2.md` → leer.
|
||||||
|
- **Rollback:** trivial (Doku). | Nutzen: niedrig–mittel | Risiko: keiner | Aufwand: klein | sofort
|
||||||
|
|
||||||
|
## Top 5 Risiken (zuerst entschärfen)
|
||||||
|
|
||||||
|
1. **Löschbare Off-site-Backups** — Host-Kompromittierung oder ein falscher `borg delete` vernichtet auch Hetzner. → Empfehlung 2 (Snapshots). Bis dahin ist das DR-Konzept gegen Ransomware unvollständig.
|
||||||
|
2. **DNS-SPOF AdGuard** — bereits einmal real eingetreten (Teil-Deploy 2026-06); betrifft auch die Selbstheilungsfähigkeit (Image-Pulls). → Empfehlung 3.
|
||||||
|
3. **Verdeckte Versionssprünge via `release`/`latest`-Digest-Bumps** — v. a. Immich (DB-Migrationen!). → Empfehlung 1.
|
||||||
|
4. **OOM-Kaskade ohne Limits** — ein Leck in einer Tier-3-App kann Postgres killen. → Empfehlung 5. (Der Critical-Events-Watcher meldet das nur, verhindert es nicht.)
|
||||||
|
5. **Blinde Alert-Kette** — Monitoring-Ausfall = Stille statt Alarm. → Empfehlung 8.
|
||||||
|
|
||||||
|
Bewusst akzeptierte Risiken (USV geparkt, ein Host, kein WAN-Failover, kein
|
||||||
|
zweites Off-site-Ziel) sind dokumentiert und werden hier nicht erneut
|
||||||
|
aufgemacht — die Entscheidungen sind nachvollziehbar.
|
||||||
|
|
||||||
|
## Quick Wins unter 30 Minuten
|
||||||
|
|
||||||
|
| Quick Win | Wirkung | Kommando/Weg |
|
||||||
|
|---|---|---|
|
||||||
|
| Hetzner-Snapshots aktivieren | Backup-Löschschutz | Robot-Konsole → Storage Box → Snapshots (Empf. 2) |
|
||||||
|
| Host-DNS-Fallback eintragen | Selbstheilung bei AdGuard-Down | Unraid Settings → Network → DNS 2 = `1.1.1.1` (Empf. 3a) |
|
||||||
|
| Arbeitskopie aufräumen | GitOps-Hygiene | `git status`, committen/verwerfen (Empf. 9) |
|
||||||
|
| Redis-Doku-Drift fixen | Master-Doku wieder korrekt | Sektion 13 editieren (Empf. 10) |
|
||||||
|
| Memory-Baseline ziehen | Grundlage für Limits | `docker stats --no-stream` auf dem Host, Output archivieren |
|
||||||
|
| Watchdog-Rule anlegen | Vorbereitung Dead-Man's-Switch | `alerts.yml` + healthchecks.io-Account (Empf. 8) |
|
||||||
|
|
||||||
|
## 30-Tage-Optimierungsplan
|
||||||
|
|
||||||
|
**Woche 1 — Risiko-Entschärfung (alles klein):**
|
||||||
|
Hetzner-Snapshots (Empf. 2) · Host-DNS-Fallback + Stop/Start-Test (Empf. 3a) ·
|
||||||
|
Immich-Tag-Pinning (Empf. 1) · Arbeitskopie aufräumen (Empf. 9) ·
|
||||||
|
Memory-Baseline starten.
|
||||||
|
|
||||||
|
**Woche 2 — Beobachtbarkeit:**
|
||||||
|
Dead-Man's-Switch produktiv (Empf. 8) · traefik/dynamic-Drift-Check in den
|
||||||
|
Posture-Check (Empf. 7) · Healthchecks für Nextcloud, Paperless, Mealie, n8n
|
||||||
|
(Empf. 4, stackweise).
|
||||||
|
|
||||||
|
**Woche 3 — Hardening:**
|
||||||
|
`no-new-privileges` für alle WAN-exponierten Apps (Empf. 6) · Healthchecks
|
||||||
|
für AdGuard/Unbound/Monitoring-Kern · restliche Mutable-Tag-Kandidaten pinnen
|
||||||
|
(komodo, scrutiny, glances, ddns-updater, tag-lose digest-only Images).
|
||||||
|
|
||||||
|
**Woche 4 — Stabilität:**
|
||||||
|
Memory-Limits aus der Baseline für die Top-5-Verbraucher (Empf. 5) ·
|
||||||
|
FRITZ!Box-DNS-Fallback-Entscheidung (Empf. 3b) · Doku nachziehen
|
||||||
|
(Master Sektion 13, SERVICE_CATALOG, dieses Dokument abhaken).
|
||||||
|
|
||||||
|
## Größere Projekte mit hohem Nutzen (später)
|
||||||
|
|
||||||
|
- **End-to-end-DR-Drill** sobald zweite Hardware existiert (bereits geplant,
|
||||||
|
bleibt der wertvollste offene Beweis).
|
||||||
|
- **Strom-/Kostentransparenz:** smarte Steckdose mit Messfunktion (z. B.
|
||||||
|
Shelly Plug S) vor den Unraid-Host, Werte via Home Assistant → InfluxDB 3 →
|
||||||
|
Grafana. Erst messen, dann ggf. optimieren (Spindown-Policy, CPU-Governor).
|
||||||
|
Messbarkeit: W-Dauerlast und kWh/Monat als Grafana-Panel.
|
||||||
|
- **USV-Review Q3/2026** wie geparkt — nach Strommessung lässt sich die
|
||||||
|
USV-Dimensionierung direkt ableiten.
|
||||||
|
- **Renovate-Policy verfeinern:** Digest-PRs für mutable Tags entweder
|
||||||
|
abschalten oder mit Warn-Label versehen, damit Befund 1 strukturell nicht
|
||||||
|
zurückkommt.
|
||||||
|
|
||||||
|
## Konkrete Verifikationskommandos (Sammlung, alle read-only)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Health-Status aller Container
|
||||||
|
docker ps --format '{{.Names}}\t{{.Status}}' | sort
|
||||||
|
|
||||||
|
# Memory-Baseline
|
||||||
|
docker stats --no-stream --format '{{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}' | sort -k3 -hr | head -15
|
||||||
|
|
||||||
|
# Welche Container ohne no-new-privileges laufen
|
||||||
|
docker ps -q | xargs docker inspect --format '{{.Name}} {{.HostConfig.SecurityOpt}}' | grep -v no-new-privileges
|
||||||
|
|
||||||
|
# Effektive Image-Referenzen (mutable Tags erkennen)
|
||||||
|
docker ps --format '{{.Names}}\t{{.Image}}' | grep -E 'latest|release|:2$|:[0-9]+$'
|
||||||
|
|
||||||
|
# DNS-Fallback-Test (Wartungsfenster!)
|
||||||
|
docker stop adguard && nslookup gitea.com && docker start adguard
|
||||||
|
|
||||||
|
# Borg-Snapshot-Gegenprobe (nach Aktivierung, von der Storage Box)
|
||||||
|
ssh -p 23 u565255@u565255.your-storagebox.de ls .snapshots/ 2>/dev/null || echo "via Robot-Konsole prüfen"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rollback-Hinweise (generell)
|
||||||
|
|
||||||
|
- Jede Compose-Änderung: Revert-Commit nach Gitea pushen → Komodo deployed
|
||||||
|
den Vorzustand; Datenpfade bleiben unberührt (alle Empfehlungen hier sind
|
||||||
|
config-only, keine Daten-/Volume-Migrationen).
|
||||||
|
- Healthchecks/Limits/security_opt: Zeilen entfernen + Redeploy genügt.
|
||||||
|
- Host-DNS/FRITZ!Box-Einträge: Eintrag löschen, sofort wirksam.
|
||||||
|
- Hetzner-Snapshots und Dead-Man's-Switch sind rein additiv.
|
||||||
|
- Nichts in diesem Dokument erfordert `push --force`, History-Rewrite oder
|
||||||
|
Löschoperationen auf Datenpfaden.
|
||||||
|
|
||||||
|
## Offene Fragen an den Operator
|
||||||
|
|
||||||
|
1. **Strom:** Gibt es eine Messung des Host-Verbrauchs (W idle/last)? Ohne
|
||||||
|
Zahl ist der Bereich Kosten/Strom nicht bewertbar. → Shelly/Messsteckdose?
|
||||||
|
2. **RAM-Ausstattung des Hosts:** Wie viel RAM hat Kallilabcore gesamt und
|
||||||
|
wie ist die aktuelle Auslastung (`free -h`)? Bestimmt, wie aggressiv
|
||||||
|
Memory-Limits sinnvoll sind.
|
||||||
|
3. **Renovate-Verhalten gewollt?** Sollen Digest-Bumps auf `release`/`latest`
|
||||||
|
weiter automatisch als PRs kommen, oder ist die Pinning-Strategie aus
|
||||||
|
Empfehlung 1 die gewünschte Linie für alle Stacks?
|
||||||
|
4. **healthchecks.io o. ä. als externe Abhängigkeit akzeptabel?** Alternativ
|
||||||
|
ginge ein ntfy-basierter Heartbeat von einem zweiten Gerät (z. B. dem
|
||||||
|
Gaming-PC per Scheduled Task) — null neue Cloud-Abhängigkeit.
|
||||||
|
5. **FRITZ!Box-DNS-Fallback (3b):** Filterlücke bei AdGuard-Down akzeptieren
|
||||||
|
oder lieber nur den Host-Fallback (3a) umsetzen?
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# Runbook: Komodo Bulk-Deploy schlaegt mit DNS `connection refused` fehl
|
||||||
|
|
||||||
|
Stand: 2026-06-11 · Typ: Runbook / ADR-light · Status: **Sofortmassnahme aktiv** (Host-DNS-Fallback gesetzt 2026-06-11 bzw. frueher)
|
||||||
|
|
||||||
|
## Symptom
|
||||||
|
|
||||||
|
Ein Bulk-Merge (z. B. Renovate-Sammel-PR) loest gleichzeitig viele Komodo-Stack-Webhooks aus. Komodo startet parallele `DeployStack`. Nur ein Teil der Stacks deployt, der Rest bleibt auf dem alten Image. In der Deploy-Stufe **Compose Pull** stehen Fehler wie:
|
||||||
|
|
||||||
|
```
|
||||||
|
Get "https://registry-1.docker.io/v2/": dial tcp: lookup registry-1.docker.io
|
||||||
|
on 192.168.178.58:53: read udp ...->192.168.178.58:53: read: connection refused
|
||||||
|
```
|
||||||
|
|
||||||
|
Manuelles Re-Deploy der betroffenen Stacks danach funktioniert (AdGuard ist dann wieder oben).
|
||||||
|
|
||||||
|
## Ursache
|
||||||
|
|
||||||
|
Der Host nutzt **AdGuard Home als einzigen Resolver** (`/etc/resolv.conf` = nur `nameserver 192.168.178.58`, keine `/etc/docker/daemon.json`). AdGuard laeuft selbst als Container auf dem Host und bindet `0.0.0.0:53`. Wird der `adguard`-Stack im selben Batch neu deployt, faellt Port 53 fuer Sekunden aus. Alle parallelen `docker compose pull` der anderen Stacks koennen `registry-1.docker.io` dann nicht aufloesen -> `connection refused` -> Deploy `success=false`.
|
||||||
|
|
||||||
|
Es ist **kein** Webhook-, Auth- oder Docker-Hub-Rate-Limit-Problem: Webhooks authentifizieren sauber, `webhook_enabled=true`, Fehlerbild ist `connection refused` auf den eigenen DNS-Port direkt nach AdGuard-Recreate. Fuer den Pull-Pfad zaehlt der Docker-Daemon/Go-Resolver (iteriert ueber die `resolv.conf`-Server und springt bei Socket-Fehlern zum naechsten), nicht der glibc-Client.
|
||||||
|
|
||||||
|
## Sofortmassnahme (Schicht 1) — umgesetzt
|
||||||
|
|
||||||
|
Unraid -> Settings -> Network Settings -> `eth0`:
|
||||||
|
|
||||||
|
- DNS server 1: `192.168.178.58` (AdGuard)
|
||||||
|
- **DNS server 2: `192.168.178.1`** (FritzBox) — **gesetzt und aktiv** (Operator-Bestaetigung 2026-06-11; Apply-Button erfordert Docker-/VM-Stop, der gespeicherte Wert greift bereits ohne Re-Apply)
|
||||||
|
|
||||||
|
Damit ueberleben Registry-Pulls einen kurzen AdGuard-Ausfall via Resolver-Failover. Im Normalbetrieb wird weiter DNS1 (AdGuard) genutzt, der Filter bleibt aktiv.
|
||||||
|
|
||||||
|
Pruefen / Bedingungen:
|
||||||
|
|
||||||
|
- **Kein `options rotate`** in `/etc/resolv.conf` (sonst dauerhafter Filter-Bypass). Aktuell nicht gesetzt; nach Apply erneut pruefen.
|
||||||
|
- Router muss oeffentliche Namen **selbst** aufloesen und nicht intern an AdGuard zurueckleiten.
|
||||||
|
- Hinweis zur Verifikation: Ein `nslookup registry-1.docker.io 192.168.178.1` bei laufendem AdGuard ist ein gutes Signal, aber **kein letzter Beweis**. Wasserdicht: AdGuard kurz stoppen und `dig @192.168.178.1 registry-1.docker.io`, oder FritzBox-Upstream / AdGuard-Querylog pruefen.
|
||||||
|
|
||||||
|
Rollback: DNS server 2 leeren + Apply.
|
||||||
|
|
||||||
|
## Betriebsregel (Schicht 2)
|
||||||
|
|
||||||
|
- **AdGuard und Unbound nicht gemeinsam mit abhaengigen Stacks im Bulk deployen.** DNS-Infrastruktur immer separat / einzeln deployen, nicht waehrend 20+ parallele Pulls laufen.
|
||||||
|
- Renovate-PRs gestaffelt mergen (eine Etappe pro Deploy) statt Sammel-Merge. Deckt dieses Problem fuer den Normalbetrieb bereits ab.
|
||||||
|
|
||||||
|
## Spaeter optional
|
||||||
|
|
||||||
|
- Komodo-Deploys serialisieren: statt vieler paralleler Stack-Webhooks eine **Procedure** (sequenzielle Stages) oder **Resource Sync** mit `after`-Ordering. Trifft die Ursache direkter, ist aber ein groesserer Umbau und **kein Renovate-Blocker**.
|
||||||
|
- Host-DNS vom AdGuard-Container entkoppeln (AdGuard eigene IP via macvlan, Host-Resolver auf Router/Unbound), damit `:53` am Host nicht exklusiv am Container-Lifecycle haengt.
|
||||||
|
|
||||||
|
## Verworfen
|
||||||
|
|
||||||
|
- `/etc/docker/daemon.json` mit `"dns": [...]`: wirkt nur fuer Container-DNS, nicht fuer Daemon-eigene Image-Pulls.
|
||||||
|
- AdGuard `network_mode: host`: beim Recreate ist der DNS-Prozess trotzdem weg; macht aus dem Single Point of Failure keinen HA-Resolver.
|
||||||
|
|
||||||
|
## Referenzen
|
||||||
|
|
||||||
|
- Diagnose-Zugriff: SSH `root@192.168.178.58`; Komodo-Mongo (`docker exec komodo-mongo`, DB `komodo`, Collections `Stack`/`Update`); Gitea SQLite `/data/gitea/gitea.db` (Tabelle `webhook`, `repo_id=3`).
|
||||||
|
- Verwandt: `docs/WORKFLOW.md` (DNS-Regeln fuer Container), `docs/GITOPS_DRIFT_RUNBOOK.md`.
|
||||||
|
</content>
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
# Smart-Home Bootstrap
|
||||||
|
|
||||||
|
Ziel: Den Stack `smart-home/` auf Kallilabcore initial startklar machen, ohne
|
||||||
|
Secrets oder UI-State ins Git zu schreiben.
|
||||||
|
|
||||||
|
## 1. Fachrepo auf dem Host bereitstellen
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd /mnt/user/services
|
||||||
|
git clone https://git.kaleschke.info/Micha/smart-home-kalli.git smart-home-kalli
|
||||||
|
cd smart-home-kalli
|
||||||
|
git checkout main
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Home-Assistant-Container mountet daraus einzelne YAML-Dateien read-only nach
|
||||||
|
`/config`.
|
||||||
|
|
||||||
|
## 2. Home-Assistant-Appdata vorbereiten
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir -p /mnt/user/appdata/homeassistant
|
||||||
|
cp /mnt/user/services/smart-home-kalli/secrets-template/secrets.yaml.example \
|
||||||
|
/mnt/user/appdata/homeassistant/secrets.yaml
|
||||||
|
cp /mnt/user/services/smart-home-kalli/secrets-template/trusted_proxies.yaml.example \
|
||||||
|
/mnt/user/appdata/homeassistant/trusted_proxies.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Danach `trusted_proxies.yaml` auf das echte Traefik-/`frontend_net`-Subnetz
|
||||||
|
anpassen:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker network inspect frontend_net
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Mosquitto vorbereiten
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir -p /mnt/user/appdata/mosquitto/config \
|
||||||
|
/mnt/user/appdata/mosquitto/data \
|
||||||
|
/mnt/user/appdata/mosquitto/log
|
||||||
|
|
||||||
|
docker run --rm -it \
|
||||||
|
-v /mnt/user/appdata/mosquitto/config:/mosquitto/external_config \
|
||||||
|
eclipse-mosquitto:2.0.22 \
|
||||||
|
mosquitto_passwd -c /mosquitto/external_config/passwordfile homeassistant
|
||||||
|
|
||||||
|
cat > /mnt/user/appdata/mosquitto/config/aclfile <<'EOF'
|
||||||
|
user homeassistant
|
||||||
|
topic readwrite #
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Das initiale Passwort anschliessend in
|
||||||
|
`/mnt/user/appdata/homeassistant/secrets.yaml` eintragen. LAN-Port `1883` bleibt
|
||||||
|
in Phase 1 geschlossen.
|
||||||
|
|
||||||
|
## 4. Stack deployen
|
||||||
|
|
||||||
|
Komodo-Stack:
|
||||||
|
|
||||||
|
- Repo: `homelab-infra`
|
||||||
|
- Pfad: `smart-home/docker-compose.yml`
|
||||||
|
- Branch: nach Review `master`
|
||||||
|
- Status 2026-06-13: Stack `smart-home` existiert in Komodo, Gitea-Webhook ist
|
||||||
|
aktiv, `deployed_hash == latest_hash`.
|
||||||
|
|
||||||
|
Nach dem Start pruefen:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker ps --filter name=homeassistant
|
||||||
|
docker ps --filter name=smarthome-mosquitto
|
||||||
|
docker logs --tail=100 homeassistant
|
||||||
|
docker logs --tail=100 smarthome-mosquitto
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Smoke-Test
|
||||||
|
|
||||||
|
- `https://home.kaleschke.info` zeigt die Home-Assistant-Oberflaeche.
|
||||||
|
- Nach Owner-Onboarding: keine Authelia-ForwardAuth mehr vor HA; HA nutzt native
|
||||||
|
Auth plus `http.ip_ban_enabled`.
|
||||||
|
- `trusted_proxies.yaml` deckt das `frontend_net` ab; damit wertet HA die echte
|
||||||
|
Client-IP aus `X-Forwarded-For` aus.
|
||||||
|
- Keine Trusted-Proxy-Fehler im HA-Log.
|
||||||
|
- MQTT-Broker-Smoke: `homeassistant`-User aus `secrets.yaml` kann gegen
|
||||||
|
`smarthome-mosquitto:1883` publish/subscriben.
|
||||||
|
- HA-MQTT-Integration ist verbunden: Config-Entry `smarthome-mosquitto` ist
|
||||||
|
`loaded`, Mosquitto sieht einen HA-Client mit User `homeassistant`.
|
||||||
|
- HA-native Backup-Erstellung funktioniert; Beispielartefakt:
|
||||||
|
`/mnt/user/appdata/homeassistant/backups/Custom_backup_2026.6.1_2026-06-13_08.25_38034438.tar`.
|
||||||
|
- Backup-Artefakt ist lesbar (`backup.json`, `homeassistant.tar.gz`).
|
||||||
|
- Agent-API-Tokens liegen als Host-Secrets unter
|
||||||
|
`/mnt/user/appdata/secrets/ha_token_codex` und
|
||||||
|
`/mnt/user/appdata/secrets/ha_token_claude`; Werte nie ausgeben oder in Git
|
||||||
|
schreiben. Die Tokens sind nur mit erhaltenem HA-Auth-State in `.storage`
|
||||||
|
brauchbar und bei Verdacht in HA zu widerrufen.
|
||||||
|
|
||||||
|
## 6. Fachrepo-Update
|
||||||
|
|
||||||
|
Das Fachrepo `/mnt/user/services/smart-home-kalli` ist kein eigener
|
||||||
|
Komodo-Stack. Aenderungen wirken erst nach diesem Host-Ablauf:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd /mnt/user/services/smart-home-kalli
|
||||||
|
git pull --ff-only origin main
|
||||||
|
docker compose -f /mnt/user/services/stacks/smart-home/smart-home/docker-compose.yml \
|
||||||
|
up -d --force-recreate homeassistant
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Force-Recreate ist Pflicht, weil `configuration.yaml`, `automations.yaml`,
|
||||||
|
`scripts.yaml` und `scenes.yaml` als Einzeldateien in den Container gemountet
|
||||||
|
werden. Nach einem `git pull` kann Docker sonst noch den alten Datei-Inode sehen
|
||||||
|
(`Stale file handle`).
|
||||||
|
|
||||||
|
## 7. UI-Editor-Politik
|
||||||
|
|
||||||
|
`automations.yaml`, `scripts.yaml` und `scenes.yaml` sind read-only aus Git
|
||||||
|
gemountet. Der Home-Assistant-UI-Editor fuer diese Dateien ist deshalb nicht der
|
||||||
|
primaere Schreibweg. Automationen und Scripts werden in Git gepflegt; UI-State
|
||||||
|
und Integrations-State bleiben in `.storage` und werden per Borg gesichert.
|
||||||
|
|
||||||
|
## 8. Abnahmebedingung
|
||||||
|
|
||||||
|
Vor produktiven Energie-Automationen muss ein Restore-Test fuer
|
||||||
|
`/mnt/user/appdata/homeassistant`, `/mnt/user/appdata/mosquitto` und den Clone
|
||||||
|
`/mnt/user/services/smart-home-kalli` dokumentiert sein.
|
||||||
|
|
||||||
|
Wichtig: Ein erfolgreich erzeugtes HA-Backup ist nur die Voraussetzung. Das Gate
|
||||||
|
ist erst geschlossen, wenn eine Restore-Probe in einem isolierten Testpfad
|
||||||
|
dokumentiert ist.
|
||||||
|
|
||||||
|
Status 2026-06-13: Gate geschlossen. Die isolierte Restore-Probe war
|
||||||
|
erfolgreich:
|
||||||
|
|
||||||
|
- Report: `/mnt/user/backups/restore-reports/homeassistant-2026-06-13.md`
|
||||||
|
- Test: HA-native Backup + Mosquitto-Appdata + Fachrepo-Clone
|
||||||
|
- Ergebnis: HA HTTP/API/check_config gruen, MQTT Publish/Subscribe und retained
|
||||||
|
Topic nach Broker-Restart gruen
|
||||||
|
|
||||||
|
Status 2026-06-13: HA-MQTT-Integration ist produktiv verbunden.
|
||||||
|
|
||||||
|
Verifikation:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
TOKEN=$(cat /mnt/user/appdata/secrets/ha_token_codex)
|
||||||
|
curl -ksS -H "Authorization: Bearer $TOKEN" \
|
||||||
|
https://home.kaleschke.info/api/config/config_entries/entry
|
||||||
|
docker logs --tail=120 smarthome-mosquitto
|
||||||
|
docker exec homeassistant python -m homeassistant --script check_config --config /config
|
||||||
|
```
|
||||||
|
|
||||||
|
Erwartung: Ein MQTT-Config-Entry `smarthome-mosquitto` mit Status `loaded`, ein
|
||||||
|
Mosquitto-Client mit User `homeassistant`, und `check_config` ohne Fehler.
|
||||||
|
|
||||||
|
## 9. SolarEdge lokal
|
||||||
|
|
||||||
|
Status 2026-06-13: SolarEdge ist lokal per Modbus TCP angebunden.
|
||||||
|
|
||||||
|
- Integration: HACS/Custom `solaredge_modbus_multi` v3.2.5
|
||||||
|
- HA-Config-Entry: `SolarEdge Local`, Status `loaded`
|
||||||
|
- Wechselrichter: `192.168.178.111:1502`
|
||||||
|
- Modbus Device-ID: `1`
|
||||||
|
- Optionen: Polling 60 Sekunden, Meter-Erkennung aktiv, Batterie-Erkennung
|
||||||
|
aktiv, Extras aus, Power-Control aus
|
||||||
|
|
||||||
|
Verifikation:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
TOKEN=$(cat /mnt/user/appdata/secrets/ha_token_codex)
|
||||||
|
curl -ksS -H "Authorization: Bearer $TOKEN" \
|
||||||
|
https://home.kaleschke.info/api/config/config_entries/entry
|
||||||
|
curl -ksS -H "Authorization: Bearer $TOKEN" \
|
||||||
|
https://home.kaleschke.info/api/states
|
||||||
|
docker exec homeassistant python -m homeassistant --script check_config --config /config
|
||||||
|
```
|
||||||
|
|
||||||
|
Wichtige Energy-Dashboard-Kandidaten:
|
||||||
|
|
||||||
|
- PV-Produktion: `sensor.solaredge_local_i1_ac_energy`
|
||||||
|
- Netzbezug: `sensor.solaredge_local_i1_m1_ac_energy_imported`
|
||||||
|
- Einspeisung: `sensor.solaredge_local_i1_m1_ac_energy_exported`
|
||||||
|
- Batterie geladen: `sensor.solaredge_local_i1_b1_energy_import`
|
||||||
|
- Batterie entladen: `sensor.solaredge_local_i1_b1_energy_export`
|
||||||
|
- Batterie-SoC: `sensor.solaredge_local_i1_b1_state_of_energy`
|
||||||
|
|
||||||
|
Nach der Integration wurde ein HA-native Backup erzeugt und tar-geprueft:
|
||||||
|
`/mnt/user/appdata/homeassistant/backups/Custom_backup_2026.6.1_2026-06-13_14.59_48645373.tar`.
|
||||||
|
|
||||||
|
Trade-off: Dieser Pfad ist lokal und liefert Inverter, Meter und Batterie ohne
|
||||||
|
Cloud-API, nutzt aber eine Custom-Integration. Bei HA-Core-Upgrades auf Warnungen
|
||||||
|
zu `solaredge_modbus_multi` achten.
|
||||||
|
|
||||||
|
## 10. Energy Dashboard
|
||||||
|
|
||||||
|
Status 2026-06-13: Energy Dashboard ist ueber die Home-Assistant-WebSocket-API
|
||||||
|
konfiguriert und validiert.
|
||||||
|
|
||||||
|
Konfiguration:
|
||||||
|
|
||||||
|
- Netz: Bezug `sensor.solaredge_local_i1_m1_ac_energy_imported`, Einspeisung
|
||||||
|
`sensor.solaredge_local_i1_m1_ac_energy_exported`
|
||||||
|
- PV: Produktion `sensor.solaredge_local_i1_ac_energy`, Live-Leistung
|
||||||
|
`sensor.solaredge_local_i1_ac_power`
|
||||||
|
- Speicher: Entladung `sensor.solaredge_local_i1_b1_energy_export`, Ladung
|
||||||
|
`sensor.solaredge_local_i1_b1_energy_import`, SoC
|
||||||
|
`sensor.solaredge_local_i1_b1_state_of_energy`
|
||||||
|
- Kosten/Preise: noch nicht gesetzt; folgt mit Tibber
|
||||||
|
|
||||||
|
Verifikation:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
TOKEN=$(cat /mnt/user/appdata/secrets/ha_token_codex)
|
||||||
|
# WebSocket: energy/get_prefs und energy/validate
|
||||||
|
sed -n '1,260p' /mnt/user/appdata/homeassistant/.storage/energy
|
||||||
|
```
|
||||||
|
|
||||||
|
Erwartung: `.storage/energy` enthaelt drei Quellen (`grid`, `solar`,
|
||||||
|
`battery`), und `energy/validate` meldet keine Issues.
|
||||||
|
|
||||||
|
Nach der Energy-Konfiguration wurde ein HA-native Backup erzeugt und
|
||||||
|
tar-geprueft:
|
||||||
|
`/mnt/user/appdata/homeassistant/backups/Custom_backup_2026.6.1_2026-06-13_15.59_25670583.tar`.
|
||||||
|
|
||||||
|
Naechster Schritt: Tibber per HA-UI-Config-Flow verbinden und danach Kosten im
|
||||||
|
Energy Dashboard ergaenzen.
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
services:
|
|
||||||
tailscale:
|
|
||||||
image: tailscale/tailscale:stable@sha256:25cde9ad76020b0e29229136d0c38b5962e9a0e1774ffac9b0df68e4a37d6cf0
|
|
||||||
container_name: Tailscale-Docker
|
|
||||||
restart: unless-stopped
|
|
||||||
network_mode: host
|
|
||||||
|
|
||||||
cap_add:
|
|
||||||
- NET_ADMIN
|
|
||||||
- NET_RAW
|
|
||||||
|
|
||||||
security_opt:
|
|
||||||
- no-new-privileges:true
|
|
||||||
|
|
||||||
devices:
|
|
||||||
- /dev/net/tun:/dev/net/tun
|
|
||||||
|
|
||||||
environment:
|
|
||||||
- TZ=Europe/Berlin
|
|
||||||
- TS_HOSTNAME=kallilab-core
|
|
||||||
- TS_STATE_DIR=/state
|
|
||||||
- TS_AUTH_ONCE=true
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
- /mnt/user/appdata/tailscale:/state
|
|
||||||
@@ -4,13 +4,16 @@ services:
|
|||||||
container_name: monitoring-prometheus
|
container_name: monitoring-prometheus
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command:
|
command:
|
||||||
- --config.file=/etc/prometheus/prometheus.yml
|
- --config.file=/etc/prometheus/config/prometheus.yml
|
||||||
- --storage.tsdb.path=/prometheus
|
- --storage.tsdb.path=/prometheus
|
||||||
- --storage.tsdb.retention.time=30d
|
- --storage.tsdb.retention.time=30d
|
||||||
- --web.enable-lifecycle
|
- --web.enable-lifecycle
|
||||||
volumes:
|
volumes:
|
||||||
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
# Verzeichnis-Mount statt Einzeldatei: auf dem Unraid-FUSE-Share (/mnt/user)
|
||||||
- ./prometheus/alerts.yml:/etc/prometheus/alerts.yml:ro
|
# bricht ein Einzeldatei-Bind-Mount bei git/Komodo-Updates zu
|
||||||
|
# "Stale NFS file handle" (Inode-Wechsel) -> Reload laedt 0 Regeln, nur
|
||||||
|
# --force-recreate heilt. Directory-Inode ist stabil, Reload reicht wieder.
|
||||||
|
- ./prometheus:/etc/prometheus/config:ro
|
||||||
- prometheus_data:/prometheus
|
- prometheus_data:/prometheus
|
||||||
networks:
|
networks:
|
||||||
- monitoring_net
|
- monitoring_net
|
||||||
@@ -66,15 +69,18 @@ services:
|
|||||||
image: prom/blackbox-exporter:v0.28.0@sha256:e753ff9f3fc458d02cca5eddab5a77e1c175eee484a8925ac7d524f04366c2fc
|
image: prom/blackbox-exporter:v0.28.0@sha256:e753ff9f3fc458d02cca5eddab5a77e1c175eee484a8925ac7d524f04366c2fc
|
||||||
container_name: monitoring-blackbox-exporter
|
container_name: monitoring-blackbox-exporter
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
# Use AdGuard so *.kaleschke.info resolves to the internal Traefik IP.
|
||||||
|
# External resolvers (1.1.1.1/8.8.8.8) return the public WAN IP, which
|
||||||
|
# causes hairpin-NAT timeouts when probing from inside the Docker network.
|
||||||
dns:
|
dns:
|
||||||
- 1.1.1.1
|
- 172.23.0.3
|
||||||
- 8.8.8.8
|
|
||||||
command:
|
command:
|
||||||
- --config.file=/etc/blackbox_exporter/blackbox.yml
|
- --config.file=/etc/blackbox_exporter/blackbox.yml
|
||||||
volumes:
|
volumes:
|
||||||
- ./blackbox/blackbox.yml:/etc/blackbox_exporter/blackbox.yml:ro
|
- ./blackbox/blackbox.yml:/etc/blackbox_exporter/blackbox.yml:ro
|
||||||
networks:
|
networks:
|
||||||
- monitoring_net
|
- monitoring_net
|
||||||
|
- dns_net
|
||||||
expose:
|
expose:
|
||||||
- "9115"
|
- "9115"
|
||||||
security_opt:
|
security_opt:
|
||||||
@@ -129,6 +135,20 @@ services:
|
|||||||
GF_USERS_ALLOW_SIGN_UP: "false"
|
GF_USERS_ALLOW_SIGN_UP: "false"
|
||||||
GF_AUTH_ANONYMOUS_ENABLED: "false"
|
GF_AUTH_ANONYMOUS_ENABLED: "false"
|
||||||
GF_PLUGINS_PREINSTALL_DISABLED: "true"
|
GF_PLUGINS_PREINSTALL_DISABLED: "true"
|
||||||
|
# --- Authelia OIDC SSO (2026-06-06) ---
|
||||||
|
GF_AUTH_GENERIC_OAUTH_ENABLED: "true"
|
||||||
|
GF_AUTH_GENERIC_OAUTH_NAME: Authelia
|
||||||
|
GF_AUTH_GENERIC_OAUTH_CLIENT_ID: grafana
|
||||||
|
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET__FILE: /run/secrets/grafana_oidc_client_secret
|
||||||
|
GF_AUTH_GENERIC_OAUTH_SCOPES: "openid profile email groups"
|
||||||
|
GF_AUTH_GENERIC_OAUTH_AUTH_URL: https://auth.kaleschke.info/api/oidc/authorization
|
||||||
|
GF_AUTH_GENERIC_OAUTH_TOKEN_URL: https://auth.kaleschke.info/api/oidc/token
|
||||||
|
GF_AUTH_GENERIC_OAUTH_API_URL: https://auth.kaleschke.info/api/oidc/userinfo
|
||||||
|
GF_AUTH_GENERIC_OAUTH_USE_PKCE: "true"
|
||||||
|
GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP: "true"
|
||||||
|
# Proof: alle OIDC-Logins als Admin; spaeter ueber groups verfeinern
|
||||||
|
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH: "'Admin'"
|
||||||
|
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_STRICT: "false"
|
||||||
entrypoint:
|
entrypoint:
|
||||||
- /bin/sh
|
- /bin/sh
|
||||||
- -c
|
- -c
|
||||||
@@ -145,6 +165,7 @@ services:
|
|||||||
secrets:
|
secrets:
|
||||||
- monitoring_grafana_admin_password
|
- monitoring_grafana_admin_password
|
||||||
- monitoring_grafana_influxdb_token
|
- monitoring_grafana_influxdb_token
|
||||||
|
- grafana_oidc_client_secret
|
||||||
expose:
|
expose:
|
||||||
- "3000"
|
- "3000"
|
||||||
security_opt:
|
security_opt:
|
||||||
@@ -160,7 +181,8 @@ services:
|
|||||||
- traefik.http.routers.monitoring-grafana.entrypoints=websecure
|
- traefik.http.routers.monitoring-grafana.entrypoints=websecure
|
||||||
- traefik.http.routers.monitoring-grafana.tls=true
|
- traefik.http.routers.monitoring-grafana.tls=true
|
||||||
- traefik.http.routers.monitoring-grafana.tls.certresolver=le
|
- traefik.http.routers.monitoring-grafana.tls.certresolver=le
|
||||||
- traefik.http.routers.monitoring-grafana.middlewares=authelia@file,secure-headers@file
|
# ForwardAuth bewusst entfernt 2026-06-06: Grafana macht jetzt eigenes OIDC-SSO gegen Authelia
|
||||||
|
- traefik.http.routers.monitoring-grafana.middlewares=secure-headers@file
|
||||||
- traefik.http.services.monitoring-grafana.loadbalancer.server.port=3000
|
- traefik.http.services.monitoring-grafana.loadbalancer.server.port=3000
|
||||||
|
|
||||||
grafana-dashboard-importer:
|
grafana-dashboard-importer:
|
||||||
@@ -332,6 +354,12 @@ services:
|
|||||||
- --data-dir=/var/lib/influxdb3/data
|
- --data-dir=/var/lib/influxdb3/data
|
||||||
- --plugin-dir=/var/lib/influxdb3/plugins
|
- --plugin-dir=/var/lib/influxdb3/plugins
|
||||||
- --admin-token-file=/run/secrets/influxdb3_admin_token
|
- --admin-token-file=/run/secrets/influxdb3_admin_token
|
||||||
|
# InfluxDB 3 Core kompaktiert Parquet-Dateien nicht (nur Enterprise).
|
||||||
|
# HA schreibt viele Sensoren haeufig -> Tabellen wie "°C"/"%"/"hPa" liefen
|
||||||
|
# ins Default-Limit von 432 Dateien/Query ("No data" in Grafana).
|
||||||
|
# Stopgap: Limit anheben. Langfristig: Enterprise (Auto-Compaction, frei
|
||||||
|
# fuer Home) oder weniger/seltener nach InfluxDB schreiben.
|
||||||
|
- --query-file-limit=20000
|
||||||
volumes:
|
volumes:
|
||||||
- /mnt/user/appdata/influxdb3/data:/var/lib/influxdb3/data
|
- /mnt/user/appdata/influxdb3/data:/var/lib/influxdb3/data
|
||||||
- /mnt/user/appdata/influxdb3/plugins:/var/lib/influxdb3/plugins
|
- /mnt/user/appdata/influxdb3/plugins:/var/lib/influxdb3/plugins
|
||||||
@@ -351,6 +379,8 @@ networks:
|
|||||||
driver: bridge
|
driver: bridge
|
||||||
frontend_net:
|
frontend_net:
|
||||||
external: true
|
external: true
|
||||||
|
dns_net:
|
||||||
|
external: true
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
prometheus_data:
|
prometheus_data:
|
||||||
@@ -364,5 +394,7 @@ secrets:
|
|||||||
file: /mnt/user/appdata/secrets/monitoring_grafana_admin_password.txt
|
file: /mnt/user/appdata/secrets/monitoring_grafana_admin_password.txt
|
||||||
monitoring_grafana_influxdb_token:
|
monitoring_grafana_influxdb_token:
|
||||||
file: /mnt/user/appdata/secrets/monitoring_grafana_influxdb_token.txt
|
file: /mnt/user/appdata/secrets/monitoring_grafana_influxdb_token.txt
|
||||||
|
grafana_oidc_client_secret:
|
||||||
|
file: /mnt/user/appdata/secrets/grafana_oidc_client_secret
|
||||||
influxdb3_admin_token:
|
influxdb3_admin_token:
|
||||||
file: /mnt/user/appdata/secrets/influxdb3_admin_token.json
|
file: /mnt/user/appdata/secrets/influxdb3_admin_token.json
|
||||||
|
|||||||
@@ -0,0 +1,204 @@
|
|||||||
|
{
|
||||||
|
"uid": "ha-solar-pv",
|
||||||
|
"title": "Solar PV System",
|
||||||
|
"tags": ["solar", "solaredge", "homeassistant", "energy"],
|
||||||
|
"timezone": "browser",
|
||||||
|
"schemaVersion": 39,
|
||||||
|
"version": 1,
|
||||||
|
"refresh": "30s",
|
||||||
|
"time": { "from": "now-24h", "to": "now" },
|
||||||
|
"templating": { "list": [] },
|
||||||
|
"annotations": { "list": [] },
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Power",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 11, "w": 12, "x": 0, "y": 0 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": { "unit": "kwatt", "custom": { "drawStyle": "line", "fillOpacity": 30, "lineWidth": 1, "showPoints": "never" } },
|
||||||
|
"overrides": [
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "displayName", "value": "Solar Produktion" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#73bf69" } } ] },
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "displayName", "value": "Strom Verbrauch" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#fade2a" } } ] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "calcs": ["lastNotNull"] }, "tooltip": { "mode": "multi" } },
|
||||||
|
"targets": [
|
||||||
|
{ "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kW\" WHERE entity_id = 'solaredge_pv_live_power' AND $__timeFilter(time) ORDER BY time" },
|
||||||
|
{ "refId": "B", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kW\" WHERE entity_id = 'kallihome_live_load_power' AND $__timeFilter(time) ORDER BY time" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"title": "Aktuelle Solar Produktion",
|
||||||
|
"type": "bargauge",
|
||||||
|
"gridPos": { "h": 4, "w": 6, "x": 12, "y": 0 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "kwatt", "min": 0, "max": 8, "color": { "mode": "continuous-GrYlRd" } }, "overrides": [] },
|
||||||
|
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "displayMode": "lcd", "orientation": "horizontal", "showUnfilled": true },
|
||||||
|
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kW\" WHERE entity_id = 'solaredge_pv_live_power' AND $__timeFilter(time) ORDER BY time" } ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"title": "Strom Produziert (Heute)",
|
||||||
|
"type": "gauge",
|
||||||
|
"gridPos": { "h": 7, "w": 6, "x": 18, "y": 0 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "kwatth", "min": 0, "max": 50, "color": { "mode": "continuous-GrYlRd" } }, "overrides": [] },
|
||||||
|
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "showThresholdMarkers": false },
|
||||||
|
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kWh\" WHERE entity_id = 'solaredge_pv_energy_today' AND $__timeFilter(time) ORDER BY time" } ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"title": "Produktion und Verbrauch kWh",
|
||||||
|
"type": "bargauge",
|
||||||
|
"gridPos": { "h": 7, "w": 6, "x": 12, "y": 4 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": { "unit": "kwatth", "min": 0, "color": { "mode": "continuous-GrYlRd" } },
|
||||||
|
"overrides": [
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "displayName", "value": "Solar Produktion" } ] },
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "displayName", "value": "Netzbezug" } ] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "displayMode": "lcd", "orientation": "horizontal", "showUnfilled": true },
|
||||||
|
"targets": [
|
||||||
|
{ "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kWh\" WHERE entity_id = 'solaredge_pv_energy_today' AND $__timeFilter(time) ORDER BY time" },
|
||||||
|
{ "refId": "B", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kWh\" WHERE entity_id = 'solaredge_grid_import_today' AND $__timeFilter(time) ORDER BY time" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"title": "Tages Produktion 30 Tage Übersicht",
|
||||||
|
"type": "barchart",
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 11 },
|
||||||
|
"timeFrom": "30d",
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "kwatth", "color": { "mode": "continuous-GrYlRd" }, "custom": { "fillOpacity": 80, "gradientMode": "scheme", "lineWidth": 1 } }, "overrides": [] },
|
||||||
|
"options": { "orientation": "vertical", "showValue": "never", "xField": "time", "legend": { "showLegend": false }, "tooltip": { "mode": "single" } },
|
||||||
|
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "table", "rawSql": "SELECT date_bin(INTERVAL '1 day', time) AS time, max(value) AS value FROM \"kWh\" WHERE entity_id = 'solaredge_pv_energy_today' AND $__timeFilter(time) GROUP BY 1 ORDER BY 1" } ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"title": "Speicher-Ladestand",
|
||||||
|
"type": "gauge",
|
||||||
|
"gridPos": { "h": 4, "w": 6, "x": 18, "y": 7 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "max": 100, "thresholds": { "mode": "absolute", "steps": [ { "color": "red", "value": null }, { "color": "yellow", "value": 20 }, { "color": "green", "value": 50 } ] } }, "overrides": [] },
|
||||||
|
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "showThresholdMarkers": true },
|
||||||
|
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"%\" WHERE entity_id = 'solaredge_local_i1_b1_state_of_energy' AND $__timeFilter(time) ORDER BY time" } ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"title": "Erreichte TOP kWh an einem Tag",
|
||||||
|
"type": "bargauge",
|
||||||
|
"gridPos": { "h": 4, "w": 12, "x": 12, "y": 11 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": { "unit": "kwatth", "min": 0, "max": 50, "color": { "mode": "continuous-GrYlRd" } },
|
||||||
|
"overrides": [
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "displayName", "value": "Bester Wert bis jetzt" } ] },
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "displayName", "value": "Heute" } ] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "displayMode": "lcd", "orientation": "horizontal", "showUnfilled": true },
|
||||||
|
"targets": [
|
||||||
|
{ "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "table", "rawSql": "SELECT max(d) AS value FROM (SELECT date_bin(INTERVAL '1 day', time) AS day, max(value) AS d FROM \"kWh\" WHERE entity_id = 'solaredge_pv_energy_today' AND time > now() - INTERVAL '365 days' GROUP BY 1)" },
|
||||||
|
{ "refId": "B", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "table", "rawSql": "SELECT value FROM \"kWh\" WHERE entity_id = 'solaredge_pv_energy_today' ORDER BY time DESC LIMIT 1" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"title": "Gesamt Produktion kWh",
|
||||||
|
"type": "stat",
|
||||||
|
"gridPos": { "h": 4, "w": 12, "x": 12, "y": 15 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "kwatth", "color": { "mode": "continuous-GrYlRd" } }, "overrides": [] },
|
||||||
|
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "area", "textMode": "value" },
|
||||||
|
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kWh\" WHERE entity_id = 'solaredge_local_i1_ac_energy' AND $__timeFilter(time) ORDER BY time" } ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"title": "Netzbilanz (heute)",
|
||||||
|
"type": "bargauge",
|
||||||
|
"gridPos": { "h": 4, "w": 12, "x": 0, "y": 19 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": { "unit": "kwatth", "min": 0, "color": { "mode": "continuous-GrYlRd" } },
|
||||||
|
"overrides": [
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "displayName", "value": "Netzbezug" } ] },
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "displayName", "value": "Einspeisung" } ] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "displayMode": "lcd", "orientation": "horizontal", "showUnfilled": true },
|
||||||
|
"targets": [
|
||||||
|
{ "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kWh\" WHERE entity_id = 'solaredge_grid_import_today' AND $__timeFilter(time) ORDER BY time" },
|
||||||
|
{ "refId": "B", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kWh\" WHERE entity_id = 'solaredge_grid_export_today' AND $__timeFilter(time) ORDER BY time" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"title": "Netz & Batterie (Verlauf)",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 24, "x": 0, "y": 23 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": { "unit": "kwatt", "custom": { "drawStyle": "line", "fillOpacity": 10, "lineWidth": 2 } },
|
||||||
|
"overrides": [
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "displayName", "value": "Netzbezug" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#fa5252" } } ] },
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "displayName", "value": "Einspeisung" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#4dabf7" } } ] },
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "C" }, "properties": [ { "id": "displayName", "value": "Speicher laden" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#37b24d" } } ] },
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "D" }, "properties": [ { "id": "displayName", "value": "Speicher entladen" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#d6336c" } } ] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "multi" } },
|
||||||
|
"targets": [
|
||||||
|
{ "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kW\" WHERE entity_id = 'solaredge_grid_import_power' AND $__timeFilter(time) ORDER BY time" },
|
||||||
|
{ "refId": "B", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kW\" WHERE entity_id = 'solaredge_grid_export_power' AND $__timeFilter(time) ORDER BY time" },
|
||||||
|
{ "refId": "C", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kW\" WHERE entity_id = 'solaredge_battery_charge_power' AND $__timeFilter(time) ORDER BY time" },
|
||||||
|
{ "refId": "D", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kW\" WHERE entity_id = 'solaredge_battery_discharge_power' AND $__timeFilter(time) ORDER BY time" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"title": "Wallbox – Ladeleistung",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 12, "x": 0, "y": 30 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "kwatt", "color": { "mode": "fixed", "fixedColor": "#9775fa" }, "custom": { "drawStyle": "line", "fillOpacity": 20, "lineWidth": 2 } }, "overrides": [] },
|
||||||
|
"options": { "legend": { "showLegend": false }, "tooltip": { "mode": "single" } },
|
||||||
|
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kW\" WHERE entity_id = 'eh7klptt_leistung' AND $__timeFilter(time) ORDER BY time" } ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"title": "Ladeleistung",
|
||||||
|
"type": "gauge",
|
||||||
|
"gridPos": { "h": 7, "w": 6, "x": 12, "y": 30 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "kwatt", "min": 0, "max": 11, "color": { "mode": "continuous-GrYlRd" } }, "overrides": [] },
|
||||||
|
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "showThresholdMarkers": false },
|
||||||
|
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kW\" WHERE entity_id = 'eh7klptt_leistung' AND $__timeFilter(time) ORDER BY time" } ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"title": "Gesamt geladen",
|
||||||
|
"type": "stat",
|
||||||
|
"gridPos": { "h": 4, "w": 6, "x": 18, "y": 30 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "kwatth", "color": { "mode": "fixed", "fixedColor": "#9775fa" } }, "overrides": [] },
|
||||||
|
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "area", "textMode": "value" },
|
||||||
|
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kWh\" WHERE entity_id = 'eh7klptt_gesamtenergie' AND $__timeFilter(time) ORDER BY time" } ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 14,
|
||||||
|
"title": "Aktuelle Session",
|
||||||
|
"type": "stat",
|
||||||
|
"gridPos": { "h": 3, "w": 6, "x": 18, "y": 34 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "kwatth", "color": { "mode": "fixed", "fixedColor": "#4dabf7" } }, "overrides": [] },
|
||||||
|
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "none", "textMode": "value" },
|
||||||
|
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kWh\" WHERE entity_id = 'eh7klptt_sitzungsenergie' AND $__timeFilter(time) ORDER BY time" } ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
{
|
||||||
|
"uid": "ha-weather-archive",
|
||||||
|
"title": "Wetterarchiv KalliHome",
|
||||||
|
"tags": ["weather", "ecowitt", "homeassistant"],
|
||||||
|
"timezone": "browser",
|
||||||
|
"schemaVersion": 39,
|
||||||
|
"version": 2,
|
||||||
|
"refresh": "1m",
|
||||||
|
"time": { "from": "now-7d", "to": "now" },
|
||||||
|
"templating": { "list": [] },
|
||||||
|
"annotations": { "list": [] },
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Außentemperatur",
|
||||||
|
"type": "gauge",
|
||||||
|
"gridPos": { "h": 5, "w": 4, "x": 0, "y": 0 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "celsius", "min": -10, "max": 40, "color": { "mode": "continuous-BlYlRd" } }, "overrides": [] },
|
||||||
|
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "showThresholdMarkers": false },
|
||||||
|
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"°C\" WHERE entity_id = 'gw3000a_outdoor_temperature' AND $__timeFilter(time) ORDER BY time" } ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"title": "Luftfeuchte",
|
||||||
|
"type": "gauge",
|
||||||
|
"gridPos": { "h": 5, "w": 4, "x": 4, "y": 0 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "max": 100, "color": { "mode": "continuous-BlYlRd" } }, "overrides": [] },
|
||||||
|
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "showThresholdMarkers": false },
|
||||||
|
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"%\" WHERE entity_id = 'gw3000a_humidity' AND $__timeFilter(time) ORDER BY time" } ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"title": "Wind",
|
||||||
|
"type": "gauge",
|
||||||
|
"gridPos": { "h": 5, "w": 4, "x": 8, "y": 0 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "velocitykmh", "min": 0, "max": 60, "color": { "mode": "continuous-GrYlRd" } }, "overrides": [] },
|
||||||
|
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "showThresholdMarkers": false },
|
||||||
|
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"km/h\" WHERE entity_id = 'gw3000a_wind_speed' AND $__timeFilter(time) ORDER BY time" } ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"title": "UV-Index",
|
||||||
|
"type": "gauge",
|
||||||
|
"gridPos": { "h": 5, "w": 4, "x": 12, "y": 0 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "short", "min": 0, "max": 11, "color": { "mode": "continuous-GrYlRd" } }, "overrides": [] },
|
||||||
|
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "showThresholdMarkers": false },
|
||||||
|
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"UV index\" WHERE entity_id = 'gw3000a_uv_index' AND $__timeFilter(time) ORDER BY time" } ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"title": "Solarstrahlung",
|
||||||
|
"type": "gauge",
|
||||||
|
"gridPos": { "h": 5, "w": 4, "x": 16, "y": 0 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "wattm2", "min": 0, "max": 1200, "color": { "mode": "continuous-GrYlRd" } }, "overrides": [] },
|
||||||
|
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "showThresholdMarkers": false },
|
||||||
|
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"W/m²\" WHERE entity_id = 'gw3000a_solar_radiation' AND $__timeFilter(time) ORDER BY time" } ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"title": "Luftdruck",
|
||||||
|
"type": "stat",
|
||||||
|
"gridPos": { "h": 5, "w": 4, "x": 20, "y": 0 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "pressurehpa", "color": { "mode": "fixed", "fixedColor": "#4dabf7" } }, "overrides": [] },
|
||||||
|
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "area", "textMode": "value" },
|
||||||
|
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"hPa\" WHERE entity_id = 'gw3000a_relative_pressure' AND $__timeFilter(time) ORDER BY time" } ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"title": "Temperatur (°C)",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 5 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": { "unit": "celsius", "custom": { "drawStyle": "line", "fillOpacity": 12, "lineWidth": 2, "showPoints": "never" } },
|
||||||
|
"overrides": [
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "displayName", "value": "Außen" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#fa5252" } } ] },
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "displayName", "value": "Gefühlt" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#ff922b" } } ] },
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "C" }, "properties": [ { "id": "displayName", "value": "Taupunkt" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#4dabf7" } } ] },
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "D" }, "properties": [ { "id": "displayName", "value": "Innen" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#82c91e" } } ] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "calcs": ["lastNotNull"] }, "tooltip": { "mode": "multi" } },
|
||||||
|
"targets": [
|
||||||
|
{ "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"°C\" WHERE entity_id = 'gw3000a_outdoor_temperature' AND $__timeFilter(time) ORDER BY time" },
|
||||||
|
{ "refId": "B", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"°C\" WHERE entity_id = 'gw3000a_feels_like_temperature' AND $__timeFilter(time) ORDER BY time" },
|
||||||
|
{ "refId": "C", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"°C\" WHERE entity_id = 'gw3000a_dewpoint' AND $__timeFilter(time) ORDER BY time" },
|
||||||
|
{ "refId": "D", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"°C\" WHERE entity_id = 'gw3000a_indoor_temperature' AND $__timeFilter(time) ORDER BY time" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"title": "Luftfeuchte (%)",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 5 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": { "unit": "percent", "min": 0, "max": 100, "custom": { "drawStyle": "line", "fillOpacity": 12, "lineWidth": 2, "showPoints": "never" } },
|
||||||
|
"overrides": [
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "displayName", "value": "Außen" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#4dabf7" } } ] },
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "displayName", "value": "Innen" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#82c91e" } } ] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "calcs": ["lastNotNull"] }, "tooltip": { "mode": "multi" } },
|
||||||
|
"targets": [
|
||||||
|
{ "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"%\" WHERE entity_id = 'gw3000a_humidity' AND $__timeFilter(time) ORDER BY time" },
|
||||||
|
{ "refId": "B", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"%\" WHERE entity_id = 'gw3000a_indoor_humidity' AND $__timeFilter(time) ORDER BY time" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"title": "Solarstrahlung (W/m²)",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 13 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "wattm2", "color": { "mode": "fixed", "fixedColor": "#f2b705" }, "custom": { "drawStyle": "line", "fillOpacity": 35, "lineWidth": 1, "showPoints": "never", "gradientMode": "opacity" } }, "overrides": [] },
|
||||||
|
"options": { "legend": { "showLegend": false }, "tooltip": { "mode": "single" } },
|
||||||
|
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"W/m²\" WHERE entity_id = 'gw3000a_solar_radiation' AND $__timeFilter(time) ORDER BY time" } ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"title": "Wind (km/h)",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 13 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": { "unit": "velocitykmh", "custom": { "drawStyle": "line", "fillOpacity": 10, "lineWidth": 2, "showPoints": "never" } },
|
||||||
|
"overrides": [
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "displayName", "value": "Wind" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#15aabf" } } ] },
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "displayName", "value": "Böe" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#fab005" } } ] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "calcs": ["lastNotNull", "max"] }, "tooltip": { "mode": "multi" } },
|
||||||
|
"targets": [
|
||||||
|
{ "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"km/h\" WHERE entity_id = 'gw3000a_wind_speed' AND $__timeFilter(time) ORDER BY time" },
|
||||||
|
{ "refId": "B", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"km/h\" WHERE entity_id = 'gw3000a_wind_gust' AND $__timeFilter(time) ORDER BY time" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"title": "Regen pro Tag (mm)",
|
||||||
|
"type": "barchart",
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 21 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "lengthmm", "color": { "mode": "fixed", "fixedColor": "#4dabf7" }, "custom": { "fillOpacity": 80, "lineWidth": 1 } }, "overrides": [] },
|
||||||
|
"options": { "orientation": "vertical", "showValue": "auto", "xField": "time", "legend": { "showLegend": false }, "tooltip": { "mode": "single" } },
|
||||||
|
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "table", "rawSql": "SELECT date_bin(INTERVAL '1 day', time) AS time, max(value) AS value FROM \"mm\" WHERE entity_id = 'gw3000a_daily_rain' AND $__timeFilter(time) GROUP BY 1 ORDER BY 1" } ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"title": "Luftdruck (hPa)",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 21 },
|
||||||
|
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "pressurehpa", "decimals": 0, "color": { "mode": "fixed", "fixedColor": "#9775fa" }, "custom": { "drawStyle": "line", "fillOpacity": 10, "lineWidth": 2, "showPoints": "never" } }, "overrides": [] },
|
||||||
|
"options": { "legend": { "showLegend": false }, "tooltip": { "mode": "single" } },
|
||||||
|
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"hPa\" WHERE entity_id = 'gw3000a_relative_pressure' AND $__timeFilter(time) ORDER BY time" } ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -31,3 +31,19 @@ datasources:
|
|||||||
insecureGrpc: true
|
insecureGrpc: true
|
||||||
secureJsonData:
|
secureJsonData:
|
||||||
token: $GRAFANA_INFLUXDB_TOKEN
|
token: $GRAFANA_INFLUXDB_TOKEN
|
||||||
|
|
||||||
|
# Wetter-/Langzeitarchiv aus Home Assistant (Ecowitt). Gleiche InfluxDB-Instanz,
|
||||||
|
# aber Datenbank `homeassistant`; gleicher Admin-Read-Token.
|
||||||
|
- name: InfluxDB HA Weather
|
||||||
|
uid: ha-weather-influx
|
||||||
|
type: influxdb
|
||||||
|
access: proxy
|
||||||
|
url: http://influxdb3-core:8181
|
||||||
|
editable: false
|
||||||
|
jsonData:
|
||||||
|
version: SQL
|
||||||
|
dbName: homeassistant
|
||||||
|
httpMode: POST
|
||||||
|
insecureGrpc: true
|
||||||
|
secureJsonData:
|
||||||
|
token: $GRAFANA_INFLUXDB_TOKEN
|
||||||
|
|||||||
@@ -131,6 +131,78 @@ groups:
|
|||||||
summary: "Latest Borg backup completed with warnings"
|
summary: "Latest Borg backup completed with warnings"
|
||||||
description: "The latest Borg UI job completed with warnings for archive {{ $labels.archive }}."
|
description: "The latest Borg UI job completed with warnings for archive {{ $labels.archive }}."
|
||||||
|
|
||||||
|
- alert: HomelabBorgScopeSourceListMissing
|
||||||
|
expr: homelab_borg_scope_expected_file_present != 1
|
||||||
|
for: 15m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
annotations:
|
||||||
|
summary: "Borg expected source list is not visible"
|
||||||
|
description: "Borg UI cannot see the repo source list used for drift checks."
|
||||||
|
|
||||||
|
- alert: HomelabBorgScopeMissingSources
|
||||||
|
expr: homelab_borg_scope_missing_sources_total > 0
|
||||||
|
for: 15m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
annotations:
|
||||||
|
summary: "Borg UI is missing expected backup sources"
|
||||||
|
description: "Borg UI is missing {{ $value }} source path(s) from ops/borg-ui/all-important-sources.txt."
|
||||||
|
|
||||||
|
- alert: HomelabBorgScopeExtraSources
|
||||||
|
expr: homelab_borg_scope_extra_sources_total > 0
|
||||||
|
for: 30m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "Borg UI has sources not tracked in the repo"
|
||||||
|
description: "Borg UI has {{ $value }} source path(s) that are not listed in ops/borg-ui/all-important-sources.txt."
|
||||||
|
|
||||||
|
- alert: HomelabBorgDumpMissing
|
||||||
|
expr: homelab_borg_dump_present == 0
|
||||||
|
for: 15m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
annotations:
|
||||||
|
summary: "Borg pre-backup dump is missing: {{ $labels.dump }}"
|
||||||
|
description: "Expected dump artifact {{ $labels.dump }} is not present in the latest dump set. The pre-backup dump job may have failed or stopped."
|
||||||
|
|
||||||
|
- alert: HomelabBorgDumpStale
|
||||||
|
expr: homelab_borg_dump_age_seconds > 30 * 60 * 60
|
||||||
|
for: 15m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
annotations:
|
||||||
|
summary: "Borg pre-backup dump is stale: {{ $labels.dump }}"
|
||||||
|
description: "Dump artifact {{ $labels.dump }} is older than 30 hours. pre-backup-dumps.sh may have stopped; Borg would keep archiving stale database content without a job failure."
|
||||||
|
|
||||||
|
- alert: HomelabBorgRepositoryCheckStale
|
||||||
|
expr: time() - homelab_borg_repository_last_check_timestamp_seconds > 14 * 24 * 60 * 60
|
||||||
|
for: 30m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "Borg repository check is stale"
|
||||||
|
description: "Borg repository {{ $labels.repository }} has not had a recorded check for more than 14 days."
|
||||||
|
|
||||||
|
- alert: HomelabBorgRetentionDisabled
|
||||||
|
expr: homelab_borg_schedule_prune_after_enabled != 1
|
||||||
|
for: 30m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "Borg retention pruning is disabled"
|
||||||
|
description: "Scheduled Borg job {{ $labels.schedule }} does not run prune after backup."
|
||||||
|
|
||||||
|
- alert: HomelabBorgCompactDisabled
|
||||||
|
expr: homelab_borg_schedule_compact_after_enabled != 1
|
||||||
|
for: 30m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "Borg compaction is disabled"
|
||||||
|
description: "Scheduled Borg job {{ $labels.schedule }} does not run compact after backup."
|
||||||
|
|
||||||
- alert: HomelabCriticalContainerDown
|
- alert: HomelabCriticalContainerDown
|
||||||
expr: homelab_critical_container_running == 0
|
expr: homelab_critical_container_running == 0
|
||||||
for: 5m
|
for: 5m
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ global:
|
|||||||
site: kallilabcore
|
site: kallilabcore
|
||||||
|
|
||||||
rule_files:
|
rule_files:
|
||||||
- /etc/prometheus/alerts.yml
|
- /etc/prometheus/config/alerts.yml
|
||||||
|
|
||||||
alerting:
|
alerting:
|
||||||
alertmanagers:
|
alertmanagers:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Borg Backup Scope for KalliLabcore
|
# Borg Backup Scope for KalliLabcore
|
||||||
|
|
||||||
Stand: 2026-05-31
|
Stand: 2026-06-17
|
||||||
|
|
||||||
This file defines the target state for replacing Backrest with Borg in this homelab.
|
This file defines the target state for replacing Backrest with Borg in this homelab.
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ The Unraid flash configuration archive is intentional as well and must be treate
|
|||||||
| Traefik | file data | `/local/appdata/traefik` |
|
| Traefik | file data | `/local/appdata/traefik` |
|
||||||
| ntfy | file data | `/local/appdata/ntfy` |
|
| ntfy | file data | `/local/appdata/ntfy` |
|
||||||
| Paperless-GPT | file data | `/local/appdata/paperless-gpt` |
|
| Paperless-GPT | file data | `/local/appdata/paperless-gpt` |
|
||||||
| Tailscale | file data | `/local/appdata/tailscale` |
|
| Tailscale | Flash config artifact | covered by `/local/borg-dumps/unraid-flash-config.tar.gz`; no active `/local/appdata/tailscale` path |
|
||||||
| AdGuard | config only | `/local/appdata/adguard/conf` |
|
| AdGuard | config only | `/local/appdata/adguard/conf` |
|
||||||
| Borg UI | SQLite dump + self-backup | `/local/borg-dumps`, `/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` |
|
| Komodo | config + Mongo dump | `/local/borg-dumps`, `/local/appdata/komodo/periphery`, `/local/appdata/komodo/core` |
|
||||||
@@ -48,7 +48,12 @@ The Unraid flash configuration archive is intentional as well and must be treate
|
|||||||
| Grafana | SQLite dump from `monitoring_grafana_data` + provisioned config in Git | `/local/borg-dumps`, `monitoring/grafana/provisioning`, `monitoring/grafana/dashboards` |
|
| Grafana | SQLite dump from `monitoring_grafana_data` + provisioned config in Git | `/local/borg-dumps`, `monitoring/grafana/provisioning`, `monitoring/grafana/dashboards` |
|
||||||
| Filebrowser | file-backed state dump + file data | `/local/borg-dumps`, `/local/appdata/filebrowser` |
|
| 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` |
|
| 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` |
|
| n8n | SQLite dump + encrypted workflow/credential state | `/local/borg-dumps`, `/local/appdata/n8n/data` |
|
||||||
|
| Home Assistant | HA-native backup + file state | `/local/appdata/homeassistant`, `/local/services/smart-home-kalli` |
|
||||||
|
| Smart-Home MQTT / Mosquitto | file data | `/local/appdata/mosquitto/config`, `/local/appdata/mosquitto/data` |
|
||||||
|
| Zigbee2MQTT (planned) | file data + coordinator state | `/local/appdata/zigbee2mqtt`, `/local/services/smart-home-kalli` |
|
||||||
|
| ESPHome (planned) | Fachrepo + optional build/runtime cache | `/local/services/smart-home-kalli/esphome`, optional `/local/appdata/esphome` |
|
||||||
|
| Hermes Agent | file data + SSH key | SSH-Key via `/local/secrets`; `/local/appdata/hermes-agent/data` ist bewusst NICHT in `all-important-sources.txt`, weil der Stack geparkt ist (Review 2026-07-25). Beim Aktivieren des Stacks in die Quellliste aufnehmen. |
|
||||||
| BentoPDF | rebuildable | no critical persistence in compose |
|
| BentoPDF | rebuildable | no critical persistence in compose |
|
||||||
|
|
||||||
## Open Decisions and Coverage Gaps
|
## Open Decisions and Coverage Gaps
|
||||||
@@ -67,6 +72,17 @@ Option A umgesetzt: `pre-backup-dumps.sh` writes `nextcloud.dump` from `nextclou
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
### User-Daten-Shares ausserhalb des App-Scope
|
||||||
|
|
||||||
|
Filebrowser serviert `/mnt/user/projekte`, `/mnt/user/documents` und `/mnt/user/photos` komplett (`ops/filebrowser/docker-compose.yml`). Der Borg-Scope deckt aber bewusst nur die App-Unterordner ab (`documents/paperless*`, `documents/nextcloud-data`, `documents/scans_inbox`, `photos/immich`, `photos/family_archive`).
|
||||||
|
|
||||||
|
- **`/mnt/user/projekte`** ist aktuell in **keinem** Borg-Scope. Ad-hoc-Dateien, die direkt unter `documents/` oder `photos/` (ausserhalb der genannten App-Ordner) abgelegt werden, ebenfalls nicht.
|
||||||
|
- Entscheidung Operator offen (Eintrag in `docs/MASTER_TODO.md`): Entweder `projekte` als eigenen read-only Borg-UI-Mount + Quelllisten-Eintrag aufnehmen, oder bewusst als "nur lokal, nicht DR-relevant" bestaetigen. Bis zur Entscheidung gilt: dort liegende Originaldaten sind **nicht** wiederherstellbar.
|
||||||
|
|
||||||
|
### Komodo keys
|
||||||
|
|
||||||
|
Production still stores Komodo Core/Periphery keys in the Docker named volume `komodo_komodo_keys`. This is a known open migration item and is not fixed by the Borg source list alone. Target state: move the keys to a host path such as `/mnt/user/appdata/komodo/keys` and mount that path into both Komodo containers, then include it in Borg. Do not treat this as solved until the live Compose stack has been migrated and Periphery reconnect has been verified.
|
||||||
|
|
||||||
## Database Dumps Required
|
## Database Dumps Required
|
||||||
|
|
||||||
### Shared PostgreSQL (`postgresql17`, runtime PostgreSQL 18)
|
### Shared PostgreSQL (`postgresql17`, runtime PostgreSQL 18)
|
||||||
@@ -85,8 +101,10 @@ The live Unraid User Scripts execute repo scripts from `/mnt/user/services/homel
|
|||||||
|
|
||||||
- Komodo MongoDB
|
- Komodo MongoDB
|
||||||
- SQLite: `gitea`, `vaultwarden`, `speedtest-tracker`, `borg-ui`, `grafana`
|
- SQLite: `gitea`, `vaultwarden`, `speedtest-tracker`, `borg-ui`, `grafana`
|
||||||
|
- SQLite: `n8n` (`n8n.sqlite.dump`, credentials require the matching `N8N_ENCRYPTION_KEY`)
|
||||||
- File-backed state: `filebrowser.bolt.dump`
|
- File-backed state: `filebrowser.bolt.dump`
|
||||||
- Unraid flash config: `unraid-flash-config.tar.gz` plus `unraid-flash-config.tar.gz.sha256`
|
- Unraid flash config: `unraid-flash-config.tar.gz` plus `unraid-flash-config.tar.gz.sha256`
|
||||||
|
- Home Assistant native backups: created by HA under `/mnt/user/appdata/homeassistant/backups` and captured as file state
|
||||||
|
|
||||||
## Explicitly Not Backed Up as Raw Live DB Files
|
## Explicitly Not Backed Up as Raw Live DB Files
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,20 @@
|
|||||||
/local/appdata/traefik
|
/local/appdata/traefik
|
||||||
/local/appdata/ntfy
|
/local/appdata/ntfy
|
||||||
/local/appdata/paperless-gpt
|
/local/appdata/paperless-gpt
|
||||||
/local/appdata/tailscale
|
|
||||||
/local/appdata/adguard/conf
|
/local/appdata/adguard/conf
|
||||||
/local/appdata/borg-ui/data
|
/local/appdata/borg-ui/data
|
||||||
/local/appdata/komodo/periphery
|
/local/appdata/komodo/periphery
|
||||||
/local/appdata/komodo/core
|
/local/appdata/komodo/core
|
||||||
|
/local/appdata/nextcloud/html
|
||||||
|
/local/nextcloud/data
|
||||||
|
/local/appdata/n8n/data
|
||||||
|
/local/appdata/filebrowser
|
||||||
|
/local/appdata/influxdb3/data
|
||||||
|
/local/appdata/influxdb3/plugins
|
||||||
/local/services/homelab-infra
|
/local/services/homelab-infra
|
||||||
|
/local/services/smart-home-kalli
|
||||||
/local/services/stacks
|
/local/services/stacks
|
||||||
/local/services/posture-check
|
/local/services/posture-check
|
||||||
|
/local/appdata/homeassistant
|
||||||
|
/local/appdata/mosquitto/config
|
||||||
|
/local/appdata/mosquitto/data
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
borg-ui:
|
borg-ui:
|
||||||
image: ainullcode/borg-ui@sha256:acb0fbe83dc4a3843abc06f814c5f1061a0701b2cfc574da2e851d17a34ab745
|
image: ainullcode/borg-ui@sha256:0922157e8f77a1b2bd23cd09366a458ea6de07fd9306aa1485f9cfe623eca17f
|
||||||
container_name: borg-ui
|
container_name: borg-ui
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
security_opt:
|
security_opt:
|
||||||
|
|||||||
@@ -325,6 +325,7 @@ main() {
|
|||||||
# Additional host-side SQLite dumps for admin tooling with appdata files.
|
# 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/borg-ui/data/borg.db" "$LATEST_DIR/borg-ui.sqlite" "borg-ui"
|
||||||
dump_sqlite_file "/var/lib/docker/volumes/monitoring_grafana_data/_data/grafana.db" "$LATEST_DIR/grafana.sqlite" "grafana"
|
dump_sqlite_file "/var/lib/docker/volumes/monitoring_grafana_data/_data/grafana.db" "$LATEST_DIR/grafana.sqlite" "grafana"
|
||||||
|
dump_sqlite_file "/mnt/user/appdata/n8n/data/database.sqlite" "$LATEST_DIR/n8n.sqlite.dump" "n8n"
|
||||||
|
|
||||||
# MongoDB
|
# MongoDB
|
||||||
dump_mongo_container "komodo-mongo" "$LATEST_DIR/komodo-mongo.archive.gz"
|
dump_mongo_container "komodo-mongo" "$LATEST_DIR/komodo-mongo.archive.gz"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
code-server:
|
code-server:
|
||||||
image: lscr.io/linuxserver/code-server:4.123.0@sha256:9dd4555720db04eb92d92cc84e7a34f0862bada5679889446a3004c45b5fa59b
|
image: lscr.io/linuxserver/code-server:4.123.0@sha256:cb261a7f87674b445e0fd66d87d55900c1b823d276c727ab0d168a75e69e9992
|
||||||
container_name: code-server
|
container_name: code-server
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
security_opt:
|
security_opt:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
filebrowser:
|
filebrowser:
|
||||||
image: filebrowser/filebrowser:v2.63.12@sha256:fc8c3a46c16bbdf97362b20c50164e97de9c1dd5f63230d28a4cb15248b53ec3
|
image: filebrowser/filebrowser:v2.63.14@sha256:1ec9b0c68297550c92f4a93feed432850c2993b261706cc3cc2e808f94a95e76
|
||||||
container_name: filebrowser
|
container_name: filebrowser
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
security_opt:
|
security_opt:
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
/* ============================================================
|
||||||
|
KalliLab "Neon Ops v2" - Glance Custom CSS
|
||||||
|
Rotierende Akzentfarben pro Widget, Gradient-Zahlen,
|
||||||
|
animierte Header-Linien, kraeftige Glows
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
/* --- Akzentfarben rotieren ueber die Widgets --- */
|
||||||
|
.widget { --kl-accent: 205 100% 60%; }
|
||||||
|
.widget:nth-of-type(4n+2) { --kl-accent: 172 95% 48%; }
|
||||||
|
.widget:nth-of-type(4n+3) { --kl-accent: 38 100% 55%; }
|
||||||
|
.widget:nth-of-type(4n) { --kl-accent: 145 85% 50%; }
|
||||||
|
|
||||||
|
/* --- Seiten-Hintergrund: kraeftigere Farb-Glows --- */
|
||||||
|
body {
|
||||||
|
background:
|
||||||
|
radial-gradient(1300px 700px at 85% -10%, hsla(205, 100%, 55%, 0.13), transparent 60%),
|
||||||
|
radial-gradient(1000px 600px at -10% 25%, hsla(172, 95%, 45%, 0.09), transparent 55%),
|
||||||
|
radial-gradient(900px 700px at 50% 115%, hsla(38, 100%, 50%, 0.07), transparent 60%),
|
||||||
|
var(--color-background);
|
||||||
|
background-attachment: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Widgets als Karten mit Akzentrand --- */
|
||||||
|
.widget {
|
||||||
|
background: linear-gradient(
|
||||||
|
160deg,
|
||||||
|
hsla(220, 30%, 100%, 0.05),
|
||||||
|
hsla(220, 30%, 100%, 0.015)
|
||||||
|
);
|
||||||
|
border: 1px solid hsl(var(--kl-accent) / 0.18);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
box-shadow:
|
||||||
|
0 10px 30px hsla(220, 60%, 3%, 0.4),
|
||||||
|
0 0 24px hsl(var(--kl-accent) / 0.06),
|
||||||
|
inset 0 1px 0 hsla(220, 40%, 90%, 0.05);
|
||||||
|
transition: border-color 0.2s ease, box-shadow 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget:hover {
|
||||||
|
border-color: hsl(var(--kl-accent) / 0.55);
|
||||||
|
box-shadow:
|
||||||
|
0 12px 36px hsla(220, 60%, 3%, 0.45),
|
||||||
|
0 0 36px hsl(var(--kl-accent) / 0.16),
|
||||||
|
inset 0 1px 0 hsla(220, 40%, 90%, 0.07);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Widgets in Gruppen/Tabs nicht doppelt einrahmen */
|
||||||
|
.widget .widget {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Widget-Titel: animierte Farbverlaufs-Linie in Akzentfarbe --- */
|
||||||
|
.widget-header {
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 7px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
color: hsl(var(--kl-accent) / 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-header::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 64px;
|
||||||
|
height: 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
hsl(var(--kl-accent)),
|
||||||
|
hsl(var(--kl-accent) / 0.25),
|
||||||
|
hsl(var(--kl-accent))
|
||||||
|
);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: kl-shimmer 4s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes kl-shimmer {
|
||||||
|
0% { background-position: 0% 0; }
|
||||||
|
100% { background-position: 200% 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Grosse Zahlen: Gradient-Text + Glow --- */
|
||||||
|
.color-highlight.size-h2,
|
||||||
|
.color-highlight.size-h3,
|
||||||
|
.color-primary.size-h2,
|
||||||
|
.color-primary.size-h3 {
|
||||||
|
background: linear-gradient(
|
||||||
|
120deg,
|
||||||
|
hsl(var(--kl-accent)),
|
||||||
|
hsl(var(--kl-accent) / 0.55) 60%,
|
||||||
|
hsl(210, 30%, 95%)
|
||||||
|
);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
filter: drop-shadow(0 0 14px hsl(var(--kl-accent) / 0.35));
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-positive {
|
||||||
|
text-shadow: 0 0 16px hsla(150, 95%, 45%, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-negative {
|
||||||
|
text-shadow: 0 0 16px hsla(350, 95%, 58%, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Status-Punkte leuchten --- */
|
||||||
|
.monitor-site-status-icon-compact,
|
||||||
|
.monitor-site-status-icon {
|
||||||
|
filter: drop-shadow(0 0 7px hsla(150, 95%, 45%, 0.55));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Navigation --- */
|
||||||
|
.nav-item.nav-item-current {
|
||||||
|
text-shadow: 0 0 18px hsla(212, 100%, 60%, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Suchleiste --- */
|
||||||
|
.search {
|
||||||
|
border: 1px solid hsla(212, 90%, 65%, 0.2);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: hsla(220, 30%, 100%, 0.04);
|
||||||
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search:focus-within {
|
||||||
|
border-color: hsla(212, 100%, 60%, 0.55);
|
||||||
|
box-shadow: 0 0 0 3px hsla(212, 100%, 55%, 0.15), 0 0 28px hsla(212, 100%, 55%, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Server-Stats: Balken rund, gradient, glow --- */
|
||||||
|
.progress-bar {
|
||||||
|
border: none;
|
||||||
|
height: 13px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: hsla(220, 30%, 60%, 0.12);
|
||||||
|
box-shadow: inset 0 1px 3px hsla(220, 60%, 3%, 0.5);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-value {
|
||||||
|
border-radius: 999px;
|
||||||
|
background: linear-gradient(90deg, hsl(205, 100%, 55%), hsl(172, 95%, 48%));
|
||||||
|
box-shadow: 0 0 10px hsla(205, 100%, 55%, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-value-notice {
|
||||||
|
background: linear-gradient(90deg, hsl(38, 100%, 55%), hsl(355, 90%, 60%));
|
||||||
|
box-shadow: 0 0 12px hsla(355, 90%, 58%, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Feinschliff --- */
|
||||||
|
::selection {
|
||||||
|
background: hsla(212, 100%, 50%, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: hsla(220, 30%, 50%, 0.25);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: hsla(212, 80%, 55%, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reduzierte Bewegung respektieren */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.widget-header::after {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
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 18
|
||||||
|
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
|
||||||
|
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
|
||||||
+40
-867
@@ -1,5 +1,6 @@
|
|||||||
server:
|
server:
|
||||||
proxied: true
|
proxied: true
|
||||||
|
assets-path: /app/assets
|
||||||
|
|
||||||
branding:
|
branding:
|
||||||
app-name: KalliLab Dashboard
|
app-name: KalliLab Dashboard
|
||||||
@@ -7,873 +8,45 @@ branding:
|
|||||||
hide-footer: true
|
hide-footer: true
|
||||||
|
|
||||||
theme:
|
theme:
|
||||||
background-color: 210 20 13
|
background-color: 222 14 8
|
||||||
primary-color: 212 100 50
|
primary-color: 205 100 58
|
||||||
positive-color: 140 70 40
|
positive-color: 150 80 45
|
||||||
negative-color: 4 78 57
|
negative-color: 355 90 60
|
||||||
contrast-multiplier: 1.25
|
contrast-multiplier: 1.3
|
||||||
text-saturation-multiplier: 0.9
|
text-saturation-multiplier: 0.5
|
||||||
disable-picker: false
|
disable-picker: false
|
||||||
|
custom-css-file: /assets/custom.css
|
||||||
|
presets:
|
||||||
|
catppuccin-mocha:
|
||||||
|
background-color: 240 21 15
|
||||||
|
primary-color: 217 92 83
|
||||||
|
positive-color: 115 54 76
|
||||||
|
negative-color: 347 70 65
|
||||||
|
contrast-multiplier: 1.2
|
||||||
|
gruvbox-dark:
|
||||||
|
background-color: 0 0 16
|
||||||
|
primary-color: 43 59 81
|
||||||
|
positive-color: 61 66 44
|
||||||
|
negative-color: 6 96 59
|
||||||
|
kallilab-light:
|
||||||
|
light: true
|
||||||
|
background-color: 220 23 95
|
||||||
|
primary-color: 212 100 35
|
||||||
|
positive-color: 140 70 30
|
||||||
|
negative-color: 0 70 45
|
||||||
|
synthwave:
|
||||||
|
background-color: 265 35 10
|
||||||
|
primary-color: 320 100 65
|
||||||
|
positive-color: 175 100 50
|
||||||
|
negative-color: 0 100 65
|
||||||
|
contrast-multiplier: 1.3
|
||||||
|
matrix:
|
||||||
|
background-color: 130 25 6
|
||||||
|
primary-color: 130 100 55
|
||||||
|
positive-color: 130 100 45
|
||||||
|
negative-color: 35 100 55
|
||||||
|
contrast-multiplier: 1.25
|
||||||
|
text-saturation-multiplier: 1.2
|
||||||
|
|
||||||
pages:
|
pages:
|
||||||
- name: Home
|
$include: pages.yml
|
||||||
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 18
|
|
||||||
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,596 @@
|
|||||||
|
- 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
|
||||||
|
- title: Immich
|
||||||
|
shortcut: "!foto"
|
||||||
|
url: https://immich.kaleschke.info/search?query={QUERY}
|
||||||
|
- title: Mealie
|
||||||
|
shortcut: "!rezept"
|
||||||
|
url: https://mealie.kaleschke.info/g/home/?search={QUERY}
|
||||||
|
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: custom-api
|
||||||
|
title: Wetter · KalliHome
|
||||||
|
title-url: https://home.kaleschke.info
|
||||||
|
cache: 30s
|
||||||
|
url: http://homeassistant:8123/api/states/sensor.gw3000a_outdoor_temperature
|
||||||
|
headers:
|
||||||
|
Authorization: Bearer ${GLANCE_HA_TOKEN}
|
||||||
|
Content-Type: application/json
|
||||||
|
subrequests:
|
||||||
|
feels:
|
||||||
|
url: http://homeassistant:8123/api/states/sensor.gw3000a_feels_like_temperature
|
||||||
|
headers:
|
||||||
|
Authorization: Bearer ${GLANCE_HA_TOKEN}
|
||||||
|
humidity:
|
||||||
|
url: http://homeassistant:8123/api/states/sensor.gw3000a_humidity
|
||||||
|
headers:
|
||||||
|
Authorization: Bearer ${GLANCE_HA_TOKEN}
|
||||||
|
wind:
|
||||||
|
url: http://homeassistant:8123/api/states/sensor.gw3000a_wind_speed
|
||||||
|
headers:
|
||||||
|
Authorization: Bearer ${GLANCE_HA_TOKEN}
|
||||||
|
gust:
|
||||||
|
url: http://homeassistant:8123/api/states/sensor.gw3000a_wind_gust
|
||||||
|
headers:
|
||||||
|
Authorization: Bearer ${GLANCE_HA_TOKEN}
|
||||||
|
rain:
|
||||||
|
url: http://homeassistant:8123/api/states/sensor.gw3000a_daily_rain
|
||||||
|
headers:
|
||||||
|
Authorization: Bearer ${GLANCE_HA_TOKEN}
|
||||||
|
solar:
|
||||||
|
url: http://homeassistant:8123/api/states/sensor.gw3000a_solar_radiation
|
||||||
|
headers:
|
||||||
|
Authorization: Bearer ${GLANCE_HA_TOKEN}
|
||||||
|
uv:
|
||||||
|
url: http://homeassistant:8123/api/states/sensor.gw3000a_uv_index
|
||||||
|
headers:
|
||||||
|
Authorization: Bearer ${GLANCE_HA_TOKEN}
|
||||||
|
pressure:
|
||||||
|
url: http://homeassistant:8123/api/states/sensor.gw3000a_relative_pressure
|
||||||
|
headers:
|
||||||
|
Authorization: Bearer ${GLANCE_HA_TOKEN}
|
||||||
|
template: |
|
||||||
|
{{ $temp := .JSON.String "state" }}
|
||||||
|
{{ $feels := (.Subrequest "feels").JSON.String "state" }}
|
||||||
|
{{ $hum := (.Subrequest "humidity").JSON.String "state" }}
|
||||||
|
{{ $wind := (.Subrequest "wind").JSON.String "state" }}
|
||||||
|
{{ $gust := (.Subrequest "gust").JSON.String "state" }}
|
||||||
|
{{ $rain := (.Subrequest "rain").JSON.String "state" }}
|
||||||
|
{{ $solar := (.Subrequest "solar").JSON.String "state" }}
|
||||||
|
{{ $uv := (.Subrequest "uv").JSON.String "state" }}
|
||||||
|
{{ $press := (.Subrequest "pressure").JSON.String "state" }}
|
||||||
|
{{ $gustF := (.Subrequest "gust").JSON.Float "state" }}
|
||||||
|
{{ $divider := "border-left: 1px solid hsla(220, 40%, 70%, 0.14);" }}
|
||||||
|
<div class="text-center" style="margin-bottom: 12px;">
|
||||||
|
<div class="color-highlight size-h2" style="font-weight: 700;">{{ $temp }}°C</div>
|
||||||
|
<div class="size-h6 color-subdue">gefühlt {{ $feels }}° · {{ $hum }}% feucht</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between text-center" style="margin-bottom: 12px;">
|
||||||
|
<div style="flex: 1;">
|
||||||
|
<div class="size-h4 {{ if gt $gustF 40.0 }}color-negative{{ else }}color-highlight{{ end }}">{{ $wind }}</div>
|
||||||
|
<div class="size-h6 uppercase color-subdue">km/h Wind</div>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1; {{ $divider }}">
|
||||||
|
<div class="size-h4 {{ if gt $gustF 40.0 }}color-negative{{ else }}color-highlight{{ end }}">{{ $gust }}</div>
|
||||||
|
<div class="size-h6 uppercase color-subdue">km/h Böe</div>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1; {{ $divider }}">
|
||||||
|
<div class="size-h4 color-highlight">{{ $rain }}</div>
|
||||||
|
<div class="size-h6 uppercase color-subdue">mm heute</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between text-center">
|
||||||
|
<div style="flex: 1;">
|
||||||
|
<div class="size-h4 color-highlight">{{ $solar }}</div>
|
||||||
|
<div class="size-h6 uppercase color-subdue">W/m² Solar</div>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1; {{ $divider }}">
|
||||||
|
<div class="size-h4 color-highlight">{{ $uv }}</div>
|
||||||
|
<div class="size-h6 uppercase color-subdue">UV-Index</div>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1; {{ $divider }}">
|
||||||
|
<div class="size-h4 color-highlight">{{ $press }}</div>
|
||||||
|
<div class="size-h6 uppercase color-subdue">hPa Druck</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
- type: calendar
|
||||||
|
first-day-of-week: monday
|
||||||
|
|
||||||
|
- type: to-do
|
||||||
|
title: Operator-Notizen
|
||||||
|
|
||||||
|
- 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: custom-api
|
||||||
|
title: Komodo Stacks
|
||||||
|
title-url: https://komodo.kaleschke.info
|
||||||
|
cache: 2m
|
||||||
|
url: http://komodo-core:9120/read
|
||||||
|
method: POST
|
||||||
|
body-type: json
|
||||||
|
body:
|
||||||
|
type: ListStacks
|
||||||
|
params: {}
|
||||||
|
headers:
|
||||||
|
X-Api-Key: ${GLANCE_KOMODO_API_KEY}
|
||||||
|
X-Api-Secret: ${GLANCE_KOMODO_API_SECRET}
|
||||||
|
Content-Type: application/json
|
||||||
|
template: |
|
||||||
|
{{ $stacks := .JSON.Array "@this" }}
|
||||||
|
{{ $total := len $stacks }}
|
||||||
|
{{ $running := 0 }}
|
||||||
|
{{ range $stacks }}{{ if eq (.String "info.state") "running" }}{{ $running = add $running 1 }}{{ end }}{{ end }}
|
||||||
|
{{ $problems := sub $total $running }}
|
||||||
|
{{ $divider := "border-left: 1px solid hsla(220, 40%, 70%, 0.14);" }}
|
||||||
|
<div style="display: flex; text-align: center;">
|
||||||
|
<div style="flex: 1;">
|
||||||
|
<div class="color-highlight size-h3">{{ $total }}</div>
|
||||||
|
<div class="size-h6 uppercase color-subdue">Stacks</div>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1; {{ $divider }}">
|
||||||
|
<div class="color-positive size-h3">{{ $running }}</div>
|
||||||
|
<div class="size-h6 uppercase color-subdue">Running</div>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1; {{ $divider }}">
|
||||||
|
<div class="{{ if gt $problems 0 }}color-negative{{ else }}color-subdue{{ end }} size-h3">{{ $problems }}</div>
|
||||||
|
<div class="size-h6 uppercase color-subdue">Auffaellig</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="height: 5px; margin-top: 14px; border-radius: 999px; overflow: hidden; background: hsla(220, 30%, 60%, 0.12);">
|
||||||
|
<div style="height: 100%; width: {{ if gt $total 0 }}{{ div (mul $running 100.0) (toFloat $total) }}{{ else }}0{{ end }}%; border-radius: 999px; background: linear-gradient(90deg, hsl(150, 85%, 42%), hsl(172, 95%, 48%));"></div>
|
||||||
|
</div>
|
||||||
|
{{ if gt $problems 0 }}
|
||||||
|
<div style="display: flex; justify-content: center; gap: 8px; flex-wrap: wrap; margin-top: 12px;">
|
||||||
|
{{ range $stacks }}
|
||||||
|
{{ if ne (.String "info.state") "running" }}
|
||||||
|
<span class="size-h6" style="padding: 3px 12px; border-radius: 999px; border: 1px solid hsla(350, 90%, 60%, 0.45); background: hsla(350, 90%, 60%, 0.08); color: var(--color-negative); letter-spacing: 0.05em;">{{ .String "name" }} · {{ .String "info.state" }}</span>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
- 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 }}
|
||||||
|
{{ $divider := "border-left: 1px solid hsla(220, 40%, 70%, 0.14);" }}
|
||||||
|
<div style="display: flex; text-align: center;">
|
||||||
|
<div style="flex: 1;">
|
||||||
|
<div class="color-highlight size-h3">{{ $photos | formatNumber }}</div>
|
||||||
|
<div class="size-h6 uppercase color-subdue">Fotos</div>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1; {{ $divider }}">
|
||||||
|
<div class="color-highlight size-h3">{{ $videos | formatNumber }}</div>
|
||||||
|
<div class="size-h6 uppercase color-subdue">Videos</div>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1; {{ $divider }}">
|
||||||
|
<div class="color-highlight size-h3">{{ printf "%.0f" $usageGiB }} GiB</div>
|
||||||
|
<div class="size-h6 uppercase color-subdue">Medien</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px; margin-top: 16px;">
|
||||||
|
<div style="flex: 1; height: 5px; border-radius: 999px; overflow: hidden; background: hsla(220, 30%, 60%, 0.12);">
|
||||||
|
<div style="height: 100%; width: {{ if $storageOK }}{{ printf "%.1f" $percentage }}%{{ else }}0%{{ end }}; border-radius: 999px; background: linear-gradient(90deg, hsl(205, 100%, 55%), hsl(172, 95%, 48%));"></div>
|
||||||
|
</div>
|
||||||
|
<div class="size-h6 color-subdue" style="white-space: nowrap;">{{ if $storageOK }}{{ printf "%.1f" $percentage }}% belegt{{ else }}Speicher API n/v{{ end }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
- type: group
|
||||||
|
widgets:
|
||||||
|
- type: monitor
|
||||||
|
title: Core
|
||||||
|
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: 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]
|
||||||
|
|
||||||
|
- type: monitor
|
||||||
|
title: Apps
|
||||||
|
cache: 1m
|
||||||
|
sites:
|
||||||
|
- 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]
|
||||||
|
|
||||||
|
- type: monitor
|
||||||
|
title: Ops
|
||||||
|
cache: 1m
|
||||||
|
sites:
|
||||||
|
- 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
|
||||||
|
subrequests:
|
||||||
|
stats:
|
||||||
|
url: http://speedtest-tracker/api/v1/stats
|
||||||
|
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 }}
|
||||||
|
{{ if eq $ip "" }}{{ $ip = .JSON.String "data.data.interface.externalIp" }}{{ end }}
|
||||||
|
{{ $isp := .JSON.String "isp" }}
|
||||||
|
{{ if eq $isp "" }}{{ $isp = .JSON.String "data.isp" }}{{ end }}
|
||||||
|
{{ if eq $isp "" }}{{ $isp = .JSON.String "data.data.isp" }}{{ end }}
|
||||||
|
{{ $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 }}
|
||||||
|
{{ if eq $download 0.0 }}{{ $download = div (mul (.JSON.Float "data.data.download.bandwidth") 8.0) 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 }}
|
||||||
|
{{ if eq $upload 0.0 }}{{ $upload = div (mul (.JSON.Float "data.data.upload.bandwidth") 8.0) 1000000.0 }}{{ end }}
|
||||||
|
{{ if gt $download 100000.0 }}{{ $download = div (mul $download 8.0) 1000000.0 }}{{ end }}
|
||||||
|
{{ if gt $upload 100000.0 }}{{ $upload = div (mul $upload 8.0) 1000000.0 }}{{ end }}
|
||||||
|
{{ $ping := .JSON.Float "ping" }}
|
||||||
|
{{ if eq $ping 0.0 }}{{ $ping = .JSON.Float "data.ping" }}{{ end }}
|
||||||
|
{{ if eq $ping 0.0 }}{{ $ping = .JSON.Float "data.data.ping.latency" }}{{ end }}
|
||||||
|
<div class="text-center" style="margin-bottom: 10px;">
|
||||||
|
<div class="color-primary size-h3" style="font-weight: 700;">{{ if ne $ip "" }}{{ $ip }}{{ else }}WAN online{{ end }}</div>
|
||||||
|
<div class="size-h6 color-subdue">{{ if ne $isp "" }}{{ $isp }}{{ else }}Speedtest Tracker{{ end }}</div>
|
||||||
|
</div>
|
||||||
|
{{ if and (eq $download 0.0) (eq $upload 0.0) }}
|
||||||
|
<div class="text-center color-subdue size-h6">Keine aktuellen Messdaten</div>
|
||||||
|
{{ else }}
|
||||||
|
<div class="flex justify-between text-center">
|
||||||
|
<div>
|
||||||
|
<div class="color-highlight size-h4">{{ printf "%.1f" $download }}</div>
|
||||||
|
<div class="size-h6 color-subdue">MBIT DOWN</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="color-highlight size-h4">{{ printf "%.1f" $upload }}</div>
|
||||||
|
<div class="size-h6 color-subdue">MBIT UP</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="color-highlight size-h4">{{ printf "%.0f ms" $ping }}</div>
|
||||||
|
<div class="size-h6 color-subdue">PING</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
- type: dns-stats
|
||||||
|
title: DNS Stats
|
||||||
|
service: adguard
|
||||||
|
url: http://adguard
|
||||||
|
username: ${GLANCE_ADGUARD_USERNAME}
|
||||||
|
password: ${GLANCE_ADGUARD_PASSWORD}
|
||||||
|
|
||||||
|
- type: custom-api
|
||||||
|
title: Borg Backup
|
||||||
|
title-url: https://borg.kaleschke.info
|
||||||
|
cache: 15m
|
||||||
|
url: http://monitoring-prometheus:9090/api/v1/query?query=(time()-homelab_borg_last_completed_timestamp_seconds)/3600
|
||||||
|
subrequests:
|
||||||
|
success:
|
||||||
|
url: http://monitoring-prometheus:9090/api/v1/query?query=homelab_borg_last_success
|
||||||
|
template: |
|
||||||
|
{{ $ageHours := .JSON.Float "data.result.0.value.1" }}
|
||||||
|
{{ $archive := .JSON.String "data.result.0.metric.archive" }}
|
||||||
|
{{ $succ := .Subrequest "success" }}
|
||||||
|
{{ $ok := $succ.JSON.Float "data.result.0.value.1" }}
|
||||||
|
{{ $status := $succ.JSON.String "data.result.0.metric.status" }}
|
||||||
|
{{ if eq (len (.JSON.Array "data.result")) 0 }}
|
||||||
|
<div class="text-center color-subdue">Keine Backup-Metrik gefunden</div>
|
||||||
|
{{ else }}
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="size-h2 {{ if gt $ageHours 30.0 }}color-negative{{ else }}color-positive{{ end }}">vor {{ printf "%.0f" $ageHours }} h</div>
|
||||||
|
<div class="size-h6 color-subdue" style="margin-top: 4px;">letztes abgeschlossenes Backup</div>
|
||||||
|
<div class="size-h6 {{ if eq $ok 1.0 }}color-positive{{ else }}color-negative{{ end }}" style="margin-top: 6px;">
|
||||||
|
{{ if eq $ok 1.0 }}letzter Job erfolgreich{{ else }}letzter Job: {{ $status }}{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ if ne $archive "" }}<div class="size-h6 color-subdue text-truncate" style="margin-top: 2px;">{{ $archive }}</div>{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
- type: group
|
||||||
|
widgets:
|
||||||
|
- type: docker-containers
|
||||||
|
title: Network
|
||||||
|
category: network
|
||||||
|
hide-by-default: true
|
||||||
|
sock-path: tcp://glance-docker-socket-proxy:2375
|
||||||
|
containers:
|
||||||
|
$include: containers-map.yml
|
||||||
|
|
||||||
|
- type: docker-containers
|
||||||
|
title: Apps
|
||||||
|
category: apps
|
||||||
|
hide-by-default: true
|
||||||
|
sock-path: tcp://glance-docker-socket-proxy:2375
|
||||||
|
containers:
|
||||||
|
$include: containers-map.yml
|
||||||
|
|
||||||
|
- type: docker-containers
|
||||||
|
title: Ops
|
||||||
|
category: ops
|
||||||
|
hide-by-default: true
|
||||||
|
sock-path: tcp://glance-docker-socket-proxy:2375
|
||||||
|
containers:
|
||||||
|
$include: containers-map.yml
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
- 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
|
||||||
|
|
||||||
|
- type: custom-api
|
||||||
|
title: Scrutiny Disk Health
|
||||||
|
title-url: https://scrutiny.kaleschke.info
|
||||||
|
cache: 30m
|
||||||
|
url: http://scrutiny:8080/api/summary
|
||||||
|
template: |
|
||||||
|
{{ $disks := .JSON.Array "data.summary.@values" }}
|
||||||
|
{{ if eq (len $disks) 0 }}
|
||||||
|
<div class="text-center color-subdue">Keine Disks gemeldet.</div>
|
||||||
|
{{ else }}
|
||||||
|
<ul class="list list-gap-4">
|
||||||
|
{{ range $disks }}
|
||||||
|
{{ $status := .Int "device.device_status" }}
|
||||||
|
<li class="flex justify-between">
|
||||||
|
<div class="color-highlight">{{ .String "device.device_name" }}</div>
|
||||||
|
<div class="size-h6 uppercase {{ if eq $status 0 }}color-positive{{ else }}color-negative{{ end }}">
|
||||||
|
{{ if eq $status 0 }}OK{{ else }}FAILED{{ end }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
- size: full
|
||||||
|
widgets:
|
||||||
|
- type: custom-api
|
||||||
|
title: GitOps - homelab-infra
|
||||||
|
title-url: https://git.kaleschke.info/Micha/homelab-infra
|
||||||
|
cache: 5m
|
||||||
|
url: http://gitea:3000/api/v1/repos/Micha/homelab-infra/commits?limit=5&stat=false
|
||||||
|
headers:
|
||||||
|
Authorization: token ${GLANCE_GITEA_TOKEN}
|
||||||
|
Accept: application/json
|
||||||
|
subrequests:
|
||||||
|
repo:
|
||||||
|
url: http://gitea:3000/api/v1/repos/Micha/homelab-infra
|
||||||
|
headers:
|
||||||
|
Authorization: token ${GLANCE_GITEA_TOKEN}
|
||||||
|
Accept: application/json
|
||||||
|
template: |
|
||||||
|
{{ $repo := .Subrequest "repo" }}
|
||||||
|
{{ $repoOK := and (ge $repo.Response.StatusCode 200) (le $repo.Response.StatusCode 299) }}
|
||||||
|
{{ if $repoOK }}
|
||||||
|
<div class="flex justify-between text-center" style="margin-bottom: 12px;">
|
||||||
|
<div>
|
||||||
|
<div class="color-highlight size-h3">{{ $repo.JSON.Int "open_issues_count" }}</div>
|
||||||
|
<div class="size-h6 uppercase">Issues</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="color-highlight size-h3">{{ $repo.JSON.Int "open_pr_counter" }}</div>
|
||||||
|
<div class="size-h6 uppercase">PRs</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="color-highlight size-h3">{{ $repo.JSON.String "default_branch" }}</div>
|
||||||
|
<div class="size-h6 uppercase">Branch</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
<ul class="list list-gap-6">
|
||||||
|
{{ range .JSON.Array "@this" }}
|
||||||
|
<li>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<div class="color-highlight text-truncate" style="max-width: 75%;">{{ .String "commit.message" | replaceMatches "(?s)\n.*" "" }}</div>
|
||||||
|
<div class="size-h6 color-subdue">{{ slice (.String "sha") 0 7 }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="size-h6 color-subdue">{{ .String "commit.author.name" }} · <span {{ .String "commit.author.date" | parseTime "rfc3339" | toRelativeTime }}></span></div>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
- 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: group
|
||||||
|
widgets:
|
||||||
|
- type: docker-containers
|
||||||
|
title: Core
|
||||||
|
category: core
|
||||||
|
hide-by-default: true
|
||||||
|
sock-path: tcp://glance-docker-socket-proxy:2375
|
||||||
|
containers:
|
||||||
|
$include: containers-map.yml
|
||||||
|
|
||||||
|
- type: docker-containers
|
||||||
|
title: Apps
|
||||||
|
category: apps
|
||||||
|
hide-by-default: true
|
||||||
|
sock-path: tcp://glance-docker-socket-proxy:2375
|
||||||
|
containers:
|
||||||
|
$include: containers-map.yml
|
||||||
|
|
||||||
|
- type: docker-containers
|
||||||
|
title: Ops
|
||||||
|
category: ops
|
||||||
|
hide-by-default: true
|
||||||
|
sock-path: tcp://glance-docker-socket-proxy:2375
|
||||||
|
containers:
|
||||||
|
$include: containers-map.yml
|
||||||
|
|
||||||
|
- size: small
|
||||||
|
widgets:
|
||||||
|
- type: custom-api
|
||||||
|
title: Paperless-ngx
|
||||||
|
title-url: https://paperless.kaleschke.info
|
||||||
|
cache: 15m
|
||||||
|
url: http://paperless-ngx:8000/api/statistics/
|
||||||
|
headers:
|
||||||
|
Authorization: Token ${GLANCE_PAPERLESS_TOKEN}
|
||||||
|
Accept: application/json
|
||||||
|
template: |
|
||||||
|
{{ $total := .JSON.Int "documents_total" }}
|
||||||
|
{{ $inbox := .JSON.Int "documents_inbox" }}
|
||||||
|
<div class="flex justify-between text-center">
|
||||||
|
<div>
|
||||||
|
<div class="color-highlight size-h3">{{ $total | formatNumber }}</div>
|
||||||
|
<div class="size-h6 uppercase">Dokumente</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="size-h3 {{ if gt $inbox 0 }}color-negative{{ else }}color-positive{{ end }}">{{ $inbox }}</div>
|
||||||
|
<div class="size-h6 uppercase">Inbox</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
- type: custom-api
|
||||||
|
title: Mealie
|
||||||
|
title-url: https://mealie.kaleschke.info
|
||||||
|
cache: 1h
|
||||||
|
url: http://mealie:9000/api/admin/about/statistics
|
||||||
|
headers:
|
||||||
|
Authorization: Bearer ${GLANCE_MEALIE_TOKEN}
|
||||||
|
Accept: application/json
|
||||||
|
template: |
|
||||||
|
<div class="flex justify-between text-center">
|
||||||
|
<div>
|
||||||
|
<div class="color-highlight size-h3">{{ .JSON.Int "totalRecipes" | formatNumber }}</div>
|
||||||
|
<div class="size-h6 uppercase">Rezepte</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="color-highlight size-h3">{{ .JSON.Int "totalCategories" }}</div>
|
||||||
|
<div class="size-h6 uppercase">Kategorien</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="color-highlight size-h3">{{ .JSON.Int "totalUsers" }}</div>
|
||||||
|
<div class="size-h6 uppercase">Nutzer</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
- 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,80 @@
|
|||||||
|
- name: Ops und Releases
|
||||||
|
slug: ops
|
||||||
|
width: wide
|
||||||
|
columns:
|
||||||
|
- size: small
|
||||||
|
widgets:
|
||||||
|
- type: rss
|
||||||
|
title: Selfhosted News
|
||||||
|
style: vertical-list
|
||||||
|
limit: 12
|
||||||
|
collapse-after: 6
|
||||||
|
cache: 6h
|
||||||
|
feeds:
|
||||||
|
- url: https://selfh.st/rss/
|
||||||
|
title: selfh.st
|
||||||
|
- url: https://tailscale.com/blog/index.xml
|
||||||
|
title: Tailscale Blog
|
||||||
|
|
||||||
|
- size: full
|
||||||
|
widgets:
|
||||||
|
- type: releases
|
||||||
|
title: Image Releases
|
||||||
|
cache: 12h
|
||||||
|
show-source-icon: true
|
||||||
|
collapse-after: 15
|
||||||
|
repositories:
|
||||||
|
- glanceapp/glance
|
||||||
|
- traefik/traefik
|
||||||
|
- go-gitea/gitea
|
||||||
|
- moghtech/komodo
|
||||||
|
- immich-app/immich
|
||||||
|
- paperless-ngx/paperless-ngx
|
||||||
|
- AdguardTeam/AdGuardHome
|
||||||
|
- dani-garcia/vaultwarden
|
||||||
|
- authelia/authelia
|
||||||
|
- mealie-recipes/mealie
|
||||||
|
- nextcloud/server
|
||||||
|
- AnalogJ/scrutiny
|
||||||
|
- alexjustesen/speedtest-tracker
|
||||||
|
- binwiederhier/ntfy
|
||||||
|
- filebrowser/filebrowser
|
||||||
|
- coder/code-server
|
||||||
|
- qdm12/ddns-updater
|
||||||
|
- nicolargo/glances
|
||||||
|
|
||||||
|
- size: small
|
||||||
|
widgets:
|
||||||
|
- type: custom-api
|
||||||
|
title: Letzte Commits
|
||||||
|
title-url: https://git.kaleschke.info/Micha/homelab-infra/commits/branch/master
|
||||||
|
cache: 5m
|
||||||
|
url: http://gitea:3000/api/v1/repos/Micha/homelab-infra/commits?limit=8&stat=false
|
||||||
|
headers:
|
||||||
|
Authorization: token ${GLANCE_GITEA_TOKEN}
|
||||||
|
Accept: application/json
|
||||||
|
template: |
|
||||||
|
<ul class="list list-gap-6 collapsible-container" data-collapse-after="5">
|
||||||
|
{{ range .JSON.Array "@this" }}
|
||||||
|
<li>
|
||||||
|
<div class="color-highlight text-truncate">{{ .String "commit.message" | replaceMatches "(?s)\n.*" "" }}</div>
|
||||||
|
<div class="size-h6 color-subdue">{{ slice (.String "sha") 0 7 }} · <span {{ .String "commit.author.date" | parseTime "rfc3339" | toRelativeTime }}></span></div>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
- type: bookmarks
|
||||||
|
title: Deploy-Kette
|
||||||
|
groups:
|
||||||
|
- title: GitOps
|
||||||
|
color: 212 100 50
|
||||||
|
links:
|
||||||
|
- title: Gitea Repo
|
||||||
|
url: https://git.kaleschke.info/Micha/homelab-infra
|
||||||
|
icon: si:gitea
|
||||||
|
- title: Komodo Stacks
|
||||||
|
url: https://komodo.kaleschke.info
|
||||||
|
icon: sh:komodo
|
||||||
|
- title: Grafana
|
||||||
|
url: https://monitoring.kaleschke.info
|
||||||
|
icon: si:grafana
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
$include: home.yml
|
||||||
|
$include: infrastructure.yml
|
||||||
|
$include: ops.yml
|
||||||
@@ -9,11 +9,20 @@ services:
|
|||||||
GLANCE_ADGUARD_USERNAME: ${GLANCE_ADGUARD_USERNAME:-}
|
GLANCE_ADGUARD_USERNAME: ${GLANCE_ADGUARD_USERNAME:-}
|
||||||
GLANCE_ADGUARD_PASSWORD: ${GLANCE_ADGUARD_PASSWORD:-}
|
GLANCE_ADGUARD_PASSWORD: ${GLANCE_ADGUARD_PASSWORD:-}
|
||||||
GLANCE_SPEEDTEST_API_KEY: ${GLANCE_SPEEDTEST_API_KEY:-}
|
GLANCE_SPEEDTEST_API_KEY: ${GLANCE_SPEEDTEST_API_KEY:-}
|
||||||
|
GLANCE_KOMODO_API_KEY: ${GLANCE_KOMODO_API_KEY:-}
|
||||||
|
GLANCE_KOMODO_API_SECRET: ${GLANCE_KOMODO_API_SECRET:-}
|
||||||
|
GLANCE_GITEA_TOKEN: ${GLANCE_GITEA_TOKEN:-}
|
||||||
|
GLANCE_PAPERLESS_TOKEN: ${GLANCE_PAPERLESS_TOKEN:-}
|
||||||
|
GLANCE_MEALIE_TOKEN: ${GLANCE_MEALIE_TOKEN:-}
|
||||||
|
GLANCE_HA_TOKEN: ${GLANCE_HA_TOKEN:-}
|
||||||
volumes:
|
volumes:
|
||||||
- ./config:/app/config:ro
|
- ./config:/app/config:ro
|
||||||
|
- ./assets:/app/assets:ro
|
||||||
networks:
|
networks:
|
||||||
- frontend_net
|
- frontend_net
|
||||||
- glance_socket_net
|
- glance_socket_net
|
||||||
|
# monitoring_net nur lesend fuer Prometheus-Query des Borg-Backup-Widgets
|
||||||
|
- monitoring_net
|
||||||
depends_on:
|
depends_on:
|
||||||
- glance-docker-socket-proxy
|
- glance-docker-socket-proxy
|
||||||
labels:
|
labels:
|
||||||
@@ -50,6 +59,8 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
frontend_net:
|
frontend_net:
|
||||||
external: true
|
external: true
|
||||||
|
monitoring_net:
|
||||||
|
external: true
|
||||||
glance_socket_net:
|
glance_socket_net:
|
||||||
name: glance_socket_net
|
name: glance_socket_net
|
||||||
internal: true
|
internal: true
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
# H:/ Nearline-Backup — Struktur und Betrieb
|
||||||
|
|
||||||
|
Stand: 2026-06-10
|
||||||
|
|
||||||
|
## Rolle der H:/
|
||||||
|
|
||||||
|
Die externe HDD (asmedia ASM235, 7.4 TB, Laufwerk `H:`) dient ausschließlich als
|
||||||
|
**Nearline-Backup-Spiegel** für kritische Dumps und Git-Bundles.
|
||||||
|
|
||||||
|
Sie ist kein Primär-Backup (das ist Hetzner/Borg) und kein dauerhaftes Archiv.
|
||||||
|
|
||||||
|
## Sollzustand
|
||||||
|
|
||||||
|
```
|
||||||
|
H:\
|
||||||
|
└── kallilab-nearline-backups\
|
||||||
|
├── borg-dumps\latest\ ← aktuelle DB-Dumps (per Script)
|
||||||
|
├── git-bundles\gitea\ ← Gitea-Repo-Bundles (per Script)
|
||||||
|
├── _dr-kit\ ← SSH-Keys, Offline-Secrets (manuell)
|
||||||
|
├── _logs\ ← Robocopy-Logs je Lauf
|
||||||
|
└── _reports\ ← Markdown-Reports je Lauf
|
||||||
|
```
|
||||||
|
|
||||||
|
Nichts weiteres gehört dauerhaft auf die H:/.
|
||||||
|
Temporäre Recovery- oder Backup-Ordner aus Notfallsituationen sind nach
|
||||||
|
Abschluss zu löschen.
|
||||||
|
|
||||||
|
## Automatischer Pull
|
||||||
|
|
||||||
|
`pull-critical-backups.ps1` zieht per Robocopy vom Unraid-SMB-Share:
|
||||||
|
|
||||||
|
- `\\192.168.178.58\backups\borg\dumps\latest` → `borg-dumps\latest\`
|
||||||
|
- `\\192.168.178.58\backups\git-bundles\gitea` → `git-bundles\gitea\`
|
||||||
|
|
||||||
|
Der Windows Scheduled Task `KalliLab H Drive Nearline Pull` laeuft seit
|
||||||
|
2026-05-28 taeglich 05:30. Das Script kopiert bewusst **nicht** mit `/MIR` und
|
||||||
|
loescht nichts auf H:/; alte Artefakte werden nur nach manueller Sichtpruefung
|
||||||
|
entfernt. Aufruf zum Testen:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell.exe -NoProfile -ExecutionPolicy Bypass -File G:\Gitea_Clone\homelab-infra\ops\h-drive-nearline\pull-critical-backups.ps1 -WhatIf
|
||||||
|
```
|
||||||
|
|
||||||
|
Das Script schließt bewusst aus:
|
||||||
|
- `unraid-flash-config.tar.gz` (0600 root:root, nicht per SMB zugänglich → Restore aus Hetzner-Borg)
|
||||||
|
- Migration-/Cutover-Verzeichnisse (`immich-vectorchord-*`, `pg18-major-*`, `redis8-*` etc.)
|
||||||
|
|
||||||
|
## _dr-kit
|
||||||
|
|
||||||
|
Enthält offline hinterlegte Schlüssel und Secrets für den DR-Fall:
|
||||||
|
- `dr-hetzner` / `dr-hetzner.pub` — SSH-Key für Hetzner Storage Box
|
||||||
|
- `dr-readonly` / `dr-readonly.pub` — Read-only Deploy-Key
|
||||||
|
- `KOmodo Secrets.txt` — Komodo Stack ENV-Offline-Dokumentation
|
||||||
|
|
||||||
|
Diese Dateien sind **manuell** zu pflegen und **nicht** vom Pull-Script verwaltet.
|
||||||
|
|
||||||
|
## Archiv-Ordner
|
||||||
|
|
||||||
|
Temporäre Notfall-Artefakte verbleiben als `_archiv-*`-Ordner bis zur bewussten
|
||||||
|
Löschentscheidung:
|
||||||
|
|
||||||
|
| Ordner | Inhalt | Anlassdatum |
|
||||||
|
|---|---|---|
|
||||||
|
| `kallilab-recovery\_archiv-nvme-crash-image-2026-05-14\` | nvme0n1 Disk-Image (1863 GB) + Crash-Runbooks aus dem Mai-2026-Ausfall | 2026-05-14 |
|
||||||
|
|
||||||
|
## Aufräum-Historie
|
||||||
|
|
||||||
|
| Datum | Aktion |
|
||||||
|
|---|---|
|
||||||
|
| 2026-06-10 | `OneDrive - Stroetmann Group\` gelöscht (leer) |
|
||||||
|
| 2026-06-10 | SSH-Keys + Secrets aus nearline-Root in `_dr-kit\` verschoben |
|
||||||
|
| 2026-06-10 | Migration-Artefakt-Verzeichnisse in `borg-dumps\latest\` gelöscht (immich-vectorchord-*, pg18-major-*, redis8-*, nextcloud-redis-pre-redis8-*, shared-redis-pre-redis8-*) |
|
||||||
|
| 2026-06-10 | Pre-major-prod-Dumps gelöscht (PG17→PG18-Migration abgeschlossen) |
|
||||||
|
| 2026-06-10 | `kallilab-recovery\2026-05-15\` gelöscht (DNS-Restore-Reste) |
|
||||||
|
| 2026-06-10 | `kallilab-recovery\2026-05-14\` → `_archiv-nvme-crash-image-2026-05-14\` umbenannt |
|
||||||
|
| 2026-06-10 | `kallilab-recovery\disk1-phase2-2026-05-23\` gelöscht (1677 GB Media-Share-Kopie; Unraid-Share verifiziert vollständig) |
|
||||||
|
|
||||||
|
## Offene Punkte
|
||||||
|
|
||||||
|
- `Windows-Neuaufsetzen-Backup\` (48 GB): nach vollständiger Rückspielung auf D:\ löschen
|
||||||
|
- `_archiv-nvme-crash-image-2026-05-14\` (1863 GB): löschen sobald sicher, dass nichts mehr aus dem alten System benötigt wird
|
||||||
|
- Log-Rotation für `_logs\` und `_reports\`: manuell oder per Script, Empfehlung 30 Tage
|
||||||
@@ -25,6 +25,7 @@ $Jobs = @(
|
|||||||
"immich.dump",
|
"immich.dump",
|
||||||
"komodo-mongo.archive.gz",
|
"komodo-mongo.archive.gz",
|
||||||
"mealie.dump",
|
"mealie.dump",
|
||||||
|
"n8n.sqlite.dump",
|
||||||
"nextcloud.dump",
|
"nextcloud.dump",
|
||||||
"postgresql17-authelia.dump",
|
"postgresql17-authelia.dump",
|
||||||
"postgresql17-globals.sql",
|
"postgresql17-globals.sql",
|
||||||
|
|||||||
@@ -45,13 +45,13 @@
|
|||||||
"description": "VPN / Remote-Zugang",
|
"description": "VPN / Remote-Zugang",
|
||||||
"tier": 1,
|
"tier": 1,
|
||||||
"category": "core",
|
"category": "core",
|
||||||
"container_name": "tailscale",
|
"container_name": null,
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"url": null,
|
"url": null,
|
||||||
"dump_file": null,
|
"dump_file": null,
|
||||||
"data_paths": ["/mnt/user/appdata/tailscale"],
|
"data_paths": ["/boot/config/plugins/tailscale/state"],
|
||||||
"first_check": "Tailscale Status auf Host pruefen; State-Datei fuer Key-Renewal vorhanden?",
|
"first_check": "Tailscale Status auf Host pruefen; native Unraid-Plugin-Instanz und Subnet-Route aktiv?",
|
||||||
"notes": "network_mode: host; NET_ADMIN, NET_RAW, /dev/net/tun — dokumentierte VPN-Ausnahmen"
|
"notes": "Natives Unraid-Plugin, nicht Docker/Komodo-verwaltet; State liegt im Flash-Backup. Alter Docker-State ist archiviert unter /mnt/user/appdata/_archive/tailscale-removed-2026-06-06/"
|
||||||
},
|
},
|
||||||
"gitea": {
|
"gitea": {
|
||||||
"description": "Git-Server — operative Quelle der Wahrheit fuer GitOps",
|
"description": "Git-Server — operative Quelle der Wahrheit fuer GitOps",
|
||||||
|
|||||||
@@ -75,14 +75,14 @@ services:
|
|||||||
description: VPN / Remote-Zugang
|
description: VPN / Remote-Zugang
|
||||||
tier: 1
|
tier: 1
|
||||||
category: core
|
category: core
|
||||||
container_name: tailscale
|
container_name: null
|
||||||
dependencies: []
|
dependencies: []
|
||||||
url: null
|
url: null
|
||||||
dump_file: null
|
dump_file: null
|
||||||
data_paths:
|
data_paths:
|
||||||
- /mnt/user/appdata/tailscale
|
- /boot/config/plugins/tailscale/state
|
||||||
first_check: "Tailscale Status auf Host pruefen; State-Datei fuer Key-Renewal vorhanden?"
|
first_check: "Tailscale Status auf Host pruefen; native Unraid-Plugin-Instanz und Subnet-Route aktiv?"
|
||||||
notes: "network_mode: host; NET_ADMIN, NET_RAW, /dev/net/tun — dokumentierte VPN-Ausnahmen"
|
notes: "Natives Unraid-Plugin, nicht Docker/Komodo-verwaltet; State liegt im Flash-Backup. Alter Docker-State ist archiviert unter /mnt/user/appdata/_archive/tailscale-removed-2026-06-06/"
|
||||||
|
|
||||||
gitea:
|
gitea:
|
||||||
description: Git-Server — operative Quelle der Wahrheit fuer GitOps
|
description: Git-Server — operative Quelle der Wahrheit fuer GitOps
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ services:
|
|||||||
# ──────────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────────
|
||||||
# MongoDB – Datenbank fuer Komodo Core
|
# MongoDB – Datenbank fuer Komodo Core
|
||||||
# Netz: komodo_net (internal: true) – niemals frontend_net
|
# Netz: komodo_net (internal: true) – niemals frontend_net
|
||||||
|
# ACHTUNG: Dieser Stack wird NICHT aus diesem Repo deployed. Der komodo-Stack
|
||||||
|
# ist in Komodo inline (file_contents) verwaltet (Bootstrap-/Self-Stack).
|
||||||
|
# Diese Datei ist nur Doku/Spiegel; Aenderungen hier wirken NICHT zur Laufzeit.
|
||||||
|
# ops/komodo/** ist in renovate.json ignorePaths. Siehe docs/RENOVATE.md.
|
||||||
|
# Digest = aktuell real laufender Stand (kein Renovate-Auto-Update).
|
||||||
# ──────────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────────
|
||||||
komodo-mongo:
|
komodo-mongo:
|
||||||
image: mongo:8.0.23@sha256:44aa79ae28ff80b56fe58681b66cda9336706df408a5175a6c04988aa54610d3
|
image: mongo:8.0.23@sha256:44aa79ae28ff80b56fe58681b66cda9336706df408a5175a6c04988aa54610d3
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
param(
|
||||||
|
[string]$ReportPath = "G:\Gitea_Clone\homelab-infra\docs\audit\dr-workstation-readiness-2026-06-06.md"
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Invoke-Capture {
|
||||||
|
param([string]$Command)
|
||||||
|
|
||||||
|
$output = & cmd.exe /c $Command 2>&1
|
||||||
|
[pscustomobject]@{
|
||||||
|
Command = $Command
|
||||||
|
ExitCode = $LASTEXITCODE
|
||||||
|
Output = ($output | ForEach-Object { ([string]$_).Replace("`0", "") })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-WslCapture {
|
||||||
|
param([string]$Bash)
|
||||||
|
Invoke-Capture -Command ('wsl -d Ubuntu -- bash -lc ' + '"' + ($Bash.Replace('"', '\"')) + '"')
|
||||||
|
}
|
||||||
|
|
||||||
|
$checks = [ordered]@{}
|
||||||
|
$checks["wsl_status"] = Invoke-Capture -Command "wsl --status"
|
||||||
|
$checks["wsl_list"] = Invoke-Capture -Command "wsl --list --verbose"
|
||||||
|
$checks["ubuntu_os"] = Invoke-WslCapture -Bash "lsb_release -a 2>/dev/null || cat /etc/os-release; uname -r"
|
||||||
|
$checks["tools"] = Invoke-WslCapture -Bash "command -v borg || true; borg --version 2>/dev/null || true; command -v ssh || true; ssh -V 2>&1 || true; command -v git || true; git --version 2>/dev/null || true"
|
||||||
|
$checks["sudo"] = Invoke-WslCapture -Bash "sudo -n true >/dev/null 2>&1 && echo sudo-noprompt-ok || echo sudo-password-needed"
|
||||||
|
$checks["wsl_ssh_files"] = Invoke-WslCapture -Bash "ls -la ~/.ssh 2>/dev/null || true; test -f ~/dr-smoke.sh && ls -la ~/dr-smoke.sh || true"
|
||||||
|
$checks["github_dr_key_smoke"] = Invoke-WslCapture -Bash "GIT_SSH_COMMAND='ssh -i ~/.ssh/dr-readonly -o BatchMode=yes -o IdentitiesOnly=yes -o ConnectTimeout=8' git ls-remote git@github.com:michaelkaleschke-spec/homelab-infra.git HEAD 2>&1 | sed -n '1,5p'"
|
||||||
|
$checks["hetzner_dr_key_smoke"] = Invoke-WslCapture -Bash "ssh -i ~/.ssh/dr-hetzner -o BatchMode=yes -o IdentitiesOnly=yes -o ConnectTimeout=8 -p 23 u565255@u565255.your-storagebox.de 'ls' 2>&1 | sed -n '1,10p'"
|
||||||
|
|
||||||
|
$borgInstalled = ($checks["tools"].Output -match "borg \d")
|
||||||
|
$githubOk = ($checks["github_dr_key_smoke"].Output -match "HEAD")
|
||||||
|
$hetznerOk = ($checks["hetzner_dr_key_smoke"].Output -match "hetzner_borg_appdata_critical")
|
||||||
|
$sudoNeedsPassword = ($checks["sudo"].Output -match "sudo-password-needed")
|
||||||
|
$drSmokeExists = ($checks["wsl_ssh_files"].Output -match "dr-smoke.sh")
|
||||||
|
|
||||||
|
$lines = @()
|
||||||
|
$lines += "# DR-Workstation Readiness - 2026-06-06"
|
||||||
|
$lines += ""
|
||||||
|
$lines += "Automatisch erzeugter lokaler Readiness-Check fuer den Operator-PC. Es wurden keine Secret-Werte, Passphrases oder Private-Key-Inhalte ausgegeben."
|
||||||
|
$lines += ""
|
||||||
|
$lines += "## Zusammenfassung"
|
||||||
|
$lines += ""
|
||||||
|
$lines += "| Check | Ergebnis |"
|
||||||
|
$lines += "|---|---|"
|
||||||
|
$lines += '| WSL2 Ubuntu | vorhanden (`Ubuntu 24.04`, WSL Version 2) |'
|
||||||
|
$lines += "| SSH/Git in WSL | vorhanden |"
|
||||||
|
$lines += "| GitHub-Read-Smoke mit DR-Key | " + ($(if ($githubOk) { "ok" } else { "nicht ok" })) + " |"
|
||||||
|
$lines += "| Borg Client | " + ($(if ($borgInstalled) { "installiert" } else { "fehlt" })) + " |"
|
||||||
|
$lines += "| Hetzner Storage Box mit DR-Key | " + ($(if ($hetznerOk) { "ok" } else { "nicht ok" })) + " |"
|
||||||
|
$lines += '| `~/dr-smoke.sh` | ' + ($(if ($drSmokeExists) { "vorhanden" } else { "fehlt" })) + ' |'
|
||||||
|
$lines += "| WSL sudo ohne Passwortprompt | " + ($(if ($sudoNeedsPassword) { "nein, Operator muss Passwort eingeben" } else { "ja" })) + " |"
|
||||||
|
$lines += ""
|
||||||
|
$lines += "## Bewertung"
|
||||||
|
$lines += ""
|
||||||
|
$lines += "- Der lokale WSL2-/Ubuntu-Unterbau ist vorhanden."
|
||||||
|
$lines += '- Die DR-Key-Arbeitskopien liegen in WSL unter `~/.ssh/dr-readonly` und `~/.ssh/dr-hetzner`.'
|
||||||
|
$lines += "- GitHub-Read-Smoke und Hetzner-SSH-Smoke sind erfolgreich."
|
||||||
|
$lines += '- `borgbackup` ist installiert.'
|
||||||
|
$lines += "- Der vollstaendige Bare-Metal-DR-Smoke wartet nur noch auf die interaktive Borg-Passphrase."
|
||||||
|
$lines += ""
|
||||||
|
$lines += "## Naechste Operator-Schritte"
|
||||||
|
$lines += ""
|
||||||
|
$lines += "In Ubuntu ausfuehren:"
|
||||||
|
$lines += ""
|
||||||
|
$lines += '```bash'
|
||||||
|
$lines += "bash ~/dr-smoke.sh"
|
||||||
|
$lines += '```'
|
||||||
|
$lines += ""
|
||||||
|
$lines += 'Borg fragt dabei interaktiv nach der Borg-Repo-Passphrase. Diese Passphrase wurde nicht auf `baerchen` gefunden und wird bewusst nicht dauerhaft gespeichert.'
|
||||||
|
$lines += ""
|
||||||
|
$lines += "## Rohchecks"
|
||||||
|
$lines += ""
|
||||||
|
foreach ($name in $checks.Keys) {
|
||||||
|
$check = $checks[$name]
|
||||||
|
$lines += "### $name"
|
||||||
|
$lines += ""
|
||||||
|
$lines += '- ExitCode: `' + $check.ExitCode + '`'
|
||||||
|
$lines += ""
|
||||||
|
$lines += '```text'
|
||||||
|
$lines += ($check.Output | ForEach-Object {
|
||||||
|
$_ -replace ([regex]::Escape($env:USERPROFILE)), '%USERPROFILE%'
|
||||||
|
})
|
||||||
|
$lines += '```'
|
||||||
|
$lines += ""
|
||||||
|
}
|
||||||
|
|
||||||
|
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $ReportPath) | Out-Null
|
||||||
|
while ($lines.Count -gt 0 -and $lines[-1] -eq "") {
|
||||||
|
$lines = $lines[0..($lines.Count - 2)]
|
||||||
|
}
|
||||||
|
$lines -join "`r`n" | Set-Content -LiteralPath $ReportPath -Encoding UTF8
|
||||||
|
Write-Host "Report written: $ReportPath"
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
param(
|
||||||
|
[string]$HostLanIp = "192.168.178.58",
|
||||||
|
[string]$FritzBoxIp = "192.168.178.1",
|
||||||
|
[ValidateSet("LanPreflight", "Guest")]
|
||||||
|
[string]$Mode = "LanPreflight",
|
||||||
|
[string]$ReportPath = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Test-TcpPort {
|
||||||
|
param(
|
||||||
|
[string]$RemoteHost,
|
||||||
|
[int]$Port,
|
||||||
|
[int]$TimeoutMs = 1500
|
||||||
|
)
|
||||||
|
|
||||||
|
$client = [System.Net.Sockets.TcpClient]::new()
|
||||||
|
try {
|
||||||
|
$async = $client.BeginConnect($RemoteHost, $Port, $null, $null)
|
||||||
|
$ok = $async.AsyncWaitHandle.WaitOne($TimeoutMs, $false)
|
||||||
|
if (-not $ok) {
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
$client.EndConnect($async)
|
||||||
|
return $true
|
||||||
|
} catch {
|
||||||
|
return $false
|
||||||
|
} finally {
|
||||||
|
$client.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Add-Result {
|
||||||
|
param(
|
||||||
|
[System.Collections.Generic.List[object]]$Results,
|
||||||
|
[string]$Name,
|
||||||
|
[string]$Target,
|
||||||
|
[bool]$Reachable,
|
||||||
|
[string]$ExpectedGuest,
|
||||||
|
[string]$Risk
|
||||||
|
)
|
||||||
|
|
||||||
|
$Results.Add([pscustomobject]@{
|
||||||
|
Name = $Name
|
||||||
|
Target = $Target
|
||||||
|
Reachable = $Reachable
|
||||||
|
ExpectedFromGuest = $ExpectedGuest
|
||||||
|
RiskIfReachableFromGuest = $Risk
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$adapters = Get-NetIPConfiguration |
|
||||||
|
Where-Object { $_.IPv4Address -and $_.NetAdapter.Status -eq "Up" } |
|
||||||
|
Select-Object InterfaceAlias,
|
||||||
|
@{Name="IPv4";Expression={$_.IPv4Address.IPAddress -join ", "}},
|
||||||
|
@{Name="Gateway";Expression={$_.IPv4DefaultGateway.NextHop -join ", "}},
|
||||||
|
@{Name="DnsServer";Expression={$_.DNSServer.ServerAddresses -join ", "}}
|
||||||
|
|
||||||
|
$results = [System.Collections.Generic.List[object]]::new()
|
||||||
|
|
||||||
|
Add-Result $results "Unraid HTTP/LAN" "${HostLanIp}:80" (Test-TcpPort $HostLanIp 80) "blocked" "Guest can reach LAN web entrypoint directly"
|
||||||
|
Add-Result $results "Unraid HTTPS/LAN" "${HostLanIp}:443" (Test-TcpPort $HostLanIp 443) "blocked" "Guest can reach LAN HTTPS entrypoint directly"
|
||||||
|
Add-Result $results "Gitea SSH/LAN" "${HostLanIp}:222" (Test-TcpPort $HostLanIp 222) "blocked" "Guest can reach Git SSH"
|
||||||
|
Add-Result $results "AdGuard Admin/LAN" "${HostLanIp}:8082" (Test-TcpPort $HostLanIp 8082) "blocked" "Guest can reach AdGuard admin UI"
|
||||||
|
Add-Result $results "InfluxDB LAN" "${HostLanIp}:8181" (Test-TcpPort $HostLanIp 8181) "blocked" "Guest can reach InfluxDB writer endpoint"
|
||||||
|
Add-Result $results "FRITZ!Box LAN UI" "${FritzBoxIp}:80" (Test-TcpPort $FritzBoxIp 80) "blocked-or-guest-gateway-only" "Guest can reach main router UI"
|
||||||
|
|
||||||
|
$risk = if ($Mode -eq "Guest") {
|
||||||
|
$results | Where-Object {
|
||||||
|
$_.ExpectedFromGuest -like "blocked*" -and $_.Reachable
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$results | Where-Object {
|
||||||
|
$_.Name -in @("AdGuard Admin/LAN", "InfluxDB LAN") -and $_.Reachable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||||
|
$lines = [System.Collections.Generic.List[string]]::new()
|
||||||
|
$lines.Add("# Guest/IoT Isolation Check")
|
||||||
|
$lines.Add("")
|
||||||
|
$lines.Add("Timestamp: $timestamp")
|
||||||
|
$lines.Add("Mode: $Mode")
|
||||||
|
$lines.Add("Host LAN IP: $HostLanIp")
|
||||||
|
$lines.Add("FRITZ!Box IP: $FritzBoxIp")
|
||||||
|
$lines.Add("Risk count: $($risk.Count)")
|
||||||
|
$lines.Add("")
|
||||||
|
$lines.Add("## Active Network Adapters")
|
||||||
|
$lines.Add("")
|
||||||
|
$lines.Add("| Interface | IPv4 | Gateway | DNS |")
|
||||||
|
$lines.Add("|---|---|---|---|")
|
||||||
|
foreach ($adapter in $adapters) {
|
||||||
|
$lines.Add("| $($adapter.InterfaceAlias) | $($adapter.IPv4) | $($adapter.Gateway) | $($adapter.DnsServer) |")
|
||||||
|
}
|
||||||
|
$lines.Add("")
|
||||||
|
$lines.Add("## Port Tests")
|
||||||
|
$lines.Add("")
|
||||||
|
$lines.Add("| Name | Target | Reachable | Expected from guest Wi-Fi | Risk if reachable from guest |")
|
||||||
|
$lines.Add("|---|---|---:|---|---|")
|
||||||
|
foreach ($result in $results) {
|
||||||
|
$lines.Add("| $($result.Name) | $($result.Target) | $($result.Reachable) | $($result.ExpectedFromGuest) | $($result.RiskIfReachableFromGuest) |")
|
||||||
|
}
|
||||||
|
$lines.Add("")
|
||||||
|
$lines.Add("## Interpretation")
|
||||||
|
$lines.Add("")
|
||||||
|
$lines.Add("- `LanPreflight`: reachable `80/443/222` can be normal; `8082` and `8181` should still be blocked.")
|
||||||
|
$lines.Add("- `Guest`: all listed LAN targets should be blocked. Public domains may still work via the internet path.")
|
||||||
|
$lines.Add("- A non-zero risk count means the selected mode failed.")
|
||||||
|
|
||||||
|
$text = $lines -join [Environment]::NewLine
|
||||||
|
|
||||||
|
if ($ReportPath) {
|
||||||
|
$parent = Split-Path -Parent $ReportPath
|
||||||
|
if ($parent) {
|
||||||
|
New-Item -ItemType Directory -Force -Path $parent | Out-Null
|
||||||
|
}
|
||||||
|
Set-Content -Path $ReportPath -Value $text -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output $text
|
||||||
|
|
||||||
|
if ($risk.Count -gt 0) {
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
exit 0
|
||||||
Executable
+90
@@ -0,0 +1,90 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
HOST_LAN_IP="${HOST_LAN_IP:-192.168.178.58}"
|
||||||
|
TAILSCALE_IP="${TAILSCALE_IP:-100.80.98.33}"
|
||||||
|
FRITZBOX_TR064_URL="${FRITZBOX_TR064_URL:-http://192.168.178.1:49000/tr64desc.xml}"
|
||||||
|
REPORT_ROOT="${REPORT_ROOT:-/mnt/user/backups/restore-reports}"
|
||||||
|
STAMP="$(date +%F-%H%M%S)"
|
||||||
|
REPORT_FILE="$REPORT_ROOT/guest-iot-preflight-$STAMP.md"
|
||||||
|
|
||||||
|
mkdir -p "$REPORT_ROOT"
|
||||||
|
|
||||||
|
tcp_check() {
|
||||||
|
local host="$1"
|
||||||
|
local port="$2"
|
||||||
|
timeout 2 bash -c "cat < /dev/null > /dev/tcp/$host/$port" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
result_row() {
|
||||||
|
local name="$1"
|
||||||
|
local target="$2"
|
||||||
|
local expectation="$3"
|
||||||
|
local status="$4"
|
||||||
|
printf '| %s | `%s` | %s | %s |\n' "$name" "$target" "$status" "$expectation"
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# Guest/IoT Preflight"
|
||||||
|
echo
|
||||||
|
echo "Timestamp: $(date '+%F %T')"
|
||||||
|
echo "Scope: host-side read-only checks before enabling FRITZ!Box guest/IoT network"
|
||||||
|
echo
|
||||||
|
echo "## FRITZ!Box TR-064"
|
||||||
|
echo
|
||||||
|
if curl -fsS --max-time 5 "$FRITZBOX_TR064_URL" >/tmp/guest-iot-fritzbox-tr064.xml 2>/dev/null; then
|
||||||
|
model="$(grep -o '<friendlyName>[^<]*' /tmp/guest-iot-fritzbox-tr064.xml | head -n1 | sed 's/<friendlyName>//')"
|
||||||
|
echo "- TR-064 descriptor reachable: yes"
|
||||||
|
echo "- Model: ${model:-unknown}"
|
||||||
|
else
|
||||||
|
echo "- TR-064 descriptor reachable: no"
|
||||||
|
fi
|
||||||
|
rm -f /tmp/guest-iot-fritzbox-tr064.xml
|
||||||
|
echo
|
||||||
|
echo "## Host listeners"
|
||||||
|
echo
|
||||||
|
echo '```text'
|
||||||
|
ss -ltnp | sort -k4 | grep -E ':(53|80|443|222|8082|8181)[[:space:]]' || true
|
||||||
|
echo '```'
|
||||||
|
echo
|
||||||
|
echo "## Port reachability from host namespace"
|
||||||
|
echo
|
||||||
|
echo "| Check | Target | Status | Expectation |"
|
||||||
|
echo "|---|---|---|---|"
|
||||||
|
|
||||||
|
for port in 80 443 222 53; do
|
||||||
|
if tcp_check "$HOST_LAN_IP" "$port"; then
|
||||||
|
result_row "LAN service" "$HOST_LAN_IP:$port" "may be reachable from normal LAN; must be blocked from guest Wi-Fi" "reachable"
|
||||||
|
else
|
||||||
|
result_row "LAN service" "$HOST_LAN_IP:$port" "not reachable from host namespace or UDP-only" "blocked"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if tcp_check "$HOST_LAN_IP" 8082; then
|
||||||
|
result_row "AdGuard Admin via LAN IP" "$HOST_LAN_IP:8082" "should be blocked" "reachable"
|
||||||
|
else
|
||||||
|
result_row "AdGuard Admin via LAN IP" "$HOST_LAN_IP:8082" "should be blocked" "blocked"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if tcp_check "$TAILSCALE_IP" 8082; then
|
||||||
|
result_row "AdGuard Admin via Tailscale IP" "$TAILSCALE_IP:8082" "operator path should work" "reachable"
|
||||||
|
else
|
||||||
|
result_row "AdGuard Admin via Tailscale IP" "$TAILSCALE_IP:8082" "operator path should work" "blocked"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if tcp_check "$HOST_LAN_IP" 8181; then
|
||||||
|
result_row "InfluxDB via LAN IP" "$HOST_LAN_IP:8181" "should be blocked unless HA LAN writer is reintroduced" "reachable"
|
||||||
|
else
|
||||||
|
result_row "InfluxDB via LAN IP" "$HOST_LAN_IP:8181" "should be blocked unless HA LAN writer is reintroduced" "blocked"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
echo "## Next operator step"
|
||||||
|
echo
|
||||||
|
echo "Enable FRITZ!Box guest Wi-Fi only after confirming LAN isolation is active. Then connect a phone/laptop to guest Wi-Fi and run:"
|
||||||
|
echo
|
||||||
|
echo '```powershell'
|
||||||
|
echo 'G:\Gitea_Clone\homelab-infra\ops\maintenance\check-guest-iot-isolation.ps1 -Mode Guest'
|
||||||
|
echo '```'
|
||||||
|
} | tee "$REPORT_FILE"
|
||||||
|
|
||||||
|
echo "Guest/IoT preflight report: $REPORT_FILE"
|
||||||
Executable
+41
@@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# DR-Workstation Quartals-Smoke
|
||||||
|
#
|
||||||
|
# Prueft Git-Read, Hetzner-SSH und Borg-Repo-Erreichbarkeit vom Operator-PC.
|
||||||
|
# Speichert keine Passphrase. Borg fragt interaktiv nach der Repo-Passphrase.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
GITHUB_KEY="${GITHUB_KEY:-$HOME/.ssh/dr-readonly}"
|
||||||
|
HETZNER_KEY="${HETZNER_KEY:-$HOME/.ssh/dr-hetzner}"
|
||||||
|
GITHUB_REPO="${GITHUB_REPO:-git@github.com:michaelkaleschke-spec/homelab-infra.git}"
|
||||||
|
BORG_REPO="${BORG_REPO:-ssh://u565255@u565255.your-storagebox.de/./hetzner_borg_appdata_critical}"
|
||||||
|
|
||||||
|
echo "=== Tooling ==="
|
||||||
|
command -v ssh
|
||||||
|
command -v git
|
||||||
|
command -v borg
|
||||||
|
borg --version
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "=== Key files ==="
|
||||||
|
test -r "$GITHUB_KEY" || { echo "Missing GitHub key: $GITHUB_KEY" >&2; exit 1; }
|
||||||
|
test -r "$HETZNER_KEY" || { echo "Missing Hetzner key: $HETZNER_KEY" >&2; exit 1; }
|
||||||
|
ls -l "$GITHUB_KEY" "$HETZNER_KEY"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "=== GitHub Deploy-Key ==="
|
||||||
|
GIT_SSH_COMMAND="ssh -i $GITHUB_KEY -o IdentitiesOnly=yes -o BatchMode=yes" \
|
||||||
|
git ls-remote "$GITHUB_REPO" HEAD
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "=== Hetzner SSH-Login ==="
|
||||||
|
ssh -i "$HETZNER_KEY" -o IdentitiesOnly=yes -o BatchMode=yes -p 23 \
|
||||||
|
u565255@u565255.your-storagebox.de "ls" | head -5
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "=== Borg-Repo ==="
|
||||||
|
export BORG_RSH="ssh -i $HETZNER_KEY -o IdentitiesOnly=yes -p 23"
|
||||||
|
borg info "$BORG_REPO" | head -12
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "DR-Smoke OK ($(date '+%F %T'))"
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Policy Check Report
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
- Compose files checked: 29
|
|
||||||
- Critical findings: 0
|
|
||||||
- Warnings: 1
|
|
||||||
- Info findings: 13
|
|
||||||
|
|
||||||
## Critical
|
|
||||||
- none
|
|
||||||
|
|
||||||
## Warnings
|
|
||||||
- [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: 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.
|
|
||||||
- [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
|
|
||||||
|
|
||||||
+64
-79
@@ -1,101 +1,86 @@
|
|||||||
# Restore Tests
|
# Restore-Tests - Betrieb und Werkzeuge
|
||||||
|
|
||||||
Kontrollierte Restore-Tests fuer `homelab-infra`.
|
Typ: Runbook/Tool-Doku · Stand: 2026-06-11 · Status: aktiv
|
||||||
|
|
||||||
Ziel:
|
Kontrollierte Restore-Tests fuer `homelab-infra`. Dieses Dokument ist das
|
||||||
|
**einzige** Betriebsdokument fuer Restore-Tests (das fruehere
|
||||||
|
`docs/RESTORE_HANDBOOK.md` ist hierin aufgegangen). Verwandt:
|
||||||
|
|
||||||
- Backups durch echte Test-Restores verifizieren
|
- `docs/RESTORE_MATRIX.md` - Restore-Quellen, Secrets, Smoke-Tests und **Test-Reifegrad je Dienst** (einziger Status-Ort)
|
||||||
- produktive Pfade nicht beschreiben
|
- `docs/DISASTER_RECOVERY.md` - echter Wiederanlauf
|
||||||
- Testlaeufe spaeter weitgehend automatisieren
|
- `schedule.md` - Kadenz, Cron-Ausdruecke und Shell-Guards
|
||||||
|
- `unraid-user-scripts.md` - Unraid-User-Script-Vorlagen fuer die Host-Jobs
|
||||||
|
|
||||||
## Grundregeln
|
## Grundregeln
|
||||||
|
|
||||||
- Restore-Quelle bleibt im Backup-Bereich, z. B. `/mnt/user/backups/borg`
|
- Restore-Quelle bleibt das produktive Borg-Repo bei Hetzner; Zugriff ueber den vorhandenen `borg-ui`-Container
|
||||||
- Test-Restores laufen nur in `/mnt/user/backups/restore-lab`
|
- Passphrase kommt aus `/mnt/user/appdata/secrets/borg_repo_passphrase.txt`, nie aus UI-Interna
|
||||||
- Reports landen in `/mnt/user/backups/restore-reports`
|
- Testdaten landen nur unter `/mnt/user/backups/restore-lab/<dienst>`; bei Fehlschlag wird nach `_failed/` verschoben statt geloescht
|
||||||
- Test-Container nutzen das Praefix `restoretest-`
|
- Reports landen unter `/mnt/user/backups/restore-reports`
|
||||||
- keine produktiven Volumes schreibend mounten
|
- Testcontainer nutzen das Praefix `restoretest-`, localhost-Ports, keine produktive Domain, keine Traefik-Route
|
||||||
- keine produktiven Domains fuer Testinstanzen uebernehmen
|
- keine produktiven Volumes schreibend mounten, keine produktiven Pfade beschreiben
|
||||||
|
- keine Restore-Automatik fuer neue Dienste ohne bewusste Freigabe
|
||||||
|
|
||||||
## Geplante Struktur
|
## Erfolgskriterien
|
||||||
|
|
||||||
- `schedule.md`: Intervalle und Verantwortlichkeiten
|
Ein Restore-Test gilt nur dann als erfolgreich, wenn Quelle lesbar war, Daten
|
||||||
- `common.sh`: gemeinsame Helfer fuer Borg-Lookup, Borg-Extract und Compose-Cleanup; prueft vor Borg-Operationen auch `borg-ui:/data/borg.db` und `borg-ui:/local/secrets/borg_repo_passphrase.txt`
|
im Restore-Lab ankamen, der Testcontainer startete, der **fachliche**
|
||||||
- `vaultwarden-restore-test.ps1`: erster Mini-Restore-Ablauf
|
Smoke-Test gelang und ein Report geschrieben wurde. "Container laeuft" allein
|
||||||
- `vaultwarden-restore-test.sh`: hosttauglicher Vaultwarden-Restore-Job
|
reicht nicht.
|
||||||
- `vaultwarden-plan.md`: konkreter Vaultwarden-Testplan
|
|
||||||
- `vaultwarden-compose.test.yml`: isolierte Testinstanz fuer Vaultwarden
|
|
||||||
- `gitea-restore-test.ps1`: Gitea-Mini-Restore-Ablauf
|
|
||||||
- `gitea-restore-test.sh`: hosttauglicher Gitea-Restore-Job
|
|
||||||
- `gitea-plan.md`: konkreter Gitea-Testplan
|
|
||||||
- `gitea-compose.test.yml`: isolierte Testinstanz fuer Gitea
|
|
||||||
- `paperless-restore-test.ps1`: Paperless-Mini-Restore-Ablauf
|
|
||||||
- `paperless-restore-test.sh`: hosttauglicher Paperless-Restore-Job
|
|
||||||
- `paperless-plan.md`: konkreter Paperless-Testplan
|
|
||||||
- `paperless-compose.test.yml`: isolierte Testinstanz fuer Paperless inkl. Test-Postgres und Test-Redis
|
|
||||||
- `immich-restore-test.ps1`: Immich-Mini-Restore-Ablauf als Plan-/Windows-Scaffold
|
|
||||||
- `immich-restore-test.sh`: hosttauglicher Immich-Restore-Job, erster echter Lauf noch offen
|
|
||||||
- `immich-plan.md`: konkreter Immich-Testplan
|
|
||||||
- `immich-runbook.md`: Operator-Runbook fuer den ersten Immich-Lauf
|
|
||||||
- `immich-compose.test.yml`: isolierte Testinstanz fuer Immich inkl. VectorChord/pgvector-Test-Postgres und Test-Redis
|
|
||||||
- `authelia-restore-test.sh`: Authelia-Restore-Job (Config-Smoke; Erstlauf 2026-06-03 erfolgreich)
|
|
||||||
- `authelia-compose.test.yml`: isolierte Testinstanz fuer Authelia inkl. Test-Postgres, Filesystem-Notifier (kein echter SMTP-Versand)
|
|
||||||
- `authelia-plan.md`: konkreter Authelia-Testplan
|
|
||||||
- `authelia-runbook.md`: Operator-Runbook fuer den ersten Authelia-Lauf
|
|
||||||
- `nextcloud-restore-test.sh`: Nextcloud-Restore-Job (Scaffold; **blockiert** durch Unraid shfs-chmod-Inkompatibilitaet - siehe unten)
|
|
||||||
- `nextcloud-compose.test.yml`: isolierte Testinstanz fuer Nextcloud inkl. Test-Postgres und Test-Redis
|
|
||||||
|
|
||||||
- `check-restore-freshness.ps1`: woechentlicher Frische-Check fuer Dumps und Reports
|
## Aufbau des Verzeichnisses
|
||||||
- `run-restore-checks.ps1`: einfacher Dispatcher fuer Restore-Jobs
|
|
||||||
- `check-restore-freshness.sh`: hosttauglicher Frische-Check
|
|
||||||
- `run-restore-checks.sh`: hosttauglicher Dispatcher
|
|
||||||
- `common.sh`: gemeinsame Host-Helferfunktionen
|
|
||||||
- `automation-plan.md`: Host-Job- und Automatisierungsmodell
|
|
||||||
|
|
||||||
## Automatisierungsmodell
|
Pro Dienst existieren bis zu drei Artefakte:
|
||||||
|
|
||||||
- Ausfuehrung: Unraid User Script / Host-Job
|
- `<dienst>-restore-test.sh` - automatisierter Host-Job (produktive Wahrheit)
|
||||||
- Logik: Repo-Skripte in diesem Verzeichnis
|
- `<dienst>-compose.test.yml` - isolierte Testinstanz
|
||||||
- Ergebnis: Markdown-Report
|
- `<dienst>-runbook.md` - manueller Ablauf bzw. Besonderheiten
|
||||||
- Meldung: `ntfy`
|
|
||||||
- Hermes: optional nur fuer Zusammenfassung und Auswertung
|
|
||||||
|
|
||||||
Wichtig:
|
Dazu zentrale Helfer:
|
||||||
|
|
||||||
- die Bash-Skripte `*.sh` sind die produktive Host-Variante
|
- `run-restore-checks.sh` - Dispatcher (Host), `run-restore-checks.ps1` (lokale Planvariante)
|
||||||
- `check-restore-freshness.ps1` und die `*.ps1`-Dateien bleiben als lokale Plan-/Hilfsvariante nutzbar
|
- `run-restore-job-with-ntfy.sh` - Wrapper: Erfolg -> `homelab-info`, Fehler -> `homelab-alerts`
|
||||||
- im Windows-Clone fehlen die `/mnt/user/...`-Pfade naturgemaess
|
- `check-restore-freshness.sh` / `.ps1` - woechentlicher Frische-Check fuer Dumps und Reports (prueft pg-Dumps per `pg_restore --list`)
|
||||||
|
- `negative-freshness-alert-test.sh` - sicherer Negativtest des Alarmwegs (synthetischer Leerpfad, quartalsweise)
|
||||||
|
- `common.sh` - gemeinsame Borg-/Compose-Helfer
|
||||||
|
- `automation-plan.md` - Host-Job- und Automatisierungsmodell
|
||||||
|
|
||||||
## Validiertes Grundmuster
|
## Betriebsmodus
|
||||||
|
|
||||||
Stand nach dem ersten echten Vaultwarden-Test:
|
Stand 2026-06-11 ist der Betrieb auf V1+ (validierte Bash-Host-Jobs mit ntfy):
|
||||||
|
|
||||||
- Borg-Quelle bleibt das produktive Remote-Repo bei Hetzner
|
- Host-Jobs laufen als Unraid User Scripts vom Repo-Spiegel `/mnt/user/services/homelab-infra`
|
||||||
- Borg-Zugriff laeuft praktisch ueber den vorhandenen `borg-ui`-Container
|
- Kadenz und Cron-Ausdruecke: `schedule.md` (woechentlicher Frische-Check, monatliche/quartalsweise Dienst-Rotation, monatlicher Zufalls-Restore)
|
||||||
- SSH-Trust wird ueber `known_hosts` im `borg-ui`-Container hergestellt
|
- Job-Vorlagen: `unraid-user-scripts.md`
|
||||||
- die Borg-Passphrase kommt fuer Restore-Tests aus einer Host-Secret-Datei
|
|
||||||
- Restore-Ziel liegt immer getrennt unter `/mnt/user/backups/restore-lab`
|
|
||||||
- Reports liegen unter `/mnt/user/backups/restore-reports`
|
|
||||||
- Testinstanzen bekommen keine produktive Domain und keine Traefik-Route
|
|
||||||
|
|
||||||
Das ist das bevorzugte Muster fuer weitere dateibasierte Restore-Tests wie `gitea`.
|
## Schnellstart
|
||||||
|
|
||||||
Fuer datenbankgestuetzte Dienste wie `paperless` kommt zusaetzlich ein isolierter Dump-Restore in Test-Postgres dazu.
|
```bash
|
||||||
|
# Frische-Check
|
||||||
|
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh freshness
|
||||||
|
|
||||||
## Status
|
# Dienst-Restore-Check (vaultwarden|gitea|paperless|immich|authelia|adguard|redis|homeassistant|komodo-bootstrap|nextcloud)
|
||||||
|
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh <dienst>
|
||||||
|
|
||||||
Aktuell ist das erste validierte Muster vorhanden.
|
# Negativtest des Alarmwegs (quartalsweise)
|
||||||
|
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh freshness-negative
|
||||||
|
|
||||||
- echter Vaultwarden-Restore am 2026-05-07 erfolgreich verifiziert
|
# Mit ntfy-Meldung
|
||||||
- echter Gitea-Restore am 2026-05-07 erfolgreich verifiziert
|
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-job-with-ntfy.sh freshness homelab-info
|
||||||
- echter Paperless-Restore am 2026-05-07 erfolgreich verifiziert
|
```
|
||||||
- Immich-Restore-Test am 2026-05-27 erfolgreich verifiziert; Test-Postgres wurde nach der VectorChord-Migration am 2026-05-31 auf das produktive Immich-Postgres-Image umgestellt
|
|
||||||
- Authelia-Restore-Smoke am 2026-06-03 erfolgreich verifiziert; bewusst ohne produktiven Dump-Restore wegen Storage-Encryption-Key-Kopplung
|
|
||||||
- Bash-Dispatcher und Bash-Restore-Jobs am 2026-05-07 erfolgreich hostseitig verifiziert
|
|
||||||
- Restore-Lab und Report-Pfade auf dem Host angelegt
|
|
||||||
- `ntfy`-Wrapper ist fuer Host-Jobs verfuegbar
|
|
||||||
- Nextcloud-Restore-Test: Scaffold existiert, aber **blockiert**. Nextcloud 33 fuehrt zur Laufzeit `chmod()` auf Dateien unter `/var/www/html` aus (`OC_Util.php:486`). Auf Unraids FUSE/shfs User-Shares ist `chmod` strukturell nicht moeglich, was zu permanenter 503 fuehrt. Loesungsoptionen: (a) Restore-Lab auf ein Cache-Drive statt User Share legen, (b) Docker-Volumes statt Bind-Mounts verwenden, (c) tmpfs-Mount fuer html/ + `rsync` der Borg-Daten hinein. Bis dahin ist Nextcloud als Backlog-Item dokumentiert.
|
|
||||||
- Komodo-Mongo-Daten-Restore am 2026-06-03 erfolgreich: 86904 Dokumente (inkl. 32 Stacks), Report `/mnt/user/backups/restore-reports/komodo-mongo-restore-2026-06-03.md`
|
|
||||||
- naechste grosse Kandidaten sind Mailarchiver und Mealie; Nextcloud bleibt blockiert (shfs-chmod)
|
|
||||||
|
|
||||||
Vor dem ersten echten Testlauf je neuem Dienst muessen Zielpfade, Quellpfade und Bereinigungsschritte bewusst freigegeben werden.
|
## Status je Dienst
|
||||||
|
|
||||||
|
Einziger Status-Ort ist die **Reifegrad-Tabelle** in `docs/RESTORE_MATRIX.md`
|
||||||
|
(letzter Test, Typ, naechster Lauf). Hier nur Besonderheiten:
|
||||||
|
|
||||||
|
- **Nextcloud:** Test am 2026-06-03 erfolgreich, aber mit Unraid-shfs-Eigenheit: Nextcloud fuehrt `chmod()` unter `/var/www/html` aus, was auf FUSE/shfs scheitert. Das Skript patcht `check_data_directory_permissions: false` und legt den `.ncdata`-Marker an.
|
||||||
|
- **Authelia:** bewusst Config-Smoke ohne produktiven Dump-Restore (Storage-Encryption-Key-Kopplung).
|
||||||
|
- **Immich:** Foto-Dateien-Restore ist bewusst nicht Teil des Smokes (separater DR-Drill); Test-Postgres nutzt das produktive VectorChord-Image.
|
||||||
|
- **Home Assistant:** nutzt das neueste HA-native Backup-Artefakt und eine Kopie der Mosquitto-Appdata; Testcontainer laufen nur auf localhost-Ports, ohne Traefik/Public Route.
|
||||||
|
- **Unraid-Flash / Tailscale:** noch ohne vollstaendigen Erstlauf - `unraid-flash-runbook.md`, `tailscale-runbook.md`; offene Schritte in `docs/MASTER_TODO.md`.
|
||||||
|
|
||||||
|
## Naechste Ausbaustufen
|
||||||
|
|
||||||
|
1. Hermes-Zusammenfassung ueber vorhandene Reports (geparkt mit Hermes)
|
||||||
|
2. Report-Rotation: Reports werden dauerhaft aufbewahrt; bei wachsender Anzahl jaehrlich nach `_archive/YYYY/` verschieben. Der Frische-Check warnt ab `MAX_REPORT_AGE_DAYS=45`, loescht aber nie automatisch.
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
services:
|
||||||
|
restoretest-adguard:
|
||||||
|
image: adguard/adguardhome:v0.107.77@sha256:e6f2b8bcda06064ab055b44933a4f0e983c35558b9cdb8d2e7ab1efcee36d890
|
||||||
|
container_name: restoretest-adguard
|
||||||
|
restart: "no"
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:15353:53/tcp"
|
||||||
|
- "127.0.0.1:15353:53/udp"
|
||||||
|
- "127.0.0.1:13001:80/tcp"
|
||||||
|
volumes:
|
||||||
|
- /mnt/user/backups/restore-lab/adguard/work:/opt/adguardhome/work
|
||||||
|
- /mnt/user/backups/restore-lab/adguard/conf:/opt/adguardhome/conf
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
Executable
+181
@@ -0,0 +1,181 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# AdGuard Home Restore Smoke Test
|
||||||
|
#
|
||||||
|
# Scope:
|
||||||
|
# - Borg-Extract von /local/appdata/adguard/conf
|
||||||
|
# - YAML-/Strukturcheck fuer AdGuardHome.yaml
|
||||||
|
# - Start einer isolierten Testinstanz auf localhost-Ports
|
||||||
|
# - HTTP-Smoke gegen Admin-UI/API
|
||||||
|
# - DNS-Smoke gegen localhost:15353, falls ein passender Resolver-Client vorhanden ist
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
. "$SCRIPT_DIR/common.sh"
|
||||||
|
|
||||||
|
WHATIF=0
|
||||||
|
KEEP_DATA=0
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--what-if) WHATIF=1 ;;
|
||||||
|
--keep-data) KEEP_DATA=1 ;;
|
||||||
|
*) echo "Unknown argument: $arg" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
RESTORE_ROOT="/mnt/user/backups/restore-lab/adguard"
|
||||||
|
REPORT_ROOT="/mnt/user/backups/restore-reports"
|
||||||
|
EXTRACT_DIR="$BORG_RESTORE_HOST_ROOT/adguard-extract"
|
||||||
|
COMPOSE_FILE="$SCRIPT_DIR/adguard-compose.test.yml"
|
||||||
|
REPORT_FILE="$REPORT_ROOT/adguard-$(date +%F).md"
|
||||||
|
TEST_HTTP="http://127.0.0.1:13001"
|
||||||
|
TEST_DNS_PORT="15353"
|
||||||
|
|
||||||
|
if [ "$WHATIF" -eq 1 ]; then
|
||||||
|
cat <<EOF
|
||||||
|
AdGuard Home restore test
|
||||||
|
Mode: WhatIf
|
||||||
|
RestoreRoot: $RESTORE_ROOT
|
||||||
|
Borg source: local/appdata/adguard/conf
|
||||||
|
Test HTTP endpoint: $TEST_HTTP
|
||||||
|
Test DNS endpoint: 127.0.0.1:$TEST_DNS_PORT
|
||||||
|
Scope: Config-Restore + isolated AdGuard boot + HTTP/DNS smoke
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
require_cmd docker
|
||||||
|
require_cmd curl
|
||||||
|
require_path "$BORG_PASSPHRASE_FILE_DEFAULT"
|
||||||
|
require_path "$COMPOSE_FILE"
|
||||||
|
|
||||||
|
RESTORE_SUCCESS=0
|
||||||
|
cleanup() {
|
||||||
|
cleanup_compose "$COMPOSE_FILE"
|
||||||
|
if [ "$RESTORE_SUCCESS" -ne 1 ]; then
|
||||||
|
preserve_on_failure "adguard" "$RESTORE_ROOT"
|
||||||
|
rm -rf "$EXTRACT_DIR"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [ "$KEEP_DATA" -ne 1 ]; then
|
||||||
|
rm -rf "$RESTORE_ROOT"
|
||||||
|
fi
|
||||||
|
rm -rf "$EXTRACT_DIR"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
rm -rf "$EXTRACT_DIR" "$RESTORE_ROOT"
|
||||||
|
mkdir -p "$RESTORE_ROOT/conf" "$RESTORE_ROOT/work"
|
||||||
|
|
||||||
|
archive="$(latest_archive_name)"
|
||||||
|
repo="$(borg_repo_url)"
|
||||||
|
|
||||||
|
if [ -z "$archive" ] || [ -z "$repo" ]; then
|
||||||
|
echo "Could not resolve Borg repo/archive from borg-ui database" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
borg_extract "/restore/adguard-extract" "local/appdata/adguard/conf"
|
||||||
|
|
||||||
|
if [ ! -d "$EXTRACT_DIR/local/appdata/adguard/conf" ]; then
|
||||||
|
echo "AdGuard conf path missing in Borg archive" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp -a "$EXTRACT_DIR/local/appdata/adguard/conf/." "$RESTORE_ROOT/conf/"
|
||||||
|
chmod -R a+rX "$RESTORE_ROOT/conf"
|
||||||
|
chmod -R a+rwX "$RESTORE_ROOT/work"
|
||||||
|
|
||||||
|
config_file="$RESTORE_ROOT/conf/AdGuardHome.yaml"
|
||||||
|
if [ ! -s "$config_file" ]; then
|
||||||
|
echo "Missing restored AdGuardHome.yaml" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v ruby >/dev/null 2>&1; then
|
||||||
|
ruby -e 'require "yaml"; YAML.load_file(ARGV.fetch(0))' "$config_file"
|
||||||
|
yaml_check="ruby-yaml-ok"
|
||||||
|
else
|
||||||
|
grep -q '^dns:' "$config_file"
|
||||||
|
grep -q '^http:' "$config_file"
|
||||||
|
yaml_check="basic-structure-ok"
|
||||||
|
fi
|
||||||
|
|
||||||
|
filter_count="$(grep -c '^[[:space:]]*-[[:space:]]*enabled:' "$config_file" 2>/dev/null || true)"
|
||||||
|
|
||||||
|
docker compose -f "$COMPOSE_FILE" up -d restoretest-adguard >/dev/null
|
||||||
|
|
||||||
|
http_status=""
|
||||||
|
for _ in $(seq 1 60); do
|
||||||
|
http_status="$(curl -s -o /tmp/adguard-body.html -w '%{http_code}' \
|
||||||
|
"$TEST_HTTP/control/status" || true)"
|
||||||
|
if [ "$http_status" = "200" ] || [ "$http_status" = "401" ] || [ "$http_status" = "403" ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$http_status" != "200" ] && [ "$http_status" != "401" ] && [ "$http_status" != "403" ]; then
|
||||||
|
echo "AdGuard HTTP smoke failed: status=$http_status" >&2
|
||||||
|
docker logs --tail 80 restoretest-adguard >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
dns_status="not-run"
|
||||||
|
dns_detail="no dig/drill command available"
|
||||||
|
if command -v dig >/dev/null 2>&1; then
|
||||||
|
if dig @127.0.0.1 -p "$TEST_DNS_PORT" git.kaleschke.info A +time=3 +tries=1 >/tmp/adguard-dig.out 2>&1; then
|
||||||
|
dns_status="ok"
|
||||||
|
dns_detail="$(grep -E '^[[:alnum:].-]+[[:space:]]+[0-9]+[[:space:]]+IN[[:space:]]+A[[:space:]]+' /tmp/adguard-dig.out | head -1 || true)"
|
||||||
|
else
|
||||||
|
dns_status="failed"
|
||||||
|
dns_detail="$(tail -20 /tmp/adguard-dig.out | tr '\n' ' ')"
|
||||||
|
fi
|
||||||
|
elif command -v drill >/dev/null 2>&1; then
|
||||||
|
if drill -p "$TEST_DNS_PORT" git.kaleschke.info @127.0.0.1 >/tmp/adguard-drill.out 2>&1; then
|
||||||
|
dns_status="ok"
|
||||||
|
dns_detail="$(grep -E '^[[:alnum:].-]+\\.[[:space:]]+[0-9]+[[:space:]]+IN[[:space:]]+A[[:space:]]+' /tmp/adguard-drill.out | head -1 || true)"
|
||||||
|
else
|
||||||
|
dns_status="failed"
|
||||||
|
dns_detail="$(tail -20 /tmp/adguard-drill.out | tr '\n' ' ')"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$dns_status" = "failed" ]; then
|
||||||
|
echo "AdGuard DNS smoke failed: $dns_detail" >&2
|
||||||
|
docker logs --tail 80 restoretest-adguard >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
write_report "$REPORT_FILE" <<EOF
|
||||||
|
# AdGuard Home Restore Test Report - $(date +%F)
|
||||||
|
|
||||||
|
- Service: \`adguard\`
|
||||||
|
- Source repo: \`$repo\`
|
||||||
|
- Archive: \`$archive\`
|
||||||
|
- Restore root: \`$RESTORE_ROOT\`
|
||||||
|
- Test container: \`restoretest-adguard\`
|
||||||
|
- Test HTTP endpoint: \`$TEST_HTTP/control/status\`
|
||||||
|
- Test DNS endpoint: \`127.0.0.1:$TEST_DNS_PORT\`
|
||||||
|
- Result: \`SUCCESS\`
|
||||||
|
|
||||||
|
## Checks
|
||||||
|
|
||||||
|
- Borg extract of conf: \`ok\`
|
||||||
|
- Restored config file: \`AdGuardHome.yaml\`
|
||||||
|
- Config check: \`$yaml_check\`
|
||||||
|
- Filter-list-like entries counted: \`$filter_count\`
|
||||||
|
- HTTP status from /control/status: \`$http_status\`
|
||||||
|
- DNS smoke: \`$dns_status\`
|
||||||
|
- DNS detail: \`$dns_detail\`
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Productive AdGuard DNS port 53 and admin port 8082 were NOT used.
|
||||||
|
- Test ports were bound to localhost only: \`127.0.0.1:15353\` and \`127.0.0.1:13001\`.
|
||||||
|
- Login credentials are part of the restored AdGuardHome.yaml and were not printed.
|
||||||
|
- Test data was cleaned after success: \`$([ "$KEEP_DATA" -eq 1 ] && echo no || echo yes)\`
|
||||||
|
EOF
|
||||||
|
|
||||||
|
RESTORE_SUCCESS=1
|
||||||
|
echo "AdGuard restore test ok -> $REPORT_FILE"
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
# Authelia Restore Test Plan
|
|
||||||
|
|
||||||
## Ziel
|
|
||||||
|
|
||||||
Nachweisen, dass die Authelia-Konfiguration aus dem produktiven Borg-Archiv in einer isolierten Testumgebung wieder lauffaehig ist und der HTTP-Health-Endpunkt antwortet, ohne dass dabei produktive Secrets, produktives Postgres oder produktiver SMTP-Versand beruehrt werden.
|
|
||||||
|
|
||||||
Bewusst **nicht** Teil dieses Tests:
|
|
||||||
|
|
||||||
- Restore mit produktiven Authelia-Secrets. Der Test nutzt ausschliesslich Wegwerf-Werte fuer `AUTHELIA_SESSION_SECRET`, `AUTHELIA_STORAGE_ENCRYPTION_KEY` und `AUTHELIA_STORAGE_POSTGRES_PASSWORD`. SMTP- und Legacy-JWT-Env-Werte werden bewusst nicht gesetzt, damit Authelia keinen `notifier.smtp`-Block oder deprecated `jwt_secret` aus Env erzeugt.
|
|
||||||
- SMTP-Realanruf an GMX. Die minimale Test-Konfiguration setzt nur den Filesystem-Notifier.
|
|
||||||
- Forward-Auth gegen Traefik. Test laeuft nur auf `127.0.0.1:19091`, keine Traefik-Route.
|
|
||||||
- WebAuthn-/Duo-/OIDC-Identity-Provider-Endpunkte. Smoke prueft `/api/health`.
|
|
||||||
- **pg_restore des produktiven `postgresql17-authelia.dump`**. Authelia verschluesselt Storage-Werte mit `AUTHELIA_STORAGE_ENCRYPTION_KEY`. Ein Restore mit produktiven Daten in eine Test-Instanz mit Wegwerf-Key schlaegt im Startup-Check **by design** fehl ("the configured encryption key does not appear to be valid for this database"). Frische des produktiven Dumps wird ueber `check-restore-freshness.sh` ueberwacht; Daten-Decrypt-Drill ist eine separate DR-Aufgabe und braucht eine eigene Sicherheits-Choreographie mit kontrollierter Schluessel-Verwendung. Beobachtet im Erstlauf 2026-06-03 (Commit-Reihe `cacf77b..8d71dfb`); seit dem 2026-06-03-Folgecommit ist der Dump-Restore explizit aus dem Smoke entfernt.
|
|
||||||
|
|
||||||
## Quelle
|
|
||||||
|
|
||||||
- Backup-Quelle: produktives Borg-Archiv (`hetzner_borg_appdata_critical`)
|
|
||||||
- fachlich relevante Pfade im Archiv:
|
|
||||||
- `local/appdata/authelia/config` (verpflichtend)
|
|
||||||
- `local/borg-dumps/latest/postgresql17-authelia.dump` (existiert ggf. im Archiv; wird vom Smoke bewusst NICHT eingespielt, siehe oben)
|
|
||||||
- produktive Secrets unter `/mnt/user/appdata/secrets/authelia_*.txt` werden **nicht** gemountet
|
|
||||||
|
|
||||||
## Test-Ziel
|
|
||||||
|
|
||||||
- Restore-Lab: `/mnt/user/backups/restore-lab/authelia`
|
|
||||||
- Testdatenpfade:
|
|
||||||
- `/mnt/user/backups/restore-lab/authelia/config` (restaurierte Originalkonfiguration + `configuration.yml.original`)
|
|
||||||
- `/mnt/user/backups/restore-lab/authelia/test-config` (Runtime-Mount mit minimaler Test-`configuration.yml`)
|
|
||||||
- `/mnt/user/backups/restore-lab/authelia/postgres` (Test-Postgres-Datadir)
|
|
||||||
- `/mnt/user/backups/restore-lab/authelia/dumps/latest/postgresql17-authelia.dump` (falls extrahiert)
|
|
||||||
- `/mnt/user/backups/restore-lab/authelia/test-config/notifier/notifications.txt` (Filesystem-Notifier-Ausgabe)
|
|
||||||
- Testcontainer:
|
|
||||||
- `restoretest-authelia` (Image-Pin wie Produktion)
|
|
||||||
- `restoretest-authelia-postgres` (postgres:18.4, gleiche Major wie shared Postgres)
|
|
||||||
- Testport: `127.0.0.1:19091:9091`
|
|
||||||
- Report-Ziel: `/mnt/user/backups/restore-reports/authelia-YYYY-MM-DD.md`
|
|
||||||
|
|
||||||
## Schutzregeln
|
|
||||||
|
|
||||||
- produktive Pfade `/mnt/user/appdata/authelia/*` werden **nicht** beschrieben
|
|
||||||
- produktive Secret-Dateien `/mnt/user/appdata/secrets/authelia_*.txt` werden **nicht** gemountet
|
|
||||||
- produktive shared PostgreSQL 18 wird **nicht** angesprochen (`test-config/configuration.yml` definiert nur Test-Postgres)
|
|
||||||
- echter SMTP-Versand wird **nicht** ausgeloest (`test-config/configuration.yml` definiert nur Filesystem-Notifier)
|
|
||||||
- produktive Domain `auth.kaleschke.info` wird **nicht** uebernommen
|
|
||||||
- Testcontainer publishen nur auf `127.0.0.1`, keine LAN-/Tailscale-Bindung
|
|
||||||
- Borg-Passphrase wird aus `/mnt/user/appdata/secrets/borg_repo_passphrase.txt` gelesen und nirgendwo geloggt
|
|
||||||
|
|
||||||
## Geplanter Ablauf
|
|
||||||
|
|
||||||
1. Restore-Lab-Pfade leer anlegen
|
|
||||||
2. `local/appdata/authelia/config` aus dem aktuellsten Borg-Archiv extrahieren
|
|
||||||
3. minimale `test-config/configuration.yml` erzeugen; restaurierte Begleitdateien wie `users_database.yml` bleiben im Runtime-Mount, produktive externe Abhaengigkeiten werden nicht uebernommen; `notifier` auf Filesystem, `ntp.disable_startup_check: true`, `storage` auf Test-Postgres
|
|
||||||
4. Test-Postgres mit `ops/restore-tests/authelia-compose.test.yml` **frisch** hochfahren (keine Daten aus Dump - siehe Encryption-Key-Begruendung oben)
|
|
||||||
5. `authelia config validate` gegen `test-config/configuration.yml` laufen lassen
|
|
||||||
6. `restoretest-authelia` starten und HTTP-Health `http://127.0.0.1:19091/api/health` pollen
|
|
||||||
7. Report unter `/mnt/user/backups/restore-reports/authelia-YYYY-MM-DD.md` schreiben
|
|
||||||
8. Testcontainer stoppen und Restore-Lab bereinigen (`--keep-data` ueberschreibt)
|
|
||||||
|
|
||||||
## Smoke-Test
|
|
||||||
|
|
||||||
Minimal erfolgreich:
|
|
||||||
|
|
||||||
- Borg-Extract der Authelia-Config gelingt
|
|
||||||
- Test-Postgres startet `healthy`
|
|
||||||
- `authelia config validate` laeuft ohne Fehler durch
|
|
||||||
- HTTP `200` auf `/api/health` innerhalb 120 s
|
|
||||||
|
|
||||||
Optional spaeter:
|
|
||||||
|
|
||||||
- vollstaendigen Auth-Flow gegen Test-User aus `users_database.yml` durchspielen
|
|
||||||
- WebAuthn-Endpunkt /api/secondfactor/webauthn pruefen
|
|
||||||
- ForwardAuth-Pfad gegen Mock-Backend testen
|
|
||||||
|
|
||||||
## Bekannte Komplikationen
|
|
||||||
|
|
||||||
| Risiko | Beschreibung | Mitigation |
|
|
||||||
|---|---|---|
|
|
||||||
| Testkonfig-Schema-Drift | Authelia erwartet nach Upgrade andere Keys in der Minimal-Konfig | bei `config validate`-Fehler Test-Block im Skript anpassen |
|
|
||||||
| SMTP-Startup-Check blockiert Start | Wenn Authelia trotz `disable_startup_check` SMTP probiert | Container-Logs lesen, ggf. Notifier-Block weiter haerten |
|
|
||||||
| NTP-Lookup im Test-Netz | Container hat keinen DNS-Resolver fuer `time.cloudflare.com` | im Smoke per `ntp.disable_startup_check: true` deaktiviert |
|
|
||||||
| Storage-Encryption-Key vs. Dump | siehe "Bewusst nicht Teil dieses Tests" - der Smoke laeuft FRISCH ohne Dump | by design - Daten-Decrypt-Drill ist separate Aufgabe |
|
|
||||||
| identity_validation Schema-Drift | Aelteres/neueres Authelia-Schema erwartet andere Keys | Validate-Config Output lesen, ggf. Test-Block anpassen |
|
|
||||||
| users_database.yml mit produktiven Hashes | Daten werden ins Restore-Lab kopiert, aber niemals gemountet auf produktive Domain | OK; Testpfad ist isoliert, kein Browser-Zugang ueber LAN |
|
|
||||||
|
|
||||||
## Status
|
|
||||||
|
|
||||||
- Skript- und Compose-Scaffold abgelegt am 2026-06-02
|
|
||||||
- Erstlauf am 2026-06-03 erfolgreich: Config aus Borg, minimale Test-Konfiguration, frisches Test-Postgres, HTTP `/api/health` `200`, Report `/mnt/user/backups/restore-reports/authelia-2026-06-03.md`
|
|
||||||
- Fuer die Rotation vorgesehen: zweiter Samstag in geraden Monaten, 07:30
|
|
||||||
@@ -6,6 +6,7 @@ param(
|
|||||||
)
|
)
|
||||||
|
|
||||||
$checks = @(
|
$checks = @(
|
||||||
|
@{ Name = "postgresql17-globals.sql"; Path = Join-Path $DumpRoot "postgresql17-globals.sql" },
|
||||||
@{ Name = "postgresql17-paperless.dump"; Path = Join-Path $DumpRoot "postgresql17-paperless.dump" },
|
@{ Name = "postgresql17-paperless.dump"; Path = Join-Path $DumpRoot "postgresql17-paperless.dump" },
|
||||||
@{ Name = "postgresql17-mailarchiver.dump"; Path = Join-Path $DumpRoot "postgresql17-mailarchiver.dump" },
|
@{ Name = "postgresql17-mailarchiver.dump"; Path = Join-Path $DumpRoot "postgresql17-mailarchiver.dump" },
|
||||||
@{ Name = "mealie.dump"; Path = Join-Path $DumpRoot "mealie.dump" },
|
@{ Name = "mealie.dump"; Path = Join-Path $DumpRoot "mealie.dump" },
|
||||||
@@ -13,6 +14,7 @@ $checks = @(
|
|||||||
@{ Name = "nextcloud.dump"; Path = Join-Path $DumpRoot "nextcloud.dump" },
|
@{ Name = "nextcloud.dump"; Path = Join-Path $DumpRoot "nextcloud.dump" },
|
||||||
@{ Name = "gitea.sqlite.dump"; Path = Join-Path $DumpRoot "gitea.sqlite.dump" },
|
@{ Name = "gitea.sqlite.dump"; Path = Join-Path $DumpRoot "gitea.sqlite.dump" },
|
||||||
@{ Name = "vaultwarden.sqlite.dump"; Path = Join-Path $DumpRoot "vaultwarden.sqlite.dump" },
|
@{ Name = "vaultwarden.sqlite.dump"; Path = Join-Path $DumpRoot "vaultwarden.sqlite.dump" },
|
||||||
|
@{ Name = "n8n.sqlite.dump"; Path = Join-Path $DumpRoot "n8n.sqlite.dump" },
|
||||||
@{ Name = "speedtest-tracker.sqlite.dump"; Path = Join-Path $DumpRoot "speedtest-tracker.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 = "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" }
|
@{ Name = "unraid-flash-config.tar.gz"; Path = Join-Path $DumpRoot "unraid-flash-config.tar.gz" }
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ check_pg_header() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for dump in \
|
for dump in \
|
||||||
|
postgresql17-globals.sql \
|
||||||
postgresql17-paperless.dump \
|
postgresql17-paperless.dump \
|
||||||
postgresql17-mailarchiver.dump \
|
postgresql17-mailarchiver.dump \
|
||||||
mealie.dump \
|
mealie.dump \
|
||||||
@@ -96,6 +97,7 @@ for dump in \
|
|||||||
nextcloud.dump \
|
nextcloud.dump \
|
||||||
gitea.sqlite.dump \
|
gitea.sqlite.dump \
|
||||||
vaultwarden.sqlite.dump \
|
vaultwarden.sqlite.dump \
|
||||||
|
n8n.sqlite.dump \
|
||||||
speedtest-tracker.sqlite.dump \
|
speedtest-tracker.sqlite.dump \
|
||||||
filebrowser.bolt.dump \
|
filebrowser.bolt.dump \
|
||||||
unraid-flash-config.tar.gz; do
|
unraid-flash-config.tar.gz; do
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
# Gitea Restore Test Plan
|
|
||||||
|
|
||||||
## Ziel
|
|
||||||
|
|
||||||
Nachweisen, dass ein Gitea-Backup in einer isolierten Testumgebung wieder startbar ist und sowohl Web-UI als auch SSH-Port wieder verfuegbar sind.
|
|
||||||
|
|
||||||
## Quelle
|
|
||||||
|
|
||||||
- Backup-Quelle: Borg / Share-Backup
|
|
||||||
- fachlich relevanter Datenpfad: `/mnt/user/services/gitea/data`
|
|
||||||
- keine separaten Secret-Dateien dokumentiert
|
|
||||||
|
|
||||||
## Test-Ziel
|
|
||||||
|
|
||||||
- Restore-Lab: `/mnt/user/backups/restore-lab/gitea`
|
|
||||||
- Testdatenpfad: `/mnt/user/backups/restore-lab/gitea/data`
|
|
||||||
- Testcontainer: `restoretest-gitea`
|
|
||||||
- Testports:
|
|
||||||
- Web: `127.0.0.1:13000:3000`
|
|
||||||
- SSH: `127.0.0.1:12222:22`
|
|
||||||
- Report-Ziel: `/mnt/user/backups/restore-reports/gitea-YYYY-MM-DD.md`
|
|
||||||
|
|
||||||
## Schutzregeln
|
|
||||||
|
|
||||||
- produktiven Pfad `/mnt/user/services/gitea/data` nie beschreiben
|
|
||||||
- produktive Domain `git.kaleschke.info` nicht fuer die Testinstanz uebernehmen
|
|
||||||
- produktiven SSH-Port `222` nicht fuer die Testinstanz uebernehmen
|
|
||||||
- keine Traefik-Labels fuer die Testinstanz
|
|
||||||
- Testcontainer nur gegen Restore-Lab-Daten starten
|
|
||||||
|
|
||||||
## Geplanter Ablauf
|
|
||||||
|
|
||||||
1. Restore-Ziel unter `/mnt/user/backups/restore-lab/gitea` vorbereiten
|
|
||||||
2. Gitea-Daten aus Backup in `restore-lab/gitea/data` wiederherstellen
|
|
||||||
3. Testinstanz mit `ops/restore-tests/gitea-compose.test.yml` starten
|
|
||||||
4. lokalen Smoke-Test gegen `http://127.0.0.1:13000` und `127.0.0.1:12222` ausfuehren
|
|
||||||
5. Report unter `/mnt/user/backups/restore-reports/` schreiben
|
|
||||||
6. Testcontainer stoppen und Testumgebung bereinigen oder bewusst stehen lassen
|
|
||||||
|
|
||||||
## Smoke-Test
|
|
||||||
|
|
||||||
Minimal erfolgreich:
|
|
||||||
|
|
||||||
- Container startet
|
|
||||||
- Web-UI antwortet
|
|
||||||
- mindestens ein bestehendes Repository-Verzeichnis ist im Restore-Lab sichtbar
|
|
||||||
- SSH-Port reagiert auf Verbindungsaufbau
|
|
||||||
|
|
||||||
Optional spaeter:
|
|
||||||
|
|
||||||
- Login-Seite gezielt pruefen
|
|
||||||
- SQLite-Datei `gitea.db` oder Nachfolger explizit bestaetigen
|
|
||||||
- `gitea doctor` oder interner Healthcheck als Zusatz
|
|
||||||
|
|
||||||
## Noch offen vor dem ersten echten Lauf
|
|
||||||
|
|
||||||
- exakter Borg-Restore-Befehl bzw. Restore-Quelle auf dem Host
|
|
||||||
- Bereinigungsstrategie fuer alte Restore-Lab-Daten
|
|
||||||
- ob Reports spaeter zusaetzlich per `ntfy` referenziert werden
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
services:
|
||||||
|
restoretest-ha-mosquitto:
|
||||||
|
image: eclipse-mosquitto:2.0.22@sha256:914f529386804c8278a4e581526b9be5e1604df44b30daabc70aa97dcefe5268
|
||||||
|
container_name: restoretest-ha-mosquitto
|
||||||
|
restart: "no"
|
||||||
|
volumes:
|
||||||
|
- ${RESTORE_ROOT:-/mnt/user/backups/restore-lab/homeassistant}/mosquitto/config/mosquitto.conf:/mosquitto/config/mosquitto.conf:ro
|
||||||
|
- ${RESTORE_ROOT:-/mnt/user/backups/restore-lab/homeassistant}/mosquitto/appdata/config:/mosquitto/external_config:ro
|
||||||
|
- ${RESTORE_ROOT:-/mnt/user/backups/restore-lab/homeassistant}/mosquitto/appdata/data:/mosquitto/data
|
||||||
|
- ${RESTORE_ROOT:-/mnt/user/backups/restore-lab/homeassistant}/mosquitto/appdata/log:/mosquitto/log
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:11883:1883"
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
|
||||||
|
restoretest-homeassistant:
|
||||||
|
image: ghcr.io/home-assistant/home-assistant:2026.6.1@sha256:59aa8824955c9db491b75d2eebe42bd68494f80c2ec69ec0d66d9dae37d37514
|
||||||
|
container_name: restoretest-homeassistant
|
||||||
|
restart: "no"
|
||||||
|
depends_on:
|
||||||
|
- restoretest-ha-mosquitto
|
||||||
|
environment:
|
||||||
|
TZ: Europe/Berlin
|
||||||
|
volumes:
|
||||||
|
- ${RESTORE_ROOT:-/mnt/user/backups/restore-lab/homeassistant}/homeassistant/config:/config
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:18123:8123"
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
+236
@@ -0,0 +1,236 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Home Assistant + Mosquitto Restore Smoke Test
|
||||||
|
#
|
||||||
|
# Scope:
|
||||||
|
# - Restore aus dem neuesten HA-nativen Backup-Artefakt
|
||||||
|
# - Kopie der Mosquitto-Appdata in ein isoliertes Restore-Lab
|
||||||
|
# - Kopie des Fachrepo-Clones zur Lesbarkeits-/Git-Status-Pruefung
|
||||||
|
# - Start isolierter Testcontainer auf localhost-Ports, ohne Traefik/Public Route
|
||||||
|
# - HA HTTP/API-Smoke und MQTT Publish/Subscribe + retained Topic nach Restart
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
. "$SCRIPT_DIR/common.sh"
|
||||||
|
|
||||||
|
WHATIF=0
|
||||||
|
KEEP_DATA=0
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--what-if) WHATIF=1 ;;
|
||||||
|
--keep-data) KEEP_DATA=1 ;;
|
||||||
|
*) echo "Unknown argument: $arg" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
RESTORE_ROOT="/mnt/user/backups/restore-lab/homeassistant"
|
||||||
|
REPORT_ROOT="/mnt/user/backups/restore-reports"
|
||||||
|
REPORT_FILE="$REPORT_ROOT/homeassistant-$(date +%F).md"
|
||||||
|
COMPOSE_FILE="$SCRIPT_DIR/homeassistant-compose.test.yml"
|
||||||
|
HA_BACKUP_DIR="/mnt/user/appdata/homeassistant/backups"
|
||||||
|
MOSQUITTO_APPDATA="/mnt/user/appdata/mosquitto"
|
||||||
|
MOSQUITTO_REPO_CONF="/mnt/user/services/homelab-infra/smart-home/mosquitto/config/mosquitto.conf"
|
||||||
|
FACHREPO_SOURCE="/mnt/user/services/smart-home-kalli"
|
||||||
|
HA_TOKEN_FILE="/mnt/user/appdata/secrets/ha_token_codex"
|
||||||
|
|
||||||
|
if [ "$WHATIF" -eq 1 ]; then
|
||||||
|
cat <<EOF
|
||||||
|
Home Assistant restore test
|
||||||
|
Mode: WhatIf
|
||||||
|
RestoreRoot: $RESTORE_ROOT
|
||||||
|
HA backup source: newest *.tar under $HA_BACKUP_DIR
|
||||||
|
Mosquitto source: $MOSQUITTO_APPDATA
|
||||||
|
Fachrepo source: $FACHREPO_SOURCE
|
||||||
|
Test endpoints: HA http://127.0.0.1:18123, MQTT 127.0.0.1:11883
|
||||||
|
Scope: HA backup extract + isolated HA boot + API token smoke + MQTT auth/retained smoke
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
require_cmd docker
|
||||||
|
require_cmd tar
|
||||||
|
require_cmd curl
|
||||||
|
require_path "$COMPOSE_FILE"
|
||||||
|
require_path "$HA_BACKUP_DIR"
|
||||||
|
require_path "$MOSQUITTO_APPDATA/config/passwordfile"
|
||||||
|
require_path "$MOSQUITTO_APPDATA/config/aclfile"
|
||||||
|
require_path "$MOSQUITTO_APPDATA/data"
|
||||||
|
require_path "$MOSQUITTO_REPO_CONF"
|
||||||
|
require_path "$FACHREPO_SOURCE"
|
||||||
|
require_path "$HA_TOKEN_FILE"
|
||||||
|
|
||||||
|
RESTORE_SUCCESS=0
|
||||||
|
cleanup() {
|
||||||
|
RESTORE_ROOT="$RESTORE_ROOT" cleanup_compose "$COMPOSE_FILE"
|
||||||
|
if [ "$RESTORE_SUCCESS" -ne 1 ]; then
|
||||||
|
preserve_on_failure "homeassistant" "$RESTORE_ROOT"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [ "$KEEP_DATA" -ne 1 ]; then
|
||||||
|
rm -rf "$RESTORE_ROOT"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
latest_backup="$(find "$HA_BACKUP_DIR" -maxdepth 1 -type f -name '*.tar' -printf '%T@ %p\n' | sort -nr | awk 'NR==1 {print substr($0, index($0,$2))}')"
|
||||||
|
if [ -z "$latest_backup" ] || [ ! -f "$latest_backup" ]; then
|
||||||
|
echo "No HA native backup tar found under $HA_BACKUP_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$RESTORE_ROOT"
|
||||||
|
mkdir -p \
|
||||||
|
"$RESTORE_ROOT/ha-backup" \
|
||||||
|
"$RESTORE_ROOT/homeassistant/config" \
|
||||||
|
"$RESTORE_ROOT/mosquitto/config" \
|
||||||
|
"$RESTORE_ROOT/mosquitto/appdata/config" \
|
||||||
|
"$RESTORE_ROOT/mosquitto/appdata/data" \
|
||||||
|
"$RESTORE_ROOT/mosquitto/appdata/log" \
|
||||||
|
"$RESTORE_ROOT/fachrepo"
|
||||||
|
|
||||||
|
tar -xf "$latest_backup" -C "$RESTORE_ROOT/ha-backup"
|
||||||
|
require_path "$RESTORE_ROOT/ha-backup/backup.json"
|
||||||
|
require_path "$RESTORE_ROOT/ha-backup/homeassistant.tar.gz"
|
||||||
|
tar -xzf "$RESTORE_ROOT/ha-backup/homeassistant.tar.gz" -C "$RESTORE_ROOT/homeassistant/config" --strip-components=1 data
|
||||||
|
|
||||||
|
cp "$MOSQUITTO_REPO_CONF" "$RESTORE_ROOT/mosquitto/config/mosquitto.conf"
|
||||||
|
cp -a "$MOSQUITTO_APPDATA/config/." "$RESTORE_ROOT/mosquitto/appdata/config/"
|
||||||
|
cp -a "$MOSQUITTO_APPDATA/data/." "$RESTORE_ROOT/mosquitto/appdata/data/"
|
||||||
|
if [ -d "$MOSQUITTO_APPDATA/log" ]; then
|
||||||
|
cp -a "$MOSQUITTO_APPDATA/log/." "$RESTORE_ROOT/mosquitto/appdata/log/" || true
|
||||||
|
fi
|
||||||
|
cp -a "$FACHREPO_SOURCE/." "$RESTORE_ROOT/fachrepo/"
|
||||||
|
|
||||||
|
ha_config="$RESTORE_ROOT/homeassistant/config"
|
||||||
|
require_path "$ha_config/configuration.yaml"
|
||||||
|
require_path "$ha_config/secrets.yaml"
|
||||||
|
require_path "$ha_config/trusted_proxies.yaml"
|
||||||
|
require_path "$ha_config/.storage/onboarding"
|
||||||
|
require_path "$ha_config/.storage/auth"
|
||||||
|
|
||||||
|
fachrepo_head="$(git -C "$RESTORE_ROOT/fachrepo" log -1 --oneline)"
|
||||||
|
fachrepo_status="$(git -C "$RESTORE_ROOT/fachrepo" status --short)"
|
||||||
|
if [ -n "$fachrepo_status" ]; then
|
||||||
|
echo "Restored fachrepo clone is not clean:" >&2
|
||||||
|
echo "$fachrepo_status" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
backup_size="$(stat -c '%s' "$latest_backup")"
|
||||||
|
ha_file_count="$(find "$ha_config" -type f | wc -l | tr -d ' ')"
|
||||||
|
ha_bytes="$(du -sb "$ha_config" | awk '{print $1}')"
|
||||||
|
mosquitto_data_bytes="$(du -sb "$RESTORE_ROOT/mosquitto/appdata" | awk '{print $1}')"
|
||||||
|
|
||||||
|
RESTORE_ROOT="$RESTORE_ROOT" docker compose -f "$COMPOSE_FILE" down >/dev/null 2>&1 || true
|
||||||
|
RESTORE_ROOT="$RESTORE_ROOT" docker compose -f "$COMPOSE_FILE" up -d >/dev/null
|
||||||
|
|
||||||
|
mqtt_user="$(sed -n 's/^mqtt_username:[[:space:]]*//p' "$ha_config/secrets.yaml" | sed "s/^['\"]//;s/['\"]$//")"
|
||||||
|
mqtt_pass="$(sed -n 's/^mqtt_password:[[:space:]]*//p' "$ha_config/secrets.yaml" | sed "s/^['\"]//;s/['\"]$//")"
|
||||||
|
if [ -z "$mqtt_user" ] || [ -z "$mqtt_pass" ]; then
|
||||||
|
echo "Missing mqtt_username or mqtt_password in restored HA secrets.yaml" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mqtt_topic="restoretest/homeassistant/smoke"
|
||||||
|
mqtt_payload="ok-$(date +%s)"
|
||||||
|
mqtt_out="$RESTORE_ROOT/mqtt-sub.out"
|
||||||
|
rm -f "$mqtt_out"
|
||||||
|
docker exec -e MQTT_USER="$mqtt_user" -e MQTT_PASS="$mqtt_pass" -e MQTT_TOPIC="$mqtt_topic" \
|
||||||
|
restoretest-ha-mosquitto sh -lc \
|
||||||
|
'mosquitto_sub -h 127.0.0.1 -p 1883 -u "$MQTT_USER" -P "$MQTT_PASS" -t "$MQTT_TOPIC" -C 1 -W 10' \
|
||||||
|
> "$mqtt_out" &
|
||||||
|
sub_pid=$!
|
||||||
|
sleep 1
|
||||||
|
docker exec -e MQTT_USER="$mqtt_user" -e MQTT_PASS="$mqtt_pass" -e MQTT_TOPIC="$mqtt_topic" -e MQTT_PAYLOAD="$mqtt_payload" \
|
||||||
|
restoretest-ha-mosquitto sh -lc \
|
||||||
|
'mosquitto_pub -h 127.0.0.1 -p 1883 -u "$MQTT_USER" -P "$MQTT_PASS" -t "$MQTT_TOPIC" -m "$MQTT_PAYLOAD"'
|
||||||
|
wait "$sub_pid"
|
||||||
|
mqtt_result="$(cat "$mqtt_out")"
|
||||||
|
if [ "$mqtt_result" != "$mqtt_payload" ]; then
|
||||||
|
echo "MQTT publish/subscribe smoke failed" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
retained_topic="restoretest/homeassistant/retained"
|
||||||
|
retained_payload="retained-$(date +%s)"
|
||||||
|
docker exec -e MQTT_USER="$mqtt_user" -e MQTT_PASS="$mqtt_pass" -e MQTT_TOPIC="$retained_topic" -e MQTT_PAYLOAD="$retained_payload" \
|
||||||
|
restoretest-ha-mosquitto sh -lc \
|
||||||
|
'mosquitto_pub -h 127.0.0.1 -p 1883 -u "$MQTT_USER" -P "$MQTT_PASS" -t "$MQTT_TOPIC" -m "$MQTT_PAYLOAD" -r'
|
||||||
|
docker restart restoretest-ha-mosquitto >/dev/null
|
||||||
|
sleep 3
|
||||||
|
retained_result="$(docker exec -e MQTT_USER="$mqtt_user" -e MQTT_PASS="$mqtt_pass" -e MQTT_TOPIC="$retained_topic" \
|
||||||
|
restoretest-ha-mosquitto sh -lc \
|
||||||
|
'mosquitto_sub -h 127.0.0.1 -p 1883 -u "$MQTT_USER" -P "$MQTT_PASS" -t "$MQTT_TOPIC" -C 1 -W 10' | tr -d '\r')"
|
||||||
|
if [ "$retained_result" != "$retained_payload" ]; then
|
||||||
|
echo "MQTT retained smoke failed" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ha_http_status=""
|
||||||
|
ha_body="$RESTORE_ROOT/ha-http-body.html"
|
||||||
|
for _ in $(seq 1 180); do
|
||||||
|
ha_http_status="$(curl -sS -o "$ha_body" -w '%{http_code}' http://127.0.0.1:18123/ || true)"
|
||||||
|
if [ "$ha_http_status" = "200" ] && grep -qi "Home Assistant" "$ha_body"; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
if [ "$ha_http_status" != "200" ] || ! grep -qi "Home Assistant" "$ha_body"; then
|
||||||
|
echo "HA HTTP smoke failed, status=$ha_http_status" >&2
|
||||||
|
docker logs --tail 120 restoretest-homeassistant >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ha_api_status="$(curl -sS -o "$RESTORE_ROOT/ha-api.json" -w '%{http_code}' \
|
||||||
|
-H "Authorization: Bearer $(cat "$HA_TOKEN_FILE")" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
http://127.0.0.1:18123/api/ || true)"
|
||||||
|
if [ "$ha_api_status" != "200" ]; then
|
||||||
|
echo "HA API token smoke failed, status=$ha_api_status" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
RESTORE_ROOT="$RESTORE_ROOT" docker compose -f "$COMPOSE_FILE" exec -T restoretest-homeassistant \
|
||||||
|
python -m homeassistant --script check_config --config /config >/tmp/restoretest-ha-check-config.out
|
||||||
|
|
||||||
|
write_report "$REPORT_FILE" <<EOF
|
||||||
|
# Home Assistant Restore Test Report - $(date +%F)
|
||||||
|
|
||||||
|
- Service: \`homeassistant\` + \`smarthome-mosquitto\`
|
||||||
|
- HA backup source: \`$latest_backup\`
|
||||||
|
- Restore root: \`$RESTORE_ROOT\`
|
||||||
|
- Test containers:
|
||||||
|
- \`restoretest-homeassistant\`
|
||||||
|
- \`restoretest-ha-mosquitto\`
|
||||||
|
- Test endpoints:
|
||||||
|
- HA: \`http://127.0.0.1:18123\`
|
||||||
|
- MQTT: \`127.0.0.1:11883\`
|
||||||
|
- Result: \`SUCCESS\`
|
||||||
|
|
||||||
|
## Checks
|
||||||
|
|
||||||
|
- HA-native backup tar readable: \`ok\`
|
||||||
|
- HA inner archive restored: \`ok\`
|
||||||
|
- HA backup size bytes: \`$backup_size\`
|
||||||
|
- Restored HA file count: \`$ha_file_count\`
|
||||||
|
- Restored HA bytes: \`$ha_bytes\`
|
||||||
|
- Restored Mosquitto appdata bytes: \`$mosquitto_data_bytes\`
|
||||||
|
- Fachrepo clone clean: \`ok\`
|
||||||
|
- Fachrepo HEAD: \`$fachrepo_head\`
|
||||||
|
- HA HTTP status: \`$ha_http_status\`
|
||||||
|
- HA API token smoke: \`$ha_api_status\`
|
||||||
|
- HA check_config: \`ok\`
|
||||||
|
- MQTT publish/subscribe with restored credentials: \`ok\`
|
||||||
|
- MQTT retained topic after broker restart: \`ok\`
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Productive \`homeassistant\` and \`smarthome-mosquitto\` containers were not used.
|
||||||
|
- Test ran without Traefik and without the productive domain.
|
||||||
|
- Test ports were bound to localhost only.
|
||||||
|
- Token and MQTT password values were used for smoke tests but not printed.
|
||||||
|
- Test data was cleaned after success: \`$([ "$KEEP_DATA" -eq 1 ] && echo no || echo yes)\`
|
||||||
|
EOF
|
||||||
|
|
||||||
|
RESTORE_SUCCESS=1
|
||||||
|
echo "Home Assistant restore test ok -> $REPORT_FILE"
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
# Immich Restore Test Plan
|
|
||||||
|
|
||||||
## Ziel
|
|
||||||
|
|
||||||
Nachweisen, dass `immich.dump` aus dem produktiven Borg-Archiv in einer isolierten Testumgebung wieder einspielbar ist und Immich-Server damit anlaufen, einloggen und Asset-Metadaten anzeigen kann.
|
|
||||||
|
|
||||||
Bewusst **nicht** Teil dieses Tests:
|
|
||||||
|
|
||||||
- Wiederherstellung produktiver Foto-Dateien aus `/mnt/user/photos/immich` und `/mnt/user/photos/family_archive`. Der Smoke-Test bleibt DB-/UI-zentriert.
|
|
||||||
- Machine-Learning-Container. Spart Image-Pull-Zeit und Resource-Last; ML-Features sind im Smoke-Test nicht erforderlich.
|
|
||||||
- Echte Browser-Login-Sequenz. Smoke-Test prueft nur, dass die Login-Seite ausgeliefert wird und die DB-Tabellen `asset` und `"user"` lesbar sind.
|
|
||||||
|
|
||||||
## Quelle
|
|
||||||
|
|
||||||
- Backup-Quelle: produktives Borg-Archiv (`hetzner_borg_appdata_critical` oder lokales Mirror)
|
|
||||||
- fachlich relevanter Dump im Archiv:
|
|
||||||
- `local/borg-dumps/latest/immich.dump`
|
|
||||||
- Erzeuger: `ops/borg-ui/scripts/pre-backup-dumps.sh`, Funktion `dump_pg_db immich_postgres ... immich immich` mit `pg_dump -Fc`
|
|
||||||
- produktive Foto-Pfade werden im Smoke-Test bewusst **nicht** angefasst
|
|
||||||
|
|
||||||
## Test-Ziel
|
|
||||||
|
|
||||||
- Restore-Lab: `/mnt/user/backups/restore-lab/immich`
|
|
||||||
- Testdatenpfade:
|
|
||||||
- `/mnt/user/backups/restore-lab/immich/postgres` (Test-Postgres-Datadir)
|
|
||||||
- `/mnt/user/backups/restore-lab/immich/upload` (leeres Upload-Volume, Immich-Server braucht den Pfad nur als Mountpoint)
|
|
||||||
- `/mnt/user/backups/restore-lab/immich/dumps/latest/immich.dump` (extrahierter Dump)
|
|
||||||
- Testcontainer:
|
|
||||||
- `restoretest-immich-server`
|
|
||||||
- `restoretest-immich-postgres` (`ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0` - identisch zur Produktion, weil VectorChord-Backups ein Image mit VectorChord brauchen)
|
|
||||||
- `restoretest-immich-redis` (`redis:8.8.0-alpine`, rebuildbar)
|
|
||||||
- Testport Web: `127.0.0.1:12283:2283`
|
|
||||||
- Report-Ziel: `/mnt/user/backups/restore-reports/immich-YYYY-MM-DD.md`
|
|
||||||
|
|
||||||
## Schutzregeln
|
|
||||||
|
|
||||||
- produktive Pfade `/mnt/user/photos/immich` und `/mnt/user/photos/family_archive` werden **nicht** in den Test-Container gemountet
|
|
||||||
- produktive Domain `immich.kaleschke.info` wird **nicht** uebernommen
|
|
||||||
- keine Traefik-Labels fuer die Testinstanz
|
|
||||||
- keine produktive `immich_postgres`-/`immich_redis`-Instanz fuer den Test verwenden
|
|
||||||
- ML-Container bleibt weg
|
|
||||||
- Testcontainer publishen nur auf `127.0.0.1`, nicht auf LAN- oder Tailscale-Interface
|
|
||||||
- Borg-Passphrase wird aus `/mnt/user/appdata/secrets/borg_repo_passphrase.txt` gelesen und niemals in Logs, Reports oder Doku geschrieben
|
|
||||||
|
|
||||||
## Geplanter Ablauf
|
|
||||||
|
|
||||||
1. Restore-Ziel unter `/mnt/user/backups/restore-lab/immich` vorbereiten (postgres, upload, dumps/latest)
|
|
||||||
2. `local/borg-dumps/latest/immich.dump` aus dem aktuellsten Borg-Archiv extrahieren
|
|
||||||
3. Test-Postgres (Immich-Postgres mit VectorChord) und Test-Redis mit `ops/restore-tests/immich-compose.test.yml` starten
|
|
||||||
4. `immich.dump` in Test-Postgres importieren (`pg_restore -Fc --clean --if-exists --no-owner --no-privileges`)
|
|
||||||
5. Testinstanz `restoretest-immich-server` starten
|
|
||||||
6. lokalen Smoke-Test gegen `http://127.0.0.1:12283` ausfuehren und Asset/User-Count aus DB lesen
|
|
||||||
7. Report unter `/mnt/user/backups/restore-reports/immich-YYYY-MM-DD.md` schreiben
|
|
||||||
8. Testcontainer stoppen und Restore-Lab bereinigen
|
|
||||||
|
|
||||||
## Smoke-Test
|
|
||||||
|
|
||||||
Minimal erfolgreich:
|
|
||||||
|
|
||||||
- Test-Postgres startet `healthy`
|
|
||||||
- `pg_restore -Fc` laeuft ohne Fehler durch
|
|
||||||
- Immich-Server liefert HTTP `200`, `302` oder `303` auf `/`
|
|
||||||
- Response enthaelt mindestens einen der Marker `Immich`, `Login`, `Signin`
|
|
||||||
- `select count(*) from asset;` und `select count(*) from "user";` sind lesbar
|
|
||||||
|
|
||||||
Optional spaeter:
|
|
||||||
|
|
||||||
- Echte Login-Form via API ansprechen
|
|
||||||
- VectorChord-/pgvector-Extensions explizit per `\dx` pruefen
|
|
||||||
- Test mit gemountetem **read-only** Foto-Sample-Pfad und Thumbnail-Rendering
|
|
||||||
- Test inkl. ML-Container, sobald genug Test-Ressourcen verfuegbar
|
|
||||||
|
|
||||||
## Bekannte Komplikationen
|
|
||||||
|
|
||||||
| Risiko | Beschreibung | Mitigation |
|
|
||||||
|---|---|---|
|
|
||||||
| Dump-Groesse unbekannt | `pg_dump -Fc` der Immich-DB kann je nach Asset-/Face-Tabellen mehrere GB sein | Erster Lauf bewusst mit `--what-if`, anschliessend Operator-Test mit Zeitmessung |
|
|
||||||
| `pg_restore`-Dauer unbekannt | Index-/Constraint-Aufbau und VectorChord-Index-Build koennen lange dauern | Test-Postgres mit Health-Polling startet; Lauf nicht abbrechen ohne `pg_restore`-Exit |
|
|
||||||
| VectorChord-/pgvector-Extension-Mismatch | Wenn das Test-Postgres-Image nicht zu Produktion passt, kann der Restore oder Immich-Start fehlschlagen | Compose pinnt denselben Digest wie `apps/immich/docker-compose.yml` |
|
|
||||||
| Immich-Server-Migrations beim Start | Immich fuehrt beim ersten Start DB-Migrations aus; das kann nach Restore noch laufen, bevor Web-UI antwortet | Smoke-Test pollt HTTP bis zu 120 s, bevor er als Fehler markiert |
|
|
||||||
| Asset-Files fehlen | Der Test mountet kein Foto-Volume; Immich zeigt "missing" auf Asset-Detail-Seiten | Smoke-Test prueft nur Login-Page und DB-Counts, nicht Asset-Rendering |
|
|
||||||
| ML-Endpoint unreachable | Immich-Server kann ML-Endpoint nicht erreichen | `IMMICH_MACHINE_LEARNING_URL` zeigt bewusst auf einen nicht erreichbaren Hostnamen; Login bleibt funktional, ML-Features bleiben deaktiviert |
|
|
||||||
|
|
||||||
## Noch offen vor dem ersten echten Lauf
|
|
||||||
|
|
||||||
- Dump-Groesse `immich.dump` auf dem Host bestimmen (`ls -lh /mnt/user/backups/borg/dumps/latest/immich.dump`)
|
|
||||||
- Erwartete Restore-Dauer durch ersten Lauf mit `--keep-data` messen
|
|
||||||
- Pruefen, ob die Immich-Tabellen `assets`/`users` im aktuellen Schema noch existieren (Schema-Drift bei Major-Update wuerde die Asset-Count-Query brechen, das Skript faengt das tolerant ab)
|
|
||||||
- Schedule-Eintrag in `ops/restore-tests/schedule.md`: aktuell ist Immich nur als "spaeter, eigener Sprint" gefuehrt. Erst nach erstem erfolgreichen Lauf in Schedule aufnehmen, z. B. quartalsweise.
|
|
||||||
@@ -34,7 +34,6 @@ Vor dem ersten Lauf muss Operator entscheiden:
|
|||||||
- `ops/restore-tests/immich-compose.test.yml`
|
- `ops/restore-tests/immich-compose.test.yml`
|
||||||
- `ops/restore-tests/immich-restore-test.sh`
|
- `ops/restore-tests/immich-restore-test.sh`
|
||||||
- `ops/restore-tests/immich-restore-test.ps1` (Scaffold, kein Live-Run)
|
- `ops/restore-tests/immich-restore-test.ps1` (Scaffold, kein Live-Run)
|
||||||
- `ops/restore-tests/immich-plan.md`
|
|
||||||
- `ops/restore-tests/immich-runbook.md`
|
- `ops/restore-tests/immich-runbook.md`
|
||||||
|
|
||||||
## Erster Lauf - trockene Variante
|
## Erster Lauf - trockene Variante
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
# Komodo Bootstrap Trockenlauf - Plan
|
|
||||||
|
|
||||||
## Ziel
|
|
||||||
|
|
||||||
Nachweisen, dass `ops/komodo/docker-compose.yml` als Recovery-Anker fuer einen Komodo-Kaltstart tauglich ist, ohne den produktiven Komodo-Stack anzufassen.
|
|
||||||
|
|
||||||
Bewusst **nicht** Teil dieses Tests:
|
|
||||||
|
|
||||||
- Restore aus dem produktiven `komodo-mongo.archive.gz`-Dump (eigene Folgeaufgabe; dieser Test prueft nur das Compose-Bootstrap, nicht den Daten-Restore).
|
|
||||||
- docker.sock-Mount fuer die Test-Periphery (die Test-Periphery darf nie produktive Container managen).
|
|
||||||
- Traefik-Route oder Authelia-Anbindung (Test laeuft ausschliesslich auf `127.0.0.1:19120`).
|
|
||||||
|
|
||||||
## Quelle
|
|
||||||
|
|
||||||
- Bootstrap-Anker: `ops/komodo/docker-compose.yml` (Soll-Stand laut `docs/SERVICES_RECOVERY.md` Stufe A-F).
|
|
||||||
- Image-Digests: identisch zur Produktion fuer komodo-core und komodo-periphery; Mongo-Image identisch.
|
|
||||||
|
|
||||||
## Test-Ziel
|
|
||||||
|
|
||||||
- Restore-Lab: `/mnt/user/backups/restore-lab/komodo`
|
|
||||||
- Wegwerf-Pfade:
|
|
||||||
- `/mnt/user/backups/restore-lab/komodo/mongo` (Test-Mongo-Datadir)
|
|
||||||
- `/mnt/user/backups/restore-lab/komodo/core` (Repo-Cache)
|
|
||||||
- `/mnt/user/backups/restore-lab/komodo/keys` (gemeinsamer Keys-Pfad fuer Core+Periphery)
|
|
||||||
- `/mnt/user/backups/restore-lab/komodo/periphery` (Periphery-Etc)
|
|
||||||
- Testcontainer:
|
|
||||||
- `restoretest-komodo-mongo`
|
|
||||||
- `restoretest-komodo-core` (Test-Port `127.0.0.1:19120`)
|
|
||||||
- `restoretest-komodo-periphery` (ohne docker.sock)
|
|
||||||
- Compose-Project: `restoretest-komodo` (isoliert von Produktions-Project `komodo`)
|
|
||||||
- Report-Ziel: `/mnt/user/backups/restore-reports/komodo-bootstrap-YYYY-MM-DD.md`
|
|
||||||
|
|
||||||
## Schutzregeln
|
|
||||||
|
|
||||||
- produktive Datadirs `/mnt/user/appdata/komodo/{mongo,core,periphery}` werden **nicht** gemountet
|
|
||||||
- produktive Container `komodo-mongo`, `komodo-core`, `komodo-periphery` werden **nicht** gestoppt
|
|
||||||
- produktive `KOMODO_*`-Secrets werden **nicht** verwendet
|
|
||||||
- Test-Compose enthaelt nur Wegwerf-Werte fuer `KOMODO_SECRET_KEY`, `KOMODO_WEBHOOK_SECRET`, `KOMODO_JWT_SECRET`, `KOMODO_PASSKEY` und Mongo-Root-Password
|
|
||||||
- Test-Periphery laeuft ohne docker.sock-Mount und ohne `/mnt/user/services`-Mount
|
|
||||||
- Test-Port nur auf `127.0.0.1:19120`, keine LAN-/Tailscale-Bindung
|
|
||||||
|
|
||||||
## Geplanter Ablauf
|
|
||||||
|
|
||||||
1. Restore-Lab-Pfade leer anlegen
|
|
||||||
2. `docker compose config` auf dem Test-Compose validieren
|
|
||||||
3. Mongo und Core hochfahren, auf Mongo-`healthy` warten
|
|
||||||
4. HTTP-Smoke gegen `http://127.0.0.1:19120` (Login-Seite oder Auth-Redirect erwartet)
|
|
||||||
5. Periphery dazustarten, kurz beobachten
|
|
||||||
6. Mongo-`authenticated ping` mit Test-Credentials
|
|
||||||
7. Report schreiben
|
|
||||||
8. Cleanup `docker compose down -v` und Restore-Lab loeschen (ausser `--keep-data`)
|
|
||||||
|
|
||||||
## Smoke-Test
|
|
||||||
|
|
||||||
Minimal erfolgreich:
|
|
||||||
|
|
||||||
- `docker compose config` valid
|
|
||||||
- Test-Mongo erreicht `healthy`
|
|
||||||
- Mongo-Authentifizierung mit Test-Creds funktioniert (`db.adminCommand({ping:1}).ok = 1`)
|
|
||||||
- Komodo-Core HTTP `200`, `302`, `303` oder `401` (alles ist ein valider Lebenszeichen)
|
|
||||||
- Test-Periphery container state `running`
|
|
||||||
|
|
||||||
Optional spaeter:
|
|
||||||
|
|
||||||
- Periphery-Verbindung gegen Test-Core verifizieren (braucht Periphery-Konfig mit `core_url`)
|
|
||||||
- Echtes Restore aus `komodo-mongo.archive.gz`-Dump in die Test-Mongo
|
|
||||||
- Schreiben einer Wegwerf-Resource (Server/Stack) ueber die API
|
|
||||||
|
|
||||||
## Bekannte Komplikationen
|
|
||||||
|
|
||||||
| Risiko | Beschreibung | Mitigation |
|
|
||||||
|---|---|---|
|
|
||||||
| Image-Drift | Komodo-Images aktualisieren ihre Major-Tag-Digests | Compose pinnt denselben Digest wie Produktion; bei Image-Update auch Test-Compose nachziehen |
|
|
||||||
| Port-Konflikt | wenn 19120 anderweitig belegt ist | nur `127.0.0.1`-Bind; bei Konflikt Port im Compose anpassen |
|
|
||||||
| Volume-Reste | unterbrochener Lauf laesst Wegwerf-Datadir liegen | Skript loescht Restore-Lab vor jedem Lauf; `--keep-data` ueberschreibt das bewusst |
|
|
||||||
| Periphery-Erreichbarkeit | Core sucht Periphery initial nicht aktiv | Test prueft nur Periphery `State.Status=running`; voller Handshake ist optional |
|
|
||||||
|
|
||||||
## Bestaetigte Laeufe
|
|
||||||
|
|
||||||
| Datum | Mode | Ergebnis | Report |
|
|
||||||
|---|---|---|---|
|
|
||||||
| 2026-05-30 | `--what-if` | Plan-Ausgabe wie erwartet | (kein Report, nur stdout) |
|
|
||||||
| 2026-05-30 | `--keep-data` | `SUCCESS`, 5/5 Checks gruen, Core HTTP `200`, Mongo healthy in ~6 s | `/mnt/user/backups/restore-reports/komodo-bootstrap-2026-05-30.md` |
|
|
||||||
|
|
||||||
## Folgeschritte
|
|
||||||
|
|
||||||
- Quartals-Belegung: Komodo-Bootstrap passt zum DR-Sanity-Check (`ops/restore-tests/schedule.md` Q2/Q4) und kann ohne Borg-Archiv jederzeit wiederholt werden.
|
|
||||||
- Optional fuer kuenftige Laeufe: echtes Restore aus `komodo-mongo.archive.gz` in die Test-Mongo, danach Schreiben einer Wegwerf-Resource ueber die API.
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user