SSyncropel Docs

Backup and Recovery

Protect your Syncropel data, including the database, task content, and configuration.

This page is for people running their own workspace or using the API. If you use a workspace created at syncropel.com, start with Your hosted workspace.

What to Protect

DataLocationContains
Database~/.syncro/hub.dbAll records, trust scores, routing rules
Task content~/.syncro-data/tasks/Task descriptions and artifacts
Aliases~/.syncro-data/aliases.tomlHuman-readable task alias mappings
Config~/.syncro/config.tomlIdentity, store URL, server settings

Automatic Backup

The spl serve instance backs up the database on every startup, into a per-instance directory keyed by the instance DID:

~/.local/share/syncropel/backups/instance-<did-tail>/hub.db.bak

<did-tail> is the 24-hex tail of your instance DID. To find your directory:

curl -s localhost:9100/health | jq -r .instance_did
ls ~/.local/share/syncropel/backups/instance-<tail>/

The daily backup job writes timestamped hub.db.<YYYYMMDD-HHMMSS> copies into the same directory. (An instance running on an isolated SYNCROPEL_HOME before its identity is bootstrapped keys the directory as home-<hash>/ instead.)

The backup directory is OUTSIDE ~/.syncro/: removing that directory does not affect the backups.

The identity is part of the workspace, and only a sealed backup carries it

A backup of hub.db alone is a backup of records. The signing keys live beside the store, not in it, and a restore without them boots as a DIFFERENT workspace: a drill on 2026-07-20 restored 89,152 records into a body that answered every authenticated route with BOOTSTRAP_REQUIRED. With SPL_BACKUP_PASSPHRASE set in the daemon's environment the daily backup seals the keys into the same set (spl backup --auto restore-keys brings them back), and the doctor's daily backup row and the workspace read say so: identity covered, or identity NOT covered with the reason and the last time a backup did cover it. A body that has never covered its identity warns even when its backup is fresh, because the thing it would bring back is not the workspace. The keys recovery window is bounded by the last successful boot that ran a backup, which is an older watermark than the last record flush; read both.

Manual Backup

# Create a timestamped backup (use your instance directory from above)
cp ~/.syncro/hub.db ~/.local/share/syncropel/backups/instance-<tail>/hub.db.$(date +%Y%m%d-%H%M%S)

# List available backups across all instance directories
ls -la ~/.local/share/syncropel/backups/*/

Recovery

# Stop the instance
spl stop

# Restore from backup
cp ~/.local/share/syncropel/backups/instance-<tail>/hub.db.bak ~/.syncro/hub.db

# Restart: trust and config rebuild automatically from records
spl serve

# Verify
spl status
spl task list
spl trust

Restoring onto a new machine: the store first, then the keys

On a new machine, put the backed-up store in place first, then bring the keys back:

# 1. Put the store copy in place (with the daemon stopped)
cp <backup>/hub.db.<timestamp> ~/.syncro/hub.db

# 2. Then restore the keys from the same backup set
#    (SPL_BACKUP_PASSPHRASE set to the backup's passphrase)
spl backup --auto restore-keys <backup>/keys.<timestamp>

Do it in this order. If you run restore-keys before the store is in place, the workspace signs with your original keys but can report the new machine's workspace name on /health. If that happened, put the store in place and run restore-keys again with --force.

From v0.270 a backup set also holds the workspace's order log (order.<timestamp>, beside keys.<timestamp>). restore-keys puts it back for you. If you copy files by hand instead, copy order.<timestamp> too: a store that held an order log and boots without one refuses to start (ORDER_LOG_MISSING) rather than quietly beginning a new history. If the set really has none, start with SPL_ACCEPT_NO_ORDER_LOG=1: the new log's first entry records the gap.

Portable Backup: Publish the Instance

From v0.118, the recommended portable backup is a published repository snapshot of the whole instance:

spl repo publish <namespace>/<repo> --relocation

--relocation is shorthand for --visibility L3 --scope instance --no-listing --carry-tokens: it snapshots the instance as a verified, portable repository tree. To rebuild a working instance from it on any machine:

# Client-side restore (with the daemon stopped)
spl repo restore <dir-or-https-url>

# Or hydrate a fresh home at boot
spl serve --repo <locator>

The startup .bak file protects against local file loss; the published repository is the canonical portable recovery form: it travels between machines and verifies its own integrity on restore.

The open tail is verified, or refused by name

A tree's sealed segments were always verified on restore. Since v0.235 the open tail (log/open/current.jsonl, the records since the last seal) is too: the writer records the tail's hash in refs/HEAD, measured from the bytes in the store, and spl repo restore, spl repo mount and spl repo drift read the tail through one verdict, reported as tail.status:

StatusMeaning
verifiedHEAD vouched for the tail and the bytes matched; its records were taken.
refusedThe fetch, the hash or the parse failed against HEAD's claim. Nothing from the tail was taken, the sealed set was restored alone, tail.reason names why and tail.not_taken says how many lines stood behind it. One well-formed record appended to the tail by anyone who can write one object into the prefix is exactly this case: content-addressing says nothing about who wrote a whole object.
unverifiedThe tree was written before v0.235 and HEAD carries no tail hash; the caller passed --accept-unverified-tail and the lines were taken with the verdict on the report. Without the flag such a tail is refused.
absentHEAD names no open segment.

The price of refusing to trust an unvouched tail is bounded by one flush interval: a different body re-basing a dead writer's tree does not carry records that lived only in a tail HEAD could not vouch for.

A copy older than your live workspace is refused unless you say so

From v0.272, spl repo restore checks the copy against the live workspace before anything lands. If the copy is older, the restore stops and names both points, so an old copy can't quietly undo newer changes, such as a key you removed:

spl repo restore <locator>                  # refused: the live workspace is newer
spl repo restore <locator> --accept-older   # proceed; the restore records that you chose this

The same check runs for spl repo mount, spl move, spl backup restore-keys and spl import. The one exception is restoring keys from a daily backup set: a key set is always older than the live workspace, and restoring it is the normal recovery, so it is noted rather than refused. Whatever you restore, anything you revoked or removed stays revoked or removed: the lists of revoked keys and erased people are joined, never replaced.

CodeWhat it means
LIVE_TREE_NEWERThe live workspace is newer than the copy. Nothing was restored.

Your task notes travel with the backup

From v0.272, task notes (~/.syncro-data/) travel with the off-machine copy, and the startup backup writes data.bak.tar.zst beside hub.db.bak. Restore them on their own with:

spl backup restore-data <backup-set>            # adds what's missing; refuses a differing file
spl backup restore-data <backup-set> --replace  # saves the current notes first, then replaces

restore-data never overwrites a note that differs from the backup unless you pass --replace, and then it saves a copy under ~/.local/share/syncropel/backups/tasks/ first. spl repo restore and spl move bring the notes into an empty data folder; otherwise they tell you the command to run.

CodeWhat it means
DATA_CONFLICTA note in the backup differs from yours. Every conflict is listed and nothing was written.

What Rebuilds on Startup

The engine reconstructs all derived state from the immutable record log:

  • Trust scores: replayed from outcome records
  • Engine config: replayed from configuration records
  • Thread state: derived from records (state = fold(records))

Prevention

  • Use separate ports for development (--port 9200) and production (port 9100)
  • Development instances use --memory for ephemeral storage
  • Task content files in ~/.syncro-data/ are independent of the database

On this page