From 80a76fbb4a529253a92353a1a5b9012b7293904f Mon Sep 17 00:00:00 2001 From: John Coffey Date: Mon, 24 Aug 2026 17:18:33 -0700 Subject: [PATCH] Lead with the production result and the two preconditions Someone deciding whether to trust this needs the first fact immediately: it has migrated a real mail server, and what that cost in downtime. It was buried in a Status section under a line count. The two things an operator has to fix before starting - multi-tenancy, and not migrating as the built-in admin - now sit above everything else too. Both are changes to the directory rather than flags, so finding them after booking a maintenance window is the wrong order, and that is exactly how both were found on the production run. Status keeps the line counts and the command table and no longer repeats the paragraph above it. --- README.md | 74 +++++++++++++++++++++++++++---------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 0f0f6d7..9ec88ac 100644 --- a/README.md +++ b/README.md @@ -10,19 +10,46 @@ job](#recovery-is-your-job) before using it on anything you care about. Go, standard library only — no external dependencies. +**This has been used to migrate a production mail server.** On 2026-08-25 it +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](#rehearse-on-a-clone-first). + +## Before you start: two things you must fix on the server + +Neither is something this tool can do for you, and both stop a migration +dead. `preflight` refuses on both, while the mail server is still running — +but they are worth knowing before you book a maintenance window, because +fixing them is a change to your directory, not a flag. + +1. **Remove or collapse multi-tenancy.** v0.16 requires a tenant-scoped + account to sit on a domain owned by that same tenant, for its primary + domain and every alias. v0.15 imposed no such rule, so an install that is + perfectly valid today can be unrepresentable in v0.16. Run + `stalwart-migrate tenants` to see who owns what. Where a domain has no + tenant of its own and only one tenant's accounts use it, the conversion + repairs it for you; where two tenants genuinely share a domain, nothing + can, and you must resolve it in v0.15 first — give each tenant its own + domains, move the accounts into one tenant, or remove the tenants + entirely. + +2. **Migrate as a directory account, not the built-in admin.** A + `[authentication.fallback-admin]` from `config.toml` authenticates + perfectly well right up to the moment the migration finishes, and then + stops existing — v0.16 keeps its configuration in the store, so the block + defining it is never read again. The migration itself still succeeds; what + you lose is the ability to verify it, recalculate quotas, or administer + the server afterwards. See [You need a named admin + account](#you-need-a-named-admin-account-before-you-migrate). + ## Status Roughly 14,600 lines of Go, stdlib only, of which about 6,300 are tests. -Every phase exists as a package, and on **2026-08-25 this tool migrated a -production mail 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. - -That run was preceded by a full dress rehearsal on a clone of the same -server, which is the single practice worth copying from this project: it -found four faults that would each have reached production, three of which -only appear against a real instance. See [Rehearse on a clone -first](#rehearse-on-a-clone-first). +Every phase exists as a package and `run` wires them into the migration +described above. | Command | State | |---|---| @@ -77,33 +104,6 @@ Lines are implementation only; each package carries its tests alongside. | `internal/plan` | 130 | yes | | `internal/config` | stub | — | -## Before you start: two things you must fix on the server - -Neither is something this tool can do for you, and both stop a migration -dead. `preflight` refuses on both, while the mail server is still running — -but they are worth knowing before you book a maintenance window, because -fixing them is a change to your directory, not a flag. - -1. **Remove or collapse multi-tenancy.** v0.16 requires a tenant-scoped - account to sit on a domain owned by that same tenant, for its primary - domain and every alias. v0.15 imposed no such rule, so an install that is - perfectly valid today can be unrepresentable in v0.16. Run - `stalwart-migrate tenants` to see who owns what. Where a domain has no - tenant of its own and only one tenant's accounts use it, the conversion - repairs it for you; where two tenants genuinely share a domain, nothing - can, and you must resolve it in v0.15 first — give each tenant its own - domains, move the accounts into one tenant, or remove the tenants - entirely. - -2. **Migrate as a directory account, not the built-in admin.** A - `[authentication.fallback-admin]` from `config.toml` authenticates - perfectly well right up to the moment the migration finishes, and then - stops existing — v0.16 keeps its configuration in the store, so the block - defining it is never read again. The migration itself still succeeds; what - you lose is the ability to verify it, recalculate quotas, or administer - the server afterwards. See [You need a named admin - account](#you-need-a-named-admin-account-before-you-migrate). - ## Why not a shell script Stalwart's 0.15 → 0.16 boundary is not a drop-in binary swap: settings move,