From 92722cedd09a75cb5cfc0bcaf645101df0a2a134 Mon Sep 17 00:00:00 2001 From: Nicky Leach Date: Fri, 19 Jun 2026 23:09:52 +0000 Subject: [PATCH] chore(qa): add no-sudo headless Chromium provisioning script Provisions Playwright's bundled Chromium in sandboxes that lack root and are missing the Chromium host shared libraries. Downloads the .deb dependency closure, extracts it into a user-space prefix with dpkg-deb, and emits an env file wiring LD_LIBRARY_PATH plus fontconfig at the prefix so chromium.launch() works headless and text renders. Verified on Ubuntu 24.04 arm64 as a non-root user: Chromium 145 launches headless and renders text at 1440x900 and 390x844. Co-Authored-By: Paperclip --- scripts/qa/provision-headless-chromium.sh | 111 ++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100755 scripts/qa/provision-headless-chromium.sh diff --git a/scripts/qa/provision-headless-chromium.sh b/scripts/qa/provision-headless-chromium.sh new file mode 100755 index 00000000000..e11bae361d5 --- /dev/null +++ b/scripts/qa/provision-headless-chromium.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +# +# provision-headless-chromium.sh +# +# Make Playwright's bundled Chromium launchable in a sandbox that has NO root / +# sudo and is missing the Chromium host shared libraries (libatk, libcups, +# libgbm, libpango, libasound, ...). +# +# It does this entirely in user space: +# 1. download the .deb closure of Playwright's documented Chromium deps +# (apt-get download needs no root), +# 2. extract them with `dpkg-deb -x` into a prefix dir (no root), +# 3. emit an env file that points LD_LIBRARY_PATH + fontconfig at the prefix. +# +# After running, `source` the emitted env file in any shell that needs to drive +# Chromium (Playwright, screenshot tools, e2e harness) and chromium.launch() +# works headless. +# +# Verified on Ubuntu 24.04 (noble) arm64, uid!=0, no sudo. +# +# Usage: +# bash scripts/qa/provision-headless-chromium.sh [PREFIX_DIR] +# source "$PREFIX_DIR/pw-chromium-env.sh" +# +# Default PREFIX_DIR: $HOME/.cache/pw-chromium-deps +# +set -euo pipefail + +PREFIX="${1:-$HOME/.cache/pw-chromium-deps}" +ROOT="$PREFIX/root" +DEBS="$PREFIX/debs" +FCCACHE="$PREFIX/fontcache" +ENV_FILE="$PREFIX/pw-chromium-env.sh" + +log() { printf '\033[36m[provision]\033[0m %s\n' "$*" >&2; } + +# Playwright 1.58 documented Chromium runtime packages for ubuntu24.04 +# (playwright-core/lib/server/registry/nativeDeps.js -> ubuntu24.04 chromium + a +# few font packages so text actually paints). apt-cache --recurse expands the +# full transitive closure from these roots. +ROOTS=( + libasound2t64 libatk-bridge2.0-0t64 libatk1.0-0t64 libatspi2.0-0t64 + libcairo2 libcups2t64 libdbus-1-3 libdrm2 libgbm1 libglib2.0-0t64 + libnspr4 libnss3 libpango-1.0-0 libx11-6 libxcb1 libxcomposite1 + libxdamage1 libxext6 libxfixes3 libxkbcommon0 libxrandr2 + fonts-liberation fonts-dejavu-core fonts-noto-core fonts-freefont-ttf +) + +# dpkg-deb / apt tooling can be poisoned by a bundled LD_LIBRARY_PATH (e.g. +# embedded-postgres ships an older liblzma). Run all apt/dpkg with a clean one. +clean() { env -u LD_LIBRARY_PATH "$@"; } + +mkdir -p "$ROOT" "$DEBS" "$FCCACHE" + +log "Resolving dependency closure for ${#ROOTS[@]} root packages..." +mapfile -t CLOSURE < <(clean apt-cache depends --recurse --no-recommends \ + --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances \ + "${ROOTS[@]}" 2>/dev/null | grep '^\w' | sort -u) +log "Closure: ${#CLOSURE[@]} packages." + +log "Downloading .debs into $DEBS (no root required)..." +( + cd "$DEBS" + dl=0; skip=0 + for p in "${CLOSURE[@]}"; do + if [ -n "$(ls "${p}_"*.deb 2>/dev/null)" ]; then dl=$((dl+1)); continue; fi + if clean apt-get download "$p" >/dev/null 2>&1; then dl=$((dl+1)); else skip=$((skip+1)); fi + done + log "Downloaded/cached $dl, skipped $skip (virtual/unavailable)." +) + +log "Extracting into $ROOT (no root required)..." +for d in "$DEBS"/*.deb; do clean dpkg-deb -x "$d" "$ROOT" 2>/dev/null || true; done + +# Discover every directory that contains a shared object. +LIBDIRS="$(find "$ROOT" -name '*.so*' -type f -printf '%h\n' | sort -u | paste -sd:)" +if [ -z "$LIBDIRS" ]; then + echo "ERROR: no shared libraries extracted — check archive access." >&2 + exit 1 +fi + +# Fontconfig: the extracted fonts live under $ROOT, not /usr/share/fonts, so +# point a custom config at them and give it a writable cache dir. +FONTS_CONF="$PREFIX/fonts.conf" +cat > "$FONTS_CONF" < + + + $ROOT/usr/share/fonts + $ROOT/usr/local/share/fonts + $FCCACHE + $ROOT/etc/fonts/conf.d + +EOF + +# Pre-build the font cache if the bundled fc-cache is available. +FC_CACHE="$(find "$ROOT" -name fc-cache -type f 2>/dev/null | head -1 || true)" +if [ -n "$FC_CACHE" ]; then + env LD_LIBRARY_PATH="$LIBDIRS" FONTCONFIG_FILE="$FONTS_CONF" "$FC_CACHE" -f >/dev/null 2>&1 || true +fi + +cat > "$ENV_FILE" <