1 Commits

Author SHA1 Message Date
renovate 8c8c1e50b2 chore(deps): update minor-and-patch-updates 2026-06-05 22:20:19 +00:00
116 changed files with 2836 additions and 5371 deletions
-3
View File
@@ -22,9 +22,6 @@
**/*.tgz
**/*.zip
# Generated reports
ops/policy-checks/last-report.md
# Local/editor noise
.DS_Store
Thumbs.db
-20
View File
@@ -1,20 +0,0 @@
# 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`.
+3 -4
View File
@@ -1,6 +1,6 @@
# Claude Code Context - Homelab Infra
Stand: 2026-06-11
Stand: 2026-05-04
Dieses Repository ist die GitOps-Quelle fuer das KalliLab CORE Homelab auf einem Unraid-Host. Es verwaltet Docker-Compose-Stacks fuer Core-Dienste, Security, Infrastruktur, Apps, Operations-Tools, Host-nahe Dienste und Traefik. Gitea Online ist die operative Quelle der Wahrheit; Komodo konsumiert den Git-Stand und deployed daraus.
@@ -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`
- Architektur-/Betriebsentscheidungen mit Begruendung: `docs/DECISIONS.md`
- Home Assistant / Ecowitt / InfluxDB: `docs/HOME_ASSISTANT_INFLUXDB_ECOWITT.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`: 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.
- `tailscale`: `network_mode: host`
- `Plex-Media-Server`: historischer Host-Netz-Sonderfall, nicht als Repo-Stack enthalten
- `scrutiny`: `privileged: true` fuer SMART/Laufwerkszugriff
- `Komodo`: Docker-Socket und native Auth ohne pauschale ForwardAuth
@@ -123,7 +123,6 @@ 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.
+190 -23
View File
@@ -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-11 | **Aktueller Schwerpunkt:** GitOps / Doku-Synchronisierung / Reproduzierbare Deployments
**Stand:** 2026-06-02 | **Aktueller Schwerpunkt:** GitOps / Doku-Synchronisierung / Reproduzierbare Deployments
---
@@ -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 (ausgelagert)](#13-betriebserfahrungen-und-entscheidungs-log-ausgelagert)
13. [Betriebserfahrungen und Entscheidungs-Log](#13-betriebserfahrungen-und-entscheidungs-log)
---
@@ -93,7 +93,6 @@ Jeder produktive Container nutzt `restart: unless-stopped`, außer eine Ausnahme
| `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)
@@ -124,8 +123,7 @@ 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)
└── smarthome_net (HA, Mosquitto, spaeter Zigbee2MQTT/ESPHome)
── monitoring_influx_lan (Bridge fuer LAN-Port-Publishing, keine Traefik-Route)
Host-Sonderfälle
├── tailscale
@@ -147,8 +145,6 @@ 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**:
@@ -244,7 +240,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 / 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 |
| `tailscale` | ✅ | `host` | VPN-Zugang | Git-Stack (`host-services/tailscale/`) | nutzt `NET_ADMIN`, `NET_RAW` und `/dev/net/tun` als dokumentierte VPN-Ausnahme |
### 7.2 Sicherheit / Identity
@@ -264,7 +260,6 @@ 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` | ✅ vorbereitet | `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` | LAN-Port erst in ESPHome-Phase mit ACLs/per-Device-Usern |
### 7.4 Produktive Apps
@@ -278,8 +273,7 @@ Legende Status:
| `immich_server` | ✅ | `immich_default`, `frontend_net` | Traefik | aktiv via `immich.kaleschke.info` | — |
| `immich_machine_learning` | ✅ | `immich_default` | intern | bleibt intern | — |
| `nextcloud` | ✅ | `frontend_net`, `nextcloud_internal` | Traefik | aktiv via `cloud.kaleschke.info`, nativer Nextcloud-Login, WebDAV/CardDAV faehig | CalDAV/CardDAV-Redirect via Traefik-Labels |
| `homeassistant` | ✅ vorbereitet | `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` | Deploy, Onboarding, Restore-Probe, Cloud-Integrationen |
| `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 | — |
| `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 | — |
| `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 |
@@ -314,7 +308,7 @@ Legende Status:
| Container | Status | Ziel |
|---|---|---|
| — | — | 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. |
| — | — | Plex ist nicht mehr offen: der Dienst ist als Repo-Compose-Stack unter `host-services/plex/` dokumentiert; `host`-Netz bleibt als Discovery-Ausnahme. |
### 7.8 Entfernte Container
@@ -376,7 +370,23 @@ labels:
## 9. Historische Migration (abgeschlossen)
Die Blockmigration aus der Portainer-/Dockerman-Phase ist abgeschlossen: Traefik laeuft labelbasiert ohne File-Provider-Service-Routen, Komodo ist alleiniger Stack-Manager, Portainer CE ist entfernt, Borg/Dumps/Restore-Tests sind produktiv. Entscheidungen und Hintergruende stehen in `docs/DECISIONS.md`; die Sprint-Historie liegt in Git.
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
## 10. Bekannte Ausnahmen und Begründungen
| Container | Ausnahme | Begründung |
@@ -394,12 +404,9 @@ Die Blockmigration aus der Portainer-/Dockerman-Phase ist abgeschlossen: Traefik
| `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 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-influxdb3-core` | Host-Port 8181 auf LAN-IP; `user: "0"` | Home Assistant laeuft in einer VM ausserhalb des Compose-Netzes und muss Metriken schreiben koennen; keine Traefik-Route, kein `frontend_net`, Zugriff nur ueber Token und LAN-IP `INFLUXDB_BIND_IP`; InfluxDB 3 Core benoetigt im aktuellen Container-Setup Root-Rechte fuer den lokalen Object-Store-Pfad im named volume |
| `monitoring-promtail` | Docker-Socket read-only | Docker-Log-Discovery fuer Loki; keine Schreibrechte, keine Appdaten-Persistenz ueber den Socket |
| `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. |
| `Ecowitt` | spaetere HTTP-Ausnahme offen | Ecowitt kann nur HTTP. Wegen globalem Traefik-HTTP-Redirect wird in Phase 2 entschieden, ob Traefik eine selektive Webhook-Ausnahme bekommt oder ob ein LAN-only HA-Port `8123` als dokumentierte Host-Port-Ausnahme noetig wird. |
---
@@ -455,15 +462,175 @@ Damit ist sofort klar:
---
## 13. Betriebserfahrungen und Entscheidungs-Log (ausgelagert)
## 13. Betriebserfahrungen und Entscheidungs-Log
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).
### 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`.
---
## Schlussformel
Dieses Dokument ist keine lose Notiz, sondern das **operative Masterdokument** für die Docker- und Zugriffsarchitektur des Homelabs.
-1
View File
@@ -66,7 +66,6 @@ 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.
+2 -13
View File
@@ -1,7 +1,7 @@
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:v2.7.5@sha256:c15bff75068effb03f4355997d03dc7e0fc58720c2b54ad6f7f10d1bc57efaa5
image: ghcr.io/immich-app/immich-server:release@sha256:c15bff75068effb03f4355997d03dc7e0fc58720c2b54ad6f7f10d1bc57efaa5
restart: unless-stopped
depends_on:
- redis
@@ -32,19 +32,8 @@ services:
immich-machine-learning:
container_name: immich_machine_learning
image: ghcr.io/immich-app/immich-machine-learning:v2.7.5@sha256:a2501141440f10516d329fdfba2c68082e19eb9ba6016c061ac80d23beadf7f3
image: ghcr.io/immich-app/immich-machine-learning:release@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:
+1 -1
View File
@@ -1,6 +1,6 @@
services:
mail-archiver:
image: s1t5/mailarchiver@sha256:4ea7ecc47ad1dd2c523b85c3967574b61e39def1b6fd26edf874e21733c4018c
image: s1t5/mailarchiver@sha256:ea7fd8c2e3e0ef0941e8dd9e726e35a8de33296f5c7b9ed811df5168ae6a9714
container_name: mail-archiver
restart: unless-stopped
environment:
+1 -17
View File
@@ -4,12 +4,6 @@ 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"
@@ -24,16 +18,6 @@ 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
@@ -54,7 +38,7 @@ services:
- traefik.http.services.mealie.loadbalancer.server.port=9000
mealie-postgres:
image: postgres:18.4@sha256:29ee7bb30d804447dc9a91fd0d74322ae1dc3a4072cc6346f70a5ed6e783b565
image: postgres:18.4@sha256:8ff36f3c66371cba71d20ceedccfc3de9669a68737607888c4ef0af93abe8e39
container_name: mealie-postgres
restart: unless-stopped
+1 -1
View File
@@ -1,6 +1,6 @@
services:
n8n:
image: docker.n8n.io/n8nio/n8n:2.26.2@sha256:61ba01bc5e39304bbc928c9dbecd938c3a5cc1331b68affba6a34d0f654c43d9
image: docker.n8n.io/n8nio/n8n:2.25.5@sha256:08862289f9e9b387d91eab66a74d40d307c0c9b74d2504866f8fe61e9063c838
container_name: n8n
restart: unless-stopped
+2 -2
View File
@@ -1,6 +1,6 @@
services:
nextcloud:
image: nextcloud:33.0.5-apache@sha256:56bdc45109067500fd0832fa64832b7c77a167d9394cbf5f0f4b59740b94194d
image: nextcloud:33.0.5-apache@sha256:4c8bf9140e07ad0293e32500de47a97647677cd4cace358db3ecdc225dff9856
container_name: nextcloud
restart: unless-stopped
depends_on:
@@ -46,7 +46,7 @@ services:
- "traefik.http.services.nextcloud.loadbalancer.server.port=80"
nextcloud-postgres:
image: postgres:18.4@sha256:29ee7bb30d804447dc9a91fd0d74322ae1dc3a4072cc6346f70a5ed6e783b565
image: postgres:18.4@sha256:8ff36f3c66371cba71d20ceedccfc3de9669a68737607888c4ef0af93abe8e39
container_name: nextcloud-postgres
restart: unless-stopped
environment:
-8
View File
@@ -3,9 +3,6 @@ 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:
@@ -20,11 +17,6 @@ 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 -1
View File
@@ -1,6 +1,6 @@
services:
super-productivity:
image: johannesjo/super-productivity:v18.9.1@sha256:773760107344e739f4c29409f7842db66a1b167d50eb2c40248cb5b5b328652e
image: johannesjo/super-productivity:v18.8.0@sha256:c739caca8e0c5e83ea4a6289884079ac49e0c3c87c7f95598b5a9fb10cc2d9c4
container_name: super-productivity
restart: unless-stopped
+1 -1
View File
@@ -1,6 +1,6 @@
services:
unbound:
image: shaanmajid/unbound:1.25.1@sha256:f140db02a005904802bf5840093e95e675321aa060a00426fdffc2a3ac2eeb6b
image: shaanmajid/unbound:1.25.1@sha256:96809ff052e8bd79bba30e067d8b27ed9a2f069b6b2a3484fe1d0eb45aba07c5
container_name: unbound
restart: unless-stopped
volumes:
+40 -13
View File
@@ -1,10 +1,8 @@
# AI Context
Typ: Einstieg/Index · Stand: 2026-06-11 · Status: aktiv
Stand: 2026-06-05
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
@@ -22,7 +20,6 @@ Diese Datei enthaelt bewusst **keinen** Arbeitsstand mehr — Status nur in
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
@@ -33,21 +30,51 @@ Diese Datei enthaelt bewusst **keinen** Arbeitsstand mehr — Status nur in
- 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: natives Unraid-Plugin (nicht repo-verwaltet); Plex: Host-Netz
- Scrutiny: privileged; Komodo/Periphery: Docker-Socket
- Tailscale und Plex: Host-Netz
- Scrutiny: privileged
- Komodo/Periphery: Docker-Socket-Zugriff
- InfluxDB 3 Core: `127.0.0.1:8181`, Root-User-Ausnahme dokumentiert
## Arbeitsstand
## Aktuelle Restpunkte
- Offene Punkte: `docs/MASTER_TODO.md` (einzige Statusliste)
- Entscheidungen und Begruendungen: `docs/DECISIONS.md`
- Belege/Reports: `/mnt/user/backups/restore-reports/` auf dem Host
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.
+68
View File
@@ -0,0 +1,68 @@
# 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.
-186
View File
@@ -1,186 +0,0 @@
# Authelia OIDC fuer Apps - Plan & Runbook
Stand: 2026-06-06. 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. Der weitere 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 | Paperless-ngx | `paperless.kaleschke.info` | `django-allauth` (Umgebungsvariablen) | `two_factor` | mittel | dokumentenlastig, Operator-nah |
**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 `${...}`. 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.
+1 -1
View File
@@ -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 `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.
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.
| Abgrenzung | Bewertung | Begruendung |
|---|---|---|
-169
View File
@@ -1,169 +0,0 @@
# 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-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.
**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 69 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).
+9 -15
View File
@@ -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
- `ops/restore-tests/README.md` - Restore-Test-Betrieb und Werkzeuge
- `docs/RESTORE_HANDBOOK.md` - praktische Restore-Betriebsanleitung
- `docs/SERVICES_RECOVERY.md` - Recovery-kritische `/mnt/user/services`-Pfade, Gitea-Mirror und Komodo-Bootstrap
- `docs/EXTERNAL_DEPENDENCIES.md` - externe Provider/Konten und Ausfall-Szenarien
- `ops/borg-ui/BACKUP_SCOPE.md` - Zielbild des Borg-Scopes
@@ -290,14 +290,7 @@ Erfolgskriterium: `docker network ls` zeigt `frontend_net`, `backend_net`, `moni
1. `traefik/`
2. `host-services/Adguard/`
> **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`).
3. `host-services/tailscale/`
**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.
@@ -565,14 +558,15 @@ und physisch ausserhalb des Rechners abgelegt sein.
---
## 11. Laufende Vorbereitung
## 11. Offene Vorbereitungs-To-dos
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
- 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
- `komodo-mongo`-Dump nach Major-Upgrades gezielt kontrollieren
- Restore-Drills nach Kadenz aus `ops/restore-tests/schedule.md` rotieren
- `baerchen` Recovery-USB-Boot-/SMB-Test nach erfolgreichem erstem Full-Lauf
verifizieren
---
+3 -10
View File
@@ -115,15 +115,6 @@ 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
@@ -167,7 +158,9 @@ 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 |
```
Falls der Punkt noch als offen in `docs/MASTER_TODO.md` steht, dort in den Kurzlog uebernehmen.
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`.
---
+11 -3
View File
@@ -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 | 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 |
| Plex Konto/Remote Access | Plex native Auth, ggf. Remote Access und Claim | mittel | Plex-Clients/Remote-Funktionen koennen ausfallen | Plex-Konto ausserhalb Repo; `PLEX_CLAIM` nur fuer Setup | LAN-Medienpfade bleiben lokal; Konto-Recovery separat sichern |
| Mobile Push | ntfy und ggf. mobile Plattform-Pushes | niedrig/mittel | Alerts erreichen Mobilgeraete ggf. nicht | App-/Device-seitig | Kritische Alerts zusaetzlich in Grafana/Glance sichtbar halten |
| 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,6 +96,14 @@ Operative Regel: Die DR-Workstation wird nicht als Test-/Spiel-PC betrachtet. WS
| Datum | Ergebnis | Naechste Aktion |
|---|---|---|
| 2026-05-26 bis 2026-06-03 | Baseline und Haertung abgeschlossen: externe Abhaengigkeiten dokumentiert; FRITZ!Box-WAN auf 443/tcp bereinigt, Remote-Dienste aus, Konfig-Backup in Vaultwarden; Hetzner-Account-Hygiene (2FA, Recovery Key offline); KOMODO_*-Notiz und GitHub-Read-Deploy-Key offline gesichert. Detailhistorie in Git. | Keine Folgeaktion |
| 2026-05-26 | 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-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 |
+1 -1
View File
@@ -91,7 +91,7 @@ Nach Aenderung:
1. Einen regulaeren Borg-Lauf abwarten oder manuell starten.
2. `check-external-operator.sh` ausfuehren.
3. Nur das Ergebnis dokumentieren: Datum/Befund im Review-Log von `docs/EXTERNAL_DEPENDENCIES.md`.
3. In `docs/AUDIT_2026-05-25_TODO.md` nur das Ergebnis dokumentieren.
## 4. FRITZ!Box-Servicefenster
+1 -1
View File
@@ -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 | Web `https://plex.kaleschke.info` oder Plex-App auf dem Geraet, mit Konto anmelden |
| **Plex** | Filme und Musik auf Fernseher, Handy und Tablet | 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.
-117
View File
@@ -1,117 +0,0 @@
# 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.
+5 -5
View File
@@ -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 | **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 |
| 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) |
## Zweck
@@ -157,10 +157,10 @@ Bewertung:
## Stromverbrauch
**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.
**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.
| Zustand | Verbrauch | Messmethode | Datum |
|---|---:|---|---|
@@ -1,15 +1,8 @@
# 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.
## Historischer Live-Stand 2026-05-04
## Live-Stand 2026-05-04
- Home Assistant ist per SSH unter `192.168.178.50:22222` erreichbar.
- `ha core check` ist erfolgreich.
+131
View File
@@ -0,0 +1,131 @@
# 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.
+68 -45
View File
@@ -1,87 +1,110 @@
# Master To-do - KalliLab CORE
Typ: Status/To-do · Stand: 2026-06-12 · Status: aktiv
Stand: 2026-06-05 (Wochenend-Sprint, nach Status-Kategorien sortiert)
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.
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.
## Status-Kategorien
- **Aktiv** - soll vorankommen; konkreter naechster Schritt steht.
- **Operator-Entscheidung** - wartet auf eine bewusste Entscheidung (ja/nein/Option).
- **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).
- **Geparkt** - bewusst nicht jetzt, mit klarem Review-Trigger.
- **Extern blockiert** - wartet auf ein externes Ereignis oder eine Abhaengigkeit.
- **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**.
---
## Aktiv
## Aktiv dieses Wochenende
| Thema | Owner | Naechster konkreter Schritt | Quelle |
|---|---|---|---|
| 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/Claude | Live: Grafana + Mealie (verifiziert), Paperless deployed (Login-Test offen). Immich + Nextcloud bewusst geparkt bis Family-Onboarding (siehe `docs/DECISIONS.md` 2026-06-06) | `docs/AUTHELIA_OIDC_PLAN.md` |
| Glance-v2-Widgets: Tokens setzen | Operator | In Komodo Stack-ENV fuer `ops-glance` setzen: `GLANCE_KOMODO_API_KEY`/`_SECRET` (Komodo read-only API-Key), `GLANCE_GITEA_TOKEN` (read-only, scope `read:repository`), `GLANCE_PAPERLESS_TOKEN`, `GLANCE_MEALIE_TOKEN`; bis dahin zeigen die neuen Widgets Fehler/leer. Speedtest-Widget: falls weiter 0.0, API-Response pruefen | `ops/glance/config/` |
| Home Assistant Foundation-Abnahme | Operator/Codex | Sofort: Owner-Onboarding abschliessen. Danach temporaere Authelia-Middleware von der HA-Route entfernen, Komodo-Stack-Eintrag + Webhook sauber anlegen/verifizieren, HA-MQTT-Smoke-Test und HA-native `backup.create` testen, Restore-Probe fuer HA/Mosquitto dokumentieren | `docs/runbooks/smart-home-bootstrap.md`, `docs/RESTORE_MATRIX.md` |
| Audit-PDF aus `docs/` entfernen | Operator | `docs/KalliLab_CORE_Audit_2026-06-06.pdf` (untracked) extern ablegen (H:/ oder Documents-Share) und lokal loeschen; Binaerdateien gehoeren nicht ins GitOps-Repo | Doku-Regeln `docs/REPO_MAP.md` |
| 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` |
---
## Operator-Entscheidung
**Stand 2026-06-11: keine offenen Operator-Entscheidungen.**
Getroffene Entscheidungen mit Begruendung und Review-Trigger: `docs/DECISIONS.md`.
| 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` |
---
## Geparkt
Bewusst nicht jetzt - Begruendungen in `docs/DECISIONS.md`, hier nur Thema und Trigger.
Bewusst nicht jetzt - mit Review-Trigger.
| Thema | Review-Trigger | Quelle |
| Thema | Entscheidung / Trigger | Quelle |
|---|---|---|
| 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` |
| Tailnet-Konsole aufraeumen (Rest) | trivial, bei Gelegenheit: tote Node-Eintraege (`kallilab-core`, alter `baerchen`) in der Tailscale-Admin-Konsole entfernen; optional State-Pfad `/mnt/user/appdata/tailscale` nach `_archive/` | `docs/NETWORK_INVENTORY.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 |
| 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` |
---
## Extern blockiert
Wartet auf ein externes Ereignis oder eine Abhaengigkeit.
| Thema | Blockiert durch | Naechster Schritt sobald entblockt | Quelle |
|---|---|---|---|
| End-to-end-DR-Drill | Keine zweite Wegwerf-Hardware verfuegbar | Komplett-Bootstrap Phase 1-5 fahren | `docs/DISASTER_RECOVERY.md` |
| `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` |
---
## Zuletzt erledigt (Kurzlog, max. 5 Eintraege)
## Erledigt im Wochenend-Sprint (2026-06-05)
- **2026-06-12** Komodo-Stack-Hygiene-Check aktiv: `services/posture-check/komodo-stack-hygiene.sh` + Unraid User Script `komodo-stack-hygiene-weekly` (Sonntag 05:00). Faengt die `immich_new`-Klasse (Stack ohne Repo, `project_missing`, Compose ohne Stack, Hash-Drift). Erster Lauf: 6 Warnings, 0 Critical.
- **2026-06-12** Immich Komodo-Stack bereinigt: `immich_new` auf `immich` korrigiert, Gitea-Account `Micha` gesetzt, per Komodo aus `apps/immich/docker-compose.yml` neu deployed. Verifiziert: `deployed_hash == latest_hash`, `immich_new_count=0`, alle vier Container healthy, HTTP 200, DB-Smoke `11983` Assets, Drift-Alert resolved.
- **2026-06-11** Host-DNS-Fallback aktiv: `eth0` DNS2 = `192.168.178.1` (FRITZ!Box) zusaetzlich zu AdGuard. AdGuard-SPOF fuer Image-Pulls entschaerft; der dokumentierte Bulk-Deploy-Vorfall kann strukturell nicht wiederkommen.
- **2026-06-11** Hetzner Storage Box: automatische Snapshots aktiv (taeglich 05:30 UTC, 7 Tage Retention). Schliesst das Ransomware-/Fehlbedienungs-Risiko gegen das Off-site-Backup. Siehe `docs/DECISIONS.md`.
- **2026-06-11** Immich Image-Tags von `release` auf `v2.7.5` gepinnt (Server + ML, Digests unveraendert): Renovate-PRs zeigen ab jetzt sichtbare Versionsspruenge statt stiller Digest-Bumps.
- 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.
---
## Pflege-Regel
- 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.
- 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.
- Keine vagen "pruefen"-Eintraege ohne Kommando oder Entscheidung.
- Historische Drill-Reports bleiben Belegmaterial, aber nicht die fuehrende Arbeitsliste.
+58 -215
View File
@@ -55,7 +55,7 @@ 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 | **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. |
| ACL-Policy extern dokumentiert | **Operator-Entscheidung offen** — siehe eigener Block unten; durch den aktiven Subnet-Router ist die ACL-Frage sicherheitsrelevanter als zuvor angenommen. |
### Tailnet-Geraete (Snapshot 2026-06-05)
@@ -65,37 +65,13 @@ Gemessen am 2026-06-05 per read-only SSH auf den Host (`tailscale status`,
| `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 | **am 2026-06-06 entfernt.** War der redundante userspace-only `Tailscale-Docker`-Stack (`host-services/tailscale/`). Komodo-Stack gestoppt+destroyed, Repo-Pfad per `git rm` entfernt, Container weg (read-only verifiziert). Node-Eintrag in der Admin-Konsole noch zu entfernen. |
| `100.112.0.90` | kallilab-core | linux | gelistet, kein aktiver Verkehr — **moeglicher Alt-/Dubletten-Node**, separat pruefen |
> **Befund 2026-06-06 (read-only auf dem Host ermittelt):** Der Host hat **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. Offen: Node-Eintraege
> `kallilab-core` und alter `baerchen` in der Admin-Konsole entfernen; State-Pfad
> `/mnt/user/appdata/tailscale` bei Gelegenheit nach `_archive/` (kein Sofort-Loeschen).
>
> **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.
> 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.
### Subnet-Router-Konsequenz
@@ -115,22 +91,34 @@ tailscale ip -4
tailscale ip -6
```
### ACL-Policy — ANGEWENDET 2026-06-06 (restriktive Tag-basierte grants)
### ACL-Policy — Entwurf und Rollout-Plan (Stand 2026-06-05, NICHT angewendet)
**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.
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.
> **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.
**Abgestimmte Richtung (Operator-Entscheidungen 2026-06-05):**
**Angewendete Policy (live, kein Secret):**
- 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):**
```json
{
@@ -142,64 +130,42 @@ erhalten.
"autoApprovers": {
"routes": { "192.168.178.0/24": ["tag:server"] }
},
"grants": [
{"src": ["tag:operator"], "dst": ["*"], "ip": ["*"]},
{"src": ["tag:server"], "dst": ["tag:operator"], "ip": ["*"]},
{"src": ["tag:family"], "dst": ["tag:server"], "ip": ["tcp:443"]}
"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"] }
],
"ssh": [
{"action": "check", "src": ["autogroup:member"], "dst": ["autogroup:self"],
"users": ["autogroup:nonroot", "root"]}
{ "action": "accept", "src": ["tag:operator"], "dst": ["tag:server"],
"users": ["root", "autogroup:nonroot"] }
]
}
```
**Geraete-Tags (live):** `kallilabcore` = `tag:server`; `baerchen-1` + `iphone-14`
= `tag:operator`; `kallilab-core` (Docker) + alter `baerchen` bewusst untagged ->
isoliert.
> Die `tag:family`-`dst` `100.80.98.33:443` ist ein **Platzhalter** und wird
> durch die real benoetigten Familien-Dienste ersetzt, sobald diese feststehen.
**Rollout-Protokoll 2026-06-06 (lockout-sicher, je Schritt read-only verifiziert):**
**Lockout-sichere Reihenfolge (wenn die Umsetzung freigegeben wird):**
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.
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).
**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.
**Smoke-Tests nach Anwendung:**
**Hintergrund / Designentscheidungen (2026-06-05/06):**
- `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.
- 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).
**Noch offene Eingaben vor Finalisierung:**
**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 aufraeumen: ERLEDIGT 2026-06-06** — Node-Eintraege `kallilab-core`
und alter Offline-`baerchen` aus der Admin-Konsole entfernt.
- State-Pfad `/mnt/user/appdata/tailscale` (vom entfernten Docker-Stack) bei
Gelegenheit nach `_archive/tailscale-removed-2026-06-06/` (kein Sofort-Loeschen).
- 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).
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.
## Portfreigaben und Exposure
@@ -217,7 +183,6 @@ 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
@@ -246,7 +211,6 @@ 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 |
@@ -263,7 +227,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 | 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 |
| Gast-WLAN | **inaktiv** (FRITZ!Box-UI) | Solange inaktiv: kein Gast-Pfad zu LAN-Diensten; AdGuard-Admin-Trennung primaer ueber Tailscale-Bind statt Netzsegmentierung |
| 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 |
@@ -288,126 +252,6 @@ 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 |
@@ -416,8 +260,7 @@ Abschnitt „SSH-Konfiguration Host".
| 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 | **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. |
| 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. |
| 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". |
+17 -30
View File
@@ -1,38 +1,29 @@
# Documentation Index
Typ: Einstieg/Index · Stand: 2026-06-11 · Status: aktiv
Stand: 2026-06-05
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".
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.
## 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 + Doku-Regeln |
| `REPO_MAP.md` | technische Landkarte des Repositories |
| `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, Smoke-Tests und Test-Reifegrad je Dienst |
| `RESTORE_MATRIX.md` | Restore-Quellen, Dumps, Secrets und Smoke-Tests je Dienst |
| `RESTORE_HANDBOOK.md` | praktische Restore-Anleitung |
| `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
@@ -40,13 +31,11 @@ geloescht (Git-Historie ist das Archiv). Verbindliche Doku-Regeln:
|---|---|
| `STORAGE_LAYOUT.md` | verbindliche Storage-/Share-/Pfad-Regeln |
| `SECRETS_MAP.md` | Secret-Namen, Speicherorte und Einbindungsarten ohne Werte |
| `AUTHELIA_OIDC_PLAN.md` | Plan & Runbook fuer app-uebergreifendes SSO via Authelia OIDC |
| `HARDWARE_INVENTORY.md` | Host-, Disk-, SMART- und Power-Baseline |
| `HARDWARE_INVENTORY.md` | Host-, Disk-, SMART-, USV- und Power-Baseline |
| `NETWORK_INVENTORY.md` | Router, DNS, Tailscale, Portfreigaben und Netzthemen |
| `GUEST_IOT_NETWORK.md` | Sicherer Ablauf fuer FRITZ!Box-Gastnetz / IoT-Isolation |
| `EXTERNAL_DEPENDENCIES.md` | Provider, Konten, DR-Workstation-Kit und externe Abhaengigkeiten |
| `EXTERNAL_DEPENDENCIES.md` | Provider, Konten und externe Abhaengigkeiten |
| `EXTERNAL_OPERATOR_RUNBOOK.md` | Hetzner-/Borg-/FRITZ!Box-Betreibercheck |
| `CAPACITY_AND_LIFECYCLE.md` | Kapazitaet, Wachstum, Upgrade-Trigger, H:/-Nearline-Einordnung |
| `CAPACITY_AND_LIFECYCLE.md` | Kapazitaet, Wachstum und Upgrade-Trigger |
## Monitoring und Automatisierung
@@ -54,20 +43,18 @@ geloescht (Git-Historie ist das Archiv). Verbindliche Doku-Regeln:
|---|---|
| `ALERT_RULES.md` | Prometheus-/ntfy-Regeln und Handlungslogik |
| `RENOVATE.md` | Self-hosted Renovate gegen Gitea |
| `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 |
| `HOME_ASSISTANT_INFLUXDB_ECOWITT.md` | Home Assistant -> InfluxDB 3 -> Grafana |
| `H_DRIVE_NEARLINE_PULL.md` | Windows-H:/ Nearline-Pull fuer kritische Restore-Artefakte |
## Nutzer- und Statusdoku
## Nutzer- und Planungsdoku
| Datei | Zweck |
|---|---|
| `FAMILY_ONBOARDING.md` | familienverstaendliche Nutzungsdoku |
| `AI_CONTEXT.md` | kompakter Kontext fuer KI-Agenten (Regeln + Pointer, kein Status) |
| `homelab-optimierung.md` | technisches Optimierungs-Assessment 2026-06-10 (offene Empfehlungen) |
| `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 |
## 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/`.
Windows-Neuaufsetzen-Dokumente liegen nicht mehr in `docs/`, sondern im fachlich passenden Ordner `../ops/windows-reinstall/docs/`.
+1 -9
View File
@@ -93,15 +93,7 @@ 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`, `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).
| Ignore-Pfade | `_archive`, `ops/grafana-influxdb`, `ops/loki` | Renovate scant alte/abgeloeste Stacks nicht |
## Aktueller Betriebsstand
+5 -12
View File
@@ -33,10 +33,8 @@ 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/MASTER_TODO.md` | einzige operative Statusliste |
| `docs/DECISIONS.md` | Entscheidungs-Register (ADR-light) |
| `docs/AUDIT_2026-05-25_TODO.md` | aktuelle Restliste |
| `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
@@ -51,13 +49,8 @@ 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 |
## Doku-Regeln
## Arbeitsregel
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.
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.
+250
View File
@@ -0,0 +1,250 @@
# 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.
+148 -21
View File
@@ -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; Restore-Smoke am 2026-06-06 erfolgreich |
| Tailscale | Flash-Backup (funktional) / Share | **Funktional: `/boot/config/plugins/tailscale/state`** (native Unraid-Plugin-Instanz `kallilabcore`, Subnet-Router, im Flash-Backup gesichert). Der frueher hier genannte Pfad `/mnt/user/appdata/tailscale` gehoert zum **userspace-only Docker-Stack** `kallilab-core` (redundant, Abbau geplant — siehe `docs/NETWORK_INVENTORY.md`) | keine | Tailscale-State im jeweiligen State-Pfad | Host-Netz | Tailscale verbunden, Subnet-Route `192.168.178.0/24` aktiv |
| AdGuard Home | Share / Borg | `/mnt/user/appdata/adguard/conf` | keine | keine zusaetzlichen Repo-Secrets dokumentiert | `dns_net`, `frontend_net` | DNS-Aufloesung funktioniert |
| Tailscale | Share / Borg | `/mnt/user/appdata/tailscale` | keine | Tailscale-State im Pfad | Host-Netz | Tailscale verbunden |
| PostgreSQL 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; Restore-Smoke am 2026-06-06 erfolgreich |
| 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 |
| 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 | 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` |
| `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` |
---
@@ -60,9 +60,6 @@ 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`; Fach-YAML aus `/mnt/user/services/smart-home-kalli/home-assistant` | HA-native Backup-Artefakte unter `/mnt/user/appdata/homeassistant/backups` falls vorhanden; keine externe DB in Phase 1 | HA-Secrets in `secrets.yaml`, Integrations-Tokens in `.storage`, MQTT-Credentials, spaeter InfluxDB-Token | Traefik, `frontend_net`, `smarthome_net`, Mosquitto, Fachrepo-Clone | `https://home.kaleschke.info` zeigt Login, MQTT-Integration verbindet sich, `backup.create` funktioniert, Energy-Dashboard-Konfiguration bleibt erhalten |
| 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 | Container startet, HA kann sich authentifiziert verbinden, retained Testtopic bleibt nach Restart erhalten |
| 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 |
---
@@ -80,8 +77,6 @@ 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 |
@@ -140,7 +135,7 @@ Die Dump-Erzeugung ist host-seitig ueber `ops/borg-ui/scripts/pre-backup-dumps.s
## Restore-Test-Reifegrad
Stand 2026-06-06. Pro Dienst auf einen Blick: Wurde der Restore schon einmal real getestet?
Stand 2026-06-05. Pro Dienst auf einen Blick: Wurde der Restore schon einmal real getestet?
| Dienst | Tier | Letzter Restore-Test | Typ | Naechster Lauf |
|---|---|---|---|---|
@@ -152,10 +147,10 @@ Stand 2026-06-06. 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 | 2026-06-06 | Config + Container + HTTP 401 + DNS + Filter-Count | quartalsweise oder nach DNS-Aenderungen |
| AdGuard Home | 1 | - | noch kein Test | - |
| Tailscale | 1 | - | noch kein Test | - |
| PostgreSQL 18 Cluster | 1 | 2026-06-03 | globals + 5 per-DB dumps, 290 Tabellen gesamt | quartalsweise |
| Redis 8 | 1 | 2026-06-06 | Pre-Cutover-Artefakt + Container + PING + INFO + DBSIZE | quartalsweise oder vor/nach Redis-Major-Aenderungen |
| Redis 8 | 1 | - | noch kein Test | - |
| 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 |
@@ -164,25 +159,157 @@ Stand 2026-06-06. 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-06 | Full-Backup geschrieben; Recovery-USB-Boot, SMB-Mount und Restore-Point-Sichtpruefung erfolgreich; vor echtem Restore abgebrochen | nach Image-Aenderungen oder quartalsweise |
| baerchen Windows Image | Workstation | 2026-06-05 | Full-Backup geschrieben; Recovery USB + SMB-Mount noch offen | Recovery-USB-Test |
---
## Naechste Restore-Test-Kandidaten (priorisiert)
Stand 2026-06-06. Die frueheren Kandidaten (Shared PG18, Komodo Mongo, Mailarchiver, Mealie, Traefik)
Stand 2026-06-05. 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 `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`)
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
---
## Restore-Test-Runbooks
## Restore-Test-Runbooks (Entwurf)
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`.
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.
+63 -14
View File
@@ -1,10 +1,6 @@
# Rollback Guide - Homelab
Typ: Runbook · Stand: 2026-06-11 · Status: aktiv
# Rollback Guide - Homelab
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.
---
@@ -76,14 +72,59 @@ 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
## Monitoring-Stack Rollback
## BentoPDF / Stirling-PDF Rollback
`monitoring/` ist der einzige Observability-Stack. Bei Problemen:
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:
1. `monitoring` in Komodo stoppen oder auf den letzten funktionierenden Commit zurueckgehen
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
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
---
@@ -91,11 +132,19 @@ Bei Problemen mit Borg UI oder Dump-Automatisierung:
Bevorzugte Quellen:
- 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/`
- Borg-Restore
- erzeugte PostgreSQL-/MariaDB-Dumps
- bekannte Appdata-Snapshots
Dienst-spezifische Restore-Quellen, Dumps und Smoke-Tests stehen in `docs/RESTORE_MATRIX.md`.
Beispiele:
```bash
cp -r /mnt/user/appdata/<service> /mnt/user/backup/
```
```bash
pg_dumpall > /mnt/user/backup/pg_dump_$(date +%Y%m%d).sql
```
---
+4 -5
View File
@@ -40,7 +40,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}`, `${GLANCE_KOMODO_API_KEY}`, `${GLANCE_KOMODO_API_SECRET}`, `${GLANCE_GITEA_TOKEN}`, `${GLANCE_PAPERLESS_TOKEN}`, `${GLANCE_MEALIE_TOKEN}` (alle read-only anlegen) | aktiv |
| Glance | Community Widget API Tokens | Stack ENV `${GLANCE_IMMICH_API_KEY}`, `${GLANCE_ADGUARD_USERNAME}`, `${GLANCE_ADGUARD_PASSWORD}`, `${GLANCE_SPEEDTEST_API_KEY}` | aktiv |
| speedtest-tracker | App Key / Admin-Zugang | Stack ENV `${APP_KEY}`, `${ADMIN_PASSWORD}` | aktiv |
| Nextcloud | Admin User | `/mnt/user/appdata/secrets/nextcloud_admin_user.txt` -> `NEXTCLOUD_ADMIN_USER_FILE` | neu |
| Nextcloud | Admin Password | `/mnt/user/appdata/secrets/nextcloud_admin_password.txt` -> `NEXTCLOUD_ADMIN_PASSWORD_FILE` | neu |
@@ -53,8 +53,7 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
| InfluxDB 3 Core | Admin Token JSON | `/mnt/user/appdata/secrets/influxdb3_admin_token.json` -> Docker Secret `/run/secrets/influxdb3_admin_token` | aktiv |
| Monitoring Grafana | Admin Password | `/mnt/user/appdata/secrets/monitoring_grafana_admin_password.txt` -> Docker Secret `/run/secrets/monitoring_grafana_admin_password` -> `GF_SECURITY_ADMIN_PASSWORD__FILE` | aktiv |
| Monitoring Grafana -> InfluxDB | Datasource Token | `/mnt/user/appdata/secrets/monitoring_grafana_influxdb_token.txt` -> Docker Secret `/run/secrets/monitoring_grafana_influxdb_token` | aktiv |
| 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) |
| Home Assistant -> InfluxDB | HA InfluxDB Token | `/homeassistant/secrets.yaml` -> `influxdb3_homeassistant_token` | geplant |
| 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 |
@@ -62,7 +61,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: | **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) |
| 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 |
---
@@ -141,7 +140,7 @@ Einige Secrets liegen bewusst nur als Komodo Stack Environment Variables vor, we
| `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`, `GLANCE_KOMODO_API_KEY`, `GLANCE_KOMODO_API_SECRET`, `GLANCE_GITEA_TOKEN`, `GLANCE_PAPERLESS_TOKEN`, `GLANCE_MEALIE_TOKEN` | Provider-UIs (Immich, AdGuard, Speedtest-Tracker, Komodo, Gitea, Paperless, Mealie) neu erzeugen | rebuildbar; alle read-only; 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` | Provider-UIs (Immich, AdGuard, Speedtest-Tracker) neu erzeugen | rebuildbar; Widgets bleiben leer bis Tokens neu erzeugt sind, kein kritischer Datentopf |
| `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
+12 -2
View File
@@ -142,7 +142,8 @@ 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` und
`ops/restore-tests/komodo-bootstrap-test.sh`,
`ops/restore-tests/komodo-bootstrap-plan.md` und
`ops/restore-tests/komodo-bootstrap-runbook.md`. Aufruf:
```bash
@@ -202,4 +203,13 @@ 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.
Offene Folgepunkte werden in `docs/MASTER_TODO.md` gefuehrt.
## 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 |
+3 -10
View File
@@ -13,7 +13,7 @@ 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, 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 |
| `tailscale` | VPN/Remote-Zugang | `host-services/tailscale/docker-compose.yml` | Tailscale | Host-Netz | `/mnt/user/appdata/tailscale` | Tier 1, State relevant | nein | `network_mode: host`, `NET_ADMIN`, `NET_RAW` und `/dev/net/tun` sind dokumentierte VPN-Ausnahmen |
| `gitea` | Git-Server / origin fuer GitOps | `core/gitea/docker-compose.yml` | `https://git.kaleschke.info`, SSH `222` | Traefik, `frontend_net`, externe DNS-Resolver fuer GitHub-Push-Mirror | `/mnt/user/services/gitea/data` | Tier 1, `gitea.sqlite.dump` + Share; privater GitHub-Push-Mirror fuer Repo-Bootstrap | ja | SSH-Port 222 direkte Host-Port-Ausnahme; Push-Mirror nach `michaelkaleschke-spec/homelab-infra` reduziert das DR-Bootstrap-Risiko |
## Security / Identity
@@ -47,7 +47,7 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und
| `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`, `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. |
| `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. |
| `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,18 +75,11 @@ 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; 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 |
| `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 |
| `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`; kein direkter Host-Port in Phase 1 | Traefik, `frontend_net`, `smarthome_net`, `smarthome-mosquitto`, Fachrepo unter `/mnt/user/services/smart-home-kalli` | `/mnt/user/appdata/homeassistant` inkl. `.storage`, `secrets.yaml`, `trusted_proxies.yaml`; YAML-Fachdateien read-only aus `/mnt/user/services/smart-home-kalli/home-assistant` | Tier 2, Borg + HA-native Backups; Restore-Probe Pflicht vor produktiven Energie-Automationen | 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` und `trusted_proxies` muessen zur Traefik-Route passen. Ecowitt-HTTP bleibt Phase-2-Entscheidung wegen globalem Traefik-Redirect. |
| `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 | nein | 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 |
+2 -2
View File
@@ -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 `ops/h-drive-nearline/README.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 `docs/H_DRIVE_NEARLINE_PULL.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/MASTER_TODO.md`.
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`.
+151
View File
@@ -0,0 +1,151 @@
# 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.
+41
View File
@@ -0,0 +1,41 @@
# 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.
+1 -7
View File
@@ -369,13 +369,7 @@ Wenn ein Stack `webhook_enabled` in Komodo hatte, zusaetzlich pruefen, ob der zu
## Dokumentationspflicht
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
Nach jeder erfolgreichen Migration oder relevanten Aenderung muessen diese Dateien geprueft werden:
- `docs/SECRETS_MAP.md`
- `docs/ROLLBACK.md`
@@ -1,86 +0,0 @@
# 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.
@@ -1,137 +0,0 @@
# 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
```
@@ -1,401 +0,0 @@
# 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 **69 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 | **45** | `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 | **34** | `docs/DISASTER_RECOVERY.md` Phase 4 Stufe 3, `docs/SERVICES_RECOVERY.md` Stufen AF, `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 4484), 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 ❌ (69 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. 4484) 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. 178343) 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 AF); `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 515 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 | niedrigmittel | 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. 4484 | 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. 178343) | 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".
-25
View File
@@ -1,25 +0,0 @@
# 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` |
-228
View File
@@ -1,228 +0,0 @@
# 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, 714 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: niedrigmittel | 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?
-58
View File
@@ -1,58 +0,0 @@
# 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>
-107
View File
@@ -1,107 +0,0 @@
# 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`
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.
- Keine Trusted-Proxy-Fehler im HA-Log.
- MQTT-Integration verbindet sich mit Host `smarthome-mosquitto`, Port `1883`.
- HA-native Backup-Erstellung funktioniert.
## 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 restart homeassistant
```
Der Restart 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.
## 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.
@@ -0,0 +1,25 @@
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
+1 -1
View File
@@ -1,6 +1,6 @@
services:
postgresql17:
image: postgres:18.4@sha256:29ee7bb30d804447dc9a91fd0d74322ae1dc3a4072cc6346f70a5ed6e783b565
image: postgres:18.4@sha256:8ff36f3c66371cba71d20ceedccfc3de9669a68737607888c4ef0af93abe8e39
container_name: postgresql17
restart: unless-stopped
+3 -26
View File
@@ -66,18 +66,15 @@ 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:
- 172.23.0.3
- 1.1.1.1
- 8.8.8.8
command:
- --config.file=/etc/blackbox_exporter/blackbox.yml
volumes:
- ./blackbox/blackbox.yml:/etc/blackbox_exporter/blackbox.yml:ro
networks:
- monitoring_net
- dns_net
expose:
- "9115"
security_opt:
@@ -132,20 +129,6 @@ 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
@@ -162,7 +145,6 @@ services:
secrets:
- monitoring_grafana_admin_password
- monitoring_grafana_influxdb_token
- grafana_oidc_client_secret
expose:
- "3000"
security_opt:
@@ -178,8 +160,7 @@ services:
- traefik.http.routers.monitoring-grafana.entrypoints=websecure
- traefik.http.routers.monitoring-grafana.tls=true
- traefik.http.routers.monitoring-grafana.tls.certresolver=le
# 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.routers.monitoring-grafana.middlewares=authelia@file,secure-headers@file
- traefik.http.services.monitoring-grafana.loadbalancer.server.port=3000
grafana-dashboard-importer:
@@ -370,8 +351,6 @@ networks:
driver: bridge
frontend_net:
external: true
dns_net:
external: true
volumes:
prometheus_data:
@@ -385,7 +364,5 @@ 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
-5
View File
@@ -48,10 +48,6 @@ 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 |
@@ -91,7 +87,6 @@ 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
-4
View File
@@ -20,9 +20,5 @@
/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 -1
View File
@@ -1,6 +1,6 @@
services:
borg-ui:
image: ainullcode/borg-ui@sha256:0922157e8f77a1b2bd23cd09366a458ea6de07fd9306aa1485f9cfe623eca17f
image: ainullcode/borg-ui@sha256:acb0fbe83dc4a3843abc06f814c5f1061a0701b2cfc574da2e851d17a34ab745
container_name: borg-ui
restart: unless-stopped
security_opt:
+1 -1
View File
@@ -1,6 +1,6 @@
services:
code-server:
image: lscr.io/linuxserver/code-server:4.123.0@sha256:cb261a7f87674b445e0fd66d87d55900c1b823d276c727ab0d168a75e69e9992
image: lscr.io/linuxserver/code-server:4.123.0@sha256:9dd4555720db04eb92d92cc84e7a34f0862bada5679889446a3004c45b5fa59b
container_name: code-server
restart: unless-stopped
security_opt:
+1 -1
View File
@@ -1,6 +1,6 @@
services:
filebrowser:
image: filebrowser/filebrowser:v2.63.14@sha256:1ec9b0c68297550c92f4a93feed432850c2993b261706cc3cc2e808f94a95e76
image: filebrowser/filebrowser:v2.63.12@sha256:fc8c3a46c16bbdf97362b20c50164e97de9c1dd5f63230d28a4cb15248b53ec3
container_name: filebrowser
restart: unless-stopped
security_opt:
-182
View File
@@ -1,182 +0,0 @@
/* ============================================================
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;
}
}
-287
View File
@@ -1,287 +0,0 @@
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
+867 -40
View File
@@ -1,6 +1,5 @@
server:
proxied: true
assets-path: /app/assets
branding:
app-name: KalliLab Dashboard
@@ -8,45 +7,873 @@ branding:
hide-footer: true
theme:
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
background-color: 210 20 13
primary-color: 212 100 50
positive-color: 140 70 40
negative-color: 4 78 57
contrast-multiplier: 1.25
text-saturation-multiplier: 0.9
disable-picker: false
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:
$include: pages.yml
- 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
-515
View File
@@ -1,515 +0,0 @@
- 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: weather
location: Berlin, Germany
units: metric
hour-format: 24h
- 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
-244
View File
@@ -1,244 +0,0 @@
- 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
-80
View File
@@ -1,80 +0,0 @@
- 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
-3
View File
@@ -1,3 +0,0 @@
$include: home.yml
$include: infrastructure.yml
$include: ops.yml
-10
View File
@@ -9,19 +9,11 @@ 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:-}
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:
@@ -58,8 +50,6 @@ services:
networks:
frontend_net:
external: true
monitoring_net:
external: true
glance_socket_net:
name: glance_socket_net
internal: true
-82
View File
@@ -1,82 +0,0 @@
# 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 -1
View File
@@ -1,4 +1,4 @@
FROM nousresearch/hermes-agent:v2026.6.5
FROM nousresearch/hermes-agent:v2026.5.29
USER root
-5
View File
@@ -2,11 +2,6 @@ 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
@@ -1,95 +0,0 @@
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"
@@ -1,127 +0,0 @@
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
@@ -1,90 +0,0 @@
#!/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"
-41
View File
@@ -1,41 +0,0 @@
#!/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'))"
+29
View File
@@ -0,0 +1,29 @@
# 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
+79 -63
View File
@@ -1,85 +1,101 @@
# Restore-Tests - Betrieb und Werkzeuge
# Restore Tests
Typ: Runbook/Tool-Doku · Stand: 2026-06-11 · Status: aktiv
Kontrollierte Restore-Tests fuer `homelab-infra`.
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:
Ziel:
- `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
- Backups durch echte Test-Restores verifizieren
- produktive Pfade nicht beschreiben
- Testlaeufe spaeter weitgehend automatisieren
## Grundregeln
- 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
- 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
## Erfolgskriterien
## Geplante Struktur
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.
- `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
## Aufbau des Verzeichnisses
- `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
Pro Dienst existieren bis zu drei Artefakte:
## Automatisierungsmodell
- `<dienst>-restore-test.sh` - automatisierter Host-Job (produktive Wahrheit)
- `<dienst>-compose.test.yml` - isolierte Testinstanz
- `<dienst>-runbook.md` - manueller Ablauf bzw. Besonderheiten
- Ausfuehrung: Unraid User Script / Host-Job
- Logik: Repo-Skripte in diesem Verzeichnis
- Ergebnis: Markdown-Report
- Meldung: `ntfy`
- Hermes: optional nur fuer Zusammenfassung und Auswertung
Dazu zentrale Helfer:
Wichtig:
- `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
- 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
## Betriebsmodus
## Validiertes Grundmuster
Stand 2026-06-11 ist der Betrieb auf V1+ (validierte Bash-Host-Jobs mit ntfy):
Stand nach dem ersten echten Vaultwarden-Test:
- 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`
- 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
## Schnellstart
Das ist das bevorzugte Muster fuer weitere dateibasierte Restore-Tests wie `gitea`.
```bash
# Frische-Check
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh freshness
Fuer datenbankgestuetzte Dienste wie `paperless` kommt zusaetzlich ein isolierter Dump-Restore in Test-Postgres dazu.
# Dienst-Restore-Check (vaultwarden|gitea|paperless|immich|authelia|adguard|redis|komodo-bootstrap|nextcloud)
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh <dienst>
## Status
# Negativtest des Alarmwegs (quartalsweise)
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh freshness-negative
Aktuell ist das erste validierte Muster vorhanden.
# Mit ntfy-Meldung
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-job-with-ntfy.sh freshness homelab-info
```
- 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)
## 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.
- **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.
Vor dem ersten echten Testlauf je neuem Dienst muessen Zielpfade, Quellpfade und Bereinigungsschritte bewusst freigegeben werden.
@@ -1,14 +0,0 @@
services:
restoretest-adguard:
image: adguard/adguardhome:v0.107.76@sha256:7157eb1dc3b26c7af1d6898759a7b3f7d0fa09891fbd2d3caa6abc1057a9179b
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
-181
View File
@@ -1,181 +0,0 @@
#!/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"
+89
View File
@@ -0,0 +1,89 @@
# 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
+59
View File
@@ -0,0 +1,59 @@
# 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
+89
View File
@@ -0,0 +1,89 @@
# 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.
+1
View File
@@ -34,6 +34,7 @@ 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
@@ -0,0 +1,88 @@
# 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.
@@ -1,74 +0,0 @@
#!/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
+72
View File
@@ -0,0 +1,72 @@
# 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
-16
View File
@@ -1,16 +0,0 @@
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
-152
View File
@@ -1,152 +0,0 @@
#!/bin/bash
set -euo pipefail
# Redis 8 Restore Smoke Test
#
# Scope:
# - Restore aus dem dokumentierten shared-redis-pre-redis8-Artefakt
# - Start einer isolierten Redis-8-Testinstanz auf localhost:16379
# - PING, INFO server und DBSIZE ohne Ausgabe des Passworts
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/redis"
REPORT_ROOT="/mnt/user/backups/restore-reports"
PRE_CUTOVER_ROOT="/mnt/user/backups/borg/dumps/latest"
SECRET_FILE="/mnt/user/appdata/secrets/redis_password.txt"
COMPOSE_FILE="$SCRIPT_DIR/redis-compose.test.yml"
REPORT_FILE="$REPORT_ROOT/redis-$(date +%F).md"
if [ "$WHATIF" -eq 1 ]; then
cat <<EOF
Redis 8 restore test
Mode: WhatIf
RestoreRoot: $RESTORE_ROOT
Restore source: newest $PRE_CUTOVER_ROOT/shared-redis-pre-redis8-*
Secret source: $SECRET_FILE
Test endpoint: 127.0.0.1:16379
Scope: Data restore + isolated Redis boot + PING/INFO/DBSIZE smoke
EOF
exit 0
fi
require_cmd docker
require_path "$PRE_CUTOVER_ROOT"
require_path "$SECRET_FILE"
require_path "$COMPOSE_FILE"
RESTORE_SUCCESS=0
cleanup() {
cleanup_compose "$COMPOSE_FILE"
if [ "$RESTORE_SUCCESS" -ne 1 ]; then
preserve_on_failure "redis" "$RESTORE_ROOT"
return
fi
if [ "$KEEP_DATA" -ne 1 ]; then
rm -rf "$RESTORE_ROOT"
fi
}
trap cleanup EXIT
rm -rf "$RESTORE_ROOT"
mkdir -p "$RESTORE_ROOT/data" "$RESTORE_ROOT/secrets"
restore_source="$(find "$PRE_CUTOVER_ROOT" -maxdepth 1 -type d -name 'shared-redis-pre-redis8-*' | sort | tail -1)"
if [ -z "$restore_source" ]; then
echo "No shared-redis-pre-redis8-* restore source found under $PRE_CUTOVER_ROOT" >&2
exit 1
fi
if [ ! -d "$restore_source" ]; then
echo "Redis restore source is not a directory: $restore_source" >&2
exit 1
fi
cp -a "$restore_source/." "$RESTORE_ROOT/data/"
cp "$SECRET_FILE" "$RESTORE_ROOT/secrets/redis_password.txt"
chmod -R a+rwX "$RESTORE_ROOT/data"
chmod a+r "$RESTORE_ROOT/secrets/redis_password.txt"
data_files="$(find "$RESTORE_ROOT/data" -type f | wc -l | tr -d ' ')"
data_bytes="$(du -sb "$RESTORE_ROOT/data" | awk '{print $1}')"
docker compose -f "$COMPOSE_FILE" up -d restoretest-redis >/dev/null
ping_result=""
for _ in $(seq 1 60); do
ping_result="$(docker exec restoretest-redis sh -lc \
'p=$(cat /run/secrets/redis_password); redis-cli -a "$p" --no-auth-warning PING' 2>/dev/null || true)"
if [ "$ping_result" = "PONG" ]; then
break
fi
sleep 1
done
if [ "$ping_result" != "PONG" ]; then
echo "Redis PING smoke failed: $ping_result" >&2
docker logs --tail 80 restoretest-redis >&2 || true
exit 1
fi
redis_version="$(docker exec restoretest-redis sh -lc \
'p=$(cat /run/secrets/redis_password); redis-cli -a "$p" --no-auth-warning INFO server | awk -F: "/^redis_version:/ {gsub(/\r/, \"\", \$2); print \$2}"')"
dbsize="$(docker exec restoretest-redis sh -lc \
'p=$(cat /run/secrets/redis_password); redis-cli -a "$p" --no-auth-warning DBSIZE' | tr -d '\r')"
aof_enabled="$(docker exec restoretest-redis sh -lc \
'p=$(cat /run/secrets/redis_password); redis-cli -a "$p" --no-auth-warning INFO persistence | awk -F: "/^aof_enabled:/ {gsub(/\r/, \"\", \$2); print \$2}"')"
case "$redis_version" in
8.*) ;;
*)
echo "Unexpected Redis version: $redis_version" >&2
exit 1
;;
esac
if [ "${dbsize:-0}" -lt 1 ]; then
echo "Unexpected Redis DBSIZE: $dbsize" >&2
exit 1
fi
write_report "$REPORT_FILE" <<EOF
# Redis 8 Restore Test Report - $(date +%F)
- Service: \`redis\`
- Restore source: \`$restore_source\`
- Restore root: \`$RESTORE_ROOT\`
- Test container: \`restoretest-redis\`
- Test endpoint: \`127.0.0.1:16379\`
- Result: \`SUCCESS\`
## Checks
- Data restore from pre-Redis8 artifact: \`ok\`
- Secret file mounted from host secret path: \`ok\`
- Restored data files: \`$data_files\`
- Restored data bytes: \`$data_bytes\`
- PING: \`$ping_result\`
- Redis version: \`$redis_version\`
- AOF enabled: \`$aof_enabled\`
- DBSIZE: \`$dbsize\`
## Notes
- Productive Redis port 6379 and productive data path were NOT used.
- Test port was bound to localhost only: \`127.0.0.1:16379\`.
- Redis password value was used from the restored secret file and was not printed.
- Test data was cleaned after success: \`$([ "$KEEP_DATA" -eq 1 ] && echo no || echo yes)\`
EOF
RESTORE_SUCCESS=1
echo "Redis restore test ok -> $REPORT_FILE"
+1 -16
View File
@@ -10,9 +10,6 @@ case "$MODE" in
freshness)
exec "$SCRIPT_DIR/check-restore-freshness.sh"
;;
freshness-negative)
exec "$SCRIPT_DIR/negative-freshness-alert-test.sh"
;;
vaultwarden)
if [ "$WHATIF" = "--what-if" ]; then
exec "$SCRIPT_DIR/vaultwarden-restore-test.sh" --what-if
@@ -43,18 +40,6 @@ case "$MODE" in
fi
exec "$SCRIPT_DIR/authelia-restore-test.sh"
;;
adguard)
if [ "$WHATIF" = "--what-if" ]; then
exec "$SCRIPT_DIR/adguard-restore-test.sh" --what-if
fi
exec "$SCRIPT_DIR/adguard-restore-test.sh"
;;
redis)
if [ "$WHATIF" = "--what-if" ]; then
exec "$SCRIPT_DIR/redis-restore-test.sh" --what-if
fi
exec "$SCRIPT_DIR/redis-restore-test.sh"
;;
nextcloud)
if [ "$WHATIF" = "--what-if" ]; then
exec "$SCRIPT_DIR/nextcloud-restore-test.sh" --what-if
@@ -98,7 +83,7 @@ case "$MODE" in
exec "$SCRIPT_DIR/shared-pg-cluster-restore-test.sh"
;;
*)
echo "Usage: $0 {freshness|freshness-negative|vaultwarden|gitea|paperless|immich|authelia|adguard|redis|nextcloud|komodo-bootstrap|komodo-mongo-restore|traefik|mailarchiver|mealie|shared-pg-cluster} [--what-if]" >&2
echo "Usage: $0 {freshness|vaultwarden|gitea|paperless|immich|authelia|nextcloud|komodo-bootstrap|komodo-mongo-restore|shared-pg-cluster} [--what-if]" >&2
exit 1
;;
esac
+1 -5
View File
@@ -28,8 +28,6 @@ Quartalsweise:
- Restore-/DR-Sanity-Check
- `immich` Restore-Smoke-Test (DB + UI, ohne produktive Foto-Mounts; Erstlauf 2026-05-27 erfolgreich)
- `adguard` Restore-Smoke-Test (Config + HTTP/DNS, nach DNS-Aenderungen auch ausserhalb des Quartals)
- `redis` Restore-Smoke-Test (Pre-Cutover-Artefakt + Redis 8, vor/nach Major-Aenderungen auch ausserhalb des Quartals)
- pruefen:
- Restore-Lab-Struktur
- Reports
@@ -47,9 +45,7 @@ Quartals-Belegung:
Bestaetigte Mini-Restores: Vaultwarden, Gitea und Paperless am 2026-05-07;
Immich am 2026-05-27; Paperless erneut am 2026-05-31; Authelia am
2026-06-03 (Config-Smoke ohne produktiven Dump-Restore); AdGuard Home am
2026-06-06 (Config + HTTP/DNS-Smoke); Redis 8 am 2026-06-06
(Pre-Cutover-Artefakt + PING/INFO/DBSIZE-Smoke).
2026-06-03 (Config-Smoke ohne produktiven Dump-Restore).
## Konkreter Kalender
-34
View File
@@ -1,34 +0,0 @@
# Tailscale - Restore-Runbook
Typ: Runbook · Stand: 2026-06-11 · Status: aktiv (noch kein Erstlauf)
Restore-Pfad fuer den Tailscale-State. Wichtig: Tailscale laeuft als
**natives Unraid-Plugin**; der funktionale State liegt unter
`/boot/config/plugins/tailscale/state` und ist Teil des Flash-Backups
(`docs/RESTORE_MATRIX.md` Tier 1).
## Voraussetzungen
- Zugriff auf das Flash-Backup-Artefakt bzw. ein Borg-Archiv mit dem State-Pfad
- Testpfad unter `/mnt/user/backups/restore-lab/tailscale` vorbereitet
- **Achtung:** Der Tailscale-State ist maschinenspezifisch. Ein Restore auf den produktiven Host wuerde die laufende Verbindung verdraengen. Nur auf einem Wegwerf- oder Offline-Host testen.
## Artefakt-Validierung (ohne produktiven Host)
1. State-Verzeichnis in den Testpfad extrahieren
2. Erwartete Dateien pruefen: `tailscaled.state` vorhanden
3. Dateisystem-Rechte pruefen: `tailscaled.state` muss fuer `root` zugaenglich sein
## Reconnect-Test (auf Wegwerf-Host oder VM)
1. Tailscale mit dem gemounteten State-Pfad starten
2. `tailscale status` zeigt `Connected` oder den erwarteten Hostnamen
3. Tailscale-Admin-Konsole zeigt das Geraet als `Online`
4. SSH ueber Tailscale-IP auf den Testhost moeglich
5. Testinstanz stoppen; Wegwerf-Geraet in der Tailscale-Admin-Konsole entfernen
**Smoke-Test-Kriterium:** Instanz verbindet sich mit bestehendem Tailscale-Account (kein neues Re-Auth noetig), Tailscale-IP ist erreichbar.
**Hinweis:** Falls der State veraltet ist (Key expired), fordert Tailscale ein
Re-Auth an. Das ist ein valides Testergebnis und belegt, wie lang der
Reconnect-Pfad bei abgelaufenem Key ist.
-45
View File
@@ -1,45 +0,0 @@
# Unraid OS Flash - Restore-Runbook
Typ: Runbook · Stand: 2026-06-11 · Status: aktiv (Stick-Boot-Test offen)
Restore-Pfad fuer die Unraid-Flash-Konfiguration. Artefakt-Validierung ist
automatisiert und belegt; offen bleibt nur der physische Ersatzstick-Boot-Test
(siehe `docs/MASTER_TODO.md`).
## Voraussetzungen
- Borg-Artefakt `unraid-flash-config.tar.gz` und `.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
## 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
Letzte Validierung: 2026-06-05, Exit 0, sha256 OK, 390 Eintraege, 8/8
Kern-Configs (siehe Reifegrad-Tabelle in `docs/RESTORE_MATRIX.md`).
## 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 Secret-Material (SSH-Host-Keys,
Tailscale-State, `passwd`/`shadow`/`smbpasswd`, Lizenz-Key) und ist wie
Secret-Backup zu behandeln. Nicht auf oeffentlichen oder unverschluesselten
Testzielen extrahieren.
+56
View File
@@ -0,0 +1,56 @@
# Vaultwarden Restore Test Plan
## Ziel
Nachweisen, dass ein Vaultwarden-Backup in einer isolierten Testumgebung wieder startbar und fachlich nutzbar ist.
## Quelle
- Backup-Quelle: Borg / Share-Backup
- fachlich relevanter Datenpfad: `/mnt/user/appdata/vaultwarden`
- Produktives Admin-Token wird fuer den Restore-Smoke bewusst nicht gemountet;
die Testinstanz nutzt einen Wegwerf-Wert aus `vaultwarden-compose.test.yml`.
## Test-Ziel
- Restore-Lab: `/mnt/user/backups/restore-lab/vaultwarden`
- Testdatenpfad: `/mnt/user/backups/restore-lab/vaultwarden/data`
- Testcontainer: `restoretest-vaultwarden`
- Testport: `127.0.0.1:18080:80`
- Report-Ziel: `/mnt/user/backups/restore-reports/vaultwarden-YYYY-MM-DD.md`
## Schutzregeln
- produktiven Pfad `/mnt/user/appdata/vaultwarden` nie beschreiben
- produktive Domain `vault.kaleschke.info` 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/vaultwarden` vorbereiten
2. Vaultwarden-Daten aus Backup in `restore-lab/vaultwarden/data` wiederherstellen
3. Testinstanz mit `ops/restore-tests/vaultwarden-compose.test.yml` starten
4. lokalen Smoke-Test gegen `http://127.0.0.1:18080` 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
- Login-Seite antwortet
- Vaultwarden-Daten sind vorhanden
Optional spaeter:
- Admin-Endpunkt nur mit separatem Wegwerf-Token pruefen
- Websocket-Endpunkt pruefen
- Anzahl/Vorhandensein zentraler Daten artefaktisch verifizieren
## 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 nur auf dem Host liegen oder zusaetzlich per ntfy referenziert werden
+1 -1
View File
@@ -1,6 +1,6 @@
services:
scrutiny:
image: ghcr.io/starosdev/scrutiny:latest-omnibus@sha256:228483f16a6236d2fa9b2fbfca2e76dc861e648fbc6ae6e680d23e5d00211a5d
image: ghcr.io/starosdev/scrutiny:latest-omnibus@sha256:bf5a07f583998b1b0a690d4b06be0378baa6f6f0140b4c815bd4fa55268df223
container_name: scrutiny
restart: unless-stopped
privileged: true
+1 -1
View File
@@ -1,6 +1,6 @@
services:
speedtest-tracker:
image: lscr.io/linuxserver/speedtest-tracker:1.14.3@sha256:c3750c40948a9360000ce62d694da92e85584b4ab6d3d9a9d1432d76fa5e0726
image: lscr.io/linuxserver/speedtest-tracker:1.14.3@sha256:79c00631575dec6d91c10ed904c211224f00813013a305c2284324e195a538bb
container_name: speedtest-tracker
restart: unless-stopped
security_opt:
+7 -14
View File
@@ -1,18 +1,11 @@
# Windows Reinstall Helpers
Typ: Runbook/Tool-Doku · Stand: 2026-06-11 · Status: aktiv (Projekt Mai 2026 abgeschlossen)
Diese Skripte sind bewusst versionierte Operator-Hilfen fuer den Windows-Neuaufsetzen-/Dual-Boot-Kontext vom Mai 2026.
Versionierte Operator-Hilfen rund um die Windows-Workstation `baerchen`.
Das Neuaufsetzen-Projekt vom Mai 2026 ist abgeschlossen; die zugehoerigen
Plaene und Snapshots liegen in `docs/archive/2026/`.
- `backup-delta-after-2026-05-07.ps1` kopiert nach einem definierten Cutoff lokale Nutzdaten in ein Backup-Ziel.
- `repair-disk0-boot-to-new-windows.ps1` repariert EFI/Bootdateien fuer das neue Windows auf der Intel-SSD und verlangt Adminrechte.
- `cleanup-dualboot-bcd.ps1` bereinigt BCD-Bootmenueeintraege und verlangt eine explizite Textbestaetigung.
- `ops/windows-reinstall/docs/windows-neuaufsetzen-masterplan.md` und `ops/windows-reinstall/docs/postinstall-erstes-ziel-codex.md` enthalten die zugehoerigen Operator-Notizen.
- `ops/windows-reinstall/docs/postdelta-2026-06-04.md` dokumentiert den PostDelta-Stand vom 2026-06-04: aktuellster Banking4-Tresor, WISO-Ergaenzungen, Overwatch-2-Config, iCUE/Corsair-Maussettings und `D:\Users\michi`-Admincheck.
Aktiv bleiben:
- `docs/windows-image-backup-baseline.md` — Veeam-Image-Backup-/Restore-Runbook fuer `baerchen` (DR-relevant, referenziert aus `docs/RESTORE_MATRIX.md`).
- `docs/laufwerks-neustruktur-2026-06-04.md` — Soll-Referenz der Laufwerks-/Ordnerstruktur.
- `backup-delta-after-2026-05-07.ps1` — kopiert nach einem definierten Cutoff lokale Nutzdaten in ein Backup-Ziel.
- `repair-disk0-boot-to-new-windows.ps1` — repariert EFI/Bootdateien (Adminrechte noetig).
- `cleanup-dualboot-bcd.ps1` — bereinigt BCD-Bootmenueeintraege (explizite Textbestaetigung noetig).
Die Skripte enthalten keine Secrets, arbeiten aber mit lokalen Windows-Datentraegern
und duerfen nur interaktiv und mit vorheriger Sichtpruefung ausgefuehrt werden.
Die Skripte enthalten keine Secrets, arbeiten aber mit lokalen Windows-Datentraegern und duerfen nur interaktiv und mit vorheriger Sichtpruefung ausgefuehrt werden.
@@ -1,194 +0,0 @@
param(
[string]$ReportPath = "G:\Gitea_Clone\homelab-infra\ops\windows-reinstall\docs\baerchen-app-license-readiness-2026-06-06.md"
)
$ErrorActionPreference = "Stop"
function Get-InstalledProgram {
param([string[]]$NamePattern)
$roots = @(
"HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*"
)
Get-ItemProperty $roots -ErrorAction SilentlyContinue |
Where-Object {
$display = $_.DisplayName
if (-not $display) { return $false }
foreach ($pattern in $NamePattern) {
if ($display -like $pattern) { return $true }
}
return $false
} |
Sort-Object DisplayName |
Select-Object DisplayName,DisplayVersion,Publisher,InstallDate
}
function Test-PathSummary {
param([string]$Path)
$item = Get-Item -LiteralPath $Path -ErrorAction SilentlyContinue
if (-not $item) {
return [pscustomobject]@{
Path = $Path
Exists = $false
Type = ""
LastWriteTime = ""
Bytes = ""
}
}
$bytes = ""
if ($item.PSIsContainer) {
$measure = Get-ChildItem -LiteralPath $Path -Recurse -Force -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum
$bytes = [int64]($measure.Sum)
} else {
$bytes = [int64]$item.Length
}
[pscustomobject]@{
Path = $Path
Exists = $true
Type = $(if ($item.PSIsContainer) { "Directory" } else { "File" })
LastWriteTime = $item.LastWriteTime.ToString("s")
Bytes = $bytes
}
}
function ConvertTo-MarkdownTable {
param(
[Parameter(ValueFromPipeline = $true)]$InputObject,
[string[]]$Columns
)
begin {
$rows = @()
}
process {
$rows += $InputObject
}
end {
if (-not $rows -or $rows.Count -eq 0) {
return "_Keine Treffer._"
}
$lines = @()
$lines += "| " + ($Columns -join " | ") + " |"
$lines += "| " + (($Columns | ForEach-Object { "---" }) -join " | ") + " |"
foreach ($row in $rows) {
$values = foreach ($column in $Columns) {
$value = [string]$row.$column
$value.Replace("|", "\|")
}
$lines += "| " + ($values -join " | ") + " |"
}
$lines -join "`n"
}
}
$programGroups = [ordered]@{
"Passwortmanager / Browser" = @("*Bitwarden*", "*Vaultwarden*", "*1Password*", "*KeePass*", "*KeePassXC*", "*Chrome*", "*Microsoft Edge*", "*Brave*", "*Firefox*")
"Banking4 / Subsembly" = @("*Banking4*", "*Subsembly*")
"WISO / Buhl" = @("*WISO*", "*Buhl*")
"Microsoft 365 / Office / OneDrive" = @("*Microsoft 365*", "*Microsoft Office*", "*Office 16*", "*OneDrive*")
}
$pathChecks = @(
"C:\Users\michi\AppData\Local\Subsembly",
"C:\Users\michi\AppData\Local\Buhl",
"C:\Users\michi\AppData\Local\Buhl Data Service GmbH",
"C:\ProgramData\Buhl Data Service GmbH",
"C:\Users\michi\Documents\steuer",
"C:\Users\michi\Desktop\Banking",
"C:\Users\michi\OneDrive",
"D:\30_Finanzen",
"D:\30_Finanzen\Recovery-Codes",
"D:\30_Finanzen\BitLocker-RecoveryKey-baerchen-2026-06-06.txt"
)
$oneDriveProcess = Get-Process OneDrive -ErrorAction SilentlyContinue |
Select-Object ProcessName,Id,StartTime
$oneDriveAccounts = Get-ChildItem "HKCU:\Software\Microsoft\OneDrive\Accounts" -ErrorAction SilentlyContinue |
Select-Object PSChildName
$officeCscript = @(
"$env:ProgramFiles\Microsoft Office\Office16\OSPP.VBS",
"${env:ProgramFiles(x86)}\Microsoft Office\Office16\OSPP.VBS"
) | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1
$officeStatus = @()
if ($officeCscript) {
$raw = & cscript.exe //Nologo $officeCscript /dstatus 2>$null
$officeStatus = $raw |
Where-Object { $_ -match "LICENSE NAME|LICENSE STATUS|ERROR CODE|Last 5 characters" } |
ForEach-Object { $_.Trim() }
}
$lines = @()
$lines += "# baerchen App-/Lizenz-Readiness - 2026-06-06"
$lines += ""
$lines += "Automatisch erzeugter lokaler Check. Keine Lizenzkeys, Passwoerter, Tokens oder Recovery-Code-Werte wurden ausgelesen oder ins Repo geschrieben."
$lines += ""
$lines += "## Ergebnis"
$lines += ""
$lines += "- Technische Inventarisierung: erledigt"
$lines += "- Manuelle Konto-/Recovery-Bestaetigung: weiterhin Operator-Schritt"
$lines += ""
$lines += "## Installierte Programme"
$lines += ""
foreach ($group in $programGroups.Keys) {
$lines += "### $group"
$lines += ""
$programs = Get-InstalledProgram -NamePattern $programGroups[$group]
$lines += ($programs | ConvertTo-MarkdownTable -Columns DisplayName,DisplayVersion,Publisher,InstallDate)
$lines += ""
}
$lines += "## Relevante Datenpfade"
$lines += ""
$pathResults = $pathChecks | ForEach-Object { Test-PathSummary $_ }
$lines += ($pathResults | ConvertTo-MarkdownTable -Columns Path,Exists,Type,LastWriteTime,Bytes)
$lines += ""
$lines += "## OneDrive / Microsoft 365 Indikatoren"
$lines += ""
$lines += "### OneDrive Prozess"
$lines += ""
$lines += ($oneDriveProcess | ConvertTo-MarkdownTable -Columns ProcessName,Id,StartTime)
$lines += ""
$lines += "### OneDrive Accounts Registry"
$lines += ""
$lines += ($oneDriveAccounts | ConvertTo-MarkdownTable -Columns PSChildName)
$lines += ""
$lines += "### Office Aktivierungsindikatoren"
$lines += ""
if ($officeStatus.Count -gt 0) {
$lines += '```text'
$lines += $officeStatus
$lines += '```'
} else {
$lines += "_Keine Office-OSPP-Aktivierungsdaten gefunden oder Office nicht klassisch installiert._"
}
$lines += ""
$lines += "## Manuell noch zu bestaetigen"
$lines += ""
$lines += "- [ ] Passwortmanager laesst sich oeffnen und enthaelt Homelab-/Banking-/Provider-Eintraege."
$lines += "- [ ] 2FA-Recovery-Codes fuer Microsoft, Hetzner, Cloudflare, Tailscale, Gitea/GitHub und Banken sind offline oder in Vaultwarden auffindbar."
$lines += "- [ ] Banking4 oeffnet den aktuellen Datentresor; ein frischer Backup-/Exportpfad ist bekannt."
$lines += '- [ ] WISO Steuer 2026 oeffnet, Buhl-Konto/Lizenz ist aktiv, Steuerdateien unter `C:\Users\michi\Documents\steuer` bzw. neuem Zielpfad sind sichtbar.'
$lines += "- [ ] Microsoft-Konto zeigt aktives M365/Office-Installationsrecht."
$lines += "- [ ] OneDrive-Sync ist angemeldet und synchronisiert die erwarteten Ordner."
$lines += ""
$lines += "## Bewertung"
$lines += ""
$lines += 'Dieses Dokument ersetzt nicht die manuelle Kontoanmeldung. Es belegt nur, welche lokalen Programme, Datenpfade und Aktivierungsindikatoren auf `baerchen` sichtbar waren.'
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $ReportPath) | Out-Null
$lines -join "`r`n" | Set-Content -LiteralPath $ReportPath -Encoding UTF8
Write-Host "Report written: $ReportPath"
@@ -221,7 +221,7 @@ Kopier-/Doppelbestand:
## Offene Punkte
- ~~WinRE/Secure Boot/TPM Admin-Check~~ **Erledigt 2026-06-05** (siehe Abschnitt "Admin-Nachlauf 2026-06-05"): WinRE aktiviert, Secure Boot `True`, TPM ready/enabled.
- **BitLocker bewusst deaktiviert (Entscheidung 2026-06-06):** Alle Laufwerke `FullyDecrypted`, Protection `Off`. Recovery laeuft ueber das Veeam-Image; kein BitLocker-Key-Management. Bei spaeterer Aktivierung waere ein neuer Aenderungsblock mit Recovery-Key-Ablage noetig.
- **BitLocker-Entscheidung offen:** Alle Laufwerke `FullyDecrypted`, Protection `Off`. Vor Aktivierung: Recovery Keys fuer mindestens `C:` und `D:` an drei Orten sichern (Vaultwarden, `D:\30_Finanzen\BitLocker-RecoveryKey-baerchen-<DATUM>.txt`, physisch). Verweis: `docs/MASTER_TODO.md` Abschnitt "Windows / Workstation baerchen".
- Optional: `D:\11_Bilder` ReadOnly-Attribut beobachten; fuer Windows-Shell-Ordner ist das in der Praxis meist unkritisch.
- Optional: `D:\13_Musik` bleibt leer, solange aus dem Backup keine Musikdaten nachgezogen werden muessen.
- Optional: `G:\Apps`, `G:\Workspace`, `D:\WSL` in der Homelab-/Dev-Doku ergaenzen.
@@ -314,7 +314,7 @@ Admin-Nachlauf 2026-06-05:
- TPM: vorhanden, ready, enabled, activated, owned
- BitLocker-Status geprueft:
- `C:`, `D:`, `E:`, `G:`, `H:` sind `FullyDecrypted`, Protection `Off`
- BitLocker wurde bewusst nicht aktiviert (Entscheidung 2026-06-06); Recovery laeuft ueber das Veeam-Image.
- BitLocker wurde nicht automatisch aktiviert, weil dafuer eine bewusste Recovery-Key- und Lockout-Entscheidung noetig ist.
- OneDrive Per-Machine Standalone Update Task wurde deaktiviert.
- SSH-Aliases angelegt und getestet:
- `kallilabcore` -> `root@100.80.98.33`
@@ -322,4 +322,4 @@ Admin-Nachlauf 2026-06-05:
Weiter offen:
- Kein BitLocker-Todo mehr. Spaetere Aktivierung nur als neuer bewusster Aenderungsblock mit externer Recovery-Key-Ablage.
- BitLocker-Entscheidung fuer mindestens `C:` und `D:` treffen. Vor Aktivierung Recovery Keys extern sichern.

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