| .github/workflows | ||
| cmd/crabbox | ||
| docs | ||
| internal/cli | ||
| scripts | ||
| worker | ||
| .gitignore | ||
| .goreleaser.yaml | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| go.mod | ||
| go.sum | ||
| index.js | ||
| index.test.js | ||
| LICENSE | ||
| openclaw.plugin.json | ||
| package.json | ||
| README.md | ||
Crabbox
Crabbox is an open source remote testbox runner for maintainers and agents. It gives a fast local loop on owned cloud capacity: provision or reuse a warm Linux box, sync the current dirty checkout, run a command remotely, stream output, and clean up.
The current implementation is a Go CLI plus a Cloudflare Worker/Durable Object coordinator. The CLI uses the coordinator for brokered Hetzner or AWS EC2 Spot leases, with direct provider calls kept as a debug fallback.
Documentation lives in docs/. Start with How Crabbox Works for the end-to-end mental model, and use Source Map to trace docs back to implementation files. The GitHub Pages site is generated from those Markdown files with a small dependency-free builder:
node scripts/build-docs-site.mjs
open dist/docs-site/index.html
Install
Latest release: 0.1.0.
brew install openclaw/tap/crabbox
crabbox --version
Without Homebrew, download the matching archive from the v0.1.0 release on GitHub:
https://github.com/openclaw/crabbox/releases/tag/v0.1.0
How It Works
Crabbox has a small control plane and a simple data plane:
developer laptop
crabbox CLI
|
| HTTPS JSON API, bearer auth
v
Cloudflare Worker
Fleet Durable Object
|
| provider API
v
Hetzner server or AWS EC2 Spot instance
developer laptop
|
| rsync + SSH
v
leased runner
The CLI is the user-facing tool. It loads config from ~/.config/crabbox/config.yaml, repo-local crabbox.yaml or .crabbox.yaml, creates a per-lease SSH key, asks the broker for a lease, waits for SSH, seeds remote Git when possible, builds a Git file-list sync manifest, skips sync when the local/remote fingerprint matches, rsyncs the current checkout, runs the requested command, streams output, and releases the lease unless --keep is set. SSH prefers the configured port and can fall back to port 22 during bootstrap.
The broker is the Cloudflare Worker at crabbox-coordinator.steipete.workers.dev. It authenticates requests with CRABBOX_SHARED_TOKEN, routes all fleet operations through a single Durable Object, and owns cloud-provider credentials. Local machines do not need AWS or Hetzner API keys for the normal path.
The Fleet Durable Object is the serialized scheduler and lease store. It creates lease IDs, records owner/profile/class/provider metadata, tracks expiry, and has an alarm that expires stale leases. Release and expiry both call the provider delete path for non-kept machines.
The provider layer provisions capacity:
- Hetzner: imports or reuses the SSH key, creates a server, applies Crabbox labels, and falls back across configured server types when quota or capacity rejects a request.
- AWS: signs EC2 Query API calls inside the Worker, imports or reuses the SSH key pair, creates or reuses the
crabbox-runnerssecurity group, launches one-time Spot instances, tags instances/volumes/Spot requests, and falls back across broad C/M/R instance families. Direct AWS mode can use Spot placement scores across configured regions before provisioning.
The runner is just an Ubuntu machine bootstrapped by cloud-init. Bootstrap creates the crabbox user, enables SSH on port 2222, installs only Crabbox plumbing (curl, Git, rsync, jq, OpenSSH), and prepares /work/crabbox plus cache directories. Project runtimes such as Go, Node, pnpm, Docker, services, and secrets belong in the repository's GitHub Actions hydration, devcontainer, Nix, mise/asdf, or setup scripts. The runner does not need broker credentials.
The normal lifecycle is:
crabbox run --class standard -- <command>loads local config.- CLI sends
POST /v1/leaseswith provider, class, TTL, idle timeout, slug, SSH public key, and bootstrap options. - Worker creates a Hetzner server or AWS Spot instance and stores the lease metadata, including
lastTouchedAtand idle expiry. - CLI waits for
crabbox-readyover SSH. - CLI seeds remote Git when possible, then rsyncs tracked plus nonignored untracked files into
/work/crabbox/<lease>/<repo>. - CLI records sync fingerprints, enforces sync size/time guardrails, runs sync sanity checks, and hydrates configured base-ref history.
- CLI runs the command over SSH and returns the remote exit code.
- 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:
crabbox warmupleases a reusable box and prints both a stablecbx_...ID and a friendly slug.crabbox actions hydrate --id blue-lobsterregisters that box as an ephemeral GitHub Actions runner, dispatches the configured workflow, and waits for the workflow to write a ready marker.- The workflow runs normal Actions steps such as checkout, dependency install, cache/service setup, and secret-backed environment hydration.
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.
Direct provider mode still exists for debugging. If no broker is configured, --provider aws uses the local AWS SDK credential chain and --provider hetzner uses HCLOUD_TOKEN or HETZNER_TOKEN. The brokered path is the default operational model.
Status
Working today:
crabbox doctorcrabbox logincrabbox logoutcrabbox whoamicrabbox initcrabbox warmupcrabbox runcrabbox sync-plancrabbox historycrabbox logscrabbox resultscrabbox cachecrabbox statuscrabbox listcrabbox usagecrabbox admincrabbox sshcrabbox inspectcrabbox stopcrabbox actionscrabbox pool listcrabbox machine cleanupcrabbox cleanup- Cloudflare Worker coordinator on Workers/Durable Objects
- bearer-token coordinator auth for automation
- Cloudflare route for
crabbox.clawd.bot/* - Hetzner server provisioning with class fallback
- AWS EC2 Spot provisioning with class fallback
- minimal cloud-init bootstrap for SSH, Git, rsync, and work directories
- Git file-list rsync overlay of tracked and nonignored local files
- sync fingerprint skip for no-change hot runs
- per-lease SSH keys under the Crabbox config directory
- coordinator cost guardrails and monthly usage summaries
- coordinator run history and retained run-log tails
- JUnit test result summaries for recorded runs
- explicit warm-box cache controls
- operator login, identity, and admin lease controls
- provider-backed price estimates with static fallback rates
- sync sanity checks for mass tracked deletions
- shallow Git hydration for configured base-ref detection
- GitHub Actions-backed hydration into project-defined runner workspaces
- SSH execution on port
2222
Not yet done:
- untrusted multi-tenant isolation
Quick Start
Prerequisites:
git,ssh,ssh-keygen,rsync, andcurl- broker config in
~/.config/crabbox/config.yamlor~/Library/Application Support/crabbox/config.yamlon macOS
Configure the deployed broker:
printf '%s' "$CRABBOX_COORDINATOR_TOKEN" | \
crabbox config set-broker \
--url https://crabbox-coordinator.steipete.workers.dev \
--provider aws \
--token-stdin
Check local prerequisites and broker access:
crabbox doctor
Inspect broker config:
crabbox config show
Onboard a repo for Crabbox:
crabbox init
Warm a reusable testbox:
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:
crabbox actions hydrate --id blue-lobster
CI=1 crabbox run --id blue-lobster -- pnpm test:changed:max
Use AWS EC2 Spot through the broker:
crabbox warmup --class beast
Run a command on an existing lease:
CI=1 crabbox run --id blue-lobster -- pnpm test:changed:max
Inspect and connect:
crabbox status --id blue-lobster
crabbox ssh --id blue-lobster
crabbox inspect --id blue-lobster --json
Inspect usage and estimated cost:
crabbox usage
crabbox usage --scope org --org openclaw
crabbox usage --scope all --json
crabbox usage reads coordinator history, so it requires a configured broker. Cost is an estimate for compute leases, not a provider invoice: the coordinator prefers explicit CRABBOX_COST_RATES_JSON overrides, then provider pricing from AWS Spot history or Hetzner server-type prices, then built-in fallback rates. Full reference: docs/commands/usage.md.
Use the OpenClaw plugin when an agent should drive Crabbox through OpenClaw tools instead of shelling out manually. The repository root is also a native OpenClaw plugin package; install it from this repo or from a packaged release, then use the crabbox_run, crabbox_warmup, crabbox_status, crabbox_list, and crabbox_stop tools.
Stop a kept server:
crabbox stop blue-lobster
Print the CLI version:
crabbox --version
Machine Classes
beast is the default. Hetzner uses dedicated-server classes:
standard ccx33, cpx62, cx53
fast ccx43, cpx62, cx53
large ccx53, ccx43, cpx62, cx53
beast ccx63, ccx53, ccx43, cpx62, cx53
During verification, Hetzner rejected ccx63, ccx53, and ccx43 because of the account dedicated-core quota, so Crabbox fell back to cpx62.
AWS uses flexible EC2 Spot candidate pools:
standard c7a.8xlarge, c7i.8xlarge, m7a.8xlarge, m7i.8xlarge, c7a.4xlarge
fast c7a.16xlarge, c7i.16xlarge, m7a.16xlarge, m7i.16xlarge, c7a.12xlarge, c7a.8xlarge
large c7a.24xlarge, c7i.24xlarge, m7a.24xlarge, m7i.24xlarge, r7a.24xlarge, c7a.16xlarge, c7a.12xlarge
beast c7a.48xlarge, c7i.48xlarge, m7a.48xlarge, m7i.48xlarge, r7a.48xlarge, c7a.32xlarge, c7i.32xlarge, m7a.32xlarge, c7a.24xlarge, c7a.16xlarge
Set CRABBOX_SERVER_TYPE or pass --type to use another EC2 type such as c8a.24xlarge.
Cloudflare Deployment
Worker source lives in worker/.
Local checks:
npm ci --prefix worker
npm run format:check --prefix worker
npm run lint --prefix worker
npm run check --prefix worker
npm test --prefix worker
npm run build --prefix worker
Deploy:
export CLOUDFLARE_API_TOKEN="$CRABBOX_CLOUDFLARE_API_TOKEN"
export CLOUDFLARE_ACCOUNT_ID="$CRABBOX_CLOUDFLARE_ACCOUNT_ID"
npx wrangler deploy --config worker/wrangler.jsonc
Required Worker secrets:
HETZNER_TOKEN
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
CRABBOX_SHARED_TOKEN
The Worker is deployed at:
https://crabbox-coordinator.steipete.workers.dev
The Cloudflare route crabbox.clawd.bot/* is also attached and currently protected by Cloudflare Access.
OpenClaw Verification
Verified from /Users/steipete/Projects/openclaw on a Cloudflare-created fallback cpx62 runner:
CI=1 /usr/bin/time -p /Users/steipete/Projects/crabbox/bin/crabbox run --id cbx_f60f47cbc879 -- pnpm test:changed:max
Result:
- 61 Vitest shards completed successfully.
- End-to-end warm wall time was 93.66 seconds through the Cloudflare coordinator path.
- The timing includes rsync scan, remote Git hydration, command execution, and output streaming.
For the fastest dedicated-core verification, raise the Hetzner dedicated-core quota and re-run on ccx63.
Configuration
Config file:
broker:
url: https://crabbox-coordinator.steipete.workers.dev
provider: aws
token: ...
class: beast
capacity:
market: spot
strategy: most-available
fallback: on-demand-after-120s
aws:
region: eu-west-1
rootGB: 400
lease:
idleTimeout: 30m
ttl: 90m
ssh:
key: ~/.ssh/id_ed25519
user: crabbox
port: "2222"
Environment variables remain supported for automation and direct-provider debug:
HCLOUD_TOKEN or HETZNER_TOKEN Hetzner Cloud API token
AWS_PROFILE/AWS_* AWS SDK credentials for direct --provider aws fallback
CRABBOX_PROFILE default default
CRABBOX_PROVIDER default hetzner
CRABBOX_CONFIG optional config file override
CRABBOX_COORDINATOR optional broker URL override
CRABBOX_COORDINATOR_TOKEN optional broker bearer token override
CRABBOX_DEFAULT_CLASS default beast
CRABBOX_IDLE_TIMEOUT default 30m
CRABBOX_TTL default 90m
CRABBOX_SERVER_TYPE provider-specific override
CRABBOX_HETZNER_LOCATION default fsn1
CRABBOX_HETZNER_IMAGE default ubuntu-24.04
CRABBOX_HETZNER_SSH_KEY default crabbox-steipete
CRABBOX_AWS_REGION default eu-west-1
CRABBOX_AWS_AMI optional Ubuntu AMI override
CRABBOX_AWS_SECURITY_GROUP_ID optional security group override
CRABBOX_AWS_SUBNET_ID optional subnet override
CRABBOX_AWS_INSTANCE_PROFILE optional IAM instance profile name
CRABBOX_AWS_ROOT_GB default 400
CRABBOX_CAPACITY_MARKET spot or on-demand
CRABBOX_CAPACITY_STRATEGY most-available, price-capacity-optimized, capacity-optimized, or sequential
CRABBOX_CAPACITY_FALLBACK default on-demand-after-120s
CRABBOX_CAPACITY_REGIONS comma-separated AWS region candidates for Spot placement score
CRABBOX_CAPACITY_AVAILABILITY_ZONES comma-separated AWS availability zone candidates
CRABBOX_SSH_KEY default ~/.ssh/id_ed25519
CRABBOX_SSH_USER default crabbox
CRABBOX_SSH_PORT default 2222
CRABBOX_WORK_ROOT default /work/crabbox
CRABBOX_SYNC_CHECKSUM opt into checksum rsync
CRABBOX_SYNC_DELETE opt into/out of rsync --delete
CRABBOX_SYNC_GIT_SEED opt into/out of remote Git seeding
CRABBOX_SYNC_FINGERPRINT opt into/out of no-op sync skipping
CRABBOX_SYNC_BASE_REF default base ref to hydrate
CRABBOX_SYNC_TIMEOUT default 15m
CRABBOX_SYNC_WARN_FILES/BYTES large-sync warning thresholds
CRABBOX_SYNC_FAIL_FILES/BYTES large-sync failure thresholds
CRABBOX_SYNC_ALLOW_LARGE bypass large-sync failure thresholds
CRABBOX_RESULTS_JUNIT comma-separated remote JUnit XML paths
CRABBOX_CACHE_PNPM/NPM/DOCKER/GIT opt into/out of cache command kinds
CRABBOX_CACHE_MAX_GB cache policy size hint
CRABBOX_CACHE_PURGE_ON_RELEASE purge cache on release policy hint
CRABBOX_ENV_ALLOW comma-separated env allowlist
CRABBOX_OWNER bearer-auth usage owner override
CRABBOX_ORG bearer-auth usage org
CRABBOX_COST_RATES_JSON explicit hourly USD cost-rate overrides
CRABBOX_EUR_TO_USD Hetzner EUR-to-USD conversion, default 1.08
CRABBOX_MAX_ACTIVE_LEASES fleet active-lease limit
CRABBOX_MAX_ACTIVE_LEASES_PER_OWNER
CRABBOX_MAX_ACTIVE_LEASES_PER_ORG
CRABBOX_MAX_MONTHLY_USD fleet reserved monthly spend limit
CRABBOX_MAX_MONTHLY_USD_PER_OWNER
CRABBOX_MAX_MONTHLY_USD_PER_ORG
Forwarded environment is intentionally narrow and project-configured:
NODE_OPTIONSCI
Do not pass secret values as command-line arguments. Keep provider tokens outside the repository.
Development
Build from source:
go build -o bin/crabbox ./cmd/crabbox
Run the local gate:
gofmt -w $(git ls-files '*.go')
go vet ./...
go test -race ./...
scripts/check-go-coverage.sh 85.0
go build -trimpath -o bin/crabbox ./cmd/crabbox
goreleaser release --snapshot --clean --skip=publish
npm ci --prefix worker
npm run format:check --prefix worker
npm run lint --prefix worker
npm run check --prefix worker
npm test --prefix worker
npm run build --prefix worker
CI runs the same checks on pushes and pull requests.
Releases
Tagged pushes matching v* publish Go CLI archives through GoReleaser. Manual reruns can use the release workflow with a tag input.
GoReleaser also updates the Homebrew formula in https://github.com/openclaw/homebrew-tap, published to users as:
brew install openclaw/tap/crabbox
The release workflow needs HOMEBREW_TAP_GITHUB_TOKEN with write access to that tap repository.
Docs
- docs/architecture.md
- docs/orchestrator.md
- docs/cli.md
- docs/commands/README.md
- docs/infrastructure.md
- docs/source-map.md
- docs/mvp-plan.md
- docs/security.md
- CHANGELOG.md
License
Crabbox is released under the MIT License. See LICENSE.