From 26814e2a9429dcaa6c745fd49d68cc7d321143b5 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 26 Oct 2026 07:59:34 +0100 Subject: [PATCH] fix(deployments): strip paths and ports from COOLIFY_FQDN in env files (#12245) --- app/Jobs/ApplicationDeploymentJob.php | 29 ++++++++++---- ...icationDeploymentCoolifyUrlDomainsTest.php | 40 +++++++++++++++++++ 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index c5f82c073f..bdc84bc6a6 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -3116,13 +3116,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $fqdn = $this->preview->fqdn; } if (isset($fqdn)) { - $domains = str($fqdn)->explode(',') - ->map(fn (string $domain) => trim($domain)) - ->filter() - ->filter(fn (string $domain) => isValidDomainUrl($domain)); + $domains = $this->validDomainsFromFqdn($fqdn); if ($domains->isNotEmpty()) { $url = $domains->map(fn (string $domain) => Url::fromString($domain)->withPort(null)->__toString())->implode(','); - $fqdn = $domains->map(fn (string $domain) => Url::fromString($domain)->getHost())->implode(','); + $fqdn = $this->hostsFromFqdn($fqdn); if ((int) $this->application->compose_parsing_version >= 3) { $this->coolify_variables .= 'COOLIFY_URL='.escapeShellValue($url).' '; $this->coolify_variables .= 'COOLIFY_FQDN='.escapeShellValue($fqdn).' '; @@ -3138,6 +3135,24 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $this->coolify_variables .= 'COOLIFY_RESOURCE_UUID='.escapeShellValue($this->application->uuid).' '; } + private function validDomainsFromFqdn(?string $fqdn): Collection + { + return str($fqdn ?? '')->explode(',') + ->map(fn (string $domain) => trim($domain)) + ->filter() + ->filter(fn (string $domain) => isValidDomainUrl($domain)); + } + + /** + * Comma-separated hosts without scheme, port, or path (the COOLIFY_FQDN value). + */ + private function hostsFromFqdn(?string $fqdn): string + { + return $this->validDomainsFromFqdn($fqdn) + ->map(fn (string $domain) => Url::fromString($domain)->getHost()) + ->implode(','); + } + private function shellAssignmentForDockerfileArg(string $assignment): string { [$key, $value] = array_pad(explode('=', $assignment, 2), 2, null); @@ -4021,7 +4036,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); } } if ($this->application->environment_variables_preview->where('key', 'COOLIFY_URL')->isEmpty()) { - $url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', ''); + $url = $this->hostsFromFqdn($this->preview->fqdn); if ((int) $this->application->compose_parsing_version >= 3) { $coolify_envs->put('COOLIFY_FQDN', $url); } else { @@ -4068,7 +4083,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); } } if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) { - $url = str($this->application->fqdn)->replace('http://', '')->replace('https://', ''); + $url = $this->hostsFromFqdn($this->application->fqdn); if ((int) $this->application->compose_parsing_version >= 3) { $coolify_envs->put('COOLIFY_FQDN', $url); } else { diff --git a/tests/Unit/ApplicationDeploymentCoolifyUrlDomainsTest.php b/tests/Unit/ApplicationDeploymentCoolifyUrlDomainsTest.php index 8fb8e0bd68..9d72d3a8fc 100644 --- a/tests/Unit/ApplicationDeploymentCoolifyUrlDomainsTest.php +++ b/tests/Unit/ApplicationDeploymentCoolifyUrlDomainsTest.php @@ -109,3 +109,43 @@ it('does not set domain variables for a hostless stored fqdn', function () { ->not->toContain('COOLIFY_URL=') ->not->toContain('COOLIFY_FQDN='); }); + +function coolifyEnvFileVariablesForFqdn(string $fqdn, string $composeParsingVersion = '3'): array +{ + coolifyVariablesForFqdn($fqdn, $composeParsingVersion); + $application = Application::withoutGlobalScopes()->latest('id')->firstOrFail(); + + $job = new TestableCoolifyUrlDeploymentJob; + $reflection = new ReflectionClass(ApplicationDeploymentJob::class); + + foreach ([ + 'application' => $application, + 'pull_request_id' => 0, + 'commit' => 'HEAD', + 'branch' => 'main', + ] as $property => $value) { + $reflection->getProperty($property)->setValue($job, $value); + } + + return $reflection->getMethod('generate_coolify_env_variables')->invoke($job, true)->all(); +} + +it('drops the path from COOLIFY_FQDN in the generated env file', function () { + $envs = coolifyEnvFileVariablesForFqdn('http://x.com/admin'); + + expect($envs['COOLIFY_FQDN'])->toBe('x.com') + ->and($envs['COOLIFY_URL'])->toBe('http://x.com/admin'); +}); + +it('drops the path and port from every domain in the generated env file', function () { + $envs = coolifyEnvFileVariablesForFqdn('https://a.example.com/admin,https://b.example.com:8080/api'); + + expect($envs['COOLIFY_FQDN'])->toBe('a.example.com,b.example.com'); +}); + +it('drops the path from COOLIFY_URL in the generated env file on the legacy compose parsing version', function () { + $envs = coolifyEnvFileVariablesForFqdn('http://x.com/admin', '2'); + + expect($envs['COOLIFY_URL'])->toBe('x.com') + ->and($envs['COOLIFY_FQDN'])->toBe('http://x.com/admin'); +});