Files
docker-compose-backup/internal/backup/retention.go
T
m1saka 034edb2b1e fix: 修复备份工具的多处问题
- 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 被大目录撑爆
2026-07-04 19:12:50 +08:00

96 lines
2.1 KiB
Go

package backup
import (
"os"
"path/filepath"
"sort"
"strings"
"time"
)
// RetentionPolicy defines how many backups to keep.
type RetentionPolicy struct {
Count int // keep at most N most recent backups
Days int // also delete backups older than N days (0 = disabled)
}
// ApplyRetention removes old backups from the project's backup directory.
// Returns the number of files removed.
func ApplyRetention(backupDir, projectName string, policy RetentionPolicy) (int, error) {
dir := filepath.Join(backupDir, projectName)
entries, err := os.ReadDir(dir)
if err != nil {
if os.IsNotExist(err) {
return 0, nil
}
return 0, err
}
type fileInfo struct {
name string
modTime time.Time
}
var backups []fileInfo
for _, entry := range entries {
if entry.IsDir() || !isBackupArchive(projectName, entry.Name()) {
continue
}
info, err := entry.Info()
if err != nil {
continue
}
backups = append(backups, fileInfo{
name: entry.Name(),
modTime: info.ModTime(),
})
}
if len(backups) == 0 {
return 0, nil
}
// Sort newest-first by modification time.
sort.Slice(backups, func(i, j int) bool {
return backups[i].modTime.After(backups[j].modTime)
})
removed := 0
// Enforce count-based retention.
if policy.Count > 0 && len(backups) > policy.Count {
for _, b := range backups[policy.Count:] {
path := filepath.Join(dir, b.name)
if err := os.Remove(path); err == nil {
removed++
}
}
// Trim the list for the next check.
backups = backups[:policy.Count]
}
// Enforce age-based retention.
if policy.Days > 0 {
cutoff := time.Now().AddDate(0, 0, -policy.Days)
for _, b := range backups {
if b.modTime.Before(cutoff) {
path := filepath.Join(dir, b.name)
if err := os.Remove(path); err == nil {
removed++
}
}
}
}
return removed, nil
}
func isBackupArchive(projectName, name string) bool {
return IsBackupArchive(projectName, name)
}
// IsBackupArchive reports whether name is a backup archive for the project.
func IsBackupArchive(projectName, name string) bool {
return strings.HasPrefix(name, projectName+"-") && strings.HasSuffix(name, ".tar.gz")
}