- go.mod: 将非法版本号 go 1.26.4 改为 go 1.26 - engine: 压缩失败时删除残留的半截 tar.gz 文件 - config: 支持仅按天数保留(count 和 days 均未设时才用默认 count=7) - engine: docker compose stop 失败时也尝试重启,避免容器停摆 - list: 仅显示真正的备份归档文件(新增导出 IsBackupArchive) - systemd: 移除 PrivateTmp,暂存目录改到安装目录内,避免私有 /tmp 被大目录撑爆
145 lines
4.2 KiB
Go
145 lines
4.2 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// Config is the top-level configuration.
|
|
type Config struct {
|
|
Global GlobalConfig `mapstructure:"global"`
|
|
Projects []ProjectConfig `mapstructure:"projects"`
|
|
Remote *RemoteConfig `mapstructure:"remote"`
|
|
}
|
|
|
|
// GlobalConfig holds global settings.
|
|
type GlobalConfig struct {
|
|
BackupDir string `mapstructure:"backup_dir"`
|
|
TempDir string `mapstructure:"temp_dir"`
|
|
}
|
|
|
|
// ProjectConfig defines one Docker Compose project to back up.
|
|
type ProjectConfig struct {
|
|
Name string `mapstructure:"name"`
|
|
Path string `mapstructure:"path"`
|
|
ComposeFile string `mapstructure:"compose_file"`
|
|
Cron string `mapstructure:"cron"`
|
|
Exclude []string `mapstructure:"exclude"`
|
|
Retention RetentionConfig `mapstructure:"retention"`
|
|
}
|
|
|
|
// RetentionConfig controls backup cleanup policy.
|
|
type RetentionConfig struct {
|
|
Count int `mapstructure:"count"`
|
|
Days int `mapstructure:"days"`
|
|
}
|
|
|
|
// RemoteConfig holds optional remote upload targets.
|
|
type RemoteConfig struct {
|
|
S3 *S3Config `mapstructure:"s3"`
|
|
WebDAV *WebDAVConfig `mapstructure:"webdav"`
|
|
}
|
|
|
|
// S3Config is the S3-compatible storage backend config.
|
|
type S3Config struct {
|
|
Endpoint string `mapstructure:"endpoint"`
|
|
Bucket string `mapstructure:"bucket"`
|
|
AccessKey string `mapstructure:"access_key"`
|
|
SecretKey string `mapstructure:"secret_key"`
|
|
Region string `mapstructure:"region"`
|
|
PathPrefix string `mapstructure:"path_prefix"`
|
|
}
|
|
|
|
// WebDAVConfig is the WebDAV storage backend config.
|
|
type WebDAVConfig struct {
|
|
URL string `mapstructure:"url"`
|
|
Username string `mapstructure:"username"`
|
|
Password string `mapstructure:"password"`
|
|
}
|
|
|
|
// DefaultRetentionCount is used when no retention count is specified.
|
|
const DefaultRetentionCount = 7
|
|
|
|
// Validate checks the configuration and fills in defaults.
|
|
func (c *Config) Validate() error {
|
|
if c.Global.BackupDir == "" {
|
|
return fmt.Errorf("global.backup_dir is required")
|
|
}
|
|
if c.Global.TempDir == "" {
|
|
c.Global.TempDir = os.TempDir()
|
|
}
|
|
|
|
if len(c.Projects) == 0 {
|
|
return fmt.Errorf("at least one project must be configured")
|
|
}
|
|
|
|
for i := range c.Projects {
|
|
p := &c.Projects[i]
|
|
if p.Name == "" {
|
|
return fmt.Errorf("projects[%d]: name is required", i)
|
|
}
|
|
if p.Path == "" {
|
|
return fmt.Errorf("projects[%d] (%s): path is required", i, p.Name)
|
|
}
|
|
if p.ComposeFile == "" {
|
|
p.ComposeFile = "docker-compose.yml"
|
|
}
|
|
// Apply the default count only when no retention policy is set at all.
|
|
// This lets a user configure days-only retention (count omitted).
|
|
if p.Retention.Count <= 0 && p.Retention.Days <= 0 {
|
|
p.Retention.Count = DefaultRetentionCount
|
|
}
|
|
}
|
|
|
|
// Expand environment variable placeholders in remote configs and validate required fields.
|
|
if c.Remote != nil {
|
|
if c.Remote.S3 != nil {
|
|
c.Remote.S3.AccessKey = expandEnv(c.Remote.S3.AccessKey)
|
|
c.Remote.S3.SecretKey = expandEnv(c.Remote.S3.SecretKey)
|
|
if strings.TrimSpace(c.Remote.S3.Bucket) == "" {
|
|
return fmt.Errorf("remote.s3.bucket is required")
|
|
}
|
|
if strings.TrimSpace(c.Remote.S3.Region) == "" {
|
|
return fmt.Errorf("remote.s3.region is required")
|
|
}
|
|
if strings.TrimSpace(c.Remote.S3.AccessKey) == "" {
|
|
return fmt.Errorf("remote.s3.access_key is required")
|
|
}
|
|
if strings.TrimSpace(c.Remote.S3.SecretKey) == "" {
|
|
return fmt.Errorf("remote.s3.secret_key is required")
|
|
}
|
|
}
|
|
if c.Remote.WebDAV != nil {
|
|
c.Remote.WebDAV.Username = expandEnv(c.Remote.WebDAV.Username)
|
|
c.Remote.WebDAV.Password = expandEnv(c.Remote.WebDAV.Password)
|
|
if strings.TrimSpace(c.Remote.WebDAV.URL) == "" {
|
|
return fmt.Errorf("remote.webdav.url is required")
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// expandEnv replaces ${VAR} or $VAR placeholders with environment values.
|
|
func expandEnv(s string) string {
|
|
return os.Expand(s, func(key string) string {
|
|
return os.Getenv(key)
|
|
})
|
|
}
|
|
|
|
// HasRemote returns true if at least one remote backend is configured.
|
|
func (c *Config) HasRemote() bool {
|
|
return c.Remote != nil && (c.Remote.S3 != nil || c.Remote.WebDAV != nil)
|
|
}
|
|
|
|
// HasCron returns true if at least one project has a cron expression.
|
|
func (c *Config) HasCron() bool {
|
|
for _, p := range c.Projects {
|
|
if strings.TrimSpace(p.Cron) != "" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
} |