instance($run); $node = (string) $run->context('node'); $vmid = (int) $run->context('vmid'); $pve = $this->pve->forHost($instance->host); $this->guest($pve, $run, 'hostnamectl set-hostname '.escapeshellarg($instance->subdomain)); // Capture the guest's own address so Traefik routes to the VM, not the // host. Poll until it's available rather than advancing with no address. $ipOut = trim($pve->guestExec($node, $vmid, 'hostname -I')['out-data'] ?? ''); $guestIp = trim(strtok($ipOut, ' ') ?: ''); if ($guestIp === '') { if ($run->started_at !== null && $run->started_at->copy()->addSeconds(90)->isPast()) { return StepResult::fail('guest_ip_unavailable'); } return StepResult::poll(10, 'waiting for the guest to obtain an address'); } $instance->update(['guest_ip' => $guestIp]); // Allow HTTP/HTTPS, deny everything else (management stays on the tunnel). $pve->applyFirewall($node, $vmid, [ ['type' => 'in', 'action' => 'ACCEPT', 'proto' => 'tcp', 'dport' => '80'], ['type' => 'in', 'action' => 'ACCEPT', 'proto' => 'tcp', 'dport' => '443'], ]); return StepResult::advance(); } }