diff --git a/README.md b/README.md index 72386cd..c4bcfc0 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,7 @@ The Pi has no inbound ports; the browser uses the Pi's own residential network.* ## Demos Captured from real runs against a Raspberry Pi 5 worker (Chromium 154, headless) -and a Mac worker (Chrome 154). See [docs/validation.md](docs/validation.md) for -the full acceptance results. +and a Mac worker (Chrome 154). | | | |---|---| @@ -390,26 +389,6 @@ Run non-local deployments behind TLS (terminate `wss://` at a reverse proxy). `--public-url` accepts either an http(s) or ws(s) URL and is normalised to a WebSocket scheme for lease endpoints. ---- - -## Status - -Milestone 0 and the first agent slice are implemented and tested on macOS/arm64 -with Chrome 154. The same end-to-end suite (relay, profile persistence, profile -locking) also runs against Chrome on Linux x64 in CI. - -- [x] Coordinator, worker, leases, capability filtering, least-loaded scheduling -- [x] Outbound control channel and per-attachment CDP tunnel -- [x] Ordinary Playwright `connectOverCDP` through the relay -- [x] Persistent worker-local profiles with exclusive locking -- [x] Lease TTL, idle cleanup, worker-enforced teardown -- [x] Playwright MCP integration via `devdooth mcp` -- [x] SQLite metadata, single-use enrollment tokens, durable device identity, revocation -- [x] Release automation, container image, install script -- [x] Raspberry Pi 5 (Linux arm64) validated end to end — see [docs/validation.md](docs/validation.md) -- [ ] Windows worker (builds, not yet run) -- [ ] Multi-owner clusters - ## Development ```bash @@ -421,11 +400,11 @@ make smoke # coordinator + worker + Playwright, end to end `scripts/browse.mjs` is a small Playwright CLI for driving a lease one action at a time (goto, click, fill, eval, screenshot, …). It is handy for manual -checks and as the tool surface for an LLM agent loop. See -[docs/validation.md](docs/validation.md) for real-hardware results. +checks and as the tool surface for an LLM agent loop. -See [docs/architecture.md](docs/architecture.md) for the reasoning behind the -design. +See [docs/architecture.md](docs/architecture.md) for the design, +[docs/deployment.md](docs/deployment.md) for production setup, and +[docs/threat-model.md](docs/threat-model.md) for the security model. ## Why Devdooth? diff --git a/docs/architecture.md b/docs/architecture.md index d1034eb..64e3e22 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -83,21 +83,18 @@ The relay copies WebSocket frames, preserving type and boundaries. It sets a 32 MiB message cap and write deadlines. A slow consumer is disconnected; it is never buffered without limit. -## Deliberate omissions +## Non-goals -- **No SDKs yet.** The HTTP API and CLI establish the contract first. -- **Single owner, no ACLs.** Device records carry identity, so adding - `owner_id` later is not a rewrite. -- **No enforced human takeover.** Pausing an opaque CDP stream does not pause an - in-flight agent. Headful sessions let a human act locally; coordinated - ownership is future work. -- **No browser downloads.** Workers use installed browsers. This avoids - packaging and redistribution work before the core is proven. +- **No SDKs.** The HTTP API and CLI are the contract; any language can call them. +- **Single owner, no ACLs.** Device records carry identity, so ownership can be + layered on without a redesign. +- **Cooperative take-over, not enforcement.** Pausing detaches the controller and + hands the window to a human; it does not stop a command already in flight. +- **No bundled browsers.** Workers use the browsers already on the machine. -## Verified +## Test coverage -`make smoke` and `go test ./e2e/` prove, on macOS/arm64 with Chrome 154 (and -against Chrome on Linux x64 in CI): +`make smoke` and `go test ./e2e/` exercise the whole path with a real browser: - a worker launches Chrome with a loopback-only debug port; - a normal Playwright client connects over CDP through the relay and drives a @@ -106,9 +103,6 @@ against Chrome on Linux x64 in CI): - a second lease on a locked profile is rejected; - `devdooth mcp` hands the leased browser to upstream Playwright MCP. -A Raspberry Pi 5 worker was validated end to end on real hardware (Linux arm64, -Chromium 154): outbound enrollment, capability reporting, a Playwright client on -another host, traffic proven to originate from the Pi, profile persistence, -loopback-only debugging, worker-kill detection, and reconnect identity. An LLM -agent completed a real Amazon search through the leased Pi browser. See -[docs/validation.md](validation.md). +Unit and integration tests cover enrollment and revocation, pause/resume, +coordinator restart, capability scheduling, and the relay's frame bounds. The +suite runs on macOS arm64 and on Linux x64 in CI. diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..7483ab5 --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,154 @@ +# Deployment + +How to run Devdooth for real: a coordinator behind TLS, and workers on the +machines that lend their browsers. + +## Coordinator + +The coordinator holds device metadata and relays CDP. Put it on a small host +with a stable address. Workers connect outbound, so only the coordinator needs +an inbound port. + +```bash +devdooth coordinator \ + --addr 127.0.0.1:8080 \ + --store /var/lib/devdooth/devdooth.db \ + --admin-token "$(cat /etc/devdooth/admin-token)" \ + --public-url https://devdooth.example.com +``` + +- `--addr 127.0.0.1:8080` keeps it on loopback; the reverse proxy faces the + internet. +- `--store` is a single SQLite file (device identities and enrollment tokens). +- `--public-url` is the address callers use. It accepts an `https://` or + `wss://` URL and is normalised to `wss://` for lease endpoints. +- If `--admin-token` is omitted, one is generated and printed once. + +### TLS with Caddy + +``` +devdooth.example.com { + reverse_proxy 127.0.0.1:8080 +} +``` + +Caddy gets and renews certificates automatically and proxies WebSockets without +extra configuration. + +### TLS with nginx + +```nginx +server { + listen 443 ssl; + server_name devdooth.example.com; + + ssl_certificate /etc/letsencrypt/live/devdooth.example.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/devdooth.example.com/privkey.pem; + + location / { + proxy_pass http://127.0.0.1:8080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_read_timeout 1h; + } +} +``` + +`proxy_read_timeout` must be generous: a lease holds a long-lived WebSocket. + +### systemd unit + +```ini +[Unit] +Description=Devdooth coordinator +After=network-online.target + +[Service] +ExecStart=/usr/local/bin/devdooth coordinator --addr 127.0.0.1:8080 --store /var/lib/devdooth/devdooth.db --public-url https://devdooth.example.com +EnvironmentFile=/etc/devdooth/coordinator.env +StateDirectory=devdooth +Restart=on-failure + +[Install] +WantedBy=multi-user.target +``` + +`/etc/devdooth/coordinator.env` holds `DEVDOOTH_ADMIN_TOKEN=...` with `0600` +permissions. `GET /healthz` is available for a load balancer or uptime check. + +## Workers + +Install once, enroll, and run the worker on each machine that should lend a +browser. The worker needs no inbound port. + +```bash +# enroll (once) as the same user that will run the service, into its data dir +devdooth join --coordinator https://devdooth.example.com \ + --enroll-token "" --name macbook \ + --data-dir /var/lib/devdooth-worker + +# run the worker +devdooth worker --coordinator https://devdooth.example.com \ + --data-dir /var/lib/devdooth-worker --profiles shopping,work +``` + +- **Headful** machines (a desktop) add `--headful` so the browser is visible for + human take-over. +- **Containers / locked-down CI** may need `--no-sandbox`. +- Give each worker a distinct `--data-dir` if you run more than one per host. + +### systemd unit + +Enroll with the same user and `--data-dir` the unit uses, so the service can +find the device identity. + +```ini +[Unit] +Description=Devdooth worker +After=network-online.target + +[Service] +User=devdooth +ExecStart=/usr/local/bin/devdooth worker --coordinator https://devdooth.example.com --data-dir /var/lib/devdooth-worker --profiles shopping,work +Restart=always +RestartSec=3 + +[Install] +WantedBy=multi-user.target +``` + +### Enroll and revoke + +Mint a token per machine and revoke when a machine is retired: + +```bash +devdooth enroll-token --url https://devdooth.example.com --token "$ADMIN" --label macbook +devdooth devices --url https://devdooth.example.com --token "$ADMIN" +devdooth devices revoke --url https://devdooth.example.com --token "$ADMIN" --id dev_... +``` + +## Clients + +Callers reach the coordinator over HTTPS and connect to the returned `wss://` +endpoint with any CDP client: + +```bash +devdooth lease --url https://devdooth.example.com --token "$ADMIN" --profile shopping --headful +``` + +```python +browser = await playwright.chromium.connect_over_cdp(endpoint) +``` + +For an LLM agent, `devdooth mcp` leases a browser and supervises Playwright MCP, +so an MCP client can drive it without extra wiring. + +## Checklist + +- [ ] Coordinator on a dedicated host, bound to loopback, behind TLS. +- [ ] Admin token stored with `0600`; workers use enrolled device identities. +- [ ] Only the coordinator exposes an inbound port. +- [ ] Workers run under an account that holds only the profiles you intend to lend. +- [ ] Device tokens revoked for machines no longer in service. diff --git a/docs/index.html b/docs/index.html index d9768bc..1f4918f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -471,33 +471,12 @@

Limitations

- -
-

Status

-

Pre-1.0, tested on macOS arm64 and Linux amd64 with real Chromium.

-

Implemented

-

- coordinator + worker + leases - CDP relay & Playwright - worker-local profiles - enrollment & revocation - Playwright MCP - release binaries - Raspberry Pi validated - human take-over (pause/resume) -

-

Planned / not yet validated

-

- Windows worker - multi-owner clusters -

-