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:
parent
239daf2023
commit
d5d764f83e
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user