From 6659153804252835658b58764bb325ef8430e930 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 6 Oct 2023 15:32:46 +0200 Subject: [PATCH 1/9] version++ fix: unreachable servers --- app/Jobs/ContainerStatusJob.php | 5 +++-- config/sentry.php | 2 +- config/version.php | 2 +- versions.json | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index f2abae3ca..7f28954a0 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -56,8 +56,9 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted $serverUptimeCheckNumberMax = 3; while (true) { if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) { - $this->server->settings()->update(['is_reachable' => false]); - $this->server->team->notify(new Unreachable($this->server)); + send_internal_notification('Server unreachable: ' . $this->server->name); + // $this->server->settings()->update(['is_reachable' => false]); + // $this->server->team->notify(new Unreachable($this->server)); return; } $result = $this->checkServerConnection(); diff --git a/config/sentry.php b/config/sentry.php index 337e904e1..ab05d1fa5 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ return [ // 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.69', + 'release' => '4.0.0-beta.70', // 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 0dafd3ed9..c58fd74b3 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ Date: Fri, 6 Oct 2023 22:31:20 +0530 Subject: [PATCH 2/9] fix(create): flex wrap on server & network selection --- resources/views/livewire/project/new/select.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/livewire/project/new/select.blade.php b/resources/views/livewire/project/new/select.blade.php index 21e6abc96..376f143dc 100644 --- a/resources/views/livewire/project/new/select.blade.php +++ b/resources/views/livewire/project/new/select.blade.php @@ -130,7 +130,7 @@
  • Select a Server
  • Select a Destination
  • -
    +
    @forelse($servers as $server)
    @@ -158,7 +158,7 @@
  • Select a Server
  • Select a Destination
  • -
    +
    @foreach ($standaloneDockers as $standaloneDocker)
    From b095b8828126df989ee92be4e27ab21bdf97e7c1 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 6 Oct 2023 20:45:35 +0200 Subject: [PATCH 3/9] fix: contribution guide --- .env.development.example | 9 +-------- CONTRIBUTION.md | 3 ++- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/.env.development.example b/.env.development.example index c956daafd..920c32d92 100644 --- a/.env.development.example +++ b/.env.development.example @@ -1,11 +1,3 @@ -############################################################################################################ -# Development Environment - -# User and group id for the user that will run the application inside the container -# Run in your terminal: `id -u` and `id -g` and that's the results -USERID= -GROUPID= -############################################################################################################ APP_NAME=Coolify-localhost APP_ID=development APP_ENV=local @@ -13,6 +5,7 @@ APP_KEY= APP_DEBUG=true APP_URL=http://localhost APP_PORT=8000 +MUX_ENABLED=false DUSK_DRIVER_URL=http://selenium:4444 diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index ef67baba1..0952e1bd8 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -15,11 +15,12 @@ You can ask for guidance anytime on our ## 2) Set your environment variables - Copy [.env.development.example](./.env.development.example) to .env. -- If necessary, set `USERID` & `GROUPID` accordingly (read in .env file). ## 3) Start & setup Coolify - Run `spin up` - You can notice that errors will be thrown. Don't worry. + - If you see weird permission errors, especially on Mac, run `sudo spin up` instead. + - Run `./scripts/run setup:dev` - This will generate a secret key for you, delete any existing database layouts, migrate database to the new layout, and seed your database. ## 4) Start development From 45bca8649b69e7e00ff77d864677a0c3dacb6c2c Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 6 Oct 2023 20:48:03 +0200 Subject: [PATCH 4/9] fix: public repository names --- app/Http/Livewire/Project/New/PublicGitRepository.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/Http/Livewire/Project/New/PublicGitRepository.php b/app/Http/Livewire/Project/New/PublicGitRepository.php index 7a206fae4..ebcbb8253 100644 --- a/app/Http/Livewire/Project/New/PublicGitRepository.php +++ b/app/Http/Livewire/Project/New/PublicGitRepository.php @@ -144,7 +144,7 @@ class PublicGitRepository extends Component if ($this->git_source === 'other') { $application_init = [ - 'name' => generate_application_name($this->git_repository, $this->git_branch), + 'name' => generate_random_name(), 'git_repository' => $this->git_repository, 'git_branch' => $this->git_branch, 'build_pack' => 'nixpacks', @@ -178,7 +178,6 @@ class PublicGitRepository extends Component $fqdn = generateFqdn($destination->server, $application->uuid); $application->fqdn = $fqdn; - $application->name = generate_application_name($this->git_repository, $this->git_branch, $application->uuid); $application->save(); return redirect()->route('project.application.configuration', [ From 9e81416fef14dbd72d2a11d3b17deee519849ac4 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Sat, 7 Oct 2023 00:51:01 +0200 Subject: [PATCH 5/9] fix: better unreachable/revived server statuses --- app/Console/Commands/Cloud.php | 33 +++++++++++++ app/Console/Kernel.php | 6 ++- app/Jobs/ContainerStatusJob.php | 25 +++++++++- app/Models/Server.php | 6 +++ app/Notifications/Server/Revived.php | 49 +++++++++++++++++++ app/Notifications/Server/Unreachable.php | 4 +- bootstrap/helpers/remoteProcess.php | 21 ++++++-- bootstrap/helpers/shared.php | 1 + .../2023_09_23_111819_add_server_emails.php | 31 ++++++++++++ .../emails/server-lost-connection.blade.php | 2 +- .../views/emails/server-revived.blade.php | 6 +++ 11 files changed, 174 insertions(+), 10 deletions(-) create mode 100644 app/Console/Commands/Cloud.php create mode 100644 app/Notifications/Server/Revived.php create mode 100644 database/migrations/2023_09_23_111819_add_server_emails.php create mode 100644 resources/views/emails/server-revived.blade.php diff --git a/app/Console/Commands/Cloud.php b/app/Console/Commands/Cloud.php new file mode 100644 index 000000000..1386b296c --- /dev/null +++ b/app/Console/Commands/Cloud.php @@ -0,0 +1,33 @@ +whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended',true)->each(function($server){ + $this->info($server->name); + }); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index ee9f67ad7..d6ea360ae 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -48,7 +48,11 @@ class Kernel extends ConsoleKernel } private function check_resources($schedule) { - $servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true); + if (isCloud()) { + $servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false); + } else { + $servers = Server::all(); + } foreach ($servers as $server) { $schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer(); } diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index 7f28954a0..a4e7688ef 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -7,6 +7,7 @@ use App\Models\ApplicationPreview; use App\Models\Server; use App\Notifications\Container\ContainerRestarted; use App\Notifications\Container\ContainerStopped; +use App\Notifications\Server\Revived; use App\Notifications\Server\Unreachable; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeEncrypted; @@ -51,14 +52,20 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted public function handle(): void { try { + ray("checking server status for {$this->server->name}"); // ray()->clearAll(); $serverUptimeCheckNumber = 0; $serverUptimeCheckNumberMax = 3; while (true) { if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) { send_internal_notification('Server unreachable: ' . $this->server->name); - // $this->server->settings()->update(['is_reachable' => false]); - // $this->server->team->notify(new Unreachable($this->server)); + if ($this->server->unreachable_email_sent === false) { + $this->server->team->notify(new Unreachable($this->server)); + } + $this->server->settings()->update([ + 'is_reachable' => false, + ]); + $this->server->update(['unreachable_email_sent' => true]); return; } $result = $this->checkServerConnection(); @@ -68,6 +75,20 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted $serverUptimeCheckNumber++; sleep(5); } + if (data_get($this->server, 'unreachable_email_sent') === true) { + $this->server->team->notify(new Revived($this->server)); + $this->server->update(['unreachable_email_sent' => false]); + } + if ( + data_get($this->server, 'settings.is_reachable') === false || + data_get($this->server, 'settings.is_usable') === false + ) { + $this->server->settings()->update([ + 'is_reachable' => true, + 'is_usable' => true + ]); + } + $containers = instant_remote_process(["docker container ls -q"], $this->server); if (!$containers) { return; diff --git a/app/Models/Server.php b/app/Models/Server.php index f174222c0..b41720a06 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; use Spatie\SchemalessAttributes\Casts\SchemalessAttributes; use Spatie\SchemalessAttributes\SchemalessAttributesTrait; +use Illuminate\Support\Str; class Server extends BaseModel { @@ -15,6 +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::created(function ($server) { ServerSetting::create([ 'server_id' => $server->id, diff --git a/app/Notifications/Server/Revived.php b/app/Notifications/Server/Revived.php new file mode 100644 index 000000000..4c9d9c7d2 --- /dev/null +++ b/app/Notifications/Server/Revived.php @@ -0,0 +1,49 @@ +server->unreachable_email_sent === false) { + return; + } + } + + public function via(object $notifiable): array + { + return setNotificationChannels($notifiable, 'status_changes'); + } + + public function toMail(): MailMessage + { + $mail = new MailMessage(); + $mail->subject("✅ Server ({$this->server->name}) revived."); + $mail->view('emails.server-revived', [ + 'name' => $this->server->name, + ]); + return $mail; + } + + public function toDiscord(): string + { + $message = "✅ Server '{$this->server->name}' revived. All automations & integrations are turned on again!"; + return $message; + } + public function toTelegram(): array + { + return [ + "message" => "✅ Server '{$this->server->name}' revived. All automations & integrations are turned on again!" + ]; + } +} diff --git a/app/Notifications/Server/Unreachable.php b/app/Notifications/Server/Unreachable.php index ec9c11d11..b7cc7af51 100644 --- a/app/Notifications/Server/Unreachable.php +++ b/app/Notifications/Server/Unreachable.php @@ -35,13 +35,13 @@ class Unreachable extends Notification implements ShouldQueue public function toDiscord(): string { - $message = "⛔ Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: You have to validate your server again after you fix the issue."; + $message = "⛔ Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations."; return $message; } public function toTelegram(): array { return [ - "message" => "⛔ Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: You have to validate your server again after you fix the issue." + "message" => "⛔ Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations." ]; } } diff --git a/bootstrap/helpers/remoteProcess.php b/bootstrap/helpers/remoteProcess.php index e5a2a5863..8aa22d6f2 100644 --- a/bootstrap/helpers/remoteProcess.php +++ b/bootstrap/helpers/remoteProcess.php @@ -7,6 +7,8 @@ use App\Models\Application; use App\Models\ApplicationDeploymentQueue; use App\Models\PrivateKey; use App\Models\Server; +use App\Notifications\Server\Revived; +use App\Notifications\Server\Unreachable; use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; @@ -85,7 +87,7 @@ function generateSshCommand(Server $server, string $command, bool $isMux = true) if ($isMux && config('coolify.mux_enabled')) { $ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r '; } - if (data_get($server,'settings.is_cloudflare_tunnel')) { + if (data_get($server, 'settings.is_cloudflare_tunnel')) { $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"; @@ -122,13 +124,14 @@ function instant_remote_process(Collection|array $command, Server $server, $thro } return $output; } -function excludeCertainErrors(string $errorOutput, ?int $exitCode = null) { +function excludeCertainErrors(string $errorOutput, ?int $exitCode = null) +{ $ignoredErrors = collect([ 'Permission denied (publickey', 'Could not resolve hostname', ]); $ignored = false; - foreach ($ignoredErrors as $ignoredError) { + foreach ($ignoredErrors as $ignoredError) { if (Str::contains($errorOutput, $ignoredError)) { $ignored = true; break; @@ -183,6 +186,9 @@ function validateServer(Server $server, bool $throwError = false) $uptime = instant_remote_process(['uptime'], $server, $throwError); if (!$uptime) { $server->settings->is_reachable = false; + $server->team->notify(new Unreachable($server)); + $server->unreachable_email_sent = true; + $server->save(); return [ "uptime" => null, "dockerVersion" => null, @@ -203,6 +209,11 @@ function validateServer(Server $server, bool $throwError = false) $server->settings->is_usable = false; } else { $server->settings->is_usable = true; + if (data_get($server, 'unreachable_email_sent') === true) { + $server->team->notify(new Revived($server)); + $server->unreachable_email_sent = false; + $server->save(); + } } return [ "uptime" => $uptime, @@ -213,7 +224,9 @@ function validateServer(Server $server, bool $throwError = false) $server->settings->is_usable = false; throw $e; } finally { - if (data_get($server, 'settings')) $server->settings->save(); + if (data_get($server, 'settings')) { + $server->settings->save(); + } } } diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index a12d6b02d..25237abab 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -310,6 +310,7 @@ function send_internal_notification(string $message): void $baseUrl = config('app.name'); $team = Team::find(0); $team->notify(new GeneralNotification("👀 {$baseUrl}: " . $message)); + ray("👀 {$baseUrl}: " . $message); } catch (\Throwable $e) { ray($e->getMessage()); } diff --git a/database/migrations/2023_09_23_111819_add_server_emails.php b/database/migrations/2023_09_23_111819_add_server_emails.php new file mode 100644 index 000000000..03c1e6bd2 --- /dev/null +++ b/database/migrations/2023_09_23_111819_add_server_emails.php @@ -0,0 +1,31 @@ +boolean('unreachable_email_sent')->default(false); + $table->dropColumn('unreachable_count'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('servers', function (Blueprint $table) { + $table->dropColumn('unreachable_email_sent'); + $table->integer('unreachable_count')->default(0); + }); + + } +}; diff --git a/resources/views/emails/server-lost-connection.blade.php b/resources/views/emails/server-lost-connection.blade.php index 83b5e2db5..6e3f28fd4 100644 --- a/resources/views/emails/server-lost-connection.blade.php +++ b/resources/views/emails/server-lost-connection.blade.php @@ -4,7 +4,7 @@ Coolify cannot connect to your server ({{$name}}). Please check your server and All automations & integrations are turned off! -IMPORTANT: You have to validate your server again after you fix the issue. +IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations. If you have any questions, please contact us. diff --git a/resources/views/emails/server-revived.blade.php b/resources/views/emails/server-revived.blade.php new file mode 100644 index 000000000..8d16fd2c8 --- /dev/null +++ b/resources/views/emails/server-revived.blade.php @@ -0,0 +1,6 @@ + + +Your server ({{$name}}) was offline for a while, but it is back online now. All automations & integrations are turned on again. + + + From 61a7b9ac94be8e3588f55b454bee189f47a261ed Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Sat, 7 Oct 2023 12:54:19 +0200 Subject: [PATCH 6/9] fix: able to set base dir for Dockerfile build pack --- app/Jobs/ApplicationDeploymentJob.php | 13 ++++++++++--- .../project/application/general.blade.php | 15 +++++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index fc0387357..cfccb6912 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -254,7 +254,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted ); $this->prepare_builder_image(); $this->clone_repository(); - + $this->set_base_dir(); $tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}"); if (strlen($tag) > 128) { $tag = $tag->substr(0, 128); @@ -364,6 +364,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted ]); $this->prepare_builder_image(); $this->clone_repository(); + $this->set_base_dir(); $this->cleanup_git(); if ($this->application->build_pack === 'nixpacks') { $this->generate_nixpacks_confs(); @@ -400,7 +401,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted ); } - + private function set_base_dir() { + $this->execute_remote_command( + [ + "echo -n 'Setting base directory to {$this->workdir}.'" + ], + ); + } private function clone_repository() { @@ -452,7 +459,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } if ($this->application->deploymentType() === 'deploy_key') { $private_key = base64_encode($this->application->private_key->private_key); - $git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_full_url} {$this->workdir}"; + $git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_full_url} {$this->basedir}"; $git_clone_command = $this->set_git_import_settings($git_clone_command); $commands = collect([ executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"), diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 112021dac..fb525a8dd 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -41,25 +41,28 @@
    @endif +

    Build

    @if ($application->could_set_build_commands()) -

    Build

    -
    - + @endif + +
    + + @if ($application->could_set_build_commands()) @if ($application->settings->is_static) @else @endif -
    - @endif + @endif +
    @if ($application->dockerfile) @endif From c40ea6f1da60e46ebd6e3923060d9f964b7142ce Mon Sep 17 00:00:00 2001 From: vaporii Date: Sun, 8 Oct 2023 15:01:09 -0500 Subject: [PATCH 7/9] corrected 'orAGnize' to 'orGAnize' --- templates/service-templates.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/service-templates.json b/templates/service-templates.json index 22cf7dbfb..46b9263cc 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -16,7 +16,7 @@ }, "fider": { "documentation": "https://fider.io/docs", - "slogan": "A platform to collect and oragnize customer feedback.", + "slogan": "A platform to collect and organize customer feedback.", "compose": "c2VydmljZXM6CiAgZmlkZXI6CiAgICBpbWFnZTogZ2V0ZmlkZXIvZmlkZXI6c3RhYmxlCiAgICBlbnZpcm9ubWVudDoKICAgICAgQkFTRV9VUkw6ICRTRVJWSUNFX0ZRRE5fRklERVIKICAgICAgREFUQUJBU0VfVVJMOiBwb3N0Z3JlczovLyRTRVJWSUNFX1VTRVJfTVlTUUw6JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUxAZGF0YWJhc2U6NTQzMi9maWRlcj9zc2xtb2RlPWRpc2FibGUKICAgICAgSldUX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfRklERVIKICAgICAgRU1BSUxfTk9SRVBMWTogJHtFTUFJTF9OT1JFUExZOi1ub3JlcGx5QGV4YW1wbGUuY29tfQogICAgICBFTUFJTF9NQUlMR1VOX0FQSTogJEVNQUlMX01BSUxHVU5fQVBJCiAgICAgIEVNQUlMX01BSUxHVU5fRE9NQUlOOiAkRU1BSUxfTUFJTEdVTl9ET01BSU4KICAgICAgRU1BSUxfTUFJTEdVTl9SRUdJT046ICRFTUFJTF9NQUlMR1VOX1JFR0lPTgogICAgICBFTUFJTF9TTVRQX0hPU1Q6ICR7RU1BSUxfU01UUF9IT1NUOi1zbXRwLm1haWxndW4uY29tfQogICAgICBFTUFJTF9TTVRQX1BPUlQ6ICR7RU1BSUxfU01UUF9QT1JUOi01ODd9CiAgICAgIEVNQUlMX1NNVFBfVVNFUk5BTUU6ICR7RU1BSUxfU01UUF9VU0VSTkFNRTotcG9zdG1hc3RlckBtYWlsZ3VuLmNvbX0KICAgICAgRU1BSUxfU01UUF9QQVNTV09SRDogJEVNQUlMX1NNVFBfUEFTU1dPUkQKICAgICAgRU1BSUxfU01UUF9FTkFCTEVfU1RBUlRUTFM6ICRFTUFJTF9TTVRQX0VOQUJMRV9TVEFSVFRMUwogICAgICBFTUFJTF9BV1NTRVNfUkVHSU9OOiAkRU1BSUxfQVdTU0VTX1JFR0lPTgogICAgICBFTUFJTF9BV1NTRVNfQUNDRVNTX0tFWV9JRDogJEVNQUlMX0FXU1NFU19BQ0NFU1NfS0VZX0lECiAgICAgIEVNQUlMX0FXU1NFU19TRUNSRVRfQUNDRVNTX0tFWTogJEVNQUlMX0FXU1NFU19TRUNSRVRfQUNDRVNTX0tFWQogIGRhdGFiYXNlOgogICAgaW1hZ2U6IHBvc3RncmVzOjEyCiAgICB2b2x1bWVzOgogICAgICAtIHBnX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhCiAgICBlbnZpcm9ubWVudDoKICAgICAgUE9TVEdSRVNfVVNFUjogJFNFUlZJQ0VfVVNFUl9NWVNRTAogICAgICBQT1NUR1JFU19QQVNTV09SRDogJFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUwKICAgICAgUE9TVEdSRVNfREI6ICR7UE9TVEdSRVNfREI6LWZpZGVyfQo=" }, "ghost": { From dcaa7a6ad7a96a688523521e135e9b633c34d02e Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 9 Oct 2023 11:00:18 +0200 Subject: [PATCH 8/9] 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 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== 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 @@ class Form extends Component $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 @@ class Status extends Component } 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 @@ class Show extends Component 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 @@ class ShowPrivateKey extends Component } } - 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 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted { 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 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted $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 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted $this->server->update(['unreachable_email_sent' => true]); return; } - $result = $this->checkServerConnection(); + $result = $this->server->validateConnection(); if ($result) { break; } @@ -76,6 +70,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted 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 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted 'is_usable' => true ]); } - + $this->server->validateDockerEngine(true); $containers = instant_remote_process(["docker container ls -q"], $this->server); if (!$containers) { return; @@ -288,7 +283,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted } 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 @@ class Server extends BaseModel { 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 @@ namespace App\Notifications\Server; 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 @@ class Revived extends Notification implements ShouldQueue 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 @@ class Unreachable extends Notification implements ShouldQueue 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 @@ a { @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 @@