sctl
Everything the dashboard can do, from a terminal.
View as Markdownsctl is not a subset of the dashboard. Anything you can do in the browser you can do here,
because both call the same API.
It runs on Linux, macOS and Windows, as a single static binary with no runtime dependencies.
Signing in
sctl login # device code: prints a code, you approve it in a browser
sctl auth whoami # who am I, in which workspace, with which permissionsThe session lives in a config file written mode 0600 on Unix-like systems:
| Platform | Path |
|---|---|
| Linux | ~/.config/sctl/config.yaml |
| macOS | ~/Library/Application Support/sctl/config.yaml |
| Windows | %AppData%\sctl\config.yaml |
Switching workspace re-mints your token into that workspace, so every command acts on exactly one workspace, the one your session is scoped to.
sctl workspace list
sctl workspace switch <workspace-id>
sctl workspace currentEverything takes an ID
Commands take IDs, not names. sctl backup trigger prod-db fails with invalid backup id; it wants the UUID. Names exist for you to read in listings, and are not accepted as
arguments anywhere.
The idiom for scripts, and the one used throughout these docs:
BACKUP_ID=$(sctl backup list | awk '$1 == "prod-db" { print $2 }')
sctl backup trigger "$BACKUP_ID"# Windows PowerShell
$BackupId = (sctl backup list | Select-String '^prod-db\s').Line.Split()[1]
sctl backup trigger $BackupIdOr just read it once from sctl backup list and paste it.
Command surface
| Command | Covers |
|---|---|
login | Device-code sign-in |
auth whoami | Session state: user, workspace, permissions |
workspace | list, create, switch, current |
member | list, invite, role, remove, invitations, resend, revoke |
role | list, create, update, delete |
apikey | create, list, revoke |
worker | provision, list, rotate, delete |
backup | list, create, configure, trigger, pause, resume, delete, submit |
run | list --backup, get |
artifact | list, get, download, delete |
restore | Client-side restore of an artifact |
apply | Reconcile a YAML manifest |
Two things that look like omissions and are not:
backup createtakes--kind;backup configuredoes not. Kind is immutable after creation, so there is no field to patch.- There is no
artifact lock. Protection is a property of the backup's retention policy (--lock-for), stamped onto each artifact as it is archived. A per-artifact lock would be a second source of truth for the same guarantee.
Configuration
Every setting can come from the config file, a flag, or an environment variable prefixed
SCTL_.
| Setting | Flag | Environment variable |
|---|---|---|
| Backend URL | --backend-url | SCTL_BACKEND_URL |
| Access token | SCTL_ACCESS_TOKEN | |
| Workspace | SCTL_WORKSPACE_ID |
Flags win over the environment, which wins over the file.
Automation
For CI, use an API key rather than your own session. It is scoped to exactly the permissions you grant it, and revoking it does not sign you out.
sctl apikey create ci-backups --perms backups:read,backups:writeThen run non-interactively by putting the key where the session token would go:
export SCTL_ACCESS_TOKEN='sk_live_...'
export SCTL_WORKSPACE_ID='<workspace-id>'
sctl backup trigger "$BACKUP_ID"There is no separate "api key mode". The key is a bearer credential exactly like a session
token, so SCTL_ACCESS_TOKEN takes either. SCTL_WORKSPACE_ID is required too, because a
machine never ran sctl workspace switch.
A key that lacks a permission is refused with 403 naming the permission, rather than a bare
failure. See API keys.
Declarative
sctl apply -f saved.yamlapply reconciles what the file declares and never deletes what the file omits. Removing
something is always explicit:
sctl backup delete "$BACKUP_ID"That asymmetry is deliberate. A file is easy to typo, easy to check out at the wrong revision, and easy to run from the wrong directory; none of those should be able to destroy a retention policy.