Files
stalwart-migrator/internal/backup/sqlbackup.go
T
jcoffey-dev 4b0bec8956 Add SPDX headers to every Go file
GPLv3's "How to Apply These Terms" asks for a notice in each source file;
this is the modern two-line SPDX form of it rather than the full paragraph.
82 files, including tests.

The blank line after the header is load-bearing. In Go a comment block
immediately preceding `package X` becomes the package doc comment, so
without the separator the SPDX lines would be absorbed into the doc for the
eleven packages whose doc.go (or main.go) opens with one, and `go doc` would
print them. Verified it doesn't.
2026-08-23 18:03:15 -07:00

95 lines
3.0 KiB
Go

// SPDX-FileCopyrightText: 2026 LINUXexpert-org
// 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
}