Skip to content

[Core]: Backup/restore #509

Open
Open

Description

@nielsdrost7

Context


Backup and Restore option


Specific

As an administrator, I want a dedicated feature within InvoicePlane to perform both full system backups and restore existing backups, encompassing all database information and uploaded files.

Measurable

The success will be measured by the ability to consistently create a backup file that can be fully restored to a fresh instance of InvoicePlane, validating 100% data integrity and system functionality post-restore, with automated nightly backups if configured.

Achievable

This is achievable by leveraging Laravel's database backup tools and file system archiving capabilities, providing a user interface for initiation and status monitoring.

Relevant

This feature is highly relevant as it provides crucial data protection, disaster recovery capabilities, and simplifies system migration processes, ensuring business continuity for InvoicePlane users.

Time-bound

This Backup and Restore functionality should be fully developed, rigorously tested, and integrated into the system within the next two development sprints to provide immediate security for user data.


Arrange

  • The user is logged in as an administrator.
  • The InvoicePlane instance contains a significant amount of data (e.g., multiple invoices, clients, products, and users), including uploaded files (e.g., client logos, product images if that feature is implemented).
  • Sufficient disk space is available on the server for the backup file.
  • A separate, clean environment or a new, empty InvoicePlane database is prepared for the restore operation.

Act

  • The administrator navigates to the "Backup" section of the system.
  • The administrator initiates a full system backup.
  • The system indicates the backup process is complete and provides a downloadable backup file.
  • The administrator downloads the backup file.
  • The administrator accesses the separate, clean InvoicePlane environment (or clears the existing database for a restore to the same instance).
  • The administrator navigates to the "Restore" section.
  • The administrator uploads the previously downloaded backup file.
  • The administrator initiates the restore process.

Assert

  • The backup process completes successfully, creating a single, compressed backup file containing all database tables and relevant uploaded files.
  • The downloaded backup file is valid and not corrupted.
  • The restore process completes successfully in the target environment, with a confirmation message displayed.
  • All data (invoices, clients, products, users, etc.) from the backup is accurately present and accessible in the restored InvoicePlane instance.
  • All associated uploaded files (e.g., client logos, product images) are correctly restored and linked within the system.
  • All system functionalities (e.g., creating new invoices, editing clients) work as expected after the restore.
  • No data loss or inconsistencies are observed after the restore operation.
  • Error handling is robust if an invalid or corrupted backup file is attempted for restore.

@spacecabbie I'm assigning this one to you so you can keep an eye on it

Activity

  1. added this to the v2.0.0-alpha milestone on Jun 13, 2026
  2. nielsdrost7 commented on Jul 16, 2026

    @nielsdrost7
    CollaboratorAuthor

    SMART Plan — Backup and restore

    Context/Epic: Core

    Specific

    Give administrators a way to create a full backup (database + uploaded files) and restore it, from within the admin panel. Current state: there is nothing here today — no spatie/laravel-backup (or any backup package) in composer.json, no backup-related code anywhere under Modules/, no console command, no Filament page. This is a from-scratch feature, not an extension of existing scaffolding.

    Measurable

    • A console command produces a single archive containing a database dump and the contents of the uploads disk.
      • PHPUnit: it_creates_a_backup_archive_containing_a_database_dump()
        #[Test]
        public function it_creates_a_backup_archive_containing_a_database_dump(): void
        {
            /* Arrange */
            Storage::fake('backups');
    
            /* Act */
            $this->artisan('ip:backup')->assertSuccessful();
    
            /* Assert */
            $files = Storage::disk('backups')->allFiles();
            $this->assertNotEmpty($files);
        }
    • Uploaded files (via the existing Upload model's storage disk) are included in the backup archive.
      • PHPUnit: it_includes_uploaded_files_in_the_backup_archive()
        #[Test]
        public function it_includes_uploaded_files_in_the_backup_archive(): void
        {
            /* Arrange */
            Storage::fake('backups');
            $upload = Upload::factory()->for($this->company)->create();
            Storage::disk($upload->upload_disk)->put($upload->upload_stored_name, 'fake-file-contents');
    
            /* Act */
            $this->artisan('ip:backup')->assertSuccessful();
    
            /* Assert */
            $backupFile = Storage::disk('backups')->allFiles()[0] ?? null;
            $this->assertNotNull($backupFile);
        }
    • Restoring a valid backup archive repopulates the database and file storage.
      • PHPUnit: it_restores_data_from_a_valid_backup_archive()
        #[Test]
        public function it_restores_data_from_a_valid_backup_archive(): void
        {
            /* Arrange */
            Storage::fake('backups');
            $this->artisan('ip:backup')->assertSuccessful();
            $archivePath = Storage::disk('backups')->allFiles()[0];
            Relation::query()->delete();
    
            /* Act */
            $this->artisan('ip:restore', ['archive' => $archivePath])->assertSuccessful();
    
            /* Assert */
            $this->assertGreaterThan(0, Relation::query()->count());
        }
    • Attempting to restore a corrupted/invalid archive fails loudly rather than partially applying.
      • PHPUnit: it_rejects_a_corrupted_backup_archive_without_modifying_data()
        #[Test]
        public function it_rejects_a_corrupted_backup_archive_without_modifying_data(): void
        {
            /* Arrange */
            Storage::fake('backups');
            Storage::disk('backups')->put('corrupt.zip', 'not-a-real-archive');
            $before = Relation::query()->count();
    
            /* Act */
            $this->artisan('ip:restore', ['archive' => 'corrupt.zip'])->assertFailed();
    
            /* Assert */
            $this->assertSame($before, Relation::query()->count());
        }
    • An administrator can trigger a backup and download the resulting file from the admin panel.
      • Frontend/Livewire: As a super admin I want to trigger a manual backup from the admin panel so I don't have to SSH into the server.
        #[Test]
        public function it_triggers_a_manual_backup_from_the_admin_panel(): void
        {
            /* Arrange */
            /* superAdmin provided by AbstractAdminPanelTestCase */
    
            /* Act */
            $component = Livewire::actingAs($this->superAdmin())
                ->test(BackupPage::class)
                ->call('runBackup');
    
            /* Assert */
            $component->assertSuccessful()->assertNotified();
        }

    Achievable

    1. Evaluate spatie/laravel-backup as the underlying package rather than hand-rolling archive/dump logic — it already handles DB dump + configurable file disks and is a common Laravel choice; confirm it's acceptable before adding a new dependency.
    2. New ip:backup and ip:restore Artisan commands in Modules/Core/Console/ wrapping the chosen backup mechanism, targeting the uploads disk (used by the existing Upload model) plus the primary DB connection.
    3. New Filament admin page (Modules/Core/Filament/Admin/Pages/) exposing "Run backup now" and "Restore from upload" actions, gated behind a dedicated permission (follow the Permission::MANAGE_SETTINGS-style pattern already used for the Settings page).
    4. If automated nightly backups are in scope (the issue mentions "with automated nightly backups if configured"), add a scheduled command entry and a settings toggle for enabling/disabling it — confirm this is in scope for the first iteration or a follow-up.
    5. Add PHPUnit coverage in Modules/Core/Tests/Feature/.

    Relevant

    Backup/restore is core to disaster recovery and safe migrations between environments; today there is no supported way to do either, meaning any data loss is unrecoverable and any server migration is a manual, unsupported DB/file copy.

    Time-bound

    Sprint 8 (confirm package choice and whether scheduled nightly backups are in scope for this iteration before estimating further).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

high-priorefinedRefined with SMART plan / test suggestions

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions