Merge branch 'next' into improve-scheduled-tasks

This commit is contained in:
Andras Bacsai 2025-11-11 11:32:15 +01:00 committed by GitHub
commit f9ab2a7ca8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 77 additions and 76 deletions

View file

@ -10,6 +10,7 @@
use App\Models\ServiceDatabase;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Lorisleiva\Actions\Concerns\AsAction;
class GetContainersStatus
@ -376,6 +377,10 @@ public function handle(Server $server, ?Collection $containers = null, ?Collecti
if (isset($this->applicationContainerRestartCounts) && $this->applicationContainerRestartCounts->has($applicationId)) {
$containerRestartCounts = $this->applicationContainerRestartCounts->get($applicationId);
$maxRestartCount = $containerRestartCounts->max() ?? 0;
}
// Wrap all database updates in a transaction to ensure consistency
DB::transaction(function () use ($application, $maxRestartCount, $containerStatuses) {
$previousRestartCount = $application->restart_count ?? 0;
if ($maxRestartCount > $previousRestartCount) {
@ -398,18 +403,18 @@ public function handle(Server $server, ?Collection $containers = null, ?Collecti
$url = null;
}
}
}
// Aggregate status after tracking restart counts
$aggregatedStatus = $this->aggregateApplicationStatus($application, $containerStatuses, $maxRestartCount);
if ($aggregatedStatus) {
$statusFromDb = $application->status;
if ($statusFromDb !== $aggregatedStatus) {
$application->update(['status' => $aggregatedStatus]);
} else {
$application->update(['last_online_at' => now()]);
// Aggregate status after tracking restart counts
$aggregatedStatus = $this->aggregateApplicationStatus($application, $containerStatuses, $maxRestartCount);
if ($aggregatedStatus) {
$statusFromDb = $application->status;
if ($statusFromDb !== $aggregatedStatus) {
$application->update(['status' => $aggregatedStatus]);
} else {
$application->update(['last_online_at' => now()]);
}
}
}
});
}
}

View file

@ -20,22 +20,23 @@ public function handle(Service $service, bool $pullLatestImages = false, bool $s
}
$service->saveComposeConfigs();
$service->isConfigurationChanged(save: true);
$commands[] = 'cd '.$service->workdir();
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
// Ensure .env file exists before docker compose tries to load it
$workdir = $service->workdir();
// $commands[] = "cd {$workdir}";
$commands[] = "echo 'Saved configuration files to {$workdir}.'";
// Ensure .env exists in the correct directory before docker compose tries to load it
// This is defensive programming - saveComposeConfigs() already creates it,
// but we guarantee it here in case of any edge cases or manual deployments
$commands[] = 'touch .env';
$commands[] = "touch {$workdir}/.env";
if ($pullLatestImages) {
$commands[] = "echo 'Pulling images.'";
$commands[] = 'docker compose pull';
$commands[] = "docker compose --project-directory {$workdir} pull";
}
if ($service->networks()->count() > 0) {
$commands[] = "echo 'Creating Docker network.'";
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
}
$commands[] = 'echo Starting service.';
$commands[] = 'docker compose up -d --remove-orphans --force-recreate --build';
$commands[] = "docker compose --project-directory {$workdir} -f {$workdir}/docker-compose.yml --project-name {$service->uuid} up -d --remove-orphans --force-recreate --build";
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
if (data_get($service, 'connect_to_docker_network')) {
$compose = data_get($service, 'docker_compose', []);

View file

@ -222,9 +222,14 @@ private function cleanup_stucked_resources()
try {
$scheduled_backups = ScheduledDatabaseBackup::all();
foreach ($scheduled_backups as $scheduled_backup) {
if (! $scheduled_backup->server()) {
echo "Deleting stuck scheduledbackup: {$scheduled_backup->name}\n";
$scheduled_backup->delete();
try {
$server = $scheduled_backup->server();
if (! $server) {
echo "Deleting stuck scheduledbackup: {$scheduled_backup->name}\n";
$scheduled_backup->delete();
}
} catch (\Throwable $e) {
echo "Error checking server for scheduledbackup {$scheduled_backup->id}: {$e->getMessage()}\n";
}
}
} catch (\Throwable $e) {
@ -416,7 +421,7 @@ private function cleanup_stucked_resources()
foreach ($serviceApplications as $service) {
if (! data_get($service, 'service')) {
echo 'ServiceApplication without service: '.$service->name.'\n';
DeleteResourceJob::dispatch($service);
$service->forceDelete();
continue;
}
@ -429,7 +434,7 @@ private function cleanup_stucked_resources()
foreach ($serviceDatabases as $service) {
if (! data_get($service, 'service')) {
echo 'ServiceDatabase without service: '.$service->name.'\n';
DeleteResourceJob::dispatch($service);
$service->forceDelete();
continue;
}

View file

@ -86,6 +86,7 @@ public function handle()
$this->call('cleanup:stucked-resources');
} catch (\Throwable $e) {
echo "Error in cleanup:stucked-resources command: {$e->getMessage()}\n";
echo "Continuing with initialization - cleanup errors will not prevent Coolify from starting\n";
}
try {
$updatedCount = ApplicationDeploymentQueue::whereIn('status', [

View file

@ -275,16 +275,6 @@ public function manual(Request $request)
instant_remote_process(["docker rm -f {$deployment_uuid}"], $server);
$activeDeployment->addLogEntry('Deployment container stopped.');
}
// Kill running process if process ID exists
if ($activeDeployment->current_process_id) {
try {
$processKillCommand = "kill -9 {$activeDeployment->current_process_id}";
instant_remote_process([$processKillCommand], $server);
} catch (\Throwable $e) {
// Process might already be gone
}
}
} catch (\Throwable $e) {
// Silently handle errors during deployment cancellation
}
@ -555,15 +545,6 @@ public function normal(Request $request)
$activeDeployment->addLogEntry('Deployment container stopped.');
}
// Kill running process if process ID exists
if ($activeDeployment->current_process_id) {
try {
$processKillCommand = "kill -9 {$activeDeployment->current_process_id}";
instant_remote_process([$processKillCommand], $server);
} catch (\Throwable $e) {
// Process might already be gone
}
}
} catch (\Throwable $e) {
// Silently handle errors during deployment cancellation
}

View file

@ -1173,7 +1173,10 @@ private function generate_runtime_environment_variables()
if ($this->build_pack !== 'dockercompose') {
$detectedPort = $this->application->detectPortFromEnvironment(false);
if ($detectedPort && ! empty($ports) && ! in_array($detectedPort, $ports)) {
ray()->orange("PORT environment variable ({$detectedPort}) does not match configured ports_exposes: ".implode(',', $ports));
$this->application_deployment_queue->addLogEntry(
"Warning: PORT environment variable ({$detectedPort}) does not match configured ports_exposes: ".implode(',', $ports).'. It could case "bad gateway" or "no server" errors. Check the "General" page to fix it.',
'stderr'
);
}
}
@ -3062,7 +3065,6 @@ private function start_by_compose_file()
{
// Ensure .env file exists before docker compose tries to load it (defensive programming)
$this->execute_remote_command(
["touch {$this->workdir}/.env", 'hidden' => true],
["touch {$this->configuration_dir}/.env", 'hidden' => true],
);

View file

@ -145,25 +145,17 @@ private function deleteApplicationPreview()
// Check if helper container exists and kill it
$deployment_uuid = $activeDeployment->deployment_uuid;
$checkCommand = "docker ps -a --filter name={$deployment_uuid} --format '{{.Names}}'";
$escapedDeploymentUuid = escapeshellarg($deployment_uuid);
$checkCommand = "docker ps -a --filter name={$escapedDeploymentUuid} --format '{{.Names}}'";
$containerExists = instant_remote_process([$checkCommand], $server);
if ($containerExists && str($containerExists)->trim()->isNotEmpty()) {
instant_remote_process(["docker rm -f {$deployment_uuid}"], $server);
instant_remote_process(["docker rm -f {$escapedDeploymentUuid}"], $server);
$activeDeployment->addLogEntry('Deployment container stopped.');
} else {
$activeDeployment->addLogEntry('Helper container not yet started. Deployment will be cancelled when job checks status.');
}
// Kill running process if process ID exists
if ($activeDeployment->current_process_id) {
try {
$processKillCommand = "kill -9 {$activeDeployment->current_process_id}";
instant_remote_process([$processKillCommand], $server);
} catch (\Throwable $e) {
// Process might already be gone
}
}
} catch (\Throwable $e) {
// Silently handle errors during deployment cancellation
}
@ -171,7 +163,8 @@ private function deleteApplicationPreview()
try {
if ($server->isSwarm()) {
instant_remote_process(["docker stack rm {$application->uuid}-{$pull_request_id}"], $server);
$escapedStackName = escapeshellarg("{$application->uuid}-{$pull_request_id}");
instant_remote_process(["docker stack rm {$escapedStackName}"], $server);
} else {
$containers = getCurrentApplicationContainerStatus($server, $application->id, $pull_request_id)->toArray();
$this->stopPreviewContainers($containers, $server);

View file

@ -94,14 +94,6 @@ public function deploy(bool $force_rebuild = false)
return;
}
// Reset restart count on deployment
$this->application->update([
'restart_count' => 0,
'last_restart_at' => null,
'last_restart_type' => null,
]);
$this->setDeploymentUuid();
$result = queue_application_deployment(
application: $this->application,
@ -109,11 +101,18 @@ public function deploy(bool $force_rebuild = false)
force_rebuild: $force_rebuild,
);
if ($result['status'] === 'skipped') {
$this->dispatch('success', 'Deployment skipped', $result['message']);
$this->dispatch('error', 'Deployment skipped', $result['message']);
return;
}
// Reset restart count on successful deployment
$this->application->update([
'restart_count' => 0,
'last_restart_at' => null,
'last_restart_type' => null,
]);
return $this->redirectRoute('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
@ -146,13 +145,6 @@ public function restart()
return;
}
// Reset restart count on manual restart
$this->application->update([
'restart_count' => 0,
'last_restart_at' => now(),
'last_restart_type' => 'manual',
]);
$this->setDeploymentUuid();
$result = queue_application_deployment(
application: $this->application,
@ -165,6 +157,13 @@ public function restart()
return;
}
// Reset restart count on manual restart
$this->application->update([
'restart_count' => 0,
'last_restart_at' => now(),
'last_restart_type' => 'manual',
]);
return $this->redirectRoute('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'],

View file

@ -149,9 +149,10 @@ public function submit($notify = true)
$this->service->save();
$this->service->saveExtraFields($this->fields);
$this->service->parse();
$this->service->refresh();
$this->service->saveComposeConfigs();
});
// Refresh and write files after a successful commit
$this->service->refresh();
$this->service->saveComposeConfigs();
$this->dispatch('refreshEnvs');
$this->dispatch('refreshServices');

View file

@ -1302,7 +1302,13 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
}
// Auto-inject .env file so Coolify environment variables are available inside containers
// This makes Applications behave consistently with manual .env file usage
$payload['env_file'] = ['.env'];
$existingEnvFiles = data_get($service, 'env_file');
$envFiles = collect(is_null($existingEnvFiles) ? [] : (is_array($existingEnvFiles) ? $existingEnvFiles : [$existingEnvFiles]))
->push('.env')
->unique()
->values();
$payload['env_file'] = $envFiles;
if ($isPullRequest) {
$serviceName = addPreviewDeploymentSuffix($serviceName, $pullRequestId);
}
@ -2284,7 +2290,13 @@ function serviceParser(Service $resource): Collection
}
// Auto-inject .env file so Coolify environment variables are available inside containers
// This makes Services behave consistently with Applications
$payload['env_file'] = ['.env'];
$existingEnvFiles = data_get($service, 'env_file');
$envFiles = collect(is_null($existingEnvFiles) ? [] : (is_array($existingEnvFiles) ? $existingEnvFiles : [$existingEnvFiles]))
->push('.env')
->unique()
->values();
$payload['env_file'] = $envFiles;
$parsedServices->put($serviceName, $payload);
}

View file

@ -76,6 +76,7 @@ services:
openpanel-worker:
image: lindesvard/openpanel-worker:latest
environment:
- DISABLE_BULLBOARD=${DISABLE_BULLBOARD:-1}
- NODE_ENV=production
- NEXT_PUBLIC_SELF_HOSTED=true
- SERVICE_URL_OPBULLBOARD

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long