diff --git a/containers/agent/Dockerfile b/containers/agent/Dockerfile index 8685e4d50..6b3b46f5d 100644 --- a/containers/agent/Dockerfile +++ b/containers/agent/Dockerfile @@ -109,6 +109,42 @@ RUN set -eux; \ PKGS="iptables curl ca-certificates git gnupg dnsutils net-tools netcat-openbsd libcap2-bin libgdiplus libev-dev libssl-dev php-intl php-gd python3"; \ apt_update_retry && \ apt_install_retry $PKGS && \ + # Chromium/Playwright native runtime libraries. The agent uses selective bind + # mounts (not a full host filesystem mount), so Playwright-managed browsers + # cannot pick these up from the host /usr. Installing the packages here is not + # by itself sufficient either: AWF_CHROOT_ENABLED=true chroots to /host and + # buildSystemMounts() bind-mounts the runner's own /usr, /lib, etc. over the + # image's, shadowing anything installed in this layer. To make these libraries + # visible inside the chroot, we record which files they own in a manifest so + # entrypoint.sh can stage copies under /run/awf-lib (the container's own + # writable rootfs, not bind-mounted) and point LD_LIBRARY_PATH at them before + # exec'ing the user command. See copy_browser_libs() in entrypoint.sh. + BROWSER_PKGS="libasound2 libatk-bridge2.0-0 libatk1.0-0 libatspi2.0-0 libcairo2 libcups2 libdbus-1-3 libdrm2 libexpat1 libgbm1 libglib2.0-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libx11-6 libxcb1 libxcomposite1 libxdamage1 libxext6 libxfixes3 libxkbcommon0 libxrandr2 libxrender1 libxshmfence1 fonts-liberation"; \ + # Ubuntu 24.04 renamed several of these to time_t-64 variants (e.g. libasound2t64), + # and the old names became ambiguous virtual packages. Prefer the t64 package when + # the base image provides one, otherwise keep the 22.04 name. + BROWSER_ACTUAL_PKGS=""; \ + for pkg in $BROWSER_PKGS; do \ + if apt-cache show "${pkg}t64" >/dev/null 2>&1; then \ + BROWSER_ACTUAL_PKGS="$BROWSER_ACTUAL_PKGS ${pkg}t64"; \ + else \ + BROWSER_ACTUAL_PKGS="$BROWSER_ACTUAL_PKGS $pkg"; \ + fi; \ + done && \ + # Installed separately (rather than folded into $PKGS) so we can diff the + # dpkg selection list before/after and capture the *transitive* dependency + # closure (fontconfig, freetype, harfbuzz, ...) that apt pulls in automatically, + # not just the packages named above. + dpkg-query -W -f='${Package}\n' | sort -u > /tmp/pkgs-before-browser.txt && \ + apt_install_retry $BROWSER_ACTUAL_PKGS && \ + dpkg-query -W -f='${Package}\n' | sort -u > /tmp/pkgs-after-browser.txt && \ + comm -13 /tmp/pkgs-before-browser.txt /tmp/pkgs-after-browser.txt > /tmp/browser-pkgs-installed.txt && \ + mkdir -p /usr/local/share/awf && \ + dpkg -L $(cat /tmp/browser-pkgs-installed.txt) 2>/dev/null \ + | while IFS= read -r f; do [ -f "$f" ] && printf '%s\n' "$f"; done \ + | grep -E '\.so(\.[0-9]+)*$' \ + | sort -u > /usr/local/share/awf/browser-libs.manifest && \ + rm -f /tmp/pkgs-before-browser.txt /tmp/pkgs-after-browser.txt /tmp/browser-pkgs-installed.txt && \ apt_upgrade_retry && \ # Raw sockets are unavailable to the agent; remove stale file capabilities # inherited from runner-like base images so they cannot be reactivated. diff --git a/containers/agent/entrypoint.sh b/containers/agent/entrypoint.sh index 7d39dcb3a..b72555229 100644 --- a/containers/agent/entrypoint.sh +++ b/containers/agent/entrypoint.sh @@ -662,6 +662,55 @@ copy_preload_libs() { fi } +copy_browser_libs() { + # Stage the Chromium/Playwright runtime shared libraries baked into the image + # (containers/agent/Dockerfile: BROWSER_PKGS) so they are visible inside the + # chroot. Installing the packages into the image's /usr is not sufficient on + # its own: AWF_CHROOT_ENABLED=true chroots to /host, and buildSystemMounts() + # bind-mounts the runner's own /usr, /lib, etc. over the image's, shadowing + # anything installed in this layer. + # + # The Dockerfile records the manifest of .so files owned by those packages + # (including their transitive dependencies, e.g. fontconfig/freetype/harfbuzz) + # at /usr/local/share/awf/browser-libs.manifest. We copy each one, preserving + # its original directory structure, under /run/awf-lib/browser-libs -- which + # lives on the container's own writable rootfs (not bind-mounted) -- and point + # LD_LIBRARY_PATH at the resulting directories. + # + # Sets BROWSER_LD_LIBRARY_PATH (empty string if unavailable or nothing to stage). + BROWSER_LD_LIBRARY_PATH="" + MANIFEST="/usr/local/share/awf/browser-libs.manifest" + if [ -s "$MANIFEST" ]; then + STAGE_ROOT="/host/run/awf-lib/browser-libs" + if mkdir -p "$STAGE_ROOT" 2>/dev/null; then + LIB_DIRS="" + while IFS= read -r lib; do + [ -z "$lib" ] && continue + [ -f "$lib" ] || continue + dest="${STAGE_ROOT}${lib}" + dest_dir="$(dirname "$dest")" + if mkdir -p "$dest_dir" 2>/dev/null && cp -a "$lib" "$dest" 2>/dev/null; then + rel_dir="$(dirname "$lib")" + case " $LIB_DIRS " in + *" $rel_dir "*) ;; + *) LIB_DIRS="$LIB_DIRS $rel_dir" ;; + esac + else + echo "[entrypoint][WARN] Could not stage browser runtime library $lib" >&2 + fi + done < "$MANIFEST" + if [ -n "$LIB_DIRS" ]; then + for rel_dir in $LIB_DIRS; do + BROWSER_LD_LIBRARY_PATH="${BROWSER_LD_LIBRARY_PATH:+$BROWSER_LD_LIBRARY_PATH:}/run/awf-lib/browser-libs${rel_dir}" + done + echo "[entrypoint] Chromium/Playwright runtime libraries staged for chroot at ${STAGE_ROOT}" + fi + else + echo "[entrypoint][WARN] Could not create ${STAGE_ROOT}; Chromium/Playwright browsers may fail to launch" >&2 + fi + fi +} + copy_agent_helper_scripts() { # Copy get-claude-key.sh and gh CLI proxy wrapper to chroot-accessible paths. # Both scripts are baked into the Docker image but shadowed by host bind mounts @@ -1511,6 +1560,7 @@ run_chroot_command() { mount_host_cgroupfs check_chroot_prereqs copy_preload_libs + copy_browser_libs copy_agent_helper_scripts copy_dind_runner_binary ensure_usr_local_bin_shims @@ -1576,8 +1626,8 @@ run_chroot_command() { CLEANUP_CMD="${CLEANUP_CMD}; sed -i '/^[0-9.]\\+[[:space:]]\\+host\\.docker\\.internal\$/d' /etc/hosts 2>/dev/null || true" echo "[entrypoint] host.docker.internal will be removed from /etc/hosts on exit" fi - # Clean up /run/awf-lib if anything was copied (one-shot-token, CA cert, key helper) - if [ -n "${ONE_SHOT_TOKEN_LIB}" ] || [ -n "${AWF_CA_CHROOT}" ] || [ -n "${SYSTEM_CA_CHROOT}" ] || [ -n "${CHROOT_KEY_HELPER}" ] || [ -n "${STAGED_RUNNER_BINARY_CHROOT}" ]; then + # Clean up /run/awf-lib if anything was copied (one-shot-token, CA cert, key helper, browser libs) + if [ -n "${ONE_SHOT_TOKEN_LIB}" ] || [ -n "${AWF_CA_CHROOT}" ] || [ -n "${SYSTEM_CA_CHROOT}" ] || [ -n "${CHROOT_KEY_HELPER}" ] || [ -n "${STAGED_RUNNER_BINARY_CHROOT}" ] || [ -n "${BROWSER_LD_LIBRARY_PATH}" ]; then CLEANUP_CMD="${CLEANUP_CMD}; rm -rf /run/awf-lib 2>/dev/null || true" fi # NOTE: the /usr/local/bin overlay is torn down by cleanup_usr_local_bin_overlay(), @@ -1633,10 +1683,18 @@ run_chroot_command() { LD_PRELOAD_CMD="export LD_PRELOAD=${ONE_SHOT_TOKEN_LIB};" fi + # Build LD_LIBRARY_PATH command so Chromium/Playwright can resolve the + # staged browser runtime libraries (see copy_browser_libs above). + LD_LIBRARY_PATH_CMD="" + if [ -n "${BROWSER_LD_LIBRARY_PATH}" ]; then + LD_LIBRARY_PATH_CMD="export LD_LIBRARY_PATH=${BROWSER_LD_LIBRARY_PATH}\${LD_LIBRARY_PATH:+:\$LD_LIBRARY_PATH};" + fi + run_command_with_stdout run_agent_with_token_protection chroot /host /bin/bash -c " cd '${CHROOT_WORKDIR}' 2>/dev/null || cd / 2>/dev/null || true trap '${CLEANUP_CMD}' EXIT ${LD_PRELOAD_CMD} + ${LD_LIBRARY_PATH_CMD} exec capsh --drop=${CAPS_TO_DROP} ${CAPSH_IDENTITY_ARGS} -- -c 'exec ${SCRIPT_FILE}' " } diff --git a/docs-site/src/content/docs/reference/agent-images.md b/docs-site/src/content/docs/reference/agent-images.md index 982621b38..c77c285de 100644 --- a/docs-site/src/content/docs/reference/agent-images.md +++ b/docs-site/src/content/docs/reference/agent-images.md @@ -76,6 +76,11 @@ The default `agent` image (based on Ubuntu 22.04) includes the following pre-ins | libssl-dev | — | `libssl-dev` | OpenSSL development headers for native extensions | | php-intl | — | `php-intl` | PHP Internationalization extension | | php-gd | — | `php-gd` | PHP GD graphics extension | +| Chromium runtime libraries | — | see below | Shared libraries required by Playwright-managed browsers | + +:::note[Chromium/Playwright runtime libraries] +Because the agent uses selective bind mounts instead of a full host filesystem mount, browsers downloaded by Playwright cannot rely on host libraries. The image therefore preinstalls `libasound2`, `libatk-bridge2.0-0`, `libatk1.0-0`, `libatspi2.0-0`, `libcairo2`, `libcups2`, `libdbus-1-3`, `libdrm2`, `libexpat1`, `libgbm1`, `libglib2.0-0`, `libnspr4`, `libnss3`, `libpango-1.0-0`, `libpangocairo-1.0-0`, `libx11-6`, `libxcb1`, `libxcomposite1`, `libxdamage1`, `libxext6`, `libxfixes3`, `libxkbcommon0`, `libxrandr2`, `libxrender1`, `libxshmfence1`, and `fonts-liberation` (plus their transitive dependencies). On Ubuntu 24.04 base images the `t64` variants of these packages are installed automatically. In chroot mode (the default), these image-installed libraries would otherwise be shadowed by the host's own `/usr`, `/lib`, etc.; `entrypoint.sh` stages copies of the files under `/run/awf-lib/browser-libs` (on the container's own writable rootfs, unaffected by the chroot bind mounts) and sets `LD_LIBRARY_PATH` so Chromium resolves them there. +::: :::caution[Docker CLI Stub] The `docker` command is present but is a stub—there is no Docker daemon running inside the container. Docker-in-Docker is not supported. Use `--mount` to access Docker sockets from the host if needed. diff --git a/docs/agent-images.md b/docs/agent-images.md index d327f487f..57c5e2e0f 100644 --- a/docs/agent-images.md +++ b/docs/agent-images.md @@ -71,6 +71,9 @@ The `default` preset image (based on Ubuntu 22.04) includes the following pre-in | capsh | — | `libcap2-bin` | Capability management | | gnupg | 2.2.27 | `gnupg` | GPG encryption | | ca-certificates | — | `ca-certificates` | Trusted root certificates | +| Chromium runtime libraries | — | see below | Shared libraries required by Playwright-managed browsers | + +**Chromium/Playwright runtime libraries:** Because the agent uses selective bind mounts instead of a full host filesystem mount, browsers downloaded by Playwright cannot rely on host libraries. The image therefore preinstalls `libasound2`, `libatk-bridge2.0-0`, `libatk1.0-0`, `libatspi2.0-0`, `libcairo2`, `libcups2`, `libdbus-1-3`, `libdrm2`, `libexpat1`, `libgbm1`, `libglib2.0-0`, `libnspr4`, `libnss3`, `libpango-1.0-0`, `libpangocairo-1.0-0`, `libx11-6`, `libxcb1`, `libxcomposite1`, `libxdamage1`, `libxext6`, `libxfixes3`, `libxkbcommon0`, `libxrandr2`, `libxrender1`, `libxshmfence1`, and `fonts-liberation` (plus their transitive dependencies). On Ubuntu 24.04 base images the `t64` variants of these packages are installed automatically. In chroot mode (the default), these image-installed libraries would otherwise be shadowed by the host's own `/usr`, `/lib`, etc.; `entrypoint.sh` stages copies of the files under `/run/awf-lib/browser-libs` (on the container's own writable rootfs, unaffected by the chroot bind mounts) and sets `LD_LIBRARY_PATH` so Chromium resolves them there. **⚠️ Docker CLI Stub:** The `docker` command is present but is a stub—there is no Docker daemon running inside the container. Docker-in-Docker is not supported. Use `--mount` to access Docker sockets from the host if needed. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 0b7bb8a32..8b778ee71 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -364,6 +364,37 @@ AWF uses a forward proxy (Squid) for HTTPS egress control rather than transparen - **Java tools**: Use `JAVA_TOOL_OPTIONS` with JVM system properties (set automatically by AWF) - **Maven**: Requires `~/.m2/settings.xml` (must be configured manually — see above) +## Playwright / Chromium Issues + +### Browser fails to launch with missing shared libraries + +**Problem:** `playwright-cli` (or `npx playwright`) fails to start Chromium with errors such as: + +``` +error while loading shared libraries: libnspr4.so: cannot open shared object file +Host system is missing dependencies to run browsers. +``` + +**Cause:** The agent container uses selective bind mounts rather than a full host filesystem mount, so browsers downloaded by Playwright cannot pick up native libraries installed on the host. + +**Solution:** The agent image preinstalls Chromium's native runtime libraries (`libnspr4`, `libnss3`, `libatk1.0-0`, `libatk-bridge2.0-0`, `libatspi2.0-0`, `libcups2`, `libdrm2`, `libgbm1`, `libpango-1.0-0`, `libxkbcommon0`, `libxcomposite1`, `libxdamage1`, `libxrandr2`, `libasound2`, `fonts-liberation`, and related packages). In chroot mode (the default), `entrypoint.sh` stages copies of these libraries under `/run/awf-lib/browser-libs` — on the container's own writable rootfs, so they are not shadowed by the host `/usr`/`/lib` bind mounts — and sets `LD_LIBRARY_PATH` so Chromium resolves them there. Use a current agent image (`--image-tag latest`, or `--build-local` when building from source) and the browser will launch. + +Verify the libraries are present inside the sandbox: + +```bash +sudo awf --allow-domains '' -- bash -c 'ldd $(find ~/.cache/ms-playwright -name headless_shell | head -1) | grep "not found" || echo "all libraries resolved"' +``` + +If you pin an older agent image, or you use a custom base image, install the dependencies yourself before running the browser: + +```bash +npx playwright install-deps chromium +``` + +### Browser downloads are blocked + +Playwright downloads browser binaries from `cdn.playwright.dev`. Either add that domain to `--allow-domains`, or download the browsers on the host before the sandbox starts and point `PLAYWRIGHT_BROWSERS_PATH` at the staged directory. + ## Harness Binary Resolution Issues ### `spawn /usr/local/bin/ ENOENT` (hardcoded absolute paths) diff --git a/scripts/ci/agent-dockerfile-browser-deps.test.ts b/scripts/ci/agent-dockerfile-browser-deps.test.ts new file mode 100644 index 000000000..e92d8cc3d --- /dev/null +++ b/scripts/ci/agent-dockerfile-browser-deps.test.ts @@ -0,0 +1,111 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { spawnSync } from 'child_process'; + +const dockerfilePath = path.resolve(__dirname, '../../containers/agent/Dockerfile'); + +const readDockerfile = (): string => fs.readFileSync(dockerfilePath, 'utf-8'); + +const readBrowserPackages = (): string[] => { + const declaration = readDockerfile().match(/BROWSER_PKGS="([^"]+)"/)?.[1]; + expect(declaration).toBeDefined(); + return declaration!.split(/\s+/).filter(Boolean); +}; + +/** + * Extracts the shell loop that appends the browser packages to PKGS, resolving + * Ubuntu 24.04 `t64` package renames, so it can be exercised with a stubbed + * `apt-cache`. + */ +const readResolutionScript = (): string => { + const block = readDockerfile().match(/for pkg in \$BROWSER_PKGS; do[\s\S]*?done && \\/)?.[0]; + + expect(block).toBeDefined(); + return block! + .replace(/done && \\\s*$/, 'done') + .split('\n') + .map((line) => line.trim().replace(/\s*\\$/, '')) + .join('\n'); +}; + +const runResolution = (t64Packages: string[]): string => { + const script = ` +set -eu +apt-cache() { + for available in ${t64Packages.map((pkg) => `"${pkg}"`).join(' ')}; do + if [ "$2" = "$available" ]; then return 0; fi + done + return 100 +} +BROWSER_ACTUAL_PKGS="" +BROWSER_PKGS="${readBrowserPackages().join(' ')}" +${readResolutionScript()} +echo "$BROWSER_ACTUAL_PKGS" +`; + + const result = spawnSync('bash', ['-c', script], { encoding: 'utf-8' }); + expect(result.status).toBe(0); + return result.stdout.trim(); +}; + +describe('agent Dockerfile Chromium runtime dependencies', () => { + it('preinstalls the shared libraries Playwright-managed Chromium needs', () => { + const packages = readBrowserPackages(); + + for (const required of [ + 'libasound2', + 'libatk-bridge2.0-0', + 'libatk1.0-0', + 'libatspi2.0-0', + 'libcups2', + 'libdbus-1-3', + 'libdrm2', + 'libgbm1', + 'libglib2.0-0', + 'libnspr4', + 'libnss3', + 'libpango-1.0-0', + 'libxcomposite1', + 'libxdamage1', + 'libxfixes3', + 'libxkbcommon0', + 'libxrandr2', + 'fonts-liberation', + ]) { + expect(packages).toContain(required); + } + }); + + it('declares the base (22.04) package names, never the t64 variants', () => { + for (const pkg of readBrowserPackages()) { + expect(pkg.endsWith('t64')).toBe(false); + } + }); + + it('keeps the 22.04 names when the base image has no t64 packages', () => { + const resolved = runResolution([]); + + expect(resolved.split(/\s+/).filter(Boolean)).toEqual(readBrowserPackages()); + }); + + it('prefers t64 packages when the base image provides them', () => { + const resolved = runResolution(['libasound2t64', 'libcups2t64']); + + expect(resolved).toContain('libasound2t64'); + expect(resolved).toContain('libcups2t64'); + expect(resolved.split(/\s+/)).not.toContain('libasound2'); + expect(resolved.split(/\s+/)).not.toContain('libcups2'); + expect(resolved).toContain('libnss3'); + }); + + it('installs the resolved browser packages separately so their transitive deps can be diffed', () => { + expect(readDockerfile()).toMatch(/apt_install_retry \$PKGS/); + expect(readDockerfile()).toMatch(/apt_install_retry \$BROWSER_ACTUAL_PKGS/); + }); + + it('records the installed .so files into a manifest entrypoint.sh stages for the chroot', () => { + const dockerfile = readDockerfile(); + expect(dockerfile).toMatch(/\/usr\/local\/share\/awf\/browser-libs\.manifest/); + expect(dockerfile).toMatch(/dpkg -L \$\(cat \/tmp\/browser-pkgs-installed\.txt\)/); + }); +}); diff --git a/tests/entrypoint-phase-functions.test.sh b/tests/entrypoint-phase-functions.test.sh index 7f839238d..4cdc9aacb 100755 --- a/tests/entrypoint-phase-functions.test.sh +++ b/tests/entrypoint-phase-functions.test.sh @@ -30,6 +30,7 @@ required_functions=( mount_host_procfs mount_host_cgroupfs copy_preload_libs + copy_browser_libs copy_agent_helper_scripts copy_dind_runner_binary resolve_chroot_binary_path @@ -122,6 +123,7 @@ chroot_helpers=( 'mount_host_cgroupfs' 'check_chroot_prereqs' 'copy_preload_libs' + 'copy_browser_libs' 'copy_agent_helper_scripts' 'copy_dind_runner_binary' 'ensure_usr_local_bin_shims' @@ -222,6 +224,81 @@ else fail "run_chroot_command() does not clean up copied system CA bundles" fi +# copy_browser_libs() must stage the Chromium/Playwright runtime libraries +# recorded in the Dockerfile's manifest (containers/agent/Dockerfile: +# /usr/local/share/awf/browser-libs.manifest) under /run/awf-lib/browser-libs +# -- on the container's own writable rootfs, not the /host bind mount -- and +# export their directories via BROWSER_LD_LIBRARY_PATH. This directly +# exercises the staging mechanism that makes the packages installed by +# BROWSER_PKGS visible inside the chroot, where buildSystemMounts() otherwise +# shadows the image's /usr, /lib, etc. with the runner's own. +run_copy_browser_libs_fixture() { + local tmp_dir + tmp_dir="$(mktemp -d)" + local host_root="${tmp_dir}/host-root" + local fixture_entrypoint="${tmp_dir}/entrypoint-fixture.sh" + local fake_lib_dir="${tmp_dir}/image-rootfs/usr/lib/x86_64-linux-gnu" + local fake_lib="${fake_lib_dir}/libnspr4.so" + local manifest="${tmp_dir}/browser-libs.manifest" + + mkdir -p "${host_root}" "${fake_lib_dir}" + printf 'real-libnspr4-bytes' > "${fake_lib}" + printf '%s\n' "${fake_lib}" > "${manifest}" + + awk '$0 != "main \"$@\""' "${ENTRYPOINT}" > "${fixture_entrypoint}" + sed -i "s#/host#\${AWF_TEST_HOST_ROOT}#g" "${fixture_entrypoint}" + sed -i "s#/usr/local/share/awf/browser-libs.manifest#\${AWF_TEST_MANIFEST}#g" "${fixture_entrypoint}" + + ( + set -e + # shellcheck disable=SC1090 + . "${fixture_entrypoint}" + + AWF_TEST_HOST_ROOT="${host_root}" + AWF_TEST_MANIFEST="${manifest}" + + unset BROWSER_LD_LIBRARY_PATH + copy_browser_libs + + staged_lib="${host_root}/run/awf-lib/browser-libs${fake_lib}" + [ -f "${staged_lib}" ] + [ "$(cat "${staged_lib}")" = "real-libnspr4-bytes" ] + [ "${BROWSER_LD_LIBRARY_PATH}" = "/run/awf-lib/browser-libs${fake_lib_dir}" ] + + # Simulate buildSystemMounts() shadowing the image's own /usr/lib with the + # runner's: overwrite the original library path with different content + # (as if the host's own libnspr4.so.0 had been bind-mounted over it). + printf 'shadowed-by-host-bind-mount' > "${fake_lib}" + + # The staged copy under /run/awf-lib must be unaffected by the shadowing, + # proving Chromium resolves the image-provided library via + # LD_LIBRARY_PATH rather than being silently broken by the chroot bind mount. + [ "$(cat "${staged_lib}")" = "real-libnspr4-bytes" ] + ) + local result=$? + rm -rf "${tmp_dir}" + return "${result}" +} + +if run_copy_browser_libs_fixture; then + pass "copy_browser_libs() stages manifest libraries under /run/awf-lib and survives /usr shadowing" +else + fail "copy_browser_libs() does not stage Chromium/Playwright runtime libraries correctly" +fi + +if grep -Eq '\[ -n "\$\{BROWSER_LD_LIBRARY_PATH\}" \]' "${ENTRYPOINT}"; then + pass "run_chroot_command() cleans up staged browser runtime libraries" +else + fail "run_chroot_command() does not clean up staged browser runtime libraries" +fi + +if grep -Fq 'LD_LIBRARY_PATH_CMD' "${ENTRYPOINT}" && \ + grep -Fq 'export LD_LIBRARY_PATH=${BROWSER_LD_LIBRARY_PATH}' "${ENTRYPOINT}"; then + pass "run_chroot_command() exports LD_LIBRARY_PATH from BROWSER_LD_LIBRARY_PATH before exec" +else + fail "run_chroot_command() does not export LD_LIBRARY_PATH from BROWSER_LD_LIBRARY_PATH" +fi + # configure_jvm_proxy must not abort the entrypoint (set -e) when $HOME is # read-only, including when .m2/.gradle already exist but cannot be written. run_configure_jvm_proxy_readonly_home_fixture() {