# Backend — Installation & Konfiguration

Das RMM-Backend laeuft auf Linux (Debian 12+) und stellt die REST API, den WebSocket Hub und den Tunnel Manager bereit.

## Komponenten

- **REST API**: Agent-Registrierung, Heartbeat, Systemdaten, Tunnel-Management
- **WebSocket Hub**: Verwaltet Agent-Verbindungen, routet Commands und Binary-Messages
- **Tunnel Manager**: Proxy-Listener, Session-Tracking, Ready-Handshake
- **PostgreSQL + TimescaleDB**: Relationale Daten + Time-Series Metriken

## 1. PostgreSQL + TimescaleDB installieren

```bash
# Debian 12+ (Bookworm/Trixie)
apt install -y postgresql postgresql-client

# TimescaleDB Repository hinzufuegen
echo "deb https://packagecloud.io/timescale/timescaledb/debian/ bookworm main" \
  > /etc/apt/sources.list.d/timescaledb.list
curl -L https://packagecloud.io/timescale/timescaledb/gpgkey | gpg --dearmor \
  > /etc/apt/trusted.gpg.d/timescaledb.gpg
apt update

# TimescaleDB passend zur PG-Version installieren (z.B. PG 17)
apt install -y timescaledb-2-postgresql-17

# TimescaleDB in postgresql.conf aktivieren
echo "shared_preload_libraries = 'timescaledb'" >> /etc/postgresql/17/main/conf.d/timescaledb.conf
systemctl restart postgresql
```

## 2. Datenbank und User anlegen

```bash
sudo -u postgres psql <<EOF
CREATE USER rmm WITH PASSWORD 'SICHERES_PASSWORT';
CREATE DATABASE rmm OWNER rmm;
\c rmm
CREATE EXTENSION IF NOT EXISTS timescaledb;
GRANT ALL ON SCHEMA public TO rmm;
EOF
```

Verbindung testen:

```bash
psql -h 127.0.0.1 -U rmm -d rmm -c "SELECT default_version FROM pg_available_extensions WHERE name='timescaledb';"
```

## 3. Build

```bash
make all       # Backend (linux/amd64) + Agent (freebsd/amd64)
make backend   # Nur Backend
make agent     # Nur Agent
```

Binaries landen in `build/`. Kein CGO — reine Go-Compilation.

## 4. TLS-Zertifikate generieren

```bash
make certs
```

Oder manuell:

```bash
mkdir -p certs
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 \
  -keyout certs/server.key -out certs/server.crt \
  -days 3650 -nodes \
  -subj "/CN=rmm-backend/O=RMM" \
  -addext "subjectAltName=IP:BACKEND_IP,IP:127.0.0.1,DNS:localhost"
```

## 5. Backend konfigurieren (`config.yaml`)

```yaml
listen_addr: ":8443"
tls_cert: "certs/server.crt"
tls_key: "certs/server.key"
api_keys:
  - "SICHERER_API_KEY"       # beliebig lang, wird in X-API-Key Header mitgegeben
jwt_secret: "LANGER_ZUFAELLIGER_STRING"  # fuer Frontend-Login (JWT-Signierung)

database:
  host: "127.0.0.1"
  port: 5432
  user: "rmm"
  password: "SICHERES_PASSWORT"
  dbname: "rmm"
  sslmode: "disable"        # "require" wenn PG ueber Netz
```

Zufälligen JWT-Secret generieren:

```bash
openssl rand -hex 32
```

Alternativ per Environment-Variablen: `RMM_LISTEN_ADDR`, `RMM_TLS_CERT`, `RMM_TLS_KEY`, `RMM_DB_HOST`, `RMM_DB_PASSWORD`.

## 6. Backend deployen und starten

```bash
# Verzeichnis anlegen
ssh root@BACKEND_IP "mkdir -p /opt/rmm/certs"

# Binary + Config + Certs kopieren
scp build/rmm-backend root@BACKEND_IP:/opt/rmm/rmm-backend
scp config.yaml root@BACKEND_IP:/opt/rmm/config.yaml
scp certs/server.* root@BACKEND_IP:/opt/rmm/certs/

# Systemd-Service einrichten (Vorlage liegt unter backend/rmm-backend.service)
scp backend/rmm-backend.service root@BACKEND_IP:/etc/systemd/system/rmm-backend.service
# WorkingDirectory und ExecStart ggf. an eigenen Pfad anpassen

ssh root@BACKEND_IP "systemctl daemon-reload && systemctl enable rmm-backend && systemctl start rmm-backend"
```

Die mitgelieferte Service-Datei (`backend/rmm-backend.service`) verwendet `/home/cynfo/rmm` als Arbeitsverzeichnis — bei abweichendem Pfad anpassen.

### Dienste auf dem Backend-Server

| Dienst | Zweck | Management |
|--------|-------|-----------|
| `postgresql` | Datenbank (PostgreSQL + TimescaleDB) | `systemctl start/stop/restart postgresql` |
| `rmm-backend` | REST API, WebSocket Hub, Tunnel Manager | `systemctl start/stop/restart rmm-backend` |

## 7. Starten und verifizieren

```bash
# Status pruefen
systemctl status rmm-backend

# Log pruefen
journalctl -u rmm-backend -f

# API testen
curl -sk -H "X-API-Key: DEIN_API_KEY" https://BACKEND_IP:8443/api/v1/agents
```

Beim ersten Start:
- Tabellen werden automatisch angelegt
- Hypertable `metrics` wird erstellt (TimescaleDB)
- Retention Policy (90 Tage) und Compression Policy (nach 7 Tagen) werden automatisch gesetzt
- Default-Admin `admin` / `YOUR_PASSWORD` wird erstellt

## Datenbank-Schema (vollständig)

Alle Tabellen werden beim Backend-Start **automatisch angelegt** (Auto-Migration inkl. ALTER TABLE für neue Spalten). Kein manuelles Schema-Setup nötig.

---

### `agents` — Agent-Registry

```sql
CREATE TABLE agents (
    id                TEXT        PRIMARY KEY,            -- UUID des Agents (persistent)
    name              TEXT        NOT NULL,               -- Anzeigename (z.B. "OPN.intra.kunde.de")
    hostname          TEXT        NOT NULL,               -- Hostname der Firewall
    ip                TEXT        NOT NULL,               -- IP-Adresse des Agents
    opnsense_version  TEXT        NOT NULL DEFAULT '',    -- OPNsense-Version (z.B. "25.1.1")
    agent_version     TEXT        NOT NULL DEFAULT '',    -- Agent-Binary-Version (z.B. "1.0.3")
    platform          TEXT        NOT NULL DEFAULT 'freebsd', -- Plattform: "freebsd", "linux"
    registered_at     TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- Erstregistrierung
    last_heartbeat    TIMESTAMPTZ,                        -- Letzter Heartbeat
    customer_id       INTEGER     REFERENCES customers(id), -- Kundenzuordnung (nullable)
    update_requested  BOOLEAN     NOT NULL DEFAULT FALSE  -- Update-Flag fuer Updater-Daemon
);
CREATE INDEX idx_agents_customer ON agents(customer_id);
```

**Status-Berechnung** (kein DB-Feld, dynamisch im Backend):

| Status | Bedingung |
|--------|-----------|
| `online` | `last_heartbeat` < 2 Minuten |
| `stale` | `last_heartbeat` < 5 Minuten |
| `offline` | `last_heartbeat` > 5 Minuten |
| `unknown` | `last_heartbeat` ist NULL |

---

### `customers` — Kundenstammdaten

```sql
CREATE TABLE customers (
    id         SERIAL      PRIMARY KEY,
    number     TEXT        NOT NULL UNIQUE, -- RMM-Kundennummer (z.B. "K05068")
    name       TEXT        NOT NULL,        -- Kundenname
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```

---

### `users` — Benutzer (Frontend-Login)

```sql
CREATE TABLE users (
    id            SERIAL      PRIMARY KEY,
    username      TEXT        NOT NULL UNIQUE,
    password_hash TEXT        NOT NULL,             -- bcrypt-Hash
    display_name  TEXT        NOT NULL DEFAULT '',
    role          TEXT        NOT NULL DEFAULT 'admin', -- aktuell nur "admin"
    created_at    TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    last_login    TIMESTAMPTZ                       -- nullable
);
```

Standard-Admin wird beim ersten Start angelegt: `admin` / `Start!123` (sofort ändern).

---

### `system_data` — Aktueller Systemzustand pro Agent

```sql
CREATE TABLE system_data (
    agent_id   TEXT        PRIMARY KEY REFERENCES agents(id) ON DELETE CASCADE,
    data_json  JSONB       NOT NULL,   -- Vollständiger Heartbeat-Payload des Agents
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```

---

### `config_backups` — Versionierte OPNsense-Configs

```sql
CREATE TABLE config_backups (
    id         SERIAL      PRIMARY KEY,
    agent_id   TEXT        NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
    hash       TEXT        NOT NULL,   -- SHA256 der config.xml (Deduplizierung)
    size       INTEGER     NOT NULL,   -- Grösse in Bytes
    config_xml TEXT        NOT NULL,   -- Vollständige config.xml
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_config_backups_agent ON config_backups(agent_id, created_at DESC);
CREATE INDEX idx_config_backups_hash  ON config_backups(agent_id, hash);
```

Deduplizierung: gleicher SHA256 → kein neuer Eintrag.

---

### `agent_events` — Online/Offline-Ereignisse

```sql
CREATE TABLE agent_events (
    id         SERIAL      PRIMARY KEY,
    agent_id   TEXT        NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
    event_type TEXT        NOT NULL,   -- "online", "offline", "stale", "connected", "disconnected"
    message    TEXT,                   -- optionale Zusatzinfo
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_agent_events_agent ON agent_events(agent_id, created_at DESC);
```

---

### `agent_firmware` — Agent-Binaries pro Plattform

```sql
CREATE TABLE agent_firmware (
    version     TEXT        NOT NULL,  -- z.B. "1.0.3"
    platform    TEXT        NOT NULL DEFAULT 'freebsd', -- "freebsd", "linux", "windows"
    hash        TEXT        NOT NULL,  -- SHA256 des Binaries
    binary_data BYTEA       NOT NULL,  -- Das Binary selbst
    uploaded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    UNIQUE (version, platform)
);
```

---

### `tasks` — Task-Scheduler (geplante Aufgaben)

```sql
CREATE TABLE tasks (
    id                    SERIAL      PRIMARY KEY,
    agent_id              TEXT        NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
    name                  TEXT        DEFAULT '',           -- Anzeigename der Aufgabe
    action                TEXT        NOT NULL,             -- "update", "backup", "reboot", ...
    status                TEXT        NOT NULL DEFAULT 'scheduled', -- "scheduled","running","completed","failed"
    schedule_type         TEXT        DEFAULT 'once',       -- "once", "daily", "weekly", "monthly"
    schedule_time         TEXT        DEFAULT '02:00',      -- Uhrzeit (HH:MM)
    schedule_weekday      INT,                              -- 0=So, 1=Mo, ... 6=Sa (nur bei "weekly")
    schedule_monthday     INT,                              -- 1-31 (nur bei "monthly")
    reboot                BOOLEAN     DEFAULT FALSE,        -- Nach Update neu starten
    health_check          BOOLEAN     DEFAULT FALSE,        -- Nach Aufgabe Erreichbarkeit prüfen
    health_check_timeout  INT         DEFAULT 600,          -- Sekunden bis Timeout des Health-Checks
    active                BOOLEAN     DEFAULT TRUE,         -- Recurring: aktiv/inaktiv
    scheduled_at          TIMESTAMPTZ NOT NULL,             -- Nächste/erste Ausführung (once)
    started_at            TIMESTAMPTZ,                      -- Tatsächlicher Start
    finished_at           TIMESTAMPTZ,                      -- Ende der Ausführung
    last_run              TIMESTAMPTZ,                      -- Letzter Lauf (recurring)
    next_run              TIMESTAMPTZ,                      -- Nächster Lauf (recurring)
    run_count             INT         DEFAULT 0,            -- Anzahl Ausführungen (recurring)
    result                JSONB,                            -- Ergebnis-Payload
    webhook_url           TEXT,                             -- URL für Webhook nach Abschluss
    webhook_sent          BOOLEAN     DEFAULT FALSE,
    webhook_response      TEXT,                             -- HTTP-Response des Webhooks
    created_by            TEXT,                             -- Username des Erstellers
    created_at            TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_tasks_status_scheduled  ON tasks(status, scheduled_at);
CREATE INDEX idx_tasks_active_next_run   ON tasks(active, next_run) WHERE active = TRUE;
```

**Webhook-Payload** (gesendetes JSON bei `webhook_url` gesetzt):

```json
{
  "customer_id":      "K05068",
  "hostname":         "opnsense.kunde.de",
  "opnsense_version": "25.1.1",
  "timestamp":        "2026-03-06T08:00:00Z",
  "trigger":          "update_completed",
  "reachable":        true,
  "update_status":    "completed"
}
```

---

### `api_keys` — API-Keys (zusätzlich zu config.yaml)

```sql
CREATE TABLE api_keys (
    id         SERIAL      PRIMARY KEY,
    name       TEXT        NOT NULL,
    key        TEXT        NOT NULL UNIQUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    last_used  TIMESTAMPTZ
);
```

---

### `installers` — OPNsense Plugin-Pakete

```sql
CREATE TABLE installers (
    id          SERIAL      PRIMARY KEY,
    platform    TEXT        NOT NULL UNIQUE, -- "freebsd", "linux"
    filename    TEXT        NOT NULL,
    data        BYTEA       NOT NULL,        -- ZIP-Paket (install.sh + Plugin)
    sha256      TEXT        NOT NULL,
    size        INTEGER     NOT NULL,
    uploaded_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```

---

### `audit_log` — Audit-Protokoll

```sql
CREATE TABLE audit_log (
    id          SERIAL      PRIMARY KEY,
    timestamp   TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    user_name   TEXT        NOT NULL,
    action      TEXT        NOT NULL,  -- z.B. "login", "delete_agent", "update_customer"
    target_type TEXT,                  -- z.B. "agent", "customer", "user"
    target_id   TEXT,
    target_name TEXT,
    details     TEXT,
    source_ip   TEXT
);
CREATE INDEX idx_audit_log_timestamp ON audit_log(timestamp DESC);
```

---

### `system_settings` — Backend-Einstellungen (Key/Value)

```sql
CREATE TABLE system_settings (
    key        TEXT PRIMARY KEY,  -- "backend_url_internal", "backend_url_external", "api_key"
    value      TEXT NOT NULL,
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```

---

### `metrics` — TimescaleDB Hypertable (Zeitreihen)

```sql
CREATE TABLE metrics (
    time     TIMESTAMPTZ      NOT NULL,
    agent_id TEXT             NOT NULL,
    metric   TEXT             NOT NULL,         -- Name der Metrik
    value    DOUBLE PRECISION NOT NULL,
    tags     JSONB                              -- z.B. {"interface":"igb0"}
);
-- In Hypertable umwandeln:
SELECT create_hypertable('metrics', 'time', if_not_exists => TRUE);
CREATE INDEX idx_metrics_agent_metric ON metrics(agent_id, metric, time DESC);
```

**Verfügbare Metriken:**

| Metrik | Einheit | Tags |
|--------|---------|------|
| `cpu_usage` | Prozent | — |
| `memory_used_percent` | Prozent | — |
| `memory_used_bytes` | Bytes | — |
| `memory_total_bytes` | Bytes | — |
| `uptime_seconds` | Sekunden | — |
| `disk_used_percent` | Prozent | `{"mountpoint":"/","dataset":"zroot/ROOT"}` |
| `disk_used_bytes` | Bytes | `{"mountpoint":"/"}` |
| `interface_rx_bytes` | Bytes | `{"interface":"igb0"}` |
| `interface_tx_bytes` | Bytes | `{"interface":"igb0"}` |
| `gateway_rtt_ms` | Millisekunden | `{"gateway":"WAN_GW"}` |
| `gateway_loss_percent` | Prozent | `{"gateway":"WAN_GW"}` |

**TimescaleDB Policies** (automatisch gesetzt):
- **Retention**: Rohdaten nach 90 Tagen gelöscht
- **Compression**: Daten älter als 7 Tage komprimiert (`segmentby: agent_id, metric`)

---

### DB-Funktionen

- **`GetAgentByName`**: Sucht Agent nach Name — für Pre-Registration Matching (Agent registriert sich, Backend findet vorregistrierten Platzhalter mit `hostname="(ausstehend)"`).

---

## API Endpoints

Base-URL: `https://your-backend:8443/api/v1`

### Authentifizierung

Zwei Auth-Methoden (CombinedAuth Middleware):
- **API-Key** (Header `X-API-Key`): Fuer Agents und Automatisierung
- **JWT Bearer** (Header `Authorization: Bearer <token>`): Fuer Frontend

```
POST   /auth/login     Login → JWT Token (8h Ablauf)
GET    /auth/me         Eigene Benutzerdaten
```

```bash
# Login
curl -sk -X POST https://your-backend:8443/api/v1/auth/login \
  -d '{"username":"admin","password":"YOUR_PASSWORD"}'
# → {"token":"eyJhbG...","user":{"id":1,"username":"admin"}}
```

### Agents

```
GET    /agents                    Alle Agents auflisten
GET    /agents/{id}               Agent + Systemdaten
GET    /agents/{id}/system        Nur Systemdaten (JSONB)
DELETE /agents/{id}               Agent loeschen (inkl. Daten, Events, Backups, Metriken)
POST   /agents                    Firewall pre-registrieren
PUT    /agents/{id}/customer      Kunden zuordnen
DELETE /agents/{id}/customer      Kundenzuordnung entfernen
POST   /agent/register            Agent-Registrierung (vom Agent selbst)
POST   /agent/heartbeat           Heartbeat + Systemdaten (vom Agent)
```

```bash
# Alle Agents
curl -sk -H "X-API-Key: YOUR_API_KEY" \
  https://your-backend:8443/api/v1/agents

# Agent mit Systemdaten
curl -sk -H "X-API-Key: YOUR_API_KEY" \
  https://your-backend:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee

# Nur Systemdaten
curl -sk -H "X-API-Key: YOUR_API_KEY" \
  https://your-backend:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/system

# Pre-Registrierung
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \
  https://your-backend:8443/api/v1/agents \
  -d '{"name":"OPN.intra.kunde.de","customer_id":1}'

# Agent loeschen
curl -sk -H "X-API-Key: YOUR_API_KEY" -X DELETE \
  https://your-backend:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887

# Kunden zuordnen
curl -sk -H "X-API-Key: YOUR_API_KEY" -X PUT \
  https://your-backend:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/customer \
  -d '{"customer_id":1}'
```

Agent-Status (automatisch berechnet):

| Status | Bedingung |
|--------|-----------|
| `online` | Heartbeat < 2 Minuten |
| `stale` | Heartbeat < 5 Minuten |
| `offline` | Heartbeat > 5 Minuten |
| `unknown` | Kein Heartbeat |

### Agent-Events

```
GET    /agents/events             Alle Events (?limit=N&type=X)
GET    /agents/{id}/events        Events eines Agents
```

Event-Typen: `online`, `offline`, `stale`, `connected`, `disconnected`

```bash
curl -sk -H "X-API-Key: YOUR_API_KEY" \
  "https://your-backend:8443/api/v1/agents/events?limit=10"

curl -sk -H "X-API-Key: YOUR_API_KEY" \
  "https://your-backend:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/events?type=offline"
```

### Remote-Befehle (Exec)

```
POST   /agents/{id}/exec          Shell-Befehl ausfuehren
```

```bash
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \
  https://your-backend:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/exec \
  -d '{"command":"uptime","timeout":30}'
# → {"data":{"output":"10:30AM  up 45 days, ..."},"status":"ok"}
```

### Tunnel-Management

```
POST   /agents/{id}/tunnel        Tunnel oeffnen
GET    /agents/{id}/tunnels       Aktive Tunnel auflisten
DELETE /agents/{id}/tunnel/{tid}  Tunnel schliessen
```

```bash
# SSH-Tunnel
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \
  https://your-backend:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/tunnel \
  -d '{"target_host":"127.0.0.1","target_port":22}'
# → {"tunnel_id":"abc123","proxy_port":10000,...}
# Verbinden: ssh -p 10000 root@your-backend

# WebGUI-Tunnel (OPNsense Port 4444)
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \
  https://your-backend:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/tunnel \
  -d '{"target_host":"127.0.0.1","target_port":4444}'
# Im Browser: https://your-backend:10001/

# PVE WebGUI-Tunnel (Proxmox Port 8006)
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \
  https://your-backend:8443/api/v1/agents/3f0a83a25d6fcdd8a623e76953559c87/tunnel \
  -d '{"target_host":"127.0.0.1","target_port":8006}'

# Aktive Tunnel
curl -sk -H "X-API-Key: YOUR_API_KEY" \
  https://your-backend:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/tunnels

# Tunnel schliessen
curl -sk -H "X-API-Key: YOUR_API_KEY" -X DELETE \
  https://your-backend:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/tunnel/abc123
```

Tunnel-Architektur:
- Proxy-Port-Range: 10000-20000 (dynamisch)
- Jede TCP-Verbindung am Proxy bekommt eigene Session
- Agent oeffnet Ziel-Verbindung erst bei erstem Datenpaket (Lazy Connect)
- Binary WebSocket Messages: `[id_len:1][session_id][tcp_payload]`

### Config-Backups

```
POST   /agents/{id}/backup                 Backup ausloesen (via WebSocket)
GET    /agents/{id}/backups                Alle Backups (?limit=N)
GET    /agents/{id}/backups/{id|latest}    Backup herunterladen (?format=xml)
DELETE /agents/{id}/backups/{id}           Backup loeschen
GET    /agents/{id}/backups/diff           Diff (?a=ID&b=ID)
```

```bash
# Backup ausloesen
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \
  https://your-backend:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/backup

# Alle Backups
curl -sk -H "X-API-Key: YOUR_API_KEY" \
  https://your-backend:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/backups

# Neuestes als XML
curl -sk -H "X-API-Key: YOUR_API_KEY" \
  "https://your-backend:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/backups/latest?format=xml" \
  -o config-backup.xml

# Diff
curl -sk -H "X-API-Key: YOUR_API_KEY" \
  "https://your-backend:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/backups/diff?a=1&b=3"
```

Deduplizierung: Gleicher SHA256-Hash = kein neuer Eintrag.

### WireGuard Peer-Management

```
GET    /agents/{id}/wireguard/peers        Alle Peers (?server_name=X)
POST   /agents/{id}/wireguard/peers        Peer anlegen
DELETE /agents/{id}/wireguard/peers/{uuid}  Peer loeschen
```

Peer-Anlage: Keypair generiert auf Firewall, naechste freie IP automatisch, Endpoint Auto-Detect via WAN-IP.

```bash
# Peer anlegen
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \
  https://your-backend:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/wireguard/peers \
  -d '{"name":"VPN_MUELLER","allowed_ips":"0.0.0.0/0","dns":"10.172.100.210"}'

# Alle Peers
curl -sk -H "X-API-Key: YOUR_API_KEY" \
  https://your-backend:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/wireguard/peers

# Peer loeschen
curl -sk -H "X-API-Key: YOUR_API_KEY" -X DELETE \
  https://your-backend:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/wireguard/peers/89c10e2d-148e-11f1-a7af-7c5a1c6c7b73
```

### Kunden (Customers)

```
GET    /customers                  Alle Kunden
POST   /customers                  Neuen Kunden anlegen
GET    /customers/{id}             Einzelnen Kunden abrufen
PUT    /customers/{id}             Kunden aktualisieren
DELETE /customers/{id}             Kunden loeschen
GET    /customers/{id}/agents      Agents eines Kunden
```

```bash
# Kunden anlegen
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \
  https://your-backend:8443/api/v1/customers \
  -d '{"number":"K05001","name":"Beispiel GmbH"}'

# Kunden aktualisieren
curl -sk -H "X-API-Key: YOUR_API_KEY" -X PUT \
  https://your-backend:8443/api/v1/customers/1 \
  -d '{"number":"K05001","name":"Beispiel AG"}'

# Agents eines Kunden
curl -sk -H "X-API-Key: YOUR_API_KEY" \
  https://your-backend:8443/api/v1/customers/1/agents
```

### Benutzer (Users)

```
GET    /users                      Alle Benutzer
POST   /users                      Neuen Benutzer anlegen
PUT    /users/{id}/password        Passwort aendern (min. 6 Zeichen)
DELETE /users/{id}                 Benutzer loeschen
```

```bash
# Benutzer anlegen
curl -sk -H "Authorization: Bearer <TOKEN>" -X POST \
  https://your-backend:8443/api/v1/users \
  -d '{"username":"operator","password":"Sicher!123"}'

# Passwort aendern
curl -sk -H "Authorization: Bearer <TOKEN>" -X PUT \
  https://your-backend:8443/api/v1/users/1/password \
  -d '{"password":"NeuesPasswort!123"}'
```

### Metriken (TimescaleDB)

```
GET    /agents/{id}/metrics          Rohe Metriken (?metric=X&from=&to=&limit=N)
GET    /agents/{id}/metrics/summary  Aggregiert (?metric=X&bucket=5+minutes&from=&to=)
```

Verfuegbare Metriken: `cpu_usage`, `memory_used_percent`, `memory_used_bytes`, `memory_total_bytes`, `uptime_seconds`, `disk_used_percent`, `disk_used_bytes`, `interface_rx_bytes`, `interface_tx_bytes`, `gateway_rtt_ms`, `gateway_loss_percent`

```bash
# CPU letzte 24h
curl -sk -H "X-API-Key: YOUR_API_KEY" \
  "https://your-backend:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/metrics?metric=cpu_usage"

# 5-Minuten-Durchschnitte
curl -sk -H "X-API-Key: YOUR_API_KEY" \
  "https://your-backend:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/metrics/summary?metric=cpu_usage&bucket=5+minutes"

# Stuendliche RAM-Zusammenfassung mit Zeitraum
curl -sk -H "X-API-Key: YOUR_API_KEY" \
  "https://your-backend:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/metrics/summary?metric=memory_used_percent&bucket=1+hour&from=2026-03-01T00:00:00Z&to=2026-03-01T23:59:59Z"
```

### Updates & Reboot

```
POST   /agents/{id}/update-check   Verfuegbare Updates pruefen
POST   /agents/{id}/update          Normales Update (Core + Packages)
POST   /agents/{id}/major-update    Major-Upgrade (2-Phasen)
POST   /agents/{id}/reboot          Reboot
```

```bash
# Update-Check
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \
  https://your-backend:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/update-check

# Update mit Reboot
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \
  https://your-backend:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/update \
  -d '{"reboot":true}'

# Major-Update Phase 1 (Base+Kernel, dann Reboot)
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \
  https://your-backend:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/major-update \
  -d '{"version":"26.1","reboot":true}'

# Major-Update Phase 2 (nach Reboot: Packages)
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \
  https://your-backend:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/major-update \
  -d '{"version":"26.1","phase":"2"}'

# Reboot
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \
  https://your-backend:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/reboot \
  -d '{"delay":5}'
```

### Firmware / Agent-Update

```
GET    /firmware                           Alle Firmware-Versionen (?platform=X)
POST   /firmware/upload?version=X&platform=Y  Binary hochladen
GET    /firmware/download?platform=Y       Binary herunterladen (fuer Updater)
POST   /agents/{id}/request-update         Update-Flag setzen
DELETE /agents/{id}/request-update         Update-Flag zuruecksetzen
POST   /agents/request-update-all          Alle Agents updaten
POST   /agents/{id}/agent-update           Legacy: Binary direkt via WebSocket pushen
```

```bash
# Firmware-Info
curl -sk -H "X-API-Key: YOUR_API_KEY" \
  https://your-backend:8443/api/v1/firmware

# Nur FreeBSD-Firmware
curl -sk -H "X-API-Key: YOUR_API_KEY" \
  "https://your-backend:8443/api/v1/firmware?platform=freebsd"

# Firmware hochladen (FreeBSD)
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \
  "https://your-backend:8443/api/v1/firmware/upload?version=1.0.3&platform=freebsd" \
  --data-binary @build/rmm-agent

# Firmware hochladen (Linux)
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \
  "https://your-backend:8443/api/v1/firmware/upload?version=1.0.1&platform=linux" \
  --data-binary @build/rmm-agent-linux

# Update fuer einzelnen Agent anfordern
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \
  https://your-backend:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/request-update

# Update-Flag zuruecksetzen
curl -sk -H "X-API-Key: YOUR_API_KEY" -X DELETE \
  https://your-backend:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/request-update

# Alle Agents updaten
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \
  https://your-backend:8443/api/v1/agents/request-update-all
```

Update-Flow: Frontend setzt Flag → Updater (separater Prozess auf Firewall) pollt alle 60s → Download + SHA256-Verify + Rollback bei Fehler → Flag wird geloescht.

### WebSocket

```
GET    /agent/ws?agent_id=X&api_key=X    WebSocket-Verbindung (Agent)
GET    /hub/status                        Hub-Status
```

```bash
# Hub-Status (verbundene Agents, aktive Tunnel)
curl -sk -H "X-API-Key: YOUR_API_KEY" \
  https://your-backend:8443/api/v1/hub/status
```

WebSocket Commands (Backend → Agent):

| Command | Parameter | Beschreibung |
|---------|-----------|-------------|
| `exec` | `command`, `timeout` | Shell-Befehl ausfuehren |
| `backup` | — | Config.xml lesen + senden |
| `update_check` | — | Verfuegbare Updates pruefen |
| `update` | `reboot` | Normales Update |
| `major_update` | `version`, `phase`, `reboot` | Major-Upgrade |
| `reboot` | `delay` | Reboot |
| `tunnel_connect` | `session_id`, `tunnel_id`, `target_host`, `target_port` | TCP-Tunnel oeffnen |
| `tunnel_disconnect` | `session_id` | Tunnel-Session schliessen |
| `wg_add_peer` | `name`, `server_name`, `allowed_ips`, `dns`, `endpoint` | WireGuard Peer anlegen |
| `wg_list_peers` | `server_name` | WireGuard Peers auflisten |
| `wg_delete_peer` | `uuid` | WireGuard Peer loeschen |
| `agent_update` | Binary + Hash (im Data-Feld) | Agent-Binary aktualisieren (Legacy) |

---

## Hinweise

- Backend braucht **kein root** — laeuft als normaler User auf Port 8443
- Beim Deploy: **alte Prozesse killen** bevor neuer startet (sonst "bind: address already in use")
- FreeBSD csh hat kein `$()` — Agent verwendet `/bin/sh -c` fuer Commands
