diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e4cff94 --- /dev/null +++ b/.env.example @@ -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 diff --git a/.gitignore b/.gitignore index d410b6a..2adcfe9 100644 --- a/.gitignore +++ b/.gitignore @@ -38,9 +38,11 @@ frontend/dist/ frontend/src/config.js !frontend/src/config.example.js -# .env hat echte Credentials — nie einchecken +# .env Dateien haben echte Credentials — nie einchecken frontend/.env !frontend/.env.example +.env +!.env.example # ======================== # OS / Editor diff --git a/README.md b/README.md index 4f037ff..9dea108 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,45 @@ Client ──► Backend:ProxyPort ──► WebSocket (Binary) ──► Agent ## Schnellstart +### Docker (empfohlen) + +Der einfachste Weg. Voraussetzung: [Docker](https://docs.docker.com/get-docker/) + [Docker Compose](https://docs.docker.com/compose/install/) installiert. + +```bash +# 1. Repository klonen +git clone https://git.example.com/rmm.git && cd rmm + +# 2. Konfiguration anlegen +cp .env.example .env +``` + +`.env` anpassen: +```env +BACKEND_HOST=192.168.1.100 # IP/Hostname dieses Servers +DB_PASSWORD=sicheres_passwort +JWT_SECRET=$(openssl rand -hex 32) +API_KEY=$(openssl rand -hex 16) +``` + +```bash +# 3. Starten (baut alle Images automatisch) +docker compose up -d + +# 4. Status prüfen +docker compose ps +docker compose logs backend +``` + +Fertig. Frontend unter `http://`, Backend-API unter `https://:8443`. + +> **Hinweis:** Das Frontend-Image wird mit der `BACKEND_HOST`-URL gebaut. +> Nach einer IP-Änderung muss das Frontend neu gebaut werden: `docker compose build frontend && docker compose up -d frontend` + +--- + +### Manuell (ohne Docker) + + ### 1. Voraussetzungen **Backend-Server** (Linux, Debian 12+ empfohlen): diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..3c06fd5 --- /dev/null +++ b/backend/Dockerfile @@ -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"] diff --git a/backend/config.yaml.example b/backend/config.yaml.example index f429023..99799ae 100644 --- a/backend/config.yaml.example +++ b/backend/config.yaml.example @@ -1,8 +1,23 @@ # 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" tls_cert: "certs/server.crt" tls_key: "certs/server.key" -db_path: "rmm.db" -api_keys: - - "YOUR_API_KEY" +# Datenbank +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" diff --git a/backend/config/config.go b/backend/config/config.go index 259c45c..2572015 100644 --- a/backend/config/config.go +++ b/backend/config/config.go @@ -1,6 +1,7 @@ package config import ( + "fmt" "os" "github.com/cynfo/rmm-backend/db" @@ -41,7 +42,7 @@ func Load(path string) (*Config, error) { 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 != "" { cfg.ListenAddr = v } @@ -51,12 +52,32 @@ func Load(path string) (*Config, error) { if v := os.Getenv("RMM_TLS_KEY"); 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 != "" { 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 != "" { cfg.Database.Password = v } + if v := os.Getenv("RMM_DB_NAME"); v != "" { + cfg.Database.DBName = v + } return cfg, nil } diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b92d874 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,49 @@ +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: + context: ./frontend + args: + VITE_BACKEND_HOST: ${BACKEND_HOST} + VITE_BACKEND_PORT: ${BACKEND_PORT:-8443} + VITE_API_KEY: ${API_KEY} + restart: unless-stopped + ports: + - "80:80" + depends_on: + - backend + +volumes: + pgdata: + certs: diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..4a40fdd --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,29 @@ +FROM node:22-alpine AS builder + +WORKDIR /app +COPY package*.json ./ +RUN npm ci + +COPY . . + +# Backend-URL wird zur Build-Zeit via ARG gesetzt +ARG VITE_BACKEND_HOST=localhost +ARG VITE_BACKEND_PORT=8443 +ARG VITE_API_KEY="" + +ENV VITE_BACKEND_HOST=$VITE_BACKEND_HOST \ + VITE_BACKEND_PORT=$VITE_BACKEND_PORT \ + VITE_API_KEY=$VITE_API_KEY + +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 + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..fbec796 --- /dev/null +++ b/frontend/nginx.conf @@ -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"; + } +}