diff --git a/app/Actions/Proxy/CheckConfiguration.php b/app/Actions/Proxy/CheckConfiguration.php index 208a6863a..5ce7e1e87 100644 --- a/app/Actions/Proxy/CheckConfiguration.php +++ b/app/Actions/Proxy/CheckConfiguration.php @@ -16,11 +16,19 @@ class CheckConfiguration return 'OK'; } $proxy_path = $server->proxyPath(); - - $proxy_configuration = instant_remote_process([ - "mkdir -p $proxy_path", - "cat $proxy_path/docker-compose.yml", - ], $server, false); + if ($server->isNonRoot()) { + $payload = [ + "mkdir -p $proxy_path", + "chown -R $server->user:$server->user $proxy_path", + "cat $proxy_path/docker-compose.yml", + ]; + } else { + $payload = [ + "mkdir -p $proxy_path", + "cat $proxy_path/docker-compose.yml", + ]; + } + $proxy_configuration = instant_remote_process($payload, $server, false); if ($reset || !$proxy_configuration || is_null($proxy_configuration)) { $proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value; diff --git a/app/Actions/Proxy/CheckProxy.php b/app/Actions/Proxy/CheckProxy.php index d39b33a2a..9323ca1d1 100644 --- a/app/Actions/Proxy/CheckProxy.php +++ b/app/Actions/Proxy/CheckProxy.php @@ -13,8 +13,9 @@ class CheckProxy if ($server->proxyType() === 'NONE') { return false; } - if (!$server->validateConnection()) { - throw new \Exception("Server Connection Error"); + ['uptime' => $uptime, 'error' => $error] = $server->validateConnection(); + if (!$uptime) { + throw new \Exception($error); } if (!$server->isProxyShouldRun()) { if ($fromUI) { diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index 333d46027..a9cec009b 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -47,7 +47,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted if (!$this->server->isFunctional()) { return 'Server is not ready.'; }; - $applications = $this->server->applications(); $skip_these_applications = collect([]); foreach ($applications as $application) { @@ -78,6 +77,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted if (is_null($containers)) { return; } + $containers = format_docker_command_output_to_json($containers); if ($containerReplicates) { $containerReplicates = format_docker_command_output_to_json($containerReplicates); @@ -201,7 +201,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted // Notify user that this container should not be there. } } - } if (data_get($container, 'Name') === '/coolify-db') { $foundDatabases[] = 0; diff --git a/app/Livewire/NewActivityMonitor.php b/app/Livewire/NewActivityMonitor.php index 8f2dba0da..853115888 100644 --- a/app/Livewire/NewActivityMonitor.php +++ b/app/Livewire/NewActivityMonitor.php @@ -11,15 +11,17 @@ class NewActivityMonitor extends Component public ?string $header = null; public $activityId; public $eventToDispatch = 'activityFinished'; + public $eventData = null; public $isPollingActive = false; protected $activity; protected $listeners = ['newActivityMonitor' => 'newMonitorActivity']; - public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished') + public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished', $eventData = null) { $this->activityId = $activityId; $this->eventToDispatch = $eventToDispatch; + $this->eventData = $eventData; $this->hydrateActivity(); @@ -55,8 +57,12 @@ class NewActivityMonitor extends Component } return; } - $this->dispatch($this->eventToDispatch); - ray('Dispatched event: ' . $this->eventToDispatch); + if (!is_null($this->eventData)) { + $this->dispatch($this->eventToDispatch, $this->eventData); + } else { + $this->dispatch($this->eventToDispatch); + } + ray('Dispatched event: ' . $this->eventToDispatch . ' with data: ' . $this->eventData); } } } diff --git a/app/Livewire/Server/Form.php b/app/Livewire/Server/Form.php index e51aff8a3..14a2809c7 100644 --- a/app/Livewire/Server/Form.php +++ b/app/Livewire/Server/Form.php @@ -76,14 +76,14 @@ class Form extends Component public function checkLocalhostConnection() { $this->submit(); - $uptime = $this->server->validateConnection(); + ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection(); if ($uptime) { $this->dispatch('success', 'Server is reachable.'); $this->server->settings->is_reachable = true; $this->server->settings->is_usable = true; $this->server->settings->save(); } else { - $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.

Check this documentation for further help.'); + $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.

Check this documentation for further help.

Error: ' . $error); return; } } diff --git a/app/Livewire/Server/ShowPrivateKey.php b/app/Livewire/Server/ShowPrivateKey.php index d44d2cb5f..e0474f2c4 100644 --- a/app/Livewire/Server/ShowPrivateKey.php +++ b/app/Livewire/Server/ShowPrivateKey.php @@ -35,10 +35,11 @@ class ShowPrivateKey extends Component public function checkConnection() { try { - $uptime = $this->server->validateConnection(); + ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection(); if ($uptime) { $this->dispatch('success', 'Server is reachable.'); } else { + ray($error); $this->dispatch('error', 'Server is not reachable.
Please validate your configuration and connection.

Check this documentation for further help.'); return; } diff --git a/app/Livewire/Server/ValidateAndInstall.php b/app/Livewire/Server/ValidateAndInstall.php index 6d4173956..b148fe2c4 100644 --- a/app/Livewire/Server/ValidateAndInstall.php +++ b/app/Livewire/Server/ValidateAndInstall.php @@ -11,7 +11,7 @@ class ValidateAndInstall extends Component { public Server $server; public int $number_of_tries = 0; - public int $max_tries = 1; + public int $max_tries = 3; public bool $install = true; public $uptime = null; public $supported_os_type = null; @@ -32,9 +32,8 @@ class ValidateAndInstall extends Component 'refresh' => '$refresh', ]; - public function init(bool $install = true) + public function init(int $data = 0) { - $this->install = $install; $this->uptime = null; $this->supported_os_type = null; $this->docker_installed = null; @@ -42,7 +41,7 @@ class ValidateAndInstall extends Component $this->docker_compose_installed = null; $this->proxy_started = null; $this->error = null; - $this->number_of_tries = 0; + $this->number_of_tries = $data; if (!$this->ask) { $this->dispatch('validateConnection'); } @@ -66,16 +65,15 @@ class ValidateAndInstall extends Component } else { $this->proxy_started = true; } - } catch (\Throwable $e) { return handleError($e, $this); } } public function validateConnection() { - $this->uptime = $this->server->validateConnection(); + ['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection(); if (!$this->uptime) { - $this->error = 'Server is not reachable. Please validate your configuration and connection.

Check this documentation for further help.'; + $this->error = 'Server is not reachable. Please validate your configuration and connection.

Check this documentation for further help.

Error: ' . $error; return; } $this->dispatch('validateOS'); @@ -99,10 +97,10 @@ class ValidateAndInstall extends Component $this->error = 'Docker Engine could not be installed. Please install Docker manually before continuing: documentation.'; return; } else { - if ($this->number_of_tries == 0) { + if ($this->number_of_tries <= $this->max_tries) { $activity = $this->server->installDocker(); $this->number_of_tries++; - $this->dispatch('newActivityMonitor', $activity->id, 'init'); + $this->dispatch('newActivityMonitor', $activity->id, 'init', $this->number_of_tries); } return; } diff --git a/app/Models/Server.php b/app/Models/Server.php index 79c98ccf6..7afadecfc 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -481,8 +481,8 @@ $schema://$host { // ray('serverUptimeCheckNumber: ' . $serverUptimeCheckNumber); // ray('serverUptimeCheckNumberMax: ' . $serverUptimeCheckNumberMax); - $result = $this->validateConnection(); - if ($result) { + ['uptime' => $uptime] = $this->validateConnection(); + if ($uptime) { if ($this->unreachable_notification_sent === true) { $this->update(['unreachable_notification_sent' => false]); } @@ -551,7 +551,7 @@ $schema://$host { public function loadUnmanagedContainers() { if ($this->isFunctional()) { - $containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this); + $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'); @@ -748,34 +748,31 @@ $schema://$host { $server = Server::find($this->id); if (!$server) { - return false; + return ['uptime' => false, 'error' => 'Server not found.']; } if ($server->skipServer()) { - return false; + return ['uptime' => false, 'error' => 'Server skipped.']; } - // EC2 does not have `uptime` command, lol - - $uptime = instant_remote_process(['ls /'], $server, false); - if (!$uptime) { - $server->settings()->update([ - 'is_reachable' => false, - ]); - return false; - } else { + try { + // EC2 does not have `uptime` command, lol + instant_remote_process(['ls /'], $server); $server->settings()->update([ 'is_reachable' => true, ]); $server->update([ 'unreachable_count' => 0, ]); + if (data_get($server, 'unreachable_notification_sent') === true) { + $server->team?->notify(new Revived($server)); + $server->update(['unreachable_notification_sent' => false]); + } + return ['uptime' => true, 'error' => null]; + } catch (\Throwable $e) { + $server->settings()->update([ + 'is_reachable' => false, + ]); + return ['uptime' => false, 'error' => $e->getMessage()]; } - - if (data_get($server, 'unreachable_notification_sent') === true) { - $server->team?->notify(new Revived($server)); - $server->update(['unreachable_notification_sent' => false]); - } - - return true; } public function installDocker() { @@ -784,7 +781,7 @@ $schema://$host { } public function validateDockerEngine($throwError = false) { - $dockerBinary = instant_remote_process(["command -v docker"], $this, false); + $dockerBinary = instant_remote_process(["command -v docker"], $this, false, no_sudo: true); if (is_null($dockerBinary)) { $this->settings->is_usable = false; $this->settings->save(); @@ -861,4 +858,8 @@ $schema://$host { return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false); } } + public function isNonRoot() + { + return $this->user !== 'root'; + } } diff --git a/bootstrap/helpers/remoteProcess.php b/bootstrap/helpers/remoteProcess.php index 3edc92ecc..fac4b84f0 100644 --- a/bootstrap/helpers/remoteProcess.php +++ b/bootstrap/helpers/remoteProcess.php @@ -32,6 +32,33 @@ function remote_process( if ($command instanceof Collection) { $command = $command->toArray(); } + if ($server->isNonRoot()) { + $command = collect($command)->map(function ($line) { + if (!str($line)->startSwith('cd')) { + return "sudo $line"; + } + return $line; + })->toArray(); + $command = collect($command)->map(function ($line) use ($server) { + if (Str::startsWith($line, 'sudo mkdir -p')) { + return "$line && sudo chown -R $server->user:$server->user " . Str::after($line, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($line, 'sudo mkdir -p'); + } + return $line; + })->toArray(); + $command = collect($command)->map(function ($line) { + if (str($line)->contains('$(') || str($line)->contains('`')) { + return str($line)->replace('$(', '$(sudo ')->replace('`', '`sudo ')->value(); + } + if (str($line)->contains('||')) { + return str($line)->replace('||', '|| sudo ')->value(); + } + if (str($line)->contains('&&')) { + return str($line)->replace('&&', '&& sudo ')->value(); + } + return $line; + })->toArray(); + } + ray($command); $command_string = implode("\n", $command); if (auth()->user()) { $teams = auth()->user()->teams->pluck('id'); @@ -144,7 +171,6 @@ function generateSshCommand(Server $server, string $command) $ssh_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" '; } $command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command"; - $delimiter = Hash::make($command); $command = str_replace($delimiter, '', $command); $ssh_command .= "-i {$privateKeyLocation} " @@ -160,17 +186,42 @@ function generateSshCommand(Server $server, string $command) . $command . PHP_EOL . $delimiter; // ray($ssh_command); - // ray($delimiter); return $ssh_command; } -function instant_remote_process(Collection|array $command, Server $server, $throwError = true) +function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false) { $timeout = config('constants.ssh.command_timeout'); if ($command instanceof Collection) { $command = $command->toArray(); } + if ($server->isNonRoot() && !$no_sudo) { + $command = collect($command)->map(function ($line) { + if (!str($line)->startSwith('cd')) { + return "sudo $line"; + } + return $line; + })->toArray(); + $command = collect($command)->map(function ($line) use ($server) { + if (Str::startsWith($line, 'sudo mkdir -p')) { + return "$line && sudo chown -R $server->user:$server->user " . Str::after($line, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($line, 'sudo mkdir -p'); + } + return $line; + })->toArray(); + $command = collect($command)->map(function ($line) { + if (str($line)->contains('$(') || str($line)->contains('`')) { + return str($line)->replace('$(', '$(sudo ')->replace('`', '`sudo ')->value(); + } + if (str($line)->contains('||')) { + return str($line)->replace('||', '|| sudo ')->value(); + } + if (str($line)->contains('&&')) { + return str($line)->replace('&&', '&& sudo ')->value(); + } + return $line; + })->toArray(); + } $command_string = implode("\n", $command); - $ssh_command = generateSshCommand($server, $command_string); + $ssh_command = generateSshCommand($server, $command_string, $no_sudo); $process = Process::timeout($timeout)->run($ssh_command); $output = trim($process->output()); $exitCode = $process->exitCode(); diff --git a/resources/views/livewire/server/new/by-ip.blade.php b/resources/views/livewire/server/new/by-ip.blade.php index e3b5b6396..989283a3e 100644 --- a/resources/views/livewire/server/new/by-ip.blade.php +++ b/resources/views/livewire/server/new/by-ip.blade.php @@ -10,7 +10,7 @@
- + {{-- --}}
diff --git a/templates/service-templates.json b/templates/service-templates.json index dcef4f9da..91aee6510 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -618,7 +618,7 @@ "n8n": { "documentation": "https:\/\/n8n.io", "slogan": "n8n is an extendable workflow automation tool.", - "compose": "c2VydmljZXM6CiAgbjhuOgogICAgaW1hZ2U6IGRvY2tlci5uOG4uaW8vbjhuaW8vbjhuCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTjhOXzU2NzgKICAgICAgLSAnTjhOX0VESVRPUl9CQVNFX1VSTD0ke1NFUlZJQ0VfRlFETl9OOE59JwogICAgICAtICdXRUJIT09LX1VSTD0ke1NFUlZJQ0VfRlFETl9OOE59JwogICAgICAtICdOOE5fSE9TVD0ke1NFUlZJQ0VfVVJMX044Tn0nCiAgICAgIC0gJ0dFTkVSSUNfVElNRVpPTkU9IkV1cm9wZS9CZXJsaW4iJwogICAgICAtICdUWj0iRXVyb3BlL0JlcmxpbiInCiAgICB2b2x1bWVzOgogICAgICAtICduOG4tZGF0YTovaG9tZS9ub2RlLy5uOG4nCg==", + "compose": "c2VydmljZXM6CiAgbjhuOgogICAgaW1hZ2U6IGRvY2tlci5uOG4uaW8vbjhuaW8vbjhuCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTjhOXzU2NzgKICAgICAgLSAnTjhOX0VESVRPUl9CQVNFX1VSTD0ke1NFUlZJQ0VfRlFETl9OOE59JwogICAgICAtICdXRUJIT09LX1VSTD0ke1NFUlZJQ0VfRlFETl9OOE59JwogICAgICAtICdOOE5fSE9TVD0ke1NFUlZJQ0VfVVJMX044Tn0nCiAgICAgIC0gJ0dFTkVSSUNfVElNRVpPTkU9IkV1cm9wZS9CZXJsaW4iJwogICAgICAtICdUWj0iRXVyb3BlL0JlcmxpbiInCiAgICB2b2x1bWVzOgogICAgICAtICduOG4tZGF0YTovaG9tZS9ub2RlLy5uOG4nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3dnZXQgLXFPLSBodHRwOi8vbG9jYWxob3N0OjU2NzgvJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", "tags": [ "n8n", "workflow",