Skip to content
bmuxBack

Portable hosting and remote access

bmux uses the same Electron engine for desktop and unattended hosting. Linux hosts run under a private Xvfb display. This release does not provide a separate Chromium headless-shell engine or claim a smaller browser bundle.

Run a host

Build the OCI image with docker build --target host -t bmux-host . and run:

docker run --rm --shm-size=256m \
  --security-opt seccomp=containers/chromium-seccomp.json \
  -v bmux-data:/data bmux-host

The image runs as UID 1000 with Chromium sandboxing enabled. The seccomp profile permits Chromium's user namespaces; install the corresponding profile on Kubernetes nodes and use a Localhost seccomp profile there. Do not add --no-sandbox. The image supports Debian Bookworm-compatible hosts on Linux x64 and arm64.

To embed bmux, use this image as the base for an application worker. Alternatively, run it beside an application container with the same UID, BMUX_DATA_DIR, and a shared /tmp/bmux-1000 socket directory. Run only one host per data directory. The application container needs Node 22.12+ and the CLI files from resources/bin. Its readiness check must test the socket without auto-starting another host.

Linux release archives contain Electron and the bmux runtime. Install the system libraries listed in the Dockerfile, extract the archive, and invoke node /opt/bmux/resources/bin/bmux.mjs host under Xvfb. The CLI detects the adjacent packaged executable; BMUX_APP overrides it. Mac paths remain unchanged. Linux uses XDG_DATA_HOME/bmux or ~/.local/share/bmux; BMUX_DATA_DIR overrides both. SIGTERM and SIGINT close the runtime. Persistent storage restores saved browser state, not arbitrary JavaScript memory.

Required home egress

Set BMUX_REQUIRED_PROXY=socks5://homeproxy:18081 on the host. It applies to every profile, including newly created ones, and cannot be cleared through the UI or CLI. Use a private HomeProxy gateway protected by NetworkPolicy. This option accepts unauthenticated endpoints; it never embeds credentials in a URL. Authenticated per-profile proxies remain available through the existing desktop UI.

The browser does not fall back to direct egress. A failed upstream connection or proxy error response produces PROXY_UNAVAILABLE for synchronous navigation. Application API/Kafka traffic and the trusted observation connection have separate network paths. This setting does not proxy arbitrary programs launched by workers.

Connection service

Build docker build --target service -t bmux-remote .. Configure:

  • DATABASE_URL: PostgreSQL connection URI for a dedicated database.
  • BMUX_PUBLIC_ORIGIN: public HTTPS origin of this service and viewer.
  • BMUX_GOOGLE_CLIENT_ID: Google web client with that JavaScript origin authorized.
  • BMUX_TURN_SECRET: shared secret for coturn's REST credential authentication.
  • BMUX_TURN_URLS: comma-separated TURN UDP, TCP, and TLS URLs.

The process creates its initial schema and exposes /health. Terminate HTTPS at an ingress that forwards WebSocket upgrades. coturn needs its own reachable relay address and UDP port range; an HTTP ingress cannot carry TURN. Keep the TURN shared secret only in service/TURN secret stores. Runtime code never prints it.

Hosts connect outbound using revocable service credentials. Google login grants access to account discovery and metrics; a host separately approves browser access. The signaling server stores service/device registrations and operational metadata. Video, input, page URLs, titles and session state travel inside WebRTC. Signed negotiation binds SDP fingerprints, recipient, runtime generation, nonce and expiry to an approved Ed25519 key. A viewer pins the service's host key before watching. The web origin serving the viewer remains trusted.

Enroll and pair

On an administrative machine with DATABASE_URL configured:

pnpm remote:admin enroll --owner GOOGLE_ACCOUNT_SUB --service sitelytics-pilot \
  --url https://remote.example.com --output /private/path/host.json

This writes a mode-600 credential file and a separate mode-600 identity file. Mount both into the worker and adjust identityFile in the credential file to its container path. Set BMUX_REMOTE_CONFIG to the credential file. Each service credential is scoped to an account and service. Replica hosts share that service identity and get separate host IDs and runtime generations. Protect the service key as an authority for all of its replicas.

Sign in to the web viewer, connect, and download its public key. Compare the device fingerprint displayed in the viewer, then approve it locally:

pnpm remote:admin approve --config /private/path/host.json \
  --key /path/viewer-public-key.json --fingerprint VERIFIED_DEVICE_FINGERPRINT

Distribute the updated approved-client configuration to the service's hosts. Hosts reread approvals without restarting. Enter the host fingerprint printed by enrollment into the viewer once per service, then select Watch. Use bmux remote status on a host to retrieve its fingerprint and presence.

To revoke locally, use remote:admin revoke --config ... --fingerprint .... Account owners can revoke a device through POST /api/revoke with their login session; this closes its active connections. Setting a service's revoked field in PostgreSQL terminates its discovery connection within 15 seconds. There is no insecure automatic trust fallback if a key changes.

Control and reporting

Watching does not change page selection or viewport. Take control obtains a 30-second renewable session lease. Explicit takeover invalidates the old lease. Competing agent mutations return CONTROL_HELD; DOM and screenshot observation remain available. The desktop status bar offers Reclaim control. Existing page scripts and already-running work can continue. Queued browser mutations recheck control when they execute. Input carries both lease and viewport generations.

Workers report attempt lifecycles through the local socket:

bmux rpc remote.job '{"id":"job-id","attempt":"unique-attempt-id"}'
bmux rpc remote.job '{"id":"job-id","attempt":"unique-attempt-id","result":"succeeded"}'

Use failed for failed attempts. Repeated terminal reports are idempotent within the host. Counts and active browser time are reported as cumulative daily snapshots; the service deduplicates by host, generation, day and sequence and retains 30 days. Reporting is best effort: an unavailable service never pauses browser work, and a crash before the next report can lose recent counters. Application queues remain responsible for job scheduling, retries and durable outcomes.

Verification

pnpm check covers shared types, signatures, replay rejection and lease behavior. Mac GUI tests use Tart with disposable profiles. Build the test Docker target and run scripts/test-linux.sh for Linux capture, the full viewer/control flow, proxy outages, service authentication, reporting and revocation. The TURN-only capture fixture accepts BMUX_TEST_ICE_SERVERS and BMUX_TEST_RELAY_ONLY=1.

The Chromium seccomp profile comes from Playwright v1.63.0: https://github.com/microsoft/playwright/blob/v1.63.0/utils/docker/seccomp_profile.json It extends Docker's default profile for user namespace creation. Its Apache-2.0 license is included in containers/LICENSE-APACHE-2.0.

Edit on GitHub