The README keeps what the tool is, how to install it and the first commands, and points to the guide on docs.ihasmail.org. Everything else moves, whole, into docs/ and CONTRIBUTING.md, where it is organized for readers who want the detail. Where the old README disagreed with the code, the code wins.
4.8 KiB
Status and field reports
What works today, what run does, what has been tested against real servers,
and the state of each package. For the short version, see the
README.
Commands
| Command | State |
|---|---|
stalwart-migrate preflight |
Works — read-only checks and a migration plan |
stalwart-migrate rehearse |
Works — read-only; converts your settings and reports what won't carry over |
stalwart-migrate run |
Works — performs the migration; --recovery-point-confirmed --yes. Container deployments additionally need --container-path-unproven (see Docker deployments) |
stalwart-migrate tenants |
Works — read-only; who owns which domain, and what would block a migration |
stalwart-migrate status <id> |
Works |
stalwart-migrate report <id> |
Works — prints what validation found for a run |
Run stalwart-migrate <command> -h for the current flags of each.
What run does
run performs the migration in this order:
preflight → stage → dump → stop → convert → recovery-mode → cutover → validate
It needs two flags: --yes (intent) and --recovery-point-confirmed (a claim
that you have a snapshot or backup you have verified you can restore — this
tool cannot undo a migration and will not start without it). Start with
rehearse first: it is read-only, needs no maintenance window, and tells you
what run will and won't carry over. See rehearsal.md.
An interrupted run resumes from its last completed step with
stalwart-migrate run --resume <run-id> and the same flags; status lists run
IDs and shows which steps completed.
Validation after cutover
After cutover, run compares the migrated instance against the snapshot
preflight took, and fails the command if an account that existed before is
missing from it. A domain that no longer appears is reported as a warning
rather than a failure: the two versions do not agree on what counts as a
domain — principals on one side, Domain objects on the other — and failing a
migration over that difference would abort runs that lost nothing.
The service is left running either way. By that point the store has been
migrated in place, so stopping it would not undo anything; your recovery point
is the way back (see recovery.md). report <run-id> prints the
same finding again later. Where preflight had no admin URL to snapshot from,
validation reports itself as skipped rather than passed.
Field reports
A production server, 2026-08-25
The tool took a live server — nine domains, six accounts, a 2.4 GB RocksDB store — from 0.15.5 to 0.16.19 with 8 seconds of downtime, every phase green including post-cutover validation, mail flowing before and after. That run was preceded by a full dress rehearsal on a clone of the same server, which is the practice this project most recommends copying: see Rehearse on a clone first.
Three servers, reported in issue #1
@kaya-eu reported three successful 0.15.5 → 0.16.19 migrations on three servers: a testing and a production instance, each 16 domains, 55 accounts and roughly 221 GB of real mail, and an arm64 home server of 1.4 GB across 181 folders.
Read that with two qualifications:
- They ran a commit predating the automated Docker cutover, so they performed the cutover by hand. What those runs exercised is preflight, the dumps, the settings conversion and the recovery-mode store migration, not the container cutover.
- They hit things worth knowing about before you follow them:
- an arm64 binary that was fetched for the wrong architecture (fixed);
- a store migration that needed one more recovery-mode boot than the tool performs (not fixed — see The store migration may need one more recovery boot);
- data loss from booting recovery mode again after a completed migration (see Do not boot recovery mode again).
Code
Roughly 14,600 lines of Go, standard library only, of which about 6,300 are
tests. Every phase exists as a package, and run wires them into the
migration described above.
Lines are implementation only; each package carries its tests alongside.
| Package | Lines | Tests |
|---|---|---|
internal/stalwartapi |
1456 | yes |
internal/backup |
1333 | yes |
internal/preflight |
1035 | yes |
internal/applyplan |
913 | yes |
internal/cutover |
784 | yes |
internal/recovery |
431 | yes |
internal/checkpoint |
406 | yes |
internal/validate |
382 | yes |
internal/stage |
233 | yes |
internal/service |
201 | yes |
internal/plan |
130 | yes |
internal/config |
stub | — |