monitoring + backup: Stale-Handle-Hardening und Dead-Man's-Switch
Schliesst den lokalen Code-Stand fuer zwei offene MASTER_TODO-Punkte ab. monitoring: restliche Einzeldatei-Bind-Mounts (alertmanager, blackbox, loki, promtail, alertmanager-ntfy-bridge) auf Directory-Mounts umgestellt, analog zum Prometheus-Fix vom 2026-06-19. Vermeidet "Stale NFS file handle" auf dem /mnt/user-FUSE-Share bei git/Komodo-Updates. grafana-provisioning war bereits Directory-Mount. `docker compose config` gruen. Beim Deploy --force-recreate noetig, da sich Mount-Zielpfade aendern. backup: endpoint-agnostischer Dead-Man's-Switch (Healthchecks-kompatibel, Cloud oder self-hosted) in pull-critical-backups.ps1 und pre-borg.sh. Pings /start, Erfolg und /fail; No-Op ohne konfigurierte URL, bricht also keinen Lauf. Ping-URLs sind Capability-URLs und bleiben als Secret ausserhalb des Repos. Doku: SECRETS_MAP, Nearline-README und MASTER_TODO nachgezogen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+3
-3
@@ -1,6 +1,6 @@
|
||||
# Master To-do - KalliLab CORE
|
||||
|
||||
Typ: Status/To-do · Stand: 2026-06-18 · Status: aktiv
|
||||
Typ: Status/To-do · Stand: 2026-06-21 · Status: aktiv
|
||||
|
||||
Diese Liste ist die **einzige** Arbeitsliste fuer offene operative Punkte im
|
||||
Homelab. Detailablaeufe stehen in den verlinkten Runbooks; Entscheidungen mit
|
||||
@@ -25,8 +25,8 @@ Host-Reports (`/mnt/user/backups/restore-reports/`) und in der Git-Historie.
|
||||
| Restore-Test Tailscale | Operator | State-Validierung + Reconnect nur auf Wegwerf-Host/VM, danach Geraet in Tailscale-Admin entfernen | `ops/restore-tests/tailscale-runbook.md` |
|
||||
| Authelia OIDC fuer Apps | Operator/Codex | Live: Grafana + Mealie login-verifiziert; Paperless Secret verdrahtet und Service-Smoke am 2026-06-17 gruen, finaler Browser-Login mit Operator-Account offen. Immich + Nextcloud bewusst geparkt bis Family-Onboarding (siehe `docs/DECISIONS.md` 2026-06-06) | `docs/AUTHELIA_OIDC_PLAN.md` |
|
||||
| Home Assistant Tibber | Operator/Codex | Tibber per HA-UI-Config-Flow verbinden. Danach Energy-Dashboard um echte Kosten/Preisquelle ergaenzen; SolarEdge-PV, Netz und Speicher sind bereits konfiguriert und validiert | `docs/runbooks/smart-home-bootstrap.md`, `docs/DECISIONS.md` |
|
||||
| Nearline-Pull Dead-Man's-Switch | Operator | H:-Pull war ~2026-06-04 bis 2026-06-18 still gestoppt (Task fehlte, kein Alarm). Lauf nachgeholt + Scheduled Task `KalliLab H Drive Nearline Pull` neu registriert (State Ready). **Verbleibt:** externer Dead-Man's-Switch (Healthchecks.io-Ping am Ende von `pull-critical-backups.ps1` und `ops/borg-ui/scripts/pre-borg.sh`), da Prometheus auf Unraid den baerchen-Pull nicht sieht | `ops/h-drive-nearline/README.md` |
|
||||
| Monitoring Single-File-Bind-Mount Hardening | Operator/Claude | Prometheus am 2026-06-19 auf stabilen Directory-Mount (`./prometheus:/etc/prometheus/config:ro`) umgestellt + recreated -> Stale-Handle-Footgun dort beseitigt, Reload reicht wieder. **Verbleibt:** gleiches Einzeldatei-Muster bei alertmanager/blackbox/loki/promtail/grafana-provisioning praeventiv auf Directory-Mount umstellen | `monitoring/docker-compose.yml` |
|
||||
| Nearline-Pull Dead-Man's-Switch | Operator | Heartbeat-Pings (`/start`, `/fail`, Erfolg) sind lokal in `pull-critical-backups.ps1` und `ops/borg-ui/scripts/pre-borg.sh` verdrahtet (endpoint-agnostisch, No-Op ohne URL). **Verbleibt:** Operator legt je einen Healthchecks-Check an, hinterlegt die Capability-URL (baerchen: ENV `HEALTHCHECKS_NEARLINE_URL` bzw. `%USERPROFILE%\.kallilab\…`; Unraid: `/mnt/user/appdata/secrets/healthchecks_borg_url`) und macht je einen Testlauf | `ops/h-drive-nearline/README.md` |
|
||||
| Monitoring Single-File-Bind-Mount Hardening | Operator/Claude | alertmanager/blackbox/loki/promtail + alertmanager-ntfy-bridge lokal auf Directory-Mounts umgestellt (grafana-provisioning war bereits Directory-Mount); `docker compose config` gruen. **Verbleibt:** Push + Komodo-Redeploy des monitoring-Stacks mit `--force-recreate` (Mount-Pfade aendern sich), danach Reload-/Alert-Smoke | `monitoring/docker-compose.yml` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb
|
||||
| nextcloud-postgres | DB Password | `/mnt/user/appdata/secrets/nextcloud_postgres_password.txt` -> `POSTGRES_PASSWORD_FILE` | neu |
|
||||
| Borg UI / Borg | Admin-Login, `SECRET_KEY`, SSH-Keys, Repo-Credentials | persistent unter `/mnt/user/appdata/borg-ui/data/` | aktiv |
|
||||
| Borg Repo | Borg-Passphrase fuer Restore-Tests und Notfallzugriff | `/mnt/user/appdata/secrets/borg_repo_passphrase.txt` -> Host-Secret-Datei, nicht im Repo | aktiv |
|
||||
| Healthchecks Dead-Man's-Switch (Borg Pre-Hook) | Ping-/Capability-URL | `/mnt/user/appdata/secrets/healthchecks_borg_url` (chmod 600) **oder** ENV `HEALTHCHECKS_BORG_URL`/`HEALTHCHECKS_URL`, gelesen von `ops/borg-ui/scripts/pre-borg.sh`; URL ist eine Capability-URL -> wie Secret behandeln, nie ins Repo | aktiv nach Operator-Setup |
|
||||
| Healthchecks Dead-Man's-Switch (Nearline-Pull) | Ping-/Capability-URL | baerchen: ENV `HEALTHCHECKS_NEARLINE_URL` **oder** `%USERPROFILE%\.kallilab\healthchecks-nearline-url.txt`, gelesen von `ops/h-drive-nearline/pull-critical-backups.ps1`; URL ist eine Capability-URL -> wie Secret behandeln, nie ins Repo | aktiv nach Operator-Setup |
|
||||
| Unraid Flash Backup | Boot-/Array-/Share-/Plugin-Konfiguration, ggf. Hashes/Keys/Templates | `/mnt/user/backups/borg/dumps/latest/unraid-flash-config.tar.gz`, via Borg/Hetzner gesichert | aktiv; wie Secret-Material behandeln |
|
||||
| Hermes Agent | Provider-Keys, Bot-Tokens, API-Server-Key | `/mnt/user/appdata/hermes-agent/data/.env` | VM-seitig offen |
|
||||
| Hermes Agent | SSH-Runner Private Key | `/mnt/user/appdata/secrets/hermes_runner_id_ed25519` -> `/root/.ssh/id_ed25519` | VM-seitig offen |
|
||||
@@ -117,6 +119,7 @@ Weitere dokumentierte Secret-Pfade:
|
||||
- Borg UI verwaltet Session-Secret, Admin-Login, SSH-Keys und Repo-Credentials in seiner persistenten `/data`-Struktur. Diese Daten liegen nicht im Git, muessen aber gesichert werden.
|
||||
- Die Borg-Repo-Passphrase liegt zusaetzlich als Host-Secret-Datei fuer Restore-Tests und Notfallzugriff vor. Der Wert ist laut Operator-Bestaetigung vom 2026-05-26 offline gesichert; Ablageort und Wert werden nicht im Repo dokumentiert.
|
||||
- Gitea verwaltet den GitHub-Push-Mirror-PAT in den Repository-Mirror-Settings. Der Wert wird nicht dokumentiert und nicht in Dateien unter `docs/` oder `core/gitea/` geschrieben.
|
||||
- Die beiden Healthchecks-Ping-URLs (Borg-Pre-Hook, Nearline-Pull) sind Capability-URLs und werden wie Secrets behandelt; sie liegen nicht im Repo. Die Skript-Integration ist endpoint-agnostisch (Healthchecks.io-Cloud oder self-hosted). Ist keine URL gesetzt, sind die Pings ein No-Op und brechen keinen Lauf ab. Operator-Setup-Schritte: `ops/h-drive-nearline/README.md` Abschnitt "Externer Dead-Man's-Switch".
|
||||
- `paperless-ngx` ist eine bewusste Ausnahme: DB-Passwort, Redis-URL und OIDC-Client-Secret bleiben aktuell als Komodo Stack Environment Variables hinterlegt, um den stabil laufenden Produktionsstand nicht fuer eine reine Secret-Mechanik-Migration zu riskieren.
|
||||
- `baerchen` nutzt fuer das Veeam-Backup aktuell den bestehenden SMB-User
|
||||
`micha`. Ein dedizierter SMB-User `veeam-baerchen` ist nur ein spaeteres
|
||||
|
||||
@@ -32,10 +32,13 @@ services:
|
||||
container_name: monitoring-alertmanager
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- --config.file=/etc/alertmanager/alertmanager.yml
|
||||
- --config.file=/etc/alertmanager/config/alertmanager.yml
|
||||
- --storage.path=/alertmanager
|
||||
volumes:
|
||||
- ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
|
||||
# Verzeichnis-Mount statt Einzeldatei (Begruendung siehe prometheus):
|
||||
# /mnt/user-FUSE bricht Einzeldatei-Bind-Mounts bei git/Komodo-Updates
|
||||
# zu "Stale NFS file handle" -> Directory-Inode ist stabil.
|
||||
- ./alertmanager:/etc/alertmanager/config:ro
|
||||
- alertmanager_data:/alertmanager
|
||||
networks:
|
||||
- monitoring_net
|
||||
@@ -57,7 +60,9 @@ services:
|
||||
- python
|
||||
- /app/bridge.py
|
||||
volumes:
|
||||
- ./alertmanager-ntfy-bridge/bridge.py:/app/bridge.py:ro
|
||||
# Verzeichnis-Mount statt Einzeldatei (Begruendung siehe prometheus):
|
||||
# vermeidet Stale-Handle auf dem /mnt/user-FUSE-Share bei git/Komodo-Updates.
|
||||
- ./alertmanager-ntfy-bridge:/app:ro
|
||||
networks:
|
||||
- monitoring_net
|
||||
expose:
|
||||
@@ -75,9 +80,11 @@ services:
|
||||
dns:
|
||||
- 172.23.0.3
|
||||
command:
|
||||
- --config.file=/etc/blackbox_exporter/blackbox.yml
|
||||
- --config.file=/etc/blackbox_exporter/config/blackbox.yml
|
||||
volumes:
|
||||
- ./blackbox/blackbox.yml:/etc/blackbox_exporter/blackbox.yml:ro
|
||||
# Verzeichnis-Mount statt Einzeldatei (Begruendung siehe prometheus):
|
||||
# vermeidet Stale-Handle auf dem /mnt/user-FUSE-Share bei git/Komodo-Updates.
|
||||
- ./blackbox:/etc/blackbox_exporter/config:ro
|
||||
networks:
|
||||
- monitoring_net
|
||||
- dns_net
|
||||
@@ -91,9 +98,11 @@ services:
|
||||
container_name: monitoring-loki
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- -config.file=/etc/loki/loki-config.yml
|
||||
- -config.file=/etc/loki/config/loki-config.yml
|
||||
volumes:
|
||||
- ./loki/loki-config.yml:/etc/loki/loki-config.yml:ro
|
||||
# Verzeichnis-Mount statt Einzeldatei (Begruendung siehe prometheus):
|
||||
# vermeidet Stale-Handle auf dem /mnt/user-FUSE-Share bei git/Komodo-Updates.
|
||||
- ./loki:/etc/loki/config:ro
|
||||
- loki_data:/loki
|
||||
networks:
|
||||
- monitoring_net
|
||||
@@ -107,9 +116,11 @@ services:
|
||||
container_name: monitoring-promtail
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- -config.file=/etc/promtail/promtail-config.yml
|
||||
- -config.file=/etc/promtail/config/promtail-config.yml
|
||||
volumes:
|
||||
- ./promtail/promtail-config.yml:/etc/promtail/promtail-config.yml:ro
|
||||
# Verzeichnis-Mount statt Einzeldatei (Begruendung siehe prometheus):
|
||||
# vermeidet Stale-Handle auf dem /mnt/user-FUSE-Share bei git/Komodo-Updates.
|
||||
- ./promtail:/etc/promtail/config:ro
|
||||
- promtail_positions:/positions
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- /var/lib/docker/containers:/var/lib/docker/containers:ro
|
||||
|
||||
@@ -22,12 +22,31 @@ case "${DUMP_ROOT:-}" in
|
||||
;;
|
||||
esac
|
||||
|
||||
# Externer Dead-Man's-Switch (endpoint-agnostisch: Healthchecks.io-Cloud oder
|
||||
# self-hosted). ntfy meldet nur Fehler eines tatsaechlich gestarteten Laufs;
|
||||
# der externe Switch faengt zusaetzlich den Fall ab, dass der Pre-Hook gar nicht
|
||||
# laeuft. Die Ping-URL ist eine Capability-URL -> wie ein Secret behandeln,
|
||||
# niemals ins Repo. Ist keine URL gesetzt, ist der Switch ein No-Op.
|
||||
HEALTHCHECKS_URL="${HEALTHCHECKS_URL:-${HEALTHCHECKS_BORG_URL:-}}"
|
||||
HEALTHCHECKS_URL_FILE="${HEALTHCHECKS_URL_FILE:-/mnt/user/appdata/secrets/healthchecks_borg_url}"
|
||||
if [ -z "$HEALTHCHECKS_URL" ] && [ -r "$HEALTHCHECKS_URL_FILE" ]; then
|
||||
HEALTHCHECKS_URL="$(tr -d '[:space:]' < "$HEALTHCHECKS_URL_FILE")"
|
||||
fi
|
||||
|
||||
hc_ping() {
|
||||
# $1: optionaler Suffix ("/start" | "/fail"); leer = Erfolg
|
||||
[ -n "$HEALTHCHECKS_URL" ] || return 0
|
||||
command -v curl >/dev/null 2>&1 || return 0
|
||||
curl -fsS -m 10 --retry 3 "${HEALTHCHECKS_URL}${1:-}" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
notify_failure() {
|
||||
local step="$1"
|
||||
local message="$2"
|
||||
if [ -x "$NTFY_SCRIPT" ]; then
|
||||
"$NTFY_SCRIPT" "$NTFY_TOPIC" "Borg pre-hook failed: $step" "$message" high || true
|
||||
fi
|
||||
hc_ping "/fail"
|
||||
}
|
||||
|
||||
run_step() {
|
||||
@@ -44,6 +63,8 @@ run_step() {
|
||||
fi
|
||||
}
|
||||
|
||||
hc_ping "/start"
|
||||
|
||||
echo "[pre-borg] Running posture-check"
|
||||
if "$POSTURE_CHECK"; then
|
||||
echo "[pre-borg] OK: posture-check"
|
||||
@@ -60,3 +81,4 @@ run_step "pre-backup-dumps" "$PRE_BACKUP_DUMPS"
|
||||
run_step "restore-freshness" env DUMP_ROOT="$FRESHNESS_DUMP_ROOT" "$FRESHNESS_CHECK"
|
||||
|
||||
echo "[pre-borg] All pre-flight checks passed"
|
||||
hc_ping
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# H:/ Nearline-Backup — Struktur und Betrieb
|
||||
|
||||
Stand: 2026-06-10
|
||||
Stand: 2026-06-21
|
||||
|
||||
## Rolle der H:/
|
||||
|
||||
@@ -45,6 +45,47 @@ Das Script schließt bewusst aus:
|
||||
- `unraid-flash-config.tar.gz` (0600 root:root, nicht per SMB zugänglich → Restore aus Hetzner-Borg)
|
||||
- Migration-/Cutover-Verzeichnisse (`immich-vectorchord-*`, `pg18-major-*`, `redis8-*` etc.)
|
||||
|
||||
## Externer Dead-Man's-Switch
|
||||
|
||||
Der Pull lief ~2026-06-04 bis 2026-06-18 still gestoppt, ohne dass etwas Alarm
|
||||
schlug (Scheduled Task fehlte; Prometheus auf Unraid sieht den baerchen-Pull
|
||||
nicht). Gegenmittel: ein externer Heartbeat. `pull-critical-backups.ps1` pingt am
|
||||
Lauf-Anfang `/start`, am Ende den Erfolg und im Fehlerfall `/fail`. Bleibt der
|
||||
Erfolgs-Ping aus, alarmiert der externe Dienst von aussen.
|
||||
|
||||
Die Integration ist **endpoint-agnostisch**: jede Healthchecks-kompatible URL
|
||||
funktioniert (Healthchecks.io-Cloud oder self-hosted). Ist keine URL gesetzt, ist
|
||||
der Switch ein No-Op und der Pull laeuft unveraendert weiter.
|
||||
|
||||
URL-Quelle (in dieser Reihenfolge):
|
||||
|
||||
1. Parameter `-HealthchecksUrl`
|
||||
2. ENV `HEALTHCHECKS_NEARLINE_URL`
|
||||
3. Datei `%USERPROFILE%\.kallilab\healthchecks-nearline-url.txt`
|
||||
|
||||
Die Ping-URL ist eine Capability-URL -> wie ein Secret behandeln, nie ins Repo
|
||||
(siehe `docs/SECRETS_MAP.md`).
|
||||
|
||||
### Operator-Setup (einmalig)
|
||||
|
||||
1. Check anlegen (Healthchecks.io oder self-hosted): Period = 1 Tag, Grace z. B.
|
||||
2-3 h passend zum taeglichen 05:30-Task. Ping-URL kopieren.
|
||||
2. Auf baerchen die URL hinterlegen, z. B. als Datei:
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force -Path "$HOME\.kallilab" | Out-Null
|
||||
Set-Content -LiteralPath "$HOME\.kallilab\healthchecks-nearline-url.txt" -Value "https://hc-ping.com/<uuid>" -NoNewline
|
||||
```
|
||||
(alternativ als persistente User-Umgebungsvariable `HEALTHCHECKS_NEARLINE_URL`.)
|
||||
3. Testlauf: `pull-critical-backups.ps1` ausfuehren; im Healthchecks-Dashboard
|
||||
muss ein "success"-Ping ankommen.
|
||||
|
||||
### Borg-Pre-Hook-Pendant
|
||||
|
||||
Den gleichen Switch gibt es host-seitig in `ops/borg-ui/scripts/pre-borg.sh`.
|
||||
URL dort via ENV `HEALTHCHECKS_BORG_URL` oder Datei
|
||||
`/mnt/user/appdata/secrets/healthchecks_borg_url` (chmod 600), bewusst als
|
||||
**eigener** Check (getrennter Heartbeat fuer die Unraid-Backup-Vorpruefung).
|
||||
|
||||
## _dr-kit
|
||||
|
||||
Enthält offline hinterlegte Schlüssel und Secrets für den DR-Fall:
|
||||
|
||||
@@ -1,11 +1,35 @@
|
||||
param(
|
||||
[string]$SourceRoot = "\\192.168.178.58\backups",
|
||||
[string]$DestinationRoot = "H:\kallilab-nearline-backups",
|
||||
[string]$HealthchecksUrl = $env:HEALTHCHECKS_NEARLINE_URL,
|
||||
[switch]$WhatIf
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Externer Dead-Man's-Switch (endpoint-agnostisch: Healthchecks.io-Cloud oder
|
||||
# self-hosted). Bleibt der Erfolgs-Ping aus, alarmiert der externe Dienst von
|
||||
# aussen - genau den Fall, den Prometheus auf Unraid fuer den baerchen-Pull
|
||||
# nicht sieht. Die Ping-URL ist eine Capability-URL -> wie ein Secret behandeln,
|
||||
# niemals ins Repo. Quelle: -HealthchecksUrl -> $env:HEALTHCHECKS_NEARLINE_URL
|
||||
# -> Datei im Userprofil. Ist keine URL gesetzt, ist der Switch ein No-Op.
|
||||
if (-not $HealthchecksUrl) {
|
||||
$hcUrlFile = Join-Path $HOME ".kallilab\healthchecks-nearline-url.txt"
|
||||
if (Test-Path -LiteralPath $hcUrlFile) {
|
||||
$HealthchecksUrl = (Get-Content -LiteralPath $hcUrlFile -Raw).Trim()
|
||||
}
|
||||
}
|
||||
|
||||
function Send-HealthcheckPing {
|
||||
param([string]$Suffix = "")
|
||||
if (-not $HealthchecksUrl) { return }
|
||||
try {
|
||||
Invoke-RestMethod -Uri ("{0}{1}" -f $HealthchecksUrl, $Suffix) -Method Get -TimeoutSec 15 | Out-Null
|
||||
} catch {
|
||||
Write-Warning "Healthchecks ping ('$Suffix') failed: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
$Jobs = @(
|
||||
@{
|
||||
Name = "borg-dumps-latest"
|
||||
@@ -145,6 +169,10 @@ if ($WhatIf) {
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Echter Lauf -> Dead-Man's-Switch aktiv. /start misst die Laufzeit, /fail
|
||||
# meldet einen abgebrochenen Lauf sofort, der Erfolgs-Ping am Ende bestaetigt.
|
||||
Send-HealthcheckPing "/start"
|
||||
try {
|
||||
$destinationDrive = Split-Path -Qualifier $DestinationRoot
|
||||
Assert-PathExists -Path $destinationDrive -Label "Destination drive"
|
||||
|
||||
@@ -186,3 +214,9 @@ $lines | Set-Content -LiteralPath $reportPath -Encoding UTF8
|
||||
|
||||
Write-Host "H:/ nearline pull completed."
|
||||
Write-Host "Report: $reportPath"
|
||||
|
||||
Send-HealthcheckPing
|
||||
} catch {
|
||||
Send-HealthcheckPing "/fail"
|
||||
throw
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user