cutover.md is the reasoning and is too long to read at 2am. This is the same sequence as commands, for this install: eight mailboxes, two people and a printer. That scale settles three things the general plan leaves open. The store is small enough that the two-pass rsync buys nothing, so it is one cp inside the window and a simpler sequence when it matters. "Every account still works" is two sign-ins. And a reboot inside the window is affordable, which is the only honest proof that the fork comes up on boot and the old unit does not — is-enabled says what is configured, a reboot says what happens. The rollback leads with chattr -i, because step 7's guard stops the Enterprise build exactly as it stops the fork, and finding that out during a rollback costs the worst ten minutes of the night. And it names the printer as its own check. It is the one user that cannot report a fault: a hardcoded credential and an old TLS stack, of the kind a stricter default quietly refuses. The people will phone; the printer will just stop, and nobody will notice for a fortnight.
7.2 KiB
The run
Status: 2026-09-19. The sheet for INBUXA's own cutover, to be followed at the terminal.
cutover.md is why; this is what to type. Where they disagree, this page is
wrong — it is the general plan cut down to one small install, and anything
surprising belongs back there.
This install
Eight mailboxes, two people and a printer (John, 2026-09-19). That scale decides three things the general plan leaves open:
- The store is small, so the copy is seconds.
cutover.mdstep 3's two-pass rsync exists for a store large enough that copying it dominates the window. Here it is complexity with nothing to buy, and a simpler sequence at 2am is worth more. Onecp -ainside the window. - "Every account still works" is a five-minute check, not a programme. Two people sign in; the printer sends one scan.
- A reboot inside the window is affordable, which is the only honest way to prove the old unit stays down. See step 9.
Fill these in before starting, rather than looking them up mid-window:
| Fork prefix | ______ (its config and unit; not under /opt/stalwart) |
| Fork store | /var/lib/inbuxa |
| Old webmail container | ______ |
| Administrator | ______ |
| Printer account, port, auth | ______ |
Before the window — nothing stopped, no rush
-
The artifact exists and says the right thing.
inbuxa --version # 2026.9.18 (Stalwart 0.16.22)Built
--release, on this machine's glibc or in the image that will run it. A debug binary is not what serves this. -
The service user exists, because the copy is chowned to it in step 5 and the unit runs as it.
id inbuxa || useradd -r -s /usr/sbin/nologin inbuxa -
The config and unit are written and point at the fork's own paths. Nothing either of them names may be inside
/opt/stalwart: it is a reference from here on and will eventually go (cutover.md, "Retiring the old install"). The unit keepsAmbientCapabilities=CAP_NET_BIND_SERVICE, which is what lets an unprivileged process hold port 25. -
Write down what has to be true afterwards, while the old server can still be asked: each mailbox and its message count, the two people's addresses, the aliases, and what the printer sends as.
The window
-
Stop the old server, and make sure it cannot come back.
systemctl stop stalwart systemctl disable stalwart systemctl mask stalwart # stronger: cannot start even as a dependency systemctl is-enabled stalwart # expect: masked docker stop <old-webmail>Wait for the process to have actually exited, not just for
stopto return. -
Copy the store and the config, and hand them to the user the fork runs as.
cp -a /opt/stalwart/data /var/lib/inbuxa chown -R inbuxa:inbuxa /var/lib/inbuxaA copy, never a move.
cp -apreserves ownership, so the chown is not optional — without it the fork cannot write its own store. -
Make the original unwritable, before the fork is started.
chattr +i /opt/stalwart/dataUntil step 5 the old server's own RocksDB lock was the only thing stopping anything else opening that store, and stopping it took that away. One open by the fork adds its column family and the Enterprise build never starts on it again — the rollback would be gone, with no way back (
cutover.md, "Answered"). This is the line that protects it. -
Start the fork, and read the log before anything else.
systemctl enable --now inbuxa journalctl -u inbuxa -n 50 --no-pagerExpect deprecation warnings naming any
STALWART_*settings to rename, and a note thatINBUXA_RECOVERY_ADMINis ignored on a configured server — both are normal. Anything at ERROR is not. -
Reboot the host.
The only honest proof that the fork comes up on boot and the old unit does not. It costs a few minutes here and nothing else does the job:
is-enabledsays what is configured, a reboot says what happens.reboot # then systemctl is-active inbuxa # active systemctl is-enabled stalwart # masked
Before letting mail flow
-
Work down this list. It is short because the install is.
inbuxa --versionnames the right base.- Every listener is up, and the certificate served on 443 and 993 is the real one, not a self-signed fallback.
- Both people sign in, with the passwords they already had, over IMAP and through the front ends. Nothing in this move touches credentials — they are in the store that was copied — so a failure here is the copy or the front ends, not the account.
- Each mailbox holds what step 4 recorded.
- A message from outside arrives. A message from inside leaves.
- Aliases still deliver.
- The queue is draining and the task queue has no new permanent failures.
- Nothing is listening on the old ports but the fork.
-
The printer. Send a scan through it.
It is the one user that cannot tell you it is broken. Scan-to-email boxes carry a hardcoded credential, an old TLS stack, and often plain auth on 25 or 587 without STARTTLS — the kind of client a stricter default quietly starts refusing. The two humans will phone; the printer will simply stop, and it will be a fortnight before anyone asks where their document went.
Do this before calling the cutover done, not after.
Rollback
Minutes, and no restore, because the old install and its store are untouched.
systemctl disable --now inbuxa
chattr -i /opt/stalwart/data # FIRST — step 7 blocks the old build too
systemctl unmask stalwart
systemctl enable --now stalwart
docker start <old-webmail>
The chattr -i comes first. The guard of step 7 stops the Enterprise
build opening that store exactly as it stops the fork; skipping it fails the
rollback with what looks like a permissions problem, at the worst possible
moment.
What it costs is everything the fork accepted while it served — mail delivered to the fork's store is not in the old one. That is free between step 8 and step 10, and stops being free the moment mail flows. Decide early and deliberately.
What it never is: pointing the old build at the fork's store. That is not a recovery path and cannot be made into one.
Afterwards
- Do not delete
/opt/stalwart. It stays, shut down, masked and read-only, until it is deliberately retired (cutover.md). It costs a stopped unit and some disk. - Watch for the first certificate renewal. The one failure that says nothing for ninety days and then takes the service down. Forcing a renewal answers it in a day instead.
- Point a stock ihasmail at the fork and note what it can no longer do,
each difference being either a regression against upstream's contract or a
feature that now expects INBUXA's own front ends (
cutover.md). - Time every phase and keep the numbers. This run is the first run of
the migration tool (
migration.md), and what it measures here is what that tool will advertise. A small server's migration should be minutes; the figure published should be one measured here, against a stated store size and account count.