Merge branch 'next' into fix-docker-time-database-v2

This commit is contained in:
Andras Bacsai 2025-11-28 13:12:00 +01:00 committed by GitHub
commit f42d076f31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 126 additions and 45 deletions

View file

@ -39,7 +39,7 @@ public function handle(Application $application, bool $previewDeployments = fals
foreach ($containersToStop as $containerName) { foreach ($containersToStop as $containerName) {
instant_remote_process(command: [ instant_remote_process(command: [
"docker stop --time=30 $containerName", "docker stop -t 30 $containerName",
"docker rm -f $containerName", "docker rm -f $containerName",
], server: $server, throwError: false); ], server: $server, throwError: false);
} }

View file

@ -26,7 +26,7 @@ public function handle(Application $application, Server $server)
if ($containerName) { if ($containerName) {
instant_remote_process( instant_remote_process(
[ [
"docker stop --time=30 $containerName", "docker stop -t 30 $containerName",
"docker rm -f $containerName", "docker rm -f $containerName",
], ],
$server $server

View file

@ -49,7 +49,7 @@ private function stopContainer($database, string $containerName, int $timeout =
{ {
$server = $database->destination->server; $server = $database->destination->server;
instant_remote_process(command: [ instant_remote_process(command: [
"docker stop --time=$timeout $containerName", "docker stop -t $timeout $containerName",
"docker rm -f $containerName", "docker rm -f $containerName",
], server: $server, throwError: false); ], server: $server, throwError: false);
} }

View file

@ -24,7 +24,7 @@ public function handle(Server $server, bool $forceStop = true, int $timeout = 30
} }
instant_remote_process(command: [ instant_remote_process(command: [
"docker stop --time=$timeout $containerName 2>/dev/null || true", "docker stop -t=$timeout $containerName 2>/dev/null || true",
"docker rm -f $containerName 2>/dev/null || true", "docker rm -f $containerName 2>/dev/null || true",
'# Wait for container to be fully removed', '# Wait for container to be fully removed',
'for i in {1..10}; do', 'for i in {1..10}; do',

View file

@ -54,7 +54,7 @@ private function stopContainersInParallel(array $containersToStop, Server $serve
$timeout = count($containersToStop) > 5 ? 10 : 30; $timeout = count($containersToStop) > 5 ? 10 : 30;
$commands = []; $commands = [];
$containerList = implode(' ', $containersToStop); $containerList = implode(' ', $containersToStop);
$commands[] = "docker stop --time=$timeout $containerList"; $commands[] = "docker stop -t $timeout $containerList";
$commands[] = "docker rm -f $containerList"; $commands[] = "docker rm -f $containerList";
instant_remote_process( instant_remote_process(
command: $commands, command: $commands,

View file

@ -351,7 +351,7 @@ public function create_service(Request $request)
'destination_id' => $destination->id, 'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(), 'destination_type' => $destination->getMorphClass(),
]; ];
if ($oneClickServiceName === 'cloudflared') { if (in_array($oneClickServiceName, NEEDS_TO_CONNECT_TO_PREDEFINED_NETWORK)) {
data_set($servicePayload, 'connect_to_docker_network', true); data_set($servicePayload, 'connect_to_docker_network', true);
} }
$service = Service::create($servicePayload); $service = Service::create($servicePayload);

View file

@ -1401,15 +1401,44 @@ private function generate_buildtime_environment_variables()
$this->application_deployment_queue->addLogEntry('[DEBUG] ========================================'); $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); $coolify_envs = $this->generate_coolify_env_variables(forBuildTime: true);
foreach ($coolify_envs as $key => $item) {
$envs_dict[$key] = escapeBashEnvValue($item);
}
// Add COOLIFY variables // 3. Add SERVICE_NAME, SERVICE_FQDN, SERVICE_URL variables for Docker Compose builds
$coolify_envs->each(function ($item, $key) use ($envs) {
$envs->push($key.'='.escapeBashEnvValue($item));
});
// Add SERVICE_NAME variables for Docker Compose builds
if ($this->build_pack === 'dockercompose') { if ($this->build_pack === 'dockercompose') {
if ($this->pull_request_id === 0) { if ($this->pull_request_id === 0) {
// Generate SERVICE_NAME for dockercompose services from processed compose // Generate SERVICE_NAME for dockercompose services from processed compose
@ -1420,7 +1449,7 @@ private function generate_buildtime_environment_variables()
} }
$services = data_get($dockerCompose, 'services', []); $services = data_get($dockerCompose, 'services', []);
foreach ($services as $serviceName => $_) { 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 // Generate SERVICE_FQDN & SERVICE_URL for non-PR deployments
@ -1433,8 +1462,8 @@ private function generate_buildtime_environment_variables()
$coolifyScheme = $coolifyUrl->getScheme(); $coolifyScheme = $coolifyUrl->getScheme();
$coolifyFqdn = $coolifyUrl->getHost(); $coolifyFqdn = $coolifyUrl->getHost();
$coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null); $coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null);
$envs->push('SERVICE_URL_'.str($forServiceName)->upper().'='.escapeBashEnvValue($coolifyUrl->__toString())); $envs_dict['SERVICE_URL_'.str($forServiceName)->upper()] = escapeBashEnvValue($coolifyUrl->__toString());
$envs->push('SERVICE_FQDN_'.str($forServiceName)->upper().'='.escapeBashEnvValue($coolifyFqdn)); $envs_dict['SERVICE_FQDN_'.str($forServiceName)->upper()] = escapeBashEnvValue($coolifyFqdn);
} }
} }
} else { } else {
@ -1442,7 +1471,7 @@ private function generate_buildtime_environment_variables()
$rawDockerCompose = Yaml::parse($this->application->docker_compose_raw); $rawDockerCompose = Yaml::parse($this->application->docker_compose_raw);
$rawServices = data_get($rawDockerCompose, 'services', []); $rawServices = data_get($rawDockerCompose, 'services', []);
foreach ($rawServices as $rawServiceName => $_) { 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 // 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(); $coolifyScheme = $coolifyUrl->getScheme();
$coolifyFqdn = $coolifyUrl->getHost(); $coolifyFqdn = $coolifyUrl->getHost();
$coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null); $coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null);
$envs->push('SERVICE_URL_'.str($forServiceName)->upper().'='.escapeBashEnvValue($coolifyUrl->__toString())); $envs_dict['SERVICE_URL_'.str($forServiceName)->upper()] = escapeBashEnvValue($coolifyUrl->__toString());
$envs->push('SERVICE_FQDN_'.str($forServiceName)->upper().'='.escapeBashEnvValue($coolifyFqdn)); $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) { if ($this->pull_request_id === 0) {
$sorted_environment_variables = $this->application->environment_variables() $sorted_environment_variables = $this->application->environment_variables()
->where('key', 'not like', 'NIXPACKS_%')
->where('is_buildtime', true) // ONLY build-time variables ->where('is_buildtime', true) // ONLY build-time variables
->orderBy($this->application->settings->is_env_sorting_enabled ? 'key' : 'id') ->orderBy($this->application->settings->is_env_sorting_enabled ? 'key' : 'id')
->get(); ->get();
@ -1483,7 +1511,12 @@ private function generate_buildtime_environment_variables()
// Strip outer quotes from real_value and apply proper bash escaping // Strip outer quotes from real_value and apply proper bash escaping
$value = trim($env->real_value, "'"); $value = trim($env->real_value, "'");
$escapedValue = escapeBashEnvValue($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()) { if (isDev()) {
$this->application_deployment_queue->addLogEntry("[DEBUG] Build-time env: {$env->key}"); $this->application_deployment_queue->addLogEntry("[DEBUG] Build-time env: {$env->key}");
@ -1495,7 +1528,12 @@ private function generate_buildtime_environment_variables()
} else { } else {
// For normal vars, use double quotes to allow $VAR expansion // For normal vars, use double quotes to allow $VAR expansion
$escapedValue = escapeBashDoubleQuoted($env->real_value); $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()) { if (isDev()) {
$this->application_deployment_queue->addLogEntry("[DEBUG] Build-time env: {$env->key}"); $this->application_deployment_queue->addLogEntry("[DEBUG] Build-time env: {$env->key}");
@ -1507,7 +1545,6 @@ private function generate_buildtime_environment_variables()
} }
} else { } else {
$sorted_environment_variables = $this->application->environment_variables_preview() $sorted_environment_variables = $this->application->environment_variables_preview()
->where('key', 'not like', 'NIXPACKS_%')
->where('is_buildtime', true) // ONLY build-time variables ->where('is_buildtime', true) // ONLY build-time variables
->orderBy($this->application->settings->is_env_sorting_enabled ? 'key' : 'id') ->orderBy($this->application->settings->is_env_sorting_enabled ? 'key' : 'id')
->get(); ->get();
@ -1525,7 +1562,12 @@ private function generate_buildtime_environment_variables()
// Strip outer quotes from real_value and apply proper bash escaping // Strip outer quotes from real_value and apply proper bash escaping
$value = trim($env->real_value, "'"); $value = trim($env->real_value, "'");
$escapedValue = escapeBashEnvValue($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()) { if (isDev()) {
$this->application_deployment_queue->addLogEntry("[DEBUG] Build-time env: {$env->key}"); $this->application_deployment_queue->addLogEntry("[DEBUG] Build-time env: {$env->key}");
@ -1537,7 +1579,12 @@ private function generate_buildtime_environment_variables()
} else { } else {
// For normal vars, use double quotes to allow $VAR expansion // For normal vars, use double quotes to allow $VAR expansion
$escapedValue = escapeBashDoubleQuoted($env->real_value); $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()) { if (isDev()) {
$this->application_deployment_queue->addLogEntry("[DEBUG] Build-time env: {$env->key}"); $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 // Return the generated environment variables
if (isDev()) { if (isDev()) {
$this->application_deployment_queue->addLogEntry('[DEBUG] ========================================'); $this->application_deployment_queue->addLogEntry('[DEBUG] ========================================');
@ -3090,7 +3143,7 @@ private function graceful_shutdown_container(string $containerName)
try { try {
$timeout = isDev() ? 1 : 30; $timeout = isDev() ? 1 : 30;
$this->execute_remote_command( $this->execute_remote_command(
["docker stop --time=$timeout $containerName", 'hidden' => true, 'ignore_errors' => true], ["docker stop -t $timeout $containerName", 'hidden' => true, 'ignore_errors' => true],
["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true] ["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true]
); );
} catch (Exception $error) { } catch (Exception $error) {

View file

@ -191,7 +191,7 @@ private function stopPreviewContainers(array $containers, $server, int $timeout
$containerList = implode(' ', array_map('escapeshellarg', $containerNames)); $containerList = implode(' ', array_map('escapeshellarg', $containerNames));
$commands = [ $commands = [
"docker stop --time=$timeout $containerList", "docker stop -t $timeout $containerList",
"docker rm -f $containerList", "docker rm -f $containerList",
]; ];
instant_remote_process( instant_remote_process(

View file

@ -278,7 +278,7 @@ private function stopContainers(array $containers, $server)
foreach ($containersToStop as $containerName) { foreach ($containersToStop as $containerName) {
instant_remote_process(command: [ instant_remote_process(command: [
"docker stop --time=30 $containerName", "docker stop -t 30 $containerName",
"docker rm -f $containerName", "docker rm -f $containerName",
], server: $server, throwError: false); ], server: $server, throwError: false);
} }

View file

@ -81,7 +81,7 @@ public function mount()
'destination_id' => $destination->id, 'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(), 'destination_type' => $destination->getMorphClass(),
]; ];
if ($oneClickServiceName === 'cloudflared' || $oneClickServiceName === 'pgadmin') { if (in_array($oneClickServiceName, NEEDS_TO_CONNECT_TO_PREDEFINED_NETWORK)) {
data_set($service_payload, 'connect_to_docker_network', true); data_set($service_payload, 'connect_to_docker_network', true);
} }
$service = Service::create($service_payload); $service = Service::create($service_payload);
@ -102,13 +102,33 @@ public function mount()
} }
}); });
} }
$service->parse(isNew: true); $service->parse(isNew: true);
return redirect()->route('project.service.configuration', [ // For Beszel service disable gzip (fixes realtime not working issue)
'service_uuid' => $service->uuid, if ($oneClickServiceName === 'beszel') {
'environment_uuid' => $environment->uuid, $appService = $service->applications()->whereName('beszel')->first();
'project_uuid' => $project->uuid, if ($appService) {
]); $appService->is_gzip_enabled = false;
$appService->save();
}
}
// For Appwrite services, disable strip prefix for services that handle domain requests
if ($oneClickServiceName === 'appwrite') {
$servicesToDisableStripPrefix = ['appwrite', 'appwrite-console', 'appwrite-realtime'];
foreach ($servicesToDisableStripPrefix as $serviceName) {
$appService = $service->applications()->whereName($serviceName)->first();
if ($appService) {
$appService->is_stripprefix_enabled = false;
$appService->save();
}
}
}
return redirect()->route('project.service.configuration', [
'service_uuid' => $service->uuid,
'environment_uuid' => $environment->uuid,
'project_uuid' => $project->uuid,
]);
} }
} }
$this->type = $type->value(); $this->type = $type->value();

View file

@ -90,9 +90,9 @@ protected function getTraefikVersions(): ?array
return is_array($traefikVersions) ? $traefikVersions : null; return is_array($traefikVersions) ? $traefikVersions : null;
} }
public function getConfigurationFilePathProperty() public function getConfigurationFilePathProperty(): string
{ {
return $this->server->proxyPath().'docker-compose.yml'; return rtrim($this->server->proxyPath(), '/') . '/docker-compose.yml';
} }
public function changeProxy() public function changeProxy()

View file

@ -67,4 +67,8 @@
'alpine', 'alpine',
]; ];
const NEEDS_TO_CONNECT_TO_PREDEFINED_NETWORK = [
'pgadmin',
'postgresus',
];
const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment']; const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment'];

View file

@ -2,7 +2,7 @@
return [ return [
'coolify' => [ 'coolify' => [
'version' => '4.0.0-beta.451', 'version' => '4.0.0-beta.452',
'helper_version' => '1.0.12', 'helper_version' => '1.0.12',
'realtime_version' => '1.0.10', 'realtime_version' => '1.0.10',
'self_hosted' => env('SELF_HOSTED', true), 'self_hosted' => env('SELF_HOSTED', true),

View file

@ -1,10 +1,10 @@
{ {
"coolify": { "coolify": {
"v4": { "v4": {
"version": "4.0.0-beta.451" "version": "4.0.0-beta.452"
}, },
"nightly": { "nightly": {
"version": "4.0.0-beta.452" "version": "4.0.0-beta.453"
}, },
"helper": { "helper": {
"version": "1.0.12" "version": "1.0.12"

View file

@ -3,7 +3,9 @@
<x-slide-over @startdatabase.window="slideOverOpen = true" closeWithX fullScreen> <x-slide-over @startdatabase.window="slideOverOpen = true" closeWithX fullScreen>
<x-slot:title>Database Startup</x-slot:title> <x-slot:title>Database Startup</x-slot:title>
<x-slot:content> <x-slot:content>
<livewire:activity-monitor header="Logs" fullHeight /> <div wire:ignore>
<livewire:activity-monitor header="Logs" fullHeight />
</div>
</x-slot:content> </x-slot:content>
</x-slide-over> </x-slide-over>
<div class="navbar-main"> <div class="navbar-main">

View file

@ -225,7 +225,9 @@ class="flex-1 p-6 border-2 rounded-sm cursor-pointer transition-all"
<x-slide-over @databaserestore.window="slideOverOpen = true" closeWithX fullScreen> <x-slide-over @databaserestore.window="slideOverOpen = true" closeWithX fullScreen>
<x-slot:title>Database Restore Output</x-slot:title> <x-slot:title>Database Restore Output</x-slot:title>
<x-slot:content> <x-slot:content>
<livewire:activity-monitor wire:key="database-restore-{{ $resource->uuid }}" header="Logs" fullHeight /> <div wire:ignore>
<livewire:activity-monitor wire:key="database-restore-{{ $resource->uuid }}" header="Logs" fullHeight />
</div>
</x-slot:content> </x-slot:content>
</x-slide-over> </x-slide-over>
@else @else

View file

@ -1,10 +1,10 @@
{ {
"coolify": { "coolify": {
"v4": { "v4": {
"version": "4.0.0-beta.451" "version": "4.0.0-beta.452"
}, },
"nightly": { "nightly": {
"version": "4.0.0-beta.452" "version": "4.0.0-beta.453"
}, },
"helper": { "helper": {
"version": "1.0.12" "version": "1.0.12"