From dcaa7a6ad7a96a688523521e135e9b633c34d02e Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 9 Oct 2023 11:00:18 +0200 Subject: [PATCH] fix: server validation process --- app/Actions/Server/InstallDocker.php | 4 +- app/Http/Controllers/ServerController.php | 32 ------------ app/Http/Livewire/Boarding/Index.php | 2 +- app/Http/Livewire/Server/Create.php | 29 +++++++++++ app/Http/Livewire/Server/Destination/Show.php | 28 ++++++++++ app/Http/Livewire/Server/Form.php | 43 +++++++++------ app/Http/Livewire/Server/PrivateKey/Show.php | 31 +++++++++++ app/Http/Livewire/Server/Proxy/Deploy.php | 2 +- app/Http/Livewire/Server/Proxy/Show.php | 28 ++++++++++ app/Http/Livewire/Server/Proxy/Status.php | 6 ++- app/Http/Livewire/Server/Show.php | 6 +++ app/Http/Livewire/Server/ShowPrivateKey.php | 38 +++++++------- app/Jobs/ContainerStatusJob.php | 19 +++---- app/Models/Server.php | 52 +++++++++++++++++-- app/Notifications/Server/Revived.php | 19 ++++++- app/Notifications/Server/Unreachable.php | 19 ++++++- resources/css/app.css | 2 +- .../views/components/server/navbar.blade.php | 8 +-- .../emails/container-restarted.blade.php | 4 -- .../views/livewire/server/create.blade.php | 9 ++++ .../server/destination/show.blade.php | 4 ++ .../views/livewire/server/form.blade.php | 35 ++++++------- .../server/private-key/show.blade.php} | 6 +-- .../livewire/server/proxy/show.blade.php | 4 ++ .../views/livewire/server/show.blade.php | 6 +-- resources/views/server/all.blade.php | 3 -- resources/views/server/destinations.blade.php | 4 -- resources/views/server/proxy.blade.php | 4 -- resources/views/server/show.blade.php | 4 -- routes/web.php | 19 +++---- 30 files changed, 321 insertions(+), 149 deletions(-) delete mode 100644 app/Http/Controllers/ServerController.php create mode 100644 app/Http/Livewire/Server/Create.php create mode 100644 app/Http/Livewire/Server/Destination/Show.php create mode 100644 app/Http/Livewire/Server/PrivateKey/Show.php create mode 100644 app/Http/Livewire/Server/Proxy/Show.php create mode 100644 resources/views/livewire/server/create.blade.php create mode 100644 resources/views/livewire/server/destination/show.blade.php rename resources/views/{server/private-key.blade.php => livewire/server/private-key/show.blade.php} (51%) create mode 100644 resources/views/livewire/server/proxy/show.blade.php delete mode 100644 resources/views/server/all.blade.php delete mode 100644 resources/views/server/destinations.blade.php delete mode 100644 resources/views/server/proxy.blade.php delete mode 100644 resources/views/server/show.blade.php diff --git a/app/Actions/Server/InstallDocker.php b/app/Actions/Server/InstallDocker.php index 2fb12d34c..e99e8a11b 100644 --- a/app/Actions/Server/InstallDocker.php +++ b/app/Actions/Server/InstallDocker.php @@ -2,12 +2,14 @@ namespace App\Actions\Server; +use Lorisleiva\Actions\Concerns\AsAction; use App\Models\Server; use App\Models\StandaloneDocker; class InstallDocker { - public function __invoke(Server $server) + use AsAction; + public function handle(Server $server) { $dockerVersion = '24.0'; $config = base64_encode('{ diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php deleted file mode 100644 index 21ca2ba77..000000000 --- a/app/Http/Controllers/ServerController.php +++ /dev/null @@ -1,32 +0,0 @@ -get(); - if (!isCloud()) { - return view('server.create', [ - 'limit_reached' => false, - 'private_keys' => $privateKeys, - ]); - } - $team = currentTeam(); - $servers = $team->servers->count(); - ['serverLimit' => $serverLimit] = $team->limits; - $limit_reached = $servers >= $serverLimit; - - return view('server.create', [ - 'limit_reached' => $limit_reached, - 'private_keys' => $privateKeys, - ]); - } -} diff --git a/app/Http/Livewire/Boarding/Index.php b/app/Http/Livewire/Boarding/Index.php index a0c5038e0..c8a53d26e 100644 --- a/app/Http/Livewire/Boarding/Index.php +++ b/app/Http/Livewire/Boarding/Index.php @@ -220,7 +220,7 @@ public function validateServer() public function installDocker() { $this->dockerInstallationStarted = true; - $activity = resolve(InstallDocker::class)($this->createdServer); + $activity = InstallDocker::run($this->createdServer); $this->emit('newMonitorActivity', $activity->id); } public function dockerInstalledOrSkipped() diff --git a/app/Http/Livewire/Server/Create.php b/app/Http/Livewire/Server/Create.php new file mode 100644 index 000000000..3d1953513 --- /dev/null +++ b/app/Http/Livewire/Server/Create.php @@ -0,0 +1,29 @@ +private_keys = PrivateKey::ownedByCurrentTeam()->get(); + if (!isCloud()) { + $this->limit_reached = false; + return; + } + $team = currentTeam(); + $servers = $team->servers->count(); + ['serverLimit' => $serverLimit] = $team->limits; + + $this->limit_reached = $servers >= $serverLimit; + } + public function render() + { + return view('livewire.server.create'); + } +} diff --git a/app/Http/Livewire/Server/Destination/Show.php b/app/Http/Livewire/Server/Destination/Show.php new file mode 100644 index 000000000..e021f9605 --- /dev/null +++ b/app/Http/Livewire/Server/Destination/Show.php @@ -0,0 +1,28 @@ +parameters = get_route_parameters(); + try { + $this->server = Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->first(); + if (is_null($this->server)) { + return redirect()->route('server.all'); + } + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + public function render() + { + return view('livewire.server.destination.show'); + } +} diff --git a/app/Http/Livewire/Server/Form.php b/app/Http/Livewire/Server/Form.php index 9f6cb594f..18fb1dbec 100644 --- a/app/Http/Livewire/Server/Form.php +++ b/app/Http/Livewire/Server/Form.php @@ -11,11 +11,12 @@ class Form extends Component { use AuthorizesRequests; public Server $server; - public $uptime; - public $dockerVersion; - public string|null $wildcard_domain = null; + public bool $isValidConnection = false; + public bool $isValidDocker = false; + public ?string $wildcard_domain = null; public int $cleanup_after_percentage; public bool $dockerInstallationStarted = false; + protected $listeners = ['serverRefresh']; protected $rules = [ 'server.name' => 'required|min:6', @@ -44,37 +45,49 @@ public function mount() $this->wildcard_domain = $this->server->settings->wildcard_domain; $this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage; } - public function instantSave() { + public function serverRefresh() { + $this->validateServer(); + } + public function instantSave() + { refresh_server_connection($this->server->privateKey); $this->validateServer(); $this->server->settings->save(); } public function installDocker() { + $this->emit('installDocker'); $this->dockerInstallationStarted = true; - $activity = resolve(InstallDocker::class)($this->server); + $activity = InstallDocker::run($this->server); $this->emit('newMonitorActivity', $activity->id); } - public function validateServer() + public function validateServer($install = true) { try { - ['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true); + $uptime = $this->server->validateConnection(); if ($uptime) { - $this->uptime = $uptime; - $this->emit('success', 'Server is reachable.'); + $install && $this->emit('success', 'Server is reachable.'); } else { - $this->emit('error', 'Server is not reachable.'); + $install &&$this->emit('error', 'Server is not reachable. Please check your connection and private key configuration.'); return; } - if ($dockerVersion) { - $this->dockerVersion = $dockerVersion; - $this->emit('success', 'Docker Engine 23+ is installed!'); + $dockerInstalled = $this->server->validateDockerEngine(); + if ($dockerInstalled) { + $install && $this->emit('success', 'Docker Engine is installed.
Checking version.'); } else { - $this->emit('error', 'No Docker Engine or older than 23 version installed.'); + $install && $this->installDocker(); + return; + } + $dockerVersion = $this->server->validateDockerEngineVersion(); + if ($dockerVersion) { + $install && $this->emit('success', 'Docker Engine version is 23+.'); + } else { + $install && $this->installDocker(); + return; } } catch (\Throwable $e) { - return handleError($e, $this, customErrorMessage: "Server is not reachable: "); + return handleError($e, $this); } finally { $this->emit('proxyStatusUpdated'); } diff --git a/app/Http/Livewire/Server/PrivateKey/Show.php b/app/Http/Livewire/Server/PrivateKey/Show.php new file mode 100644 index 000000000..51c91350e --- /dev/null +++ b/app/Http/Livewire/Server/PrivateKey/Show.php @@ -0,0 +1,31 @@ +parameters = get_route_parameters(); + try { + $this->server = Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->first(); + if (is_null($this->server)) { + return redirect()->route('server.all'); + } + $this->privateKeys = PrivateKey::ownedByCurrentTeam()->get()->where('is_git_related', false); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + public function render() + { + return view('livewire.server.private-key.show'); + } +} diff --git a/app/Http/Livewire/Server/Proxy/Deploy.php b/app/Http/Livewire/Server/Proxy/Deploy.php index 85899d7b7..ad5884e18 100644 --- a/app/Http/Livewire/Server/Proxy/Deploy.php +++ b/app/Http/Livewire/Server/Proxy/Deploy.php @@ -11,7 +11,7 @@ class Deploy extends Component public Server $server; public bool $traefikDashboardAvailable = false; public ?string $currentRoute = null; - protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable']; + protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable', 'serverRefresh' => 'proxyStatusUpdated']; public function mount() { $this->currentRoute = request()->route()->getName(); diff --git a/app/Http/Livewire/Server/Proxy/Show.php b/app/Http/Livewire/Server/Proxy/Show.php new file mode 100644 index 000000000..daadf0ede --- /dev/null +++ b/app/Http/Livewire/Server/Proxy/Show.php @@ -0,0 +1,28 @@ +parameters = get_route_parameters(); + try { + $this->server = Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->first(); + if (is_null($this->server)) { + return redirect()->route('server.all'); + } + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + public function render() + { + return view('livewire.server.proxy.show'); + } +} diff --git a/app/Http/Livewire/Server/Proxy/Status.php b/app/Http/Livewire/Server/Proxy/Status.php index 6eb198a4d..5cfd22082 100644 --- a/app/Http/Livewire/Server/Proxy/Status.php +++ b/app/Http/Livewire/Server/Proxy/Status.php @@ -26,7 +26,9 @@ public function getProxyStatus() } public function getProxyStatusWithNoti() { - $this->emit('success', 'Refreshed proxy status.'); - $this->getProxyStatus(); + if ($this->server->isFunctional()) { + $this->emit('success', 'Refreshed proxy status.'); + $this->getProxyStatus(); + } } } diff --git a/app/Http/Livewire/Server/Show.php b/app/Http/Livewire/Server/Show.php index 79d39bb19..77ae447d7 100644 --- a/app/Http/Livewire/Server/Show.php +++ b/app/Http/Livewire/Server/Show.php @@ -10,8 +10,10 @@ class Show extends Component { use AuthorizesRequests; public ?Server $server = null; + public $parameters = []; public function mount() { + $this->parameters = get_route_parameters(); try { $this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->first(); if (is_null($this->server)) { @@ -21,6 +23,10 @@ public function mount() return handleError($e, $this); } } + public function submit() + { + $this->emit('serverRefresh'); + } public function render() { return view('livewire.server.show'); diff --git a/app/Http/Livewire/Server/ShowPrivateKey.php b/app/Http/Livewire/Server/ShowPrivateKey.php index c9c11becb..1974226fc 100644 --- a/app/Http/Livewire/Server/ShowPrivateKey.php +++ b/app/Http/Livewire/Server/ShowPrivateKey.php @@ -32,36 +32,34 @@ public function setPrivateKey($newPrivateKeyId) } } - public function checkConnection() + public function checkConnection($install = false) { try { - ['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true); + $uptime = $this->server->validateConnection(); if ($uptime) { - $this->server->settings->update([ - 'is_reachable' => true - ]); - $this->emit('success', 'Server is reachable with this private key.'); + $install && $this->emit('success', 'Server is reachable.'); } else { - $this->server->settings->update([ - 'is_reachable' => false, - 'is_usable' => false - ]); - $this->emit('error', 'Server is not reachable with this private key.'); + $install && $this->emit('error', 'Server is not reachable. Please check your connection and private key configuration.'); return; } - if ($dockerVersion) { - $this->server->settings->update([ - 'is_usable' => true - ]); - $this->emit('success', 'Server is usable for Coolify.'); + $dockerInstalled = $this->server->validateDockerEngine(); + if ($dockerInstalled) { + $install && $this->emit('success', 'Docker Engine is installed.
Checking version.'); } else { - $this->server->settings->update([ - 'is_usable' => false - ]); - $this->emit('error', 'Old (lower than 23) or no Docker version detected. Install Docker Engine on the General tab.'); + $install && $this->installDocker(); + return; + } + $dockerVersion = $this->server->validateDockerEngineVersion(); + if ($dockerVersion) { + $install && $this->emit('success', 'Docker Engine version is 23+.'); + } else { + $install && $this->installDocker(); + return; } } catch (\Throwable $e) { return handleError($e, $this); + } finally { + $this->emit('proxyStatusUpdated'); } } diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index a4e7688ef..6d09644dc 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -41,15 +41,7 @@ public function uniqueId(): string { return $this->server->uuid; } - - private function checkServerConnection() - { - $uptime = instant_remote_process(['uptime'], $this->server, false); - if (!is_null($uptime)) { - return true; - } - } - public function handle(): void + public function handle() { try { ray("checking server status for {$this->server->name}"); @@ -57,9 +49,11 @@ public function handle(): void $serverUptimeCheckNumber = 0; $serverUptimeCheckNumberMax = 3; while (true) { + ray('checking # ' . $serverUptimeCheckNumber); if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) { send_internal_notification('Server unreachable: ' . $this->server->name); if ($this->server->unreachable_email_sent === false) { + ray('Server unreachable, sending notification...'); $this->server->team->notify(new Unreachable($this->server)); } $this->server->settings()->update([ @@ -68,7 +62,7 @@ public function handle(): void $this->server->update(['unreachable_email_sent' => true]); return; } - $result = $this->checkServerConnection(); + $result = $this->server->validateConnection(); if ($result) { break; } @@ -76,6 +70,7 @@ public function handle(): void sleep(5); } if (data_get($this->server, 'unreachable_email_sent') === true) { + ray('Server is reachable again, sending notification...'); $this->server->team->notify(new Revived($this->server)); $this->server->update(['unreachable_email_sent' => false]); } @@ -88,7 +83,7 @@ public function handle(): void 'is_usable' => true ]); } - + $this->server->validateDockerEngine(true); $containers = instant_remote_process(["docker container ls -q"], $this->server); if (!$containers) { return; @@ -288,7 +283,7 @@ public function handle(): void } catch (\Throwable $e) { send_internal_notification('ContainerStatusJob failed with: ' . $e->getMessage()); ray($e->getMessage()); - throw $e; + return handleError($e); } } } diff --git a/app/Models/Server.php b/app/Models/Server.php index b41720a06..5cbd6deb5 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -16,9 +16,11 @@ class Server extends BaseModel protected static function booted() { - static::saved(function ($server) { - $server->ip = Str::of($server->ip)->trim(); - $server->user = Str::of($server->user)->trim(); + static::saving(function ($server) { + $server->forceFill([ + 'ip' => Str::of($server->ip)->trim(), + 'user' => Str::of($server->user)->trim(), + ]); }); static::created(function ($server) { @@ -205,4 +207,48 @@ public function isFunctional() { return $this->settings->is_reachable && $this->settings->is_usable; } + public function validateConnection() + { + $uptime = instant_remote_process(['uptime'], $this, false); + if (!$uptime) { + $this->settings->is_reachable = false; + $this->settings->save(); + return false; + } + $this->settings->is_reachable = true; + $this->settings->save(); + return true; + } + public function validateDockerEngine($throwError = false) + { + $dockerBinary = instant_remote_process(["command -v docker"], $this, false); + if (is_null($dockerBinary)) { + $this->settings->is_usable = false; + $this->settings->save(); + if ($throwError) { + throw new \Exception('Server is not usable.'); + } + return false; + } + $this->settings->is_usable = true; + $this->settings->save(); + $this->validateCoolifyNetwork(); + return true; + } + public function validateDockerEngineVersion() + { + $dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this, false); + $dockerVersion = checkMinimumDockerEngineVersion($dockerVersion); + if (is_null($dockerVersion)) { + $this->settings->is_usable = false; + $this->settings->save(); + return false; + } + $this->settings->is_usable = true; + $this->settings->save(); + return true; + } + public function validateCoolifyNetwork() { + return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false); + } } diff --git a/app/Notifications/Server/Revived.php b/app/Notifications/Server/Revived.php index 4c9d9c7d2..07771a287 100644 --- a/app/Notifications/Server/Revived.php +++ b/app/Notifications/Server/Revived.php @@ -4,6 +4,9 @@ use App\Models\Server; use Illuminate\Bus\Queueable; +use App\Notifications\Channels\DiscordChannel; +use App\Notifications\Channels\EmailChannel; +use App\Notifications\Channels\TelegramChannel; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; @@ -22,7 +25,21 @@ public function __construct(public Server $server) public function via(object $notifiable): array { - return setNotificationChannels($notifiable, 'status_changes'); + $channels = []; + $isEmailEnabled = isEmailEnabled($notifiable); + $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); + $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); + + if ($isDiscordEnabled) { + $channels[] = DiscordChannel::class; + } + if ($isEmailEnabled ) { + $channels[] = EmailChannel::class; + } + if ($isTelegramEnabled) { + $channels[] = TelegramChannel::class; + } + return $channels; } public function toMail(): MailMessage diff --git a/app/Notifications/Server/Unreachable.php b/app/Notifications/Server/Unreachable.php index b7cc7af51..705988e31 100644 --- a/app/Notifications/Server/Unreachable.php +++ b/app/Notifications/Server/Unreachable.php @@ -3,6 +3,9 @@ namespace App\Notifications\Server; use App\Models\Server; +use App\Notifications\Channels\DiscordChannel; +use App\Notifications\Channels\EmailChannel; +use App\Notifications\Channels\TelegramChannel; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; @@ -20,7 +23,21 @@ public function __construct(public Server $server) public function via(object $notifiable): array { - return setNotificationChannels($notifiable, 'status_changes'); + $channels = []; + $isEmailEnabled = isEmailEnabled($notifiable); + $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); + $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); + + if ($isDiscordEnabled) { + $channels[] = DiscordChannel::class; + } + if ($isEmailEnabled ) { + $channels[] = EmailChannel::class; + } + if ($isTelegramEnabled) { + $channels[] = TelegramChannel::class; + } + return $channels; } public function toMail(): MailMessage diff --git a/resources/css/app.css b/resources/css/app.css index ea7109d3d..c1e8f3259 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -56,7 +56,7 @@ .box { @apply flex items-center p-2 transition-colors cursor-pointer min-h-16 bg-coolgray-200 hover:bg-coollabs-100 hover:text-white hover:no-underline min-w-[24rem]; } .box-without-bg { - @apply flex items-center p-2 transition-colors min-h-16 hover:text-white hover:no-underline min-w-[24rem]; + @apply flex items-center p-2 transition-colors min-h-16 hover:text-white hover:no-underline min-w-[24rem]; } .lds-heart { diff --git a/resources/views/components/server/navbar.blade.php b/resources/views/components/server/navbar.blade.php index d36a35b8f..8230c3a7c 100644 --- a/resources/views/components/server/navbar.blade.php +++ b/resources/views/components/server/navbar.blade.php @@ -8,25 +8,25 @@