From 19533514c213672727a385ba650bc923a9811445 Mon Sep 17 00:00:00 2001 From: Rostislav Dugin Date: Fri, 2 Jan 2026 11:12:52 +0300 Subject: [PATCH 1/6] FEATURE (postgresql): Move to directory format to speed up parallel backups --- .../features/backups/backups/common/dto.go | 17 + .../{usecases => }/common/interfaces.go | 0 .../features/backups/backups/controller.go | 73 +++- .../backups/backups/controller_test.go | 135 ++++++ .../features/backups/backups/interfaces.go | 2 +- .../features/backups/backups/model.go | 4 +- .../features/backups/backups/service.go | 19 +- .../features/backups/backups/service_test.go | 2 +- .../backups/backups/usecases/common/dto.go | 9 - .../backups/usecases/create_backup_uc.go | 4 +- .../usecases/mariadb/create_backup_uc.go | 14 +- .../usecases/mongodb/create_backup_uc.go | 14 +- .../usecases/mysql/create_backup_uc.go | 14 +- .../usecases/postgresql/create_backup_uc.go | 394 +++++++++--------- .../usecases/postgresql/restore_backup_uc.go | 336 ++++++++++++++- .../20260101200438_add_backup_format.sql | 9 + frontend/src/entity/backups/api/backupsApi.ts | 19 +- .../model/mongodb/MongodbDatabase.ts | 2 +- .../features/backups/ui/BackupsComponent.tsx | 12 +- .../databases/ui/CreateDatabaseComponent.tsx | 4 +- .../edit/EditMongoDbSpecificDataComponent.tsx | 12 +- .../EditPostgreSqlSpecificDataComponent.tsx | 54 ++- .../show/ShowMongoDbSpecificDataComponent.tsx | 14 +- .../ShowPostgreSqlSpecificDataComponent.tsx | 5 + frontend/src/shared/api/apiHelper.ts | 19 + 25 files changed, 876 insertions(+), 311 deletions(-) create mode 100644 backend/internal/features/backups/backups/common/dto.go rename backend/internal/features/backups/backups/{usecases => }/common/interfaces.go (100%) delete mode 100644 backend/internal/features/backups/backups/usecases/common/dto.go create mode 100644 backend/migrations/20260101200438_add_backup_format.sql diff --git a/backend/internal/features/backups/backups/common/dto.go b/backend/internal/features/backups/backups/common/dto.go new file mode 100644 index 0000000..3075f2b --- /dev/null +++ b/backend/internal/features/backups/backups/common/dto.go @@ -0,0 +1,17 @@ +package common + +import backups_config "databasus-backend/internal/features/backups/config" + +type BackupType string + +const ( + BackupTypeDefault BackupType = "DEFAULT" // For MySQL, MongoDB, PostgreSQL legacy (-Fc) + BackupTypeDirectory BackupType = "DIRECTORY" // PostgreSQL directory type (-Fd) +) + +type BackupMetadata struct { + EncryptionSalt *string + EncryptionIV *string + Encryption backups_config.BackupEncryption + Type BackupType +} diff --git a/backend/internal/features/backups/backups/usecases/common/interfaces.go b/backend/internal/features/backups/backups/common/interfaces.go similarity index 100% rename from backend/internal/features/backups/backups/usecases/common/interfaces.go rename to backend/internal/features/backups/backups/common/interfaces.go diff --git a/backend/internal/features/backups/backups/controller.go b/backend/internal/features/backups/backups/controller.go index 1e909b8..8d9163c 100644 --- a/backend/internal/features/backups/backups/controller.go +++ b/backend/internal/features/backups/backups/controller.go @@ -1,6 +1,7 @@ package backups import ( + "databasus-backend/internal/features/backups/backups/common" "databasus-backend/internal/features/databases" users_middleware "databasus-backend/internal/features/users/middleware" "fmt" @@ -182,7 +183,7 @@ func (c *BackupController) GetFile(ctx *gin.Context) { return } - fileReader, dbType, err := c.backupService.GetBackupFile(user, id) + fileReader, backup, database, err := c.backupService.GetBackupFile(user, id) if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return @@ -193,15 +194,12 @@ func (c *BackupController) GetFile(ctx *gin.Context) { } }() - extension := ".dump" - if dbType == databases.DatabaseTypeMysql || dbType == databases.DatabaseTypeMariadb { - extension = ".sql.zst" - } + filename := c.generateBackupFilename(backup, database) ctx.Header("Content-Type", "application/octet-stream") ctx.Header( "Content-Disposition", - fmt.Sprintf("attachment; filename=\"backup_%s%s\"", id.String(), extension), + fmt.Sprintf("attachment; filename=\"%s\"", filename), ) _, err = io.Copy(ctx.Writer, fileReader) @@ -214,3 +212,66 @@ func (c *BackupController) GetFile(ctx *gin.Context) { type MakeBackupRequest struct { DatabaseID uuid.UUID `json:"database_id" binding:"required"` } + +func (c *BackupController) generateBackupFilename( + backup *Backup, + database *databases.Database, +) string { + // Format timestamp as YYYY-MM-DD_HH-mm-ss + timestamp := backup.CreatedAt.Format("2006-01-02_15-04-05") + + // Sanitize database name for filename (replace spaces and special chars) + safeName := sanitizeFilename(database.Name) + + // Determine extension based on database type and backup type + extension := c.getBackupExtension(database.Type, backup.Type) + + return fmt.Sprintf("%s_backup_%s%s", safeName, timestamp, extension) +} + +func (c *BackupController) getBackupExtension( + dbType databases.DatabaseType, + backupType common.BackupType, +) string { + switch dbType { + case databases.DatabaseTypeMysql, databases.DatabaseTypeMariadb: + return ".sql.zst" + case databases.DatabaseTypePostgres: + // For PostgreSQL, use .tar for directory type, .dump for custom type + if backupType == common.BackupTypeDirectory { + return ".tar" + } + return ".dump" + case databases.DatabaseTypeMongodb: + return ".archive" + default: + return ".backup" + } +} + +func sanitizeFilename(name string) string { + // Replace characters that are invalid in filenames + replacer := map[rune]rune{ + ' ': '_', + '/': '-', + '\\': '-', + ':': '-', + '*': '-', + '?': '-', + '"': '-', + '<': '-', + '>': '-', + '|': '-', + } + + result := make([]rune, 0, len(name)) + for _, char := range name { + if replacement, exists := replacer[char]; exists { + result = append(result, replacement) + } else { + result = append(result, char) + } + } + + return string(result) +} diff --git a/backend/internal/features/backups/backups/controller_test.go b/backend/internal/features/backups/backups/controller_test.go index fec0dd6..c502a3e 100644 --- a/backend/internal/features/backups/backups/controller_test.go +++ b/backend/internal/features/backups/backups/controller_test.go @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" audit_logs "databasus-backend/internal/features/audit_logs" + common "databasus-backend/internal/features/backups/backups/common" backups_config "databasus-backend/internal/features/backups/config" "databasus-backend/internal/features/databases" "databasus-backend/internal/features/databases/databases/postgresql" @@ -494,6 +495,123 @@ func Test_DownloadBackup_AuditLogWritten(t *testing.T) { assert.True(t, found, "Audit log for backup download not found") } +func Test_DownloadBackup_ProperFilenameForPostgreSQL(t *testing.T) { + tests := []struct { + name string + databaseName string + backupType string + expectedExt string + expectedInName string + }{ + { + name: "PostgreSQL with directory type", + databaseName: "my_postgres_db", + backupType: "DIRECTORY", + expectedExt: ".tar", + expectedInName: "my_postgres_db_backup_", + }, + { + name: "PostgreSQL with default type", + databaseName: "my_postgres_db", + backupType: "DEFAULT", + expectedExt: ".dump", + expectedInName: "my_postgres_db_backup_", + }, + { + name: "Database name with spaces", + databaseName: "my test db", + backupType: "DIRECTORY", + expectedExt: ".tar", + expectedInName: "my_test_db_backup_", + }, + { + name: "Database name with special characters", + databaseName: "my:db/test", + backupType: "DEFAULT", + expectedExt: ".dump", + expectedInName: "my-db-test_backup_", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + router := createTestRouter() + owner := users_testing.CreateTestUser(users_enums.UserRoleMember) + workspace := workspaces_testing.CreateTestWorkspace("Test Workspace", owner, router) + + database := createTestDatabase(tt.databaseName, workspace.ID, owner.Token, router) + storage := createTestStorage(workspace.ID) + + configService := backups_config.GetBackupConfigService() + config, err := configService.GetBackupConfigByDbId(database.ID) + assert.NoError(t, err) + + config.IsBackupsEnabled = true + config.StorageID = &storage.ID + config.Storage = storage + _, err = configService.SaveBackupConfig(config) + assert.NoError(t, err) + + backup := createTestBackupWithType(database, owner, tt.backupType) + + resp := test_utils.MakeGetRequest( + t, + router, + fmt.Sprintf("/api/v1/backups/%s/file", backup.ID.String()), + "Bearer "+owner.Token, + http.StatusOK, + ) + + contentDisposition := resp.Headers.Get("Content-Disposition") + assert.NotEmpty(t, contentDisposition, "Content-Disposition header should be present") + + // Verify the filename contains expected parts + assert.Contains( + t, + contentDisposition, + tt.expectedInName, + "Filename should contain sanitized database name", + ) + assert.Contains( + t, + contentDisposition, + tt.expectedExt, + "Filename should have correct extension", + ) + assert.Contains(t, contentDisposition, "attachment", "Should be an attachment") + + // Verify timestamp format (YYYY-MM-DD_HH-mm-ss) + assert.Regexp( + t, + `\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}`, + contentDisposition, + "Filename should contain timestamp", + ) + }) + } +} + +func Test_SanitizeFilename(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {input: "simple_name", expected: "simple_name"}, + {input: "name with spaces", expected: "name_with_spaces"}, + {input: "name/with\\slashes", expected: "name-with-slashes"}, + {input: "name:with*special?chars", expected: "name-with-special-chars"}, + {input: "namepipes|", expected: "name-with-pipes-"}, + {input: `name"with"quotes`, expected: "name-with-quotes"}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + result := sanitizeFilename(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + func Test_CancelBackup_InProgressBackup_SuccessfullyCancelled(t *testing.T) { router := createTestRouter() owner := users_testing.CreateTestUser(users_enums.UserRoleMember) @@ -709,3 +827,20 @@ func createTestBackup( return backup } + +func createTestBackupWithType( + database *databases.Database, + owner *users_dto.SignInResponseDTO, + backupType string, +) *Backup { + backup := createTestBackup(database, owner) + + // Update the format field + repo := &BackupRepository{} + backup.Type = common.BackupType(backupType) + if err := repo.Save(backup); err != nil { + panic(err) + } + + return backup +} diff --git a/backend/internal/features/backups/backups/interfaces.go b/backend/internal/features/backups/backups/interfaces.go index 835a64c..1c8bce7 100644 --- a/backend/internal/features/backups/backups/interfaces.go +++ b/backend/internal/features/backups/backups/interfaces.go @@ -3,7 +3,7 @@ package backups import ( "context" - usecases_common "databasus-backend/internal/features/backups/backups/usecases/common" + usecases_common "databasus-backend/internal/features/backups/backups/common" backups_config "databasus-backend/internal/features/backups/config" "databasus-backend/internal/features/databases" "databasus-backend/internal/features/notifiers" diff --git a/backend/internal/features/backups/backups/model.go b/backend/internal/features/backups/backups/model.go index 4d0c708..165aa2b 100644 --- a/backend/internal/features/backups/backups/model.go +++ b/backend/internal/features/backups/backups/model.go @@ -1,6 +1,7 @@ package backups import ( + common "databasus-backend/internal/features/backups/backups/common" backups_config "databasus-backend/internal/features/backups/config" "time" @@ -24,5 +25,6 @@ type Backup struct { EncryptionIV *string `json:"-" gorm:"column:encryption_iv"` Encryption backups_config.BackupEncryption `json:"encryption" gorm:"column:encryption;type:text;not null;default:'NONE'"` - CreatedAt time.Time `json:"createdAt" gorm:"column:created_at"` + Type common.BackupType `json:"type" gorm:"column:type;type:text;not null;default:'DEFAULT'"` + CreatedAt time.Time `json:"createdAt" gorm:"column:created_at"` } diff --git a/backend/internal/features/backups/backups/service.go b/backend/internal/features/backups/backups/service.go index bb86ca0..107b587 100644 --- a/backend/internal/features/backups/backups/service.go +++ b/backend/internal/features/backups/backups/service.go @@ -343,6 +343,7 @@ func (s *BackupService) MakeBackup(databaseID uuid.UUID, isLastTry bool) { backup.EncryptionSalt = backupMetadata.EncryptionSalt backup.EncryptionIV = backupMetadata.EncryptionIV backup.Encryption = backupMetadata.Encryption + backup.Type = backupMetadata.Type } if err := s.backupRepository.Save(backup); err != nil { @@ -502,19 +503,19 @@ func (s *BackupService) CancelBackup( func (s *BackupService) GetBackupFile( user *users_models.User, backupID uuid.UUID, -) (io.ReadCloser, databases.DatabaseType, error) { +) (io.ReadCloser, *Backup, *databases.Database, error) { backup, err := s.backupRepository.FindByID(backupID) if err != nil { - return nil, "", err + return nil, nil, nil, err } database, err := s.databaseService.GetDatabaseByID(backup.DatabaseID) if err != nil { - return nil, "", err + return nil, nil, nil, err } if database.WorkspaceID == nil { - return nil, "", errors.New("cannot download backup for database without workspace") + return nil, nil, nil, errors.New("cannot download backup for database without workspace") } canAccess, _, err := s.workspaceService.CanUserAccessWorkspace( @@ -522,10 +523,12 @@ func (s *BackupService) GetBackupFile( user, ) if err != nil { - return nil, "", err + return nil, nil, nil, err } if !canAccess { - return nil, "", errors.New("insufficient permissions to download backup for this database") + return nil, nil, nil, errors.New( + "insufficient permissions to download backup for this database", + ) } s.auditLogService.WriteAuditLog( @@ -540,10 +543,10 @@ func (s *BackupService) GetBackupFile( reader, err := s.getBackupReader(backupID) if err != nil { - return nil, "", err + return nil, nil, nil, err } - return reader, database.Type, nil + return reader, backup, database, nil } func (s *BackupService) deleteBackup(backup *Backup) error { diff --git a/backend/internal/features/backups/backups/service_test.go b/backend/internal/features/backups/backups/service_test.go index 3813307..6e7081b 100644 --- a/backend/internal/features/backups/backups/service_test.go +++ b/backend/internal/features/backups/backups/service_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "databasus-backend/internal/features/backups/backups/usecases/common" + common "databasus-backend/internal/features/backups/backups/common" backups_config "databasus-backend/internal/features/backups/config" "databasus-backend/internal/features/databases" encryption_secrets "databasus-backend/internal/features/encryption/secrets" diff --git a/backend/internal/features/backups/backups/usecases/common/dto.go b/backend/internal/features/backups/backups/usecases/common/dto.go deleted file mode 100644 index a53e60b..0000000 --- a/backend/internal/features/backups/backups/usecases/common/dto.go +++ /dev/null @@ -1,9 +0,0 @@ -package common - -import backups_config "databasus-backend/internal/features/backups/config" - -type BackupMetadata struct { - EncryptionSalt *string - EncryptionIV *string - Encryption backups_config.BackupEncryption -} diff --git a/backend/internal/features/backups/backups/usecases/create_backup_uc.go b/backend/internal/features/backups/backups/usecases/create_backup_uc.go index c49117b..bf798c8 100644 --- a/backend/internal/features/backups/backups/usecases/create_backup_uc.go +++ b/backend/internal/features/backups/backups/usecases/create_backup_uc.go @@ -4,7 +4,7 @@ import ( "context" "errors" - usecases_common "databasus-backend/internal/features/backups/backups/usecases/common" + common "databasus-backend/internal/features/backups/backups/common" usecases_mariadb "databasus-backend/internal/features/backups/backups/usecases/mariadb" usecases_mongodb "databasus-backend/internal/features/backups/backups/usecases/mongodb" usecases_mysql "databasus-backend/internal/features/backups/backups/usecases/mysql" @@ -30,7 +30,7 @@ func (uc *CreateBackupUsecase) Execute( database *databases.Database, storage *storages.Storage, backupProgressListener func(completedMBs float64), -) (*usecases_common.BackupMetadata, error) { +) (*common.BackupMetadata, error) { switch database.Type { case databases.DatabaseTypePostgres: return uc.CreatePostgresqlBackupUsecase.Execute( diff --git a/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go b/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go index 85f7806..16c74cc 100644 --- a/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go +++ b/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go @@ -18,8 +18,8 @@ import ( "github.com/klauspost/compress/zstd" "databasus-backend/internal/config" + common "databasus-backend/internal/features/backups/backups/common" backup_encryption "databasus-backend/internal/features/backups/backups/encryption" - usecases_common "databasus-backend/internal/features/backups/backups/usecases/common" backups_config "databasus-backend/internal/features/backups/config" "databasus-backend/internal/features/databases" mariadbtypes "databasus-backend/internal/features/databases/databases/mariadb" @@ -30,7 +30,7 @@ import ( ) const ( - backupTimeout = 23 * time.Hour + backupTimeout = 6 * time.Hour shutdownCheckInterval = 1 * time.Second copyBufferSize = 8 * 1024 * 1024 progressReportIntervalMB = 1.0 @@ -57,7 +57,7 @@ func (uc *CreateMariadbBackupUsecase) Execute( db *databases.Database, storage *storages.Storage, backupProgressListener func(completedMBs float64), -) (*usecases_common.BackupMetadata, error) { +) (*common.BackupMetadata, error) { uc.logger.Info( "Creating MariaDB backup via mariadb-dump", "databaseId", db.ID, @@ -140,7 +140,7 @@ func (uc *CreateMariadbBackupUsecase) streamToStorage( storage *storages.Storage, backupProgressListener func(completedMBs float64), mdbConfig *mariadbtypes.MariadbDatabase, -) (*usecases_common.BackupMetadata, error) { +) (*common.BackupMetadata, error) { uc.logger.Info("Streaming MariaDB backup to storage", "mariadbBin", mariadbBin) ctx, cancel := uc.createBackupContext(parentCtx) @@ -196,7 +196,7 @@ func (uc *CreateMariadbBackupUsecase) streamToStorage( if err != nil { return nil, fmt.Errorf("failed to create zstd writer: %w", err) } - countingWriter := usecases_common.NewCountingWriter(zstdWriter) + countingWriter := common.NewCountingWriter(zstdWriter) saveErrCh := make(chan error, 1) go func() { @@ -401,8 +401,8 @@ func (uc *CreateMariadbBackupUsecase) setupBackupEncryption( backupID uuid.UUID, backupConfig *backups_config.BackupConfig, storageWriter io.WriteCloser, -) (io.Writer, *backup_encryption.EncryptionWriter, usecases_common.BackupMetadata, error) { - metadata := usecases_common.BackupMetadata{} +) (io.Writer, *backup_encryption.EncryptionWriter, common.BackupMetadata, error) { + metadata := common.BackupMetadata{} if backupConfig.Encryption != backups_config.BackupEncryptionEncrypted { metadata.Encryption = backups_config.BackupEncryptionNone diff --git a/backend/internal/features/backups/backups/usecases/mongodb/create_backup_uc.go b/backend/internal/features/backups/backups/usecases/mongodb/create_backup_uc.go index 73a86fe..000cd2d 100644 --- a/backend/internal/features/backups/backups/usecases/mongodb/create_backup_uc.go +++ b/backend/internal/features/backups/backups/usecases/mongodb/create_backup_uc.go @@ -15,8 +15,8 @@ import ( "github.com/google/uuid" "databasus-backend/internal/config" + common "databasus-backend/internal/features/backups/backups/common" backup_encryption "databasus-backend/internal/features/backups/backups/encryption" - usecases_common "databasus-backend/internal/features/backups/backups/usecases/common" backups_config "databasus-backend/internal/features/backups/config" "databasus-backend/internal/features/databases" mongodbtypes "databasus-backend/internal/features/databases/databases/mongodb" @@ -27,7 +27,7 @@ import ( ) const ( - backupTimeout = 23 * time.Hour + backupTimeout = 6 * time.Hour shutdownCheckInterval = 1 * time.Second copyBufferSize = 8 * 1024 * 1024 progressReportIntervalMB = 1.0 @@ -51,7 +51,7 @@ func (uc *CreateMongodbBackupUsecase) Execute( db *databases.Database, storage *storages.Storage, backupProgressListener func(completedMBs float64), -) (*usecases_common.BackupMetadata, error) { +) (*common.BackupMetadata, error) { uc.logger.Info( "Creating MongoDB backup via mongodump", "databaseId", db.ID, @@ -124,7 +124,7 @@ func (uc *CreateMongodbBackupUsecase) streamToStorage( args []string, storage *storages.Storage, backupProgressListener func(completedMBs float64), -) (*usecases_common.BackupMetadata, error) { +) (*common.BackupMetadata, error) { uc.logger.Info("Streaming MongoDB backup to storage", "mongodumpBin", mongodumpBin) ctx, cancel := uc.createBackupContext(parentCtx) @@ -175,7 +175,7 @@ func (uc *CreateMongodbBackupUsecase) streamToStorage( return nil, err } - countingWriter := usecases_common.NewCountingWriter(finalWriter) + countingWriter := common.NewCountingWriter(finalWriter) saveErrCh := make(chan error, 1) go func() { @@ -264,8 +264,8 @@ func (uc *CreateMongodbBackupUsecase) setupBackupEncryption( backupID uuid.UUID, backupConfig *backups_config.BackupConfig, storageWriter io.WriteCloser, -) (io.Writer, *backup_encryption.EncryptionWriter, usecases_common.BackupMetadata, error) { - backupMetadata := usecases_common.BackupMetadata{ +) (io.Writer, *backup_encryption.EncryptionWriter, common.BackupMetadata, error) { + backupMetadata := common.BackupMetadata{ Encryption: backups_config.BackupEncryptionNone, } diff --git a/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go b/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go index 92eca3a..d912c40 100644 --- a/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go +++ b/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go @@ -18,8 +18,8 @@ import ( "github.com/klauspost/compress/zstd" "databasus-backend/internal/config" + common "databasus-backend/internal/features/backups/backups/common" backup_encryption "databasus-backend/internal/features/backups/backups/encryption" - usecases_common "databasus-backend/internal/features/backups/backups/usecases/common" backups_config "databasus-backend/internal/features/backups/config" "databasus-backend/internal/features/databases" mysqltypes "databasus-backend/internal/features/databases/databases/mysql" @@ -30,7 +30,7 @@ import ( ) const ( - backupTimeout = 23 * time.Hour + backupTimeout = 6 * time.Hour shutdownCheckInterval = 1 * time.Second copyBufferSize = 8 * 1024 * 1024 progressReportIntervalMB = 1.0 @@ -57,7 +57,7 @@ func (uc *CreateMysqlBackupUsecase) Execute( db *databases.Database, storage *storages.Storage, backupProgressListener func(completedMBs float64), -) (*usecases_common.BackupMetadata, error) { +) (*common.BackupMetadata, error) { uc.logger.Info( "Creating MySQL backup via mysqldump", "databaseId", db.ID, @@ -155,7 +155,7 @@ func (uc *CreateMysqlBackupUsecase) streamToStorage( storage *storages.Storage, backupProgressListener func(completedMBs float64), myConfig *mysqltypes.MysqlDatabase, -) (*usecases_common.BackupMetadata, error) { +) (*common.BackupMetadata, error) { uc.logger.Info("Streaming MySQL backup to storage", "mysqlBin", mysqlBin) ctx, cancel := uc.createBackupContext(parentCtx) @@ -211,7 +211,7 @@ func (uc *CreateMysqlBackupUsecase) streamToStorage( if err != nil { return nil, fmt.Errorf("failed to create zstd writer: %w", err) } - countingWriter := usecases_common.NewCountingWriter(zstdWriter) + countingWriter := common.NewCountingWriter(zstdWriter) saveErrCh := make(chan error, 1) go func() { @@ -414,8 +414,8 @@ func (uc *CreateMysqlBackupUsecase) setupBackupEncryption( backupID uuid.UUID, backupConfig *backups_config.BackupConfig, storageWriter io.WriteCloser, -) (io.Writer, *backup_encryption.EncryptionWriter, usecases_common.BackupMetadata, error) { - metadata := usecases_common.BackupMetadata{} +) (io.Writer, *backup_encryption.EncryptionWriter, common.BackupMetadata, error) { + metadata := common.BackupMetadata{} if backupConfig.Encryption != backups_config.BackupEncryptionEncrypted { metadata.Encryption = backups_config.BackupEncryptionNone diff --git a/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go b/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go index 6742417..9e5880b 100644 --- a/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go +++ b/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go @@ -1,6 +1,7 @@ package usecases_postgresql import ( + "archive/tar" "context" "encoding/base64" "errors" @@ -15,23 +16,23 @@ import ( "time" "databasus-backend/internal/config" + common "databasus-backend/internal/features/backups/backups/common" backup_encryption "databasus-backend/internal/features/backups/backups/encryption" - usecases_common "databasus-backend/internal/features/backups/backups/usecases/common" backups_config "databasus-backend/internal/features/backups/config" "databasus-backend/internal/features/databases" pgtypes "databasus-backend/internal/features/databases/databases/postgresql" encryption_secrets "databasus-backend/internal/features/encryption/secrets" "databasus-backend/internal/features/storages" "databasus-backend/internal/util/encryption" + files_utils "databasus-backend/internal/util/files" "databasus-backend/internal/util/tools" "github.com/google/uuid" ) const ( - backupTimeout = 23 * time.Hour + backupTimeout = 6 * time.Hour shutdownCheckInterval = 1 * time.Second - copyBufferSize = 8 * 1024 * 1024 progressReportIntervalMB = 1.0 pgConnectTimeout = 30 compressionLevel = 5 @@ -46,11 +47,6 @@ type CreatePostgresqlBackupUsecase struct { fieldEncryptor encryption.FieldEncryptor } -type writeResult struct { - bytesWritten int - writeErr error -} - func (uc *CreatePostgresqlBackupUsecase) Execute( ctx context.Context, backupID uuid.UUID, @@ -60,9 +56,9 @@ func (uc *CreatePostgresqlBackupUsecase) Execute( backupProgressListener func( completedMBs float64, ), -) (*usecases_common.BackupMetadata, error) { +) (*common.BackupMetadata, error) { uc.logger.Info( - "Creating PostgreSQL backup via pg_dump custom format", + "Creating PostgreSQL backup via pg_dump directory type", "databaseId", db.ID, "storageId", @@ -83,14 +79,12 @@ func (uc *CreatePostgresqlBackupUsecase) Execute( return nil, fmt.Errorf("database name is required for pg_dump backups") } - args := uc.buildPgDumpArgs(pg) - decryptedPassword, err := uc.fieldEncryptor.Decrypt(db.ID, pg.Password) if err != nil { return nil, fmt.Errorf("failed to decrypt database password: %w", err) } - return uc.streamToStorage( + return uc.executeDirectoryBackup( ctx, backupID, backupConfig, @@ -100,66 +94,127 @@ func (uc *CreatePostgresqlBackupUsecase) Execute( config.GetEnv().EnvMode, config.GetEnv().PostgresesInstallDir, ), - args, + pg, decryptedPassword, storage, - db, backupProgressListener, ) } -// streamToStorage streams pg_dump output directly to storage -func (uc *CreatePostgresqlBackupUsecase) streamToStorage( +// executeDirectoryBackup runs pg_dump with directory type and streams as TAR to storage +func (uc *CreatePostgresqlBackupUsecase) executeDirectoryBackup( parentCtx context.Context, backupID uuid.UUID, backupConfig *backups_config.BackupConfig, pgBin string, - args []string, + pg *pgtypes.PostgresqlDatabase, password string, storage *storages.Storage, - db *databases.Database, backupProgressListener func(completedMBs float64), -) (*usecases_common.BackupMetadata, error) { - uc.logger.Info("Streaming PostgreSQL backup to storage", "pgBin", pgBin, "args", args) - +) (*common.BackupMetadata, error) { ctx, cancel := uc.createBackupContext(parentCtx) defer cancel() - pgpassFile, err := uc.setupPgpassFile(db.Postgresql, password) + // Create temporary directory for pg_dump output + err := files_utils.EnsureDirectories([]string{config.GetEnv().TempFolder}) + if err != nil { + return nil, fmt.Errorf("failed to ensure temp directories: %w", err) + } + + tempDir, err := os.MkdirTemp(config.GetEnv().TempFolder, "pgdump_"+backupID.String()) + if err != nil { + return nil, fmt.Errorf("failed to create temp directory: %w", err) + } + defer func() { + _ = os.RemoveAll(tempDir) + }() + + outputDir := filepath.Join(tempDir, "dump") + + args := uc.buildPgDumpArgs(pg, outputDir) + uc.logger.Info( + "Executing PostgreSQL backup with directory type", + "pgBin", + pgBin, + "args", + args, + "outputDir", + outputDir, + ) + + pgpassFile, err := uc.setupPgpassFile(pg, password) if err != nil { return nil, err } defer func() { if pgpassFile != "" { - // Remove the entire temp directory (which contains the .pgpass file) _ = os.RemoveAll(filepath.Dir(pgpassFile)) } }() + // Execute pg_dump to directory cmd := exec.CommandContext(ctx, pgBin, args...) - uc.logger.Info("Executing PostgreSQL backup command", "command", cmd.String()) - - if err := uc.setupPgEnvironment(cmd, pgpassFile, db.Postgresql.IsHttps, password, db.Postgresql.CpuCount, pgBin); err != nil { + if err := uc.setupPgEnvironment(cmd, pgpassFile, pg.IsHttps, password, pg.CpuCount, pgBin); err != nil { return nil, err } - pgStdout, err := cmd.StdoutPipe() - if err != nil { - return nil, fmt.Errorf("stdout pipe: %w", err) - } - pgStderr, err := cmd.StderrPipe() if err != nil { return nil, fmt.Errorf("stderr pipe: %w", err) } - // Capture stderr in a separate goroutine to ensure we don't miss any error output stderrCh := make(chan []byte, 1) go func() { stderrOutput, _ := io.ReadAll(pgStderr) stderrCh <- stderrOutput }() + if err = cmd.Start(); err != nil { + return nil, fmt.Errorf("start %s: %w", filepath.Base(pgBin), err) + } + + waitErr := cmd.Wait() + stderrOutput := <-stderrCh + + select { + case <-ctx.Done(): + return nil, uc.checkCancellationReason() + default: + } + + if waitErr != nil { + if err := uc.checkCancellation(ctx); err != nil { + return nil, err + } + return nil, uc.buildPgDumpErrorMessage(waitErr, stderrOutput, pgBin, args, password) + } + + uc.logger.Info( + "pg_dump completed successfully, streaming TAR to storage", + "outputDir", + outputDir, + ) + + // Stream directory as TAR to storage + return uc.streamDirectoryToStorage( + ctx, + backupID, + backupConfig, + outputDir, + storage, + backupProgressListener, + ) +} + +// streamDirectoryToStorage creates a TAR archive from the directory and streams it to storage +func (uc *CreatePostgresqlBackupUsecase) streamDirectoryToStorage( + ctx context.Context, + backupID uuid.UUID, + backupConfig *backups_config.BackupConfig, + sourceDir string, + storage *storages.Storage, + backupProgressListener func(completedMBs float64), +) (*common.BackupMetadata, error) { storageReader, storageWriter := io.Pipe() finalWriter, encryptionWriter, backupMetadata, err := uc.setupBackupEncryption( @@ -171,162 +226,176 @@ func (uc *CreatePostgresqlBackupUsecase) streamToStorage( return nil, err } - countingWriter := usecases_common.NewCountingWriter(finalWriter) + // Set type to DIRECTORY for new PostgreSQL backups + backupMetadata.Type = common.BackupTypeDirectory - // The backup ID becomes the object key / filename in storage - - // Start streaming into storage in its own goroutine + // Start streaming into storage saveErrCh := make(chan error, 1) go func() { saveErr := storage.SaveFile(ctx, uc.fieldEncryptor, uc.logger, backupID, storageReader) saveErrCh <- saveErr }() - // Start pg_dump - if err = cmd.Start(); err != nil { - return nil, fmt.Errorf("start %s: %w", filepath.Base(pgBin), err) - } - - // Copy pg output directly to storage with shutdown checks - copyResultCh := make(chan error, 1) - bytesWrittenCh := make(chan int64, 1) + // Create TAR and stream to storage + tarErrCh := make(chan error, 1) + totalSizeCh := make(chan int64, 1) go func() { - bytesWritten, err := uc.copyWithShutdownCheck( + totalSize, tarErr := uc.writeTarToWriter( ctx, - countingWriter, - pgStdout, + sourceDir, + finalWriter, backupProgressListener, ) - bytesWrittenCh <- bytesWritten - copyResultCh <- err + totalSizeCh <- totalSize + tarErrCh <- tarErr + + // Close encryption writer first if present + if encryptionWriter != nil { + if closeErr := encryptionWriter.Close(); closeErr != nil { + uc.logger.Error("Failed to close encryption writer", "error", closeErr) + } + } + // Then close the pipe writer to signal EOF to storage + if closeErr := storageWriter.Close(); closeErr != nil { + uc.logger.Error("Failed to close pipe writer", "error", closeErr) + } }() - copyErr := <-copyResultCh - bytesWritten := <-bytesWrittenCh - waitErr := cmd.Wait() + tarErr := <-tarErrCh + totalSize := <-totalSizeCh + saveErr := <-saveErrCh select { case <-ctx.Done(): - uc.cleanupOnCancellation(encryptionWriter, storageWriter, saveErrCh) return nil, uc.checkCancellationReason() default: } - if err := uc.closeWriters(encryptionWriter, storageWriter); err != nil { - <-saveErrCh - return nil, err - } - - saveErr := <-saveErrCh - stderrOutput := <-stderrCh - - // Send final sizing after backup is completed - if waitErr == nil && copyErr == nil && saveErr == nil && backupProgressListener != nil { - sizeMB := float64(bytesWritten) / (1024 * 1024) + // Send final size after backup is completed + if tarErr == nil && saveErr == nil && backupProgressListener != nil { + sizeMB := float64(totalSize) / (1024 * 1024) backupProgressListener(sizeMB) } - switch { - case waitErr != nil: + if tarErr != nil { if err := uc.checkCancellation(ctx); err != nil { return nil, err } - return nil, uc.buildPgDumpErrorMessage(waitErr, stderrOutput, pgBin, args, password) - case copyErr != nil: - if err := uc.checkCancellation(ctx); err != nil { - return nil, err - } - return nil, fmt.Errorf("copy to storage: %w", copyErr) - case saveErr != nil: - if err := uc.checkCancellation(ctx); err != nil { - return nil, err - } - return nil, fmt.Errorf("save to storage: %w", saveErr) + return nil, fmt.Errorf("failed to create TAR archive: %w", tarErr) } + if saveErr != nil { + if err := uc.checkCancellation(ctx); err != nil { + return nil, err + } + return nil, fmt.Errorf("failed to save to storage: %w", saveErr) + } + + uc.logger.Info( + "Backup completed successfully", + "backupId", + backupID, + "totalSizeBytes", + totalSize, + ) return &backupMetadata, nil } -func (uc *CreatePostgresqlBackupUsecase) copyWithShutdownCheck( +// writeTarToWriter creates a TAR archive from sourceDir and writes it to the writer +func (uc *CreatePostgresqlBackupUsecase) writeTarToWriter( ctx context.Context, - dst io.Writer, - src io.Reader, + sourceDir string, + writer io.Writer, backupProgressListener func(completedMBs float64), ) (int64, error) { - buf := make([]byte, copyBufferSize) - var totalBytesWritten int64 + tarWriter := tar.NewWriter(writer) + defer func() { + _ = tarWriter.Close() + }() + + var totalSize int64 var lastReportedMB float64 - for { + err := filepath.Walk(sourceDir, func(path string, info os.FileInfo, walkErr error) error { + if walkErr != nil { + return walkErr + } + select { case <-ctx.Done(): - return totalBytesWritten, fmt.Errorf("copy cancelled: %w", ctx.Err()) + return ctx.Err() default: } if config.IsShouldShutdown() { - return totalBytesWritten, fmt.Errorf("copy cancelled due to shutdown") + return fmt.Errorf("backup cancelled due to shutdown") } - bytesRead, readErr := src.Read(buf) - if bytesRead > 0 { - writeResultCh := make(chan writeResult, 1) - go func() { - bytesWritten, writeErr := dst.Write(buf[0:bytesRead]) - writeResultCh <- writeResult{bytesWritten, writeErr} - }() + // Get relative path for TAR header + relPath, err := filepath.Rel(sourceDir, path) + if err != nil { + return fmt.Errorf("failed to get relative path: %w", err) + } - var bytesWritten int - var writeErr error + // Skip the root directory itself + if relPath == "." { + return nil + } - select { - case <-ctx.Done(): - return totalBytesWritten, fmt.Errorf("copy cancelled during write: %w", ctx.Err()) - case result := <-writeResultCh: - bytesWritten = result.bytesWritten - writeErr = result.writeErr - } + // Create TAR header + header, err := tar.FileInfoHeader(info, "") + if err != nil { + return fmt.Errorf("failed to create TAR header: %w", err) + } + header.Name = relPath - if bytesWritten < 0 || bytesRead < bytesWritten { - bytesWritten = 0 - if writeErr == nil { - writeErr = fmt.Errorf("invalid write result") - } - } + if err := tarWriter.WriteHeader(header); err != nil { + return fmt.Errorf("failed to write TAR header: %w", err) + } - if writeErr != nil { - return totalBytesWritten, writeErr - } + // If it's a directory, we're done + if info.IsDir() { + return nil + } - if bytesRead != bytesWritten { - return totalBytesWritten, io.ErrShortWrite - } + // Copy file content to TAR + file, err := os.Open(path) + if err != nil { + return fmt.Errorf("failed to open file %s: %w", path, err) + } + defer func() { + _ = file.Close() + }() - totalBytesWritten += int64(bytesWritten) + written, err := io.Copy(tarWriter, file) + if err != nil { + return fmt.Errorf("failed to write file %s to TAR: %w", path, err) + } - if backupProgressListener != nil { - currentSizeMB := float64(totalBytesWritten) / (1024 * 1024) - if currentSizeMB >= lastReportedMB+progressReportIntervalMB { - backupProgressListener(currentSizeMB) - lastReportedMB = currentSizeMB - } + totalSize += written + + // Report progress + if backupProgressListener != nil { + currentSizeMB := float64(totalSize) / (1024 * 1024) + if currentSizeMB >= lastReportedMB+progressReportIntervalMB { + backupProgressListener(currentSizeMB) + lastReportedMB = currentSizeMB } } - if readErr != nil { - if readErr != io.EOF { - return totalBytesWritten, readErr - } - break - } - } + return nil + }) - return totalBytesWritten, nil + return totalSize, err } -func (uc *CreatePostgresqlBackupUsecase) buildPgDumpArgs(pg *pgtypes.PostgresqlDatabase) []string { +func (uc *CreatePostgresqlBackupUsecase) buildPgDumpArgs( + pg *pgtypes.PostgresqlDatabase, + outputDir string, +) []string { args := []string{ - "-Fc", + "-Fd", // Directory type (enables parallel dump) + "-f", outputDir, // Output directory "--no-password", "-h", pg.Host, "-p", strconv.Itoa(pg.Port), @@ -335,7 +404,7 @@ func (uc *CreatePostgresqlBackupUsecase) buildPgDumpArgs(pg *pgtypes.PostgresqlD "--verbose", } - // Add parallel jobs based on CPU count + // Parallel jobs now actually work with directory type if pg.CpuCount > 1 { args = append(args, "-j", strconv.Itoa(pg.CpuCount)) } @@ -476,8 +545,8 @@ func (uc *CreatePostgresqlBackupUsecase) setupBackupEncryption( backupID uuid.UUID, backupConfig *backups_config.BackupConfig, storageWriter io.WriteCloser, -) (io.Writer, *backup_encryption.EncryptionWriter, usecases_common.BackupMetadata, error) { - metadata := usecases_common.BackupMetadata{} +) (io.Writer, *backup_encryption.EncryptionWriter, common.BackupMetadata, error) { + metadata := common.BackupMetadata{} if backupConfig.Encryption != backups_config.BackupEncryptionEncrypted { metadata.Encryption = backups_config.BackupEncryptionNone @@ -521,63 +590,6 @@ func (uc *CreatePostgresqlBackupUsecase) setupBackupEncryption( return encWriter, encWriter, metadata, nil } -func (uc *CreatePostgresqlBackupUsecase) cleanupOnCancellation( - encryptionWriter *backup_encryption.EncryptionWriter, - storageWriter io.WriteCloser, - saveErrCh chan error, -) { - if encryptionWriter != nil { - go func() { - if closeErr := encryptionWriter.Close(); closeErr != nil { - uc.logger.Error( - "Failed to close encrypting writer during cancellation", - "error", - closeErr, - ) - } - }() - } - - if err := storageWriter.Close(); err != nil { - uc.logger.Error("Failed to close pipe writer during cancellation", "error", err) - } - - <-saveErrCh -} - -func (uc *CreatePostgresqlBackupUsecase) closeWriters( - encryptionWriter *backup_encryption.EncryptionWriter, - storageWriter io.WriteCloser, -) error { - encryptionCloseErrCh := make(chan error, 1) - if encryptionWriter != nil { - go func() { - closeErr := encryptionWriter.Close() - if closeErr != nil { - uc.logger.Error("Failed to close encrypting writer", "error", closeErr) - } - encryptionCloseErrCh <- closeErr - }() - } else { - encryptionCloseErrCh <- nil - } - - encryptionCloseErr := <-encryptionCloseErrCh - if encryptionCloseErr != nil { - if err := storageWriter.Close(); err != nil { - uc.logger.Error("Failed to close pipe writer after encryption error", "error", err) - } - return fmt.Errorf("failed to close encryption writer: %w", encryptionCloseErr) - } - - if err := storageWriter.Close(); err != nil { - uc.logger.Error("Failed to close pipe writer", "error", err) - return err - } - - return nil -} - func (uc *CreatePostgresqlBackupUsecase) checkCancellation(ctx context.Context) error { select { case <-ctx.Done(): diff --git a/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go b/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go index 0ff81e2..6eaca00 100644 --- a/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go +++ b/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go @@ -1,6 +1,7 @@ package usecases_postgresql import ( + "archive/tar" "context" "encoding/base64" "errors" @@ -16,6 +17,7 @@ import ( "databasus-backend/internal/config" "databasus-backend/internal/features/backups/backups" + common "databasus-backend/internal/features/backups/backups/common" "databasus-backend/internal/features/backups/backups/encryption" backups_config "databasus-backend/internal/features/backups/config" "databasus-backend/internal/features/databases" @@ -54,6 +56,8 @@ func (uc *RestorePostgresqlBackupUsecase) Execute( restore.ID, "backupId", backup.ID, + "format", + backup.Type, ) pg := restoringToDB.Postgresql @@ -65,33 +69,72 @@ func (uc *RestorePostgresqlBackupUsecase) Execute( return fmt.Errorf("target database name is required for pg_restore") } - // Use parallel jobs based on CPU count (same as backup) + pgBin := tools.GetPostgresqlExecutable( + pg.Version, + "pg_restore", + config.GetEnv().EnvMode, + config.GetEnv().PostgresesInstallDir, + ) + + // Route based on backup type + switch backup.Type { + case common.BackupTypeDirectory: + return uc.restoreDirectoryType( + originalDB, + restoringToDB, + pgBin, + backup, + storage, + pg, + isExcludeExtensions, + ) + case common.BackupTypeDefault, "": // empty = legacy DEFAULT + return uc.restoreCustomType( + originalDB, + pgBin, + backup, + storage, + pg, + isExcludeExtensions, + ) + default: + return fmt.Errorf("unsupported backup type: %s", backup.Type) + } +} + +// restoreCustomType restores a backup in custom type (-Fc) - legacy type +func (uc *RestorePostgresqlBackupUsecase) restoreCustomType( + originalDB *databases.Database, + pgBin string, + backup *backups.Backup, + storage *storages.Storage, + pg *pgtypes.PostgresqlDatabase, + isExcludeExtensions bool, +) error { + uc.logger.Info("Restoring backup in custom type (-Fc)", "backupId", backup.ID) + + // Use parallel jobs based on CPU count // Cap between 1 and 8 to avoid overwhelming the server - parallelJobs := max(1, min(restoringToDB.Postgresql.CpuCount, 8)) + parallelJobs := max(1, min(pg.CpuCount, 8)) args := []string{ - "-Fc", // expect custom format (same as backup) + "-Fc", // expect custom type "-j", strconv.Itoa(parallelJobs), // parallel jobs based on CPU count - "--no-password", // Use environment variable for password, prevent prompts + "--no-password", "-h", pg.Host, "-p", strconv.Itoa(pg.Port), "-U", pg.Username, "-d", *pg.Database, - "--verbose", // Add verbose output to help with debugging - "--clean", // Clean (drop) database objects before recreating them - "--if-exists", // Use IF EXISTS when dropping objects - "--no-owner", // Skip restoring ownership - "--no-acl", // Skip restoring access privileges (GRANT/REVOKE commands) + "--verbose", + "--clean", + "--if-exists", + "--no-owner", + "--no-acl", } return uc.restoreFromStorage( originalDB, - tools.GetPostgresqlExecutable( - pg.Version, - "pg_restore", - config.GetEnv().EnvMode, - config.GetEnv().PostgresesInstallDir, - ), + pgBin, args, pg.Password, backup, @@ -101,6 +144,100 @@ func (uc *RestorePostgresqlBackupUsecase) Execute( ) } +// restoreDirectoryType restores a backup in directory type (-Fd) - new TAR type +func (uc *RestorePostgresqlBackupUsecase) restoreDirectoryType( + originalDB *databases.Database, + _ *databases.Database, // restoringToDB not used but kept for API consistency + pgBin string, + backup *backups.Backup, + storage *storages.Storage, + pg *pgtypes.PostgresqlDatabase, + isExcludeExtensions bool, +) error { + uc.logger.Info("Restoring backup in directory type (-Fd)", "backupId", backup.ID) + + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) + defer cancel() + + // Monitor for shutdown + go func() { + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + if config.IsShouldShutdown() { + cancel() + return + } + } + } + }() + + // Create temporary .pgpass file + pgpassFile, err := uc.createTempPgpassFile(pg, pg.Password) + if err != nil { + return fmt.Errorf("failed to create temporary .pgpass file: %w", err) + } + defer func() { + if pgpassFile != "" { + _ = os.RemoveAll(filepath.Dir(pgpassFile)) + } + }() + + // Download and extract TAR to temporary directory + tempDir, cleanupFunc, err := uc.downloadAndExtractTar(ctx, backup, storage) + if err != nil { + return fmt.Errorf("failed to download and extract backup: %w", err) + } + defer cleanupFunc() + + // Use parallel jobs based on CPU count + parallelJobs := max(1, min(pg.CpuCount, 8)) + + args := []string{ + "-Fd", // directory type + "-j", strconv.Itoa(parallelJobs), // parallel restore + "--no-password", + "-h", pg.Host, + "-p", strconv.Itoa(pg.Port), + "-U", pg.Username, + "-d", *pg.Database, + "--verbose", + "--clean", + "--if-exists", + "--no-owner", + "--no-acl", + } + + // If excluding extensions, generate filtered TOC list + if isExcludeExtensions { + tocListFile, err := uc.generateFilteredTocList( + ctx, + pgBin, + tempDir, + pgpassFile, + pg, + ) + if err != nil { + return fmt.Errorf("failed to generate filtered TOC list: %w", err) + } + defer func() { + _ = os.Remove(tocListFile) + }() + + args = append(args, "-L", tocListFile) + } + + // Add the directory as the last argument + args = append(args, tempDir) + + return uc.executePgRestore(ctx, originalDB, pgBin, args, pgpassFile, pg) +} + // restoreFromStorage restores backup data from storage using pg_restore func (uc *RestorePostgresqlBackupUsecase) restoreFromStorage( database *databases.Database, @@ -321,6 +458,175 @@ func (uc *RestorePostgresqlBackupUsecase) downloadBackupToTempFile( return tempBackupFile, cleanupFunc, nil } +// downloadAndExtractTar downloads a TAR backup from storage and extracts it to a temporary directory +func (uc *RestorePostgresqlBackupUsecase) downloadAndExtractTar( + ctx context.Context, + backup *backups.Backup, + storage *storages.Storage, +) (string, func(), error) { + err := files_utils.EnsureDirectories([]string{config.GetEnv().TempFolder}) + if err != nil { + return "", nil, fmt.Errorf("failed to ensure directories: %w", err) + } + + // Create temporary directory for extracted data + tempDir, err := os.MkdirTemp(config.GetEnv().TempFolder, "restore_dir_"+uuid.New().String()) + if err != nil { + return "", nil, fmt.Errorf("failed to create temporary directory: %w", err) + } + + cleanupFunc := func() { + _ = os.RemoveAll(tempDir) + } + + uc.logger.Info( + "Downloading TAR backup from storage", + "backupId", backup.ID, + "tempDir", tempDir, + "encrypted", backup.Encryption == backups_config.BackupEncryptionEncrypted, + ) + + fieldEncryptor := util_encryption.GetFieldEncryptor() + rawReader, err := storage.GetFile(fieldEncryptor, backup.ID) + if err != nil { + cleanupFunc() + return "", nil, fmt.Errorf("failed to get backup file from storage: %w", err) + } + defer func() { + if err := rawReader.Close(); err != nil { + uc.logger.Error("Failed to close backup reader", "error", err) + } + }() + + // Create a reader that handles decryption if needed + var backupReader io.Reader = rawReader + if backup.Encryption == backups_config.BackupEncryptionEncrypted { + if backup.EncryptionSalt == nil || backup.EncryptionIV == nil { + cleanupFunc() + return "", nil, fmt.Errorf("backup is encrypted but missing encryption metadata") + } + + masterKey, err := uc.secretKeyService.GetSecretKey() + if err != nil { + cleanupFunc() + return "", nil, fmt.Errorf("failed to get master key for decryption: %w", err) + } + + salt, err := base64.StdEncoding.DecodeString(*backup.EncryptionSalt) + if err != nil { + cleanupFunc() + return "", nil, fmt.Errorf("failed to decode encryption salt: %w", err) + } + + iv, err := base64.StdEncoding.DecodeString(*backup.EncryptionIV) + if err != nil { + cleanupFunc() + return "", nil, fmt.Errorf("failed to decode encryption IV: %w", err) + } + + decryptReader, err := encryption.NewDecryptionReader( + rawReader, + masterKey, + backup.ID, + salt, + iv, + ) + if err != nil { + cleanupFunc() + return "", nil, fmt.Errorf("failed to create decryption reader: %w", err) + } + + backupReader = decryptReader + uc.logger.Info("Using decryption for encrypted backup", "backupId", backup.ID) + } + + // Extract TAR archive to temp directory + if err := uc.extractTar(ctx, backupReader, tempDir); err != nil { + cleanupFunc() + return "", nil, fmt.Errorf("failed to extract TAR archive: %w", err) + } + + uc.logger.Info("TAR backup extracted to temporary directory", "tempDir", tempDir) + return tempDir, cleanupFunc, nil +} + +// extractTar extracts a TAR archive to the specified directory +func (uc *RestorePostgresqlBackupUsecase) extractTar( + ctx context.Context, + reader io.Reader, + destDir string, +) error { + tarReader := tar.NewReader(reader) + + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + if config.IsShouldShutdown() { + return fmt.Errorf("extraction cancelled due to shutdown") + } + + header, err := tarReader.Next() + if err == io.EOF { + break + } + if err != nil { + return fmt.Errorf("failed to read TAR header: %w", err) + } + + targetPath := filepath.Join(destDir, header.Name) + + // Ensure the target path is within destDir (prevent path traversal) + if !strings.HasPrefix(filepath.Clean(targetPath), filepath.Clean(destDir)) { + return fmt.Errorf("invalid file path in TAR: %s", header.Name) + } + + switch header.Typeflag { + case tar.TypeDir: + if err := os.MkdirAll(targetPath, os.FileMode(header.Mode)); err != nil { + return fmt.Errorf("failed to create directory %s: %w", targetPath, err) + } + case tar.TypeReg: + // Ensure parent directory exists + if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil { + return fmt.Errorf("failed to create parent directory for %s: %w", targetPath, err) + } + + outFile, err := os.OpenFile( + targetPath, + os.O_CREATE|os.O_WRONLY|os.O_TRUNC, + os.FileMode(header.Mode), + ) + if err != nil { + return fmt.Errorf("failed to create file %s: %w", targetPath, err) + } + + _, copyErr := uc.copyWithShutdownCheck(ctx, outFile, tarReader) + closeErr := outFile.Close() + + if copyErr != nil { + return fmt.Errorf("failed to write file %s: %w", targetPath, copyErr) + } + if closeErr != nil { + return fmt.Errorf("failed to close file %s: %w", targetPath, closeErr) + } + default: + uc.logger.Warn( + "Skipping unsupported TAR entry type", + "type", + header.Typeflag, + "name", + header.Name, + ) + } + } + + return nil +} + // executePgRestore executes the pg_restore command with proper environment setup func (uc *RestorePostgresqlBackupUsecase) executePgRestore( ctx context.Context, diff --git a/backend/migrations/20260101200438_add_backup_format.sql b/backend/migrations/20260101200438_add_backup_format.sql new file mode 100644 index 0000000..3fa8039 --- /dev/null +++ b/backend/migrations/20260101200438_add_backup_format.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE backups ADD COLUMN type TEXT NOT NULL DEFAULT 'DEFAULT'; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE backups DROP COLUMN type; +-- +goose StatementEnd diff --git a/frontend/src/entity/backups/api/backupsApi.ts b/frontend/src/entity/backups/api/backupsApi.ts index 5c3da5f..0bf7c38 100644 --- a/frontend/src/entity/backups/api/backupsApi.ts +++ b/frontend/src/entity/backups/api/backupsApi.ts @@ -29,8 +29,23 @@ export const backupsApi = { return apiHelper.fetchDeleteRaw(`${getApplicationServer()}/api/v1/backups/${id}`); }, - async downloadBackup(id: string): Promise { - return apiHelper.fetchGetBlob(`${getApplicationServer()}/api/v1/backups/${id}/file`); + async downloadBackup(id: string): Promise<{ blob: Blob; filename: string }> { + const result = await apiHelper.fetchGetBlobWithHeaders( + `${getApplicationServer()}/api/v1/backups/${id}/file`, + ); + + // Extract filename from Content-Disposition header + const contentDisposition = result.headers.get('Content-Disposition'); + let filename = `backup_${id}.backup`; // fallback filename + + if (contentDisposition) { + const filenameMatch = contentDisposition.match(/filename="?(.+?)"?$/); + if (filenameMatch && filenameMatch[1]) { + filename = filenameMatch[1]; + } + } + + return { blob: result.blob, filename }; }, async cancelBackup(id: string) { diff --git a/frontend/src/entity/databases/model/mongodb/MongodbDatabase.ts b/frontend/src/entity/databases/model/mongodb/MongodbDatabase.ts index ddc1f25..6981fd2 100644 --- a/frontend/src/entity/databases/model/mongodb/MongodbDatabase.ts +++ b/frontend/src/entity/databases/model/mongodb/MongodbDatabase.ts @@ -9,6 +9,6 @@ export interface MongodbDatabase { password: string; database: string; authDatabase: string; - useTls: boolean; + isHttps: boolean; cpuCount: number; } diff --git a/frontend/src/features/backups/ui/BackupsComponent.tsx b/frontend/src/features/backups/ui/BackupsComponent.tsx index 93d0216..f996a46 100644 --- a/frontend/src/features/backups/ui/BackupsComponent.tsx +++ b/frontend/src/features/backups/ui/BackupsComponent.tsx @@ -64,21 +64,13 @@ export const BackupsComponent = ({ database, isCanManageDBs, scrollContainerRef const downloadBackup = async (backupId: string) => { try { - const blob = await backupsApi.downloadBackup(backupId); + const { blob, filename } = await backupsApi.downloadBackup(backupId); // Create a download link const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; - - // Find the backup to get a meaningful filename - const backup = backups.find((b) => b.id === backupId); - const createdAt = backup ? dayjs(backup.createdAt).format('YYYY-MM-DD_HH-mm-ss') : 'backup'; - const extension = - database.type === DatabaseType.MYSQL || database.type === DatabaseType.MARIADB - ? '.sql.zst' - : '.dump'; - link.download = `${database.name}_backup_${createdAt}${extension}`; + link.download = filename; // Trigger download document.body.appendChild(link); diff --git a/frontend/src/features/databases/ui/CreateDatabaseComponent.tsx b/frontend/src/features/databases/ui/CreateDatabaseComponent.tsx index b6835aa..033ae03 100644 --- a/frontend/src/features/databases/ui/CreateDatabaseComponent.tsx +++ b/frontend/src/features/databases/ui/CreateDatabaseComponent.tsx @@ -50,13 +50,13 @@ const initializeDatabaseTypeData = (db: Database): Database => { switch (db.type) { case DatabaseType.POSTGRES: - return { ...base, postgresql: db.postgresql ?? ({ cpuCount: 1 } as PostgresqlDatabase) }; + return { ...base, postgresql: db.postgresql ?? ({ cpuCount: 4 } as PostgresqlDatabase) }; case DatabaseType.MYSQL: return { ...base, mysql: db.mysql ?? ({} as MysqlDatabase) }; case DatabaseType.MARIADB: return { ...base, mariadb: db.mariadb ?? ({} as MariadbDatabase) }; case DatabaseType.MONGODB: - return { ...base, mongodb: db.mongodb ?? ({ cpuCount: 1 } as MongodbDatabase) }; + return { ...base, mongodb: db.mongodb ?? ({ cpuCount: 4 } as MongodbDatabase) }; default: return db; } diff --git a/frontend/src/features/databases/ui/edit/EditMongoDbSpecificDataComponent.tsx b/frontend/src/features/databases/ui/edit/EditMongoDbSpecificDataComponent.tsx index b7b216d..bbcfdde 100644 --- a/frontend/src/features/databases/ui/edit/EditMongoDbSpecificDataComponent.tsx +++ b/frontend/src/features/databases/ui/edit/EditMongoDbSpecificDataComponent.tsx @@ -77,8 +77,8 @@ export const EditMongoDbSpecificDataComponent = ({ password: result.password, database: result.database, authDatabase: result.authDatabase, - useTls: result.useTls, - cpuCount: 1, + isHttps: result.useTls, + cpuCount: 4, }, }; @@ -287,15 +287,15 @@ export const EditMongoDbSpecificDataComponent = ({ )}
-
Use TLS
+
Use HTTPS
{ if (!editingDatabase.mongodb) return; setEditingDatabase({ ...editingDatabase, - mongodb: { ...editingDatabase.mongodb, useTls: checked }, + mongodb: { ...editingDatabase.mongodb, isHttps: checked }, }); setIsConnectionTested(false); }} @@ -309,7 +309,7 @@ export const EditMongoDbSpecificDataComponent = ({ { if (!editingDatabase.mongodb) return; diff --git a/frontend/src/features/databases/ui/edit/EditPostgreSqlSpecificDataComponent.tsx b/frontend/src/features/databases/ui/edit/EditPostgreSqlSpecificDataComponent.tsx index 81183cb..eae5376 100644 --- a/frontend/src/features/databases/ui/edit/EditPostgreSqlSpecificDataComponent.tsx +++ b/frontend/src/features/databases/ui/edit/EditPostgreSqlSpecificDataComponent.tsx @@ -82,7 +82,7 @@ export const EditPostgreSqlSpecificDataComponent = ({ password: result.password, database: result.database, isHttps: result.isHttps, - cpuCount: 1, + cpuCount: 4, }, }; @@ -356,36 +356,34 @@ export const EditPostgreSqlSpecificDataComponent = ({ />
- {isRestoreMode && ( -
-
CPU count
-
- { - if (!editingDatabase.postgresql) return; +
+
CPU count
+
+ { + if (!editingDatabase.postgresql) return; - setEditingDatabase({ - ...editingDatabase, - postgresql: { ...editingDatabase.postgresql, cpuCount: value || 1 }, - }); - setIsConnectionTested(false); - }} - size="small" - className="max-w-[75px] grow" - /> + setEditingDatabase({ + ...editingDatabase, + postgresql: { ...editingDatabase.postgresql, cpuCount: value || 1 }, + }); + setIsConnectionTested(false); + }} + size="small" + className="max-w-[75px] grow" + /> - - - -
+ + +
- )} +
{
-
Use TLS
-
{database.mongodb?.useTls ? 'Yes' : 'No'}
+
Use HTTPS
+
{database.mongodb?.isHttps ? 'Yes' : 'No'}
+
+ +
+
CPU count
+
{database.mongodb?.cpuCount}
{database.mongodb?.authDatabase && ( @@ -43,11 +48,6 @@ export const ShowMongoDbSpecificDataComponent = ({ database }: Props) => {
{database.mongodb.authDatabase}
)} - -
-
CPU count
-
{database.mongodb?.cpuCount || 1}
-
); }; diff --git a/frontend/src/features/databases/ui/show/ShowPostgreSqlSpecificDataComponent.tsx b/frontend/src/features/databases/ui/show/ShowPostgreSqlSpecificDataComponent.tsx index 8ea247d..8c58714 100644 --- a/frontend/src/features/databases/ui/show/ShowPostgreSqlSpecificDataComponent.tsx +++ b/frontend/src/features/databases/ui/show/ShowPostgreSqlSpecificDataComponent.tsx @@ -54,6 +54,11 @@ export const ShowPostgreSqlSpecificDataComponent = ({ database }: Props) => {
{database.postgresql?.isHttps ? 'Yes' : 'No'}
+
+
CPU count
+
{database.postgresql?.cpuCount}
+
+ {!!database.postgresql?.includeSchemas?.length && (
Include schemas
diff --git a/frontend/src/shared/api/apiHelper.ts b/frontend/src/shared/api/apiHelper.ts index 0481326..8cfa34a 100644 --- a/frontend/src/shared/api/apiHelper.ts +++ b/frontend/src/shared/api/apiHelper.ts @@ -176,6 +176,25 @@ export const apiHelper = { return response.blob(); }, + fetchGetBlobWithHeaders: async ( + url: string, + requestOptions?: RequestOptions, + isRetryOnError = false, + ): Promise<{ blob: Blob; headers: Headers }> => { + const optionsWrapper = (requestOptions ?? new RequestOptions()) + .addHeader('Access-Control-Allow-Methods', 'GET') + .addHeader('Authorization', accessTokenHelper.getAccessToken()); + + const response = await makeRequest( + url, + optionsWrapper, + isRetryOnError ? 0 : REPEAT_TRIES_COUNT, + ); + + const blob = await response.blob(); + return { blob, headers: response.headers }; + }, + fetchPutJson: async ( url: string, requestOptions?: RequestOptions, From 75b0477874f5048f852d83f0138998353a1ccc69 Mon Sep 17 00:00:00 2001 From: Rostislav Dugin Date: Fri, 2 Jan 2026 11:49:26 +0300 Subject: [PATCH 2/6] FIX (temp): Remove temp directory for NAS --- .../26b1ae2f-cf0e-4508-8a00-6c8b0c11da0e | Bin 79702 -> 0 bytes .../8c6e587a-0eae-44c1-b82c-e5a8db2b3642 | 1 - .../0b8633ac-de51-4eb8-bd4e-2bc4e8de4c86 | Bin 79601 -> 0 bytes .../86d3f727-2a60-4a86-af86-31657237d21c | 1 - .../c9ef162b-71c8-462c-aee2-5aa845e604a1 | Bin 79659 -> 0 bytes 5 files changed, 2 deletions(-) delete mode 100644 backend/temp/nas/.deleted/test-files/26b1ae2f-cf0e-4508-8a00-6c8b0c11da0e delete mode 100644 backend/temp/nas/.deleted/test-files/8c6e587a-0eae-44c1-b82c-e5a8db2b3642 delete mode 100644 backend/temp/nas/test-files/0b8633ac-de51-4eb8-bd4e-2bc4e8de4c86 delete mode 100644 backend/temp/nas/test-files/86d3f727-2a60-4a86-af86-31657237d21c delete mode 100644 backend/temp/nas/test-files/c9ef162b-71c8-462c-aee2-5aa845e604a1 diff --git a/backend/temp/nas/.deleted/test-files/26b1ae2f-cf0e-4508-8a00-6c8b0c11da0e b/backend/temp/nas/.deleted/test-files/26b1ae2f-cf0e-4508-8a00-6c8b0c11da0e deleted file mode 100644 index 2d63a4fb17e89eb33e937d49e66dbae5a2675811..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79702 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 z 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 diff --git a/backend/temp/nas/.deleted/test-files/8c6e587a-0eae-44c1-b82c-e5a8db2b3642 b/backend/temp/nas/.deleted/test-files/8c6e587a-0eae-44c1-b82c-e5a8db2b3642 deleted file mode 100644 index 712670e..0000000 --- a/backend/temp/nas/.deleted/test-files/8c6e587a-0eae-44c1-b82c-e5a8db2b3642 +++ /dev/null @@ -1 +0,0 @@ -This is test data for storage testing \ No newline at end of file diff --git a/backend/temp/nas/test-files/0b8633ac-de51-4eb8-bd4e-2bc4e8de4c86 b/backend/temp/nas/test-files/0b8633ac-de51-4eb8-bd4e-2bc4e8de4c86 deleted file mode 100644 index 4cda233af23d6eefbc7fff381eaae7e544bbca61..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79601 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 z±GA1GR)d%=VeeYu`j4(Qc(W zG~QtEv+k%lpPGejFXyyL3(E&Ib$NnVj`*fsMX5@hr~RW=&~Z0kBC90_r{la22mz?L zgh6;UVN|9&y#HI`NB@>M=-s?BB}drMds$IMrk7iyRCjsY zK}b}$%t|NSDkTw9PqWdd$A<}bE2SS#Ht0Xc&J6(^Z58yVmPR-m!Xzc8 z{O0q3)GTnnx&z$SjfS4Z_Ngv9j~*_OVr3$)A5&a=15bgCdWhQyLD*CS|MlSXUl0D7 zS7y}o?#>Q00D1sW@?Ybhe+_2;Ya|(#NB{S_Pjjvn>J|(0L=p89Vk@#X_8;4=jc1HW zY&;=3<8vQ8elwAaG0ESuE|~ZRjIO0)vT=q&S+?a?Djv(?SGWg|D%NK0b_NOp~7@``Kn6cziWp-IJdlgsAc%J$@G$*S(m!d3(a;-uQ>(1WZ_x~S_8%$)>|1co`FdqJg zku=_8+sBF;xb1rNQP2?qZ8`b$UVA|eKJT0R$rt=&#w1blsU*R-cp*^)+6Tyt847>4IS~;fsHl3}{MH;F8N@mKiWD;Q_@AaC`nX6Iq}pmzlfK^MDCIODMEzo`Yf7_d+QPZ_&?UI zf2_a%$NCLCi5$#I4A`pk{f@q2dfn9WaGv-nR7Of7{MRTKC!_mMJyGRN${PdW%GFeu z0&Pi>J^`qhLPRKbI6*hnQXd>&mbxVq0!T*vna~)~?uqqSf;GFhP=XMg(XBjrrMtgn zPU-M^rpW2O_>(ptO$a#K&Gs*sfZWD`RhXb?OTvm8y6EkxYWkuBVwo0eJCSO%P-~ST z$MAVU_JL&?NBkNu6_VBPL`rg=Ou3h@N4_-_h>(^!PGF$Hvp{{H3h;W*7nCiB;Nu28Yh3aaXP@86hUnu>c(UI?RUjCOLNYo8Y^*W^h2bOJLQdR(!+Mw7w*TzPu%e-me)peDA-c3?$o zM7h4B1H)$=2^Mr2dqct6K773bgbmee?BmU-Z5FxXs9-obdWbh4&vme?t|}GnnVqn6 z^_373pVkBUv`J>?wv2P_@fvUC`N)@_L_Lftc3K*|1=yG))@#Z|0!=>_h+zctz;yms zbnqX_k+F0t943!>Z7CDX$cl5`3+9MWe=*^Xspn*fIDPaOu+Y`1Y51i&6T}nWZN};9 skQ5srh!8D0h}se@a`b~C`k=e<`aOm=r8hXaIJA3-F=42{^uLMtKX(^kyZ`_I diff --git a/backend/temp/nas/test-files/86d3f727-2a60-4a86-af86-31657237d21c b/backend/temp/nas/test-files/86d3f727-2a60-4a86-af86-31657237d21c deleted file mode 100644 index 712670e..0000000 --- a/backend/temp/nas/test-files/86d3f727-2a60-4a86-af86-31657237d21c +++ /dev/null @@ -1 +0,0 @@ -This is test data for storage testing \ No newline at end of file diff --git a/backend/temp/nas/test-files/c9ef162b-71c8-462c-aee2-5aa845e604a1 b/backend/temp/nas/test-files/c9ef162b-71c8-462c-aee2-5aa845e604a1 deleted file mode 100644 index 7c853c20d3819e121829be7fcb49543155840bd2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79659 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 AtpET3 From 1b8d783d4e0b768344ead05ad0d8660a28cc8c18 Mon Sep 17 00:00:00 2001 From: Rostislav Dugin Date: Fri, 2 Jan 2026 11:50:08 +0300 Subject: [PATCH 3/6] FIX (temp): Add NAS temp directory to .gitignore --- backend/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/.gitignore b/backend/.gitignore index 719b706..6de24f3 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -16,4 +16,5 @@ databasus-backend.exe ui/build/* pgdata-for-restore/ temp/ -cmd.exe \ No newline at end of file +cmd.exe +temp/ \ No newline at end of file From ca3f0281a337afdfff466aba4fff6841204cb1bb Mon Sep 17 00:00:00 2001 From: Rostislav Dugin Date: Fri, 2 Jan 2026 12:09:43 +0300 Subject: [PATCH 4/6] FIX (temp folders): Improve temp folders cleanup over backups and restores --- .../backups/backups/usecases/mariadb/create_backup_uc.go | 2 +- .../backups/backups/usecases/mysql/create_backup_uc.go | 2 +- .../backups/backups/usecases/postgresql/create_backup_uc.go | 2 +- .../features/restores/usecases/mariadb/restore_backup_uc.go | 2 +- .../features/restores/usecases/mysql/restore_backup_uc.go | 2 +- .../restores/usecases/postgresql/restore_backup_uc.go | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go b/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go index 16c74cc..4d3cf97 100644 --- a/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go +++ b/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go @@ -264,7 +264,7 @@ func (uc *CreateMariadbBackupUsecase) createTempMyCnfFile( mdbConfig *mariadbtypes.MariadbDatabase, password string, ) (string, error) { - tempDir, err := os.MkdirTemp("", "mycnf") + tempDir, err := os.MkdirTemp(config.GetEnv().TempFolder, "mycnf_"+uuid.New().String()) if err != nil { return "", fmt.Errorf("failed to create temp directory: %w", err) } diff --git a/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go b/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go index d912c40..293c8c2 100644 --- a/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go +++ b/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go @@ -279,7 +279,7 @@ func (uc *CreateMysqlBackupUsecase) createTempMyCnfFile( myConfig *mysqltypes.MysqlDatabase, password string, ) (string, error) { - tempDir, err := os.MkdirTemp("", "mycnf") + tempDir, err := os.MkdirTemp(config.GetEnv().TempFolder, "mycnf_"+uuid.New().String()) if err != nil { return "", fmt.Errorf("failed to create temp directory: %w", err) } diff --git a/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go b/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go index 9e5880b..b59e95e 100644 --- a/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go +++ b/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go @@ -771,7 +771,7 @@ func (uc *CreatePostgresqlBackupUsecase) createTempPgpassFile( escapedPassword, ) - tempDir, err := os.MkdirTemp("", "pgpass") + tempDir, err := os.MkdirTemp(config.GetEnv().TempFolder, "pgpass_"+uuid.New().String()) if err != nil { return "", fmt.Errorf("failed to create temporary directory: %w", err) } diff --git a/backend/internal/features/restores/usecases/mariadb/restore_backup_uc.go b/backend/internal/features/restores/usecases/mariadb/restore_backup_uc.go index 31feef9..b5775f9 100644 --- a/backend/internal/features/restores/usecases/mariadb/restore_backup_uc.go +++ b/backend/internal/features/restores/usecases/mariadb/restore_backup_uc.go @@ -330,7 +330,7 @@ func (uc *RestoreMariadbBackupUsecase) createTempMyCnfFile( mdbConfig *mariadbtypes.MariadbDatabase, password string, ) (string, error) { - tempDir, err := os.MkdirTemp("", "mycnf") + tempDir, err := os.MkdirTemp(config.GetEnv().TempFolder, "mycnf_"+uuid.New().String()) if err != nil { return "", fmt.Errorf("failed to create temp directory: %w", err) } diff --git a/backend/internal/features/restores/usecases/mysql/restore_backup_uc.go b/backend/internal/features/restores/usecases/mysql/restore_backup_uc.go index a720d3d..8205660 100644 --- a/backend/internal/features/restores/usecases/mysql/restore_backup_uc.go +++ b/backend/internal/features/restores/usecases/mysql/restore_backup_uc.go @@ -322,7 +322,7 @@ func (uc *RestoreMysqlBackupUsecase) createTempMyCnfFile( myConfig *mysqltypes.MysqlDatabase, password string, ) (string, error) { - tempDir, err := os.MkdirTemp("", "mycnf") + tempDir, err := os.MkdirTemp(config.GetEnv().TempFolder, "mycnf_"+uuid.New().String()) if err != nil { return "", fmt.Errorf("failed to create temp directory: %w", err) } diff --git a/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go b/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go index 6eaca00..642e6a1 100644 --- a/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go +++ b/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go @@ -287,7 +287,7 @@ func (uc *RestorePostgresqlBackupUsecase) restoreFromStorage( } defer func() { if pgpassFile != "" { - _ = os.Remove(pgpassFile) + _ = os.RemoveAll(filepath.Dir(pgpassFile)) } }() @@ -927,7 +927,7 @@ func (uc *RestorePostgresqlBackupUsecase) generateFilteredTocList( } // Write filtered TOC to temporary file - tocFile, err := os.CreateTemp("", "pg_restore_toc_*.list") + tocFile, err := os.CreateTemp(config.GetEnv().TempFolder, "pg_restore_toc_*.list") if err != nil { return "", fmt.Errorf("failed to create TOC list file: %w", err) } @@ -974,7 +974,7 @@ func (uc *RestorePostgresqlBackupUsecase) createTempPgpassFile( escapedPassword, ) - tempDir, err := os.MkdirTemp("", "pgpass") + tempDir, err := os.MkdirTemp(config.GetEnv().TempFolder, "pgpass_"+uuid.New().String()) if err != nil { return "", fmt.Errorf("failed to create temporary directory: %w", err) } From b9668875ef07b0364abcabbc52be39fef57d5f03 Mon Sep 17 00:00:00 2001 From: Rostislav Dugin Date: Fri, 2 Jan 2026 12:21:02 +0300 Subject: [PATCH 5/6] FIX (mongodb): Fix MongoDB build for ARM --- Dockerfile | 18 +++++++++++------- assets/tools/README.md | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 assets/tools/README.md diff --git a/Dockerfile b/Dockerfile index 31b6ec5..990ffbb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -172,19 +172,23 @@ RUN if [ "$TARGETARCH" = "amd64" ]; then \ # ========= Install MongoDB Database Tools ========= # Note: MongoDB Database Tools are backward compatible - single version supports all server versions (4.0-8.0) -# Use dpkg with apt-get -f install to handle dependencies +# Note: For ARM64, we use Ubuntu 22.04 package as MongoDB doesn't provide Debian 12 ARM64 packages RUN apt-get update && \ if [ "$TARGETARCH" = "amd64" ]; then \ wget -q https://fastdl.mongodb.org/tools/db/mongodb-database-tools-debian12-x86_64-100.10.0.deb -O /tmp/mongodb-database-tools.deb; \ elif [ "$TARGETARCH" = "arm64" ]; then \ - wget -q https://fastdl.mongodb.org/tools/db/mongodb-database-tools-debian12-aarch64-100.10.0.deb -O /tmp/mongodb-database-tools.deb; \ + wget -q https://fastdl.mongodb.org/tools/db/mongodb-database-tools-ubuntu2204-arm64-100.10.0.deb -O /tmp/mongodb-database-tools.deb; \ fi && \ - dpkg -i /tmp/mongodb-database-tools.deb || true && \ - apt-get install -f -y --no-install-recommends && \ - rm /tmp/mongodb-database-tools.deb && \ + dpkg -i /tmp/mongodb-database-tools.deb || apt-get install -f -y --no-install-recommends && \ + rm -f /tmp/mongodb-database-tools.deb && \ rm -rf /var/lib/apt/lists/* && \ - ln -sf /usr/bin/mongodump /usr/local/mongodb-database-tools/bin/mongodump && \ - ln -sf /usr/bin/mongorestore /usr/local/mongodb-database-tools/bin/mongorestore + mkdir -p /usr/local/mongodb-database-tools/bin && \ + if [ -f /usr/bin/mongodump ]; then \ + ln -sf /usr/bin/mongodump /usr/local/mongodb-database-tools/bin/mongodump; \ + fi && \ + if [ -f /usr/bin/mongorestore ]; then \ + ln -sf /usr/bin/mongorestore /usr/local/mongodb-database-tools/bin/mongorestore; \ + fi # Create postgres user and set up directories RUN useradd -m -s /bin/bash postgres || true && \ diff --git a/assets/tools/README.md b/assets/tools/README.md new file mode 100644 index 0000000..2d2f7a8 --- /dev/null +++ b/assets/tools/README.md @@ -0,0 +1,17 @@ +We keep binaries here to speed up CI \ CD tasks and building. + +Docker image needs: +- PostgreSQL client tools (versions 12-18) +- MySQL client tools (versions 5.7, 8.0, 8.4, 9) +- MariaDB client tools (versions 10.6, 12.1) +- MongoDB Database Tools (latest) + +For the most of tools, we need a couple of binaries for each version. However, if we download them on each run, it will download a couple of GBs each time. + +So, for speed up we keep only required executables (like pg_dump, mysqldump, mariadb-dump, mongodump, etc.). + +It takes: +- ~ 100MB for ARM +- ~ 100MB for x64 + +Instead of GBs. See Dockefile for usage details. \ No newline at end of file From 205cb1ec0200d57676c0f17c0d9cf6e9d8c1eb41 Mon Sep 17 00:00:00 2001 From: Rostislav Dugin Date: Fri, 2 Jan 2026 12:33:31 +0300 Subject: [PATCH 6/6] FEATURE (restores): Validate there is enough disk space on restore --- backend/internal/features/disk/service.go | 10 ++++- .../features/restores/controller_test.go | 38 ++++++++++++++++ backend/internal/features/restores/di.go | 2 + backend/internal/features/restores/service.go | 45 +++++++++++++++++++ 4 files changed, 93 insertions(+), 2 deletions(-) diff --git a/backend/internal/features/disk/service.go b/backend/internal/features/disk/service.go index bc05360..fdd40eb 100644 --- a/backend/internal/features/disk/service.go +++ b/backend/internal/features/disk/service.go @@ -1,7 +1,9 @@ package disk import ( + "databasus-backend/internal/config" "fmt" + "path/filepath" "runtime" "github.com/shirou/gopsutil/v4/disk" @@ -12,10 +14,14 @@ type DiskService struct{} func (s *DiskService) GetDiskUsage() (*DiskUsage, error) { platform := s.detectPlatform() - // Set path based on platform - path := "/" + var path string + if platform == PlatformWindows { path = "C:\\" + } else { + // Use databasus-data folder location for Linux (Docker) + cfg := config.GetEnv() + path = filepath.Dir(cfg.DataFolder) // Gets /databasus-data from /databasus-data/backups } diskUsage, err := disk.Usage(path) diff --git a/backend/internal/features/restores/controller_test.go b/backend/internal/features/restores/controller_test.go index 693fea0..27b48c7 100644 --- a/backend/internal/features/restores/controller_test.go +++ b/backend/internal/features/restores/controller_test.go @@ -250,6 +250,44 @@ func Test_RestoreBackup_AuditLogWritten(t *testing.T) { assert.True(t, found, "Audit log for restore not found") } +func Test_RestoreBackup_InsufficientDiskSpace_ReturnsError(t *testing.T) { + router := createTestRouter() + owner := users_testing.CreateTestUser(users_enums.UserRoleMember) + workspace := workspaces_testing.CreateTestWorkspace("Test Workspace", owner, router) + + _, backup := createTestDatabaseWithBackupForRestore(workspace, owner, router) + + // Update backup size to 10 TB via repository + repo := &backups.BackupRepository{} + backup.BackupSizeMb = 10485760.0 // 10 TB in MB + err := repo.Save(backup) + assert.NoError(t, err) + + request := RestoreBackupRequest{ + PostgresqlDatabase: &postgresql.PostgresqlDatabase{ + Version: tools.PostgresqlVersion16, + Host: "localhost", + Port: 5432, + Username: "postgres", + Password: "postgres", + }, + } + + testResp := test_utils.MakePostRequest( + t, + router, + fmt.Sprintf("/api/v1/restores/%s/restore", backup.ID.String()), + "Bearer "+owner.Token, + request, + http.StatusBadRequest, + ) + + bodyStr := string(testResp.Body) + assert.Contains(t, bodyStr, "is required") + assert.Contains(t, bodyStr, "is available") + assert.Contains(t, bodyStr, "disk space") +} + func createTestDatabaseWithBackupForRestore( workspace *workspaces_models.Workspace, owner *users_dto.SignInResponseDTO, diff --git a/backend/internal/features/restores/di.go b/backend/internal/features/restores/di.go index 774104f..7bafd87 100644 --- a/backend/internal/features/restores/di.go +++ b/backend/internal/features/restores/di.go @@ -5,6 +5,7 @@ import ( "databasus-backend/internal/features/backups/backups" backups_config "databasus-backend/internal/features/backups/config" "databasus-backend/internal/features/databases" + "databasus-backend/internal/features/disk" "databasus-backend/internal/features/restores/usecases" "databasus-backend/internal/features/storages" workspaces_services "databasus-backend/internal/features/workspaces/services" @@ -24,6 +25,7 @@ var restoreService = &RestoreService{ workspaces_services.GetWorkspaceService(), audit_logs.GetAuditLogService(), encryption.GetFieldEncryptor(), + disk.GetDiskService(), } var restoreController = &RestoreController{ restoreService, diff --git a/backend/internal/features/restores/service.go b/backend/internal/features/restores/service.go index 22fbf0f..d7ef4a1 100644 --- a/backend/internal/features/restores/service.go +++ b/backend/internal/features/restores/service.go @@ -5,6 +5,7 @@ import ( "databasus-backend/internal/features/backups/backups" backups_config "databasus-backend/internal/features/backups/config" "databasus-backend/internal/features/databases" + "databasus-backend/internal/features/disk" "databasus-backend/internal/features/restores/enums" "databasus-backend/internal/features/restores/models" "databasus-backend/internal/features/restores/usecases" @@ -32,6 +33,7 @@ type RestoreService struct { workspaceService *workspaces_services.WorkspaceService auditLogService *audit_logs.AuditLogService fieldEncryptor encryption.FieldEncryptor + diskService *disk.DiskService } func (s *RestoreService) OnBeforeBackupRemove(backup *backups.Backup) error { @@ -126,6 +128,11 @@ func (s *RestoreService) RestoreBackupWithAuth( return err } + // Validate disk space before starting restore + if err := s.validateDiskSpace(backup); err != nil { + return err + } + go func() { if err := s.RestoreBackup(backup, requestDTO); err != nil { s.logger.Error("Failed to restore backup", "error", err) @@ -361,3 +368,41 @@ func (s *RestoreService) validateVersionCompatibility( } return nil } + +func (s *RestoreService) validateDiskSpace(backup *backups.Backup) error { + diskUsage, err := s.diskService.GetDiskUsage() + if err != nil { + return fmt.Errorf("failed to check disk space: %w", err) + } + + // Convert backup size from MB to bytes + backupSizeBytes := int64(backup.BackupSizeMb * 1024 * 1024) + + // Calculate required space: backup size + 10% buffer + bufferBytes := int64(float64(backupSizeBytes) * 0.1) + requiredBytes := backupSizeBytes + bufferBytes + + // Ensure minimum of 1 GB total (even if backup is small) + minRequiredBytes := int64(1024 * 1024 * 1024) // 1 GB + if requiredBytes < minRequiredBytes { + requiredBytes = minRequiredBytes + } + + // Check if there's enough free space + if diskUsage.FreeSpaceBytes < requiredBytes { + backupSizeGB := float64(backupSizeBytes) / (1024 * 1024 * 1024) + bufferSizeGB := float64(bufferBytes) / (1024 * 1024 * 1024) + requiredGB := float64(requiredBytes) / (1024 * 1024 * 1024) + availableGB := float64(diskUsage.FreeSpaceBytes) / (1024 * 1024 * 1024) + + return fmt.Errorf( + "to restore this backup, %.1f GB (%.1f GB backup + %.1f GB buffer) is required, but only %.1f GB is available. Please free up disk space before restoring", + requiredGB, + backupSizeGB, + bufferSizeGB, + availableGB, + ) + } + + return nil +}