118 lines
2.7 KiB
Go
118 lines
2.7 KiB
Go
package backup
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/go-sql-driver/mysql"
|
|
|
|
"gitocean/internal/model"
|
|
)
|
|
|
|
func Create(cfg model.ServerConfig, file string) error {
|
|
tmp, err := os.MkdirTemp("", "gitocean-backup-*")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer os.RemoveAll(tmp)
|
|
metadata := filepath.Join(tmp, "metadata.sql")
|
|
mysqlArgs, dbName, err := MySQLCLIArgs(cfg.MySQLDSN)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd := exec.Command("mysqldump", append(mysqlArgs, dbName)...)
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
return fmt.Errorf("mysqldump failed: %w", err)
|
|
}
|
|
if err := os.WriteFile(metadata, out, 0600); err != nil {
|
|
return err
|
|
}
|
|
cmd = exec.Command("tar", "-czf", file, "-C", ".", cfg.Storage, "-C", tmp, "metadata.sql")
|
|
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
|
return cmd.Run()
|
|
}
|
|
|
|
func Restore(cfg model.ServerConfig, file string) error {
|
|
tmp, err := os.MkdirTemp("", "gitocean-restore-*")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer os.RemoveAll(tmp)
|
|
cmd := exec.Command("tar", "-xzf", file, "-C", tmp)
|
|
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
mysqlArgs, dbName, err := MySQLCLIArgs(cfg.MySQLDSN)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
metadata := filepath.Join(tmp, "metadata.sql")
|
|
f, err := os.Open(metadata)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
cmd = exec.Command("mysql", append(mysqlArgs, dbName)...)
|
|
cmd.Stdin = f
|
|
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
return CopyDir(filepath.Join(tmp, cfg.Storage), cfg.Storage)
|
|
}
|
|
|
|
func MySQLCLIArgs(dsn string) ([]string, string, error) {
|
|
cfg, err := mysql.ParseDSN(dsn)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
args := []string{}
|
|
if cfg.User != "" {
|
|
args = append(args, "-u"+cfg.User)
|
|
}
|
|
if cfg.Passwd != "" {
|
|
args = append(args, "-p"+cfg.Passwd)
|
|
}
|
|
if cfg.Net == "tcp" && cfg.Addr != "" {
|
|
host, port := cfg.Addr, ""
|
|
if strings.Contains(cfg.Addr, ":") {
|
|
host, port, _ = strings.Cut(cfg.Addr, ":")
|
|
}
|
|
if host != "" {
|
|
args = append(args, "-h"+host)
|
|
}
|
|
if port != "" {
|
|
args = append(args, "-P"+port)
|
|
}
|
|
} else if cfg.Net == "unix" && cfg.Addr != "" {
|
|
args = append(args, "--socket", cfg.Addr)
|
|
}
|
|
return args, cfg.DBName, nil
|
|
}
|
|
|
|
func CopyDir(src, dst string) error {
|
|
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rel, err := filepath.Rel(src, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
to := filepath.Join(dst, rel)
|
|
if info.IsDir() {
|
|
return os.MkdirAll(to, info.Mode())
|
|
}
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(to, b, info.Mode())
|
|
})
|
|
}
|