What “instance scope” means
An instance bundle is the whole Crewship in one file:- a page-level snapshot of the whole database (SQLite’s online backup API) — every workspace, user, credential row, setting and history;
- every file store next to it: attachment files and workspace files, the memory trees and their version blobs, page projects, chats and skills, each file listed with its SHA-256 in the payload’s index;
- every workspace’s crew containers (workspace, memory, home and tools, per the scope level);
- optionally, the recovery kit: the vault keys the database’s sealed values need (see below).
crewship recover into an empty
data directory. Crew-scope and workspace-scope backups are covered in the
Backup & Restore guide; this page is about what the instance
scope adds.
Access control
- Instance administrators only. Every instance backup route sits behind
the instance gate: the
CREWSHIP_OWNER_EMAILbreak-glass owner, or anyone an instance admin named (users.instance_role = 'ADMIN'). An install that named nobody named the owners of its oldest workspace once, the first time anyone’s status was asked, recorded it in the instance audit (instance.admin_bootstrapped) and never does it again — removing every named admin later does not hand the instance to that workspace’s owners. An OWNER or ADMIN of a single workspace is refused with403. CLI tokens need theinstance:adminscope. - Per-workspace backups follow the same rule. The
/api/v1/admin/backups*routes act on the workspace the request names and admit its OWNER or ADMIN, or an instance administrator who is not a member of it. Nobody else reaches another workspace’s bundles. - One run at a time. A second run while one is going answers
409. - Always encrypted. An unencrypted instance bundle is refused. Prefer age
recipients (
age1…public keys whose private halves live offline). A passphrase is accepted, but with the recovery kit on it is then the only thing between a copied bundle and every secret in the instance: use a long, generated one.
The recovery kit
Sealed values (credentials, webhook secrets, integration keys) are AES-256-GCM envelopes stamped with the key version that sealed them (v1:, v2: …). The
keys live in the environment (ENCRYPTION_KEY, ENCRYPTION_KEY_V2 …), never
in the database, so a database backup alone restores credentials nobody can
read.
With the recovery kit on (crewship admin instance backups recovery-kit on,
off by default, audited), every key version an envelope in the database
references rides inside the age-encrypted payload of each instance bundle,
as recovery-kit/keys.json, byte for byte as the source had it (the journal
hash chain key is derived from the ENCRYPTION_KEY string itself). Workspace
and crew bundles never carry it. crewship recover writes the keys to
<data-dir>/recovered-keys.env (mode 0600).
Whoever holds a private backup key can read every secret in the instance.
Store the age private key on a different device from the one that keeps the
live ENCRYPTION_KEY, and delete recovered-keys.env once its lines are in
your secret store.
GET /api/v1/admin/instance/backups/vault-keys shows, per version, how many
sealed values use it and whether this server has its key — counts only, never
key material. A referenced version the server cannot resolve is recorded in the
bundle as vault_key_missing rather than silently left out.
Crypto chain
- Bundle alone: the payload is age-encrypted.
- Bundle + age key, no kit: the database opens, sealed values do not —
the attacker still needs the source’s
ENCRYPTION_KEY. - Bundle + age key, kit on: full compromise of every secret in the instance. That is the price of a restore that works without the source host.
The quiet window
Release 1.0 requires one writing server per persistent local database, enforced by a process-lifetime OS writer lease before database startup. A second writer is refused; network/shared storage is not supported. Stop all old daemons before upgrading, because older binaries do not participate in this protocol. The quiet window checks the same owner before copying and at the end of the copy. Lost ownership aborts the backup without publishing its staged bundle; health/readiness reportwriter_ownership: lost and the server stops. See the
single-writer decision.
A consistent copy needs the database and the files to agree. The server first
drains running work (agent runs, started work items, routine runs; up to two
hours, then the run is skipped), then holds every write to protected data
for the copy only: mutating HTTP requests answer 503 with Retry-After
(backup, instance-hold and sign-in routes stay open), inbound webhooks answer
503, schedulers and queue consumers do not fire, and retention and GC
sweepers wait. A hard cap (20 minutes) releases the hold and abandons the copy.
The hold lives in memory only: a crash mid-copy comes back with nothing held.
Packing and encryption run after the hold is released; the run records
hold_ms.
Restore: sessions, keys and holds
crewship recover runs offline and:
- drops every sign-in session and pairing, clears backup locks, the scheduler lease and provider-login leases;
- writes a new
NEXTAUTH_SECRETandCREWSHIP_ADMIN_TOKEN_HMAC_KEYinto<data-dir>/secrets.env, so no token issued by the source works on the restored server (the rotation is enforced, not advisory); - clears
instance_config.hostname, which the new host fills in at first boot; - holds routines and schedules, inbound webhooks and queued work (including
notification retries) in
instance_holds, so nothing fires, answers or retries until an instance admin resumes it (crewship admin instance holds resume <key>, audited).
Rotation policy
- age recipient: rotate whenever a holder of the private key leaves. Bundles sealed to the retired key should be re-sealed or retired.
ENCRYPTION_KEY: rotate at least yearly, or immediately on suspected host compromise, with versioned keys (CREWSHIP_ENCRYPTION_KEY_VERSION,ENCRYPTION_KEY_V2…) andcrewship admin reencrypt. With the recovery kit on, bundles taken before the rotation still carry the old versions.NEXTAUTH_SECRET: rotated by everycrewship recover; rotate by hand if an operator session token leaks.
Incident response
If an instance bundle is known or suspected to have leaked:- Rotate the age recipient holding the matching private key.
- If the bundle carried the recovery kit, treat every secret in the instance
as exposed: rotate
ENCRYPTION_KEY(a new version pluscrewship admin reencrypt) and the upstream credentials themselves. - Force-logout every session by rotating
NEXTAUTH_SECRET. - Review
crewship admin instance auditforinstance.backup_*entries in the incident window.
Known limitations
- A live compromise before the backup is out of scope: root on the source is inside the trust boundary.
MANIFEST.jsonis plaintext soinspectworks without a key. It carries no secrets, but workspace names and slugs, hostnames, file counts and key version numbers are visible.- Recovery goes into an empty data directory only (
--forcereplaces an existing one). Merging an instance bundle into a running server is not supported. - Crew container files are staged under
<data-dir>/restore-staging/crews/; they are not landed in containers automatically.