Files
stalwart-migrator/cmd/stalwart-migrate/main.go
T
jcoffey-dev a0f846a31b Replace the sandbox dry run with a read-only rehearsal
`run --dry-run` cloned the data directory into a sandbox, migrated the copy,
booted it, and compared content before and after. Running that design
against a real 0.15.5 instance and a real production settings corpus
retired it:

  * The mechanics were never the risk. Backup, dump, convert and the
    recovery-mode store migration all worked essentially first time.
  * Its final comparison cannot work at all. It needs the migrated sandbox
    to answer an API, and server.listener is not among the settings
    migrate_v016.py carries - so a migrated instance has no listeners and
    answers on nothing. That is the true post-migration state, not a
    sandbox artifact to engineer around.
  * The expensive half bought the least: against a 3.6 GB production store
    it copies the data twice, reading a live mail store, to prove RocksDB
    files copy and recovery mode can open them.

Meanwhile the cheap half found every problem that would have derailed a
real migration - an empty defaultHostname v0.16 rejects, passwords v0.16
refuses to create, and a 12,182-key reconstruction worklist - and needs no
data copy at all.

So `stalwart-migrate rehearse`: preflight, dump, convert, report. It copies
nothing, starts no server, and never writes to the store, so it is safe to
run against production repeatedly without a maintenance window. It needs no
target binary either, since convert is pure Python.

The scratch directory is cleaned up as before, with the rehearsal's two
conclusions lifted out first and recorded as artifacts: export.json (what
will carry over) and unmigrated.txt (what will not). Recording an artifact
whose path was about to be deleted was a bug in the first cut of this;
both now resolve.

`run` keeps its refusal and explains where rehearse went. `--dry-run` is
kept as a flag purely to say what replaced it.

Verified against the smoke VM end to end: rehearsal completes read-only in
seconds and reports 3505 unmigrated settings on a default install,
listeners included.
2026-08-23 20:50:40 -07:00

55 lines
1.4 KiB
Go

// SPDX-FileCopyrightText: 2026 LINUXexpert-org
// SPDX-License-Identifier: GPL-3.0-or-later
// Command stalwart-migrate drives an in-place Stalwart Mail Server upgrade
// (0.15.5 -> latest) through preflight checks, a defense-in-depth backup,
// a checkpointed migration, and post-migration validation. Recovery from a
// failed migration is the operator's own snapshot or backup and is out of
// scope for this tool - see ARCHITECTURE.md §4.8.
package main
import (
"fmt"
"os"
)
func main() {
if len(os.Args) < 2 {
usage()
os.Exit(1)
}
var err error
switch os.Args[1] {
case "preflight":
err = runPreflight(os.Args[2:])
case "rehearse":
err = runRehearse(os.Args[2:])
case "run":
err = runRun(os.Args[2:])
case "status":
err = runStatus(os.Args[2:])
case "report":
err = fmt.Errorf("not implemented yet: see internal/validate")
default:
usage()
os.Exit(1)
}
if err != nil {
fmt.Fprintln(os.Stderr, "stalwart-migrate:", err)
os.Exit(1)
}
}
func usage() {
fmt.Fprintln(os.Stderr, `usage: stalwart-migrate <command> [flags]
commands:
preflight run read-only checks and print the migration plan
rehearse convert this instance's settings and report what will NOT carry over (read-only)
run perform the migration (not implemented yet - refuses)
status show the state of an in-progress or completed run
report print the validation report for a run`)
}