Compare commits

...

10 Commits

Author SHA1 Message Date
cynfo3000
b9d7383b21 docs: Debian 13 PostgreSQL-Versionskonflikt mit TimescaleDB dokumentiert
Debian 13 (Trixie) liefert PostgreSQL 17.8, TimescaleDB benoetigt >= 17.9.
Loesung: offizielles PostgreSQL-Apt-Repo (apt.postgresql.org/bookworm-pgdg)
statt Debian-eigene Pakete verwenden.
Fehlerbehebung fuer bestehende Installationen ebenfalls dokumentiert.
2026-03-08 21:14:24 +01:00
cynfo3000
d312a33d9e docs: Docker-Installationsanleitung in docs/DOCKER.md
- Schritt-fuer-Schritt vom ersten git clone bis zum ersten Login
- Haeufige Aufgaben: Logs, Neustart, Update, Konfiguration aendern
- Datensicherung: pg_dump Befehle
- Troubleshooting: die haeufigsten Probleme
- Port-Uebersicht
- README: Docker-Quickstart auf 4 Zeilen reduziert, Verweis auf DOCKER.md
- Actions-Workflow entfernt (keine fertigene Images noetig)
- docker-compose.build.yml entfernt (vereinfacht)
2026-03-08 19:46:53 +01:00
cynfo3000
3afd5c8445 feat: fertige Docker Images via Gitea Actions + Runtime-Konfiguration
Some checks failed
Docker Images bauen und pushen / build-and-push (push) Has been cancelled
CI/CD:
- .gitea/workflows/docker-publish.yml: baut + pusht bei Push auf release und Tags
- Images: git.cynfo.net/christian/rmm2/backend:latest + frontend:latest
- Gitea Secrets: REGISTRY_USER + REGISTRY_PASSWORD gesetzt

Runtime-Konfiguration (kein Rebuild bei IP-Aenderung):
- frontend/docker-entrypoint.sh: generiert runtime-config.js aus ENV-Vars beim Start
- frontend/src/config.js: window.__RMM_CONFIG__ (Docker) vor Vite-ENV (in .gitignore, nicht im release-Commit)
- frontend/public/runtime-config.js: leerer Fallback fuer lokale Entwicklung
- frontend/index.html: laedt /runtime-config.js vor dem App-Bundle

docker-compose:
- Nutzt fertige Images aus der Gitea Registry
- docker-compose.build.yml: Override fuer lokalen Build
- Alle Frontend-Werte via ENV zur Laufzeit
2026-03-08 19:42:56 +01:00
cynfo3000
9d5f060cbd feat: Docker-Support (Compose + Dockerfiles)
- docker-compose.yml: db (timescaledb/pg17) + backend + frontend
- backend/Dockerfile: multi-stage (golang:1.24-alpine -> debian:bookworm-slim)
- frontend/Dockerfile: multi-stage (node:22-alpine -> nginx:alpine), Build-Args fuer VITE_*
- frontend/nginx.conf: SPA-Routing + Asset-Caching
- .env.example: BACKEND_HOST, DB_PASSWORD, JWT_SECRET, API_KEY
- backend/config/config.go: ENV-Var-Support vervollstaendigt (JWT_SECRET, API_KEY, DB_PORT, DB_USER, DB_NAME)
- backend/config.yaml.example: ENV-Var Kommentare ergaenzt
- README: Docker-Quickstart als empfohlener Einstieg (3 Befehle)
- .gitignore: .env zur ignore-Liste hinzugefuegt
2026-03-08 19:39:10 +01:00
cynfo3000
3916dc8bee docs: Agent-Installation aus Schnellstart entfernt
Gehoert nicht in den Quickstart — laeuft pro Firewall/Kunde
separat und ist in agent-bsd/README.md dokumentiert.
2026-03-08 19:35:42 +01:00
cynfo3000
be7cd32d45 docs: API-Key-Henne-Ei-Problem erklaert
Der api_key wird vom Nutzer selbst in config.yaml festgelegt und
gleichzeitig in frontend/.env eingetragen — kein laufendes System
noetig. Hinweis auf zusaetzliche Keys ueber die Settings-UI.
2026-03-08 19:33:09 +01:00
cynfo3000
65c0e6145b docs: vollstaendige Installationsanleitung im Schnellstart
- Schritt-fuer-Schritt: Voraussetzungen, Konfiguration, DB-Setup,
  Bauen, Backend-Deploy, Frontend-Deploy, Agent-Installation
- .env-basierte Konfiguration dokumentiert (kein config.js mehr)
- 2FA-Einrichtung als empfohlener erster Schritt nach Login erwaehnt
- Troubleshooting-Tabelle fuer haeufige Probleme
- Standardpasswort Start!123 entfernt (wird beim ersten Start generiert)
2026-03-08 19:29:57 +01:00
cynfo3000
9035da89ce release: interne IPs durch .env-Platzhalter ersetzt
- frontend/src/config.js liest jetzt aus VITE_BACKEND_HOST / VITE_BACKEND_PORT / VITE_API_KEY
- frontend/.env.example hinzugefuegt (Vorlage fuer eigene Konfiguration)
- frontend/src/config.example.js auf .env-Ansatz aktualisiert
- frontend/.env zu .gitignore hinzugefuegt
- dist/ war bereits ignoriert (Build-Artefakt mit internen IPs)
- backend/main.go: localIPs() ermittelt IPs dynamisch, keine Hardcodierung
- Alle internen IPs (192.168.x.x, 10.172.x.x) nur in ignorierten Dateien
2026-03-08 19:21:19 +01:00
cynfo3000
ebd3aecdb5 Merge branch 'main' into release 2026-03-08 19:19:50 +01:00
cynfo3000
5fc46a8148 release: alle hardcodierten IPs entfernt, Konfiguration via Env-Variablen und Auto-Discovery 2026-03-07 14:31:24 +01:00
24 changed files with 707 additions and 48 deletions

18
.env.example Normal file
View File

@ -0,0 +1,18 @@
# docker-compose Konfiguration
# Kopiere diese Datei nach .env und trage deine Werte ein
# IP oder Hostname des Servers auf dem du docker compose ausfuehrst
# (wird vom Browser genutzt um das Backend zu erreichen)
BACKEND_HOST=192.168.1.100
BACKEND_PORT=8443
# Datenbank-Passwort (frei waehlbar)
DB_PASSWORD=sicheres_db_passwort
# JWT Secret — langer zufaelliger String (min. 32 Zeichen)
# Beispiel: openssl rand -hex 32
JWT_SECRET=ZUFAELLIGER_STRING_MIN_32_ZEICHEN
# API Key — frei waehlbar, wird fuer Agent und Frontend benoetigt
# Beispiel: openssl rand -hex 16
API_KEY=SELBST_GEWAEHLTER_API_KEY

6
.gitignore vendored
View File

@ -38,6 +38,12 @@ frontend/dist/
frontend/src/config.js frontend/src/config.js
!frontend/src/config.example.js !frontend/src/config.example.js
# .env Dateien haben echte Credentials — nie einchecken
frontend/.env
!frontend/.env.example
.env
!.env.example
# ======================== # ========================
# OS / Editor # OS / Editor
# ======================== # ========================

182
README.md
View File

@ -158,13 +158,179 @@ Client ──► Backend:ProxyPort ──► WebSocket (Binary) ──► Agent
## Schnellstart ## Schnellstart
### Docker (empfohlen)
Voraussetzung: Docker installiert (`curl -fsSL https://get.docker.com | sh`).
```bash ```bash
make all # Backend + Agent + Updater bauen git clone <repo-url> && cd rmm
make certs # TLS-Zertifikate generieren git checkout release
make deploy-backend # Backend deployen cp .env.example .env # .env anpassen (IP-Adresse, Passwörter, API-Key)
docker compose up -d
``` ```
Agent auf OPNsense installieren → siehe [agent-bsd/README.md](agent-bsd/README.md) Fertig. Frontend unter `http://<BACKEND_HOST>`.
Vollständige Anleitung mit allen Details, Troubleshooting und Backup: **[docs/DOCKER.md](docs/DOCKER.md)**
---
### Manuell (ohne Docker)
### 1. Voraussetzungen
**Backend-Server** (Linux, Debian 12+ empfohlen):
```bash
# PostgreSQL 17 + TimescaleDB
apt install -y postgresql postgresql-client
# TimescaleDB: https://docs.timescale.com/self-hosted/latest/install/
```
**Build-System** (macOS oder Linux):
```bash
# Go 1.24+
go version # muss >= 1.24 sein
```
---
### 2. Konfiguration
**Backend:**
```bash
cp backend/config.yaml.example backend/config.yaml
```
```yaml
# backend/config.yaml (wichtigste Felder)
listen_addr: ":8443"
db_dsn: "postgres://rmm:PASSWORT@localhost:5432/rmm?sslmode=disable"
jwt_secret: "ZUFAELLIGER_STRING_MIN_32_ZEICHEN"
api_key: "SELBST_GEWAEHLTER_API_KEY"
```
> **Hinweis:** Den `api_key` legst du selbst fest — z.B. eine UUID oder ein langer zufaelliger String.
> Denselben Wert traegst du gleich in die Frontend-`.env` ein. Du brauchst kein laufendes
> System dafuer. Ueber die Einstellungen-Seite koennen spaeter weitere Keys (z.B. pro Agent)
> hinzugefuegt werden.
**Frontend:**
```bash
cp frontend/.env.example frontend/.env
```
```env
# frontend/.env
VITE_BACKEND_HOST=192.168.1.100 # IP/Hostname deines Backend-Servers
VITE_BACKEND_PORT=8443
VITE_API_KEY=SELBST_GEWAEHLTER_API_KEY # identisch mit api_key aus backend/config.yaml
```
---
### 3. Datenbank anlegen
```bash
# Als postgres-User auf dem Backend-Server:
sudo -u postgres psql << 'SQL'
CREATE USER rmm WITH PASSWORD 'PASSWORT';
CREATE DATABASE rmm OWNER rmm;
\c rmm
CREATE EXTENSION IF NOT EXISTS timescaledb;
SQL
```
Die Tabellen werden beim ersten Backend-Start automatisch angelegt (Auto-Migration).
---
### 4. Bauen
```bash
# Alles in einem Schritt:
make all
# Oder einzeln:
make backend # → build/rmm-backend (linux/amd64)
make agent-bsd # → build/rmm-agent-bsd (freebsd/amd64)
make agent-linux # → build/rmm-agent-linux (linux/amd64)
make frontend # → frontend/dist/
```
---
### 5. Backend deployen
```bash
# Binary + Config auf den Server kopieren:
scp build/rmm-backend root@<backend-server>:/opt/rmm/
scp backend/config.yaml root@<backend-server>:/opt/rmm/
# Systemd-Service einrichten:
cat > /etc/systemd/system/rmm-backend.service << 'EOF'
[Unit]
Description=RMM Backend
After=network.target postgresql.service
[Service]
ExecStart=/opt/rmm/rmm-backend
WorkingDirectory=/opt/rmm
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now rmm-backend
systemctl status rmm-backend
```
Der erste Start legt automatisch einen Admin-User an (Credentials werden ins Log geschrieben).
---
### 6. Frontend deployen
```bash
# Build (mit .env-Konfiguration aus Schritt 2):
cd frontend && npm install && npm run build
# Auf Web-Server kopieren (z.B. Nginx):
scp -r frontend/dist/* root@<frontend-server>:/var/www/html/
# Nginx-Konfiguration (einfach):
cat > /etc/nginx/sites-available/rmm << 'EOF'
server {
listen 80;
root /var/www/html;
index index.html;
location / { try_files $uri $uri/ /index.html; }
}
EOF
ln -s /etc/nginx/sites-available/rmm /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
```
---
### 7. Erste Anmeldung & 2FA
1. Frontend unter `http://<frontend-server>` aufrufen
2. Mit dem beim ersten Backend-Start erstellten Admin-Account anmelden
3. Einstellungen → **Zwei-Faktor-Authentifizierung** → Einrichten (empfohlen)
4. TOTP-Secret in Aegis / Google Authenticator eingeben, ersten Code bestätigen
---
### Troubleshooting
| Problem | Lösung |
|---------|--------|
| Backend startet nicht | `journalctl -u rmm-backend -f` — häufig DB-Verbindung oder Port belegt |
| Agent verbindet nicht | API-Key in `agent/config.yaml` prüfen, TLS-Zertifikat ggf. als trusted markieren |
| Frontend zeigt keine Daten | `VITE_BACKEND_HOST` und `VITE_API_KEY` in `.env` prüfen, neu bauen |
| TimescaleDB fehlt | `CREATE EXTENSION timescaledb` in der rmm-Datenbank ausführen |
## Dateistruktur ## Dateistruktur
@ -210,12 +376,12 @@ rmm/
- **Persistente Agent-ID**: Ueberlebt Agent- und Backend-Neustarts - **Persistente Agent-ID**: Ueberlebt Agent- und Backend-Neustarts
- **FreeBSD-kompatibel**: Volle Pfade (/usr/bin/netstat etc.), daemon statt nohup - **FreeBSD-kompatibel**: Volle Pfade (/usr/bin/netstat etc.), daemon statt nohup
## Zugangsdaten ## Zugangsdaten (nach Installation)
``` ```
Backend: https://your-backend:8443 Backend: https://<backend-ip>:8443
API-Key: YOUR_API_KEY API-Key: in backend/config.yaml → api_key
Frontend: http://your-backend (admin / Start!123) Frontend: https://<frontend-ip> (Login: admin / Passwort beim ersten Start gesetzt)
``` ```
## Lizenz ## Lizenz

31
backend/Dockerfile Normal file
View File

@ -0,0 +1,31 @@
FROM golang:1.24-alpine AS builder
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o rmm-backend .
# ---
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /build/rmm-backend .
COPY config.yaml.example config.yaml.example
RUN mkdir -p certs
EXPOSE 8443
# Umgebungsvariablen mit Standardwerten (koennen alle per ENV ueberschrieben werden)
ENV RMM_LISTEN_ADDR=":8443" \
RMM_DB_HOST="db" \
RMM_DB_PORT="5432" \
RMM_DB_USER="rmm" \
RMM_DB_NAME="rmm"
CMD ["./rmm-backend"]

View File

@ -1,8 +1,23 @@
# RMM Backend Konfiguration # RMM Backend Konfiguration
# Kopiere diese Datei nach config.yaml und passe die Werte an.
# Alle Werte koennen auch als Umgebungsvariablen gesetzt werden (Docker).
listen_addr: ":8443" listen_addr: ":8443"
tls_cert: "certs/server.crt" tls_cert: "certs/server.crt"
tls_key: "certs/server.key" tls_key: "certs/server.key"
db_path: "rmm.db"
api_keys: # Datenbank
- "YOUR_API_KEY" database:
host: "127.0.0.1" # ENV: RMM_DB_HOST
port: 5432 # ENV: RMM_DB_PORT
user: "rmm" # ENV: RMM_DB_USER
password: "" # ENV: RMM_DB_PASSWORD
dbname: "rmm" # ENV: RMM_DB_NAME
sslmode: "disable"
# Sicherheit — zufaellige Strings waehlen, nie die Beispielwerte verwenden!
jwt_secret: "ZUFAELLIGER_STRING_MIN_32_ZEICHEN" # ENV: RMM_JWT_SECRET
# API-Keys fuer Agent und Frontend-Zugriff
api_keys: # ENV: RMM_API_KEY (wird zusaetzlich angehaengt)
- "SELBST_GEWAEHLTER_API_KEY"

View File

@ -1,6 +1,7 @@
package config package config
import ( import (
"fmt"
"os" "os"
"github.com/cynfo/rmm-backend/db" "github.com/cynfo/rmm-backend/db"
@ -41,7 +42,7 @@ func Load(path string) (*Config, error) {
return nil, err return nil, err
} }
// Env overrides // Env overrides — alle Werte koennen via ENV ueberschrieben werden (z.B. Docker)
if v := os.Getenv("RMM_LISTEN_ADDR"); v != "" { if v := os.Getenv("RMM_LISTEN_ADDR"); v != "" {
cfg.ListenAddr = v cfg.ListenAddr = v
} }
@ -51,12 +52,32 @@ func Load(path string) (*Config, error) {
if v := os.Getenv("RMM_TLS_KEY"); v != "" { if v := os.Getenv("RMM_TLS_KEY"); v != "" {
cfg.TLSKey = v cfg.TLSKey = v
} }
if v := os.Getenv("RMM_JWT_SECRET"); v != "" {
cfg.JWTSecret = v
}
if v := os.Getenv("RMM_API_KEY"); v != "" {
cfg.APIKeys = append(cfg.APIKeys, v)
}
if v := os.Getenv("RMM_DB_HOST"); v != "" { if v := os.Getenv("RMM_DB_HOST"); v != "" {
cfg.Database.Host = v cfg.Database.Host = v
} }
if v := os.Getenv("RMM_DB_PORT"); v != "" {
if port := 0; len(v) > 0 {
fmt.Sscanf(v, "%d", &port)
if port > 0 {
cfg.Database.Port = port
}
}
}
if v := os.Getenv("RMM_DB_USER"); v != "" {
cfg.Database.User = v
}
if v := os.Getenv("RMM_DB_PASSWORD"); v != "" { if v := os.Getenv("RMM_DB_PASSWORD"); v != "" {
cfg.Database.Password = v cfg.Database.Password = v
} }
if v := os.Getenv("RMM_DB_NAME"); v != "" {
cfg.Database.DBName = v
}
return cfg, nil return cfg, nil
} }

View File

@ -151,7 +151,7 @@ func ensureTLS(certPath, keyPath string) error {
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true, BasicConstraintsValid: true,
IPAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("192.168.85.13")}, IPAddresses: localIPs(),
DNSNames: []string{"localhost", "rmm-backend"}, DNSNames: []string{"localhost", "rmm-backend"},
} }
@ -182,3 +182,28 @@ func ensureTLS(certPath, keyPath string) error {
log.Printf("TLS-Zertifikat generiert: %s, %s", certPath, keyPath) log.Printf("TLS-Zertifikat generiert: %s, %s", certPath, keyPath)
return nil return nil
} }
// localIPs sammelt alle lokalen IP-Adressen des Hosts für das TLS-Zertifikat
func localIPs() []net.IP {
ips := []net.IP{net.ParseIP("127.0.0.1")}
ifaces, err := net.Interfaces()
if err != nil {
return ips
}
for _, iface := range ifaces {
addrs, _ := iface.Addrs()
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip != nil && !ip.IsLoopback() && ip.To4() != nil {
ips = append(ips, ip)
}
}
}
return ips
}

View File

@ -1,14 +1,24 @@
#!/bin/bash #!/bin/bash
# Frontend Build & Deploy
# Umgebungsvariablen setzen:
# REMOTE=root@<FRONTEND-SERVER>
# VITE_BACKEND_HOST=<BACKEND-IP-ODER-HOSTNAME>
# VITE_BACKEND_PORT=8443 (optional, default: 8443)
# VITE_API_KEY=<API-KEY> (optional)
set -e set -e
FRONTEND_DIR="$(dirname "$0")/frontend"
REMOTE="root@192.168.85.20"
WEB_ROOT="/var/www/html"
echo "Building..." REMOTE="${REMOTE:-root@localhost}"
cd "$FRONTEND_DIR" && npm run build WEBROOT="${WEBROOT:-/var/www/html}"
echo "Deploying..." echo "==> Baue Frontend..."
ssh "$REMOTE" "rm -rf ${WEB_ROOT}/assets/*" VITE_BACKEND_HOST="${VITE_BACKEND_HOST}" \
scp -r dist/* "$REMOTE:${WEB_ROOT}/" VITE_BACKEND_PORT="${VITE_BACKEND_PORT:-8443}" \
ssh "$REMOTE" "chmod -R 755 ${WEB_ROOT}/assets && chmod 644 ${WEB_ROOT}/assets/* ${WEB_ROOT}/index.html ${WEB_ROOT}/vite.svg 2>/dev/null; nginx -s reload" VITE_API_KEY="${VITE_API_KEY}" \
echo "Done." npm run build
echo "==> Deploye nach $REMOTE:$WEBROOT ..."
ssh "$REMOTE" "rm -rf ${WEBROOT}/assets/*"
scp -r dist/assets dist/index.html "$REMOTE:${WEBROOT}/"
ssh "$REMOTE" "chown -R www-data:www-data ${WEBROOT}/ && chmod -R 755 ${WEBROOT}/"
echo "==> Fertig"

48
docker-compose.yml Normal file
View File

@ -0,0 +1,48 @@
services:
db:
image: timescale/timescaledb:latest-pg17
restart: unless-stopped
environment:
POSTGRES_DB: rmm
POSTGRES_USER: rmm
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U rmm"]
interval: 5s
timeout: 5s
retries: 10
backend:
build: ./backend
restart: unless-stopped
ports:
- "8443:8443"
environment:
RMM_DB_HOST: db
RMM_DB_PASSWORD: ${DB_PASSWORD}
RMM_JWT_SECRET: ${JWT_SECRET}
RMM_API_KEY: ${API_KEY}
volumes:
- certs:/app/certs
depends_on:
db:
condition: service_healthy
frontend:
build: ./frontend
restart: unless-stopped
ports:
- "80:80"
environment:
BACKEND_HOST: ${BACKEND_HOST}
BACKEND_PORT: ${BACKEND_PORT:-8443}
API_KEY: ${API_KEY}
depends_on:
- backend
volumes:
pgdata:
certs:

View File

@ -291,7 +291,7 @@ Siehe [FIRMWARE.md](FIRMWARE.md) fuer Details.
# Peer anlegen # Peer anlegen
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \ curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \
https://your-backend:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/wireguard/peers \ 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"}' -d '{"name":"VPN_MUELLER","allowed_ips":"0.0.0.0/0","dns":"<DNS-SERVER>"}'
# Peer mit eigenem Endpoint # Peer mit eigenem Endpoint
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \ curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \

View File

@ -620,7 +620,7 @@ Peer-Anlage: Keypair generiert auf Firewall, naechste freie IP automatisch, Endp
# Peer anlegen # Peer anlegen
curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \ curl -sk -H "X-API-Key: YOUR_API_KEY" -X POST \
https://your-backend:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/wireguard/peers \ 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"}' -d '{"name":"VPN_MUELLER","allowed_ips":"0.0.0.0/0","dns":"<DNS-SERVER>"}'
# Alle Peers # Alle Peers
curl -sk -H "X-API-Key: YOUR_API_KEY" \ curl -sk -H "X-API-Key: YOUR_API_KEY" \

221
docs/DOCKER.md Normal file
View File

@ -0,0 +1,221 @@
# Docker Installation
Diese Anleitung erklärt, wie du das RMM-System mit Docker auf einem Linux-Server installierst.
## Voraussetzungen
- Linux-Server (Debian 12 / Ubuntu 22.04 oder neuer empfohlen)
- Mindestens 2 GB RAM, 10 GB freier Speicherplatz
- Docker und Docker Compose installiert
**Docker installieren** (falls noch nicht vorhanden):
```bash
curl -fsSL https://get.docker.com | sh
```
Das war es. Docker Compose ist seit Docker 2.x automatisch dabei (`docker compose`).
---
## Installation
### Schritt 1 — Repository klonen
```bash
git clone https://git.cynfo.net/christian/rmm2.git
cd rmm2
git checkout release
```
---
### Schritt 2 — Konfigurationsdatei anlegen
```bash
cp .env.example .env
```
Öffne `.env` mit einem Texteditor und trage deine Werte ein:
```bash
nano .env
```
Die Datei sieht so aus:
```env
# IP-Adresse oder Hostname dieses Servers
# (die Adresse über die du später im Browser das Frontend erreichst)
BACKEND_HOST=192.168.1.100
# Port des Backends — 8443 ist der Standard, nur ändern wenn nötig
BACKEND_PORT=8443
# Datenbank-Passwort — frei wählbar, wird nur intern verwendet
DB_PASSWORD=einSicheresPasswort123
# JWT-Secret — langer zufälliger String, mindestens 32 Zeichen
# Tipp: openssl rand -hex 32
JWT_SECRET=hierEinenLangenZufaelligenStringEintragen
# API-Key — frei wählbar, wird vom Frontend und den Agents verwendet
# Tipp: openssl rand -hex 16
API_KEY=hierEinenApiKeyEintragen
```
> **Wichtig:** `BACKEND_HOST` ist die IP-Adresse oder der Hostname des Servers auf dem
> Docker läuft — nicht `localhost`. Der Browser muss diese Adresse erreichen können,
> um mit dem Backend zu kommunizieren.
---
### Schritt 3 — Starten
```bash
docker compose up -d
```
Beim ersten Start lädt Docker die benötigten Basis-Images (postgres, nginx etc.) und
baut dann das Backend und das Frontend. Das dauert beim ersten Mal 3-5 Minuten.
Danach läuft das System und startet automatisch nach einem Server-Reboot neu.
---
### Schritt 4 — Prüfen ob alles läuft
```bash
docker compose ps
```
Die Ausgabe sollte so aussehen:
```
NAME STATUS PORTS
rmm-db-1 Up (healthy) 5432/tcp
rmm-backend-1 Up 0.0.0.0:8443->8443/tcp
rmm-frontend-1 Up 0.0.0.0:80->80/tcp
```
Alle drei Services müssen `Up` zeigen. Wenn `db` noch `starting` zeigt, warte
20-30 Sekunden — die Datenbank braucht beim allerersten Start etwas länger.
---
### Schritt 5 — Erster Login
Rufe das Frontend im Browser auf:
```
http://<BACKEND_HOST>
```
Beim ersten Start legt das Backend automatisch einen Admin-Account an.
Die Zugangsdaten stehen im Backend-Log:
```bash
docker compose logs backend | grep -i "admin\|password\|passwort"
```
---
## Häufige Aufgaben
### Logs anschauen
```bash
# Alle Services
docker compose logs -f
# Nur Backend
docker compose logs -f backend
# Nur die letzten 50 Zeilen
docker compose logs --tail=50 backend
```
### System stoppen und neu starten
```bash
# Stoppen (Daten bleiben erhalten)
docker compose down
# Starten
docker compose up -d
# Neu starten (z.B. nach Konfigurationsänderung)
docker compose restart backend
```
### System aktualisieren
Nach einem `git pull` musst du die Images neu bauen:
```bash
git pull
docker compose build
docker compose up -d
```
### Konfiguration ändern (`.env` wurde bearbeitet)
```bash
# Nach Änderung von BACKEND_HOST, API_KEY etc.:
docker compose up -d
```
Docker Compose erkennt automatisch, dass sich die ENV-Werte geändert haben
und startet die betroffenen Container neu.
---
## Datensicherung
Die Datenbank liegt in einem Docker Volume (`pgdata`). Um ein Backup zu erstellen:
```bash
# Backup erstellen
docker compose exec db pg_dump -U rmm rmm > rmm-backup-$(date +%Y%m%d).sql
# Backup wiederherstellen
docker compose exec -T db psql -U rmm rmm < rmm-backup-20260101.sql
```
---
## Troubleshooting
### Backend startet nicht
```bash
docker compose logs backend
```
Häufige Ursachen:
- **Datenbank noch nicht bereit**: Warte 30 Sekunden, dann `docker compose restart backend`
- **Port 8443 belegt**: Prüfe mit `ss -tlnp | grep 8443` was den Port belegt
### Frontend zeigt „Verbindung fehlgeschlagen"
- Prüfe ob `BACKEND_HOST` in `.env` die richtige IP-Adresse ist
- Prüfe ob Port 8443 in der Firewall freigegeben ist
- Teste direkt: `curl -sk https://<BACKEND_HOST>:8443/api/v1/health`
### Datenbank-Volume zurücksetzen (Achtung: löscht alle Daten)
```bash
docker compose down -v # -v löscht auch Volumes
docker compose up -d
```
---
## Ports
| Port | Service | Beschreibung |
|------|---------|-------------|
| 80 | Frontend | Web-Oberfläche (Browser) |
| 8443 | Backend | REST-API + WebSocket (Agents + Frontend) |
Port 80 und 8443 müssen von deinem Netzwerk erreichbar sein.
Port 5432 (Datenbank) ist nur intern verfügbar.

View File

@ -51,7 +51,7 @@ Dann `config.js` anpassen:
```js ```js
// frontend/src/config.js // frontend/src/config.js
export const BACKEND_HOST = '192.168.85.13' // IP des Backend-Servers (für direkte Links, z.B. Tunnel-Ports) export const BACKEND_HOST = '<BACKEND-IP>' // IP des Backend-Servers (für direkte Links, z.B. Tunnel-Ports)
export const BACKEND_URL = `https://${BACKEND_HOST}:8443` export const BACKEND_URL = `https://${BACKEND_HOST}:8443`
export const API_KEY = 'DEIN_API_KEY' // aus backend/config.yaml → api_keys[0] export const API_KEY = 'DEIN_API_KEY' // aus backend/config.yaml → api_keys[0]
``` ```
@ -66,7 +66,7 @@ Für die **lokale Entwicklung** (`npm run dev`) muss zusätzlich `vite.config.js
// vite.config.js → server.proxy // vite.config.js → server.proxy
proxy: { proxy: {
'/api': { '/api': {
target: 'https://192.168.85.13:8443', // Backend-IP anpassen target: 'https://<BACKEND-IP>:8443', // Backend-IP anpassen
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,
ws: true, // WebSocket-Proxy aktivieren (Web Terminal, Agent-WS) ws: true, // WebSocket-Proxy aktivieren (Web Terminal, Agent-WS)
@ -127,7 +127,7 @@ server {
location /api/ { location /api/ {
client_max_body_size 50m; client_max_body_size 50m;
proxy_pass https://192.168.85.13:8443; proxy_pass https://<BACKEND-IP>:8443;
proxy_ssl_verify off; proxy_ssl_verify off;
proxy_set_header Host $host; proxy_set_header Host $host;
@ -166,7 +166,7 @@ rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx nginx -t && systemctl reload nginx
``` ```
> **Backend-IP anpassen:** `192.168.85.13:8443` durch die tatsächliche Backend-Adresse ersetzen. > **Backend-IP anpassen:** `<BACKEND-IP>:8443` durch die tatsächliche Backend-Adresse ersetzen.
--- ---
@ -228,7 +228,7 @@ API-Calls werden per Proxy an das Backend weitergeleitet — `vite.config.js` an
// vite.config.js // vite.config.js
proxy: { proxy: {
'/api': { '/api': {
target: 'https://192.168.85.13:8443', // Backend-IP anpassen target: 'https://<BACKEND-IP>:8443', // Backend-IP anpassen
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,
ws: true, // WebSocket-Proxy (Web Terminal, Agent-WS) ws: true, // WebSocket-Proxy (Web Terminal, Agent-WS)

View File

@ -8,14 +8,28 @@ Einrichtung von PostgreSQL mit TimescaleDB auf dem RMM-Backend-Server (Debian 13
## 1. PostgreSQL installieren ## 1. PostgreSQL installieren
> **Wichtig für Debian 13 (Trixie):** Das Standard-Debian-Repository liefert PostgreSQL 17.8,
> TimescaleDB benötigt aber mindestens 17.9. Deshalb muss zuerst das **offizielle
> PostgreSQL-Apt-Repository** eingerichtet werden — das liefert immer die aktuelle Version.
```bash ```bash
apt update apt update
apt install -y postgresql postgresql-contrib postgresql-common apt install -y curl gnupg
# Offizielles PostgreSQL-Repository einrichten
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \
| gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg
echo "deb https://apt.postgresql.org/pub/repos/apt bookworm-pgdg main" \
> /etc/apt/sources.list.d/pgdg.list
apt update
apt install -y postgresql-17
systemctl enable postgresql systemctl enable postgresql
systemctl start postgresql systemctl start postgresql
# Version prüfen (Trixie liefert PG17) # Version prüfen — muss 17.9 oder neuer zeigen
psql --version psql --version
``` ```
@ -24,17 +38,15 @@ psql --version
## 2. TimescaleDB installieren ## 2. TimescaleDB installieren
```bash ```bash
# Repository einrichten # TimescaleDB-Repository einrichten
apt install -y curl gnupg
echo "deb https://packagecloud.io/timescale/timescaledb/debian/ bookworm main" \ echo "deb https://packagecloud.io/timescale/timescaledb/debian/ bookworm main" \
> /etc/apt/sources.list.d/timescaledb.list > /etc/apt/sources.list.d/timescaledb.list
curl -sL https://packagecloud.io/timescale/timescaledb/gpgkey | \ curl -sL https://packagecloud.io/timescale/timescaledb/gpgkey \
gpg --dearmor > /etc/apt/trusted.gpg.d/timescaledb.gpg | gpg --dearmor > /etc/apt/trusted.gpg.d/timescaledb.gpg
apt update apt update
# Passende Version zu PG17 installieren # TimescaleDB für PostgreSQL 17 installieren
apt install -y timescaledb-2-loader-postgresql-17=2.25.1~debian12-1708 \ apt install -y timescaledb-2-postgresql-17
timescaledb-2-postgresql-17=2.25.1~debian12-1708
# In postgresql.conf aktivieren # In postgresql.conf aktivieren
sed -i "s/#shared_preload_libraries = ''/shared_preload_libraries = 'timescaledb'/" \ sed -i "s/#shared_preload_libraries = ''/shared_preload_libraries = 'timescaledb'/" \
@ -43,6 +55,14 @@ sed -i "s/#shared_preload_libraries = ''/shared_preload_libraries = 'timescaledb
systemctl restart postgresql systemctl restart postgresql
``` ```
> **Falls der Install mit einem Abhängigkeitsfehler fehlschlägt** (`postgresql-17 >= 17.9 required`):
> Das bedeutet, dass noch das Debian-eigene PostgreSQL 17.8 installiert ist.
> Lösung:
> ```bash
> apt remove -y postgresql* && apt autoremove -y
> # Dann Schritt 1 (PostgreSQL aus dem pgdg-Repo) wiederholen
> ```
--- ---
## 3. Datenbank und User anlegen ## 3. Datenbank und User anlegen

11
frontend/.env.example Normal file
View File

@ -0,0 +1,11 @@
# RMM Frontend Konfiguration
# Kopiere diese Datei nach .env und trage deine Werte ein
# IP/Hostname des RMM Backends (ohne https://, ohne Port)
VITE_BACKEND_HOST=your-backend-host
# Port des RMM Backends
VITE_BACKEND_PORT=8443
# API-Key fuer den Frontend-Zugriff (aus den Backend-Einstellungen)
VITE_API_KEY=your-api-key-here

26
frontend/Dockerfile Normal file
View File

@ -0,0 +1,26 @@
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ---
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
EXPOSE 80
# Konfiguration via ENV zur Laufzeit (kein Rebuild bei IP-Aenderung noetig)
ENV BACKEND_HOST=localhost \
BACKEND_PORT=8443 \
API_KEY=""
ENTRYPOINT ["/docker-entrypoint.sh"]

View File

@ -265,7 +265,7 @@ DELETE /api/v1/agents/{id}/wireguard/peers/{uuid} Peer loeschen
# Peer anlegen (Auto: Keypair, naechste freie IP, Endpoint) # Peer anlegen (Auto: Keypair, naechste freie IP, Endpoint)
curl -sk -H "Authorization: Bearer <TOKEN>" -X POST \ curl -sk -H "Authorization: Bearer <TOKEN>" -X POST \
https://your-backend:8443/api/v1/agents/6beb8dfd.../wireguard/peers \ https://your-backend:8443/api/v1/agents/6beb8dfd.../wireguard/peers \
-d '{"name":"VPN_MUELLER","allowed_ips":"0.0.0.0/0","dns":"10.172.100.210"}' -d '{"name":"VPN_MUELLER","allowed_ips":"0.0.0.0/0","dns":"<DNS-SERVER>"}'
# → {peer_config: "[Interface]\nPrivateKey=...\n[Peer]\n...", ...} # → {peer_config: "[Interface]\nPrivateKey=...\n[Peer]\n...", ...}
# Alle Peers # Alle Peers

View File

@ -0,0 +1,13 @@
#!/bin/sh
# Generiert /usr/share/nginx/html/runtime-config.js aus ENV-Variablen
# Wird beim Container-Start ausgefuehrt
cat > /usr/share/nginx/html/runtime-config.js << EOF
window.__RMM_CONFIG__ = {
backendHost: "${BACKEND_HOST:-localhost}",
backendPort: "${BACKEND_PORT:-8443}",
apiKey: "${API_KEY:-}"
};
EOF
exec nginx -g "daemon off;"

View File

@ -1,6 +1,8 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<!-- Runtime-Konfiguration (Docker): wird von nginx aus ENV-Vars generiert -->
<script src="/runtime-config.js" onerror="window.__RMM_CONFIG__={}"></script>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />

21
frontend/nginx.conf Normal file
View File

@ -0,0 +1,21 @@
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
# SPA-Routing: alle Pfade auf index.html
location / {
try_files $uri $uri/ /index.html;
}
# Caching fuer Assets
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Kein Caching fuer index.html
location = /index.html {
add_header Cache-Control "no-cache";
}
}

View File

@ -0,0 +1,3 @@
// Lokaler Entwicklungs-Fallback — in Docker wird diese Datei durch
// docker-entrypoint.sh mit echten Werten ueberschrieben
window.__RMM_CONFIG__ = {};

View File

@ -1178,7 +1178,7 @@ function WireGuardTab({ agentId, sys }) {
const [peers, setPeers] = useState([]) const [peers, setPeers] = useState([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [adding, setAdding] = useState(false) const [adding, setAdding] = useState(false)
const [newPeer, setNewPeer] = useState({ name: '', dns: '10.172.100.210, 10.172.100.220' }) const [newPeer, setNewPeer] = useState({ name: '', dns: '' })
const [result, setResult] = useState(null) const [result, setResult] = useState(null)
const [copied, setCopied] = useState(false) const [copied, setCopied] = useState(false)
const [error, setError] = useState('') const [error, setError] = useState('')
@ -1213,7 +1213,7 @@ function WireGuardTab({ agentId, sys }) {
}) })
setResult(resp?.data || resp) setResult(resp?.data || resp)
setAdding(false) setAdding(false)
setNewPeer({ name: '', dns: '10.172.100.210, 10.172.100.220' }) setNewPeer({ name: '', dns: '' })
loadPeers() loadPeers()
} catch (e) { } catch (e) {
setError(e.message) setError(e.message)

View File

@ -1,6 +1,8 @@
// Frontend-Konfiguration — VOR dem Build anpassen, dann als config.js speichern // Frontend-Konfiguration — wird automatisch aus .env gelesen
// config.js ist in .gitignore und wird NICHT eingecheckt // Kopiere frontend/.env.example → frontend/.env und trage deine Werte ein
// config.js und .env sind in .gitignore und werden NICHT eingecheckt
export const BACKEND_HOST = 'BACKEND_IP_ODER_HOSTNAME' export const BACKEND_HOST = import.meta.env.VITE_BACKEND_HOST || 'localhost'
export const BACKEND_URL = `https://${BACKEND_HOST}:8443` export const BACKEND_PORT = import.meta.env.VITE_BACKEND_PORT || '8443'
export const API_KEY = 'DEIN_API_KEY' export const BACKEND_URL = `https://${BACKEND_HOST}:${BACKEND_PORT}`
export const API_KEY = import.meta.env.VITE_API_KEY || ''

View File

@ -556,7 +556,7 @@ function SystemSettingsSection() {
type="text" type="text"
value={form.backend_url_internal || ''} value={form.backend_url_internal || ''}
onChange={(e) => setForm({ ...form, backend_url_internal: e.target.value })} onChange={(e) => setForm({ ...form, backend_url_internal: e.target.value })}
placeholder="https://192.168.85.13:8443" placeholder="https://<BACKEND-IP>:8443"
className="w-full bg-gray-800 border border-gray-700 rounded px-3 py-2 text-white text-sm focus:outline-none focus:border-orange-500 mt-1" className="w-full bg-gray-800 border border-gray-700 rounded px-3 py-2 text-white text-sm focus:outline-none focus:border-orange-500 mt-1"
/> />
<div className="text-[10px] text-gray-600 mt-0.5">Fuer Tunnel-Zugriff und lokale Verbindungen</div> <div className="text-[10px] text-gray-600 mt-0.5">Fuer Tunnel-Zugriff und lokale Verbindungen</div>