235 lines
22 KiB
Markdown
235 lines
22 KiB
Markdown
|
|
# Monitoring — Rebuild-Runbook
|
|||
|
|
|
|||
|
|
Neue LXC `CT104 monitoring` (`<LAN_IP_MONITORING>`, Tailscale-Mitglied `monitoring.<TAILNET>`) hostet als Docker-Compose-Stack: InfluxDB (Metrik-Datenbank), Grafana (Dashboards), Uptime Kuma (Erreichbarkeit + Alert-Routing), ntfy (Push-Benachrichtigungen). Ergänzt um PVE-Metric-Server-Export, Telegraf auf dem VPS, ein HDD-SMART-Tracking-Script und Login-/Zugriffs-Tracking je Dienst.
|
|||
|
|
|
|||
|
|
## 1. CT104 anlegen
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
pct create 104 local:vztmpl/debian-13-standard_13.6-1_amd64.tar.zst \
|
|||
|
|
--hostname monitoring --memory 1536 --swap 512 --cores 2 \
|
|||
|
|
--rootfs local-lvm:12 \
|
|||
|
|
--net0 name=eth0,bridge=vmbr0,ip=<LAN_IP_MONITORING>/24,gw=<LAN_IP_ROUTER> \
|
|||
|
|
--nameserver <LAN_IP_ADGUARD> --unprivileged 1 --features nesting=1 \
|
|||
|
|
--onboot 1 --timezone Europe/Berlin --ostype debian
|
|||
|
|
pct start 104
|
|||
|
|
|
|||
|
|
pct stop 104
|
|||
|
|
cat >> /etc/pve/lxc/104.conf <<'EOF'
|
|||
|
|
lxc.cgroup2.devices.allow: c 10:200 rwm
|
|||
|
|
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file 0 0
|
|||
|
|
EOF
|
|||
|
|
pct start 104
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Danach Docker (`curl -fsSL https://get.docker.com | sh`) und Tailscale (`curl -fsSL https://tailscale.com/install.sh | sh && tailscale up --hostname=monitoring --accept-routes=false`) installieren.
|
|||
|
|
|
|||
|
|
⚠️ **NICHT vergessen:** CT104 ins nächtliche `vzdump`-Backup aufnehmen (`crontab -e` auf dem PVE-Host, `104` zur ID-Liste ergänzen) — sonst ist der Monitoring-Stack selbst nicht gesichert.
|
|||
|
|
|
|||
|
|
## 2. Docker-Compose-Stack
|
|||
|
|
|
|||
|
|
`/opt/monitoring/docker-compose.yml` (Auszug, vollständige Datei im laufenden System): vier Services `influxdb`, `grafana`, `uptime-kuma`, `ntfy`, gemeinsamer `x-logging`-Anchor (10m×3 Dateien, bestehende Konvention). Ports: InfluxDB 8086, Grafana 3000, Uptime Kuma 3001, ntfy 8090→80.
|
|||
|
|
|
|||
|
|
⚠️ **NICHT TUN:** Grafana-Datenverzeichnis (`./grafana`) beim ersten Start nicht vor-chownen — der Container läuft als UID 472, ein per `pct exec`/root angelegtes Bind-Mount-Verzeichnis gehört aber root:root. Fix: `chown -R 472:472 /opt/monitoring/grafana` **vor** dem ersten Start von Grafana, sonst Crash-Loop ("not writable").
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mkdir -p /opt/monitoring/{influxdb,grafana,uptime-kuma,ntfy}
|
|||
|
|
chown -R 472:472 /opt/monitoring/grafana
|
|||
|
|
cd /opt/monitoring && docker compose up -d
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 3. InfluxDB: Setup, Buckets, Retention, Downsampling
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
docker exec influxdb influx setup --username admin --password '<PW>' \
|
|||
|
|
--org homelab --bucket raw --retention 14d --force
|
|||
|
|
docker exec influxdb influx bucket create --name aggregated --org homelab --retention 730d
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Zwei Buckets statt vieler kleiner: `raw` (14 Tage, volle Auflösung — deckt die längste Rohdaten-Anforderung aller Datentypen ab) und `aggregated` (2 Jahre, stündliche Mittelwerte). Downsampling-Task (`influx task create -f downsample.flux`):
|
|||
|
|
|
|||
|
|
```flux
|
|||
|
|
option task = {name: "downsample-hourly", every: 1h}
|
|||
|
|
|
|||
|
|
from(bucket: "raw")
|
|||
|
|
|> range(start: -task.every)
|
|||
|
|
|> filter(fn: (r) => r._field != "name" and r._field != "status" and r._field != "type" and r._field != "tags" and r._field != "uptime_format" and r._field != "content")
|
|||
|
|
|> aggregateWindow(every: 1h, fn: mean, createEmpty: false)
|
|||
|
|
|> to(bucket: "aggregated", org: "homelab")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
⚠️ **NICHT TUN:** Den `raw`-Bucket ohne Feld-Filter blind mit `aggregateWindow(fn: mean)` downsamplen. Proxmox' `system`-Measurement (LXC- und Storage-Objekte) enthält Text-Felder (`name`, `status`, `type`, `tags`, `uptime_format`, `content`) — `mean` auf einer String-Spalte lässt den Task mit `unsupported input type for mean aggregate: string` fehlschlagen. Vor dem Anlegen des Tasks die tatsächlichen Feldnamen je Measurement prüfen (`schema.fieldKeys`), nicht raten.
|
|||
|
|
|
|||
|
|
Retention-Prinzip (Details/Begründung siehe Plan-Historie): Rohdaten kurz (Tage), Aggregate lang (Jahre) — hält die Gesamtgröße über Jahre konstant klein statt linear mit der Laufzeit zu wachsen.
|
|||
|
|
|
|||
|
|
## 4. PVE Metric Server → InfluxDB
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
pvesh create /cluster/metrics/server/monitoring \
|
|||
|
|
--type influxdb --server <LAN_IP_MONITORING> --port 8086 \
|
|||
|
|
--influxdbproto http --organization homelab --bucket raw --token '<TOKEN>'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Pusht Host- und LXC-Metriken automatisch alle ~10s, kein Agent in den LXCs nötig. Guest-Metriken landen unter Measurement `system` (Felder `cpu`, `mem`, `maxmem`, …, Tag `object=lxc`), Host-Metriken unter `cpustat`/`memory`/`blockstat`/`nics` (Tag `object=nodes`) — **unterschiedliches Schema**, beim Dashboard-Bau beide Fälle abdecken.
|
|||
|
|
|
|||
|
|
## 5. Telegraf auf dem VPS
|
|||
|
|
|
|||
|
|
VPS ist kein PVE-Cluster-Mitglied, braucht eigenen Agent:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
apt install -y gnupg
|
|||
|
|
curl -sL -o /tmp/telegraf.deb https://dl.influxdata.com/telegraf/releases/telegraf_1.39.1-1_amd64.deb
|
|||
|
|
dpkg -i /tmp/telegraf.deb
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
⚠️ **NICHT TUN:** Das offizielle InfluxData-apt-Repo (`repos.influxdata.com/debian`) auf Debian 13 (trixie) einbinden — die GPG-Signatur wird von `apt`/`sqv` mit "Missing key" abgelehnt (Schlüssel-Rotation nicht mit dem dokumentierten `_compat.key` synchron). Stattdessen das `.deb` direkt von `dl.influxdata.com/telegraf/releases/` laden und mit `dpkg -i` installieren.
|
|||
|
|
|
|||
|
|
⚠️ **NICHT vergessen:** Minimale Debian-13-Installationen haben oft kein `cron`-Paket (`crontab: command not found`) — `apt install -y cron && systemctl enable --now cron` vor dem ersten `crontab -e`.
|
|||
|
|
|
|||
|
|
`/etc/telegraf/telegraf.conf`: `outputs.influxdb_v2` auf `http://<CT104-Tailscale-IP>:8086`, Bucket `raw`, Org `homelab`; Inputs `cpu`, `mem`, `disk`, `net`, `system`.
|
|||
|
|
|
|||
|
|
## 6. HDD-SMART-Tracking (PVE-Host)
|
|||
|
|
|
|||
|
|
`/usr/local/bin/hdd-smart-to-influx.sh` liest `smartctl -A` für beide Platten (Attribute `Load_Cycle_Count`, `Start_Stop_Count`, `Temperature_Celsius` — Raw-Wert ist das erste Feld vor evtl. Klammertext), `zfs list -H -o name,used,avail,refer -p` für `nas`/`nas/freigabe`/`nas/backups` (Measurement `zfs_usage`, `/` im Dataset-Namen zu `_` normalisiert für den Tag-Wert) **und** NVMe-Belegung (Measurement `nvme_usage`) — die VG `pve` deckt praktisch die gesamte Intenso-NVMe ab (nur EFI-/Boot-Partitionen liegen außerhalb, vernachlässigbar). Schreibt alles per InfluxDB-Line-Protocol via `curl` in den `raw`-Bucket. systemd-Timer `hdd-smart-to-influx.timer`, alle 5 Minuten:
|
|||
|
|
|
|||
|
|
⚠️ **NICHT TUN:** Bei LVM-Thin-Provisioning (hier: LV `pve/data`, 137GB Pool für alle LXC-Disks) `vgs -o vg_size,vg_free` als "wie voll ist die Platte" interpretieren. Das misst nur, wie viel VG-Fläche noch **unallokiert** ist (also Platz für neue LVs) — nicht, wie viel der bereits allokierten LVs tatsächlich mit Daten beschrieben ist. Bei diesem Setup zeigte `vgs` 93% "belegt", obwohl real nur ~8% Daten auf der Platte lagen (root-LV zu 68GB allokiert, aber nur 6GB genutzt; Thin-Pool zu 137GB allokiert, aber nur 8,88% beschrieben). Für eine ehrliche Nutzungsanzeige: `df --output=used -B1 /` (root-FS) + `lvs -o lv_size,data_percent pve/data` (Thin-Pool-Ist-Nutzung) addieren → Feld `actual_used_gb`, getrennt von `total_gb`/`free_gb` (VG-Allokation, weiterhin nützlich um zu wissen, wie viel Raum für neue LVs übrig ist).
|
|||
|
|
|
|||
|
|
```ini
|
|||
|
|
# /etc/systemd/system/hdd-smart-to-influx.timer
|
|||
|
|
[Timer]
|
|||
|
|
OnBootSec=2min
|
|||
|
|
OnUnitActiveSec=5min
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Grafana-Panel "HDD Load-Cycle-Rate" (`derivative(unit: 1h, nonNegative: true)` auf `load_cycle_count`) ist die eigentliche Verifikation, ob ein Spindown-Timeout in der Praxis zu häufiges Spin-up/down verursacht.
|
|||
|
|
|
|||
|
|
⚠️ **NICHT TUN:** Bei Prozent-Berechnungen aus zwei InfluxDB-Feldern (z.B. `used_bytes / (used_bytes + avail_bytes)`) auf automatische Int→Float-Konvertierung verlassen. Line-Protocol-Felder mit `i`-Suffix sind in Flux `int`, eine Division mit einem Float-Literal (`* 100.0`) scheitert dann mit `type conflict: float != int`. Fix: `float(v: r.used_bytes) / float(v: r.used_bytes + r.avail_bytes) * 100.0`.
|
|||
|
|
|
|||
|
|
## 7. Grafana: Datasource + Dashboard per Provisioning
|
|||
|
|
|
|||
|
|
`/opt/monitoring/grafana-provisioning/datasources/influxdb.yaml` (InfluxDB2/Flux, fester `uid: influxdb-homelab`) und `/opt/monitoring/grafana-provisioning/dashboards/{provider.yaml,homelab.json}`, gemountet nach `/etc/grafana/provisioning`. Dashboard-JSON referenziert die Datasource über die feste `uid`, nicht per Name.
|
|||
|
|
|
|||
|
|
Dashboard-Struktur (per `"type": "row"`-Panels gruppiert, jede Zeile bewusst `"collapsed": false`): **Übersicht** (Esprimo/PVE-Host CPU+RAM als Graph + Intenso-NVMe frei/gesamt/Belegung-%, direkt darunter dasselbe Muster für den VPS: CPU+RAM als Graph + Root-Filesystem frei/gesamt/Belegung-%), **Ressourcen** (CPU/RAM/Speicherbelegung nur LXCs — PVE-Host und VPS bewusst nicht doppelt, stehen schon in der Übersicht), **Festplatten** (ZFS-Belegung in Bytes, HDD-Load-Cycle-Count/-Rate, Temperatur), **Logins & Zugriffe** (erfolgreiche/fehlgeschlagene Logins je Dienst, gestapelte Balken). `refresh: "1m"` auf Dashboard-Ebene gesetzt.
|
|||
|
|
|
|||
|
|
VPS-Speicherbelegung braucht kein neues Tracking — Telegrafs `disk`-Input-Plugin (bereits für die Ressourcen-Metriken konfiguriert, siehe Abschnitt 5) liefert `free`/`total`/`used_percent` für `/` schon fertig aufbereitet, kein manueller Prozent-Umweg wie bei NVMe/ZFS nötig.
|
|||
|
|
|
|||
|
|
**LXC-Speicherbelegung** (Panel "Speicherbelegung: LXCs", Ressourcen-Abschnitt): braucht kein neues Tracking-Script — Felder `disk`/`maxdisk` kommen schon über den PVE-Metric-Export im `system`-Measurement mit (Rootfs-Füllstand je Container, nicht zu verwechseln mit der NVMe- oder ZFS-Belegung).
|
|||
|
|
|
|||
|
|
⚠️ **NICHT TUN:** Bei Stat-Panels mit `pivot()`+`map()`-Berechnung (z.B. Prozent aus zwei Feldern) das Ergebnis ohne abschließendes `|> keep(columns: ["_time", "_value"])` zurückgeben. Grafana zeigt bei `reduceOptions.fields: ""` (Auto-Erkennung) sonst **jedes** numerische Feld der Tabelle als eigene Kachel-Zahl an — inklusive der Zwischenwerte (`used_bytes`, `avail_bytes`) neben dem eigentlichen `_value`. Gleiches Prinzip für Legenden bei Mehrserien-Timeseries-Panels: ohne `keep(columns: ["_time", "host", "_value"])` zeigt die Legende alle Tags (`nodename`, `object`, `vmid`, …) statt nur des relevanten (`host`).
|
|||
|
|
|
|||
|
|
⚠️ **NICHT TUN:** Bei Stat-Panels mit reinen Referenzzahlen (z.B. "frei (GB)", "gesamt (GB)") `colorMode: "value"` ohne explizite Thresholds stehen lassen. Grafanas Default-Threshold färbt ab Wert 80 automatisch rot — bei einer GB-Zahl wie 213,9 (viel freier Platz, eigentlich gut) sieht das wie ein Alarm aus, obwohl "hoch" hier positiv ist. Für reine Referenzzahlen `colorMode: "none"` setzen; Ampelfarben nur bei tatsächlich alarmwürdigen Metriken (z.B. Belegungs-Prozent, wo hoch = schlecht stimmt) verwenden.
|
|||
|
|
|
|||
|
|
⚠️ **NICHT vergessen:** `GF_SECURITY_ADMIN_PASSWORD` wirkt nur bei der **ersten** DB-Initialisierung. Ist Grafana schon einmal mit Default-Credentials (`admin`/`admin`) gestartet, hilft nur `docker exec grafana grafana cli admin reset-admin-password '<PW>'` (neuere Grafana-Versionen: `grafana cli`, nicht mehr das alte `grafana-cli`-Binary).
|
|||
|
|
|
|||
|
|
## 8. Uptime Kuma: Monitore + ntfy-Notification
|
|||
|
|
|
|||
|
|
Kein offizielles REST-API für Monitor-Verwaltung — Setup über die Python-Bibliothek `uptime-kuma-api` (nutzt das interne Socket.IO-Protokoll):
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
python3 -m venv /opt/monitoring/kuma-venv
|
|||
|
|
/opt/monitoring/kuma-venv/bin/pip install uptime-kuma-api
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
from uptime_kuma_api import UptimeKumaApi, MonitorType, NotificationType
|
|||
|
|
api = UptimeKumaApi('http://localhost:3001')
|
|||
|
|
api.setup('admin', '<PW>') # nur beim allerersten Mal
|
|||
|
|
api.login('admin', '<PW>')
|
|||
|
|
api.add_notification(name='ntfy-homelab', type=NotificationType.NTFY, isDefault=True,
|
|||
|
|
ntfyserverurl='http://ntfy:80', ntfytopic='homelab-alerts',
|
|||
|
|
ntfyPriority=4, ntfyAuthenticationMethod='none')
|
|||
|
|
api.add_monitor(type=MonitorType.HTTP, name='...', url='...', interval=60, maxretries=2)
|
|||
|
|
api.add_monitor(type=MonitorType.PING, name='...', hostname='...', interval=60, maxretries=2)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
⚠️ **NICHT TUN:** Sich auf `isDefault=True` bei `add_notification` verlassen, um die Notification automatisch an alle (auch später erstellte) Monitore zu hängen — greift über die API nicht zuverlässig. Stattdessen nach dem Anlegen explizit für jeden Monitor `api.edit_monitor(id, notificationIDList={'<notif_id>': True})` setzen.
|
|||
|
|
|
|||
|
|
⚠️ **NICHT TUN:** `retries=` als Kwarg für `add_monitor` verwenden — die Bibliothek erwartet `maxretries`, sonst `TypeError`.
|
|||
|
|
|
|||
|
|
Angelegte Monitore: 5× HTTP (Guacamole öffentlich, Vaultwarden/PVE-Web über Tailnet-Namen, AdGuard/Filebrowser über LAN-IP — bewusst IP statt `*.pve`-Name bei AdGuard, um keine Zirkularität zu erzeugen, falls AdGuard selbst der Ausfall ist), 6× Ping (VPS über Tailscale-IP, PVE-Host + 4 LXCs über LAN-IP).
|
|||
|
|
|
|||
|
|
## 9. ntfy: Erreichbarkeit + tailscale serve
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
tailscale serve --bg http://localhost:8090
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
⚠️ **NICHT TUN:** `https+insecure://localhost:8090` verwenden (das Muster aus `01-pve-host.md`/PVE-Webinterface) — dort war es richtig, weil das Backend (`pveproxy`) selbst HTTPS mit selbstsigniertem Zertifikat spricht. ntfy spricht intern nur **plain HTTP**; `https+insecure://` gegen einen HTTP-Backend führt zu `502 Bad Gateway`. Korrekt: `http://localhost:8090`.
|
|||
|
|
|
|||
|
|
Nach Einrichtung `NTFY_BASE_URL` in der Compose-Datei auf `https://monitoring.<TAILNET>` setzen und `docker compose up -d ntfy` (Recreate).
|
|||
|
|
|
|||
|
|
## 10. Backup-Cronjobs: Fehlschlag-Alerts
|
|||
|
|
|
|||
|
|
Bestehende Cronjobs (`vzdump`, `backup-guacamole.sh`, rsync-Pull) auf Wrapper-Skripte umgestellt, die bei Fehlschlag (`if ! <command>; then curl ntfy; fi` bzw. `trap ... ERR`) eine Push-Nachricht senden. Bewusst nur bei Fehlschlag, kein täglicher Erfolgs-Spam. Ersetzt die bis dahin wirkungslose `vzdump`-Mail-Benachrichtigung (Postfix auf dem PVE-Host ist `inet_interfaces = loopback-only`, kein Relay, kein root-Postfach — die Mail ging faktisch ins Leere).
|
|||
|
|
|
|||
|
|
## 11. Login-/Zugriffs-Tracking
|
|||
|
|
|
|||
|
|
Kleine Skripte (alle 5 Min. per Cron) statt eines Loki/Promtail-Log-Stacks — schreiben Erfolgs-/Fehlschlag-Zähler nach InfluxDB, pushen bei Schwellenwert (>3 Fehlschläge/5 Min.) einen ntfy-Alert. Zusätzlich pusht jedes Skript bei `SUCCESS -gt 0` eine niedrigprioritäre ntfy-Meldung für erfolgreiche Logins (eigener Block je Skript, `Priority: default` statt `high`, Tag `white_check_mark` statt `warning`).
|
|||
|
|
|
|||
|
|
⚠️ **Bekannte Rauschquelle:** Vaultwardens Clients nutzen denselben `/identity/connect/token`-Endpunkt auch für OAuth-Token-Refreshes, nicht nur für echte Logins — der Erfolgs-Alert kann dadurch öfter feuern als ein tatsächlicher neuer Login stattfindet. Bewusst so belassen (Nutzeranforderung), aber beim nächsten Mal ggf. auf `grant_type=password` in den Logs filtern, um echte Logins von Refreshes zu trennen.
|
|||
|
|
|
|||
|
|
| Dienst | Quelle |
|
|||
|
|
|---|---|
|
|||
|
|
| Guacamole (VPS) | Docker-Logs (`InMemoryAuthenticationFailureTracker`) + `guacamole_user_history`-Tabelle für Erfolge |
|
|||
|
|
| Vaultwarden (CT103) | Docker-Logs, `(login) POST /identity/connect/token => <Status>` |
|
|||
|
|
| PVE-Weboberfläche (Host) | `/var/log/pveproxy/access.log`, byte-offset-basiertes Tailing (kein natives `--since`) |
|
|||
|
|
| SSH (PVE-Host) | `journalctl -u ssh --since "5 min ago"` |
|
|||
|
|
| SSH/fail2ban (VPS) | `fail2ban-client status sshd` (Currently/Total failed/banned) — kein Log-Parsing nötig, fail2ban liefert die Zahlen direkt; Alert nur wenn „Currently banned" gegenüber letzter Messung steigt |
|
|||
|
|
| AdGuard-Web-UI (CT100) | **nicht umgesetzt** — AdGuard loggt fehlgeschlagene Web-UI-Logins nachweislich nicht (getestet: weder Journal noch mit `verbose`-Flag), trotz eingebautem Lockout (`auth_attempts: 5`, `block_auth_min: 15`). Bewusste, dokumentierte Lücke statt einer fragilen Behelfslösung |
|
|||
|
|
|
|||
|
|
**Update 2026-07-19 — Wer/IP statt nur Zahlen:** Alle fünf Skripte (außer fail2ban, das schon Zahlen liefert) hängen den echten Login-Kontext an den ntfy-Text an:
|
|||
|
|
- **SSH (Host):** `user@ip` per `sed -nE` aus `Accepted`/`Failed password`/`Invalid user`-Zeilen extrahiert
|
|||
|
|
- **PVE-Web:** Quell-IP (erstes Feld in `access.log`, `::ffff:`-Präfix entfernt)
|
|||
|
|
- **fail2ban:** von reinem Status-Polling auf `fail2ban.log`-Tailing (byte-offset) umgestellt — liefert die tatsächlich gesperrte(n) IP(s) aus den `NOTICE [sshd] Ban <ip>`-Zeilen statt nur "eine neue IP"
|
|||
|
|
- **Vaultwarden:** brauchte `IP_HEADER: "X-Forwarded-For"` in der Compose-Datei (Vaultwarden loggt IPs sonst gar nicht) — `tailscale serve` setzt den Header bereits automatisch, nur Vaultwarden vertraute ihm nicht. Danach erscheint bei Fehlschlägen `Username or password is incorrect... IP: X. Username: Y.` im Log, extrahierbar. Bei **Erfolg** bleibt die IP unverfügbar (dieser Log-Pfad existiert nur im Fehlerfall)
|
|||
|
|
- **Guacamole:** brauchte `REMOTE_IP_VALVE_ENABLED: "true"` in der Compose-Datei (Tomcats RemoteIpValve, liest `X-Forwarded-For` von Caddy). Vorher zeigte der Fehlschlag-Tracker nur die interne Docker-Gateway-IP (`172.18.0.1`), nach dem Fix die echte Client-IP. Bei **Erfolg** bleibt `guacamole_user_history.remote_host` trotzdem auf der internen Adresse — der DB-Verlauf nutzt offenbar einen anderen internen Pfad als der Live-Fehlschlag-Tracker, der die Valve-korrigierte Adresse honoriert. Nicht weiter debuggt (Aufwand/Nutzen), Username bei Erfolg ist trotzdem neu und nützlich
|
|||
|
|
|
|||
|
|
⚠️ **NICHT TUN:** `paste -sd', ' -` verwenden, um mehrere Werte mit `, ` zu verbinden. `paste -s -d LISTE` zykelt durch die **einzelnen Zeichen** der Delimiter-Liste statt sie als einen mehrzeichigen Trenner zu nutzen — bei 3+ Werten entsteht `a,b c,d e,f` (abwechselnd Komma und Leerzeichen) statt `a, b, c, d, e, f`. Fällt bei nur 1-2 Werten nicht auf (Zufallstreffer), erst bei mehreren wird der Bug sichtbar. Fix: `paste -sd',' - | sed 's/,/, /g'`.
|
|||
|
|
|
|||
|
|
## 12. HDD-Tracking hat sich selbst sabotiert (kritischer Fund)
|
|||
|
|
|
|||
|
|
Das 5-Minuten-Tracking-Script (`hdd-smart-to-influx.sh`) weckte mit jedem Lauf die schlafenden Platten — `smartctl -A` liest per Default auch bei einer Platte im Standby, was sie aufweckt. Da Check-Takt (5 Min.) und Spindown-Timeout (5 Min., siehe `01-pve-host.md`) identisch sind, hat das Monitoring den gerade erst eingerichteten Spindown fast vollständig ausgehebelt — bewiesen durch manuellen Test (Platte per `hdparm -y` schlafen gelegt, `smartctl -A` drüber, Status sofort wieder `active/idle`).
|
|||
|
|
|
|||
|
|
**Fix:** `smartctl -n standby -A <dev>` — überspringt (ohne Weckvorgang) die Abfrage, wenn die Platte schon schläft; Skript erkennt das am Exit-Code/der Meldung `Device is in STANDBY mode` und schreibt für diesen Durchlauf einfach keinen Datenpunkt für die schlafende Platte (InfluxDB/Grafana kommen mit Lücken problemlos klar).
|
|||
|
|
|
|||
|
|
⚠️ **NICHT TUN:** `smartctl -A` (ohne `-n standby`) in einem Monitoring-Script verwenden, das eine Platte im Standby beobachten soll — das ist ein Widerspruch in sich, das Skript verhindert genau das, was es messen will.
|
|||
|
|
|
|||
|
|
## 13. Esprimo-Hardware-Sensoren (CPU-Temp + Lüfter)
|
|||
|
|
|
|||
|
|
Fujitsu-eigener Sensorchip, nicht automatisch aktiv:
|
|||
|
|
```bash
|
|||
|
|
apt install -y lm-sensors
|
|||
|
|
yes | sensors-detect --auto
|
|||
|
|
echo -e "ftsteutates\ncoretemp" > /etc/modules-load.d/ftsteutates.conf
|
|||
|
|
modprobe ftsteutates coretemp
|
|||
|
|
```
|
|||
|
|
Liefert `coretemp-isa-0000` (`Package id 0` = CPU-Temperatur) und `ftsteutates-i2c-0-73` (`fan2` = einziger belegter Lüfterkanal von 8, Rest `FAULT`/nicht verbaut). Werte per `sensors <chip>` parsen, ins `hdd-smart-to-influx.sh`-Script integriert (Measurement `esprimo_hw`, Felder `cpu_temp_c`/`fan_rpm`) — läuft im selben 5-Min-Timer mit statt einen eigenen zu brauchen.
|
|||
|
|
|
|||
|
|
Grafana-Panel kombiniert beide Metriken in einer Kachel mit zwei Y-Achsen (Temperatur links, RPM rechts, per `byFrameRefID`-Field-Override) — zeigt auf einen Blick, ob und wann der Lüfter bei steigender Last einsetzt (Ziel: möglichst viel passive Kühlung).
|
|||
|
|
|
|||
|
|
## 14. Load-Cycle-Verifikation: tägliche Momentaufnahme statt Dauerkurve
|
|||
|
|
|
|||
|
|
Zusätzlich zur laufenden 5-Min-Erfassung (jetzt korrekt, siehe Punkt 12) schreibt `vzdump-backup.sh` einmal täglich (03:00, während die Platten wegen des Snapshots ohnehin wach sind — kein zusätzlicher Weckvorgang) den rohen `Load_Cycle_Count` nach InfluxDB. Grafana-Panel "Differenz zum Vortag":
|
|||
|
|
```flux
|
|||
|
|
from(bucket: "aggregated")
|
|||
|
|
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|
|||
|
|
|> filter(fn: (r) => r._measurement == "smart" and r._field == "load_cycle_count")
|
|||
|
|
|> aggregateWindow(every: 1d, fn: last, createEmpty: false)
|
|||
|
|
|> difference(nonNegative: true)
|
|||
|
|
```
|
|||
|
|
Beantwortet direkt "wie viele Spin-ups gab es seit gestern" — eindeutiger als die stündliche Rate-Kurve, die bei Datenlücken (Platte war lange am Stück wach oder lange am Stück im Standby) leicht misszuinterpretieren ist.
|
|||
|
|
|
|||
|
|
## Guacamole-SFTP: bekanntes, ungelöstes Problem
|
|||
|
|
|
|||
|
|
Die SFTP-Begleitverbindung (Datei-Transfer-Feature, `enable-sftp`) der VNC-Verbindung "Management-Desktop (Browser)" ist aktuell **deaktiviert** (`enable-sftp: false` in der DB). Ursache war zunächst ein reines Passwort-Drift zwischen dem in Guacamole hinterlegten Passwort und dem tatsächlichen System-Passwort des `transfer`-Users auf CT102 (behoben, `chpasswd`) — aber selbst mit korrektem Passwort scheiterte `guacd`s eingebauter SSH-Client (`libssh2`) weiterhin an der Authentifizierung, während derselbe Login mit dem normalen OpenSSH-Client (von PVE-Host **und** vom VPS aus, exakt derselbe Netzwerkpfad wie `guacd`) anstandslos funktionierte. Deutet auf eine Algorithmus-/Protokoll-Inkompatibilität zwischen `libssh2` und CT102s OpenSSH-Server hin (z.B. moderne KEX-Defaults, die `libssh2` nicht unterstützt), nicht auf ein Credential-Problem.
|
|||
|
|
|
|||
|
|
VNC selbst funktioniert einwandfrei (davon unabhängig). Für eine vollständige Lösung: `sshd_config` auf CT102 um ältere, `libssh2`-kompatible `KexAlgorithms`/`HostKeyAlgorithms` erweitern (Kompatibilitäts-Fallback, nicht global schwächen) und erneut mit aktiviertem `enable-sftp` testen — noch nicht gemacht (Priorität war, den Nutzer schnell wieder reinzulassen).
|
|||
|
|
|
|||
|
|
## Referenz: Zugänge
|
|||
|
|
|
|||
|
|
| Dienst | URL | Hinweis |
|
|||
|
|
|---|---|---|
|
|||
|
|
| Grafana | `http://<LAN_IP_MONITORING>:3000` | Admin-Passwort in Vaultwarden |
|
|||
|
|
| Uptime Kuma | `http://<LAN_IP_MONITORING>:3001` | Admin-Passwort in Vaultwarden |
|
|||
|
|
| InfluxDB | `http://<LAN_IP_MONITORING>:8086` | Admin-Passwort + Token in Vaultwarden |
|
|||
|
|
| ntfy (Push) | `https://monitoring.<TAILNET>`, Topic `homelab-alerts` | Android/iOS-App auf den self-hosted Server zeigen lassen |
|
|||
|
|
| NAS-Backups (SMB) | `\\nas.pve\backups` | Read-only, gleicher Nutzer `<PRIMARY_USER>` wie `freigabe` |
|