Files
2026-06-25 19:28:04 +01:00

90 lines
2.3 KiB
Go

package backup
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"time"
"godump/config"
"godump/logger"
)
func backupDatabase(cfg config.InstanceConfig, dbName string) (int64, error) {
// Create backup directory for this database
targetDir := filepath.Join(cfg.BackupDir, dbName)
if err := os.MkdirAll(targetDir, 0755); err != nil {
return 0, fmt.Errorf("failed to create backup directory %s: %w", targetDir, err)
}
// Filename format: instancename_dbname_2006-01-02_15-04-05.sql.gz
timestamp := time.Now().Format("2006-01-02_15-04-05")
filename := fmt.Sprintf("%s_%s_%s.sql.gz", cfg.Name, dbName, timestamp)
targetFile := filepath.Join(targetDir, filename)
f, err := os.Create(targetFile)
if err != nil {
return 0, fmt.Errorf("failed to create backup file %s: %w", targetFile, err)
}
defer f.Close()
// Using sh -c allows us to pipe mysqldump output to gzip easily.
// Since we are taking dbName from INFORMATION_SCHEMA, we should still be careful with quotes,
// but mysqldump arguments can be passed via command directly, and we can pipe in go, or use sh.
// Piping in Go is safer.
cmdDump := exec.Command("mysqldump",
fmt.Sprintf("-h%s", cfg.Host),
fmt.Sprintf("-P%d", cfg.Port),
fmt.Sprintf("-u%s", cfg.User),
fmt.Sprintf("-p%s", cfg.Password),
"--single-transaction",
"--routines",
"--triggers",
dbName,
)
cmdGzip := exec.Command("gzip", "-c")
// Create pipe between mysqldump and gzip
dumpOut, err := cmdDump.StdoutPipe()
if err != nil {
return 0, fmt.Errorf("failed to create dump stdout pipe: %w", err)
}
cmdDump.Stderr = os.Stderr // or capture it
cmdGzip.Stdin = dumpOut
cmdGzip.Stdout = f
cmdGzip.Stderr = os.Stderr
if err := cmdDump.Start(); err != nil {
return 0, fmt.Errorf("failed to start mysqldump: %w", err)
}
if err := cmdGzip.Start(); err != nil {
cmdDump.Process.Kill()
return 0, fmt.Errorf("failed to start gzip: %w", err)
}
errGzip := cmdGzip.Wait()
errDump := cmdDump.Wait()
if errDump != nil {
return 0, fmt.Errorf("mysqldump failed: %w", errDump)
}
if errGzip != nil {
return 0, fmt.Errorf("gzip failed: %w", errGzip)
}
// Get file size
stat, err := f.Stat()
if err != nil {
logger.Warn(cfg.Name, "Could not stat file %s to get size: %v", targetFile, err)
return 0, nil
}
return stat.Size(), nil
}