Compare commits
126 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e44ce38cf2 | |||
| 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
|
||||
**/*.zip
|
||||
|
||||
# Generated reports
|
||||
ops/policy-checks/last-report.md
|
||||
|
||||
# Local/editor noise
|
||||
.DS_Store
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
@@ -22,7 +22,7 @@ Zusaetzlich je nach Thema:
|
||||
- Secrets: `docs/SECRETS_MAP.md`
|
||||
- GitOps-/Komodo-/Runtime-Drift: `docs/GITOPS_DRIFT_RUNBOOK.md`
|
||||
- Gesamtbild fuer KI-Agenten: `docs/AI_CONTEXT.md`
|
||||
- Home Assistant / Ecowitt / InfluxDB: `docs/HOME_ASSISTANT_INFLUXDB_ECOWITT.md`
|
||||
- Architektur-/Betriebsentscheidungen mit Begruendung: `docs/DECISIONS.md`
|
||||
|
||||
## Projektbeschreibung
|
||||
|
||||
@@ -90,7 +90,7 @@ Wenn Drift vermutet wird, nicht raten. Erst die Pflichtmatrix in `docs/GITOPS_DR
|
||||
- `traefik`: Host-Ports 80/443
|
||||
- `gitea`: SSH-Port 222
|
||||
- `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
|
||||
- `scrutiny`: `privileged: true` fuer SMART/Laufwerkszugriff
|
||||
- `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
|
||||
|
||||
- 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.
|
||||
- Aenderungen klein halten und nur den betroffenen Bereich anfassen.
|
||||
- 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.
|
||||
> **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)
|
||||
11. [Projektorganisation und Arbeitsmodus](#11-projektorganisation-und-arbeitsmodus)
|
||||
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 |
|
||||
| `dns_net` | bridge | Resolver-Schicht: AdGuard Home + Unbound | bleibt |
|
||||
| `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 |
|
||||
| `monitoring_net` | Compose-intern, bridge | zentraler Observability-Stack fuer Prometheus, Loki, Grafana, Promtail, Exporter und InfluxDB | Zielzustand |
|
||||
| `monitoring_influx_lan` | Compose-intern, bridge | nicht-oeffentliches Zusatznetz nur fuer Docker Host-Port-Publishing von InfluxDB 8181 | Zielzustand |
|
||||
| `glance_socket_net` | Compose-intern, `internal: true` | interner Zugriff von Glance auf den Docker-Socket-Proxy | umgesetzt |
|
||||
| `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 |
|
||||
|
||||
### 3.2 Finales Diagramm (vereinfacht)
|
||||
@@ -123,7 +125,8 @@ App-interne Netze
|
||||
├── immich_default (internal: true) ✅
|
||||
├── nextcloud_internal (internal: true) ✅
|
||||
├── 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
|
||||
├── tailscale
|
||||
@@ -145,6 +148,8 @@ Diese Dienste sind über echte `*.kaleschke.info`-Domains erreichbar:
|
||||
- `gitea` (Web) — git.kaleschke.info
|
||||
- `immich_server` — immich.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
|
||||
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 |
|
||||
| `unbound` | ✅ | `dns_net` | intern | Upstream-Resolver für AdGuard, isoliert | — |
|
||||
| `ddns-updater` | ✅ | `frontend_net` | intern | Cloudflare DNS API; bleibt in `frontend_net` | Dokumentierte Ausnahme |
|
||||
| `tailscale` | ✅ | `host` | VPN-Zugang | Git-Stack (`host-services/tailscale/`) | nutzt `NET_ADMIN`, `NET_RAW` und `/dev/net/tun` als dokumentierte VPN-Ausnahme |
|
||||
| `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
|
||||
|
||||
@@ -260,6 +265,7 @@ Legende Status:
|
||||
| `immich_redis` | ⏳ | `immich_default` | intern | intern-only | anonymes Volume → named volume |
|
||||
| `nextcloud-postgres` | ✅ | `nextcloud_internal` | intern | app-eigene Nextcloud-Datenbank mit `_FILE`-Secret | — |
|
||||
| `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
|
||||
|
||||
@@ -271,9 +277,10 @@ Legende Status:
|
||||
| `ntfy` | ✅ | `frontend_net` | Traefik | aktiv via `ntfy.kaleschke.info`, Git-Stack | — |
|
||||
| `gitea` | ✅ | `frontend_net` | Traefik + SSH-Port 222 | Web via Traefik, SSH direkt gebunden | — |
|
||||
| `immich_server` | ✅ | `immich_default`, `frontend_net` | Traefik | aktiv via `immich.kaleschke.info` | — |
|
||||
| `immich_machine_learning` | ✅ | `immich_default` | intern | bleibt intern | — |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
|---|---|---|
|
||||
| — | — | 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
|
||||
|
||||
@@ -370,23 +377,7 @@ labels:
|
||||
|
||||
## 9. Historische Migration (abgeschlossen)
|
||||
|
||||
Die frühere Blockmigration aus der Portainer-/Dockerman-Phase ist fachlich abgeschlossen.
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
## 10. Bekannte Ausnahmen und Begründungen
|
||||
|
||||
| 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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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)
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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`.
|
||||
Architektur- und Betriebsentscheidungen werden seit 2026-06-11 zentral in
|
||||
`docs/DECISIONS.md` gefuehrt (ADR-light: Entscheidung, Kontext, Review-Trigger).
|
||||
Dieses Dokument haelt nur noch das Zielbild. Neue Entscheidungen werden dort
|
||||
eingetragen; hier aendert sich nur etwas, wenn das Zielbild selbst betroffen
|
||||
ist (Netze, Zugangsmodell, Ausnahmen in Sektion 10).
|
||||
|
||||
---
|
||||
|
||||
## Schlussformel
|
||||
|
||||
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
|
||||
|
||||
- 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 bleibt bewusst bei nativer Authentifizierung; zentrale Traefik-Auth wird dort nicht pauschal vorgeschaltet.
|
||||
- Portainer CE ist abgeschaltet und kein Teil des aktiven Betriebs mehr.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
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
|
||||
depends_on:
|
||||
- redis
|
||||
@@ -32,12 +32,34 @@ services:
|
||||
|
||||
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
|
||||
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:
|
||||
- model-cache:/cache
|
||||
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_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:
|
||||
- no-new-privileges:true
|
||||
|
||||
@@ -75,5 +97,10 @@ networks:
|
||||
name: immich_default
|
||||
internal: true
|
||||
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:
|
||||
external: true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
mail-archiver:
|
||||
image: s1t5/mailarchiver@sha256:ea7fd8c2e3e0ef0941e8dd9e726e35a8de33296f5c7b9ed811df5168ae6a9714
|
||||
image: s1t5/mailarchiver@sha256:4ea7ecc47ad1dd2c523b85c3967574b61e39def1b6fd26edf874e21733c4018c
|
||||
container_name: mail-archiver
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
|
||||
@@ -4,6 +4,12 @@ services:
|
||||
container_name: mealie
|
||||
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:
|
||||
TZ: Europe/Berlin
|
||||
ALLOW_SIGNUP: "false"
|
||||
@@ -18,6 +24,16 @@ services:
|
||||
|
||||
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:
|
||||
- /mnt/user/appdata/mealie/data:/app/data
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
n8n:
|
||||
image: docker.n8n.io/n8nio/n8n:2.22.6@sha256:07138bb60aee990651e9c2090d7dde330cba3a5bd84fcc5cba63b2997243bc45
|
||||
image: docker.n8n.io/n8nio/n8n:2.27.2@sha256:77a649a752e07c417c3dad8ac094611f0e4aa79674964ad338ef702752a5988c
|
||||
container_name: n8n
|
||||
restart: unless-stopped
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
nextcloud:
|
||||
image: nextcloud:33.0.4-apache@sha256:caa40b8beaf0057ac213d8dfc515c36ce64f7a8f0825b6a287e6f7cf2f4a095d
|
||||
image: nextcloud:33.0.5-apache@sha256:56bdc45109067500fd0832fa64832b7c77a167d9394cbf5f0f4b59740b94194d
|
||||
container_name: nextcloud
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
ntfy:
|
||||
image: binwiederhier/ntfy@sha256:b32b4221a64ec2e7c000f0782b2feef24022e1a09a24e531640f4cbba6cfa1e6
|
||||
image: binwiederhier/ntfy@sha256:f8a9b104313b87cc24ae4f775f39e6328205b57dff6ede3eaf098a91e5d79f59
|
||||
container_name: ntfy
|
||||
restart: unless-stopped
|
||||
dns:
|
||||
|
||||
@@ -3,6 +3,9 @@ services:
|
||||
image: ghcr.io/paperless-ngx/paperless-ngx:2.20.15@sha256:6c86cad803970ea782683a8e80e7403444c5bf3cf70de63b4d3c8e87500db92f
|
||||
container_name: paperless-ngx
|
||||
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:
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
@@ -17,6 +20,11 @@ services:
|
||||
- PAPERLESS_OCR_LANGUAGE=deu+eng
|
||||
- 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
|
||||
- PAPERLESS_CONSUMER_ENABLE_BARCODES=1
|
||||
- PAPERLESS_CONSUMER_ENABLE_ASN_BARCODE=1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
super-productivity:
|
||||
image: johannesjo/super-productivity:v18.8.0@sha256:c739caca8e0c5e83ea4a6289884079ac49e0c3c87c7f95598b5a9fb10cc2d9c4
|
||||
image: johannesjo/super-productivity:v18.9.1@sha256:773760107344e739f4c29409f7842db66a1b167d50eb2c40248cb5b5b328652e
|
||||
container_name: super-productivity
|
||||
restart: unless-stopped
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
unbound:
|
||||
image: shaanmajid/unbound:1.25.1@sha256:96809ff052e8bd79bba30e067d8b27ed9a2f069b6b2a3484fe1d0eb45aba07c5
|
||||
image: shaanmajid/unbound:1.25.1@sha256:f140db02a005904802bf5840093e95e675321aa060a00426fdffc2a3ac2eeb6b
|
||||
container_name: unbound
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
|
||||
+13
-40
@@ -1,8 +1,10 @@
|
||||
# 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.
|
||||
Diese Datei enthaelt bewusst **keinen** Arbeitsstand mehr — Status nur in
|
||||
`docs/MASTER_TODO.md`, Entscheidungen nur in `docs/DECISIONS.md`.
|
||||
|
||||
## Systembild
|
||||
|
||||
@@ -20,6 +22,7 @@ Kurzer Kontext fuer KI-Agenten. Nicht als Ersatz fuer die echten Runbooks lesen.
|
||||
3. betroffene Compose-Datei
|
||||
4. bei Service-Fragen `docs/SERVICE_CATALOG.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
|
||||
|
||||
@@ -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.
|
||||
- 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.
|
||||
- Doku-Regel: ein Fakt hat genau ein Zuhause; verlinken statt kopieren (`docs/REPO_MAP.md`).
|
||||
|
||||
## Bekannte Ausnahmen
|
||||
|
||||
Autoritativ: `HOMELAB_ARCHITECTURE_MASTER_V2.md` §10. Kurzliste:
|
||||
|
||||
- Traefik: Host-Ports 80/443, WAN-Freigabe nur 443
|
||||
- Gitea: SSH auf Host-Port 222, keine WAN-Freigabe
|
||||
- AdGuard: DNS 53 direkt; Admin nur auf Tailscale-IP `100.80.98.33:8082`
|
||||
- Tailscale und Plex: Host-Netz
|
||||
- Scrutiny: privileged
|
||||
- Komodo/Periphery: Docker-Socket-Zugriff
|
||||
- Tailscale: natives Unraid-Plugin (nicht repo-verwaltet); Plex: Host-Netz
|
||||
- Scrutiny: privileged; Komodo/Periphery: Docker-Socket
|
||||
- InfluxDB 3 Core: `127.0.0.1:8181`, Root-User-Ausnahme dokumentiert
|
||||
|
||||
## Aktuelle Restpunkte
|
||||
## Arbeitsstand
|
||||
|
||||
Authoritativ: `docs/MASTER_TODO.md`.
|
||||
|
||||
Kurzfassung:
|
||||
|
||||
- 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.
|
||||
- Offene Punkte: `docs/MASTER_TODO.md` (einzige Statusliste)
|
||||
- Entscheidungen und Begruendungen: `docs/DECISIONS.md`
|
||||
- Belege/Reports: `/mnt/user/backups/restore-reports/` auf dem Host
|
||||
|
||||
@@ -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 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 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -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/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/EXTERNAL_DEPENDENCIES.md` - externe Provider/Konten und Ausfall-Szenarien
|
||||
- `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/`
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
- 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
|
||||
- regelmaessige automatisierte Restore-Smoke-Tests fuer Vaultwarden, Gitea und Paperless etablieren
|
||||
Offene Punkte werden in `docs/MASTER_TODO.md` gefuehrt. Daueraufgaben:
|
||||
|
||||
- Unraid-Flash-Artefakt regelmaessig pruefen (`ops/maintenance/check-unraid-flash-backup.sh`)
|
||||
- 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
|
||||
- `baerchen` Recovery-USB-Boot-/SMB-Test nach erfolgreichem erstem Full-Lauf
|
||||
verifizieren
|
||||
- Restore-Drills nach Kadenz aus `ops/restore-tests/schedule.md` rotieren
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
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
|
||||
cat > ~/dr-smoke.sh <<'EOF'
|
||||
#!/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 |
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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`.
|
||||
Falls der Punkt noch als offen in `docs/MASTER_TODO.md` steht, dort in den Kurzlog uebernehmen.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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 |
|
||||
| Let's Encrypt | TLS-Zertifikate | hoch | Cert-Erneuerung faellt aus | automatisch via Traefik und Cloudflare DNS-Challenge | Cert-Expiry Alert einrichten; Cloudflare-Token und Traefik-Storage pruefen |
|
||||
| Container Registries | Image Pulls von Docker Hub, GHCR, LSCR, Gitea Registry u. a. | mittel | Redeploy/Update blockiert | ueberwiegend oeffentlich; keine produktiven Registry-Tokens im Repo | Gepinnte Digests und lokale Runtime helfen kurzfristig; Updates geplant und einzeln deployen |
|
||||
| Plex Konto/Remote Access | Plex native Auth, ggf. Remote Access und Claim | mittel | Plex-Clients/Remote-Funktionen koennen ausfallen | Plex-Konto ausserhalb Repo; `PLEX_CLAIM` nur fuer Setup | LAN-Medienpfade bleiben lokal; Konto-Recovery separat sichern |
|
||||
| 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 |
|
||||
| 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 |
|
||||
|---|---|---|
|
||||
| 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 | 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-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-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.
|
||||
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
|
||||
|
||||
|
||||
@@ -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 |
|
||||
| **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 |
|
||||
| **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.
|
||||
|
||||
|
||||
@@ -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) |
|
||||
| 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
|
||||
|
||||
@@ -157,10 +157,10 @@ Bewertung:
|
||||
|
||||
## Stromverbrauch
|
||||
|
||||
**Operator-Entscheidung offen: Messgeraet beschaffen.** Stand 2026-06-05 ist kein
|
||||
Smart-Plug/Messgeraet vorhanden, daher liegen keine Messwerte vor. Die Werte
|
||||
bleiben bewusst offen, bis ein messfaehiges Geraet beschafft ist. Erst danach
|
||||
werden Idle/Normal/Backup/Last in einem Durchlauf erfasst.
|
||||
**Bewusst ohne Messung (Operator-Entscheidung 2026-06-06).** Es wird kein
|
||||
Messgeraet beschafft; Idle/Normal/Backup/Last bleiben dauerhaft offen. Kein
|
||||
offener Todo. Falls spaeter doch eine Mess-Steckdose angeschafft wird, reicht
|
||||
ein einziger Messdurchlauf, um die Tabelle zu fuellen.
|
||||
|
||||
| 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.
|
||||
+42
-68
@@ -1,110 +1,84 @@
|
||||
# Master To-do - KalliLab CORE
|
||||
|
||||
Stand: 2026-06-05 (Wochenend-Sprint, nach Status-Kategorien sortiert)
|
||||
Typ: Status/To-do · Stand: 2026-06-17 · Status: aktiv
|
||||
|
||||
Diese Liste ist die zentrale Arbeitsliste fuer offene operative Punkte im
|
||||
Homelab. Detailentscheidungen bleiben in den verlinkten Runbooks; diese Datei
|
||||
haelt Status, naechsten konkreten Schritt und Quelle zusammen.
|
||||
Diese Liste ist die **einzige** Arbeitsliste fuer offene operative Punkte im
|
||||
Homelab. Detailablaeufe stehen in den verlinkten Runbooks; Entscheidungen mit
|
||||
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
|
||||
|
||||
- **Aktiv dieses Wochenende** - soll jetzt vorankommen (Claude, Codex oder Operator); konkreter naechster Schritt steht.
|
||||
- **Operator-Entscheidung** - wartet auf eine bewusste Entscheidung des Betreibers (ja/nein/welche Option).
|
||||
- **Aktiv** - soll vorankommen; konkreter naechster Schritt steht.
|
||||
- **Operator-Entscheidung** - wartet auf eine bewusste Entscheidung (ja/nein/Option).
|
||||
- **Geparkt** - bewusst nicht jetzt, mit klarem Review-Trigger.
|
||||
- **Extern blockiert** - wartet auf ein externes Ereignis oder eine Abhaengigkeit (Nachtlauf, zweite Hardware, Geraetebeschaffung).
|
||||
|
||||
Owner-Aufteilung fuer das Wochenende: `baerchen`/Veeam/Backup-Verifikation liegt
|
||||
bei **Codex**; Doku-/Inventar-/Onboarding-Arbeit liegt bei **Claude**;
|
||||
Host-/Entscheidungsaufgaben beim **Operator**.
|
||||
- **Extern blockiert** - wartet auf ein externes Ereignis oder eine Abhaengigkeit.
|
||||
|
||||
---
|
||||
|
||||
## Aktiv dieses Wochenende
|
||||
## Aktiv
|
||||
|
||||
| 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"). 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` |
|
||||
| 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 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" |
|
||||
| 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" |
|
||||
| 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" |
|
||||
| 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)" |
|
||||
| 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` |
|
||||
| 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` |
|
||||
| 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` |
|
||||
| Restore-Test Tailscale | Operator | State-Validierung + Reconnect nur auf Wegwerf-Host/VM, danach Geraet in Tailscale-Admin entfernen | `ops/restore-tests/tailscale-runbook.md` |
|
||||
| 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` |
|
||||
| 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` |
|
||||
|
||||
---
|
||||
|
||||
## Operator-Entscheidung
|
||||
|
||||
| Thema | Entscheidungsfrage | 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` |
|
||||
| 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` |
|
||||
**Stand 2026-06-11: keine offenen Operator-Entscheidungen.**
|
||||
Getroffene Entscheidungen mit Begruendung und Review-Trigger: `docs/DECISIONS.md`.
|
||||
|
||||
---
|
||||
|
||||
## 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` |
|
||||
| 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` |
|
||||
| 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` |
|
||||
| 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` |
|
||||
| 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` |
|
||||
| Negativ-Test Backup-Frische | Quartalsweise: bewusst kaputten/fehlenden Dump in Testpfad simulieren, pruefen ob `homelab-alerts` feuert | `docs/AUDIT_2026-05-25_TODO.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` |
|
||||
| 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` | Optional spaeter, nur wenn Unraid-User-/Share-Rechte bewusst angefasst werden | `ops/windows-reinstall/docs/windows-image-backup-baseline.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` |
|
||||
| Hermes-Agent | NAS-Stack bleibt deaktiviert; Review-Deadline 2026-07-25 | `docs/AUDIT_2026-05-25_TODO.md`, `docs/SERVICE_CATALOG.md` |
|
||||
| Filebrowser-Mounts | Bei zukuenftigem Hardening-Sprint Mount-Scope reduzieren | `docs/SERVICE_CATALOG.md` |
|
||||
| Scrutiny Privileged-Ausnahme | Nur mit klarer Begruendung aendern; sonst dokumentierte Ausnahme beibehalten | `docs/SERVICE_CATALOG.md` |
|
||||
| Immich Redis named volume | Anonymes Volume bei passender Wartung auf named volume umstellen oder Ausnahme dokumentieren | `docs/SERVICE_CATALOG.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` |
|
||||
| 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` |
|
||||
| USV-Anschaffung | Q3-Review ab 2026-07-01, Hardware-Upgrade oder realer Stromausfall mit Datenfolge | `docs/DECISIONS.md` |
|
||||
| Cold-Backup-Rotation (zweites Off-site-Ziel) | Hetzner-Probleme, stark wachsender Datenwert oder geaenderte Praeferenz | `docs/DECISIONS.md` |
|
||||
| WAN-Ausfallschutz | haeufigere/laengere DSL-Ausfaelle oder kritischer Remote-Zugang | `docs/NETWORK_INVENTORY.md` |
|
||||
| Borg `append-only` auf Hetzner | robusterer Hetzner-Mechanismus oder geaendertes Ransomware-Risikoprofil | `docs/DECISIONS.md` |
|
||||
| CrowdSec vor Traefik | breitere Attack Surface als nur `443/tcp` | `docs/DECISIONS.md` |
|
||||
| Nextcloud 2FA (Operator-TOTP) | OIDC-/SSO-Block erreicht die App-Login-Ebene | `docs/DECISIONS.md` |
|
||||
| Hermes-Agent | Review-Deadline 2026-07-25; NAS-Stack bleibt deaktiviert | `docs/SERVICE_CATALOG.md` |
|
||||
| Dedizierter SMB-User `veeam-baerchen` | 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` |
|
||||
| Scrutiny Privileged-Ausnahme | nur mit klarer Begruendung aendern | `docs/SERVICE_CATALOG.md` |
|
||||
| Immich Redis named volume | passende Wartung am Immich-Stack | `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` |
|
||||
| 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` |
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
## Extern blockiert
|
||||
|
||||
Wartet auf ein externes Ereignis oder eine Abhaengigkeit.
|
||||
|
||||
| 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 (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` |
|
||||
| End-to-end-DR-Drill | Keine zweite Wegwerf-Hardware verfuegbar | Komplett-Bootstrap Phase 1-5 fahren | `docs/DISASTER_RECOVERY.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.
|
||||
- Restore-Test-Runbook-Stubs fuer Unraid Flash / AdGuard / Tailscale / Redis 8 in `docs/RESTORE_MATRIX.md` ergaenzt.
|
||||
- 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.
|
||||
- `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.
|
||||
- `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.**
|
||||
- `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.
|
||||
- **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.
|
||||
- **2026-06-17** Repo-Hygiene abgeschlossen: Glance-Widget-Tokens sind in Runtime gesetzt, Audit-PDF liegt extern unter `H:\kallilab-recovery\audits`, Worktree clean.
|
||||
- **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.
|
||||
- **2026-06-13** Home Assistant MQTT-Integration produktiv verbunden: Config-Entry `smarthome-mosquitto` ist `loaded`, Mosquitto sieht den HA-Client `homeassistant`; `check_config` gruen.
|
||||
- **2026-06-13** HA Energy Dashboard konfiguriert: Netz, PV und Speicher aus SolarEdge Local gesetzt, `energy/validate` ohne Issues; HA-Backup danach erzeugt.
|
||||
|
||||
---
|
||||
|
||||
## Pflege-Regel
|
||||
|
||||
- Neue operative To-dos zuerst hier eintragen oder aus Detaildokumenten hierher uebernehmen, immer mit Status-Kategorie.
|
||||
- Wenn ein Punkt erledigt ist, in der Detaildoku den Beleg/Report eintragen und diese Liste aktualisieren.
|
||||
- Neue operative To-dos zuerst hier eintragen, immer mit Status-Kategorie.
|
||||
- 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.
|
||||
- Historische Drill-Reports bleiben Belegmaterial, aber nicht die fuehrende Arbeitsliste.
|
||||
|
||||
+220
-64
@@ -1,7 +1,7 @@
|
||||
# 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.
|
||||
Letzte Pruefung: 2026-06-05 (Tailscale-Inventar), 2026-06-01 (Router/Ports)
|
||||
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-17 (Tailscale-Inventar), 2026-06-01 (Router/Ports)
|
||||
|
||||
## Zweck
|
||||
|
||||
@@ -38,7 +38,7 @@ Dieses Dokument beschreibt Router, DNS, Tailscale, Portfreigaben und Netztrennun
|
||||
| Komponente | Rolle | Adresse | Bemerkung |
|
||||
|---|---|---|---|
|
||||
| AdGuard Home | LAN DNS / Filter | Host `192.168.178.58`, Docker `172.23.0.3` | DNS auf Port 53; Admin soll nur via Tailscale-IP `100.80.98.33:8082` erreichbar sein |
|
||||
| Unbound | Rekursiver Resolver | Docker `dns_net` | Upstream fuer AdGuard |
|
||||
| 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 |
|
||||
| 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) |
|
||||
| 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. |
|
||||
| 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 |
|
||||
|---|---|---|---|
|
||||
| `100.80.98.33` | kallilabcore | linux | aktiv (Host, Subnet-Router) |
|
||||
| `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 |
|
||||
| `100.112.0.90` | kallilab-core | linux | gelistet, kein aktiver Verkehr — **moeglicher Alt-/Dubletten-Node**, separat pruefen |
|
||||
| `100.73.83.55` | iphone-14 | iOS | bekannt, aktuell offline |
|
||||
|
||||
> Hygiene-Hinweis (kein Secret): Es existieren zwei linux-Nodes mit aehnlichem
|
||||
> Namen (`kallilabcore` und `kallilab-core`) sowie zwei `baerchen`-Nodes
|
||||
> (`baerchen-1` aktiv, `baerchen` offline). Bei Gelegenheit in der
|
||||
> Tailscale-Admin-Konsole pruefen, ob die inaktiven Eintraege stillgelegt werden
|
||||
> koennen. Das ist Aufraeumarbeit, kein akutes Risiko.
|
||||
> **Historischer Befund 2026-06-06 (read-only auf dem Host ermittelt):** Der Host
|
||||
> hatte damals **zwei** `tailscaled`-Prozesse:
|
||||
>
|
||||
> 1. **Native Unraid-Plugin** = `kallilabcore` (100.80.98.33). Prozess
|
||||
> `/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
|
||||
|
||||
@@ -91,34 +114,22 @@ tailscale ip -4
|
||||
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`
|
||||
verwaltet (kein Wert/Secret gehoert ins Repo). Aktueller Live-Stand ist
|
||||
Default-Allow (`src: ["*"] -> dst: ["*:*"]`), d. h. jedes Tailnet-Geraet darf
|
||||
alles inklusive der LAN-Subnet-Route.
|
||||
**Status: live und verifiziert.** Die restriktive Policy wurde am 2026-06-06
|
||||
gemeinsam mit dem Operator in der lockout-sicheren Reihenfolge ausgerollt und
|
||||
read-only verifiziert (siehe "Rollout-Protokoll" unten). Ausgangspunkt war die
|
||||
**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.
|
||||
- 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):**
|
||||
**Angewendete Policy (live, kein Secret):**
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -130,42 +141,64 @@ alles inklusive der LAN-Subnet-Route.
|
||||
"autoApprovers": {
|
||||
"routes": { "192.168.178.0/24": ["tag:server"] }
|
||||
},
|
||||
"acls": [
|
||||
{ "action": "accept", "src": ["tag:operator"], "dst": ["*:*"] },
|
||||
{ "action": "accept", "src": ["tag:server"], "dst": ["tag:operator:*"] },
|
||||
{ "action": "accept", "src": ["tag:family"], "dst": ["100.80.98.33:443"] }
|
||||
"grants": [
|
||||
{"src": ["tag:operator"], "dst": ["*"], "ip": ["*"]},
|
||||
{"src": ["tag:server"], "dst": ["tag:operator"], "ip": ["*"]},
|
||||
{"src": ["tag:family"], "dst": ["tag:server"], "ip": ["tcp:443"]}
|
||||
],
|
||||
"ssh": [
|
||||
{ "action": "accept", "src": ["tag:operator"], "dst": ["tag:server"],
|
||||
"users": ["root", "autogroup:nonroot"] }
|
||||
{"action": "check", "src": ["autogroup:member"], "dst": ["autogroup:self"],
|
||||
"users": ["autogroup:nonroot", "root"]}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> Die `tag:family`-`dst` `100.80.98.33:443` ist ein **Platzhalter** und wird
|
||||
> durch die real benoetigten Familien-Dienste ersetzt, sobald diese feststehen.
|
||||
**Geraete-Tags (live):** `kallilabcore` = `tag:server`; `baerchen-1` + `iphone-14`
|
||||
= `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
|
||||
zunaechst behalten**.
|
||||
2. Geraete taggen (`baerchen-1`, `iphone-14` -> operator; `kallilabcore` ->
|
||||
server) und je Geraet die Verbindung verifizieren.
|
||||
3. Subnet-Route bleibt approved (jetzt via `autoApprovers`/`tag:server`).
|
||||
4. **Erst zuletzt** die Allow-all-Regel entfernen -> restriktiv schalten.
|
||||
5. Sofort Smoke-Tests fahren (siehe unten).
|
||||
1. Policy additiv erweitert (Tags/grants definiert, Allow-all noch drin) -> alle Peers unveraendert verbunden, Route approved.
|
||||
2. `baerchen-1` getaggt `tag:operator` -> online, verifiziert.
|
||||
3. `iphone-14` getaggt `tag:operator` -> verifiziert.
|
||||
4. `kallilab-core` faktisch geprueft (Docker-Sidecar, keine Abhaengigen) -> bewusst untagged gelassen.
|
||||
5. Host `kallilabcore` getaggt `tag:server` -> Route blieb via `autoApprovers` automatisch approved, SSH ok.
|
||||
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.
|
||||
|
||||
**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).
|
||||
- `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.
|
||||
**Hintergrund / Designentscheidungen (2026-06-05/06):**
|
||||
|
||||
**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).
|
||||
2. Konkrete Liste der Dienste/Ports, die Familiengeraete ueber Tailscale brauchen.
|
||||
**Offene Folgepunkte (kein Risiko, Hygiene/spaeter):**
|
||||
|
||||
- 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
|
||||
|
||||
@@ -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. |
|
||||
| `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
|
||||
|
||||
@@ -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 |
|
||||
| 222/tcp | Gitea SSH | Git SSH | nur LAN/Tailscale; keine WAN-Freigabe |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
@@ -252,6 +287,126 @@ docker network inspect frontend_net | jq '.[0].Containers | keys'
|
||||
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
|
||||
|
||||
| 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-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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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
|
||||
|
||||
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
|
||||
|
||||
| Datei | Zweck |
|
||||
|---|---|
|
||||
| `../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 |
|
||||
| `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 |
|
||||
| `DECISIONS.md` | Entscheidungs-Register (ADR-light) |
|
||||
| `MASTER_TODO.md` | einzige operative Statusliste |
|
||||
|
||||
## Betrieb und Recovery
|
||||
|
||||
| Datei | Zweck |
|
||||
|---|---|
|
||||
| `DISASTER_RECOVERY.md` | Wiederanlauf nach Host-/Systemausfall |
|
||||
| `RESTORE_MATRIX.md` | Restore-Quellen, Dumps, Secrets und Smoke-Tests je Dienst |
|
||||
| `RESTORE_HANDBOOK.md` | praktische Restore-Anleitung |
|
||||
| `RESTORE_MATRIX.md` | Restore-Quellen, Dumps, Secrets, Smoke-Tests und Test-Reifegrad je Dienst |
|
||||
| `SERVICES_RECOVERY.md` | Gitea-/Komodo-/Services-Bootstrap |
|
||||
| `ROLLBACK.md` | Rueckweg bei GitOps-/Deploy-Fehlern |
|
||||
| `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
|
||||
|
||||
@@ -31,11 +40,13 @@ Diese Datei trennt aktive Betriebsdokumentation von historischer Arbeitsdoku. Ne
|
||||
|---|---|
|
||||
| `STORAGE_LAYOUT.md` | verbindliche Storage-/Share-/Pfad-Regeln |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `CAPACITY_AND_LIFECYCLE.md` | Kapazitaet, Wachstum und Upgrade-Trigger |
|
||||
| `CAPACITY_AND_LIFECYCLE.md` | Kapazitaet, Wachstum, Upgrade-Trigger, H:/-Nearline-Einordnung |
|
||||
|
||||
## Monitoring und Automatisierung
|
||||
|
||||
@@ -43,18 +54,20 @@ Diese Datei trennt aktive Betriebsdokumentation von historischer Arbeitsdoku. Ne
|
||||
|---|---|
|
||||
| `ALERT_RULES.md` | Prometheus-/ntfy-Regeln und Handlungslogik |
|
||||
| `RENOVATE.md` | Self-hosted Renovate gegen Gitea |
|
||||
| `HOME_ASSISTANT_INFLUXDB_ECOWITT.md` | Home Assistant -> InfluxDB 3 -> Grafana |
|
||||
| `H_DRIVE_NEARLINE_PULL.md` | Windows-H:/ Nearline-Pull fuer kritische Restore-Artefakte |
|
||||
| `runbooks/komodo-bulk-deploy-dns.md` | Bulk-Deploy-Pulls scheitern an DNS bei AdGuard-Recreate |
|
||||
| `../ops/h-drive-nearline/README.md` | Windows-H:/ Nearline-Pull fuer kritische Restore-Artefakte |
|
||||
|
||||
## Nutzer- und Planungsdoku
|
||||
## Nutzer- und Statusdoku
|
||||
|
||||
| Datei | Zweck |
|
||||
|---|---|
|
||||
| `FAMILY_ONBOARDING.md` | familienverstaendliche Nutzungsdoku |
|
||||
| `AUDIT_2026-05-25_TODO.md` | kompakte Restliste aus dem Audit-Zyklus |
|
||||
| `MASTER_TODO.md` | zentrale operative Master-To-do-Liste ueber alle Bereiche |
|
||||
| `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 |
|
||||
| `AI_CONTEXT.md` | kompakter Kontext fuer KI-Agenten (Regeln + Pointer, kein Status) |
|
||||
| `homelab-optimierung.md` | technisches Optimierungs-Assessment 2026-06-10 (offene Empfehlungen) |
|
||||
|
||||
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 |
|
||||
| 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 |
|
||||
| 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
|
||||
|
||||
|
||||
+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/SECRETS_MAP.md` | Secret-Namen und Pfade ohne Werte |
|
||||
| `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/runbooks/komodo-bulk-deploy-dns.md` | Bulk-Deploy-Pulls scheitern an DNS, wenn AdGuard im selben Batch recreated wird |
|
||||
|
||||
## 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 |
|
||||
| `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
|
||||
gebraucht wird. Einmalige Audits, Prompt-Kopien und lange Verlaufsprotokolle
|
||||
gehoeren in Git-Commits, nicht als neue Markdown-Dateien.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
+23
-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 |
|
||||
| Traefik | Share / Borg | `/mnt/user/appdata/traefik`, besonders `dynamic/`, `letsencrypt`, `secrets` | keine eigene DB | `cloudflare_dns_api_token` | `frontend_net`, `backend_net` | `https://traefik.kaleschke.info` erreichbar, Dashboard ueber Authelia |
|
||||
| AdGuard Home | Share / Borg | `/mnt/user/appdata/adguard/conf` | keine | keine zusaetzlichen Repo-Secrets dokumentiert | `dns_net`, `frontend_net` | DNS-Aufloesung funktioniert |
|
||||
| Tailscale | Share / Borg | `/mnt/user/appdata/tailscale` | keine | Tailscale-State im Pfad | Host-Netz | Tailscale verbunden |
|
||||
| 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 | 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` |
|
||||
| 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` |
|
||||
| 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 |
|
||||
@@ -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 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `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 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 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 |
|
||||
| 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 |
|
||||
@@ -60,6 +60,9 @@ 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` |
|
||||
| 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` |
|
||||
| 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 +80,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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| ddns-updater | Rebuildbar | geringe Persistenzrelevanz | keine | Provider-Zugang ueber Stack ENV | Internetzugang | Update-Job laeuft |
|
||||
|
||||
@@ -135,7 +140,7 @@ Die Dump-Erzeugung ist host-seitig ueber `ops/borg-ui/scripts/pre-backup-dumps.s
|
||||
|
||||
## 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 |
|
||||
|---|---|---|---|---|
|
||||
@@ -147,10 +152,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) |
|
||||
| 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 |
|
||||
| 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 | - |
|
||||
| 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 |
|
||||
| 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 |
|
||||
@@ -159,157 +164,26 @@ Stand 2026-06-05. Pro Dienst auf einen Blick: Wurde der Restore schon einmal rea
|
||||
| ntfy | 2 | - | rebuildbar, kein Test noetig | - |
|
||||
| Borg UI | 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)
|
||||
|
||||
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.
|
||||
|
||||
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**.
|
||||
2. **AdGuard Home** - Config-Restore in Testpfad oder Wegwerf-Instanz pruefen
|
||||
3. **Tailscale** - State-/Reconnect-Pfad dokumentiert testen
|
||||
4. **Redis 8 (Shared)** - Restore aus Datenpfad oder Pre-Cutover-Backup in isolierter Testinstanz pruefen
|
||||
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. **Tailscale** - State-/Reconnect-Pfad dokumentiert testen (`ops/restore-tests/tailscale-runbook.md`)
|
||||
|
||||
---
|
||||
|
||||
## Restore-Test-Runbooks (Entwurf)
|
||||
## Restore-Test-Runbooks
|
||||
|
||||
Diese Abschnitte sind vorbereitete Checklisten fuer die noch untesteten Restore-Pfade.
|
||||
Sie sind **nicht** als produktive Anleitungen zu verwenden, bevor ein erster Testlauf
|
||||
die konkreten Artefaktnamen und Pfade bestaetigt hat.
|
||||
|
||||
### 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.
|
||||
Die Ablaeufe je Dienst liegen als Runbooks und automatisierte Skripte unter
|
||||
`ops/restore-tests/` (Einstieg: `ops/restore-tests/README.md`). Fuer die noch
|
||||
offenen Pfade: `ops/restore-tests/unraid-flash-runbook.md` und
|
||||
`ops/restore-tests/tailscale-runbook.md`.
|
||||
|
||||
+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.
|
||||
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
|
||||
4. Restore zuerst in einen Testpfad schreiben, nicht direkt in Produktivpfade
|
||||
|
||||
## BentoPDF / Stirling-PDF Rollback
|
||||
## Monitoring-Stack Rollback
|
||||
|
||||
Bei Problemen mit BentoPDF:
|
||||
|
||||
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:
|
||||
`monitoring/` ist der einzige Observability-Stack. Bei Problemen:
|
||||
|
||||
1. `monitoring` in Komodo stoppen oder auf den letzten funktionierenden Commit zurueckgehen
|
||||
2. nur im echten Notfall die abgeloesten Altstaende aus der Git-Historie vor dem Repo-Cleanup wiederherstellen, z. B. aus Commit `ff5991c`; nicht dauerhaft parallel zum Zielstack betreiben
|
||||
3. named volumes `prometheus_data`, `loki_data`, `promtail_positions`, `grafana_data` sowie `/mnt/user/appdata/influxdb3` nicht blind loeschen
|
||||
4. Secrets `monitoring_grafana_admin_password.txt`, `monitoring_grafana_influxdb_token.txt` und `influxdb3_admin_token.json` nur nach bewusstem Entscheid entfernen
|
||||
5. Home Assistant Writer erst wieder umstellen, wenn `curl -i http://192.168.178.58:8181/` erwartbar `401 Unauthorized` liefert
|
||||
6. Grafana-Datasources `Prometheus`, `Loki` und `InfluxDB 3 Core` testen
|
||||
|
||||
## Uptime Kuma Removal Rollback
|
||||
|
||||
Falls die Blackbox-/Grafana-Ablösung unerwartet nicht ausreicht:
|
||||
|
||||
1. per Ruecknahme-Commit `ops/uptime-kuma/docker-compose.yml`, die Blackbox-/Glance-/Authelia-Referenzen und die Restore-Freshness-Pruefung auf den letzten Uptime-Kuma-Stand zurueckbringen
|
||||
2. nach Gitea pushen und den Uptime-Kuma-Stack in Komodo neu anlegen oder aus dem letzten Stack-Backup wiederherstellen
|
||||
3. `/mnt/user/appdata/_archive/uptime-kuma-removed-2026-05-25` nach `/mnt/user/appdata/uptime-kuma` zurueckverschieben, falls die Archivierung bereits erfolgt ist
|
||||
4. `https://uptime.kaleschke.info` und die Monitore pruefen
|
||||
5. erst danach den Blackbox-/Grafana-Zielzustand erneut bewerten
|
||||
|
||||
## Glance Dashboard Rollback
|
||||
|
||||
Vor dem ersten produktiven Einsatz reicht es, den vorbereiteten Stack `ops/glance` nicht zu deployen oder per Ruecknahme-Commit aus dem Repo zu entfernen.
|
||||
|
||||
Nach einem Deploy:
|
||||
|
||||
1. `glance` in Komodo stoppen oder auf den letzten funktionierenden Commit zurueckgehen
|
||||
2. keine Produktivdaten loeschen; Glance nutzt nur Repo-Konfiguration und Stack-ENV
|
||||
3. pruefen, ob `https://glance.kaleschke.info` nicht mehr geroutet wird oder wieder den erwarteten Stand zeigt
|
||||
4. der `glance-docker-socket-proxy` darf nicht separat als Dauercontainer laufen bleiben
|
||||
2. 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. Grafana-Datasources `Prometheus`, `Loki` und `InfluxDB 3 Core` testen
|
||||
|
||||
---
|
||||
|
||||
@@ -132,19 +91,11 @@ Nach einem Deploy:
|
||||
|
||||
Bevorzugte Quellen:
|
||||
|
||||
- Borg-Restore
|
||||
- erzeugte PostgreSQL-/MariaDB-Dumps
|
||||
- bekannte Appdata-Snapshots
|
||||
- Borg-Restore (zuerst in Testpfade unter `/mnt/user/backups/restore-lab/`)
|
||||
- erzeugte Dumps unter `/mnt/user/backups/borg/dumps/latest`
|
||||
- bekannte Appdata-Archivstaende unter `/mnt/user/appdata/_archive/`
|
||||
|
||||
Beispiele:
|
||||
|
||||
```bash
|
||||
cp -r /mnt/user/appdata/<service> /mnt/user/backup/
|
||||
```
|
||||
|
||||
```bash
|
||||
pg_dumpall > /mnt/user/backup/pg_dump_$(date +%Y%m%d).sql
|
||||
```
|
||||
Dienst-spezifische Restore-Quellen, Dumps und Smoke-Tests stehen in `docs/RESTORE_MATRIX.md`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+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 |
|
||||
| Paperless-ngx | DB Password | Stack ENV `${PAPERLESS_DBPASS}` | 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 |
|
||||
| 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 |
|
||||
@@ -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 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
@@ -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 | 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 |
|
||||
| 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 -> 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`) |
|
||||
| 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 |
|
||||
@@ -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 |
|
||||
| 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 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
|
||||
|-- borg_repo_passphrase.txt
|
||||
|-- influxdb3_admin_token.json
|
||||
|-- ha_influxdb_token
|
||||
|-- ha_token_claude
|
||||
|-- ha_token_codex
|
||||
|-- filebrowser_admin_password.txt
|
||||
|-- homelab_smtp_password.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.
|
||||
- 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.
|
||||
- `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
|
||||
`micha`. Ein dedizierter SMB-User `veeam-baerchen` ist nur ein spaeteres
|
||||
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 |
|
||||
|---|---|---|---|
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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. |
|
||||
|
||||
### 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:
|
||||
`ops/restore-tests/komodo-bootstrap-compose.test.yml`,
|
||||
`ops/restore-tests/komodo-bootstrap-test.sh`,
|
||||
`ops/restore-tests/komodo-bootstrap-plan.md` und
|
||||
`ops/restore-tests/komodo-bootstrap-test.sh` und
|
||||
`ops/restore-tests/komodo-bootstrap-runbook.md`. Aufruf:
|
||||
|
||||
```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 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
|
||||
|
||||
| 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 |
|
||||
Offene Folgepunkte werden in `docs/MASTER_TODO.md` gefuehrt.
|
||||
|
||||
+14
-7
@@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
|
||||
@@ -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 |
|
||||
| `adguard` | DNS-Server / LAN DNS | `host-services/Adguard/docker-compose.yml` | LAN-Port `53`, Admin `100.80.98.33:8082` | `dns_net`, `frontend_net`, Unbound | `/mnt/user/appdata/adguard/conf`, `/mnt/user/appdata/adguard/work` | Tier 1, config relevant | nein | Direkter DNS-Port 53 bleibt; Admin-Port ist bewusst ohne Traefik/2FA, aber auf Tailscale-IP begrenzt (Operator-Entscheidung 2026-05-26) |
|
||||
| `unbound` | Upstream DNS Resolver fuer AdGuard | `apps/unbound/docker-compose.yml` | intern | `dns_net` | `/mnt/user/appdata/unbound/config` | rebuildbar / config relevant | nein | intern isoliert |
|
||||
| `tailscale` | VPN/Remote-Zugang | `host-services/tailscale/docker-compose.yml` | Tailscale | Host-Netz | `/mnt/user/appdata/tailscale` | Tier 1, State relevant | nein | `network_mode: host`, `NET_ADMIN`, `NET_RAW` und `/dev/net/tun` sind dokumentierte VPN-Ausnahmen |
|
||||
| `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, 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 |
|
||||
|
||||
## 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 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `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. |
|
||||
| `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_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-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 |
|
||||
| `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-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` |
|
||||
| `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`). |
|
||||
@@ -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-node-exporter` | Host-Metriken fuer Prometheus | `monitoring/docker-compose.yml` | intern `:9100` | Host `/proc`, `/sys`, `/` read-only, Prometheus | kein kritischer Zustand | rebuildbar | nein | Host-Observability-Ausnahme mit read-only Rootfs/Proc/Sys-Mounts |
|
||||
| `monitoring-cadvisor` | Container-Metriken fuer Prometheus | `monitoring/docker-compose.yml` | intern `:8080` | Docker/Host read-only Mounts, Prometheus | kein kritischer Zustand | rebuildbar | nein | Host-Observability-Ausnahme fuer Container-Metriken; keine direkten Ports |
|
||||
| `monitoring-influxdb3-core` | InfluxDB 3 Core fuer Home-Assistant-/Ecowitt-Langzeitdaten | `monitoring/docker-compose.yml` | 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-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. |
|
||||
|
||||
## 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
|
||||
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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.
|
||||
|
||||
@@ -384,4 +384,4 @@ Wenn Hermes-Worker auf weiteren Hosts skaliert: dieser Storage-Layout-Plan gilt
|
||||
|
||||
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
|
||||
2. `webhook_enabled` in Komodo aktivieren
|
||||
3. passenden Gitea-Webhook fuer die aktuelle Stack-ID anlegen
|
||||
4. Gitea-Hook gegen `http://komodo-core:9120/listener/github/stack/<stack-id>/deploy` pruefen
|
||||
5. einen Push oder Test-Delivery ausloesen und `last_status`/Komodo-Deploy pruefen
|
||||
6. Ausnahmen explizit dokumentieren
|
||||
4. Branch-Filter im Gitea-Hook auf den produktiven Branch setzen, aktuell `master`
|
||||
5. Gitea-Hook gegen `http://komodo-core:9120/listener/github/stack/<stack-id>/deploy` pruefen
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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/ROLLBACK.md`
|
||||
|
||||
+8
-1
@@ -1,8 +1,15 @@
|
||||
# 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.
|
||||
|
||||
## Live-Stand 2026-05-04
|
||||
## Historischer Live-Stand 2026-05-04
|
||||
|
||||
- Home Assistant ist per SSH unter `192.168.178.50:22222` erreichbar.
|
||||
- `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.
|
||||
- BitLocker-Status mit Adminrechten pruefen. **Nachlauf 2026-06-05:** Status
|
||||
wurde geprueft; C:/D:/E:/G:/H: sind `FullyDecrypted`, Protection `Off`.
|
||||
Offen bleibt nur die bewusste BitLocker-Entscheidung.
|
||||
- Passwortmanager, 2FA-Recovery-Codes und Browser-Sync manuell pruefen.
|
||||
- Banking4-Speicherort explizit pruefen.
|
||||
- Banking4 im Programm selbst oeffnen und aktuellen Datentresor/Backup-Export bestaetigen. Der Key und der Datentresor sind bereits lokal auf H: gesichert.
|
||||
- WISO Steuer 2026 oeffnen und Lizenz/Buhl-Konto sowie Speicherorte der Steuerdateien bestaetigen.
|
||||
- Microsoft-Konto fuer M365 pruefen: Office-Webkonto/Abonnement, Installationsrecht, OneDrive-Sync.
|
||||
**Entscheidung 2026-06-06:** BitLocker bleibt bewusst deaktiviert; Recovery
|
||||
laeuft ueber Veeam-Image, kein BitLocker-Key-Management.
|
||||
- Passwortmanager, 2FA-Recovery-Codes und Browser-Sync manuell pruefen. **Erledigt 2026-06-06 laut Operator-Bestaetigung.**
|
||||
- Banking4-Speicherort explizit 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. **Erledigt 2026-06-06 laut Operator-Bestaetigung.**
|
||||
- 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.
|
||||
- `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.
|
||||
@@ -464,7 +470,7 @@ Direkt nach der Installation:
|
||||
- Windows-Aktivierung prüfen
|
||||
- Laufwerksbuchstaben sauber vergeben
|
||||
- Windows Defender und Firewall prüfen
|
||||
- BitLocker bewusst aktivieren oder deaktiviert lassen
|
||||
- BitLocker bewusst deaktiviert lassen (Entscheidung 2026-06-06)
|
||||
- Wiederherstellungspunkt erstellen
|
||||
|
||||
Basisprogramme:
|
||||
@@ -533,8 +539,8 @@ Zielstruktur:
|
||||
Status 2026-06-05: Diese Checkliste ist historisch fuer die Freigabe der
|
||||
Neuinstallation. Die technische Neuinstallation, Laufwerksbereinigung,
|
||||
WinRE-Pruefung und Veeam-Baseline sind in neueren Dokumenten nachgezogen.
|
||||
Als offene manuelle Pruefungen bleiben vor allem Passwortmanager/2FA,
|
||||
Banking4, WISO und Microsoft/M365.
|
||||
Status 2026-06-06: Passwortmanager/2FA, Banking4, WISO und Microsoft/M365
|
||||
wurden durch den Operator bestaetigt ("alle Dienste laufen").
|
||||
|
||||
- [ ] Backup-Struktur auf H: erstellt
|
||||
- [ ] Programmliste exportiert
|
||||
@@ -542,10 +548,10 @@ Banking4, WISO und Microsoft/M365.
|
||||
- [ ] Windows-Aktivierung dokumentiert
|
||||
- [ ] Benutzerordner gesichert
|
||||
- [ ] Browser-Lesezeichen exportiert oder Sync geprüft
|
||||
- [ ] Passwortmanager geprüft
|
||||
- [ ] 2FA-Recovery-Codes gesichert
|
||||
- [x] Passwortmanager geprüft
|
||||
- [x] 2FA-Recovery-Codes gesichert
|
||||
- [ ] SSH/API/GPG/Zertifikate gesichert
|
||||
- [ ] Banking4-Speicherort geprüft und gesichert
|
||||
- [x] Banking4-Speicherort geprüft und gesichert
|
||||
- [ ] Homelab-/NAS-Doku gesichert
|
||||
- [ ] D:, F: und G: analysiert
|
||||
- [ ] Unklare Daten nach `99_Unsortiert_von_D_F_G` kopiert
|
||||
@@ -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,6 +1,6 @@
|
||||
services:
|
||||
adguard:
|
||||
image: adguard/adguardhome:v0.107.76@sha256:7157eb1dc3b26c7af1d6898759a7b3f7d0fa09891fbd2d3caa6abc1057a9179b
|
||||
image: adguard/adguardhome:v0.107.77@sha256:e6f2b8bcda06064ab055b44933a4f0e983c35558b9cdb8d2e7ab1efcee36d890
|
||||
container_name: adguard
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
|
||||
@@ -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
|
||||
@@ -25,7 +25,7 @@ services:
|
||||
- cadvisor
|
||||
|
||||
alertmanager:
|
||||
image: prom/alertmanager:v0.32.1@sha256:51a825c2a40acc3e338fdd00d622e01ec090f72be2b3ea46be0839cd47a4d286
|
||||
image: prom/alertmanager:v0.32.2@sha256:b85533a2eb45865835315810315f6951331b2dbc8c93a6cf9a51e156a006a706
|
||||
container_name: monitoring-alertmanager
|
||||
restart: unless-stopped
|
||||
command:
|
||||
@@ -66,15 +66,18 @@ services:
|
||||
image: prom/blackbox-exporter:v0.28.0@sha256:e753ff9f3fc458d02cca5eddab5a77e1c175eee484a8925ac7d524f04366c2fc
|
||||
container_name: monitoring-blackbox-exporter
|
||||
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:
|
||||
- 1.1.1.1
|
||||
- 8.8.8.8
|
||||
- 172.23.0.3
|
||||
command:
|
||||
- --config.file=/etc/blackbox_exporter/blackbox.yml
|
||||
volumes:
|
||||
- ./blackbox/blackbox.yml:/etc/blackbox_exporter/blackbox.yml:ro
|
||||
networks:
|
||||
- monitoring_net
|
||||
- dns_net
|
||||
expose:
|
||||
- "9115"
|
||||
security_opt:
|
||||
@@ -115,7 +118,7 @@ services:
|
||||
- loki
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:13.0.1@sha256:0f86bada30d65ef9d0183b90c1e2682ac92d53d95da8bed322b984ea78a4a73a
|
||||
image: grafana/grafana:13.0.2@sha256:5dad0df181cb644a14e13617b913b261a54f7d4fd4510721dba420929f35bea2
|
||||
container_name: monitoring-grafana
|
||||
user: "0"
|
||||
restart: unless-stopped
|
||||
@@ -129,6 +132,20 @@ services:
|
||||
GF_USERS_ALLOW_SIGN_UP: "false"
|
||||
GF_AUTH_ANONYMOUS_ENABLED: "false"
|
||||
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:
|
||||
- /bin/sh
|
||||
- -c
|
||||
@@ -145,6 +162,7 @@ services:
|
||||
secrets:
|
||||
- monitoring_grafana_admin_password
|
||||
- monitoring_grafana_influxdb_token
|
||||
- grafana_oidc_client_secret
|
||||
expose:
|
||||
- "3000"
|
||||
security_opt:
|
||||
@@ -160,7 +178,8 @@ services:
|
||||
- traefik.http.routers.monitoring-grafana.entrypoints=websecure
|
||||
- traefik.http.routers.monitoring-grafana.tls=true
|
||||
- traefik.http.routers.monitoring-grafana.tls.certresolver=le
|
||||
- traefik.http.routers.monitoring-grafana.middlewares=authelia@file,secure-headers@file
|
||||
# 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
|
||||
|
||||
grafana-dashboard-importer:
|
||||
@@ -318,7 +337,7 @@ services:
|
||||
- no-new-privileges:true
|
||||
|
||||
influxdb3-core:
|
||||
image: influxdb:3.9.2-core@sha256:31ad94df2248134989b2cf73d965e51dd5f35dfae22d7ed8f4776b12e6f69f4e
|
||||
image: influxdb:3.9.3-core@sha256:c27c9b2ca2625b5b6966f0b09baa448102310e63a471fd60dff22646a2522e29
|
||||
container_name: monitoring-influxdb3-core
|
||||
user: "0"
|
||||
restart: unless-stopped
|
||||
@@ -332,6 +351,12 @@ services:
|
||||
- --data-dir=/var/lib/influxdb3/data
|
||||
- --plugin-dir=/var/lib/influxdb3/plugins
|
||||
- --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:
|
||||
- /mnt/user/appdata/influxdb3/data:/var/lib/influxdb3/data
|
||||
- /mnt/user/appdata/influxdb3/plugins:/var/lib/influxdb3/plugins
|
||||
@@ -351,6 +376,8 @@ networks:
|
||||
driver: bridge
|
||||
frontend_net:
|
||||
external: true
|
||||
dns_net:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
prometheus_data:
|
||||
@@ -364,5 +391,7 @@ secrets:
|
||||
file: /mnt/user/appdata/secrets/monitoring_grafana_admin_password.txt
|
||||
monitoring_grafana_influxdb_token:
|
||||
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:
|
||||
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
|
||||
secureJsonData:
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
|
||||
@@ -38,7 +38,7 @@ The Unraid flash configuration archive is intentional as well and must be treate
|
||||
| Traefik | file data | `/local/appdata/traefik` |
|
||||
| ntfy | file data | `/local/appdata/ntfy` |
|
||||
| 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` |
|
||||
| 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` |
|
||||
@@ -48,6 +48,10 @@ 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` |
|
||||
| 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` |
|
||||
| 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 | `/local/appdata/hermes-agent/data`, `/local/secrets/hermes_runner_id_ed25519` |
|
||||
| BentoPDF | rebuildable | no critical persistence in compose |
|
||||
|
||||
@@ -87,6 +91,7 @@ The live Unraid User Scripts execute repo scripts from `/mnt/user/services/homel
|
||||
- SQLite: `gitea`, `vaultwarden`, `speedtest-tracker`, `borg-ui`, `grafana`
|
||||
- File-backed state: `filebrowser.bolt.dump`
|
||||
- Unraid flash config: `unraid-flash-config.tar.gz` plus `unraid-flash-config.tar.gz.sha256`
|
||||
- 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
|
||||
|
||||
|
||||
@@ -14,11 +14,14 @@
|
||||
/local/appdata/traefik
|
||||
/local/appdata/ntfy
|
||||
/local/appdata/paperless-gpt
|
||||
/local/appdata/tailscale
|
||||
/local/appdata/adguard/conf
|
||||
/local/appdata/borg-ui/data
|
||||
/local/appdata/komodo/periphery
|
||||
/local/appdata/komodo/core
|
||||
/local/services/homelab-infra
|
||||
/local/services/smart-home-kalli
|
||||
/local/services/stacks
|
||||
/local/services/posture-check
|
||||
/local/appdata/homeassistant
|
||||
/local/appdata/mosquitto/config
|
||||
/local/appdata/mosquitto/data
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
borg-ui:
|
||||
image: ainullcode/borg-ui@sha256:b44c0a92b650d80f215a986dadda5c2604c61eb28a7571e19c046eff41d761e7
|
||||
image: ainullcode/borg-ui@sha256:0922157e8f77a1b2bd23cd09366a458ea6de07fd9306aa1485f9cfe623eca17f
|
||||
container_name: borg-ui
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
code-server:
|
||||
image: lscr.io/linuxserver/code-server:4.122.0@sha256:0caf1b65ebec84b94397108b56da6c33f124c5390f5832da94e75f4609c0e2ad
|
||||
image: lscr.io/linuxserver/code-server:4.123.0@sha256:cb261a7f87674b445e0fd66d87d55900c1b823d276c727ab0d168a75e69e9992
|
||||
container_name: code-server
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
filebrowser:
|
||||
image: filebrowser/filebrowser:v2.63.5@sha256:aefb0c20de10ef8b617995ca5522479ad40d41e6386bd01946a345c6026ff31c
|
||||
image: filebrowser/filebrowser:v2.63.14@sha256:1ec9b0c68297550c92f4a93feed432850c2993b261706cc3cc2e808f94a95e76
|
||||
container_name: filebrowser
|
||||
restart: unless-stopped
|
||||
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:
|
||||
proxied: true
|
||||
assets-path: /app/assets
|
||||
|
||||
branding:
|
||||
app-name: KalliLab Dashboard
|
||||
@@ -7,873 +8,45 @@ branding:
|
||||
hide-footer: true
|
||||
|
||||
theme:
|
||||
background-color: 210 20 13
|
||||
primary-color: 212 100 50
|
||||
positive-color: 140 70 40
|
||||
negative-color: 4 78 57
|
||||
contrast-multiplier: 1.25
|
||||
text-saturation-multiplier: 0.9
|
||||
background-color: 222 14 8
|
||||
primary-color: 205 100 58
|
||||
positive-color: 150 80 45
|
||||
negative-color: 355 90 60
|
||||
contrast-multiplier: 1.3
|
||||
text-saturation-multiplier: 0.5
|
||||
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:
|
||||
- name: Home
|
||||
slug: home
|
||||
width: wide
|
||||
head-widgets:
|
||||
- type: search
|
||||
search-engine: duckduckgo
|
||||
new-tab: true
|
||||
autofocus: true
|
||||
placeholder: Suche im Web oder springe per Bang...
|
||||
bangs:
|
||||
- title: Gitea
|
||||
shortcut: "!git"
|
||||
url: https://git.kaleschke.info/explore/repos?q={QUERY}
|
||||
- title: Paperless
|
||||
shortcut: "!doc"
|
||||
url: https://paperless.kaleschke.info/documents?query={QUERY}
|
||||
- title: Nextcloud
|
||||
shortcut: "!cloud"
|
||||
url: https://cloud.kaleschke.info/apps/files/?dir=/{QUERY}
|
||||
- title: Komodo
|
||||
shortcut: "!komodo"
|
||||
url: https://komodo.kaleschke.info
|
||||
columns:
|
||||
- size: small
|
||||
widgets:
|
||||
- type: group
|
||||
widgets:
|
||||
- type: custom-api
|
||||
title: Day
|
||||
body-type: string
|
||||
skip-json-validation: true
|
||||
cache: 1s
|
||||
template: |
|
||||
{{ $localTime := now }}
|
||||
{{ $elapsedSeconds := add (mul $localTime.Hour 3600) (mul $localTime.Minute 60) | add $localTime.Second }}
|
||||
{{ $dayProgress := div (mul $elapsedSeconds 100.0) 86400.0 }}
|
||||
{{ $gradient := "#70a1ff" }}
|
||||
{{ if gt $dayProgress 25.0 }}{{ $gradient = "#ff6b6b, #70a1ff" }}{{ end }}
|
||||
{{ if gt $dayProgress 50.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df" }}{{ end }}
|
||||
{{ if gt $dayProgress 75.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df, #70a1ff" }}{{ end }}
|
||||
<div style="text-align: center;">
|
||||
<div style="width: 100%; height: 12px; background: #23262f; border: 1px solid color-mix(in srgb, var(--color-text-subdue) 55%, transparent); border-radius: 10px; overflow: hidden;">
|
||||
<div style="height: 100%; width: {{ $dayProgress }}%; background: linear-gradient(90deg, {{ $gradient }});"></div>
|
||||
</div>
|
||||
<div class="size-h1" style="margin-top: 6px;">{{ printf "%.2f" $dayProgress }}% des Tages sind vorbei</div>
|
||||
</div>
|
||||
|
||||
- type: custom-api
|
||||
title: Month
|
||||
body-type: string
|
||||
skip-json-validation: true
|
||||
cache: 1s
|
||||
template: |
|
||||
{{ $localTime := now }}
|
||||
{{ $month := $localTime.Month }}
|
||||
{{ $daysInMonth := 31 }}
|
||||
{{ if eq $month 2 }}{{ $daysInMonth = 28 }}{{ end }}
|
||||
{{ if or (eq $month 4) (eq $month 6) (eq $month 9) (eq $month 11) }}{{ $daysInMonth = 30 }}{{ end }}
|
||||
{{ $secondsToday := add (mul $localTime.Hour 3600) (mul $localTime.Minute 60) | add $localTime.Second }}
|
||||
{{ $daysElapsed := add (sub $localTime.Day 1) (div $secondsToday 86400.0) }}
|
||||
{{ $monthProgress := mul (div $daysElapsed $daysInMonth) 100.0 }}
|
||||
{{ $gradient := "#70a1ff" }}
|
||||
{{ if gt $monthProgress 25.0 }}{{ $gradient = "#ff6b6b, #70a1ff" }}{{ end }}
|
||||
{{ if gt $monthProgress 50.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df" }}{{ end }}
|
||||
{{ if gt $monthProgress 75.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df, #70a1ff" }}{{ end }}
|
||||
<div style="text-align: center;">
|
||||
<div style="width: 100%; height: 12px; background: #23262f; border: 1px solid color-mix(in srgb, var(--color-text-subdue) 55%, transparent); border-radius: 10px; overflow: hidden;">
|
||||
<div style="height: 100%; width: {{ $monthProgress }}%; background: linear-gradient(90deg, {{ $gradient }});"></div>
|
||||
</div>
|
||||
<div class="size-h1" style="margin-top: 6px;">{{ printf "%.2f" $monthProgress }}% des Monats sind vorbei</div>
|
||||
</div>
|
||||
|
||||
- type: custom-api
|
||||
title: Year
|
||||
body-type: string
|
||||
skip-json-validation: true
|
||||
cache: 1s
|
||||
template: |
|
||||
{{ $localTime := now }}
|
||||
{{ $secondsToday := add (mul $localTime.Hour 3600) (mul $localTime.Minute 60) | add $localTime.Second }}
|
||||
{{ $secondsElapsed := add (mul (sub $localTime.YearDay 1) 86400) $secondsToday }}
|
||||
{{ $yearProgress := div (mul $secondsElapsed 100.0) (mul 365 86400) }}
|
||||
{{ $gradient := "#70a1ff" }}
|
||||
{{ if gt $yearProgress 25.0 }}{{ $gradient = "#ff6b6b, #70a1ff" }}{{ end }}
|
||||
{{ if gt $yearProgress 50.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df" }}{{ end }}
|
||||
{{ if gt $yearProgress 75.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df, #70a1ff" }}{{ end }}
|
||||
<div style="text-align: center;">
|
||||
<div style="width: 100%; height: 12px; background: #23262f; border: 1px solid color-mix(in srgb, var(--color-text-subdue) 55%, transparent); border-radius: 10px; overflow: hidden;">
|
||||
<div style="height: 100%; width: {{ $yearProgress }}%; background: linear-gradient(90deg, {{ $gradient }});"></div>
|
||||
</div>
|
||||
<div class="size-h1" style="margin-top: 6px;">{{ printf "%.2f" $yearProgress }}% des Jahres sind vorbei</div>
|
||||
</div>
|
||||
|
||||
- type: clock
|
||||
hour-format: 24h
|
||||
show-progress: true
|
||||
timezones:
|
||||
- timezone: Europe/Berlin
|
||||
label: Berlin
|
||||
- timezone: UTC
|
||||
label: UTC
|
||||
|
||||
- type: calendar
|
||||
first-day-of-week: monday
|
||||
|
||||
- type: bookmarks
|
||||
title: Direkte Einstiege
|
||||
groups:
|
||||
- title: Core
|
||||
color: 212 100 50
|
||||
links:
|
||||
- title: Komodo
|
||||
url: https://komodo.kaleschke.info
|
||||
icon: sh:komodo
|
||||
- title: Gitea
|
||||
url: https://git.kaleschke.info
|
||||
icon: si:gitea
|
||||
- title: Monitoring
|
||||
url: https://monitoring.kaleschke.info
|
||||
icon: si:grafana
|
||||
- title: Ops
|
||||
color: 45 70 55
|
||||
links:
|
||||
- title: Borg
|
||||
url: https://borg.kaleschke.info
|
||||
icon: mdi:archive
|
||||
- title: Glances
|
||||
url: https://glances.kaleschke.info
|
||||
icon: sh:glances
|
||||
- title: Scrutiny
|
||||
url: https://scrutiny.kaleschke.info
|
||||
icon: sh:scrutiny
|
||||
|
||||
- size: full
|
||||
widgets:
|
||||
- type: server-stats
|
||||
title: Server Stats
|
||||
servers:
|
||||
- type: local
|
||||
name: Kallilabcore
|
||||
hide-mountpoints-by-default: false
|
||||
|
||||
- type: group
|
||||
widgets:
|
||||
- type: custom-api
|
||||
title: Immich
|
||||
title-url: https://immich.kaleschke.info
|
||||
cache: 10m
|
||||
url: http://immich_server:2283/api/server/statistics
|
||||
headers:
|
||||
x-api-key: ${GLANCE_IMMICH_API_KEY}
|
||||
subrequests:
|
||||
storage:
|
||||
url: http://immich_server:2283/api/server/storage
|
||||
headers:
|
||||
x-api-key: ${GLANCE_IMMICH_API_KEY}
|
||||
template: |
|
||||
{{ $photos := .JSON.Int "photos" }}
|
||||
{{ $videos := .JSON.Int "videos" }}
|
||||
{{ $usageGiB := div (toFloat (.JSON.Int "usage")) 1073741824.0 }}
|
||||
{{ $storage := .Subrequest "storage" }}
|
||||
{{ $storageOK := and (ge $storage.Response.StatusCode 200) (le $storage.Response.StatusCode 299) }}
|
||||
{{ $percentage := 0.0 }}
|
||||
{{ if $storageOK }}{{ $percentage = $storage.JSON.Float "diskUsagePercentage" }}{{ end }}
|
||||
<div class="flex justify-between text-center">
|
||||
<div>
|
||||
<div class="color-highlight size-h3">{{ $photos | formatNumber }}</div>
|
||||
<div class="size-h6 uppercase">Fotos</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="color-highlight size-h3">{{ $videos | formatNumber }}</div>
|
||||
<div class="size-h6 uppercase">Videos</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="color-highlight size-h3">{{ printf "%.0f" $usageGiB }} GiB</div>
|
||||
<div class="size-h6 uppercase">Medien</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="height: 8px; margin-top: 14px; border-radius: 999px; overflow: hidden; background: color-mix(in srgb, var(--color-text-subdue) 22%, transparent);">
|
||||
<div style="height: 100%; width: {{ if $storageOK }}{{ printf "%.1f" $percentage }}%{{ else }}0%{{ end }}; border-radius: 999px; background: var(--color-primary);"></div>
|
||||
</div>
|
||||
<div class="size-h6 color-subdue" style="margin-top: 8px;">{{ if $storageOK }}{{ printf "%.1f" $percentage }}% Speicher belegt{{ else }}Speicher API nicht verfuegbar{{ end }}</div>
|
||||
|
||||
- type: monitor
|
||||
title: Homelab Status
|
||||
cache: 1m
|
||||
sites:
|
||||
- title: AdGuard Home
|
||||
url: http://192.168.178.58:8082
|
||||
check-url: http://adguard
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/adguard-home.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: Authelia
|
||||
url: https://auth.kaleschke.info
|
||||
check-url: http://authelia:9091/api/health
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authelia.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: Gitea
|
||||
url: https://git.kaleschke.info
|
||||
check-url: http://gitea:3000/api/healthz
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: Traefik
|
||||
url: https://traefik.kaleschke.info
|
||||
check-url: http://traefik:8082/metrics
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: Vaultwarden
|
||||
url: https://vault.kaleschke.info
|
||||
check-url: http://vaultwarden/alive
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vaultwarden.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: Komodo
|
||||
url: https://komodo.kaleschke.info
|
||||
check-url: http://komodo-core:9120
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/komodo.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: Paperless-ngx
|
||||
url: https://paperless.kaleschke.info
|
||||
check-url: http://paperless-ngx:8000
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/paperless-ngx.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: Paperless-GPT
|
||||
url: https://paperless-gpt.kaleschke.info
|
||||
check-url: http://paperless-gpt:8080
|
||||
icon: mdi:robot
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: Immich
|
||||
url: https://immich.kaleschke.info
|
||||
check-url: http://immich_server:2283/api/server/ping
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: Mealie
|
||||
url: https://mealie.kaleschke.info
|
||||
check-url: http://mealie:9000
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mealie.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: Nextcloud
|
||||
url: https://cloud.kaleschke.info
|
||||
check-url: http://nextcloud/status.php
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: ntfy
|
||||
url: https://ntfy.kaleschke.info
|
||||
check-url: http://ntfy/v1/health
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/ntfy.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: Mail Archiver
|
||||
url: https://mail.kaleschke.info
|
||||
check-url: http://mail-archiver:5000
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mailcow.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: BentoPDF
|
||||
url: https://pdf.kaleschke.info
|
||||
check-url: http://bentopdf:8080
|
||||
icon: mdi:file-pdf-box
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: Glance
|
||||
url: https://glance.kaleschke.info
|
||||
check-url: http://glance:8080
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glance.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: Monitoring Grafana
|
||||
url: https://monitoring.kaleschke.info
|
||||
check-url: http://monitoring-grafana:3000/api/health
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/grafana.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: Glances
|
||||
url: https://glances.kaleschke.info
|
||||
check-url: http://glances:61208
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glances.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: Scrutiny
|
||||
url: https://scrutiny.kaleschke.info
|
||||
check-url: http://scrutiny:8080
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/scrutiny.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: Speedtest Tracker
|
||||
url: https://speedtest.kaleschke.info
|
||||
check-url: http://speedtest-tracker
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/speedtest-tracker.png
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: Filebrowser
|
||||
url: https://files.kaleschke.info
|
||||
check-url: http://filebrowser
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/filebrowser.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: code-server
|
||||
url: https://code.kaleschke.info
|
||||
check-url: http://code-server:8443
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vscode.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: Borg UI
|
||||
url: https://borg.kaleschke.info
|
||||
check-url: http://borg-ui:8081
|
||||
icon: mdi:archive-sync
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
|
||||
- size: small
|
||||
widgets:
|
||||
- type: custom-api
|
||||
title: Internet
|
||||
title-url: https://speedtest.kaleschke.info
|
||||
cache: 1h
|
||||
url: http://speedtest-tracker/api/v1/results/latest
|
||||
headers:
|
||||
Authorization: Bearer ${GLANCE_SPEEDTEST_API_KEY}
|
||||
Accept: application/json
|
||||
template: |
|
||||
{{ $ip := .JSON.String "external_ip" }}
|
||||
{{ if eq $ip "" }}{{ $ip = .JSON.String "data.interface.externalIp" }}{{ end }}
|
||||
{{ $isp := .JSON.String "isp" }}
|
||||
{{ if eq $isp "" }}{{ $isp = .JSON.String "data.isp" }}{{ end }}
|
||||
{{ $server := .JSON.String "server_name" }}
|
||||
{{ if eq $server "" }}{{ $server = .JSON.String "data.server_name" }}{{ end }}
|
||||
<div style="display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center;">
|
||||
<div class="color-primary size-h2" style="font-weight: 700;">{{ if ne $ip "" }}{{ $ip }}{{ else }}WAN online{{ end }}</div>
|
||||
<div class="size-h5 color-highlight">Speedtest Tracker</div>
|
||||
<div class="size-h6 color-subdue" style="font-style: italic;">{{ if ne $isp "" }}{{ $isp }}{{ else }}{{ $server }}{{ end }}</div>
|
||||
</div>
|
||||
|
||||
- type: custom-api
|
||||
title: Internet Speed
|
||||
title-url: https://speedtest.kaleschke.info
|
||||
cache: 1h
|
||||
url: http://speedtest-tracker/api/v1/results/latest
|
||||
headers:
|
||||
Authorization: Bearer ${GLANCE_SPEEDTEST_API_KEY}
|
||||
Accept: application/json
|
||||
subrequests:
|
||||
stats:
|
||||
url: http://speedtest-tracker/api/v1/stats
|
||||
headers:
|
||||
Authorization: Bearer ${GLANCE_SPEEDTEST_API_KEY}
|
||||
Accept: application/json
|
||||
template: |
|
||||
{{ $stats := .Subrequest "stats" }}
|
||||
{{ $download := .JSON.Float "download" }}
|
||||
{{ if eq $download 0.0 }}{{ $download = .JSON.Float "data.download" }}{{ end }}
|
||||
{{ if eq $download 0.0 }}{{ $download = div (.JSON.Float "download_bits") 1000000.0 }}{{ end }}
|
||||
{{ if eq $download 0.0 }}{{ $download = div (.JSON.Float "data.download_bits") 1000000.0 }}{{ end }}
|
||||
{{ $upload := .JSON.Float "upload" }}
|
||||
{{ if eq $upload 0.0 }}{{ $upload = .JSON.Float "data.upload" }}{{ end }}
|
||||
{{ if eq $upload 0.0 }}{{ $upload = div (.JSON.Float "upload_bits") 1000000.0 }}{{ end }}
|
||||
{{ if eq $upload 0.0 }}{{ $upload = div (.JSON.Float "data.upload_bits") 1000000.0 }}{{ end }}
|
||||
{{ $ping := .JSON.Float "ping" }}
|
||||
{{ if eq $ping 0.0 }}{{ $ping = .JSON.Float "data.ping" }}{{ end }}
|
||||
{{ $downloadAvg := $stats.JSON.Float "avg_download" }}
|
||||
{{ if eq $downloadAvg 0.0 }}{{ $downloadAvg = $stats.JSON.Float "data.download.avg" }}{{ end }}
|
||||
{{ if eq $downloadAvg 0.0 }}{{ $downloadAvg = div ($stats.JSON.Float "data.download.avg_bits") 1000000.0 }}{{ end }}
|
||||
{{ $uploadAvg := $stats.JSON.Float "avg_upload" }}
|
||||
{{ if eq $uploadAvg 0.0 }}{{ $uploadAvg = $stats.JSON.Float "data.upload.avg" }}{{ end }}
|
||||
{{ if eq $uploadAvg 0.0 }}{{ $uploadAvg = div ($stats.JSON.Float "data.upload.avg_bits") 1000000.0 }}{{ end }}
|
||||
{{ $pingAvg := $stats.JSON.Float "avg_ping" }}
|
||||
{{ if eq $pingAvg 0.0 }}{{ $pingAvg = $stats.JSON.Float "data.ping.avg" }}{{ end }}
|
||||
{{ $downloadChange := percentChange $downloadAvg $download }}
|
||||
{{ $uploadChange := percentChange $uploadAvg $upload }}
|
||||
{{ $pingChange := percentChange $pingAvg $ping }}
|
||||
<div class="flex justify-between text-center margin-block-3">
|
||||
<div>
|
||||
<div class="size-small {{ if lt $downloadChange 0.0 }}color-negative{{ else }}color-positive{{ end }}">{{ printf "%+.1f%%" $downloadChange }}</div>
|
||||
<div class="color-highlight size-h3">{{ printf "%.1f" $download }}</div>
|
||||
<div class="size-h6 color-subdue">DOWNLOAD</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="size-small {{ if lt $uploadChange 0.0 }}color-negative{{ else }}color-positive{{ end }}">{{ printf "%+.1f%%" $uploadChange }}</div>
|
||||
<div class="color-highlight size-h3">{{ printf "%.1f" $upload }}</div>
|
||||
<div class="size-h6 color-subdue">UPLOAD</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="size-small {{ if gt $pingChange 0.0 }}color-negative{{ else }}color-positive{{ end }}">{{ printf "%+.1f%%" $pingChange }}</div>
|
||||
<div class="color-highlight size-h3">{{ printf "%.0f ms" $ping }}</div>
|
||||
<div class="size-h6 color-subdue">PING</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
- type: dns-stats
|
||||
title: DNS Stats
|
||||
service: adguard
|
||||
url: http://adguard
|
||||
username: ${GLANCE_ADGUARD_USERNAME}
|
||||
password: ${GLANCE_ADGUARD_PASSWORD}
|
||||
|
||||
- type: monitor
|
||||
title: DNS und VPN
|
||||
cache: 1m
|
||||
sites:
|
||||
- title: AdGuard Home
|
||||
url: http://192.168.178.58:8082
|
||||
check-url: http://adguard
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/adguard-home.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
- title: Traefik
|
||||
url: https://traefik.kaleschke.info
|
||||
check-url: http://traefik:8082/metrics
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
|
||||
timeout: 5s
|
||||
alt-status-codes: [200, 302, 401, 403]
|
||||
|
||||
- type: docker-containers
|
||||
title: Network Container
|
||||
category: network
|
||||
hide-by-default: true
|
||||
sock-path: tcp://glance-docker-socket-proxy:2375
|
||||
containers: &containers
|
||||
traefik:
|
||||
name: Traefik
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
|
||||
url: https://traefik.kaleschke.info
|
||||
description: Reverse Proxy
|
||||
category: core
|
||||
hide: false
|
||||
gitea:
|
||||
name: Gitea
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
|
||||
url: https://git.kaleschke.info
|
||||
description: GitOps Origin
|
||||
category: core
|
||||
hide: false
|
||||
authelia:
|
||||
name: Authelia
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authelia.svg
|
||||
url: https://auth.kaleschke.info
|
||||
description: ForwardAuth
|
||||
category: core
|
||||
hide: false
|
||||
vaultwarden:
|
||||
name: Vaultwarden
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vaultwarden.svg
|
||||
url: https://vault.kaleschke.info
|
||||
description: Password Vault
|
||||
category: core
|
||||
hide: false
|
||||
postgresql17:
|
||||
name: PostgreSQL 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
|
||||
$include: pages.yml
|
||||
|
||||
@@ -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_PASSWORD: ${GLANCE_ADGUARD_PASSWORD:-}
|
||||
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:
|
||||
- ./config:/app/config:ro
|
||||
- ./assets:/app/assets:ro
|
||||
networks:
|
||||
- frontend_net
|
||||
- glance_socket_net
|
||||
# monitoring_net nur lesend fuer Prometheus-Query des Borg-Backup-Widgets
|
||||
- monitoring_net
|
||||
depends_on:
|
||||
- glance-docker-socket-proxy
|
||||
labels:
|
||||
@@ -50,6 +59,8 @@ services:
|
||||
networks:
|
||||
frontend_net:
|
||||
external: true
|
||||
monitoring_net:
|
||||
external: true
|
||||
glance_socket_net:
|
||||
name: glance_socket_net
|
||||
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
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM nousresearch/hermes-agent:v2026.5.29
|
||||
FROM nousresearch/hermes-agent:v2026.6.5
|
||||
|
||||
USER root
|
||||
|
||||
|
||||
@@ -45,13 +45,13 @@
|
||||
"description": "VPN / Remote-Zugang",
|
||||
"tier": 1,
|
||||
"category": "core",
|
||||
"container_name": "tailscale",
|
||||
"container_name": null,
|
||||
"dependencies": [],
|
||||
"url": null,
|
||||
"dump_file": null,
|
||||
"data_paths": ["/mnt/user/appdata/tailscale"],
|
||||
"first_check": "Tailscale Status auf Host pruefen; State-Datei fuer Key-Renewal vorhanden?",
|
||||
"notes": "network_mode: host; NET_ADMIN, NET_RAW, /dev/net/tun — dokumentierte VPN-Ausnahmen"
|
||||
"data_paths": ["/boot/config/plugins/tailscale/state"],
|
||||
"first_check": "Tailscale Status auf Host pruefen; native Unraid-Plugin-Instanz und Subnet-Route aktiv?",
|
||||
"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": {
|
||||
"description": "Git-Server — operative Quelle der Wahrheit fuer GitOps",
|
||||
|
||||
@@ -75,14 +75,14 @@ services:
|
||||
description: VPN / Remote-Zugang
|
||||
tier: 1
|
||||
category: core
|
||||
container_name: tailscale
|
||||
container_name: null
|
||||
dependencies: []
|
||||
url: null
|
||||
dump_file: null
|
||||
data_paths:
|
||||
- /mnt/user/appdata/tailscale
|
||||
first_check: "Tailscale Status auf Host pruefen; State-Datei fuer Key-Renewal vorhanden?"
|
||||
notes: "network_mode: host; NET_ADMIN, NET_RAW, /dev/net/tun — dokumentierte VPN-Ausnahmen"
|
||||
- /boot/config/plugins/tailscale/state
|
||||
first_check: "Tailscale Status auf Host pruefen; native Unraid-Plugin-Instanz und Subnet-Route aktiv?"
|
||||
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:
|
||||
description: Git-Server — operative Quelle der Wahrheit fuer GitOps
|
||||
|
||||
@@ -2,6 +2,11 @@ services:
|
||||
# ──────────────────────────────────────────────────────────────────
|
||||
# MongoDB – Datenbank fuer Komodo Core
|
||||
# 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:
|
||||
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
|
||||
- produktive Pfade nicht beschreiben
|
||||
- Testlaeufe spaeter weitgehend automatisieren
|
||||
- `docs/RESTORE_MATRIX.md` - Restore-Quellen, Secrets, Smoke-Tests und **Test-Reifegrad je Dienst** (einziger Status-Ort)
|
||||
- `docs/DISASTER_RECOVERY.md` - echter Wiederanlauf
|
||||
- `schedule.md` - Kadenz, Cron-Ausdruecke und Shell-Guards
|
||||
- `unraid-user-scripts.md` - Unraid-User-Script-Vorlagen fuer die Host-Jobs
|
||||
|
||||
## Grundregeln
|
||||
|
||||
- Restore-Quelle bleibt im Backup-Bereich, z. B. `/mnt/user/backups/borg`
|
||||
- Test-Restores laufen nur in `/mnt/user/backups/restore-lab`
|
||||
- Reports landen in `/mnt/user/backups/restore-reports`
|
||||
- Test-Container nutzen das Praefix `restoretest-`
|
||||
- keine produktiven Volumes schreibend mounten
|
||||
- keine produktiven Domains fuer Testinstanzen uebernehmen
|
||||
- Restore-Quelle bleibt das produktive Borg-Repo bei Hetzner; Zugriff ueber den vorhandenen `borg-ui`-Container
|
||||
- Passphrase kommt aus `/mnt/user/appdata/secrets/borg_repo_passphrase.txt`, nie aus UI-Interna
|
||||
- Testdaten landen nur unter `/mnt/user/backups/restore-lab/<dienst>`; bei Fehlschlag wird nach `_failed/` verschoben statt geloescht
|
||||
- Reports landen unter `/mnt/user/backups/restore-reports`
|
||||
- Testcontainer nutzen das Praefix `restoretest-`, localhost-Ports, keine produktive Domain, keine Traefik-Route
|
||||
- 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
|
||||
- `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`
|
||||
- `vaultwarden-restore-test.ps1`: erster Mini-Restore-Ablauf
|
||||
- `vaultwarden-restore-test.sh`: hosttauglicher Vaultwarden-Restore-Job
|
||||
- `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
|
||||
Ein Restore-Test gilt nur dann als erfolgreich, wenn Quelle lesbar war, Daten
|
||||
im Restore-Lab ankamen, der Testcontainer startete, der **fachliche**
|
||||
Smoke-Test gelang und ein Report geschrieben wurde. "Container laeuft" allein
|
||||
reicht nicht.
|
||||
|
||||
- `check-restore-freshness.ps1`: woechentlicher Frische-Check fuer Dumps und Reports
|
||||
- `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
|
||||
## Aufbau des Verzeichnisses
|
||||
|
||||
## Automatisierungsmodell
|
||||
Pro Dienst existieren bis zu drei Artefakte:
|
||||
|
||||
- Ausfuehrung: Unraid User Script / Host-Job
|
||||
- Logik: Repo-Skripte in diesem Verzeichnis
|
||||
- Ergebnis: Markdown-Report
|
||||
- Meldung: `ntfy`
|
||||
- Hermes: optional nur fuer Zusammenfassung und Auswertung
|
||||
- `<dienst>-restore-test.sh` - automatisierter Host-Job (produktive Wahrheit)
|
||||
- `<dienst>-compose.test.yml` - isolierte Testinstanz
|
||||
- `<dienst>-runbook.md` - manueller Ablauf bzw. Besonderheiten
|
||||
|
||||
Wichtig:
|
||||
Dazu zentrale Helfer:
|
||||
|
||||
- die Bash-Skripte `*.sh` sind die produktive Host-Variante
|
||||
- `check-restore-freshness.ps1` und die `*.ps1`-Dateien bleiben als lokale Plan-/Hilfsvariante nutzbar
|
||||
- im Windows-Clone fehlen die `/mnt/user/...`-Pfade naturgemaess
|
||||
- `run-restore-checks.sh` - Dispatcher (Host), `run-restore-checks.ps1` (lokale Planvariante)
|
||||
- `run-restore-job-with-ntfy.sh` - Wrapper: Erfolg -> `homelab-info`, Fehler -> `homelab-alerts`
|
||||
- `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
|
||||
- Borg-Zugriff laeuft praktisch ueber den vorhandenen `borg-ui`-Container
|
||||
- SSH-Trust wird ueber `known_hosts` im `borg-ui`-Container hergestellt
|
||||
- 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
|
||||
- Host-Jobs laufen als Unraid User Scripts vom Repo-Spiegel `/mnt/user/services/homelab-infra`
|
||||
- Kadenz und Cron-Ausdruecke: `schedule.md` (woechentlicher Frische-Check, monatliche/quartalsweise Dienst-Rotation, monatlicher Zufalls-Restore)
|
||||
- Job-Vorlagen: `unraid-user-scripts.md`
|
||||
|
||||
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
|
||||
- echter Gitea-Restore am 2026-05-07 erfolgreich verifiziert
|
||||
- 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)
|
||||
# Mit ntfy-Meldung
|
||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-job-with-ntfy.sh freshness homelab-info
|
||||
```
|
||||
|
||||
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
|
||||
@@ -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-restore-test.sh`
|
||||
- `ops/restore-tests/immich-restore-test.ps1` (Scaffold, kein Live-Run)
|
||||
- `ops/restore-tests/immich-plan.md`
|
||||
- `ops/restore-tests/immich-runbook.md`
|
||||
|
||||
## 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.
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
LAB_ROOT="${LAB_ROOT:-/mnt/user/backups/restore-lab/freshness-negative}"
|
||||
REPORT_ROOT="${REPORT_ROOT:-/mnt/user/backups/restore-reports}"
|
||||
ALERT_TOPIC="${ALERT_TOPIC:-homelab-alerts}"
|
||||
INFO_TOPIC="${INFO_TOPIC:-homelab-info}"
|
||||
SEND_NTFY="${SEND_NTFY:-1}"
|
||||
|
||||
stamp="$(date +%F-%H%M%S)"
|
||||
test_root="$LAB_ROOT/$stamp"
|
||||
dump_root="$test_root/dumps"
|
||||
test_report_root="$test_root/reports"
|
||||
report_file="$REPORT_ROOT/freshness-negative-$stamp.md"
|
||||
raw_log="$test_root/check-output.md"
|
||||
|
||||
mkdir -p "$dump_root" "$test_report_root" "$REPORT_ROOT"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$test_root"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
set +e
|
||||
DUMP_ROOT="$dump_root" \
|
||||
REPORT_ROOT="$test_report_root" \
|
||||
MAX_DUMP_AGE_HOURS=26 \
|
||||
MAX_REPORT_AGE_DAYS=45 \
|
||||
"$SCRIPT_DIR/check-restore-freshness.sh" >"$raw_log" 2>&1
|
||||
rc=$?
|
||||
set -e
|
||||
|
||||
critical_count="$(awk -F': ' '/^Critical:/ {print $2; exit}' "$raw_log" | tr -d '[:space:]')"
|
||||
critical_count="${critical_count:-0}"
|
||||
|
||||
{
|
||||
echo "# Restore Freshness Negative Alert Test"
|
||||
echo
|
||||
echo "Timestamp: $(date '+%F %T')"
|
||||
echo "Result: $([ "$rc" -ne 0 ] && [ "$critical_count" -gt 0 ] && echo "ok" || echo "failed")"
|
||||
echo "Check exit code: $rc"
|
||||
echo "Critical count: $critical_count"
|
||||
echo "Synthetic dump root: $dump_root"
|
||||
echo "Synthetic report root: $test_report_root"
|
||||
echo "Production dump root touched: no"
|
||||
echo
|
||||
echo "## Check Output"
|
||||
echo
|
||||
cat "$raw_log"
|
||||
} >"$report_file"
|
||||
|
||||
if [ "$rc" -ne 0 ] && [ "$critical_count" -gt 0 ]; then
|
||||
if [ "$SEND_NTFY" = "1" ]; then
|
||||
bash "$SCRIPT_DIR/send-ntfy.sh" \
|
||||
"$ALERT_TOPIC" \
|
||||
"TEST: Restore freshness alert path ok" \
|
||||
"Negativtest erfolgreich: check-restore-freshness.sh meldete ${critical_count} Criticals gegen synthetischen leeren Testpfad. Produktive Dumps wurden nicht veraendert. Report: $report_file" \
|
||||
high
|
||||
fi
|
||||
echo "Negative freshness alert test ok. Report: $report_file"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$SEND_NTFY" = "1" ]; then
|
||||
bash "$SCRIPT_DIR/send-ntfy.sh" \
|
||||
"$ALERT_TOPIC" \
|
||||
"TEST FAILED: Restore freshness alert path" \
|
||||
"Negativtest fehlgeschlagen: erwarteter Critical-Zustand wurde nicht erkannt. Report: $report_file" \
|
||||
high || true
|
||||
fi
|
||||
|
||||
echo "Negative freshness alert test failed. Report: $report_file" >&2
|
||||
exit 1
|
||||
@@ -1,72 +0,0 @@
|
||||
# Paperless Restore Test Plan
|
||||
|
||||
## Ziel
|
||||
|
||||
Nachweisen, dass ein Paperless-Backup in einer isolierten Testumgebung wieder startbar ist und sowohl Dokumentenpfade als auch PostgreSQL-Dump sauber zusammenlaufen.
|
||||
|
||||
## Quelle
|
||||
|
||||
- Backup-Quelle: Borg / Share-Backup
|
||||
- fachlich relevante Dateipfade:
|
||||
- `/mnt/user/appdata/paperless-ngx/data`
|
||||
- `/mnt/user/documents/paperless`
|
||||
- `/mnt/user/documents/paperless/export`
|
||||
- `/mnt/user/documents/scans_inbox`
|
||||
- fachlich relevanter Dump:
|
||||
- `/mnt/user/backups/borg/dumps/latest/postgresql17-paperless.dump`
|
||||
|
||||
## Test-Ziel
|
||||
|
||||
- Restore-Lab: `/mnt/user/backups/restore-lab/paperless`
|
||||
- Testdatenpfade:
|
||||
- `/mnt/user/backups/restore-lab/paperless/data`
|
||||
- `/mnt/user/backups/restore-lab/paperless/media`
|
||||
- `/mnt/user/backups/restore-lab/paperless/export`
|
||||
- `/mnt/user/backups/restore-lab/paperless/consume`
|
||||
- `/mnt/user/backups/restore-lab/paperless/postgres`
|
||||
- Testcontainer:
|
||||
- `restoretest-paperless`
|
||||
- `restoretest-paperless-postgres`
|
||||
- `restoretest-paperless-redis`
|
||||
- Testport Web: `127.0.0.1:18120:8000`
|
||||
- Report-Ziel: `/mnt/user/backups/restore-reports/paperless-YYYY-MM-DD.md`
|
||||
|
||||
## Schutzregeln
|
||||
|
||||
- produktive Pfade nie beschreiben
|
||||
- produktive Domain `paperless.kaleschke.info` nicht fuer die Testinstanz uebernehmen
|
||||
- keine Traefik-Labels fuer die Testinstanz
|
||||
- keine produktive PostgreSQL- oder Redis-Instanz fuer den Test verwenden
|
||||
- Testcontainer nur gegen Restore-Lab-Daten und isolierte Test-Backends starten
|
||||
|
||||
## Geplanter Ablauf
|
||||
|
||||
1. Restore-Ziel unter `/mnt/user/backups/restore-lab/paperless` vorbereiten
|
||||
2. Paperless-Dateipfade aus Borg in das Restore-Lab wiederherstellen
|
||||
3. Test-Postgres und Test-Redis mit `ops/restore-tests/paperless-compose.test.yml` starten
|
||||
4. `postgresql17-paperless.dump` in Test-Postgres importieren
|
||||
5. Testinstanz `restoretest-paperless` starten
|
||||
6. lokalen Smoke-Test gegen `http://127.0.0.1:18120` ausfuehren
|
||||
7. Report unter `/mnt/user/backups/restore-reports/` schreiben
|
||||
8. Testcontainer stoppen und Testumgebung bereinigen
|
||||
|
||||
## Smoke-Test
|
||||
|
||||
Minimal erfolgreich:
|
||||
|
||||
- Test-Postgres startet
|
||||
- Dump-Import gelingt
|
||||
- Paperless-Web-UI antwortet
|
||||
- mindestens ein Dokument liegt im Restore-Lab-Medienpfad
|
||||
|
||||
Optional spaeter:
|
||||
|
||||
- Login-Seite gezielt pruefen
|
||||
- Dokumentanzahl aus UI oder DB querpruefen
|
||||
- OCR-/Task-Worker-Status verifizieren
|
||||
|
||||
## Noch offen vor dem ersten echten Lauf
|
||||
|
||||
- exakter Borg-Restore-Befehl fuer alle vier Dateipfade
|
||||
- exakter `pg_restore`-Befehl im Test-Postgres
|
||||
- wie stark wir `consume` im ersten Lauf ueberhaupt brauchen
|
||||
@@ -0,0 +1,16 @@
|
||||
services:
|
||||
restoretest-redis:
|
||||
image: redis:8.8.0-alpine@sha256:09160599abd229764c0fb44cb6be640294e1d360a54b19985ab4843dcf2d90f1
|
||||
container_name: restoretest-redis
|
||||
restart: "no"
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- exec redis-server --appendonly yes --requirepass "$$(cat /run/secrets/redis_password)"
|
||||
ports:
|
||||
- "127.0.0.1:16379:6379/tcp"
|
||||
volumes:
|
||||
- /mnt/user/backups/restore-lab/redis/data:/data
|
||||
- /mnt/user/backups/restore-lab/redis/secrets/redis_password.txt:/run/secrets/redis_password:ro
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user