From 4dc46b45b9b8562772b33bf06561b83aa7505380 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 27 Mar 2025 09:37:59 +0000 Subject: [PATCH 01/22] docs: update changelog --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c16c8a3e..20c416b02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,25 @@ All notable changes to this project will be documented in this file. ### 🚀 Features +- *(database)* Disable MongoDB SSL by default in migration + +### 🚜 Refactor + +- *(proxy)* Improve port availability checks with multiple methods +- *(database)* Update MongoDB SSL configuration for improved security +- *(database)* Enhance SSL configuration handling for various databases +- *(notifications)* Update Telegram button URL for staging environment +- *(models)* Remove unnecessary cloud check in isEnabled method +- *(database)* Streamline event listeners in Redis General component +- *(database)* Remove redundant database status display in MongoDB view +- *(database)* Update import statements for Auth in database components +- *(database)* Require PEM key file for SSL certificate regeneration +- *(database)* Change MySQL daemon command to MariaDB daemon + +## [4.0.0-beta.399] - 2025-03-25 + +### 🚀 Features + - *(service)* Neon - *(migration)* Add `ssl_certificates` table and model - *(migration)* Add ssl setting to `standalone_postgresqls` table @@ -159,12 +178,15 @@ All notable changes to this project will be documented in this file. - *(invite-link)* Adjust layout for better responsiveness in form - *(invite-link)* Enhance form layout for improved responsiveness - *(network)* Enhance docker network creation with ipv6 fallback +- *(network)* Check for existing coolify network before creation +- *(database)* Enhance encryption process for local file volumes ### 📚 Documentation - Update changelog - Update changelog - *(CONTRIBUTING)* Add note about Laravel Horizon accessibility +- Update changelog ### ⚙️ Miscellaneous Tasks @@ -176,6 +198,7 @@ All notable changes to this project will be documented in this file. - *(ui)* Improve valid until handling - Improve code quality suggested by code rabbit - *(supabase)* Update Supabase service template and Postgres image version +- *(versions)* Update version numbers for coolify and nightly ## [4.0.0-beta.398] - 2025-03-01 From 2947a9ff1d42ecd4d600b5e8885a8260410747f8 Mon Sep 17 00:00:00 2001 From: Karan Vijayakumar Date: Fri, 28 Mar 2025 01:16:29 +0900 Subject: [PATCH 02/22] feat(deploy): add pull request ID parameter to deploy endpoint --- app/Http/Controllers/Api/DeployController.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 73b452f86..6cdba8262 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -142,6 +142,7 @@ class DeployController extends Controller new OA\Parameter(name: 'tag', in: 'query', description: 'Tag name(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')), new OA\Parameter(name: 'uuid', in: 'query', description: 'Resource UUID(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')), new OA\Parameter(name: 'force', in: 'query', description: 'Force rebuild (without cache)', schema: new OA\Schema(type: 'boolean')), + new OA\Parameter(name: 'pr', in: 'query', description: 'Pull Request Id', schema: new OA\Schema(type: 'integer')), ], responses: [ @@ -187,6 +188,8 @@ class DeployController extends Controller $uuids = $request->query->get('uuid'); $tags = $request->query->get('tag'); $force = $request->query->get('force') ?? false; + $pr = $request->query->get('pr'); + if ($uuids && $tags) { return response()->json(['message' => 'You can only use uuid or tag, not both.'], 400); @@ -194,16 +197,19 @@ class DeployController extends Controller if (is_null($teamId)) { return invalidTokenResponse(); } + if ($tags && $pr) { + return response()->json(['message' => 'You can only use tag or pr, not both.'], 400); + } if ($tags) { return $this->by_tags($tags, $teamId, $force); } elseif ($uuids) { - return $this->by_uuids($uuids, $teamId, $force); + return $this->by_uuids($uuids, $teamId, $force, $pr); } return response()->json(['message' => 'You must provide uuid or tag.'], 400); } - private function by_uuids(string $uuid, int $teamId, bool $force = false) + private function by_uuids(string $uuid, int $teamId, bool $force = false, int $pr = 0) { $uuids = explode(',', $uuid); $uuids = collect(array_filter($uuids)); @@ -216,7 +222,7 @@ class DeployController extends Controller foreach ($uuids as $uuid) { $resource = getResourceByUuid($uuid, $teamId); if ($resource) { - ['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force); + ['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force, $pr); if ($deployment_uuid) { $deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]); } else { @@ -281,7 +287,7 @@ class DeployController extends Controller return response()->json(['message' => 'No resources found with this tag.'], 404); } - public function deploy_resource($resource, bool $force = false): array + public function deploy_resource($resource, bool $force = false, int $pr = 0): array { $message = null; $deployment_uuid = null; @@ -295,6 +301,7 @@ class DeployController extends Controller application: $resource, deployment_uuid: $deployment_uuid, force_rebuild: $force, + pull_request_id: $pr, ); $message = "Application {$resource->name} deployment queued."; break; From 045b6341c025941db1671f4732928d433082d872 Mon Sep 17 00:00:00 2001 From: Karan Vijayakumar Date: Fri, 28 Mar 2025 01:27:14 +0900 Subject: [PATCH 03/22] feat(api): add pull request ID parameter to applications endpoint --- openapi.json | 8 ++++++++ openapi.yaml | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/openapi.json b/openapi.json index 819f229cc..1d7d69fe7 100644 --- a/openapi.json +++ b/openapi.json @@ -4477,6 +4477,14 @@ "schema": { "type": "boolean" } + }, + { + "name": "pr", + "in": "query", + "description": "Pull request ID", + "schema": { + "type": "integer" + } } ], "responses": { diff --git a/openapi.yaml b/openapi.yaml index c965e9fe2..7cddb7b55 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -3152,6 +3152,12 @@ paths: description: 'Force rebuild (without cache)' schema: type: boolean + - + name: pr + in: query + description: 'Pull request id' + schema: + type: integer responses: '200': description: "Get deployment(s) UUID's" From f0a1abc55d38c8faadfd7e02baee7f3da6f608d6 Mon Sep 17 00:00:00 2001 From: Karan Vijayakumar Date: Fri, 28 Mar 2025 01:50:21 +0900 Subject: [PATCH 04/22] fix(DeployController): cast 'pr' query parameter to integer Ensure the 'pr' query parameter is consistently treated as an integer to avoid type-related issues in subsequent logic --- app/Http/Controllers/Api/DeployController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 6cdba8262..9396995ed 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -188,7 +188,7 @@ class DeployController extends Controller $uuids = $request->query->get('uuid'); $tags = $request->query->get('tag'); $force = $request->query->get('force') ?? false; - $pr = $request->query->get('pr'); + $pr = $request->query->get('pr') ? (int) $request->query->get('pr') : 0; if ($uuids && $tags) { From d71b669cb8a89b72d055c62373ebb20657d1f26c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 28 Mar 2025 21:19:02 +0000 Subject: [PATCH 05/22] docs: update changelog --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20c416b02..e4e773f22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,35 @@ All notable changes to this project will be documented in this file. ## [unreleased] +### 🐛 Bug Fixes + +- *(file-storage)* Double save on compose volumes + +### 🚜 Refactor + +- *(nightly)* Update version numbers and enhance upgrade script +- *(versions)* Update version numbers for coolify and nightly +- *(email)* Validate team membership for email recipients +- *(shared)* Simplify deployment status check logic +- *(shared)* Add logging for running deployment jobs +- *(shared)* Enhance job status check to include 'reserved' +- *(email)* Improve error handling by passing context to handleError +- *(email)* Streamline email sending logic and improve configuration handling +- *(email)* Remove unnecessary whitespace in email sending logic +- *(email)* Allow custom email recipients in email sending logic +- *(email)* Enhance sender information formatting in email logic +- *(proxy)* Remove redundant stop call in restart method +- *(file-storage)* Add loadStorageOnServer method for improved error handling +- *(docker)* Parse and sanitize YAML compose file before encoding +- *(file-storage)* Improve layout and structure of input fields +- *(email)* Update label for test email recipient input + +### 📚 Documentation + +- Update changelog + +## [4.0.0-beta.400] - 2025-03-27 + ### 🚀 Features - *(database)* Disable MongoDB SSL by default in migration From 5cbb23e2de927cf86c21d4d80bc818260c8d006d Mon Sep 17 00:00:00 2001 From: Shixian Sheng Date: Sun, 30 Mar 2025 07:47:44 -0400 Subject: [PATCH 06/22] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c055f1009..d69bad70a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -243,4 +243,4 @@ To add a new service to Coolify, please refer to our documentation: ### Contributing to Documentation To contribute to the Coolify documentation, please refer to this guide: -[Contributing to the Coolify Documentation](https://github.com/coollabsio/documentation-coolify/blob/main/CONTRIBUTING.md) +[Contributing to the Coolify Documentation](https://github.com/coollabsio/documentation-coolify/blob/main/docs/get-started/contribute/documentation.md) From a8018ad2c4928bab68a3e4a2361eb5657f3cc49e Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sun, 30 Mar 2025 18:04:09 +0200 Subject: [PATCH 07/22] refactor(database): improve decryption and deduplication of local file volumes --- ...00_revert_some_local_volume_encryption.php | 60 ++++++++++++++++--- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/database/migrations/2025_03_29_204400_revert_some_local_volume_encryption.php b/database/migrations/2025_03_29_204400_revert_some_local_volume_encryption.php index 683f1be3d..13419e82f 100644 --- a/database/migrations/2025_03_29_204400_revert_some_local_volume_encryption.php +++ b/database/migrations/2025_03_29_204400_revert_some_local_volume_encryption.php @@ -13,15 +13,17 @@ return new class extends Migration public function up(): void { if (DB::table('local_file_volumes')->exists()) { + // First, get all volumes and decrypt their values + $decryptedVolumes = collect(); + DB::table('local_file_volumes') ->orderBy('id') - ->chunk(100, function ($volumes) { + ->chunk(100, function ($volumes) use (&$decryptedVolumes) { foreach ($volumes as $volume) { - DB::beginTransaction(); - try { $fs_path = $volume->fs_path; $mount_path = $volume->mount_path; + try { if ($fs_path) { $fs_path = Crypt::decryptString($fs_path); @@ -36,18 +38,60 @@ return new class extends Migration } catch (\Exception $e) { } - DB::table('local_file_volumes')->where('id', $volume->id)->update([ + $decryptedVolumes->push([ + 'id' => $volume->id, 'fs_path' => $fs_path, 'mount_path' => $mount_path, + 'resource_id' => $volume->resource_id, + 'resource_type' => $volume->resource_type, ]); - echo "Updated volume {$volume->id}\n"; + } catch (\Exception $e) { - echo "Error encrypting local file volume fields: {$e->getMessage()}\n"; - Log::error('Error encrypting local file volume fields: '.$e->getMessage()); + echo "Error decrypting volume {$volume->id}: {$e->getMessage()}\n"; + Log::error("Error decrypting volume {$volume->id}: ".$e->getMessage()); } - DB::commit(); } }); + + // Group by the unique constraint fields and keep only the first occurrence + $uniqueVolumes = $decryptedVolumes->groupBy(function ($volume) { + return $volume['mount_path'].'|'.$volume['resource_id'].'|'.$volume['resource_type']; + })->map(function ($group) { + return $group->first(); + }); + + // Get IDs to delete (all except the ones we're keeping) + $idsToKeep = $uniqueVolumes->pluck('id')->toArray(); + $idsToDelete = $decryptedVolumes->pluck('id')->diff($idsToKeep)->toArray(); + + // Delete duplicate records + if (! empty($idsToDelete)) { + // Show details of volumes being deleted + $volumesToDelete = $decryptedVolumes->whereIn('id', $idsToDelete); + echo "\nVolumes to be deleted:\n"; + foreach ($volumesToDelete as $volume) { + echo "ID: {$volume['id']}, Mount Path: {$volume['mount_path']}, Resource ID: {$volume['resource_id']}, Resource Type: {$volume['resource_type']}\n"; + echo "FS Path: {$volume['fs_path']}\n"; + echo "-------------------\n"; + } + + DB::table('local_file_volumes')->whereIn('id', $idsToDelete)->delete(); + echo 'Deleted '.count($idsToDelete)." duplicate volume(s)\n"; + } + + // Update the remaining records with decrypted values + foreach ($uniqueVolumes as $volume) { + try { + DB::table('local_file_volumes')->where('id', $volume['id'])->update([ + 'fs_path' => $volume['fs_path'], + 'mount_path' => $volume['mount_path'], + ]); + echo "Updated volume {$volume['id']}\n"; + } catch (\Exception $e) { + echo "Error updating volume {$volume['id']}: {$e->getMessage()}\n"; + Log::error("Error updating volume {$volume['id']}: ".$e->getMessage()); + } + } } } From c7591fde15fbef7ef6ce238ff563dbc782fbfc8f Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sun, 30 Mar 2025 20:07:56 +0200 Subject: [PATCH 08/22] refactor(database): remove debug output from volume update process --- .../2025_03_29_204400_revert_some_local_volume_encryption.php | 1 - 1 file changed, 1 deletion(-) diff --git a/database/migrations/2025_03_29_204400_revert_some_local_volume_encryption.php b/database/migrations/2025_03_29_204400_revert_some_local_volume_encryption.php index 13419e82f..fe3e51318 100644 --- a/database/migrations/2025_03_29_204400_revert_some_local_volume_encryption.php +++ b/database/migrations/2025_03_29_204400_revert_some_local_volume_encryption.php @@ -86,7 +86,6 @@ return new class extends Migration 'fs_path' => $volume['fs_path'], 'mount_path' => $volume['mount_path'], ]); - echo "Updated volume {$volume['id']}\n"; } catch (\Exception $e) { echo "Error updating volume {$volume['id']}: {$e->getMessage()}\n"; Log::error("Error updating volume {$volume['id']}: ".$e->getMessage()); From 887a96dd359ace2b5da0a3c0789ce86aed348df6 Mon Sep 17 00:00:00 2001 From: Darren Sisson Date: Mon, 31 Mar 2025 10:44:29 +0100 Subject: [PATCH 09/22] removed start interval as it is not a valid option and is not defined in coolify --- app/Models/Application.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/Models/Application.php b/app/Models/Application.php index 57a69423d..d07577cc7 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -1530,7 +1530,6 @@ class Application extends BaseModel $interval = str($healthcheckCommand)->match('/--interval=([0-9]+[a-zµ]*)/'); $timeout = str($healthcheckCommand)->match('/--timeout=([0-9]+[a-zµ]*)/'); $start_period = str($healthcheckCommand)->match('/--start-period=([0-9]+[a-zµ]*)/'); - $start_interval = str($healthcheckCommand)->match('/--start-interval=([0-9]+[a-zµ]*)/'); $retries = str($healthcheckCommand)->match('/--retries=(\d+)/'); if ($interval->isNotEmpty()) { @@ -1542,13 +1541,10 @@ class Application extends BaseModel if ($start_period->isNotEmpty()) { $this->health_check_start_period = parseDockerfileInterval($start_period); } - if ($start_interval->isNotEmpty()) { - $this->health_check_start_interval = parseDockerfileInterval($start_interval); - } if ($retries->isNotEmpty()) { $this->health_check_retries = $retries->toInteger(); } - if ($interval || $timeout || $start_period || $start_interval || $retries) { + if ($interval || $timeout || $start_period || $retries) { $this->custom_healthcheck_found = true; $this->save(); } From 3b04d3df9196f4030518b317c20d045b8aaa7e4f Mon Sep 17 00:00:00 2001 From: __m__ Date: Mon, 31 Mar 2025 12:31:17 +0100 Subject: [PATCH 10/22] feat(deployments): add list application deployments api route --- app/Http/Controllers/Api/DeployController.php | 71 +++++++++++++++++++ routes/api.php | 1 + 2 files changed, 72 insertions(+) diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 73b452f86..4bb7dedbd 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -6,6 +6,7 @@ use App\Actions\Database\StartDatabase; use App\Actions\Service\StartService; use App\Http\Controllers\Controller; use App\Models\ApplicationDeploymentQueue; +use App\Models\Application; use App\Models\Server; use App\Models\Tag; use Illuminate\Http\Request; @@ -314,4 +315,74 @@ class DeployController extends Controller return ['message' => $message, 'deployment_uuid' => $deployment_uuid]; } + + #[OA\Get( + summary: 'List application deployments', + description: 'List application deployments by using the app uuid', + path: '/deployments/applications/{uuid}', + operationId: 'list-deployments', + security: [ + ['bearerAuth' => []], + ], + tags: ['Deployments'], + responses: [ + new OA\Response( + response: 200, + description: 'List application deployments by using the app uuid.', + content: [ + + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'array', + items: new OA\Items(ref: '#/components/schemas/Application'), + ) + ), + ]), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + ] + )] + public function get_application_deployments(Request $request) + { + $request->validate([ + 'skip' => ['nullable', 'integer', 'min:0'], + 'take' => ['nullable', 'integer', 'min:1'], + ]); + + $app_uuid = $request->route('uuid', null); + $skip = $request->get('skip', 0); + $take = $request->get('take', 10); + + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + $servers = Server::whereTeamId($teamId)->get(); + + if (is_null($app_uuid)) { + return response()->json(['message' => 'Application uuid is required'], 400); + } + + $application = Application::where('uuid', $app_uuid)->first(); + + if (is_null($application)) { + return response()->json(['message' => 'Application not found'], 404); + } + + $team = $application->team(); + if ($team->id != $teamId) { + return response()->json(['message' => 'Application not found'], 404); + } + + $deployments = $application->deployments($skip, $take); + + return response()->json($deployments); + } } diff --git a/routes/api.php b/routes/api.php index d2aa0a5b7..b65e59e8b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -56,6 +56,7 @@ Route::group([ Route::match(['get', 'post'], '/deploy', [DeployController::class, 'deploy'])->middleware(['api.ability:write,deploy']); Route::get('/deployments', [DeployController::class, 'deployments'])->middleware(['api.ability:read']); Route::get('/deployments/{uuid}', [DeployController::class, 'deployment_by_uuid'])->middleware(['api.ability:read']); + Route::get('/deployments/applications/{uuid}', [DeployController::class, 'get_application_deployments'])->middleware(['api.ability:read']); Route::get('/servers', [ServersController::class, 'servers'])->middleware(['api.ability:read']); Route::get('/servers/{uuid}', [ServersController::class, 'server_by_uuid'])->middleware(['api.ability:read']); From 4fbfff9a2a399bfe630c240549da13fc0130a6c3 Mon Sep 17 00:00:00 2001 From: Shixian Sheng Date: Mon, 31 Mar 2025 07:34:58 -0400 Subject: [PATCH 11/22] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d69bad70a..7b642fdf5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -243,4 +243,4 @@ To add a new service to Coolify, please refer to our documentation: ### Contributing to Documentation To contribute to the Coolify documentation, please refer to this guide: -[Contributing to the Coolify Documentation](https://github.com/coollabsio/documentation-coolify/blob/main/docs/get-started/contribute/documentation.md) +[Contributing to the Coolify Documentation](https://github.com/coollabsio/documentation-coolify/blob/main/readme.md) From d6d1c9ad82dbcccd49badbdba204a510de700da8 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 31 Mar 2025 14:02:15 +0200 Subject: [PATCH 12/22] feat(database): add CA certificate generation for database servers --- app/Actions/Database/StartDragonfly.php | 11 ++++++ app/Actions/Database/StartKeydb.php | 11 ++++++ app/Actions/Database/StartMariadb.php | 11 ++++++ app/Actions/Database/StartMongodb.php | 10 +++++ app/Actions/Database/StartMysql.php | 11 ++++++ app/Actions/Database/StartPostgresql.php | 11 ++++++ app/Actions/Database/StartRedis.php | 11 ++++++ .../Project/Database/Dragonfly/General.php | 15 ++++++- app/Models/Server.php | 39 +++++++++++++++++++ 9 files changed, 129 insertions(+), 1 deletion(-) diff --git a/app/Actions/Database/StartDragonfly.php b/app/Actions/Database/StartDragonfly.php index 882ed3c2e..38ad99d2e 100644 --- a/app/Actions/Database/StartDragonfly.php +++ b/app/Actions/Database/StartDragonfly.php @@ -57,6 +57,17 @@ class StartDragonfly $server = $this->database->destination->server; $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + if (! $caCert) { + $server->generateCaCertificate(); + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + } + + if (! $caCert) { + $this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.'); + + return; + } + $this->ssl_certificate = $this->database->sslCertificates()->first(); if (! $this->ssl_certificate) { diff --git a/app/Actions/Database/StartKeydb.php b/app/Actions/Database/StartKeydb.php index 311b5094a..59bcd4123 100644 --- a/app/Actions/Database/StartKeydb.php +++ b/app/Actions/Database/StartKeydb.php @@ -58,6 +58,17 @@ class StartKeydb $server = $this->database->destination->server; $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + if (! $caCert) { + $server->generateCaCertificate(); + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + } + + if (! $caCert) { + $this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.'); + + return; + } + $this->ssl_certificate = $this->database->sslCertificates()->first(); if (! $this->ssl_certificate) { diff --git a/app/Actions/Database/StartMariadb.php b/app/Actions/Database/StartMariadb.php index c29273a66..13dba4b43 100644 --- a/app/Actions/Database/StartMariadb.php +++ b/app/Actions/Database/StartMariadb.php @@ -59,6 +59,17 @@ class StartMariadb $server = $this->database->destination->server; $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + if (! $caCert) { + $server->generateCaCertificate(); + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + } + + if (! $caCert) { + $this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.'); + + return; + } + $this->ssl_certificate = $this->database->sslCertificates()->first(); if (! $this->ssl_certificate) { diff --git a/app/Actions/Database/StartMongodb.php b/app/Actions/Database/StartMongodb.php index 3ea8287ac..ff0233e62 100644 --- a/app/Actions/Database/StartMongodb.php +++ b/app/Actions/Database/StartMongodb.php @@ -63,6 +63,16 @@ class StartMongodb $server = $this->database->destination->server; $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + if (! $caCert) { + $server->generateCaCertificate(); + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + } + + if (! $caCert) { + $this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.'); + + return; + } $this->ssl_certificate = $this->database->sslCertificates()->first(); if (! $this->ssl_certificate) { diff --git a/app/Actions/Database/StartMysql.php b/app/Actions/Database/StartMysql.php index a2e08c316..5d5611e07 100644 --- a/app/Actions/Database/StartMysql.php +++ b/app/Actions/Database/StartMysql.php @@ -59,6 +59,17 @@ class StartMysql $server = $this->database->destination->server; $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + if (! $caCert) { + $server->generateCaCertificate(); + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + } + + if (! $caCert) { + $this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.'); + + return; + } + $this->ssl_certificate = $this->database->sslCertificates()->first(); if (! $this->ssl_certificate) { diff --git a/app/Actions/Database/StartPostgresql.php b/app/Actions/Database/StartPostgresql.php index 97e565ec8..a40eac17b 100644 --- a/app/Actions/Database/StartPostgresql.php +++ b/app/Actions/Database/StartPostgresql.php @@ -64,6 +64,17 @@ class StartPostgresql $server = $this->database->destination->server; $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + if (! $caCert) { + $server->generateCaCertificate(); + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + } + + if (! $caCert) { + $this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.'); + + return; + } + $this->ssl_certificate = $this->database->sslCertificates()->first(); if (! $this->ssl_certificate) { diff --git a/app/Actions/Database/StartRedis.php b/app/Actions/Database/StartRedis.php index 9e7a2a084..68a1f3fe3 100644 --- a/app/Actions/Database/StartRedis.php +++ b/app/Actions/Database/StartRedis.php @@ -58,6 +58,17 @@ class StartRedis $server = $this->database->destination->server; $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + if (! $caCert) { + $server->generateCaCertificate(); + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + } + + if (! $caCert) { + $this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.'); + + return; + } + $this->ssl_certificate = $this->database->sslCertificates()->first(); if (! $this->ssl_certificate) { diff --git a/app/Livewire/Project/Database/Dragonfly/General.php b/app/Livewire/Project/Database/Dragonfly/General.php index 9aef91ac4..0fffbef31 100644 --- a/app/Livewire/Project/Database/Dragonfly/General.php +++ b/app/Livewire/Project/Database/Dragonfly/General.php @@ -214,10 +214,23 @@ class General extends Component return; } - $caCert = SslCertificate::where('server_id', $existingCert->server_id) + $server = $this->database->destination->server; + + $caCert = SslCertificate::where('server_id', $server->id) ->where('is_ca_certificate', true) ->first(); + if (! $caCert) { + $server->generateCaCertificate(); + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + } + + if (! $caCert) { + $this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.'); + + return; + } + SslHelper::generateSslCertificate( commonName: $existingCert->commonName, subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [], diff --git a/app/Models/Server.php b/app/Models/Server.php index fedb95697..56aa58e87 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -7,7 +7,9 @@ use App\Actions\Server\InstallDocker; use App\Actions\Server\StartSentinel; use App\Enums\ProxyTypes; use App\Events\ServerReachabilityChanged; +use App\Helpers\SslHelper; use App\Jobs\CheckAndStartSentinelJob; +use App\Jobs\RegenerateSslCertJob; use App\Notifications\Server\Reachable; use App\Notifications\Server\Unreachable; use App\Services\ConfigurationRepository; @@ -1337,4 +1339,41 @@ $schema://$host { $configRepository = app(ConfigurationRepository::class); $configRepository->disableSshMux(); } + + public function generateCaCertificate() + { + try { + ray('Generating CA certificate for server', $this->id); + SslHelper::generateSslCertificate( + commonName: 'Coolify CA Certificate', + serverId: $this->id, + isCaCertificate: true, + validityDays: 10 * 365 + ); + $caCertificate = SslCertificate::where('server_id', $this->id)->where('is_ca_certificate', true)->first(); + ray('CA certificate generated', $caCertificate); + if ($caCertificate) { + $certificateContent = $caCertificate->ssl_certificate; + $caCertPath = config('constants.coolify.base_config_path').'/ssl/'; + + $commands = collect([ + "mkdir -p $caCertPath", + "chown -R 9999:root $caCertPath", + "chmod -R 700 $caCertPath", + "rm -rf $caCertPath/coolify-ca.crt", + "echo '{$certificateContent}' > $caCertPath/coolify-ca.crt", + "chmod 644 $caCertPath/coolify-ca.crt", + ]); + + instant_remote_process($commands, $this, false); + + dispatch(new RegenerateSslCertJob( + server_id: $this->id, + force_regeneration: true + )); + } + } catch (\Throwable $e) { + return handleError($e); + } + } } From fcfd00eebe9b8de29a38a3ef387103814417e5fe Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 31 Mar 2025 15:10:50 +0200 Subject: [PATCH 13/22] feat(application): add SPA configuration and update Nginx generation logic --- app/Jobs/ApplicationDeploymentJob.php | 12 +++++-- app/Livewire/Project/Application/General.php | 10 ++++-- bootstrap/helpers/shared.php | 34 +++++++++++++++++-- ..._124212_add_specific_spa_configuration.php | 28 +++++++++++++++ .../project/application/general.blade.php | 18 ++++++---- 5 files changed, 88 insertions(+), 14 deletions(-) create mode 100644 database/migrations/2025_03_31_124212_add_specific_spa_configuration.php diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 92186953b..b72d2ade3 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -2027,7 +2027,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); if (str($this->application->custom_nginx_configuration)->isNotEmpty()) { $nginx_config = base64_encode($this->application->custom_nginx_configuration); } else { - $nginx_config = base64_encode(defaultNginxConfiguration()); + if ($this->application->settings->is_spa) { + $nginx_config = base64_encode(defaultNginxConfiguration('spa')); + } else { + $nginx_config = base64_encode(defaultNginxConfiguration()); + } } } else { if ($this->application->build_pack === 'nixpacks') { @@ -2094,7 +2098,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); if (str($this->application->custom_nginx_configuration)->isNotEmpty()) { $nginx_config = base64_encode($this->application->custom_nginx_configuration); } else { - $nginx_config = base64_encode(defaultNginxConfiguration()); + if ($this->application->settings->is_spa) { + $nginx_config = base64_encode(defaultNginxConfiguration('spa')); + } else { + $nginx_config = base64_encode(defaultNginxConfiguration()); + } } } $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 1d58ed33a..b85023a0c 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -86,6 +86,7 @@ class General extends Component 'application.post_deployment_command_container' => 'nullable', 'application.custom_nginx_configuration' => 'nullable', 'application.settings.is_static' => 'boolean|required', + 'application.settings.is_spa' => 'boolean|required', 'application.settings.is_build_server_enabled' => 'boolean|required', 'application.settings.is_container_label_escape_enabled' => 'boolean|required', 'application.settings.is_container_label_readonly_enabled' => 'boolean|required', @@ -124,6 +125,7 @@ class General extends Component 'application.docker_compose_custom_build_command' => 'Docker compose custom build command', 'application.custom_nginx_configuration' => 'Custom Nginx configuration', 'application.settings.is_static' => 'Is static', + 'application.settings.is_spa' => 'Is SPA', 'application.settings.is_build_server_enabled' => 'Is build server enabled', 'application.settings.is_container_label_escape_enabled' => 'Is container label escape enabled', 'application.settings.is_container_label_readonly_enabled' => 'Is container label readonly', @@ -171,6 +173,9 @@ class General extends Component public function instantSave() { + if ($this->application->settings->isDirty('is_spa')) { + $this->generateNginxConfiguration($this->application->settings->is_spa ? 'spa' : 'static'); + } $this->application->settings->save(); $this->dispatch('success', 'Settings saved.'); $this->application->refresh(); @@ -190,6 +195,7 @@ class General extends Component if ($this->application->settings->is_container_label_readonly_enabled) { $this->resetDefaultLabels(false); } + } public function loadComposeFile($isInit = false) @@ -287,9 +293,9 @@ class General extends Component } } - public function generateNginxConfiguration() + public function generateNginxConfiguration($type = 'static') { - $this->application->custom_nginx_configuration = defaultNginxConfiguration(); + $this->application->custom_nginx_configuration = defaultNginxConfiguration($type); $this->application->save(); $this->dispatch('success', 'Nginx configuration generated.'); } diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index a020e7558..12bfccfaf 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -4061,9 +4061,35 @@ function isEmailRateLimited(string $limiterKey, int $decaySeconds = 3600, ?calla return $rateLimited; } -function defaultNginxConfiguration(): string +function defaultNginxConfiguration(string $type = 'static'): string { - return 'server { + if ($type === 'spa') { + return <<<'NGINX' +server { + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html; + } + + # Handle 404 errors + error_page 404 /404.html; + location = /404.html { + root /usr/share/nginx/html; + internal; + } + + # Handle server errors (50x) + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + internal; + } +} +NGINX; + } else { + return <<<'NGINX' +server { location / { root /usr/share/nginx/html; index index.html index.htm; @@ -4083,7 +4109,9 @@ function defaultNginxConfiguration(): string root /usr/share/nginx/html; internal; } -}'; +} +NGINX; + } } function convertGitUrl(string $gitRepository, string $deploymentType, ?GithubApp $source = null): array diff --git a/database/migrations/2025_03_31_124212_add_specific_spa_configuration.php b/database/migrations/2025_03_31_124212_add_specific_spa_configuration.php new file mode 100644 index 000000000..1ec0d722b --- /dev/null +++ b/database/migrations/2025_03_31_124212_add_specific_spa_configuration.php @@ -0,0 +1,28 @@ +boolean('is_spa')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('application_settings', function (Blueprint $table) { + $table->dropColumn('is_spa'); + }); + } +}; diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 1cc71d063..8c12d1d62 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -69,6 +69,17 @@ Generate Default Nginx Configuration @endif +
+ @if ($application->could_set_build_commands()) + + @endif + @if ($application->settings->is_static && $application->build_pack !== 'static') + + @endif +
@if ($application->build_pack !== 'dockercompose')
@if ($application->settings->is_container_label_readonly_enabled == false) @@ -274,13 +285,6 @@ label="Use a Build Server?" />
@endif - @if ($application->could_set_build_commands()) -
- -
- @endif @endif @endif From adc3d952544e7e602c18adbcce1b76dc18f0e51b Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 31 Mar 2025 16:52:51 +0200 Subject: [PATCH 14/22] fix: only get apps for the current team --- app/Http/Controllers/Api/DeployController.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 4bb7dedbd..870f7fdce 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -5,8 +5,8 @@ namespace App\Http\Controllers\Api; use App\Actions\Database\StartDatabase; use App\Actions\Service\StartService; use App\Http\Controllers\Controller; -use App\Models\ApplicationDeploymentQueue; use App\Models\Application; +use App\Models\ApplicationDeploymentQueue; use App\Models\Server; use App\Models\Tag; use Illuminate\Http\Request; @@ -370,17 +370,11 @@ class DeployController extends Controller return response()->json(['message' => 'Application uuid is required'], 400); } - $application = Application::where('uuid', $app_uuid)->first(); + $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $app_uuid)->first(); if (is_null($application)) { return response()->json(['message' => 'Application not found'], 404); } - - $team = $application->team(); - if ($team->id != $teamId) { - return response()->json(['message' => 'Application not found'], 404); - } - $deployments = $application->deployments($skip, $take); return response()->json($deployments); From 99dd516d6b33713629abe2130bf8a29d06c1b828 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 31 Mar 2025 17:22:40 +0200 Subject: [PATCH 15/22] Update app/Http/Controllers/Api/DeployController.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- app/Http/Controllers/Api/DeployController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 9396995ed..a3adcbb6b 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -142,7 +142,7 @@ class DeployController extends Controller new OA\Parameter(name: 'tag', in: 'query', description: 'Tag name(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')), new OA\Parameter(name: 'uuid', in: 'query', description: 'Resource UUID(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')), new OA\Parameter(name: 'force', in: 'query', description: 'Force rebuild (without cache)', schema: new OA\Schema(type: 'boolean')), - new OA\Parameter(name: 'pr', in: 'query', description: 'Pull Request Id', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'pr', in: 'query', description: 'Pull Request Id for deploying specific PR builds. Cannot be used with tag parameter.', schema: new OA\Schema(type: 'integer')), ], responses: [ From d8248ba15362829a583faf8725d0a987131dde2d Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 31 Mar 2025 17:23:58 +0200 Subject: [PATCH 16/22] Update app/Http/Controllers/Api/DeployController.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- app/Http/Controllers/Api/DeployController.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index a3adcbb6b..2da517256 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -188,8 +188,7 @@ class DeployController extends Controller $uuids = $request->query->get('uuid'); $tags = $request->query->get('tag'); $force = $request->query->get('force') ?? false; - $pr = $request->query->get('pr') ? (int) $request->query->get('pr') : 0; - + $pr = $request->query->get('pr') ? max((int) $request->query->get('pr'), 0) : 0; if ($uuids && $tags) { return response()->json(['message' => 'You can only use uuid or tag, not both.'], 400); From ee5c0f0413a78843603de1e2db2cb3a00aabfe27 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 31 Mar 2025 17:33:01 +0200 Subject: [PATCH 17/22] fix(deploy): validate team ID before deployment --- app/Http/Controllers/Api/DeployController.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 2da517256..7772f15e2 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -185,6 +185,11 @@ class DeployController extends Controller public function deploy(Request $request) { $teamId = getTeamIdFromToken(); + + if (is_null($teamId)) { + return invalidTokenResponse(); + } + $uuids = $request->query->get('uuid'); $tags = $request->query->get('tag'); $force = $request->query->get('force') ?? false; @@ -193,9 +198,6 @@ class DeployController extends Controller if ($uuids && $tags) { return response()->json(['message' => 'You can only use uuid or tag, not both.'], 400); } - if (is_null($teamId)) { - return invalidTokenResponse(); - } if ($tags && $pr) { return response()->json(['message' => 'You can only use tag or pr, not both.'], 400); } From 1d72403d3033f3266bc5e3481257edd3f9832a55 Mon Sep 17 00:00:00 2001 From: CaptainFallaway <69320425+CaptainFallaway@users.noreply.github.com> Date: Mon, 31 Mar 2025 17:38:22 +0200 Subject: [PATCH 18/22] chore(service): update minecraft service ENVs --- templates/compose/minecraft.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/templates/compose/minecraft.yaml b/templates/compose/minecraft.yaml index 544120097..f0574ed44 100644 --- a/templates/compose/minecraft.yaml +++ b/templates/compose/minecraft.yaml @@ -25,16 +25,19 @@ services: - ANNOUNCE_PLAYER_ACHIEVEMENTS=${MINECRAFT_ANNOUNCE_PLAYER_ACHIEVEMENTS:-true} - GENERATE_STRUCTURES=${MINECRAFT_GENERATE_STRUCTURES:-true} - PVP=${MINECRAFT_PVP:-true} + - MODE=${MINECRAFT_GAME_MODE:-survival} - FORCE_GAMEMODE=${MINECRAFT_FORCE_GAMEMODE:-false} - HARDCORE=${MINECRAFT_HARDCORE:-false} - ENABLE_COMMAND_BLOCK=${MINECRAFT_ENABLE_COMMAND_BLOCK:-false} - SPAWN_ANIMALS=${MINECRAFT_SPAWN_ANIMALS:-true} - SPAWN_MONSTERS=${MINECRAFT_SPAWN_MONSTERS:-true} - SPAWN_NPCS=${MINECRAFT_SPAWN_NPCS:-true} - - SNOOPER_ENABLED=${MINECRAFT_SNOOPER_ENABLED:-true} + - SNOOPER_ENABLED=${MINECRAFT_SNOOPER_ENABLED:-false} - ONLINE_MODE=${MINECRAFT_ONLINE_MODE:-true} - PLAYER_IDLE_TIMEOUT=${MINECRAFT_PLAYER_IDLE_TIMEOUT:-0} - - MEMORY=${MINECRAFT_MEMORY:-1G} + - INIT_MEMORY=${MINECRAFT_INIT_MEMORY:-256M} + - MAX_MEMORY=${MINECRAFT_MAX_MEMORY:-1G} + - GUI=${MINECRAFT_GUI:-false} - ENABLE_AUTOPAUSE=${MINECRAFT_ENABLE_AUTOPAUSE:-false} - RCON_PASSWORD=${SERVICE_PASSWORD_RCON} - PORT=${PORT:-25565} From 799855ea281136723ad1acd2ab96132cbbdd88a2 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 31 Mar 2025 17:38:50 +0200 Subject: [PATCH 19/22] refactor(dev): remove OpenAPI generation functionality --- app/Console/Commands/Dev.php | 34 +--------------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/app/Console/Commands/Dev.php b/app/Console/Commands/Dev.php index 257de0a92..a4cfde6f8 100644 --- a/app/Console/Commands/Dev.php +++ b/app/Console/Commands/Dev.php @@ -5,12 +5,10 @@ namespace App\Console\Commands; use App\Models\InstanceSettings; use Illuminate\Console\Command; use Illuminate\Support\Facades\Artisan; -use Illuminate\Support\Facades\Process; -use Symfony\Component\Yaml\Yaml; class Dev extends Command { - protected $signature = 'dev {--init} {--generate-openapi}'; + protected $signature = 'dev {--init}'; protected $description = 'Helper commands for development.'; @@ -21,36 +19,6 @@ class Dev extends Command return; } - if ($this->option('generate-openapi')) { - $this->generateOpenApi(); - - return; - } - } - - public function generateOpenApi() - { - // Generate OpenAPI documentation - echo "Generating OpenAPI documentation.\n"; - // https://github.com/OAI/OpenAPI-Specification/releases - $process = Process::run([ - '/var/www/html/vendor/bin/openapi', - 'app', - '-o', - 'openapi.yaml', - '--version', - '3.1.0', - ]); - $error = $process->errorOutput(); - $error = preg_replace('/^.*an object literal,.*$/m', '', $error); - $error = preg_replace('/^\h*\v+/m', '', $error); - echo $error; - echo $process->output(); - // Convert YAML to JSON - $yaml = file_get_contents('openapi.yaml'); - $json = json_encode(Yaml::parse($yaml), JSON_PRETTY_PRINT); - file_put_contents('openapi.json', $json); - echo "Converted OpenAPI YAML to JSON.\n"; } public function init() From 9f67633f2cff416d3cb481515dda12863ae3ce1f Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 31 Mar 2025 17:38:54 +0200 Subject: [PATCH 20/22] feat(api): add endpoints for retrieving application logs and deployments --- app/Console/Commands/OpenApi.php | 6 + app/Http/Controllers/Api/DeployController.php | 2 +- openapi.json | 216 +++++++++++++++++- openapi.yaml | 102 +++------ 4 files changed, 246 insertions(+), 80 deletions(-) diff --git a/app/Console/Commands/OpenApi.php b/app/Console/Commands/OpenApi.php index 6cbcb310c..3cef85477 100644 --- a/app/Console/Commands/OpenApi.php +++ b/app/Console/Commands/OpenApi.php @@ -4,6 +4,7 @@ namespace App\Console\Commands; use Illuminate\Console\Command; use Illuminate\Support\Facades\Process; +use Symfony\Component\Yaml\Yaml; class OpenApi extends Command { @@ -29,5 +30,10 @@ class OpenApi extends Command $error = preg_replace('/^\h*\v+/m', '', $error); echo $error; echo $process->output(); + + $yaml = file_get_contents('openapi.yaml'); + $json = json_encode(Yaml::parse($yaml), JSON_PRETTY_PRINT); + file_put_contents('openapi.json', $json); + echo "Converted OpenAPI YAML to JSON.\n"; } } diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 65ed11d9c..424c2cc76 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -328,7 +328,7 @@ class DeployController extends Controller summary: 'List application deployments', description: 'List application deployments by using the app uuid', path: '/deployments/applications/{uuid}', - operationId: 'list-deployments', + operationId: 'list-deployments-by-app-uuid', security: [ ['bearerAuth' => []], ], diff --git a/openapi.json b/openapi.json index 1d7d69fe7..98447067e 100644 --- a/openapi.json +++ b/openapi.json @@ -2105,6 +2105,70 @@ ] } }, + "\/applications\/{uuid}\/logs": { + "get": { + "tags": [ + "Applications" + ], + "summary": "Get application logs.", + "description": "Get application logs by UUID.", + "operationId": "get-application-logs-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "lines", + "in": "query", + "description": "Number of lines to show from the end of the logs.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 100 + } + } + ], + "responses": { + "200": { + "description": "Get application logs by UUID.", + "content": { + "application\/json": { + "schema": { + "properties": { + "logs": { + "type": "string" + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, "\/applications\/{uuid}\/envs": { "get": { "tags": [ @@ -4481,7 +4545,7 @@ { "name": "pr", "in": "query", - "description": "Pull request ID", + "description": "Pull Request Id for deploying specific PR builds. Cannot be used with tag parameter.", "schema": { "type": "integer" } @@ -4531,6 +4595,42 @@ ] } }, + "\/deployments\/applications\/{uuid}": { + "get": { + "tags": [ + "Deployments" + ], + "summary": "List application deployments", + "description": "List application deployments by using the app uuid", + "operationId": "list-deployments-by-app-uuid", + "responses": { + "200": { + "description": "List application deployments by using the app uuid.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/Application" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, "\/version": { "get": { "summary": "Version", @@ -5862,8 +5962,8 @@ "tags": [ "Services" ], - "summary": "Create", - "description": "Create a one-click service", + "summary": "Create service", + "description": "Create a one-click \/ custom service", "operationId": "create-service", "requestBody": { "required": true, @@ -6013,7 +6113,7 @@ }, "responses": { "201": { - "description": "Create a service.", + "description": "Service created successfully.", "content": { "application\/json": { "schema": { @@ -6185,6 +6285,114 @@ "bearerAuth": [] } ] + }, + "patch": { + "tags": [ + "Services" + ], + "summary": "Update", + "description": "Update service by UUID.", + "operationId": "update-service-by-uuid", + "requestBody": { + "description": "Service updated.", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "server_uuid", + "project_uuid", + "environment_name", + "environment_uuid", + "docker_compose_raw" + ], + "properties": { + "name": { + "type": "string", + "description": "The service name." + }, + "description": { + "type": "string", + "description": "The service description." + }, + "project_uuid": { + "type": "string", + "description": "The project UUID." + }, + "environment_name": { + "type": "string", + "description": "The environment name." + }, + "environment_uuid": { + "type": "string", + "description": "The environment UUID." + }, + "server_uuid": { + "type": "string", + "description": "The server UUID." + }, + "destination_uuid": { + "type": "string", + "description": "The destination UUID." + }, + "instant_deploy": { + "type": "boolean", + "description": "The flag to indicate if the service should be deployed instantly." + }, + "connect_to_docker_network": { + "type": "boolean", + "default": false, + "description": "Connect the service to the predefined docker network." + }, + "docker_compose_raw": { + "type": "string", + "description": "The Docker Compose raw content." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Service updated.", + "content": { + "application\/json": { + "schema": { + "properties": { + "uuid": { + "type": "string", + "description": "Service UUID." + }, + "domains": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Service domains." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] } }, "\/services\/{uuid}\/envs": { diff --git a/openapi.yaml b/openapi.yaml index 7cddb7b55..ba4b7193e 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -3155,7 +3155,7 @@ paths: - name: pr in: query - description: 'Pull request id' + description: 'Pull Request Id for deploying specific PR builds. Cannot be used with tag parameter.' schema: type: integer responses: @@ -3174,6 +3174,29 @@ paths: security: - bearerAuth: [] + '/deployments/applications/{uuid}': + get: + tags: + - Deployments + summary: 'List application deployments' + description: 'List application deployments by using the app uuid' + operationId: list-deployments-by-app-uuid + responses: + '200': + description: 'List application deployments by using the app uuid.' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Application' + '401': + $ref: '#/components/responses/401' + '400': + $ref: '#/components/responses/400' + security: + - + bearerAuth: [] /version: get: summary: Version @@ -4001,80 +4024,9 @@ paths: post: tags: - Services - summary: Create - description: 'Create a service' + summary: 'Create service' + description: 'Create a one-click / custom service' operationId: create-service - requestBody: - required: true - content: - application/json: - schema: - required: - - server_uuid - - project_uuid - - environment_name - - environment_uuid - - docker_compose_raw - properties: - name: - type: string - maxLength: 255 - description: 'Name of the service.' - description: - type: string - nullable: true - description: 'Description of the service.' - project_uuid: - type: string - description: 'Project UUID.' - environment_name: - type: string - description: 'Environment name. You need to provide at least one of environment_name or environment_uuid.' - environment_uuid: - type: string - description: 'Environment UUID. You need to provide at least one of environment_name or environment_uuid.' - server_uuid: - type: string - description: 'Server UUID.' - destination_uuid: - type: string - description: 'Destination UUID. Required if server has multiple destinations.' - instant_deploy: - type: boolean - default: false - description: 'Start the service immediately after creation.' - connect_to_docker_network: - type: boolean - default: false - description: 'The flag to connect the service to the predefined Docker network.' - docker_compose_raw: - type: string - description: 'The Docker Compose raw content.' - type: object - responses: - '201': - description: 'Service created successfully.' - content: - application/json: - schema: - properties: - uuid: { type: string, description: 'Service UUID.' } - domains: { type: array, items: { type: string, nullable: true }, description: 'Service domains.' } - type: object - '401': - $ref: '#/components/responses/401' - '400': - $ref: '#/components/responses/400' - security: - - - bearerAuth: [] - /services/one-click: - post: - tags: - - Services - summary: 'Create one-click' - description: 'Create a one-click service' - operationId: create-one-click-service requestBody: required: true content: @@ -4277,7 +4229,7 @@ paths: connect_to_docker_network: type: boolean default: false - description: 'The flag to connect the service to the predefined Docker network.' + description: 'Connect the service to the predefined docker network.' docker_compose_raw: type: string description: 'The Docker Compose raw content.' From 2751b54dce1c4dd812eebbbfedca411a45b9f18b Mon Sep 17 00:00:00 2001 From: Kenneth Iversen Date: Mon, 31 Mar 2025 17:40:36 +0200 Subject: [PATCH 21/22] feat(lang): Added Norwegian language (#5280) --- lang/no.json | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 lang/no.json diff --git a/lang/no.json b/lang/no.json new file mode 100644 index 000000000..204819d47 --- /dev/null +++ b/lang/no.json @@ -0,0 +1,40 @@ +{ + "auth.login": "Logg inn", + "auth.login.authentik": "Logg inn med Authentik", + "auth.login.azure": "Logg inn med Microsoft", + "auth.login.bitbucket": "Logg inn med Bitbucket", + "auth.login.github": "Logg inn med GitHub", + "auth.login.gitlab": "Logg inn med Gitlab", + "auth.login.google": "Logg inn med Google", + "auth.login.infomaniak": "Logg inn med Infomaniak", + "auth.already_registered": "Allerede registrert?", + "auth.confirm_password": "Bekreft passord", + "auth.forgot_password": "Glemt passord", + "auth.forgot_password_send_email": "Send e-post for tilbakestilling av passord", + "auth.register_now": "Registrer deg", + "auth.logout": "Logg ut", + "auth.register": "Registrer", + "auth.registration_disabled": "Registrering er deaktivert. Vennligst kontakt administrator.", + "auth.reset_password": "Tilbakestill passord", + "auth.failed": "Disse legitimasjonene samsvarer ikke med våre registre.", + "auth.failed.callback": "Klarte ikke å behandle tilbakekall fra innloggingsleverandør.", + "auth.failed.password": "Det oppgitte passordet er feil.", + "auth.failed.email": "Vi finner ingen bruker med den e-postadressen.", + "auth.throttle": "For mange innloggingsforsøk. Vennligst prøv igjen om :seconds sekunder.", + "input.name": "Navn", + "input.email": "E-post", + "input.password": "Passord", + "input.password.again": "Passord igjen", + "input.code": "Engangskode", + "input.recovery_code": "Gjenopprettingskode", + "button.save": "Lagre", + "repository.url": "Eksempler
For offentlige repositorier, bruk https://....
For private repositorier, bruk git@....

https://github.com/coollabsio/coolify-examples main gren vil bli valgt
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify gren vil bli valgt.
https://gitea.com/sedlav/expressjs.git main gren vil bli valgt.
https://gitlab.com/andrasbacsai/nodejs-example.git main gren vil bli valgt.", + "service.stop": "Denne tjenesten vil bli stoppet.", + "resource.docker_cleanup": "Kjør Docker-opprydding (fjern ubrukte bilder og byggebuffer).", + "resource.non_persistent": "Alle ikke-persistente data vil bli slettet.", + "resource.delete_volumes": "Slett alle volumer tilknyttet denne ressursen permanent.", + "resource.delete_connected_networks": "Slett alle ikke-forhåndsdefinerte nettverk tilknyttet denne ressursen permanent.", + "resource.delete_configurations": "Slett alle konfigurasjonsfiler fra serveren permanent.", + "database.delete_backups_locally": "Alle sikkerhetskopier vil bli slettet permanent fra lokal lagring.", + "warning.sslipdomain": "Konfigurasjonen din er lagret, men sslip-domene med https er IKKE anbefalt, fordi Let's Encrypt-servere med dette offentlige domenet er hastighetsbegrenset (SSL-sertifikatvalidering vil mislykkes).

Bruk ditt eget domene i stedet." +} \ No newline at end of file From b5eaeef047a7c8cd9138b9b1fad77a49528f24c9 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 31 Mar 2025 17:44:11 +0200 Subject: [PATCH 22/22] Update no.json --- lang/no.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/no.json b/lang/no.json index 204819d47..29d5af124 100644 --- a/lang/no.json +++ b/lang/no.json @@ -37,4 +37,4 @@ "resource.delete_configurations": "Slett alle konfigurasjonsfiler fra serveren permanent.", "database.delete_backups_locally": "Alle sikkerhetskopier vil bli slettet permanent fra lokal lagring.", "warning.sslipdomain": "Konfigurasjonen din er lagret, men sslip-domene med https er IKKE anbefalt, fordi Let's Encrypt-servere med dette offentlige domenet er hastighetsbegrenset (SSL-sertifikatvalidering vil mislykkes).

Bruk ditt eget domene i stedet." -} \ No newline at end of file +}