Initial commit: stalwart-migrator design and scaffolding

In-place upgrade tool for Stalwart Mail Server (0.15.5 -> latest) with
checkpointed rollback and post-migration validation. Design stage; see
ARCHITECTURE.md.
This commit is contained in:
2026-08-22 18:17:17 -07:00
commit 719a945d64
71 changed files with 6677 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
package backup
import (
"context"
"fmt"
"os/exec"
)
// FDBOptions configures a FoundationDB backup via fdbbackup, FDB's own
// backup CLI (not something Stalwart-specific) - see ARCHITECTURE.md §4.2.
type FDBOptions struct {
ClusterFile string // -C; empty uses fdbbackup's own default cluster file
Destination string // -d, a backup URL e.g. "file:///var/backups/stalwart-fdb"
Tag string // -t; defaults to "default" if empty
}
func (o FDBOptions) tag() string {
if o.Tag == "" {
return "default"
}
return o.Tag
}
// BuildFDBBackupStartArgs returns the fdbbackup argv for starting a backup,
// without the leading "fdbbackup".
func BuildFDBBackupStartArgs(o FDBOptions) []string {
args := []string{"start"}
if o.ClusterFile != "" {
args = append(args, "-C", o.ClusterFile)
}
args = append(args, "-d", o.Destination, "-t", o.tag())
return args
}
// StartFDBBackup kicks off an fdbbackup run. fdbbackup start returns as soon
// as the backup job is registered, not when it finishes - callers that need
// to know it's done should poll FDBBackupStatus and look for their own
// definition of "complete" in its output, since this wrapper doesn't parse
// fdbbackup's status text (that format isn't stable enough here to depend
// on without verifying it against the fdbbackup version actually in use).
func StartFDBBackup(ctx context.Context, o FDBOptions) error {
cmd := exec.CommandContext(ctx, "fdbbackup", BuildFDBBackupStartArgs(o)...)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("backup: fdbbackup start failed: %w (output: %s)", err, out)
}
return nil
}
// FDBBackupStatus returns fdbbackup's raw status output for the given tag,
// for a human or a caller-supplied parser to interpret.
func FDBBackupStatus(ctx context.Context, o FDBOptions) (string, error) {
args := []string{"status", "-t", o.tag()}
if o.ClusterFile != "" {
args = append(args, "-C", o.ClusterFile)
}
cmd := exec.CommandContext(ctx, "fdbbackup", args...)
out, err := cmd.CombinedOutput()
if err != nil {
return string(out), fmt.Errorf("backup: fdbbackup status failed: %w (output: %s)", err, out)
}
return string(out), nil
}