Keep the four files a run cannot produce again
The settings and principals dumps, the apply plan and its supplement lived only in --work-dir, which a successful run deletes. All four are irreplaceable once the store has been migrated: the dumps can only be taken from a live pre-migration instance, and the plan is what was actually replayed. `rehearse` already kept the plan and the supplement, so the read-only command preserved more of its conclusions than the destructive one did. They are now copied into the run's state directory before the store is touched, recorded as artifacts with checksums, and kept whether or not the run succeeded and whether or not --keep-artifacts was passed. README claimed the dumps stayed on disk; now they do. What made this concrete: an operator who booted recovery mode again after a completed migration, for an unrelated reason, and found Domain and Account queries coming back empty on the next start — twice, on two different servers, and verified as genuinely gone rather than a stale read. Re-applying that run's export.json and supplement.json against a fresh recovery boot is what got the server back both times, and they had those files only because they had thought to pass --keep-artifacts. Nobody should have to guess that in advance. The README now says not to boot recovery mode after a migration. That is Stalwart's behaviour rather than this tool's, but this tool is where an operator learns the technique, and it said nothing about it being a one-time step. Reported by @kaya-eu in #1.
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/applyplan"
|
||||
@@ -74,7 +75,9 @@ func runRun(args []string) (err error) {
|
||||
binarySHA := fs.String("target-binary-sha256", "", "pinned sha256 of the target release archive")
|
||||
minFree := fs.Float64("min-free-multiple", 2.0, "required free disk space as a multiple of the data directory size")
|
||||
recalcQuotas := fs.Bool("recalculate-quotas", true, "schedule the post-migration quota rebuild")
|
||||
keepArtifacts := fs.Bool("keep-artifacts", false, "don't delete work-dir/<run-id> afterward")
|
||||
keepArtifacts := fs.Bool("keep-artifacts", false, "don't delete work-dir/<run-id> afterward. The run's own inputs - the "+
|
||||
"settings and principals dumps, the apply plan and its supplement - are kept in state-dir/<run-id> either way; this "+
|
||||
"additionally keeps the scratch files around them")
|
||||
resume := fs.String("resume", "", "resume an interrupted run by id instead of starting a new one (see `status` for ids)")
|
||||
yes := fs.Bool("yes", false, "actually perform the migration")
|
||||
containerUnproven := fs.Bool("container-path-unproven", false,
|
||||
@@ -158,7 +161,12 @@ func runRun(args []string) (err error) {
|
||||
}
|
||||
if rmErr := os.RemoveAll(runWorkDir); rmErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "warning: couldn't clean up %s: %v\n", runWorkDir, rmErr)
|
||||
return
|
||||
}
|
||||
// What is deleted here is scratch. The run's inputs were copied to
|
||||
// the state directory before the store was touched, because they
|
||||
// cannot be produced again once it has been.
|
||||
fmt.Printf("\ncleaned up %s; the run's dumps and apply plan are kept in %s\n", runWorkDir, runStateDir)
|
||||
}()
|
||||
|
||||
fmt.Println("\n--- preflight ---")
|
||||
@@ -416,6 +424,22 @@ func runRun(args []string) (err error) {
|
||||
applyFiles = append(applyFiles, supplementPath)
|
||||
}
|
||||
|
||||
if _, err := store.RunStep(rs, checkpoint.PhaseBackup, "preserve-plan", func() (checkpoint.StepOutcome, error) {
|
||||
kept, err := preservePlan(runStateDir, map[string]string{
|
||||
"settings-dump": settingsPath,
|
||||
"principals-dump": principalsPath,
|
||||
"converted-export": convertedExport,
|
||||
"supplement": supplementPath,
|
||||
}, rs)
|
||||
if err != nil {
|
||||
return checkpoint.StepOutcome{}, err
|
||||
}
|
||||
return checkpoint.StepOutcome{Detail: fmt.Sprintf("kept %s in %s", strings.Join(kept, ", "), runStateDir)}, nil
|
||||
}); err != nil {
|
||||
return fmt.Errorf("preserve the run's plan: %w", err)
|
||||
}
|
||||
fmt.Println(rs.Outcome(checkpoint.PhaseBackup, "preserve-plan").Detail)
|
||||
|
||||
fmt.Println("\n--- recovery-mode migration (the store is migrated IN PLACE) ---")
|
||||
recOpts := recovery.Options{
|
||||
BinaryPath: staged, ConfigPath: convertedConfig,
|
||||
|
||||
Reference in New Issue
Block a user