deploy: apply helper gateway from DHCP routers, trap/timeouts, running status, per-stamp files
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KqhjP7nud5gdBg25zCiRk9
This commit is contained in:
+68
-11
@@ -259,7 +259,9 @@ rm -rf "$nmd" "$nme"
|
||||
|
||||
# 18. the detached apply helper: syntax, then behaviour against stubbed system commands in a sandbox
|
||||
h=$(VANLINK_CONF=hosts/van.conf ./deploy.sh --print-apply-helper 2>&1) || { bad "--print-apply-helper failed"; echo "$h" | sed 's/^/ /'; h=; }
|
||||
if [ -n "$h" ]; then
|
||||
if [ -n "$h" ] && [ "$(id -u)" = 0 ]; then
|
||||
bad "check 18 refuses to run the helper sandbox as root"
|
||||
elif [ -n "$h" ]; then
|
||||
echo "$h" | bash -n && ok "apply helper: bash -n" || bad "apply helper: bash -n"
|
||||
echo "$h" | grep -q 'enP2p33s0' && ok "apply helper waits on the Starlink port" || bad "apply helper does not reference STARLINK_IFACE"
|
||||
echo "$h" | grep -q 'systemctl restart van-failover' && ok "apply helper restarts van-failover" || bad "apply helper lacks van-failover restart"
|
||||
@@ -269,6 +271,10 @@ if [ -n "$h" ]; then
|
||||
done
|
||||
cat >> "$stubs/netplan" <<'STUB'
|
||||
if [ "$1" = apply ]; then
|
||||
if [ -e "${STUB_HOLD:-/nonexistent}" ]; then
|
||||
: > "$STUB_SB/applying"
|
||||
while [ -e "$STUB_HOLD" ]; do /bin/sleep 0.05; done
|
||||
fi
|
||||
n=$(cat "$STUB_SB/napply" 2>/dev/null || echo 0); echo $((n + 1)) > "$STUB_SB/napply"
|
||||
[ "${STUB_FAIL_APPLY:-}" = first ] && [ "$n" = 0 ] && exit 1
|
||||
fi
|
||||
@@ -279,11 +285,14 @@ case "$2" in
|
||||
GENERAL.STATE) echo "${NM_STATE-100 (connected)}" ;;
|
||||
GENERAL.CONNECTION) echo "${NM_CONN-netplan-enP2p33s0}" ;;
|
||||
IP4.ADDRESS) echo "${NM_ADDR-192.168.100.10/24}" ;;
|
||||
IP4.GATEWAY) echo "${NM_GW-192.168.100.1}" ;;
|
||||
IP4.GATEWAY) echo "${NM_GW-}" ;;
|
||||
# never-default: IP4.GATEWAY is empty, the router is only in the DHCP lease options (nmcli -g joins them with " | ")
|
||||
DHCP4.OPTION) echo "${NM_DHCP-requested_routers = 1 | routers = 192.168.100.1 | subnet_mask = 255.255.255.0}" ;;
|
||||
esac
|
||||
exit 0
|
||||
STUB
|
||||
cat >> "$stubs/ip" <<'STUB'
|
||||
if [ "$2" = get ]; then echo "1.1.1.1 via 192.168.100.1 dev x"; exit 0; fi
|
||||
case "${STUB_ROUTE:-yes}" in
|
||||
yes) echo "default via 192.168.100.1 dev x" ;;
|
||||
after-rollback) [ -e "$STUB_ND/50-van-wan.yaml" ] || echo "default via 192.168.100.1 dev x" ;;
|
||||
@@ -302,17 +311,23 @@ STUB
|
||||
for c in netplan nmcli ip ping systemctl reboot sleep timeout; do [ -x "$stubs/$c" ] || stubs_ok=0; done
|
||||
[ "$stubs_ok" = 1 ] && ok "apply helper sandbox: all system commands stubbed" || bad "apply helper sandbox: stub missing"
|
||||
|
||||
an_run() { # an_run [VAR=val ...] — fresh sandbox, run the helper; sets sb rc
|
||||
an_prep() { # fresh sandbox + generated helper; sets sb
|
||||
sb=$(mktemp -d); mkdir -p "$sb/netplan" "$sb/sysnet"
|
||||
printf 'armbian\n' > "$sb/netplan/10-dhcp-all-interfaces.yaml"; chmod 0600 "$sb/netplan/10-dhcp-all-interfaces.yaml"
|
||||
printf 'old\n' > "$sb/netplan/60-other.yaml"
|
||||
cp -a "$sb/netplan" "$sb/bak"
|
||||
[ -n "${AN_NOPORT:-}" ] || mkdir "$sb/sysnet/enP2p33s0"
|
||||
[ -z "${AN_NOBAK:-}" ] || rm -rf "$sb/bak"
|
||||
[ -z "${AN_BAKBAD:-}" ] || chmod 000 "$sb/bak/60-other.yaml"
|
||||
printf 'network:\n version: 2\n' > "$sb/rendered.yaml"
|
||||
VANLINK_CONF=hosts/van.conf ./deploy.sh --print-apply-helper --backup "$sb/bak" --netplan-dir "$sb/netplan" \
|
||||
--log "$sb/log" --status "$sb/status" --sysnet "$sb/sysnet" --rendered "$sb/rendered.yaml" \
|
||||
--deadlines "1 1 1" --stamp T > "$sb/helper.sh"
|
||||
# never run a helper that does not point at the sandbox
|
||||
grep -qF "nd='$sb/netplan'" "$sb/helper.sh" && [ "$(id -u)" != 0 ] || { bad "helper does not bake the sandbox netplan dir (or root) - refusing to run"; return 1; }
|
||||
}
|
||||
an_run() { # an_run [VAR=val ...] — fresh sandbox, run the helper; sets sb rc
|
||||
an_prep || { rc=99; return 0; }
|
||||
env PATH="$stubs:/usr/bin:/bin" STUB_CALLS="$sb/calls" STUB_SB="$sb" STUB_ND="$sb/netplan" "$@" bash "$sb/helper.sh" && rc=0 || rc=$?
|
||||
}
|
||||
a_is() { [ "$2" = "$3" ] && ok "apply helper $1" || bad "apply helper $1 (got '$2', want '$3')"; }
|
||||
@@ -320,6 +335,8 @@ STUB
|
||||
# (1) success
|
||||
an_run
|
||||
a_is "success: exit 0" "$rc" 0; a_is "success: status" "$(st_word)" ok
|
||||
grep -q '^ping -c1 -W2 -I enP2p33s0 192.168.100.1$' "$sb/calls" && ok "apply helper success: gateway from the DHCP routers option" || bad "apply helper success: routers option not used"
|
||||
grep -q 'route to 1.1.1.1:' "$sb/log" && ok "apply helper success: route logged" || bad "apply helper success: no route log"
|
||||
grep -q 'apply-network OK' "$sb/log" && ok "apply helper success: log OK" || bad "apply helper success: log"
|
||||
a_is "success: 50-van-wan.yaml mode" "$(stat -c %a "$sb/netplan/50-van-wan.yaml" 2>/dev/null)" 600
|
||||
[ -e "$sb/netplan/10-dhcp-all-interfaces.yaml.vanlink-disabled" ] && [ ! -e "$sb/netplan/10-dhcp-all-interfaces.yaml" ] && ok "apply helper success: Armbian file moved aside" || bad "apply helper success: Armbian file not moved aside"
|
||||
@@ -335,7 +352,11 @@ STUB
|
||||
# (2b) stage 1 fails: wrong connection name; gateway unpingable
|
||||
an_run NM_CONN="Wired connection 1"; a_is "stage1 conn name: status" "$(st_word)" rolled-back; rm -rf "$sb"
|
||||
an_run STUB_GW_PING=1; a_is "stage1 gateway ping: status" "$(st_word)" rolled-back; rm -rf "$sb"
|
||||
an_run NM_GW=; a_is "stage1 no gateway: status" "$(st_word)" rolled-back; rm -rf "$sb"
|
||||
an_run NM_DHCP="requested_routers = 1 | subnet_mask = 255.255.255.0"; a_is "stage1 no IP4.GATEWAY and no routers option: status" "$(st_word)" rolled-back; rm -rf "$sb"
|
||||
an_run NM_DHCP="routers = 192.168.100.1 192.168.100.2"; a_is "routers option with two routers: status" "$(st_word)" ok
|
||||
grep -q '^ping -c1 -W2 -I enP2p33s0 192.168.100.1$' "$sb/calls" && ok "apply helper: pinged the first router" || bad "apply helper: first router not pinged"; rm -rf "$sb"
|
||||
an_run NM_GW=192.168.100.9 NM_DHCP=; a_is "IP4.GATEWAY present: status" "$(st_word)" ok
|
||||
grep -q '^ping -c1 -W2 -I enP2p33s0 192.168.100.9$' "$sb/calls" && ok "apply helper: pinged IP4.GATEWAY" || bad "apply helper: IP4.GATEWAY not pinged"; rm -rf "$sb"
|
||||
# (3) netplan apply fails, port absent
|
||||
AN_NOPORT=1 an_run STUB_FAIL_APPLY=first
|
||||
a_is "apply failure: exit 1" "$rc" 1; a_is "apply failure: not ok" "$(st_word)" rolled-back
|
||||
@@ -343,12 +364,19 @@ STUB
|
||||
rm -rf "$sb"
|
||||
# (3b) port absent and apply ok: stage 1 needs only the apply
|
||||
AN_NOPORT=1 an_run; a_is "port absent, apply ok: status" "$(st_word)" ok; rm -rf "$sb"
|
||||
# (4) rollback cp fails (backup dir missing)
|
||||
AN_NOBAK=1 an_run NM_STATE=bad
|
||||
a_is "rollback cp failure: exit 1" "$rc" 1; a_is "rollback cp failure: status" "$(st_word)" rollback-failed
|
||||
grep -q 'ROLLBACK FAILED' "$sb/log" && ok "apply helper rollback failure: loud log" || bad "apply helper rollback failure: log"
|
||||
grep -q '^reboot\|systemctl reboot' "$sb/calls" && bad "apply helper rollback failure with connectivity: must not reboot" || ok "apply helper rollback failure with connectivity: no reboot"
|
||||
# (4) backup dir missing: undo own changes, report failure, never reboot into the new config
|
||||
AN_NOBAK=1 an_run NM_STATE=bad STUB_ROUTE=none
|
||||
a_is "backup missing: exit 1" "$rc" 1; a_is "backup missing: status" "$(st_word)" rollback-failed
|
||||
grep -q 'ROLLBACK FAILED: BACKUP-MISSING' "$sb/log" && ok "apply helper backup missing: loud log" || bad "apply helper backup missing: log"
|
||||
grep -q '^systemctl reboot' "$sb/calls" && bad "apply helper backup missing: must not reboot" || ok "apply helper backup missing: no reboot"
|
||||
[ ! -e "$sb/netplan/50-van-wan.yaml" ] && [ -e "$sb/netplan/10-dhcp-all-interfaces.yaml" ] && [ ! -e "$sb/netplan/10-dhcp-all-interfaces.yaml.vanlink-disabled" ] && ok "apply helper backup missing: own changes undone" || bad "apply helper backup missing: own changes not undone"
|
||||
rm -rf "$sb"
|
||||
# (4b) rollback cp fails (unreadable backup file) but connectivity is fine: loud, no reboot
|
||||
AN_BAKBAD=1 an_run NM_STATE=bad
|
||||
a_is "cp failure: status" "$(st_word)" rollback-failed
|
||||
grep -q 'ROLLBACK FAILED: CP-FAILED' "$sb/log" && ok "apply helper cp failure: loud log" || bad "apply helper cp failure: log"
|
||||
grep -q '^systemctl reboot' "$sb/calls" && bad "apply helper cp failure with connectivity: must not reboot" || ok "apply helper cp failure with connectivity: no reboot"
|
||||
chmod 600 "$sb/bak/60-other.yaml"; rm -rf "$sb"
|
||||
# (5) stage 2 fails: no default route until rolled back
|
||||
an_run STUB_ROUTE=after-rollback
|
||||
a_is "stage2: exit 1" "$rc" 1; a_is "stage2: status" "$(st_word)" rolled-back
|
||||
@@ -357,17 +385,46 @@ STUB
|
||||
# (6) clean rollback but no connectivity (uplink unplugged): must NOT reboot
|
||||
an_run STUB_ROUTE=none
|
||||
a_is "no connectivity after clean rollback: exit" "$rc" 1
|
||||
a_is "no connectivity after clean rollback: status" "$(cat "$sb/status" 2>/dev/null | cut -d' ' -f1,3)" "rolled-back restored-no-connectivity"
|
||||
a_is "no connectivity after clean rollback: status" "$(cut -d' ' -f1,3 "$sb/status" 2>/dev/null)" "rolled-back restored-no-connectivity;"
|
||||
grep -q 'restored-no-connectivity; stage 2' "$sb/status" && ok "apply helper: original reason kept in status" || bad "apply helper: reason lost from status"
|
||||
grep -q 'restored, but no connectivity' "$sb/log" && ok "apply helper: no-connectivity log" || bad "apply helper: no-connectivity log"
|
||||
grep -q '^systemctl reboot' "$sb/calls" && bad "apply helper: must not reboot after a clean rollback" || ok "apply helper: no reboot after a clean rollback"
|
||||
[ -e "$sb/netplan/10-dhcp-all-interfaces.yaml" ] && [ ! -e "$sb/netplan/50-van-wan.yaml" ] && ok "apply helper: clean rollback restored files" || bad "apply helper: clean rollback files"
|
||||
rm -rf "$sb"
|
||||
# (7) rollback cp fails AND no connectivity: last-resort reboot
|
||||
AN_NOBAK=1 an_run STUB_ROUTE=none
|
||||
AN_BAKBAD=1 an_run STUB_ROUTE=none
|
||||
a_is "failed rollback, no connectivity: status" "$(st_word)" rollback-failed
|
||||
grep -q '^systemctl reboot' "$sb/calls" && ok "apply helper: reboots when the rollback failed and connectivity is absent" || bad "apply helper: no reboot after failed rollback"
|
||||
rm -rf "$sb"
|
||||
chmod 600 "$sb/bak/60-other.yaml" 2>/dev/null || true; rm -rf "$sb"
|
||||
# (8) TERM while a slow netplan apply is running: the trap restores, once
|
||||
an_prep && {
|
||||
: > "$sb/hold"
|
||||
env PATH="$stubs:/usr/bin:/bin" STUB_CALLS="$sb/calls" STUB_SB="$sb" STUB_ND="$sb/netplan" STUB_HOLD="$sb/hold" bash "$sb/helper.sh" &
|
||||
hp=$!
|
||||
for _ in $(seq 1 100); do [ -e "$sb/applying" ] && break; /bin/sleep 0.05; done
|
||||
[ -e "$sb/applying" ] && ok "apply helper TERM: helper reached the slow apply" || bad "apply helper TERM: never reached the apply"
|
||||
a_is "TERM: status while running" "$(cut -d' ' -f1,3 "$sb/status" 2>/dev/null)" "running $sb/bak"
|
||||
kill -TERM "$hp"; rm -f "$sb/hold"
|
||||
wait "$hp" && rc=0 || rc=$?
|
||||
a_is "TERM: exit 1" "$rc" 1
|
||||
a_is "TERM: status" "$(cut -d' ' -f1,3 "$sb/status" 2>/dev/null)" "rolled-back terminated"
|
||||
[ -e "$sb/netplan/10-dhcp-all-interfaces.yaml" ] && [ ! -e "$sb/netplan/50-van-wan.yaml" ] && ok "apply helper TERM: restored" || bad "apply helper TERM: not restored"
|
||||
[ "$(grep -c 'restoring' "$sb/log")" = 1 ] && ok "apply helper TERM: restore ran once" || bad "apply helper TERM: restore count"
|
||||
rm -rf "$sb"
|
||||
}
|
||||
rm -rf "$stubs"
|
||||
fi
|
||||
# 18b. status file gate and STARLINK_IFACE validation
|
||||
gs=$(mktemp -d)
|
||||
for w in running rollback-failed ok rolled-back; do
|
||||
echo "$w 2026-01-01T00:00:00+00:00 x" > "$gs/s"
|
||||
case $w in running|rollback-failed) want=yes ;; *) want=no ;; esac
|
||||
[ "$(./deploy.sh --apply-status-check "$gs/s")" = "$want" ] && ok "apply-status-check: $w -> $want" || bad "apply-status-check: $w should be $want"
|
||||
done
|
||||
[ "$(./deploy.sh --apply-status-check "$gs/none")" = no ] && ok "apply-status-check: no file -> no" || bad "apply-status-check: no file"
|
||||
sed 's/^STARLINK_IFACE=.*/STARLINK_IFACE="a'"'"'b"/' hosts/van.conf > "$gs/bad.conf"
|
||||
if ./deploy.sh --check "$gs/bad.conf" >/dev/null 2>&1; then bad "STARLINK_IFACE with a quote must be rejected"; else ok "STARLINK_IFACE with a quote is rejected"; fi
|
||||
rm -rf "$gs"
|
||||
|
||||
exit "$fail"
|
||||
|
||||
Reference in New Issue
Block a user