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()) }) }