fix: address review findings for backup management

- HIGH: Add sync.Mutex to backup Engine to prevent concurrent
  backup/restore operations
- HIGH: Restore uses io.Copy instead of ReadFile to avoid OOM on
  large databases
- HIGH: Send HTTP response before closing DB during restore, then
  perform destructive operations in a goroutine
- HIGH: Create pre-restore safety backup before overwriting database
- HIGH: Autobackup cron reschedules dynamically when settings change
  via callback pattern (same as DNS provider changes)
This commit is contained in:
2026-04-02 15:39:54 +03:00
parent a9c7775bb7
commit 3c9727162a
5 changed files with 97 additions and 37 deletions
+20 -7
View File
@@ -224,10 +224,20 @@ func main() {
}
}
// Schedule autobackup if enabled.
if settings.BackupEnabled && settings.BackupIntervalHours > 0 {
interval := fmt.Sprintf("@every %dh", settings.BackupIntervalHours)
if _, err := cronScheduler.AddFunc(interval, func() {
// Schedule autobackup if enabled. Track entry ID for rescheduling.
var backupCronID cron.EntryID
scheduleAutobackup := func(enabled bool, intervalHours int) {
// Remove existing schedule if any.
if backupCronID != 0 {
cronScheduler.Remove(backupCronID)
backupCronID = 0
slog.Info("autobackup: removed previous schedule")
}
if !enabled || intervalHours <= 0 {
return
}
interval := fmt.Sprintf("@every %dh", intervalHours)
id, err := cronScheduler.AddFunc(interval, func() {
b, err := backupEngine.CreateBackup("auto")
if err != nil {
slog.Error("autobackup failed", "error", err)
@@ -235,17 +245,19 @@ func main() {
}
slog.Info("autobackup completed", "id", b.ID, "filename", b.Filename)
// Prune after auto backup.
currentSettings, err := db.GetSettings()
if err == nil && currentSettings.BackupRetentionCount > 0 {
backupEngine.Prune(currentSettings.BackupRetentionCount)
}
}); err != nil {
})
if err != nil {
slog.Warn("failed to schedule autobackup", "error", err)
} else {
slog.Info("autobackup scheduled", "interval_hours", settings.BackupIntervalHours)
backupCronID = id
slog.Info("autobackup scheduled", "interval_hours", intervalHours)
}
}
scheduleAutobackup(settings.BackupEnabled, settings.BackupIntervalHours)
// Build API server.
apiServer := api.NewServer(db, dockerClient, npmClient, dep, webhookHandler, eventBus, encKey)
@@ -253,6 +265,7 @@ func main() {
apiServer.SetProxyManager(proxyManager)
apiServer.SetBackupEngine(backupEngine)
apiServer.SetDBPath(dbPath)
apiServer.SetBackupSettingsChangedCallback(scheduleAutobackup)
apiServer.SetDNSProvider(dnsProvider)
apiServer.SetDNSProviderChangedCallback(func(provider dns.Provider) {
dep.SetDNSProvider(provider)