# Proxmox Monitor — Konzept Eine Agent-Server-Anwendung zum Monitoring von Proxmox-Hosts mit Fokus auf ZFS-Gesundheit und VM-Übersicht. Implementiert in Elixir/OTP. ## Designprinzipien - **KISS**: Jede Entscheidung zugunsten der einfacheren Lösung, solange sie funktioniert. - **YAGNI**: Features werden erst gebaut, wenn sie konkret gebraucht werden — nicht prophylaktisch. - **Read-only**: Der Agent führt keine verändernden Commands auf dem Proxmox-Host aus. - **Push-Architektur**: Agents initiieren die Verbindung zum Server (NAT-freundlich). --- ## Architektur-Übersicht ``` ┌──────────────────┐ ┌────────────────────┐ │ Proxmox-Host 1 │ │ Server (LXC) │ │ ┌────────────┐ │ │ im Rechenzentrum │ │ │ Agent │──┼──WSS──┐ │ │ │ └────────────┘ │ │ │ ┌──────────────┐ │ └──────────────────┘ └──▶│ │ Caddy │ │ │ │ Reverse Proxy│ │ ┌──────────────────┐ │ └──────┬───────┘ │ │ Proxmox-Host 2 │ │ │ │ │ ┌────────────┐ │ │ ┌──────▼───────┐ │ │ │ Agent │──┼──WSS─────▶│ │ Phoenix │ │ │ └────────────┘ │ │ │ LiveView │ │ └──────────────────┘ │ └──────┬───────┘ │ ... │ │ │ │ ┌──────▼───────┐ │ ┌──────────────────┐ │ │ SQLite │ │ │ Proxmox-Host N │ │ └──────────────┘ │ │ ┌────────────┐ │ │ │ │ │ Agent │──┼──WSS─────▶│ │ │ └────────────┘ │ │ │ └──────────────────┘ └────────────────────┘ ``` ## Technologie-Stack | Komponente | Technologie | Begründung | |------------|-------------|------------| | Agent | Elixir + Burrito | Eigenständige Binary, keine Erlang-Installation auf Proxmox nötig | | Server | Phoenix + LiveView | Realtime-UI ohne separates Frontend | | Transport | Phoenix Channels (WSS) | Persistente Verbindung, Auto-Reconnect, Offline-Detection gratis | | Datenbank | SQLite + Ecto | Für ~20 Hosts vollkommen ausreichend, keine separate DB-Instanz | | TLS / Reverse Proxy | Caddy (bereits vorhanden) | Let's Encrypt automatisch | | Deployment (Server) | LXC-Container auf Proxmox im RZ | Geringer Overhead, saubere Isolation | | Deployment (Agent) | systemd-Service | Standard auf Debian/Proxmox | ## Systemanforderungen - **Proxmox-Hosts**: Proxmox VE 8.3+ mit OpenZFS 2.3+ (für `-j` JSON-Output) - **Server**: LXC oder VM mit ausreichend RAM (1 GB reicht), Debian/Ubuntu - **Netzwerk**: Server muss öffentlich über HTTPS erreichbar sein (via Caddy) --- ## Agent ### Verantwortlichkeiten Der Agent läuft auf jedem Proxmox-Host, sammelt in festen Intervallen Metriken und schickt sie an den Server. Er hält eine persistente WebSocket-Verbindung zum Server über Phoenix Channels. ### Sammlungs-Intervalle Nicht alles muss gleich häufig gesammelt werden. Daten mit hoher Änderungsrate werden öfter abgefragt, statische Informationen seltener. | Intervall | Daten | |-----------|-------| | 30 Sekunden | Host-Metriken, VM-Runtime-Status, ZFS-Pool-Status, Storage-Auslastung | | 5 Minuten | Snapshots, Dataset-Liste, VM-Config, Guest-Agent-IPs | | 30 Minuten | Proxmox-Version, pending APT-Updates, ZFS-Version | ### Zu sammelnde Daten **Host-Metriken** (aus `/proc` und `uptime`) - CPU-Auslastung (%), Load-Average (1/5/15) - RAM: used, total, available - Uptime in Sekunden - Root-Filesystem: used, total - Hostname, Kernel-Version **Proxmox-Storage** (`pvesh get /nodes//storage --output-format json`) - Alle konfigurierten Storages (ZFS, NFS, Local, etc.) - Typ, Status (active/inactive), used, total - Content-Typen (images, backup, iso, ...) **ZFS-Pools** (`zpool status -j --json-flat-vdevs --json-int`, `zpool list -j --json-int`) - Rohes JSON (vollständig gespeichert für spätere Analyse) - Zusätzlich extrahiertes Summary: - Pool-Name, Health-State - Size, Allocated, Free (Bytes) - Fragmentation (%), Capacity (%) - Error-Counter (read/write/checksum) - Scrub-Status, letzter erfolgreicher Scrub - Anzahl vdevs, Anzahl degraded vdevs **ZFS-Datasets & Snapshots** (`zfs list -j --json-int`) - Rohes JSON (Datasets und Snapshots) - Snapshot-Summary pro Dataset: - Anzahl Snapshots - Alter des ältesten / neuesten Snapshots - Gesamt-Speicher (usedbysnapshots) **Virtuelle Maschinen & LXC-Container** (`pvesh`) - Statisch (aus Config): VMID, Name, Type (qemu/lxc), Cores, RAM (max), Disks (mit Storage-Backend), Tags, Autostart - Dynamisch (aus Runtime-Status): Status, Uptime, CPU-Auslastung, RAM-Verbrauch, Disk-I/O, Netzwerk-I/O - Via Guest-Agent (QEMU, optional): IP-Adressen aller Interfaces, OS-Info, Disk-Usage im Guest - LXC: IPs direkt aus Container-Config **System-Info** - Proxmox-Version (`pveversion`) - Anzahl verfügbarer Updates (`apt list --upgradable 2>/dev/null | wc -l`) - ZFS-Version - Agent-Version (fest einkompiliert) ### Konfiguration Datei: `/etc/proxmox-monitor/agent.toml`, Zugriffsrechte `0600`, Eigentümer `root`. ```toml server_url = "wss://monitor.example.com/socket/websocket" token = "agent_abc123xyz..." host_id = "pve-host-01" # optional, sonst Hostname [intervals] fast_seconds = 30 medium_seconds = 300 slow_seconds = 1800 ``` ### Deployment ```bash # Einmaliges Setup auf einem Proxmox-Host scp proxmox-monitor-agent root@pve-host-01:/usr/local/bin/ scp agent.toml root@pve-host-01:/etc/proxmox-monitor/ scp proxmox-monitor-agent.service root@pve-host-01:/etc/systemd/system/ ssh root@pve-host-01 "systemctl enable --now proxmox-monitor-agent" ``` Der Agent muss als root laufen, da `zpool status` bei Degraded-Pools und einigen ZFS-Details Root-Rechte benötigt. Das ist akzeptabel, weil der Agent ausschließlich lesende Commands ausführt und keine eingehenden Verbindungen akzeptiert. --- ## Server ### Verantwortlichkeiten - Nimmt Verbindungen von Agents entgegen, authentifiziert sie - Speichert Metriken in SQLite - Stellt LiveView-Dashboard bereit - Cleanup alter Metriken (Retention) ### Datenmodell ```elixir # hosts - id (PK) - name (unique) - token_hash (bcrypt) - created_at - last_seen_at - agent_version (nullable) - proxmox_version (nullable) - zfs_version (nullable) - status ("online" | "offline" | "never_connected") # metrics (Zeitreihe, ein Eintrag pro Sample) - id (PK) - host_id (FK) - collected_at (indexed) - interval_type ("fast" | "medium" | "slow") - payload (JSON) # Gesamter Sample-Inhalt # Indexiert wegen Dashboard-Queries: # - (host_id, collected_at DESC) für "letzte N Samples eines Hosts" # - (collected_at) für Retention-Cleanup ``` **Begründung für JSON-Payload statt normalisierter Spalten:** Die Daten werden fast immer vollständig pro Host/Zeitpunkt gelesen (für die Detail-View). Separate Spalten pro Metrik würden die Schema-Evolution aufwendig machen. SQLite unterstützt JSON-Operatoren (`->>`) für Queries, falls wir mal einzelne Felder filtern müssen. ### Phoenix Channels — Protokoll **Join** (Agent → Server): ```json { "topic": "host:pve-host-01", "payload": { "token": "agent_abc123xyz...", "agent_version": "0.1.0" } } ``` Server validiert Token gegen `hosts.token_hash`. Bei Erfolg: Assign der `host_id` an den Socket, `last_seen_at` aktualisieren, Status auf `online` setzen. **Events** (Agent → Server): ```json // Event: "metric:fast" | "metric:medium" | "metric:slow" { "collected_at": "2026-04-21T12:34:56Z", "data": { /* je nach Intervall-Typ */ } } ``` **Disconnect-Handling** (Server): Wenn der Channel-Prozess stirbt (Terminate-Callback), wird der Host sofort als `offline` markiert. Kein Polling, keine Timeouts nötig. ### LiveView-Seiten **1. Übersicht (`/`)** - Eine Karte pro Host, Grid-Layout - Status-Ampel: Grün (alles ok) / Gelb (Warnung) / Rot (kritisch) / Grau (offline) - Kritisch = Pool DEGRADED/FAULTED, Capacity > 90%, Agent offline - Warnung = Capacity 80-90%, alte Snapshots, pending Updates, Scrub überfällig - Pro Karte sichtbar: Host-Name, CPU, RAM, Uptime, Pool-Status (Zusammenfassung), VM-Anzahl - Sortier-/Filter-Optionen: nach Status, nach Host-Name **2. Host-Detail (`/hosts/:name`)** - Header mit Proxmox-Version, Uptime, letzter Kontakt - Tabs oder Sektionen: - **Metriken**: CPU/RAM/Load-Graphen über 24h (simple Line-Charts mit Chart.js oder Contex) - **ZFS-Pools**: Pro Pool eine Box mit Health, Capacity-Bar, Fragmentation, Error-Counters, Scrub-Info, vdev-Liste - **Snapshots**: Tabelle pro Dataset mit Anzahl, Alter des ältesten/neuesten Snapshots - **Storage**: Tabelle aller Proxmox-Storages mit Auslastung - **VMs/LXCs**: Tabelle mit Name, Type, Status, CPU/RAM-Auslastung, IPs, Tags **3. VM-Suche (`/vms`)** - Globale Suche über alle Hosts - Input: Name oder IP - Ergebnis: Tabelle mit VM-Name, Host, Status, IP, Ressourcen - Killer-Feature bei 20 Hosts: "Wo läuft `nginx-proxy`?" **4. Host-Verwaltung (`/admin/hosts`)** - Liste aller Hosts mit Status, letzter Kontakt, Agent-Version - Button "Neuen Host hinzufügen": Formular mit Name → generiert Token, zeigt Install-Anleitung - Pro Host: "Token revoken" (setzt neuen Token), "Host löschen" ### Authentifizierung **Web-UI:** - Single-User, Login via Passwort (Argon2-Hash in Environment-Variable oder Secret-Config) - Minimal-Setup mit einer einzigen Session/Cookie - Kein User-Management-UI nötig **Agents:** - Shared Token pro Agent - Token-Generierung: 32 Bytes Random, Base64-URL-encoded - In DB als Bcrypt-Hash gespeichert - Revoke durch Token-Neugenerierung im Admin-UI ### Retention / Cleanup Ein GenServer läuft stündlich und löscht: - `metrics` älter als 48 Stunden Das reicht für "was ist in den letzten 2 Tagen passiert"-Analysen. Bei 20 Hosts × 30s-Samples × 48h = ca. 230.000 Rows. Völlig unproblematisch für SQLite. --- ## Projekt-Struktur Monorepo mit zwei getrennten Mix-Projekten: ``` proxmox_monitor/ ├── README.md ├── konzept.md # Dieses Dokument │ ├── agent/ # Mix-Projekt → Burrito-Binary │ ├── mix.exs │ ├── config/ │ │ ├── config.exs │ │ └── runtime.exs │ ├── lib/ │ │ ├── agent.ex │ │ ├── agent/ │ │ │ ├── application.ex │ │ │ ├── config.ex │ │ │ ├── reporter.ex # Phoenix Channel Client │ │ │ ├── supervisor.ex │ │ │ └── collectors/ │ │ │ ├── host.ex │ │ │ ├── storage.ex │ │ │ ├── zfs.ex │ │ │ ├── vms.ex │ │ │ └── system_info.ex │ │ └── agent/schema/ # Structs für Samples │ │ ├── sample.ex │ │ ├── pool_summary.ex │ │ └── ... │ ├── rel/ │ │ └── burrito.exs │ └── test/ │ └── server/ # Phoenix-Projekt ├── mix.exs ├── config/ ├── lib/ │ ├── server/ │ │ ├── application.ex │ │ ├── hosts.ex # Kontext: Host-Verwaltung │ │ ├── metrics.ex # Kontext: Metrik-Speicherung │ │ ├── retention.ex # GenServer: Cleanup │ │ └── schema/ │ │ ├── host.ex │ │ └── metric.ex │ └── server_web/ │ ├── router.ex │ ├── endpoint.ex │ ├── channels/ │ │ ├── agent_socket.ex │ │ └── host_channel.ex │ ├── live/ │ │ ├── overview_live.ex │ │ ├── host_detail_live.ex │ │ ├── vm_search_live.ex │ │ └── admin_hosts_live.ex │ └── controllers/ │ └── auth_controller.ex ├── priv/ │ └── repo/migrations/ └── test/ ``` --- ## Sicherheit - **TLS erzwungen** über Caddy, Agent verifiziert Server-Zertifikat - **Token niemals in Plaintext** in DB (Bcrypt) oder Logs - **Agent-Config mit Rechten `0600`, Root** - **Rate-Limiting** auf Channel-Join via Hammer-Library (z.B. 5 Versuche/Minute pro IP) - **Keine eingehenden Verbindungen zum Agent** — Agent initiiert immer - **Server-URL im Agent fest gepinnt** (keine Redirects) - **Read-only auf dem Proxmox-Host** — Agent führt keine verändernden Commands aus --- ## MVP-Scope — Was ist drin, was nicht ### ✅ Im MVP - Agent sammelt Host-, ZFS-, VM-, Storage-, System-Metriken - Persistente WebSocket-Verbindung via Phoenix Channels - SQLite-Speicherung mit 48h Retention - Dashboard: Übersicht, Host-Detail, VM-Suche, Admin - Single-User-Auth für Web-UI - Token-basierte Agent-Auth - Burrito-Binary für Agent - LXC-Deployment für Server ### ❌ Bewusst nicht im MVP (YAGNI) - **Alerts per E-Mail/Telegram/Gotify** — Dashboard zeigt farbig, reicht erstmal - **SMART-Werte der Disks** — eigenes Unterprojekt, später - **Backup-Task-Historie** — aufwendiges Parsing, später - **Cluster-Support** — nicht benötigt - **Agent-Self-Update** — manuell via scp reicht bei 20 Hosts - **Remote-Actions** (VM starten, Scrub triggern) — Read-only bleibt Read-only - **Multi-User, RBAC** — Single-User reicht - **Langzeit-Historie (>48h)** — separates Konzept (Downsampling) wenn gebraucht - **Mobile App** — Web-UI responsive reicht --- ## Roadmap ### Phase 1 — Grundgerüst (Woche 1-2) - Agent-Skeleton: Ein Collector (Host-Metriken), Output auf Console - Server-Skeleton: Phoenix-Projekt, Host-Schema, einfacher Channel, der Daten empfängt und loggt - Ende: Agent verbindet sich lokal zum Server und pusht CPU-Metriken ### Phase 2 — ZFS & VMs (Woche 3-4) - ZFS-Collector (pool status, list, snapshots) - VM-Collector (pvesh) - Storage-Collector - Server speichert in SQLite, simple Anzeige-Route ### Phase 3 — LiveView-Dashboard (Woche 5-6) - Übersichtsseite mit Status-Ampeln - Host-Detail mit allen Sektionen - VM-Suche - Auth für Web-UI ### Phase 4 — Admin & Deployment (Woche 7) - Admin-UI für Host-Verwaltung - Retention-GenServer - Burrito-Build für Agent - LXC-Deployment-Dokumentation ### Phase 5 — Produktiv-Rollout (Woche 8) - Deployment auf Server im RZ - Caddy-Konfiguration - Agent auf 2-3 Test-Hosts ausrollen - Nach erfolgreichem Test: Rollout auf alle 20 Hosts ### Nach dem MVP — ggf. als nächstes 1. **Alerts** (wenn klar ist, welche Thresholds sinnvoll sind) 2. **SMART-Monitoring** (für ZFS-Vorhersage) 3. **Backup-Task-Tracking** 4. **Langzeit-Historie** mit Downsampling --- ## Offene Entscheidungen / Vormerkungen Diese Punkte können beim Implementieren entschieden werden, sind aber fürs Konzept nicht kritisch: - **Chart-Library**: Chart.js (JS, via LiveView-Hook) vs. Contex (SVG, pure Elixir). Contex ist "eleganter", Chart.js bietet mehr Optionen out-of-the-box. - **Shared Structs zwischen Agent/Server**: Anfangs duplizieren. Falls das wehtut, später in eine geteilte Lib extrahieren. - **Exception-Handling bei pvesh/zpool-Fehlern**: Ausfall einer Datenquelle darf nicht das ganze Sample verwerfen. Teil-Samples mit Fehler-Flag akzeptieren. - **Logs**: Strukturiert (JSON) vs. Text. Vorschlag: Server strukturiert für spätere Auswertung, Agent textbasiert für einfaches journalctl-Debugging.