From 4f5df53f1f228e095fece651bab925feb071c727 Mon Sep 17 00:00:00 2001 From: sebi Date: Fri, 4 Apr 2025 18:38:54 +0100 Subject: [PATCH 01/24] add hostname --- bootstrap/helpers/docker.php | 2 ++ .../views/livewire/project/application/general.blade.php | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index de80adbef..8bad79708 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -702,6 +702,7 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null) '--ulimit', '--device', '--shm-size', + '--hostname', ]); $mapping = collect([ '--cap-add' => 'cap_add', @@ -715,6 +716,7 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null) '--ip' => 'ip', '--shm-size' => 'shm_size', '--gpus' => 'gpus', + '--hostname' => 'hostname' ]); foreach ($matches as $match) { $option = $match[1]; diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 8c12d1d62..8bf41a986 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -181,7 +181,7 @@ @if ($application->build_pack === 'dockerimage') @else @if ($application->could_set_build_commands()) @@ -274,7 +274,7 @@ @endif @if ($application->build_pack !== 'dockercompose') From 2634f516d567a5a52cb729639c6012bda0204580 Mon Sep 17 00:00:00 2001 From: Christopher Kaster Date: Thu, 17 Apr 2025 14:14:32 +0200 Subject: [PATCH 02/24] feat: Add HTTP Basic Authentication --- app/Livewire/Project/Application/General.php | 3 + app/Models/Application.php | 3 + bootstrap/helpers/docker.php | 67 ++++++++++++++++--- ...add_application_http_basic_auth_fields.php | 32 +++++++++ .../project/application/general.blade.php | 12 ++++ 5 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index eaa988b99..a5c8fae88 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -94,6 +94,9 @@ class General extends Component 'application.settings.is_preserve_repository_enabled' => 'boolean|required', 'application.watch_paths' => 'nullable', 'application.redirect' => 'string|required', + 'application.http_basic_auth_enable' => 'boolean|required', + 'application.http_basic_auth_username' => 'nullable', + 'application.http_basic_auth_password' => 'nullable', ]; protected $validationAttributes = [ diff --git a/app/Models/Application.php b/app/Models/Application.php index 2feaebf94..01cdd565d 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -103,6 +103,9 @@ use Visus\Cuid2\Cuid2; 'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'], 'compose_parsing_version' => ['type' => 'string', 'description' => 'How Coolify parse the compose file.'], 'custom_nginx_configuration' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom Nginx configuration base64 encoded.'], + 'http_basic_auth_enable' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], + 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], + 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], ] )] diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index de80adbef..0a6c8684d 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -296,7 +296,8 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource) return $payload; } -function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both', ?string $predefinedPort = null) + +function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both', ?string $predefinedPort = null, bool $http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null) { $labels = collect([]); if ($serviceLabels) { @@ -304,6 +305,9 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, } else { $labels->push("caddy_ingress_network={$network}"); } + + $http_basic_auth_enabled = $http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null; + foreach ($domains as $loop => $domain) { $url = Url::fromString($domain); $host = $url->getHost(); @@ -343,17 +347,30 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, if (isDev()) { // $labels->push("caddy_{$loop}.tls=internal"); } + if ($http_basic_auth_enabled) { + $http_basic_auth_password = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); + $labels->push("caddy_{$loop}.basicauth.{$http_basic_auth_username}=\"{$http_basic_auth_password}\""); + } } return $labels->sort(); } -function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null, string $redirect_direction = 'both') + +function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null, string $redirect_direction = 'both', bool $http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null) { $labels = collect([]); $labels->push('traefik.enable=true'); $labels->push('traefik.http.middlewares.gzip.compress=true'); $labels->push('traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https'); + $http_basic_auth_enabled = $http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null; + $http_basic_auth_label = "http-basic-auth-{$uuid}"; + + if ($http_basic_auth_enabled) { + $http_basic_auth_password = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); + $labels->push("traefik.http.middlewares.{$http_basic_auth_label}.basicauth.users={$http_basic_auth_username}:{$http_basic_auth_password}"); + } + $middlewares_from_labels = collect([]); if ($serviceLabels) { @@ -511,6 +528,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $labels = $labels->merge($redirect_to_www); $middlewares->push($to_www_name); } + if ($http_basic_auth_enabled) { + $middlewares->push($http_basic_auth_label); + } $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) { $middlewares->push($middleware_name); }); @@ -534,6 +554,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $labels = $labels->merge($redirect_to_www); $middlewares->push($to_www_name); } + if ($http_basic_auth_enabled) { + $middlewares->push($http_basic_auth_label); + } $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) { $middlewares->push($middleware_name); }); @@ -577,7 +600,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - redirect_direction: $application->redirect + redirect_direction: $application->redirect, + http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_username: $application->http_basic_auth_username, + http_basic_auth_password: $application->http_basic_auth_password, )); break; case ProxyTypes::CADDY->value: @@ -589,7 +615,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - redirect_direction: $application->redirect + redirect_direction: $application->redirect, + http_basic_auth_enabled: $application->http_basic_auth_enabled, + http_basic_auth_username: $application->http_basic_auth_username, + http_basic_auth_password: $application->http_basic_auth_password, )); break; } @@ -601,7 +630,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - redirect_direction: $application->redirect + redirect_direction: $application->redirect, + http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_username: $application->http_basic_auth_username, + http_basic_auth_password: $application->http_basic_auth_password, )); $labels = $labels->merge(fqdnLabelsForCaddy( network: $application->destination->network, @@ -611,7 +643,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - redirect_direction: $application->redirect + redirect_direction: $application->redirect, + http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_username: $application->http_basic_auth_username, + http_basic_auth_password: $application->http_basic_auth_password, )); } } @@ -631,7 +666,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview onlyPort: $onlyPort, is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), - is_stripprefix_enabled: $application->isStripprefixEnabled() + is_stripprefix_enabled: $application->isStripprefixEnabled(), + http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_username: $application->http_basic_auth_username, + http_basic_auth_password: $application->http_basic_auth_password, )); break; case ProxyTypes::CADDY->value: @@ -642,7 +680,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview onlyPort: $onlyPort, is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), - is_stripprefix_enabled: $application->isStripprefixEnabled() + is_stripprefix_enabled: $application->isStripprefixEnabled(), + http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_username: $application->http_basic_auth_username, + http_basic_auth_password: $application->http_basic_auth_password, )); break; } @@ -653,7 +694,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview onlyPort: $onlyPort, is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), - is_stripprefix_enabled: $application->isStripprefixEnabled() + is_stripprefix_enabled: $application->isStripprefixEnabled(), + http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_username: $application->http_basic_auth_username, + http_basic_auth_password: $application->http_basic_auth_password, )); $labels = $labels->merge(fqdnLabelsForCaddy( network: $application->destination->network, @@ -662,7 +706,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview onlyPort: $onlyPort, is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), - is_stripprefix_enabled: $application->isStripprefixEnabled() + is_stripprefix_enabled: $application->isStripprefixEnabled(), + http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_username: $application->http_basic_auth_username, + http_basic_auth_password: $application->http_basic_auth_password, )); } } diff --git a/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php b/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php new file mode 100644 index 000000000..bc8f2712a --- /dev/null +++ b/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php @@ -0,0 +1,32 @@ +boolean('http_basic_auth_enable')->default(false); + $table->string('http_basic_auth_username')->nullable(true)->default(null); + $table->string('http_basic_auth_password')->nullable(true)->default(null); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn('http_basic_auth_enable'); + $table->dropColumn('http_basic_auth_username'); + $table->dropColumn('http_basic_auth_password'); + }); + } +}; diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index f971c8a4f..2c2cbde63 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -349,6 +349,18 @@ @endif +

HTTP Basic Authentication

+
+
+ +
+ +
+ + +
+
+ @if ($application->settings->is_container_label_readonly_enabled) From 3f9228fb800ef9cd0fd313568c044bc01aff7907 Mon Sep 17 00:00:00 2001 From: Laurence Date: Sat, 19 Apr 2025 12:17:21 +0100 Subject: [PATCH 03/24] fix: Add 201 json code to servers validate api response --- app/Http/Controllers/Api/ServersController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php index a9a0a2e53..cbd20400a 100644 --- a/app/Http/Controllers/Api/ServersController.php +++ b/app/Http/Controllers/Api/ServersController.php @@ -809,6 +809,6 @@ class ServersController extends Controller } ValidateServer::dispatch($server); - return response()->json(['message' => 'Validation started.']); + return response()->json(['message' => 'Validation started.'], 201); } } From c388e30499be2088e4933be33c6bcf23754f581d Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sun, 20 Apr 2025 17:58:41 +0200 Subject: [PATCH 04/24] refactor(jobs): comment out unused Caddy label handling in ApplicationDeploymentJob and simplify proxy path logic in Server model --- app/Jobs/ApplicationDeploymentJob.php | 38 +++++++++++++-------------- app/Models/Server.php | 6 +---- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index ab80a19de..b8141668d 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1712,25 +1712,25 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $labels = $labels->filter(function ($value, $key) { return ! Str::startsWith($value, 'coolify.'); }); - $found_caddy_labels = $labels->filter(function ($value, $key) { - return Str::startsWith($value, 'caddy_'); - }); - if ($found_caddy_labels->count() === 0) { - if ($this->pull_request_id !== 0) { - $domains = str(data_get($this->preview, 'fqdn'))->explode(','); - } else { - $domains = str(data_get($this->application, 'fqdn'))->explode(','); - } - $labels = $labels->merge(fqdnLabelsForCaddy( - network: $this->application->destination->network, - uuid: $this->application->uuid, - domains: $domains, - onlyPort: $onlyPort, - is_force_https_enabled: $this->application->isForceHttpsEnabled(), - is_gzip_enabled: $this->application->isGzipEnabled(), - is_stripprefix_enabled: $this->application->isStripprefixEnabled() - )); - } + // $found_caddy_labels = $labels->filter(function ($value, $key) { + // return Str::startsWith($value, 'caddy_'); + // }); + // if ($found_caddy_labels->count() === 0) { + // if ($this->pull_request_id !== 0) { + // $domains = str(data_get($this->preview, 'fqdn'))->explode(','); + // } else { + // $domains = str(data_get($this->application, 'fqdn'))->explode(','); + // } + // $labels = $labels->merge(fqdnLabelsForCaddy( + // network: $this->application->destination->network, + // uuid: $this->application->uuid, + // domains: $domains, + // onlyPort: $onlyPort, + // is_force_https_enabled: $this->application->isForceHttpsEnabled(), + // is_gzip_enabled: $this->application->isGzipEnabled(), + // is_stripprefix_enabled: $this->application->isStripprefixEnabled() + // )); + // } $this->application->custom_labels = base64_encode($labels->implode("\n")); $this->application->save(); } else { diff --git a/app/Models/Server.php b/app/Models/Server.php index 41af45f30..caf65cc58 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -492,11 +492,7 @@ $schema://$host { if ($proxyType === ProxyTypes::TRAEFIK->value) { // Do nothing } elseif ($proxyType === ProxyTypes::CADDY->value) { - if (isDev()) { - $proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/caddy'; - } else { - $proxy_path = $proxy_path.'/caddy'; - } + $proxy_path = $proxy_path.'/caddy'; } elseif ($proxyType === ProxyTypes::NGINX->value) { if (isDev()) { $proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/nginx'; From 6d12e7edc7884b110667a2cb2e95dd5c2510ac31 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 21 Apr 2025 09:57:27 +0200 Subject: [PATCH 05/24] refactor(database): simplify database type checks in ServiceDatabase and enhance image validation in Docker helper --- app/Models/ServiceDatabase.php | 2 +- bootstrap/helpers/docker.php | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/Models/ServiceDatabase.php b/app/Models/ServiceDatabase.php index c2a0df8cd..40d183033 100644 --- a/app/Models/ServiceDatabase.php +++ b/app/Models/ServiceDatabase.php @@ -141,6 +141,6 @@ class ServiceDatabase extends BaseModel str($this->databaseType())->contains('postgres') || str($this->databaseType())->contains('postgis') || str($this->databaseType())->contains('mariadb') || - str($this->databaseType())->contains('mongodb'); + str($this->databaseType())->contains('mongo'); } } diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index de80adbef..313df34a7 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -682,8 +682,10 @@ function isDatabaseImage(?string $image = null) $image = str($image)->append(':latest'); } $imageName = $image->before(':'); - if (collect(DATABASE_DOCKER_IMAGES)->contains($imageName)) { - return true; + foreach (DATABASE_DOCKER_IMAGES as $database_docker_image) { + if (str($imageName)->contains($database_docker_image)) { + return true; + } } return false; From 02b6aaaaf0c3d133e2c8a45d2dfcb511567a44cb Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 21 Apr 2025 09:58:56 +0200 Subject: [PATCH 06/24] refactor(shared): remove unused ray debugging statement from newParser function --- bootstrap/helpers/shared.php | 1 - 1 file changed, 1 deletion(-) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index aa821dc22..44e20c9b3 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -3819,7 +3819,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int return $volume; }); - ray($serviceLabels); $payload = collect($service)->merge([ 'container_name' => $containerName, 'restart' => $restart->value(), From 9548c7d3121e8da4453eddbb236b0314ec045310 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 21 Apr 2025 10:01:31 +0200 Subject: [PATCH 07/24] fix(backup-edit): conditionally enable S3 checkbox based on available validated S3 storage --- .../views/livewire/project/database/backup-edit.blade.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/resources/views/livewire/project/database/backup-edit.blade.php b/resources/views/livewire/project/database/backup-edit.blade.php index 603983864..f83af91a0 100644 --- a/resources/views/livewire/project/database/backup-edit.blade.php +++ b/resources/views/livewire/project/database/backup-edit.blade.php @@ -20,7 +20,12 @@
- + @if ($s3s->count() > 0) + + @else + + @endif
@if ($backup->save_s3)
From d731cdabdfe98c4bf0a663cf3a0f66cf2161063e Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 22 Apr 2025 10:20:54 +0200 Subject: [PATCH 08/24] fix(source): update no sources found message for clarity --- resources/views/livewire/project/application/source.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/livewire/project/application/source.blade.php b/resources/views/livewire/project/application/source.blade.php index 56bace154..85382055e 100644 --- a/resources/views/livewire/project/application/source.blade.php +++ b/resources/views/livewire/project/application/source.blade.php @@ -81,7 +81,7 @@
@empty -
No sources found
+
No other sources found
@endforelse From cd2f71b463002da79864510e2dca60e59508c27a Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 22 Apr 2025 10:20:57 +0200 Subject: [PATCH 09/24] feat(deployment): add repository_project_id handling for private GitHub apps and clean up unused Caddy label logic --- app/Jobs/ApplicationDeploymentJob.php | 30 ++++++++------------- app/Livewire/Project/Application/Source.php | 1 + 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index b8141668d..c29093ce0 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1377,6 +1377,17 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue private function check_git_if_build_needed() { + if ($this->source->getMorphClass() === \App\Models\GithubApp::class && $this->source->is_public === false) { + $repository = githubApi($this->source, "repos/{$this->customRepository}"); + $data = data_get($repository, 'data'); + if (isset($data->id)) { + $repository_project_id = $data->id; + if (blank($this->application->repository_project_id) || $this->application->repository_project_id !== $repository_project_id) { + $this->application->repository_project_id = $repository_project_id; + $this->application->save(); + } + } + } $this->generate_git_import_commands(); $local_branch = $this->branch; if ($this->pull_request_id !== 0) { @@ -1712,25 +1723,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $labels = $labels->filter(function ($value, $key) { return ! Str::startsWith($value, 'coolify.'); }); - // $found_caddy_labels = $labels->filter(function ($value, $key) { - // return Str::startsWith($value, 'caddy_'); - // }); - // if ($found_caddy_labels->count() === 0) { - // if ($this->pull_request_id !== 0) { - // $domains = str(data_get($this->preview, 'fqdn'))->explode(','); - // } else { - // $domains = str(data_get($this->application, 'fqdn'))->explode(','); - // } - // $labels = $labels->merge(fqdnLabelsForCaddy( - // network: $this->application->destination->network, - // uuid: $this->application->uuid, - // domains: $domains, - // onlyPort: $onlyPort, - // is_force_https_enabled: $this->application->isForceHttpsEnabled(), - // is_gzip_enabled: $this->application->isGzipEnabled(), - // is_stripprefix_enabled: $this->application->isStripprefixEnabled() - // )); - // } $this->application->custom_labels = base64_encode($labels->implode("\n")); $this->application->save(); } else { diff --git a/app/Livewire/Project/Application/Source.php b/app/Livewire/Project/Application/Source.php index 013d8b8fe..e27a550c1 100644 --- a/app/Livewire/Project/Application/Source.php +++ b/app/Livewire/Project/Application/Source.php @@ -111,6 +111,7 @@ class Source extends Component $this->application->update([ 'source_id' => $sourceId, 'source_type' => $sourceType, + 'repository_project_id' => null, ]); $this->application->refresh(); $this->getSources(); From 4ea00cff310f2a689257a29bad31aab404253f23 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 22 Apr 2025 10:44:30 +0200 Subject: [PATCH 10/24] refactor(applications): remove redundant error response in create_env method --- app/Http/Controllers/Api/ApplicationsController.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index aa5f2ff14..f25f63b11 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -2562,10 +2562,6 @@ class ApplicationsController extends Controller ])->setStatusCode(201); } } - - return response()->json([ - 'message' => 'Something went wrong.', - ], 500); } #[OA\Delete( From 3160b8a5a5c4d24af58c7158133af7e7a0731b8a Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 22 Apr 2025 10:44:37 +0200 Subject: [PATCH 11/24] feat(api): enhance OpenAPI specifications with token variable and additional key attributes --- app/Http/Controllers/Api/OpenApi.php | 4 +++- openapi.json | 14 +++++++++++++- openapi.yaml | 8 ++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/OpenApi.php b/app/Http/Controllers/Api/OpenApi.php index 60337a76c..ee75c1c6c 100644 --- a/app/Http/Controllers/Api/OpenApi.php +++ b/app/Http/Controllers/Api/OpenApi.php @@ -5,7 +5,9 @@ namespace App\Http\Controllers\Api; use OpenApi\Attributes as OA; #[OA\Info(title: 'Coolify', version: '0.1')] -#[OA\Server(url: 'https://app.coolify.io/api/v1', description: 'Coolify Cloud API. Change the host to your own instance if you are self-hosting.')] +#[OA\Server(url: 'https://app.coolify.io/api/v1', description: 'Coolify Cloud API. Change the host to your own instance if you are self-hosting.', variables: [ + new OA\ServerVariable('token', 'your-coolify-token-here', 'Coolify token for authentication'), +])] #[OA\SecurityScheme( type: 'http', scheme: 'bearer', diff --git a/openapi.json b/openapi.json index 5070f1be9..7f1aa637b 100644 --- a/openapi.json +++ b/openapi.json @@ -7,7 +7,13 @@ "servers": [ { "url": "https:\/\/app.coolify.io\/api\/v1", - "description": "Coolify Cloud API. Change the host to your own instance if you are self-hosting." + "description": "Coolify Cloud API. Change the host to your own instance if you are self-hosting.", + "variables": { + "token": { + "default": "Coolify token for authentication", + "description": "your-coolify-token-here" + } + } } ], "paths": { @@ -7729,6 +7735,12 @@ "type": "string", "format": "private-key" }, + "public_key": { + "type": "string" + }, + "fingerprint": { + "type": "string" + }, "is_git_related": { "type": "boolean" }, diff --git a/openapi.yaml b/openapi.yaml index 1fd42055b..07e1f82c2 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -6,6 +6,10 @@ servers: - url: 'https://app.coolify.io/api/v1' description: 'Coolify Cloud API. Change the host to your own instance if you are self-hosting.' + variables: + token: + default: 'Coolify token for authentication' + description: your-coolify-token-here paths: /applications: get: @@ -5159,6 +5163,10 @@ components: private_key: type: string format: private-key + public_key: + type: string + fingerprint: + type: string is_git_related: type: boolean team_id: From 91f28656350c0c11c860cf9851b6c25baf0a5157 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 22 Apr 2025 11:05:55 +0200 Subject: [PATCH 12/24] refactor(api): restructure routes to include versioning and maintain existing feedback endpoint --- routes/api.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/routes/api.php b/routes/api.php index b65e59e8b..ba78034d8 100644 --- a/routes/api.php +++ b/routes/api.php @@ -16,8 +16,13 @@ use App\Models\Server; use Illuminate\Support\Facades\Route; Route::get('/health', [OtherController::class, 'healthcheck']); -Route::post('/feedback', [OtherController::class, 'feedback']); +Route::group([ + 'prefix' => 'v1', +], function () { + Route::get('/health', [OtherController::class, 'healthcheck']); +}); +Route::post('/feedback', [OtherController::class, 'feedback']); Route::group([ 'middleware' => ['auth:sanctum', 'api.ability:write'], 'prefix' => 'v1', From 1d52df0e4eb892a046404a44c13f5078c75f32a6 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 22 Apr 2025 11:12:30 +0200 Subject: [PATCH 13/24] refactor(api): remove token variable from OpenAPI specifications for clarity --- app/Http/Controllers/Api/OpenApi.php | 4 +--- openapi.json | 8 +------- openapi.yaml | 4 ---- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/app/Http/Controllers/Api/OpenApi.php b/app/Http/Controllers/Api/OpenApi.php index ee75c1c6c..60337a76c 100644 --- a/app/Http/Controllers/Api/OpenApi.php +++ b/app/Http/Controllers/Api/OpenApi.php @@ -5,9 +5,7 @@ namespace App\Http\Controllers\Api; use OpenApi\Attributes as OA; #[OA\Info(title: 'Coolify', version: '0.1')] -#[OA\Server(url: 'https://app.coolify.io/api/v1', description: 'Coolify Cloud API. Change the host to your own instance if you are self-hosting.', variables: [ - new OA\ServerVariable('token', 'your-coolify-token-here', 'Coolify token for authentication'), -])] +#[OA\Server(url: 'https://app.coolify.io/api/v1', description: 'Coolify Cloud API. Change the host to your own instance if you are self-hosting.')] #[OA\SecurityScheme( type: 'http', scheme: 'bearer', diff --git a/openapi.json b/openapi.json index 7f1aa637b..b48bd85e9 100644 --- a/openapi.json +++ b/openapi.json @@ -7,13 +7,7 @@ "servers": [ { "url": "https:\/\/app.coolify.io\/api\/v1", - "description": "Coolify Cloud API. Change the host to your own instance if you are self-hosting.", - "variables": { - "token": { - "default": "Coolify token for authentication", - "description": "your-coolify-token-here" - } - } + "description": "Coolify Cloud API. Change the host to your own instance if you are self-hosting." } ], "paths": { diff --git a/openapi.yaml b/openapi.yaml index 07e1f82c2..239da6820 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -6,10 +6,6 @@ servers: - url: 'https://app.coolify.io/api/v1' description: 'Coolify Cloud API. Change the host to your own instance if you are self-hosting.' - variables: - token: - default: 'Coolify token for authentication' - description: your-coolify-token-here paths: /applications: get: From bb8cc467ab53c1ea29014aa3c2f32e7340c684c3 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 22 Apr 2025 11:16:45 +0200 Subject: [PATCH 14/24] fix(api): correct middleware for service update route to ensure proper permissions --- routes/api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/api.php b/routes/api.php index ba78034d8..405d8240e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -122,7 +122,7 @@ Route::group([ Route::post('/services', [ServicesController::class, 'create_service'])->middleware(['api.ability:write']); Route::get('/services/{uuid}', [ServicesController::class, 'service_by_uuid'])->middleware(['api.ability:read']); - Route::patch('/services/{uuid}', [ServicesController::class, 'update_by_uuid'])->middleware(['ability:write']); + Route::patch('/services/{uuid}', [ServicesController::class, 'update_by_uuid'])->middleware(['api.ability:write']); Route::delete('/services/{uuid}', [ServicesController::class, 'delete_by_uuid'])->middleware(['api.ability:write']); Route::get('/services/{uuid}/envs', [ServicesController::class, 'envs'])->middleware(['api.ability:read']); From eee57d4c06716c25ba7d599074ebc7baeb8d0db7 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 22 Apr 2025 11:16:49 +0200 Subject: [PATCH 15/24] fix(api): handle JSON response in service creation and update methods for improved error handling --- app/Http/Controllers/Api/ServicesController.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index c3730d83f..e792779e1 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -380,6 +380,9 @@ class ServicesController extends Controller $service = new Service; $result = $this->upsert_service($request, $service, $teamId); + if ($result instanceof \Illuminate\Http\JsonResponse) { + return $result; + } return response()->json(serializeApiResponse($result))->setStatusCode(201); } else { @@ -608,12 +611,14 @@ class ServicesController extends Controller } $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first(); - if (! $service) { return response()->json(['message' => 'Service not found.'], 404); } $result = $this->upsert_service($request, $service, $teamId); + if ($result instanceof \Illuminate\Http\JsonResponse) { + return $result; + } return response()->json(serializeApiResponse($result))->setStatusCode(200); } From 00fe6e90034b848af0651fc68dbe4d932e72021c Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 22 Apr 2025 11:40:35 +0200 Subject: [PATCH 16/24] refactor(environment-variables): remove protected variable checks from delete methods for cleaner logic --- app/Livewire/Project/Shared/EnvironmentVariable/All.php | 9 --------- app/Livewire/Project/Shared/EnvironmentVariable/Show.php | 7 ------- 2 files changed, 16 deletions(-) diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index 57952ddb3..699dca187 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -236,15 +236,6 @@ class All extends Component return 0; } - // Check for system variables that shouldn't be deleted - foreach ($variablesToDelete as $envVar) { - if ($this->isProtectedEnvironmentVariable($envVar->key)) { - $this->dispatch('error', "Cannot delete system environment variable '{$envVar->key}'."); - - return 0; - } - } - // Check if any of these variables are used in Docker Compose if ($this->resource->type() === 'service' || $this->resource->build_pack === 'dockercompose') { foreach ($variablesToDelete as $envVar) { diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php index d58151abf..535ac6c67 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -178,13 +178,6 @@ class Show extends Component public function delete() { try { - // Check if the variable is protected - if ($this->isProtectedEnvironmentVariable($this->env->key)) { - $this->dispatch('error', "Cannot delete system environment variable '{$this->env->key}'."); - - return; - } - // Check if the variable is used in Docker Compose if ($this->type === 'service' || $this->type === 'application' && $this->env->resource()?->docker_compose) { [$isUsed, $reason] = $this->isEnvironmentVariableUsedInDockerCompose($this->env->key, $this->env->resource()?->docker_compose); From 9e608f7ba5af494d0cd364e7cb31652c128f0d24 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 22 Apr 2025 21:30:27 +0200 Subject: [PATCH 17/24] refactor(http-basic-auth): rename 'http_basic_auth_enable' to 'http_basic_auth_enabled' across application files for consistency --- app/Livewire/Project/Application/General.php | 2 +- app/Models/Application.php | 2 +- bootstrap/helpers/docker.php | 18 ++++++++---------- ..._add_application_http_basic_auth_fields.php | 4 ++-- .../project/application/general.blade.php | 9 +++++---- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index a5c8fae88..b7cb693b6 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -94,7 +94,7 @@ class General extends Component 'application.settings.is_preserve_repository_enabled' => 'boolean|required', 'application.watch_paths' => 'nullable', 'application.redirect' => 'string|required', - 'application.http_basic_auth_enable' => 'boolean|required', + 'application.http_basic_auth_enabled' => 'boolean|required', 'application.http_basic_auth_username' => 'nullable', 'application.http_basic_auth_password' => 'nullable', ]; diff --git a/app/Models/Application.php b/app/Models/Application.php index cc69155cb..3306510d1 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -103,7 +103,7 @@ use Visus\Cuid2\Cuid2; 'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'], 'compose_parsing_version' => ['type' => 'string', 'description' => 'How Coolify parse the compose file.'], 'custom_nginx_configuration' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom Nginx configuration base64 encoded.'], - 'http_basic_auth_enable' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], + 'http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], ] diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 40d4acc0d..05fef91f5 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -344,9 +344,6 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) { $labels->push("caddy_{$loop}.redir={$schema}://{$host_without_www}{uri}"); } - if (isDev()) { - // $labels->push("caddy_{$loop}.tls=internal"); - } if ($http_basic_auth_enabled) { $http_basic_auth_password = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); $labels->push("caddy_{$loop}.basicauth.{$http_basic_auth_username}=\"{$http_basic_auth_password}\""); @@ -585,6 +582,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview if ($pull_request_id !== 0) { $appUuid = $appUuid.'-pr-'.$pull_request_id; } + ray($application); $labels = collect([]); if ($pull_request_id === 0) { if ($application->fqdn) { @@ -601,7 +599,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), redirect_direction: $application->redirect, - http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_enabled: $application->http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -631,7 +629,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), redirect_direction: $application->redirect, - http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_enabled: $application->http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -644,7 +642,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), redirect_direction: $application->redirect, - http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_enabled: $application->http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -667,7 +665,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_enabled: $application->http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -681,7 +679,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_enabled: $application->http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -695,7 +693,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_enabled: $application->http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -707,7 +705,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_enabled: $application->http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); diff --git a/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php b/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php index bc8f2712a..247300abd 100644 --- a/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php +++ b/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php @@ -12,7 +12,7 @@ return new class extends Migration public function up(): void { Schema::table('applications', function (Blueprint $table) { - $table->boolean('http_basic_auth_enable')->default(false); + $table->boolean('http_basic_auth_enabled')->default(false); $table->string('http_basic_auth_username')->nullable(true)->default(null); $table->string('http_basic_auth_password')->nullable(true)->default(null); }); @@ -24,7 +24,7 @@ return new class extends Migration public function down(): void { Schema::table('applications', function (Blueprint $table) { - $table->dropColumn('http_basic_auth_enable'); + $table->dropColumn('http_basic_auth_enabled'); $table->dropColumn('http_basic_auth_username'); $table->dropColumn('http_basic_auth_password'); }); diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 2c2cbde63..b6047d4be 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -350,14 +350,15 @@

HTTP Basic Authentication

-
+
- +
-
+
- +
From 8520beff513440cd559ae1c20f40936d549fc832 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 23 Apr 2025 11:21:37 +0200 Subject: [PATCH 18/24] refactor(docker): remove debug statement and enhance hostname handling in Docker run conversion --- bootstrap/helpers/docker.php | 7 ++----- tests/Feature/DockerCustomCommandsTest.php | 3 ++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index d094b0f57..03c4b3376 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -582,7 +582,6 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview if ($pull_request_id !== 0) { $appUuid = $appUuid.'-pr-'.$pull_request_id; } - ray($application); $labels = collect([]); if ($pull_request_id === 0) { if ($application->fqdn) { @@ -749,7 +748,6 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null) '--ulimit', '--device', '--shm-size', - '--hostname', ]); $mapping = collect([ '--cap-add' => 'cap_add', @@ -763,7 +761,7 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null) '--ip' => 'ip', '--shm-size' => 'shm_size', '--gpus' => 'gpus', - '--hostname' => 'hostname' + '--hostname' => 'hostname', ]); foreach ($matches as $match) { $option = $match[1]; @@ -810,7 +808,7 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null) } }); $compose_options->put($mapping[$option], $ulimits); - } elseif ($option === '--shm-size') { + } elseif ($option === '--shm-size' || $option === '--hostname') { if (! is_null($value) && is_array($value) && count($value) > 0) { $compose_options->put($mapping[$option], $value[0]); } @@ -849,7 +847,6 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null) continue; } - $compose_options->forget($option); } } diff --git a/tests/Feature/DockerCustomCommandsTest.php b/tests/Feature/DockerCustomCommandsTest.php index a07372069..e2e2cee05 100644 --- a/tests/Feature/DockerCustomCommandsTest.php +++ b/tests/Feature/DockerCustomCommandsTest.php @@ -1,10 +1,11 @@ toBe([ 'cap_add' => ['NET_ADMIN', 'NET_RAW', 'SYS_ADMIN'], + 'hostname' => 'test', ]); }); From e4648bcf9cfbb0cae9b5d123563b7fc7a74e6852 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 23 Apr 2025 11:57:26 +0200 Subject: [PATCH 19/24] feat(docker): add HTTP Basic Authentication support and enhance hostname parsing in Docker run conversion --- bootstrap/helpers/docker.php | 13 +++++++++++++ tests/Feature/DockerCustomCommandsTest.php | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 03c4b3376..7801ed00a 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -453,6 +453,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $labels = $labels->merge($redirect_to_www); $middlewares->push($to_www_name); } + if ($http_basic_auth_enabled) { + $middlewares->push($http_basic_auth_label); + } $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) { $middlewares->push($middleware_name); }); @@ -476,6 +479,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $labels = $labels->merge($redirect_to_www); $middlewares->push($to_www_name); } + if ($http_basic_auth_enabled) { + $middlewares->push($http_basic_auth_label); + } $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) { $middlewares->push($middleware_name); }); @@ -772,6 +778,13 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null) $options[$option][] = $value; $options[$option] = array_unique($options[$option]); } + if ($option === '--hostname') { + $regexForParsingHostname = '/hostname=([^\s]+)/'; + preg_match($regexForParsingHostname, $custom_docker_run_options, $hostname_matches); + $value = $hostname_matches[1] ?? null; + $options[$option][] = $value; + $options[$option] = array_unique($options[$option]); + } if (isset($match[2]) && $match[2] !== '') { $value = $match[2]; $options[$option][] = $value; diff --git a/tests/Feature/DockerCustomCommandsTest.php b/tests/Feature/DockerCustomCommandsTest.php index e2e2cee05..21943b0b5 100644 --- a/tests/Feature/DockerCustomCommandsTest.php +++ b/tests/Feature/DockerCustomCommandsTest.php @@ -1,11 +1,11 @@ toBe([ 'cap_add' => ['NET_ADMIN', 'NET_RAW', 'SYS_ADMIN'], - 'hostname' => 'test', + 'hostname' => 'my-super-host', ]); }); From 0dceb40a6edb620091090d3d1adac2cf80a0442d Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 23 Apr 2025 11:59:01 +0200 Subject: [PATCH 20/24] feat(api): add HTTP Basic Authentication fields to OpenAPI specifications and enhance PrivateKey model descriptions --- app/Models/PrivateKey.php | 4 ++-- openapi.json | 20 ++++++++++++++++++-- openapi.yaml | 13 +++++++++++++ routes/api.php | 1 - 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/app/Models/PrivateKey.php b/app/Models/PrivateKey.php index 97c32fa31..dbed7b439 100644 --- a/app/Models/PrivateKey.php +++ b/app/Models/PrivateKey.php @@ -17,8 +17,8 @@ use phpseclib3\Crypt\PublicKeyLoader; 'name' => ['type' => 'string'], 'description' => ['type' => 'string'], 'private_key' => ['type' => 'string', 'format' => 'private-key'], - 'public_key' => ['type' => 'string'], - 'fingerprint' => ['type' => 'string'], + 'public_key' => ['type' => 'string', 'description' => 'The public key of the private key.'], + 'fingerprint' => ['type' => 'string', 'description' => 'The fingerprint of the private key.'], 'is_git_related' => ['type' => 'boolean'], 'team_id' => ['type' => 'integer'], 'created_at' => ['type' => 'string'], diff --git a/openapi.json b/openapi.json index b48bd85e9..ccd7e2d83 100644 --- a/openapi.json +++ b/openapi.json @@ -7553,6 +7553,20 @@ "type": "string", "nullable": true, "description": "Custom Nginx configuration base64 encoded." + }, + "http_basic_auth_enabled": { + "type": "boolean", + "description": "HTTP Basic Authentication enabled." + }, + "http_basic_auth_username": { + "type": "string", + "nullable": true, + "description": "Username for HTTP Basic Authentication" + }, + "http_basic_auth_password": { + "type": "string", + "nullable": true, + "description": "Password for HTTP Basic Authentication" } }, "type": "object" @@ -7730,10 +7744,12 @@ "format": "private-key" }, "public_key": { - "type": "string" + "type": "string", + "description": "The public key of the private key." }, "fingerprint": { - "type": "string" + "type": "string", + "description": "The fingerprint of the private key." }, "is_git_related": { "type": "boolean" diff --git a/openapi.yaml b/openapi.yaml index 239da6820..41b1efdae 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -5042,6 +5042,17 @@ components: type: string nullable: true description: 'Custom Nginx configuration base64 encoded.' + http_basic_auth_enabled: + type: boolean + description: 'HTTP Basic Authentication enabled.' + http_basic_auth_username: + type: string + nullable: true + description: 'Username for HTTP Basic Authentication' + http_basic_auth_password: + type: string + nullable: true + description: 'Password for HTTP Basic Authentication' type: object ApplicationDeploymentQueue: description: 'Project model' @@ -5161,8 +5172,10 @@ components: format: private-key public_key: type: string + description: 'The public key of the private key.' fingerprint: type: string + description: 'The fingerprint of the private key.' is_git_related: type: boolean team_id: diff --git a/routes/api.php b/routes/api.php index 405d8240e..8ac8aef14 100644 --- a/routes/api.php +++ b/routes/api.php @@ -93,7 +93,6 @@ Route::group([ Route::patch('/applications/{uuid}/envs/bulk', [ApplicationsController::class, 'create_bulk_envs'])->middleware(['api.ability:write']); Route::patch('/applications/{uuid}/envs', [ApplicationsController::class, 'update_env_by_uuid'])->middleware(['api.ability:write']); Route::delete('/applications/{uuid}/envs/{env_uuid}', [ApplicationsController::class, 'delete_env_by_uuid'])->middleware(['api.ability:write']); - // Route::post('/applications/{uuid}/execute', [ApplicationsController::class, 'execute_command_by_uuid'])->middleware(['ability:write']); Route::get('/applications/{uuid}/logs', [ApplicationsController::class, 'logs_by_uuid'])->middleware(['api.ability:read']); Route::match(['get', 'post'], '/applications/{uuid}/start', [ApplicationsController::class, 'action_deploy'])->middleware(['api.ability:write']); From 78ef80f80052af5d44ad58ece30f7255ecba1a4f Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 23 Apr 2025 13:22:01 +0200 Subject: [PATCH 21/24] refactor --- .../Api/ApplicationsController.php | 58 ++++++++++++--- app/Livewire/Project/Application/General.php | 9 ++- app/Models/Application.php | 7 +- bootstrap/helpers/docker.php | 53 +++++++------- ...add_application_http_basic_auth_fields.php | 4 +- openapi.json | 72 ++++++++++++++++++- openapi.yaml | 57 ++++++++++++++- .../project/application/general.blade.php | 18 ++--- tests/Feature/DockerCustomCommandsTest.php | 32 ++++++++- 9 files changed, 257 insertions(+), 53 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index f25f63b11..3e0627b21 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -45,6 +45,7 @@ class ApplicationsController extends Controller 'private_key_id', 'value', 'real_value', + 'http_basic_auth_password', ]); } @@ -183,6 +184,9 @@ class ApplicationsController extends Controller 'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'], 'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'], 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], + 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], + 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], + 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], ], ) ), @@ -299,6 +303,9 @@ class ApplicationsController extends Controller 'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'], 'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'], 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], + 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], + 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], + 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], ], ) ), @@ -415,6 +422,9 @@ class ApplicationsController extends Controller 'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'], 'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'], 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], + 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], + 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], + 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], ], ) ), @@ -515,6 +525,9 @@ class ApplicationsController extends Controller 'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']], 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'], 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], + 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], + 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], + 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], ], ) ), @@ -612,6 +625,9 @@ class ApplicationsController extends Controller 'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']], 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'], 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], + 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], + 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], + 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], ], ) ), @@ -711,7 +727,6 @@ class ApplicationsController extends Controller private function create_application(Request $request, $type) { - $allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); @@ -721,6 +736,8 @@ class ApplicationsController extends Controller if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; } + $allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password']; + $validator = customApiValidator($request->all(), [ 'name' => 'string|max:255', 'description' => 'string|nullable', @@ -729,6 +746,9 @@ class ApplicationsController extends Controller 'environment_uuid' => 'string|nullable', 'server_uuid' => 'string|required', 'destination_uuid' => 'string', + 'is_http_basic_auth_enabled' => 'boolean', + 'http_basic_auth_username' => 'string|nullable', + 'http_basic_auth_password' => 'string|nullable', ]); $extraFields = array_diff(array_keys($request->all()), $allowedFields); @@ -1758,25 +1778,19 @@ class ApplicationsController extends Controller if (is_null($teamId)) { return invalidTokenResponse(); } - - if ($request->collect()->count() == 0) { - return response()->json([ - 'message' => 'Invalid request.', - ], 400); - } $return = validateIncomingRequest($request); if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; } - $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first(); + $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first(); if (! $application) { return response()->json([ 'message' => 'Application not found', ], 404); } $server = $application->destination->server; - $allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'custom_nginx_configuration']; + $allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password']; $validationRules = [ 'name' => 'string|max:255', @@ -1789,6 +1803,9 @@ class ApplicationsController extends Controller 'docker_compose_custom_start_command' => 'string|nullable', 'docker_compose_custom_build_command' => 'string|nullable', 'custom_nginx_configuration' => 'string|nullable', + 'is_http_basic_auth_enabled' => 'boolean|nullable', + 'http_basic_auth_username' => 'string', + 'http_basic_auth_password' => 'string', ]; $validationRules = array_merge(sharedDataApplications(), $validationRules); $validator = customApiValidator($request->all(), $validationRules); @@ -1844,6 +1861,29 @@ class ApplicationsController extends Controller 'errors' => $errors, ], 422); } + + if ($request->has('is_http_basic_auth_enabled') && $request->is_http_basic_auth_enabled === true) { + if (blank($application->http_basic_auth_username) || blank($application->http_basic_auth_password)) { + $validationErrors = []; + if (blank($request->http_basic_auth_username)) { + $validationErrors['http_basic_auth_username'] = 'The http_basic_auth_username is required.'; + } + if (blank($request->http_basic_auth_password)) { + $validationErrors['http_basic_auth_password'] = 'The http_basic_auth_password is required.'; + } + if (count($validationErrors) > 0) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validationErrors, + ], 422); + } + } + } + if ($request->has('is_http_basic_auth_enabled') && $application->is_container_label_readonly_enabled === false) { + $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); + $application->save(); + } + $domains = $request->domains; $requestHasDomains = $request->has('domains'); if ($requestHasDomains && $server->isProxyShouldRun()) { diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index b7cb693b6..74f47232c 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -92,11 +92,11 @@ class General extends Component 'application.settings.is_container_label_escape_enabled' => 'boolean|required', 'application.settings.is_container_label_readonly_enabled' => 'boolean|required', 'application.settings.is_preserve_repository_enabled' => 'boolean|required', + 'application.is_http_basic_auth_enabled' => 'boolean|required', + 'application.http_basic_auth_username' => 'string|nullable', + 'application.http_basic_auth_password' => 'string|nullable', 'application.watch_paths' => 'nullable', 'application.redirect' => 'string|required', - 'application.http_basic_auth_enabled' => 'boolean|required', - 'application.http_basic_auth_username' => 'nullable', - 'application.http_basic_auth_password' => 'nullable', ]; protected $validationAttributes = [ @@ -181,6 +181,9 @@ class General extends Component if ($this->application->settings->isDirty('is_spa')) { $this->generateNginxConfiguration($this->application->settings->is_spa ? 'spa' : 'static'); } + if ($this->application->isDirty('is_http_basic_auth_enabled')) { + $this->application->save(); + } $this->application->settings->save(); $this->dispatch('success', 'Settings saved.'); $this->application->refresh(); diff --git a/app/Models/Application.php b/app/Models/Application.php index 3306510d1..b84b7cd37 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -103,7 +103,7 @@ use Visus\Cuid2\Cuid2; 'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'], 'compose_parsing_version' => ['type' => 'string', 'description' => 'How Coolify parse the compose file.'], 'custom_nginx_configuration' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom Nginx configuration base64 encoded.'], - 'http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], + 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], ] @@ -119,7 +119,10 @@ class Application extends BaseModel protected $appends = ['server_status']; - protected $casts = ['custom_network_aliases' => 'array']; + protected $casts = [ + 'custom_network_aliases' => 'array', + 'http_basic_auth_password' => 'encrypted', + ]; public function customNetworkAliases(): Attribute { diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 7801ed00a..9363e4bf0 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -297,7 +297,7 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource) return $payload; } -function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both', ?string $predefinedPort = null, bool $http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null) +function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both', ?string $predefinedPort = null, bool $is_http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null) { $labels = collect([]); if ($serviceLabels) { @@ -306,7 +306,8 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, $labels->push("caddy_ingress_network={$network}"); } - $http_basic_auth_enabled = $http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null; + $is_http_basic_auth_enabled = $is_http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null; + $hashedPassword = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); foreach ($domains as $loop => $domain) { $url = Url::fromString($domain); @@ -344,28 +345,27 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) { $labels->push("caddy_{$loop}.redir={$schema}://{$host_without_www}{uri}"); } - if ($http_basic_auth_enabled) { - $http_basic_auth_password = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); - $labels->push("caddy_{$loop}.basicauth.{$http_basic_auth_username}=\"{$http_basic_auth_password}\""); + if ($is_http_basic_auth_enabled) { + $labels->push("caddy_{$loop}.basicauth.{$http_basic_auth_username}=\"{$hashedPassword}\""); } } return $labels->sort(); } -function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null, string $redirect_direction = 'both', bool $http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null) +function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null, string $redirect_direction = 'both', bool $is_http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null) { $labels = collect([]); $labels->push('traefik.enable=true'); $labels->push('traefik.http.middlewares.gzip.compress=true'); $labels->push('traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https'); - $http_basic_auth_enabled = $http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null; + $is_http_basic_auth_enabled = $is_http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null; $http_basic_auth_label = "http-basic-auth-{$uuid}"; + $hashedPassword = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); - if ($http_basic_auth_enabled) { - $http_basic_auth_password = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); - $labels->push("traefik.http.middlewares.{$http_basic_auth_label}.basicauth.users={$http_basic_auth_username}:{$http_basic_auth_password}"); + if ($is_http_basic_auth_enabled) { + $labels->push("traefik.http.middlewares.{$http_basic_auth_label}.basicauth.users={$http_basic_auth_username}:{$hashedPassword}"); } $middlewares_from_labels = collect([]); @@ -453,7 +453,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $labels = $labels->merge($redirect_to_www); $middlewares->push($to_www_name); } - if ($http_basic_auth_enabled) { + if ($is_http_basic_auth_enabled) { $middlewares->push($http_basic_auth_label); } $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) { @@ -479,7 +479,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $labels = $labels->merge($redirect_to_www); $middlewares->push($to_www_name); } - if ($http_basic_auth_enabled) { + if ($is_http_basic_auth_enabled) { $middlewares->push($http_basic_auth_label); } $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) { @@ -531,7 +531,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $labels = $labels->merge($redirect_to_www); $middlewares->push($to_www_name); } - if ($http_basic_auth_enabled) { + if ($is_http_basic_auth_enabled) { $middlewares->push($http_basic_auth_label); } $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) { @@ -557,7 +557,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $labels = $labels->merge($redirect_to_www); $middlewares->push($to_www_name); } - if ($http_basic_auth_enabled) { + if ($is_http_basic_auth_enabled) { $middlewares->push($http_basic_auth_label); } $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) { @@ -604,7 +604,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), redirect_direction: $application->redirect, - http_basic_auth_enabled: $application->http_basic_auth_enabled, + is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -619,7 +619,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), redirect_direction: $application->redirect, - http_basic_auth_enabled: $application->http_basic_auth_enabled, + is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -634,7 +634,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), redirect_direction: $application->redirect, - http_basic_auth_enabled: $application->http_basic_auth_enabled, + is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -647,7 +647,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), redirect_direction: $application->redirect, - http_basic_auth_enabled: $application->http_basic_auth_enabled, + is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -670,7 +670,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - http_basic_auth_enabled: $application->http_basic_auth_enabled, + is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -684,7 +684,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - http_basic_auth_enabled: $application->http_basic_auth_enabled, + is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -698,7 +698,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - http_basic_auth_enabled: $application->http_basic_auth_enabled, + is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -710,7 +710,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - http_basic_auth_enabled: $application->http_basic_auth_enabled, + is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -779,11 +779,14 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null) $options[$option] = array_unique($options[$option]); } if ($option === '--hostname') { - $regexForParsingHostname = '/hostname=([^\s]+)/'; + // Match --hostname=value or --hostname value + $regexForParsingHostname = '/--hostname(?:=|\s+)([^\s]+)/'; preg_match($regexForParsingHostname, $custom_docker_run_options, $hostname_matches); $value = $hostname_matches[1] ?? null; - $options[$option][] = $value; - $options[$option] = array_unique($options[$option]); + if ($value) { + $options[$option][] = $value; + $options[$option] = array_unique($options[$option]); + } } if (isset($match[2]) && $match[2] !== '') { $value = $match[2]; diff --git a/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php b/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php index 247300abd..4b8e11bc8 100644 --- a/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php +++ b/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php @@ -12,7 +12,7 @@ return new class extends Migration public function up(): void { Schema::table('applications', function (Blueprint $table) { - $table->boolean('http_basic_auth_enabled')->default(false); + $table->boolean('is_http_basic_auth_enabled')->default(false); $table->string('http_basic_auth_username')->nullable(true)->default(null); $table->string('http_basic_auth_password')->nullable(true)->default(null); }); @@ -24,7 +24,7 @@ return new class extends Migration public function down(): void { Schema::table('applications', function (Blueprint $table) { - $table->dropColumn('http_basic_auth_enabled'); + $table->dropColumn('is_http_basic_auth_enabled'); $table->dropColumn('http_basic_auth_username'); $table->dropColumn('http_basic_auth_password'); }); diff --git a/openapi.json b/openapi.json index ccd7e2d83..40f663e51 100644 --- a/openapi.json +++ b/openapi.json @@ -339,6 +339,20 @@ "type": "boolean", "nullable": true, "description": "Use build server." + }, + "is_http_basic_auth_enabled": { + "type": "boolean", + "description": "HTTP Basic Authentication enabled." + }, + "http_basic_auth_username": { + "type": "string", + "nullable": true, + "description": "Username for HTTP Basic Authentication" + }, + "http_basic_auth_password": { + "type": "string", + "nullable": true, + "description": "Password for HTTP Basic Authentication" } }, "type": "object" @@ -673,6 +687,20 @@ "type": "boolean", "nullable": true, "description": "Use build server." + }, + "is_http_basic_auth_enabled": { + "type": "boolean", + "description": "HTTP Basic Authentication enabled." + }, + "http_basic_auth_username": { + "type": "string", + "nullable": true, + "description": "Username for HTTP Basic Authentication" + }, + "http_basic_auth_password": { + "type": "string", + "nullable": true, + "description": "Password for HTTP Basic Authentication" } }, "type": "object" @@ -1007,6 +1035,20 @@ "type": "boolean", "nullable": true, "description": "Use build server." + }, + "is_http_basic_auth_enabled": { + "type": "boolean", + "description": "HTTP Basic Authentication enabled." + }, + "http_basic_auth_username": { + "type": "string", + "nullable": true, + "description": "Username for HTTP Basic Authentication" + }, + "http_basic_auth_password": { + "type": "string", + "nullable": true, + "description": "Password for HTTP Basic Authentication" } }, "type": "object" @@ -1270,6 +1312,20 @@ "type": "boolean", "nullable": true, "description": "Use build server." + }, + "is_http_basic_auth_enabled": { + "type": "boolean", + "description": "HTTP Basic Authentication enabled." + }, + "http_basic_auth_username": { + "type": "string", + "nullable": true, + "description": "Username for HTTP Basic Authentication" + }, + "http_basic_auth_password": { + "type": "string", + "nullable": true, + "description": "Password for HTTP Basic Authentication" } }, "type": "object" @@ -1516,6 +1572,20 @@ "type": "boolean", "nullable": true, "description": "Use build server." + }, + "is_http_basic_auth_enabled": { + "type": "boolean", + "description": "HTTP Basic Authentication enabled." + }, + "http_basic_auth_username": { + "type": "string", + "nullable": true, + "description": "Username for HTTP Basic Authentication" + }, + "http_basic_auth_password": { + "type": "string", + "nullable": true, + "description": "Password for HTTP Basic Authentication" } }, "type": "object" @@ -7554,7 +7624,7 @@ "nullable": true, "description": "Custom Nginx configuration base64 encoded." }, - "http_basic_auth_enabled": { + "is_http_basic_auth_enabled": { "type": "boolean", "description": "HTTP Basic Authentication enabled." }, diff --git a/openapi.yaml b/openapi.yaml index 41b1efdae..c5113d9f7 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -248,6 +248,17 @@ paths: type: boolean nullable: true description: 'Use build server.' + is_http_basic_auth_enabled: + type: boolean + description: 'HTTP Basic Authentication enabled.' + http_basic_auth_username: + type: string + nullable: true + description: 'Username for HTTP Basic Authentication' + http_basic_auth_password: + type: string + nullable: true + description: 'Password for HTTP Basic Authentication' type: object responses: '201': @@ -487,6 +498,17 @@ paths: type: boolean nullable: true description: 'Use build server.' + is_http_basic_auth_enabled: + type: boolean + description: 'HTTP Basic Authentication enabled.' + http_basic_auth_username: + type: string + nullable: true + description: 'Username for HTTP Basic Authentication' + http_basic_auth_password: + type: string + nullable: true + description: 'Password for HTTP Basic Authentication' type: object responses: '201': @@ -726,6 +748,17 @@ paths: type: boolean nullable: true description: 'Use build server.' + is_http_basic_auth_enabled: + type: boolean + description: 'HTTP Basic Authentication enabled.' + http_basic_auth_username: + type: string + nullable: true + description: 'Username for HTTP Basic Authentication' + http_basic_auth_password: + type: string + nullable: true + description: 'Password for HTTP Basic Authentication' type: object responses: '201': @@ -912,6 +945,17 @@ paths: type: boolean nullable: true description: 'Use build server.' + is_http_basic_auth_enabled: + type: boolean + description: 'HTTP Basic Authentication enabled.' + http_basic_auth_username: + type: string + nullable: true + description: 'Username for HTTP Basic Authentication' + http_basic_auth_password: + type: string + nullable: true + description: 'Password for HTTP Basic Authentication' type: object responses: '201': @@ -1089,6 +1133,17 @@ paths: type: boolean nullable: true description: 'Use build server.' + is_http_basic_auth_enabled: + type: boolean + description: 'HTTP Basic Authentication enabled.' + http_basic_auth_username: + type: string + nullable: true + description: 'Username for HTTP Basic Authentication' + http_basic_auth_password: + type: string + nullable: true + description: 'Password for HTTP Basic Authentication' type: object responses: '201': @@ -5042,7 +5097,7 @@ components: type: string nullable: true description: 'Custom Nginx configuration base64 encoded.' - http_basic_auth_enabled: + is_http_basic_auth_enabled: type: boolean description: 'HTTP Basic Authentication enabled.' http_basic_auth_username: diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 994d95bb8..d1b052603 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -350,16 +350,18 @@

HTTP Basic Authentication

-
+
- -
- -
- - +
+ @if ($application->is_http_basic_auth_enabled) +
+ + +
+ @endif
@if ($application->settings->is_container_label_readonly_enabled) diff --git a/tests/Feature/DockerCustomCommandsTest.php b/tests/Feature/DockerCustomCommandsTest.php index 21943b0b5..a7829a534 100644 --- a/tests/Feature/DockerCustomCommandsTest.php +++ b/tests/Feature/DockerCustomCommandsTest.php @@ -1,11 +1,39 @@ toBe([ + 'hostname' => 'test', + ]); +}); +test('HostnameWithoutEqualSign', function () { + $input = '--hostname test'; + $output = convertDockerRunToCompose($input); + expect($output)->toBe([ + 'hostname' => 'test', + ]); +}); +test('HostnameWithoutEqualSignAndHyphens', function () { + $input = '--hostname my-super-host'; + $output = convertDockerRunToCompose($input); + expect($output)->toBe([ + 'hostname' => 'my-super-host', + ]); +}); + +test('HostnameWithHyphens', function () { + $input = '--hostname=my-super-host'; + $output = convertDockerRunToCompose($input); + expect($output)->toBe([ + 'hostname' => 'my-super-host', + ]); +}); test('ConvertCapAdd', function () { - $input = '--cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add SYS_ADMIN --hostname=my-super-host'; + $input = '--cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add SYS_ADMIN'; $output = convertDockerRunToCompose($input); expect($output)->toBe([ 'cap_add' => ['NET_ADMIN', 'NET_RAW', 'SYS_ADMIN'], - 'hostname' => 'my-super-host', ]); }); From 5b9df7fd4530a884da891381cb7080fe6847b724 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 23 Apr 2025 13:23:27 +0200 Subject: [PATCH 22/24] refactor(server): simplify proxy path logic and remove unnecessary conditions --- app/Models/Server.php | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/app/Models/Server.php b/app/Models/Server.php index caf65cc58..89f6ad218 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -486,19 +486,13 @@ $schema://$host { $base_path = config('constants.coolify.base_config_path'); $proxyType = $this->proxyType(); $proxy_path = "$base_path/proxy"; - // TODO: should use /traefik for already existing configurations? - // Should move everything except /caddy and /nginx to /traefik - // The code needs to be modified as well, so maybe it does not worth it + if ($proxyType === ProxyTypes::TRAEFIK->value) { - // Do nothing + $proxy_path = '/'; } elseif ($proxyType === ProxyTypes::CADDY->value) { $proxy_path = $proxy_path.'/caddy'; } elseif ($proxyType === ProxyTypes::NGINX->value) { - if (isDev()) { - $proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/nginx'; - } else { - $proxy_path = $proxy_path.'/nginx'; - } + $proxy_path = $proxy_path.'/nginx'; } return $proxy_path; From 04e1d5c75deeffac10d0559ab4498ba572339a96 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 23 Apr 2025 13:30:27 +0200 Subject: [PATCH 23/24] fix(docker): ensure password hashing only occurs when HTTP Basic Authentication is enabled --- bootstrap/helpers/docker.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 9363e4bf0..f2554f715 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -307,7 +307,9 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, } $is_http_basic_auth_enabled = $is_http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null; - $hashedPassword = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); + if ($is_http_basic_auth_enabled) { + $hashedPassword = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); + } foreach ($domains as $loop => $domain) { $url = Url::fromString($domain); @@ -362,7 +364,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $is_http_basic_auth_enabled = $is_http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null; $http_basic_auth_label = "http-basic-auth-{$uuid}"; - $hashedPassword = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); + if ($is_http_basic_auth_enabled) { + $hashedPassword = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); + } if ($is_http_basic_auth_enabled) { $labels->push("traefik.http.middlewares.{$http_basic_auth_label}.basicauth.users={$http_basic_auth_username}:{$hashedPassword}"); From 08d2ad83ee8c56bb6350ea55cac846f6dacc1f20 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 23 Apr 2025 13:34:27 +0200 Subject: [PATCH 24/24] fix(docker): enhance hostname and GPU option validation in Docker run to compose conversion --- bootstrap/helpers/docker.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index f2554f715..bade67eb8 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -787,7 +787,7 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null) $regexForParsingHostname = '/--hostname(?:=|\s+)([^\s]+)/'; preg_match($regexForParsingHostname, $custom_docker_run_options, $hostname_matches); $value = $hostname_matches[1] ?? null; - if ($value) { + if ($value && ! empty(trim($value))) { $options[$option][] = $value; $options[$option] = array_unique($options[$option]); } @@ -829,7 +829,7 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null) }); $compose_options->put($mapping[$option], $ulimits); } elseif ($option === '--shm-size' || $option === '--hostname') { - if (! is_null($value) && is_array($value) && count($value) > 0) { + if (! is_null($value) && is_array($value) && count($value) > 0 && ! empty(trim($value[0]))) { $compose_options->put($mapping[$option], $value[0]); } } elseif ($option === '--gpus') { @@ -837,7 +837,7 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null) 'driver' => 'nvidia', 'capabilities' => ['gpu'], ]; - if (! is_null($value) && is_array($value) && count($value) > 0) { + if (! is_null($value) && is_array($value) && count($value) > 0 && ! empty(trim($value[0]))) { if (str($value[0]) != 'all') { if (str($value[0])->contains(',')) { $payload['device_ids'] = str($value[0])->explode(',')->toArray();