+ @else
+ To configure automatic backup for your Coolify instance, you first need to add a database resource
+ into Coolify.
+ Configure Backup
+ @endif
@else
- To configure automatic backup for your Coolify instance, you first need to add a database resource
- into Coolify.
- Configure Backup
+
+
+ Instance Backup is currently disabled because the localhost server is not properly validated.
+ Please validate your server to enable Instance Backup.
+
From c6b7779c824e87b5a0683002d0cf330e7c76d625 Mon Sep 17 00:00:00 2001
From: Chingiz Mammadov
Date: Tue, 1 Apr 2025 16:45:44 +0400
Subject: [PATCH 03/16] feat(lang): Added Azerbaijani language updated turkish
language. (#5497)
---
lang/az.json | 40 ++++++++++++++++++++++++++++++++++++++++
lang/tr.json | 10 +++++++++-
2 files changed, 49 insertions(+), 1 deletion(-)
create mode 100644 lang/az.json
diff --git a/lang/az.json b/lang/az.json
new file mode 100644
index 000000000..973c70c2f
--- /dev/null
+++ b/lang/az.json
@@ -0,0 +1,40 @@
+{
+ "auth.login": "Daxil ol",
+ "auth.login.authentik": "Authentik ilə daxil ol",
+ "auth.login.azure": "Azure ilə daxil ol",
+ "auth.login.bitbucket": "Bitbucket ilə daxil ol",
+ "auth.login.github": "Github ilə daxil ol",
+ "auth.login.gitlab": "GitLab ilə daxil ol",
+ "auth.login.google": "Google ilə daxil ol",
+ "auth.login.infomaniak": "Infomaniak ilə daxil ol",
+ "auth.already_registered": "Qeytiyatınız var?",
+ "auth.confirm_password": "Şifrəni təsdiqləyin",
+ "auth.forgot_password": "Şifrəmi unutdum",
+ "auth.forgot_password_send_email": "Şifrəni sıfırlamaq üçün e-poçt göndər",
+ "auth.register_now": "Qeydiyyat",
+ "auth.logout": "Çıxış",
+ "auth.register": "Qeydiyyat",
+ "auth.registration_disabled": "Qeydiyyat bağlıdır. Administratorla əlaqə saxlayın.",
+ "auth.reset_password": "Şifrənin bərpası",
+ "auth.failed": "Bu məlumatlar bizim qeydlərimizlə uyğun gəlmir.",
+ "auth.failed.callback": "Giriş təminatçısından geri çağırma işlənə bilmədi.",
+ "auth.failed.password": "Daxil etdiyiniz şifrə yanlışdır.",
+ "auth.failed.email": "Bu e-poçt ünvanı ilə istifadəçi tapılmadı.",
+ "auth.throttle": "Çox sayda uğursuz giriş cəhdi. Zəhmət olmasa :seconds saniyə sonra yenidən cəhd edin.",
+ "input.name": "Ad",
+ "input.email": "E-poçt",
+ "input.password": "Şifrə",
+ "input.password.again": "Şifrəni təkrar daxil edin",
+ "input.code": "Bir dəfəlik kod",
+ "input.recovery_code": "Bərpa kodu",
+ "button.save": "Yadda saxla",
+ "repository.url": "Nümunələr Publik repozitoriyalar üçün https://... istifadə edin. Özəl repozitoriyalar üçün git@... istifadə edin.
https://github.com/coollabsio/coolify-examples main branch-ı seçiləcək https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify branch-ı seçiləcək. https://gitea.com/sedlav/expressjs.git main branch-ı seçiləcək. https://gitlab.com/andrasbacsai/nodejs-example.git main branch-ı seçiləcək.",
+ "service.stop": "Bu xidmət dayandırılacaq.",
+ "resource.docker_cleanup": "Docker təmizlənməsini işə salın (istifadə olunmayan şəkillər və builder keşini silin).",
+ "resource.non_persistent": "Bütün qeyri-daimi məlumatlar silinəcək.",
+ "resource.delete_volumes": "Bu resursla əlaqəli bütün həcm məlumatları tamamilə silinəcək.",
+ "resource.delete_connected_networks": "Bu resursla əlaqəli bütün əvvəlcədən təyin olunmamış şəbəkələr tamamilə silinəcək.",
+ "resource.delete_configurations": "Serverdən bütün konfiqurasiya faylları tamamilə silinəcək.",
+ "database.delete_backups_locally": "Bütün ehtiyat nüsxələr lokal yaddaşdan tamamilə silinəcək.",
+ "warning.sslipdomain": "Konfiqurasiya yadda saxlanıldı, lakin sslip domeni ilə https TÖVSİYƏ EDİLMİR, çünki Let's Encrypt serverləri bu ümumi domenlə məhdudlaşdırılır (SSL sertifikatının təsdiqlənməsi uğursuz olacaq).
Əvəzində öz domeninizdən istifadə edin."
+}
diff --git a/lang/tr.json b/lang/tr.json
index 3cbcee409..663c756f9 100644
--- a/lang/tr.json
+++ b/lang/tr.json
@@ -27,5 +27,13 @@
"input.code": "Tek Kullanımlık Kod",
"input.recovery_code": "Kurtarma Kodu",
"button.save": "Kaydet",
- "repository.url": "Örnekler Halka açık depolar için https://... kullanın. Özel depolar için git@... kullanın.
https://github.com/coollabsio/coolify-examples main dalı seçilecek https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify dalı seçilecek. https://gitea.com/sedlav/expressjs.git main dalı seçilecek. https://gitlab.com/andrasbacsai/nodejs-example.git main dalı seçilecek."
+ "repository.url": "Örnekler Halka açık depolar için https://... kullanın. Özel depolar için git@... kullanın.
https://github.com/coollabsio/coolify-examples main dalı seçilecek https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify dalı seçilecek. https://gitea.com/sedlav/expressjs.git main dalı seçilecek. https://gitlab.com/andrasbacsai/nodejs-example.git main dalı seçilecek.",
+ "service.stop": "Bu servis durdurulacak.",
+ "resource.docker_cleanup": "Docker temizliği çalıştır (kullanılmayan imajları ve oluşturucu önbelleğini kaldır).",
+ "resource.non_persistent": "Tüm kalıcı olmayan veriler silinecek.",
+ "resource.delete_volumes": "Bu kaynakla ilişkili tüm hacimler kalıcı olarak silinecek.",
+ "resource.delete_connected_networks": "Bu kaynakla ilişkili önceden tanımlanmamış tüm ağlar kalıcı olarak silinecek.",
+ "resource.delete_configurations": "Sunucudaki tüm yapılandırma dosyaları kalıcı olarak silinecek.",
+ "database.delete_backups_locally": "Tüm yedekler yerel depolamadan kalıcı olarak silinecek.",
+ "warning.sslipdomain": "Yapılandırmanız kaydedildi, ancak sslip domain ile https ÖNERİLMEZ, çünkü Let's Encrypt sunucuları bu genel domain ile sınırlandırılmıştır (SSL sertifikası doğrulaması başarısız olur).
Bunun yerine kendi domaininizi kullanın."
}
From 1384de7566df186c90e7c0c9030fa4289b1d3931 Mon Sep 17 00:00:00 2001
From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Date: Tue, 1 Apr 2025 20:57:20 +0200
Subject: [PATCH 04/16] fix(docs): comment out execute for now
- Due to security concerns, execute is disabled, so we need to comment out the code as well to update the docs.
---
.../Api/ApplicationsController.php | 368 +++++++++---------
openapi.json | 74 ----
openapi.yaml | 46 ---
3 files changed, 184 insertions(+), 304 deletions(-)
diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php
index cbeb6b55d..0044e86b4 100644
--- a/app/Http/Controllers/Api/ApplicationsController.php
+++ b/app/Http/Controllers/Api/ApplicationsController.php
@@ -2881,198 +2881,198 @@ class ApplicationsController extends Controller
);
}
- #[OA\Post(
- summary: 'Execute Command',
- description: "Execute a command on the application's current container.",
- path: '/applications/{uuid}/execute',
- operationId: 'execute-command-application',
- security: [
- ['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(
- required: true,
- description: 'Command to execute.',
- content: new OA\MediaType(
- mediaType: 'application/json',
- schema: new OA\Schema(
- type: 'object',
- properties: [
- 'command' => ['type' => 'string', 'description' => 'Command to execute.'],
- ],
- ),
- ),
- ),
- responses: [
- new OA\Response(
- response: 200,
- description: "Execute a command on the application's current container.",
- content: [
- new OA\MediaType(
- mediaType: 'application/json',
- schema: new OA\Schema(
- type: 'object',
- properties: [
- 'message' => ['type' => 'string', 'example' => 'Command executed.'],
- 'response' => ['type' => 'string'],
- ]
- )
- ),
- ]
- ),
- new OA\Response(
- response: 401,
- ref: '#/components/responses/401',
- ),
- new OA\Response(
- response: 400,
- ref: '#/components/responses/400',
- ),
- new OA\Response(
- response: 404,
- ref: '#/components/responses/404',
- ),
- ]
- )]
- public function execute_command_by_uuid(Request $request)
- {
- // TODO: Need to review this from security perspective, to not allow arbitrary command execution
- $allowedFields = ['command'];
- $teamId = getTeamIdFromToken();
- if (is_null($teamId)) {
- return invalidTokenResponse();
- }
- $uuid = $request->route('uuid');
- if (! $uuid) {
- return response()->json(['message' => 'UUID is required.'], 400);
- }
- $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
- if (! $application) {
- return response()->json(['message' => 'Application not found.'], 404);
- }
- $return = validateIncomingRequest($request);
- if ($return instanceof \Illuminate\Http\JsonResponse) {
- return $return;
- }
- $validator = customApiValidator($request->all(), [
- 'command' => 'string|required',
- ]);
+ // #[OA\Post(
+ // summary: 'Execute Command',
+ // description: "Execute a command on the application's current container.",
+ // path: '/applications/{uuid}/execute',
+ // operationId: 'execute-command-application',
+ // security: [
+ // ['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(
+ // required: true,
+ // description: 'Command to execute.',
+ // content: new OA\MediaType(
+ // mediaType: 'application/json',
+ // schema: new OA\Schema(
+ // type: 'object',
+ // properties: [
+ // 'command' => ['type' => 'string', 'description' => 'Command to execute.'],
+ // ],
+ // ),
+ // ),
+ // ),
+ // responses: [
+ // new OA\Response(
+ // response: 200,
+ // description: "Execute a command on the application's current container.",
+ // content: [
+ // new OA\MediaType(
+ // mediaType: 'application/json',
+ // schema: new OA\Schema(
+ // type: 'object',
+ // properties: [
+ // 'message' => ['type' => 'string', 'example' => 'Command executed.'],
+ // 'response' => ['type' => 'string'],
+ // ]
+ // )
+ // ),
+ // ]
+ // ),
+ // new OA\Response(
+ // response: 401,
+ // ref: '#/components/responses/401',
+ // ),
+ // new OA\Response(
+ // response: 400,
+ // ref: '#/components/responses/400',
+ // ),
+ // new OA\Response(
+ // response: 404,
+ // ref: '#/components/responses/404',
+ // ),
+ // ]
+ // )]
+ // public function execute_command_by_uuid(Request $request)
+ // {
+ // // TODO: Need to review this from security perspective, to not allow arbitrary command execution
+ // $allowedFields = ['command'];
+ // $teamId = getTeamIdFromToken();
+ // if (is_null($teamId)) {
+ // return invalidTokenResponse();
+ // }
+ // $uuid = $request->route('uuid');
+ // if (! $uuid) {
+ // return response()->json(['message' => 'UUID is required.'], 400);
+ // }
+ // $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+ // if (! $application) {
+ // return response()->json(['message' => 'Application not found.'], 404);
+ // }
+ // $return = validateIncomingRequest($request);
+ // if ($return instanceof \Illuminate\Http\JsonResponse) {
+ // return $return;
+ // }
+ // $validator = customApiValidator($request->all(), [
+ // 'command' => 'string|required',
+ // ]);
- $extraFields = array_diff(array_keys($request->all()), $allowedFields);
- if ($validator->fails() || ! empty($extraFields)) {
- $errors = $validator->errors();
- if (! empty($extraFields)) {
- foreach ($extraFields as $field) {
- $errors->add($field, 'This field is not allowed.');
- }
- }
+ // $extraFields = array_diff(array_keys($request->all()), $allowedFields);
+ // if ($validator->fails() || ! empty($extraFields)) {
+ // $errors = $validator->errors();
+ // if (! empty($extraFields)) {
+ // foreach ($extraFields as $field) {
+ // $errors->add($field, 'This field is not allowed.');
+ // }
+ // }
- return response()->json([
- 'message' => 'Validation failed.',
- 'errors' => $errors,
- ], 422);
- }
+ // return response()->json([
+ // 'message' => 'Validation failed.',
+ // 'errors' => $errors,
+ // ], 422);
+ // }
- $container = getCurrentApplicationContainerStatus($application->destination->server, $application->id)->firstOrFail();
- $status = getContainerStatus($application->destination->server, $container['Names']);
+ // $container = getCurrentApplicationContainerStatus($application->destination->server, $application->id)->firstOrFail();
+ // $status = getContainerStatus($application->destination->server, $container['Names']);
- if ($status !== 'running') {
- return response()->json([
- 'message' => 'Application is not running.',
- ], 400);
- }
+ // if ($status !== 'running') {
+ // return response()->json([
+ // 'message' => 'Application is not running.',
+ // ], 400);
+ // }
- $commands = collect([
- executeInDocker($container['Names'], $request->command),
- ]);
+ // $commands = collect([
+ // executeInDocker($container['Names'], $request->command),
+ // ]);
- $res = instant_remote_process(command: $commands, server: $application->destination->server);
+ // $res = instant_remote_process(command: $commands, server: $application->destination->server);
- return response()->json([
- 'message' => 'Command executed.',
- 'response' => $res,
- ]);
- }
+ // return response()->json([
+ // 'message' => 'Command executed.',
+ // 'response' => $res,
+ // ]);
+ // }
- 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/openapi.json b/openapi.json
index 98447067e..dbbc3dc24 100644
--- a/openapi.json
+++ b/openapi.json
@@ -2771,80 +2771,6 @@
]
}
},
- "\/applications\/{uuid}\/execute": {
- "post": {
- "tags": [
- "Applications"
- ],
- "summary": "Execute Command",
- "description": "Execute a command on the application's current container.",
- "operationId": "execute-command-application",
- "parameters": [
- {
- "name": "uuid",
- "in": "path",
- "description": "UUID of the application.",
- "required": true,
- "schema": {
- "type": "string",
- "format": "uuid"
- }
- }
- ],
- "requestBody": {
- "description": "Command to execute.",
- "required": true,
- "content": {
- "application\/json": {
- "schema": {
- "properties": {
- "command": {
- "type": "string",
- "description": "Command to execute."
- }
- },
- "type": "object"
- }
- }
- }
- },
- "responses": {
- "200": {
- "description": "Execute a command on the application's current container.",
- "content": {
- "application\/json": {
- "schema": {
- "properties": {
- "message": {
- "type": "string",
- "example": "Command executed."
- },
- "response": {
- "type": "string"
- }
- },
- "type": "object"
- }
- }
- }
- },
- "401": {
- "$ref": "#\/components\/responses\/401"
- },
- "400": {
- "$ref": "#\/components\/responses\/400"
- },
- "404": {
- "$ref": "#\/components\/responses\/404"
- }
- },
- "security": [
- {
- "bearerAuth": []
- }
- ]
- }
- },
"\/databases": {
"get": {
"tags": [
diff --git a/openapi.yaml b/openapi.yaml
index ba4b7193e..b8f34ef19 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -1905,52 +1905,6 @@ paths:
security:
-
bearerAuth: []
- '/applications/{uuid}/execute':
- post:
- tags:
- - Applications
- summary: 'Execute Command'
- description: "Execute a command on the application's current container."
- operationId: execute-command-application
- parameters:
- -
- name: uuid
- in: path
- description: 'UUID of the application.'
- required: true
- schema:
- type: string
- format: uuid
- requestBody:
- description: 'Command to execute.'
- required: true
- content:
- application/json:
- schema:
- properties:
- command:
- type: string
- description: 'Command to execute.'
- type: object
- responses:
- '200':
- description: "Execute a command on the application's current container."
- content:
- application/json:
- schema:
- properties:
- message: { type: string, example: 'Command executed.' }
- response: { type: string }
- type: object
- '401':
- $ref: '#/components/responses/401'
- '400':
- $ref: '#/components/responses/400'
- '404':
- $ref: '#/components/responses/404'
- security:
- -
- bearerAuth: []
/databases:
get:
tags:
From 1016b000c6c890280ecafb4e936dab8eb94f315a Mon Sep 17 00:00:00 2001
From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Date: Wed, 2 Apr 2025 14:28:00 +0200
Subject: [PATCH 05/16] fix(installation): mount the docker config
- Mount docker config during installation to use docker credentials, if there are any.
---
scripts/upgrade.sh | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/scripts/upgrade.sh b/scripts/upgrade.sh
index e59f4d138..57c3a09a2 100644
--- a/scripts/upgrade.sh
+++ b/scripts/upgrade.sh
@@ -39,9 +39,15 @@ if ! docker network inspect coolify >/dev/null 2>&1; then
fi
# docker network create --attachable --driver=overlay coolify-overlay 2>/dev/null
+# Check if Docker config file exists
+DOCKER_CONFIG_MOUNT=""
+if [ -f /etc/docker/config.json ]; then
+ DOCKER_CONFIG_MOUNT="-v /etc/docker/config.json:/root/.docker/config.json"
+fi
+
if [ -f /data/coolify/source/docker-compose.custom.yml ]; then
echo "docker-compose.custom.yml detected." >>$LOGFILE
- docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-helper:${LATEST_HELPER_VERSION} bash -c "LATEST_IMAGE=${LATEST_IMAGE} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml -f /data/coolify/source/docker-compose.custom.yml up -d --remove-orphans --force-recreate --wait --wait-timeout 60" >>$LOGFILE 2>&1
+ docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock ${DOCKER_CONFIG_MOUNT} --rm ${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-helper:${LATEST_HELPER_VERSION} bash -c "LATEST_IMAGE=${LATEST_IMAGE} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml -f /data/coolify/source/docker-compose.custom.yml up -d --remove-orphans --force-recreate --wait --wait-timeout 60" >>$LOGFILE 2>&1
else
- docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-helper:${LATEST_HELPER_VERSION} bash -c "LATEST_IMAGE=${LATEST_IMAGE} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --remove-orphans --force-recreate --wait --wait-timeout 60" >>$LOGFILE 2>&1
+ docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock ${DOCKER_CONFIG_MOUNT} --rm ${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-helper:${LATEST_HELPER_VERSION} bash -c "LATEST_IMAGE=${LATEST_IMAGE} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --remove-orphans --force-recreate --wait --wait-timeout 60" >>$LOGFILE 2>&1
fi
From f53f67165655c27072cb4f1a2ead3726f459a778 Mon Sep 17 00:00:00 2001
From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Date: Wed, 2 Apr 2025 14:35:37 +0200
Subject: [PATCH 06/16] fix(installation): path to config file for docker login
---
scripts/upgrade.sh | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/scripts/upgrade.sh b/scripts/upgrade.sh
index 57c3a09a2..32bffad48 100644
--- a/scripts/upgrade.sh
+++ b/scripts/upgrade.sh
@@ -41,8 +41,8 @@ fi
# Check if Docker config file exists
DOCKER_CONFIG_MOUNT=""
-if [ -f /etc/docker/config.json ]; then
- DOCKER_CONFIG_MOUNT="-v /etc/docker/config.json:/root/.docker/config.json"
+if [ -f /root/.docker/config.json ]; then
+ DOCKER_CONFIG_MOUNT="-v /root/.docker/config.json:/root/.docker/config.json"
fi
if [ -f /data/coolify/source/docker-compose.custom.yml ]; then
From 2f6bd04f6bf65781d29c4b0243b864393057b97c Mon Sep 17 00:00:00 2001
From: Elton Ciatto <53355283+eltonciatto@users.noreply.github.com>
Date: Wed, 2 Apr 2025 09:36:57 -0300
Subject: [PATCH 07/16] feat(lang): Added Portuguese from Brazil language
(#5500)
---
lang/pt-br.json | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
create mode 100644 lang/pt-br.json
diff --git a/lang/pt-br.json b/lang/pt-br.json
new file mode 100644
index 000000000..2e793890f
--- /dev/null
+++ b/lang/pt-br.json
@@ -0,0 +1,40 @@
+{
+ "auth.login": "Entrar",
+ "auth.login.authentik": "Entrar com Authentik",
+ "auth.login.azure": "Entrar com Microsoft",
+ "auth.login.bitbucket": "Entrar com Bitbucket",
+ "auth.login.github": "Entrar com GitHub",
+ "auth.login.gitlab": "Entrar com Gitlab",
+ "auth.login.google": "Entrar com Google",
+ "auth.login.infomaniak": "Entrar com Infomaniak",
+ "auth.already_registered": "Já tem uma conta?",
+ "auth.confirm_password": "Confirmar senha",
+ "auth.forgot_password": "Esqueceu a senha",
+ "auth.forgot_password_send_email": "Enviar e-mail para redefinir senha",
+ "auth.register_now": "Cadastre-se",
+ "auth.logout": "Sair",
+ "auth.register": "Cadastrar",
+ "auth.registration_disabled": "O registro está desativado. Por favor, contate o administrador.",
+ "auth.reset_password": "Redefinir senha",
+ "auth.failed": "Essas credenciais não correspondem aos nossos registros.",
+ "auth.failed.callback": "Falha ao processar o callback do provedor de login.",
+ "auth.failed.password": "A senha fornecida está incorreta.",
+ "auth.failed.email": "Não encontramos nenhum usuário com esse endereço de e-mail.",
+ "auth.throttle": "Muitas tentativas de login. Por favor, tente novamente em :seconds segundos.",
+ "input.name": "Nome",
+ "input.email": "E-mail",
+ "input.password": "Senha",
+ "input.password.again": "Senha novamente",
+ "input.code": "Código de uso único",
+ "input.recovery_code": "Código de recuperação",
+ "button.save": "Salvar",
+ "repository.url": "Exemplos Para repositórios públicos, use https://.... Para repositórios privados, use git@....
https://github.com/coollabsio/coolify-examples main branch será selecionado https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify branch será selecionado. https://gitea.com/sedlav/expressjs.git main branch será selecionado. https://gitlab.com/andrasbacsai/nodejs-example.git main branch será selecionado.",
+ "service.stop": "Este serviço será parado.",
+ "resource.docker_cleanup": "Executar limpeza do Docker (remover imagens não utilizadas e cache de build).",
+ "resource.non_persistent": "Todos os dados não persistentes serão excluídos.",
+ "resource.delete_volumes": "Excluir permanentemente todos os volumes associados a este recurso.",
+ "resource.delete_connected_networks": "Excluir permanentemente todas as redes não predefinidas associadas a este recurso.",
+ "resource.delete_configurations": "Excluir permanentemente todos os arquivos de configuração do servidor.",
+ "database.delete_backups_locally": "Todos os backups serão excluídos permanentemente do armazenamento local.",
+ "warning.sslipdomain": "Sua configuração foi salva, mas o domínio sslip com https NÃO é recomendado, porque os servidores do Let's Encrypt com este domínio público têm limitação de taxa (a validação do certificado SSL falhará).
Use seu próprio domínio em vez disso."
+}
From 78ce16d50d2c0a37d4afa9189ab963f27ccc38fc Mon Sep 17 00:00:00 2001
From: Alvin Maulana
Date: Wed, 2 Apr 2025 20:42:24 +0800
Subject: [PATCH 08/16] feat(lang): add Indonesian language translations
(#5513)
---
lang/id.json | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
create mode 100644 lang/id.json
diff --git a/lang/id.json b/lang/id.json
new file mode 100644
index 000000000..d35556402
--- /dev/null
+++ b/lang/id.json
@@ -0,0 +1,40 @@
+{
+ "auth.login": "Masuk",
+ "auth.login.authentik": "Masuk dengan Authentik",
+ "auth.login.azure": "Masuk dengan Microsoft",
+ "auth.login.bitbucket": "Masuk dengan Bitbucket",
+ "auth.login.github": "Masuk dengan GitHub",
+ "auth.login.gitlab": "Masuk dengan Gitlab",
+ "auth.login.google": "Masuk dengan Google",
+ "auth.login.infomaniak": "Masuk dengan Infomaniak",
+ "auth.already_registered": "Sudah terdaftar?",
+ "auth.confirm_password": "Konfirmasi kata sandi",
+ "auth.forgot_password": "Lupa kata sandi",
+ "auth.forgot_password_send_email": "Kirim email reset kata sandi",
+ "auth.register_now": "Daftar",
+ "auth.logout": "Keluar",
+ "auth.register": "Daftar",
+ "auth.registration_disabled": "Pendaftaran dinonaktifkan. Harap hubungi administrator.",
+ "auth.reset_password": "Reset kata sandi",
+ "auth.failed": "Kredensial ini tidak cocok dengan catatan kami.",
+ "auth.failed.callback": "Gagal memproses callback dari penyedia login.",
+ "auth.failed.password": "Kata sandi yang diberikan salah.",
+ "auth.failed.email": "Kami tidak dapat menemukan pengguna dengan alamat e-mail tersebut.",
+ "auth.throttle": "Terlalu banyak percobaan login. Silakan coba lagi dalam :seconds detik.",
+ "input.name": "Nama",
+ "input.email": "Email",
+ "input.password": "Kata sandi",
+ "input.password.again": "Kata sandi lagi",
+ "input.code": "Kode sekali pakai",
+ "input.recovery_code": "Kode pemulihan",
+ "button.save": "Simpan",
+ "repository.url": "Contoh Untuk repositori Publik, gunakan https://.... Untuk repositori Privat, gunakan git@....
https://github.com/coollabsio/coolify-examples cabang main akan dipilih https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify cabang nodejs-fastify akan dipilih. https://gitea.com/sedlav/expressjs.git cabang main akan dipilih. https://gitlab.com/andrasbacsai/nodejs-example.git cabang main akan dipilih.",
+ "service.stop": "Layanan ini akan dihentikan.",
+ "resource.docker_cleanup": "Jalankan Pembersihan Docker (hapus gambar yang tidak digunakan dan cache builder).",
+ "resource.non_persistent": "Semua data non-persisten akan dihapus.",
+ "resource.delete_volumes": "Hapus permanen semua volume yang terkait dengan sumber daya ini.",
+ "resource.delete_connected_networks": "Hapus permanen semua jaringan non-predefined yang terkait dengan sumber daya ini.",
+ "resource.delete_configurations": "Hapus permanen semua file konfigurasi dari server.",
+ "database.delete_backups_locally": "Semua backup akan dihapus permanen dari penyimpanan lokal.",
+ "warning.sslipdomain": "Konfigurasi Anda disimpan, tetapi domain sslip dengan https TIDAK direkomendasikan, karena server Let's Encrypt dengan domain publik ini dibatasi (validasi sertifikat SSL akan gagal).
Gunakan domain Anda sendiri sebagai gantinya."
+}
From 787a56ce51cc99fba5257c261c006e056d1a7be6 Mon Sep 17 00:00:00 2001
From: Klaas van Schelven
Date: Wed, 2 Apr 2025 14:53:27 +0200
Subject: [PATCH 09/16] fix(service): add health check to Bugsink service
(#5512)
---
templates/compose/bugsink.yaml | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/templates/compose/bugsink.yaml b/templates/compose/bugsink.yaml
index bf015d727..5cdca45d1 100644
--- a/templates/compose/bugsink.yaml
+++ b/templates/compose/bugsink.yaml
@@ -36,6 +36,11 @@ services:
depends_on:
mysql:
condition: service_healthy
+ healthcheck:
+ test: ["CMD-SHELL", "python -c 'import requests; requests.get(\"http://localhost:8000/\").raise_for_status()'"]
+ interval: 5s
+ timeout: 20s
+ retries: 10
volumes:
- my-datavolume:
\ No newline at end of file
+ my-datavolume:
From 78e2d63f3833e371b04b1abb0fa9c82df713371c Mon Sep 17 00:00:00 2001
From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Date: Wed, 2 Apr 2025 14:59:31 +0200
Subject: [PATCH 10/16] chore(service): remove unused code in Bugsink service
---
templates/compose/bugsink.yaml | 20 +++++++++-----------
templates/service-templates.json | 2 +-
2 files changed, 10 insertions(+), 12 deletions(-)
diff --git a/templates/compose/bugsink.yaml b/templates/compose/bugsink.yaml
index 5cdca45d1..787852ff8 100644
--- a/templates/compose/bugsink.yaml
+++ b/templates/compose/bugsink.yaml
@@ -3,17 +3,18 @@
# tags: python, error-tracking, django, mysql
# logo: svgs/bugsink.svg
# port: 8000
+
services:
mysql:
image: 'mysql:latest'
restart: unless-stopped
environment:
- - 'MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_ROOT}'
- - 'MYSQL_DATABASE=${MYSQL_DATABASE:-bugsink}'
- - 'MYSQL_USER=${SERVICE_USER_BUGSINK}'
- - 'MYSQL_PASSWORD=${SERVICE_PASSWORD_BUGSINK}'
+ - MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_ROOT}
+ - MYSQL_DATABASE=${MYSQL_DATABASE:-bugsink}
+ - MYSQL_USER=${SERVICE_USER_BUGSINK}
+ - MYSQL_PASSWORD=${SERVICE_PASSWORD_BUGSINK}
volumes:
- - 'my-datavolume:/var/lib/mysql'
+ - my-datavolume:/var/lib/mysql
healthcheck:
test:
- CMD
@@ -29,10 +30,10 @@ services:
restart: unless-stopped
environment:
- SECRET_KEY=$SERVICE_PASSWORD_64_BUGSINK
- - 'CREATE_SUPERUSER=admin:${SERVICE_PASSWORD_BUGSINK}'
+ - CREATE_SUPERUSER=admin:$SERVICE_PASSWORD_BUGSINK
- SERVICE_FQDN_BUGSINK_8000
- - 'BASE_URL=${SERVICE_FQDN_BUGSINK_8000}'
- - 'DATABASE_URL=mysql://${SERVICE_USER_BUGSINK}:$SERVICE_PASSWORD_BUGSINK@mysql:3306/${MYSQL_DATABASE:-bugsink}'
+ - BASE_URL=$SERVICE_FQDN_BUGSINK_8000
+ - DATABASE_URL=mysql://${SERVICE_USER_BUGSINK}:$SERVICE_PASSWORD_BUGSINK@mysql:3306/${MYSQL_DATABASE:-bugsink}
depends_on:
mysql:
condition: service_healthy
@@ -41,6 +42,3 @@ services:
interval: 5s
timeout: 20s
retries: 10
-
-volumes:
- my-datavolume:
diff --git a/templates/service-templates.json b/templates/service-templates.json
index e300aaf39..531bb639a 100644
--- a/templates/service-templates.json
+++ b/templates/service-templates.json
@@ -271,7 +271,7 @@
"bugsink": {
"documentation": "https://www.bugsink.com/docs/?utm_source=coolify.io",
"slogan": "Self-hosted Error Tracking",
- "compose": "c2VydmljZXM6CiAgbXlzcWw6CiAgICBpbWFnZTogJ215c3FsOmxhdGVzdCcKICAgIHJlc3RhcnQ6IHVubGVzcy1zdG9wcGVkCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTVlTUUxfUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUk9PVH0nCiAgICAgIC0gJ01ZU1FMX0RBVEFCQVNFPSR7TVlTUUxfREFUQUJBU0U6LWJ1Z3Npbmt9JwogICAgICAtICdNWVNRTF9VU0VSPSR7U0VSVklDRV9VU0VSX0JVR1NJTkt9JwogICAgICAtICdNWVNRTF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfQlVHU0lOS30nCiAgICB2b2x1bWVzOgogICAgICAtICdteS1kYXRhdm9sdW1lOi92YXIvbGliL215c3FsJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIG15c3FsYWRtaW4KICAgICAgICAtIHBpbmcKICAgICAgICAtICctaCcKICAgICAgICAtIDEyNy4wLjAuMQogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgd2ViOgogICAgaW1hZ2U6IGJ1Z3NpbmsvYnVnc2luawogICAgcmVzdGFydDogdW5sZXNzLXN0b3BwZWQKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFQ1JFVF9LRVk9JFNFUlZJQ0VfUEFTU1dPUkRfNjRfQlVHU0lOSwogICAgICAtICdDUkVBVEVfU1VQRVJVU0VSPWFkbWluOiR7U0VSVklDRV9QQVNTV09SRF9CVUdTSU5LfScKICAgICAgLSBTRVJWSUNFX0ZRRE5fQlVHU0lOS184MDAwCiAgICAgIC0gJ0JBU0VfVVJMPSR7U0VSVklDRV9GUUROX0JVR1NJTktfODAwMH0nCiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1teXNxbDovLyR7U0VSVklDRV9VU0VSX0JVR1NJTkt9OiRTRVJWSUNFX1BBU1NXT1JEX0JVR1NJTktAbXlzcWw6MzMwNi8ke01ZU1FMX0RBVEFCQVNFOi1idWdzaW5rfScKICAgIGRlcGVuZHNfb246CiAgICAgIG15c3FsOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CnZvbHVtZXM6CiAgbXktZGF0YXZvbHVtZTogbnVsbAo=",
+ "compose": "c2VydmljZXM6CiAgbXlzcWw6CiAgICBpbWFnZTogJ215c3FsOmxhdGVzdCcKICAgIHJlc3RhcnQ6IHVubGVzcy1zdG9wcGVkCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTVlTUUxfUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUk9PVH0nCiAgICAgIC0gJ01ZU1FMX0RBVEFCQVNFPSR7TVlTUUxfREFUQUJBU0U6LWJ1Z3Npbmt9JwogICAgICAtICdNWVNRTF9VU0VSPSR7U0VSVklDRV9VU0VSX0JVR1NJTkt9JwogICAgICAtICdNWVNRTF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfQlVHU0lOS30nCiAgICB2b2x1bWVzOgogICAgICAtICdteS1kYXRhdm9sdW1lOi92YXIvbGliL215c3FsJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIG15c3FsYWRtaW4KICAgICAgICAtIHBpbmcKICAgICAgICAtICctaCcKICAgICAgICAtIDEyNy4wLjAuMQogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgd2ViOgogICAgaW1hZ2U6IGJ1Z3NpbmsvYnVnc2luawogICAgcmVzdGFydDogdW5sZXNzLXN0b3BwZWQKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFQ1JFVF9LRVk9JFNFUlZJQ0VfUEFTU1dPUkRfNjRfQlVHU0lOSwogICAgICAtICdDUkVBVEVfU1VQRVJVU0VSPWFkbWluOiRTRVJWSUNFX1BBU1NXT1JEX0JVR1NJTksnCiAgICAgIC0gU0VSVklDRV9GUUROX0JVR1NJTktfODAwMAogICAgICAtIEJBU0VfVVJMPSRTRVJWSUNFX0ZRRE5fQlVHU0lOS184MDAwCiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1teXNxbDovLyR7U0VSVklDRV9VU0VSX0JVR1NJTkt9OiRTRVJWSUNFX1BBU1NXT1JEX0JVR1NJTktAbXlzcWw6MzMwNi8ke01ZU1FMX0RBVEFCQVNFOi1idWdzaW5rfScKICAgIGRlcGVuZHNfb246CiAgICAgIG15c3FsOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3B5dGhvbiAtYyAnJ2ltcG9ydCByZXF1ZXN0czsgcmVxdWVzdHMuZ2V0KCJodHRwOi8vbG9jYWxob3N0OjgwMDAvIikucmFpc2VfZm9yX3N0YXR1cygpJycnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK",
"tags": [
"python",
"error-tracking",
From d15f1b9b94a2082c48dcdf7a4d3c5bab83640bf0 Mon Sep 17 00:00:00 2001
From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Date: Wed, 2 Apr 2025 16:28:14 +0200
Subject: [PATCH 11/16] fix(email): Emails are not sent in multiple cases
- fix(email): transactional emails are all not sent if `Use system wide (transactional) email settings` is disabled and no other email provide is setup on the Notifications page
- fix(email): no emails are sent if SMTP username and SMTP password are empty (which is the case in dev for example)
- fix(email): Wrong test email notification is used, causing the transactional email test notification to fail if no email provider is set up on the Notifications page.
---
app/Livewire/SettingsEmail.php | 2 +-
app/Notifications/Channels/EmailChannel.php | 7 ++++---
app/Notifications/TransactionalEmails/InvitationLink.php | 3 ++-
app/Notifications/TransactionalEmails/ResetPassword.php | 3 ++-
app/Notifications/TransactionalEmails/Test.php | 3 ++-
5 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/app/Livewire/SettingsEmail.php b/app/Livewire/SettingsEmail.php
index 858f3b187..4426c7812 100644
--- a/app/Livewire/SettingsEmail.php
+++ b/app/Livewire/SettingsEmail.php
@@ -4,7 +4,7 @@ namespace App\Livewire;
use App\Models\InstanceSettings;
use App\Models\Team;
-use App\Notifications\Test;
+use App\Notifications\TransactionalEmails\Test;
use Illuminate\Support\Facades\RateLimiter;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
diff --git a/app/Notifications/Channels/EmailChannel.php b/app/Notifications/Channels/EmailChannel.php
index a452ef8dd..8a9a95107 100644
--- a/app/Notifications/Channels/EmailChannel.php
+++ b/app/Notifications/Channels/EmailChannel.php
@@ -12,8 +12,9 @@ class EmailChannel
public function send(SendsEmail $notifiable, Notification $notification): void
{
$useInstanceEmailSettings = $notifiable->emailNotificationSettings->use_instance_email_settings;
+ $isTransactionalEmail = data_get($notification, 'isTransactionalEmail', false);
$customEmails = data_get($notification, 'emails', null);
- if ($useInstanceEmailSettings) {
+ if ($useInstanceEmailSettings || $isTransactionalEmail) {
$settings = instanceSettings();
} else {
$settings = $notifiable->emailNotificationSettings;
@@ -49,8 +50,8 @@ class EmailChannel
$settings->smtp_port,
$encryption
);
- $transport->setUsername($settings->smtp_username);
- $transport->setPassword($settings->smtp_password);
+ $transport->setUsername($settings->smtp_username ?? '');
+ $transport->setPassword($settings->smtp_password ?? '');
$mailer = new \Symfony\Component\Mailer\Mailer($transport);
diff --git a/app/Notifications/TransactionalEmails/InvitationLink.php b/app/Notifications/TransactionalEmails/InvitationLink.php
index 30ace99dc..fce3eb948 100644
--- a/app/Notifications/TransactionalEmails/InvitationLink.php
+++ b/app/Notifications/TransactionalEmails/InvitationLink.php
@@ -16,9 +16,10 @@ class InvitationLink extends CustomEmailNotification
return [TransactionalEmailChannel::class];
}
- public function __construct(public User $user)
+ public function __construct(public User $user, public bool $isTransactionalEmail)
{
$this->onQueue('high');
+ $this->isTransactionalEmail = true;
}
public function toMail(): MailMessage
diff --git a/app/Notifications/TransactionalEmails/ResetPassword.php b/app/Notifications/TransactionalEmails/ResetPassword.php
index 4593ddb0d..031a2d95a 100644
--- a/app/Notifications/TransactionalEmails/ResetPassword.php
+++ b/app/Notifications/TransactionalEmails/ResetPassword.php
@@ -17,10 +17,11 @@ class ResetPassword extends Notification
public InstanceSettings $settings;
- public function __construct($token)
+ public function __construct($token, public bool $isTransactionalEmail)
{
$this->settings = instanceSettings();
$this->token = $token;
+ $this->isTransactionalEmail = true;
}
public static function createUrlUsing($callback)
diff --git a/app/Notifications/TransactionalEmails/Test.php b/app/Notifications/TransactionalEmails/Test.php
index eeb32a254..951c007d9 100644
--- a/app/Notifications/TransactionalEmails/Test.php
+++ b/app/Notifications/TransactionalEmails/Test.php
@@ -8,9 +8,10 @@ use Illuminate\Notifications\Messages\MailMessage;
class Test extends CustomEmailNotification
{
- public function __construct(public string $emails)
+ public function __construct(public string $emails, public string $isTransactionalEmail)
{
$this->onQueue('high');
+ $this->isTransactionalEmail = true;
}
public function via(): array
From 9f58128623bc1a969fedeb3f1c3b77dcb434349c Mon Sep 17 00:00:00 2001
From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Date: Wed, 2 Apr 2025 17:03:13 +0200
Subject: [PATCH 12/16] fix(deployments): use graceful shutdown instead of `rm`
---
app/Jobs/ApplicationDeploymentJob.php | 18 ++++--------------
1 file changed, 4 insertions(+), 14 deletions(-)
diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php
index b72d2ade3..5dbdbf215 100644
--- a/app/Jobs/ApplicationDeploymentJob.php
+++ b/app/Jobs/ApplicationDeploymentJob.php
@@ -329,13 +329,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
} else {
$this->write_deployment_configurations();
}
- $this->execute_remote_command(
- [
- "docker rm -f {$this->deployment_uuid} >/dev/null 2>&1",
- 'hidden' => true,
- 'ignore_errors' => true,
- ]
- );
+ $this->application_deployment_queue->addLogEntry("Starting graceful shutdown container: {$this->deployment_uuid}");
+ $this->graceful_shutdown_container($this->deployment_uuid);
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
}
@@ -1366,13 +1361,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
}
$this->application_deployment_queue->addLogEntry("Preparing container with helper image: $helperImage.");
- $this->execute_remote_command(
- [
- 'command' => "docker rm -f {$this->deployment_uuid}",
- 'ignore_errors' => true,
- 'hidden' => true,
- ]
- );
+ $this->application_deployment_queue->addLogEntry("Starting graceful shutdown container: {$this->deployment_uuid}");
+ $this->graceful_shutdown_container($this->deployment_uuid);
$this->execute_remote_command(
[
$runCommand,
From 2248028a84cbd15a745a079fbfd90000fcb8e6b8 Mon Sep 17 00:00:00 2001
From: Will Russell
Date: Wed, 2 Apr 2025 18:18:13 +0100
Subject: [PATCH 13/16] fix(docs): contribute service url (#5517)
---
CONTRIBUTING.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7b642fdf5..1ba4d1876 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -238,7 +238,7 @@ After completing these steps, you'll have a fresh development setup.
### Contributing a New Service
To add a new service to Coolify, please refer to our documentation:
-[Adding a New Service](https://coolify.io/docs/knowledge-base/contribute/service)
+[Adding a New Service](https://coolify.io/docs/get-started/contribute/service)
### Contributing to Documentation
From 331693546bfb7197ac773062d12c88e78e6f9474 Mon Sep 17 00:00:00 2001
From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Date: Wed, 2 Apr 2025 19:38:31 +0200
Subject: [PATCH 14/16] fix(proxy): proxy restart does not work on domain
- When you restart the proxy on an instance domain, the proxy stops and is removed, but never restarted. So you loose access over the domain and have to go in over IP and Port.
This is because we are doing the restart directly in the UI instead of in the background via a job, and the proxy is serving the UI domain.
---
app/Actions/Proxy/StopProxy.php | 56 ++++++++++++++++++++++++++++
app/Jobs/RestartProxyJob.php | 46 +++++++++++++++++++++++
app/Livewire/Server/Proxy/Deploy.php | 43 +++------------------
3 files changed, 107 insertions(+), 38 deletions(-)
create mode 100644 app/Actions/Proxy/StopProxy.php
create mode 100644 app/Jobs/RestartProxyJob.php
diff --git a/app/Actions/Proxy/StopProxy.php b/app/Actions/Proxy/StopProxy.php
new file mode 100644
index 000000000..a5dcc6cf4
--- /dev/null
+++ b/app/Actions/Proxy/StopProxy.php
@@ -0,0 +1,56 @@
+isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy';
+ $timeout = 30;
+
+ $process = $this->stopContainer($containerName, $timeout);
+
+ $startTime = Carbon::now()->getTimestamp();
+ while ($process->running()) {
+ if (Carbon::now()->getTimestamp() - $startTime >= $timeout) {
+ $this->forceStopContainer($containerName, $server);
+ break;
+ }
+ usleep(100000);
+ }
+
+ $this->removeContainer($containerName, $server);
+ } catch (\Throwable $e) {
+ return handleError($e);
+ } finally {
+ $server->proxy->force_stop = $forceStop;
+ $server->proxy->status = 'exited';
+ $server->save();
+ }
+ }
+
+ private function stopContainer(string $containerName, int $timeout): InvokedProcess
+ {
+ return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
+ }
+
+ private function forceStopContainer(string $containerName, Server $server)
+ {
+ instant_remote_process(["docker kill $containerName"], $server, throwError: false);
+ }
+
+ private function removeContainer(string $containerName, Server $server)
+ {
+ instant_remote_process(["docker rm -f $containerName"], $server, throwError: false);
+ }
+}
diff --git a/app/Jobs/RestartProxyJob.php b/app/Jobs/RestartProxyJob.php
new file mode 100644
index 000000000..7fc716f70
--- /dev/null
+++ b/app/Jobs/RestartProxyJob.php
@@ -0,0 +1,46 @@
+server->uuid))->dontRelease()];
+ }
+
+ public function __construct(public Server $server) {}
+
+ public function handle()
+ {
+ try {
+ StopProxy::run($this->server);
+
+ $this->server->proxy->force_stop = false;
+ $this->server->save();
+ StartProxy::run($this->server, force: true);
+
+ CheckProxy::run($this->server, true);
+ } catch (\Throwable $e) {
+ return handleError($e);
+ }
+ }
+}
diff --git a/app/Livewire/Server/Proxy/Deploy.php b/app/Livewire/Server/Proxy/Deploy.php
index 4a7e4124e..48eede4e5 100644
--- a/app/Livewire/Server/Proxy/Deploy.php
+++ b/app/Livewire/Server/Proxy/Deploy.php
@@ -4,11 +4,10 @@ namespace App\Livewire\Server\Proxy;
use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy;
+use App\Actions\Proxy\StopProxy;
use App\Events\ProxyStatusChanged;
+use App\Jobs\RestartProxyJob;
use App\Models\Server;
-use Carbon\Carbon;
-use Illuminate\Process\InvokedProcess;
-use Illuminate\Support\Facades\Process;
use Livewire\Component;
class Deploy extends Component
@@ -65,6 +64,7 @@ class Deploy extends Component
public function restart()
{
try {
+ RestartProxyJob::dispatch($this->server);
$this->dispatch('checkProxy');
} catch (\Throwable $e) {
return handleError($e, $this);
@@ -97,43 +97,10 @@ class Deploy extends Component
public function stop(bool $forceStop = true)
{
try {
- $containerName = $this->server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy';
- $timeout = 30;
-
- $process = $this->stopContainer($containerName, $timeout);
-
- $startTime = Carbon::now()->getTimestamp();
- while ($process->running()) {
- if (Carbon::now()->getTimestamp() - $startTime >= $timeout) {
- $this->forceStopContainer($containerName);
- break;
- }
- usleep(100000);
- }
-
- $this->removeContainer($containerName);
+ StopProxy::run($this->server, $forceStop);
+ $this->dispatch('proxyStatusUpdated');
} catch (\Throwable $e) {
return handleError($e, $this);
- } finally {
- $this->server->proxy->force_stop = $forceStop;
- $this->server->proxy->status = 'exited';
- $this->server->save();
- $this->dispatch('proxyStatusUpdated');
}
}
-
- private function stopContainer(string $containerName, int $timeout): InvokedProcess
- {
- return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
- }
-
- private function forceStopContainer(string $containerName)
- {
- instant_remote_process(["docker kill $containerName"], $this->server, throwError: false);
- }
-
- private function removeContainer(string $containerName)
- {
- instant_remote_process(["docker rm -f $containerName"], $this->server, throwError: false);
- }
}
From 7e0ff9cc3cf60bbb1228446692ae09df2cfe0e21 Mon Sep 17 00:00:00 2001
From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Date: Wed, 2 Apr 2025 19:38:53 +0200
Subject: [PATCH 15/16] fix(ui): only show copy button on https
---
resources/views/components/forms/copy-button.blade.php | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/resources/views/components/forms/copy-button.blade.php b/resources/views/components/forms/copy-button.blade.php
index 39be2bc6a..12fadc595 100644
--- a/resources/views/components/forms/copy-button.blade.php
+++ b/resources/views/components/forms/copy-button.blade.php
@@ -1,8 +1,9 @@
@props(['text'])
-