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:
@@ -0,0 +1,67 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// VandelayOptions configures a per-account content export via Stalwart's
|
||||
// own Vandelay import/export tool - the documented, backend-independent
|
||||
// backup mechanism (ARCHITECTURE.md §4.2's belt-and-suspenders layer): each
|
||||
// account's mail, calendars, contacts, Sieve scripts, and identities land in
|
||||
// one self-contained SQLite archive.
|
||||
type VandelayOptions struct {
|
||||
BinaryPath string // defaults to "vandelay"
|
||||
URL string // the source JMAP server
|
||||
AuthBasic string // "user:app-password", per vandelay's own --auth-basic flag
|
||||
OutDir string // one <account>.sqlite file per account goes here
|
||||
}
|
||||
|
||||
// BuildVandelayImportArgs returns vandelay's argv for exporting one
|
||||
// account's content into a self-contained SQLite archive, without the
|
||||
// leading "vandelay" itself. "import" is vandelay's own verb for this - it
|
||||
// names the direction relative to the archive file, not the live server;
|
||||
// see ARCHITECTURE.md §4.2.
|
||||
func BuildVandelayImportArgs(o VandelayOptions, accountName, outFile string) []string {
|
||||
return []string{
|
||||
"import", "jmap",
|
||||
"--url", o.URL,
|
||||
"--auth-basic", o.AuthBasic,
|
||||
"--account-name", accountName,
|
||||
outFile,
|
||||
}
|
||||
}
|
||||
|
||||
// ExportAccount runs one account's Vandelay export and returns the archive
|
||||
// path it wrote.
|
||||
func ExportAccount(ctx context.Context, o VandelayOptions, accountName string) (outFile string, err error) {
|
||||
binary := o.BinaryPath
|
||||
if binary == "" {
|
||||
binary = "vandelay"
|
||||
}
|
||||
outFile = filepath.Join(o.OutDir, accountName+".sqlite")
|
||||
cmd := exec.CommandContext(ctx, binary, BuildVandelayImportArgs(o, accountName, outFile)...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("backup: vandelay export of %s failed: %w (output: %s)", accountName, err, out)
|
||||
}
|
||||
return outFile, nil
|
||||
}
|
||||
|
||||
// ExportAccounts exports every account in accounts, stopping at the first
|
||||
// failure. A partial per-account backup set is reported precisely (how many
|
||||
// succeeded, which one failed and why) rather than silently continuing past
|
||||
// a failure that might indicate a systemic problem - bad credentials, an
|
||||
// unreachable server - rather than a one-off.
|
||||
func ExportAccounts(ctx context.Context, o VandelayOptions, accounts []string) (outFiles []string, err error) {
|
||||
for _, acct := range accounts {
|
||||
f, err := ExportAccount(ctx, o, acct)
|
||||
if err != nil {
|
||||
return outFiles, fmt.Errorf("backup: exported %d/%d accounts before failing: %w", len(outFiles), len(accounts), err)
|
||||
}
|
||||
outFiles = append(outFiles, f)
|
||||
}
|
||||
return outFiles, nil
|
||||
}
|
||||
Reference in New Issue
Block a user