# 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://`) → 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`: ```js 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:///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`: ```js // 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:8444` → `ws://host:8444/...`) **als auch** hinter HTTPS (`https://cam.example.com` → `wss://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: ```js 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: ```js 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:** ```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): ```caddy 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] … 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://: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`.