Linux Browser Runtime

Register and operate an EthanKit Browser Runtime on Linux with four-session capacity, private-network access, live streaming, and Human in the Loop.

简体中文

When to use it

A registered Browser Runtime keeps the EthanKit model loop in the API while browser MCP calls, Chromium, live frames, and human handoff run on a Linux machine you control. Use it when:

  • The target is reachable only from a corporate network or approved egress IP.
  • Sign-in state must persist in a dedicated browser profile.
  • Login, MFA, CAPTCHA, or approval requires Human in the Loop (HIL).
  • One machine needs to host several isolated browser sessions.

The Runtime initiates its connection to the EthanKit API. A normal deployment needs outbound HTTPS/WSS only. It does not require a public IP or an inbound development, CDP, or VNC port.

Prerequisites

Ubuntu 22.04/24.04 x86_64 is recommended. For four concurrent sessions, start with 8 vCPU, 16 GiB RAM, and 30 GiB of free disk, then load-test representative pages. Video-heavy sites and large profiles need more resources. A GPU is optional.

Prepare:

  • Node.js 24, Git, pnpm 10.30.2, and a Rust toolchain for the native supervisor.
  • DNS and outbound TCP 443 access to the EthanKit API and target sites.
  • A non-root Linux account. sudo is required for OS dependencies and lingering.
  • Administrative SSH through JumpServer or another trusted network path. The VM does not need a public IP.
  • Xvfb, Openbox, and loopback-only x11vnc for HIL.

Install browser and desktop dependencies:

sudo apt-get update
sudo apt-get install -y \
  build-essential ca-certificates cargo curl ffmpeg git rustc \
  xvfb openbox x11vnc xdotool dbus-x11 \
  fonts-liberation fonts-noto-cjk

corepack enable
corepack prepare [email protected] --activate
pnpm --filter @ethankit/agent-browser exec playwright install-deps chromium

ffmpeg produces WebM operation reels. Browsing still works without it, but reel artifacts fail.

Configure SSH through JumpServer

Create a dedicated local key instead of reusing your primary personal key:

ssh-keygen -t ed25519 -f ~/.ssh/ethankit-browser-runtime \
  -C "ethankit-browser-runtime"

Log in through JumpServer, append the complete .pub line to the Runtime user's ~/.ssh/authorized_keys, and fix permissions:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

Verify access:

ssh -i ~/.ssh/ethankit-browser-runtime <user>@<private-ip>

If the private address is not locally routed, use the company-provided ProxyJump configuration. Do not add a temporary public IP to the VM.

Install and register the Runtime

Check out the repository revision matching the EthanKit deployment, then run on the VM:

pnpm install --frozen-lockfile
pnpm browser-runtime:install

In the Browser Use inspector or Browser Use Lab, choose “Register a machine” and create a one-time pairing token. It expires after ten minutes and can be used once. Interactive entry keeps it out of shell history:

ethankit-browser setup \
  --runtime-name prod-browser-runtime-shanghai-01

The CLI defaults to https://api.ethankit.com, installs the supported Playwright Chromium, creates a dedicated persistent EthanKit profile, and starts the Runtime in the background. Use --api-url <origin> only for a self-hosted API.

Never put a pairing token in documentation, chat, a systemd unit, or Git. After registration, the long-lived credential is stored with mode 0600 in ~/.ethankit/browser-runtime.json.

Allow private destinations

The SSRF guard is enabled by default. Public destinations work without configuration. A hostname that resolves to a private IP must be explicitly allowed:

ethankit-browser private-hosts add \
  paigod.work '*.paigod.work' \
  pplabs.tech '*.pplabs.tech' \
  ppinfra.com '*.ppinfra.com' \
  ppio.com '*.ppio.com'

A wildcard covers subdomains only. Add both example.com and '*.example.com' when the apex may also be visited. Quote * so the shell does not expand it.

When systemd owns the Runtime, stop the unit before changing configuration so the CLI and systemd do not race to restart the daemon:

systemctl --user stop ethankit-browser
ethankit-browser private-hosts add intranet.example '*.intranet.example'
systemctl --user start ethankit-browser

Configure the virtual desktop and systemd

deploy/browser-runtime-linux/ contains four user services and an input-state reset helper:

  • ethankit-xvfb.service creates a 1920×1080×24 display at DISPLAY=:99.
  • ethankit-openbox.service manages Chromium windows.
  • ethankit-x11vnc.service exposes HIL on 127.0.0.1:5900 only.
  • ethankit-browser.service runs the Runtime in foreground mode and advertises four-session capacity.
  • ethankit-x11vnc-reset-input releases stale X11 key state after the first VNC client authenticates and after the last client disconnects.

Create the VNC password first:

install -d -m 700 ~/.vnc
x11vnc -storepasswd ~/.vnc/passwd
chmod 600 ~/.vnc/passwd

To avoid a small restored browser window, copy the default Openbox configuration:

install -d -m 700 ~/.config/openbox
cp /etc/xdg/openbox/rc.xml ~/.config/openbox/rc.xml

Add this rule inside <applications> in ~/.config/openbox/rc.xml:

<application class="Chromium-browser" type="normal">
  <decor>yes</decor>
  <focus>yes</focus>
  <desktop>1</desktop>
  <maximized>yes</maximized>
</application>

Install and enable the user services:

install -d -m 700 ~/.config/systemd/user
install -d -m 700 ~/.local/libexec
install -m 755 \
  deploy/browser-runtime-linux/ethankit-x11vnc-reset-input \
  ~/.local/libexec/
install -m 644 deploy/browser-runtime-linux/*.service \
  ~/.config/systemd/user/

sudo loginctl enable-linger "$USER"
systemctl --user daemon-reload
systemctl --user enable --now \
  ethankit-xvfb.service \
  ethankit-openbox.service \
  ethankit-x11vnc.service

# setup started a background daemon; stop it before switching to systemd.
ethankit-browser stop
systemctl --user enable --now ethankit-browser.service

If the CLI is installed in a custom directory, add that directory to PATH in ethankit-browser.service. Never run both the CLI-managed background daemon and the systemd foreground daemon.

Verify the installation:

systemctl --user --no-pager --full status \
  ethankit-xvfb ethankit-openbox ethankit-x11vnc ethankit-browser
ethankit-browser status
ethankit-browser doctor
ethankit-browser logs -n 100

Initialize the login profile

A persistent profile lets tasks reuse sign-in state. Stop the systemd Runtime before opening the dedicated profile:

systemctl --user stop ethankit-browser
DISPLAY=:99 ethankit-browser profile init \
  --url https://test-console.paigod.work/

Complete sign-in over the SSH/VNC tunnel described below, close the Chromium window, and restart the Runtime:

systemctl --user start ethankit-browser

Do not copy a system Chrome Default profile into managed Chromium. If browser versions become incompatible, use profile repair; it preserves a timestamped backup before creating a clean dedicated profile.

Four-session capacity

ethankit-browser.service sets AGENT_BROWSER_MAX_SESSIONS=4, so the Runtime advertises four slots to the API. Keep these constraints in mind:

  • Start with at least 8 vCPU/16 GiB RAM and measure representative page memory.
  • Four concurrent sessions should use isolated profiles. Each gets a private copy and cannot lock another session's Chrome databases.
  • A persistent profile can be owned by only one session at a time. For four independent persistent sign-in contexts, deploy multiple Runtimes or OS users with separate profiles, state files, SSRF proxy ports, and displays.
  • Four simultaneous HIL windows share one virtual desktop and are impractical for parallel human operation. Use separate Runtimes for interactive production workloads.

Human in the Loop

VNC must remain bound to the VM loopback address. When human action is required, create a local SSH tunnel:

ssh -N \
  -L 15900:127.0.0.1:5900 \
  -i ~/.ssh/ethankit-browser-runtime \
  <user>@<private-ip>

Connect a VNC client to 127.0.0.1:15900. On macOS:

open 'vnc://127.0.0.1:15900'

The correct workflow is:

  1. Wait until EthanKit explicitly shows WAITING FOR HUMAN or “waiting for your action.”
  2. Complete login, MFA, CAPTCHA, or approval in VNC. Never provide credentials or verification codes in chat.
  3. Do not close, manually minimize, or reopen Chromium. The Runtime must retain the same browser process and CDP target.
  4. Return to EthanKit and choose “I’m done, continue.” The Runtime verifies browser identity, minimizes the same window, and resumes automation.

Brief focus movement during automation may be the Agent operating the page. Once the Runtime is in WAITING FOR HUMAN, however, focus traversing elements continuously at a fixed rate is abnormal. If a VNC viewer disconnects before a key-release event arrives, X11 can retain that key as permanently pressed. macOS Screen Sharing may also open a short probe connection before the real viewer connection, making this failure easier to trigger.

-norepeat suppresses only some auto-repeat behavior, while -clear_keys runs when x11vnc starts and exits, not after every viewer disconnect. The supplied unit therefore invokes ethankit-x11vnc-reset-input through -afteraccept and -gone: once after the first viewer authenticates and again after the last viewer disconnects. Treat one virtual display as a single-operator HIL desktop; -shared is not a multi-user collaboration mechanism.

Network and port security

Port/protocolDirectionPublic exposurePurpose
TCP 443OutboundN/AEthanKit HTTPS/WSS and target sites
TCP 22InboundJumpServer/trusted network onlyAdministrative SSH and forwarding
TCP 5900VM loopbackNox11vnc, reached through SSH forwarding
TCP 9234VM loopbackNoRuntime SSRF forward proxy
Random CDP portVM loopbackNoRuntime observation of Chromium

Do not expose ports 5900, 9234, or CDP in a security group. The Runtime needs neither a public IP nor a “development port.”

The Xvfb template permits local access for desktop processes on the same VM. Use a single-purpose VM and dedicated OS account; do not share it with untrusted local users.

Troubleshooting

First verify the expected lifecycle:

preparing_profile -> starting_engine -> waiting_for_cdp
-> starting_stream -> ready -> draining -> stopping -> cleaning -> closed

Follow logs with:

journalctl --user -u ethankit-browser -f
# or
ethankit-browser logs -f

Common symptoms:

  • Stuck at WAITING FOR CDP: confirm Xvfb, Openbox, and the Runtime all use DISPLAY=:99, then find the first-frame screenshot error. Do not run an external watcher that hides or unmaps Chromium before CDP is ready.
  • page.screenshot: Timeout 10000ms exceeded after fonts loaded: the window was probably hidden too early, or the Runtime build is old. Remove external minimization scripts, upgrade, and restart.
  • lost its CDP target/ECONNREFUSED 127.0.0.1:<port>: Chromium changed its CDP endpoint during navigation. Current Runtime builds keep reading DevToolsActivePort and resume the stream; upgrade older builds.
  • Operation screenshots exist but the live view stops: the stream observer is likely still bound to the previous CDP Browser object. Upgrade to a Runtime containing the reconnect fix.
  • VNC is black: confirm ethankit-xvfb, ethankit-openbox, and ethankit-x11vnc are active and that the tunnel targets 127.0.0.1:5900.
  • The browser is too small: confirm Xvfb uses 1920×1080 and the Chromium maximize rule is active in Openbox.
  • Focus keeps moving as if Tab were held during HIL: first confirm Runtime logs show waiting_for_human and no new browser tool call, then check journalctl --user -u ethankit-x11vnc for key_down=23 Tab. That message means VNC/X11 lost KeyUp; it is not a page animation. Release only Tab with DISPLAY=:99 xdotool keyup Tab. If the stuck key is unknown, run DISPLAY=:99 x11vnc -sync -R clear_all while nobody is typing. Verify that focus remains stable, then upgrade to the unit containing the input-reset hooks.
  • SSRF blocked CONNECT <host>: add the exact private destination and wildcard, then restart the Runtime.
  • persistent browser profile is busy: wait for the current persistent session, use isolated mode, or assign another Runtime.
  • A one-off session lease does not match after session close usually means an old response arrived late. It is harmless if the Runtime reconnects; restart and compare API/daemon versions if it repeats.

Upgrade and rollback

Stop the systemd service before reinstalling from a release or branch:

systemctl --user stop ethankit-browser
git pull --ff-only
pnpm install --frozen-lockfile
pnpm browser-runtime:install -- --rebuild

install -d -m 700 ~/.local/libexec ~/.config/systemd/user
install -m 755 \
  deploy/browser-runtime-linux/ethankit-x11vnc-reset-input \
  ~/.local/libexec/
install -m 644 deploy/browser-runtime-linux/*.service \
  ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user restart \
  ethankit-xvfb ethankit-openbox ethankit-x11vnc

systemctl --user start ethankit-browser
ethankit-browser doctor

Do not upgrade during an active HIL handoff; restarting the virtual desktop disconnects VNC and Chromium. An upgrade does not remove ~/.ethankit/browser-runtime.json, config.json, or the dedicated profile. Back up ~/.ethankit/browser-runtime/ before a production upgrade, then verify one live Browser Use Lab run and one HIL handoff.