mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-12 15:53:46 -04:00
fix(server): enable disabled self-hosted servers again on validation
On self-hosted, a validation that connects now clears force_disabled. The unreachable cleanup skips the localhost server, and the server page explains why a server is disabled. Cloud and transferred servers stay disabled. Closes #12475
This commit is contained in:
@@ -15,7 +15,8 @@ class CleanupUnreachableServers extends Command
|
||||
public function handle()
|
||||
{
|
||||
echo "Running unreachable server cleanup...\n";
|
||||
$servers = Server::where('unreachable_count', '>=', 3)->whereHas('notificationThrottles', fn ($query) => $query->where('notification', Unreachable::class))->where('updated_at', '<', now()->subDays(7))->get();
|
||||
// The localhost server is the Coolify host itself, so the cleanup never touches it.
|
||||
$servers = Server::where('id', '!=', 0)->where('unreachable_count', '>=', 3)->whereHas('notificationThrottles', fn ($query) => $query->where('notification', Unreachable::class))->where('updated_at', '<', now()->subDays(7))->get();
|
||||
if ($servers->count() > 0) {
|
||||
foreach ($servers as $server) {
|
||||
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
||||
|
||||
@@ -1438,6 +1438,15 @@ $siteAddress {
|
||||
return $this->settings->force_disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* On self-hosted, only the unreachable cleanup disables a server, so a working connection can enable it again.
|
||||
* On Cloud the flag enforces the plan server limit, and a transferred server stays an archive.
|
||||
*/
|
||||
public function canBeReEnabled(): bool
|
||||
{
|
||||
return $this->settings->force_disabled === true && ! isCloud() && ! $this->isTransferredAway();
|
||||
}
|
||||
|
||||
/**
|
||||
* Server was migrated away from this Coolify instance (source side).
|
||||
* Must not be revalidated or re-enabled as a live managed host.
|
||||
@@ -2544,6 +2553,11 @@ $siteAddress {
|
||||
|
||||
public function validateConnection(bool $justCheckingNewKey = false)
|
||||
{
|
||||
// A validation enables a disabled self-hosted server again, but only when the connection works.
|
||||
$isReEnabling = $this->canBeReEnabled() && ! $this->hasPlaceholderIp();
|
||||
if ($isReEnabling) {
|
||||
$this->forceEnableServer();
|
||||
}
|
||||
if ($this->skipServer()) {
|
||||
return ['uptime' => false, 'error' => 'Server skipped.'];
|
||||
}
|
||||
@@ -2564,6 +2578,9 @@ $siteAddress {
|
||||
|
||||
return ['uptime' => true, 'error' => null];
|
||||
} catch (\Throwable $e) {
|
||||
if ($isReEnabling) {
|
||||
$this->forceDisableServer();
|
||||
}
|
||||
$error = $e->getMessage();
|
||||
if ($error === SshMultiplexingHelper::HOST_KEY_CHANGED_MESSAGE && ! $this->recordHostKeyMismatch()) {
|
||||
$error = 'SSH host key verification failed, but the server still sends the trusted host key.';
|
||||
|
||||
@@ -221,6 +221,14 @@
|
||||
</x-callout>
|
||||
@endif
|
||||
|
||||
@if ($server->canBeReEnabled())
|
||||
<x-callout type="danger" title="Server disabled" class="mb-4">
|
||||
Coolify disabled this server because it was unreachable for more than 7 days. Coolify does
|
||||
not connect to it. Correct the connection settings, then click
|
||||
<strong>Validate connection</strong>. When the connection works, the server is enabled again.
|
||||
</x-callout>
|
||||
@endif
|
||||
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<x-forms.input canGate="update" :canResource="$server" id="name" label="Name"
|
||||
required :disabled="$isValidating" />
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\NotificationThrottle;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use App\Notifications\Server\Unreachable;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::forceCreate(['id' => 0]);
|
||||
config(['constants.coolify.self_hosted' => true, 'constants.ssh.mux_enabled' => false]);
|
||||
Storage::fake('ssh-keys');
|
||||
|
||||
$team = Team::factory()->create();
|
||||
$this->server = Server::factory()->create([
|
||||
'team_id' => $team->id,
|
||||
'private_key_id' => PrivateKey::factory()->create(['team_id' => $team->id])->id,
|
||||
]);
|
||||
$this->server->forceDisableServer();
|
||||
});
|
||||
|
||||
it('enables a disabled self-hosted server again when the validation connects', function () {
|
||||
Process::fake();
|
||||
|
||||
$result = $this->server->validateConnection();
|
||||
|
||||
expect($result)->toBe(['uptime' => true, 'error' => null])
|
||||
->and($this->server->settings->fresh()->force_disabled)->toBeFalse()
|
||||
->and($this->server->settings->fresh()->is_reachable)->toBeTrue();
|
||||
});
|
||||
|
||||
it('keeps a self-hosted server disabled when the validation cannot connect', function () {
|
||||
Process::fake(['*' => Process::result(errorOutput: 'ssh: connect to host 192.0.2.1 port 22: Connection refused', exitCode: 255)]);
|
||||
|
||||
$result = $this->server->validateConnection();
|
||||
|
||||
expect($result['uptime'])->toBeFalse()
|
||||
->and($result['error'])->not->toBe('Server skipped.')
|
||||
->and($this->server->settings->fresh()->force_disabled)->toBeTrue();
|
||||
});
|
||||
|
||||
it('does not enable a disabled server on cloud, where the flag enforces the plan limit', function () {
|
||||
config(['constants.coolify.self_hosted' => false]);
|
||||
Process::fake();
|
||||
|
||||
expect($this->server->validateConnection())->toBe(['uptime' => false, 'error' => 'Server skipped.'])
|
||||
->and($this->server->settings->fresh()->force_disabled)->toBeTrue();
|
||||
Process::assertNothingRan();
|
||||
});
|
||||
|
||||
it('does not enable the archive of a transferred server', function () {
|
||||
$this->server->update(['server_metadata' => ['transfer' => ['status' => 'transferred']]]);
|
||||
Process::fake();
|
||||
|
||||
expect($this->server->validateConnection())->toBe(['uptime' => false, 'error' => 'Server skipped.'])
|
||||
->and($this->server->settings->fresh()->force_disabled)->toBeTrue();
|
||||
Process::assertNothingRan();
|
||||
});
|
||||
|
||||
it('never disables the localhost server in the unreachable cleanup', function () {
|
||||
$localhost = Server::factory()->create(['id' => 0, 'team_id' => $this->server->team_id, 'unreachable_count' => 50]);
|
||||
$localhost->forceFill(['updated_at' => now()->subDays(8)])->saveQuietly();
|
||||
NotificationThrottle::record($localhost, Unreachable::class);
|
||||
|
||||
$this->artisan('cleanup:unreachable-servers')->assertSuccessful();
|
||||
|
||||
expect($localhost->settings->fresh()->force_disabled)->toBeFalse();
|
||||
});
|
||||
Reference in New Issue
Block a user