`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.
60 lines
2.8 KiB
Go
60 lines
2.8 KiB
Go
// SPDX-FileCopyrightText: 2026 LINUXexpert-org
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/LINUXexpert-org/stalwart-migrator/internal/checkpoint"
|
|
)
|
|
|
|
// runRun implements `stalwart-migrate run`, which refuses.
|
|
//
|
|
// It refuses because §4.3 staging and the pipeline that would drive
|
|
// preflight -> backup -> stage -> recovery-mode -> cutover -> validate
|
|
// against real paths don't exist. The phases themselves mostly do:
|
|
// internal/preflight, internal/backup, internal/recovery and
|
|
// internal/cutover are all implemented, and preflight, backup, the settings
|
|
// dump, convert and the recovery-mode store migration have been exercised
|
|
// against a real Stalwart 0.15.5. Cutover has not - it has never run
|
|
// outside its own tests, and it is the phase that mutates production.
|
|
//
|
|
// What used to live here was `--dry-run`, which cloned the store into a
|
|
// sandbox and migrated the copy. That is now `stalwart-migrate rehearse`,
|
|
// minus the cloning: see ARCHITECTURE.md §4.9 for why the expensive half
|
|
// was dropped rather than fixed.
|
|
func runRun(args []string) error {
|
|
fs := flag.NewFlagSet("run", flag.ExitOnError)
|
|
fs.String("binary", "/usr/local/bin/stalwart", "path to the currently-installed stalwart binary")
|
|
fs.String("config", "/etc/stalwart/config.toml", "path to stalwart's current config file")
|
|
fs.String("data-dir", "/var/lib/stalwart", "stalwart data directory")
|
|
fs.String("target", "latest", `target Stalwart version, or "latest"`)
|
|
fs.String("state-dir", checkpoint.DefaultBaseDir, "directory to store run checkpoints in")
|
|
dryRun := fs.Bool("dry-run", false, "removed - see `stalwart-migrate rehearse`")
|
|
if err := fs.Parse(args); err != nil {
|
|
return err
|
|
}
|
|
|
|
if *dryRun {
|
|
return fmt.Errorf("--dry-run has been replaced by `stalwart-migrate rehearse`, which converts this " +
|
|
"instance's settings and reports what will and won't carry over. It no longer clones the data " +
|
|
"directory: that only proved the store opens, and cost a full copy of it to find out " +
|
|
"(ARCHITECTURE.md §4.9)")
|
|
}
|
|
|
|
fmt.Fprintln(os.Stderr,
|
|
"real migrations aren't available yet: the staging phase (ARCHITECTURE.md §4.3) and the pipeline that\n"+
|
|
"would drive preflight -> backup -> stage -> recovery-mode -> cutover -> validate don't exist, so this\n"+
|
|
"command has no path that touches production.\n\n"+
|
|
"Two things worth knowing while you wait:\n"+
|
|
" * `stalwart-migrate rehearse` converts your settings and reports what will NOT carry over. Measured\n"+
|
|
" against a production instance that was 98% of them, listeners included - so it decides your\n"+
|
|
" migration plan, and it's safe to run now.\n"+
|
|
" * Recovery from a failed migration is your own snapshot or backup. This tool does not undo a\n"+
|
|
" migration (§4.8).")
|
|
return fmt.Errorf("`run` is not implemented")
|
|
}
|