Skip to content

Commit 975d702

Browse files
coygeeksteipete
andauthored
feat: add NVIDIA Brev provider (#297)
* feat: add nvidia brev provider foundation Register the nvidia-brev provider with aliases, non-secret config and flag plumbing, and read-only doctor checks through the local Brev CLI seam. Keep lifecycle operations as explicit unsupported stubs for the follow-up lifecycle plan while covering config-show redaction and secret flag absence in tests. * feat(nvidia-brev): implement lifecycle and SSH leases Add fakeable Brev CLI lifecycle handling, workspace JSON parsing, generated SSH config target selection, local claim metadata, safe release and cleanup semantics, and rollback coverage for the nvidia-brev provider. * docs(nvidia-brev): add provider docs and live smoke * fix(nvidia-brev): remove dead helpers * fix(nvidia-brev): support stop-mode reuse * fix(nvidia-brev): preserve generic work root * fix(nvidia-brev): harden lifecycle and live smoke * fix(nvidia-brev): persist release policy * fix(nvidia-brev): guard deletes and slug allocation * fix(nvidia-brev): preserve effective config * fix(nvidia-brev): secure lifecycle recovery * fix(nvidia-brev): recover ambiguous creates * fix(nvidia-brev): enforce trusted lifecycle scope * fix(nvidia-brev): secure SSH preparation * fix(nvidia-brev): remove dead helper --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
1 parent 57e1113 commit 975d702

29 files changed

Lines changed: 6530 additions & 8 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
### Added
1616

1717
- Added an idempotent workspace adapter over coordinator leases, with durable owner-scoped lifecycle mapping and truthful capability negotiation for external control planes.
18+
- Added `provider: nvidia-brev` for direct Linux GPU workspaces through the Brev CLI and generated SSH config, including normal Crabbox sync/run access, guarded ownership cleanup, and live `nvidia-smi` smoke coverage. Thanks @coygeek.
1819
- Added a generated provider decision matrix with checked metadata for execution model, access, substrate, GPU fit, lifecycle, cleanup, and provider caveats; docs validation now fails on provider drift. Thanks @coygeek.
1920
- Added confirmed lifecycle actions to portal lease rows, with provider shutdown for coordinator-managed boxes and explicitly metadata-only deregistration for client-managed boxes.
2021
- Added `provider: superserve` for delegated Linux sandbox runs through the Superserve control and data planes, including archive sync, retained leases, ownership-guarded lifecycle operations, and credentialed live smoke coverage. Thanks @coygeek.

docs/provider-backends.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ internal/providers/semaphore # Semaphore SSH lease backend
222222
internal/providers/sprites # Sprites SSH backend
223223
internal/providers/exedev # exe.dev SSH backend
224224
internal/providers/runpod # RunPod GPU pod SSH backend
225+
internal/providers/nvidiabrev # NVIDIA Brev GPU workspace SSH backend
225226
internal/providers/railway # Railway.app delegated backend
226227
internal/providers/blacksmith # Blacksmith Testbox delegated backend
227228
internal/providers/e2b # E2B delegated backend

docs/providers/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ selection metadata. Regenerate it with `node scripts/generate-provider-matrix.mj
6060
`scripts/check-docs.sh` fails when provider registration, metadata, docs paths, or
6161
this generated table drift.
6262

63-
Current built-in surface: 50 providers (30 SSH lease, 19 delegated run, 1 service control).
63+
Current built-in surface: 51 providers (31 SSH lease, 19 delegated run, 1 service control).
6464

6565
Access terms:
6666

@@ -102,6 +102,7 @@ Access terms:
102102
| [mxc](mxc.md) (`execution-container`) | built-in; `delegated-run` · local-sandbox | No SSH; `provider-owned` · direct only; features: none | `windows/normal`; Microsoft Execution Container | `local`; GPU: no | Windows runtime; container termination | Local isolated Windows command execution | Windows host and execution-container support required |
103103
| [namespace-devbox](namespace-devbox.md) (`namespace`, `namespace-devboxes`) | built-in; `ssh-lease` · direct-cloud | Crabbox-managed SSH; `crabbox-sync` · direct only; features: `ssh`, `crabbox-sync`, `cleanup` | `linux`; Namespace Devbox | `provider-managed`; GPU: unknown | Namespace devbox CLI; stop by default; optional delete | Fast managed development box over SSH | Uses the devbox product, not Namespace Compute instances |
104104
| [namespace-instance](namespace-instance.md) (`namespace-compute`) | built-in; `ssh-lease` · direct-cloud | Crabbox-managed SSH; `crabbox-sync` · direct only; features: `ssh`, `crabbox-sync`, `cleanup` | `linux`; Namespace Compute instance | `provider-managed`; GPU: unknown | Namespace nsc CLI; instance delete | Short-lived managed Linux compute over SSH | Requires the nsc CLI and direct provider credentials |
105+
| [nvidia-brev](nvidia-brev.md) (`brev`, `nvidia`) | built-in; `ssh-lease` · gpu-cloud | Crabbox-managed SSH; `crabbox-sync` · direct only; features: `ssh`, `crabbox-sync`, `cleanup` | `linux`; NVIDIA Brev GPU workspace | `provider-managed`; GPU: yes | NVIDIA Brev CLI; delete by default; optional stop | Managed NVIDIA GPU workspace over SSH | Requires Brev CLI auth, quota, and available GPU capacity |
105106
| [opencomputer](opencomputer.md) (`oc`, `open-computer`) | built-in; `delegated-run` · delegated-sandbox | No SSH; `archive-sync` · direct only; features: `archive-sync` | `linux`; OpenComputer Linux VM | `provider-managed`; GPU: unknown | OpenComputer; VM delete | Hosted delegated Linux VM execution | REST execution contract, not an SSH lease |
106107
| [opensandbox](opensandbox.md) | built-in; `delegated-run` · delegated-sandbox | No SSH; `archive-sync` · direct only; features: `archive-sync`, `cleanup` | `linux`; OpenSandbox sandbox | `provider-managed`; GPU: unknown | OpenSandbox; sandbox delete | Hosted delegated sandbox through an open SDK | Requires compatible OpenSandbox control and exec endpoints |
107108
| [parallels](parallels.md) | built-in; `ssh-lease` · local-vm | Crabbox-managed SSH; `crabbox-sync` · direct only; features: `ssh`, `crabbox-sync`, `cleanup`, `desktop`, `browser`, `code`, `workspace-checkpoint`, `workspace-fork`, `workspace-restore`, `provider-snapshot` | `linux`, `macos`, `windows/normal`, `windows/wsl2`; Parallels linked-clone VM | `local`; GPU: no | Crabbox; clone delete | Local macOS, Linux, or Windows VM with snapshots | Requires prepared Parallels source VMs and SSH |

docs/providers/nvidia-brev.md

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
# NVIDIA Brev Provider
2+
3+
Read when:
4+
5+
- choosing `provider: nvidia-brev`;
6+
- using Crabbox on NVIDIA Brev GPU workspaces;
7+
- changing `internal/providers/nvidiabrev`.
8+
9+
NVIDIA Brev is a **direct-only Linux SSH-lease** provider. Crabbox shells out
10+
to the local `brev` CLI to create, list, refresh, stop, and delete Brev
11+
workspaces. After Brev writes its SSH config, Crabbox uses the normal SSH
12+
transport for sync, `run`, `ssh`, `status`, `list`, and `stop`.
13+
14+
Crabbox does not store or accept Brev secrets. Authentication stays in the Brev
15+
CLI's own credential store.
16+
17+
## Prerequisites
18+
19+
- Install the Brev CLI and authenticate it:
20+
21+
```sh
22+
brev login
23+
```
24+
25+
- Confirm Brev can list workspaces:
26+
27+
```sh
28+
brev ls --json
29+
```
30+
31+
- For OAuth login, select the organization used for lifecycle operations:
32+
33+
```sh
34+
brev set
35+
```
36+
37+
API-key and workspace contexts may provide an explicit organization scope
38+
without an active OAuth organization.
39+
40+
- Keep OpenSSH and `rsync` available locally for Crabbox's SSH workflow.
41+
- Use an account with enough Brev quota and a GPU type that is available in the
42+
selected Brev cloud.
43+
44+
## Capabilities
45+
46+
| Capability | Supported |
47+
| --- | --- |
48+
| OS targets | Linux only |
49+
| SSH | Yes, from Brev's generated SSH config |
50+
| Crabbox sync (rsync over SSH) | Yes |
51+
| Provider-managed sync | No |
52+
| Desktop / browser / code | No |
53+
| Actions hydration | Yes, as a normal Linux SSH lease |
54+
| Coordinator (broker) | No - direct only |
55+
| Tailscale | No, Brev CLI-managed SSH access is used |
56+
| Cleanup | Yes, for Crabbox-owned workspaces with local claims |
57+
58+
Aliases: `brev`, `nvidia`.
59+
60+
## Configuration
61+
62+
```yaml
63+
provider: nvidia-brev
64+
target: linux
65+
nvidiaBrev:
66+
cli: brev
67+
type: ""
68+
gpuName: A100
69+
provider: ""
70+
mode: vm
71+
launchable: ""
72+
startupScript: ""
73+
releaseAction: delete
74+
target: container
75+
user: ""
76+
workRoot: /tmp/crabbox
77+
```
78+
79+
Defaults:
80+
81+
- `cli`: `brev`
82+
- `gpuName`: `A100`
83+
- `mode`: `vm`
84+
- `releaseAction`: `delete`
85+
- `target`: `container`
86+
- `workRoot`: `/tmp/crabbox`
87+
88+
`startupScript` follows Brev's native syntax: use an inline command such as
89+
`pip install torch`, or prefix a local file path with `@`, for example
90+
`@setup.sh` or `@/opt/setup.sh`. Local `@file` startup scripts are accepted only
91+
from trusted user config, environment overrides, or command-line flags; project
92+
config cannot select local files.
93+
94+
Provider flags:
95+
96+
```text
97+
--nvidia-brev-cli
98+
--nvidia-brev-org
99+
--nvidia-brev-type
100+
--nvidia-brev-gpu-name
101+
--nvidia-brev-provider
102+
--nvidia-brev-mode
103+
--nvidia-brev-launchable
104+
--nvidia-brev-startup-script
105+
--nvidia-brev-release-action
106+
--nvidia-brev-target
107+
--nvidia-brev-user
108+
--nvidia-brev-work-root
109+
```
110+
111+
Environment overrides:
112+
113+
```text
114+
CRABBOX_NVIDIA_BREV_CLI
115+
CRABBOX_NVIDIA_BREV_ORG
116+
CRABBOX_NVIDIA_BREV_TYPE
117+
CRABBOX_NVIDIA_BREV_GPU_NAME
118+
CRABBOX_NVIDIA_BREV_PROVIDER
119+
CRABBOX_NVIDIA_BREV_MODE
120+
CRABBOX_NVIDIA_BREV_LAUNCHABLE
121+
CRABBOX_NVIDIA_BREV_STARTUP_SCRIPT
122+
CRABBOX_NVIDIA_BREV_RELEASE_ACTION
123+
CRABBOX_NVIDIA_BREV_TARGET
124+
CRABBOX_NVIDIA_BREV_USER
125+
CRABBOX_NVIDIA_BREV_WORK_ROOT
126+
```
127+
128+
`nvidiaBrev.org` scopes read-only inventory through `brev ls --org`. Brev's
129+
mutating commands and `brev refresh` do not accept that selector, so Crabbox
130+
rejects lifecycle and SSH resolution when `org` is configured. Use `brev set`
131+
to select the active organization before running mutating Crabbox commands.
132+
133+
## Lifecycle
134+
135+
### Doctor
136+
137+
`doctor` is read-only. It checks the Brev CLI version and lists the current Brev
138+
inventory:
139+
140+
```sh
141+
crabbox doctor --provider nvidia-brev
142+
crabbox doctor --provider nvidia-brev --json
143+
```
144+
145+
It does not create, stop, or delete a workspace.
146+
147+
### Acquire
148+
149+
`warmup` and `run` create cost-bearing Brev workspaces:
150+
151+
```sh
152+
crabbox warmup --provider nvidia-brev --slug gpu-smoke --keep
153+
crabbox run --provider nvidia-brev -- nvidia-smi
154+
```
155+
156+
Acquire flow:
157+
158+
1. list existing workspaces to allocate a Crabbox slug;
159+
2. run `brev create <name> --detached` with the configured Brev selectors;
160+
3. wait for the workspace to report ready;
161+
4. run `brev refresh`;
162+
5. read the Brev SSH config from `~/.brev/ssh_config`;
163+
6. resolve the configured target alias;
164+
7. wait for SSH readiness;
165+
8. write a local Crabbox lease claim and run the normal SSH workflow.
166+
167+
Crabbox names workspaces as `crabbox-<slug>-<lease-suffix>` so `list` and
168+
`cleanup` can distinguish Crabbox-owned Brev workspaces from manual ones.
169+
170+
If `brev create` returns an ambiguous transport error, Crabbox checks inventory
171+
for the deterministic workspace name and stores a recovery claim. Non-kept
172+
workspaces are deleted when ownership is confirmed; kept workspaces remain
173+
manageable by lease ID or slug. Name-only recovery claims report `failed` until
174+
the workspace appears and can be cleared by explicit release after the recovery
175+
grace period.
176+
177+
### SSH target selection
178+
179+
`nvidiaBrev.target` controls which Brev SSH config host Crabbox selects:
180+
181+
- `container` (default) selects the workspace host alias.
182+
- `host` selects the `<workspace-name>-host` alias.
183+
184+
Brev may emit either a direct `HostName`/`Port` target or a `ProxyCommand`.
185+
Crabbox supports both forms as long as the SSH config entry includes a user and
186+
identity file.
187+
188+
### Release and cleanup
189+
190+
The default release action is `delete`:
191+
192+
```sh
193+
crabbox stop --provider nvidia-brev gpu-smoke
194+
```
195+
196+
`delete` records the claim as deleting, clears its SSH endpoint, runs
197+
`brev delete`, and keeps polling until Brev inventory confirms that the
198+
workspace is absent. The local claim remains available for a later `stop` or
199+
`cleanup` retry if polling is interrupted.
200+
201+
Crabbox stores the active Brev organization ID in each claim. Because Brev
202+
mutations do not accept an organization selector, lifecycle commands reject an
203+
active-org mismatch and retain the claim. Run `brev set` for the lease's
204+
original organization, then retry the command.
205+
206+
If the active organization changes while a workspace is being created, Crabbox
207+
does not delete from either organization automatically. It retains a recovery
208+
claim with both observed organization IDs for manual reconciliation, avoiding
209+
deletion of an unrelated same-named workspace.
210+
211+
If `nvidiaBrev.releaseAction` is `stop`, Crabbox runs `brev stop`, keeps the
212+
local claim, and records the lease as stopped for later reuse or cleanup.
213+
214+
`cleanup` only mutates Brev workspaces that have matching local Crabbox claims:
215+
216+
```sh
217+
crabbox cleanup --provider nvidia-brev --dry-run
218+
crabbox cleanup --provider nvidia-brev
219+
```
220+
221+
Manual Brev workspaces and unclaimed Crabbox-looking workspaces are skipped
222+
rather than deleted blindly.
223+
224+
## Examples
225+
226+
Run a GPU smoke and delete the workspace after the command:
227+
228+
```sh
229+
crabbox run --provider nvidia-brev -- nvidia-smi
230+
```
231+
232+
Warm a reusable GPU workspace, run a CUDA check, then release it:
233+
234+
```sh
235+
crabbox warmup --provider nvidia-brev --slug cuda-box --keep
236+
crabbox run --provider nvidia-brev --id cuda-box --no-sync -- python3 - <<'PY'
237+
print("cuda-ready")
238+
PY
239+
crabbox ssh --provider nvidia-brev --id cuda-box
240+
crabbox status --provider nvidia-brev --id cuda-box --wait
241+
crabbox stop --provider nvidia-brev cuda-box
242+
```
243+
244+
Choose a different Brev GPU selector:
245+
246+
```sh
247+
crabbox run \
248+
--provider nvidia-brev \
249+
--nvidia-brev-gpu-name L40S \
250+
-- nvidia-smi
251+
```
252+
253+
## Live smoke
254+
255+
The repository live smoke is intentionally separate from default CI because it
256+
can create a billable GPU workspace:
257+
258+
```sh
259+
CRABBOX_NVIDIA_BREV_LIVE=1 scripts/live-nvidia-brev-smoke.sh
260+
```
261+
262+
The script builds or reuses `bin/crabbox`, runs `doctor`, creates one workspace
263+
with `--keep=false` so partial acquisition failures roll back, proves
264+
`nvidia-smi` through `crabbox run`, lists the lease, and then deletes the lease
265+
with `crabbox stop`. It prints a stable classification:
266+
267+
- `live_nvidia_brev_smoke_passed` when the full GPU path passes;
268+
- `environment_blocked` for missing CLI/auth/configuration;
269+
- `provider_quota_blocked` for quota or rate-limit failures;
270+
- `capacity_blocked` when Brev cannot allocate the requested GPU;
271+
- `validation_failed` when Crabbox output is malformed or missing the smoke
272+
lease.
273+
274+
Without `CRABBOX_NVIDIA_BREV_LIVE=1`, the script exits successfully with
275+
`classification=environment_blocked reason=CRABBOX_NVIDIA_BREV_LIVE_not_enabled`
276+
and creates no workspace.
277+
278+
## Cost and privacy discipline
279+
280+
- Treat every `warmup` or `run` without an existing `--id` as cost-bearing.
281+
- Prefer `releaseAction: delete` for disposable test runs.
282+
- Run `crabbox list --provider nvidia-brev --all` before and after live tests
283+
when auditing inventory.
284+
- Do not put Brev secrets, organization identifiers, private SSH material, or
285+
local credential files in repository config, docs, scripts, logs, or command
286+
arguments.
287+
- Use neutral slugs and examples in shared repos.
288+
289+
Related docs:
290+
291+
- [Provider reference](README.md)
292+
- [Provider backends](../provider-backends.md)
293+
- [Provider feature overview](../features/providers.md)

docs/providers/provider-metadata.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,20 @@
419419
"caveat": "Windows host and execution-container support required",
420420
"docs": "mxc.md"
421421
},
422+
"nvidia-brev": {
423+
"status": "built-in",
424+
"category": "gpu-cloud",
425+
"substrate": "NVIDIA Brev GPU workspace",
426+
"location": "provider-managed",
427+
"ssh": "crabbox-managed",
428+
"sync": "crabbox-sync",
429+
"gpu": "yes",
430+
"lifecycle": "NVIDIA Brev CLI",
431+
"cleanup": "delete by default; optional stop",
432+
"bestFit": "Managed NVIDIA GPU workspace over SSH",
433+
"caveat": "Requires Brev CLI auth, quota, and available GPU capacity",
434+
"docs": "nvidia-brev.md"
435+
},
422436
"namespace-devbox": {
423437
"status": "built-in",
424438
"category": "direct-cloud",

internal/cli/claim.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,8 @@ func applyLeaseClaimEndpoint(claim *leaseClaim, server Server, target SSHTarget)
535535
}
536536

537537
func claimEndpointInactiveState(state string) bool {
538-
return statusTerminalState(state) || strings.EqualFold(strings.TrimSpace(state), "paused")
538+
state = strings.TrimSpace(state)
539+
return statusTerminalState(state) || strings.EqualFold(state, "paused") || strings.EqualFold(state, "deleting")
539540
}
540541

541542
// updateLeaseClaimTailscale records a tailnet endpoint on an existing claim.

0 commit comments

Comments
 (0)