diff --git a/app/Provisioning/Steps/Customer/ConfigureDnsAndTls.php b/app/Provisioning/Steps/Customer/ConfigureDnsAndTls.php index a63dbf3..f64d4d0 100644 --- a/app/Provisioning/Steps/Customer/ConfigureDnsAndTls.php +++ b/app/Provisioning/Steps/Customer/ConfigureDnsAndTls.php @@ -41,11 +41,12 @@ class ConfigureDnsAndTls extends CustomerStep $this->recordResource($run, $host, 'dns_record_id', $recordId); } - // Traefik file-provider route → the guest VM (fall back to the host only - // if the guest address wasn't captured). + // Traefik file-provider route, written on the serving host (DNS points at + // it) and pointing at the guest VM. if (! $instance->route_written) { - $target = $instance->guest_ip ?: ($host->wg_ip ?? $host->public_ip); - $this->traefik->write($instance->subdomain, $target); + $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]); } diff --git a/app/Services/Traefik/FakeTraefikWriter.php b/app/Services/Traefik/FakeTraefikWriter.php index 5417872..79a059c 100644 --- a/app/Services/Traefik/FakeTraefikWriter.php +++ b/app/Services/Traefik/FakeTraefikWriter.php @@ -4,19 +4,23 @@ namespace App\Services\Traefik; class FakeTraefikWriter implements TraefikWriter { - /** @var array subdomain => target */ + /** @var array subdomain => backend */ public array $routes = []; + /** @var array subdomain => traffic host */ + public array $hosts = []; + public bool $certReady = true; - public function write(string $subdomain, string $targetHost): void + public function write(string $trafficHost, string $subdomain, string $backend): void { - $this->routes[$subdomain] = $targetHost; + $this->routes[$subdomain] = $backend; + $this->hosts[$subdomain] = $trafficHost; } - public function remove(string $subdomain): void + public function remove(string $trafficHost, string $subdomain): void { - unset($this->routes[$subdomain]); + unset($this->routes[$subdomain], $this->hosts[$subdomain]); } public function certReachable(string $fqdn): bool diff --git a/app/Services/Traefik/SshTraefikWriter.php b/app/Services/Traefik/SshTraefikWriter.php index 7ed516c..6a525a4 100644 --- a/app/Services/Traefik/SshTraefikWriter.php +++ b/app/Services/Traefik/SshTraefikWriter.php @@ -2,35 +2,32 @@ namespace App\Services\Traefik; +use App\Services\Ssh\RemoteShell; use Illuminate\Support\Facades\Http; -use RuntimeException; /** - * Writes Traefik file-provider YAML into the configured dynamic directory (where - * Traefik runs) and checks HTTP-01 cert issuance by probing the fqdn over HTTPS. - * The concrete write path (host SSH / mounted volume) is a deployment watch-item. - * Not unit-tested (live I/O). + * Writes the Traefik file-provider YAML onto the host that actually serves the + * traffic (DNS points at it), via SSH — not the worker's local filesystem — so + * that host's Traefik picks up the route. Checks HTTP-01 cert issuance by + * probing the fqdn. Not unit-tested (live I/O). */ class SshTraefikWriter implements TraefikWriter { - public function write(string $subdomain, string $targetHost): void + public function __construct(private RemoteShell $shell) {} + + public function write(string $trafficHost, string $subdomain, string $backend): void { $zone = (string) config('provisioning.dns.zone'); - $fqdn = "{$subdomain}.{$zone}"; - $yaml = $this->render($subdomain, $fqdn, $targetHost); + $yaml = $this->render($subdomain, "{$subdomain}.{$zone}", $backend); - $path = rtrim((string) config('provisioning.traefik.dynamic_path'), '/')."/{$subdomain}.yml"; - if (file_put_contents($path, $yaml) === false) { - throw new RuntimeException("Failed to write Traefik config: {$path}"); - } + $this->shell->connectWithKey($trafficHost, 'root', (string) config('provisioning.ssh.private_key')); + $this->shell->putFile($this->path($subdomain), $yaml); // putFile throws on failure } - public function remove(string $subdomain): void + public function remove(string $trafficHost, string $subdomain): void { - $path = rtrim((string) config('provisioning.traefik.dynamic_path'), '/')."/{$subdomain}.yml"; - if (is_file($path)) { - unlink($path); - } + $this->shell->connectWithKey($trafficHost, 'root', (string) config('provisioning.ssh.private_key')); + $this->shell->run('rm -f '.escapeshellarg($this->path($subdomain))); } public function certReachable(string $fqdn): bool @@ -45,7 +42,12 @@ class SshTraefikWriter implements TraefikWriter } } - private function render(string $subdomain, string $fqdn, string $targetHost): string + private function path(string $subdomain): string + { + return rtrim((string) config('provisioning.traefik.dynamic_path'), '/')."/{$subdomain}.yml"; + } + + private function render(string $subdomain, string $fqdn, string $backend): string { return implode("\n", [ 'http:', @@ -60,7 +62,7 @@ class SshTraefikWriter implements TraefikWriter " {$subdomain}:", ' loadBalancer:', ' servers:', - " - url: \"http://{$targetHost}:80\"", + " - url: \"http://{$backend}:80\"", '', ]); } diff --git a/app/Services/Traefik/TraefikWriter.php b/app/Services/Traefik/TraefikWriter.php index cda6622..1daa16e 100644 --- a/app/Services/Traefik/TraefikWriter.php +++ b/app/Services/Traefik/TraefikWriter.php @@ -4,10 +4,13 @@ namespace App\Services\Traefik; interface TraefikWriter { - /** Write a file-provider router for subdomain → target host. */ - public function write(string $subdomain, string $targetHost): void; + /** + * Write a file-provider router on the Traefik host ($trafficHost) that routes + * the subdomain to the guest ($backend). + */ + public function write(string $trafficHost, string $subdomain, string $backend): void; - public function remove(string $subdomain): void; + public function remove(string $trafficHost, string $subdomain): void; /** True once the ACME (HTTP-01) certificate for the fqdn is served. */ public function certReachable(string $fqdn): bool;