77 lines
2.9 KiB
PHP
77 lines
2.9 KiB
PHP
<?php
|
|
|
|
namespace App\Provisioning\Steps\Host;
|
|
|
|
use App\Models\ProvisioningRun;
|
|
use App\Provisioning\StepResult;
|
|
use App\Services\Dns\HostDnsDirectory;
|
|
use App\Support\HostName;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Gives the host a name: fsn-01.node.clupilot.com.
|
|
*
|
|
* The name itself is not chosen here — it was assigned once, at creation
|
|
* (App\Support\HostName::claim, called from StartHostOnboarding), and this
|
|
* step only publishes it. This entry points at the host's WireGuard address,
|
|
* NOT its public IP, and it is written to the vpn-dns container's --hostsdir
|
|
* — resolvable only inside the tunnel — rather than the public Hetzner zone.
|
|
* The name is for us, so an operator can ssh fsn-01.node.… instead of
|
|
* memorising addresses; publishing it in PUBLIC DNS would hand every scanner
|
|
* the internal subnet and roughly how many hosts sit behind it, which is the
|
|
* one thing the whole network design avoids. See App\Services\Dns\HostDnsDirectory.
|
|
*
|
|
* DNS is convenience, not a prerequisite for a working host: a failure here
|
|
* logs and moves on rather than stranding an otherwise finished onboarding.
|
|
*/
|
|
class RegisterHostDns extends HostStep
|
|
{
|
|
public function __construct(private HostDnsDirectory $dns) {}
|
|
|
|
public function key(): string
|
|
{
|
|
return 'register_host_dns';
|
|
}
|
|
|
|
public function execute(ProvisioningRun $run): StepResult
|
|
{
|
|
$host = $this->host($run);
|
|
|
|
if (blank($host->wg_ip)) {
|
|
return StepResult::fail('The host has no management address to publish.');
|
|
}
|
|
|
|
// Der Name steht seit dem Anlegen fest (StartHostOnboarding →
|
|
// HostName::claim). Dieser Schritt bildete ihn früher ein zweites Mal
|
|
// — daher kamen zwei Namen für dieselbe Maschine, von denen nur einer
|
|
// auflöste. Er veröffentlicht jetzt nur noch, was schon gilt.
|
|
$name = $host->name;
|
|
$fqdn = HostName::fqdn($name);
|
|
|
|
try {
|
|
$this->dns->write($name, $fqdn, $host->wg_ip);
|
|
} catch (Throwable $e) {
|
|
// Retried first: the write is idempotent (overwrite-by-name), so a
|
|
// retry after a transient failure (e.g. the shared volume briefly
|
|
// unavailable) just rewrites the same entry rather than risking a
|
|
// half-written file.
|
|
$allowed = min(3, (int) $run->max_attempts);
|
|
if ($run->attempt + 1 < $allowed) {
|
|
return StepResult::retry(30, 'DNS unavailable: '.$e->getMessage());
|
|
}
|
|
|
|
// Out of attempts: a name is convenience, and a host that is
|
|
// otherwise ready should not be stranded for the want of one.
|
|
$run->events()->create([
|
|
'step' => $this->key(),
|
|
'outcome' => 'info',
|
|
'message' => 'DNS name could not be registered: '.$e->getMessage(),
|
|
]);
|
|
|
|
return StepResult::advance();
|
|
}
|
|
|
|
return StepResult::advance();
|
|
}
|
|
}
|