From b997b7393b40f300ea1064cc84d304aeb8ae9911 Mon Sep 17 00:00:00 2001 From: Kael Date: Fri, 11 Oct 2024 02:44:52 +1100 Subject: [PATCH 01/51] feat: allow disabling default redirect, set status to 503 --- app/Listeners/ProxyStartedNotification.php | 2 +- app/Livewire/Server/Proxy.php | 16 +- app/Models/Server.php | 139 +++++++++--------- .../views/livewire/server/proxy.blade.php | 9 +- .../proxy/dynamic-configurations.blade.php | 2 +- 5 files changed, 91 insertions(+), 77 deletions(-) diff --git a/app/Listeners/ProxyStartedNotification.php b/app/Listeners/ProxyStartedNotification.php index d0541b162..9045b1e5c 100644 --- a/app/Listeners/ProxyStartedNotification.php +++ b/app/Listeners/ProxyStartedNotification.php @@ -14,7 +14,7 @@ class ProxyStartedNotification public function handle(ProxyStarted $event): void { $this->server = data_get($event, 'data'); - $this->server->setupDefault404Redirect(); + $this->server->setupDefaultRedirect(); $this->server->setupDynamicProxyConfiguration(); $this->server->proxy->force_stop = false; $this->server->save(); diff --git a/app/Livewire/Server/Proxy.php b/app/Livewire/Server/Proxy.php index 55d0c4966..fbdba53c1 100644 --- a/app/Livewire/Server/Proxy.php +++ b/app/Livewire/Server/Proxy.php @@ -16,6 +16,7 @@ class Proxy extends Component public $proxy_settings = null; + public bool $redirect_enabled = true; public ?string $redirect_url = null; protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit']; @@ -27,6 +28,7 @@ class Proxy extends Component public function mount() { $this->selectedProxy = $this->server->proxyType(); + $this->redirect_enabled = data_get($this->server, 'proxy.redirect_enabled', true); $this->redirect_url = data_get($this->server, 'proxy.redirect_url'); } @@ -65,13 +67,25 @@ class Proxy extends Component } } + public function instantSaveRedirect() + { + try { + $this->server->proxy->redirect_enabled = $this->redirect_enabled; + $this->server->save(); + $this->server->setupDefaultRedirect(); + $this->dispatch('success', 'Proxy configuration saved.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + public function submit() { try { SaveConfiguration::run($this->server, $this->proxy_settings); $this->server->proxy->redirect_url = $this->redirect_url; $this->server->save(); - $this->server->setupDefault404Redirect(); + $this->server->setupDefaultRedirect(); $this->dispatch('success', 'Proxy configuration saved.'); } catch (\Throwable $e) { return handleError($e, $this); diff --git a/app/Models/Server.php b/app/Models/Server.php index 8864deef1..0a92caa60 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -94,6 +94,14 @@ class Server extends BaseModel ]); } } + if (!isset($server->proxy->redirect_enabled)) { + $server->proxy->redirect_enabled = true; + } + }); + static::retrieved(function ($server) { + if (!isset($server->proxy->redirect_enabled)) { + $server->proxy->redirect_enabled = true; + } }); static::deleting(function ($server) { $server->destinations()->each(function ($destination) { @@ -164,70 +172,72 @@ class Server extends BaseModel return $this->proxyType() && $this->proxyType() !== 'NONE' && $this->isFunctional() && ! $this->isSwarmWorker() && ! $this->settings->is_build_server; } - public function setupDefault404Redirect() + public function setupDefaultRedirect() { + $banner = + "# This file is generated by Coolify, do not edit it manually.\n" . + "# Disable the default redirect to customize (only if you know what are you doing).\n\n"; $dynamic_conf_path = $this->proxyPath().'/dynamic'; $proxy_type = $this->proxyType(); + $redirect_enabled = $this->proxy->redirect_enabled ?? true; $redirect_url = $this->proxy->redirect_url; + if ($proxy_type === ProxyTypes::TRAEFIK->value) { - $default_redirect_file = "$dynamic_conf_path/default_redirect_404.yaml"; + $default_redirect_file = "$dynamic_conf_path/default_redirect_503.yaml"; } elseif ($proxy_type === ProxyTypes::CADDY->value) { - $default_redirect_file = "$dynamic_conf_path/default_redirect_404.caddy"; + $default_redirect_file = "$dynamic_conf_path/default_redirect_503.caddy"; } - if (empty($redirect_url)) { + + instant_remote_process([ + "mkdir -p $dynamic_conf_path", + "rm -f $dynamic_conf_path/default_redirect_404.yaml", + "rm -f $dynamic_conf_path/default_redirect_404.caddy", + ], $this); + + if (!$redirect_enabled) { + instant_remote_process(["rm -f $default_redirect_file"], $this); + } else { if ($proxy_type === ProxyTypes::CADDY->value) { - $conf = ':80, :443 { -respond 404 + if (empty($redirect_url)) { + $conf = ':80, :443 { + respond 503 }'; - $conf = - "# This file is automatically generated by Coolify.\n". - "# Do not edit it manually (only if you know what are you doing).\n\n". - $conf; - $base64 = base64_encode($conf); - instant_remote_process([ - "mkdir -p $dynamic_conf_path", - "echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null", - ], $this); - $this->reloadCaddy(); - - return; - } - instant_remote_process([ - "mkdir -p $dynamic_conf_path", - "rm -f $default_redirect_file", - ], $this); - - return; - } - if ($proxy_type === ProxyTypes::TRAEFIK->value) { - $dynamic_conf = [ - 'http' => [ - 'routers' => [ - 'catchall' => [ - 'entryPoints' => [ - 0 => 'http', - 1 => 'https', - ], - 'service' => 'noop', - 'rule' => 'HostRegexp(`{catchall:.*}`)', - 'priority' => 1, - 'middlewares' => [ - 0 => 'redirect-regexp@file', + } else { + $conf = ":80, :443 { + redir $redirect_url +}"; + } + } elseif ($proxy_type === ProxyTypes::TRAEFIK->value) { + $dynamic_conf = [ + 'http' => [ + 'routers' => [ + 'catchall' => [ + 'entryPoints' => [ + 0 => 'http', + 1 => 'https', + ], + 'service' => 'noop', + 'rule' => 'HostRegexp(`{catchall:.*}`)', + 'priority' => 1, ], ], - ], - 'services' => [ - 'noop' => [ - 'loadBalancer' => [ - 'servers' => [ - 0 => [ - 'url' => '', - ], + 'services' => [ + 'noop' => [ + 'loadBalancer' => [ + 'servers' => [], ], ], ], ], - 'middlewares' => [ + ]; + if (!empty($redirect_url)) { + $dynamic_conf['http']['routers']['catchall']['middlewares'] = [ + 0 => 'redirect-regexp@file', + ]; + $dynamic_conf['http']['services']['noop']['loadBalancer']['servers'][0] = [ + 'url' => '', + ]; + $dynamic_conf['http']['middlewares'] = [ 'redirect-regexp' => [ 'redirectRegex' => [ 'regex' => '(.*)', @@ -235,32 +245,17 @@ respond 404 'permanent' => false, ], ], - ], - ], - ]; - $conf = Yaml::dump($dynamic_conf, 12, 2); - $conf = - "# This file is automatically generated by Coolify.\n". - "# Do not edit it manually (only if you know what are you doing).\n\n". - $conf; - - $base64 = base64_encode($conf); - } elseif ($proxy_type === ProxyTypes::CADDY->value) { - $conf = ":80, :443 { - redir $redirect_url -}"; - $conf = - "# This file is automatically generated by Coolify.\n". - "# Do not edit it manually (only if you know what are you doing).\n\n". - $conf; + ]; + } + $conf = Yaml::dump($dynamic_conf, 12, 2); + } + $conf = $banner . $conf; $base64 = base64_encode($conf); + instant_remote_process([ + "echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null", + ], $this); } - instant_remote_process([ - "mkdir -p $dynamic_conf_path", - "echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null", - ], $this); - if ($proxy_type === 'CADDY') { $this->reloadCaddy(); } diff --git a/resources/views/livewire/server/proxy.blade.php b/resources/views/livewire/server/proxy.blade.php index 25c3f7acd..5748a5876 100644 --- a/resources/views/livewire/server/proxy.blade.php +++ b/resources/views/livewire/server/proxy.blade.php @@ -28,6 +28,13 @@ id="server.settings.generate_exact_labels" label="Generate labels only for {{ str($server->proxyType())->title() }}" instantSave /> +
Default request handler
+
+ + @if ($redirect_enabled) + + @endif +
@if ($server->proxyType() === ProxyTypes::TRAEFIK->value)

Traefik

@elseif ($server->proxyType() === 'CADDY') @@ -40,8 +47,6 @@ configurations. @endif -
diff --git a/resources/views/livewire/server/proxy/dynamic-configurations.blade.php b/resources/views/livewire/server/proxy/dynamic-configurations.blade.php index a8192cdb1..dd1fa59f0 100644 --- a/resources/views/livewire/server/proxy/dynamic-configurations.blade.php +++ b/resources/views/livewire/server/proxy/dynamic-configurations.blade.php @@ -29,7 +29,7 @@ @if (str_replace('|', '.', $fileName) === 'coolify.yaml' || str_replace('|', '.', $fileName) === 'Caddyfile' || str_replace('|', '.', $fileName) === 'coolify.caddy' || - str_replace('|', '.', $fileName) === 'default_redirect_404.caddy') + str_replace('|', '.', $fileName) === 'default_redirect_503.caddy')

File: {{ str_replace('|', '.', $fileName) }}

From 2f3503d8b8509ddc9904a2fc3ffc12c3cd98e7b4 Mon Sep 17 00:00:00 2001 From: Kael Date: Fri, 11 Oct 2024 03:24:46 +1100 Subject: [PATCH 02/51] fix: don't allow editing traefik config --- .../views/livewire/server/proxy/dynamic-configurations.blade.php | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/views/livewire/server/proxy/dynamic-configurations.blade.php b/resources/views/livewire/server/proxy/dynamic-configurations.blade.php index dd1fa59f0..5499ea95b 100644 --- a/resources/views/livewire/server/proxy/dynamic-configurations.blade.php +++ b/resources/views/livewire/server/proxy/dynamic-configurations.blade.php @@ -29,6 +29,7 @@ @if (str_replace('|', '.', $fileName) === 'coolify.yaml' || str_replace('|', '.', $fileName) === 'Caddyfile' || str_replace('|', '.', $fileName) === 'coolify.caddy' || + str_replace('|', '.', $fileName) === 'default_redirect_503.yaml' || str_replace('|', '.', $fileName) === 'default_redirect_503.caddy')

File: {{ str_replace('|', '.', $fileName) }}

From 1f499c148867295b1db3f329330c01d7e4d3fe8a Mon Sep 17 00:00:00 2001 From: Vishwanath Martur <64204611+vishwamartur@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:04:54 +0530 Subject: [PATCH 03/51] Add no encryption option for SMTP settings Related to #4311 Add option to configure SMTP settings without encryption. * Update `app/Livewire/Notifications/Email.php` and `app/Livewire/SettingsEmail.php` to include "No Encryption" option in the `smtpEncryption` field and update validation rules. * Modify `app/Notifications/Channels/EmailChannel.php` to handle the "No Encryption" option in the `bootConfigs` method. * Add `set_transanctional_email_settings` function in `app/Livewire/Help.php` to support the "No Encryption" option. * Update `config/mail.php` to handle the "No Encryption" option in the mail configuration. --- app/Livewire/Help.php | 32 +++++++++++++++++++++ app/Livewire/Notifications/Email.php | 2 +- app/Livewire/SettingsEmail.php | 2 +- app/Notifications/Channels/EmailChannel.php | 2 +- config/mail.php | 2 +- 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/app/Livewire/Help.php b/app/Livewire/Help.php index f51527fbe..aa354e94e 100644 --- a/app/Livewire/Help.php +++ b/app/Livewire/Help.php @@ -56,3 +56,35 @@ class Help extends Component return view('livewire.help')->layout('layouts.app'); } } + +function set_transanctional_email_settings($settings = null) +{ + if (is_null($settings)) { + $settings = instanceSettings(); + } + + if ($settings->resend_enabled) { + config()->set('mail.default', 'resend'); + config()->set('resend.api_key', $settings->resend_api_key); + + return 'resend'; + } + + if ($settings->smtp_enabled) { + config()->set('mail.default', 'smtp'); + config()->set('mail.mailers.smtp', [ + 'transport' => 'smtp', + 'host' => $settings->smtp_host, + 'port' => $settings->smtp_port, + 'encryption' => $settings->smtp_encryption === 'none' ? null : $settings->smtp_encryption, + 'username' => $settings->smtp_username, + 'password' => $settings->smtp_password, + 'timeout' => $settings->smtp_timeout, + 'local_domain' => null, + ]); + + return 'smtp'; + } + + return false; +} diff --git a/app/Livewire/Notifications/Email.php b/app/Livewire/Notifications/Email.php index fcedf1305..682180aa8 100644 --- a/app/Livewire/Notifications/Email.php +++ b/app/Livewire/Notifications/Email.php @@ -37,7 +37,7 @@ class Email extends Component #[Validate(['nullable', 'numeric'])] public ?int $smtpPort = null; - #[Validate(['nullable', 'string'])] + #[Validate(['nullable', 'string', 'in:tls,ssl,none'])] public ?string $smtpEncryption = null; #[Validate(['nullable', 'string'])] diff --git a/app/Livewire/SettingsEmail.php b/app/Livewire/SettingsEmail.php index 61f720b3a..abf3a12f9 100644 --- a/app/Livewire/SettingsEmail.php +++ b/app/Livewire/SettingsEmail.php @@ -19,7 +19,7 @@ class SettingsEmail extends Component #[Validate(['nullable', 'numeric', 'min:1', 'max:65535'])] public ?int $smtpPort = null; - #[Validate(['nullable', 'string'])] + #[Validate(['nullable', 'string', 'in:tls,ssl,none'])] public ?string $smtpEncryption = null; #[Validate(['nullable', 'string'])] diff --git a/app/Notifications/Channels/EmailChannel.php b/app/Notifications/Channels/EmailChannel.php index af9af978d..e745990e4 100644 --- a/app/Notifications/Channels/EmailChannel.php +++ b/app/Notifications/Channels/EmailChannel.php @@ -66,7 +66,7 @@ class EmailChannel 'transport' => 'smtp', 'host' => data_get($notifiable, 'smtp_host'), 'port' => data_get($notifiable, 'smtp_port'), - 'encryption' => data_get($notifiable, 'smtp_encryption'), + 'encryption' => data_get($notifiable, 'smtp_encryption') === 'none' ? null : data_get($notifiable, 'smtp_encryption'), 'username' => data_get($notifiable, 'smtp_username'), 'password' => data_get($notifiable, 'smtp_password'), 'timeout' => data_get($notifiable, 'smtp_timeout'), diff --git a/config/mail.php b/config/mail.php index 26af507d9..b36bd363c 100644 --- a/config/mail.php +++ b/config/mail.php @@ -38,7 +38,7 @@ return [ 'transport' => 'smtp', 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), 'port' => env('MAIL_PORT', 587), - 'encryption' => env('MAIL_ENCRYPTION', 'tls'), + 'encryption' => env('MAIL_ENCRYPTION', 'tls') === 'none' ? null : env('MAIL_ENCRYPTION', 'tls'), 'username' => env('MAIL_USERNAME'), 'password' => env('MAIL_PASSWORD'), 'timeout' => null, From f45411f3d6f869ea52ccc0022a28856e31830cc5 Mon Sep 17 00:00:00 2001 From: Vishwanath Martur <64204611+vishwamartur@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:08:53 +0530 Subject: [PATCH 04/51] Fix metrics error when data is less than selected date Related to #4309 Fix the 'sql: Scan error on column index 5, name "usedPercent": converting NULL to float64 is unsupported' error on the metrics page. * Update the `getMemoryMetrics` method in `app/Models/Server.php` to handle NULL values for the "usedPercent" field by setting them to 0.0. * Add a check for NULL values in the `getMemoryMetrics` method before converting to float64. --- app/Models/Server.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Models/Server.php b/app/Models/Server.php index 5bbd13ac7..1e872227a 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -611,7 +611,8 @@ $schema://$host { } $memory = json_decode($memory, true); $parsedCollection = collect($memory)->map(function ($metric) { - return [(int) $metric['time'], (float) $metric['usedPercent']]; + $usedPercent = $metric['usedPercent'] ?? 0.0; + return [(int) $metric['time'], (float) $usedPercent]; }); return $parsedCollection->toArray(); From a836d78f0bb7716134f2424d917636046b41e91a Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 25 Nov 2024 14:07:50 +0100 Subject: [PATCH 05/51] remove unnecessary function --- app/Livewire/Help.php | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/app/Livewire/Help.php b/app/Livewire/Help.php index aa354e94e..f51527fbe 100644 --- a/app/Livewire/Help.php +++ b/app/Livewire/Help.php @@ -56,35 +56,3 @@ class Help extends Component return view('livewire.help')->layout('layouts.app'); } } - -function set_transanctional_email_settings($settings = null) -{ - if (is_null($settings)) { - $settings = instanceSettings(); - } - - if ($settings->resend_enabled) { - config()->set('mail.default', 'resend'); - config()->set('resend.api_key', $settings->resend_api_key); - - return 'resend'; - } - - if ($settings->smtp_enabled) { - config()->set('mail.default', 'smtp'); - config()->set('mail.mailers.smtp', [ - 'transport' => 'smtp', - 'host' => $settings->smtp_host, - 'port' => $settings->smtp_port, - 'encryption' => $settings->smtp_encryption === 'none' ? null : $settings->smtp_encryption, - 'username' => $settings->smtp_username, - 'password' => $settings->smtp_password, - 'timeout' => $settings->smtp_timeout, - 'local_domain' => null, - ]); - - return 'smtp'; - } - - return false; -} From b3f968db76193f62dc51f4bb2bf996358441cc17 Mon Sep 17 00:00:00 2001 From: SierraJC <7351311+SierraJC@users.noreply.github.com> Date: Sun, 1 Dec 2024 10:15:45 +1100 Subject: [PATCH 06/51] fix: missing `mysql_password` API property --- app/Http/Controllers/Api/DatabasesController.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php index ce658d2a2..21816557f 100644 --- a/app/Http/Controllers/Api/DatabasesController.php +++ b/app/Http/Controllers/Api/DatabasesController.php @@ -213,6 +213,7 @@ class DatabasesController extends Controller 'mongo_initdb_root_password' => ['type' => 'string', 'description' => 'Mongo initdb root password'], 'mongo_initdb_init_database' => ['type' => 'string', 'description' => 'Mongo initdb init database'], 'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'], + 'mysql_password' => ['type' => 'string', 'description' => 'MySQL password'], 'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'], 'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'], 'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'], @@ -443,9 +444,10 @@ class DatabasesController extends Controller break; case 'standalone-mysql': - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_user', 'mysql_database', 'mysql_conf']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf']; $validator = customApiValidator($request->all(), [ 'mysql_root_password' => 'string', + 'mysql_password' => 'string', 'mysql_user' => 'string', 'mysql_database' => 'string', 'mysql_conf' => 'string', @@ -909,6 +911,7 @@ class DatabasesController extends Controller 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'], 'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], 'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'], + 'mysql_password' => ['type' => 'string', 'description' => 'MySQL password'], 'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'], 'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'], 'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'], @@ -1013,7 +1016,7 @@ class DatabasesController extends Controller public function create_database(Request $request, NewDatabaseTypes $type) { - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database', 'mysql_root_password', 'mysql_user', 'mysql_database', 'mysql_conf']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { @@ -1220,9 +1223,10 @@ class DatabasesController extends Controller return response()->json(serializeApiResponse($payload))->setStatusCode(201); } elseif ($type === NewDatabaseTypes::MYSQL) { - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_user', 'mysql_database', 'mysql_conf']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf']; $validator = customApiValidator($request->all(), [ 'mysql_root_password' => 'string', + 'mysql_password' => 'string', 'mysql_user' => 'string', 'mysql_database' => 'string', 'mysql_conf' => 'string', From f279729f088629ecdf4de3b058540c40c34f19bb Mon Sep 17 00:00:00 2001 From: SierraJC <7351311+SierraJC@users.noreply.github.com> Date: Sun, 1 Dec 2024 10:16:41 +1100 Subject: [PATCH 07/51] fix: incorrect MongoDB init API property --- app/Http/Controllers/Api/DatabasesController.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php index 21816557f..58c4467e9 100644 --- a/app/Http/Controllers/Api/DatabasesController.php +++ b/app/Http/Controllers/Api/DatabasesController.php @@ -211,7 +211,7 @@ class DatabasesController extends Controller 'mongo_conf' => ['type' => 'string', 'description' => 'Mongo conf'], 'mongo_initdb_root_username' => ['type' => 'string', 'description' => 'Mongo initdb root username'], 'mongo_initdb_root_password' => ['type' => 'string', 'description' => 'Mongo initdb root password'], - 'mongo_initdb_init_database' => ['type' => 'string', 'description' => 'Mongo initdb init database'], + 'mongo_initdb_database' => ['type' => 'string', 'description' => 'Mongo initdb init database'], 'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'], 'mysql_password' => ['type' => 'string', 'description' => 'MySQL password'], 'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'], @@ -242,7 +242,7 @@ class DatabasesController extends Controller )] public function update_by_uuid(Request $request) { - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database', 'mysql_root_password', 'mysql_user', 'mysql_database', 'mysql_conf']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); @@ -414,12 +414,12 @@ class DatabasesController extends Controller } break; case 'standalone-mongodb': - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database']; $validator = customApiValidator($request->all(), [ 'mongo_conf' => 'string', 'mongo_initdb_root_username' => 'string', 'mongo_initdb_root_password' => 'string', - 'mongo_initdb_init_database' => 'string', + 'mongo_initdb_database' => 'string', ]); if ($request->has('mongo_conf')) { if (! isBase64Encoded($request->mongo_conf)) { @@ -1016,7 +1016,7 @@ class DatabasesController extends Controller public function create_database(Request $request, NewDatabaseTypes $type) { - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { @@ -1460,12 +1460,12 @@ class DatabasesController extends Controller return response()->json(serializeApiResponse($payload))->setStatusCode(201); } elseif ($type === NewDatabaseTypes::MONGODB) { - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database']; $validator = customApiValidator($request->all(), [ 'mongo_conf' => 'string', 'mongo_initdb_root_username' => 'string', 'mongo_initdb_root_password' => 'string', - 'mongo_initdb_init_database' => 'string', + 'mongo_initdb_database' => 'string', ]); $extraFields = array_diff(array_keys($request->all()), $allowedFields); if ($validator->fails() || ! empty($extraFields)) { From ce6602eb6398a2f99edb4180b300d89d19e186d5 Mon Sep 17 00:00:00 2001 From: SierraJC <7351311+SierraJC@users.noreply.github.com> Date: Sun, 1 Dec 2024 10:17:49 +1100 Subject: [PATCH 08/51] chore: regenerate openapi spec --- openapi.json | 10 +++++++++- openapi.yaml | 8 +++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/openapi.json b/openapi.json index 2ec218438..5d35331ec 100644 --- a/openapi.json +++ b/openapi.json @@ -3011,7 +3011,7 @@ "type": "string", "description": "Mongo initdb root password" }, - "mongo_initdb_init_database": { + "mongo_initdb_database": { "type": "string", "description": "Mongo initdb init database" }, @@ -3019,6 +3019,10 @@ "type": "string", "description": "MySQL root password" }, + "mysql_password": { + "type": "string", + "description": "MySQL password" + }, "mysql_user": { "type": "string", "description": "MySQL user" @@ -3842,6 +3846,10 @@ "type": "string", "description": "MySQL root password" }, + "mysql_password": { + "type": "string", + "description": "MySQL password" + }, "mysql_user": { "type": "string", "description": "MySQL user" diff --git a/openapi.yaml b/openapi.yaml index 2a22c730c..20bf34873 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -2089,12 +2089,15 @@ paths: mongo_initdb_root_password: type: string description: 'Mongo initdb root password' - mongo_initdb_init_database: + mongo_initdb_database: type: string description: 'Mongo initdb init database' mysql_root_password: type: string description: 'MySQL root password' + mysql_password: + type: string + description: 'MySQL password' mysql_user: type: string description: 'MySQL user' @@ -2684,6 +2687,9 @@ paths: mysql_root_password: type: string description: 'MySQL root password' + mysql_password: + type: string + description: 'MySQL password' mysql_user: type: string description: 'MySQL user' From c74728162ede3aa137affd457bdc606d0d69be77 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 3 Dec 2024 12:41:56 +0100 Subject: [PATCH 09/51] wip: test rename GitHub app --- app/Livewire/Source/Github/Change.php | 5 +++++ .../views/livewire/source/github/change.blade.php | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/Livewire/Source/Github/Change.php b/app/Livewire/Source/Github/Change.php index 07cef54f9..79cc02219 100644 --- a/app/Livewire/Source/Github/Change.php +++ b/app/Livewire/Source/Github/Change.php @@ -142,6 +142,11 @@ class Change extends Component } } + public function getUpdatePath() + { + return "{$this->github_app->html_url}/settings/apps/{$this->github_app->app_id}"; + } + public function submit() { try { diff --git a/resources/views/livewire/source/github/change.blade.php b/resources/views/livewire/source/github/change.blade.php index 5e576fa85..215c54411 100644 --- a/resources/views/livewire/source/github/change.blade.php +++ b/resources/views/livewire/source/github/change.blade.php @@ -58,7 +58,15 @@ @else
From f38196c4213e1358d89c35c66f4c3b4f8ba6e258 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 3 Dec 2024 12:54:20 +0100 Subject: [PATCH 10/51] fix: URL and sync new app name --- app/Livewire/Source/Github/Change.php | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/app/Livewire/Source/Github/Change.php b/app/Livewire/Source/Github/Change.php index 79cc02219..5d57ecf1e 100644 --- a/app/Livewire/Source/Github/Change.php +++ b/app/Livewire/Source/Github/Change.php @@ -4,6 +4,7 @@ namespace App\Livewire\Source\Github; use App\Jobs\GithubAppPermissionJob; use App\Models\GithubApp; +use Illuminate\Support\Facades\Http; use Livewire\Component; class Change extends Component @@ -144,7 +145,29 @@ class Change extends Component public function getUpdatePath() { - return "{$this->github_app->html_url}/settings/apps/{$this->github_app->app_id}"; + return "{$this->github_app->html_url}/settings/apps/{$this->github_app->name}"; + } + + public function syncGithubAppName() + { + try { + $github_access_token = generate_github_installation_token($this->github_app); + + $response = Http::withToken($github_access_token) + ->get("{$this->github_app->api_url}/app"); + + if ($response->successful()) { + $app_data = $response->json(); + if ($app_data['name'] !== $this->github_app->name) { + $this->github_app->name = $app_data['name']; + $this->github_app->save(); + $this->name = str($this->github_app->name)->kebab(); + $this->dispatch('success', 'Github App name synchronized.'); + } + } + } catch (\Throwable $e) { + return handleError($e, $this); + } } public function submit() From 737e81aa38f854b860047e3782912e33cab244a3 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 3 Dec 2024 13:12:58 +0100 Subject: [PATCH 11/51] wip button to sync new app name --- app/Livewire/Source/Github/Change.php | 20 ++++++++++++++----- .../livewire/source/github/change.blade.php | 1 + 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/app/Livewire/Source/Github/Change.php b/app/Livewire/Source/Github/Change.php index 5d57ecf1e..55ced1e3e 100644 --- a/app/Livewire/Source/Github/Change.php +++ b/app/Livewire/Source/Github/Change.php @@ -154,16 +154,26 @@ class Change extends Component $github_access_token = generate_github_installation_token($this->github_app); $response = Http::withToken($github_access_token) - ->get("{$this->github_app->api_url}/app"); + ->withHeaders([ + 'Accept' => 'application/vnd.github+json', + 'X-GitHub-Api-Version' => '2022-11-28', + ]) + ->get("{$this->github_app->api_url}/installation/repositories"); if ($response->successful()) { $app_data = $response->json(); - if ($app_data['name'] !== $this->github_app->name) { - $this->github_app->name = $app_data['name']; + $app_name = data_get($app_data, 'installation.app_slug'); + + if ($app_name && $app_name !== $this->github_app->name) { + $this->github_app->name = $app_name; + $this->name = str($app_name)->kebab(); $this->github_app->save(); - $this->name = str($this->github_app->name)->kebab(); - $this->dispatch('success', 'Github App name synchronized.'); + $this->dispatch('success', 'Github App name synchronized successfully.'); + } else { + $this->dispatch('info', 'Github App name is already up to date.'); } + } else { + $this->dispatch('error', 'Failed to fetch Github App information. Status: '.$response->status()); } } catch (\Throwable $e) { return handleError($e, $this); diff --git a/resources/views/livewire/source/github/change.blade.php b/resources/views/livewire/source/github/change.blade.php index 215c54411..2d873b465 100644 --- a/resources/views/livewire/source/github/change.blade.php +++ b/resources/views/livewire/source/github/change.blade.php @@ -60,6 +60,7 @@
+ Sync Name Update From a2860971a6dc8b17072b18962552ff604c28d552 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:36:30 +0100 Subject: [PATCH 12/51] try jwt --- app/Livewire/Source/Github/Change.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/Livewire/Source/Github/Change.php b/app/Livewire/Source/Github/Change.php index 55ced1e3e..4c41cb9e4 100644 --- a/app/Livewire/Source/Github/Change.php +++ b/app/Livewire/Source/Github/Change.php @@ -151,18 +151,18 @@ class Change extends Component public function syncGithubAppName() { try { - $github_access_token = generate_github_installation_token($this->github_app); + $jwt = $this->github_app->generateJWT(); - $response = Http::withToken($github_access_token) + $response = Http::withToken($jwt) ->withHeaders([ 'Accept' => 'application/vnd.github+json', 'X-GitHub-Api-Version' => '2022-11-28', ]) - ->get("{$this->github_app->api_url}/installation/repositories"); + ->get('https://api.github.com/app'); if ($response->successful()) { $app_data = $response->json(); - $app_name = data_get($app_data, 'installation.app_slug'); + $app_name = $app_data['name'] ?? null; if ($app_name && $app_name !== $this->github_app->name) { $this->github_app->name = $app_name; @@ -170,7 +170,7 @@ class Change extends Component $this->github_app->save(); $this->dispatch('success', 'Github App name synchronized successfully.'); } else { - $this->dispatch('info', 'Github App name is already up to date.'); + $this->dispatch('info', 'If you changed the name in GitHub, please wait a few moments and try syncing again.'); } } else { $this->dispatch('error', 'Failed to fetch Github App information. Status: '.$response->status()); From 6d43bbc6b95c552bf3398517a4b79c9b06a48642 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:43:11 +0100 Subject: [PATCH 13/51] fix naming --- app/Livewire/Source/Github/Change.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Livewire/Source/Github/Change.php b/app/Livewire/Source/Github/Change.php index 4c41cb9e4..4f76d7299 100644 --- a/app/Livewire/Source/Github/Change.php +++ b/app/Livewire/Source/Github/Change.php @@ -151,14 +151,14 @@ class Change extends Component public function syncGithubAppName() { try { - $jwt = $this->github_app->generateJWT(); + $github_access_token = generate_github_installation_token($this->github_app); - $response = Http::withToken($jwt) + $response = Http::withToken($github_access_token) ->withHeaders([ 'Accept' => 'application/vnd.github+json', 'X-GitHub-Api-Version' => '2022-11-28', ]) - ->get('https://api.github.com/app'); + ->get("{$this->github_app->api_url}/app"); if ($response->successful()) { $app_data = $response->json(); From 56f6bdf7a776b951cdad2920c4e3b07a196ada00 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:11:35 +0100 Subject: [PATCH 14/51] use private key to make a jwt --- app/Livewire/Source/Github/Change.php | 58 +++++++++++++++++++++------ 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/app/Livewire/Source/Github/Change.php b/app/Livewire/Source/Github/Change.php index 4f76d7299..4968a549d 100644 --- a/app/Livewire/Source/Github/Change.php +++ b/app/Livewire/Source/Github/Change.php @@ -4,7 +4,11 @@ namespace App\Livewire\Source\Github; use App\Jobs\GithubAppPermissionJob; use App\Models\GithubApp; +use App\Models\PrivateKey; use Illuminate\Support\Facades\Http; +use Lcobucci\JWT\Configuration; +use Lcobucci\JWT\Signer\Key\InMemory; +use Lcobucci\JWT\Signer\Rsa\Sha256; use Livewire\Component; class Change extends Component @@ -148,32 +152,60 @@ class Change extends Component return "{$this->github_app->html_url}/settings/apps/{$this->github_app->name}"; } + private function generateGithubJwt($private_key, $app_id): string + { + $configuration = Configuration::forAsymmetricSigner( + new Sha256, + InMemory::plainText($private_key), + InMemory::plainText($private_key) + ); + + $now = time(); + + return $configuration->builder() + ->issuedBy((string) $app_id) + ->permittedFor('https://api.github.com') + ->identifiedBy((string) $now) + ->issuedAt(new \DateTimeImmutable("@{$now}")) + ->expiresAt(new \DateTimeImmutable('@'.($now + 600))) + ->getToken($configuration->signer(), $configuration->signingKey()) + ->toString(); + } + public function syncGithubAppName() { try { - $github_access_token = generate_github_installation_token($this->github_app); + $privateKey = PrivateKey::find($this->github_app->private_key_id); - $response = Http::withToken($github_access_token) - ->withHeaders([ - 'Accept' => 'application/vnd.github+json', - 'X-GitHub-Api-Version' => '2022-11-28', - ]) - ->get("{$this->github_app->api_url}/app"); + if (! $privateKey) { + $this->dispatch('error', 'Private key not found for this GitHub App.'); + + return; + } + + $jwt = $this->generateGithubJwt($privateKey->private_key, $this->github_app->app_id); + + $response = Http::withHeaders([ + 'Accept' => 'application/vnd.github+json', + 'X-GitHub-Api-Version' => '2022-11-28', + 'Authorization' => "Bearer {$jwt}", + ])->get("{$this->github_app->api_url}/app"); if ($response->successful()) { $app_data = $response->json(); - $app_name = $app_data['name'] ?? null; + $app_slug = $app_data['slug'] ?? null; - if ($app_name && $app_name !== $this->github_app->name) { - $this->github_app->name = $app_name; - $this->name = str($app_name)->kebab(); + if ($app_slug) { + $this->github_app->name = $app_slug; + $this->name = str($app_slug)->kebab(); $this->github_app->save(); $this->dispatch('success', 'Github App name synchronized successfully.'); } else { - $this->dispatch('info', 'If you changed the name in GitHub, please wait a few moments and try syncing again.'); + $this->dispatch('info', 'Could not find app slug in GitHub response.'); } } else { - $this->dispatch('error', 'Failed to fetch Github App information. Status: '.$response->status()); + $error_message = $response->json()['message'] ?? 'Unknown error'; + $this->dispatch('error', "Failed to fetch Github App information: {$error_message}"); } } catch (\Throwable $e) { return handleError($e, $this); From 5f985426abc7d11f8d16fb675f6c3430fc4e6ae1 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:27:20 +0100 Subject: [PATCH 15/51] feat: update private key nam with new slug as well --- app/Http/Controllers/Webhook/Github.php | 2 +- app/Livewire/Source/Github/Change.php | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Webhook/Github.php b/app/Http/Controllers/Webhook/Github.php index 3683adaa8..ac1d4ded2 100644 --- a/app/Http/Controllers/Webhook/Github.php +++ b/app/Http/Controllers/Webhook/Github.php @@ -463,7 +463,7 @@ class Github extends Controller $private_key = data_get($data, 'pem'); $webhook_secret = data_get($data, 'webhook_secret'); $private_key = PrivateKey::create([ - 'name' => $slug, + 'name' => "github-app-{$slug}", 'private_key' => $private_key, 'team_id' => $github_app->team_id, 'is_git_related' => true, diff --git a/app/Livewire/Source/Github/Change.php b/app/Livewire/Source/Github/Change.php index 4968a549d..7f9200891 100644 --- a/app/Livewire/Source/Github/Change.php +++ b/app/Livewire/Source/Github/Change.php @@ -198,8 +198,10 @@ class Change extends Component if ($app_slug) { $this->github_app->name = $app_slug; $this->name = str($app_slug)->kebab(); + $privateKey->name = "github-app-{$app_slug}"; + $privateKey->save(); $this->github_app->save(); - $this->dispatch('success', 'Github App name synchronized successfully.'); + $this->dispatch('success', 'Github App name and SSH key name synchronized successfully.'); } else { $this->dispatch('info', 'Could not find app slug in GitHub response.'); } From fef8d0c62c103df93d840d3e86eab488940915fb Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:50:45 +0100 Subject: [PATCH 16/51] fix: typos and naming --- app/Livewire/Source/Github/Change.php | 14 ++++++++------ .../views/livewire/source/github/change.blade.php | 8 +++++--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/Livewire/Source/Github/Change.php b/app/Livewire/Source/Github/Change.php index 7f9200891..b1f0605bb 100644 --- a/app/Livewire/Source/Github/Change.php +++ b/app/Livewire/Source/Github/Change.php @@ -62,6 +62,7 @@ class Change extends Component $this->github_app->refresh()->makeVisible('client_secret')->makeVisible('webhook_secret'); $this->dispatch('success', 'Github App permissions updated.'); } + // public function check() // { @@ -95,6 +96,7 @@ class Change extends Component // ray($runners_by_repository); // } + public function mount() { try { @@ -147,7 +149,7 @@ class Change extends Component } } - public function getUpdatePath() + public function getGithubAppNameUpdatePath() { return "{$this->github_app->html_url}/settings/apps/{$this->github_app->name}"; } @@ -172,13 +174,13 @@ class Change extends Component ->toString(); } - public function syncGithubAppName() + public function updateGithubAppName() { try { $privateKey = PrivateKey::find($this->github_app->private_key_id); if (! $privateKey) { - $this->dispatch('error', 'Private key not found for this GitHub App.'); + $this->dispatch('error', 'No private key found for this GitHub App.'); return; } @@ -201,13 +203,13 @@ class Change extends Component $privateKey->name = "github-app-{$app_slug}"; $privateKey->save(); $this->github_app->save(); - $this->dispatch('success', 'Github App name and SSH key name synchronized successfully.'); + $this->dispatch('success', 'GitHub App name and SSH key name synchronized successfully.'); } else { - $this->dispatch('info', 'Could not find app slug in GitHub response.'); + $this->dispatch('info', 'Could not find App Name (slug) in GitHub response.'); } } else { $error_message = $response->json()['message'] ?? 'Unknown error'; - $this->dispatch('error', "Failed to fetch Github App information: {$error_message}"); + $this->dispatch('error', "Failed to fetch GitHub App information: {$error_message}"); } } catch (\Throwable $e) { return handleError($e, $this); diff --git a/resources/views/livewire/source/github/change.blade.php b/resources/views/livewire/source/github/change.blade.php index 2d873b465..3c11646c2 100644 --- a/resources/views/livewire/source/github/change.blade.php +++ b/resources/views/livewire/source/github/change.blade.php @@ -60,10 +60,12 @@
- Sync Name - + + Sync Name + + - Update + Rename From f51300a19213df0d29fbf0161fc91495bd296c52 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 3 Dec 2024 16:17:35 +0100 Subject: [PATCH 17/51] fix: client and webhook secret disappear after sync --- app/Livewire/Source/Github/Change.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/Livewire/Source/Github/Change.php b/app/Livewire/Source/Github/Change.php index b1f0605bb..51c5ae3e9 100644 --- a/app/Livewire/Source/Github/Change.php +++ b/app/Livewire/Source/Github/Change.php @@ -56,6 +56,13 @@ class Change extends Component 'github_app.administration' => 'nullable|string', ]; + public function boot() + { + if ($this->github_app) { + $this->github_app->makeVisible(['client_secret', 'webhook_secret']); + } + } + public function checkPermissions() { GithubAppPermissionJob::dispatchSync($this->github_app); @@ -102,10 +109,10 @@ class Change extends Component try { $github_app_uuid = request()->github_app_uuid; $this->github_app = GithubApp::ownedByCurrentTeam()->whereUuid($github_app_uuid)->firstOrFail(); + $this->github_app->makeVisible(['client_secret', 'webhook_secret']); $this->applications = $this->github_app->applications; $settings = instanceSettings(); - $this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret'); $this->name = str($this->github_app->name)->kebab(); $this->fqdn = $settings->fqdn; From 26ba433fd3f4371fd971f57cfb41b001a364fa45 Mon Sep 17 00:00:00 2001 From: Jeremy Angele Date: Tue, 3 Dec 2024 22:24:36 +0100 Subject: [PATCH 18/51] Use computed property for timezones --- app/Livewire/Server/Show.php | 15 ++++++++++----- resources/views/livewire/server/show.blade.php | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/Livewire/Server/Show.php b/app/Livewire/Server/Show.php index a5544489d..ac5211c1b 100644 --- a/app/Livewire/Server/Show.php +++ b/app/Livewire/Server/Show.php @@ -5,7 +5,7 @@ namespace App\Livewire\Server; use App\Actions\Server\StartSentinel; use App\Actions\Server\StopSentinel; use App\Models\Server; -use Livewire\Attributes\Locked; +use Livewire\Attributes\Computed; use Livewire\Attributes\Validate; use Livewire\Component; @@ -79,9 +79,6 @@ class Show extends Component #[Validate(['required'])] public string $serverTimezone; - #[Locked] - public array $timezones; - public function getListeners() { $teamId = auth()->user()->currentTeam()->id; @@ -96,13 +93,21 @@ class Show extends Component { try { $this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail(); - $this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray(); $this->syncData(); } catch (\Throwable $e) { return handleError($e, $this); } } + #[Computed] + public function timezones(): array + { + return collect(timezone_identifiers_list()) + ->sort() + ->values() + ->toArray(); + } + public function syncData(bool $toModel = false) { if ($toModel) { diff --git a/resources/views/livewire/server/show.blade.php b/resources/views/livewire/server/show.blade.php index 5aed0b4e2..0d1d5e681 100644 --- a/resources/views/livewire/server/show.blade.php +++ b/resources/views/livewire/server/show.blade.php @@ -88,7 +88,7 @@
Date: Wed, 4 Dec 2024 12:43:52 +0100 Subject: [PATCH 21/51] test: setup database for upcoming tests --- app/Models/Application.php | 3 +- app/Models/Server.php | 3 +- config/database.php | 16 ++++ database/factories/ApplicationFactory.php | 22 ++++++ database/factories/ServerFactory.php | 17 ++++ phpunit.xml | 4 +- tests/Feature/ExecuteContainerCommandTest.php | 57 ++++++++++++++ tests/Traits/HandlesTestDatabase.php | 78 +++++++++++++++++++ 8 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 database/factories/ApplicationFactory.php create mode 100644 database/factories/ServerFactory.php create mode 100644 tests/Feature/ExecuteContainerCommandTest.php create mode 100644 tests/Traits/HandlesTestDatabase.php diff --git a/app/Models/Application.php b/app/Models/Application.php index c284528f1..a68c1d54a 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -4,6 +4,7 @@ namespace App\Models; use App\Enums\ApplicationDeploymentStatus; use Illuminate\Database\Eloquent\Casts\Attribute; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Process\InvokedProcess; @@ -104,7 +105,7 @@ use Visus\Cuid2\Cuid2; class Application extends BaseModel { - use SoftDeletes; + use HasFactory, SoftDeletes; private static $parserVersion = '4'; diff --git a/app/Models/Server.php b/app/Models/Server.php index 83b91b254..e0a66c58b 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -11,6 +11,7 @@ use App\Notifications\Server\Reachable; use App\Notifications\Server\Unreachable; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; @@ -48,7 +49,7 @@ use Symfony\Component\Yaml\Yaml; class Server extends BaseModel { - use SchemalessAttributesTrait, SoftDeletes; + use HasFactory, SchemalessAttributesTrait, SoftDeletes; public static $batch_counter = 0; diff --git a/config/database.php b/config/database.php index f48a68082..6f4acbfd2 100644 --- a/config/database.php +++ b/config/database.php @@ -49,6 +49,22 @@ return [ 'search_path' => 'public', 'sslmode' => 'prefer', ], + + 'testing' => [ + 'driver' => 'pgsql', + 'url' => env('DATABASE_TEST_URL'), + 'host' => env('DB_TEST_HOST', 'postgres'), + 'port' => env('DB_TEST_PORT', '5432'), + 'database' => env('DB_TEST_DATABASE', 'coolify_test'), + 'username' => env('DB_TEST_USERNAME', 'coolify'), + 'password' => env('DB_TEST_PASSWORD', 'password'), + 'charset' => 'utf8', + 'prefix' => '', + 'prefix_indexes' => true, + 'search_path' => 'public', + 'sslmode' => 'prefer', + ], + ], /* diff --git a/database/factories/ApplicationFactory.php b/database/factories/ApplicationFactory.php new file mode 100644 index 000000000..ded507c56 --- /dev/null +++ b/database/factories/ApplicationFactory.php @@ -0,0 +1,22 @@ + fake()->unique()->name(), + 'destination_id' => 1, + 'git_repository' => fake()->url(), + 'git_branch' => fake()->word(), + 'build_pack' => 'nixpacks', + 'ports_exposes' => '3000', + 'environment_id' => 1, + 'destination_id' => 1, + ]; + } +} diff --git a/database/factories/ServerFactory.php b/database/factories/ServerFactory.php new file mode 100644 index 000000000..29546bf56 --- /dev/null +++ b/database/factories/ServerFactory.php @@ -0,0 +1,17 @@ + fake()->unique()->name(), + 'ip' => fake()->unique()->ipv4(), + 'private_key_id' => 1, + ]; + } +} diff --git a/phpunit.xml b/phpunit.xml index 45cb69439..f1c2be92d 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -13,8 +13,8 @@ - - + + diff --git a/tests/Feature/ExecuteContainerCommandTest.php b/tests/Feature/ExecuteContainerCommandTest.php new file mode 100644 index 000000000..6d485fe65 --- /dev/null +++ b/tests/Feature/ExecuteContainerCommandTest.php @@ -0,0 +1,57 @@ +shouldSetUpDatabase()) { + $this->setUpTestDatabase(); + } + // Create test data + $this->user = User::factory()->create(); + $this->team = $this->user->teams()->first(); + $this->server = Server::factory()->create(['team_id' => $this->team->id]); + $this->application = Application::factory()->create(); + + // Login the user + $this->actingAs($this->user); + } + + protected function tearDown(): void + { + if ($this->shouldSetUpDatabase()) { + $this->tearDownTestDatabase(); + } + parent::tearDown(); + } + + private function shouldSetUpDatabase(): bool + { + return in_array($this->name(), [ + 'it_allows_valid_container_access', + 'it_prevents_cross_server_container_access', + ]); + } +} diff --git a/tests/Traits/HandlesTestDatabase.php b/tests/Traits/HandlesTestDatabase.php new file mode 100644 index 000000000..adb577e7c --- /dev/null +++ b/tests/Traits/HandlesTestDatabase.php @@ -0,0 +1,78 @@ +createTestDatabase($database); + + // Run migrations + Artisan::call('migrate:fresh', [ + '--database' => 'testing', + '--seed' => false, + ]); + } catch (\Exception $e) { + $this->tearDownTestDatabase(); + throw $e; + } + } + + protected function tearDownTestDatabase(): void + { + try { + // Drop test database + $database = config('database.connections.testing.database'); + $this->dropTestDatabase($database); + } catch (\Exception $e) { + // Log error but don't throw + error_log('Failed to tear down test database: '.$e->getMessage()); + } + } + + protected function createTestDatabase($database) + { + try { + // Connect to postgres database to create/drop test database + config(['database.connections.pgsql.database' => 'postgres']); + DB::purge('pgsql'); + DB::reconnect('pgsql'); + + // Drop if exists and create new database + DB::connection('pgsql')->statement("DROP DATABASE IF EXISTS $database WITH (FORCE);"); + DB::connection('pgsql')->statement("CREATE DATABASE $database;"); + + // Switch back to testing connection + DB::disconnect('pgsql'); + DB::reconnect('testing'); + } catch (\Exception $e) { + $this->tearDownTestDatabase(); + throw new \Exception('Could not create test database: '.$e->getMessage()); + } + } + + protected function dropTestDatabase($database) + { + try { + // Connect to postgres database to drop test database + config(['database.connections.pgsql.database' => 'postgres']); + DB::purge('pgsql'); + DB::reconnect('pgsql'); + + // Drop the test database + DB::connection('pgsql')->statement("DROP DATABASE IF EXISTS $database WITH (FORCE);"); + + DB::disconnect('pgsql'); + } catch (\Exception $e) { + // Log error but don't throw + error_log('Failed to drop test database: '.$e->getMessage()); + } + } +} From 029e8954c5e5b3dd63145b1d1047462f201ccffd Mon Sep 17 00:00:00 2001 From: Eirik H Date: Wed, 4 Dec 2024 13:19:00 +0100 Subject: [PATCH 22/51] Support PopOS as server PopOS is already added as supported for the main Coolify instance, but fails when adding a PopOS machine as an additional server. --- bootstrap/helpers/constants.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/helpers/constants.php b/bootstrap/helpers/constants.php index 303fcab8e..b568e090c 100644 --- a/bootstrap/helpers/constants.php +++ b/bootstrap/helpers/constants.php @@ -46,7 +46,7 @@ const SPECIFIC_SERVICES = [ // Based on /etc/os-release const SUPPORTED_OS = [ - 'ubuntu debian raspbian', + 'ubuntu debian raspbian pop', 'centos fedora rhel ol rocky amzn almalinux', 'sles opensuse-leap opensuse-tumbleweed', 'arch', From 94c264c6d7ce5accdc720201a5cbadf011dc87d8 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 4 Dec 2024 13:49:19 +0100 Subject: [PATCH 23/51] Fix deployment status border color in deployment index view --- .../livewire/project/application/deployment/index.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/livewire/project/application/deployment/index.blade.php b/resources/views/livewire/project/application/deployment/index.blade.php index f6fdb64ab..3fa52b7f3 100644 --- a/resources/views/livewire/project/application/deployment/index.blade.php +++ b/resources/views/livewire/project/application/deployment/index.blade.php @@ -32,7 +32,7 @@ @forelse ($deployments as $deployment)
+ 'border-white border-dashed ' => data_get($deployment, 'status') === 'in_progress' || data_get($deployment, 'status') === 'cancelled-by-user', 'border-error border-dashed ' => From f535ed2359f9400ded0eed439360e059bb28f41b Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 4 Dec 2024 14:00:49 +0100 Subject: [PATCH 24/51] Enhance modal input component and storage forms - Added 'minWidth' attribute to modal input component for better responsiveness. - Updated modal input instantiation in storage view to set a minimum width of 64rem. - Improved layout and user guidance in the storage add forms by adding descriptive text for volume, file, and directory mounts. - Changed button labels from 'Save' to 'Add' for clarity in storage forms. --- .../views/components/modal-input.blade.php | 3 +- .../project/service/storage.blade.php | 2 +- .../project/shared/storages/add.blade.php | 50 ++++++++++++------- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/resources/views/components/modal-input.blade.php b/resources/views/components/modal-input.blade.php index c090037d5..acd3b8bdf 100644 --- a/resources/views/components/modal-input.blade.php +++ b/resources/views/components/modal-input.blade.php @@ -7,6 +7,7 @@ 'action' => 'delete', 'content' => null, 'closeOutside' => true, + 'minWidth' => '36rem', ])
@@ -40,7 +41,7 @@ x-transition:leave="ease-in duration-100" x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100" x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95" - class="relative w-full py-6 border rounded drop-shadow min-w-full lg:min-w-[36rem] max-w-fit bg-white border-neutral-200 dark:bg-base px-6 dark:border-coolgray-300"> + class="relative w-full py-6 border rounded drop-shadow min-w-full lg:min-w-[{{ $minWidth }}] max-w-fit bg-white border-neutral-200 dark:bg-base px-6 dark:border-coolgray-300">

{{ $title }}