feat(trpc): provision Blaxel sandboxes for cloud workspaces

Closed
SA
3mo ago
+515-16

Second of five. Stacked on #6489 (cloud_workspaces table) — review that first; this PR's diff includes it until #6489 merges.

Provisions real Blaxel sandboxes and brokers access to them. Nothing in the desktop app calls this yet.

Auth is the whole design

A sandbox's preview URL is private, so Blaxel's edge rejects unauthenticated requests before they reach anything we run. cloudWorkspace.access checks org membership, then mints a 10-minute provider token. The client connects directly to the sandbox with it.

That directness is the point: a proxy on the data path would break terminal websockets, and an outbound relay socket would stop the sandbox ever sleeping. This gets auth without either.

The consequence to be aware of: the sandbox trusts whatever the edge lets through. cloudWorkspace.access is the only authorization gate, so its org check is load-bearing.

Verified against real Blaxel

scripts/sandbox/smoke.ts provisions, mints, probes and tears down:

without token → 401   (edge rejects)
with token    → 502   (reaches the sandbox)
PASS: preview auth gate

The 502 is correct — nothing listens on 4879 until PR 3 puts host-service in the image.

The image (scripts/sandbox/image.ts)

Declarative via the Blaxel SDK rather than a Dockerfile, so it lives in the repo and needs no local Docker. Two constraints, both found by probing a live sandbox:

  • Debian, not Alpine. node-pty's prebuilt binary links glibc (GLIBC_2.28), so on musl it falls back to compiling — which drags in build-base + python3, about 315 MiB.
  • The pinned node-pty version. The stable release ships no prebuilds and compiles on any libc; only the 1.2.0-beta.14 this repo pins carries prebuilds/linux-x64.

Versions are read from packages/host-service/package.json rather than hardcoded — a sandbox running a different better-sqlite3 than host-service was built against is a native-ABI mismatch that surfaces as a runtime crash. The build also asserts the prebuild exists, so a future bump that loses it fails the image build instead of silently compiling.

Verified on the built image: Debian 12, Node 24.19, NO_TOOLCHAIN_CONFIRMED, SQLITE_OK, PTY_OK, git clone works.

Notes

  • No SandboxProvider interface. There's one provider; an interface would be a second thing to keep in sync with no second implementation to justify it. The June spike had one and it earned its reputation.
  • The row is written before the provider call, so a crash mid-provision leaves a reconcilable provisioning row rather than an orphaned sandbox nothing references. Failures flip it to failed, keeping the provider name for cleanup.
  • BLAXEL_API_KEY/BLAXEL_WORKSPACE are optional env: deployments without them can't create cloud workspaces and say so via PRECONDITION_FAILED, rather than failing env validation at boot.

Verification

typecheck 0 · lint 0 · smoke test green against production Blaxel.


Summary by cubic

Provisions Blaxel sandboxes for Cloud Workspaces and exposes them via a new cloudWorkspace tRPC router. Previously we only wrote DB rows; now we create a private preview and mint 10‑minute tokens so clients connect directly while the Blaxel edge keeps previews private.

  • Router: list (ready only); create (write row, deterministically set providerSandboxId, provision, persist sandboxUrl, mark failed on error); access (org check, mint token, return url/token/expiresAt); delete (best‑effort teardown, ignore missing sandbox, mark deleted). Wired into the root; no UI calls it yet.

  • Adds a Blaxel client via @blaxel/core and a Debian-based sandbox image on port 4879 (scripts/sandbox/image.ts). The image reads host‑service native versions (better-sqlite3, node-pty) and asserts node-pty prebuilds exist.

  • scripts/sandbox/smoke.ts provisions, verifies 401 without a token vs not‑401 with a token, then tears down. A 502 is expected until host‑service lands.

  • Ops/rollout

    • Set BLAXEL_API_KEY and BLAXEL_WORKSPACE. Optional: BLAXEL_REGION (default us-pdx-1), BLAXEL_SANDBOX_IMAGE (default superset-hostsvc). Without required envs, methods return PRECONDITION_FAILED; the service still boots.
    • Build/publish the image: bun run scripts/sandbox/image.ts (use --dry to inspect).
    • Smoke test with Blaxel envs set: bun run scripts/sandbox/smoke.ts.