Crabbox docs

Islo

Islo

Read when:

  • choosing provider: islo;
  • configuring the Islo sandbox image, sizing, snapshot, or gateway profile;
  • reviewing how Crabbox behaves on a delegated-run provider.

provider: islo is a delegated-run provider: Islo owns the sandbox and the command transport, while Crabbox owns local config, repo claims, the sync manifest and its guardrails, slugs, timing summaries, and normalized list/status rendering. Crabbox uses the Islo Go SDK for auth and sandbox lifecycle (create, list, status, pause, resume) and calls the HTTP API directly for stop (an empty-body DELETE), archive upload, shares, and command output — the last via a small SSE reader for the POST /sandboxes/{name}/exec/stream endpoint, since the SDK's exec helper coalesces streamed output.

Sandboxes are Linux-only. crabbox ssh --provider islo can print a direct SSH command for a Crabbox-created sandbox at <sandbox>.islo, but Islo run commands and sync still use Islo's streaming exec and archive APIs rather than SSH/rsync.

#Auth

export ISLO_API_KEY=ak_...

ISLO_BASE_URL (or islo.baseUrl) overrides the default https://api.islo.dev. Both keys also accept the CRABBOX_-prefixed forms CRABBOX_ISLO_API_KEY and CRABBOX_ISLO_BASE_URL, which take precedence.

#Config

provider: islo
target: linux
islo:
  baseUrl: https://api.islo.dev
  image: docker.io/library/ubuntu:26.04
  workdir: crabbox
  gatewayProfile: ""
  snapshotName: ""
  vcpus: 2
  memoryMB: 4096
  diskGB: 20
  idlePause: false

Defaults: baseUrl https://api.islo.dev, workdir crabbox, vcpus 2, memoryMB 4096, diskGB 20. Crabbox keeps the resolved image/capacity defaults in config for display and override compatibility, but omits implicit default image, vcpus, memoryMB, and diskGB values from sandbox creation so Islo can use its tenant defaults. Explicit config, environment, and flag values are still sent even when they equal the Crabbox defaults.

islo.workdir is a relative directory name under /workspace. Absolute paths and .. escapes are rejected before sandbox creation. Crabbox applies the value to archive upload and command execution, so the working set still lands in /workspace/<islo.workdir> without relying on create-time workdir support.

Each config key has an equivalent flag and CRABBOX_ISLO_* environment variable:

Config keyFlagEnv var
baseUrl--islo-base-urlCRABBOX_ISLO_BASE_URL
image--islo-imageCRABBOX_ISLO_IMAGE
workdir--islo-workdirCRABBOX_ISLO_WORKDIR
gatewayProfile--islo-gateway-profileCRABBOX_ISLO_GATEWAY_PROFILE
snapshotName--islo-snapshot-nameCRABBOX_ISLO_SNAPSHOT_NAME
vcpus--islo-vcpusCRABBOX_ISLO_VCPUS
memoryMB--islo-memory-mbCRABBOX_ISLO_MEMORY_MB
diskGB--islo-disk-gbCRABBOX_ISLO_DISK_GB
idlePause--islo-idle-pauseCRABBOX_ISLO_IDLE_PAUSE

gatewayProfile accepts an Islo gateway profile name or id and is passed opaquely in the sandbox create request. Gateway profiles are created and managed on the Islo side and configure Islo's own egress gateway for the sandbox — the setting is unrelated to the Crabbox coordinator. When unset, the field is omitted from the create request so Islo applies its own default.

crabbox warmup --provider islo --islo-image docker.io/library/ubuntu:26.04
crabbox run --provider islo -- pnpm test
crabbox status --provider islo --id blue-lobster
crabbox pause --provider islo blue-lobster
crabbox resume --provider islo blue-lobster
crabbox heartbeat --provider islo blue-lobster
crabbox stop --provider islo blue-lobster

#Idle pause policy (opt-in)

Set --islo-idle-pause, islo.idlePause: true, or CRABBOX_ISLO_IDLE_PAUSE=true to send the idle timeout on new sandbox creation as pause_after_idle, rounded up to seconds, with auto_resume=never. The default sends no lifecycle policy. Existing policies are not rewritten, and no provider deletion deadline is added.

An idle policy may pause long-running or externally accessed workloads because provider activity accounting is not established. Reused paused leases are explicitly resumed before run --id or ssh, regardless of this option. Reclaim with the option enabled rejects a reported incompatible idle timeout; legacy responses without policy metadata remain adoptable without confirming policy enforcement. See the provider reference for the operational tradeoffs and recovery behavior.

#Behavior

  • warmup creates a crabbox-... Islo sandbox and records a local lease ID of the form isb_<sandbox-name> plus a Crabbox slug.
  • run creates or reuses a sandbox, validates islo.workdir, builds the Crabbox sync manifest, uploads it as a gzipped archive into /workspace/<islo.workdir>, streams stdout/stderr from Islo's SSE exec endpoint, and returns the remote exit code. A stream is only treated as successful once an exit event arrives.
  • list and status go through the Islo SDK; stop issues a direct DELETE. All three act only on Crabbox-created sandboxes. Identifiers may be a Crabbox slug, an isb_... lease ID, or a Crabbox-created sandbox name; non-Crabbox sandboxes are rejected.
  • Every sandbox has an immutable ID in addition to its name. Crabbox publishes that ID with the local claim in one guarded write at create and --reclaim adoption. A competing claim is not overwritten. crabbox inspect --json reports it as providerResourceId, and status prefers a by-ID lookup over the sandbox name. A malformed by-ID response fails closed; a name fallback that cannot match the claimed ID is reported as not ready and cannot trigger remote Tailscale checks.
  • stop validates the claimed resource ID and current name before sending a name-based DELETE. It confirms completion with a matching GET /sandboxes/-/by-id/{id} response whose status is deleted, or with a 404 on a name positively identified during the same teardown. A deleted_at timestamp without terminal status is not proof. Only confirmed cleanup drops the local claim; an uncertain outcome keeps it for retry. List omission is never deletion evidence.
  • DELETE /sandboxes/{name} is name-only, so for a lease with a resource ID the teardown will not delete a name it has not identified. It refuses on a positive identity mismatch (the name resolves to a resource id the lease does not own) and it also refuses when neither lookup could identify the name at all, such as during an API read outage: deleting blind could destroy a different sandbox. Such a stop fails, keeps the claim, and says to retry it - the sandbox may still be running and billable until then. These checks do not make DELETE atomic by ID: an out-of-band replacement between the read and the name-based DELETE can still race with cleanup. Creator attribution (created_by and created_by_entity) is diagnostic only; differences are advisory, not an ownership or account boundary.
  • A legacy claim without a resource ID preserves the existing name-only cleanup fallback. If teardown cannot positively identify a resource, a 404 on its name is reported as the weaker name-404-unbound proof, with a warning that no specific resource generation was confirmed. Recreate the lease to obtain an ID-bound claim.
  • Cleanup after a run retains its recovery claim when teardown is unproven. Run cleanup and Tailscale setup rollback check the resource identity and repository owner captured at acquisition, leaving a replacement claim untouched. A missing or unreadable claim is also a cleanup failure, never authority to delete by a stored name. Recover and verify the original resource ID before cleaning up that sandbox; it may remain running and billable.
  • pause snapshots the sandbox and releases its active compute while preserving the local lease claim; resume restores the sandbox to running.
  • heartbeat runs one no-op true exec against a running sandbox, which is what registers activity, and reports the sandbox's echoed lifecycle.pause_after_idle when it has one. It writes no lifecycle policy, so it cannot change a sandbox's absolute deadline, and persists nothing locally. A sandbox observed as paused or terminal is refused; heartbeat does not call the resume API. See providers/islo.md.
  • The sandbox is deleted on release unless kept. --keep-on-failure keeps a newly created failed sandbox until an explicit stop or provider-side expiry.

#Create deadlines and uncertain responses

Creation has a five-minute client budget; an earlier caller deadline still wins. A failed or incomplete create response reports an unconfirmed name, not an acquired lease. See create deadlines and uncertain responses for transport boundaries and explicit identity-checked recovery.

#URL bridge (per-port shares)

Islo declares the url-bridge capability. Crabbox publishes a per-port public HTTPS share for an exposed sandbox port via Islo's POST /sandboxes/{name}/shares API and reuses an existing share for the same port when one is present. This is how delegated providers surface a reachable URL in place of an SSH-tunneled bridge.

Requested share TTLs are clamped into Islo's legal 60s–7d range, matching the pond peers --share-ttl contract. Reuse skips a share that expires within the next 30 seconds, so a nearly-expired share is replaced with a fresh one rather than handed out.

#Tailscale (userspace tailnet)

Islo advertises FeatureTailscale in addition to url-bridge. Because Islo is a delegated-run provider with no Crabbox-managed SSH lease, Crabbox cannot reuse the SSH runner-bootstrap that VM providers (Hetzner/Azure/GCP) use to join the tailnet. Instead, when a lease is created with --tailscale, Crabbox brings the sandbox onto the tailnet through the Islo exec stream — no Islo-side changes are required:

  1. it downloads the pinned static Tailscale build into the sandbox (the image ships wget, not curl, and has no systemd to run the packaged unit);
  2. it starts tailscaled in userspace-networking mode. This is deliberate: kernel mode rewrites the sandbox routing table, which severs the Islo exec transport mid-run. Userspace mode never touches host routing, so the node joins the tailnet and the exec channel survives;
  3. it runs tailscale up with the pond-scoped advertise tags, TS_CONTROL_URL as --login-server when set, and any configured exit-node flags;
  4. it records the assigned tailnet IPv4 on the lease claim for health and ACL checks. pond peers keeps the URL bridge as the member's dialable transport and notes that Tailscale is available for outbound proxy traffic only.
export CRABBOX_TAILSCALE_AUTH_KEY=tskey-auth-...     # reusable, ephemeral, tagged node auth key
crabbox warmup --pond mesh --slug node-a --provider islo --tailscale
crabbox warmup --pond mesh --slug node-b --provider islo --tailscale
crabbox pond peers --pond mesh --json                # URL transport plus outbound-proxy note

The static build and its architecture-specific SHA-256 digests are pinned together in Crabbox. The direct auth key must be both reusable and ephemeral. Reusable is required because memory-only identity must re-enroll after daemon loss; ephemeral keeps those replacement device records from accumulating after sandboxes disappear. Tailscale auth keys are opaque, so Crabbox cannot inspect these properties and treats the supplied key as an operator contract. The Islo path runs Tailscale in userspace mode, so it does not install a kernel TUN route. For enrolled leases, Crabbox supplies workload commands with local proxy defaults (ALL_PROXY=socks5://127.0.0.2:1055, HTTP_PROXY=http://127.0.0.2:1055, and HTTPS_PROXY=http://127.0.0.2:1055) and their lowercase equivalents; explicit command environment values in either case override those defaults. An explicit ALL_PROXY/all_proxy also suppresses the protocol-specific defaults. Other processes must opt into those proxies or another userspace Tailscale surface. The proxy uses 127.0.0.2, separate from userspace Tailscale's inbound loopback mapping. Crabbox runs tailscaled as root with its binaries and control socket in a root-only directory, while repository sync and workload commands run as Islo's non-root islo user. Node identity stays in memory and the auth key is passed through stdin, so an Islo filesystem snapshot cannot clone either credential. The control socket is revalidated before lease reuse, status reporting, and pond peers; after daemon loss, recovery requires a usable auth key. If recovery fails, stale tailnet claim metadata is removed and the lease remains visible through its URL bridge for status and discovery. run fails closed instead of executing an enrolled workload with ordinary direct egress. Read-only status and pond peers checks do not run the long repair path; lease reuse through run performs re-enrollment when needed. Unproxied process traffic still uses the sandbox's normal network namespace. Exit-node settings are passed through to tailscale up, but only traffic sent through the userspace Tailscale path uses them. Inbound tailnet connections are blocked with shields-up; Islo's FeatureTailscale contract is outbound proxy access, not a forwarded loopback service surface.

A lease warmed without --tailscale is unchanged: no tailnet IP is recorded and pond peers reports it on the URL bridge as before. The pond ACL tag and its auto-bootstrap (CRABBOX_POND_ACL_BOOTSTRAP=1 + TS_API_KEY) apply to Islo exactly as they do for other direct Tailscale-capable providers. Tailscale enrollment is creation-time only: a reused plain Islo lease must be recreated with --tailscale rather than enrolled in place.

#Rejected options

Because Islo owns command transport and there is no Crabbox-managed SSH/rsync target, these run options are rejected:

  • --sync-only, --checksum, --force-sync-large, --full-resync — no Crabbox rsync target to drive.
  • --script, --script-stdin, --fresh-pr, local stdout/stderr captures, --capture-on-fail, --artifact-glob, --env-helper, --stop-after — these require Crabbox-owned transport or execution. --require-artifact and --download support safe relative single files up to 64 KiB, retrieved through Islo exec after a successful command.

Large-sync guardrails still apply: the gzipped archive upload runs the same size preflight as rsync providers, but because --force-sync-large is rejected on Islo, an oversize sync cannot be forced through and fails the preflight instead. --shell passes the raw shell string through to the remote shell.

#SSH access

Crabbox can resolve kept Islo sandboxes for direct SSH:

islo ssh --setup
crabbox ssh --provider islo --id blue-lobster
# ssh islo@crabbox-repo-abcdef.islo

Install and authenticate the Islo CLI, then run islo ssh --setup once to install its SSH proxy configuration and short-lived certificate support. The Islo CLI must remain authenticated through islo login or ISLO_API_KEY; CRABBOX_ISLO_API_KEY alone is only read by Crabbox. By default the rendered target is islo@<sandbox>.islo on port 22. Explicit ssh.user, ssh.port, or ssh.key settings are honored. This is a login helper only: vnc, code, Crabbox rsync, and Actions hydration are not available on provider: islo. When you need a Crabbox-managed SSH box, use Hetzner, AWS, static SSH, or Daytona instead.

#Why the provider kind stays delegated-run

The provider declares core.ProviderKindDelegatedRun in its Spec (internal/providers/islo/provider.go) rather than an SSH lease. The current adapter has an unattended API transport, but no Crabbox-managed SSH credential lifecycle:

  • The adapter uses sandbox lifecycle, exec, and files APIs. It does not obtain an SSH endpoint or issue a per-lease SSH key or certificate through those calls, and it has no associated credential expiry or revocation operation.
  • The provider's SSH story for humans depends on the interactive one-time islo ssh --setup step described above, which installs the Islo CLI's own SSH proxy configuration and short-lived certificate support locally. Crabbox can render the resulting target (Resolve in internal/providers/islo/ssh.go), but it cannot provision that setup, cannot verify it from an unattended runner, and cannot bound its lifetime — so it is not usable as automation transport.

Changing the provider kind alone would not supply those missing integration steps. run, sync, and teardown therefore stay on the exec, files, and delete APIs Crabbox can drive unattended, and crabbox ssh stays a login helper. See the Islo SSH setup documentation for the separate human-login workflow.

An unattended SSH-lease integration would need all four of these:

  1. an API-issued SSH hostname or endpoint for a sandbox;
  2. an API-issued short-lived credential bound to a known OS user;
  3. an explicit expiry on that credential;
  4. a revocation mechanism that takes effect before that expiry.

#Identity and absence semantics

The identity and absence distinctions the adapter must preserve, and what Crabbox does with them today:

  • id identifies one resource; name is an addressing label. The API assigns a sandbox id at creation and exposes a lookup by that public UUID. A caller-supplied name must not be treated as unique over time: a name that resolves is not proof that a previously recorded resource is still live. Crabbox records that immutable ID with new or explicitly adopted claims. Lifecycle operations still address the generated, normalized sandbox name; an identity check is not an atomic delete-by-ID guarantee.
  • Status prefers the resource ID recorded on the claim. The adapter uses the by-id endpoint and validates the response identity. A name fallback that cannot match the claimed ID is reported as not ready and cannot trigger remote Tailscale checks. This status behavior does not turn name-based SSH resolution or delegated execution into ID-addressed operations. A name lookup answers a question about that name at one point in time, not about every resource that has occupied it.
  • The list endpoint is eventually consistent and must not be used to prove absence. It can keep returning a sandbox for seconds after that sandbox's own GET reports 404. Crabbox calls ListSandboxes only for inventory rendering, from List and Doctor (internal/providers/islo/backend.go), never to decide whether one specific sandbox still exists.

crabbox stop requires an exact local claim and confirms cleanup before removing it or printing released. For an ID-bound claim, completion requires a by-ID response matching the recorded ID with status deleted, or a name 404 after positively identifying the resource during that teardown. A by-ID 404 alone is not a tombstone, and deleted_at alone is not terminal proof. A matching ID and terminal status do not require a deletion timestamp.

The low-level HTTP client's acceptance of a DELETE response below 400 or an idempotent 404 is not sufficient for stop to succeed. Uncertain identity reads or unproven completion retain the claim for retry. Legacy claims without an ID can fall back to the weaker name-404-unbound proof with a warning when no resource was positively identified. See teardown safety for the ownership checks and remaining name-reuse race.

HTTP-level idempotency does not bypass the local claim check: after confirmed cleanup removes the claim, a second stop exits 4 without reaching the API. An unconfirmed cleanup keeps its claim so it can be retried without reclaiming the lease.

The create API also accepts an optional request_id, which the adapter does not send today. Any future retry integration must establish its idempotency and conflict semantics rather than infer from an HTTP status that a failed create allocated nothing.