Fix module path and restore cmd/ omitted by gitignore
Rename module github.com/johnellis/stalwart-migrator -> github.com/LINUXexpert-org/stalwart-migrator to match the repository location, so the module resolves under `go get`. The initial commit's .gitignore listed the compiled binary as a bare `stalwart-migrate` pattern, which Git matches at any depth -- so it also excluded the cmd/stalwart-migrate/ source directory, and the initial commit shipped without the CLI entrypoint. Anchor the pattern to the repo root as /stalwart-migrate and add the four missing files. go build, go vet, and go test ./... all pass.
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
||||
# Binaries
|
||||
/bin/
|
||||
/dist/
|
||||
stalwart-migrate
|
||||
/stalwart-migrate
|
||||
*.exe
|
||||
|
||||
# Test / coverage
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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, with rollback
|
||||
// available at every step. See ARCHITECTURE.md for the full design.
|
||||
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 "run":
|
||||
err = runRun(os.Args[2:])
|
||||
case "status":
|
||||
err = runStatus(os.Args[2:])
|
||||
case "rollback":
|
||||
err = fmt.Errorf("not implemented yet: see internal/rollback")
|
||||
case "confirm":
|
||||
err = fmt.Errorf("not implemented yet: see internal/checkpoint")
|
||||
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
|
||||
run --dry-run: simulate and validate against a sandbox (real cutover isn't implemented yet)
|
||||
status show the state of an in-progress or completed run
|
||||
rollback restore the pre-migration backup for a given run
|
||||
confirm close the rollback window for a completed run
|
||||
report print the validation report for a run`)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/checkpoint"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/preflight"
|
||||
)
|
||||
|
||||
func runPreflight(args []string) error {
|
||||
fs := flag.NewFlagSet("preflight", flag.ExitOnError)
|
||||
binaryPath := fs.String("binary", "/usr/local/bin/stalwart", "path to the installed stalwart binary")
|
||||
configPath := fs.String("config", "/etc/stalwart/config.toml", "path to stalwart's config file")
|
||||
dataDir := fs.String("data-dir", "/var/lib/stalwart", "stalwart data directory")
|
||||
containerName := fs.String("container", "stalwart", "docker container name, if applicable")
|
||||
adminURL := fs.String("admin-url", "", "base URL for a JMAP reachability check (optional but recommended)")
|
||||
adminUser := fs.String("admin-user", "", "admin username for the reachability check")
|
||||
adminPassword := fs.String("admin-password", os.Getenv("STALWART_MIGRATE_ADMIN_PASSWORD"),
|
||||
"admin password for the reachability check (or set STALWART_MIGRATE_ADMIN_PASSWORD)")
|
||||
targetVersion := fs.String("target", "latest", `target Stalwart version, or "latest"`)
|
||||
minFree := fs.Float64("min-free-multiple", 2.0, "required free disk space as a multiple of the data directory size")
|
||||
stateDir := fs.String("state-dir", checkpoint.DefaultBaseDir, "directory to store run checkpoints in")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
store := checkpoint.NewStore(*stateDir)
|
||||
rs, err := store.Create("", *targetVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create run: %w", err)
|
||||
}
|
||||
|
||||
checker := preflight.New(preflight.Options{
|
||||
BinaryPath: *binaryPath,
|
||||
ConfigPath: *configPath,
|
||||
DataDir: *dataDir,
|
||||
ContainerName: *containerName,
|
||||
AdminURL: *adminURL,
|
||||
AdminUser: *adminUser,
|
||||
AdminPassword: *adminPassword,
|
||||
TargetVersion: *targetVersion,
|
||||
MinFreeMultiple: *minFree,
|
||||
})
|
||||
|
||||
report, err := checker.Run(context.Background(), store, rs)
|
||||
fmt.Print(report.String())
|
||||
fmt.Printf("\nrun id: %s\n", rs.RunID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("preflight run %s failed to complete: %w", rs.RunID, err)
|
||||
}
|
||||
if report.Blocking() {
|
||||
return fmt.Errorf("preflight found blocking issues for run %s - see FAIL lines above", rs.RunID)
|
||||
}
|
||||
fmt.Println("preflight passed - safe to proceed with `stalwart-migrate run`")
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/backup"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/checkpoint"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/plan"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/preflight"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/recovery"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/validate"
|
||||
)
|
||||
|
||||
// runRun implements `stalwart-migrate run`. Only --dry-run is available
|
||||
// today: a real cutover needs internal/rollback (so a failed migration can
|
||||
// actually be undone) and real systemd/Docker service control, neither of
|
||||
// which exist yet - see ARCHITECTURE.md §8. Committing to a real migration
|
||||
// without a working rollback would violate the one thing this whole tool
|
||||
// exists to guarantee, so `run` without --dry-run refuses rather than doing
|
||||
// it partway.
|
||||
//
|
||||
// --dry-run runs preflight and a real backup (see the caveat printed below
|
||||
// about why backup still touches the live data directory), then - if the
|
||||
// plan crosses the 0.15/0.16 boundary - clones the verified backup into a
|
||||
// disposable sandbox, converts the settings snapshot to point at that
|
||||
// sandbox (via migrate_v016.py's own documented --patch-paths mechanism,
|
||||
// not by this tool guessing at config.json's schema), runs the real
|
||||
// recovery-mode migration against the sandbox, and boots the result
|
||||
// normally to confirm it comes up. Nothing at the real binary path or the
|
||||
// real service is ever touched.
|
||||
//
|
||||
// Every byte a dry run writes - the fs-backup copy, the settings/principals
|
||||
// dumps, the downloaded migrate_v016.py, the sandbox clone and its
|
||||
// config/export files - lives under one per-run directory
|
||||
// (work-dir/<run-id>) that a deferred cleanup at the bottom of this
|
||||
// function removes on every exit path: success, a failed check partway
|
||||
// through, or an early refusal. The only thing left behind afterward is the
|
||||
// checkpoint's state.json under --state-dir, which is exactly the
|
||||
// success/failure log a rerun's `status <run-id>` reads - not bulk data.
|
||||
// --keep-artifacts opts out, for when a failure needs inspecting.
|
||||
func runRun(args []string) (err error) {
|
||||
fs := flag.NewFlagSet("run", flag.ExitOnError)
|
||||
binaryPath := fs.String("binary", "/usr/local/bin/stalwart", "path to the currently-installed stalwart binary")
|
||||
targetBinaryPath := fs.String("target-binary", "", "path to an already-downloaded target-version stalwart binary (required to simulate a major-boundary migration)")
|
||||
configPath := fs.String("config", "/etc/stalwart/config.toml", "path to stalwart's current config file")
|
||||
dataDir := fs.String("data-dir", "/var/lib/stalwart", "stalwart data directory")
|
||||
containerName := fs.String("container", "stalwart", "docker container name, if applicable")
|
||||
adminURL := fs.String("admin-url", "", "base URL for the live instance's admin/JMAP API (required)")
|
||||
adminUser := fs.String("admin-user", "", "admin username")
|
||||
adminPassword := fs.String("admin-password", os.Getenv("STALWART_MIGRATE_ADMIN_PASSWORD"),
|
||||
"admin password (or set STALWART_MIGRATE_ADMIN_PASSWORD)")
|
||||
targetVersion := fs.String("target", "latest", `target Stalwart version, or "latest"`)
|
||||
stateDir := fs.String("state-dir", checkpoint.DefaultBaseDir, "directory to store run checkpoints in")
|
||||
workDir := fs.String("work-dir", "/var/lib/stalwart-migrator/work", "scratch directory for backups, dumps, and the dry-run sandbox (cleaned up afterward - see --keep-artifacts)")
|
||||
stalwartCLI := fs.String("stalwart-cli", "stalwart-cli", "path to the stalwart-cli binary")
|
||||
pythonPath := fs.String("python", "python3", "path to python3")
|
||||
migrationScriptSHA256 := fs.String("migration-script-sha256", "", "pinned sha256 of migrate_v016.py (recommended; see preflight/backup output for the hash to pin after a first unpinned run)")
|
||||
recoveryPort := fs.Int("recovery-port", 8080, "port recovery mode's HTTP listener binds, per UPGRADING/v0_16.md's own examples")
|
||||
minFree := fs.Float64("min-free-multiple", 2.0, "required free disk space as a multiple of the data directory size")
|
||||
dryRun := fs.Bool("dry-run", false, "simulate and validate the migration against a disposable sandbox, without touching production")
|
||||
keepArtifacts := fs.Bool("keep-artifacts", false, "don't delete work-dir/<run-id> afterward (the fs-backup copy, dumps, and sandbox) - useful for inspecting a failure")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !*dryRun {
|
||||
return fmt.Errorf(
|
||||
"real (non-dry-run) migrations aren't available yet: internal/rollback and real systemd/Docker service control " +
|
||||
"aren't implemented, so this tool can't yet guarantee it can undo a failed cutover. Run with --dry-run to " +
|
||||
"validate the migration mechanics against a disposable sandbox copy of your data - nothing in production is touched",
|
||||
)
|
||||
}
|
||||
if *adminURL == "" {
|
||||
return fmt.Errorf("--admin-url is required")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
httpClient := &http.Client{}
|
||||
|
||||
if err := os.MkdirAll(*workDir, 0o750); err != nil {
|
||||
return fmt.Errorf("create work dir %s: %w", *workDir, err)
|
||||
}
|
||||
|
||||
store := checkpoint.NewStore(*stateDir)
|
||||
rs, err := store.Create("", *targetVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create run: %w", err)
|
||||
}
|
||||
fmt.Printf("run id: %s\n\n", rs.RunID)
|
||||
|
||||
runWorkDir := filepath.Join(*workDir, rs.RunID)
|
||||
logPath := filepath.Join(*stateDir, rs.RunID, "state.json")
|
||||
defer func() {
|
||||
if _, statErr := os.Stat(runWorkDir); os.IsNotExist(statErr) {
|
||||
return // nothing was ever written (e.g. refused before backup ran)
|
||||
}
|
||||
if *keepArtifacts {
|
||||
fmt.Printf("\nartifacts kept at %s (--keep-artifacts) - remove manually when done inspecting\n", runWorkDir)
|
||||
return
|
||||
}
|
||||
outcome := "succeeded"
|
||||
if err != nil {
|
||||
outcome = "failed"
|
||||
}
|
||||
if rmErr := os.RemoveAll(runWorkDir); rmErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "\nwarning: dry run %s, but failed to clean up %s: %v (remove it manually)\n", outcome, runWorkDir, rmErr)
|
||||
return
|
||||
}
|
||||
fmt.Printf("\ndry run %s - cleaned up %s; the run log is at %s\n", outcome, runWorkDir, logPath)
|
||||
}()
|
||||
|
||||
fmt.Println("--- preflight ---")
|
||||
checker := preflight.New(preflight.Options{
|
||||
BinaryPath: *binaryPath, ConfigPath: *configPath, DataDir: *dataDir, ContainerName: *containerName,
|
||||
AdminURL: *adminURL, AdminUser: *adminUser, AdminPassword: *adminPassword,
|
||||
TargetVersion: *targetVersion, MinFreeMultiple: *minFree, HTTPClient: httpClient,
|
||||
})
|
||||
pfReport, err := checker.Run(ctx, store, rs)
|
||||
fmt.Print(pfReport.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("preflight failed to complete: %w", err)
|
||||
}
|
||||
if pfReport.Blocking() {
|
||||
return fmt.Errorf("preflight found blocking issues - see FAIL lines above")
|
||||
}
|
||||
|
||||
p, err := plan.Decide(rs.SourceVersion, rs.TargetVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("plan: %w", err)
|
||||
}
|
||||
fmt.Printf("\nplan: %s\n", p.Reason)
|
||||
|
||||
fmt.Println("\n--- backup ---")
|
||||
fmt.Println("(dry-run does not stop the live Stalwart service itself - no service control is implemented yet. " +
|
||||
"For a guaranteed-consistent snapshot, stop stalwart before running this; otherwise the filesystem copy may " +
|
||||
"reflect a live, in-use store. This is unrelated to whether production gets touched - it never does.)")
|
||||
|
||||
backupDir := filepath.Join(runWorkDir, "backup")
|
||||
scriptDest := filepath.Join(runWorkDir, "migrate_v016.py")
|
||||
settingsPath := filepath.Join(runWorkDir, "settings.json")
|
||||
principalsPath := filepath.Join(runWorkDir, "principals.json")
|
||||
|
||||
backupOpts := backup.Options{
|
||||
BinaryPath: *binaryPath,
|
||||
SkipBinaryPreservation: true, // dry-run: never touch the production binary
|
||||
DataDir: *dataDir,
|
||||
BackupDir: backupDir,
|
||||
MigrationScriptSHA256: *migrationScriptSHA256,
|
||||
ScriptDestPath: scriptDest,
|
||||
AdminURL: *adminURL,
|
||||
AdminUser: *adminUser,
|
||||
AdminPassword: *adminPassword,
|
||||
SettingsDumpPath: settingsPath,
|
||||
PrincipalsDumpPath: principalsPath,
|
||||
PythonPath: *pythonPath,
|
||||
HTTPClient: httpClient,
|
||||
}
|
||||
bkReport, err := backup.Run(ctx, store, rs, backupOpts)
|
||||
fmt.Print(bkReport.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("backup failed: %w", err)
|
||||
}
|
||||
|
||||
if !p.CrossesMajorBoundary {
|
||||
fmt.Println("\nthis is a same-boundary patch upgrade: there's no recovery-mode phase to simulate. " +
|
||||
"preflight and backup above are as far as a dry-run goes for this path - a real run would be a binary swap and restart.")
|
||||
return nil
|
||||
}
|
||||
|
||||
if *targetBinaryPath == "" {
|
||||
return fmt.Errorf("--target-binary is required to simulate a major-boundary migration (0.15 -> 0.16 crosses one here)")
|
||||
}
|
||||
|
||||
fmt.Println("\n--- convert (settings -> sandbox config) ---")
|
||||
sandboxDataDir := filepath.Join(runWorkDir, "sandbox-data")
|
||||
sandboxConfigPath := filepath.Join(runWorkDir, "sandbox-config.json")
|
||||
sandboxExportPath := filepath.Join(runWorkDir, "sandbox-export.json")
|
||||
|
||||
if _, err := store.RunStep(rs, checkpoint.PhaseStage, "clone-sandbox-data", func() (checkpoint.StepOutcome, error) {
|
||||
manifest, err := backup.CopyDataDir(backupDir, sandboxDataDir)
|
||||
if err != nil {
|
||||
return checkpoint.StepOutcome{}, err
|
||||
}
|
||||
return checkpoint.StepOutcome{Detail: fmt.Sprintf("cloned the verified backup (%d files) into the sandbox at %s", len(manifest.Files), sandboxDataDir)}, nil
|
||||
}); err != nil {
|
||||
return fmt.Errorf("clone sandbox data: %w", err)
|
||||
}
|
||||
fmt.Printf("cloned verified backup into sandbox: %s\n", sandboxDataDir)
|
||||
|
||||
if _, err := store.RunStep(rs, checkpoint.PhaseStage, "convert-settings", func() (checkpoint.StepOutcome, error) {
|
||||
if err := backup.RunSettingsConvert(ctx, backup.SettingsConvertOptions{
|
||||
PythonPath: *pythonPath, ScriptPath: scriptDest,
|
||||
SettingsPath: settingsPath, PrincipalsPath: principalsPath,
|
||||
ConfigPath: sandboxConfigPath, OutputPath: sandboxExportPath,
|
||||
PatchPaths: map[string]string{*dataDir: sandboxDataDir},
|
||||
}); err != nil {
|
||||
return checkpoint.StepOutcome{}, err
|
||||
}
|
||||
return checkpoint.StepOutcome{Detail: fmt.Sprintf("generated %s and %s, patched to point at the sandbox", sandboxConfigPath, sandboxExportPath)}, nil
|
||||
}); err != nil {
|
||||
return fmt.Errorf("convert settings: %w", err)
|
||||
}
|
||||
fmt.Println("generated sandbox config.json and export.json")
|
||||
|
||||
fmt.Println("\n--- recovery-mode migration (against the sandbox) ---")
|
||||
listenURL := fmt.Sprintf("http://127.0.0.1:%d/", *recoveryPort)
|
||||
recReport, err := recovery.Run(ctx, store, rs, recovery.Options{
|
||||
BinaryPath: *targetBinaryPath, ConfigPath: sandboxConfigPath, ListenURL: listenURL,
|
||||
AdminUser: "admin", ApplyFiles: []string{sandboxExportPath}, CLIBinaryPath: *stalwartCLI,
|
||||
HTTPClient: httpClient,
|
||||
})
|
||||
fmt.Print(recReport.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("recovery-mode migration against the sandbox failed: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("\n--- boot check (normal boot of the migrated sandbox) ---")
|
||||
if rs.PreflightSnapshot != nil {
|
||||
fmt.Println("(comparing against the pre-migration account/mailbox snapshot preflight captured - " +
|
||||
"this is the actual no-data-loss check, not just a reachability probe)")
|
||||
} else {
|
||||
fmt.Println("(no pre-migration snapshot to compare against - preflight couldn't capture one, most likely " +
|
||||
"because --admin-url wasn't set; only reachability is checked)")
|
||||
}
|
||||
valReport, err := validate.Run(ctx, store, rs, validate.BootCheckOptions{
|
||||
BinaryPath: *targetBinaryPath, ConfigPath: sandboxConfigPath, ListenURL: listenURL, HTTPClient: httpClient,
|
||||
ContentIntegrityBefore: rs.PreflightSnapshot,
|
||||
AdminUser: *adminUser,
|
||||
AdminPassword: *adminPassword,
|
||||
})
|
||||
fmt.Print(valReport.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("post-migration validation failed: %w", err)
|
||||
}
|
||||
|
||||
verified := "the migration mechanics succeeded"
|
||||
if rs.PreflightSnapshot != nil {
|
||||
verified = "the migration mechanics succeeded AND every account/mailbox message count matched before vs. after"
|
||||
}
|
||||
fmt.Printf("\nDRY RUN COMPLETE for run %s: %s, against a disposable sandbox copy of your data. Nothing in production was touched.\n", rs.RunID, verified)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/checkpoint"
|
||||
)
|
||||
|
||||
func runStatus(args []string) error {
|
||||
fs := flag.NewFlagSet("status", flag.ExitOnError)
|
||||
stateDir := fs.String("state-dir", checkpoint.DefaultBaseDir, "directory runs are checkpointed in")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
store := checkpoint.NewStore(*stateDir)
|
||||
|
||||
if fs.NArg() == 0 {
|
||||
ids, err := store.List()
|
||||
if err != nil {
|
||||
return fmt.Errorf("list runs: %w", err)
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
fmt.Println("no runs found in", *stateDir)
|
||||
return nil
|
||||
}
|
||||
fmt.Println("runs (newest first):")
|
||||
for _, id := range ids {
|
||||
fmt.Println(" ", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
runID := fs.Arg(0)
|
||||
rs, err := store.Load(runID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load run %s: %w", runID, err)
|
||||
}
|
||||
|
||||
fmt.Printf("run: %s\n", rs.RunID)
|
||||
fmt.Printf("source: %s\n", rs.SourceVersion)
|
||||
fmt.Printf("target: %s\n", rs.TargetVersion)
|
||||
fmt.Printf("topology: deployment=%s store=%s\n", rs.Topology.DeploymentKind, rs.Topology.StoreBackend)
|
||||
fmt.Printf("rollback window closed: %v\n", rs.RollbackWindowClosed)
|
||||
fmt.Println("steps:")
|
||||
for _, step := range rs.Steps {
|
||||
tag := string(step.Status)
|
||||
if step.Verdict != "" {
|
||||
tag = step.Verdict
|
||||
}
|
||||
line := fmt.Sprintf(" [%-4s] %s/%s", tag, step.Phase, step.Name)
|
||||
if step.Detail != "" {
|
||||
line += " - " + step.Detail
|
||||
}
|
||||
if step.Error != "" {
|
||||
line += " (error: " + step.Error + ")"
|
||||
}
|
||||
fmt.Println(line)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
module github.com/johnellis/stalwart-migrator
|
||||
module github.com/LINUXexpert-org/stalwart-migrator
|
||||
|
||||
go 1.26.5
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/johnellis/stalwart-migrator/internal/checkpoint"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/checkpoint"
|
||||
)
|
||||
|
||||
// Options configures a full backup pass. Which fields matter depends on
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/johnellis/stalwart-migrator/internal/checkpoint"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/checkpoint"
|
||||
)
|
||||
|
||||
func TestBackupRunEndToEndAndResume(t *testing.T) {
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/johnellis/stalwart-migrator/internal/checkpoint"
|
||||
"github.com/johnellis/stalwart-migrator/internal/stalwartapi"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/checkpoint"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/stalwartapi"
|
||||
)
|
||||
|
||||
// Options configures a Checker. Every field has a conservative default
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/johnellis/stalwart-migrator/internal/checkpoint"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/checkpoint"
|
||||
)
|
||||
|
||||
// writeFakeBinary creates a shell script that behaves like `stalwart
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/johnellis/stalwart-migrator/internal/checkpoint"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/checkpoint"
|
||||
)
|
||||
|
||||
// Options configures one full recovery-mode migration cycle: starting the
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/johnellis/stalwart-migrator/internal/checkpoint"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/checkpoint"
|
||||
)
|
||||
|
||||
func TestRecoveryRunEndToEndAndResume(t *testing.T) {
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/johnellis/stalwart-migrator/internal/checkpoint"
|
||||
"github.com/johnellis/stalwart-migrator/internal/recovery"
|
||||
"github.com/johnellis/stalwart-migrator/internal/stalwartapi"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/checkpoint"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/recovery"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/stalwartapi"
|
||||
)
|
||||
|
||||
// BootCheckOptions configures a normal (non-recovery-mode) boot of the
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/johnellis/stalwart-migrator/internal/checkpoint"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/checkpoint"
|
||||
)
|
||||
|
||||
func freePort(t *testing.T) int {
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/johnellis/stalwart-migrator/internal/checkpoint"
|
||||
"github.com/johnellis/stalwart-migrator/internal/stalwartapi"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/checkpoint"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/stalwartapi"
|
||||
)
|
||||
|
||||
// MailboxDelta is one mailbox whose message count didn't match between the
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/johnellis/stalwart-migrator/internal/checkpoint"
|
||||
"github.com/johnellis/stalwart-migrator/internal/stalwartapi"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/checkpoint"
|
||||
"github.com/LINUXexpert-org/stalwart-migrator/internal/stalwartapi"
|
||||
)
|
||||
|
||||
// jmapEnvelope mirrors the wire shape stalwartapi.Client.call() parses.
|
||||
|
||||
Reference in New Issue
Block a user