From 194f427165181f452d035c4ef18aa4c4eaf4862e Mon Sep 17 00:00:00 2001 From: hiroTamada Date: Mon, 9 Mar 2026 16:16:14 -0400 Subject: [PATCH] feat: add interactive live view to headless Chromium image Make Chromium render to Xvfb instead of using offscreen headless mode so that X11-based tools (screenshots, recordings, xdotool, xclip) and a new browser-based live view all work. Changes: - Remove --headless flag from chromium-launcher supervisor config - Remove --ozone-platform=headless, --disable-software-rasterizer, and --hide-scrollbars from default CHROMIUM_FLAGS - Add --window-size and --window-position flags so Chromium fills the Xvfb display - Install x11vnc, novnc, and websockify packages - Add supervisor configs for x11vnc and noVNC (websockify on port 8080) - Add custom minimal noVNC client (index.html) with auto-connect, scaling, and no UI chrome - Gate live view behind ENABLE_LIVE_VIEW=true env var - Expose port 8080 in run-docker.sh for parity with headful image Made-with: Cursor --- images/chromium-headless/image/Dockerfile | 8 +++- .../chromium-headless/image/novnc-view.html | 48 +++++++++++++++++++ .../image/supervisor/services/chromium.conf | 2 +- .../image/supervisor/services/novnc.conf | 7 +++ .../image/supervisor/services/x11vnc.conf | 7 +++ images/chromium-headless/image/wrapper.sh | 23 +++++++-- images/chromium-headless/run-docker.sh | 2 + 7 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 images/chromium-headless/image/novnc-view.html create mode 100644 images/chromium-headless/image/supervisor/services/novnc.conf create mode 100644 images/chromium-headless/image/supervisor/services/x11vnc.conf diff --git a/images/chromium-headless/image/Dockerfile b/images/chromium-headless/image/Dockerfile index 7be9610e..cc4f55c9 100644 --- a/images/chromium-headless/image/Dockerfile +++ b/images/chromium-headless/image/Dockerfile @@ -102,6 +102,8 @@ ARG TARGETARCH ARG TARGETOS ARG CACHEIDPREFIX=${TARGETOS:-linux}-${TARGETARCH:-amd64}-ubuntu2204 +ENV DEBIAN_FRONTEND=noninteractive + RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-apt-cache \ --mount=type=cache,target=/var/lib/apt,sharing=locked,id=$CACHEIDPREFIX-apt-lib \ set -xe; \ @@ -143,7 +145,10 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-ap fonts-noto-color-emoji \ fonts-nanum \ software-properties-common \ - supervisor; \ + supervisor \ + x11vnc \ + novnc \ + websockify; \ fc-cache -f # install chromium and sqlite3 for debugging the cookies file @@ -223,6 +228,7 @@ COPY images/chromium-headless/image/wrapper.sh /usr/bin/wrapper.sh # Supervisord configuration COPY images/chromium-headless/image/supervisord.conf /etc/supervisor/supervisord.conf COPY images/chromium-headless/image/supervisor/services/ /etc/supervisor/conf.d/services/ +COPY images/chromium-headless/image/novnc-view.html /usr/share/novnc/index.html COPY shared/envoy/supervisor-envoy.conf /etc/supervisor/conf.d/services/envoy.conf # Install Envoy proxy diff --git a/images/chromium-headless/image/novnc-view.html b/images/chromium-headless/image/novnc-view.html new file mode 100644 index 00000000..78e69ebe --- /dev/null +++ b/images/chromium-headless/image/novnc-view.html @@ -0,0 +1,48 @@ + + + + Live View + + + + + + + + + diff --git a/images/chromium-headless/image/supervisor/services/chromium.conf b/images/chromium-headless/image/supervisor/services/chromium.conf index 7018e12a..ae1f22e9 100644 --- a/images/chromium-headless/image/supervisor/services/chromium.conf +++ b/images/chromium-headless/image/supervisor/services/chromium.conf @@ -1,5 +1,5 @@ [program:chromium] -command=/usr/local/bin/chromium-launcher --headless +command=/usr/local/bin/chromium-launcher autostart=false autorestart=true startsecs=0 diff --git a/images/chromium-headless/image/supervisor/services/novnc.conf b/images/chromium-headless/image/supervisor/services/novnc.conf new file mode 100644 index 00000000..efb95099 --- /dev/null +++ b/images/chromium-headless/image/supervisor/services/novnc.conf @@ -0,0 +1,7 @@ +[program:novnc] +command=websockify --web /usr/share/novnc 8080 localhost:5900 +autostart=false +autorestart=true +startsecs=2 +stdout_logfile=/var/log/supervisord/novnc +redirect_stderr=true diff --git a/images/chromium-headless/image/supervisor/services/x11vnc.conf b/images/chromium-headless/image/supervisor/services/x11vnc.conf new file mode 100644 index 00000000..908a93a6 --- /dev/null +++ b/images/chromium-headless/image/supervisor/services/x11vnc.conf @@ -0,0 +1,7 @@ +[program:x11vnc] +command=x11vnc -display :1 -forever -shared -nopw -rfbport 5900 -noxdamage +autostart=false +autorestart=true +startsecs=2 +stdout_logfile=/var/log/supervisord/x11vnc +redirect_stderr=true diff --git a/images/chromium-headless/image/wrapper.sh b/images/chromium-headless/image/wrapper.sh index 7faff130..d09c0ff4 100755 --- a/images/chromium-headless/image/wrapper.sh +++ b/images/chromium-headless/image/wrapper.sh @@ -79,6 +79,10 @@ export HOSTNAME="${HOSTNAME:-kernel-vm}" # NOTE: --disable-background-networking was intentionally removed because it prevents # Chrome from fetching extensions via ExtensionInstallForcelist enterprise policy. # Enterprise extensions require Chrome to make HTTP requests to fetch update.xml and .crx files. +# +# Chromium renders to Xvfb (DISPLAY=:1) so that screenshots, recordings, xdotool +# input, and live view all work. Flags like --ozone-platform=headless and +# --disable-software-rasterizer are intentionally absent. if [ -z "${CHROMIUM_FLAGS:-}" ]; then CHROMIUM_FLAGS="--accept-lang=en-US,en \ --allow-pre-commit-input \ @@ -106,24 +110,23 @@ if [ -z "${CHROMIUM_FLAGS:-}" ]; then --disable-prompt-on-repost \ --disable-renderer-backgrounding \ --disable-search-engine-choice-screen \ - --disable-software-rasterizer \ --enable-use-zoom-for-dsf=false \ --export-tagged-pdf \ --force-color-profile=srgb \ --hide-crash-restore-bubble \ - --hide-scrollbars \ --metrics-recording-only \ --mute-audio \ --no-default-browser-check \ --no-first-run \ --no-sandbox \ --no-service-autorun \ - --ozone-platform=headless \ --password-store=basic \ --unsafely-disable-devtools-self-xss-warnings \ --use-angle=swiftshader \ --use-gl=angle \ - --use-mock-keychain" + --use-mock-keychain \ + --window-size=${WIDTH:-1920},${HEIGHT:-1080} \ + --window-position=0,0" fi export CHROMIUM_FLAGS @@ -216,6 +219,8 @@ cleanup () { echo "[wrapper] Cleaning up..." # Re-enable scale-to-zero if the script terminates early enable_scale_to_zero + supervisorctl -c /etc/supervisor/supervisord.conf stop novnc || true + supervisorctl -c /etc/supervisor/supervisord.conf stop x11vnc || true supervisorctl -c /etc/supervisor/supervisord.conf stop chromedriver || true supervisorctl -c /etc/supervisor/supervisord.conf stop chromium || true supervisorctl -c /etc/supervisor/supervisord.conf stop xvfb || true @@ -274,6 +279,16 @@ echo "[wrapper] Starting ChromeDriver via supervisord" supervisorctl -c /etc/supervisor/supervisord.conf start chromedriver wait_for_tcp_port 127.0.0.1 9225 "ChromeDriver" 50 0.2 "10s" || true +if [[ "${ENABLE_LIVE_VIEW:-}" == "true" ]]; then + echo "[wrapper] Starting x11vnc via supervisord" + supervisorctl -c /etc/supervisor/supervisord.conf start x11vnc + wait_for_tcp_port 127.0.0.1 5900 "x11vnc" 50 0.2 "10s" || true + + echo "[wrapper] Starting noVNC via supervisord" + supervisorctl -c /etc/supervisor/supervisord.conf start novnc + wait_for_tcp_port 127.0.0.1 8080 "noVNC" 50 0.2 "10s" || true +fi + echo "[wrapper] startup complete!" # Re-enable scale-to-zero once startup has completed (when not under Docker) if [[ -z "${WITHDOCKER:-}" ]]; then diff --git a/images/chromium-headless/run-docker.sh b/images/chromium-headless/run-docker.sh index 56f582bf..303ea32d 100755 --- a/images/chromium-headless/run-docker.sh +++ b/images/chromium-headless/run-docker.sh @@ -17,7 +17,9 @@ RUN_ARGS=( -p 9222:9222 -p 9224:9224 -p 444:10001 + -p 8080:8080 -v "$HOST_RECORDINGS_DIR:/recordings" + -e ENABLE_LIVE_VIEW="${ENABLE_LIVE_VIEW:-true}" ) if [[ -n "${PLAYWRIGHT_ENGINE:-}" ]]; then