28 Commits

Author SHA1 Message Date
renovate a28d3bbc33 chore(deps): update postgres:18.4 docker digest to 29ee7bb 2026-06-12 22:20:51 +00:00
Micha 4ab6dcefd2 fix: protect ha onboarding with authelia 2026-06-12 21:52:45 +02:00
Micha c24b792808 fix: allow home hairpin during ha onboarding 2026-06-12 21:51:34 +02:00
Micha 25a4ada891 fix: guard home assistant onboarding 2026-06-12 21:50:15 +02:00
Micha 6e6005aefd feat: add smart home foundation 2026-06-12 20:51:18 +02:00
Micha ad438a07b3 fix: allow mosquitto config ownership setup 2026-06-12 20:45:32 +02:00
Micha ce6f5c72dd feat: add smart home runtime foundation 2026-06-12 20:38:03 +02:00
Micha 630ee8dd90 ops: glance server-stats balken kraeftiger (13px, innenschatten)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:00:38 +02:00
Micha b1ca9ef19c ops: glance server-stats balken - rund, gradient, glow, warnfarbe ab 85%
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 19:57:36 +02:00
Micha 1c949d3fcc ops: glance internet-widget - bytes/s nach mbit/s umrechnen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 19:55:05 +02:00
Micha cfa6c01768 ops: glance komodo/immich widgets - stat-leisten mit trennlinien, pills, gradient-bars
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 19:24:12 +02:00
Micha 3474d53ce5 ops: glance borg-widget fix - alter via promql berechnen statt now.Unix im template
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 19:05:48 +02:00
Micha ca81b959cc ops: glance borg-backup-widget via prometheus + synthwave/matrix presets
- glance zusaetzlich in monitoring_net (nur lesende Prometheus-Query, kein neuer Listener)
- Borg-Widget: Backup-Alter aus homelab_borg_last_completed_timestamp_seconds, Status aus homelab_borg_last_success
- Theme-Presets synthwave und matrix

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 19:50:53 +02:00
28 changed files with 1962 additions and 881 deletions
+20
View File
@@ -0,0 +1,20 @@
# Agent Context - Homelab Infra
Typ: Einstieg/Index · Stand: 2026-06-11 · Status: aktiv
Einstiegspunkt fuer KI-Agenten (Codex, Gemini u. a.; Claude nutzt zusaetzlich
`CLAUDE.md`). Kein eigener Inhalt - nur Pflichtpfade.
## Vor jeder Arbeit lesen
1. `docs/AI_CONTEXT.md` - Systembild, harte Regeln, Ausnahmen-Kurzliste
2. `HOMELAB_ARCHITECTURE_MASTER_V2.md` - Architektur-Zielbild
3. `docs/WORKFLOW.md` - verbindlicher GitOps-/No-Drift-Ablauf
4. die betroffene `docker-compose.yml` bzw. das betroffene Runbook (Index: `docs/README.md`)
## Nicht verhandelbar
- Keine Secret-Werte lesen, zitieren oder schreiben - nur Namen und Pfade.
- Keine Deployments, Host-Hotfixes oder Docker-Schreibbefehle ohne ausdrueckliche Anweisung.
- Doku-Regeln aus `docs/REPO_MAP.md` einhalten: ein Fakt, ein Zuhause. Status nur in `docs/MASTER_TODO.md`, Entscheidungen nur in `docs/DECISIONS.md`.
- Bei Drift oder zwei fehlgeschlagenen Reparaturversuchen: stoppen, `docs/GITOPS_DRIFT_RUNBOOK.md`.
+9 -2
View File
@@ -93,6 +93,7 @@ Jeder produktive Container nutzt `restart: unless-stopped`, außer eine Ausnahme
| `monitoring_net` | Compose-intern, bridge | zentraler Observability-Stack fuer Prometheus, Loki, Grafana, Promtail, Exporter und InfluxDB | Zielzustand |
| `monitoring_influx_lan` | Compose-intern, bridge | nicht-oeffentliches Zusatznetz nur fuer Docker Host-Port-Publishing von InfluxDB 8181 | Zielzustand |
| `glance_socket_net` | Compose-intern, `internal: true` | interner Zugriff von Glance auf den Docker-Socket-Proxy | umgesetzt |
| `smarthome_net` | bridge, `internal: true` | interne Smart-Home-Kommunikation zwischen Home Assistant, Mosquitto, spaeter Zigbee2MQTT/ESPHome | vorbereitet |
| `host` | host | nur für echte Sonderfälle | begründet |
### 3.2 Finales Diagramm (vereinfacht)
@@ -123,7 +124,8 @@ App-interne Netze
├── immich_default (internal: true) ✅
├── nextcloud_internal (internal: true) ✅
├── monitoring_net (zentraler Observability-Stack)
── monitoring_influx_lan (Bridge fuer LAN-Port-Publishing, keine Traefik-Route)
── monitoring_influx_lan (Bridge fuer LAN-Port-Publishing, keine Traefik-Route)
└── smarthome_net (HA, Mosquitto, spaeter Zigbee2MQTT/ESPHome)
Host-Sonderfälle
├── tailscale
@@ -146,6 +148,7 @@ Diese Dienste sind über echte `*.kaleschke.info`-Domains erreichbar:
- `immich_server` — immich.kaleschke.info
- `nextcloud` — cloud.kaleschke.info
- `plex` — plex.kaleschke.info (Traefik, native Plex-Auth; Plex Remote Access/Port 32400 bleibt aus)
- `homeassistant` — home.kaleschke.info (Traefik, native Home-Assistant-Auth)
### 4.2 Nicht öffentlich / nur Tailscale oder Traefik + Middleware
Diese Dienste sind **keine Public Apps**:
@@ -261,6 +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` | ✅ 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
@@ -274,6 +278,7 @@ Legende Status:
| `immich_server` | ✅ | `immich_default`, `frontend_net` | Traefik | aktiv via `immich.kaleschke.info` | — |
| `immich_machine_learning` | ✅ | `immich_default` | intern | bleibt intern | — |
| `nextcloud` | ✅ | `frontend_net`, `nextcloud_internal` | Traefik | aktiv via `cloud.kaleschke.info`, nativer Nextcloud-Login, WebDAV/CardDAV faehig | CalDAV/CardDAV-Redirect via Traefik-Labels |
| `homeassistant` | ✅ vorbereitet | `frontend_net`, `smarthome_net` | Traefik via `home.kaleschke.info`, native HA-Auth | Home Assistant Container im GitOps-Stack `smart-home/`; kein HAOS, kein Supervised; Fach-YAML kommt aus `smart-home-kalli`, `.storage` bleibt in `/mnt/user/appdata/homeassistant` | Deploy, Onboarding, Restore-Probe, Cloud-Integrationen |
| `plex` | ✅ | `host` | Traefik via `plex.kaleschke.info` + Plex native Auth; LAN direkt `:32400` | Compose-Stack unter `host-services/plex/`; Host-Netz bleibt fuer Discovery / Plex GDM dokumentierte Ausnahme; Traefik routet per File-Provider-Ausnahme auf `http://192.168.178.58:32400`, weil Docker-Labels Host-Netz-Container aus Traefik heraus auf `127.0.0.1` routen wuerden; kein direkter WAN-Port 32400 und Plex Remote Access bleibt aus; Server geclaimt von `Xeridos`; Smart-TVs (Schlafzimmer, Wohnzimmer) ueber WLAN-LAN per mDNS | — |
| `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 |
@@ -389,10 +394,12 @@ Die Blockmigration aus der Portainer-/Dockerman-Phase ist abgeschlossen: Traefik
| `mail-archiver` | `frontend_net` + `backend_net` | braucht Internetzugang für IMAP-Abruf (GMX, Gmail) und DB-Zugang |
| `traefik/dynamic/*` | manueller Host-Sync trotz GitOps | File-Provider bleibt bewusst fuer `middlewares.yml`, `tls.yml` und `dashboards.yml`; Komodo deployed diese Dateien nicht automatisch |
| `nextcloud` | keine zentrale ForwardAuth-Middleware | Nextcloud bringt eigene Auth, Clients und WebDAV/CardDAV-Endpunkte mit; Traefik bleibt Reverse Proxy, Auth bleibt app-nativ |
| `monitoring-influxdb3-core` | Host-Port 8181 auf LAN-IP; `user: "0"` | Home Assistant laeuft in einer VM ausserhalb des Compose-Netzes und muss Metriken schreiben koennen; keine Traefik-Route, kein `frontend_net`, Zugriff nur ueber Token und LAN-IP `INFLUXDB_BIND_IP`; InfluxDB 3 Core benoetigt im aktuellen Container-Setup Root-Rechte fuer den lokalen Object-Store-Pfad im named volume |
| `monitoring-influxdb3-core` | Host-Port 8181 auf LAN-IP; `user: "0"` | Home Assistant schreibt spaeter Langzeitdaten. Nach der HA-Container-Entscheidung muss der Writer-Pfad in der Influx-Phase explizit gewaehlt werden: entweder LAN-Bind via `INFLUXDB_BIND_IP` oder gezieltes gemeinsames internes Netz. Keine Traefik-Route, Zugriff nur ueber Token; InfluxDB 3 Core benoetigt im aktuellen Container-Setup Root-Rechte fuer den lokalen Object-Store-Pfad im named volume |
| `monitoring-promtail` | Docker-Socket read-only | Docker-Log-Discovery fuer Loki; keine Schreibrechte, keine Appdaten-Persistenz ueber den Socket |
| `n8n` | keine pauschale Authelia-Middleware | Webhook-Endpunkte (`/webhook/*`, `/webhook-test/*`) muessen ohne ForwardAuth erreichbar bleiben; n8n bringt eigene Owner-/Login-Auth mit (analog Komodo/Nextcloud) |
| `plex` | Traefik ohne Authelia, File-Provider-Ausnahme trotz Host-Netz | Plex bringt native Konto-/Client-Auth mit; vorgeschaltete ForwardAuth wuerde Plex Web, Apps und Client-Flows stoeren. Docker-Labels sind fuer diesen Host-Netz-Container ungeeignet, weil Traefik sonst `127.0.0.1:32400` nutzt; daher `traefik/dynamic/plex.yml` mit Ziel `192.168.178.58:32400`. Route nur ueber Traefik/443 (`plex.kaleschke.info`), direkter Plex-WAN-Port 32400 und Plex Remote Access bleiben deaktiviert. |
| `homeassistant` | Traefik ohne Authelia, Fach-YAML aus separatem Repo | Home Assistant bringt eigene Auth, mobile Apps, Webhooks und Integrationsfluesse mit. Der Container haengt in `frontend_net` fuer Traefik und in `smarthome_net` fuer MQTT/Zigbee2MQTT/ESPHome. `.storage` und Secrets bleiben in Appdata und werden per Borg gesichert, nicht versioniert. |
| `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. |
---
+2 -2
View File
@@ -1,7 +1,7 @@
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:release@sha256:c15bff75068effb03f4355997d03dc7e0fc58720c2b54ad6f7f10d1bc57efaa5
image: ghcr.io/immich-app/immich-server:v2.7.5@sha256:c15bff75068effb03f4355997d03dc7e0fc58720c2b54ad6f7f10d1bc57efaa5
restart: unless-stopped
depends_on:
- redis
@@ -32,7 +32,7 @@ services:
immich-machine-learning:
container_name: immich_machine_learning
image: ghcr.io/immich-app/immich-machine-learning:release@sha256:a2501141440f10516d329fdfba2c68082e19eb9ba6016c061ac80d23beadf7f3
image: ghcr.io/immich-app/immich-machine-learning:v2.7.5@sha256:a2501141440f10516d329fdfba2c68082e19eb9ba6016c061ac80d23beadf7f3
restart: unless-stopped
environment:
# Workaround fuer gunicorn-25.1.0-Control-Socket-Bug: der Worker haengt
+1 -1
View File
@@ -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
+2 -2
View File
@@ -1,6 +1,6 @@
services:
nextcloud:
image: nextcloud:33.0.5-apache@sha256:330c866bf5aba2a000b2f4300344f5e84442ce68cd498e290d0c096271b566bc
image: nextcloud:33.0.5-apache@sha256:56bdc45109067500fd0832fa64832b7c77a167d9394cbf5f0f4b59740b94194d
container_name: nextcloud
restart: unless-stopped
depends_on:
@@ -46,7 +46,7 @@ services:
- "traefik.http.services.nextcloud.loadbalancer.server.port=80"
nextcloud-postgres:
image: postgres:18.4@sha256:8ff36f3c66371cba71d20ceedccfc3de9669a68737607888c4ef0af93abe8e39
image: postgres:18.4@sha256:29ee7bb30d804447dc9a91fd0d74322ae1dc3a4072cc6346f70a5ed6e783b565
container_name: nextcloud-postgres
restart: unless-stopped
environment:
+51
View File
@@ -11,6 +11,57 @@ in `HOMELAB_ARCHITECTURE_MASTER_V2.md` §13, `docs/MASTER_TODO.md` (Geparkt),
---
## 2026-06-12 - Home Assistant als Container im GitOps-Stack
**Entscheidung:** Home Assistant laeuft neu als `homeassistant` Container im
Stack `smart-home/`, nicht als HAOS-VM und nicht als Supervised-Installation.
Mosquitto laeuft als eigener Container im selben Stack; Zigbee2MQTT und ESPHome
werden spaeter ebenfalls als eigenstaendige Container ergaenzt. HA haengt in
`frontend_net` fuer Traefik und in `smarthome_net` fuer MQTT/Zigbee2MQTT/ESPHome.
Das Fachrepo `smart-home-kalli` liefert versionierte HA-YAML-Dateien read-only;
`.storage`, `secrets.yaml` und Integrations-State bleiben in
`/mnt/user/appdata/homeassistant`.
**Kontext:** Das fruehere HAOS-VM-Setup ging bei einem Crash ohne brauchbares
Backup verloren. Das Homelab betreibt produktive Dienste inzwischen ueber
Gitea, Komodo, Compose, Renovate und Borg. HA Container passt in dieses
Betriebsmodell und vermeidet eine zweite Update-/Backup-Welt. Supervised ist
kein Zielpfad mehr; HAOS bleibt die Alternative, falls Add-on-Komfort,
Matter/Thread/HomeKit-Discovery oder Host-nahe HA-Funktionen wichtiger werden
als GitOps-Konformitaet.
**Review-Trigger:** Viele mDNS-/SSDP-abhaengige lokale Integrationen
(HomeKit, Cast, Matter/Thread), Bedarf an HA-Add-ons als Betriebsstandard,
oder wiederholte Probleme durch Bridge-Netzwerkbetrieb.
## 2026-06-12 - Ecowitt-Ingress bleibt bewusste Phase-2-Entscheidung
**Entscheidung:** In Phase 1 wird kein Host-Port `8123` fuer Home Assistant
veroeffentlicht. Ecowitt wird spaeter entweder ueber eine gezielte
Traefik-HTTP-Ausnahme fuer den Webhook-Pfad angebunden oder, falls der globale
HTTP-zu-HTTPS-EntryPoint-Redirect nicht sauber selektiv abloesbar ist, ueber
einen dokumentierten LAN-only Host-Port `8123`.
**Kontext:** Ecowitt kann nur HTTP und kein HTTPS. Traefik hat aktuell einen
globalen `web` -> `websecure` Redirect auf EntryPoint-Ebene. Ein normaler
HTTP-Router kann diese Regel voraussichtlich nicht umgehen, ohne Traefik selbst
umzubauen. Deshalb wird die Entscheidung nicht vorgezogen.
**Review-Trigger:** Start der Ecowitt-/InfluxDB-Phase oder Umbau der Traefik
HTTP-Redirect-Architektur.
## 2026-06-11 — Host-DNS-Fallback aktiv (AdGuard-SPOF entschaerft)
**Entscheidung:** Unraid-Host nutzt `eth0` DNS server 1 = `192.168.178.58` (AdGuard) und **DNS server 2 = `192.168.178.1`** (FRITZ!Box) als Failover.
**Kontext:** AdGuard war einziger LAN-Resolver; ein Recreate hat 2026-06 einen Bulk-Deploy zerlegt, weil Docker-Pulls am eigenen DNS-Container scheiterten. Der Fallback bleibt nur passiv aktiv (Go-Resolver springt erst bei Socket-Fehler weiter), der Filter wirkt im Normalbetrieb unveraendert. `options rotate` ist nicht gesetzt. Umsetzung der Empfehlung 3a aus dem Optimierungs-Assessment vom 2026-06-10. Runbook: `docs/runbooks/komodo-bulk-deploy-dns.md`.
**Review-Trigger:** Wenn AdGuard durch eine andere Filter-Loesung ersetzt wird oder ein zweiter Host-Resolver verfuegbar ist.
## 2026-06-11 — Hetzner Storage Box: automatische Snapshots aktiv
**Entscheidung:** Automatische Snapshots auf der Hetzner Storage Box (BX11, `u565255.your-storagebox.de`) sind aktiv: taeglich um 05:30 UTC (nach dem Borg-Lauf 04:30 lokal), Retention 7 Tage, Snapshot-Verzeichnis sichtbar fuer Einzeldatei-Restore via `.zfs/snapshot/`.
**Kontext:** Borg `append-only` ist bewusst nicht umgesetzt (siehe Eintrag 2026-06-01); damit war ein kompromittierter Host bisher in der Lage, auch das Off-site-Backup zu loeschen. Storage-Box-Snapshots sind host-seitig nicht loeschbar und im BX11-Tarif inklusive. Kosten: 0 EUR zusaetzlich. Umsetzung der Empfehlung 2 aus dem Optimierungs-Assessment vom 2026-06-10.
**Review-Trigger:** Hetzner-Quota-Druck (aktuell 65 GB / 1 TB - viel Luft) oder Aenderung der Backup-Strategie.
## 2026-06-11 — Doku-Konsolidierung: ein Fakt, ein Zuhause
**Entscheidung:** Die Dokumentation wird nach `docs/archive/2026/homelab-doku-optimierung-2026-06-11.md` konsolidiert: `MASTER_TODO.md` ist die einzige Statusliste, dieses Register die einzige Entscheidungssammlung, `docs/archive/` nimmt abgeschlossene Snapshots auf, Erledigtes verlaesst die Arbeitskopie. Keine Ordner-Restruktur des Bestands.
+10 -4
View File
@@ -1,6 +1,6 @@
# Master To-do - KalliLab CORE
Typ: Status/To-do · Stand: 2026-06-11 · 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
@@ -24,6 +24,9 @@ Host-Reports (`/mnt/user/backups/restore-reports/`) und in der Git-Historie.
| Restore-Test Unraid OS Flash (Stick-Boot) | Operator | Artefakt-Validierung 2026-06-05 erledigt (`ops/maintenance/check-unraid-flash-backup.sh`). **Verbleibt:** physischer Ersatzstick-Boot-Test, wenn ein Wegwerf-Stick bereitliegt | `ops/restore-tests/unraid-flash-runbook.md` |
| Restore-Test Tailscale | Operator | State-Validierung + Reconnect nur auf Wegwerf-Host/VM, danach Geraet in Tailscale-Admin entfernen | `ops/restore-tests/tailscale-runbook.md` |
| Authelia OIDC fuer Apps | Operator/Claude | Live: Grafana + Mealie (verifiziert), Paperless deployed (Login-Test offen). Immich + Nextcloud bewusst geparkt bis Family-Onboarding (siehe `docs/DECISIONS.md` 2026-06-06) | `docs/AUTHELIA_OIDC_PLAN.md` |
| Glance-v2-Widgets: Tokens setzen | Operator | In Komodo Stack-ENV fuer `ops-glance` setzen: `GLANCE_KOMODO_API_KEY`/`_SECRET` (Komodo read-only API-Key), `GLANCE_GITEA_TOKEN` (read-only, scope `read:repository`), `GLANCE_PAPERLESS_TOKEN`, `GLANCE_MEALIE_TOKEN`; bis dahin zeigen die neuen Widgets Fehler/leer. Speedtest-Widget: falls weiter 0.0, API-Response pruefen | `ops/glance/config/` |
| Home Assistant Foundation-Abnahme | Operator/Codex | Sofort: Owner-Onboarding abschliessen. Danach temporaere Authelia-Middleware von der HA-Route entfernen, Komodo-Stack-Eintrag + Webhook sauber anlegen/verifizieren, HA-MQTT-Smoke-Test und HA-native `backup.create` testen, Restore-Probe fuer HA/Mosquitto dokumentieren | `docs/runbooks/smart-home-bootstrap.md`, `docs/RESTORE_MATRIX.md` |
| Audit-PDF aus `docs/` entfernen | Operator | `docs/KalliLab_CORE_Audit_2026-06-06.pdf` (untracked) extern ablegen (H:/ oder Documents-Share) und lokal loeschen; Binaerdateien gehoeren nicht ins GitOps-Repo | Doku-Regeln `docs/REPO_MAP.md` |
---
@@ -54,6 +57,7 @@ Bewusst nicht jetzt - Begruendungen in `docs/DECISIONS.md`, hier nur Thema und T
| Immich Redis named volume | passende Wartung am Immich-Stack | `docs/SERVICE_CATALOG.md` |
| Storage-Wachstum (zweite NVMe, zweite Array-Disk, ZFS/BTRFS) | Trigger aus Capacity-Doku | `docs/STORAGE_LAYOUT.md`, `docs/CAPACITY_AND_LIFECYCLE.md` |
| Wiederkehrende Restore-Drills | laufend nach Kadenz, inkl. quartalsweisem Frische-Negativtest (`run-restore-checks.sh freshness-negative`) | `docs/RESTORE_MATRIX.md`, `ops/restore-tests/schedule.md` |
| Doku-Quartals-Gaertnern (~15 min) | quartalsweise, erster Lauf mit Q3-Review ab 2026-07-01: Datiertes archivieren, Done-/Review-Logs kuerzen, tote Links pruefen | `docs/REPO_MAP.md` Doku-Regeln |
---
@@ -67,9 +71,11 @@ Bewusst nicht jetzt - Begruendungen in `docs/DECISIONS.md`, hier nur Thema und T
## Zuletzt erledigt (Kurzlog, max. 5 Eintraege)
- **2026-06-11** Doku-Konsolidierung umgesetzt: `docs/archive/`, `docs/DECISIONS.md`, Statuslisten auf diese Datei reduziert, Restore-Doku zusammengefuehrt. Details: `docs/DECISIONS.md` Eintrag 2026-06-11.
- **2026-06-06** Wochenend-Sprint abgeschlossen: Veeam-Recovery-Test, DR-Workstation-Kit final (DR-Smoke OK), Tailscale-ACL restriktiv + redundanter Docker-Stack entfernt, Gast-/IoT-Netz validiert, AdGuard-/Redis-Restore-Smokes, Authelia-2FA-Catch-all, Frische-Negativtest. Belege: Host-Reports, `docs/DECISIONS.md`.
- **2026-06-03** Restore-Backlog geschlossen: Nextcloud, Shared-PG18-Cluster, Komodo-Mongo, Mailarchiver, Mealie, Traefik. Reports unter `/mnt/user/backups/restore-reports/`.
- **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.
---
+1
View File
@@ -14,6 +14,7 @@ geloescht (Git-Historie ist das Archiv). Verbindliche Doku-Regeln:
| Datei | Zweck |
|---|---|
| `../README.md` | kurzer Repo-Einstieg |
| `../AGENTS.md` | Einstiegspunkt fuer KI-Agenten (Codex u. a.) |
| `../HOMELAB_ARCHITECTURE_MASTER_V2.md` | Architektur-Quelle fuer Netz, Zugriff und Ausnahmen |
| `WORKFLOW.md` | verbindlicher GitOps-/No-Drift-Ablauf |
| `REPO_MAP.md` | technische Landkarte des Repositories + Doku-Regeln |
+6 -1
View File
@@ -60,6 +60,9 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
| Glance | Git / Borg-Repo | Repo-Konfiguration unter `ops/glance/config/glance.yml`; keine kritische Datenpersistenz | keine | `GLANCE_IMMICH_API_KEY`, `GLANCE_ADGUARD_USERNAME`, `GLANCE_ADGUARD_PASSWORD`, `GLANCE_SPEEDTEST_API_KEY` | Traefik, Authelia, optional interne API-Ziele | Dashboard startet, Widgets laden, Docker-Status laeuft nur ueber `glance-docker-socket-proxy` |
| ntfy | Borg / Share | `/mnt/user/appdata/ntfy` | keine | keine besonderen Secret-Dateien dokumentiert | Traefik | UI und Push-Endpunkt erreichbar |
| Paperless-GPT | Borg / Share | `/mnt/user/appdata/paperless-gpt` | keine eigene DB | `PAPERLESS_API_TOKEN`, `OPENAI_API_KEY` | Traefik, Paperless, OpenAI API | UI startet, Konfiguration vorhanden; LLM-Provider zeigt `openai` / `gpt-5.4-mini` |
| Home Assistant | Borg + HA-native Backups + Fachrepo | `/mnt/user/appdata/homeassistant` inkl. `.storage`, `secrets.yaml`, `trusted_proxies.yaml`; Fach-YAML aus `/mnt/user/services/smart-home-kalli/home-assistant` | HA-native Backup-Artefakte unter `/mnt/user/appdata/homeassistant/backups` falls vorhanden; keine externe DB in Phase 1 | HA-Secrets in `secrets.yaml`, Integrations-Tokens in `.storage`, MQTT-Credentials, spaeter InfluxDB-Token | Traefik, `frontend_net`, `smarthome_net`, Mosquitto, Fachrepo-Clone | `https://home.kaleschke.info` zeigt Login, MQTT-Integration verbindet sich, `backup.create` funktioniert, Energy-Dashboard-Konfiguration bleibt erhalten |
| Smart-Home MQTT / Mosquitto | Borg / Share | `/mnt/user/appdata/mosquitto/config`, `/mnt/user/appdata/mosquitto/data`, `/mnt/user/appdata/mosquitto/log` | Mosquitto persistiert retained messages/subscriptions dateibasiert | `passwordfile`, `aclfile`, spaeter per-Device-User | `smarthome_net`, Home Assistant, spaeter ESPHome/Zigbee2MQTT | Container startet, HA kann sich authentifiziert verbinden, retained Testtopic bleibt nach Restart erhalten |
| Smart-Home Fachrepo | Gitea + Borg-Repo-Clone | `/mnt/user/services/smart-home-kalli` | keine | keine echten Secrets im Repo; `secrets-template/` nur Beispiele | Gitea, Home Assistant Mounts | `git status` sauber, HA liest `configuration.yaml` und `packages/` aus dem Clone |
---
@@ -77,6 +80,8 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
| InfluxDB 3 Core | historischer Altstand / Datenuebernahme | `/mnt/user/appdata/influxdb3/data`, `/mnt/user/appdata/influxdb3/plugins` | dateibasierter Object Store | `influxdb3_admin_token.json` | `monitoring-influxdb3-core` | Datenpfad wird vom Monitoring-Zielstack weitergenutzt und darf nicht blind geloescht werden |
| Loki / Alloy | historischer Altstand | `/mnt/user/appdata/loki/config`, `/mnt/user/appdata/loki/data`, `/mnt/user/appdata/alloy/config` | keine primaere DB; Loki-Dateispeicher war transient | keine zusaetzlichen Secrets | nicht aktiv | Compose-Pfad aus aktivem Repo entfernt; aktuelle Logsammlung laeuft ueber `monitoring-loki`/`monitoring-promtail` |
| Monitoring Stack | Rebuild + named volumes + InfluxDB-Appdata | `prometheus_data`, `loki_data`, `promtail_positions`, `grafana_data`; InfluxDB unter `/mnt/user/appdata/influxdb3/data` und `/mnt/user/appdata/influxdb3/plugins`; Provisioning aus `monitoring/grafana/provisioning` | Prometheus-TSDB, Loki-Dateispeicher und InfluxDB-Dateistore; Diagnose-/Langzeitdaten, keine Tier-1-Restore-Quelle | `monitoring_grafana_admin_password.txt`, `monitoring_grafana_influxdb_token.txt`, `influxdb3_admin_token.json` | `monitoring_net`, `monitoring_influx_lan`, `frontend_net`, Traefik, Authelia, Docker socket read-only fuer Promtail, Host-Mounts fuer node-exporter/cAdvisor | `https://monitoring.kaleschke.info` leitet zu Authelia; Prometheus Targets sind up; Grafana-Datasources `Prometheus`, `Loki` und `InfluxDB 3 Core` funktionieren |
| Zigbee2MQTT (geplant) | Borg + Fachrepo | `/mnt/user/appdata/zigbee2mqtt` inkl. `configuration.yaml`, `database.db`, `coordinator_backup.json`, `state.json`; Fach-Doku im Repo `smart-home-kalli` | keine externe DB | `network_key`, MQTT-Credentials, LAN-Koordinator-IP/Firmwarestand | Mosquitto, LAN-PoE-Koordinator, `smarthome_net` | Z2M startet, Coordinator verbindet sich, geraete bleiben gepairt, Testgeraet sendet MQTT-State |
| ESPHome (geplant) | Fachrepo + Borg fuer Build-/Runtime-State | `/mnt/user/appdata/esphome` falls Dashboard/Build-Cache genutzt wird; YAML unter `/mnt/user/services/smart-home-kalli/esphome` | keine | ESPHome-Secrets ausserhalb Git, API-/OTA-Keys | WLAN/LAN, Mosquitto falls MQTT genutzt wird | Dashboard startet, ein Testgeraet kompiliert/validiert, OTA/API-Verbindung funktioniert |
| Hermes Agent | VM-seitig offen | `/mnt/user/appdata/hermes-agent/data`, `/mnt/user/appdata/hermes-agent/ssh` | keine eigene DB | Host-`.env` fuer Provider-/API-/Home-Assistant-Tokens, `hermes_runner_id_ed25519`, `HERMES_DASHBOARD_HOST` | separate Hermes-VM/Runner, Traefik, Authelia, `hermes_net` | NAS-Stack nicht starten, solange Runner-VM und echte `.env` fehlen |
| ddns-updater | Rebuildbar | geringe Persistenzrelevanz | keine | Provider-Zugang ueber Stack ENV | Internetzugang | Update-Job laeuft |
@@ -180,4 +185,4 @@ Verbleibende offene Restore-Pfade ohne vollstaendigen Test:
Die Ablaeufe je Dienst liegen als Runbooks und automatisierte Skripte unter
`ops/restore-tests/` (Einstieg: `ops/restore-tests/README.md`). Fuer die noch
offenen Pfade: `ops/restore-tests/unraid-flash-runbook.md` und
`ops/restore-tests/tailscale-runbook.md`.
`ops/restore-tests/tailscale-runbook.md`.
+2 -2
View File
@@ -40,7 +40,7 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
| Komodo Mongo | Root Password | `/mnt/user/appdata/secrets/komodo_mongo_password.txt` -> `MONGO_INITDB_ROOT_PASSWORD_FILE` | aktiv |
| Komodo Core | App Secrets | Stack ENV `${KOMODO_SECRET_KEY}`, `${KOMODO_WEBHOOK_SECRET}`, `${KOMODO_JWT_SECRET}`, `${KOMODO_MONGO_PASSWORD}`, `${KOMODO_PERIPHERY_PASSKEY}` | aktiv |
| Gitea Push Mirror | GitHub fine-grained PAT fuer `michaelkaleschke-spec/homelab-infra` | Gitea Repository Mirror Settings, persistent in `/mnt/user/services/gitea/data`; kein Datei-Secret im Repo | aktiv |
| Glance | Community Widget API Tokens | Stack ENV `${GLANCE_IMMICH_API_KEY}`, `${GLANCE_ADGUARD_USERNAME}`, `${GLANCE_ADGUARD_PASSWORD}`, `${GLANCE_SPEEDTEST_API_KEY}` | aktiv |
| Glance | Community Widget API Tokens | Stack ENV `${GLANCE_IMMICH_API_KEY}`, `${GLANCE_ADGUARD_USERNAME}`, `${GLANCE_ADGUARD_PASSWORD}`, `${GLANCE_SPEEDTEST_API_KEY}`, `${GLANCE_KOMODO_API_KEY}`, `${GLANCE_KOMODO_API_SECRET}`, `${GLANCE_GITEA_TOKEN}`, `${GLANCE_PAPERLESS_TOKEN}`, `${GLANCE_MEALIE_TOKEN}` (alle read-only anlegen) | 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 |
@@ -141,7 +141,7 @@ Einige Secrets liegen bewusst nur als Komodo Stack Environment Variables vor, we
| `speedtest-tracker` | `APP_KEY`, `ADMIN_PASSWORD` | Komodo-Mongo-Dump -> Vaultwarden -> externe Notiz | `APP_KEY` ist verschluesselungsrelevant; bei echtem Verlust App-State frisch initialisieren |
| `komodo-core` | `KOMODO_SECRET_KEY`, `KOMODO_WEBHOOK_SECRET`, `KOMODO_JWT_SECRET`, `KOMODO_MONGO_PASSWORD`, `KOMODO_PERIPHERY_PASSKEY` | Vaultwarden -> externe Notiz (Henne-Ei: Komodo-Mongo-Dump ist hier **nicht** Restore-Quelle, weil Komodo dafuer schon laufen muesste) | siehe `docs/SERVICES_RECOVERY.md` Komodo-Bootstrap; ohne diese Werte ist der Self-Stack nicht reproduzierbar |
| `hermes-agent` | `HERMES_DASHBOARD_HOST` plus Provider-/API-/Home-Assistant-Tokens in Host-`.env` | Vaultwarden -> externe Notiz | Stack ist aktuell geparkt (Review 2026-07-25); ohne Werte bleibt der Stack deaktiviert, kein Schaden am Rest |
| `glance` | `GLANCE_IMMICH_API_KEY`, `GLANCE_ADGUARD_USERNAME`, `GLANCE_ADGUARD_PASSWORD`, `GLANCE_SPEEDTEST_API_KEY` | Provider-UIs (Immich, AdGuard, Speedtest-Tracker) neu erzeugen | rebuildbar; Widgets bleiben leer bis Tokens neu erzeugt sind, kein kritischer Datentopf |
| `glance` | `GLANCE_IMMICH_API_KEY`, `GLANCE_ADGUARD_USERNAME`, `GLANCE_ADGUARD_PASSWORD`, `GLANCE_SPEEDTEST_API_KEY`, `GLANCE_KOMODO_API_KEY`, `GLANCE_KOMODO_API_SECRET`, `GLANCE_GITEA_TOKEN`, `GLANCE_PAPERLESS_TOKEN`, `GLANCE_MEALIE_TOKEN` | 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
+8 -1
View File
@@ -75,11 +75,18 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und
| `monitoring-promtail` | Docker-Log-Collector fuer Monitoring-Loki | `monitoring/docker-compose.yml`, `monitoring/promtail/promtail-config.yml` | intern | Docker socket read-only, Docker json-file Logs, Loki | named volume `promtail_positions` | rebuildbar | nein | Dokumentierte Host-Observability-Ausnahme: `/var/run/docker.sock:/var/run/docker.sock:ro` und `/var/lib/docker/containers:ro`; keine Appdaten, nur Log-Discovery |
| `monitoring-node-exporter` | Host-Metriken fuer Prometheus | `monitoring/docker-compose.yml` | intern `:9100` | Host `/proc`, `/sys`, `/` read-only, Prometheus | kein kritischer Zustand | rebuildbar | nein | Host-Observability-Ausnahme mit read-only Rootfs/Proc/Sys-Mounts |
| `monitoring-cadvisor` | Container-Metriken fuer Prometheus | `monitoring/docker-compose.yml` | intern `:8080` | Docker/Host read-only Mounts, Prometheus | kein kritischer Zustand | rebuildbar | nein | Host-Observability-Ausnahme fuer Container-Metriken; keine direkten Ports |
| `monitoring-influxdb3-core` | InfluxDB 3 Core fuer Home-Assistant-/Ecowitt-Langzeitdaten | `monitoring/docker-compose.yml` | Host-Port `8181` je `INFLUXDB_BIND_IP`, keine Public URL | Monitoring-Grafana, Home Assistant Writer | `/mnt/user/appdata/influxdb3/data`, `/mnt/user/appdata/influxdb3/plugins` | Tier 3 | nein | 2026-05-31 effektiv auf `127.0.0.1:8181` gebunden, also nicht LAN-exponiert; `user: "0"` ist fuer den lokalen Object-Store-Pfad dokumentiert; uebernimmt den bisherigen InfluxDB-Daten-/Token-Katalog; `401 Unauthorized` beim Curl ohne Token ist erwarteter Reachability-Test |
| `monitoring-influxdb3-core` | InfluxDB 3 Core fuer Home-Assistant-/Ecowitt-Langzeitdaten | `monitoring/docker-compose.yml` | Host-Port `8181` je `INFLUXDB_BIND_IP`, keine Public URL | Monitoring-Grafana, Home Assistant Writer | `/mnt/user/appdata/influxdb3/data`, `/mnt/user/appdata/influxdb3/plugins` | Tier 3 | nein | 2026-05-31 effektiv auf `127.0.0.1:8181` gebunden, also nicht LAN-exponiert; vor dem HA-Writer muss entschieden werden, ob `INFLUXDB_BIND_IP` auf eine LAN-IP geht oder HA gezielt ein gemeinsames internes Netz mit InfluxDB bekommt. `user: "0"` ist fuer den lokalen Object-Store-Pfad dokumentiert; `401 Unauthorized` beim Curl ohne Token ist erwarteter Reachability-Test |
| `hermes-gateway` | Hermes Agent Gateway/API intern | `ops/hermes-agent/docker-compose.yml` | intern `8642` auf `hermes_net` | SSH Runner (VM 192.168.178.143), LLM Provider, optional Home Assistant | `/mnt/user/appdata/hermes-agent/data`, SSH key path | Tier 3, Borg/Share | nein | NAS-Stack bleibt deaktiviert, solange die separate Hermes-VM/Runner-Seite nicht wiederhergestellt ist; kein Docker-Socket |
| `hermes-dashboard` | Hermes Dashboard | `ops/hermes-agent/docker-compose.yml` | `https://hermes.kaleschke.info` via `${HERMES_DASHBOARD_HOST}` | `hermes-gateway`, Traefik + Authelia | shared read-only data mount | Tier 3, Borg/Share | ja + Authelia | Compose-Profil `dashboard`; aktuell VM-seitig offen, nicht Teil des NAS-Finalstarts |
| `n8n` | Workflow-Automation; aktuell genutzt fuer Mail->LLM->Gitea-Issue (Inbox `Micha/mails`) | `apps/n8n/docker-compose.yml`, `apps/n8n/workflows/*.json` | `https://n8n.kaleschke.info` | Traefik (ohne pauschale Authelia, analog Komodo/Nextcloud), GMX IMAP, OpenAI API, Gitea API | `/mnt/user/appdata/n8n/data` (SQLite, Credentials, Workflows) | Tier 2, Borg + `n8n-data` (Credentials sind nur mit `N8N_ENCRYPTION_KEY` entschluesselbar) | ja, native Auth | Wegen Webhook-Endpunkten (`/webhook/*`) bewusst ohne `authelia@file`; eigene Login-/Owner-Auth bleibt Pflicht; `N8N_ENCRYPTION_KEY` ist Stack-ENV-Pflichtsecret, Verlust macht Credentials unbrauchbar. |
## Smart Home
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|---|---|---|---|---|---|---|---|---|
| `homeassistant` | Zentrale Smart-Home-Steuerung, Energy Dashboard, Integrations-Hub | Runtime: `smart-home/docker-compose.yml`; Fachkonfiguration: Repo `smart-home-kalli` | `https://home.kaleschke.info`; kein direkter Host-Port in Phase 1 | Traefik, `frontend_net`, `smarthome_net`, `smarthome-mosquitto`, Fachrepo unter `/mnt/user/services/smart-home-kalli` | `/mnt/user/appdata/homeassistant` inkl. `.storage`, `secrets.yaml`, `trusted_proxies.yaml`; YAML-Fachdateien read-only aus `/mnt/user/services/smart-home-kalli/home-assistant` | Tier 2, Borg + HA-native Backups; Restore-Probe Pflicht vor produktiven Energie-Automationen | ja, native HA-Auth | HA Container statt HAOS-VM; keine Add-ons, keine Supervised-Installation. `configuration.yaml` kommt aus dem Fachrepo, `.storage` wird nicht versioniert. `http.use_x_forwarded_for` und `trusted_proxies` muessen zur Traefik-Route passen. Ecowitt-HTTP bleibt Phase-2-Entscheidung wegen globalem Traefik-Redirect. |
| `smarthome-mosquitto` | MQTT-Broker fuer HA, spaeter ESPHome und Zigbee2MQTT | `smart-home/docker-compose.yml`, `smart-home/mosquitto/config/mosquitto.conf` | intern `smarthome_net:1883`; kein LAN-Port in Phase 1 | `smarthome_net`, Passwort-/ACL-Dateien in Appdata | `/mnt/user/appdata/mosquitto/config`, `/mnt/user/appdata/mosquitto/data`, `/mnt/user/appdata/mosquitto/log` | Tier 2, Borg; Passwortdatei, ACLs und persistente Broker-Daten relevant | nein | LAN-Port `1883` erst in ESPHome-Phase mit ACLs und per-Device-Usern. |
## Host Operations
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
+4 -4
View File
@@ -1,6 +1,6 @@
# Runbook: Komodo Bulk-Deploy schlaegt mit DNS `connection refused` fehl
Stand: 2026-06-10 · Typ: Runbook / ADR-light · Status: Sofortmassnahme empfohlen, noch nicht umgesetzt
Stand: 2026-06-11 · Typ: Runbook / ADR-light · Status: **Sofortmassnahme aktiv** (Host-DNS-Fallback gesetzt 2026-06-11 bzw. frueher)
## Symptom
@@ -19,12 +19,12 @@ Der Host nutzt **AdGuard Home als einzigen Resolver** (`/etc/resolv.conf` = nur
Es ist **kein** Webhook-, Auth- oder Docker-Hub-Rate-Limit-Problem: Webhooks authentifizieren sauber, `webhook_enabled=true`, Fehlerbild ist `connection refused` auf den eigenen DNS-Port direkt nach AdGuard-Recreate. Fuer den Pull-Pfad zaehlt der Docker-Daemon/Go-Resolver (iteriert ueber die `resolv.conf`-Server und springt bei Socket-Fehlern zum naechsten), nicht der glibc-Client.
## Sofortmassnahme (Schicht 1)
## Sofortmassnahme (Schicht 1) — umgesetzt
Unraid -> Settings -> Network Settings -> `eth0`:
- DNS server 1: `192.168.178.58` (AdGuard, bleibt)
- **DNS server 2: `192.168.178.1`** (FritzBox) -> Apply
- DNS server 1: `192.168.178.58` (AdGuard)
- **DNS server 2: `192.168.178.1`** (FritzBox) **gesetzt und aktiv** (Operator-Bestaetigung 2026-06-11; Apply-Button erfordert Docker-/VM-Stop, der gespeicherte Wert greift bereits ohne Re-Apply)
Damit ueberleben Registry-Pulls einen kurzen AdGuard-Ausfall via Resolver-Failover. Im Normalbetrieb wird weiter DNS1 (AdGuard) genutzt, der Filter bleibt aktiv.
+107
View File
@@ -0,0 +1,107 @@
# Smart-Home Bootstrap
Ziel: Den Stack `smart-home/` auf Kallilabcore initial startklar machen, ohne
Secrets oder UI-State ins Git zu schreiben.
## 1. Fachrepo auf dem Host bereitstellen
```sh
cd /mnt/user/services
git clone https://git.kaleschke.info/Micha/smart-home-kalli.git smart-home-kalli
cd smart-home-kalli
git checkout main
```
Der Home-Assistant-Container mountet daraus einzelne YAML-Dateien read-only nach
`/config`.
## 2. Home-Assistant-Appdata vorbereiten
```sh
mkdir -p /mnt/user/appdata/homeassistant
cp /mnt/user/services/smart-home-kalli/secrets-template/secrets.yaml.example \
/mnt/user/appdata/homeassistant/secrets.yaml
cp /mnt/user/services/smart-home-kalli/secrets-template/trusted_proxies.yaml.example \
/mnt/user/appdata/homeassistant/trusted_proxies.yaml
```
Danach `trusted_proxies.yaml` auf das echte Traefik-/`frontend_net`-Subnetz
anpassen:
```sh
docker network inspect frontend_net
```
## 3. Mosquitto vorbereiten
```sh
mkdir -p /mnt/user/appdata/mosquitto/config \
/mnt/user/appdata/mosquitto/data \
/mnt/user/appdata/mosquitto/log
docker run --rm -it \
-v /mnt/user/appdata/mosquitto/config:/mosquitto/external_config \
eclipse-mosquitto:2.0.22 \
mosquitto_passwd -c /mosquitto/external_config/passwordfile homeassistant
cat > /mnt/user/appdata/mosquitto/config/aclfile <<'EOF'
user homeassistant
topic readwrite #
EOF
```
Das initiale Passwort anschliessend in
`/mnt/user/appdata/homeassistant/secrets.yaml` eintragen. LAN-Port `1883` bleibt
in Phase 1 geschlossen.
## 4. Stack deployen
Komodo-Stack:
- Repo: `homelab-infra`
- Pfad: `smart-home/docker-compose.yml`
- Branch: nach Review `master`
Nach dem Start pruefen:
```sh
docker ps --filter name=homeassistant
docker ps --filter name=smarthome-mosquitto
docker logs --tail=100 homeassistant
docker logs --tail=100 smarthome-mosquitto
```
## 5. Smoke-Test
- `https://home.kaleschke.info` zeigt die Home-Assistant-Oberflaeche.
- Keine Trusted-Proxy-Fehler im HA-Log.
- MQTT-Integration verbindet sich mit Host `smarthome-mosquitto`, Port `1883`.
- HA-native Backup-Erstellung funktioniert.
## 6. Fachrepo-Update
Das Fachrepo `/mnt/user/services/smart-home-kalli` ist kein eigener
Komodo-Stack. Aenderungen wirken erst nach diesem Host-Ablauf:
```sh
cd /mnt/user/services/smart-home-kalli
git pull --ff-only origin main
docker restart homeassistant
```
Der Restart ist Pflicht, weil `configuration.yaml`, `automations.yaml`,
`scripts.yaml` und `scenes.yaml` als Einzeldateien in den Container gemountet
werden. Nach einem `git pull` kann Docker sonst noch den alten Datei-Inode sehen.
## 7. UI-Editor-Politik
`automations.yaml`, `scripts.yaml` und `scenes.yaml` sind read-only aus Git
gemountet. Der Home-Assistant-UI-Editor fuer diese Dateien ist deshalb nicht der
primaere Schreibweg. Automationen und Scripts werden in Git gepflegt; UI-State
und Integrations-State bleiben in `.storage` und werden per Borg gesichert.
## 8. Abnahmebedingung
Vor produktiven Energie-Automationen muss ein Restore-Test fuer
`/mnt/user/appdata/homeassistant`, `/mnt/user/appdata/mosquitto` und den Clone
`/mnt/user/services/smart-home-kalli` dokumentiert sein.
+1 -1
View File
@@ -1,6 +1,6 @@
services:
postgresql17:
image: postgres:18.4@sha256:8ff36f3c66371cba71d20ceedccfc3de9669a68737607888c4ef0af93abe8e39
image: postgres:18.4@sha256:29ee7bb30d804447dc9a91fd0d74322ae1dc3a4072cc6346f70a5ed6e783b565
container_name: postgresql17
restart: unless-stopped
+5
View File
@@ -48,6 +48,10 @@ The Unraid flash configuration archive is intentional as well and must be treate
| Grafana | SQLite dump from `monitoring_grafana_data` + provisioned config in Git | `/local/borg-dumps`, `monitoring/grafana/provisioning`, `monitoring/grafana/dashboards` |
| Filebrowser | file-backed state dump + file data | `/local/borg-dumps`, `/local/appdata/filebrowser` |
| InfluxDB 3 Core | file data | `/local/appdata/influxdb3/data`, `/local/appdata/influxdb3/plugins` |
| Home Assistant | HA-native backup + file state | `/local/appdata/homeassistant`, `/local/services/smart-home-kalli` |
| Smart-Home MQTT / Mosquitto | file data | `/local/appdata/mosquitto/config`, `/local/appdata/mosquitto/data` |
| Zigbee2MQTT (planned) | file data + coordinator state | `/local/appdata/zigbee2mqtt`, `/local/services/smart-home-kalli` |
| ESPHome (planned) | Fachrepo + optional build/runtime cache | `/local/services/smart-home-kalli/esphome`, optional `/local/appdata/esphome` |
| Hermes Agent | file data + SSH key | `/local/appdata/hermes-agent/data`, `/local/secrets/hermes_runner_id_ed25519` |
| BentoPDF | rebuildable | no critical persistence in compose |
@@ -87,6 +91,7 @@ The live Unraid User Scripts execute repo scripts from `/mnt/user/services/homel
- SQLite: `gitea`, `vaultwarden`, `speedtest-tracker`, `borg-ui`, `grafana`
- File-backed state: `filebrowser.bolt.dump`
- Unraid flash config: `unraid-flash-config.tar.gz` plus `unraid-flash-config.tar.gz.sha256`
- Home Assistant native backups: created by HA under `/mnt/user/appdata/homeassistant/backups` and captured as file state
## Explicitly Not Backed Up as Raw Live DB Files
+4
View File
@@ -20,5 +20,9 @@
/local/appdata/komodo/periphery
/local/appdata/komodo/core
/local/services/homelab-infra
/local/services/smart-home-kalli
/local/services/stacks
/local/services/posture-check
/local/appdata/homeassistant
/local/appdata/mosquitto/config
/local/appdata/mosquitto/data
+182
View File
@@ -0,0 +1,182 @@
/* ============================================================
KalliLab "Neon Ops v2" - Glance Custom CSS
Rotierende Akzentfarben pro Widget, Gradient-Zahlen,
animierte Header-Linien, kraeftige Glows
============================================================ */
/* --- Akzentfarben rotieren ueber die Widgets --- */
.widget { --kl-accent: 205 100% 60%; }
.widget:nth-of-type(4n+2) { --kl-accent: 172 95% 48%; }
.widget:nth-of-type(4n+3) { --kl-accent: 38 100% 55%; }
.widget:nth-of-type(4n) { --kl-accent: 145 85% 50%; }
/* --- Seiten-Hintergrund: kraeftigere Farb-Glows --- */
body {
background:
radial-gradient(1300px 700px at 85% -10%, hsla(205, 100%, 55%, 0.13), transparent 60%),
radial-gradient(1000px 600px at -10% 25%, hsla(172, 95%, 45%, 0.09), transparent 55%),
radial-gradient(900px 700px at 50% 115%, hsla(38, 100%, 50%, 0.07), transparent 60%),
var(--color-background);
background-attachment: fixed;
}
/* --- Widgets als Karten mit Akzentrand --- */
.widget {
background: linear-gradient(
160deg,
hsla(220, 30%, 100%, 0.05),
hsla(220, 30%, 100%, 0.015)
);
border: 1px solid hsl(var(--kl-accent) / 0.18);
border-radius: 14px;
padding: 14px 16px;
box-shadow:
0 10px 30px hsla(220, 60%, 3%, 0.4),
0 0 24px hsl(var(--kl-accent) / 0.06),
inset 0 1px 0 hsla(220, 40%, 90%, 0.05);
transition: border-color 0.2s ease, box-shadow 0.25s ease;
}
.widget:hover {
border-color: hsl(var(--kl-accent) / 0.55);
box-shadow:
0 12px 36px hsla(220, 60%, 3%, 0.45),
0 0 36px hsl(var(--kl-accent) / 0.16),
inset 0 1px 0 hsla(220, 40%, 90%, 0.07);
}
/* Widgets in Gruppen/Tabs nicht doppelt einrahmen */
.widget .widget {
background: none;
border: none;
border-radius: 0;
padding: 0;
box-shadow: none;
}
/* --- Widget-Titel: animierte Farbverlaufs-Linie in Akzentfarbe --- */
.widget-header {
letter-spacing: 0.14em;
position: relative;
padding-bottom: 7px;
margin-bottom: 4px;
color: hsl(var(--kl-accent) / 0.85);
}
.widget-header::after {
content: "";
position: absolute;
left: 0;
bottom: 0;
width: 64px;
height: 2px;
border-radius: 2px;
background: linear-gradient(
90deg,
hsl(var(--kl-accent)),
hsl(var(--kl-accent) / 0.25),
hsl(var(--kl-accent))
);
background-size: 200% 100%;
animation: kl-shimmer 4s linear infinite;
}
@keyframes kl-shimmer {
0% { background-position: 0% 0; }
100% { background-position: 200% 0; }
}
/* --- Grosse Zahlen: Gradient-Text + Glow --- */
.color-highlight.size-h2,
.color-highlight.size-h3,
.color-primary.size-h2,
.color-primary.size-h3 {
background: linear-gradient(
120deg,
hsl(var(--kl-accent)),
hsl(var(--kl-accent) / 0.55) 60%,
hsl(210, 30%, 95%)
);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
filter: drop-shadow(0 0 14px hsl(var(--kl-accent) / 0.35));
}
.color-positive {
text-shadow: 0 0 16px hsla(150, 95%, 45%, 0.45);
}
.color-negative {
text-shadow: 0 0 16px hsla(350, 95%, 58%, 0.45);
}
/* --- Status-Punkte leuchten --- */
.monitor-site-status-icon-compact,
.monitor-site-status-icon {
filter: drop-shadow(0 0 7px hsla(150, 95%, 45%, 0.55));
}
/* --- Navigation --- */
.nav-item.nav-item-current {
text-shadow: 0 0 18px hsla(212, 100%, 60%, 0.6);
}
/* --- Suchleiste --- */
.search {
border: 1px solid hsla(212, 90%, 65%, 0.2);
border-radius: 12px;
background: hsla(220, 30%, 100%, 0.04);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.search:focus-within {
border-color: hsla(212, 100%, 60%, 0.55);
box-shadow: 0 0 0 3px hsla(212, 100%, 55%, 0.15), 0 0 28px hsla(212, 100%, 55%, 0.12);
}
/* --- Server-Stats: Balken rund, gradient, glow --- */
.progress-bar {
border: none;
height: 13px;
border-radius: 999px;
background: hsla(220, 30%, 60%, 0.12);
box-shadow: inset 0 1px 3px hsla(220, 60%, 3%, 0.5);
overflow: hidden;
}
.progress-value {
border-radius: 999px;
background: linear-gradient(90deg, hsl(205, 100%, 55%), hsl(172, 95%, 48%));
box-shadow: 0 0 10px hsla(205, 100%, 55%, 0.35);
}
.progress-value-notice {
background: linear-gradient(90deg, hsl(38, 100%, 55%), hsl(355, 90%, 60%));
box-shadow: 0 0 12px hsla(355, 90%, 58%, 0.45);
}
/* --- Feinschliff --- */
::selection {
background: hsla(212, 100%, 50%, 0.35);
}
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-thumb {
background: hsla(220, 30%, 50%, 0.25);
border-radius: 8px;
}
::-webkit-scrollbar-thumb:hover {
background: hsla(212, 80%, 55%, 0.45);
}
/* Reduzierte Bewegung respektieren */
@media (prefers-reduced-motion: reduce) {
.widget-header::after {
animation: none;
}
}
+287
View File
@@ -0,0 +1,287 @@
traefik:
name: Traefik
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
url: https://traefik.kaleschke.info
description: Reverse Proxy
category: core
hide: false
gitea:
name: Gitea
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
url: https://git.kaleschke.info
description: GitOps Origin
category: core
hide: false
authelia:
name: Authelia
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authelia.svg
url: https://auth.kaleschke.info
description: ForwardAuth
category: core
hide: false
vaultwarden:
name: Vaultwarden
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vaultwarden.svg
url: https://vault.kaleschke.info
description: Password Vault
category: core
hide: false
postgresql17:
name: PostgreSQL 18
icon: si:postgresql
description: Shared DB
category: core
hide: false
Redis:
name: Redis
icon: si:redis
description: Shared Cache
category: core
hide: false
adguard:
name: AdGuard
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/adguard-home.svg
url: http://192.168.178.58:8082
description: DNS Filter
category: network
hide: false
unbound:
name: Unbound
icon: mdi:dns
description: Upstream Resolver
category: network
hide: false
ddns-updater:
name: DDNS Updater
icon: mdi:cloud-sync
description: Cloudflare DNS
category: network
hide: false
paperless-ngx:
name: Paperless-ngx
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/paperless-ngx.svg
url: https://paperless.kaleschke.info
description: Dokumente
category: apps
hide: false
paperless-gpt:
name: Paperless-GPT
icon: mdi:robot
url: https://paperless-gpt.kaleschke.info
description: Dokumenten-KI
category: apps
hide: false
immich_server:
name: Immich
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
url: https://immich.kaleschke.info
description: Fotos und Videos
category: apps
id: immich
hide: false
immich_postgres:
name: DB
parent: immich
category: apps
hide: false
immich_redis:
name: Redis
parent: immich
category: apps
hide: false
immich_machine_learning:
name: ML
parent: immich
category: apps
hide: false
mealie:
name: Mealie
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mealie.svg
url: https://mealie.kaleschke.info
description: Rezepte
category: apps
id: mealie
hide: false
mealie-postgres:
name: DB
parent: mealie
category: apps
hide: false
nextcloud:
name: Nextcloud
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg
url: https://cloud.kaleschke.info
description: Dateien und Sync
category: apps
id: nextcloud
hide: false
nextcloud-postgres:
name: DB
parent: nextcloud
category: apps
hide: false
nextcloud-redis:
name: Redis
parent: nextcloud
category: apps
hide: false
mail-archiver:
name: Mail Archiver
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mailcow.svg
url: https://mail.kaleschke.info
description: Mail-Archiv
category: apps
hide: false
ntfy:
name: ntfy
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/ntfy.svg
url: https://ntfy.kaleschke.info
description: Push Alerts
category: apps
hide: false
bentopdf:
name: BentoPDF
icon: mdi:file-pdf-box
url: https://pdf.kaleschke.info
description: PDF Tools
category: apps
hide: false
glance:
name: Glance
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glance.svg
url: https://glance.kaleschke.info
description: Homelab Uebersicht
category: ops
hide: false
glance-docker-socket-proxy:
name: Glance Socket Proxy
icon: si:docker
description: Read-only Docker API
category: ops
hide: false
monitoring-grafana:
name: Monitoring Grafana
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/grafana.svg
url: https://monitoring.kaleschke.info
description: Observability UI
category: ops
id: monitoring
hide: false
monitoring-prometheus:
name: Prometheus
parent: monitoring
category: ops
hide: false
monitoring-loki:
name: Loki
parent: monitoring
category: ops
hide: false
monitoring-promtail:
name: Promtail
parent: monitoring
category: ops
hide: false
monitoring-alertmanager:
name: Alertmanager
parent: monitoring
category: ops
hide: false
monitoring-alertmanager-ntfy-bridge:
name: ntfy Bridge
parent: monitoring
category: ops
hide: false
monitoring-blackbox-exporter:
name: Blackbox
parent: monitoring
category: ops
hide: false
monitoring-node-exporter:
name: Node Exporter
parent: monitoring
category: ops
hide: false
monitoring-cadvisor:
name: cAdvisor
parent: monitoring
category: ops
hide: false
monitoring-influxdb3-core:
name: InfluxDB 3
parent: monitoring
category: ops
hide: false
glances:
name: Glances
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glances.svg
url: https://glances.kaleschke.info
description: Host-Monitoring
category: ops
hide: false
scrutiny:
name: Scrutiny
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/scrutiny.svg
url: https://scrutiny.kaleschke.info
description: SMART
category: ops
hide: false
speedtest-tracker:
name: Speedtest
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/speedtest-tracker.png
url: https://speedtest.kaleschke.info
description: WAN-Messung
category: ops
hide: false
filebrowser:
name: Filebrowser
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/filebrowser.svg
url: https://files.kaleschke.info
description: Dateizugriff
category: ops
hide: false
code-server:
name: code-server
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vscode.svg
url: https://code.kaleschke.info
description: Web IDE
category: ops
hide: false
borg-ui:
name: Borg UI
icon: mdi:archive-sync
url: https://borg.kaleschke.info
description: Backup und Restore
category: ops
hide: false
hermes-dashboard:
name: Hermes
icon: mdi:shield-sparkles
url: https://hermes.kaleschke.info
description: Ops Agent UI
category: ops
id: hermes
hide: false
hermes-gateway:
name: Gateway
parent: hermes
category: ops
hide: false
komodo-core:
name: Komodo
icon: sh:komodo
url: https://komodo.kaleschke.info
description: Stack Manager
category: ops
id: komodo
hide: false
komodo-mongo:
name: Mongo
parent: komodo
category: ops
hide: false
komodo-periphery:
name: Periphery
parent: komodo
category: ops
hide: false
+40 -861
View File
@@ -1,5 +1,6 @@
server:
proxied: true
assets-path: /app/assets
branding:
app-name: KalliLab Dashboard
@@ -7,867 +8,45 @@ branding:
hide-footer: true
theme:
background-color: 210 20 13
primary-color: 212 100 50
positive-color: 140 70 40
negative-color: 4 78 57
contrast-multiplier: 1.25
text-saturation-multiplier: 0.9
background-color: 222 14 8
primary-color: 205 100 58
positive-color: 150 80 45
negative-color: 355 90 60
contrast-multiplier: 1.3
text-saturation-multiplier: 0.5
disable-picker: false
custom-css-file: /assets/custom.css
presets:
catppuccin-mocha:
background-color: 240 21 15
primary-color: 217 92 83
positive-color: 115 54 76
negative-color: 347 70 65
contrast-multiplier: 1.2
gruvbox-dark:
background-color: 0 0 16
primary-color: 43 59 81
positive-color: 61 66 44
negative-color: 6 96 59
kallilab-light:
light: true
background-color: 220 23 95
primary-color: 212 100 35
positive-color: 140 70 30
negative-color: 0 70 45
synthwave:
background-color: 265 35 10
primary-color: 320 100 65
positive-color: 175 100 50
negative-color: 0 100 65
contrast-multiplier: 1.3
matrix:
background-color: 130 25 6
primary-color: 130 100 55
positive-color: 130 100 45
negative-color: 35 100 55
contrast-multiplier: 1.25
text-saturation-multiplier: 1.2
pages:
- name: Home
slug: home
width: wide
head-widgets:
- type: search
search-engine: duckduckgo
new-tab: true
autofocus: true
placeholder: Suche im Web oder springe per Bang...
bangs:
- title: Gitea
shortcut: "!git"
url: https://git.kaleschke.info/explore/repos?q={QUERY}
- title: Paperless
shortcut: "!doc"
url: https://paperless.kaleschke.info/documents?query={QUERY}
- title: Nextcloud
shortcut: "!cloud"
url: https://cloud.kaleschke.info/apps/files/?dir=/{QUERY}
- title: Komodo
shortcut: "!komodo"
url: https://komodo.kaleschke.info
columns:
- size: small
widgets:
- type: group
widgets:
- type: custom-api
title: Day
body-type: string
skip-json-validation: true
cache: 1s
template: |
{{ $localTime := now }}
{{ $elapsedSeconds := add (mul $localTime.Hour 3600) (mul $localTime.Minute 60) | add $localTime.Second }}
{{ $dayProgress := div (mul $elapsedSeconds 100.0) 86400.0 }}
{{ $gradient := "#70a1ff" }}
{{ if gt $dayProgress 25.0 }}{{ $gradient = "#ff6b6b, #70a1ff" }}{{ end }}
{{ if gt $dayProgress 50.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df" }}{{ end }}
{{ if gt $dayProgress 75.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df, #70a1ff" }}{{ end }}
<div style="text-align: center;">
<div style="width: 100%; height: 12px; background: #23262f; border: 1px solid color-mix(in srgb, var(--color-text-subdue) 55%, transparent); border-radius: 10px; overflow: hidden;">
<div style="height: 100%; width: {{ $dayProgress }}%; background: linear-gradient(90deg, {{ $gradient }});"></div>
</div>
<div class="size-h1" style="margin-top: 6px;">{{ printf "%.2f" $dayProgress }}% des Tages sind vorbei</div>
</div>
- type: custom-api
title: Month
body-type: string
skip-json-validation: true
cache: 1s
template: |
{{ $localTime := now }}
{{ $month := $localTime.Month }}
{{ $daysInMonth := 31 }}
{{ if eq $month 2 }}{{ $daysInMonth = 28 }}{{ end }}
{{ if or (eq $month 4) (eq $month 6) (eq $month 9) (eq $month 11) }}{{ $daysInMonth = 30 }}{{ end }}
{{ $secondsToday := add (mul $localTime.Hour 3600) (mul $localTime.Minute 60) | add $localTime.Second }}
{{ $daysElapsed := add (sub $localTime.Day 1) (div $secondsToday 86400.0) }}
{{ $monthProgress := mul (div $daysElapsed $daysInMonth) 100.0 }}
{{ $gradient := "#70a1ff" }}
{{ if gt $monthProgress 25.0 }}{{ $gradient = "#ff6b6b, #70a1ff" }}{{ end }}
{{ if gt $monthProgress 50.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df" }}{{ end }}
{{ if gt $monthProgress 75.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df, #70a1ff" }}{{ end }}
<div style="text-align: center;">
<div style="width: 100%; height: 12px; background: #23262f; border: 1px solid color-mix(in srgb, var(--color-text-subdue) 55%, transparent); border-radius: 10px; overflow: hidden;">
<div style="height: 100%; width: {{ $monthProgress }}%; background: linear-gradient(90deg, {{ $gradient }});"></div>
</div>
<div class="size-h1" style="margin-top: 6px;">{{ printf "%.2f" $monthProgress }}% des Monats sind vorbei</div>
</div>
- type: custom-api
title: Year
body-type: string
skip-json-validation: true
cache: 1s
template: |
{{ $localTime := now }}
{{ $secondsToday := add (mul $localTime.Hour 3600) (mul $localTime.Minute 60) | add $localTime.Second }}
{{ $secondsElapsed := add (mul (sub $localTime.YearDay 1) 86400) $secondsToday }}
{{ $yearProgress := div (mul $secondsElapsed 100.0) (mul 365 86400) }}
{{ $gradient := "#70a1ff" }}
{{ if gt $yearProgress 25.0 }}{{ $gradient = "#ff6b6b, #70a1ff" }}{{ end }}
{{ if gt $yearProgress 50.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df" }}{{ end }}
{{ if gt $yearProgress 75.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df, #70a1ff" }}{{ end }}
<div style="text-align: center;">
<div style="width: 100%; height: 12px; background: #23262f; border: 1px solid color-mix(in srgb, var(--color-text-subdue) 55%, transparent); border-radius: 10px; overflow: hidden;">
<div style="height: 100%; width: {{ $yearProgress }}%; background: linear-gradient(90deg, {{ $gradient }});"></div>
</div>
<div class="size-h1" style="margin-top: 6px;">{{ printf "%.2f" $yearProgress }}% des Jahres sind vorbei</div>
</div>
- type: clock
hour-format: 24h
show-progress: true
timezones:
- timezone: Europe/Berlin
label: Berlin
- timezone: UTC
label: UTC
- type: calendar
first-day-of-week: monday
- type: bookmarks
title: Direkte Einstiege
groups:
- title: Core
color: 212 100 50
links:
- title: Komodo
url: https://komodo.kaleschke.info
icon: sh:komodo
- title: Gitea
url: https://git.kaleschke.info
icon: si:gitea
- title: Monitoring
url: https://monitoring.kaleschke.info
icon: si:grafana
- title: Ops
color: 45 70 55
links:
- title: Borg
url: https://borg.kaleschke.info
icon: mdi:archive
- title: Glances
url: https://glances.kaleschke.info
icon: sh:glances
- title: Scrutiny
url: https://scrutiny.kaleschke.info
icon: sh:scrutiny
- size: full
widgets:
- type: server-stats
title: Server Stats
servers:
- type: local
name: Kallilabcore
hide-mountpoints-by-default: false
- type: group
widgets:
- type: custom-api
title: Immich
title-url: https://immich.kaleschke.info
cache: 10m
url: http://immich_server:2283/api/server/statistics
headers:
x-api-key: ${GLANCE_IMMICH_API_KEY}
subrequests:
storage:
url: http://immich_server:2283/api/server/storage
headers:
x-api-key: ${GLANCE_IMMICH_API_KEY}
template: |
{{ $photos := .JSON.Int "photos" }}
{{ $videos := .JSON.Int "videos" }}
{{ $usageGiB := div (toFloat (.JSON.Int "usage")) 1073741824.0 }}
{{ $storage := .Subrequest "storage" }}
{{ $storageOK := and (ge $storage.Response.StatusCode 200) (le $storage.Response.StatusCode 299) }}
{{ $percentage := 0.0 }}
{{ if $storageOK }}{{ $percentage = $storage.JSON.Float "diskUsagePercentage" }}{{ end }}
<div class="flex justify-between text-center">
<div>
<div class="color-highlight size-h3">{{ $photos | formatNumber }}</div>
<div class="size-h6 uppercase">Fotos</div>
</div>
<div>
<div class="color-highlight size-h3">{{ $videos | formatNumber }}</div>
<div class="size-h6 uppercase">Videos</div>
</div>
<div>
<div class="color-highlight size-h3">{{ printf "%.0f" $usageGiB }} GiB</div>
<div class="size-h6 uppercase">Medien</div>
</div>
</div>
<div style="height: 8px; margin-top: 14px; border-radius: 999px; overflow: hidden; background: color-mix(in srgb, var(--color-text-subdue) 22%, transparent);">
<div style="height: 100%; width: {{ if $storageOK }}{{ printf "%.1f" $percentage }}%{{ else }}0%{{ end }}; border-radius: 999px; background: var(--color-primary);"></div>
</div>
<div class="size-h6 color-subdue" style="margin-top: 8px;">{{ if $storageOK }}{{ printf "%.1f" $percentage }}% Speicher belegt{{ else }}Speicher API nicht verfuegbar{{ end }}</div>
- type: monitor
title: Homelab Status
cache: 1m
sites:
- title: AdGuard Home
url: http://192.168.178.58:8082
check-url: http://adguard
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/adguard-home.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Authelia
url: https://auth.kaleschke.info
check-url: http://authelia:9091/api/health
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authelia.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Gitea
url: https://git.kaleschke.info
check-url: http://gitea:3000/api/healthz
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Traefik
url: https://traefik.kaleschke.info
check-url: http://traefik:8082/metrics
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Vaultwarden
url: https://vault.kaleschke.info
check-url: http://vaultwarden/alive
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vaultwarden.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Komodo
url: https://komodo.kaleschke.info
check-url: http://komodo-core:9120
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/komodo.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Paperless-ngx
url: https://paperless.kaleschke.info
check-url: http://paperless-ngx:8000
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/paperless-ngx.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Paperless-GPT
url: https://paperless-gpt.kaleschke.info
check-url: http://paperless-gpt:8080
icon: mdi:robot
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Immich
url: https://immich.kaleschke.info
check-url: http://immich_server:2283/api/server/ping
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Mealie
url: https://mealie.kaleschke.info
check-url: http://mealie:9000
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mealie.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Nextcloud
url: https://cloud.kaleschke.info
check-url: http://nextcloud/status.php
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: ntfy
url: https://ntfy.kaleschke.info
check-url: http://ntfy/v1/health
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/ntfy.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Mail Archiver
url: https://mail.kaleschke.info
check-url: http://mail-archiver:5000
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mailcow.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: BentoPDF
url: https://pdf.kaleschke.info
check-url: http://bentopdf:8080
icon: mdi:file-pdf-box
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Glance
url: https://glance.kaleschke.info
check-url: http://glance:8080
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glance.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Monitoring Grafana
url: https://monitoring.kaleschke.info
check-url: http://monitoring-grafana:3000/api/health
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/grafana.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Glances
url: https://glances.kaleschke.info
check-url: http://glances:61208
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glances.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Scrutiny
url: https://scrutiny.kaleschke.info
check-url: http://scrutiny:8080
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/scrutiny.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Speedtest Tracker
url: https://speedtest.kaleschke.info
check-url: http://speedtest-tracker
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/speedtest-tracker.png
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Filebrowser
url: https://files.kaleschke.info
check-url: http://filebrowser
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/filebrowser.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: code-server
url: https://code.kaleschke.info
check-url: http://code-server:8443
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vscode.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Borg UI
url: https://borg.kaleschke.info
check-url: http://borg-ui:8081
icon: mdi:archive-sync
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- size: small
widgets:
- type: custom-api
title: Internet
title-url: https://speedtest.kaleschke.info
cache: 1h
url: http://speedtest-tracker/api/v1/results/latest
headers:
Authorization: Bearer ${GLANCE_SPEEDTEST_API_KEY}
Accept: application/json
template: |
{{ $ip := .JSON.String "external_ip" }}
{{ if eq $ip "" }}{{ $ip = .JSON.String "data.interface.externalIp" }}{{ end }}
{{ $isp := .JSON.String "isp" }}
{{ if eq $isp "" }}{{ $isp = .JSON.String "data.isp" }}{{ end }}
{{ $server := .JSON.String "server_name" }}
{{ if eq $server "" }}{{ $server = .JSON.String "data.server_name" }}{{ end }}
<div style="display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center;">
<div class="color-primary size-h2" style="font-weight: 700;">{{ if ne $ip "" }}{{ $ip }}{{ else }}WAN online{{ end }}</div>
<div class="size-h5 color-highlight">Speedtest Tracker</div>
<div class="size-h6 color-subdue" style="font-style: italic;">{{ if ne $isp "" }}{{ $isp }}{{ else }}{{ $server }}{{ end }}</div>
</div>
- type: custom-api
title: Internet Speed
title-url: https://speedtest.kaleschke.info
cache: 1h
url: http://speedtest-tracker/api/v1/results/latest
headers:
Authorization: Bearer ${GLANCE_SPEEDTEST_API_KEY}
Accept: application/json
subrequests:
stats:
url: http://speedtest-tracker/api/v1/stats
headers:
Authorization: Bearer ${GLANCE_SPEEDTEST_API_KEY}
Accept: application/json
template: |
{{ $stats := .Subrequest "stats" }}
{{ $download := .JSON.Float "download" }}
{{ if eq $download 0.0 }}{{ $download = .JSON.Float "data.download" }}{{ end }}
{{ if eq $download 0.0 }}{{ $download = div (.JSON.Float "download_bits") 1000000.0 }}{{ end }}
{{ if eq $download 0.0 }}{{ $download = div (.JSON.Float "data.download_bits") 1000000.0 }}{{ end }}
{{ $upload := .JSON.Float "upload" }}
{{ if eq $upload 0.0 }}{{ $upload = .JSON.Float "data.upload" }}{{ end }}
{{ if eq $upload 0.0 }}{{ $upload = div (.JSON.Float "upload_bits") 1000000.0 }}{{ end }}
{{ if eq $upload 0.0 }}{{ $upload = div (.JSON.Float "data.upload_bits") 1000000.0 }}{{ end }}
{{ $ping := .JSON.Float "ping" }}
{{ if eq $ping 0.0 }}{{ $ping = .JSON.Float "data.ping" }}{{ end }}
{{ $downloadAvg := $stats.JSON.Float "avg_download" }}
{{ if eq $downloadAvg 0.0 }}{{ $downloadAvg = $stats.JSON.Float "data.download.avg" }}{{ end }}
{{ if eq $downloadAvg 0.0 }}{{ $downloadAvg = div ($stats.JSON.Float "data.download.avg_bits") 1000000.0 }}{{ end }}
{{ $uploadAvg := $stats.JSON.Float "avg_upload" }}
{{ if eq $uploadAvg 0.0 }}{{ $uploadAvg = $stats.JSON.Float "data.upload.avg" }}{{ end }}
{{ if eq $uploadAvg 0.0 }}{{ $uploadAvg = div ($stats.JSON.Float "data.upload.avg_bits") 1000000.0 }}{{ end }}
{{ $pingAvg := $stats.JSON.Float "avg_ping" }}
{{ if eq $pingAvg 0.0 }}{{ $pingAvg = $stats.JSON.Float "data.ping.avg" }}{{ end }}
{{ $downloadChange := percentChange $downloadAvg $download }}
{{ $uploadChange := percentChange $uploadAvg $upload }}
{{ $pingChange := percentChange $pingAvg $ping }}
<div class="flex justify-between text-center margin-block-3">
<div>
<div class="size-small {{ if lt $downloadChange 0.0 }}color-negative{{ else }}color-positive{{ end }}">{{ printf "%+.1f%%" $downloadChange }}</div>
<div class="color-highlight size-h3">{{ printf "%.1f" $download }}</div>
<div class="size-h6 color-subdue">DOWNLOAD</div>
</div>
<div>
<div class="size-small {{ if lt $uploadChange 0.0 }}color-negative{{ else }}color-positive{{ end }}">{{ printf "%+.1f%%" $uploadChange }}</div>
<div class="color-highlight size-h3">{{ printf "%.1f" $upload }}</div>
<div class="size-h6 color-subdue">UPLOAD</div>
</div>
<div>
<div class="size-small {{ if gt $pingChange 0.0 }}color-negative{{ else }}color-positive{{ end }}">{{ printf "%+.1f%%" $pingChange }}</div>
<div class="color-highlight size-h3">{{ printf "%.0f ms" $ping }}</div>
<div class="size-h6 color-subdue">PING</div>
</div>
</div>
- type: dns-stats
title: DNS Stats
service: adguard
url: http://adguard
username: ${GLANCE_ADGUARD_USERNAME}
password: ${GLANCE_ADGUARD_PASSWORD}
- type: monitor
title: DNS und VPN
cache: 1m
sites:
- title: AdGuard Home
url: http://192.168.178.58:8082
check-url: http://adguard
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/adguard-home.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Traefik
url: https://traefik.kaleschke.info
check-url: http://traefik:8082/metrics
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- type: docker-containers
title: Network Container
category: network
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers: &containers
traefik:
name: Traefik
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
url: https://traefik.kaleschke.info
description: Reverse Proxy
category: core
hide: false
gitea:
name: Gitea
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
url: https://git.kaleschke.info
description: GitOps Origin
category: core
hide: false
authelia:
name: Authelia
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authelia.svg
url: https://auth.kaleschke.info
description: ForwardAuth
category: core
hide: false
vaultwarden:
name: Vaultwarden
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vaultwarden.svg
url: https://vault.kaleschke.info
description: Password Vault
category: core
hide: false
postgresql17:
name: PostgreSQL 18
icon: si:postgresql
description: Shared DB
category: core
hide: false
Redis:
name: Redis
icon: si:redis
description: Shared Cache
category: core
hide: false
adguard:
name: AdGuard
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/adguard-home.svg
url: http://192.168.178.58:8082
description: DNS Filter
category: network
hide: false
unbound:
name: Unbound
icon: mdi:dns
description: Upstream Resolver
category: network
hide: false
ddns-updater:
name: DDNS Updater
icon: mdi:cloud-sync
description: Cloudflare DNS
category: network
hide: false
paperless-ngx:
name: Paperless-ngx
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/paperless-ngx.svg
url: https://paperless.kaleschke.info
description: Dokumente
category: apps
hide: false
paperless-gpt:
name: Paperless-GPT
icon: mdi:robot
url: https://paperless-gpt.kaleschke.info
description: Dokumenten-KI
category: apps
hide: false
immich_server:
name: Immich
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
url: https://immich.kaleschke.info
description: Fotos und Videos
category: apps
id: immich
hide: false
immich_postgres:
name: DB
parent: immich
category: apps
hide: false
immich_redis:
name: Redis
parent: immich
category: apps
hide: false
immich_machine_learning:
name: ML
parent: immich
category: apps
hide: false
mealie:
name: Mealie
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mealie.svg
url: https://mealie.kaleschke.info
description: Rezepte
category: apps
id: mealie
hide: false
mealie-postgres:
name: DB
parent: mealie
category: apps
hide: false
nextcloud:
name: Nextcloud
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg
url: https://cloud.kaleschke.info
description: Dateien und Sync
category: apps
id: nextcloud
hide: false
nextcloud-postgres:
name: DB
parent: nextcloud
category: apps
hide: false
nextcloud-redis:
name: Redis
parent: nextcloud
category: apps
hide: false
mail-archiver:
name: Mail Archiver
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mailcow.svg
url: https://mail.kaleschke.info
description: Mail-Archiv
category: apps
hide: false
ntfy:
name: ntfy
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/ntfy.svg
url: https://ntfy.kaleschke.info
description: Push Alerts
category: apps
hide: false
bentopdf:
name: BentoPDF
icon: mdi:file-pdf-box
url: https://pdf.kaleschke.info
description: PDF Tools
category: apps
hide: false
glance:
name: Glance
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glance.svg
url: https://glance.kaleschke.info
description: Homelab Uebersicht
category: ops
hide: false
glance-docker-socket-proxy:
name: Glance Socket Proxy
icon: si:docker
description: Read-only Docker API
category: ops
hide: false
monitoring-grafana:
name: Monitoring Grafana
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/grafana.svg
url: https://monitoring.kaleschke.info
description: Observability UI
category: ops
id: monitoring
hide: false
monitoring-prometheus:
name: Prometheus
parent: monitoring
category: ops
hide: false
monitoring-loki:
name: Loki
parent: monitoring
category: ops
hide: false
monitoring-promtail:
name: Promtail
parent: monitoring
category: ops
hide: false
monitoring-alertmanager:
name: Alertmanager
parent: monitoring
category: ops
hide: false
monitoring-alertmanager-ntfy-bridge:
name: ntfy Bridge
parent: monitoring
category: ops
hide: false
monitoring-blackbox-exporter:
name: Blackbox
parent: monitoring
category: ops
hide: false
monitoring-node-exporter:
name: Node Exporter
parent: monitoring
category: ops
hide: false
monitoring-cadvisor:
name: cAdvisor
parent: monitoring
category: ops
hide: false
monitoring-influxdb3-core:
name: InfluxDB 3
parent: monitoring
category: ops
hide: false
glances:
name: Glances
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glances.svg
url: https://glances.kaleschke.info
description: Host-Monitoring
category: ops
hide: false
scrutiny:
name: Scrutiny
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/scrutiny.svg
url: https://scrutiny.kaleschke.info
description: SMART
category: ops
hide: false
speedtest-tracker:
name: Speedtest
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/speedtest-tracker.png
url: https://speedtest.kaleschke.info
description: WAN-Messung
category: ops
hide: false
filebrowser:
name: Filebrowser
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/filebrowser.svg
url: https://files.kaleschke.info
description: Dateizugriff
category: ops
hide: false
code-server:
name: code-server
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vscode.svg
url: https://code.kaleschke.info
description: Web IDE
category: ops
hide: false
borg-ui:
name: Borg UI
icon: mdi:archive-sync
url: https://borg.kaleschke.info
description: Backup und Restore
category: ops
hide: false
hermes-dashboard:
name: Hermes
icon: mdi:shield-sparkles
url: https://hermes.kaleschke.info
description: Ops Agent UI
category: ops
id: hermes
hide: false
hermes-gateway:
name: Gateway
parent: hermes
category: ops
hide: false
komodo-core:
name: Komodo
icon: sh:komodo
url: https://komodo.kaleschke.info
description: Stack Manager
category: ops
id: komodo
hide: false
komodo-mongo:
name: Mongo
parent: komodo
category: ops
hide: false
komodo-periphery:
name: Periphery
parent: komodo
category: ops
hide: false
- type: docker-containers
title: App Container
category: apps
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers: *containers
- type: docker-containers
title: Ops Container
category: ops
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers: *containers
- name: Infrastructure and Media
slug: infrastructure
width: wide
columns:
- size: small
widgets:
- type: bookmarks
title: Core
groups:
- title: Control Plane
color: 212 100 50
links:
- title: Komodo
url: https://komodo.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/komodo.svg
- title: Gitea
url: https://git.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
- title: Traefik
url: https://traefik.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
- title: Authelia
url: https://auth.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authelia.svg
- type: bookmarks
title: Media und Apps
groups:
- title: Apps
color: 140 70 40
links:
- title: Immich
url: https://immich.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
- title: Paperless
url: https://paperless.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/paperless-ngx.svg
- title: Nextcloud
url: https://cloud.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg
- title: Mealie
url: https://mealie.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mealie.svg
- size: full
widgets:
- type: monitor
title: Platform Checks
cache: 1m
sites:
- title: Gitea
url: https://git.kaleschke.info
check-url: http://gitea:3000/api/healthz
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Monitoring Grafana
url: https://monitoring.kaleschke.info
check-url: http://monitoring-grafana:3000/api/health
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/grafana.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Glance
url: https://glance.kaleschke.info
check-url: http://glance:8080
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glance.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Immich
url: https://immich.kaleschke.info
check-url: http://immich_server:2283/api/server/ping
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Paperless-ngx
url: https://paperless.kaleschke.info
check-url: http://paperless-ngx:8000
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/paperless-ngx.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Nextcloud
url: https://cloud.kaleschke.info
check-url: http://nextcloud/status.php
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- type: docker-containers
title: Core Container
category: core
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers: *containers
- type: docker-containers
title: App Container
category: apps
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers: *containers
- type: docker-containers
title: Ops Container
category: ops
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers: *containers
- size: small
widgets:
- type: bookmarks
title: Ops
groups:
- title: Tools
color: 4 78 57
links:
- title: Glances
url: https://glances.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glances.svg
- title: Scrutiny
url: https://scrutiny.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/scrutiny.svg
- title: Speedtest
url: https://speedtest.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/speedtest-tracker.png
$include: pages.yml
+515
View File
@@ -0,0 +1,515 @@
- name: Home
slug: home
width: wide
head-widgets:
- type: search
search-engine: duckduckgo
new-tab: true
autofocus: true
placeholder: Suche im Web oder springe per Bang...
bangs:
- title: Gitea
shortcut: "!git"
url: https://git.kaleschke.info/explore/repos?q={QUERY}
- title: Paperless
shortcut: "!doc"
url: https://paperless.kaleschke.info/documents?query={QUERY}
- title: Nextcloud
shortcut: "!cloud"
url: https://cloud.kaleschke.info/apps/files/?dir=/{QUERY}
- title: Komodo
shortcut: "!komodo"
url: https://komodo.kaleschke.info
- title: Immich
shortcut: "!foto"
url: https://immich.kaleschke.info/search?query={QUERY}
- title: Mealie
shortcut: "!rezept"
url: https://mealie.kaleschke.info/g/home/?search={QUERY}
columns:
- size: small
widgets:
- type: group
widgets:
- type: custom-api
title: Day
body-type: string
skip-json-validation: true
cache: 1s
template: |
{{ $localTime := now }}
{{ $elapsedSeconds := add (mul $localTime.Hour 3600) (mul $localTime.Minute 60) | add $localTime.Second }}
{{ $dayProgress := div (mul $elapsedSeconds 100.0) 86400.0 }}
{{ $gradient := "#70a1ff" }}
{{ if gt $dayProgress 25.0 }}{{ $gradient = "#ff6b6b, #70a1ff" }}{{ end }}
{{ if gt $dayProgress 50.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df" }}{{ end }}
{{ if gt $dayProgress 75.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df, #70a1ff" }}{{ end }}
<div style="text-align: center;">
<div style="width: 100%; height: 12px; background: #23262f; border: 1px solid color-mix(in srgb, var(--color-text-subdue) 55%, transparent); border-radius: 10px; overflow: hidden;">
<div style="height: 100%; width: {{ $dayProgress }}%; background: linear-gradient(90deg, {{ $gradient }});"></div>
</div>
<div class="size-h1" style="margin-top: 6px;">{{ printf "%.2f" $dayProgress }}% des Tages sind vorbei</div>
</div>
- type: custom-api
title: Month
body-type: string
skip-json-validation: true
cache: 1s
template: |
{{ $localTime := now }}
{{ $month := $localTime.Month }}
{{ $daysInMonth := 31 }}
{{ if eq $month 2 }}{{ $daysInMonth = 28 }}{{ end }}
{{ if or (eq $month 4) (eq $month 6) (eq $month 9) (eq $month 11) }}{{ $daysInMonth = 30 }}{{ end }}
{{ $secondsToday := add (mul $localTime.Hour 3600) (mul $localTime.Minute 60) | add $localTime.Second }}
{{ $daysElapsed := add (sub $localTime.Day 1) (div $secondsToday 86400.0) }}
{{ $monthProgress := mul (div $daysElapsed $daysInMonth) 100.0 }}
{{ $gradient := "#70a1ff" }}
{{ if gt $monthProgress 25.0 }}{{ $gradient = "#ff6b6b, #70a1ff" }}{{ end }}
{{ if gt $monthProgress 50.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df" }}{{ end }}
{{ if gt $monthProgress 75.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df, #70a1ff" }}{{ end }}
<div style="text-align: center;">
<div style="width: 100%; height: 12px; background: #23262f; border: 1px solid color-mix(in srgb, var(--color-text-subdue) 55%, transparent); border-radius: 10px; overflow: hidden;">
<div style="height: 100%; width: {{ $monthProgress }}%; background: linear-gradient(90deg, {{ $gradient }});"></div>
</div>
<div class="size-h1" style="margin-top: 6px;">{{ printf "%.2f" $monthProgress }}% des Monats sind vorbei</div>
</div>
- type: custom-api
title: Year
body-type: string
skip-json-validation: true
cache: 1s
template: |
{{ $localTime := now }}
{{ $secondsToday := add (mul $localTime.Hour 3600) (mul $localTime.Minute 60) | add $localTime.Second }}
{{ $secondsElapsed := add (mul (sub $localTime.YearDay 1) 86400) $secondsToday }}
{{ $yearProgress := div (mul $secondsElapsed 100.0) (mul 365 86400) }}
{{ $gradient := "#70a1ff" }}
{{ if gt $yearProgress 25.0 }}{{ $gradient = "#ff6b6b, #70a1ff" }}{{ end }}
{{ if gt $yearProgress 50.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df" }}{{ end }}
{{ if gt $yearProgress 75.0 }}{{ $gradient = "#ff6b6b, #f8e71c, #7ed6df, #70a1ff" }}{{ end }}
<div style="text-align: center;">
<div style="width: 100%; height: 12px; background: #23262f; border: 1px solid color-mix(in srgb, var(--color-text-subdue) 55%, transparent); border-radius: 10px; overflow: hidden;">
<div style="height: 100%; width: {{ $yearProgress }}%; background: linear-gradient(90deg, {{ $gradient }});"></div>
</div>
<div class="size-h1" style="margin-top: 6px;">{{ printf "%.2f" $yearProgress }}% des Jahres sind vorbei</div>
</div>
- type: clock
hour-format: 24h
show-progress: true
timezones:
- timezone: Europe/Berlin
label: Berlin
- timezone: UTC
label: UTC
- type: weather
location: Berlin, Germany
units: metric
hour-format: 24h
- type: calendar
first-day-of-week: monday
- type: to-do
title: Operator-Notizen
- type: bookmarks
title: Direkte Einstiege
groups:
- title: Core
color: 212 100 50
links:
- title: Komodo
url: https://komodo.kaleschke.info
icon: sh:komodo
- title: Gitea
url: https://git.kaleschke.info
icon: si:gitea
- title: Monitoring
url: https://monitoring.kaleschke.info
icon: si:grafana
- title: Ops
color: 45 70 55
links:
- title: Borg
url: https://borg.kaleschke.info
icon: mdi:archive
- title: Glances
url: https://glances.kaleschke.info
icon: sh:glances
- title: Scrutiny
url: https://scrutiny.kaleschke.info
icon: sh:scrutiny
- size: full
widgets:
- type: server-stats
title: Server Stats
servers:
- type: local
name: Kallilabcore
hide-mountpoints-by-default: false
- type: custom-api
title: Komodo Stacks
title-url: https://komodo.kaleschke.info
cache: 2m
url: http://komodo-core:9120/read
method: POST
body-type: json
body:
type: ListStacks
params: {}
headers:
X-Api-Key: ${GLANCE_KOMODO_API_KEY}
X-Api-Secret: ${GLANCE_KOMODO_API_SECRET}
Content-Type: application/json
template: |
{{ $stacks := .JSON.Array "@this" }}
{{ $total := len $stacks }}
{{ $running := 0 }}
{{ range $stacks }}{{ if eq (.String "info.state") "running" }}{{ $running = add $running 1 }}{{ end }}{{ end }}
{{ $problems := sub $total $running }}
{{ $divider := "border-left: 1px solid hsla(220, 40%, 70%, 0.14);" }}
<div style="display: flex; text-align: center;">
<div style="flex: 1;">
<div class="color-highlight size-h3">{{ $total }}</div>
<div class="size-h6 uppercase color-subdue">Stacks</div>
</div>
<div style="flex: 1; {{ $divider }}">
<div class="color-positive size-h3">{{ $running }}</div>
<div class="size-h6 uppercase color-subdue">Running</div>
</div>
<div style="flex: 1; {{ $divider }}">
<div class="{{ if gt $problems 0 }}color-negative{{ else }}color-subdue{{ end }} size-h3">{{ $problems }}</div>
<div class="size-h6 uppercase color-subdue">Auffaellig</div>
</div>
</div>
<div style="height: 5px; margin-top: 14px; border-radius: 999px; overflow: hidden; background: hsla(220, 30%, 60%, 0.12);">
<div style="height: 100%; width: {{ if gt $total 0 }}{{ div (mul $running 100.0) (toFloat $total) }}{{ else }}0{{ end }}%; border-radius: 999px; background: linear-gradient(90deg, hsl(150, 85%, 42%), hsl(172, 95%, 48%));"></div>
</div>
{{ if gt $problems 0 }}
<div style="display: flex; justify-content: center; gap: 8px; flex-wrap: wrap; margin-top: 12px;">
{{ range $stacks }}
{{ if ne (.String "info.state") "running" }}
<span class="size-h6" style="padding: 3px 12px; border-radius: 999px; border: 1px solid hsla(350, 90%, 60%, 0.45); background: hsla(350, 90%, 60%, 0.08); color: var(--color-negative); letter-spacing: 0.05em;">{{ .String "name" }} · {{ .String "info.state" }}</span>
{{ end }}
{{ end }}
</div>
{{ end }}
- type: custom-api
title: Immich
title-url: https://immich.kaleschke.info
cache: 10m
url: http://immich_server:2283/api/server/statistics
headers:
x-api-key: ${GLANCE_IMMICH_API_KEY}
subrequests:
storage:
url: http://immich_server:2283/api/server/storage
headers:
x-api-key: ${GLANCE_IMMICH_API_KEY}
template: |
{{ $photos := .JSON.Int "photos" }}
{{ $videos := .JSON.Int "videos" }}
{{ $usageGiB := div (toFloat (.JSON.Int "usage")) 1073741824.0 }}
{{ $storage := .Subrequest "storage" }}
{{ $storageOK := and (ge $storage.Response.StatusCode 200) (le $storage.Response.StatusCode 299) }}
{{ $percentage := 0.0 }}
{{ if $storageOK }}{{ $percentage = $storage.JSON.Float "diskUsagePercentage" }}{{ end }}
{{ $divider := "border-left: 1px solid hsla(220, 40%, 70%, 0.14);" }}
<div style="display: flex; text-align: center;">
<div style="flex: 1;">
<div class="color-highlight size-h3">{{ $photos | formatNumber }}</div>
<div class="size-h6 uppercase color-subdue">Fotos</div>
</div>
<div style="flex: 1; {{ $divider }}">
<div class="color-highlight size-h3">{{ $videos | formatNumber }}</div>
<div class="size-h6 uppercase color-subdue">Videos</div>
</div>
<div style="flex: 1; {{ $divider }}">
<div class="color-highlight size-h3">{{ printf "%.0f" $usageGiB }} GiB</div>
<div class="size-h6 uppercase color-subdue">Medien</div>
</div>
</div>
<div style="display: flex; align-items: center; gap: 12px; margin-top: 16px;">
<div style="flex: 1; height: 5px; border-radius: 999px; overflow: hidden; background: hsla(220, 30%, 60%, 0.12);">
<div style="height: 100%; width: {{ if $storageOK }}{{ printf "%.1f" $percentage }}%{{ else }}0%{{ end }}; border-radius: 999px; background: linear-gradient(90deg, hsl(205, 100%, 55%), hsl(172, 95%, 48%));"></div>
</div>
<div class="size-h6 color-subdue" style="white-space: nowrap;">{{ if $storageOK }}{{ printf "%.1f" $percentage }}% belegt{{ else }}Speicher API n/v{{ end }}</div>
</div>
- type: group
widgets:
- type: monitor
title: Core
cache: 1m
sites:
- title: AdGuard Home
url: http://192.168.178.58:8082
check-url: http://adguard
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/adguard-home.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Authelia
url: https://auth.kaleschke.info
check-url: http://authelia:9091/api/health
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authelia.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Gitea
url: https://git.kaleschke.info
check-url: http://gitea:3000/api/healthz
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Traefik
url: https://traefik.kaleschke.info
check-url: http://traefik:8082/metrics
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Vaultwarden
url: https://vault.kaleschke.info
check-url: http://vaultwarden/alive
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vaultwarden.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Komodo
url: https://komodo.kaleschke.info
check-url: http://komodo-core:9120
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/komodo.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Glance
url: https://glance.kaleschke.info
check-url: http://glance:8080
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glance.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- type: monitor
title: Apps
cache: 1m
sites:
- title: Paperless-ngx
url: https://paperless.kaleschke.info
check-url: http://paperless-ngx:8000
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/paperless-ngx.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Paperless-GPT
url: https://paperless-gpt.kaleschke.info
check-url: http://paperless-gpt:8080
icon: mdi:robot
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Immich
url: https://immich.kaleschke.info
check-url: http://immich_server:2283/api/server/ping
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Mealie
url: https://mealie.kaleschke.info
check-url: http://mealie:9000
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mealie.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Nextcloud
url: https://cloud.kaleschke.info
check-url: http://nextcloud/status.php
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: ntfy
url: https://ntfy.kaleschke.info
check-url: http://ntfy/v1/health
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/ntfy.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Mail Archiver
url: https://mail.kaleschke.info
check-url: http://mail-archiver:5000
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mailcow.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: BentoPDF
url: https://pdf.kaleschke.info
check-url: http://bentopdf:8080
icon: mdi:file-pdf-box
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- type: monitor
title: Ops
cache: 1m
sites:
- title: Monitoring Grafana
url: https://monitoring.kaleschke.info
check-url: http://monitoring-grafana:3000/api/health
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/grafana.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Glances
url: https://glances.kaleschke.info
check-url: http://glances:61208
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glances.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Scrutiny
url: https://scrutiny.kaleschke.info
check-url: http://scrutiny:8080
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/scrutiny.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Speedtest Tracker
url: https://speedtest.kaleschke.info
check-url: http://speedtest-tracker
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/speedtest-tracker.png
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Filebrowser
url: https://files.kaleschke.info
check-url: http://filebrowser
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/filebrowser.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: code-server
url: https://code.kaleschke.info
check-url: http://code-server:8443
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/vscode.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Borg UI
url: https://borg.kaleschke.info
check-url: http://borg-ui:8081
icon: mdi:archive-sync
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- size: small
widgets:
- type: custom-api
title: Internet
title-url: https://speedtest.kaleschke.info
cache: 1h
url: http://speedtest-tracker/api/v1/results/latest
headers:
Authorization: Bearer ${GLANCE_SPEEDTEST_API_KEY}
Accept: application/json
subrequests:
stats:
url: http://speedtest-tracker/api/v1/stats
headers:
Authorization: Bearer ${GLANCE_SPEEDTEST_API_KEY}
Accept: application/json
template: |
{{ $ip := .JSON.String "external_ip" }}
{{ if eq $ip "" }}{{ $ip = .JSON.String "data.interface.externalIp" }}{{ end }}
{{ if eq $ip "" }}{{ $ip = .JSON.String "data.data.interface.externalIp" }}{{ end }}
{{ $isp := .JSON.String "isp" }}
{{ if eq $isp "" }}{{ $isp = .JSON.String "data.isp" }}{{ end }}
{{ if eq $isp "" }}{{ $isp = .JSON.String "data.data.isp" }}{{ end }}
{{ $download := .JSON.Float "download" }}
{{ if eq $download 0.0 }}{{ $download = .JSON.Float "data.download" }}{{ end }}
{{ if eq $download 0.0 }}{{ $download = div (.JSON.Float "download_bits") 1000000.0 }}{{ end }}
{{ if eq $download 0.0 }}{{ $download = div (.JSON.Float "data.download_bits") 1000000.0 }}{{ end }}
{{ if eq $download 0.0 }}{{ $download = div (mul (.JSON.Float "data.data.download.bandwidth") 8.0) 1000000.0 }}{{ end }}
{{ $upload := .JSON.Float "upload" }}
{{ if eq $upload 0.0 }}{{ $upload = .JSON.Float "data.upload" }}{{ end }}
{{ if eq $upload 0.0 }}{{ $upload = div (.JSON.Float "upload_bits") 1000000.0 }}{{ end }}
{{ if eq $upload 0.0 }}{{ $upload = div (.JSON.Float "data.upload_bits") 1000000.0 }}{{ end }}
{{ if eq $upload 0.0 }}{{ $upload = div (mul (.JSON.Float "data.data.upload.bandwidth") 8.0) 1000000.0 }}{{ end }}
{{ if gt $download 100000.0 }}{{ $download = div (mul $download 8.0) 1000000.0 }}{{ end }}
{{ if gt $upload 100000.0 }}{{ $upload = div (mul $upload 8.0) 1000000.0 }}{{ end }}
{{ $ping := .JSON.Float "ping" }}
{{ if eq $ping 0.0 }}{{ $ping = .JSON.Float "data.ping" }}{{ end }}
{{ if eq $ping 0.0 }}{{ $ping = .JSON.Float "data.data.ping.latency" }}{{ end }}
<div class="text-center" style="margin-bottom: 10px;">
<div class="color-primary size-h3" style="font-weight: 700;">{{ if ne $ip "" }}{{ $ip }}{{ else }}WAN online{{ end }}</div>
<div class="size-h6 color-subdue">{{ if ne $isp "" }}{{ $isp }}{{ else }}Speedtest Tracker{{ end }}</div>
</div>
{{ if and (eq $download 0.0) (eq $upload 0.0) }}
<div class="text-center color-subdue size-h6">Keine aktuellen Messdaten</div>
{{ else }}
<div class="flex justify-between text-center">
<div>
<div class="color-highlight size-h4">{{ printf "%.1f" $download }}</div>
<div class="size-h6 color-subdue">MBIT DOWN</div>
</div>
<div>
<div class="color-highlight size-h4">{{ printf "%.1f" $upload }}</div>
<div class="size-h6 color-subdue">MBIT UP</div>
</div>
<div>
<div class="color-highlight size-h4">{{ printf "%.0f ms" $ping }}</div>
<div class="size-h6 color-subdue">PING</div>
</div>
</div>
{{ end }}
- type: dns-stats
title: DNS Stats
service: adguard
url: http://adguard
username: ${GLANCE_ADGUARD_USERNAME}
password: ${GLANCE_ADGUARD_PASSWORD}
- type: custom-api
title: Borg Backup
title-url: https://borg.kaleschke.info
cache: 15m
url: http://monitoring-prometheus:9090/api/v1/query?query=(time()-homelab_borg_last_completed_timestamp_seconds)/3600
subrequests:
success:
url: http://monitoring-prometheus:9090/api/v1/query?query=homelab_borg_last_success
template: |
{{ $ageHours := .JSON.Float "data.result.0.value.1" }}
{{ $archive := .JSON.String "data.result.0.metric.archive" }}
{{ $succ := .Subrequest "success" }}
{{ $ok := $succ.JSON.Float "data.result.0.value.1" }}
{{ $status := $succ.JSON.String "data.result.0.metric.status" }}
{{ if eq (len (.JSON.Array "data.result")) 0 }}
<div class="text-center color-subdue">Keine Backup-Metrik gefunden</div>
{{ else }}
<div class="text-center">
<div class="size-h2 {{ if gt $ageHours 30.0 }}color-negative{{ else }}color-positive{{ end }}">vor {{ printf "%.0f" $ageHours }} h</div>
<div class="size-h6 color-subdue" style="margin-top: 4px;">letztes abgeschlossenes Backup</div>
<div class="size-h6 {{ if eq $ok 1.0 }}color-positive{{ else }}color-negative{{ end }}" style="margin-top: 6px;">
{{ if eq $ok 1.0 }}letzter Job erfolgreich{{ else }}letzter Job: {{ $status }}{{ end }}
</div>
{{ if ne $archive "" }}<div class="size-h6 color-subdue text-truncate" style="margin-top: 2px;">{{ $archive }}</div>{{ end }}
</div>
{{ end }}
- type: group
widgets:
- type: docker-containers
title: Network
category: network
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers:
$include: containers-map.yml
- type: docker-containers
title: Apps
category: apps
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers:
$include: containers-map.yml
- type: docker-containers
title: Ops
category: ops
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers:
$include: containers-map.yml
+244
View File
@@ -0,0 +1,244 @@
- name: Infrastructure and Media
slug: infrastructure
width: wide
columns:
- size: small
widgets:
- type: bookmarks
title: Core
groups:
- title: Control Plane
color: 212 100 50
links:
- title: Komodo
url: https://komodo.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/komodo.svg
- title: Gitea
url: https://git.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
- title: Traefik
url: https://traefik.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/traefik.svg
- title: Authelia
url: https://auth.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authelia.svg
- type: bookmarks
title: Media und Apps
groups:
- title: Apps
color: 140 70 40
links:
- title: Immich
url: https://immich.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
- title: Paperless
url: https://paperless.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/paperless-ngx.svg
- title: Nextcloud
url: https://cloud.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg
- title: Mealie
url: https://mealie.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mealie.svg
- type: custom-api
title: Scrutiny Disk Health
title-url: https://scrutiny.kaleschke.info
cache: 30m
url: http://scrutiny:8080/api/summary
template: |
{{ $disks := .JSON.Array "data.summary.@values" }}
{{ if eq (len $disks) 0 }}
<div class="text-center color-subdue">Keine Disks gemeldet.</div>
{{ else }}
<ul class="list list-gap-4">
{{ range $disks }}
{{ $status := .Int "device.device_status" }}
<li class="flex justify-between">
<div class="color-highlight">{{ .String "device.device_name" }}</div>
<div class="size-h6 uppercase {{ if eq $status 0 }}color-positive{{ else }}color-negative{{ end }}">
{{ if eq $status 0 }}OK{{ else }}FAILED{{ end }}
</div>
</li>
{{ end }}
</ul>
{{ end }}
- size: full
widgets:
- type: custom-api
title: GitOps - homelab-infra
title-url: https://git.kaleschke.info/Micha/homelab-infra
cache: 5m
url: http://gitea:3000/api/v1/repos/Micha/homelab-infra/commits?limit=5&stat=false
headers:
Authorization: token ${GLANCE_GITEA_TOKEN}
Accept: application/json
subrequests:
repo:
url: http://gitea:3000/api/v1/repos/Micha/homelab-infra
headers:
Authorization: token ${GLANCE_GITEA_TOKEN}
Accept: application/json
template: |
{{ $repo := .Subrequest "repo" }}
{{ $repoOK := and (ge $repo.Response.StatusCode 200) (le $repo.Response.StatusCode 299) }}
{{ if $repoOK }}
<div class="flex justify-between text-center" style="margin-bottom: 12px;">
<div>
<div class="color-highlight size-h3">{{ $repo.JSON.Int "open_issues_count" }}</div>
<div class="size-h6 uppercase">Issues</div>
</div>
<div>
<div class="color-highlight size-h3">{{ $repo.JSON.Int "open_pr_counter" }}</div>
<div class="size-h6 uppercase">PRs</div>
</div>
<div>
<div class="color-highlight size-h3">{{ $repo.JSON.String "default_branch" }}</div>
<div class="size-h6 uppercase">Branch</div>
</div>
</div>
{{ end }}
<ul class="list list-gap-6">
{{ range .JSON.Array "@this" }}
<li>
<div class="flex justify-between">
<div class="color-highlight text-truncate" style="max-width: 75%;">{{ .String "commit.message" | replaceMatches "(?s)\n.*" "" }}</div>
<div class="size-h6 color-subdue">{{ slice (.String "sha") 0 7 }}</div>
</div>
<div class="size-h6 color-subdue">{{ .String "commit.author.name" }} · <span {{ .String "commit.author.date" | parseTime "rfc3339" | toRelativeTime }}></span></div>
</li>
{{ end }}
</ul>
- type: monitor
title: Platform Checks
cache: 1m
sites:
- title: Gitea
url: https://git.kaleschke.info
check-url: http://gitea:3000/api/healthz
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Monitoring Grafana
url: https://monitoring.kaleschke.info
check-url: http://monitoring-grafana:3000/api/health
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/grafana.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Glance
url: https://glance.kaleschke.info
check-url: http://glance:8080
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glance.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Immich
url: https://immich.kaleschke.info
check-url: http://immich_server:2283/api/server/ping
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Paperless-ngx
url: https://paperless.kaleschke.info
check-url: http://paperless-ngx:8000
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/paperless-ngx.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- title: Nextcloud
url: https://cloud.kaleschke.info
check-url: http://nextcloud/status.php
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg
timeout: 5s
alt-status-codes: [200, 302, 401, 403]
- type: group
widgets:
- type: docker-containers
title: Core
category: core
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers:
$include: containers-map.yml
- type: docker-containers
title: Apps
category: apps
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers:
$include: containers-map.yml
- type: docker-containers
title: Ops
category: ops
hide-by-default: true
sock-path: tcp://glance-docker-socket-proxy:2375
containers:
$include: containers-map.yml
- size: small
widgets:
- type: custom-api
title: Paperless-ngx
title-url: https://paperless.kaleschke.info
cache: 15m
url: http://paperless-ngx:8000/api/statistics/
headers:
Authorization: Token ${GLANCE_PAPERLESS_TOKEN}
Accept: application/json
template: |
{{ $total := .JSON.Int "documents_total" }}
{{ $inbox := .JSON.Int "documents_inbox" }}
<div class="flex justify-between text-center">
<div>
<div class="color-highlight size-h3">{{ $total | formatNumber }}</div>
<div class="size-h6 uppercase">Dokumente</div>
</div>
<div>
<div class="size-h3 {{ if gt $inbox 0 }}color-negative{{ else }}color-positive{{ end }}">{{ $inbox }}</div>
<div class="size-h6 uppercase">Inbox</div>
</div>
</div>
- type: custom-api
title: Mealie
title-url: https://mealie.kaleschke.info
cache: 1h
url: http://mealie:9000/api/admin/about/statistics
headers:
Authorization: Bearer ${GLANCE_MEALIE_TOKEN}
Accept: application/json
template: |
<div class="flex justify-between text-center">
<div>
<div class="color-highlight size-h3">{{ .JSON.Int "totalRecipes" | formatNumber }}</div>
<div class="size-h6 uppercase">Rezepte</div>
</div>
<div>
<div class="color-highlight size-h3">{{ .JSON.Int "totalCategories" }}</div>
<div class="size-h6 uppercase">Kategorien</div>
</div>
<div>
<div class="color-highlight size-h3">{{ .JSON.Int "totalUsers" }}</div>
<div class="size-h6 uppercase">Nutzer</div>
</div>
</div>
- type: bookmarks
title: Ops
groups:
- title: Tools
color: 4 78 57
links:
- title: Glances
url: https://glances.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glances.svg
- title: Scrutiny
url: https://scrutiny.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/scrutiny.svg
- title: Speedtest
url: https://speedtest.kaleschke.info
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/speedtest-tracker.png
+80
View File
@@ -0,0 +1,80 @@
- name: Ops und Releases
slug: ops
width: wide
columns:
- size: small
widgets:
- type: rss
title: Selfhosted News
style: vertical-list
limit: 12
collapse-after: 6
cache: 6h
feeds:
- url: https://selfh.st/rss/
title: selfh.st
- url: https://tailscale.com/blog/index.xml
title: Tailscale Blog
- size: full
widgets:
- type: releases
title: Image Releases
cache: 12h
show-source-icon: true
collapse-after: 15
repositories:
- glanceapp/glance
- traefik/traefik
- go-gitea/gitea
- moghtech/komodo
- immich-app/immich
- paperless-ngx/paperless-ngx
- AdguardTeam/AdGuardHome
- dani-garcia/vaultwarden
- authelia/authelia
- mealie-recipes/mealie
- nextcloud/server
- AnalogJ/scrutiny
- alexjustesen/speedtest-tracker
- binwiederhier/ntfy
- filebrowser/filebrowser
- coder/code-server
- qdm12/ddns-updater
- nicolargo/glances
- size: small
widgets:
- type: custom-api
title: Letzte Commits
title-url: https://git.kaleschke.info/Micha/homelab-infra/commits/branch/master
cache: 5m
url: http://gitea:3000/api/v1/repos/Micha/homelab-infra/commits?limit=8&stat=false
headers:
Authorization: token ${GLANCE_GITEA_TOKEN}
Accept: application/json
template: |
<ul class="list list-gap-6 collapsible-container" data-collapse-after="5">
{{ range .JSON.Array "@this" }}
<li>
<div class="color-highlight text-truncate">{{ .String "commit.message" | replaceMatches "(?s)\n.*" "" }}</div>
<div class="size-h6 color-subdue">{{ slice (.String "sha") 0 7 }} · <span {{ .String "commit.author.date" | parseTime "rfc3339" | toRelativeTime }}></span></div>
</li>
{{ end }}
</ul>
- type: bookmarks
title: Deploy-Kette
groups:
- title: GitOps
color: 212 100 50
links:
- title: Gitea Repo
url: https://git.kaleschke.info/Micha/homelab-infra
icon: si:gitea
- title: Komodo Stacks
url: https://komodo.kaleschke.info
icon: sh:komodo
- title: Grafana
url: https://monitoring.kaleschke.info
icon: si:grafana
+3
View File
@@ -0,0 +1,3 @@
$include: home.yml
$include: infrastructure.yml
$include: ops.yml
+10
View File
@@ -9,11 +9,19 @@ services:
GLANCE_ADGUARD_USERNAME: ${GLANCE_ADGUARD_USERNAME:-}
GLANCE_ADGUARD_PASSWORD: ${GLANCE_ADGUARD_PASSWORD:-}
GLANCE_SPEEDTEST_API_KEY: ${GLANCE_SPEEDTEST_API_KEY:-}
GLANCE_KOMODO_API_KEY: ${GLANCE_KOMODO_API_KEY:-}
GLANCE_KOMODO_API_SECRET: ${GLANCE_KOMODO_API_SECRET:-}
GLANCE_GITEA_TOKEN: ${GLANCE_GITEA_TOKEN:-}
GLANCE_PAPERLESS_TOKEN: ${GLANCE_PAPERLESS_TOKEN:-}
GLANCE_MEALIE_TOKEN: ${GLANCE_MEALIE_TOKEN:-}
volumes:
- ./config:/app/config:ro
- ./assets:/app/assets:ro
networks:
- frontend_net
- glance_socket_net
# monitoring_net nur lesend fuer Prometheus-Query des Borg-Backup-Widgets
- monitoring_net
depends_on:
- glance-docker-socket-proxy
labels:
@@ -50,6 +58,8 @@ services:
networks:
frontend_net:
external: true
monitoring_net:
external: true
glance_socket_net:
name: glance_socket_net
internal: true
@@ -0,0 +1,237 @@
#!/usr/bin/env bash
set -euo pipefail
# Komodo-Stack-Hygiene-Check.
#
# Prueft, dass jeder Komodo-Stack sauber gegen das Git-Repo konfiguriert ist,
# und dass jeder Compose-File im Repo einen passenden Komodo-Stack hat.
# Findet die Klasse von Fehlern, die `immich_new` (2026-06-12) durchgelassen
# hat: Stack RUNNING, aber kein Repo / kein Account / project_missing.
REPO_ROOT="${REPO_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
OUTPUT_PATH="${OUTPUT_PATH:-/mnt/user/services/posture-check/komodo-stack-hygiene-last.json}"
NTFY_SCRIPT="${NTFY_SCRIPT:-$REPO_ROOT/ops/restore-tests/send-ntfy.sh}"
NTFY_TOPIC="${NTFY_TOPIC:-homelab-alerts}"
SEND_NTFY="${SEND_NTFY:-1}"
KOMODO_ENV_FILE="${KOMODO_ENV_FILE:-/mnt/user/appdata/secrets/codex_komodo_api.env}"
KOMODO_CONTAINER="${KOMODO_CONTAINER:-komodo-core}"
# Komma-separierte Allowlist fuer bewusst inline-managed Stacks.
# Quelle: memory/komodo-stack-inline-managed.md, CLAUDE.md.
INLINE_ALLOWLIST="${INLINE_ALLOWLIST:-komodo,grafana}"
# Compose-Files unter diesen Pfaden zaehlen NICHT als erwartete Stacks
# (Beispiele, Archive, Submodule).
COMPOSE_EXCLUDE_PATTERN="${COMPOSE_EXCLUDE_PATTERN:-/archive/|/examples/|/.git/}"
# Compose-Dir-Namen, die bewusst NICHT als Komodo-Stack laufen sollen
# (Work-in-progress, Build-/Dev-Compose, manuell deployed). Komma-separiert.
EXPECTED_NOT_IN_KOMODO="${EXPECTED_NOT_IN_KOMODO:-hermes-agent}"
TMP_DIR="${TMP_DIR:-/tmp/kallilab-komodo-stack-hygiene}"
mkdir -p "$TMP_DIR"
RESULTS_FILE="$TMP_DIR/results.$$"
STACKS_FILE="$TMP_DIR/stacks.$$.json"
: > "$RESULTS_FILE"
trap 'rm -f "$RESULTS_FILE" "$STACKS_FILE"' EXIT
json_escape() {
sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/\t/\\t/g'
}
add_result() {
printf '%s\t%s\t%s\n' "$1" "$2" "$3" >> "$RESULTS_FILE"
}
is_inline_allowed() {
local name="$1"
local IFS=,
for entry in $INLINE_ALLOWLIST; do
[ "$name" = "$entry" ] && return 0
done
return 1
}
is_expected_not_in_komodo() {
local name="$1"
local IFS=,
for entry in $EXPECTED_NOT_IN_KOMODO; do
[ "$name" = "$entry" ] && return 0
done
return 1
}
# True drift: do files inside this stack's compose-dir actually differ
# between deployed_hash and latest_hash? Komodo's deployed_hash bumps only
# on redeploy, while latest_hash tracks master HEAD - that produces a noisy
# "Pending Update" even when the stack itself wasn't touched.
stack_files_changed() {
local name="$1" deployed="$2" latest="$3"
local dir
# Locate the stack's compose dir (case-insensitive, same as Mode 3).
dir="$(find "$REPO_ROOT" -type d -iname "$name" -not -path "*/.git/*" 2>/dev/null | head -1)"
[ -n "$dir" ] || return 0 # No dir -> can't tell, treat as drift to be safe
( cd "$REPO_ROOT" && git rev-parse --verify --quiet "$deployed" >/dev/null ) || return 0
( cd "$REPO_ROOT" && git rev-parse --verify --quiet "$latest" >/dev/null ) || return 0
local rel="${dir#$REPO_ROOT/}"
if ( cd "$REPO_ROOT" && git diff --quiet "$deployed".."$latest" -- "$rel" ); then
return 1 # no change
fi
return 0 # real change
}
# Komodo-API-Credentials laden und Stack-Liste holen.
if [ ! -r "$KOMODO_ENV_FILE" ]; then
add_result "warning" "komodo-api" "Komodo env file not readable: $KOMODO_ENV_FILE"
else
set -a
# shellcheck disable=SC1090
. "$KOMODO_ENV_FILE"
set +a
if ! docker exec \
-e KOMODO_CLI_HOST \
-e KOMODO_CLI_KEY \
-e KOMODO_CLI_SECRET \
"$KOMODO_CONTAINER" km list -a stacks -f json > "$STACKS_FILE" 2>/dev/null; then
add_result "warning" "komodo-api" "km list stacks failed (container=$KOMODO_CONTAINER)"
: > "$STACKS_FILE"
fi
fi
# Per-Stack-Checks. Trenner: "|" statt Tab, weil IFS=Tab leere Felder kollabiert
# (Tab ist Whitespace in IFS). "|" kommt in Stack-Namen/Repos/Hashes nicht vor.
if [ -s "$STACKS_FILE" ]; then
while IFS='|' read -r name repo project_missing missing_files state deployed_hash latest_hash files_on_host file_contents; do
[ -n "$name" ] || continue
if is_inline_allowed "$name"; then
add_result "ok" "$name" "Inline-managed (allowlisted), skipping repo checks"
continue
fi
# Failure-Mode 1: Stack hat keine Git-Quelle (immich_new-Symptom).
if [ "$repo" = "-" ] && [ "$files_on_host" != "True" ] && [ "$file_contents" != "True" ]; then
add_result "critical" "$name" "Stack has no repo configured and is not inline-allowed"
continue
fi
# Failure-Mode 2: Komodo meldet Project Missing.
if [ "$project_missing" = "True" ]; then
add_result "critical" "$name" "project_missing=true (missing_files=$missing_files)"
continue
fi
# Failure-Mode 3: Stack-Name passt zu keinem Compose-File im Repo.
# Case-insensitive (Compose-Dir kann GroSs/klein abweichen, z.B. Adguard).
match_found=""
while IFS= read -r dir; do
[ -n "$dir" ] || continue
if [ -f "$dir/docker-compose.yml" ] \
|| [ -f "$dir/docker-compose.yaml" ] \
|| [ -f "$dir/compose.yml" ] \
|| [ -f "$dir/compose.yaml" ]; then
match_found=1
break
fi
done < <(find "$REPO_ROOT" -type d -iname "$name" -not -path "*/.git/*" 2>/dev/null)
if [ -z "$match_found" ]; then
# Verwaiste Stacks wie das frueher gesehene `immich_new`: Komodo kennt
# ihn, aber im Repo gibt's keinen Compose-Pfad.
add_result "warning" "$name" "Stack name does not match any compose directory in repo"
fi
# Failure-Mode 4: Deployed-Hash hinkt latest hinterher UND der Stack-Dir
# hat tatsaechlich File-Aenderungen dazwischen. Reine Komodo-Hash-Bewegung
# ohne Stack-Inhalt aendert nichts und ist kein echter Drift.
# "-" = unbekannt (z.B. gitea self-host edge case), nicht als Drift werten.
if [ "$deployed_hash" != "-" ] && [ "$latest_hash" != "-" ] \
&& [ "$deployed_hash" != "$latest_hash" ] \
&& stack_files_changed "$name" "$deployed_hash" "$latest_hash"; then
add_result "warning" "$name" "deployed_hash $deployed_hash != latest_hash $latest_hash (stack files changed)"
fi
# Failure-Mode 5: Stack ist down.
if [ "$state" = "down" ] || [ "$state" = "unknown" ]; then
add_result "warning" "$name" "Stack state is $state"
fi
add_result "ok" "$name" "Stack hygiene OK (state=$state, hash=$deployed_hash)"
done < <(jq -r '.[] | [
.name // "-",
(.info.repo // "-"),
(.info.project_missing | if . then "True" else "False" end),
(((.info.missing_files // []) | join(",")) | if . == "" then "-" else . end),
(.info.state // "-"),
(.info.deployed_hash // "-"),
(.info.latest_hash // "-"),
(.info.files_on_host | if . then "True" else "False" end),
(.info.file_contents | if . then "True" else "False" end)
] | join("|")' "$STACKS_FILE")
fi
# Failure-Mode 6: Compose-File im Repo, aber kein Komodo-Stack mit gleichem Namen.
if [ -s "$STACKS_FILE" ]; then
known_names="$(jq -r '.[].name' "$STACKS_FILE")"
while IFS= read -r -d '' compose; do
rel="${compose#$REPO_ROOT/}"
if printf '%s' "$rel" | grep -Eq "$COMPOSE_EXCLUDE_PATTERN"; then
continue
fi
dir_name="$(basename "$(dirname "$compose")")"
if is_inline_allowed "$dir_name"; then
continue
fi
if is_expected_not_in_komodo "$dir_name"; then
continue
fi
# Case-insensitive, weil z.B. host-services/Adguard <-> Komodo-Stack adguard
# legitim als gematched gilt.
if ! printf '%s\n' "$known_names" | grep -Fixq "$dir_name"; then
add_result "warning" "$dir_name" "Compose file $rel has no matching Komodo stack"
fi
done < <(find "$REPO_ROOT" -path "$REPO_ROOT/.git" -prune -o -type f \
\( -name docker-compose.yml -o -name docker-compose.yaml \
-o -name compose.yml -o -name compose.yaml \) -print0)
fi
timestamp="$(date -Iseconds)"
critical_count="$(awk -F '\t' '$1 == "critical" { c++ } END { print c + 0 }' "$RESULTS_FILE")"
warning_count="$(awk -F '\t' '$1 == "warning" { c++ } END { print c + 0 }' "$RESULTS_FILE")"
status="ok"
[ "$warning_count" -gt 0 ] && status="warning"
[ "$critical_count" -gt 0 ] && status="critical"
mkdir -p "$(dirname "$OUTPUT_PATH")"
{
printf '{\n'
printf ' "timestamp": "%s",\n' "$(printf '%s' "$timestamp" | json_escape)"
printf ' "status": "%s",\n' "$status"
printf ' "critical_count": %s,\n' "$critical_count"
printf ' "warning_count": %s,\n' "$warning_count"
printf ' "checks": [\n'
first=1
while IFS=$'\t' read -r severity name message; do
if [ "$first" -eq 0 ]; then printf ',\n'; fi
first=0
printf ' {"severity":"%s","name":"%s","message":"%s"}' \
"$(printf '%s' "$severity" | json_escape)" \
"$(printf '%s' "$name" | json_escape)" \
"$(printf '%s' "$message" | json_escape)"
done < "$RESULTS_FILE"
printf '\n ]\n}\n'
} > "$OUTPUT_PATH.tmp"
mv "$OUTPUT_PATH.tmp" "$OUTPUT_PATH"
cat "$OUTPUT_PATH"
if [ "$critical_count" -gt 0 ] || [ "$warning_count" -gt 0 ]; then
if [ "$SEND_NTFY" = "1" ] && [ -x "$NTFY_SCRIPT" ]; then
priority="default"
[ "$warning_count" -gt 0 ] && priority="high"
[ "$critical_count" -gt 0 ] && priority="urgent"
"$NTFY_SCRIPT" "$NTFY_TOPIC" \
"Komodo stack hygiene: $critical_count critical, $warning_count warning" \
"See $OUTPUT_PATH" "$priority" || true
fi
[ "$critical_count" -gt 0 ] && exit 2
exit 1
fi
+58
View File
@@ -0,0 +1,58 @@
# Smart Home Runtime Stack
Runtime-Zustand fuer Home Assistant auf Kallilabcore. Dieser Ordner gehoert zu
`homelab-infra`, weil Komodo den Stack deployt und Renovate die Images pflegt.
## Dienste
- `homeassistant`: Home Assistant Container, erreichbar ueber Traefik unter
`https://home.kaleschke.info`
- `smarthome-mosquitto`: interner MQTT-Broker fuer Home Assistant, spaeter
Zigbee2MQTT und ESPHome
## Abhaengigkeiten
- `frontend_net` existiert bereits und wird von Traefik genutzt.
- `smarthome_net` wird durch diesen Stack angelegt und ist `internal: true`.
- Das Fachrepo `smart-home-kalli` muss auf dem Unraid-Host unter
`/mnt/user/services/smart-home-kalli` liegen. Nur ausgewählte YAML-Dateien
werden read-only nach `/config` gemountet; `.storage` bleibt in
`/mnt/user/appdata/homeassistant`.
- Vor dem ersten Start muessen diese Dateien hostseitig angelegt werden:
- `/mnt/user/appdata/homeassistant/secrets.yaml`
- `/mnt/user/appdata/homeassistant/trusted_proxies.yaml`
- `/mnt/user/appdata/mosquitto/config/passwordfile`
- `/mnt/user/appdata/mosquitto/config/aclfile`
Das detaillierte Host-Bootstrap-Runbook liegt unter
`docs/runbooks/smart-home-bootstrap.md`.
## MQTT Bootstrap
Beispiel fuer den initialen Home-Assistant-MQTT-User auf dem Unraid-Host:
```sh
mkdir -p /mnt/user/appdata/mosquitto/config
docker run --rm -it \
-v /mnt/user/appdata/mosquitto/config:/mosquitto/external_config \
eclipse-mosquitto:2.0.22 \
mosquitto_passwd -c /mosquitto/external_config/passwordfile homeassistant
cat > /mnt/user/appdata/mosquitto/config/aclfile <<'EOF'
user homeassistant
topic readwrite #
EOF
```
LAN-Port `1883` bleibt in Phase 1 geschlossen. Eine Portfreigabe fuer externe
MQTT-Clients wird erst in der ESPHome-Phase mit ACLs und per-Device-Usern
ergaenzt.
## Ecowitt
Ecowitt wird nicht in Phase 1 exponiert. Wegen des globalen Traefik
HTTP-zu-HTTPS-Redirects bleibt die Ingress-Entscheidung offen:
1. Traefik-HTTP-Ausnahme nur fuer den Ecowitt-Webhook, falls der globale
EntryPoint-Redirect gezielt abloesbar ist.
2. Dokumentierter LAN-only Host-Port `8123` als Fallback, wenn Option 1 den
bestehenden Traefik-Standard zu stark verbiegt.
+58
View File
@@ -0,0 +1,58 @@
services:
homeassistant:
image: ghcr.io/home-assistant/home-assistant:2026.6.1@sha256:59aa8824955c9db491b75d2eebe42bd68494f80c2ec69ec0d66d9dae37d37514
container_name: homeassistant
restart: unless-stopped
environment:
TZ: Europe/Berlin
volumes:
- /mnt/user/appdata/homeassistant:/config
- /mnt/user/services/smart-home-kalli/home-assistant/configuration.yaml:/config/configuration.yaml:ro
- /mnt/user/services/smart-home-kalli/home-assistant/automations.yaml:/config/automations.yaml:ro
- /mnt/user/services/smart-home-kalli/home-assistant/scripts.yaml:/config/scripts.yaml:ro
- /mnt/user/services/smart-home-kalli/home-assistant/scenes.yaml:/config/scenes.yaml:ro
- /mnt/user/services/smart-home-kalli/home-assistant/packages:/config/packages:ro
networks:
- frontend_net
- smarthome_net
expose:
- "8123"
security_opt:
- no-new-privileges:true
depends_on:
- mosquitto
labels:
- traefik.enable=true
- traefik.docker.network=frontend_net
- traefik.http.routers.homeassistant.rule=Host(`home.kaleschke.info`)
- traefik.http.routers.homeassistant.entrypoints=websecure
- traefik.http.routers.homeassistant.tls=true
- traefik.http.routers.homeassistant.tls.certresolver=le
# 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:
image: eclipse-mosquitto:2.0.22@sha256:914f529386804c8278a4e581526b9be5e1604df44b30daabc70aa97dcefe5268
container_name: smarthome-mosquitto
restart: unless-stopped
volumes:
- ./mosquitto/config/mosquitto.conf:/mosquitto/config/mosquitto.conf
- /mnt/user/appdata/mosquitto/config:/mosquitto/external_config
- /mnt/user/appdata/mosquitto/data:/mosquitto/data
- /mnt/user/appdata/mosquitto/log:/mosquitto/log
networks:
- smarthome_net
expose:
- "1883"
security_opt:
- no-new-privileges:true
networks:
frontend_net:
external: true
smarthome_net:
name: smarthome_net
driver: bridge
internal: true
@@ -0,0 +1,15 @@
per_listener_settings true
listener 1883 0.0.0.0
allow_anonymous false
password_file /mosquitto/external_config/passwordfile
acl_file /mosquitto/external_config/aclfile
persistence true
persistence_location /mosquitto/data/
log_dest stdout
log_type error
log_type warning
log_type notice
connection_messages true