e4a313e626
The headless browser leaves the API stack. It becomes its own compose unit
(chrome/docker-compose.yml) deployed on the home machine and reached over the
tailnet, returning 471 MiB of working set to a 1974 MiB VPS that has no swap.
No fallback sidecar is left behind.
The backend needs no code change: BROWSER_WS_URL was already the only coupling,
so relocation is one environment variable. Its default is now empty rather than
a pinned Docker IP — an unreachable or unconfigured browser degrades exactly as
it always has, with plain-TLS libraries unaffected, kagane and novelfull logged
and skipped, and stored covers still served.
The browser unit publishes CDP on ${BROWSER_BIND_ADDR} with no default, because
CDP authenticates nothing and the home machine has a real LAN: an unset value
must fail the deploy rather than silently expose an endpoint that is remote code
execution for anything that reaches it. Resource limits are sized against the
measured 645 MiB untuned peak and the CI runner that already holds 1.2 GiB of
that box.
bookmark-api gains the default network. Dropping `browser` left it on `db`
alone, which is internal: true — that meant no published port and, worse, no
egress for the poller at all. Caught by bringing the stack up.
Docs: ADR-0006 for the topology, DEPLOY.md §7 for first-time setup of the
browser machine, REDEPLOY.md §8 for its independent update cadence, plus the
architecture diagrams, config tables and troubleshooting rows.
52 lines
2.6 KiB
Markdown
52 lines
2.6 KiB
Markdown
# ADR-0005: On-demand browser sidecar
|
|
|
|
Date: 2026-08-09
|
|
Status: accepted
|
|
|
|
Superseded in part by ADR-0006: the lifecycle below is unchanged, but the
|
|
service no longer lives in the API stack and the name `headless-shell` is gone.
|
|
|
|
## Decision
|
|
|
|
Keep the `headless-shell` service and its CDP port alive, but start Google Chrome
|
|
only when the first CDP connection arrives. The entrypoint supervises a `socat`
|
|
front-end, serializes browser start/reap state with `flock`, and tracks each
|
|
connection with a marker named for its helper PID. A reaper stops Chrome after
|
|
300 seconds with no live markers. Marker reconciliation covers a helper killed
|
|
before its cleanup trap runs.
|
|
|
|
Chrome runs in its own process group so reap sends the termination signal to
|
|
Chrome and its renderer children. The explicit `/home/chrome/profile` user-data
|
|
directory remains: Chrome remaps remote debugging to loopback on modern builds,
|
|
and Chrome ignores remote-debugging flags on a default profile. `socat` therefore
|
|
continues to front Chrome's loopback CDP port.
|
|
|
|
The profile is a named Compose volume. Clearance cookies survive both a reap and
|
|
`docker compose up --build`; the browser still starts with a fresh debugger UUID,
|
|
so chromedp must keep endpoint discovery enabled and must not use
|
|
`chromedp.NoModifyURL`.
|
|
|
|
The socat front-end and explicit profile are retained because Chromium remaps a
|
|
non-loopback debugging address to loopback since M113, while Chrome ignores the
|
|
remote-debugging flags on a default profile since Chrome 136. Flag tuning is
|
|
deliberately not adopted: its roughly 30% idle-footprint saving is irrelevant
|
|
to a browser that exists for seconds per wake and risks an untested fingerprint.
|
|
|
|
## Constraints
|
|
|
|
The 300-second floor is deliberate. Chromium batches cookie persistence on a
|
|
roughly 31-second timer, and Go's default HTTP transport can keep the discovery
|
|
connection parked for about 90 seconds after use. Reaping only with zero live
|
|
connections holds Chrome through both windows and through the poller's staggered
|
|
batch plus cover prefetch.
|
|
|
|
The anti-bot properties remain unchanged: a plausible non-UTC timezone, a
|
|
Chrome-version-derived User-Agent without `HeadlessChrome`, and no automation
|
|
flag. A remote browser restart can surface as `context.Canceled`, the same error
|
|
as a caller deadline, so the backend wraps cancellation observed with a closed
|
|
CDP connection as `browser interrupted`; the focused test asserts that
|
|
classification without killing a real browser.
|
|
The same process-group stop runs during supervisor shutdown, not only during
|
|
idle reap, so Chrome can flush its cookie batch before a container rebuild or
|
|
graceful stop.
|