Cutover: the fork must not live on /opt/stalwart, which is to be removed

John, 2026-09-19: the fork takes a copy of the config rather than pointing
at the old one, and /opt/stalwart goes away once the migration is
confirmed.

That turns step 4's store path from a free choice into a constraint. The
step said an existing install keeps whatever its configuration names, which
is true of the server and no longer true of this migration: nothing the
fork runs on may sit under a directory that is going to be deleted. Worth
checking before starting rather than after removing.

Removing it strands nothing else. ACME account keys and issued certificates
are written to the registry, inside the store, so they came across with the
copy; /opt/stalwart holds the old binary, its config and its data and
nothing the fork reads.

What it does end is the rollback, so the new section says when. The
rollback stops being one within hours anyway — after mail has flowed,
going back means losing what arrived since — so the real question is how
long to keep a cold copy of the pre-cutover state. The gate is the first
certificate renewal, which is the one check in "The first week" whose
failure would send anyone back; forcing a renewal closes it in a day
rather than ninety. Archive the store off-host before removing the
directory.
This commit is contained in:
2026-09-19 22:36:51 -07:00
parent a6c7152c52
commit bfda639d73
+46 -6
View File
@@ -96,12 +96,17 @@ unprivileged process hold port 25.
original refuses the fork harmlessly and still starts under the
Enterprise build — measured, `tools/fork/cutover-rehearsal/probe_guard.py`.
Undo it only if you are rolling back.
4. Install the fork at its own path with its own config, pointing at the
copied store. Most settings travel inside the store — they live in the
registry — so the config file is mainly the store path and the hostname.
New installs default to `/var/lib/inbuxa`; an existing install keeps
whatever its configuration names (SPEC.md §2.5), so this is a free
choice as long as the config and the unit agree.
4. Install the fork at its own path, with **its own copy** of the config —
not a reference to the old one. Most settings travel inside the store —
they live in the registry — so the config file is mainly the store path
and the hostname.
The store path is **not** a free choice, because `/opt/stalwart` is to be
removed once the migration is confirmed ("Retiring the old install",
below). Nothing the fork runs on may live under it: the store goes to the
fork's own path (`/var/lib/inbuxa` for a new install), the config beside
it, and neither may name a path inside `/opt/stalwart`. Check that before
starting, not after deleting.
5. Start the fork. Read the log before opening the ports.
6. Bring up INBUXA Admin and the new webmail. Both talk to the server over
JMAP and OAuth, and neither runs on the mail host's web interface
@@ -156,6 +161,41 @@ never been checked, and checking it is worth an hour beforehand rather than
an argument at 2am. Until it is checked, treat the fork's store as
forward-only.
## Retiring the old install
`/opt/stalwart` goes away once the migration is confirmed (John,
2026-09-19). It holds the old binary, its config and its data, and nothing
else the fork needs: ACME account keys and issued certificates live in the
registry, inside the store, so they came across with the copy
(`crates/common/src/network/acme/`). Removing the directory strands nothing.
What it does end is the rollback, permanently, so it is worth being exact
about when.
**The rollback stops being a rollback long before this.** From the moment
mail flows, going back to the old store means losing everything that
arrived since. After a day, `/opt/stalwart` is not something anyone would
actually switch to — it is a cold copy of the pre-cutover state. The
decision is therefore not "when do we stop being able to roll back", which
answers itself within hours, but "when do we stop keeping a copy of what
was there before".
**The gate.** Keep it until the fork has renewed a certificate. That is the
one thing in "The first week" that fails quietly and late, and the only
check whose failure would send anyone back. Waiting for it naturally means
waiting most of 90 days; forcing a renewal instead closes the gate in a
day, and is worth doing for that reason alone.
**Archive before removing.** A compressed copy of the pre-cutover store,
kept off the host, costs little and answers "what did this look like
before" for as long as anyone asks. Deleting the directory is fine; being
left with no copy of the pre-migration state is a different thing, and not
what is being asked for here.
Only then: remove the unit file, `/opt/stalwart`, and the old ihasmail
container and image. Until then the unit stays installed and disabled, and
the store stays read-only (step 3).
## After: stock ihasmail against the fork
Once the migration is done, point an **unmodified public ihasmail**