chore(qa): add no-sudo headless Chromium provisioning script (#8349)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Visual QA and screenshot-based review depend on a working headless browser in agent sandboxes. > - Playwright can provide the Chromium binary, but some sandboxes lack the host shared libraries that Chromium needs to launch. > - Those sandboxes often do not have sudo, so `playwright install-deps` cannot install system packages. > - This pull request adds a user-space provisioning script for the Chromium runtime dependency closure. > - The benefit is that QA can bootstrap a render-capable Chromium environment without requiring root access or changing app runtime code. ## Linked Issues or Issue Description No public issue exists for this exact QA provisioning gap. Problem: in no-sudo Linux sandboxes, Playwright's bundled Chromium may fail to launch because the host is missing required shared libraries such as libatk, libcups, libgbm, libpango, and libasound. Expected behavior: QA agents should be able to provision a local prefix that lets Playwright launch bundled Chromium headlessly and render text without root access. Related public PR: #7301 covers broader runtime tooling/dependency setup, but this PR is a focused userspace script for the no-sudo Chromium dependency case. ## What Changed - Added `scripts/qa/provision-headless-chromium.sh`. - The script downloads the dependency closure for Playwright's Ubuntu 24.04 Chromium runtime packages with `apt-get download`, extracts the `.deb` files into a user-space prefix, and emits an env file that sets `LD_LIBRARY_PATH` and `FONTCONFIG_FILE`. - The script also configures extracted fonts and an isolated font cache so headless rendering paints text. ## Verification - Ran `bash -n scripts/qa/provision-headless-chromium.sh`. - The handoff verification states the script was run from a clean prefix on Ubuntu 24.04 arm64 as a non-root user, then Chromium 145 launched headless and rendered text at 1440x900 and 390x844 in light and dark mode. ## Risks Low risk. This adds a standalone QA utility script and does not modify application runtime, package manifests, CI configuration, migrations, or production code. ## Model Used OpenAI GPT-5 Codex, tool-enabled coding agent with shell and GitHub CLI access. The original script was prepared by an agent workflow; Git Expert amended public-facing references and prepared the PR. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Executable
+128
@@ -0,0 +1,128 @@
|
||||
#!/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; }
|
||||
|
||||
for cmd in apt-cache apt-get dpkg-deb find grep sort paste; do
|
||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||
echo "ERROR: required command not found: $cmd" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# 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.
|
||||
# NOTE: when bumping @playwright/test, re-derive this list from
|
||||
# node_modules/playwright-core/lib/server/registry/nativeDeps.js (ubuntu24.04
|
||||
# chromium entry) and update the version comment above.
|
||||
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."
|
||||
if [ "${#CLOSURE[@]}" -eq 0 ]; then
|
||||
echo "ERROR: dependency closure is empty -- apt package index may be unpopulated. Run 'apt-get update' and retry." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Downloading .debs into $DEBS (no root required)..."
|
||||
(
|
||||
cd "$DEBS"
|
||||
dl=0; skip=0
|
||||
for p in "${CLOSURE[@]}"; do
|
||||
if compgen -G "${p}_*.deb" >/dev/null 2>&1; 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)..."
|
||||
(
|
||||
shopt -s nullglob
|
||||
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" <<EOF
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<dir>$ROOT/usr/share/fonts</dir>
|
||||
<dir>$ROOT/usr/local/share/fonts</dir>
|
||||
<cachedir>$FCCACHE</cachedir>
|
||||
<include ignore_missing="yes">$ROOT/etc/fonts/conf.d</include>
|
||||
</fontconfig>
|
||||
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" <<EOF
|
||||
# Source this to make Playwright's bundled Chromium launchable. Generated by
|
||||
# scripts/qa/provision-headless-chromium.sh.
|
||||
export LD_LIBRARY_PATH="$LIBDIRS\${LD_LIBRARY_PATH:+:\$LD_LIBRARY_PATH}"
|
||||
export FONTCONFIG_FILE="$FONTS_CONF"
|
||||
EOF
|
||||
|
||||
log "Done."
|
||||
log "Activate with: source \"$ENV_FILE\""
|
||||
echo "$ENV_FILE"
|
||||
Reference in New Issue
Block a user