From 2c30d1fb86f11c9274608cccd7f758abc8fd2f17 Mon Sep 17 00:00:00 2001 From: nexxo Date: Sun, 2 Aug 2026 01:38:48 +0200 Subject: [PATCH] Host anlegen: die Seite beschrieb das Rettungssystem und startete die Pipeline --- app/Livewire/Admin/HostCreate.php | 37 +++++---- app/Livewire/Admin/Hosts.php | 1 - lang/de/hosts.php | 41 ++++------ lang/en/hosts.php | 39 ++++----- .../components/admin/takeover-rail.blade.php | 14 ++-- .../components/admin/takeover-steps.blade.php | 23 +++--- .../livewire/admin/host-create.blade.php | 81 ++++++------------ .../views/livewire/admin/hosts.blade.php | 2 +- tests/Feature/Admin/HostManagementTest.php | 5 +- tests/Feature/Admin/HostTakeoverGuideTest.php | 27 ++---- tests/Feature/Admin/HostTakeoverPageTest.php | 82 +++++++++++++------ 11 files changed, 165 insertions(+), 187 deletions(-) diff --git a/app/Livewire/Admin/HostCreate.php b/app/Livewire/Admin/HostCreate.php index df55aee..ee773a4 100644 --- a/app/Livewire/Admin/HostCreate.php +++ b/app/Livewire/Admin/HostCreate.php @@ -3,6 +3,7 @@ namespace App\Livewire\Admin; use App\Actions\StartHostOnboarding; +use App\Models\Datacenter; use App\Support\HostEnrolment; use App\Support\HostName; use App\Support\HostTakeoverCommand; @@ -14,16 +15,9 @@ use Livewire\Component; class HostCreate extends Component { /** - * Die fertige Befehlszeile — gesetzt, sobald der Host angelegt ist. - * - * Sie wird GENAU EINMAL gezeigt. In der Datenbank steht nur der Hash des - * Codes, und der private Schlüssel steht dort überhaupt nicht; wer die Seite - * neu lädt, bekommt sie nicht wieder, sondern legt einen neuen Code an. Das - * ist kein Versehen, sondern der Grund, warum ein abgefangener Blick auf - * einen Bildschirm später nichts mehr wert ist. + * Gesetzt, sobald der Host angelegt ist — und zugleich die Weiche der + * Ansicht zwischen Formular und Bestätigung. */ - public ?string $command = null; - public ?string $createdUuid = null; public ?string $createdName = null; @@ -33,7 +27,7 @@ class HostCreate extends Component public function mount(): void { - $this->datacenter = (string) \App\Models\Datacenter::query()->active()->orderBy('name')->value('code'); + $this->datacenter = (string) Datacenter::query()->active()->orderBy('name')->value('code'); } #[Validate('required|ip|unique:hosts,public_ip')] @@ -49,10 +43,18 @@ class HostCreate extends Component $host = $action->run($data); - // Kein Weiterleiten mehr. Die Befehlszeile gibt es nur hier und nur - // jetzt — wer den Betreiber auf eine andere Seite schickt, schickt ihn - // von dem einzigen Wert weg, den er braucht. - $this->command = HostTakeoverCommand::for($host, HostEnrolment::issueWithKeys($host)); + // Der Einmal-Code und das Schlüsselpaar entstehen weiterhin: dieselbe + // Methode legt auch die Tunneladresse und den öffentlichen Schlüssel am + // Host an, und der Code trägt den Bootstrap-Weg, der über + // Admin\ReissueTakeover erreichbar bleibt. + // + // Die BEFEHLSZEILE daraus zeigt diese Seite nicht mehr. Sie gehört zum + // Rettungssystem-Weg, und das Speichern hier startet die + // Provisionierungs-Pipeline: `EstablishSshTrust` meldet sich mit dem + // Root-Passwort an einer laufenden Debian-Maschine an, danach kommt + // Proxmox per Paket dazu. Es gibt für den Betreiber nichts zu kopieren. + HostEnrolment::issueWithKeys($host); + $this->createdUuid = $host->uuid; $this->createdName = $host->name; } @@ -65,10 +67,13 @@ class HostCreate extends Component $preview = $this->datacenter === '' ? null : HostName::preview($this->datacenter); return view('livewire.admin.host-create', [ - 'datacenters' => \App\Models\Datacenter::query()->active()->orderBy('name')->get(), + 'datacenters' => Datacenter::query()->active()->orderBy('name')->get(), 'previewName' => $preview, 'previewFqdn' => $preview === null ? null : HostName::fqdn($preview), - 'archiveUrl' => HostTakeoverCommand::archiveUrl(), + // `archiveUrl` fiel weg: er war der Hinweis, woher die Bootstrap- + // Zeile ihr Skript holt, und die Zeile zeigt diese Seite nicht mehr. + // Die fehlenden Tunnel-Einstellungen bleiben — ohne sie kommt auch + // die Pipeline nicht in den Tunnel. 'missingSettings' => HostTakeoverCommand::missingSettings(), ]); } diff --git a/app/Livewire/Admin/Hosts.php b/app/Livewire/Admin/Hosts.php index 9a7fc89..f3d95e2 100644 --- a/app/Livewire/Admin/Hosts.php +++ b/app/Livewire/Admin/Hosts.php @@ -56,7 +56,6 @@ class Hosts extends Component // Für die aufklappbare Anleitung: dieselbe Adresse, die später in // der Befehlszeile steht, damit hier nichts anderes behauptet wird // als dort getan wird. - 'archiveUrl' => \App\Support\HostTakeoverCommand::archiveUrl(), ]); } } diff --git a/lang/de/hosts.php b/lang/de/hosts.php index 2f15420..63d494e 100644 --- a/lang/de/hosts.php +++ b/lang/de/hosts.php @@ -183,43 +183,38 @@ return [ 'takeover' => [ 'eyebrow' => 'Host-Übernahme', 'how_title' => 'So läuft eine Host-Übernahme ab', - 'how_sub' => 'Sechs Schritte, etwa 20 bis 40 Minuten. Die ersten beiden passieren beim Anbieter.', + 'how_sub' => 'Drei Schritte. Nur der erste passiert beim Anbieter.', 'rail_title' => 'Ablauf', 'badge_before' => 'vorher', // Kurz — sie stehen in der Schiene nebeneinander, nicht als Absatz. + // + // Bis hierher standen hier SECHS Schritte, und zwei davon hießen + // „Rettungssystem". Sie beschrieben den Bootstrap-Weg: Rettungssystem + // starten, Befehlszeile kopieren, dort einfügen. Beim Speichern läuft + // aber die Provisionierungs-Pipeline — `EstablishSshTrust` meldet sich + // schlicht mit dem Root-Passwort an einer LAUFENDEN Debian-Maschine an, + // dann kommt Proxmox per Paket dazu. Die Seite beschrieb den einen Weg + // und ging den anderen. 's1_label' => 'Server bestellen', - 's2_label' => 'Rettungssystem', - 's3_label' => 'Host anlegen', - 's4_label' => 'Zeile kopieren', - 's5_label' => 'Einfügen', - 's6_label' => 'Zusehen', + 's2_label' => 'Host anlegen', + 's3_label' => 'Zusehen', 's1_title' => 'Server bestellen', - 's1_body' => 'Dediziert, nicht Cloud. Ohne /dev/kvm startet kein Gast, und das Skript weist die Maschine ab.', + 's1_body' => 'Dediziert, nicht Cloud, mit vorinstalliertem Debian 13 und Zugang als Root. Ohne /dev/kvm startet später kein Gast.', - 's2_title' => 'Rettungssystem starten', - 's2_body' => 'Einschalten UND neu starten. Läuft der Server noch im alten System, weigert sich das Skript — es überschreibt Platten.', + 's2_title' => 'Host anlegen', + 's2_body' => 'Rechenzentrum, öffentliche IP, Root-Passwort. Das Passwort gilt nur für die erste Anmeldung und wird verworfen, sobald der Schlüssel liegt.', - 's3_title' => 'Host anlegen', - 's3_body' => 'Die Angaben unten. Danach entsteht der Einmal-Code, und seine Frist von 24 Stunden beginnt.', - - 's4_title' => 'Befehlszeile kopieren', - 's4_body' => 'Sie erscheint nach dem Speichern und wird genau einmal gezeigt.', - - 's5_title' => 'Im Rettungssystem einfügen', - 's5_body' => 'Per SSH als root, Zeile einfügen, Eingabetaste. Nichts abtippen, nichts ausfüllen.', - 's5_hint' => 'Holt das Skript von :url und startet es. Mehr lädt der Server nicht nach.', - - 's6_title' => 'Zusehen', - 's6_body' => 'Der Fortschritt läuft auf der Host-Seite mit. Bis der Tunnel steht, meldet der Server nichts — danach kommt alles auf einmal nach.', + 's3_title' => 'Zusehen', + 's3_body' => 'CluPilot meldet sich selbst an, installiert Proxmox, startet in dessen Kernel und baut Netzbrücke, Tunnel und Vorlage. Der Fortschritt läuft auf der Host-Seite mit.', 'requirements_title' => 'Beim Anbieter, vorher', - 'requirements_sub' => 'Beides muss erledigt sein, bevor unten gespeichert wird — der Code läuft ab dem Speichern.', + 'requirements_sub' => 'Das muss stehen, bevor unten gespeichert wird — danach meldet sich CluPilot an der Maschine an.', 'form_title' => 'Der Host', - 'form_sub' => 'Vier Angaben. Den Rest holt sich der Server selbst.', + 'form_sub' => 'Drei Angaben. Den Rest holt sich der Server selbst.', 'command_title' => 'Die Befehlszeile', 'command_sub' => 'Einfügen im Rettungssystem, als root. Sie trägt alles, was der Server vor dem Tunnel braucht.', diff --git a/lang/en/hosts.php b/lang/en/hosts.php index 685186b..dd2c82d 100644 --- a/lang/en/hosts.php +++ b/lang/en/hosts.php @@ -183,42 +183,35 @@ return [ 'takeover' => [ 'eyebrow' => 'Host takeover', 'how_title' => 'How a host takeover works', - 'how_sub' => 'Six steps, roughly 20 to 40 minutes. The first two happen at the provider.', + 'how_sub' => 'Three steps. Only the first happens at the provider.', 'rail_title' => 'Procedure', 'badge_before' => 'first', + // There were SIX steps here, two of them named "rescue system": boot the + // rescue system, copy the command line, paste it there. That described + // the bootstrap route. Saving actually starts the provisioning pipeline + // — EstablishSshTrust simply signs in with the root password on a + // RUNNING Debian machine, and Proxmox arrives as a package after that. + // The page described one route and took the other. 's1_label' => 'Order server', - 's2_label' => 'Rescue system', - 's3_label' => 'Add host', - 's4_label' => 'Copy line', - 's5_label' => 'Paste', - 's6_label' => 'Watch', + 's2_label' => 'Add host', + 's3_label' => 'Watch', 's1_title' => 'Order the server', - 's1_body' => 'Dedicated, not cloud. Without /dev/kvm no guest will start, and the script turns the machine away.', + 's1_body' => 'Dedicated, not cloud, with Debian 13 preinstalled and root access. Without /dev/kvm no guest will start later on.', - 's2_title' => 'Boot the rescue system', - 's2_body' => 'Enable it AND restart. If the server is still in the old system the script refuses — it overwrites disks.', + 's2_title' => 'Add the host', + 's2_body' => 'Datacenter, public IP, root password. The password is only for the first sign-in and is discarded once the key is in place.', - 's3_title' => 'Add the host', - 's3_body' => 'The fields below. The one-time code is created after that, and its 24-hour window starts.', - - 's4_title' => 'Copy the command line', - 's4_body' => 'It appears after saving and is shown exactly once.', - - 's5_title' => 'Paste it into the rescue system', - 's5_body' => 'SSH in as root, paste, press enter. Nothing typed out, nothing filled in.', - 's5_hint' => 'Fetches the installer from :url and starts it. The server downloads nothing else.', - - 's6_title' => 'Watch', - 's6_body' => 'Progress appears on the host page. Until the tunnel is up the server reports nothing — then everything arrives at once.', + 's3_title' => 'Watch', + 's3_body' => 'CluPilot signs in by itself, installs Proxmox, reboots into its kernel and builds the bridge, the tunnel and the template. Progress appears on the host page.', 'requirements_title' => 'At the provider, first', - 'requirements_sub' => 'Both have to be done before you save below — the code starts expiring the moment you do.', + 'requirements_sub' => 'This has to be in place before you save below — CluPilot signs in to the machine straight after.', 'form_title' => 'The host', - 'form_sub' => 'Four fields. The server works out the rest itself.', + 'form_sub' => 'Three fields. The server works out the rest itself.', 'command_title' => 'The command line', 'command_sub' => 'Paste into the rescue system as root. It carries everything the server needs before the tunnel.', diff --git a/resources/views/components/admin/takeover-rail.blade.php b/resources/views/components/admin/takeover-rail.blade.php index e79546d..8c21fbd 100644 --- a/resources/views/components/admin/takeover-rail.blade.php +++ b/resources/views/components/admin/takeover-rail.blade.php @@ -1,7 +1,7 @@ @props([ - // Der Schritt, an dem der Betreiber gerade steht: 3 solange das Formular - // offen ist, 4 sobald die Befehlszeile da ist. - 'current' => 3, + // Der Schritt, an dem der Betreiber gerade steht: 2 solange das Formular + // offen ist, 3 sobald gespeichert wurde und die Pipeline läuft. + 'current' => 2, ]) {{-- Der Ablauf als schmale Schiene, nicht als Textwand. @@ -20,13 +20,15 @@ dasselbe Problem wäre eine zweite Sprache. --}} @php + // Drei, nicht sechs. Die drei gestrichenen — Rettungssystem starten, Zeile + // kopieren, dort einfügen — gehörten zum Bootstrap-Weg. Gefahren wird beim + // Speichern die Provisionierungs-Pipeline, und die meldet sich selbst an + // einer laufenden Debian-Maschine an. Der Betreiber tut wirklich weniger; + // die Schiene sagt es jetzt auch. $steps = [ 1 => __('hosts.takeover.s1_label'), 2 => __('hosts.takeover.s2_label'), 3 => __('hosts.takeover.s3_label'), - 4 => __('hosts.takeover.s4_label'), - 5 => __('hosts.takeover.s5_label'), - 6 => __('hosts.takeover.s6_label'), ]; @endphp diff --git a/resources/views/components/admin/takeover-steps.blade.php b/resources/views/components/admin/takeover-steps.blade.php index d6f9239..4cf7cc0 100644 --- a/resources/views/components/admin/takeover-steps.blade.php +++ b/resources/views/components/admin/takeover-steps.blade.php @@ -1,22 +1,22 @@ -@props(['archiveUrl' => '']) - -{{-- Dieselben sechs Schritte, für die Stelle, an der noch nichts angelegt ist: +{{-- Dieselben drei Schritte, für die Stelle, an der noch nichts angelegt ist: die Host-Liste. Dort ist es eine Auskunft und kein Ablauf — also Zeilen mit je einem Satz, nicht die Schiene mit Zustand. Eine Zeile je Schritt, nach dem Muster von `x-ui.row`: links wozu, rechts - was. Die zwei Schritte beim Anbieter tragen eine Plakette, sonst liest sich - die Liste, als könne man oben anfangen und sich durcharbeiten — und Schritt - 2 ist eine halbe Stunde Wartezeit. --}} + was. Der Schritt beim Anbieter trägt eine Plakette, sonst liest sich die + Liste, als könne man oben anfangen und sich durcharbeiten. + + Waren sechs, zwei davon „Rettungssystem". Sie beschrieben den + Bootstrap-Weg; gefahren wird beim Anlegen die Provisionierungs-Pipeline, + die sich selbst an einer laufenden Debian-Maschine anmeldet. Mit den + Schritten fiel auch `archiveUrl` weg — die Adresse gehörte zur + Befehlszeile, die es hier nicht mehr gibt. --}} @php $steps = [ 1 => [__('hosts.takeover.s1_title'), __('hosts.takeover.s1_body'), true], - 2 => [__('hosts.takeover.s2_title'), __('hosts.takeover.s2_body'), true], + 2 => [__('hosts.takeover.s2_title'), __('hosts.takeover.s2_body'), false], 3 => [__('hosts.takeover.s3_title'), __('hosts.takeover.s3_body'), false], - 4 => [__('hosts.takeover.s4_title'), __('hosts.takeover.s4_body'), false], - 5 => [__('hosts.takeover.s5_title'), __('hosts.takeover.s5_body'), false], - 6 => [__('hosts.takeover.s6_title'), __('hosts.takeover.s6_body'), false], ]; @endphp @@ -32,9 +32,6 @@

{{ $body }} - @if ($number === 5 && $archiveUrl) - {{ __('hosts.takeover.s5_hint', ['url' => $archiveUrl]) }} - @endif

@endforeach diff --git a/resources/views/livewire/admin/host-create.blade.php b/resources/views/livewire/admin/host-create.blade.php index 937fc58..04fab01 100644 --- a/resources/views/livewire/admin/host-create.blade.php +++ b/resources/views/livewire/admin/host-create.blade.php @@ -6,25 +6,25 @@

{{ __('hosts.takeover.eyebrow') }}

- @if ($command === null) + @if ($createdUuid === null) {{ __('hosts.create_title') }} @else {{ __('hosts.takeover.title', ['name' => $createdName]) }} @endif

- {{ $command === null ? __('hosts.create_sub') : __('hosts.takeover.subtitle') }} + {{ $createdUuid === null ? __("hosts.create_sub") : __("hosts.takeover.subtitle") }}

{{-- Der Ablauf als Schiene, nicht als Wand. Sie beantwortet „wo stehe ich" auf einen Blick und überlässt dem Hauptteil die Arbeit. --}} -
- @if ($command === null) + @if ($createdUuid === null) @if ($missingSettings)

{{ __('hosts.takeover.missing_title') }}

@@ -32,9 +32,12 @@
@endif - {{-- Die zwei Schritte beim Anbieter. Sie stehen VOR dem - Formular, weil sie vorher erledigt sein müssen: der - Einmal-Code beginnt mit dem Speichern abzulaufen. --}} + {{-- Der eine Schritt beim Anbieter. Er steht VOR dem Formular, + weil er vorher erledigt sein muss: unmittelbar nach dem + Speichern meldet sich CluPilot an der Maschine an. + Hier standen zwei Zeilen — die zweite hieß „Rettungssystem + starten" und gehörte zum Bootstrap-Weg, den diese Seite + nicht geht. --}}

{{ __('hosts.takeover.requirements_title') }}

{{ __('hosts.takeover.requirements_sub') }}

@@ -43,9 +46,6 @@

{{ __('hosts.takeover.s1_body') }}

- -

{{ __('hosts.takeover.s2_body') }}

-
@@ -97,55 +97,24 @@
@else - {{-- Ab hier ist die Befehlszeile die Hauptsache. Sie bekommt die - volle Breite und steht ganz oben; alles andere ordnet sich - unter. --}} + {{-- Hier stand die Befehlszeile, die der Betreiber im + Rettungssystem einfügen sollte. Sie gehört zum + Bootstrap-Weg; beim Speichern läuft die + Provisionierungs-Pipeline, und die meldet sich selbst an. + Es gibt also nichts mehr zu kopieren — nur noch etwas + anzusehen. --}}
-

{{ __('hosts.takeover.command_title') }}

-

{{ __('hosts.takeover.command_sub') }}

+

{{ __('hosts.takeover.s3_title') }}

+

{{ __('hosts.takeover.s3_body') }}

-
-
-

{{ __('hosts.takeover.once_title') }}

- -
- - {{-- Umbrechen statt waagerecht rollen: aus einem Kasten - mit Rollbalken markiert jemand die Hälfte und merkt - es erst auf der Maschine. --}} -
{{ $command }}
+ - -

{{ __('hosts.takeover.once_body') }}

-
- -
-

{{ __('hosts.takeover.after_title') }}

- - - -

{{ __('hosts.takeover.s5_body') }}

-

{{ __('hosts.takeover.s5_hint', ['url' => $archiveUrl]) }}

-
- -

{{ __('hosts.takeover.s6_body') }}

- -
-
@endif diff --git a/resources/views/livewire/admin/hosts.blade.php b/resources/views/livewire/admin/hosts.blade.php index 693656e..3b55f31 100644 --- a/resources/views/livewire/admin/hosts.blade.php +++ b/resources/views/livewire/admin/hosts.blade.php @@ -23,7 +23,7 @@ - + {{-- Filter bar — scales to many hosts (search + datacenter + status). --}} diff --git a/tests/Feature/Admin/HostManagementTest.php b/tests/Feature/Admin/HostManagementTest.php index bc61444..f6a1bcf 100644 --- a/tests/Feature/Admin/HostManagementTest.php +++ b/tests/Feature/Admin/HostManagementTest.php @@ -57,7 +57,10 @@ it('creates a host and starts onboarding with an encrypted password', function ( // das Rettungssystem gibt es nur hier und nur jetzt. Wer den Betreiber // weiterschickt, schickt ihn von dem einzigen Wert weg, den er braucht. ->assertNoRedirect() - ->assertSee('clupilot-bootstrap.sh'); + // Keine Befehlszeile mehr: das Speichern startet die Pipeline, die + // sich selbst anmeldet. Was bleibt, ist der Weg zum Zusehen. + ->assertDontSee('clupilot-bootstrap.sh') + ->assertSee(__('hosts.takeover.watch')); $host = Host::query()->where('name', 'fsn-01')->first(); expect($host)->not->toBeNull()->and($host->status)->toBe('pending'); diff --git a/tests/Feature/Admin/HostTakeoverGuideTest.php b/tests/Feature/Admin/HostTakeoverGuideTest.php index 0275d3b..5ccbfd8 100644 --- a/tests/Feature/Admin/HostTakeoverGuideTest.php +++ b/tests/Feature/Admin/HostTakeoverGuideTest.php @@ -3,6 +3,7 @@ use App\Livewire\Admin\HostCreate; use App\Livewire\Admin\Hosts; use App\Models\Operator; +use Livewire\Features\SupportTesting\Testable; use Livewire\Livewire; beforeEach(function () { @@ -11,7 +12,7 @@ beforeEach(function () { config()->set('provisioning.dns.platform_zone', 'clupilot.com'); }); -function guidePage(string $component): \Livewire\Features\SupportTesting\Testable +function guidePage(string $component): Testable { return Livewire::actingAs(Operator::factory()->role('Owner')->create(), 'operator')->test($component); } @@ -30,14 +31,13 @@ it('explains the whole procedure before anything is created', function () { // Alle sechs, als Schiene — kurze Beschriftungen, damit die Seite eine // Seite bleibt und keine Textwand wird. Das war der zweite Fund: sechs // Absätze übereinander beantworteten alles und zeigten nichts. - foreach (range(1, 6) as $step) { + foreach (range(1, 3) as $step) { $page->assertSee(__("hosts.takeover.s{$step}_label")); } - // Die zwei beim Anbieter ausführlich, weil sie VOR dem Speichern erledigt - // sein müssen und nur hier erklärt werden. - $page->assertSee(__('hosts.takeover.s1_body')) - ->assertSee(__('hosts.takeover.s2_body')); + // Der beim Anbieter ausführlich, weil er VOR dem Speichern erledigt sein + // muss und nur hier erklärt wird. + $page->assertSee(__('hosts.takeover.s1_body')); }); /** @@ -57,23 +57,10 @@ it('offers the same procedure on the hosts list', function () { * durcharbeiten — und Schritt 2 ist dann eine halbe Stunde Wartezeit mitten * in einem laufenden Code. */ -it('marks the two provider steps as things to do first', function () { +it('marks the provider step as something to do first', function () { guidePage(HostCreate::class)->assertSee(__('hosts.takeover.badge_before')); }); -/** - * Die Anleitung nennt die Adresse, von der geladen wird, und es muss dieselbe - * sein, die später in der Zeile steht. Zwei Fassungen davon liefen auseinander, - * und der Unterschied fiele auf einem Server auf, der schon bestellt ist. - */ -it('names the same archive address the command will use', function () { - // Auf der Liste, wo die Anleitung Auskunft gibt. Auf der Anlegen-Seite - // steht die Adresse erst NACH dem Speichern: dort ist sie Teil der - // Befehlszeile und keine Angabe für sich, und vorher wäre sie eine - // Einzelheit mehr auf einer Seite, die vier Felder ausgefüllt haben will. - guidePage(Hosts::class)->assertSee('files.clupilot.test/bootstrap.tar.gz'); -}); - /** * Vor dem Anlegen gibt es keine Befehlszeile — sonst stünde dort eine, die zu * keinem Host gehört. diff --git a/tests/Feature/Admin/HostTakeoverPageTest.php b/tests/Feature/Admin/HostTakeoverPageTest.php index 03141c3..18dcb7b 100644 --- a/tests/Feature/Admin/HostTakeoverPageTest.php +++ b/tests/Feature/Admin/HostTakeoverPageTest.php @@ -1,10 +1,13 @@ role('Owner')->create(), 'operator') ->test(HostCreate::class) @@ -36,21 +39,46 @@ function createHostAs(): \Livewire\Features\SupportTesting\Testable } /** - * Die eine Frage, die diese Seite beantworten muss: was gebe ich auf dem Server - * ein? Steht die Zeile nicht da, ist die ganze Übernahme nicht benutzbar. + * Die Bootstrap-Zeile, wie sie der Weg über das Rettungssystem braucht. + * + * Sie wird hier direkt gebaut statt über eine Seite abgegriffen. Ihre + * Richtigkeit hat nichts damit zu tun, wer sie anzeigt — und genau diese + * Kopplung war der Grund, warum fünf Tests umfielen, als die Anlegen-Seite + * aufhörte, sie zu zeigen. */ -it('shows the command exactly once, right after creating the host', function () { - $page = createHostAs(); +function takeoverCommandFor(Host $host): string +{ + return HostTakeoverCommand::for($host, HostEnrolment::issueWithKeys($host)); +} - $page->assertSee('curl') - ->assertSee('clupilot-bootstrap.sh') - ->assertSee('--code'); +/** + * Die Anlegen-Seite zeigt keine Befehlszeile mehr. + * + * Sie gehörte zum Bootstrap-Weg: Rettungssystem starten, Zeile kopieren, dort + * einfügen. Das Speichern startet aber die Provisionierungs-Pipeline, und die + * meldet sich mit dem Root-Passwort selbst an einer laufenden Debian-Maschine + * an. Es gibt nichts zu kopieren — die Seite behauptete es trotzdem. + */ +it('does not hand the operator a command line they do not need', function () { + createHostAs() + ->assertDontSee('clupilot-bootstrap.sh') + ->assertDontSee('--code') + // Stattdessen der Weg zu dem, was jetzt wirklich passiert. + ->assertSee(__('hosts.takeover.watch')); +}); + +/** + * Der Weg über das Rettungssystem bleibt erreichbar — nur nicht mehr von der + * Anlegen-Seite aus. Wer leeres Blech ohne vorinstalliertes System hat, + * bekommt die Zeile über Admin\ReissueTakeover. + */ +it('still issues the command line where the rescue route lives', function () { + $host = Host::factory()->create(['datacenter' => 'fsn']); - // Ein frisch geladenes Bauteil — also jemand, der die Seite neu lädt — hat - // nichts mehr. Der Code steht nur als Hash in der Datenbank. Livewire::actingAs(Operator::factory()->role('Owner')->create(), 'operator') - ->test(HostCreate::class) - ->assertDontSee('curl'); + ->test(ReissueTakeover::class, ['uuid' => $host->uuid]) + ->call('issue') + ->assertSee('clupilot-bootstrap.sh'); }); /** @@ -59,7 +87,7 @@ it('shows the command exactly once, right after creating the host', function () * das merkt man erst auf einer Maschine, die schon bestellt ist. */ it('carries every value the script needs before the tunnel', function () { - $command = createHostAs()->get('command'); + $command = takeoverCommandFor(Host::factory()->create(['datacenter' => 'fsn'])); foreach (['--code', '--wg-private', '--wg-ip', '--hub-pubkey', '--hub-endpoint', '--api', '--fqdn'] as $argument) { expect($command)->toContain($argument); @@ -94,7 +122,7 @@ it('keeps host names out of the customer zone', function () { $host = Host::factory()->create(['name' => 'fsn-01']); - expect(App\Support\HostTakeoverCommand::fqdnFor($host)) + expect(HostTakeoverCommand::fqdnFor($host)) ->toBe('fsn-01.node.clupilot.com'); }); @@ -110,7 +138,7 @@ it('fetches the installer over the public hostname', function () { // Konsolen-Hostnamen: die Konsole ist per Client-IP abgeriegelt // (RestrictConsoleNetwork), und ein frischer Server im Rettungssystem steht // in keinem der freigegebenen Bereiche. - expect(createHostAs()->get('command')) + expect(takeoverCommandFor(Host::factory()->create(['datacenter' => 'fsn']))) ->toContain('https://files.clupilot.test/bootstrap.tar.gz') ->not->toContain('admin.'); }); @@ -120,7 +148,7 @@ it('fetches the installer over the public hostname', function () { * zerlegt die Shell die Zeile an einer Stelle, die niemand sieht. */ it('quotes the keys so a shell cannot split them', function () { - $command = createHostAs()->get('command'); + $command = takeoverCommandFor(Host::factory()->create(['datacenter' => 'fsn'])); expect($command)->toMatch("/--wg-private '[^']+'/") ->and($command)->toMatch("/--hub-pubkey '[^']+'/"); @@ -131,13 +159,14 @@ it('quotes the keys so a shell cannot split them', function () { * die Anleitung richtig und der Ablauf trotzdem kaputt. */ it('shows a code that actually resolves to the new host', function () { - $command = createHostAs()->get('command'); + $host = Host::factory()->create(['datacenter' => 'fsn', 'name' => 'fsn-07']); + $command = takeoverCommandFor($host); preg_match('/--code ([A-Za-z0-9]+)/', $command, $matches); - // Der Name kommt jetzt von CluPilot selbst, nicht mehr vom Formular — der - // erste Host in diesem (frischen) Testlauf für fsn heißt fsn-01. - expect(HostEnrolment::claim($matches[1])?->name)->toBe('fsn-01'); + // Auf DIESEN Host, nicht bloß auf irgendeinen: der Code ist der Ausweis, + // mit dem sich der Server meldet. + expect(HostEnrolment::claim($matches[1])?->is($host))->toBeTrue(); }); /** @@ -155,16 +184,15 @@ it('warns before creating a host when the tunnel settings are missing', function }); /** - * Nach dem Anlegen bleibt dieselbe Anleitung stehen, nur mit der Zeile darin. - * Ein Betreiber, der bei Schritt 5 nicht weiterweiß, soll nicht auf einer Seite - * landen, die nur noch aus einem Kasten besteht. + * Nach dem Anlegen bleibt derselbe Ablauf stehen, nur einen Schritt weiter. + * Die Schiene bleibt vollstaendig sichtbar: wer wissen will, was noch kommt, + * soll nicht auf einer Seite landen, die nur noch aus einem Knopf besteht. */ it('keeps the whole procedure visible after the host exists', function () { createHostAs() - ->assertSee(__('hosts.takeover.s1_title')) - ->assertSee(__('hosts.takeover.s5_title')) - ->assertSee(__('hosts.takeover.s6_title')) - ->assertSee('--wg-private', escape: false); + ->assertSee(__('hosts.takeover.s1_label')) + ->assertSee(__('hosts.takeover.s2_label')) + ->assertSee(__('hosts.takeover.s3_label')); }); it('refuses to create a host without the permission', function () {