diff --git a/DOKPLOY_DEPLOY.md b/DOKPLOY_DEPLOY.md index 0d93509..ab4a1cd 100644 --- a/DOKPLOY_DEPLOY.md +++ b/DOKPLOY_DEPLOY.md @@ -17,7 +17,9 @@ The bot has been updated to support automated login using environment variables. * `OPENROUTER_API_KEY`: Your OpenRouter API key. * `REDDIT_USERNAME`: Your Reddit username. * `REDDIT_PASSWORD`: Your Reddit password. - * `BROWSER_HEADLESS`: `true` (Important for server environments). + * `BROWSER_HEADLESS`: `false` (required for interactive noVNC login). + * `VNC_PASSWORD`: A strong password for the interactive VNC session. + * `NOVNC_PUBLIC_URL`: `http://:16080`. 3. **Volumes (Persistence)**: * The `docker-compose.yml` is already configured to mount a `./data` volume. @@ -28,6 +30,19 @@ The bot has been updated to support automated login using environment variables. * Push your code to your Git repository connected to Dokploy. * Click "Deploy". +## noVNC networking + +The Compose configuration publishes noVNC directly on host port `16080`. +Do not route it through a Dokploy/Traefik domain: the upstream proxy strips +the WebSocket Upgrade request that noVNC requires, producing `rfb.js:688` with +WebSocket code `1011` and “Failed to connect to downstream server”. + +Set `NOVNC_PUBLIC_URL` to `http://:16080`, then open that URL in +a separate tab from the dashboard. If the dashboard itself is HTTPS, browsers +will not embed this HTTP endpoint in its iframe; the separate-tab link is the +intended route. Restrict inbound TCP `16080` at the Dokploy host firewall to +your own IP or VPN subnet. + ## How it works 1. On the first run, the bot will see the `data/browser-session` folder is empty/invalid. diff --git a/docker-compose.yml b/docker-compose.yml index 79a9db0..d618adf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,11 +15,14 @@ services: # path on every restart. volumes: - reddit_bot_data:/app/data - # Ports are NOT published to the host. Dokploy/Traefik routes traffic - # to the container's internal ports via configured domains. + # The dashboard goes through Traefik. noVNC must bypass the upstream + # proxy because it strips WebSocket Upgrade requests needed by websockify. + # Restrict host port 16080 with the host firewall; VNC_PASSWORD still + # authenticates the VNC session. + ports: + - "16080:6080" expose: - "3000" # Web dashboard - - "6080" # noVNC for human login environment: - OPENROUTER_API_KEY=${OPENROUTER_API_KEY} # Required whenever the web dashboard is enabled; never commit its value. @@ -30,8 +33,9 @@ services: - BROWSER_HEADLESS=false # Required to lock down the VNC endpoint. - VNC_PASSWORD=${VNC_PASSWORD} - # Public Traefik route used by the dashboard's recovery button. - - NOVNC_PUBLIC_URL=https://red-vnc.apps.jbrunton.co.uk + # Direct noVNC URL, e.g. http://:16080. It must not go + # through the WebSocket-stripping reverse proxy. + - NOVNC_PUBLIC_URL=${NOVNC_PUBLIC_URL} volumes: reddit_bot_data: diff --git a/tests/docker-compose.test.ts b/tests/docker-compose.test.ts new file mode 100644 index 0000000..bbef4bf --- /dev/null +++ b/tests/docker-compose.test.ts @@ -0,0 +1,12 @@ +import { readFile } from 'node:fs/promises'; +import { describe, expect, it } from 'vitest'; + +describe('Dokploy noVNC deployment configuration', () => { + it('publishes noVNC directly and leaves its public URL deployment-configured', async () => { + const compose = await readFile(new URL('../docker-compose.yml', import.meta.url), 'utf8'); + + expect(compose).toMatch(/ports:\s*\r?\n\s*- "16080:6080"/); + expect(compose).toMatch(/NOVNC_PUBLIC_URL=\$\{NOVNC_PUBLIC_URL\}/); + expect(compose).not.toContain('https://red-vnc.apps.jbrunton.co.uk'); + }); +});