feat(bastion): unattended VyOS network install with HA (bond + VRRP) #16
Reference in New Issue
Block a user
Delete Branch "feat/vyos-unattended-install"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Unattended VyOS network install through the PXE bastion, for the two Protectli VP2440 firewall boxes (HA pair: LACP bond on SFP+, VRRP floating gateways, hw-id-pinned interfaces).
VyOS has no unattended installer —
install_image()is unconditionally interactive (--no-promptis wired only toadd). The automation is injected via live-config'shookscomponent: iPXE boots the live kernel withfetch=+live-config.hooks=, the hook fetches a generated per-MAC Python driver, and the driver buildsconfig.boot, stages the rootfs, and drives the interactive installer over a pty, reporting progress to/api/progress(including the full installer transcript on failure).Key pieces
templates/vyos-boot.ipxe.ts— live-boot iPXE script (nononetworking, noconsole=ttyS0)templates/vyos-config-spec.ts— bond0 (802.3ad) + tagged VLANs + VRRP groups (vrid = VLAN id) + sync group + hw-id pinning + SSH keystemplates/vyos-install.py.ts— the generated pty driver; config built from the image's ownconfig.boot.defaultviavyos.configtree, version footer reattached viavyos.component_versionroutes/vyos.ts—/vyos/autoinstall.sh+ per-MAC/vyos/install.pymain.ts—prepareVyosArtifacts()(xorriso extraction; ISO URLlatestresolves the newestvyos-nightly-buildGitHub release — downloads.vyos.io no longer serves direct ISOs)vyosspec threaded end to end (--vyos-*,--vlan,--vlan-vipflags with guards; bond-includes-PXE-NIC rejected)Verification
--force, full-path disk answers)Deployed to the labmaster bastion and both firewalls are queued; boxes install on next PXE boot.
🤖 Generated with Claude Code
https://claude.ai/code/session_01DMVzWZgiKW2wquf5z8S1yH
Follow-up: Fedora-grade quality for the VyOS path
Pushed
5d00c42. Audited what the Fedora pipeline gives us and ported everything relevant to a router./api/log→labctl provision logs -finstalled.ipready at <ip>%post--vyos-fresh-configto override/etc/lab-provisionedanalog/config/lab-provisionedvyosfor VyOS in reprovision/recheckvyos-provision.test.ts, 3 install roundsTwo hangs the VM test caught
Both are now covered by a unit test asserting all 17
MSG_*prompts match exactly one rule. Verified it fails against the unfixed code:Verification
Reinstall preservation proven directly in the VM — round 2 queued as
vyos-r2but the box kepthost-name "vyos-r1"and the/configmarker file, exactly as intended; round 3 with--vyos-fresh-configflipped to the generated config while keeping the marker.Not done, deliberately: finer progress stages (the live transcript supersedes them) and a VyOS-native rescue mode (existing Fedora Anaconda rescue inspects a VyOS disk fine — plain ext4, no LVM).
🤖 Generated with Claude Code
Both Protectli VP2440s failed to install on real hardware: they fetched kernel+initrd and then went silent. The console showed why — Looking for a connected Ethernet interface ... e2 ? e3 ? e4 ? e5 ? Connected e4 found Connected e5 found [4.595647] igc 0000:02:00.0 e2: NIC Link is Up IP-Config: e4 ... no response after 15 secs - giving up Unable to find a live file system on the network live-boot picks the first *connected* interface. The i40e SFP+ pair links before the igc copper port (up at 4.6s), so it chose the fiber ports, which have no DHCP, and never tried the NIC that actually PXE booted. Fix: pass BOOTIF=01-<mac> on the kernel cmdline. live-boot's Device_from_bootif() (verified present in this image) matches it against /sys/class/net and sets DEVICE directly. The MAC comes from the dispatch key — i.e. exactly the NIC that PXE booted — which is more reliable than iPXE's ${net0} on a box where the booting NIC may not be net0. Why the integration test missed it: the VM had ONE NIC, so "first connected interface" was trivially correct, and virtio links instantly so there was no negotiation race. createPxeVm now takes decoyNics, attaching extra NICs ahead of the PXE NIC on a network with no route to the bastion; the VyOS test uses 2. Without BOOTIF that reproduces the hardware failure. getVmMac is network-aware so it still returns the booting NIC. Also: the bastion had every clue and said nothing — it logged INSTALL STARTED, served kernel+initrd, then nothing for 7 minutes. dispatch now stamps dispatched_at, and /api/logs/:mac returns stalled_for_s / stalled (8 min threshold, sized for the ~600MB squashfs fetch), so a machine wedged before the installer environment comes up is diagnosable without a console. Verified on hardware: both firewalls installed, bond0 802.3ad + VLANs 2/3/9/10/200 + VRRP (priority 200/100, VIP .254 per VLAN) applied, and /config/lab-provisioned written. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DMVzWZgiKW2wquf5z8S1yHThe Grafana heatmap of 1s and 0s said almost nothing, and the state timeline was an unreadable pile of overlapping series labels. Replaced as the primary view with a purpose-built page served by the exporter itself. - Probe now captures ICMP RTT, exposed as labsim_rtt_ms{src,dst}. A path that is up but slow is a different problem from one that is down, and a pass/fail grid cannot show it. - Exporter serves / (topology), /api/matrix (JSON) and /metrics. - topology.html: node per VLAN in a ring, VyOS router in the centre because every inter-VLAN packet really does traverse it, one line per pair coloured green/red with the RTT on it. Hovering gives per-direction state. A node ring goes red if anything to or from it is blocked. Side panels list blocked paths and the slowest links. Refreshes every 5s, no dependencies. Grafana stays for what it is actually good at — history of when a path flipped. Label placement is deliberate: RTT captions sit ~32% along each edge with a perpendicular nudge, because every diagonal of a 6-node mesh crosses the centre and midpoint labels stack on the router node. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DMVzWZgiKW2wquf5z8S1yHView command line instructions
Checkout
From your project repository, check out a new branch and test the changes.