# PostgreSQL + TimescaleDB Setup Einrichtung von PostgreSQL mit TimescaleDB auf dem RMM-Backend-Server (Debian 13 Trixie). > **Hinweis:** Das Backend legt alle Tabellen beim ersten Start **automatisch an** (Auto-Migration inkl. ALTER TABLE für neue Spalten). Das Schema muss **nicht** manuell erstellt werden. Diese Datei dient als Referenz und Installationsanleitung. --- ## 1. PostgreSQL installieren ```bash apt update apt install -y postgresql postgresql-contrib postgresql-common systemctl enable postgresql systemctl start postgresql # Version prüfen (Trixie liefert PG17) psql --version ``` --- ## 2. TimescaleDB installieren ```bash # Repository einrichten apt install -y curl gnupg echo "deb https://packagecloud.io/timescale/timescaledb/debian/ bookworm main" \ > /etc/apt/sources.list.d/timescaledb.list curl -sL https://packagecloud.io/timescale/timescaledb/gpgkey | \ gpg --dearmor > /etc/apt/trusted.gpg.d/timescaledb.gpg apt update # Passende Version zu PG17 installieren apt install -y timescaledb-2-loader-postgresql-17=2.25.1~debian12-1708 \ timescaledb-2-postgresql-17=2.25.1~debian12-1708 # In postgresql.conf aktivieren sed -i "s/#shared_preload_libraries = ''/shared_preload_libraries = 'timescaledb'/" \ /etc/postgresql/17/main/postgresql.conf systemctl restart postgresql ``` --- ## 3. Datenbank und User anlegen ```bash sudo -u postgres psql << 'EOF' CREATE DATABASE rmm; CREATE USER rmm WITH PASSWORD 'SICHERES_PASSWORT'; GRANT ALL PRIVILEGES ON DATABASE rmm TO rmm; \c rmm GRANT ALL ON SCHEMA public TO rmm; CREATE EXTENSION IF NOT EXISTS timescaledb; \dx EOF ``` --- ## 4. pg_hba.conf anpassen ```bash echo "host rmm rmm 127.0.0.1/32 scram-sha-256" >> /etc/postgresql/17/main/pg_hba.conf systemctl reload postgresql ``` --- ## 5. Verbindung testen ```bash psql -h 127.0.0.1 -U rmm -d rmm -c "SELECT extname, extversion FROM pg_extension WHERE extname = 'timescaledb';" ``` --- ## 6. Schema (Referenz — wird automatisch angelegt) ### `agents` — Agent-Registry ```sql CREATE TABLE agents ( id TEXT PRIMARY KEY, name TEXT NOT NULL, hostname TEXT NOT NULL, ip TEXT NOT NULL, opnsense_version TEXT NOT NULL DEFAULT '', agent_version TEXT NOT NULL DEFAULT '', platform TEXT NOT NULL DEFAULT 'freebsd', registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), last_heartbeat TIMESTAMPTZ, customer_id INTEGER REFERENCES customers(id), update_requested BOOLEAN NOT NULL DEFAULT FALSE ); CREATE INDEX idx_agents_customer ON agents(customer_id); ``` ### `customers` — Kundenstammdaten ```sql CREATE TABLE customers ( id SERIAL PRIMARY KEY, number TEXT NOT NULL UNIQUE, -- z.B. "K05068" name TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); ``` ### `users` — Frontend-Benutzer ```sql CREATE TABLE users ( id SERIAL PRIMARY KEY, username TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, -- bcrypt display_name TEXT NOT NULL DEFAULT '', role TEXT NOT NULL DEFAULT 'admin', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), last_login TIMESTAMPTZ ); -- Standard-Admin: admin / Start!123 (beim ersten Backend-Start angelegt) ``` ### `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, 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 (Deduplizierung) size INTEGER NOT NULL, config_xml TEXT NOT NULL, 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); ``` ### `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, 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 ```sql CREATE TABLE agent_firmware ( version TEXT NOT NULL, platform TEXT NOT NULL DEFAULT 'freebsd', -- "freebsd","linux","windows" hash TEXT NOT NULL, -- SHA256 binary_data BYTEA NOT NULL, uploaded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (version, platform) ); ``` ### `tasks` — Task-Scheduler ```sql CREATE TABLE tasks ( id SERIAL PRIMARY KEY, agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE, name TEXT DEFAULT '', action TEXT NOT NULL, -- "update","backup","reboot",... status TEXT NOT NULL DEFAULT 'scheduled', schedule_type TEXT DEFAULT 'once', -- "once","daily","weekly","monthly" schedule_time TEXT DEFAULT '02:00', schedule_weekday INT, -- 0=So..6=Sa schedule_monthday INT, -- 1-31 reboot BOOLEAN DEFAULT FALSE, health_check BOOLEAN DEFAULT FALSE, health_check_timeout INT DEFAULT 600, active BOOLEAN DEFAULT TRUE, scheduled_at TIMESTAMPTZ NOT NULL, started_at TIMESTAMPTZ, finished_at TIMESTAMPTZ, last_run TIMESTAMPTZ, next_run TIMESTAMPTZ, run_count INT DEFAULT 0, result JSONB, webhook_url TEXT, webhook_sent BOOLEAN DEFAULT FALSE, webhook_response TEXT, created_by TEXT, 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; ``` ### `api_keys` — API-Keys ```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, filename TEXT NOT NULL, data BYTEA NOT NULL, 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, target_type TEXT, 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 ```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 ```sql CREATE TABLE metrics ( time TIMESTAMPTZ NOT NULL, agent_id TEXT NOT NULL, metric TEXT NOT NULL, value DOUBLE PRECISION NOT NULL, tags JSONB ); SELECT create_hypertable('metrics', 'time', if_not_exists => TRUE); CREATE INDEX idx_metrics_agent_metric ON metrics(agent_id, metric, time DESC); -- Retention: Rohdaten nach 90 Tagen löschen SELECT add_retention_policy('metrics', INTERVAL '90 days'); -- Compression: Daten älter als 7 Tage komprimieren ALTER TABLE metrics SET ( timescaledb.compress, timescaledb.compress_segmentby = 'agent_id, metric', timescaledb.compress_orderby = 'time DESC' ); SELECT add_compression_policy('metrics', INTERVAL '7 days'); ``` --- ## 7. Nützliche Abfragen ```sql -- Alle Agents mit Status SELECT id, name, hostname, ip, opnsense_version, agent_version, last_heartbeat, CASE WHEN last_heartbeat > NOW() - INTERVAL '2 min' THEN 'online' WHEN last_heartbeat > NOW() - INTERVAL '5 min' THEN 'stale' ELSE 'offline' END AS status FROM agents ORDER BY name; -- CPU-Auslastung letzte 24h (5-Minuten-Durchschnitt) SELECT time_bucket('5 minutes', time) AS t, agent_id, AVG(value) AS cpu_avg FROM metrics WHERE metric = 'cpu_usage' AND time > NOW() - INTERVAL '24 hours' GROUP BY t, agent_id ORDER BY t; -- Disk-Warnung: > 80% belegt SELECT agent_id, tags->>'mountpoint' AS mount, value AS used_pct, time FROM metrics WHERE metric = 'disk_used_percent' AND value > 80 AND time > NOW() - INTERVAL '1 hour' ORDER BY value DESC; -- Offene Tasks SELECT t.id, a.name AS agent, t.action, t.status, t.scheduled_at, t.webhook_url FROM tasks t JOIN agents a ON t.agent_id = a.id WHERE t.status IN ('scheduled','running') ORDER BY t.scheduled_at; -- Datenbankgrösse SELECT pg_size_pretty(pg_database_size('rmm')) AS db_size; SELECT relname AS tabelle, pg_size_pretty(pg_total_relation_size(relid)) AS groesse FROM pg_catalog.pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC; ``` --- ## Übersicht | Tabelle | Typ | Beschreibung | |---------|-----|-------------| | `agents` | Relational | Agent-Registry | | `customers` | Relational | Kundenstammdaten | | `users` | Relational | Frontend-Benutzer | | `system_data` | Relational | Aktueller Systemzustand (JSONB) | | `config_backups` | Relational | Versionierte OPNsense-Configs | | `agent_events` | Relational | Online/Offline-Ereignisse | | `agent_firmware` | Relational | Agent-Binaries pro Plattform | | `tasks` | Relational | Task-Scheduler mit Webhook | | `api_keys` | Relational | API-Keys | | `installers` | Relational | OPNsense Plugin-Pakete | | `audit_log` | Relational | Audit-Protokoll | | `system_settings` | Relational | Backend-Einstellungen (Key/Value) | | `metrics` | TimescaleDB | Zeitreihen (CPU, RAM, Disk, Network, Gateway) |