Files
backup/config/config.go
2026-02-02 17:30:35 +08:00

264 lines
6.6 KiB
Go

package config
import (
"fmt"
"os"
"os/exec"
"time"
"gopkg.in/yaml.v3"
)
// Config represents the configuration structure
type Config struct {
Name string `yaml:"name"`
Items []Item `yaml:"items"`
Options Options `yaml:"options"`
}
// Item represents a backup item
type Item struct {
Name string `yaml:"name"`
Type string `yaml:"type"` // local, mysql, postgresql
Path string `yaml:"path,omitempty"`
Exclude []string `yaml:"exclude,omitempty"` // Patterns to exclude (for local type)
Host string `yaml:"host,omitempty"`
Port int `yaml:"port,omitempty"`
User string `yaml:"user,omitempty"`
Password string `yaml:"password,omitempty"`
Database string `yaml:"database,omitempty"`
}
// Options represents backup options
type Options struct {
Storage string `yaml:"storage"`
S3 S3Config `yaml:"s3"`
MaxBackups int `yaml:"max_backups"`
BackupInterval string `yaml:"backup_interval"`
}
// S3Config represents S3 storage configuration
type S3Config struct {
Endpoint string `yaml:"endpoint"`
AccessKey string `yaml:"access_key"`
SecretKey string `yaml:"secret_key"`
Bucket string `yaml:"bucket"`
Region string `yaml:"region"`
Path string `yaml:"path"`
Compression string `yaml:"compression"`
Encryption string `yaml:"encryption"`
EncryptionKey string `yaml:"encryption_key"`
EncryptionAlgorithm string `yaml:"encryption_algorithm"`
}
// ParseInterval parses the backup interval string
func (o *Options) ParseInterval() (time.Duration, error) {
switch o.BackupInterval {
case "1h":
return time.Hour, nil
case "6h":
return 6 * time.Hour, nil
case "12h":
return 12 * time.Hour, nil
case "1d":
return 24 * time.Hour, nil
case "7d":
return 7 * 24 * time.Hour, nil
default:
return time.ParseDuration(o.BackupInterval)
}
}
// LoadConfig loads configuration from a YAML file
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}
var config Config
if err := yaml.Unmarshal(data, &config); err != nil {
return nil, fmt.Errorf("failed to parse config file: %w", err)
}
if err := config.Validate(); err != nil {
return nil, fmt.Errorf("invalid config: %w", err)
}
return &config, nil
}
// Validate validates the configuration
func (c *Config) Validate() error {
if c.Name == "" {
return fmt.Errorf("name is required")
}
if len(c.Items) == 0 {
return fmt.Errorf("at least one backup item is required")
}
for i, item := range c.Items {
if item.Name == "" {
return fmt.Errorf("item[%d]: name is required", i)
}
switch item.Type {
case "local":
if item.Path == "" {
return fmt.Errorf("item[%d]: path is required for local type", i)
}
case "mysql", "postgresql", "mariadb":
if item.Host == "" {
return fmt.Errorf("item[%d]: host is required for %s type", i, item.Type)
}
if item.Database == "" {
return fmt.Errorf("item[%d]: database is required for %s type", i, item.Type)
}
default:
return fmt.Errorf("item[%d]: unsupported type %s", i, item.Type)
}
}
if c.Options.Storage != "s3" {
return fmt.Errorf("only s3 storage is supported")
}
if c.Options.S3.Bucket == "" {
return fmt.Errorf("s3 bucket is required")
}
if c.Options.MaxBackups < 1 {
return fmt.Errorf("max_backups must be at least 1")
}
if _, err := c.Options.ParseInterval(); err != nil {
return fmt.Errorf("invalid backup_interval: %w", err)
}
return nil
}
// TestItems tests if all backup items are accessible
func (c *Config) TestItems() error {
for i, item := range c.Items {
switch item.Type {
case "local":
if err := testLocalItem(item); err != nil {
return fmt.Errorf("item[%d] %s: %w", i, item.Name, err)
}
case "mysql":
if err := testMySQLItem(item); err != nil {
return fmt.Errorf("item[%d] %s: %w", i, item.Name, err)
}
case "postgresql":
if err := testPostgreSQLItem(item); err != nil {
return fmt.Errorf("item[%d] %s: %w", i, item.Name, err)
}
case "mariadb":
if err := testMariaDBItem(item); err != nil {
return fmt.Errorf("item[%d] %s: %w", i, item.Name, err)
}
}
}
return nil
}
// testLocalItem tests if a local path exists and is accessible
func testLocalItem(item Item) error {
info, err := os.Stat(item.Path)
if err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("path does not exist: %s", item.Path)
}
return fmt.Errorf("cannot access path: %w", err)
}
if !info.IsDir() {
return fmt.Errorf("path is not a directory: %s", item.Path)
}
return nil
}
// testMySQLItem tests if MySQL database is accessible
func testMySQLItem(item Item) error {
// Build mysql command to test connection
args := []string{
fmt.Sprintf("--host=%s", item.Host),
fmt.Sprintf("--user=%s", item.User),
}
if item.Port > 0 {
args = append(args, fmt.Sprintf("--port=%d", item.Port))
}
if item.Password != "" {
args = append(args, fmt.Sprintf("--password=%s", item.Password))
}
args = append(args, "--execute=SELECT 1", item.Database)
cmd := exec.Command("mysql", args...)
if err := cmd.Run(); err != nil {
return fmt.Errorf("cannot connect to MySQL database: %w", err)
}
return nil
}
// testPostgreSQLItem tests if PostgreSQL database is accessible
func testPostgreSQLItem(item Item) error {
// Build psql command to test connection
args := []string{
fmt.Sprintf("--host=%s", item.Host),
fmt.Sprintf("--username=%s", item.User),
fmt.Sprintf("--dbname=%s", item.Database),
"--command=SELECT 1",
"--no-password",
}
if item.Port > 0 {
args = append(args, fmt.Sprintf("--port=%d", item.Port))
}
cmd := exec.Command("psql", args...)
// Set password as environment variable
if item.Password != "" {
cmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", item.Password))
}
if err := cmd.Run(); err != nil {
return fmt.Errorf("cannot connect to PostgreSQL database: %w", err)
}
return nil
}
// testMariaDBItem tests if MariaDB database is accessible
func testMariaDBItem(item Item) error {
// Build mariadb command to test connection
args := []string{
fmt.Sprintf("--host=%s", item.Host),
fmt.Sprintf("--user=%s", item.User),
}
if item.Port > 0 {
args = append(args, fmt.Sprintf("--port=%d", item.Port))
}
if item.Password != "" {
args = append(args, fmt.Sprintf("--password=%s", item.Password))
}
args = append(args, "--execute=SELECT 1", item.Database)
cmd := exec.Command("mariadb", args...)
if err := cmd.Run(); err != nil {
return fmt.Errorf("cannot connect to MariaDB database: %w", err)
}
return nil
}