Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a28d3bbc33 |
@@ -3,7 +3,7 @@
|
||||
> **Single Source of Truth** für Docker-Netzwerkarchitektur, Sicherheitsregeln, Zielbild und Migration des Kallilabcore-Homelabs.
|
||||
> **Arbeitsregel für KI-Assistenten:** Dieses Dokument immer zuerst lesen, bevor Fragen zu Containern, Netzwerken, Traefik, Tailscale, Migration oder Security beantwortet werden.
|
||||
|
||||
**Stand:** 2026-06-13 | **Aktueller Schwerpunkt:** Home Assistant Tibber / Energie-Kosten
|
||||
**Stand:** 2026-06-11 | **Aktueller Schwerpunkt:** GitOps / Doku-Synchronisierung / Reproduzierbare Deployments
|
||||
|
||||
---
|
||||
|
||||
@@ -88,8 +88,7 @@ Jeder produktive Container nutzt `restart: unless-stopped`, außer eine Ausnahme
|
||||
| `backend_net` | bridge, `internal: true` | interne App-/DB-/Cache-Kommunikation | Standard |
|
||||
| `dns_net` | bridge | Resolver-Schicht: AdGuard Home + Unbound | bleibt |
|
||||
| `mealie_internal` | bridge, `internal: true` | internes Netz nur für `mealie` + `mealie-postgres` | ✅ umgesetzt |
|
||||
| `immich_default` | Compose-intern, `internal: true` | internes Immich-Netz (Server, Postgres, Redis, ML) | ✅ umgesetzt |
|
||||
| `immich_egress` | Compose-intern, bridge (nicht `internal`) | Outbound-only fuer `immich_machine_learning` (Modell-Download huggingface) | ✅ umgesetzt |
|
||||
| `immich_default` | Compose-intern, `internal: true` | internes Immich-Netz | ✅ umgesetzt |
|
||||
| `nextcloud_internal` | bridge, `internal: true` | internes Netz nur fuer `nextcloud` + `nextcloud-postgres` + `nextcloud-redis` | ✅ vorbereitet |
|
||||
| `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 |
|
||||
@@ -265,7 +264,7 @@ Legende Status:
|
||||
| `immich_redis` | ⏳ | `immich_default` | intern | intern-only | anonymes Volume → named volume |
|
||||
| `nextcloud-postgres` | ✅ | `nextcloud_internal` | intern | app-eigene Nextcloud-Datenbank mit `_FILE`-Secret | — |
|
||||
| `nextcloud-redis` | ✅ | `nextcloud_internal` | intern | app-eigener Cache fuer File Locking / Sessions | — |
|
||||
| `smarthome-mosquitto` | ✅ | `smarthome_net` | intern `1883`, kein Host-Port in Phase 1 | MQTT-Datenbus fuer Home Assistant, spaeter ESPHome und Zigbee2MQTT; Passwortdatei und ACLs in `/mnt/user/appdata/mosquitto/config`; MQTT-Smoke und HA-MQTT-Integration am 2026-06-13 erfolgreich | LAN-Port erst in ESPHome-Phase mit ACLs/per-Device-Usern |
|
||||
| `smarthome-mosquitto` | ✅ vorbereitet | `smarthome_net` | intern `1883`, kein Host-Port in Phase 1 | MQTT-Datenbus fuer Home Assistant, spaeter ESPHome und Zigbee2MQTT; Passwortdatei und ACLs in `/mnt/user/appdata/mosquitto/config` | LAN-Port erst in ESPHome-Phase mit ACLs/per-Device-Usern |
|
||||
|
||||
### 7.4 Produktive Apps
|
||||
|
||||
@@ -277,9 +276,9 @@ Legende Status:
|
||||
| `ntfy` | ✅ | `frontend_net` | Traefik | aktiv via `ntfy.kaleschke.info`, Git-Stack | — |
|
||||
| `gitea` | ✅ | `frontend_net` | Traefik + SSH-Port 222 | Web via Traefik, SSH direkt gebunden | — |
|
||||
| `immich_server` | ✅ | `immich_default`, `frontend_net` | Traefik | aktiv via `immich.kaleschke.info` | — |
|
||||
| `immich_machine_learning` | ✅ | `immich_default`, `immich_egress` | intern (keine Traefik-Route) | `immich_default` fuer Server-Erreichbarkeit + dediziertes `immich_egress` (nicht-internal) fuer einmaligen Modell-Download (CLIP/buffalo_l) nach `model-cache`; bewusst nicht `frontend_net`, da unauth. ML-API | — |
|
||||
| `immich_machine_learning` | ✅ | `immich_default` | intern | bleibt intern | — |
|
||||
| `nextcloud` | ✅ | `frontend_net`, `nextcloud_internal` | Traefik | aktiv via `cloud.kaleschke.info`, nativer Nextcloud-Login, WebDAV/CardDAV faehig | CalDAV/CardDAV-Redirect via Traefik-Labels |
|
||||
| `homeassistant` | ✅ | `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 |
|
||||
| `homeassistant` | ✅ vorbereitet | `frontend_net`, `smarthome_net` | Traefik via `home.kaleschke.info`, native HA-Auth | Home Assistant Container im GitOps-Stack `smart-home/`; kein HAOS, kein Supervised; Fach-YAML kommt aus `smart-home-kalli`, `.storage` bleibt in `/mnt/user/appdata/homeassistant` | Deploy, Onboarding, Restore-Probe, Cloud-Integrationen |
|
||||
| `plex` | ✅ | `host` | Traefik via `plex.kaleschke.info` + Plex native Auth; LAN direkt `:32400` | Compose-Stack unter `host-services/plex/`; Host-Netz bleibt fuer Discovery / Plex GDM dokumentierte Ausnahme; Traefik routet per File-Provider-Ausnahme auf `http://192.168.178.58:32400`, weil Docker-Labels Host-Netz-Container aus Traefik heraus auf `127.0.0.1` routen wuerden; kein direkter WAN-Port 32400 und Plex Remote Access bleibt aus; Server geclaimt von `Xeridos`; Smart-TVs (Schlafzimmer, Wohnzimmer) ueber WLAN-LAN per mDNS | — |
|
||||
| `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 |
|
||||
@@ -400,8 +399,7 @@ Die Blockmigration aus der Portainer-/Dockerman-Phase ist abgeschlossen: Traefik
|
||||
| `n8n` | keine pauschale Authelia-Middleware | Webhook-Endpunkte (`/webhook/*`, `/webhook-test/*`) muessen ohne ForwardAuth erreichbar bleiben; n8n bringt eigene Owner-/Login-Auth mit (analog Komodo/Nextcloud) |
|
||||
| `plex` | Traefik ohne Authelia, File-Provider-Ausnahme trotz Host-Netz | Plex bringt native Konto-/Client-Auth mit; vorgeschaltete ForwardAuth wuerde Plex Web, Apps und Client-Flows stoeren. Docker-Labels sind fuer diesen Host-Netz-Container ungeeignet, weil Traefik sonst `127.0.0.1:32400` nutzt; daher `traefik/dynamic/plex.yml` mit Ziel `192.168.178.58:32400`. Route nur ueber Traefik/443 (`plex.kaleschke.info`), direkter Plex-WAN-Port 32400 und Plex Remote Access bleiben deaktiviert. |
|
||||
| `homeassistant` | Traefik ohne Authelia, Fach-YAML aus separatem Repo | Home Assistant bringt eigene Auth, mobile Apps, Webhooks und Integrationsfluesse mit. Der Container haengt in `frontend_net` fuer Traefik und in `smarthome_net` fuer MQTT/Zigbee2MQTT/ESPHome. `.storage` und Secrets bleiben in Appdata und werden per Borg gesichert, nicht versioniert. |
|
||||
| `homeassistant` (Ecowitt) | LAN-only Host-Port `8123` auf `192.168.178.58` | Ecowitt-GW3000 kann kein HTTPS und pusht per HTTP an den HA-Webhook. HA bekommt einen Host-Bind nur auf der LAN-IP (`192.168.178.58:8123:8123`, nicht `0.0.0.0`/WAN), analog InfluxDB 8181. Kein Traefik-Umbau des globalen HTTP-Redirects noetig, da Ecowitt rein im LAN pusht. Webhook nicht `local_only`, geschuetzt durch 128-bit-Zufalls-ID. Siehe `docs/DECISIONS.md` (2026-06-13). |
|
||||
| `homeassistant` (SolarEdge Local) | HACS/Custom-Integration `solaredge_modbus_multi` | Lokaler SolarEdge-Zugriff laeuft ueber Modbus TCP `192.168.178.111:1502`, Device-ID `1`. Das ist bewusst lokal statt Cloud-API, weil kein SolarEdge-API-Key verfuegbar ist und der Wechselrichter Modbus-Daten fuer Inverter, Smart Meter und Batterie liefert. Custom-Integration-Warnungen bei HA-Core-Upgrades beachten. |
|
||||
| `Ecowitt` | spaetere HTTP-Ausnahme offen | Ecowitt kann nur HTTP. Wegen globalem Traefik-HTTP-Redirect wird in Phase 2 entschieden, ob Traefik eine selektive Webhook-Ausnahme bekommt oder ob ein LAN-only HA-Port `8123` als dokumentierte Host-Port-Ausnahme noetig wird. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -48,18 +48,7 @@ services:
|
||||
volumes:
|
||||
- model-cache:/cache
|
||||
networks:
|
||||
# immich_default (internal) = Erreichbarkeit durch immich-server.
|
||||
# immich_egress (nicht-internal) = Outbound zu huggingface, damit ML die
|
||||
# Modelle (CLIP ViT-B-32, buffalo_l) einmalig nach model-cache laedt.
|
||||
# Ohne dieses Netz scheitert der Modell-Download an der DNS-Aufloesung
|
||||
# (immich_default ist internal: true) -> Smart Search/Gesichtserkennung tot.
|
||||
- immich_default
|
||||
- immich_egress
|
||||
dns:
|
||||
# Egress-Netz braucht externe Aufloesung (huggingface.co); explizit nach
|
||||
# docs/WORKFLOW.md "DNS-Regeln fuer Container", analog traefik/ddns-updater.
|
||||
- 1.1.1.1
|
||||
- 8.8.8.8
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
@@ -97,10 +86,5 @@ networks:
|
||||
name: immich_default
|
||||
internal: true
|
||||
driver: bridge
|
||||
immich_egress:
|
||||
# Bewusst NICHT internal: nur fuer den ML-Modell-Download (Outbound).
|
||||
# Nur immich_machine_learning haengt hier; DB/Redis bleiben in immich_default.
|
||||
name: immich_egress
|
||||
driver: bridge
|
||||
frontend_net:
|
||||
external: true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
mail-archiver:
|
||||
image: s1t5/mailarchiver@sha256:9860b170040dc096aeedc47bdbb09e2913aa8742eec205e29edd0ab79f9dda7e
|
||||
image: s1t5/mailarchiver@sha256:4ea7ecc47ad1dd2c523b85c3967574b61e39def1b6fd26edf874e21733c4018c
|
||||
container_name: mail-archiver
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
|
||||
@@ -54,7 +54,7 @@ services:
|
||||
- traefik.http.services.mealie.loadbalancer.server.port=9000
|
||||
|
||||
mealie-postgres:
|
||||
image: postgres:18.4@sha256:8ff36f3c66371cba71d20ceedccfc3de9669a68737607888c4ef0af93abe8e39
|
||||
image: postgres:18.4@sha256:29ee7bb30d804447dc9a91fd0d74322ae1dc3a4072cc6346f70a5ed6e783b565
|
||||
container_name: mealie-postgres
|
||||
restart: unless-stopped
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ services:
|
||||
- "traefik.http.services.nextcloud.loadbalancer.server.port=80"
|
||||
|
||||
nextcloud-postgres:
|
||||
image: postgres:18.4@sha256:8ff36f3c66371cba71d20ceedccfc3de9669a68737607888c4ef0af93abe8e39
|
||||
image: postgres:18.4@sha256:29ee7bb30d804447dc9a91fd0d74322ae1dc3a4072cc6346f70a5ed6e783b565
|
||||
container_name: nextcloud-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
super-productivity:
|
||||
image: johannesjo/super-productivity:v18.10.0@sha256:cc548f3a1bdd31a270b195e6d2b3530689aca0db70b03d31dfcf9365d322f1f2
|
||||
image: johannesjo/super-productivity:v18.9.1@sha256:773760107344e739f4c29409f7842db66a1b167d50eb2c40248cb5b5b328652e
|
||||
container_name: super-productivity
|
||||
restart: unless-stopped
|
||||
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
# Authelia OIDC fuer Apps - Plan & Runbook
|
||||
|
||||
Stand: 2026-06-17. Authelia-Version: **v4.39.20**.
|
||||
Stand: 2026-06-06. Authelia-Version: **v4.39.20**.
|
||||
|
||||
Ziel: App-uebergreifendes Single-Sign-On ueber Authelia als OpenID-Connect-Provider
|
||||
(`https://auth.kaleschke.info`). Statt pro App eigener Logins meldet man sich einmal
|
||||
bei Authelia an (inkl. 2FA) und wird per OIDC an die App durchgereicht.
|
||||
|
||||
> **Status:** aktives Runbook. Grafana und Mealie sind seit 2026-06-06 live
|
||||
> und per Login-Smoke verifiziert. Paperless ist seit 2026-06-17 technisch
|
||||
> verdrahtet (Authelia-Client + Stack-ENV-Secret + Service-Smoke gruen);
|
||||
> finaler Browser-Login mit Operator-Account bleibt offen. Der Rollout bleibt
|
||||
> additiv: lokale App-Logins bleiben als Fallback aktiv.
|
||||
> und per Login-Smoke verifiziert. Der weitere Rollout bleibt additiv: lokale
|
||||
> App-Logins bleiben als Fallback aktiv.
|
||||
|
||||
---
|
||||
|
||||
@@ -87,7 +85,7 @@ docker exec authelia authelia crypto hash generate pbkdf2 \
|
||||
| 2 | Immich | `immich.kaleschke.info` | nativ (Admin-UI/Config-File) | s. u. (Familie) | mittel | **GEPARKT bis Onboarding (Entscheidung 2026-06-06):** nur `micha` hat Authelia-Account, Familien-SSO-Nutzen entsteht erst mit Familien-Accounts; Immich ist mobil-lastig (hoechste Stoeranfaelligkeit) und braucht UI/Config-File. Erst nach Onboarding gezielt. Runbook bereit. |
|
||||
| 3 | Nextcloud | `cloud.kaleschke.info` | App `user_oidc` (+occ) | s. u. | mittel | **GEPARKT bis Onboarding (Entscheidung 2026-06-06):** wie Immich; braucht `user_oidc`-App-Install + `occ`. Lokaler Login bleibt. Erst nach Onboarding. Runbook bereit. |
|
||||
| **4 ERLEDIGT 2026-06-06** | Mealie | `mealie.kaleschke.info` | nativ | `one_factor` | niedrig | **Live + Login verifiziert.** OIDC-Env additiv (lokaler Login bleibt), Secret als Stack-ENV `${MEALIE_OIDC_CLIENT_SECRET}`, `extra_hosts` noetig (s. Gotchas) |
|
||||
| **5 TEILWEISE ERLEDIGT 2026-06-17** | Paperless-ngx | `paperless.kaleschke.info` | `django-allauth` (Umgebungsvariablen) | `one_factor` (hostseitiger Ist-Stand; `two_factor` spaeter moeglich) | mittel | **Authelia-Client + `${PAPERLESS_OIDC_SECRET}` in Stack-ENV gesetzt, Authelia-Config validiert, Paperless HTTP-Smoke `200`.** Lokaler Login bleibt Fallback; finaler Browser-Login mit Operator-Account offen. |
|
||||
| 5 | Paperless-ngx | `paperless.kaleschke.info` | `django-allauth` (Umgebungsvariablen) | `two_factor` | mittel | dokumentenlastig, Operator-nah |
|
||||
|
||||
**Nicht OIDC:** Vaultwarden hat kein Standard-Endnutzer-OIDC (SSO ist Enterprise/Bitwarden-Feature) -> bleibt eigener Login. ntfy bleibt wie gehabt.
|
||||
|
||||
@@ -177,8 +175,7 @@ GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP=true
|
||||
E-Mail-Claim. Stimmt die Authelia-E-Mail mit dem App-Account, wird verknuepft;
|
||||
sonst legt die App (bei aktivem Signup) einen neuen User an.
|
||||
- **Secret-Mechanik je App verschieden:** Grafana `__FILE` (Docker-Secret),
|
||||
Mealie Stack-ENV `${MEALIE_OIDC_CLIENT_SECRET}`, Paperless Stack-ENV
|
||||
`${PAPERLESS_OIDC_SECRET}`. Hash immer in der Authelia-Host-Config, Klartext nie ins Repo.
|
||||
Mealie Stack-ENV `${...}`. Hash immer in der Authelia-Host-Config, Klartext nie ins Repo.
|
||||
|
||||
## Spaetere Feinschliffe vor breitem Rollout
|
||||
|
||||
|
||||
@@ -11,128 +11,6 @@ in `HOMELAB_ARCHITECTURE_MASTER_V2.md` §13, `docs/MASTER_TODO.md` (Geparkt),
|
||||
|
||||
---
|
||||
|
||||
## 2026-06-16 - Immich ML bekommt dediziertes Egress-Netz (Modell-Download)
|
||||
|
||||
**Entscheidung:** `immich_machine_learning` haengt zusaetzlich zu `immich_default`
|
||||
(`internal: true`) in einem neuen, bewusst **nicht**-internen Compose-Netz
|
||||
`immich_egress` und bekommt explizit `dns: 1.1.1.1/8.8.8.8`. Damit kann ML die
|
||||
Modelle (CLIP `ViT-B-32__openai`, Gesichtserkennung `buffalo_l`) einmalig von
|
||||
huggingface nach `model-cache` laden. DB und Redis bleiben unveraendert in
|
||||
`immich_default` isoliert.
|
||||
|
||||
**Kontext:** Am 2026-06-16 live gemessen: ML lief zwar `healthy`, aber
|
||||
`/cache` war 0 B und die Logs zeigten in Schleife
|
||||
`NameResolutionError: Failed to resolve 'huggingface.co'`. Ursache: ML hing nur
|
||||
im `internal: true`-Netz `immich_default` ohne Egress/DNS. Folge: Smart Search
|
||||
und Gesichtserkennung waren faktisch tot, trotz gesundem Container (Healthcheck
|
||||
prueft nur den HTTP-Endpoint, nicht die Modellverfuegbarkeit). Das Zielbild §7.4
|
||||
sagte vorher "bleibt intern" und widersprach damit dem eigenen Einordnungsschema
|
||||
§6 Schritt 2 ("braucht Internet -> nicht-internal").
|
||||
|
||||
**Alternativen:** (a) ML an `frontend_net` haengen — verworfen, weil die
|
||||
unauthentifizierte ML-API dann im geteilten Web-Netz erreichbar waere.
|
||||
(b) `immich_default` auf nicht-internal umstellen — verworfen, weil dann auch
|
||||
Postgres/Redis Outbound-Internet bekaemen (Least-Privilege-Verlust). Das
|
||||
dediziertes Egress-Netz isoliert den Internetbedarf auf genau ML.
|
||||
|
||||
**Review-Trigger:** Immich-Update, das ML-Modelle anders bezieht; Wunsch nach
|
||||
vollstaendigem Air-Gap (dann Modelle offline vorseeden statt Egress); oder wenn
|
||||
ML weitere Modelle braucht (Egress bleibt dafuer noetig).
|
||||
|
||||
## 2026-06-13 - Wetter-/Langzeitarchiv: HA schreibt nach InfluxDB 3 Core
|
||||
|
||||
**Entscheidung:** Home Assistant schreibt die Ecowitt-Sensoren
|
||||
(`sensor.gw3000a_*`) dauerhaft nach InfluxDB 3 Core (DB `homeassistant`),
|
||||
visualisiert im Grafana-Dashboard `ha-weather-archive` ("Wetterarchiv
|
||||
KalliHome"). HA wurde dem bestehenden `monitoring_net` als zusaetzliches Netz
|
||||
hinzugefuegt und schreibt intern an `monitoring-influxdb3-core:8181`
|
||||
(v2-Write-API) - kein Host-Port, keine LAN-Exposition. Das war die im
|
||||
Ecowitt-Eintrag offen gelassene Reachability-Entscheidung (Alternative:
|
||||
LAN-Bind 8181).
|
||||
|
||||
**Kontext:** Gewuenscht war ein echtes Langzeit-Wetterarchiv unabhaengig von
|
||||
HAs kurzer SQLite-Historie. HAs eingebaute Langzeit-Statistiken decken den Fall
|
||||
stuendlich bereits ab; InfluxDB liefert volle Aufloesung und eigene Grafana-
|
||||
Dashboards. InfluxDB 3 Core kennt nur Admin-Tokens (keine feingranularen
|
||||
Scopes), daher hat der HA-Schreibtoken vollen Admin-Zugriff auf die
|
||||
Monitoring-InfluxDB - bewusst akzeptiert (Operator-Freigabe), unabhaengig
|
||||
widerrufbar, Token nur in Appdata-Secrets (`ha_influxdb_token` + HA
|
||||
`secrets.yaml`).
|
||||
|
||||
**Betriebsstand 2026-06-13:** HA im `monitoring_net`, Writer aktiv (Daten in
|
||||
Measurements `°C`, `%`, `hPa`, `km/h`, `W/m²`, `mm`, `lx`, `°`), zweite
|
||||
Grafana-Datasource `ha-weather-influx` (DB `homeassistant`) und Dashboard
|
||||
provisioniert. Glance zeigt zusaetzlich eine Live-Wetterkachel direkt aus der
|
||||
HA-API (`GLANCE_HA_TOKEN`).
|
||||
|
||||
**Review-Trigger:** InfluxDB-3-Enterprise mit Token-Scopes (dann HA-Token
|
||||
einschraenken), Wegfall des Monitoring-Stacks, oder Neubewertung der
|
||||
HA-Internet-Exposition (HA haengt jetzt auch im Observability-Netz).
|
||||
|
||||
## 2026-06-13 - SolarEdge lokal ueber Modbus TCP angebunden
|
||||
|
||||
**Entscheidung:** SolarEdge wird in Home Assistant lokal ueber
|
||||
`solaredge_modbus_multi` angebunden, nicht ueber die SolarEdge-Cloud-API. Der
|
||||
Wechselrichter ist im LAN als `192.168.178.111` erreichbar, MAC-OUI
|
||||
`84:D6:C5` gehoert zu SolarEdge, Modbus TCP laeuft auf Port `1502`, Device-ID
|
||||
`1`. Die Integration liefert Inverter-, Smart-Meter- und Batterie-Entitaeten.
|
||||
|
||||
**Kontext:** Der Operator kann im SolarEdge-Portal keinen API-Key erzeugen; das
|
||||
fruehere Setup lief bereits lokal. Der alte in der Doku genannte VONETS-Adapter
|
||||
`192.168.178.71` ist nicht erreichbar und bleibt kein verlaesslicher Zielpfad.
|
||||
Die native HA-Core-Integration `solaredge` waere Cloud-Polling mit Site-ID/API-
|
||||
Key; `solaredge_local` erwartet dagegen die lokale HTTP-SetApp-API unter
|
||||
`/web/v1/status`, die am Wechselrichter nicht offen ist. Der vorhandene
|
||||
HACS-/Custom-Component-Pfad `solaredge_modbus_multi` v3.2.5 passt zur realen
|
||||
Schnittstelle und wurde ohne neue Downloads wiederverwendet.
|
||||
|
||||
**Betriebsstand 2026-06-13:** Config-Entry `SolarEdge Local` ist `loaded`,
|
||||
Polling alle 60 Sekunden, Meter- und Batterie-Erkennung aktiv, Extras und
|
||||
Power-Control-Schreibfunktionen deaktiviert. Relevante Energy-Dashboard-
|
||||
Kandidaten:
|
||||
`sensor.solaredge_local_i1_ac_energy`,
|
||||
`sensor.solaredge_local_i1_m1_ac_energy_imported`,
|
||||
`sensor.solaredge_local_i1_m1_ac_energy_exported`,
|
||||
`sensor.solaredge_local_i1_b1_energy_import`,
|
||||
`sensor.solaredge_local_i1_b1_energy_export`. Nach der Integration wurde ein
|
||||
HA-native Backup erzeugt:
|
||||
`/mnt/user/appdata/homeassistant/backups/Custom_backup_2026.6.1_2026-06-13_14.59_48645373.tar`.
|
||||
Das HA Energy Dashboard wurde anschliessend mit Netz, PV und Speicher aus
|
||||
SolarEdge Local konfiguriert und per `energy/validate` ohne Issues geprueft.
|
||||
Kosten/Preise bleiben bis zur Tibber-Anbindung leer. Nach dieser UI-State-
|
||||
Aenderung wurde ein weiteres HA-native Backup erzeugt:
|
||||
`/mnt/user/appdata/homeassistant/backups/Custom_backup_2026.6.1_2026-06-13_15.59_25670583.tar`.
|
||||
|
||||
**Trade-off:** Die lokale Modbus-Integration passt zum Prinzip "lokal vor
|
||||
Cloud" und liefert deutlich bessere Betriebsdaten als die Cloud-API, ist aber
|
||||
eine HACS-/Custom-Integration und damit nicht durch HA-Core getestet. Bei
|
||||
Problemen zuerst Integration deaktivieren oder auf HA-Core-Cloud-Polling
|
||||
zurueckfallen, sobald Site-ID/API-Key verfuegbar sind.
|
||||
|
||||
**Review-Trigger:** HA-Core-Upgrade mit Custom-Integration-Warnungen,
|
||||
Ausfaelle von `192.168.178.111:1502`, Wechselrichtertausch/IP-Aenderung,
|
||||
oder wenn Energie-Automationen schreibende Power-Control-Funktionen brauchen.
|
||||
|
||||
## 2026-06-13 - Ecowitt-Ingress: LAN-only Host-Bind 8123 umgesetzt
|
||||
|
||||
**Entscheidung:** Home Assistant bekommt einen LAN-only Host-Bind
|
||||
`192.168.178.58:8123:8123` (nur LAN-IP, nicht `0.0.0.0`/WAN). Das Ecowitt-GW3000
|
||||
pusht per HTTP direkt an den HA-Webhook. Damit ist die offene
|
||||
Phase-2-Entscheidung (Eintrag 2026-06-12) zugunsten des LAN-Bind-Fallbacks
|
||||
entschieden; ein Umbau des globalen Traefik HTTP-zu-HTTPS-Redirects entfaellt,
|
||||
weil Ecowitt rein im LAN pusht und Traefik gar nicht braucht.
|
||||
|
||||
**Kontext:** Der globale `web`->`websecure`-Redirect auf EntryPoint-Ebene laesst
|
||||
sich nicht sauber selektiv aushebeln. Der LAN-Bind ist analog zur dokumentierten
|
||||
InfluxDB-8181-Ausnahme, WAN-sicher (FRITZ!Box forwardet nur 443 auf Traefik) und
|
||||
ohne Traefik-Umbau. Der HA-Webhook ist nicht `local_only`; Schutz ist die
|
||||
128-bit-Zufalls-Webhook-ID. Restrisiko: der Pfad ist theoretisch auch ueber
|
||||
Traefik/443 erreichbar, praktisch aber unratbar.
|
||||
|
||||
**Review-Trigger:** Wenn der Webhook haerter abgesichert werden soll
|
||||
(Traefik-IPAllowList auf `/api/webhook/` oder `local_only`), oder bei Ausbau
|
||||
auf Ecowitt-Langzeitspeicherung in InfluxDB.
|
||||
|
||||
## 2026-06-12 - Home Assistant als Container im GitOps-Stack
|
||||
|
||||
**Entscheidung:** Home Assistant laeuft neu als `homeassistant` Container im
|
||||
@@ -152,22 +30,6 @@ 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.
|
||||
|
||||
+11
-8
@@ -1,6 +1,6 @@
|
||||
# Master To-do - KalliLab CORE
|
||||
|
||||
Typ: Status/To-do · Stand: 2026-06-17 · Status: aktiv
|
||||
Typ: Status/To-do · Stand: 2026-06-12 · Status: aktiv
|
||||
|
||||
Diese Liste ist die **einzige** Arbeitsliste fuer offene operative Punkte im
|
||||
Homelab. Detailablaeufe stehen in den verlinkten Runbooks; Entscheidungen mit
|
||||
@@ -23,8 +23,10 @@ Host-Reports (`/mnt/user/backups/restore-reports/`) und in der Git-Historie.
|
||||
| Family-Onboarding erster Termin | Operator | Checkliste ist fertig (`docs/FAMILY_ONBOARDING.md` Abschnitt "Erster Onboarding-Termin"). Personen/Geraete festlegen, Reihenfolge Vaultwarden -> Immich -> Mealie pro Person abarbeiten | `docs/FAMILY_ONBOARDING.md` |
|
||||
| Restore-Test Unraid OS Flash (Stick-Boot) | Operator | Artefakt-Validierung 2026-06-05 erledigt (`ops/maintenance/check-unraid-flash-backup.sh`). **Verbleibt:** physischer Ersatzstick-Boot-Test, wenn ein Wegwerf-Stick bereitliegt | `ops/restore-tests/unraid-flash-runbook.md` |
|
||||
| Restore-Test Tailscale | Operator | State-Validierung + Reconnect nur auf Wegwerf-Host/VM, danach Geraet in Tailscale-Admin entfernen | `ops/restore-tests/tailscale-runbook.md` |
|
||||
| Authelia OIDC fuer Apps | Operator/Codex | Live: Grafana + Mealie login-verifiziert; Paperless Secret verdrahtet und Service-Smoke am 2026-06-17 gruen, finaler Browser-Login mit Operator-Account offen. Immich + Nextcloud bewusst geparkt bis Family-Onboarding (siehe `docs/DECISIONS.md` 2026-06-06) | `docs/AUTHELIA_OIDC_PLAN.md` |
|
||||
| Home Assistant Tibber | Operator/Codex | Tibber per HA-UI-Config-Flow verbinden. Danach Energy-Dashboard um echte Kosten/Preisquelle ergaenzen; SolarEdge-PV, Netz und Speicher sind bereits konfiguriert und validiert | `docs/runbooks/smart-home-bootstrap.md`, `docs/DECISIONS.md` |
|
||||
| Authelia OIDC fuer Apps | Operator/Claude | Live: Grafana + Mealie (verifiziert), Paperless deployed (Login-Test offen). Immich + Nextcloud bewusst geparkt bis Family-Onboarding (siehe `docs/DECISIONS.md` 2026-06-06) | `docs/AUTHELIA_OIDC_PLAN.md` |
|
||||
| Glance-v2-Widgets: Tokens setzen | Operator | In Komodo Stack-ENV fuer `ops-glance` setzen: `GLANCE_KOMODO_API_KEY`/`_SECRET` (Komodo read-only API-Key), `GLANCE_GITEA_TOKEN` (read-only, scope `read:repository`), `GLANCE_PAPERLESS_TOKEN`, `GLANCE_MEALIE_TOKEN`; bis dahin zeigen die neuen Widgets Fehler/leer. Speedtest-Widget: falls weiter 0.0, API-Response pruefen | `ops/glance/config/` |
|
||||
| Home Assistant Foundation-Abnahme | Operator/Codex | Sofort: Owner-Onboarding abschliessen. Danach temporaere Authelia-Middleware von der HA-Route entfernen, Komodo-Stack-Eintrag + Webhook sauber anlegen/verifizieren, HA-MQTT-Smoke-Test und HA-native `backup.create` testen, Restore-Probe fuer HA/Mosquitto dokumentieren | `docs/runbooks/smart-home-bootstrap.md`, `docs/RESTORE_MATRIX.md` |
|
||||
| Audit-PDF aus `docs/` entfernen | Operator | `docs/KalliLab_CORE_Audit_2026-06-06.pdf` (untracked) extern ablegen (H:/ oder Documents-Share) und lokal loeschen; Binaerdateien gehoeren nicht ins GitOps-Repo | Doku-Regeln `docs/REPO_MAP.md` |
|
||||
|
||||
---
|
||||
|
||||
@@ -48,6 +50,7 @@ Bewusst nicht jetzt - Begruendungen in `docs/DECISIONS.md`, hier nur Thema und T
|
||||
| CrowdSec vor Traefik | breitere Attack Surface als nur `443/tcp` | `docs/DECISIONS.md` |
|
||||
| Nextcloud 2FA (Operator-TOTP) | OIDC-/SSO-Block erreicht die App-Login-Ebene | `docs/DECISIONS.md` |
|
||||
| Hermes-Agent | Review-Deadline 2026-07-25; NAS-Stack bleibt deaktiviert | `docs/SERVICE_CATALOG.md` |
|
||||
| Tailnet-Konsole aufraeumen (Rest) | trivial, bei Gelegenheit: tote Node-Eintraege (`kallilab-core`, alter `baerchen`) in der Tailscale-Admin-Konsole entfernen; optional State-Pfad `/mnt/user/appdata/tailscale` nach `_archive/` | `docs/NETWORK_INVENTORY.md` |
|
||||
| Dedizierter SMB-User `veeam-baerchen` | nur wenn Unraid-User-/Share-Rechte bewusst angefasst werden | `ops/windows-reinstall/docs/windows-image-backup-baseline.md` |
|
||||
| Filebrowser-Mount-Scope | naechster Hardening-Sprint | `docs/SERVICE_CATALOG.md` |
|
||||
| Scrutiny Privileged-Ausnahme | nur mit klarer Begruendung aendern | `docs/SERVICE_CATALOG.md` |
|
||||
@@ -68,11 +71,11 @@ Bewusst nicht jetzt - Begruendungen in `docs/DECISIONS.md`, hier nur Thema und T
|
||||
|
||||
## Zuletzt erledigt (Kurzlog, max. 5 Eintraege)
|
||||
|
||||
- **2026-06-17** Offene TODOs gegen Live-Stand abgeglichen: Paperless-OIDC-Secret verdrahtet und Service-Smoke gruen; alter Tailscale-Docker-State nach `_archive/tailscale-removed-2026-06-06/` verschoben; Tailnet-Restpunkt geschlossen.
|
||||
- **2026-06-17** Repo-Hygiene abgeschlossen: Glance-Widget-Tokens sind in Runtime gesetzt, Audit-PDF liegt extern unter `H:\kallilab-recovery\audits`, Worktree clean.
|
||||
- **2026-06-17** Komodo/Gitea-Webhooks normalisiert: aktive Komodo-Hooks fuer `Micha/homelab-infra` nutzen Branch-Filter `master`; DB-Backup vor Host-Hotfix erstellt. Workflow-Regel nachgezogen.
|
||||
- **2026-06-13** Home Assistant MQTT-Integration produktiv verbunden: Config-Entry `smarthome-mosquitto` ist `loaded`, Mosquitto sieht den HA-Client `homeassistant`; `check_config` gruen.
|
||||
- **2026-06-13** HA Energy Dashboard konfiguriert: Netz, PV und Speicher aus SolarEdge Local gesetzt, `energy/validate` ohne Issues; HA-Backup danach erzeugt.
|
||||
- **2026-06-12** Komodo-Stack-Hygiene-Check aktiv: `services/posture-check/komodo-stack-hygiene.sh` + Unraid User Script `komodo-stack-hygiene-weekly` (Sonntag 05:00). Faengt die `immich_new`-Klasse (Stack ohne Repo, `project_missing`, Compose ohne Stack, Hash-Drift). Erster Lauf: 6 Warnings, 0 Critical.
|
||||
- **2026-06-12** Immich Komodo-Stack bereinigt: `immich_new` auf `immich` korrigiert, Gitea-Account `Micha` gesetzt, per Komodo aus `apps/immich/docker-compose.yml` neu deployed. Verifiziert: `deployed_hash == latest_hash`, `immich_new_count=0`, alle vier Container healthy, HTTP 200, DB-Smoke `11983` Assets, Drift-Alert resolved.
|
||||
- **2026-06-11** Host-DNS-Fallback aktiv: `eth0` DNS2 = `192.168.178.1` (FRITZ!Box) zusaetzlich zu AdGuard. AdGuard-SPOF fuer Image-Pulls entschaerft; der dokumentierte Bulk-Deploy-Vorfall kann strukturell nicht wiederkommen.
|
||||
- **2026-06-11** Hetzner Storage Box: automatische Snapshots aktiv (taeglich 05:30 UTC, 7 Tage Retention). Schliesst das Ransomware-/Fehlbedienungs-Risiko gegen das Off-site-Backup. Siehe `docs/DECISIONS.md`.
|
||||
- **2026-06-11** Immich Image-Tags von `release` auf `v2.7.5` gepinnt (Server + ML, Digests unveraendert): Renovate-PRs zeigen ab jetzt sichtbare Versionsspruenge statt stiller Digest-Bumps.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+18
-17
@@ -1,7 +1,7 @@
|
||||
# Network Inventory - KalliLab CORE
|
||||
|
||||
Status: Host-Audit erfasst; Router-Baseline und Portfreigaben-UI bereinigt; FRITZ!Box-Remote-Dienste aus; IPv6-Exposure technisch und per UI entschaerft; Tailscale-Inventar am 2026-06-17 real gemessen.
|
||||
Letzte Pruefung: 2026-06-17 (Tailscale-Inventar), 2026-06-01 (Router/Ports)
|
||||
Status: Host-Audit erfasst; Router-Baseline und Portfreigaben-UI bereinigt; FRITZ!Box-Remote-Dienste aus; IPv6-Exposure technisch und per UI entschaerft; Tailscale-Inventar am 2026-06-05 real gemessen.
|
||||
Letzte Pruefung: 2026-06-05 (Tailscale-Inventar), 2026-06-01 (Router/Ports)
|
||||
|
||||
## Zweck
|
||||
|
||||
@@ -38,7 +38,7 @@ Dieses Dokument beschreibt Router, DNS, Tailscale, Portfreigaben und Netztrennun
|
||||
| Komponente | Rolle | Adresse | Bemerkung |
|
||||
|---|---|---|---|
|
||||
| AdGuard Home | LAN DNS / Filter | Host `192.168.178.58`, Docker `172.23.0.3` | DNS auf Port 53; Admin soll nur via Tailscale-IP `100.80.98.33:8082` erreichbar sein |
|
||||
| Unbound | DNSSEC-validierender Forwarding-Resolver | Docker `dns_net` | Upstream fuer AdGuard; forwardet per DoT zu Cloudflare, keine Root-Rekursion |
|
||||
| Unbound | Rekursiver Resolver | Docker `dns_net` | Upstream fuer AdGuard |
|
||||
| Cloudflare | Authoritative DNS | extern | DNS-Challenge fuer TLS |
|
||||
| Router | DHCP DNS-Verteilung | TBD | Muss auf AdGuard zeigen, falls so betrieben |
|
||||
|
||||
@@ -57,16 +57,18 @@ Gemessen am 2026-06-05 per read-only SSH auf den Host (`tailscale status`,
|
||||
| Subnet Router | **Ja, aktiv.** Host advertised und ist Primary fuer `192.168.178.0/24` (`Self.PrimaryRoutes: ["192.168.178.0/24"]`, ebenfalls in `AllowedIPs`). Das LAN ist also fuer das gesamte Tailnet ueber diesen Subnet-Router erreichbar — bewusst gemessener Ist-Zustand, **kein** "keine Route" wie zuvor vermutet. |
|
||||
| ACL-Policy extern dokumentiert | **Angewendet 2026-06-06** — restriktive Tag-basierte `grants`-Policy live (`tag:server`/`tag:operator`, `tag:family` schlafend). Default-Allow entfernt, verifiziert. Details im Block unten. |
|
||||
|
||||
### Tailnet-Geraete (Snapshot 2026-06-17)
|
||||
### Tailnet-Geraete (Snapshot 2026-06-05)
|
||||
|
||||
| Tailscale-IP | Node | OS | Status |
|
||||
|---|---|---|---|
|
||||
| `100.80.98.33` | kallilabcore | linux | aktiv (Host, Subnet-Router) |
|
||||
| `100.78.133.37` | baerchen-1 | windows | aktiv (aktuelle Operator-Workstation, direct) |
|
||||
| `100.73.83.55` | iphone-14 | iOS | bekannt, aktuell offline |
|
||||
| `100.105.203.21` | baerchen | windows | offline, zuletzt vor ~1 Tag gesehen (Alt-Node) |
|
||||
| `100.73.83.55` | iphone-14 | iOS | bekannt |
|
||||
| `100.112.0.90` | kallilab-core | linux | **am 2026-06-06 entfernt.** War der redundante userspace-only `Tailscale-Docker`-Stack (`host-services/tailscale/`). Komodo-Stack gestoppt+destroyed, Repo-Pfad per `git rm` entfernt, Container weg (read-only verifiziert). Node-Eintrag in der Admin-Konsole noch zu entfernen. |
|
||||
|
||||
> **Historischer Befund 2026-06-06 (read-only auf dem Host ermittelt):** Der Host
|
||||
> hatte damals **zwei** `tailscaled`-Prozesse:
|
||||
> **Befund 2026-06-06 (read-only auf dem Host ermittelt):** Der Host hat **zwei**
|
||||
> `tailscaled`-Prozesse:
|
||||
>
|
||||
> 1. **Native Unraid-Plugin** = `kallilabcore` (100.80.98.33). Prozess
|
||||
> `/usr/local/sbin/tailscaled -statedir /boot/config/plugins/tailscale/state
|
||||
@@ -87,10 +89,9 @@ Gemessen am 2026-06-05 per read-only SSH auf den Host (`tailscale status`,
|
||||
> (Operator), `git rm host-services/tailscale/`, Glance-Widget entfernt, und
|
||||
> Architektur-/Service-Catalog-/DR-/CLAUDE-Doku auf "natives Plugin" nachgezogen.
|
||||
> Read-only verifiziert: Container weg, nur noch der native `tailscaled` mit
|
||||
> `tailscale1`, Subnet-Route + Operator-Zugriff intakt. Nachpruefung 2026-06-17:
|
||||
> `tailscale status --self=false` zeigt nur noch `baerchen-1` und `iphone-14`;
|
||||
> der alte State-Pfad `/mnt/user/appdata/tailscale` ist weg und liegt archiviert
|
||||
> unter `/mnt/user/appdata/_archive/tailscale-removed-2026-06-06/`.
|
||||
> `tailscale1`, Subnet-Route + Operator-Zugriff intakt. Offen: Node-Eintraege
|
||||
> `kallilab-core` und alter `baerchen` in der Admin-Konsole entfernen; State-Pfad
|
||||
> `/mnt/user/appdata/tailscale` bei Gelegenheit nach `_archive/` (kein Sofort-Loeschen).
|
||||
>
|
||||
> **Doku-Korrektur erledigt:** `docs/RESTORE_MATRIX.md` zeigt jetzt auf den
|
||||
> funktionalen State `/boot/config/plugins/tailscale/state` (im Flash-Backup)
|
||||
@@ -154,8 +155,8 @@ erhalten.
|
||||
```
|
||||
|
||||
**Geraete-Tags (live):** `kallilabcore` = `tag:server`; `baerchen-1` + `iphone-14`
|
||||
= `tag:operator`. Alte Nodes `kallilab-core` und `baerchen` sind nicht mehr im
|
||||
aktuellen Tailnet-Status sichtbar.
|
||||
= `tag:operator`; `kallilab-core` (Docker) + alter `baerchen` bewusst untagged ->
|
||||
isoliert.
|
||||
|
||||
**Rollout-Protokoll 2026-06-06 (lockout-sicher, je Schritt read-only verifiziert):**
|
||||
|
||||
@@ -192,10 +193,10 @@ ist die vollstaendige Wahrheit.
|
||||
- Familien-Dienste/Ports konkretisieren — erst wenn ein reales Familiengeraet dazukommt.
|
||||
- **Zwei-Tailscale-Konsolidierung: ERLEDIGT 2026-06-06** — redundanter Docker-Stack
|
||||
abgebaut, nur noch die native Plugin-Instanz `kallilabcore` (Subnet-Router) aktiv.
|
||||
- **Tailnet-Konsole/Altstate aufraeumen: ERLEDIGT 2026-06-17** — Node-Eintraege
|
||||
`kallilab-core` und alter Offline-`baerchen` sind im aktuellen Tailnet-Status
|
||||
nicht mehr sichtbar; State-Pfad `/mnt/user/appdata/tailscale` vom entfernten
|
||||
Docker-Stack liegt unter `_archive/tailscale-removed-2026-06-06/`.
|
||||
- **Tailnet-Konsole aufraeumen: ERLEDIGT 2026-06-06** — Node-Eintraege `kallilab-core`
|
||||
und alter Offline-`baerchen` aus der Admin-Konsole entfernt.
|
||||
- State-Pfad `/mnt/user/appdata/tailscale` (vom entfernten Docker-Stack) bei
|
||||
Gelegenheit nach `_archive/tailscale-removed-2026-06-06/` (kein Sofort-Loeschen).
|
||||
- Optionaler Off-LAN-Routentest: von einem Operator-Geraet im Mobilfunk
|
||||
(nicht im Heim-LAN) ein LAN-Ziel ueber `192.168.178.0/24` erreichen, um die
|
||||
Subnet-Route end-to-end zu bestaetigen (im Heim-LAN nicht sauber isolierbar).
|
||||
|
||||
@@ -29,7 +29,7 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
|
||||
| Unraid OS Flash | Borg-Artefakt + optional Unraid Connect | `/boot/config` aus `unraid-flash-config.tar.gz` | `unraid-flash-config.tar.gz`, `.sha256`, Manifest | enthaelt sensible Host-Konfiguration, wie Secret-Material behandeln | Unraid USB Flash Creator / neuer Boot-Stick | Unraid bootet, Array-Zuordnung und Shares sind sichtbar |
|
||||
| Traefik | Share / Borg | `/mnt/user/appdata/traefik`, besonders `dynamic/`, `letsencrypt`, `secrets` | keine eigene DB | `cloudflare_dns_api_token` | `frontend_net`, `backend_net` | `https://traefik.kaleschke.info` erreichbar, Dashboard ueber Authelia |
|
||||
| AdGuard Home | Share / Borg | `/mnt/user/appdata/adguard/conf` | keine | keine zusaetzlichen Repo-Secrets dokumentiert | `dns_net`, `frontend_net` | DNS-Aufloesung funktioniert; Restore-Smoke am 2026-06-06 erfolgreich |
|
||||
| Tailscale | Flash-Backup (funktional) | **Funktional: `/boot/config/plugins/tailscale/state`** (native Unraid-Plugin-Instanz `kallilabcore`, Subnet-Router, im Flash-Backup gesichert). Der frueher genannte Pfad `/mnt/user/appdata/tailscale` gehoerte zum entfernten userspace-only Docker-Stack `kallilab-core` und ist seit 2026-06-17 nach `/mnt/user/appdata/_archive/tailscale-removed-2026-06-06/` verschoben; nicht mehr als aktive Restore-Quelle behandeln | keine | Tailscale-State im Flash-Backup; Archivpfad nur fuer Altanalyse | Host-Netz | Tailscale verbunden, Subnet-Route `192.168.178.0/24` aktiv |
|
||||
| Tailscale | Flash-Backup (funktional) / Share | **Funktional: `/boot/config/plugins/tailscale/state`** (native Unraid-Plugin-Instanz `kallilabcore`, Subnet-Router, im Flash-Backup gesichert). Der frueher hier genannte Pfad `/mnt/user/appdata/tailscale` gehoert zum **userspace-only Docker-Stack** `kallilab-core` (redundant, Abbau geplant — siehe `docs/NETWORK_INVENTORY.md`) | keine | Tailscale-State im jeweiligen State-Pfad | Host-Netz | Tailscale verbunden, Subnet-Route `192.168.178.0/24` aktiv |
|
||||
| PostgreSQL 18 | Share + Dumps | `/mnt/user/appdata/postgresql18` (archivierter Rollback-Altstand: `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/postgresql17`) | `postgresql17-globals.sql`, `postgresql17-mailarchiver.dump`, `postgresql17-paperless.dump`, optional `postgresql17-authelia.dump` | `postgres_password.txt`, App-Rollen-Passwoerter aus den jeweiligen Stack-ENV/Secret-Dateien | `backend_net` | DB startet, Ziel-Datenbanken vorhanden; `SHOW data_checksums` ist `on` |
|
||||
| Redis 8 | Share / Host | `/mnt/user/appdata/redis`; Rollback-Backup unter `/mnt/user/backups/borg/dumps/latest/shared-redis-pre-redis8-<ts>` | RDB/AOF-Dateien im Datenpfad | `redis_password.txt` | `backend_net` | Redis startet, `redis_version` ist 8.x, Apps verbinden sich; Restore-Smoke am 2026-06-06 erfolgreich |
|
||||
| Authelia | Borg | `/mnt/user/appdata/authelia/config`, `/mnt/user/appdata/secrets/*authelia*` | Shared PostgreSQL 18, optional Dump `postgresql17-authelia.dump` | JWT/Session/Storage/Postgres-/SMTP-Secret-Dateien | PostgreSQL 18, Traefik, GMX SMTP | Login-Seite und ForwardAuth funktionieren; SMTP-Notifier startet; aktive Sessions werden nach Restart neu aufgebaut; Restore-Smoke am 2026-06-03 erfolgreich: Config aus Borg, minimale Test-Config, frisches Test-Postgres, HTTP `/api/health` 200, Report `/mnt/user/backups/restore-reports/authelia-2026-06-03.md` |
|
||||
@@ -52,7 +52,7 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
|
||||
|
||||
| Dienst | Fuehrende Quelle | Datei-Restore | Dump / DB | Secrets / ENV | Abhaengigkeiten | Smoke-Test |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Paperless-ngx | Borg + Dumps | `/mnt/user/appdata/paperless-ngx/data`, `/mnt/user/documents/paperless`, `/mnt/user/documents/paperless/export`, `/mnt/user/documents/scans_inbox` | `postgresql17-paperless.dump` | `PAPERLESS_DBPASS`, `PAPERLESS_REDIS`, `PAPERLESS_OIDC_SECRET`, `borg_repo_passphrase.txt` fuer Restore-Tests | PostgreSQL 18, Redis, Traefik, Authelia OIDC | Web-UI startet, Dokumente vorhanden; Restore-Test am 2026-05-31 erfolgreich: Borg-Archiv `Tägliche-Sicherung-2026-05-31T04:30:13.181`, isolierter PostgreSQL-18-/Redis-8-Testpfad, HTTP `200`, `32` Dokumente im Test-DB-Check, Report `/mnt/user/backups/restore-reports/paperless-2026-05-31.md`; OIDC-Secret am 2026-06-17 verdrahtet, lokaler Login bleibt Fallback |
|
||||
| Paperless-ngx | Borg + Dumps | `/mnt/user/appdata/paperless-ngx/data`, `/mnt/user/documents/paperless`, `/mnt/user/documents/paperless/export`, `/mnt/user/documents/scans_inbox` | `postgresql17-paperless.dump` | `PAPERLESS_DBPASS`, `PAPERLESS_REDIS`, `borg_repo_passphrase.txt` fuer Restore-Tests | PostgreSQL 18, Redis, Traefik | Web-UI startet, Dokumente vorhanden; Restore-Test am 2026-05-31 erfolgreich: Borg-Archiv `Tägliche-Sicherung-2026-05-31T04:30:13.181`, isolierter PostgreSQL-18-/Redis-8-Testpfad, HTTP `200`, `32` Dokumente im Test-DB-Check, Report `/mnt/user/backups/restore-reports/paperless-2026-05-31.md` |
|
||||
| Mealie | Borg + Dump | `/mnt/user/appdata/mealie/data`, `/mnt/user/appdata/mealie/postgres18` (archivierter Rollback-Altstand: `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/mealie-postgres17`) | `mealie.dump` | `mealie_postgres_password.txt` | `mealie-postgres`, Traefik | UI startet, Rezepte vorhanden |
|
||||
| Immich | Borg + Dump | `/mnt/user/photos/immich`, `/mnt/user/photos/family_archive`, `/mnt/user/appdata/immich_postgres_vectorchord`; archivierter Rollback-Altstand: `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/immich-postgres-pgvecto-rs` | `immich.dump`; nach VectorChord braucht ein Restore ein Postgres-Image mit VectorChord | `IMMICH_DB_PASSWORD`, `immich_postgres_password.txt`, `borg_repo_passphrase.txt` fuer Restore-Tests | `immich_postgres`, `immich_redis`, Traefik | DB- und UI-Smoke gegen produktives Borg-Archiv am 2026-05-27 erfolgreich validiert; VectorChord-Migration am 2026-05-31: `11977` Assets, `11107` Smart-Search-Zeilen, `7092` Face-Search-Zeilen, `vchord 0.4.3`, `vector 0.8.1`, HTTP/API-Smoke 200. Voll-Restore der Foto-Dateien bleibt separater DR-Drill |
|
||||
| Mail-Archiver | Borg + Shared Dump | `/mnt/user/appdata/mailarchiver/data-protection-keys` | `postgresql17-mailarchiver.dump` | `MAILARCHIVER_DB_CONNECTION`, `MAILARCHIVER_AUTH_PASSWORD` | PostgreSQL 18, Traefik, Authelia | Authelia-Weiterleitung greift; nach Login startet die Web-UI und das Archiv laesst sich oeffnen |
|
||||
@@ -60,8 +60,8 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
|
||||
| Glance | Git / Borg-Repo | Repo-Konfiguration unter `ops/glance/config/glance.yml`; keine kritische Datenpersistenz | keine | `GLANCE_IMMICH_API_KEY`, `GLANCE_ADGUARD_USERNAME`, `GLANCE_ADGUARD_PASSWORD`, `GLANCE_SPEEDTEST_API_KEY` | Traefik, Authelia, optional interne API-Ziele | Dashboard startet, Widgets laden, Docker-Status laeuft nur ueber `glance-docker-socket-proxy` |
|
||||
| ntfy | Borg / Share | `/mnt/user/appdata/ntfy` | keine | keine besonderen Secret-Dateien dokumentiert | Traefik | UI und Push-Endpunkt erreichbar |
|
||||
| Paperless-GPT | Borg / Share | `/mnt/user/appdata/paperless-gpt` | keine eigene DB | `PAPERLESS_API_TOKEN`, `OPENAI_API_KEY` | Traefik, Paperless, OpenAI API | UI startet, Konfiguration vorhanden; LLM-Provider zeigt `openai` / `gpt-5.4-mini` |
|
||||
| Home Assistant | Borg + HA-native Backups + Fachrepo | `/mnt/user/appdata/homeassistant` inkl. `.storage`, `secrets.yaml`, `trusted_proxies.yaml`, `custom_components` (HACS, `solaredge_modbus_multi`); Fach-YAML aus `/mnt/user/services/smart-home-kalli/home-assistant` | HA-native Backup-Artefakte unter `/mnt/user/appdata/homeassistant/backups`; erstes Artefakt 2026-06-13 erzeugt und tar-lesbar (`backup.json`, `homeassistant.tar.gz`); Backup nach SolarEdge-Integration: `Custom_backup_2026.6.1_2026-06-13_14.59_48645373.tar`; Backup nach Energy-Dashboard-Konfiguration: `Custom_backup_2026.6.1_2026-06-13_15.59_25670583.tar`; keine externe DB in Phase 1 | HA-Secrets in `secrets.yaml`, Integrations-Tokens in `.storage`, MQTT-Credentials, Agent-API-Tokens als Host-Secrets `ha_token_codex`/`ha_token_claude` (nur mit erhaltenem `.storage`-Auth-State nutzbar), spaeter Tibber/InfluxDB-Tokens | Traefik, `frontend_net`, `smarthome_net`, Mosquitto, Fachrepo-Clone, SolarEdge-Wechselrichter `192.168.178.111:1502` | Restore-Test am 2026-06-13 erfolgreich: HA-native Backup + Mosquitto-Appdata + Fachrepo-Clone isoliert gestartet, HA HTTP/API/check_config gruen; produktiv danach HA-MQTT-Config-Entry `smarthome-mosquitto` geladen, SolarEdge Local `solaredge_modbus_multi` loaded mit 68 Entitaeten und Energy Dashboard fuer Netz/PV/Speicher per `energy/validate` ohne Issues; Report `/mnt/user/backups/restore-reports/homeassistant-2026-06-13.md` |
|
||||
| Smart-Home MQTT / Mosquitto | Borg / Share | `/mnt/user/appdata/mosquitto/config`, `/mnt/user/appdata/mosquitto/data`, `/mnt/user/appdata/mosquitto/log` | Mosquitto persistiert retained messages/subscriptions dateibasiert | `passwordfile`, `aclfile`, spaeter per-Device-User | `smarthome_net`, Home Assistant, spaeter ESPHome/Zigbee2MQTT | Restore-Test am 2026-06-13 erfolgreich: authentifizierter Publish/Subscribe-Smoke mit `homeassistant`-User und retained Topic nach Broker-Restart gruen; produktiv verbindet sich HA als User `homeassistant` |
|
||||
| Home Assistant | Borg + HA-native Backups + Fachrepo | `/mnt/user/appdata/homeassistant` inkl. `.storage`, `secrets.yaml`, `trusted_proxies.yaml`; Fach-YAML aus `/mnt/user/services/smart-home-kalli/home-assistant` | HA-native Backup-Artefakte unter `/mnt/user/appdata/homeassistant/backups` falls vorhanden; keine externe DB in Phase 1 | HA-Secrets in `secrets.yaml`, Integrations-Tokens in `.storage`, MQTT-Credentials, spaeter InfluxDB-Token | Traefik, `frontend_net`, `smarthome_net`, Mosquitto, Fachrepo-Clone | `https://home.kaleschke.info` zeigt Login, MQTT-Integration verbindet sich, `backup.create` funktioniert, Energy-Dashboard-Konfiguration bleibt erhalten |
|
||||
| Smart-Home MQTT / Mosquitto | Borg / Share | `/mnt/user/appdata/mosquitto/config`, `/mnt/user/appdata/mosquitto/data`, `/mnt/user/appdata/mosquitto/log` | Mosquitto persistiert retained messages/subscriptions dateibasiert | `passwordfile`, `aclfile`, spaeter per-Device-User | `smarthome_net`, Home Assistant, spaeter ESPHome/Zigbee2MQTT | Container startet, HA kann sich authentifiziert verbinden, retained Testtopic bleibt nach Restart erhalten |
|
||||
| Smart-Home Fachrepo | Gitea + Borg-Repo-Clone | `/mnt/user/services/smart-home-kalli` | keine | keine echten Secrets im Repo; `secrets-template/` nur Beispiele | Gitea, Home Assistant Mounts | `git status` sauber, HA liest `configuration.yaml` und `packages/` aus dem Clone |
|
||||
|
||||
---
|
||||
@@ -165,7 +165,6 @@ Stand 2026-06-06. Pro Dienst auf einen Blick: Wurde der Restore schon einmal rea
|
||||
| Borg UI | 3 | - | rebuildbar | - |
|
||||
| Filebrowser | 3 | - | rebuildbar | - |
|
||||
| baerchen Windows Image | Workstation | 2026-06-06 | Full-Backup geschrieben; Recovery-USB-Boot, SMB-Mount und Restore-Point-Sichtpruefung erfolgreich; vor echtem Restore abgebrochen | nach Image-Aenderungen oder quartalsweise |
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+4
-10
@@ -25,7 +25,6 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
|
||||
| mealie-postgres | DB Password | `/mnt/user/appdata/secrets/mealie_postgres_password.txt` -> `POSTGRES_PASSWORD_FILE` | aktiv |
|
||||
| Paperless-ngx | DB Password | Stack ENV `${PAPERLESS_DBPASS}` | aktiv |
|
||||
| Paperless-ngx | Redis URL | Stack ENV `${PAPERLESS_REDIS}` | aktiv |
|
||||
| Paperless OIDC (Authelia) | Client Secret | Stack ENV `${PAPERLESS_OIDC_SECRET}` in `/mnt/user/services/stacks/paperless/apps/paperless/.env` (Komodo-Stack-ENV); pbkdf2-Hash im Authelia-Host-Config-Client `paperless` (kein Wert im Repo) | aktiv (2026-06-17) |
|
||||
| Paperless-GPT | OpenAI API Key | Stack ENV `${OPENAI_API_KEY}`; nicht im Repo, nicht in Logs | aktiv |
|
||||
| code-server | Passwort | `/mnt/user/appdata/code-server/secrets/password` -> `FILE__PASSWORD` | aktiv |
|
||||
| Filebrowser | Admin Password | `/mnt/user/appdata/secrets/filebrowser_admin_password.txt` -> initialisierte SQLite-DB | aktiv |
|
||||
@@ -41,7 +40,7 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
|
||||
| Komodo Mongo | Root Password | `/mnt/user/appdata/secrets/komodo_mongo_password.txt` -> `MONGO_INITDB_ROOT_PASSWORD_FILE` | aktiv |
|
||||
| Komodo Core | App Secrets | Stack ENV `${KOMODO_SECRET_KEY}`, `${KOMODO_WEBHOOK_SECRET}`, `${KOMODO_JWT_SECRET}`, `${KOMODO_MONGO_PASSWORD}`, `${KOMODO_PERIPHERY_PASSKEY}` | aktiv |
|
||||
| Gitea Push Mirror | GitHub fine-grained PAT fuer `michaelkaleschke-spec/homelab-infra` | Gitea Repository Mirror Settings, persistent in `/mnt/user/services/gitea/data`; kein Datei-Secret im Repo | aktiv |
|
||||
| Glance | Community Widget API Tokens | Stack ENV `${GLANCE_IMMICH_API_KEY}`, `${GLANCE_ADGUARD_USERNAME}`, `${GLANCE_ADGUARD_PASSWORD}`, `${GLANCE_SPEEDTEST_API_KEY}`, `${GLANCE_KOMODO_API_KEY}`, `${GLANCE_KOMODO_API_SECRET}`, `${GLANCE_GITEA_TOKEN}`, `${GLANCE_PAPERLESS_TOKEN}`, `${GLANCE_MEALIE_TOKEN}` (alle read-only anlegen), `${GLANCE_HA_TOKEN}` (HA Long-Lived Access Token; Glance nutzt nur `GET /api/states`) | aktiv |
|
||||
| Glance | Community Widget API Tokens | Stack ENV `${GLANCE_IMMICH_API_KEY}`, `${GLANCE_ADGUARD_USERNAME}`, `${GLANCE_ADGUARD_PASSWORD}`, `${GLANCE_SPEEDTEST_API_KEY}`, `${GLANCE_KOMODO_API_KEY}`, `${GLANCE_KOMODO_API_SECRET}`, `${GLANCE_GITEA_TOKEN}`, `${GLANCE_PAPERLESS_TOKEN}`, `${GLANCE_MEALIE_TOKEN}` (alle read-only anlegen) | aktiv |
|
||||
| speedtest-tracker | App Key / Admin-Zugang | Stack ENV `${APP_KEY}`, `${ADMIN_PASSWORD}` | aktiv |
|
||||
| Nextcloud | Admin User | `/mnt/user/appdata/secrets/nextcloud_admin_user.txt` -> `NEXTCLOUD_ADMIN_USER_FILE` | neu |
|
||||
| Nextcloud | Admin Password | `/mnt/user/appdata/secrets/nextcloud_admin_password.txt` -> `NEXTCLOUD_ADMIN_PASSWORD_FILE` | neu |
|
||||
@@ -52,8 +51,6 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
|
||||
| Hermes Agent | Provider-Keys, Bot-Tokens, API-Server-Key | `/mnt/user/appdata/hermes-agent/data/.env` | VM-seitig offen |
|
||||
| Hermes Agent | SSH-Runner Private Key | `/mnt/user/appdata/secrets/hermes_runner_id_ed25519` -> `/root/.ssh/id_ed25519` | VM-seitig offen |
|
||||
| InfluxDB 3 Core | Admin Token JSON | `/mnt/user/appdata/secrets/influxdb3_admin_token.json` -> Docker Secret `/run/secrets/influxdb3_admin_token` | aktiv |
|
||||
| Home Assistant -> InfluxDB | Write Token (Wetterarchiv) | `/mnt/user/appdata/secrets/ha_influxdb_token` + HA `/config/secrets.yaml` Key `influxdb_ha_token`; InfluxDB-3-Core Named-Admin-Token (voller Zugriff, da Core keine Scopes kennt) | aktiv |
|
||||
| Home Assistant | Agent API Tokens | `/mnt/user/appdata/secrets/ha_token_claude`, `ha_token_codex` (Long-Lived Access Tokens fuer read-only API-Zugriff durch KI-Agenten) | aktiv |
|
||||
| Monitoring Grafana | Admin Password | `/mnt/user/appdata/secrets/monitoring_grafana_admin_password.txt` -> Docker Secret `/run/secrets/monitoring_grafana_admin_password` -> `GF_SECURITY_ADMIN_PASSWORD__FILE` | aktiv |
|
||||
| Monitoring Grafana -> InfluxDB | Datasource Token | `/mnt/user/appdata/secrets/monitoring_grafana_influxdb_token.txt` -> Docker Secret `/run/secrets/monitoring_grafana_influxdb_token` | aktiv |
|
||||
| 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) |
|
||||
@@ -101,9 +98,6 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
|
||||
|-- redis_password.txt
|
||||
|-- borg_repo_passphrase.txt
|
||||
|-- influxdb3_admin_token.json
|
||||
|-- ha_influxdb_token
|
||||
|-- ha_token_claude
|
||||
|-- ha_token_codex
|
||||
|-- filebrowser_admin_password.txt
|
||||
|-- homelab_smtp_password.txt
|
||||
`-- vaultwarden_admin_token.txt
|
||||
@@ -117,7 +111,7 @@ Weitere dokumentierte Secret-Pfade:
|
||||
- Borg UI verwaltet Session-Secret, Admin-Login, SSH-Keys und Repo-Credentials in seiner persistenten `/data`-Struktur. Diese Daten liegen nicht im Git, muessen aber gesichert werden.
|
||||
- Die Borg-Repo-Passphrase liegt zusaetzlich als Host-Secret-Datei fuer Restore-Tests und Notfallzugriff vor. Der Wert ist laut Operator-Bestaetigung vom 2026-05-26 offline gesichert; Ablageort und Wert werden nicht im Repo dokumentiert.
|
||||
- Gitea verwaltet den GitHub-Push-Mirror-PAT in den Repository-Mirror-Settings. Der Wert wird nicht dokumentiert und nicht in Dateien unter `docs/` oder `core/gitea/` geschrieben.
|
||||
- `paperless-ngx` ist eine bewusste Ausnahme: DB-Passwort, Redis-URL und OIDC-Client-Secret bleiben aktuell als Komodo Stack Environment Variables hinterlegt, um den stabil laufenden Produktionsstand nicht fuer eine reine Secret-Mechanik-Migration zu riskieren.
|
||||
- `paperless-ngx` ist eine bewusste Ausnahme: DB-Passwort und Redis-URL bleiben aktuell als Komodo Stack Environment Variables hinterlegt, um den stabil laufenden Produktionsstand nicht fuer eine reine Secret-Mechanik-Migration zu riskieren.
|
||||
- `baerchen` nutzt fuer das Veeam-Backup aktuell den bestehenden SMB-User
|
||||
`micha`. Ein dedizierter SMB-User `veeam-baerchen` ist nur ein spaeteres
|
||||
Hardening-Ziel, solange keine Unraid-User-/Share-Aenderungen gewuenscht sind.
|
||||
@@ -140,14 +134,14 @@ Einige Secrets liegen bewusst nur als Komodo Stack Environment Variables vor, we
|
||||
|
||||
| Stack | Stack-ENV-Variablen | Restore-Quelle (Reihenfolge) | Folgen bei Verlust aller Quellen |
|
||||
|---|---|---|---|
|
||||
| `paperless-ngx` | `PAPERLESS_DBPASS`, `PAPERLESS_REDIS`, `PAPERLESS_OIDC_SECRET` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | App-DB ist im Postgres-Cluster, Passwort muss in Postgres und Stack-ENV synchron neu gesetzt werden; Redis-URL ist deterministisch rekonstruierbar (Host, Port, Passwort), wenn Redis-Passwort-Datei vorliegt; OIDC-Client-Secret kann mit passendem Authelia-Client neu rotiert werden |
|
||||
| `paperless-ngx` | `PAPERLESS_DBPASS`, `PAPERLESS_REDIS` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | App-DB ist im Postgres-Cluster, Passwort muss in Postgres und Stack-ENV synchron neu gesetzt werden; Redis-URL ist deterministisch rekonstruierbar (Host, Port, Passwort), wenn Redis-Passwort-Datei vorliegt |
|
||||
| `paperless-gpt` | `PAPERLESS_API_TOKEN`, `OPENAI_API_KEY` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | Paperless-Token kann in Paperless neu erzeugt werden; OpenAI-Key muss im OpenAI-Projekt rotiert/neu erstellt werden |
|
||||
| `immich-server` | `IMMICH_DB_PASSWORD` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | analog Paperless: Postgres-User-Passwort in `immich_postgres` und Stack-ENV gemeinsam zuruecksetzen |
|
||||
| `mail-archiver` | `MAILARCHIVER_DB_CONNECTION`, `MAILARCHIVER_AUTH_PASSWORD` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | DB-Connection-String enthaelt Postgres-Pass; App-Auth-Password fuer Web-UI |
|
||||
| `speedtest-tracker` | `APP_KEY`, `ADMIN_PASSWORD` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | `APP_KEY` ist verschluesselungsrelevant; bei echtem Verlust App-State frisch initialisieren |
|
||||
| `komodo-core` | `KOMODO_SECRET_KEY`, `KOMODO_WEBHOOK_SECRET`, `KOMODO_JWT_SECRET`, `KOMODO_MONGO_PASSWORD`, `KOMODO_PERIPHERY_PASSKEY` | Vaultwarden -> externe Notiz (Henne-Ei: Komodo-Mongo-Dump ist hier **nicht** Restore-Quelle, weil Komodo dafuer schon laufen muesste) | siehe `docs/SERVICES_RECOVERY.md` Komodo-Bootstrap; ohne diese Werte ist der Self-Stack nicht reproduzierbar |
|
||||
| `hermes-agent` | `HERMES_DASHBOARD_HOST` plus Provider-/API-/Home-Assistant-Tokens in Host-`.env` | Vaultwarden -> externe Notiz | Stack ist aktuell geparkt (Review 2026-07-25); ohne Werte bleibt der Stack deaktiviert, kein Schaden am Rest |
|
||||
| `glance` | `GLANCE_IMMICH_API_KEY`, `GLANCE_ADGUARD_USERNAME`, `GLANCE_ADGUARD_PASSWORD`, `GLANCE_SPEEDTEST_API_KEY`, `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 |
|
||||
| `glance` | `GLANCE_IMMICH_API_KEY`, `GLANCE_ADGUARD_USERNAME`, `GLANCE_ADGUARD_PASSWORD`, `GLANCE_SPEEDTEST_API_KEY`, `GLANCE_KOMODO_API_KEY`, `GLANCE_KOMODO_API_SECRET`, `GLANCE_GITEA_TOKEN`, `GLANCE_PAPERLESS_TOKEN`, `GLANCE_MEALIE_TOKEN` | Provider-UIs (Immich, AdGuard, Speedtest-Tracker, Komodo, Gitea, Paperless, Mealie) neu erzeugen | rebuildbar; alle read-only; Widgets bleiben leer bis Tokens neu erzeugt sind, kein kritischer Datentopf |
|
||||
| `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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Service Catalog
|
||||
|
||||
Stand: 2026-06-13
|
||||
Stand: 2026-06-02
|
||||
|
||||
Dieser Katalog beschreibt produktive und repo-vorbereitete Dienste aus Sicht von Betrieb, Restore und KI-Kontext. Er basiert auf dem Repo-Sollzustand. Vor produktiven Eingriffen immer den Live-Zustand in Komodo/Docker pruefen.
|
||||
|
||||
@@ -12,7 +12,7 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `traefik` | zentraler Reverse Proxy, TLS, Docker-Label-Routing | `traefik/docker-compose.yml`, `traefik/dynamic/*` | `https://traefik.kaleschke.info` | Docker socket, Cloudflare DNS API, `frontend_net`, `backend_net` | `/mnt/user/appdata/traefik/dynamic`, `/mnt/user/appdata/traefik/letsencrypt` | Tier 1, Share/Borg | ja, eigene Dashboard-Route mit Authelia | Host-Ports 80/443 sind zentrale Ausnahme; dynamic configs werden nicht automatisch von Komodo deployed |
|
||||
| `adguard` | DNS-Server / LAN DNS | `host-services/Adguard/docker-compose.yml` | LAN-Port `53`, Admin `100.80.98.33:8082` | `dns_net`, `frontend_net`, Unbound | `/mnt/user/appdata/adguard/conf`, `/mnt/user/appdata/adguard/work` | Tier 1, config relevant | nein | Direkter DNS-Port 53 bleibt; Admin-Port ist bewusst ohne Traefik/2FA, aber auf Tailscale-IP begrenzt (Operator-Entscheidung 2026-05-26) |
|
||||
| `unbound` | DNSSEC-validierender Forwarding-Resolver fuer AdGuard | `apps/unbound/docker-compose.yml` | intern | `dns_net` | `/mnt/user/appdata/unbound/config` | rebuildbar / config relevant | nein | intern isoliert; forwardet per DoT zu Cloudflare, keine Root-Rekursion |
|
||||
| `unbound` | Upstream DNS Resolver fuer AdGuard | `apps/unbound/docker-compose.yml` | intern | `dns_net` | `/mnt/user/appdata/unbound/config` | rebuildbar / config relevant | nein | intern isoliert |
|
||||
| `tailscale` | VPN/Remote-Zugang, Subnet-Router | **Natives Unraid-Plugin** `tailscale.plg` (nicht repo-/Komodo-verwaltet) | Tailscale | Host-Netz (`tailscale1`) | `/boot/config/plugins/tailscale/state` (im Flash-Backup) | Tier 1, State relevant | nein | Subnet-Router `192.168.178.0/24`; redundanter Docker-Stack `host-services/tailscale/` am 2026-06-06 entfernt |
|
||||
| `gitea` | Git-Server / origin fuer GitOps | `core/gitea/docker-compose.yml` | `https://git.kaleschke.info`, SSH `222` | Traefik, `frontend_net`, externe DNS-Resolver fuer GitHub-Push-Mirror | `/mnt/user/services/gitea/data` | Tier 1, `gitea.sqlite.dump` + Share; privater GitHub-Push-Mirror fuer Repo-Bootstrap | ja | SSH-Port 222 direkte Host-Port-Ausnahme; Push-Mirror nach `michaelkaleschke-spec/homelab-infra` reduziert das DR-Bootstrap-Risiko |
|
||||
|
||||
@@ -35,12 +35,12 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und
|
||||
|
||||
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `paperless-ngx` | Dokumentenmanagement | `apps/paperless/docker-compose.yml` | `https://paperless.kaleschke.info` | PostgreSQL 18, Redis 8, Traefik, Authelia OIDC | `/mnt/user/appdata/paperless-ngx/data`, `/mnt/user/documents/paperless`, `/mnt/user/documents/scans_inbox` | Tier 2, Borg + `postgresql17-paperless.dump` | ja + Authelia | DB/Redis/OIDC Secrets bleiben bewusst Stack ENV; OIDC ist additiv via Authelia konfiguriert, lokaler Login bleibt Fallback; Dump-Dateiname behaelt den historischen Cluster-Namen |
|
||||
| `paperless-ngx` | Dokumentenmanagement | `apps/paperless/docker-compose.yml` | `https://paperless.kaleschke.info` | PostgreSQL 18, Redis 8, Traefik | `/mnt/user/appdata/paperless-ngx/data`, `/mnt/user/documents/paperless`, `/mnt/user/documents/scans_inbox` | Tier 2, Borg + `postgresql17-paperless.dump` | ja | DB/Redis Secrets bleiben bewusst Stack ENV; Dump-Dateiname behaelt den historischen Cluster-Namen |
|
||||
| `paperless-gpt` | KI-Ergaenzung fuer Paperless | `apps/paperless-gpt/docker-compose.yml` | `https://paperless-gpt.kaleschke.info` | Paperless API, OpenAI API, Traefik | `/mnt/user/appdata/paperless-gpt/data`, `/mnt/user/appdata/paperless-gpt/prompts` | Tier 2 | ja + Authelia | `PAPERLESS_API_TOKEN` und `OPENAI_API_KEY` als Stack ENV; LLM und Vision-OCR laufen ueber `gpt-5.4-mini`, kein Zugriff mehr auf lokale Ollama-VM. **Behalten-Entscheidung 2026-05-28:** Container bleibt aktiv, auch wenn aktuell keine Traefik-Zugriffe in der Woche; Ablouseplanung erst mit Paperless-NGX 3.0 (eigene KI-Features erwartet) - dann neu bewerten. |
|
||||
| `immich_server` | Foto-/Video-App | `apps/immich/docker-compose.yml` | `https://immich.kaleschke.info` | Immich Postgres, Immich Redis, ML, Traefik | `/mnt/user/photos/immich`, `/mnt/user/photos/family_archive` | Tier 2, Borg + `immich.dump` | ja | native App-Auth; externes Fotoarchiv gemountet |
|
||||
| `immich_postgres` | Immich-Datenbank | `apps/immich/docker-compose.yml` | intern | `immich_default` | `/mnt/user/appdata/immich_postgres_vectorchord`, archivierter Rollback-Altstand `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/immich-postgres-pgvecto-rs`, `immich_postgres_password.txt` | Dump `immich.dump`; Restore braucht ein Image mit VectorChord/pgvector | nein | PG14 bleibt bewusst; Immich-DB-Image `ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0`; nie ins `frontend_net` |
|
||||
| `immich_redis` | Immich Cache | `apps/immich/docker-compose.yml` | intern | `immich_default` | kein kritischer Pfad dokumentiert | rebuildbar | nein | Redis 8.8; Architektur nennt anonymes Volume -> named volume als offenes Thema |
|
||||
| `immich_machine_learning` | Immich ML | `apps/immich/docker-compose.yml` | intern | `immich_default`, `immich_egress` | `model-cache` | rebuildbar | nein | keine Traefik-Route; `immich_egress` (nicht-internal) nur fuer Modell-Download zu huggingface, sonst scheitert Smart Search/Gesichtserkennung an DNS |
|
||||
| `immich_machine_learning` | Immich ML | `apps/immich/docker-compose.yml` | intern | `immich_default` | `model-cache` | rebuildbar | nein | intern-only |
|
||||
| `mealie` | Rezeptverwaltung | `apps/mealie/docker-compose.yml` | `https://mealie.kaleschke.info` | `mealie-postgres`, Traefik | `/mnt/user/appdata/mealie/data` | Tier 2, Borg + `mealie.dump` | ja | App + DB in internem Netz getrennt |
|
||||
| `mealie-postgres` | Mealie-Datenbank | `apps/mealie/docker-compose.yml` | intern | `mealie_internal` | `/mnt/user/appdata/mealie/postgres18`, archivierter Rollback-Altstand `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/mealie-postgres17`, `mealie_postgres_password.txt` | Dump `mealie.dump` | nein | interne DB; PostgreSQL 18 |
|
||||
| `mail-archiver` | Mail-Archivierung | `apps/mail-archiver/docker-compose.yml` | `https://mail.kaleschke.info` | PostgreSQL 18, Internet/IMAP, Traefik, Authelia | `/mnt/user/appdata/mailarchiver/data-protection-keys` | Tier 2, `postgresql17-mailarchiver.dump` | ja + Authelia | Hybrid-Dienst: `frontend_net` fuer Internet, `backend_net` fuer DB; App-eigene Auth bleibt zusaetzliche Schutzschicht; Dump-Dateiname behaelt den historischen Cluster-Namen |
|
||||
@@ -84,8 +84,8 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und
|
||||
|
||||
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `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. |
|
||||
| `homeassistant` | Zentrale Smart-Home-Steuerung, Energy Dashboard, Integrations-Hub | Runtime: `smart-home/docker-compose.yml`; Fachkonfiguration: Repo `smart-home-kalli` | `https://home.kaleschke.info`; kein direkter Host-Port in Phase 1 | Traefik, `frontend_net`, `smarthome_net`, `smarthome-mosquitto`, Fachrepo unter `/mnt/user/services/smart-home-kalli` | `/mnt/user/appdata/homeassistant` inkl. `.storage`, `secrets.yaml`, `trusted_proxies.yaml`; YAML-Fachdateien read-only aus `/mnt/user/services/smart-home-kalli/home-assistant` | Tier 2, Borg + HA-native Backups; Restore-Probe Pflicht vor produktiven Energie-Automationen | ja, native HA-Auth | HA Container statt HAOS-VM; keine Add-ons, keine Supervised-Installation. `configuration.yaml` kommt aus dem Fachrepo, `.storage` wird nicht versioniert. `http.use_x_forwarded_for` und `trusted_proxies` muessen zur Traefik-Route passen. Ecowitt-HTTP bleibt Phase-2-Entscheidung wegen globalem Traefik-Redirect. |
|
||||
| `smarthome-mosquitto` | MQTT-Broker fuer HA, spaeter ESPHome und Zigbee2MQTT | `smart-home/docker-compose.yml`, `smart-home/mosquitto/config/mosquitto.conf` | intern `smarthome_net:1883`; kein LAN-Port in Phase 1 | `smarthome_net`, Passwort-/ACL-Dateien in Appdata | `/mnt/user/appdata/mosquitto/config`, `/mnt/user/appdata/mosquitto/data`, `/mnt/user/appdata/mosquitto/log` | Tier 2, Borg; Passwortdatei, ACLs und persistente Broker-Daten relevant | nein | LAN-Port `1883` erst in ESPHome-Phase mit ACLs und per-Device-Usern. |
|
||||
|
||||
## Host Operations
|
||||
|
||||
|
||||
+3
-9
@@ -124,20 +124,14 @@ Pflichtschritte beim Anlegen:
|
||||
1. Stack in Komodo aus Gitea anlegen
|
||||
2. `webhook_enabled` in Komodo aktivieren
|
||||
3. passenden Gitea-Webhook fuer die aktuelle Stack-ID anlegen
|
||||
4. Branch-Filter im Gitea-Hook auf den produktiven Branch setzen, aktuell `master`
|
||||
5. Gitea-Hook gegen `http://komodo-core:9120/listener/github/stack/<stack-id>/deploy` pruefen
|
||||
6. einen Push oder Test-Delivery ausloesen und `last_status`/Komodo-Deploy pruefen
|
||||
7. Ausnahmen explizit dokumentieren
|
||||
4. Gitea-Hook gegen `http://komodo-core:9120/listener/github/stack/<stack-id>/deploy` pruefen
|
||||
5. einen Push oder Test-Delivery ausloesen und `last_status`/Komodo-Deploy pruefen
|
||||
6. Ausnahmen explizit dokumentieren
|
||||
|
||||
**Regel:** Kein neuer produktiver GitOps-Stack ohne funktionierenden Gitea->Komodo-Webhook. Bewusste Ausnahmen muessen im selben Aenderungsblock dokumentiert werden, inklusive Grund und Alternativ-Deploy-Weg.
|
||||
|
||||
Der Standardfall nutzt den globalen `KOMODO_WEBHOOK_SECRET` aus der Komodo-Host-`.env`, ausser Komodo zeigt fuer den Stack explizit ein eigenes per-Stack-Secret.
|
||||
|
||||
Der Gitea-Branch-Filter darf nicht leer oder `*` bleiben, solange der Komodo-Stack
|
||||
einen konkreten Repo-Branch erwartet. Sonst triggern Feature-/Arbeitsbranches alle
|
||||
Stack-Listener, Komodo verwirft sie mit `request branch does not match expected`
|
||||
und der Operations-Report bekommt unnuetzes Komodo-/Traefik-Rauschen.
|
||||
|
||||
### Ausnahme: Komodo-Zugangsmodell
|
||||
|
||||
Komodo bleibt **bewusst** ohne zentrale Traefik-ForwardAuth-Middleware.
|
||||
|
||||
@@ -61,8 +61,6 @@ 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:
|
||||
|
||||
@@ -76,23 +74,9 @@ 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.
|
||||
- MQTT-Integration verbindet sich mit Host `smarthome-mosquitto`, Port `1883`.
|
||||
- HA-native Backup-Erstellung funktioniert.
|
||||
|
||||
## 6. Fachrepo-Update
|
||||
|
||||
@@ -102,14 +86,12 @@ 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
|
||||
docker restart homeassistant
|
||||
```
|
||||
|
||||
Der Force-Recreate ist Pflicht, weil `configuration.yaml`, `automations.yaml`,
|
||||
Der Restart ist Pflicht, weil `configuration.yaml`, `automations.yaml`,
|
||||
`scripts.yaml` und `scenes.yaml` als Einzeldateien in den Container gemountet
|
||||
werden. Nach einem `git pull` kann Docker sonst noch den alten Datei-Inode sehen
|
||||
(`Stale file handle`).
|
||||
werden. Nach einem `git pull` kann Docker sonst noch den alten Datei-Inode sehen.
|
||||
|
||||
## 7. UI-Editor-Politik
|
||||
|
||||
@@ -123,102 +105,3 @@ und Integrations-State bleiben in `.storage` und werden per Borg gesichert.
|
||||
Vor produktiven Energie-Automationen muss ein Restore-Test fuer
|
||||
`/mnt/user/appdata/homeassistant`, `/mnt/user/appdata/mosquitto` und den Clone
|
||||
`/mnt/user/services/smart-home-kalli` dokumentiert sein.
|
||||
|
||||
Wichtig: Ein erfolgreich erzeugtes HA-Backup ist nur die Voraussetzung. Das Gate
|
||||
ist erst geschlossen, wenn eine Restore-Probe in einem isolierten Testpfad
|
||||
dokumentiert ist.
|
||||
|
||||
Status 2026-06-13: Gate geschlossen. Die isolierte Restore-Probe war
|
||||
erfolgreich:
|
||||
|
||||
- Report: `/mnt/user/backups/restore-reports/homeassistant-2026-06-13.md`
|
||||
- Test: HA-native Backup + Mosquitto-Appdata + Fachrepo-Clone
|
||||
- Ergebnis: HA HTTP/API/check_config gruen, MQTT Publish/Subscribe und retained
|
||||
Topic nach Broker-Restart gruen
|
||||
|
||||
Status 2026-06-13: HA-MQTT-Integration ist produktiv verbunden.
|
||||
|
||||
Verifikation:
|
||||
|
||||
```sh
|
||||
TOKEN=$(cat /mnt/user/appdata/secrets/ha_token_codex)
|
||||
curl -ksS -H "Authorization: Bearer $TOKEN" \
|
||||
https://home.kaleschke.info/api/config/config_entries/entry
|
||||
docker logs --tail=120 smarthome-mosquitto
|
||||
docker exec homeassistant python -m homeassistant --script check_config --config /config
|
||||
```
|
||||
|
||||
Erwartung: Ein MQTT-Config-Entry `smarthome-mosquitto` mit Status `loaded`, ein
|
||||
Mosquitto-Client mit User `homeassistant`, und `check_config` ohne Fehler.
|
||||
|
||||
## 9. SolarEdge lokal
|
||||
|
||||
Status 2026-06-13: SolarEdge ist lokal per Modbus TCP angebunden.
|
||||
|
||||
- Integration: HACS/Custom `solaredge_modbus_multi` v3.2.5
|
||||
- HA-Config-Entry: `SolarEdge Local`, Status `loaded`
|
||||
- Wechselrichter: `192.168.178.111:1502`
|
||||
- Modbus Device-ID: `1`
|
||||
- Optionen: Polling 60 Sekunden, Meter-Erkennung aktiv, Batterie-Erkennung
|
||||
aktiv, Extras aus, Power-Control aus
|
||||
|
||||
Verifikation:
|
||||
|
||||
```sh
|
||||
TOKEN=$(cat /mnt/user/appdata/secrets/ha_token_codex)
|
||||
curl -ksS -H "Authorization: Bearer $TOKEN" \
|
||||
https://home.kaleschke.info/api/config/config_entries/entry
|
||||
curl -ksS -H "Authorization: Bearer $TOKEN" \
|
||||
https://home.kaleschke.info/api/states
|
||||
docker exec homeassistant python -m homeassistant --script check_config --config /config
|
||||
```
|
||||
|
||||
Wichtige Energy-Dashboard-Kandidaten:
|
||||
|
||||
- PV-Produktion: `sensor.solaredge_local_i1_ac_energy`
|
||||
- Netzbezug: `sensor.solaredge_local_i1_m1_ac_energy_imported`
|
||||
- Einspeisung: `sensor.solaredge_local_i1_m1_ac_energy_exported`
|
||||
- Batterie geladen: `sensor.solaredge_local_i1_b1_energy_import`
|
||||
- Batterie entladen: `sensor.solaredge_local_i1_b1_energy_export`
|
||||
- Batterie-SoC: `sensor.solaredge_local_i1_b1_state_of_energy`
|
||||
|
||||
Nach der Integration wurde ein HA-native Backup erzeugt und tar-geprueft:
|
||||
`/mnt/user/appdata/homeassistant/backups/Custom_backup_2026.6.1_2026-06-13_14.59_48645373.tar`.
|
||||
|
||||
Trade-off: Dieser Pfad ist lokal und liefert Inverter, Meter und Batterie ohne
|
||||
Cloud-API, nutzt aber eine Custom-Integration. Bei HA-Core-Upgrades auf Warnungen
|
||||
zu `solaredge_modbus_multi` achten.
|
||||
|
||||
## 10. Energy Dashboard
|
||||
|
||||
Status 2026-06-13: Energy Dashboard ist ueber die Home-Assistant-WebSocket-API
|
||||
konfiguriert und validiert.
|
||||
|
||||
Konfiguration:
|
||||
|
||||
- Netz: Bezug `sensor.solaredge_local_i1_m1_ac_energy_imported`, Einspeisung
|
||||
`sensor.solaredge_local_i1_m1_ac_energy_exported`
|
||||
- PV: Produktion `sensor.solaredge_local_i1_ac_energy`, Live-Leistung
|
||||
`sensor.solaredge_local_i1_ac_power`
|
||||
- Speicher: Entladung `sensor.solaredge_local_i1_b1_energy_export`, Ladung
|
||||
`sensor.solaredge_local_i1_b1_energy_import`, SoC
|
||||
`sensor.solaredge_local_i1_b1_state_of_energy`
|
||||
- Kosten/Preise: noch nicht gesetzt; folgt mit Tibber
|
||||
|
||||
Verifikation:
|
||||
|
||||
```sh
|
||||
TOKEN=$(cat /mnt/user/appdata/secrets/ha_token_codex)
|
||||
# WebSocket: energy/get_prefs und energy/validate
|
||||
sed -n '1,260p' /mnt/user/appdata/homeassistant/.storage/energy
|
||||
```
|
||||
|
||||
Erwartung: `.storage/energy` enthaelt drei Quellen (`grid`, `solar`,
|
||||
`battery`), und `energy/validate` meldet keine Issues.
|
||||
|
||||
Nach der Energy-Konfiguration wurde ein HA-native Backup erzeugt und
|
||||
tar-geprueft:
|
||||
`/mnt/user/appdata/homeassistant/backups/Custom_backup_2026.6.1_2026-06-13_15.59_25670583.tar`.
|
||||
|
||||
Naechster Schritt: Tibber per HA-UI-Config-Flow verbinden und danach Kosten im
|
||||
Energy Dashboard ergaenzen.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
postgresql17:
|
||||
image: postgres:18.4@sha256:8ff36f3c66371cba71d20ceedccfc3de9669a68737607888c4ef0af93abe8e39
|
||||
image: postgres:18.4@sha256:29ee7bb30d804447dc9a91fd0d74322ae1dc3a4072cc6346f70a5ed6e783b565
|
||||
container_name: postgresql17
|
||||
restart: unless-stopped
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ services:
|
||||
- cadvisor
|
||||
|
||||
alertmanager:
|
||||
image: prom/alertmanager:v0.33.0@sha256:af26fbe4dd1886ac0efd7bd55cd9027da262e105b137a376522b7c14c3626e4a
|
||||
image: prom/alertmanager:v0.32.2@sha256:b85533a2eb45865835315810315f6951331b2dbc8c93a6cf9a51e156a006a706
|
||||
container_name: monitoring-alertmanager
|
||||
restart: unless-stopped
|
||||
command:
|
||||
@@ -42,7 +42,7 @@ services:
|
||||
- no-new-privileges:true
|
||||
|
||||
alertmanager-ntfy-bridge:
|
||||
image: python:3.14-alpine@sha256:26730869004e2b9c4b9ad09cab8625e81d256d1ce97e72df5520e806b1709f92
|
||||
image: python:3.14-alpine@sha256:5a824eb82cc75361f98611f3cfc5091ea33f10a6ccea4d4ebdabbc523b9a1614
|
||||
container_name: monitoring-alertmanager-ntfy-bridge
|
||||
restart: unless-stopped
|
||||
dns:
|
||||
@@ -337,7 +337,7 @@ services:
|
||||
- no-new-privileges:true
|
||||
|
||||
influxdb3-core:
|
||||
image: influxdb:3.10.0-core@sha256:b3e577f38c19963597170d8850a3a7f77af8f0cfa866c64cd13e5de0f238e114
|
||||
image: influxdb:3.9.3-core@sha256:c27c9b2ca2625b5b6966f0b09baa448102310e63a471fd60dff22646a2522e29
|
||||
container_name: monitoring-influxdb3-core
|
||||
user: "0"
|
||||
restart: unless-stopped
|
||||
@@ -351,12 +351,6 @@ services:
|
||||
- --data-dir=/var/lib/influxdb3/data
|
||||
- --plugin-dir=/var/lib/influxdb3/plugins
|
||||
- --admin-token-file=/run/secrets/influxdb3_admin_token
|
||||
# InfluxDB 3 Core kompaktiert Parquet-Dateien nicht (nur Enterprise).
|
||||
# HA schreibt viele Sensoren haeufig -> Tabellen wie "°C"/"%"/"hPa" liefen
|
||||
# ins Default-Limit von 432 Dateien/Query ("No data" in Grafana).
|
||||
# Stopgap: Limit anheben. Langfristig: Enterprise (Auto-Compaction, frei
|
||||
# fuer Home) oder weniger/seltener nach InfluxDB schreiben.
|
||||
- --query-file-limit=20000
|
||||
volumes:
|
||||
- /mnt/user/appdata/influxdb3/data:/var/lib/influxdb3/data
|
||||
- /mnt/user/appdata/influxdb3/plugins:/var/lib/influxdb3/plugins
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
{
|
||||
"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" } ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
{
|
||||
"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,19 +31,3 @@ datasources:
|
||||
insecureGrpc: true
|
||||
secureJsonData:
|
||||
token: $GRAFANA_INFLUXDB_TOKEN
|
||||
|
||||
# Wetter-/Langzeitarchiv aus Home Assistant (Ecowitt). Gleiche InfluxDB-Instanz,
|
||||
# aber Datenbank `homeassistant`; gleicher Admin-Read-Token.
|
||||
- name: InfluxDB HA Weather
|
||||
uid: ha-weather-influx
|
||||
type: influxdb
|
||||
access: proxy
|
||||
url: http://influxdb3-core:8181
|
||||
editable: false
|
||||
jsonData:
|
||||
version: SQL
|
||||
dbName: homeassistant
|
||||
httpMode: POST
|
||||
insecureGrpc: true
|
||||
secureJsonData:
|
||||
token: $GRAFANA_INFLUXDB_TOKEN
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Borg Backup Scope for KalliLabcore
|
||||
|
||||
Stand: 2026-06-17
|
||||
Stand: 2026-05-31
|
||||
|
||||
This file defines the target state for replacing Backrest with Borg in this homelab.
|
||||
|
||||
@@ -38,7 +38,7 @@ The Unraid flash configuration archive is intentional as well and must be treate
|
||||
| Traefik | file data | `/local/appdata/traefik` |
|
||||
| ntfy | file data | `/local/appdata/ntfy` |
|
||||
| Paperless-GPT | file data | `/local/appdata/paperless-gpt` |
|
||||
| Tailscale | Flash config artifact | covered by `/local/borg-dumps/unraid-flash-config.tar.gz`; no active `/local/appdata/tailscale` path |
|
||||
| Tailscale | file data | `/local/appdata/tailscale` |
|
||||
| AdGuard | config only | `/local/appdata/adguard/conf` |
|
||||
| Borg UI | SQLite dump + self-backup | `/local/borg-dumps`, `/local/appdata/borg-ui/data` |
|
||||
| Komodo | config + Mongo dump | `/local/borg-dumps`, `/local/appdata/komodo/periphery`, `/local/appdata/komodo/core` |
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
/local/appdata/traefik
|
||||
/local/appdata/ntfy
|
||||
/local/appdata/paperless-gpt
|
||||
/local/appdata/tailscale
|
||||
/local/appdata/adguard/conf
|
||||
/local/appdata/borg-ui/data
|
||||
/local/appdata/komodo/periphery
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
borg-ui:
|
||||
image: ainullcode/borg-ui@sha256:e51b3d2e6cb38d1ba127ef60ba442c1e157965327196e6f7afb69f30c0ba99d1
|
||||
image: ainullcode/borg-ui@sha256:0922157e8f77a1b2bd23cd09366a458ea6de07fd9306aa1485f9cfe623eca17f
|
||||
container_name: borg-ui
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
code-server:
|
||||
image: lscr.io/linuxserver/code-server:4.125.0@sha256:7e9523734c003b6336781942df7b48aa6936a9df6931c12a19a1f7ad7858eeba
|
||||
image: lscr.io/linuxserver/code-server:4.123.0@sha256:cb261a7f87674b445e0fd66d87d55900c1b823d276c727ab0d168a75e69e9992
|
||||
container_name: code-server
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
filebrowser:
|
||||
image: filebrowser/filebrowser:v2.63.15@sha256:9805b21cf910f3ef6f4a1c8f441f1dd6cc4197136f9541fe2a1ab6d050706e4b
|
||||
image: filebrowser/filebrowser:v2.63.14@sha256:1ec9b0c68297550c92f4a93feed432850c2993b261706cc3cc2e808f94a95e76
|
||||
container_name: filebrowser
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
|
||||
@@ -106,91 +106,10 @@
|
||||
- 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: weather
|
||||
location: Berlin, Germany
|
||||
units: metric
|
||||
hour-format: 24h
|
||||
|
||||
- type: calendar
|
||||
first-day-of-week: monday
|
||||
|
||||
@@ -14,7 +14,6 @@ services:
|
||||
GLANCE_GITEA_TOKEN: ${GLANCE_GITEA_TOKEN:-}
|
||||
GLANCE_PAPERLESS_TOKEN: ${GLANCE_PAPERLESS_TOKEN:-}
|
||||
GLANCE_MEALIE_TOKEN: ${GLANCE_MEALIE_TOKEN:-}
|
||||
GLANCE_HA_TOKEN: ${GLANCE_HA_TOKEN:-}
|
||||
volumes:
|
||||
- ./config:/app/config:ro
|
||||
- ./assets:/app/assets:ro
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
glances:
|
||||
image: nicolargo/glances:latest-full@sha256:58651aabedf62db8bfc1d252f8d3889675dfcdb5d0ad1c177ae5879c21626f3a
|
||||
image: nicolargo/glances:latest-full@sha256:60872a1af0e40a3150975617c7e811ad7ad48f95bc45d033fb0c1737a037e4d2
|
||||
container_name: glances
|
||||
restart: unless-stopped
|
||||
pid: host
|
||||
|
||||
@@ -45,13 +45,13 @@
|
||||
"description": "VPN / Remote-Zugang",
|
||||
"tier": 1,
|
||||
"category": "core",
|
||||
"container_name": null,
|
||||
"container_name": "tailscale",
|
||||
"dependencies": [],
|
||||
"url": null,
|
||||
"dump_file": null,
|
||||
"data_paths": ["/boot/config/plugins/tailscale/state"],
|
||||
"first_check": "Tailscale Status auf Host pruefen; native Unraid-Plugin-Instanz und Subnet-Route aktiv?",
|
||||
"notes": "Natives Unraid-Plugin, nicht Docker/Komodo-verwaltet; State liegt im Flash-Backup. Alter Docker-State ist archiviert unter /mnt/user/appdata/_archive/tailscale-removed-2026-06-06/"
|
||||
"data_paths": ["/mnt/user/appdata/tailscale"],
|
||||
"first_check": "Tailscale Status auf Host pruefen; State-Datei fuer Key-Renewal vorhanden?",
|
||||
"notes": "network_mode: host; NET_ADMIN, NET_RAW, /dev/net/tun — dokumentierte VPN-Ausnahmen"
|
||||
},
|
||||
"gitea": {
|
||||
"description": "Git-Server — operative Quelle der Wahrheit fuer GitOps",
|
||||
|
||||
@@ -75,14 +75,14 @@ services:
|
||||
description: VPN / Remote-Zugang
|
||||
tier: 1
|
||||
category: core
|
||||
container_name: null
|
||||
container_name: tailscale
|
||||
dependencies: []
|
||||
url: null
|
||||
dump_file: null
|
||||
data_paths:
|
||||
- /boot/config/plugins/tailscale/state
|
||||
first_check: "Tailscale Status auf Host pruefen; native Unraid-Plugin-Instanz und Subnet-Route aktiv?"
|
||||
notes: "Natives Unraid-Plugin, nicht Docker/Komodo-verwaltet; State liegt im Flash-Backup. Alter Docker-State ist archiviert unter /mnt/user/appdata/_archive/tailscale-removed-2026-06-06/"
|
||||
- /mnt/user/appdata/tailscale
|
||||
first_check: "Tailscale Status auf Host pruefen; State-Datei fuer Key-Renewal vorhanden?"
|
||||
notes: "network_mode: host; NET_ADMIN, NET_RAW, /dev/net/tun — dokumentierte VPN-Ausnahmen"
|
||||
|
||||
gitea:
|
||||
description: Git-Server — operative Quelle der Wahrheit fuer GitOps
|
||||
|
||||
@@ -59,7 +59,7 @@ Stand 2026-06-11 ist der Betrieb auf V1+ (validierte Bash-Host-Jobs mit ntfy):
|
||||
# Frische-Check
|
||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh freshness
|
||||
|
||||
# Dienst-Restore-Check (vaultwarden|gitea|paperless|immich|authelia|adguard|redis|homeassistant|komodo-bootstrap|nextcloud)
|
||||
# Dienst-Restore-Check (vaultwarden|gitea|paperless|immich|authelia|adguard|redis|komodo-bootstrap|nextcloud)
|
||||
bash /mnt/user/services/homelab-infra/ops/restore-tests/run-restore-checks.sh <dienst>
|
||||
|
||||
# Negativtest des Alarmwegs (quartalsweise)
|
||||
@@ -77,7 +77,6 @@ Einziger Status-Ort ist die **Reifegrad-Tabelle** in `docs/RESTORE_MATRIX.md`
|
||||
- **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,6 +1,6 @@
|
||||
services:
|
||||
restoretest-adguard:
|
||||
image: adguard/adguardhome:v0.107.77@sha256:e6f2b8bcda06064ab055b44933a4f0e983c35558b9cdb8d2e7ab1efcee36d890
|
||||
image: adguard/adguardhome:v0.107.76@sha256:7157eb1dc3b26c7af1d6898759a7b3f7d0fa09891fbd2d3caa6abc1057a9179b
|
||||
container_name: restoretest-adguard
|
||||
restart: "no"
|
||||
ports:
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
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
|
||||
@@ -1,236 +0,0 @@
|
||||
#!/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"
|
||||
@@ -55,12 +55,6 @@ case "$MODE" in
|
||||
fi
|
||||
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)
|
||||
if [ "$WHATIF" = "--what-if" ]; then
|
||||
exec "$SCRIPT_DIR/nextcloud-restore-test.sh" --what-if
|
||||
@@ -104,7 +98,7 @@ case "$MODE" in
|
||||
exec "$SCRIPT_DIR/shared-pg-cluster-restore-test.sh"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {freshness|freshness-negative|vaultwarden|gitea|paperless|immich|authelia|adguard|redis|homeassistant|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|nextcloud|komodo-bootstrap|komodo-mongo-restore|traefik|mailarchiver|mealie|shared-pg-cluster} [--what-if]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
scrutiny:
|
||||
image: ghcr.io/starosdev/scrutiny:latest-omnibus@sha256:d79e6f1bc299ab28fbd95c9e05fa5a8c565332d2cb9091a91e42d84d4d939989
|
||||
image: ghcr.io/starosdev/scrutiny:latest-omnibus@sha256:228483f16a6236d2fa9b2fbfca2e76dc861e648fbc6ae6e680d23e5d00211a5d
|
||||
container_name: scrutiny
|
||||
restart: unless-stopped
|
||||
privileged: true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
speedtest-tracker:
|
||||
image: lscr.io/linuxserver/speedtest-tracker:1.14.4@sha256:f99dfd097709016dfb4387d65bfdc0419bde99cf1dce7e26e70ca616c86f1281
|
||||
image: lscr.io/linuxserver/speedtest-tracker:1.14.3@sha256:c3750c40948a9360000ce62d694da92e85584b4ab6d3d9a9d1432d76fa5e0726
|
||||
container_name: speedtest-tracker
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
|
||||
+1
-2
@@ -39,11 +39,10 @@
|
||||
"labels": ["dependencies", "minor-patch"]
|
||||
},
|
||||
{
|
||||
"description": "Kritische Kerninfra (Traefik=Public-Entrypoint, AdGuard/Unbound=DNS, n8n, Nextcloud): nicht im Sammel-PR, eigene einzeln reviewbare PRs, kein Auto-Merge",
|
||||
"description": "Kritische Kerninfra (Traefik=Public-Entrypoint, Unbound=DNS, n8n, Nextcloud): nicht im Sammel-PR, eigene einzeln reviewbare PRs, kein Auto-Merge",
|
||||
"matchManagers": ["docker-compose", "dockerfile"],
|
||||
"matchPackageNames": [
|
||||
"traefik",
|
||||
"adguard/adguardhome",
|
||||
"shaanmajid/unbound",
|
||||
"docker.n8n.io/n8nio/n8n",
|
||||
"nextcloud"
|
||||
|
||||
@@ -30,7 +30,7 @@ parse_compose() {
|
||||
return value
|
||||
}
|
||||
function emit() {
|
||||
if (service && image && !has_profile) {
|
||||
if (service && image) {
|
||||
print clean(container) "\t" clean(image)
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,6 @@ parse_compose() {
|
||||
sub(/:$/, "", service)
|
||||
image=""
|
||||
container=service
|
||||
has_profile=0
|
||||
next
|
||||
}
|
||||
service && /^ image:/ {
|
||||
@@ -53,10 +52,6 @@ parse_compose() {
|
||||
sub(/^[[:space:]]*container_name:[[:space:]]*/, "", container)
|
||||
next
|
||||
}
|
||||
service && /^ profiles:/ {
|
||||
has_profile=1
|
||||
next
|
||||
}
|
||||
END { emit() }
|
||||
' "$compose"
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ CERT_MAX_ROWS="${CERT_MAX_ROWS:-12}"
|
||||
IMAGE_AGE_WARN_DAYS="${IMAGE_AGE_WARN_DAYS:-180}"
|
||||
IMAGE_AGE_ALLOW_FILE="${IMAGE_AGE_ALLOW_FILE:-/mnt/user/services/homelab-infra/services/posture-check/image-age-allow.patterns}"
|
||||
LOG_VOLUME_TOP_N="${LOG_VOLUME_TOP_N:-10}"
|
||||
LOG_VOLUME_OBSERVE_THRESHOLD="${LOG_VOLUME_OBSERVE_THRESHOLD:-100000}"
|
||||
DISK_USAGE_WARN_PCT="${DISK_USAGE_WARN_PCT:-85}"
|
||||
CERT_WARN_DAYS="${CERT_WARN_DAYS:-21}"
|
||||
BACKUP_DRIFT_FACTOR="${BACKUP_DRIFT_FACTOR:-2.0}"
|
||||
@@ -218,73 +217,6 @@ derive_report_status() {
|
||||
set_summary "report_status" "$REPORT_STATUS"
|
||||
}
|
||||
|
||||
print_status_reasons() {
|
||||
local count=0
|
||||
|
||||
add_reason() {
|
||||
printf '%s\n' "- $1"
|
||||
count=$((count + 1))
|
||||
}
|
||||
|
||||
[ "${borg_status:-unknown}" != "completed" ] && add_reason "Borg Backup ist \`${borg_status:-unknown}\` statt \`completed\`."
|
||||
[ "${prometheus_alerts:-0}" = "unknown" ] && add_reason "Prometheus Alerts konnten nicht sicher gelesen werden."
|
||||
[ "${cert_warnings:-0}" != "0" ] && add_reason "Zertifikatswarnungen: \`${cert_warnings:-0}\`."
|
||||
[ "${disk_warnings:-0}" != "0" ] && add_reason "Storage-Warnungen: \`${disk_warnings:-0}\`."
|
||||
if [ "${image_warnings:-0}" != "0" ]; then
|
||||
if [ -n "${image_warning_names:-}" ]; then
|
||||
add_reason "Image-Warnungen: \`${image_warnings:-0}\` (${image_warning_names})."
|
||||
else
|
||||
add_reason "Image-Warnungen: \`${image_warnings:-0}\`."
|
||||
fi
|
||||
fi
|
||||
[ "${containers_exited_nonzero:-0}" != "0" ] && add_reason "Container exited non-zero: \`${containers_exited_nonzero:-0}\`."
|
||||
[ "${host_recent_boot:-0}" = "1" ] && add_reason "Host-Reboot innerhalb der letzten 24 Stunden."
|
||||
[ "${backup_duration_drift:-0}" = "1" ] && add_reason "Backup-Dauer-Drift erkannt."
|
||||
[ "${noise_threshold_exceeded:-0}" != "0" ] && add_reason "Noise-Pattern ueber Eskalations-Schwelle: \`${noise_threshold_exceeded:-0}\`."
|
||||
|
||||
if [ "${prometheus_alerts_pending:-0}" != "0" ] && [ "${prometheus_alerts_pending:-0}" != "unknown" ]; then
|
||||
add_reason "Prometheus pending Alerts: \`${prometheus_alerts_pending:-0}\`."
|
||||
fi
|
||||
if [ "${prometheus_alerts_firing:-0}" != "0" ] && [ "${prometheus_alerts_firing:-0}" != "unknown" ]; then
|
||||
add_reason "Prometheus firing Alerts: \`${prometheus_alerts_firing:-0}\`."
|
||||
fi
|
||||
[ "${containers_unhealthy:-0}" != "0" ] && add_reason "Unhealthy Container: \`${containers_unhealthy:-0}\`."
|
||||
|
||||
if [ "$count" -eq 0 ]; then
|
||||
printf '%s\n' "- Keine direkten Ampel-Ausloeser im Summary-Set gefunden."
|
||||
fi
|
||||
}
|
||||
|
||||
print_notable_observations() {
|
||||
local count=0
|
||||
|
||||
add_observation() {
|
||||
printf '%s\n' "- $1"
|
||||
count=$((count + 1))
|
||||
}
|
||||
|
||||
if [ "${traefik_5xx:-0}" != "0" ] && [ "${traefik_5xx:-0}" != "unknown" ]; then
|
||||
if [ -n "${traefik_5xx_top:-}" ] && [ "${traefik_5xx_top:-none}" != "none" ]; then
|
||||
add_observation "Traefik 5xx: \`${traefik_5xx:-0}\` (Top-Gruppe: \`${traefik_5xx_top}\`)."
|
||||
else
|
||||
add_observation "Traefik 5xx: \`${traefik_5xx:-0}\`."
|
||||
fi
|
||||
fi
|
||||
if [ "${log_highlights:-0}" != "0" ] && [ "${log_highlights:-0}" != "unknown" ]; then
|
||||
add_observation "Log-Highlights: \`${log_highlights:-0}\` handlungsrelevante Treffer; Beispiele stehen in der Log-Auswertung."
|
||||
fi
|
||||
if printf '%s' "${log_volume_total:-0}" | grep -Eq '^[0-9]+$' && [ "${log_volume_total:-0}" -ge "$LOG_VOLUME_OBSERVE_THRESHOLD" ]; then
|
||||
add_observation "Log-Volumen: \`${log_volume_total:-0}\` Zeilen im Zeitraum; Top-Verursacher stehen im Log-Volumen-Abschnitt."
|
||||
fi
|
||||
if [ "${docker_events:-0}" != "0" ] && [ "${docker_events:-0}" != "unknown" ]; then
|
||||
add_observation "Docker Critical Events: \`${docker_events:-0}\`."
|
||||
fi
|
||||
|
||||
if [ "$count" -eq 0 ]; then
|
||||
printf '%s\n' "- Keine zusaetzlichen auffaelligen Beobachtungen im Management-Summary."
|
||||
fi
|
||||
}
|
||||
|
||||
collect_borg() {
|
||||
append "## Borg Backup"
|
||||
append ""
|
||||
@@ -652,7 +584,6 @@ collect_image_freshness() {
|
||||
local image_file="$TMP_DIR/images.tsv"
|
||||
local image_warnings=0
|
||||
local image_allowed=0
|
||||
local image_warning_names=""
|
||||
local now_epoch
|
||||
: > "$image_file"
|
||||
now_epoch="$(date +%s)"
|
||||
@@ -699,7 +630,6 @@ collect_image_freshness() {
|
||||
else
|
||||
note="ueberaltert"
|
||||
image_warnings=$((image_warnings + 1))
|
||||
image_warning_names="${image_warning_names:+$image_warning_names,}$name:${age_days}d"
|
||||
fi
|
||||
fi
|
||||
printf '%d\t%s\t%s\t%s\n' "$age_days" "$name" "$image_tag" "$note" >> "$image_file"
|
||||
@@ -707,7 +637,6 @@ collect_image_freshness() {
|
||||
|
||||
set_summary "image_warnings" "$image_warnings"
|
||||
set_summary "image_allowed" "$image_allowed"
|
||||
set_summary "image_warning_names" "$image_warning_names"
|
||||
|
||||
if [ ! -s "$image_file" ]; then
|
||||
append "- Keine Image-Daten verfuegbar."
|
||||
@@ -852,16 +781,8 @@ collect_traefik_5xx() {
|
||||
set_summary "traefik_5xx" "$count"
|
||||
|
||||
if [ "$count" -eq 0 ]; then
|
||||
set_summary "traefik_5xx_top" "none"
|
||||
append "- Keine 5xx-Antworten."
|
||||
else
|
||||
local top_group
|
||||
top_group="$(awk '{ code=$9; service=$12; gsub(/"/, "", service); counts[service " " code]++ } END { for (k in counts) print counts[k], k }' "$file" \
|
||||
| sort -nr \
|
||||
| head -n 1 \
|
||||
| awk '{ print $2 ":" $3 ":" $1 }' \
|
||||
| sed -E 's#[^A-Za-z0-9_.:@/-]+#_#g')"
|
||||
set_summary "traefik_5xx_top" "${top_group:-none}"
|
||||
append "- 5xx-Antworten: $count"
|
||||
append ""
|
||||
append "### Gruppiert nach Service/Code"
|
||||
@@ -1260,20 +1181,10 @@ write_report() {
|
||||
if [ "$REPORT_STATUS" = "OK" ]; then
|
||||
printf 'Im betrachteten Zeitraum zeigt das Homelab eine stabile Betriebslage. Das letzte Borg-Backup ist erfolgreich abgeschlossen, Prometheus meldet keine firing Alerts, keine unhealthy Container, Zertifikate und Storage im erwarteten Bereich.\n\n'
|
||||
elif [ "$REPORT_STATUS" = "WARNUNG" ]; then
|
||||
printf 'Im betrachteten Zeitraum gibt es Punkte, die Aufmerksamkeit verdienen. Der Betrieb ist nicht automatisch als kompromittiert zu bewerten; die konkreten Ampel-Ausloeser stehen direkt darunter.\n\n'
|
||||
printf 'Im betrachteten Zeitraum gibt es Punkte, die Aufmerksamkeit verdienen. Der Betrieb ist nicht automatisch als kompromittiert zu bewerten, aber mindestens ein Signal (Backup, Pending Alert, Zertifikat, Storage, Image-Alter, Drift oder Reboot) weicht vom Normalzustand ab.\n\n'
|
||||
else
|
||||
printf 'Im betrachteten Zeitraum liegt ein kritisches Betriebssignal vor. Der Bericht sollte zeitnah gelesen und die betroffenen Komponenten priorisiert geprueft werden.\n\n'
|
||||
fi
|
||||
printf '### Warum dieser Status?\n\n'
|
||||
if [ "$REPORT_STATUS" = "OK" ]; then
|
||||
printf '%s\n\n' "- Keine Ampel-Ausloeser im Summary-Set."
|
||||
else
|
||||
print_status_reasons
|
||||
printf '\n'
|
||||
fi
|
||||
printf '### Weitere auffaellige Beobachtungen\n\n'
|
||||
print_notable_observations
|
||||
printf '\n'
|
||||
printf '### Management-Bewertung\n\n'
|
||||
printf '%s\n' "- Status: \`$REPORT_STATUS\`"
|
||||
printf '%s\n' "- Borg Backup: \`${borg_status:-unknown}\`"
|
||||
|
||||
@@ -28,9 +28,3 @@ immich_postgres 2026-09-10
|
||||
# (Dez 2025). Das Image-Alter ist nur Build-Alter, keine veraltete Version.
|
||||
# Re-check: ob eine blackbox_exporter-Version > v0.28.0 erschienen ist.
|
||||
monitoring-blackbox-exporter 2026-09-10
|
||||
|
||||
# glance-docker-socket-proxy: v0.4.2 ist am 2026-06-17 weiterhin der neueste
|
||||
# stabile Tag / latest. Neuere Tags sind nur master/nightly und werden fuer den
|
||||
# lesenden Glance-Socket-Proxy bewusst nicht produktiv eingesetzt.
|
||||
# Re-check: ob ein stabiler Tag > v0.4.2 erschienen ist.
|
||||
glance-docker-socket-proxy 2026-09-17
|
||||
|
||||
@@ -87,19 +87,3 @@ adguard.*bad question section.*only 1 question allowed
|
||||
# this lookup is harmless and does not affect any dashboard.
|
||||
# Re-check: only if Amazon Prometheus is added as a datasource.
|
||||
monitoring-grafana.*grafana-amazonprometheus-datasource not found
|
||||
|
||||
# cAdvisor stale container filesystem stats on Unraid.
|
||||
# Why: cAdvisor can keep reporting an already removed Docker container path in
|
||||
# fsHandler even though the container and path no longer exist. This is a
|
||||
# collector bookkeeping issue, not a failed workload or missing data path.
|
||||
# Re-check: if the message references an existing/running container, if
|
||||
# Prometheus target health fails, or if broader cAdvisor errors appear.
|
||||
monitoring-cadvisor.*failed to collect filesystem stats.*var/lib/docker/containers/[0-9a-f]{64}
|
||||
|
||||
# cAdvisor startup lines that match the generic "oom" / "failed" grep.
|
||||
# Why: "oom_event" is a metric name printed during startup, and Unraid loop
|
||||
# devices can disappear while cAdvisor enumerates block devices.
|
||||
# Re-check: if cAdvisor target health fails or these messages appear outside
|
||||
# container startup together with missing container metrics.
|
||||
monitoring-cadvisor.*enabled metrics:.*oom_event
|
||||
monitoring-cadvisor.*stat failed on /dev/loop[0-9]+ with error: no such file or directory
|
||||
|
||||
@@ -431,24 +431,24 @@ def render_summary_grid(entries):
|
||||
status = classify(label, value)
|
||||
theme = STATUS_THEMES.get(status, STATUS_THEMES["UNKNOWN"])
|
||||
cards.append(
|
||||
'<td style="padding:6px;width:50%;vertical-align:top">'
|
||||
'<td style="padding:6px;width:33.33%;vertical-align:top">'
|
||||
f'<div style="background:{theme["card_bg"]};'
|
||||
f'border:1px solid {theme["card_border"]};'
|
||||
'border-radius:8px;padding:11px 12px;min-height:74px">'
|
||||
'border-radius:8px;padding:12px 14px">'
|
||||
f'<div style="font-size:11px;color:#1e293b;'
|
||||
'text-transform:uppercase;letter-spacing:0.04em;font-weight:700;'
|
||||
f'line-height:1.35;opacity:0.78;overflow-wrap:anywhere">{html.escape(label)}</div>'
|
||||
f'<div style="font-size:16px;font-weight:700;'
|
||||
'text-transform:uppercase;letter-spacing:0.08em;font-weight:700;'
|
||||
f'line-height:1.3;opacity:0.78">{html.escape(label)}</div>'
|
||||
f'<div style="font-size:17px;font-weight:700;'
|
||||
f'color:{theme["card_text"]};margin-top:5px;line-height:1.25;'
|
||||
f'word-break:normal;overflow-wrap:anywhere;font-variant-numeric:tabular-nums">'
|
||||
f'word-break:break-word;font-variant-numeric:tabular-nums">'
|
||||
f'{html.escape(value)}</div>'
|
||||
'</div></td>'
|
||||
)
|
||||
rows_html = []
|
||||
for chunk_start in range(0, len(cards), 2):
|
||||
chunk = cards[chunk_start:chunk_start + 2]
|
||||
while len(chunk) < 2:
|
||||
chunk.append('<td style="padding:6px;width:50%"></td>')
|
||||
for chunk_start in range(0, len(cards), 3):
|
||||
chunk = cards[chunk_start:chunk_start + 3]
|
||||
while len(chunk) < 3:
|
||||
chunk.append('<td style="padding:6px;width:33.33%"></td>')
|
||||
rows_html.append("<tr>" + "".join(chunk) + "</tr>")
|
||||
return (
|
||||
'<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" '
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
homeassistant:
|
||||
image: ghcr.io/home-assistant/home-assistant:2026.6.3@sha256:aed891b8f801072302815b4b0fab5adb714182967e9d2e2d4a2be558241c73ad
|
||||
image: ghcr.io/home-assistant/home-assistant:2026.6.1@sha256:59aa8824955c9db491b75d2eebe42bd68494f80c2ec69ec0d66d9dae37d37514
|
||||
container_name: homeassistant
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
@@ -15,16 +15,8 @@ services:
|
||||
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"
|
||||
expose:
|
||||
- "8123"
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
depends_on:
|
||||
@@ -36,7 +28,8 @@ services:
|
||||
- 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
|
||||
# Temporary onboarding guard: remove after the HA owner account exists.
|
||||
- traefik.http.routers.homeassistant.middlewares=authelia@file,secure-headers@file
|
||||
- traefik.http.services.homeassistant.loadbalancer.server.port=8123
|
||||
|
||||
mosquitto:
|
||||
@@ -63,9 +56,3 @@ networks:
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user