Skip to content

[Feat] Global backups & cross server backups#1198

Open
RichardAnderson wants to merge 7 commits into
vitodeploy:4.xfrom
RichardAnderson:feat/global-backups
Open

[Feat] Global backups & cross server backups#1198
RichardAnderson wants to merge 7 commits into
vitodeploy:4.xfrom
RichardAnderson:feat/global-backups

Conversation

@RichardAnderson

@RichardAnderson RichardAnderson commented Jul 14, 2026

Copy link
Copy Markdown
Member
  • Adds a global backups section to a project.
  • Allows cross server backups (closes Cross server backups #868)
  • Limits cross server backups to same DB engine at >= same version
  • Improves postgresql backup and restore process to support cross server restores
  • Updated dropdown search fields to be 100% width, instead of dynamic width based on content length

Summary by CodeRabbit

  • New Features
    • Added a project-scoped Backups area with optional server filtering and streamlined per-backup actions.
    • Backup creation/restore flows now support selecting a destination server for database restores.
    • Backup files now show source database engine/version (when available) and improved size display.
  • Bug Fixes
    • Strengthened restore compatibility checks for matching project/database readiness, version and engine constraints.
    • Added stricter access/ownership guards so backups and files from other servers/projects are blocked (404).
    • Improved backup progress/status refresh and tighter PostgreSQL backup/restore error handling.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7801218a-ff02-4e97-8db6-4a3b936ca6f1

📥 Commits

Reviewing files that changed from the base of the PR and between 346976d and 83c9ea6.

📒 Files selected for processing (4)
  • app/Http/Controllers/BackupController.php
  • app/Models/Server.php
  • resources/js/pages/backups/components/create-backup.tsx
  • resources/js/pages/backups/components/file-columns.tsx

📝 Walkthrough

Walkthrough

Backups are now listed at project or server scope, database backup files record source metadata, and cross-server restores validate project, readiness, storage, engine, and version compatibility. Backup actions, restore targeting, broadcasts, file metadata, and selector sizing are updated.

Changes

Backup management

Layer / File(s) Summary
Backup metadata and compatibility
app/Models/BackupFile.php, app/Services/Database/..., database/migrations/..., resources/js/types/backup-file.d.ts
Database source metadata, version comparison, target-server paths, and compatibility checks are added.
Restore validation and execution
app/Actions/Backup/RestoreBackup.php, app/Jobs/Backup/..., resources/views/ssh/services/database/..., tests/Feature/BackupTest.php
Restore validation covers project ownership, readiness, storage, engine, version, and paths before dispatching server-specific jobs.
Project and server backup listings
app/Http/Controllers/BackupController.php, app/Tables/BackupTable.php, app/Models/Project.php, app/Models/Server.php
Routes and relationships support project-wide and server-scoped backup tables, with server ownership guards and deletion cascading.
Backup creation and restore interface
resources/js/pages/backups/..., resources/js/lib/utils.ts, resources/js/components/app-sidebar.tsx
The UI supports server selection, cross-server restore targets, row actions, source metadata, formatted sizes, and project-level tables.
Shared selector presentation
resources/js/components/..., resources/js/pages/{servers,services,sites,users}/...
Popover widths are tied to their trigger dimensions across shared selectors.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant RestoreBackup
  participant TargetDatabase
  participant BackupFile
  participant RestoreDatabaseJob
  Operator->>RestoreBackup: select target server and database
  RestoreBackup->>TargetDatabase: validate project and readiness
  RestoreBackup->>BackupFile: validate compatibility
  RestoreBackup->>RestoreDatabaseJob: dispatch restore
  RestoreDatabaseJob->>BackupFile: update status and restored_to
Loading

Possibly related PRs

Suggested reviewers: saeedvaziry

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and matches the main change: global backups with cross-server restore support.
Linked Issues check ✅ Passed The PR satisfies #868 by moving backup management to project/global scope and supporting cross-server restores with storage and compatibility checks.
Out of Scope Changes check ✅ Passed The changes are broadly aligned with the backup/global scope and the UI width updates called out in the objectives.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@RichardAnderson
RichardAnderson marked this pull request as ready for review July 14, 2026 19:35
@RichardAnderson
RichardAnderson requested a review from Copilot July 14, 2026 19:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds project-level (“global”) backups and enables restoring database backups across servers within the same project, with compatibility checks (engine match and target version >= source). It also enhances PostgreSQL backup/restore scripts for safer cross-server restores and standardizes dropdown popover widths.

Changes:

  • Introduced a global backups index (project-scoped) backed by BackupTable/VitoTable and added sidebar navigation.
  • Captured source DB engine/version on backup files and enforced cross-server restore constraints (project-only, cloud-only, engine/version compatible, target server/database ready).
  • Improved PostgreSQL dump/restore behavior and normalized various UI dropdown popovers to use trigger-width sizing; centralized formatBytes.

Reviewed changes

Copilot reviewed 38 out of 39 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/Feature/BackupTest.php Adds coverage for global backups scoping, metadata capture, and cross-server restore rules.
resources/views/ssh/services/database/postgresql/restore.blade.php Forces psql to stop on first error during restore.
resources/views/ssh/services/database/postgresql/backup.blade.php Produces dumps more suitable for cross-server restore (--clean, --if-exists, etc.).
resources/js/types/backup-file.d.ts Exposes database_engine / database_version to the frontend.
resources/js/pages/users/components/user-select.tsx Makes popover content match trigger width.
resources/js/pages/sites/stats.tsx Uses shared formatBytes utility instead of local implementation.
resources/js/pages/sites/components/site-select.tsx Makes popover content match trigger width (with min width).
resources/js/pages/sites/components/isolated-user-select.tsx Makes popover content match trigger width.
resources/js/pages/services/components/install.tsx Makes popover content match trigger width.
resources/js/pages/servers/components/templates.tsx Makes popover content match trigger width.
resources/js/pages/servers/components/server-select.tsx Makes popover content match trigger width (with min width).
resources/js/pages/servers/components/create-server.tsx Makes popover content match trigger width.
resources/js/pages/backups/index.tsx Migrates backups listing to VitoTable and supports global vs per-server layout.
resources/js/pages/backups/files.tsx Hides DB-only columns when viewing file backups.
resources/js/pages/backups/components/restore-backup.tsx Adds “restore to server” for DB backups and displays source engine/version.
resources/js/pages/backups/components/file-columns.tsx Adds source metadata + size column for backup files and uses shared formatBytes.
resources/js/pages/backups/components/create-backup.tsx Supports creating backups from global context (server selectable).
resources/js/pages/backups/components/columns.tsx Removes legacy DataTable columns implementation (migrated to table/actions pattern).
resources/js/pages/backups/components/backup-actions.tsx New actions dropdown for backup rows (edit/enable/files/delete).
resources/js/lib/utils.ts Adds shared formatBytes(bytes, decimals) helper.
resources/js/components/project-select.tsx Makes popover content match trigger width (with min width).
resources/js/components/multi-select.tsx Makes popover content match trigger width.
resources/js/components/app-sidebar.tsx Adds “Backups” entry to the app sidebar (global backups).
database/migrations/2026_07_14_120631_add_source_database_metadata_to_backup_files_table.php Adds database_engine and database_version columns to backup_files.
database/factories/BackupFileFactory.php Adds factory helper to seed source DB metadata.
app/Tables/BackupTable.php New InertiaTable-backed table for backups (global + per-server).
app/Services/Database/Mysql.php Ensures version command returns a single match (head -n 1).
app/Services/Database/AbstractDatabase.php Captures source engine/version, makes size capture resilient, and fixes temp path for cross-server restore.
app/Models/Project.php Adds backups() relationship via servers (has-many-through).
app/Models/BackupFile.php Adds version normalization + cross-server restore compatibility checks; tempPath supports target server.
app/Jobs/Backup/RestoreFileJob.php Broadcasts parent backup updates after file updates.
app/Jobs/Backup/RestoreDatabaseJob.php Uses target server user for temp path cleanup; broadcasts parent backup updates.
app/Jobs/Backup/DeleteFileJob.php Ensures parent backup updates are broadcast on delete/failure paths.
app/Http/Resources/BackupFileResource.php Includes source DB metadata in API resource.
app/Http/Controllers/BackupFileController.php Enforces route-model consistency (404 on mismatches) and tightens authorization target.
app/Http/Controllers/BackupController.php Adds global backups route + migrates per-server listing to BackupTable.
app/Enums/BackupType.php Implements table display contract for InertiaTable enum rendering.
app/Actions/Backup/RunBackup.php Broadcasts parent backup updates and improves file-size capture robustness.
app/Actions/Backup/RestoreBackup.php Enables cross-server restore validation and broadcasts parent backup updates.

Comment thread resources/js/pages/backups/components/file-columns.tsx
Comment thread app/Actions/Backup/RestoreBackup.php

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/Services/Database/AbstractDatabase.php (1)

279-295: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Downloaded restore temp file is never cleaned up on success.

restoreBackup() downloads the backup into $tempPath on the target server but never deletes it afterwards, unlike runBackup() which explicitly calls os()->deleteFile() after the upload (line 270). RestoreDatabaseJob::handle() doesn't clean up either — only failed() deletes the temp file. Every successful restore therefore leaves a .sql.gz dump under /home/{sshUser}/ on the target server permanently, and with cross-server restores now possible, this can accumulate across multiple servers and eventually exhaust disk space.

🐛 Proposed fix — mirror the cleanup pattern from `runBackup()`
     public function restoreBackup(BackupFile $backupFile, string $database): void
     {
         $tempPath = $backupFile->tempPath($this->service->server);

         $backupFile->backup->storage->provider()->ssh($this->service->server)->download(
             $backupFile->path(),
             $tempPath,
         );

         $this->service->server->ssh()->exec(
             view($this->getScriptView('restore'), [
                 'database' => $database,
                 'path' => $tempPath,
             ]),
             'restore-database'
         );
+
+        $this->service->server->os()->deleteFile($tempPath);
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Services/Database/AbstractDatabase.php` around lines 279 - 295, Update
restoreBackup() to delete the downloaded temporary backup file from the target
server after the restore command completes, mirroring runBackup()’s
os()->deleteFile() cleanup pattern. Keep the existing download and restore flow
unchanged, and ensure cleanup occurs on successful execution.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/Actions/Backup/RestoreBackup.php`:
- Around line 88-117: Update the validation closure around the Database model
lookup to null-check the loaded server relationship before accessing project_id
or isReady(). Treat a missing server as an invalid selection by failing
validation, then return before the existing project and readiness checks; keep
the remaining database and restore compatibility validation unchanged.

In `@app/Http/Controllers/BackupController.php`:
- Around line 47-50: Ensure hierarchical route integrity in BackupController by
applying scoped implicit binding via the controller’s ScopeBindings attribute,
or add abort_unless($backup->server_id === $server->id, 404) to show, update,
run, enable, disable, and destroy before authorization or other actions.
Preserve the existing behavior for matching server and backup records.

In `@app/Jobs/Backup/DeleteFileJob.php`:
- Around line 27-30: Null-check backup and server relationships in the
asynchronous job flows before dereferencing them, returning early when either is
missing. In app/Jobs/Backup/DeleteFileJob.php lines 27-30 and 55-58, guard the
backup/server accesses and server logging; in app/Jobs/Backup/RestoreFileJob.php
lines 42-45, 66-69, and 83-91, guard server retrieval, failure handling, and
socket-event dispatch respectively. Use the existing job callbacks and preserve
normal behavior when both relationships exist.

In `@app/Models/BackupFile.php`:
- Around line 101-137: Reduce the reported complexity of
restoreCompatibilityError by extracting the database engine and version
validation into a focused private helper, such as
versionCompatibilityError(Service $service): ?string. Preserve the existing
validation order, error messages, and return behavior while having
restoreCompatibilityError delegate the service-specific checks to that helper.

In `@app/Services/Database/AbstractDatabase.php`:
- Around line 241-251: In runBackup, narrow the catch around database_version
capture from Throwable to the specific SSH-related exception used by the
service, such as SSHError, while retaining the existing logging and null
fallback. Allow unrelated errors from version() or normalizeVersion() to
propagate instead of being silently swallowed.

In `@resources/js/pages/backups/components/create-backup.tsx`:
- Around line 66-70: Update the onValueChange handler to preserve the current
database backup type when the newly selected server supports database backups;
otherwise continue setting the type to file. Keep clearing the database
selection when changing servers, and use the existing server capability data and
selected-server state to determine support.

In `@tests/Feature/BackupTest.php`:
- Around line 677-741: Add a dedicated test alongside
test_backup_run_succeeds_with_null_version_when_version_query_yields_nothing
that configures the SSH/version command used by AbstractDatabase::runBackup() to
throw an exception, then runs the backup and asserts the backup file is CREATED
with database_engine set and database_version null. Reuse the existing setup and
factory pattern while specifically exercising the exception-catching path.

---

Outside diff comments:
In `@app/Services/Database/AbstractDatabase.php`:
- Around line 279-295: Update restoreBackup() to delete the downloaded temporary
backup file from the target server after the restore command completes,
mirroring runBackup()’s os()->deleteFile() cleanup pattern. Keep the existing
download and restore flow unchanged, and ensure cleanup occurs on successful
execution.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c0da9a68-ad23-4553-9075-c2fec0b0bf9c

📥 Commits

Reviewing files that changed from the base of the PR and between 252ac4c and 346976d.

📒 Files selected for processing (39)
  • app/Actions/Backup/RestoreBackup.php
  • app/Actions/Backup/RunBackup.php
  • app/Enums/BackupType.php
  • app/Http/Controllers/BackupController.php
  • app/Http/Controllers/BackupFileController.php
  • app/Http/Resources/BackupFileResource.php
  • app/Jobs/Backup/DeleteFileJob.php
  • app/Jobs/Backup/RestoreDatabaseJob.php
  • app/Jobs/Backup/RestoreFileJob.php
  • app/Models/BackupFile.php
  • app/Models/Project.php
  • app/Services/Database/AbstractDatabase.php
  • app/Services/Database/Mysql.php
  • app/Tables/BackupTable.php
  • database/factories/BackupFileFactory.php
  • database/migrations/2026_07_14_120631_add_source_database_metadata_to_backup_files_table.php
  • resources/js/components/app-sidebar.tsx
  • resources/js/components/multi-select.tsx
  • resources/js/components/project-select.tsx
  • resources/js/lib/utils.ts
  • resources/js/pages/backups/components/backup-actions.tsx
  • resources/js/pages/backups/components/columns.tsx
  • resources/js/pages/backups/components/create-backup.tsx
  • resources/js/pages/backups/components/file-columns.tsx
  • resources/js/pages/backups/components/restore-backup.tsx
  • resources/js/pages/backups/files.tsx
  • resources/js/pages/backups/index.tsx
  • resources/js/pages/servers/components/create-server.tsx
  • resources/js/pages/servers/components/server-select.tsx
  • resources/js/pages/servers/components/templates.tsx
  • resources/js/pages/services/components/install.tsx
  • resources/js/pages/sites/components/isolated-user-select.tsx
  • resources/js/pages/sites/components/site-select.tsx
  • resources/js/pages/sites/stats.tsx
  • resources/js/pages/users/components/user-select.tsx
  • resources/js/types/backup-file.d.ts
  • resources/views/ssh/services/database/postgresql/backup.blade.php
  • resources/views/ssh/services/database/postgresql/restore.blade.php
  • tests/Feature/BackupTest.php
💤 Files with no reviewable changes (1)
  • resources/js/pages/backups/components/columns.tsx

Comment thread app/Actions/Backup/RestoreBackup.php
Comment thread app/Http/Controllers/BackupController.php
Comment thread app/Jobs/Backup/DeleteFileJob.php
Comment thread app/Models/BackupFile.php
Comment thread app/Services/Database/AbstractDatabase.php
Comment thread resources/js/pages/backups/components/create-backup.tsx
Comment thread tests/Feature/BackupTest.php
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cross server backups

2 participants