fix(hetzner): validate over SSH when the Hetzner API call fails

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andras BacsaiandClaude Opus 5.5 committed 2026-10-28 14:16:22 +01:00
1 parent c4a7cd6d4a
commit 597a878d80
3 files changed
+33 -2

No files matched your search

+8 -1
View File
@@ -3,6 +3,7 @@
namespace App\Actions\Server;
use App\Models\Server;
use Illuminate\Support\Facades\Log;
use Lorisleiva\Actions\Concerns\AsAction;
class ValidateServer
@@ -38,7 +39,13 @@ class ValidateServer
'validation_logs' => null,
]);
if ($server->hetzner_server_id) {
$status = $server->refreshHetznerState();
try {
$status = $server->refreshHetznerState();
} catch (\Throwable $e) {
// The provider API can be down or the token revoked; the SSH checks below still decide.
Log::warning('Could not refresh the Hetzner server status before validation', ['server_id' => $server->id, 'error' => $e->getMessage()]);
$status = null;
}
if (in_array($status, ['initializing', 'starting', 'stopping', 'off', 'deleting', 'rebuilding', 'migrating'], true)) {
$this->error = $status === 'off'
? 'Hetzner server is off. Power it on before validating.'
+8 -1
View File
@@ -8,6 +8,7 @@ use App\Events\ServerValidated;
use App\Jobs\CheckAndStartSentinelJob;
use App\Models\Server;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Log;
use Livewire\Attributes\Locked;
use Livewire\Component;
@@ -117,7 +118,13 @@ class ValidateAndInstall extends Component
$this->authorize('update', $this->server);
Server::throttleValidation($this->server->team_id, $this->server->ip);
if ($this->server->hetzner_server_id) {
$status = $this->server->refreshHetznerState();
try {
$status = $this->server->refreshHetznerState();
} catch (\Throwable $e) {
// The provider API can be down or the token revoked; the SSH checks below still decide.
Log::warning('Could not refresh the Hetzner server status before validation', ['server_id' => $this->server->id, 'error' => $e->getMessage()]);
$status = null;
}
$this->server->refresh();
if (in_array($status, ['initializing', 'starting', 'stopping', 'off', 'deleting', 'rebuilding', 'migrating'], true)) {
@@ -10,6 +10,7 @@ use App\Models\Team;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Process;
use Livewire\Livewire;
uses(RefreshDatabase::class);
@@ -84,3 +85,19 @@ it('blocks validation modal connection checks when the Hetzner server is off', f
'validation_logs' => 'Hetzner server is off. Power it on before validating.',
]);
});
it('validates over SSH when the Hetzner API call fails', function () {
Http::fake(['https://api.hetzner.cloud/v1/servers/456' => Http::response(['error' => ['code' => 'unauthorized']], 401)]);
$this->server->update(['ip' => '203.0.113.10']);
Process::fake();
Livewire::test(ValidateAndInstall::class, ['server' => $this->server])
->call('validateConnection')
->assertSet('error', null)
->assertSet('uptime', true)
->assertDispatched('validateOS');
Process::fake(['*' => Process::result(errorOutput: 'ssh: connect to host 203.0.113.10 port 22: Connection refused', exitCode: 255)]);
expect(fn () => ValidateServer::run($this->server))->toThrow(Exception::class, 'Connection refused');
});