instance($run); $host = $instance->host; $fqdn = $instance->subdomain.'.'.ProvisioningSettings::dnsZone(); // DNS — provider upsert is idempotent; the local row uses firstOrCreate on // the record id and is written BEFORE the breadcrumb (the short-circuit guard). if (! $this->hasResource($run, 'dns_record_id')) { $recordId = $this->dns->upsertRecord($fqdn, 'A', $host->public_ip); $instance->dnsRecords()->firstOrCreate( ['record_id' => $recordId], ['provider' => 'hetzner', 'fqdn' => $fqdn, 'type' => 'A', 'value' => $host->public_ip], ); $this->recordResource($run, $host, 'dns_record_id', $recordId); } // Traefik file-provider route, written on the serving host (DNS points at // it) and pointing at the guest VM. if (! $instance->route_written) { $trafficHost = $host->wg_ip ?? $host->public_ip; $backend = $instance->guest_ip ?: $host->public_ip; $this->traefik->write($trafficHost, $instance->subdomain, $backend); $instance->update(['route_written' => true]); } // TLS via HTTP-01 — poll until the certificate is served. if (! $instance->cert_ok) { if ($this->traefik->certReachable($fqdn)) { $instance->update(['cert_ok' => true]); } elseif ($run->started_at !== null && $run->started_at->copy()->addSeconds(840)->isPast()) { return StepResult::fail('cert_timeout'); } else { return StepResult::poll(20, 'waiting for certificate'); } } return StepResult::advance(); } }