diff --git a/README.md b/README.md index 8ca51e4..69b47a4 100644 --- a/README.md +++ b/README.md @@ -876,7 +876,16 @@ RVS_HTTP_PORT=3000 ``` Steht der Proxy auf einem anderen Rechner, den Port per Firewall **nur fuer den -Proxy** freigeben — dahinter ist alles Klartext. Umstellen von Caddy: +Proxy** freigeben. + +**Strecke Proxy → RVS verschluesseln:** `RVS_HTTP_TLS=true` — der RVS spricht +dann HTTPS auf `RVS_HTTP_PORT`, mit einem selbstsignierten Zertifikat (beim +ersten Start in `rvs/data/rvs-tls/` erzeugt; eigenes `rvs.crt`/`rvs.key` darf +dort liegen). Im Proxy als Ziel `https://:`, das Zertifikat dabei +**nicht** pruefen (nginx: Default; Nginx Proxy Manager: Scheme `https`, +„Websockets Support" an). Nur ohne Caddy — der spricht den RVS per HTTP an. + +Umstellen von Caddy: `docker compose rm -sf caddy && docker compose up -d`. nginx-Beispiel (WebSocket braucht HTTP/1.1 + Upgrade-Header und lange Timeouts): @@ -891,7 +900,7 @@ server { ssl_certificate_key /etc/letsencrypt/live/rvs.example.de/privkey.pem; location / { - proxy_pass http://192.168.1.10:3000; # RVS_HTTP_BIND-IP : RVS_HTTP_PORT + proxy_pass http://192.168.1.10:3000; # RVS_HTTP_BIND-IP : RVS_HTTP_PORT (https:// bei RVS_HTTP_TLS=true) proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; diff --git a/rvs/.env.example b/rvs/.env.example index 17f31a3..a91f826 100644 --- a/rvs/.env.example +++ b/rvs/.env.example @@ -27,3 +27,9 @@ CADDY_HTTPS_PORT=444 # den Proxy freigeben (Klartext!). RVS_HTTP_BIND=127.0.0.1 RVS_HTTP_PORT=3000 +# Auch die Strecke Proxy → RVS verschluesseln: der RVS spricht dann HTTPS +# auf RVS_HTTP_PORT, mit selbstsigniertem Zertifikat (wird beim ersten Start +# in ./data/rvs-tls/ erzeugt; eigenes rvs.crt/rvs.key darf dort liegen). +# Im Proxy dann https://: als Ziel, Zertifikat NICHT pruefen. +# Nur ohne Caddy — der Caddy im Stack spricht den RVS per HTTP an. +RVS_HTTP_TLS=false diff --git a/rvs/Dockerfile b/rvs/Dockerfile index bc8f8d3..a1de8b0 100644 --- a/rvs/Dockerfile +++ b/rvs/Dockerfile @@ -2,6 +2,9 @@ FROM node:22-alpine WORKDIR /app +# openssl: erzeugt bei RVS_HTTP_TLS=true das interne Zertifikat +RUN apk add --no-cache openssl + # Erst package.json kopieren — Docker-Cache für npm install nutzen COPY package.json ./ RUN npm install --production diff --git a/rvs/docker-compose.yml b/rvs/docker-compose.yml index 55a4374..2e0593b 100644 --- a/rvs/docker-compose.yml +++ b/rvs/docker-compose.yml @@ -28,7 +28,9 @@ services: - "${RVS_HTTP_BIND:-127.0.0.1}:${RVS_HTTP_PORT:-3000}:3000" volumes: - ./updates:/updates # APK-Dateien fuer Auto-Update + - ./data/rvs-tls:/certs # internes Zertifikat (RVS_HTTP_TLS) environment: + - RVS_HTTP_TLS=${RVS_HTTP_TLS:-false} - MAX_SESSIONS=10 # 4 GB V8-Heap — sonst OOM beim Empfang von 1 GB-Files # (base64 inflated ~1.34 GB plus WS-Frame-Margin). diff --git a/rvs/server.js b/rvs/server.js index c3f7b50..bdc5a73 100644 --- a/rvs/server.js +++ b/rvs/server.js @@ -2,6 +2,8 @@ const { WebSocketServer } = require("ws"); const http = require("http"); +const https = require("https"); +const { execFileSync } = require("child_process"); const fs = require("fs"); const path = require("path"); @@ -9,6 +11,12 @@ const path = require("path"); const PORT = parseInt(process.env.PORT || "3000", 10); const MAX_SESSIONS = parseInt(process.env.MAX_SESSIONS || "10", 10); const UPDATES_DIR = process.env.UPDATES_DIR || "/updates"; +// HTTPS statt HTTP auf PORT — fuer die Strecke externer Reverse-Proxy → RVS. +// Das oeffentliche Zertifikat hat der Proxy; hier reicht ein selbstsigniertes +// (wird beim ersten Start erzeugt). NICHT zusammen mit dem Caddy im Stack +// (der spricht den RVS per HTTP an). +const HTTP_TLS = /^(1|true|yes|ja)$/i.test(process.env.RVS_HTTP_TLS || ""); +const TLS_DIR = process.env.RVS_TLS_DIR || "/certs"; // Kein Polling — APK wird manuell per git pull bereitgestellt // Erlaubte Nachrichtentypen — alles andere wird verworfen @@ -274,7 +282,25 @@ function cleanupRooms() { // WICHTIG: Node-Default-Heap ist ~1.5 GB. Fuer 1 GB-Files muss der // Container mit --max-old-space-size=4096 (oder NODE_OPTIONS env var) // gestartet werden, sonst OOM-Crash beim Empfang. -const httpServer = http.createServer(handleHttpRequest); +/** Zertifikat fuer den internen HTTPS-Port: vorhandenes aus TLS_DIR nehmen + * (eigenes darf man dort ablegen), sonst einmalig selbstsigniert erzeugen. */ +function loadInternalTls() { + const key = path.join(TLS_DIR, "rvs.key"); + const crt = path.join(TLS_DIR, "rvs.crt"); + if (!fs.existsSync(key) || !fs.existsSync(crt)) { + fs.mkdirSync(TLS_DIR, { recursive: true }); + execFileSync("openssl", ["req", "-x509", "-newkey", "rsa:2048", "-nodes", "-sha256", + "-days", "3650", "-subj", "/CN=aria-rvs-intern", "-keyout", key, "-out", crt], + { stdio: "ignore" }); + fs.chmodSync(key, 0o600); + log(`Internes TLS-Zertifikat erzeugt: ${crt} (selbstsigniert, 10 Jahre)`); + } + return { key: fs.readFileSync(key), cert: fs.readFileSync(crt) }; +} + +const httpServer = HTTP_TLS + ? https.createServer(loadInternalTls(), handleHttpRequest) + : http.createServer(handleHttpRequest); const wss = new WebSocketServer({ noServer: true, maxPayload: 1500 * 1024 * 1024 }); // HTTP-Upgrade-Pfad → an WebSocket-Server reichen @@ -285,7 +311,7 @@ httpServer.on("upgrade", (req, socket, head) => { }); httpServer.listen(PORT, () => { - log(`RVS läuft auf Port ${PORT} (HTTP + WS) | Max Sessions: ${MAX_SESSIONS}`); + log(`RVS läuft auf Port ${PORT} (${HTTP_TLS ? "HTTPS + WSS, internes Zertifikat" : "HTTP + WS"}) | Max Sessions: ${MAX_SESSIONS}`); // Beim Start pruefen ob eine APK da ist const apkInfo = getLatestAPK(); if (apkInfo) log(`APK bereit: v${apkInfo.version} (${(fs.statSync(apkInfo.path).size / 1024 / 1024).toFixed(1)}MB)`);