From 9998dccc0a82828b7ea07fab67db5862c65da409 Mon Sep 17 00:00:00 2001
From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Date: Thu, 24 Sep 2026 18:11:17 +0200
Subject: [PATCH] feat(backups): support scheduled sqlite backups
---
app/Jobs/DatabaseBackupJob.php | 40 ++++++++++++++++++-
.../Database/CreateScheduledBackup.php | 2 +
.../database/backup-edit/general.blade.php | 4 ++
tests/Feature/Jobs/DatabaseBackupJobTest.php | 2 +-
4 files changed, 45 insertions(+), 3 deletions(-)
diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php
index 27fefb33ef..c6eaaf84e0 100644
--- a/app/Jobs/DatabaseBackupJob.php
+++ b/app/Jobs/DatabaseBackupJob.php
@@ -13,6 +13,7 @@ use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
+use App\Models\StandaloneSqlite;
use App\Models\Team;
use App\Notifications\Database\BackupFailed;
use App\Notifications\Database\BackupSuccess;
@@ -44,7 +45,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
public Server $server;
- public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneClickhouse|ServiceDatabase $database;
+ public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneClickhouse|StandaloneSqlite|ServiceDatabase $database;
public ?string $container_name = null;
@@ -282,6 +283,8 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$databasesToBackup = [$this->database->mariadb_database];
} elseif ($this->database instanceof StandaloneClickhouse) {
$databasesToBackup = [$this->database->clickhouse_db];
+ } elseif ($this->database instanceof StandaloneSqlite) {
+ $databasesToBackup = $this->database->sqlite_databases;
} else {
return;
}
@@ -398,6 +401,18 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
]);
BackupCreated::dispatch($this->team->id);
$this->backup_standalone_clickhouse($database);
+ } elseif ($this->database instanceof StandaloneSqlite) {
+ $this->backup_file = "/sqlite-backup-$database-".Carbon::now()->timestamp.'.gz';
+ $this->backup_location = $this->backup_dir.$this->backup_file;
+ $this->backup_log = ScheduledDatabaseBackupExecution::create([
+ 'uuid' => $this->backup_log_uuid,
+ 'database_name' => $database,
+ 'filename' => $this->backup_location,
+ 'scheduled_database_backup_id' => $this->backup->id,
+ 'local_storage_deleted' => false,
+ ]);
+ BackupCreated::dispatch($this->team->id);
+ $this->backup_standalone_sqlite($database);
} else {
throw new \Exception('Unsupported database type');
}
@@ -622,7 +637,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
return array_map('trim', explode('|', $databases));
}
- if ($type->contains(['postgres', 'mysql', 'mariadb', 'clickhouse'])) {
+ if ($type->contains(['postgres', 'mysql', 'mariadb', 'clickhouse', 'sqlite'])) {
return array_map('trim', explode(',', $databases));
}
@@ -742,6 +757,27 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
}
}
+ private function backup_standalone_sqlite(string $database): void
+ {
+ try {
+ if (! preg_match(StandaloneSqlite::DATABASES_PATTERN, $database)) {
+ throw new \Exception("Invalid database file name: {$database}");
+ }
+ $commands[] = 'mkdir -p '.escapeshellarg($this->backup_dir);
+ $script = 'f=$(mktemp) && sqlite3 -readonly '.escapeshellarg(StandaloneSqlite::DATA_DIRECTORY.'/'.$database).' "VACUUM INTO \'$f\'" && cat "$f"; s=$?; rm -f "$f"; exit $s';
+ $dumpCommand = 'docker exec '.escapeshellarg($this->container_name).' sh -c '.escapeshellarg($script);
+ $commands[] = $this->buildCompressedDumpCommand($dumpCommand).' > '.escapeshellarg($this->backup_location);
+ $this->backup_output = instant_remote_process($commands, $this->server, true, false, $this->timeout, disableMultiplexing: true);
+ $this->backup_output = trim($this->backup_output);
+ if ($this->backup_output === '') {
+ $this->backup_output = null;
+ }
+ } catch (Throwable $e) {
+ $this->add_to_error_output($e->getMessage());
+ throw $e;
+ }
+ }
+
private function add_to_backup_output($output): void
{
if ($this->backup_output) {
diff --git a/app/Livewire/Project/Database/CreateScheduledBackup.php b/app/Livewire/Project/Database/CreateScheduledBackup.php
index 96d2ac7aaf..e113173ffc 100644
--- a/app/Livewire/Project/Database/CreateScheduledBackup.php
+++ b/app/Livewire/Project/Database/CreateScheduledBackup.php
@@ -80,6 +80,8 @@ class CreateScheduledBackup extends Component
$payload['databases_to_backup'] = $database->mariadb_database;
} elseif ($database->type() === 'standalone-clickhouse') {
$payload['databases_to_backup'] = $database->clickhouse_db;
+ } elseif ($database->type() === 'standalone-sqlite') {
+ $payload['databases_to_backup'] = $database->sqlite_databases;
}
$databaseBackup = ScheduledDatabaseBackup::create($payload);
diff --git a/resources/views/livewire/project/database/backup-edit/general.blade.php b/resources/views/livewire/project/database/backup-edit/general.blade.php
index 5c794f2aaf..49385b0258 100644
--- a/resources/views/livewire/project/database/backup-edit/general.blade.php
+++ b/resources/views/livewire/project/database/backup-edit/general.blade.php
@@ -79,6 +79,10 @@