From ad8f4423180332d1732da0076ed226ab5ca91c8c Mon Sep 17 00:00:00 2001 From: Yanluis Fermin <32645451+Jacxk@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:59:35 -0400 Subject: [PATCH 001/367] refactor(services): update validation rules to be optional --- .../Controllers/Api/ServicesController.php | 71 ++++++++++--------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index 542be83de..f385b4d0e 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -7,6 +7,7 @@ use App\Actions\Service\StartService; use App\Actions\Service\StopService; use App\Http\Controllers\Controller; use App\Jobs\DeleteResourceJob; +use App\Models\Environment; use App\Models\EnvironmentVariable; use App\Models\Project; use App\Models\Server; @@ -550,7 +551,6 @@ class ServicesController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - 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.'], @@ -627,16 +627,16 @@ class ServicesController extends Controller { $allowedFields = ['name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network']; $validator = customApiValidator($request->all(), [ - 'project_uuid' => 'string|required', + 'project_uuid' => 'string|nullable', 'environment_name' => 'string|nullable', 'environment_uuid' => 'string|nullable', - 'server_uuid' => 'string|required', + 'server_uuid' => 'string|nullable', 'destination_uuid' => 'string', 'name' => 'string|max:255', 'description' => 'string|nullable', 'instant_deploy' => 'boolean', 'connect_to_docker_network' => 'boolean', - 'docker_compose_raw' => 'string|required', + 'docker_compose_raw' => 'string|nullable', ]); $extraFields = array_diff(array_keys($request->all()), $allowedFields); @@ -654,14 +654,19 @@ class ServicesController extends Controller ], 422); } - $environmentUuid = $request->environment_uuid; - $environmentName = $request->environment_name; - if (blank($environmentUuid) && blank($environmentName)) { - return response()->json(['message' => 'You need to provide at least one of environment_name or environment_uuid.'], 422); + $environmentUuid = null; + $environmentName = null; + + if ($request->environment_uuid) { + $environmentUuid = $request->environment_uuid; + } elseif ($request->environment_name) { + $environmentName = $request->environment_name; + } else { + $environmentUuid = $service->environment->uuid; } - $serverUuid = $request->server_uuid; - $instantDeploy = $request->instant_deploy ?? false; - $project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first(); + $serverUuid = $request->server_uuid ?? $service->server->uuid; + $projectUuid = $request->project_uuid ?? $service->environment->project->uuid; + $project = Project::whereTeamId($teamId)->whereUuid($projectUuid)->first(); if (! $project) { return response()->json(['message' => 'Project not found.'], 404); } @@ -684,39 +689,41 @@ class ServicesController extends Controller return response()->json(['message' => 'Server has multiple destinations and you do not set destination_uuid.'], 400); } $destination = $destinations->first(); - if (! isBase64Encoded($request->docker_compose_raw)) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => [ - 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.', - ], - ], 422); + if ($request->has('docker_compose_raw')) { + if (! isBase64Encoded($request->docker_compose_raw)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.', + ], + ], 422); + } + $dockerComposeRaw = base64_decode($request->docker_compose_raw); + if (mb_detect_encoding($dockerComposeRaw, 'ASCII', true) === false) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.', + ], + ], 422); + } + $dockerCompose = base64_decode($request->docker_compose_raw); + $dockerComposeRaw = Yaml::dump(Yaml::parse($dockerCompose), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); + $service->docker_compose_raw = $dockerComposeRaw; } - $dockerComposeRaw = base64_decode($request->docker_compose_raw); - if (mb_detect_encoding($dockerComposeRaw, 'ASCII', true) === false) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => [ - 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.', - ], - ], 422); - } - $dockerCompose = base64_decode($request->docker_compose_raw); - $dockerComposeRaw = Yaml::dump(Yaml::parse($dockerCompose), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); $connectToDockerNetwork = $request->connect_to_docker_network ?? false; $service->name = $request->name ?? null; $service->description = $request->description ?? null; - $service->docker_compose_raw = $dockerComposeRaw; $service->environment_id = $environment->id; $service->server_id = $server->id; $service->destination_id = $destination->id; $service->destination_type = $destination->getMorphClass(); $service->connect_to_docker_network = $connectToDockerNetwork; $service->save(); - + $service->parse(); - if ($instantDeploy) { + if ($request->instant_deploy) { StartService::dispatch($service); } From 79fc9927d7e8af964467e2d1fa240d4bed1fe6c1 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Sun, 3 Aug 2025 22:22:16 +0200 Subject: [PATCH 002/367] fix(git): tracking issue due to case sensitivity --- app/View/Components/services/advanced.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/View/Components/services/advanced.php b/app/View/Components/services/advanced.php index 8104eaad4..99729a262 100644 --- a/app/View/Components/services/advanced.php +++ b/app/View/Components/services/advanced.php @@ -1,13 +1,13 @@ Date: Sun, 3 Aug 2025 22:26:45 +0200 Subject: [PATCH 003/367] fix(git): tracking issue due to case sensitivity --- .../Components/{services/advanced.php => Services/Advanced.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/View/Components/{services/advanced.php => Services/Advanced.php} (100%) diff --git a/app/View/Components/services/advanced.php b/app/View/Components/Services/Advanced.php similarity index 100% rename from app/View/Components/services/advanced.php rename to app/View/Components/Services/Advanced.php From 723f14e6f6a84bab744a792c3e628c5e5a3fa308 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Sun, 3 Aug 2025 22:36:38 +0200 Subject: [PATCH 004/367] fix(git): tracking issue due to case sensitivity --- .../{services/advanced.php => Services/Advanced.php} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename app/View/Components/{services/advanced.php => Services/Advanced.php} (85%) diff --git a/app/View/Components/services/advanced.php b/app/View/Components/Services/Advanced.php similarity index 85% rename from app/View/Components/services/advanced.php rename to app/View/Components/Services/Advanced.php index 8104eaad4..99729a262 100644 --- a/app/View/Components/services/advanced.php +++ b/app/View/Components/Services/Advanced.php @@ -1,13 +1,13 @@ Date: Mon, 4 Aug 2025 13:11:39 +0200 Subject: [PATCH 005/367] fix(ui): Delete button width on small screens (#6308) --- .../livewire/project/shared/environment-variable/show.blade.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/views/livewire/project/shared/environment-variable/show.blade.php b/resources/views/livewire/project/shared/environment-variable/show.blade.php index 82ee789b6..7335124d1 100644 --- a/resources/views/livewire/project/shared/environment-variable/show.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/show.blade.php @@ -88,6 +88,7 @@ @@ -101,6 +102,7 @@ From efdaf46d08ceb81ccc2658640d95973f90b70c21 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 13:27:31 +0200 Subject: [PATCH 006/367] fix(service): matrix entrypoint - make sure registration is enabled not just non empty - make sure RECAPTCHA_PUBLIC_KEY is set when adding the recaptcha block --- templates/compose/matrix.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/compose/matrix.yaml b/templates/compose/matrix.yaml index 99232967e..efaf505c2 100644 --- a/templates/compose/matrix.yaml +++ b/templates/compose/matrix.yaml @@ -89,14 +89,14 @@ services: # # ######################## - test -n "${ENABLE_REGISTRATION}" && ! grep "#registration" /data/homeserver.yaml &>/dev/null \ + [ "${ENABLE_REGISTRATION}" = "true" ] && ! grep "#registration" /data/homeserver.yaml &>/dev/null \ && echo >> /data/homeserver.yaml \ && cat <> /data/homeserver.yaml #registration enable_registration: true # Allows users to register on your server. EOF - ! grep ${RECAPTCHA_PUBLIC_KEY} /data/homeserver.yaml &>/dev/null \ + [ -n "${RECAPTCHA_PUBLIC_KEY}" ] && ! grep "${RECAPTCHA_PUBLIC_KEY}" /data/homeserver.yaml &>/dev/null \ && echo >> /data/homeserver.yaml \ && cat <> /data/homeserver.yaml # reCAPTCHA settings From 554b566735509fb2b834dd0dbad5987aad31123a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20Sobczy=C5=84ski?= <32263891+h4570@users.noreply.github.com> Date: Mon, 4 Aug 2025 14:09:26 +0200 Subject: [PATCH 007/367] fix(ui): add flex-wrap to prevent overflow on small screens (#6307) --- .../project/shared/environment-variable/show.blade.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/views/livewire/project/shared/environment-variable/show.blade.php b/resources/views/livewire/project/shared/environment-variable/show.blade.php index 7335124d1..5a197dc7a 100644 --- a/resources/views/livewire/project/shared/environment-variable/show.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/show.blade.php @@ -21,7 +21,7 @@ @else @if ($isDisabled) -
+
@if ($is_shared) @@ -29,7 +29,7 @@ @endif
@else -
+
@if ($is_multiline) @@ -42,7 +42,7 @@ @endif
@endif -
+
@if (!$is_redis_credential) @if ($type === 'service') Date: Mon, 4 Aug 2025 14:13:33 +0200 Subject: [PATCH 008/367] feat(lang): add Polish language & improve forgot_password translation (#6306) --- lang/ar.json | 5 ++- lang/az.json | 5 ++- lang/cs.json | 5 ++- lang/de.json | 5 ++- lang/en.json | 5 ++- lang/es.json | 5 ++- lang/fa.json | 5 ++- lang/fr.json | 5 ++- lang/id.json | 5 ++- lang/it.json | 5 ++- lang/ja.json | 5 ++- lang/no.json | 5 ++- lang/pl.json | 44 +++++++++++++++++++ lang/pt-br.json | 5 ++- lang/pt.json | 5 ++- lang/ro.json | 5 ++- lang/tr.json | 5 ++- lang/vi.json | 5 ++- lang/zh-cn.json | 5 ++- lang/zh-tw.json | 5 ++- .../views/auth/forgot-password.blade.php | 2 +- resources/views/auth/login.blade.php | 4 +- 22 files changed, 104 insertions(+), 41 deletions(-) create mode 100644 lang/pl.json diff --git a/lang/ar.json b/lang/ar.json index 263924c24..c966cc686 100644 --- a/lang/ar.json +++ b/lang/ar.json @@ -11,7 +11,8 @@ "auth.login.infomaniak": "تسجيل الدخول باستخدام Infomaniak", "auth.already_registered": "هل سبق لك التسجيل؟", "auth.confirm_password": "تأكيد كلمة المرور", - "auth.forgot_password": "نسيت كلمة المرور", + "auth.forgot_password_link": "هل نسيت كلمة المرور؟", + "auth.forgot_password_heading": "استعادة كلمة المرور", "auth.forgot_password_send_email": "إرسال بريد إلكتروني لإعادة تعيين كلمة المرور", "auth.register_now": "تسجيل", "auth.logout": "تسجيل الخروج", @@ -39,4 +40,4 @@ "resource.delete_configurations": "حذف جميع ملفات التعريف من الخادم بشكل دائم.", "database.delete_backups_locally": "حذف كافة النسخ الاحتياطية نهائيًا من التخزين المحلي.", "warning.sslipdomain": "تم حفظ ملفات التعريف الخاصة بك، ولكن استخدام نطاق sslip مع https غير مستحسن، لأن خوادم Let's Encrypt مع هذا النطاق العام محدودة المعدل (ستفشل عملية التحقق من شهادة SSL).

استخدم نطاقك الخاص بدلاً من ذلك." -} +} \ No newline at end of file diff --git a/lang/az.json b/lang/az.json index 92f56ddbc..85cee7589 100644 --- a/lang/az.json +++ b/lang/az.json @@ -11,7 +11,8 @@ "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_link": "Şifrəmi unutdum?", + "auth.forgot_password_heading": "Şifrəni bərpa et", "auth.forgot_password_send_email": "Şifrəni sıfırlamaq üçün e-poçt göndər", "auth.register_now": "Qeydiyyat", "auth.logout": "Çıxış", @@ -39,4 +40,4 @@ "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." -} +} \ No newline at end of file diff --git a/lang/cs.json b/lang/cs.json index 00455aa81..9e5d2c44e 100644 --- a/lang/cs.json +++ b/lang/cs.json @@ -10,7 +10,8 @@ "auth.login.infomaniak": "Přihlásit se pomocí Infomaniak", "auth.already_registered": "Již jste registrováni?", "auth.confirm_password": "Potvrďte heslo", - "auth.forgot_password": "Zapomněli jste heslo", + "auth.forgot_password_link": "Zapomněli jste heslo?", + "auth.forgot_password_heading": "Obnovení hesla", "auth.forgot_password_send_email": "Poslat e-mail pro resetování hesla", "auth.register_now": "Registrovat se", "auth.logout": "Odhlásit se", @@ -30,4 +31,4 @@ "input.recovery_code": "Obnovovací kód", "button.save": "Uložit", "repository.url": "Příklady
Pro veřejné repozitáře, použijte https://....
Pro soukromé repozitáře, použijte git@....

https://github.com/coollabsio/coolify-examples main branch bude zvolena
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify branch bude vybrána.
https://gitea.com/sedlav/expressjs.git main branch vybrána.
https://gitlab.com/andrasbacsai/nodejs-example.git main branch bude vybrána." -} +} \ No newline at end of file diff --git a/lang/de.json b/lang/de.json index f56b21710..fd587de22 100644 --- a/lang/de.json +++ b/lang/de.json @@ -11,7 +11,8 @@ "auth.login.zitadel": "Mit Zitadel anmelden", "auth.already_registered": "Bereits registriert?", "auth.confirm_password": "Passwort bestätigen", - "auth.forgot_password": "Passwort vergessen", + "auth.forgot_password_link": "Passwort vergessen?", + "auth.forgot_password_heading": "Passwort-Wiederherstellung", "auth.forgot_password_send_email": "Passwort zurücksetzen E-Mail senden", "auth.register_now": "Registrieren", "auth.logout": "Abmelden", @@ -31,4 +32,4 @@ "input.recovery_code": "Wiederherstellungscode", "button.save": "Speichern", "repository.url": "Beispiele
Für öffentliche Repositories benutze https://....
Für private Repositories benutze git@....

https://github.com/coollabsio/coolify-examples main Branch wird ausgewählt
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify Branch wird ausgewählt.
https://gitea.com/sedlav/expressjs.git main Branch wird ausgewählt.
https://gitlab.com/andrasbacsai/nodejs-example.git main Branch wird ausgewählt." -} +} \ No newline at end of file diff --git a/lang/en.json b/lang/en.json index 4a398a9f9..af7f2145d 100644 --- a/lang/en.json +++ b/lang/en.json @@ -12,7 +12,8 @@ "auth.login.zitadel": "Login with Zitadel", "auth.already_registered": "Already registered?", "auth.confirm_password": "Confirm password", - "auth.forgot_password": "Forgot password", + "auth.forgot_password_link": "Forgot password?", + "auth.forgot_password_heading": "Password recovery", "auth.forgot_password_send_email": "Send password reset email", "auth.register_now": "Register", "auth.logout": "Logout", @@ -40,4 +41,4 @@ "resource.delete_configurations": "Permanently delete all configuration files from the server.", "database.delete_backups_locally": "All backups will be permanently deleted from local storage.", "warning.sslipdomain": "Your configuration is saved, but sslip domain with https is NOT recommended, because Let's Encrypt servers with this public domain are rate limited (SSL certificate validation will fail).

Use your own domain instead." -} +} \ No newline at end of file diff --git a/lang/es.json b/lang/es.json index 73363a9bf..f56387f05 100644 --- a/lang/es.json +++ b/lang/es.json @@ -10,7 +10,8 @@ "auth.login.infomaniak": "Acceder con Infomaniak", "auth.already_registered": "¿Ya estás registrado?", "auth.confirm_password": "Confirmar contraseña", - "auth.forgot_password": "¿Olvidaste tu contraseña?", + "auth.forgot_password_link": "¿Olvidaste tu contraseña?", + "auth.forgot_password_heading": "Recuperación de contraseña", "auth.forgot_password_send_email": "Enviar correo de recuperación de contraseña", "auth.register_now": "Registrar", "auth.logout": "Cerrar sesión", @@ -30,4 +31,4 @@ "input.recovery_code": "Código de recuperación", "button.save": "Guardar", "repository.url": "Examples
Para repositorios públicos, usar https://....
Para repositorios privados, usar git@....

https://github.com/coollabsio/coolify-examples main la rama 'main' será seleccionada.
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify la rama 'nodejs-fastify' será seleccionada.
https://gitea.com/sedlav/expressjs.git main la rama 'main' será seleccionada.
https://gitlab.com/andrasbacsai/nodejs-example.git main la rama 'main' será seleccionada." -} +} \ No newline at end of file diff --git a/lang/fa.json b/lang/fa.json index d68049e77..ae22ee946 100644 --- a/lang/fa.json +++ b/lang/fa.json @@ -10,7 +10,8 @@ "auth.login.infomaniak": "ورود با Infomaniak", "auth.already_registered": "قبلاً ثبت نام کرده‌اید؟", "auth.confirm_password": "تایید رمز عبور", - "auth.forgot_password": "فراموشی رمز عبور", + "auth.forgot_password_link": "رمز عبور را فراموش کرده‌اید؟", + "auth.forgot_password_heading": "بازیابی رمز عبور", "auth.forgot_password_send_email": "ارسال ایمیل بازیابی رمز عبور", "auth.register_now": "ثبت نام", "auth.logout": "خروج", @@ -30,4 +31,4 @@ "input.recovery_code": "کد بازیابی", "button.save": "ذخیره", "repository.url": "مثال‌ها
برای مخازن عمومی، از https://... استفاده کنید.
برای مخازن خصوصی، از git@... استفاده کنید.

شاخه main انتخاب خواهد شد.
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify شاخه nodejs-fastify انتخاب خواهد شد.
https://gitea.com/sedlav/expressjs.git شاخه main انتخاب خواهد شد.
https://gitlab.com/andrasbacsai/nodejs-example.git شاخه main انتخاب خواهد شد." -} +} \ No newline at end of file diff --git a/lang/fr.json b/lang/fr.json index 2516d0f69..d98a1ebc8 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -11,7 +11,8 @@ "auth.login.infomaniak": "Connexion avec Infomaniak", "auth.already_registered": "Déjà enregistré ?", "auth.confirm_password": "Confirmer le mot de passe", - "auth.forgot_password": "Mot de passe oublié", + "auth.forgot_password_link": "Mot de passe oublié ?", + "auth.forgot_password_heading": "Récupération du mot de passe", "auth.forgot_password_send_email": "Envoyer l'email de réinitialisation de mot de passe", "auth.register_now": "S'enregistrer", "auth.logout": "Déconnexion", @@ -39,4 +40,4 @@ "resource.delete_configurations": "Supprimer définitivement tous les fichiers de configuration du serveur.", "database.delete_backups_locally": "Toutes les sauvegardes seront définitivement supprimées du stockage local.", "warning.sslipdomain": "Votre configuration est enregistrée, mais l'utilisation du domaine sslip avec https N'EST PAS recommandée, car les serveurs Let's Encrypt avec ce domaine public sont limités en taux (la validation du certificat SSL échouera).

Utilisez plutôt votre propre domaine." -} +} \ No newline at end of file diff --git a/lang/id.json b/lang/id.json index b0e38197a..d85176cda 100644 --- a/lang/id.json +++ b/lang/id.json @@ -11,7 +11,8 @@ "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_link": "Lupa kata sandi?", + "auth.forgot_password_heading": "Pemulihan Kata Sandi", "auth.forgot_password_send_email": "Kirim email reset kata sandi", "auth.register_now": "Daftar", "auth.logout": "Keluar", @@ -39,4 +40,4 @@ "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." -} +} \ No newline at end of file diff --git a/lang/it.json b/lang/it.json index c0edc314b..e4c1a9c05 100644 --- a/lang/it.json +++ b/lang/it.json @@ -11,7 +11,8 @@ "auth.login.infomaniak": "Accedi con Infomaniak", "auth.already_registered": "Già registrato?", "auth.confirm_password": "Conferma password", - "auth.forgot_password": "Password dimenticata", + "auth.forgot_password_link": "Hai dimenticato la password?", + "auth.forgot_password_heading": "Recupero password", "auth.forgot_password_send_email": "Invia email per reimpostare la password", "auth.register_now": "Registrati", "auth.logout": "Esci", @@ -39,4 +40,4 @@ "resource.delete_configurations": "Elimina definitivamente tutti i file di configurazione dal server.", "database.delete_backups_locally": "Tutti i backup verranno eliminati definitivamente dall'archiviazione locale.", "warning.sslipdomain": "La tua configurazione è stata salvata, ma il dominio sslip con https NON è raccomandato, poiché i server di Let's Encrypt con questo dominio pubblico hanno limitazioni di frequenza (la convalida del certificato SSL fallirà).

Utilizza invece il tuo dominio personale." -} +} \ No newline at end of file diff --git a/lang/ja.json b/lang/ja.json index 87d87d99b..05987e7ce 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -10,7 +10,8 @@ "auth.login.infomaniak": "Infomaniakでログイン", "auth.already_registered": "すでに登録済みですか?", "auth.confirm_password": "パスワードを確認", - "auth.forgot_password": "パスワードを忘れた", + "auth.forgot_password_link": "パスワードをお忘れですか?", + "auth.forgot_password_heading": "パスワードの再設定", "auth.forgot_password_send_email": "パスワードリセットメールを送信", "auth.register_now": "今すぐ登録", "auth.logout": "ログアウト", @@ -30,4 +31,4 @@ "input.recovery_code": "リカバリーコード", "button.save": "保存", "repository.url": "
公開リポジトリの場合はhttps://...を使用してください。
プライベートリポジトリの場合はgit@...を使用してください。

https://github.com/coollabsio/coolify-examples mainブランチが選択されます
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastifyブランチが選択されます。
https://gitea.com/sedlav/expressjs.git mainブランチが選択されます。
https://gitlab.com/andrasbacsai/nodejs-example.git mainブランチが選択されます。" -} +} \ No newline at end of file diff --git a/lang/no.json b/lang/no.json index a84f6aa6c..967bdf606 100644 --- a/lang/no.json +++ b/lang/no.json @@ -11,7 +11,8 @@ "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_link": "Glemt passord?", + "auth.forgot_password_heading": "Gjenoppretting av passord", "auth.forgot_password_send_email": "Send e-post for tilbakestilling av passord", "auth.register_now": "Registrer deg", "auth.logout": "Logg ut", @@ -39,4 +40,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 diff --git a/lang/pl.json b/lang/pl.json new file mode 100644 index 000000000..bcd8e2393 --- /dev/null +++ b/lang/pl.json @@ -0,0 +1,44 @@ +{ + "auth.login": "Zaloguj", + "auth.login.authentik": "Zaloguj się przez Authentik", + "auth.login.azure": "Zaloguj się przez Microsoft", + "auth.login.bitbucket": "Zaloguj się przez Bitbucket", + "auth.login.clerk": "Zaloguj się przez Clerk", + "auth.login.discord": "Zaloguj się przez Discord", + "auth.login.github": "Zaloguj się przez GitHub", + "auth.login.gitlab": "Zaloguj się przez Gitlab", + "auth.login.google": "Zaloguj się przez Google", + "auth.login.infomaniak": "Zaloguj się przez Infomaniak", + "auth.login.zitadel": "Zaloguj się przez Zitadel", + "auth.already_registered": "Już zarejestrowany?", + "auth.confirm_password": "Potwierdź hasło", + "auth.forgot_password_link": "Zapomniałeś hasło?", + "auth.forgot_password_heading": "Odzyskiwanie hasła", + "auth.forgot_password_send_email": "Wyślij email resetujący hasło", + "auth.register_now": "Zarejestruj", + "auth.logout": "Wyloguj", + "auth.register": "Zarejestruj", + "auth.registration_disabled": "Rejestracja jest wyłączona. Skontaktuj się z administratorem.", + "auth.reset_password": "Zresetuj hasło", + "auth.failed": "Podane dane nie zgadzają się z naszymi rekordami.", + "auth.failed.callback": "Nie udało się przeprocesować callbacku od dostawcy logowania.", + "auth.failed.password": "Podane hasło jest nieprawidłowe.", + "auth.failed.email": "Nie znaleziono użytkownika z takim adresem email.", + "auth.throttle": "Zbyt wiele prób logowania. Spróbuj ponownie za :seconds sekund.", + "input.name": "Nazwa", + "input.email": "Email", + "input.password": "Hasło", + "input.password.again": "Hasło ponownie", + "input.code": "Jednorazowy kod", + "input.recovery_code": "Kod odzyskiwania", + "button.save": "Zapisz", + "repository.url": "Przykłady
Dla publicznych repozytoriów użyj https://....
Dla prywatnych repozytoriów, użyj git@....

https://github.com/coollabsio/coolify-examples - zostanie wybrany branch main
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify - zostanie wybrany branch nodejs-fastify
https://gitea.com/sedlav/expressjs.git - zostanie wybrany branch main
https://gitlab.com/andrasbacsai/nodejs-example.git - zostanie wybrany branch main", + "service.stop": "Ten serwis zostanie zatrzymany.", + "resource.docker_cleanup": "Uruchom Docker Cleanup (usunie nieużywane obrazy i cache buildera).", + "resource.non_persistent": "Wszystkie nietrwałe dane zostaną usunięte.", + "resource.delete_volumes": "Trwale usuń wszystkie wolumeny powiązane z tym zasobem.", + "resource.delete_connected_networks": "Trwale usuń wszystkie niepredefiniowane sieci powiązane z tym zasobem.", + "resource.delete_configurations": "Trwale usuń wszystkie pliki konfiguracyjne z serwera.", + "database.delete_backups_locally": "Wszystkie backupy zostaną trwale usunięte z lokalnej pamięci.", + "warning.sslipdomain": "Twoja konfiguracja została zapisana, lecz domena sslip z https jest NIEZALECANA, ponieważ serwery Let's Encrypt z tą publiczną domeną są pod rate limitem (walidacja certyfikatu SSL certificate się nie powiedzie).

Lepiej użyj własnej domeny." +} \ No newline at end of file diff --git a/lang/pt-br.json b/lang/pt-br.json index c3a102995..f3ebb6c69 100644 --- a/lang/pt-br.json +++ b/lang/pt-br.json @@ -11,7 +11,8 @@ "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_link": "Esqueceu a senha?", + "auth.forgot_password_heading": "Recuperação de senha", "auth.forgot_password_send_email": "Enviar e-mail para redefinir senha", "auth.register_now": "Cadastre-se", "auth.logout": "Sair", @@ -39,4 +40,4 @@ "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." -} +} \ No newline at end of file diff --git a/lang/pt.json b/lang/pt.json index 80ff8c146..08ad19df3 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -10,7 +10,8 @@ "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_link": "Esqueceu a senha?", + "auth.forgot_password_heading": "Recuperação de senha", "auth.forgot_password_send_email": "Enviar e-mail de redefinição de senha", "auth.register_now": "Cadastrar-se", "auth.logout": "Sair", @@ -30,4 +31,4 @@ "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 a branch main será selecionada
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify a branch nodejs-fastify será selecionada.
https://gitea.com/sedlav/expressjs.git a branch main será selecionada.
https://gitlab.com/andrasbacsai/nodejs-example.git a branch main será selecionada." -} +} \ No newline at end of file diff --git a/lang/ro.json b/lang/ro.json index 5588ea6f4..18028d087 100644 --- a/lang/ro.json +++ b/lang/ro.json @@ -10,7 +10,8 @@ "auth.login.infomaniak": "Autentificare prin Infomaniak", "auth.already_registered": "Sunteți deja înregistrat?", "auth.confirm_password": "Confirmați parola", - "auth.forgot_password": "Ați uitat parola", + "auth.forgot_password_link": "Ați uitat parola?", + "auth.forgot_password_heading": "Recuperare parolă", "auth.forgot_password_send_email": "Trimiteți e-mail-ul pentru resetarea parolei", "auth.register_now": "Înregistrare", "auth.logout": "Deconectare", @@ -37,4 +38,4 @@ "resource.delete_connected_networks": "Ștergeți definitiv toate rețelele non-predefinite asociate cu această resursă.", "resource.delete_configurations": "Ștergeți definitiv toate fișierele de configurare de pe server.", "database.delete_backups_locally": "Toate copiile de rezervă vor fi șterse definitiv din stocarea locală." -} +} \ No newline at end of file diff --git a/lang/tr.json b/lang/tr.json index 74f693dc9..e3f34aa14 100644 --- a/lang/tr.json +++ b/lang/tr.json @@ -10,7 +10,8 @@ "auth.login.infomaniak": "Infomaniak ile Giriş Yap", "auth.already_registered": "Zaten kayıtlı mısınız?", "auth.confirm_password": "Şifreyi Onayla", - "auth.forgot_password": "Şifremi Unuttum", + "auth.forgot_password_link": "Şifrenizi mi unuttunuz?", + "auth.forgot_password_heading": "Şifre Kurtarma", "auth.forgot_password_send_email": "Şifre sıfırlama e-postası gönder", "auth.register_now": "Kayıt Ol", "auth.logout": "Çıkış Yap", @@ -38,4 +39,4 @@ "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." -} +} \ No newline at end of file diff --git a/lang/vi.json b/lang/vi.json index 46edac599..76e380477 100644 --- a/lang/vi.json +++ b/lang/vi.json @@ -10,7 +10,8 @@ "auth.login.infomaniak": "Đăng Nhập Bằng Infomaniak", "auth.already_registered": "Đã đăng ký?", "auth.confirm_password": "Nhập lại mật khẩu", - "auth.forgot_password": "Quên mật khẩu", + "auth.forgot_password_link": "Quên mật khẩu?", + "auth.forgot_password_heading": "Khôi phục mật khẩu", "auth.forgot_password_send_email": "Gửi email đặt lại mật khẩu", "auth.register_now": "Đăng ký ngay", "auth.logout": "Đăng xuất", @@ -30,4 +31,4 @@ "input.recovery_code": "Mã khôi phục", "button.save": "Lưu", "repository.url": "Ví dụ
Với repo công khai, sử dụng https://....
Với repo riêng tư, sử dụng git@....

https://github.com/coollabsio/coolify-examples nhánh chính sẽ được chọn
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nhánh nodejs-fastify sẽ được chọn.
https://gitea.com/sedlav/expressjs.git nhánh chính sẽ được chọn.
https://gitlab.com/andrasbacsai/nodejs-example.git nhánh chính sẽ được chọn." -} +} \ No newline at end of file diff --git a/lang/zh-cn.json b/lang/zh-cn.json index d46c71e07..530621ee1 100644 --- a/lang/zh-cn.json +++ b/lang/zh-cn.json @@ -10,7 +10,8 @@ "auth.login.infomaniak": "使用 Infomaniak 登录", "auth.already_registered": "已经注册?", "auth.confirm_password": "确认密码", - "auth.forgot_password": "忘记密码", + "auth.forgot_password_link": "忘记密码?", + "auth.forgot_password_heading": "密码找回", "auth.forgot_password_send_email": "发送密码重置邮件", "auth.register_now": "注册", "auth.logout": "退出登录", @@ -30,4 +31,4 @@ "input.recovery_code": "恢复码", "button.save": "保存", "repository.url": "示例
对于公共代码仓库,请使用 https://...
对于私有代码仓库,请使用 git@...

https://github.com/coollabsio/coolify-examples main 分支将被选择
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify 分支将被选择。
https://gitea.com/sedlav/expressjs.git main 分支将被选择。
https://gitlab.com/andrasbacsai/nodejs-example.git main 分支将被选择" -} +} \ No newline at end of file diff --git a/lang/zh-tw.json b/lang/zh-tw.json index c0784c7b7..aa078104b 100644 --- a/lang/zh-tw.json +++ b/lang/zh-tw.json @@ -10,7 +10,8 @@ "auth.login.infomaniak": "使用 Infomaniak 登入", "auth.already_registered": "已經註冊?", "auth.confirm_password": "確認密碼", - "auth.forgot_password": "忘記密碼", + "auth.forgot_password_link": "忘記密碼?", + "auth.forgot_password_heading": "密碼找回", "auth.forgot_password_send_email": "發送重設密碼電郵", "auth.register_now": "註冊", "auth.logout": "登出", @@ -30,4 +31,4 @@ "input.recovery_code": "恢復碼", "button.save": "儲存", "repository.url": "例子
對於公共代碼倉庫,請使用 https://...
對於私有代碼倉庫,請使用 git@...

https://github.com/coollabsio/coolify-examples main 分支將被選擇
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify 分支將被選擇。
https://gitea.com/sedlav/expressjs.git main 分支將被選擇。
https://gitlab.com/andrasbacsai/nodejs-example.git main 分支將被選擇。" -} +} \ No newline at end of file diff --git a/resources/views/auth/forgot-password.blade.php b/resources/views/auth/forgot-password.blade.php index 249aa18f9..66a924fb8 100644 --- a/resources/views/auth/forgot-password.blade.php +++ b/resources/views/auth/forgot-password.blade.php @@ -4,7 +4,7 @@ Coolify
- {{ __('auth.forgot_password') }} + {{ __('auth.forgot_password_heading') }}
diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 42faf517f..8bd8e81fc 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -23,7 +23,7 @@ required label="{{ __('input.password') }}" /> - {{ __('auth.forgot_password') }}? + {{ __('auth.forgot_password_link') }} @else - {{ __('auth.forgot_password') }}? + {{ __('auth.forgot_password_link') }} @endenv From 84a2073f7ff9f81d68efc4dee703d2ae1e83d006 Mon Sep 17 00:00:00 2001 From: Datenschmutz <63157166+Datenschmutz@users.noreply.github.com> Date: Mon, 4 Aug 2025 14:15:37 +0200 Subject: [PATCH 009/367] feat(service): update Authentik template (#6264) --- templates/compose/authentik.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/compose/authentik.yaml b/templates/compose/authentik.yaml index d50110a44..06637d88b 100644 --- a/templates/compose/authentik.yaml +++ b/templates/compose/authentik.yaml @@ -6,7 +6,7 @@ services: authentik-server: - image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.6.3} + image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.6.4} restart: unless-stopped command: server environment: @@ -35,7 +35,7 @@ services: redis: condition: service_healthy authentik-worker: - image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.6.3} + image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.6.4} restart: unless-stopped command: worker environment: From 8332311999def671e6bfe0505acd82890768c2f0 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 14:53:18 +0200 Subject: [PATCH 010/367] chore(deps): update node and php deps --- composer.lock | 409 +++++++++++++++++++++++++-------------------- package-lock.json | 412 +++++++++++++++++++++++----------------------- 2 files changed, 439 insertions(+), 382 deletions(-) diff --git a/composer.lock b/composer.lock index 8d170cdc1..acf153038 100644 --- a/composer.lock +++ b/composer.lock @@ -870,16 +870,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.351.1", + "version": "3.352.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "f3e20c8cdd2cc5827d77a0b3c0872fab89cdf805" + "reference": "7f3ad0da2545b24259273ea7ab892188bae7d91b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f3e20c8cdd2cc5827d77a0b3c0872fab89cdf805", - "reference": "f3e20c8cdd2cc5827d77a0b3c0872fab89cdf805", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/7f3ad0da2545b24259273ea7ab892188bae7d91b", + "reference": "7f3ad0da2545b24259273ea7ab892188bae7d91b", "shasum": "" }, "require": { @@ -961,9 +961,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.351.1" + "source": "https://github.com/aws/aws-sdk-php/tree/3.352.0" }, - "time": "2025-07-17T18:07:08+00:00" + "time": "2025-08-01T18:04:23+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1373,16 +1373,16 @@ }, { "name": "doctrine/dbal", - "version": "4.3.0", + "version": "4.3.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "5fe09532be619202d59c70956c6fb20e97933ee3" + "reference": "ac336c95ea9e13433d56ca81c308b39db0e1a2a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/5fe09532be619202d59c70956c6fb20e97933ee3", - "reference": "5fe09532be619202d59c70956c6fb20e97933ee3", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/ac336c95ea9e13433d56ca81c308b39db0e1a2a7", + "reference": "ac336c95ea9e13433d56ca81c308b39db0e1a2a7", "shasum": "" }, "require": { @@ -1459,7 +1459,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/4.3.0" + "source": "https://github.com/doctrine/dbal/tree/4.3.1" }, "funding": [ { @@ -1475,7 +1475,7 @@ "type": "tidelift" } ], - "time": "2025-06-16T19:31:04+00:00" + "time": "2025-07-22T10:09:51+00:00" }, { "name": "doctrine/deprecations", @@ -2678,16 +2678,16 @@ }, { "name": "laravel/framework", - "version": "v12.20.0", + "version": "v12.21.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "1b9a00f8caf5503c92aa436279172beae1a484ff" + "reference": "ac8c4e73bf1b5387b709f7736d41427e6af1c93b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/1b9a00f8caf5503c92aa436279172beae1a484ff", - "reference": "1b9a00f8caf5503c92aa436279172beae1a484ff", + "url": "https://api.github.com/repos/laravel/framework/zipball/ac8c4e73bf1b5387b709f7736d41427e6af1c93b", + "reference": "ac8c4e73bf1b5387b709f7736d41427e6af1c93b", "shasum": "" }, "require": { @@ -2889,7 +2889,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-07-08T15:02:21+00:00" + "time": "2025-07-22T15:41:55+00:00" }, { "name": "laravel/horizon", @@ -3111,16 +3111,16 @@ }, { "name": "laravel/sanctum", - "version": "v4.1.2", + "version": "v4.2.0", "source": { "type": "git", "url": "https://github.com/laravel/sanctum.git", - "reference": "e4c09e69aecd5a383e0c1b85a6bb501c997d7491" + "reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/e4c09e69aecd5a383e0c1b85a6bb501c997d7491", - "reference": "e4c09e69aecd5a383e0c1b85a6bb501c997d7491", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/fd6df4f79f48a72992e8d29a9c0ee25422a0d677", + "reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677", "shasum": "" }, "require": { @@ -3171,7 +3171,7 @@ "issues": "https://github.com/laravel/sanctum/issues", "source": "https://github.com/laravel/sanctum" }, - "time": "2025-07-01T15:49:32+00:00" + "time": "2025-07-09T19:45:24+00:00" }, { "name": "laravel/serializable-closure", @@ -3236,16 +3236,16 @@ }, { "name": "laravel/socialite", - "version": "v5.21.0", + "version": "v5.23.0", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "d83639499ad14985c9a6a9713b70073300ce998d" + "reference": "e9e0fc83b9d8d71c8385a5da20e5b95ca6234cf5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/d83639499ad14985c9a6a9713b70073300ce998d", - "reference": "d83639499ad14985c9a6a9713b70073300ce998d", + "url": "https://api.github.com/repos/laravel/socialite/zipball/e9e0fc83b9d8d71c8385a5da20e5b95ca6234cf5", + "reference": "e9e0fc83b9d8d71c8385a5da20e5b95ca6234cf5", "shasum": "" }, "require": { @@ -3304,7 +3304,7 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2025-05-19T12:56:37+00:00" + "time": "2025-07-23T14:16:08+00:00" }, { "name": "laravel/tinker", @@ -3510,16 +3510,16 @@ }, { "name": "league/commonmark", - "version": "2.7.0", + "version": "2.7.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405" + "reference": "10732241927d3971d28e7ea7b5712721fa2296ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", - "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/10732241927d3971d28e7ea7b5712721fa2296ca", + "reference": "10732241927d3971d28e7ea7b5712721fa2296ca", "shasum": "" }, "require": { @@ -3548,7 +3548,7 @@ "symfony/process": "^5.4 | ^6.0 | ^7.0", "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", "unleashedtech/php-coding-standard": "^3.1.1", - "vimeo/psalm": "^4.24.0 || ^5.0.0" + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" }, "suggest": { "symfony/yaml": "v2.3+ required if using the Front Matter extension" @@ -3613,7 +3613,7 @@ "type": "tidelift" } ], - "time": "2025-05-05T12:20:28+00:00" + "time": "2025-07-20T12:47:49+00:00" }, { "name": "league/config", @@ -4696,16 +4696,16 @@ }, { "name": "nesbot/carbon", - "version": "3.10.1", + "version": "3.10.2", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "1fd1935b2d90aef2f093c5e35f7ae1257c448d00" + "reference": "76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/1fd1935b2d90aef2f093c5e35f7ae1257c448d00", - "reference": "1fd1935b2d90aef2f093c5e35f7ae1257c448d00", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24", + "reference": "76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24", "shasum": "" }, "require": { @@ -4797,7 +4797,7 @@ "type": "tidelift" } ], - "time": "2025-06-21T15:19:35+00:00" + "time": "2025-08-02T09:36:06+00:00" }, { "name": "nette/schema", @@ -4949,16 +4949,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.5.0", + "version": "v5.6.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56", + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56", "shasum": "" }, "require": { @@ -5001,9 +5001,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0" }, - "time": "2025-05-31T08:24:38+00:00" + "time": "2025-07-27T20:03:57+00:00" }, { "name": "nubs/random-name-generator", @@ -6663,16 +6663,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.9", + "version": "v0.12.10", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "1b801844becfe648985372cb4b12ad6840245ace" + "reference": "6e80abe6f2257121f1eb9a4c55bf29d921025b22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/1b801844becfe648985372cb4b12ad6840245ace", - "reference": "1b801844becfe648985372cb4b12ad6840245ace", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/6e80abe6f2257121f1eb9a4c55bf29d921025b22", + "reference": "6e80abe6f2257121f1eb9a4c55bf29d921025b22", "shasum": "" }, "require": { @@ -6722,12 +6722,11 @@ "authors": [ { "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" + "email": "justin@justinhileman.info" } ], "description": "An interactive shell for modern PHP.", - "homepage": "http://psysh.org", + "homepage": "https://psysh.org", "keywords": [ "REPL", "console", @@ -6736,9 +6735,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.9" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.10" }, - "time": "2025-06-23T02:35:06+00:00" + "time": "2025-08-04T12:39:37+00:00" }, { "name": "purplepixie/phpdns", @@ -7247,16 +7246,16 @@ }, { "name": "sentry/sentry", - "version": "4.14.1", + "version": "4.14.2", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-php.git", - "reference": "a28c4a6f5fda2bf730789a638501d7a737a64eda" + "reference": "bfeec74303d60d3f8bc33701ab3e86f8a8729f17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/a28c4a6f5fda2bf730789a638501d7a737a64eda", - "reference": "a28c4a6f5fda2bf730789a638501d7a737a64eda", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/bfeec74303d60d3f8bc33701ab3e86f8a8729f17", + "reference": "bfeec74303d60d3f8bc33701ab3e86f8a8729f17", "shasum": "" }, "require": { @@ -7320,7 +7319,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-php/issues", - "source": "https://github.com/getsentry/sentry-php/tree/4.14.1" + "source": "https://github.com/getsentry/sentry-php/tree/4.14.2" }, "funding": [ { @@ -7332,7 +7331,7 @@ "type": "custom" } ], - "time": "2025-06-23T15:25:52+00:00" + "time": "2025-07-21T08:28:29+00:00" }, { "name": "sentry/sentry-laravel", @@ -8779,16 +8778,16 @@ }, { "name": "symfony/console", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101" + "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/9e27aecde8f506ba0fd1d9989620c04a87697101", - "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101", + "url": "https://api.github.com/repos/symfony/console/zipball/5f360ebc65c55265a74d23d7fe27f957870158a1", + "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1", "shasum": "" }, "require": { @@ -8853,7 +8852,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.1" + "source": "https://github.com/symfony/console/tree/v7.3.2" }, "funding": [ { @@ -8864,12 +8863,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-07-30T17:13:41+00:00" }, { "name": "symfony/css-selector", @@ -9005,16 +9008,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "35b55b166f6752d6aaf21aa042fc5ed280fce235" + "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/35b55b166f6752d6aaf21aa042fc5ed280fce235", - "reference": "35b55b166f6752d6aaf21aa042fc5ed280fce235", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/0b31a944fcd8759ae294da4d2808cbc53aebd0c3", + "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3", "shasum": "" }, "require": { @@ -9062,7 +9065,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.3.1" + "source": "https://github.com/symfony/error-handler/tree/v7.3.2" }, "funding": [ { @@ -9073,12 +9076,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-13T07:48:40+00:00" + "time": "2025-07-07T08:17:57+00:00" }, { "name": "symfony/event-dispatcher", @@ -9238,16 +9245,16 @@ }, { "name": "symfony/finder", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d" + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d", + "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", "shasum": "" }, "require": { @@ -9282,7 +9289,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.0" + "source": "https://github.com/symfony/finder/tree/v7.3.2" }, "funding": [ { @@ -9293,25 +9300,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-30T19:00:26+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "23dd60256610c86a3414575b70c596e5deff6ed9" + "reference": "6877c122b3a6cc3695849622720054f6e6fa5fa6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/23dd60256610c86a3414575b70c596e5deff6ed9", - "reference": "23dd60256610c86a3414575b70c596e5deff6ed9", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6877c122b3a6cc3695849622720054f6e6fa5fa6", + "reference": "6877c122b3a6cc3695849622720054f6e6fa5fa6", "shasum": "" }, "require": { @@ -9361,7 +9372,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.3.1" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.2" }, "funding": [ { @@ -9372,25 +9383,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-23T15:07:14+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "1644879a66e4aa29c36fe33dfa6c54b450ce1831" + "reference": "6ecc895559ec0097e221ed2fd5eb44d5fede083c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/1644879a66e4aa29c36fe33dfa6c54b450ce1831", - "reference": "1644879a66e4aa29c36fe33dfa6c54b450ce1831", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6ecc895559ec0097e221ed2fd5eb44d5fede083c", + "reference": "6ecc895559ec0097e221ed2fd5eb44d5fede083c", "shasum": "" }, "require": { @@ -9475,7 +9490,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.3.1" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.2" }, "funding": [ { @@ -9486,25 +9501,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-28T08:24:55+00:00" + "time": "2025-07-31T10:45:04+00:00" }, { "name": "symfony/mailer", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "b5db5105b290bdbea5ab27b89c69effcf1cb3368" + "reference": "d43e84d9522345f96ad6283d5dfccc8c1cfc299b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/b5db5105b290bdbea5ab27b89c69effcf1cb3368", - "reference": "b5db5105b290bdbea5ab27b89c69effcf1cb3368", + "url": "https://api.github.com/repos/symfony/mailer/zipball/d43e84d9522345f96ad6283d5dfccc8c1cfc299b", + "reference": "d43e84d9522345f96ad6283d5dfccc8c1cfc299b", "shasum": "" }, "require": { @@ -9555,7 +9574,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.3.1" + "source": "https://github.com/symfony/mailer/tree/v7.3.2" }, "funding": [ { @@ -9566,25 +9585,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-07-15T11:36:08+00:00" }, { "name": "symfony/mime", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9" + "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", - "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", + "url": "https://api.github.com/repos/symfony/mime/zipball/e0a0f859148daf1edf6c60b398eb40bfc96697d1", + "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1", "shasum": "" }, "require": { @@ -9639,7 +9662,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.3.0" + "source": "https://github.com/symfony/mime/tree/v7.3.2" }, "funding": [ { @@ -9650,25 +9673,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-19T08:51:26+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca" + "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/afb9a8038025e5dbc657378bfab9198d75f10fca", - "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/119bcf13e67dbd188e5dbc74228b1686f66acd37", + "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37", "shasum": "" }, "require": { @@ -9706,7 +9733,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.3.0" + "source": "https://github.com/symfony/options-resolver/tree/v7.3.2" }, "funding": [ { @@ -9717,12 +9744,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-04T13:12:05+00:00" + "time": "2025-07-15T11:36:08+00:00" }, { "name": "symfony/polyfill-ctype", @@ -10587,16 +10618,16 @@ }, { "name": "symfony/routing", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "8e213820c5fea844ecea29203d2a308019007c15" + "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/8e213820c5fea844ecea29203d2a308019007c15", - "reference": "8e213820c5fea844ecea29203d2a308019007c15", + "url": "https://api.github.com/repos/symfony/routing/zipball/7614b8ca5fa89b9cd233e21b627bfc5774f586e4", + "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4", "shasum": "" }, "require": { @@ -10648,7 +10679,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.3.0" + "source": "https://github.com/symfony/routing/tree/v7.3.2" }, "funding": [ { @@ -10659,12 +10690,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-24T20:43:28+00:00" + "time": "2025-07-15T11:36:08+00:00" }, { "name": "symfony/service-contracts", @@ -10813,16 +10848,16 @@ }, { "name": "symfony/string", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", + "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca", + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca", "shasum": "" }, "require": { @@ -10880,7 +10915,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.0" + "source": "https://github.com/symfony/string/tree/v7.3.2" }, "funding": [ { @@ -10891,25 +10926,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-20T20:19:01+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { "name": "symfony/translation", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "241d5ac4910d256660238a7ecf250deba4c73063" + "reference": "81b48f4daa96272efcce9c7a6c4b58e629df3c90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/241d5ac4910d256660238a7ecf250deba4c73063", - "reference": "241d5ac4910d256660238a7ecf250deba4c73063", + "url": "https://api.github.com/repos/symfony/translation/zipball/81b48f4daa96272efcce9c7a6c4b58e629df3c90", + "reference": "81b48f4daa96272efcce9c7a6c4b58e629df3c90", "shasum": "" }, "require": { @@ -10976,7 +11015,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.3.1" + "source": "https://github.com/symfony/translation/tree/v7.3.2" }, "funding": [ { @@ -10987,12 +11026,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-07-30T17:31:46+00:00" }, { "name": "symfony/translation-contracts", @@ -11148,16 +11191,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42" + "reference": "53205bea27450dc5c65377518b3275e126d45e75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6e209fbe5f5a7b6043baba46fe5735a4b85d0d42", - "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/53205bea27450dc5c65377518b3275e126d45e75", + "reference": "53205bea27450dc5c65377518b3275e126d45e75", "shasum": "" }, "require": { @@ -11169,7 +11212,6 @@ "symfony/console": "<6.4" }, "require-dev": { - "ext-iconv": "*", "symfony/console": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", "symfony/process": "^6.4|^7.0", @@ -11212,7 +11254,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.1" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.2" }, "funding": [ { @@ -11223,25 +11265,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-07-29T20:02:46+00:00" }, { "name": "symfony/yaml", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb" + "reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/0c3555045a46ab3cd4cc5a69d161225195230edb", - "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb", + "url": "https://api.github.com/repos/symfony/yaml/zipball/b8d7d868da9eb0919e99c8830431ea087d6aae30", + "reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30", "shasum": "" }, "require": { @@ -11284,7 +11330,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.3.1" + "source": "https://github.com/symfony/yaml/tree/v7.3.2" }, "funding": [ { @@ -11295,12 +11341,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-03T06:57:57+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -12039,16 +12089,16 @@ "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.15.4", + "version": "v3.16.0", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "c0667ea91f7185f1e074402c5788195e96bf8106" + "reference": "f265cf5e38577d42311f1a90d619bcd3740bea23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/c0667ea91f7185f1e074402c5788195e96bf8106", - "reference": "c0667ea91f7185f1e074402c5788195e96bf8106", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/f265cf5e38577d42311f1a90d619bcd3740bea23", + "reference": "f265cf5e38577d42311f1a90d619bcd3740bea23", "shasum": "" }, "require": { @@ -12056,7 +12106,7 @@ "illuminate/session": "^9|^10|^11|^12", "illuminate/support": "^9|^10|^11|^12", "php": "^8.1", - "php-debugbar/php-debugbar": "~2.1.1", + "php-debugbar/php-debugbar": "~2.2.0", "symfony/finder": "^6|^7" }, "require-dev": { @@ -12076,7 +12126,7 @@ ] }, "branch-alias": { - "dev-master": "3.15-dev" + "dev-master": "3.16-dev" } }, "autoload": { @@ -12108,7 +12158,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.15.4" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.16.0" }, "funding": [ { @@ -12120,7 +12170,7 @@ "type": "github" } ], - "time": "2025-04-16T06:32:06+00:00" + "time": "2025-07-14T11:56:43+00:00" }, { "name": "brianium/paratest", @@ -12641,16 +12691,16 @@ }, { "name": "laravel/telescope", - "version": "v5.10.0", + "version": "v5.10.2", "source": { "type": "git", "url": "https://github.com/laravel/telescope.git", - "reference": "fc0a8662682c0375b534033873debb780c003486" + "reference": "6d249d93ab06dc147ac62ea02b4272c2e7a24b72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/telescope/zipball/fc0a8662682c0375b534033873debb780c003486", - "reference": "fc0a8662682c0375b534033873debb780c003486", + "url": "https://api.github.com/repos/laravel/telescope/zipball/6d249d93ab06dc147ac62ea02b4272c2e7a24b72", + "reference": "6d249d93ab06dc147ac62ea02b4272c2e7a24b72", "shasum": "" }, "require": { @@ -12704,9 +12754,9 @@ ], "support": { "issues": "https://github.com/laravel/telescope/issues", - "source": "https://github.com/laravel/telescope/tree/v5.10.0" + "source": "https://github.com/laravel/telescope/tree/v5.10.2" }, - "time": "2025-07-07T14:47:19+00:00" + "time": "2025-07-24T05:26:13+00:00" }, { "name": "mockery/mockery", @@ -12793,16 +12843,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.3", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "faed855a7b5f4d4637717c2b3863e277116beb36" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36", - "reference": "faed855a7b5f4d4637717c2b3863e277116beb36", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -12841,7 +12891,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -12849,7 +12899,7 @@ "type": "tidelift" } ], - "time": "2025-07-05T12:25:42+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nunomaduro/collision", @@ -13394,16 +13444,16 @@ }, { "name": "php-debugbar/php-debugbar", - "version": "v2.1.6", + "version": "v2.2.4", "source": { "type": "git", "url": "https://github.com/php-debugbar/php-debugbar.git", - "reference": "16fa68da5617220594aa5e33fa9de415f94784a0" + "reference": "3146d04671f51f69ffec2a4207ac3bdcf13a9f35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/16fa68da5617220594aa5e33fa9de415f94784a0", - "reference": "16fa68da5617220594aa5e33fa9de415f94784a0", + "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/3146d04671f51f69ffec2a4207ac3bdcf13a9f35", + "reference": "3146d04671f51f69ffec2a4207ac3bdcf13a9f35", "shasum": "" }, "require": { @@ -13411,6 +13461,9 @@ "psr/log": "^1|^2|^3", "symfony/var-dumper": "^4|^5|^6|^7" }, + "replace": { + "maximebf/debugbar": "self.version" + }, "require-dev": { "dbrekelmans/bdi": "^1", "phpunit/phpunit": "^8|^9", @@ -13425,7 +13478,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -13458,9 +13511,9 @@ ], "support": { "issues": "https://github.com/php-debugbar/php-debugbar/issues", - "source": "https://github.com/php-debugbar/php-debugbar/tree/v2.1.6" + "source": "https://github.com/php-debugbar/php-debugbar/tree/v2.2.4" }, - "time": "2025-02-21T17:47:03+00:00" + "time": "2025-07-22T14:01:30+00:00" }, { "name": "php-webdriver/webdriver", @@ -13530,16 +13583,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.18", + "version": "2.1.21", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "ee1f390b7a70cdf74a2b737e554f68afea885db7" + "reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ee1f390b7a70cdf74a2b737e554f68afea885db7", - "reference": "ee1f390b7a70cdf74a2b737e554f68afea885db7", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1ccf445757458c06a04eb3f803603cb118fe5fa6", + "reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6", "shasum": "" }, "require": { @@ -13584,7 +13637,7 @@ "type": "github" } ], - "time": "2025-07-17T17:22:31+00:00" + "time": "2025-07-28T19:35:08+00:00" }, { "name": "phpunit/php-code-coverage", @@ -15436,16 +15489,16 @@ }, { "name": "symfony/http-client", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "4403d87a2c16f33345dca93407a8714ee8c05a64" + "reference": "1c064a0c67749923483216b081066642751cc2c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/4403d87a2c16f33345dca93407a8714ee8c05a64", - "reference": "4403d87a2c16f33345dca93407a8714ee8c05a64", + "url": "https://api.github.com/repos/symfony/http-client/zipball/1c064a0c67749923483216b081066642751cc2c7", + "reference": "1c064a0c67749923483216b081066642751cc2c7", "shasum": "" }, "require": { @@ -15511,7 +15564,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.3.1" + "source": "https://github.com/symfony/http-client/tree/v7.3.2" }, "funding": [ { @@ -15522,12 +15575,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-28T07:58:39+00:00" + "time": "2025-07-15T11:36:08+00:00" }, { "name": "symfony/http-client-contracts", diff --git a/package-lock.json b/package-lock.json index 10489a7d4..34b2c1dd5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -90,9 +90,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz", - "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -104,9 +104,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz", - "integrity": "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", "cpu": [ "ppc64" ], @@ -121,9 +121,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.6.tgz", - "integrity": "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", "cpu": [ "arm" ], @@ -138,9 +138,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.6.tgz", - "integrity": "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", "cpu": [ "arm64" ], @@ -155,9 +155,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.6.tgz", - "integrity": "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", "cpu": [ "x64" ], @@ -172,9 +172,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.6.tgz", - "integrity": "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", "cpu": [ "arm64" ], @@ -189,9 +189,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.6.tgz", - "integrity": "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", "cpu": [ "x64" ], @@ -206,9 +206,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.6.tgz", - "integrity": "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", "cpu": [ "arm64" ], @@ -223,9 +223,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.6.tgz", - "integrity": "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", "cpu": [ "x64" ], @@ -240,9 +240,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.6.tgz", - "integrity": "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", "cpu": [ "arm" ], @@ -257,9 +257,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.6.tgz", - "integrity": "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", "cpu": [ "arm64" ], @@ -274,9 +274,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.6.tgz", - "integrity": "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", "cpu": [ "ia32" ], @@ -291,9 +291,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.6.tgz", - "integrity": "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", "cpu": [ "loong64" ], @@ -308,9 +308,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.6.tgz", - "integrity": "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", "cpu": [ "mips64el" ], @@ -325,9 +325,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.6.tgz", - "integrity": "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", "cpu": [ "ppc64" ], @@ -342,9 +342,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.6.tgz", - "integrity": "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", "cpu": [ "riscv64" ], @@ -359,9 +359,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.6.tgz", - "integrity": "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", "cpu": [ "s390x" ], @@ -376,9 +376,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.6.tgz", - "integrity": "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", "cpu": [ "x64" ], @@ -393,9 +393,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.6.tgz", - "integrity": "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", "cpu": [ "arm64" ], @@ -410,9 +410,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.6.tgz", - "integrity": "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", "cpu": [ "x64" ], @@ -427,9 +427,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.6.tgz", - "integrity": "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", "cpu": [ "arm64" ], @@ -444,9 +444,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.6.tgz", - "integrity": "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", "cpu": [ "x64" ], @@ -461,9 +461,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.6.tgz", - "integrity": "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", "cpu": [ "arm64" ], @@ -478,9 +478,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.6.tgz", - "integrity": "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", "cpu": [ "x64" ], @@ -495,9 +495,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.6.tgz", - "integrity": "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", "cpu": [ "arm64" ], @@ -512,9 +512,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.6.tgz", - "integrity": "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", "cpu": [ "ia32" ], @@ -529,9 +529,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz", - "integrity": "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", "cpu": [ "x64" ], @@ -546,9 +546,9 @@ } }, "node_modules/@ioredis/commands": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", - "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.3.0.tgz", + "integrity": "sha512-M/T6Zewn7sDaBQEqIZ8Rb+i9y8qfGmq+5SDFSf9sA2lUZTmdDLVdOiQaeDp+Q4wElZ9HG1GAX5KhDaidp6LQsQ==", "license": "MIT" }, "node_modules/@isaacs/fs-minipass": { @@ -604,9 +604,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz", - "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", + "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", "cpu": [ "arm" ], @@ -618,9 +618,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz", - "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", + "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", "cpu": [ "arm64" ], @@ -632,9 +632,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz", - "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", + "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", "cpu": [ "arm64" ], @@ -646,9 +646,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz", - "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", + "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", "cpu": [ "x64" ], @@ -660,9 +660,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz", - "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", + "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", "cpu": [ "arm64" ], @@ -674,9 +674,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz", - "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", + "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", "cpu": [ "x64" ], @@ -688,9 +688,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz", - "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", + "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", "cpu": [ "arm" ], @@ -702,9 +702,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz", - "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", + "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", "cpu": [ "arm" ], @@ -716,9 +716,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz", - "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", + "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", "cpu": [ "arm64" ], @@ -730,9 +730,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz", - "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", + "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", "cpu": [ "arm64" ], @@ -744,9 +744,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz", - "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", + "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", "cpu": [ "loong64" ], @@ -757,10 +757,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz", - "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", + "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", "cpu": [ "ppc64" ], @@ -772,9 +772,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz", - "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", + "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", "cpu": [ "riscv64" ], @@ -786,9 +786,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz", - "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", + "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", "cpu": [ "riscv64" ], @@ -800,9 +800,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz", - "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", + "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", "cpu": [ "s390x" ], @@ -814,9 +814,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz", - "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", + "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", "cpu": [ "x64" ], @@ -828,9 +828,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz", - "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", + "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", "cpu": [ "x64" ], @@ -842,9 +842,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz", - "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", "cpu": [ "arm64" ], @@ -856,9 +856,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz", - "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", "cpu": [ "ia32" ], @@ -870,9 +870,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz", - "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", "cpu": [ "x64" ], @@ -1623,9 +1623,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz", - "integrity": "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1636,32 +1636,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.6", - "@esbuild/android-arm": "0.25.6", - "@esbuild/android-arm64": "0.25.6", - "@esbuild/android-x64": "0.25.6", - "@esbuild/darwin-arm64": "0.25.6", - "@esbuild/darwin-x64": "0.25.6", - "@esbuild/freebsd-arm64": "0.25.6", - "@esbuild/freebsd-x64": "0.25.6", - "@esbuild/linux-arm": "0.25.6", - "@esbuild/linux-arm64": "0.25.6", - "@esbuild/linux-ia32": "0.25.6", - "@esbuild/linux-loong64": "0.25.6", - "@esbuild/linux-mips64el": "0.25.6", - "@esbuild/linux-ppc64": "0.25.6", - "@esbuild/linux-riscv64": "0.25.6", - "@esbuild/linux-s390x": "0.25.6", - "@esbuild/linux-x64": "0.25.6", - "@esbuild/netbsd-arm64": "0.25.6", - "@esbuild/netbsd-x64": "0.25.6", - "@esbuild/openbsd-arm64": "0.25.6", - "@esbuild/openbsd-x64": "0.25.6", - "@esbuild/openharmony-arm64": "0.25.6", - "@esbuild/sunos-x64": "0.25.6", - "@esbuild/win32-arm64": "0.25.6", - "@esbuild/win32-ia32": "0.25.6", - "@esbuild/win32-x64": "0.25.6" + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" } }, "node_modules/estree-walker": { @@ -1687,9 +1687,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "dev": true, "funding": [ { @@ -1875,9 +1875,9 @@ } }, "node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", "dev": true, "license": "MIT", "bin": { @@ -2397,9 +2397,9 @@ } }, "node_modules/react": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", - "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", "dev": true, "license": "MIT", "peer": true, @@ -2429,9 +2429,9 @@ } }, "node_modules/rollup": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", - "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", "dev": true, "license": "MIT", "dependencies": { @@ -2445,26 +2445,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.45.1", - "@rollup/rollup-android-arm64": "4.45.1", - "@rollup/rollup-darwin-arm64": "4.45.1", - "@rollup/rollup-darwin-x64": "4.45.1", - "@rollup/rollup-freebsd-arm64": "4.45.1", - "@rollup/rollup-freebsd-x64": "4.45.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", - "@rollup/rollup-linux-arm-musleabihf": "4.45.1", - "@rollup/rollup-linux-arm64-gnu": "4.45.1", - "@rollup/rollup-linux-arm64-musl": "4.45.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", - "@rollup/rollup-linux-riscv64-gnu": "4.45.1", - "@rollup/rollup-linux-riscv64-musl": "4.45.1", - "@rollup/rollup-linux-s390x-gnu": "4.45.1", - "@rollup/rollup-linux-x64-gnu": "4.45.1", - "@rollup/rollup-linux-x64-musl": "4.45.1", - "@rollup/rollup-win32-arm64-msvc": "4.45.1", - "@rollup/rollup-win32-ia32-msvc": "4.45.1", - "@rollup/rollup-win32-x64-msvc": "4.45.1", + "@rollup/rollup-android-arm-eabi": "4.46.2", + "@rollup/rollup-android-arm64": "4.46.2", + "@rollup/rollup-darwin-arm64": "4.46.2", + "@rollup/rollup-darwin-x64": "4.46.2", + "@rollup/rollup-freebsd-arm64": "4.46.2", + "@rollup/rollup-freebsd-x64": "4.46.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", + "@rollup/rollup-linux-arm-musleabihf": "4.46.2", + "@rollup/rollup-linux-arm64-gnu": "4.46.2", + "@rollup/rollup-linux-arm64-musl": "4.46.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", + "@rollup/rollup-linux-ppc64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-musl": "4.46.2", + "@rollup/rollup-linux-s390x-gnu": "4.46.2", + "@rollup/rollup-linux-x64-gnu": "4.46.2", + "@rollup/rollup-linux-x64-musl": "4.46.2", + "@rollup/rollup-win32-arm64-msvc": "4.46.2", + "@rollup/rollup-win32-ia32-msvc": "4.46.2", + "@rollup/rollup-win32-x64-msvc": "4.46.2", "fsevents": "~2.3.2" } }, From f06d80e80e364628125ce35e887b16f186f0419c Mon Sep 17 00:00:00 2001 From: Gauthier POGAM--LE MONTAGNER Date: Mon, 4 Aug 2025 15:32:15 +0200 Subject: [PATCH 011/367] feat(service): add sequin template (#6105) --- public/svgs/sequin.svg | 16 +++++++++ templates/compose/sequin.yaml | 68 +++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 public/svgs/sequin.svg create mode 100644 templates/compose/sequin.yaml diff --git a/public/svgs/sequin.svg b/public/svgs/sequin.svg new file mode 100644 index 000000000..623bc1159 --- /dev/null +++ b/public/svgs/sequin.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/templates/compose/sequin.yaml b/templates/compose/sequin.yaml new file mode 100644 index 000000000..18f88fd89 --- /dev/null +++ b/templates/compose/sequin.yaml @@ -0,0 +1,68 @@ +# documentation: https://sequinstream.com/docs/ +# slogan: The fastest Postgres change data capture +# tags: postgres, sync, data +# logo: svgs/sequin.svg +# port: 7376 + +services: + sequin: + image: sequin/sequin:latest + environment: + - SERVICE_FQDN_SEQUIN_7376 + - SERVER_HOST=${SERVICE_FQDN_SEQUIN} + - PG_HOSTNAME=postgres + - PG_DATABASE=${POSTGRES_DB:-sequin-db} + - PG_PORT=5432 + - PG_USERNAME=${SERVICE_USER_POSTGRES} + - PG_PASSWORD=${SERVICE_PASSWORD_POSTGRES} + - PG_POOL_SIZE=20 + - SECRET_KEY_BASE=${SERVICE_REALBASE64_64_SECRETKEY} + - VAULT_KEY=${SERVICE_REALBASE64_VAULTKEY} + - REDIS_URL=redis://redis:6379 + - CONFIG_FILE_PATH=/config/playground.yml + - FEATURE_ACCOUNT_SELF_SIGNUP=${FEATURE_ACCOUNT_SELF_SIGNUP:-false} + - SEQUIN_TELEMETRY_DISABLED=${SEQUIN_TELEMETRY_DISABLED:-false} + - CRASH_REPORTING_DISABLED=${CRASH_REPORTING_DISABLED:-false} + depends_on: + redis: + condition: service_started + postgres: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "http://localhost:7376/health"] + + postgres: + image: 'postgres:16' + environment: + - POSTGRES_DB=${POSTGRES_DB:-sequin-db} + - POSTGRES_USER=${SERVICE_USER_POSTGRES} + - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} + command: + - postgres + - '-c' + - wal_level=logical + healthcheck: + test: + - CMD-SHELL + - 'pg_isready -U ${SERVICE_USER_POSTGRES} -d sequin' + interval: 10s + timeout: 2s + retries: 5 + start_period: 2s + start_interval: 1s + volumes: + - 'postgres_data:/var/lib/postgresql/data' + + redis: + image: 'redis:7' + command: + - redis-server + - '--port' + - '6379' + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 20s + retries: 10 + volumes: + - 'redis_data:/data' From b165d506c968480fed25219b7ab54cab1add813a Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 15:35:27 +0200 Subject: [PATCH 012/367] chore(service): improve sequin - make sure Redis is healthy - formatting --- templates/compose/sequin.yaml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/templates/compose/sequin.yaml b/templates/compose/sequin.yaml index 18f88fd89..35e210bdf 100644 --- a/templates/compose/sequin.yaml +++ b/templates/compose/sequin.yaml @@ -9,7 +9,7 @@ services: image: sequin/sequin:latest environment: - SERVICE_FQDN_SEQUIN_7376 - - SERVER_HOST=${SERVICE_FQDN_SEQUIN} + - SERVER_HOST=${SERVICE_URL_SEQUIN} - PG_HOSTNAME=postgres - PG_DATABASE=${POSTGRES_DB:-sequin-db} - PG_PORT=5432 @@ -25,44 +25,44 @@ services: - CRASH_REPORTING_DISABLED=${CRASH_REPORTING_DISABLED:-false} depends_on: redis: - condition: service_started + condition: service_healthy postgres: condition: service_healthy healthcheck: test: ["CMD", "curl", "http://localhost:7376/health"] postgres: - image: 'postgres:16' + image: postgres:16 environment: - POSTGRES_DB=${POSTGRES_DB:-sequin-db} - POSTGRES_USER=${SERVICE_USER_POSTGRES} - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} command: - postgres - - '-c' + - "-c" - wal_level=logical healthcheck: test: - CMD-SHELL - - 'pg_isready -U ${SERVICE_USER_POSTGRES} -d sequin' + - "pg_isready -U ${SERVICE_USER_POSTGRES} -d sequin" interval: 10s timeout: 2s retries: 5 start_period: 2s start_interval: 1s volumes: - - 'postgres_data:/var/lib/postgresql/data' + - postgres_data:/var/lib/postgresql/data redis: - image: 'redis:7' + image: redis:7 command: - redis-server - - '--port' - - '6379' + - "--port" + - "6379" healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 20s retries: 10 volumes: - - 'redis_data:/data' + - redis_data:/data From 10823666b2a0b032b96025b40c04e6ed7410e7b2 Mon Sep 17 00:00:00 2001 From: Jonas <56066318+OG-Jons@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:48:54 +0900 Subject: [PATCH 013/367] feat(service): add pi-hole template (#6020) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 🏔️ Peak <122374094+peaklabs-dev@users.noreply.github.com> --- public/svgs/pihole.svg | 1 + templates/compose/pi-hole.yaml | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 public/svgs/pihole.svg create mode 100644 templates/compose/pi-hole.yaml diff --git a/public/svgs/pihole.svg b/public/svgs/pihole.svg new file mode 100644 index 000000000..a4efefcc8 --- /dev/null +++ b/public/svgs/pihole.svg @@ -0,0 +1 @@ +NewVortex \ No newline at end of file diff --git a/templates/compose/pi-hole.yaml b/templates/compose/pi-hole.yaml new file mode 100644 index 000000000..476067b16 --- /dev/null +++ b/templates/compose/pi-hole.yaml @@ -0,0 +1,30 @@ +# documentation: https://pi-hole.net/ +# slogan: Network-wide Ad Blocking +# tags: ad-block,dns,sinkhole,ntp,dhcp +# logo: svgs/pihole.svg +# port: 80 + +services: + pihole: + image: pihole/pihole:latest + ports: + # DNS Ports + - "53:53/tcp" + - "53:53/udp" + # Uncomment the below if using Pi-hole as your DHCP Server + - "67:67/udp" + # Uncomment the line below if you are using Pi-hole as your NTP server + - "123:123/udp" + environment: + - SERVICE_FQDN_PIHOLE_80 + - TZ=${TZ:-Europe/London} + - FTLCONF_webserver_api_password=${SERVICE_PASSWORD_PIHOLE} + - FTLCONF_dns_listeningMode=${FTLCONF_dns_listeningMode:-all} + volumes: + - pihole-data:/etc/pihole + cap_add: + # See https://github.com/pi-hole/docker-pi-hole#note-on-capabilities + # Required if you are using Pi-hole as your DHCP server, else not needed + - NET_ADMIN + # Required if you are using Pi-hole as your NTP client to be able to set the host's system time + - SYS_TIME From 2a526c54d5ed7d2e52a307266ec9789b8c1d5d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=8F=94=EF=B8=8F=20Peak?= <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 21:15:56 +0200 Subject: [PATCH 014/367] fix(docker): volumes get delete when stopping a service if `Delete Unused Volumes` is activated (#6317) --- app/Actions/Application/StopApplication.php | 2 +- app/Actions/Database/StopDatabase.php | 2 +- app/Actions/Server/CleanupDocker.php | 6 ++--- app/Actions/Server/UpdateCoolify.php | 2 +- app/Actions/Service/DeleteService.php | 4 +-- app/Actions/Service/StopService.php | 2 +- .../Api/ApplicationsController.php | 6 ++--- .../Controllers/Api/DatabasesController.php | 6 ++--- .../Controllers/Api/ServicesController.php | 6 ++--- app/Jobs/DeleteResourceJob.php | 10 ++++---- app/Jobs/DockerCleanupJob.php | 25 ++++++++++++++++--- app/Jobs/ServerResourceManager.php | 2 +- app/Livewire/Project/Shared/Danger.php | 6 ++--- app/Livewire/Server/DockerCleanup.php | 2 +- 14 files changed, 49 insertions(+), 32 deletions(-) diff --git a/app/Actions/Application/StopApplication.php b/app/Actions/Application/StopApplication.php index 0ca703fce..ee3398b04 100644 --- a/app/Actions/Application/StopApplication.php +++ b/app/Actions/Application/StopApplication.php @@ -49,7 +49,7 @@ class StopApplication } if ($dockerCleanup) { - CleanupDocker::dispatch($server, true); + CleanupDocker::dispatch($server, false, false); } } catch (\Exception $e) { return $e->getMessage(); diff --git a/app/Actions/Database/StopDatabase.php b/app/Actions/Database/StopDatabase.php index a03c9269e..6fcdedeeb 100644 --- a/app/Actions/Database/StopDatabase.php +++ b/app/Actions/Database/StopDatabase.php @@ -29,7 +29,7 @@ class StopDatabase $this->stopContainer($database, $database->uuid, 30); if ($dockerCleanup) { - CleanupDocker::dispatch($server, true); + CleanupDocker::dispatch($server, false, false); } if ($database->is_public) { diff --git a/app/Actions/Server/CleanupDocker.php b/app/Actions/Server/CleanupDocker.php index 754feecb1..392562167 100644 --- a/app/Actions/Server/CleanupDocker.php +++ b/app/Actions/Server/CleanupDocker.php @@ -11,7 +11,7 @@ class CleanupDocker public string $jobQueue = 'high'; - public function handle(Server $server) + public function handle(Server $server, bool $deleteUnusedVolumes = false, bool $deleteUnusedNetworks = false) { $settings = instanceSettings(); $realtimeImage = config('constants.coolify.realtime_image'); @@ -36,11 +36,11 @@ class CleanupDocker "docker images --filter before=$realtimeImageWithoutPrefixVersion --filter reference=$realtimeImageWithoutPrefix | grep $realtimeImageWithoutPrefix | awk '{print $3}' | xargs -r docker rmi -f", ]; - if ($server->settings->delete_unused_volumes) { + if ($deleteUnusedVolumes) { $commands[] = 'docker volume prune -af'; } - if ($server->settings->delete_unused_networks) { + if ($deleteUnusedNetworks) { $commands[] = 'docker network prune -f'; } diff --git a/app/Actions/Server/UpdateCoolify.php b/app/Actions/Server/UpdateCoolify.php index 9a6cc140b..2a06428e2 100644 --- a/app/Actions/Server/UpdateCoolify.php +++ b/app/Actions/Server/UpdateCoolify.php @@ -29,7 +29,7 @@ class UpdateCoolify if (! $this->server) { return; } - CleanupDocker::dispatch($this->server); + CleanupDocker::dispatch($this->server, false, false); $this->latestVersion = get_latest_version_of_coolify(); $this->currentVersion = config('constants.coolify.version'); if (! $manual_update) { diff --git a/app/Actions/Service/DeleteService.php b/app/Actions/Service/DeleteService.php index 404e11559..8790901cd 100644 --- a/app/Actions/Service/DeleteService.php +++ b/app/Actions/Service/DeleteService.php @@ -11,7 +11,7 @@ class DeleteService { use AsAction; - public function handle(Service $service, bool $deleteConfigurations, bool $deleteVolumes, bool $dockerCleanup, bool $deleteConnectedNetworks) + public function handle(Service $service, bool $deleteVolumes, bool $deleteConnectedNetworks, bool $deleteConfigurations, bool $dockerCleanup) { try { $server = data_get($service, 'server'); @@ -71,7 +71,7 @@ class DeleteService $service->forceDelete(); if ($dockerCleanup) { - CleanupDocker::dispatch($server, true); + CleanupDocker::dispatch($server, false, false); } } } diff --git a/app/Actions/Service/StopService.php b/app/Actions/Service/StopService.php index a7fa4b8b2..190b8885d 100644 --- a/app/Actions/Service/StopService.php +++ b/app/Actions/Service/StopService.php @@ -40,7 +40,7 @@ class StopService $service->deleteConnectedNetworks(); } if ($dockerCleanup) { - CleanupDocker::dispatch($server, true); + CleanupDocker::dispatch($server, false, false); } } catch (\Exception $e) { return $e->getMessage(); diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 0860c7133..dcf0e5fbe 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -1699,10 +1699,10 @@ class ApplicationsController extends Controller DeleteResourceJob::dispatch( resource: $application, - deleteConfigurations: $request->query->get('delete_configurations', true), deleteVolumes: $request->query->get('delete_volumes', true), - dockerCleanup: $request->query->get('docker_cleanup', true), - deleteConnectedNetworks: $request->query->get('delete_connected_networks', true) + deleteConnectedNetworks: $request->query->get('delete_connected_networks', true), + deleteConfigurations: $request->query->get('delete_configurations', true), + dockerCleanup: $request->query->get('docker_cleanup', true) ); return response()->json([ diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php index 504665f6a..6ac052b3c 100644 --- a/app/Http/Controllers/Api/DatabasesController.php +++ b/app/Http/Controllers/Api/DatabasesController.php @@ -1608,10 +1608,10 @@ class DatabasesController extends Controller DeleteResourceJob::dispatch( resource: $database, - deleteConfigurations: $request->query->get('delete_configurations', true), deleteVolumes: $request->query->get('delete_volumes', true), - dockerCleanup: $request->query->get('docker_cleanup', true), - deleteConnectedNetworks: $request->query->get('delete_connected_networks', true) + deleteConnectedNetworks: $request->query->get('delete_connected_networks', true), + deleteConfigurations: $request->query->get('delete_configurations', true), + dockerCleanup: $request->query->get('docker_cleanup', true) ); return response()->json([ diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index 542be83de..34d8ee669 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -510,10 +510,10 @@ class ServicesController extends Controller DeleteResourceJob::dispatch( resource: $service, - deleteConfigurations: $request->query->get('delete_configurations', true), deleteVolumes: $request->query->get('delete_volumes', true), - dockerCleanup: $request->query->get('docker_cleanup', true), - deleteConnectedNetworks: $request->query->get('delete_connected_networks', true) + deleteConnectedNetworks: $request->query->get('delete_connected_networks', true), + deleteConfigurations: $request->query->get('delete_configurations', true), + dockerCleanup: $request->query->get('docker_cleanup', true) ); return response()->json([ diff --git a/app/Jobs/DeleteResourceJob.php b/app/Jobs/DeleteResourceJob.php index 2750110f2..a725df52f 100644 --- a/app/Jobs/DeleteResourceJob.php +++ b/app/Jobs/DeleteResourceJob.php @@ -32,10 +32,10 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue public function __construct( public Application|ApplicationPreview|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, - public bool $deleteConfigurations = true, public bool $deleteVolumes = true, - public bool $dockerCleanup = true, - public bool $deleteConnectedNetworks = true + public bool $deleteConnectedNetworks = true, + public bool $deleteConfigurations = true, + public bool $dockerCleanup = true ) { $this->onQueue('high'); } @@ -66,7 +66,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue break; case 'service': StopService::run($this->resource, true); - DeleteService::run($this->resource, $this->deleteConfigurations, $this->deleteVolumes, $this->dockerCleanup, $this->deleteConnectedNetworks); + DeleteService::run($this->resource, $this->deleteVolumes, $this->deleteConnectedNetworks, $this->deleteConfigurations, $this->dockerCleanup); return; } @@ -106,7 +106,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue if ($this->dockerCleanup) { $server = data_get($this->resource, 'server') ?? data_get($this->resource, 'destination.server'); if ($server) { - CleanupDocker::dispatch($server, true); + CleanupDocker::dispatch($server, false, false); } } Artisan::queue('cleanup:stucked-resources'); diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php index 519728ab0..f3f3a2ae4 100644 --- a/app/Jobs/DockerCleanupJob.php +++ b/app/Jobs/DockerCleanupJob.php @@ -34,7 +34,12 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue return [(new WithoutOverlapping('docker-cleanup-'.$this->server->uuid))->expireAfter(600)->dontRelease()]; } - public function __construct(public Server $server, public bool $manualCleanup = false) {} + public function __construct( + public Server $server, + public bool $manualCleanup = false, + public bool $deleteUnusedVolumes = false, + public bool $deleteUnusedNetworks = false + ) {} public function handle(): void { @@ -50,7 +55,11 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue $this->usageBefore = $this->server->getDiskUsage(); if ($this->manualCleanup || $this->server->settings->force_docker_cleanup) { - $cleanup_log = CleanupDocker::run(server: $this->server); + $cleanup_log = CleanupDocker::run( + server: $this->server, + deleteUnusedVolumes: $this->deleteUnusedVolumes, + deleteUnusedNetworks: $this->deleteUnusedNetworks + ); $usageAfter = $this->server->getDiskUsage(); $message = ($this->manualCleanup ? 'Manual' : 'Forced').' Docker cleanup job executed successfully. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.'; @@ -67,7 +76,11 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue } if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) { - $cleanup_log = CleanupDocker::run(server: $this->server); + $cleanup_log = CleanupDocker::run( + server: $this->server, + deleteUnusedVolumes: $this->deleteUnusedVolumes, + deleteUnusedNetworks: $this->deleteUnusedNetworks + ); $message = 'Docker cleanup job executed successfully, but no disk usage could be determined.'; $this->execution_log->update([ @@ -81,7 +94,11 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue } if ($this->usageBefore >= $this->server->settings->docker_cleanup_threshold) { - $cleanup_log = CleanupDocker::run(server: $this->server); + $cleanup_log = CleanupDocker::run( + server: $this->server, + deleteUnusedVolumes: $this->deleteUnusedVolumes, + deleteUnusedNetworks: $this->deleteUnusedNetworks + ); $usageAfter = $this->server->getDiskUsage(); $diskSaved = $this->usageBefore - $usageAfter; diff --git a/app/Jobs/ServerResourceManager.php b/app/Jobs/ServerResourceManager.php index cdf8efc56..8a4b55a3f 100644 --- a/app/Jobs/ServerResourceManager.php +++ b/app/Jobs/ServerResourceManager.php @@ -133,7 +133,7 @@ class ServerResourceManager implements ShouldQueue $dockerCleanupFrequency = VALID_CRON_STRINGS[$dockerCleanupFrequency]; } if ($this->shouldRunNow($dockerCleanupFrequency, $serverTimezone)) { - DockerCleanupJob::dispatch($server); + DockerCleanupJob::dispatch($server, false, $server->settings->delete_unused_volumes, $server->settings->delete_unused_networks); } // Dispatch ServerPatchCheckJob if due (weekly) diff --git a/app/Livewire/Project/Shared/Danger.php b/app/Livewire/Project/Shared/Danger.php index 7da48f9fb..94a4c161c 100644 --- a/app/Livewire/Project/Shared/Danger.php +++ b/app/Livewire/Project/Shared/Danger.php @@ -99,10 +99,10 @@ class Danger extends Component $this->resource->delete(); DeleteResourceJob::dispatch( $this->resource, - $this->delete_configurations, $this->delete_volumes, - $this->docker_cleanup, - $this->delete_connected_networks + $this->delete_connected_networks, + $this->delete_configurations, + $this->docker_cleanup ); return redirect()->route('project.resource.index', [ diff --git a/app/Livewire/Server/DockerCleanup.php b/app/Livewire/Server/DockerCleanup.php index d3378d63f..c97a8f2c9 100644 --- a/app/Livewire/Server/DockerCleanup.php +++ b/app/Livewire/Server/DockerCleanup.php @@ -71,7 +71,7 @@ class DockerCleanup extends Component public function manualCleanup() { try { - DockerCleanupJob::dispatch($this->server, true); + DockerCleanupJob::dispatch($this->server, true, $this->deleteUnusedVolumes, $this->deleteUnusedNetworks); $this->dispatch('success', 'Manual cleanup job started. Depending on the amount of data, this might take a while.'); } catch (\Throwable $e) { return handleError($e, $this); From 0998be35036a69110e44ee5fa6ef13d9b0389889 Mon Sep 17 00:00:00 2001 From: Lucas Castelo Date: Mon, 4 Aug 2025 21:22:07 +0200 Subject: [PATCH 015/367] feat(services): add Chroma service (#6201) --- public/svgs/chroma.svg | 13 +++++++++++++ templates/compose/chroma.yaml | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 public/svgs/chroma.svg create mode 100644 templates/compose/chroma.yaml diff --git a/public/svgs/chroma.svg b/public/svgs/chroma.svg new file mode 100644 index 000000000..930288fbf --- /dev/null +++ b/public/svgs/chroma.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/templates/compose/chroma.yaml b/templates/compose/chroma.yaml new file mode 100644 index 000000000..11029113b --- /dev/null +++ b/templates/compose/chroma.yaml @@ -0,0 +1,20 @@ +# documentation: https://cookbook.chromadb.dev/ +# slogan: Chroma is the open-source search and retrieval database for AI applications. +# tags: ai,vector-database,semantic-search,machine-learning,bm25,embeddings,llm +# logo: svgs/chroma.svg +# port: 8000 + +services: + chromadb: + image: chromadb/chroma:1.0.15 + volumes: + - chroma-data:/data + environment: + - SERVICE_FQDN_CHROMA_8000 + - IS_PERSISTENT=TRUE + - PERSIST_DIRECTORY=/data + healthcheck: + test: [ "CMD", "/bin/bash", "-c", "cat < /dev/null > /dev/tcp/localhost/8000" ] + interval: 30s + timeout: 10s + retries: 3 From 1d97e3549452fed8d888f5786d8731a8e2ab4017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Andrade=20Guzm=C3=A1n?= Date: Mon, 4 Aug 2025 15:33:17 -0400 Subject: [PATCH 016/367] chore(service): add `NOT_SECURED` env to Postiz (#6243) --- templates/compose/postiz.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/compose/postiz.yaml b/templates/compose/postiz.yaml index 2631e16fe..5e4fbcf2f 100644 --- a/templates/compose/postiz.yaml +++ b/templates/compose/postiz.yaml @@ -77,6 +77,7 @@ services: - NEXT_PUBLIC_POLOTNO=${NEXT_PUBLIC_POLOTNO} - IS_GENERAL=true - NX_ADD_PLUGINS=${NX_ADD_PLUGINS:-false} + - NOT_SECURED=${NOT_SECURED:-false} # Payment Settings - FEE_AMOUNT=${FEE_AMOUNT:-0.05} From af22514cad4e6355d4e7a646a296a84c78a0a898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Gon=C3=A7alves=20de=20Andrade?= <0vetor0@gmail.com> Date: Mon, 4 Aug 2025 16:36:38 -0300 Subject: [PATCH 017/367] chore(service): improve evolution-api environment variables (#6283) --- templates/compose/evolution-api.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/compose/evolution-api.yaml b/templates/compose/evolution-api.yaml index 87db576f9..ad5718e9d 100644 --- a/templates/compose/evolution-api.yaml +++ b/templates/compose/evolution-api.yaml @@ -105,10 +105,11 @@ services: - WEBHOOK_EVENTS_ERRORS_WEBHOOK=${WEBHOOK_EVENTS_ERRORS_WEBHOOK:-} - 'CONFIG_SESSION_PHONE_CLIENT=${CONFIG_SESSION_PHONE_CLIENT:-Evolution API V2}' - CONFIG_SESSION_PHONE_NAME=${CONFIG_SESSION_PHONE_NAME:-Chrome} - - CONFIG_SESSION_PHONE_VERSION=${CONFIG_SESSION_PHONE_VERSION:-2.3000.1020885143} - QRCODE_LIMIT=${QRCODE_LIMIT:-30} - OPENAI_ENABLED=${OPENAI_ENABLED:-true} - DIFY_ENABLED=${DIFY_ENABLED:-true} + - FLOWISE_ENABLED=${FLOWISE_ENABLED:-true} + - N8N_ENABLED=${N8N_ENABLED:-true} - TYPEBOT_ENABLED=${TYPEBOT_ENABLED:-true} - TYPEBOT_API_VERSION=${TYPEBOT_API_VERSION:-latest} - CHATWOOT_ENABLED=${CHATWOOT_ENABLED:-true} From c483f1bdd7d2fb2a6a4197c08f5292cc0e26fa78 Mon Sep 17 00:00:00 2001 From: Slawa Gladkov Date: Mon, 4 Aug 2025 21:48:48 +0200 Subject: [PATCH 018/367] chore(service): update Langfuse template to v3 (#6301) --- public/svgs/langfuse.png | Bin 9788 -> 0 bytes public/svgs/langfuse.svg | 9 ++ templates/compose/langfuse.yaml | 151 +++++++++++++++++++++++++++----- 3 files changed, 136 insertions(+), 24 deletions(-) delete mode 100644 public/svgs/langfuse.png create mode 100644 public/svgs/langfuse.svg diff --git a/public/svgs/langfuse.png b/public/svgs/langfuse.png deleted file mode 100644 index 8dec0fe4ae1dd9c91ea9e8e0d6ed89227fef4143..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9788 zcmd_wAZW3k3)?E008jSRF(8z`rrR+SQsz0+N35A0ANQ`Q<67; z@IC#hYV4cQOJVjW z=+2CTZLHv@3x3hp2Vr z-53A{fC-BAf5EkWGC{Z~IR~wS-b_spN7U%U<0LkTXagX4-OYgU1$tb- z16jB35xt=ZhbU;t7?5N3KRITLU()PUU)H{XuWN!E5CO3ERm`gI@iI>YLC@&m z+P#l=WRJ>>eM&KYT~u{Dq;pFh*z0S6V_a1pO)5AFNMbjVB3$A##fYVRjee4S$@{W3+^?4$Yz6pafw`BEI^P23RoW1HhRA$`Z771WZ6u5&SI!bVYLX zSta>!1tRnf!rv&$IjHw#G17bk-tZobp#g~@dFjglN|Ts7vLM-*)sjkn0^vtjROItd zLXJ>&xooh|%v6K?g$bqhQb2}D&RA{V*ON8yb+y|Gi*wEy9bg34Ti2(!7Ux5agS+g) zYL9Moh&HuE{H*8bq$r+In>d{k=KuwKz`QvQ&OgJ0r(QqWT)*gzY;@f8T5>%(f@npCbxIjY{QPjA&4ywS%-jr&|(B0`;Vd(k>k|j3So`zx85v3bG+|r&){On%mDok1P8jHl3TSS0b0H`5mw-O!s)0j3C zyN5I_tpm>C?Ji)qPi=?r9sla`*WPCI;Zr^lrebww&>p9FNGieAao0P%Wyugznh8|c z2zUHKR>Ti3Ze+`ukejjIN*|=*9cP%_PU+Xlcqz=qR6J>cjb+01gHS0r#~c8+Axv0* z8mVN+Ht4XCAW9I?Srg+yL+$>hmxe>5V4{O)P>?bL^6^%_oHqWq+OJ`XKefstp_Es_ zvQe40jSVHSL=Qh3fnSVM|I4WOOj?tuWz7Ukjwjc1`q2kEK&2Y8Kq-s3rgkN=j7O2# zeZ_VNpDL%bSHXLzgwl#C3(f7tUN(oKdtE|io0q%~1gVCesqX$wMEE%RD04D|{Eqqs zgr@CV*h)#@bu$8fk%S(*L58Sb(@0Pl8*OS#A$dY|R(V_wrIg-vp?v6eI5`#uQ~|+8 z-&;Zi4S)l2A&`*tK`5-gkxYeOpxjygG+zl7mxj7-dTW3=bdcq4Kf54KihTyb4OCa= zWj+tYQR32TkHFRUAq_f-66MNquL#K=R(E%LgAna?c~H3b_L{Vb%)9g3oBjn%Z1geU z)~sm>zo)Rx?SmRj^z@&8`zhq#Qe12dfr!l$ysLHV-qsp(P>l*Lo3zVG*bB)nFu2P8 z?apCXR1i0o(MN|MAA}fg)2q%o<V|GNqUIHr;jn6hW7pC+OPWg0TI{71@Z~_K?ENI5| zyJR-0q(G_06pF8J&;NmdfhY!ak6>*vanO6G@}_BG3#56Y4EU>W(zt*EIKb3#K7D6I zVyTCGOv}W^zUCFPVUf*f7v4d^X`zKV)4G$2cL%QDGzo~I6DK2c%HFx|{x&jrL_=Mm zI2}T#8;fd_;kKGtA9A$#mn4D5AVGw!jGB>Z*(|F4P&>GVcQ+>Uyl?upAadE;3HLDv4i=^07zQV zj>$%;WLQXgjG?-*N8!Dyx(29)HE8EevL%7Fj6hj#UcTw*IAMBKR^9UeZsd zKUmAULllR;H#om|N33fD<8jek{WLT`KT$fI(|5Hlt>Rty4szg7s4#Ce1}dTPzgPBR>LKCm;Eh)%{okTklV!w_vmbww4VlroHO(<2WU5-m%|)QqZa7qADqSPc|)zuP;K57F~=oLaEF`)nVt%k6=So2EfS0* zc)f(Rfy+UlYd9*u!?;qII&smWGpx@=Ldq!6=e zRi0b?W!~B!*Y&cC#zngCNn)T?3E3bUeXQWd%x2ek@(K6mUnx(S3!_6c;RtD?MX?&^ zPlKOcJMC2u6~tu@DHG4JGQ`1IP7k%$kImCp#VrlN8m6z`YY^9yv!F8nK*W~Z#;sRyPmyhc8GTqb} zqEDEA)zX*o^&M=}+HQ6eA=F>rJ?U8M2`(8O|E~4*g@AP%e zrtl7){dwg+;XA|G4*uMgV-TplkeHBlv^jT5oywy|AQj zKXC8CgiQE+uXAy??Z#G-#mIM%ruY*X^z~x}$DI8n1dT-deG>5>e;=Miu7oH3K=PcFM%){0hRUr`tO^CFSX)vb`wSQLXCN4g{$#GP( z;U{)xq31T&G%PIY7DsWCm_SssmN%UzLQL^b!(2-{c>}vuv};gqJ=6g1HMow0@ozdP z{pyN8{jg)UOBpLLqc(5{-&6#=V9>E$E0j|_{+>WXbEUheMtq;>rB#sPYk`ay4_4a@RfWP4f@(z@4xc+}la2J{%;v#1x1jYmE0$L|4 zDPh>^_46X9Ct*>>Je9)Y2BE(T$#HDjROY{km;UYQ5G}w#L%TX{2)^xTlW=xVNx3a{ z#)9Iy@tXW|5gTuAv3|NvD2S76Aml{{yBY~jngYJ2f0EN9BChndV+|q2r!(yQVELg1 zVH@>v1AV#|_j}Y1fxAYNBp16DuADU00C>-LKw5F=`h7uct;X@) zc+JF|mn)P&2TO%ReGh75H%$QF4m}cgp?PlNsd8EK-%PAfgpF9yNG3dMU5$CuxeDU*UhhC#k>kW^L4EAbjxa&>8yYYQ~Ob59hMX8bxHDfdSo@ z3RFjJpY(o^MO_?ImH@hN*K-Vnz;>W6n47Xc&6wOc_sNKg_}hp z_5u>(o+UbG_4;%@KA_mU#Z8#h>=XvP$8718TQNO?|61CUpiwYCOixB+7bf8!Va(_e zhS|6twh{_f*^y`DH>dE1Jn0-i5fTR;erD7ouuWx$Tb}a{lD?9QVON|t%ykPZ>!|un zeGnue)~#gSbk?*gGKEE5i{`SBUd2DI`GWve2z^X}OdKc4GrS4FN3k&V3v1n@_hERC z&qT6w!RvPJm9B0(s{()yD4{k^TX|%%I{N-2OR0+b)L{dxLQ#sXVwPCAHG9SFEaj4m zUggUrB7|8pI9ByhK#GvPht$rfFUKJak@A6YqKb^I?-;eN=-cih=crKgY^3I%M}Nl0 z&&IHKP`&zh6_&Bbn!CrJ|Z_P%oL0EUXd2JT4)Ai=`;4}$qFpJQGKs| z+F=S#C`iY*Wp=_2vRA@C^{;zNM$%^dKu_#w{sWcjb#g!gQ4|z2#w`B!RY59Vn9LI} zaX3lG4=WYU_hP2nr6NH7zi4mNM75=#(?)D8MK*Bbn-S?GqP@}f9e4@9; zG^E=_p|nMp9N$=Ko_{|_;4yd~ol>4~3J!XOww=Ruo!sr96eMHW z1GHQ=IIPk-U^H(*J95MXuO<_Aw4%Oj;$QimyoK>hS>Br)G$vfuM_^Tu1#;%vSS^S) z=}x|!iuf=ycaump!?NlE|268@Z&4?;kEn_>g$#V`3!jwGZECqC@7>&Dz-M7MOCmAV zYR>rdlqZ%OJ6f1}$p?y|ouQ-hfhq7!4%YuoUrpypns2BP*2y9Ka%R^V4NS#a3-l&b zAJEtgFf~JIj7;AS5?Iiy`P;#c2T~Ow{tccSse=)(6>$dC zFz)+|%Ay(k@2IqreAC6PhSGcz24&OIB5?&EWrDs8e6Vq%?-EA$6O`p;4$vOn&Cz=e zPaL$oWn6mfuhmOo zoQW4&CpeJga*GMYViH!fB5f+_2!boC3A^@uHBD{Y&LX-ZW(V>;!0SV=PG!L6iyy`={8 zgCoa$@AGxu(xZ9xPwBi3Kvnu9m7i_84RLuo{+<8wZ{op^0+Q~Ygnvw|dfN}yok7sI zw-^li!CAZyY^reFNnTmo6a_7rGjp-_-KEWU`=c3YekC}QgM?-ecYp`_{U zw3245Yn0W{B;6HjHhdq)bHh86g&myJE(z0W-*AQyU$xmoKF;$vCuyF1%TpWE4+s4v zo~U}hHxr3=8-Q}0upuTJ7EanAtTvlZN+H)gZ&D2u^UvNmY-uYX9(N(QiSb0^i6Nxk zq>biS=o%GVf?Ne@;aW8@<(LP5Bf7-4Mg&Up$bYx`p?dLv5c#1^XFXS8y|Kq9fDDXv z>Jyy<4y``?k`k)5n92dWhM7j>E`-~%{&4w8+6=va8inW{0F=@SM221Vn}*(?Z$SUC zH2wFYg=oWZABjTXJlwfMY3x}A+MqXy9@juSd+yLd`D_@V_=EM&;!K}v@1zN{fBc)5NfbG}1dnfr;B7|017J|SQ*9tGd9AHe?& z8^NHe@_;)E#kkF1h*;Wm(uG-ZKKL{#~a&{7{%y>E&?1GS0lMq5elHh%Q3-ryvb?V=rIW<^Y)G-v3G;A^vM(ynJp(!%`a zx~$o*`3Ul+_-4IvBEy{(U$C$$3}7_Jj9+=_T$H|_oq;-IC^q+($pSGcT_Vrmn)wh7z=F@1$4FSV}y01cFHh6}H zIplHH>07?L+Ips+=!bW|P;PPtin0;e+1ulWr4od7?qKJY!O**3d(yF>K5c1Fip=O! zy8ZZ>+mEmbyrEV7!^k0D`yUn-bOxd~E^$Z5g1(nv|D8Z)B6c-6I>ZL2hbej(jpc_UHNPpb3iQzZ z{>l&YBEq<4odjFbI6T9_J9kF<`7OJa=-3At`_6SSVlFvZG0Qp9eehv-a z8#2+%vDNs{tT8FEXt9&PY=zUm+DNt&^PKYZxIc`e zIjX;+z<*g2V{X0$+^H-CQ5-XfA;=l_% znXR#m633e_d49zF>?KlU_jyXFZVS29)U1qXLuZ$BpN>5F=%M6mFKh1BpNlt2XcrFS zIKzzYh!&*E_yP>rcwCgTb*L;kFzK$Prj~%{xiAl&r?HB-)ysw-uSS2CL2`>^(_&6! zuv<3rbT}@kdR{RYpbbxQPO30qUM41xA3dlSg0HGJl+{GpMo35+P9@unODK zn)8L_7r*fr@_<2+B|+^ghyCTLnAQ^Jmft!LY(V!in(UEfRNswBe^4<^sasO&(FM>h z??@NmyTqWjwDHlaW|T4E)aovfC%{k4%sa`o1Cu6-KP%6M7DviHD#9d_Z}En)k^Y9{ z31>Vi)bAaa=s)HQSEmH^%pA~ha!5r)MC($sf{+N_5-km(!5VUoML=ZyXVNsdd) zTphgK5IfpTvrwyYvr{_3DuGfg+PwbEPa{2PMDmrt1oB&?{%`Z`*=F=R>OO@lM&$*b zX(hB;mF!Km>0Y5q82i8?%L|>+UEiMx@zl}>WxuGQ`2^STdIoq-I`jtWcG}8dKNOAR z1L2k7et91BCgg4A8Lh1xvNALGanKReF<_@?GPCLQqCA{U*uI&?Bn^38!G-(%nNzj- z4Z*Pn>kZCO=h4GPT2|C#g4DNKzQYk6xQgwm_B3fP^RE*^Kn|vL^mRjn2!2a?dXwar zzb73gQsWMnMgAkhWpHM_jhUZT1jdFBMbHk1A(j^);-uHkfGmpmIA!MYX_sdL9?NhG z2y#jOlJ5{^IVf!oAU2vWGrp@vU-R1>vqm(qB*n`6Goh2*W$%cc zC~xjF^!jeaJG)Eqo28*MH%J&s5{x@JC>5F0uDBW7u+L{sVY$|F44oUMfu;q7gyh=& z<`_>en(B zoZ(GZoc#<>7|RwXK7_MT%0}nPgBM?vC=>`t|Deak6-w6?fD8;ze{Fw+6jqSfzl{}h z!}hZ7e6nZ#zML_Nxjf=d^M&!L->~3b9P(uiZGF&NCJ%rNNV?;AWS|%4<*9O8k0&Gp zHK}p1Q}{XT{56LtZ1GS;Ni{B*UPPK$`dEG%@-!UCCEuo+)EKLFo|_lr`{cZdsMSx5 zHha;w9nKJ6mhtiHM94;9kdlh+eIU+sh493*4j{qTgT$D$x9@;KXqv(>pi4vcWxJk0 z$-qq7NaWzEYB||>H7zZ$bRd*;+ri@L{%|o9f2zs4{ZTsph?C;Lg0g{N<|73K6Ibk3 zO^}$KD}-_m|Asoh<*CO{i7*fVR-=>%E>j)xwZt4cA(~TJ<>=2AB>+4lglJ|~p1iLV z{ggjcKuS{1VYZK_#WQlQvLz6Z&DpF=rv)o->So&xMMriDD6aR`fpa@WwOH94Vj2Y1ZD`NE9Jb;Kw}d1I zp7sn)MqD*sXAvEufeC_s_(ASoG1Kf?>kc6J!&RA$%Q)n`J$_+;bHFFk%&P4X;jgZxZE*{A5 z#af=ez$91y$TR}G(*ghZN>A9xBs7hPJC$|fBehX(QUta@hGFaKy0S%Ws^Ndv+aPm| z$m}_z!{3LZ8Ldd+jRQ^TqR0%*_DOSCej#2S(B(|w_=XYPl3BAFWF_*k+6?#w@$<0H zi=;B$=nx#<+dfdN}bx7*~OSh zmX8jO;keI;LgQ*AgjE3Lj z%Be8)%+N?GF)mEZ<82SXC!|^;sMTIzn&wlsM$cyz7^c`AIiF}a;*njjaA|kX2~|zd zpmErW9QvmqnTWO)k>SMZ@dllIscG8%W-&3HCx##HG87vEHeF?-#goUgEP^7(DZ{xN ziJVf!u}-!u0tQkKBD=T}$wHMfh6fhV-t)5zR19!tMb}ph8{@vDbH3ukyg_KHeazZ+ z+x!7+eqBQ9)skOxT-=*VTww>2Nh(6V`npM(@U$w@+k`?zFk4^` zfvVPHHamfKlLc`nXd8))?5I=9(8^^8{|>`9vqA-n2VtJ)MoHls0{!TDhtV~G;4%je z36gkj->^8qTx1*H36{JF8FDcu;)$7vzfXyLAqC(sL|8B7Jo$(k^aCLEeP|W>IyH)S z`}lJhgvPhsT+HFFNUozU??Z*dM|%K>q}xQ$eCbvBoOfrLN0$QZlko-Rd6Deo8v18} z{;;T0tOIl)?{b_s5FBunOPY}iTPCi|3%q&Mg;(#pBHkjZi|vh(q3ML0o0V= KDb*-ghx{KZU0w?S diff --git a/public/svgs/langfuse.svg b/public/svgs/langfuse.svg new file mode 100644 index 000000000..b04e07490 --- /dev/null +++ b/public/svgs/langfuse.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/templates/compose/langfuse.yaml b/templates/compose/langfuse.yaml index 125a85351..5797dd4a1 100644 --- a/templates/compose/langfuse.yaml +++ b/templates/compose/langfuse.yaml @@ -1,30 +1,83 @@ # documentation: https://langfuse.com/docs # slogan: Langfuse is an open-source LLM engineering platform that helps teams collaboratively debug, analyze, and iterate on their LLM applications. # tags: ai, qdrant, weaviate, langchain, openai, gpt, llm, lmops, langfuse, llmops, tracing, observation, metrics -# logo: svgs/langfuse.png +# logo: svgs/langfuse.svg # port: 3000 +x-app-env: &app-env + - NEXTAUTH_URL=${SERVICE_FQDN_LANGFUSE} + - DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-langfuse} + - SALT=${SERVICE_PASSWORD_SALT} + - ENCRYPTION_KEY=${SERVICE_PASSWORD_64_LANGFUSE} + - TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-true} + - LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false} + - CLICKHOUSE_MIGRATION_URL=clickhouse://clickhouse:9000 + - CLICKHOUSE_URL=http://clickhouse:8123 + - CLICKHOUSE_USER=clickhouse + - CLICKHOUSE_PASSWORD=$SERVICE_PASSWORD_CLICKHOUSE + - CLICKHOUSE_CLUSTER_ENABLED=false + - LANGFUSE_USE_AZURE_BLOB=false + - LANGFUSE_S3_EVENT_UPLOAD_BUCKET=langfuse + - LANGFUSE_S3_EVENT_UPLOAD_REGION=auto + - LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=minio + - LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=$SERVICE_PASSWORD_MINIO + - LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=http://minio:9000 + - LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE=true + - LANGFUSE_S3_EVENT_UPLOAD_PREFIX=events/ + - LANGFUSE_S3_MEDIA_UPLOAD_BUCKET=langfuse + - LANGFUSE_S3_MEDIA_UPLOAD_REGION=auto + - LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID=minio + - LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY=$SERVICE_PASSWORD_MINIO + - LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT=http://localhost:9090 + - LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE=true + - LANGFUSE_S3_MEDIA_UPLOAD_PREFIX=media/ + - LANGFUSE_S3_BATCH_EXPORT_ENABLED=false + - LANGFUSE_S3_BATCH_EXPORT_BUCKET=langfuse + - LANGFUSE_S3_BATCH_EXPORT_PREFIX=exports/ + - LANGFUSE_S3_BATCH_EXPORT_REGION=auto + - LANGFUSE_S3_BATCH_EXPORT_ENDPOINT=http://minio:9000 + - LANGFUSE_S3_BATCH_EXPORT_EXTERNAL_ENDPOINT=http://localhost:9090 + - LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID=minio + - LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY=$SERVICE_PASSWORD_MINIO + - LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE=true + - LANGFUSE_INGESTION_QUEUE_DELAY_MS=1 + - LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS=1000 + - REDIS_HOST=redis + - REDIS_PORT=6379 + - REDIS_AUTH=$SERVICE_PASSWORD_REDIS + - REDIS_TLS_ENABLED=false + - REDIS_TLS_CA=/certs/ca.crt + - REDIS_TLS_CERT=/certs/redis.crt + - REDIS_TLS_KEY=/certs/redis.key + - EMAIL_FROM_ADDRESS= + - SMTP_CONNECTION_URL= + - NEXTAUTH_SECRET=${SERVICE_BASE64_NEXTAUTHSECRET} # + - AUTH_DISABLE_SIGNUP=${AUTH_DISABLE_SIGNUP:-true} + - HOSTNAME=${HOSTNAME:-0.0.0.0} + - LANGFUSE_INIT_ORG_ID=${LANGFUSE_INIT_ORG_ID:-my-org} + - LANGFUSE_INIT_ORG_NAME=${LANGFUSE_INIT_ORG_NAME:-My Org} + - LANGFUSE_INIT_PROJECT_ID=${LANGFUSE_INIT_PROJECT_ID:-my-project} + - LANGFUSE_INIT_PROJECT_NAME=${LANGFUSE_INIT_PROJECT_NAME:-My Project} + - LANGFUSE_INIT_USER_EMAIL=${LANGFUSE_INIT_USER_EMAIL:-admin@example.com} + - LANGFUSE_INIT_USER_NAME=${LANGFUSE_INIT_USER_NAME:-Admin} + - LANGFUSE_INIT_USER_PASSWORD=${SERVICE_PASSWORD_LANGFUSE} + services: langfuse: - image: langfuse/langfuse:2 + image: docker.io/langfuse/langfuse:3 + restart: always + depends_on: &langfuse-depends-on + postgres: + condition: service_healthy + minio: + condition: service_healthy + redis: + condition: service_healthy + clickhouse: + condition: service_healthy environment: - - SERVICE_FQDN_LANGFUSE_3000 - - DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-langfuse} - - DIRECT_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-langfuse} - - SALT=$SERVICE_PASSWORD_SALT - - AUTH_DISABLE_SIGNUP=${AUTH_DISABLE_SIGNUP:-false} - - NEXTAUTH_URL=$SERVICE_FQDN_LANGFUSE_3000 - - NEXTAUTH_SECRET=${SERVICE_BASE64_64_NEXTAUTHSECRET} - - TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-false} - - LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false} - - HOSTNAME=${HOSTNAME:-0.0.0.0} - - LANGFUSE_INIT_USER_NAME=${LANGFUSE_INIT_USER_NAME:-Admin} - - LANGFUSE_INIT_USER_EMAIL=${LANGFUSE_INIT_USER_EMAIL:-admin@example.com} - - LANGFUSE_INIT_USER_PASSWORD=${SERVICE_PASSWORD_LANGFUSE} - - LANGFUSE_INIT_ORG_ID=${LANGFUSE_INIT_ORG_ID:-my-org} - - LANGFUSE_INIT_ORG_NAME=${LANGFUSE_INIT_ORG_NAME:-My Org} - - LANGFUSE_INIT_PROJECT_ID=${LANGFUSE_INIT_PROJECT_ID:-my-project} - - LANGFUSE_INIT_PROJECT_NAME=${LANGFUSE_INIT_PROJECT_NAME:-My Project} + <<: *app-env + SERVICE_FQDN_LANGFUSE_3000: ${SERVICE_FQDN_LANGFUSE_3000} healthcheck: test: - CMD @@ -35,17 +88,21 @@ services: interval: 5s timeout: 5s retries: 3 - depends_on: - postgres: - condition: service_healthy + langfuse-worker: + image: langfuse/langfuse-worker:3 + restart: always + environment: + <<: *app-env + depends_on: *langfuse-depends-on postgres: - image: "postgres:16-alpine" + image: postgres:17-alpine + restart: always environment: - POSTGRES_DB=${POSTGRES_DB:-langfuse} - POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES - POSTGRES_USER=$SERVICE_USER_POSTGRES volumes: - - "pg-data:/var/lib/postgresql/data" + - langfuse_postgres_data:/var/lib/postgresql/data healthcheck: test: - CMD-SHELL @@ -53,3 +110,49 @@ services: interval: 5s timeout: 5s retries: 10 + redis: + image: redis:8 + restart: always + command: ["sh", "-c", "redis-server --requirepass \"$SERVICE_PASSWORD_REDIS\""] + volumes: + - langfuse_redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "-a", "$SERVICE_PASSWORD_REDIS", "PING"] + interval: 3s + timeout: 10s + retries: 10 + minio: + image: minio/minio:latest + restart: always + entrypoint: sh + # create the 'langfuse' bucket before starting the service + command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data' + environment: + - MINIO_ROOT_USER=minio + - MINIO_ROOT_PASSWORD=$SERVICE_PASSWORD_MINIO + - MINIO_BROWSER=off + volumes: + - langfuse_minio_data:/data + healthcheck: + test: ["CMD", "mc", "ready", "local"] + interval: 1s + timeout: 5s + retries: 5 + start_period: 1s + clickhouse: + image: clickhouse/clickhouse-server:latest + restart: always + user: "101:101" + environment: + - CLICKHOUSE_DB=default + - CLICKHOUSE_USER=clickhouse + - CLICKHOUSE_PASSWORD=$SERVICE_PASSWORD_CLICKHOUSE + volumes: + - langfuse_clickhouse_data:/var/lib/clickhouse + - langfuse_clickhouse_logs:/var/log/clickhouse-server + healthcheck: + test: wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1 + interval: 5s + timeout: 5s + retries: 10 + start_period: 1s From dcf3b43b30589e855cf5d5296ea18454fef28773 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:10:47 +0200 Subject: [PATCH 019/367] refactor(service): improve langfuse - remove minio - adjust ENVs - autogenerate more stuff - formatting --- templates/compose/langfuse.yaml | 120 +++++++++++++------------------- 1 file changed, 48 insertions(+), 72 deletions(-) diff --git a/templates/compose/langfuse.yaml b/templates/compose/langfuse.yaml index 5797dd4a1..ddfa8b944 100644 --- a/templates/compose/langfuse.yaml +++ b/templates/compose/langfuse.yaml @@ -6,52 +6,48 @@ x-app-env: &app-env - NEXTAUTH_URL=${SERVICE_FQDN_LANGFUSE} - - DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-langfuse} + - DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-langfuse-db} - SALT=${SERVICE_PASSWORD_SALT} - ENCRYPTION_KEY=${SERVICE_PASSWORD_64_LANGFUSE} - - TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-true} + - TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-false} - LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false} - CLICKHOUSE_MIGRATION_URL=clickhouse://clickhouse:9000 - CLICKHOUSE_URL=http://clickhouse:8123 - - CLICKHOUSE_USER=clickhouse - - CLICKHOUSE_PASSWORD=$SERVICE_PASSWORD_CLICKHOUSE + - CLICKHOUSE_USER=${SERVICE_USER_CLICKHOUSE} + - CLICKHOUSE_PASSWORD=${SERVICE_PASSWORD_CLICKHOUSE} - CLICKHOUSE_CLUSTER_ENABLED=false - - LANGFUSE_USE_AZURE_BLOB=false - - LANGFUSE_S3_EVENT_UPLOAD_BUCKET=langfuse - - LANGFUSE_S3_EVENT_UPLOAD_REGION=auto - - LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=minio - - LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=$SERVICE_PASSWORD_MINIO - - LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=http://minio:9000 - - LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE=true - - LANGFUSE_S3_EVENT_UPLOAD_PREFIX=events/ - - LANGFUSE_S3_MEDIA_UPLOAD_BUCKET=langfuse - - LANGFUSE_S3_MEDIA_UPLOAD_REGION=auto - - LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID=minio - - LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY=$SERVICE_PASSWORD_MINIO - - LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT=http://localhost:9090 - - LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE=true - - LANGFUSE_S3_MEDIA_UPLOAD_PREFIX=media/ - - LANGFUSE_S3_BATCH_EXPORT_ENABLED=false - - LANGFUSE_S3_BATCH_EXPORT_BUCKET=langfuse - - LANGFUSE_S3_BATCH_EXPORT_PREFIX=exports/ - - LANGFUSE_S3_BATCH_EXPORT_REGION=auto - - LANGFUSE_S3_BATCH_EXPORT_ENDPOINT=http://minio:9000 - - LANGFUSE_S3_BATCH_EXPORT_EXTERNAL_ENDPOINT=http://localhost:9090 - - LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID=minio - - LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY=$SERVICE_PASSWORD_MINIO - - LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE=true - - LANGFUSE_INGESTION_QUEUE_DELAY_MS=1 - - LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS=1000 + - LANGFUSE_USE_AZURE_BLOB=${LANGFUSE_USE_AZURE_BLOB:-false} + - LANGFUSE_S3_EVENT_UPLOAD_BUCKET=${LANGFUSE_S3_EVENT_UPLOAD_BUCKET:-langfuse} + - LANGFUSE_S3_EVENT_UPLOAD_REGION=${LANGFUSE_S3_EVENT_UPLOAD_REGION:-auto} + - LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=${LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID} + - LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=${LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY} + - LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=${LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT} + - LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE=${LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE:-true} + - LANGFUSE_S3_EVENT_UPLOAD_PREFIX=${LANGFUSE_S3_EVENT_UPLOAD_PREFIX:-events/} + - LANGFUSE_S3_MEDIA_UPLOAD_BUCKET=${LANGFUSE_S3_MEDIA_UPLOAD_BUCKET:-langfuse} + - LANGFUSE_S3_MEDIA_UPLOAD_REGION=${LANGFUSE_S3_MEDIA_UPLOAD_REGION:-auto} + - LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID=${LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID} + - LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY=${LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY} + - LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT=${LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT} + - LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE=${LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE:-true} + - LANGFUSE_S3_MEDIA_UPLOAD_PREFIX=${LANGFUSE_S3_MEDIA_UPLOAD_PREFIX:-media/} + - LANGFUSE_S3_BATCH_EXPORT_ENABLED=${LANGFUSE_S3_BATCH_EXPORT_ENABLED:-false} + - LANGFUSE_S3_BATCH_EXPORT_BUCKET=${LANGFUSE_S3_BATCH_EXPORT_BUCKET:-langfuse} + - LANGFUSE_S3_BATCH_EXPORT_PREFIX=${LANGFUSE_S3_BATCH_EXPORT_PREFIX:-exports/} + - LANGFUSE_S3_BATCH_EXPORT_REGION=${LANGFUSE_S3_BATCH_EXPORT_REGION:-auto} + - LANGFUSE_S3_BATCH_EXPORT_ENDPOINT=${LANGFUSE_S3_BATCH_EXPORT_ENDPOINT} + - LANGFUSE_S3_BATCH_EXPORT_EXTERNAL_ENDPOINT=${LANGFUSE_S3_BATCH_EXPORT_EXTERNAL_ENDPOINT} + - LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID=${LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID} + - LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY=${LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY} + - LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE=${LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE:-true} + - LANGFUSE_INGESTION_QUEUE_DELAY_MS=${LANGFUSE_INGESTION_QUEUE_DELAY_MS:-1} + - LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS=${LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS:-1000} - REDIS_HOST=redis - REDIS_PORT=6379 - - REDIS_AUTH=$SERVICE_PASSWORD_REDIS - - REDIS_TLS_ENABLED=false - - REDIS_TLS_CA=/certs/ca.crt - - REDIS_TLS_CERT=/certs/redis.crt - - REDIS_TLS_KEY=/certs/redis.key - - EMAIL_FROM_ADDRESS= - - SMTP_CONNECTION_URL= - - NEXTAUTH_SECRET=${SERVICE_BASE64_NEXTAUTHSECRET} # + - REDIS_AUTH=${SERVICE_PASSWORD_REDIS} + - EMAIL_FROM_ADDRESS=${EMAIL_FROM_ADDRESS:-admin@example.com} + - SMTP_CONNECTION_URL=${SMTP_CONNECTION_URL:-} + - NEXTAUTH_SECRET=${SERVICE_BASE64_NEXTAUTHSECRET} - AUTH_DISABLE_SIGNUP=${AUTH_DISABLE_SIGNUP:-true} - HOSTNAME=${HOSTNAME:-0.0.0.0} - LANGFUSE_INIT_ORG_ID=${LANGFUSE_INIT_ORG_ID:-my-org} @@ -59,18 +55,15 @@ x-app-env: &app-env - LANGFUSE_INIT_PROJECT_ID=${LANGFUSE_INIT_PROJECT_ID:-my-project} - LANGFUSE_INIT_PROJECT_NAME=${LANGFUSE_INIT_PROJECT_NAME:-My Project} - LANGFUSE_INIT_USER_EMAIL=${LANGFUSE_INIT_USER_EMAIL:-admin@example.com} - - LANGFUSE_INIT_USER_NAME=${LANGFUSE_INIT_USER_NAME:-Admin} + - LANGFUSE_INIT_USER_NAME=${SERVICE_USER_LANGFUSE} - LANGFUSE_INIT_USER_PASSWORD=${SERVICE_PASSWORD_LANGFUSE} services: langfuse: - image: docker.io/langfuse/langfuse:3 - restart: always + image: langfuse/langfuse:3 depends_on: &langfuse-depends-on postgres: condition: service_healthy - minio: - condition: service_healthy redis: condition: service_healthy clickhouse: @@ -88,19 +81,19 @@ services: interval: 5s timeout: 5s retries: 3 + langfuse-worker: image: langfuse/langfuse-worker:3 - restart: always environment: <<: *app-env depends_on: *langfuse-depends-on + postgres: image: postgres:17-alpine - restart: always environment: - - POSTGRES_DB=${POSTGRES_DB:-langfuse} - - POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES - - POSTGRES_USER=$SERVICE_USER_POSTGRES + - POSTGRES_DB=${POSTGRES_DB:-langfuse-db} + - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} + - POSTGRES_USER=${SERVICE_USER_POSTGRES} volumes: - langfuse_postgres_data:/var/lib/postgresql/data healthcheck: @@ -110,10 +103,12 @@ services: interval: 5s timeout: 5s retries: 10 + redis: image: redis:8 - restart: always command: ["sh", "-c", "redis-server --requirepass \"$SERVICE_PASSWORD_REDIS\""] + environment: + - REDIS_PASSWORD=${SERVICE_PASSWORD_REDIS} volumes: - langfuse_redis_data:/data healthcheck: @@ -121,32 +116,14 @@ services: interval: 3s timeout: 10s retries: 10 - minio: - image: minio/minio:latest - restart: always - entrypoint: sh - # create the 'langfuse' bucket before starting the service - command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data' - environment: - - MINIO_ROOT_USER=minio - - MINIO_ROOT_PASSWORD=$SERVICE_PASSWORD_MINIO - - MINIO_BROWSER=off - volumes: - - langfuse_minio_data:/data - healthcheck: - test: ["CMD", "mc", "ready", "local"] - interval: 1s - timeout: 5s - retries: 5 - start_period: 1s + clickhouse: image: clickhouse/clickhouse-server:latest - restart: always user: "101:101" environment: - - CLICKHOUSE_DB=default - - CLICKHOUSE_USER=clickhouse - - CLICKHOUSE_PASSWORD=$SERVICE_PASSWORD_CLICKHOUSE + - CLICKHOUSE_DB=${CLICKHOUSE_DB:-default} + - CLICKHOUSE_USER=${SERVICE_USER_CLICKHOUSE} + - CLICKHOUSE_PASSWORD=${SERVICE_PASSWORD_CLICKHOUSE} volumes: - langfuse_clickhouse_data:/var/lib/clickhouse - langfuse_clickhouse_logs:/var/log/clickhouse-server @@ -155,4 +132,3 @@ services: interval: 5s timeout: 5s retries: 10 - start_period: 1s From 36b2bb56c5969ebe63a695094a6c5e7697f2a007 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:11:09 +0200 Subject: [PATCH 020/367] Update service-templates.json --- templates/service-templates.json | 57 ++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/templates/service-templates.json b/templates/service-templates.json index 6aa4e1eb8..c037a8889 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -144,7 +144,7 @@ "authentik": { "documentation": "https://docs.goauthentik.io/docs/installation/docker-compose?utm_source=coolify.io", "slogan": "An open-source Identity Provider, focused on flexibility and versatility.", - "compose": "c2VydmljZXM6CiAgYXV0aGVudGlrLXNlcnZlcjoKICAgIGltYWdlOiAnZ2hjci5pby9nb2F1dGhlbnRpay9zZXJ2ZXI6JHtBVVRIRU5USUtfVEFHOi0yMDI1LjYuM30nCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgY29tbWFuZDogc2VydmVyCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQVVUSEVOVElLU0VSVkVSXzkwMDAKICAgICAgLSAnQVVUSEVOVElLX1JFRElTX19IT1NUPSR7UkVESVNfSE9TVDotcmVkaXN9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fSE9TVD0ke1BPU1RHUkVTX0hPU1Q6LXBvc3RncmVzcWx9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX05BTUU9JHtQT1NUR1JFU19EQjotYXV0aGVudGlrfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1NFQ1JFVF9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X0FVVEhFTlRJS1NFUlZFUn0nCiAgICAgIC0gJ0FVVEhFTlRJS19FUlJPUl9SRVBPUlRJTkdfX0VOQUJMRUQ9JHtBVVRIRU5USUtfRVJST1JfUkVQT1JUSU5HX19FTkFCTEVEOi10cnVlfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19IT1NUPSR7QVVUSEVOVElLX0VNQUlMX19IT1NUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19QT1JUPSR7QVVUSEVOVElLX0VNQUlMX19QT1JUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19VU0VSTkFNRT0ke0FVVEhFTlRJS19FTUFJTF9fVVNFUk5BTUV9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1BBU1NXT1JEPSR7QVVUSEVOVElLX0VNQUlMX19QQVNTV09SRH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMUz0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMU30nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTD0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVD0ke0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fRlJPTT0ke0FVVEhFTlRJS19FTUFJTF9fRlJPTX0nCiAgICB2b2x1bWVzOgogICAgICAtICcuL21lZGlhOi9tZWRpYScKICAgICAgLSAnLi9jdXN0b20tdGVtcGxhdGVzOi90ZW1wbGF0ZXMnCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3Jlc3FsOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHJlZGlzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgYXV0aGVudGlrLXdvcmtlcjoKICAgIGltYWdlOiAnZ2hjci5pby9nb2F1dGhlbnRpay9zZXJ2ZXI6JHtBVVRIRU5USUtfVEFHOi0yMDI1LjYuM30nCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgY29tbWFuZDogd29ya2VyCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnQVVUSEVOVElLX1JFRElTX19IT1NUPSR7UkVESVNfSE9TVDotcmVkaXN9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fSE9TVD0ke1BPU1RHUkVTX0hPU1Q6LXBvc3RncmVzcWx9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX05BTUU9JHtQT1NUR1JFU19EQjotYXV0aGVudGlrfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1NFQ1JFVF9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X0FVVEhFTlRJS1NFUlZFUn0nCiAgICAgIC0gJ0FVVEhFTlRJS19FUlJPUl9SRVBPUlRJTkdfX0VOQUJMRUQ9JHtBVVRIRU5USUtfRVJST1JfUkVQT1JUSU5HX19FTkFCTEVEfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19IT1NUPSR7QVVUSEVOVElLX0VNQUlMX19IT1NUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19QT1JUPSR7QVVUSEVOVElLX0VNQUlMX19QT1JUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19VU0VSTkFNRT0ke0FVVEhFTlRJS19FTUFJTF9fVVNFUk5BTUV9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1BBU1NXT1JEPSR7QVVUSEVOVElLX0VNQUlMX19QQVNTV09SRH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMUz0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMU30nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTD0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVD0ke0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fRlJPTT0ke0FVVEhFTlRJS19FTUFJTF9fRlJPTX0nCiAgICB1c2VyOiByb290CiAgICB2b2x1bWVzOgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jaycKICAgICAgLSAnLi9tZWRpYTovbWVkaWEnCiAgICAgIC0gJy4vY2VydHM6L2NlcnRzJwogICAgICAtICcuL2N1c3RvbS10ZW1wbGF0ZXM6L3RlbXBsYXRlcycKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzcWw6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICBwb3N0Z3Jlc3FsOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNi1hbHBpbmUnCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1kICQke1BPU1RHUkVTX0RCfSAtVSAkJHtQT1NUR1JFU19VU0VSfScKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogICAgdm9sdW1lczoKICAgICAgLSAnYXV0aGVudGlrLWRiOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVNRTH0nCiAgICAgIC0gUE9TVEdSRVNfREI9YXV0aGVudGlrCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOmFscGluZScKICAgIGNvbW1hbmQ6ICctLXNhdmUgNjAgMSAtLWxvZ2xldmVsIHdhcm5pbmcnCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdyZWRpcy1jbGkgcGluZyB8IGdyZXAgUE9ORycKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogICAgdm9sdW1lczoKICAgICAgLSAncmVkaXM6L2RhdGEnCg==", + "compose": "c2VydmljZXM6CiAgYXV0aGVudGlrLXNlcnZlcjoKICAgIGltYWdlOiAnZ2hjci5pby9nb2F1dGhlbnRpay9zZXJ2ZXI6JHtBVVRIRU5USUtfVEFHOi0yMDI1LjYuNH0nCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgY29tbWFuZDogc2VydmVyCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQVVUSEVOVElLU0VSVkVSXzkwMDAKICAgICAgLSAnQVVUSEVOVElLX1JFRElTX19IT1NUPSR7UkVESVNfSE9TVDotcmVkaXN9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fSE9TVD0ke1BPU1RHUkVTX0hPU1Q6LXBvc3RncmVzcWx9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX05BTUU9JHtQT1NUR1JFU19EQjotYXV0aGVudGlrfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1NFQ1JFVF9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X0FVVEhFTlRJS1NFUlZFUn0nCiAgICAgIC0gJ0FVVEhFTlRJS19FUlJPUl9SRVBPUlRJTkdfX0VOQUJMRUQ9JHtBVVRIRU5USUtfRVJST1JfUkVQT1JUSU5HX19FTkFCTEVEOi10cnVlfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19IT1NUPSR7QVVUSEVOVElLX0VNQUlMX19IT1NUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19QT1JUPSR7QVVUSEVOVElLX0VNQUlMX19QT1JUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19VU0VSTkFNRT0ke0FVVEhFTlRJS19FTUFJTF9fVVNFUk5BTUV9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1BBU1NXT1JEPSR7QVVUSEVOVElLX0VNQUlMX19QQVNTV09SRH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMUz0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMU30nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTD0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVD0ke0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fRlJPTT0ke0FVVEhFTlRJS19FTUFJTF9fRlJPTX0nCiAgICB2b2x1bWVzOgogICAgICAtICcuL21lZGlhOi9tZWRpYScKICAgICAgLSAnLi9jdXN0b20tdGVtcGxhdGVzOi90ZW1wbGF0ZXMnCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3Jlc3FsOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHJlZGlzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgYXV0aGVudGlrLXdvcmtlcjoKICAgIGltYWdlOiAnZ2hjci5pby9nb2F1dGhlbnRpay9zZXJ2ZXI6JHtBVVRIRU5USUtfVEFHOi0yMDI1LjYuNH0nCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgY29tbWFuZDogd29ya2VyCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnQVVUSEVOVElLX1JFRElTX19IT1NUPSR7UkVESVNfSE9TVDotcmVkaXN9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fSE9TVD0ke1BPU1RHUkVTX0hPU1Q6LXBvc3RncmVzcWx9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX05BTUU9JHtQT1NUR1JFU19EQjotYXV0aGVudGlrfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1NFQ1JFVF9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X0FVVEhFTlRJS1NFUlZFUn0nCiAgICAgIC0gJ0FVVEhFTlRJS19FUlJPUl9SRVBPUlRJTkdfX0VOQUJMRUQ9JHtBVVRIRU5USUtfRVJST1JfUkVQT1JUSU5HX19FTkFCTEVEfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19IT1NUPSR7QVVUSEVOVElLX0VNQUlMX19IT1NUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19QT1JUPSR7QVVUSEVOVElLX0VNQUlMX19QT1JUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19VU0VSTkFNRT0ke0FVVEhFTlRJS19FTUFJTF9fVVNFUk5BTUV9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1BBU1NXT1JEPSR7QVVUSEVOVElLX0VNQUlMX19QQVNTV09SRH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMUz0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMU30nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTD0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVD0ke0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fRlJPTT0ke0FVVEhFTlRJS19FTUFJTF9fRlJPTX0nCiAgICB1c2VyOiByb290CiAgICB2b2x1bWVzOgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jaycKICAgICAgLSAnLi9tZWRpYTovbWVkaWEnCiAgICAgIC0gJy4vY2VydHM6L2NlcnRzJwogICAgICAtICcuL2N1c3RvbS10ZW1wbGF0ZXM6L3RlbXBsYXRlcycKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzcWw6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICBwb3N0Z3Jlc3FsOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNi1hbHBpbmUnCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1kICQke1BPU1RHUkVTX0RCfSAtVSAkJHtQT1NUR1JFU19VU0VSfScKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogICAgdm9sdW1lczoKICAgICAgLSAnYXV0aGVudGlrLWRiOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVNRTH0nCiAgICAgIC0gUE9TVEdSRVNfREI9YXV0aGVudGlrCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOmFscGluZScKICAgIGNvbW1hbmQ6ICctLXNhdmUgNjAgMSAtLWxvZ2xldmVsIHdhcm5pbmcnCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdyZWRpcy1jbGkgcGluZyB8IGdyZXAgUE9ORycKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogICAgdm9sdW1lczoKICAgICAgLSAncmVkaXM6L2RhdGEnCg==", "tags": [ "identity", "login", @@ -402,6 +402,23 @@ "minversion": "0.0.0", "port": "80" }, + "chroma": { + "documentation": "https://cookbook.chromadb.dev/?utm_source=coolify.io", + "slogan": "Chroma is the open-source search and retrieval database for AI applications.", + "compose": "c2VydmljZXM6CiAgY2hyb21hZGI6CiAgICBpbWFnZTogJ2Nocm9tYWRiL2Nocm9tYToxLjAuMTUnCiAgICB2b2x1bWVzOgogICAgICAtICdjaHJvbWEtZGF0YTovZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9DSFJPTUFfODAwMAogICAgICAtIElTX1BFUlNJU1RFTlQ9VFJVRQogICAgICAtIFBFUlNJU1RfRElSRUNUT1JZPS9kYXRhCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gL2Jpbi9iYXNoCiAgICAgICAgLSAnLWMnCiAgICAgICAgLSAnY2F0IDwgL2Rldi9udWxsID4gL2Rldi90Y3AvbG9jYWxob3N0LzgwMDAnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDMK", + "tags": [ + "ai", + "vector-database", + "semantic-search", + "machine-learning", + "bm25", + "embeddings", + "llm" + ], + "logo": "svgs/chroma.svg", + "minversion": "0.0.0", + "port": "8000" + }, "classicpress-with-mariadb": { "documentation": "https://www.classicpress.net/?utm_source=coolify.io", "slogan": "A lightweight, stable, instantly familiar free open-source content management system, based on WordPress without the block editor (Gutenberg).", @@ -831,7 +848,7 @@ "evolution-api": { "documentation": "https://doc.evolution-api.com/v1/pt/get-started/introduction?utm_source=coolify.io", "slogan": "Evolution API Installation with Postgres and Redis", - "compose": "dmVyc2lvbjogJzMuOCcKc2VydmljZXM6CiAgYXBpOgogICAgaW1hZ2U6ICdldm9hcGljbG91ZC9ldm9sdXRpb24tYXBpOmxhdGVzdCcKICAgIHJlc3RhcnQ6IGFsd2F5cwogICAgZGVwZW5kc19vbjoKICAgICAgLSByZWRpcwogICAgICAtIHBvc3RncmVzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fRVZPXzgwODAKICAgICAgLSBTRVJWRVJfVVJMPSRTRVJWSUNFX0ZRRE5fRVZPCiAgICAgIC0gJ0RCX1RZUEU9JHtEQl9UWVBFOi1wb3N0Z3Jlc2RifScKICAgICAgLSAnREJfUE9TVEdSRVNEQl9EQVRBQkFTRT0ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ0RCX1BPU1RHUkVTREJfSE9TVD0ke0RCX1BPU1RHUkVTREJfSE9TVDotcG9zdGdyZXN9JwogICAgICAtICdEQl9QT1NUR1JFU0RCX1BPUlQ9JHtEQl9QT1NUR1JFU0RCX1BPUlQ6LTU0MzJ9JwogICAgICAtICdEQl9QT1NUR1JFU0RCX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdEQl9QT1NUR1JFU0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ0RBVEFCQVNFX1BST1ZJREVSPSR7REFUQUJBU0VfUFJPVklERVI6LXBvc3RncmVzcWx9JwogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QHBvc3RncmVzOjU0MzIvJHtQT1NUR1JFU19EQjotcG9zdGdyZXN9JwogICAgICAtICdEQVRBQkFTRV9DT05ORUNUSU9OX1VSST1wb3N0Z3Jlc3FsOi8vJHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9OiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU31AcG9zdGdyZXM6NTQzMi8ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ0RFTF9JTlNUQU5DRT0ke0RFTF9JTlNUQU5DRTotZmFsc2V9JwogICAgICAtICdEQVRBQkFTRV9TQVZFX0RBVEFfSU5TVEFOQ0U9JHtEQVRBQkFTRV9TQVZFX0RBVEFfSU5TVEFOQ0U6LXRydWV9JwogICAgICAtICdEQVRBQkFTRV9TQVZFX0RBVEFfTkVXX01FU1NBR0U9JHtEQVRBQkFTRV9TQVZFX0RBVEFfTkVXX01FU1NBR0U6LXRydWV9JwogICAgICAtICdEQVRBQkFTRV9TQVZFX01FU1NBR0VfVVBEQVRFPSR7REFUQUJBU0VfU0FWRV9NRVNTQUdFX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ0RBVEFCQVNFX1NBVkVfREFUQV9DT05UQUNUUz0ke0RBVEFCQVNFX1NBVkVfREFUQV9DT05UQUNUUzotdHJ1ZX0nCiAgICAgIC0gJ0RBVEFCQVNFX1NBVkVfREFUQV9DSEFUUz0ke0RBVEFCQVNFX1NBVkVfREFUQV9DSEFUUzotdHJ1ZX0nCiAgICAgIC0gJ0RBVEFCQVNFX1NBVkVfREFUQV9MQUJFTFM9JHtEQVRBQkFTRV9TQVZFX0RBVEFfTEFCRUxTOi10cnVlfScKICAgICAgLSAnREFUQUJBU0VfU0FWRV9EQVRBX0hJU1RPUklDPSR7REFUQUJBU0VfU0FWRV9EQVRBX0hJU1RPUklDOi10cnVlfScKICAgICAgLSAnREFUQUJBU0VfQ09OTkVDVElPTl9DTElFTlRfTkFNRT0ke0RBVEFCQVNFX0NPTk5FQ1RJT05fQ0xJRU5UX05BTUU6LWV2b2x1dGlvbl92Mn0nCiAgICAgIC0gJ1JBQkJJVE1RX0VOQUJMRUQ9JHtSQUJCSVRNUV9FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX1VSST0ke1JBQkJJVE1RX1VSSTotYW1xcDovL2FkbWluOmFkbWluQHJhYmJpdG1xOjU2NzIvZGVmYXVsdH0nCiAgICAgIC0gJ1JBQkJJVE1RX0VYQ0hBTkdFX05BTUU9JHtSQUJCSVRNUV9FWENIQU5HRV9OQU1FOi1ldm9sdXRpb25fdjJ9JwogICAgICAtICdSQUJCSVRNUV9HTE9CQUxfRU5BQkxFRD0ke1JBQkJJVE1RX0dMT0JBTF9FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19BUFBMSUNBVElPTl9TVEFSVFVQPSR7UkFCQklUTVFfRVZFTlRTX0FQUExJQ0FUSU9OX1NUQVJUVVA6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0lOU1RBTkNFX0NSRUFURT0ke1JBQkJJVE1RX0VWRU5UU19JTlNUQU5DRV9DUkVBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0lOU1RBTkNFX0RFTEVURT0ke1JBQkJJVE1RX0VWRU5UU19JTlNUQU5DRV9ERUxFVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX1FSQ09ERV9VUERBVEVEPSR7UkFCQklUTVFfRVZFTlRTX1FSQ09ERV9VUERBVEVEOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19TRVQ9JHtSQUJCSVRNUV9FVkVOVFNfTUVTU0FHRVNfU0VUOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19VUFNFUlQ9JHtSQUJCSVRNUV9FVkVOVFNfTUVTU0FHRVNfVVBTRVJUOi10cnVlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX01FU1NBR0VTX0VESVRFRD0ke1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19FRElURUQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX01FU1NBR0VTX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19VUERBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX01FU1NBR0VTX0RFTEVURT0ke1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19ERUxFVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX1NFTkRfTUVTU0FHRT0ke1JBQkJJVE1RX0VWRU5UU19TRU5EX01FU1NBR0U6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NPTlRBQ1RTX1NFVD0ke1JBQkJJVE1RX0VWRU5UU19DT05UQUNUU19TRVQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NPTlRBQ1RTX1VQU0VSVD0ke1JBQkJJVE1RX0VWRU5UU19DT05UQUNUU19VUFNFUlQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NPTlRBQ1RTX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19DT05UQUNUU19VUERBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX1BSRVNFTkNFX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19QUkVTRU5DRV9VUERBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NIQVRTX1NFVD0ke1JBQkJJVE1RX0VWRU5UU19DSEFUU19TRVQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NIQVRTX1VQU0VSVD0ke1JBQkJJVE1RX0VWRU5UU19DSEFUU19VUFNFUlQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NIQVRTX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19DSEFUU19VUERBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NIQVRTX0RFTEVURT0ke1JBQkJJVE1RX0VWRU5UU19DSEFUU19ERUxFVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0dST1VQU19VUFNFUlQ9JHtSQUJCSVRNUV9FVkVOVFNfR1JPVVBTX1VQU0VSVDotZmFsc2V9JwogICAgICAtICdSQUJCSVRNUV9FVkVOVFNfR1JPVVBfVVBEQVRFPSR7UkFCQklUTVFfRVZFTlRTX0dST1VQX1VQREFURTotZmFsc2V9JwogICAgICAtICdSQUJCSVRNUV9FVkVOVFNfR1JPVVBfUEFSVElDSVBBTlRTX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19HUk9VUF9QQVJUSUNJUEFOVFNfVVBEQVRFOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19DT05ORUNUSU9OX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19DT05ORUNUSU9OX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19DQUxMPSR7UkFCQklUTVFfRVZFTlRTX0NBTEw6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX1RZUEVCT1RfU1RBUlQ9JHtSQUJCSVRNUV9FVkVOVFNfVFlQRUJPVF9TVEFSVDotZmFsc2V9JwogICAgICAtICdSQUJCSVRNUV9FVkVOVFNfVFlQRUJPVF9DSEFOR0VfU1RBVFVTPSR7UkFCQklUTVFfRVZFTlRTX1RZUEVCT1RfQ0hBTkdFX1NUQVRVUzotZmFsc2V9JwogICAgICAtICdTUVNfRU5BQkxFRD0ke1NRU19FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1NRU19BQ0NFU1NfS0VZX0lEPSR7U1FTX0FDQ0VTU19LRVlfSUQ6LX0nCiAgICAgIC0gJ1NRU19TRUNSRVRfQUNDRVNTX0tFWT0ke1NRU19TRUNSRVRfQUNDRVNTX0tFWTotfScKICAgICAgLSAnU1FTX0FDQ09VTlRfSUQ9JHtTUVNfQUNDT1VOVF9JRDotfScKICAgICAgLSAnU1FTX1JFR0lPTj0ke1NRU19SRUdJT046LX0nCiAgICAgIC0gJ1dFQlNPQ0tFVF9FTkFCTEVEPSR7V0VCU09DS0VUX0VOQUJMRUQ6LWZhbHNlfScKICAgICAgLSAnV0VCU09DS0VUX0dMT0JBTF9FVkVOVFM9JHtXRUJTT0NLRVRfR0xPQkFMX0VWRU5UUzotZmFsc2V9JwogICAgICAtICdXQV9CVVNJTkVTU19UT0tFTl9XRUJIT09LPSR7V0FfQlVTSU5FU1NfVE9LRU5fV0VCSE9PSzotZXZvbHV0aW9ufScKICAgICAgLSAnV0FfQlVTSU5FU1NfVVJMPSR7V0FfQlVTSU5FU1NfVVJMOi1odHRwczovL2dyYXBoLmZhY2Vib29rLmNvbX0nCiAgICAgIC0gJ1dBX0JVU0lORVNTX1ZFUlNJT049JHtXQV9CVVNJTkVTU19WRVJTSU9OOi12MjAuMH0nCiAgICAgIC0gJ1dBX0JVU0lORVNTX0xBTkdVQUdFPSR7V0FfQlVTSU5FU1NfTEFOR1VBR0U6LXB0X0JSfScKICAgICAgLSAiV0VCSE9PS19HTE9CQUxfVVJMPSR7V0VCSE9PS19HTE9CQUxfVVJMOi0nJ30iCiAgICAgIC0gJ1dFQkhPT0tfR0xPQkFMX0VOQUJMRUQ9JHtXRUJIT09LX0dMT0JBTF9FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1dFQkhPT0tfR0xPQkFMX1dFQkhPT0tfQllfRVZFTlRTPSR7V0VCSE9PS19HTE9CQUxfV0VCSE9PS19CWV9FVkVOVFM6LWZhbHNlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfQVBQTElDQVRJT05fU1RBUlRVUD0ke1dFQkhPT0tfRVZFTlRTX0FQUExJQ0FUSU9OX1NUQVJUVVA6LWZhbHNlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfUVJDT0RFX1VQREFURUQ9JHtXRUJIT09LX0VWRU5UU19RUkNPREVfVVBEQVRFRDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1NFVD0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1NFVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1VQU0VSVD0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1VQU0VSVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX0VESVRFRD0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX0VESVRFRDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX0RFTEVURT0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX0RFTEVURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX1NFTkRfTUVTU0FHRT0ke1dFQkhPT0tfRVZFTlRTX1NFTkRfTUVTU0FHRTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1NFVD0ke1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1NFVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1VQU0VSVD0ke1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1VQU0VSVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX1BSRVNFTkNFX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX1BSRVNFTkNFX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NIQVRTX1NFVD0ke1dFQkhPT0tfRVZFTlRTX0NIQVRTX1NFVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NIQVRTX1VQU0VSVD0ke1dFQkhPT0tfRVZFTlRTX0NIQVRTX1VQU0VSVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NIQVRTX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX0NIQVRTX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NIQVRTX0RFTEVURT0ke1dFQkhPT0tfRVZFTlRTX0NIQVRTX0RFTEVURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0dST1VQU19VUFNFUlQ9JHtXRUJIT09LX0VWRU5UU19HUk9VUFNfVVBTRVJUOi10cnVlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfR1JPVVBTX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX0dST1VQU19VUERBVEU6LXRydWV9JwogICAgICAtICdXRUJIT09LX0VWRU5UU19HUk9VUF9QQVJUSUNJUEFOVFNfVVBEQVRFPSR7V0VCSE9PS19FVkVOVFNfR1JPVVBfUEFSVElDSVBBTlRTX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NPTk5FQ1RJT05fVVBEQVRFPSR7V0VCSE9PS19FVkVOVFNfQ09OTkVDVElPTl9VUERBVEU6LXRydWV9JwogICAgICAtICdXRUJIT09LX0VWRU5UU19MQUJFTFNfRURJVD0ke1dFQkhPT0tfRVZFTlRTX0xBQkVMU19FRElUOi10cnVlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfTEFCRUxTX0FTU09DSUFUSU9OPSR7V0VCSE9PS19FVkVOVFNfTEFCRUxTX0FTU09DSUFUSU9OOi10cnVlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfQ0FMTD0ke1dFQkhPT0tfRVZFTlRTX0NBTEw6LXRydWV9JwogICAgICAtICdXRUJIT09LX0VWRU5UU19UWVBFQk9UX1NUQVJUPSR7V0VCSE9PS19FVkVOVFNfVFlQRUJPVF9TVEFSVDotZmFsc2V9JwogICAgICAtICdXRUJIT09LX0VWRU5UU19UWVBFQk9UX0NIQU5HRV9TVEFUVVM9JHtXRUJIT09LX0VWRU5UU19UWVBFQk9UX0NIQU5HRV9TVEFUVVM6LWZhbHNlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfRVJST1JTPSR7V0VCSE9PS19FVkVOVFNfRVJST1JTOi1mYWxzZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0VSUk9SU19XRUJIT09LPSR7V0VCSE9PS19FVkVOVFNfRVJST1JTX1dFQkhPT0s6LX0nCiAgICAgIC0gJ0NPTkZJR19TRVNTSU9OX1BIT05FX0NMSUVOVD0ke0NPTkZJR19TRVNTSU9OX1BIT05FX0NMSUVOVDotRXZvbHV0aW9uIEFQSSBWMn0nCiAgICAgIC0gJ0NPTkZJR19TRVNTSU9OX1BIT05FX05BTUU9JHtDT05GSUdfU0VTU0lPTl9QSE9ORV9OQU1FOi1DaHJvbWV9JwogICAgICAtICdDT05GSUdfU0VTU0lPTl9QSE9ORV9WRVJTSU9OPSR7Q09ORklHX1NFU1NJT05fUEhPTkVfVkVSU0lPTjotMi4zMDAwLjEwMjA4ODUxNDN9JwogICAgICAtICdRUkNPREVfTElNSVQ9JHtRUkNPREVfTElNSVQ6LTMwfScKICAgICAgLSAnT1BFTkFJX0VOQUJMRUQ9JHtPUEVOQUlfRU5BQkxFRDotdHJ1ZX0nCiAgICAgIC0gJ0RJRllfRU5BQkxFRD0ke0RJRllfRU5BQkxFRDotdHJ1ZX0nCiAgICAgIC0gJ1RZUEVCT1RfRU5BQkxFRD0ke1RZUEVCT1RfRU5BQkxFRDotdHJ1ZX0nCiAgICAgIC0gJ1RZUEVCT1RfQVBJX1ZFUlNJT049JHtUWVBFQk9UX0FQSV9WRVJTSU9OOi1sYXRlc3R9JwogICAgICAtICdDSEFUV09PVF9FTkFCTEVEPSR7Q0hBVFdPT1RfRU5BQkxFRDotdHJ1ZX0nCiAgICAgIC0gJ0NIQVRXT09UX01FU1NBR0VfUkVBRD0ke0NIQVRXT09UX01FU1NBR0VfUkVBRDotdHJ1ZX0nCiAgICAgIC0gJ0NIQVRXT09UX01FU1NBR0VfREVMRVRFPSR7Q0hBVFdPT1RfTUVTU0FHRV9ERUxFVEU6LXRydWV9JwogICAgICAtICdDSEFUV09PVF9JTVBPUlRfREFUQUJBU0VfQ09OTkVDVElPTl9VUkk9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QHBvc3RncmVzOjU0MzIvJHtQT1NUR1JFU19EQjotY2hhdHdvb3R9JwogICAgICAtICdDSEFUV09PVF9JTVBPUlRfUExBQ0VIT0xERVJfTUVESUFfTUVTU0FHRT0ke0NIQVRXT09UX0lNUE9SVF9QTEFDRUhPTERFUl9NRURJQV9NRVNTQUdFOi10cnVlfScKICAgICAgLSAnQ0FDSEVfUkVESVNfRU5BQkxFRD0ke0NBQ0hFX1JFRElTX0VOQUJMRUQ6LXRydWV9JwogICAgICAtICdDQUNIRV9SRURJU19VUkk9JHtDQUNIRV9SRURJU19VUkk6LXJlZGlzOi8vcmVkaXM6NjM3OS82fScKICAgICAgLSAnQ0FDSEVfUkVESVNfUFJFRklYX0tFWT0ke0NBQ0hFX1JFRElTX1BSRUZJWF9LRVk6LWV2b2x1dGlvbl92Mn0nCiAgICAgIC0gJ0NBQ0hFX1JFRElTX1NBVkVfSU5TVEFOQ0VTPSR7Q0FDSEVfUkVESVNfU0FWRV9JTlNUQU5DRVM6LWZhbHNlfScKICAgICAgLSAnQ0FDSEVfTE9DQUxfRU5BQkxFRD0ke0NBQ0hFX0xPQ0FMX0VOQUJMRUQ6LWZhbHNlfScKICAgICAgLSAnUzNfRU5BQkxFRD0ke1MzX0VOQUJMRUQ6LWZhbHNlfScKICAgICAgLSAnUzNfQUNDRVNTX0tFWT0ke1MzX0FDQ0VTU19LRVk6LX0nCiAgICAgIC0gJ1MzX1NFQ1JFVF9LRVk9JHtTM19TRUNSRVRfS0VZOi19JwogICAgICAtICdTM19CVUNLRVQ9JHtTM19CVUNLRVQ6LWV2b2x1dGlvbn0nCiAgICAgIC0gJ1MzX1BPUlQ9JHtTM19QT1JUOi00NDN9JwogICAgICAtICdTM19SRUdJT049JHtTM19SRUdJT046LXVzLWVhc3QtMX0nCiAgICAgIC0gJ1MzX0VORFBPSU5UPSR7UzNfRU5EUE9JTlQ6LWZpbGVzLnNpdGUuY29tfScKICAgICAgLSAnUzNfVVNFX1NTTD0ke1MzX1VTRV9TU0w6LXRydWV9JwogICAgICAtICdBVVRIRU5USUNBVElPTl9BUElfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9BVVRIRU5USUNBVElPTkFQSUtFWX0nCiAgICAgIC0gJ0FVVEhFTlRJQ0FUSU9OX0VYUE9TRV9JTl9GRVRDSF9JTlNUQU5DRVM9JHtBVVRIRU5USUNBVElPTl9FWFBPU0VfSU5fRkVUQ0hfSU5TVEFOQ0VTOi10cnVlfScKICAgICAgLSAnTEFOR1VBR0U9JHtMQU5HVUFHRTotZW59JwogICAgdm9sdW1lczoKICAgICAgLSAnZXZvbHV0aW9uX2luc3RhbmNlczovZXZvbHV0aW9uL2luc3RhbmNlcycKICAgIGV4cG9zZToKICAgICAgLSA4MDgwCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOmxhdGVzdCcKICAgIGNvbW1hbmQ6ICJyZWRpcy1zZXJ2ZXIgLS1wb3J0IDYzNzkgLS1hcHBlbmRvbmx5IHllc1xuIgogICAgcmVzdGFydDogYWx3YXlzCiAgICB2b2x1bWVzOgogICAgICAtICdldm9sdXRpb25fcmVkaXM6L2RhdGEnCiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2LWFscGluZScKICAgIGNvbW1hbmQ6CiAgICAgIC0gcG9zdGdyZXMKICAgICAgLSAnLWMnCiAgICAgIC0gbWF4X2Nvbm5lY3Rpb25zPTEwMDAKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotcG9zdGdyZXN9JwogICAgICAtICdBVVRIRU5USUNBVElPTl9BUElfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9BVVRIRU5USUNBVElPTkFQSUtFWX0nCiAgICByZXN0YXJ0OiBhbHdheXMKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3Bvc3RncmVzX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwp2b2x1bWVzOgogIGV2b2x1dGlvbl9pbnN0YW5jZXM6IG51bGwKICBldm9sdXRpb25fcmVkaXM6IG51bGwKICBwb3N0Z3Jlc19kYXRhOiBudWxsCg==", + "compose": "dmVyc2lvbjogJzMuOCcKc2VydmljZXM6CiAgYXBpOgogICAgaW1hZ2U6ICdldm9hcGljbG91ZC9ldm9sdXRpb24tYXBpOmxhdGVzdCcKICAgIHJlc3RhcnQ6IGFsd2F5cwogICAgZGVwZW5kc19vbjoKICAgICAgLSByZWRpcwogICAgICAtIHBvc3RncmVzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fRVZPXzgwODAKICAgICAgLSBTRVJWRVJfVVJMPSRTRVJWSUNFX0ZRRE5fRVZPCiAgICAgIC0gJ0RCX1RZUEU9JHtEQl9UWVBFOi1wb3N0Z3Jlc2RifScKICAgICAgLSAnREJfUE9TVEdSRVNEQl9EQVRBQkFTRT0ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ0RCX1BPU1RHUkVTREJfSE9TVD0ke0RCX1BPU1RHUkVTREJfSE9TVDotcG9zdGdyZXN9JwogICAgICAtICdEQl9QT1NUR1JFU0RCX1BPUlQ9JHtEQl9QT1NUR1JFU0RCX1BPUlQ6LTU0MzJ9JwogICAgICAtICdEQl9QT1NUR1JFU0RCX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdEQl9QT1NUR1JFU0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ0RBVEFCQVNFX1BST1ZJREVSPSR7REFUQUJBU0VfUFJPVklERVI6LXBvc3RncmVzcWx9JwogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QHBvc3RncmVzOjU0MzIvJHtQT1NUR1JFU19EQjotcG9zdGdyZXN9JwogICAgICAtICdEQVRBQkFTRV9DT05ORUNUSU9OX1VSST1wb3N0Z3Jlc3FsOi8vJHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9OiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU31AcG9zdGdyZXM6NTQzMi8ke1BPU1RHUkVTX0RCOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ0RFTF9JTlNUQU5DRT0ke0RFTF9JTlNUQU5DRTotZmFsc2V9JwogICAgICAtICdEQVRBQkFTRV9TQVZFX0RBVEFfSU5TVEFOQ0U9JHtEQVRBQkFTRV9TQVZFX0RBVEFfSU5TVEFOQ0U6LXRydWV9JwogICAgICAtICdEQVRBQkFTRV9TQVZFX0RBVEFfTkVXX01FU1NBR0U9JHtEQVRBQkFTRV9TQVZFX0RBVEFfTkVXX01FU1NBR0U6LXRydWV9JwogICAgICAtICdEQVRBQkFTRV9TQVZFX01FU1NBR0VfVVBEQVRFPSR7REFUQUJBU0VfU0FWRV9NRVNTQUdFX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ0RBVEFCQVNFX1NBVkVfREFUQV9DT05UQUNUUz0ke0RBVEFCQVNFX1NBVkVfREFUQV9DT05UQUNUUzotdHJ1ZX0nCiAgICAgIC0gJ0RBVEFCQVNFX1NBVkVfREFUQV9DSEFUUz0ke0RBVEFCQVNFX1NBVkVfREFUQV9DSEFUUzotdHJ1ZX0nCiAgICAgIC0gJ0RBVEFCQVNFX1NBVkVfREFUQV9MQUJFTFM9JHtEQVRBQkFTRV9TQVZFX0RBVEFfTEFCRUxTOi10cnVlfScKICAgICAgLSAnREFUQUJBU0VfU0FWRV9EQVRBX0hJU1RPUklDPSR7REFUQUJBU0VfU0FWRV9EQVRBX0hJU1RPUklDOi10cnVlfScKICAgICAgLSAnREFUQUJBU0VfQ09OTkVDVElPTl9DTElFTlRfTkFNRT0ke0RBVEFCQVNFX0NPTk5FQ1RJT05fQ0xJRU5UX05BTUU6LWV2b2x1dGlvbl92Mn0nCiAgICAgIC0gJ1JBQkJJVE1RX0VOQUJMRUQ9JHtSQUJCSVRNUV9FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX1VSST0ke1JBQkJJVE1RX1VSSTotYW1xcDovL2FkbWluOmFkbWluQHJhYmJpdG1xOjU2NzIvZGVmYXVsdH0nCiAgICAgIC0gJ1JBQkJJVE1RX0VYQ0hBTkdFX05BTUU9JHtSQUJCSVRNUV9FWENIQU5HRV9OQU1FOi1ldm9sdXRpb25fdjJ9JwogICAgICAtICdSQUJCSVRNUV9HTE9CQUxfRU5BQkxFRD0ke1JBQkJJVE1RX0dMT0JBTF9FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19BUFBMSUNBVElPTl9TVEFSVFVQPSR7UkFCQklUTVFfRVZFTlRTX0FQUExJQ0FUSU9OX1NUQVJUVVA6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0lOU1RBTkNFX0NSRUFURT0ke1JBQkJJVE1RX0VWRU5UU19JTlNUQU5DRV9DUkVBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0lOU1RBTkNFX0RFTEVURT0ke1JBQkJJVE1RX0VWRU5UU19JTlNUQU5DRV9ERUxFVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX1FSQ09ERV9VUERBVEVEPSR7UkFCQklUTVFfRVZFTlRTX1FSQ09ERV9VUERBVEVEOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19TRVQ9JHtSQUJCSVRNUV9FVkVOVFNfTUVTU0FHRVNfU0VUOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19VUFNFUlQ9JHtSQUJCSVRNUV9FVkVOVFNfTUVTU0FHRVNfVVBTRVJUOi10cnVlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX01FU1NBR0VTX0VESVRFRD0ke1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19FRElURUQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX01FU1NBR0VTX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19VUERBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX01FU1NBR0VTX0RFTEVURT0ke1JBQkJJVE1RX0VWRU5UU19NRVNTQUdFU19ERUxFVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX1NFTkRfTUVTU0FHRT0ke1JBQkJJVE1RX0VWRU5UU19TRU5EX01FU1NBR0U6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NPTlRBQ1RTX1NFVD0ke1JBQkJJVE1RX0VWRU5UU19DT05UQUNUU19TRVQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NPTlRBQ1RTX1VQU0VSVD0ke1JBQkJJVE1RX0VWRU5UU19DT05UQUNUU19VUFNFUlQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NPTlRBQ1RTX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19DT05UQUNUU19VUERBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX1BSRVNFTkNFX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19QUkVTRU5DRV9VUERBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NIQVRTX1NFVD0ke1JBQkJJVE1RX0VWRU5UU19DSEFUU19TRVQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NIQVRTX1VQU0VSVD0ke1JBQkJJVE1RX0VWRU5UU19DSEFUU19VUFNFUlQ6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NIQVRTX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19DSEFUU19VUERBVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0NIQVRTX0RFTEVURT0ke1JBQkJJVE1RX0VWRU5UU19DSEFUU19ERUxFVEU6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX0dST1VQU19VUFNFUlQ9JHtSQUJCSVRNUV9FVkVOVFNfR1JPVVBTX1VQU0VSVDotZmFsc2V9JwogICAgICAtICdSQUJCSVRNUV9FVkVOVFNfR1JPVVBfVVBEQVRFPSR7UkFCQklUTVFfRVZFTlRTX0dST1VQX1VQREFURTotZmFsc2V9JwogICAgICAtICdSQUJCSVRNUV9FVkVOVFNfR1JPVVBfUEFSVElDSVBBTlRTX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19HUk9VUF9QQVJUSUNJUEFOVFNfVVBEQVRFOi1mYWxzZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19DT05ORUNUSU9OX1VQREFURT0ke1JBQkJJVE1RX0VWRU5UU19DT05ORUNUSU9OX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1JBQkJJVE1RX0VWRU5UU19DQUxMPSR7UkFCQklUTVFfRVZFTlRTX0NBTEw6LWZhbHNlfScKICAgICAgLSAnUkFCQklUTVFfRVZFTlRTX1RZUEVCT1RfU1RBUlQ9JHtSQUJCSVRNUV9FVkVOVFNfVFlQRUJPVF9TVEFSVDotZmFsc2V9JwogICAgICAtICdSQUJCSVRNUV9FVkVOVFNfVFlQRUJPVF9DSEFOR0VfU1RBVFVTPSR7UkFCQklUTVFfRVZFTlRTX1RZUEVCT1RfQ0hBTkdFX1NUQVRVUzotZmFsc2V9JwogICAgICAtICdTUVNfRU5BQkxFRD0ke1NRU19FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1NRU19BQ0NFU1NfS0VZX0lEPSR7U1FTX0FDQ0VTU19LRVlfSUQ6LX0nCiAgICAgIC0gJ1NRU19TRUNSRVRfQUNDRVNTX0tFWT0ke1NRU19TRUNSRVRfQUNDRVNTX0tFWTotfScKICAgICAgLSAnU1FTX0FDQ09VTlRfSUQ9JHtTUVNfQUNDT1VOVF9JRDotfScKICAgICAgLSAnU1FTX1JFR0lPTj0ke1NRU19SRUdJT046LX0nCiAgICAgIC0gJ1dFQlNPQ0tFVF9FTkFCTEVEPSR7V0VCU09DS0VUX0VOQUJMRUQ6LWZhbHNlfScKICAgICAgLSAnV0VCU09DS0VUX0dMT0JBTF9FVkVOVFM9JHtXRUJTT0NLRVRfR0xPQkFMX0VWRU5UUzotZmFsc2V9JwogICAgICAtICdXQV9CVVNJTkVTU19UT0tFTl9XRUJIT09LPSR7V0FfQlVTSU5FU1NfVE9LRU5fV0VCSE9PSzotZXZvbHV0aW9ufScKICAgICAgLSAnV0FfQlVTSU5FU1NfVVJMPSR7V0FfQlVTSU5FU1NfVVJMOi1odHRwczovL2dyYXBoLmZhY2Vib29rLmNvbX0nCiAgICAgIC0gJ1dBX0JVU0lORVNTX1ZFUlNJT049JHtXQV9CVVNJTkVTU19WRVJTSU9OOi12MjAuMH0nCiAgICAgIC0gJ1dBX0JVU0lORVNTX0xBTkdVQUdFPSR7V0FfQlVTSU5FU1NfTEFOR1VBR0U6LXB0X0JSfScKICAgICAgLSAiV0VCSE9PS19HTE9CQUxfVVJMPSR7V0VCSE9PS19HTE9CQUxfVVJMOi0nJ30iCiAgICAgIC0gJ1dFQkhPT0tfR0xPQkFMX0VOQUJMRUQ9JHtXRUJIT09LX0dMT0JBTF9FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1dFQkhPT0tfR0xPQkFMX1dFQkhPT0tfQllfRVZFTlRTPSR7V0VCSE9PS19HTE9CQUxfV0VCSE9PS19CWV9FVkVOVFM6LWZhbHNlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfQVBQTElDQVRJT05fU1RBUlRVUD0ke1dFQkhPT0tfRVZFTlRTX0FQUExJQ0FUSU9OX1NUQVJUVVA6LWZhbHNlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfUVJDT0RFX1VQREFURUQ9JHtXRUJIT09LX0VWRU5UU19RUkNPREVfVVBEQVRFRDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1NFVD0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1NFVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1VQU0VSVD0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1VQU0VSVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX0VESVRFRD0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX0VESVRFRDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX0RFTEVURT0ke1dFQkhPT0tfRVZFTlRTX01FU1NBR0VTX0RFTEVURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX1NFTkRfTUVTU0FHRT0ke1dFQkhPT0tfRVZFTlRTX1NFTkRfTUVTU0FHRTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1NFVD0ke1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1NFVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1VQU0VSVD0ke1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1VQU0VSVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX0NPTlRBQ1RTX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX1BSRVNFTkNFX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX1BSRVNFTkNFX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NIQVRTX1NFVD0ke1dFQkhPT0tfRVZFTlRTX0NIQVRTX1NFVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NIQVRTX1VQU0VSVD0ke1dFQkhPT0tfRVZFTlRTX0NIQVRTX1VQU0VSVDotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NIQVRTX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX0NIQVRTX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NIQVRTX0RFTEVURT0ke1dFQkhPT0tfRVZFTlRTX0NIQVRTX0RFTEVURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0dST1VQU19VUFNFUlQ9JHtXRUJIT09LX0VWRU5UU19HUk9VUFNfVVBTRVJUOi10cnVlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfR1JPVVBTX1VQREFURT0ke1dFQkhPT0tfRVZFTlRTX0dST1VQU19VUERBVEU6LXRydWV9JwogICAgICAtICdXRUJIT09LX0VWRU5UU19HUk9VUF9QQVJUSUNJUEFOVFNfVVBEQVRFPSR7V0VCSE9PS19FVkVOVFNfR1JPVVBfUEFSVElDSVBBTlRTX1VQREFURTotdHJ1ZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0NPTk5FQ1RJT05fVVBEQVRFPSR7V0VCSE9PS19FVkVOVFNfQ09OTkVDVElPTl9VUERBVEU6LXRydWV9JwogICAgICAtICdXRUJIT09LX0VWRU5UU19MQUJFTFNfRURJVD0ke1dFQkhPT0tfRVZFTlRTX0xBQkVMU19FRElUOi10cnVlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfTEFCRUxTX0FTU09DSUFUSU9OPSR7V0VCSE9PS19FVkVOVFNfTEFCRUxTX0FTU09DSUFUSU9OOi10cnVlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfQ0FMTD0ke1dFQkhPT0tfRVZFTlRTX0NBTEw6LXRydWV9JwogICAgICAtICdXRUJIT09LX0VWRU5UU19UWVBFQk9UX1NUQVJUPSR7V0VCSE9PS19FVkVOVFNfVFlQRUJPVF9TVEFSVDotZmFsc2V9JwogICAgICAtICdXRUJIT09LX0VWRU5UU19UWVBFQk9UX0NIQU5HRV9TVEFUVVM9JHtXRUJIT09LX0VWRU5UU19UWVBFQk9UX0NIQU5HRV9TVEFUVVM6LWZhbHNlfScKICAgICAgLSAnV0VCSE9PS19FVkVOVFNfRVJST1JTPSR7V0VCSE9PS19FVkVOVFNfRVJST1JTOi1mYWxzZX0nCiAgICAgIC0gJ1dFQkhPT0tfRVZFTlRTX0VSUk9SU19XRUJIT09LPSR7V0VCSE9PS19FVkVOVFNfRVJST1JTX1dFQkhPT0s6LX0nCiAgICAgIC0gJ0NPTkZJR19TRVNTSU9OX1BIT05FX0NMSUVOVD0ke0NPTkZJR19TRVNTSU9OX1BIT05FX0NMSUVOVDotRXZvbHV0aW9uIEFQSSBWMn0nCiAgICAgIC0gJ0NPTkZJR19TRVNTSU9OX1BIT05FX05BTUU9JHtDT05GSUdfU0VTU0lPTl9QSE9ORV9OQU1FOi1DaHJvbWV9JwogICAgICAtICdRUkNPREVfTElNSVQ9JHtRUkNPREVfTElNSVQ6LTMwfScKICAgICAgLSAnT1BFTkFJX0VOQUJMRUQ9JHtPUEVOQUlfRU5BQkxFRDotdHJ1ZX0nCiAgICAgIC0gJ0RJRllfRU5BQkxFRD0ke0RJRllfRU5BQkxFRDotdHJ1ZX0nCiAgICAgIC0gJ0ZMT1dJU0VfRU5BQkxFRD0ke0ZMT1dJU0VfRU5BQkxFRDotdHJ1ZX0nCiAgICAgIC0gJ044Tl9FTkFCTEVEPSR7TjhOX0VOQUJMRUQ6LXRydWV9JwogICAgICAtICdUWVBFQk9UX0VOQUJMRUQ9JHtUWVBFQk9UX0VOQUJMRUQ6LXRydWV9JwogICAgICAtICdUWVBFQk9UX0FQSV9WRVJTSU9OPSR7VFlQRUJPVF9BUElfVkVSU0lPTjotbGF0ZXN0fScKICAgICAgLSAnQ0hBVFdPT1RfRU5BQkxFRD0ke0NIQVRXT09UX0VOQUJMRUQ6LXRydWV9JwogICAgICAtICdDSEFUV09PVF9NRVNTQUdFX1JFQUQ9JHtDSEFUV09PVF9NRVNTQUdFX1JFQUQ6LXRydWV9JwogICAgICAtICdDSEFUV09PVF9NRVNTQUdFX0RFTEVURT0ke0NIQVRXT09UX01FU1NBR0VfREVMRVRFOi10cnVlfScKICAgICAgLSAnQ0hBVFdPT1RfSU1QT1JUX0RBVEFCQVNFX0NPTk5FQ1RJT05fVVJJPXBvc3RncmVzcWw6Ly8ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU306JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUBwb3N0Z3Jlczo1NDMyLyR7UE9TVEdSRVNfREI6LWNoYXR3b290fScKICAgICAgLSAnQ0hBVFdPT1RfSU1QT1JUX1BMQUNFSE9MREVSX01FRElBX01FU1NBR0U9JHtDSEFUV09PVF9JTVBPUlRfUExBQ0VIT0xERVJfTUVESUFfTUVTU0FHRTotdHJ1ZX0nCiAgICAgIC0gJ0NBQ0hFX1JFRElTX0VOQUJMRUQ9JHtDQUNIRV9SRURJU19FTkFCTEVEOi10cnVlfScKICAgICAgLSAnQ0FDSEVfUkVESVNfVVJJPSR7Q0FDSEVfUkVESVNfVVJJOi1yZWRpczovL3JlZGlzOjYzNzkvNn0nCiAgICAgIC0gJ0NBQ0hFX1JFRElTX1BSRUZJWF9LRVk9JHtDQUNIRV9SRURJU19QUkVGSVhfS0VZOi1ldm9sdXRpb25fdjJ9JwogICAgICAtICdDQUNIRV9SRURJU19TQVZFX0lOU1RBTkNFUz0ke0NBQ0hFX1JFRElTX1NBVkVfSU5TVEFOQ0VTOi1mYWxzZX0nCiAgICAgIC0gJ0NBQ0hFX0xPQ0FMX0VOQUJMRUQ9JHtDQUNIRV9MT0NBTF9FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1MzX0VOQUJMRUQ9JHtTM19FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ1MzX0FDQ0VTU19LRVk9JHtTM19BQ0NFU1NfS0VZOi19JwogICAgICAtICdTM19TRUNSRVRfS0VZPSR7UzNfU0VDUkVUX0tFWTotfScKICAgICAgLSAnUzNfQlVDS0VUPSR7UzNfQlVDS0VUOi1ldm9sdXRpb259JwogICAgICAtICdTM19QT1JUPSR7UzNfUE9SVDotNDQzfScKICAgICAgLSAnUzNfUkVHSU9OPSR7UzNfUkVHSU9OOi11cy1lYXN0LTF9JwogICAgICAtICdTM19FTkRQT0lOVD0ke1MzX0VORFBPSU5UOi1maWxlcy5zaXRlLmNvbX0nCiAgICAgIC0gJ1MzX1VTRV9TU0w9JHtTM19VU0VfU1NMOi10cnVlfScKICAgICAgLSAnQVVUSEVOVElDQVRJT05fQVBJX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfQVVUSEVOVElDQVRJT05BUElLRVl9JwogICAgICAtICdBVVRIRU5USUNBVElPTl9FWFBPU0VfSU5fRkVUQ0hfSU5TVEFOQ0VTPSR7QVVUSEVOVElDQVRJT05fRVhQT1NFX0lOX0ZFVENIX0lOU1RBTkNFUzotdHJ1ZX0nCiAgICAgIC0gJ0xBTkdVQUdFPSR7TEFOR1VBR0U6LWVufScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2V2b2x1dGlvbl9pbnN0YW5jZXM6L2V2b2x1dGlvbi9pbnN0YW5jZXMnCiAgICBleHBvc2U6CiAgICAgIC0gODA4MAogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczpsYXRlc3QnCiAgICBjb21tYW5kOiAicmVkaXMtc2VydmVyIC0tcG9ydCA2Mzc5IC0tYXBwZW5kb25seSB5ZXNcbiIKICAgIHJlc3RhcnQ6IGFsd2F5cwogICAgdm9sdW1lczoKICAgICAgLSAnZXZvbHV0aW9uX3JlZGlzOi9kYXRhJwogIHBvc3RncmVzOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNi1hbHBpbmUnCiAgICBjb21tYW5kOgogICAgICAtIHBvc3RncmVzCiAgICAgIC0gJy1jJwogICAgICAtIG1heF9jb25uZWN0aW9ucz0xMDAwCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LXBvc3RncmVzfScKICAgICAgLSAnQVVUSEVOVElDQVRJT05fQVBJX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfQVVUSEVOVElDQVRJT05BUElLRVl9JwogICAgcmVzdGFydDogYWx3YXlzCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0Z3Jlc19kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKdm9sdW1lczoKICBldm9sdXRpb25faW5zdGFuY2VzOiBudWxsCiAgZXZvbHV0aW9uX3JlZGlzOiBudWxsCiAgcG9zdGdyZXNfZGF0YTogbnVsbAo=", "tags": [ "evolution-api", "evo-api", @@ -1753,7 +1770,7 @@ "langfuse": { "documentation": "https://langfuse.com/docs?utm_source=coolify.io", "slogan": "Langfuse is an open-source LLM engineering platform that helps teams collaboratively debug, analyze, and iterate on their LLM applications.", - "compose": "c2VydmljZXM6CiAgbGFuZ2Z1c2U6CiAgICBpbWFnZTogJ2xhbmdmdXNlL2xhbmdmdXNlOjInCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTEFOR0ZVU0VfMzAwMAogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QHBvc3RncmVzOjU0MzIvJHtQT1NUR1JFU19EQjotbGFuZ2Z1c2V9JwogICAgICAtICdESVJFQ1RfVVJMPXBvc3RncmVzcWw6Ly8ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU306JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUBwb3N0Z3Jlczo1NDMyLyR7UE9TVEdSRVNfREI6LWxhbmdmdXNlfScKICAgICAgLSBTQUxUPSRTRVJWSUNFX1BBU1NXT1JEX1NBTFQKICAgICAgLSAnQVVUSF9ESVNBQkxFX1NJR05VUD0ke0FVVEhfRElTQUJMRV9TSUdOVVA6LWZhbHNlfScKICAgICAgLSBORVhUQVVUSF9VUkw9JFNFUlZJQ0VfRlFETl9MQU5HRlVTRV8zMDAwCiAgICAgIC0gJ05FWFRBVVRIX1NFQ1JFVD0ke1NFUlZJQ0VfQkFTRTY0XzY0X05FWFRBVVRIU0VDUkVUfScKICAgICAgLSAnVEVMRU1FVFJZX0VOQUJMRUQ9JHtURUxFTUVUUllfRU5BQkxFRDotZmFsc2V9JwogICAgICAtICdMQU5HRlVTRV9FTkFCTEVfRVhQRVJJTUVOVEFMX0ZFQVRVUkVTPSR7TEFOR0ZVU0VfRU5BQkxFX0VYUEVSSU1FTlRBTF9GRUFUVVJFUzotZmFsc2V9JwogICAgICAtICdIT1NUTkFNRT0ke0hPU1ROQU1FOi0wLjAuMC4wfScKICAgICAgLSAnTEFOR0ZVU0VfSU5JVF9VU0VSX05BTUU9JHtMQU5HRlVTRV9JTklUX1VTRVJfTkFNRTotQWRtaW59JwogICAgICAtICdMQU5HRlVTRV9JTklUX1VTRVJfRU1BSUw9JHtMQU5HRlVTRV9JTklUX1VTRVJfRU1BSUw6LWFkbWluQGV4YW1wbGUuY29tfScKICAgICAgLSAnTEFOR0ZVU0VfSU5JVF9VU0VSX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9MQU5HRlVTRX0nCiAgICAgIC0gJ0xBTkdGVVNFX0lOSVRfT1JHX0lEPSR7TEFOR0ZVU0VfSU5JVF9PUkdfSUQ6LW15LW9yZ30nCiAgICAgIC0gJ0xBTkdGVVNFX0lOSVRfT1JHX05BTUU9JHtMQU5HRlVTRV9JTklUX09SR19OQU1FOi1NeSBPcmd9JwogICAgICAtICdMQU5HRlVTRV9JTklUX1BST0pFQ1RfSUQ9JHtMQU5HRlVTRV9JTklUX1BST0pFQ1RfSUQ6LW15LXByb2plY3R9JwogICAgICAtICdMQU5HRlVTRV9JTklUX1BST0pFQ1RfTkFNRT0ke0xBTkdGVVNFX0lOSVRfUFJPSkVDVF9OQU1FOi1NeSBQcm9qZWN0fScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLXEnCiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTozMDAwL2FwaS9wdWJsaWMvaGVhbHRoJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICBwb3N0Z3JlczoKICAgIGltYWdlOiAncG9zdGdyZXM6MTYtYWxwaW5lJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LWxhbmdmdXNlfScKICAgICAgLSBQT1NUR1JFU19QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgdm9sdW1lczoKICAgICAgLSAncGctZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLWggbG9jYWxob3N0IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCg==", + "compose": "eC1hcHAtZW52OgogIC0gJ05FWFRBVVRIX1VSTD0ke1NFUlZJQ0VfRlFETl9MQU5HRlVTRX0nCiAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzcWw6Ly8ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU306JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUBwb3N0Z3Jlczo1NDMyLyR7UE9TVEdSRVNfREI6LWxhbmdmdXNlLWRifScKICAtICdTQUxUPSR7U0VSVklDRV9QQVNTV09SRF9TQUxUfScKICAtICdFTkNSWVBUSU9OX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfNjRfTEFOR0ZVU0V9JwogIC0gJ1RFTEVNRVRSWV9FTkFCTEVEPSR7VEVMRU1FVFJZX0VOQUJMRUQ6LWZhbHNlfScKICAtICdMQU5HRlVTRV9FTkFCTEVfRVhQRVJJTUVOVEFMX0ZFQVRVUkVTPSR7TEFOR0ZVU0VfRU5BQkxFX0VYUEVSSU1FTlRBTF9GRUFUVVJFUzotZmFsc2V9JwogIC0gJ0NMSUNLSE9VU0VfTUlHUkFUSU9OX1VSTD1jbGlja2hvdXNlOi8vY2xpY2tob3VzZTo5MDAwJwogIC0gJ0NMSUNLSE9VU0VfVVJMPWh0dHA6Ly9jbGlja2hvdXNlOjgxMjMnCiAgLSAnQ0xJQ0tIT1VTRV9VU0VSPSR7U0VSVklDRV9VU0VSX0NMSUNLSE9VU0V9JwogIC0gJ0NMSUNLSE9VU0VfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX0NMSUNLSE9VU0V9JwogIC0gQ0xJQ0tIT1VTRV9DTFVTVEVSX0VOQUJMRUQ9ZmFsc2UKICAtICdMQU5HRlVTRV9VU0VfQVpVUkVfQkxPQj0ke0xBTkdGVVNFX1VTRV9BWlVSRV9CTE9COi1mYWxzZX0nCiAgLSAnTEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX0JVQ0tFVD0ke0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9CVUNLRVQ6LWxhbmdmdXNlfScKICAtICdMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfUkVHSU9OPSR7TEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX1JFR0lPTjotYXV0b30nCiAgLSAnTEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX0FDQ0VTU19LRVlfSUQ9JHtMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfQUNDRVNTX0tFWV9JRH0nCiAgLSAnTEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX1NFQ1JFVF9BQ0NFU1NfS0VZPSR7TEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX1NFQ1JFVF9BQ0NFU1NfS0VZfScKICAtICdMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfRU5EUE9JTlQ9JHtMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfRU5EUE9JTlR9JwogIC0gJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9GT1JDRV9QQVRIX1NUWUxFPSR7TEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX0ZPUkNFX1BBVEhfU1RZTEU6LXRydWV9JwogIC0gJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9QUkVGSVg9JHtMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfUFJFRklYOi1ldmVudHMvfScKICAtICdMQU5HRlVTRV9TM19NRURJQV9VUExPQURfQlVDS0VUPSR7TEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX0JVQ0tFVDotbGFuZ2Z1c2V9JwogIC0gJ0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9SRUdJT049JHtMQU5HRlVTRV9TM19NRURJQV9VUExPQURfUkVHSU9OOi1hdXRvfScKICAtICdMQU5HRlVTRV9TM19NRURJQV9VUExPQURfQUNDRVNTX0tFWV9JRD0ke0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9BQ0NFU1NfS0VZX0lEfScKICAtICdMQU5HRlVTRV9TM19NRURJQV9VUExPQURfU0VDUkVUX0FDQ0VTU19LRVk9JHtMQU5HRlVTRV9TM19NRURJQV9VUExPQURfU0VDUkVUX0FDQ0VTU19LRVl9JwogIC0gJ0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9FTkRQT0lOVD0ke0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9FTkRQT0lOVH0nCiAgLSAnTEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX0ZPUkNFX1BBVEhfU1RZTEU9JHtMQU5HRlVTRV9TM19NRURJQV9VUExPQURfRk9SQ0VfUEFUSF9TVFlMRTotdHJ1ZX0nCiAgLSAnTEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX1BSRUZJWD0ke0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9QUkVGSVg6LW1lZGlhL30nCiAgLSAnTEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0VOQUJMRUQ9JHtMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfRU5BQkxFRDotZmFsc2V9JwogIC0gJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9CVUNLRVQ9JHtMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfQlVDS0VUOi1sYW5nZnVzZX0nCiAgLSAnTEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX1BSRUZJWD0ke0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9QUkVGSVg6LWV4cG9ydHMvfScKICAtICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfUkVHSU9OPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX1JFR0lPTjotYXV0b30nCiAgLSAnTEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0VORFBPSU5UPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0VORFBPSU5UfScKICAtICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfRVhURVJOQUxfRU5EUE9JTlQ9JHtMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfRVhURVJOQUxfRU5EUE9JTlR9JwogIC0gJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9BQ0NFU1NfS0VZX0lEPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0FDQ0VTU19LRVlfSUR9JwogIC0gJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9TRUNSRVRfQUNDRVNTX0tFWT0ke0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9TRUNSRVRfQUNDRVNTX0tFWX0nCiAgLSAnTEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0ZPUkNFX1BBVEhfU1RZTEU9JHtMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfRk9SQ0VfUEFUSF9TVFlMRTotdHJ1ZX0nCiAgLSAnTEFOR0ZVU0VfSU5HRVNUSU9OX1FVRVVFX0RFTEFZX01TPSR7TEFOR0ZVU0VfSU5HRVNUSU9OX1FVRVVFX0RFTEFZX01TOi0xfScKICAtICdMQU5HRlVTRV9JTkdFU1RJT05fQ0xJQ0tIT1VTRV9XUklURV9JTlRFUlZBTF9NUz0ke0xBTkdGVVNFX0lOR0VTVElPTl9DTElDS0hPVVNFX1dSSVRFX0lOVEVSVkFMX01TOi0xMDAwfScKICAtIFJFRElTX0hPU1Q9cmVkaXMKICAtIFJFRElTX1BPUlQ9NjM3OQogIC0gJ1JFRElTX0FVVEg9JHtTRVJWSUNFX1BBU1NXT1JEX1JFRElTfScKICAtICdFTUFJTF9GUk9NX0FERFJFU1M9JHtFTUFJTF9GUk9NX0FERFJFU1M6LWFkbWluQGV4YW1wbGUuY29tfScKICAtICdTTVRQX0NPTk5FQ1RJT05fVVJMPSR7U01UUF9DT05ORUNUSU9OX1VSTDotfScKICAtICdORVhUQVVUSF9TRUNSRVQ9JHtTRVJWSUNFX0JBU0U2NF9ORVhUQVVUSFNFQ1JFVH0nCiAgLSAnQVVUSF9ESVNBQkxFX1NJR05VUD0ke0FVVEhfRElTQUJMRV9TSUdOVVA6LXRydWV9JwogIC0gJ0hPU1ROQU1FPSR7SE9TVE5BTUU6LTAuMC4wLjB9JwogIC0gJ0xBTkdGVVNFX0lOSVRfT1JHX0lEPSR7TEFOR0ZVU0VfSU5JVF9PUkdfSUQ6LW15LW9yZ30nCiAgLSAnTEFOR0ZVU0VfSU5JVF9PUkdfTkFNRT0ke0xBTkdGVVNFX0lOSVRfT1JHX05BTUU6LU15IE9yZ30nCiAgLSAnTEFOR0ZVU0VfSU5JVF9QUk9KRUNUX0lEPSR7TEFOR0ZVU0VfSU5JVF9QUk9KRUNUX0lEOi1teS1wcm9qZWN0fScKICAtICdMQU5HRlVTRV9JTklUX1BST0pFQ1RfTkFNRT0ke0xBTkdGVVNFX0lOSVRfUFJPSkVDVF9OQU1FOi1NeSBQcm9qZWN0fScKICAtICdMQU5HRlVTRV9JTklUX1VTRVJfRU1BSUw9JHtMQU5HRlVTRV9JTklUX1VTRVJfRU1BSUw6LWFkbWluQGV4YW1wbGUuY29tfScKICAtICdMQU5HRlVTRV9JTklUX1VTRVJfTkFNRT0ke1NFUlZJQ0VfVVNFUl9MQU5HRlVTRX0nCiAgLSAnTEFOR0ZVU0VfSU5JVF9VU0VSX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9MQU5HRlVTRX0nCnNlcnZpY2VzOgogIGxhbmdmdXNlOgogICAgaW1hZ2U6ICdsYW5nZnVzZS9sYW5nZnVzZTozJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgY2xpY2tob3VzZToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgZW52aXJvbm1lbnQ6CiAgICAgIDA6ICdORVhUQVVUSF9VUkw9JHtTRVJWSUNFX0ZRRE5fTEFOR0ZVU0V9JwogICAgICAxOiAnREFUQUJBU0VfVVJMPXBvc3RncmVzcWw6Ly8ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU306JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUBwb3N0Z3Jlczo1NDMyLyR7UE9TVEdSRVNfREI6LWxhbmdmdXNlLWRifScKICAgICAgMjogJ1NBTFQ9JHtTRVJWSUNFX1BBU1NXT1JEX1NBTFR9JwogICAgICAzOiAnRU5DUllQVElPTl9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X0xBTkdGVVNFfScKICAgICAgNDogJ1RFTEVNRVRSWV9FTkFCTEVEPSR7VEVMRU1FVFJZX0VOQUJMRUQ6LWZhbHNlfScKICAgICAgNTogJ0xBTkdGVVNFX0VOQUJMRV9FWFBFUklNRU5UQUxfRkVBVFVSRVM9JHtMQU5HRlVTRV9FTkFCTEVfRVhQRVJJTUVOVEFMX0ZFQVRVUkVTOi1mYWxzZX0nCiAgICAgIDY6ICdDTElDS0hPVVNFX01JR1JBVElPTl9VUkw9Y2xpY2tob3VzZTovL2NsaWNraG91c2U6OTAwMCcKICAgICAgNzogJ0NMSUNLSE9VU0VfVVJMPWh0dHA6Ly9jbGlja2hvdXNlOjgxMjMnCiAgICAgIDg6ICdDTElDS0hPVVNFX1VTRVI9JHtTRVJWSUNFX1VTRVJfQ0xJQ0tIT1VTRX0nCiAgICAgIDk6ICdDTElDS0hPVVNFX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9DTElDS0hPVVNFfScKICAgICAgMTA6IENMSUNLSE9VU0VfQ0xVU1RFUl9FTkFCTEVEPWZhbHNlCiAgICAgIDExOiAnTEFOR0ZVU0VfVVNFX0FaVVJFX0JMT0I9JHtMQU5HRlVTRV9VU0VfQVpVUkVfQkxPQjotZmFsc2V9JwogICAgICAxMjogJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9CVUNLRVQ9JHtMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfQlVDS0VUOi1sYW5nZnVzZX0nCiAgICAgIDEzOiAnTEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX1JFR0lPTj0ke0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9SRUdJT046LWF1dG99JwogICAgICAxNDogJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9BQ0NFU1NfS0VZX0lEPSR7TEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX0FDQ0VTU19LRVlfSUR9JwogICAgICAxNTogJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9TRUNSRVRfQUNDRVNTX0tFWT0ke0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9TRUNSRVRfQUNDRVNTX0tFWX0nCiAgICAgIDE2OiAnTEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX0VORFBPSU5UPSR7TEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX0VORFBPSU5UfScKICAgICAgMTc6ICdMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfRk9SQ0VfUEFUSF9TVFlMRT0ke0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9GT1JDRV9QQVRIX1NUWUxFOi10cnVlfScKICAgICAgMTg6ICdMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfUFJFRklYPSR7TEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX1BSRUZJWDotZXZlbnRzL30nCiAgICAgIDE5OiAnTEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX0JVQ0tFVD0ke0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9CVUNLRVQ6LWxhbmdmdXNlfScKICAgICAgMjA6ICdMQU5HRlVTRV9TM19NRURJQV9VUExPQURfUkVHSU9OPSR7TEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX1JFR0lPTjotYXV0b30nCiAgICAgIDIxOiAnTEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX0FDQ0VTU19LRVlfSUQ9JHtMQU5HRlVTRV9TM19NRURJQV9VUExPQURfQUNDRVNTX0tFWV9JRH0nCiAgICAgIDIyOiAnTEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX1NFQ1JFVF9BQ0NFU1NfS0VZPSR7TEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX1NFQ1JFVF9BQ0NFU1NfS0VZfScKICAgICAgMjM6ICdMQU5HRlVTRV9TM19NRURJQV9VUExPQURfRU5EUE9JTlQ9JHtMQU5HRlVTRV9TM19NRURJQV9VUExPQURfRU5EUE9JTlR9JwogICAgICAyNDogJ0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9GT1JDRV9QQVRIX1NUWUxFPSR7TEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX0ZPUkNFX1BBVEhfU1RZTEU6LXRydWV9JwogICAgICAyNTogJ0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9QUkVGSVg9JHtMQU5HRlVTRV9TM19NRURJQV9VUExPQURfUFJFRklYOi1tZWRpYS99JwogICAgICAyNjogJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9FTkFCTEVEPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0VOQUJMRUQ6LWZhbHNlfScKICAgICAgMjc6ICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfQlVDS0VUPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0JVQ0tFVDotbGFuZ2Z1c2V9JwogICAgICAyODogJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9QUkVGSVg9JHtMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfUFJFRklYOi1leHBvcnRzL30nCiAgICAgIDI5OiAnTEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX1JFR0lPTj0ke0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9SRUdJT046LWF1dG99JwogICAgICAzMDogJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9FTkRQT0lOVD0ke0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9FTkRQT0lOVH0nCiAgICAgIDMxOiAnTEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0VYVEVSTkFMX0VORFBPSU5UPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0VYVEVSTkFMX0VORFBPSU5UfScKICAgICAgMzI6ICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfQUNDRVNTX0tFWV9JRD0ke0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9BQ0NFU1NfS0VZX0lEfScKICAgICAgMzM6ICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfU0VDUkVUX0FDQ0VTU19LRVk9JHtMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfU0VDUkVUX0FDQ0VTU19LRVl9JwogICAgICAzNDogJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9GT1JDRV9QQVRIX1NUWUxFPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0ZPUkNFX1BBVEhfU1RZTEU6LXRydWV9JwogICAgICAzNTogJ0xBTkdGVVNFX0lOR0VTVElPTl9RVUVVRV9ERUxBWV9NUz0ke0xBTkdGVVNFX0lOR0VTVElPTl9RVUVVRV9ERUxBWV9NUzotMX0nCiAgICAgIDM2OiAnTEFOR0ZVU0VfSU5HRVNUSU9OX0NMSUNLSE9VU0VfV1JJVEVfSU5URVJWQUxfTVM9JHtMQU5HRlVTRV9JTkdFU1RJT05fQ0xJQ0tIT1VTRV9XUklURV9JTlRFUlZBTF9NUzotMTAwMH0nCiAgICAgIDM3OiBSRURJU19IT1NUPXJlZGlzCiAgICAgIDM4OiBSRURJU19QT1JUPTYzNzkKICAgICAgMzk6ICdSRURJU19BVVRIPSR7U0VSVklDRV9QQVNTV09SRF9SRURJU30nCiAgICAgIDQwOiAnRU1BSUxfRlJPTV9BRERSRVNTPSR7RU1BSUxfRlJPTV9BRERSRVNTOi1hZG1pbkBleGFtcGxlLmNvbX0nCiAgICAgIDQxOiAnU01UUF9DT05ORUNUSU9OX1VSTD0ke1NNVFBfQ09OTkVDVElPTl9VUkw6LX0nCiAgICAgIDQyOiAnTkVYVEFVVEhfU0VDUkVUPSR7U0VSVklDRV9CQVNFNjRfTkVYVEFVVEhTRUNSRVR9JwogICAgICA0MzogJ0FVVEhfRElTQUJMRV9TSUdOVVA9JHtBVVRIX0RJU0FCTEVfU0lHTlVQOi10cnVlfScKICAgICAgNDQ6ICdIT1NUTkFNRT0ke0hPU1ROQU1FOi0wLjAuMC4wfScKICAgICAgNDU6ICdMQU5HRlVTRV9JTklUX09SR19JRD0ke0xBTkdGVVNFX0lOSVRfT1JHX0lEOi1teS1vcmd9JwogICAgICA0NjogJ0xBTkdGVVNFX0lOSVRfT1JHX05BTUU9JHtMQU5HRlVTRV9JTklUX09SR19OQU1FOi1NeSBPcmd9JwogICAgICA0NzogJ0xBTkdGVVNFX0lOSVRfUFJPSkVDVF9JRD0ke0xBTkdGVVNFX0lOSVRfUFJPSkVDVF9JRDotbXktcHJvamVjdH0nCiAgICAgIDQ4OiAnTEFOR0ZVU0VfSU5JVF9QUk9KRUNUX05BTUU9JHtMQU5HRlVTRV9JTklUX1BST0pFQ1RfTkFNRTotTXkgUHJvamVjdH0nCiAgICAgIDQ5OiAnTEFOR0ZVU0VfSU5JVF9VU0VSX0VNQUlMPSR7TEFOR0ZVU0VfSU5JVF9VU0VSX0VNQUlMOi1hZG1pbkBleGFtcGxlLmNvbX0nCiAgICAgIDUwOiAnTEFOR0ZVU0VfSU5JVF9VU0VSX05BTUU9JHtTRVJWSUNFX1VTRVJfTEFOR0ZVU0V9JwogICAgICA1MTogJ0xBTkdGVVNFX0lOSVRfVVNFUl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTEFOR0ZVU0V9JwogICAgICBTRVJWSUNFX0ZRRE5fTEFOR0ZVU0VfMzAwMDogJyR7U0VSVklDRV9GUUROX0xBTkdGVVNFXzMwMDB9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHdnZXQKICAgICAgICAtICctcScKICAgICAgICAtICctLXNwaWRlcicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjMwMDAvYXBpL3B1YmxpYy9oZWFsdGgnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAzCiAgbGFuZ2Z1c2Utd29ya2VyOgogICAgaW1hZ2U6ICdsYW5nZnVzZS9sYW5nZnVzZS13b3JrZXI6MycKICAgIGVudmlyb25tZW50OgogICAgICAtICdORVhUQVVUSF9VUkw9JHtTRVJWSUNFX0ZRRE5fTEFOR0ZVU0V9JwogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QHBvc3RncmVzOjU0MzIvJHtQT1NUR1JFU19EQjotbGFuZ2Z1c2UtZGJ9JwogICAgICAtICdTQUxUPSR7U0VSVklDRV9QQVNTV09SRF9TQUxUfScKICAgICAgLSAnRU5DUllQVElPTl9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X0xBTkdGVVNFfScKICAgICAgLSAnVEVMRU1FVFJZX0VOQUJMRUQ9JHtURUxFTUVUUllfRU5BQkxFRDotZmFsc2V9JwogICAgICAtICdMQU5HRlVTRV9FTkFCTEVfRVhQRVJJTUVOVEFMX0ZFQVRVUkVTPSR7TEFOR0ZVU0VfRU5BQkxFX0VYUEVSSU1FTlRBTF9GRUFUVVJFUzotZmFsc2V9JwogICAgICAtICdDTElDS0hPVVNFX01JR1JBVElPTl9VUkw9Y2xpY2tob3VzZTovL2NsaWNraG91c2U6OTAwMCcKICAgICAgLSAnQ0xJQ0tIT1VTRV9VUkw9aHR0cDovL2NsaWNraG91c2U6ODEyMycKICAgICAgLSAnQ0xJQ0tIT1VTRV9VU0VSPSR7U0VSVklDRV9VU0VSX0NMSUNLSE9VU0V9JwogICAgICAtICdDTElDS0hPVVNFX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9DTElDS0hPVVNFfScKICAgICAgLSBDTElDS0hPVVNFX0NMVVNURVJfRU5BQkxFRD1mYWxzZQogICAgICAtICdMQU5HRlVTRV9VU0VfQVpVUkVfQkxPQj0ke0xBTkdGVVNFX1VTRV9BWlVSRV9CTE9COi1mYWxzZX0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9CVUNLRVQ9JHtMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfQlVDS0VUOi1sYW5nZnVzZX0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9SRUdJT049JHtMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfUkVHSU9OOi1hdXRvfScKICAgICAgLSAnTEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX0FDQ0VTU19LRVlfSUQ9JHtMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfQUNDRVNTX0tFWV9JRH0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9TRUNSRVRfQUNDRVNTX0tFWT0ke0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9TRUNSRVRfQUNDRVNTX0tFWX0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9FTkRQT0lOVD0ke0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9FTkRQT0lOVH0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX0VWRU5UX1VQTE9BRF9GT1JDRV9QQVRIX1NUWUxFPSR7TEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX0ZPUkNFX1BBVEhfU1RZTEU6LXRydWV9JwogICAgICAtICdMQU5HRlVTRV9TM19FVkVOVF9VUExPQURfUFJFRklYPSR7TEFOR0ZVU0VfUzNfRVZFTlRfVVBMT0FEX1BSRUZJWDotZXZlbnRzL30nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9CVUNLRVQ9JHtMQU5HRlVTRV9TM19NRURJQV9VUExPQURfQlVDS0VUOi1sYW5nZnVzZX0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9SRUdJT049JHtMQU5HRlVTRV9TM19NRURJQV9VUExPQURfUkVHSU9OOi1hdXRvfScKICAgICAgLSAnTEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX0FDQ0VTU19LRVlfSUQ9JHtMQU5HRlVTRV9TM19NRURJQV9VUExPQURfQUNDRVNTX0tFWV9JRH0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9TRUNSRVRfQUNDRVNTX0tFWT0ke0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9TRUNSRVRfQUNDRVNTX0tFWX0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9FTkRQT0lOVD0ke0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9FTkRQT0lOVH0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX01FRElBX1VQTE9BRF9GT1JDRV9QQVRIX1NUWUxFPSR7TEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX0ZPUkNFX1BBVEhfU1RZTEU6LXRydWV9JwogICAgICAtICdMQU5HRlVTRV9TM19NRURJQV9VUExPQURfUFJFRklYPSR7TEFOR0ZVU0VfUzNfTUVESUFfVVBMT0FEX1BSRUZJWDotbWVkaWEvfScKICAgICAgLSAnTEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0VOQUJMRUQ9JHtMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfRU5BQkxFRDotZmFsc2V9JwogICAgICAtICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfQlVDS0VUPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0JVQ0tFVDotbGFuZ2Z1c2V9JwogICAgICAtICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfUFJFRklYPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX1BSRUZJWDotZXhwb3J0cy99JwogICAgICAtICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfUkVHSU9OPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX1JFR0lPTjotYXV0b30nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9FTkRQT0lOVD0ke0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9FTkRQT0lOVH0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9FWFRFUk5BTF9FTkRQT0lOVD0ke0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9FWFRFUk5BTF9FTkRQT0lOVH0nCiAgICAgIC0gJ0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9BQ0NFU1NfS0VZX0lEPSR7TEFOR0ZVU0VfUzNfQkFUQ0hfRVhQT1JUX0FDQ0VTU19LRVlfSUR9JwogICAgICAtICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfU0VDUkVUX0FDQ0VTU19LRVk9JHtMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfU0VDUkVUX0FDQ0VTU19LRVl9JwogICAgICAtICdMQU5HRlVTRV9TM19CQVRDSF9FWFBPUlRfRk9SQ0VfUEFUSF9TVFlMRT0ke0xBTkdGVVNFX1MzX0JBVENIX0VYUE9SVF9GT1JDRV9QQVRIX1NUWUxFOi10cnVlfScKICAgICAgLSAnTEFOR0ZVU0VfSU5HRVNUSU9OX1FVRVVFX0RFTEFZX01TPSR7TEFOR0ZVU0VfSU5HRVNUSU9OX1FVRVVFX0RFTEFZX01TOi0xfScKICAgICAgLSAnTEFOR0ZVU0VfSU5HRVNUSU9OX0NMSUNLSE9VU0VfV1JJVEVfSU5URVJWQUxfTVM9JHtMQU5HRlVTRV9JTkdFU1RJT05fQ0xJQ0tIT1VTRV9XUklURV9JTlRFUlZBTF9NUzotMTAwMH0nCiAgICAgIC0gUkVESVNfSE9TVD1yZWRpcwogICAgICAtIFJFRElTX1BPUlQ9NjM3OQogICAgICAtICdSRURJU19BVVRIPSR7U0VSVklDRV9QQVNTV09SRF9SRURJU30nCiAgICAgIC0gJ0VNQUlMX0ZST01fQUREUkVTUz0ke0VNQUlMX0ZST01fQUREUkVTUzotYWRtaW5AZXhhbXBsZS5jb219JwogICAgICAtICdTTVRQX0NPTk5FQ1RJT05fVVJMPSR7U01UUF9DT05ORUNUSU9OX1VSTDotfScKICAgICAgLSAnTkVYVEFVVEhfU0VDUkVUPSR7U0VSVklDRV9CQVNFNjRfTkVYVEFVVEhTRUNSRVR9JwogICAgICAtICdBVVRIX0RJU0FCTEVfU0lHTlVQPSR7QVVUSF9ESVNBQkxFX1NJR05VUDotdHJ1ZX0nCiAgICAgIC0gJ0hPU1ROQU1FPSR7SE9TVE5BTUU6LTAuMC4wLjB9JwogICAgICAtICdMQU5HRlVTRV9JTklUX09SR19JRD0ke0xBTkdGVVNFX0lOSVRfT1JHX0lEOi1teS1vcmd9JwogICAgICAtICdMQU5HRlVTRV9JTklUX09SR19OQU1FPSR7TEFOR0ZVU0VfSU5JVF9PUkdfTkFNRTotTXkgT3JnfScKICAgICAgLSAnTEFOR0ZVU0VfSU5JVF9QUk9KRUNUX0lEPSR7TEFOR0ZVU0VfSU5JVF9QUk9KRUNUX0lEOi1teS1wcm9qZWN0fScKICAgICAgLSAnTEFOR0ZVU0VfSU5JVF9QUk9KRUNUX05BTUU9JHtMQU5HRlVTRV9JTklUX1BST0pFQ1RfTkFNRTotTXkgUHJvamVjdH0nCiAgICAgIC0gJ0xBTkdGVVNFX0lOSVRfVVNFUl9FTUFJTD0ke0xBTkdGVVNFX0lOSVRfVVNFUl9FTUFJTDotYWRtaW5AZXhhbXBsZS5jb219JwogICAgICAtICdMQU5HRlVTRV9JTklUX1VTRVJfTkFNRT0ke1NFUlZJQ0VfVVNFUl9MQU5HRlVTRX0nCiAgICAgIC0gJ0xBTkdGVVNFX0lOSVRfVVNFUl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTEFOR0ZVU0V9JwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgY2xpY2tob3VzZToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogIHBvc3RncmVzOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNy1hbHBpbmUnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotbGFuZ2Z1c2UtZGJ9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2xhbmdmdXNlX3Bvc3RncmVzX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1oIGxvY2FsaG9zdCAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAxMAogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczo4JwogICAgY29tbWFuZDoKICAgICAgLSBzaAogICAgICAtICctYycKICAgICAgLSAncmVkaXMtc2VydmVyIC0tcmVxdWlyZXBhc3MgIiRTRVJWSUNFX1BBU1NXT1JEX1JFRElTIicKICAgIGVudmlyb25tZW50OgogICAgICAtICdSRURJU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUkVESVN9JwogICAgdm9sdW1lczoKICAgICAgLSAnbGFuZ2Z1c2VfcmVkaXNfZGF0YTovZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSByZWRpcy1jbGkKICAgICAgICAtICctYScKICAgICAgICAtICRTRVJWSUNFX1BBU1NXT1JEX1JFRElTCiAgICAgICAgLSBQSU5HCiAgICAgIGludGVydmFsOiAzcwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTAKICBjbGlja2hvdXNlOgogICAgaW1hZ2U6ICdjbGlja2hvdXNlL2NsaWNraG91c2Utc2VydmVyOmxhdGVzdCcKICAgIHVzZXI6ICcxMDE6MTAxJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0NMSUNLSE9VU0VfREI9JHtDTElDS0hPVVNFX0RCOi1kZWZhdWx0fScKICAgICAgLSAnQ0xJQ0tIT1VTRV9VU0VSPSR7U0VSVklDRV9VU0VSX0NMSUNLSE9VU0V9JwogICAgICAtICdDTElDS0hPVVNFX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9DTElDS0hPVVNFfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2xhbmdmdXNlX2NsaWNraG91c2VfZGF0YTovdmFyL2xpYi9jbGlja2hvdXNlJwogICAgICAtICdsYW5nZnVzZV9jbGlja2hvdXNlX2xvZ3M6L3Zhci9sb2cvY2xpY2tob3VzZS1zZXJ2ZXInCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDogJ3dnZXQgLS1uby12ZXJib3NlIC0tdHJpZXM9MSAtLXNwaWRlciBodHRwOi8vbG9jYWxob3N0OjgxMjMvcGluZyB8fCBleGl0IDEnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAxMAo=", "tags": [ "ai", "qdrant", @@ -1769,7 +1786,7 @@ "observation", "metrics" ], - "logo": "svgs/langfuse.png", + "logo": "svgs/langfuse.svg", "minversion": "0.0.0", "port": "3000" }, @@ -1949,7 +1966,7 @@ "matrix": { "documentation": "https://matrix.org/docs/chat_basics/matrix-for-im/?utm_source=coolify.io", "slogan": "Chat securely with your family, friends, community, or build great apps with Matrix!", - "compose": "c2VydmljZXM6CiAgbWF0cml4OgogICAgaW1hZ2U6ICdtYXRyaXhkb3Rvcmcvc3luYXBzZTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTUFUUklYXzgwMDgKICAgICAgLSAnU1lOQVBTRV9TRVJWRVJfTkFNRT0ke1NFUlZJQ0VfVVJMX01BVFJJWH0nCiAgICAgIC0gJ1NZTkFQU0VfUkVQT1JUX1NUQVRTPSR7U1lOQVBTRV9SRVBPUlRfU1RBVFM6LW5vfScKICAgICAgLSAnRU5BQkxFX1JFR0lTVFJBVElPTj0ke0VOQUJMRV9SRUdJU1RSQVRJT046LWZhbHNlfScKICAgICAgLSAnUkVDQVBUQ0hBX1BVQkxJQ19LRVk9JHtSRUNBUFRDSEFfUFVCTElDX0tFWX0nCiAgICAgIC0gJ1JFQ0FQVENIQV9QUklWQVRFX0tFWT0ke1JFQ0FQVENIQV9QUklWQVRFX0tFWX0nCiAgICAgIC0gJ19TRVJWRVJfTkFNRT0ke1NFUlZJQ0VfVVJMX01BVFJJWH0nCiAgICAgIC0gJ19BRE1JTl9OQU1FPSR7U0VSVklDRV9VU0VSX0FETUlOfScKICAgICAgLSAnX0FETUlOX1BBU1M9JHtTRVJWSUNFX1BBU1NXT1JEX0FETUlOfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ21hdHJpeC1kYXRhOi9kYXRhJwogICAgZW50cnlwb2ludDoKICAgICAgLSAvYmluL2Jhc2gKICAgICAgLSAnLWMnCiAgICAgIC0gIiEgdGVzdCAtZiAvZGF0YS9ob21lc2VydmVyLnlhbWwgJiYgL3N0YXJ0LnB5IGdlbmVyYXRlXG5cbiMgcmVnaXN0cmF0aW9uX3NoYXJlZF9zZWNyZXRcbmdyZXAgXCJyZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldFwiIC9kYXRhL2hvbWVzZXJ2ZXIueWFtbCBcXFxufCBhd2sgJ3twcmludCAkMn0nID4gLi9yZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldFxuXG4jIG1hY2Fyb29uX3NlY3JldF9rZXlcbmdyZXAgXCJtYWNhcm9vbl9zZWNyZXRfa2V5XCIgL2RhdGEvaG9tZXNlcnZlci55YW1sIFxcXG58IGF3ayAne3ByaW50ICQyfScgPiAuL21hY2Fyb29uX3NlY3JldF9rZXlcblxuIyBmb3JtX3NlY3JldFxuZ3JlcCBcImZvcm1fc2VjcmV0XCIgL2RhdGEvaG9tZXNlcnZlci55YW1sIFxcXG58IGF3ayAne3ByaW50ICQyfScgPiAuL2Zvcm1fc2VjcmV0XG5cbiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjXG4jICAgICAgICAgICAgICAgICAgICAgICAgI1xuIyBob21lc2VydmVyLnlhbWw6IHN0YXJ0ICNcbiMgICAgICAgICAgICAgICAgICAgICAgICAjXG4jIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjI1xuY2F0IDw8RU9GID4gL2RhdGEvaG9tZXNlcnZlci55YW1sXG5zZXJ2ZXJfbmFtZTogXCIke1NFUlZJQ0VfVVJMX01BVFJJWH1cIlxucGlkX2ZpbGU6IC9kYXRhL2hvbWVzZXJ2ZXIucGlkXG5cbiMgc2VydmVyXG5saXN0ZW5lcnM6XG4gIC0gcG9ydDogODAwOFxuICAgIHRsczogZmFsc2VcbiAgICB0eXBlOiBodHRwXG4gICAgeF9mb3J3YXJkZWQ6IHRydWVcbiAgICByZXNvdXJjZXM6XG4gICAgICAtIG5hbWVzOiBbY2xpZW50LCBmZWRlcmF0aW9uXVxuICAgICAgICBjb21wcmVzczogZmFsc2VcblxuIyBkYXRhYmFzZVxuZGF0YWJhc2U6XG4gIG5hbWU6IHNxbGl0ZTNcbiAgYXJnczpcbiAgICBkYXRhYmFzZTogL2RhdGEvaG9tZXNlcnZlci5kYlxuXG4jIGdlbmVyYWxcbmxvZ19jb25maWc6IFwiL2RhdGEvJHtTRVJWSUNFX1VSTF9NQVRSSVh9LmxvZy5jb25maWdcIlxubWVkaWFfc3RvcmVfcGF0aDogL2RhdGEvbWVkaWFfc3RvcmVcbnJlcG9ydF9zdGF0czogZmFsc2VcblxuIyBzZWNyZXRzXG5yZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldDogJCg8Li9yZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldClcbm1hY2Fyb29uX3NlY3JldF9rZXk6ICQoPC4vbWFjYXJvb25fc2VjcmV0X2tleSlcbmZvcm1fc2VjcmV0OiAkKDwuL2Zvcm1fc2VjcmV0KVxuc2lnbmluZ19rZXlfcGF0aDogXCIvZGF0YS8ke1NFUlZJQ0VfVVJMX01BVFJJWH0uc2lnbmluZy5rZXlcIlxuXG4jcm9vbXNcbmF1dG9fam9pbl9yb29tczpcbiAgLSBcIiNnZW5lcmFsOiR7U0VSVklDRV9VUkxfTUFUUklYfVwiXG5cbiMgZmVkZXJhdGlvblxudHJ1c3RlZF9rZXlfc2VydmVyczpcbiAgLSBzZXJ2ZXJfbmFtZTogXCJtYXRyaXgub3JnXCJcbmF1dG9jcmVhdGVfYXV0b19qb2luX3Jvb21zX2ZlZGVyYXRlZDogZmFsc2VcbmFsbG93X3B1YmxpY19yb29tc19vdmVyX2ZlZGVyYXRpb246IGZhbHNlXG5FT0ZcbiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjI1xuIyAgICAgICAgICAgICAgICAgICAgICAjXG4jIGhvbWVzZXJ2ZXIueWFtbDogZW5kICNcbiMgICAgICAgICAgICAgICAgICAgICAgI1xuIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjXG5cbnRlc3QgLW4gXCIke0VOQUJMRV9SRUdJU1RSQVRJT059XCIgJiYgISBncmVwIFwiI3JlZ2lzdHJhdGlvblwiIC9kYXRhL2hvbWVzZXJ2ZXIueWFtbCAmPi9kZXYvbnVsbCBcXFxuJiYgZWNobyA+PiAvZGF0YS9ob21lc2VydmVyLnlhbWwgXFxcbiYmIGNhdCA8PEVPRiA+PiAvZGF0YS9ob21lc2VydmVyLnlhbWxcbiNyZWdpc3RyYXRpb25cbmVuYWJsZV9yZWdpc3RyYXRpb246IHRydWUgICMgQWxsb3dzIHVzZXJzIHRvIHJlZ2lzdGVyIG9uIHlvdXIgc2VydmVyLlxuRU9GXG5cbiEgZ3JlcCAke1JFQ0FQVENIQV9QVUJMSUNfS0VZfSAvZGF0YS9ob21lc2VydmVyLnlhbWwgJj4vZGV2L251bGwgXFxcbiYmIGVjaG8gPj4gL2RhdGEvaG9tZXNlcnZlci55YW1sIFxcXG4mJiBjYXQgPDxFT0YgPj4gL2RhdGEvaG9tZXNlcnZlci55YW1sXG4jIHJlQ0FQVENIQSBzZXR0aW5nc1xuZW5hYmxlX3JlZ2lzdHJhdGlvbl9jYXB0Y2hhOiB0cnVlICAjIEVuYWJsZXMgQ0FQVENIQSBmb3IgcmVnaXN0cmF0aW9ucy5cbnJlY2FwdGNoYV9wdWJsaWNfa2V5OiBcIiR7UkVDQVBUQ0hBX1BVQkxJQ19LRVl9XCJcbnJlY2FwdGNoYV9wcml2YXRlX2tleTogXCIke1JFQ0FQVENIQV9QUklWQVRFX0tFWX1cIlxucmVjYXB0Y2hhX3NpdGV2ZXJpZnlfYXBpOiBcImh0dHBzOi8vd3d3Lmdvb2dsZS5jb20vcmVjYXB0Y2hhL2FwaS9zaXRldmVyaWZ5XCJcbkVPRlxuXG5yZWdpc3Rlcl9hZG1pbigpe1xuICB3aGlsZSAhIGN1cmwgLUkgbG9jYWxob3N0OjgwMDggJj4vZGV2L251bGw7IGRvXG4gICAgc2xlZXAgMVxuICBkb25lXG4gIHJlZ2lzdGVyX25ld19tYXRyaXhfdXNlciBcXFxuICAgIC1hIFxcXG4gICAgLXUgJHtTRVJWSUNFX1VTRVJfQURNSU59IFxcXG4gICAgLXAgJHtTRVJWSUNFX1BBU1NXT1JEX0FETUlOfSBcXFxuICAgIC1jIC9kYXRhL2hvbWVzZXJ2ZXIueWFtbCBcXFxuICAgIGh0dHA6Ly9sb2NhbGhvc3Q6ODAwOCAmPi9kZXYvbnVsbFxufVxucmVnaXN0ZXJfYWRtaW4gJlxuXG4vc3RhcnQucHlcbiIKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLUknCiAgICAgICAgLSAnbG9jYWxob3N0OjgwMDgnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAzcwogICAgICByZXRyaWVzOiA1Cg==", + "compose": "c2VydmljZXM6CiAgbWF0cml4OgogICAgaW1hZ2U6ICdtYXRyaXhkb3Rvcmcvc3luYXBzZTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTUFUUklYXzgwMDgKICAgICAgLSAnU1lOQVBTRV9TRVJWRVJfTkFNRT0ke1NFUlZJQ0VfVVJMX01BVFJJWH0nCiAgICAgIC0gJ1NZTkFQU0VfUkVQT1JUX1NUQVRTPSR7U1lOQVBTRV9SRVBPUlRfU1RBVFM6LW5vfScKICAgICAgLSAnRU5BQkxFX1JFR0lTVFJBVElPTj0ke0VOQUJMRV9SRUdJU1RSQVRJT046LWZhbHNlfScKICAgICAgLSAnUkVDQVBUQ0hBX1BVQkxJQ19LRVk9JHtSRUNBUFRDSEFfUFVCTElDX0tFWX0nCiAgICAgIC0gJ1JFQ0FQVENIQV9QUklWQVRFX0tFWT0ke1JFQ0FQVENIQV9QUklWQVRFX0tFWX0nCiAgICAgIC0gJ19TRVJWRVJfTkFNRT0ke1NFUlZJQ0VfVVJMX01BVFJJWH0nCiAgICAgIC0gJ19BRE1JTl9OQU1FPSR7U0VSVklDRV9VU0VSX0FETUlOfScKICAgICAgLSAnX0FETUlOX1BBU1M9JHtTRVJWSUNFX1BBU1NXT1JEX0FETUlOfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ21hdHJpeC1kYXRhOi9kYXRhJwogICAgZW50cnlwb2ludDoKICAgICAgLSAvYmluL2Jhc2gKICAgICAgLSAnLWMnCiAgICAgIC0gIiEgdGVzdCAtZiAvZGF0YS9ob21lc2VydmVyLnlhbWwgJiYgL3N0YXJ0LnB5IGdlbmVyYXRlXG5cbiMgcmVnaXN0cmF0aW9uX3NoYXJlZF9zZWNyZXRcbmdyZXAgXCJyZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldFwiIC9kYXRhL2hvbWVzZXJ2ZXIueWFtbCBcXFxufCBhd2sgJ3twcmludCAkMn0nID4gLi9yZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldFxuXG4jIG1hY2Fyb29uX3NlY3JldF9rZXlcbmdyZXAgXCJtYWNhcm9vbl9zZWNyZXRfa2V5XCIgL2RhdGEvaG9tZXNlcnZlci55YW1sIFxcXG58IGF3ayAne3ByaW50ICQyfScgPiAuL21hY2Fyb29uX3NlY3JldF9rZXlcblxuIyBmb3JtX3NlY3JldFxuZ3JlcCBcImZvcm1fc2VjcmV0XCIgL2RhdGEvaG9tZXNlcnZlci55YW1sIFxcXG58IGF3ayAne3ByaW50ICQyfScgPiAuL2Zvcm1fc2VjcmV0XG5cbiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjXG4jICAgICAgICAgICAgICAgICAgICAgICAgI1xuIyBob21lc2VydmVyLnlhbWw6IHN0YXJ0ICNcbiMgICAgICAgICAgICAgICAgICAgICAgICAjXG4jIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjI1xuY2F0IDw8RU9GID4gL2RhdGEvaG9tZXNlcnZlci55YW1sXG5zZXJ2ZXJfbmFtZTogXCIke1NFUlZJQ0VfVVJMX01BVFJJWH1cIlxucGlkX2ZpbGU6IC9kYXRhL2hvbWVzZXJ2ZXIucGlkXG5cbiMgc2VydmVyXG5saXN0ZW5lcnM6XG4gIC0gcG9ydDogODAwOFxuICAgIHRsczogZmFsc2VcbiAgICB0eXBlOiBodHRwXG4gICAgeF9mb3J3YXJkZWQ6IHRydWVcbiAgICByZXNvdXJjZXM6XG4gICAgICAtIG5hbWVzOiBbY2xpZW50LCBmZWRlcmF0aW9uXVxuICAgICAgICBjb21wcmVzczogZmFsc2VcblxuIyBkYXRhYmFzZVxuZGF0YWJhc2U6XG4gIG5hbWU6IHNxbGl0ZTNcbiAgYXJnczpcbiAgICBkYXRhYmFzZTogL2RhdGEvaG9tZXNlcnZlci5kYlxuXG4jIGdlbmVyYWxcbmxvZ19jb25maWc6IFwiL2RhdGEvJHtTRVJWSUNFX1VSTF9NQVRSSVh9LmxvZy5jb25maWdcIlxubWVkaWFfc3RvcmVfcGF0aDogL2RhdGEvbWVkaWFfc3RvcmVcbnJlcG9ydF9zdGF0czogZmFsc2VcblxuIyBzZWNyZXRzXG5yZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldDogJCg8Li9yZWdpc3RyYXRpb25fc2hhcmVkX3NlY3JldClcbm1hY2Fyb29uX3NlY3JldF9rZXk6ICQoPC4vbWFjYXJvb25fc2VjcmV0X2tleSlcbmZvcm1fc2VjcmV0OiAkKDwuL2Zvcm1fc2VjcmV0KVxuc2lnbmluZ19rZXlfcGF0aDogXCIvZGF0YS8ke1NFUlZJQ0VfVVJMX01BVFJJWH0uc2lnbmluZy5rZXlcIlxuXG4jcm9vbXNcbmF1dG9fam9pbl9yb29tczpcbiAgLSBcIiNnZW5lcmFsOiR7U0VSVklDRV9VUkxfTUFUUklYfVwiXG5cbiMgZmVkZXJhdGlvblxudHJ1c3RlZF9rZXlfc2VydmVyczpcbiAgLSBzZXJ2ZXJfbmFtZTogXCJtYXRyaXgub3JnXCJcbmF1dG9jcmVhdGVfYXV0b19qb2luX3Jvb21zX2ZlZGVyYXRlZDogZmFsc2VcbmFsbG93X3B1YmxpY19yb29tc19vdmVyX2ZlZGVyYXRpb246IGZhbHNlXG5FT0ZcbiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjI1xuIyAgICAgICAgICAgICAgICAgICAgICAjXG4jIGhvbWVzZXJ2ZXIueWFtbDogZW5kICNcbiMgICAgICAgICAgICAgICAgICAgICAgI1xuIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjXG5cblsgXCIke0VOQUJMRV9SRUdJU1RSQVRJT059XCIgPSBcInRydWVcIiBdICYmICEgZ3JlcCBcIiNyZWdpc3RyYXRpb25cIiAvZGF0YS9ob21lc2VydmVyLnlhbWwgJj4vZGV2L251bGwgXFxcbiYmIGVjaG8gPj4gL2RhdGEvaG9tZXNlcnZlci55YW1sIFxcXG4mJiBjYXQgPDxFT0YgPj4gL2RhdGEvaG9tZXNlcnZlci55YW1sXG4jcmVnaXN0cmF0aW9uXG5lbmFibGVfcmVnaXN0cmF0aW9uOiB0cnVlICAjIEFsbG93cyB1c2VycyB0byByZWdpc3RlciBvbiB5b3VyIHNlcnZlci5cbkVPRlxuXG5bIC1uIFwiJHtSRUNBUFRDSEFfUFVCTElDX0tFWX1cIiBdICYmICEgZ3JlcCBcIiR7UkVDQVBUQ0hBX1BVQkxJQ19LRVl9XCIgL2RhdGEvaG9tZXNlcnZlci55YW1sICY+L2Rldi9udWxsIFxcXG4mJiBlY2hvID4+IC9kYXRhL2hvbWVzZXJ2ZXIueWFtbCBcXFxuJiYgY2F0IDw8RU9GID4+IC9kYXRhL2hvbWVzZXJ2ZXIueWFtbFxuIyByZUNBUFRDSEEgc2V0dGluZ3NcbmVuYWJsZV9yZWdpc3RyYXRpb25fY2FwdGNoYTogdHJ1ZSAgIyBFbmFibGVzIENBUFRDSEEgZm9yIHJlZ2lzdHJhdGlvbnMuXG5yZWNhcHRjaGFfcHVibGljX2tleTogXCIke1JFQ0FQVENIQV9QVUJMSUNfS0VZfVwiXG5yZWNhcHRjaGFfcHJpdmF0ZV9rZXk6IFwiJHtSRUNBUFRDSEFfUFJJVkFURV9LRVl9XCJcbnJlY2FwdGNoYV9zaXRldmVyaWZ5X2FwaTogXCJodHRwczovL3d3dy5nb29nbGUuY29tL3JlY2FwdGNoYS9hcGkvc2l0ZXZlcmlmeVwiXG5FT0ZcblxucmVnaXN0ZXJfYWRtaW4oKXtcbiAgd2hpbGUgISBjdXJsIC1JIGxvY2FsaG9zdDo4MDA4ICY+L2Rldi9udWxsOyBkb1xuICAgIHNsZWVwIDFcbiAgZG9uZVxuICByZWdpc3Rlcl9uZXdfbWF0cml4X3VzZXIgXFxcbiAgICAtYSBcXFxuICAgIC11ICR7U0VSVklDRV9VU0VSX0FETUlOfSBcXFxuICAgIC1wICR7U0VSVklDRV9QQVNTV09SRF9BRE1JTn0gXFxcbiAgICAtYyAvZGF0YS9ob21lc2VydmVyLnlhbWwgXFxcbiAgICBodHRwOi8vbG9jYWxob3N0OjgwMDggJj4vZGV2L251bGxcbn1cbnJlZ2lzdGVyX2FkbWluICZcblxuL3N0YXJ0LnB5XG4iCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1JJwogICAgICAgIC0gJ2xvY2FsaG9zdDo4MDA4JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogM3MKICAgICAgcmV0cmllczogNQo=", "tags": [ "chat", "slack", @@ -2717,6 +2734,21 @@ "logo": "svgs/phpmyadmin.svg", "minversion": "0.0.0" }, + "pi-hole": { + "documentation": "https://pi-hole.net/?utm_source=coolify.io", + "slogan": "Network-wide Ad Blocking", + "compose": "c2VydmljZXM6CiAgcGlob2xlOgogICAgaW1hZ2U6ICdwaWhvbGUvcGlob2xlOmxhdGVzdCcKICAgIHBvcnRzOgogICAgICAtICc1Mzo1My90Y3AnCiAgICAgIC0gJzUzOjUzL3VkcCcKICAgICAgLSAnNjc6NjcvdWRwJwogICAgICAtICcxMjM6MTIzL3VkcCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9QSUhPTEVfODAKICAgICAgLSAnVFo9JHtUWjotRXVyb3BlL0xvbmRvbn0nCiAgICAgIC0gJ0ZUTENPTkZfd2Vic2VydmVyX2FwaV9wYXNzd29yZD0ke1NFUlZJQ0VfUEFTU1dPUkRfUElIT0xFfScKICAgICAgLSAnRlRMQ09ORl9kbnNfbGlzdGVuaW5nTW9kZT0ke0ZUTENPTkZfZG5zX2xpc3RlbmluZ01vZGU6LWFsbH0nCiAgICB2b2x1bWVzOgogICAgICAtICdwaWhvbGUtZGF0YTovZXRjL3BpaG9sZScKICAgIGNhcF9hZGQ6CiAgICAgIC0gTkVUX0FETUlOCiAgICAgIC0gU1lTX1RJTUUK", + "tags": [ + "ad-block", + "dns", + "sinkhole", + "ntp", + "dhcp" + ], + "logo": "svgs/pihole.svg", + "minversion": "0.0.0", + "port": "80" + }, "pingvinshare-with-clamav": { "documentation": "https://github.com/stonith404/pingvin-share?utm_source=coolify.io", "slogan": "A self-hosted file sharing platform that combines lightness and beauty, perfect for seamless and efficient file sharing.", @@ -2842,7 +2874,7 @@ "postiz": { "documentation": "https://docs.postiz.com?utm_source=coolify.io", "slogan": "Open source social media scheduling tool.", - "compose": "c2VydmljZXM6CiAgcG9zdGl6OgogICAgaW1hZ2U6ICdnaGNyLmlvL2dpdHJvb21ocS9wb3N0aXotYXBwOnYxLjYwLjEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fUE9TVElaXzUwMDAKICAgICAgLSAnTUFJTl9VUkw9JHtTRVJWSUNFX0ZRRE5fUE9TVElafScKICAgICAgLSAnRlJPTlRFTkRfVVJMPSR7U0VSVklDRV9GUUROX1BPU1RJWn0nCiAgICAgIC0gJ05FWFRfUFVCTElDX0JBQ0tFTkRfVVJMPSR7U0VSVklDRV9GUUROX1BPU1RJWn0vYXBpJwogICAgICAtICdKV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1RTRUNSRVR9JwogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovL3Bvc3RncmVzOiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfUBwb3N0Z3Jlczo1NDMyLyR7UE9TVEdSRVNRTF9EQVRBQkFTRTotcG9zdGl6LWRifScKICAgICAgLSAnUkVESVNfVVJMPXJlZGlzOi8vZGVmYXVsdDoke1NFUlZJQ0VfUEFTU1dPUkRfUkVESVN9QHJlZGlzOjYzNzknCiAgICAgIC0gJ0JBQ0tFTkRfSU5URVJOQUxfVVJMPWh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCcKICAgICAgLSAnQ0xPVURGTEFSRV9BQ0NPVU5UX0lEPSR7Q0xPVURGTEFSRV9BQ0NPVU5UX0lEfScKICAgICAgLSAnQ0xPVURGTEFSRV9BQ0NFU1NfS0VZPSR7Q0xPVURGTEFSRV9BQ0NFU1NfS0VZfScKICAgICAgLSAnQ0xPVURGTEFSRV9TRUNSRVRfQUNDRVNTX0tFWT0ke0NMT1VERkxBUkVfU0VDUkVUX0FDQ0VTU19LRVl9JwogICAgICAtICdDTE9VREZMQVJFX0JVQ0tFVE5BTUU9JHtDTE9VREZMQVJFX0JVQ0tFVE5BTUV9JwogICAgICAtICdDTE9VREZMQVJFX0JVQ0tFVF9VUkw9JHtDTE9VREZMQVJFX0JVQ0tFVF9VUkx9JwogICAgICAtICdDTE9VREZMQVJFX1JFR0lPTj0ke0NMT1VERkxBUkVfUkVHSU9OfScKICAgICAgLSAnU1RPUkFHRV9QUk9WSURFUj0ke1NUT1JBR0VfUFJPVklERVI6LWxvY2FsfScKICAgICAgLSAnVVBMT0FEX0RJUkVDVE9SWT0ke1VQTE9BRF9ESVJFQ1RPUlk6LS91cGxvYWRzfScKICAgICAgLSAnTkVYVF9QVUJMSUNfVVBMT0FEX0RJUkVDVE9SWT0ke05FWFRfUFVCTElDX1VQTE9BRF9ESVJFQ1RPUlk6LS91cGxvYWRzfScKICAgICAgLSAnTkVYVF9QVUJMSUNfVVBMT0FEX1NUQVRJQ19ESVJFQ1RPUlk9JHtORVhUX1BVQkxJQ19VUExPQURfU1RBVElDX0RJUkVDVE9SWX0nCiAgICAgIC0gJ1JFU0VORF9BUElfS0VZPSR7UkVTRU5EX0FQSV9LRVl9JwogICAgICAtICdFTUFJTF9GUk9NX0FERFJFU1M9JHtFTUFJTF9GUk9NX0FERFJFU1N9JwogICAgICAtICdFTUFJTF9GUk9NX05BTUU9JHtFTUFJTF9GUk9NX05BTUV9JwogICAgICAtICdFTUFJTF9QUk9WSURFUj0ke0VNQUlMX1BST1ZJREVSfScKICAgICAgLSAnWF9BUElfS0VZPSR7U0VSVklDRV9YX0FQSX0nCiAgICAgIC0gJ1hfQVBJX1NFQ1JFVD0ke1NFUlZJQ0VfWF9TRUNSRVR9JwogICAgICAtICdMSU5LRURJTl9DTElFTlRfSUQ9JHtTRVJWSUNFX0xJTktFRElOX0lEfScKICAgICAgLSAnTElOS0VESU5fQ0xJRU5UX1NFQ1JFVD0ke1NFUlZJQ0VfTElOS0VESU5fU0VDUkVUfScKICAgICAgLSAnUkVERElUX0NMSUVOVF9JRD0ke1NFUlZJQ0VfUkVERElUX0FQSX0nCiAgICAgIC0gJ1JFRERJVF9DTElFTlRfU0VDUkVUPSR7U0VSVklDRV9SRURESVRfU0VDUkVUfScKICAgICAgLSAnR0lUSFVCX0NMSUVOVF9JRD0ke1NFUlZJQ0VfR0lUSFVCX0lEfScKICAgICAgLSAnR0lUSFVCX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX0dJVEhVQl9TRUNSRVR9JwogICAgICAtICdUSFJFQURTX0FQUF9JRD0ke1NFUlZJQ0VfVEhSRUFEU19JRH0nCiAgICAgIC0gJ1RIUkVBRFNfQVBQX1NFQ1JFVD0ke1NFUlZJQ0VfVEhSRUFEU19TRUNSRVR9JwogICAgICAtICdGQUNFQk9PS19BUFBfSUQ9JHtTRVJWSUNFX0ZBQ0VCT09LX0lEfScKICAgICAgLSAnRkFDRUJPT0tfQVBQX1NFQ1JFVD0ke1NFUlZJQ0VfRkFDRUJPT0tfU0VDUkVUfScKICAgICAgLSAnWU9VVFVCRV9DTElFTlRfSUQ9JHtTRVJWSUNFX1lPVVRVQkVfSUR9JwogICAgICAtICdZT1VUVUJFX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX1lPVVRVQkVfU0VDUkVUfScKICAgICAgLSAnVElLVE9LX0NMSUVOVF9JRD0ke1NFUlZJQ0VfVElLVE9LX0lEfScKICAgICAgLSAnVElLVE9LX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX1RJS1RPS19TRUNSRVR9JwogICAgICAtICdQSU5URVJFU1RfQ0xJRU5UX0lEPSR7U0VSVklDRV9QSU5URVJFU1RfSUR9JwogICAgICAtICdQSU5URVJFU1RfQ0xJRU5UX1NFQ1JFVD0ke1NFUlZJQ0VfUElOVEVSRVNUX1NFQ1JFVH0nCiAgICAgIC0gJ0RSSUJCQkxFX0NMSUVOVF9JRD0ke1NFUlZJQ0VfRFJJQkJMRV9JRH0nCiAgICAgIC0gJ0RSSUJCQkxFX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX0RSSUJCTEVfU0VDUkVUfScKICAgICAgLSAnRElTQ09SRF9DTElFTlRfSUQ9JHtTRVJWSUNFX0RJU0NPUkRfSUR9JwogICAgICAtICdESVNDT1JEX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX0RJU0NPUkRfU0VDUkVUfScKICAgICAgLSAnRElTQ09SRF9CT1RfVE9LRU5fSUQ9JHtTRVJWSUNFX0RJU0NPUkRfVE9LRU59JwogICAgICAtICdTTEFDS19JRD0ke1NFUlZJQ0VfU0xBQ0tfSUR9JwogICAgICAtICdTTEFDS19TRUNSRVQ9JHtTRVJWSUNFX1NMQUNLX1NFQ1JFVH0nCiAgICAgIC0gJ1NMQUNLX1NJR05JTkdfU0VDUkVUPSR7U0xBQ0tfU0lHTklOR19TRUNSRVR9JwogICAgICAtICdNQVNUT0RPTl9DTElFTlRfSUQ9JHtTRVJWSUNFX01BU1RPRE9OX0lEfScKICAgICAgLSAnTUFTVE9ET05fQ0xJRU5UX1NFQ1JFVD0ke1NFUlZJQ0VfTUFTVE9ET05fU0VDUkVUfScKICAgICAgLSAnQkVFSElJVkVfQVBJX0tFWT0ke1NFUlZJQ0VfQkVFSElJVkVfS0VZfScKICAgICAgLSAnQkVFSElJVkVfUFVCTElDQVRJT05fSUQ9JHtTRVJWSUNFX0JFRUhJSVZFX1BVQklEfScKICAgICAgLSAnT1BFTkFJX0FQSV9LRVk9JHtTRVJWSUNFX09QRU5BSV9LRVl9JwogICAgICAtICdORVhUX1BVQkxJQ19ESVNDT1JEX1NVUFBPUlQ9JHtORVhUX1BVQkxJQ19ESVNDT1JEX1NVUFBPUlR9JwogICAgICAtICdORVhUX1BVQkxJQ19QT0xPVE5PPSR7TkVYVF9QVUJMSUNfUE9MT1ROT30nCiAgICAgIC0gSVNfR0VORVJBTD10cnVlCiAgICAgIC0gJ05YX0FERF9QTFVHSU5TPSR7TlhfQUREX1BMVUdJTlM6LWZhbHNlfScKICAgICAgLSAnRkVFX0FNT1VOVD0ke0ZFRV9BTU9VTlQ6LTAuMDV9JwogICAgICAtICdTVFJJUEVfUFVCTElTSEFCTEVfS0VZPSR7U1RSSVBFX1BVQkxJU0hBQkxFX0tFWX0nCiAgICAgIC0gJ1NUUklQRV9TRUNSRVRfS0VZPSR7U1RSSVBFX1NFQ1JFVF9LRVl9JwogICAgICAtICdTVFJJUEVfU0lHTklOR19LRVk9JHtTVFJJUEVfU0lHTklOR19LRVl9JwogICAgICAtICdTVFJJUEVfU0lHTklOR19LRVlfQ09OTkVDVD0ke1NUUklQRV9TSUdOSU5HX0tFWV9DT05ORUNUfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3Bvc3Rpel9jb25maWc6L2NvbmZpZy8nCiAgICAgIC0gJ3Bvc3Rpel91cGxvYWRzOi91cGxvYWRzLycKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHJlZGlzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3dnZXQgLXFPLSBodHRwOi8vMTI3LjAuMC4xOjUwMDAvJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE0LjUnCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0aXpfcG9zdGdyZXNxbF9kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX1VTRVI9cG9zdGdyZXMKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTUUx9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTUUxfREFUQUJBU0U6LXBvc3Rpei1kYn0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREI6LXBvc3Rpei1kYn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICByZWRpczoKICAgIGltYWdlOiAncmVkaXM6Ny4yJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1JFRElTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9SRURJU30nCiAgICBjb21tYW5kOiAncmVkaXMtc2VydmVyIC0tcmVxdWlyZXBhc3MgJHtTRVJWSUNFX1BBU1NXT1JEX1JFRElTfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3Bvc3Rpel9yZWRpc19kYXRhOi9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHJlZGlzLWNsaQogICAgICAgIC0gJy1hJwogICAgICAgIC0gJyR7U0VSVklDRV9QQVNTV09SRF9SRURJU30nCiAgICAgICAgLSBwaW5nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMjAK", + "compose": "c2VydmljZXM6CiAgcG9zdGl6OgogICAgaW1hZ2U6ICdnaGNyLmlvL2dpdHJvb21ocS9wb3N0aXotYXBwOnYxLjYwLjEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fUE9TVElaXzUwMDAKICAgICAgLSAnTUFJTl9VUkw9JHtTRVJWSUNFX0ZRRE5fUE9TVElafScKICAgICAgLSAnRlJPTlRFTkRfVVJMPSR7U0VSVklDRV9GUUROX1BPU1RJWn0nCiAgICAgIC0gJ05FWFRfUFVCTElDX0JBQ0tFTkRfVVJMPSR7U0VSVklDRV9GUUROX1BPU1RJWn0vYXBpJwogICAgICAtICdKV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1RTRUNSRVR9JwogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovL3Bvc3RncmVzOiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfUBwb3N0Z3Jlczo1NDMyLyR7UE9TVEdSRVNRTF9EQVRBQkFTRTotcG9zdGl6LWRifScKICAgICAgLSAnUkVESVNfVVJMPXJlZGlzOi8vZGVmYXVsdDoke1NFUlZJQ0VfUEFTU1dPUkRfUkVESVN9QHJlZGlzOjYzNzknCiAgICAgIC0gJ0JBQ0tFTkRfSU5URVJOQUxfVVJMPWh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCcKICAgICAgLSAnQ0xPVURGTEFSRV9BQ0NPVU5UX0lEPSR7Q0xPVURGTEFSRV9BQ0NPVU5UX0lEfScKICAgICAgLSAnQ0xPVURGTEFSRV9BQ0NFU1NfS0VZPSR7Q0xPVURGTEFSRV9BQ0NFU1NfS0VZfScKICAgICAgLSAnQ0xPVURGTEFSRV9TRUNSRVRfQUNDRVNTX0tFWT0ke0NMT1VERkxBUkVfU0VDUkVUX0FDQ0VTU19LRVl9JwogICAgICAtICdDTE9VREZMQVJFX0JVQ0tFVE5BTUU9JHtDTE9VREZMQVJFX0JVQ0tFVE5BTUV9JwogICAgICAtICdDTE9VREZMQVJFX0JVQ0tFVF9VUkw9JHtDTE9VREZMQVJFX0JVQ0tFVF9VUkx9JwogICAgICAtICdDTE9VREZMQVJFX1JFR0lPTj0ke0NMT1VERkxBUkVfUkVHSU9OfScKICAgICAgLSAnU1RPUkFHRV9QUk9WSURFUj0ke1NUT1JBR0VfUFJPVklERVI6LWxvY2FsfScKICAgICAgLSAnVVBMT0FEX0RJUkVDVE9SWT0ke1VQTE9BRF9ESVJFQ1RPUlk6LS91cGxvYWRzfScKICAgICAgLSAnTkVYVF9QVUJMSUNfVVBMT0FEX0RJUkVDVE9SWT0ke05FWFRfUFVCTElDX1VQTE9BRF9ESVJFQ1RPUlk6LS91cGxvYWRzfScKICAgICAgLSAnTkVYVF9QVUJMSUNfVVBMT0FEX1NUQVRJQ19ESVJFQ1RPUlk9JHtORVhUX1BVQkxJQ19VUExPQURfU1RBVElDX0RJUkVDVE9SWX0nCiAgICAgIC0gJ1JFU0VORF9BUElfS0VZPSR7UkVTRU5EX0FQSV9LRVl9JwogICAgICAtICdFTUFJTF9GUk9NX0FERFJFU1M9JHtFTUFJTF9GUk9NX0FERFJFU1N9JwogICAgICAtICdFTUFJTF9GUk9NX05BTUU9JHtFTUFJTF9GUk9NX05BTUV9JwogICAgICAtICdFTUFJTF9QUk9WSURFUj0ke0VNQUlMX1BST1ZJREVSfScKICAgICAgLSAnWF9BUElfS0VZPSR7U0VSVklDRV9YX0FQSX0nCiAgICAgIC0gJ1hfQVBJX1NFQ1JFVD0ke1NFUlZJQ0VfWF9TRUNSRVR9JwogICAgICAtICdMSU5LRURJTl9DTElFTlRfSUQ9JHtTRVJWSUNFX0xJTktFRElOX0lEfScKICAgICAgLSAnTElOS0VESU5fQ0xJRU5UX1NFQ1JFVD0ke1NFUlZJQ0VfTElOS0VESU5fU0VDUkVUfScKICAgICAgLSAnUkVERElUX0NMSUVOVF9JRD0ke1NFUlZJQ0VfUkVERElUX0FQSX0nCiAgICAgIC0gJ1JFRERJVF9DTElFTlRfU0VDUkVUPSR7U0VSVklDRV9SRURESVRfU0VDUkVUfScKICAgICAgLSAnR0lUSFVCX0NMSUVOVF9JRD0ke1NFUlZJQ0VfR0lUSFVCX0lEfScKICAgICAgLSAnR0lUSFVCX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX0dJVEhVQl9TRUNSRVR9JwogICAgICAtICdUSFJFQURTX0FQUF9JRD0ke1NFUlZJQ0VfVEhSRUFEU19JRH0nCiAgICAgIC0gJ1RIUkVBRFNfQVBQX1NFQ1JFVD0ke1NFUlZJQ0VfVEhSRUFEU19TRUNSRVR9JwogICAgICAtICdGQUNFQk9PS19BUFBfSUQ9JHtTRVJWSUNFX0ZBQ0VCT09LX0lEfScKICAgICAgLSAnRkFDRUJPT0tfQVBQX1NFQ1JFVD0ke1NFUlZJQ0VfRkFDRUJPT0tfU0VDUkVUfScKICAgICAgLSAnWU9VVFVCRV9DTElFTlRfSUQ9JHtTRVJWSUNFX1lPVVRVQkVfSUR9JwogICAgICAtICdZT1VUVUJFX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX1lPVVRVQkVfU0VDUkVUfScKICAgICAgLSAnVElLVE9LX0NMSUVOVF9JRD0ke1NFUlZJQ0VfVElLVE9LX0lEfScKICAgICAgLSAnVElLVE9LX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX1RJS1RPS19TRUNSRVR9JwogICAgICAtICdQSU5URVJFU1RfQ0xJRU5UX0lEPSR7U0VSVklDRV9QSU5URVJFU1RfSUR9JwogICAgICAtICdQSU5URVJFU1RfQ0xJRU5UX1NFQ1JFVD0ke1NFUlZJQ0VfUElOVEVSRVNUX1NFQ1JFVH0nCiAgICAgIC0gJ0RSSUJCQkxFX0NMSUVOVF9JRD0ke1NFUlZJQ0VfRFJJQkJMRV9JRH0nCiAgICAgIC0gJ0RSSUJCQkxFX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX0RSSUJCTEVfU0VDUkVUfScKICAgICAgLSAnRElTQ09SRF9DTElFTlRfSUQ9JHtTRVJWSUNFX0RJU0NPUkRfSUR9JwogICAgICAtICdESVNDT1JEX0NMSUVOVF9TRUNSRVQ9JHtTRVJWSUNFX0RJU0NPUkRfU0VDUkVUfScKICAgICAgLSAnRElTQ09SRF9CT1RfVE9LRU5fSUQ9JHtTRVJWSUNFX0RJU0NPUkRfVE9LRU59JwogICAgICAtICdTTEFDS19JRD0ke1NFUlZJQ0VfU0xBQ0tfSUR9JwogICAgICAtICdTTEFDS19TRUNSRVQ9JHtTRVJWSUNFX1NMQUNLX1NFQ1JFVH0nCiAgICAgIC0gJ1NMQUNLX1NJR05JTkdfU0VDUkVUPSR7U0xBQ0tfU0lHTklOR19TRUNSRVR9JwogICAgICAtICdNQVNUT0RPTl9DTElFTlRfSUQ9JHtTRVJWSUNFX01BU1RPRE9OX0lEfScKICAgICAgLSAnTUFTVE9ET05fQ0xJRU5UX1NFQ1JFVD0ke1NFUlZJQ0VfTUFTVE9ET05fU0VDUkVUfScKICAgICAgLSAnQkVFSElJVkVfQVBJX0tFWT0ke1NFUlZJQ0VfQkVFSElJVkVfS0VZfScKICAgICAgLSAnQkVFSElJVkVfUFVCTElDQVRJT05fSUQ9JHtTRVJWSUNFX0JFRUhJSVZFX1BVQklEfScKICAgICAgLSAnT1BFTkFJX0FQSV9LRVk9JHtTRVJWSUNFX09QRU5BSV9LRVl9JwogICAgICAtICdORVhUX1BVQkxJQ19ESVNDT1JEX1NVUFBPUlQ9JHtORVhUX1BVQkxJQ19ESVNDT1JEX1NVUFBPUlR9JwogICAgICAtICdORVhUX1BVQkxJQ19QT0xPVE5PPSR7TkVYVF9QVUJMSUNfUE9MT1ROT30nCiAgICAgIC0gSVNfR0VORVJBTD10cnVlCiAgICAgIC0gJ05YX0FERF9QTFVHSU5TPSR7TlhfQUREX1BMVUdJTlM6LWZhbHNlfScKICAgICAgLSAnTk9UX1NFQ1VSRUQ9JHtOT1RfU0VDVVJFRDotZmFsc2V9JwogICAgICAtICdGRUVfQU1PVU5UPSR7RkVFX0FNT1VOVDotMC4wNX0nCiAgICAgIC0gJ1NUUklQRV9QVUJMSVNIQUJMRV9LRVk9JHtTVFJJUEVfUFVCTElTSEFCTEVfS0VZfScKICAgICAgLSAnU1RSSVBFX1NFQ1JFVF9LRVk9JHtTVFJJUEVfU0VDUkVUX0tFWX0nCiAgICAgIC0gJ1NUUklQRV9TSUdOSU5HX0tFWT0ke1NUUklQRV9TSUdOSU5HX0tFWX0nCiAgICAgIC0gJ1NUUklQRV9TSUdOSU5HX0tFWV9DT05ORUNUPSR7U1RSSVBFX1NJR05JTkdfS0VZX0NPTk5FQ1R9JwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGl6X2NvbmZpZzovY29uZmlnLycKICAgICAgLSAncG9zdGl6X3VwbG9hZHM6L3VwbG9hZHMvJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnd2dldCAtcU8tIGh0dHA6Ly8xMjcuMC4wLjE6NTAwMC8nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICBwb3N0Z3JlczoKICAgIGltYWdlOiAncG9zdGdyZXM6MTQuNScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3Bvc3Rpel9wb3N0Z3Jlc3FsX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfVVNFUj1wb3N0Z3JlcwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNRTF9EQVRBQkFTRTotcG9zdGl6LWRifScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQjotcG9zdGl6LWRifScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczo3LjInCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUkVESVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1JFRElTfScKICAgIGNvbW1hbmQ6ICdyZWRpcy1zZXJ2ZXIgLS1yZXF1aXJlcGFzcyAke1NFUlZJQ0VfUEFTU1dPUkRfUkVESVN9JwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGl6X3JlZGlzX2RhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSAnLWEnCiAgICAgICAgLSAnJHtTRVJWSUNFX1BBU1NXT1JEX1JFRElTfScKICAgICAgICAtIHBpbmcKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAyMAo=", "tags": [ "post everywhere", "social media", @@ -3083,6 +3115,19 @@ "minversion": "0.0.0", "port": "8080" }, + "sequin": { + "documentation": "https://sequinstream.com/docs/?utm_source=coolify.io", + "slogan": "The fastest Postgres change data capture", + "compose": "c2VydmljZXM6CiAgc2VxdWluOgogICAgaW1hZ2U6ICdzZXF1aW4vc2VxdWluOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9TRVFVSU5fNzM3NgogICAgICAtICdTRVJWRVJfSE9TVD0ke1NFUlZJQ0VfVVJMX1NFUVVJTn0nCiAgICAgIC0gUEdfSE9TVE5BTUU9cG9zdGdyZXMKICAgICAgLSAnUEdfREFUQUJBU0U9JHtQT1NUR1JFU19EQjotc2VxdWluLWRifScKICAgICAgLSBQR19QT1JUPTU0MzIKICAgICAgLSAnUEdfVVNFUk5BTUU9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQR19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtIFBHX1BPT0xfU0laRT0yMAogICAgICAtICdTRUNSRVRfS0VZX0JBU0U9JHtTRVJWSUNFX1JFQUxCQVNFNjRfNjRfU0VDUkVUS0VZfScKICAgICAgLSAnVkFVTFRfS0VZPSR7U0VSVklDRV9SRUFMQkFTRTY0X1ZBVUxUS0VZfScKICAgICAgLSAnUkVESVNfVVJMPXJlZGlzOi8vcmVkaXM6NjM3OScKICAgICAgLSBDT05GSUdfRklMRV9QQVRIPS9jb25maWcvcGxheWdyb3VuZC55bWwKICAgICAgLSAnRkVBVFVSRV9BQ0NPVU5UX1NFTEZfU0lHTlVQPSR7RkVBVFVSRV9BQ0NPVU5UX1NFTEZfU0lHTlVQOi1mYWxzZX0nCiAgICAgIC0gJ1NFUVVJTl9URUxFTUVUUllfRElTQUJMRUQ9JHtTRVFVSU5fVEVMRU1FVFJZX0RJU0FCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ0NSQVNIX1JFUE9SVElOR19ESVNBQkxFRD0ke0NSQVNIX1JFUE9SVElOR19ESVNBQkxFRDotZmFsc2V9JwogICAgZGVwZW5kc19vbjoKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDo3Mzc2L2hlYWx0aCcKICBwb3N0Z3JlczoKICAgIGltYWdlOiAncG9zdGdyZXM6MTYnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotc2VxdWluLWRifScKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICBjb21tYW5kOgogICAgICAtIHBvc3RncmVzCiAgICAgIC0gJy1jJwogICAgICAtIHdhbF9sZXZlbD1sb2dpY2FsCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9IC1kIHNlcXVpbicKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiAycwogICAgICByZXRyaWVzOiA1CiAgICAgIHN0YXJ0X3BlcmlvZDogMnMKICAgICAgc3RhcnRfaW50ZXJ2YWw6IDFzCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0Z3Jlc19kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICByZWRpczoKICAgIGltYWdlOiAncmVkaXM6NycKICAgIGNvbW1hbmQ6CiAgICAgIC0gcmVkaXMtc2VydmVyCiAgICAgIC0gJy0tcG9ydCcKICAgICAgLSAnNjM3OScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSByZWRpcy1jbGkKICAgICAgICAtIHBpbmcKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogICAgdm9sdW1lczoKICAgICAgLSAncmVkaXNfZGF0YTovZGF0YScK", + "tags": [ + "postgres", + "sync", + "data" + ], + "logo": "svgs/sequin.svg", + "minversion": "0.0.0", + "port": "7376" + }, "shlink": { "documentation": "https://shlink.io/?utm_source=coolify.io", "slogan": "The definitive self-hosted URL shortener", From c551be9be8b3bb6daac24543de7a5398157bd14c Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:11:29 +0200 Subject: [PATCH 021/367] chore(core): remove unused argument --- app/Actions/Database/StopDatabase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Actions/Database/StopDatabase.php b/app/Actions/Database/StopDatabase.php index 6fcdedeeb..5c881e743 100644 --- a/app/Actions/Database/StopDatabase.php +++ b/app/Actions/Database/StopDatabase.php @@ -18,7 +18,7 @@ class StopDatabase { use AsAction; - public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database, bool $isDeleteOperation = false, bool $dockerCleanup = true) + public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database, bool $dockerCleanup = true) { try { $server = $database->destination->server; From daa96c27a704de6b4628ddacee8974ceb05c96c5 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:12:04 +0200 Subject: [PATCH 022/367] chore(deletion): rename isDeleteOperation to deleteConnectedNetworks --- app/Actions/Service/StopService.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Actions/Service/StopService.php b/app/Actions/Service/StopService.php index 190b8885d..3f4e96479 100644 --- a/app/Actions/Service/StopService.php +++ b/app/Actions/Service/StopService.php @@ -14,7 +14,7 @@ class StopService public string $jobQueue = 'high'; - public function handle(Service $service, bool $isDeleteOperation = false, bool $dockerCleanup = true) + public function handle(Service $service, bool $deleteConnectedNetworks = false, bool $dockerCleanup = true) { try { $server = $service->destination->server; @@ -36,7 +36,7 @@ class StopService $this->stopContainersInParallel($containersToStop, $server); } - if ($isDeleteOperation) { + if ($deleteConnectedNetworks) { $service->deleteConnectedNetworks(); } if ($dockerCleanup) { From 279edf696ce90b90497933526a1e775b95cceafc Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:12:56 +0200 Subject: [PATCH 023/367] chore(docker): remove unused arguments on StopService --- app/Livewire/Project/CloneMe.php | 4 ++-- app/Livewire/Project/Shared/ResourceOperations.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Livewire/Project/CloneMe.php b/app/Livewire/Project/CloneMe.php index a7c44577c..a5d80a11a 100644 --- a/app/Livewire/Project/CloneMe.php +++ b/app/Livewire/Project/CloneMe.php @@ -454,7 +454,7 @@ class CloneMe extends Component if ($this->cloneVolumeData) { try { - StopService::dispatch($application, false, false); + StopService::dispatch($application); $sourceVolume = $volume->name; $targetVolume = $newPersistentVolume->name; $sourceServer = $application->service->destination->server; @@ -508,7 +508,7 @@ class CloneMe extends Component if ($this->cloneVolumeData) { try { - StopService::dispatch($database->service, false, false); + StopService::dispatch($database->service); $sourceVolume = $volume->name; $targetVolume = $newPersistentVolume->name; $sourceServer = $database->service->destination->server; diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php index fb19acb55..c8916bf19 100644 --- a/app/Livewire/Project/Shared/ResourceOperations.php +++ b/app/Livewire/Project/Shared/ResourceOperations.php @@ -412,7 +412,7 @@ class ResourceOperations extends Component if ($this->cloneVolumeData) { try { - StopService::dispatch($application, false, false); + StopService::dispatch($application); $sourceVolume = $volume->name; $targetVolume = $newPersistentVolume->name; $sourceServer = $application->service->destination->server; @@ -454,7 +454,7 @@ class ResourceOperations extends Component if ($this->cloneVolumeData) { try { - StopService::dispatch($database->service, false, false); + StopService::dispatch($database->service); $sourceVolume = $volume->name; $targetVolume = $newPersistentVolume->name; $sourceServer = $database->service->destination->server; From 36961d8ae840e9e4632bf9fc0aee5b1d10fd1494 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:14:19 +0200 Subject: [PATCH 024/367] fix(docker): cleanup always running on deletion - docker cleanup was always running on deletion instead of using the settings set in the deletion modal --- app/Jobs/DeleteResourceJob.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Jobs/DeleteResourceJob.php b/app/Jobs/DeleteResourceJob.php index a725df52f..b9fbebcc9 100644 --- a/app/Jobs/DeleteResourceJob.php +++ b/app/Jobs/DeleteResourceJob.php @@ -52,7 +52,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue switch ($this->resource->type()) { case 'application': - StopApplication::run($this->resource, previewDeployments: true); + StopApplication::run($this->resource, previewDeployments: true, dockerCleanup: $this->dockerCleanup); break; case 'standalone-postgresql': case 'standalone-redis': @@ -62,10 +62,10 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue case 'standalone-keydb': case 'standalone-dragonfly': case 'standalone-clickhouse': - StopDatabase::run($this->resource, true); + StopDatabase::run($this->resource, dockerCleanup: $this->dockerCleanup); break; case 'service': - StopService::run($this->resource, true); + StopService::run($this->resource, $this->deleteConnectedNetworks, $this->dockerCleanup); DeleteService::run($this->resource, $this->deleteVolumes, $this->deleteConnectedNetworks, $this->deleteConfigurations, $this->dockerCleanup); return; @@ -78,7 +78,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue $this->resource->deleteVolumes(); $this->resource->persistentStorages()->delete(); } - $this->resource->fileStorages()->delete(); + $this->resource->fileStorages()->delete(); // these are file mounts which should probably have their own flag $isDatabase = $this->resource instanceof StandalonePostgresql || $this->resource instanceof StandaloneRedis From a40dd8880d49ef691ca4322f86395f363b85ecf4 Mon Sep 17 00:00:00 2001 From: Cynthia Ebert <54354036+Cinzya@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:16:49 +0200 Subject: [PATCH 025/367] fix(proxy): remove hardcoded port 80/443 checks (#6275) --- app/Actions/Proxy/CheckProxy.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Actions/Proxy/CheckProxy.php b/app/Actions/Proxy/CheckProxy.php index d4b03ffc1..a06e547c5 100644 --- a/app/Actions/Proxy/CheckProxy.php +++ b/app/Actions/Proxy/CheckProxy.php @@ -66,7 +66,7 @@ class CheckProxy if ($server->id === 0) { $ip = 'host.docker.internal'; } - $portsToCheck = ['80', '443']; + $portsToCheck = []; try { if ($server->proxyType() !== ProxyTypes::NONE->value) { From ed0f2c1c59ab7a98202a796df7d7ab5ae5bd9dd0 Mon Sep 17 00:00:00 2001 From: Gurvan <32047986+gurvancampion@users.noreply.github.com> Date: Tue, 5 Aug 2025 18:31:43 +0200 Subject: [PATCH 026/367] feat(service): add OpenPanel template (#5310) --- public/svgs/openpanel.svg | 1 + templates/compose/openpanel.yaml | 196 +++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 public/svgs/openpanel.svg create mode 100644 templates/compose/openpanel.yaml diff --git a/public/svgs/openpanel.svg b/public/svgs/openpanel.svg new file mode 100644 index 000000000..8508fc69e --- /dev/null +++ b/public/svgs/openpanel.svg @@ -0,0 +1 @@ + diff --git a/templates/compose/openpanel.yaml b/templates/compose/openpanel.yaml new file mode 100644 index 000000000..6d3738dc0 --- /dev/null +++ b/templates/compose/openpanel.yaml @@ -0,0 +1,196 @@ +# documentation: https://openpanel.dev/docs +# slogan: Open source alternative to Mixpanel and Plausible for product analytics +# tags: analytics, insights, privacy, mixpanel, plausible, google, alternative +# logo: svgs/openpanel.svg +# port: 3000 + +services: + opdb: + image: postgres:16-alpine + restart: always + volumes: + - opdb-data:/var/lib/postgresql/data + environment: + - POSTGRES_DB=${OPENPANEL_POSTGRES_DB:-openpanel-db} + - POSTGRES_USER=${SERVICE_USER_POSTGRES} + - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} + healthcheck: + test: [CMD-SHELL, "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] + interval: 10s + timeout: 5s + retries: 5 + + opkv: + image: redis:7.4-alpine + restart: always + volumes: + - opkv-data:/data + command: redis-server --requirepass ${SERVICE_PASSWORD_REDIS} --maxmemory-policy noeviction + healthcheck: + test: [CMD, redis-cli, -a, "${SERVICE_PASSWORD_REDIS}", ping] + interval: 10s + timeout: 5s + retries: 5 + + opch: + image: clickhouse/clickhouse-server:24.3.2-alpine + restart: always + volumes: + - opch-data:/var/lib/clickhouse + - opch-logs:/var/log/clickhouse-server + - type: bind + source: ./clickhouse-config.xml + target: /etc/clickhouse-server/config.d/op-config.xml + read_only: true + content: | + + + warning + true + + 10 + + + + + + + + + + 0.0.0.0 + 0.0.0.0 + opch + + 0 + + + 1 + replica1 + openpanel_cluster + + + - type: bind + source: ./clickhouse-user-config.xml + target: /etc/clickhouse-server/users.d/op-user-config.xml + read_only: true + content: | + + + + 0 + 0 + + + + - type: bind + source: ./init-db.sh + target: /docker-entrypoint-initdb.d/init-db.sh + content: | + #!/bin/sh + set -e + + clickhouse client -n <<-EOSQL + CREATE DATABASE IF NOT EXISTS openpanel; + EOSQL + healthcheck: + test: [CMD-SHELL, 'clickhouse-client --query "SELECT 1"'] + interval: 10s + timeout: 5s + retries: 5 + ulimits: + nofile: + soft: 262144 + hard: 262144 + + opapi: + image: lindesvard/openpanel-api:latest + restart: always + command: > + sh -c " + echo 'Running migrations...' + CI=true pnpm -r run migrate:deploy + + pnpm start + " + depends_on: + opdb: + condition: service_healthy + opch: + condition: service_healthy + opkv: + condition: service_healthy + environment: + # Common + - NODE_ENV=production + - NEXT_PUBLIC_SELF_HOSTED=true + # URLs + - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - DATABASE_URL_DIRECT=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@opkv:6379 + - CLICKHOUSE_URL=${OPENPANEL_CLICKHOUSE_URL:-http://opch:8123/openpanel} + - SERVICE_FQDN_OPAPI + # Set coolify FQDN domain + - NEXT_PUBLIC_API_URL=$SERVICE_FQDN_OPAPI + - NEXT_PUBLIC_DASHBOARD_URL=$SERVICE_FQDN_OPDASHBOARD + # Others + - COOKIE_SECRET=${SERVICE_BASE64_COOKIESECRET} + - ALLOW_REGISTRATION=${OPENPANEL_ALLOW_REGISTRATION:-false} + - ALLOW_INVITATION=${OPENPANEL_ALLOW_INVITATION:-true} + - EMAIL_SENDER=${OPENPANEL_EMAIL_SENDER} + - RESEND_API_KEY=${RESEND_API_KEY} + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:3000/healthcheck || exit 1"] + interval: 10s + timeout: 5s + retries: 5 + + opdashboard: + image: lindesvard/openpanel-dashboard:latest + restart: always + depends_on: + opapi: + condition: service_healthy + environment: + # Common + - NODE_ENV=production + - NEXT_PUBLIC_SELF_HOSTED=true + # URLs + - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@opkv:6379 + - CLICKHOUSE_URL=${OPENPANEL_CLICKHOUSE_URL:-http://opch:8123/openpanel} + - SERVICE_FQDN_OPDASHBOARD + # Set coolify FQDN domain + - NEXT_PUBLIC_API_URL=$SERVICE_FQDN_OPAPI + - NEXT_PUBLIC_DASHBOARD_URL=$SERVICE_FQDN_OPDASHBOARD + healthcheck: + test: + ["CMD-SHELL", "curl -f http://localhost:3000/api/healthcheck || exit 1"] + interval: 10s + timeout: 5s + retries: 5 + + opworker: + image: lindesvard/openpanel-worker:latest + restart: always + depends_on: + opapi: + condition: service_healthy + environment: + # FQDN + - SERVICE_FQDN_OPBULLBOARD + # Common + - NODE_ENV=production + - NEXT_PUBLIC_SELF_HOSTED=true + # URLs + - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - DATABASE_URL_DIRECT=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@opkv:6379 + - CLICKHOUSE_URL=${OPENPANEL_CLICKHOUSE_URL:-http://opch:8123/openpanel} + # Set coolify FQDN domain + - NEXT_PUBLIC_API_URL=$SERVICE_FQDN_OPAPI + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:3000/healthcheck || exit 1"] + interval: 10s + timeout: 5s + retries: 5 From b52c414fe481d87aa10f14ad2e7575b500031eb4 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 5 Aug 2025 19:11:34 +0200 Subject: [PATCH 027/367] refactor(service): improve openpanel template - rename all services - add more depends_on checks to ensure everything works properly and is healthy - reorder and re-format the template, ENVs and the individual sections - remove comments --- templates/compose/openpanel.yaml | 213 ++++++++++++++++--------------- 1 file changed, 107 insertions(+), 106 deletions(-) diff --git a/templates/compose/openpanel.yaml b/templates/compose/openpanel.yaml index 6d3738dc0..7eb2615e4 100644 --- a/templates/compose/openpanel.yaml +++ b/templates/compose/openpanel.yaml @@ -5,11 +5,104 @@ # port: 3000 services: - opdb: + openpanel-dashboard: + image: lindesvard/openpanel-dashboard:latest + environment: + - NODE_ENV=production + - NEXT_PUBLIC_SELF_HOSTED=true + - SERVICE_FQDN_OPDASHBOARD_3000 + - NEXT_PUBLIC_API_URL=${SERVICE_FQDN_OPAPI} + - NEXT_PUBLIC_DASHBOARD_URL=${SERVICE_FQDN_OPDASHBOARD} + - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@redis:6379 + - CLICKHOUSE_URL=http://clickhouse:8123/openpanel + depends_on: + openpanel-api: + condition: service_healthy + openpanel-worker: + condition: service_healthy + postgres: + condition: service_healthy + redis: + condition: service_healthy + clickhouse: + condition: service_healthy + healthcheck: + test: + ["CMD-SHELL", "curl -f http://localhost:3000/api/healthcheck || exit 1"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 15s + + openpanel-api: + image: lindesvard/openpanel-api:latest + command: > + sh -c " + echo 'Running migrations...' + CI=true pnpm -r run migrate:deploy + + pnpm start + " + environment: + - NODE_ENV=production + - NEXT_PUBLIC_SELF_HOSTED=true + - SERVICE_FQDN_OPAPI + - NEXT_PUBLIC_API_URL=${SERVICE_FQDN_OPAPI} + - NEXT_PUBLIC_DASHBOARD_URL=${SERVICE_FQDN_OPDASHBOARD} + - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - DATABASE_URL_DIRECT=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@redis:6379 + - CLICKHOUSE_URL=http://clickhouse:8123/openpanel + - COOKIE_SECRET=${SERVICE_BASE64_COOKIESECRET} + - ALLOW_REGISTRATION=${OPENPANEL_ALLOW_REGISTRATION:-false} + - ALLOW_INVITATION=${OPENPANEL_ALLOW_INVITATION:-false} + - EMAIL_SENDER=${OPENPANEL_EMAIL_SENDER} + - RESEND_API_KEY=${RESEND_API_KEY} + depends_on: + postgres: + condition: service_healthy + clickhouse: + condition: service_healthy + redis: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:3000/healthcheck || exit 1"] + interval: 10s + timeout: 5s + retries: 5 + + openpanel-worker: + image: lindesvard/openpanel-worker:latest + environment: + - NODE_ENV=production + - NEXT_PUBLIC_SELF_HOSTED=true + - SERVICE_FQDN_OPBULLBOARD + - NEXT_PUBLIC_API_URL=${SERVICE_FQDN_OPAPI} + - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - DATABASE_URL_DIRECT=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public + - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@redis:6379 + - CLICKHOUSE_URL=http://clickhouse:8123/openpanel + depends_on: + openpanel-api: + condition: service_healthy + postgres: + condition: service_healthy + redis: + condition: service_healthy + clickhouse: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:3000/healthcheck || exit 1"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 5s + + postgres: image: postgres:16-alpine - restart: always volumes: - - opdb-data:/var/lib/postgresql/data + - openpanel_postgres_data:/var/lib/postgresql/data environment: - POSTGRES_DB=${OPENPANEL_POSTGRES_DB:-openpanel-db} - POSTGRES_USER=${SERVICE_USER_POSTGRES} @@ -20,11 +113,12 @@ services: timeout: 5s retries: 5 - opkv: + redis: image: redis:7.4-alpine - restart: always volumes: - - opkv-data:/data + - openpanel_redis_data:/data + environment: + - REDIS_PASSWORD=${SERVICE_PASSWORD_REDIS} command: redis-server --requirepass ${SERVICE_PASSWORD_REDIS} --maxmemory-policy noeviction healthcheck: test: [CMD, redis-cli, -a, "${SERVICE_PASSWORD_REDIS}", ping] @@ -32,12 +126,11 @@ services: timeout: 5s retries: 5 - opch: + clickhouse: image: clickhouse/clickhouse-server:24.3.2-alpine - restart: always volumes: - - opch-data:/var/lib/clickhouse - - opch-logs:/var/log/clickhouse-server + - openpanel_clickhouse_data:/var/lib/clickhouse + - openpanel_clickhouse_logs:/var/log/clickhouse-server - type: bind source: ./clickhouse-config.xml target: /etc/clickhouse-server/config.d/op-config.xml @@ -93,104 +186,12 @@ services: clickhouse client -n <<-EOSQL CREATE DATABASE IF NOT EXISTS openpanel; EOSQL + ulimits: + nofile: + soft: 262144 + hard: 262144 healthcheck: test: [CMD-SHELL, 'clickhouse-client --query "SELECT 1"'] interval: 10s timeout: 5s retries: 5 - ulimits: - nofile: - soft: 262144 - hard: 262144 - - opapi: - image: lindesvard/openpanel-api:latest - restart: always - command: > - sh -c " - echo 'Running migrations...' - CI=true pnpm -r run migrate:deploy - - pnpm start - " - depends_on: - opdb: - condition: service_healthy - opch: - condition: service_healthy - opkv: - condition: service_healthy - environment: - # Common - - NODE_ENV=production - - NEXT_PUBLIC_SELF_HOSTED=true - # URLs - - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public - - DATABASE_URL_DIRECT=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public - - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@opkv:6379 - - CLICKHOUSE_URL=${OPENPANEL_CLICKHOUSE_URL:-http://opch:8123/openpanel} - - SERVICE_FQDN_OPAPI - # Set coolify FQDN domain - - NEXT_PUBLIC_API_URL=$SERVICE_FQDN_OPAPI - - NEXT_PUBLIC_DASHBOARD_URL=$SERVICE_FQDN_OPDASHBOARD - # Others - - COOKIE_SECRET=${SERVICE_BASE64_COOKIESECRET} - - ALLOW_REGISTRATION=${OPENPANEL_ALLOW_REGISTRATION:-false} - - ALLOW_INVITATION=${OPENPANEL_ALLOW_INVITATION:-true} - - EMAIL_SENDER=${OPENPANEL_EMAIL_SENDER} - - RESEND_API_KEY=${RESEND_API_KEY} - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:3000/healthcheck || exit 1"] - interval: 10s - timeout: 5s - retries: 5 - - opdashboard: - image: lindesvard/openpanel-dashboard:latest - restart: always - depends_on: - opapi: - condition: service_healthy - environment: - # Common - - NODE_ENV=production - - NEXT_PUBLIC_SELF_HOSTED=true - # URLs - - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public - - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@opkv:6379 - - CLICKHOUSE_URL=${OPENPANEL_CLICKHOUSE_URL:-http://opch:8123/openpanel} - - SERVICE_FQDN_OPDASHBOARD - # Set coolify FQDN domain - - NEXT_PUBLIC_API_URL=$SERVICE_FQDN_OPAPI - - NEXT_PUBLIC_DASHBOARD_URL=$SERVICE_FQDN_OPDASHBOARD - healthcheck: - test: - ["CMD-SHELL", "curl -f http://localhost:3000/api/healthcheck || exit 1"] - interval: 10s - timeout: 5s - retries: 5 - - opworker: - image: lindesvard/openpanel-worker:latest - restart: always - depends_on: - opapi: - condition: service_healthy - environment: - # FQDN - - SERVICE_FQDN_OPBULLBOARD - # Common - - NODE_ENV=production - - NEXT_PUBLIC_SELF_HOSTED=true - # URLs - - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public - - DATABASE_URL_DIRECT=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@opdb:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public - - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@opkv:6379 - - CLICKHOUSE_URL=${OPENPANEL_CLICKHOUSE_URL:-http://opch:8123/openpanel} - # Set coolify FQDN domain - - NEXT_PUBLIC_API_URL=$SERVICE_FQDN_OPAPI - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:3000/healthcheck || exit 1"] - interval: 10s - timeout: 5s - retries: 5 From f34225dfd6e3325093a08d902aaf37b0faffdd62 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 6 Aug 2025 03:02:10 +0200 Subject: [PATCH 028/367] Update service-templates.json --- templates/service-templates.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/templates/service-templates.json b/templates/service-templates.json index c037a8889..9f2c0a773 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -2574,6 +2574,23 @@ "minversion": "0.0.0", "port": "8080" }, + "openpanel": { + "documentation": "https://openpanel.dev/docs?utm_source=coolify.io", + "slogan": "Open source alternative to Mixpanel and Plausible for product analytics", + "compose": "c2VydmljZXM6CiAgb3BlbnBhbmVsLWRhc2hib2FyZDoKICAgIGltYWdlOiAnbGluZGVzdmFyZC9vcGVucGFuZWwtZGFzaGJvYXJkOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIE5PREVfRU5WPXByb2R1Y3Rpb24KICAgICAgLSBORVhUX1BVQkxJQ19TRUxGX0hPU1RFRD10cnVlCiAgICAgIC0gU0VSVklDRV9GUUROX09QREFTSEJPQVJEXzMwMDAKICAgICAgLSAnTkVYVF9QVUJMSUNfQVBJX1VSTD0ke1NFUlZJQ0VfRlFETl9PUEFQSX0nCiAgICAgIC0gJ05FWFRfUFVCTElDX0RBU0hCT0FSRF9VUkw9JHtTRVJWSUNFX0ZRRE5fT1BEQVNIQk9BUkR9JwogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXM6Ly8ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU306JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUBwb3N0Z3Jlczo1NDMyLyR7T1BFTlBBTkVMX1BPU1RHUkVTX0RCOi1vcGVucGFuZWwtZGJ9P3NjaGVtYT1wdWJsaWMnCiAgICAgIC0gJ1JFRElTX1VSTD1yZWRpczovL2RlZmF1bHQ6JHtTRVJWSUNFX1BBU1NXT1JEX1JFRElTfUByZWRpczo2Mzc5JwogICAgICAtICdDTElDS0hPVVNFX1VSTD1odHRwOi8vY2xpY2tob3VzZTo4MTIzL29wZW5wYW5lbCcKICAgIGRlcGVuZHNfb246CiAgICAgIG9wZW5wYW5lbC1hcGk6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgb3BlbnBhbmVsLXdvcmtlcjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBwb3N0Z3JlczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICByZWRpczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBjbGlja2hvdXNlOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ2N1cmwgLWYgaHR0cDovL2xvY2FsaG9zdDozMDAwL2FwaS9oZWFsdGhjaGVjayB8fCBleGl0IDEnCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogNQogICAgICBzdGFydF9wZXJpb2Q6IDE1cwogIG9wZW5wYW5lbC1hcGk6CiAgICBpbWFnZTogJ2xpbmRlc3ZhcmQvb3BlbnBhbmVsLWFwaTpsYXRlc3QnCiAgICBjb21tYW5kOiAic2ggLWMgXCJcbiAgZWNobyAnUnVubmluZyBtaWdyYXRpb25zLi4uJ1xuICBDST10cnVlIHBucG0gLXIgcnVuIG1pZ3JhdGU6ZGVwbG95XG5cbiAgcG5wbSBzdGFydFxuXCJcbiIKICAgIGVudmlyb25tZW50OgogICAgICAtIE5PREVfRU5WPXByb2R1Y3Rpb24KICAgICAgLSBORVhUX1BVQkxJQ19TRUxGX0hPU1RFRD10cnVlCiAgICAgIC0gU0VSVklDRV9GUUROX09QQVBJCiAgICAgIC0gJ05FWFRfUFVCTElDX0FQSV9VUkw9JHtTRVJWSUNFX0ZRRE5fT1BBUEl9JwogICAgICAtICdORVhUX1BVQkxJQ19EQVNIQk9BUkRfVVJMPSR7U0VSVklDRV9GUUROX09QREFTSEJPQVJEfScKICAgICAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzOi8vJHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9OiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU31AcG9zdGdyZXM6NTQzMi8ke09QRU5QQU5FTF9QT1NUR1JFU19EQjotb3BlbnBhbmVsLWRifT9zY2hlbWE9cHVibGljJwogICAgICAtICdEQVRBQkFTRV9VUkxfRElSRUNUPXBvc3RncmVzOi8vJHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9OiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU31AcG9zdGdyZXM6NTQzMi8ke09QRU5QQU5FTF9QT1NUR1JFU19EQjotb3BlbnBhbmVsLWRifT9zY2hlbWE9cHVibGljJwogICAgICAtICdSRURJU19VUkw9cmVkaXM6Ly9kZWZhdWx0OiR7U0VSVklDRV9QQVNTV09SRF9SRURJU31AcmVkaXM6NjM3OScKICAgICAgLSAnQ0xJQ0tIT1VTRV9VUkw9aHR0cDovL2NsaWNraG91c2U6ODEyMy9vcGVucGFuZWwnCiAgICAgIC0gJ0NPT0tJRV9TRUNSRVQ9JHtTRVJWSUNFX0JBU0U2NF9DT09LSUVTRUNSRVR9JwogICAgICAtICdBTExPV19SRUdJU1RSQVRJT049JHtPUEVOUEFORUxfQUxMT1dfUkVHSVNUUkFUSU9OOi1mYWxzZX0nCiAgICAgIC0gJ0FMTE9XX0lOVklUQVRJT049JHtPUEVOUEFORUxfQUxMT1dfSU5WSVRBVElPTjotZmFsc2V9JwogICAgICAtICdFTUFJTF9TRU5ERVI9JHtPUEVOUEFORUxfRU1BSUxfU0VOREVSfScKICAgICAgLSAnUkVTRU5EX0FQSV9LRVk9JHtSRVNFTkRfQVBJX0tFWX0nCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3JlczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBjbGlja2hvdXNlOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHJlZGlzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ2N1cmwgLWYgaHR0cDovL2xvY2FsaG9zdDozMDAwL2hlYWx0aGNoZWNrIHx8IGV4aXQgMScKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiA1CiAgb3BlbnBhbmVsLXdvcmtlcjoKICAgIGltYWdlOiAnbGluZGVzdmFyZC9vcGVucGFuZWwtd29ya2VyOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIE5PREVfRU5WPXByb2R1Y3Rpb24KICAgICAgLSBORVhUX1BVQkxJQ19TRUxGX0hPU1RFRD10cnVlCiAgICAgIC0gU0VSVklDRV9GUUROX09QQlVMTEJPQVJECiAgICAgIC0gJ05FWFRfUFVCTElDX0FQSV9VUkw9JHtTRVJWSUNFX0ZRRE5fT1BBUEl9JwogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXM6Ly8ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU306JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUBwb3N0Z3Jlczo1NDMyLyR7T1BFTlBBTkVMX1BPU1RHUkVTX0RCOi1vcGVucGFuZWwtZGJ9P3NjaGVtYT1wdWJsaWMnCiAgICAgIC0gJ0RBVEFCQVNFX1VSTF9ESVJFQ1Q9cG9zdGdyZXM6Ly8ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU306JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUBwb3N0Z3Jlczo1NDMyLyR7T1BFTlBBTkVMX1BPU1RHUkVTX0RCOi1vcGVucGFuZWwtZGJ9P3NjaGVtYT1wdWJsaWMnCiAgICAgIC0gJ1JFRElTX1VSTD1yZWRpczovL2RlZmF1bHQ6JHtTRVJWSUNFX1BBU1NXT1JEX1JFRElTfUByZWRpczo2Mzc5JwogICAgICAtICdDTElDS0hPVVNFX1VSTD1odHRwOi8vY2xpY2tob3VzZTo4MTIzL29wZW5wYW5lbCcKICAgIGRlcGVuZHNfb246CiAgICAgIG9wZW5wYW5lbC1hcGk6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgY2xpY2tob3VzZToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdjdXJsIC1mIGh0dHA6Ly9sb2NhbGhvc3Q6MzAwMC9oZWFsdGhjaGVjayB8fCBleGl0IDEnCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogNQogICAgICBzdGFydF9wZXJpb2Q6IDVzCiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2LWFscGluZScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ29wZW5wYW5lbF9wb3N0Z3Jlc19kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19EQj0ke09QRU5QQU5FTF9QT1NUR1JFU19EQjotb3BlbnBhbmVsLWRifScKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDUKICByZWRpczoKICAgIGltYWdlOiAncmVkaXM6Ny40LWFscGluZScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ29wZW5wYW5lbF9yZWRpc19kYXRhOi9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1JFRElTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9SRURJU30nCiAgICBjb21tYW5kOiAncmVkaXMtc2VydmVyIC0tcmVxdWlyZXBhc3MgJHtTRVJWSUNFX1BBU1NXT1JEX1JFRElTfSAtLW1heG1lbW9yeS1wb2xpY3kgbm9ldmljdGlvbicKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSByZWRpcy1jbGkKICAgICAgICAtICctYScKICAgICAgICAtICcke1NFUlZJQ0VfUEFTU1dPUkRfUkVESVN9JwogICAgICAgIC0gcGluZwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDUKICBjbGlja2hvdXNlOgogICAgaW1hZ2U6ICdjbGlja2hvdXNlL2NsaWNraG91c2Utc2VydmVyOjI0LjMuMi1hbHBpbmUnCiAgICB2b2x1bWVzOgogICAgICAtICdvcGVucGFuZWxfY2xpY2tob3VzZV9kYXRhOi92YXIvbGliL2NsaWNraG91c2UnCiAgICAgIC0gJ29wZW5wYW5lbF9jbGlja2hvdXNlX2xvZ3M6L3Zhci9sb2cvY2xpY2tob3VzZS1zZXJ2ZXInCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2NsaWNraG91c2UtY29uZmlnLnhtbAogICAgICAgIHRhcmdldDogL2V0Yy9jbGlja2hvdXNlLXNlcnZlci9jb25maWcuZC9vcC1jb25maWcueG1sCiAgICAgICAgcmVhZF9vbmx5OiB0cnVlCiAgICAgICAgY29udGVudDogIjxjbGlja2hvdXNlPlxuICAgIDxsb2dnZXI+XG4gICAgICAgIDxsZXZlbD53YXJuaW5nPC9sZXZlbD5cbiAgICAgICAgPGNvbnNvbGU+dHJ1ZTwvY29uc29sZT5cbiAgICA8L2xvZ2dlcj5cbiAgICA8a2VlcF9hbGl2ZV90aW1lb3V0PjEwPC9rZWVwX2FsaXZlX3RpbWVvdXQ+XG4gICAgPCEtLSBTdG9wIGFsbCB0aGUgdW5uZWNlc3NhcnkgbG9nZ2luZyAtLT5cbiAgICA8cXVlcnlfdGhyZWFkX2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPHF1ZXJ5X2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPHRleHRfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8dHJhY2VfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8bWV0cmljX2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPGFzeW5jaHJvbm91c19tZXRyaWNfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8c2Vzc2lvbl9sb2cgcmVtb3ZlPVwicmVtb3ZlXCIvPlxuICAgIDxwYXJ0X2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPGxpc3Rlbl9ob3N0PjAuMC4wLjA8L2xpc3Rlbl9ob3N0PlxuICAgIDxpbnRlcnNlcnZlcl9saXN0ZW5faG9zdD4wLjAuMC4wPC9pbnRlcnNlcnZlcl9saXN0ZW5faG9zdD5cbiAgICA8aW50ZXJzZXJ2ZXJfaHR0cF9ob3N0Pm9wY2g8L2ludGVyc2VydmVyX2h0dHBfaG9zdD5cbiAgICA8IS0tIERpc2FibGUgY2dyb3VwIG1lbW9yeSBvYnNlcnZlciAtLT5cbiAgICA8Y2dyb3Vwc19tZW1vcnlfdXNhZ2Vfb2JzZXJ2ZXJfd2FpdF90aW1lPjA8L2Nncm91cHNfbWVtb3J5X3VzYWdlX29ic2VydmVyX3dhaXRfdGltZT5cbiAgICA8IS0tIE5vdCB1c2VkIGFueW1vcmUsIGJ1dCBrZXB0IGZvciBiYWNrd2FyZHMgY29tcGF0aWJpbGl0eSAtLT5cbiAgICA8bWFjcm9zPlxuICAgICAgICA8c2hhcmQ+MTwvc2hhcmQ+XG4gICAgICAgIDxyZXBsaWNhPnJlcGxpY2ExPC9yZXBsaWNhPlxuICAgICAgICA8Y2x1c3Rlcj5vcGVucGFuZWxfY2x1c3RlcjwvY2x1c3Rlcj5cbiAgICA8L21hY3Jvcz5cbjwvY2xpY2tob3VzZT4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2NsaWNraG91c2UtdXNlci1jb25maWcueG1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2NsaWNraG91c2Utc2VydmVyL3VzZXJzLmQvb3AtdXNlci1jb25maWcueG1sCiAgICAgICAgcmVhZF9vbmx5OiB0cnVlCiAgICAgICAgY29udGVudDogIjxjbGlja2hvdXNlPlxuICAgIDxwcm9maWxlcz5cbiAgICAgICAgPGRlZmF1bHQ+XG4gICAgICAgICAgICA8bG9nX3F1ZXJpZXM+MDwvbG9nX3F1ZXJpZXM+XG4gICAgICAgICAgICA8bG9nX3F1ZXJ5X3RocmVhZHM+MDwvbG9nX3F1ZXJ5X3RocmVhZHM+XG4gICAgICAgIDwvZGVmYXVsdD5cbiAgICA8L3Byb2ZpbGVzPlxuPC9jbGlja2hvdXNlPlxuIgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9pbml0LWRiLnNoCiAgICAgICAgdGFyZ2V0OiAvZG9ja2VyLWVudHJ5cG9pbnQtaW5pdGRiLmQvaW5pdC1kYi5zaAogICAgICAgIGNvbnRlbnQ6ICIjIS9iaW4vc2hcbnNldCAtZVxuXG5jbGlja2hvdXNlIGNsaWVudCAtbiA8PC1FT1NRTFxuICBDUkVBVEUgREFUQUJBU0UgSUYgTk9UIEVYSVNUUyBvcGVucGFuZWw7XG5FT1NRTCIKICAgIHVsaW1pdHM6CiAgICAgIG5vZmlsZToKICAgICAgICBzb2Z0OiAyNjIxNDQKICAgICAgICBoYXJkOiAyNjIxNDQKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnY2xpY2tob3VzZS1jbGllbnQgLS1xdWVyeSAiU0VMRUNUIDEiJwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDUK", + "tags": [ + "analytics", + "insights", + "privacy", + "mixpanel", + "plausible", + "google", + "alternative" + ], + "logo": "svgs/openpanel.svg", + "minversion": "0.0.0", + "port": "3000" + }, "orangehrm": { "documentation": "https://starterhelp.orangehrm.com/hc/en-us?utm_source=coolify.io", "slogan": "OrangeHRM open source HR management software.", From 4f77bd43472335403814efc87579330092f7b022 Mon Sep 17 00:00:00 2001 From: Trung-DV Date: Tue, 5 Aug 2025 15:20:22 +0700 Subject: [PATCH 029/367] Fix volume target, use the last part Signed-off-by: Trung-DV --- bootstrap/helpers/shared.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 7ce511f2c..a6b8fe55b 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -3420,7 +3420,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int } if (is_string($volume)) { $source = str($volume)->before(':'); - $target = str($volume)->after(':')->beforeLast(':'); + $target = str($volume)->afterLast(':'); $source = $name; $volume = "$source:$target"; } elseif (is_array($volume)) { From 1d4a19fb6104bf6f8b475e4aaa299f375e98f356 Mon Sep 17 00:00:00 2001 From: Gauthier POGAM--LE MONTAGNER Date: Mon, 11 Aug 2025 12:08:17 +0200 Subject: [PATCH 030/367] fix(service): update healthcheck of penpot backend container (#6272) --- templates/compose/penpot.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/penpot.yaml b/templates/compose/penpot.yaml index fa92abb7f..9a4e2de8d 100644 --- a/templates/compose/penpot.yaml +++ b/templates/compose/penpot.yaml @@ -55,7 +55,7 @@ services: - PENPOT_SMTP_TLS=${PENPOT_SMTP_TLS:-false} - PENPOT_SMTP_SSL=${PENPOT_SMTP_SSL:-false} healthcheck: - test: ['CMD', 'curl', '-f', 'http://127.0.0.1:6060/readyz'] + test: ['CMD', 'node', '-e', "require('http').get({host:'127.0.0.1', port:6060, path:'/readyz'}, res => process.exit(res.statusCode===200 ? 0 : 1)).on('error', () => process.exit(1));"] interval: 10s timeout: 30s retries: 15 From 03040d6bc87c6c9c296c4d7de567ce3dbe3d9634 Mon Sep 17 00:00:00 2001 From: Gauthier POGAM--LE MONTAGNER Date: Mon, 11 Aug 2025 12:13:25 +0200 Subject: [PATCH 031/367] feat(service): add librechat template (#5654) --- public/svgs/librechat.svg | 32 +++++++ templates/compose/librechat.yaml | 157 +++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 public/svgs/librechat.svg create mode 100644 templates/compose/librechat.yaml diff --git a/public/svgs/librechat.svg b/public/svgs/librechat.svg new file mode 100644 index 000000000..36a536d65 --- /dev/null +++ b/public/svgs/librechat.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/compose/librechat.yaml b/templates/compose/librechat.yaml new file mode 100644 index 000000000..e5647bb2d --- /dev/null +++ b/templates/compose/librechat.yaml @@ -0,0 +1,157 @@ +# documentation: https://docs.librechat.ai/install/configuration/dotenv.html +# slogan: Self-hosted, powerful, and privacy-focused chat UI for multiple AI models +# tags: ai,chat,gpt,claude,palm,openai,azure,huggingface,anthropic,ollama,llm +# logo: svgs/librechat.svg +# port: 3080 + +services: + librechat: + image: ghcr.io/danny-avila/librechat-dev-api:latest + depends_on: + mongodb: + condition: service_healthy + rag_api: + condition: service_healthy + environment: + - HOST=0.0.0.0 + - PORT=3080 + - SERVICE_FQDN_LIBRECHAT_3080 + # MongoDB settings + - MONGO_URI=mongodb://${SERVICE_USER_MONGO}:${SERVICE_PASSWORD_MONGO}@mongodb:27017/librechat?authSource=admin + # Meilisearch settings + - MEILI_HOST=http://meilisearch:7700 + - MEILI_MASTER_KEY=${SERVICE_PASSWORD_MEILI} + # RAG settings + - RAG_PORT=8000 + - RAG_API_URL=http://rag_api:8000 + # Auth settings + - DOMAIN_CLIENT=${SERVICE_FQDN_LIBRECHAT} + - DOMAIN_SERVER=${SERVICE_FQDN_LIBRECHAT} + - JWT_SECRET=${SERVICE_PASSWORD_JWT} + - JWT_REFRESH_SECRET=${SERVICE_PASSWORD_64_JWT} + # App settings + - APP_TITLE=${APP_TITLE:-LibreChat} + - ALLOW_EMAIL_LOGIN=${ALLOW_EMAIL_LOGIN:-true} + - ALLOW_REGISTRATION=${ALLOW_REGISTRATION:-true} + - ALLOW_SOCIAL_LOGIN=${ALLOW_SOCIAL_LOGIN:-false} + - ALLOW_SOCIAL_REGISTRATION=${ALLOW_SOCIAL_REGISTRATION:-false} + - ALLOW_PASSWORD_RESET=${ALLOW_PASSWORD_RESET:-false} + - ALLOW_UNVERIFIED_EMAIL_LOGIN=${ALLOW_UNVERIFIED_EMAIL_LOGIN:-true} + # Encryption settings + - CREDS_KEY=${SERVICE_PASSWORD_64_CREDS} + - CREDS_IV=${SERVICE_PASSWORD_CREDS} + # API Keys + - ANTHROPIC_API_KEY=${SERVICE_ANTHROPIC_API_KEY:-user_provided} + - GOOGLE_KEY=${SERVICE_GOOGLE_API_KEY:-user_provided} + - OPENAI_API_KEY=${SERVICE_OPENAI_API_KEY:-user_provided} + - ASSISTANTS_API_KEY=${SERVICE_ASSISTANTS_API_KEY:-user_provided} + # Debug settings + - DEBUG_LOGGING=${DEBUG_LOGGING:-false} + - DEBUG_OPENAI=${DEBUG_OPENAI:-false} + - DEBUG_PLUGINS=${DEBUG_OPENAI:-false} + - NO_INDEX=${NO_INDEX:-true} + healthcheck: + test: + [ + 'CMD', + 'wget', + '--no-verbose', + '--tries=1', + '--spider', + 'http://127.0.0.1:3080/api/health', + ] + interval: 5s + timeout: 10s + retries: 3 + volumes: + - librechat-images:/app/client/public/images + - librechat-logs:/app/api/logs + - librechat-uploads:/app/uploads + - type: bind + source: ./librechat.yaml + target: /app/librechat.yaml + content: | + # For more information, see the Configuration Guide: + # https://www.librechat.ai/docs/configuration/librechat_yaml + + # Configuration version (required) + version: 1.2.8 + + mongodb: + environment: + - MONGO_INITDB_ROOT_USERNAME=${SERVICE_USER_MONGO} + - MONGO_INITDB_ROOT_PASSWORD=${SERVICE_PASSWORD_MONGO} + image: mongo:8 + volumes: + - mongodb-data:/data/db + healthcheck: + test: + [ + 'CMD', + 'mongosh', + '--eval', + "db.runCommand('ping').ok", + '127.0.0.1:27017/test', + '--quiet', + ] + interval: 5s + timeout: 10s + retries: 3 + + meilisearch: + image: getmeili/meilisearch:v1.12.3 + environment: + - MEILI_MASTER_KEY=${SERVICE_PASSWORD_MEILI} + - MEILI_NO_ANALYTICS=${MEILI_NO_ANALYTICS:-false} + - MEILI_ENV=production + - MEILI_HOST=http://meilisearch:7700 + volumes: + - meilisearch-data:/meili_data + healthcheck: + test: ['CMD', 'curl', '-f', 'http://127.0.0.1:7700/health'] + interval: 2s + timeout: 10s + retries: 15 + + vectordb: + image: ankane/pgvector:latest + environment: + - POSTGRES_DB=rag + - POSTGRES_USER=${SERVICE_USER_POSTGRES} + - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} + - POSTGRES_HOST_AUTH_METHOD=trust + volumes: + - vectordb-data:/var/lib/postgresql/data + healthcheck: + test: + - CMD + - pg_isready + - '--username=$SERVICE_USER_POSTGRES' + - '--host=127.0.0.1' + - '--port=5432' + - '--dbname=rag' + interval: 2s + timeout: 1m + retries: 5 + start_period: 10s + + rag_api: + image: ghcr.io/danny-avila/librechat-rag-api-dev-lite:latest + depends_on: + vectordb: + condition: service_healthy + environment: + - POSTGRES_DB=rag + - POSTGRES_USER=${SERVICE_USER_POSTGRES} + - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} + - DB_HOST=vectordb + - DB_USER=${SERVICE_USER_POSTGRES} + - DB_PASSWORD=${SERVICE_PASSWORD_POSTGRES} + - DB_NAME=rag + - RAG_PORT=8000 + - RAG_OPENAI_API_KEY=${SERVICE_OPENAI_API_KEY:-user_provided} + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"] + interval: 5s + timeout: 10s + retries: 10 From bee98e02bc7e6335f3882162266aaeb2b3579aa7 Mon Sep 17 00:00:00 2001 From: howardshand Date: Mon, 11 Aug 2025 05:29:18 -0500 Subject: [PATCH 032/367] feat(service): add Homebox service (#6116) --- public/svgs/homebox.svg | 11 +++++++++++ templates/compose/homebox.yaml | 27 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 public/svgs/homebox.svg create mode 100644 templates/compose/homebox.yaml diff --git a/public/svgs/homebox.svg b/public/svgs/homebox.svg new file mode 100644 index 000000000..08670bbb9 --- /dev/null +++ b/public/svgs/homebox.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/templates/compose/homebox.yaml b/templates/compose/homebox.yaml new file mode 100644 index 000000000..0f645aa93 --- /dev/null +++ b/templates/compose/homebox.yaml @@ -0,0 +1,27 @@ +# documentation: https://github.com/sysadminsmedia/homebox +# slogan: Homebox is the inventory and organization system built for the Home User. +# tags: inventory, home, organize +# logo: svgs/homebox.svg +# port: 7745 + +services: + homebox: + image: 'ghcr.io/sysadminsmedia/homebox:latest' + environment: + - SERVICE_FQDN_HOMEBOX_7745 + - HBOX_OPTIONS_ALLOW_REGISTRATION=${HBOX_OPTIONS_ALLOW_REGISTRATION:-false} + - HBOX_LOG_LEVEL=${HBOX_LOG_LEVEL:-info} + - HBOX_LOG_FORMAT=${HBOX_LOG_FORMAT:-text} + - HBOX_WEB_MAX_UPLOAD_SIZE=${HBOX_WEB_MAX_UPLOAD_SIZE:-10} + - HBOX_MAILER_HOST=${HBOX_MAILER_HOST} + - HBOX_MAILER_PORT=${HBOX_MAILER_PORT:-587} + - HBOX_MAILER_USERNAME=${HBOX_MAILER_USERNAME} + - HBOX_MAILER_PASSWORD=${HBOX_MAILER_PASSWORD} + - HBOX_MAILER_FROM=${HBOX_MAILER_FROM} + volumes: + - 'homebox-data:/data/' + healthcheck: + test: ["CMD", "sh", "-c", "wget --method=GET -qO- http://localhost:7745/api/v1/status > /dev/null || exit 1"] + interval: 30s + timeout: 5s + retries: 10 From 7dcb5c43ae91aded4d50c60c596a43d84fc10878 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 11 Aug 2025 12:31:09 +0200 Subject: [PATCH 033/367] chore(service): homebox formatting --- templates/compose/homebox.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/compose/homebox.yaml b/templates/compose/homebox.yaml index 0f645aa93..6d3f3e597 100644 --- a/templates/compose/homebox.yaml +++ b/templates/compose/homebox.yaml @@ -6,7 +6,7 @@ services: homebox: - image: 'ghcr.io/sysadminsmedia/homebox:latest' + image: ghcr.io/sysadminsmedia/homebox:latest environment: - SERVICE_FQDN_HOMEBOX_7745 - HBOX_OPTIONS_ALLOW_REGISTRATION=${HBOX_OPTIONS_ALLOW_REGISTRATION:-false} @@ -19,7 +19,7 @@ services: - HBOX_MAILER_PASSWORD=${HBOX_MAILER_PASSWORD} - HBOX_MAILER_FROM=${HBOX_MAILER_FROM} volumes: - - 'homebox-data:/data/' + - homebox-data:/data healthcheck: test: ["CMD", "sh", "-c", "wget --method=GET -qO- http://localhost:7745/api/v1/status > /dev/null || exit 1"] interval: 30s From 9ec72e8769fc75b82d29bcf9f288215cdd6cdd61 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 11 Aug 2025 12:32:01 +0200 Subject: [PATCH 034/367] refactor(service): improve librechat - remove comments - format and reorder service --- templates/compose/librechat.yaml | 104 +++++++++++++++---------------- 1 file changed, 51 insertions(+), 53 deletions(-) diff --git a/templates/compose/librechat.yaml b/templates/compose/librechat.yaml index e5647bb2d..fcb8f8c6d 100644 --- a/templates/compose/librechat.yaml +++ b/templates/compose/librechat.yaml @@ -7,29 +7,19 @@ services: librechat: image: ghcr.io/danny-avila/librechat-dev-api:latest - depends_on: - mongodb: - condition: service_healthy - rag_api: - condition: service_healthy environment: - - HOST=0.0.0.0 - - PORT=3080 - SERVICE_FQDN_LIBRECHAT_3080 - # MongoDB settings - - MONGO_URI=mongodb://${SERVICE_USER_MONGO}:${SERVICE_PASSWORD_MONGO}@mongodb:27017/librechat?authSource=admin - # Meilisearch settings - - MEILI_HOST=http://meilisearch:7700 - - MEILI_MASTER_KEY=${SERVICE_PASSWORD_MEILI} - # RAG settings - - RAG_PORT=8000 - - RAG_API_URL=http://rag_api:8000 - # Auth settings - DOMAIN_CLIENT=${SERVICE_FQDN_LIBRECHAT} - DOMAIN_SERVER=${SERVICE_FQDN_LIBRECHAT} + - HOST=0.0.0.0 + - PORT=3080 + - MONGO_URI=mongodb://${SERVICE_USER_MONGO}:${SERVICE_PASSWORD_MONGO}@mongodb:27017/librechat?authSource=admin + - MEILI_HOST=http://meilisearch:7700 + - MEILI_MASTER_KEY=${SERVICE_PASSWORD_MEILI} + - RAG_PORT=8000 + - RAG_API_URL=http://rag-api:8000 - JWT_SECRET=${SERVICE_PASSWORD_JWT} - JWT_REFRESH_SECRET=${SERVICE_PASSWORD_64_JWT} - # App settings - APP_TITLE=${APP_TITLE:-LibreChat} - ALLOW_EMAIL_LOGIN=${ALLOW_EMAIL_LOGIN:-true} - ALLOW_REGISTRATION=${ALLOW_REGISTRATION:-true} @@ -37,32 +27,16 @@ services: - ALLOW_SOCIAL_REGISTRATION=${ALLOW_SOCIAL_REGISTRATION:-false} - ALLOW_PASSWORD_RESET=${ALLOW_PASSWORD_RESET:-false} - ALLOW_UNVERIFIED_EMAIL_LOGIN=${ALLOW_UNVERIFIED_EMAIL_LOGIN:-true} - # Encryption settings - CREDS_KEY=${SERVICE_PASSWORD_64_CREDS} - CREDS_IV=${SERVICE_PASSWORD_CREDS} - # API Keys - ANTHROPIC_API_KEY=${SERVICE_ANTHROPIC_API_KEY:-user_provided} - GOOGLE_KEY=${SERVICE_GOOGLE_API_KEY:-user_provided} - OPENAI_API_KEY=${SERVICE_OPENAI_API_KEY:-user_provided} - ASSISTANTS_API_KEY=${SERVICE_ASSISTANTS_API_KEY:-user_provided} - # Debug settings - DEBUG_LOGGING=${DEBUG_LOGGING:-false} - DEBUG_OPENAI=${DEBUG_OPENAI:-false} - DEBUG_PLUGINS=${DEBUG_OPENAI:-false} - NO_INDEX=${NO_INDEX:-true} - healthcheck: - test: - [ - 'CMD', - 'wget', - '--no-verbose', - '--tries=1', - '--spider', - 'http://127.0.0.1:3080/api/health', - ] - interval: 5s - timeout: 10s - retries: 3 volumes: - librechat-images:/app/client/public/images - librechat-logs:/app/api/logs @@ -71,28 +45,46 @@ services: source: ./librechat.yaml target: /app/librechat.yaml content: | - # For more information, see the Configuration Guide: - # https://www.librechat.ai/docs/configuration/librechat_yaml - - # Configuration version (required) version: 1.2.8 + depends_on: + mongodb: + condition: service_healthy + meilisearch: + condition: service_healthy + vectordb: + condition: service_healthy + rag-api: + condition: service_healthy + healthcheck: + test: + [ + "CMD", + "wget", + "--no-verbose", + "--tries=1", + "--spider", + "http://127.0.0.1:3080/api/health", + ] + interval: 5s + timeout: 10s + retries: 5 mongodb: + image: mongo:8 environment: - MONGO_INITDB_ROOT_USERNAME=${SERVICE_USER_MONGO} - MONGO_INITDB_ROOT_PASSWORD=${SERVICE_PASSWORD_MONGO} - image: mongo:8 volumes: - mongodb-data:/data/db healthcheck: test: [ - 'CMD', - 'mongosh', - '--eval', + "CMD", + "mongosh", + "--eval", "db.runCommand('ping').ok", - '127.0.0.1:27017/test', - '--quiet', + "127.0.0.1:27017/test", + "--quiet", ] interval: 5s timeout: 10s @@ -108,7 +100,7 @@ services: volumes: - meilisearch-data:/meili_data healthcheck: - test: ['CMD', 'curl', '-f', 'http://127.0.0.1:7700/health'] + test: ["CMD", "curl", "-f", "http://127.0.0.1:7700/health"] interval: 2s timeout: 10s retries: 15 @@ -126,20 +118,17 @@ services: test: - CMD - pg_isready - - '--username=$SERVICE_USER_POSTGRES' - - '--host=127.0.0.1' - - '--port=5432' - - '--dbname=rag' + - "--username=$SERVICE_USER_POSTGRES" + - "--host=127.0.0.1" + - "--port=5432" + - "--dbname=rag" interval: 2s timeout: 1m retries: 5 start_period: 10s - rag_api: + rag-api: image: ghcr.io/danny-avila/librechat-rag-api-dev-lite:latest - depends_on: - vectordb: - condition: service_healthy environment: - POSTGRES_DB=rag - POSTGRES_USER=${SERVICE_USER_POSTGRES} @@ -150,8 +139,17 @@ services: - DB_NAME=rag - RAG_PORT=8000 - RAG_OPENAI_API_KEY=${SERVICE_OPENAI_API_KEY:-user_provided} + depends_on: + vectordb: + condition: service_healthy healthcheck: - test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"] + test: + [ + "CMD", + "python", + "-c", + "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')", + ] interval: 5s timeout: 10s retries: 10 From e572017d27be55e56adf19d2351bfbab7cd1f679 Mon Sep 17 00:00:00 2001 From: Yanluis Fermin <32645451+Jacxk@users.noreply.github.com> Date: Mon, 11 Aug 2025 08:03:46 -0400 Subject: [PATCH 035/367] fix(api): duplicated logs in application endpoint (#6292) --- bootstrap/helpers/docker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 944c51e3c..739f98f22 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -1101,7 +1101,7 @@ function getContainerLogs(Server $server, string $container_id, int $lines = 100 ], $server); } - $output .= removeAnsiColors($output); + $output = removeAnsiColors($output); return $output; } From d53e493dcc601988490a210c90781e8c07d7c69e Mon Sep 17 00:00:00 2001 From: Aaryan meena <134821046+aaryan359@users.noreply.github.com> Date: Mon, 11 Aug 2025 18:03:31 +0530 Subject: [PATCH 036/367] chore: clarify usage of custom redis configuration (#6321) --- .../project/database/redis/general.blade.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/resources/views/livewire/project/database/redis/general.blade.php b/resources/views/livewire/project/database/redis/general.blade.php index 577c0d3e9..b4876f325 100644 --- a/resources/views/livewire/project/database/redis/general.blade.php +++ b/resources/views/livewire/project/database/redis/general.blade.php @@ -132,8 +132,16 @@ id="database.public_port" label="Public Port" />
+ placeholder="# maxmemory 256mb +# maxmemory-policy allkeys-lru +# timeout 300" + helper="You only need to provide the Redis directives you want to override — Redis will use default values for everything else.

+⚠️ Important: Coolify automatically applies the requirepass directive using the password shown in the Password field above. If you override requirepass in your custom configuration, make sure it matches the password field to avoid authentication issues.

+🔗 Tip: View the full Redis default configuration to see what options are available." + label="Custom Redis Configuration" rows="10" id="database.redis_conf" /> + + +

Advanced

Date: Mon, 11 Aug 2025 09:03:13 -0400 Subject: [PATCH 037/367] fix(service): documenso signees always pending (#6334) --- templates/compose/documenso.yaml | 59 ++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/templates/compose/documenso.yaml b/templates/compose/documenso.yaml index 6a3873799..97ae6f918 100644 --- a/templates/compose/documenso.yaml +++ b/templates/compose/documenso.yaml @@ -26,6 +26,16 @@ services: - NEXT_PRIVATE_SMTP_FROM_ADDRESS=${NEXT_PRIVATE_SMTP_FROM_ADDRESS} - NEXT_PRIVATE_DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@database/${POSTGRES_DB:-documenso-db}?schema=public - NEXT_PRIVATE_DIRECT_DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@database/${POSTGRES_DB:-documenso-db}?schema=public + - NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=/app/apps/remix/certs/certificate.p12 + - NEXT_PRIVATE_SIGNING_PASSPHRASE=${SERVICE_PASSWORD_DOCUMENSO} + - CERT_VALID_DAYS=${CERT_VALID_DAYS:-365} + - CERT_INFO_COUNTRY_NAME=${CERT_INFO_COUNTRY_NAME:-DO} + - CERT_INFO_STATE_OR_PROVIDENCE=${CERT_INFO_STATE_OR_PROVIDENCE:-Santiago} + - CERT_INFO_LOCALITY_NAME=${CERT_INFO_LOCALITY_NAME:-Santiago} + - CERT_INFO_ORGANIZATION_NAME=${CERT_INFO_ORGANIZATION_NAME:-Example INC} + - CERT_INFO_ORGANIZATIONAL_UNIT=${CERT_INFO_ORGANIZATIONAL_UNIT:-IT Department} + - CERT_INFO_EMAIL=${CERT_INFO_EMAIL:-example@gmail.com} + - NEXT_PUBLIC_DISABLE_SIGNUP=${DISABLE_LOGIN:-false} healthcheck: test: - CMD-SHELL @@ -33,6 +43,55 @@ services: interval: 2s timeout: 10s retries: 20 + entrypoint: + - /bin/sh + - -c + - | + echo "./certs" > /tmp/certs_dir_path + echo "./make-certs.sh" > /tmp/cert_script_path + echo "${SERVICE_PASSWORD_DOCUMENSO}" > /tmp/cert_pass + + touch /tmp/cert_info_path + cat < /tmp/cert_info_path + [ req ] + distinguished_name = req_distinguished_name + prompt = no + [ req_distinguished_name ] + C = ${CERT_INFO_COUNTRY_NAME} + ST = ${CERT_INFO_STATE_OR_PROVIDENCE} + L = ${CERT_INFO_LOCALITY_NAME} + O = ${CERT_INFO_ORGANIZATION_NAME} + OU = ${CERT_INFO_ORGANIZATIONAL_UNIT} + CN = ${SERVICE_FQDN_DOCUMENSO} + emailAddress = ${CERT_INFO_EMAIL} + EOF + + cat < "$(cat /tmp/cert_script_path)" + mkdir -p "$(cat /tmp/certs_dir_path)" && cd "$(cat /tmp/certs_dir_path)" + + openssl genrsa -out private.key 2048 + + openssl req \ + -new \ + -x509 \ + -key private.key \ + -out certificate.crt \ + -days ${CERT_VALID_DAYS} \ + -config /tmp/cert_info_path + + openssl pkcs12 \ + -export \ + -out certificate.p12 \ + -inkey private.key \ + -in certificate.crt \ + -legacy \ + -password file:/tmp/cert_pass + EOF + chmod +x "$(cat /tmp/cert_script_path)" + + sh "$(cat /tmp/cert_script_path)" + + ./start.sh database: image: postgres:17 From c404581b25c8c5319dc9fe1460bdedd9c3c532ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=8F=94=EF=B8=8F=20Peak?= <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 11 Aug 2025 18:22:03 +0200 Subject: [PATCH 038/367] fix(database): custom postgres configs with SSL (#6352) --- app/Actions/Database/StartPostgresql.php | 26 +++++++++++------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/app/Actions/Database/StartPostgresql.php b/app/Actions/Database/StartPostgresql.php index a40eac17b..4314ccd2f 100644 --- a/app/Actions/Database/StartPostgresql.php +++ b/app/Actions/Database/StartPostgresql.php @@ -185,6 +185,8 @@ class StartPostgresql } } + $command = ['postgres']; + if (filled($this->database->postgres_conf)) { $docker_compose['services'][$container_name]['volumes'] = array_merge( $docker_compose['services'][$container_name]['volumes'], @@ -195,29 +197,25 @@ class StartPostgresql 'read_only' => true, ]] ); - $docker_compose['services'][$container_name]['command'] = [ - 'postgres', - '-c', - 'config_file=/etc/postgresql/postgresql.conf', - ]; + $command = array_merge($command, ['-c', 'config_file=/etc/postgresql/postgresql.conf']); } if ($this->database->enable_ssl) { - $docker_compose['services'][$container_name]['command'] = [ - 'postgres', - '-c', - 'ssl=on', - '-c', - 'ssl_cert_file=/var/lib/postgresql/certs/server.crt', - '-c', - 'ssl_key_file=/var/lib/postgresql/certs/server.key', - ]; + $command = array_merge($command, [ + '-c', 'ssl=on', + '-c', 'ssl_cert_file=/var/lib/postgresql/certs/server.crt', + '-c', 'ssl_key_file=/var/lib/postgresql/certs/server.key', + ]); } // Add custom docker run options $docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options); $docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); + if (count($command) > 1) { + $docker_compose['services'][$container_name]['command'] = $command; + } + $docker_compose = Yaml::dump($docker_compose, 10); $docker_compose_base64 = base64_encode($docker_compose); $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; From 9b0fd2073a06a1f470d5b2b36bae9f43f4ab19c7 Mon Sep 17 00:00:00 2001 From: Yanluis Fermin <32645451+Jacxk@users.noreply.github.com> Date: Mon, 11 Aug 2025 12:31:12 -0400 Subject: [PATCH 039/367] fix(api): update service upsert to retain name and description values if not set --- app/Http/Controllers/Api/ServicesController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index f2d6c4dcf..1db2a3663 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -713,8 +713,8 @@ class ServicesController extends Controller } $connectToDockerNetwork = $request->connect_to_docker_network ?? false; - $service->name = $request->name ?? null; - $service->description = $request->description ?? null; + $service->name = $request->name ?? $service->name; + $service->description = $request->description ?? $service->description; $service->environment_id = $environment->id; $service->server_id = $server->id; $service->destination_id = $destination->id; From bf738b2d2d8cb749a35ce7c74298f6b9961ad21b Mon Sep 17 00:00:00 2001 From: Verity <83372423+CallMeVerity@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:55:37 +0100 Subject: [PATCH 040/367] feat(service): add pterodactyl & wings services (#5537) --- templates/compose/pterodactyl-with-wings.yaml | 140 ++++++++++++++++++ templates/compose/pterodactyl.yaml | 93 +++--------- templates/compose/wings.yaml | 44 ++++++ 3 files changed, 208 insertions(+), 69 deletions(-) create mode 100644 templates/compose/pterodactyl-with-wings.yaml create mode 100644 templates/compose/wings.yaml diff --git a/templates/compose/pterodactyl-with-wings.yaml b/templates/compose/pterodactyl-with-wings.yaml new file mode 100644 index 000000000..bd0c07cca --- /dev/null +++ b/templates/compose/pterodactyl-with-wings.yaml @@ -0,0 +1,140 @@ +# documentation: https://pterodactyl.io/ +# slogan: Pterodactyl is a free, open-source game server management panel +# tags: game, game server, management, panel, minecraft +# logo: svgs/pterodactyl.png +# port: 80, 8443 + +services: + mariadb: + image: mariadb:10.5 + healthcheck: + test: + ["CMD-SHELL", "healthcheck.sh --connect --innodb_initialized || exit 1"] + start_period: 10s + interval: 10s + timeout: 1s + retries: 3 + environment: + - MYSQL_ROOT_PASSWORD=$SERVICE_PASSWORD_MYSQLROOT + - MYSQL_DATABASE=pterodactyl-db + - MYSQL_USER=$SERVICE_USER_MYSQL + - MYSQL_PASSWORD=$SERVICE_PASSWORD_MYSQL + volumes: + - pterodactyl-db:/var/lib/mysql + + redis: + image: redis:alpine + healthcheck: + test: ["CMD-SHELL", "redis-cli ping || exit 1"] + interval: 10s + timeout: 1s + retries: 3 + + pterodactyl: + image: ghcr.io/pterodactyl/panel:latest + volumes: + - "panel-var:/app/var/" + - "panel-nginx:/etc/nginx/http.d/" + - "panel-certs:/etc/letsencrypt/" + - type: bind + source: ./etc/entrypoint.sh + target: /entrypoint.sh + mode: "0755" + content: | + #!/bin/sh + set -e + + echo "Setting logs permissions..." + chown -R nginx: /app/storage/logs/ + + USER_EXISTS=$(php artisan tinker --no-ansi --execute='echo \Pterodactyl\Models\User::where("email", "'"$ADMIN_EMAIL"'")->exists() ? "1" : "0";') + + if [ "$USER_EXISTS" = "0" ]; then + echo "Admin User does not exist, creating user now." + php artisan p:user:make --no-interaction \ + --admin=1 \ + --email="$ADMIN_EMAIL" \ + --username="$ADMIN_USERNAME" \ + --name-first="$ADMIN_FIRSTNAME" \ + --name-last="$ADMIN_LASTNAME" \ + --password="$ADMIN_PASSWORD" + echo "Admin user created successfully!" + else + echo "Admin User already exists, skipping creation." + fi + + exec supervisord --nodaemon + command: ["/entrypoint.sh"] + healthcheck: + test: ["CMD-SHELL", "curl -sf http://localhost:80 || exit 1"] + interval: 10s + timeout: 1s + retries: 3 + environment: + - SERVICE_FQDN_PTERODACTYL_80 + - ADMIN_EMAIL=${ADMIN_EMAIL:-admin@example.com} + - ADMIN_USERNAME=${SERVICE_USER_ADMIN} + - ADMIN_FIRSTNAME=${ADMIN_FIRSTNAME:-Admin} + - ADMIN_LASTNAME=${ADMIN_LASTNAME:-User} + - ADMIN_PASSWORD=${SERVICE_PASSWORD_ADMIN} + - PTERODACTYL_HTTPS=${PTERODACTYL_HTTPS:-false} + - APP_ENV=production + - APP_ENVIRONMENT_ONLY=false + - APP_URL=$SERVICE_FQDN_PTERODACTYL + - APP_TIMEZONE=${TIMEZONE:-UTC} + - APP_SERVICE_AUTHOR=${APP_SERVICE_AUTHOR:-author@example.com} + - LOG_LEVEL=${LOG_LEVEL:-debug} + - CACHE_DRIVER=redis + - SESSION_DRIVER=redis + - QUEUE_DRIVER=redis + - REDIS_HOST=redis + - DB_DATABASE=pterodactyl-db + - DB_USERNAME=$SERVICE_USER_MYSQL + - DB_HOST=mariadb + - DB_PORT=3306 + - DB_PASSWORD=$SERVICE_PASSWORD_MYSQL + - MAIL_FROM=$MAIL_FROM + - MAIL_DRIVER=$MAIL_DRIVER + - MAIL_HOST=$MAIL_HOST + - MAIL_PORT=$MAIL_PORT + - MAIL_USERNAME=$MAIL_USERNAME + - MAIL_PASSWORD=$MAIL_PASSWORD + - MAIL_ENCRYPTION=$MAIL_ENCRYPTION + + wings: + image: "ghcr.io/pterodactyl/wings:latest" + environment: + - SERVICE_FQDN_WINGS_8443 + - "TZ=${TIMEZONE:-UTC}" + - WINGS_USERNAME=$SERVICE_USER_WINGS + volumes: + - "/var/run/docker.sock:/var/run/docker.sock" + - "/var/lib/docker/containers/:/var/lib/docker/containers/" + - "/var/lib/pterodactyl/volumes:/var/lib/pterodactyl/volumes" + - "/tmp/pterodactyl:/tmp/pterodactyl" + - wings_lib:/var/lib/pterodactyl/ + - wings_logs:/var/log/pterodactyl/ + - type: bind + source: ./etc/config.yml + target: /etc/pterodactyl/config.yml + content: | + debug: false + uuid: ReplaceConfig + token_id: ReplaceConfig + token: ReplaceConfig + api: + host: 0.0.0.0 + port: 8443 # Warning, panel must have 443 as daemon port, while here it should should be 8443, FQDN in Coolify for this service should be https://*:8443 + ssl: + enabled: false + cert: ReplaceConfig + key: ReplaceConfig + upload_limit: 100 + system: + data: /var/lib/pterodactyl/volumes + sftp: + bind_port: 2022 + allowed_mounts: [] + remote: '' + ports: + - '2022:2022' \ No newline at end of file diff --git a/templates/compose/pterodactyl.yaml b/templates/compose/pterodactyl.yaml index ea64de47a..32939ad7e 100644 --- a/templates/compose/pterodactyl.yaml +++ b/templates/compose/pterodactyl.yaml @@ -1,4 +1,3 @@ -# ignore: true # documentation: https://pterodactyl.io/ # slogan: Pterodactyl is a free, open-source game server management panel # tags: game, game server, management, panel, minecraft @@ -8,8 +7,6 @@ services: mariadb: image: mariadb:10.5 - restart: unless-stopped - command: --default-authentication-plugin=mysql_native_password healthcheck: test: ["CMD-SHELL", "healthcheck.sh --connect --innodb_initialized || exit 1"] @@ -18,17 +15,15 @@ services: timeout: 1s retries: 3 environment: - - SERVICE_PASSWORD_MYSQL - MYSQL_ROOT_PASSWORD=$SERVICE_PASSWORD_MYSQLROOT - - MYSQL_DATABASE=panel - - MYSQL_USER=pterodactyl + - MYSQL_DATABASE=pterodactyl-db + - MYSQL_USER=$SERVICE_USER_MYSQL - MYSQL_PASSWORD=$SERVICE_PASSWORD_MYSQL volumes: - pterodactyl-db:/var/lib/mysql redis: image: redis:alpine - restart: unless-stopped healthcheck: test: ["CMD-SHELL", "redis-cli ping || exit 1"] interval: 10s @@ -37,7 +32,6 @@ services: pterodactyl: image: ghcr.io/pterodactyl/panel:latest - restart: unless-stopped volumes: - "panel-var:/app/var/" - "panel-nginx:/etc/nginx/http.d/" @@ -50,28 +44,26 @@ services: #!/bin/sh set -e - echo "Waiting for services to be ready..." - sleep 30 + echo "Setting logs permissions..." + chown -R nginx: /app/storage/logs/ - echo "Setting logs permissions..." - chown -R nginx: /app/storage/logs/ + USER_EXISTS=$(php artisan tinker --no-ansi --execute='echo \Pterodactyl\Models\User::where("email", "'"$ADMIN_EMAIL"'")->exists() ? "1" : "0";') - if ! php artisan p:user:list | grep -q "$ADMIN_EMAIL"; then - echo "Creating admin user..." - php artisan p:user:make --no-interaction \ - --admin=1 \ - --email="$ADMIN_EMAIL" \ - --username="$ADMIN_USERNAME" \ - --name-first="$ADMIN_FIRSTNAME" \ - --name-last="$ADMIN_LASTNAME" \ - --password="$ADMIN_PASSWORD" - echo "Admin user created" - else - echo "Admin user already exists, skipping creation" - fi - - exec supervisord -c --nodaemon + if [ "$USER_EXISTS" = "0" ]; then + echo "Admin User does not exist, creating user now." + php artisan p:user:make --no-interaction \ + --admin=1 \ + --email="$ADMIN_EMAIL" \ + --username="$ADMIN_USERNAME" \ + --name-first="$ADMIN_FIRSTNAME" \ + --name-last="$ADMIN_LASTNAME" \ + --password="$ADMIN_PASSWORD" + echo "Admin user created successfully!" + else + echo "Admin User already exists, skipping creation." + fi + exec supervisord --nodaemon command: ["/entrypoint.sh"] healthcheck: test: ["CMD-SHELL", "curl -sf http://localhost:80 || exit 1"] @@ -79,7 +71,7 @@ services: timeout: 1s retries: 3 environment: - - SERVICE_FQDN_PTERODACTYL + - SERVICE_FQDN_PTERODACTYL_80 - ADMIN_EMAIL=${ADMIN_EMAIL:-admin@example.com} - ADMIN_USERNAME=${SERVICE_USER_ADMIN} - ADMIN_FIRSTNAME=${ADMIN_FIRSTNAME:-Admin} @@ -88,7 +80,7 @@ services: - PTERODACTYL_HTTPS=${PTERODACTYL_HTTPS:-false} - APP_ENV=production - APP_ENVIRONMENT_ONLY=false - - APP_URL=${PTERODACTYL_PUBLIC_FQDN:-$SERVICE_FQDN_PTERODACTYL} + - APP_URL=$SERVICE_FQDN_PTERODACTYL - APP_TIMEZONE=${TIMEZONE:-UTC} - APP_SERVICE_AUTHOR=${APP_SERVICE_AUTHOR:-author@example.com} - LOG_LEVEL=${LOG_LEVEL:-debug} @@ -96,6 +88,8 @@ services: - SESSION_DRIVER=redis - QUEUE_DRIVER=redis - REDIS_HOST=redis + - DB_DATABASE=pterodactyl-db + - DB_USERNAME=$SERVICE_USER_MYSQL - DB_HOST=mariadb - DB_PORT=3306 - DB_PASSWORD=$SERVICE_PASSWORD_MYSQL @@ -105,43 +99,4 @@ services: - MAIL_PORT=$MAIL_PORT - MAIL_USERNAME=$MAIL_USERNAME - MAIL_PASSWORD=$MAIL_PASSWORD - - MAIL_ENCRYPTION=$MAIL_ENCRYPTION - - wings: - image: ghcr.io/pterodactyl/wings:latest - restart: unless-stopped - environment: - - SERVICE_FQDN_WINGS_8080 - - TZ=${TIMEZONE:-UTC} - - WINGS_USERNAME=pterodactyl - volumes: - - "/var/run/docker.sock:/var/run/docker.sock" - - "/var/lib/docker/containers/:/var/lib/docker/containers/" - - "/var/lib/pterodactyl/:/var/lib/pterodactyl/" # See https://discord.com/channels/122900397965705216/493443725012500490/1272195151309045902 - - "/tmp/pterodactyl/:/tmp/pterodactyl/" # See https://discord.com/channels/122900397965705216/493443725012500490/1272195151309045902 - - "wings-logs:/var/log/pterodactyl/" - - - type: bind - source: ./etc/config.yml - target: /etc/pterodactyl/config.yml - content: | - docker: - network: - interface: 172.28.0.1 - dns: - - 1.1.1.1 - - 1.0.0.1 - name: pterodactyl_nw - ispn: false - driver: "" - network_mode: pterodactyl_nw - is_internal: false - enable_icc: true - network_mtu: 1500 - interfaces: - v4: - subnet: 172.28.0.0/16 - gateway: 172.28.0.1 - v6: - subnet: fdba:17c8:6c94::/64 - gateway: fdba:17c8:6c94::1011 + - MAIL_ENCRYPTION=$MAIL_ENCRYPTION \ No newline at end of file diff --git a/templates/compose/wings.yaml b/templates/compose/wings.yaml new file mode 100644 index 000000000..ce4a073f8 --- /dev/null +++ b/templates/compose/wings.yaml @@ -0,0 +1,44 @@ +# documentation: https://pterodactyl.io/ +# slogan: Wings is Pterodactyl's server control plane +# tags: game, game server, management, panel, minecraft +# logo: svgs/pterodactyl.png +# port: 8443 + +services: + wings: + image: "ghcr.io/pterodactyl/wings:latest" + environment: + - SERVICE_FQDN_WINGS_8443 + - "TZ=${TIMEZONE:-UTC}" + - WINGS_USERNAME=$SERVICE_USER_WINGS + volumes: + - "/var/run/docker.sock:/var/run/docker.sock" + - "/var/lib/docker/containers/:/var/lib/docker/containers/" + - "/var/lib/pterodactyl/volumes:/var/lib/pterodactyl/volumes" + - "/tmp/pterodactyl:/tmp/pterodactyl" + - wings_lib:/var/lib/pterodactyl/ + - wings_logs:/var/log/pterodactyl/ + - type: bind + source: ./etc/config.yml + target: /etc/pterodactyl/config.yml + content: | + debug: false + uuid: ReplaceConfig + token_id: ReplaceConfig + token: ReplaceConfig + api: + host: 0.0.0.0 + port: 8443 # Warning, panel must have 443 as daemon port, while here it should should be 8443, FQDN in Coolify for this service should be https://*:8443 + ssl: + enabled: false + cert: ReplaceConfig + key: ReplaceConfig + upload_limit: 100 + system: + data: /var/lib/pterodactyl/volumes + sftp: + bind_port: 2022 + allowed_mounts: [] + remote: '' + ports: + - '2022:2022' \ No newline at end of file From 9d7e4286b66076f305dbf1876dc192a9d14a4686 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 11 Aug 2025 19:25:59 +0200 Subject: [PATCH 041/367] Update service-templates.json --- templates/service-templates.json | 83 +++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/templates/service-templates.json b/templates/service-templates.json index 9f2c0a773..49884462f 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -685,7 +685,7 @@ "documenso": { "documentation": "https://docs.documenso.com/?utm_source=coolify.io", "slogan": "Document signing, finally open source", - "compose": "c2VydmljZXM6CiAgZG9jdW1lbnNvOgogICAgaW1hZ2U6IGRvY3VtZW5zby9kb2N1bWVuc28KICAgIGRlcGVuZHNfb246CiAgICAgIGRhdGFiYXNlOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fRE9DVU1FTlNPXzMwMDAKICAgICAgLSAnTkVYVEFVVEhfVVJMPSR7U0VSVklDRV9GUUROX0RPQ1VNRU5TT30nCiAgICAgIC0gJ05FWFRBVVRIX1NFQ1JFVD0ke1NFUlZJQ0VfQkFTRTY0X0FVVEhTRUNSRVR9JwogICAgICAtICdORVhUX1BSSVZBVEVfRU5DUllQVElPTl9LRVk9JHtTRVJWSUNFX0JBU0U2NF9FTkNSWVBUSU9OS0VZfScKICAgICAgLSAnTkVYVF9QUklWQVRFX0VOQ1JZUFRJT05fU0VDT05EQVJZX0tFWT0ke1NFUlZJQ0VfQkFTRTY0X1NFQ09OREFSWUVOQ1JZUFRJT05LRVl9JwogICAgICAtICdORVhUX1BVQkxJQ19XRUJBUFBfVVJMPSR7U0VSVklDRV9GUUROX0RPQ1VNRU5TT30nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9TTVRQX1RSQU5TUE9SVD0ke05FWFRfUFJJVkFURV9TTVRQX1RSQU5TUE9SVH0nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9TTVRQX0hPU1Q9JHtORVhUX1BSSVZBVEVfU01UUF9IT1NUfScKICAgICAgLSAnTkVYVF9QUklWQVRFX1NNVFBfUE9SVD0ke05FWFRfUFJJVkFURV9TTVRQX1BPUlR9JwogICAgICAtICdORVhUX1BSSVZBVEVfU01UUF9VU0VSTkFNRT0ke05FWFRfUFJJVkFURV9TTVRQX1VTRVJOQU1FfScKICAgICAgLSAnTkVYVF9QUklWQVRFX1NNVFBfUEFTU1dPUkQ9JHtORVhUX1BSSVZBVEVfU01UUF9QQVNTV09SRH0nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9TTVRQX0ZST01fTkFNRT0ke05FWFRfUFJJVkFURV9TTVRQX0ZST01fTkFNRX0nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9TTVRQX0ZST01fQUREUkVTUz0ke05FWFRfUFJJVkFURV9TTVRQX0ZST01fQUREUkVTU30nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9EQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QGRhdGFiYXNlLyR7UE9TVEdSRVNfREI6LWRvY3VtZW5zby1kYn0/c2NoZW1hPXB1YmxpYycKICAgICAgLSAnTkVYVF9QUklWQVRFX0RJUkVDVF9EQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QGRhdGFiYXNlLyR7UE9TVEdSRVNfREI6LWRvY3VtZW5zby1kYn0/c2NoZW1hPXB1YmxpYycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAid2dldCAtcSAtTyAtIGh0dHA6Ly9kb2N1bWVuc286MzAwMC8gfCBncmVwIC1xICdTaWduIGluIHRvIHlvdXIgYWNjb3VudCciCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMjAKICBkYXRhYmFzZToKICAgIGltYWdlOiAncG9zdGdyZXM6MTcnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LWRvY3VtZW5zby1kYn0nCiAgICB2b2x1bWVzOgogICAgICAtICdkb2N1bWVuc29fcG9zdGdyZXNxbF9kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK", + "compose": "c2VydmljZXM6CiAgZG9jdW1lbnNvOgogICAgaW1hZ2U6IGRvY3VtZW5zby9kb2N1bWVuc28KICAgIGRlcGVuZHNfb246CiAgICAgIGRhdGFiYXNlOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fRE9DVU1FTlNPXzMwMDAKICAgICAgLSAnTkVYVEFVVEhfVVJMPSR7U0VSVklDRV9GUUROX0RPQ1VNRU5TT30nCiAgICAgIC0gJ05FWFRBVVRIX1NFQ1JFVD0ke1NFUlZJQ0VfQkFTRTY0X0FVVEhTRUNSRVR9JwogICAgICAtICdORVhUX1BSSVZBVEVfRU5DUllQVElPTl9LRVk9JHtTRVJWSUNFX0JBU0U2NF9FTkNSWVBUSU9OS0VZfScKICAgICAgLSAnTkVYVF9QUklWQVRFX0VOQ1JZUFRJT05fU0VDT05EQVJZX0tFWT0ke1NFUlZJQ0VfQkFTRTY0X1NFQ09OREFSWUVOQ1JZUFRJT05LRVl9JwogICAgICAtICdORVhUX1BVQkxJQ19XRUJBUFBfVVJMPSR7U0VSVklDRV9GUUROX0RPQ1VNRU5TT30nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9TTVRQX1RSQU5TUE9SVD0ke05FWFRfUFJJVkFURV9TTVRQX1RSQU5TUE9SVH0nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9TTVRQX0hPU1Q9JHtORVhUX1BSSVZBVEVfU01UUF9IT1NUfScKICAgICAgLSAnTkVYVF9QUklWQVRFX1NNVFBfUE9SVD0ke05FWFRfUFJJVkFURV9TTVRQX1BPUlR9JwogICAgICAtICdORVhUX1BSSVZBVEVfU01UUF9VU0VSTkFNRT0ke05FWFRfUFJJVkFURV9TTVRQX1VTRVJOQU1FfScKICAgICAgLSAnTkVYVF9QUklWQVRFX1NNVFBfUEFTU1dPUkQ9JHtORVhUX1BSSVZBVEVfU01UUF9QQVNTV09SRH0nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9TTVRQX0ZST01fTkFNRT0ke05FWFRfUFJJVkFURV9TTVRQX0ZST01fTkFNRX0nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9TTVRQX0ZST01fQUREUkVTUz0ke05FWFRfUFJJVkFURV9TTVRQX0ZST01fQUREUkVTU30nCiAgICAgIC0gJ05FWFRfUFJJVkFURV9EQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QGRhdGFiYXNlLyR7UE9TVEdSRVNfREI6LWRvY3VtZW5zby1kYn0/c2NoZW1hPXB1YmxpYycKICAgICAgLSAnTkVYVF9QUklWQVRFX0RJUkVDVF9EQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QGRhdGFiYXNlLyR7UE9TVEdSRVNfREI6LWRvY3VtZW5zby1kYn0/c2NoZW1hPXB1YmxpYycKICAgICAgLSBORVhUX1BSSVZBVEVfU0lHTklOR19MT0NBTF9GSUxFX1BBVEg9L2FwcC9hcHBzL3JlbWl4L2NlcnRzL2NlcnRpZmljYXRlLnAxMgogICAgICAtICdORVhUX1BSSVZBVEVfU0lHTklOR19QQVNTUEhSQVNFPSR7U0VSVklDRV9QQVNTV09SRF9ET0NVTUVOU099JwogICAgICAtICdDRVJUX1ZBTElEX0RBWVM9JHtDRVJUX1ZBTElEX0RBWVM6LTM2NX0nCiAgICAgIC0gJ0NFUlRfSU5GT19DT1VOVFJZX05BTUU9JHtDRVJUX0lORk9fQ09VTlRSWV9OQU1FOi1ET30nCiAgICAgIC0gJ0NFUlRfSU5GT19TVEFURV9PUl9QUk9WSURFTkNFPSR7Q0VSVF9JTkZPX1NUQVRFX09SX1BST1ZJREVOQ0U6LVNhbnRpYWdvfScKICAgICAgLSAnQ0VSVF9JTkZPX0xPQ0FMSVRZX05BTUU9JHtDRVJUX0lORk9fTE9DQUxJVFlfTkFNRTotU2FudGlhZ299JwogICAgICAtICdDRVJUX0lORk9fT1JHQU5JWkFUSU9OX05BTUU9JHtDRVJUX0lORk9fT1JHQU5JWkFUSU9OX05BTUU6LUV4YW1wbGUgSU5DfScKICAgICAgLSAnQ0VSVF9JTkZPX09SR0FOSVpBVElPTkFMX1VOSVQ9JHtDRVJUX0lORk9fT1JHQU5JWkFUSU9OQUxfVU5JVDotSVQgRGVwYXJ0bWVudH0nCiAgICAgIC0gJ0NFUlRfSU5GT19FTUFJTD0ke0NFUlRfSU5GT19FTUFJTDotZXhhbXBsZUBnbWFpbC5jb219JwogICAgICAtICdORVhUX1BVQkxJQ19ESVNBQkxFX1NJR05VUD0ke0RJU0FCTEVfTE9HSU46LWZhbHNlfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAid2dldCAtcSAtTyAtIGh0dHA6Ly9kb2N1bWVuc286MzAwMC8gfCBncmVwIC1xICdTaWduIGluIHRvIHlvdXIgYWNjb3VudCciCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMjAKICAgIGVudHJ5cG9pbnQ6CiAgICAgIC0gL2Jpbi9zaAogICAgICAtICctYycKICAgICAgLSAiZWNobyBcIi4vY2VydHNcIiA+IC90bXAvY2VydHNfZGlyX3BhdGhcbmVjaG8gXCIuL21ha2UtY2VydHMuc2hcIiA+IC90bXAvY2VydF9zY3JpcHRfcGF0aFxuZWNobyBcIiR7U0VSVklDRV9QQVNTV09SRF9ET0NVTUVOU099XCIgPiAvdG1wL2NlcnRfcGFzc1xuXG50b3VjaCAvdG1wL2NlcnRfaW5mb19wYXRoXG5jYXQgPDxFT0YgPiAvdG1wL2NlcnRfaW5mb19wYXRoXG5bIHJlcSBdXG5kaXN0aW5ndWlzaGVkX25hbWUgPSByZXFfZGlzdGluZ3Vpc2hlZF9uYW1lXG5wcm9tcHQgPSBub1xuWyByZXFfZGlzdGluZ3Vpc2hlZF9uYW1lIF1cbkMgICAgICAgICAgICA9ICR7Q0VSVF9JTkZPX0NPVU5UUllfTkFNRX1cblNUICAgICAgICAgICA9ICR7Q0VSVF9JTkZPX1NUQVRFX09SX1BST1ZJREVOQ0V9XG5MICAgICAgICAgICAgPSAke0NFUlRfSU5GT19MT0NBTElUWV9OQU1FfVxuTyAgICAgICAgICAgID0gJHtDRVJUX0lORk9fT1JHQU5JWkFUSU9OX05BTUV9XG5PVSAgICAgICAgICAgPSAke0NFUlRfSU5GT19PUkdBTklaQVRJT05BTF9VTklUfVxuQ04gICAgICAgICAgID0gJHtTRVJWSUNFX0ZRRE5fRE9DVU1FTlNPfVxuZW1haWxBZGRyZXNzID0gJHtDRVJUX0lORk9fRU1BSUx9XG5FT0ZcblxuY2F0IDw8RU9GID4gXCIkKGNhdCAvdG1wL2NlcnRfc2NyaXB0X3BhdGgpXCJcbm1rZGlyIC1wIFwiJChjYXQgL3RtcC9jZXJ0c19kaXJfcGF0aClcIiAmJiBjZCBcIiQoY2F0IC90bXAvY2VydHNfZGlyX3BhdGgpXCJcblxub3BlbnNzbCBnZW5yc2EgLW91dCBwcml2YXRlLmtleSAyMDQ4XG5cbm9wZW5zc2wgcmVxIFxcXG4gIC1uZXcgXFxcbiAgLXg1MDkgXFxcbiAgLWtleSBwcml2YXRlLmtleSBcXFxuICAtb3V0IGNlcnRpZmljYXRlLmNydCBcXFxuICAtZGF5cyAke0NFUlRfVkFMSURfREFZU30gXFxcbiAgLWNvbmZpZyAvdG1wL2NlcnRfaW5mb19wYXRoXG5cbm9wZW5zc2wgcGtjczEyIFxcXG4gIC1leHBvcnQgXFxcbiAgLW91dCBjZXJ0aWZpY2F0ZS5wMTIgXFxcbiAgLWlua2V5IHByaXZhdGUua2V5IFxcXG4gIC1pbiBjZXJ0aWZpY2F0ZS5jcnQgXFxcbiAgLWxlZ2FjeSBcXFxuICAtcGFzc3dvcmQgZmlsZTovdG1wL2NlcnRfcGFzc1xuRU9GXG5jaG1vZCAreCBcIiQoY2F0IC90bXAvY2VydF9zY3JpcHRfcGF0aClcIlxuXG5zaCBcIiQoY2F0IC90bXAvY2VydF9zY3JpcHRfcGF0aClcIlxuXG4uL3N0YXJ0LnNoXG4iCiAgZGF0YWJhc2U6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE3JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1kb2N1bWVuc28tZGJ9JwogICAgdm9sdW1lczoKICAgICAgLSAnZG9jdW1lbnNvX3Bvc3RncmVzcWxfZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", "tags": [ "signing", "opensource", @@ -1480,6 +1480,19 @@ "minversion": "0.0.0", "port": "7575" }, + "homebox": { + "documentation": "https://github.com/sysadminsmedia/homebox?utm_source=coolify.io", + "slogan": "Homebox is the inventory and organization system built for the Home User.", + "compose": "c2VydmljZXM6CiAgaG9tZWJveDoKICAgIGltYWdlOiAnZ2hjci5pby9zeXNhZG1pbnNtZWRpYS9ob21lYm94OmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9IT01FQk9YXzc3NDUKICAgICAgLSAnSEJPWF9PUFRJT05TX0FMTE9XX1JFR0lTVFJBVElPTj0ke0hCT1hfT1BUSU9OU19BTExPV19SRUdJU1RSQVRJT046LWZhbHNlfScKICAgICAgLSAnSEJPWF9MT0dfTEVWRUw9JHtIQk9YX0xPR19MRVZFTDotaW5mb30nCiAgICAgIC0gJ0hCT1hfTE9HX0ZPUk1BVD0ke0hCT1hfTE9HX0ZPUk1BVDotdGV4dH0nCiAgICAgIC0gJ0hCT1hfV0VCX01BWF9VUExPQURfU0laRT0ke0hCT1hfV0VCX01BWF9VUExPQURfU0laRTotMTB9JwogICAgICAtICdIQk9YX01BSUxFUl9IT1NUPSR7SEJPWF9NQUlMRVJfSE9TVH0nCiAgICAgIC0gJ0hCT1hfTUFJTEVSX1BPUlQ9JHtIQk9YX01BSUxFUl9QT1JUOi01ODd9JwogICAgICAtICdIQk9YX01BSUxFUl9VU0VSTkFNRT0ke0hCT1hfTUFJTEVSX1VTRVJOQU1FfScKICAgICAgLSAnSEJPWF9NQUlMRVJfUEFTU1dPUkQ9JHtIQk9YX01BSUxFUl9QQVNTV09SRH0nCiAgICAgIC0gJ0hCT1hfTUFJTEVSX0ZST009JHtIQk9YX01BSUxFUl9GUk9NfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2hvbWVib3gtZGF0YTovZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBzaAogICAgICAgIC0gJy1jJwogICAgICAgIC0gJ3dnZXQgLS1tZXRob2Q9R0VUIC1xTy0gaHR0cDovL2xvY2FsaG9zdDo3NzQ1L2FwaS92MS9zdGF0dXMgPiAvZGV2L251bGwgfHwgZXhpdCAxJwogICAgICBpbnRlcnZhbDogMzBzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCg==", + "tags": [ + "inventory", + "home", + "organize" + ], + "logo": "svgs/homebox.svg", + "minversion": "0.0.0", + "port": "7745" + }, "homepage": { "documentation": "https://gethomepage.dev/latest/?utm_source=coolify.io", "slogan": "A modern, fully static, fast, secure fully proxied, highly customizable application dashboard", @@ -1806,6 +1819,27 @@ "minversion": "0.0.0", "port": "8080" }, + "librechat": { + "documentation": "https://docs.librechat.ai/install/configuration/dotenv.html?utm_source=coolify.io", + "slogan": "Self-hosted, powerful, and privacy-focused chat UI for multiple AI models", + "compose": "c2VydmljZXM6CiAgbGlicmVjaGF0OgogICAgaW1hZ2U6ICdnaGNyLmlvL2Rhbm55LWF2aWxhL2xpYnJlY2hhdC1kZXYtYXBpOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9MSUJSRUNIQVRfMzA4MAogICAgICAtICdET01BSU5fQ0xJRU5UPSR7U0VSVklDRV9GUUROX0xJQlJFQ0hBVH0nCiAgICAgIC0gJ0RPTUFJTl9TRVJWRVI9JHtTRVJWSUNFX0ZRRE5fTElCUkVDSEFUfScKICAgICAgLSBIT1NUPTAuMC4wLjAKICAgICAgLSBQT1JUPTMwODAKICAgICAgLSAnTU9OR09fVVJJPW1vbmdvZGI6Ly8ke1NFUlZJQ0VfVVNFUl9NT05HT306JHtTRVJWSUNFX1BBU1NXT1JEX01PTkdPfUBtb25nb2RiOjI3MDE3L2xpYnJlY2hhdD9hdXRoU291cmNlPWFkbWluJwogICAgICAtICdNRUlMSV9IT1NUPWh0dHA6Ly9tZWlsaXNlYXJjaDo3NzAwJwogICAgICAtICdNRUlMSV9NQVNURVJfS0VZPSR7U0VSVklDRV9QQVNTV09SRF9NRUlMSX0nCiAgICAgIC0gUkFHX1BPUlQ9ODAwMAogICAgICAtICdSQUdfQVBJX1VSTD1odHRwOi8vcmFnLWFwaTo4MDAwJwogICAgICAtICdKV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9KV1R9JwogICAgICAtICdKV1RfUkVGUkVTSF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X0pXVH0nCiAgICAgIC0gJ0FQUF9USVRMRT0ke0FQUF9USVRMRTotTGlicmVDaGF0fScKICAgICAgLSAnQUxMT1dfRU1BSUxfTE9HSU49JHtBTExPV19FTUFJTF9MT0dJTjotdHJ1ZX0nCiAgICAgIC0gJ0FMTE9XX1JFR0lTVFJBVElPTj0ke0FMTE9XX1JFR0lTVFJBVElPTjotdHJ1ZX0nCiAgICAgIC0gJ0FMTE9XX1NPQ0lBTF9MT0dJTj0ke0FMTE9XX1NPQ0lBTF9MT0dJTjotZmFsc2V9JwogICAgICAtICdBTExPV19TT0NJQUxfUkVHSVNUUkFUSU9OPSR7QUxMT1dfU09DSUFMX1JFR0lTVFJBVElPTjotZmFsc2V9JwogICAgICAtICdBTExPV19QQVNTV09SRF9SRVNFVD0ke0FMTE9XX1BBU1NXT1JEX1JFU0VUOi1mYWxzZX0nCiAgICAgIC0gJ0FMTE9XX1VOVkVSSUZJRURfRU1BSUxfTE9HSU49JHtBTExPV19VTlZFUklGSUVEX0VNQUlMX0xPR0lOOi10cnVlfScKICAgICAgLSAnQ1JFRFNfS0VZPSR7U0VSVklDRV9QQVNTV09SRF82NF9DUkVEU30nCiAgICAgIC0gJ0NSRURTX0lWPSR7U0VSVklDRV9QQVNTV09SRF9DUkVEU30nCiAgICAgIC0gJ0FOVEhST1BJQ19BUElfS0VZPSR7U0VSVklDRV9BTlRIUk9QSUNfQVBJX0tFWTotdXNlcl9wcm92aWRlZH0nCiAgICAgIC0gJ0dPT0dMRV9LRVk9JHtTRVJWSUNFX0dPT0dMRV9BUElfS0VZOi11c2VyX3Byb3ZpZGVkfScKICAgICAgLSAnT1BFTkFJX0FQSV9LRVk9JHtTRVJWSUNFX09QRU5BSV9BUElfS0VZOi11c2VyX3Byb3ZpZGVkfScKICAgICAgLSAnQVNTSVNUQU5UU19BUElfS0VZPSR7U0VSVklDRV9BU1NJU1RBTlRTX0FQSV9LRVk6LXVzZXJfcHJvdmlkZWR9JwogICAgICAtICdERUJVR19MT0dHSU5HPSR7REVCVUdfTE9HR0lORzotZmFsc2V9JwogICAgICAtICdERUJVR19PUEVOQUk9JHtERUJVR19PUEVOQUk6LWZhbHNlfScKICAgICAgLSAnREVCVUdfUExVR0lOUz0ke0RFQlVHX09QRU5BSTotZmFsc2V9JwogICAgICAtICdOT19JTkRFWD0ke05PX0lOREVYOi10cnVlfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2xpYnJlY2hhdC1pbWFnZXM6L2FwcC9jbGllbnQvcHVibGljL2ltYWdlcycKICAgICAgLSAnbGlicmVjaGF0LWxvZ3M6L2FwcC9hcGkvbG9ncycKICAgICAgLSAnbGlicmVjaGF0LXVwbG9hZHM6L2FwcC91cGxvYWRzJwogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9saWJyZWNoYXQueWFtbAogICAgICAgIHRhcmdldDogL2FwcC9saWJyZWNoYXQueWFtbAogICAgICAgIGNvbnRlbnQ6ICJ2ZXJzaW9uOiAxLjIuOFxuIgogICAgZGVwZW5kc19vbjoKICAgICAgbW9uZ29kYjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBtZWlsaXNlYXJjaDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICB2ZWN0b3JkYjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICByYWctYXBpOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tbm8tdmVyYm9zZScKICAgICAgICAtICctLXRyaWVzPTEnCiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTozMDgwL2FwaS9oZWFsdGgnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogNQogIG1vbmdvZGI6CiAgICBpbWFnZTogJ21vbmdvOjgnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTU9OR09fSU5JVERCX1JPT1RfVVNFUk5BTUU9JHtTRVJWSUNFX1VTRVJfTU9OR099JwogICAgICAtICdNT05HT19JTklUREJfUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTU9OR099JwogICAgdm9sdW1lczoKICAgICAgLSAnbW9uZ29kYi1kYXRhOi9kYXRhL2RiJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIG1vbmdvc2gKICAgICAgICAtICctLWV2YWwnCiAgICAgICAgLSAiZGIucnVuQ29tbWFuZCgncGluZycpLm9rIgogICAgICAgIC0gJzEyNy4wLjAuMToyNzAxNy90ZXN0JwogICAgICAgIC0gJy0tcXVpZXQnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMwogIG1laWxpc2VhcmNoOgogICAgaW1hZ2U6ICdnZXRtZWlsaS9tZWlsaXNlYXJjaDp2MS4xMi4zJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ01FSUxJX01BU1RFUl9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEX01FSUxJfScKICAgICAgLSAnTUVJTElfTk9fQU5BTFlUSUNTPSR7TUVJTElfTk9fQU5BTFlUSUNTOi1mYWxzZX0nCiAgICAgIC0gTUVJTElfRU5WPXByb2R1Y3Rpb24KICAgICAgLSAnTUVJTElfSE9TVD1odHRwOi8vbWVpbGlzZWFyY2g6NzcwMCcKICAgIHZvbHVtZXM6CiAgICAgIC0gJ21laWxpc2VhcmNoLWRhdGE6L21laWxpX2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6NzcwMC9oZWFsdGgnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICB2ZWN0b3JkYjoKICAgIGltYWdlOiAnYW5rYW5lL3BndmVjdG9yOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX0RCPXJhZwogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSBQT1NUR1JFU19IT1NUX0FVVEhfTUVUSE9EPXRydXN0CiAgICB2b2x1bWVzOgogICAgICAtICd2ZWN0b3JkYi1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBwZ19pc3JlYWR5CiAgICAgICAgLSAnLS11c2VybmFtZT0kU0VSVklDRV9VU0VSX1BPU1RHUkVTJwogICAgICAgIC0gJy0taG9zdD0xMjcuMC4wLjEnCiAgICAgICAgLSAnLS1wb3J0PTU0MzInCiAgICAgICAgLSAnLS1kYm5hbWU9cmFnJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMW0KICAgICAgcmV0cmllczogNQogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogIHJhZy1hcGk6CiAgICBpbWFnZTogJ2doY3IuaW8vZGFubnktYXZpbGEvbGlicmVjaGF0LXJhZy1hcGktZGV2LWxpdGU6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfREI9cmFnCiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtIERCX0hPU1Q9dmVjdG9yZGIKICAgICAgLSAnREJfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gREJfTkFNRT1yYWcKICAgICAgLSBSQUdfUE9SVD04MDAwCiAgICAgIC0gJ1JBR19PUEVOQUlfQVBJX0tFWT0ke1NFUlZJQ0VfT1BFTkFJX0FQSV9LRVk6LXVzZXJfcHJvdmlkZWR9JwogICAgZGVwZW5kc19vbjoKICAgICAgdmVjdG9yZGI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBweXRob24KICAgICAgICAtICctYycKICAgICAgICAtICJpbXBvcnQgdXJsbGliLnJlcXVlc3Q7IHVybGxpYi5yZXF1ZXN0LnVybG9wZW4oJ2h0dHA6Ly8xMjcuMC4wLjE6ODAwMC9oZWFsdGgnKSIKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxMAo=", + "tags": [ + "ai", + "chat", + "gpt", + "claude", + "palm", + "openai", + "azure", + "huggingface", + "anthropic", + "ollama", + "llm" + ], + "logo": "svgs/librechat.svg", + "minversion": "0.0.0", + "port": "3080" + }, "libreoffice": { "documentation": "https://docs.linuxserver.io/images/docker-libreoffice/?utm_source=coolify.io", "slogan": "LibreOffice is a free and powerful office suite.", @@ -2715,7 +2749,7 @@ "penpot": { "documentation": "https://help.penpot.app/technical-guide/getting-started/#install-with-docker?utm_source=coolify.io", "slogan": "Penpot is the first Open Source design and prototyping platform for product teams.", - "compose": "c2VydmljZXM6CiAgZnJvbnRlbmQ6CiAgICBpbWFnZTogJ3BlbnBvdGFwcC9mcm9udGVuZDpsYXRlc3QnCiAgICB2b2x1bWVzOgogICAgICAtICdwZW5wb3QtYXNzZXRzOi9vcHQvZGF0YS9hc3NldHMnCiAgICBkZXBlbmRzX29uOgogICAgICBwZW5wb3QtYmFja2VuZDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBwZW5wb3QtZXhwb3J0ZXI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9GUk9OVEVORF84MDgwCiAgICAgIC0gJ1BFTlBPVF9GTEFHUz0ke1BFTlBPVF9GUk9OVEVORF9GTEFHUzotZW5hYmxlLWxvZ2luLXdpdGgtcGFzc3dvcmR9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjgwODAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBwZW5wb3QtYmFja2VuZDoKICAgIGltYWdlOiAncGVucG90YXBwL2JhY2tlbmQ6bGF0ZXN0JwogICAgdm9sdW1lczoKICAgICAgLSAncGVucG90LWFzc2V0czovb3B0L2RhdGEvYXNzZXRzJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudmlyb25tZW50OgogICAgICAtICdQRU5QT1RfRkxBR1M9JHtQRU5QT1RfQkFDS0VORF9GTEFHUzotZW5hYmxlLWxvZ2luLXdpdGgtcGFzc3dvcmQgZW5hYmxlLXNtdHAgZW5hYmxlLXByZXBsLXNlcnZlcn0nCiAgICAgIC0gUEVOUE9UX0hUVFBfU0VSVkVSX1BPUlQ9NjA2MAogICAgICAtIFBFTlBPVF9TRUNSRVRfS0VZPSRTRVJWSUNFX1JFQUxCQVNFNjRfNjRfUEVOUE9UCiAgICAgIC0gUEVOUE9UX1BVQkxJQ19VUkk9JFNFUlZJQ0VfRlFETl9GUk9OVEVORF84MDgwCiAgICAgIC0gJ1BFTlBPVF9CQUNLRU5EX1VSST1odHRwOi8vcGVucG90LWJhY2tlbmQnCiAgICAgIC0gJ1BFTlBPVF9FWFBPUlRFUl9VUkk9aHR0cDovL3BlbnBvdC1leHBvcnRlcicKICAgICAgLSAnUEVOUE9UX0RBVEFCQVNFX1VSST1wb3N0Z3Jlc3FsOi8vcG9zdGdyZXMvJHtQT1NUR1JFU19EQjotcGVucG90fScKICAgICAgLSAnUEVOUE9UX0RBVEFCQVNFX1VTRVJOQU1FPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnUEVOUE9UX0RBVEFCQVNFX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BFTlBPVF9SRURJU19VUkk9cmVkaXM6Ly9yZWRpcy8wJwogICAgICAtIFBFTlBPVF9BU1NFVFNfU1RPUkFHRV9CQUNLRU5EPWFzc2V0cy1mcwogICAgICAtIFBFTlBPVF9TVE9SQUdFX0FTU0VUU19GU19ESVJFQ1RPUlk9L29wdC9kYXRhL2Fzc2V0cwogICAgICAtICdQRU5QT1RfVEVMRU1FVFJZX0VOQUJMRUQ9JHtQRU5QT1RfVEVMRU1FVFJZX0VOQUJMRUQ6LWZhbHNlfScKICAgICAgLSAnUEVOUE9UX1NNVFBfREVGQVVMVF9GUk9NPSR7UEVOUE9UX1NNVFBfREVGQVVMVF9GUk9NOi1uby1yZXBseUBleGFtcGxlLmNvbX0nCiAgICAgIC0gJ1BFTlBPVF9TTVRQX0RFRkFVTFRfUkVQTFlfVE89JHtQRU5QT1RfU01UUF9ERUZBVUxUX1JFUExZX1RPOi1uby1yZXBseUBleGFtcGxlLmNvbX0nCiAgICAgIC0gJ1BFTlBPVF9TTVRQX0hPU1Q9JHtQRU5QT1RfU01UUF9IT1NUOi1tYWlscGl0fScKICAgICAgLSAnUEVOUE9UX1NNVFBfUE9SVD0ke1BFTlBPVF9TTVRQX1BPUlQ6LTEwMjV9JwogICAgICAtICdQRU5QT1RfU01UUF9VU0VSTkFNRT0ke1BFTlBPVF9TTVRQX1VTRVJOQU1FOi1wZW5wb3R9JwogICAgICAtICdQRU5QT1RfU01UUF9QQVNTV09SRD0ke1BFTlBPVF9TTVRQX1BBU1NXT1JEOi1wZW5wb3R9JwogICAgICAtICdQRU5QT1RfU01UUF9UTFM9JHtQRU5QT1RfU01UUF9UTFM6LWZhbHNlfScKICAgICAgLSAnUEVOUE9UX1NNVFBfU1NMPSR7UEVOUE9UX1NNVFBfU1NMOi1mYWxzZX0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6NjA2MC9yZWFkeXonCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogMzBzCiAgICAgIHJldHJpZXM6IDE1CiAgcGVucG90LWV4cG9ydGVyOgogICAgaW1hZ2U6ICdwZW5wb3RhcHAvZXhwb3J0ZXI6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUEVOUE9UX1BVQkxJQ19VUkk9JFNFUlZJQ0VfRlFETl9GUk9OVEVORF84MDgwCiAgICAgIC0gJ1BFTlBPVF9SRURJU19VUkk9cmVkaXM6Ly9yZWRpcy8wJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjYwNjEvcmVhZHl6JwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1CiAgbWFpbHBpdDoKICAgIGltYWdlOiAnYXhsbGVudC9tYWlscGl0OmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9NQUlMUElUXzgwMjUKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSAvbWFpbHBpdAogICAgICAgIC0gcmVhZHl6CiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICBwb3N0Z3JlczoKICAgIGltYWdlOiAncG9zdGdyZXM6MTUnCiAgICB2b2x1bWVzOgogICAgICAtICdwZW5wb3QtcG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX0lOSVREQl9BUkdTPS0tZGF0YS1jaGVja3N1bXMKICAgICAgLSBQT1NUR1JFU19VU0VSPSRTRVJWSUNFX1VTRVJfUE9TVEdSRVMKICAgICAgLSBQT1NUR1JFU19QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1wZW5wb3R9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczo3LWFscGluZScKICAgIGNvbW1hbmQ6ICdyZWRpcy1zZXJ2ZXIgLS1hcHBlbmRvbmx5IHllcycKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3BlbnBvdC1yZWRpcy1kYXRhOi9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHJlZGlzLWNsaQogICAgICAgIC0gcGluZwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", + "compose": "c2VydmljZXM6CiAgZnJvbnRlbmQ6CiAgICBpbWFnZTogJ3BlbnBvdGFwcC9mcm9udGVuZDpsYXRlc3QnCiAgICB2b2x1bWVzOgogICAgICAtICdwZW5wb3QtYXNzZXRzOi9vcHQvZGF0YS9hc3NldHMnCiAgICBkZXBlbmRzX29uOgogICAgICBwZW5wb3QtYmFja2VuZDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBwZW5wb3QtZXhwb3J0ZXI6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9GUk9OVEVORF84MDgwCiAgICAgIC0gJ1BFTlBPVF9GTEFHUz0ke1BFTlBPVF9GUk9OVEVORF9GTEFHUzotZW5hYmxlLWxvZ2luLXdpdGgtcGFzc3dvcmR9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjgwODAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBwZW5wb3QtYmFja2VuZDoKICAgIGltYWdlOiAncGVucG90YXBwL2JhY2tlbmQ6bGF0ZXN0JwogICAgdm9sdW1lczoKICAgICAgLSAncGVucG90LWFzc2V0czovb3B0L2RhdGEvYXNzZXRzJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGVudmlyb25tZW50OgogICAgICAtICdQRU5QT1RfRkxBR1M9JHtQRU5QT1RfQkFDS0VORF9GTEFHUzotZW5hYmxlLWxvZ2luLXdpdGgtcGFzc3dvcmQgZW5hYmxlLXNtdHAgZW5hYmxlLXByZXBsLXNlcnZlcn0nCiAgICAgIC0gUEVOUE9UX0hUVFBfU0VSVkVSX1BPUlQ9NjA2MAogICAgICAtIFBFTlBPVF9TRUNSRVRfS0VZPSRTRVJWSUNFX1JFQUxCQVNFNjRfNjRfUEVOUE9UCiAgICAgIC0gUEVOUE9UX1BVQkxJQ19VUkk9JFNFUlZJQ0VfRlFETl9GUk9OVEVORF84MDgwCiAgICAgIC0gJ1BFTlBPVF9CQUNLRU5EX1VSST1odHRwOi8vcGVucG90LWJhY2tlbmQnCiAgICAgIC0gJ1BFTlBPVF9FWFBPUlRFUl9VUkk9aHR0cDovL3BlbnBvdC1leHBvcnRlcicKICAgICAgLSAnUEVOUE9UX0RBVEFCQVNFX1VSST1wb3N0Z3Jlc3FsOi8vcG9zdGdyZXMvJHtQT1NUR1JFU19EQjotcGVucG90fScKICAgICAgLSAnUEVOUE9UX0RBVEFCQVNFX1VTRVJOQU1FPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnUEVOUE9UX0RBVEFCQVNFX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BFTlBPVF9SRURJU19VUkk9cmVkaXM6Ly9yZWRpcy8wJwogICAgICAtIFBFTlBPVF9BU1NFVFNfU1RPUkFHRV9CQUNLRU5EPWFzc2V0cy1mcwogICAgICAtIFBFTlBPVF9TVE9SQUdFX0FTU0VUU19GU19ESVJFQ1RPUlk9L29wdC9kYXRhL2Fzc2V0cwogICAgICAtICdQRU5QT1RfVEVMRU1FVFJZX0VOQUJMRUQ9JHtQRU5QT1RfVEVMRU1FVFJZX0VOQUJMRUQ6LWZhbHNlfScKICAgICAgLSAnUEVOUE9UX1NNVFBfREVGQVVMVF9GUk9NPSR7UEVOUE9UX1NNVFBfREVGQVVMVF9GUk9NOi1uby1yZXBseUBleGFtcGxlLmNvbX0nCiAgICAgIC0gJ1BFTlBPVF9TTVRQX0RFRkFVTFRfUkVQTFlfVE89JHtQRU5QT1RfU01UUF9ERUZBVUxUX1JFUExZX1RPOi1uby1yZXBseUBleGFtcGxlLmNvbX0nCiAgICAgIC0gJ1BFTlBPVF9TTVRQX0hPU1Q9JHtQRU5QT1RfU01UUF9IT1NUOi1tYWlscGl0fScKICAgICAgLSAnUEVOUE9UX1NNVFBfUE9SVD0ke1BFTlBPVF9TTVRQX1BPUlQ6LTEwMjV9JwogICAgICAtICdQRU5QT1RfU01UUF9VU0VSTkFNRT0ke1BFTlBPVF9TTVRQX1VTRVJOQU1FOi1wZW5wb3R9JwogICAgICAtICdQRU5QT1RfU01UUF9QQVNTV09SRD0ke1BFTlBPVF9TTVRQX1BBU1NXT1JEOi1wZW5wb3R9JwogICAgICAtICdQRU5QT1RfU01UUF9UTFM9JHtQRU5QT1RfU01UUF9UTFM6LWZhbHNlfScKICAgICAgLSAnUEVOUE9UX1NNVFBfU1NMPSR7UEVOUE9UX1NNVFBfU1NMOi1mYWxzZX0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gbm9kZQogICAgICAgIC0gJy1lJwogICAgICAgIC0gInJlcXVpcmUoJ2h0dHAnKS5nZXQoe2hvc3Q6JzEyNy4wLjAuMScsIHBvcnQ6NjA2MCwgcGF0aDonL3JlYWR5eid9LCByZXMgPT4gcHJvY2Vzcy5leGl0KHJlcy5zdGF0dXNDb2RlPT09MjAwID8gMCA6IDEpKS5vbignZXJyb3InLCAoKSA9PiBwcm9jZXNzLmV4aXQoMSkpOyIKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiAzMHMKICAgICAgcmV0cmllczogMTUKICBwZW5wb3QtZXhwb3J0ZXI6CiAgICBpbWFnZTogJ3BlbnBvdGFwcC9leHBvcnRlcjpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQRU5QT1RfUFVCTElDX1VSST0kU0VSVklDRV9GUUROX0ZST05URU5EXzgwODAKICAgICAgLSAnUEVOUE9UX1JFRElTX1VSST1yZWRpczovL3JlZGlzLzAnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6NjA2MS9yZWFkeXonCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBtYWlscGl0OgogICAgaW1hZ2U6ICdheGxsZW50L21haWxwaXQ6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX01BSUxQSVRfODAyNQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIC9tYWlscGl0CiAgICAgICAgLSByZWFkeXoKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHBvc3RncmVzOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3BlbnBvdC1wb3N0Z3Jlc3FsLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfSU5JVERCX0FSR1M9LS1kYXRhLWNoZWNrc3VtcwogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LXBlbnBvdH0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOjctYWxwaW5lJwogICAgY29tbWFuZDogJ3JlZGlzLXNlcnZlciAtLWFwcGVuZG9ubHkgeWVzJwogICAgdm9sdW1lczoKICAgICAgLSAncGVucG90LXJlZGlzLWRhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBwaW5nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK", "tags": [ "penpot", "design", @@ -2949,6 +2983,36 @@ "minversion": "0.0.0", "port": "9696" }, + "pterodactyl-with-wings": { + "documentation": "https://pterodactyl.io/?utm_source=coolify.io", + "slogan": "Pterodactyl is a free, open-source game server management panel", + "compose": "c2VydmljZXM6CiAgbWFyaWFkYjoKICAgIGltYWdlOiAnbWFyaWFkYjoxMC41JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdoZWFsdGhjaGVjay5zaCAtLWNvbm5lY3QgLS1pbm5vZGJfaW5pdGlhbGl6ZWQgfHwgZXhpdCAxJwogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDFzCiAgICAgIHJldHJpZXM6IDMKICAgIGVudmlyb25tZW50OgogICAgICAtIE1ZU1FMX1JPT1RfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUxST09UCiAgICAgIC0gTVlTUUxfREFUQUJBU0U9cHRlcm9kYWN0eWwtZGIKICAgICAgLSBNWVNRTF9VU0VSPSRTRVJWSUNFX1VTRVJfTVlTUUwKICAgICAgLSBNWVNRTF9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9NWVNRTAogICAgdm9sdW1lczoKICAgICAgLSAncHRlcm9kYWN0eWwtZGI6L3Zhci9saWIvbXlzcWwnCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOmFscGluZScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncmVkaXMtY2xpIHBpbmcgfHwgZXhpdCAxJwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDFzCiAgICAgIHJldHJpZXM6IDMKICBwdGVyb2RhY3R5bDoKICAgIGltYWdlOiAnZ2hjci5pby9wdGVyb2RhY3R5bC9wYW5lbDpsYXRlc3QnCiAgICB2b2x1bWVzOgogICAgICAtICdwYW5lbC12YXI6L2FwcC92YXIvJwogICAgICAtICdwYW5lbC1uZ2lueDovZXRjL25naW54L2h0dHAuZC8nCiAgICAgIC0gJ3BhbmVsLWNlcnRzOi9ldGMvbGV0c2VuY3J5cHQvJwogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9ldGMvZW50cnlwb2ludC5zaAogICAgICAgIHRhcmdldDogL2VudHJ5cG9pbnQuc2gKICAgICAgICBtb2RlOiAnMDc1NScKICAgICAgICBjb250ZW50OiAiIyEvYmluL3NoXG5zZXQgLWVcblxuIGVjaG8gXCJTZXR0aW5nIGxvZ3MgcGVybWlzc2lvbnMuLi5cIlxuIGNob3duIC1SIG5naW54OiAvYXBwL3N0b3JhZ2UvbG9ncy9cblxuIFVTRVJfRVhJU1RTPSQocGhwIGFydGlzYW4gdGlua2VyIC0tbm8tYW5zaSAtLWV4ZWN1dGU9J2VjaG8gXFxQdGVyb2RhY3R5bFxcTW9kZWxzXFxVc2VyOjp3aGVyZShcImVtYWlsXCIsIFwiJ1wiJEFETUlOX0VNQUlMXCInXCIpLT5leGlzdHMoKSA/IFwiMVwiIDogXCIwXCI7JylcblxuIGlmIFsgXCIkVVNFUl9FWElTVFNcIiA9IFwiMFwiIF07IHRoZW5cbiAgIGVjaG8gXCJBZG1pbiBVc2VyIGRvZXMgbm90IGV4aXN0LCBjcmVhdGluZyB1c2VyIG5vdy5cIlxuICAgcGhwIGFydGlzYW4gcDp1c2VyOm1ha2UgLS1uby1pbnRlcmFjdGlvbiBcXFxuICAgICAtLWFkbWluPTEgXFxcbiAgICAgLS1lbWFpbD1cIiRBRE1JTl9FTUFJTFwiIFxcXG4gICAgIC0tdXNlcm5hbWU9XCIkQURNSU5fVVNFUk5BTUVcIiBcXFxuICAgICAtLW5hbWUtZmlyc3Q9XCIkQURNSU5fRklSU1ROQU1FXCIgXFxcbiAgICAgLS1uYW1lLWxhc3Q9XCIkQURNSU5fTEFTVE5BTUVcIiBcXFxuICAgICAtLXBhc3N3b3JkPVwiJEFETUlOX1BBU1NXT1JEXCJcbiAgIGVjaG8gXCJBZG1pbiB1c2VyIGNyZWF0ZWQgc3VjY2Vzc2Z1bGx5IVwiXG4gZWxzZVxuICAgZWNobyBcIkFkbWluIFVzZXIgYWxyZWFkeSBleGlzdHMsIHNraXBwaW5nIGNyZWF0aW9uLlwiXG4gZmlcblxuIGV4ZWMgc3VwZXJ2aXNvcmQgLS1ub2RhZW1vblxuIgogICAgY29tbWFuZDoKICAgICAgLSAvZW50cnlwb2ludC5zaAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdjdXJsIC1zZiBodHRwOi8vbG9jYWxob3N0OjgwIHx8IGV4aXQgMScKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiAxcwogICAgICByZXRyaWVzOiAzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fUFRFUk9EQUNUWUxfODAKICAgICAgLSAnQURNSU5fRU1BSUw9JHtBRE1JTl9FTUFJTDotYWRtaW5AZXhhbXBsZS5jb219JwogICAgICAtICdBRE1JTl9VU0VSTkFNRT0ke1NFUlZJQ0VfVVNFUl9BRE1JTn0nCiAgICAgIC0gJ0FETUlOX0ZJUlNUTkFNRT0ke0FETUlOX0ZJUlNUTkFNRTotQWRtaW59JwogICAgICAtICdBRE1JTl9MQVNUTkFNRT0ke0FETUlOX0xBU1ROQU1FOi1Vc2VyfScKICAgICAgLSAnQURNSU5fUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX0FETUlOfScKICAgICAgLSAnUFRFUk9EQUNUWUxfSFRUUFM9JHtQVEVST0RBQ1RZTF9IVFRQUzotZmFsc2V9JwogICAgICAtIEFQUF9FTlY9cHJvZHVjdGlvbgogICAgICAtIEFQUF9FTlZJUk9OTUVOVF9PTkxZPWZhbHNlCiAgICAgIC0gQVBQX1VSTD0kU0VSVklDRV9GUUROX1BURVJPREFDVFlMCiAgICAgIC0gJ0FQUF9USU1FWk9ORT0ke1RJTUVaT05FOi1VVEN9JwogICAgICAtICdBUFBfU0VSVklDRV9BVVRIT1I9JHtBUFBfU0VSVklDRV9BVVRIT1I6LWF1dGhvckBleGFtcGxlLmNvbX0nCiAgICAgIC0gJ0xPR19MRVZFTD0ke0xPR19MRVZFTDotZGVidWd9JwogICAgICAtIENBQ0hFX0RSSVZFUj1yZWRpcwogICAgICAtIFNFU1NJT05fRFJJVkVSPXJlZGlzCiAgICAgIC0gUVVFVUVfRFJJVkVSPXJlZGlzCiAgICAgIC0gUkVESVNfSE9TVD1yZWRpcwogICAgICAtIERCX0RBVEFCQVNFPXB0ZXJvZGFjdHlsLWRiCiAgICAgIC0gREJfVVNFUk5BTUU9JFNFUlZJQ0VfVVNFUl9NWVNRTAogICAgICAtIERCX0hPU1Q9bWFyaWFkYgogICAgICAtIERCX1BPUlQ9MzMwNgogICAgICAtIERCX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX01ZU1FMCiAgICAgIC0gTUFJTF9GUk9NPSRNQUlMX0ZST00KICAgICAgLSBNQUlMX0RSSVZFUj0kTUFJTF9EUklWRVIKICAgICAgLSBNQUlMX0hPU1Q9JE1BSUxfSE9TVAogICAgICAtIE1BSUxfUE9SVD0kTUFJTF9QT1JUCiAgICAgIC0gTUFJTF9VU0VSTkFNRT0kTUFJTF9VU0VSTkFNRQogICAgICAtIE1BSUxfUEFTU1dPUkQ9JE1BSUxfUEFTU1dPUkQKICAgICAgLSBNQUlMX0VOQ1JZUFRJT049JE1BSUxfRU5DUllQVElPTgogIHdpbmdzOgogICAgaW1hZ2U6ICdnaGNyLmlvL3B0ZXJvZGFjdHlsL3dpbmdzOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9XSU5HU184NDQzCiAgICAgIC0gJ1RaPSR7VElNRVpPTkU6LVVUQ30nCiAgICAgIC0gV0lOR1NfVVNFUk5BTUU9JFNFUlZJQ0VfVVNFUl9XSU5HUwogICAgdm9sdW1lczoKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2snCiAgICAgIC0gJy92YXIvbGliL2RvY2tlci9jb250YWluZXJzLzovdmFyL2xpYi9kb2NrZXIvY29udGFpbmVycy8nCiAgICAgIC0gJy92YXIvbGliL3B0ZXJvZGFjdHlsL3ZvbHVtZXM6L3Zhci9saWIvcHRlcm9kYWN0eWwvdm9sdW1lcycKICAgICAgLSAnL3RtcC9wdGVyb2RhY3R5bDovdG1wL3B0ZXJvZGFjdHlsJwogICAgICAtICd3aW5nc19saWI6L3Zhci9saWIvcHRlcm9kYWN0eWwvJwogICAgICAtICd3aW5nc19sb2dzOi92YXIvbG9nL3B0ZXJvZGFjdHlsLycKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vZXRjL2NvbmZpZy55bWwKICAgICAgICB0YXJnZXQ6IC9ldGMvcHRlcm9kYWN0eWwvY29uZmlnLnltbAogICAgICAgIGNvbnRlbnQ6ICJkZWJ1ZzogZmFsc2VcbnV1aWQ6IFJlcGxhY2VDb25maWdcbnRva2VuX2lkOiBSZXBsYWNlQ29uZmlnXG50b2tlbjogUmVwbGFjZUNvbmZpZ1xuYXBpOlxuICBob3N0OiAwLjAuMC4wXG4gIHBvcnQ6IDg0NDMgIyBXYXJuaW5nLCBwYW5lbCBtdXN0IGhhdmUgNDQzIGFzIGRhZW1vbiBwb3J0LCB3aGlsZSBoZXJlIGl0IHNob3VsZCBzaG91bGQgYmUgODQ0MywgRlFETiBpbiBDb29saWZ5IGZvciB0aGlzIHNlcnZpY2Ugc2hvdWxkIGJlIGh0dHBzOi8vKjo4NDQzXG4gIHNzbDpcbiAgICBlbmFibGVkOiBmYWxzZVxuICAgIGNlcnQ6IFJlcGxhY2VDb25maWdcbiAgICBrZXk6IFJlcGxhY2VDb25maWdcbiAgdXBsb2FkX2xpbWl0OiAxMDBcbnN5c3RlbTpcbiAgZGF0YTogL3Zhci9saWIvcHRlcm9kYWN0eWwvdm9sdW1lc1xuICBzZnRwOlxuICAgIGJpbmRfcG9ydDogMjAyMlxuYWxsb3dlZF9tb3VudHM6IFtdXG5yZW1vdGU6ICcnIgogICAgcG9ydHM6CiAgICAgIC0gJzIwMjI6MjAyMicK", + "tags": [ + "game", + "game server", + "management", + "panel", + "minecraft" + ], + "logo": "svgs/pterodactyl.png", + "minversion": "0.0.0", + "port": "80, 8443" + }, + "pterodactyl": { + "documentation": "https://pterodactyl.io/?utm_source=coolify.io", + "slogan": "Pterodactyl is a free, open-source game server management panel", + "compose": "c2VydmljZXM6CiAgbWFyaWFkYjoKICAgIGltYWdlOiAnbWFyaWFkYjoxMC41JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdoZWFsdGhjaGVjay5zaCAtLWNvbm5lY3QgLS1pbm5vZGJfaW5pdGlhbGl6ZWQgfHwgZXhpdCAxJwogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDFzCiAgICAgIHJldHJpZXM6IDMKICAgIGVudmlyb25tZW50OgogICAgICAtIE1ZU1FMX1JPT1RfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUxST09UCiAgICAgIC0gTVlTUUxfREFUQUJBU0U9cHRlcm9kYWN0eWwtZGIKICAgICAgLSBNWVNRTF9VU0VSPSRTRVJWSUNFX1VTRVJfTVlTUUwKICAgICAgLSBNWVNRTF9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9NWVNRTAogICAgdm9sdW1lczoKICAgICAgLSAncHRlcm9kYWN0eWwtZGI6L3Zhci9saWIvbXlzcWwnCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOmFscGluZScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncmVkaXMtY2xpIHBpbmcgfHwgZXhpdCAxJwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDFzCiAgICAgIHJldHJpZXM6IDMKICBwdGVyb2RhY3R5bDoKICAgIGltYWdlOiAnZ2hjci5pby9wdGVyb2RhY3R5bC9wYW5lbDpsYXRlc3QnCiAgICB2b2x1bWVzOgogICAgICAtICdwYW5lbC12YXI6L2FwcC92YXIvJwogICAgICAtICdwYW5lbC1uZ2lueDovZXRjL25naW54L2h0dHAuZC8nCiAgICAgIC0gJ3BhbmVsLWNlcnRzOi9ldGMvbGV0c2VuY3J5cHQvJwogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9ldGMvZW50cnlwb2ludC5zaAogICAgICAgIHRhcmdldDogL2VudHJ5cG9pbnQuc2gKICAgICAgICBtb2RlOiAnMDc1NScKICAgICAgICBjb250ZW50OiAiIyEvYmluL3NoXG5zZXQgLWVcblxuIGVjaG8gXCJTZXR0aW5nIGxvZ3MgcGVybWlzc2lvbnMuLi5cIlxuIGNob3duIC1SIG5naW54OiAvYXBwL3N0b3JhZ2UvbG9ncy9cblxuIFVTRVJfRVhJU1RTPSQocGhwIGFydGlzYW4gdGlua2VyIC0tbm8tYW5zaSAtLWV4ZWN1dGU9J2VjaG8gXFxQdGVyb2RhY3R5bFxcTW9kZWxzXFxVc2VyOjp3aGVyZShcImVtYWlsXCIsIFwiJ1wiJEFETUlOX0VNQUlMXCInXCIpLT5leGlzdHMoKSA/IFwiMVwiIDogXCIwXCI7JylcblxuIGlmIFsgXCIkVVNFUl9FWElTVFNcIiA9IFwiMFwiIF07IHRoZW5cbiAgIGVjaG8gXCJBZG1pbiBVc2VyIGRvZXMgbm90IGV4aXN0LCBjcmVhdGluZyB1c2VyIG5vdy5cIlxuICAgcGhwIGFydGlzYW4gcDp1c2VyOm1ha2UgLS1uby1pbnRlcmFjdGlvbiBcXFxuICAgICAtLWFkbWluPTEgXFxcbiAgICAgLS1lbWFpbD1cIiRBRE1JTl9FTUFJTFwiIFxcXG4gICAgIC0tdXNlcm5hbWU9XCIkQURNSU5fVVNFUk5BTUVcIiBcXFxuICAgICAtLW5hbWUtZmlyc3Q9XCIkQURNSU5fRklSU1ROQU1FXCIgXFxcbiAgICAgLS1uYW1lLWxhc3Q9XCIkQURNSU5fTEFTVE5BTUVcIiBcXFxuICAgICAtLXBhc3N3b3JkPVwiJEFETUlOX1BBU1NXT1JEXCJcbiAgIGVjaG8gXCJBZG1pbiB1c2VyIGNyZWF0ZWQgc3VjY2Vzc2Z1bGx5IVwiXG4gZWxzZVxuICAgZWNobyBcIkFkbWluIFVzZXIgYWxyZWFkeSBleGlzdHMsIHNraXBwaW5nIGNyZWF0aW9uLlwiXG4gZmlcblxuIGV4ZWMgc3VwZXJ2aXNvcmQgLS1ub2RhZW1vblxuIgogICAgY29tbWFuZDoKICAgICAgLSAvZW50cnlwb2ludC5zaAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdjdXJsIC1zZiBodHRwOi8vbG9jYWxob3N0OjgwIHx8IGV4aXQgMScKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiAxcwogICAgICByZXRyaWVzOiAzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fUFRFUk9EQUNUWUxfODAKICAgICAgLSAnQURNSU5fRU1BSUw9JHtBRE1JTl9FTUFJTDotYWRtaW5AZXhhbXBsZS5jb219JwogICAgICAtICdBRE1JTl9VU0VSTkFNRT0ke1NFUlZJQ0VfVVNFUl9BRE1JTn0nCiAgICAgIC0gJ0FETUlOX0ZJUlNUTkFNRT0ke0FETUlOX0ZJUlNUTkFNRTotQWRtaW59JwogICAgICAtICdBRE1JTl9MQVNUTkFNRT0ke0FETUlOX0xBU1ROQU1FOi1Vc2VyfScKICAgICAgLSAnQURNSU5fUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX0FETUlOfScKICAgICAgLSAnUFRFUk9EQUNUWUxfSFRUUFM9JHtQVEVST0RBQ1RZTF9IVFRQUzotZmFsc2V9JwogICAgICAtIEFQUF9FTlY9cHJvZHVjdGlvbgogICAgICAtIEFQUF9FTlZJUk9OTUVOVF9PTkxZPWZhbHNlCiAgICAgIC0gQVBQX1VSTD0kU0VSVklDRV9GUUROX1BURVJPREFDVFlMCiAgICAgIC0gJ0FQUF9USU1FWk9ORT0ke1RJTUVaT05FOi1VVEN9JwogICAgICAtICdBUFBfU0VSVklDRV9BVVRIT1I9JHtBUFBfU0VSVklDRV9BVVRIT1I6LWF1dGhvckBleGFtcGxlLmNvbX0nCiAgICAgIC0gJ0xPR19MRVZFTD0ke0xPR19MRVZFTDotZGVidWd9JwogICAgICAtIENBQ0hFX0RSSVZFUj1yZWRpcwogICAgICAtIFNFU1NJT05fRFJJVkVSPXJlZGlzCiAgICAgIC0gUVVFVUVfRFJJVkVSPXJlZGlzCiAgICAgIC0gUkVESVNfSE9TVD1yZWRpcwogICAgICAtIERCX0RBVEFCQVNFPXB0ZXJvZGFjdHlsLWRiCiAgICAgIC0gREJfVVNFUk5BTUU9JFNFUlZJQ0VfVVNFUl9NWVNRTAogICAgICAtIERCX0hPU1Q9bWFyaWFkYgogICAgICAtIERCX1BPUlQ9MzMwNgogICAgICAtIERCX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX01ZU1FMCiAgICAgIC0gTUFJTF9GUk9NPSRNQUlMX0ZST00KICAgICAgLSBNQUlMX0RSSVZFUj0kTUFJTF9EUklWRVIKICAgICAgLSBNQUlMX0hPU1Q9JE1BSUxfSE9TVAogICAgICAtIE1BSUxfUE9SVD0kTUFJTF9QT1JUCiAgICAgIC0gTUFJTF9VU0VSTkFNRT0kTUFJTF9VU0VSTkFNRQogICAgICAtIE1BSUxfUEFTU1dPUkQ9JE1BSUxfUEFTU1dPUkQKICAgICAgLSBNQUlMX0VOQ1JZUFRJT049JE1BSUxfRU5DUllQVElPTgo=", + "tags": [ + "game", + "game server", + "management", + "panel", + "minecraft" + ], + "logo": "svgs/pterodactyl.png", + "minversion": "0.0.0", + "port": "80" + }, "qbittorrent": { "documentation": "https://docs.linuxserver.io/images/docker-qbittorrent/?utm_source=coolify.io", "slogan": "The qBittorrent project aims to provide an open-source software alternative to \u03bcTorrent.", @@ -3793,6 +3857,21 @@ "minversion": "0.0.0", "port": "8000" }, + "wings": { + "documentation": "https://pterodactyl.io/?utm_source=coolify.io", + "slogan": "Wings is Pterodactyl's server control plane", + "compose": "c2VydmljZXM6CiAgd2luZ3M6CiAgICBpbWFnZTogJ2doY3IuaW8vcHRlcm9kYWN0eWwvd2luZ3M6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX1dJTkdTXzg0NDMKICAgICAgLSAnVFo9JHtUSU1FWk9ORTotVVRDfScKICAgICAgLSBXSU5HU19VU0VSTkFNRT0kU0VSVklDRV9VU0VSX1dJTkdTCiAgICB2b2x1bWVzOgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jaycKICAgICAgLSAnL3Zhci9saWIvZG9ja2VyL2NvbnRhaW5lcnMvOi92YXIvbGliL2RvY2tlci9jb250YWluZXJzLycKICAgICAgLSAnL3Zhci9saWIvcHRlcm9kYWN0eWwvdm9sdW1lczovdmFyL2xpYi9wdGVyb2RhY3R5bC92b2x1bWVzJwogICAgICAtICcvdG1wL3B0ZXJvZGFjdHlsOi90bXAvcHRlcm9kYWN0eWwnCiAgICAgIC0gJ3dpbmdzX2xpYjovdmFyL2xpYi9wdGVyb2RhY3R5bC8nCiAgICAgIC0gJ3dpbmdzX2xvZ3M6L3Zhci9sb2cvcHRlcm9kYWN0eWwvJwogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9ldGMvY29uZmlnLnltbAogICAgICAgIHRhcmdldDogL2V0Yy9wdGVyb2RhY3R5bC9jb25maWcueW1sCiAgICAgICAgY29udGVudDogImRlYnVnOiBmYWxzZVxudXVpZDogUmVwbGFjZUNvbmZpZ1xudG9rZW5faWQ6IFJlcGxhY2VDb25maWdcbnRva2VuOiBSZXBsYWNlQ29uZmlnXG5hcGk6XG4gIGhvc3Q6IDAuMC4wLjBcbiAgcG9ydDogODQ0MyAjIFdhcm5pbmcsIHBhbmVsIG11c3QgaGF2ZSA0NDMgYXMgZGFlbW9uIHBvcnQsIHdoaWxlIGhlcmUgaXQgc2hvdWxkIHNob3VsZCBiZSA4NDQzLCBGUUROIGluIENvb2xpZnkgZm9yIHRoaXMgc2VydmljZSBzaG91bGQgYmUgaHR0cHM6Ly8qOjg0NDNcbiAgc3NsOlxuICAgIGVuYWJsZWQ6IGZhbHNlXG4gICAgY2VydDogUmVwbGFjZUNvbmZpZ1xuICAgIGtleTogUmVwbGFjZUNvbmZpZ1xuICB1cGxvYWRfbGltaXQ6IDEwMFxuc3lzdGVtOlxuICBkYXRhOiAvdmFyL2xpYi9wdGVyb2RhY3R5bC92b2x1bWVzXG4gIHNmdHA6XG4gICAgYmluZF9wb3J0OiAyMDIyXG5hbGxvd2VkX21vdW50czogW11cbnJlbW90ZTogJyciCiAgICBwb3J0czoKICAgICAgLSAnMjAyMjoyMDIyJwo=", + "tags": [ + "game", + "game server", + "management", + "panel", + "minecraft" + ], + "logo": "svgs/pterodactyl.png", + "minversion": "0.0.0", + "port": "8443" + }, "wireguard-easy": { "documentation": "https://github.com/wg-easy/wg-easy?utm_source=coolify.io", "slogan": "The easiest way to run WireGuard VPN + Web-based Admin UI.", From 8eb3f94644908c6b80ce0d07fdb34333668ad769 Mon Sep 17 00:00:00 2001 From: Scan <103391616+scanash00@users.noreply.github.com> Date: Mon, 11 Aug 2025 11:55:35 -0800 Subject: [PATCH 042/367] feat(service): add Bluesky PDS template (#6302) --- public/svgs/bluesky.svg | 3 ++ templates/compose/bluesky-pds.yaml | 72 +++++++++++++++++++++--------- 2 files changed, 54 insertions(+), 21 deletions(-) create mode 100644 public/svgs/bluesky.svg diff --git a/public/svgs/bluesky.svg b/public/svgs/bluesky.svg new file mode 100644 index 000000000..77ebea072 --- /dev/null +++ b/public/svgs/bluesky.svg @@ -0,0 +1,3 @@ + + + diff --git a/templates/compose/bluesky-pds.yaml b/templates/compose/bluesky-pds.yaml index 679eb8e79..7d2804421 100644 --- a/templates/compose/bluesky-pds.yaml +++ b/templates/compose/bluesky-pds.yaml @@ -1,34 +1,64 @@ -# ignore: true # documentation: https://github.com/bluesky-social/pds -# slogan: A social network for the decentralized web -# tags: pds, bluesky, social, network, decentralized -# logo: +# slogan: Bluesky PDS (Personal Data Server) +# tags: bluesky, pds, platform +# logo: svgs/bluesky.svg # port: 3000 services: pds: - image: ghcr.io/bluesky-social/pds:0.4 + image: 'ghcr.io/bluesky-social/pds:latest' volumes: - - pds-data:/pds + - ./pds-data:/pds environment: - SERVICE_FQDN_PDS_3000 - - PDS_JWT_SECRET=${SERVICE_BASE64_PDS} - - PDS_ADMIN_PASSWORD=${SERVICE_PASSWORD_PDS} - - PDS_ADMIN_EMAIL=${PDS_ADMIN_EMAIL:-admin@example.com} - - PDS_DATADIR=${PDS_DATADIR:-/pds} - - PDS_BLOBSTORE_DISK_LOCATION=${PDS_DATADIR:-/pds}/blocks - - PDS_BLOB_UPLOAD_LIMIT=${PDS_BLOB_UPLOAD_LIMIT:-52428800} - PDS_HOSTNAME=${SERVICE_URL_PDS} - - PDS_DID_PLC_URL=https://plc.directory - - PDS_BSKY_APP_VIEW_URL=https://api.bsky.app - - PDS_BSKY_APP_VIEW_DID=did:web:api.bsky.app - - PDS_REPORT_SERVICE_URL=https://mod.bsky.app - - PDS_REPORT_SERVICE_DID=did:plc:ar7c4by46qjdydhdevvrndac - - PDS_CRAWLERS=https://bsky.network + - PDS_JWT_SECRET=${SERVICE_PASSWORD_JWT_SECRET} + - PDS_ADMIN_PASSWORD=${SERVICE_PASSWORD_ADMIN} + - PDS_ADMIN_EMAIL=${SERVICE_EMAIL_ADMIN} + - PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=${PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX} + - PDS_DATA_DIRECTORY=${PDS_DATA_DIRECTORY:-/pds} + - PDS_BLOBSTORE_DISK_LOCATION=${PDS_DATA_DIRECTORY:-/pds}/blocks + - PDS_BLOB_UPLOAD_LIMIT=${PDS_BLOB_UPLOAD_LIMIT:-52428800} + - PDS_DID_PLC_URL=${PDS_DID_PLC_URL:-https://plc.directory} + - PDS_BSKY_APP_VIEW_URL=${PDS_BSKY_APP_VIEW_URL:-https://api.bsky.app} + - PDS_BSKY_APP_VIEW_DID=${PDS_BSKY_APP_VIEW_DID:-did:web:api.bsky.app} + - PDS_REPORT_SERVICE_URL=${PDS_REPORT_SERVICE_URL:-https://mod.bsky.app/xrpc/com.atproto.moderation.createReport} + - PDS_REPORT_SERVICE_DID=${PDS_REPORT_SERVICE_DID:-did:plc:ar7c4by46qjdydhdevvrndac} + - PDS_CRAWLERS=${PDS_CRAWLERS:-https://bsky.network} - LOG_ENABLED=${LOG_ENABLED:-true} - - PDS_EMAIL_SMTP_URL=${PDS_EMAIL_SMTP_URL:-smtp://localhost:8025} - - PDS_EMAIL_FROM_ADDRESS=${PDS_EMAIL_FROM_ADDRESS:-admin@example.com} - - PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=${SERVICE_HEX_32_ROTATIONKEY} + + command: > + sh -c ' + echo "Installing curl, bash, and pdsadmin..." + apk add --no-cache curl bash && \ + curl -o /usr/local/bin/pdsadmin.sh https://raw.githubusercontent.com/bluesky-social/pds/main/pdsadmin.sh && \ + chmod +x /usr/local/bin/pdsadmin.sh && \ + ln -sf /usr/local/bin/pdsadmin.sh /usr/local/bin/pdsadmin + + echo "Generating /pds/pds.env..." + printf "%s\n" \ + "SERVICE_FQDN_PDS_3000=$${SERVICE_FQDN_PDS_3000}" \ + "PDS_HOSTNAME=$${PDS_HOSTNAME}" \ + "PDS_JWT_SECRET=$${PDS_JWT_SECRET}" \ + "PDS_ADMIN_PASSWORD=$${PDS_ADMIN_PASSWORD}" \ + "PDS_ADMIN_EMAIL=$${PDS_ADMIN_EMAIL}" \ + "PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=$${PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX}" \ + "PDS_DATA_DIRECTORY=$${PDS_DATA_DIRECTORY}" \ + "PDS_BLOBSTORE_DISK_LOCATION=$${PDS_DATA_DIRECTORY}/blocks" \ + "PDS_BLOB_UPLOAD_LIMIT=$${PDS_BLOB_UPLOAD_LIMIT}" \ + "PDS_DID_PLC_URL=$${PDS_DID_PLC_URL}" \ + "PDS_BSKY_APP_VIEW_URL=$${PDS_BSKY_APP_VIEW_URL}" \ + "PDS_BSKY_APP_VIEW_DID=$${PDS_BSKY_APP_VIEW_DID}" \ + "PDS_REPORT_SERVICE_URL=$${PDS_REPORT_SERVICE_URL}" \ + "PDS_REPORT_SERVICE_DID=$${PDS_REPORT_SERVICE_DID}" \ + "PDS_CRAWLERS=$${PDS_CRAWLERS}" \ + "LOG_ENABLED=$${LOG_ENABLED}" \ + > /pds/pds.env + + echo "Launching PDS..." + exec node --enable-source-maps index.js + ' + healthcheck: test: ["CMD", "wget", "--spider", "http://127.0.0.1:3000/xrpc/_health"] interval: 2s From 158711a1658e9ca000711f33852d73f6a76b838c Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 11 Aug 2025 22:03:52 +0200 Subject: [PATCH 043/367] Update service-templates.json --- templates/service-templates.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/templates/service-templates.json b/templates/service-templates.json index 49884462f..1dbfac94b 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -203,6 +203,19 @@ "logo": "svgs/bitcoin.svg", "minversion": "0.0.0" }, + "bluesky-pds": { + "documentation": "https://github.com/bluesky-social/pds?utm_source=coolify.io", + "slogan": "Bluesky PDS (Personal Data Server)", + "compose": "c2VydmljZXM6CiAgcGRzOgogICAgaW1hZ2U6ICdnaGNyLmlvL2JsdWVza3ktc29jaWFsL3BkczpsYXRlc3QnCiAgICB2b2x1bWVzOgogICAgICAtICcuL3Bkcy1kYXRhOi9wZHMnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fUERTXzMwMDAKICAgICAgLSAnUERTX0hPU1ROQU1FPSR7U0VSVklDRV9VUkxfUERTfScKICAgICAgLSAnUERTX0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX0pXVF9TRUNSRVR9JwogICAgICAtICdQRFNfQURNSU5fUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX0FETUlOfScKICAgICAgLSAnUERTX0FETUlOX0VNQUlMPSR7U0VSVklDRV9FTUFJTF9BRE1JTn0nCiAgICAgIC0gJ1BEU19QTENfUk9UQVRJT05fS0VZX0syNTZfUFJJVkFURV9LRVlfSEVYPSR7UERTX1BMQ19ST1RBVElPTl9LRVlfSzI1Nl9QUklWQVRFX0tFWV9IRVh9JwogICAgICAtICdQRFNfREFUQV9ESVJFQ1RPUlk9JHtQRFNfREFUQV9ESVJFQ1RPUlk6LS9wZHN9JwogICAgICAtICdQRFNfQkxPQlNUT1JFX0RJU0tfTE9DQVRJT049JHtQRFNfREFUQV9ESVJFQ1RPUlk6LS9wZHN9L2Jsb2NrcycKICAgICAgLSAnUERTX0JMT0JfVVBMT0FEX0xJTUlUPSR7UERTX0JMT0JfVVBMT0FEX0xJTUlUOi01MjQyODgwMH0nCiAgICAgIC0gJ1BEU19ESURfUExDX1VSTD0ke1BEU19ESURfUExDX1VSTDotaHR0cHM6Ly9wbGMuZGlyZWN0b3J5fScKICAgICAgLSAnUERTX0JTS1lfQVBQX1ZJRVdfVVJMPSR7UERTX0JTS1lfQVBQX1ZJRVdfVVJMOi1odHRwczovL2FwaS5ic2t5LmFwcH0nCiAgICAgIC0gJ1BEU19CU0tZX0FQUF9WSUVXX0RJRD0ke1BEU19CU0tZX0FQUF9WSUVXX0RJRDotZGlkOndlYjphcGkuYnNreS5hcHB9JwogICAgICAtICdQRFNfUkVQT1JUX1NFUlZJQ0VfVVJMPSR7UERTX1JFUE9SVF9TRVJWSUNFX1VSTDotaHR0cHM6Ly9tb2QuYnNreS5hcHAveHJwYy9jb20uYXRwcm90by5tb2RlcmF0aW9uLmNyZWF0ZVJlcG9ydH0nCiAgICAgIC0gJ1BEU19SRVBPUlRfU0VSVklDRV9ESUQ9JHtQRFNfUkVQT1JUX1NFUlZJQ0VfRElEOi1kaWQ6cGxjOmFyN2M0Ynk0NnFqZHlkaGRldnZybmRhY30nCiAgICAgIC0gJ1BEU19DUkFXTEVSUz0ke1BEU19DUkFXTEVSUzotaHR0cHM6Ly9ic2t5Lm5ldHdvcmt9JwogICAgICAtICdMT0dfRU5BQkxFRD0ke0xPR19FTkFCTEVEOi10cnVlfScKICAgIGNvbW1hbmQ6ICJzaCAtYyAnXG4gIGVjaG8gXCJJbnN0YWxsaW5nIGN1cmwsIGJhc2gsIGFuZCBwZHNhZG1pbi4uLlwiXG4gIGFwayBhZGQgLS1uby1jYWNoZSBjdXJsIGJhc2ggJiYgXFxcbiAgY3VybCAtbyAvdXNyL2xvY2FsL2Jpbi9wZHNhZG1pbi5zaCBodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vYmx1ZXNreS1zb2NpYWwvcGRzL21haW4vcGRzYWRtaW4uc2ggJiYgXFxcbiAgY2htb2QgK3ggL3Vzci9sb2NhbC9iaW4vcGRzYWRtaW4uc2ggJiYgXFxcbiAgbG4gLXNmIC91c3IvbG9jYWwvYmluL3Bkc2FkbWluLnNoIC91c3IvbG9jYWwvYmluL3Bkc2FkbWluXG5cbiAgZWNobyBcIkdlbmVyYXRpbmcgL3Bkcy9wZHMuZW52Li4uXCJcbiAgcHJpbnRmIFwiJXNcXG5cIiBcXFxuICBcIlNFUlZJQ0VfRlFETl9QRFNfMzAwMD0kJHtTRVJWSUNFX0ZRRE5fUERTXzMwMDB9XCIgXFxcbiAgXCJQRFNfSE9TVE5BTUU9JCR7UERTX0hPU1ROQU1FfVwiIFxcXG4gIFwiUERTX0pXVF9TRUNSRVQ9JCR7UERTX0pXVF9TRUNSRVR9XCIgXFxcbiAgXCJQRFNfQURNSU5fUEFTU1dPUkQ9JCR7UERTX0FETUlOX1BBU1NXT1JEfVwiIFxcXG4gIFwiUERTX0FETUlOX0VNQUlMPSQke1BEU19BRE1JTl9FTUFJTH1cIiBcXFxuICBcIlBEU19QTENfUk9UQVRJT05fS0VZX0syNTZfUFJJVkFURV9LRVlfSEVYPSQke1BEU19QTENfUk9UQVRJT05fS0VZX0syNTZfUFJJVkFURV9LRVlfSEVYfVwiIFxcXG4gIFwiUERTX0RBVEFfRElSRUNUT1JZPSQke1BEU19EQVRBX0RJUkVDVE9SWX1cIiBcXFxuICBcIlBEU19CTE9CU1RPUkVfRElTS19MT0NBVElPTj0kJHtQRFNfREFUQV9ESVJFQ1RPUll9L2Jsb2Nrc1wiIFxcXG4gIFwiUERTX0JMT0JfVVBMT0FEX0xJTUlUPSQke1BEU19CTE9CX1VQTE9BRF9MSU1JVH1cIiBcXFxuICBcIlBEU19ESURfUExDX1VSTD0kJHtQRFNfRElEX1BMQ19VUkx9XCIgXFxcbiAgXCJQRFNfQlNLWV9BUFBfVklFV19VUkw9JCR7UERTX0JTS1lfQVBQX1ZJRVdfVVJMfVwiIFxcXG4gIFwiUERTX0JTS1lfQVBQX1ZJRVdfRElEPSQke1BEU19CU0tZX0FQUF9WSUVXX0RJRH1cIiBcXFxuICBcIlBEU19SRVBPUlRfU0VSVklDRV9VUkw9JCR7UERTX1JFUE9SVF9TRVJWSUNFX1VSTH1cIiBcXFxuICBcIlBEU19SRVBPUlRfU0VSVklDRV9ESUQ9JCR7UERTX1JFUE9SVF9TRVJWSUNFX0RJRH1cIiBcXFxuICBcIlBEU19DUkFXTEVSUz0kJHtQRFNfQ1JBV0xFUlN9XCIgXFxcbiAgXCJMT0dfRU5BQkxFRD0kJHtMT0dfRU5BQkxFRH1cIiBcXFxuICA+IC9wZHMvcGRzLmVudlxuXG4gIGVjaG8gXCJMYXVuY2hpbmcgUERTLi4uXCJcbiAgZXhlYyBub2RlIC0tZW5hYmxlLXNvdXJjZS1tYXBzIGluZGV4LmpzXG4nXG4iCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tc3BpZGVyJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6MzAwMC94cnBjL19oZWFsdGgnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTAK", + "tags": [ + "bluesky", + "pds", + "platform" + ], + "logo": "svgs/bluesky.svg", + "minversion": "0.0.0", + "port": "3000" + }, "bookstack": { "documentation": "https://www.bookstackapp.com/docs/?utm_source=coolify.io", "slogan": "BookStack is a simple, self-hosted, easy-to-use platform for organising and storing information", From 103a9c2df29c0c47983ea5faec8d75fe373884c0 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 23 Jul 2025 17:57:05 +0200 Subject: [PATCH 044/367] fix(policy): update delete method to check for admin status in S3StoragePolicy --- app/Policies/S3StoragePolicy.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Policies/S3StoragePolicy.php b/app/Policies/S3StoragePolicy.php index 28f5f8426..4f837a3dd 100644 --- a/app/Policies/S3StoragePolicy.php +++ b/app/Policies/S3StoragePolicy.php @@ -21,7 +21,7 @@ class S3StoragePolicy */ public function view(User $user, S3Storage $storage): bool { - return $user->teams()->where('id', $storage->team_id)->exists(); + return $user->teams()->get()->firstWhere('id', $storage->team_id)->exists(); } /** @@ -37,7 +37,7 @@ class S3StoragePolicy */ public function update(User $user, Server $server): bool { - return $user->teams()->get()->firstWhere('id', $server->team_id) !== null; + return $user->teams()->get()->firstWhere('id', $server->team_id)->exists() && $user->isAdmin(); } /** @@ -45,7 +45,7 @@ class S3StoragePolicy */ public function delete(User $user, S3Storage $storage): bool { - return $user->teams()->where('id', $storage->team_id)->exists(); + return $user->teams()->get()->firstWhere('id', $storage->team_id)->exists() && $user->isAdmin(); } /** From cc5abc093d35be0b25bb6a69fefa34575df1eba3 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 23 Jul 2025 20:49:12 +0200 Subject: [PATCH 045/367] fix(container): sort containers alphabetically by name in ExecuteContainerCommand and update filtering in Terminal Index --- app/Livewire/Project/Shared/ExecuteContainerCommand.php | 6 ++++++ app/Livewire/Terminal/Index.php | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/Livewire/Project/Shared/ExecuteContainerCommand.php b/app/Livewire/Project/Shared/ExecuteContainerCommand.php index 2d55807c7..6833492a6 100644 --- a/app/Livewire/Project/Shared/ExecuteContainerCommand.php +++ b/app/Livewire/Project/Shared/ExecuteContainerCommand.php @@ -132,6 +132,12 @@ class ExecuteContainerCommand extends Component }); } } + + // Sort containers alphabetically by name + $this->containers = $this->containers->sortBy(function ($container) { + return data_get($container, 'container.Names'); + }); + if ($this->containers->count() === 1) { $this->selected_container = data_get($this->containers->first(), 'container.Names'); } diff --git a/app/Livewire/Terminal/Index.php b/app/Livewire/Terminal/Index.php index 10084a991..f4008cb1c 100644 --- a/app/Livewire/Terminal/Index.php +++ b/app/Livewire/Terminal/Index.php @@ -59,7 +59,7 @@ class Index extends Component return null; })->filter(); - }); + })->sortBy('name'); } public function updatedSelectedUuid() From a0bc4dac55e5f9255376dc017874035eb8642fa7 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 24 Jul 2025 09:39:27 +0200 Subject: [PATCH 046/367] fix(application): streamline environment variable updates for Docker Compose services and enhance FQDN generation logic --- app/Livewire/Project/Application/General.php | 63 +- app/Livewire/Project/Resource/Create.php | 2 +- app/Models/Application.php | 2 +- app/Models/Service.php | 4 +- bootstrap/helpers/parsers.php | 1657 ++++++++++++++++++ bootstrap/helpers/services.php | 201 +-- bootstrap/helpers/shared.php | 113 +- 7 files changed, 1794 insertions(+), 248 deletions(-) create mode 100644 bootstrap/helpers/parsers.php diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 58a35caa0..2eadbaa0e 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -4,7 +4,6 @@ namespace App\Livewire\Project\Application; use App\Actions\Application\GenerateConfig; use App\Models\Application; -use App\Models\EnvironmentVariable; use Illuminate\Support\Collection; use Livewire\Component; use Spatie\Url\Url; @@ -270,7 +269,6 @@ class General extends Component $this->application->save(); $this->dispatch('success', 'Domain generated.'); if ($this->application->build_pack === 'dockercompose') { - $this->updateServiceEnvironmentVariables(); $this->loadComposeFile(showToast: false); } @@ -344,7 +342,7 @@ class General extends Component $this->application->custom_labels = base64_encode($this->customLabels); $this->application->save(); if ($this->application->build_pack === 'dockercompose') { - $this->loadComposeFile(); + $this->loadComposeFile(showToast: false); } $this->dispatch('configurationChanged'); } catch (\Throwable $e) { @@ -421,7 +419,7 @@ class General extends Component } if ($this->application->build_pack === 'dockercompose' && $this->initialDockerComposeLocation !== $this->application->docker_compose_location) { - $compose_return = $this->loadComposeFile(); + $compose_return = $this->loadComposeFile(showToast: false); if ($compose_return instanceof \Livewire\Features\SupportEvents\Event) { return; } @@ -470,7 +468,6 @@ class General extends Component } $this->application->docker_compose_domains = json_encode($originalDomains); - foreach ($originalDomains as $serviceName => $service) { $domain = data_get($service, 'domain'); if ($domain) { @@ -486,12 +483,6 @@ class General extends Component } $this->application->custom_labels = base64_encode($this->customLabels); $this->application->save(); - - // Update SERVICE_FQDN_ and SERVICE_URL_ environment variables for Docker Compose applications - if ($this->application->build_pack === 'dockercompose') { - $this->updateServiceEnvironmentVariables(); - } - $showToaster && ! $warning && $this->dispatch('success', 'Application settings updated!'); } catch (\Throwable $e) { $originalFqdn = $this->application->getOriginal('fqdn'); @@ -525,25 +516,33 @@ class General extends Component foreach ($domains as $serviceName => $service) { $serviceNameFormatted = str($serviceName)->upper()->replace('-', '_'); $domain = data_get($service, 'domain'); + // Delete SERVICE_FQDN_ and SERVICE_URL_ variables if domain is removed + $this->application->environment_variables()->where('resourceable_type', Application::class) + ->where('resourceable_id', $this->application->id) + ->where('key', 'LIKE', "SERVICE_FQDN_{$serviceNameFormatted}%") + ->delete(); + + $this->application->environment_variables()->where('resourceable_type', Application::class) + ->where('resourceable_id', $this->application->id) + ->where('key', 'LIKE', "SERVICE_URL_{$serviceNameFormatted}%") + ->delete(); if ($domain) { // Create or update SERVICE_FQDN_ and SERVICE_URL_ variables $fqdn = Url::fromString($domain); $port = $fqdn->getPort(); $path = $fqdn->getPath(); - $fqdnValue = $fqdn->getScheme().'://'.$fqdn->getHost(); - if ($path !== '/') { - $fqdnValue = $fqdnValue.$path; - } - $urlValue = str($domain)->after('://'); + $urlValue = $fqdn->getScheme().'://'.$fqdn->getHost(); if ($path !== '/') { $urlValue = $urlValue.$path; } + $fqdnValue = str($domain)->after('://'); + if ($path !== '/') { + $fqdnValue = $fqdnValue.$path; + } // Create/update SERVICE_FQDN_ - EnvironmentVariable::updateOrCreate([ - 'resourceable_type' => Application::class, - 'resourceable_id' => $this->application->id, + $this->application->environment_variables()->updateOrCreate([ 'key' => "SERVICE_FQDN_{$serviceNameFormatted}", ], [ 'value' => $fqdnValue, @@ -552,21 +551,16 @@ class General extends Component ]); // Create/update SERVICE_URL_ - EnvironmentVariable::updateOrCreate([ - 'resourceable_type' => Application::class, - 'resourceable_id' => $this->application->id, + $this->application->environment_variables()->updateOrCreate([ 'key' => "SERVICE_URL_{$serviceNameFormatted}", ], [ 'value' => $urlValue, 'is_build_time' => false, 'is_preview' => false, ]); - // Create/update port-specific variables if port exists - if ($port) { - EnvironmentVariable::updateOrCreate([ - 'resourceable_type' => Application::class, - 'resourceable_id' => $this->application->id, + if (filled($port)) { + $this->application->environment_variables()->updateOrCreate([ 'key' => "SERVICE_FQDN_{$serviceNameFormatted}_{$port}", ], [ 'value' => $fqdnValue, @@ -574,9 +568,7 @@ class General extends Component 'is_preview' => false, ]); - EnvironmentVariable::updateOrCreate([ - 'resourceable_type' => Application::class, - 'resourceable_id' => $this->application->id, + $this->application->environment_variables()->updateOrCreate([ 'key' => "SERVICE_URL_{$serviceNameFormatted}_{$port}", ], [ 'value' => $urlValue, @@ -584,17 +576,6 @@ class General extends Component 'is_preview' => false, ]); } - } else { - // Delete SERVICE_FQDN_ and SERVICE_URL_ variables if domain is removed - EnvironmentVariable::where('resourceable_type', Application::class) - ->where('resourceable_id', $this->application->id) - ->where('key', 'LIKE', "SERVICE_FQDN_{$serviceNameFormatted}%") - ->delete(); - - EnvironmentVariable::where('resourceable_type', Application::class) - ->where('resourceable_id', $this->application->id) - ->where('key', 'LIKE', "SERVICE_URL_{$serviceNameFormatted}%") - ->delete(); } } } diff --git a/app/Livewire/Project/Resource/Create.php b/app/Livewire/Project/Resource/Create.php index e7cff4f29..d8e397e59 100644 --- a/app/Livewire/Project/Resource/Create.php +++ b/app/Livewire/Project/Resource/Create.php @@ -102,7 +102,7 @@ class Create extends Component } }); } - $service->parse(isNew: true); + $service->parse(isNew: true, isOneClick: true); return redirect()->route('project.service.configuration', [ 'service_uuid' => $service->uuid, diff --git a/app/Models/Application.php b/app/Models/Application.php index 86eea1de8..ed97454d2 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -1353,7 +1353,7 @@ class Application extends BaseModel public function parse(int $pull_request_id = 0, ?int $preview_id = null) { if ((int) $this->compose_parsing_version >= 3) { - return newParser($this, $pull_request_id, $preview_id); + return applicationParser($this, $pull_request_id, $preview_id); } elseif ($this->docker_compose_raw) { return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id); } else { diff --git a/app/Models/Service.php b/app/Models/Service.php index da6c34fbb..4bef46642 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -1274,10 +1274,10 @@ class Service extends BaseModel instant_remote_process($commands, $this->server); } - public function parse(bool $isNew = false): Collection + public function parse(bool $isNew = false, bool $isOneClick = false): Collection { if ((int) $this->compose_parsing_version >= 3) { - return newParser($this); + return serviceParser($this, isOneClick: $isOneClick); } elseif ($this->docker_compose_raw) { return parseDockerComposeFile($this, $isNew); } else { diff --git a/bootstrap/helpers/parsers.php b/bootstrap/helpers/parsers.php new file mode 100644 index 000000000..649de731d --- /dev/null +++ b/bootstrap/helpers/parsers.php @@ -0,0 +1,1657 @@ +fileStorages(); + + try { + $yaml = Yaml::parse($compose); + } catch (\Exception) { + return collect([]); + } + $services = data_get($yaml, 'services', collect([])); + $topLevel = collect([ + 'volumes' => collect(data_get($yaml, 'volumes', [])), + 'networks' => collect(data_get($yaml, 'networks', [])), + 'configs' => collect(data_get($yaml, 'configs', [])), + 'secrets' => collect(data_get($yaml, 'secrets', [])), + ]); + // If there are predefined volumes, make sure they are not null + if ($topLevel->get('volumes')->count() > 0) { + $temp = collect([]); + foreach ($topLevel['volumes'] as $volumeName => $volume) { + if (is_null($volume)) { + continue; + } + $temp->put($volumeName, $volume); + } + $topLevel['volumes'] = $temp; + } + // Get the base docker network + $baseNetwork = collect([$uuid]); + if ($isPullRequest) { + $baseNetwork = collect(["{$uuid}-{$pullRequestId}"]); + } + + $parsedServices = collect([]); + + $allMagicEnvironments = collect([]); + foreach ($services as $serviceName => $service) { + $magicEnvironments = collect([]); + $image = data_get_str($service, 'image'); + $environment = collect(data_get($service, 'environment', [])); + $buildArgs = collect(data_get($service, 'build.args', [])); + $environment = $environment->merge($buildArgs); + + $environment = collect(data_get($service, 'environment', [])); + $buildArgs = collect(data_get($service, 'build.args', [])); + $environment = $environment->merge($buildArgs); + + // convert environment variables to one format + $environment = convertToKeyValueCollection($environment); + + // Add Coolify defined environments + $allEnvironments = $resource->environment_variables()->get(['key', 'value']); + + $allEnvironments = $allEnvironments->mapWithKeys(function ($item) { + return [$item['key'] => $item['value']]; + }); + // filter and add magic environments + foreach ($environment as $key => $value) { + // Get all SERVICE_ variables from keys and values + $key = str($key); + $value = str($value); + $regex = '/\$(\{?([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\}?)/'; + preg_match_all($regex, $value, $valueMatches); + if (count($valueMatches[1]) > 0) { + foreach ($valueMatches[1] as $match) { + $match = replaceVariables($match); + if ($match->startsWith('SERVICE_')) { + if ($magicEnvironments->has($match->value())) { + continue; + } + $magicEnvironments->put($match->value(), ''); + } + } + } + // Get magic environments where we need to preset the FQDN + if ($key->startsWith('SERVICE_FQDN_')) { + // SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000 + if (substr_count(str($key)->value(), '_') === 3) { + $fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value(); + $port = $key->afterLast('_')->value(); + } else { + $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + $port = null; + } + $fqdn = $resource->fqdn; + if (blank($resource->fqdn)) { + $fqdn = generateFqdn($server, "$uuid"); + } + + if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) { + $path = $value->value(); + if ($path !== '/') { + $fqdn = "$fqdn$path"; + } + } + $fqdnWithPort = $fqdn; + if ($port) { + $fqdnWithPort = "$fqdn:$port"; + } + if (is_null($resource->fqdn)) { + data_forget($resource, 'environment_variables'); + data_forget($resource, 'environment_variables_preview'); + $resource->fqdn = $fqdnWithPort; + $resource->save(); + } + + if (substr_count(str($key)->value(), '_') === 2) { + $resource->environment_variables()->updateOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + if (substr_count(str($key)->value(), '_') === 3) { + + $newKey = str($key)->beforeLast('_'); + $resource->environment_variables()->updateOrCreate([ + 'key' => $newKey->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } + } + + $allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments); + if ($magicEnvironments->count() > 0) { + foreach ($magicEnvironments as $key => $value) { + $key = str($key); + $value = replaceVariables($value); + $command = parseCommandFromMagicEnvVariable($key); + if ($command->value() === 'FQDN') { + if ($isOneClick) { + $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); + } + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } elseif ($command->value() === 'URL') { + if ($isOneClick) { + $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); + } + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } else { + $value = generateEnvValue($command, $resource); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } + } + } + + // Parse the rest of the services + foreach ($services as $serviceName => $service) { + $image = data_get_str($service, 'image'); + $restart = data_get_str($service, 'restart', RESTART_MODE); + $logging = data_get($service, 'logging'); + + if ($server->isLogDrainEnabled()) { + if ($resource->isLogDrainEnabled()) { + $logging = generate_fluentd_configuration(); + } + } + $volumes = collect(data_get($service, 'volumes', [])); + $networks = collect(data_get($service, 'networks', [])); + $use_network_mode = data_get($service, 'network_mode') !== null; + $depends_on = collect(data_get($service, 'depends_on', [])); + $labels = collect(data_get($service, 'labels', [])); + if ($labels->count() > 0) { + if (isAssociativeArray($labels)) { + $newLabels = collect([]); + $labels->each(function ($value, $key) use ($newLabels) { + $newLabels->push("$key=$value"); + }); + $labels = $newLabels; + } + } + $environment = collect(data_get($service, 'environment', [])); + $ports = collect(data_get($service, 'ports', [])); + $buildArgs = collect(data_get($service, 'build.args', [])); + $environment = $environment->merge($buildArgs); + + $environment = convertToKeyValueCollection($environment); + $coolifyEnvironments = collect([]); + + $isDatabase = isDatabaseImage($image, $service); + $volumesParsed = collect([]); + + $baseName = generateApplicationContainerName( + application: $resource, + pull_request_id: $pullRequestId + ); + $containerName = "$serviceName-$baseName"; + $predefinedPort = null; + + $originalResource = $resource; + + if ($volumes->count() > 0) { + foreach ($volumes as $index => $volume) { + $type = null; + $source = null; + $target = null; + $content = null; + $isDirectory = false; + if (is_string($volume)) { + $source = str($volume)->before(':'); + $target = str($volume)->after(':')->beforeLast(':'); + $foundConfig = $fileStorages->whereMountPath($target)->first(); + if (sourceIsLocal($source)) { + $type = str('bind'); + if ($foundConfig) { + $contentNotNull_temp = data_get($foundConfig, 'content'); + if ($contentNotNull_temp) { + $content = $contentNotNull_temp; + } + $isDirectory = data_get($foundConfig, 'is_directory'); + } else { + // By default, we cannot determine if the bind is a directory or not, so we set it to directory + $isDirectory = true; + } + } else { + $type = str('volume'); + } + } elseif (is_array($volume)) { + $type = data_get_str($volume, 'type'); + $source = data_get_str($volume, 'source'); + $target = data_get_str($volume, 'target'); + $content = data_get($volume, 'content'); + $isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null); + + $foundConfig = $fileStorages->whereMountPath($target)->first(); + if ($foundConfig) { + $contentNotNull_temp = data_get($foundConfig, 'content'); + if ($contentNotNull_temp) { + $content = $contentNotNull_temp; + } + $isDirectory = data_get($foundConfig, 'is_directory'); + } else { + // if isDirectory is not set (or false) & content is also not set, we assume it is a directory + if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) { + $isDirectory = true; + } + } + } + if ($type->value() === 'bind') { + if ($source->value() === '/var/run/docker.sock') { + $volume = $source->value().':'.$target->value(); + } elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') { + $volume = $source->value().':'.$target->value(); + } else { + if ((int) $resource->compose_parsing_version >= 4) { + $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid); + } else { + $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid); + } + $source = replaceLocalSource($source, $mainDirectory); + if ($isPullRequest) { + $source = $source."-pr-$pullRequestId"; + } + LocalFileVolume::updateOrCreate( + [ + 'mount_path' => $target, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ], + [ + 'fs_path' => $source, + 'mount_path' => $target, + 'content' => $content, + 'is_directory' => $isDirectory, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ] + ); + if (isDev()) { + if ((int) $resource->compose_parsing_version >= 4) { + $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid); + } else { + $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid); + } + } + $volume = "$source:$target"; + } + } elseif ($type->value() === 'volume') { + if ($topLevel->get('volumes')->has($source->value())) { + $temp = $topLevel->get('volumes')->get($source->value()); + if (data_get($temp, 'driver_opts.type') === 'cifs') { + continue; + } + if (data_get($temp, 'driver_opts.type') === 'nfs') { + continue; + } + } + $slugWithoutUuid = Str::slug($source, '-'); + $name = "{$uuid}_{$slugWithoutUuid}"; + + if ($isPullRequest) { + $name = "{$name}-pr-$pullRequestId"; + } + if (is_string($volume)) { + $source = str($volume)->before(':'); + $target = str($volume)->after(':')->beforeLast(':'); + $source = $name; + $volume = "$source:$target"; + } elseif (is_array($volume)) { + data_set($volume, 'source', $name); + } + $topLevel->get('volumes')->put($name, [ + 'name' => $name, + ]); + LocalPersistentVolume::updateOrCreate( + [ + 'name' => $name, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ], + [ + 'name' => $name, + 'mount_path' => $target, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ] + ); + } + dispatch(new ServerFilesFromServerJob($originalResource)); + $volumesParsed->put($index, $volume); + } + } + + if ($depends_on?->count() > 0) { + if ($isPullRequest) { + $newDependsOn = collect([]); + $depends_on->each(function ($dependency, $condition) use ($pullRequestId, $newDependsOn) { + if (is_numeric($condition)) { + $dependency = "$dependency-pr-$pullRequestId"; + + $newDependsOn->put($condition, $dependency); + } else { + $condition = "$condition-pr-$pullRequestId"; + $newDependsOn->put($condition, $dependency); + } + }); + $depends_on = $newDependsOn; + } + } + if (! $use_network_mode) { + if ($topLevel->get('networks')?->count() > 0) { + foreach ($topLevel->get('networks') as $networkName => $network) { + if ($networkName === 'default') { + continue; + } + // ignore aliases + if ($network['aliases'] ?? false) { + continue; + } + $networkExists = $networks->contains(function ($value, $key) use ($networkName) { + return $value == $networkName || $key == $networkName; + }); + if (! $networkExists) { + $networks->put($networkName, null); + } + } + } + $baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) { + return $value == $baseNetwork; + }); + if (! $baseNetworkExists) { + foreach ($baseNetwork as $network) { + $topLevel->get('networks')->put($network, [ + 'name' => $network, + 'external' => true, + ]); + } + } + } + + // Collect/create/update ports + $collectedPorts = collect([]); + if ($ports->count() > 0) { + foreach ($ports as $sport) { + if (is_string($sport) || is_numeric($sport)) { + $collectedPorts->push($sport); + } + if (is_array($sport)) { + $target = data_get($sport, 'target'); + $published = data_get($sport, 'published'); + $protocol = data_get($sport, 'protocol'); + $collectedPorts->push("$target:$published/$protocol"); + } + } + } + + $networks_temp = collect(); + + if (! $use_network_mode) { + foreach ($networks as $key => $network) { + if (gettype($network) === 'string') { + // networks: + // - appwrite + $networks_temp->put($network, null); + } elseif (gettype($network) === 'array') { + // networks: + // default: + // ipv4_address: 192.168.203.254 + $networks_temp->put($key, $network); + } + } + foreach ($baseNetwork as $key => $network) { + $networks_temp->put($network, null); + } + + if (data_get($resource, 'settings.connect_to_docker_network')) { + $network = $resource->destination->network; + $networks_temp->put($network, null); + $topLevel->get('networks')->put($network, [ + 'name' => $network, + 'external' => true, + ]); + } + } + + $normalEnvironments = $environment->diffKeys($allMagicEnvironments); + $normalEnvironments = $normalEnvironments->filter(function ($value, $key) { + return ! str($value)->startsWith('SERVICE_'); + }); + foreach ($normalEnvironments as $key => $value) { + $key = str($key); + $value = str($value); + $originalValue = $value; + $parsedValue = replaceVariables($value); + if ($value->startsWith('$SERVICE_')) { + $resource->environment_variables()->firstOrCreate([ + 'key' => $key, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + ]); + + continue; + } + if (! $value->startsWith('$')) { + continue; + } + if ($key->value() === $parsedValue->value()) { + $value = null; + $resource->environment_variables()->firstOrCreate([ + 'key' => $key, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } else { + if ($value->startsWith('$')) { + $isRequired = false; + if ($value->contains(':-')) { + $value = replaceVariables($value); + $key = $value->before(':'); + $value = $value->after(':-'); + } elseif ($value->contains('-')) { + $value = replaceVariables($value); + + $key = $value->before('-'); + $value = $value->after('-'); + } elseif ($value->contains(':?')) { + $value = replaceVariables($value); + + $key = $value->before(':'); + $value = $value->after(':?'); + $isRequired = true; + } elseif ($value->contains('?')) { + $value = replaceVariables($value); + + $key = $value->before('?'); + $value = $value->after('?'); + $isRequired = true; + } + if ($originalValue->value() === $value->value()) { + // This means the variable does not have a default value, so it needs to be created in Coolify + $parsedKeyValue = replaceVariables($value); + $resource->environment_variables()->firstOrCreate([ + 'key' => $parsedKeyValue, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'is_build_time' => false, + 'is_preview' => false, + 'is_required' => $isRequired, + ]); + // Add the variable to the environment so it will be shown in the deployable compose file + $environment[$parsedKeyValue->value()] = $value; + + continue; + } + $resource->environment_variables()->firstOrCreate([ + 'key' => $key, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + 'is_required' => $isRequired, + ]); + } + } + } + $branch = $originalResource->git_branch; + if ($pullRequestId !== 0) { + $branch = "pull/{$pullRequestId}/head"; + } + if ($originalResource->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) { + $coolifyEnvironments->put('COOLIFY_BRANCH', "\"{$branch}\""); + } + + // Add COOLIFY_RESOURCE_UUID to environment + if ($resource->environment_variables->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) { + $coolifyEnvironments->put('COOLIFY_RESOURCE_UUID', "{$resource->uuid}"); + } + + // Add COOLIFY_CONTAINER_NAME to environment + if ($resource->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { + $coolifyEnvironments->put('COOLIFY_CONTAINER_NAME', "{$containerName}"); + } + + if ($isPullRequest) { + $preview = $resource->previews()->find($preview_id); + $domains = collect(json_decode(data_get($preview, 'docker_compose_domains'))) ?? collect([]); + } else { + $domains = collect(json_decode($resource->docker_compose_domains)) ?? collect([]); + } + ray($domains); + $fqdns = data_get($domains, "$serviceName.domain"); + // Generate SERVICE_FQDN & SERVICE_URL for dockercompose + if ($resource->build_pack === 'dockercompose') { + foreach ($domains as $forServiceName => $domain) { + $parsedDomain = data_get($domain, 'domain'); + $serviceNameFormatted = str($serviceName)->upper()->replace('-', '_'); + + if (filled($parsedDomain)) { + $parsedDomain = str($parsedDomain)->explode(',')->first(); + $coolifyUrl = Url::fromString($parsedDomain); + $coolifyScheme = $coolifyUrl->getScheme(); + $coolifyFqdn = $coolifyUrl->getHost(); + $coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null); + $coolifyEnvironments->put('SERVICE_URL_'.str($forServiceName)->upper()->replace('-', '_'), $coolifyUrl->__toString()); + $coolifyEnvironments->put('SERVICE_FQDN_'.str($forServiceName)->upper()->replace('-', '_'), $coolifyFqdn); + $resource->environment_variables()->updateOrCreate([ + 'resourceable_type' => Application::class, + 'resourceable_id' => $resource->id, + 'key' => 'SERVICE_URL_'.str($forServiceName)->upper()->replace('-', '_'), + ], [ + 'value' => $coolifyUrl->__toString(), + 'is_build_time' => false, + 'is_preview' => false, + ]); + $resource->environment_variables()->updateOrCreate([ + 'resourceable_type' => Application::class, + 'resourceable_id' => $resource->id, + 'key' => 'SERVICE_FQDN_'.str($forServiceName)->upper()->replace('-', '_'), + ], [ + 'value' => $coolifyFqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } else { + $resource->environment_variables()->where('resourceable_type', Application::class) + ->where('resourceable_id', $resource->id) + ->where('key', 'LIKE', "SERVICE_FQDN_{$serviceNameFormatted}%") + ->delete(); + $resource->environment_variables()->where('resourceable_type', Application::class) + ->where('resourceable_id', $resource->id) + ->where('key', 'LIKE', "SERVICE_URL_{$serviceNameFormatted}%") + ->delete(); + } + } + } + // If the domain is set, we need to generate the FQDNs for the preview + if (filled($fqdns)) { + $fqdns = str($fqdns)->explode(','); + if ($isPullRequest) { + $preview = $resource->previews()->find($preview_id); + $docker_compose_domains = collect(json_decode(data_get($preview, 'docker_compose_domains'))); + if ($docker_compose_domains->count() > 0) { + $found_fqdn = data_get($docker_compose_domains, "$serviceName.domain"); + if ($found_fqdn) { + $fqdns = collect($found_fqdn); + } else { + $fqdns = collect([]); + } + } else { + $fqdns = $fqdns->map(function ($fqdn) use ($pullRequestId, $resource) { + $preview = ApplicationPreview::findPreviewByApplicationAndPullId($resource->id, $pullRequestId); + $url = Url::fromString($fqdn); + $template = $resource->preview_url_template; + $host = $url->getHost(); + $schema = $url->getScheme(); + $random = new Cuid2; + $preview_fqdn = str_replace('{{random}}', $random, $template); + $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn); + $preview_fqdn = "$schema://$preview_fqdn"; + $preview->fqdn = $preview_fqdn; + $preview->save(); + + return $preview_fqdn; + }); + } + } + } + $defaultLabels = defaultLabels( + id: $resource->id, + name: $containerName, + projectName: $resource->project()->name, + resourceName: $resource->name, + pull_request_id: $pullRequestId, + type: 'application', + environment: $resource->environment->name, + ); + + $isDatabase = isDatabaseImage($image, $service); + // Add COOLIFY_FQDN & COOLIFY_URL to environment + if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { + $fqdnsWithoutPort = $fqdns->map(function ($fqdn) { + return str($fqdn)->after('://')->before(':')->prepend(str($fqdn)->before('://')->append('://')); + }); + $coolifyEnvironments->put('COOLIFY_URL', $fqdnsWithoutPort->implode(',')); + + $urls = $fqdns->map(function ($fqdn) { + return str($fqdn)->replace('http://', '')->replace('https://', '')->before(':'); + }); + $coolifyEnvironments->put('COOLIFY_FQDN', $urls->implode(',')); + } + add_coolify_default_environment_variables($resource, $coolifyEnvironments, $resource->environment_variables); + if ($environment->count() > 0) { + $environment = $environment->filter(function ($value, $key) { + return ! str($key)->startsWith('SERVICE_FQDN_'); + })->map(function ($value, $key) use ($resource) { + // if value is empty, set it to null so if you set the environment variable in the .env file (Coolify's UI), it will used + if (str($value)->isEmpty()) { + if ($resource->environment_variables()->where('key', $key)->exists()) { + $value = $resource->environment_variables()->where('key', $key)->first()->value; + } else { + $value = null; + } + } + + return $value; + }); + } + $serviceLabels = $labels->merge($defaultLabels); + if ($serviceLabels->count() > 0) { + $isContainerLabelEscapeEnabled = data_get($resource, 'settings.is_container_label_escape_enabled'); + if ($isContainerLabelEscapeEnabled) { + $serviceLabels = $serviceLabels->map(function ($value, $key) { + return escapeDollarSign($value); + }); + } + } + if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { + $shouldGenerateLabelsExactly = $resource->destination->server->settings->generate_exact_labels; + $uuid = $resource->uuid; + $network = data_get($resource, 'destination.network'); + if ($isPullRequest) { + $uuid = "{$resource->uuid}-{$pullRequestId}"; + } + if ($isPullRequest) { + $network = "{$resource->destination->network}-{$pullRequestId}"; + } + if ($shouldGenerateLabelsExactly) { + switch ($server->proxyType()) { + case ProxyTypes::TRAEFIK->value: + $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image + )); + break; + case ProxyTypes::CADDY->value: + $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( + network: $network, + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image, + predefinedPort: $predefinedPort + )); + break; + } + } else { + $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image + )); + $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( + network: $network, + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image, + predefinedPort: $predefinedPort + )); + } + } + data_forget($service, 'volumes.*.content'); + data_forget($service, 'volumes.*.isDirectory'); + data_forget($service, 'volumes.*.is_directory'); + data_forget($service, 'exclude_from_hc'); + + $volumesParsed = $volumesParsed->map(function ($volume) { + data_forget($volume, 'content'); + data_forget($volume, 'is_directory'); + data_forget($volume, 'isDirectory'); + + return $volume; + }); + + $payload = collect($service)->merge([ + 'container_name' => $containerName, + 'restart' => $restart->value(), + 'labels' => $serviceLabels, + ]); + if (! $use_network_mode) { + $payload['networks'] = $networks_temp; + } + if ($ports->count() > 0) { + $payload['ports'] = $ports; + } + if ($volumesParsed->count() > 0) { + $payload['volumes'] = $volumesParsed; + } + if ($environment->count() > 0 || $coolifyEnvironments->count() > 0) { + $payload['environment'] = $environment->merge($coolifyEnvironments); + } + if ($logging) { + $payload['logging'] = $logging; + } + if ($depends_on->count() > 0) { + $payload['depends_on'] = $depends_on; + } + if ($isPullRequest) { + $serviceName = "{$serviceName}-pr-{$pullRequestId}"; + } + + $parsedServices->put($serviceName, $payload); + } + $topLevel->put('services', $parsedServices); + + $customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets']; + + $topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) { + return array_search($key, $customOrder); + }); + + $resource->docker_compose = Yaml::dump(convertToArray($topLevel), 10, 2); + data_forget($resource, 'environment_variables'); + data_forget($resource, 'environment_variables_preview'); + $resource->save(); + + return $topLevel; +} + +function serviceParser(Service $resource, bool $isOneClick = false): Collection +{ + $uuid = data_get($resource, 'uuid'); + $compose = data_get($resource, 'docker_compose_raw'); + if (! $compose) { + return collect([]); + } + + $server = data_get($resource, 'server'); + $allServices = get_service_templates(); + + try { + $yaml = Yaml::parse($compose); + } catch (\Exception) { + return collect([]); + } + $services = data_get($yaml, 'services', collect([])); + $topLevel = collect([ + 'volumes' => collect(data_get($yaml, 'volumes', [])), + 'networks' => collect(data_get($yaml, 'networks', [])), + 'configs' => collect(data_get($yaml, 'configs', [])), + 'secrets' => collect(data_get($yaml, 'secrets', [])), + ]); + // If there are predefined volumes, make sure they are not null + if ($topLevel->get('volumes')->count() > 0) { + $temp = collect([]); + foreach ($topLevel['volumes'] as $volumeName => $volume) { + if (is_null($volume)) { + continue; + } + $temp->put($volumeName, $volume); + } + $topLevel['volumes'] = $temp; + } + // Get the base docker network + $baseNetwork = collect([$uuid]); + + $parsedServices = collect([]); + + $allMagicEnvironments = collect([]); + foreach ($services as $serviceName => $service) { + $predefinedPort = null; + $magicEnvironments = collect([]); + $image = data_get_str($service, 'image'); + $environment = collect(data_get($service, 'environment', [])); + $buildArgs = collect(data_get($service, 'build.args', [])); + $environment = $environment->merge($buildArgs); + $isDatabase = isDatabaseImage($image, $service); + + $containerName = "$serviceName-{$resource->uuid}"; + + if ($serviceName === 'registry') { + $tempServiceName = 'docker-registry'; + } else { + $tempServiceName = $serviceName; + } + if (str(data_get($service, 'image'))->contains('glitchtip')) { + $tempServiceName = 'glitchtip'; + } + if ($serviceName === 'supabase-kong') { + $tempServiceName = 'supabase'; + } + $serviceDefinition = data_get($allServices, $tempServiceName); + $predefinedPort = data_get($serviceDefinition, 'port'); + if ($serviceName === 'plausible') { + $predefinedPort = '8000'; + } + if ($isDatabase) { + $applicationFound = ServiceApplication::where('name', $serviceName)->where('service_id', $resource->id)->first(); + if ($applicationFound) { + $savedService = $applicationFound; + } else { + $savedService = ServiceDatabase::firstOrCreate([ + 'name' => $serviceName, + 'service_id' => $resource->id, + ]); + } + } else { + $savedService = ServiceApplication::firstOrCreate([ + 'name' => $serviceName, + 'service_id' => $resource->id, + ], [ + 'is_gzip_enabled' => true, + ]); + } + // Check if image changed + if ($savedService->image !== $image) { + $savedService->image = $image; + $savedService->save(); + } + // Pocketbase does not need gzip for SSE. + if (str($savedService->image)->contains('pocketbase') && $savedService->is_gzip_enabled) { + $savedService->is_gzip_enabled = false; + $savedService->save(); + } + + $environment = collect(data_get($service, 'environment', [])); + $buildArgs = collect(data_get($service, 'build.args', [])); + $environment = $environment->merge($buildArgs); + + // convert environment variables to one format + $environment = convertToKeyValueCollection($environment); + + // Add Coolify defined environments + $allEnvironments = $resource->environment_variables()->get(['key', 'value']); + + $allEnvironments = $allEnvironments->mapWithKeys(function ($item) { + return [$item['key'] => $item['value']]; + }); + // filter and add magic environments + foreach ($environment as $key => $value) { + // Get all SERVICE_ variables from keys and values + $key = str($key); + $value = str($value); + $regex = '/\$(\{?([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\}?)/'; + preg_match_all($regex, $value, $valueMatches); + if (count($valueMatches[1]) > 0) { + foreach ($valueMatches[1] as $match) { + $match = replaceVariables($match); + if ($match->startsWith('SERVICE_')) { + if ($magicEnvironments->has($match->value())) { + continue; + } + $magicEnvironments->put($match->value(), ''); + } + } + } + // Get magic environments where we need to preset the FQDN + if ($key->startsWith('SERVICE_FQDN_')) { + // SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000 + if (substr_count(str($key)->value(), '_') === 3) { + $fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value(); + $port = $key->afterLast('_')->value(); + } else { + $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + $port = null; + } + if ($isOneClick) { + if (blank($savedService->fqdn)) { + if ($fqdnFor) { + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + } else { + $fqdn = generateFqdn($server, "{$savedService->name}-$uuid"); + } + } else { + $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); + } + + } else { + // For services which are not one-click, if no explicit FQDN is set, leave SERVICE_FQDN_ variables empty + if (blank($savedService->fqdn)) { + $fqdn = ''; + } else { + $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); + } + } + if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) { + $path = $value->value(); + if ($path !== '/') { + $fqdn = "$fqdn$path"; + } + } + $fqdnWithPort = $fqdn; + if ($port) { + $fqdnWithPort = "$fqdn:$port"; + } + if (is_null($savedService->fqdn)) { + $savedService->fqdn = $fqdnWithPort; + $savedService->save(); + } + + if (substr_count(str($key)->value(), '_') === 2) { + $resource->environment_variables()->updateOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + if (substr_count(str($key)->value(), '_') === 3) { + $newKey = str($key)->beforeLast('_'); + $resource->environment_variables()->updateOrCreate([ + 'key' => $newKey->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } + } + + $allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments); + if ($magicEnvironments->count() > 0) { + foreach ($magicEnvironments as $key => $value) { + $key = str($key); + $value = replaceVariables($value); + $command = parseCommandFromMagicEnvVariable($key); + if ($command->value() === 'FQDN') { + if ($isOneClick) { + $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); + } + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } elseif ($command->value() === 'URL') { + if ($isOneClick) { + $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); + } + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } else { + $value = generateEnvValue($command, $resource); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } + } + } + + $serviceAppsLogDrainEnabledMap = $resource->applications()->get()->keyBy('name')->map(function ($app) { + return $app->isLogDrainEnabled(); + }); + + // Parse the rest of the services + foreach ($services as $serviceName => $service) { + $image = data_get_str($service, 'image'); + $restart = data_get_str($service, 'restart', RESTART_MODE); + $logging = data_get($service, 'logging'); + + if ($server->isLogDrainEnabled()) { + if ($serviceAppsLogDrainEnabledMap->get($serviceName)) { + $logging = generate_fluentd_configuration(); + } + } + $volumes = collect(data_get($service, 'volumes', [])); + $networks = collect(data_get($service, 'networks', [])); + $use_network_mode = data_get($service, 'network_mode') !== null; + $depends_on = collect(data_get($service, 'depends_on', [])); + $labels = collect(data_get($service, 'labels', [])); + if ($labels->count() > 0) { + if (isAssociativeArray($labels)) { + $newLabels = collect([]); + $labels->each(function ($value, $key) use ($newLabels) { + $newLabels->push("$key=$value"); + }); + $labels = $newLabels; + } + } + $environment = collect(data_get($service, 'environment', [])); + $ports = collect(data_get($service, 'ports', [])); + $buildArgs = collect(data_get($service, 'build.args', [])); + $environment = $environment->merge($buildArgs); + + $environment = convertToKeyValueCollection($environment); + $coolifyEnvironments = collect([]); + + $isDatabase = isDatabaseImage($image, $service); + $volumesParsed = collect([]); + + $containerName = "$serviceName-{$resource->uuid}"; + + if ($serviceName === 'registry') { + $tempServiceName = 'docker-registry'; + } else { + $tempServiceName = $serviceName; + } + if (str(data_get($service, 'image'))->contains('glitchtip')) { + $tempServiceName = 'glitchtip'; + } + if ($serviceName === 'supabase-kong') { + $tempServiceName = 'supabase'; + } + $serviceDefinition = data_get($allServices, $tempServiceName); + $predefinedPort = data_get($serviceDefinition, 'port'); + if ($serviceName === 'plausible') { + $predefinedPort = '8000'; + } + + if ($isDatabase) { + $applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first(); + if ($applicationFound) { + $savedService = $applicationFound; + } else { + $savedService = ServiceDatabase::firstOrCreate([ + 'name' => $serviceName, + 'image' => $image, + 'service_id' => $resource->id, + ]); + } + } else { + $savedService = ServiceApplication::firstOrCreate([ + 'name' => $serviceName, + 'image' => $image, + 'service_id' => $resource->id, + ]); + } + $fileStorages = $savedService->fileStorages(); + if ($savedService->image !== $image) { + $savedService->image = $image; + $savedService->save(); + } + + $originalResource = $savedService; + + if ($volumes->count() > 0) { + foreach ($volumes as $index => $volume) { + $type = null; + $source = null; + $target = null; + $content = null; + $isDirectory = false; + if (is_string($volume)) { + $source = str($volume)->before(':'); + $target = str($volume)->after(':')->beforeLast(':'); + $foundConfig = $fileStorages->whereMountPath($target)->first(); + if (sourceIsLocal($source)) { + $type = str('bind'); + if ($foundConfig) { + $contentNotNull_temp = data_get($foundConfig, 'content'); + if ($contentNotNull_temp) { + $content = $contentNotNull_temp; + } + $isDirectory = data_get($foundConfig, 'is_directory'); + } else { + // By default, we cannot determine if the bind is a directory or not, so we set it to directory + $isDirectory = true; + } + } else { + $type = str('volume'); + } + } elseif (is_array($volume)) { + $type = data_get_str($volume, 'type'); + $source = data_get_str($volume, 'source'); + $target = data_get_str($volume, 'target'); + $content = data_get($volume, 'content'); + $isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null); + + $foundConfig = $fileStorages->whereMountPath($target)->first(); + if ($foundConfig) { + $contentNotNull_temp = data_get($foundConfig, 'content'); + if ($contentNotNull_temp) { + $content = $contentNotNull_temp; + } + $isDirectory = data_get($foundConfig, 'is_directory'); + } else { + // if isDirectory is not set (or false) & content is also not set, we assume it is a directory + if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) { + $isDirectory = true; + } + } + } + if ($type->value() === 'bind') { + if ($source->value() === '/var/run/docker.sock') { + $volume = $source->value().':'.$target->value(); + } elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') { + $volume = $source->value().':'.$target->value(); + } else { + if ((int) $resource->compose_parsing_version >= 4) { + $mainDirectory = str(base_configuration_dir().'/services/'.$uuid); + } else { + $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid); + } + $source = replaceLocalSource($source, $mainDirectory); + LocalFileVolume::updateOrCreate( + [ + 'mount_path' => $target, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ], + [ + 'fs_path' => $source, + 'mount_path' => $target, + 'content' => $content, + 'is_directory' => $isDirectory, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ] + ); + if (isDev()) { + if ((int) $resource->compose_parsing_version >= 4) { + $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/services/'.$uuid); + } else { + $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid); + } + } + $volume = "$source:$target"; + } + } elseif ($type->value() === 'volume') { + if ($topLevel->get('volumes')->has($source->value())) { + $temp = $topLevel->get('volumes')->get($source->value()); + if (data_get($temp, 'driver_opts.type') === 'cifs') { + continue; + } + if (data_get($temp, 'driver_opts.type') === 'nfs') { + continue; + } + } + $slugWithoutUuid = Str::slug($source, '-'); + $name = "{$uuid}_{$slugWithoutUuid}"; + + if (is_string($volume)) { + $source = str($volume)->before(':'); + $target = str($volume)->after(':')->beforeLast(':'); + $source = $name; + $volume = "$source:$target"; + } elseif (is_array($volume)) { + data_set($volume, 'source', $name); + } + $topLevel->get('volumes')->put($name, [ + 'name' => $name, + ]); + LocalPersistentVolume::updateOrCreate( + [ + 'name' => $name, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ], + [ + 'name' => $name, + 'mount_path' => $target, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ] + ); + } + dispatch(new ServerFilesFromServerJob($originalResource)); + $volumesParsed->put($index, $volume); + } + } + + if (! $use_network_mode) { + if ($topLevel->get('networks')?->count() > 0) { + foreach ($topLevel->get('networks') as $networkName => $network) { + if ($networkName === 'default') { + continue; + } + // ignore aliases + if ($network['aliases'] ?? false) { + continue; + } + $networkExists = $networks->contains(function ($value, $key) use ($networkName) { + return $value == $networkName || $key == $networkName; + }); + if (! $networkExists) { + $networks->put($networkName, null); + } + } + } + $baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) { + return $value == $baseNetwork; + }); + if (! $baseNetworkExists) { + foreach ($baseNetwork as $network) { + $topLevel->get('networks')->put($network, [ + 'name' => $network, + 'external' => true, + ]); + } + } + } + + // Collect/create/update ports + $collectedPorts = collect([]); + if ($ports->count() > 0) { + foreach ($ports as $sport) { + if (is_string($sport) || is_numeric($sport)) { + $collectedPorts->push($sport); + } + if (is_array($sport)) { + $target = data_get($sport, 'target'); + $published = data_get($sport, 'published'); + $protocol = data_get($sport, 'protocol'); + $collectedPorts->push("$target:$published/$protocol"); + } + } + } + $originalResource->ports = $collectedPorts->implode(','); + $originalResource->save(); + + $networks_temp = collect(); + + if (! $use_network_mode) { + foreach ($networks as $key => $network) { + if (gettype($network) === 'string') { + // networks: + // - appwrite + $networks_temp->put($network, null); + } elseif (gettype($network) === 'array') { + // networks: + // default: + // ipv4_address: 192.168.203.254 + $networks_temp->put($key, $network); + } + } + foreach ($baseNetwork as $key => $network) { + $networks_temp->put($network, null); + } + } + + $normalEnvironments = $environment->diffKeys($allMagicEnvironments); + $normalEnvironments = $normalEnvironments->filter(function ($value, $key) { + return ! str($value)->startsWith('SERVICE_'); + }); + foreach ($normalEnvironments as $key => $value) { + $key = str($key); + $value = str($value); + $originalValue = $value; + $parsedValue = replaceVariables($value); + if ($value->startsWith('$SERVICE_')) { + $resource->environment_variables()->firstOrCreate([ + 'key' => $key, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + ]); + + continue; + } + if (! $value->startsWith('$')) { + continue; + } + if ($key->value() === $parsedValue->value()) { + $value = null; + $resource->environment_variables()->firstOrCreate([ + 'key' => $key, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } else { + if ($value->startsWith('$')) { + $isRequired = false; + if ($value->contains(':-')) { + $value = replaceVariables($value); + $key = $value->before(':'); + $value = $value->after(':-'); + } elseif ($value->contains('-')) { + $value = replaceVariables($value); + + $key = $value->before('-'); + $value = $value->after('-'); + } elseif ($value->contains(':?')) { + $value = replaceVariables($value); + + $key = $value->before(':'); + $value = $value->after(':?'); + $isRequired = true; + } elseif ($value->contains('?')) { + $value = replaceVariables($value); + + $key = $value->before('?'); + $value = $value->after('?'); + $isRequired = true; + } + if ($originalValue->value() === $value->value()) { + // This means the variable does not have a default value, so it needs to be created in Coolify + $parsedKeyValue = replaceVariables($value); + $resource->environment_variables()->firstOrCreate([ + 'key' => $parsedKeyValue, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'is_build_time' => false, + 'is_preview' => false, + 'is_required' => $isRequired, + ]); + // Add the variable to the environment so it will be shown in the deployable compose file + $environment[$parsedKeyValue->value()] = $value; + + continue; + } + $resource->environment_variables()->firstOrCreate([ + 'key' => $key, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + 'is_required' => $isRequired, + ]); + } + } + } + + // Add COOLIFY_RESOURCE_UUID to environment + if ($resource->environment_variables->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) { + $coolifyEnvironments->put('COOLIFY_RESOURCE_UUID', "{$resource->uuid}"); + } + + // Add COOLIFY_CONTAINER_NAME to environment + if ($resource->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { + $coolifyEnvironments->put('COOLIFY_CONTAINER_NAME', "{$containerName}"); + } + + if ($savedService->serviceType()) { + $fqdns = generateServiceSpecificFqdns($savedService); + } else { + $fqdns = collect(data_get($savedService, 'fqdns'))->filter(); + } + + $defaultLabels = defaultLabels( + id: $resource->id, + name: $containerName, + projectName: $resource->project()->name, + resourceName: $resource->name, + type: 'service', + subType: $isDatabase ? 'database' : 'application', + subId: $savedService->id, + subName: $savedService->human_name ?? $savedService->name, + environment: $resource->environment->name, + ); + + // Add COOLIFY_FQDN & COOLIFY_URL to environment + if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { + $fqdnsWithoutPort = $fqdns->map(function ($fqdn) { + return str($fqdn)->replace('http://', '')->replace('https://', '')->before(':'); + }); + $coolifyEnvironments->put('COOLIFY_FQDN', $fqdnsWithoutPort->implode(',')); + $urls = $fqdns->map(function ($fqdn) { + return str($fqdn)->after('://')->before(':')->prepend(str($fqdn)->before('://')->append('://')); + }); + $coolifyEnvironments->put('COOLIFY_URL', $urls->implode(',')); + } + add_coolify_default_environment_variables($resource, $coolifyEnvironments, $resource->environment_variables); + if ($environment->count() > 0) { + $environment = $environment->filter(function ($value, $key) { + return ! str($key)->startsWith('SERVICE_FQDN_'); + })->map(function ($value, $key) use ($resource) { + // if value is empty, set it to null so if you set the environment variable in the .env file (Coolify's UI), it will used + if (str($value)->isEmpty()) { + if ($resource->environment_variables()->where('key', $key)->exists()) { + $value = $resource->environment_variables()->where('key', $key)->first()->value; + } else { + $value = null; + } + } + + return $value; + }); + } + $serviceLabels = $labels->merge($defaultLabels); + if ($serviceLabels->count() > 0) { + $isContainerLabelEscapeEnabled = data_get($resource, 'is_container_label_escape_enabled'); + if ($isContainerLabelEscapeEnabled) { + $serviceLabels = $serviceLabels->map(function ($value, $key) { + return escapeDollarSign($value); + }); + } + } + if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { + $shouldGenerateLabelsExactly = $resource->server->settings->generate_exact_labels; + $uuid = $resource->uuid; + $network = data_get($resource, 'destination.network'); + if ($shouldGenerateLabelsExactly) { + switch ($server->proxyType()) { + case ProxyTypes::TRAEFIK->value: + $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image + )); + break; + case ProxyTypes::CADDY->value: + $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( + network: $network, + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image, + predefinedPort: $predefinedPort + )); + break; + } + } else { + $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image + )); + $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( + network: $network, + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image, + predefinedPort: $predefinedPort + )); + } + } + if (data_get($service, 'restart') === 'no' || data_get($service, 'exclude_from_hc')) { + $savedService->update(['exclude_from_status' => true]); + } + data_forget($service, 'volumes.*.content'); + data_forget($service, 'volumes.*.isDirectory'); + data_forget($service, 'volumes.*.is_directory'); + data_forget($service, 'exclude_from_hc'); + + $volumesParsed = $volumesParsed->map(function ($volume) { + data_forget($volume, 'content'); + data_forget($volume, 'is_directory'); + data_forget($volume, 'isDirectory'); + + return $volume; + }); + + $payload = collect($service)->merge([ + 'container_name' => $containerName, + 'restart' => $restart->value(), + 'labels' => $serviceLabels, + ]); + if (! $use_network_mode) { + $payload['networks'] = $networks_temp; + } + if ($ports->count() > 0) { + $payload['ports'] = $ports; + } + if ($volumesParsed->count() > 0) { + $payload['volumes'] = $volumesParsed; + } + if ($environment->count() > 0 || $coolifyEnvironments->count() > 0) { + $payload['environment'] = $environment->merge($coolifyEnvironments); + } + if ($logging) { + $payload['logging'] = $logging; + } + if ($depends_on->count() > 0) { + $payload['depends_on'] = $depends_on; + } + + $parsedServices->put($serviceName, $payload); + } + $topLevel->put('services', $parsedServices); + + $customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets']; + + $topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) { + return array_search($key, $customOrder); + }); + + $resource->docker_compose = Yaml::dump(convertToArray($topLevel), 10, 2); + data_forget($resource, 'environment_variables'); + data_forget($resource, 'environment_variables_preview'); + $resource->save(); + + return $topLevel; +} diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php index 1e1d2a073..cf12a28a5 100644 --- a/bootstrap/helpers/services.php +++ b/bootstrap/helpers/services.php @@ -1,7 +1,6 @@ image = $updatedImage; $resource->save(); } + + $serviceName = str($resource->name)->upper()->replace('-', '_'); + $resource->service->environment_variables()->where('key', 'LIKE', "SERVICE_FQDN_{$serviceName}%")->delete(); + $resource->service->environment_variables()->where('key', 'LIKE', "SERVICE_URL_{$serviceName}%")->delete(); + if ($resource->fqdn) { $resourceFqdns = str($resource->fqdn)->explode(','); - if ($resourceFqdns->count() === 1) { - $resourceFqdns = $resourceFqdns->first(); - $variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', '_'); - $fqdn = Url::fromString($resourceFqdns); - $port = $fqdn->getPort(); - $path = $fqdn->getPath(); - $fqdn = $fqdn->getScheme().'://'.$fqdn->getHost(); - $fqdnValue = ($path === '/') ? $fqdn : $fqdn.$path; - EnvironmentVariable::updateOrCreate([ - 'resourceable_type' => Service::class, - 'resourceable_id' => $resource->service_id, - 'key' => $variableName, - ], [ - 'value' => $fqdnValue, - 'is_build_time' => false, - 'is_preview' => false, - ]); - if ($port) { - $variableName = $variableName."_$port"; - EnvironmentVariable::updateOrCreate([ - 'resourceable_type' => Service::class, - 'resourceable_id' => $resource->service_id, - 'key' => $variableName, - ], [ - 'value' => $fqdnValue, - 'is_build_time' => false, - 'is_preview' => false, - ]); - } - $variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', '_'); - $url = Url::fromString($fqdn); - $port = $url->getPort(); - $path = $url->getPath(); - $url = $url->getHost(); - $urlValue = str($fqdn)->after('://'); - if ($path !== '/') { - $urlValue = $urlValue.$path; - } - EnvironmentVariable::updateOrCreate([ + $resourceFqdns = $resourceFqdns->first(); + $variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', '_'); + $url = Url::fromString($resourceFqdns); + $port = $url->getPort(); + $path = $url->getPath(); + $urlValue = $url->getScheme().'://'.$url->getHost(); + $urlValue = ($path === '/') ? $urlValue : $urlValue.$path; + $resource->service->environment_variables()->updateOrCreate([ + 'resourceable_type' => Service::class, + 'resourceable_id' => $resource->service_id, + 'key' => $variableName, + ], [ + 'value' => $urlValue, + 'is_build_time' => false, + 'is_preview' => false, + ]); + if ($port) { + $variableName = $variableName."_$port"; + $resource->service->environment_variables()->updateOrCreate([ 'resourceable_type' => Service::class, 'resourceable_id' => $resource->service_id, 'key' => $variableName, @@ -164,114 +148,37 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) 'is_build_time' => false, 'is_preview' => false, ]); - if ($port) { - $variableName = $variableName."_$port"; - EnvironmentVariable::updateOrCreate([ - 'resourceable_type' => Service::class, - 'resourceable_id' => $resource->service_id, - 'key' => $variableName, - ], [ - 'value' => $urlValue, - 'is_build_time' => false, - 'is_preview' => false, - ]); - } - } elseif ($resourceFqdns->count() > 1) { - foreach ($resourceFqdns as $fqdn) { - $host = Url::fromString($fqdn); - $port = $host->getPort(); - $url = $host->getHost(); - $path = $host->getPath(); - $host = $host->getScheme().'://'.$host->getHost(); - if ($port) { - $port_envs = EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', 'like', "SERVICE_FQDN_%_$port") - ->get(); - foreach ($port_envs as $port_env) { - $service_fqdn = str($port_env->key)->beforeLast('_')->after('SERVICE_FQDN_'); - $env = EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', 'SERVICE_FQDN_'.$service_fqdn) - ->first(); - if ($env) { - if ($path === '/') { - $env->value = $host; - } else { - $env->value = $host.$path; - } - $env->save(); - } - if ($path === '/') { - $port_env->value = $host; - } else { - $port_env->value = $host.$path; - } - $port_env->save(); - } - $port_envs_url = EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', 'like', "SERVICE_URL_%_$port") - ->get(); - foreach ($port_envs_url as $port_env_url) { - $service_url = str($port_env_url->key)->beforeLast('_')->after('SERVICE_URL_'); - $env = EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', 'SERVICE_URL_'.$service_url) - ->first(); - if ($env) { - if ($path === '/') { - $env->value = $url; - } else { - $env->value = $url.$path; - } - $env->save(); - } - if ($path === '/') { - $port_env_url->value = $url; - } else { - $port_env_url->value = $url.$path; - } - $port_env_url->save(); - } - } else { - $variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', '_'); - $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', $variableName) - ->first(); - $fqdn = Url::fromString($fqdn); - $fqdn = $fqdn->getScheme().'://'.$fqdn->getHost().$fqdn->getPath(); - if ($generatedEnv) { - $generatedEnv->value = $fqdn; - $generatedEnv->save(); - } - $variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', '_'); - $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', $variableName) - ->first(); - $url = Url::fromString($fqdn); - $url = $url->getHost().$url->getPath(); - if ($generatedEnv) { - $url = str($fqdn)->after('://'); - $generatedEnv->value = $url; - $generatedEnv->save(); - } - } - } } - } else { - // If FQDN is removed, delete the corresponding environment variables - $serviceName = str($resource->name)->upper()->replace('-', '_'); - EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', 'LIKE', "SERVICE_FQDN_{$serviceName}%") - ->delete(); - EnvironmentVariable::where('resourceable_type', Service::class) - ->where('resourceable_id', $resource->service_id) - ->where('key', 'LIKE', "SERVICE_URL_{$serviceName}%") - ->delete(); + $variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', '_'); + $fqdn = Url::fromString($resourceFqdns); + $port = $fqdn->getPort(); + $path = $fqdn->getPath(); + $fqdn = $fqdn->getHost(); + $fqdnValue = str($fqdn)->after('://'); + if ($path !== '/') { + $fqdnValue = $fqdnValue.$path; + } + $resource->service->environment_variables()->updateOrCreate([ + 'resourceable_type' => Service::class, + 'resourceable_id' => $resource->service_id, + 'key' => $variableName, + ], [ + 'value' => $fqdnValue, + 'is_build_time' => false, + 'is_preview' => false, + ]); + if ($port) { + $variableName = $variableName."_$port"; + $resource->service->environment_variables()->updateOrCreate([ + 'resourceable_type' => Service::class, + 'resourceable_id' => $resource->service_id, + 'key' => $variableName, + ], [ + 'value' => $fqdnValue, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } } } catch (\Throwable $e) { return handleError($e); diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 7ce511f2c..1e9b95288 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -2918,10 +2918,18 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } } -function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $preview_id = null): Collection +function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $preview_id = null, bool $isOneClick = false): Collection { $isApplication = $resource instanceof Application; $isService = $resource instanceof Service; + if ($isApplication) { + return applicationParser($resource, $pull_request_id, $preview_id); + } + if ($isService) { + return serviceParser($resource, $isOneClick); + } + + return collect([]); $uuid = data_get($resource, 'uuid'); $compose = data_get($resource, 'docker_compose_raw'); @@ -3078,14 +3086,25 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $fqdn = generateFqdn($server, "$uuid"); } } elseif ($isService) { - if (blank($savedService->fqdn)) { - if ($fqdnFor) { - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + if ($isOneClick) { + if (blank($savedService->fqdn)) { + if ($fqdnFor) { + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + } else { + $fqdn = generateFqdn($server, "{$savedService->name}-$uuid"); + } } else { - $fqdn = generateFqdn($server, "{$savedService->name}-$uuid"); + $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); } + } else { - $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); + // For services which are not one-click, if no explicit FQDN is set, leave SERVICE_FQDN_ variables empty + if (blank($savedService->fqdn)) { + $fqdn = ''; + } else { + $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); + } + ray($fqdn); } } @@ -3141,51 +3160,41 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $key = str($key); $value = replaceVariables($value); $command = parseCommandFromMagicEnvVariable($key); - $found = $resource->environment_variables()->where('key', $key->value())->where('resourceable_type', get_class($resource))->where('resourceable_id', $resource->id)->first(); - if ($found) { - continue; - } if ($command->value() === 'FQDN') { - if ($isApplication && $resource->build_pack === 'dockercompose') { - continue; + if ($isOneClick) { + $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); + } + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); } - $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); - if (str($fqdnFor)->contains('_')) { - $fqdnFor = str($fqdnFor)->before('_'); - } - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); } elseif ($command->value() === 'URL') { - if ($isApplication && $resource->build_pack === 'dockercompose') { - continue; + if ($isOneClick) { + $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); + } + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); } - // For services, only generate URL if explicit FQDN is set - if ($isService && blank($savedService->fqdn)) { - continue; - } - $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); - if (str($fqdnFor)->contains('_')) { - $fqdnFor = str($fqdnFor)->before('_'); - } - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); } else { $value = generateEnvValue($command, $resource); $resource->environment_variables()->firstOrCreate([ @@ -3279,12 +3288,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first(); if ($applicationFound) { $savedService = $applicationFound; - // $savedService = ServiceDatabase::firstOrCreate([ - // 'name' => $applicationFound->name, - // 'image' => $applicationFound->image, - // 'service_id' => $applicationFound->service_id, - // ]); - // $applicationFound->delete(); } else { $savedService = ServiceDatabase::firstOrCreate([ 'name' => $serviceName, @@ -3550,7 +3553,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $normalEnvironments = $normalEnvironments->filter(function ($value, $key) { return ! str($value)->startsWith('SERVICE_'); }); - foreach ($normalEnvironments as $key => $value) { $key = str($key); $value = str($value); @@ -3669,6 +3671,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $fqdns = data_get($domains, "$serviceName.domain"); // Generate SERVICE_FQDN & SERVICE_URL for dockercompose if ($resource->build_pack === 'dockercompose') { + foreach ($domains as $forServiceName => $domain) { $parsedDomain = data_get($domain, 'domain'); if (filled($parsedDomain)) { @@ -3731,7 +3734,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int } else { $fqdns = collect(data_get($savedService, 'fqdns'))->filter(); } - $defaultLabels = defaultLabels( id: $resource->id, name: $containerName, @@ -3757,7 +3759,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $coolifyEnvironments->put('COOLIFY_FQDN', $urls->implode(',')); } add_coolify_default_environment_variables($resource, $coolifyEnvironments, $resource->environment_variables); - if ($environment->count() > 0) { $environment = $environment->filter(function ($value, $key) { return ! str($key)->startsWith('SERVICE_FQDN_'); From 1ddec358a55793bd12a2d4f99d6b866c756dbb37 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sat, 2 Aug 2025 15:45:21 +0200 Subject: [PATCH 047/367] feat(input): add autofocus attribute to input component for improved accessibility --- app/View/Components/Forms/Input.php | 1 + resources/views/components/forms/input.blade.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/View/Components/Forms/Input.php b/app/View/Components/Forms/Input.php index 7283ef20f..a7bd87949 100644 --- a/app/View/Components/Forms/Input.php +++ b/app/View/Components/Forms/Input.php @@ -25,6 +25,7 @@ class Input extends Component public string $autocomplete = 'off', public ?int $minlength = null, public ?int $maxlength = null, + public bool $autofocus = false, ) {} public function render(): View|Closure|string diff --git a/resources/views/components/forms/input.blade.php b/resources/views/components/forms/input.blade.php index 20e6485bf..799a5f110 100644 --- a/resources/views/components/forms/input.blade.php +++ b/resources/views/components/forms/input.blade.php @@ -32,7 +32,7 @@ wire:dirty.class="dark:focus:ring-warning dark:ring-warning" wire:loading.attr="disabled" type="{{ $type }}" @readonly($readonly) @disabled($disabled) id="{{ $id }}" name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}" - aria-placeholder="{{ $attributes->get('placeholder') }}"> + aria-placeholder="{{ $attributes->get('placeholder') }}" @if ($autofocus) x-ref="autofocusInput" @endif>
@else @@ -45,7 +45,7 @@ max="{{ $attributes->get('max') }}" minlength="{{ $attributes->get('minlength') }}" maxlength="{{ $attributes->get('maxlength') }}" @if ($id !== 'null') id={{ $id }} @endif name="{{ $name }}" - placeholder="{{ $attributes->get('placeholder') }}"> + placeholder="{{ $attributes->get('placeholder') }}" @if ($autofocus) x-ref="autofocusInput" @endif> @endif @if (!$label && $helper) From a2c5f4b9d1f9afa68db054ad9f392533a9c29977 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sat, 2 Aug 2025 15:50:09 +0200 Subject: [PATCH 048/367] refactor(public-git-repository): enhance form structure and add autofocus to repository URL input --- .../new/public-git-repository.blade.php | 162 ++++++++++-------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/resources/views/livewire/project/new/public-git-repository.blade.php b/resources/views/livewire/project/new/public-git-repository.blade.php index ad48b6d4d..e07cfaaae 100644 --- a/resources/views/livewire/project/new/public-git-repository.blade.php +++ b/resources/views/livewire/project/new/public-git-repository.blade.php @@ -1,84 +1,94 @@ -
+

Create a new Application

Deploy any public Git repositories.
+ +
-
-
- - - Check repository - -
-
- For example application deployments, checkout Coolify - Examples. -
- @if ($branchFound) - @if ($rate_limit_remaining && $rate_limit_reset) -
-
Rate Limit
- -
- @endif -
-
- @if ($git_source === 'other') - - @else - - @endif - - - - - - - @if ($isStatic) - - @endif -
- @if ($build_pack === 'dockercompose') - - - Compose file location in your repository:{{ Str::start($base_directory . $docker_compose_location, '/') }} - @else - - @endif - @if ($show_is_static) - -
- -
- @endif - {{--
- -
--}} - {{-- @if ($build_pack === 'dockercompose' && isDev()) -
If you choose Docker Compose based deployments, you cannot - change it afterwards.
- - @endif --}} -
- - Continue - - @endif +
+ + + Check repository + +
+
+ For example application deployments, checkout Coolify + Examples.
+ + @if ($branchFound) + @if ($rate_limit_remaining && $rate_limit_reset) +
+
Rate Limit
+ +
+ @endif + + +
+
+
+ @if ($git_source === 'other') + + @else + + @endif + + + + + + + @if ($isStatic) + + @endif +
+ @if ($build_pack === 'dockercompose') +
+ + +
+ + Compose file location in your repository: +
+
+ @else + + @endif + @if ($show_is_static) + +
+ +
+ @endif + {{--
+ +
--}} + {{-- @if ($build_pack === 'dockercompose' && isDev()) +
If you choose Docker Compose based deployments, you cannot + change it afterwards.
+ + @endif --}} +
+ + Continue + +
+ @endif
From e2518e53d9d5842265b0dd95b9102af1680e41df Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 4 Aug 2025 10:21:15 +0200 Subject: [PATCH 049/367] refactor(public-git-repository): remove commented-out code for cleaner template --- .../livewire/project/new/public-git-repository.blade.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/resources/views/livewire/project/new/public-git-repository.blade.php b/resources/views/livewire/project/new/public-git-repository.blade.php index e07cfaaae..ba9e37784 100644 --- a/resources/views/livewire/project/new/public-git-repository.blade.php +++ b/resources/views/livewire/project/new/public-git-repository.blade.php @@ -76,15 +76,6 @@ helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
@endif - {{--
- -
--}} - {{-- @if ($build_pack === 'dockercompose' && isDev()) -
If you choose Docker Compose based deployments, you cannot - change it afterwards.
- - @endif --}}
Continue From e8892b3d29290b69b796a6f2bddeab84d323b392 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 4 Aug 2025 10:46:59 +0200 Subject: [PATCH 050/367] feat(core): finally fqdn is fqdn and url is url. haha --- app/Livewire/Project/Application/General.php | 18 +- .../Project/Application/PreviewsCompose.php | 2 +- app/Livewire/Project/CloneMe.php | 2 +- app/Livewire/Project/Resource/Create.php | 2 +- .../Project/Shared/ResourceOperations.php | 2 +- app/Models/Application.php | 18 +- app/Models/Service.php | 6 +- bootstrap/helpers/docker.php | 8 +- bootstrap/helpers/parsers.php | 235 +- bootstrap/helpers/shared.php | 1914 +++++++++-------- 10 files changed, 1159 insertions(+), 1048 deletions(-) diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 2eadbaa0e..e7515b23f 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -227,7 +227,19 @@ class General extends Component return; } - $this->application->parse(); + + // Refresh parsedServiceDomains to reflect any changes in docker_compose_domains + $this->application->refresh(); + $this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : []; + ray($this->parsedServiceDomains); + // Convert service names with dots to use underscores for HTML form binding + $sanitizedDomains = []; + foreach ($this->parsedServiceDomains as $serviceName => $domain) { + $sanitizedKey = str($serviceName)->slug('_')->toString(); + $sanitizedDomains[$sanitizedKey] = $domain; + } + $this->parsedServiceDomains = $sanitizedDomains; + $showToast && $this->dispatch('success', 'Docker compose file loaded.'); $this->dispatch('compose_loaded'); $this->dispatch('refreshStorages'); @@ -245,7 +257,7 @@ class General extends Component public function generateDomain(string $serviceName) { $uuid = new Cuid2; - $domain = generateFqdn($this->application->destination->server, $uuid); + $domain = generateUrl(server: $this->application->destination->server, random: $uuid); $sanitizedKey = str($serviceName)->slug('_')->toString(); $this->parsedServiceDomains[$sanitizedKey]['domain'] = $domain; @@ -315,7 +327,7 @@ class General extends Component { $server = data_get($this->application, 'destination.server'); if ($server) { - $fqdn = generateFqdn($server, $this->application->uuid); + $fqdn = generateFqdn(server: $server, random: $this->application->uuid, parserVersion: $this->application->compose_parsing_version); $this->application->fqdn = $fqdn; $this->application->save(); $this->resetDefaultLabels(); diff --git a/app/Livewire/Project/Application/PreviewsCompose.php b/app/Livewire/Project/Application/PreviewsCompose.php index 334d96cad..5938b2944 100644 --- a/app/Livewire/Project/Application/PreviewsCompose.php +++ b/app/Livewire/Project/Application/PreviewsCompose.php @@ -48,7 +48,7 @@ class PreviewsCompose extends Component $random = new Cuid2; // Generate a unique domain like main app services do - $generated_fqdn = generateFqdn($server, $random); + $generated_fqdn = generateFqdn(server: $server, random: $random, parserVersion: $this->preview->application->compose_parsing_version); $preview_fqdn = str_replace('{{random}}', $random, $template); $preview_fqdn = str_replace('{{domain}}', str($generated_fqdn)->after('://'), $preview_fqdn); diff --git a/app/Livewire/Project/CloneMe.php b/app/Livewire/Project/CloneMe.php index a5d80a11a..57f4a0c68 100644 --- a/app/Livewire/Project/CloneMe.php +++ b/app/Livewire/Project/CloneMe.php @@ -129,7 +129,7 @@ class CloneMe extends Component $uuid = (string) new Cuid2; $url = $application->fqdn; if ($this->server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) { - $url = generateFqdn($this->server, $uuid); + $url = generateFqdn(server: $this->server, random: $uuid, parserVersion: $application->compose_parsing_version); } $newApplication = $application->replicate([ diff --git a/app/Livewire/Project/Resource/Create.php b/app/Livewire/Project/Resource/Create.php index d8e397e59..e7cff4f29 100644 --- a/app/Livewire/Project/Resource/Create.php +++ b/app/Livewire/Project/Resource/Create.php @@ -102,7 +102,7 @@ class Create extends Component } }); } - $service->parse(isNew: true, isOneClick: true); + $service->parse(isNew: true); return redirect()->route('project.service.configuration', [ 'service_uuid' => $service->uuid, diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php index c8916bf19..853dbe57a 100644 --- a/app/Livewire/Project/Shared/ResourceOperations.php +++ b/app/Livewire/Project/Shared/ResourceOperations.php @@ -61,7 +61,7 @@ class ResourceOperations extends Component $url = $this->resource->fqdn; if ($server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) { - $url = generateFqdn($server, $uuid); + $url = generateFqdn(server: $server, random: $uuid, parserVersion: $this->resource->compose_parsing_version); } $new_resource = $this->resource->replicate([ diff --git a/app/Models/Application.php b/app/Models/Application.php index ed97454d2..f74ed89d1 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -111,7 +111,7 @@ class Application extends BaseModel { use HasConfiguration, HasFactory, SoftDeletes; - private static $parserVersion = '4'; + private static $parserVersion = '5'; protected $guarded = []; @@ -1442,7 +1442,21 @@ class Application extends BaseModel $parsedServices = $this->parse(); if ($this->docker_compose_domains) { $json = collect(json_decode($this->docker_compose_domains)); - $names = collect(data_get($parsedServices, 'services'))->keys()->toArray(); + foreach ($json as $key => $value) { + if (str($key)->contains('-')) { + $key = str($key)->replace('-', '_'); + } + $json->put((string) $key, $value); + } + $services = collect(data_get($parsedServices, 'services', [])); + foreach ($services as $name => $service) { + if (str($name)->contains('-')) { + $replacedName = str($name)->replace('-', '_'); + $services->put((string) $replacedName, $service); + $services->forget((string) $name); + } + } + $names = collect($services)->keys()->toArray(); $jsonNames = $json->keys()->toArray(); $diff = array_diff($jsonNames, $names); $json = $json->filter(function ($value, $key) use ($diff) { diff --git a/app/Models/Service.php b/app/Models/Service.php index 4bef46642..ed3241f46 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -42,7 +42,7 @@ class Service extends BaseModel { use HasFactory, SoftDeletes; - private static $parserVersion = '4'; + private static $parserVersion = '5'; protected $guarded = []; @@ -1274,10 +1274,10 @@ class Service extends BaseModel instant_remote_process($commands, $this->server); } - public function parse(bool $isNew = false, bool $isOneClick = false): Collection + public function parse(bool $isNew = false): Collection { if ((int) $this->compose_parsing_version >= 3) { - return serviceParser($this, isOneClick: $isOneClick); + return serviceParser($this); } elseif ($this->docker_compose_raw) { return parseDockerComposeFile($this, $isNew); } else { diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 739f98f22..1737ca714 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -256,12 +256,12 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource) if (str($MINIO_BROWSER_REDIRECT_URL->value ?? '')->isEmpty()) { $MINIO_BROWSER_REDIRECT_URL->update([ - 'value' => generateFqdn($server, 'console-'.$uuid, true), + 'value' => generateFqdn(server: $server, random: 'console-'.$uuid, parserVersion: $resource->compose_parsing_version, forceHttps: true), ]); } if (str($MINIO_SERVER_URL->value ?? '')->isEmpty()) { $MINIO_SERVER_URL->update([ - 'value' => generateFqdn($server, 'minio-'.$uuid, true), + 'value' => generateFqdn(server: $server, random: 'minio-'.$uuid, parserVersion: $resource->compose_parsing_version, forceHttps: true), ]); } $payload = collect([ @@ -279,12 +279,12 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource) if (str($LOGTO_ENDPOINT->value ?? '')->isEmpty()) { $LOGTO_ENDPOINT->update([ - 'value' => generateFqdn($server, 'logto-'.$uuid), + 'value' => generateFqdn(server: $server, random: 'logto-'.$uuid, parserVersion: $resource->compose_parsing_version), ]); } if (str($LOGTO_ADMIN_ENDPOINT->value ?? '')->isEmpty()) { $LOGTO_ADMIN_ENDPOINT->update([ - 'value' => generateFqdn($server, 'logto-admin-'.$uuid), + 'value' => generateFqdn(server: $server, random: 'logto-admin-'.$uuid, parserVersion: $resource->compose_parsing_version), ]); } $payload = collect([ diff --git a/bootstrap/helpers/parsers.php b/bootstrap/helpers/parsers.php index 649de731d..e5e0cbe86 100644 --- a/bootstrap/helpers/parsers.php +++ b/bootstrap/helpers/parsers.php @@ -16,7 +16,7 @@ use Spatie\Url\Url; use Symfony\Component\Yaml\Yaml; use Visus\Cuid2\Cuid2; -function applicationParser(Application $resource, int $pull_request_id = 0, ?int $preview_id = null, bool $isOneClick = false): Collection +function applicationParser(Application $resource, int $pull_request_id = 0, ?int $preview_id = null): Collection { $uuid = data_get($resource, 'uuid'); $compose = data_get($resource, 'docker_compose_raw'); @@ -100,6 +100,7 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int } } // Get magic environments where we need to preset the FQDN + // for example SERVICE_FQDN_APP_3000 (without a value) if ($key->startsWith('SERVICE_FQDN_')) { // SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000 if (substr_count(str($key)->value(), '_') === 3) { @@ -111,7 +112,7 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int } $fqdn = $resource->fqdn; if (blank($resource->fqdn)) { - $fqdn = generateFqdn($server, "$uuid"); + $fqdn = generateFqdn(server: $server, random: "$uuid", parserVersion: $resource->compose_parsing_version); } if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) { @@ -160,44 +161,58 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int $allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments); if ($magicEnvironments->count() > 0) { + // Generate Coolify environment variables foreach ($magicEnvironments as $key => $value) { $key = str($key); $value = replaceVariables($value); $command = parseCommandFromMagicEnvVariable($key); if ($command->value() === 'FQDN') { - if ($isOneClick) { - $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); - if (str($fqdnFor)->contains('_')) { - $fqdnFor = str($fqdnFor)->before('_'); - } - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, + $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + if (str($fqdnFor)->contains('-')) { + $fqdnFor = str($fqdnFor)->replace('-', '_'); + } + $fqdn = generateFqdn(server: $server, random: "$fqdnFor-$uuid", parserVersion: $resource->compose_parsing_version); + $url = generateUrl(server: $server, random: "$fqdnFor-$uuid"); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + if ($resource->build_pack === 'dockercompose') { + $domains = collect(json_decode(data_get($resource, 'docker_compose_domains'))) ?? collect([]); + // Put URL in the domains array instead of FQDN + $domains->put((string) $fqdnFor, [ + 'domain' => $url, ]); + $resource->docker_compose_domains = $domains->toJson(); + $resource->save(); } } elseif ($command->value() === 'URL') { - if ($isOneClick) { - $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); - if (str($fqdnFor)->contains('_')) { - $fqdnFor = str($fqdnFor)->before('_'); - } - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, + $urlFor = $key->after('SERVICE_URL_')->lower()->value(); + if (str($urlFor)->contains('-')) { + $urlFor = str($urlFor)->replace('-', '_'); + } + $url = generateUrl(server: $server, random: "$urlFor-$uuid"); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $url, + 'is_build_time' => false, + 'is_preview' => false, + ]); + if ($resource->build_pack === 'dockercompose') { + $domains = collect(json_decode(data_get($resource, 'docker_compose_domains'))) ?? collect([]); + $domains->put((string) $urlFor, [ + 'domain' => $url, ]); + $resource->docker_compose_domains = $domains->toJson(); + $resource->save(); } } else { $value = generateEnvValue($command, $resource); @@ -599,7 +614,6 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int } else { $domains = collect(json_decode($resource->docker_compose_domains)) ?? collect([]); } - ray($domains); $fqdns = data_get($domains, "$serviceName.domain"); // Generate SERVICE_FQDN & SERVICE_URL for dockercompose if ($resource->build_pack === 'dockercompose') { @@ -849,7 +863,7 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int return $topLevel; } -function serviceParser(Service $resource, bool $isOneClick = false): Collection +function serviceParser(Service $resource): Collection { $uuid = data_get($resource, 'uuid'); $compose = data_get($resource, 'docker_compose_raw'); @@ -977,49 +991,86 @@ function serviceParser(Service $resource, bool $isOneClick = false): Collection } } // Get magic environments where we need to preset the FQDN - if ($key->startsWith('SERVICE_FQDN_')) { + if ($key->startsWith('SERVICE_FQDN_') || $key->startsWith('SERVICE_URL_')) { // SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000 if (substr_count(str($key)->value(), '_') === 3) { - $fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value(); + if ($key->startsWith('SERVICE_FQDN_')) { + $urlFor = null; + $fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value(); + } + if ($key->startsWith('SERVICE_URL_')) { + $fqdnFor = null; + $urlFor = $key->after('SERVICE_URL_')->beforeLast('_')->lower()->value(); + } $port = $key->afterLast('_')->value(); } else { - $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + if ($key->startsWith('SERVICE_FQDN_')) { + $urlFor = null; + $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + } + if ($key->startsWith('SERVICE_URL_')) { + $fqdnFor = null; + $urlFor = $key->after('SERVICE_URL_')->lower()->value(); + } $port = null; } - if ($isOneClick) { - if (blank($savedService->fqdn)) { - if ($fqdnFor) { - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - } else { - $fqdn = generateFqdn($server, "{$savedService->name}-$uuid"); - } + // if ($isOneClick) { + if (blank($savedService->fqdn)) { + if ($fqdnFor) { + $fqdn = generateFqdn(server: $server, random: "$fqdnFor-$uuid", parserVersion: $resource->compose_parsing_version); } else { - $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); + $fqdn = generateFqdn(server: $server, random: "{$savedService->name}-$uuid", parserVersion: $resource->compose_parsing_version); + } + if ($urlFor) { + $url = generateUrl($server, "$urlFor-$uuid"); + } else { + $url = generateUrl($server, "{$savedService->name}-$uuid"); } - } else { - // For services which are not one-click, if no explicit FQDN is set, leave SERVICE_FQDN_ variables empty - if (blank($savedService->fqdn)) { - $fqdn = ''; - } else { - $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); - } + $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); + $url = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); } + + // } else { + // // For services which are not one-click, if no explicit FQDN is set, leave SERVICE_FQDN_ variables empty + // if (blank($savedService->fqdn)) { + // $fqdn = ''; + // } else { + // $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); + // } + // } if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) { $path = $value->value(); if ($path !== '/') { $fqdn = "$fqdn$path"; + $url = "$url$path"; } } $fqdnWithPort = $fqdn; - if ($port) { + $urlWithPort = $url; + if ($fqdn && $port) { $fqdnWithPort = "$fqdn:$port"; } + if ($url && $port) { + $urlWithPort = "$url:$port"; + } + ray("urlWithPort: $urlWithPort, fqdnWithPort: $fqdnWithPort", "parserVersion: $resource->compose_parsing_version", 'isVersionGreaterThan4207: '.version_compare('4.0.0-beta.420.7', config('constants.coolify.version'), '>=')); if (is_null($savedService->fqdn)) { - $savedService->fqdn = $fqdnWithPort; + if ((int) $resource->compose_parsing_version >= 5 && version_compare(config('constants.coolify.version'), '4.0.0-beta.420.7', '>=')) { + if ($fqdnFor) { + ray("setting fqdn(fqdnWithPort) to $fqdnWithPort"); + $savedService->fqdn = $fqdnWithPort; + } + if ($urlFor) { + ray("setting fqdn(urlWithPort) to $urlWithPort"); + $savedService->fqdn = $urlWithPort; + } + } else { + ray("setting fqdn(fqdnWithPort) old parser version to $fqdnWithPort"); + $savedService->fqdn = $fqdnWithPort; + } $savedService->save(); } - if (substr_count(str($key)->value(), '_') === 2) { $resource->environment_variables()->updateOrCreate([ 'key' => $key->value(), @@ -1030,6 +1081,15 @@ function serviceParser(Service $resource, bool $isOneClick = false): Collection 'is_build_time' => false, 'is_preview' => false, ]); + $resource->environment_variables()->updateOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $url, + 'is_build_time' => false, + 'is_preview' => false, + ]); } if (substr_count(str($key)->value(), '_') === 3) { $newKey = str($key)->beforeLast('_'); @@ -1042,6 +1102,15 @@ function serviceParser(Service $resource, bool $isOneClick = false): Collection 'is_build_time' => false, 'is_preview' => false, ]); + $resource->environment_variables()->updateOrCreate([ + 'key' => $newKey->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $url, + 'is_build_time' => false, + 'is_preview' => false, + ]); } } } @@ -1053,40 +1122,36 @@ function serviceParser(Service $resource, bool $isOneClick = false): Collection $value = replaceVariables($value); $command = parseCommandFromMagicEnvVariable($key); if ($command->value() === 'FQDN') { - if ($isOneClick) { - $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); - if (str($fqdnFor)->contains('_')) { - $fqdnFor = str($fqdnFor)->before('_'); - } - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); + $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); } + $fqdn = generateFqdn(server: $server, random: "$fqdnFor-$uuid", parserVersion: $resource->compose_parsing_version); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); } elseif ($command->value() === 'URL') { - if ($isOneClick) { - $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); - if (str($fqdnFor)->contains('_')) { - $fqdnFor = str($fqdnFor)->before('_'); - } - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); + $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); } + $fqdn = generateFqdn(server: $server, random: "$fqdnFor-$uuid", parserVersion: $resource->compose_parsing_version); + $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); } else { $value = generateEnvValue($command, $resource); $resource->environment_variables()->firstOrCreate([ diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 1e9b95288..624e0b0d1 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -402,7 +402,7 @@ function data_get_str($data, $key, $default = null): Stringable return str($str); } -function generateFqdn(Server $server, string $random, bool $forceHttps = false): string +function generateUrl(Server $server, string $random, bool $forceHttps = false): string { $wildcard = data_get($server, 'settings.wildcard_domain'); if (is_null($wildcard) || $wildcard === '') { @@ -418,6 +418,26 @@ function generateFqdn(Server $server, string $random, bool $forceHttps = false): return "$scheme://{$random}.$host$path"; } +function generateFqdn(Server $server, string $random, bool $forceHttps = false, int $parserVersion = 4): string +{ + $wildcard = data_get($server, 'settings.wildcard_domain'); + if (is_null($wildcard) || $wildcard === '') { + $wildcard = sslip($server); + } + $url = Url::fromString($wildcard); + $host = $url->getHost(); + $path = $url->getPath() === '/' ? '' : $url->getPath(); + $scheme = $url->getScheme(); + if ($forceHttps) { + $scheme = 'https'; + } + + if ($parserVersion >= 5 && version_compare(config('constants.coolify.version'), '4.0.0-beta.420.7', '>=')) { + return "{$random}.$host$path"; + } + + return "$scheme://{$random}.$host$path"; +} function sslip(Server $server) { if (isDev() && $server->id === 0) { @@ -2918,1008 +2938,1008 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } } -function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $preview_id = null, bool $isOneClick = false): Collection -{ - $isApplication = $resource instanceof Application; - $isService = $resource instanceof Service; - if ($isApplication) { - return applicationParser($resource, $pull_request_id, $preview_id); - } - if ($isService) { - return serviceParser($resource, $isOneClick); - } +// function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $preview_id = null): Collection +// { +// $isApplication = $resource instanceof Application; +// $isService = $resource instanceof Service; +// if ($isApplication) { +// return applicationParser($resource, $pull_request_id, $preview_id); +// } +// if ($isService) { +// return serviceParser($resource); +// } - return collect([]); +// return collect([]); - $uuid = data_get($resource, 'uuid'); - $compose = data_get($resource, 'docker_compose_raw'); - if (! $compose) { - return collect([]); - } +// $uuid = data_get($resource, 'uuid'); +// $compose = data_get($resource, 'docker_compose_raw'); +// if (! $compose) { +// return collect([]); +// } - if ($isApplication) { - $pullRequestId = $pull_request_id; - $isPullRequest = $pullRequestId == 0 ? false : true; - $server = data_get($resource, 'destination.server'); - $fileStorages = $resource->fileStorages(); - } elseif ($isService) { - $server = data_get($resource, 'server'); - $allServices = get_service_templates(); - } else { - return collect([]); - } +// if ($isApplication) { +// $pullRequestId = $pull_request_id; +// $isPullRequest = $pullRequestId == 0 ? false : true; +// $server = data_get($resource, 'destination.server'); +// $fileStorages = $resource->fileStorages(); +// } elseif ($isService) { +// $server = data_get($resource, 'server'); +// $allServices = get_service_templates(); +// } else { +// return collect([]); +// } - try { - $yaml = Yaml::parse($compose); - } catch (\Exception) { - return collect([]); - } - $services = data_get($yaml, 'services', collect([])); - $topLevel = collect([ - 'volumes' => collect(data_get($yaml, 'volumes', [])), - 'networks' => collect(data_get($yaml, 'networks', [])), - 'configs' => collect(data_get($yaml, 'configs', [])), - 'secrets' => collect(data_get($yaml, 'secrets', [])), - ]); - // If there are predefined volumes, make sure they are not null - if ($topLevel->get('volumes')->count() > 0) { - $temp = collect([]); - foreach ($topLevel['volumes'] as $volumeName => $volume) { - if (is_null($volume)) { - continue; - } - $temp->put($volumeName, $volume); - } - $topLevel['volumes'] = $temp; - } - // Get the base docker network - $baseNetwork = collect([$uuid]); - if ($isApplication && $isPullRequest) { - $baseNetwork = collect(["{$uuid}-{$pullRequestId}"]); - } +// try { +// $yaml = Yaml::parse($compose); +// } catch (\Exception) { +// return collect([]); +// } +// $services = data_get($yaml, 'services', collect([])); +// $topLevel = collect([ +// 'volumes' => collect(data_get($yaml, 'volumes', [])), +// 'networks' => collect(data_get($yaml, 'networks', [])), +// 'configs' => collect(data_get($yaml, 'configs', [])), +// 'secrets' => collect(data_get($yaml, 'secrets', [])), +// ]); +// // If there are predefined volumes, make sure they are not null +// if ($topLevel->get('volumes')->count() > 0) { +// $temp = collect([]); +// foreach ($topLevel['volumes'] as $volumeName => $volume) { +// if (is_null($volume)) { +// continue; +// } +// $temp->put($volumeName, $volume); +// } +// $topLevel['volumes'] = $temp; +// } +// // Get the base docker network +// $baseNetwork = collect([$uuid]); +// if ($isApplication && $isPullRequest) { +// $baseNetwork = collect(["{$uuid}-{$pullRequestId}"]); +// } - $parsedServices = collect([]); +// $parsedServices = collect([]); - $allMagicEnvironments = collect([]); - foreach ($services as $serviceName => $service) { - $predefinedPort = null; - $magicEnvironments = collect([]); - $image = data_get_str($service, 'image'); - $environment = collect(data_get($service, 'environment', [])); - $buildArgs = collect(data_get($service, 'build.args', [])); - $environment = $environment->merge($buildArgs); - $isDatabase = isDatabaseImage($image, $service); +// $allMagicEnvironments = collect([]); +// foreach ($services as $serviceName => $service) { +// $predefinedPort = null; +// $magicEnvironments = collect([]); +// $image = data_get_str($service, 'image'); +// $environment = collect(data_get($service, 'environment', [])); +// $buildArgs = collect(data_get($service, 'build.args', [])); +// $environment = $environment->merge($buildArgs); +// $isDatabase = isDatabaseImage($image, $service); - if ($isService) { - $containerName = "$serviceName-{$resource->uuid}"; +// if ($isService) { +// $containerName = "$serviceName-{$resource->uuid}"; - if ($serviceName === 'registry') { - $tempServiceName = 'docker-registry'; - } else { - $tempServiceName = $serviceName; - } - if (str(data_get($service, 'image'))->contains('glitchtip')) { - $tempServiceName = 'glitchtip'; - } - if ($serviceName === 'supabase-kong') { - $tempServiceName = 'supabase'; - } - $serviceDefinition = data_get($allServices, $tempServiceName); - $predefinedPort = data_get($serviceDefinition, 'port'); - if ($serviceName === 'plausible') { - $predefinedPort = '8000'; - } - if ($isDatabase) { - $applicationFound = ServiceApplication::where('name', $serviceName)->where('service_id', $resource->id)->first(); - if ($applicationFound) { - $savedService = $applicationFound; - } else { - $savedService = ServiceDatabase::firstOrCreate([ - 'name' => $serviceName, - 'service_id' => $resource->id, - ]); - } - } else { - $savedService = ServiceApplication::firstOrCreate([ - 'name' => $serviceName, - 'service_id' => $resource->id, - ], [ - 'is_gzip_enabled' => true, - ]); - } - // Check if image changed - if ($savedService->image !== $image) { - $savedService->image = $image; - $savedService->save(); - } - // Pocketbase does not need gzip for SSE. - if (str($savedService->image)->contains('pocketbase') && $savedService->is_gzip_enabled) { - $savedService->is_gzip_enabled = false; - $savedService->save(); - } - } +// if ($serviceName === 'registry') { +// $tempServiceName = 'docker-registry'; +// } else { +// $tempServiceName = $serviceName; +// } +// if (str(data_get($service, 'image'))->contains('glitchtip')) { +// $tempServiceName = 'glitchtip'; +// } +// if ($serviceName === 'supabase-kong') { +// $tempServiceName = 'supabase'; +// } +// $serviceDefinition = data_get($allServices, $tempServiceName); +// $predefinedPort = data_get($serviceDefinition, 'port'); +// if ($serviceName === 'plausible') { +// $predefinedPort = '8000'; +// } +// if ($isDatabase) { +// $applicationFound = ServiceApplication::where('name', $serviceName)->where('service_id', $resource->id)->first(); +// if ($applicationFound) { +// $savedService = $applicationFound; +// } else { +// $savedService = ServiceDatabase::firstOrCreate([ +// 'name' => $serviceName, +// 'service_id' => $resource->id, +// ]); +// } +// } else { +// $savedService = ServiceApplication::firstOrCreate([ +// 'name' => $serviceName, +// 'service_id' => $resource->id, +// ], [ +// 'is_gzip_enabled' => true, +// ]); +// } +// // Check if image changed +// if ($savedService->image !== $image) { +// $savedService->image = $image; +// $savedService->save(); +// } +// // Pocketbase does not need gzip for SSE. +// if (str($savedService->image)->contains('pocketbase') && $savedService->is_gzip_enabled) { +// $savedService->is_gzip_enabled = false; +// $savedService->save(); +// } +// } - $environment = collect(data_get($service, 'environment', [])); - $buildArgs = collect(data_get($service, 'build.args', [])); - $environment = $environment->merge($buildArgs); +// $environment = collect(data_get($service, 'environment', [])); +// $buildArgs = collect(data_get($service, 'build.args', [])); +// $environment = $environment->merge($buildArgs); - // convert environment variables to one format - $environment = convertToKeyValueCollection($environment); +// // convert environment variables to one format +// $environment = convertToKeyValueCollection($environment); - // Add Coolify defined environments - $allEnvironments = $resource->environment_variables()->get(['key', 'value']); +// // Add Coolify defined environments +// $allEnvironments = $resource->environment_variables()->get(['key', 'value']); - $allEnvironments = $allEnvironments->mapWithKeys(function ($item) { - return [$item['key'] => $item['value']]; - }); - // filter and add magic environments - foreach ($environment as $key => $value) { - // Get all SERVICE_ variables from keys and values - $key = str($key); - $value = str($value); - $regex = '/\$(\{?([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\}?)/'; - preg_match_all($regex, $value, $valueMatches); - if (count($valueMatches[1]) > 0) { - foreach ($valueMatches[1] as $match) { - $match = replaceVariables($match); - if ($match->startsWith('SERVICE_')) { - if ($magicEnvironments->has($match->value())) { - continue; - } - $magicEnvironments->put($match->value(), ''); - } - } - } - // Get magic environments where we need to preset the FQDN - if ($key->startsWith('SERVICE_FQDN_')) { - // SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000 - if (substr_count(str($key)->value(), '_') === 3) { - $fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value(); - $port = $key->afterLast('_')->value(); - } else { - $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); - $port = null; - } - if ($isApplication) { - $fqdn = $resource->fqdn; - if (blank($resource->fqdn)) { - $fqdn = generateFqdn($server, "$uuid"); - } - } elseif ($isService) { - if ($isOneClick) { - if (blank($savedService->fqdn)) { - if ($fqdnFor) { - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - } else { - $fqdn = generateFqdn($server, "{$savedService->name}-$uuid"); - } - } else { - $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); - } +// $allEnvironments = $allEnvironments->mapWithKeys(function ($item) { +// return [$item['key'] => $item['value']]; +// }); +// // filter and add magic environments +// foreach ($environment as $key => $value) { +// // Get all SERVICE_ variables from keys and values +// $key = str($key); +// $value = str($value); +// $regex = '/\$(\{?([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\}?)/'; +// preg_match_all($regex, $value, $valueMatches); +// if (count($valueMatches[1]) > 0) { +// foreach ($valueMatches[1] as $match) { +// $match = replaceVariables($match); +// if ($match->startsWith('SERVICE_')) { +// if ($magicEnvironments->has($match->value())) { +// continue; +// } +// $magicEnvironments->put($match->value(), ''); +// } +// } +// } +// // Get magic environments where we need to preset the FQDN +// if ($key->startsWith('SERVICE_FQDN_')) { +// // SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000 +// if (substr_count(str($key)->value(), '_') === 3) { +// $fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value(); +// $port = $key->afterLast('_')->value(); +// } else { +// $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); +// $port = null; +// } +// if ($isApplication) { +// $fqdn = $resource->fqdn; +// if (blank($resource->fqdn)) { +// $fqdn = generateFqdn($server, "$uuid"); +// } +// } elseif ($isService) { +// if ($isOneClick) { +// if (blank($savedService->fqdn)) { +// if ($fqdnFor) { +// $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); +// } else { +// $fqdn = generateFqdn($server, "{$savedService->name}-$uuid"); +// } +// } else { +// $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); +// } - } else { - // For services which are not one-click, if no explicit FQDN is set, leave SERVICE_FQDN_ variables empty - if (blank($savedService->fqdn)) { - $fqdn = ''; - } else { - $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); - } - ray($fqdn); - } - } +// } else { +// // For services which are not one-click, if no explicit FQDN is set, leave SERVICE_FQDN_ variables empty +// if (blank($savedService->fqdn)) { +// $fqdn = ''; +// } else { +// $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); +// } +// ray($fqdn); +// } +// } - if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) { - $path = $value->value(); - if ($path !== '/') { - $fqdn = "$fqdn$path"; - } - } - $fqdnWithPort = $fqdn; - if ($port) { - $fqdnWithPort = "$fqdn:$port"; - } - if ($isApplication && is_null($resource->fqdn)) { - data_forget($resource, 'environment_variables'); - data_forget($resource, 'environment_variables_preview'); - $resource->fqdn = $fqdnWithPort; - $resource->save(); - } elseif ($isService && is_null($savedService->fqdn)) { - $savedService->fqdn = $fqdnWithPort; - $savedService->save(); - } +// if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) { +// $path = $value->value(); +// if ($path !== '/') { +// $fqdn = "$fqdn$path"; +// } +// } +// $fqdnWithPort = $fqdn; +// if ($port) { +// $fqdnWithPort = "$fqdn:$port"; +// } +// if ($isApplication && is_null($resource->fqdn)) { +// data_forget($resource, 'environment_variables'); +// data_forget($resource, 'environment_variables_preview'); +// $resource->fqdn = $fqdnWithPort; +// $resource->save(); +// } elseif ($isService && is_null($savedService->fqdn)) { +// $savedService->fqdn = $fqdnWithPort; +// $savedService->save(); +// } - if (substr_count(str($key)->value(), '_') === 2) { - $resource->environment_variables()->updateOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); - } - if (substr_count(str($key)->value(), '_') === 3) { - $newKey = str($key)->beforeLast('_'); - $resource->environment_variables()->updateOrCreate([ - 'key' => $newKey->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); - } - } - } +// if (substr_count(str($key)->value(), '_') === 2) { +// $resource->environment_variables()->updateOrCreate([ +// 'key' => $key->value(), +// 'resourceable_type' => get_class($resource), +// 'resourceable_id' => $resource->id, +// ], [ +// 'value' => $fqdn, +// 'is_build_time' => false, +// 'is_preview' => false, +// ]); +// } +// if (substr_count(str($key)->value(), '_') === 3) { +// $newKey = str($key)->beforeLast('_'); +// $resource->environment_variables()->updateOrCreate([ +// 'key' => $newKey->value(), +// 'resourceable_type' => get_class($resource), +// 'resourceable_id' => $resource->id, +// ], [ +// 'value' => $fqdn, +// 'is_build_time' => false, +// 'is_preview' => false, +// ]); +// } +// } +// } - $allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments); - if ($magicEnvironments->count() > 0) { - foreach ($magicEnvironments as $key => $value) { - $key = str($key); - $value = replaceVariables($value); - $command = parseCommandFromMagicEnvVariable($key); - if ($command->value() === 'FQDN') { - if ($isOneClick) { - $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); - if (str($fqdnFor)->contains('_')) { - $fqdnFor = str($fqdnFor)->before('_'); - } - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); - } - } elseif ($command->value() === 'URL') { - if ($isOneClick) { - $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); - if (str($fqdnFor)->contains('_')) { - $fqdnFor = str($fqdnFor)->before('_'); - } - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); - } - } else { - $value = generateEnvValue($command, $resource); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $value, - 'is_build_time' => false, - 'is_preview' => false, - ]); - } - } - } - } +// $allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments); +// if ($magicEnvironments->count() > 0) { +// foreach ($magicEnvironments as $key => $value) { +// $key = str($key); +// $value = replaceVariables($value); +// $command = parseCommandFromMagicEnvVariable($key); +// if ($command->value() === 'FQDN') { +// if ($isOneClick) { +// $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); +// if (str($fqdnFor)->contains('_')) { +// $fqdnFor = str($fqdnFor)->before('_'); +// } +// $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); +// $resource->environment_variables()->firstOrCreate([ +// 'key' => $key->value(), +// 'resourceable_type' => get_class($resource), +// 'resourceable_id' => $resource->id, +// ], [ +// 'value' => $fqdn, +// 'is_build_time' => false, +// 'is_preview' => false, +// ]); +// } +// } elseif ($command->value() === 'URL') { +// if ($isOneClick) { +// $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); +// if (str($fqdnFor)->contains('_')) { +// $fqdnFor = str($fqdnFor)->before('_'); +// } +// $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); +// $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); +// $resource->environment_variables()->firstOrCreate([ +// 'key' => $key->value(), +// 'resourceable_type' => get_class($resource), +// 'resourceable_id' => $resource->id, +// ], [ +// 'value' => $fqdn, +// 'is_build_time' => false, +// 'is_preview' => false, +// ]); +// } +// } else { +// $value = generateEnvValue($command, $resource); +// $resource->environment_variables()->firstOrCreate([ +// 'key' => $key->value(), +// 'resourceable_type' => get_class($resource), +// 'resourceable_id' => $resource->id, +// ], [ +// 'value' => $value, +// 'is_build_time' => false, +// 'is_preview' => false, +// ]); +// } +// } +// } +// } - $serviceAppsLogDrainEnabledMap = collect([]); - if ($resource instanceof Service) { - $serviceAppsLogDrainEnabledMap = $resource->applications()->get()->keyBy('name')->map(function ($app) { - return $app->isLogDrainEnabled(); - }); - } +// $serviceAppsLogDrainEnabledMap = collect([]); +// if ($resource instanceof Service) { +// $serviceAppsLogDrainEnabledMap = $resource->applications()->get()->keyBy('name')->map(function ($app) { +// return $app->isLogDrainEnabled(); +// }); +// } - // Parse the rest of the services - foreach ($services as $serviceName => $service) { - $image = data_get_str($service, 'image'); - $restart = data_get_str($service, 'restart', RESTART_MODE); - $logging = data_get($service, 'logging'); +// // Parse the rest of the services +// foreach ($services as $serviceName => $service) { +// $image = data_get_str($service, 'image'); +// $restart = data_get_str($service, 'restart', RESTART_MODE); +// $logging = data_get($service, 'logging'); - if ($server->isLogDrainEnabled()) { - if ($resource instanceof Application && $resource->isLogDrainEnabled()) { - $logging = generate_fluentd_configuration(); - } - if ($resource instanceof Service && $serviceAppsLogDrainEnabledMap->get($serviceName)) { - $logging = generate_fluentd_configuration(); - } - } - $volumes = collect(data_get($service, 'volumes', [])); - $networks = collect(data_get($service, 'networks', [])); - $use_network_mode = data_get($service, 'network_mode') !== null; - $depends_on = collect(data_get($service, 'depends_on', [])); - $labels = collect(data_get($service, 'labels', [])); - if ($labels->count() > 0) { - if (isAssociativeArray($labels)) { - $newLabels = collect([]); - $labels->each(function ($value, $key) use ($newLabels) { - $newLabels->push("$key=$value"); - }); - $labels = $newLabels; - } - } - $environment = collect(data_get($service, 'environment', [])); - $ports = collect(data_get($service, 'ports', [])); - $buildArgs = collect(data_get($service, 'build.args', [])); - $environment = $environment->merge($buildArgs); +// if ($server->isLogDrainEnabled()) { +// if ($resource instanceof Application && $resource->isLogDrainEnabled()) { +// $logging = generate_fluentd_configuration(); +// } +// if ($resource instanceof Service && $serviceAppsLogDrainEnabledMap->get($serviceName)) { +// $logging = generate_fluentd_configuration(); +// } +// } +// $volumes = collect(data_get($service, 'volumes', [])); +// $networks = collect(data_get($service, 'networks', [])); +// $use_network_mode = data_get($service, 'network_mode') !== null; +// $depends_on = collect(data_get($service, 'depends_on', [])); +// $labels = collect(data_get($service, 'labels', [])); +// if ($labels->count() > 0) { +// if (isAssociativeArray($labels)) { +// $newLabels = collect([]); +// $labels->each(function ($value, $key) use ($newLabels) { +// $newLabels->push("$key=$value"); +// }); +// $labels = $newLabels; +// } +// } +// $environment = collect(data_get($service, 'environment', [])); +// $ports = collect(data_get($service, 'ports', [])); +// $buildArgs = collect(data_get($service, 'build.args', [])); +// $environment = $environment->merge($buildArgs); - $environment = convertToKeyValueCollection($environment); - $coolifyEnvironments = collect([]); +// $environment = convertToKeyValueCollection($environment); +// $coolifyEnvironments = collect([]); - $isDatabase = isDatabaseImage($image, $service); - $volumesParsed = collect([]); +// $isDatabase = isDatabaseImage($image, $service); +// $volumesParsed = collect([]); - if ($isApplication) { - $baseName = generateApplicationContainerName( - application: $resource, - pull_request_id: $pullRequestId - ); - $containerName = "$serviceName-$baseName"; - $predefinedPort = null; - } elseif ($isService) { - $containerName = "$serviceName-{$resource->uuid}"; +// if ($isApplication) { +// $baseName = generateApplicationContainerName( +// application: $resource, +// pull_request_id: $pullRequestId +// ); +// $containerName = "$serviceName-$baseName"; +// $predefinedPort = null; +// } elseif ($isService) { +// $containerName = "$serviceName-{$resource->uuid}"; - if ($serviceName === 'registry') { - $tempServiceName = 'docker-registry'; - } else { - $tempServiceName = $serviceName; - } - if (str(data_get($service, 'image'))->contains('glitchtip')) { - $tempServiceName = 'glitchtip'; - } - if ($serviceName === 'supabase-kong') { - $tempServiceName = 'supabase'; - } - $serviceDefinition = data_get($allServices, $tempServiceName); - $predefinedPort = data_get($serviceDefinition, 'port'); - if ($serviceName === 'plausible') { - $predefinedPort = '8000'; - } +// if ($serviceName === 'registry') { +// $tempServiceName = 'docker-registry'; +// } else { +// $tempServiceName = $serviceName; +// } +// if (str(data_get($service, 'image'))->contains('glitchtip')) { +// $tempServiceName = 'glitchtip'; +// } +// if ($serviceName === 'supabase-kong') { +// $tempServiceName = 'supabase'; +// } +// $serviceDefinition = data_get($allServices, $tempServiceName); +// $predefinedPort = data_get($serviceDefinition, 'port'); +// if ($serviceName === 'plausible') { +// $predefinedPort = '8000'; +// } - if ($isDatabase) { - $applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first(); - if ($applicationFound) { - $savedService = $applicationFound; - } else { - $savedService = ServiceDatabase::firstOrCreate([ - 'name' => $serviceName, - 'image' => $image, - 'service_id' => $resource->id, - ]); - } - } else { - $savedService = ServiceApplication::firstOrCreate([ - 'name' => $serviceName, - 'image' => $image, - 'service_id' => $resource->id, - ]); - } - $fileStorages = $savedService->fileStorages(); - if ($savedService->image !== $image) { - $savedService->image = $image; - $savedService->save(); - } - } +// if ($isDatabase) { +// $applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first(); +// if ($applicationFound) { +// $savedService = $applicationFound; +// } else { +// $savedService = ServiceDatabase::firstOrCreate([ +// 'name' => $serviceName, +// 'image' => $image, +// 'service_id' => $resource->id, +// ]); +// } +// } else { +// $savedService = ServiceApplication::firstOrCreate([ +// 'name' => $serviceName, +// 'image' => $image, +// 'service_id' => $resource->id, +// ]); +// } +// $fileStorages = $savedService->fileStorages(); +// if ($savedService->image !== $image) { +// $savedService->image = $image; +// $savedService->save(); +// } +// } - $originalResource = $isApplication ? $resource : $savedService; +// $originalResource = $isApplication ? $resource : $savedService; - if ($volumes->count() > 0) { - foreach ($volumes as $index => $volume) { - $type = null; - $source = null; - $target = null; - $content = null; - $isDirectory = false; - if (is_string($volume)) { - $source = str($volume)->before(':'); - $target = str($volume)->after(':')->beforeLast(':'); - $foundConfig = $fileStorages->whereMountPath($target)->first(); - if (sourceIsLocal($source)) { - $type = str('bind'); - if ($foundConfig) { - $contentNotNull_temp = data_get($foundConfig, 'content'); - if ($contentNotNull_temp) { - $content = $contentNotNull_temp; - } - $isDirectory = data_get($foundConfig, 'is_directory'); - } else { - // By default, we cannot determine if the bind is a directory or not, so we set it to directory - $isDirectory = true; - } - } else { - $type = str('volume'); - } - } elseif (is_array($volume)) { - $type = data_get_str($volume, 'type'); - $source = data_get_str($volume, 'source'); - $target = data_get_str($volume, 'target'); - $content = data_get($volume, 'content'); - $isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null); +// if ($volumes->count() > 0) { +// foreach ($volumes as $index => $volume) { +// $type = null; +// $source = null; +// $target = null; +// $content = null; +// $isDirectory = false; +// if (is_string($volume)) { +// $source = str($volume)->before(':'); +// $target = str($volume)->after(':')->beforeLast(':'); +// $foundConfig = $fileStorages->whereMountPath($target)->first(); +// if (sourceIsLocal($source)) { +// $type = str('bind'); +// if ($foundConfig) { +// $contentNotNull_temp = data_get($foundConfig, 'content'); +// if ($contentNotNull_temp) { +// $content = $contentNotNull_temp; +// } +// $isDirectory = data_get($foundConfig, 'is_directory'); +// } else { +// // By default, we cannot determine if the bind is a directory or not, so we set it to directory +// $isDirectory = true; +// } +// } else { +// $type = str('volume'); +// } +// } elseif (is_array($volume)) { +// $type = data_get_str($volume, 'type'); +// $source = data_get_str($volume, 'source'); +// $target = data_get_str($volume, 'target'); +// $content = data_get($volume, 'content'); +// $isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null); - $foundConfig = $fileStorages->whereMountPath($target)->first(); - if ($foundConfig) { - $contentNotNull_temp = data_get($foundConfig, 'content'); - if ($contentNotNull_temp) { - $content = $contentNotNull_temp; - } - $isDirectory = data_get($foundConfig, 'is_directory'); - } else { - // if isDirectory is not set (or false) & content is also not set, we assume it is a directory - if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) { - $isDirectory = true; - } - } - } - if ($type->value() === 'bind') { - if ($source->value() === '/var/run/docker.sock') { - $volume = $source->value().':'.$target->value(); - } elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') { - $volume = $source->value().':'.$target->value(); - } else { - if ((int) $resource->compose_parsing_version >= 4) { - if ($isApplication) { - $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid); - } elseif ($isService) { - $mainDirectory = str(base_configuration_dir().'/services/'.$uuid); - } - } else { - $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid); - } - $source = replaceLocalSource($source, $mainDirectory); - if ($isApplication && $isPullRequest) { - $source = $source."-pr-$pullRequestId"; - } - LocalFileVolume::updateOrCreate( - [ - 'mount_path' => $target, - 'resource_id' => $originalResource->id, - 'resource_type' => get_class($originalResource), - ], - [ - 'fs_path' => $source, - 'mount_path' => $target, - 'content' => $content, - 'is_directory' => $isDirectory, - 'resource_id' => $originalResource->id, - 'resource_type' => get_class($originalResource), - ] - ); - if (isDev()) { - if ((int) $resource->compose_parsing_version >= 4) { - if ($isApplication) { - $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid); - } elseif ($isService) { - $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/services/'.$uuid); - } - } else { - $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid); - } - } - $volume = "$source:$target"; - } - } elseif ($type->value() === 'volume') { - if ($topLevel->get('volumes')->has($source->value())) { - $temp = $topLevel->get('volumes')->get($source->value()); - if (data_get($temp, 'driver_opts.type') === 'cifs') { - continue; - } - if (data_get($temp, 'driver_opts.type') === 'nfs') { - continue; - } - } - $slugWithoutUuid = Str::slug($source, '-'); - $name = "{$uuid}_{$slugWithoutUuid}"; +// $foundConfig = $fileStorages->whereMountPath($target)->first(); +// if ($foundConfig) { +// $contentNotNull_temp = data_get($foundConfig, 'content'); +// if ($contentNotNull_temp) { +// $content = $contentNotNull_temp; +// } +// $isDirectory = data_get($foundConfig, 'is_directory'); +// } else { +// // if isDirectory is not set (or false) & content is also not set, we assume it is a directory +// if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) { +// $isDirectory = true; +// } +// } +// } +// if ($type->value() === 'bind') { +// if ($source->value() === '/var/run/docker.sock') { +// $volume = $source->value().':'.$target->value(); +// } elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') { +// $volume = $source->value().':'.$target->value(); +// } else { +// if ((int) $resource->compose_parsing_version >= 4) { +// if ($isApplication) { +// $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid); +// } elseif ($isService) { +// $mainDirectory = str(base_configuration_dir().'/services/'.$uuid); +// } +// } else { +// $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid); +// } +// $source = replaceLocalSource($source, $mainDirectory); +// if ($isApplication && $isPullRequest) { +// $source = $source."-pr-$pullRequestId"; +// } +// LocalFileVolume::updateOrCreate( +// [ +// 'mount_path' => $target, +// 'resource_id' => $originalResource->id, +// 'resource_type' => get_class($originalResource), +// ], +// [ +// 'fs_path' => $source, +// 'mount_path' => $target, +// 'content' => $content, +// 'is_directory' => $isDirectory, +// 'resource_id' => $originalResource->id, +// 'resource_type' => get_class($originalResource), +// ] +// ); +// if (isDev()) { +// if ((int) $resource->compose_parsing_version >= 4) { +// if ($isApplication) { +// $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid); +// } elseif ($isService) { +// $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/services/'.$uuid); +// } +// } else { +// $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid); +// } +// } +// $volume = "$source:$target"; +// } +// } elseif ($type->value() === 'volume') { +// if ($topLevel->get('volumes')->has($source->value())) { +// $temp = $topLevel->get('volumes')->get($source->value()); +// if (data_get($temp, 'driver_opts.type') === 'cifs') { +// continue; +// } +// if (data_get($temp, 'driver_opts.type') === 'nfs') { +// continue; +// } +// } +// $slugWithoutUuid = Str::slug($source, '-'); +// $name = "{$uuid}_{$slugWithoutUuid}"; - if ($isApplication && $isPullRequest) { - $name = "{$name}-pr-$pullRequestId"; - } - if (is_string($volume)) { - $source = str($volume)->before(':'); - $target = str($volume)->after(':')->beforeLast(':'); - $source = $name; - $volume = "$source:$target"; - } elseif (is_array($volume)) { - data_set($volume, 'source', $name); - } - $topLevel->get('volumes')->put($name, [ - 'name' => $name, - ]); - LocalPersistentVolume::updateOrCreate( - [ - 'name' => $name, - 'resource_id' => $originalResource->id, - 'resource_type' => get_class($originalResource), - ], - [ - 'name' => $name, - 'mount_path' => $target, - 'resource_id' => $originalResource->id, - 'resource_type' => get_class($originalResource), - ] - ); - } - dispatch(new ServerFilesFromServerJob($originalResource)); - $volumesParsed->put($index, $volume); - } - } +// if ($isApplication && $isPullRequest) { +// $name = "{$name}-pr-$pullRequestId"; +// } +// if (is_string($volume)) { +// $source = str($volume)->before(':'); +// $target = str($volume)->after(':')->beforeLast(':'); +// $source = $name; +// $volume = "$source:$target"; +// } elseif (is_array($volume)) { +// data_set($volume, 'source', $name); +// } +// $topLevel->get('volumes')->put($name, [ +// 'name' => $name, +// ]); +// LocalPersistentVolume::updateOrCreate( +// [ +// 'name' => $name, +// 'resource_id' => $originalResource->id, +// 'resource_type' => get_class($originalResource), +// ], +// [ +// 'name' => $name, +// 'mount_path' => $target, +// 'resource_id' => $originalResource->id, +// 'resource_type' => get_class($originalResource), +// ] +// ); +// } +// dispatch(new ServerFilesFromServerJob($originalResource)); +// $volumesParsed->put($index, $volume); +// } +// } - if ($depends_on?->count() > 0) { - if ($isApplication && $isPullRequest) { - $newDependsOn = collect([]); - $depends_on->each(function ($dependency, $condition) use ($pullRequestId, $newDependsOn) { - if (is_numeric($condition)) { - $dependency = "$dependency-pr-$pullRequestId"; +// if ($depends_on?->count() > 0) { +// if ($isApplication && $isPullRequest) { +// $newDependsOn = collect([]); +// $depends_on->each(function ($dependency, $condition) use ($pullRequestId, $newDependsOn) { +// if (is_numeric($condition)) { +// $dependency = "$dependency-pr-$pullRequestId"; - $newDependsOn->put($condition, $dependency); - } else { - $condition = "$condition-pr-$pullRequestId"; - $newDependsOn->put($condition, $dependency); - } - }); - $depends_on = $newDependsOn; - } - } - if (! $use_network_mode) { - if ($topLevel->get('networks')?->count() > 0) { - foreach ($topLevel->get('networks') as $networkName => $network) { - if ($networkName === 'default') { - continue; - } - // ignore aliases - if ($network['aliases'] ?? false) { - continue; - } - $networkExists = $networks->contains(function ($value, $key) use ($networkName) { - return $value == $networkName || $key == $networkName; - }); - if (! $networkExists) { - $networks->put($networkName, null); - } - } - } - $baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) { - return $value == $baseNetwork; - }); - if (! $baseNetworkExists) { - foreach ($baseNetwork as $network) { - $topLevel->get('networks')->put($network, [ - 'name' => $network, - 'external' => true, - ]); - } - } - } +// $newDependsOn->put($condition, $dependency); +// } else { +// $condition = "$condition-pr-$pullRequestId"; +// $newDependsOn->put($condition, $dependency); +// } +// }); +// $depends_on = $newDependsOn; +// } +// } +// if (! $use_network_mode) { +// if ($topLevel->get('networks')?->count() > 0) { +// foreach ($topLevel->get('networks') as $networkName => $network) { +// if ($networkName === 'default') { +// continue; +// } +// // ignore aliases +// if ($network['aliases'] ?? false) { +// continue; +// } +// $networkExists = $networks->contains(function ($value, $key) use ($networkName) { +// return $value == $networkName || $key == $networkName; +// }); +// if (! $networkExists) { +// $networks->put($networkName, null); +// } +// } +// } +// $baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) { +// return $value == $baseNetwork; +// }); +// if (! $baseNetworkExists) { +// foreach ($baseNetwork as $network) { +// $topLevel->get('networks')->put($network, [ +// 'name' => $network, +// 'external' => true, +// ]); +// } +// } +// } - // Collect/create/update ports - $collectedPorts = collect([]); - if ($ports->count() > 0) { - foreach ($ports as $sport) { - if (is_string($sport) || is_numeric($sport)) { - $collectedPorts->push($sport); - } - if (is_array($sport)) { - $target = data_get($sport, 'target'); - $published = data_get($sport, 'published'); - $protocol = data_get($sport, 'protocol'); - $collectedPorts->push("$target:$published/$protocol"); - } - } - } - if ($isService) { - $originalResource->ports = $collectedPorts->implode(','); - $originalResource->save(); - } +// // Collect/create/update ports +// $collectedPorts = collect([]); +// if ($ports->count() > 0) { +// foreach ($ports as $sport) { +// if (is_string($sport) || is_numeric($sport)) { +// $collectedPorts->push($sport); +// } +// if (is_array($sport)) { +// $target = data_get($sport, 'target'); +// $published = data_get($sport, 'published'); +// $protocol = data_get($sport, 'protocol'); +// $collectedPorts->push("$target:$published/$protocol"); +// } +// } +// } +// if ($isService) { +// $originalResource->ports = $collectedPorts->implode(','); +// $originalResource->save(); +// } - $networks_temp = collect(); +// $networks_temp = collect(); - if (! $use_network_mode) { - foreach ($networks as $key => $network) { - if (gettype($network) === 'string') { - // networks: - // - appwrite - $networks_temp->put($network, null); - } elseif (gettype($network) === 'array') { - // networks: - // default: - // ipv4_address: 192.168.203.254 - $networks_temp->put($key, $network); - } - } - foreach ($baseNetwork as $key => $network) { - $networks_temp->put($network, null); - } +// if (! $use_network_mode) { +// foreach ($networks as $key => $network) { +// if (gettype($network) === 'string') { +// // networks: +// // - appwrite +// $networks_temp->put($network, null); +// } elseif (gettype($network) === 'array') { +// // networks: +// // default: +// // ipv4_address: 192.168.203.254 +// $networks_temp->put($key, $network); +// } +// } +// foreach ($baseNetwork as $key => $network) { +// $networks_temp->put($network, null); +// } - if ($isApplication) { - if (data_get($resource, 'settings.connect_to_docker_network')) { - $network = $resource->destination->network; - $networks_temp->put($network, null); - $topLevel->get('networks')->put($network, [ - 'name' => $network, - 'external' => true, - ]); - } - } - } +// if ($isApplication) { +// if (data_get($resource, 'settings.connect_to_docker_network')) { +// $network = $resource->destination->network; +// $networks_temp->put($network, null); +// $topLevel->get('networks')->put($network, [ +// 'name' => $network, +// 'external' => true, +// ]); +// } +// } +// } - $normalEnvironments = $environment->diffKeys($allMagicEnvironments); - $normalEnvironments = $normalEnvironments->filter(function ($value, $key) { - return ! str($value)->startsWith('SERVICE_'); - }); - foreach ($normalEnvironments as $key => $value) { - $key = str($key); - $value = str($value); - $originalValue = $value; - $parsedValue = replaceVariables($value); - if ($value->startsWith('$SERVICE_')) { - $resource->environment_variables()->firstOrCreate([ - 'key' => $key, - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $value, - 'is_build_time' => false, - 'is_preview' => false, - ]); +// $normalEnvironments = $environment->diffKeys($allMagicEnvironments); +// $normalEnvironments = $normalEnvironments->filter(function ($value, $key) { +// return ! str($value)->startsWith('SERVICE_'); +// }); +// foreach ($normalEnvironments as $key => $value) { +// $key = str($key); +// $value = str($value); +// $originalValue = $value; +// $parsedValue = replaceVariables($value); +// if ($value->startsWith('$SERVICE_')) { +// $resource->environment_variables()->firstOrCreate([ +// 'key' => $key, +// 'resourceable_type' => get_class($resource), +// 'resourceable_id' => $resource->id, +// ], [ +// 'value' => $value, +// 'is_build_time' => false, +// 'is_preview' => false, +// ]); - continue; - } - if (! $value->startsWith('$')) { - continue; - } - if ($key->value() === $parsedValue->value()) { - $value = null; - $resource->environment_variables()->firstOrCreate([ - 'key' => $key, - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $value, - 'is_build_time' => false, - 'is_preview' => false, - ]); - } else { - if ($value->startsWith('$')) { - $isRequired = false; - if ($value->contains(':-')) { - $value = replaceVariables($value); - $key = $value->before(':'); - $value = $value->after(':-'); - } elseif ($value->contains('-')) { - $value = replaceVariables($value); +// continue; +// } +// if (! $value->startsWith('$')) { +// continue; +// } +// if ($key->value() === $parsedValue->value()) { +// $value = null; +// $resource->environment_variables()->firstOrCreate([ +// 'key' => $key, +// 'resourceable_type' => get_class($resource), +// 'resourceable_id' => $resource->id, +// ], [ +// 'value' => $value, +// 'is_build_time' => false, +// 'is_preview' => false, +// ]); +// } else { +// if ($value->startsWith('$')) { +// $isRequired = false; +// if ($value->contains(':-')) { +// $value = replaceVariables($value); +// $key = $value->before(':'); +// $value = $value->after(':-'); +// } elseif ($value->contains('-')) { +// $value = replaceVariables($value); - $key = $value->before('-'); - $value = $value->after('-'); - } elseif ($value->contains(':?')) { - $value = replaceVariables($value); +// $key = $value->before('-'); +// $value = $value->after('-'); +// } elseif ($value->contains(':?')) { +// $value = replaceVariables($value); - $key = $value->before(':'); - $value = $value->after(':?'); - $isRequired = true; - } elseif ($value->contains('?')) { - $value = replaceVariables($value); +// $key = $value->before(':'); +// $value = $value->after(':?'); +// $isRequired = true; +// } elseif ($value->contains('?')) { +// $value = replaceVariables($value); - $key = $value->before('?'); - $value = $value->after('?'); - $isRequired = true; - } - if ($originalValue->value() === $value->value()) { - // This means the variable does not have a default value, so it needs to be created in Coolify - $parsedKeyValue = replaceVariables($value); - $resource->environment_variables()->firstOrCreate([ - 'key' => $parsedKeyValue, - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'is_build_time' => false, - 'is_preview' => false, - 'is_required' => $isRequired, - ]); - // Add the variable to the environment so it will be shown in the deployable compose file - // $environment[$parsedKeyValue->value()] = $resource->environment_variables()->where('key', $parsedKeyValue)->where('resourceable_type', get_class($resource))->where('resourceable_id', $resource->id)->first()->real_value; - $environment[$parsedKeyValue->value()] = $value; +// $key = $value->before('?'); +// $value = $value->after('?'); +// $isRequired = true; +// } +// if ($originalValue->value() === $value->value()) { +// // This means the variable does not have a default value, so it needs to be created in Coolify +// $parsedKeyValue = replaceVariables($value); +// $resource->environment_variables()->firstOrCreate([ +// 'key' => $parsedKeyValue, +// 'resourceable_type' => get_class($resource), +// 'resourceable_id' => $resource->id, +// ], [ +// 'is_build_time' => false, +// 'is_preview' => false, +// 'is_required' => $isRequired, +// ]); +// // Add the variable to the environment so it will be shown in the deployable compose file +// // $environment[$parsedKeyValue->value()] = $resource->environment_variables()->where('key', $parsedKeyValue)->where('resourceable_type', get_class($resource))->where('resourceable_id', $resource->id)->first()->real_value; +// $environment[$parsedKeyValue->value()] = $value; - continue; - } - $resource->environment_variables()->firstOrCreate([ - 'key' => $key, - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $value, - 'is_build_time' => false, - 'is_preview' => false, - 'is_required' => $isRequired, - ]); - } - } - } - if ($isApplication) { - $branch = $originalResource->git_branch; - if ($pullRequestId !== 0) { - $branch = "pull/{$pullRequestId}/head"; - } - if ($originalResource->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) { - $coolifyEnvironments->put('COOLIFY_BRANCH', "\"{$branch}\""); - } - } +// continue; +// } +// $resource->environment_variables()->firstOrCreate([ +// 'key' => $key, +// 'resourceable_type' => get_class($resource), +// 'resourceable_id' => $resource->id, +// ], [ +// 'value' => $value, +// 'is_build_time' => false, +// 'is_preview' => false, +// 'is_required' => $isRequired, +// ]); +// } +// } +// } +// if ($isApplication) { +// $branch = $originalResource->git_branch; +// if ($pullRequestId !== 0) { +// $branch = "pull/{$pullRequestId}/head"; +// } +// if ($originalResource->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) { +// $coolifyEnvironments->put('COOLIFY_BRANCH', "\"{$branch}\""); +// } +// } - // Add COOLIFY_RESOURCE_UUID to environment - if ($resource->environment_variables->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) { - $coolifyEnvironments->put('COOLIFY_RESOURCE_UUID', "{$resource->uuid}"); - } +// // Add COOLIFY_RESOURCE_UUID to environment +// if ($resource->environment_variables->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) { +// $coolifyEnvironments->put('COOLIFY_RESOURCE_UUID', "{$resource->uuid}"); +// } - // Add COOLIFY_CONTAINER_NAME to environment - if ($resource->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { - $coolifyEnvironments->put('COOLIFY_CONTAINER_NAME', "{$containerName}"); - } +// // Add COOLIFY_CONTAINER_NAME to environment +// if ($resource->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { +// $coolifyEnvironments->put('COOLIFY_CONTAINER_NAME', "{$containerName}"); +// } - if ($isApplication) { - if ($isPullRequest) { - $preview = $resource->previews()->find($preview_id); - $domains = collect(json_decode(data_get($preview, 'docker_compose_domains'))) ?? collect([]); - } else { - $domains = collect(json_decode($resource->docker_compose_domains)) ?? collect([]); - } - $fqdns = data_get($domains, "$serviceName.domain"); - // Generate SERVICE_FQDN & SERVICE_URL for dockercompose - if ($resource->build_pack === 'dockercompose') { +// if ($isApplication) { +// if ($isPullRequest) { +// $preview = $resource->previews()->find($preview_id); +// $domains = collect(json_decode(data_get($preview, 'docker_compose_domains'))) ?? collect([]); +// } else { +// $domains = collect(json_decode($resource->docker_compose_domains)) ?? collect([]); +// } +// $fqdns = data_get($domains, "$serviceName.domain"); +// // Generate SERVICE_FQDN & SERVICE_URL for dockercompose +// if ($resource->build_pack === 'dockercompose') { - foreach ($domains as $forServiceName => $domain) { - $parsedDomain = data_get($domain, 'domain'); - if (filled($parsedDomain)) { - $parsedDomain = str($parsedDomain)->explode(',')->first(); - $coolifyUrl = Url::fromString($parsedDomain); - $coolifyScheme = $coolifyUrl->getScheme(); - $coolifyFqdn = $coolifyUrl->getHost(); - $coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null); - $coolifyEnvironments->put('SERVICE_URL_'.str($forServiceName)->upper()->replace('-', '_'), $coolifyUrl->__toString()); - $coolifyEnvironments->put('SERVICE_FQDN_'.str($forServiceName)->upper()->replace('-', '_'), $coolifyFqdn); - } - } - } - // If the domain is set, we need to generate the FQDNs for the preview - if (filled($fqdns)) { - $fqdns = str($fqdns)->explode(','); - if ($isPullRequest) { - $preview = $resource->previews()->find($preview_id); - $docker_compose_domains = collect(json_decode(data_get($preview, 'docker_compose_domains'))); - if ($docker_compose_domains->count() > 0) { - $found_fqdn = data_get($docker_compose_domains, "$serviceName.domain"); - if ($found_fqdn) { - $fqdns = collect($found_fqdn); - } else { - $fqdns = collect([]); - } - } else { - $fqdns = $fqdns->map(function ($fqdn) use ($pullRequestId, $resource) { - $preview = ApplicationPreview::findPreviewByApplicationAndPullId($resource->id, $pullRequestId); - $url = Url::fromString($fqdn); - $template = $resource->preview_url_template; - $host = $url->getHost(); - $schema = $url->getScheme(); - $random = new Cuid2; - $preview_fqdn = str_replace('{{random}}', $random, $template); - $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); - $preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn); - $preview_fqdn = "$schema://$preview_fqdn"; - $preview->fqdn = $preview_fqdn; - $preview->save(); +// foreach ($domains as $forServiceName => $domain) { +// $parsedDomain = data_get($domain, 'domain'); +// if (filled($parsedDomain)) { +// $parsedDomain = str($parsedDomain)->explode(',')->first(); +// $coolifyUrl = Url::fromString($parsedDomain); +// $coolifyScheme = $coolifyUrl->getScheme(); +// $coolifyFqdn = $coolifyUrl->getHost(); +// $coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null); +// $coolifyEnvironments->put('SERVICE_URL_'.str($forServiceName)->upper()->replace('-', '_'), $coolifyUrl->__toString()); +// $coolifyEnvironments->put('SERVICE_FQDN_'.str($forServiceName)->upper()->replace('-', '_'), $coolifyFqdn); +// } +// } +// } +// // If the domain is set, we need to generate the FQDNs for the preview +// if (filled($fqdns)) { +// $fqdns = str($fqdns)->explode(','); +// if ($isPullRequest) { +// $preview = $resource->previews()->find($preview_id); +// $docker_compose_domains = collect(json_decode(data_get($preview, 'docker_compose_domains'))); +// if ($docker_compose_domains->count() > 0) { +// $found_fqdn = data_get($docker_compose_domains, "$serviceName.domain"); +// if ($found_fqdn) { +// $fqdns = collect($found_fqdn); +// } else { +// $fqdns = collect([]); +// } +// } else { +// $fqdns = $fqdns->map(function ($fqdn) use ($pullRequestId, $resource) { +// $preview = ApplicationPreview::findPreviewByApplicationAndPullId($resource->id, $pullRequestId); +// $url = Url::fromString($fqdn); +// $template = $resource->preview_url_template; +// $host = $url->getHost(); +// $schema = $url->getScheme(); +// $random = new Cuid2; +// $preview_fqdn = str_replace('{{random}}', $random, $template); +// $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); +// $preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn); +// $preview_fqdn = "$schema://$preview_fqdn"; +// $preview->fqdn = $preview_fqdn; +// $preview->save(); - return $preview_fqdn; - }); - } - } - } - $defaultLabels = defaultLabels( - id: $resource->id, - name: $containerName, - projectName: $resource->project()->name, - resourceName: $resource->name, - pull_request_id: $pullRequestId, - type: 'application', - environment: $resource->environment->name, - ); +// return $preview_fqdn; +// }); +// } +// } +// } +// $defaultLabels = defaultLabels( +// id: $resource->id, +// name: $containerName, +// projectName: $resource->project()->name, +// resourceName: $resource->name, +// pull_request_id: $pullRequestId, +// type: 'application', +// environment: $resource->environment->name, +// ); - } elseif ($isService) { - if ($savedService->serviceType()) { - $fqdns = generateServiceSpecificFqdns($savedService); - } else { - $fqdns = collect(data_get($savedService, 'fqdns'))->filter(); - } - $defaultLabels = defaultLabels( - id: $resource->id, - name: $containerName, - projectName: $resource->project()->name, - resourceName: $resource->name, - type: 'service', - subType: $isDatabase ? 'database' : 'application', - subId: $savedService->id, - subName: $savedService->human_name ?? $savedService->name, - environment: $resource->environment->name, - ); - } - // Add COOLIFY_FQDN & COOLIFY_URL to environment - if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { - $fqdnsWithoutPort = $fqdns->map(function ($fqdn) { - return str($fqdn)->after('://')->before(':')->prepend(str($fqdn)->before('://')->append('://')); - }); - $coolifyEnvironments->put('COOLIFY_URL', $fqdnsWithoutPort->implode(',')); +// } elseif ($isService) { +// if ($savedService->serviceType()) { +// $fqdns = generateServiceSpecificFqdns($savedService); +// } else { +// $fqdns = collect(data_get($savedService, 'fqdns'))->filter(); +// } +// $defaultLabels = defaultLabels( +// id: $resource->id, +// name: $containerName, +// projectName: $resource->project()->name, +// resourceName: $resource->name, +// type: 'service', +// subType: $isDatabase ? 'database' : 'application', +// subId: $savedService->id, +// subName: $savedService->human_name ?? $savedService->name, +// environment: $resource->environment->name, +// ); +// } +// // Add COOLIFY_FQDN & COOLIFY_URL to environment +// if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { +// $fqdnsWithoutPort = $fqdns->map(function ($fqdn) { +// return str($fqdn)->after('://')->before(':')->prepend(str($fqdn)->before('://')->append('://')); +// }); +// $coolifyEnvironments->put('COOLIFY_URL', $fqdnsWithoutPort->implode(',')); - $urls = $fqdns->map(function ($fqdn) { - return str($fqdn)->replace('http://', '')->replace('https://', '')->before(':'); - }); - $coolifyEnvironments->put('COOLIFY_FQDN', $urls->implode(',')); - } - add_coolify_default_environment_variables($resource, $coolifyEnvironments, $resource->environment_variables); - if ($environment->count() > 0) { - $environment = $environment->filter(function ($value, $key) { - return ! str($key)->startsWith('SERVICE_FQDN_'); - })->map(function ($value, $key) use ($resource) { - // if value is empty, set it to null so if you set the environment variable in the .env file (Coolify's UI), it will used - if (str($value)->isEmpty()) { - if ($resource->environment_variables()->where('key', $key)->exists()) { - $value = $resource->environment_variables()->where('key', $key)->first()->value; - } else { - $value = null; - } - } +// $urls = $fqdns->map(function ($fqdn) { +// return str($fqdn)->replace('http://', '')->replace('https://', '')->before(':'); +// }); +// $coolifyEnvironments->put('COOLIFY_FQDN', $urls->implode(',')); +// } +// add_coolify_default_environment_variables($resource, $coolifyEnvironments, $resource->environment_variables); +// if ($environment->count() > 0) { +// $environment = $environment->filter(function ($value, $key) { +// return ! str($key)->startsWith('SERVICE_FQDN_'); +// })->map(function ($value, $key) use ($resource) { +// // if value is empty, set it to null so if you set the environment variable in the .env file (Coolify's UI), it will used +// if (str($value)->isEmpty()) { +// if ($resource->environment_variables()->where('key', $key)->exists()) { +// $value = $resource->environment_variables()->where('key', $key)->first()->value; +// } else { +// $value = null; +// } +// } - return $value; - }); - } - $serviceLabels = $labels->merge($defaultLabels); - if ($serviceLabels->count() > 0) { - if ($isApplication) { - $isContainerLabelEscapeEnabled = data_get($resource, 'settings.is_container_label_escape_enabled'); - } else { - $isContainerLabelEscapeEnabled = data_get($resource, 'is_container_label_escape_enabled'); - } - if ($isContainerLabelEscapeEnabled) { - $serviceLabels = $serviceLabels->map(function ($value, $key) { - return escapeDollarSign($value); - }); - } - } - if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { - if ($isApplication) { - $shouldGenerateLabelsExactly = $resource->destination->server->settings->generate_exact_labels; - $uuid = $resource->uuid; - $network = data_get($resource, 'destination.network'); - if ($isPullRequest) { - $uuid = "{$resource->uuid}-{$pullRequestId}"; - } - if ($isPullRequest) { - $network = "{$resource->destination->network}-{$pullRequestId}"; - } - } else { - $shouldGenerateLabelsExactly = $resource->server->settings->generate_exact_labels; - $uuid = $resource->uuid; - $network = data_get($resource, 'destination.network'); - } - if ($shouldGenerateLabelsExactly) { - switch ($server->proxyType()) { - case ProxyTypes::TRAEFIK->value: - $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( - uuid: $uuid, - domains: $fqdns, - is_force_https_enabled: true, - serviceLabels: $serviceLabels, - is_gzip_enabled: $originalResource->isGzipEnabled(), - is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), - service_name: $serviceName, - image: $image - )); - break; - case ProxyTypes::CADDY->value: - $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( - network: $network, - uuid: $uuid, - domains: $fqdns, - is_force_https_enabled: true, - serviceLabels: $serviceLabels, - is_gzip_enabled: $originalResource->isGzipEnabled(), - is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), - service_name: $serviceName, - image: $image, - predefinedPort: $predefinedPort - )); - break; - } - } else { - $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( - uuid: $uuid, - domains: $fqdns, - is_force_https_enabled: true, - serviceLabels: $serviceLabels, - is_gzip_enabled: $originalResource->isGzipEnabled(), - is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), - service_name: $serviceName, - image: $image - )); - $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( - network: $network, - uuid: $uuid, - domains: $fqdns, - is_force_https_enabled: true, - serviceLabels: $serviceLabels, - is_gzip_enabled: $originalResource->isGzipEnabled(), - is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), - service_name: $serviceName, - image: $image, - predefinedPort: $predefinedPort - )); - } - } - if ($isService) { - if (data_get($service, 'restart') === 'no' || data_get($service, 'exclude_from_hc')) { - $savedService->update(['exclude_from_status' => true]); - } - } - data_forget($service, 'volumes.*.content'); - data_forget($service, 'volumes.*.isDirectory'); - data_forget($service, 'volumes.*.is_directory'); - data_forget($service, 'exclude_from_hc'); +// return $value; +// }); +// } +// $serviceLabels = $labels->merge($defaultLabels); +// if ($serviceLabels->count() > 0) { +// if ($isApplication) { +// $isContainerLabelEscapeEnabled = data_get($resource, 'settings.is_container_label_escape_enabled'); +// } else { +// $isContainerLabelEscapeEnabled = data_get($resource, 'is_container_label_escape_enabled'); +// } +// if ($isContainerLabelEscapeEnabled) { +// $serviceLabels = $serviceLabels->map(function ($value, $key) { +// return escapeDollarSign($value); +// }); +// } +// } +// if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { +// if ($isApplication) { +// $shouldGenerateLabelsExactly = $resource->destination->server->settings->generate_exact_labels; +// $uuid = $resource->uuid; +// $network = data_get($resource, 'destination.network'); +// if ($isPullRequest) { +// $uuid = "{$resource->uuid}-{$pullRequestId}"; +// } +// if ($isPullRequest) { +// $network = "{$resource->destination->network}-{$pullRequestId}"; +// } +// } else { +// $shouldGenerateLabelsExactly = $resource->server->settings->generate_exact_labels; +// $uuid = $resource->uuid; +// $network = data_get($resource, 'destination.network'); +// } +// if ($shouldGenerateLabelsExactly) { +// switch ($server->proxyType()) { +// case ProxyTypes::TRAEFIK->value: +// $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( +// uuid: $uuid, +// domains: $fqdns, +// is_force_https_enabled: true, +// serviceLabels: $serviceLabels, +// is_gzip_enabled: $originalResource->isGzipEnabled(), +// is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), +// service_name: $serviceName, +// image: $image +// )); +// break; +// case ProxyTypes::CADDY->value: +// $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( +// network: $network, +// uuid: $uuid, +// domains: $fqdns, +// is_force_https_enabled: true, +// serviceLabels: $serviceLabels, +// is_gzip_enabled: $originalResource->isGzipEnabled(), +// is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), +// service_name: $serviceName, +// image: $image, +// predefinedPort: $predefinedPort +// )); +// break; +// } +// } else { +// $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( +// uuid: $uuid, +// domains: $fqdns, +// is_force_https_enabled: true, +// serviceLabels: $serviceLabels, +// is_gzip_enabled: $originalResource->isGzipEnabled(), +// is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), +// service_name: $serviceName, +// image: $image +// )); +// $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( +// network: $network, +// uuid: $uuid, +// domains: $fqdns, +// is_force_https_enabled: true, +// serviceLabels: $serviceLabels, +// is_gzip_enabled: $originalResource->isGzipEnabled(), +// is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), +// service_name: $serviceName, +// image: $image, +// predefinedPort: $predefinedPort +// )); +// } +// } +// if ($isService) { +// if (data_get($service, 'restart') === 'no' || data_get($service, 'exclude_from_hc')) { +// $savedService->update(['exclude_from_status' => true]); +// } +// } +// data_forget($service, 'volumes.*.content'); +// data_forget($service, 'volumes.*.isDirectory'); +// data_forget($service, 'volumes.*.is_directory'); +// data_forget($service, 'exclude_from_hc'); - $volumesParsed = $volumesParsed->map(function ($volume) { - data_forget($volume, 'content'); - data_forget($volume, 'is_directory'); - data_forget($volume, 'isDirectory'); +// $volumesParsed = $volumesParsed->map(function ($volume) { +// data_forget($volume, 'content'); +// data_forget($volume, 'is_directory'); +// data_forget($volume, 'isDirectory'); - return $volume; - }); +// return $volume; +// }); - $payload = collect($service)->merge([ - 'container_name' => $containerName, - 'restart' => $restart->value(), - 'labels' => $serviceLabels, - ]); - if (! $use_network_mode) { - $payload['networks'] = $networks_temp; - } - if ($ports->count() > 0) { - $payload['ports'] = $ports; - } - if ($volumesParsed->count() > 0) { - $payload['volumes'] = $volumesParsed; - } - if ($environment->count() > 0 || $coolifyEnvironments->count() > 0) { - $payload['environment'] = $environment->merge($coolifyEnvironments); - } - if ($logging) { - $payload['logging'] = $logging; - } - if ($depends_on->count() > 0) { - $payload['depends_on'] = $depends_on; - } - if ($isApplication && $isPullRequest) { - $serviceName = "{$serviceName}-pr-{$pullRequestId}"; - } +// $payload = collect($service)->merge([ +// 'container_name' => $containerName, +// 'restart' => $restart->value(), +// 'labels' => $serviceLabels, +// ]); +// if (! $use_network_mode) { +// $payload['networks'] = $networks_temp; +// } +// if ($ports->count() > 0) { +// $payload['ports'] = $ports; +// } +// if ($volumesParsed->count() > 0) { +// $payload['volumes'] = $volumesParsed; +// } +// if ($environment->count() > 0 || $coolifyEnvironments->count() > 0) { +// $payload['environment'] = $environment->merge($coolifyEnvironments); +// } +// if ($logging) { +// $payload['logging'] = $logging; +// } +// if ($depends_on->count() > 0) { +// $payload['depends_on'] = $depends_on; +// } +// if ($isApplication && $isPullRequest) { +// $serviceName = "{$serviceName}-pr-{$pullRequestId}"; +// } - $parsedServices->put($serviceName, $payload); - } - $topLevel->put('services', $parsedServices); +// $parsedServices->put($serviceName, $payload); +// } +// $topLevel->put('services', $parsedServices); - $customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets']; +// $customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets']; - $topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) { - return array_search($key, $customOrder); - }); +// $topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) { +// return array_search($key, $customOrder); +// }); - $resource->docker_compose = Yaml::dump(convertToArray($topLevel), 10, 2); - data_forget($resource, 'environment_variables'); - data_forget($resource, 'environment_variables_preview'); - $resource->save(); +// $resource->docker_compose = Yaml::dump(convertToArray($topLevel), 10, 2); +// data_forget($resource, 'environment_variables'); +// data_forget($resource, 'environment_variables_preview'); +// $resource->save(); - return $topLevel; -} +// return $topLevel; +// } function generate_fluentd_configuration(): array { From 0e7cc988a6d7b091be0c903b700c245b120cce43 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 7 Aug 2025 21:57:00 +0200 Subject: [PATCH 051/367] feat(user): add changelog read tracking and unread count method --- app/Console/Commands/InitChangelog.php | 98 ++++++ app/Livewire/SettingsDropdown.php | 52 ++++ app/Models/User.php | 10 + app/Models/UserChangelogRead.php | 48 +++ app/Services/ChangelogService.php | 294 ++++++++++++++++++ config/services.php | 2 +- ...2403_create_user_changelog_reads_table.php | 34 ++ docker/production/Dockerfile | 1 + resources/views/components/navbar.blade.php | 187 +++++------ .../livewire/settings-dropdown.blade.php | 271 ++++++++++++++++ 10 files changed, 887 insertions(+), 110 deletions(-) create mode 100644 app/Console/Commands/InitChangelog.php create mode 100644 app/Livewire/SettingsDropdown.php create mode 100644 app/Models/UserChangelogRead.php create mode 100644 app/Services/ChangelogService.php create mode 100644 database/migrations/2025_08_07_142403_create_user_changelog_reads_table.php create mode 100644 resources/views/livewire/settings-dropdown.blade.php diff --git a/app/Console/Commands/InitChangelog.php b/app/Console/Commands/InitChangelog.php new file mode 100644 index 000000000..f9eb12f04 --- /dev/null +++ b/app/Console/Commands/InitChangelog.php @@ -0,0 +1,98 @@ +argument('month') ?: Carbon::now()->format('Y-m'); + + // Validate month format + if (! preg_match('/^\d{4}-(0[1-9]|1[0-2])$/', $month)) { + $this->error('Invalid month format. Use YYYY-MM format with valid months 01-12 (e.g., 2025-08)'); + + return self::FAILURE; + } + + $changelogsDir = base_path('changelogs'); + $filePath = $changelogsDir."/{$month}.json"; + + // Create changelogs directory if it doesn't exist + if (! is_dir($changelogsDir)) { + mkdir($changelogsDir, 0755, true); + $this->info("Created changelogs directory: {$changelogsDir}"); + } + + // Check if file already exists + if (file_exists($filePath)) { + if (! $this->confirm("File {$month}.json already exists. Overwrite?")) { + $this->info('Operation cancelled'); + + return self::SUCCESS; + } + } + + // Parse the month for example data + $carbonMonth = Carbon::createFromFormat('Y-m', $month); + $monthName = $carbonMonth->format('F Y'); + $sampleDate = $carbonMonth->addDays(14)->toISOString(); // Mid-month + + // Get version from config + $version = 'v'.config('constants.coolify.version'); + + // Create example changelog structure + $exampleData = [ + 'entries' => [ + [ + 'version' => $version, + 'title' => 'Example Feature Release', + 'content' => "This is an example changelog entry for {$monthName}. Replace this with your actual release notes. Include details about new features, improvements, bug fixes, and any breaking changes.", + 'published_at' => $sampleDate, + ], + ], + ]; + + // Write the file + $jsonContent = json_encode($exampleData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + + if (file_put_contents($filePath, $jsonContent) === false) { + $this->error("Failed to create changelog file: {$filePath}"); + + return self::FAILURE; + } + + $this->info("✅ Created changelog file: changelogs/{$month}.json"); + $this->line(" Example entry created for {$monthName}"); + $this->line(' Edit the file to add your actual changelog entries'); + + // Show the file contents + if ($this->option('verbose')) { + $this->newLine(); + $this->line('File contents:'); + $this->line($jsonContent); + } + + return self::SUCCESS; + } +} diff --git a/app/Livewire/SettingsDropdown.php b/app/Livewire/SettingsDropdown.php new file mode 100644 index 000000000..5bb3e37a4 --- /dev/null +++ b/app/Livewire/SettingsDropdown.php @@ -0,0 +1,52 @@ +getUnreadChangelogCount(); + } + + public function getEntriesProperty() + { + $user = Auth::user(); + + return app(ChangelogService::class)->getEntriesForUser($user); + } + + public function openWhatsNewModal() + { + $this->showWhatsNewModal = true; + } + + public function closeWhatsNewModal() + { + $this->showWhatsNewModal = false; + } + + public function markAsRead($identifier) + { + app(ChangelogService::class)->markAsReadForUser($identifier, Auth::user()); + } + + public function markAllAsRead() + { + app(ChangelogService::class)->markAllAsReadForUser(Auth::user()); + } + + public function render() + { + return view('livewire.settings-dropdown', [ + 'entries' => $this->entries, + 'unreadCount' => $this->unreadCount, + ]); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 6cd1b66db..3c5a220f8 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -203,6 +203,16 @@ class User extends Authenticatable implements SendsEmail return $this->belongsToMany(Team::class)->withPivot('role'); } + public function changelogReads() + { + return $this->hasMany(UserChangelogRead::class); + } + + public function getUnreadChangelogCount(): int + { + return app(\App\Services\ChangelogService::class)->getUnreadCountForUser($this); + } + public function getRecipients(): array { return [$this->email]; diff --git a/app/Models/UserChangelogRead.php b/app/Models/UserChangelogRead.php new file mode 100644 index 000000000..28c384cb5 --- /dev/null +++ b/app/Models/UserChangelogRead.php @@ -0,0 +1,48 @@ + 'datetime', + ]; + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public static function markAsRead(int $userId, string $identifier): void + { + self::firstOrCreate([ + 'user_id' => $userId, + 'changelog_identifier' => $identifier, + ], [ + 'read_at' => now(), + ]); + } + + public static function isReadByUser(int $userId, string $identifier): bool + { + return self::where('user_id', $userId) + ->where('changelog_identifier', $identifier) + ->exists(); + } + + public static function getReadIdentifiersForUser(int $userId): array + { + return self::where('user_id', $userId) + ->pluck('changelog_identifier') + ->toArray(); + } +} diff --git a/app/Services/ChangelogService.php b/app/Services/ChangelogService.php new file mode 100644 index 000000000..c6f4c8752 --- /dev/null +++ b/app/Services/ChangelogService.php @@ -0,0 +1,294 @@ +fetchChangelogData(); + + if (! $data || ! isset($data['entries'])) { + return collect(); + } + + return collect($data['entries']) + ->filter(fn ($entry) => $this->validateEntryData($entry)) + ->map(function ($entry) { + $entry['published_at'] = Carbon::parse($entry['published_at']); + $entry['content_html'] = $this->parseMarkdown($entry['content']); + + return (object) $entry; + }) + ->filter(fn ($entry) => $entry->published_at <= now()) + ->sortBy('published_at') + ->reverse() + ->values(); + } + + // Load entries from recent months for performance + $availableMonths = $this->getAvailableMonths(); + $monthsToLoad = $availableMonths->take($recentMonths); + + return $monthsToLoad + ->flatMap(fn ($month) => $this->getEntriesForMonth($month)) + ->sortBy('published_at') + ->reverse() + ->values(); + } + + public function getAllEntries(): Collection + { + $availableMonths = $this->getAvailableMonths(); + + return $availableMonths + ->flatMap(fn ($month) => $this->getEntriesForMonth($month)) + ->sortBy('published_at') + ->reverse() + ->values(); + } + + public function getEntriesForUser(User $user): Collection + { + $entries = $this->getEntries(); + $readIdentifiers = UserChangelogRead::getReadIdentifiersForUser($user->id); + + return $entries->map(function ($entry) use ($readIdentifiers) { + $entry->is_read = in_array($entry->version, $readIdentifiers); + + return $entry; + })->sortBy([ + ['is_read', 'asc'], // unread first + ['published_at', 'desc'], // then by date + ])->values(); + } + + public function getUnreadCountForUser(User $user): int + { + if (isDev()) { + $entries = $this->getEntries(); + $readIdentifiers = UserChangelogRead::getReadIdentifiersForUser($user->id); + + return $entries->reject(fn ($entry) => in_array($entry->version, $readIdentifiers))->count(); + } else { + return Cache::remember( + 'user_unread_changelog_count_'.$user->id, + now()->addHour(), + function () use ($user) { + $entries = $this->getEntries(); + $readIdentifiers = UserChangelogRead::getReadIdentifiersForUser($user->id); + + return $entries->reject(fn ($entry) => in_array($entry->version, $readIdentifiers))->count(); + } + ); + } + } + + public function getAvailableMonths(): Collection + { + $pattern = base_path('changelogs/*.json'); + $files = glob($pattern); + + if ($files === false) { + return collect(); + } + + return collect($files) + ->map(fn ($file) => basename($file, '.json')) + ->filter(fn ($name) => preg_match('/^\d{4}-\d{2}$/', $name)) + ->sort() + ->reverse() + ->values(); + } + + public function getEntriesForMonth(string $month): Collection + { + $path = base_path("changelogs/{$month}.json"); + + if (! file_exists($path)) { + return collect(); + } + + $content = file_get_contents($path); + + if ($content === false) { + Log::error("Failed to read changelog file: {$month}.json"); + + return collect(); + } + + $data = json_decode($content, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + Log::error("Invalid JSON in {$month}.json: ".json_last_error_msg()); + + return collect(); + } + + if (! isset($data['entries']) || ! is_array($data['entries'])) { + return collect(); + } + + return collect($data['entries']) + ->filter(fn ($entry) => $this->validateEntryData($entry)) + ->map(function ($entry) { + $entry['published_at'] = Carbon::parse($entry['published_at']); + $entry['content_html'] = $this->parseMarkdown($entry['content']); + + return (object) $entry; + }) + ->filter(fn ($entry) => $entry->published_at <= now()) + ->sortBy('published_at') + ->reverse() + ->values(); + } + + private function fetchChangelogData(): ?array + { + // Legacy support for old changelog.json + $path = base_path('changelog.json'); + + if (file_exists($path)) { + $content = file_get_contents($path); + + if ($content === false) { + Log::error('Failed to read changelog.json file'); + + return null; + } + + $data = json_decode($content, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + Log::error('Invalid JSON in changelog.json: '.json_last_error_msg()); + + return null; + } + + return $data; + } + + // New monthly structure - combine all months + $allEntries = []; + foreach ($this->getAvailableMonths() as $month) { + $monthEntries = $this->getEntriesForMonth($month); + foreach ($monthEntries as $entry) { + $allEntries[] = (array) $entry; + } + } + + return ['entries' => $allEntries]; + } + + public function markAsReadForUser(string $version, User $user): void + { + UserChangelogRead::markAsRead($user->id, $version); + Cache::forget('user_unread_changelog_count_'.$user->id); + } + + public function markAllAsReadForUser(User $user): void + { + $entries = $this->getEntries(); + + foreach ($entries as $entry) { + UserChangelogRead::markAsRead($user->id, $entry->version); + } + + Cache::forget('user_unread_changelog_count_'.$user->id); + } + + private function validateEntryData(array $data): bool + { + $required = ['version', 'title', 'content', 'published_at']; + + foreach ($required as $field) { + if (! isset($data[$field]) || empty($data[$field])) { + return false; + } + } + + return true; + } + + public function clearAllReadStatus(): array + { + try { + $count = UserChangelogRead::count(); + UserChangelogRead::truncate(); + + // Clear all user caches + $this->clearAllUserCaches(); + + return [ + 'success' => true, + 'message' => "Successfully cleared {$count} read status records", + ]; + } catch (\Exception $e) { + Log::error('Failed to clear read status: '.$e->getMessage()); + + return [ + 'success' => false, + 'message' => 'Failed to clear read status: '.$e->getMessage(), + ]; + } + } + + private function clearAllUserCaches(): void + { + $users = User::select('id')->get(); + + foreach ($users as $user) { + Cache::forget('user_unread_changelog_count_'.$user->id); + } + } + + private function parseMarkdown(string $content): string + { + // Convert markdown to HTML using simple regex patterns + $html = $content; + + // Headers + $html = preg_replace('/^### (.*?)$/m', '

$1

', $html); + $html = preg_replace('/^## (.*?)$/m', '

$1

', $html); + $html = preg_replace('/^# (.*?)$/m', '

$1

', $html); + + // Bold text + $html = preg_replace('/\*\*(.*?)\*\*/', '$1', $html); + $html = preg_replace('/__(.*?)__/', '$1', $html); + + // Italic text + $html = preg_replace('/\*(.*?)\*/', '$1', $html); + $html = preg_replace('/_(.*?)_/', '$1', $html); + + // Code blocks + $html = preg_replace('/```(.*?)```/s', '
$1
', $html); + + // Inline code + $html = preg_replace('/`([^`]+)`/', '$1', $html); + + // Links + $html = preg_replace('/\[([^\]]+)\]\(([^)]+)\)/', '$1', $html); + + // Line breaks (convert double newlines to paragraphs) + $paragraphs = preg_split('/\n\s*\n/', trim($html)); + $html = '

'.implode('

', $paragraphs).'

'; + + // Single line breaks + $html = preg_replace('/\n/', '
', $html); + + // Unordered lists + $html = preg_replace('/^\- (.*)$/m', '
  • • $1
  • ', $html); + $html = preg_replace('/(
  • .*<\/li>)/s', '
      $1
    ', $html); + + return $html; + } +} diff --git a/config/services.php b/config/services.php index 7add50a5c..6a21cda18 100644 --- a/config/services.php +++ b/config/services.php @@ -65,6 +65,6 @@ return [ 'client_secret' => env('ZITADEL_CLIENT_SECRET'), 'redirect' => env('ZITADEL_REDIRECT_URI'), 'base_url' => env('ZITADEL_BASE_URL'), - ] + ], ]; diff --git a/database/migrations/2025_08_07_142403_create_user_changelog_reads_table.php b/database/migrations/2025_08_07_142403_create_user_changelog_reads_table.php new file mode 100644 index 000000000..4c340d106 --- /dev/null +++ b/database/migrations/2025_08_07_142403_create_user_changelog_reads_table.php @@ -0,0 +1,34 @@ +id(); + $table->foreignId('user_id')->constrained()->onDelete('cascade'); + $table->string('changelog_identifier'); + $table->timestamp('read_at'); + $table->timestamps(); + + $table->unique(['user_id', 'changelog_identifier']); + $table->index('user_id'); + $table->index('changelog_identifier'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('user_changelog_reads'); + } +}; diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile index a2a4b5fa3..6c9628a81 100644 --- a/docker/production/Dockerfile +++ b/docker/production/Dockerfile @@ -120,6 +120,7 @@ COPY --chown=www-data:www-data templates ./templates COPY --chown=www-data:www-data resources/views ./resources/views COPY --chown=www-data:www-data artisan artisan COPY --chown=www-data:www-data openapi.yaml ./openapi.yaml +COPY --chown=www-data:www-data changelogs/ ./changelogs/ RUN composer dump-autoload diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index be26d55ca..1a145aa4b 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -1,119 +1,88 @@ -