From 56f465d8a9a885c38007258d195d32668ffee7f2 Mon Sep 17 00:00:00 2001 From: John Coffey Date: Sat, 22 Aug 2026 21:59:57 -0700 Subject: [PATCH] docs: rewrite the README around what actually works The old README was wrong in both directions. It called the project "architected, not yet implemented" while ~6,000 lines of tested code exist across backup, preflight, checkpointing, validation and recovery; and it listed six subcommands as a flat menu when three of them return "not implemented yet" and a fourth refuses unless given --dry-run. Replaced with a per-command and per-package status table, each entry checked against the binary rather than the design doc: rollback, confirm and report were run to confirm they error out, and the package line counts come from the tree. Records why `run` refuses without --dry-run -- internal/rollback is a doc.go and nothing else, and committing to a migration with no working rollback would break the one guarantee the tool exists to make. That refusal is correct behaviour today, not a defect to be filed. Corrects a claim in my own first draft: preflight is not purely read-only. Its checks against the Stalwart install are, but it records the run as a checkpoint first, so it fails without write access to /var/lib/stalwart-migrator -- a compile-time constant with no flag or env override. Found by running it. --- README.md | 100 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 88 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 60c9b1c..2bc4dab 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,94 @@ # stalwart-migrator -In-place upgrade tool for Stalwart Mail Server: 0.15.5 → latest, with no -data loss, checkpointed rollback at every step, and automated post-migration -validation. +In-place upgrade tool for Stalwart Mail Server, 0.15.5 → latest: no data +loss, a checkpoint at every step so a failure can be undone, and automated +validation that the server still works afterwards. -Design status: architected, not yet implemented — see -[ARCHITECTURE.md](ARCHITECTURE.md) for the full design, the phase-by-phase -plan, and the research it's grounded in. +Go, standard library only — no external dependencies. + +## Status + +Partially implemented. Roughly 6,000 lines of tested code across backup, +preflight, checkpointing, validation and recovery; two packages are still +stubs, and that gap is what gates the rest. + +| Command | State | +|---|---| +| `stalwart-migrate preflight` | **Works** — read-only checks and a migration plan | +| `stalwart-migrate run --dry-run` | **Works** — preflight, real backup, sandboxed trial conversion | +| `stalwart-migrate run` | **Refuses on purpose** — see below | +| `stalwart-migrate status ` | **Works** | +| `stalwart-migrate rollback ` | Not implemented | +| `stalwart-migrate confirm ` | Not implemented | +| `stalwart-migrate report ` | Not implemented | + +**`run` without `--dry-run` deliberately refuses to proceed.** A real cutover +needs `internal/rollback` — currently a `doc.go` and nothing else — plus real +systemd/Docker service control. Committing to a migration with no working +rollback would break the single guarantee the tool exists to make, so it +stops rather than going partway. That refusal is the correct behaviour today, +not a bug. + +Package state: + +| Package | Lines | Tests | +|---|---|---| +| `internal/backup` | 1805 | yes | +| `internal/preflight` | 1324 | yes | +| `internal/validate` | 792 | yes | +| `internal/stalwartapi` | 716 | yes | +| `internal/recovery` | 702 | yes | +| `internal/checkpoint` | 559 | yes | +| `internal/plan` | 196 | yes | +| `internal/rollback` | stub | — | +| `internal/config` | stub | — | + +## Why not a shell script + +Stalwart's 0.15 → 0.16 boundary is not a drop-in binary swap: settings move, +and the data directory has to be migrated rather than merely copied. The +failure mode that matters is a half-migrated mail store with no way back — +which is why backup verification, checkpointing, and rollback are the design +centre rather than conveniences bolted on afterwards. + +[`ARCHITECTURE.md`](ARCHITECTURE.md) covers this in full: §1 on why a thin +wrapper is insufficient, §4 on the migration phases, §5 on the checkpoint +state machine, §6 on the CLI surface, and §8 on what is still open. + +## Build and test + +```sh +go build ./... +go test ./... +``` + +Requires Go 1.26 or newer. + +## Trying it safely + +`preflight` is the sensible starting point — its checks against the Stalwart +installation are read-only: + +```sh +sudo go run ./cmd/stalwart-migrate preflight +``` + +It still needs write access, because it records the run as a checkpoint +before doing anything else: ``` -stalwart-migrate preflight # read-only checks + plan -stalwart-migrate run # execute the migration -stalwart-migrate status -stalwart-migrate rollback -stalwart-migrate confirm -stalwart-migrate report +create run: checkpoint: create run directory: +mkdir /var/lib/stalwart-migrator: permission denied ``` + +That path is `checkpoint.DefaultBaseDir`, a compile-time constant with no +flag or environment override — so preflight needs either root or a +pre-created writable `/var/lib/stalwart-migrator`. (`run` takes `--work-dir` +for its scratch space, but that is a different directory and does not move +the checkpoint store.) + +`run --dry-run` performs a **real backup**, which touches the live data +directory — read the caveat the command prints before using it on anything +you care about. Where the plan crosses the 0.15/0.16 boundary it clones that +verified backup into a disposable sandbox and converts the copy, leaving the +original untouched.