1
0
mirror of https://github.com/pocketbase/pocketbase.git synced 2025-03-19 14:17:48 +02:00

avoid unnecessary copying the backup archive on restore when the local file system is used

This commit is contained in:
Gani Georgiev 2024-12-14 10:12:50 +02:00
parent 239daf2023
commit d5d764f83e
2 changed files with 64 additions and 39 deletions

View File

@ -142,6 +142,10 @@ func (app *BaseApp) CreateBackup(ctx context.Context, name string) error {
// //
// If a failure occure during the restore process the dir changes are reverted. // If a failure occure during the restore process the dir changes are reverted.
// If for whatever reason the revert is not possible, it panics. // If for whatever reason the revert is not possible, it panics.
//
// Note that if your pb_data has custom network mounts as subdirectories, then
// it is possible the restore to fail during the `os.Rename` operations
// (see https://github.com/pocketbase/pocketbase/issues/4647).
func (app *BaseApp) RestoreBackup(ctx context.Context, name string) error { func (app *BaseApp) RestoreBackup(ctx context.Context, name string) error {
if app.Store().Has(StoreKeyActiveBackup) { if app.Store().Has(StoreKeyActiveBackup) {
return errors.New("try again later - another backup/restore operation has already been started") return errors.New("try again later - another backup/restore operation has already been started")
@ -162,6 +166,13 @@ func (app *BaseApp) RestoreBackup(ctx context.Context, name string) error {
return errors.New("restore is not supported on Windows") return errors.New("restore is not supported on Windows")
} }
// make sure that the special temp directory exists
// note: it needs to be inside the current pb_data to avoid "cross-device link" errors
localTempDir := filepath.Join(e.App.DataDir(), LocalTempDirName)
if err := os.MkdirAll(localTempDir, os.ModePerm); err != nil {
return fmt.Errorf("failed to create a temp dir: %w", err)
}
fsys, err := e.App.NewBackupsFilesystem() fsys, err := e.App.NewBackupsFilesystem()
if err != nil { if err != nil {
return err return err
@ -170,57 +181,71 @@ func (app *BaseApp) RestoreBackup(ctx context.Context, name string) error {
fsys.SetContext(e.Context) fsys.SetContext(e.Context)
// fetch the backup file in a temp location if ok, _ := fsys.Exists(name); !ok {
return fmt.Errorf("missing or invalid backup file %q to restore", name)
}
extractedDataDir := filepath.Join(localTempDir, "pb_restore_"+security.PseudorandomString(8))
defer os.RemoveAll(extractedDataDir)
// extract the zip
if e.App.Settings().Backups.S3.Enabled {
br, err := fsys.GetFile(name) br, err := fsys.GetFile(name)
if err != nil { if err != nil {
return err return err
} }
defer br.Close() defer br.Close()
// make sure that the special temp directory exists
// note: it needs to be inside the current pb_data to avoid "cross-device link" errors
localTempDir := filepath.Join(e.App.DataDir(), LocalTempDirName)
if err := os.MkdirAll(localTempDir, os.ModePerm); err != nil {
return fmt.Errorf("failed to create a temp dir: %w", err)
}
// create a temp zip file from the blob.Reader and try to extract it // create a temp zip file from the blob.Reader and try to extract it
tempZip, err := os.CreateTemp(localTempDir, "pb_restore_zip") tempZip, err := os.CreateTemp(localTempDir, "pb_restore_zip")
if err != nil { if err != nil {
return err return err
} }
defer os.Remove(tempZip.Name()) defer os.Remove(tempZip.Name())
defer tempZip.Close() // note: this technically shouldn't be necessary but it is here to workaround platforms discrepancies
if _, err := io.Copy(tempZip, br); err != nil { _, err = io.Copy(tempZip, br)
if err != nil {
return err return err
} }
extractedDataDir := filepath.Join(localTempDir, "pb_restore_"+security.PseudorandomString(4)) err = archive.Extract(tempZip.Name(), extractedDataDir)
defer os.RemoveAll(extractedDataDir) if err != nil {
if err := archive.Extract(tempZip.Name(), extractedDataDir); err != nil {
return err return err
} }
// ensure that a database file exists // remove the temp zip file since we no longer need it
extractedDB := filepath.Join(extractedDataDir, "data.db")
if _, err := os.Stat(extractedDB); err != nil {
return fmt.Errorf("data.db file is missing or invalid: %w", err)
}
// remove the extracted zip file since we no longer need it
// (this is in case the app restarts and the defer calls are not called) // (this is in case the app restarts and the defer calls are not called)
if err := os.Remove(tempZip.Name()); err != nil { _ = tempZip.Close()
err = os.Remove(tempZip.Name())
if err != nil {
e.App.Logger().Debug( e.App.Logger().Debug(
"[RestoreBackup] Failed to remove the temp zip backup file", "[RestoreBackup] Failed to remove the temp zip backup file",
slog.String("file", tempZip.Name()), slog.String("file", tempZip.Name()),
slog.String("error", err.Error()), slog.String("error", err.Error()),
) )
} }
} else {
// manually construct the local path to avoid creating a copy of the zip file
// since the blob reader currently doesn't implement ReaderAt
zipPath := filepath.Join(app.DataDir(), LocalBackupsDirName, filepath.Base(name))
err = archive.Extract(zipPath, extractedDataDir)
if err != nil {
return err
}
}
// ensure that at least a database file exists
extractedDB := filepath.Join(extractedDataDir, "data.db")
if _, err := os.Stat(extractedDB); err != nil {
return fmt.Errorf("data.db file is missing or invalid: %w", err)
}
// move the current pb_data content to a special temp location // move the current pb_data content to a special temp location
// that will hold the old data between dirs replace // that will hold the old data between dirs replace
// (the temp dir will be automatically removed on the next app start) // (the temp dir will be automatically removed on the next app start)
oldTempDataDir := filepath.Join(localTempDir, "old_pb_data_"+security.PseudorandomString(4)) oldTempDataDir := filepath.Join(localTempDir, "old_pb_data_"+security.PseudorandomString(8))
if err := osutils.MoveDirContent(e.App.DataDir(), oldTempDataDir, e.Exclude...); err != nil { if err := osutils.MoveDirContent(e.App.DataDir(), oldTempDataDir, e.Exclude...); err != nil {
return fmt.Errorf("failed to move the current pb_data content to a temp location: %w", err) return fmt.Errorf("failed to move the current pb_data content to a temp location: %w", err)
} }

View File

@ -8,7 +8,7 @@ import (
"github.com/pocketbase/pocketbase/tools/list" "github.com/pocketbase/pocketbase/tools/list"
) )
// MoveDirContent moves the src dir content, that is not listed in the exclide list, // MoveDirContent moves the src dir content, that is not listed in the exclude list,
// to dest dir (it will be created if missing). // to dest dir (it will be created if missing).
// //
// The rootExclude argument is used to specify a list of src root entries to exclude. // The rootExclude argument is used to specify a list of src root entries to exclude.