#!/bin/bash # nm-unmanaged.sh — dracut cmdline-stage hook (runs BEFORE NetworkManager starts). # # In the SECURE path (802.1X/MACsec), wpa_supplicant owns the physical NIC: it # runs EAPOL and the MACsec key agreement (MKA) on it, and that link carries the # root filesystem. NetworkManager must NOT touch that NIC — a competing NM # supplicant would drop MKA and kill netroot. So drop an NM config that marks the # physical NIC unmanaged. IP is carried by macsec0, which is left MANAGED so NM # does DHCP *and renews the lease* on it. # # In the TEST path (rd.dot1x=0) there is no macsec0; IP rides on the physical NIC # directly and we WANT NM to manage+renew it — so write nothing. # # Why /run (not /etc) and why the cmdline stage: /run/NetworkManager/conf.d is # read by NM and the tmpfs survives switch-root, so the same rule applies in both # the initramfs and the real root. Writing here at the cmdline stage guarantees # the file exists before NM is started, so NM never transiently manages the EAPOL # NIC. This replaces the old static /etc/.../99-dot1x-unmanaged.conf, which could # not be mode-aware (it wrongly unmanaged en* in test mode too, blocking renewal). type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh # Test mode: leave the boot NIC managed so NM renews its lease. [ "$(getarg rd.dot1x=)" = "0" ] && return 0 _dir=/run/NetworkManager/conf.d _conf="$_dir/10-dot1x-unmanaged.conf" [ -e "$_conf" ] && return 0 # idempotent (cmdline hooks may re-run) mkdir -p "$_dir" # interface-name:en* matches the predictable physical NIC names (eno/ens/enp); # macsec0 is deliberately NOT listed, so NM manages it for DHCP/renewal. { echo "[keyfile]" echo "unmanaged-devices=interface-name:en*" } > "$_conf" info "dot1x-macsec: NM unmanaged drop-in written ($_conf) — physical NIC reserved for wpa_supplicant"