docs: document lease slugs and idle timeout
This commit is contained in:
parent
42a7e5129c
commit
4957aba8a1
30
README.md
30
README.md
@ -51,20 +51,20 @@ The **runner** is just an Ubuntu machine bootstrapped by cloud-init. Bootstrap c
|
||||
The normal lifecycle is:
|
||||
|
||||
1. `crabbox run --class standard -- <command>` loads local config.
|
||||
2. CLI sends `POST /v1/leases` with provider, class, TTL, SSH public key, and bootstrap options.
|
||||
3. Worker creates a Hetzner server or AWS Spot instance and stores the lease.
|
||||
2. CLI sends `POST /v1/leases` with provider, class, TTL, idle timeout, slug, SSH public key, and bootstrap options.
|
||||
3. Worker creates a Hetzner server or AWS Spot instance and stores the lease metadata, including `lastTouchedAt` and idle expiry.
|
||||
4. CLI waits for `crabbox-ready` over SSH.
|
||||
5. CLI seeds remote Git when possible, then rsyncs tracked plus nonignored untracked files into `/work/crabbox/<lease>/<repo>`.
|
||||
6. CLI records sync fingerprints, enforces sync size/time guardrails, runs sync sanity checks, and hydrates configured base-ref history.
|
||||
7. CLI runs the command over SSH and returns the remote exit code.
|
||||
8. CLI releases the lease; the broker terminates the machine unless it was kept.
|
||||
8. CLI releases the lease unless it was kept; kept leases still auto-release after idle timeout.
|
||||
|
||||
The GitHub Actions hydration lifecycle reuses the same machines, but lets the repository's workflow define setup:
|
||||
|
||||
1. `crabbox warmup --idle-timeout 90m` leases a reusable box.
|
||||
2. `crabbox actions hydrate --id cbx_...` registers that box as an ephemeral GitHub Actions runner, dispatches the configured workflow, and waits for the workflow to write a ready marker.
|
||||
1. `crabbox warmup` leases a reusable box and prints both a stable `cbx_...` ID and a friendly slug.
|
||||
2. `crabbox actions hydrate --id blue-lobster` registers that box as an ephemeral GitHub Actions runner, dispatches the configured workflow, and waits for the workflow to write a ready marker.
|
||||
3. The workflow runs normal Actions steps such as checkout, dependency install, cache/service setup, and secret-backed environment hydration.
|
||||
4. `crabbox run --id cbx_... -- <command>` syncs the local dirty checkout into the hydrated `$GITHUB_WORKSPACE`, sources the workflow's non-secret env handoff, and runs commands there.
|
||||
4. `crabbox run --id blue-lobster -- <command>` syncs the local dirty checkout into the hydrated `$GITHUB_WORKSPACE`, sources the workflow's non-secret env handoff, and runs commands there.
|
||||
|
||||
Crabbox does not parse or reimplement GitHub Actions YAML. The project-owned workflow decides what to install and when the machine is ready. GitHub secrets and OIDC request tokens remain workflow-step scoped unless that workflow intentionally persists its own short-lived handoff.
|
||||
|
||||
@ -166,34 +166,34 @@ bin/crabbox init
|
||||
Warm a reusable testbox:
|
||||
|
||||
```sh
|
||||
bin/crabbox warmup --profile project-check --class beast --idle-timeout 90m
|
||||
bin/crabbox warmup --profile project-check --class beast
|
||||
```
|
||||
|
||||
Hydrate that box through the repo's GitHub Actions setup, then run local tests inside the hydrated workspace:
|
||||
|
||||
```sh
|
||||
bin/crabbox actions hydrate --id cbx_...
|
||||
CI=1 bin/crabbox run --id cbx_... -- pnpm test:changed:max
|
||||
bin/crabbox actions hydrate --id blue-lobster
|
||||
CI=1 bin/crabbox run --id blue-lobster -- pnpm test:changed:max
|
||||
```
|
||||
|
||||
Use AWS EC2 Spot through the broker:
|
||||
|
||||
```sh
|
||||
bin/crabbox warmup --class beast --idle-timeout 90m
|
||||
bin/crabbox warmup --class beast
|
||||
```
|
||||
|
||||
Run a command on an existing lease:
|
||||
|
||||
```sh
|
||||
CI=1 bin/crabbox run --id cbx_... -- pnpm test:changed:max
|
||||
CI=1 bin/crabbox run --id blue-lobster -- pnpm test:changed:max
|
||||
```
|
||||
|
||||
Inspect and connect:
|
||||
|
||||
```sh
|
||||
bin/crabbox status --id cbx_...
|
||||
bin/crabbox ssh --id cbx_...
|
||||
bin/crabbox inspect --id cbx_... --json
|
||||
bin/crabbox status --id blue-lobster
|
||||
bin/crabbox ssh --id blue-lobster
|
||||
bin/crabbox inspect --id blue-lobster --json
|
||||
```
|
||||
|
||||
Inspect usage and estimated cost:
|
||||
@ -209,7 +209,7 @@ bin/crabbox usage --scope all --json
|
||||
Stop a kept server:
|
||||
|
||||
```sh
|
||||
bin/crabbox stop cbx_...
|
||||
bin/crabbox stop blue-lobster
|
||||
```
|
||||
|
||||
Print the CLI version:
|
||||
|
||||
@ -22,11 +22,11 @@ The CLI is a Go binary. The broker is a Cloudflare Worker plus a single Durable
|
||||
## A run, end to end
|
||||
|
||||
1. CLI loads config from flags, env, repo, user, defaults.
|
||||
2. CLI mints a per-lease SSH key, calls `POST /v1/leases` on the broker.
|
||||
3. Worker checks active-lease and monthly spend caps, reserves worst-case TTL cost, provisions a server, returns host / port / user / workdir / expiry.
|
||||
2. CLI mints a per-lease SSH key and slug, then calls `POST /v1/leases` on the broker.
|
||||
3. Worker checks active-lease and monthly spend caps, reserves worst-case TTL cost, provisions a server, returns host / port / user / workdir / expiry / slug.
|
||||
4. CLI waits for `crabbox-ready`, seeds remote Git when possible, rsyncs the Git file-list manifest, runs sync guardrails and sanity checks, hydrates the configured base ref.
|
||||
5. CLI runs the command over SSH, streams output, sends heartbeats.
|
||||
6. CLI releases the lease unless `--keep` is set; the broker terminates the runner and frees the reserved cost.
|
||||
5. CLI runs the command over SSH, streams output, sends heartbeats/touches.
|
||||
6. CLI releases the lease unless `--keep` is set; kept leases still auto-release after idle timeout, and the broker frees reserved cost when the lease closes.
|
||||
|
||||
See [How Crabbox Works](how-it-works.md) for the full picture, including warm-machine reuse and the brokered vs direct provider paths.
|
||||
|
||||
@ -39,11 +39,11 @@ crabbox login
|
||||
# one-shot run on a fresh leased box
|
||||
crabbox run -- pnpm test
|
||||
|
||||
# keep a warm box around for repeated runs
|
||||
crabbox warmup --idle-timeout 90m
|
||||
crabbox run --id cbx_8f2 -- pnpm test:changed
|
||||
crabbox ssh --id cbx_8f2
|
||||
crabbox stop cbx_8f2
|
||||
# keep a warm box around for repeated runs; output includes an ID and slug
|
||||
crabbox warmup
|
||||
crabbox run --id blue-lobster -- pnpm test:changed
|
||||
crabbox ssh --id blue-lobster
|
||||
crabbox stop blue-lobster
|
||||
```
|
||||
|
||||
`crabbox doctor` validates local config, network reachability, and SSH key availability before you commit to a long workflow. `crabbox usage` summarizes recent spend by user, org, provider, and server type.
|
||||
|
||||
@ -34,15 +34,15 @@ leased machine
|
||||
|
||||
1. CLI loads config and authenticates to Cloudflare Access.
|
||||
2. CLI creates a per-lease SSH key.
|
||||
3. CLI sends `POST /v1/leases` with lease ID, profile, TTL, repo metadata, desired machine class, and SSH public key.
|
||||
3. CLI sends `POST /v1/leases` with lease ID, slug, profile, TTL, idle timeout, desired machine class, and SSH public key.
|
||||
4. Coordinator validates identity and policy.
|
||||
5. Durable Object chooses a provider from config and creates a Hetzner server or AWS EC2 Spot instance.
|
||||
6. Coordinator returns lease ID, machine address, SSH user, workdir, and expiry.
|
||||
6. Coordinator returns lease ID, slug, machine address, SSH user, workdir, and expiry.
|
||||
7. CLI waits for `crabbox-ready`.
|
||||
8. CLI seeds remote Git when possible, compares sync fingerprints, and syncs changed files with `rsync --delete`.
|
||||
9. CLI runs sync sanity and configured base-ref hydration.
|
||||
10. CLI runs the command over SSH and streams stdout/stderr.
|
||||
11. CLI heartbeats while the command runs.
|
||||
11. CLI heartbeats while the command runs; heartbeats touch `lastTouchedAt` and recompute idle expiry up to the TTL cap.
|
||||
12. CLI releases the lease when done.
|
||||
13. Durable Object alarm cleans up stale leases and expired machines.
|
||||
|
||||
|
||||
68
docs/cli.md
68
docs/cli.md
@ -33,26 +33,26 @@ crabbox config show [--json]
|
||||
crabbox config path
|
||||
crabbox config set-broker --url <url> --token-stdin [--provider hetzner|aws]
|
||||
crabbox warmup [--provider hetzner|aws] [--profile <name>] [--idle-timeout <duration>]
|
||||
crabbox run [--id <lease-id>] [--shell] [--checksum] [--debug] [--force-sync-large] -- <command...>
|
||||
crabbox run [--id <lease-id-or-slug>] [--shell] [--checksum] [--debug] [--force-sync-large] -- <command...>
|
||||
crabbox sync-plan [--limit <n>]
|
||||
crabbox history [--lease <lease-id>] [--owner <email>] [--org <name>] [--limit <n>] [--json]
|
||||
crabbox logs <run-id> [--json]
|
||||
crabbox results <run-id> [--json]
|
||||
crabbox cache stats --id <lease-id> [--json]
|
||||
crabbox cache purge --id <lease-id> --kind pnpm|npm|docker|git|all --force
|
||||
crabbox cache warm --id <lease-id> -- <command...>
|
||||
crabbox actions hydrate --id <lease-id> [--workflow <file|name|id>] [--wait-timeout <duration>]
|
||||
crabbox actions register --id <lease-id> [--repo owner/name]
|
||||
crabbox cache stats --id <lease-id-or-slug> [--json]
|
||||
crabbox cache purge --id <lease-id-or-slug> --kind pnpm|npm|docker|git|all --force
|
||||
crabbox cache warm --id <lease-id-or-slug> -- <command...>
|
||||
crabbox actions hydrate --id <lease-id-or-slug> [--workflow <file|name|id>] [--wait-timeout <duration>]
|
||||
crabbox actions register --id <lease-id-or-slug> [--repo owner/name]
|
||||
crabbox actions dispatch [--workflow <file|name|id>] [-f key=value]
|
||||
crabbox status --id <lease-id> [--wait]
|
||||
crabbox status --id <lease-id-or-slug> [--wait]
|
||||
crabbox list [--json]
|
||||
crabbox usage [--scope user|org|all] [--user <email>] [--org <name>] [--month YYYY-MM] [--json]
|
||||
crabbox admin leases [--state active|released|expired|failed] [--owner <email>] [--org <name>] [--json]
|
||||
crabbox admin release <lease-id> [--delete]
|
||||
crabbox admin delete <lease-id> --force
|
||||
crabbox ssh --id <lease-id>
|
||||
crabbox inspect --id <lease-id> [--json]
|
||||
crabbox stop <lease-id>
|
||||
crabbox admin release <lease-id-or-slug> [--delete]
|
||||
crabbox admin delete <lease-id-or-slug> --force
|
||||
crabbox ssh --id <lease-id-or-slug>
|
||||
crabbox inspect --id <lease-id-or-slug> [--json]
|
||||
crabbox stop <lease-id-or-slug>
|
||||
crabbox cleanup [--dry-run]
|
||||
```
|
||||
|
||||
@ -73,19 +73,19 @@ crabbox run --class beast -- pnpm check:changed
|
||||
Warm a box, then reuse it:
|
||||
|
||||
```sh
|
||||
crabbox warmup --profile project-check --idle-timeout 90m
|
||||
crabbox run --id cbx_123 -- pnpm test:changed
|
||||
crabbox run --id cbx_123 --shell 'pnpm install --frozen-lockfile && pnpm test'
|
||||
crabbox stop cbx_123
|
||||
crabbox warmup --profile project-check
|
||||
crabbox run --id blue-lobster -- pnpm test:changed
|
||||
crabbox run --id blue-lobster --shell 'pnpm install --frozen-lockfile && pnpm test'
|
||||
crabbox stop blue-lobster
|
||||
```
|
||||
|
||||
Hydrate through GitHub Actions, then run local dirty work in the hydrated workspace:
|
||||
|
||||
```sh
|
||||
crabbox warmup --idle-timeout 90m
|
||||
crabbox actions hydrate --id cbx_123
|
||||
crabbox run --id cbx_123 -- pnpm test:changed
|
||||
crabbox stop cbx_123
|
||||
crabbox warmup
|
||||
crabbox actions hydrate --id blue-lobster
|
||||
crabbox run --id blue-lobster -- pnpm test:changed
|
||||
crabbox stop blue-lobster
|
||||
```
|
||||
|
||||
Inspect pool:
|
||||
@ -117,7 +117,7 @@ crabbox cleanup --dry-run
|
||||
crabbox cleanup
|
||||
```
|
||||
|
||||
Cleanup is intentionally conservative: it skips kept machines and active states. When a coordinator is configured, brokered cleanup is owned by the Durable Object TTL alarm instead of provider-side sweeping.
|
||||
Cleanup is intentionally conservative: it skips kept machines, deletes expired ready/leased/active direct machines, and gives running/provisioning direct machines an extra stale safety window. When a coordinator is configured, brokered cleanup is owned by the Durable Object alarm instead of provider-side sweeping.
|
||||
|
||||
Debug config:
|
||||
|
||||
@ -131,7 +131,7 @@ crabbox config show --json
|
||||
Inspect recorded runs:
|
||||
|
||||
```sh
|
||||
crabbox run --id cbx_123 --junit junit.xml -- go test ./...
|
||||
crabbox run --id blue-lobster --junit junit.xml -- go test ./...
|
||||
crabbox history --lease cbx_123
|
||||
crabbox logs run_123
|
||||
crabbox results run_123
|
||||
@ -140,16 +140,16 @@ crabbox results run_123
|
||||
Inspect or warm caches on a kept box:
|
||||
|
||||
```sh
|
||||
crabbox cache stats --id cbx_123
|
||||
crabbox cache warm --id cbx_123 -- pnpm install --frozen-lockfile
|
||||
crabbox cache purge --id cbx_123 --kind pnpm --force
|
||||
crabbox cache stats --id blue-lobster
|
||||
crabbox cache warm --id blue-lobster -- pnpm install --frozen-lockfile
|
||||
crabbox cache purge --id blue-lobster --kind pnpm --force
|
||||
```
|
||||
|
||||
Trusted operator lease controls:
|
||||
|
||||
```sh
|
||||
crabbox admin leases --state active
|
||||
crabbox admin release cbx_123
|
||||
crabbox admin release blue-lobster
|
||||
crabbox admin delete cbx_123 --force
|
||||
```
|
||||
|
||||
@ -176,13 +176,13 @@ Fresh non-kept leases retry once with a new machine when bootstrap never reaches
|
||||
Flags:
|
||||
|
||||
```text
|
||||
--id <lease-id> reuse an existing lease
|
||||
--id <lease-id-or-slug> reuse an existing lease
|
||||
--provider <name> hetzner or aws
|
||||
--profile <name> profile to run on
|
||||
--class <name> machine class override
|
||||
--type <name> provider server or instance type override
|
||||
--ttl <duration> lease TTL, default from profile
|
||||
--idle-timeout <duration>
|
||||
--ttl <duration> maximum lease lifetime, default 90m
|
||||
--idle-timeout <duration> idle expiry, default 30m
|
||||
--no-sync run without syncing
|
||||
--sync-only sync and exit
|
||||
--force-sync-large allow a sync candidate above configured fail thresholds
|
||||
@ -191,10 +191,13 @@ Flags:
|
||||
--checksum use checksum rsync instead of size/time
|
||||
--debug print sync timing and itemized rsync output
|
||||
--junit <paths> comma-separated remote JUnit XML paths to attach to run history
|
||||
--reclaim claim an existing lease for the current repo
|
||||
```
|
||||
|
||||
Secrets must not be accepted as flag values. Env forwarding is name-based only.
|
||||
|
||||
Crabbox stores local lease claims under its state directory. `warmup` and first reuse claim the lease for the current repo; later `run`, `ssh`, `cache`, and `actions hydrate/register` refuse a conflicting repo claim unless `--reclaim` is set.
|
||||
|
||||
## Exit Codes
|
||||
|
||||
```text
|
||||
@ -231,6 +234,9 @@ broker:
|
||||
token: ...
|
||||
profile: project-check
|
||||
class: beast
|
||||
lease:
|
||||
idleTimeout: 30m
|
||||
ttl: 90m
|
||||
capacity:
|
||||
market: spot
|
||||
strategy: most-available
|
||||
@ -306,6 +312,8 @@ CRABBOX_COORDINATOR_TOKEN
|
||||
CRABBOX_PROVIDER
|
||||
CRABBOX_PROFILE
|
||||
CRABBOX_CONFIG
|
||||
CRABBOX_IDLE_TIMEOUT
|
||||
CRABBOX_TTL
|
||||
CRABBOX_SSH_KEY
|
||||
CRABBOX_RESULTS_JUNIT
|
||||
CRABBOX_SYNC_TIMEOUT
|
||||
@ -336,7 +344,7 @@ Human output:
|
||||
|
||||
```text
|
||||
acquiring lease profile=project-check ttl=90m
|
||||
leased cbx_abc123 machine=hz-ccx33-01 expires=2026-04-30T17:30:00Z
|
||||
leased cbx_abc123 slug=blue-lobster provider=aws server=i-0123 type=c7a.48xlarge ip=203.0.113.10 idle_timeout=30m0s expires=2026-05-01T17:30:00Z
|
||||
syncing 184 files -> /work/crabbox/cbx_abc123/openclaw
|
||||
running pnpm check:changed
|
||||
...
|
||||
|
||||
@ -5,29 +5,33 @@
|
||||
It uses GitHub's runner and workflow APIs:
|
||||
|
||||
- `actions register` gets a repository runner registration token through `gh api`, installs the official `actions/runner` package on an existing box, and starts it with systemd.
|
||||
- `actions hydrate` registers the runner, dispatches the configured workflow with the lease label, waits for the workflow to write the hydrated workspace marker, and then returns.
|
||||
- `actions hydrate` registers the runner, dispatches the configured workflow with the canonical lease label, waits for the workflow to write the hydrated workspace marker, and then returns.
|
||||
- `actions dispatch` calls `gh workflow run` for the configured workflow.
|
||||
|
||||
For `actions hydrate`, Crabbox inspects the selected workflow's `workflow_dispatch.inputs` when the workflow path is available under `.github/workflows/`. It only sends declared inputs, requires `crabbox_id`, `crabbox_runner_label`, and `crabbox_keep_alive_minutes`, and treats `crabbox_job` as optional. If GitHub still rejects `crabbox_job` as an unexpected input, Crabbox retries once without it so older workflow refs remain usable.
|
||||
|
||||
Runner names and extra labels use the friendly slug when available, but workflow inputs and state-file paths keep using the canonical `cbx_...` ID.
|
||||
|
||||
On success, `actions hydrate` prints a concise total duration line.
|
||||
|
||||
```sh
|
||||
crabbox warmup --actions-runner --idle-timeout 90m
|
||||
crabbox actions hydrate --id cbx_123
|
||||
crabbox actions register --id cbx_123
|
||||
crabbox warmup --actions-runner
|
||||
crabbox actions hydrate --id blue-lobster
|
||||
crabbox actions register --id blue-lobster
|
||||
crabbox actions dispatch -f testbox_id=cbx_123
|
||||
crabbox run --id cbx_123 -- pnpm test
|
||||
crabbox run --id blue-lobster -- pnpm test
|
||||
```
|
||||
|
||||
Subcommands:
|
||||
|
||||
```text
|
||||
hydrate --id <lease> [--repo owner/name] [--workflow <file|name|id>] [--ref <ref>] [--wait-timeout 20m] [--keep-alive-minutes 90] [-f key=value]
|
||||
register --id <lease> [--repo owner/name] [--name <runner-name>] [--labels <csv>] [--version latest] [--ephemeral=true]
|
||||
hydrate --id <lease-id-or-slug> [--repo owner/name] [--workflow <file|name|id>] [--ref <ref>] [--wait-timeout 20m] [--keep-alive-minutes 90] [--reclaim] [-f key=value]
|
||||
register --id <lease-id-or-slug> [--repo owner/name] [--name <runner-name>] [--labels <csv>] [--version latest] [--ephemeral=true] [--reclaim]
|
||||
dispatch [--repo owner/name] [--workflow <file|name|id>] [--ref <ref>] [-f key=value]
|
||||
```
|
||||
|
||||
Hydrate/register validate the local repo claim before touching the lease. Use `--reclaim` when intentionally moving a lease to the current repo.
|
||||
|
||||
Config:
|
||||
|
||||
```yaml
|
||||
@ -50,9 +54,9 @@ When `actions.job` is set and the workflow declares `crabbox_job`, Crabbox sends
|
||||
Use hydration when CI already knows how to prepare the repository and an agent needs a fast local-style loop:
|
||||
|
||||
```sh
|
||||
crabbox warmup --idle-timeout 90m
|
||||
crabbox actions hydrate --id cbx_123
|
||||
crabbox run --id cbx_123 -- pnpm test:changed
|
||||
crabbox warmup
|
||||
crabbox actions hydrate --id blue-lobster
|
||||
crabbox run --id blue-lobster -- pnpm test:changed
|
||||
```
|
||||
|
||||
The Actions workflow owns repository-specific setup: checkout, dependency install, services, caches, secrets, and any project tools. Crabbox only registers the runner, dispatches the workflow, waits for the marker, and later syncs local edits into the marked workspace. There is no project-specific setup code in the Crabbox binary.
|
||||
@ -103,4 +107,4 @@ services_file="$HOME/.crabbox/actions/${{ inputs.crabbox_id }}.services"
|
||||
mv "${state}.tmp" "$state"
|
||||
```
|
||||
|
||||
`crabbox run --id <lease>` reads that marker, syncs into the hydrated `$GITHUB_WORKSPACE`, and sources the non-secret env file when present. The env file should contain stable GitHub/runner context such as `GITHUB_WORKSPACE`, `GITHUB_RUN_ID`, `RUNNER_TEMP`, and `RUNNER_TOOL_CACHE`; do not persist secrets or OIDC request tokens there. Keep the workflow job alive when service containers or job-scoped setup must remain running for the remote command loop. `crabbox stop <lease>` writes the `.stop` marker before releasing the box.
|
||||
`crabbox run --id <lease-id-or-slug>` reads that marker, syncs into the hydrated `$GITHUB_WORKSPACE`, and sources the non-secret env file when present. The env file should contain stable GitHub/runner context such as `GITHUB_WORKSPACE`, `GITHUB_RUN_ID`, `RUNNER_TEMP`, and `RUNNER_TOOL_CACHE`; do not persist secrets or OIDC request tokens there. Keep the workflow job alive when service containers or job-scoped setup must remain running for the remote command loop. `crabbox stop <lease-id-or-slug>` writes the `.stop` marker before releasing the box.
|
||||
|
||||
@ -5,11 +5,13 @@
|
||||
```sh
|
||||
crabbox admin leases
|
||||
crabbox admin leases --state active --json
|
||||
crabbox admin release cbx_...
|
||||
crabbox admin release cbx_... --delete
|
||||
crabbox admin release blue-lobster
|
||||
crabbox admin release blue-lobster --delete
|
||||
crabbox admin delete cbx_... --force
|
||||
```
|
||||
|
||||
Release/delete accept a canonical `cbx_...` ID or an active slug; use the canonical ID when an admin slug lookup is ambiguous.
|
||||
|
||||
Admin commands require a configured coordinator and bearer token. The current coordinator trusts the shared operator token; do not expose it to untrusted users.
|
||||
|
||||
## leases
|
||||
|
||||
@ -3,12 +3,14 @@
|
||||
`crabbox cache` inspects, purges, or warms caches on a leased box.
|
||||
|
||||
```sh
|
||||
crabbox cache stats --id cbx_...
|
||||
crabbox cache stats --id cbx_... --json
|
||||
crabbox cache warm --id cbx_... -- pnpm install --frozen-lockfile
|
||||
crabbox cache purge --id cbx_... --kind pnpm --force
|
||||
crabbox cache stats --id blue-lobster
|
||||
crabbox cache stats --id blue-lobster --json
|
||||
crabbox cache warm --id blue-lobster -- pnpm install --frozen-lockfile
|
||||
crabbox cache purge --id blue-lobster --kind pnpm --force
|
||||
```
|
||||
|
||||
`--id` accepts the stable `cbx_...` ID or an active friendly slug. Cache commands that SSH to the box touch the lease and validate the local repo claim; add `--reclaim` to move an existing claim.
|
||||
|
||||
Cache kinds:
|
||||
|
||||
```text
|
||||
|
||||
@ -9,7 +9,7 @@ crabbox cleanup
|
||||
|
||||
Cleanup refuses to run when a coordinator is configured. Brokered cleanup belongs to the Durable Object alarm.
|
||||
|
||||
Direct cleanup skips kept machines and active states. It deletes clearly expired inactive machines and stale active-state machines after an extra safety window.
|
||||
Direct cleanup skips kept machines, deletes expired ready/leased/active machines, and gives running/provisioning machines an extra stale safety window. It relies on provider labels such as `lease`, `slug`, `expires_at`, and `state`.
|
||||
|
||||
Flags:
|
||||
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
`crabbox inspect` prints detailed lease and provider metadata.
|
||||
|
||||
```sh
|
||||
crabbox inspect --id cbx_123
|
||||
crabbox inspect --id cbx_123 --json
|
||||
crabbox inspect --id blue-lobster
|
||||
crabbox inspect --id blue-lobster --json
|
||||
```
|
||||
|
||||
Use this for debugging coordinator state, provider labels, expiry, and SSH target details.
|
||||
@ -12,7 +12,7 @@ Use this for debugging coordinator state, provider labels, expiry, and SSH targe
|
||||
Flags:
|
||||
|
||||
```text
|
||||
--id <lease-id>
|
||||
--id <lease-id-or-slug>
|
||||
--provider hetzner|aws
|
||||
--json
|
||||
```
|
||||
|
||||
@ -3,13 +3,13 @@
|
||||
`crabbox run` syncs the current dirty checkout to a box, runs a command, streams output, and returns the remote exit code.
|
||||
|
||||
```sh
|
||||
crabbox run --id cbx_123 -- pnpm test:changed:max
|
||||
crabbox run --class beast --idle-timeout 90m -- pnpm check
|
||||
crabbox run --id cbx_123 --shell 'pnpm install --frozen-lockfile && pnpm test'
|
||||
crabbox run --id blue-lobster -- pnpm test:changed:max
|
||||
crabbox run --class beast -- pnpm check
|
||||
crabbox run --id blue-lobster --shell 'pnpm install --frozen-lockfile && pnpm test'
|
||||
crabbox run --id cbx_123 --junit junit.xml -- go test ./...
|
||||
```
|
||||
|
||||
If `--id` is omitted, Crabbox creates a fresh non-kept lease and releases it when the command exits.
|
||||
If `--id` is omitted, Crabbox creates a fresh non-kept lease and releases it when the command exits. `--id` accepts the stable `cbx_...` ID or the active friendly slug.
|
||||
|
||||
When the lease has been hydrated by `crabbox actions hydrate`, `run` reads the remote marker under `$HOME/.crabbox/actions`, syncs into the workflow's `$GITHUB_WORKSPACE`, and sources the non-secret env file written by the workflow. That preserves the setup the workflow performed: checkout path, installed dependencies, service containers, caches, runner temp/toolcache paths, and any project-specific preparation. GitHub secrets and OIDC request tokens remain workflow-step scoped unless the project explicitly persists its own short-lived credentials.
|
||||
|
||||
@ -32,7 +32,7 @@ Use `crabbox sync-plan` to inspect the same local manifest without leasing a box
|
||||
Flags:
|
||||
|
||||
```text
|
||||
--id <lease-id>
|
||||
--id <lease-id-or-slug>
|
||||
--provider hetzner|aws
|
||||
--profile <name>
|
||||
--class <name>
|
||||
@ -47,4 +47,8 @@ Flags:
|
||||
--checksum
|
||||
--debug
|
||||
--junit <comma-separated remote XML paths>
|
||||
--reclaim
|
||||
```
|
||||
|
||||
`--idle-timeout` controls inactivity expiry, default `30m`. `--ttl` remains the maximum wall-clock lifetime, default `90m`.
|
||||
Crabbox records a local repo claim for each reused lease. If a lease is already claimed by another repo, use `--reclaim` to move the claim intentionally.
|
||||
|
||||
@ -3,14 +3,17 @@
|
||||
`crabbox ssh` prints the SSH command for a lease.
|
||||
|
||||
```sh
|
||||
crabbox ssh --id cbx_123
|
||||
crabbox ssh --id blue-lobster
|
||||
```
|
||||
|
||||
The output includes the per-lease private key path when Crabbox created one.
|
||||
The output includes the per-lease private key path when Crabbox created one. Printing an SSH command touches coordinator leases because it signals intended manual use.
|
||||
|
||||
Flags:
|
||||
|
||||
```text
|
||||
--id <lease-id>
|
||||
--id <lease-id-or-slug>
|
||||
--provider hetzner|aws
|
||||
--reclaim
|
||||
```
|
||||
|
||||
`ssh` touches the lease and validates the local repo claim. Use `--reclaim` when intentionally taking over a lease from another repo.
|
||||
|
||||
@ -3,17 +3,17 @@
|
||||
`crabbox status` prints the current state for a lease.
|
||||
|
||||
```sh
|
||||
crabbox status --id cbx_123
|
||||
crabbox status --id cbx_123 --wait --wait-timeout 10m
|
||||
crabbox status --id cbx_123 --json
|
||||
crabbox status --id blue-lobster
|
||||
crabbox status --id blue-lobster --wait --wait-timeout 10m
|
||||
crabbox status --id blue-lobster --json
|
||||
```
|
||||
|
||||
`--wait` blocks until the box is ready or the timeout expires. This is useful when a warmup returns quickly in future orchestrator-backed hydration flows.
|
||||
`--id` accepts the canonical `cbx_...` ID or active slug. Plain status is read-only; `--wait` touches the lease while waiting.
|
||||
|
||||
Flags:
|
||||
|
||||
```text
|
||||
--id <lease-id>
|
||||
--id <lease-id-or-slug>
|
||||
--provider hetzner|aws
|
||||
--wait
|
||||
--wait-timeout <duration>
|
||||
|
||||
@ -3,10 +3,11 @@
|
||||
`crabbox stop` releases a coordinator lease or deletes a direct-provider machine.
|
||||
|
||||
```sh
|
||||
crabbox stop cbx_123
|
||||
crabbox stop blue-lobster
|
||||
```
|
||||
|
||||
`crabbox release` remains as a compatibility alias.
|
||||
The argument accepts the stable `cbx_...` ID or an active friendly slug.
|
||||
|
||||
Flags:
|
||||
|
||||
|
||||
@ -3,11 +3,11 @@
|
||||
`crabbox warmup` provisions or leases a remote box and waits until SSH and the toolchain are ready.
|
||||
|
||||
```sh
|
||||
crabbox warmup --class beast --idle-timeout 90m
|
||||
crabbox warmup --actions-runner --idle-timeout 90m
|
||||
crabbox warmup --class beast
|
||||
crabbox warmup --actions-runner
|
||||
```
|
||||
|
||||
The command returns a `cbx_...` lease ID. Reuse that ID for subsequent `run`, `status`, `ssh`, `inspect`, and `stop` commands.
|
||||
The command returns a stable `cbx_...` lease ID and a friendly slug. Reuse either for subsequent `run`, `status`, `ssh`, `inspect`, and `stop` commands; scripts should keep using the canonical ID.
|
||||
|
||||
On success, `warmup` prints a concise total duration line.
|
||||
|
||||
@ -22,11 +22,13 @@ Flags:
|
||||
--idle-timeout <duration>
|
||||
--keep
|
||||
--actions-runner
|
||||
--reclaim
|
||||
```
|
||||
|
||||
`--idle-timeout` is the preferred name for agent workflows. It maps to the same lease expiry as `--ttl`.
|
||||
`--idle-timeout` releases the lease after no touch for that duration, default `30m`. `--ttl` remains the maximum wall-clock lifetime, default `90m`.
|
||||
Warmup records a local claim tying the lease to the current repo; `--reclaim` overwrites an existing local claim for that lease.
|
||||
|
||||
`--actions-runner` immediately registers the warm box as an ephemeral self-hosted GitHub Actions runner for the current repository. Most projects should prefer `crabbox actions hydrate --id <lease>` after warmup because it also dispatches the workflow and waits for the ready marker.
|
||||
`--actions-runner` immediately registers the warm box as an ephemeral self-hosted GitHub Actions runner for the current repository. Most projects should prefer `crabbox actions hydrate --id <lease-id-or-slug>` after warmup because it also dispatches the workflow and waits for the ready marker.
|
||||
|
||||
New leases use per-lease SSH keys under the user config directory:
|
||||
|
||||
|
||||
@ -10,12 +10,12 @@ Actions hydration lets a repository reuse its existing GitHub Actions setup with
|
||||
|
||||
The flow:
|
||||
|
||||
1. `crabbox warmup --idle-timeout 90m` leases a machine.
|
||||
2. `crabbox actions hydrate --id cbx_...` registers that machine as an ephemeral self-hosted runner for the repository.
|
||||
1. `crabbox warmup` leases a machine and prints both `cbx_...` and a friendly slug.
|
||||
2. `crabbox actions hydrate --id blue-lobster` registers that machine as an ephemeral self-hosted runner for the repository.
|
||||
3. Crabbox inspects the configured workflow's `workflow_dispatch.inputs` when it can read the workflow path, then dispatches it with the lease ID, dynamic runner label, keepalive timeout, and optional expected hydrate job.
|
||||
4. The workflow runs on `[self-hosted, crabbox-cbx-...]`, checks out the repo, installs dependencies, starts services, warms caches, and performs any repo-specific setup.
|
||||
4. The workflow runs on `[self-hosted, crabbox-cbx-...]`; the runner also carries a readable slug label such as `crabbox-blue-lobster`.
|
||||
5. The workflow writes `$HOME/.crabbox/actions/<lease>.env` with `WORKSPACE`, `RUN_ID`, `JOB`, `ENV_FILE`, `SERVICES_FILE`, and `READY_AT`.
|
||||
6. `crabbox run --id cbx_... -- <command>` reads that marker, syncs the local dirty checkout into `$GITHUB_WORKSPACE`, and sources the non-secret env file when present.
|
||||
6. `crabbox run --id blue-lobster -- <command>` reads that marker, syncs the local dirty checkout into `$GITHUB_WORKSPACE`, and sources the non-secret env file when present.
|
||||
|
||||
The important boundary: project setup lives in the repository workflow. Crabbox owns runner registration, dispatch, marker waiting, SSH sync, and command execution. It does not contain repository-specific setup code.
|
||||
|
||||
|
||||
@ -29,7 +29,7 @@ Trusted operator controls:
|
||||
|
||||
```sh
|
||||
crabbox admin leases --state active
|
||||
crabbox admin release cbx_...
|
||||
crabbox admin release blue-lobster
|
||||
crabbox admin delete cbx_... --force
|
||||
```
|
||||
|
||||
|
||||
@ -32,9 +32,9 @@ The per-kind toggles control `cache stats` and `cache purge`. Disabled kinds are
|
||||
Commands:
|
||||
|
||||
```sh
|
||||
crabbox cache stats --id cbx_...
|
||||
crabbox cache warm --id cbx_... -- pnpm install --frozen-lockfile
|
||||
crabbox cache purge --id cbx_... --kind pnpm --force
|
||||
crabbox cache stats --id blue-lobster
|
||||
crabbox cache warm --id blue-lobster -- pnpm install --frozen-lockfile
|
||||
crabbox cache purge --id blue-lobster --kind pnpm --force
|
||||
```
|
||||
|
||||
Caches are speed hints, not source of truth. The synced worktree remains authoritative. Disposable leases lose cache state when the VM is deleted; kept leases can reuse cache state across repeated agent runs.
|
||||
|
||||
@ -15,16 +15,18 @@ active -> expired
|
||||
active -> failed
|
||||
```
|
||||
|
||||
The CLI heartbeats active coordinator leases while a command runs. Release and expiry both call the provider delete path for non-kept machines. Kept machines remain available until explicitly stopped or expired by policy.
|
||||
The CLI heartbeats active coordinator leases while a command runs. Heartbeat is a touch: it updates `lastTouchedAt` and extends idle expiry up to the lease TTL cap. Release and expiry both call the provider delete path; `keep=true` only skips command-exit release, not coordinator idle expiry.
|
||||
|
||||
The CLI also keeps a local claim file per lease so repo-local wrappers do not need their own ledger. Commands that reuse a lease validate that the current repo matches the claim; `--reclaim` moves the claim intentionally.
|
||||
|
||||
Brokered cleanup belongs to the Durable Object alarm. `crabbox cleanup` refuses to sweep provider resources when a coordinator is configured because that can race live brokered leases.
|
||||
|
||||
Direct-provider cleanup is conservative:
|
||||
|
||||
- skip `keep=true`;
|
||||
- skip active states;
|
||||
- delete clearly expired inactive machines;
|
||||
- delete stale active machines only after expiry plus the extra safety window.
|
||||
- skip running/provisioning states until expiry plus the extra safety window;
|
||||
- delete clearly expired ready/leased/active direct machines;
|
||||
- delete clearly expired inactive machines.
|
||||
|
||||
Provider resources should carry Crabbox labels/tags so orphan cleanup can identify them without touching unrelated infrastructure.
|
||||
|
||||
|
||||
@ -51,7 +51,7 @@ A single `crabbox run` command walks through five phases:
|
||||
|
||||
**1. Plan.** Load config (flags -> env -> repo -> user -> defaults). Mint a temporary lease ID and per-lease SSH key.
|
||||
|
||||
**2. Lease.** `POST /v1/leases` to the broker with class, provider, TTL, bootstrap options, and the SSH public key. Worker authenticates, then forwards to the Fleet Durable Object. Durable Object enforces active-lease and monthly spend caps, asks the provider for live pricing, reserves the worst-case TTL cost, provisions the machine, and returns host / SSH user / port / work root / expiry / lease ID. CLI re-keys its local key dir if the broker assigned a different final lease ID.
|
||||
**2. Lease.** `POST /v1/leases` to the broker with class, provider, TTL, idle timeout, slug, bootstrap options, and the SSH public key. Worker authenticates, then forwards to the Fleet Durable Object. Durable Object enforces active-lease and monthly spend caps, asks the provider for live pricing, reserves the worst-case TTL cost, provisions the machine, and returns host / SSH user / port / work root / expiry / lease ID / slug. CLI re-keys its local key dir if the broker assigned a different final lease ID.
|
||||
|
||||
**3. Sync.** Wait for SSH and the `crabbox-ready` marker. Seed remote Git when possible. Compare local and remote sync fingerprints; skip rsync if nothing changed. Otherwise rsync the dirty checkout into `/work/crabbox/<lease>/<repo>`, run sanity checks, hydrate the configured base ref.
|
||||
|
||||
@ -64,13 +64,13 @@ A single `crabbox run` command walks through five phases:
|
||||
`crabbox warmup` follows the same lease creation path, then keeps the box ready for later use. Reuse is explicit:
|
||||
|
||||
```sh
|
||||
crabbox warmup --profile project-check --idle-timeout 90m
|
||||
crabbox run --id cbx_... -- pnpm test:changed
|
||||
crabbox ssh --id cbx_...
|
||||
crabbox stop cbx_...
|
||||
crabbox warmup --profile project-check
|
||||
crabbox run --id blue-lobster -- pnpm test:changed
|
||||
crabbox ssh --id blue-lobster
|
||||
crabbox stop blue-lobster
|
||||
```
|
||||
|
||||
While the CLI is using a lease it sends heartbeats; the Durable Object extends the lease accordingly. If a lease goes stale, the alarm expires it and deletes non-kept resources.
|
||||
While the CLI is using a lease it sends heartbeats; the Durable Object updates `lastTouchedAt` and recomputes idle expiry. If a warm lease goes untouched for the idle timeout, the alarm releases it.
|
||||
|
||||
## Brokered vs Direct Provider
|
||||
|
||||
|
||||
@ -14,14 +14,14 @@ Crabbox exposes operational visibility through CLI commands, coordinator usage s
|
||||
Use `status`, `list`, and `inspect`:
|
||||
|
||||
```sh
|
||||
bin/crabbox status --id cbx_...
|
||||
bin/crabbox status --id blue-lobster
|
||||
bin/crabbox list --json
|
||||
bin/crabbox inspect --id cbx_... --json
|
||||
bin/crabbox inspect --id blue-lobster --json
|
||||
```
|
||||
|
||||
Important fields:
|
||||
|
||||
- lease ID;
|
||||
- lease ID and slug;
|
||||
- owner and org;
|
||||
- provider and server type;
|
||||
- state;
|
||||
@ -66,8 +66,8 @@ History is for command debugging, not unlimited log archival. Logs are bounded t
|
||||
Use SSH for live process and filesystem inspection:
|
||||
|
||||
```sh
|
||||
bin/crabbox ssh --id cbx_...
|
||||
bin/crabbox inspect --id cbx_... --json
|
||||
bin/crabbox ssh --id blue-lobster
|
||||
bin/crabbox inspect --id blue-lobster --json
|
||||
```
|
||||
|
||||
Useful remote checks:
|
||||
@ -80,7 +80,7 @@ free -h
|
||||
ps aux --sort=-%cpu | head
|
||||
```
|
||||
|
||||
If a lease was created with `--keep`, SSH remains available until `crabbox stop` or TTL cleanup removes it.
|
||||
If a lease was created with `--keep`, SSH remains available until `crabbox stop`, idle expiry, or the TTL cap removes it.
|
||||
|
||||
## Actions Hydration
|
||||
|
||||
@ -89,11 +89,11 @@ If a lease was created with `--keep`, SSH remains available until `crabbox stop`
|
||||
Use:
|
||||
|
||||
```sh
|
||||
bin/crabbox actions hydrate --id cbx_...
|
||||
bin/crabbox inspect --id cbx_... --json
|
||||
bin/crabbox actions hydrate --id blue-lobster
|
||||
bin/crabbox inspect --id blue-lobster --json
|
||||
```
|
||||
|
||||
The hydrated run writes non-secret handoff data for later `crabbox run --id cbx_...` commands. Secrets and OIDC tokens remain workflow-step scoped unless the workflow intentionally writes its own short-lived handoff.
|
||||
The hydrated run writes non-secret handoff data for later `crabbox run --id blue-lobster` commands. Secrets and OIDC tokens remain workflow-step scoped unless the workflow intentionally writes its own short-lived handoff.
|
||||
|
||||
## Worker Logs
|
||||
|
||||
|
||||
@ -95,8 +95,8 @@ Use:
|
||||
```sh
|
||||
bin/crabbox list
|
||||
bin/crabbox admin leases --state active
|
||||
bin/crabbox inspect --id cbx_... --json
|
||||
bin/crabbox stop cbx_...
|
||||
bin/crabbox inspect --id blue-lobster --json
|
||||
bin/crabbox stop blue-lobster
|
||||
```
|
||||
|
||||
Trusted operators can use `crabbox admin release` or `crabbox admin delete --force` for stuck leases.
|
||||
|
||||
@ -41,9 +41,9 @@ The Worker stores coordinator leases as `active`, `released`, `expired`, or `fai
|
||||
|
||||
## Heartbeats And Idle Timeout
|
||||
|
||||
`crabbox warmup --idle-timeout 90m` and `crabbox run --idle-timeout 90m` map to lease TTL. The CLI sends coordinator heartbeats while a lease is in use. Each heartbeat extends `expiresAt` by the original TTL.
|
||||
`crabbox warmup --idle-timeout 30m` and `crabbox run --idle-timeout 30m` set inactivity expiry. `--ttl` is a separate maximum wall-clock lifetime. The CLI sends coordinator heartbeats while a lease is in use; each heartbeat updates `lastTouchedAt` and recomputes `expiresAt = min(createdAt + ttl, lastTouchedAt + idleTimeout)`.
|
||||
|
||||
Direct-provider mode does not have a central heartbeat. It labels machines with `created_at`, `expires_at`, and `state`; `crabbox cleanup` uses those labels conservatively.
|
||||
Direct-provider mode does not have a central heartbeat or alarm. It labels machines with `created_at`, `last_touched_at`, `idle_timeout_secs`, `expires_at`, `state`, `lease`, and `slug`; `crabbox cleanup` uses those labels conservatively.
|
||||
|
||||
## Cleanup
|
||||
|
||||
@ -52,7 +52,8 @@ Brokered cleanup is owned by the Durable Object alarm. `crabbox cleanup` refuses
|
||||
Direct cleanup only deletes machines that are clearly safe:
|
||||
|
||||
- `keep=true` is skipped;
|
||||
- active states are skipped;
|
||||
- running and provisioning states are skipped until the extra stale window;
|
||||
- expired ready/leased/active direct machines can be cleaned up manually;
|
||||
- expired inactive machines can be deleted;
|
||||
- stale active states older than expiry plus 12 hours can be deleted.
|
||||
|
||||
|
||||
@ -14,11 +14,11 @@ Crabbox performance comes from avoiding repeated setup, keeping the sync small,
|
||||
Use `warmup` for repeated agent loops:
|
||||
|
||||
```sh
|
||||
bin/crabbox warmup --class beast --idle-timeout 90m
|
||||
bin/crabbox run --id cbx_... -- pnpm test:changed:max
|
||||
bin/crabbox warmup --class beast
|
||||
bin/crabbox run --id blue-lobster -- pnpm test:changed:max
|
||||
```
|
||||
|
||||
Warm leases avoid waiting for a fresh VM and preserve package caches outside the synced source tree. Use `crabbox stop cbx_...` when the loop is done.
|
||||
Warm leases avoid waiting for a fresh VM and preserve package caches outside the synced source tree. They release after the idle timeout, default `30m`, if untouched. Use `crabbox stop blue-lobster` when the loop is done.
|
||||
|
||||
## Sync Size
|
||||
|
||||
@ -59,16 +59,16 @@ Runner bootstrap prepares shared package cache locations for Node, pnpm, Docker,
|
||||
Use explicit cache commands on kept leases:
|
||||
|
||||
```sh
|
||||
bin/crabbox cache stats --id cbx_...
|
||||
bin/crabbox cache warm --id cbx_... -- pnpm install --frozen-lockfile
|
||||
bin/crabbox cache purge --id cbx_... --kind pnpm --force
|
||||
bin/crabbox cache stats --id blue-lobster
|
||||
bin/crabbox cache warm --id blue-lobster -- pnpm install --frozen-lockfile
|
||||
bin/crabbox cache purge --id blue-lobster --kind pnpm --force
|
||||
```
|
||||
|
||||
For repeatable setup that needs repository secrets, use Actions hydration:
|
||||
|
||||
```sh
|
||||
bin/crabbox actions hydrate --id cbx_...
|
||||
bin/crabbox run --id cbx_... -- pnpm test:changed:max
|
||||
bin/crabbox actions hydrate --id blue-lobster
|
||||
bin/crabbox run --id blue-lobster -- pnpm test:changed:max
|
||||
```
|
||||
|
||||
The workflow owns dependency installation, cache/service setup, and secret-backed environment hydration. Crabbox attaches later commands to the hydrated workspace.
|
||||
|
||||
@ -100,14 +100,14 @@ Cleanup is security-sensitive.
|
||||
|
||||
Required:
|
||||
|
||||
- Lease TTL.
|
||||
- Heartbeat deadline.
|
||||
- Lease TTL cap.
|
||||
- Idle timeout and heartbeat/touch deadline.
|
||||
- Explicit release.
|
||||
- Durable Object alarm cleanup.
|
||||
- Provider label sweep for clearly expired, inactive orphan machines.
|
||||
- Boot-time cleanup of stale `/work/crabbox/*` dirs.
|
||||
|
||||
Direct-CLI cleanup currently uses provider labels and skips active states. When a coordinator is configured, provider-side cleanup is disabled because the Durable Object TTL alarm owns brokered cleanup.
|
||||
Direct-CLI cleanup uses provider labels. It skips kept machines, deletes expired ready/leased/active machines, and only removes running/provisioning machines after the extra stale safety window. When a coordinator is configured, provider-side cleanup is disabled because the Durable Object alarm owns brokered cleanup.
|
||||
|
||||
Release must be idempotent. Delete must tolerate already-deleted provider resources.
|
||||
|
||||
|
||||
@ -166,8 +166,8 @@ Symptoms:
|
||||
Checks:
|
||||
|
||||
```sh
|
||||
bin/crabbox actions hydrate --id cbx_...
|
||||
bin/crabbox inspect --id cbx_... --json
|
||||
bin/crabbox actions hydrate --id blue-lobster
|
||||
bin/crabbox inspect --id blue-lobster --json
|
||||
```
|
||||
|
||||
Fixes:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user