From 40d9e05c0b06dbb7035a3ca27b1900b7e7b76218 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 7 Jan 2025 14:02:19 +0100 Subject: [PATCH] feat: restore backup from server file --- app/Events/RestoreJobFinished.php | 34 +++++++++++ app/Livewire/Project/Database/Import.php | 58 +++++++++++++++---- .../project/database/import.blade.php | 15 ++++- 3 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 app/Events/RestoreJobFinished.php diff --git a/app/Events/RestoreJobFinished.php b/app/Events/RestoreJobFinished.php new file mode 100644 index 000000000..d3adb7798 --- /dev/null +++ b/app/Events/RestoreJobFinished.php @@ -0,0 +1,34 @@ +startsWith('/tmp/') + && str($scriptPath)->startsWith('/tmp/') + && ! str($tmpPath)->contains('..') + && ! str($scriptPath)->contains('..') + && strlen($tmpPath) > 5 // longer than just "/tmp/" + && strlen($scriptPath) > 5 + ) { + $commands[] = "docker exec {$container} sh -c 'rm {$scriptPath}'"; + $commands[] = "docker exec {$container} sh -c 'rm {$tmpPath}'"; + instant_remote_process($commands, Server::find($serverId), throwError: true); + } + } + } +} diff --git a/app/Livewire/Project/Database/Import.php b/app/Livewire/Project/Database/Import.php index b76e7ec2c..6eecb7a1e 100644 --- a/app/Livewire/Project/Database/Import.php +++ b/app/Livewire/Project/Database/Import.php @@ -41,6 +41,8 @@ class Import extends Component public string $restoreCommandText = ''; + public string $customLocation = ''; + public string $postgresqlRestoreCommand = 'pg_restore -U $POSTGRES_USER -d $POSTGRES_DB'; public string $mysqlRestoreCommand = 'mysql -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE'; @@ -60,6 +62,9 @@ class Import extends Component public function mount() { + if (isDev()) { + $this->customLocation = '/data/coolify/pg-dump-all-1736245863.gz'; + } $this->parameters = get_route_parameters(); $this->getContainers(); } @@ -140,6 +145,24 @@ EOD; } } + public function checkFile() + { + if (filled($this->customLocation)) { + try { + $result = instant_remote_process(["ls -l {$this->customLocation}"], $this->server, throwError: false); + if (blank($result)) { + $this->dispatch('error', 'The file does not exist or has been deleted.'); + + return; + } + $this->filename = $this->customLocation; + $this->dispatch('success', 'The file exists.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + } + public function runImport() { if ($this->filename === '') { @@ -148,17 +171,24 @@ EOD; return; } try { - $uploadedFilename = "upload/{$this->resource->uuid}/restore"; - $path = Storage::path($uploadedFilename); - if (! Storage::exists($uploadedFilename)) { - $this->dispatch('error', 'The file does not exist or has been deleted.'); + $this->importCommands = []; + if (filled($this->customLocation)) { + $backupFileName = '/tmp/restore_'.$this->resource->uuid; + $this->importCommands[] = "docker cp {$this->customLocation} {$this->container}:{$backupFileName}"; + $tmpPath = $backupFileName; + } else { + $backupFileName = "upload/{$this->resource->uuid}/restore"; + $path = Storage::path($backupFileName); + if (! Storage::exists($backupFileName)) { + $this->dispatch('error', 'The file does not exist or has been deleted.'); - return; + return; + } + $tmpPath = '/tmp/'.basename($backupFileName).'_'.$this->resource->uuid; + instant_scp($path, $tmpPath, $this->server); + Storage::delete($backupFileName); + $this->importCommands[] = "docker cp {$tmpPath} {$this->container}:{$tmpPath}"; } - $tmpPath = '/tmp/'.basename($uploadedFilename); - instant_scp($path, $tmpPath, $this->server); - Storage::delete($uploadedFilename); - $this->importCommands[] = "docker cp {$tmpPath} {$this->container}:{$tmpPath}"; // Copy the restore command to a script file $scriptPath = "/tmp/restore_{$this->resource->uuid}.sh"; @@ -202,18 +232,22 @@ EOD; $this->importCommands[] = "docker cp {$scriptPath} {$this->container}:{$scriptPath}"; $this->importCommands[] = "docker exec {$this->container} sh -c '{$scriptPath}'"; - $this->importCommands[] = "docker exec {$this->container} sh -c 'rm {$scriptPath} && rm {$tmpPath}'"; - $this->importCommands[] = "docker exec {$this->container} sh -c 'echo \"Import finished with exit code $?\"'"; if (! empty($this->importCommands)) { - $activity = remote_process($this->importCommands, $this->server, ignore_errors: true); + $activity = remote_process($this->importCommands, $this->server, ignore_errors: true, callEventOnFinish: 'RestoreJobFinished', callEventData: [ + 'scriptPath' => $scriptPath, + 'tmpPath' => $tmpPath, + 'container' => $this->container, + 'serverId' => $this->server->id, + ]); $this->dispatch('activityMonitor', $activity->id); } } catch (\Throwable $e) { return handleError($e, $this); } finally { $this->filename = null; + $this->importCommands = []; } } } diff --git a/resources/views/livewire/project/database/import.blade.php b/resources/views/livewire/project/database/import.blade.php index 47afe611d..795c0a420 100644 --- a/resources/views/livewire/project/database/import.blade.php +++ b/resources/views/livewire/project/database/import.blade.php @@ -12,7 +12,7 @@ parallelChunkUploads: false, init: function() { let button = this.element.querySelector('button'); - button.innerText = 'Select or Drop a backup file here' + button.innerText = 'Select or drop a backup file here.' this.on('sending', function(file, xhr, formData) { const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); formData.append("_token", token); @@ -80,15 +80,24 @@ @endif - +

Backup File

+
+ + Check File +
+
+ Or +
@csrf
+

File Information

-
File: /
+
Location: /
Restore Backup