Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

sithbit-migrate settings

The store-migration tool’s configuration: two complete store definitions in one file. For what the tool moves, the dry-run/commit split, and the caveats to read before committing, see the operator guide sithbit-migrate: moving a store between backends.

sithbit-migrate

The one-shot tool that copies an existing store into another backend — accounts, mailboxes, messages with their chain state, blobs, and the job queue. It runs, reports, and exits; it serves no listener and no health endpoint, so nothing polls it.

Unlike every other binary — each of which carries a single [store] section — the migrator reads one store and writes another, so it holds two independent store configurations under [source] and [target]. Both take exactly the [store] shape the servers use, down to the per-backend sub-sections ([source.aws], [target.postgres], [target.blobs], and the rest), and both carry the same defaults — so a section you leave out is the dev SQLite store rather than an error. An empty or absent file migrates that store onto itself, a harmless no-op, which is what makes a dry run safe to try first.

Config file sithbit_migrate.toml (or SITHBIT_MIGRATE_CONFIG), env prefix SITHBIT_MIGRATE. Individual settings override from the environment the usual way, with __ descending one nesting level — e.g. SITHBIT_MIGRATE_TARGET__KIND=aws, SITHBIT_MIGRATE_TARGET__AWS__TABLE=sithbit-prod.

Row markers follow the going-public legend, read here as “before a real cross-backend run” rather than “before a listener goes public” — this tool binds nothing. Every marker is an operator obligation: the migrator has no validate(), and the no-op defaults are a document that runs cleanly while moving nothing.

KeyDefaultMeaning
[source](dev SQLite)REQUIRED (public). Left at its default this section is the dev SQLite store, so the run reads the wrong store and reports success. The store you are leaving — a whole [store] section under another name, typically kind = "sqlite". Every key and sub-section the [store] reference lists applies here unchanged
source.kind"sqlite"REQUIRED (public). Name the backend you are actually reading; nothing infers it. Backend of the store being read. The migrator compiles in every backend, so any kind may appear on either side
source.database"sithbit.db"RECOMMENDED (public). SQLite database file to read (kind = "sqlite"). The default is the dev file name — point it at the store you are actually leaving, and note that a missing file opens an empty database rather than failing
source.credential_key_file"credential.key"REQUIRED (public). The key that seals stored mail passwords in the source — it must be the key that store was actually written with, or the passwords it holds cannot be read back
[target](dev SQLite)REQUIRED (public). Left at its default the target is the same dev SQLite store as the source, which is the harmless no-op the tool ships with. The store you are moving to — the same whole [store] shape. Its tables, queues, and lease store are created idempotently when it opens, exactly as they are when a server first boots against it. The one exception is an S3 blob bucket: like the servers, the migrator assumes it already exists (Azure blob containers are auto-created; S3 buckets — including GCS buckets reached through the interop endpoint — are not)
target.kind"sqlite"REQUIRED (public). The setting that decides where the data lands. Backend of the store being written: "postgres", "aws", "azure", "turso" or "cloudflare" for a real migration
target.credential_key_file"credential.key"REQUIRED (public). Must resolve to the same 32 bytes as the source’s, and the migrator enforces it: before any migration step, dry run and --commit alike, it compares the two keys and refuses the run when they differ ([source] credential_key_file and [target] credential_key_file hold different keys: migrated mail passwords would be unreadable on the target. Copy the source key file to the target before migrating.). The key seals every stored mail password, which the copy carries as ciphertext, so a target opened under a different key would hold credentials nobody can read back. Copy the file, or point both sections at the same cloud secret, before migrating — and note that the target store is opened (and a missing target key file is generated fresh on disk) before the check fires, so a path that does not exist yet is refused as a mismatch and leaves a stray generated key file behind; copying the source key over it resolves both. See the caveats

Because both sections are [store] sections, their per-backend sub-sections are documented once, on the [store] reference[source.blobs] and [target.blobs] for the blob store, [target.postgres] for url, [target.aws] for region / table / queue_prefix, [target.azure] for account / table / queue_prefix / access_key, and [target.turso] / [target.cloudflare] likewise. Only the prefix differs. The one shape that does not simply follow the prefix is Cloudflare, whose blobs come from [target.cloudflare.r2] rather than [target.blobs].

A minimal SQLite-to-AWS file:

[source]
kind = "sqlite"
database = "sithbit.db"

[target]
kind = "aws"
credential_key_file = "credential.key"
[target.aws]
region = "us-east-1"
table = "sithbit"
queue_prefix = "sithbit"

The annotated mail_migrate/sithbit_migrate.example.toml shows the same document with every entry commented out at its default, and the PostgreSQL and Azure alternatives doubly commented beneath it — each replaces a table above rather than adding to it, so uncomment one level at a time.

Nothing reaches the target until you pass --commit; see Dry-run, then commit.