Files
pxe-server/playbook/templates/netplan-pxe.yaml.j2
cproudlock b11e678297 NICs: pick the imaging port at install, GE can go into any other port
Sites no longer need either MAC in site-vars.

pxe_nic_mac empty: before anything changes, the playbook runs
pxe-pick-nic --auto. It takes the one wired port with a link, accepts it
only if pxe-nic-check calls it isolated (no LLDP/CDP/802.1X, no other
DHCP server, at most 10 hosts heard in 65 s), and pins it. The choice is
saved in /etc/pxe-server/nic-pick.conf and never re-guessed at boot.
When it cannot tell (no link, two links, not isolated) nothing changes,
and the console banner says why and to run sudo pxe-pick-nic: pull the
imaging cable and plug it back in, and the port that blinked is the
imaging one. Both helpers are installed before the pick, so that works
after a refused first boot. The site's corp domains and allowed
switches are passed to the check.

corp_nic_mac empty: every other wired port (en*, eth*) is a catch-all
DHCP client, so GE can be plugged in at install or later.
- dnsmasq stays pinned with interface=pxe0. A new check refuses any
  dhcp-range outside the imaging subnet, and proxy ranges.
- The catch-all ports ignore offers from 172.16.9.1.
- UFW allows SSH and 9009 by source on any interface, and keeps those
  rules when the old any-interface rules are removed.
- pxe_nic_name must not look like en*/eth* in this mode.

pxe-netplan-apply.sh takes 3 or 5 args. Without a pinned GE NIC it:
- renames stray names (the old pxe0 after a re-pick, a leftover corp0)
  to enx<mac>, so the catch-all takes them, and restores them on revert;
- checks that every NIC that had a DHCP address before has one after,
  tracked by MAC.

With both MACs set (the WJ server), netplan, the dnsmasq pin and UFW
render byte-for-byte as before.

Tested in the pxetest VMs:
- Fresh install with no MACs and the GE link down: the imaging port was
  picked and pinned as pxe0 with 172.16.9.1, and dnsmasq bound DHCP to
  pxe0 only. GE plugged in afterwards got a lease, SSH worked, and the
  guard reported pxe0 isolated.
- --auto refuses with two links.
- The interactive pick works by pulling and replugging the link.
- A 3-arg re-apply is UNCHANGED.

Unit tests (fake sysfs) cover the re-pick and pinned-to-catch-all renames.
2026-10-16 15:56:47 -04:00

67 lines
2.7 KiB
Django/Jinja

# Managed by pxe_server_setup.yml (vars pxe_nic_mac, corp_nic_mac,
# pxe_nic_name, corp_nic_name, pxe_server_ip, pxe_subnet). Local edits are
# replaced on the next playbook run; the previous file is kept under
# /var/backups/netplan-<timestamp>/.
#
{% if corp_nic_mac %}
# Two NICs, each matched by MAC and given a stable name:
# pxe-lan imaging LAN, static, no gateway, no DNS. The ONLY NIC that
# serves DHCP/TFTP/HTTP boot/SMB. ignore-carrier: the address
# is put on (and kept) without link, so a switch that is off
# or a cable pulled at boot does not leave dnsmasq, Samba and
# Apache without {{ pxe_server_ip }} (networkd
# ConfigureWithoutCarrier= + IgnoreCarrierLoss=).
# ge-corp GE corporate network, DHCP client (SSH + webapp 9009, updates).
{% else %}
# The imaging NIC pinned by MAC, every other wired port a DHCP client:
# pxe-lan imaging LAN, matched by MAC and given a stable name. Static,
# no gateway, no DNS. The ONLY NIC that serves DHCP/TFTP/HTTP
# boot/SMB. ignore-carrier: the address is put on (and kept)
# without link, so a switch that is off or a cable pulled at
# boot does not leave dnsmasq, Samba and Apache without
# {{ pxe_server_ip }} (networkd ConfigureWithoutCarrier= +
# IgnoreCarrierLoss=).
# zz-en, zz-eth every other wired port: DHCP client, so the GE cable
# can go into any of them, at install or later. No set-name.
# networkd applies the first matching .network file in name
# order: 10-netplan-pxe-lan sorts before 10-netplan-zz-*, and
# {{ pxe_nic_name }} does not look like en*/eth* (the playbook
# checks), so the imaging NIC never falls into these. dnsmasq
# never serves them (interface= pins it to {{ pxe_nic_name }}),
# and a drop-in makes them ignore offers from {{ pxe_server_ip }}.
{% endif %}
network:
version: 2
renderer: networkd
ethernets:
pxe-lan:
match:
macaddress: "{{ pxe_nic_mac | lower }}"
set-name: {{ pxe_nic_name }}
dhcp4: false
dhcp6: false
ignore-carrier: true
optional: true
addresses:
- {{ pxe_server_ip }}/{{ pxe_prefix }}
{% if corp_nic_mac %}
ge-corp:
match:
macaddress: "{{ corp_nic_mac | lower }}"
set-name: {{ corp_nic_name }}
dhcp4: true
dhcp4-overrides:
route-metric: {{ corp_route_metric }}
{% else %}
{% for id, glob in [('zz-en', 'en*'), ('zz-eth', 'eth*')] %}
{{ id }}:
match:
name: "{{ glob }}"
dhcp4: true
dhcp6: false
optional: true
dhcp4-overrides:
route-metric: {{ corp_route_metric }}
{% endfor %}
{% endif %}