From cb3ee44e35bda7c2b73dfdc6494dbbf12447eb33 Mon Sep 17 00:00:00 2001 From: Benjamin Swerdlow Date: Tue, 1 Sep 2026 22:33:01 -0700 Subject: [PATCH 1/5] Remove Blaxel; move Freestyle to the public platform (0.2.9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Blaxel is gone as a Cloud VM provider, and the two Freestyle arms collapse into one driver on the public platform (api.freestyle.sh, freestyle@0.2.9). Provider removal: - Delete drivers/blaxel.ts, images/blaxel/, build-blaxel-image.sh, and test-blaxel-vm-poc.ts; drop "blaxel" from ProviderId, the driver registry, the create kill switch, and the image manifest. - defaultProviderId() is now "freestyle" (load-dev-env.sh follows). - A migration rebuilds the vm_provider enum without 'blaxel', rewriting any surviving rows to 'e2b' first. The trailing DROP TYPE is the interlock: a missed column aborts the whole transaction instead of splitting the schema. Freestyle collapse: - freestyleBeta.ts is promoted to the only freestyle driver. The legacy 0.1.x arm (SSH gateway, cmuxd-remote WebSocket PTY on 7777) is deleted along with POST /api/vm/:id/ssh-endpoint, the openSshEndpoint workflow, the dead bakedFreestyleSignedAdmin plumbing, and wsLease.ts. - Every guest command now pins linuxUser: "root". The 0.2 API's default is "uid 1000, or root if absent" and the devbox image ships such a user, so an unpinned exec would move the daemon, its install, and the model-plane write off the root layout they are baked around. - providerImageNotFound() also recognizes snapshot-not-found: Freestyle resolves an image to a snapshot id, so its missing-image answer is a 404 on the snapshot rather than an IMAGE_NOT_FOUND code. - The reaper's orphan-volume scan no longer hardcodes a provider. Volumes were Blaxel-only; it now asks the registry and reports partial coverage when no driver exposes an inventory. The desktop/noVNC seam stays (wrapper, route, image kind) for Freestyle desktop support later, minus the retired gateway's preview domain. Known-pending: the devbox snapshot sh-fb3dcf7b… was baked against beta-api and is marked validationStatus "unknown", so Freestyle creates fail closed until build-devbox-freestyle.ts re-bakes it on the public platform and the new id lands in the manifest. The env audit asserts this rather than hiding it. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01AhaCvb89W567Qugstm8Gdq --- .github/workflows/cloud-vm-env-audit.yml | 4 +- CLI/cmux.swift | 20 +- Resources/Localizable.xcstrings | 2 +- Sources/Cloud/VMMachineKind.swift | 4 +- Sources/SettingsSearchAliases.swift | 2 +- docs/cloud-cmux-tui-daemon.md | 41 +- .../references/sidebar-parity.md | 4 +- web/app/api/vm/[id]/attach-endpoint/route.ts | 2 +- web/app/api/vm/[id]/ssh-endpoint/route.ts | 54 - web/app/api/vm/base/routeShared.ts | 2 - web/app/api/vm/route.ts | 2 - web/bun.lock | 82 +- .../migration.sql | 33 + web/db/schema.ts | 2 +- web/package.json | 3 +- web/scripts/build-blaxel-image.sh | 32 - web/scripts/build-devbox-freestyle.ts | 39 +- web/scripts/cloud-vm/defaultProviderAudit.mjs | 15 +- web/scripts/cloud-vm/projects.mjs | 12 +- web/scripts/cloud-vm/smoke-vm-api.mjs | 18 +- web/scripts/cloud-vm/stress-vm-api.mjs | 8 +- web/scripts/devbox-image-common.ts | 2 +- web/scripts/load-dev-env.sh | 7 +- web/scripts/test-blaxel-vm-poc.ts | 118 - web/scripts/test-cloud-vm-ws-auth.ts | 139 +- web/scripts/verify-devbox-image.ts | 17 +- web/services/coderouter/vmModelPlane.ts | 2 +- web/services/vms/README.md | 149 +- web/services/vms/config.ts | 2 - web/services/vms/desktopWrapper.ts | 26 +- web/services/vms/drivers/blaxel.ts | 2471 ----------------- web/services/vms/drivers/cmuxTuiDaemon.ts | 39 +- web/services/vms/drivers/daytona.ts | 4 +- web/services/vms/drivers/e2b.ts | 8 +- web/services/vms/drivers/freestyle.ts | 1300 +++------ web/services/vms/drivers/freestyleBeta.ts | 620 ----- web/services/vms/drivers/index.ts | 26 +- web/services/vms/drivers/types.ts | 14 +- web/services/vms/drivers/wsLease.ts | 74 - web/services/vms/entitlements.ts | 2 +- web/services/vms/errors.ts | 4 +- web/services/vms/images/blaxel/Dockerfile | 307 -- web/services/vms/images/blaxel/WALLPAPER.md | 18 - .../vms/images/blaxel/agent-config.sh | 116 - web/services/vms/images/blaxel/blaxel.toml | 16 - .../images/blaxel/chrome-managed-policy.json | 11 - web/services/vms/images/blaxel/cmux-bashrc | 70 - web/services/vms/images/blaxel/entrypoint.sh | 71 - .../vms/images/blaxel/ghostty-cmux.desktop | 10 - .../images/blaxel/google-chrome-cmux.desktop | 10 - web/services/vms/images/blaxel/seed-history | 2 - web/services/vms/images/blaxel/start-vnc.sh | 96 - .../vms/images/blaxel/thunar-cmux.desktop | 10 - web/services/vms/images/blaxel/tint2rc | 42 - web/services/vms/images/blaxel/wallpaper.jpg | Bin 698487 -> 0 bytes web/services/vms/images/devbox/Dockerfile | 13 +- web/services/vms/images/devbox/README.md | 16 +- .../vms/images/devbox/agent-config.sh | 4 +- web/services/vms/images/devbox/cmux-bashrc | 2 +- web/services/vms/images/manifest.json | 134 +- web/services/vms/images/resolver.ts | 26 +- web/services/vms/reaper.ts | 39 +- web/services/vms/routeHelpers.ts | 42 +- web/services/vms/workflows.ts | 58 +- web/tests/cloud-vm-env-audit.test.ts | 98 +- web/tests/vm-access-revocation.test.ts | 4 +- web/tests/vm-blaxel-fetch-retry.test.ts | 244 -- web/tests/vm-blaxel-image.test.ts | 315 --- web/tests/vm-blaxel-provider.test.ts | 904 ------ ...l-cmux-tui.test.ts => vm-cmux-tui.test.ts} | 14 +- web/tests/vm-create-kill-switch.test.ts | 10 +- web/tests/vm-daytona-provider.test.ts | 2 +- web/tests/vm-desktop-wrapper.test.ts | 5 +- web/tests/vm-devbox-image.test.ts | 120 +- web/tests/vm-e2b-provider.test.ts | 4 +- web/tests/vm-freestyle-provider.test.ts | 312 +-- web/tests/vm-image-resolver.test.ts | 237 +- web/tests/vm-limit-refresh.test.ts | 24 +- web/tests/vm-observability.test.ts | 2 +- web/tests/vm-reaper.test.ts | 41 +- web/tests/vm-route-auth.test.ts | 122 +- web/tests/vm-route-input.test.ts | 2 +- .../vm-snapshot-not-found-dispatch.test.ts | 2 +- web/tests/vm-unsupported-op.test.ts | 73 +- web/tests/vm-workflows.test.ts | 460 +-- 85 files changed, 1172 insertions(+), 8341 deletions(-) delete mode 100644 web/app/api/vm/[id]/ssh-endpoint/route.ts create mode 100644 web/db/migrations/20260901120000_remove_blaxel_vm_provider/migration.sql delete mode 100755 web/scripts/build-blaxel-image.sh delete mode 100644 web/scripts/test-blaxel-vm-poc.ts delete mode 100644 web/services/vms/drivers/blaxel.ts delete mode 100644 web/services/vms/drivers/freestyleBeta.ts delete mode 100644 web/services/vms/drivers/wsLease.ts delete mode 100644 web/services/vms/images/blaxel/Dockerfile delete mode 100644 web/services/vms/images/blaxel/WALLPAPER.md delete mode 100644 web/services/vms/images/blaxel/agent-config.sh delete mode 100644 web/services/vms/images/blaxel/blaxel.toml delete mode 100644 web/services/vms/images/blaxel/chrome-managed-policy.json delete mode 100644 web/services/vms/images/blaxel/cmux-bashrc delete mode 100755 web/services/vms/images/blaxel/entrypoint.sh delete mode 100644 web/services/vms/images/blaxel/ghostty-cmux.desktop delete mode 100644 web/services/vms/images/blaxel/google-chrome-cmux.desktop delete mode 100644 web/services/vms/images/blaxel/seed-history delete mode 100755 web/services/vms/images/blaxel/start-vnc.sh delete mode 100644 web/services/vms/images/blaxel/thunar-cmux.desktop delete mode 100644 web/services/vms/images/blaxel/tint2rc delete mode 100644 web/services/vms/images/blaxel/wallpaper.jpg delete mode 100644 web/tests/vm-blaxel-fetch-retry.test.ts delete mode 100644 web/tests/vm-blaxel-image.test.ts delete mode 100644 web/tests/vm-blaxel-provider.test.ts rename web/tests/{vm-blaxel-cmux-tui.test.ts => vm-cmux-tui.test.ts} (98%) diff --git a/.github/workflows/cloud-vm-env-audit.yml b/.github/workflows/cloud-vm-env-audit.yml index f1481c86880d..d05b8c325efc 100644 --- a/.github/workflows/cloud-vm-env-audit.yml +++ b/.github/workflows/cloud-vm-env-audit.yml @@ -1,8 +1,8 @@ name: Cloud VM env audit # Guards against deployed-env / code drift: the 2026-08-26 outage shipped code -# that assumed CMUX_VM_DEFAULT_PROVIDER=blaxel while production still said -# freestyle, and every Cloud VM create 503ed for two days with nothing paging. +# that assumed one CMUX_VM_DEFAULT_PROVIDER while production still said +# another, and every Cloud VM create 503ed for two days with nothing paging. # This audit pulls the real production env and fails when the default # provider's image value is missing from the image manifest, so drift is a red # workflow on merge (and daily) instead of a user-facing outage. diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 74756a50769c..908be2d66f60 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -4294,7 +4294,7 @@ struct CMUXCLI { // never pins an image id unless the person passes `--image`: a pinned id // that drifted from the web deploy's manifest failed every create with // `vm_image_config_error`. - /// `--size` spellings → memory in MB. vCPUs scale with memory on Blaxel. + /// `--size` spellings → memory in MB. vCPUs scale with memory. private static let cloudVMSizeAliases: [String: Int] = [ "2g": 2048, "2gb": 2048, "small": 2048, "4g": 4096, "4gb": 4096, "medium": 4096, @@ -4597,7 +4597,7 @@ struct CMUXCLI { return nil } let normalized = trimmed.lowercased() - guard normalized == "e2b" || normalized == "freestyle" || normalized == "daytona" || normalized == "blaxel" else { + guard normalized == "e2b" || normalized == "freestyle" || normalized == "daytona" else { throw CLIError(message: """ vm new: unsupported Cloud VM service override. @@ -5930,8 +5930,8 @@ struct CMUXCLI { } if let normalizedProvider { params["provider"] = normalizedProvider } // Size is independent of the image/provider override. Providers that do - // not expose sizing ignore this optional field; Blaxel uses it for runtime - // memory and the backend applies the plan ceiling. + // not expose sizing ignore this optional field; providers that do use it + // for runtime memory get it, and the backend applies the plan ceiling. if let memoryMb { params["memory_mb"] = memoryMb } // The persistent per-machine home is keyed off whether the *person* // overrode the image/provider (`imageOptRaw`), not the CLI-injected @@ -5943,8 +5943,8 @@ struct CMUXCLI { providerOption: providerOpt ) // The persistent-default create sends no provider override: the backend's - // CMUX_VM_DEFAULT_PROVIDER decides, with Blaxel as the default. An explicit - // provider remains available for deliberate rollback/provider experiments. + // CMUX_VM_DEFAULT_PROVIDER decides, with Freestyle as the default. An + // explicit provider remains available for deliberate rollback/experiments. if usesPersistentDefaultCloud { // Every new machine is its own persistent computer: the backend mounts a // volume derived from the machine's generated name, so `vm new` mints a @@ -13061,7 +13061,7 @@ struct CMUXCLI { logVMTiming( "base.open", vmID: (response["id"] as? String) ?? "?", - provider: (response["provider"] as? String) ?? "blaxel", + provider: (response["provider"] as? String) ?? "freestyle", startedAt: vmCreateStartedAt ) @@ -13071,7 +13071,7 @@ struct CMUXCLI { } let id = (response["id"] as? String) ?? "?" - let provider = (response["provider"] as? String) ?? "blaxel" + let provider = (response["provider"] as? String) ?? "freestyle" let image = (response["image"] as? String) ?? "?" let base = response["base"] as? [String: Any] let generation = (base?["generation"] as? Int) ?? (base?["generation"] as? NSNumber)?.intValue @@ -13151,7 +13151,7 @@ struct CMUXCLI { logVMTiming( "base.reset", vmID: (response["id"] as? String) ?? "?", - provider: (response["provider"] as? String) ?? "blaxel", + provider: (response["provider"] as? String) ?? "freestyle", startedAt: vmCreateStartedAt ) @@ -13161,7 +13161,7 @@ struct CMUXCLI { } let id = (response["id"] as? String) ?? "?" - let provider = (response["provider"] as? String) ?? "blaxel" + let provider = (response["provider"] as? String) ?? "freestyle" let image = (response["image"] as? String) ?? "?" let base = response["base"] as? [String: Any] let generation = (base?["generation"] as? Int) ?? (base?["generation"] as? NSNumber)?.intValue diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index a993948edc46..0089e7886571 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -193763,7 +193763,7 @@ "en": { "stringUnit": { "state": "translated", - "value": "cloud machines vm virtual machine persistent computer sandbox plan upgrade fleet blaxel" + "value": "cloud machines vm virtual machine persistent computer sandbox plan upgrade fleet freestyle" } }, "ja": { diff --git a/Sources/Cloud/VMMachineKind.swift b/Sources/Cloud/VMMachineKind.swift index 1be3ae4efef4..c21be7fa62e3 100644 --- a/Sources/Cloud/VMMachineKind.swift +++ b/Sources/Cloud/VMMachineKind.swift @@ -4,8 +4,8 @@ import Foundation /// plane happens to deploy for it today. /// /// Clients request machines by kind and let the backend map the kind to the -/// image its environment supports (`BLAXEL_SANDBOX_DESKTOP_IMAGE`, -/// `BLAXEL_SANDBOX_IMAGE`, or the deployed manifest default). Pinning an +/// image its environment supports (the provider's `_DESKTOP_IMAGE` selector, +/// its base image selector, or the deployed manifest default). Pinning an /// image id on the client broke every build whose id drifted from the web /// deploy's manifest (`vm_image_config_error`), so the id is never sent unless /// a person passes `--image` explicitly. diff --git a/Sources/SettingsSearchAliases.swift b/Sources/SettingsSearchAliases.swift index fbf1b5f2ef4a..2e2b7055fa78 100644 --- a/Sources/SettingsSearchAliases.swift +++ b/Sources/SettingsSearchAliases.swift @@ -16,7 +16,7 @@ enum SettingsSearchAliasIndex { case .mobile: return localized("settings.search.alias.section.mobile", defaultValue: "ios iphone ipad mobile pairing local network permission sync") case .cloudMachines: - return localized("settings.search.alias.section.cloudMachines", defaultValue: "cloud machines vm virtual machine persistent computer sandbox plan upgrade fleet blaxel") + return localized("settings.search.alias.section.cloudMachines", defaultValue: "cloud machines vm virtual machine persistent computer sandbox plan upgrade fleet freestyle") case .networking: return localized("settings.search.alias.section.networking", defaultValue: "iroh relay relays server fleet provider region custom self hosted private network tailscale vpn direct peer") case .sidebarAppearance: diff --git a/docs/cloud-cmux-tui-daemon.md b/docs/cloud-cmux-tui-daemon.md index 534d339555fa..5e7bd66a0a2f 100644 --- a/docs/cloud-cmux-tui-daemon.md +++ b/docs/cloud-cmux-tui-daemon.md @@ -1,8 +1,8 @@ # Cloud VMs on the cmux-tui remote daemon Design for replacing the Go `cmuxd-remote` daemon in Cloud VMs with the -cmux-tui remote daemon, validated by a working transport spike -(`scripts/spike-cmux-tui-blaxel.sh`). North star: every cloud terminal is a +cmux-tui remote daemon, validated by a working transport spike. North star: +every cloud terminal is a cmux-tui terminal, the macOS app renders it through the Ghostty manual-IO surface, and any cmux-tui terminal (cloud, ssh, local) can be attached by dragging it out of the right pane. @@ -38,8 +38,11 @@ The cmux-tui stack already solves each of these on `main`: ## What the spike proved (2026-08-26) -All steps are automated in `scripts/spike-cmux-tui-blaxel.sh` and were run -against a live Blaxel sandbox: +Historical record. The spike ran against a live Blaxel sandbox; Blaxel has +since been removed as a provider (its driver, images, and build scripts are +gone) and Freestyle on the public platform is the default. The transport +conclusions below still describe how every cmux Cloud machine works, but the +Blaxel-specific mechanics are history, not current code: 1. A static musl `cmux-tui` (55 MB stripped, built on a Blacksmith testbox in 1m47s warm) runs unmodified in a `blaxel/base-image` microVM. @@ -68,7 +71,7 @@ against a live Blaxel sandbox: An Aug-20 client binary interoperated with a daemon built from `main` tip, consistent with the protocol-version gate doing its job (both protocol 5). -## Local repro without Blaxel credentials +## Local repro without provider credentials `scripts/spike-cmux-tui-local.sh` runs the same protocol loop with a local `server start --remote-ws 127.0.0.1:` process standing in for the VM: @@ -90,35 +93,33 @@ never on a connection-scoped lease. One artifact replaces `cmuxd-remote-linux-amd64` everywhere: `cmux-tui-x86_64-unknown-linux-musl` from the existing package lane, pinned by -sha256 exactly as `CMUX_VM_BLAXEL_DAEMON_URL`/`_SHA256` pin the Go binary -today. The per-provider delivery mechanisms stay what they are: +sha256, from the artifacts manifest. The per-provider delivery mechanisms stay +what they are: | provider | today | change | | --- | --- | --- | -| blaxel | gzip+base64 runtime injection at create | same path, chunked upload or URL fetch (binary is ~4x larger); start `server start --remote-ws` instead of `serve --ws` | | e2b | baked into template by `web/scripts/build-cloud-vm-images.ts` | swap the copied binary and start command | | daytona | baked into snapshot, entrypoint restarts it | same swap; the driver's repair exec restarts `server start` | | freestyle | systemd unit in the VM snapshot | same swap in the unit file | The daemon's remote state dir must live on the persistent volume (the machine's -home: `/home/cmux` on blaxel — the daemon and every terminal pane run as the -non-root `cmux` user with passwordless sudo; sandboxes created before that -change keep their volume at `/root` and stay root until resurrected, and a -machine where the user is unusable — missing user or runuser, or a failed -bindfs identity view — degrades to a root daemon rather than failing — hence -the HOME-derived default `~/.local/state/cmux/remote` already qualifies) +home; every current provider runs the daemon as root with `HOME=/root`, so the +HOME-derived default `~/.local/state/cmux/remote` already qualifies. The +non-root layout described below (`CMUX_CLOUD_LAYOUT`) is retained as a seam +but no driver selects it today.) so daemon identity and enrolled devices survive sandbox resurrection. Session state (`--state`) lives there too, so workspace layout restores from the journal checkpoint after a daemon restart; running processes do not survive a restart, and clients see the generation change instead of a silent new shell. -On a Blaxel layout machine, the daemon watches the bindfs home view for mount -events. If the view disappears, the supervisor stops the user daemon and exits -with a restartable failure code. Blaxel starts the command again, which reruns +On a layout machine, the daemon watches the bindfs home view for mount events. +If the view disappears, the supervisor stops the user daemon and exits with a +restartable failure code. The provider starts the command again, which reruns the idempotent user setup and repairs the view before selecting the non-root daemon. If repair fails, it detects the still-mounted `/cmux/home` backing path and runs the daemon there as root. Active terminals therefore do not continue -writing into the disposable rootfs directory. +writing into the disposable rootfs directory. No provider selects this layout +today; it is kept for a future non-root cloud home. ## Lease/auth integration with the attach-endpoint flow @@ -197,8 +198,8 @@ grid, matching current cmuxd-remote semantics. ## Rollout -Phase 1: ship the cmux-tui daemon alongside cmuxd-remote (second port, -blaxel first since it needs no image rebake), attach-endpoint returns both +Phase 1: ship the cmux-tui daemon alongside cmuxd-remote (second port), +attach-endpoint returns both transports, macOS opts in behind a feature flag. Phase 2: default new attaches to `cmux-remote`, keep `websocket` as fallback for one release. Phase 3: delete the Go daemon path per provider, then the `daemon/remote` diff --git a/skills/cmux-cloud-vm/references/sidebar-parity.md b/skills/cmux-cloud-vm/references/sidebar-parity.md index de4956199d63..b081ac20d7cf 100644 --- a/skills/cmux-cloud-vm/references/sidebar-parity.md +++ b/skills/cmux-cloud-vm/references/sidebar-parity.md @@ -15,8 +15,8 @@ Machine row › **Open Full cmux-tui Client** | `cmux vm tui ` | (pane comman Machine row › **Refresh**, any group › Refresh | `cmux vm tree --refresh` / `cmux surface ls --refresh` | `vm.tree {refresh}` | ✅ Machine row › **Rename…** | `cmux vm rename