H1 corp_admin_cidrs (UFW 22/9009 on the corp NIC from these only; refuses
when it would lock out the current SSH client) and corp_ssh_password_auth
(sshd drop-in, passwords only from the imaging subnet). Defaults keep the
current behaviour and print a loud warning.
H2 pxe-netplan-apply.sh: pre-flight refusal (pxe-MAC NIC holds the default
route, a DHCP lease or the route to an SSH client), run from the USB
before anything changes; post-apply checks (names, imaging IPv4, corp
IPv4 back within 30 s, SSH route) and a 120 s systemd-run auto-revert.
H3 restore() puts the previous NIC names back (two-phase rename via
temporary names, so swaps work) before generate + apply.
H4 a failed rename is fatal (restore + exit 1); the play drops the old
any-interface UFW rules only after verifying both NICs and their
per-interface SSH rules.
H5 first-boot checks the playbook exit code, retries at next boot (max 5),
writes /var/lib/pxe-firstboot.failed and /etc/issue + /etc/motd banners.
M1 build-usb.sh verifies iPXE files against the playbook pins, every wheel
against its own package's hashes, drops the unpinned pip fallback,
requires --site-vars or --no-site-vars.
M2 webapp restarts when the code, wheels or unit changed.
M3 ignore-carrier on the imaging NIC; ip_nonlocal_bind kept for the
apache/networkd boot race.
M4 menu suffix must match blancco_kver; missing kernel with no earlier
build is fatal; a live menu with the staged Blancco test entry is not
overwritten.
M5 build-proxmox-iso.sh is unsupported (hard exit); use build-usb --image.
M6 deploy-unattend.sh: password via SSHPASS env and sudo stdin only,
%q-quoted remote args, sha256 check of the live file; lint-unattend.py
reads PXE_SERVER/PXE_USER/PXE_PASS and uses sshpass -e.
M7 the playbook lints the unattend files before the force: yes copies
(lint-unattend.py ships on CIDATA as scripts/); pre-commit ACMR.
M8 Blancco initramfs build: no ignore_errors, pipefail, trap cleanup.
Also: Send-ImagingStage.ps1 deploy, carrier hook install tasks
(pxe-nic-carrier, networkd-dispatcher), L1 imaging-active hold
(allow_disruptive) and no iPXE downgrade below an Updates-page install,
L2 iso-volid, L3 kver pin read from the playbook, L4 cp -rL, L5 refuse
>= 4 GiB WinPE files, L6 PIP_DIR wipe guard, L7 per-package wheel hashes,
no online fallback, L8 check-updates.py (apt_pkg/cached compare, all
components, exit 2 on no debs / bad answers).
433 lines
15 KiB
Bash
Executable File
433 lines
15 KiB
Bash
Executable File
#!/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
|
|
# pxe-netplan-apply.sh --preflight STAGED_YAML PXE_NAME PXE_MAC CORP_NAME CORP_MAC
|
|
# pxe-netplan-apply.sh --revert BACKUP_DIR (the auto-revert timer)
|
|
#
|
|
# Env (set by pxe_server_setup.yml):
|
|
# PXE_SUBNET imaging LAN, e.g. 172.16.9.0/24 (SSH peers inside it may
|
|
# legitimately come in on the imaging NIC)
|
|
# PXE_SSH_CLIENT IP of the admin's SSH client ($SSH_CONNECTION field 1);
|
|
# every established sshd peer is checked as well
|
|
# PXE_NETPLAN_REVERT_AFTER seconds before the auto-revert fires (120)
|
|
# PXE_NETPLAN_CORP_WAIT seconds to wait for the corp IPv4 again (30)
|
|
#
|
|
# Steps (apply):
|
|
# 1. validate STAGED_YAML in a scratch root (netplan generate --root-dir);
|
|
# nothing on the box changes if it is invalid
|
|
# 2. stop here (UNCHANGED) if it is already installed, no other file
|
|
# configures wired NICs, and both NICs carry their stable names
|
|
# 3. pre-flight: REFUSE (exit 1, nothing changed) when the NIC that
|
|
# PXE_MAC names holds the default route, a DHCP lease, or the route to
|
|
# an SSH client outside PXE_SUBNET. That NIC becomes static with no
|
|
# gateway, so any of these means it is the corp-cabled one (swapped
|
|
# MACs) and applying would cut the admin off.
|
|
# 4. back up /etc/netplan to /var/backups/netplan-<timestamp>/, record the
|
|
# current NIC names, and arm an auto-revert (systemd-run timer) that
|
|
# restores the backup unless step 8 cancels it
|
|
# 5. install it (other wired configs moved aside), netplan generate
|
|
# 6. rename the NICs to their stable names (the .link files netplan
|
|
# writes do it on every later boot)
|
|
# 7. netplan apply
|
|
# 8. check: both names present, the imaging NIC has its IPv4, the corp
|
|
# NIC has IPv4 again if it had one before (PXE_NETPLAN_CORP_WAIT s),
|
|
# SSH clients still route out of the corp side; then cancel the revert
|
|
# Any failure in 5-8 restores the backup AND the previous NIC names, then
|
|
# exits 1. Prints CHANGED / UNCHANGED (or NEEDED for --preflight) as the
|
|
# last line.
|
|
|
|
set -u
|
|
|
|
NETPLAN_DIR=${NETPLAN_DIR:-/etc/netplan}
|
|
SYSFS_NET=${SYSFS_NET:-/sys/class/net}
|
|
BACKUP_ROOT=${BACKUP_ROOT:-/var/backups}
|
|
LEASE_DIR=${LEASE_DIR:-/run/systemd/netif/leases}
|
|
LOCK_FILE=${LOCK_FILE:-/run/pxe-netplan-apply.lock}
|
|
REVERT_AFTER=${PXE_NETPLAN_REVERT_AFTER:-120}
|
|
CORP_WAIT=${PXE_NETPLAN_CORP_WAIT:-30}
|
|
PXE_WAIT=${PXE_NETPLAN_PXE_WAIT:-20}
|
|
TARGET=$NETPLAN_DIR/50-cloud-init.yaml
|
|
SELF=$(readlink -f "$0")
|
|
|
|
log() { echo "pxe-netplan-apply: $*"; }
|
|
|
|
# --- NIC helpers -------------------------------------------------------------
|
|
|
|
# Name of the physical NIC that has MAC $1 (empty if none).
|
|
iface_of() {
|
|
local d
|
|
for d in "$SYSFS_NET"/*; do
|
|
[ -e "$d/device" ] || continue
|
|
if [ "$(cat "$d/address" 2>/dev/null)" = "$1" ]; then
|
|
basename "$d"
|
|
return 0
|
|
fi
|
|
done
|
|
return 0
|
|
}
|
|
|
|
has_v4() {
|
|
ip -4 -o addr show dev "$1" scope global 2>/dev/null | grep -q ' inet '
|
|
}
|
|
|
|
default_route_devs() {
|
|
{ ip -4 route show default; ip -6 route show default; } 2>/dev/null |
|
|
awk '{for (i = 1; i < NF; i++) if ($i == "dev") print $(i + 1)}' | sort -u
|
|
}
|
|
|
|
# DHCP-assigned address (networkd and NetworkManager both flag those
|
|
# "dynamic"), or a networkd lease file for the link.
|
|
has_dhcp_lease() {
|
|
local idx
|
|
ip -o addr show dev "$1" 2>/dev/null | grep -qw dynamic && return 0
|
|
idx=$(cat "$SYSFS_NET/$1/ifindex" 2>/dev/null)
|
|
[ -n "$idx" ] && [ -s "$LEASE_DIR/$idx" ] && return 0
|
|
return 1
|
|
}
|
|
|
|
route_dev() {
|
|
ip -o route get "$1" 2>/dev/null |
|
|
awk '{for (i = 1; i < NF; i++) if ($i == "dev") {print $(i + 1); exit}}'
|
|
}
|
|
|
|
ip2int() {
|
|
local a b c d
|
|
[[ "$1" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$ ]] || return 1
|
|
a=${BASH_REMATCH[1]} b=${BASH_REMATCH[2]} c=${BASH_REMATCH[3]} d=${BASH_REMATCH[4]}
|
|
echo $(( (a << 24) | (b << 16) | (c << 8) | d ))
|
|
}
|
|
|
|
in_subnet() {
|
|
local ipn netn len mask
|
|
[ -n "${2:-}" ] || return 1
|
|
ipn=$(ip2int "$1") || return 1
|
|
netn=$(ip2int "${2%/*}") || return 1
|
|
len=${2#*/}
|
|
[[ "$len" =~ ^[0-9]+$ ]] && [ "$len" -le 32 ] || return 1
|
|
mask=$(( len == 0 ? 0 : (0xFFFFFFFF << (32 - len)) & 0xFFFFFFFF ))
|
|
[ $(( ipn & mask )) -eq $(( netn & mask )) ]
|
|
}
|
|
|
|
# Admin SSH clients: PXE_SSH_CLIENT, our own SSH_CONNECTION, and every
|
|
# established peer of sshd (port 22). IPv4-mapped IPv6 is unwrapped.
|
|
ssh_peers() {
|
|
{
|
|
[ -n "${PXE_SSH_CLIENT:-}" ] && echo "$PXE_SSH_CLIENT"
|
|
[ -n "${SSH_CONNECTION:-}" ] && echo "${SSH_CONNECTION%% *}"
|
|
ss -Htn state established '( sport = :22 )' 2>/dev/null | awk '{print $NF}' |
|
|
sed -E 's/:[0-9]+$//; s/^\[//; s/\]$//'
|
|
} | sed -E 's/^::ffff://' | grep -E '^[0-9a-fA-F.:]+$' | sort -u
|
|
}
|
|
|
|
# --- pre-flight --------------------------------------------------------------
|
|
|
|
# 0 = safe to apply. Prints every reason it is not.
|
|
preflight() {
|
|
local pif cif peer dev bad=0
|
|
pif=$(iface_of "$PXE_MAC")
|
|
cif=$(iface_of "$CORP_MAC")
|
|
if [ -z "$pif" ]; then
|
|
log "REFUSED: no physical NIC has pxe_nic_mac $PXE_MAC"
|
|
return 1
|
|
fi
|
|
if [ -z "$cif" ]; then
|
|
log "REFUSED: no physical NIC has corp_nic_mac $CORP_MAC"
|
|
return 1
|
|
fi
|
|
if default_route_devs | grep -qxF "$pif"; then
|
|
log "REFUSED: $pif (pxe_nic_mac $PXE_MAC) holds the default route"
|
|
bad=1
|
|
fi
|
|
if has_dhcp_lease "$pif"; then
|
|
log "REFUSED: $pif (pxe_nic_mac $PXE_MAC) has a DHCP lease"
|
|
bad=1
|
|
fi
|
|
while IFS= read -r peer; do
|
|
[ -n "$peer" ] || continue
|
|
dev=$(route_dev "$peer")
|
|
[ "$dev" = "$pif" ] || continue
|
|
if in_subnet "$peer" "${PXE_SUBNET:-}"; then
|
|
continue # an admin on the imaging LAN keeps 172.16.9.1
|
|
fi
|
|
log "REFUSED: the SSH client $peer is reached through $pif (pxe_nic_mac $PXE_MAC)"
|
|
bad=1
|
|
done < <(ssh_peers)
|
|
if [ "$bad" -ne 0 ]; then
|
|
log "The NIC named by pxe_nic_mac looks like the GE corporate one: pxe_nic_mac"
|
|
log "and corp_nic_mac are probably swapped, or the cables are. It would become"
|
|
log "static ${PXE_SUBNET:-imaging} with no gateway and cut this box off. Nothing was changed."
|
|
log "NICs now: $(for d in "$SYSFS_NET"/*; do [ -e "$d/device" ] && printf '%s=%s ' "$(basename "$d")" "$(cat "$d/address" 2>/dev/null)"; done)"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# --- renaming ----------------------------------------------------------------
|
|
|
|
link_rename() {
|
|
local cur=$1 new=$2
|
|
log "renaming $cur to $new"
|
|
if ip link set dev "$cur" down &&
|
|
ip link set dev "$cur" name "$new" &&
|
|
ip link set dev "$new" up; then
|
|
return 0
|
|
fi
|
|
log "ERROR: rename $cur -> $new failed"
|
|
ip link set dev "$cur" up 2>/dev/null
|
|
ip link set dev "$new" up 2>/dev/null
|
|
return 1
|
|
}
|
|
|
|
# set_names MAC1 NAME1 MAC2 NAME2: give each MAC its name. A NIC that must
|
|
# change goes through a temporary name first, so a swap (pxe0 <-> corp0)
|
|
# works in both directions. Temporary names carry this run's PID, so they
|
|
# never collide with a name left over from an earlier (failed) pass.
|
|
set_names() {
|
|
local -a macs=("$1" "$3") want=("$2" "$4") cur=()
|
|
local i
|
|
RENAME_SEQ=$(( ${RENAME_SEQ:-0} + 1 ))
|
|
for i in 0 1; do
|
|
[ -n "${want[i]}" ] || return 0
|
|
cur[i]=$(iface_of "${macs[i]}")
|
|
if [ -z "${cur[i]}" ]; then
|
|
log "ERROR: no NIC with MAC ${macs[i]}"
|
|
return 1
|
|
fi
|
|
done
|
|
for i in 0 1; do
|
|
[ "${cur[i]}" = "${want[i]}" ] && continue
|
|
link_rename "${cur[i]}" "pxt$$-$RENAME_SEQ$i" || return 1
|
|
cur[i]=pxt$$-$RENAME_SEQ$i
|
|
done
|
|
for i in 0 1; do
|
|
[ "${cur[i]}" = "${want[i]}" ] && continue
|
|
link_rename "${cur[i]}" "${want[i]}" || return 1
|
|
done
|
|
for i in 0 1; do
|
|
if [ "$(iface_of "${macs[i]}")" != "${want[i]}" ]; then
|
|
log "ERROR: MAC ${macs[i]} is not named ${want[i]} after renaming"
|
|
return 1
|
|
fi
|
|
done
|
|
return 0
|
|
}
|
|
|
|
names_ok() {
|
|
[ "$(iface_of "$PXE_MAC")" = "$PXE_NAME" ] &&
|
|
[ "$(iface_of "$CORP_MAC")" = "$CORP_NAME" ]
|
|
}
|
|
|
|
# --- backup / restore --------------------------------------------------------
|
|
|
|
# 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
|
|
}
|
|
|
|
# State of one apply, next to (not inside) the backup, so restoring the
|
|
# backup never copies it into /etc/netplan.
|
|
write_state() {
|
|
{
|
|
echo "NETPLAN_DIR=$NETPLAN_DIR"
|
|
echo "PXE_MAC=$PXE_MAC"
|
|
echo "ORIG_PXE_IF=$ORIG_PXE_IF"
|
|
echo "CORP_MAC=$CORP_MAC"
|
|
echo "ORIG_CORP_IF=$ORIG_CORP_IF"
|
|
} >"$BACKUP.state"
|
|
}
|
|
|
|
read_state() {
|
|
local k v
|
|
while IFS='=' read -r k v; do
|
|
case "$k" in
|
|
NETPLAN_DIR) [[ "$v" =~ ^/[A-Za-z0-9/._-]*$ ]] && NETPLAN_DIR=$v ;;
|
|
PXE_MAC|CORP_MAC) [[ "$v" =~ ^([0-9a-f]{2}:){5}[0-9a-f]{2}$ ]] && printf -v "$k" '%s' "$v" ;;
|
|
ORIG_PXE_IF|ORIG_CORP_IF) [[ "$v" =~ ^[A-Za-z0-9._-]{0,15}$ ]] && printf -v "$k" '%s' "$v" ;;
|
|
esac
|
|
done <"$BACKUP.state"
|
|
TARGET=$NETPLAN_DIR/50-cloud-init.yaml
|
|
}
|
|
|
|
cancel_revert() {
|
|
[ -n "${REVERT_UNIT:-}" ] || return 0
|
|
systemctl stop "$REVERT_UNIT.timer" "$REVERT_UNIT.service" >/dev/null 2>&1
|
|
if systemctl is-active --quiet "$REVERT_UNIT.timer" 2>/dev/null; then
|
|
log "WARNING: could not cancel $REVERT_UNIT.timer (the committed marker still stops it)"
|
|
fi
|
|
}
|
|
|
|
# Put the backed-up config AND the previous NIC names back.
|
|
restore() {
|
|
local rc=0
|
|
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/"
|
|
if [ -n "${ORIG_PXE_IF:-}" ] && [ -n "${ORIG_CORP_IF:-}" ]; then
|
|
set_names "$PXE_MAC" "$ORIG_PXE_IF" "$CORP_MAC" "$ORIG_CORP_IF" ||
|
|
{ log "ERROR: could not put the previous NIC names back"; rc=1; }
|
|
fi
|
|
netplan generate && netplan apply || { log "ERROR: netplan apply of the restored config failed"; rc=1; }
|
|
touch "$BACKUP.reverted"
|
|
return $rc
|
|
}
|
|
|
|
fail() {
|
|
log "ERROR: $*"
|
|
restore
|
|
cancel_revert
|
|
log "the previous config is back in place; nothing applied"
|
|
exit 1
|
|
}
|
|
|
|
# --- modes -------------------------------------------------------------------
|
|
|
|
MODE=apply
|
|
case "${1:-}" in
|
|
--preflight) MODE=preflight; shift ;;
|
|
--revert)
|
|
[ $# -eq 2 ] || { echo "usage: $0 --revert BACKUP_DIR" >&2; exit 2; }
|
|
BACKUP=${2%/}
|
|
[ -d "$BACKUP" ] && [ -f "$BACKUP.state" ] || { log "revert: no backup/state at $BACKUP"; exit 1; }
|
|
exec 9>"$LOCK_FILE"
|
|
flock -w 600 9 || log "revert: lock still held after 600 s; reverting anyway"
|
|
if [ -e "$BACKUP.committed" ] || [ -e "$BACKUP.reverted" ]; then
|
|
log "revert: $BACKUP already committed or reverted; nothing to do"
|
|
exit 0
|
|
fi
|
|
PXE_MAC="" CORP_MAC="" ORIG_PXE_IF="" ORIG_CORP_IF=""
|
|
read_state
|
|
log "revert: the apply was not confirmed within its window"
|
|
restore
|
|
exit $?
|
|
;;
|
|
esac
|
|
|
|
if [ $# -ne 5 ]; then
|
|
echo "usage: $0 [--preflight] 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,,}
|
|
|
|
# 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. pre-flight
|
|
if ! preflight; then
|
|
exit 1
|
|
fi
|
|
if [ "$MODE" = preflight ]; then
|
|
log "pre-flight passed; the netplan config would change"
|
|
echo NEEDED
|
|
exit 0
|
|
fi
|
|
|
|
exec 9>"$LOCK_FILE"
|
|
flock -w 60 9 || { log "ERROR: another pxe-netplan-apply holds $LOCK_FILE; nothing changed"; exit 1; }
|
|
|
|
# 4. back up, record names, arm the auto-revert
|
|
ORIG_PXE_IF=$(iface_of "$PXE_MAC")
|
|
ORIG_CORP_IF=$(iface_of "$CORP_MAC")
|
|
CORP_HAD_V4=0
|
|
has_v4 "$ORIG_CORP_IF" && CORP_HAD_V4=1
|
|
BACKUP=$BACKUP_ROOT/netplan-$(date +%Y%m%d-%H%M%S)
|
|
mkdir -p "$BACKUP"
|
|
cp -a "$NETPLAN_DIR/." "$BACKUP/"
|
|
write_state
|
|
log "previous config saved in $BACKUP (NICs: $ORIG_PXE_IF, $ORIG_CORP_IF)"
|
|
|
|
REVERT_UNIT=""
|
|
if [ "$REVERT_AFTER" -gt 0 ] 2>/dev/null && command -v systemd-run >/dev/null; then
|
|
REVERT_UNIT=pxe-netplan-revert-$(basename "$BACKUP" | tr -cd 'A-Za-z0-9-')
|
|
if systemd-run --quiet --unit="$REVERT_UNIT" --on-active="$REVERT_AFTER" \
|
|
--timer-property=AccuracySec=1s "$SELF" --revert "$BACKUP"; then
|
|
log "auto-revert armed: $REVERT_UNIT.timer restores $BACKUP in ${REVERT_AFTER}s unless this run confirms"
|
|
else
|
|
log "WARNING: could not arm the auto-revert timer; relying on the in-script checks"
|
|
REVERT_UNIT=""
|
|
fi
|
|
else
|
|
log "WARNING: auto-revert off (no systemd-run or PXE_NETPLAN_REVERT_AFTER=0)"
|
|
fi
|
|
|
|
# An SSH drop (HUP) must not stop us half-way; TERM/INT restore.
|
|
trap '' HUP
|
|
trap 'fail "interrupted"' INT TERM
|
|
|
|
# 5. install, moving aside the other wired configs (an en* DHCP match would
|
|
# put a DHCP client on the imaging NIC too)
|
|
while IFS= read -r f; do
|
|
[ -n "$f" ] || continue
|
|
log "moving aside $f (copy in $BACKUP)"
|
|
rm -f "$f"
|
|
done < <(other_files)
|
|
install -m 600 "$STAGED" "$TARGET"
|
|
netplan generate || fail "netplan generate failed"
|
|
|
|
# 6. stable names now, so dnsmasq/UFW/Samba (which use them) work before
|
|
# the next reboot. Does not depend on the netplan version renaming.
|
|
set_names "$PXE_MAC" "$PXE_NAME" "$CORP_MAC" "$CORP_NAME" || fail "NIC rename failed"
|
|
|
|
# 7. apply
|
|
netplan apply || fail "netplan apply failed"
|
|
|
|
# 8. check
|
|
names_ok || fail "$PXE_NAME / $CORP_NAME are not on MACs $PXE_MAC / $CORP_MAC after apply"
|
|
ok=0
|
|
for _ in $(seq 1 "$PXE_WAIT"); do
|
|
if has_v4 "$PXE_NAME"; then ok=1; break; fi
|
|
sleep 1
|
|
done
|
|
[ "$ok" -eq 1 ] || fail "$PXE_NAME has no IPv4 address after ${PXE_WAIT}s"
|
|
if [ "$CORP_HAD_V4" -eq 1 ]; then
|
|
ok=0
|
|
for _ in $(seq 1 "$CORP_WAIT"); do
|
|
if has_v4 "$CORP_NAME"; then ok=1; break; fi
|
|
sleep 1
|
|
done
|
|
[ "$ok" -eq 1 ] || fail "$CORP_NAME had IPv4 before and has none after ${CORP_WAIT}s"
|
|
elif ! has_v4 "$CORP_NAME"; then
|
|
log "WARNING: $CORP_NAME has no IPv4 address (it had none before either: corp cable unplugged or no DHCP)"
|
|
fi
|
|
while IFS= read -r peer; do
|
|
[ -n "$peer" ] || continue
|
|
dev=$(route_dev "$peer")
|
|
if [ "$dev" = "$PXE_NAME" ] && ! in_subnet "$peer" "${PXE_SUBNET:-}"; then
|
|
fail "the SSH client $peer now routes through $PXE_NAME"
|
|
fi
|
|
if [ -z "$dev" ]; then
|
|
fail "no route to the SSH client $peer after apply"
|
|
fi
|
|
done < <(ssh_peers)
|
|
|
|
touch "$BACKUP.committed"
|
|
cancel_revert
|
|
trap - INT TERM
|
|
ip -br addr show dev "$PXE_NAME" 2>/dev/null
|
|
ip -br addr show dev "$CORP_NAME" 2>/dev/null
|
|
echo CHANGED
|
|
exit 0
|