diff --git a/app/Livewire/Admin/ConfirmRemoveHost.php b/app/Livewire/Admin/ConfirmRemoveHost.php index 2c37e56..de08361 100644 --- a/app/Livewire/Admin/ConfirmRemoveHost.php +++ b/app/Livewire/Admin/ConfirmRemoveHost.php @@ -3,6 +3,7 @@ namespace App\Livewire\Admin; use App\Models\Host; +use App\Services\Wireguard\WireguardHub; use LivewireUI\Modal\ModalComponent; /** @@ -21,11 +22,16 @@ class ConfirmRemoveHost extends ModalComponent $this->name = Host::query()->where('uuid', $uuid)->value('name') ?? ''; } - public function remove() + public function remove(WireguardHub $hub) { $host = Host::query()->where('uuid', $this->uuid)->first(); if ($host !== null) { + // Drop the hub peer first so its wg_ip can't later be reused while a + // stale peer still routes to the removed server. + if (filled($host->wg_pubkey)) { + $hub->removePeer($host->wg_pubkey); + } $host->runs()->delete(); // cascades run_resources + step_events $host->delete(); } diff --git a/app/Provisioning/Steps/Host/RebootIntoPveKernel.php b/app/Provisioning/Steps/Host/RebootIntoPveKernel.php index ac98a3f..ea5a7f7 100644 --- a/app/Provisioning/Steps/Host/RebootIntoPveKernel.php +++ b/app/Provisioning/Steps/Host/RebootIntoPveKernel.php @@ -59,6 +59,11 @@ class RebootIntoPveKernel extends HostStep $deadline = $run->context('reboot_deadline'); if ($deadline !== null && now()->greaterThan(Carbon::parse($deadline))) { + // Clear the reboot markers so a manual retry re-issues a fresh reboot + // instead of immediately re-hitting the expired deadline. + $run->forgetContext('reboot_issued'); + $run->forgetContext('reboot_deadline'); + return StepResult::fail('Host did not return on the Proxmox kernel before the deadline.'); } diff --git a/tests/Feature/Admin/HostManagementTest.php b/tests/Feature/Admin/HostManagementTest.php index df97a96..abb33c9 100644 --- a/tests/Feature/Admin/HostManagementTest.php +++ b/tests/Feature/Admin/HostManagementTest.php @@ -99,8 +99,10 @@ it('retries a failed run from the detail page', function () { Queue::assertPushed(AdvanceRunJob::class); }); -it('removes the host record without wiping the server', function () { - $host = Host::factory()->create(); +it('removes the host record and its wireguard peer without wiping the server', function () { + $services = fakeServices(); + $host = Host::factory()->active()->create(['wg_pubkey' => 'HOSTPUB=']); + $services['hub']->addPeer('HOSTPUB=', $host->wg_ip); ProvisioningRun::factory()->forHost($host)->create(); Livewire::actingAs(admin()) @@ -108,7 +110,8 @@ it('removes the host record without wiping the server', function () { ->call('remove') ->assertRedirect(route('admin.hosts')); - expect(Host::query()->find($host->id))->toBeNull(); + expect(Host::query()->find($host->id))->toBeNull() + ->and($services['hub']->peers())->not->toHaveKey('HOSTPUB='); }); it('renders the live stepper for a running host', function () { diff --git a/tests/Feature/Provisioning/HostStepsTest.php b/tests/Feature/Provisioning/HostStepsTest.php index 99cbbca..260fc7c 100644 --- a/tests/Feature/Provisioning/HostStepsTest.php +++ b/tests/Feature/Provisioning/HostStepsTest.php @@ -175,7 +175,8 @@ it('fails when the host does not return before the reboot deadline', function () 'reboot_deadline' => now()->subMinute()->toIso8601String(), ]); - expect(app(RebootIntoPveKernel::class)->execute($run)->type)->toBe('fail'); + expect(app(RebootIntoPveKernel::class)->execute($run)->type)->toBe('fail') + ->and($run->fresh()->context('reboot_issued'))->toBeNull(); // cleared so a retry re-issues }); // --- CreateAutomationToken ---