Add Glance community homelab widgets

This commit is contained in:
2026-05-17 18:07:57 +02:00
parent aeb7573b03
commit 175cd6951f
6 changed files with 168 additions and 3 deletions
+1
View File
@@ -22,6 +22,7 @@ Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ab
- Glance zeigt HTTP-Monitore fuer Core, Apps und Ops, Docker-Containergruppen, Host-Snapshot und Bookmarks.
- Docker-Status laeuft nicht ueber einen direkten Socket-Mount in Glance, sondern ueber `glance-docker-socket-proxy` auf einem internen `glance_socket_net`.
- Die HTTP-Monitore nutzen oeffentliche URLs als Klickziel und interne `check-url`-Endpunkte auf `frontend_net`, damit Glance nicht vom externen Hairpin-/Auth-Pfad abhaengt.
- Community-Widgets fuer Immich, Paperless, Speedtest Tracker und Scrutiny wurden ergaenzt. API-Zugriffe nutzen interne Service-URLs und Stack-ENV-Tokens.
### 2026-05-17 - Monitoring-Zielstack konsolidiert
+3 -2
View File
@@ -54,7 +54,7 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
| `monitoring/loki/loki-config.yml` | Loki Filesystem/Retention-Konfiguration fuer dedizierten Monitoring-Stack |
| `monitoring/promtail/promtail-config.yml` | Promtail Docker-Socket-Discovery fuer dedizierten Monitoring-Stack |
| `monitoring/grafana/provisioning/*` | Grafana Datasource-/Dashboard-Provisioning fuer Prometheus und Loki |
| `ops/glance/config/glance.yml` | Glance Dashboard-Konfiguration fuer Homelab-Monitore, Docker-Containergruppen, Host-Snapshot und Bookmarks |
| `ops/glance/config/glance.yml` | Glance Dashboard-Konfiguration fuer Homelab-Monitore, Community-Widgets, Docker-Containergruppen, Host-Snapshot und Bookmarks |
| `ops/borg-ui/scripts/pre-backup-dumps.sh` | Host-seitiges Dump-Skript fuer PostgreSQL, SQLite-Container-Dumps und Komodo Mongo |
| `services/posture-check/posture-check.sh` | Host-seitiger Posture-Check fuer Filesystem, Mover-Drift, NVMe-SMART, Fuellstand und ntfy-Alarmierung |
| `services/posture-check/docker-critical-events.sh` | Host-seitiger Docker-Event-Watcher fuer kritische ntfy-Alarme |
@@ -108,7 +108,7 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
| Borg UI | `ops/borg-ui/docker-compose.yml` | `borg-ui` -> `ainullcode/borg-ui:latest@sha256:...` | `borg.kaleschke.info` | `frontend_net` | keine | Borg repo, Dump-Scope, Restore-Ziel |
| code-server | `ops/code-server/docker-compose.yml` | `code-server` -> `lscr.io/linuxserver/code-server:4.116.0@sha256:...` | `code.kaleschke.info` | `frontend_net` | keine | Passwort-Datei, Workspace-Mounts |
| Filebrowser | `ops/filebrowser/docker-compose.yml` | `filebrowser` -> `filebrowser/filebrowser:v2.63.2@sha256:...` | `files.kaleschke.info` | `frontend_net` | keine | Documents/Photos/Projekte-Mounts, Admin-UI hinter Authelia |
| Glance | `ops/glance/docker-compose.yml` | `glance` -> `glanceapp/glance:v0.8.4`, `glance-docker-socket-proxy` -> `tecnativa/docker-socket-proxy:v0.4.2` | `glance.kaleschke.info` | `frontend_net`, `glance_socket_net` | keine | Homelab-Dashboard mit Monitor-, Docker-, DNS-, Server-Stats- und Release-Widgets; Docker-API nur ueber internen Socket-Proxy |
| Glance | `ops/glance/docker-compose.yml` | `glance` -> `glanceapp/glance:v0.8.4`, `glance-docker-socket-proxy` -> `tecnativa/docker-socket-proxy:v0.4.2` | `glance.kaleschke.info` | `frontend_net`, `glance_socket_net` | keine | Homelab-Dashboard mit Monitor-, Community-, Docker- und Server-Stats-Widgets; Docker-API nur ueber internen Socket-Proxy |
| Glances | `ops/glances/docker-compose.yml` | `glances` -> `nicolargo/glances:latest-full@sha256:...` | `glances.kaleschke.info` | `frontend_net` | keine | Rootfs/Docker-Socket fuer Monitoring |
| Grafana/InfluxDB | `ops/grafana-influxdb/docker-compose.yml` | `grafana`, `influxdb3-core` | `grafana.kaleschke.info` | `frontend_net`, `grafana_influx_internal`, `grafana_influx_lan` | `influxdb3-core`: `${INFLUXDB_BIND_IP:-127.0.0.1}:8181:8181` | abgeloester Altstand; nach erfolgreicher Migration durch `monitoring/` ersetzen |
| Loki/Alloy | `ops/loki/docker-compose.yml` | `loki`, `alloy` | keine | `backend_net` | keine | abgeloester Altstand; nach erfolgreicher Migration durch `monitoring-loki`/`monitoring-promtail` ersetzen |
@@ -221,6 +221,7 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam
| Immich | `IMMICH_DB_PASSWORD` Stack ENV; `immich_postgres_password.txt` fuer Postgres |
| Mail Archiver | `MAILARCHIVER_DB_CONNECTION`, `MAILARCHIVER_AUTH_PASSWORD` als Stack ENV |
| Homepage | viele `HOMEPAGE_VAR_*` Stack ENV Keys fuer Tokens/Logins |
| Glance | `GLANCE_IMMICH_API_KEY`, `GLANCE_PAPERLESS_TOKEN`, `GLANCE_SPEEDTEST_API_TOKEN` als Stack ENV fuer Community-Widgets |
| Speedtest | `APP_KEY`, `ADMIN_PASSWORD` als Stack ENV |
| Nextcloud | Admin User, Admin Password, Postgres Password via Secret-Dateien |
| Komodo | `KOMODO_SECRET_KEY`, `KOMODO_WEBHOOK_SECRET`, `KOMODO_JWT_SECRET`, `KOMODO_MONGO_PASSWORD`, `KOMODO_PERIPHERY_PASSKEY`; Mongo Passwort-Datei |
+1
View File
@@ -39,6 +39,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 |
| Homepage | API Tokens / Zugangsdaten | Stack ENV `HOMEPAGE_VAR_*` | aktiv |
| Glance | Community Widget API Tokens | Stack ENV `${GLANCE_IMMICH_API_KEY}`, `${GLANCE_PAPERLESS_TOKEN}`, `${GLANCE_SPEEDTEST_API_TOKEN}` | 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 |
+1 -1
View File
@@ -55,7 +55,7 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und
| Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs |
|---|---|---|---|---|---|---|---|---|
| `homepage` | Start-Dashboard | `apps/homepage/docker-compose.yml` | `https://home.kaleschke.info` | Traefik, viele API Tokens | `/mnt/user/appdata/homepage`, `/mnt/user/appdata/homepage/images` | Tier 2 | ja + Authelia laut Compose | Authelia schuetzt die Domain ueber die 1FA-Wildcard-Regel; Docker socket im Recovery entfernt, Docker-Widgets erst wieder via dokumentierter Ausnahme oder Socket-Proxy |
| `glance` | Homelab-Uebersicht / Status-Dashboard | `ops/glance/docker-compose.yml`, `ops/glance/config/glance.yml` | `https://glance.kaleschke.info` | Traefik + Authelia, interne HTTP-Checks, interner Docker-Socket-Proxy | Repo-Konfiguration; keine kritische Persistenz | Tier 3, rebuildbar | ja + Authelia | Zeigt aktive Dienste, HTTP-Monitore, Docker-Containergruppen, Host-Snapshot und Bookmarks; Docker-API nur ueber `glance-docker-socket-proxy` auf internem Netz |
| `glance` | Homelab-Uebersicht / Status-Dashboard | `ops/glance/docker-compose.yml`, `ops/glance/config/glance.yml` | `https://glance.kaleschke.info` | Traefik + Authelia, interne HTTP-Checks, Immich/Paperless/Speedtest/Scrutiny APIs, interner Docker-Socket-Proxy | Repo-Konfiguration; keine kritische Persistenz | Tier 3, rebuildbar | ja + Authelia | Zeigt aktive Dienste, Community-Widgets, HTTP-Monitore, Docker-Containergruppen, Host-Snapshot und Bookmarks; Docker-API nur ueber `glance-docker-socket-proxy` auf internem Netz |
| `komodo-core` | GitOps UI/API/Stack-Manager | `ops/komodo/docker-compose.yml` | `https://komodo.kaleschke.info` | Mongo, Gitea, Traefik | `/mnt/user/appdata/komodo/core`, `komodo_keys` | Tier 1 | ja, native Auth | keine pauschale Authelia-ForwardAuth; Gitea DNS override |
| `komodo-mongo` | Komodo Datenbank | `ops/komodo/docker-compose.yml` | intern | `komodo_net` | `/mnt/user/appdata/komodo/mongo`, `komodo_mongo_password.txt` | Tier 1, `komodo-mongo.archive.gz` | nein | Dump am 2026-05-04 bestaetigt; nach Major-Upgrades pruefen |
| `komodo-periphery` | Komodo Host-Agent | `ops/komodo/docker-compose.yml` | intern Core -> Periphery | Docker socket, `/mnt/user/services`, `frontend_net`, `komodo_net` | `/mnt/user/appdata/komodo/periphery`, `komodo_keys` | Tier 1 | nein | Docker-Socket-Ausnahme; `/mnt/user/services` Mount fuer Stack-Workspaces |
+159
View File
@@ -93,6 +93,165 @@ pages:
- size: full
widgets:
- 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: custom-api
title: Paperless
title-url: https://paperless.kaleschke.info
cache: 5m
url: http://paperless-ngx:8000/api/statistics/
headers:
Authorization: Token ${GLANCE_PAPERLESS_TOKEN}
Accept: application/json
template: |
<div class="flex justify-between text-center">
<div>
<div class="color-highlight size-h3">{{ .JSON.Int "documents_total" | formatNumber }}</div>
<div class="size-h6 uppercase">Dokumente</div>
</div>
<div>
<div class="color-highlight size-h3">{{ .JSON.Int "documents_inbox" | formatNumber }}</div>
<div class="size-h6 uppercase">Inbox</div>
</div>
<div>
<div class="color-highlight size-h3">{{ .JSON.Int "character_count" | formatNumber }}</div>
<div class="size-h6 uppercase">Zeichen</div>
</div>
</div>
- type: group
widgets:
- 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_TOKEN}
Accept: application/json
subrequests:
stats:
url: http://speedtest-tracker/api/v1/stats
headers:
Authorization: Bearer ${GLANCE_SPEEDTEST_API_TOKEN}
Accept: application/json
template: |
{{ $stats := .Subrequest "stats" }}
{{ $downloadChange := percentChange (.JSON.Float "data.download_bits") ($stats.JSON.Float "data.download.avg_bits") }}
{{ $uploadChange := percentChange (.JSON.Float "data.upload_bits") ($stats.JSON.Float "data.upload.avg_bits") }}
{{ $pingChange := percentChange (.JSON.Float "data.ping") ($stats.JSON.Float "data.ping.avg") }}
<div class="flex justify-between text-center">
<div>
<div class="size-small {{ if gt $downloadChange 0.0 }}color-positive{{ else if lt $downloadChange 0.0 }}color-negative{{ else }}color-primary{{ end }}">{{ $downloadChange | printf "%+.1f%%" }}</div>
<div class="color-highlight size-h3">{{ .JSON.Float "data.download_bits" | mul 0.000001 | printf "%.1f" }}</div>
<div class="size-h6 uppercase">Download</div>
</div>
<div>
<div class="size-small {{ if gt $uploadChange 0.0 }}color-positive{{ else if lt $uploadChange 0.0 }}color-negative{{ else }}color-primary{{ end }}">{{ $uploadChange | printf "%+.1f%%" }}</div>
<div class="color-highlight size-h3">{{ .JSON.Float "data.upload_bits" | mul 0.000001 | printf "%.1f" }}</div>
<div class="size-h6 uppercase">Upload</div>
</div>
<div>
<div class="size-small {{ if lt $pingChange 0.0 }}color-positive{{ else if gt $pingChange 0.0 }}color-negative{{ else }}color-primary{{ end }}">{{ $pingChange | printf "%+.1f%%" }}</div>
<div class="color-highlight size-h3">{{ .JSON.Float "data.ping" | printf "%.0f ms" }}</div>
<div class="size-h6 uppercase">Ping</div>
</div>
</div>
- type: custom-api
title: Drive Health
title-url: https://scrutiny.kaleschke.info
cache: 1h
url: http://scrutiny:8080/api/summary
method: GET
options:
filter_archived: true
sort_by: device.device_name
sort_order: asc
template: |
{{- $filterArchived := .Options.filter_archived }}
{{- $sortBy := .Options.sort_by }}
{{- $sortOrder := .Options.sort_order }}
{{- $drives := .JSON.Array "data.summary|@values" }}
{{- $sorted := $drives }}
{{- if or (eq $sortBy "device.capacity") (eq $sortBy "smart.power_on_hours") }}
{{- $sorted = sortByInt $sortBy $sortOrder $drives }}
{{- else }}
{{- $sorted = sortByString $sortBy $sortOrder $drives }}
{{- end }}
{{- $total := 0 }}
{{- range $sorted }}
{{- $archived := .Get "device.archived" }}
{{- $archivedBool := false }}
{{- if $archived }}{{- $archivedBool = eq $archived.Raw "true" }}{{- end }}
{{- if and $filterArchived $archivedBool }}{{- continue }}{{- end }}
{{- $total = add $total 1 }}
{{- end }}
{{- $count := 0 }}
{{- range $sorted }}
{{- $archived := .Get "device.archived" }}
{{- $archivedBool := false }}
{{- if $archived }}{{- $archivedBool = eq $archived.Raw "true" }}{{- end }}
{{- if and $filterArchived $archivedBool }}{{- continue }}{{- end }}
{{- $count = add $count 1 }}
{{- $device := .Get "device" }}
{{- $deviceName := $device.String "device_name" }}
{{- $model := $device.String "model_name" }}
{{- $wwn := $device.String "wwn" }}
{{- $days := printf "%.0f" (div (.Get "smart.power_on_hours").Num 24) }}
{{- $capacity := printf "%.0f" (div (.Get "device.capacity").Num 1000000000000) }}
{{- $status := (.Get "device.device_status").Num }}
{{- $latestTemp := index (.Array "temp_history") (sub (len (.Array "temp_history")) 1) }}
{{- $latestTempValue := $latestTemp.Int "temp" }}
<div class="flex justify-between items-center" style="gap: 12px; margin-top: 10px;">
<a href="https://scrutiny.kaleschke.info/web/device/{{ $wwn }}" target="_blank">
<div class="color-highlight" style="font-weight: 700; text-transform: uppercase;">/DEV/{{ $deviceName }} - {{ $capacity }}TB - {{ $latestTempValue }}C</div>
<div class="size-h6 color-subdue">{{ $model }} - {{ $days }} Tage Laufzeit</div>
</a>
<span class="{{ if eq $status 0.0 }}color-positive{{ else }}color-negative{{ end }}" style="font-size: 20px;">&bull;</span>
</div>
{{- if lt $count $total }}<hr class="color-secondary" style="margin: 10px 0; border: none; border-bottom: 1px solid currentColor;" />{{- end }}
{{- end }}
- type: monitor
title: Core, Security und Ingress
cache: 1m
+3
View File
@@ -5,6 +5,9 @@ services:
restart: unless-stopped
environment:
TZ: Europe/Berlin
GLANCE_IMMICH_API_KEY: ${GLANCE_IMMICH_API_KEY:-}
GLANCE_PAPERLESS_TOKEN: ${GLANCE_PAPERLESS_TOKEN:-}
GLANCE_SPEEDTEST_API_TOKEN: ${GLANCE_SPEEDTEST_API_TOKEN:-}
volumes:
- ./config:/app/config:ro
networks: