Files
appRobotWebcam/doc/06_portForwarding_roadmap.md
2026-06-04 17:47:58 +02:00

7.2 KiB
Raw Permalink Blame History

AppRobotWebcam Port-Forwarding / HTTPS-Proxy Roadmap

Ziel: Die Viewer-Webseite über einen HTTPS-Reverse-Proxy nach aussen geben, ohne go2rtc oder interne Ports ins Internet zu hängen. Stand: 2026-06-04 · noch nicht umgesetzt (Plan zum Abarbeiten).


TL;DR — welche Ports nach aussen?

Port Dienst Internet? Begründung
443 HTTPS-Reverse-Proxy ja einziger Internet-Port TLS-Terminierung + einziger Einstieg
8444 Node.js (Webseite, /api, Snapshots, Stream-WS) nur Proxy→Backend (LAN/localhost) Proxy leitet hierhin weiter; nicht ins Internet
1984 go2rtc API / WebSocket / Debug-UI nein bleibt intern (localhost)
8555/udp go2rtc WebRTC-Media nein im aktuellen MJPEG-Modus ungenutzt (siehe Caveat unten)

Merksatz: Nach Umsetzung dieser Roadmap ist der einzige offene Port 443 am Proxy. Alles andere läuft über genau eine Origin (https://<host>) → Proxy → 8444.

network_mode: host: Die Container binden direkt an Host-Ports — es gibt kein Docker-ports:-Mapping. „Offen/zu" steuerst du allein über die Host-Firewall. Läuft der Proxy auf demselben Host, muss 8444 gar nicht in der Firewall geöffnet werden (Proxy erreicht 127.0.0.1:8444).


Aktueller Stand (was läuft)

  • Live-Bild kommt als MJPEG über WebSocket (MODE = 'mjpeg', public/viewer.js:9). Die WebRTC-Kommentare im docker-compose.yaml beschreiben einen anderen Aufbau, der nicht aktiv ist → UDP 8555 wird derzeit nicht gebraucht.
  • Node.js (8444) liefert: Webseite, /config.json, /health, /api/snapshot/* und proxied /api, /video-rtc.js, /video-stream.js per HTTP an go2rtc (server.js:34).
  • Snapshots und Skripte laufen bereits relativ/same-origin über 8444 → proxy-tauglich.

Das Problem (warum es so noch nicht hinter HTTPS läuft)

Der Viewer baut die Stream-Verbindung aktuell direkt zu go2rtc auf — public/viewer.js:36:

const wsUrl = `ws://${location.hostname}:${GO2RTC_PORT}/api/ws?src=...`;  // GO2RTC_PORT = 1984

Hinter einem HTTPS-Proxy scheitert das doppelt:

  1. Mixed Content — eine https://-Seite darf kein unverschlüsseltes ws:// öffnen → Browser blockt die Verbindung hart.
  2. Proxy-Umgehung — die URL zeigt direkt auf Port 1984, den wir bewusst nicht exponieren (dort hängt auch die offene go2rtc-Debug-UI).

→ Folge: Seite lädt, aber kein Live-Bild.


Lösung — 3 Schritte

Danach geht der Stream über dieselbe Origin wie die Seite (wss://<host>/api/ws), durch den Proxy, auf 8444, intern weiter zu go2rtc 1984.

Schritt 1 — public/viewer.js: same-origin & protokoll-bewusste WS-URL

startStream(), aktuell viewer.js:36:

// ALT:
const wsUrl = `ws://${location.hostname}:${GO2RTC_PORT}/api/ws?src=${encodeURIComponent(cam.id)}`;

// NEU:
const proto  = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl  = `${proto}//${location.host}/api/ws?src=${encodeURIComponent(cam.id)}`;
  • location.host enthält ggf. den Port → funktioniert sowohl direkt im LAN (http://host:8444ws://host:8444/...) als auch hinter HTTPS (https://cam.example.comwss://cam.example.com/...).
  • /api/ws ist im Proxy-pathFilter bereits enthalten (server.js:37).
  • GO2RTC_PORT / der /config.json-Fetch in init() werden für den Stream damit nicht mehr gebraucht (dürfen als harmloser Toter Code bleiben oder raus).

Schritt 2 — server.js: Proxy WebSockets durchreichen lassen

Im createProxyMiddleware-Block (server.js:34) ws: true ergänzen:

const go2rtcProxy = createProxyMiddleware({
  target:       GO2RTC_URL,
  changeOrigin: true,
  ws:           true,                                   // ← NEU
  pathFilter:   ['/api', '/video-rtc.js', '/video-stream.js'],
  logger:       console,
  on: { /* … unverändert … */ },
});

Und nach dem Erstellen des HTTP-Servers (server.js:113) den Upgrade-Handler binden:

const server = http.createServer(app);
server.on('upgrade', go2rtcProxy.upgrade);             // ← NEU (WebSocket-Upgrades an go2rtc)

http-proxy-middleware@^3 (vorhanden, package.json): ws: true plus der explizite server.on('upgrade', …) ist die dokumentierte, zuverlässige Kombination.

Schritt 3 — Reverse-Proxy: WebSocket-Upgrade durchreichen

nginx:

server {
  listen 443 ssl;
  server_name cam.example.com;
  # ssl_certificate … / ssl_certificate_key …;

  location / {
    proxy_pass         http://127.0.0.1:8444;
    proxy_http_version 1.1;
    proxy_set_header   Upgrade    $http_upgrade;        # ← Pflicht für WS
    proxy_set_header   Connection "upgrade";            # ← Pflicht für WS
    proxy_set_header   Host       $host;
    proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header   X-Forwarded-Proto $scheme;
    proxy_read_timeout 3600s;                           # WS-Stream offen halten
  }
}

Synology DSM (Systemsteuerung → Anmeldeportal → Erweitert → Reverse Proxy):

  1. Regel anlegen: Quelle HTTPS / cam.example.com / 443 → Ziel HTTP / localhost / 8444.
  2. In der Regel → Reiter Eigene Kopfzeile (Custom Header)Erstellen → WebSocket (fügt Upgrade/Connection automatisch hinzu). Ohne diesen Schritt kommt kein Bild.
  3. Optional Timeout erhöhen, damit der Stream nicht nach kurzer Zeit getrennt wird.

Caddy (zur Referenz — WS geht automatisch):

cam.example.com {
  reverse_proxy 127.0.0.1:8444
}

Verifikation (nach Deploy abhaken)

  • https://cam.example.com/ lädt, Header + Kamera-Kacheln sichtbar.
  • DevTools → Console: [WebcamViewer][init] … <video-stream> definiert, kein Mixed-Content-Fehler.
  • DevTools → Network → Filter „WS": Eintrag wss://cam.example.com/api/ws?src=cam0 mit Status 101 Switching Protocols (nicht 4xx/blocked).
  • Live-Bild für cam0 und cam1 läuft; Status zeigt „MJPEG · live".
  • „⬇ Snapshot alle" lädt JPG(s) herunter (/api/snapshot/... über Proxy).
  • Von aussen: nur 443 erreichbar. 1984, 8444, 8555 aus dem Internet nicht erreichbar (z. B. curl https://<public>:1984 → Timeout/refused).

Rollback

Kleine, isolierte Änderung in genau zwei Dateien — risikoarm:

  1. public/viewer.js und server.js auf den alten Stand zurück (git).
  2. docker restart AppRobotWebcam (lädt Code neu).

Der Live-Stream im LAN funktioniert mit der neuen viewer.js weiterhin (ws://host:8444/...), d. h. die Änderung ist auch ohne Proxy gefahrlos.


Caveat — falls je wieder WebRTC (MODE mit webrtc)

Wird public/viewer.js jemals zurück auf MODE = 'webrtc,mse,mjpeg' gestellt (siehe doc/04_Delay_roadmap.md), ändert sich die Port-Lage:

  • WebRTC-Signaling läuft weiter über die WebSocket (/api/ws) → durch den Proxy ok.
  • WebRTC-Media läuft über UDP 8555 und geht nicht durch einen HTTP(S)-Proxy. Dann bräuchte man entweder UDP 8555 direkt erreichbar oder einen TURN-Server.

Für den aktuellen MJPEG-Betrieb ist das irrelevant — alles läuft über TCP/WS auf 8444.