diff --git a/app/Livewire/Project/Database/Import.php b/app/Livewire/Project/Database/Import.php new file mode 100644 index 000000000..5b7c7de88 --- /dev/null +++ b/app/Livewire/Project/Database/Import.php @@ -0,0 +1,129 @@ +parameters = get_route_parameters(); + $this->getContainers(); + } + + public function getContainers() + { + $this->containers = collect(); + if (!data_get($this->parameters, 'database_uuid')) { + abort(404); + } + + $resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->first(); + if (is_null($resource)) { + $resource = StandaloneRedis::where('uuid', $this->parameters['database_uuid'])->first(); + if (is_null($resource)) { + $resource = StandaloneMongodb::where('uuid', $this->parameters['database_uuid'])->first(); + if (is_null($resource)) { + $resource = StandaloneMysql::where('uuid', $this->parameters['database_uuid'])->first(); + if (is_null($resource)) { + $resource = StandaloneMariadb::where('uuid', $this->parameters['database_uuid'])->first(); + if (is_null($resource)) { + abort(404); + } + } + } + } + } + $this->resource = $resource; + $this->server = $this->resource->destination->server; + $this->container = $this->resource->uuid; + if (str(data_get($this,'resource.status'))->startsWith('running')) { + $this->containers->push($this->container); + } + + if ($this->containers->count() > 1) { + $this->validated = false; + $this->validationMsg = 'The database service has more than one container running. Cannot import.'; + } + + if ($this->resource->getMorphClass() == 'App\Models\StandaloneRedis' + || $this->resource->getMorphClass() == 'App\Models\StandaloneMongodb') { + $this->validated = false; + $this->validationMsg = 'This database type is not currently supported.'; + } + + } + + public function runImport() + { + $this->validate([ + 'file' => 'required|file|max:102400' + ]); + + $this->importRunning = true; + $this->scpInProgress = true; + + try { + $uploadedFilename = $this->file->store('backup-import'); + $path = \Storage::path($uploadedFilename); + $tmpPath = '/tmp/' . basename($uploadedFilename); + + // SCP the backup file to the server. + instant_scp($path, $tmpPath, $this->server); + $this->scpInProgress = false; + + $this->importCommands[] = "docker cp {$tmpPath} {$this->container}:{$tmpPath}"; + + switch ($this->resource->getMorphClass()) { + case 'App\Models\StandaloneMariadb': + $this->importCommands[] = "docker exec {$this->container} sh -c 'mariadb -u\$MARIADB_USER -p\$MARIADB_PASSWORD \$MARIADB_DATABASE < {$tmpPath}'"; + $this->importCommands[] = "rm {$tmpPath}"; + break; + case 'App\Models\StandaloneMysql': + $this->importCommands[] = "docker exec {$this->container} sh -c 'mysql -u\$MYSQL_USER -p\$MYSQL_PASSWORD \$MYSQL_DATABASE < {$tmpPath}'"; + $this->importCommands[] = "rm {$tmpPath}"; + break; + case 'App\Models\StandalonePostgresql': + $this->importCommands[] = "docker exec {$this->container} sh -c 'pg_restore -U \$POSTGRES_USER -d \$POSTGRES_DB {$tmpPath}'"; + $this->importCommands[] = "rm {$tmpPath}"; + break; + } + + $this->importCommands[] = "docker exec {$this->container} sh -c '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); + $this->dispatch('newMonitorActivity', $activity->id); + } + } catch (\Throwable $e) { + $this->validated = false; + $this->validationMsg = $e->getMessage(); + } + + } + + +} diff --git a/bootstrap/helpers/remoteProcess.php b/bootstrap/helpers/remoteProcess.php index 91e42c5be..f49c7cafc 100644 --- a/bootstrap/helpers/remoteProcess.php +++ b/bootstrap/helpers/remoteProcess.php @@ -67,6 +67,47 @@ function savePrivateKeyToFs(Server $server) return $location; } +function generateScpCommand(Server $server, string $source, string $dest) +{ + $user = $server->user; + $port = $server->port; + $privateKeyLocation = savePrivateKeyToFs($server); + $timeout = config('constants.ssh.command_timeout'); + $connectionTimeout = config('constants.ssh.connection_timeout'); + $serverInterval = config('constants.ssh.server_interval'); + + $scp_command = "timeout $timeout scp "; + $scp_command .= "-i {$privateKeyLocation} " + . '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ' + . '-o PasswordAuthentication=no ' + . "-o ConnectTimeout=$connectionTimeout " + . "-o ServerAliveInterval=$serverInterval " + . '-o RequestTTY=no ' + . '-o LogLevel=ERROR ' + . "-P {$port} " + . "{$source} " + . "{$user}@{$server->ip}:{$dest}"; + + return $scp_command; +} +function instant_scp(string $source, string $dest, Server $server, $throwError = true) +{ + $timeout = config('constants.ssh.command_timeout'); + $scp_command = generateScpCommand($server, $source, $dest); + $process = Process::timeout($timeout)->run($scp_command); + $output = trim($process->output()); + $exitCode = $process->exitCode(); + if ($exitCode !== 0) { + if (!$throwError) { + return null; + } + return excludeCertainErrors($process->errorOutput(), $exitCode); + } + if ($output === 'null') { + $output = null; + } + return $output; +} function generateSshCommand(Server $server, string $command, bool $isMux = true) { $user = $server->user; diff --git a/config/livewire.php b/config/livewire.php index b1a3bf555..83229fcea 100644 --- a/config/livewire.php +++ b/config/livewire.php @@ -53,7 +53,9 @@ 'temporary_file_upload' => [ 'disk' => null, // Example: 'local', 's3' | Default: 'default' - 'rules' => null, // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB) + 'rules' => [ // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB) + 'file', 'max:256000' + ], 'directory' => null, // Example: 'tmp' | Default: 'livewire-tmp' 'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1' 'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs... diff --git a/docker/dev-ssu/Dockerfile b/docker/dev-ssu/Dockerfile index f6f141271..cf79afe4d 100644 --- a/docker/dev-ssu/Dockerfile +++ b/docker/dev-ssu/Dockerfile @@ -37,3 +37,7 @@ RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \ curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \ ;fi" +RUN { \ + echo 'upload_max_filesize=256M'; \ + echo 'post_max_size=256M'; \ + } > /etc/php/current_version/cli/conf.d/upload-limits.ini \ No newline at end of file diff --git a/docker/prod-ssu/Dockerfile b/docker/prod-ssu/Dockerfile index 238fe5658..d5ba465b7 100644 --- a/docker/prod-ssu/Dockerfile +++ b/docker/prod-ssu/Dockerfile @@ -62,3 +62,8 @@ RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \ echo 'arm64' && \ curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \ ;fi" + +RUN { \ + echo 'upload_max_filesize=256M'; \ + echo 'post_max_size=256M'; \ + } > /etc/php/current_version/cli/conf.d/upload-limits.ini \ No newline at end of file diff --git a/resources/views/livewire/project/database/import.blade.php b/resources/views/livewire/project/database/import.blade.php new file mode 100644 index 000000000..64ef28105 --- /dev/null +++ b/resources/views/livewire/project/database/import.blade.php @@ -0,0 +1,41 @@ +
+
+ + + + This is a destructive action, existing data will be replaced! +
+ + @if (!$validated) +
{{ $validationMsg }}
+ @else + @if (!$importRunning) +
+
+ + @error('file') {{ $message }} @enderror + Import +
+ +
+
+
+ @endif + @endif + + @if ($scpInProgress) +
Database backup is being copied to server..
+ @endif + +
+ +
+