fix: better unreachable/revived server statuses

This commit is contained in:
Andras Bacsai 2023-10-07 00:51:01 +02:00
parent c58706e3e4
commit 9e81416fef
11 changed files with 174 additions and 10 deletions

View File

@ -0,0 +1,33 @@
<?php
namespace App\Console\Commands;
use App\Models\Server;
use Illuminate\Console\Command;
class Cloud extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'cloud:unused-servers';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Get Unused Servers from Cloud';
/**
* Execute the console command.
*/
public function handle()
{
Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended',true)->each(function($server){
$this->info($server->name);
});
}
}

View File

@ -48,7 +48,11 @@ class Kernel extends ConsoleKernel
} }
private function check_resources($schedule) 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) { foreach ($servers as $server) {
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer(); $schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
} }

View File

@ -7,6 +7,7 @@ use App\Models\ApplicationPreview;
use App\Models\Server; use App\Models\Server;
use App\Notifications\Container\ContainerRestarted; use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped; use App\Notifications\Container\ContainerStopped;
use App\Notifications\Server\Revived;
use App\Notifications\Server\Unreachable; use App\Notifications\Server\Unreachable;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Contracts\Queue\ShouldBeEncrypted;
@ -51,14 +52,20 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
public function handle(): void public function handle(): void
{ {
try { try {
ray("checking server status for {$this->server->name}");
// ray()->clearAll(); // ray()->clearAll();
$serverUptimeCheckNumber = 0; $serverUptimeCheckNumber = 0;
$serverUptimeCheckNumberMax = 3; $serverUptimeCheckNumberMax = 3;
while (true) { while (true) {
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) { if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
send_internal_notification('Server unreachable: ' . $this->server->name); send_internal_notification('Server unreachable: ' . $this->server->name);
// $this->server->settings()->update(['is_reachable' => false]); if ($this->server->unreachable_email_sent === false) {
// $this->server->team->notify(new Unreachable($this->server)); $this->server->team->notify(new Unreachable($this->server));
}
$this->server->settings()->update([
'is_reachable' => false,
]);
$this->server->update(['unreachable_email_sent' => true]);
return; return;
} }
$result = $this->checkServerConnection(); $result = $this->checkServerConnection();
@ -68,6 +75,20 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$serverUptimeCheckNumber++; $serverUptimeCheckNumber++;
sleep(5); 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); $containers = instant_remote_process(["docker container ls -q"], $this->server);
if (!$containers) { if (!$containers) {
return; return;

View File

@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes; use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
use Spatie\SchemalessAttributes\SchemalessAttributesTrait; use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
use Illuminate\Support\Str;
class Server extends BaseModel class Server extends BaseModel
{ {
@ -15,6 +16,11 @@ class Server extends BaseModel
protected static function booted() 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) { static::created(function ($server) {
ServerSetting::create([ ServerSetting::create([
'server_id' => $server->id, 'server_id' => $server->id,

View File

@ -0,0 +1,49 @@
<?php
namespace App\Notifications\Server;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class Revived extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 1;
public function __construct(public Server $server)
{
if ($this->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!"
];
}
}

View File

@ -35,13 +35,13 @@ class Unreachable extends Notification implements ShouldQueue
public function toDiscord(): string 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; return $message;
} }
public function toTelegram(): array public function toTelegram(): array
{ {
return [ 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."
]; ];
} }
} }

View File

@ -7,6 +7,8 @@ use App\Models\Application;
use App\Models\ApplicationDeploymentQueue; use App\Models\ApplicationDeploymentQueue;
use App\Models\PrivateKey; use App\Models\PrivateKey;
use App\Models\Server; use App\Models\Server;
use App\Notifications\Server\Revived;
use App\Notifications\Server\Unreachable;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@ -85,7 +87,7 @@ function generateSshCommand(Server $server, string $command, bool $isMux = true)
if ($isMux && config('coolify.mux_enabled')) { 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 '; $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" '; $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"; $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; return $output;
} }
function excludeCertainErrors(string $errorOutput, ?int $exitCode = null) { function excludeCertainErrors(string $errorOutput, ?int $exitCode = null)
{
$ignoredErrors = collect([ $ignoredErrors = collect([
'Permission denied (publickey', 'Permission denied (publickey',
'Could not resolve hostname', 'Could not resolve hostname',
]); ]);
$ignored = false; $ignored = false;
foreach ($ignoredErrors as $ignoredError) { foreach ($ignoredErrors as $ignoredError) {
if (Str::contains($errorOutput, $ignoredError)) { if (Str::contains($errorOutput, $ignoredError)) {
$ignored = true; $ignored = true;
break; break;
@ -183,6 +186,9 @@ function validateServer(Server $server, bool $throwError = false)
$uptime = instant_remote_process(['uptime'], $server, $throwError); $uptime = instant_remote_process(['uptime'], $server, $throwError);
if (!$uptime) { if (!$uptime) {
$server->settings->is_reachable = false; $server->settings->is_reachable = false;
$server->team->notify(new Unreachable($server));
$server->unreachable_email_sent = true;
$server->save();
return [ return [
"uptime" => null, "uptime" => null,
"dockerVersion" => null, "dockerVersion" => null,
@ -203,6 +209,11 @@ function validateServer(Server $server, bool $throwError = false)
$server->settings->is_usable = false; $server->settings->is_usable = false;
} else { } else {
$server->settings->is_usable = true; $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 [ return [
"uptime" => $uptime, "uptime" => $uptime,
@ -213,7 +224,9 @@ function validateServer(Server $server, bool $throwError = false)
$server->settings->is_usable = false; $server->settings->is_usable = false;
throw $e; throw $e;
} finally { } finally {
if (data_get($server, 'settings')) $server->settings->save(); if (data_get($server, 'settings')) {
$server->settings->save();
}
} }
} }

View File

@ -310,6 +310,7 @@ function send_internal_notification(string $message): void
$baseUrl = config('app.name'); $baseUrl = config('app.name');
$team = Team::find(0); $team = Team::find(0);
$team->notify(new GeneralNotification("👀 {$baseUrl}: " . $message)); $team->notify(new GeneralNotification("👀 {$baseUrl}: " . $message));
ray("👀 {$baseUrl}: " . $message);
} catch (\Throwable $e) { } catch (\Throwable $e) {
ray($e->getMessage()); ray($e->getMessage());
} }

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('servers', function (Blueprint $table) {
$table->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);
});
}
};

View File

@ -4,7 +4,7 @@ Coolify cannot connect to your server ({{$name}}). Please check your server and
All automations & integrations are turned off! 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. If you have any questions, please contact us.

View File

@ -0,0 +1,6 @@
<x-emails.layout>
Your server ({{$name}}) was offline for a while, but it is back online now. All automations & integrations are turned on again.
</x-emails.layout>