Files
docker-compose-backup/internal/backup/retention.go
T
2026-07-04 17:20:28 +08:00

91 lines
1.9 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 strings.HasPrefix(name, projectName+"-") && strings.HasSuffix(name, ".tar.gz")
}