#!/bin/bash # pxe-netplan-apply.sh - install the two-NIC netplan config without ever # leaving the box unreachable on the corporate NIC. # # Usage: pxe-netplan-apply.sh STAGED_YAML PXE_NAME PXE_MAC CORP_NAME CORP_MAC # # Called by pxe_server_setup.yml. Steps: # 1. validate STAGED_YAML in a scratch root (netplan generate --root-dir); # nothing on the box changes if it is invalid # 2. stop here (prints UNCHANGED) if it is already installed, no other # file configures wired NICs, and both NICs carry their stable names # 3. back up /etc/netplan to /var/backups/netplan-/ # 4. install it (other wired configs moved aside), netplan generate # (restore on failure) # 5. rename the NICs to their stable names (the .link files netplan # writes do it on every later boot), netplan apply (restore on failure) # 6. check the imaging address and the corp NIC (warning only: the corp # cable may simply be unplugged) # Prints CHANGED or UNCHANGED as the last line. Exit 1 = nothing applied # (the previous config is back in place). set -u if [ $# -ne 5 ]; then echo "usage: $0 STAGED_YAML PXE_NAME PXE_MAC CORP_NAME CORP_MAC" >&2 exit 2 fi STAGED=$1 PXE_NAME=$2 PXE_MAC=${3,,} CORP_NAME=$4 CORP_MAC=${5,,} NETPLAN_DIR=${NETPLAN_DIR:-/etc/netplan} TARGET=$NETPLAN_DIR/50-cloud-init.yaml log() { echo "pxe-netplan-apply: $*"; } # Name of the physical NIC that has MAC $1 (empty if none). iface_of() { local d for d in /sys/class/net/*; do [ -e "$d/device" ] || continue if [ "$(cat "$d/address" 2>/dev/null)" = "$1" ]; then basename "$d" return 0 fi done return 0 } names_ok() { [ "$(iface_of "$PXE_MAC")" = "$PXE_NAME" ] && [ "$(iface_of "$CORP_MAC")" = "$CORP_NAME" ] } # Other netplan files that configure wired NICs (installer leftovers, an # en* DHCP match): they would put a second config on the two NICs. Files # without "ethernets" (WiFi, NetworkManager wifi profiles) are left alone. other_files() { find "$NETPLAN_DIR" -maxdepth 1 -type f \( -name '*.yaml' -o -name '*.yml' \) \ ! -path "$TARGET" -exec grep -l 'ethernets' {} + 2>/dev/null | sort } # 1. validate in a scratch root SCRATCH=$(mktemp -d) trap 'rm -rf "$SCRATCH"' EXIT mkdir -p "$SCRATCH/etc/netplan" install -m 600 "$STAGED" "$SCRATCH/etc/netplan/50-cloud-init.yaml" if ! netplan generate --root-dir "$SCRATCH"; then log "ERROR: $STAGED does not validate; nothing changed" exit 1 fi # 2. already in place? if cmp -s "$STAGED" "$TARGET" && [ -z "$(other_files)" ] && names_ok; then log "netplan already in place, $PXE_NAME and $CORP_NAME present" echo UNCHANGED exit 0 fi # 3. back up BACKUP=/var/backups/netplan-$(date +%Y%m%d-%H%M%S) mkdir -p "$BACKUP" cp -a "$NETPLAN_DIR/." "$BACKUP/" log "previous config saved in $BACKUP" restore() { log "restoring the previous config from $BACKUP" find "$NETPLAN_DIR" -maxdepth 1 -type f \( -name '*.yaml' -o -name '*.yml' \) -delete cp -a "$BACKUP/." "$NETPLAN_DIR/" netplan generate && netplan apply } # 4. install, moving aside the other wired configs (an en* DHCP match would # put a DHCP client on the imaging NIC too) other_files | while IFS= read -r f; do log "moving aside $f (copy in $BACKUP)" rm -f "$f" done install -m 600 "$STAGED" "$TARGET" if ! netplan generate; then log "ERROR: netplan generate failed" restore exit 1 fi # 5. stable names now, so dnsmasq/UFW/Samba (which use them) work before # the next reboot. Does not depend on the netplan version renaming. rename() { local mac=$1 want=$2 cur cur=$(iface_of "$mac") if [ -z "$cur" ]; then log "WARNING: no NIC with MAC $mac" return 0 fi [ "$cur" = "$want" ] && return 0 log "renaming $cur ($mac) to $want" if ! { ip link set dev "$cur" down && ip link set dev "$cur" name "$want" && ip link set dev "$want" up; }; then log "WARNING: rename $cur -> $want failed; it applies at the next reboot" ip link set dev "$cur" up 2>/dev/null fi return 0 } rename "$PXE_MAC" "$PXE_NAME" rename "$CORP_MAC" "$CORP_NAME" if ! netplan apply; then log "ERROR: netplan apply failed" restore exit 1 fi # 6. check for _ in $(seq 1 20); do ip -4 -br addr show dev "$PXE_NAME" 2>/dev/null | grep -q ' [0-9]' && break sleep 1 done ip -br addr show dev "$PXE_NAME" 2>/dev/null || log "WARNING: $PXE_NAME not present yet (reboot to apply the .link names)" for _ in $(seq 1 30); do ip -4 -br addr show dev "$CORP_NAME" 2>/dev/null | grep -q ' [0-9]' && break sleep 1 done ip -4 -br addr show dev "$CORP_NAME" 2>/dev/null | grep -q ' [0-9]' || log "WARNING: $CORP_NAME has no IPv4 address yet (corp cable unplugged or DHCP slow)" echo CHANGED exit 0