diff --git a/app/Actions/Proxy/CheckProxy.php b/app/Actions/Proxy/CheckProxy.php index 32a4897a6..d39b33a2a 100644 --- a/app/Actions/Proxy/CheckProxy.php +++ b/app/Actions/Proxy/CheckProxy.php @@ -13,6 +13,9 @@ public function handle(Server $server, $fromUI = false) if ($server->proxyType() === 'NONE') { return false; } + if (!$server->validateConnection()) { + throw new \Exception("Server Connection Error"); + } if (!$server->isProxyShouldRun()) { if ($fromUI) { throw new \Exception("Proxy should not run. You selected the Custom Proxy."); diff --git a/app/Actions/Server/UpdateCoolify.php b/app/Actions/Server/UpdateCoolify.php index 6c01f75c9..c74072068 100644 --- a/app/Actions/Server/UpdateCoolify.php +++ b/app/Actions/Server/UpdateCoolify.php @@ -45,7 +45,6 @@ public function handle(bool $force = false, bool $async = false) } $this->update(); } - send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}"); } catch (\Throwable $e) { ray('InstanceAutoUpdateJob failed'); ray($e->getMessage()); @@ -83,6 +82,7 @@ private function update() "bash /data/coolify/source/upgrade.sh $this->latestVersion" ], $this->server); } + send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}"); return; } } diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 51c024af6..6ad87e07f 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -179,6 +179,11 @@ public function __construct(int $application_deployment_queue_id) public function handle(): void { + if (!$this->server->isFunctional()) { + $this->application_deployment_queue->addLogEntry("Server is not functional."); + $this->fail("Server is not functional."); + return; + } try { // Generate custom host<->ip mapping $allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server); @@ -646,21 +651,21 @@ private function generate_image_names() { if ($this->application->dockerfile) { if ($this->application->docker_registry_image_name) { - $this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:build"); - $this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:latest"); + $this->build_image_name = "{$this->application->docker_registry_image_name}:build"; + $this->production_image_name = "{$this->application->docker_registry_image_name}:latest"; } else { - $this->build_image_name = Str::lower("{$this->application->uuid}:build"); - $this->production_image_name = Str::lower("{$this->application->uuid}:latest"); + $this->build_image_name = "{$this->application->uuid}:build"; + $this->production_image_name = "{$this->application->uuid}:latest"; } } else if ($this->application->build_pack === 'dockerimage') { - $this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}"); + $this->production_image_name = "{$this->dockerImage}:{$this->dockerImageTag}"; } else if ($this->pull_request_id !== 0) { if ($this->application->docker_registry_image_name) { - $this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}-build"); - $this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}"); + $this->build_image_name = "{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}-build"; + $this->production_image_name = "{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}"; } else { - $this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build"); - $this->production_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}"); + $this->build_image_name = "{$this->application->uuid}:pr-{$this->pull_request_id}-build"; + $this->production_image_name = "{$this->application->uuid}:pr-{$this->pull_request_id}"; } } else { $this->dockerImageTag = str($this->commit)->substr(0, 128); @@ -668,11 +673,11 @@ private function generate_image_names() $this->dockerImageTag = $this->application->docker_registry_image_tag; } if ($this->application->docker_registry_image_name) { - $this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:{$this->dockerImageTag}-build"); - $this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:{$this->dockerImageTag}"); + $this->build_image_name = "{$this->application->docker_registry_image_name}:{$this->dockerImageTag}-build"; + $this->production_image_name = "{$this->application->docker_registry_image_name}:{$this->dockerImageTag}"; } else { - $this->build_image_name = Str::lower("{$this->application->uuid}:{$this->dockerImageTag}-build"); - $this->production_image_name = Str::lower("{$this->application->uuid}:{$this->dockerImageTag}"); + $this->build_image_name = "{$this->application->uuid}:{$this->dockerImageTag}-build"; + $this->production_image_name = "{$this->application->uuid}:{$this->dockerImageTag}"; } } } @@ -1001,11 +1006,6 @@ private function generate_git_import_commands() return $commands; } - private function set_git_import_settings($git_clone_command) - { - return $this->application->setGitImportSettings($this->deployment_uuid, $git_clone_command); - } - private function cleanup_git() { $this->execute_remote_command( @@ -1814,7 +1814,6 @@ private function next(string $status) public function failed(Throwable $exception): void { - $this->next(ApplicationDeploymentStatus::FAILED->value); $this->application_deployment_queue->addLogEntry("Oops something is not okay, are you okay? 😢", 'stderr'); if (str($exception->getMessage())->isNotEmpty()) { diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index c6b8f2e51..7c3700107 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -163,18 +163,16 @@ public function loadComposeFile($isInit = false) } public function generateDomain(string $serviceName) { - $domain = $this->parsedServiceDomains[$serviceName]['domain'] ?? null; - if (!$domain) { - $uuid = new Cuid2(7); - $domain = generateFqdn($this->application->destination->server, $uuid); - $this->parsedServiceDomains[$serviceName]['domain'] = $domain; - $this->application->docker_compose_domains = json_encode($this->parsedServiceDomains); - $this->application->save(); - } + $uuid = new Cuid2(7); + $domain = generateFqdn($this->application->destination->server, $uuid); + $this->parsedServiceDomains[$serviceName]['domain'] = $domain; + $this->application->docker_compose_domains = json_encode($this->parsedServiceDomains); + $this->application->save(); + $this->dispatch('success', 'Domain generated.'); return $domain; } - public function updatedApplicationBaseDirectory() { - raY('asdf'); + public function updatedApplicationBaseDirectory() + { if ($this->application->build_pack === 'dockercompose') { $this->loadComposeFile(); } @@ -206,30 +204,47 @@ public function getWildcardDomain() $fqdn = generateFqdn($server, $this->application->uuid); $this->application->fqdn = $fqdn; $this->application->save(); - $this->updatedApplicationFqdn(); + $this->dispatch('success', 'Wildcard domain generated.'); } } - public function resetDefaultLabels($showToaster = true) + public function resetDefaultLabels() { $this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n"); $this->ports_exposes = $this->application->ports_exposes; - $this->submit($showToaster); + + $this->application->custom_labels = base64_encode($this->customLabels); + $this->application->save(); } - public function updatedApplicationFqdn() + public function checkFqdns($showToaster = true) { - $this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim(); - $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim(); - $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) { - return str($domain)->trim()->lower(); - }); - $this->application->fqdn = $this->application->fqdn->unique()->implode(','); - $this->application->save(); - $this->resetDefaultLabels(false); + if (data_get($this->application, 'fqdn')) { + $domains = str($this->application->fqdn)->trim()->explode(','); + if ($this->application->additional_servers->count() === 0) { + foreach ($domains as $domain) { + if (!validate_dns_entry($domain, $this->application->destination->server)) { + $showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.

Check this documentation for further help."); + } + } + } + check_domain_usage(resource: $this->application); + $this->application->fqdn = $domains->implode(','); + } } public function submit($showToaster = true) { try { + $this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim(); + $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim(); + $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) { + return str($domain)->trim()->lower(); + }); + $this->application->fqdn = $this->application->fqdn->unique()->implode(','); + + $this->checkFqdns(); + + $this->application->save(); + if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') { $this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n"); $this->application->custom_labels = base64_encode($this->customLabels); @@ -241,25 +256,14 @@ public function submit($showToaster = true) } $this->validate(); if ($this->ports_exposes !== $this->application->ports_exposes) { - $this->resetDefaultLabels(false); + $this->resetDefaultLabels(); } if (data_get($this->application, 'build_pack') === 'dockerimage') { $this->validate([ 'application.docker_registry_image_name' => 'required', ]); } - if (data_get($this->application, 'fqdn')) { - $domains = str($this->application->fqdn)->trim()->explode(','); - if ($this->application->additional_servers->count() === 0) { - foreach ($domains as $domain) { - if (!validate_dns_entry($domain, $this->application->destination->server)) { - $showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.

Check this documentation for further help."); - } - } - } - check_fqdn_usage($this->application); - $this->application->fqdn = $domains->implode(','); - } + if (data_get($this->application, 'custom_docker_run_options')) { $this->application->custom_docker_run_options = str($this->application->custom_docker_run_options)->trim(); } @@ -277,6 +281,15 @@ public function submit($showToaster = true) } if ($this->application->build_pack === 'dockercompose') { $this->application->docker_compose_domains = json_encode($this->parsedServiceDomains); + foreach ($this->parsedServiceDomains as $serviceName => $service) { + $domain = data_get($service, 'domain'); + if ($domain) { + if (!validate_dns_entry($domain, $this->application->destination->server)) { + $showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.

Check this documentation for further help."); + } + check_domain_usage(resource: $this->application); + } + } if ($this->application->settings->is_raw_compose_deployment_enabled) { $this->application->parseRawCompose(); } else { diff --git a/app/Livewire/Project/Service/Index.php b/app/Livewire/Project/Service/Index.php index ede42c2c7..fe335afb1 100644 --- a/app/Livewire/Project/Service/Index.php +++ b/app/Livewire/Project/Service/Index.php @@ -10,7 +10,7 @@ class Index extends Component { - public Service $service; + public ?Service $service = null; public ?ServiceApplication $serviceApplication = null; public ?ServiceDatabase $serviceDatabase = null; public array $parameters; @@ -26,7 +26,10 @@ public function mount() $this->services = collect([]); $this->parameters = get_route_parameters(); $this->query = request()->query(); - $this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail(); + $this->service = Service::whereUuid($this->parameters['service_uuid'])->first(); + if (!$this->service) { + return redirect()->route('dashboard'); + } $service = $this->service->applications()->whereUuid($this->parameters['stack_service_uuid'])->first(); if ($service) { $this->serviceApplication = $service; diff --git a/app/Livewire/Project/Service/ServiceApplicationView.php b/app/Livewire/Project/Service/ServiceApplicationView.php index 0986987f9..dfa2baced 100644 --- a/app/Livewire/Project/Service/ServiceApplicationView.php +++ b/app/Livewire/Project/Service/ServiceApplicationView.php @@ -65,12 +65,12 @@ public function mount() public function submit() { try { - check_fqdn_usage($this->application); + check_domain_usage(resource: $this->application); $this->validate(); $this->application->save(); updateCompose($this->application); if (str($this->application->fqdn)->contains(',')) { - $this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.'); + $this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.

Only use multiple domains if you know what you are doing.'); } else { $this->dispatch('success', 'Service saved.'); } diff --git a/app/Livewire/Settings/Configuration.php b/app/Livewire/Settings/Configuration.php index 79ff3bcb6..fbc88785d 100644 --- a/app/Livewire/Settings/Configuration.php +++ b/app/Livewire/Settings/Configuration.php @@ -59,23 +59,37 @@ public function instantSave() public function submit() { - $this->resetErrorBag(); - if ($this->settings->public_port_min > $this->settings->public_port_max) { - $this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.'); - return; + try { + $error_show = false; + $this->server = Server::findOrFail(0); + $this->resetErrorBag(); + if ($this->settings->public_port_min > $this->settings->public_port_max) { + $this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.'); + return; + } + $this->validate(); + + if ($this->settings->is_dns_validation_enabled) { + if (!validate_dns_entry($this->settings->fqdn, $this->server)) { + $this->dispatch('error', "Validating DNS ({$this->settings->fqdn}) failed.

Make sure you have added the DNS records correctly.

Check this documentation for further help."); + $error_show = true; + } + } + check_domain_usage(domain: $this->settings->fqdn); + $this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->replaceEnd(',', '')->trim(); + $this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->trim()->explode(',')->map(function ($dns) { + return str($dns)->trim()->lower(); + }); + $this->settings->custom_dns_servers = $this->settings->custom_dns_servers->unique(); + $this->settings->custom_dns_servers = $this->settings->custom_dns_servers->implode(','); + + $this->settings->save(); + $this->server->setupDynamicProxyConfiguration(); + if (!$error_show) { + $this->dispatch('success', 'Instance settings updated successfully!'); + } + } catch (\Exception $e) { + return handleError($e, $this); } - $this->validate(); - - $this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->replaceEnd(',', '')->trim(); - $this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->trim()->explode(',')->map(function ($dns) { - return str($dns)->trim()->lower(); - }); - $this->settings->custom_dns_servers = $this->settings->custom_dns_servers->unique(); - $this->settings->custom_dns_servers = $this->settings->custom_dns_servers->implode(','); - - $this->settings->save(); - $this->server = Server::findOrFail(0); - $this->server->setupDynamicProxyConfiguration(); - $this->dispatch('success', 'Instance settings updated successfully!'); } } diff --git a/app/Livewire/Upgrade.php b/app/Livewire/Upgrade.php index 37c2a64ef..96ee76325 100644 --- a/app/Livewire/Upgrade.php +++ b/app/Livewire/Upgrade.php @@ -32,10 +32,10 @@ public function checkUpdate() public function upgrade() { try { - $this->rateLimit(1, 30); if ($this->showProgress) { return; } + $this->rateLimit(1, 30); $this->showProgress = true; UpdateCoolify::run(force: true, async: true); $this->dispatch('success', "Updating Coolify to {$this->latestVersion} version..."); diff --git a/app/Models/Application.php b/app/Models/Application.php index 4d330a00c..3ebe80ee3 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -598,21 +598,30 @@ function generateGitImportCommands(string $deployment_uuid, int $pull_request_id } else { $github_access_token = generate_github_installation_token($this->source); if ($exec_in_docker) { - $commands->push(executeInDocker($deployment_uuid, "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git {$baseDir}")); + $git_clone_command = "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git {$baseDir}"; $fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git"; } else { - $commands->push("{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository} {$baseDir}"); + $git_clone_command = "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository} {$baseDir}"; $fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}"; } + $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false); + if ($exec_in_docker) { + $commands->push(executeInDocker($deployment_uuid, $git_clone_command)); + } else { + $commands->push($git_clone_command); + } } if ($pull_request_id !== 0) { $branch = "pull/{$pull_request_id}/head:$pr_branch_name"; + + $git_checkout_command = $this->buildGitCheckoutCommand($pr_branch_name); if ($exec_in_docker) { - $commands->push(executeInDocker($deployment_uuid, "cd {$baseDir} && git fetch origin {$branch} && git checkout $pr_branch_name")); + $commands->push(executeInDocker($deployment_uuid, "cd {$baseDir} && git fetch origin {$branch} && $git_checkout_command")); } else { - $commands->push("cd {$baseDir} && git fetch origin {$branch} && git checkout $pr_branch_name"); + $commands->push("cd {$baseDir} && git fetch origin {$branch} && $git_checkout_command"); } } + ray($commands); return [ 'commands' => $commands->implode(' && '), 'branch' => $branch, @@ -654,7 +663,7 @@ function generateGitImportCommands(string $deployment_uuid, int $pull_request_id } else { $commands->push("echo 'Checking out $branch'"); } - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name"; + $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name); } else if ($git_type === 'github') { $branch = "pull/{$pull_request_id}/head:$pr_branch_name"; if ($exec_in_docker) { @@ -662,14 +671,14 @@ function generateGitImportCommands(string $deployment_uuid, int $pull_request_id } else { $commands->push("echo 'Checking out $branch'"); } - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name"; + $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name); } else if ($git_type === 'bitbucket') { if ($exec_in_docker) { $commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'")); } else { $commands->push("echo 'Checking out $branch'"); } - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git checkout $commit"; + $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" " . $this->buildGitCheckoutCommand($commit); } } @@ -697,7 +706,7 @@ function generateGitImportCommands(string $deployment_uuid, int $pull_request_id } else { $commands->push("echo 'Checking out $branch'"); } - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name"; + $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name); } else if ($git_type === 'github') { $branch = "pull/{$pull_request_id}/head:$pr_branch_name"; if ($exec_in_docker) { @@ -705,14 +714,14 @@ function generateGitImportCommands(string $deployment_uuid, int $pull_request_id } else { $commands->push("echo 'Checking out $branch'"); } - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name"; + $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name); } else if ($git_type === 'bitbucket') { if ($exec_in_docker) { $commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'")); } else { $commands->push("echo 'Checking out $branch'"); } - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git checkout $commit"; + $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" " . $this->buildGitCheckoutCommand($commit); } } @@ -904,6 +913,16 @@ public function fqdns(): Attribute : explode(',', $this->fqdn), ); } + protected function buildGitCheckoutCommand($target): string + { + $command = "git checkout $target"; + + if ($this->settings->is_git_submodules_enabled) { + $command .= " && git submodule update --init --recursive"; + } + + return $command; + } public function watchPaths(): Attribute { return Attribute::make( diff --git a/app/Models/Server.php b/app/Models/Server.php index cbe895936..bcb06954a 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -550,21 +550,21 @@ public function startUnmanaged($id) } public function loadUnmanagedContainers() { - if ($this->isFunctional()) { - $containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this); - $containers = format_docker_command_output_to_json($containers); - $containers = $containers->map(function ($container) { - $labels = data_get($container, 'Labels'); - if (!str($labels)->contains("coolify.managed")) { - return $container; - } - return null; - }); - $containers = $containers->filter(); - return collect($containers); - } else { - return collect([]); - } + if ($this->isFunctional()) { + $containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this); + $containers = format_docker_command_output_to_json($containers); + $containers = $containers->map(function ($container) { + $labels = data_get($container, 'Labels'); + if (!str($labels)->contains("coolify.managed")) { + return $container; + } + return null; + }); + $containers = $containers->filter(); + return collect($containers); + } else { + return collect([]); + } } public function hasDefinedResources() { @@ -690,7 +690,13 @@ public function isProxyShouldRun() } public function isFunctional() { - return $this->settings->is_reachable && $this->settings->is_usable && !$this->settings->force_disabled; + $isFunctional = $this->settings->is_reachable && $this->settings->is_usable && !$this->settings->force_disabled; + ['private_key_filename' => $private_key_filename, 'mux_filename' => $mux_filename] = server_ssh_configuration($this); + if (!$isFunctional) { + Storage::disk('ssh-keys')->delete($private_key_filename); + Storage::disk('ssh-mux')->delete($mux_filename); + } + return $isFunctional; } public function isLogDrainEnabled() { diff --git a/app/Notifications/Server/Revived.php b/app/Notifications/Server/Revived.php index 3e5a99425..c670ded9a 100644 --- a/app/Notifications/Server/Revived.php +++ b/app/Notifications/Server/Revived.php @@ -2,6 +2,7 @@ namespace App\Notifications\Server; +use App\Jobs\ContainerStatusJob; use App\Models\Server; use Illuminate\Bus\Queueable; use App\Notifications\Channels\DiscordChannel; @@ -21,6 +22,7 @@ public function __construct(public Server $server) if ($this->server->unreachable_notification_sent === false) { return; } + dispatch(new ContainerStatusJob($server)); } public function via(object $notifiable): array diff --git a/bootstrap/helpers/constants.php b/bootstrap/helpers/constants.php index 84997d134..ee9a74b72 100644 --- a/bootstrap/helpers/constants.php +++ b/bootstrap/helpers/constants.php @@ -37,7 +37,7 @@ // Based on /etc/os-release const SUPPORTED_OS = [ 'ubuntu debian raspbian', - 'centos fedora rhel ol rocky', + 'centos fedora rhel ol rocky amzn', 'sles opensuse-leap opensuse-tumbleweed' ]; diff --git a/bootstrap/helpers/remoteProcess.php b/bootstrap/helpers/remoteProcess.php index 0aba82ea1..0a51b3ffc 100644 --- a/bootstrap/helpers/remoteProcess.php +++ b/bootstrap/helpers/remoteProcess.php @@ -55,17 +55,30 @@ function remote_process( ), ])(); } - +function server_ssh_configuration(Server $server) +{ + $uuid = data_get($server, 'uuid'); + if (is_null($uuid)) { + throw new \Exception("Server does not have a uuid"); + } + $private_key_filename = "id.root@{$server->uuid}"; + $location = '/var/www/html/storage/app/ssh/keys/' . $private_key_filename; + $mux_filename = '/var/www/html/storage/app/ssh/mux/' . $server->muxFilename(); + return [ + 'location' => $location, + 'mux_filename' => $mux_filename, + 'private_key_filename' => $private_key_filename + ]; +} function savePrivateKeyToFs(Server $server) { if (data_get($server, 'privateKey.private_key') === null) { throw new \Exception("Server {$server->name} does not have a private key"); } - $sshKeyFileLocation = "id.root@{$server->uuid}"; + ['location' => $location, 'private_key_filename' => $private_key_filename] = server_ssh_configuration($server); Storage::disk('ssh-keys')->makeDirectory('.'); Storage::disk('ssh-mux')->makeDirectory('.'); - Storage::disk('ssh-keys')->put($sshKeyFileLocation, $server->privateKey->private_key); - $location = '/var/www/html/storage/app/ssh/keys/' . $sshKeyFileLocation; + Storage::disk('ssh-keys')->put($private_key_filename, $server->privateKey->private_key); return $location; } @@ -223,6 +236,13 @@ function remove_iip($text) $text = preg_replace('/x-access-token:.*?(?=@)/', "x-access-token:" . REDACTED, $text); return preg_replace('/\x1b\[[0-9;]*m/', '', $text); } +function remove_mux_and_private_key(Server $server) +{ + $muxFilename = $server->muxFilename(); + $privateKeyLocation = savePrivateKeyToFs($server); + Storage::disk('ssh-mux')->delete($muxFilename); + Storage::disk('ssh-keys')->delete($privateKeyLocation); +} function refresh_server_connection(?PrivateKey $private_key = null) { if (is_null($private_key)) { diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php index 7cb8595db..b690659dd 100644 --- a/bootstrap/helpers/services.php +++ b/bootstrap/helpers/services.php @@ -88,33 +88,106 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) $dockerCompose = Yaml::parse($dockerComposeRaw); // Switch Image - $image = data_get($resource, 'image'); - data_set($dockerCompose, "services.{$name}.image", $image); - $dockerComposeRaw = Yaml::dump($dockerCompose, 10, 2); - $resource->service->docker_compose_raw = $dockerComposeRaw; - $resource->service->save(); - - if ($resource->fqdn && !str($resource->fqdn)->contains(',')) { - // Update FQDN - $variableName = "SERVICE_FQDN_" . Str::of($resource->name)->upper(); - $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); - $fqdn = Url::fromString($resource->fqdn); - $fqdn = $fqdn->getScheme() . '://' . $fqdn->getHost(); - if ($generatedEnv) { - $generatedEnv->value = $fqdn; - $generatedEnv->save(); - } - $variableName = "SERVICE_URL_" . Str::of($resource->name)->upper(); - $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); - $url = Url::fromString($resource->fqdn); - $url = $url->getHost(); - if ($generatedEnv) { - $url = Str::of($resource->fqdn)->after('://'); - $generatedEnv->value = $url; - $generatedEnv->save(); + $updatedImage = data_get_str($resource, 'image'); + $currentImage = data_get_str($dockerCompose, "services.{$name}.image"); + if ($currentImage !== $updatedImage) { + data_set($dockerCompose, "services.{$name}.image", $updatedImage->value()); + $dockerComposeRaw = Yaml::dump($dockerCompose, 10, 2); + $resource->service->docker_compose_raw = $dockerComposeRaw; + $resource->service->save(); + $resource->image = $updatedImage; + $resource->save(); + } + if ($resource->fqdn) { + $resourceFqdns = str($resource->fqdn)->explode(','); + if ($resourceFqdns->count() === 1) { + $resourceFqdns = $resourceFqdns->first(); + $variableName = "SERVICE_FQDN_" . Str::of($resource->name)->upper()->replace('-', ''); + $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); + $fqdn = Url::fromString($resourceFqdns); + $port = $fqdn->getPort(); + $fqdn = $fqdn->getScheme() . '://' . $fqdn->getHost(); + if ($generatedEnv) { + $generatedEnv->value = $fqdn; + $generatedEnv->save(); + } + if ($port) { + $variableName = $variableName . "_$port"; + $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); + if ($generatedEnv) { + $generatedEnv->value = $fqdn . ':' . $port; + $generatedEnv->save(); + } + } + $variableName = "SERVICE_URL_" . Str::of($resource->name)->upper()->replace('-', ''); + $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); + $url = Url::fromString($fqdn); + $port = $url->getPort(); + $url = $url->getHost(); + if ($generatedEnv) { + $url = Str::of($fqdn)->after('://'); + $generatedEnv->value = $url; + $generatedEnv->save(); + } + if ($port) { + $variableName = $variableName . "_$port"; + $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); + if ($generatedEnv) { + $generatedEnv->value = $url . ':' . $port; + $generatedEnv->save(); + } + } + } else if ($resourceFqdns->count() > 1) { + foreach ($resourceFqdns as $fqdn) { + $host = Url::fromString($fqdn); + $port = $host->getPort(); + $url = $host->getHost(); + $host = $host->getScheme() . '://' . $host->getHost(); + if ($port) { + $port_envs = EnvironmentVariable::where('service_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('service_id', $resource->service_id)->where('key', 'SERVICE_FQDN_' . $service_fqdn)->first(); + if ($env) { + $env->value = $host; + $env->save(); + } + $port_env->value = $host . ':' . $port; + $port_env->save(); + } + $port_envs_url = EnvironmentVariable::where('service_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('service_id', $resource->service_id)->where('key', 'SERVICE_URL_' . $service_url)->first(); + if ($env) { + $env->value = $url; + $env->save(); + } + $port_env_url->value = $url . ':' . $port; + $port_env_url->save(); + } + } else { + $variableName = "SERVICE_FQDN_" . Str::of($resource->name)->upper()->replace('-', ''); + $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); + $fqdn = Url::fromString($fqdn); + $fqdn = $fqdn->getScheme() . '://' . $fqdn->getHost(); + if ($generatedEnv) { + $generatedEnv->value = $fqdn; + $generatedEnv->save(); + } + $variableName = "SERVICE_URL_" . Str::of($resource->name)->upper()->replace('-', ''); + $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); + $url = Url::fromString($fqdn); + $url = $url->getHost(); + if ($generatedEnv) { + $url = Str::of($fqdn)->after('://'); + $generatedEnv->value = $url; + $generatedEnv->save(); + } + } + } } } - } catch (\Throwable $e) { return handleError($e); } diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 458dbae85..d40c707e4 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -443,7 +443,7 @@ function sslip(Server $server) function getServiceTemplates() { - if (!isDev()) { + if (isDev()) { $services = File::get(base_path('templates/service-templates.json')); $services = collect(json_decode($services))->sortKeys(); } else { @@ -1168,6 +1168,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal // }); // ray($withoutServiceEnvs); // data_set($service, 'environment', $withoutServiceEnvs->toArray()); + updateCompose($savedService); return $service; }); $finalServices = [ @@ -1856,13 +1857,26 @@ function ip_match($ip, $cidrs, &$match = null) } return false; } -function check_fqdn_usage(ServiceApplication|Application $own_resource) +function check_domain_usage(ServiceApplication|Application|null $resource = null, ?string $domain = null) { - $domains = collect($own_resource->fqdns)->map(function ($domain) { + if ($resource) { + if ($resource->getMorphClass() === 'App\Models\Application' && $resource->build_pack === 'dockercompose') { + $domains = data_get(json_decode($resource->docker_compose_domains, true), "*.domain"); + ray($domains); + $domains = collect($domains); + } else { + $domains = collect($resource->fqdns); + } + } else if ($domain) { + $domains = collect($domain); + } else { + throw new \RuntimeException("No resource or FQDN provided."); + } + $domains = $domains->map(function ($domain) { if (str($domain)->endsWith('/')) { $domain = str($domain)->beforeLast('/'); } - return str($domain)->replace('http://', '')->replace('https://', ''); + return str($domain); }); $apps = Application::all(); foreach ($apps as $app) { @@ -1871,10 +1885,15 @@ function check_fqdn_usage(ServiceApplication|Application $own_resource) if (str($domain)->endsWith('/')) { $domain = str($domain)->beforeLast('/'); } - $naked_domain = str($domain)->replace('http://', '')->replace('https://', '')->value(); + $naked_domain = str($domain)->value(); if ($domains->contains($naked_domain)) { - if ($app->uuid !== $own_resource->uuid) { - throw new \RuntimeException("Domain $naked_domain is already in use by another resource:
{$app->name}."); + if (data_get($resource, 'uuid')) { + ray($resource->uuid, $app->uuid); + if ($resource->uuid !== $app->uuid) { + throw new \RuntimeException("Domain $naked_domain is already in use by another resource called:

{$app->name}."); + } + } else if ($domain) { + throw new \RuntimeException("Domain $naked_domain is already in use by another resource called:

{$app->name}."); } } } @@ -1886,12 +1905,29 @@ function check_fqdn_usage(ServiceApplication|Application $own_resource) if (str($domain)->endsWith('/')) { $domain = str($domain)->beforeLast('/'); } - $naked_domain = str($domain)->replace('http://', '')->replace('https://', '')->value(); + $naked_domain = str($domain)->value(); if ($domains->contains($naked_domain)) { - if ($app->uuid !== $own_resource->uuid) { - throw new \RuntimeException("Domain $naked_domain is already in use by another resource."); + if (data_get($resource, 'uuid')) { + if ($resource->uuid !== $app->uuid) { + throw new \RuntimeException("Domain $naked_domain is already in use by another resource called:

{$app->name}."); + } + } else if ($domain) { + throw new \RuntimeException("Domain $naked_domain is already in use by another resource called:

{$app->name}."); } } } } + if ($resource) { + $settings = InstanceSettings::get(); + if (data_get($settings, 'fqdn')) { + $domain = data_get($settings, 'fqdn'); + if (str($domain)->endsWith('/')) { + $domain = str($domain)->beforeLast('/'); + } + $naked_domain = str($domain)->value(); + if ($domains->contains($naked_domain)) { + throw new \RuntimeException("Domain $naked_domain is already in use by this Coolify instance."); + } + } + } } diff --git a/config/sentry.php b/config/sentry.php index a3fbc0a3a..5250d3547 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.251', + 'release' => '4.0.0-beta.252', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index d88e65a1b..a0ca28c13 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ @endif @foreach ($enabled_oauth_providers as $provider_setting) - {{ __("auth.login.$provider_setting->provider") }} diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index aae13b4c8..96af53d3f 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -1,4 +1,4 @@ -