mirror of
https://github.com/coollabsio/coolify.git
synced 2026-10-06 22:17:23 -04:00
feat(backups): support scheduled sqlite backups
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -79,6 +79,10 @@
|
||||
<x-forms.input label="Databases to back up"
|
||||
helper="Comma-separated database names. Leave empty to include the default database."
|
||||
id="databasesToBackup" />
|
||||
@elseif ($backup->database_type === 'App\Models\StandaloneSqlite')
|
||||
<x-forms.input label="Database files to back up"
|
||||
helper="Comma-separated file names inside the data volume. Leave empty to back up every configured file."
|
||||
id="databasesToBackup" />
|
||||
@endif
|
||||
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
|
||||
@@ -367,7 +367,7 @@ test('all dump all database commands use shared helper compression', function ()
|
||||
->toContain('$this->buildCompressedDumpCommand($backupCommand)')
|
||||
->toContain('mysqldump -u root')
|
||||
->toContain('mariadb-dump -u root')
|
||||
->and(substr_count($source, '$this->buildCompressedDumpCommand($dumpCommand)'))->toBe(2)
|
||||
->and(substr_count($source, '$this->buildCompressedDumpCommand($dumpCommand)'))->toBe(3)
|
||||
->and($source)->not->toContain('| gzip >');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user