Files
dsh-browser-install/README.md
T
Plugin Dev 604eb44127
Release / release (push) Successful in 6s
feat: tag and publish a Gitea release on every push to master
Replicate the dsh-facet release stack: a Gitea Actions workflow
(.gitea/workflows/release.yml) + .gitea/scripts/release.sh read the commit
subjects since the last tag, bump package.json, prepend CHANGELOG.md, tag
vX.Y.Z and publish the release via API. Agreed install ref is
#semver:^0.1.0, and tags are what 'dsh plugin --profile web update' moves
to, so the workflow is what makes updates work. Add the release.test.sh
suite (43 checks), npm scripts (test:release / release:plan /
release:notes), AGENTS.md with the commit conventions, CHANGELOG.md seed,
and the README *Releases* rules.
2026-10-01 09:00:18 +01:00

166 lines
9.9 KiB
Markdown

# 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-<rev>/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)
This repo lives on `git.notexpectedyet.com`, so the install uses the same `git+https://…#semver:` convention as the other plugins in the profile (`dsh-facet`, `dsh-recall`, `dsh-slopo`). On GitHub the equivalent shorthand would be `github:<you>/dsh-browser-install#semver:^0.1.0`:
```sh
dsh plugin --profile web add "git+https://git.notexpectedyet.com/Not-Expected-Yet/dsh-browser-install.git#semver:^0.1.0"
```
- The **`#semver:^0.1.0` ref is what makes updates work**: it resolves against the repo's version tags (each release is a `v<version>` tag), and `pnpm update` moves the dependency within the range. This mirrors exactly how `dsh-facet`/`dsh-recall`/`dsh-slopo` are kept current in this profile. To publish a new version: bump `version` in `package.json`, commit, tag it, push — then 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 resolves git deps over HTTPS, so **the repo must be public** on the forge (or the DSH host needs git credentials). A private/new repo makes `git ls-remote` fail with "could not read Username ... terminal prompts disabled", which is what a bare `#master` install hit here.
## 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 <package> 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. |
## Releases
`master` releases itself. Every push runs `.gitea/workflows/release.yml`
(Gitea Actions), which reads the commit subjects since the last tag, bumps
`package.json`, prepends `CHANGELOG.md`, tags `vX.Y.Z`, and publishes the Gitea
release with those subjects as the body. The tag is what `dsh plugin --profile
web update` moves to via the `#semver:^0.1.0` install ref — no tags, no update,
which is why this workflow exists.
### Commit subjects are release input
| Subject | Bump | Notes section |
|---|---|---|
| `feat: …` | minor | **Added** |
| `feat!: …`, `fix(x)!: …`, `BREAKING`, or `[major]` | major | **Breaking** |
| `fix: …`, `perf: …`, `revert: …` | patch | **Fixed** |
| `docs: …` | patch | **Documented** |
| plain sentence / anything else | patch | **Other** (or flat list) |
| `[skip release]` | — | nothing published |
`feat` counts only as its own word, so `Fix the feature flag leak` is a patch.
`[minor]` raises a patch subject to a minor without a `feat`. Only the subject
line is read.
### First release
With no tags yet, the released version is the one declared in `package.json`
(currently `v0.1.0`) — the first tag matches what the code says, and every later
release moves forward from it.
### Manual run
`workflow_dispatch` is enabled for the first release (no tags yet) and for a
release whose push failed after the tag was created; trigger it from the
Actions tab.
### Checking before pushing
```sh
npm run release:plan # would this HEAD release? (yes/no)
npm run release:notes # the exact notes it would publish
npm run test:release # the checks behind the bump and the notes
```
## 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).