Desktops and lifecycle
Lifecycle states, generation fencing, TTL/auto-hibernate, and first-wake env/setup.
A desktop is a real, persistent Linux machine: an id, an org, a tier, an
image version, and a disk that survives hibernation. Its wire shape
(DesktopJson) is what every desktop-returning route responds with:
{
"id": "...",
"org_id": "...",
"tier": "small",
"state": "awake",
"image_version": "...",
"generation": 3,
"current_generation_id": "...",
"idle_timeout_secs": 900,
"billing_mode": "hourly",
"host_id": "..."
}Lifecycle states
A desktop's state field is one of:
| State | Meaning |
|---|---|
provisioning | Just created, not yet placed on a host. |
waking | A wake operation is in flight. |
awake | Running -- compute is billed, guest ops (exec/files/stream) are available. |
hibernating | A hibernate operation is in flight. |
hibernated | Stopped -- disk state preserved, compute not billed. |
destroyed | Permanently gone, along with its disk. Irreversible. |
wake, hibernate, and destroy are the three mutating transitions,
triggered by POST /v1/desktops/{id}/{wake,hibernate,destroy}. Each of
those returns an operation handle rather than
blocking until the transition finishes -- see that page for the polling
model.
Guest operations (exec, files, stream) only work while a desktop is
awake; calling them against a desktop in any other state is a 409 Conflict (see API overview).
Generation fencing
Every desktop carries a generation, an integer that increments across its
lifecycle transitions. Mutation requests can optionally include an
expected_generation in the request body:
{ "expected_generation": 3 }If the desktop's current generation doesn't match, the operation settles
failed with a "generation mismatch" error, surfaced as 409 Conflict when
observed through ?wait=true (or on the settled Operation itself when
polled directly). This is a compare-and-swap-style guard against
stale-read races -- e.g. two callers racing to wake the same desktop, or a
caller acting on a desktop it fetched a while ago and hasn't refreshed
since. Omitting expected_generation skips the check entirely.
Fork
POST /v1/desktops/{id}/fork creates independent copies of a hibernated
desktop -- see Concepts: Forking for the
full model.
TTL and auto-hibernate
Every desktop carries idle_timeout_secs -- seconds of guest idle time,
measured from wake, before the desktop is automatically hibernated:
-
Set it at creation via
CreateDesktopBody.idle_timeout_secs, or change it on an existing desktop withPATCH /v1/desktops/{id}:curl -sS -X PATCH "$BASE/desktops/$DESKTOP_ID" \ -H "$AUTH" -H 'Content-Type: application/json' \ -d '{"idle_timeout_secs": 3600}' -
0means never auto-hibernate. A negative value is400. -
Omitting
idle_timeout_secson aPATCHis a no-op -- the current desktop is returned unchanged, per normal PATCH semantics. -
The clock resets from each wake, not from the last guest activity inside it -- "idle" here means "time since this desktop last transitioned to
awake," not an activity heuristic.
Ephemeral desktops auto-destroy, not just hibernate
A desktop created as ephemeral (currently only possible via
fork with "ephemeral": true) does not
just hibernate at its idle timeout -- it DESTROYs itself. Its
idle_timeout_secs is forced to 900 (15 minutes) at creation
regardless of what its source desktop's own value was. Treat an
ephemeral fork as disposable: don't park anything in it you'd mind
losing.
env and setup_script
POST /v1/desktops accepts two optional first-wake bootstrap fields:
{
"tier": "small",
"env": { "APP_ENV": "staging", "API_TOKEN": "..." },
"setup_script": "#!/bin/sh\napt-get update && apt-get install -y ripgrep\n"
}| Field | Written where | Caps |
|---|---|---|
env | /root/.canto/env in the guest, on the desktop's first successful wake | 100 keys / 64 KiB total serialized size; every key must match ^[A-Za-z_][A-Za-z0-9_]*$ |
setup_script | Run once in the guest under hostd's reserved process_id = "setup", on first successful wake | 64 KiB |
Both are 400 past their cap (or, for env, on any key that doesn't match
the POSIX env-name rule). Neither is echoed back by GET/POST on the
desktop -- they're write-only bootstrap inputs, not persisted/readable
fields on the DesktopJson wire shape. setup_script runs exactly once,
on the desktop's first wake; it does not re-run on subsequent wakes or
after a hibernate/wake cycle.