package main import ( "fmt" "log/slog" "os" "path/filepath" "sort" "time" "git.sr.ht/~ashkeel/strimertul/log" "git.sr.ht/~ashkeel/strimertul/database" "git.sr.ht/~ashkeel/strimertul/utils" ) func BackupTask(driver database.Driver, options database.BackupOptions) { if options.BackupDir == "" { slog.Warn("Backup directory not set, database backups are disabled") return } err := os.MkdirAll(options.BackupDir, 0o755) if err != nil { slog.Error("Could not create backup directory, moving to a temporary folder", log.Error(err)) options.BackupDir = os.TempDir() slog.Info("Using temporary directory", slog.String("backup-dir", options.BackupDir)) return } ticker := time.NewTicker(time.Duration(options.BackupInterval) * time.Minute) defer ticker.Stop() for range ticker.C { performBackup(driver, options) } } func performBackup(driver database.Driver, options database.BackupOptions) { // Run backup procedure file, err := os.Create(fmt.Sprintf("%s/%s.db", options.BackupDir, time.Now().Format("20060102-150405"))) if err != nil { slog.Error("Could not create backup file", log.Error(err)) return } err = driver.Backup(file) if err != nil { slog.Error("Could not backup database", log.Error(err)) } _ = file.Close() slog.Info("Database backup created", slog.String("backup-file", file.Name())) // Remove old backups files, err := os.ReadDir(options.BackupDir) if err != nil { slog.Error("Could not read backup directory", log.Error(err)) return } // If maxBackups is set, remove older backups when we reach the limit if options.MaxBackups > 0 && len(files) > options.MaxBackups { // Sort by date sort.Sort(utils.ByDate(files)) // Get files to remove toRemove := files[:len(files)-options.MaxBackups] for _, file := range toRemove { err = os.Remove(fmt.Sprintf("%s/%s", options.BackupDir, file.Name())) if err != nil { slog.Error("Could not remove backup file", log.Error(err)) } } } } type BackupInfo struct { Filename string `json:"filename"` Date int64 `json:"date"` Size int64 `json:"size"` } func (a *App) GetBackups() (list []BackupInfo) { files, err := os.ReadDir(a.backupOptions.BackupDir) if err != nil { slog.Error("Could not read backup directory", log.Error(err)) return nil } for _, file := range files { if file.IsDir() { continue } info, err := file.Info() if err != nil { slog.Error("Could not get info for backup file", log.Error(err)) continue } list = append(list, BackupInfo{ Filename: file.Name(), Date: info.ModTime().UnixMilli(), Size: info.Size(), }) } return } func (a *App) RestoreBackup(backupName string) error { path := filepath.Join(a.backupOptions.BackupDir, backupName) file, err := os.Open(path) if err != nil { return fmt.Errorf("could not open import file for reading: %w", err) } defer utils.Close(file) inStream := file if a.driver == nil { a.driver, err = database.GetDatabaseDriver(a.cliParams) if err != nil { return fmt.Errorf("could not open database: %w", err) } } err = a.driver.Restore(inStream) if err != nil { return fmt.Errorf("could not restore database: %w", err) } slog.Info("Restored database from backup") return nil }