119 SPDX-FileCopyrightText headers and the README's licence line. The distinction that matters here: LINUXexpert-org appears in this repository in two completely different roles. As a copyright holder in the SPDX headers, which is what changes, and as the GitHub organisation in the module path and 64 import statements, which does not -- the repository still lives at github.com/LINUXexpert-org/stalwart-migrator, and rewriting that would not be a licence change, it would break the build. Both replacements are anchored to their copyright forms, so an import path cannot match either. Import count is 64 before and after, and go.mod is untouched. LICENSE untouched: the FSF's copyright on the GPL text and the "<name of author>" placeholders are not ours to edit. go vet, go build and go test all clean.
95 lines
3.0 KiB
Go
95 lines
3.0 KiB
Go
// SPDX-FileCopyrightText: 2026 Coffey Labs
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package backup
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
)
|
|
|
|
// criticalTables is the exact table set Stalwart's own v0.16 upgrade guide
|
|
// backs up before migrating (principals/directory, domains, and the other
|
|
// tables its migration script and recovery mode depend on) - a targeted
|
|
// dump, not a full-instance one, matching the guide's own tested restore
|
|
// path and staying fast on large installs. See ARCHITECTURE.md §4.2 and
|
|
// UPGRADING/v0_16.md.
|
|
var criticalTables = []string{"s", "d", "r", "h", "b", "g", "j", "f", "u"}
|
|
|
|
// SQLOptions configures a targeted critical-table dump for an external SQL
|
|
// store backend.
|
|
type SQLOptions struct {
|
|
Host string
|
|
Port string
|
|
Database string
|
|
User string
|
|
Password string
|
|
OutPath string
|
|
}
|
|
|
|
// BuildPgDumpArgs returns pg_dump's argv for the critical-table backup,
|
|
// without the leading "pg_dump" itself.
|
|
func BuildPgDumpArgs(o SQLOptions) []string {
|
|
args := []string{"-U", o.User, "-d", o.Database}
|
|
if o.Host != "" {
|
|
args = append(args, "-h", o.Host)
|
|
}
|
|
if o.Port != "" {
|
|
args = append(args, "-p", o.Port)
|
|
}
|
|
for _, t := range criticalTables {
|
|
args = append(args, "-t", t)
|
|
}
|
|
return append(args, "-f", o.OutPath)
|
|
}
|
|
|
|
// BuildMySQLDumpArgs returns mysqldump's argv for the same critical-table
|
|
// set. mysqldump writes to stdout, so RunMySQLDump redirects it rather than
|
|
// this function taking an output path flag.
|
|
func BuildMySQLDumpArgs(o SQLOptions) []string {
|
|
args := []string{"-u", o.User, o.Database}
|
|
if o.Host != "" {
|
|
args = append(args, "-h", o.Host)
|
|
}
|
|
if o.Port != "" {
|
|
args = append(args, "-P", o.Port)
|
|
}
|
|
return append(args, criticalTables...)
|
|
}
|
|
|
|
// RunPgDump executes pg_dump with the password passed via the standard
|
|
// PGPASSWORD environment variable, never on the command line where it would
|
|
// be visible to anything reading this process's argv (e.g. `ps`).
|
|
func RunPgDump(ctx context.Context, o SQLOptions) error {
|
|
cmd := exec.CommandContext(ctx, "pg_dump", BuildPgDumpArgs(o)...)
|
|
cmd.Env = append(os.Environ(), "PGPASSWORD="+o.Password)
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("backup: pg_dump failed: %w (output: %s)", err, strings.TrimSpace(string(out)))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RunMySQLDump executes mysqldump with the password passed via the standard
|
|
// MYSQL_PWD environment variable, redirecting its stdout to o.OutPath.
|
|
func RunMySQLDump(ctx context.Context, o SQLOptions) error {
|
|
f, err := os.OpenFile(o.OutPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o640)
|
|
if err != nil {
|
|
return fmt.Errorf("backup: create %s: %w", o.OutPath, err)
|
|
}
|
|
defer f.Close()
|
|
|
|
cmd := exec.CommandContext(ctx, "mysqldump", BuildMySQLDumpArgs(o)...)
|
|
cmd.Env = append(os.Environ(), "MYSQL_PWD="+o.Password)
|
|
cmd.Stdout = f
|
|
var stderr strings.Builder
|
|
cmd.Stderr = &stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("backup: mysqldump failed: %w (stderr: %s)", err, strings.TrimSpace(stderr.String()))
|
|
}
|
|
return nil
|
|
}
|