Skip to content

Commit 3190226

Browse files
authored
fix: preinstall Chromium runtime libraries in agent image
1 parent 336c1f4 commit 3190226

5 files changed

Lines changed: 157 additions & 0 deletions

File tree

containers/agent/Dockerfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,21 @@ RUN set -eux; \
107107
apt-get -o Acquire::Retries=3 -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 upgrade -y; \
108108
}; \
109109
PKGS="iptables curl ca-certificates git gnupg dnsutils net-tools netcat-openbsd libcap2-bin libgdiplus libev-dev libssl-dev php-intl php-gd python3"; \
110+
# Chromium/Playwright native runtime libraries. The agent uses selective bind
111+
# mounts (not a full host filesystem mount), so Playwright-managed browsers
112+
# cannot pick these up from the host and must be present in the image.
113+
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"; \
110114
apt_update_retry && \
115+
# Ubuntu 24.04 renamed several of these to time_t-64 variants (e.g. libasound2t64),
116+
# and the old names became ambiguous virtual packages. Prefer the t64 package when
117+
# the base image provides one, otherwise keep the 22.04 name.
118+
for pkg in $BROWSER_PKGS; do \
119+
if apt-cache show "${pkg}t64" >/dev/null 2>&1; then \
120+
PKGS="$PKGS ${pkg}t64"; \
121+
else \
122+
PKGS="$PKGS $pkg"; \
123+
fi; \
124+
done && \
111125
apt_install_retry $PKGS && \
112126
apt_upgrade_retry && \
113127
# Raw sockets are unavailable to the agent; remove stale file capabilities

docs-site/src/content/docs/reference/agent-images.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ The default `agent` image (based on Ubuntu 22.04) includes the following pre-ins
7676
| libssl-dev || `libssl-dev` | OpenSSL development headers for native extensions |
7777
| php-intl || `php-intl` | PHP Internationalization extension |
7878
| php-gd || `php-gd` | PHP GD graphics extension |
79+
| Chromium runtime libraries || see below | Shared libraries required by Playwright-managed browsers |
80+
81+
:::note[Chromium/Playwright runtime libraries]
82+
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.
83+
:::
7984

8085
:::caution[Docker CLI Stub]
8186
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.

docs/agent-images.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ The `default` preset image (based on Ubuntu 22.04) includes the following pre-in
7171
| capsh || `libcap2-bin` | Capability management |
7272
| gnupg | 2.2.27 | `gnupg` | GPG encryption |
7373
| ca-certificates || `ca-certificates` | Trusted root certificates |
74+
| Chromium runtime libraries || see below | Shared libraries required by Playwright-managed browsers |
75+
76+
**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.
7477

7578
**⚠️ 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.
7679

docs/troubleshooting.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,37 @@ AWF uses a forward proxy (Squid) for HTTPS egress control rather than transparen
364364
- **Java tools**: Use `JAVA_TOOL_OPTIONS` with JVM system properties (set automatically by AWF)
365365
- **Maven**: Requires `~/.m2/settings.xml` (must be configured manually — see above)
366366

367+
## Playwright / Chromium Issues
368+
369+
### Browser fails to launch with missing shared libraries
370+
371+
**Problem:** `playwright-cli` (or `npx playwright`) fails to start Chromium with errors such as:
372+
373+
```
374+
error while loading shared libraries: libnspr4.so: cannot open shared object file
375+
Host system is missing dependencies to run browsers.
376+
```
377+
378+
**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.
379+
380+
**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). Use a current agent image (`--image-tag latest`, or `--build-local` when building from source) and the browser will launch.
381+
382+
Verify the libraries are present inside the sandbox:
383+
384+
```bash
385+
sudo awf --allow-domains '' -- bash -c 'ldd $(find ~/.cache/ms-playwright -name headless_shell | head -1) | grep "not found" || echo "all libraries resolved"'
386+
```
387+
388+
If you pin an older agent image, or you use a custom base image, install the dependencies yourself before running the browser:
389+
390+
```bash
391+
npx playwright install-deps chromium
392+
```
393+
394+
### Browser downloads are blocked
395+
396+
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.
397+
367398
## Harness Binary Resolution Issues
368399

369400
### `spawn /usr/local/bin/<tool> ENOENT` (hardcoded absolute paths)
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
import { spawnSync } from 'child_process';
4+
5+
const dockerfilePath = path.resolve(__dirname, '../../containers/agent/Dockerfile');
6+
7+
const readDockerfile = (): string => fs.readFileSync(dockerfilePath, 'utf-8');
8+
9+
const readBrowserPackages = (): string[] => {
10+
const declaration = readDockerfile().match(/BROWSER_PKGS="([^"]+)"/)?.[1];
11+
expect(declaration).toBeDefined();
12+
return declaration!.split(/\s+/).filter(Boolean);
13+
};
14+
15+
/**
16+
* Extracts the shell loop that appends the browser packages to PKGS, resolving
17+
* Ubuntu 24.04 `t64` package renames, so it can be exercised with a stubbed
18+
* `apt-cache`.
19+
*/
20+
const readResolutionScript = (): string => {
21+
const block = readDockerfile().match(/for pkg in \$BROWSER_PKGS; do[\s\S]*?done && \\/)?.[0];
22+
23+
expect(block).toBeDefined();
24+
return block!
25+
.replace(/done && \\\s*$/, 'done')
26+
.split('\n')
27+
.map((line) => line.trim().replace(/\s*\\$/, ''))
28+
.join('\n');
29+
};
30+
31+
const runResolution = (t64Packages: string[]): string => {
32+
const script = `
33+
set -eu
34+
apt-cache() {
35+
for available in ${t64Packages.map((pkg) => `"${pkg}"`).join(' ')}; do
36+
if [ "$2" = "$available" ]; then return 0; fi
37+
done
38+
return 100
39+
}
40+
PKGS="iptables"
41+
BROWSER_PKGS="${readBrowserPackages().join(' ')}"
42+
${readResolutionScript()}
43+
echo "$PKGS"
44+
`;
45+
46+
const result = spawnSync('bash', ['-c', script], { encoding: 'utf-8' });
47+
expect(result.status).toBe(0);
48+
return result.stdout.trim();
49+
};
50+
51+
describe('agent Dockerfile Chromium runtime dependencies', () => {
52+
it('preinstalls the shared libraries Playwright-managed Chromium needs', () => {
53+
const packages = readBrowserPackages();
54+
55+
for (const required of [
56+
'libasound2',
57+
'libatk-bridge2.0-0',
58+
'libatk1.0-0',
59+
'libatspi2.0-0',
60+
'libcups2',
61+
'libdbus-1-3',
62+
'libdrm2',
63+
'libgbm1',
64+
'libglib2.0-0',
65+
'libnspr4',
66+
'libnss3',
67+
'libpango-1.0-0',
68+
'libxcomposite1',
69+
'libxdamage1',
70+
'libxfixes3',
71+
'libxkbcommon0',
72+
'libxrandr2',
73+
'fonts-liberation',
74+
]) {
75+
expect(packages).toContain(required);
76+
}
77+
});
78+
79+
it('declares the base (22.04) package names, never the t64 variants', () => {
80+
for (const pkg of readBrowserPackages()) {
81+
expect(pkg.endsWith('t64')).toBe(false);
82+
}
83+
});
84+
85+
it('keeps the 22.04 names when the base image has no t64 packages', () => {
86+
const resolved = runResolution([]);
87+
88+
expect(resolved.split(/\s+/)).toEqual(['iptables', ...readBrowserPackages()]);
89+
});
90+
91+
it('prefers t64 packages when the base image provides them', () => {
92+
const resolved = runResolution(['libasound2t64', 'libcups2t64']);
93+
94+
expect(resolved).toContain('libasound2t64');
95+
expect(resolved).toContain('libcups2t64');
96+
expect(resolved.split(/\s+/)).not.toContain('libasound2');
97+
expect(resolved.split(/\s+/)).not.toContain('libcups2');
98+
expect(resolved).toContain('libnss3');
99+
});
100+
101+
it('installs the resolved browser packages with the other agent packages', () => {
102+
expect(readDockerfile()).toMatch(/apt_install_retry \$PKGS/);
103+
});
104+
});

0 commit comments

Comments
 (0)