# dsh-browser-install Automatically installs **Playwright Chromium** (`npx playwright install chromium --with-deps`) into the Docker container running DeepSeek Harness, and bridges the installed binary onto `PATH` so the **dsh-ego-browser** side-bar plugin finds it — no manual "find the binary / symlink it / set `EGO_LINUX_*` env vars / wrap it with `--no-sandbox`" steps for agents ever again. ## Why this plugin exists `dsh-ego-browser` discovers its browser the following way (see `lib/index.js` → `resolveEgoEnv` and `runtime/ego-linux/src/chrome.mjs`): 1. It scans `PATH` + fixed locations for `google-chrome`, `google-chrome-stable`, `chromium`, `chromium-browser`, … and 2. on **root** hosts (docker default), it hands the found binary to a bundled `--no-sandbox` wrapper that only looks at fixed paths (`/usr/bin/*`, `/opt/google/...`, `/usr/local/bin/google-chrome-stable`, `/snap/bin/chromium`). Playwright installs Chromium into `~/.cache/ms-playwright/chromium-/chrome-linux{,64}/chrome` — a location **invisible to both scans**. That is why agents kept having to hunt the binary down, symlink/wrap it, and set `EGO_LINUX_CHROME` / `EGO_LINUX_EXTRA_ARGS` by hand. This plugin does exactly that bridging once, automatically: - on load it checks the Playwright cache (idempotent, no-op when the browser is already there), - if the browser is missing it runs `npx --yes playwright install chromium --with-deps` **in the background** (profile startup is never blocked for minutes), and - it then symlinks the installed binary at `/usr/local/bin/google-chrome-stable` and `/usr/local/bin/chromium` — the exact names ego-browser's PATH scan *and* its bundled `--no-sandbox` wrapper look for. On this root container, the chain then resolves to: root detected → bundled `--no-sandbox` wrapper → `--no-sandbox` launch; no `DISPLAY` → automatic headless. **No env vars, no manual wrapper, no agent steps.** Nothing is ever clobbered: a pre-existing system Chrome or a user wrapper keeps winning (the plugin only creates links where nothing of its own/an external binary exists). ## Install (from git — recommended) Install from your forge (the command below is the full-URL form for any host; on GitHub you can use the shorthand `github:/dsh-browser-install#main`): ```sh dsh plugin --profile web add https://git.notexpectedyet.com/Not-Expected-Yet/dsh-browser-install.git#main ``` - The `#main` **branch ref is what makes updates work**: `pnpm update` re-resolves the branch tip (verified against pnpm 12.8.1), so after you push new commits simply run ```sh dsh plugin --profile web update # all deps dsh plugin --profile web update dsh-browser-install # just this one ``` - Bundle changes only become active after a profile restart: `dsh --profile web`. - No build step: the bundle ships plain ESM and declares no `prepare` script, so pnpm's default block on git-dependency build scripts never applies — nothing to `allowBuilds` in `pnpm-workspace.yaml`. - pnpm installs git deps over HTTPS, so keep the repo public (or set up credentials on the host). ## Alternative: install from a local checkout ```sh dsh plugin --profile web add /root/workspaces/Plugin\ Development/dsh-browser-install ``` A directory install is a symlinked `file:` dependency — handy while iterating. For persistent installs and updates, prefer the git flow above. ## Restart and verify Restart the profile, then check the plugin loaded and did its job: ```sh dsh --profile web ``` ```sh dsh --profile web --dump-config | grep -A3 browser-install ls -la /usr/local/bin/google-chrome-stable # -> .../ms-playwright/chromium-*/chrome-linux64/chrome ``` Then from any agent session, `ego_doctor` should report a browser binary, and `ego_space_open`/`ego_navigate` work with no environment prep. > Non-root hosts: set `symlinkDir` to a directory on your `PATH` (e.g. `~/.local/bin`). On root only `/usr/local/bin` is needed — it also satisfies the bundled `--no-sandbox` wrapper's absolute-path list. ## Config All fields are optional. Override them in your profile's `cordis.patch.yml` (or in the bundle row's `config`): ```yaml - id: browser-install name: "dsh-browser-install" config: withDeps: true # run `--with-deps` (apt system deps); needs root/apt target: chromium # chromium | chromium-headless-shell | chrome package: playwright # npx package spec, e.g. "playwright@1.54.1" forceInstall: false # always re-run the install on load installOnStartup: true # install in background when the browser is missing cacheDir: "" # empty = $PLAYWRIGHT_BROWSERS_PATH | ~/.cache/ms-playwright symlinkDir: /usr/local/bin symlinkNames: [google-chrome-stable, chromium] installTimeoutMs: 600000 ``` | Field | Default | Meaning | |---|---|---| | `installOnStartup` | `true` | Run the Playwright install when the plugin loads **and** no matching browser is found. The install runs in the background so profile startup is not blocked. | | `forceInstall` | `false` | Always run the install on load, even when a browser is already present (e.g. after a Playwright upgrade). | | `package` | `playwright` | Package spec passed to `npx --yes install ...`. Pin with e.g. `playwright@1.54.1`. | | `target` | `chromium` | Which Playwright browser to ensure: `chromium` (full build), `chromium-headless-shell`, or `chrome` (Google Chrome). Default matches what ego-browser expects. | | `withDeps` | `true` | Pass `--with-deps` so Playwright also installs the browser's system dependencies via apt (requires root; the DSH image runs as root). | | `cacheDir` | auto | Playwright browsers cache root. Empty = `$PLAYWRIGHT_BROWSERS_PATH`, else `~/.cache/ms-playwright`. When set here, the installer child also gets `PLAYWRIGHT_BROWSERS_PATH`. | | `symlinkDir` | `/usr/local/bin` | Directory for the PATH links; must be on the DSH process `PATH`. | | `symlinkNames` | `[google-chrome-stable, chromium]` | Link names to create. `google-chrome-stable` is the one ego-browser's PATH scan and its `--no-sandbox` wrapper both check. | | `installTimeoutMs` | `600000` | Max time for the background install before it is killed. | ## Behaviour notes - **No-clobber links:** a link whose target lives inside the Playwright cache is treated as ours and refreshed to the newest installed revision; any other existing file/symlink is left untouched (and wins). - **Lifecycle:** the background installer child is killed if the plugin is unloaded mid-install (`ctx.effect`). The PATH links are intentionally *not* removed on unload — a running browser may be using them and they are harmless, idempotent shared state. - **Error visibility:** install failures, symlink errors, and a `symlinkDir` missing from `PATH` are all logged loudly under the `dsh-browser-install` logger scope, so an agent can read the reason instead of guessing. - **ego-browser settings untouched:** the plugin does not modify dsh-ego-browser's config or any `EGO_LINUX_*` env vars — it only fills the gap (install + visible symlink) that makes ego-browser's existing auto-adaptation work (root → `--no-sandbox` wrapper, no DISPLAY → headless). ## Uninstall ```sh dsh plugin --profile web remove dsh-browser-install ``` The symlinks are left in place (harmless; the browser keeps working for ego-browser). Remove them manually if you truly want them gone: ```sh rm -f /usr/local/bin/google-chrome-stable /usr/local/bin/chromium ``` ## Development Pure helpers (`resolveCacheDir`, `resolveBrowserBinary`, `planSymlinks`, `applySymlinkPlan`, `buildInstallCmd`) are exported for testing; the plugin itself needs no build step (plain ESM, no TypeScript compile). - Node ≥ 22 (the harness image ships v24). - Requires DSH ≥ 0.1.2-rc.1 (same baseline as dsh-ego-browser). - Runtime dependency: `@deepseek-ai/schemastery` (already present in the profile).