package core import ( "context" "fmt" "log/slog" "sort" "time" "github.com/pocketbase/pocketbase/tools/inflector" ) const ( StoreKeyActiveBackup = "@activeBackup" ) // generateBackupName generates a new backup name based on the app name and current date. func generateBackupName(app App, prefix string) string { appName := inflector.Snakecase(app.Settings().Meta.AppName) if len(appName) > 50 { appName = appName[:50] } return fmt.Sprintf( "%s%s_%s.zip", prefix, appName, time.Now().UTC().Format("20060102150405"), ) } // registerAutobackupHooks registers the autobackup app serve hooks. func (app *BaseApp) registerAutobackupHooks() { const jobId = "__pbAutoBackup__" loadJob := func() { rawSchedule := app.Settings().Backups.Cron if rawSchedule == "" { app.Cron().Remove(jobId) return } app.Cron().Add(jobId, rawSchedule, func() { const autoPrefix = "@auto_pb_backup_" name := generateBackupName(app, autoPrefix) if err := app.CreateBackup(context.Background(), name); err != nil { app.Logger().Error( "[Backup cron] Failed to create backup", slog.String("name", name), slog.String("error", err.Error()), ) alertError := sendSystemAlertToAllSuperusers( app, "Autobackup failure", "Failed to create/upload automated backup. Raw error:\n"+err.Error(), ) if alertError != nil { app.Logger().Warn( "[Backup cron] Failed to send backup error alerts", slog.String("name", name), slog.String("error", alertError.Error()), ) } } maxKeep := app.Settings().Backups.CronMaxKeep if maxKeep == 0 { return // no explicit limit } fsys, err := app.NewBackupsFilesystem() if err != nil { app.Logger().Error( "[Backup cron] Failed to initialize the backup filesystem", slog.String("error", err.Error()), ) return } defer fsys.Close() files, err := fsys.List(autoPrefix) if err != nil { app.Logger().Error( "[Backup cron] Failed to list autogenerated backups", slog.String("error", err.Error()), ) return } if maxKeep >= len(files) { return // nothing to remove } // sort desc sort.Slice(files, func(i, j int) bool { return files[i].ModTime.After(files[j].ModTime) }) // keep only the most recent n auto backup files toRemove := files[maxKeep:] for _, f := range toRemove { if err := fsys.Delete(f.Key); err != nil { app.Logger().Error( "[Backup cron] Failed to remove old autogenerated backup", slog.String("key", f.Key), slog.String("error", err.Error()), ) } } }) } app.OnBootstrap().BindFunc(func(e *BootstrapEvent) error { if err := e.Next(); err != nil { return err } loadJob() return nil }) app.OnSettingsReload().BindFunc(func(e *SettingsReloadEvent) error { if err := e.Next(); err != nil { return err } loadJob() return nil }) }