From 1e7b15ae77f009ef90c2a8d576dd56dbf189079a Mon Sep 17 00:00:00 2001 From: Laurence Date: Wed, 9 Apr 2025 17:52:12 +0100 Subject: [PATCH 01/36] enhance: Add missing UUID to openapi spec --- .../Api/ApplicationsController.php | 146 ++++++++++-------- app/Http/Controllers/Api/DeployController.php | 34 ++++ .../Controllers/Api/ProjectController.php | 12 ++ .../Controllers/Api/ServicesController.php | 12 ++ openapi.yaml | 66 ++++++++ 5 files changed, 203 insertions(+), 67 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 45968b6c6..5d94d7ab8 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -1610,6 +1610,18 @@ class ApplicationsController extends Controller ['bearerAuth' => []], ], tags: ['Applications'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the application.', + required: true, + schema: new OA\Schema( + type: 'string', + format: 'uuid', + ) + ), + ], requestBody: new OA\RequestBody( description: 'Application updated.', required: true, @@ -3006,73 +3018,73 @@ class ApplicationsController extends Controller // ]); // } - private function validateDataApplications(Request $request, Server $server) - { - $teamId = getTeamIdFromToken(); + private function validateDataApplications(Request $request, Server $server) + { + $teamId = getTeamIdFromToken(); - // Validate ports_mappings - if ($request->has('ports_mappings')) { - $ports = []; - foreach (explode(',', $request->ports_mappings) as $portMapping) { - $port = explode(':', $portMapping); - if (in_array($port[0], $ports)) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => [ - 'ports_mappings' => 'The first number before : should be unique between mappings.', - ], - ], 422); - } - $ports[] = $port[0]; - } - } - // Validate custom_labels - if ($request->has('custom_labels')) { - if (! isBase64Encoded($request->custom_labels)) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => [ - 'custom_labels' => 'The custom_labels should be base64 encoded.', - ], - ], 422); - } - $customLabels = base64_decode($request->custom_labels); - if (mb_detect_encoding($customLabels, 'ASCII', true) === false) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => [ - 'custom_labels' => 'The custom_labels should be base64 encoded.', - ], - ], 422); - } - } - if ($request->has('domains') && $server->isProxyShouldRun()) { - $uuid = $request->uuid; - $fqdn = $request->domains; - $fqdn = str($fqdn)->replaceEnd(',', '')->trim(); - $fqdn = str($fqdn)->replaceStart(',', '')->trim(); - $errors = []; - $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) { - if (filter_var($domain, FILTER_VALIDATE_URL) === false) { - $errors[] = 'Invalid domain: '.$domain; - } + // Validate ports_mappings + if ($request->has('ports_mappings')) { + $ports = []; + foreach (explode(',', $request->ports_mappings) as $portMapping) { + $port = explode(':', $portMapping); + if (in_array($port[0], $ports)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'ports_mappings' => 'The first number before : should be unique between mappings.', + ], + ], 422); + } + $ports[] = $port[0]; + } + } + // Validate custom_labels + if ($request->has('custom_labels')) { + if (! isBase64Encoded($request->custom_labels)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'custom_labels' => 'The custom_labels should be base64 encoded.', + ], + ], 422); + } + $customLabels = base64_decode($request->custom_labels); + if (mb_detect_encoding($customLabels, 'ASCII', true) === false) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'custom_labels' => 'The custom_labels should be base64 encoded.', + ], + ], 422); + } + } + if ($request->has('domains') && $server->isProxyShouldRun()) { + $uuid = $request->uuid; + $fqdn = $request->domains; + $fqdn = str($fqdn)->replaceEnd(',', '')->trim(); + $fqdn = str($fqdn)->replaceStart(',', '')->trim(); + $errors = []; + $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) { + if (filter_var($domain, FILTER_VALIDATE_URL) === false) { + $errors[] = 'Invalid domain: '.$domain; + } - return str($domain)->trim()->lower(); - }); - if (count($errors) > 0) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => $errors, - ], 422); - } - if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => [ - 'domains' => 'One of the domain is already used.', - ], - ], 422); - } - } - } + return str($domain)->trim()->lower(); + }); + if (count($errors) > 0) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $errors, + ], 422); + } + if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'domains' => 'One of the domain is already used.', + ], + ], 422); + } + } + } } diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 424c2cc76..6b9f40fca 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -333,6 +333,40 @@ class DeployController extends Controller ['bearerAuth' => []], ], tags: ['Deployments'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the application.', + required: true, + schema: new OA\Schema( + type: 'string', + format: 'uuid', + ) + ), + new OA\Parameter( + name: 'skip', + in: 'query', + description: 'Number of records to skip.', + required: false, + schema: new OA\Schema( + type: 'integer', + minimum: 0, + default: 0, + ) + ), + new OA\Parameter( + name: 'take', + in: 'query', + description: 'Number of records to take.', + required: false, + schema: new OA\Schema( + type: 'integer', + minimum: 1, + default: 10, + ) + ), + ], responses: [ new OA\Response( response: 200, diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php index b94ce9c67..98637c3e8 100644 --- a/app/Http/Controllers/Api/ProjectController.php +++ b/app/Http/Controllers/Api/ProjectController.php @@ -267,6 +267,18 @@ class ProjectController extends Controller ['bearerAuth' => []], ], tags: ['Projects'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the project.', + required: true, + schema: new OA\Schema( + type: 'string', + format: 'uuid', + ) + ), + ], requestBody: new OA\RequestBody( required: true, description: 'Project updated.', diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index 027bd5c1c..c3730d83f 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -527,6 +527,18 @@ class ServicesController extends Controller ['bearerAuth' => []], ], tags: ['Services'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the service.', + required: true, + schema: new OA\Schema( + type: 'string', + format: 'uuid', + ) + ), + ], requestBody: new OA\RequestBody( description: 'Service updated.', required: true, diff --git a/openapi.yaml b/openapi.yaml index a9c538218..6cd179c34 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1277,6 +1277,15 @@ paths: summary: Update description: 'Update application by UUID.' operationId: update-application-by-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the application.' + required: true + schema: + type: string + format: uuid requestBody: description: 'Application updated.' required: true @@ -3135,6 +3144,33 @@ paths: summary: 'List application deployments' description: 'List application deployments by using the app uuid' operationId: list-deployments-by-app-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the application.' + required: true + schema: + type: string + format: uuid + - + name: skip + in: query + description: 'Number of records to skip.' + required: false + schema: + type: integer + default: 0 + minimum: 0 + - + name: take + in: query + description: 'Number of records to take.' + required: false + schema: + type: integer + default: 10 + minimum: 1 responses: '200': description: 'List application deployments by using the app uuid.' @@ -3377,6 +3413,15 @@ paths: summary: Update description: 'Update Project.' operationId: update-project-by-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the project.' + required: true + schema: + type: string + format: uuid requestBody: description: 'Project updated.' required: true @@ -3630,6 +3675,14 @@ paths: $ref: '#/components/responses/400' '404': description: 'Private Key not found.' + '422': + description: 'Private Key is in use and cannot be deleted.' + content: + application/json: + schema: + properties: + message: { type: string, example: 'Private Key is in use and cannot be deleted.' } + type: object security: - bearerAuth: [] @@ -4145,6 +4198,15 @@ paths: summary: Update description: 'Update service by UUID.' operationId: update-service-by-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + format: uuid requestBody: description: 'Service updated.' required: true @@ -4769,6 +4831,10 @@ components: type: string nullable: true description: 'Ports mappings.' + custom_network_aliases: + type: string + nullable: true + description: 'Network aliases for Docker container.' base_directory: type: string description: 'Base directory for all commands.' From 11c1babc850207bc4624347999d4ed27c0e258b5 Mon Sep 17 00:00:00 2001 From: elmariss Date: Thu, 10 Apr 2025 01:06:01 +0200 Subject: [PATCH 02/36] fix check if image changed --- bootstrap/helpers/shared.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index b90de4dbc..117e5c9dc 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -2987,7 +2987,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $predefinedPort = '8000'; } if ($isDatabase) { - $applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first(); + $applicationFound = ServiceApplication::where('name', $serviceName)->where('service_id', $resource->id)->first(); if ($applicationFound) { $savedService = $applicationFound; $savedService = ServiceDatabase::firstOrCreate([ @@ -2999,17 +2999,22 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int } else { $savedService = ServiceDatabase::firstOrCreate([ 'name' => $serviceName, - 'image' => $image, 'service_id' => $resource->id, ]); } } else { $savedService = ServiceApplication::firstOrCreate([ 'name' => $serviceName, - 'image' => $image, 'service_id' => $resource->id, ]); } + + // Check if image changed + if ($savedService->image !== $image) { + $savedService->image = $image; + $savedService->save(); + } + $environment = collect(data_get($service, 'environment', [])); $buildArgs = collect(data_get($service, 'build.args', [])); $environment = $environment->merge($buildArgs); From e8ff21b3cadb2aa2fab4b402b7cbe70fc7cce8d0 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 10 Apr 2025 16:20:41 +0200 Subject: [PATCH 03/36] chore(versions): bump version to 408 for coolify and 409 for nightly --- config/constants.php | 2 +- other/nightly/versions.json | 4 ++-- versions.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/constants.php b/config/constants.php index 439f32940..df53b82ea 100644 --- a/config/constants.php +++ b/config/constants.php @@ -2,7 +2,7 @@ return [ 'coolify' => [ - 'version' => '4.0.0-beta.407', + 'version' => '4.0.0-beta.408', 'helper_version' => '1.0.8', 'realtime_version' => '1.0.6', 'self_hosted' => env('SELF_HOSTED', true), diff --git a/other/nightly/versions.json b/other/nightly/versions.json index 50a917a8d..bfcae55e3 100644 --- a/other/nightly/versions.json +++ b/other/nightly/versions.json @@ -1,10 +1,10 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.407" + "version": "4.0.0-beta.408" }, "nightly": { - "version": "4.0.0-beta.408" + "version": "4.0.0-beta.409" }, "helper": { "version": "1.0.8" diff --git a/versions.json b/versions.json index 50a917a8d..bfcae55e3 100644 --- a/versions.json +++ b/versions.json @@ -1,10 +1,10 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.407" + "version": "4.0.0-beta.408" }, "nightly": { - "version": "4.0.0-beta.408" + "version": "4.0.0-beta.409" }, "helper": { "version": "1.0.8" From abf922b392c1370eda6bc2e55c40525219aec799 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 10 Apr 2025 16:25:53 +0200 Subject: [PATCH 04/36] feat(GithubApp): update default events to include 'pull_request' and streamline event handling --- resources/views/livewire/source/github/change.blade.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/resources/views/livewire/source/github/change.blade.php b/resources/views/livewire/source/github/change.blade.php index 1b5493c58..a47452437 100644 --- a/resources/views/livewire/source/github/change.blade.php +++ b/resources/views/livewire/source/github/change.blade.php @@ -277,12 +277,15 @@ emails: 'read', administration: 'read' }; + const default_events = ['push']; if (preview_deployment_permissions) { default_permissions.pull_requests = 'write'; + default_events.push('pull_request'); } if (administration) { default_permissions.administration = 'write'; } + const data = { name, url: baseUrl, @@ -297,7 +300,7 @@ setup_url: `${webhookBaseUrl}/source/github/install?source=${uuid}`, setup_on_update: true, default_permissions, - default_events: ['pull_request', 'push'] + default_events }; const form = document.createElement('form'); form.setAttribute('method', 'post'); From 324e0d1cd85c7fee5f93bbe7d0d0c047378ed226 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 10 Apr 2025 18:20:46 +0200 Subject: [PATCH 05/36] feat(CleanupDocker): add support for realtime image management in Docker cleanup process --- app/Actions/Server/CleanupDocker.php | 11 +++++++++++ config/constants.php | 1 + 2 files changed, 12 insertions(+) diff --git a/app/Actions/Server/CleanupDocker.php b/app/Actions/Server/CleanupDocker.php index ba4c2311a..754feecb1 100644 --- a/app/Actions/Server/CleanupDocker.php +++ b/app/Actions/Server/CleanupDocker.php @@ -14,15 +14,26 @@ class CleanupDocker public function handle(Server $server) { $settings = instanceSettings(); + $realtimeImage = config('constants.coolify.realtime_image'); + $realtimeImageVersion = config('constants.coolify.realtime_version'); + $realtimeImageWithVersion = "$realtimeImage:$realtimeImageVersion"; + $realtimeImageWithoutPrefix = 'coollabsio/coolify-realtime'; + $realtimeImageWithoutPrefixVersion = "coollabsio/coolify-realtime:$realtimeImageVersion"; + $helperImageVersion = data_get($settings, 'helper_version'); $helperImage = config('constants.coolify.helper_image'); $helperImageWithVersion = "$helperImage:$helperImageVersion"; + $helperImageWithoutPrefix = 'coollabsio/coolify-helper'; + $helperImageWithoutPrefixVersion = "coollabsio/coolify-helper:$helperImageVersion"; $commands = [ 'docker container prune -f --filter "label=coolify.managed=true" --filter "label!=coolify.proxy=true"', 'docker image prune -af --filter "label!=coolify.managed=true"', 'docker builder prune -af', "docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi -f", + "docker images --filter before=$realtimeImageWithVersion --filter reference=$realtimeImage | grep $realtimeImage | awk '{print $3}' | xargs -r docker rmi -f", + "docker images --filter before=$helperImageWithoutPrefixVersion --filter reference=$helperImageWithoutPrefix | grep $helperImageWithoutPrefix | awk '{print $3}' | xargs -r docker rmi -f", + "docker images --filter before=$realtimeImageWithoutPrefixVersion --filter reference=$realtimeImageWithoutPrefix | grep $realtimeImageWithoutPrefix | awk '{print $3}' | xargs -r docker rmi -f", ]; if ($server->settings->delete_unused_volumes) { diff --git a/config/constants.php b/config/constants.php index df53b82ea..519ed55bb 100644 --- a/config/constants.php +++ b/config/constants.php @@ -10,6 +10,7 @@ return [ 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), 'registry_url' => env('REGISTRY_URL', 'ghcr.io'), 'helper_image' => env('HELPER_IMAGE', env('REGISTRY_URL', 'ghcr.io').'/coollabsio/coolify-helper'), + 'realtime_image' => env('REALTIME_IMAGE', env('REGISTRY_URL', 'ghcr.io').'/coollabsio/coolify-realtime'), 'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false), ], From d07524128910f8d183f31c5f5d31f96209f0ccb7 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 11 Apr 2025 15:27:56 +0200 Subject: [PATCH 06/36] feat(Deployment): enhance queue_application_deployment to handle existing deployments and return appropriate status messages --- .../Api/ApplicationsController.php | 59 ++++++++++++--- app/Http/Controllers/Api/DeployController.php | 13 ++-- app/Http/Controllers/Webhook/Bitbucket.php | 40 +++++++---- app/Http/Controllers/Webhook/Gitea.php | 42 +++++++---- app/Http/Controllers/Webhook/Github.php | 72 +++++++++++++------ app/Http/Controllers/Webhook/Gitlab.php | 43 +++++++---- app/Jobs/ApplicationDeploymentJob.php | 2 +- app/Livewire/Project/Application/Heading.php | 14 +++- app/Livewire/Project/Application/Previews.php | 7 +- app/Livewire/Project/Shared/Destination.php | 7 +- bootstrap/helpers/applications.php | 59 ++++++++++++--- 11 files changed, 272 insertions(+), 86 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 5d94d7ab8..aa5f2ff14 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -880,12 +880,17 @@ class ApplicationsController extends Controller if ($instantDeploy) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, no_questions_asked: true, is_api: true, ); + if ($result['status'] === 'skipped') { + return response()->json([ + 'message' => $result['message'], + ], 200); + } } else { if ($application->build_pack === 'dockercompose') { LoadComposeFile::dispatch($application); @@ -1004,12 +1009,17 @@ class ApplicationsController extends Controller if ($instantDeploy) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, no_questions_asked: true, is_api: true, ); + if ($result['status'] === 'skipped') { + return response()->json([ + 'message' => $result['message'], + ], 200); + } } else { if ($application->build_pack === 'dockercompose') { LoadComposeFile::dispatch($application); @@ -1101,12 +1111,17 @@ class ApplicationsController extends Controller if ($instantDeploy) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, no_questions_asked: true, is_api: true, ); + if ($result['status'] === 'skipped') { + return response()->json([ + 'message' => $result['message'], + ], 200); + } } else { if ($application->build_pack === 'dockercompose') { LoadComposeFile::dispatch($application); @@ -1190,12 +1205,17 @@ class ApplicationsController extends Controller if ($instantDeploy) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, no_questions_asked: true, is_api: true, ); + if ($result['status'] === 'skipped') { + return response()->json([ + 'message' => $result['message'], + ], 200); + } } return response()->json(serializeApiResponse([ @@ -1254,12 +1274,17 @@ class ApplicationsController extends Controller if ($instantDeploy) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, no_questions_asked: true, is_api: true, ); + if ($result['status'] === 'skipped') { + return response()->json([ + 'message' => $result['message'], + ], 200); + } } return response()->json(serializeApiResponse([ @@ -1896,11 +1921,16 @@ class ApplicationsController extends Controller if ($instantDeploy) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, is_api: true, ); + if ($result['status'] === 'skipped') { + return response()->json([ + 'message' => $result['message'], + ], 200); + } } return response()->json([ @@ -2717,13 +2747,21 @@ class ApplicationsController extends Controller $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, force_rebuild: $force, is_api: true, no_questions_asked: $instant_deploy ); + if ($result['status'] === 'skipped') { + return response()->json( + [ + 'message' => $result['message'], + ], + 200 + ); + } return response()->json( [ @@ -2878,12 +2916,17 @@ class ApplicationsController extends Controller $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, restart_only: true, is_api: true, ); + if ($result['status'] === 'skipped') { + return response()->json([ + 'message' => $result['message'], + ], 200); + } return response()->json( [ diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 6b9f40fca..00bb97f27 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -8,6 +8,7 @@ use App\Http\Controllers\Controller; use App\Models\Application; use App\Models\ApplicationDeploymentQueue; use App\Models\Server; +use App\Models\Service; use App\Models\Tag; use Illuminate\Http\Request; use OpenApi\Attributes as OA; @@ -297,17 +298,21 @@ class DeployController extends Controller return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid]; } switch ($resource?->getMorphClass()) { - case \App\Models\Application::class: + case Application::class: $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $resource, deployment_uuid: $deployment_uuid, force_rebuild: $force, pull_request_id: $pr, ); - $message = "Application {$resource->name} deployment queued."; + if ($result['status'] === 'skipped') { + $message = $result['message']; + } else { + $message = "Application {$resource->name} deployment queued."; + } break; - case \App\Models\Service::class: + case Service::class: StartService::run($resource); $message = "Service {$resource->name} started. It could take a while, be patient."; break; diff --git a/app/Http/Controllers/Webhook/Bitbucket.php b/app/Http/Controllers/Webhook/Bitbucket.php index 33d8f8532..490b66e58 100644 --- a/app/Http/Controllers/Webhook/Bitbucket.php +++ b/app/Http/Controllers/Webhook/Bitbucket.php @@ -100,18 +100,26 @@ class Bitbucket extends Controller if ($x_bitbucket_event === 'repo:push') { if ($application->isDeployable()) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, commit: $commit, force_rebuild: false, is_webhook: true ); - $return_payloads->push([ - 'application' => $application->name, - 'status' => 'success', - 'message' => 'Preview deployment queued.', - ]); + if ($result['status'] === 'skipped') { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'skipped', + 'message' => $result['message'], + ]); + } else { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'success', + 'message' => 'Deployment queued.', + ]); + } } else { $return_payloads->push([ 'application' => $application->name, @@ -143,7 +151,7 @@ class Bitbucket extends Controller ]); } } - queue_application_deployment( + $result = queue_application_deployment( application: $application, pull_request_id: $pull_request_id, deployment_uuid: $deployment_uuid, @@ -152,11 +160,19 @@ class Bitbucket extends Controller is_webhook: true, git_type: 'bitbucket' ); - $return_payloads->push([ - 'application' => $application->name, - 'status' => 'success', - 'message' => 'Preview deployment queued.', - ]); + if ($result['status'] === 'skipped') { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'skipped', + 'message' => $result['message'], + ]); + } else { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'success', + 'message' => 'Preview deployment queued.', + ]); + } } else { $return_payloads->push([ 'application' => $application->name, diff --git a/app/Http/Controllers/Webhook/Gitea.php b/app/Http/Controllers/Webhook/Gitea.php index 87fd2255f..3c3d6e0b6 100644 --- a/app/Http/Controllers/Webhook/Gitea.php +++ b/app/Http/Controllers/Webhook/Gitea.php @@ -116,19 +116,27 @@ class Gitea extends Controller $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); if ($is_watch_path_triggered || is_null($application->watch_paths)) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, force_rebuild: false, commit: data_get($payload, 'after', 'HEAD'), is_webhook: true, ); - $return_payloads->push([ - 'status' => 'success', - 'message' => 'Deployment queued.', - 'application_uuid' => $application->uuid, - 'application_name' => $application->name, - ]); + if ($result['status'] === 'skipped') { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'skipped', + 'message' => $result['message'], + ]); + } else { + $return_payloads->push([ + 'status' => 'success', + 'message' => 'Deployment queued.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + ]); + } } else { $paths = str($application->watch_paths)->explode("\n"); $return_payloads->push([ @@ -175,7 +183,7 @@ class Gitea extends Controller ]); } } - queue_application_deployment( + $result = queue_application_deployment( application: $application, pull_request_id: $pull_request_id, deployment_uuid: $deployment_uuid, @@ -184,11 +192,19 @@ class Gitea extends Controller is_webhook: true, git_type: 'gitea' ); - $return_payloads->push([ - 'application' => $application->name, - 'status' => 'success', - 'message' => 'Preview deployment queued.', - ]); + if ($result['status'] === 'skipped') { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'skipped', + 'message' => $result['message'], + ]); + } else { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'success', + 'message' => 'Preview deployment queued.', + ]); + } } else { $return_payloads->push([ 'application' => $application->name, diff --git a/app/Http/Controllers/Webhook/Github.php b/app/Http/Controllers/Webhook/Github.php index 882f2be8b..597ec023f 100644 --- a/app/Http/Controllers/Webhook/Github.php +++ b/app/Http/Controllers/Webhook/Github.php @@ -122,19 +122,29 @@ class Github extends Controller $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); if ($is_watch_path_triggered || is_null($application->watch_paths)) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, force_rebuild: false, commit: data_get($payload, 'after', 'HEAD'), is_webhook: true, ); - $return_payloads->push([ - 'status' => 'success', - 'message' => 'Deployment queued.', - 'application_uuid' => $application->uuid, - 'application_name' => $application->name, - ]); + if ($result['status'] === 'skipped') { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'skipped', + 'message' => $result['message'], + ]); + } else { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'success', + 'message' => 'Deployment queued.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + 'deployment_uuid' => $result['deployment_uuid'], + ]); + } } else { $paths = str($application->watch_paths)->explode("\n"); $return_payloads->push([ @@ -181,7 +191,8 @@ class Github extends Controller ]); } } - queue_application_deployment( + + $result = queue_application_deployment( application: $application, pull_request_id: $pull_request_id, deployment_uuid: $deployment_uuid, @@ -190,11 +201,19 @@ class Github extends Controller is_webhook: true, git_type: 'github' ); - $return_payloads->push([ - 'application' => $application->name, - 'status' => 'success', - 'message' => 'Preview deployment queued.', - ]); + if ($result['status'] === 'skipped') { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'skipped', + 'message' => $result['message'], + ]); + } else { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'success', + 'message' => 'Preview deployment queued.', + ]); + } } else { $return_payloads->push([ 'application' => $application->name, @@ -341,7 +360,7 @@ class Github extends Controller $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); if ($is_watch_path_triggered || is_null($application->watch_paths)) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, commit: data_get($payload, 'after', 'HEAD'), @@ -349,10 +368,11 @@ class Github extends Controller is_webhook: true, ); $return_payloads->push([ - 'status' => 'success', - 'message' => 'Deployment queued.', + 'status' => $result['status'], + 'message' => $result['message'], 'application_uuid' => $application->uuid, 'application_name' => $application->name, + 'deployment_uuid' => $result['deployment_uuid'], ]); } else { $paths = str($application->watch_paths)->explode("\n"); @@ -389,7 +409,7 @@ class Github extends Controller 'pull_request_html_url' => $pull_request_html_url, ]); } - queue_application_deployment( + $result = queue_application_deployment( application: $application, pull_request_id: $pull_request_id, deployment_uuid: $deployment_uuid, @@ -398,11 +418,19 @@ class Github extends Controller is_webhook: true, git_type: 'github' ); - $return_payloads->push([ - 'application' => $application->name, - 'status' => 'success', - 'message' => 'Preview deployment queued.', - ]); + if ($result['status'] === 'skipped') { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'skipped', + 'message' => $result['message'], + ]); + } else { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'success', + 'message' => 'Preview deployment queued.', + ]); + } } else { $return_payloads->push([ 'application' => $application->name, diff --git a/app/Http/Controllers/Webhook/Gitlab.php b/app/Http/Controllers/Webhook/Gitlab.php index cf6874b8c..d6d12a05f 100644 --- a/app/Http/Controllers/Webhook/Gitlab.php +++ b/app/Http/Controllers/Webhook/Gitlab.php @@ -142,19 +142,28 @@ class Gitlab extends Controller $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); if ($is_watch_path_triggered || is_null($application->watch_paths)) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, commit: data_get($payload, 'after', 'HEAD'), force_rebuild: false, is_webhook: true, ); - $return_payloads->push([ - 'status' => 'success', - 'message' => 'Deployment queued.', - 'application_uuid' => $application->uuid, - 'application_name' => $application->name, - ]); + if ($result['status'] === 'skipped') { + $return_payloads->push([ + 'status' => $result['status'], + 'message' => $result['message'], + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + ]); + } else { + $return_payloads->push([ + 'status' => 'success', + 'message' => 'Deployment queued.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + ]); + } } else { $paths = str($application->watch_paths)->explode("\n"); $return_payloads->push([ @@ -201,7 +210,7 @@ class Gitlab extends Controller ]); } } - queue_application_deployment( + $result = queue_application_deployment( application: $application, pull_request_id: $pull_request_id, deployment_uuid: $deployment_uuid, @@ -210,11 +219,19 @@ class Gitlab extends Controller is_webhook: true, git_type: 'gitlab' ); - $return_payloads->push([ - 'application' => $application->name, - 'status' => 'success', - 'message' => 'Preview Deployment queued', - ]); + if ($result['status'] === 'skipped') { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'skipped', + 'message' => $result['message'], + ]); + } else { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'success', + 'message' => 'Preview Deployment queued', + ]); + } } else { $return_payloads->push([ 'application' => $application->name, diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 6cf642f27..7bea73aa2 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1392,7 +1392,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue return; } foreach ($destination_ids as $destination_id) { - $destination = StandaloneDocker::find($destination_id); + $destination = StandaloneDocker::find($destination_id)->first(); $server = $destination->server; if ($server->team_id !== $this->mainServer->team_id) { $this->application_deployment_queue->addLogEntry("Skipping deployment to {$server->name}. Not in the same team?!"); diff --git a/app/Livewire/Project/Application/Heading.php b/app/Livewire/Project/Application/Heading.php index 0d7d7755f..475d2dfa8 100644 --- a/app/Livewire/Project/Application/Heading.php +++ b/app/Livewire/Project/Application/Heading.php @@ -84,11 +84,16 @@ class Heading extends Component return; } $this->setDeploymentUuid(); - queue_application_deployment( + $result = queue_application_deployment( application: $this->application, deployment_uuid: $this->deploymentUuid, force_rebuild: $force_rebuild, ); + if ($result['status'] === 'skipped') { + $this->dispatch('success', 'Deployment skipped', $result['message']); + + return; + } return $this->redirectRoute('project.application.deployment.show', [ 'project_uuid' => $this->parameters['project_uuid'], @@ -126,11 +131,16 @@ class Heading extends Component return; } $this->setDeploymentUuid(); - queue_application_deployment( + $result = queue_application_deployment( application: $this->application, deployment_uuid: $this->deploymentUuid, restart_only: true, ); + if ($result['status'] === 'skipped') { + $this->dispatch('success', 'Deployment skipped', $result['message']); + + return; + } return $this->redirectRoute('project.application.deployment.show', [ 'project_uuid' => $this->parameters['project_uuid'], diff --git a/app/Livewire/Project/Application/Previews.php b/app/Livewire/Project/Application/Previews.php index bdf62706c..88ce65c53 100644 --- a/app/Livewire/Project/Application/Previews.php +++ b/app/Livewire/Project/Application/Previews.php @@ -159,13 +159,18 @@ class Previews extends Component 'pull_request_html_url' => $pull_request_html_url, ]); } - queue_application_deployment( + $result = queue_application_deployment( application: $this->application, deployment_uuid: $this->deployment_uuid, force_rebuild: false, pull_request_id: $pull_request_id, git_type: $found->git_type ?? null, ); + if ($result['status'] === 'skipped') { + $this->dispatch('success', 'Deployment skipped', $result['message']); + + return; + } return redirect()->route('project.application.deployment.show', [ 'project_uuid' => $this->parameters['project_uuid'], diff --git a/app/Livewire/Project/Shared/Destination.php b/app/Livewire/Project/Shared/Destination.php index 1759fe08a..71a913add 100644 --- a/app/Livewire/Project/Shared/Destination.php +++ b/app/Livewire/Project/Shared/Destination.php @@ -79,7 +79,7 @@ class Destination extends Component $deployment_uuid = new Cuid2; $server = Server::ownedByCurrentTeam()->findOrFail($server_id); $destination = $server->standaloneDockers->where('id', $network_id)->firstOrFail(); - queue_application_deployment( + $result = queue_application_deployment( deployment_uuid: $deployment_uuid, application: $this->resource, server: $server, @@ -87,6 +87,11 @@ class Destination extends Component only_this_server: true, no_questions_asked: true, ); + if ($result['status'] === 'skipped') { + $this->dispatch('success', 'Deployment skipped', $result['message']); + + return; + } return redirect()->route('project.application.deployment.show', [ 'project_uuid' => data_get($this->resource, 'environment.project.uuid'), diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index d5283898e..3f1e8513c 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -24,6 +24,26 @@ function queue_application_deployment(Application $application, string $deployme if ($destination) { $destination_id = $destination->id; } + + // Check if there's already a deployment in progress or queued for this application and commit + $existing_deployment = ApplicationDeploymentQueue::where('application_id', $application_id) + ->where('commit', $commit) + ->whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value]) + ->first(); + + if ($existing_deployment) { + // If force_rebuild is true or rollback is true or no_questions_asked is true, we'll still create a new deployment + if (! $force_rebuild && ! $rollback && ! $no_questions_asked) { + // Return the existing deployment's details + return [ + 'status' => 'skipped', + 'message' => 'Deployment already queued for this commit.', + 'deployment_uuid' => $existing_deployment->deployment_uuid, + 'existing_deployment' => $existing_deployment, + ]; + } + } + $deployment = ApplicationDeploymentQueue::create([ 'application_id' => $application_id, 'application_name' => $application->name, @@ -47,11 +67,17 @@ function queue_application_deployment(Application $application, string $deployme ApplicationDeploymentJob::dispatch( application_deployment_queue_id: $deployment->id, ); - } elseif (next_queuable($server_id, $application_id)) { + } elseif (next_queuable($server_id, $application_id, $commit)) { ApplicationDeploymentJob::dispatch( application_deployment_queue_id: $deployment->id, ); } + + return [ + 'status' => 'queued', + 'message' => 'Deployment queued.', + 'deployment_uuid' => $deployment_uuid, + ]; } function force_start_deployment(ApplicationDeploymentQueue $deployment) { @@ -78,20 +104,35 @@ function queue_next_deployment(Application $application) } } -function next_queuable(string $server_id, string $application_id): bool +function next_queuable(string $server_id, string $application_id, string $commit = 'HEAD'): bool { - $deployments = ApplicationDeploymentQueue::where('server_id', $server_id)->whereIn('status', ['in_progress', ApplicationDeploymentStatus::QUEUED])->get()->sortByDesc('created_at'); - $same_application_deployments = $deployments->where('application_id', $application_id); - $in_progress = $same_application_deployments->filter(function ($value, $key) { - return $value->status === 'in_progress'; - }); - if ($in_progress->count() > 0) { + // Check if there's already a deployment in progress for this application and commit + $existing_deployment = ApplicationDeploymentQueue::where('application_id', $application_id) + ->where('commit', $commit) + ->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value) + ->first(); + + if ($existing_deployment) { return false; } + + // Check if there's any deployment in progress for this application + $in_progress = ApplicationDeploymentQueue::where('application_id', $application_id) + ->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value) + ->exists(); + + if ($in_progress) { + return false; + } + + // Check server's concurrent build limit $server = Server::find($server_id); $concurrent_builds = $server->settings->concurrent_builds; + $active_deployments = ApplicationDeploymentQueue::where('server_id', $server_id) + ->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value) + ->count(); - if ($deployments->count() > $concurrent_builds) { + if ($active_deployments >= $concurrent_builds) { return false; } From 61cc49d3170260f9642ce82aedb592a9f2839e58 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 11 Apr 2025 15:37:42 +0200 Subject: [PATCH 07/36] fix(DeployController): update request handling to use input method and enhance OpenAPI description for deployment endpoint --- app/Http/Controllers/Api/DeployController.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 00bb97f27..46606e24a 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -133,7 +133,7 @@ class DeployController extends Controller #[OA\Get( summary: 'Deploy', - description: 'Deploy by tag or uuid. `Post` request also accepted.', + description: 'Deploy by tag or uuid. `Post` request also accepted with `uuid` and `tag` json body.', path: '/deploy', operationId: 'deploy-by-tag-or-uuid', security: [ @@ -192,10 +192,10 @@ class DeployController extends Controller return invalidTokenResponse(); } - $uuids = $request->query->get('uuid'); - $tags = $request->query->get('tag'); - $force = $request->query->get('force') ?? false; - $pr = $request->query->get('pr') ? max((int) $request->query->get('pr'), 0) : 0; + $uuids = $request->input('uuid'); + $tags = $request->input('tag'); + $force = $request->input('force') ?? false; + $pr = $request->input('pr') ? max((int) $request->input('pr'), 0) : 0; if ($uuids && $tags) { return response()->json(['message' => 'You can only use uuid or tag, not both.'], 400); From 5ce57eda165bf85cd4038901731d5cdee7eb94c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Apr 2025 15:30:16 +0000 Subject: [PATCH 08/36] chore(deps-dev): bump vite from 6.2.4 to 6.2.6 Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.4 to 6.2.6. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v6.2.6/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v6.2.6/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-version: 6.2.6 dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index ff1e8f7ec..c995800ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "pusher-js": "8.4.0", "tailwind-scrollbar": "^3.1.0", "tailwindcss": "3.4.17", - "vite": "^6.2.4", + "vite": "^6.2.6", "vue": "3.5.13" } }, @@ -2994,9 +2994,9 @@ "license": "MIT" }, "node_modules/vite": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.4.tgz", - "integrity": "sha512-veHMSew8CcRzhL5o8ONjy8gkfmFJAd5Ac16oxBUjlwgX3Gq2Wqr+qNC3TjPIpy7TPV/KporLga5GT9HqdrCizw==", + "version": "6.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz", + "integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 43f10e373..ed933a4cf 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "pusher-js": "8.4.0", "tailwind-scrollbar": "^3.1.0", "tailwindcss": "3.4.17", - "vite": "^6.2.4", + "vite": "^6.2.6", "vue": "3.5.13" }, "dependencies": { From 4c8ebe146ca712a755ef5bddf787e71e59aa69df Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 11 Apr 2025 17:31:58 +0200 Subject: [PATCH 09/36] refactor(ApplicationDeploymentJob): streamline environment variable handling by introducing generate_coolify_env_variables method and consolidating logic for pull request and main branch scenarios --- app/Jobs/ApplicationDeploymentJob.php | 232 +++++++++++++++----------- 1 file changed, 139 insertions(+), 93 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 7bea73aa2..f97ff853c 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -899,100 +899,12 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $sorted_environment_variables_preview = $this->application->environment_variables_preview->sortBy('id'); } $ports = $this->application->main_port(); - if ($this->pull_request_id !== 0) { - $this->env_filename = ".env-pr-$this->pull_request_id"; - // Add SOURCE_COMMIT if not exists - if ($this->application->environment_variables_preview->where('key', 'SOURCE_COMMIT')->isEmpty()) { - if (! is_null($this->commit)) { - $envs->push("SOURCE_COMMIT={$this->commit}"); - } else { - $envs->push('SOURCE_COMMIT=unknown'); - } - } - if ($this->application->environment_variables_preview->where('key', 'COOLIFY_FQDN')->isEmpty()) { - $envs->push("COOLIFY_FQDN={$this->preview->fqdn}"); - $envs->push("COOLIFY_DOMAIN_URL={$this->preview->fqdn}"); - } - if ($this->application->environment_variables_preview->where('key', 'COOLIFY_URL')->isEmpty()) { - $url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', ''); - $envs->push("COOLIFY_URL={$url}"); - $envs->push("COOLIFY_DOMAIN_FQDN={$url}"); - } - if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') { - if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) { - $envs->push("COOLIFY_BRANCH=\"{$local_branch}\""); - } - if ($this->application->environment_variables_preview->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) { - $envs->push("COOLIFY_RESOURCE_UUID={$this->application->uuid}"); - } - if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { - $envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}"); - } - } - - add_coolify_default_environment_variables($this->application, $envs, $this->application->environment_variables_preview); - - foreach ($sorted_environment_variables_preview as $env) { - $real_value = $env->real_value; - if ($env->version === '4.0.0-beta.239') { - $real_value = $env->real_value; - } else { - if ($env->is_literal || $env->is_multiline) { - $real_value = '\''.$real_value.'\''; - } else { - $real_value = escapeEnvVariables($env->real_value); - } - } - $envs->push($env->key.'='.$real_value); - } - // Add PORT if not exists, use the first port as default - if ($this->build_pack !== 'dockercompose') { - if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) { - $envs->push("PORT={$ports[0]}"); - } - } - // Add HOST if not exists - if ($this->application->environment_variables_preview->where('key', 'HOST')->isEmpty()) { - $envs->push('HOST=0.0.0.0'); - } - } else { + $coolify_envs = $this->generate_coolify_env_variables(); + $coolify_envs->each(function ($item, $key) use ($envs) { + $envs->push($key.'='.$item); + }); + if ($this->pull_request_id === 0) { $this->env_filename = '.env'; - // Add SOURCE_COMMIT if not exists - if ($this->application->environment_variables->where('key', 'SOURCE_COMMIT')->isEmpty()) { - if (! is_null($this->commit)) { - $envs->push("SOURCE_COMMIT={$this->commit}"); - } else { - $envs->push('SOURCE_COMMIT=unknown'); - } - } - if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) { - if ((int) $this->application->compose_parsing_version >= 3) { - $envs->push("COOLIFY_URL={$this->application->fqdn}"); - } else { - $envs->push("COOLIFY_FQDN={$this->application->fqdn}"); - } - } - if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) { - $url = str($this->application->fqdn)->replace('http://', '')->replace('https://', ''); - if ((int) $this->application->compose_parsing_version >= 3) { - $envs->push("COOLIFY_FQDN={$url}"); - } else { - $envs->push("COOLIFY_URL={$url}"); - } - } - if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') { - if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) { - $envs->push("COOLIFY_BRANCH=\"{$local_branch}\""); - } - if ($this->application->environment_variables->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) { - $envs->push("COOLIFY_RESOURCE_UUID={$this->application->uuid}"); - } - if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { - $envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}"); - } - } - - add_coolify_default_environment_variables($this->application, $envs, $this->application->environment_variables); foreach ($sorted_environment_variables as $env) { $real_value = $env->real_value; @@ -1017,6 +929,32 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue if ($this->application->environment_variables->where('key', 'HOST')->isEmpty()) { $envs->push('HOST=0.0.0.0'); } + } else { + $this->env_filename = ".env-pr-$this->pull_request_id"; + foreach ($sorted_environment_variables_preview as $env) { + $real_value = $env->real_value; + if ($env->version === '4.0.0-beta.239') { + $real_value = $env->real_value; + } else { + if ($env->is_literal || $env->is_multiline) { + $real_value = '\''.$real_value.'\''; + } else { + $real_value = escapeEnvVariables($env->real_value); + } + } + $envs->push($env->key.'='.$real_value); + } + // Add PORT if not exists, use the first port as default + if ($this->build_pack !== 'dockercompose') { + if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) { + $envs->push("PORT={$ports[0]}"); + } + } + // Add HOST if not exists + if ($this->application->environment_variables_preview->where('key', 'HOST')->isEmpty()) { + $envs->push('HOST=0.0.0.0'); + } + } if ($envs->isEmpty()) { $this->env_filename = null; @@ -1625,20 +1563,128 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $this->env_nixpacks_args = $this->env_nixpacks_args->implode(' '); } + private function generate_coolify_env_variables(): Collection + { + $coolify_envs = collect([]); + $local_branch = $this->branch; + if ($this->pull_request_id !== 0) { + // Add SOURCE_COMMIT if not exists + if ($this->application->environment_variables_preview->where('key', 'SOURCE_COMMIT')->isEmpty()) { + if (! is_null($this->commit)) { + $coolify_envs->put('SOURCE_COMMIT', $this->commit); + } else { + $coolify_envs->put('SOURCE_COMMIT', 'unknown'); + } + } + if ($this->application->environment_variables_preview->where('key', 'COOLIFY_FQDN')->isEmpty()) { + $coolify_envs->put('COOLIFY_FQDN', $this->preview->fqdn); + $coolify_envs->put('COOLIFY_DOMAIN_URL', $this->preview->fqdn); + } + if ($this->application->environment_variables_preview->where('key', 'COOLIFY_URL')->isEmpty()) { + $url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', ''); + $coolify_envs->put('COOLIFY_URL', $url); + $coolify_envs->put('COOLIFY_DOMAIN_FQDN', $url); + } + if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') { + if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) { + $coolify_envs->put('COOLIFY_BRANCH', $local_branch); + } + if ($this->application->environment_variables_preview->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) { + $coolify_envs->put('COOLIFY_RESOURCE_UUID', $this->application->uuid); + } + if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { + $coolify_envs->put('COOLIFY_CONTAINER_NAME', $this->container_name); + } + } + + add_coolify_default_environment_variables($this->application, $coolify_envs, $this->application->environment_variables_preview); + + } else { + // Add SOURCE_COMMIT if not exists + if ($this->application->environment_variables->where('key', 'SOURCE_COMMIT')->isEmpty()) { + if (! is_null($this->commit)) { + $coolify_envs->put('SOURCE_COMMIT', $this->commit); + } else { + $coolify_envs->put('SOURCE_COMMIT', 'unknown'); + } + } + if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) { + if ((int) $this->application->compose_parsing_version >= 3) { + $coolify_envs->put('COOLIFY_URL', $this->application->fqdn); + } else { + $coolify_envs->put('COOLIFY_FQDN', $this->application->fqdn); + } + } + if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) { + $url = str($this->application->fqdn)->replace('http://', '')->replace('https://', ''); + if ((int) $this->application->compose_parsing_version >= 3) { + $coolify_envs->put('COOLIFY_FQDN', $url); + } else { + $coolify_envs->put('COOLIFY_URL', $url); + } + } + if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') { + if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) { + $coolify_envs->put('COOLIFY_BRANCH', $local_branch); + } + if ($this->application->environment_variables->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) { + $coolify_envs->put('COOLIFY_RESOURCE_UUID', $this->application->uuid); + } + if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { + $coolify_envs->put('COOLIFY_CONTAINER_NAME', $this->container_name); + } + } + + add_coolify_default_environment_variables($this->application, $coolify_envs, $this->application->environment_variables); + + } + + return $coolify_envs; + } + private function generate_env_variables() { $this->env_args = collect([]); $this->env_args->put('SOURCE_COMMIT', $this->commit); + $coolify_envs = $this->generate_coolify_env_variables(); if ($this->pull_request_id === 0) { foreach ($this->application->build_environment_variables as $env) { if (! is_null($env->real_value)) { $this->env_args->put($env->key, $env->real_value); + if (str($env->real_value)->startsWith('$')) { + $variable_key = str($env->real_value)->after('$'); + if ($variable_key->startsWith('COOLIFY_')) { + $variable = $coolify_envs->get($variable_key->value()); + if (filled($variable)) { + $this->env_args->prepend($variable, $variable_key->value()); + } + } else { + $variable = $this->application->environment_variables()->where('key', $variable_key)->first(); + if ($variable) { + $this->env_args->prepend($variable->real_value, $env->key); + } + } + } } } } else { foreach ($this->application->build_environment_variables_preview as $env) { if (! is_null($env->real_value)) { $this->env_args->put($env->key, $env->real_value); + if (str($env->real_value)->startsWith('$')) { + $variable_key = str($env->real_value)->after('$'); + if ($variable_key->startsWith('COOLIFY_')) { + $variable = $coolify_envs->get($variable_key->value()); + if (filled($variable)) { + $this->env_args->prepend($variable, $variable_key->value()); + } + } else { + $variable = $this->application->environment_variables_preview()->where('key', $variable_key)->first(); + if ($variable) { + $this->env_args->prepend($variable->real_value, $env->key); + } + } + } } } } From ec368255458f11f29adc157317384f611556170a Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 11 Apr 2025 18:22:42 +0200 Subject: [PATCH 10/36] feat(SourceManagement): add functionality to change Git source and display current source in the application settings --- app/Livewire/Project/Application/Source.php | 26 +++++++++++++++ .../project/application/source.blade.php | 32 +++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/app/Livewire/Project/Application/Source.php b/app/Livewire/Project/Application/Source.php index ade297d50..3afdbe075 100644 --- a/app/Livewire/Project/Application/Source.php +++ b/app/Livewire/Project/Application/Source.php @@ -30,11 +30,15 @@ class Source extends Component #[Validate(['nullable', 'string'])] public ?string $gitCommitSha = null; + #[Locked] + public $sources; + public function mount() { try { $this->syncData(); $this->getPrivateKeys(); + $this->getSources(); } catch (\Throwable $e) { handleError($e, $this); } @@ -66,6 +70,14 @@ class Source extends Component }); } + private function getSources() + { + // filter the current source out + $this->sources = currentTeam()->sources()->whereNotNull('app_id')->reject(function ($source) { + return $source->id === $this->application->source_id; + }); + } + public function setPrivateKey(int $privateKeyId) { try { @@ -92,4 +104,18 @@ class Source extends Component return handleError($e, $this); } } + + public function changeSource($sourceId, $sourceType) + { + try { + $this->application->update([ + 'source_id' => $sourceId, + 'source_type' => $sourceType, + ]); + $this->application->refresh(); + $this->dispatch('success', 'Source updated!'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } } diff --git a/resources/views/livewire/project/application/source.blade.php b/resources/views/livewire/project/application/source.blade.php index b542d5428..a32742a01 100644 --- a/resources/views/livewire/project/application/source.blade.php +++ b/resources/views/livewire/project/application/source.blade.php @@ -26,6 +26,9 @@
Code source of your application.
+
Currently connected source: {{ $application->source->name }} +
@@ -34,6 +37,35 @@
+ + @if (filled($sources) && $sources->count() > 0) +
+

Change Git Source

+
+ @foreach ($sources as $source) + + +
+ {{ $source->name }} + @if ($application->source_id === $source->id) + (current) + @endif +
+
+ {{ $source->organization_name ?? 'Personal Account' }} +
+
+
+ @endforeach +
+
+ @endif + @if ($privateKeyId)

Deploy Key

Currently attached Private Key: Date: Fri, 11 Apr 2025 19:32:41 +0200 Subject: [PATCH 11/36] refactor(ApplicationDeploymentJob, ApplicationDeploymentQueue): improve deployment status handling and log entry management with transaction support --- app/Jobs/ApplicationDeploymentJob.php | 21 ++++++++------- app/Models/ApplicationDeploymentQueue.php | 31 ++++++++++++++--------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index f97ff853c..635032dfe 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -2459,20 +2459,23 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); private function next(string $status) { queue_next_deployment($this->application); - // If the deployment is cancelled by the user, don't update the status - if ( - $this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value && - $this->application_deployment_queue->status !== ApplicationDeploymentStatus::FAILED->value - ) { - $this->application_deployment_queue->update([ - 'status' => $status, - ]); + + // Never allow changing status from FAILED or CANCELLED_BY_USER to anything else + if ($this->application_deployment_queue->status === ApplicationDeploymentStatus::FAILED->value || + $this->application_deployment_queue->status === ApplicationDeploymentStatus::CANCELLED_BY_USER->value) { + return; } - if ($this->application_deployment_queue->status === ApplicationDeploymentStatus::FAILED->value) { + + $this->application_deployment_queue->update([ + 'status' => $status, + ]); + + if ($status === ApplicationDeploymentStatus::FAILED->value) { $this->application->environment->project->team?->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview)); return; } + if ($status === ApplicationDeploymentStatus::FINISHED->value) { if (! $this->only_this_server) { $this->deploy_to_additional_destinations(); diff --git a/app/Models/ApplicationDeploymentQueue.php b/app/Models/ApplicationDeploymentQueue.php index fd8f1cba2..2a9bea67a 100644 --- a/app/Models/ApplicationDeploymentQueue.php +++ b/app/Models/ApplicationDeploymentQueue.php @@ -5,6 +5,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\DB; use OpenApi\Attributes as OA; #[OA\Schema( @@ -101,17 +102,23 @@ class ApplicationDeploymentQueue extends Model 'hidden' => $hidden, 'batch' => 1, ]; - if ($this->logs) { - $previousLogs = json_decode($this->logs, associative: true, flags: JSON_THROW_ON_ERROR); - $newLogEntry['order'] = count($previousLogs) + 1; - $previousLogs[] = $newLogEntry; - $this->update([ - 'logs' => json_encode($previousLogs, flags: JSON_THROW_ON_ERROR), - ]); - } else { - $this->update([ - 'logs' => json_encode([$newLogEntry], flags: JSON_THROW_ON_ERROR), - ]); - } + + // Use a transaction to ensure atomicity + DB::transaction(function () use ($newLogEntry) { + // Reload the model to get the latest logs + $this->refresh(); + + if ($this->logs) { + $previousLogs = json_decode($this->logs, associative: true, flags: JSON_THROW_ON_ERROR); + $newLogEntry['order'] = count($previousLogs) + 1; + $previousLogs[] = $newLogEntry; + $this->logs = json_encode($previousLogs, flags: JSON_THROW_ON_ERROR); + } else { + $this->logs = json_encode([$newLogEntry], flags: JSON_THROW_ON_ERROR); + } + + // Save without triggering events to prevent potential race conditions + $this->saveQuietly(); + }); } } From 4c95dccfb3b025e821ee3e45c01f3813ebc7d4ea Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 11 Apr 2025 20:25:25 +0200 Subject: [PATCH 12/36] refactor(SourceManagement): sort sources by name and improve UI for changing Git source with better error handling --- app/Livewire/Project/Application/Source.php | 3 +- bootstrap/helpers/github.php | 3 ++ .../project/application/source.blade.php | 38 ++++++++++--------- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/app/Livewire/Project/Application/Source.php b/app/Livewire/Project/Application/Source.php index 3afdbe075..013d8b8fe 100644 --- a/app/Livewire/Project/Application/Source.php +++ b/app/Livewire/Project/Application/Source.php @@ -75,7 +75,7 @@ class Source extends Component // filter the current source out $this->sources = currentTeam()->sources()->whereNotNull('app_id')->reject(function ($source) { return $source->id === $this->application->source_id; - }); + })->sortBy('name'); } public function setPrivateKey(int $privateKeyId) @@ -113,6 +113,7 @@ class Source extends Component 'source_type' => $sourceType, ]); $this->application->refresh(); + $this->getSources(); $this->dispatch('success', 'Source updated!'); } catch (\Throwable $e) { return handleError($e, $this); diff --git a/bootstrap/helpers/github.php b/bootstrap/helpers/github.php index 81f8ff18a..0de2f2fd9 100644 --- a/bootstrap/helpers/github.php +++ b/bootstrap/helpers/github.php @@ -52,6 +52,9 @@ function generateGithubToken(GithubApp $source, string $type) if (! $response->successful()) { $error = data_get($response->json(), 'message', 'no error message found'); + if ($error === 'Not Found') { + $error = 'Repository not found. Is it moved or deleted?'; + } throw new RuntimeException("Failed to get installation token for {$source->name} with error: ".$error); } diff --git a/resources/views/livewire/project/application/source.blade.php b/resources/views/livewire/project/application/source.blade.php index a32742a01..29c5c6142 100644 --- a/resources/views/livewire/project/application/source.blade.php +++ b/resources/views/livewire/project/application/source.blade.php @@ -38,33 +38,37 @@
- @if (filled($sources) && $sources->count() > 0) -
-

Change Git Source

-
- @foreach ($sources as $source) +
+

Change Git Source

+
+ @forelse ($sources as $source) +
-
- {{ $source->name }} - @if ($application->source_id === $source->id) - (current) - @endif -
-
- {{ $source->organization_name ?? 'Personal Account' }} +
+
+ {{ $source->name }} + @if ($application->source_id === $source->id) + (current) + @endif +
+
+ {{ $source->organization ?? 'Personal Account' }} +
- @endforeach -
+
+ @empty +
No sources found
+ @endforelse
- @endif +
@if ($privateKeyId)

Deploy Key

From 44e96e032ff85f796ebadf2b7b9dc735a91ab915 Mon Sep 17 00:00:00 2001 From: Laurence Date: Fri, 11 Apr 2025 20:37:54 +0100 Subject: [PATCH 13/36] enhance: Add missing openapi items to PrivateKey --- app/Models/PrivateKey.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Models/PrivateKey.php b/app/Models/PrivateKey.php index 0e702e460..97c32fa31 100644 --- a/app/Models/PrivateKey.php +++ b/app/Models/PrivateKey.php @@ -17,6 +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'], 'is_git_related' => ['type' => 'boolean'], 'team_id' => ['type' => 'integer'], 'created_at' => ['type' => 'string'], From 78ae93f3cfd75326ff464debd86d6ceb7064d436 Mon Sep 17 00:00:00 2001 From: SrJooJ Date: Sat, 12 Apr 2025 02:30:08 -0300 Subject: [PATCH 14/36] add new variable S3_REGION --- templates/compose/evolution-api.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/compose/evolution-api.yaml b/templates/compose/evolution-api.yaml index 1ca09076e..5ca520948 100644 --- a/templates/compose/evolution-api.yaml +++ b/templates/compose/evolution-api.yaml @@ -126,6 +126,7 @@ services: - S3_SECRET_KEY=${S3_SECRET_KEY:-} - S3_BUCKET=${S3_BUCKET:-evolution} - S3_PORT=${S3_PORT:-443} + - S3_REGION=${S3_REGION:-us-east-1} - S3_ENDPOINT=${S3_ENDPOINT:-files.site.com} - S3_USE_SSL=${S3_USE_SSL:-true} - 'AUTHENTICATION_API_KEY=${SERVICE_PASSWORD_AUTHENTICATIONAPIKEY}' From de839e3fcb788a200a0d42fce788a302f11d3e15 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sat, 12 Apr 2025 13:59:54 +0200 Subject: [PATCH 15/36] refactor(Email): streamline SMTP and resend settings handling in copyFromInstanceSettings method --- app/Livewire/Notifications/Email.php | 31 +++++++++++++--------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/app/Livewire/Notifications/Email.php b/app/Livewire/Notifications/Email.php index 3ed20f907..3b732af99 100644 --- a/app/Livewire/Notifications/Email.php +++ b/app/Livewire/Notifications/Email.php @@ -337,32 +337,29 @@ class Email extends Component public function copyFromInstanceSettings() { $settings = instanceSettings(); + $this->smtpFromAddress = $settings->smtp_from_address; + $this->smtpFromName = $settings->smtp_from_name; if ($settings->smtp_enabled) { $this->smtpEnabled = true; - $this->smtpFromAddress = $settings->smtp_from_address; - $this->smtpFromName = $settings->smtp_from_name; - $this->smtpRecipients = $settings->smtp_recipients; - $this->smtpHost = $settings->smtp_host; - $this->smtpPort = $settings->smtp_port; - $this->smtpEncryption = $settings->smtp_encryption; - $this->smtpUsername = $settings->smtp_username; - $this->smtpPassword = $settings->smtp_password; - $this->smtpTimeout = $settings->smtp_timeout; $this->resendEnabled = false; - $this->saveModel(); - - return; } + + $this->smtpRecipients = $settings->smtp_recipients; + $this->smtpHost = $settings->smtp_host; + $this->smtpPort = $settings->smtp_port; + $this->smtpEncryption = $settings->smtp_encryption; + $this->smtpUsername = $settings->smtp_username; + $this->smtpPassword = $settings->smtp_password; + $this->smtpTimeout = $settings->smtp_timeout; + if ($settings->resend_enabled) { $this->resendEnabled = true; - $this->resendApiKey = $settings->resend_api_key; $this->smtpEnabled = false; - $this->saveModel(); - - return; } - $this->dispatch('error', 'Instance SMTP/Resend settings are not enabled.'); + $this->resendApiKey = $settings->resend_api_key; + $this->saveModel(); + } public function render() From 6eea3c50d88298660987b8f707f04898fb9a848c Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sat, 12 Apr 2025 14:04:34 +0200 Subject: [PATCH 16/36] refactor(Email): enhance error handling in SMTP and resend methods by passing context to handleError function --- app/Livewire/Notifications/Email.php | 2 +- app/Livewire/SettingsEmail.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Livewire/Notifications/Email.php b/app/Livewire/Notifications/Email.php index 3b732af99..c5f518e16 100644 --- a/app/Livewire/Notifications/Email.php +++ b/app/Livewire/Notifications/Email.php @@ -269,7 +269,7 @@ class Email extends Component } catch (\Throwable $e) { $this->smtpEnabled = false; - return handleError($e); + return handleError($e, $this); } } diff --git a/app/Livewire/SettingsEmail.php b/app/Livewire/SettingsEmail.php index b2394d7b0..73e8c7398 100644 --- a/app/Livewire/SettingsEmail.php +++ b/app/Livewire/SettingsEmail.php @@ -177,7 +177,7 @@ class SettingsEmail extends Component } catch (\Throwable $e) { $this->smtpEnabled = false; - return handleError($e); + return handleError($e, $this); } } @@ -207,7 +207,7 @@ class SettingsEmail extends Component } catch (\Throwable $e) { $this->resendEnabled = false; - return handleError($e); + return handleError($e, $this); } } From 4bc9786046f3427576077d04c7d8dfc709a34f7a Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sun, 13 Apr 2025 14:24:27 +0200 Subject: [PATCH 17/36] refactor(DynamicConfigurations): improve handling of dynamic configuration content by ensuring fallback to empty string when content is null --- app/Livewire/Server/Proxy/DynamicConfigurations.php | 3 ++- .../livewire/server/proxy/dynamic-configurations.blade.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Livewire/Server/Proxy/DynamicConfigurations.php b/app/Livewire/Server/Proxy/DynamicConfigurations.php index 6277a24bd..7db890638 100644 --- a/app/Livewire/Server/Proxy/DynamicConfigurations.php +++ b/app/Livewire/Server/Proxy/DynamicConfigurations.php @@ -38,7 +38,8 @@ class DynamicConfigurations extends Component $contents = collect([]); foreach ($files as $file) { $without_extension = str_replace('.', '|', $file); - $contents[$without_extension] = instant_remote_process(["cat {$proxy_path}/dynamic/{$file}"], $this->server); + $content = instant_remote_process(["cat {$proxy_path}/dynamic/{$file}"], $this->server); + $contents[$without_extension] = $content ?? ''; } $this->contents = $contents; $this->dispatch('$refresh'); diff --git a/resources/views/livewire/server/proxy/dynamic-configurations.blade.php b/resources/views/livewire/server/proxy/dynamic-configurations.blade.php index 30400ef73..1dddbe433 100644 --- a/resources/views/livewire/server/proxy/dynamic-configurations.blade.php +++ b/resources/views/livewire/server/proxy/dynamic-configurations.blade.php @@ -38,7 +38,7 @@ wire:model="contents.{{ $fileName }}" rows="5" /> @else From 1ab7405e2e68e1bf2bae6d807c8b5b16cf6a8177 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sun, 13 Apr 2025 15:16:54 +0200 Subject: [PATCH 18/36] refactor(ServicesGenerate): update command signature from 'services:generate' to 'generate:services' for consistency; update Dockerfile to run service generation during build; update Odoo image version to 18 and add extra addons volume in compose configuration --- app/Console/Commands/ServicesGenerate.php | 2 +- docker/production/Dockerfile | 33 ++++++++++++----------- templates/compose/odoo.yaml | 3 ++- templates/service-templates.json | 2 +- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/app/Console/Commands/ServicesGenerate.php b/app/Console/Commands/ServicesGenerate.php index b45707c5c..1c325d7dd 100644 --- a/app/Console/Commands/ServicesGenerate.php +++ b/app/Console/Commands/ServicesGenerate.php @@ -11,7 +11,7 @@ class ServicesGenerate extends Command /** * {@inheritdoc} */ - protected $signature = 'services:generate'; + protected $signature = 'generate:services'; /** * {@inheritdoc} diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile index 38bb50f3f..e7ba549f5 100644 --- a/docker/production/Dockerfile +++ b/docker/production/Dockerfile @@ -22,7 +22,7 @@ USER root ARG USER_ID ARG GROUP_ID -RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \ +RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx WORKDIR /var/www/html @@ -30,6 +30,7 @@ COPY --chown=www-data:www-data composer.json composer.lock ./ RUN composer install --no-dev --no-interaction --no-plugins --no-scripts --prefer-dist USER www-data +RUN php artisan generate:services # ================================================================= # Stage 2: Frontend assets compilation @@ -63,13 +64,13 @@ WORKDIR /var/www/html USER root -RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \ +RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx # Install PostgreSQL repository and keys -RUN apk add --no-cache gnupg && \ - mkdir -p /usr/share/keyrings && \ - curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor > /usr/share/keyrings/postgresql.gpg +RUN apk add --no-cache gnupg && + mkdir -p /usr/share/keyrings && + curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor >/usr/share/keyrings/postgresql.gpg # Install system dependencies RUN apk add --no-cache \ @@ -82,17 +83,17 @@ RUN apk add --no-cache \ vim # Configure shell aliases -RUN echo "alias ll='ls -al'" >> /etc/profile && \ - echo "alias a='php artisan'" >> /etc/profile && \ - echo "alias logs='tail -f storage/logs/laravel.log'" >> /etc/profile +RUN echo "alias ll='ls -al'" >>/etc/profile && + echo "alias a='php artisan'" >>/etc/profile && + echo "alias logs='tail -f storage/logs/laravel.log'" >>/etc/profile # Install Cloudflared based on architecture -RUN mkdir -p /usr/local/bin && \ - if [ "${TARGETPLATFORM}" = "linux/amd64" ]; then \ - curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64" -o /usr/local/bin/cloudflared; \ - elif [ "${TARGETPLATFORM}" = "linux/arm64" ]; then \ - curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64" -o /usr/local/bin/cloudflared; \ - fi && \ +RUN mkdir -p /usr/local/bin && + if [ "${TARGETPLATFORM}" = "linux/amd64" ]; then + curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64" -o /usr/local/bin/cloudflared + elif [ "${TARGETPLATFORM}" = "linux/arm64" ]; then + curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64" -o /usr/local/bin/cloudflared + fi && chmod +x /usr/local/bin/cloudflared # Configure PHP @@ -128,8 +129,8 @@ COPY docker/production/etc/nginx/conf.d/custom.conf /etc/nginx/conf.d/custom.con COPY docker/production/etc/nginx/site-opts.d/http.conf /etc/nginx/site-opts.d/http.conf COPY --chmod=755 docker/production/etc/s6-overlay/ /etc/s6-overlay/ -RUN mkdir -p /etc/nginx/conf.d && \ - chown -R www-data:www-data /etc/nginx && \ +RUN mkdir -p /etc/nginx/conf.d && + chown -R www-data:www-data /etc/nginx && chmod -R 755 /etc/nginx # Install MinIO client diff --git a/templates/compose/odoo.yaml b/templates/compose/odoo.yaml index 2c25283fc..e999095f0 100644 --- a/templates/compose/odoo.yaml +++ b/templates/compose/odoo.yaml @@ -6,7 +6,7 @@ services: odoo: - image: odoo:17 + image: odoo:18 environment: - SERVICE_FQDN_ODOO_8069 - HOST=postgresql @@ -14,6 +14,7 @@ services: - PASSWORD=$SERVICE_PASSWORD_POSTGRES volumes: - odoo-web-data:/var/lib/odoo + - odoo-extra-addons:/mnt/extra-addons healthcheck: test: ["CMD", "curl", "-f", "http://127.0.0.1:8069"] interval: 2s diff --git a/templates/service-templates.json b/templates/service-templates.json index 531bb639a..f3ab0be72 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -2274,7 +2274,7 @@ "odoo": { "documentation": "https://www.odoo.com/?utm_source=coolify.io", "slogan": "Odoo is a suite of open-source business apps that cover all your company needs.", - "compose": "c2VydmljZXM6CiAgb2RvbzoKICAgIGltYWdlOiAnb2RvbzoxNycKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9PRE9PXzgwNjkKICAgICAgLSBIT1NUPXBvc3RncmVzcWwKICAgICAgLSBVU0VSPSRTRVJWSUNFX1VTRVJfUE9TVEdSRVMKICAgICAgLSBQQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgdm9sdW1lczoKICAgICAgLSAnb2Rvby13ZWItZGF0YTovdmFyL2xpYi9vZG9vJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjgwNjknCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMzAKICBwb3N0Z3Jlc3FsOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNi1hbHBpbmUnCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0Z3Jlc3FsLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfVVNFUj0kU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIC0gUE9TVEdSRVNfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVMKICAgICAgLSBQT1NUR1JFU19EQj1wb3N0Z3JlcwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kIHBvc3RncmVzJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", + "compose": "c2VydmljZXM6CiAgb2RvbzoKICAgIGltYWdlOiAnb2RvbzoxOCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9PRE9PXzgwNjkKICAgICAgLSBIT1NUPXBvc3RncmVzcWwKICAgICAgLSBVU0VSPSRTRVJWSUNFX1VTRVJfUE9TVEdSRVMKICAgICAgLSBQQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgdm9sdW1lczoKICAgICAgLSAnb2Rvby13ZWItZGF0YTovdmFyL2xpYi9vZG9vJwogICAgICAtICdvZG9vLWV4dHJhLWFkZG9uczovbW50L2V4dHJhLWFkZG9ucycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo4MDY5JwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDMwCiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTYtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIC0gUE9TVEdSRVNfREI9cG9zdGdyZXMKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCBwb3N0Z3JlcycKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAo=", "tags": [ "business", "apps", From 3803bac2aae6bfdc67e886a2738a643f625ded94 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sun, 13 Apr 2025 15:22:00 +0200 Subject: [PATCH 19/36] refactor(Dockerfile): streamline RUN commands for improved readability and maintainability by adding line continuations --- docker/production/Dockerfile | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile index e7ba549f5..b7d571a8a 100644 --- a/docker/production/Dockerfile +++ b/docker/production/Dockerfile @@ -22,7 +22,7 @@ USER root ARG USER_ID ARG GROUP_ID -RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && +RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \ docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx WORKDIR /var/www/html @@ -64,13 +64,13 @@ WORKDIR /var/www/html USER root -RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && +RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \ docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx # Install PostgreSQL repository and keys -RUN apk add --no-cache gnupg && - mkdir -p /usr/share/keyrings && - curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor >/usr/share/keyrings/postgresql.gpg +RUN apk add --no-cache gnupg && \ + mkdir -p /usr/share/keyrings && \ + curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor > /usr/share/keyrings/postgresql.gpg # Install system dependencies RUN apk add --no-cache \ @@ -83,17 +83,17 @@ RUN apk add --no-cache \ vim # Configure shell aliases -RUN echo "alias ll='ls -al'" >>/etc/profile && - echo "alias a='php artisan'" >>/etc/profile && - echo "alias logs='tail -f storage/logs/laravel.log'" >>/etc/profile +RUN echo "alias ll='ls -al'" >> /etc/profile && \ + echo "alias a='php artisan'" >> /etc/profile && \ + echo "alias logs='tail -f storage/logs/laravel.log'" >> /etc/profile # Install Cloudflared based on architecture -RUN mkdir -p /usr/local/bin && - if [ "${TARGETPLATFORM}" = "linux/amd64" ]; then - curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64" -o /usr/local/bin/cloudflared - elif [ "${TARGETPLATFORM}" = "linux/arm64" ]; then - curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64" -o /usr/local/bin/cloudflared - fi && +RUN mkdir -p /usr/local/bin && \ + if [ "${TARGETPLATFORM}" = "linux/amd64" ]; then \ + curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64" -o /usr/local/bin/cloudflared; \ + elif [ "${TARGETPLATFORM}" = "linux/arm64" ]; then \ + curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64" -o /usr/local/bin/cloudflared; \ + fi && \ chmod +x /usr/local/bin/cloudflared # Configure PHP @@ -129,8 +129,8 @@ COPY docker/production/etc/nginx/conf.d/custom.conf /etc/nginx/conf.d/custom.con COPY docker/production/etc/nginx/site-opts.d/http.conf /etc/nginx/site-opts.d/http.conf COPY --chmod=755 docker/production/etc/s6-overlay/ /etc/s6-overlay/ -RUN mkdir -p /etc/nginx/conf.d && - chown -R www-data:www-data /etc/nginx && +RUN mkdir -p /etc/nginx/conf.d && \ + chown -R www-data:www-data /etc/nginx && \ chmod -R 755 /etc/nginx # Install MinIO client From 5cc189b69a037549f7f3fe5ca0810e0fac908a00 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sun, 13 Apr 2025 15:38:17 +0200 Subject: [PATCH 20/36] refactor(Dockerfile): reintroduce service generation command in the build process for consistency and ensure proper asset compilation --- docker/production/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile index b7d571a8a..ae93c6237 100644 --- a/docker/production/Dockerfile +++ b/docker/production/Dockerfile @@ -30,7 +30,6 @@ COPY --chown=www-data:www-data composer.json composer.lock ./ RUN composer install --no-dev --no-interaction --no-plugins --no-scripts --prefer-dist USER www-data -RUN php artisan generate:services # ================================================================= # Stage 2: Frontend assets compilation @@ -124,6 +123,8 @@ COPY --chown=www-data:www-data openapi.yaml ./openapi.yaml RUN composer dump-autoload +RUN php artisan generate:services + # Configure Nginx and S6 overlay COPY docker/production/etc/nginx/conf.d/custom.conf /etc/nginx/conf.d/custom.conf COPY docker/production/etc/nginx/site-opts.d/http.conf /etc/nginx/site-opts.d/http.conf From 927caefb42f8c6936db062ab2757712ae0b3eda5 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sun, 13 Apr 2025 15:48:00 +0200 Subject: [PATCH 21/36] feat(OpenApi): enhance OpenAPI specifications by adding UUID parameters for application, project, and service updates; improve deployment listing with pagination parameters; update command signature for OpenApi generation --- app/Console/Commands/OpenApi.php | 2 +- openapi.json | 93 +++++++++++++++++++++++++++++++- openapi.yaml | 2 +- 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/app/Console/Commands/OpenApi.php b/app/Console/Commands/OpenApi.php index 3cef85477..341232907 100644 --- a/app/Console/Commands/OpenApi.php +++ b/app/Console/Commands/OpenApi.php @@ -8,7 +8,7 @@ use Symfony\Component\Yaml\Yaml; class OpenApi extends Command { - protected $signature = 'openapi'; + protected $signature = 'generate:openapi'; protected $description = 'Generate OpenApi file.'; diff --git a/openapi.json b/openapi.json index 3956c4380..5070f1be9 100644 --- a/openapi.json +++ b/openapi.json @@ -1798,6 +1798,18 @@ "summary": "Update", "description": "Update application by UUID.", "operationId": "update-application-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], "requestBody": { "description": "Application updated.", "required": true, @@ -4441,7 +4453,7 @@ "Deployments" ], "summary": "Deploy", - "description": "Deploy by tag or uuid. `Post` request also accepted.", + "description": "Deploy by tag or uuid. `Post` request also accepted with `uuid` and `tag` json body.", "operationId": "deploy-by-tag-or-uuid", "parameters": [ { @@ -4529,6 +4541,40 @@ "summary": "List application deployments", "description": "List application deployments by using the app uuid", "operationId": "list-deployments-by-app-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "skip", + "in": "query", + "description": "Number of records to skip.", + "required": false, + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + } + }, + { + "name": "take", + "in": "query", + "description": "Number of records to take.", + "required": false, + "schema": { + "type": "integer", + "default": 10, + "minimum": 1 + } + } + ], "responses": { "200": { "description": "List application deployments by using the app uuid.", @@ -4921,6 +4967,18 @@ "summary": "Update", "description": "Update Project.", "operationId": "update-project-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the project.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], "requestBody": { "description": "Project updated.", "required": true, @@ -5321,6 +5379,22 @@ }, "404": { "description": "Private Key not found." + }, + "422": { + "description": "Private Key is in use and cannot be deleted.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Private Key is in use and cannot be deleted." + } + }, + "type": "object" + } + } + } } }, "security": [ @@ -6222,6 +6296,18 @@ "summary": "Update", "description": "Update service by UUID.", "operationId": "update-service-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the service.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], "requestBody": { "description": "Service updated.", "required": true, @@ -7198,6 +7284,11 @@ "nullable": true, "description": "Ports mappings." }, + "custom_network_aliases": { + "type": "string", + "nullable": true, + "description": "Network aliases for Docker container." + }, "base_directory": { "type": "string", "description": "Base directory for all commands." diff --git a/openapi.yaml b/openapi.yaml index 6cd179c34..1fd42055b 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -3094,7 +3094,7 @@ paths: tags: - Deployments summary: Deploy - description: 'Deploy by tag or uuid. `Post` request also accepted.' + description: 'Deploy by tag or uuid. `Post` request also accepted with `uuid` and `tag` json body.' operationId: deploy-by-tag-or-uuid parameters: - From 41e874eb05059a9dc9379dbd9db6c9a3d2bc522d Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sun, 13 Apr 2025 15:49:27 +0200 Subject: [PATCH 22/36] fix(pre-commit): correct input redirection for /dev/tty and add OpenAPI generation command --- hooks/pre-commit | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hooks/pre-commit b/hooks/pre-commit index 69a5a9d41..d083fdcbb 100644 --- a/hooks/pre-commit +++ b/hooks/pre-commit @@ -1,7 +1,7 @@ #!/bin/sh # Detect whether /dev/tty is available & functional if sh -c ": >/dev/tty" >/dev/null 2>/dev/null; then - exec < /dev/tty + exec Date: Sun, 13 Apr 2025 15:49:56 +0200 Subject: [PATCH 23/36] chore(versions): update nightly version to 4.0.0-beta.410 --- versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.json b/versions.json index bfcae55e3..dc52abf9f 100644 --- a/versions.json +++ b/versions.json @@ -4,7 +4,7 @@ "version": "4.0.0-beta.408" }, "nightly": { - "version": "4.0.0-beta.409" + "version": "4.0.0-beta.410" }, "helper": { "version": "1.0.8" From 201116746bc0ae7204a584088b5a0ccac1f08ded Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sun, 13 Apr 2025 15:53:08 +0200 Subject: [PATCH 24/36] chore(pre-commit): remove OpenAPI generation command from pre-commit hook --- hooks/pre-commit | 2 -- 1 file changed, 2 deletions(-) diff --git a/hooks/pre-commit b/hooks/pre-commit index d083fdcbb..029f67917 100644 --- a/hooks/pre-commit +++ b/hooks/pre-commit @@ -19,5 +19,3 @@ $(pwd)/vendor/bin/pint $files -q if [ $? -eq 0 ]; then git add $files fi - -php artisan generate:openapi From 93ec2f804b02278980e2bb85b4a8e49dd5924a38 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sun, 13 Apr 2025 16:00:50 +0200 Subject: [PATCH 25/36] refactor(commands): reorganize OpenAPI and Services generation commands into a new namespace for better structure; remove old command files --- app/Console/Commands/{ => Generate}/OpenApi.php | 4 ++-- .../Commands/{ServicesGenerate.php => Generate/Services.php} | 4 ++-- templates/service-templates.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename app/Console/Commands/{ => Generate}/OpenApi.php (92%) rename app/Console/Commands/{ServicesGenerate.php => Generate/Services.php} (97%) diff --git a/app/Console/Commands/OpenApi.php b/app/Console/Commands/Generate/OpenApi.php similarity index 92% rename from app/Console/Commands/OpenApi.php rename to app/Console/Commands/Generate/OpenApi.php index 341232907..2b266c258 100644 --- a/app/Console/Commands/OpenApi.php +++ b/app/Console/Commands/Generate/OpenApi.php @@ -1,6 +1,6 @@ Date: Sun, 13 Apr 2025 16:02:06 +0200 Subject: [PATCH 26/36] refactor(Dockerfile): remove service generation command from the build process to streamline Dockerfile and improve build efficiency --- docker/production/Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile index ae93c6237..8d74ba107 100644 --- a/docker/production/Dockerfile +++ b/docker/production/Dockerfile @@ -123,8 +123,6 @@ COPY --chown=www-data:www-data openapi.yaml ./openapi.yaml RUN composer dump-autoload -RUN php artisan generate:services - # Configure Nginx and S6 overlay COPY docker/production/etc/nginx/conf.d/custom.conf /etc/nginx/conf.d/custom.conf COPY docker/production/etc/nginx/site-opts.d/http.conf /etc/nginx/site-opts.d/http.conf From 5b9753eeb24b93357bc3cb118ad8e5888e65a0e9 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sun, 13 Apr 2025 20:38:16 +0200 Subject: [PATCH 27/36] refactor(navbar-delete-team): simplify modal confirmation layout and enhance button styling for better user experience --- .../views/livewire/navbar-delete-team.blade.php | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/resources/views/livewire/navbar-delete-team.blade.php b/resources/views/livewire/navbar-delete-team.blade.php index 60b25a3d5..d0c47b874 100644 --- a/resources/views/livewire/navbar-delete-team.blade.php +++ b/resources/views/livewire/navbar-delete-team.blade.php @@ -1,13 +1,6 @@ -
- + + shortConfirmationLabel="Team Name" step3ButtonText="Permanently Delete" />
From c375a8e7aeb3e211cc3665029c2a2af36961ddea Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sun, 13 Apr 2025 20:49:02 +0200 Subject: [PATCH 28/36] fix(pricing-plans): adjust grid class for improved layout consistency in subscription pricing plans --- resources/views/livewire/subscription/pricing-plans.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/livewire/subscription/pricing-plans.blade.php b/resources/views/livewire/subscription/pricing-plans.blade.php index 2cbfbb1ca..027c9de4d 100644 --- a/resources/views/livewire/subscription/pricing-plans.blade.php +++ b/resources/views/livewire/subscription/pricing-plans.blade.php @@ -20,7 +20,7 @@
+ class="grid grid-cols-1 -mt-16 divide-y divide-neutral-200 dark:divide-coolgray-500 isolate gap-y-16 sm:mx-auto lg:-mx-8 lg:mt-0 lg:max-w-none lg:grid-cols-1 lg:divide-x lg:divide-y-0 xl:-mx-4">

Pay-as-you-go

From de2bd160ff8822c6f7169a80e71effbf4f81e4c0 Mon Sep 17 00:00:00 2001 From: Ashwin van Dijk Date: Sun, 13 Apr 2025 21:34:00 +0200 Subject: [PATCH 29/36] fix(mongodb): Also apply custom config when SSL is enabled --- app/Actions/Database/StartMongodb.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/Actions/Database/StartMongodb.php b/app/Actions/Database/StartMongodb.php index a42f03eb5..870b5b7e5 100644 --- a/app/Actions/Database/StartMongodb.php +++ b/app/Actions/Database/StartMongodb.php @@ -217,6 +217,10 @@ class StartMongodb if ($this->database->enable_ssl) { $commandParts = ['mongod']; + if (! empty($this->database->mongo_conf)) { + $commandParts = ['mongod', '--config', '/etc/mongo/mongod.conf']; + } + $sslConfig = match ($this->database->ssl_mode) { 'allow' => [ '--tlsMode=allowTLS', From eda1e1899fb5af274a2e3437603587a89406b1a1 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 14 Apr 2025 10:30:43 +0200 Subject: [PATCH 30/36] fix(migrations): make stripe_comment field nullable in subscriptions table --- ...5_04_01_124212_stripe_comment_nullable.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 database/migrations/2025_04_01_124212_stripe_comment_nullable.php diff --git a/database/migrations/2025_04_01_124212_stripe_comment_nullable.php b/database/migrations/2025_04_01_124212_stripe_comment_nullable.php new file mode 100644 index 000000000..7f61c202e --- /dev/null +++ b/database/migrations/2025_04_01_124212_stripe_comment_nullable.php @@ -0,0 +1,28 @@ +longText('stripe_comment')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('subscriptions', function (Blueprint $table) { + $table->longText('stripe_comment')->nullable(false)->change(); + }); + } +}; From 8b3c4d7ad9223212384980bd66e1ac4be77dee41 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 14 Apr 2025 10:30:53 +0200 Subject: [PATCH 31/36] refactor(Server): remove debug logging from isReachableChanged method to clean up code and improve performance --- app/Models/Server.php | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/app/Models/Server.php b/app/Models/Server.php index 60d0da3ed..41af45f30 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -20,7 +20,6 @@ use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Stringable; use OpenApi\Attributes as OA; @@ -1026,22 +1025,11 @@ $schema://$host { $this->refresh(); $unreachableNotificationSent = (bool) $this->unreachable_notification_sent; $isReachable = (bool) $this->settings->is_reachable; - - Log::debug('Server reachability check', [ - 'server_id' => $this->id, - 'is_reachable' => $isReachable, - 'notification_sent' => $unreachableNotificationSent, - 'unreachable_count' => $this->unreachable_count, - ]); - if ($isReachable === true) { $this->unreachable_count = 0; $this->save(); if ($unreachableNotificationSent === true) { - Log::debug('Server is now reachable, sending notification', [ - 'server_id' => $this->id, - ]); $this->sendReachableNotification(); } @@ -1049,17 +1037,10 @@ $schema://$host { } $this->increment('unreachable_count'); - Log::debug('Incremented unreachable count', [ - 'server_id' => $this->id, - 'new_count' => $this->unreachable_count, - ]); if ($this->unreachable_count === 1) { $this->settings->is_reachable = true; $this->settings->save(); - Log::debug('First unreachable attempt, marking as reachable', [ - 'server_id' => $this->id, - ]); return; } @@ -1068,11 +1049,6 @@ $schema://$host { $failedChecks = 0; for ($i = 0; $i < 3; $i++) { $status = $this->serverStatus(); - Log::debug('Additional reachability check', [ - 'server_id' => $this->id, - 'attempt' => $i + 1, - 'status' => $status, - ]); sleep(5); if (! $status) { $failedChecks++; @@ -1080,9 +1056,6 @@ $schema://$host { } if ($failedChecks === 3 && ! $unreachableNotificationSent) { - Log::debug('Server confirmed unreachable after 3 attempts, sending notification', [ - 'server_id' => $this->id, - ]); $this->sendUnreachableNotification(); } } From 32326bb7dd45fe6fa86e408f0779d9edfe807ed9 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 14 Apr 2025 10:31:13 +0200 Subject: [PATCH 32/36] feat(subscription): enhance subscription management with loading states and Stripe status checks --- app/Livewire/Subscription/Index.php | 40 +++++++++- app/Models/Team.php | 2 - resources/views/livewire/dashboard.blade.php | 9 +-- .../livewire/subscription/index.blade.php | 73 +++++++++++++------ .../subscription/pricing-plans.blade.php | 20 ++--- 5 files changed, 101 insertions(+), 43 deletions(-) diff --git a/app/Livewire/Subscription/Index.php b/app/Livewire/Subscription/Index.php index df450cf7e..3e77768a0 100644 --- a/app/Livewire/Subscription/Index.php +++ b/app/Livewire/Subscription/Index.php @@ -12,19 +12,30 @@ class Index extends Component public bool $alreadySubscribed = false; + public bool $isUnpaid = false; + + public bool $isCancelled = false; + + public bool $isMember = false; + + public bool $loading = true; + public function mount() { if (! isCloud()) { return redirect(RouteServiceProvider::HOME); } if (auth()->user()?->isMember()) { - return redirect()->route('dashboard'); + $this->isMember = true; } if (data_get(currentTeam(), 'subscription') && isSubscriptionActive()) { return redirect()->route('subscription.show'); } $this->settings = instanceSettings(); $this->alreadySubscribed = currentTeam()->subscription()->exists(); + if (! $this->alreadySubscribed) { + $this->loading = false; + } } public function stripeCustomerPortal() @@ -37,6 +48,33 @@ class Index extends Component return redirect($session->url); } + public function getStripeStatus() + { + $subscription = currentTeam()->subscription; + $stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key')); + $customer = $stripe->customers->retrieve(currentTeam()->subscription->stripe_customer_id); + if ($customer) { + $subscriptions = $stripe->subscriptions->all(['customer' => $customer->id]); + $currentTeam = currentTeam()->id ?? null; + if (count($subscriptions->data) > 0 && $currentTeam) { + $foundSubscription = collect($subscriptions->data)->firstWhere('metadata.team_id', $currentTeam); + if ($foundSubscription) { + $status = data_get($foundSubscription, 'status'); + $subscription->update([ + 'stripe_subscription_id' => $foundSubscription->id, + ]); + if ($status === 'unpaid') { + $this->isUnpaid = true; + } + } + } + if (count($subscriptions->data) === 0) { + $this->isCancelled = true; + } + } + $this->loading = false; + } + public function render() { return view('livewire.subscription.index'); diff --git a/app/Models/Team.php b/app/Models/Team.php index d36f8c1ab..42b88f9e7 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -192,8 +192,6 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen public function subscriptionEnded() { $this->subscription->update([ - 'stripe_subscription_id' => null, - 'stripe_plan_id' => null, 'stripe_cancel_at_period_end' => false, 'stripe_invoice_paid' => false, 'stripe_trial_already_ended' => false, diff --git a/resources/views/livewire/dashboard.blade.php b/resources/views/livewire/dashboard.blade.php index 806704d5d..10794e79f 100644 --- a/resources/views/livewire/dashboard.blade.php +++ b/resources/views/livewire/dashboard.blade.php @@ -8,13 +8,8 @@

Dashboard

Your self-hosted infrastructure.
@if (request()->query->get('success')) -
- - - - Your subscription has been activated! Welcome onboard!
It could take a few seconds before your +
+ Your subscription has been activated! Welcome onboard! It could take a few seconds before your subscription is activated.
Please be patient.
@endif diff --git a/resources/views/livewire/subscription/index.blade.php b/resources/views/livewire/subscription/index.blade.php index c29fdae89..974b209c7 100644 --- a/resources/views/livewire/subscription/index.blade.php +++ b/resources/views/livewire/subscription/index.blade.php @@ -3,37 +3,62 @@ Subscribe | Coolify @if (auth()->user()->isAdminFromSession()) -
-
-

Subscriptions

- @if (subscriptionProvider() === 'stripe' && $alreadySubscribed) - Manage My Subscription - @endif + @if (request()->query->get('cancelled')) +
+ + + + Something went wrong with your subscription. Please try again or contact + support.
- @if (request()->query->get('cancelled')) -
- - - - Something went wrong with your subscription. Please try again or contact - support. -
- @endif - - @if (config('subscription.provider') === 'stripe') - - @endif + @endif +
+

Subscriptions

+ @if ($loading) +
+ Loading your subscription status... +
+ @else + @if ($isUnpaid) +
+ Your last payment was failed for Coolify Cloud. +
+
+

Open the following link, navigate to the button and pay your unpaid/past due + subscription. +

+ Billing Portal +
+ @else + @if (config('subscription.provider') === 'stripe') +
$isCancelled, + 'pb-10' => !$isCancelled, + ])> + @if ($isCancelled) +
+ It looks like your previous subscription has been cancelled, because you forgot to + pay + the bills.
Please subscribe again to continue using Coolify.
+
+ @endif +
+ + @endif + @endif + @endif @else

Subscription

-
You are not an admin or have been removed from this team. If this does not make sense, please contact - us.
+
You are not an admin so you cannot manage your Team's subscription. If this does not make sense, please + contact + us. +
@endif
diff --git a/resources/views/livewire/subscription/pricing-plans.blade.php b/resources/views/livewire/subscription/pricing-plans.blade.php index 027c9de4d..19b795102 100644 --- a/resources/views/livewire/subscription/pricing-plans.blade.php +++ b/resources/views/livewire/subscription/pricing-plans.blade.php @@ -1,4 +1,4 @@ -
+
- - Subscribe - - - Subscribe - +
+ + Subscribe + + + Subscribe + +
  • Date: Mon, 14 Apr 2025 11:48:44 +0200 Subject: [PATCH 33/36] chore(versions): update realtime version to 1.0.7 and bump dependencies in package.json --- docker/coolify-realtime/package.json | 6 +++--- other/nightly/versions.json | 4 ++-- versions.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/coolify-realtime/package.json b/docker/coolify-realtime/package.json index 0a9b80cb5..7851d7f4d 100644 --- a/docker/coolify-realtime/package.json +++ b/docker/coolify-realtime/package.json @@ -5,9 +5,9 @@ "@xterm/addon-fit": "0.10.0", "@xterm/xterm": "5.5.0", "cookie": "1.0.2", - "axios": "1.7.9", - "dotenv": "16.4.7", + "axios": "1.8.4", + "dotenv": "16.5.0", "node-pty": "1.0.0", "ws": "8.18.1" } -} +} \ No newline at end of file diff --git a/other/nightly/versions.json b/other/nightly/versions.json index bfcae55e3..8d7639e3f 100644 --- a/other/nightly/versions.json +++ b/other/nightly/versions.json @@ -4,13 +4,13 @@ "version": "4.0.0-beta.408" }, "nightly": { - "version": "4.0.0-beta.409" + "version": "4.0.0-beta.410" }, "helper": { "version": "1.0.8" }, "realtime": { - "version": "1.0.6" + "version": "1.0.7" }, "sentinel": { "version": "0.0.15" diff --git a/versions.json b/versions.json index dc52abf9f..8d7639e3f 100644 --- a/versions.json +++ b/versions.json @@ -10,7 +10,7 @@ "version": "1.0.8" }, "realtime": { - "version": "1.0.6" + "version": "1.0.7" }, "sentinel": { "version": "0.0.15" From ac390abec2faf8bc13ef66686847f58a89ffdea8 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 14 Apr 2025 13:57:03 +0200 Subject: [PATCH 34/36] fix(templates): correct casing of denoKV references in service templates and YAML files --- templates/compose/denoKV.yaml | 2 +- templates/service-templates.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/compose/denoKV.yaml b/templates/compose/denoKV.yaml index 65e40e436..b6f9682f4 100644 --- a/templates/compose/denoKV.yaml +++ b/templates/compose/denoKV.yaml @@ -1,7 +1,7 @@ # documentation: https://docs.deno.com/deploy/kv/manual/ # slogan: The Denoland key-value database # tags: deno, kv, key-value, database -# logo: svgs/denoKV.svg +# logo: svgs/denokv.svg # port: 4512 services: diff --git a/templates/service-templates.json b/templates/service-templates.json index ca4189045..0ee5ddb51 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -563,7 +563,7 @@ "minversion": "0.0.0", "port": "8080" }, - "denoKV": { + "denokv": { "documentation": "https://docs.deno.com/deploy/kv/manual/?utm_source=coolify.io", "slogan": "The Denoland key-value database", "compose": "c2VydmljZXM6CiAgZGVub2t2OgogICAgaW1hZ2U6ICdnaGNyLmlvL2Rlbm9sYW5kL2Rlbm9rdjpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnQUNDRVNTX1RPS0VOPSR7U0VSVklDRV9QQVNTV09SRF9ERU5PS1Z9JwogICAgICAtIFNFUlZJQ0VfRlFETl9ERU5PS1ZfNDUxMgogICAgdm9sdW1lczoKICAgICAgLSAnJHtDT09MSUZZX1ZPTFVNRV9BUFB9Oi9kYXRhJwogICAgY29tbWFuZDogJy0tc3FsaXRlLXBhdGggL2RhdGEvZGVub2t2LnNxbGl0ZSBzZXJ2ZSAtLWFjY2Vzcy10b2tlbiAke1NFUlZJQ0VfUEFTU1dPUkRfREVOT0tWfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBuYwogICAgICAgIC0gJy16dicKICAgICAgICAtIDEyNy4wLjAuMQogICAgICAgIC0gJzQ1MTInCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAzCg==", @@ -573,7 +573,7 @@ "key-value", "database" ], - "logo": "svgs/denoKV.svg", + "logo": "svgs/denokv.svg", "minversion": "0.0.0", "port": "4512" }, From 9ae5ec0ed3b9d947bfaff261db6850051c6f2520 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 14 Apr 2025 13:57:41 +0200 Subject: [PATCH 35/36] Update app/Livewire/Subscription/Index.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- app/Livewire/Subscription/Index.php | 46 +++++++++++++++++------------ 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/app/Livewire/Subscription/Index.php b/app/Livewire/Subscription/Index.php index 3e77768a0..8a9cc456f 100644 --- a/app/Livewire/Subscription/Index.php +++ b/app/Livewire/Subscription/Index.php @@ -50,29 +50,37 @@ class Index extends Component public function getStripeStatus() { - $subscription = currentTeam()->subscription; - $stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key')); - $customer = $stripe->customers->retrieve(currentTeam()->subscription->stripe_customer_id); - if ($customer) { - $subscriptions = $stripe->subscriptions->all(['customer' => $customer->id]); - $currentTeam = currentTeam()->id ?? null; - if (count($subscriptions->data) > 0 && $currentTeam) { - $foundSubscription = collect($subscriptions->data)->firstWhere('metadata.team_id', $currentTeam); - if ($foundSubscription) { - $status = data_get($foundSubscription, 'status'); - $subscription->update([ - 'stripe_subscription_id' => $foundSubscription->id, - ]); - if ($status === 'unpaid') { - $this->isUnpaid = true; + try { + $subscription = currentTeam()->subscription; + $stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key')); + $customer = $stripe->customers->retrieve(currentTeam()->subscription->stripe_customer_id); + if ($customer) { + $subscriptions = $stripe->subscriptions->all(['customer' => $customer->id]); + $currentTeam = currentTeam()->id ?? null; + if (count($subscriptions->data) > 0 && $currentTeam) { + $foundSubscription = collect($subscriptions->data)->firstWhere('metadata.team_id', $currentTeam); + if ($foundSubscription) { + $status = data_get($foundSubscription, 'status'); + $subscription->update([ + 'stripe_subscription_id' => $foundSubscription->id, + ]); + if ($status === 'unpaid') { + $this->isUnpaid = true; + } } } + if (count($subscriptions->data) === 0) { + $this->isCancelled = true; + } } - if (count($subscriptions->data) === 0) { - $this->isCancelled = true; - } + } catch (\Exception $e) { + // Log the error + logger()->error('Stripe API error: ' . $e->getMessage()); + // Set a flag to show an error message to the user + $this->addError('stripe', 'Could not retrieve subscription information. Please try again later.'); + } finally { + $this->loading = false; } - $this->loading = false; } public function render() From 06b8d78153f3fe02cb5e84188c83f99d0a42f026 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 14 Apr 2025 13:58:27 +0200 Subject: [PATCH 36/36] fix(deployment): handle missing destination in deployment process to prevent errors --- app/Jobs/ApplicationDeploymentJob.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 635032dfe..ab80a19de 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1330,7 +1330,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue return; } foreach ($destination_ids as $destination_id) { - $destination = StandaloneDocker::find($destination_id)->first(); + $destination = StandaloneDocker::find($destination_id); + if (! $destination) { + continue; + } $server = $destination->server; if ($server->team_id !== $this->mainServer->team_id) { $this->application_deployment_queue->addLogEntry("Skipping deployment to {$server->name}. Not in the same team?!");