The runbook said nothing in it had been rehearsed. Now the sequence has
been, on data made up for the purpose: upstream 0.16.22 in a container as
the install running today, the fork beside it, both unprivileged with
CAP_NET_BIND_SERVICE. It rehearses the sequence, not the data, which is
what the compat tests are for. 27 of 27 checks passed and the rollback
took 1.5 seconds.
The question §"Open" asked about carrying a store back is answered, and
the answer is no. Upstream refuses to start on a store the fork has
opened: "Column families not opened: _". The fork adds one RocksDB column
family for masked email (SUBSPACE_INBUXA = b'_') and opens with
create_missing_column_families, so it creates it on first open; upstream
has no descriptor for it and RocksDB will not open a database holding one
it was not told about.
That makes step 3's "a copy, not a move" load-bearing in a way the step
did not say. One open by the fork is enough: pointing it at the original
even once, to check something, leaves the Enterprise install unable to
start, and there is no rollback after that. It fails loudly and before
reading anything, which is the good version of this failure, but it is
not recoverable.
Two things the rehearsal found that would have wasted time on the day:
memberTenantId does not come down from the domain and is refused on
create, so a tenant "admin" set up the obvious way is a server
administrator and the check passes while proving nothing; and IMAP's
INBOX is not JMAP's account, because mail from an unauthenticated sender
is filed as spam, so the two counts differ before and after alike.
What the rehearsal does not cover is in its README and in §"Open":
systemd and `systemctl disable stalwart` above all, ACME renewal, load,
the front ends, and INBUXA's own data.
Asked for by John, 2026-09-19. Public ihasmail is Stalwart-facing and knows
nothing about INBUXA, so running an unmodified one against the migrated
server checks something the fork's own suites cannot: that a client written
for upstream still works.
Each difference it finds is one of two things, and the point is to say
which: a regression against upstream's contract, which the fork's tests
would not catch because they test the fork; or a feature that now expects
INBUXA's own front ends, which belongs in the contract and the release
notes rather than in a user's surprise.
After mail is flowing, not as a gate. It informs the contract; it doesn't
block a cutover.
INBUXA's cutover is the first run of something other operators will want:
an existing Stalwart server becoming an INBUXA one with nothing re-entered
and nothing re-issued. Accounts, passwords, app passwords, OAuth sessions,
aliases, tenants, DNS records and provider settings, certificates and ACME
state, Sieve scripts, the queue and the mail all live in the store, so a
migration that copies the store carries them.
Offered beside the fresh-install workflow, which has different questions to
ask, so §6.1 now names both.
It rolls back, which is where it parts company with stalwart-migrator:
that one upgrades in place and says outright it cannot undo a migration.
This one never writes to what it migrates from, so going back is stopping
one service and starting another. Rollback is automatic when verification
fails, available on demand while the old install stands, honest about the
mail that stays behind, and never points the old server at the store the
fork has written.
Every phase is timed, and the number to advertise is the downtime, phases
2 to 7, not the total that preflight and the copy dominate. The report
writes both as JSON so a release note quotes something measured.
John's plan, 2026-09-19: stop the Enterprise server and the ihasmail
container, install the fork at its own path, copy the data across, bring up
INBUXA Admin and the new webmail, and every account carries on.
That is a better shape than the in-place swap this draft assumed, because
the rollback becomes a service swap rather than a restore: the old install
and its data are untouched, so going back is stopping one unit and starting
another. What it costs is whatever the fork accepted in between, since the
two stores diverge the moment the fork starts.
It buys one failure the in-place swap couldn't produce: both servers on one
set of ports, each with its own store, if a reboot brings the old unit back.
So the unit is disabled, not just stopped.
Also written down: the front ends' OAuth clients travel inside the store, so
a front end that keeps its client id and redirect URIs keeps working and one
deployed fresh needs them set up, which fails looking like an account
problem when it isn't.
/etc/ufw/user.rules is world-readable, so this needed no privilege after
all. The default input policy is DROP and 8899 is not among the allowed
ports, so pebble's connection to the suite's listener is dropped. That
matches the live run, where twelve probes from a container completed no
handshake while the same openssl reached pebble's own TLS port.
The nc readings that pointed the other way — instant refusals on closed
ports, where a DROP should hang — are still unexplained, and are left on
the page as unexplained rather than quietly dropped, since they are what
sent an earlier pass through this page in the wrong direction.
Nobody has added the allow rule and re-run the suite, so the fix is
written down as a prediction. The cutover draft says the same: the test
failing here is not evidence that renewal works on the host.
Steps 1 to 3 of SPEC §7 are met, so the remaining one is running the fork
as the mail server. The draft covers the sequence on the host, what to
check before letting mail flow, and what to watch in the first week.
Two things it refuses to gloss: rolling back stops being a snapshot restore
the moment the fork accepts a message, because nobody has tested whether
the Enterprise build reads a store the fork has written; and certificate
renewal is the failure that arrives 90 days late and quietly, on the one
path the suites couldn't settle.
Nothing in it has been rehearsed. The rehearsal on a copy is step 2 of
"Before the day", and it is what turns "the data opens" into "the server
runs on it".