option('dry-run'); $started = 0; /** @var array Grund => Anzahl */ $skipped = []; $query = Host::query(); if ($name = $this->option('host')) { $query->where(fn ($q) => $q->where('name', $name)->orWhere('public_ip', $name)); } foreach ($query->cursor() as $host) { $reason = $this->reasonToSkip($host); if ($reason !== null) { $skipped[$reason] = ($skipped[$reason] ?? 0) + 1; continue; } $started++; $this->line(($dryRun ? '[Probelauf] ' : '')."{$host->name} ({$host->public_ip}) über {$host->wg_ip}"); if (! $dryRun) { $this->startRun($host); } } foreach ($skipped as $reason => $count) { $this->line("übersprungen ({$reason}): {$count}"); } $this->info($dryRun ? "Probelauf: {$started} Host(s) bekämen ihr Regelwerk neu, ".array_sum($skipped).' übersprungen. Nichts wurde geändert.' : "{$started} Lauf/Läufe gestartet, ".array_sum($skipped).' übersprungen.'); return self::SUCCESS; } /** * Warum dieser Host in Ruhe gelassen wird, oder null. * * Der Grund IST der Bericht: „12 übersprungen" und sonst nichts ist keine * Auskunft, mit der jemand etwas anfangen kann — „12 sind noch in der * Übernahme" und „12 haben keinen Tunnel" sehen von hier aus gleich aus. */ private function reasonToSkip(Host $host): ?string { // Der Schritt verbindet sich über die WireGuard-Adresse und prüft den // Tunnel von der Hostseite. Ohne beides gibt es nichts nachzuziehen. if (blank($host->wg_ip) || blank($host->ssh_host_key)) { return 'kein Tunnel, kein gepinnter Schlüssel'; } // Ein Host mitten in der Übernahme bekommt sein Regelwerk ohnehin // gleich — und ein zweiter Lauf daneben schriebe dieselbe Datei. if (! in_array($host->status, ['active', 'disabled'], true)) { return 'nicht übernommen (Status '.$host->status.')'; } if ($this->hasRunInFlight($host)) { return 'anderer Lauf aktiv'; } return null; } private function startRun(Host $host): void { $run = ProvisioningRun::create([ 'subject_type' => Host::class, 'subject_id' => $host->id, 'pipeline' => 'host-firewall', 'status' => ProvisioningRun::STATUS_PENDING, 'current_step' => 0, 'context' => [], ]); AdvanceRunJob::dispatch($run->uuid); } /** * JEDER Lauf gegen diesen Host, absichtlich weit gefasst: das hier ist ein * Reparatur-Durchgang. Er läuft wieder, und ein heute übergangener Host ist * beim nächsten Aufruf dran. Nichts geht durch Warten verloren. */ private function hasRunInFlight(Host $host): bool { return ProvisioningRun::query() ->where('subject_type', Host::class) ->where('subject_id', $host->id) ->inFlight() ->exists(); } }