https://gist.github.com/Peltoche/60b8b81dfbf70164d0e2b88988003229 was used for it, thanks to @Peltoche for publishing it.
99 lines
2.8 KiB
Go
99 lines
2.8 KiB
Go
package migrator
|
|
|
|
import (
|
|
"compress/gzip"
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
log "github.com/go-pkgz/lgr"
|
|
)
|
|
|
|
// AutoBackup struct handles daily backups params for siteID
|
|
type AutoBackup struct {
|
|
Exporter Exporter
|
|
BackupLocation string
|
|
SiteID string
|
|
KeepMax int
|
|
Duration time.Duration
|
|
}
|
|
|
|
// Do runs daily export to local files, keeps up to keepMax backups for given siteID
|
|
func (ab AutoBackup) Do(ctx context.Context) {
|
|
log.Printf("[INFO] activate auto-backup for %s under %s, duration %s", ab.SiteID, ab.BackupLocation, ab.Duration)
|
|
tick := time.NewTicker(ab.Duration)
|
|
defer tick.Stop()
|
|
log.Printf("[DEBUG] first backup for %s at %s", ab.SiteID, time.Now().Add(ab.Duration))
|
|
|
|
for {
|
|
select {
|
|
case <-tick.C:
|
|
if _, err := ab.makeBackup(); err != nil {
|
|
log.Printf("[WARN] auto-backup for %s failed, %s", ab.SiteID, err)
|
|
continue
|
|
}
|
|
ab.removeOldBackupFiles()
|
|
log.Printf("[DEBUG] next backup for %s at %s", ab.SiteID, time.Now().Add(ab.Duration))
|
|
case <-ctx.Done():
|
|
log.Printf("[WARN] terminated autobackup for %s", ab.SiteID)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ab AutoBackup) makeBackup() (string, error) {
|
|
log.Printf("[DEBUG] make backup for %s", ab.SiteID)
|
|
backupFile := fmt.Sprintf("%s/backup-%s-%s.gz", ab.BackupLocation, ab.SiteID, time.Now().Format("20060102"))
|
|
fh, err := os.Create(backupFile) //nolint:gosec // harmless
|
|
if err != nil {
|
|
return "", fmt.Errorf("can't create backup file %s: %w", backupFile, err)
|
|
}
|
|
gz := gzip.NewWriter(fh)
|
|
|
|
if _, err = ab.Exporter.Export(gz, ab.SiteID); err != nil {
|
|
return "", fmt.Errorf("export failed for %s: %w", ab.SiteID, err)
|
|
}
|
|
if err = gz.Close(); err != nil {
|
|
return "", fmt.Errorf("can't close gz for %s: %w", backupFile, err)
|
|
}
|
|
if err = fh.Close(); err != nil {
|
|
return "", fmt.Errorf("can't close file handler for %s: %w", backupFile, err)
|
|
}
|
|
log.Printf("[DEBUG] created backup file %s", backupFile)
|
|
return backupFile, nil
|
|
}
|
|
|
|
func (ab AutoBackup) removeOldBackupFiles() {
|
|
files, err := os.ReadDir(ab.BackupLocation)
|
|
if err != nil {
|
|
log.Printf("[WARN] can't read files in backup directory %s, %s", ab.BackupLocation, err)
|
|
return
|
|
}
|
|
backFiles := []os.FileInfo{}
|
|
for _, file := range files {
|
|
info, e := file.Info()
|
|
if e != nil {
|
|
log.Printf("[WARN] can't read info for directory %s, %s", file.Name(), e)
|
|
return
|
|
}
|
|
if strings.HasPrefix(file.Name(), "backup-"+ab.SiteID) {
|
|
backFiles = append(backFiles, info)
|
|
}
|
|
}
|
|
sort.Slice(backFiles, func(i int, j int) bool { return backFiles[i].Name() < backFiles[j].Name() })
|
|
|
|
if len(backFiles) > ab.KeepMax {
|
|
for i := 0; i < len(backFiles)-ab.KeepMax; i++ {
|
|
fpath := ab.BackupLocation + "/" + backFiles[i].Name()
|
|
if e := os.Remove(fpath); e != nil {
|
|
log.Printf("[WARN] can't delete %s, %s", fpath, err)
|
|
continue
|
|
}
|
|
log.Printf("[DEBUG] removed %s", fpath)
|
|
}
|
|
}
|
|
}
|