fix: prevent duplicate environment variables in buildtime.env and support nixpacks plan variable overrides (#7373)

This commit is contained in:
Andras Bacsai 2025-11-28 10:39:32 +01:00 committed by GitHub
commit 0209bb63e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1401,15 +1401,44 @@ private function generate_buildtime_environment_variables()
$this->application_deployment_queue->addLogEntry('[DEBUG] ========================================');
}
$envs = collect([]);
// Use associative array for automatic deduplication
$envs_dict = [];
// 1. Add nixpacks plan variables FIRST (lowest priority - can be overridden)
if ($this->build_pack === 'nixpacks' &&
isset($this->nixpacks_plan_json) &&
$this->nixpacks_plan_json->isNotEmpty()) {
$planVariables = data_get($this->nixpacks_plan_json, 'variables', []);
if (! empty($planVariables)) {
if (isDev()) {
$this->application_deployment_queue->addLogEntry('[DEBUG] Adding '.count($planVariables).' nixpacks plan variables to buildtime.env');
}
foreach ($planVariables as $key => $value) {
// Skip COOLIFY_* and SERVICE_* - they'll be added later with higher priority
if (str_starts_with($key, 'COOLIFY_') || str_starts_with($key, 'SERVICE_')) {
continue;
}
$escapedValue = escapeBashEnvValue($value);
$envs_dict[$key] = $escapedValue;
if (isDev()) {
$this->application_deployment_queue->addLogEntry("[DEBUG] Nixpacks var: {$key}={$escapedValue}");
}
}
}
}
// 2. Add COOLIFY variables (can override nixpacks, but shouldn't happen in practice)
$coolify_envs = $this->generate_coolify_env_variables(forBuildTime: true);
foreach ($coolify_envs as $key => $item) {
$envs_dict[$key] = escapeBashEnvValue($item);
}
// Add COOLIFY variables
$coolify_envs->each(function ($item, $key) use ($envs) {
$envs->push($key.'='.escapeBashEnvValue($item));
});
// Add SERVICE_NAME variables for Docker Compose builds
// 3. Add SERVICE_NAME, SERVICE_FQDN, SERVICE_URL variables for Docker Compose builds
if ($this->build_pack === 'dockercompose') {
if ($this->pull_request_id === 0) {
// Generate SERVICE_NAME for dockercompose services from processed compose
@ -1420,7 +1449,7 @@ private function generate_buildtime_environment_variables()
}
$services = data_get($dockerCompose, 'services', []);
foreach ($services as $serviceName => $_) {
$envs->push('SERVICE_NAME_'.str($serviceName)->upper().'='.escapeBashEnvValue($serviceName));
$envs_dict['SERVICE_NAME_'.str($serviceName)->upper()] = escapeBashEnvValue($serviceName);
}
// Generate SERVICE_FQDN & SERVICE_URL for non-PR deployments
@ -1433,8 +1462,8 @@ private function generate_buildtime_environment_variables()
$coolifyScheme = $coolifyUrl->getScheme();
$coolifyFqdn = $coolifyUrl->getHost();
$coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null);
$envs->push('SERVICE_URL_'.str($forServiceName)->upper().'='.escapeBashEnvValue($coolifyUrl->__toString()));
$envs->push('SERVICE_FQDN_'.str($forServiceName)->upper().'='.escapeBashEnvValue($coolifyFqdn));
$envs_dict['SERVICE_URL_'.str($forServiceName)->upper()] = escapeBashEnvValue($coolifyUrl->__toString());
$envs_dict['SERVICE_FQDN_'.str($forServiceName)->upper()] = escapeBashEnvValue($coolifyFqdn);
}
}
} else {
@ -1442,7 +1471,7 @@ private function generate_buildtime_environment_variables()
$rawDockerCompose = Yaml::parse($this->application->docker_compose_raw);
$rawServices = data_get($rawDockerCompose, 'services', []);
foreach ($rawServices as $rawServiceName => $_) {
$envs->push('SERVICE_NAME_'.str($rawServiceName)->upper().'='.escapeBashEnvValue(addPreviewDeploymentSuffix($rawServiceName, $this->pull_request_id)));
$envs_dict['SERVICE_NAME_'.str($rawServiceName)->upper()] = escapeBashEnvValue(addPreviewDeploymentSuffix($rawServiceName, $this->pull_request_id));
}
// Generate SERVICE_FQDN & SERVICE_URL for preview deployments with PR-specific domains
@ -1455,17 +1484,16 @@ private function generate_buildtime_environment_variables()
$coolifyScheme = $coolifyUrl->getScheme();
$coolifyFqdn = $coolifyUrl->getHost();
$coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null);
$envs->push('SERVICE_URL_'.str($forServiceName)->upper().'='.escapeBashEnvValue($coolifyUrl->__toString()));
$envs->push('SERVICE_FQDN_'.str($forServiceName)->upper().'='.escapeBashEnvValue($coolifyFqdn));
$envs_dict['SERVICE_URL_'.str($forServiceName)->upper()] = escapeBashEnvValue($coolifyUrl->__toString());
$envs_dict['SERVICE_FQDN_'.str($forServiceName)->upper()] = escapeBashEnvValue($coolifyFqdn);
}
}
}
}
// Add build-time user variables only
// 4. Add user-defined build-time variables LAST (highest priority - can override everything)
if ($this->pull_request_id === 0) {
$sorted_environment_variables = $this->application->environment_variables()
->where('key', 'not like', 'NIXPACKS_%')
->where('is_buildtime', true) // ONLY build-time variables
->orderBy($this->application->settings->is_env_sorting_enabled ? 'key' : 'id')
->get();
@ -1483,7 +1511,12 @@ private function generate_buildtime_environment_variables()
// Strip outer quotes from real_value and apply proper bash escaping
$value = trim($env->real_value, "'");
$escapedValue = escapeBashEnvValue($value);
$envs->push($env->key.'='.$escapedValue);
if (isDev() && isset($envs_dict[$env->key])) {
$this->application_deployment_queue->addLogEntry("[DEBUG] User override: {$env->key} (was: {$envs_dict[$env->key]}, now: {$escapedValue})");
}
$envs_dict[$env->key] = $escapedValue;
if (isDev()) {
$this->application_deployment_queue->addLogEntry("[DEBUG] Build-time env: {$env->key}");
@ -1495,7 +1528,12 @@ private function generate_buildtime_environment_variables()
} else {
// For normal vars, use double quotes to allow $VAR expansion
$escapedValue = escapeBashDoubleQuoted($env->real_value);
$envs->push($env->key.'='.$escapedValue);
if (isDev() && isset($envs_dict[$env->key])) {
$this->application_deployment_queue->addLogEntry("[DEBUG] User override: {$env->key} (was: {$envs_dict[$env->key]}, now: {$escapedValue})");
}
$envs_dict[$env->key] = $escapedValue;
if (isDev()) {
$this->application_deployment_queue->addLogEntry("[DEBUG] Build-time env: {$env->key}");
@ -1507,7 +1545,6 @@ private function generate_buildtime_environment_variables()
}
} else {
$sorted_environment_variables = $this->application->environment_variables_preview()
->where('key', 'not like', 'NIXPACKS_%')
->where('is_buildtime', true) // ONLY build-time variables
->orderBy($this->application->settings->is_env_sorting_enabled ? 'key' : 'id')
->get();
@ -1525,7 +1562,12 @@ private function generate_buildtime_environment_variables()
// Strip outer quotes from real_value and apply proper bash escaping
$value = trim($env->real_value, "'");
$escapedValue = escapeBashEnvValue($value);
$envs->push($env->key.'='.$escapedValue);
if (isDev() && isset($envs_dict[$env->key])) {
$this->application_deployment_queue->addLogEntry("[DEBUG] User override: {$env->key} (was: {$envs_dict[$env->key]}, now: {$escapedValue})");
}
$envs_dict[$env->key] = $escapedValue;
if (isDev()) {
$this->application_deployment_queue->addLogEntry("[DEBUG] Build-time env: {$env->key}");
@ -1537,7 +1579,12 @@ private function generate_buildtime_environment_variables()
} else {
// For normal vars, use double quotes to allow $VAR expansion
$escapedValue = escapeBashDoubleQuoted($env->real_value);
$envs->push($env->key.'='.$escapedValue);
if (isDev() && isset($envs_dict[$env->key])) {
$this->application_deployment_queue->addLogEntry("[DEBUG] User override: {$env->key} (was: {$envs_dict[$env->key]}, now: {$escapedValue})");
}
$envs_dict[$env->key] = $escapedValue;
if (isDev()) {
$this->application_deployment_queue->addLogEntry("[DEBUG] Build-time env: {$env->key}");
@ -1549,6 +1596,12 @@ private function generate_buildtime_environment_variables()
}
}
// Convert dictionary back to collection in KEY=VALUE format
$envs = collect([]);
foreach ($envs_dict as $key => $value) {
$envs->push($key.'='.$value);
}
// Return the generated environment variables
if (isDev()) {
$this->application_deployment_queue->addLogEntry('[DEBUG] ========================================');