diff --git a/Dockerfile b/Dockerfile index 1a89800c7..9a569477e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,9 +43,17 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libjpeg-dev \ redis-server \ supervisor \ - && apt-get clean \ + xvfb \ + x11vnc \ + fluxbox \ + websockify \ + && apt-get clean \ && rm -rf /var/lib/apt/lists/* +# Install noVNC for browser-based VNC access +RUN git clone --depth 1 https://github.com/novnc/noVNC /opt/novnc \ + && git clone --depth 1 https://github.com/novnc/websockify /opt/novnc/utils/websockify + RUN apt-get update && apt-get install -y --no-install-recommends \ libglib2.0-0 \ libnss3 \ diff --git a/deploy/docker/README.md b/deploy/docker/README.md index a0273f978..9f025dfd8 100644 --- a/deploy/docker/README.md +++ b/deploy/docker/README.md @@ -17,6 +17,7 @@ - [Screenshot Endpoint](#screenshot-endpoint) - [PDF Export Endpoint](#pdf-export-endpoint) - [JavaScript Execution Endpoint](#javascript-execution-endpoint) + - [Browser VNC Endpoint](#browser-vnc-endpoint) - [Library Context Endpoint](#library-context-endpoint) - [MCP (Model Context Protocol) Support](#mcp-model-context-protocol-support) - [What is MCP?](#what-is-mcp) @@ -377,6 +378,20 @@ Executes JavaScript snippets on the specified URL and returns the full crawl res - `scripts`: List of JavaScript snippets to execute sequentially +### Browser VNC Endpoint + +``` +GET /vnc +``` + +Opens a browser-based VNC session for interacting with the container's desktop environment. Use `/vnc/url` to retrieve only the iframe URL. + +``` +GET /vnc/url +``` + +Returns a JSON object containing the URL of the embedded noVNC client. + --- ## Dockerfile Parameters diff --git a/deploy/docker/server.py b/deploy/docker/server.py index 0bd6ac2d1..c76ecb80c 100644 --- a/deploy/docker/server.py +++ b/deploy/docker/server.py @@ -33,7 +33,11 @@ ) from utils import ( - FilterType, load_config, setup_logging, verify_email_domain + FilterType, + load_config, + setup_logging, + verify_email_domain, + get_base_url, ) import os import sys @@ -48,7 +52,11 @@ ) from rank_bm25 import BM25Okapi from fastapi.responses import ( - StreamingResponse, RedirectResponse, PlainTextResponse, JSONResponse + StreamingResponse, + RedirectResponse, + PlainTextResponse, + JSONResponse, + HTMLResponse, ) from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware from fastapi.middleware.trustedhost import TrustedHostMiddleware @@ -129,11 +137,31 @@ async def lifespan(_: FastAPI): name="play", ) +# Serve noVNC static files if available +VNC_DIR = pathlib.Path("/opt/novnc") +if VNC_DIR.exists(): + app.mount("/novnc", StaticFiles(directory=VNC_DIR, html=True), name="novnc") + @app.get("/") async def root(): return RedirectResponse("/playground") + +@app.get("/vnc") +async def vnc_page(request: Request): + """Return a simple page embedding the noVNC client.""" + url = f"{get_base_url(request)}/novnc/vnc.html?autoconnect=true&resize=scale" + html = f"" + return HTMLResponse(f"{html}") + + +@app.get("/vnc/url") +async def vnc_url(request: Request): + """Return the direct URL to the noVNC client.""" + url = f"{get_base_url(request)}/novnc/vnc.html?autoconnect=true&resize=scale" + return {"url": url} + # ─────────────────── infra / middleware ───────────────────── redis = aioredis.from_url(config["redis"].get("uri", "redis://localhost")) diff --git a/deploy/docker/supervisord.conf b/deploy/docker/supervisord.conf index a1b994aa2..3e5a1fd6f 100644 --- a/deploy/docker/supervisord.conf +++ b/deploy/docker/supervisord.conf @@ -20,9 +20,53 @@ user=appuser ; Run gunicorn as our non-root user autorestart=true priority=20 environment=PYTHONUNBUFFERED=1 ; Ensure Python output is sent straight to logs +environment=DISPLAY=:99 stdout_logfile=/dev/stdout ; Redirect gunicorn stdout to container stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr ; Redirect gunicorn stderr to container stderr stderr_logfile_maxbytes=0 +[program:xvfb] +command=/usr/bin/Xvfb :99 -screen 0 1280x720x24 +user=appuser +autorestart=true +priority=5 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:fluxbox] +command=/usr/bin/fluxbox +user=appuser +autorestart=true +priority=6 +environment=DISPLAY=:99 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:x11vnc] +command=/usr/bin/x11vnc -display :99 -nopw -forever -shared -rfbport 5900 -quiet +user=appuser +autorestart=true +priority=7 +environment=DISPLAY=:99 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:websockify] +command=/usr/bin/websockify 6080 localhost:5900 --web /opt/novnc +user=appuser +autorestart=true +priority=8 +environment=DISPLAY=:99 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + # Optional: Add filebeat or other logging agents here if needed \ No newline at end of file