Files

185 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ProxLedger – Architektur- und Umsetzungsplan
Stand: 2026-09-17 · Version 0.1.0-dev
## 1. Überblick
```
Kundennetz A (NAT) Kundennetz B Betreiber
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────────────┐
│ PVE-Host │ │ PBS-Host │ │ ProxLedger (Docker / LXC) │
│ proxledger-agent│─HTTPS─▶│ proxledger-agent│─────▶│ ┌────────┐ ┌───────────┐ │
│ (systemd-Timer) │ Push │ (systemd-Timer) │ Push │ │ web │ │ worker │ │
└──────────────────┘ └──────────────────┘ │ │Next.js │ │ Cron-Jobs │ │
│ └───┬────┘ └─────┬─────┘ │
│ └──── Postgres ┘ │
└──────────────────────────────┘
│ SMTP (M365) / Webhook
▼
E-Mail, Matrix, Zabbix, HA
```
- **Push-Modell:** Das Dashboard fragt nie aktiv bei Hosts an. Agents melden sich täglich per HTTPS.
- **web:** Next.js (App Router) liefert UI, Agent-API (`/api/agent/v1/*`) und `install.sh`/`proxledger-agent.sh`.
- **worker:** Derselbe Code-Stand, eigener Prozess (`node dist/worker.js`) für zeitgesteuerte Aufgaben:
Warnregeln auswerten, „keine Meldung seit 48 h“, Benachrichtigungen versenden, Zusammenfassungen.
- **PostgreSQL:** einzige Datenhaltung; PDF-Uploads auf einem Volume (`/data/uploads`), Metadaten in der DB.
## 2. Tech-Stack
| Bereich | Wahl | Begründung |
|---|---|---|
| Framework | Next.js (App Router), TypeScript strict | Vorgabe |
| ORM | Prisma + PostgreSQL | Vorgabe, Migrationen versioniert |
| UI | `admin-lte` 4.x **nur CSS** + Bootstrap 5.3 + eigene React-Komponenten | AdminLTE 4.9.1 ist stabil, jQuery-frei; die Vanilla-JS-Plugins (PushMenu, Treeview, CardWidget) manipulieren das DOM direkt und werden als React-Komponenten nachgebaut |
| Icons | Bootstrap Icons | AdminLTE-4-Standard |
| Schrift | Source Sans 3 über `next/font` (selbst gehostet) | kein externer Request zur Laufzeit |
| Validierung | zod (API und Server Actions) | Vorgabe |
| Auth | Auth.js (OIDC authentik + Credentials-Fallback) | ausgereift, App-Router-kompatibel |
| Passwörter/Tokens | argon2id (Passwörter), SHA-256 über 256-Bit-Zufallstoken (API-Tokens) | Token haben volle Entropie, SHA-256 reicht und ist schnell nachschlagbar |
| Scheduler | `croner` im Worker | leichtgewichtig, zeitzonenfähig |
| E-Mail | nodemailer (SMTP, STARTTLS 587, M365-tauglich) | |
| PDF | `@react-pdf/renderer` | kein Chromium im Container |
| Tests | Vitest; Integrationstests gegen Postgres (Docker) | |
| Agent | Bash, `curl`, Bordmittel; systemd-Service + Timer | Vorgabe |
Zeitzone der Anzeige: `Europe/Berlin`, Datenhaltung in UTC. Datumsformat `dd.MM.yyyy`, Währung EUR.
## 3. Datenmodell
Gegenüber dem Vorschlag im Auftrag zwei bewusste Änderungen:
1. **Host-Identität ist nicht die Server-ID.** Die Server-ID ändert sich bei Hardwaretausch/Neuinstallation – genau das soll
eine Warnung auslösen. Ein Host wird daher über seinen Agent-Token bzw. eine beim Enrollment erzeugte `agentId`
(abgeleitet aus `/etc/machine-id`) identifiziert; die Server-ID ist ein Attribut mit Historie. Meldet ein neu
registrierter Agent eine Server-ID, die bereits ein bestehender Host hatte, wird das als Neuinstallation erkannt und
zur Übernahme vorgeschlagen.
2. **Rechnungen sind eigene Entitäten mit Positionen.** Eine Proxmox-Bestellung enthält oft mehrere Keys, eine
Kundenrechnung oft mehrere Hosts. Jede Laufzeit verweist auf höchstens eine Einkaufs- und eine Verkaufsposition.
Marge = Verkauf netto − Einkauf netto der Position(en).
### Entitäten (vereinfacht)
```
Customer 1─n Site
Customer 1─n Host (customerId nullable → „nicht zugeordnet“)
Site 1─n Host
Host 1─n Report
Host 1─n SubscriptionKey (Key-Historie: key, firstSeen, lastSeen)
Host 1─n ServerIdHistory
Host 1─n SubscriptionPeriod (Laufzeit: startDate, endDate, key, status: vorgeschlagen|bestätigt)
SubscriptionPeriod n─1 PurchaseInvoiceItem ─n─1 PurchaseInvoice (Proxmox)
SubscriptionPeriod n─1 SalesInvoiceItem ─n─1 SalesInvoice (Kunde)
Host 1─n AgentToken (Hash, erstellt, zuletzt benutzt, widerrufen)
EnrollmentToken (Hash, gültig bis, max. Nutzungen, optional customerId/siteId)
Alert (type, severity, hostId?, customerId?, periodId?, fingerprint, openedAt, resolvedAt, acknowledgedAt)
Notification (alertId?, channel, sentAt, status) – Deduplizierung über Alert-Fingerprint
AlertRuleSetting (key, enabled, thresholds JSON)
NotificationChannel (type: email|webhook, config JSON, enabled)
User (email, name, role: admin|readonly, passwordHash?, oidcSubject?)
AuditLog (userId?, action, entity, entityId, diff JSON, createdAt)
Tag n─m Host
Attachment (PDF: path, mime, size, sha256) – an PurchaseInvoice / SalesInvoice
```
**PurchaseInvoice (Proxmox):** Rechnungsnummer, Rechnungsdatum, Fälligkeitsdatum, netto, brutto, Rabatt, Zahlungsart,
bezahlt (bool), Bezahldatum, PDF.
**SalesInvoice (Kunde):** erstellt (bool), Rechnungsnummer, Rechnungsdatum, Betrag netto/brutto, vom Kunden bezahlt (bool),
Bezahldatum, PDF.
USt-Satz standardmäßig 19 %, pro Rechnung überschreibbar. Beträge als `Decimal(12,2)`.
Das „Häkchen direkt in der Tabelle“ setzt den Status der verknüpften Rechnung. Existiert noch keine Rechnung, legt das
Häkchen eine Minimal-Rechnung an (Nummer später nachtragbar).
## 4. Agent-API
Basis: `/api/agent/v1`, nur JSON, strikt getrennt vom UI (eigene Route-Group, keine Session-Cookies, eigenes Rate-Limit).
| Methode | Pfad | Auth | Zweck |
|---|---|---|---|
| POST | `/enroll` | Enrollment-Token (Bearer) | Host registrieren → liefert `hostId` + host-spezifischen API-Token (einmalig im Klartext) |
| POST | `/report` | Host-Token (Bearer) | Statusbericht; Antwort enthält `latestAgentVersion`, `serverTime` |
| GET | `/ping` | Host-Token | Verbindungstest (`proxledger-agent --test`) |
| GET | `/install.sh`, `/agent.sh` | öffentlich | Installer und Agent-Skript (Version eingebettet) |
- zod-Schemas mit `nullable()` für alle optionalen Felder; unbekannte Felder werden ignoriert und geloggt.
- Rate-Limit: Token-Bucket im Prozess (pro IP und pro Token), z. B. 10 Anfragen/Minute; `429` mit `Retry-After`.
- Report-Verarbeitung in einer Transaktion: Report speichern → Host-Stammdaten aktualisieren → Key-/Server-ID-Historie →
Laufzeitvorschlag bei neuem `nextduedate` → betroffene Warnregeln neu auswerten.
- Nachgesendete (gepufferte) Reports tragen ihren Original-Zeitstempel (`collectedAt`) und werden idempotent über `reportId` (UUID vom Agent) angenommen.
## 5. Agent
- `install.sh`: prüft root/Produkt, lädt `proxledger-agent.sh` vom Dashboard, `enroll`, schreibt `/etc/proxledger/agent.conf` (600),
installiert `proxledger.service` + `proxledger.timer` (`OnCalendar=daily`, `RandomizedDelaySec=4h`, `Persistent=true`), erster Report sofort.
- Optionen: `--token`, `--url`, `--ca-cert`, `--uninstall`, `--update`, `--test`.
- Sammelt: Subscription (PVE/PBS/PMG), Hostname, FQDN, IPs, Cluster/Node, Produktversion, Kernel, Sockets, CPU-Modell, Uptime,
Anzahl Updates, Agent-Version, Zeitstempel. Fehlende Werte → `null`. JSON wird ohne `jq` erzeugt (eigene Escape-Funktion).
- Fehlschlag: 3 Versuche mit Backoff (30 s, 2 min, 8 min), danach Ablage in `/var/lib/proxledger/spool/`, beim nächsten Lauf nachsenden (max. 30 Dateien).
- Logging über `logger -t proxledger` bzw. stdout ins Journal.
- Update: nur wenn `AUTO_UPDATE=yes` in der Config oder manuell via `--update`; Prüfsumme vom Dashboard wird verifiziert.
## 6. Warnregeln
Regeln sind reine Funktionen `(Kontext) → AlertCandidate[]`, getrennt testbar. Der Worker gleicht Kandidaten mit offenen Alerts
ab (Fingerprint = Regel + Objekt-ID [+ Schwelle]): neue öffnen, verschwundene schließen, bestehende unverändert lassen.
Benachrichtigt wird nur beim Öffnen bzw. beim Wechsel auf eine höhere Stufe (30 → 14 → 7 Tage).
| Regel | Standard |
|---|---|
| Subscription läuft ab | 30 / 14 / 7 Tage |
| Status ≠ active | sofort |
| Sockets lizenziert ≠ physisch | sofort |
| Keine Meldung | 48 h |
| Server-ID geändert | sofort |
| Key von mehreren Hosts gemeldet | sofort |
| Host ohne Kunde | sofort (nur Info) |
| Proxmox-Rechnung unbezahlt, Fälligkeit | ≤ 7 Tage |
| Verlängert/bezahlt, Kundenrechnung fehlt | sofort |
| Kundenrechnung unbezahlt | nach 30 Tagen |
## 7. Sicherheit
- UI-Routen per Middleware geschützt (ab #013). **Bis dahin nicht öffentlich betreiben.**
- Tokens: 32 Byte Zufall, Präfix `plx_e_` (Enrollment) / `plx_h_` (Host), in der DB nur SHA-256-Hash; einzeln widerrufbar.
- Audit-Log für alle schreibenden Server Actions (vor #013 mit Akteur „system“).
- Security-Header (CSP, HSTS hinter Reverse Proxy), Upload-Prüfung (nur PDF, max. 10 MB).
## 8. Projektstruktur
```
proxledger/
├─ agent/ Bash-Agent, Installer, systemd-Units
├─ deploy/ docker-compose, LXC-Installer, Beispiel-Proxy-Configs
├─ docs/ Architektur, Issues
├─ prisma/ schema.prisma, migrations, seed.ts
├─ public/ favicon.svg, Icons
├─ src/
│ ├─ app/
│ │ ├─ (dashboard)/ UI-Seiten (AdminLTE-Layout)
│ │ ├─ (auth)/login/
│ │ └─ api/agent/v1/ Agent-API
│ ├─ components/ Layout, Logo, SmallBox, Card, Badge, DataTable …
│ ├─ lib/ db, auth, tokens, validation, format
│ ├─ server/ Services (reports, alerts, invoices, notifications)
│ ├─ worker/ Scheduler-Einstiegspunkt
│ └─ mock/ Beispieldaten für den Prototyp
└─ tests/
```
## 9. Meilensteine
| Version | Inhalt | Issues |
|---|---|---|
| 0.1.0 | Projekt-Setup, Prototyp, Datenmodell, Agent-API | #001–#004 |
| 0.2.0 | Agent-Skript | #005 |
| 0.3.0 | Kunden, Standorte, Hostzuweisung, Enrollment-Tokens | #006 |
| 0.4.0 | Host-Tabelle, Suche, Detailansichten | #007 |
| 0.5.0 | Laufzeiten und Rechnungen | #008 |
| 0.6.0 | Kundenübersicht, Dashboard live, Jahresbahn, Export | #009, #010 |
| 0.7.0 | Warnregeln | #011 |
| 0.8.0 | Benachrichtigungen | #012 |
| 0.9.0 | Authentifizierung, Audit-Log | #013 |
| 0.10.0 | Deployment (Docker, LXC), Doku | #014 |
| 1.0.0 | Feinschliff, Dark-Mode, Mobil, Härtung | #015 |