From 73e38ff951724fe6d6e187d3d229373a3ed07d69 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 2 Oct 2024 08:13:49 +0200 Subject: [PATCH 01/27] chore: Update version numbers to 4.0.0-beta.350 in configuration files --- config/sentry.php | 2 +- config/version.php | 2 +- versions.json | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/sentry.php b/config/sentry.php index 5e091b3ec..2fb257d2e 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ return [ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.349', + 'release' => '4.0.0-beta.350', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index 91c58fdcd..fa3b3acbf 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ Date: Wed, 2 Oct 2024 08:15:03 +0200 Subject: [PATCH 02/27] fix: ipv6 scp should use -6 flag --- app/Helpers/SshMultiplexingHelper.php | 4 +++- app/Models/Server.php | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/Helpers/SshMultiplexingHelper.php b/app/Helpers/SshMultiplexingHelper.php index b0a832605..8a6856a2e 100644 --- a/app/Helpers/SshMultiplexingHelper.php +++ b/app/Helpers/SshMultiplexingHelper.php @@ -94,7 +94,9 @@ class SshMultiplexingHelper $muxPersistTime = config('constants.ssh.mux_persist_time'); $scp_command = "timeout $timeout scp "; - + if ($server->isIpv6()) { + $scp_command .= '-6 '; + } if (self::isMultiplexingEnabled()) { $scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} "; self::ensureMultiplexedConnection($server); diff --git a/app/Models/Server.php b/app/Models/Server.php index f896541ad..54942f5fb 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -1221,4 +1221,9 @@ $schema://$host { return instant_remote_process($commands, $this, false); } + + public function isIpv6(): bool + { + return str($this->ip)->contains(':'); + } } From 024ad8e943c53757ff77302c4c234e73952e2d42 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 2 Oct 2024 09:20:08 +0200 Subject: [PATCH 03/27] fix: cleanup stucked applicationdeploymentqueue --- app/Console/Commands/CleanupStuckedResources.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/Console/Commands/CleanupStuckedResources.php b/app/Console/Commands/CleanupStuckedResources.php index dfd09d4b7..66c25ec27 100644 --- a/app/Console/Commands/CleanupStuckedResources.php +++ b/app/Console/Commands/CleanupStuckedResources.php @@ -4,6 +4,7 @@ namespace App\Console\Commands; use App\Jobs\CleanupHelperContainersJob; use App\Models\Application; +use App\Models\ApplicationDeploymentQueue; use App\Models\ApplicationPreview; use App\Models\ScheduledDatabaseBackup; use App\Models\ScheduledTask; @@ -47,6 +48,17 @@ class CleanupStuckedResources extends Command } catch (\Throwable $e) { echo "Error in cleaning stucked resources: {$e->getMessage()}\n"; } + try { + $applicationsDeploymentQueue = ApplicationDeploymentQueue::get(); + foreach ($applicationsDeploymentQueue as $applicationDeploymentQueue) { + if (is_null($applicationDeploymentQueue->application)) { + echo "Deleting stuck application deployment queue: {$applicationDeploymentQueue->id}\n"; + $applicationDeploymentQueue->delete(); + } + } + } catch (\Throwable $e) { + echo "Error in cleaning stuck application deployment queue: {$e->getMessage()}\n"; + } try { $applications = Application::withTrashed()->whereNotNull('deleted_at')->get(); foreach ($applications as $application) { From 8ca8ab82b04f3b4751e256dcb8cb3c00bfd0a0e4 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 2 Oct 2024 09:20:49 +0200 Subject: [PATCH 04/27] refactor: Remove deployment queue when deleting an application --- app/Models/Application.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/Models/Application.php b/app/Models/Application.php index dfa875a5a..e4ab3918a 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -143,6 +143,9 @@ class Application extends BaseModel } $application->tags()->detach(); $application->previews()->delete(); + foreach ($application->deployment_queue as $deployment) { + $deployment->delete(); + } }); } @@ -710,6 +713,11 @@ class Application extends BaseModel return $this->hasMany(ApplicationPreview::class); } + public function deployment_queue() + { + return $this->hasMany(ApplicationDeploymentQueue::class); + } + public function destination() { return $this->morphTo(); From a530804a712a3078b2c53470dd6b631824c37777 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 2 Oct 2024 09:21:28 +0200 Subject: [PATCH 05/27] feat: Add command to check application deployment queue --- .../CheckApplicationDeploymentQueue.php | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 app/Console/Commands/CheckApplicationDeploymentQueue.php diff --git a/app/Console/Commands/CheckApplicationDeploymentQueue.php b/app/Console/Commands/CheckApplicationDeploymentQueue.php new file mode 100644 index 000000000..9f66e058e --- /dev/null +++ b/app/Console/Commands/CheckApplicationDeploymentQueue.php @@ -0,0 +1,50 @@ +option('seconds'); + $deployments = ApplicationDeploymentQueue::whereIn('status', [ + ApplicationDeploymentStatus::IN_PROGRESS, + ApplicationDeploymentStatus::QUEUED, + ])->where('created_at', '>=', now()->subSeconds($seconds))->get(); + if ($deployments->isEmpty()) { + $this->info('No deployments found in the last '.$seconds.' seconds.'); + + return; + } + + $this->info('Found '.$deployments->count().' deployments created in the last '.$seconds.' seconds.'); + + foreach ($deployments as $deployment) { + if ($this->option('force')) { + $this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.'); + $this->cancelDeployment($deployment); + } else { + $this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.'); + if ($this->confirm('Do you want to cancel this deployment?', true)) { + $this->cancelDeployment($deployment); + } + } + } + } + + private function cancelDeployment(ApplicationDeploymentQueue $deployment) + { + $deployment->update(['status' => ApplicationDeploymentStatus::FAILED]); + if ($deployment->server?->isFunctional()) { + remote_process(['docker rm -f '.$deployment->deployment_uuid], $deployment->server, false); + } + } +} From bbd2748ad7395363d9a13bf9b3ec7bc606f31d4e Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 2 Oct 2024 09:21:50 +0200 Subject: [PATCH 06/27] chore: Update command signature and description for cleanup application deployment queue --- app/Console/Commands/CleanupApplicationDeploymentQueue.php | 4 ++-- app/Livewire/Dashboard.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Console/Commands/CleanupApplicationDeploymentQueue.php b/app/Console/Commands/CleanupApplicationDeploymentQueue.php index f068e3eb2..3aae28ae6 100644 --- a/app/Console/Commands/CleanupApplicationDeploymentQueue.php +++ b/app/Console/Commands/CleanupApplicationDeploymentQueue.php @@ -7,9 +7,9 @@ use Illuminate\Console\Command; class CleanupApplicationDeploymentQueue extends Command { - protected $signature = 'cleanup:application-deployment-queue {--team-id=}'; + protected $signature = 'cleanup:deployment-queue {--team-id=}'; - protected $description = 'CleanupApplicationDeploymentQueue'; + protected $description = 'Cleanup application deployment queue.'; public function handle() { diff --git a/app/Livewire/Dashboard.php b/app/Livewire/Dashboard.php index 1f0b68dd3..d18a7689e 100644 --- a/app/Livewire/Dashboard.php +++ b/app/Livewire/Dashboard.php @@ -30,7 +30,7 @@ class Dashboard extends Component public function cleanup_queue() { - Artisan::queue('cleanup:application-deployment-queue', [ + Artisan::queue('cleanup:deployment-queue', [ '--team-id' => currentTeam()->id, ]); } From 97943db5f4941876af307b85df593f7ece9fbc29 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 2 Oct 2024 09:21:54 +0200 Subject: [PATCH 07/27] chore: Add missing import for Attribute class in ApplicationDeploymentQueue model --- app/Models/ApplicationDeploymentQueue.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/Models/ApplicationDeploymentQueue.php b/app/Models/ApplicationDeploymentQueue.php index 90d7608cc..c261c30c6 100644 --- a/app/Models/ApplicationDeploymentQueue.php +++ b/app/Models/ApplicationDeploymentQueue.php @@ -2,6 +2,7 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Carbon; use OpenApi\Attributes as OA; @@ -39,6 +40,20 @@ class ApplicationDeploymentQueue extends Model { protected $guarded = []; + public function application(): Attribute + { + return Attribute::make( + get: fn () => Application::find($this->application_id), + ); + } + + public function server(): Attribute + { + return Attribute::make( + get: fn () => Server::find($this->server_id), + ); + } + public function setStatus(string $status) { $this->update([ From 2be2f0ac7900d4e8cc62a14e5009be3120afdd21 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 2 Oct 2024 10:25:45 +0200 Subject: [PATCH 08/27] feat: support Hetzner S3 --- app/Livewire/Storage/Create.php | 18 ++++++++++-------- bootstrap/helpers/s3.php | 3 ++- .../views/livewire/storage/create.blade.php | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/app/Livewire/Storage/Create.php b/app/Livewire/Storage/Create.php index a05834ecc..c5250e1e3 100644 --- a/app/Livewire/Storage/Create.php +++ b/app/Livewire/Storage/Create.php @@ -43,15 +43,17 @@ class Create extends Component 'endpoint' => 'Endpoint', ]; - public function mount() + public function updatedEndpoint($value) { - if (isDev()) { - $this->name = 'Local MinIO'; - $this->description = 'Local MinIO'; - $this->key = 'minioadmin'; - $this->secret = 'minioadmin'; - $this->bucket = 'local'; - $this->endpoint = 'http://coolify-minio:9000'; + if (! str($value)->startsWith('https://') && ! str($value)->startsWith('http://')) { + $this->endpoint = 'https://'.$value; + $value = $this->endpoint; + } + + if (str($value)->contains('your-objectstorage.com') && ! isset($this->bucket)) { + $this->bucket = str($value)->after('//')->before('.'); + } elseif (str($value)->contains('your-objectstorage.com')) { + $this->bucket = $this->bucket ?: str($value)->after('//')->before('.'); } } diff --git a/bootstrap/helpers/s3.php b/bootstrap/helpers/s3.php index 4a2252016..e4469cd6b 100644 --- a/bootstrap/helpers/s3.php +++ b/bootstrap/helpers/s3.php @@ -8,6 +8,7 @@ function set_s3_target(S3Storage $s3) $is_digital_ocean = false; if ($s3->endpoint) { $is_digital_ocean = Str::contains($s3->endpoint, 'digitaloceanspaces.com'); + $is_hetzner = Str::contains($s3->endpoint, 'your-objectstorage.com'); } config()->set('filesystems.disks.custom-s3', [ 'driver' => 's3', @@ -17,7 +18,7 @@ function set_s3_target(S3Storage $s3) 'bucket' => $s3['bucket'], 'endpoint' => $s3['endpoint'], 'use_path_style_endpoint' => true, - 'bucket_endpoint' => $is_digital_ocean, + 'bucket_endpoint' => $is_digital_ocean || $is_hetzner, 'aws_url' => $s3->awsUrl(), ]); } diff --git a/resources/views/livewire/storage/create.blade.php b/resources/views/livewire/storage/create.blade.php index 107bec296..41d09e679 100644 --- a/resources/views/livewire/storage/create.blade.php +++ b/resources/views/livewire/storage/create.blade.php @@ -4,7 +4,7 @@ - +
From dd782e75f5a94a0ee469f96ac458b6d548249dee Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 2 Oct 2024 11:45:30 +0200 Subject: [PATCH 09/27] fix: local dev s3 uploads fix: hetzner s3 uploads (mc alias instead of mc host) --- app/Jobs/DatabaseBackupJob.php | 22 ++++++++++++++++--- app/Models/S3Storage.php | 10 +++++++++ bootstrap/helpers/s3.php | 8 ++----- .../components/modal-confirmation.blade.php | 7 +++--- .../database/backup-executions.blade.php | 19 +++++----------- .../create-scheduled-backup.blade.php | 20 +++++++++-------- 6 files changed, 50 insertions(+), 36 deletions(-) diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index 7109fda3b..9369eaaf9 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -237,7 +237,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue } } $this->backup_dir = backup_dir().'/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name; - if ($this->database->name === 'coolify-db') { $databasesToBackup = ['coolify']; $this->directory_name = $this->container_name = 'coolify-db'; @@ -515,10 +514,27 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue $this->ensureHelperImageAvailable(); $fullImageName = $this->getFullImageName(); - $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}"; - $commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret"; + + if (isDev()) { + if ($this->database->name === 'coolify-db') { + $backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/databases/coolify/coolify-db-'.$this->server->ip.$this->backup_file; + $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}"; + } else { + $backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name.$this->backup_file; + $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}"; + } + } else { + $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}"; + } + if ($this->s3->isHetzner()) { + $endpointWithoutBucket = 'https://'.str($endpoint)->after('https://')->after('.')->value(); + $commands[] = "docker exec backup-of-{$this->backup->uuid} mc alias set --path=off --api=S3v4 temporary {$endpointWithoutBucket} $key $secret"; + } else { + $commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret"; + } $commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/"; instant_remote_process($commands, $this->server); + $this->add_to_backup_output('Uploaded to S3.'); } catch (\Throwable $e) { $this->add_to_backup_output($e->getMessage()); diff --git a/app/Models/S3Storage.php b/app/Models/S3Storage.php index 4c7faaa6f..a432a6e9c 100644 --- a/app/Models/S3Storage.php +++ b/app/Models/S3Storage.php @@ -40,6 +40,16 @@ class S3Storage extends BaseModel return "{$this->endpoint}/{$this->bucket}"; } + public function isHetzner() + { + return str($this->endpoint)->contains('your-objectstorage.com'); + } + + public function isDigitalOcean() + { + return str($this->endpoint)->contains('digitaloceanspaces.com'); + } + public function testConnection(bool $shouldSave = false) { try { diff --git a/bootstrap/helpers/s3.php b/bootstrap/helpers/s3.php index e4469cd6b..2ee7bf44a 100644 --- a/bootstrap/helpers/s3.php +++ b/bootstrap/helpers/s3.php @@ -1,15 +1,11 @@ endpoint) { - $is_digital_ocean = Str::contains($s3->endpoint, 'digitaloceanspaces.com'); - $is_hetzner = Str::contains($s3->endpoint, 'your-objectstorage.com'); - } + config()->set('filesystems.disks.custom-s3', [ 'driver' => 's3', 'region' => $s3['region'], @@ -18,7 +14,7 @@ function set_s3_target(S3Storage $s3) 'bucket' => $s3['bucket'], 'endpoint' => $s3['endpoint'], 'use_path_style_endpoint' => true, - 'bucket_endpoint' => $is_digital_ocean || $is_hetzner, + 'bucket_endpoint' => $s3->isHetzner() || $s3->isDigitalOcean(), 'aws_url' => $s3->awsUrl(), ]); } diff --git a/resources/views/components/modal-confirmation.blade.php b/resources/views/components/modal-confirmation.blade.php index d82a046d3..81a4b808b 100644 --- a/resources/views/components/modal-confirmation.blade.php +++ b/resources/views/components/modal-confirmation.blade.php @@ -296,9 +296,8 @@