fix(engine-b): write Traefik route to the serving host over SSH, not locally
TraefikWriter now targets the host that serves the traffic (DNS points at it): SshTraefikWriter SSHes in and writes the file-provider YAML there. Interface takes the traffic host + guest backend. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>feat/portal-design
parent
483a79a822
commit
157496f0c5
|
|
@ -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]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,19 +4,23 @@ namespace App\Services\Traefik;
|
|||
|
||||
class FakeTraefikWriter implements TraefikWriter
|
||||
{
|
||||
/** @var array<string, string> subdomain => target */
|
||||
/** @var array<string, string> subdomain => backend */
|
||||
public array $routes = [];
|
||||
|
||||
/** @var array<string, string> 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
|
||||
|
|
|
|||
|
|
@ -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\"",
|
||||
'',
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue