jcoffey-dev f45ef16d98
ci / test (pull_request) Successful in 1m8s
ci / release (pull_request) Skipped
ci: run socket-free jobs on the light label
Both runners carry `light` (host1, and host2 over the wg-hosts link), so
these jobs run on whichever host is free. Jobs that mount the docker socket
keep `runs-on: docker`, which only host1 has.
2026-09-22 06:45:29 -07:00
2026-08-23 18:01:12 -07:00

stalwart-migrator

Latest release License: GPL-3.0-or-later Docs: docs.ihasmail.org

In-place upgrade tool for Stalwart Mail Server, 0.15.5 → 0.16: no data loss, a checkpoint at every step so an interrupted run resumes instead of restarting, and automated validation that the server still works afterwards. Go, standard library only.

A companion to ihasmail, a JMAP-first webmail client for Stalwart. That one is what you read your mail in; this one gets the server underneath it onto a version that speaks the protocol it needs.

Caution

A migration cannot be undone, and this tool does not undo one. Recovery from a failed migration is your own snapshot or backup, taken beforehand and checked. run will not start until you confirm you have one. See Recovery.

Documentation

📘 Upgrade guide Start here. The whole upgrade, step by step, on docs.ihasmail.org: what to fix first, rehearsing, running it, and what to check afterwards
🧪 Rehearsing Running preflight safely, what rehearse reports, rehearsing on a clone
🐳 Docker Container deployments: flags, what cutover carries and refuses, where the config goes
⚠️ Known Stalwart problems Tenants, the admin account, dropped ACME, certificates on mail ports, the extra recovery boot
🛟 Recovery Why recovery is your snapshot, what the tool keeps, never booting recovery mode again
📊 Status Command and package state, validation, field reports
⚙️ Architecture The design: phases, checkpoints, and the reasoning behind them
🔧 Contributing Building, testing, releases, and where the design is written down

Requirements

  • Linux (amd64 or arm64) with Stalwart 0.15.5, as a systemd service or a single Docker container
  • root on the mail server
  • python3, for Stalwart's own migrate_v016.py
  • stalwart-cli 1.0.2 or later, a separate download from the server
  • an administrator account in Stalwart's directory — not the fallback-admin from config.toml, which does not survive the migration

Fix these on the server first

Both stop a migration, and preflight refuses on both:

  1. Tenants that share a domain. v0.16 requires an account in a tenant to use only that tenant's domains. stalwart-migrate tenants shows who owns what.
  2. A config-file admin. Migrate as a directory account with the admin role, whose local part is unique and whose rights don't come only from tenant-admin.

Details: Known Stalwart problems.

Install

ARCH=amd64   # or arm64
curl -fsSLO https://github.com/Coffey-Labs/stalwart-migrator/releases/latest/download/stalwart-migrate-linux-$ARCH.tar.gz
curl -fsSLO https://github.com/Coffey-Labs/stalwart-migrator/releases/latest/download/SHA256SUMS
sha256sum --ignore-missing -c SHA256SUMS
tar -xzf stalwart-migrate-linux-$ARCH.tar.gz
sudo install -m 0755 stalwart-migrate /usr/local/bin/
stalwart-migrate version

Or build from source with Go 1.26.8 or newer: go build -o stalwart-migrate ./cmd/stalwart-migrate.

Use

Give the admin password with --admin-password or STALWART_MIGRATE_ADMIN_PASSWORD.

# 1. Read-only checks and a migration plan
sudo stalwart-migrate preflight --admin-url https://mail.example.com --admin-user [email protected]

# 2. Read-only: convert your settings and report what won't carry over
sudo stalwart-migrate rehearse --admin-url https://mail.example.com --admin-user [email protected]

# 3. Rehearse the real migration on a clone of the server (strongly recommended)

# 4. Migrate, once you have a snapshot you have checked you can restore
sudo stalwart-migrate run --admin-url https://mail.example.com --admin-user [email protected] \
    --recovery-point-confirmed --yes

# Afterwards
sudo stalwart-migrate status <run-id>     # which steps completed
sudo stalwart-migrate report <run-id>     # what validation found

A Docker container also needs --container-path-unproven and --target-image; see Docker deployments. After the migration, check the certificate on ports 993 and 465, and recreate your ACME provider — Stalwart's converter drops it without saying so.

Status

Every command works: preflight, rehearse, run, tenants, status and report. It has migrated a production server (0.15.5 → 0.16.19, 8 seconds of downtime) and, in another operator's hands, three more. Two things are still open: some store migrations need one more recovery-mode boot, which is a manual step, and the Docker path has never completed a migration against a real Stalwart image. Details and field reports: Status.

License

Copyright (C) 2026 Coffey Labs. GPL-3.0-or-later: free software, with no warranty. The full text is in LICENSE.

No third-party code is vendored — the tool is standard library only, and the migrate_v016.py it downloads at runtime is Stalwart's own script, fetched rather than redistributed.

S
Description
Imported from github.com during the 2026-09-20 standup (local dir: stalwart-migrator)
Readme GPL-3.0
547 KiB
v2026.9.15
Latest
2026-09-21 05:10:39 +00:00
Languages
Go 99.8%
Shell 0.2%