60 Commits

Author SHA1 Message Date
renovate e8c5ddbca5 chore(deps): update docker.n8n.io/n8nio/n8n docker tag to v2.26.4 2026-06-15 16:20:51 +00:00
Micha e8cde1e2e0 fix: use showLegend false for hidden timeseries legends
displayMode: hidden ist in dieser Grafana-Version fuer Timeseries ungueltig
und liess die Panels (Solarstrahlung, Luftdruck, Wallbox-Ladeleistung) leer.
Auf legend.showLegend=false umgestellt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-14 11:18:59 +02:00
Micha f236bfec00 style: upgrade grafana weather dashboard
Live-Gauges (Temp/Feuchte/Wind/UV/Solar mit Farbverlauf) + Luftdruck-Stat
oben, aufgehuebschte Verlaufs-Charts (Temp/Feuchte/Wind mit Innen-Serien,
Solar als Flaeche), Regen pro Tag als barchart. Analog zum Solar-Dashboard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-14 11:14:30 +02:00
Micha fd1b7001f6 fix: solar dashboard 30-day barchart and top-day table format
- "Tages Produktion 30 Tage" auf barchart-Panel (timeseries-bars rendert
  spaerliche Tageswerte nicht); format table + xField time
- "Erreichte TOP kWh": Subquery hat keine Zeitspalte -> format table statt
  time_series (behebt Panel-Fehler); Heute als skalare Query

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-14 11:08:58 +02:00
Micha d45a49d648 style: align solar dashboard with grafana 18753 look
LCD-Segment-Bargauges mit continuous-GrYlRd, radiale Gauges im selben
Farbverlauf, Power-Chart als gruen/gelb gefuellte Flaechen, 30-Tage-
Balken mit GrYlRd, Top-Tag + Heute als LCD-Bars. SolarEdge-Entitaeten
und Batterie/Wallbox-Panels beibehalten.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-14 11:00:25 +02:00
Micha 1255863a4e feat: add easee wallbox panels to solar dashboard
Wallbox-Ladeleistung (Verlauf + Gauge), Gesamt geladen und aktuelle
Session aus eh7klptt_* im Grafana Solar-Dashboard ergaenzt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 22:04:52 +02:00
Micha 26fc96a7af feat: add grafana solar pv dashboard from ha/solaredge
Provisioniertes Dashboard "Solar PV System" (uid ha-solar-pv) auf der
InfluxDB-HA-Datasource: Leistung (PV/Haus/Netz/Batterie), Live-Gauges,
PV heute, Gesamt-/Lifetime-Produktion, 30-Tage-Produktion, Rekord-Tag,
Netzbilanz. Angelehnt an Grafana-Dashboard 18753, auf SolarEdge skaliert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 20:37:27 +02:00
Micha e18720d1f8 docs: record ha influxdb weather archive and glance ha token
- DECISIONS: HA -> InfluxDB 3 Core Wetterarchiv (monitoring_net-Attach,
  Admin-Token-Trade-off, Grafana-Datasource/Dashboard)
- SECRETS_MAP: ha_influxdb_token, Agent-Tokens, GLANCE_HA_TOKEN

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 19:51:07 +02:00
Micha a1e6a03f79 chore: remove redundant berlin weather tile from glance
Die generische Berlin-Wetterkachel ist durch die lokale Ecowitt-Kachel
(custom-api aus HA) ersetzt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 19:51:07 +02:00
Micha 8200697258 fix: parse glance weather gust as float via gjson
toFloat erwartet eine Zahl, der HA-State kommt aber als String -> Template-
Fehler. Boee jetzt direkt per (.Subrequest "gust").JSON.Float "state" lesen,
gjson parst den numerischen String korrekt fuer den Schwellenvergleich.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 19:44:42 +02:00
Micha 05b12c4802 fix: pass GLANCE_HA_TOKEN into glance container
Die Compose-environment-Sektion listet die GLANCE_*-Vars einzeln; der neue
GLANCE_HA_TOKEN fehlte und kam daher nie im Container an (Glance: variable
not found). Jetzt durchgereicht analog der anderen Tokens.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 19:26:46 +02:00
Micha 8d01c3537a feat: add glance weather tile from home assistant
custom-api Wetterkachel zieht die Ecowitt-Sensoren live aus HA
(intern http://homeassistant:8123, frontend_net) im Neon-Ops-Stil.
Boeen > 40 km/h werden rot markiert (analog HA-Warnautomation).
Benoetigt GLANCE_HA_TOKEN als Glance-Stack-ENV.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 18:52:43 +02:00
Micha 230e0cc9dc fix: weather dashboard entity_id without domain prefix
HA influxdb-Integration speichert entity_id ohne 'sensor.'-Praefix.
Queries entsprechend angepasst (gw3000a_* statt sensor.gw3000a_*).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 17:46:47 +02:00
Micha c9bd4af2a8 docs: record ha energy dashboard setup 2026-06-13 16:02:10 +02:00
Micha 5927b478fa docs: record local solaredge integration 2026-06-13 15:02:41 +02:00
Micha ee69bbf730 feat: add grafana weather archive dashboard
Provisioniertes Dashboard 'Wetterarchiv KalliHome' (uid ha-weather-archive)
auf der Datasource ha-weather-influx: Temperatur, Feuchte, Wind, Solar,
Regen/Tag, Luftdruck aus den Ecowitt-Langzeitdaten.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 14:55:26 +02:00
Micha d908d967d4 feat: add grafana datasource for ha weather archive (influxdb)
Zweite InfluxDB-Datasource 'InfluxDB HA Weather' (uid ha-weather-influx)
auf DB homeassistant fuer das Ecowitt-Langzeitarchiv. Gleiche Instanz/Token
wie die bestehende Monitoring-Datasource.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 14:51:55 +02:00
Micha 606779d342 feat: attach home assistant to monitoring_net for influxdb writer
HA bekommt Zugang zum bestehenden monitoring_net, um Wetter-/Langzeitdaten
intern an monitoring-influxdb3-core:8181 zu schreiben (Wetterarchiv).
Kein Host-Port, keine LAN-Exposition; gewaehlte Reachability-Option aus
docs/DECISIONS.md (2026-06-13).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 14:46:25 +02:00
Micha 0fabed4d1a docs: record ecowitt lan-only ingress decision
LAN-only Host-Bind 192.168.178.58:8123 fuer den Ecowitt-HTTP-Push
dokumentiert: DECISIONS-Eintrag (loest Phase-2-Frage), Architektur-Master
Ausnahme 10, SERVICE_CATALOG. Webhook + LAN-Endpunkt verifiziert; offen
bleibt nur die GW3000-Customized-Server-Konfiguration am Geraet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 10:06:29 +02:00
Micha 76b9ffa140 feat: add lan-only host bind for ecowitt http push
Ecowitt GW3000 kann kein HTTPS und pusht per HTTP an den HA-Webhook.
HA bekommt einen LAN-only Host-Bind 192.168.178.58:8123 (nicht WAN),
analog zur dokumentierten InfluxDB-8181-Ausnahme. Kein Traefik-Umbau
des globalen HTTP-Redirects noetig, da Ecowitt rein im LAN pusht.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 10:02:46 +02:00
Micha 170a7dcc1f docs: record ha mqtt integration 2026-06-13 08:49:46 +02:00
Micha 0f5045ea8e docs: close home assistant restore gate 2026-06-13 08:40:47 +02:00
Micha dfa3acc21e ops: add home assistant restore test 2026-06-13 08:37:33 +02:00
Micha 2eb8da1cd4 docs: clarify mqtt broker smoke status 2026-06-13 08:33:01 +02:00
Micha 2acbc1adde docs: record home assistant foundation status 2026-06-13 08:30:53 +02:00
Micha 342d0a0a27 fix: use native ha auth after onboarding 2026-06-13 08:07:08 +02:00
Micha 4ab6dcefd2 fix: protect ha onboarding with authelia 2026-06-12 21:52:45 +02:00
Micha c24b792808 fix: allow home hairpin during ha onboarding 2026-06-12 21:51:34 +02:00
Micha 25a4ada891 fix: guard home assistant onboarding 2026-06-12 21:50:15 +02:00
Micha 6e6005aefd feat: add smart home foundation 2026-06-12 20:51:18 +02:00
Micha ad438a07b3 fix: allow mosquitto config ownership setup 2026-06-12 20:45:32 +02:00
Micha ce6f5c72dd feat: add smart home runtime foundation 2026-06-12 20:38:03 +02:00
Micha 630ee8dd90 ops: glance server-stats balken kraeftiger (13px, innenschatten)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:00:38 +02:00
Micha b1ca9ef19c ops: glance server-stats balken - rund, gradient, glow, warnfarbe ab 85%
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 19:57:36 +02:00
Micha 1c949d3fcc ops: glance internet-widget - bytes/s nach mbit/s umrechnen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 19:55:05 +02:00
Micha cfa6c01768 ops: glance komodo/immich widgets - stat-leisten mit trennlinien, pills, gradient-bars
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 19:24:12 +02:00
Micha 3474d53ce5 ops: glance borg-widget fix - alter via promql berechnen statt now.Unix im template
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 19:05:48 +02:00
Micha ca81b959cc ops: glance borg-backup-widget via prometheus + synthwave/matrix presets
- glance zusaetzlich in monitoring_net (nur lesende Prometheus-Query, kein neuer Listener)
- Borg-Widget: Backup-Alter aus homelab_borg_last_completed_timestamp_seconds, Status aus homelab_borg_last_success
- Theme-Presets synthwave und matrix

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 19:01:32 +02:00
Micha 23764dff38 ops: glance farbschema entlilat - neutraler grund, akzente blau/cyan/amber/gruen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:44:00 +02:00
Micha 3c4a48d7e5 ops: glance neon-ops v2 - rotierende akzentfarben, gradient-zahlen, animierte header
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:30:38 +02:00
Micha c0a39f5dfc ops: glance neon-ops look - card styling, glows, sattere theme-farben
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:27:30 +02:00
Micha a1d7b6e433 ops: glance layout verdichtet - internet kombiniert, container-tabs, mealie/commit-fixes
- Home rechte Spalte: Internet+Speed in einem Widget, DNS-und-VPN-Monitor entfernt, Container-Listen als Tab-Gruppe
- Infrastructure: Container-Listen als Tab-Gruppe, Mealie-Statistik auf /api/admin/about/statistics (404-Fix)
- Commit-Widgets: toRelativeTime als span-Attribut, nur erste Commit-Zeile

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 16:47:25 +02:00
Micha 45f43da659 ops: glance speedtest widget - ookla raw data fallbacks (data.data.*)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 16:44:18 +02:00
Micha 290cb8949e ops: glance dashboard v2 - split config, stack widgets, releases page
- Config per $include aufgeteilt (glance.yml -> pages/home/infrastructure/ops, containers-map zentral)
- Neue Widgets: Komodo Stacks, Gitea GitOps, Paperless, Mealie, Scrutiny Disk Health, Wetter, To-do
- Neue Seite Ops und Releases (releases-Widget fuer gepinnte Images, RSS, Commit-Log)
- Homelab-Status in Tab-Gruppen Core/Apps/Ops, Speedtest-Widget mit ehrlichem Leerzustand
- Theme-Presets (Catppuccin, Gruvbox, Light) + custom.css via Assets-Mount
- Compose: 5 neue read-only Token-ENVs, Doku in SECRETS_MAP/MASTER_TODO nachgezogen

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 16:06:42 +02:00
Micha d933d3cee8 ops: refine komodo stack hygiene check
- Hash drift now requires actual file changes inside the stack's
  compose-dir between deployed_hash and latest_hash. Komodo's
  deployed_hash bumps only on redeploy while latest_hash tracks master
  HEAD, which produced six false-positive "Pending Update" warnings
  for stacks whose own files never changed.
- Add EXPECTED_NOT_IN_KOMODO env (default: hermes-agent) for compose
  files intentionally not Komodo-managed (work-in-progress, build/dev
  compose).

End-to-end run on host: 0 critical, 0 warnings.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-12 13:23:52 +02:00
Micha baedf9f932 docs: record komodo-stack-hygiene-weekly activation
Cron registered in /boot/config/plugins/user.scripts and live in
/etc/cron.d/root after update_cron. First scheduled run: Sun 05:00.
End-to-end smoke test on host: 6 warnings, 0 critical.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-12 12:57:06 +02:00
Micha b387757e87 ops: add komodo stack hygiene posture-check
Catches the failure class that let immich_new slip through: stacks
without a configured repo, project_missing, hash drift, and repo
compose files without a matching Komodo stack. Dry-run on host found
6 honest warnings, 0 critical. Wrapper as Unraid User Script for
weekly cadence is tracked in MASTER_TODO.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-12 12:51:07 +02:00
Micha 3eedbcbe16 docs: record immich stack cleanup 2026-06-12 08:24:27 +02:00
Micha 9033724b15 docs: record host DNS fallback as active
eth0 DNS server 2 = 192.168.178.1 (FRITZ!Box) is set as failover behind
AdGuard. Mark the komodo-bulk-deploy-dns runbook immediate measure as
implemented. Closes the AdGuard SPOF for Docker image pulls.
Ref: docs/homelab-optimierung.md recommendation 3a.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 20:26:22 +02:00
Micha aae176f1b7 docs: record Hetzner Storage Box automatic snapshots as active
Daily snapshots at 05:30 UTC (after the 04:30 local Borg run), 7 days
retention, snapshot directory visible for single-file restore via
.zfs/snapshot/. Closes the ransomware/misuse gap left open by the
explicit decision against Borg append-only (2026-06-01).
Ref: docs/homelab-optimierung.md recommendation 2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 20:25:01 +02:00
Micha c7590e6603 fix(immich): pin server and ML to v2.7.5 instead of mutable release tag
Digests unchanged (verified against GHCR manifest API: release ==
v2.7.5 for both images). Renovate now produces visible version PRs
instead of silent digest bumps that hide major version jumps.
Ref: docs/homelab-optimierung.md recommendation 1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 20:02:26 +02:00
Micha 3e486b95f6 docs: add pdf cleanup and quarterly doc gardening to MASTER_TODO
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 19:55:15 +02:00
Micha 08b4be7a5d docs: add AGENTS.md entry point for non-Claude AI agents
Codex CLI auto-reads AGENTS.md; file only points to AI_CONTEXT,
architecture master, workflow and the binding doc rules - no duplicated
content (one fact, one home).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 19:50:53 +02:00
Micha a4f4696b0d docs: anchor documentation rules, rebuild index, archive proposal
- REPO_MAP.md: replace Arbeitsregel with 8 binding documentation rules
  (one fact one home, done leaves the working copy, file types, header
  convention, quarterly gardening)
- WORKFLOW.md Dokumentationspflicht and CLAUDE.md aligned to the rules
- docs/README.md index rebuilt for the consolidated state
- H drive docs merged into ops/h-drive-nearline/README.md (scheduled
  task + no-MIR rule added); docs/H_DRIVE_NEARLINE_PULL.md removed
- implemented proposal archived to docs/archive/2026/

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 07:14:11 +02:00
Micha 1fcdb68221 docs: consolidate restore documentation into ops/restore-tests
- merge RESTORE_HANDBOOK.md into ops/restore-tests/README.md (single
  operations doc; restore status lives only in RESTORE_MATRIX maturity
  table)
- RESTORE_MATRIX.md: extract embedded runbook drafts (261 -> 141 lines);
  unraid-flash and tailscale stubs become ops/restore-tests runbooks,
  adguard/redis checklists superseded by validated scripts
- delete six historical pre-first-run *-plan.md files (runbook + script
  are the source of truth since the validated first runs)
- SERVICES_RECOVERY: drop completed task table; DISASTER_RECOVERY:
  point related docs and section 11 to MASTER_TODO/schedule

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 07:11:16 +02:00
Micha 489a429316 docs: single status list - dissolve audit restliste, slim AI context
- MASTER_TODO.md is now the only status list: parked decisions point to
  DECISIONS.md, done log capped at 5 condensed entries
- delete AUDIT_2026-05-25_TODO.md (open items and parked decisions fully
  covered by MASTER_TODO/DECISIONS)
- AI_CONTEXT.md: drop duplicated status block, keep rules and pointers
- EXTERNAL_DEPENDENCIES.md: condense review log to recent entries
- fix references in DR_WORKSTATION_SETUP, EXTERNAL_OPERATOR_RUNBOOK,
  STORAGE_LAYOUT, REPO_MAP, docs/README

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 07:08:43 +02:00
Micha 513f41b852 docs: introduce DECISIONS.md decision register, slim architecture master
- new docs/DECISIONS.md (ADR-light): decisions migrated from master
  section 13, MASTER_TODO parked items, hardware inventory and audit
  restliste into one chronological register
- HOMELAB_ARCHITECTURE_MASTER_V2.md: section 13 replaced by pointer,
  section 9 condensed (502 -> 372 lines, target picture only)
- ROLLBACK.md: drop rollback recipes for already removed services
  (uptime-kuma, grafana/influx legacy, stirling/glance bootstrap notes)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 07:06:18 +02:00
Micha c80b51f585 docs: introduce docs/archive, remove finished sprint boards and generated report
- docs/archive/2026/ with index README: DR tabletop drill, workstation
  audits, HA/Ecowitt draft, pre-Borg backup audit, finished windows
  reinstall project docs
- delete weekend sprint boards (content preserved in MASTER_TODO done log
  and git history)
- untrack generated ops/policy-checks/last-report.md and gitignore it
- fix references (CLAUDE.md, docs/README.md, ops/windows-reinstall/README.md)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 07:02:57 +02:00
Micha 42ed59a4d7 docs: commit pending status updates from 2026-06-06 sprint wrap-up
Preserves uncommitted working-copy updates (Veeam recovery test done,
BitLocker decision, ACL rollout, freshness negative test) before the
documentation consolidation restructures these files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 07:00:25 +02:00
Micha 58c3324557 docs: add homelab documentation optimization proposal
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 06:36:53 +02:00
80 changed files with 3894 additions and 2703 deletions
+3
View File
@@ -22,6 +22,9 @@
**/*.tgz **/*.tgz
**/*.zip **/*.zip
# Generated reports
ops/policy-checks/last-report.md
# Local/editor noise # Local/editor noise
.DS_Store .DS_Store
Thumbs.db Thumbs.db
+20
View File
@@ -0,0 +1,20 @@
# Agent Context - Homelab Infra
Typ: Einstieg/Index · Stand: 2026-06-11 · Status: aktiv
Einstiegspunkt fuer KI-Agenten (Codex, Gemini u. a.; Claude nutzt zusaetzlich
`CLAUDE.md`). Kein eigener Inhalt - nur Pflichtpfade.
## Vor jeder Arbeit lesen
1. `docs/AI_CONTEXT.md` - Systembild, harte Regeln, Ausnahmen-Kurzliste
2. `HOMELAB_ARCHITECTURE_MASTER_V2.md` - Architektur-Zielbild
3. `docs/WORKFLOW.md` - verbindlicher GitOps-/No-Drift-Ablauf
4. die betroffene `docker-compose.yml` bzw. das betroffene Runbook (Index: `docs/README.md`)
## Nicht verhandelbar
- Keine Secret-Werte lesen, zitieren oder schreiben - nur Namen und Pfade.
- Keine Deployments, Host-Hotfixes oder Docker-Schreibbefehle ohne ausdrueckliche Anweisung.
- Doku-Regeln aus `docs/REPO_MAP.md` einhalten: ein Fakt, ein Zuhause. Status nur in `docs/MASTER_TODO.md`, Entscheidungen nur in `docs/DECISIONS.md`.
- Bei Drift oder zwei fehlgeschlagenen Reparaturversuchen: stoppen, `docs/GITOPS_DRIFT_RUNBOOK.md`.
+3 -2
View File
@@ -1,6 +1,6 @@
# Claude Code Context - Homelab Infra # Claude Code Context - Homelab Infra
Stand: 2026-05-04 Stand: 2026-06-11
Dieses Repository ist die GitOps-Quelle fuer das KalliLab CORE Homelab auf einem Unraid-Host. Es verwaltet Docker-Compose-Stacks fuer Core-Dienste, Security, Infrastruktur, Apps, Operations-Tools, Host-nahe Dienste und Traefik. Gitea Online ist die operative Quelle der Wahrheit; Komodo konsumiert den Git-Stand und deployed daraus. Dieses Repository ist die GitOps-Quelle fuer das KalliLab CORE Homelab auf einem Unraid-Host. Es verwaltet Docker-Compose-Stacks fuer Core-Dienste, Security, Infrastruktur, Apps, Operations-Tools, Host-nahe Dienste und Traefik. Gitea Online ist die operative Quelle der Wahrheit; Komodo konsumiert den Git-Stand und deployed daraus.
@@ -22,7 +22,7 @@ Zusaetzlich je nach Thema:
- Secrets: `docs/SECRETS_MAP.md` - Secrets: `docs/SECRETS_MAP.md`
- GitOps-/Komodo-/Runtime-Drift: `docs/GITOPS_DRIFT_RUNBOOK.md` - GitOps-/Komodo-/Runtime-Drift: `docs/GITOPS_DRIFT_RUNBOOK.md`
- Gesamtbild fuer KI-Agenten: `docs/AI_CONTEXT.md` - Gesamtbild fuer KI-Agenten: `docs/AI_CONTEXT.md`
- Home Assistant / Ecowitt / InfluxDB: `docs/HOME_ASSISTANT_INFLUXDB_ECOWITT.md` - Architektur-/Betriebsentscheidungen mit Begruendung: `docs/DECISIONS.md`
## Projektbeschreibung ## Projektbeschreibung
@@ -123,6 +123,7 @@ Standard-Rollback ist ein Ruecknahme-Commit oder gezielte Rueckaenderung mit Pus
## Arbeitsweise fuer Claude ## Arbeitsweise fuer Claude
- Erst lesen, dann handeln. - Erst lesen, dann handeln.
- Doku-Regeln aus `docs/REPO_MAP.md` einhalten: ein Fakt, ein Zuhause. Status nur in `docs/MASTER_TODO.md`, Entscheidungen nur in `docs/DECISIONS.md`, Erledigtes verlaesst die Arbeitskopie.
- Bei Unsicherheit Zustand messen, nicht erraten. - Bei Unsicherheit Zustand messen, nicht erraten.
- Aenderungen klein halten und nur den betroffenen Bereich anfassen. - Aenderungen klein halten und nur den betroffenen Bereich anfassen.
- Bestehende Doku und Repo-Konventionen bevorzugen. - Bestehende Doku und Repo-Konventionen bevorzugen.
+19 -188
View File
@@ -3,7 +3,7 @@
> **Single Source of Truth** für Docker-Netzwerkarchitektur, Sicherheitsregeln, Zielbild und Migration des Kallilabcore-Homelabs. > **Single Source of Truth** für Docker-Netzwerkarchitektur, Sicherheitsregeln, Zielbild und Migration des Kallilabcore-Homelabs.
> **Arbeitsregel für KI-Assistenten:** Dieses Dokument immer zuerst lesen, bevor Fragen zu Containern, Netzwerken, Traefik, Tailscale, Migration oder Security beantwortet werden. > **Arbeitsregel für KI-Assistenten:** Dieses Dokument immer zuerst lesen, bevor Fragen zu Containern, Netzwerken, Traefik, Tailscale, Migration oder Security beantwortet werden.
**Stand:** 2026-06-02 | **Aktueller Schwerpunkt:** GitOps / Doku-Synchronisierung / Reproduzierbare Deployments **Stand:** 2026-06-13 | **Aktueller Schwerpunkt:** Home Assistant Tibber / Energie-Kosten
--- ---
@@ -20,7 +20,7 @@
10. [Bekannte Ausnahmen und Begründungen](#10-bekannte-ausnahmen-und-begründungen) 10. [Bekannte Ausnahmen und Begründungen](#10-bekannte-ausnahmen-und-begründungen)
11. [Projektorganisation und Arbeitsmodus](#11-projektorganisation-und-arbeitsmodus) 11. [Projektorganisation und Arbeitsmodus](#11-projektorganisation-und-arbeitsmodus)
12. [Nutzung mit KI / Kontext-Regel](#12-nutzung-mit-ki--kontext-regel) 12. [Nutzung mit KI / Kontext-Regel](#12-nutzung-mit-ki--kontext-regel)
13. [Betriebserfahrungen und Entscheidungs-Log](#13-betriebserfahrungen-und-entscheidungs-log) 13. [Betriebserfahrungen und Entscheidungs-Log (ausgelagert)](#13-betriebserfahrungen-und-entscheidungs-log-ausgelagert)
--- ---
@@ -93,6 +93,7 @@ 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_net` | Compose-intern, bridge | zentraler Observability-Stack fuer Prometheus, Loki, Grafana, Promtail, Exporter und InfluxDB | Zielzustand |
| `monitoring_influx_lan` | Compose-intern, bridge | nicht-oeffentliches Zusatznetz nur fuer Docker Host-Port-Publishing von InfluxDB 8181 | Zielzustand | | `monitoring_influx_lan` | Compose-intern, bridge | nicht-oeffentliches Zusatznetz nur fuer Docker Host-Port-Publishing von InfluxDB 8181 | Zielzustand |
| `glance_socket_net` | Compose-intern, `internal: true` | interner Zugriff von Glance auf den Docker-Socket-Proxy | umgesetzt | | `glance_socket_net` | Compose-intern, `internal: true` | interner Zugriff von Glance auf den Docker-Socket-Proxy | umgesetzt |
| `smarthome_net` | bridge, `internal: true` | interne Smart-Home-Kommunikation zwischen Home Assistant, Mosquitto, spaeter Zigbee2MQTT/ESPHome | vorbereitet |
| `host` | host | nur für echte Sonderfälle | begründet | | `host` | host | nur für echte Sonderfälle | begründet |
### 3.2 Finales Diagramm (vereinfacht) ### 3.2 Finales Diagramm (vereinfacht)
@@ -123,7 +124,8 @@ App-interne Netze
├── immich_default (internal: true) ✅ ├── immich_default (internal: true) ✅
├── nextcloud_internal (internal: true) ✅ ├── nextcloud_internal (internal: true) ✅
├── monitoring_net (zentraler Observability-Stack) ├── monitoring_net (zentraler Observability-Stack)
── monitoring_influx_lan (Bridge fuer LAN-Port-Publishing, keine Traefik-Route) ── monitoring_influx_lan (Bridge fuer LAN-Port-Publishing, keine Traefik-Route)
└── smarthome_net (HA, Mosquitto, spaeter Zigbee2MQTT/ESPHome)
Host-Sonderfälle Host-Sonderfälle
├── tailscale ├── tailscale
@@ -146,6 +148,7 @@ Diese Dienste sind über echte `*.kaleschke.info`-Domains erreichbar:
- `immich_server` — immich.kaleschke.info - `immich_server` — immich.kaleschke.info
- `nextcloud` — cloud.kaleschke.info - `nextcloud` — cloud.kaleschke.info
- `plex` — plex.kaleschke.info (Traefik, native Plex-Auth; Plex Remote Access/Port 32400 bleibt aus) - `plex` — plex.kaleschke.info (Traefik, native Plex-Auth; Plex Remote Access/Port 32400 bleibt aus)
- `homeassistant` — home.kaleschke.info (Traefik, native Home-Assistant-Auth)
### 4.2 Nicht öffentlich / nur Tailscale oder Traefik + Middleware ### 4.2 Nicht öffentlich / nur Tailscale oder Traefik + Middleware
Diese Dienste sind **keine Public Apps**: Diese Dienste sind **keine Public Apps**:
@@ -261,6 +264,7 @@ Legende Status:
| `immich_redis` | ⏳ | `immich_default` | intern | intern-only | anonymes Volume → named volume | | `immich_redis` | ⏳ | `immich_default` | intern | intern-only | anonymes Volume → named volume |
| `nextcloud-postgres` | ✅ | `nextcloud_internal` | intern | app-eigene Nextcloud-Datenbank mit `_FILE`-Secret | — | | `nextcloud-postgres` | ✅ | `nextcloud_internal` | intern | app-eigene Nextcloud-Datenbank mit `_FILE`-Secret | — |
| `nextcloud-redis` | ✅ | `nextcloud_internal` | intern | app-eigener Cache fuer File Locking / Sessions | — | | `nextcloud-redis` | ✅ | `nextcloud_internal` | intern | app-eigener Cache fuer File Locking / Sessions | — |
| `smarthome-mosquitto` | ✅ | `smarthome_net` | intern `1883`, kein Host-Port in Phase 1 | MQTT-Datenbus fuer Home Assistant, spaeter ESPHome und Zigbee2MQTT; Passwortdatei und ACLs in `/mnt/user/appdata/mosquitto/config`; MQTT-Smoke und HA-MQTT-Integration am 2026-06-13 erfolgreich | LAN-Port erst in ESPHome-Phase mit ACLs/per-Device-Usern |
### 7.4 Produktive Apps ### 7.4 Produktive Apps
@@ -274,6 +278,7 @@ Legende Status:
| `immich_server` | ✅ | `immich_default`, `frontend_net` | Traefik | aktiv via `immich.kaleschke.info` | — | | `immich_server` | ✅ | `immich_default`, `frontend_net` | Traefik | aktiv via `immich.kaleschke.info` | — |
| `immich_machine_learning` | ✅ | `immich_default` | intern | bleibt intern | — | | `immich_machine_learning` | ✅ | `immich_default` | intern | bleibt intern | — |
| `nextcloud` | ✅ | `frontend_net`, `nextcloud_internal` | Traefik | aktiv via `cloud.kaleschke.info`, nativer Nextcloud-Login, WebDAV/CardDAV faehig | CalDAV/CardDAV-Redirect via Traefik-Labels | | `nextcloud` | ✅ | `frontend_net`, `nextcloud_internal` | Traefik | aktiv via `cloud.kaleschke.info`, nativer Nextcloud-Login, WebDAV/CardDAV faehig | CalDAV/CardDAV-Redirect via Traefik-Labels |
| `homeassistant` | ✅ | `frontend_net`, `smarthome_net` | Traefik via `home.kaleschke.info`, native HA-Auth | Home Assistant Container im GitOps-Stack `smart-home/`; kein HAOS, kein Supervised; Fach-YAML kommt aus `smart-home-kalli`, `.storage` bleibt in `/mnt/user/appdata/homeassistant`; Komodo-Stack und Gitea-Webhook aktiv; HA-native Backup-Erzeugung, Restore-Probe, HA-MQTT-Integration, SolarEdge Local und Energy Dashboard am 2026-06-13 erfolgreich | Tibber, Energie-Kosten, spaeter Energie-Automationen |
| `plex` | ✅ | `host` | Traefik via `plex.kaleschke.info` + Plex native Auth; LAN direkt `:32400` | Compose-Stack unter `host-services/plex/`; Host-Netz bleibt fuer Discovery / Plex GDM dokumentierte Ausnahme; Traefik routet per File-Provider-Ausnahme auf `http://192.168.178.58:32400`, weil Docker-Labels Host-Netz-Container aus Traefik heraus auf `127.0.0.1` routen wuerden; kein direkter WAN-Port 32400 und Plex Remote Access bleibt aus; Server geclaimt von `Xeridos`; Smart-TVs (Schlafzimmer, Wohnzimmer) ueber WLAN-LAN per mDNS | — | | `plex` | ✅ | `host` | Traefik via `plex.kaleschke.info` + Plex native Auth; LAN direkt `:32400` | Compose-Stack unter `host-services/plex/`; Host-Netz bleibt fuer Discovery / Plex GDM dokumentierte Ausnahme; Traefik routet per File-Provider-Ausnahme auf `http://192.168.178.58:32400`, weil Docker-Labels Host-Netz-Container aus Traefik heraus auf `127.0.0.1` routen wuerden; kein direkter WAN-Port 32400 und Plex Remote Access bleibt aus; Server geclaimt von `Xeridos`; Smart-TVs (Schlafzimmer, Wohnzimmer) ueber WLAN-LAN per mDNS | — |
| `super-productivity` | ✅ vorbereitet | `frontend_net` | Traefik + Middleware | Persoenliche Task-PWA des Operators; Issues kommen aus Gitea `Micha/mails` via n8n-Mail-Workflow | Deploy + Webhook + DNS-Eintrag offen | | `super-productivity` | ✅ vorbereitet | `frontend_net` | Traefik + Middleware | Persoenliche Task-PWA des Operators; Issues kommen aus Gitea `Micha/mails` via n8n-Mail-Workflow | Deploy + Webhook + DNS-Eintrag offen |
| `n8n` | ✅ vorbereitet | `frontend_net` | Traefik, native Auth (keine pauschale Authelia) | Workflow-Automation; erster Workflow: GMX-Mail -> OpenAI-Extraktion -> Gitea-Issue in `Micha/mails`; `N8N_ENCRYPTION_KEY` ist Stack-ENV-Pflichtsecret | Deploy + Webhook + Owner-Setup offen | | `n8n` | ✅ vorbereitet | `frontend_net` | Traefik, native Auth (keine pauschale Authelia) | Workflow-Automation; erster Workflow: GMX-Mail -> OpenAI-Extraktion -> Gitea-Issue in `Micha/mails`; `N8N_ENCRYPTION_KEY` ist Stack-ENV-Pflichtsecret | Deploy + Webhook + Owner-Setup offen |
@@ -371,23 +376,7 @@ labels:
## 9. Historische Migration (abgeschlossen) ## 9. Historische Migration (abgeschlossen)
Die frühere Blockmigration aus der Portainer-/Dockerman-Phase ist fachlich abgeschlossen. Die Blockmigration aus der Portainer-/Dockerman-Phase ist abgeschlossen: Traefik laeuft labelbasiert ohne File-Provider-Service-Routen, Komodo ist alleiniger Stack-Manager, Portainer CE ist entfernt, Borg/Dumps/Restore-Tests sind produktiv. Entscheidungen und Hintergruende stehen in `docs/DECISIONS.md`; die Sprint-Historie liegt in Git.
Dieser Abschnitt dient nur noch als **historischer Vermerk**:
- Traefik läuft labelbasiert ohne Service-Routen im File-Provider.
- Komodo ist der einzige aktive Stack-Manager.
- Portainer CE ist entfernt.
- Borg/Borg UI, Dump-Automatisierung und Restore-Test sind produktiv eingeführt.
- Frühere Sprint-/Block-Checklisten werden hier **nicht mehr operativ gepflegt**.
Für den laufenden Betrieb gilt stattdessen:
- Zielbild und Architektur in diesem Dokument
- Git-/Komodo-Ablauf in `docs/WORKFLOW.md`
- fachliche Änderungen in der jeweils betroffenen Stack-Doku
- Entscheidungen und besondere Umstellungen im Entscheidungs-Log unten
## 10. Bekannte Ausnahmen und Begründungen ## 10. Bekannte Ausnahmen und Begründungen
| Container | Ausnahme | Begründung | | Container | Ausnahme | Begründung |
@@ -405,10 +394,13 @@ Für den laufenden Betrieb gilt stattdessen:
| `mail-archiver` | `frontend_net` + `backend_net` | braucht Internetzugang für IMAP-Abruf (GMX, Gmail) und DB-Zugang | | `mail-archiver` | `frontend_net` + `backend_net` | braucht Internetzugang für IMAP-Abruf (GMX, Gmail) und DB-Zugang |
| `traefik/dynamic/*` | manueller Host-Sync trotz GitOps | File-Provider bleibt bewusst fuer `middlewares.yml`, `tls.yml` und `dashboards.yml`; Komodo deployed diese Dateien nicht automatisch | | `traefik/dynamic/*` | manueller Host-Sync trotz GitOps | File-Provider bleibt bewusst fuer `middlewares.yml`, `tls.yml` und `dashboards.yml`; Komodo deployed diese Dateien nicht automatisch |
| `nextcloud` | keine zentrale ForwardAuth-Middleware | Nextcloud bringt eigene Auth, Clients und WebDAV/CardDAV-Endpunkte mit; Traefik bleibt Reverse Proxy, Auth bleibt app-nativ | | `nextcloud` | keine zentrale ForwardAuth-Middleware | Nextcloud bringt eigene Auth, Clients und WebDAV/CardDAV-Endpunkte mit; Traefik bleibt Reverse Proxy, Auth bleibt app-nativ |
| `monitoring-influxdb3-core` | Host-Port 8181 auf LAN-IP; `user: "0"` | Home Assistant laeuft in einer VM ausserhalb des Compose-Netzes und muss Metriken schreiben koennen; keine Traefik-Route, kein `frontend_net`, Zugriff nur ueber Token und LAN-IP `INFLUXDB_BIND_IP`; InfluxDB 3 Core benoetigt im aktuellen Container-Setup Root-Rechte fuer den lokalen Object-Store-Pfad im named volume | | `monitoring-influxdb3-core` | Host-Port 8181 auf LAN-IP; `user: "0"` | Home Assistant schreibt spaeter Langzeitdaten. Nach der HA-Container-Entscheidung muss der Writer-Pfad in der Influx-Phase explizit gewaehlt werden: entweder LAN-Bind via `INFLUXDB_BIND_IP` oder gezieltes gemeinsames internes Netz. Keine Traefik-Route, Zugriff nur ueber Token; InfluxDB 3 Core benoetigt im aktuellen Container-Setup Root-Rechte fuer den lokalen Object-Store-Pfad im named volume |
| `monitoring-promtail` | Docker-Socket read-only | Docker-Log-Discovery fuer Loki; keine Schreibrechte, keine Appdaten-Persistenz ueber den Socket | | `monitoring-promtail` | Docker-Socket read-only | Docker-Log-Discovery fuer Loki; keine Schreibrechte, keine Appdaten-Persistenz ueber den Socket |
| `n8n` | keine pauschale Authelia-Middleware | Webhook-Endpunkte (`/webhook/*`, `/webhook-test/*`) muessen ohne ForwardAuth erreichbar bleiben; n8n bringt eigene Owner-/Login-Auth mit (analog Komodo/Nextcloud) | | `n8n` | keine pauschale Authelia-Middleware | Webhook-Endpunkte (`/webhook/*`, `/webhook-test/*`) muessen ohne ForwardAuth erreichbar bleiben; n8n bringt eigene Owner-/Login-Auth mit (analog Komodo/Nextcloud) |
| `plex` | Traefik ohne Authelia, File-Provider-Ausnahme trotz Host-Netz | Plex bringt native Konto-/Client-Auth mit; vorgeschaltete ForwardAuth wuerde Plex Web, Apps und Client-Flows stoeren. Docker-Labels sind fuer diesen Host-Netz-Container ungeeignet, weil Traefik sonst `127.0.0.1:32400` nutzt; daher `traefik/dynamic/plex.yml` mit Ziel `192.168.178.58:32400`. Route nur ueber Traefik/443 (`plex.kaleschke.info`), direkter Plex-WAN-Port 32400 und Plex Remote Access bleiben deaktiviert. | | `plex` | Traefik ohne Authelia, File-Provider-Ausnahme trotz Host-Netz | Plex bringt native Konto-/Client-Auth mit; vorgeschaltete ForwardAuth wuerde Plex Web, Apps und Client-Flows stoeren. Docker-Labels sind fuer diesen Host-Netz-Container ungeeignet, weil Traefik sonst `127.0.0.1:32400` nutzt; daher `traefik/dynamic/plex.yml` mit Ziel `192.168.178.58:32400`. Route nur ueber Traefik/443 (`plex.kaleschke.info`), direkter Plex-WAN-Port 32400 und Plex Remote Access bleiben deaktiviert. |
| `homeassistant` | Traefik ohne Authelia, Fach-YAML aus separatem Repo | Home Assistant bringt eigene Auth, mobile Apps, Webhooks und Integrationsfluesse mit. Der Container haengt in `frontend_net` fuer Traefik und in `smarthome_net` fuer MQTT/Zigbee2MQTT/ESPHome. `.storage` und Secrets bleiben in Appdata und werden per Borg gesichert, nicht versioniert. |
| `homeassistant` (Ecowitt) | LAN-only Host-Port `8123` auf `192.168.178.58` | Ecowitt-GW3000 kann kein HTTPS und pusht per HTTP an den HA-Webhook. HA bekommt einen Host-Bind nur auf der LAN-IP (`192.168.178.58:8123:8123`, nicht `0.0.0.0`/WAN), analog InfluxDB 8181. Kein Traefik-Umbau des globalen HTTP-Redirects noetig, da Ecowitt rein im LAN pusht. Webhook nicht `local_only`, geschuetzt durch 128-bit-Zufalls-ID. Siehe `docs/DECISIONS.md` (2026-06-13). |
| `homeassistant` (SolarEdge Local) | HACS/Custom-Integration `solaredge_modbus_multi` | Lokaler SolarEdge-Zugriff laeuft ueber Modbus TCP `192.168.178.111:1502`, Device-ID `1`. Das ist bewusst lokal statt Cloud-API, weil kein SolarEdge-API-Key verfuegbar ist und der Wechselrichter Modbus-Daten fuer Inverter, Smart Meter und Batterie liefert. Custom-Integration-Warnungen bei HA-Core-Upgrades beachten. |
--- ---
@@ -464,176 +456,15 @@ Damit ist sofort klar:
--- ---
## 13. Betriebserfahrungen und Entscheidungs-Log ## 13. Betriebserfahrungen und Entscheidungs-Log (ausgelagert)
### Fix Common Problems Plugin entfernt (2026-06-03) Architektur- und Betriebsentscheidungen werden seit 2026-06-11 zentral in
`docs/DECISIONS.md` gefuehrt (ADR-light: Entscheidung, Kontext, Review-Trigger).
Befund: Drei `grep -R ... /usr/local/emhttp`-Prozesse liefen seit ~7 Tagen durchgehend mit je 100 % CPU (TIME+ 177-179 h). Status `R`, von PID 1 adoptierte Zombies einer laengst beendeten Fix-Common-Problems-(FCP)-Scan-Session. Folge: konstante Load 14.6 auf 12 Cores, IOWAIT-Peaks bis 55 %, USB-Flash unter Dauer-IO. Dieses Dokument haelt nur noch das Zielbild. Neue Entscheidungen werden dort
eingetragen; hier aendert sich nur etwas, wenn das Zielbild selbst betroffen
Ursache: Unraids `/usr/local/emhttp` enthaelt Symlinks `mnt -> /mnt` (mehrere TB Array) und `boot -> /boot` (USB-Flash). GNU `grep -R` dereferenziert Symlinks rekursiv. Ein FCP-Scan-Schritt (`/etc/cron.daily/fix.common.problems.sh -> scripts/scan.php`) hat dadurch effektiv die gesamte Array-Struktur gegrept und ist beim ersten Treffer-Loop haengen geblieben. Der Lock `/tmp/fix.common.problems/scanRunning` war vom 2026-06-03 04:40 - jeder weitere Daily-Cron-Run wuerde dasselbe Verhalten reproduzieren. ist (Netze, Zugangsmodell, Ausnahmen in Sektion 10).
Massnahme: FCP-Plugin per `plugin remove fix.common.problems.plg` deinstalliert. Cron-Eintrag, Plugin-Verzeichnis und `/tmp`-Reste sauber. Load fiel innerhalb Minuten auf 1.08 (1-min).
Entscheidung: FCP wird bewusst **nicht** wieder installiert. Begruendung:
- Restliche Risiken werden bereits ueber andere Wege abgedeckt: Scrutiny (Laufwerks-SMART), Monitoring-Stack (Container-Health, Prometheus-Alerts, Blackbox), Posture-Check (Filesystem-/Drift-/Authelia-Audit), Critical-Events-Watcher (`services/posture-check/docker-critical-events.sh`).
- FCP ist ein externes Community-Plugin und nicht Teil der Repo-managed GitOps-Welt; Verhalten haengt von einer Online-Templates-Datei ab.
- Ein einmaliges Hang-up reicht, um die Flash-Drive 7 Tage lang zu thrashen - das Verhaeltnis Nutzen/Risiko ist negativ.
Folgen fuer Doku: Eintrag in `docs/AUDIT_2026-05-25_TODO.md` unter "Zuletzt geschlossen"; FCP taucht nicht mehr als Voraussetzung in DR/Monitoring-Pfaden auf, da es nie produktiv referenziert war.
### Plex Server Reclaim und LAN-only-Profil (2026-05-28)
Befund: Die `Preferences.xml` des Plex-Servers war seit dem 18.05.2026 13:18 jungfraeulich (391 Bytes, ohne `PlexOnlineMail`/`PlexOnlineUsername`/`PlexOnlineToken`). Der Server war damit nicht mit einem Plex.tv-Account geclaimt, obwohl die Smart-TVs ueber LAN-Discovery (mDNS/Plex-GDM) weiter funktionierten. Beim Login als `Xeridos` ueber `app.plex.tv` meldete der Server "Keine Berechtigung", weil kein Owner registriert war. Zusaetzlich war die `library_sections`-Konfiguration leer (Backups vom 19./22./28.05. ebenfalls ~370 KB statt MBs/GBs); die Bibliotheks-Konfiguration war seit dem 18.05. weg, die Filmdateien unter `/mnt/user/media/*` blieben aber intakt (~833 Verzeichnisse, davon `movies/` 1.4 TB und `Heimatfilme/` 300 GB).
Reclaim:
- Operator-Claim-Token via `https://www.plex.tv/claim` als `Xeridos` erzeugt.
- Plex-Container per `PLEX_CLAIM=claim-... docker compose up -d --force-recreate plex` am Host-Pfad `/mnt/user/services/stacks/plex/host-services/plex` neu erstellt. Token wurde **nur** als Shell-Inline-ENV mitgegeben, **nicht** in eine `.env`-Datei, **nicht** in die Compose, **nicht** in die Komodo-Stack-ENV geschrieben.
- Nach Erfolg: zweiter `docker compose up -d --force-recreate plex` ohne `PLEX_CLAIM`, damit der verbrauchte Token nicht im `docker inspect`-ENV-Snapshot persistiert.
- Bash-History defensiv geleert.
Endstand:
- `PlexOnlineUsername="Xeridos"`, `PlexOnlineMail="michideheld@gmx.de"`, `PlexOnlineHome="1"`.
- Bibliotheken neu angelegt via Plex-Web → Verwalte Mediatheken → `/data/movies`, `/data/Heimatfilme` etc.
- `PublishServerOnPlexOnlineKey="0"` (Remote Access deaktiviert), Plex-Relay aus.
- 2026-06-06: Externer Komfortzugriff ueber `https://plex.kaleschke.info` via Traefik ergaenzt. Das ist **kein** Plex-Remote-Access und keine direkte FRITZ!Box-Freigabe auf `32400`; Plex bleibt hinter Traefik/443 und nutzt native Plex-Auth.
Konsequenzen fuer Doku/Betrieb:
- Plex-Home-Familien-Profil ("Familie") muss bei Bedarf neu eingeladen werden; war ohnehin nicht aktiv genutzt.
- Watch-State aus der Zeit vor dem 18.05. ist nicht recoverbar; Filme/Serien laufen bei Wiederaufruf bei 00:00 los.
- `host-services/plex/docker-compose.yml` enthaelt weiter `PLEX_CLAIM: ${PLEX_CLAIM:-}`, damit ein zukuenftiger Reclaim ohne Repo-Aenderung moeglich ist.
### Traefik — Wechsel zu reinen Docker-Labels (2026-03-28)
Die statischen File-Provider-Konfigurationen in `/mnt/user/appdata/traefik/dynamic/` wurden vollständig bereinigt:
- **Gelöscht:** `immich.yml`, `gitea.yml`, `mealie.yml`, `scrutiny.yml`, `vaultwarden.yml.bak`
- **Verbleibend (notwendig):** `middlewares.yml`, `tls.yml`, `dashboards.yml`
**Hintergrund:** Die alten File-Provider-Configs haben `@file`-Routen mit `@docker`-Routen konkurrieren lassen. In Traefik v3 gewinnt der File-Provider und hat z.B. Immich auf die falsche IP geroutet (Bad Gateway). Nach Löschung läuft Traefik ausschließlich auf Docker-Labels.
**Regel:** Neue Dienste ausschließlich via Docker Compose Labels konfigurieren. Keine neuen `.yml`-Dateien im `dynamic/`-Verzeichnis für Service-Routen anlegen.
### Komodo — Ablösung von Portainer als Stack-Manager (2026-03-28)
Komodo ist nun der primäre GitOps-Stack-Manager:
- **Komodo Core** läuft als Docker-Stack (`ops/komodo/docker-compose.yml`)
- **Komodo Periphery** läuft auf dem Unraid-Host für direktes Server-Management
- Stacks werden via Gitea synchronisiert und über Komodo deployed
- Portainer CE ist abgeschaltet; Komodo ist der alleinige aktive Stack-Manager
**Betriebsregel:** Alle Stack-Änderungen laufen über Git; Komodo konsumiert nur den Stand aus Gitea.
**Zugangsregel:** Komodo bleibt bewusst bei nativer Authentifizierung ohne pauschal vorgeschaltete ForwardAuth-Middleware vor dem gesamten Router. Hintergrund sind die gemischten UI-, API-, Webhook- und Periphery-Endpunkte unter derselben Domain.
### Komodo Self-Stack Drift-Recovery (2026-05-04)
- Befund: `komodo-core` und `komodo-periphery` liefen aus temporaeren `/tmp/*repair.yml`-Dateien, waehrend `komodo-mongo` auf den fehlenden persistenten Pfad `/mnt/user/services/stacks/komodo/compose.yaml` verwies.
- Recovery: Repair-YAMLs und Runtime-ENV wurden unter `/mnt/user/appdata/komodo/_drift_backup_2026-05-04/` gesichert; eine zusaetzliche Recovery-ENV liegt unter `/mnt/user/appdata/secrets/_komodo_stack_env_recovery_2026-05-04.env` und ist als temporaeres Tier-1-Secret-Material zu behandeln.
- Der persistente Self-Stack wurde unter `/mnt/user/services/stacks/komodo/compose.yaml` aus `ops/komodo/docker-compose.yml` wiederhergestellt. Die hostseitige `.env` bleibt ausserhalb von Git.
- Reconcile-Regel: Bei Self-Stack-Drift keinen pauschalen `docker compose up -d` ausfuehren, wenn der Dry-run `komodo-mongo` recreaten wuerde. Core und Periphery koennen gezielt mit `--no-deps` neu erstellt werden, Mongo bleibt dabei unangetastet.
- Ergebnis: Alle drei Komodo-Container zeigen wieder auf `/mnt/user/services/stacks/komodo/compose.yaml`; Mongo blieb waehrend der Rueckfuehrung healthy.
### AdGuard Home — Ablösung von Pi-hole (2026-03-28)
`binhex-official-pihole` wurde entfernt und durch `AdGuard Home` + `unbound` ersetzt:
- AdGuard läuft als Git-Stack (`host-services/Adguard/docker-compose.yml`)
- Netzwerke: `dns_net` (feste IP 172.23.0.3) + `frontend_net`
- Port 53 (DNS) direkt gebunden — dokumentierte Ausnahme
- Admin-UI direkt gebunden via Tailscale-IP `100.80.98.33:8082` auf Container-Port 80 — 2026-05-26 bewusst als einfache Operator-Entscheidung ohne Traefik-/2FA-Umstellung
- `unbound` läuft weiterhin als Upstream-Resolver in `dns_net`
### diun — Entfernung (2026-03-28)
`diun` (Docker Image Update Notifier) wurde deinstalliert:
- Stack gelöscht
- Orphan-Netzwerk `diun_diun_default` bereinigt
- Repo-Eintrag `infra/diun/` aus Git entfernt
Update-Monitoring kann über Komodo's eingebaute Update-Notifications abgedeckt werden.
### ntfy — Push-Notifications (Git-Stack)
`ntfy` läuft als Git-Stack (`apps/ntfy/docker-compose.yml`):
- `ntfy.kaleschke.info` via Traefik
- `NTFY_UPSTREAM_BASE_URL: https://ntfy.sh` für mobile Push-Notifications
- `NTFY_BEHIND_PROXY: true` korrekt gesetzt
### immich_default — internal: true gesetzt (2026-03-29)
`immich_default` wurde von `external: true` auf ein Compose-verwaltetes internes Netz umgestellt:
- **Vorher:** `external: true` (manuell erstellt, falsche Labels `com.docker.compose.network=default`)
- **Nachher:** Compose-managed, `internal: true`, `driver: bridge`, korrekte Labels
- Durchgeführt via: manuelles `docker stop` der Containers → `docker network rm immich_default` → Komodo Redeploy
- Ergebnis: alle Immich-Container (`immich_postgres`, `immich_redis`, `immich_machine_learning`) sind jetzt vom Internet isoliert; nur `immich_server` hat zusätzlich `frontend_net` für Traefik
### Secrets in Komodo Stacks
Host-Pfade in `env_file` (z.B. `/mnt/...`) sind in Git-Stacks nicht verfügbar. Standardlösung: Stack Environment Variables + `${VARIABLE_NAME}` in der Compose.
**Regel:** Wenn `_FILE` nicht unterstützt wird → Stack Environment Variable. Kein Secret im Git.
**Bewusste Ausnahme:** `paperless-ngx` bleibt fuer `PAPERLESS_DBPASS` und `PAPERLESS_REDIS` vorerst bei Stack Environment Variables. Eine Umstellung auf `_FILE` ist fachlich denkbar, wird aber nicht gegen den aktuell stabilen Produktionsstand erzwungen.
### Borg UI / BorgBase (2026-04-12)
- `borg-ui` läuft als Admin-Dienst in `ops/borg-ui/docker-compose.yml`
- nur `frontend_net`, weil Web-UI + externer SSH-Zugang zu BorgBase benötigt werden
- keine direkten Host-Ports; Zugriff ausschließlich via Traefik + Middleware über `borg.kaleschke.info`
- breite Restore-/Backup-Mounts bewusst gesetzt; inklusive `/local/secrets` fuer Disaster Recovery, separates Restore-Ziel unter `/mnt/user/appdata/borg-ui/restore`
- kein separater Borg-CLI-Container nötig, da Borg UI die Borg-CLI bereits im Container mitbringt
| Container | `_FILE` Support |
|---|---|
| Vaultwarden | ✅ ja |
| PostgreSQL | ✅ ja |
| code-server | ✅ ja (`PASSWORD_FILE`) |
| Immich Postgres | ✅ ja (`POSTGRES_PASSWORD_FILE`) |
| Mealie | ✅ ja (`POSTGRES_PASSWORD_FILE`) |
| paperless-ngx | ❌ nein für DB-Pass → Stack ENV |
### Reproduzierbare Deployments (2026-04-17)
Mutable Tags wie `latest`, `stable`, `release` oder reine Major-Tags wurden auf die **aktuell laufenden Digests** eingefroren. Das ist bewusst **kein Upgrade-Mechanismus**, sondern dient dazu, den heute funktionierenden Laufzeitstand exakt im Repo festzuhalten. Echte Versions-Upgrades bleiben ein eigener, geplanter Schritt.
### Stateful Digest-Pinning (2026-05-05, ergaenzt 2026-05-16)
- Tier-1/stateful Basisdienste werden bevorzugt mit sprechendem Minor-/Patch-Tag plus Digest gepinnt, z. B. `postgres:17.9@sha256:...` oder `mongo:7.0.32@sha256:...`.
- Redis-Caches sind seit dem Hardening-Sprint 2026-05-16 auf `redis:7.4-alpine@sha256:...` vereinheitlicht. Updates erfolgen bewusst stackweise mit Smoke-Test.
- Bereits versionierte Apps koennen optional spaeter ebenfalls Digests erhalten; dieser Schritt ist getrennt vom Datenhalter-Pinning.
### Nextcloud und Stirling-PDF (2026-04-19)
- `nextcloud` wird bewusst **nicht** als AIO-Stack gebaut, sondern als klassischer Docker-Microservice-Stack mit eigenem PostgreSQL und eigenem Redis. Das passt besser zum bestehenden GitOps-/Compose-Modell des Repos.
- `nextcloud` bleibt bei nativer App-Authentifizierung ohne zentrale ForwardAuth-Middleware vor dem Router, damit Browser-Login, Desktop-/Mobile-Clients sowie WebDAV/CardDAV sauber funktionieren.
- `stirling-pdf` wird als geschuetzter Tool-Stack hinter `authelia@file,secure-headers@file` betrieben; die interne Stirling-Login-Funktion bleibt deaktiviert, um Doppel-Login zu vermeiden.
### BentoPDF und Monitoring-Zielstack (2026-04-30, aktualisiert 2026-05-17)
- `bentopdf` ersetzt repo-seitig `stirling-pdf` auf der bestehenden Domain `pdf.kaleschke.info`, bleibt aber bis zum bewussten Komodo-Deploy nur vorbereitet.
- BentoPDF benoetigt fuer Office-Konvertierung die Cross-Origin-Isolation-Header `Cross-Origin-Opener-Policy: same-origin` und `Cross-Origin-Embedder-Policy: require-corp`; diese werden per Traefik-Docker-Middleware gesetzt.
- `monitoring/` ist der zentrale Zielstack fuer Prometheus, Loki, Promtail, Grafana, node-exporter, cAdvisor und InfluxDB 3 Core.
- `monitoring-grafana` wird als geschuetztes Monitoring-UI unter `monitoring.kaleschke.info` betrieben.
- `monitoring-influxdb3-core` bleibt ohne Traefik-/Public-Route; fuer interne Writer wie Home Assistant kann Port `8181` per `INFLUXDB_BIND_IP` auf eine LAN-Adresse gebunden werden.
- Fuer dieses Port-Publishing nutzt `monitoring-influxdb3-core` zusaetzlich `monitoring_influx_lan`. Das ist keine Public-App-Freigabe und ersetzt nicht die Token-Authentifizierung.
- InfluxDB 3 Core nutzt einen festen Versionstag statt `latest`, weil der InfluxDB-`latest`-Tag versionsstrategisch im Umbruch ist.
- Die alten Pfade `ops/grafana-influxdb` und `ops/loki` wurden am 2026-05-26 aus dem aktiven Repo entfernt; `monitoring/` ist der einzige Observability-Zielstack.
- Uptime Kuma wurde nach erfolgreichem Blackbox-/Grafana-Smoke-Test entfernt; `monitoring/` ist die Quelle fuer HTTP-Erreichbarkeit und Alerts.
### Monitoring-Logging-Baseline (2026-05-17)
- `monitoring-loki` laeuft intern auf `monitoring_net`, ohne Traefik-Route und ohne Host-Port.
- `monitoring-promtail` sammelt Docker-Logs ueber `/var/run/docker.sock:ro` und `/var/lib/docker/containers:ro` und schreibt sie an Loki.
- `monitoring-grafana` bekommt provisionierte Datasources fuer Prometheus, Loki und InfluxDB 3 Core.
- Loki-Logdaten sind Diagnosematerial mit begrenzter Retention, keine primaere Restore-Quelle.
### Authelia ohne Redis-Session-Backend (2026-05-04)
- Authelia nutzt PostgreSQL fuer persistente Storage-Daten, aber bewusst kein Redis-Session-Backend.
- Das haelt den Tier-1-Auth-Pfad einfacher; nach einem Authelia-Restart muessen aktive Sessions neu aufgebaut werden.
- `infra/redis` ist historisch als "shared Cache" angelegt, wird aber faktisch nur von Paperless als App-Cache genutzt. Immich, Nextcloud und Mealie betreiben jeweils eigene Redis-Instanzen in ihren App-internen Netzen; Authelia laeuft bewusst ohne Redis. Eine spaetere Konsolidierung in `apps/paperless/` (analog zu Mealie/Immich/Nextcloud) bleibt fachlich denkbar, ist aber kein priorisierter Schritt.
### ddns-updater — Netz-Ausnahme
Bleibt bewusst in `frontend_net` statt `backend_net`, weil `backend_net` `internal: true` ist und ddns-updater die Cloudflare-API erreichen muss.
### mail-archiver — Hybrid-Dienst
Benötigt `backend_net` (PostgreSQL) + `frontend_net` (IMAP-Abruf von GMX/Gmail). Kein reiner Backend-Dienst. Die Web-UI ist via Traefik unter `mail.kaleschke.info` erreichbar und wird durch `authelia@file,secure-headers@file` plus App-eigene Auth geschuetzt.
### Netzwerk-Standard für Apps mit Datenbanken
- App → `frontend_net` + internes Netzwerk
- Datenbank → nur internes Netzwerk (`internal: true`)
Beispiel (Mealie): `mealie``frontend_net` + `mealie_internal`, `mealie-postgres` → nur `mealie_internal`.
--- ---
## Schlussformel ## Schlussformel
Dieses Dokument ist keine lose Notiz, sondern das **operative Masterdokument** für die Docker- und Zugriffsarchitektur des Homelabs. Dieses Dokument ist keine lose Notiz, sondern das **operative Masterdokument** für die Docker- und Zugriffsarchitektur des Homelabs.
+1
View File
@@ -66,6 +66,7 @@ Bei Hardware-, Netzwerk-, Provider- oder Kapazitaetsfragen zusaetzlich:
## Status ## Status
- Offene Punkte stehen ausschliesslich in `docs/MASTER_TODO.md`; Entscheidungen mit Begruendung in `docs/DECISIONS.md`.
- Komodo ist der primaere und einzige produktive Stack-Manager. - Komodo ist der primaere und einzige produktive Stack-Manager.
- Komodo bleibt bewusst bei nativer Authentifizierung; zentrale Traefik-Auth wird dort nicht pauschal vorgeschaltet. - Komodo bleibt bewusst bei nativer Authentifizierung; zentrale Traefik-Auth wird dort nicht pauschal vorgeschaltet.
- Portainer CE ist abgeschaltet und kein Teil des aktiven Betriebs mehr. - Portainer CE ist abgeschaltet und kein Teil des aktiven Betriebs mehr.
+2 -2
View File
@@ -1,7 +1,7 @@
services: services:
immich-server: immich-server:
container_name: immich_server container_name: immich_server
image: ghcr.io/immich-app/immich-server:release@sha256:c15bff75068effb03f4355997d03dc7e0fc58720c2b54ad6f7f10d1bc57efaa5 image: ghcr.io/immich-app/immich-server:v2.7.5@sha256:c15bff75068effb03f4355997d03dc7e0fc58720c2b54ad6f7f10d1bc57efaa5
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
- redis - redis
@@ -32,7 +32,7 @@ services:
immich-machine-learning: immich-machine-learning:
container_name: immich_machine_learning container_name: immich_machine_learning
image: ghcr.io/immich-app/immich-machine-learning:release@sha256:a2501141440f10516d329fdfba2c68082e19eb9ba6016c061ac80d23beadf7f3 image: ghcr.io/immich-app/immich-machine-learning:v2.7.5@sha256:a2501141440f10516d329fdfba2c68082e19eb9ba6016c061ac80d23beadf7f3
restart: unless-stopped restart: unless-stopped
environment: environment:
# Workaround fuer gunicorn-25.1.0-Control-Socket-Bug: der Worker haengt # Workaround fuer gunicorn-25.1.0-Control-Socket-Bug: der Worker haengt
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
n8n: n8n:
image: docker.n8n.io/n8nio/n8n:2.26.2@sha256:61ba01bc5e39304bbc928c9dbecd938c3a5cc1331b68affba6a34d0f654c43d9 image: docker.n8n.io/n8nio/n8n:2.26.4@sha256:ddb5204bc8c7b71c446dfe1c1dce296df138bcfadb7f5e27d7de35ec2889d026
container_name: n8n container_name: n8n
restart: unless-stopped restart: unless-stopped
+13 -40
View File
@@ -1,8 +1,10 @@
# AI Context # AI Context
Stand: 2026-06-05 Typ: Einstieg/Index · Stand: 2026-06-11 · Status: aktiv
Kurzer Kontext fuer KI-Agenten. Nicht als Ersatz fuer die echten Runbooks lesen. Kurzer Kontext fuer KI-Agenten. Nicht als Ersatz fuer die echten Runbooks lesen.
Diese Datei enthaelt bewusst **keinen** Arbeitsstand mehr — Status nur in
`docs/MASTER_TODO.md`, Entscheidungen nur in `docs/DECISIONS.md`.
## Systembild ## Systembild
@@ -20,6 +22,7 @@ Kurzer Kontext fuer KI-Agenten. Nicht als Ersatz fuer die echten Runbooks lesen.
3. betroffene Compose-Datei 3. betroffene Compose-Datei
4. bei Service-Fragen `docs/SERVICE_CATALOG.md` 4. bei Service-Fragen `docs/SERVICE_CATALOG.md`
5. bei Restore/DR `docs/DISASTER_RECOVERY.md` und `docs/RESTORE_MATRIX.md` 5. bei Restore/DR `docs/DISASTER_RECOVERY.md` und `docs/RESTORE_MATRIX.md`
6. bei "warum ist das so?"-Fragen `docs/DECISIONS.md`
## Harte Regeln ## Harte Regeln
@@ -30,51 +33,21 @@ Kurzer Kontext fuer KI-Agenten. Nicht als Ersatz fuer die echten Runbooks lesen.
- Traefik dynamic config und Authelia Host-Config sind manuelle Sync-Ausnahmen. - Traefik dynamic config und Authelia Host-Config sind manuelle Sync-Ausnahmen.
- Bei Drift zuerst Git, Gitea, Komodo Workspace, Docker Runtime und Host getrennt pruefen. - Bei Drift zuerst Git, Gitea, Komodo Workspace, Docker Runtime und Host getrennt pruefen.
- Nach zwei fehlgeschlagenen Reparaturversuchen stoppen und `docs/GITOPS_DRIFT_RUNBOOK.md` nutzen. - Nach zwei fehlgeschlagenen Reparaturversuchen stoppen und `docs/GITOPS_DRIFT_RUNBOOK.md` nutzen.
- Doku-Regel: ein Fakt hat genau ein Zuhause; verlinken statt kopieren (`docs/REPO_MAP.md`).
## Bekannte Ausnahmen ## Bekannte Ausnahmen
Autoritativ: `HOMELAB_ARCHITECTURE_MASTER_V2.md` §10. Kurzliste:
- Traefik: Host-Ports 80/443, WAN-Freigabe nur 443 - Traefik: Host-Ports 80/443, WAN-Freigabe nur 443
- Gitea: SSH auf Host-Port 222, keine WAN-Freigabe - Gitea: SSH auf Host-Port 222, keine WAN-Freigabe
- AdGuard: DNS 53 direkt; Admin nur auf Tailscale-IP `100.80.98.33:8082` - AdGuard: DNS 53 direkt; Admin nur auf Tailscale-IP `100.80.98.33:8082`
- Tailscale und Plex: Host-Netz - Tailscale: natives Unraid-Plugin (nicht repo-verwaltet); Plex: Host-Netz
- Scrutiny: privileged - Scrutiny: privileged; Komodo/Periphery: Docker-Socket
- Komodo/Periphery: Docker-Socket-Zugriff
- InfluxDB 3 Core: `127.0.0.1:8181`, Root-User-Ausnahme dokumentiert - InfluxDB 3 Core: `127.0.0.1:8181`, Root-User-Ausnahme dokumentiert
## Aktuelle Restpunkte ## Arbeitsstand
Authoritativ: `docs/MASTER_TODO.md`. - Offene Punkte: `docs/MASTER_TODO.md` (einzige Statusliste)
- Entscheidungen und Begruendungen: `docs/DECISIONS.md`
Kurzfassung: - Belege/Reports: `/mnt/user/backups/restore-reports/` auf dem Host
- Auth-/OIDC-/CrowdSec-/Hermes-Themen bewusst geparkt
- Wochenend-Sprint 2026-06-05: `docs/WEEKEND_EXECUTION_PLAN_2026-06-05.md`
und `docs/WEEKEND_STATUS_2026-06-05.md`
Letzte Bestaetigung:
- Windows-Image `baerchen`: Veeam Agent Free Job `baerchen-c-image` auf
`\\kallilabcore\backups\windows-images\baerchen`, erster Full-Backup-Lauf
2026-06-05 erfolgreich, GUI-Wert 53,8 GB, Dauer 0:11:31. Recovery-USB ist
erstellt; Boot-/SMB-/Restore-Point-Test ohne Restore ist noch offen.
- Veeam Storage Encryption ist beim ersten Full-Lauf nicht aktiv
(`StorageEncryptionEnabled=False`); nachtraegliche Aktivierung ist eine
Operator-Entscheidung, weil sie Passwort- und Restore-Prozess aendert.
- BitLocker fuer `baerchen` ist bewusst nicht aktiviert und bleibt
Operator-Entscheidung.
- Tailscale-Inventar 2026-06-05 real gemessen: `Kallilabcore`
`100.80.98.33`, IPv6 `fd7a:115c:a1e0::2c01:62b2`, kein Exit Node, aber
aktiver Subnet Router fuer `192.168.178.0/24`. Dadurch ist die Tailnet-ACL
sicherheitsrelevant; Entscheidung Default-Allow vs tag-basierte ACL offen.
- Unraid-Flash-Backup-Artefaktpruefung: `ops/maintenance/check-unraid-flash-backup.sh`
prueft Artefakt, SHA256, Alter und Kern-Configs. Test 2026-06-05 gegen Host
erfolgreich laut `docs/MASTER_TODO.md`.
- Borg-Nachlauf 2026-06-01 erfolgreich: Archiv `Taegliche-Sicherung-2026-06-01T04:30:26.913`, Freshness Critical 0 / Warnings 0.
- H:/ Nearline-Pull 2026-06-01 repariert: Borg-Dumps werden kuratiert kopiert, Gitea-Bundles aktuell.
- Family-Status-Dashboard liegt als `monitoring/grafana/dashboards/family-status.json` im Repo.
- Alt-Volumes nach PG18/VectorChord-Burn-in sind seit 2026-06-02 reversibel archiviert unter `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602`; die alten Originalpfade sind nicht mehr aktiv gemountet.
- Family-Onboarding ist auf drei Nutzungsziele fokussiert: Vaultwarden, Immich und Mealie; praktischer Ablauf in `docs/FAMILY_ONBOARDING.md`.
- Externer Betreibercheck: `ops/maintenance/check-external-operator.sh`; FRITZ!Box 7590 meldet FRITZ!OS `154.08.25`, DNS fuer Public Apps hat keine AAAA-Records, Host hat keine globale Provider-IPv6.
- FRITZ!Box-UI 2026-06-01: Remote-HTTPS auf FRITZ!Box-UI aus, FTP/FTPS auf Speichermedien aus, WAN-Freigabe nur `443/tcp`, keine aktive IPv6-Freigabe sichtbar, UPnP-Selbstfreigaben aus.
- FRITZ!Box-Konfig-Backup 2026-06-01 extern/off-system in Vaultwarden abgelegt; Datei und Kennwort bleiben ausserhalb des Repos.
- Hetzner-Account-Hygiene 2026-06-01 erledigt: 2FA aktiv, Recovery Key offline gedruckt, Zahlung ok; Storage Box SSH-only, Maintenance-Key in Vaultwarden. Append-only forced-command brach Key-Auth und wurde per Passwort-Recovery zurueckgesetzt; Operator-Entscheidung: fuer dieses Homelab bewusst nicht umsetzen.
-68
View File
@@ -1,68 +0,0 @@
# Audit-Restliste 2026-05-25
Status: **kompakte Restliste**. Die erledigten Sprint-Tabellen und langen
Audit-Snapshots wurden aus der Arbeitskopie entfernt; Detailhistorie liegt in Git.
Letzter Sync mit `docs/MASTER_TODO.md`: 2026-06-05. Offene Punkte sind deckungsgleich;
neue Restore-Runbook-Stubs (Unraid Flash / AdGuard / Tailscale / Redis 8) wurden
in `docs/RESTORE_MATRIX.md` ergaenzt.
## Aktuell offene Punkte
| Prioritaet | Punkt | Naechster Schritt |
|---|---|---|
| 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
- DR-Workstation Bare-Metal-Kit abgeschlossen (2026-06-06): WSL2 Ubuntu 24.04, SSH/Git, Borg 1.2.8, DR-Key-Arbeitskopien `~/.ssh/dr-readonly` und `~/.ssh/dr-hetzner`, `~/dr-smoke.sh`. Finaler Operator-Smoke erfolgreich: GitHub HEAD `3a263a4...`, Hetzner Storage Box Repos sichtbar (`backup`, `backup2`, `hetzner_borg_appdata`, `hetzner_borg_appdata_critical`), Borg-Repo `hetzner_borg_appdata_critical` gelesen, Repository ID `5dd9b949...`, encrypted `Yes (repokey)`, `DR-Smoke OK (2026-06-06 10:05:30)`. Borg-Passphrase wurde nur interaktiv eingegeben und nicht gespeichert.
- 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.
+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 der Gitea-Bundles aus `/mnt/user/backups/git-bundles/gitea` | identisch | Bundles sind klein und schnell synchronisiert |
| Pull des Unraid-Flash-Artefakts `unraid-flash-config.tar.gz` | bewusst nicht im H:/ Scope | Restore-Quelle bleibt Hetzner-Borg; Flash-Config wie Secret behandeln | | Pull des Unraid-Flash-Artefakts `unraid-flash-config.tar.gz` | bewusst nicht im H:/ Scope | Restore-Quelle bleibt Hetzner-Borg; Flash-Config wie Secret behandeln |
Der konkrete Pull-Pfad ist in `docs/H_DRIVE_NEARLINE_PULL.md` und `ops/h-drive-nearline/pull-critical-backups.ps1` produktiv. Der Windows Scheduled Task `KalliLab H Drive Nearline Pull` laeuft seit 2026-05-28 taeglich 05:30. Der konkrete Pull-Pfad ist in `ops/h-drive-nearline/README.md` und `ops/h-drive-nearline/pull-critical-backups.ps1` produktiv. Der Windows Scheduled Task `KalliLab H Drive Nearline Pull` laeuft seit 2026-05-28 taeglich 05:30.
| Abgrenzung | Bewertung | Begruendung | | Abgrenzung | Bewertung | Begruendung |
|---|---|---| |---|---|---|
+279
View File
@@ -0,0 +1,279 @@
# 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-13 - Wetter-/Langzeitarchiv: HA schreibt nach InfluxDB 3 Core
**Entscheidung:** Home Assistant schreibt die Ecowitt-Sensoren
(`sensor.gw3000a_*`) dauerhaft nach InfluxDB 3 Core (DB `homeassistant`),
visualisiert im Grafana-Dashboard `ha-weather-archive` ("Wetterarchiv
KalliHome"). HA wurde dem bestehenden `monitoring_net` als zusaetzliches Netz
hinzugefuegt und schreibt intern an `monitoring-influxdb3-core:8181`
(v2-Write-API) - kein Host-Port, keine LAN-Exposition. Das war die im
Ecowitt-Eintrag offen gelassene Reachability-Entscheidung (Alternative:
LAN-Bind 8181).
**Kontext:** Gewuenscht war ein echtes Langzeit-Wetterarchiv unabhaengig von
HAs kurzer SQLite-Historie. HAs eingebaute Langzeit-Statistiken decken den Fall
stuendlich bereits ab; InfluxDB liefert volle Aufloesung und eigene Grafana-
Dashboards. InfluxDB 3 Core kennt nur Admin-Tokens (keine feingranularen
Scopes), daher hat der HA-Schreibtoken vollen Admin-Zugriff auf die
Monitoring-InfluxDB - bewusst akzeptiert (Operator-Freigabe), unabhaengig
widerrufbar, Token nur in Appdata-Secrets (`ha_influxdb_token` + HA
`secrets.yaml`).
**Betriebsstand 2026-06-13:** HA im `monitoring_net`, Writer aktiv (Daten in
Measurements `°C`, `%`, `hPa`, `km/h`, `W/m²`, `mm`, `lx`, `°`), zweite
Grafana-Datasource `ha-weather-influx` (DB `homeassistant`) und Dashboard
provisioniert. Glance zeigt zusaetzlich eine Live-Wetterkachel direkt aus der
HA-API (`GLANCE_HA_TOKEN`).
**Review-Trigger:** InfluxDB-3-Enterprise mit Token-Scopes (dann HA-Token
einschraenken), Wegfall des Monitoring-Stacks, oder Neubewertung der
HA-Internet-Exposition (HA haengt jetzt auch im Observability-Netz).
## 2026-06-13 - SolarEdge lokal ueber Modbus TCP angebunden
**Entscheidung:** SolarEdge wird in Home Assistant lokal ueber
`solaredge_modbus_multi` angebunden, nicht ueber die SolarEdge-Cloud-API. Der
Wechselrichter ist im LAN als `192.168.178.111` erreichbar, MAC-OUI
`84:D6:C5` gehoert zu SolarEdge, Modbus TCP laeuft auf Port `1502`, Device-ID
`1`. Die Integration liefert Inverter-, Smart-Meter- und Batterie-Entitaeten.
**Kontext:** Der Operator kann im SolarEdge-Portal keinen API-Key erzeugen; das
fruehere Setup lief bereits lokal. Der alte in der Doku genannte VONETS-Adapter
`192.168.178.71` ist nicht erreichbar und bleibt kein verlaesslicher Zielpfad.
Die native HA-Core-Integration `solaredge` waere Cloud-Polling mit Site-ID/API-
Key; `solaredge_local` erwartet dagegen die lokale HTTP-SetApp-API unter
`/web/v1/status`, die am Wechselrichter nicht offen ist. Der vorhandene
HACS-/Custom-Component-Pfad `solaredge_modbus_multi` v3.2.5 passt zur realen
Schnittstelle und wurde ohne neue Downloads wiederverwendet.
**Betriebsstand 2026-06-13:** Config-Entry `SolarEdge Local` ist `loaded`,
Polling alle 60 Sekunden, Meter- und Batterie-Erkennung aktiv, Extras und
Power-Control-Schreibfunktionen deaktiviert. Relevante Energy-Dashboard-
Kandidaten:
`sensor.solaredge_local_i1_ac_energy`,
`sensor.solaredge_local_i1_m1_ac_energy_imported`,
`sensor.solaredge_local_i1_m1_ac_energy_exported`,
`sensor.solaredge_local_i1_b1_energy_import`,
`sensor.solaredge_local_i1_b1_energy_export`. Nach der Integration wurde ein
HA-native Backup erzeugt:
`/mnt/user/appdata/homeassistant/backups/Custom_backup_2026.6.1_2026-06-13_14.59_48645373.tar`.
Das HA Energy Dashboard wurde anschliessend mit Netz, PV und Speicher aus
SolarEdge Local konfiguriert und per `energy/validate` ohne Issues geprueft.
Kosten/Preise bleiben bis zur Tibber-Anbindung leer. Nach dieser UI-State-
Aenderung wurde ein weiteres HA-native Backup erzeugt:
`/mnt/user/appdata/homeassistant/backups/Custom_backup_2026.6.1_2026-06-13_15.59_25670583.tar`.
**Trade-off:** Die lokale Modbus-Integration passt zum Prinzip "lokal vor
Cloud" und liefert deutlich bessere Betriebsdaten als die Cloud-API, ist aber
eine HACS-/Custom-Integration und damit nicht durch HA-Core getestet. Bei
Problemen zuerst Integration deaktivieren oder auf HA-Core-Cloud-Polling
zurueckfallen, sobald Site-ID/API-Key verfuegbar sind.
**Review-Trigger:** HA-Core-Upgrade mit Custom-Integration-Warnungen,
Ausfaelle von `192.168.178.111:1502`, Wechselrichtertausch/IP-Aenderung,
oder wenn Energie-Automationen schreibende Power-Control-Funktionen brauchen.
## 2026-06-13 - Ecowitt-Ingress: LAN-only Host-Bind 8123 umgesetzt
**Entscheidung:** Home Assistant bekommt einen LAN-only Host-Bind
`192.168.178.58:8123:8123` (nur LAN-IP, nicht `0.0.0.0`/WAN). Das Ecowitt-GW3000
pusht per HTTP direkt an den HA-Webhook. Damit ist die offene
Phase-2-Entscheidung (Eintrag 2026-06-12) zugunsten des LAN-Bind-Fallbacks
entschieden; ein Umbau des globalen Traefik HTTP-zu-HTTPS-Redirects entfaellt,
weil Ecowitt rein im LAN pusht und Traefik gar nicht braucht.
**Kontext:** Der globale `web`->`websecure`-Redirect auf EntryPoint-Ebene laesst
sich nicht sauber selektiv aushebeln. Der LAN-Bind ist analog zur dokumentierten
InfluxDB-8181-Ausnahme, WAN-sicher (FRITZ!Box forwardet nur 443 auf Traefik) und
ohne Traefik-Umbau. Der HA-Webhook ist nicht `local_only`; Schutz ist die
128-bit-Zufalls-Webhook-ID. Restrisiko: der Pfad ist theoretisch auch ueber
Traefik/443 erreichbar, praktisch aber unratbar.
**Review-Trigger:** Wenn der Webhook haerter abgesichert werden soll
(Traefik-IPAllowList auf `/api/webhook/` oder `local_only`), oder bei Ausbau
auf Ecowitt-Langzeitspeicherung in InfluxDB.
## 2026-06-12 - Home Assistant als Container im GitOps-Stack
**Entscheidung:** Home Assistant laeuft neu als `homeassistant` Container im
Stack `smart-home/`, nicht als HAOS-VM und nicht als Supervised-Installation.
Mosquitto laeuft als eigener Container im selben Stack; Zigbee2MQTT und ESPHome
werden spaeter ebenfalls als eigenstaendige Container ergaenzt. HA haengt in
`frontend_net` fuer Traefik und in `smarthome_net` fuer MQTT/Zigbee2MQTT/ESPHome.
Das Fachrepo `smart-home-kalli` liefert versionierte HA-YAML-Dateien read-only;
`.storage`, `secrets.yaml` und Integrations-State bleiben in
`/mnt/user/appdata/homeassistant`.
**Kontext:** Das fruehere HAOS-VM-Setup ging bei einem Crash ohne brauchbares
Backup verloren. Das Homelab betreibt produktive Dienste inzwischen ueber
Gitea, Komodo, Compose, Renovate und Borg. HA Container passt in dieses
Betriebsmodell und vermeidet eine zweite Update-/Backup-Welt. Supervised ist
kein Zielpfad mehr; HAOS bleibt die Alternative, falls Add-on-Komfort,
Matter/Thread/HomeKit-Discovery oder Host-nahe HA-Funktionen wichtiger werden
als GitOps-Konformitaet.
**Betriebsstand 2026-06-13:** Owner-Onboarding ist abgeschlossen, die
temporaere Authelia-Onboarding-Guard-Middleware ist entfernt, `smart-home`
existiert als Komodo-Stack mit Gitea-Webhook, HA-native `backup.create` erzeugt
ein lesbares Backup-Artefakt, und der Mosquitto-Broker besteht einen
authentifizierten Publish/Subscribe-Smoke. Die Restore-Probe wurde am
2026-06-13 erfolgreich abgeschlossen: HA-native Backup + Mosquitto-Appdata +
Fachrepo-Clone wurden isoliert gestartet, HA HTTP/API/check_config waren gruen,
MQTT Publish/Subscribe und retained Topic nach Broker-Restart waren gruen.
Report: `/mnt/user/backups/restore-reports/homeassistant-2026-06-13.md`.
Die HA-MQTT-Integration wurde anschliessend am 2026-06-13 ueber den
Home-Assistant-Config-Flow verbunden; Config-Entry `smarthome-mosquitto` ist
`loaded`, Mosquitto sieht den HA-Client mit User `homeassistant`, und
`check_config` ist gruen. Damit ist die Foundation abgeschlossen. Naechster
Produktivschritt ist Tibber, danach SolarEdge mit bewusster Entscheidung
zwischen schneller Cloud-Integration und lokalem Modbus-TCP.
**Review-Trigger:** Viele mDNS-/SSDP-abhaengige lokale Integrationen
(HomeKit, Cast, Matter/Thread), Bedarf an HA-Add-ons als Betriebsstandard,
oder wiederholte Probleme durch Bridge-Netzwerkbetrieb.
## 2026-06-12 - Ecowitt-Ingress bleibt bewusste Phase-2-Entscheidung
**Entscheidung:** In Phase 1 wird kein Host-Port `8123` fuer Home Assistant
veroeffentlicht. Ecowitt wird spaeter entweder ueber eine gezielte
Traefik-HTTP-Ausnahme fuer den Webhook-Pfad angebunden oder, falls der globale
HTTP-zu-HTTPS-EntryPoint-Redirect nicht sauber selektiv abloesbar ist, ueber
einen dokumentierten LAN-only Host-Port `8123`.
**Kontext:** Ecowitt kann nur HTTP und kein HTTPS. Traefik hat aktuell einen
globalen `web` -> `websecure` Redirect auf EntryPoint-Ebene. Ein normaler
HTTP-Router kann diese Regel voraussichtlich nicht umgehen, ohne Traefik selbst
umzubauen. Deshalb wird die Entscheidung nicht vorgezogen.
**Review-Trigger:** Start der Ecowitt-/InfluxDB-Phase oder Umbau der Traefik
HTTP-Redirect-Architektur.
## 2026-06-11 — Host-DNS-Fallback aktiv (AdGuard-SPOF entschaerft)
**Entscheidung:** Unraid-Host nutzt `eth0` DNS server 1 = `192.168.178.58` (AdGuard) und **DNS server 2 = `192.168.178.1`** (FRITZ!Box) als Failover.
**Kontext:** AdGuard war einziger LAN-Resolver; ein Recreate hat 2026-06 einen Bulk-Deploy zerlegt, weil Docker-Pulls am eigenen DNS-Container scheiterten. Der Fallback bleibt nur passiv aktiv (Go-Resolver springt erst bei Socket-Fehler weiter), der Filter wirkt im Normalbetrieb unveraendert. `options rotate` ist nicht gesetzt. Umsetzung der Empfehlung 3a aus dem Optimierungs-Assessment vom 2026-06-10. Runbook: `docs/runbooks/komodo-bulk-deploy-dns.md`.
**Review-Trigger:** Wenn AdGuard durch eine andere Filter-Loesung ersetzt wird oder ein zweiter Host-Resolver verfuegbar ist.
## 2026-06-11 — Hetzner Storage Box: automatische Snapshots aktiv
**Entscheidung:** Automatische Snapshots auf der Hetzner Storage Box (BX11, `u565255.your-storagebox.de`) sind aktiv: taeglich um 05:30 UTC (nach dem Borg-Lauf 04:30 lokal), Retention 7 Tage, Snapshot-Verzeichnis sichtbar fuer Einzeldatei-Restore via `.zfs/snapshot/`.
**Kontext:** Borg `append-only` ist bewusst nicht umgesetzt (siehe Eintrag 2026-06-01); damit war ein kompromittierter Host bisher in der Lage, auch das Off-site-Backup zu loeschen. Storage-Box-Snapshots sind host-seitig nicht loeschbar und im BX11-Tarif inklusive. Kosten: 0 EUR zusaetzlich. Umsetzung der Empfehlung 2 aus dem Optimierungs-Assessment vom 2026-06-10.
**Review-Trigger:** Hetzner-Quota-Druck (aktuell 65 GB / 1 TB - viel Luft) oder Aenderung der Backup-Strategie.
## 2026-06-11 — Doku-Konsolidierung: ein Fakt, ein Zuhause
**Entscheidung:** Die Dokumentation wird nach `docs/archive/2026/homelab-doku-optimierung-2026-06-11.md` konsolidiert: `MASTER_TODO.md` ist die einzige Statusliste, dieses Register die einzige Entscheidungssammlung, `docs/archive/` nimmt abgeschlossene Snapshots auf, Erledigtes verlaesst die Arbeitskopie. Keine Ordner-Restruktur des Bestands.
**Kontext:** 74 Markdown-Dateien / ~9.400 Zeilen; einzelne Sachverhalte waren an 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).
+7 -8
View File
@@ -8,7 +8,7 @@ Verwandte Dokumente:
- `docs/ROLLBACK.md` - Rueckweg bei Fehlern im laufenden GitOps-Betrieb - `docs/ROLLBACK.md` - Rueckweg bei Fehlern im laufenden GitOps-Betrieb
- `docs/RESTORE_MATRIX.md` - Restore-Quellen und Verifikationsregeln pro Dienst - `docs/RESTORE_MATRIX.md` - Restore-Quellen und Verifikationsregeln pro Dienst
- `docs/RESTORE_HANDBOOK.md` - praktische Restore-Betriebsanleitung - `ops/restore-tests/README.md` - Restore-Test-Betrieb und Werkzeuge
- `docs/SERVICES_RECOVERY.md` - Recovery-kritische `/mnt/user/services`-Pfade, Gitea-Mirror und Komodo-Bootstrap - `docs/SERVICES_RECOVERY.md` - Recovery-kritische `/mnt/user/services`-Pfade, Gitea-Mirror und Komodo-Bootstrap
- `docs/EXTERNAL_DEPENDENCIES.md` - externe Provider/Konten und Ausfall-Szenarien - `docs/EXTERNAL_DEPENDENCIES.md` - externe Provider/Konten und Ausfall-Szenarien
- `ops/borg-ui/BACKUP_SCOPE.md` - Zielbild des Borg-Scopes - `ops/borg-ui/BACKUP_SCOPE.md` - Zielbild des Borg-Scopes
@@ -565,15 +565,14 @@ und physisch ausserhalb des Rechners abgelegt sein.
--- ---
## 11. Offene Vorbereitungs-To-dos ## 11. Laufende Vorbereitung
- Unraid-USB-/Flash-Backup regelmaessig ueber `unraid-flash-config.tar.gz` und optional Unraid Connect pruefen Offene Punkte werden in `docs/MASTER_TODO.md` gefuehrt. Daueraufgaben:
- Borg-Passphrase ist laut Operator-Bestaetigung vom 2026-05-26 extern/offline hinterlegt; bei Reviews nur Existenz/Lesbarkeit der Offline-Kopie pruefen, nie den Wert dokumentieren
- Komodo Stack-ENV-Werte zentral ausserhalb von Komodo dokumentieren - Unraid-Flash-Artefakt regelmaessig pruefen (`ops/maintenance/check-unraid-flash-backup.sh`)
- regelmaessige automatisierte Restore-Smoke-Tests fuer Vaultwarden, Gitea und Paperless etablieren - Offline-Kopien (Borg-Passphrase, KOMODO_*-Notiz, DR-Keys) bei Reviews nur auf Auffindbarkeit pruefen, nie Werte dokumentieren
- `komodo-mongo`-Dump nach Major-Upgrades gezielt kontrollieren - `komodo-mongo`-Dump nach Major-Upgrades gezielt kontrollieren
- `baerchen` Recovery-USB-Boot-/SMB-Test nach erfolgreichem erstem Full-Lauf - Restore-Drills nach Kadenz aus `ops/restore-tests/schedule.md` rotieren
verifizieren
--- ---
+1 -3
View File
@@ -167,9 +167,7 @@ Nach erfolgreicher Einrichtung im Repo dokumentieren. In `docs/EXTERNAL_DEPENDEN
| 2026-06-XX | DR-Workstation produktiv: WSL2 Ubuntu auf Gaming-PC, borgbackup installiert, Hetzner-DR-Key und GitHub-Deploy-Key in ~/.ssh, Quartals-Smoke-Skript ~/dr-smoke.sh. Bare-Metal-DR-Pillars sind damit alle vier produktionsreif. | Quartalsweise Smoke laufen lassen | | 2026-06-XX | DR-Workstation produktiv: WSL2 Ubuntu auf Gaming-PC, borgbackup installiert, Hetzner-DR-Key und GitHub-Deploy-Key in ~/.ssh, Quartals-Smoke-Skript ~/dr-smoke.sh. Bare-Metal-DR-Pillars sind damit alle vier produktionsreif. | Quartalsweise Smoke laufen lassen |
``` ```
Audit-Restliste analog: in `docs/AUDIT_2026-05-25_TODO.md` den P1 "DR-Workstation Bare-Metal-Kit: WSL2 + Borg-Client installieren" auf erledigt setzen und unter "Zuletzt geschlossen" einen Eintrag mit Smoke-Ergebnis machen. Falls der Punkt noch als offen in `docs/MASTER_TODO.md` steht, dort in den Kurzlog uebernehmen.
Wenn ich (Claude) am Tag der Einrichtung mit SSH-Zugang dabei bin, ziehe ich das nach. Sonst per `git add docs/EXTERNAL_DEPENDENCIES.md docs/AUDIT_2026-05-25_TODO.md && git commit && git push`.
--- ---
+1 -10
View File
@@ -96,15 +96,6 @@ Operative Regel: Die DR-Workstation wird nicht als Test-/Spiel-PC betrachtet. WS
| Datum | Ergebnis | Naechste Aktion | | Datum | Ergebnis | Naechste Aktion |
|---|---|---| |---|---|---|
| 2026-05-26 | Bekannte externe Abhaengigkeiten aus Repo-/Betriebsdoku dokumentiert; keine Secret-Werte aufgenommen. Borg-Passphrase ist laut Operator offline gesichert. | Account-Besitz, 2FA-Recovery-Codes und Zahlungswege extern bestaetigen | | 2026-05-26 bis 2026-06-03 | Baseline und Haertung abgeschlossen: externe Abhaengigkeiten dokumentiert; FRITZ!Box-WAN auf 443/tcp bereinigt, Remote-Dienste aus, Konfig-Backup in Vaultwarden; Hetzner-Account-Hygiene (2FA, Recovery Key offline); KOMODO_*-Notiz und GitHub-Read-Deploy-Key offline gesichert. Detailhistorie in Git. | Keine Folgeaktion |
| 2026-05-26 | Telekom-DSL und FRITZ!Box 7590 (damals FRITZ!OS 8.21) als WAN-/Router-Abhaengigkeit aufgenommen; Ausfallschutz nicht eingerichtet | FRITZ!OS-Update am 2026-06-01 als `154.08.25` beobachtet |
| 2026-05-28 | FRITZ!Box-Portfreigaben bereinigt: aktiv bleibt nur `443/tcp`; `80/tcp` entfernt, `222/tcp` bewusst nicht angelegt; UPnP-Recht fuer VONETS-Bridge deaktiviert | IPv6-/Dienste-Review am 2026-06-01 nachgezogen |
| 2026-06-01 | Externer Betreibercheck vorbereitet: `docs/EXTERNAL_OPERATOR_RUNBOOK.md` und `ops/maintenance/check-external-operator.sh`; FRITZ!Box meldet per TR-064 FRITZ!OS `154.08.25`, Public DNS hat keine AAAA-Records, Host hat keine globale Provider-IPv6 | Account-Hygiene am 2026-06-01 nachgezogen |
| 2026-06-01 | FRITZ!Box-UI gegengeprueft und Konfig-Backup extern/off-system in Vaultwarden abgelegt; Remote-HTTPS auf FRITZ!Box-UI aus, FTP/FTPS auf Speichermedien aus, nur `443/tcp -> 192.168.178.58`, keine aktive IPv6-Freigabe sichtbar, UPnP-Selbstfreigaben aus | Bei naechstem Router-Update erneut exportieren |
| 2026-06-01 | Hetzner-Account-Hygiene erledigt: externe Mail ok, Zahlung ok, 2FA aktiv, Recovery Key offline gedruckt. Storage Box: SSH aktiv, SMB/WebDAV aus, Maintenance-Key in Vaultwarden, Borg-Repo-Zugriff nach Recovery geprueft. Borg `append-only` wird bewusst nicht umgesetzt. | Keine Folgeaktion |
| 2026-06-03 | Hetzner Storage Box Maintenance-Key zusaetzlich offline gesichert bestaetigt (Operator-Antwort im DR-Tabletop 2026-06-03). Damit ist der Hetzner-Zugang im Bare-Metal-Fall ohne Vaultwarden moeglich. | Keine Folgeaktion |
| 2026-06-03 | DR-Tabletop ergibt drei offene Bootstrap-Bloecke: KOMODO_*-Notiz nicht offline, GitHub-Mirror-Read-PAT/Deploy-Key nicht angelegt, DR-Workstation nicht als DR-Kit konfiguriert. Details in `docs/DR_DRILL_2026-06-03.md` und Folge-Tasks in `docs/AUDIT_2026-05-25_TODO.md`. | KOMODO_*-Notiz erzeugen, Read-PAT erzeugen, WSL2+Borg auf Gaming-PC einrichten |
| 2026-06-03 | KOMODO_*-Notiz offline gesichert (Operator-Bestaetigung im DR-Tabletop-Followup). Quelle bleibt host-seitige `.env` (`/mnt/user/services/stacks/komodo/.env`) bzw. Drift-Recovery-Kopie vom 2026-05-04. Bare-Metal-Komodo-Bootstrap ist damit ohne Vaultwarden moeglich. | Restliche P1-Operator-Aufgaben: GitHub-Read-PAT, DR-Workstation-Setup, Nextcloud-Restore-Test |
| 2026-06-03 | GitHub-Mirror Read-Only Deploy-Key `DR Read-Only 2026-06-03` (ed25519, Passphrase-frei) erzeugt, in GitHub Repo Settings ohne Write-Access hinterlegt, Smoke `git ls-remote` erfolgreich (`d947c7f` matched master HEAD), Private-Key offline neben KOMODO_*-Notiz abgelegt, Arbeitsplatz-Kopie geloescht. | Restliche P1-Operator-Aufgaben: DR-Workstation-Setup, Nextcloud-Restore-Test |
| 2026-06-03 | Hetzner Storage Box DR-SSH-Key `dr-hetzner-2026-06-03` (ed25519, Passphrase-frei) erzeugt, via `install-ssh-key` auf Storage Box `u565255.your-storagebox.de:23` autorisiert, passwortloser Login erfolgreich (Borg-Repos sichtbar), Private-Key offline neben KOMODO_*-Notiz und GitHub-Deploy-Key abgelegt, Arbeitsplatz-Kopie geloescht. Bare-Metal-Borg-Restore von der DR-Workstation ist damit moeglich, sobald WSL2 + Borg-Client installiert sind. | Restliche P1-Operator-Aufgaben: WSL2 + Borg-Client auf DR-Workstation installieren, Nextcloud-Restore-Test | | 2026-06-03 | Hetzner Storage Box DR-SSH-Key `dr-hetzner-2026-06-03` (ed25519, Passphrase-frei) erzeugt, via `install-ssh-key` auf Storage Box `u565255.your-storagebox.de:23` autorisiert, passwortloser Login erfolgreich (Borg-Repos sichtbar), Private-Key offline neben KOMODO_*-Notiz und GitHub-Deploy-Key abgelegt, Arbeitsplatz-Kopie geloescht. Bare-Metal-Borg-Restore von der DR-Workstation ist damit moeglich, sobald WSL2 + Borg-Client installiert sind. | Restliche P1-Operator-Aufgaben: WSL2 + Borg-Client auf DR-Workstation installieren, Nextcloud-Restore-Test |
| 2026-06-06 | DR-Workstation produktiv: WSL2 Ubuntu 24.04 vorhanden, SSH/Git und Borg 1.2.8 in WSL vorhanden, DR-Key-Arbeitskopien unter `~/.ssh/dr-readonly` und `~/.ssh/dr-hetzner`, GitHub-Read-Smoke und Hetzner-SSH-Smoke erfolgreich, `ops/maintenance/dr-workstation-smoke.sh` nach `~/dr-smoke.sh` kopiert. Finaler Operator-Smoke erfolgreich: GitHub HEAD `3a263a4...`, Hetzner Storage Box Repos sichtbar, Borg-Repo `hetzner_borg_appdata_critical` gelesen, Repository ID `5dd9b949...`, encrypted `Yes (repokey)`, `DR-Smoke OK (2026-06-06 10:05:30)`. | Quartalsweise `bash ~/dr-smoke.sh`; Borg-Passphrase weiterhin nur interaktiv eingeben und nicht speichern | | 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. 1. Einen regulaeren Borg-Lauf abwarten oder manuell starten.
2. `check-external-operator.sh` ausfuehren. 2. `check-external-operator.sh` ausfuehren.
3. In `docs/AUDIT_2026-05-25_TODO.md` nur das Ergebnis dokumentieren. 3. Nur das Ergebnis dokumentieren: Datum/Befund im Review-Log von `docs/EXTERNAL_DEPENDENCIES.md`.
## 4. FRITZ!Box-Servicefenster ## 4. FRITZ!Box-Servicefenster
-131
View File
@@ -1,131 +0,0 @@
# H:/ Nearline Pull
Status: **produktiv** (2026-05-28). Erster echter Lauf 2026-05-27 20:45 erfolgreich. Windows Scheduled Task `KalliLab H Drive Nearline Pull` taeglich 05:30 ist seit 2026-05-28 aktiv.
## Erstlauf-Befund 2026-05-27
- Erster `-WhatIf`-loser Lauf: 18 Borg-Dump-Files erfolgreich gepullt, 4 unraid-flash-config-Files und 10 Gitea-Bundle-Files blockiert (`Zugriff verweigert`).
- Ursache: Bundles wurden mit `chmod 600` geschrieben, Flash-Config bewusst `0600 root:root`, Filebrowser-Dump erbte 0640. Der SMB-Read-Share auf dem Operator-PC liest mit unprivilegierten Rechten, kein root.
- Fixes im selben Sprint:
- `ops/borg-ui/scripts/gitea-bundle-mirror.sh` schreibt Bundles und Sidecars jetzt 0644 (Bundle-Inhalt = Git-Historie, ohne Secrets durch `.gitignore`).
- `ops/borg-ui/scripts/pre-backup-dumps.sh` setzt alle Dumps via `atomic_write` per Default auf 0644; `unraid-flash-config.*` bleibt explizit 0600.
- `ops/h-drive-nearline/pull-critical-backups.ps1` excluded die `unraid-flash-config.*`-Familie ueber `/XF`, damit Flash-Config bewusst nicht in den Nearline-Scope kommt.
- Zweiter Lauf (nach Fixes): beide Robocopy-Jobs Exit-Code 1, **19 Borg-Dumps + 10 Gitea-Bundle-Files** auf H:/.
## Befund 2026-06-01
- Der Scheduled Task um 05:30 kopierte die aktuellen Dumps, brach aber mit Robocopy Exit-Code 8 ab, weil im Dump-Root alte `*-pre-*` Dateien und Migration-/Cutover-Verzeichnisse mit restriktiven Rechten lagen.
- Fix: `ops/h-drive-nearline/pull-critical-backups.ps1` kopiert fuer `borg-dumps-latest` nur noch die kuratierte Pflichtdatei-Liste und schliesst Migration-/Cutover-Verzeichnisse aus.
- Manueller Kontrolllauf 2026-06-01 08:25 erfolgreich: `borg-dumps-latest` Exit-Code 0, `gitea-bundles` Exit-Code 1 (Robocopy-Erfolg mit Kopien), Report `H:\kallilab-nearline-backups\_reports\nearline-pull-2026-06-01-082553.md`.
## Zweck
`H:/` ist eine zweite lokale Nearline-Kopie fuer die wichtigsten Restore-Artefakte. Es ersetzt weder Hetzner/Borg noch ein echtes Off-site-/Airgap-Ziel, reduziert aber das Risiko, dass ein lokaler Restore nur vom Unraid-Array abhaengt.
## Quelle und Ziel
| Zweck | Quelle | Ziel |
|---|---|---|
| Aktuelle kuratierte Dumps ohne Flash-Backup | `\\192.168.178.58\backups\borg\dumps\latest` | `H:\kallilab-nearline-backups\borg-dumps\latest` |
| Gitea-Bundles | `\\192.168.178.58\backups\git-bundles\gitea` | `H:\kallilab-nearline-backups\git-bundles\gitea` |
Das Skript kopiert bewusst **nicht** mit `/MIR` und loescht keine Dateien auf `H:/`. Alte Artefakte duerfen dort erst nach manueller Sichtpruefung geloescht werden.
Der Borg-Dumps-Job ist eine Whitelist der aktuellen Nearline-Pflichtartefakte. Einmalige Migrations-Sicherungen, Pre-Major-Snapshots und Redis-Cutover-Verzeichnisse bleiben ueber Borg/Hetzner abgedeckt, sind aber kein H:/-Nearline-Pflichtbestand.
## Skript
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File G:\Gitea_Clone\homelab-infra\ops\h-drive-nearline\pull-critical-backups.ps1 -WhatIf
```
Echter Lauf:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File G:\Gitea_Clone\homelab-infra\ops\h-drive-nearline\pull-critical-backups.ps1
```
Reports landen unter:
```text
H:\kallilab-nearline-backups\_reports
```
Robocopy-Logs landen unter:
```text
H:\kallilab-nearline-backups\_logs
```
## Geplanter Schedule
Empfohlen: taeglich 05:30 Uhr, nach dem Borg-Dump-Fenster um ca. 04:00 Uhr.
Aktiv seit 2026-05-28. Tatsaechlicher Register-Befehl (RunLevel-Enum-Wert ist `Limited`, nicht `LeastPrivilege`):
```powershell
$Action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -File `"G:\Gitea_Clone\homelab-infra\ops\h-drive-nearline\pull-critical-backups.ps1`""
$Trigger = New-ScheduledTaskTrigger -Daily -At 05:30
$Settings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-StartWhenAvailable `
-ExecutionTimeLimit (New-TimeSpan -Hours 2)
Register-ScheduledTask `
-TaskName "KalliLab H Drive Nearline Pull" `
-Action $Action `
-Trigger $Trigger `
-Settings $Settings `
-Description "Copies critical KalliLab restore artifacts from Unraid SMB backup share to H:/ nearline disk." `
-RunLevel Limited
```
Status pruefen:
```powershell
Get-ScheduledTask -TaskName "KalliLab H Drive Nearline Pull" | Format-List TaskName, State
Get-ScheduledTaskInfo -TaskName "KalliLab H Drive Nearline Pull" | Format-List LastRunTime, LastTaskResult, NextRunTime, NumberOfMissedRuns
```
Manueller Trigger zum Testen:
```powershell
Start-ScheduledTask -TaskName "KalliLab H Drive Nearline Pull"
```
Verhalten:
- Laeuft als angemeldeter User (`RunLevel Limited`); wenn der PC abgemeldet ist, wartet der Task bis zur naechsten Anmeldung (`StartWhenAvailable`).
- Akku-Modus blockiert nicht (`AllowStartIfOnBatteries`).
- Maximale Laufzeit 2 h, danach wird der Task abgebrochen.
## Erfolgscheck
Nach einem echten Lauf muessen mindestens diese Artefakte unter `H:\kallilab-nearline-backups` liegen:
- `borg-dumps\latest\immich.dump`
- `borg-dumps\latest\komodo-mongo.archive.gz`
- `borg-dumps\latest\postgresql17-paperless.dump`
- `borg-dumps\latest\postgresql17-mailarchiver.dump`
- `borg-dumps\latest\nextcloud.dump`
- `borg-dumps\latest\mealie.dump`
- `borg-dumps\latest\gitea.sqlite.dump`
- `borg-dumps\latest\vaultwarden.sqlite.dump`
- `git-bundles\gitea\latest-report.md`
- `git-bundles\gitea\micha\*.bundle`
Bewusst **nicht** im Nearline-Scope:
- `unraid-flash-config.tar.gz` (hostseitig 0600 root:root; Restore-Quelle bleibt das Hetzner-Borg-Repo, siehe `docs/RESTORE_MATRIX.md` Tier 1 Unraid OS Flash).
## Schutzregeln
- Kein CIFS-/SMB-Hard-Mount von `H:/` auf Unraid.
- Kein Borg-Repo direkt auf `H:/` ueber SMB.
- Kein `/MIR` und kein automatisches Loeschen auf `H:/`.
- Flash-Backup wie Secret behandeln; `H:/` bleibt lokaler Operator-Datentraeger.
+45 -70
View File
@@ -1,112 +1,87 @@
# Master To-do - KalliLab CORE # Master To-do - KalliLab CORE
Stand: 2026-06-06 (Wochenend-Sprint, nach Status-Kategorien sortiert) Typ: Status/To-do · Stand: 2026-06-12 · Status: aktiv
Diese Liste ist die zentrale Arbeitsliste fuer offene operative Punkte im Diese Liste ist die **einzige** Arbeitsliste fuer offene operative Punkte im
Homelab. Detailentscheidungen bleiben in den verlinkten Runbooks; diese Datei Homelab. Detailablaeufe stehen in den verlinkten Runbooks; Entscheidungen mit
haelt Status, naechsten konkreten Schritt und Quelle zusammen. Begruendung stehen in `docs/DECISIONS.md`; Belege fuer Erledigtes liegen in
Host-Reports (`/mnt/user/backups/restore-reports/`) und in der Git-Historie.
## Status-Kategorien ## Status-Kategorien
- **Aktiv dieses Wochenende** - soll jetzt vorankommen (Claude, Codex oder Operator); konkreter naechster Schritt steht. - **Aktiv** - soll vorankommen; konkreter naechster Schritt steht.
- **Operator-Entscheidung** - wartet auf eine bewusste Entscheidung des Betreibers (ja/nein/welche Option). - **Operator-Entscheidung** - wartet auf eine bewusste Entscheidung (ja/nein/Option).
- **Geparkt** - bewusst nicht jetzt, mit klarem Review-Trigger. - **Geparkt** - bewusst nicht jetzt, mit klarem Review-Trigger.
- **Extern blockiert** - wartet auf ein externes Ereignis oder eine Abhaengigkeit (Nachtlauf, zweite Hardware, Geraetebeschaffung). - **Extern blockiert** - wartet auf ein externes Ereignis oder eine Abhaengigkeit.
Owner-Aufteilung fuer das Wochenende: `baerchen`/Veeam/Backup-Verifikation liegt
bei **Codex**; Doku-/Inventar-/Onboarding-Arbeit liegt bei **Claude**;
Host-/Entscheidungsaufgaben beim **Operator**.
--- ---
## Aktiv dieses Wochenende ## Aktiv
| Thema | Owner | Naechster konkreter Schritt | Quelle | | Thema | Owner | Naechster konkreter Schritt | Quelle |
|---|---|---|---| |---|---|---|---|
| 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` | | 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 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 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 | 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 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 | **Aktive Phase abgeschlossen 2026-06-06.** Live: Grafana (admin, Login verifiziert) + Mealie (family, verifiziert) + Paperless (family, deployed; Login-Test offen). Muster + Gotchas in `docs/AUTHELIA_OIDC_PLAN.md`. **Immich + Nextcloud bewusst GEPARKT bis Onboarding** (Entscheidung 2026-06-06): nur `micha` hat Authelia-Account, Familien-SSO-Nutzen + UI/occ-Aufwand lohnen erst mit Familien-Accounts. Runbook bereit | `docs/AUTHELIA_OIDC_PLAN.md`, `security/authelia/configuration.yml` | | 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 Tibber | Operator/Codex | Tibber per HA-UI-Config-Flow verbinden. Danach Energy-Dashboard um echte Kosten/Preisquelle ergaenzen; SolarEdge-PV, Netz und Speicher sind bereits konfiguriert und validiert | `docs/runbooks/smart-home-bootstrap.md`, `docs/DECISIONS.md` |
| 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` |
--- ---
## Operator-Entscheidung ## Operator-Entscheidung
**Stand 2026-06-06: keine offenen Operator-Entscheidungen.** Alle am 2026-06-06 **Stand 2026-06-11: keine offenen Operator-Entscheidungen.**
entschieden — Ergebnisse in "Aktiv", "Geparkt" bzw. "Entschieden 2026-06-06". Getroffene Entscheidungen mit Begruendung und Review-Trigger: `docs/DECISIONS.md`.
--- ---
## Geparkt ## Geparkt
Bewusst nicht jetzt - mit Review-Trigger. Bewusst nicht jetzt - Begruendungen in `docs/DECISIONS.md`, hier nur Thema und Trigger.
| Thema | Entscheidung / Trigger | Quelle | | Thema | Review-Trigger | Quelle |
|---|---|---| |---|---|---|
| USV-Anschaffung | **Auf Q3/2026 geparkt** (2026-06-05). Power-Loss bleibt akzeptiertes Risiko. Trigger: Hardware-Upgrade, realer Stromausfall mit Datenfolge, oder Q3-Review ab 2026-07-01 | `docs/HARDWARE_INVENTORY.md` | | USV-Anschaffung | Q3-Review ab 2026-07-01, Hardware-Upgrade oder realer Stromausfall mit Datenfolge | `docs/DECISIONS.md` |
| Cold-Backup-Rotation | **Bewusst Hetzner-only** (2026-06-05). Keine zweite rotierende Cold-Kopie. Trigger: stark wachsender Datenwert, wiederholte Hetzner-Probleme, geaenderte Praeferenz | `docs/HARDWARE_INVENTORY.md` | | Cold-Backup-Rotation (zweites Off-site-Ziel) | Hetzner-Probleme, stark wachsender Datenwert oder geaenderte Praeferenz | `docs/DECISIONS.md` |
| WAN-Ausfallschutz | **Spaeter evaluieren** (2026-06-05). Mobilfunk-Failover inaktiv; lokale Apps laufen bei WAN-Ausfall weiter. Trigger: haeufigere/laengere DSL-Ausfaelle oder kritischer Remote-Zugang | `docs/NETWORK_INVENTORY.md` | | WAN-Ausfallschutz | haeufigere/laengere DSL-Ausfaelle oder kritischer Remote-Zugang | `docs/NETWORK_INVENTORY.md` |
| 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` | | Borg `append-only` auf Hetzner | robusterer Hetzner-Mechanismus oder geaendertes Ransomware-Risikoprofil | `docs/DECISIONS.md` |
| Negativ-Test Backup-Frische | **Validiert 2026-06-06:** `ops/restore-tests/negative-freshness-alert-test.sh` simuliert fehlende Dumps nur in einem synthetischen Restore-Lab-Pfad und sendet einen Test-Alert nach `homelab-alerts`; Host-Lauf schrieb Report `/mnt/user/backups/restore-reports/freshness-negative-2026-06-06-130320.md` (10 Criticals, produktive Dumps unangetastet). Quartalsweise wiederholen: `ops/restore-tests/run-restore-checks.sh freshness-negative` | `ops/restore-tests/README.md`, `docs/AUDIT_2026-05-25_TODO.md` | | CrowdSec vor Traefik | breitere Attack Surface als nur `443/tcp` | `docs/DECISIONS.md` |
| End-to-end-DR-Drill | Komplett-Bootstrap Phase 1-5 auf Wegwerf-Host; realistisch erst mit zweiter Hardware (siehe auch Extern blockiert) | `docs/AUDIT_2026-05-25_TODO.md`, `docs/DISASTER_RECOVERY.md` | | Nextcloud 2FA (Operator-TOTP) | OIDC-/SSO-Block erreicht die App-Login-Ebene | `docs/DECISIONS.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` | | Hermes-Agent | Review-Deadline 2026-07-25; NAS-Stack bleibt deaktiviert | `docs/SERVICE_CATALOG.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` | | 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` |
| Nextcloud 2FA (Operator-TOTP) | **Geparkt (Entscheidung 2026-06-06):** Operator-TOTP fuer Nextcloud erst zusammen mit der app-weiten Familien-/OIDC-Policy entscheiden. Trigger: OIDC-/SSO-Block (jetzt aktiv) erreicht die App-Login-Ebene | `docs/AUDIT_2026-05-25_TODO.md` | | Dedizierter SMB-User `veeam-baerchen` | nur wenn Unraid-User-/Share-Rechte bewusst angefasst werden | `ops/windows-reinstall/docs/windows-image-backup-baseline.md` |
| Tailnet-Konsole aufraeumen (Rest) | Nach Docker-Stack-Abbau (2026-06-06) nur noch tote Node-Eintraege: `kallilab-core` (down) und alter Offline-`baerchen` in der Tailscale-Admin-Konsole entfernen. Optional State-Pfad `/mnt/user/appdata/tailscale` nach `_archive/`. Trivial, kein Risiko | `docs/NETWORK_INVENTORY.md` | | Filebrowser-Mount-Scope | naechster Hardening-Sprint | `docs/SERVICE_CATALOG.md` |
| CrowdSec vor Traefik | Bewusst nicht umgesetzt; einzige WAN-Tuer ist `443/tcp`, Authelia `regulation:` deckt Brute-Force ab. Neu bewerten bei breiterer Attack Surface | `docs/AUDIT_2026-05-25_TODO.md` | | Scrutiny Privileged-Ausnahme | nur mit klarer Begruendung aendern | `docs/SERVICE_CATALOG.md` |
| Hermes-Agent | NAS-Stack bleibt deaktiviert; Review-Deadline 2026-07-25 | `docs/AUDIT_2026-05-25_TODO.md`, `docs/SERVICE_CATALOG.md` | | Immich Redis named volume | passende Wartung am Immich-Stack | `docs/SERVICE_CATALOG.md` |
| Filebrowser-Mounts | Bei zukuenftigem Hardening-Sprint Mount-Scope reduzieren | `docs/SERVICE_CATALOG.md` | | Storage-Wachstum (zweite NVMe, zweite Array-Disk, ZFS/BTRFS) | Trigger aus Capacity-Doku | `docs/STORAGE_LAYOUT.md`, `docs/CAPACITY_AND_LIFECYCLE.md` |
| Scrutiny Privileged-Ausnahme | Nur mit klarer Begruendung aendern; sonst dokumentierte Ausnahme beibehalten | `docs/SERVICE_CATALOG.md` | | Wiederkehrende Restore-Drills | laufend nach Kadenz, inkl. quartalsweisem Frische-Negativtest (`run-restore-checks.sh freshness-negative`) | `docs/RESTORE_MATRIX.md`, `ops/restore-tests/schedule.md` |
| Immich Redis named volume | Anonymes Volume bei passender Wartung auf named volume umstellen oder Ausnahme dokumentieren | `docs/SERVICE_CATALOG.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 |
| 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 ## Extern blockiert
Wartet auf ein externes Ereignis oder eine Abhaengigkeit.
| Thema | Blockiert durch | Naechster Schritt sobald entblockt | Quelle | | Thema | Blockiert durch | Naechster Schritt sobald entblockt | Quelle |
|---|---|---|---| |---|---|---|---|
| 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` | | End-to-end-DR-Drill | Keine zweite Wegwerf-Hardware verfuegbar | Komplett-Bootstrap Phase 1-5 fahren | `docs/DISASTER_RECOVERY.md` |
--- ---
## Erledigt im Wochenend-Sprint (2026-06-05) ## Zuletzt erledigt (Kurzlog, max. 5 Eintraege)
- Restore-Matrix "Naechste Restore-Test-Kandidaten" bereinigt: 5 am 2026-06-03 abgeschlossene Kandidaten entfernt, durch die 4 real offenen Pfade ersetzt; Stand-Datum aktualisiert. - **2026-06-13** Home Assistant MQTT-Integration produktiv verbunden: Config-Entry `smarthome-mosquitto` ist `loaded`, Mosquitto sieht den HA-Client `homeassistant`; `check_config` gruen.
- Restore-Test-Runbook-Stubs fuer Unraid Flash / AdGuard / Tailscale / Redis 8 in `docs/RESTORE_MATRIX.md` ergaenzt. - **2026-06-13** HA Energy Dashboard konfiguriert: Netz, PV und Speicher aus SolarEdge Local gesetzt, `energy/validate` ohne Issues; HA-Backup danach erzeugt.
- Alte Windows-Doku bereinigt: WinRE-/Admin-Check-To-dos in `boot-cleanup-plan-2026-06-04.md` und `laufwerks-neustruktur-2026-06-04.md` als erledigt markiert. - **2026-06-13** SolarEdge lokal angebunden: `solaredge_modbus_multi` v3.2.5 ueber `192.168.178.111:1502`, Device-ID `1`; 68 Entitaeten inkl. Inverter, Smart Meter und Batterie; HA-Backup danach erzeugt.
- `docs/HARDWARE_INVENTORY.md`: USV (Q3-Park), Cold-Backup (Hetzner-only) und Stromverbrauch von diffusen TBDs auf bewusste Entscheidungen mit Review-Triggern gehoben. - **2026-06-13** Home Assistant Restore-Probe erfolgreich: isolierter Test aus HA-native Backup + Mosquitto-Appdata + Fachrepo-Clone, HA HTTP/API/check_config gruen, MQTT Publish/Subscribe und retained Topic nach Broker-Restart gruen. Report: `/mnt/user/backups/restore-reports/homeassistant-2026-06-13.md`.
- `docs/NETWORK_INVENTORY.md`: Tailscale-Inventar am 2026-06-05 **real per read-only SSH gemessen** und eingetragen: IPv6 `fd7a:115c:a1e0::2c01:62b2`, Exit Node `nein`, **Subnet-Router fuer `192.168.178.0/24` aktiv** (widerlegt fruehere Vermutung), Tailnet `taild9fcf2.ts.net`, Geraete-Snapshot + Dubletten-Hinweis. WAN-Failover und Gast-/IoT geschaerft. `zu messen`-Platzhalter entfernt. **`Tailscale-Inventar messen` damit geschlossen.** - **2026-06-13** Home Assistant Foundation live: `smart-home` in Komodo angelegt, Gitea-Webhook aktiv, Authelia-Onboarding-Guard entfernt, HA-native Auth + Login-Ban aktiv, HA-Backup erzeugt/geprueft und MQTT-Broker-Smoke erfolgreich.
- `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 am 2026-06-06 erfolgreich beim Operator angekommen.
- Restore-Test AdGuard Home: automatisierter Test `ops/restore-tests/adguard-restore-test.sh` erstellt und am 2026-06-06 auf Unraid erfolgreich ausgefuehrt. Ergebnis: Borg-Config-Restore aus Archiv `Taegliche-Sicherung-2026-06-06T04:30:05.910`, isolierter Container `restoretest-adguard`, HTTP `/control/status` = `401`, DNS-Smoke `git.kaleschke.info -> 192.168.178.58`, 7 Filterlisten-Eintraege, Report `/mnt/user/backups/restore-reports/adguard-2026-06-06.md`.
- Restore-Test Redis 8: automatisierter Test `ops/restore-tests/redis-restore-test.sh` erstellt und am 2026-06-06 auf Unraid erfolgreich ausgefuehrt. Ergebnis: Restore aus `/mnt/user/backups/borg/dumps/latest/shared-redis-pre-redis8-20260531-185011`, isolierter Container `restoretest-redis`, `PING` = `PONG`, Redis `8.8.0`, AOF `1`, `DBSIZE` = `1`, Report `/mnt/user/backups/restore-reports/redis-2026-06-06.md`.
- **Tailscale ACL-Policy restriktiv ausgerollt (2026-06-06):** Von Default-Allow auf Tag-basierte `grants`-Policy umgestellt, gemeinsam mit dem Operator in lockout-sicherer Reihenfolge (additiv -> taggen -> Allow-all entfernen), jeder Schritt read-only per SSH verifiziert. Live: `kallilabcore`=`tag:server`, `baerchen-1`+`iphone-14`=`tag:operator`, `tag:family` vorbereitet/schlafend. Subnet-Route `192.168.178.0/24` bleibt via `autoApprovers` approved. Smoke-Tests gruen (Operator-SSH, AdGuard-Admin `HTTP 302` ueber Tailnet, Ping 0%); untagged Nodes (`kallilab-core` Docker-Sidecar, alter `baerchen`) isoliert. Beleg: `docs/NETWORK_INVENTORY.md` Abschnitt "ACL-Policy — ANGEWENDET 2026-06-06". Familien-Dienste konkretisieren bei erstem realem Familiengeraet.
- **Redundanten Docker-Tailscale-Stack entfernt (2026-06-06):** Befund: Host hatte zwei `tailscaled` — die funktionale native Plugin-Instanz `kallilabcore` (echtes TUN `tailscale1`, Subnet-Router, State im Flash-Backup) und den redundanten userspace-only Docker-Stack `kallilab-core` (`host-services/tailscale/`, routet nichts, nichts haengt dran). Sauber per GitOps abgebaut: Operator hat Komodo-Stack `tailscale` gestoppt+destroyed; danach `git rm host-services/tailscale/`, Glance-Widget entfernt, Architektur-/Service-Catalog-/DR-Bootstrap-/CLAUDE-/Restore-Matrix-/Netzwerk-Doku auf "natives Plugin" nachgezogen. Read-only verifiziert: Container weg, nur noch der native `tailscaled`, Subnet-Route + Operator-Zugriff intakt. Rest: tote Node-Eintraege in der Admin-Konsole entfernen (eigener Todo).
- DR-Workstation Bare-Metal-Kit abgeschlossen: WSL2 Ubuntu 24.04 auf `baerchen`, Borg 1.2.8, GitHub-Read-DR-Key und Hetzner-DR-Key in WSL, `~/dr-smoke.sh` vorhanden. Finaler Smoke 2026-06-06 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)`. Passphrase wurde nur interaktiv eingegeben und nicht gespeichert.
- Restore-Frische-Negativtest validiert: `ops/restore-tests/negative-freshness-alert-test.sh` erstellt und am 2026-06-06 auf Unraid erfolgreich ausgefuehrt. Ergebnis: synthetischer leerer Dump-Pfad erzeugte erwartungsgemaess 10 Criticals, Test-Alert nach `homelab-alerts` gesendet, Report `/mnt/user/backups/restore-reports/freshness-negative-2026-06-06-130320.md`, produktive Dumps unangetastet.
- Gast-/IoT-Netz aktiviert und validiert: FRITZ!Box-Gastzugang `Fritzi Gastzugang` aktiv, Heimnetz-Zugriff aus dem Gastnetz blockiert. LAN- und Host-Preflight gruen; iPhone-Smoke aus dem Gast-WLAN bestaetigt, dass `192.168.178.58:8082`, `:8181`, `:222`, `https://192.168.178.58` und `192.168.178.1` nicht erreichbar sind. Runbook: `docs/GUEST_IOT_NETWORK.md`.
- `baerchen` Veeam-Recovery-Test ohne echten Restore abgeschlossen: Recovery-USB `VEEAMRE` bootet, SMB-Ziel `\\kallilabcore\backups\windows-images\baerchen` ist in der Recovery Environment erreichbar, Restore Point wird angezeigt, Test vor echtem Restore abgebrochen. Runbook: `ops/windows-reinstall/docs/windows-image-backup-baseline.md`.
- **Operator-Entscheidungen 2026-06-06 abgeschlossen** (Liste damit ohne offene Entscheidungen):
- **BitLocker `baerchen`: bewusst deaktiviert.** Recovery laeuft ueber Veeam-Image; kein BitLocker-Key-Management. Restrisiko physischer Diebstahl bewusst akzeptiert.
- **Veeam Storage Encryption: bewusst unverschluesselt.** Erster Full-Lauf bleibt; Image liegt auf dem lokalen SMB-Share `\\kallilabcore\backups`. Neu bewerten bei Off-host-Auslagerung des Images.
- **Stromverbrauch: bewusst ohne Messung.** Kein Messgeraet; Werte bleiben dauerhaft offen, kein Beschaffungs-Todo mehr.
- **Authelia Rest-2FA: KOMPLETT erledigt 2026-06-06.** Catch-all `*.kaleschke.info` -> `two_factor` in Repo **und** Host-Config (chirurgische Einzelzeilen-Aenderung mit Backup, OIDC-Beszel-Client + Secret unangetastet), `docker restart authelia` -> healthy + "Startup complete", Operator-2FA-Login auf einer vorher-1FA-Domain verifiziert. Nebenbei vorbestehenden Drift gefunden+bereinigt (Host-Config war vom 25. Mai, borg/code nie gemerged); Repo-Baseline an Host-Endzustand angeglichen, damit `authelia-diff.sh` clean wird sobald der Host-Mirror nachzieht. Rollback-`.bak` auf dem Host vorhanden.
- **Authelia OIDC: angehen** (neuer aktiver Block) — **Gast-/IoT-Netz: einrichten/planen** (neuer aktiver Block) — **Nextcloud 2FA: geparkt** bis OIDC die App-Login-Ebene erreicht.
--- ---
## Pflege-Regel ## Pflege-Regel
- Neue operative To-dos zuerst hier eintragen oder aus Detaildokumenten hierher uebernehmen, immer mit Status-Kategorie. - Neue operative To-dos zuerst hier eintragen, immer mit Status-Kategorie.
- Wenn ein Punkt erledigt ist, in der Detaildoku den Beleg/Report eintragen und diese Liste aktualisieren. - Erledigt: Beleg liegt im Host-Report bzw. Commit; hier nur ein Kurzlog-Eintrag (max. 3 Zeilen), aelteste Eintraege fliegen raus, sobald mehr als 5.
- Entscheidungen (auch "bewusst nein") gehoeren mit Begruendung nach `docs/DECISIONS.md`, hier nur Thema + Trigger.
- Keine vagen "pruefen"-Eintraege ohne Kommando oder Entscheidung. - Keine vagen "pruefen"-Eintraege ohne Kommando oder Entscheidung.
- Historische Drill-Reports bleiben Belegmaterial, aber nicht die fuehrende Arbeitsliste.
+28 -17
View File
@@ -1,29 +1,38 @@
# Documentation Index # Documentation Index
Stand: 2026-06-05 Typ: Einstieg/Index · Stand: 2026-06-11 · Status: aktiv
Diese Datei trennt aktive Betriebsdokumentation von historischer Arbeitsdoku. Neue operative Dokumente duerfen nur in `docs/` liegen, wenn sie heute als Einstieg, Runbook, Inventar oder offene Arbeitsliste gebraucht werden. Erledigte Audits, Chat-Handoffs, Prompt-Dateien und abgeschlossene Plaene bleiben in der Git-Historie, aber nicht als dauerhafte Arbeitskopie. Diese Datei trennt aktive Betriebsdokumentation von historischer Arbeitsdoku.
Neue operative Dokumente duerfen nur in `docs/` liegen, wenn sie heute als
Einstieg, Runbook, Inventar, Entscheidung oder Statusliste gebraucht werden.
Abgeschlossene Audits, Drills und Plaene wandern nach `archive/` oder werden
geloescht (Git-Historie ist das Archiv). Verbindliche Doku-Regeln:
`REPO_MAP.md` Abschnitt "Doku-Regeln".
## Pflicht-Einstieg ## Pflicht-Einstieg
| Datei | Zweck | | Datei | Zweck |
|---|---| |---|---|
| `../README.md` | kurzer Repo-Einstieg | | `../README.md` | kurzer Repo-Einstieg |
| `../AGENTS.md` | Einstiegspunkt fuer KI-Agenten (Codex u. a.) |
| `../HOMELAB_ARCHITECTURE_MASTER_V2.md` | Architektur-Quelle fuer Netz, Zugriff und Ausnahmen | | `../HOMELAB_ARCHITECTURE_MASTER_V2.md` | Architektur-Quelle fuer Netz, Zugriff und Ausnahmen |
| `WORKFLOW.md` | verbindlicher GitOps-/No-Drift-Ablauf | | `WORKFLOW.md` | verbindlicher GitOps-/No-Drift-Ablauf |
| `REPO_MAP.md` | technische Landkarte des Repositories | | `REPO_MAP.md` | technische Landkarte des Repositories + Doku-Regeln |
| `SERVICE_CATALOG.md` | produktiver Service-Katalog | | `SERVICE_CATALOG.md` | produktiver Service-Katalog |
| `DECISIONS.md` | Entscheidungs-Register (ADR-light) |
| `MASTER_TODO.md` | einzige operative Statusliste |
## Betrieb und Recovery ## Betrieb und Recovery
| Datei | Zweck | | Datei | Zweck |
|---|---| |---|---|
| `DISASTER_RECOVERY.md` | Wiederanlauf nach Host-/Systemausfall | | `DISASTER_RECOVERY.md` | Wiederanlauf nach Host-/Systemausfall |
| `RESTORE_MATRIX.md` | Restore-Quellen, Dumps, Secrets und Smoke-Tests je Dienst | | `RESTORE_MATRIX.md` | Restore-Quellen, Dumps, Secrets, Smoke-Tests und Test-Reifegrad je Dienst |
| `RESTORE_HANDBOOK.md` | praktische Restore-Anleitung |
| `SERVICES_RECOVERY.md` | Gitea-/Komodo-/Services-Bootstrap | | `SERVICES_RECOVERY.md` | Gitea-/Komodo-/Services-Bootstrap |
| `ROLLBACK.md` | Rueckweg bei GitOps-/Deploy-Fehlern | | `ROLLBACK.md` | Rueckweg bei GitOps-/Deploy-Fehlern |
| `GITOPS_DRIFT_RUNBOOK.md` | Pflichtmatrix bei Drift zwischen Git, Komodo, Docker und Host | | `GITOPS_DRIFT_RUNBOOK.md` | Pflichtmatrix bei Drift zwischen Git, Komodo, Docker und Host |
| `DR_WORKSTATION_SETUP.md` | DR-Gaming-PC einrichten (WSL2 + Borg-Client + SSH-Keys) |
| `../ops/restore-tests/README.md` | Restore-Test-Betrieb, Skripte und Kadenz |
## Inventare und Policies ## Inventare und Policies
@@ -32,12 +41,12 @@ Diese Datei trennt aktive Betriebsdokumentation von historischer Arbeitsdoku. Ne
| `STORAGE_LAYOUT.md` | verbindliche Storage-/Share-/Pfad-Regeln | | `STORAGE_LAYOUT.md` | verbindliche Storage-/Share-/Pfad-Regeln |
| `SECRETS_MAP.md` | Secret-Namen, Speicherorte und Einbindungsarten ohne Werte | | `SECRETS_MAP.md` | Secret-Namen, Speicherorte und Einbindungsarten ohne Werte |
| `AUTHELIA_OIDC_PLAN.md` | Plan & Runbook fuer app-uebergreifendes SSO via Authelia OIDC | | `AUTHELIA_OIDC_PLAN.md` | Plan & Runbook fuer app-uebergreifendes SSO via Authelia OIDC |
| `HARDWARE_INVENTORY.md` | Host-, Disk-, SMART-, USV- und Power-Baseline | | `HARDWARE_INVENTORY.md` | Host-, Disk-, SMART- und Power-Baseline |
| `NETWORK_INVENTORY.md` | Router, DNS, Tailscale, Portfreigaben und Netzthemen | | `NETWORK_INVENTORY.md` | Router, DNS, Tailscale, Portfreigaben und Netzthemen |
| `GUEST_IOT_NETWORK.md` | Sicherer Ablauf fuer FRITZ!Box-Gastnetz / IoT-Isolation | | `GUEST_IOT_NETWORK.md` | Sicherer Ablauf fuer FRITZ!Box-Gastnetz / IoT-Isolation |
| `EXTERNAL_DEPENDENCIES.md` | Provider, Konten und externe Abhaengigkeiten | | `EXTERNAL_DEPENDENCIES.md` | Provider, Konten, DR-Workstation-Kit und externe Abhaengigkeiten |
| `EXTERNAL_OPERATOR_RUNBOOK.md` | Hetzner-/Borg-/FRITZ!Box-Betreibercheck | | `EXTERNAL_OPERATOR_RUNBOOK.md` | Hetzner-/Borg-/FRITZ!Box-Betreibercheck |
| `CAPACITY_AND_LIFECYCLE.md` | Kapazitaet, Wachstum und Upgrade-Trigger | | `CAPACITY_AND_LIFECYCLE.md` | Kapazitaet, Wachstum, Upgrade-Trigger, H:/-Nearline-Einordnung |
## Monitoring und Automatisierung ## Monitoring und Automatisierung
@@ -45,18 +54,20 @@ Diese Datei trennt aktive Betriebsdokumentation von historischer Arbeitsdoku. Ne
|---|---| |---|---|
| `ALERT_RULES.md` | Prometheus-/ntfy-Regeln und Handlungslogik | | `ALERT_RULES.md` | Prometheus-/ntfy-Regeln und Handlungslogik |
| `RENOVATE.md` | Self-hosted Renovate gegen Gitea | | `RENOVATE.md` | Self-hosted Renovate gegen Gitea |
| `HOME_ASSISTANT_INFLUXDB_ECOWITT.md` | Archivierter Entwurf: Home Assistant -> InfluxDB 3 -> Grafana; nicht aktiv seit Crash | | `runbooks/komodo-bulk-deploy-dns.md` | Bulk-Deploy-Pulls scheitern an DNS bei AdGuard-Recreate |
| `H_DRIVE_NEARLINE_PULL.md` | Windows-H:/ Nearline-Pull fuer kritische Restore-Artefakte | | `../ops/h-drive-nearline/README.md` | Windows-H:/ Nearline-Pull fuer kritische Restore-Artefakte |
## Nutzer- und Planungsdoku ## Nutzer- und Statusdoku
| Datei | Zweck | | Datei | Zweck |
|---|---| |---|---|
| `FAMILY_ONBOARDING.md` | familienverstaendliche Nutzungsdoku | | `FAMILY_ONBOARDING.md` | familienverstaendliche Nutzungsdoku |
| `AUDIT_2026-05-25_TODO.md` | kompakte Restliste aus dem Audit-Zyklus | | `AI_CONTEXT.md` | kompakter Kontext fuer KI-Agenten (Regeln + Pointer, kein Status) |
| `MASTER_TODO.md` | zentrale operative Master-To-do-Liste ueber alle Bereiche | | `homelab-optimierung.md` | technisches Optimierungs-Assessment 2026-06-10 (offene Empfehlungen) |
| `WEEKEND_EXECUTION_PLAN_2026-06-05.md` | Owner-Aufteilung und Wochenendplan fuer Todo-Abschluss |
| `WEEKEND_STATUS_2026-06-05.md` | kurzlebiges Arbeitsboard fuer den laufenden Wochenend-Sprint |
| `AI_CONTEXT.md` | kompakter Kontext fuer KI-Agenten |
Windows-Neuaufsetzen-Dokumente liegen nicht mehr in `docs/`, sondern im fachlich passenden Ordner `../ops/windows-reinstall/docs/`. ## Archiv
Abgeschlossene Snapshots, Drills und Audits: `archive/README.md`.
Windows-Neuaufsetzen-Doku (Projekt abgeschlossen) liegt ebenfalls dort;
aktiv geblieben sind nur Veeam-Baseline und Laufwerksstruktur unter
`../ops/windows-reinstall/`.
+11 -5
View File
@@ -33,7 +33,8 @@ Details gilt immer die betroffene Compose-Datei oder das jeweilige Runbook.
| `docs/RESTORE_MATRIX.md` | Restore-Quelle je Dienst | | `docs/RESTORE_MATRIX.md` | Restore-Quelle je Dienst |
| `docs/SECRETS_MAP.md` | Secret-Namen und Pfade ohne Werte | | `docs/SECRETS_MAP.md` | Secret-Namen und Pfade ohne Werte |
| `docs/GITOPS_DRIFT_RUNBOOK.md` | Git/Gitea/Komodo/Docker/Host-Drift | | `docs/GITOPS_DRIFT_RUNBOOK.md` | Git/Gitea/Komodo/Docker/Host-Drift |
| `docs/AUDIT_2026-05-25_TODO.md` | aktuelle Restliste | | `docs/MASTER_TODO.md` | einzige operative Statusliste |
| `docs/DECISIONS.md` | Entscheidungs-Register (ADR-light) |
| `docs/DR_WORKSTATION_SETUP.md` | Schritt-fuer-Schritt-Runbook fuer den DR-Gaming-PC (WSL2 + Borg-Client + SSH-Keys) | | `docs/DR_WORKSTATION_SETUP.md` | Schritt-fuer-Schritt-Runbook fuer den DR-Gaming-PC (WSL2 + Borg-Client + SSH-Keys) |
| `docs/runbooks/komodo-bulk-deploy-dns.md` | Bulk-Deploy-Pulls scheitern an DNS, wenn AdGuard im selben Batch recreated wird | | `docs/runbooks/komodo-bulk-deploy-dns.md` | Bulk-Deploy-Pulls scheitern an DNS, wenn AdGuard im selben Batch recreated wird |
@@ -50,8 +51,13 @@ Details gilt immer die betroffene Compose-Datei oder das jeweilige Runbook.
| `services/authelia-diff.sh` | Authelia ACL Repo-zu-Host-Vergleich | | `services/authelia-diff.sh` | Authelia ACL Repo-zu-Host-Vergleich |
| `ops/h-drive-nearline/pull-critical-backups.ps1` | H:/ Nearline-Pull | | `ops/h-drive-nearline/pull-critical-backups.ps1` | H:/ Nearline-Pull |
## Arbeitsregel ## Doku-Regeln
Neue Doku nur anlegen, wenn sie dauerhaft als Runbook, Inventar oder Restliste 1. **Ein Fakt, ein Zuhause.** Status -> `docs/MASTER_TODO.md`; Entscheidungen -> `docs/DECISIONS.md`; Zielbild -> `HOMELAB_ARCHITECTURE_MASTER_V2.md`/Inventare/`SERVICE_CATALOG`; Ablauf -> genau ein Runbook; Beleg -> Host-Report (`/mnt/user/backups/restore-reports/`) oder Git-Commit. Alle anderen Stellen verlinken statt kopieren.
gebraucht wird. Einmalige Audits, Prompt-Kopien und lange Verlaufsprotokolle 2. **Erledigt = raus aus der Arbeitskopie.** Abgeschlossene Plaene, Sprints, Audits und Drills nach `docs/archive/` (Belege mit Referenzwert) oder loeschen (Sprint-Boards, erledigte Listen) - Git ist das Archiv.
gehoeren in Git-Commits, nicht als neue Markdown-Dateien. 3. **Neue Datei nur mit klarem Typ:** Einstieg/Index, Architektur, Inventar/Referenz, Runbook, Entscheidung, Status oder befristeter Snapshot. Sonst ist es ein Eintrag in einer bestehenden Datei.
4. **Done-Eintraege max. 3 Zeilen**, Details in Commit/Report; Kurzlog in `MASTER_TODO` max. 5 Eintraege.
5. **Datum im Dateinamen nur fuer Snapshots**; datierte Dateien im `docs/`-Root sind per Definition Aufraeum-Kandidaten.
6. **Index-Pflicht:** jede neue/geloeschte Doku-Datei aktualisiert `docs/README.md` im selben Commit.
7. **Quartals-Gaertnern (~15 min):** Datiertes archivieren, Done-/Review-Logs kuerzen, tote Links pruefen.
8. **Kopfzeile je Dokument:** `Typ: ... · Stand: YYYY-MM-DD · Status: ...`. Bestandsnamen (SCREAMING_SNAKE) bleiben; neue Dateien in Unterordnern in kebab-case.
-250
View File
@@ -1,250 +0,0 @@
# Restore Handbook - KalliLab CORE
Stand: 2026-06-03
Dieses Handbuch ist die praktische Betriebsanleitung fuer Restore-Checks und Restore-Lab in KalliLab CORE.
Es ergaenzt:
- `docs/RESTORE_MATRIX.md`
- `docs/DISASTER_RECOVERY.md`
- `ops/restore-tests/*`
---
## 1. Ziel
Dieses Handbuch beantwortet vier Fragen:
1. Was ist die Restore-Quelle?
2. Wo wird getestet?
3. Wie pruefen wir Erfolg?
4. Wie machen wir das regelmaessig mit wenig Handarbeit?
---
## 2. Grundmuster
Alle validierten Restore-Tests folgen demselben Muster:
- Quelle bleibt das produktive Borg-Repo bei Hetzner
- Borg-Zugriff laeuft ueber den vorhandenen `borg-ui`-Container
- Passphrase kommt aus `/mnt/user/appdata/secrets/borg_repo_passphrase.txt`
- Testdaten landen unter `/mnt/user/backups/restore-lab/<dienst>`
- Reports landen unter `/mnt/user/backups/restore-reports`
- Testinstanzen laufen lokal ohne Traefik und ohne produktive Domain
- nach Erfolg werden Testcontainer und Testdaten wieder entfernt
---
## 3. Bereits praktisch verifiziert
### Vaultwarden
- Erstlauf: 2026-05-07
- Nachweis: Borg-Restore, Testcontainer, Login-Seite erreichbar
### Gitea
- Erstlauf: 2026-05-07
- Nachweis: Borg-Restore, Web-UI, SSH-TCP-Port
### Paperless
- Erstlauf: 2026-05-07, Folgelauf: 2026-05-31
- Nachweis: Borg-Datei-Restore, Dump-Import in Test-Postgres, Login-Seite, Doc-Count
### Immich
- Erstlauf: 2026-05-27
- Nachweis: DB-Dump-Restore in VectorChord-Test-Postgres, HTTP-Smoke, Asset-Count
- Hinweis: Foto-Dateien-Restore ist bewusst nicht Teil des Smokes
### Authelia
- Erstlauf: 2026-06-03
- Nachweis: Config-Borg-Restore, `authelia config validate`, HTTP-Health `/api/health`
- Hinweis: Daten-Restore des produktiven Dumps ist bewusst nicht Teil des Smokes (Storage-Encryption-Key-Kopplung)
### Komodo Bootstrap
- Erstlauf: 2026-05-30
- Nachweis: Compose-Validierung, Mongo healthy, Core HTTP, Periphery running
- Hinweis: Daten-Restore aus `komodo-mongo.archive.gz` ist noch nicht getestet
---
## 4. Verzeichnisstruktur
### Produktiv
- `/mnt/user/appdata`
- `/mnt/user/services`
- `/mnt/user/documents`
- `/mnt/user/backups/borg/dumps/latest`
### Restore-Lab
- `/mnt/user/backups/restore-lab/vaultwarden`
- `/mnt/user/backups/restore-lab/gitea`
- `/mnt/user/backups/restore-lab/paperless`
- `/mnt/user/backups/restore-lab/immich`
- `/mnt/user/backups/restore-lab/authelia`
- `/mnt/user/backups/restore-lab/komodo`
- `/mnt/user/backups/restore-lab/_failed` (Diagnose-Material bei Fehllaeufen)
### Reports
- `/mnt/user/backups/restore-reports`
---
## 5. Restore-Frequenz
- jeden Montag, 06:30: Frische-Check fuer Dumps und Reports
- 1. Samstag im Monat, 07:00: Vaultwarden
- 3. Samstag im Monat, 07:15: Gitea
- 2. Samstag in ungeraden Monaten, 08:00: Paperless
- 2. Sonntag in Feb/Mai/Aug/Nov, 08:30: Immich
- 2. Samstag in geraden Monaten, 07:30: Authelia
- 1. Kalendertag im Monat, 09:00: Zufaelliger Restore aus Pool
Vollstaendiger Kalender mit Cron-Ausdruecken und Shell-Guards steht in `ops/restore-tests/schedule.md`.
---
## 6. Betriebsmodus
Stand 2026-06-03 ist der Betrieb auf V1+ (V1 mit ntfy):
- validierte Bash-Host-Jobs fuer Vaultwarden, Gitea, Paperless, Immich, Authelia, Komodo-Bootstrap
- Host-Job-Definitionen und Cron-Vorlagen liegen im Repo (`ops/restore-tests/unraid-user-scripts.md`)
- `ntfy`-Wrapper sendet Erfolg an `homelab-info`, Fehler an `homelab-alerts`
- Frische-Check prueft zusaetzlich pg-Custom-Format-Dumps per `pg_restore --list` Header-Validierung
- bei Fehlschlag wird das Restore-Lab nach `_failed/` verschoben statt geloescht
Noch geplant fuer V2:
- Hermes-Zusammenfassung ueber vorhandene Reports
- Sammelreports und Report-Rotation
- weitere Dienste (Nextcloud, Mailarchiver, Mealie)
---
## 7. User Script Jobs auf Unraid
Die Vorlagen stehen in:
- `ops/restore-tests/unraid-user-scripts.md`
Host-Repo-Pfad:
```text
/mnt/user/services/homelab-infra
```
Jobs:
1. `restore-freshness-weekly`
2. `restore-vaultwarden-monthly`
3. `restore-gitea-monthly`
4. `restore-paperless-bimonthly`
5. `restore-immich-quarterly`
6. `restore-authelia-bimonthly`
7. `monthly-random-restore`
---
## 8. Erfolgskriterien
Ein Restore-Test gilt nur dann als erfolgreich, wenn:
- Restore-Quelle lesbar war
- Daten im Restore-Lab ankamen
- Testcontainer startete
- Smoke-Test erfolgreich war
- Report geschrieben wurde
Nur `Container laeuft` reicht nicht.
---
## 9. Sicherheitsregeln
- keine produktiven Pfade beschreiben
- keine produktiven Container fuer Restore-Tests verwenden
- keine produktiven Domains fuer Testinstanzen verwenden
- keine Secrets im Repo
- keine Restore-Automatik fuer neue Dienste ohne bewusste Freigabe
---
## 10. Schnellstart
### Frische-Check
Auf dem Unraid-Host:
```bash
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh freshness
```
### Vaultwarden Restore-Check
```bash
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh vaultwarden
```
### Gitea Restore-Check
```bash
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh gitea
```
### Paperless Restore-Check
```bash
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh paperless
```
### Immich Restore-Check
```bash
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh immich
```
### Authelia Restore-Check
```bash
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh authelia
```
### Komodo Bootstrap Trockenlauf
```bash
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh komodo-bootstrap
```
### Optional mit `ntfy`
```bash
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-job-with-ntfy.sh freshness homelab-info
```
---
## 11. Naechste Ausbaustufen
1. Nextcloud-Restore-Test (mit `occ maintenance:mode`-Choreographie)
2. Mailarchiver-Restore-Test
3. Mealie-Restore-Test
4. Komodo-Mongo-Daten-Restore (echtes `mongorestore` statt reinem Bootstrap)
5. Shared-PostgreSQL-18-Cluster-Restore-Drill (globals + per-DB-Dumps)
6. Traefik-Restore-Test (mit `dynamic/` und LE-State)
7. Hermes-Zusammenfassung ueber vorhandene Reports
8. Report-Rotation (archivieren nach 12 Monaten)
9. Negativ-Test: bewusst kaputten Dump in den Frische-Check einfuettern
## 12. Report-Aufbewahrung
Reports unter `/mnt/user/backups/restore-reports` werden dauerhaft aufbewahrt. Bei wachsender Anzahl (ca. 50-60 pro Jahr) empfiehlt sich eine jaehrliche Archivierung alter Reports in einen Unterordner `_archive/YYYY/`. Der Frische-Check warnt bei `MAX_REPORT_AGE_DAYS=45`, loescht aber bewusst nicht automatisch.
+13 -166
View File
@@ -60,6 +60,9 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
| Glance | Git / Borg-Repo | Repo-Konfiguration unter `ops/glance/config/glance.yml`; keine kritische Datenpersistenz | keine | `GLANCE_IMMICH_API_KEY`, `GLANCE_ADGUARD_USERNAME`, `GLANCE_ADGUARD_PASSWORD`, `GLANCE_SPEEDTEST_API_KEY` | Traefik, Authelia, optional interne API-Ziele | Dashboard startet, Widgets laden, Docker-Status laeuft nur ueber `glance-docker-socket-proxy` | | Glance | Git / Borg-Repo | Repo-Konfiguration unter `ops/glance/config/glance.yml`; keine kritische Datenpersistenz | keine | `GLANCE_IMMICH_API_KEY`, `GLANCE_ADGUARD_USERNAME`, `GLANCE_ADGUARD_PASSWORD`, `GLANCE_SPEEDTEST_API_KEY` | Traefik, Authelia, optional interne API-Ziele | Dashboard startet, Widgets laden, Docker-Status laeuft nur ueber `glance-docker-socket-proxy` |
| ntfy | Borg / Share | `/mnt/user/appdata/ntfy` | keine | keine besonderen Secret-Dateien dokumentiert | Traefik | UI und Push-Endpunkt erreichbar | | ntfy | Borg / Share | `/mnt/user/appdata/ntfy` | keine | keine besonderen Secret-Dateien dokumentiert | Traefik | UI und Push-Endpunkt erreichbar |
| Paperless-GPT | Borg / Share | `/mnt/user/appdata/paperless-gpt` | keine eigene DB | `PAPERLESS_API_TOKEN`, `OPENAI_API_KEY` | Traefik, Paperless, OpenAI API | UI startet, Konfiguration vorhanden; LLM-Provider zeigt `openai` / `gpt-5.4-mini` | | Paperless-GPT | Borg / Share | `/mnt/user/appdata/paperless-gpt` | keine eigene DB | `PAPERLESS_API_TOKEN`, `OPENAI_API_KEY` | Traefik, Paperless, OpenAI API | UI startet, Konfiguration vorhanden; LLM-Provider zeigt `openai` / `gpt-5.4-mini` |
| Home Assistant | Borg + HA-native Backups + Fachrepo | `/mnt/user/appdata/homeassistant` inkl. `.storage`, `secrets.yaml`, `trusted_proxies.yaml`, `custom_components` (HACS, `solaredge_modbus_multi`); Fach-YAML aus `/mnt/user/services/smart-home-kalli/home-assistant` | HA-native Backup-Artefakte unter `/mnt/user/appdata/homeassistant/backups`; erstes Artefakt 2026-06-13 erzeugt und tar-lesbar (`backup.json`, `homeassistant.tar.gz`); Backup nach SolarEdge-Integration: `Custom_backup_2026.6.1_2026-06-13_14.59_48645373.tar`; Backup nach Energy-Dashboard-Konfiguration: `Custom_backup_2026.6.1_2026-06-13_15.59_25670583.tar`; keine externe DB in Phase 1 | HA-Secrets in `secrets.yaml`, Integrations-Tokens in `.storage`, MQTT-Credentials, Agent-API-Tokens als Host-Secrets `ha_token_codex`/`ha_token_claude` (nur mit erhaltenem `.storage`-Auth-State nutzbar), spaeter Tibber/InfluxDB-Tokens | Traefik, `frontend_net`, `smarthome_net`, Mosquitto, Fachrepo-Clone, SolarEdge-Wechselrichter `192.168.178.111:1502` | Restore-Test am 2026-06-13 erfolgreich: HA-native Backup + Mosquitto-Appdata + Fachrepo-Clone isoliert gestartet, HA HTTP/API/check_config gruen; produktiv danach HA-MQTT-Config-Entry `smarthome-mosquitto` geladen, SolarEdge Local `solaredge_modbus_multi` loaded mit 68 Entitaeten und Energy Dashboard fuer Netz/PV/Speicher per `energy/validate` ohne Issues; Report `/mnt/user/backups/restore-reports/homeassistant-2026-06-13.md` |
| Smart-Home MQTT / Mosquitto | Borg / Share | `/mnt/user/appdata/mosquitto/config`, `/mnt/user/appdata/mosquitto/data`, `/mnt/user/appdata/mosquitto/log` | Mosquitto persistiert retained messages/subscriptions dateibasiert | `passwordfile`, `aclfile`, spaeter per-Device-User | `smarthome_net`, Home Assistant, spaeter ESPHome/Zigbee2MQTT | Restore-Test am 2026-06-13 erfolgreich: authentifizierter Publish/Subscribe-Smoke mit `homeassistant`-User und retained Topic nach Broker-Restart gruen; produktiv verbindet sich HA als User `homeassistant` |
| Smart-Home Fachrepo | Gitea + Borg-Repo-Clone | `/mnt/user/services/smart-home-kalli` | keine | keine echten Secrets im Repo; `secrets-template/` nur Beispiele | Gitea, Home Assistant Mounts | `git status` sauber, HA liest `configuration.yaml` und `packages/` aus dem Clone |
--- ---
@@ -77,6 +80,8 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
| InfluxDB 3 Core | historischer Altstand / Datenuebernahme | `/mnt/user/appdata/influxdb3/data`, `/mnt/user/appdata/influxdb3/plugins` | dateibasierter Object Store | `influxdb3_admin_token.json` | `monitoring-influxdb3-core` | Datenpfad wird vom Monitoring-Zielstack weitergenutzt und darf nicht blind geloescht werden | | InfluxDB 3 Core | historischer Altstand / Datenuebernahme | `/mnt/user/appdata/influxdb3/data`, `/mnt/user/appdata/influxdb3/plugins` | dateibasierter Object Store | `influxdb3_admin_token.json` | `monitoring-influxdb3-core` | Datenpfad wird vom Monitoring-Zielstack weitergenutzt und darf nicht blind geloescht werden |
| Loki / Alloy | historischer Altstand | `/mnt/user/appdata/loki/config`, `/mnt/user/appdata/loki/data`, `/mnt/user/appdata/alloy/config` | keine primaere DB; Loki-Dateispeicher war transient | keine zusaetzlichen Secrets | nicht aktiv | Compose-Pfad aus aktivem Repo entfernt; aktuelle Logsammlung laeuft ueber `monitoring-loki`/`monitoring-promtail` | | Loki / Alloy | historischer Altstand | `/mnt/user/appdata/loki/config`, `/mnt/user/appdata/loki/data`, `/mnt/user/appdata/alloy/config` | keine primaere DB; Loki-Dateispeicher war transient | keine zusaetzlichen Secrets | nicht aktiv | Compose-Pfad aus aktivem Repo entfernt; aktuelle Logsammlung laeuft ueber `monitoring-loki`/`monitoring-promtail` |
| Monitoring Stack | Rebuild + named volumes + InfluxDB-Appdata | `prometheus_data`, `loki_data`, `promtail_positions`, `grafana_data`; InfluxDB unter `/mnt/user/appdata/influxdb3/data` und `/mnt/user/appdata/influxdb3/plugins`; Provisioning aus `monitoring/grafana/provisioning` | Prometheus-TSDB, Loki-Dateispeicher und InfluxDB-Dateistore; Diagnose-/Langzeitdaten, keine Tier-1-Restore-Quelle | `monitoring_grafana_admin_password.txt`, `monitoring_grafana_influxdb_token.txt`, `influxdb3_admin_token.json` | `monitoring_net`, `monitoring_influx_lan`, `frontend_net`, Traefik, Authelia, Docker socket read-only fuer Promtail, Host-Mounts fuer node-exporter/cAdvisor | `https://monitoring.kaleschke.info` leitet zu Authelia; Prometheus Targets sind up; Grafana-Datasources `Prometheus`, `Loki` und `InfluxDB 3 Core` funktionieren | | Monitoring Stack | Rebuild + named volumes + InfluxDB-Appdata | `prometheus_data`, `loki_data`, `promtail_positions`, `grafana_data`; InfluxDB unter `/mnt/user/appdata/influxdb3/data` und `/mnt/user/appdata/influxdb3/plugins`; Provisioning aus `monitoring/grafana/provisioning` | Prometheus-TSDB, Loki-Dateispeicher und InfluxDB-Dateistore; Diagnose-/Langzeitdaten, keine Tier-1-Restore-Quelle | `monitoring_grafana_admin_password.txt`, `monitoring_grafana_influxdb_token.txt`, `influxdb3_admin_token.json` | `monitoring_net`, `monitoring_influx_lan`, `frontend_net`, Traefik, Authelia, Docker socket read-only fuer Promtail, Host-Mounts fuer node-exporter/cAdvisor | `https://monitoring.kaleschke.info` leitet zu Authelia; Prometheus Targets sind up; Grafana-Datasources `Prometheus`, `Loki` und `InfluxDB 3 Core` funktionieren |
| Zigbee2MQTT (geplant) | Borg + Fachrepo | `/mnt/user/appdata/zigbee2mqtt` inkl. `configuration.yaml`, `database.db`, `coordinator_backup.json`, `state.json`; Fach-Doku im Repo `smart-home-kalli` | keine externe DB | `network_key`, MQTT-Credentials, LAN-Koordinator-IP/Firmwarestand | Mosquitto, LAN-PoE-Koordinator, `smarthome_net` | Z2M startet, Coordinator verbindet sich, geraete bleiben gepairt, Testgeraet sendet MQTT-State |
| ESPHome (geplant) | Fachrepo + Borg fuer Build-/Runtime-State | `/mnt/user/appdata/esphome` falls Dashboard/Build-Cache genutzt wird; YAML unter `/mnt/user/services/smart-home-kalli/esphome` | keine | ESPHome-Secrets ausserhalb Git, API-/OTA-Keys | WLAN/LAN, Mosquitto falls MQTT genutzt wird | Dashboard startet, ein Testgeraet kompiliert/validiert, OTA/API-Verbindung funktioniert |
| Hermes Agent | VM-seitig offen | `/mnt/user/appdata/hermes-agent/data`, `/mnt/user/appdata/hermes-agent/ssh` | keine eigene DB | Host-`.env` fuer Provider-/API-/Home-Assistant-Tokens, `hermes_runner_id_ed25519`, `HERMES_DASHBOARD_HOST` | separate Hermes-VM/Runner, Traefik, Authelia, `hermes_net` | NAS-Stack nicht starten, solange Runner-VM und echte `.env` fehlen | | Hermes Agent | VM-seitig offen | `/mnt/user/appdata/hermes-agent/data`, `/mnt/user/appdata/hermes-agent/ssh` | keine eigene DB | Host-`.env` fuer Provider-/API-/Home-Assistant-Tokens, `hermes_runner_id_ed25519`, `HERMES_DASHBOARD_HOST` | separate Hermes-VM/Runner, Traefik, Authelia, `hermes_net` | NAS-Stack nicht starten, solange Runner-VM und echte `.env` fehlen |
| ddns-updater | Rebuildbar | geringe Persistenzrelevanz | keine | Provider-Zugang ueber Stack ENV | Internetzugang | Update-Job laeuft | | ddns-updater | Rebuildbar | geringe Persistenzrelevanz | keine | Provider-Zugang ueber Stack ENV | Internetzugang | Update-Job laeuft |
@@ -160,6 +165,7 @@ Stand 2026-06-06. Pro Dienst auf einen Blick: Wurde der Restore schon einmal rea
| Borg UI | 3 | - | rebuildbar | - | | Borg UI | 3 | - | rebuildbar | - |
| Filebrowser | 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-06 | Full-Backup geschrieben; Recovery-USB-Boot, SMB-Mount und Restore-Point-Sichtpruefung erfolgreich; vor echtem Restore abgebrochen | nach Image-Aenderungen oder quartalsweise |
| Home Assistant + Mosquitto | 2 | 2026-06-13 | HA-native Backup + Mosquitto-Appdata + Fachrepo-Clone, isolierte Testcontainer, HA HTTP/API/check_config, MQTT Publish/Subscribe + retained Topic nach Broker-Restart | vor groesseren Smart-Home-Aenderungen oder nach relevanten HA/Mosquitto-Architekturaenderungen |
--- ---
@@ -170,173 +176,14 @@ wurden alle am 2026-06-03 abgeschlossen und sind in der Reifegrad-Tabelle belegt
Verbleibende offene Restore-Pfade ohne vollstaendigen Test: Verbleibende offene Restore-Pfade ohne vollstaendigen Test:
1. **Unraid OS Flash** - Artefakt-Validierung am 2026-06-05 erfolgreich (siehe Reifegrad-Tabelle und Runbook unten); offen bleibt nur der **physische Ersatzstick-Boot-Test**. 1. **Unraid OS Flash** - Artefakt-Validierung am 2026-06-05 erfolgreich (siehe Reifegrad-Tabelle und `ops/restore-tests/unraid-flash-runbook.md`); offen bleibt nur der **physische Ersatzstick-Boot-Test**.
2. **Tailscale** - State-/Reconnect-Pfad dokumentiert testen 2. **Tailscale** - State-/Reconnect-Pfad dokumentiert testen (`ops/restore-tests/tailscale-runbook.md`)
--- ---
## Restore-Test-Runbooks (Entwurf) ## Restore-Test-Runbooks
Diese Abschnitte sind vorbereitete Checklisten fuer die noch untesteten Restore-Pfade. Die Ablaeufe je Dienst liegen als Runbooks und automatisierte Skripte unter
Sie sind **nicht** als produktive Anleitungen zu verwenden, bevor ein erster Testlauf `ops/restore-tests/` (Einstieg: `ops/restore-tests/README.md`). Fuer die noch
die konkreten Artefaktnamen und Pfade bestaetigt hat. offenen Pfade: `ops/restore-tests/unraid-flash-runbook.md` und
`ops/restore-tests/tailscale-runbook.md`.
### Unraid OS Flash
**Voraussetzungen:**
- Borg-Artefakt `unraid-flash-config.tar.gz` und `unraid-flash-config.tar.gz.sha256` unter `/mnt/user/backups/borg/dumps/latest` oder im Hetzner-Borg-Repo verfuegbar
- Neuer leerer USB-Stick (Empfehlung: 16 GB, USB 2.0 kompatibel)
- Unraid USB Flash Creator oder manueller Restore-Pfad
- Offline-gesicherte Borg-Passphrase verfuegbar
**Checkliste Artefakt-Validierung (ohne produktiven Stick):**
Automatisiert via Repo-Skript `ops/maintenance/check-unraid-flash-backup.sh`
(read-only, keine Extraktion). Manuelle Einzelschritte:
1. SHA256-Pruefung: `sha256sum -c unraid-flash-config.tar.gz.sha256`
2. Artefakt-Inhalt pruefen: `tar -tzf unraid-flash-config.tar.gz | head -40` — erwartet `config/` als Prefix
3. Kern-Configs vorhanden: `super.dat`, `disk.cfg`, `ident.cfg`, `share.cfg`, `network.cfg`, `docker.cfg`, `go`, `domain.cfg`
4. Keine produktiven Konfigurationspfade (z. B. `config/ssh/`) ausserhalb des Test-Environments extrahieren
5. Manifest-Datei auf Vollstaendigkeit pruefen
**Validierungsergebnis 2026-06-05 (read-only per SSH):** Artefakt frisch
(2026-06-05 04:00, ~16 h alt beim Test), `sha256sum -c` = OK, 390 Eintraege,
alle 8 Kern-Configs vorhanden. Das Archiv enthaelt erwartungsgemaess
Secret-Material (SSH-Host-Keys, Tailscale-State, `passwd`/`shadow`/`smbpasswd`,
`Trial.key`) und ist wie Secret-Backup zu behandeln. Es wurde nichts extrahiert,
nur Eintragsnamen gelistet. Offen bleibt der physische Ersatzstick-Boot-Test.
**Checkliste vollstaendiger Restore-Test (auf Wegwerf-Stick):**
1. Neuen USB-Stick mit Unraid USB Flash Creator formatieren und Basis-Unraid draufspielen
2. `config/`-Verzeichnis aus `unraid-flash-config.tar.gz` in den `/boot/config`-Pfad des neuen Sticks extrahieren
3. Im Testrahmen booten (kein Array starten, keine Shares mounten)
4. Pruefen: Unraid-Grundkonfiguration (Shares, Hostname, Netzwerk) ist sichtbar
5. Array-Zuordnung lesbar, ohne Drive-Assigns zu bestaetigen
**Smoke-Test-Kriterium:** Unraid bootet, Hostname ist `Kallilabcore`, Share-Konfiguration ist sichtbar, kein Array gestartet.
**Sonderregel:** Das Artefakt enthaelt Host-Konfiguration und SSH-Keys und ist wie Secret-Material zu behandeln. Nicht auf oeffentlichen oder unverschluesselten Testzielen extrahieren.
---
### AdGuard Home
**Validierungsergebnis 2026-06-06:** Automatisierter Test
`ops/restore-tests/adguard-restore-test.sh` auf Unraid erfolgreich ausgefuehrt.
Report: `/mnt/user/backups/restore-reports/adguard-2026-06-06.md`.
Getestet wurden Borg-Extract der Config, `AdGuardHome.yaml`-Struktur,
isolierter Testcontainer `restoretest-adguard` auf localhost-Ports,
HTTP `/control/status` = `401`, DNS-Smoke `git.kaleschke.info -> 192.168.178.58`,
7 Filterlisten-Eintraege. Testdaten wurden nach Erfolg bereinigt.
**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
**Automatisierter Test:**
```bash
/mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh adguard
```
**Manuelle 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. `15353`):
```yaml
ports:
- "127.0.0.1:15353:53/udp"
- "127.0.0.1:13001:80/tcp"
volumes:
- /mnt/user/backups/restore-lab/adguard/conf:/opt/adguardhome/conf
```
4. `http://127.0.0.1:13001/control/status` erreichbar (`200`, `401` oder `403` sind fuer den Smoke ausreichend)
5. DNS-Aufloesung: `dig @127.0.0.1 -p 15353 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)
**Validierungsergebnis 2026-06-06:** Automatisierter Test
`ops/restore-tests/redis-restore-test.sh` auf Unraid erfolgreich ausgefuehrt.
Report: `/mnt/user/backups/restore-reports/redis-2026-06-06.md`.
Getestet wurde das Pre-Cutover-Artefakt
`/mnt/user/backups/borg/dumps/latest/shared-redis-pre-redis8-20260531-185011`
in einer isolierten Redis-8.8-Testinstanz auf `127.0.0.1:16379`.
Ergebnis: `PING` = `PONG`, `redis_version` = `8.8.0`, AOF aktiv (`1`),
`DBSIZE` = `1`. Produktiver Port und produktiver Datenpfad wurden nicht genutzt.
**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
**Automatisierter Test:**
```bash
/mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh redis
```
**Manuelle Checkliste:**
1. RDB/AOF-Datei aus dem Backup in den Testpfad kopieren:
```
cp /mnt/user/backups/borg/dumps/latest/shared-redis-pre-redis8-<ts>/dump.rdb \
/mnt/user/backups/restore-lab/redis/
```
(oder Borg-Extract aus dem Appdata-Archiv)
2. Testcontainer starten (kein produktiver Port 6379, stattdessen z. B. `16379`):
```yaml
ports:
- "127.0.0.1:16379:6379"
volumes:
- /mnt/user/backups/restore-lab/redis:/data
command: redis-server --requirepass <aus Secret> --appendonly yes
```
3. Verbindungstest: `redis-cli -p 16379 -a <pass> PING` antwortet `PONG`
4. Redis-Version pruefen: `redis-cli -p 16379 -a <pass> INFO server | grep redis_version` zeigt `8.x`
5. Stichprobe Key-Bestand: `redis-cli -p 16379 -a <pass> DBSIZE` zeigt plausible Zahl (nicht 0)
6. Testcontainer stoppen und Testpfad aufraeumen
**Smoke-Test-Kriterium:** Redis 8 startet mit dem Restore-Datenpfad, `PING` antwortet, `DBSIZE` ist nicht 0.
**Shared Redis Besonderheit:** Shared Redis wird produktiv nur von Paperless genutzt (AOF aktiv). Bei einem echten Restore nach App-Absturz: Erst Redis aus Backup hochziehen, dann Paperless. Nextcloud hat eigene Redis-Instanz ohne Passwort.
+14 -63
View File
@@ -1,6 +1,10 @@
# Rollback Guide - Homelab # Rollback Guide - Homelab
Typ: Runbook · Stand: 2026-06-11 · Status: aktiv
Dieses Dokument beschreibt den sicheren Rueckweg im aktuellen GitOps-Betrieb. Dieses Dokument beschreibt den sicheren Rueckweg im aktuellen GitOps-Betrieb.
Rollback-Anleitungen fuer bereits entfernte Dienste (Uptime-Kuma, Grafana-/
InfluxDB-Altstack, Stirling-PDF) liegen in der Git-Historie, nicht mehr hier.
--- ---
@@ -72,59 +76,14 @@ Bei Problemen mit Borg UI oder Dump-Automatisierung:
3. Persistenz unter `/mnt/user/appdata/borg-ui/` und `/mnt/user/backups/borg/dumps/` nicht blind loeschen 3. Persistenz unter `/mnt/user/appdata/borg-ui/` und `/mnt/user/backups/borg/dumps/` nicht blind loeschen
4. Restore zuerst in einen Testpfad schreiben, nicht direkt in Produktivpfade 4. Restore zuerst in einen Testpfad schreiben, nicht direkt in Produktivpfade
## BentoPDF / Stirling-PDF Rollback ## Monitoring-Stack Rollback
Bei Problemen mit BentoPDF: `monitoring/` ist der einzige Observability-Stack. Bei Problemen:
1. Git-Stand auf die letzte funktionierende Stirling-PDF-Compose zuruecknehmen oder gezielt `apps/bentopdf` wieder durch `apps/stirling-pdf` ersetzen
2. Commit + Push nach Gitea
3. betroffenen Stack in Komodo redeployen
4. `https://pdf.kaleschke.info` pruefen
Die alte Stirling-PDF-Persistenz unter `/mnt/user/appdata/stirling-pdf` nicht loeschen, solange der BentoPDF-Ersatz nicht fachlich abgenommen ist.
## Grafana / InfluxDB Rollback
Vor dem ersten produktiven Einsatz reicht es, den vorbereiteten Stack nicht zu deployen oder per Ruecknahme-Commit aus dem Repo zu entfernen.
Nach einem Deploy:
1. alten Grafana/InfluxDB-Stack in Komodo gestoppt lassen; der fruehere Compose-Pfad `ops/grafana-influxdb` ist seit 2026-05-26 nicht mehr im aktiven Repo
2. Persistenz unter `/mnt/user/appdata/grafana` und `/mnt/user/appdata/influxdb3` unangetastet lassen
3. Secrets unter `/mnt/user/appdata/secrets/grafana_admin_password.txt`, `/mnt/user/appdata/secrets/grafana_influxdb_token.txt` und `/mnt/user/appdata/secrets/influxdb3_admin_token.json` nur nach bewusstem Entscheid entfernen
4. Grafana-Domain und InfluxDB-Zugriff testen, bis klar ist, dass keine produktiven Dashboards oder Writer mehr davon abhaengen
## Monitoring-Zielstack Rollback
Der Zielzustand ist `monitoring/` als einziger Observability-Stack. Bei Problemen nach der Migration:
1. `monitoring` in Komodo stoppen oder auf den letzten funktionierenden Commit zurueckgehen 1. `monitoring` in Komodo stoppen oder auf den letzten funktionierenden Commit zurueckgehen
2. nur im echten Notfall die abgeloesten Altstaende aus der Git-Historie vor dem Repo-Cleanup wiederherstellen, z. B. aus Commit `ff5991c`; nicht dauerhaft parallel zum Zielstack betreiben 2. named volumes `prometheus_data`, `loki_data`, `promtail_positions`, `grafana_data` sowie `/mnt/user/appdata/influxdb3` nicht blind loeschen
3. named volumes `prometheus_data`, `loki_data`, `promtail_positions`, `grafana_data` sowie `/mnt/user/appdata/influxdb3` nicht blind loeschen 3. Secrets (`monitoring_grafana_admin_password.txt`, `monitoring_grafana_influxdb_token.txt`, `influxdb3_admin_token.json`) nur nach bewusstem Entscheid entfernen
4. Secrets `monitoring_grafana_admin_password.txt`, `monitoring_grafana_influxdb_token.txt` und `influxdb3_admin_token.json` nur nach bewusstem Entscheid entfernen 4. Grafana-Datasources `Prometheus`, `Loki` und `InfluxDB 3 Core` testen
5. Home Assistant Writer erst wieder umstellen, wenn `curl -i http://192.168.178.58:8181/` erwartbar `401 Unauthorized` liefert
6. Grafana-Datasources `Prometheus`, `Loki` und `InfluxDB 3 Core` testen
## Uptime Kuma Removal Rollback
Falls die Blackbox-/Grafana-Ablösung unerwartet nicht ausreicht:
1. per Ruecknahme-Commit `ops/uptime-kuma/docker-compose.yml`, die Blackbox-/Glance-/Authelia-Referenzen und die Restore-Freshness-Pruefung auf den letzten Uptime-Kuma-Stand zurueckbringen
2. nach Gitea pushen und den Uptime-Kuma-Stack in Komodo neu anlegen oder aus dem letzten Stack-Backup wiederherstellen
3. `/mnt/user/appdata/_archive/uptime-kuma-removed-2026-05-25` nach `/mnt/user/appdata/uptime-kuma` zurueckverschieben, falls die Archivierung bereits erfolgt ist
4. `https://uptime.kaleschke.info` und die Monitore pruefen
5. erst danach den Blackbox-/Grafana-Zielzustand erneut bewerten
## Glance Dashboard Rollback
Vor dem ersten produktiven Einsatz reicht es, den vorbereiteten Stack `ops/glance` nicht zu deployen oder per Ruecknahme-Commit aus dem Repo zu entfernen.
Nach einem Deploy:
1. `glance` in Komodo stoppen oder auf den letzten funktionierenden Commit zurueckgehen
2. keine Produktivdaten loeschen; Glance nutzt nur Repo-Konfiguration und Stack-ENV
3. pruefen, ob `https://glance.kaleschke.info` nicht mehr geroutet wird oder wieder den erwarteten Stand zeigt
4. der `glance-docker-socket-proxy` darf nicht separat als Dauercontainer laufen bleiben
--- ---
@@ -132,19 +91,11 @@ Nach einem Deploy:
Bevorzugte Quellen: Bevorzugte Quellen:
- Borg-Restore - Borg-Restore (zuerst in Testpfade unter `/mnt/user/backups/restore-lab/`)
- erzeugte PostgreSQL-/MariaDB-Dumps - erzeugte Dumps unter `/mnt/user/backups/borg/dumps/latest`
- bekannte Appdata-Snapshots - bekannte Appdata-Archivstaende unter `/mnt/user/appdata/_archive/`
Beispiele: Dienst-spezifische Restore-Quellen, Dumps und Smoke-Tests stehen in `docs/RESTORE_MATRIX.md`.
```bash
cp -r /mnt/user/appdata/<service> /mnt/user/backup/
```
```bash
pg_dumpall > /mnt/user/backup/pg_dump_$(date +%Y%m%d).sql
```
--- ---
+7 -2
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 Mongo | Root Password | `/mnt/user/appdata/secrets/komodo_mongo_password.txt` -> `MONGO_INITDB_ROOT_PASSWORD_FILE` | aktiv |
| Komodo Core | App Secrets | Stack ENV `${KOMODO_SECRET_KEY}`, `${KOMODO_WEBHOOK_SECRET}`, `${KOMODO_JWT_SECRET}`, `${KOMODO_MONGO_PASSWORD}`, `${KOMODO_PERIPHERY_PASSKEY}` | aktiv | | Komodo Core | App Secrets | Stack ENV `${KOMODO_SECRET_KEY}`, `${KOMODO_WEBHOOK_SECRET}`, `${KOMODO_JWT_SECRET}`, `${KOMODO_MONGO_PASSWORD}`, `${KOMODO_PERIPHERY_PASSKEY}` | aktiv |
| Gitea Push Mirror | GitHub fine-grained PAT fuer `michaelkaleschke-spec/homelab-infra` | Gitea Repository Mirror Settings, persistent in `/mnt/user/services/gitea/data`; kein Datei-Secret im Repo | aktiv | | Gitea Push Mirror | GitHub fine-grained PAT fuer `michaelkaleschke-spec/homelab-infra` | Gitea Repository Mirror Settings, persistent in `/mnt/user/services/gitea/data`; kein Datei-Secret im Repo | aktiv |
| Glance | Community Widget API Tokens | Stack ENV `${GLANCE_IMMICH_API_KEY}`, `${GLANCE_ADGUARD_USERNAME}`, `${GLANCE_ADGUARD_PASSWORD}`, `${GLANCE_SPEEDTEST_API_KEY}` | aktiv | | Glance | Community Widget API Tokens | Stack ENV `${GLANCE_IMMICH_API_KEY}`, `${GLANCE_ADGUARD_USERNAME}`, `${GLANCE_ADGUARD_PASSWORD}`, `${GLANCE_SPEEDTEST_API_KEY}`, `${GLANCE_KOMODO_API_KEY}`, `${GLANCE_KOMODO_API_SECRET}`, `${GLANCE_GITEA_TOKEN}`, `${GLANCE_PAPERLESS_TOKEN}`, `${GLANCE_MEALIE_TOKEN}` (alle read-only anlegen), `${GLANCE_HA_TOKEN}` (HA Long-Lived Access Token; Glance nutzt nur `GET /api/states`) | aktiv |
| speedtest-tracker | App Key / Admin-Zugang | Stack ENV `${APP_KEY}`, `${ADMIN_PASSWORD}` | aktiv | | speedtest-tracker | App Key / Admin-Zugang | Stack ENV `${APP_KEY}`, `${ADMIN_PASSWORD}` | aktiv |
| Nextcloud | Admin User | `/mnt/user/appdata/secrets/nextcloud_admin_user.txt` -> `NEXTCLOUD_ADMIN_USER_FILE` | neu | | Nextcloud | Admin User | `/mnt/user/appdata/secrets/nextcloud_admin_user.txt` -> `NEXTCLOUD_ADMIN_USER_FILE` | neu |
| Nextcloud | Admin Password | `/mnt/user/appdata/secrets/nextcloud_admin_password.txt` -> `NEXTCLOUD_ADMIN_PASSWORD_FILE` | neu | | Nextcloud | Admin Password | `/mnt/user/appdata/secrets/nextcloud_admin_password.txt` -> `NEXTCLOUD_ADMIN_PASSWORD_FILE` | neu |
@@ -51,6 +51,8 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
| Hermes Agent | Provider-Keys, Bot-Tokens, API-Server-Key | `/mnt/user/appdata/hermes-agent/data/.env` | VM-seitig offen | | Hermes Agent | Provider-Keys, Bot-Tokens, API-Server-Key | `/mnt/user/appdata/hermes-agent/data/.env` | VM-seitig offen |
| Hermes Agent | SSH-Runner Private Key | `/mnt/user/appdata/secrets/hermes_runner_id_ed25519` -> `/root/.ssh/id_ed25519` | VM-seitig offen | | Hermes Agent | SSH-Runner Private Key | `/mnt/user/appdata/secrets/hermes_runner_id_ed25519` -> `/root/.ssh/id_ed25519` | VM-seitig offen |
| InfluxDB 3 Core | Admin Token JSON | `/mnt/user/appdata/secrets/influxdb3_admin_token.json` -> Docker Secret `/run/secrets/influxdb3_admin_token` | aktiv | | InfluxDB 3 Core | Admin Token JSON | `/mnt/user/appdata/secrets/influxdb3_admin_token.json` -> Docker Secret `/run/secrets/influxdb3_admin_token` | aktiv |
| Home Assistant -> InfluxDB | Write Token (Wetterarchiv) | `/mnt/user/appdata/secrets/ha_influxdb_token` + HA `/config/secrets.yaml` Key `influxdb_ha_token`; InfluxDB-3-Core Named-Admin-Token (voller Zugriff, da Core keine Scopes kennt) | aktiv |
| Home Assistant | Agent API Tokens | `/mnt/user/appdata/secrets/ha_token_claude`, `ha_token_codex` (Long-Lived Access Tokens fuer read-only API-Zugriff durch KI-Agenten) | aktiv |
| Monitoring Grafana | Admin Password | `/mnt/user/appdata/secrets/monitoring_grafana_admin_password.txt` -> Docker Secret `/run/secrets/monitoring_grafana_admin_password` -> `GF_SECURITY_ADMIN_PASSWORD__FILE` | aktiv | | Monitoring Grafana | Admin Password | `/mnt/user/appdata/secrets/monitoring_grafana_admin_password.txt` -> Docker Secret `/run/secrets/monitoring_grafana_admin_password` -> `GF_SECURITY_ADMIN_PASSWORD__FILE` | aktiv |
| Monitoring Grafana -> InfluxDB | Datasource Token | `/mnt/user/appdata/secrets/monitoring_grafana_influxdb_token.txt` -> Docker Secret `/run/secrets/monitoring_grafana_influxdb_token` | aktiv | | Monitoring Grafana -> InfluxDB | Datasource Token | `/mnt/user/appdata/secrets/monitoring_grafana_influxdb_token.txt` -> Docker Secret `/run/secrets/monitoring_grafana_influxdb_token` | aktiv |
| 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) | | 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) |
@@ -98,6 +100,9 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
|-- redis_password.txt |-- redis_password.txt
|-- borg_repo_passphrase.txt |-- borg_repo_passphrase.txt
|-- influxdb3_admin_token.json |-- influxdb3_admin_token.json
|-- ha_influxdb_token
|-- ha_token_claude
|-- ha_token_codex
|-- filebrowser_admin_password.txt |-- filebrowser_admin_password.txt
|-- homelab_smtp_password.txt |-- homelab_smtp_password.txt
`-- vaultwarden_admin_token.txt `-- vaultwarden_admin_token.txt
@@ -141,7 +146,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 | | `speedtest-tracker` | `APP_KEY`, `ADMIN_PASSWORD` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | `APP_KEY` ist verschluesselungsrelevant; bei echtem Verlust App-State frisch initialisieren |
| `komodo-core` | `KOMODO_SECRET_KEY`, `KOMODO_WEBHOOK_SECRET`, `KOMODO_JWT_SECRET`, `KOMODO_MONGO_PASSWORD`, `KOMODO_PERIPHERY_PASSKEY` | Vaultwarden -> externe Notiz (Henne-Ei: Komodo-Mongo-Dump ist hier **nicht** Restore-Quelle, weil Komodo dafuer schon laufen muesste) | siehe `docs/SERVICES_RECOVERY.md` Komodo-Bootstrap; ohne diese Werte ist der Self-Stack nicht reproduzierbar | | `komodo-core` | `KOMODO_SECRET_KEY`, `KOMODO_WEBHOOK_SECRET`, `KOMODO_JWT_SECRET`, `KOMODO_MONGO_PASSWORD`, `KOMODO_PERIPHERY_PASSKEY` | Vaultwarden -> externe Notiz (Henne-Ei: Komodo-Mongo-Dump ist hier **nicht** Restore-Quelle, weil Komodo dafuer schon laufen muesste) | siehe `docs/SERVICES_RECOVERY.md` Komodo-Bootstrap; ohne diese Werte ist der Self-Stack nicht reproduzierbar |
| `hermes-agent` | `HERMES_DASHBOARD_HOST` plus Provider-/API-/Home-Assistant-Tokens in Host-`.env` | Vaultwarden -> externe Notiz | Stack ist aktuell geparkt (Review 2026-07-25); ohne Werte bleibt der Stack deaktiviert, kein Schaden am Rest | | `hermes-agent` | `HERMES_DASHBOARD_HOST` plus Provider-/API-/Home-Assistant-Tokens in Host-`.env` | Vaultwarden -> externe Notiz | Stack ist aktuell geparkt (Review 2026-07-25); ohne Werte bleibt der Stack deaktiviert, kein Schaden am Rest |
| `glance` | `GLANCE_IMMICH_API_KEY`, `GLANCE_ADGUARD_USERNAME`, `GLANCE_ADGUARD_PASSWORD`, `GLANCE_SPEEDTEST_API_KEY` | Provider-UIs (Immich, AdGuard, Speedtest-Tracker) neu erzeugen | rebuildbar; Widgets bleiben leer bis Tokens neu erzeugt sind, kein kritischer Datentopf | | `glance` | `GLANCE_IMMICH_API_KEY`, `GLANCE_ADGUARD_USERNAME`, `GLANCE_ADGUARD_PASSWORD`, `GLANCE_SPEEDTEST_API_KEY`, `GLANCE_KOMODO_API_KEY`, `GLANCE_KOMODO_API_SECRET`, `GLANCE_GITEA_TOKEN`, `GLANCE_PAPERLESS_TOKEN`, `GLANCE_MEALIE_TOKEN`, `GLANCE_HA_TOKEN` | Provider-UIs (Immich, AdGuard, Speedtest-Tracker, Komodo, Gitea, Paperless, Mealie, Home Assistant) neu erzeugen | rebuildbar; Widgets bleiben leer bis Tokens neu erzeugt sind, kein kritischer Datentopf; `GLANCE_HA_TOKEN` muss zusaetzlich in `ops/glance/docker-compose.yml` durchgereicht werden |
| `n8n` | `N8N_ENCRYPTION_KEY` | Host-Secret-Datei `/mnt/user/appdata/secrets/n8n_encryption_key.txt` -> Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | Bei Verlust aller Quellen: n8n startet, aber **alle gespeicherten Credentials sind unbrauchbar** (Re-Eingabe noetig: GMX IMAP, OpenAI, Gitea PAT). Workflows bleiben strukturell erhalten. | | `n8n` | `N8N_ENCRYPTION_KEY` | Host-Secret-Datei `/mnt/user/appdata/secrets/n8n_encryption_key.txt` -> Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | Bei Verlust aller Quellen: n8n startet, aber **alle gespeicherten Credentials sind unbrauchbar** (Re-Eingabe noetig: GMX IMAP, OpenAI, Gitea PAT). Workflows bleiben strukturell erhalten. |
### Komodo-Sonderfall ### Komodo-Sonderfall
+2 -12
View File
@@ -142,8 +142,7 @@ Erst nach erfolgreichem Komodo-Bootstrap werden produktive Stacks ueber den doku
Trockenlauf gegen Wegwerf-Pfade ist seit 2026-05-29 als Repo-Skript abgelegt: Trockenlauf gegen Wegwerf-Pfade ist seit 2026-05-29 als Repo-Skript abgelegt:
`ops/restore-tests/komodo-bootstrap-compose.test.yml`, `ops/restore-tests/komodo-bootstrap-compose.test.yml`,
`ops/restore-tests/komodo-bootstrap-test.sh`, `ops/restore-tests/komodo-bootstrap-test.sh` und
`ops/restore-tests/komodo-bootstrap-plan.md` und
`ops/restore-tests/komodo-bootstrap-runbook.md`. Aufruf: `ops/restore-tests/komodo-bootstrap-runbook.md`. Aufruf:
```bash ```bash
@@ -203,13 +202,4 @@ Authoritativ ist `docs/SECRETS_MAP.md`. Fuer den Kaltstart ist diese Reihenfolge
- Wenn Gitea und Komodo beide down sind, gewinnt der externe GitHub-Mirror als Repo-Quelle. - Wenn Gitea und Komodo beide down sind, gewinnt der externe GitHub-Mirror als Repo-Quelle.
- Wenn Borg ohne Passphrase nicht entschluesselbar ist, ist Recovery blockiert. Die Offline-Sicherung wurde am 2026-05-26 vom Operator bestaetigt; bei Reviews nur pruefen, dass sie weiterhin auffindbar und lesbar ist. - Wenn Borg ohne Passphrase nicht entschluesselbar ist, ist Recovery blockiert. Die Offline-Sicherung wurde am 2026-05-26 vom Operator bestaetigt; bei Reviews nur pruefen, dass sie weiterhin auffindbar und lesbar ist.
## Naechste Aufgaben Offene Folgepunkte werden in `docs/MASTER_TODO.md` gefuehrt.
| Status | Aufgabe |
|---|---|
| erledigt (Skript + Host-Test) | Gitea-Bundle- oder Mirror-Mechanik final entscheiden |
| erledigt | Komodo-Bootstrap-Quelle finalisieren |
| erledigt (Doku) | Komodo-Kaltstart in linearen Stufen A-F dokumentieren |
| erledigt 2026-05-29 | Komodo-Trockenlauf-Skript in `ops/restore-tests/` analog zu Immich vorbereiten |
| erledigt 2026-05-30 | Restore-Kommandos nach erstem Trockenlauf mit echten Pfaden ergaenzen |
| erledigt | Services-Recovery in `docs/DISASTER_RECOVERY.md` verlinken |
+9 -2
View File
@@ -1,6 +1,6 @@
# Service Catalog # Service Catalog
Stand: 2026-06-02 Stand: 2026-06-13
Dieser Katalog beschreibt produktive und repo-vorbereitete Dienste aus Sicht von Betrieb, Restore und KI-Kontext. Er basiert auf dem Repo-Sollzustand. Vor produktiven Eingriffen immer den Live-Zustand in Komodo/Docker pruefen. Dieser Katalog beschreibt produktive und repo-vorbereitete Dienste aus Sicht von Betrieb, Restore und KI-Kontext. Er basiert auf dem Repo-Sollzustand. Vor produktiven Eingriffen immer den Live-Zustand in Komodo/Docker pruefen.
@@ -75,11 +75,18 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und
| `monitoring-promtail` | Docker-Log-Collector fuer Monitoring-Loki | `monitoring/docker-compose.yml`, `monitoring/promtail/promtail-config.yml` | intern | Docker socket read-only, Docker json-file Logs, Loki | named volume `promtail_positions` | rebuildbar | nein | Dokumentierte Host-Observability-Ausnahme: `/var/run/docker.sock:/var/run/docker.sock:ro` und `/var/lib/docker/containers:ro`; keine Appdaten, nur Log-Discovery | | `monitoring-promtail` | Docker-Log-Collector fuer Monitoring-Loki | `monitoring/docker-compose.yml`, `monitoring/promtail/promtail-config.yml` | intern | Docker socket read-only, Docker json-file Logs, Loki | named volume `promtail_positions` | rebuildbar | nein | Dokumentierte Host-Observability-Ausnahme: `/var/run/docker.sock:/var/run/docker.sock:ro` und `/var/lib/docker/containers:ro`; keine Appdaten, nur Log-Discovery |
| `monitoring-node-exporter` | Host-Metriken fuer Prometheus | `monitoring/docker-compose.yml` | intern `:9100` | Host `/proc`, `/sys`, `/` read-only, Prometheus | kein kritischer Zustand | rebuildbar | nein | Host-Observability-Ausnahme mit read-only Rootfs/Proc/Sys-Mounts | | `monitoring-node-exporter` | Host-Metriken fuer Prometheus | `monitoring/docker-compose.yml` | intern `:9100` | Host `/proc`, `/sys`, `/` read-only, Prometheus | kein kritischer Zustand | rebuildbar | nein | Host-Observability-Ausnahme mit read-only Rootfs/Proc/Sys-Mounts |
| `monitoring-cadvisor` | Container-Metriken fuer Prometheus | `monitoring/docker-compose.yml` | intern `:8080` | Docker/Host read-only Mounts, Prometheus | kein kritischer Zustand | rebuildbar | nein | Host-Observability-Ausnahme fuer Container-Metriken; keine direkten Ports | | `monitoring-cadvisor` | Container-Metriken fuer Prometheus | `monitoring/docker-compose.yml` | intern `:8080` | Docker/Host read-only Mounts, Prometheus | kein kritischer Zustand | rebuildbar | nein | Host-Observability-Ausnahme fuer Container-Metriken; keine direkten Ports |
| `monitoring-influxdb3-core` | InfluxDB 3 Core fuer Home-Assistant-/Ecowitt-Langzeitdaten | `monitoring/docker-compose.yml` | Host-Port `8181` je `INFLUXDB_BIND_IP`, keine Public URL | Monitoring-Grafana, Home Assistant Writer | `/mnt/user/appdata/influxdb3/data`, `/mnt/user/appdata/influxdb3/plugins` | Tier 3 | nein | 2026-05-31 effektiv auf `127.0.0.1:8181` gebunden, also nicht LAN-exponiert; `user: "0"` ist fuer den lokalen Object-Store-Pfad dokumentiert; uebernimmt den bisherigen InfluxDB-Daten-/Token-Katalog; `401 Unauthorized` beim Curl ohne Token ist erwarteter Reachability-Test | | `monitoring-influxdb3-core` | InfluxDB 3 Core fuer Home-Assistant-/Ecowitt-Langzeitdaten | `monitoring/docker-compose.yml` | Host-Port `8181` je `INFLUXDB_BIND_IP`, keine Public URL | Monitoring-Grafana, Home Assistant Writer | `/mnt/user/appdata/influxdb3/data`, `/mnt/user/appdata/influxdb3/plugins` | Tier 3 | nein | 2026-05-31 effektiv auf `127.0.0.1:8181` gebunden, also nicht LAN-exponiert; vor dem HA-Writer muss entschieden werden, ob `INFLUXDB_BIND_IP` auf eine LAN-IP geht oder HA gezielt ein gemeinsames internes Netz mit InfluxDB bekommt. `user: "0"` ist fuer den lokalen Object-Store-Pfad dokumentiert; `401 Unauthorized` beim Curl ohne Token ist erwarteter Reachability-Test |
| `hermes-gateway` | Hermes Agent Gateway/API intern | `ops/hermes-agent/docker-compose.yml` | intern `8642` auf `hermes_net` | SSH Runner (VM 192.168.178.143), LLM Provider, optional Home Assistant | `/mnt/user/appdata/hermes-agent/data`, SSH key path | Tier 3, Borg/Share | nein | NAS-Stack bleibt deaktiviert, solange die separate Hermes-VM/Runner-Seite nicht wiederhergestellt ist; kein Docker-Socket | | `hermes-gateway` | Hermes Agent Gateway/API intern | `ops/hermes-agent/docker-compose.yml` | intern `8642` auf `hermes_net` | SSH Runner (VM 192.168.178.143), LLM Provider, optional Home Assistant | `/mnt/user/appdata/hermes-agent/data`, SSH key path | Tier 3, Borg/Share | nein | NAS-Stack bleibt deaktiviert, solange die separate Hermes-VM/Runner-Seite nicht wiederhergestellt ist; kein Docker-Socket |
| `hermes-dashboard` | Hermes Dashboard | `ops/hermes-agent/docker-compose.yml` | `https://hermes.kaleschke.info` via `${HERMES_DASHBOARD_HOST}` | `hermes-gateway`, Traefik + Authelia | shared read-only data mount | Tier 3, Borg/Share | ja + Authelia | Compose-Profil `dashboard`; aktuell VM-seitig offen, nicht Teil des NAS-Finalstarts | | `hermes-dashboard` | Hermes Dashboard | `ops/hermes-agent/docker-compose.yml` | `https://hermes.kaleschke.info` via `${HERMES_DASHBOARD_HOST}` | `hermes-gateway`, Traefik + Authelia | shared read-only data mount | Tier 3, Borg/Share | ja + Authelia | Compose-Profil `dashboard`; aktuell VM-seitig offen, nicht Teil des NAS-Finalstarts |
| `n8n` | Workflow-Automation; aktuell genutzt fuer Mail->LLM->Gitea-Issue (Inbox `Micha/mails`) | `apps/n8n/docker-compose.yml`, `apps/n8n/workflows/*.json` | `https://n8n.kaleschke.info` | Traefik (ohne pauschale Authelia, analog Komodo/Nextcloud), GMX IMAP, OpenAI API, Gitea API | `/mnt/user/appdata/n8n/data` (SQLite, Credentials, Workflows) | Tier 2, Borg + `n8n-data` (Credentials sind nur mit `N8N_ENCRYPTION_KEY` entschluesselbar) | ja, native Auth | Wegen Webhook-Endpunkten (`/webhook/*`) bewusst ohne `authelia@file`; eigene Login-/Owner-Auth bleibt Pflicht; `N8N_ENCRYPTION_KEY` ist Stack-ENV-Pflichtsecret, Verlust macht Credentials unbrauchbar. | | `n8n` | Workflow-Automation; aktuell genutzt fuer Mail->LLM->Gitea-Issue (Inbox `Micha/mails`) | `apps/n8n/docker-compose.yml`, `apps/n8n/workflows/*.json` | `https://n8n.kaleschke.info` | Traefik (ohne pauschale Authelia, analog Komodo/Nextcloud), GMX IMAP, OpenAI API, Gitea API | `/mnt/user/appdata/n8n/data` (SQLite, Credentials, Workflows) | Tier 2, Borg + `n8n-data` (Credentials sind nur mit `N8N_ENCRYPTION_KEY` entschluesselbar) | ja, native Auth | Wegen Webhook-Endpunkten (`/webhook/*`) bewusst ohne `authelia@file`; eigene Login-/Owner-Auth bleibt Pflicht; `N8N_ENCRYPTION_KEY` ist Stack-ENV-Pflichtsecret, Verlust macht Credentials unbrauchbar. |
## Smart Home
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|---|---|---|---|---|---|---|---|---|
| `homeassistant` | Zentrale Smart-Home-Steuerung, Energy Dashboard, Integrations-Hub | Runtime: `smart-home/docker-compose.yml`; Fachkonfiguration: Repo `smart-home-kalli` | `https://home.kaleschke.info`; zusaetzlich LAN-only Host-Bind `192.168.178.58:8123` nur fuer den Ecowitt-HTTP-Push | Traefik, `frontend_net`, `smarthome_net`, `smarthome-mosquitto`, SolarEdge-Wechselrichter `192.168.178.111:1502`, Fachrepo unter `/mnt/user/services/smart-home-kalli` | `/mnt/user/appdata/homeassistant` inkl. `.storage`, `secrets.yaml`, `trusted_proxies.yaml` und `custom_components` (HACS, `solaredge_modbus_multi`); YAML-Fachdateien read-only aus `/mnt/user/services/smart-home-kalli/home-assistant`; Agent-API-Tokens als Host-Secrets `ha_token_codex`/`ha_token_claude` | Tier 2, Borg + HA-native Backups; erstes HA-Backup am 2026-06-13 erzeugt/geprueft; Restore-Probe am 2026-06-13 erfolgreich, Report `/mnt/user/backups/restore-reports/homeassistant-2026-06-13.md`; Backup nach SolarEdge-Integration: `/mnt/user/appdata/homeassistant/backups/Custom_backup_2026.6.1_2026-06-13_14.59_48645373.tar`; Backup nach Energy-Dashboard-Konfiguration: `/mnt/user/appdata/homeassistant/backups/Custom_backup_2026.6.1_2026-06-13_15.59_25670583.tar` | ja, native HA-Auth | HA Container statt HAOS-VM; keine Add-ons, keine Supervised-Installation. `configuration.yaml` kommt aus dem Fachrepo, `.storage` wird nicht versioniert. `http.use_x_forwarded_for`, `trusted_proxies` und `ip_ban_enabled` sind aktiv. HA-MQTT-Integration `smarthome-mosquitto` ist seit 2026-06-13 geladen. SolarEdge ist seit 2026-06-13 lokal ueber `solaredge_modbus_multi` v3.2.5 angebunden: `SolarEdge Local`, `192.168.178.111:1502`, Device-ID `1`, Meter+Batterie-Erkennung an, Power-Control aus. Energy Dashboard ist fuer Netz, PV und Speicher konfiguriert; Kosten folgen mit Tibber. Komodo-Stack und Gitea-Webhook sind aktiv. Ecowitt-Ingress seit 2026-06-13 ueber LAN-only Host-Bind `192.168.178.58:8123` geloest; offen ist nur die GW3000-Customized-Server-Konfiguration. Naechster Produktivschritt: Tibber. |
| `smarthome-mosquitto` | MQTT-Broker fuer HA, spaeter ESPHome und Zigbee2MQTT | `smart-home/docker-compose.yml`, `smart-home/mosquitto/config/mosquitto.conf` | intern `smarthome_net:1883`; kein LAN-Port in Phase 1 | `smarthome_net`, Passwort-/ACL-Dateien in Appdata | `/mnt/user/appdata/mosquitto/config`, `/mnt/user/appdata/mosquitto/data`, `/mnt/user/appdata/mosquitto/log` | Tier 2, Borg; Passwortdatei, ACLs und persistente Broker-Daten relevant; Restore-Probe am 2026-06-13 erfolgreich | nein | Authentifizierter Publish/Subscribe-Smoke und retained Topic nach Broker-Restart am 2026-06-13 erfolgreich. Home Assistant verbindet sich als User `homeassistant`. LAN-Port `1883` erst in ESPHome-Phase mit ACLs und per-Device-Usern. |
## Host Operations ## Host Operations
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs | | Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
+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 | | Disk1 (Array) | WDC WD60EFAX-68JH4N1 (`WD-WX32D90PC0V0`) | **XFS** auf `md1p1` | 5.5T nutzbar | Nutzdaten, Backups, Services | NTFS-zu-XFS-Migration Phase 2 abgeschlossen am 2026-05-25 |
| Parity | TOSHIBA HDWG480 (`2460A03VFA3H`) | — (keine FS) | 7.3T | Redundanz für Array | Unverändert | | Parity | TOSHIBA HDWG480 (`2460A03VFA3H`) | — (keine FS) | 7.3T | Redundanz für Array | Unverändert |
| Boot | Samsung Flash Drive (`0375125090000587`) | FAT32 | 59.8G | Unraid-OS, Konfiguration | Regelmäßig per Flash-Backup gesichert | | Boot | Samsung Flash Drive (`0375125090000587`) | FAT32 | 59.8G | Unraid-OS, Konfiguration | Regelmäßig per Flash-Backup gesichert |
| Externe Backup-Platte | H:/ `Externe HDD` am Windows-PC | NTFS | 8.0T | Nearline-Pull-Ziel für kritische Restore-Artefakte | Kein Off-site-/Airgap-Ersatz; Pull-Workflow in `docs/H_DRIVE_NEARLINE_PULL.md` | | Externe Backup-Platte | H:/ `Externe HDD` am Windows-PC | NTFS | 8.0T | Nearline-Pull-Ziel für kritische Restore-Artefakte | Kein Off-site-/Airgap-Ersatz; Pull-Workflow in `ops/h-drive-nearline/README.md` |
Physikalische Basisdaten sind aus `docs/HARDWARE_INVENTORY.md` und dem Host-Readout vom 2026-05-27 übernommen. Detailwerte zu SMART/Health bleiben dort die autoritative Quelle; dieses Dokument hält die Storage-Policy. Physikalische Basisdaten sind aus `docs/HARDWARE_INVENTORY.md` und dem Host-Readout vom 2026-05-27 übernommen. Detailwerte zu SMART/Health bleiben dort die autoritative Quelle; dieses Dokument hält die Storage-Policy.
@@ -384,4 +384,4 @@ Wenn Hermes-Worker auf weiteren Hosts skaliert: dieser Storage-Layout-Plan gilt
Status: **Active v1.4 seit 2026-05-27**. Status: **Active v1.4 seit 2026-05-27**.
Detailhistorie und alte Review-Tabellen liegen in der Git-Historie. Aktuelle Folgepunkte stehen nicht mehr hier, sondern in `docs/AUDIT_2026-05-25_TODO.md`. Detailhistorie und alte Review-Tabellen liegen in der Git-Historie. Aktuelle Folgepunkte stehen nicht mehr hier, sondern in `docs/MASTER_TODO.md`.
-151
View File
@@ -1,151 +0,0 @@
# Weekend Execution Plan - 2026-06-05 bis 2026-06-07
Ziel: Bis Ende des Wochenendes alle offenen To-dos aus `docs/MASTER_TODO.md`
entweder erledigen, verifiziert schliessen, oder bewusst als geparkt/extern
blockiert markieren. Nicht jeder Punkt ist realistisch "fertig" im Sinne von
technisch umgesetzt: Family-Onboarding, zweite Hardware, USV und WAN-Failover
brauchen Operator- oder Hardware-Entscheidungen.
## Arbeitsregeln
- Secrets niemals in Chat, Logs oder Repo schreiben.
- Homelab-Aenderungen nur via GitOps, keine direkten Komodo-/Docker-Hotfixes.
- Destruktive Windows- oder Host-Schritte nur nach expliziter Freigabe.
- Ergebnis jedes abgeschlossenen Punkts in der Detaildoku und in
`docs/MASTER_TODO.md` nachziehen.
- Am Ende: ein sauberer Commit-Block; Push erst nach Freigabe.
## Owner-Aufteilung
| Owner | Fokus | Ergebnis |
|---|---|---|
| Codex | `baerchen` Veeam, Doku-Konsolidierung, lokale Checks, Commit-Vorbereitung | Veeam-Erstbackup geprueft, Recovery-Test dokumentiert, Masterliste aktualisiert |
| Claude | Family-Onboarding-Paket, Network-/Tailscale-Entscheidungen, Hardware-/Todo-Konsolidierung, nicht-destruktive Runbooks | Konkrete Doku-Patches, ausfuehrbare Checklisten, klare Operator-Fragen statt diffuser TBDs |
| Operator | Physische/GUI-Schritte, Secrets, Familie, Hardwareentscheidungen | Recovery-USB booten, Passwoerter/Keys bereitstellen, Family-Onboarding starten/entscheiden |
## Codex-Aufgaben
| Prioritaet | Aufgabe | Abschlusskriterium |
|---|---|---|
| P1 | Veeam-Erstbackup `baerchen-c-image` pruefen | **erledigt 2026-06-05:** Full-Lauf geschrieben, Veeam-GUI 53,8 GB, Dauer 0:11:31, MetaCheck 0 Fehler/0 Warnungen; Storage Encryption war nicht aktiv und ist als Operator-Entscheidung dokumentiert |
| P1 | Recovery-USB-Test begleiten | `VEEAMRE` bootet, SMB-Ziel sichtbar, Restore Point sichtbar, vor Restore abgebrochen |
| P1 | `windows-image-backup-baseline.md` finalisieren | Erster Lauf und Teststatus mit Datum eingetragen |
| P1 | `docs/MASTER_TODO.md` nach jedem Abschluss aktualisieren | erledigte Punkte entfernt oder in "geschlossen" vermerkt |
| P2 | Alte Windows-Reinstall-Doku bereinigen | ueberholte WinRE-/Admin-To-dos als erledigt/ueberholt markiert |
| P2 | Git-Status sortieren | Eigene Aenderungen klar von vorhandenen User-Aenderungen getrennt |
| P2 | Commit vorbereiten | Commit-Message-Vorschlag und Datei-Liste bereit; kein Push ohne Freigabe |
## Claude-Aufgaben
Claude soll parallel nur repo-seitig arbeiten und keine produktiven Host-Aenderungen
ausfuehren. Die Aufgaben sind bewusst als echte Doku-/Planungsarbeit formuliert,
nicht nur als Pruefaufgaben:
| Prioritaet | Aufgabe | Abschlusskriterium |
|---|---|---|
| P1 | `docs/MASTER_TODO.md` gegen Detaildokus gegenpruefen | **erledigt 2026-06-05:** Sync-Notiz in `docs/AUDIT_2026-05-25_TODO.md`, Masterliste aktualisiert |
| P1 | Restore-Backlog aktualisieren | **erledigt 2026-06-05:** erledigte Kandidaten aus `docs/RESTORE_MATRIX.md` bereinigt |
| P1 | Family-Onboarding in ein ausfuehrbares Session-Paket umwandeln | **erledigt 2026-06-05:** `docs/FAMILY_ONBOARDING.md` enthaelt Vorbereitungs-, Termin- und Erfolgskriterien ohne Secret-Werte |
| P1 | `docs/NETWORK_INVENTORY.md` TBDs in Entscheidungen oder konkrete Operator-Fragen verwandeln | **erledigt 2026-06-05:** Tailscale IPv6/Exit Node/Subnet Router/ACL-Policy sind als Messaufgabe/Operator-Entscheidung formuliert; Gast-/WAN-Pfade sind geparkt oder mit Vorbedingungen versehen |
| P2 | Nicht-destruktive Runbooks fuer offene Restore-Tests vorbereiten | **erledigt 2026-06-05:** Runbook-Stubs fuer Unraid Flash, AdGuard, Tailscale, Redis 8 in `docs/RESTORE_MATRIX.md` |
| P2 | `docs/AUDIT_2026-05-25_TODO.md` und `MASTER_TODO.md` synchronisieren | **erledigt 2026-06-05:** keine doppelten oder widerspruechlichen P1/P2-Punkte |
| P2 | Windows-Reinstall-Altdoku auf ueberholte To-dos pruefen | **erledigt 2026-06-05:** WinRE/Admin-Check-Altlasten als erledigt/ueberholt markiert |
| P2 | Hardware-/Betriebsentscheidungen konsolidieren | **teilweise erledigt 2026-06-05:** USV und Cold-Backup-Rotation sind entschieden/geparkt; Masterliste fuehrt sie nicht mehr als aktive Umsetzungsaufgaben |
| P3 | Geparkte Punkte klassifizieren | Family/USV/WAN/CrowdSec/OIDC klar als Entscheidung statt Umsetzungsarbeit markiert |
## Operator-Aufgaben
| Prioritaet | Aufgabe | Abschlusskriterium |
|---|---|---|
| P1 | Veeam-Encryption-Entscheidung treffen | Fuer den ersten Full-Lauf ist kein Veeam-Encryption-Passwort noetig; falls Storage Encryption aktiviert wird, Passwort in Vaultwarden anlegen und neues Full erzeugen |
| P1 | Recovery-USB physisch booten | Boot ins Veeam-Recovery-System gelingt |
| P1 | Keine echten Restore-Ziele bestaetigen | Restore-Test wird vor destruktiver Datentraegerauswahl abgebrochen |
| P2 | BitLocker-Entscheidung treffen | `aktivieren`, `spaeter`, oder `bewusst aus` dokumentiert |
| P2 | Family-Onboarding real starten oder terminieren | konkreter Termin/Personenkreis statt offenem Wunsch |
| P3 | Hardware-Entscheidungen | USV/Cold-Rotation/WAN-Failover als kaufen, spaeter, oder bewusst nein markieren |
## Realistische Wochenend-Ziele
Bis Sonntagabend realistisch fertig:
- `baerchen` Veeam-Erstbackup verifiziert.
- `baerchen` Recovery-USB-Test ohne Restore verifiziert.
- Veeam-/BitLocker-Doku bereinigt.
- Master-To-do-Liste bereinigt.
- Restore-Backlog sortiert.
- Alte/ueberholte To-dos als erledigt/ueberholt markiert.
- Blockierte Punkte explizit als Betreiber-/Hardware-/Familienentscheidung markiert.
Nicht realistisch ohne externe Voraussetzungen:
- End-to-end-DR-Drill ohne zweite Hardware.
- Family-Onboarding ohne Familie/Geraete.
- USV erledigen ohne Kauf.
- WAN-Failover erledigen ohne Mobilfunk-/Router-Entscheidung.
- Dedizierter SMB-User ohne bewusste Unraid-User-/Share-Aenderung.
## Prompt fuer Claude
```text
Du bist Claude im KalliLab CORE Homelab-Repo.
Arbeitsziel fuer dieses Wochenende:
Hilf, alle offenen To-dos aus `docs/MASTER_TODO.md` bis Sonntagabend entweder
zu erledigen, sauber zu dokumentieren, oder bewusst als geparkt/blockiert zu
klassifizieren. Arbeite repo-seitig, keine produktiven Host-Aenderungen.
Pflichtregeln:
- Lies zuerst `CLAUDE.md`.
- Lies danach `HOMELAB_ARCHITECTURE_MASTER_V2.md`, `docs/WORKFLOW.md`,
`docs/README.md`, `docs/REPO_MAP.md`, `docs/MASTER_TODO.md`,
`docs/RESTORE_MATRIX.md`, `docs/DISASTER_RECOVERY.md`,
`docs/SECRETS_MAP.md` und `ops/windows-reinstall/docs/windows-image-backup-baseline.md`.
- Keine Secrets ins Repo. Nur Secret-Namen, Pfade und Ablageorte dokumentieren.
- Keine Komodo-/Docker-/Host-Hotfixes. Keine produktiven Schreibbefehle auf dem Homelab.
- Keine destruktiven Aktionen.
- Beachte vorhandene uncommitted Aenderungen; nichts revertieren, was du nicht selbst gemacht hast.
Konkrete Aufgaben:
1. Wandle `docs/FAMILY_ONBOARDING.md` von einer guten Erklaerseite in ein
ausfuehrbares Session-Paket um:
- 30-Minuten-Ablauf fuer das erste echte Onboarding
- Checkliste pro Geraet/Person ohne Namen oder Secret-Werte
- klare Abschlusskriterien fuer Vaultwarden, Immich und Mealie
- Liste der Operator-Fragen, falls Konten/Startpasswoerter fehlen
2. Bereinige `docs/NETWORK_INVENTORY.md`:
- Tailscale IPv6, Exit Node, Subnet Router und ACL-Policy nicht als
unerklaerte `TBD` stehen lassen
- wenn nicht verifizierbar: als konkrete Operator-Frage oder bewusst offene
Entscheidung formulieren
- Gast-/IoT-Zugriff als Entscheidungspfad dokumentieren, nicht als vage
Altlast
3. Ziehe `docs/MASTER_TODO.md` nach deinen Edits nach:
- echte naechste Schritte in P1/P2
- geparkte Entscheidungen nur im geparkten/geschlossenen Bereich
- keine Duplikate zu `docs/AUDIT_2026-05-25_TODO.md`
4. Falls du weitere diffuse TBDs in Hardware/Network/Family findest: nicht nur
melden, sondern in konkrete Entscheidung, geparkten Punkt oder naechsten
Operator-Schritt umformulieren.
5. Schon erledigte Restore-/Windows-Doku-Aufgaben nicht erneut bearbeiten,
ausser du findest einen klaren Widerspruch.
6. Am Ende liefere:
- geaenderte Dateien
- welche Punkte geschlossen wurden
- welche Punkte blockiert/geparkt bleiben und warum
- welche Operator-Schritte noch noetig sind
Nicht tun:
- Keine Secrets anzeigen oder erfinden.
- Kein Push.
- Kein `docker`, `ssh` oder Host-Schreibzugriff.
- Kein BitLocker, keine Veeam-Aenderung, keine Unraid-User-/Share-Aenderung.
```
## Abschlusskriterien fuer Sonntag
- `docs/MASTER_TODO.md` ist die fuehrende Liste.
- Alle erledigten Punkte haben Beleg in der Detaildoku.
- Alle nicht erledigbaren Punkte sind als blockiert/geparkt mit Grund markiert.
- `git status` ist verstanden: eigene Doku-Aenderungen vs. bestehende
User-Aenderungen sind getrennt.
- Commit ist vorbereitet, Push erfolgt nur nach Operator-Freigabe.
-41
View File
@@ -1,41 +0,0 @@
# Weekend Status - 2026-06-05
Kurzlebiges Arbeitsboard fuer den Wochenend-Sprint. Fuehrende Liste bleibt
`docs/MASTER_TODO.md`; dieses Board haelt nur den aktuellen Arbeitsstand fest.
## Jetzt laufend
| Owner | Aufgabe | Status | Naechster Schritt |
|---|---|---|---|
| Codex | Veeam-Erstbackup `baerchen-c-image` | erledigt | Erster Full-Lauf 2026-06-05 geschrieben; Recovery-Test bleibt offen |
| Codex | Veeam-Verifikationshilfe | erledigt | Hilfsskript bleibt fuer spaetere Checks verfuegbar |
| Claude | Restore-/Altdoku-Bereinigung | erledigt | Keine weitere Arbeit an Veeam/Windows/Restore-Matrix ohne neuen Widerspruch |
| Claude | Family-/Network-Ausfuehrungspaket | erledigt | Masterliste und Weekend-Plan sind nachgezogen |
## Naechste Operator-Schritte
| Zeitpunkt | Aufgabe | Ergebnis, das dokumentiert wird |
|---|---|---|
| Erledigt | Veeam-Erstbackup `baerchen-c-image` pruefen | 2026-06-05 19:46, Full-Lauf erfolgreich, Veeam-GUI 53,8 GB, Dauer 0:11:31 |
| Als naechstes | Recovery-USB `VEEAMRE` booten | Boot OK, Netzwerk OK, SMB-Ziel sichtbar |
| Im Recovery-Test | Restore Point anzeigen; falls spaeter verschluesselt: Passwort testen | Restore Point sichtbar; vor echtem Restore abgebrochen |
| Spaeter | BitLocker-Entscheidung treffen | `aktivieren`, `spaeter`, oder `bewusst aus` in `docs/MASTER_TODO.md`/Baseline nachziehen |
## Heute bereits geschlossen
| Thema | Ergebnis |
|---|---|
| WinRE/Admin-Altlasten | In Windows-Reinstall-Doku als erledigt/ueberholt markiert |
| Restore-Test-Kandidaten | Erledigte Kandidaten aus der aktiven Liste entfernt; Stubs fuer offene Kandidaten ergaenzt |
| Family-Onboarding | Aus der Familien-Doku wurde ein konkreter 30-45-Minuten-Terminablauf mit Vorbereitung und Erfolgskriterien |
| Network-TBDs | Tailscale-/Gastnetz-/WAN-Failover-Punkte wurden in Messaufgaben, Vorbedingungen oder geparkte Entscheidungen umgewandelt |
| Veeam-Erstbackup | Full-Lauf 2026-06-05 erfolgreich geschrieben: Veeam-GUI 53,8 GB, Dauer 0:11:31, MetaCheck 0 Fehler/0 Warnungen, VSS success; Veeam Storage Encryption war nicht aktiv |
| Cold-Backup-Rotation | Bewusst Hetzner-only; kein aktives Todo mehr |
| USV | Bewusst auf Q3/2026 geparkt; Power-Loss bleibt akzeptiertes Risiko |
## Nicht ohne neue Freigabe anfassen
- Keine BitLocker-Aktivierung.
- Keine Aenderung am Veeam-Job oder Encryption-Status.
- Keine Unraid-User-/Share-Aenderung.
- Keine produktiven Host- oder Docker-Schreibbefehle.
+7 -1
View File
@@ -369,7 +369,13 @@ Wenn ein Stack `webhook_enabled` in Komodo hatte, zusaetzlich pruefen, ob der zu
## Dokumentationspflicht ## Dokumentationspflicht
Nach jeder erfolgreichen Migration oder relevanten Aenderung muessen diese Dateien geprueft werden: Es gilt "ein Fakt, ein Zuhause" (`docs/REPO_MAP.md` Doku-Regeln): aktualisiert
wird das jeweils zustaendige Dokument plus `docs/README.md`-Index, nicht
mehrere Kopien. Nach jeder relevanten Aenderung pruefen, **welche** dieser
Zuhause betroffen sind:
- `docs/DECISIONS.md` falls eine bewusste Entscheidung getroffen oder revidiert wurde
- `docs/MASTER_TODO.md` falls sich der Status offener Punkte aendert
- `docs/SECRETS_MAP.md` - `docs/SECRETS_MAP.md`
- `docs/ROLLBACK.md` - `docs/ROLLBACK.md`
@@ -0,0 +1,401 @@
# Homelab-Doku-Optimierung — Analyse und Vorschlag 2026-06-11
Typ: Analyse / Optimierungsvorschlag · Stand: 2026-06-11 · Status: **umgesetzt am 2026-06-11** (archiviert; siehe `docs/DECISIONS.md` Eintrag 2026-06-11). Nicht umgesetzt blieben nur: Hermes-README-Kuerzung (beim Review 2026-07-25), PDF-Ablage extern (Operator), optionale Projekte aus Abschnitt 13.
Read-only-Analyse der gesamten Markdown-Dokumentation (Stand `master`, lokale
Arbeitskopie 2026-06-11). Es wurde nichts gelöscht, verschoben oder verändert;
dieses Dokument ist der einzige neue Inhalt. Abgrenzung: `docs/homelab-optimierung.md`
(2026-06-10) bewertet die **technische** Betriebsebene; dieses Dokument bewertet
ausschließlich die **Dokumentation und ihre Regeln**.
---
## 1. Executive Summary
Die Doku ist inhaltlich exzellent und ungewöhnlich diszipliniert gepflegt —
das Problem ist nicht Qualität oder Veralterung, sondern **Volumen, Mehrfachpflege
und fehlende Lebenszyklus-Regeln**. Kennzahlen:
- **74 versionierte Markdown-Dateien, ~9.400 Zeilen** (davon `docs/`: 35 Dateien / ~5.050 Zeilen, `ops/`: 34 Dateien).
- Praktisch alle Dateien wurden in den letzten 4 Wochen angefasst — es gibt **kein Stale-Problem, aber ein Pflegelast-Problem**.
- Ein einzelner Sachverhalt wird heute an **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".
@@ -45,7 +45,8 @@ Noch offen:
- Manuelle Screenshots in `H:\Windows-Neuaufsetzen-Backup\14_Screenshots` ablegen. - Manuelle Screenshots in `H:\Windows-Neuaufsetzen-Backup\14_Screenshots` ablegen.
- BitLocker-Status mit Adminrechten pruefen. **Nachlauf 2026-06-05:** Status - BitLocker-Status mit Adminrechten pruefen. **Nachlauf 2026-06-05:** Status
wurde geprueft; C:/D:/E:/G:/H: sind `FullyDecrypted`, Protection `Off`. wurde geprueft; C:/D:/E:/G:/H: sind `FullyDecrypted`, Protection `Off`.
Offen bleibt nur die bewusste BitLocker-Entscheidung. **Entscheidung 2026-06-06:** BitLocker bleibt bewusst deaktiviert; Recovery
laeuft ueber Veeam-Image, kein BitLocker-Key-Management.
- Passwortmanager, 2FA-Recovery-Codes und Browser-Sync manuell pruefen. **Erledigt 2026-06-06 laut Operator-Bestaetigung.** - Passwortmanager, 2FA-Recovery-Codes und Browser-Sync manuell pruefen. **Erledigt 2026-06-06 laut Operator-Bestaetigung.**
- Banking4-Speicherort explizit pruefen. **Erledigt 2026-06-06 laut Operator-Bestaetigung.** - Banking4-Speicherort explizit pruefen. **Erledigt 2026-06-06 laut Operator-Bestaetigung.**
- Banking4 im Programm selbst oeffnen und aktuellen Datentresor/Backup-Export bestaetigen. Der Key und der Datentresor sind bereits lokal auf H: gesichert. **Erledigt 2026-06-06 laut Operator-Bestaetigung.** - Banking4 im Programm selbst oeffnen und aktuellen Datentresor/Backup-Export bestaetigen. Der Key und der Datentresor sind bereits lokal auf H: gesichert. **Erledigt 2026-06-06 laut Operator-Bestaetigung.**
@@ -469,7 +470,7 @@ Direkt nach der Installation:
- Windows-Aktivierung prüfen - Windows-Aktivierung prüfen
- Laufwerksbuchstaben sauber vergeben - Laufwerksbuchstaben sauber vergeben
- Windows Defender und Firewall prüfen - Windows Defender und Firewall prüfen
- BitLocker bewusst aktivieren oder deaktiviert lassen - BitLocker bewusst deaktiviert lassen (Entscheidung 2026-06-06)
- Wiederherstellungspunkt erstellen - Wiederherstellungspunkt erstellen
Basisprogramme: Basisprogramme:
+25
View File
@@ -0,0 +1,25 @@
# Archiv
Typ: Index · Stand: 2026-06-11 · Status: aktiv
Abgeschlossene Snapshots, Drills, Audits und abgeloeste Plaene mit Referenzwert.
Inhalte hier werden nicht mehr gepflegt; die fuehrenden Quellen stehen in der
Spalte "Abgeloest durch". Sprint-Boards und erledigte Arbeitslisten werden nicht
archiviert, sondern geloescht (Git-Historie ist das Archiv).
## 2026
| Datei | Was es war | Abgeloest durch / Ergebnis eingearbeitet in |
|---|---|---|
| `2026/BACKUP_AUDIT_STATUS_QUO_2026-04-15.md` | Ist-Aufnahme Backup vor der Borg-Migration | `ops/borg-ui/BACKUP_SCOPE.md` |
| `2026/DR_DRILL_2026-06-03.md` | DR-Tabletop-Drill, 23 Befunde | Doku-Fixes in `docs/DISASTER_RECOVERY.md` und `docs/EXTERNAL_DEPENDENCIES.md` |
| `2026/system-audit-baerchen-2026-06-05.md` | Read-only-Audit der Windows-Workstation | Befunde abgearbeitet bzw. Operator-Entscheidungen in `docs/DECISIONS.md` |
| `2026/dr-workstation-readiness-2026-06-06.md` | Automatischer Readiness-Check DR-Workstation | `docs/EXTERNAL_DEPENDENCIES.md` Abschnitt "DR-Workstation Bare-Metal-Kit" |
| `2026/HOME_ASSISTANT_INFLUXDB_ECOWITT.md` | Zielbild-Entwurf HA -> InfluxDB 3; HA existiert seit Crash nicht mehr | Neuaufbau braucht neue Inventur; Entwurf nur Referenz |
| `2026/windows-neuaufsetzen-masterplan.md` | Masterplan Windows-Neuaufsetzen Mai 2026 (abgeschlossen) | Aktiv bleibt nur `ops/windows-reinstall/` (Skripte, Veeam-Baseline, Laufwerksstruktur) |
| `2026/postdelta-2026-06-04.md` | PostDelta-Datenstand nach Neuinstallation | Projekt abgeschlossen |
| `2026/programme-entscheidung-2026-06-04.md` | Programm-Reinstall-Entscheidungen | Projekt abgeschlossen |
| `2026/boot-cleanup-plan-2026-06-04.md` | BCD-/Boot-Bereinigungsplan | Umgesetzt; Endzustand im System-Audit belegt |
| `2026/postinstall-erstes-ziel-codex.md` | Postinstall-Arbeitsauftrag | Projekt abgeschlossen |
| `2026/baerchen-app-license-readiness-2026-06-06.md` | App-/Lizenz-Readiness-Check | Projekt abgeschlossen |
| `2026/homelab-doku-optimierung-2026-06-11.md` | Analyse + Vorschlag zur Doku-Konsolidierung | umgesetzt 2026-06-11; Regeln in `docs/REPO_MAP.md`, Entscheidung in `docs/DECISIONS.md` |
+4 -4
View File
@@ -1,6 +1,6 @@
# Runbook: Komodo Bulk-Deploy schlaegt mit DNS `connection refused` fehl # Runbook: Komodo Bulk-Deploy schlaegt mit DNS `connection refused` fehl
Stand: 2026-06-10 · Typ: Runbook / ADR-light · Status: Sofortmassnahme empfohlen, noch nicht umgesetzt Stand: 2026-06-11 · Typ: Runbook / ADR-light · Status: **Sofortmassnahme aktiv** (Host-DNS-Fallback gesetzt 2026-06-11 bzw. frueher)
## Symptom ## Symptom
@@ -19,12 +19,12 @@ Der Host nutzt **AdGuard Home als einzigen Resolver** (`/etc/resolv.conf` = nur
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. 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) ## Sofortmassnahme (Schicht 1) — umgesetzt
Unraid -> Settings -> Network Settings -> `eth0`: Unraid -> Settings -> Network Settings -> `eth0`:
- DNS server 1: `192.168.178.58` (AdGuard, bleibt) - DNS server 1: `192.168.178.58` (AdGuard)
- **DNS server 2: `192.168.178.1`** (FritzBox) -> Apply - **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. Damit ueberleben Registry-Pulls einen kurzen AdGuard-Ausfall via Resolver-Failover. Im Normalbetrieb wird weiter DNS1 (AdGuard) genutzt, der Filter bleibt aktiv.
+224
View File
@@ -0,0 +1,224 @@
# Smart-Home Bootstrap
Ziel: Den Stack `smart-home/` auf Kallilabcore initial startklar machen, ohne
Secrets oder UI-State ins Git zu schreiben.
## 1. Fachrepo auf dem Host bereitstellen
```sh
cd /mnt/user/services
git clone https://git.kaleschke.info/Micha/smart-home-kalli.git smart-home-kalli
cd smart-home-kalli
git checkout main
```
Der Home-Assistant-Container mountet daraus einzelne YAML-Dateien read-only nach
`/config`.
## 2. Home-Assistant-Appdata vorbereiten
```sh
mkdir -p /mnt/user/appdata/homeassistant
cp /mnt/user/services/smart-home-kalli/secrets-template/secrets.yaml.example \
/mnt/user/appdata/homeassistant/secrets.yaml
cp /mnt/user/services/smart-home-kalli/secrets-template/trusted_proxies.yaml.example \
/mnt/user/appdata/homeassistant/trusted_proxies.yaml
```
Danach `trusted_proxies.yaml` auf das echte Traefik-/`frontend_net`-Subnetz
anpassen:
```sh
docker network inspect frontend_net
```
## 3. Mosquitto vorbereiten
```sh
mkdir -p /mnt/user/appdata/mosquitto/config \
/mnt/user/appdata/mosquitto/data \
/mnt/user/appdata/mosquitto/log
docker run --rm -it \
-v /mnt/user/appdata/mosquitto/config:/mosquitto/external_config \
eclipse-mosquitto:2.0.22 \
mosquitto_passwd -c /mosquitto/external_config/passwordfile homeassistant
cat > /mnt/user/appdata/mosquitto/config/aclfile <<'EOF'
user homeassistant
topic readwrite #
EOF
```
Das initiale Passwort anschliessend in
`/mnt/user/appdata/homeassistant/secrets.yaml` eintragen. LAN-Port `1883` bleibt
in Phase 1 geschlossen.
## 4. Stack deployen
Komodo-Stack:
- Repo: `homelab-infra`
- Pfad: `smart-home/docker-compose.yml`
- Branch: nach Review `master`
- Status 2026-06-13: Stack `smart-home` existiert in Komodo, Gitea-Webhook ist
aktiv, `deployed_hash == latest_hash`.
Nach dem Start pruefen:
```sh
docker ps --filter name=homeassistant
docker ps --filter name=smarthome-mosquitto
docker logs --tail=100 homeassistant
docker logs --tail=100 smarthome-mosquitto
```
## 5. Smoke-Test
- `https://home.kaleschke.info` zeigt die Home-Assistant-Oberflaeche.
- Nach Owner-Onboarding: keine Authelia-ForwardAuth mehr vor HA; HA nutzt native
Auth plus `http.ip_ban_enabled`.
- `trusted_proxies.yaml` deckt das `frontend_net` ab; damit wertet HA die echte
Client-IP aus `X-Forwarded-For` aus.
- Keine Trusted-Proxy-Fehler im HA-Log.
- MQTT-Broker-Smoke: `homeassistant`-User aus `secrets.yaml` kann gegen
`smarthome-mosquitto:1883` publish/subscriben.
- HA-MQTT-Integration ist verbunden: Config-Entry `smarthome-mosquitto` ist
`loaded`, Mosquitto sieht einen HA-Client mit User `homeassistant`.
- HA-native Backup-Erstellung funktioniert; Beispielartefakt:
`/mnt/user/appdata/homeassistant/backups/Custom_backup_2026.6.1_2026-06-13_08.25_38034438.tar`.
- Backup-Artefakt ist lesbar (`backup.json`, `homeassistant.tar.gz`).
- Agent-API-Tokens liegen als Host-Secrets unter
`/mnt/user/appdata/secrets/ha_token_codex` und
`/mnt/user/appdata/secrets/ha_token_claude`; Werte nie ausgeben oder in Git
schreiben. Die Tokens sind nur mit erhaltenem HA-Auth-State in `.storage`
brauchbar und bei Verdacht in HA zu widerrufen.
## 6. Fachrepo-Update
Das Fachrepo `/mnt/user/services/smart-home-kalli` ist kein eigener
Komodo-Stack. Aenderungen wirken erst nach diesem Host-Ablauf:
```sh
cd /mnt/user/services/smart-home-kalli
git pull --ff-only origin main
docker compose -f /mnt/user/services/stacks/smart-home/smart-home/docker-compose.yml \
up -d --force-recreate homeassistant
```
Der Force-Recreate ist Pflicht, weil `configuration.yaml`, `automations.yaml`,
`scripts.yaml` und `scenes.yaml` als Einzeldateien in den Container gemountet
werden. Nach einem `git pull` kann Docker sonst noch den alten Datei-Inode sehen
(`Stale file handle`).
## 7. UI-Editor-Politik
`automations.yaml`, `scripts.yaml` und `scenes.yaml` sind read-only aus Git
gemountet. Der Home-Assistant-UI-Editor fuer diese Dateien ist deshalb nicht der
primaere Schreibweg. Automationen und Scripts werden in Git gepflegt; UI-State
und Integrations-State bleiben in `.storage` und werden per Borg gesichert.
## 8. Abnahmebedingung
Vor produktiven Energie-Automationen muss ein Restore-Test fuer
`/mnt/user/appdata/homeassistant`, `/mnt/user/appdata/mosquitto` und den Clone
`/mnt/user/services/smart-home-kalli` dokumentiert sein.
Wichtig: Ein erfolgreich erzeugtes HA-Backup ist nur die Voraussetzung. Das Gate
ist erst geschlossen, wenn eine Restore-Probe in einem isolierten Testpfad
dokumentiert ist.
Status 2026-06-13: Gate geschlossen. Die isolierte Restore-Probe war
erfolgreich:
- Report: `/mnt/user/backups/restore-reports/homeassistant-2026-06-13.md`
- Test: HA-native Backup + Mosquitto-Appdata + Fachrepo-Clone
- Ergebnis: HA HTTP/API/check_config gruen, MQTT Publish/Subscribe und retained
Topic nach Broker-Restart gruen
Status 2026-06-13: HA-MQTT-Integration ist produktiv verbunden.
Verifikation:
```sh
TOKEN=$(cat /mnt/user/appdata/secrets/ha_token_codex)
curl -ksS -H "Authorization: Bearer $TOKEN" \
https://home.kaleschke.info/api/config/config_entries/entry
docker logs --tail=120 smarthome-mosquitto
docker exec homeassistant python -m homeassistant --script check_config --config /config
```
Erwartung: Ein MQTT-Config-Entry `smarthome-mosquitto` mit Status `loaded`, ein
Mosquitto-Client mit User `homeassistant`, und `check_config` ohne Fehler.
## 9. SolarEdge lokal
Status 2026-06-13: SolarEdge ist lokal per Modbus TCP angebunden.
- Integration: HACS/Custom `solaredge_modbus_multi` v3.2.5
- HA-Config-Entry: `SolarEdge Local`, Status `loaded`
- Wechselrichter: `192.168.178.111:1502`
- Modbus Device-ID: `1`
- Optionen: Polling 60 Sekunden, Meter-Erkennung aktiv, Batterie-Erkennung
aktiv, Extras aus, Power-Control aus
Verifikation:
```sh
TOKEN=$(cat /mnt/user/appdata/secrets/ha_token_codex)
curl -ksS -H "Authorization: Bearer $TOKEN" \
https://home.kaleschke.info/api/config/config_entries/entry
curl -ksS -H "Authorization: Bearer $TOKEN" \
https://home.kaleschke.info/api/states
docker exec homeassistant python -m homeassistant --script check_config --config /config
```
Wichtige Energy-Dashboard-Kandidaten:
- PV-Produktion: `sensor.solaredge_local_i1_ac_energy`
- Netzbezug: `sensor.solaredge_local_i1_m1_ac_energy_imported`
- Einspeisung: `sensor.solaredge_local_i1_m1_ac_energy_exported`
- Batterie geladen: `sensor.solaredge_local_i1_b1_energy_import`
- Batterie entladen: `sensor.solaredge_local_i1_b1_energy_export`
- Batterie-SoC: `sensor.solaredge_local_i1_b1_state_of_energy`
Nach der Integration wurde ein HA-native Backup erzeugt und tar-geprueft:
`/mnt/user/appdata/homeassistant/backups/Custom_backup_2026.6.1_2026-06-13_14.59_48645373.tar`.
Trade-off: Dieser Pfad ist lokal und liefert Inverter, Meter und Batterie ohne
Cloud-API, nutzt aber eine Custom-Integration. Bei HA-Core-Upgrades auf Warnungen
zu `solaredge_modbus_multi` achten.
## 10. Energy Dashboard
Status 2026-06-13: Energy Dashboard ist ueber die Home-Assistant-WebSocket-API
konfiguriert und validiert.
Konfiguration:
- Netz: Bezug `sensor.solaredge_local_i1_m1_ac_energy_imported`, Einspeisung
`sensor.solaredge_local_i1_m1_ac_energy_exported`
- PV: Produktion `sensor.solaredge_local_i1_ac_energy`, Live-Leistung
`sensor.solaredge_local_i1_ac_power`
- Speicher: Entladung `sensor.solaredge_local_i1_b1_energy_export`, Ladung
`sensor.solaredge_local_i1_b1_energy_import`, SoC
`sensor.solaredge_local_i1_b1_state_of_energy`
- Kosten/Preise: noch nicht gesetzt; folgt mit Tibber
Verifikation:
```sh
TOKEN=$(cat /mnt/user/appdata/secrets/ha_token_codex)
# WebSocket: energy/get_prefs und energy/validate
sed -n '1,260p' /mnt/user/appdata/homeassistant/.storage/energy
```
Erwartung: `.storage/energy` enthaelt drei Quellen (`grid`, `solar`,
`battery`), und `energy/validate` meldet keine Issues.
Nach der Energy-Konfiguration wurde ein HA-native Backup erzeugt und
tar-geprueft:
`/mnt/user/appdata/homeassistant/backups/Custom_backup_2026.6.1_2026-06-13_15.59_25670583.tar`.
Naechster Schritt: Tibber per HA-UI-Config-Flow verbinden und danach Kosten im
Energy Dashboard ergaenzen.
+204
View File
@@ -0,0 +1,204 @@
{
"uid": "ha-solar-pv",
"title": "Solar PV System",
"tags": ["solar", "solaredge", "homeassistant", "energy"],
"timezone": "browser",
"schemaVersion": 39,
"version": 1,
"refresh": "30s",
"time": { "from": "now-24h", "to": "now" },
"templating": { "list": [] },
"annotations": { "list": [] },
"panels": [
{
"id": 1,
"title": "Power",
"type": "timeseries",
"gridPos": { "h": 11, "w": 12, "x": 0, "y": 0 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": {
"defaults": { "unit": "kwatt", "custom": { "drawStyle": "line", "fillOpacity": 30, "lineWidth": 1, "showPoints": "never" } },
"overrides": [
{ "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "displayName", "value": "Solar Produktion" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#73bf69" } } ] },
{ "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "displayName", "value": "Strom Verbrauch" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#fade2a" } } ] }
]
},
"options": { "legend": { "displayMode": "list", "placement": "bottom", "calcs": ["lastNotNull"] }, "tooltip": { "mode": "multi" } },
"targets": [
{ "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kW\" WHERE entity_id = 'solaredge_pv_live_power' AND $__timeFilter(time) ORDER BY time" },
{ "refId": "B", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kW\" WHERE entity_id = 'kallihome_live_load_power' AND $__timeFilter(time) ORDER BY time" }
]
},
{
"id": 2,
"title": "Aktuelle Solar Produktion",
"type": "bargauge",
"gridPos": { "h": 4, "w": 6, "x": 12, "y": 0 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": { "defaults": { "unit": "kwatt", "min": 0, "max": 8, "color": { "mode": "continuous-GrYlRd" } }, "overrides": [] },
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "displayMode": "lcd", "orientation": "horizontal", "showUnfilled": true },
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kW\" WHERE entity_id = 'solaredge_pv_live_power' AND $__timeFilter(time) ORDER BY time" } ]
},
{
"id": 3,
"title": "Strom Produziert (Heute)",
"type": "gauge",
"gridPos": { "h": 7, "w": 6, "x": 18, "y": 0 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": { "defaults": { "unit": "kwatth", "min": 0, "max": 50, "color": { "mode": "continuous-GrYlRd" } }, "overrides": [] },
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "showThresholdMarkers": false },
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kWh\" WHERE entity_id = 'solaredge_pv_energy_today' AND $__timeFilter(time) ORDER BY time" } ]
},
{
"id": 4,
"title": "Produktion und Verbrauch kWh",
"type": "bargauge",
"gridPos": { "h": 7, "w": 6, "x": 12, "y": 4 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": {
"defaults": { "unit": "kwatth", "min": 0, "color": { "mode": "continuous-GrYlRd" } },
"overrides": [
{ "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "displayName", "value": "Solar Produktion" } ] },
{ "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "displayName", "value": "Netzbezug" } ] }
]
},
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "displayMode": "lcd", "orientation": "horizontal", "showUnfilled": true },
"targets": [
{ "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kWh\" WHERE entity_id = 'solaredge_pv_energy_today' AND $__timeFilter(time) ORDER BY time" },
{ "refId": "B", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kWh\" WHERE entity_id = 'solaredge_grid_import_today' AND $__timeFilter(time) ORDER BY time" }
]
},
{
"id": 5,
"title": "Tages Produktion 30 Tage Übersicht",
"type": "barchart",
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 11 },
"timeFrom": "30d",
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": { "defaults": { "unit": "kwatth", "color": { "mode": "continuous-GrYlRd" }, "custom": { "fillOpacity": 80, "gradientMode": "scheme", "lineWidth": 1 } }, "overrides": [] },
"options": { "orientation": "vertical", "showValue": "never", "xField": "time", "legend": { "showLegend": false }, "tooltip": { "mode": "single" } },
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "table", "rawSql": "SELECT date_bin(INTERVAL '1 day', time) AS time, max(value) AS value FROM \"kWh\" WHERE entity_id = 'solaredge_pv_energy_today' AND $__timeFilter(time) GROUP BY 1 ORDER BY 1" } ]
},
{
"id": 6,
"title": "Speicher-Ladestand",
"type": "gauge",
"gridPos": { "h": 4, "w": 6, "x": 18, "y": 7 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "max": 100, "thresholds": { "mode": "absolute", "steps": [ { "color": "red", "value": null }, { "color": "yellow", "value": 20 }, { "color": "green", "value": 50 } ] } }, "overrides": [] },
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "showThresholdMarkers": true },
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"%\" WHERE entity_id = 'solaredge_local_i1_b1_state_of_energy' AND $__timeFilter(time) ORDER BY time" } ]
},
{
"id": 7,
"title": "Erreichte TOP kWh an einem Tag",
"type": "bargauge",
"gridPos": { "h": 4, "w": 12, "x": 12, "y": 11 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": {
"defaults": { "unit": "kwatth", "min": 0, "max": 50, "color": { "mode": "continuous-GrYlRd" } },
"overrides": [
{ "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "displayName", "value": "Bester Wert bis jetzt" } ] },
{ "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "displayName", "value": "Heute" } ] }
]
},
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "displayMode": "lcd", "orientation": "horizontal", "showUnfilled": true },
"targets": [
{ "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "table", "rawSql": "SELECT max(d) AS value FROM (SELECT date_bin(INTERVAL '1 day', time) AS day, max(value) AS d FROM \"kWh\" WHERE entity_id = 'solaredge_pv_energy_today' AND time > now() - INTERVAL '365 days' GROUP BY 1)" },
{ "refId": "B", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "table", "rawSql": "SELECT value FROM \"kWh\" WHERE entity_id = 'solaredge_pv_energy_today' ORDER BY time DESC LIMIT 1" }
]
},
{
"id": 8,
"title": "Gesamt Produktion kWh",
"type": "stat",
"gridPos": { "h": 4, "w": 12, "x": 12, "y": 15 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": { "defaults": { "unit": "kwatth", "color": { "mode": "continuous-GrYlRd" } }, "overrides": [] },
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "area", "textMode": "value" },
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kWh\" WHERE entity_id = 'solaredge_local_i1_ac_energy' AND $__timeFilter(time) ORDER BY time" } ]
},
{
"id": 9,
"title": "Netzbilanz (heute)",
"type": "bargauge",
"gridPos": { "h": 4, "w": 12, "x": 0, "y": 19 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": {
"defaults": { "unit": "kwatth", "min": 0, "color": { "mode": "continuous-GrYlRd" } },
"overrides": [
{ "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "displayName", "value": "Netzbezug" } ] },
{ "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "displayName", "value": "Einspeisung" } ] }
]
},
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "displayMode": "lcd", "orientation": "horizontal", "showUnfilled": true },
"targets": [
{ "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kWh\" WHERE entity_id = 'solaredge_grid_import_today' AND $__timeFilter(time) ORDER BY time" },
{ "refId": "B", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kWh\" WHERE entity_id = 'solaredge_grid_export_today' AND $__timeFilter(time) ORDER BY time" }
]
},
{
"id": 10,
"title": "Netz & Batterie (Verlauf)",
"type": "timeseries",
"gridPos": { "h": 7, "w": 24, "x": 0, "y": 23 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": {
"defaults": { "unit": "kwatt", "custom": { "drawStyle": "line", "fillOpacity": 10, "lineWidth": 2 } },
"overrides": [
{ "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "displayName", "value": "Netzbezug" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#fa5252" } } ] },
{ "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "displayName", "value": "Einspeisung" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#4dabf7" } } ] },
{ "matcher": { "id": "byFrameRefID", "options": "C" }, "properties": [ { "id": "displayName", "value": "Speicher laden" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#37b24d" } } ] },
{ "matcher": { "id": "byFrameRefID", "options": "D" }, "properties": [ { "id": "displayName", "value": "Speicher entladen" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#d6336c" } } ] }
]
},
"options": { "legend": { "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "multi" } },
"targets": [
{ "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kW\" WHERE entity_id = 'solaredge_grid_import_power' AND $__timeFilter(time) ORDER BY time" },
{ "refId": "B", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kW\" WHERE entity_id = 'solaredge_grid_export_power' AND $__timeFilter(time) ORDER BY time" },
{ "refId": "C", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kW\" WHERE entity_id = 'solaredge_battery_charge_power' AND $__timeFilter(time) ORDER BY time" },
{ "refId": "D", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kW\" WHERE entity_id = 'solaredge_battery_discharge_power' AND $__timeFilter(time) ORDER BY time" }
]
},
{
"id": 11,
"title": "Wallbox Ladeleistung",
"type": "timeseries",
"gridPos": { "h": 7, "w": 12, "x": 0, "y": 30 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": { "defaults": { "unit": "kwatt", "color": { "mode": "fixed", "fixedColor": "#9775fa" }, "custom": { "drawStyle": "line", "fillOpacity": 20, "lineWidth": 2 } }, "overrides": [] },
"options": { "legend": { "showLegend": false }, "tooltip": { "mode": "single" } },
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kW\" WHERE entity_id = 'eh7klptt_leistung' AND $__timeFilter(time) ORDER BY time" } ]
},
{
"id": 12,
"title": "Ladeleistung",
"type": "gauge",
"gridPos": { "h": 7, "w": 6, "x": 12, "y": 30 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": { "defaults": { "unit": "kwatt", "min": 0, "max": 11, "color": { "mode": "continuous-GrYlRd" } }, "overrides": [] },
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "showThresholdMarkers": false },
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kW\" WHERE entity_id = 'eh7klptt_leistung' AND $__timeFilter(time) ORDER BY time" } ]
},
{
"id": 13,
"title": "Gesamt geladen",
"type": "stat",
"gridPos": { "h": 4, "w": 6, "x": 18, "y": 30 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": { "defaults": { "unit": "kwatth", "color": { "mode": "fixed", "fixedColor": "#9775fa" } }, "overrides": [] },
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "area", "textMode": "value" },
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kWh\" WHERE entity_id = 'eh7klptt_gesamtenergie' AND $__timeFilter(time) ORDER BY time" } ]
},
{
"id": 14,
"title": "Aktuelle Session",
"type": "stat",
"gridPos": { "h": 3, "w": 6, "x": 18, "y": 34 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": { "defaults": { "unit": "kwatth", "color": { "mode": "fixed", "fixedColor": "#4dabf7" } }, "overrides": [] },
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "none", "textMode": "value" },
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"kWh\" WHERE entity_id = 'eh7klptt_sitzungsenergie' AND $__timeFilter(time) ORDER BY time" } ]
}
]
}
@@ -0,0 +1,165 @@
{
"uid": "ha-weather-archive",
"title": "Wetterarchiv KalliHome",
"tags": ["weather", "ecowitt", "homeassistant"],
"timezone": "browser",
"schemaVersion": 39,
"version": 2,
"refresh": "1m",
"time": { "from": "now-7d", "to": "now" },
"templating": { "list": [] },
"annotations": { "list": [] },
"panels": [
{
"id": 1,
"title": "Außentemperatur",
"type": "gauge",
"gridPos": { "h": 5, "w": 4, "x": 0, "y": 0 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": { "defaults": { "unit": "celsius", "min": -10, "max": 40, "color": { "mode": "continuous-BlYlRd" } }, "overrides": [] },
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "showThresholdMarkers": false },
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"°C\" WHERE entity_id = 'gw3000a_outdoor_temperature' AND $__timeFilter(time) ORDER BY time" } ]
},
{
"id": 2,
"title": "Luftfeuchte",
"type": "gauge",
"gridPos": { "h": 5, "w": 4, "x": 4, "y": 0 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "max": 100, "color": { "mode": "continuous-BlYlRd" } }, "overrides": [] },
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "showThresholdMarkers": false },
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"%\" WHERE entity_id = 'gw3000a_humidity' AND $__timeFilter(time) ORDER BY time" } ]
},
{
"id": 3,
"title": "Wind",
"type": "gauge",
"gridPos": { "h": 5, "w": 4, "x": 8, "y": 0 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": { "defaults": { "unit": "velocitykmh", "min": 0, "max": 60, "color": { "mode": "continuous-GrYlRd" } }, "overrides": [] },
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "showThresholdMarkers": false },
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"km/h\" WHERE entity_id = 'gw3000a_wind_speed' AND $__timeFilter(time) ORDER BY time" } ]
},
{
"id": 4,
"title": "UV-Index",
"type": "gauge",
"gridPos": { "h": 5, "w": 4, "x": 12, "y": 0 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": { "defaults": { "unit": "short", "min": 0, "max": 11, "color": { "mode": "continuous-GrYlRd" } }, "overrides": [] },
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "showThresholdMarkers": false },
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"UV index\" WHERE entity_id = 'gw3000a_uv_index' AND $__timeFilter(time) ORDER BY time" } ]
},
{
"id": 5,
"title": "Solarstrahlung",
"type": "gauge",
"gridPos": { "h": 5, "w": 4, "x": 16, "y": 0 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": { "defaults": { "unit": "wattm2", "min": 0, "max": 1200, "color": { "mode": "continuous-GrYlRd" } }, "overrides": [] },
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "showThresholdMarkers": false },
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"W/m²\" WHERE entity_id = 'gw3000a_solar_radiation' AND $__timeFilter(time) ORDER BY time" } ]
},
{
"id": 6,
"title": "Luftdruck",
"type": "stat",
"gridPos": { "h": 5, "w": 4, "x": 20, "y": 0 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": { "defaults": { "unit": "pressurehpa", "color": { "mode": "fixed", "fixedColor": "#4dabf7" } }, "overrides": [] },
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "area", "textMode": "value" },
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"hPa\" WHERE entity_id = 'gw3000a_relative_pressure' AND $__timeFilter(time) ORDER BY time" } ]
},
{
"id": 7,
"title": "Temperatur (°C)",
"type": "timeseries",
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 5 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": {
"defaults": { "unit": "celsius", "custom": { "drawStyle": "line", "fillOpacity": 12, "lineWidth": 2, "showPoints": "never" } },
"overrides": [
{ "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "displayName", "value": "Außen" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#fa5252" } } ] },
{ "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "displayName", "value": "Gefühlt" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#ff922b" } } ] },
{ "matcher": { "id": "byFrameRefID", "options": "C" }, "properties": [ { "id": "displayName", "value": "Taupunkt" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#4dabf7" } } ] },
{ "matcher": { "id": "byFrameRefID", "options": "D" }, "properties": [ { "id": "displayName", "value": "Innen" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#82c91e" } } ] }
]
},
"options": { "legend": { "displayMode": "list", "placement": "bottom", "calcs": ["lastNotNull"] }, "tooltip": { "mode": "multi" } },
"targets": [
{ "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"°C\" WHERE entity_id = 'gw3000a_outdoor_temperature' AND $__timeFilter(time) ORDER BY time" },
{ "refId": "B", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"°C\" WHERE entity_id = 'gw3000a_feels_like_temperature' AND $__timeFilter(time) ORDER BY time" },
{ "refId": "C", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"°C\" WHERE entity_id = 'gw3000a_dewpoint' AND $__timeFilter(time) ORDER BY time" },
{ "refId": "D", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"°C\" WHERE entity_id = 'gw3000a_indoor_temperature' AND $__timeFilter(time) ORDER BY time" }
]
},
{
"id": 8,
"title": "Luftfeuchte (%)",
"type": "timeseries",
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 5 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": {
"defaults": { "unit": "percent", "min": 0, "max": 100, "custom": { "drawStyle": "line", "fillOpacity": 12, "lineWidth": 2, "showPoints": "never" } },
"overrides": [
{ "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "displayName", "value": "Außen" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#4dabf7" } } ] },
{ "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "displayName", "value": "Innen" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#82c91e" } } ] }
]
},
"options": { "legend": { "displayMode": "list", "placement": "bottom", "calcs": ["lastNotNull"] }, "tooltip": { "mode": "multi" } },
"targets": [
{ "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"%\" WHERE entity_id = 'gw3000a_humidity' AND $__timeFilter(time) ORDER BY time" },
{ "refId": "B", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"%\" WHERE entity_id = 'gw3000a_indoor_humidity' AND $__timeFilter(time) ORDER BY time" }
]
},
{
"id": 9,
"title": "Solarstrahlung (W/m²)",
"type": "timeseries",
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 13 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": { "defaults": { "unit": "wattm2", "color": { "mode": "fixed", "fixedColor": "#f2b705" }, "custom": { "drawStyle": "line", "fillOpacity": 35, "lineWidth": 1, "showPoints": "never", "gradientMode": "opacity" } }, "overrides": [] },
"options": { "legend": { "showLegend": false }, "tooltip": { "mode": "single" } },
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"W/m²\" WHERE entity_id = 'gw3000a_solar_radiation' AND $__timeFilter(time) ORDER BY time" } ]
},
{
"id": 10,
"title": "Wind (km/h)",
"type": "timeseries",
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 13 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": {
"defaults": { "unit": "velocitykmh", "custom": { "drawStyle": "line", "fillOpacity": 10, "lineWidth": 2, "showPoints": "never" } },
"overrides": [
{ "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "displayName", "value": "Wind" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#15aabf" } } ] },
{ "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "displayName", "value": "Böe" }, { "id": "color", "value": { "mode": "fixed", "fixedColor": "#fab005" } } ] }
]
},
"options": { "legend": { "displayMode": "list", "placement": "bottom", "calcs": ["lastNotNull", "max"] }, "tooltip": { "mode": "multi" } },
"targets": [
{ "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"km/h\" WHERE entity_id = 'gw3000a_wind_speed' AND $__timeFilter(time) ORDER BY time" },
{ "refId": "B", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"km/h\" WHERE entity_id = 'gw3000a_wind_gust' AND $__timeFilter(time) ORDER BY time" }
]
},
{
"id": 11,
"title": "Regen pro Tag (mm)",
"type": "barchart",
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 21 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": { "defaults": { "unit": "lengthmm", "color": { "mode": "fixed", "fixedColor": "#4dabf7" }, "custom": { "fillOpacity": 80, "lineWidth": 1 } }, "overrides": [] },
"options": { "orientation": "vertical", "showValue": "auto", "xField": "time", "legend": { "showLegend": false }, "tooltip": { "mode": "single" } },
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "table", "rawSql": "SELECT date_bin(INTERVAL '1 day', time) AS time, max(value) AS value FROM \"mm\" WHERE entity_id = 'gw3000a_daily_rain' AND $__timeFilter(time) GROUP BY 1 ORDER BY 1" } ]
},
{
"id": 12,
"title": "Luftdruck (hPa)",
"type": "timeseries",
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 21 },
"datasource": { "type": "influxdb", "uid": "ha-weather-influx" },
"fieldConfig": { "defaults": { "unit": "pressurehpa", "decimals": 0, "color": { "mode": "fixed", "fixedColor": "#9775fa" }, "custom": { "drawStyle": "line", "fillOpacity": 10, "lineWidth": 2, "showPoints": "never" } }, "overrides": [] },
"options": { "legend": { "showLegend": false }, "tooltip": { "mode": "single" } },
"targets": [ { "refId": "A", "datasource": { "type": "influxdb", "uid": "ha-weather-influx" }, "rawQuery": true, "format": "time_series", "rawSql": "SELECT time, value FROM \"hPa\" WHERE entity_id = 'gw3000a_relative_pressure' AND $__timeFilter(time) ORDER BY time" } ]
}
]
}
@@ -31,3 +31,19 @@ datasources:
insecureGrpc: true insecureGrpc: true
secureJsonData: secureJsonData:
token: $GRAFANA_INFLUXDB_TOKEN token: $GRAFANA_INFLUXDB_TOKEN
# Wetter-/Langzeitarchiv aus Home Assistant (Ecowitt). Gleiche InfluxDB-Instanz,
# aber Datenbank `homeassistant`; gleicher Admin-Read-Token.
- name: InfluxDB HA Weather
uid: ha-weather-influx
type: influxdb
access: proxy
url: http://influxdb3-core:8181
editable: false
jsonData:
version: SQL
dbName: homeassistant
httpMode: POST
insecureGrpc: true
secureJsonData:
token: $GRAFANA_INFLUXDB_TOKEN
+5
View File
@@ -48,6 +48,10 @@ The Unraid flash configuration archive is intentional as well and must be treate
| Grafana | SQLite dump from `monitoring_grafana_data` + provisioned config in Git | `/local/borg-dumps`, `monitoring/grafana/provisioning`, `monitoring/grafana/dashboards` | | Grafana | SQLite dump from `monitoring_grafana_data` + provisioned config in Git | `/local/borg-dumps`, `monitoring/grafana/provisioning`, `monitoring/grafana/dashboards` |
| Filebrowser | file-backed state dump + file data | `/local/borg-dumps`, `/local/appdata/filebrowser` | | Filebrowser | file-backed state dump + file data | `/local/borg-dumps`, `/local/appdata/filebrowser` |
| InfluxDB 3 Core | file data | `/local/appdata/influxdb3/data`, `/local/appdata/influxdb3/plugins` | | InfluxDB 3 Core | file data | `/local/appdata/influxdb3/data`, `/local/appdata/influxdb3/plugins` |
| 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` | | Hermes Agent | file data + SSH key | `/local/appdata/hermes-agent/data`, `/local/secrets/hermes_runner_id_ed25519` |
| BentoPDF | rebuildable | no critical persistence in compose | | BentoPDF | rebuildable | no critical persistence in compose |
@@ -87,6 +91,7 @@ The live Unraid User Scripts execute repo scripts from `/mnt/user/services/homel
- SQLite: `gitea`, `vaultwarden`, `speedtest-tracker`, `borg-ui`, `grafana` - SQLite: `gitea`, `vaultwarden`, `speedtest-tracker`, `borg-ui`, `grafana`
- File-backed state: `filebrowser.bolt.dump` - File-backed state: `filebrowser.bolt.dump`
- Unraid flash config: `unraid-flash-config.tar.gz` plus `unraid-flash-config.tar.gz.sha256` - Unraid flash config: `unraid-flash-config.tar.gz` plus `unraid-flash-config.tar.gz.sha256`
- Home Assistant native backups: created by HA under `/mnt/user/appdata/homeassistant/backups` and captured as file state
## Explicitly Not Backed Up as Raw Live DB Files ## Explicitly Not Backed Up as Raw Live DB Files
+4
View File
@@ -20,5 +20,9 @@
/local/appdata/komodo/periphery /local/appdata/komodo/periphery
/local/appdata/komodo/core /local/appdata/komodo/core
/local/services/homelab-infra /local/services/homelab-infra
/local/services/smart-home-kalli
/local/services/stacks /local/services/stacks
/local/services/posture-check /local/services/posture-check
/local/appdata/homeassistant
/local/appdata/mosquitto/config
/local/appdata/mosquitto/data
+182
View File
@@ -0,0 +1,182 @@
/* ============================================================
KalliLab "Neon Ops v2" - Glance Custom CSS
Rotierende Akzentfarben pro Widget, Gradient-Zahlen,
animierte Header-Linien, kraeftige Glows
============================================================ */
/* --- Akzentfarben rotieren ueber die Widgets --- */
.widget { --kl-accent: 205 100% 60%; }
.widget:nth-of-type(4n+2) { --kl-accent: 172 95% 48%; }
.widget:nth-of-type(4n+3) { --kl-accent: 38 100% 55%; }
.widget:nth-of-type(4n) { --kl-accent: 145 85% 50%; }
/* --- Seiten-Hintergrund: kraeftigere Farb-Glows --- */
body {
background:
radial-gradient(1300px 700px at 85% -10%, hsla(205, 100%, 55%, 0.13), transparent 60%),
radial-gradient(1000px 600px at -10% 25%, hsla(172, 95%, 45%, 0.09), transparent 55%),
radial-gradient(900px 700px at 50% 115%, hsla(38, 100%, 50%, 0.07), transparent 60%),
var(--color-background);
background-attachment: fixed;
}
/* --- Widgets als Karten mit Akzentrand --- */
.widget {
background: linear-gradient(
160deg,
hsla(220, 30%, 100%, 0.05),
hsla(220, 30%, 100%, 0.015)
);
border: 1px solid hsl(var(--kl-accent) / 0.18);
border-radius: 14px;
padding: 14px 16px;
box-shadow:
0 10px 30px hsla(220, 60%, 3%, 0.4),
0 0 24px hsl(var(--kl-accent) / 0.06),
inset 0 1px 0 hsla(220, 40%, 90%, 0.05);
transition: border-color 0.2s ease, box-shadow 0.25s ease;
}
.widget:hover {
border-color: hsl(var(--kl-accent) / 0.55);
box-shadow:
0 12px 36px hsla(220, 60%, 3%, 0.45),
0 0 36px hsl(var(--kl-accent) / 0.16),
inset 0 1px 0 hsla(220, 40%, 90%, 0.07);
}
/* Widgets in Gruppen/Tabs nicht doppelt einrahmen */
.widget .widget {
background: none;
border: none;
border-radius: 0;
padding: 0;
box-shadow: none;
}
/* --- Widget-Titel: animierte Farbverlaufs-Linie in Akzentfarbe --- */
.widget-header {
letter-spacing: 0.14em;
position: relative;
padding-bottom: 7px;
margin-bottom: 4px;
color: hsl(var(--kl-accent) / 0.85);
}
.widget-header::after {
content: "";
position: absolute;
left: 0;
bottom: 0;
width: 64px;
height: 2px;
border-radius: 2px;
background: linear-gradient(
90deg,
hsl(var(--kl-accent)),
hsl(var(--kl-accent) / 0.25),
hsl(var(--kl-accent))
);
background-size: 200% 100%;
animation: kl-shimmer 4s linear infinite;
}
@keyframes kl-shimmer {
0% { background-position: 0% 0; }
100% { background-position: 200% 0; }
}
/* --- Grosse Zahlen: Gradient-Text + Glow --- */
.color-highlight.size-h2,
.color-highlight.size-h3,
.color-primary.size-h2,
.color-primary.size-h3 {
background: linear-gradient(
120deg,
hsl(var(--kl-accent)),
hsl(var(--kl-accent) / 0.55) 60%,
hsl(210, 30%, 95%)
);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
filter: drop-shadow(0 0 14px hsl(var(--kl-accent) / 0.35));
}
.color-positive {
text-shadow: 0 0 16px hsla(150, 95%, 45%, 0.45);
}
.color-negative {
text-shadow: 0 0 16px hsla(350, 95%, 58%, 0.45);
}
/* --- Status-Punkte leuchten --- */
.monitor-site-status-icon-compact,
.monitor-site-status-icon {
filter: drop-shadow(0 0 7px hsla(150, 95%, 45%, 0.55));
}
/* --- Navigation --- */
.nav-item.nav-item-current {
text-shadow: 0 0 18px hsla(212, 100%, 60%, 0.6);
}
/* --- Suchleiste --- */
.search {
border: 1px solid hsla(212, 90%, 65%, 0.2);
border-radius: 12px;
background: hsla(220, 30%, 100%, 0.04);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.search:focus-within {
border-color: hsla(212, 100%, 60%, 0.55);
box-shadow: 0 0 0 3px hsla(212, 100%, 55%, 0.15), 0 0 28px hsla(212, 100%, 55%, 0.12);
}
/* --- Server-Stats: Balken rund, gradient, glow --- */
.progress-bar {
border: none;
height: 13px;
border-radius: 999px;
background: hsla(220, 30%, 60%, 0.12);
box-shadow: inset 0 1px 3px hsla(220, 60%, 3%, 0.5);
overflow: hidden;
}
.progress-value {
border-radius: 999px;
background: linear-gradient(90deg, hsl(205, 100%, 55%), hsl(172, 95%, 48%));
box-shadow: 0 0 10px hsla(205, 100%, 55%, 0.35);
}
.progress-value-notice {
background: linear-gradient(90deg, hsl(38, 100%, 55%), hsl(355, 90%, 60%));
box-shadow: 0 0 12px hsla(355, 90%, 58%, 0.45);
}
/* --- Feinschliff --- */
::selection {
background: hsla(212, 100%, 50%, 0.35);
}
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-thumb {
background: hsla(220, 30%, 50%, 0.25);
border-radius: 8px;
}
::-webkit-scrollbar-thumb:hover {
background: hsla(212, 80%, 55%, 0.45);
}
/* Reduzierte Bewegung respektieren */
@media (prefers-reduced-motion: reduce) {
.widget-header::after {
animation: none;
}
}
+287
View File
@@ -0,0 +1,287 @@
traefik:
name: Traefik
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
url: https://traefik.kaleschke.info
description: Reverse Proxy
category: core
hide: false
gitea:
name: Gitea
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
url: https://git.kaleschke.info
description: GitOps Origin
category: core
hide: false
authelia:
name: Authelia
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authelia.svg
url: https://auth.kaleschke.info
description: ForwardAuth
category: core
hide: false
vaultwarden:
name: Vaultwarden
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vaultwarden.svg
url: https://vault.kaleschke.info
description: Password Vault
category: core
hide: false
postgresql17:
name: PostgreSQL 18
icon: si:postgresql
description: Shared DB
category: core
hide: false
Redis:
name: Redis
icon: si:redis
description: Shared Cache
category: core
hide: false
adguard:
name: AdGuard
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/adguard-home.svg
url: http://192.168.178.58:8082
description: DNS Filter
category: network
hide: false
unbound:
name: Unbound
icon: mdi:dns
description: Upstream Resolver
category: network
hide: false
ddns-updater:
name: DDNS Updater
icon: mdi:cloud-sync
description: Cloudflare DNS
category: network
hide: false
paperless-ngx:
name: Paperless-ngx
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/paperless-ngx.svg
url: https://paperless.kaleschke.info
description: Dokumente
category: apps
hide: false
paperless-gpt:
name: Paperless-GPT
icon: mdi:robot
url: https://paperless-gpt.kaleschke.info
description: Dokumenten-KI
category: apps
hide: false
immich_server:
name: Immich
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
url: https://immich.kaleschke.info
description: Fotos und Videos
category: apps
id: immich
hide: false
immich_postgres:
name: DB
parent: immich
category: apps
hide: false
immich_redis:
name: Redis
parent: immich
category: apps
hide: false
immich_machine_learning:
name: ML
parent: immich
category: apps
hide: false
mealie:
name: Mealie
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mealie.svg
url: https://mealie.kaleschke.info
description: Rezepte
category: apps
id: mealie
hide: false
mealie-postgres:
name: DB
parent: mealie
category: apps
hide: false
nextcloud:
name: Nextcloud
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg
url: https://cloud.kaleschke.info
description: Dateien und Sync
category: apps
id: nextcloud
hide: false
nextcloud-postgres:
name: DB
parent: nextcloud
category: apps
hide: false
nextcloud-redis:
name: Redis
parent: nextcloud
category: apps
hide: false
mail-archiver:
name: Mail Archiver
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mailcow.svg
url: https://mail.kaleschke.info
description: Mail-Archiv
category: apps
hide: false
ntfy:
name: ntfy
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/ntfy.svg
url: https://ntfy.kaleschke.info
description: Push Alerts
category: apps
hide: false
bentopdf:
name: BentoPDF
icon: mdi:file-pdf-box
url: https://pdf.kaleschke.info
description: PDF Tools
category: apps
hide: false
glance:
name: Glance
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glance.svg
url: https://glance.kaleschke.info
description: Homelab Uebersicht
category: ops
hide: false
glance-docker-socket-proxy:
name: Glance Socket Proxy
icon: si:docker
description: Read-only Docker API
category: ops
hide: false
monitoring-grafana:
name: Monitoring Grafana
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/grafana.svg
url: https://monitoring.kaleschke.info
description: Observability UI
category: ops
id: monitoring
hide: false
monitoring-prometheus:
name: Prometheus
parent: monitoring
category: ops
hide: false
monitoring-loki:
name: Loki
parent: monitoring
category: ops
hide: false
monitoring-promtail:
name: Promtail
parent: monitoring
category: ops
hide: false
monitoring-alertmanager:
name: Alertmanager
parent: monitoring
category: ops
hide: false
monitoring-alertmanager-ntfy-bridge:
name: ntfy Bridge
parent: monitoring
category: ops
hide: false
monitoring-blackbox-exporter:
name: Blackbox
parent: monitoring
category: ops
hide: false
monitoring-node-exporter:
name: Node Exporter
parent: monitoring
category: ops
hide: false
monitoring-cadvisor:
name: cAdvisor
parent: monitoring
category: ops
hide: false
monitoring-influxdb3-core:
name: InfluxDB 3
parent: monitoring
category: ops
hide: false
glances:
name: Glances
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glances.svg
url: https://glances.kaleschke.info
description: Host-Monitoring
category: ops
hide: false
scrutiny:
name: Scrutiny
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/scrutiny.svg
url: https://scrutiny.kaleschke.info
description: SMART
category: ops
hide: false
speedtest-tracker:
name: Speedtest
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/speedtest-tracker.png
url: https://speedtest.kaleschke.info
description: WAN-Messung
category: ops
hide: false
filebrowser:
name: Filebrowser
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/filebrowser.svg
url: https://files.kaleschke.info
description: Dateizugriff
category: ops
hide: false
code-server:
name: code-server
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vscode.svg
url: https://code.kaleschke.info
description: Web IDE
category: ops
hide: false
borg-ui:
name: Borg UI
icon: mdi:archive-sync
url: https://borg.kaleschke.info
description: Backup und Restore
category: ops
hide: false
hermes-dashboard:
name: Hermes
icon: mdi:shield-sparkles
url: https://hermes.kaleschke.info
description: Ops Agent UI
category: ops
id: hermes
hide: false
hermes-gateway:
name: Gateway
parent: hermes
category: ops
hide: false
komodo-core:
name: Komodo
icon: sh:komodo
url: https://komodo.kaleschke.info
description: Stack Manager
category: ops
id: komodo
hide: false
komodo-mongo:
name: Mongo
parent: komodo
category: ops
hide: false
komodo-periphery:
name: Periphery
parent: komodo
category: ops
hide: false
+40 -861
View File
@@ -1,5 +1,6 @@
server: server:
proxied: true proxied: true
assets-path: /app/assets
branding: branding:
app-name: KalliLab Dashboard app-name: KalliLab Dashboard
@@ -7,867 +8,45 @@ branding:
hide-footer: true hide-footer: true
theme: theme:
background-color: 210 20 13 background-color: 222 14 8
primary-color: 212 100 50 primary-color: 205 100 58
positive-color: 140 70 40 positive-color: 150 80 45
negative-color: 4 78 57 negative-color: 355 90 60
contrast-multiplier: 1.25 contrast-multiplier: 1.3
text-saturation-multiplier: 0.9 text-saturation-multiplier: 0.5
disable-picker: false disable-picker: false
custom-css-file: /assets/custom.css
presets:
catppuccin-mocha:
background-color: 240 21 15
primary-color: 217 92 83
positive-color: 115 54 76
negative-color: 347 70 65
contrast-multiplier: 1.2
gruvbox-dark:
background-color: 0 0 16
primary-color: 43 59 81
positive-color: 61 66 44
negative-color: 6 96 59
kallilab-light:
light: true
background-color: 220 23 95
primary-color: 212 100 35
positive-color: 140 70 30
negative-color: 0 70 45
synthwave:
background-color: 265 35 10
primary-color: 320 100 65
positive-color: 175 100 50
negative-color: 0 100 65
contrast-multiplier: 1.3
matrix:
background-color: 130 25 6
primary-color: 130 100 55
positive-color: 130 100 45
negative-color: 35 100 55
contrast-multiplier: 1.25
text-saturation-multiplier: 1.2
pages: pages:
- name: Home $include: pages.yml
slug: home
width: wide
head-widgets:
- type: search
search-engine: duckduckgo
new-tab: true
autofocus: true
placeholder: Suche im Web oder springe per Bang...
bangs:
- title: Gitea
shortcut: "!git"
url: https://git.kaleschke.info/explore/repos?q={QUERY}
- title: Paperless
shortcut: "!doc"
url: https://paperless.kaleschke.info/documents?query={QUERY}
- title: Nextcloud
shortcut: "!cloud"
url: https://cloud.kaleschke.info/apps/files/?dir=/{QUERY}
- title: Komodo
shortcut: "!komodo"
url: https://komodo.kaleschke.info
columns:
- size: small
widgets:
- type: group
widgets:
- type: custom-api
title: Day
body-type: string
skip-json-validation: true
cache: 1s
template: |
{{ $localTime := now }}
{{ $elapsedSeconds := add (mul $localTime.Hour 3600) (mul $localTime.Minute 60) | add $localTime.Second }}
{{ $dayProgress := div (mul $elapsedSeconds 100.0) 86400.0 }}
{{ $gradient := "#70a1ff" }}
{{ if gt $dayProgress 25.0 }}{{ $gradient = "#ff6b6b, #70a1ff" }}{{ end }}
{{ if gt $dayProgress 50.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df" }}{{ end }}
{{ if gt $dayProgress 75.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df, #70a1ff" }}{{ end }}
<div style="text-align: center;">
<div style="width: 100%; height: 12px; background: #23262f; border: 1px solid color-mix(in srgb, var(--color-text-subdue) 55%, transparent); border-radius: 10px; overflow: hidden;">
<div style="height: 100%; width: {{ $dayProgress }}%; background: linear-gradient(90deg, {{ $gradient }});"></div>
</div>
<div class="size-h1" style="margin-top: 6px;">{{ printf "%.2f" $dayProgress }}% des Tages sind vorbei</div>
</div>
- type: custom-api
title: Month
body-type: string
skip-json-validation: true
cache: 1s
template: |
{{ $localTime := now }}
{{ $month := $localTime.Month }}
{{ $daysInMonth := 31 }}
{{ if eq $month 2 }}{{ $daysInMonth = 28 }}{{ end }}
{{ if or (eq $month 4) (eq $month 6) (eq $month 9) (eq $month 11) }}{{ $daysInMonth = 30 }}{{ end }}
{{ $secondsToday := add (mul $localTime.Hour 3600) (mul $localTime.Minute 60) | add $localTime.Second }}
{{ $daysElapsed := add (sub $localTime.Day 1) (div $secondsToday 86400.0) }}
{{ $monthProgress := mul (div $daysElapsed $daysInMonth) 100.0 }}
{{ $gradient := "#70a1ff" }}
{{ if gt $monthProgress 25.0 }}{{ $gradient = "#ff6b6b, #70a1ff" }}{{ end }}
{{ if gt $monthProgress 50.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df" }}{{ end }}
{{ if gt $monthProgress 75.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df, #70a1ff" }}{{ end }}
<div style="text-align: center;">
<div style="width: 100%; height: 12px; background: #23262f; border: 1px solid color-mix(in srgb, var(--color-text-subdue) 55%, transparent); border-radius: 10px; overflow: hidden;">
<div style="height: 100%; width: {{ $monthProgress }}%; background: linear-gradient(90deg, {{ $gradient }});"></div>
</div>
<div class="size-h1" style="margin-top: 6px;">{{ printf "%.2f" $monthProgress }}% des Monats sind vorbei</div>
</div>
- type: custom-api
title: Year
body-type: string
skip-json-validation: true
cache: 1s
template: |
{{ $localTime := now }}
{{ $secondsToday := add (mul $localTime.Hour 3600) (mul $localTime.Minute 60) | add $localTime.Second }}
{{ $secondsElapsed := add (mul (sub $localTime.YearDay 1) 86400) $secondsToday }}
{{ $yearProgress := div (mul $secondsElapsed 100.0) (mul 365 86400) }}
{{ $gradient := "#70a1ff" }}
{{ if gt $yearProgress 25.0 }}{{ $gradient = "#ff6b6b, #70a1ff" }}{{ end }}
{{ if gt $yearProgress 50.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df" }}{{ end }}
{{ if gt $yearProgress 75.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df, #70a1ff" }}{{ end }}
<div style="text-align: center;">
<div style="width: 100%; height: 12px; background: #23262f; border: 1px solid color-mix(in srgb, var(--color-text-subdue) 55%, transparent); border-radius: 10px; overflow: hidden;">
<div style="height: 100%; width: {{ $yearProgress }}%; background: linear-gradient(90deg, {{ $gradient }});"></div>
</div>
<div class="size-h1" style="margin-top: 6px;">{{ printf "%.2f" $yearProgress }}% des Jahres sind vorbei</div>
</div>
- type: clock
hour-format: 24h
show-progress: true
timezones:
- timezone: Europe/Berlin
label: Berlin
- timezone: UTC
label: UTC
- type: calendar
first-day-of-week: monday
- type: bookmarks
title: Direkte Einstiege
groups:
- title: Core
color: 212 100 50
links:
- title: Komodo
url: https://komodo.kaleschke.info
icon: sh:komodo
- title: Gitea
url: https://git.kaleschke.info
icon: si:gitea
- title: Monitoring
url: https://monitoring.kaleschke.info
icon: si:grafana
- title: Ops
color: 45 70 55
links:
- title: Borg
url: https://borg.kaleschke.info
icon: mdi:archive
- title: Glances
url: https://glances.kaleschke.info
icon: sh:glances
- title: Scrutiny
url: https://scrutiny.kaleschke.info
icon: sh:scrutiny
- size: full
widgets:
- type: server-stats
title: Server Stats
servers:
- type: local
name: Kallilabcore
hide-mountpoints-by-default: false
- type: group
widgets:
- type: custom-api
title: Immich
title-url: https://immich.kaleschke.info
cache: 10m
url: http://immich_server:2283/api/server/statistics
headers:
x-api-key: ${GLANCE_IMMICH_API_KEY}
subrequests:
storage:
url: http://immich_server:2283/api/server/storage
headers:
x-api-key: ${GLANCE_IMMICH_API_KEY}
template: |
{{ $photos := .JSON.Int "photos" }}
{{ $videos := .JSON.Int "videos" }}
{{ $usageGiB := div (toFloat (.JSON.Int "usage")) 1073741824.0 }}
{{ $storage := .Subrequest "storage" }}
{{ $storageOK := and (ge $storage.Response.StatusCode 200) (le $storage.Response.StatusCode 299) }}
{{ $percentage := 0.0 }}
{{ if $storageOK }}{{ $percentage = $storage.JSON.Float "diskUsagePercentage" }}{{ end }}
<div class="flex justify-between text-center">
<div>
<div class="color-highlight size-h3">{{ $photos | formatNumber }}</div>
<div class="size-h6 uppercase">Fotos</div>
</div>
<div>
<div class="color-highlight size-h3">{{ $videos | formatNumber }}</div>
<div class="size-h6 uppercase">Videos</div>
</div>
<div>
<div class="color-highlight size-h3">{{ printf "%.0f" $usageGiB }} GiB</div>
<div class="size-h6 uppercase">Medien</div>
</div>
</div>
<div style="height: 8px; margin-top: 14px; border-radius: 999px; overflow: hidden; background: color-mix(in srgb, var(--color-text-subdue) 22%, transparent);">
<div style="height: 100%; width: {{ if $storageOK }}{{ printf "%.1f" $percentage }}%{{ else }}0%{{ end }}; border-radius: 999px; background: var(--color-primary);"></div>
</div>
<div class="size-h6 color-subdue" style="margin-top: 8px;">{{ if $storageOK }}{{ printf "%.1f" $percentage }}% Speicher belegt{{ else }}Speicher API nicht verfuegbar{{ end }}</div>
- type: monitor
title: Homelab Status
cache: 1m
sites:
- title: AdGuard Home
url: http://192.168.178.58:8082
check-url: http://adguard
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/adguard-home.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Authelia
url: https://auth.kaleschke.info
check-url: http://authelia:9091/api/health
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authelia.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Gitea
url: https://git.kaleschke.info
check-url: http://gitea:3000/api/healthz
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Traefik
url: https://traefik.kaleschke.info
check-url: http://traefik:8082/metrics
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Vaultwarden
url: https://vault.kaleschke.info
check-url: http://vaultwarden/alive
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vaultwarden.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Komodo
url: https://komodo.kaleschke.info
check-url: http://komodo-core:9120
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/komodo.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Paperless-ngx
url: https://paperless.kaleschke.info
check-url: http://paperless-ngx:8000
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/paperless-ngx.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Paperless-GPT
url: https://paperless-gpt.kaleschke.info
check-url: http://paperless-gpt:8080
icon: mdi:robot
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Immich
url: https://immich.kaleschke.info
check-url: http://immich_server:2283/api/server/ping
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Mealie
url: https://mealie.kaleschke.info
check-url: http://mealie:9000
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mealie.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Nextcloud
url: https://cloud.kaleschke.info
check-url: http://nextcloud/status.php
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: ntfy
url: https://ntfy.kaleschke.info
check-url: http://ntfy/v1/health
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/ntfy.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Mail Archiver
url: https://mail.kaleschke.info
check-url: http://mail-archiver:5000
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mailcow.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: BentoPDF
url: https://pdf.kaleschke.info
check-url: http://bentopdf:8080
icon: mdi:file-pdf-box
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Glance
url: https://glance.kaleschke.info
check-url: http://glance:8080
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glance.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Monitoring Grafana
url: https://monitoring.kaleschke.info
check-url: http://monitoring-grafana:3000/api/health
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/grafana.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Glances
url: https://glances.kaleschke.info
check-url: http://glances:61208
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glances.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Scrutiny
url: https://scrutiny.kaleschke.info
check-url: http://scrutiny:8080
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/scrutiny.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Speedtest Tracker
url: https://speedtest.kaleschke.info
check-url: http://speedtest-tracker
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/speedtest-tracker.png
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Filebrowser
url: https://files.kaleschke.info
check-url: http://filebrowser
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/filebrowser.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: code-server
url: https://code.kaleschke.info
check-url: http://code-server:8443
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vscode.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Borg UI
url: https://borg.kaleschke.info
check-url: http://borg-ui:8081
icon: mdi:archive-sync
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- size: small
widgets:
- type: custom-api
title: Internet
title-url: https://speedtest.kaleschke.info
cache: 1h
url: http://speedtest-tracker/api/v1/results/latest
headers:
Authorization: Bearer ${GLANCE_SPEEDTEST_API_KEY}
Accept: application/json
template: |
{{ $ip := .JSON.String "external_ip" }}
{{ if eq $ip "" }}{{ $ip = .JSON.String "data.interface.externalIp" }}{{ end }}
{{ $isp := .JSON.String "isp" }}
{{ if eq $isp "" }}{{ $isp = .JSON.String "data.isp" }}{{ end }}
{{ $server := .JSON.String "server_name" }}
{{ if eq $server "" }}{{ $server = .JSON.String "data.server_name" }}{{ end }}
<div style="display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center;">
<div class="color-primary size-h2" style="font-weight: 700;">{{ if ne $ip "" }}{{ $ip }}{{ else }}WAN online{{ end }}</div>
<div class="size-h5 color-highlight">Speedtest Tracker</div>
<div class="size-h6 color-subdue" style="font-style: italic;">{{ if ne $isp "" }}{{ $isp }}{{ else }}{{ $server }}{{ end }}</div>
</div>
- type: custom-api
title: Internet Speed
title-url: https://speedtest.kaleschke.info
cache: 1h
url: http://speedtest-tracker/api/v1/results/latest
headers:
Authorization: Bearer ${GLANCE_SPEEDTEST_API_KEY}
Accept: application/json
subrequests:
stats:
url: http://speedtest-tracker/api/v1/stats
headers:
Authorization: Bearer ${GLANCE_SPEEDTEST_API_KEY}
Accept: application/json
template: |
{{ $stats := .Subrequest "stats" }}
{{ $download := .JSON.Float "download" }}
{{ if eq $download 0.0 }}{{ $download = .JSON.Float "data.download" }}{{ end }}
{{ if eq $download 0.0 }}{{ $download = div (.JSON.Float "download_bits") 1000000.0 }}{{ end }}
{{ if eq $download 0.0 }}{{ $download = div (.JSON.Float "data.download_bits") 1000000.0 }}{{ end }}
{{ $upload := .JSON.Float "upload" }}
{{ if eq $upload 0.0 }}{{ $upload = .JSON.Float "data.upload" }}{{ end }}
{{ if eq $upload 0.0 }}{{ $upload = div (.JSON.Float "upload_bits") 1000000.0 }}{{ end }}
{{ if eq $upload 0.0 }}{{ $upload = div (.JSON.Float "data.upload_bits") 1000000.0 }}{{ end }}
{{ $ping := .JSON.Float "ping" }}
{{ if eq $ping 0.0 }}{{ $ping = .JSON.Float "data.ping" }}{{ end }}
{{ $downloadAvg := $stats.JSON.Float "avg_download" }}
{{ if eq $downloadAvg 0.0 }}{{ $downloadAvg = $stats.JSON.Float "data.download.avg" }}{{ end }}
{{ if eq $downloadAvg 0.0 }}{{ $downloadAvg = div ($stats.JSON.Float "data.download.avg_bits") 1000000.0 }}{{ end }}
{{ $uploadAvg := $stats.JSON.Float "avg_upload" }}
{{ if eq $uploadAvg 0.0 }}{{ $uploadAvg = $stats.JSON.Float "data.upload.avg" }}{{ end }}
{{ if eq $uploadAvg 0.0 }}{{ $uploadAvg = div ($stats.JSON.Float "data.upload.avg_bits") 1000000.0 }}{{ end }}
{{ $pingAvg := $stats.JSON.Float "avg_ping" }}
{{ if eq $pingAvg 0.0 }}{{ $pingAvg = $stats.JSON.Float "data.ping.avg" }}{{ end }}
{{ $downloadChange := percentChange $downloadAvg $download }}
{{ $uploadChange := percentChange $uploadAvg $upload }}
{{ $pingChange := percentChange $pingAvg $ping }}
<div class="flex justify-between text-center margin-block-3">
<div>
<div class="size-small {{ if lt $downloadChange 0.0 }}color-negative{{ else }}color-positive{{ end }}">{{ printf "%+.1f%%" $downloadChange }}</div>
<div class="color-highlight size-h3">{{ printf "%.1f" $download }}</div>
<div class="size-h6 color-subdue">DOWNLOAD</div>
</div>
<div>
<div class="size-small {{ if lt $uploadChange 0.0 }}color-negative{{ else }}color-positive{{ end }}">{{ printf "%+.1f%%" $uploadChange }}</div>
<div class="color-highlight size-h3">{{ printf "%.1f" $upload }}</div>
<div class="size-h6 color-subdue">UPLOAD</div>
</div>
<div>
<div class="size-small {{ if gt $pingChange 0.0 }}color-negative{{ else }}color-positive{{ end }}">{{ printf "%+.1f%%" $pingChange }}</div>
<div class="color-highlight size-h3">{{ printf "%.0f ms" $ping }}</div>
<div class="size-h6 color-subdue">PING</div>
</div>
</div>
- type: dns-stats
title: DNS Stats
service: adguard
url: http://adguard
username: ${GLANCE_ADGUARD_USERNAME}
password: ${GLANCE_ADGUARD_PASSWORD}
- type: monitor
title: DNS und VPN
cache: 1m
sites:
- title: AdGuard Home
url: http://192.168.178.58:8082
check-url: http://adguard
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/adguard-home.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Traefik
url: https://traefik.kaleschke.info
check-url: http://traefik:8082/metrics
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- type: docker-containers
title: Network Container
category: network
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers: &containers
traefik:
name: Traefik
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
url: https://traefik.kaleschke.info
description: Reverse Proxy
category: core
hide: false
gitea:
name: Gitea
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
url: https://git.kaleschke.info
description: GitOps Origin
category: core
hide: false
authelia:
name: Authelia
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authelia.svg
url: https://auth.kaleschke.info
description: ForwardAuth
category: core
hide: false
vaultwarden:
name: Vaultwarden
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vaultwarden.svg
url: https://vault.kaleschke.info
description: Password Vault
category: core
hide: false
postgresql17:
name: PostgreSQL 18
icon: si:postgresql
description: Shared DB
category: core
hide: false
Redis:
name: Redis
icon: si:redis
description: Shared Cache
category: core
hide: false
adguard:
name: AdGuard
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/adguard-home.svg
url: http://192.168.178.58:8082
description: DNS Filter
category: network
hide: false
unbound:
name: Unbound
icon: mdi:dns
description: Upstream Resolver
category: network
hide: false
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
+596
View File
@@ -0,0 +1,596 @@
- name: Home
slug: home
width: wide
head-widgets:
- type: search
search-engine: duckduckgo
new-tab: true
autofocus: true
placeholder: Suche im Web oder springe per Bang...
bangs:
- title: Gitea
shortcut: "!git"
url: https://git.kaleschke.info/explore/repos?q={QUERY}
- title: Paperless
shortcut: "!doc"
url: https://paperless.kaleschke.info/documents?query={QUERY}
- title: Nextcloud
shortcut: "!cloud"
url: https://cloud.kaleschke.info/apps/files/?dir=/{QUERY}
- title: Komodo
shortcut: "!komodo"
url: https://komodo.kaleschke.info
- title: Immich
shortcut: "!foto"
url: https://immich.kaleschke.info/search?query={QUERY}
- title: Mealie
shortcut: "!rezept"
url: https://mealie.kaleschke.info/g/home/?search={QUERY}
columns:
- size: small
widgets:
- type: group
widgets:
- type: custom-api
title: Day
body-type: string
skip-json-validation: true
cache: 1s
template: |
{{ $localTime := now }}
{{ $elapsedSeconds := add (mul $localTime.Hour 3600) (mul $localTime.Minute 60) | add $localTime.Second }}
{{ $dayProgress := div (mul $elapsedSeconds 100.0) 86400.0 }}
{{ $gradient := "#70a1ff" }}
{{ if gt $dayProgress 25.0 }}{{ $gradient = "#ff6b6b, #70a1ff" }}{{ end }}
{{ if gt $dayProgress 50.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df" }}{{ end }}
{{ if gt $dayProgress 75.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df, #70a1ff" }}{{ end }}
<div style="text-align: center;">
<div style="width: 100%; height: 12px; background: #23262f; border: 1px solid color-mix(in srgb, var(--color-text-subdue) 55%, transparent); border-radius: 10px; overflow: hidden;">
<div style="height: 100%; width: {{ $dayProgress }}%; background: linear-gradient(90deg, {{ $gradient }});"></div>
</div>
<div class="size-h1" style="margin-top: 6px;">{{ printf "%.2f" $dayProgress }}% des Tages sind vorbei</div>
</div>
- type: custom-api
title: Month
body-type: string
skip-json-validation: true
cache: 1s
template: |
{{ $localTime := now }}
{{ $month := $localTime.Month }}
{{ $daysInMonth := 31 }}
{{ if eq $month 2 }}{{ $daysInMonth = 28 }}{{ end }}
{{ if or (eq $month 4) (eq $month 6) (eq $month 9) (eq $month 11) }}{{ $daysInMonth = 30 }}{{ end }}
{{ $secondsToday := add (mul $localTime.Hour 3600) (mul $localTime.Minute 60) | add $localTime.Second }}
{{ $daysElapsed := add (sub $localTime.Day 1) (div $secondsToday 86400.0) }}
{{ $monthProgress := mul (div $daysElapsed $daysInMonth) 100.0 }}
{{ $gradient := "#70a1ff" }}
{{ if gt $monthProgress 25.0 }}{{ $gradient = "#ff6b6b, #70a1ff" }}{{ end }}
{{ if gt $monthProgress 50.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df" }}{{ end }}
{{ if gt $monthProgress 75.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df, #70a1ff" }}{{ end }}
<div style="text-align: center;">
<div style="width: 100%; height: 12px; background: #23262f; border: 1px solid color-mix(in srgb, var(--color-text-subdue) 55%, transparent); border-radius: 10px; overflow: hidden;">
<div style="height: 100%; width: {{ $monthProgress }}%; background: linear-gradient(90deg, {{ $gradient }});"></div>
</div>
<div class="size-h1" style="margin-top: 6px;">{{ printf "%.2f" $monthProgress }}% des Monats sind vorbei</div>
</div>
- type: custom-api
title: Year
body-type: string
skip-json-validation: true
cache: 1s
template: |
{{ $localTime := now }}
{{ $secondsToday := add (mul $localTime.Hour 3600) (mul $localTime.Minute 60) | add $localTime.Second }}
{{ $secondsElapsed := add (mul (sub $localTime.YearDay 1) 86400) $secondsToday }}
{{ $yearProgress := div (mul $secondsElapsed 100.0) (mul 365 86400) }}
{{ $gradient := "#70a1ff" }}
{{ if gt $yearProgress 25.0 }}{{ $gradient = "#ff6b6b, #70a1ff" }}{{ end }}
{{ if gt $yearProgress 50.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df" }}{{ end }}
{{ if gt $yearProgress 75.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df, #70a1ff" }}{{ end }}
<div style="text-align: center;">
<div style="width: 100%; height: 12px; background: #23262f; border: 1px solid color-mix(in srgb, var(--color-text-subdue) 55%, transparent); border-radius: 10px; overflow: hidden;">
<div style="height: 100%; width: {{ $yearProgress }}%; background: linear-gradient(90deg, {{ $gradient }});"></div>
</div>
<div class="size-h1" style="margin-top: 6px;">{{ printf "%.2f" $yearProgress }}% des Jahres sind vorbei</div>
</div>
- type: clock
hour-format: 24h
show-progress: true
timezones:
- timezone: Europe/Berlin
label: Berlin
- timezone: UTC
label: UTC
- type: custom-api
title: Wetter · KalliHome
title-url: https://home.kaleschke.info
cache: 30s
url: http://homeassistant:8123/api/states/sensor.gw3000a_outdoor_temperature
headers:
Authorization: Bearer ${GLANCE_HA_TOKEN}
Content-Type: application/json
subrequests:
feels:
url: http://homeassistant:8123/api/states/sensor.gw3000a_feels_like_temperature
headers:
Authorization: Bearer ${GLANCE_HA_TOKEN}
humidity:
url: http://homeassistant:8123/api/states/sensor.gw3000a_humidity
headers:
Authorization: Bearer ${GLANCE_HA_TOKEN}
wind:
url: http://homeassistant:8123/api/states/sensor.gw3000a_wind_speed
headers:
Authorization: Bearer ${GLANCE_HA_TOKEN}
gust:
url: http://homeassistant:8123/api/states/sensor.gw3000a_wind_gust
headers:
Authorization: Bearer ${GLANCE_HA_TOKEN}
rain:
url: http://homeassistant:8123/api/states/sensor.gw3000a_daily_rain
headers:
Authorization: Bearer ${GLANCE_HA_TOKEN}
solar:
url: http://homeassistant:8123/api/states/sensor.gw3000a_solar_radiation
headers:
Authorization: Bearer ${GLANCE_HA_TOKEN}
uv:
url: http://homeassistant:8123/api/states/sensor.gw3000a_uv_index
headers:
Authorization: Bearer ${GLANCE_HA_TOKEN}
pressure:
url: http://homeassistant:8123/api/states/sensor.gw3000a_relative_pressure
headers:
Authorization: Bearer ${GLANCE_HA_TOKEN}
template: |
{{ $temp := .JSON.String "state" }}
{{ $feels := (.Subrequest "feels").JSON.String "state" }}
{{ $hum := (.Subrequest "humidity").JSON.String "state" }}
{{ $wind := (.Subrequest "wind").JSON.String "state" }}
{{ $gust := (.Subrequest "gust").JSON.String "state" }}
{{ $rain := (.Subrequest "rain").JSON.String "state" }}
{{ $solar := (.Subrequest "solar").JSON.String "state" }}
{{ $uv := (.Subrequest "uv").JSON.String "state" }}
{{ $press := (.Subrequest "pressure").JSON.String "state" }}
{{ $gustF := (.Subrequest "gust").JSON.Float "state" }}
{{ $divider := "border-left: 1px solid hsla(220, 40%, 70%, 0.14);" }}
<div class="text-center" style="margin-bottom: 12px;">
<div class="color-highlight size-h2" style="font-weight: 700;">{{ $temp }}°C</div>
<div class="size-h6 color-subdue">gefühlt {{ $feels }}° · {{ $hum }}% feucht</div>
</div>
<div class="flex justify-between text-center" style="margin-bottom: 12px;">
<div style="flex: 1;">
<div class="size-h4 {{ if gt $gustF 40.0 }}color-negative{{ else }}color-highlight{{ end }}">{{ $wind }}</div>
<div class="size-h6 uppercase color-subdue">km/h Wind</div>
</div>
<div style="flex: 1; {{ $divider }}">
<div class="size-h4 {{ if gt $gustF 40.0 }}color-negative{{ else }}color-highlight{{ end }}">{{ $gust }}</div>
<div class="size-h6 uppercase color-subdue">km/h Böe</div>
</div>
<div style="flex: 1; {{ $divider }}">
<div class="size-h4 color-highlight">{{ $rain }}</div>
<div class="size-h6 uppercase color-subdue">mm heute</div>
</div>
</div>
<div class="flex justify-between text-center">
<div style="flex: 1;">
<div class="size-h4 color-highlight">{{ $solar }}</div>
<div class="size-h6 uppercase color-subdue">W/m² Solar</div>
</div>
<div style="flex: 1; {{ $divider }}">
<div class="size-h4 color-highlight">{{ $uv }}</div>
<div class="size-h6 uppercase color-subdue">UV-Index</div>
</div>
<div style="flex: 1; {{ $divider }}">
<div class="size-h4 color-highlight">{{ $press }}</div>
<div class="size-h6 uppercase color-subdue">hPa Druck</div>
</div>
</div>
- type: calendar
first-day-of-week: monday
- type: to-do
title: Operator-Notizen
- type: bookmarks
title: Direkte Einstiege
groups:
- title: Core
color: 212 100 50
links:
- title: Komodo
url: https://komodo.kaleschke.info
icon: sh:komodo
- title: Gitea
url: https://git.kaleschke.info
icon: si:gitea
- title: Monitoring
url: https://monitoring.kaleschke.info
icon: si:grafana
- title: Ops
color: 45 70 55
links:
- title: Borg
url: https://borg.kaleschke.info
icon: mdi:archive
- title: Glances
url: https://glances.kaleschke.info
icon: sh:glances
- title: Scrutiny
url: https://scrutiny.kaleschke.info
icon: sh:scrutiny
- size: full
widgets:
- type: server-stats
title: Server Stats
servers:
- type: local
name: Kallilabcore
hide-mountpoints-by-default: false
- type: custom-api
title: Komodo Stacks
title-url: https://komodo.kaleschke.info
cache: 2m
url: http://komodo-core:9120/read
method: POST
body-type: json
body:
type: ListStacks
params: {}
headers:
X-Api-Key: ${GLANCE_KOMODO_API_KEY}
X-Api-Secret: ${GLANCE_KOMODO_API_SECRET}
Content-Type: application/json
template: |
{{ $stacks := .JSON.Array "@this" }}
{{ $total := len $stacks }}
{{ $running := 0 }}
{{ range $stacks }}{{ if eq (.String "info.state") "running" }}{{ $running = add $running 1 }}{{ end }}{{ end }}
{{ $problems := sub $total $running }}
{{ $divider := "border-left: 1px solid hsla(220, 40%, 70%, 0.14);" }}
<div style="display: flex; text-align: center;">
<div style="flex: 1;">
<div class="color-highlight size-h3">{{ $total }}</div>
<div class="size-h6 uppercase color-subdue">Stacks</div>
</div>
<div style="flex: 1; {{ $divider }}">
<div class="color-positive size-h3">{{ $running }}</div>
<div class="size-h6 uppercase color-subdue">Running</div>
</div>
<div style="flex: 1; {{ $divider }}">
<div class="{{ if gt $problems 0 }}color-negative{{ else }}color-subdue{{ end }} size-h3">{{ $problems }}</div>
<div class="size-h6 uppercase color-subdue">Auffaellig</div>
</div>
</div>
<div style="height: 5px; margin-top: 14px; border-radius: 999px; overflow: hidden; background: hsla(220, 30%, 60%, 0.12);">
<div style="height: 100%; width: {{ if gt $total 0 }}{{ div (mul $running 100.0) (toFloat $total) }}{{ else }}0{{ end }}%; border-radius: 999px; background: linear-gradient(90deg, hsl(150, 85%, 42%), hsl(172, 95%, 48%));"></div>
</div>
{{ if gt $problems 0 }}
<div style="display: flex; justify-content: center; gap: 8px; flex-wrap: wrap; margin-top: 12px;">
{{ range $stacks }}
{{ if ne (.String "info.state") "running" }}
<span class="size-h6" style="padding: 3px 12px; border-radius: 999px; border: 1px solid hsla(350, 90%, 60%, 0.45); background: hsla(350, 90%, 60%, 0.08); color: var(--color-negative); letter-spacing: 0.05em;">{{ .String "name" }} · {{ .String "info.state" }}</span>
{{ end }}
{{ end }}
</div>
{{ end }}
- type: custom-api
title: Immich
title-url: https://immich.kaleschke.info
cache: 10m
url: http://immich_server:2283/api/server/statistics
headers:
x-api-key: ${GLANCE_IMMICH_API_KEY}
subrequests:
storage:
url: http://immich_server:2283/api/server/storage
headers:
x-api-key: ${GLANCE_IMMICH_API_KEY}
template: |
{{ $photos := .JSON.Int "photos" }}
{{ $videos := .JSON.Int "videos" }}
{{ $usageGiB := div (toFloat (.JSON.Int "usage")) 1073741824.0 }}
{{ $storage := .Subrequest "storage" }}
{{ $storageOK := and (ge $storage.Response.StatusCode 200) (le $storage.Response.StatusCode 299) }}
{{ $percentage := 0.0 }}
{{ if $storageOK }}{{ $percentage = $storage.JSON.Float "diskUsagePercentage" }}{{ end }}
{{ $divider := "border-left: 1px solid hsla(220, 40%, 70%, 0.14);" }}
<div style="display: flex; text-align: center;">
<div style="flex: 1;">
<div class="color-highlight size-h3">{{ $photos | formatNumber }}</div>
<div class="size-h6 uppercase color-subdue">Fotos</div>
</div>
<div style="flex: 1; {{ $divider }}">
<div class="color-highlight size-h3">{{ $videos | formatNumber }}</div>
<div class="size-h6 uppercase color-subdue">Videos</div>
</div>
<div style="flex: 1; {{ $divider }}">
<div class="color-highlight size-h3">{{ printf "%.0f" $usageGiB }} GiB</div>
<div class="size-h6 uppercase color-subdue">Medien</div>
</div>
</div>
<div style="display: flex; align-items: center; gap: 12px; margin-top: 16px;">
<div style="flex: 1; height: 5px; border-radius: 999px; overflow: hidden; background: hsla(220, 30%, 60%, 0.12);">
<div style="height: 100%; width: {{ if $storageOK }}{{ printf "%.1f" $percentage }}%{{ else }}0%{{ end }}; border-radius: 999px; background: linear-gradient(90deg, hsl(205, 100%, 55%), hsl(172, 95%, 48%));"></div>
</div>
<div class="size-h6 color-subdue" style="white-space: nowrap;">{{ if $storageOK }}{{ printf "%.1f" $percentage }}% belegt{{ else }}Speicher API n/v{{ end }}</div>
</div>
- type: group
widgets:
- type: monitor
title: Core
cache: 1m
sites:
- title: AdGuard Home
url: http://192.168.178.58:8082
check-url: http://adguard
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/adguard-home.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Authelia
url: https://auth.kaleschke.info
check-url: http://authelia:9091/api/health
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authelia.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Gitea
url: https://git.kaleschke.info
check-url: http://gitea:3000/api/healthz
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Traefik
url: https://traefik.kaleschke.info
check-url: http://traefik:8082/metrics
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Vaultwarden
url: https://vault.kaleschke.info
check-url: http://vaultwarden/alive
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vaultwarden.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Komodo
url: https://komodo.kaleschke.info
check-url: http://komodo-core:9120
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/komodo.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Glance
url: https://glance.kaleschke.info
check-url: http://glance:8080
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glance.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- type: monitor
title: Apps
cache: 1m
sites:
- title: Paperless-ngx
url: https://paperless.kaleschke.info
check-url: http://paperless-ngx:8000
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/paperless-ngx.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Paperless-GPT
url: https://paperless-gpt.kaleschke.info
check-url: http://paperless-gpt:8080
icon: mdi:robot
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Immich
url: https://immich.kaleschke.info
check-url: http://immich_server:2283/api/server/ping
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Mealie
url: https://mealie.kaleschke.info
check-url: http://mealie:9000
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mealie.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Nextcloud
url: https://cloud.kaleschke.info
check-url: http://nextcloud/status.php
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: ntfy
url: https://ntfy.kaleschke.info
check-url: http://ntfy/v1/health
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/ntfy.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Mail Archiver
url: https://mail.kaleschke.info
check-url: http://mail-archiver:5000
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mailcow.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: BentoPDF
url: https://pdf.kaleschke.info
check-url: http://bentopdf:8080
icon: mdi:file-pdf-box
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- type: monitor
title: Ops
cache: 1m
sites:
- title: Monitoring Grafana
url: https://monitoring.kaleschke.info
check-url: http://monitoring-grafana:3000/api/health
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/grafana.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Glances
url: https://glances.kaleschke.info
check-url: http://glances:61208
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glances.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Scrutiny
url: https://scrutiny.kaleschke.info
check-url: http://scrutiny:8080
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/scrutiny.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Speedtest Tracker
url: https://speedtest.kaleschke.info
check-url: http://speedtest-tracker
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/speedtest-tracker.png
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Filebrowser
url: https://files.kaleschke.info
check-url: http://filebrowser
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/filebrowser.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: code-server
url: https://code.kaleschke.info
check-url: http://code-server:8443
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vscode.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Borg UI
url: https://borg.kaleschke.info
check-url: http://borg-ui:8081
icon: mdi:archive-sync
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- size: small
widgets:
- type: custom-api
title: Internet
title-url: https://speedtest.kaleschke.info
cache: 1h
url: http://speedtest-tracker/api/v1/results/latest
headers:
Authorization: Bearer ${GLANCE_SPEEDTEST_API_KEY}
Accept: application/json
subrequests:
stats:
url: http://speedtest-tracker/api/v1/stats
headers:
Authorization: Bearer ${GLANCE_SPEEDTEST_API_KEY}
Accept: application/json
template: |
{{ $ip := .JSON.String "external_ip" }}
{{ if eq $ip "" }}{{ $ip = .JSON.String "data.interface.externalIp" }}{{ end }}
{{ if eq $ip "" }}{{ $ip = .JSON.String "data.data.interface.externalIp" }}{{ end }}
{{ $isp := .JSON.String "isp" }}
{{ if eq $isp "" }}{{ $isp = .JSON.String "data.isp" }}{{ end }}
{{ if eq $isp "" }}{{ $isp = .JSON.String "data.data.isp" }}{{ end }}
{{ $download := .JSON.Float "download" }}
{{ if eq $download 0.0 }}{{ $download = .JSON.Float "data.download" }}{{ end }}
{{ if eq $download 0.0 }}{{ $download = div (.JSON.Float "download_bits") 1000000.0 }}{{ end }}
{{ if eq $download 0.0 }}{{ $download = div (.JSON.Float "data.download_bits") 1000000.0 }}{{ end }}
{{ if eq $download 0.0 }}{{ $download = div (mul (.JSON.Float "data.data.download.bandwidth") 8.0) 1000000.0 }}{{ end }}
{{ $upload := .JSON.Float "upload" }}
{{ if eq $upload 0.0 }}{{ $upload = .JSON.Float "data.upload" }}{{ end }}
{{ if eq $upload 0.0 }}{{ $upload = div (.JSON.Float "upload_bits") 1000000.0 }}{{ end }}
{{ if eq $upload 0.0 }}{{ $upload = div (.JSON.Float "data.upload_bits") 1000000.0 }}{{ end }}
{{ if eq $upload 0.0 }}{{ $upload = div (mul (.JSON.Float "data.data.upload.bandwidth") 8.0) 1000000.0 }}{{ end }}
{{ if gt $download 100000.0 }}{{ $download = div (mul $download 8.0) 1000000.0 }}{{ end }}
{{ if gt $upload 100000.0 }}{{ $upload = div (mul $upload 8.0) 1000000.0 }}{{ end }}
{{ $ping := .JSON.Float "ping" }}
{{ if eq $ping 0.0 }}{{ $ping = .JSON.Float "data.ping" }}{{ end }}
{{ if eq $ping 0.0 }}{{ $ping = .JSON.Float "data.data.ping.latency" }}{{ end }}
<div class="text-center" style="margin-bottom: 10px;">
<div class="color-primary size-h3" style="font-weight: 700;">{{ if ne $ip "" }}{{ $ip }}{{ else }}WAN online{{ end }}</div>
<div class="size-h6 color-subdue">{{ if ne $isp "" }}{{ $isp }}{{ else }}Speedtest Tracker{{ end }}</div>
</div>
{{ if and (eq $download 0.0) (eq $upload 0.0) }}
<div class="text-center color-subdue size-h6">Keine aktuellen Messdaten</div>
{{ else }}
<div class="flex justify-between text-center">
<div>
<div class="color-highlight size-h4">{{ printf "%.1f" $download }}</div>
<div class="size-h6 color-subdue">MBIT DOWN</div>
</div>
<div>
<div class="color-highlight size-h4">{{ printf "%.1f" $upload }}</div>
<div class="size-h6 color-subdue">MBIT UP</div>
</div>
<div>
<div class="color-highlight size-h4">{{ printf "%.0f ms" $ping }}</div>
<div class="size-h6 color-subdue">PING</div>
</div>
</div>
{{ end }}
- type: dns-stats
title: DNS Stats
service: adguard
url: http://adguard
username: ${GLANCE_ADGUARD_USERNAME}
password: ${GLANCE_ADGUARD_PASSWORD}
- type: custom-api
title: Borg Backup
title-url: https://borg.kaleschke.info
cache: 15m
url: http://monitoring-prometheus:9090/api/v1/query?query=(time()-homelab_borg_last_completed_timestamp_seconds)/3600
subrequests:
success:
url: http://monitoring-prometheus:9090/api/v1/query?query=homelab_borg_last_success
template: |
{{ $ageHours := .JSON.Float "data.result.0.value.1" }}
{{ $archive := .JSON.String "data.result.0.metric.archive" }}
{{ $succ := .Subrequest "success" }}
{{ $ok := $succ.JSON.Float "data.result.0.value.1" }}
{{ $status := $succ.JSON.String "data.result.0.metric.status" }}
{{ if eq (len (.JSON.Array "data.result")) 0 }}
<div class="text-center color-subdue">Keine Backup-Metrik gefunden</div>
{{ else }}
<div class="text-center">
<div class="size-h2 {{ if gt $ageHours 30.0 }}color-negative{{ else }}color-positive{{ end }}">vor {{ printf "%.0f" $ageHours }} h</div>
<div class="size-h6 color-subdue" style="margin-top: 4px;">letztes abgeschlossenes Backup</div>
<div class="size-h6 {{ if eq $ok 1.0 }}color-positive{{ else }}color-negative{{ end }}" style="margin-top: 6px;">
{{ if eq $ok 1.0 }}letzter Job erfolgreich{{ else }}letzter Job: {{ $status }}{{ end }}
</div>
{{ if ne $archive "" }}<div class="size-h6 color-subdue text-truncate" style="margin-top: 2px;">{{ $archive }}</div>{{ end }}
</div>
{{ end }}
- type: group
widgets:
- type: docker-containers
title: Network
category: network
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers:
$include: containers-map.yml
- type: docker-containers
title: Apps
category: apps
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers:
$include: containers-map.yml
- type: docker-containers
title: Ops
category: ops
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers:
$include: containers-map.yml
+244
View File
@@ -0,0 +1,244 @@
- name: Infrastructure and Media
slug: infrastructure
width: wide
columns:
- size: small
widgets:
- type: bookmarks
title: Core
groups:
- title: Control Plane
color: 212 100 50
links:
- title: Komodo
url: https://komodo.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/komodo.svg
- title: Gitea
url: https://git.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
- title: Traefik
url: https://traefik.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
- title: Authelia
url: https://auth.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authelia.svg
- type: bookmarks
title: Media und Apps
groups:
- title: Apps
color: 140 70 40
links:
- title: Immich
url: https://immich.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
- title: Paperless
url: https://paperless.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/paperless-ngx.svg
- title: Nextcloud
url: https://cloud.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg
- title: Mealie
url: https://mealie.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mealie.svg
- type: custom-api
title: Scrutiny Disk Health
title-url: https://scrutiny.kaleschke.info
cache: 30m
url: http://scrutiny:8080/api/summary
template: |
{{ $disks := .JSON.Array "data.summary.@values" }}
{{ if eq (len $disks) 0 }}
<div class="text-center color-subdue">Keine Disks gemeldet.</div>
{{ else }}
<ul class="list list-gap-4">
{{ range $disks }}
{{ $status := .Int "device.device_status" }}
<li class="flex justify-between">
<div class="color-highlight">{{ .String "device.device_name" }}</div>
<div class="size-h6 uppercase {{ if eq $status 0 }}color-positive{{ else }}color-negative{{ end }}">
{{ if eq $status 0 }}OK{{ else }}FAILED{{ end }}
</div>
</li>
{{ end }}
</ul>
{{ end }}
- size: full
widgets:
- type: custom-api
title: GitOps - homelab-infra
title-url: https://git.kaleschke.info/Micha/homelab-infra
cache: 5m
url: http://gitea:3000/api/v1/repos/Micha/homelab-infra/commits?limit=5&stat=false
headers:
Authorization: token ${GLANCE_GITEA_TOKEN}
Accept: application/json
subrequests:
repo:
url: http://gitea:3000/api/v1/repos/Micha/homelab-infra
headers:
Authorization: token ${GLANCE_GITEA_TOKEN}
Accept: application/json
template: |
{{ $repo := .Subrequest "repo" }}
{{ $repoOK := and (ge $repo.Response.StatusCode 200) (le $repo.Response.StatusCode 299) }}
{{ if $repoOK }}
<div class="flex justify-between text-center" style="margin-bottom: 12px;">
<div>
<div class="color-highlight size-h3">{{ $repo.JSON.Int "open_issues_count" }}</div>
<div class="size-h6 uppercase">Issues</div>
</div>
<div>
<div class="color-highlight size-h3">{{ $repo.JSON.Int "open_pr_counter" }}</div>
<div class="size-h6 uppercase">PRs</div>
</div>
<div>
<div class="color-highlight size-h3">{{ $repo.JSON.String "default_branch" }}</div>
<div class="size-h6 uppercase">Branch</div>
</div>
</div>
{{ end }}
<ul class="list list-gap-6">
{{ range .JSON.Array "@this" }}
<li>
<div class="flex justify-between">
<div class="color-highlight text-truncate" style="max-width: 75%;">{{ .String "commit.message" | replaceMatches "(?s)\n.*" "" }}</div>
<div class="size-h6 color-subdue">{{ slice (.String "sha") 0 7 }}</div>
</div>
<div class="size-h6 color-subdue">{{ .String "commit.author.name" }} · <span {{ .String "commit.author.date" | parseTime "rfc3339" | toRelativeTime }}></span></div>
</li>
{{ end }}
</ul>
- type: monitor
title: Platform Checks
cache: 1m
sites:
- title: Gitea
url: https://git.kaleschke.info
check-url: http://gitea:3000/api/healthz
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Monitoring Grafana
url: https://monitoring.kaleschke.info
check-url: http://monitoring-grafana:3000/api/health
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/grafana.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Glance
url: https://glance.kaleschke.info
check-url: http://glance:8080
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glance.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Immich
url: https://immich.kaleschke.info
check-url: http://immich_server:2283/api/server/ping
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Paperless-ngx
url: https://paperless.kaleschke.info
check-url: http://paperless-ngx:8000
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/paperless-ngx.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Nextcloud
url: https://cloud.kaleschke.info
check-url: http://nextcloud/status.php
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- type: group
widgets:
- type: docker-containers
title: Core
category: core
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers:
$include: containers-map.yml
- type: docker-containers
title: Apps
category: apps
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers:
$include: containers-map.yml
- type: docker-containers
title: Ops
category: ops
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers:
$include: containers-map.yml
- size: small
widgets:
- type: custom-api
title: Paperless-ngx
title-url: https://paperless.kaleschke.info
cache: 15m
url: http://paperless-ngx:8000/api/statistics/
headers:
Authorization: Token ${GLANCE_PAPERLESS_TOKEN}
Accept: application/json
template: |
{{ $total := .JSON.Int "documents_total" }}
{{ $inbox := .JSON.Int "documents_inbox" }}
<div class="flex justify-between text-center">
<div>
<div class="color-highlight size-h3">{{ $total | formatNumber }}</div>
<div class="size-h6 uppercase">Dokumente</div>
</div>
<div>
<div class="size-h3 {{ if gt $inbox 0 }}color-negative{{ else }}color-positive{{ end }}">{{ $inbox }}</div>
<div class="size-h6 uppercase">Inbox</div>
</div>
</div>
- type: custom-api
title: Mealie
title-url: https://mealie.kaleschke.info
cache: 1h
url: http://mealie:9000/api/admin/about/statistics
headers:
Authorization: Bearer ${GLANCE_MEALIE_TOKEN}
Accept: application/json
template: |
<div class="flex justify-between text-center">
<div>
<div class="color-highlight size-h3">{{ .JSON.Int "totalRecipes" | formatNumber }}</div>
<div class="size-h6 uppercase">Rezepte</div>
</div>
<div>
<div class="color-highlight size-h3">{{ .JSON.Int "totalCategories" }}</div>
<div class="size-h6 uppercase">Kategorien</div>
</div>
<div>
<div class="color-highlight size-h3">{{ .JSON.Int "totalUsers" }}</div>
<div class="size-h6 uppercase">Nutzer</div>
</div>
</div>
- type: bookmarks
title: Ops
groups:
- title: Tools
color: 4 78 57
links:
- title: Glances
url: https://glances.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glances.svg
- title: Scrutiny
url: https://scrutiny.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/scrutiny.svg
- title: Speedtest
url: https://speedtest.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/speedtest-tracker.png
+80
View File
@@ -0,0 +1,80 @@
- name: Ops und Releases
slug: ops
width: wide
columns:
- size: small
widgets:
- type: rss
title: Selfhosted News
style: vertical-list
limit: 12
collapse-after: 6
cache: 6h
feeds:
- url: https://selfh.st/rss/
title: selfh.st
- url: https://tailscale.com/blog/index.xml
title: Tailscale Blog
- size: full
widgets:
- type: releases
title: Image Releases
cache: 12h
show-source-icon: true
collapse-after: 15
repositories:
- glanceapp/glance
- traefik/traefik
- go-gitea/gitea
- moghtech/komodo
- immich-app/immich
- paperless-ngx/paperless-ngx
- AdguardTeam/AdGuardHome
- dani-garcia/vaultwarden
- authelia/authelia
- mealie-recipes/mealie
- nextcloud/server
- AnalogJ/scrutiny
- alexjustesen/speedtest-tracker
- binwiederhier/ntfy
- filebrowser/filebrowser
- coder/code-server
- qdm12/ddns-updater
- nicolargo/glances
- size: small
widgets:
- type: custom-api
title: Letzte Commits
title-url: https://git.kaleschke.info/Micha/homelab-infra/commits/branch/master
cache: 5m
url: http://gitea:3000/api/v1/repos/Micha/homelab-infra/commits?limit=8&stat=false
headers:
Authorization: token ${GLANCE_GITEA_TOKEN}
Accept: application/json
template: |
<ul class="list list-gap-6 collapsible-container" data-collapse-after="5">
{{ range .JSON.Array "@this" }}
<li>
<div class="color-highlight text-truncate">{{ .String "commit.message" | replaceMatches "(?s)\n.*" "" }}</div>
<div class="size-h6 color-subdue">{{ slice (.String "sha") 0 7 }} · <span {{ .String "commit.author.date" | parseTime "rfc3339" | toRelativeTime }}></span></div>
</li>
{{ end }}
</ul>
- type: bookmarks
title: Deploy-Kette
groups:
- title: GitOps
color: 212 100 50
links:
- title: Gitea Repo
url: https://git.kaleschke.info/Micha/homelab-infra
icon: si:gitea
- title: Komodo Stacks
url: https://komodo.kaleschke.info
icon: sh:komodo
- title: Grafana
url: https://monitoring.kaleschke.info
icon: si:grafana
+3
View File
@@ -0,0 +1,3 @@
$include: home.yml
$include: infrastructure.yml
$include: ops.yml
+11
View File
@@ -9,11 +9,20 @@ services:
GLANCE_ADGUARD_USERNAME: ${GLANCE_ADGUARD_USERNAME:-} GLANCE_ADGUARD_USERNAME: ${GLANCE_ADGUARD_USERNAME:-}
GLANCE_ADGUARD_PASSWORD: ${GLANCE_ADGUARD_PASSWORD:-} GLANCE_ADGUARD_PASSWORD: ${GLANCE_ADGUARD_PASSWORD:-}
GLANCE_SPEEDTEST_API_KEY: ${GLANCE_SPEEDTEST_API_KEY:-} GLANCE_SPEEDTEST_API_KEY: ${GLANCE_SPEEDTEST_API_KEY:-}
GLANCE_KOMODO_API_KEY: ${GLANCE_KOMODO_API_KEY:-}
GLANCE_KOMODO_API_SECRET: ${GLANCE_KOMODO_API_SECRET:-}
GLANCE_GITEA_TOKEN: ${GLANCE_GITEA_TOKEN:-}
GLANCE_PAPERLESS_TOKEN: ${GLANCE_PAPERLESS_TOKEN:-}
GLANCE_MEALIE_TOKEN: ${GLANCE_MEALIE_TOKEN:-}
GLANCE_HA_TOKEN: ${GLANCE_HA_TOKEN:-}
volumes: volumes:
- ./config:/app/config:ro - ./config:/app/config:ro
- ./assets:/app/assets:ro
networks: networks:
- frontend_net - frontend_net
- glance_socket_net - glance_socket_net
# monitoring_net nur lesend fuer Prometheus-Query des Borg-Backup-Widgets
- monitoring_net
depends_on: depends_on:
- glance-docker-socket-proxy - glance-docker-socket-proxy
labels: labels:
@@ -50,6 +59,8 @@ services:
networks: networks:
frontend_net: frontend_net:
external: true external: true
monitoring_net:
external: true
glance_socket_net: glance_socket_net:
name: glance_socket_net name: glance_socket_net
internal: true internal: true
+82
View File
@@ -0,0 +1,82 @@
# H:/ Nearline-Backup — Struktur und Betrieb
Stand: 2026-06-10
## Rolle der H:/
Die externe HDD (asmedia ASM235, 7.4 TB, Laufwerk `H:`) dient ausschließlich als
**Nearline-Backup-Spiegel** für kritische Dumps und Git-Bundles.
Sie ist kein Primär-Backup (das ist Hetzner/Borg) und kein dauerhaftes Archiv.
## Sollzustand
```
H:\
└── kallilab-nearline-backups\
├── borg-dumps\latest\ ← aktuelle DB-Dumps (per Script)
├── git-bundles\gitea\ ← Gitea-Repo-Bundles (per Script)
├── _dr-kit\ ← SSH-Keys, Offline-Secrets (manuell)
├── _logs\ ← Robocopy-Logs je Lauf
└── _reports\ ← Markdown-Reports je Lauf
```
Nichts weiteres gehört dauerhaft auf die H:/.
Temporäre Recovery- oder Backup-Ordner aus Notfallsituationen sind nach
Abschluss zu löschen.
## Automatischer Pull
`pull-critical-backups.ps1` zieht per Robocopy vom Unraid-SMB-Share:
- `\\192.168.178.58\backups\borg\dumps\latest``borg-dumps\latest\`
- `\\192.168.178.58\backups\git-bundles\gitea``git-bundles\gitea\`
Der Windows Scheduled Task `KalliLab H Drive Nearline Pull` laeuft seit
2026-05-28 taeglich 05:30. Das Script kopiert bewusst **nicht** mit `/MIR` und
loescht nichts auf H:/; alte Artefakte werden nur nach manueller Sichtpruefung
entfernt. Aufruf zum Testen:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File G:\Gitea_Clone\homelab-infra\ops\h-drive-nearline\pull-critical-backups.ps1 -WhatIf
```
Das Script schließt bewusst aus:
- `unraid-flash-config.tar.gz` (0600 root:root, nicht per SMB zugänglich → Restore aus Hetzner-Borg)
- Migration-/Cutover-Verzeichnisse (`immich-vectorchord-*`, `pg18-major-*`, `redis8-*` etc.)
## _dr-kit
Enthält offline hinterlegte Schlüssel und Secrets für den DR-Fall:
- `dr-hetzner` / `dr-hetzner.pub` — SSH-Key für Hetzner Storage Box
- `dr-readonly` / `dr-readonly.pub` — Read-only Deploy-Key
- `KOmodo Secrets.txt` — Komodo Stack ENV-Offline-Dokumentation
Diese Dateien sind **manuell** zu pflegen und **nicht** vom Pull-Script verwaltet.
## Archiv-Ordner
Temporäre Notfall-Artefakte verbleiben als `_archiv-*`-Ordner bis zur bewussten
Löschentscheidung:
| Ordner | Inhalt | Anlassdatum |
|---|---|---|
| `kallilab-recovery\_archiv-nvme-crash-image-2026-05-14\` | nvme0n1 Disk-Image (1863 GB) + Crash-Runbooks aus dem Mai-2026-Ausfall | 2026-05-14 |
## Aufräum-Historie
| Datum | Aktion |
|---|---|
| 2026-06-10 | `OneDrive - Stroetmann Group\` gelöscht (leer) |
| 2026-06-10 | SSH-Keys + Secrets aus nearline-Root in `_dr-kit\` verschoben |
| 2026-06-10 | Migration-Artefakt-Verzeichnisse in `borg-dumps\latest\` gelöscht (immich-vectorchord-*, pg18-major-*, redis8-*, nextcloud-redis-pre-redis8-*, shared-redis-pre-redis8-*) |
| 2026-06-10 | Pre-major-prod-Dumps gelöscht (PG17→PG18-Migration abgeschlossen) |
| 2026-06-10 | `kallilab-recovery\2026-05-15\` gelöscht (DNS-Restore-Reste) |
| 2026-06-10 | `kallilab-recovery\2026-05-14\``_archiv-nvme-crash-image-2026-05-14\` umbenannt |
| 2026-06-10 | `kallilab-recovery\disk1-phase2-2026-05-23\` gelöscht (1677 GB Media-Share-Kopie; Unraid-Share verifiziert vollständig) |
## Offene Punkte
- `Windows-Neuaufsetzen-Backup\` (48 GB): nach vollständiger Rückspielung auf D:\ löschen
- `_archiv-nvme-crash-image-2026-05-14\` (1863 GB): löschen sobald sicher, dass nichts mehr aus dem alten System benötigt wird
- Log-Rotation für `_logs\` und `_reports\`: manuell oder per Script, Empfehlung 30 Tage
-29
View File
@@ -1,29 +0,0 @@
# Policy Check Report
## Summary
- Compose files checked: 29
- Critical findings: 0
- Warnings: 1
- Info findings: 13
## Critical
- none
## Warnings
- [USER001] monitoring\docker-compose.yml :: influxdb3-core: Runs as user 0. Documented exception, keep visible for hardening.
## Info
- [PORT001] core\gitea\docker-compose.yml :: gitea: Allowed host port mapping: 222:22
- [PORT001] host-services\Adguard\docker-compose.yml :: adguard: Allowed host port mapping: 53:53/tcp
- [PORT001] host-services\Adguard\docker-compose.yml :: adguard: Allowed host port mapping: 53:53/udp
- [PORT001] host-services\Adguard\docker-compose.yml :: adguard: Allowed host port mapping: 100.80.98.33:8082:80
- [HOSTNET001] host-services\plex\docker-compose.yml :: plex: network_mode: host is a documented exception.
- [HOSTNET001] host-services\tailscale\docker-compose.yml :: tailscale: network_mode: host is a documented exception.
- [IMAGE002] infra\ddns-updater\docker-compose.yml :: ddns-updater: Image uses a latest tag but is digest-pinned and documented as an exception.
- [PORT001] monitoring\docker-compose.yml :: influxdb3-core: Allowed host port mapping: ${INFLUXDB_BIND_IP:-127.0.0.1}:8181:8181
- [IMAGE002] ops\glances\docker-compose.yml :: glances: Image uses a latest tag but is digest-pinned and documented as an exception.
- [IMAGE002] ops\scrutiny\docker-compose.yml :: scrutiny: Image uses a latest tag but is digest-pinned and documented as an exception.
- [PRIV001] ops\scrutiny\docker-compose.yml :: scrutiny: Privileged mode is a documented exception.
- [PORT001] traefik\docker-compose.yml :: traefik: Allowed host port mapping: 80:80
- [PORT001] traefik\docker-compose.yml :: traefik: Allowed host port mapping: 443:443
+64 -87
View File
@@ -1,109 +1,86 @@
# Restore Tests # Restore-Tests - Betrieb und Werkzeuge
Kontrollierte Restore-Tests fuer `homelab-infra`. Typ: Runbook/Tool-Doku · Stand: 2026-06-11 · Status: aktiv
Ziel: Kontrollierte Restore-Tests fuer `homelab-infra`. Dieses Dokument ist das
**einzige** Betriebsdokument fuer Restore-Tests (das fruehere
`docs/RESTORE_HANDBOOK.md` ist hierin aufgegangen). Verwandt:
- Backups durch echte Test-Restores verifizieren - `docs/RESTORE_MATRIX.md` - Restore-Quellen, Secrets, Smoke-Tests und **Test-Reifegrad je Dienst** (einziger Status-Ort)
- produktive Pfade nicht beschreiben - `docs/DISASTER_RECOVERY.md` - echter Wiederanlauf
- Testlaeufe spaeter weitgehend automatisieren - `schedule.md` - Kadenz, Cron-Ausdruecke und Shell-Guards
- `unraid-user-scripts.md` - Unraid-User-Script-Vorlagen fuer die Host-Jobs
## Grundregeln ## Grundregeln
- Restore-Quelle bleibt im Backup-Bereich, z. B. `/mnt/user/backups/borg` - Restore-Quelle bleibt das produktive Borg-Repo bei Hetzner; Zugriff ueber den vorhandenen `borg-ui`-Container
- Test-Restores laufen nur in `/mnt/user/backups/restore-lab` - Passphrase kommt aus `/mnt/user/appdata/secrets/borg_repo_passphrase.txt`, nie aus UI-Interna
- Reports landen in `/mnt/user/backups/restore-reports` - Testdaten landen nur unter `/mnt/user/backups/restore-lab/<dienst>`; bei Fehlschlag wird nach `_failed/` verschoben statt geloescht
- Test-Container nutzen das Praefix `restoretest-` - Reports landen unter `/mnt/user/backups/restore-reports`
- keine produktiven Volumes schreibend mounten - Testcontainer nutzen das Praefix `restoretest-`, localhost-Ports, keine produktive Domain, keine Traefik-Route
- keine produktiven Domains fuer Testinstanzen uebernehmen - keine produktiven Volumes schreibend mounten, keine produktiven Pfade beschreiben
- keine Restore-Automatik fuer neue Dienste ohne bewusste Freigabe
## Geplante Struktur ## Erfolgskriterien
- `schedule.md`: Intervalle und Verantwortlichkeiten Ein Restore-Test gilt nur dann als erfolgreich, wenn Quelle lesbar war, Daten
- `common.sh`: gemeinsame Helfer fuer Borg-Lookup, Borg-Extract und Compose-Cleanup; prueft vor Borg-Operationen auch `borg-ui:/data/borg.db` und `borg-ui:/local/secrets/borg_repo_passphrase.txt` im Restore-Lab ankamen, der Testcontainer startete, der **fachliche**
- `vaultwarden-restore-test.ps1`: erster Mini-Restore-Ablauf Smoke-Test gelang und ein Report geschrieben wurde. "Container laeuft" allein
- `vaultwarden-restore-test.sh`: hosttauglicher Vaultwarden-Restore-Job reicht nicht.
- `vaultwarden-plan.md`: konkreter Vaultwarden-Testplan
- `vaultwarden-compose.test.yml`: isolierte Testinstanz fuer Vaultwarden
- `gitea-restore-test.ps1`: Gitea-Mini-Restore-Ablauf
- `gitea-restore-test.sh`: hosttauglicher Gitea-Restore-Job
- `gitea-plan.md`: konkreter Gitea-Testplan
- `gitea-compose.test.yml`: isolierte Testinstanz fuer Gitea
- `paperless-restore-test.ps1`: Paperless-Mini-Restore-Ablauf
- `paperless-restore-test.sh`: hosttauglicher Paperless-Restore-Job
- `paperless-plan.md`: konkreter Paperless-Testplan
- `paperless-compose.test.yml`: isolierte Testinstanz fuer Paperless inkl. Test-Postgres und Test-Redis
- `immich-restore-test.ps1`: Immich-Mini-Restore-Ablauf als Plan-/Windows-Scaffold
- `immich-restore-test.sh`: hosttauglicher Immich-Restore-Job, erster echter Lauf noch offen
- `immich-plan.md`: konkreter Immich-Testplan
- `immich-runbook.md`: Operator-Runbook fuer den ersten Immich-Lauf
- `immich-compose.test.yml`: isolierte Testinstanz fuer Immich inkl. VectorChord/pgvector-Test-Postgres und Test-Redis
- `authelia-restore-test.sh`: Authelia-Restore-Job (Config-Smoke; Erstlauf 2026-06-03 erfolgreich)
- `authelia-compose.test.yml`: isolierte Testinstanz fuer Authelia inkl. Test-Postgres, Filesystem-Notifier (kein echter SMTP-Versand)
- `authelia-plan.md`: konkreter Authelia-Testplan
- `authelia-runbook.md`: Operator-Runbook fuer den ersten Authelia-Lauf
- `adguard-restore-test.sh`: AdGuard-Home-Restore-Job (Config + isolierter Container + HTTP/DNS-Smoke; Erstlauf 2026-06-06 erfolgreich)
- `adguard-compose.test.yml`: isolierte AdGuard-Testinstanz auf localhost-Ports `13001` und `15353`
- `redis-restore-test.sh`: Redis-8-Restore-Job (Pre-Cutover-Artefakt + isolierter Container + PING/INFO/DBSIZE; Erstlauf 2026-06-06 erfolgreich)
- `redis-compose.test.yml`: isolierte Redis-8-Testinstanz auf localhost-Port `16379`
- `nextcloud-restore-test.sh`: Nextcloud-Restore-Job (Scaffold; **blockiert** durch Unraid shfs-chmod-Inkompatibilitaet - siehe unten)
- `nextcloud-compose.test.yml`: isolierte Testinstanz fuer Nextcloud inkl. Test-Postgres und Test-Redis
- `check-restore-freshness.ps1`: woechentlicher Frische-Check fuer Dumps und Reports ## Aufbau des Verzeichnisses
- `run-restore-checks.ps1`: einfacher Dispatcher fuer Restore-Jobs
- `check-restore-freshness.sh`: hosttauglicher Frische-Check
- `negative-freshness-alert-test.sh`: sicherer Negativtest fuer den Frische-Alarmweg; nutzt synthetische leere Testpfade unter `/mnt/user/backups/restore-lab/freshness-negative`, veraendert keine produktiven Dumps und sendet bei erkanntem Fehler einen Test-Alert nach `homelab-alerts`
- `run-restore-checks.sh`: hosttauglicher Dispatcher
- `common.sh`: gemeinsame Host-Helferfunktionen
- `automation-plan.md`: Host-Job- und Automatisierungsmodell
## Automatisierungsmodell Pro Dienst existieren bis zu drei Artefakte:
- Ausfuehrung: Unraid User Script / Host-Job - `<dienst>-restore-test.sh` - automatisierter Host-Job (produktive Wahrheit)
- Logik: Repo-Skripte in diesem Verzeichnis - `<dienst>-compose.test.yml` - isolierte Testinstanz
- Ergebnis: Markdown-Report - `<dienst>-runbook.md` - manueller Ablauf bzw. Besonderheiten
- Meldung: `ntfy`
- Hermes: optional nur fuer Zusammenfassung und Auswertung
Wichtig: Dazu zentrale Helfer:
- die Bash-Skripte `*.sh` sind die produktive Host-Variante - `run-restore-checks.sh` - Dispatcher (Host), `run-restore-checks.ps1` (lokale Planvariante)
- `check-restore-freshness.ps1` und die `*.ps1`-Dateien bleiben als lokale Plan-/Hilfsvariante nutzbar - `run-restore-job-with-ntfy.sh` - Wrapper: Erfolg -> `homelab-info`, Fehler -> `homelab-alerts`
- im Windows-Clone fehlen die `/mnt/user/...`-Pfade naturgemaess - `check-restore-freshness.sh` / `.ps1` - woechentlicher Frische-Check fuer Dumps und Reports (prueft pg-Dumps per `pg_restore --list`)
- `negative-freshness-alert-test.sh` - sicherer Negativtest des Alarmwegs (synthetischer Leerpfad, quartalsweise)
- `common.sh` - gemeinsame Borg-/Compose-Helfer
- `automation-plan.md` - Host-Job- und Automatisierungsmodell
## Validiertes Grundmuster ## Betriebsmodus
Stand nach dem ersten echten Vaultwarden-Test: Stand 2026-06-11 ist der Betrieb auf V1+ (validierte Bash-Host-Jobs mit ntfy):
- Borg-Quelle bleibt das produktive Remote-Repo bei Hetzner - Host-Jobs laufen als Unraid User Scripts vom Repo-Spiegel `/mnt/user/services/homelab-infra`
- Borg-Zugriff laeuft praktisch ueber den vorhandenen `borg-ui`-Container - Kadenz und Cron-Ausdruecke: `schedule.md` (woechentlicher Frische-Check, monatliche/quartalsweise Dienst-Rotation, monatlicher Zufalls-Restore)
- SSH-Trust wird ueber `known_hosts` im `borg-ui`-Container hergestellt - Job-Vorlagen: `unraid-user-scripts.md`
- die Borg-Passphrase kommt fuer Restore-Tests aus einer Host-Secret-Datei
- Restore-Ziel liegt immer getrennt unter `/mnt/user/backups/restore-lab`
- Reports liegen unter `/mnt/user/backups/restore-reports`
- Testinstanzen bekommen keine produktive Domain und keine Traefik-Route
Das ist das bevorzugte Muster fuer weitere dateibasierte Restore-Tests wie `gitea`. ## Schnellstart
Fuer datenbankgestuetzte Dienste wie `paperless` kommt zusaetzlich ein isolierter Dump-Restore in Test-Postgres dazu. ```bash
# Frische-Check
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh freshness
## Status # Dienst-Restore-Check (vaultwarden|gitea|paperless|immich|authelia|adguard|redis|homeassistant|komodo-bootstrap|nextcloud)
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh <dienst>
Aktuell ist das erste validierte Muster vorhanden. # Negativtest des Alarmwegs (quartalsweise)
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh freshness-negative
- echter Vaultwarden-Restore am 2026-05-07 erfolgreich verifiziert # Mit ntfy-Meldung
- echter Gitea-Restore am 2026-05-07 erfolgreich verifiziert bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-job-with-ntfy.sh freshness homelab-info
- echter Paperless-Restore am 2026-05-07 erfolgreich verifiziert ```
- Immich-Restore-Test am 2026-05-27 erfolgreich verifiziert; Test-Postgres wurde nach der VectorChord-Migration am 2026-05-31 auf das produktive Immich-Postgres-Image umgestellt
- Authelia-Restore-Smoke am 2026-06-03 erfolgreich verifiziert; bewusst ohne produktiven Dump-Restore wegen Storage-Encryption-Key-Kopplung
- AdGuard-Home-Restore-Smoke am 2026-06-06 erfolgreich verifiziert; Borg-Config-Restore, HTTP `/control/status` 401, DNS-Smoke ok, 7 Filterlisten-Eintraege, Report `/mnt/user/backups/restore-reports/adguard-2026-06-06.md`
- Redis-8-Restore-Smoke am 2026-06-06 erfolgreich verifiziert; Pre-Cutover-Artefakt, Redis 8.8, PING ok, AOF aktiv, DBSIZE 1, Report `/mnt/user/backups/restore-reports/redis-2026-06-06.md`
- 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
- Frische-Negativtest ist als sicherer Host-Job verfuegbar und am 2026-06-06 auf Unraid validiert: `ops/restore-tests/run-restore-checks.sh freshness-negative`. Ergebnis: synthetischer leerer Dump-Pfad erzeugte 10 Criticals, Test-Alert ging nach `homelab-alerts`, produktive Dump-Pfade blieben unangetastet. Report: `/mnt/user/backups/restore-reports/freshness-negative-2026-06-06-130320.md`.
- Nextcloud-Restore-Test: Scaffold existiert, aber **blockiert**. Nextcloud 33 fuehrt zur Laufzeit `chmod()` auf Dateien unter `/var/www/html` aus (`OC_Util.php:486`). Auf Unraids FUSE/shfs User-Shares ist `chmod` strukturell nicht moeglich, was zu permanenter 503 fuehrt. Loesungsoptionen: (a) Restore-Lab auf ein Cache-Drive statt User Share legen, (b) Docker-Volumes statt Bind-Mounts verwenden, (c) tmpfs-Mount fuer html/ + `rsync` der Borg-Daten hinein. Bis dahin ist Nextcloud als Backlog-Item dokumentiert.
- Komodo-Mongo-Daten-Restore am 2026-06-03 erfolgreich: 86904 Dokumente (inkl. 32 Stacks), Report `/mnt/user/backups/restore-reports/komodo-mongo-restore-2026-06-03.md`
- naechste grosse Kandidaten sind Mailarchiver und Mealie; Nextcloud bleibt blockiert (shfs-chmod)
Vor dem ersten echten Testlauf je neuem Dienst muessen Zielpfade, Quellpfade und Bereinigungsschritte bewusst freigegeben werden. ## Status je Dienst
Einziger Status-Ort ist die **Reifegrad-Tabelle** in `docs/RESTORE_MATRIX.md`
(letzter Test, Typ, naechster Lauf). Hier nur Besonderheiten:
- **Nextcloud:** Test am 2026-06-03 erfolgreich, aber mit Unraid-shfs-Eigenheit: Nextcloud fuehrt `chmod()` unter `/var/www/html` aus, was auf FUSE/shfs scheitert. Das Skript patcht `check_data_directory_permissions: false` und legt den `.ncdata`-Marker an.
- **Authelia:** bewusst Config-Smoke ohne produktiven Dump-Restore (Storage-Encryption-Key-Kopplung).
- **Immich:** Foto-Dateien-Restore ist bewusst nicht Teil des Smokes (separater DR-Drill); Test-Postgres nutzt das produktive VectorChord-Image.
- **Home Assistant:** nutzt das neueste HA-native Backup-Artefakt und eine Kopie der Mosquitto-Appdata; Testcontainer laufen nur auf localhost-Ports, ohne Traefik/Public Route.
- **Unraid-Flash / Tailscale:** noch ohne vollstaendigen Erstlauf - `unraid-flash-runbook.md`, `tailscale-runbook.md`; offene Schritte in `docs/MASTER_TODO.md`.
## Naechste Ausbaustufen
1. Hermes-Zusammenfassung ueber vorhandene Reports (geparkt mit Hermes)
2. Report-Rotation: Reports werden dauerhaft aufbewahrt; bei wachsender Anzahl jaehrlich nach `_archive/YYYY/` verschieben. Der Frische-Check warnt ab `MAX_REPORT_AGE_DAYS=45`, loescht aber nie automatisch.
-89
View File
@@ -1,89 +0,0 @@
# Authelia Restore Test Plan
## Ziel
Nachweisen, dass die Authelia-Konfiguration aus dem produktiven Borg-Archiv in einer isolierten Testumgebung wieder lauffaehig ist und der HTTP-Health-Endpunkt antwortet, ohne dass dabei produktive Secrets, produktives Postgres oder produktiver SMTP-Versand beruehrt werden.
Bewusst **nicht** Teil dieses Tests:
- Restore mit produktiven Authelia-Secrets. Der Test nutzt ausschliesslich Wegwerf-Werte fuer `AUTHELIA_SESSION_SECRET`, `AUTHELIA_STORAGE_ENCRYPTION_KEY` und `AUTHELIA_STORAGE_POSTGRES_PASSWORD`. SMTP- und Legacy-JWT-Env-Werte werden bewusst nicht gesetzt, damit Authelia keinen `notifier.smtp`-Block oder deprecated `jwt_secret` aus Env erzeugt.
- SMTP-Realanruf an GMX. Die minimale Test-Konfiguration setzt nur den Filesystem-Notifier.
- Forward-Auth gegen Traefik. Test laeuft nur auf `127.0.0.1:19091`, keine Traefik-Route.
- WebAuthn-/Duo-/OIDC-Identity-Provider-Endpunkte. Smoke prueft `/api/health`.
- **pg_restore des produktiven `postgresql17-authelia.dump`**. Authelia verschluesselt Storage-Werte mit `AUTHELIA_STORAGE_ENCRYPTION_KEY`. Ein Restore mit produktiven Daten in eine Test-Instanz mit Wegwerf-Key schlaegt im Startup-Check **by design** fehl ("the configured encryption key does not appear to be valid for this database"). Frische des produktiven Dumps wird ueber `check-restore-freshness.sh` ueberwacht; Daten-Decrypt-Drill ist eine separate DR-Aufgabe und braucht eine eigene Sicherheits-Choreographie mit kontrollierter Schluessel-Verwendung. Beobachtet im Erstlauf 2026-06-03 (Commit-Reihe `cacf77b..8d71dfb`); seit dem 2026-06-03-Folgecommit ist der Dump-Restore explizit aus dem Smoke entfernt.
## Quelle
- Backup-Quelle: produktives Borg-Archiv (`hetzner_borg_appdata_critical`)
- fachlich relevante Pfade im Archiv:
- `local/appdata/authelia/config` (verpflichtend)
- `local/borg-dumps/latest/postgresql17-authelia.dump` (existiert ggf. im Archiv; wird vom Smoke bewusst NICHT eingespielt, siehe oben)
- produktive Secrets unter `/mnt/user/appdata/secrets/authelia_*.txt` werden **nicht** gemountet
## Test-Ziel
- Restore-Lab: `/mnt/user/backups/restore-lab/authelia`
- Testdatenpfade:
- `/mnt/user/backups/restore-lab/authelia/config` (restaurierte Originalkonfiguration + `configuration.yml.original`)
- `/mnt/user/backups/restore-lab/authelia/test-config` (Runtime-Mount mit minimaler Test-`configuration.yml`)
- `/mnt/user/backups/restore-lab/authelia/postgres` (Test-Postgres-Datadir)
- `/mnt/user/backups/restore-lab/authelia/dumps/latest/postgresql17-authelia.dump` (falls extrahiert)
- `/mnt/user/backups/restore-lab/authelia/test-config/notifier/notifications.txt` (Filesystem-Notifier-Ausgabe)
- Testcontainer:
- `restoretest-authelia` (Image-Pin wie Produktion)
- `restoretest-authelia-postgres` (postgres:18.4, gleiche Major wie shared Postgres)
- Testport: `127.0.0.1:19091:9091`
- Report-Ziel: `/mnt/user/backups/restore-reports/authelia-YYYY-MM-DD.md`
## Schutzregeln
- produktive Pfade `/mnt/user/appdata/authelia/*` werden **nicht** beschrieben
- produktive Secret-Dateien `/mnt/user/appdata/secrets/authelia_*.txt` werden **nicht** gemountet
- produktive shared PostgreSQL 18 wird **nicht** angesprochen (`test-config/configuration.yml` definiert nur Test-Postgres)
- echter SMTP-Versand wird **nicht** ausgeloest (`test-config/configuration.yml` definiert nur Filesystem-Notifier)
- produktive Domain `auth.kaleschke.info` wird **nicht** uebernommen
- Testcontainer publishen nur auf `127.0.0.1`, keine LAN-/Tailscale-Bindung
- Borg-Passphrase wird aus `/mnt/user/appdata/secrets/borg_repo_passphrase.txt` gelesen und nirgendwo geloggt
## Geplanter Ablauf
1. Restore-Lab-Pfade leer anlegen
2. `local/appdata/authelia/config` aus dem aktuellsten Borg-Archiv extrahieren
3. minimale `test-config/configuration.yml` erzeugen; restaurierte Begleitdateien wie `users_database.yml` bleiben im Runtime-Mount, produktive externe Abhaengigkeiten werden nicht uebernommen; `notifier` auf Filesystem, `ntp.disable_startup_check: true`, `storage` auf Test-Postgres
4. Test-Postgres mit `ops/restore-tests/authelia-compose.test.yml` **frisch** hochfahren (keine Daten aus Dump - siehe Encryption-Key-Begruendung oben)
5. `authelia config validate` gegen `test-config/configuration.yml` laufen lassen
6. `restoretest-authelia` starten und HTTP-Health `http://127.0.0.1:19091/api/health` pollen
7. Report unter `/mnt/user/backups/restore-reports/authelia-YYYY-MM-DD.md` schreiben
8. Testcontainer stoppen und Restore-Lab bereinigen (`--keep-data` ueberschreibt)
## Smoke-Test
Minimal erfolgreich:
- Borg-Extract der Authelia-Config gelingt
- Test-Postgres startet `healthy`
- `authelia config validate` laeuft ohne Fehler durch
- HTTP `200` auf `/api/health` innerhalb 120 s
Optional spaeter:
- vollstaendigen Auth-Flow gegen Test-User aus `users_database.yml` durchspielen
- WebAuthn-Endpunkt /api/secondfactor/webauthn pruefen
- ForwardAuth-Pfad gegen Mock-Backend testen
## Bekannte Komplikationen
| Risiko | Beschreibung | Mitigation |
|---|---|---|
| Testkonfig-Schema-Drift | Authelia erwartet nach Upgrade andere Keys in der Minimal-Konfig | bei `config validate`-Fehler Test-Block im Skript anpassen |
| SMTP-Startup-Check blockiert Start | Wenn Authelia trotz `disable_startup_check` SMTP probiert | Container-Logs lesen, ggf. Notifier-Block weiter haerten |
| NTP-Lookup im Test-Netz | Container hat keinen DNS-Resolver fuer `time.cloudflare.com` | im Smoke per `ntp.disable_startup_check: true` deaktiviert |
| Storage-Encryption-Key vs. Dump | siehe "Bewusst nicht Teil dieses Tests" - der Smoke laeuft FRISCH ohne Dump | by design - Daten-Decrypt-Drill ist separate Aufgabe |
| identity_validation Schema-Drift | Aelteres/neueres Authelia-Schema erwartet andere Keys | Validate-Config Output lesen, ggf. Test-Block anpassen |
| users_database.yml mit produktiven Hashes | Daten werden ins Restore-Lab kopiert, aber niemals gemountet auf produktive Domain | OK; Testpfad ist isoliert, kein Browser-Zugang ueber LAN |
## Status
- Skript- und Compose-Scaffold abgelegt am 2026-06-02
- Erstlauf am 2026-06-03 erfolgreich: Config aus Borg, minimale Test-Konfiguration, frisches Test-Postgres, HTTP `/api/health` `200`, Report `/mnt/user/backups/restore-reports/authelia-2026-06-03.md`
- Fuer die Rotation vorgesehen: zweiter Samstag in geraden Monaten, 07:30
-59
View File
@@ -1,59 +0,0 @@
# Gitea Restore Test Plan
## Ziel
Nachweisen, dass ein Gitea-Backup in einer isolierten Testumgebung wieder startbar ist und sowohl Web-UI als auch SSH-Port wieder verfuegbar sind.
## Quelle
- Backup-Quelle: Borg / Share-Backup
- fachlich relevanter Datenpfad: `/mnt/user/services/gitea/data`
- keine separaten Secret-Dateien dokumentiert
## Test-Ziel
- Restore-Lab: `/mnt/user/backups/restore-lab/gitea`
- Testdatenpfad: `/mnt/user/backups/restore-lab/gitea/data`
- Testcontainer: `restoretest-gitea`
- Testports:
- Web: `127.0.0.1:13000:3000`
- SSH: `127.0.0.1:12222:22`
- Report-Ziel: `/mnt/user/backups/restore-reports/gitea-YYYY-MM-DD.md`
## Schutzregeln
- produktiven Pfad `/mnt/user/services/gitea/data` nie beschreiben
- produktive Domain `git.kaleschke.info` nicht fuer die Testinstanz uebernehmen
- produktiven SSH-Port `222` nicht fuer die Testinstanz uebernehmen
- keine Traefik-Labels fuer die Testinstanz
- Testcontainer nur gegen Restore-Lab-Daten starten
## Geplanter Ablauf
1. Restore-Ziel unter `/mnt/user/backups/restore-lab/gitea` vorbereiten
2. Gitea-Daten aus Backup in `restore-lab/gitea/data` wiederherstellen
3. Testinstanz mit `ops/restore-tests/gitea-compose.test.yml` starten
4. lokalen Smoke-Test gegen `http://127.0.0.1:13000` und `127.0.0.1:12222` ausfuehren
5. Report unter `/mnt/user/backups/restore-reports/` schreiben
6. Testcontainer stoppen und Testumgebung bereinigen oder bewusst stehen lassen
## Smoke-Test
Minimal erfolgreich:
- Container startet
- Web-UI antwortet
- mindestens ein bestehendes Repository-Verzeichnis ist im Restore-Lab sichtbar
- SSH-Port reagiert auf Verbindungsaufbau
Optional spaeter:
- Login-Seite gezielt pruefen
- SQLite-Datei `gitea.db` oder Nachfolger explizit bestaetigen
- `gitea doctor` oder interner Healthcheck als Zusatz
## Noch offen vor dem ersten echten Lauf
- exakter Borg-Restore-Befehl bzw. Restore-Quelle auf dem Host
- Bereinigungsstrategie fuer alte Restore-Lab-Daten
- ob Reports spaeter zusaetzlich per `ntfy` referenziert werden
@@ -0,0 +1,29 @@
services:
restoretest-ha-mosquitto:
image: eclipse-mosquitto:2.0.22@sha256:914f529386804c8278a4e581526b9be5e1604df44b30daabc70aa97dcefe5268
container_name: restoretest-ha-mosquitto
restart: "no"
volumes:
- ${RESTORE_ROOT:-/mnt/user/backups/restore-lab/homeassistant}/mosquitto/config/mosquitto.conf:/mosquitto/config/mosquitto.conf:ro
- ${RESTORE_ROOT:-/mnt/user/backups/restore-lab/homeassistant}/mosquitto/appdata/config:/mosquitto/external_config:ro
- ${RESTORE_ROOT:-/mnt/user/backups/restore-lab/homeassistant}/mosquitto/appdata/data:/mosquitto/data
- ${RESTORE_ROOT:-/mnt/user/backups/restore-lab/homeassistant}/mosquitto/appdata/log:/mosquitto/log
ports:
- "127.0.0.1:11883:1883"
security_opt:
- no-new-privileges:true
restoretest-homeassistant:
image: ghcr.io/home-assistant/home-assistant:2026.6.1@sha256:59aa8824955c9db491b75d2eebe42bd68494f80c2ec69ec0d66d9dae37d37514
container_name: restoretest-homeassistant
restart: "no"
depends_on:
- restoretest-ha-mosquitto
environment:
TZ: Europe/Berlin
volumes:
- ${RESTORE_ROOT:-/mnt/user/backups/restore-lab/homeassistant}/homeassistant/config:/config
ports:
- "127.0.0.1:18123:8123"
security_opt:
- no-new-privileges:true
+236
View File
@@ -0,0 +1,236 @@
#!/bin/bash
set -euo pipefail
# Home Assistant + Mosquitto Restore Smoke Test
#
# Scope:
# - Restore aus dem neuesten HA-nativen Backup-Artefakt
# - Kopie der Mosquitto-Appdata in ein isoliertes Restore-Lab
# - Kopie des Fachrepo-Clones zur Lesbarkeits-/Git-Status-Pruefung
# - Start isolierter Testcontainer auf localhost-Ports, ohne Traefik/Public Route
# - HA HTTP/API-Smoke und MQTT Publish/Subscribe + retained Topic nach Restart
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/common.sh"
WHATIF=0
KEEP_DATA=0
for arg in "$@"; do
case "$arg" in
--what-if) WHATIF=1 ;;
--keep-data) KEEP_DATA=1 ;;
*) echo "Unknown argument: $arg" >&2; exit 1 ;;
esac
done
RESTORE_ROOT="/mnt/user/backups/restore-lab/homeassistant"
REPORT_ROOT="/mnt/user/backups/restore-reports"
REPORT_FILE="$REPORT_ROOT/homeassistant-$(date +%F).md"
COMPOSE_FILE="$SCRIPT_DIR/homeassistant-compose.test.yml"
HA_BACKUP_DIR="/mnt/user/appdata/homeassistant/backups"
MOSQUITTO_APPDATA="/mnt/user/appdata/mosquitto"
MOSQUITTO_REPO_CONF="/mnt/user/services/homelab-infra/smart-home/mosquitto/config/mosquitto.conf"
FACHREPO_SOURCE="/mnt/user/services/smart-home-kalli"
HA_TOKEN_FILE="/mnt/user/appdata/secrets/ha_token_codex"
if [ "$WHATIF" -eq 1 ]; then
cat <<EOF
Home Assistant restore test
Mode: WhatIf
RestoreRoot: $RESTORE_ROOT
HA backup source: newest *.tar under $HA_BACKUP_DIR
Mosquitto source: $MOSQUITTO_APPDATA
Fachrepo source: $FACHREPO_SOURCE
Test endpoints: HA http://127.0.0.1:18123, MQTT 127.0.0.1:11883
Scope: HA backup extract + isolated HA boot + API token smoke + MQTT auth/retained smoke
EOF
exit 0
fi
require_cmd docker
require_cmd tar
require_cmd curl
require_path "$COMPOSE_FILE"
require_path "$HA_BACKUP_DIR"
require_path "$MOSQUITTO_APPDATA/config/passwordfile"
require_path "$MOSQUITTO_APPDATA/config/aclfile"
require_path "$MOSQUITTO_APPDATA/data"
require_path "$MOSQUITTO_REPO_CONF"
require_path "$FACHREPO_SOURCE"
require_path "$HA_TOKEN_FILE"
RESTORE_SUCCESS=0
cleanup() {
RESTORE_ROOT="$RESTORE_ROOT" cleanup_compose "$COMPOSE_FILE"
if [ "$RESTORE_SUCCESS" -ne 1 ]; then
preserve_on_failure "homeassistant" "$RESTORE_ROOT"
return
fi
if [ "$KEEP_DATA" -ne 1 ]; then
rm -rf "$RESTORE_ROOT"
fi
}
trap cleanup EXIT
latest_backup="$(find "$HA_BACKUP_DIR" -maxdepth 1 -type f -name '*.tar' -printf '%T@ %p\n' | sort -nr | awk 'NR==1 {print substr($0, index($0,$2))}')"
if [ -z "$latest_backup" ] || [ ! -f "$latest_backup" ]; then
echo "No HA native backup tar found under $HA_BACKUP_DIR" >&2
exit 1
fi
rm -rf "$RESTORE_ROOT"
mkdir -p \
"$RESTORE_ROOT/ha-backup" \
"$RESTORE_ROOT/homeassistant/config" \
"$RESTORE_ROOT/mosquitto/config" \
"$RESTORE_ROOT/mosquitto/appdata/config" \
"$RESTORE_ROOT/mosquitto/appdata/data" \
"$RESTORE_ROOT/mosquitto/appdata/log" \
"$RESTORE_ROOT/fachrepo"
tar -xf "$latest_backup" -C "$RESTORE_ROOT/ha-backup"
require_path "$RESTORE_ROOT/ha-backup/backup.json"
require_path "$RESTORE_ROOT/ha-backup/homeassistant.tar.gz"
tar -xzf "$RESTORE_ROOT/ha-backup/homeassistant.tar.gz" -C "$RESTORE_ROOT/homeassistant/config" --strip-components=1 data
cp "$MOSQUITTO_REPO_CONF" "$RESTORE_ROOT/mosquitto/config/mosquitto.conf"
cp -a "$MOSQUITTO_APPDATA/config/." "$RESTORE_ROOT/mosquitto/appdata/config/"
cp -a "$MOSQUITTO_APPDATA/data/." "$RESTORE_ROOT/mosquitto/appdata/data/"
if [ -d "$MOSQUITTO_APPDATA/log" ]; then
cp -a "$MOSQUITTO_APPDATA/log/." "$RESTORE_ROOT/mosquitto/appdata/log/" || true
fi
cp -a "$FACHREPO_SOURCE/." "$RESTORE_ROOT/fachrepo/"
ha_config="$RESTORE_ROOT/homeassistant/config"
require_path "$ha_config/configuration.yaml"
require_path "$ha_config/secrets.yaml"
require_path "$ha_config/trusted_proxies.yaml"
require_path "$ha_config/.storage/onboarding"
require_path "$ha_config/.storage/auth"
fachrepo_head="$(git -C "$RESTORE_ROOT/fachrepo" log -1 --oneline)"
fachrepo_status="$(git -C "$RESTORE_ROOT/fachrepo" status --short)"
if [ -n "$fachrepo_status" ]; then
echo "Restored fachrepo clone is not clean:" >&2
echo "$fachrepo_status" >&2
exit 1
fi
backup_size="$(stat -c '%s' "$latest_backup")"
ha_file_count="$(find "$ha_config" -type f | wc -l | tr -d ' ')"
ha_bytes="$(du -sb "$ha_config" | awk '{print $1}')"
mosquitto_data_bytes="$(du -sb "$RESTORE_ROOT/mosquitto/appdata" | awk '{print $1}')"
RESTORE_ROOT="$RESTORE_ROOT" docker compose -f "$COMPOSE_FILE" down >/dev/null 2>&1 || true
RESTORE_ROOT="$RESTORE_ROOT" docker compose -f "$COMPOSE_FILE" up -d >/dev/null
mqtt_user="$(sed -n 's/^mqtt_username:[[:space:]]*//p' "$ha_config/secrets.yaml" | sed "s/^['\"]//;s/['\"]$//")"
mqtt_pass="$(sed -n 's/^mqtt_password:[[:space:]]*//p' "$ha_config/secrets.yaml" | sed "s/^['\"]//;s/['\"]$//")"
if [ -z "$mqtt_user" ] || [ -z "$mqtt_pass" ]; then
echo "Missing mqtt_username or mqtt_password in restored HA secrets.yaml" >&2
exit 1
fi
mqtt_topic="restoretest/homeassistant/smoke"
mqtt_payload="ok-$(date +%s)"
mqtt_out="$RESTORE_ROOT/mqtt-sub.out"
rm -f "$mqtt_out"
docker exec -e MQTT_USER="$mqtt_user" -e MQTT_PASS="$mqtt_pass" -e MQTT_TOPIC="$mqtt_topic" \
restoretest-ha-mosquitto sh -lc \
'mosquitto_sub -h 127.0.0.1 -p 1883 -u "$MQTT_USER" -P "$MQTT_PASS" -t "$MQTT_TOPIC" -C 1 -W 10' \
> "$mqtt_out" &
sub_pid=$!
sleep 1
docker exec -e MQTT_USER="$mqtt_user" -e MQTT_PASS="$mqtt_pass" -e MQTT_TOPIC="$mqtt_topic" -e MQTT_PAYLOAD="$mqtt_payload" \
restoretest-ha-mosquitto sh -lc \
'mosquitto_pub -h 127.0.0.1 -p 1883 -u "$MQTT_USER" -P "$MQTT_PASS" -t "$MQTT_TOPIC" -m "$MQTT_PAYLOAD"'
wait "$sub_pid"
mqtt_result="$(cat "$mqtt_out")"
if [ "$mqtt_result" != "$mqtt_payload" ]; then
echo "MQTT publish/subscribe smoke failed" >&2
exit 1
fi
retained_topic="restoretest/homeassistant/retained"
retained_payload="retained-$(date +%s)"
docker exec -e MQTT_USER="$mqtt_user" -e MQTT_PASS="$mqtt_pass" -e MQTT_TOPIC="$retained_topic" -e MQTT_PAYLOAD="$retained_payload" \
restoretest-ha-mosquitto sh -lc \
'mosquitto_pub -h 127.0.0.1 -p 1883 -u "$MQTT_USER" -P "$MQTT_PASS" -t "$MQTT_TOPIC" -m "$MQTT_PAYLOAD" -r'
docker restart restoretest-ha-mosquitto >/dev/null
sleep 3
retained_result="$(docker exec -e MQTT_USER="$mqtt_user" -e MQTT_PASS="$mqtt_pass" -e MQTT_TOPIC="$retained_topic" \
restoretest-ha-mosquitto sh -lc \
'mosquitto_sub -h 127.0.0.1 -p 1883 -u "$MQTT_USER" -P "$MQTT_PASS" -t "$MQTT_TOPIC" -C 1 -W 10' | tr -d '\r')"
if [ "$retained_result" != "$retained_payload" ]; then
echo "MQTT retained smoke failed" >&2
exit 1
fi
ha_http_status=""
ha_body="$RESTORE_ROOT/ha-http-body.html"
for _ in $(seq 1 180); do
ha_http_status="$(curl -sS -o "$ha_body" -w '%{http_code}' http://127.0.0.1:18123/ || true)"
if [ "$ha_http_status" = "200" ] && grep -qi "Home Assistant" "$ha_body"; then
break
fi
sleep 1
done
if [ "$ha_http_status" != "200" ] || ! grep -qi "Home Assistant" "$ha_body"; then
echo "HA HTTP smoke failed, status=$ha_http_status" >&2
docker logs --tail 120 restoretest-homeassistant >&2 || true
exit 1
fi
ha_api_status="$(curl -sS -o "$RESTORE_ROOT/ha-api.json" -w '%{http_code}' \
-H "Authorization: Bearer $(cat "$HA_TOKEN_FILE")" \
-H 'Content-Type: application/json' \
http://127.0.0.1:18123/api/ || true)"
if [ "$ha_api_status" != "200" ]; then
echo "HA API token smoke failed, status=$ha_api_status" >&2
exit 1
fi
RESTORE_ROOT="$RESTORE_ROOT" docker compose -f "$COMPOSE_FILE" exec -T restoretest-homeassistant \
python -m homeassistant --script check_config --config /config >/tmp/restoretest-ha-check-config.out
write_report "$REPORT_FILE" <<EOF
# Home Assistant Restore Test Report - $(date +%F)
- Service: \`homeassistant\` + \`smarthome-mosquitto\`
- HA backup source: \`$latest_backup\`
- Restore root: \`$RESTORE_ROOT\`
- Test containers:
- \`restoretest-homeassistant\`
- \`restoretest-ha-mosquitto\`
- Test endpoints:
- HA: \`http://127.0.0.1:18123\`
- MQTT: \`127.0.0.1:11883\`
- Result: \`SUCCESS\`
## Checks
- HA-native backup tar readable: \`ok\`
- HA inner archive restored: \`ok\`
- HA backup size bytes: \`$backup_size\`
- Restored HA file count: \`$ha_file_count\`
- Restored HA bytes: \`$ha_bytes\`
- Restored Mosquitto appdata bytes: \`$mosquitto_data_bytes\`
- Fachrepo clone clean: \`ok\`
- Fachrepo HEAD: \`$fachrepo_head\`
- HA HTTP status: \`$ha_http_status\`
- HA API token smoke: \`$ha_api_status\`
- HA check_config: \`ok\`
- MQTT publish/subscribe with restored credentials: \`ok\`
- MQTT retained topic after broker restart: \`ok\`
## Notes
- Productive \`homeassistant\` and \`smarthome-mosquitto\` containers were not used.
- Test ran without Traefik and without the productive domain.
- Test ports were bound to localhost only.
- Token and MQTT password values were used for smoke tests but not printed.
- Test data was cleaned after success: \`$([ "$KEEP_DATA" -eq 1 ] && echo no || echo yes)\`
EOF
RESTORE_SUCCESS=1
echo "Home Assistant restore test ok -> $REPORT_FILE"
-89
View File
@@ -1,89 +0,0 @@
# Immich Restore Test Plan
## Ziel
Nachweisen, dass `immich.dump` aus dem produktiven Borg-Archiv in einer isolierten Testumgebung wieder einspielbar ist und Immich-Server damit anlaufen, einloggen und Asset-Metadaten anzeigen kann.
Bewusst **nicht** Teil dieses Tests:
- Wiederherstellung produktiver Foto-Dateien aus `/mnt/user/photos/immich` und `/mnt/user/photos/family_archive`. Der Smoke-Test bleibt DB-/UI-zentriert.
- Machine-Learning-Container. Spart Image-Pull-Zeit und Resource-Last; ML-Features sind im Smoke-Test nicht erforderlich.
- Echte Browser-Login-Sequenz. Smoke-Test prueft nur, dass die Login-Seite ausgeliefert wird und die DB-Tabellen `asset` und `"user"` lesbar sind.
## Quelle
- Backup-Quelle: produktives Borg-Archiv (`hetzner_borg_appdata_critical` oder lokales Mirror)
- fachlich relevanter Dump im Archiv:
- `local/borg-dumps/latest/immich.dump`
- Erzeuger: `ops/borg-ui/scripts/pre-backup-dumps.sh`, Funktion `dump_pg_db immich_postgres ... immich immich` mit `pg_dump -Fc`
- produktive Foto-Pfade werden im Smoke-Test bewusst **nicht** angefasst
## Test-Ziel
- Restore-Lab: `/mnt/user/backups/restore-lab/immich`
- Testdatenpfade:
- `/mnt/user/backups/restore-lab/immich/postgres` (Test-Postgres-Datadir)
- `/mnt/user/backups/restore-lab/immich/upload` (leeres Upload-Volume, Immich-Server braucht den Pfad nur als Mountpoint)
- `/mnt/user/backups/restore-lab/immich/dumps/latest/immich.dump` (extrahierter Dump)
- Testcontainer:
- `restoretest-immich-server`
- `restoretest-immich-postgres` (`ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0` - identisch zur Produktion, weil VectorChord-Backups ein Image mit VectorChord brauchen)
- `restoretest-immich-redis` (`redis:8.8.0-alpine`, rebuildbar)
- Testport Web: `127.0.0.1:12283:2283`
- Report-Ziel: `/mnt/user/backups/restore-reports/immich-YYYY-MM-DD.md`
## Schutzregeln
- produktive Pfade `/mnt/user/photos/immich` und `/mnt/user/photos/family_archive` werden **nicht** in den Test-Container gemountet
- produktive Domain `immich.kaleschke.info` wird **nicht** uebernommen
- keine Traefik-Labels fuer die Testinstanz
- keine produktive `immich_postgres`-/`immich_redis`-Instanz fuer den Test verwenden
- ML-Container bleibt weg
- Testcontainer publishen nur auf `127.0.0.1`, nicht auf LAN- oder Tailscale-Interface
- Borg-Passphrase wird aus `/mnt/user/appdata/secrets/borg_repo_passphrase.txt` gelesen und niemals in Logs, Reports oder Doku geschrieben
## Geplanter Ablauf
1. Restore-Ziel unter `/mnt/user/backups/restore-lab/immich` vorbereiten (postgres, upload, dumps/latest)
2. `local/borg-dumps/latest/immich.dump` aus dem aktuellsten Borg-Archiv extrahieren
3. Test-Postgres (Immich-Postgres mit VectorChord) und Test-Redis mit `ops/restore-tests/immich-compose.test.yml` starten
4. `immich.dump` in Test-Postgres importieren (`pg_restore -Fc --clean --if-exists --no-owner --no-privileges`)
5. Testinstanz `restoretest-immich-server` starten
6. lokalen Smoke-Test gegen `http://127.0.0.1:12283` ausfuehren und Asset/User-Count aus DB lesen
7. Report unter `/mnt/user/backups/restore-reports/immich-YYYY-MM-DD.md` schreiben
8. Testcontainer stoppen und Restore-Lab bereinigen
## Smoke-Test
Minimal erfolgreich:
- Test-Postgres startet `healthy`
- `pg_restore -Fc` laeuft ohne Fehler durch
- Immich-Server liefert HTTP `200`, `302` oder `303` auf `/`
- Response enthaelt mindestens einen der Marker `Immich`, `Login`, `Signin`
- `select count(*) from asset;` und `select count(*) from "user";` sind lesbar
Optional spaeter:
- Echte Login-Form via API ansprechen
- VectorChord-/pgvector-Extensions explizit per `\dx` pruefen
- Test mit gemountetem **read-only** Foto-Sample-Pfad und Thumbnail-Rendering
- Test inkl. ML-Container, sobald genug Test-Ressourcen verfuegbar
## Bekannte Komplikationen
| Risiko | Beschreibung | Mitigation |
|---|---|---|
| Dump-Groesse unbekannt | `pg_dump -Fc` der Immich-DB kann je nach Asset-/Face-Tabellen mehrere GB sein | Erster Lauf bewusst mit `--what-if`, anschliessend Operator-Test mit Zeitmessung |
| `pg_restore`-Dauer unbekannt | Index-/Constraint-Aufbau und VectorChord-Index-Build koennen lange dauern | Test-Postgres mit Health-Polling startet; Lauf nicht abbrechen ohne `pg_restore`-Exit |
| VectorChord-/pgvector-Extension-Mismatch | Wenn das Test-Postgres-Image nicht zu Produktion passt, kann der Restore oder Immich-Start fehlschlagen | Compose pinnt denselben Digest wie `apps/immich/docker-compose.yml` |
| Immich-Server-Migrations beim Start | Immich fuehrt beim ersten Start DB-Migrations aus; das kann nach Restore noch laufen, bevor Web-UI antwortet | Smoke-Test pollt HTTP bis zu 120 s, bevor er als Fehler markiert |
| Asset-Files fehlen | Der Test mountet kein Foto-Volume; Immich zeigt "missing" auf Asset-Detail-Seiten | Smoke-Test prueft nur Login-Page und DB-Counts, nicht Asset-Rendering |
| ML-Endpoint unreachable | Immich-Server kann ML-Endpoint nicht erreichen | `IMMICH_MACHINE_LEARNING_URL` zeigt bewusst auf einen nicht erreichbaren Hostnamen; Login bleibt funktional, ML-Features bleiben deaktiviert |
## Noch offen vor dem ersten echten Lauf
- Dump-Groesse `immich.dump` auf dem Host bestimmen (`ls -lh /mnt/user/backups/borg/dumps/latest/immich.dump`)
- Erwartete Restore-Dauer durch ersten Lauf mit `--keep-data` messen
- Pruefen, ob die Immich-Tabellen `assets`/`users` im aktuellen Schema noch existieren (Schema-Drift bei Major-Update wuerde die Asset-Count-Query brechen, das Skript faengt das tolerant ab)
- Schedule-Eintrag in `ops/restore-tests/schedule.md`: aktuell ist Immich nur als "spaeter, eigener Sprint" gefuehrt. Erst nach erstem erfolgreichen Lauf in Schedule aufnehmen, z. B. quartalsweise.
-1
View File
@@ -34,7 +34,6 @@ Vor dem ersten Lauf muss Operator entscheiden:
- `ops/restore-tests/immich-compose.test.yml` - `ops/restore-tests/immich-compose.test.yml`
- `ops/restore-tests/immich-restore-test.sh` - `ops/restore-tests/immich-restore-test.sh`
- `ops/restore-tests/immich-restore-test.ps1` (Scaffold, kein Live-Run) - `ops/restore-tests/immich-restore-test.ps1` (Scaffold, kein Live-Run)
- `ops/restore-tests/immich-plan.md`
- `ops/restore-tests/immich-runbook.md` - `ops/restore-tests/immich-runbook.md`
## Erster Lauf - trockene Variante ## Erster Lauf - trockene Variante
@@ -1,88 +0,0 @@
# Komodo Bootstrap Trockenlauf - Plan
## Ziel
Nachweisen, dass `ops/komodo/docker-compose.yml` als Recovery-Anker fuer einen Komodo-Kaltstart tauglich ist, ohne den produktiven Komodo-Stack anzufassen.
Bewusst **nicht** Teil dieses Tests:
- Restore aus dem produktiven `komodo-mongo.archive.gz`-Dump (eigene Folgeaufgabe; dieser Test prueft nur das Compose-Bootstrap, nicht den Daten-Restore).
- docker.sock-Mount fuer die Test-Periphery (die Test-Periphery darf nie produktive Container managen).
- Traefik-Route oder Authelia-Anbindung (Test laeuft ausschliesslich auf `127.0.0.1:19120`).
## Quelle
- Bootstrap-Anker: `ops/komodo/docker-compose.yml` (Soll-Stand laut `docs/SERVICES_RECOVERY.md` Stufe A-F).
- Image-Digests: identisch zur Produktion fuer komodo-core und komodo-periphery; Mongo-Image identisch.
## Test-Ziel
- Restore-Lab: `/mnt/user/backups/restore-lab/komodo`
- Wegwerf-Pfade:
- `/mnt/user/backups/restore-lab/komodo/mongo` (Test-Mongo-Datadir)
- `/mnt/user/backups/restore-lab/komodo/core` (Repo-Cache)
- `/mnt/user/backups/restore-lab/komodo/keys` (gemeinsamer Keys-Pfad fuer Core+Periphery)
- `/mnt/user/backups/restore-lab/komodo/periphery` (Periphery-Etc)
- Testcontainer:
- `restoretest-komodo-mongo`
- `restoretest-komodo-core` (Test-Port `127.0.0.1:19120`)
- `restoretest-komodo-periphery` (ohne docker.sock)
- Compose-Project: `restoretest-komodo` (isoliert von Produktions-Project `komodo`)
- Report-Ziel: `/mnt/user/backups/restore-reports/komodo-bootstrap-YYYY-MM-DD.md`
## Schutzregeln
- produktive Datadirs `/mnt/user/appdata/komodo/{mongo,core,periphery}` werden **nicht** gemountet
- produktive Container `komodo-mongo`, `komodo-core`, `komodo-periphery` werden **nicht** gestoppt
- produktive `KOMODO_*`-Secrets werden **nicht** verwendet
- Test-Compose enthaelt nur Wegwerf-Werte fuer `KOMODO_SECRET_KEY`, `KOMODO_WEBHOOK_SECRET`, `KOMODO_JWT_SECRET`, `KOMODO_PASSKEY` und Mongo-Root-Password
- Test-Periphery laeuft ohne docker.sock-Mount und ohne `/mnt/user/services`-Mount
- Test-Port nur auf `127.0.0.1:19120`, keine LAN-/Tailscale-Bindung
## Geplanter Ablauf
1. Restore-Lab-Pfade leer anlegen
2. `docker compose config` auf dem Test-Compose validieren
3. Mongo und Core hochfahren, auf Mongo-`healthy` warten
4. HTTP-Smoke gegen `http://127.0.0.1:19120` (Login-Seite oder Auth-Redirect erwartet)
5. Periphery dazustarten, kurz beobachten
6. Mongo-`authenticated ping` mit Test-Credentials
7. Report schreiben
8. Cleanup `docker compose down -v` und Restore-Lab loeschen (ausser `--keep-data`)
## Smoke-Test
Minimal erfolgreich:
- `docker compose config` valid
- Test-Mongo erreicht `healthy`
- Mongo-Authentifizierung mit Test-Creds funktioniert (`db.adminCommand({ping:1}).ok = 1`)
- Komodo-Core HTTP `200`, `302`, `303` oder `401` (alles ist ein valider Lebenszeichen)
- Test-Periphery container state `running`
Optional spaeter:
- Periphery-Verbindung gegen Test-Core verifizieren (braucht Periphery-Konfig mit `core_url`)
- Echtes Restore aus `komodo-mongo.archive.gz`-Dump in die Test-Mongo
- Schreiben einer Wegwerf-Resource (Server/Stack) ueber die API
## Bekannte Komplikationen
| Risiko | Beschreibung | Mitigation |
|---|---|---|
| Image-Drift | Komodo-Images aktualisieren ihre Major-Tag-Digests | Compose pinnt denselben Digest wie Produktion; bei Image-Update auch Test-Compose nachziehen |
| Port-Konflikt | wenn 19120 anderweitig belegt ist | nur `127.0.0.1`-Bind; bei Konflikt Port im Compose anpassen |
| Volume-Reste | unterbrochener Lauf laesst Wegwerf-Datadir liegen | Skript loescht Restore-Lab vor jedem Lauf; `--keep-data` ueberschreibt das bewusst |
| Periphery-Erreichbarkeit | Core sucht Periphery initial nicht aktiv | Test prueft nur Periphery `State.Status=running`; voller Handshake ist optional |
## Bestaetigte Laeufe
| Datum | Mode | Ergebnis | Report |
|---|---|---|---|
| 2026-05-30 | `--what-if` | Plan-Ausgabe wie erwartet | (kein Report, nur stdout) |
| 2026-05-30 | `--keep-data` | `SUCCESS`, 5/5 Checks gruen, Core HTTP `200`, Mongo healthy in ~6 s | `/mnt/user/backups/restore-reports/komodo-bootstrap-2026-05-30.md` |
## Folgeschritte
- Quartals-Belegung: Komodo-Bootstrap passt zum DR-Sanity-Check (`ops/restore-tests/schedule.md` Q2/Q4) und kann ohne Borg-Archiv jederzeit wiederholt werden.
- Optional fuer kuenftige Laeufe: echtes Restore aus `komodo-mongo.archive.gz` in die Test-Mongo, danach Schreiben einer Wegwerf-Resource ueber die API.
-72
View File
@@ -1,72 +0,0 @@
# Paperless Restore Test Plan
## Ziel
Nachweisen, dass ein Paperless-Backup in einer isolierten Testumgebung wieder startbar ist und sowohl Dokumentenpfade als auch PostgreSQL-Dump sauber zusammenlaufen.
## Quelle
- Backup-Quelle: Borg / Share-Backup
- fachlich relevante Dateipfade:
- `/mnt/user/appdata/paperless-ngx/data`
- `/mnt/user/documents/paperless`
- `/mnt/user/documents/paperless/export`
- `/mnt/user/documents/scans_inbox`
- fachlich relevanter Dump:
- `/mnt/user/backups/borg/dumps/latest/postgresql17-paperless.dump`
## Test-Ziel
- Restore-Lab: `/mnt/user/backups/restore-lab/paperless`
- Testdatenpfade:
- `/mnt/user/backups/restore-lab/paperless/data`
- `/mnt/user/backups/restore-lab/paperless/media`
- `/mnt/user/backups/restore-lab/paperless/export`
- `/mnt/user/backups/restore-lab/paperless/consume`
- `/mnt/user/backups/restore-lab/paperless/postgres`
- Testcontainer:
- `restoretest-paperless`
- `restoretest-paperless-postgres`
- `restoretest-paperless-redis`
- Testport Web: `127.0.0.1:18120:8000`
- Report-Ziel: `/mnt/user/backups/restore-reports/paperless-YYYY-MM-DD.md`
## Schutzregeln
- produktive Pfade nie beschreiben
- produktive Domain `paperless.kaleschke.info` nicht fuer die Testinstanz uebernehmen
- keine Traefik-Labels fuer die Testinstanz
- keine produktive PostgreSQL- oder Redis-Instanz fuer den Test verwenden
- Testcontainer nur gegen Restore-Lab-Daten und isolierte Test-Backends starten
## Geplanter Ablauf
1. Restore-Ziel unter `/mnt/user/backups/restore-lab/paperless` vorbereiten
2. Paperless-Dateipfade aus Borg in das Restore-Lab wiederherstellen
3. Test-Postgres und Test-Redis mit `ops/restore-tests/paperless-compose.test.yml` starten
4. `postgresql17-paperless.dump` in Test-Postgres importieren
5. Testinstanz `restoretest-paperless` starten
6. lokalen Smoke-Test gegen `http://127.0.0.1:18120` ausfuehren
7. Report unter `/mnt/user/backups/restore-reports/` schreiben
8. Testcontainer stoppen und Testumgebung bereinigen
## Smoke-Test
Minimal erfolgreich:
- Test-Postgres startet
- Dump-Import gelingt
- Paperless-Web-UI antwortet
- mindestens ein Dokument liegt im Restore-Lab-Medienpfad
Optional spaeter:
- Login-Seite gezielt pruefen
- Dokumentanzahl aus UI oder DB querpruefen
- OCR-/Task-Worker-Status verifizieren
## Noch offen vor dem ersten echten Lauf
- exakter Borg-Restore-Befehl fuer alle vier Dateipfade
- exakter `pg_restore`-Befehl im Test-Postgres
- wie stark wir `consume` im ersten Lauf ueberhaupt brauchen
+7 -1
View File
@@ -55,6 +55,12 @@ case "$MODE" in
fi fi
exec "$SCRIPT_DIR/redis-restore-test.sh" exec "$SCRIPT_DIR/redis-restore-test.sh"
;; ;;
homeassistant)
if [ "$WHATIF" = "--what-if" ]; then
exec "$SCRIPT_DIR/homeassistant-restore-test.sh" --what-if
fi
exec "$SCRIPT_DIR/homeassistant-restore-test.sh"
;;
nextcloud) nextcloud)
if [ "$WHATIF" = "--what-if" ]; then if [ "$WHATIF" = "--what-if" ]; then
exec "$SCRIPT_DIR/nextcloud-restore-test.sh" --what-if exec "$SCRIPT_DIR/nextcloud-restore-test.sh" --what-if
@@ -98,7 +104,7 @@ case "$MODE" in
exec "$SCRIPT_DIR/shared-pg-cluster-restore-test.sh" 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|freshness-negative|vaultwarden|gitea|paperless|immich|authelia|adguard|redis|homeassistant|nextcloud|komodo-bootstrap|komodo-mongo-restore|traefik|mailarchiver|mealie|shared-pg-cluster} [--what-if]" >&2
exit 1 exit 1
;; ;;
esac esac
+34
View File
@@ -0,0 +1,34 @@
# 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
@@ -0,0 +1,45 @@
# 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
@@ -1,56 +0,0 @@
# 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
+14 -7
View File
@@ -1,11 +1,18 @@
# Windows Reinstall Helpers # Windows Reinstall Helpers
Diese Skripte sind bewusst versionierte Operator-Hilfen fuer den Windows-Neuaufsetzen-/Dual-Boot-Kontext vom Mai 2026. Typ: Runbook/Tool-Doku · Stand: 2026-06-11 · Status: aktiv (Projekt Mai 2026 abgeschlossen)
- `backup-delta-after-2026-05-07.ps1` kopiert nach einem definierten Cutoff lokale Nutzdaten in ein Backup-Ziel. Versionierte Operator-Hilfen rund um die Windows-Workstation `baerchen`.
- `repair-disk0-boot-to-new-windows.ps1` repariert EFI/Bootdateien fuer das neue Windows auf der Intel-SSD und verlangt Adminrechte. Das Neuaufsetzen-Projekt vom Mai 2026 ist abgeschlossen; die zugehoerigen
- `cleanup-dualboot-bcd.ps1` bereinigt BCD-Bootmenueeintraege und verlangt eine explizite Textbestaetigung. Plaene und Snapshots liegen in `docs/archive/2026/`.
- `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.
Die Skripte enthalten keine Secrets, arbeiten aber mit lokalen Windows-Datentraegern und duerfen nur interaktiv und mit vorheriger Sichtpruefung ausgefuehrt werden. 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.
@@ -221,7 +221,7 @@ Kopier-/Doppelbestand:
## Offene Punkte ## 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. - ~~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-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". - **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.
- Optional: `D:\11_Bilder` ReadOnly-Attribut beobachten; fuer Windows-Shell-Ordner ist das in der Praxis meist unkritisch. - 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: `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. - 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 - TPM: vorhanden, ready, enabled, activated, owned
- BitLocker-Status geprueft: - BitLocker-Status geprueft:
- `C:`, `D:`, `E:`, `G:`, `H:` sind `FullyDecrypted`, Protection `Off` - `C:`, `D:`, `E:`, `G:`, `H:` sind `FullyDecrypted`, Protection `Off`
- BitLocker wurde nicht automatisch aktiviert, weil dafuer eine bewusste Recovery-Key- und Lockout-Entscheidung noetig ist. - BitLocker wurde bewusst nicht aktiviert (Entscheidung 2026-06-06); Recovery laeuft ueber das Veeam-Image.
- OneDrive Per-Machine Standalone Update Task wurde deaktiviert. - OneDrive Per-Machine Standalone Update Task wurde deaktiviert.
- SSH-Aliases angelegt und getestet: - SSH-Aliases angelegt und getestet:
- `kallilabcore` -> `root@100.80.98.33` - `kallilabcore` -> `root@100.80.98.33`
@@ -322,4 +322,4 @@ Admin-Nachlauf 2026-06-05:
Weiter offen: Weiter offen:
- BitLocker-Entscheidung fuer mindestens `C:` und `D:` treffen. Vor Aktivierung Recovery Keys extern sichern. - Kein BitLocker-Todo mehr. Spaetere Aktivierung nur als neuer bewusster Aenderungsblock mit externer Recovery-Key-Ablage.
@@ -1,6 +1,6 @@
# Windows Image Backup Baseline - baerchen # Windows Image Backup Baseline - baerchen
Stand: 2026-06-05 Stand: 2026-06-06
Dieses Runbook beschreibt den Windows-Image-Backup-Workflow fuer den frisch Dieses Runbook beschreibt den Windows-Image-Backup-Workflow fuer den frisch
aufgesetzten Windows-11-Rechner `baerchen`. Ziel ist ein schneller Bare-Metal- aufgesetzten Windows-11-Rechner `baerchen`. Ziel ist ein schneller Bare-Metal-
@@ -22,7 +22,7 @@ Unraid-SMB-Share `backups`.
| Share-Modell | bestehender Unraid-Share `backups`, kein neuer Share | | Share-Modell | bestehender Unraid-Share `backups`, kein neuer Share |
| SMB-User | `micha` (bestehender Unraid-User mit Read/Write auf `backups`) | | SMB-User | `micha` (bestehender Unraid-User mit Read/Write auf `backups`) |
| Veeam Job | `baerchen-c-image` | | Veeam Job | `baerchen-c-image` |
| Verschluesselung | Stand erster Lauf: Veeam Storage Encryption **nicht aktiv** (`StorageEncryptionEnabled=False` im Job-Log); optional separat aktivieren und neues Full-Backup erzeugen | | Verschluesselung | Veeam Storage Encryption **bewusst nicht aktiv** (`StorageEncryptionEnabled=False` im Job-Log); neu bewerten nur bei Off-host-Auslagerung des Windows-Images |
| Recovery Media | USB-Stick `VEEAMRE` auf Laufwerk F: erstellt | | Recovery Media | USB-Stick `VEEAMRE` auf Laufwerk F: erstellt |
## Bewusste Entscheidungen ## Bewusste Entscheidungen
@@ -33,8 +33,8 @@ Unraid-SMB-Share `backups`.
- Es wurde vorerst kein dedizierter SMB-User `veeam-baerchen` angelegt, um - Es wurde vorerst kein dedizierter SMB-User `veeam-baerchen` angelegt, um
keine Unraid-Share-/User-Aenderung zu erzwingen. Der produktive Job nutzt keine Unraid-Share-/User-Aenderung zu erzwingen. Der produktive Job nutzt
den bestehenden User `micha`. den bestehenden User `micha`.
- BitLocker wurde am 2026-06-05 nicht aktiviert. TPM, Secure Boot und WinRE - BitLocker bleibt bewusst deaktiviert (Entscheidung 2026-06-06). Recovery
wurden geprueft; BitLocker bleibt ein separater Security-Schritt. laeuft ueber das Veeam-Image; kein BitLocker-Key-Management.
- Der Recovery-Stick ist Teil des Restore-Pfads und muss getrennt vom Rechner - Der Recovery-Stick ist Teil des Restore-Pfads und muss getrennt vom Rechner
aufbewahrt werden. aufbewahrt werden.
@@ -65,14 +65,13 @@ Veeam Agent -> Job `baerchen-c-image`
- Shared folder: `\\kallilabcore\backups\windows-images\baerchen` - Shared folder: `\\kallilabcore\backups\windows-images\baerchen`
- Credentials: bestehender Unraid-SMB-User `micha` - Credentials: bestehender Unraid-SMB-User `micha`
- Compression: `Optimal` - Compression: `Optimal`
- Storage encryption: Stand erster Lauf **nicht aktiv** - Storage encryption: **bewusst nicht aktiv**
- Schedule: Workstation-Schedule in Veeam; Stand 2026-06-05: taeglich nachts - Schedule: Workstation-Schedule in Veeam; Stand 2026-06-05: taeglich nachts
eingerichtet. eingerichtet.
Wenn Veeam Storage Encryption spaeter aktiviert wird, ist das Wenn Veeam Storage Encryption spaeter doch aktiviert wird, ist das ein neuer
Veeam-Job-Passwort nicht aus dem Repo wiederherstellbar. Es muss dann in bewusster Aenderungsblock: Passwort in Vaultwarden anlegen, Job umstellen,
Vaultwarden als eigener Eintrag/Secure Note liegen und vor dem ersten neues Full-Backup erzeugen und Recovery-Test wiederholen.
verschluesselten Full-Backup getestet werden.
## Secrets und Ablageorte ## Secrets und Ablageorte
@@ -80,9 +79,9 @@ Keine Secret-Werte in dieses Repository schreiben.
| Secret | Ablage | | Secret | Ablage |
|---|---| |---|---|
| Veeam Job Encryption Password | nur noetig, falls Veeam Storage Encryption aktiviert wird; Ziel: Vaultwarden Secure Note `Veeam baerchen backup encryption password` | | Veeam Job Encryption Password | nicht noetig, solange Veeam Storage Encryption bewusst deaktiviert bleibt; Ziel bei spaeterer Aktivierung: Vaultwarden Secure Note `Veeam baerchen backup encryption password` |
| SMB Credential fuer Backup-Ziel | bestehender Unraid/Vaultwarden-Eintrag fuer User `micha` | | SMB Credential fuer Backup-Ziel | bestehender Unraid/Vaultwarden-Eintrag fuer User `micha` |
| BitLocker Recovery Key | noch nicht aktiv; Ziel bei Aktivierung: `D:\30_Finanzen\BitLocker-RecoveryKey-baerchen-<DATUM>.txt`, Vaultwarden Secure Note, physischer Ausdruck | | BitLocker Recovery Key | nicht noetig, weil BitLocker bewusst deaktiviert ist; Ziel bei spaeterer Aktivierung: `D:\30_Finanzen\BitLocker-RecoveryKey-baerchen-<DATUM>.txt`, Vaultwarden Secure Note, physischer Ausdruck |
## Recovery Media ## Recovery Media
@@ -173,12 +172,10 @@ den Job erfolgreich schreibt, liegt das meist an getrennten Credentials:
Veeam nutzt gespeicherte Job-Credentials, waehrend die interaktive Windows- Veeam nutzt gespeicherte Job-Credentials, waehrend die interaktive Windows-
Sitzung zusaetzlich per `net use` authentifiziert werden muss. Sitzung zusaetzlich per `net use` authentifiziert werden muss.
## Offene Punkte ## Verbleibende Optionen
- Entscheiden, ob Veeam Storage Encryption nachtraeglich aktiviert werden soll.
Wenn ja: Passwort in Vaultwarden anlegen, Job umstellen und ein neues Full-
Backup erzeugen.
- Optional: BitLocker C: separat aktivieren und Recovery-Key an den drei
vorgesehenen Orten sichern.
- Optional: spaeter dedizierten SMB-User `veeam-baerchen` anlegen, falls die - Optional: spaeter dedizierten SMB-User `veeam-baerchen` anlegen, falls die
Unraid-User-/Share-Policy wieder angefasst wird. Unraid-User-/Share-Policy wieder angefasst wird.
- Veeam Storage Encryption und BitLocker sind keine offenen Entscheidungen mehr;
beide bleiben bewusst deaktiviert und werden nur bei neuem Risiko-/Ablageprofil
erneut bewertet.
@@ -0,0 +1,237 @@
#!/usr/bin/env bash
set -euo pipefail
# Komodo-Stack-Hygiene-Check.
#
# Prueft, dass jeder Komodo-Stack sauber gegen das Git-Repo konfiguriert ist,
# und dass jeder Compose-File im Repo einen passenden Komodo-Stack hat.
# Findet die Klasse von Fehlern, die `immich_new` (2026-06-12) durchgelassen
# hat: Stack RUNNING, aber kein Repo / kein Account / project_missing.
REPO_ROOT="${REPO_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
OUTPUT_PATH="${OUTPUT_PATH:-/mnt/user/services/posture-check/komodo-stack-hygiene-last.json}"
NTFY_SCRIPT="${NTFY_SCRIPT:-$REPO_ROOT/ops/restore-tests/send-ntfy.sh}"
NTFY_TOPIC="${NTFY_TOPIC:-homelab-alerts}"
SEND_NTFY="${SEND_NTFY:-1}"
KOMODO_ENV_FILE="${KOMODO_ENV_FILE:-/mnt/user/appdata/secrets/codex_komodo_api.env}"
KOMODO_CONTAINER="${KOMODO_CONTAINER:-komodo-core}"
# Komma-separierte Allowlist fuer bewusst inline-managed Stacks.
# Quelle: memory/komodo-stack-inline-managed.md, CLAUDE.md.
INLINE_ALLOWLIST="${INLINE_ALLOWLIST:-komodo,grafana}"
# Compose-Files unter diesen Pfaden zaehlen NICHT als erwartete Stacks
# (Beispiele, Archive, Submodule).
COMPOSE_EXCLUDE_PATTERN="${COMPOSE_EXCLUDE_PATTERN:-/archive/|/examples/|/.git/}"
# Compose-Dir-Namen, die bewusst NICHT als Komodo-Stack laufen sollen
# (Work-in-progress, Build-/Dev-Compose, manuell deployed). Komma-separiert.
EXPECTED_NOT_IN_KOMODO="${EXPECTED_NOT_IN_KOMODO:-hermes-agent}"
TMP_DIR="${TMP_DIR:-/tmp/kallilab-komodo-stack-hygiene}"
mkdir -p "$TMP_DIR"
RESULTS_FILE="$TMP_DIR/results.$$"
STACKS_FILE="$TMP_DIR/stacks.$$.json"
: > "$RESULTS_FILE"
trap 'rm -f "$RESULTS_FILE" "$STACKS_FILE"' EXIT
json_escape() {
sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/\t/\\t/g'
}
add_result() {
printf '%s\t%s\t%s\n' "$1" "$2" "$3" >> "$RESULTS_FILE"
}
is_inline_allowed() {
local name="$1"
local IFS=,
for entry in $INLINE_ALLOWLIST; do
[ "$name" = "$entry" ] && return 0
done
return 1
}
is_expected_not_in_komodo() {
local name="$1"
local IFS=,
for entry in $EXPECTED_NOT_IN_KOMODO; do
[ "$name" = "$entry" ] && return 0
done
return 1
}
# True drift: do files inside this stack's compose-dir actually differ
# between deployed_hash and latest_hash? Komodo's deployed_hash bumps only
# on redeploy, while latest_hash tracks master HEAD - that produces a noisy
# "Pending Update" even when the stack itself wasn't touched.
stack_files_changed() {
local name="$1" deployed="$2" latest="$3"
local dir
# Locate the stack's compose dir (case-insensitive, same as Mode 3).
dir="$(find "$REPO_ROOT" -type d -iname "$name" -not -path "*/.git/*" 2>/dev/null | head -1)"
[ -n "$dir" ] || return 0 # No dir -> can't tell, treat as drift to be safe
( cd "$REPO_ROOT" && git rev-parse --verify --quiet "$deployed" >/dev/null ) || return 0
( cd "$REPO_ROOT" && git rev-parse --verify --quiet "$latest" >/dev/null ) || return 0
local rel="${dir#$REPO_ROOT/}"
if ( cd "$REPO_ROOT" && git diff --quiet "$deployed".."$latest" -- "$rel" ); then
return 1 # no change
fi
return 0 # real change
}
# Komodo-API-Credentials laden und Stack-Liste holen.
if [ ! -r "$KOMODO_ENV_FILE" ]; then
add_result "warning" "komodo-api" "Komodo env file not readable: $KOMODO_ENV_FILE"
else
set -a
# shellcheck disable=SC1090
. "$KOMODO_ENV_FILE"
set +a
if ! docker exec \
-e KOMODO_CLI_HOST \
-e KOMODO_CLI_KEY \
-e KOMODO_CLI_SECRET \
"$KOMODO_CONTAINER" km list -a stacks -f json > "$STACKS_FILE" 2>/dev/null; then
add_result "warning" "komodo-api" "km list stacks failed (container=$KOMODO_CONTAINER)"
: > "$STACKS_FILE"
fi
fi
# Per-Stack-Checks. Trenner: "|" statt Tab, weil IFS=Tab leere Felder kollabiert
# (Tab ist Whitespace in IFS). "|" kommt in Stack-Namen/Repos/Hashes nicht vor.
if [ -s "$STACKS_FILE" ]; then
while IFS='|' read -r name repo project_missing missing_files state deployed_hash latest_hash files_on_host file_contents; do
[ -n "$name" ] || continue
if is_inline_allowed "$name"; then
add_result "ok" "$name" "Inline-managed (allowlisted), skipping repo checks"
continue
fi
# Failure-Mode 1: Stack hat keine Git-Quelle (immich_new-Symptom).
if [ "$repo" = "-" ] && [ "$files_on_host" != "True" ] && [ "$file_contents" != "True" ]; then
add_result "critical" "$name" "Stack has no repo configured and is not inline-allowed"
continue
fi
# Failure-Mode 2: Komodo meldet Project Missing.
if [ "$project_missing" = "True" ]; then
add_result "critical" "$name" "project_missing=true (missing_files=$missing_files)"
continue
fi
# Failure-Mode 3: Stack-Name passt zu keinem Compose-File im Repo.
# Case-insensitive (Compose-Dir kann GroSs/klein abweichen, z.B. Adguard).
match_found=""
while IFS= read -r dir; do
[ -n "$dir" ] || continue
if [ -f "$dir/docker-compose.yml" ] \
|| [ -f "$dir/docker-compose.yaml" ] \
|| [ -f "$dir/compose.yml" ] \
|| [ -f "$dir/compose.yaml" ]; then
match_found=1
break
fi
done < <(find "$REPO_ROOT" -type d -iname "$name" -not -path "*/.git/*" 2>/dev/null)
if [ -z "$match_found" ]; then
# Verwaiste Stacks wie das frueher gesehene `immich_new`: Komodo kennt
# ihn, aber im Repo gibt's keinen Compose-Pfad.
add_result "warning" "$name" "Stack name does not match any compose directory in repo"
fi
# Failure-Mode 4: Deployed-Hash hinkt latest hinterher UND der Stack-Dir
# hat tatsaechlich File-Aenderungen dazwischen. Reine Komodo-Hash-Bewegung
# ohne Stack-Inhalt aendert nichts und ist kein echter Drift.
# "-" = unbekannt (z.B. gitea self-host edge case), nicht als Drift werten.
if [ "$deployed_hash" != "-" ] && [ "$latest_hash" != "-" ] \
&& [ "$deployed_hash" != "$latest_hash" ] \
&& stack_files_changed "$name" "$deployed_hash" "$latest_hash"; then
add_result "warning" "$name" "deployed_hash $deployed_hash != latest_hash $latest_hash (stack files changed)"
fi
# Failure-Mode 5: Stack ist down.
if [ "$state" = "down" ] || [ "$state" = "unknown" ]; then
add_result "warning" "$name" "Stack state is $state"
fi
add_result "ok" "$name" "Stack hygiene OK (state=$state, hash=$deployed_hash)"
done < <(jq -r '.[] | [
.name // "-",
(.info.repo // "-"),
(.info.project_missing | if . then "True" else "False" end),
(((.info.missing_files // []) | join(",")) | if . == "" then "-" else . end),
(.info.state // "-"),
(.info.deployed_hash // "-"),
(.info.latest_hash // "-"),
(.info.files_on_host | if . then "True" else "False" end),
(.info.file_contents | if . then "True" else "False" end)
] | join("|")' "$STACKS_FILE")
fi
# Failure-Mode 6: Compose-File im Repo, aber kein Komodo-Stack mit gleichem Namen.
if [ -s "$STACKS_FILE" ]; then
known_names="$(jq -r '.[].name' "$STACKS_FILE")"
while IFS= read -r -d '' compose; do
rel="${compose#$REPO_ROOT/}"
if printf '%s' "$rel" | grep -Eq "$COMPOSE_EXCLUDE_PATTERN"; then
continue
fi
dir_name="$(basename "$(dirname "$compose")")"
if is_inline_allowed "$dir_name"; then
continue
fi
if is_expected_not_in_komodo "$dir_name"; then
continue
fi
# Case-insensitive, weil z.B. host-services/Adguard <-> Komodo-Stack adguard
# legitim als gematched gilt.
if ! printf '%s\n' "$known_names" | grep -Fixq "$dir_name"; then
add_result "warning" "$dir_name" "Compose file $rel has no matching Komodo stack"
fi
done < <(find "$REPO_ROOT" -path "$REPO_ROOT/.git" -prune -o -type f \
\( -name docker-compose.yml -o -name docker-compose.yaml \
-o -name compose.yml -o -name compose.yaml \) -print0)
fi
timestamp="$(date -Iseconds)"
critical_count="$(awk -F '\t' '$1 == "critical" { c++ } END { print c + 0 }' "$RESULTS_FILE")"
warning_count="$(awk -F '\t' '$1 == "warning" { c++ } END { print c + 0 }' "$RESULTS_FILE")"
status="ok"
[ "$warning_count" -gt 0 ] && status="warning"
[ "$critical_count" -gt 0 ] && status="critical"
mkdir -p "$(dirname "$OUTPUT_PATH")"
{
printf '{\n'
printf ' "timestamp": "%s",\n' "$(printf '%s' "$timestamp" | json_escape)"
printf ' "status": "%s",\n' "$status"
printf ' "critical_count": %s,\n' "$critical_count"
printf ' "warning_count": %s,\n' "$warning_count"
printf ' "checks": [\n'
first=1
while IFS=$'\t' read -r severity name message; do
if [ "$first" -eq 0 ]; then printf ',\n'; fi
first=0
printf ' {"severity":"%s","name":"%s","message":"%s"}' \
"$(printf '%s' "$severity" | json_escape)" \
"$(printf '%s' "$name" | json_escape)" \
"$(printf '%s' "$message" | json_escape)"
done < "$RESULTS_FILE"
printf '\n ]\n}\n'
} > "$OUTPUT_PATH.tmp"
mv "$OUTPUT_PATH.tmp" "$OUTPUT_PATH"
cat "$OUTPUT_PATH"
if [ "$critical_count" -gt 0 ] || [ "$warning_count" -gt 0 ]; then
if [ "$SEND_NTFY" = "1" ] && [ -x "$NTFY_SCRIPT" ]; then
priority="default"
[ "$warning_count" -gt 0 ] && priority="high"
[ "$critical_count" -gt 0 ] && priority="urgent"
"$NTFY_SCRIPT" "$NTFY_TOPIC" \
"Komodo stack hygiene: $critical_count critical, $warning_count warning" \
"See $OUTPUT_PATH" "$priority" || true
fi
[ "$critical_count" -gt 0 ] && exit 2
exit 1
fi
+58
View File
@@ -0,0 +1,58 @@
# Smart Home Runtime Stack
Runtime-Zustand fuer Home Assistant auf Kallilabcore. Dieser Ordner gehoert zu
`homelab-infra`, weil Komodo den Stack deployt und Renovate die Images pflegt.
## Dienste
- `homeassistant`: Home Assistant Container, erreichbar ueber Traefik unter
`https://home.kaleschke.info`
- `smarthome-mosquitto`: interner MQTT-Broker fuer Home Assistant, spaeter
Zigbee2MQTT und ESPHome
## Abhaengigkeiten
- `frontend_net` existiert bereits und wird von Traefik genutzt.
- `smarthome_net` wird durch diesen Stack angelegt und ist `internal: true`.
- Das Fachrepo `smart-home-kalli` muss auf dem Unraid-Host unter
`/mnt/user/services/smart-home-kalli` liegen. Nur ausgewählte YAML-Dateien
werden read-only nach `/config` gemountet; `.storage` bleibt in
`/mnt/user/appdata/homeassistant`.
- Vor dem ersten Start muessen diese Dateien hostseitig angelegt werden:
- `/mnt/user/appdata/homeassistant/secrets.yaml`
- `/mnt/user/appdata/homeassistant/trusted_proxies.yaml`
- `/mnt/user/appdata/mosquitto/config/passwordfile`
- `/mnt/user/appdata/mosquitto/config/aclfile`
Das detaillierte Host-Bootstrap-Runbook liegt unter
`docs/runbooks/smart-home-bootstrap.md`.
## MQTT Bootstrap
Beispiel fuer den initialen Home-Assistant-MQTT-User auf dem Unraid-Host:
```sh
mkdir -p /mnt/user/appdata/mosquitto/config
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
```
LAN-Port `1883` bleibt in Phase 1 geschlossen. Eine Portfreigabe fuer externe
MQTT-Clients wird erst in der ESPHome-Phase mit ACLs und per-Device-Usern
ergaenzt.
## Ecowitt
Ecowitt wird nicht in Phase 1 exponiert. Wegen des globalen Traefik
HTTP-zu-HTTPS-Redirects bleibt die Ingress-Entscheidung offen:
1. Traefik-HTTP-Ausnahme nur fuer den Ecowitt-Webhook, falls der globale
EntryPoint-Redirect gezielt abloesbar ist.
2. Dokumentierter LAN-only Host-Port `8123` als Fallback, wenn Option 1 den
bestehenden Traefik-Standard zu stark verbiegt.
+71
View File
@@ -0,0 +1,71 @@
services:
homeassistant:
image: ghcr.io/home-assistant/home-assistant:2026.6.1@sha256:59aa8824955c9db491b75d2eebe42bd68494f80c2ec69ec0d66d9dae37d37514
container_name: homeassistant
restart: unless-stopped
environment:
TZ: Europe/Berlin
volumes:
- /mnt/user/appdata/homeassistant:/config
- /mnt/user/services/smart-home-kalli/home-assistant/configuration.yaml:/config/configuration.yaml:ro
- /mnt/user/services/smart-home-kalli/home-assistant/automations.yaml:/config/automations.yaml:ro
- /mnt/user/services/smart-home-kalli/home-assistant/scripts.yaml:/config/scripts.yaml:ro
- /mnt/user/services/smart-home-kalli/home-assistant/scenes.yaml:/config/scenes.yaml:ro
- /mnt/user/services/smart-home-kalli/home-assistant/packages:/config/packages:ro
networks:
- frontend_net
- smarthome_net
# Zugang zum bestehenden Monitoring-Netz nur fuer den InfluxDB-3-Writer
# (Wetter-/Langzeitarchiv). HA schreibt intern an monitoring-influxdb3-core:8181,
# kein Host-Port, keine LAN-Exposition. Siehe docs/DECISIONS.md (2026-06-13).
- monitoring_net
# LAN-only Host-Bind nur fuer den Ecowitt-HTTP-Push: das GW3000-Gateway kann
# kein HTTPS und pusht per HTTP direkt an den HA-Webhook. Bindung ausschliesslich
# auf die LAN-IP (nicht 0.0.0.0, nicht WAN). Dokumentierte Ausnahme analog
# InfluxDB 8181, siehe docs/DECISIONS.md (2026-06-13) und Architektur-Master 10.
ports:
- "192.168.178.58:8123:8123"
security_opt:
- no-new-privileges:true
depends_on:
- mosquitto
labels:
- traefik.enable=true
- traefik.docker.network=frontend_net
- traefik.http.routers.homeassistant.rule=Host(`home.kaleschke.info`)
- traefik.http.routers.homeassistant.entrypoints=websecure
- traefik.http.routers.homeassistant.tls=true
- traefik.http.routers.homeassistant.tls.certresolver=le
- traefik.http.routers.homeassistant.middlewares=secure-headers@file
- traefik.http.services.homeassistant.loadbalancer.server.port=8123
mosquitto:
image: eclipse-mosquitto:2.0.22@sha256:914f529386804c8278a4e581526b9be5e1604df44b30daabc70aa97dcefe5268
container_name: smarthome-mosquitto
restart: unless-stopped
volumes:
- ./mosquitto/config/mosquitto.conf:/mosquitto/config/mosquitto.conf
- /mnt/user/appdata/mosquitto/config:/mosquitto/external_config
- /mnt/user/appdata/mosquitto/data:/mosquitto/data
- /mnt/user/appdata/mosquitto/log:/mosquitto/log
networks:
- smarthome_net
expose:
- "1883"
security_opt:
- no-new-privileges:true
networks:
frontend_net:
external: true
smarthome_net:
name: smarthome_net
driver: bridge
internal: true
# Bestehendes Observability-Netz (vom monitoring-Stack angelegt); hier nur
# extern referenziert, damit HA den InfluxDB-3-Writer erreicht.
monitoring_net:
external: true
name: monitoring_net
@@ -0,0 +1,15 @@
per_listener_settings true
listener 1883 0.0.0.0
allow_anonymous false
password_file /mosquitto/external_config/passwordfile
acl_file /mosquitto/external_config/aclfile
persistence true
persistence_location /mosquitto/data/
log_dest stdout
log_type error
log_type warning
log_type notice
connection_messages true
+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
traefik: traefik:
image: traefik:v3.7@sha256:d6858791f9e74df44ca4014166647c41cdc2abd3bf2a71b832ca4e1c6a91b257 image: traefik:v3.7@sha256:fcdef599e6259359833dd2e1d49f9e964f66825d69bd3dd468f51102ce013d03
container_name: traefik container_name: traefik
restart: unless-stopped restart: unless-stopped
security_opt: security_opt: