apply-network: final-review fixes (diagnostics, NM off the uplink until handover, preflight, stricter stage 2)
I-1 helper logs diagnostics (nmcli, DHCP routers, networkctl -n 0, ip addr, ip route; each
under timeout 5) before a stage 1/2 restore, and the port's networkctl list line on success
(information only, never gating).
I-2 ap/nm-uplink-pending.conf -> /etc/NetworkManager/conf.d/zz-van-wan-pending.conf keeps NM
off STARLINK_IFACE while the WAN netplan is missing; installed before network-manager,
removed once the netplan matches, left as is when it differs (port already handed to NM).
apply_network backs it up as nm-pending.conf; the helper removes it + reloads NM conf right
before netplan generate/apply, restore() puts it back and reloads after the re-apply.
--wan-plan prints "uplink NM pending conf: ...".
I-3 nm_wan_state_netplan: "unreadable" (0600 file, non-root); --wan-plan and post-check say so
without warning.
I-4 apply_preflight_problems (+ hidden --apply-preflight, VANLINK_HOSTNAME test knob ignored as
root): port absent / conf of another host -> refused without --force. Stage 2 also needs
van-failover's own "proto static" default route on the port.
M-1 helper restarts van-br0-setup (if the unit exists) after the forward netplan apply.
M-3 nm_pkg_installed (dpkg-query Status) in deploy, --wan-plan and --apply-network.
M-5 header documents VANLINK_CONF; stale test comment fixed.
M-6 cellular_profile_exists ignores VANLINK_NM_DIR as root.
M-7 tests: NM-off files installed before network-manager; read-only sub-commands never apt-get.
M-8 an existing, differing WAN netplan is shown (redact_secrets now masks YAML password:) and
refused without --force.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KqhjP7nud5gdBg25zCiRk9
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# Installed by ./deploy.sh as /etc/NetworkManager/conf.d/zz-van-wan-pending.conf only while the
|
||||
# WAN netplan (/etc/netplan/50-van-wan.yaml) is missing, i.e. before the uplink port has been
|
||||
# handed to NetworkManager: until then systemd-networkd owns the port (DHCP lease, default route)
|
||||
# and NM must leave it alone, or it shows up as "connected (externally)" and van-failover starts
|
||||
# steering it. ./deploy.sh --apply-network removes this file right before it applies the netplan
|
||||
# (and a rollback puts it back); a routine deploy removes it once the netplan matches.
|
||||
# "+=" appends to the unmanaged list of van-ap-unmanaged.conf; the zz- name sorts after it.
|
||||
[keyfile]
|
||||
unmanaged-devices+=interface-name:@STARLINK_IFACE@
|
||||
@@ -14,16 +14,23 @@
|
||||
# ./deploy.sh --apply-network [--force] # root: a detached helper installs the WAN netplan, applies it, gates on uplink health (2 stages) and rolls back
|
||||
# # automatically on failure; status in /var/lib/vanlink/apply-network.status. No-op when already applied, and refused after a
|
||||
# # running/rollback-failed status, unless --force.
|
||||
# # Refused (unless --force) when the preflight finds a problem, or when an existing
|
||||
# # /etc/netplan/50-van-wan.yaml differs from the rendered one (the diff is shown, secrets redacted).
|
||||
# ./deploy.sh --apply-preflight [conf] # problems --apply-network would refuse on, one per line (none: no output, exit 0):
|
||||
# # STARLINK_IFACE absent in sysfs; /etc/vanlink.conf -> hosts/<name>.conf with <name> != `hostname -s`
|
||||
# ./deploy.sh --apply-status-check <file> # test seam: yes|no, would this status file (running|rollback-failed) block --apply-network
|
||||
# ./deploy.sh --print-apply-helper [conf] # print the detached apply/rollback helper script (read-only, no root; for review and tests)
|
||||
# # test-only flags: --backup --netplan-dir --log --status --sysnet --rendered --deadlines "s1 s2 rc" --stamp
|
||||
# # test-only flags: --backup --netplan-dir --log --status --sysnet --rendered --nm-conf-dir --deadlines "s1 s2 rc" --stamp
|
||||
# ./deploy.sh render <file> # preview a templated file on stdout
|
||||
# ./deploy.sh --redact # filter stdin, masking secret values (used by --diff; for tests)
|
||||
# VANLINK_SYSNET=<dir> # test override: sysfs net dir used by --post-check (default /sys/class/net)
|
||||
# VANLINK_CONF=<file> # host config path (default /etc/vanlink.conf); sub-commands taking [conf] use that argument instead
|
||||
# VANLINK_SYSNET=<dir> # test override: sysfs net dir used by --post-check and --apply-preflight (default /sys/class/net)
|
||||
# VANLINK_HOSTNAME=<name> # test override: host name used by --apply-preflight (default `hostname -s`; ignored when run as root)
|
||||
# VANLINK_FAILOVER_STATE=<file> # test override: van-failover state file read by --post-check (default /run/van-failover/state.json)
|
||||
# VANLINK_APPLY_STATUS=<file> # test override: apply-network status file read by --post-check (default /var/lib/vanlink/apply-network.status)
|
||||
# VANLINK_WAN_NETPLAN=<file> # test override: installed WAN netplan compared by nm_wan_state_netplan (default /etc/netplan/50-van-wan.yaml)
|
||||
# VANLINK_NM_DIR=<dir> # test override: where existing NM *.nmconnection files are looked up (default /etc/NetworkManager/system-connections)
|
||||
# VANLINK_NM_DIR=<dir> # test override: where existing NM *.nmconnection files are looked up (default /etc/NetworkManager/system-connections;
|
||||
# # ignored when run as root)
|
||||
# Idempotent. Netplan (the NM-managed WANs; eth0 is a LAN port on br0) is never
|
||||
# changed by a routine deploy (applying it flaps the uplinks). ENABLE_NM_WAN=0:
|
||||
# applied manually from the reference copy in ap/50-van-wan.yaml, and checked for
|
||||
@@ -106,15 +113,37 @@ render_wan_netplan() { # ap/50-van-wan.yaml, plus the wifis stanza when the hos
|
||||
fi
|
||||
}
|
||||
|
||||
nm_wan_state_netplan() { # missing | differs | matches (the installed WAN netplan vs the rendered one)
|
||||
nm_wan_state_netplan() { # missing | unreadable | differs | matches (the installed WAN netplan vs the rendered one)
|
||||
local f=${VANLINK_WAN_NETPLAN:-/etc/netplan/50-van-wan.yaml}
|
||||
if [ ! -f "$f" ]; then echo missing
|
||||
elif [ ! -r "$f" ]; then echo unreadable # installed 0600 by --apply-network; only root can compare it
|
||||
elif diff -q <(render_wan_netplan) "$f" >/dev/null 2>&1; then echo matches
|
||||
else echo differs; fi
|
||||
}
|
||||
|
||||
nm_pkg_installed() { # true if the network-manager package is installed (dpkg's Status, not just known to dpkg)
|
||||
local s
|
||||
s=$(dpkg-query -W -f='${Status}' network-manager 2>/dev/null) || return 1
|
||||
[ "$s" = "install ok installed" ]
|
||||
}
|
||||
|
||||
# Keeps NetworkManager off the uplink port until --apply-network hands it over (ap/nm-uplink-pending.conf).
|
||||
NM_PENDING_CONF=/etc/NetworkManager/conf.d/zz-van-wan-pending.conf
|
||||
# nm_pending_action <netplan-state> <pending-conf> -> would install | installed | would remove | absent
|
||||
# The routine deploy installs the pending conf only while the WAN netplan is missing (the uplink was never
|
||||
# handed to NM) and removes it once it matches. differs/unreadable: the port was already handed to NM by an
|
||||
# earlier netplan, so the file is left as it is (installing it would take a live uplink away from NM).
|
||||
nm_pending_action() {
|
||||
case "$1" in
|
||||
missing) if [ -e "$2" ]; then echo installed; else echo "would install"; fi ;;
|
||||
matches) if [ -e "$2" ]; then echo "would remove"; else echo absent; fi ;;
|
||||
*) if [ -e "$2" ]; then echo installed; else echo absent; fi ;;
|
||||
esac
|
||||
}
|
||||
|
||||
cellular_profile_exists() { # true if an NM profile with id=$CELLULAR_CONN_NAME exists (any file name, or known to nmcli)
|
||||
local d=${VANLINK_NM_DIR:-/etc/NetworkManager/system-connections} f names
|
||||
local d=/etc/NetworkManager/system-connections f names
|
||||
[ "$(id -u)" = 0 ] || d=${VANLINK_NM_DIR:-$d} # the test knob is ignored when run as root
|
||||
for f in "$d"/*.nmconnection; do
|
||||
[ -f "$f" ] || continue
|
||||
if grep -Fx "id=${CELLULAR_CONN_NAME}" "$f" >/dev/null 2>&1; then return 0; fi
|
||||
@@ -146,7 +175,20 @@ install_rendered() { # install_rendered <src> <dst> [mode]
|
||||
redact_secrets() { # stdin -> stdout; mask values of secret-looking keys, keep key + diff markers
|
||||
sed -E \
|
||||
-e 's/((pass(word|phrase)|secret|token)[A-Za-z_]*[[:space:]]*=).*/\1<redacted>/I' \
|
||||
-e 's/("[A-Za-z_]*(pass(word|phrase)|secret|token|user)[A-Za-z_]*"[[:space:]]*:[[:space:]]*)"[^"]*"/\1"<redacted>"/I'
|
||||
-e 's/("[A-Za-z_]*(pass(word|phrase)|secret|token|user)[A-Za-z_]*"[[:space:]]*:[[:space:]]*)"[^"]*"/\1"<redacted>"/I' \
|
||||
-e 's/^([-+ ]?[[:space:]]*(pass(word|phrase)|secret|token)[A-Za-z_]*:[[:space:]]*)[^[:space:]].*/\1<redacted>/I'
|
||||
}
|
||||
|
||||
# apply_preflight_problems <conf> <sysnet-dir> <hostname> — one line per reason --apply-network should not run
|
||||
# here (nothing when fine): the uplink port does not exist, or the conf is a hosts/<name>.conf of another host
|
||||
# (checked only when the conf resolves into a hosts/ directory).
|
||||
apply_preflight_problems() {
|
||||
local conf=$1 sn=$2 hn=$3 real
|
||||
[ -e "$sn/$STARLINK_IFACE" ] || echo "uplink port STARLINK_IFACE=$STARLINK_IFACE does not exist ($sn/$STARLINK_IFACE)"
|
||||
real=$(readlink -f "$conf")
|
||||
if [ "$(basename "$(dirname "$real")")" = hosts ] && [ "$(basename "$real" .conf)" != "$hn" ]; then
|
||||
echo "$conf -> $real is not this host's config (hostname $hn)"
|
||||
fi
|
||||
}
|
||||
|
||||
template_files() { # every repo file that carries an @TOKEN@ placeholder
|
||||
@@ -336,7 +378,11 @@ PY
|
||||
else warn "post-deploy: van-failover state $fst missing or unreadable"; fi
|
||||
# k. netplan drift
|
||||
f=$(nm_wan_state_netplan)
|
||||
[ "$f" = matches ] && echo " ok WAN netplan matches" || warn "post-deploy: WAN netplan $f — run ./deploy.sh --apply-network"
|
||||
case "$f" in
|
||||
matches) echo " ok WAN netplan matches" ;;
|
||||
unreadable) echo " ok WAN netplan: cannot read as non-root (run with sudo to verify)" ;;
|
||||
*) warn "post-deploy: WAN netplan $f — run ./deploy.sh --apply-network" ;;
|
||||
esac
|
||||
# l. last --apply-network
|
||||
as_f=${VANLINK_APPLY_STATUS:-/var/lib/vanlink/apply-network.status}
|
||||
if [ -r "$as_f" ]; then
|
||||
@@ -353,8 +399,9 @@ PY
|
||||
|
||||
# --apply-network time budgets (seconds), in one place; all derived from the AN_* constants.
|
||||
# (netplan generate/apply run under timeout -k AN_KILL_T: +AN_KILL_T each in the worst case)
|
||||
# forward path AN_FWD = generate + apply + 2*kill + stage 1 + restart + stage 2
|
||||
# rollback path AN_RB = generate + apply + 2*kill + restart + connectivity recheck
|
||||
# forward path AN_FWD = NM conf reload + generate + apply + 2*kill + van-br0-setup (unit check + restart)
|
||||
# + stage 1 + restart + stage 2 + networkd info line + diagnostics (logged once, on a failed gate)
|
||||
# rollback path AN_RB = generate + apply + 2*kill + NM conf reload + restart + connectivity recheck
|
||||
# helper worst case AN_HELPER_MAX = AN_FWD + AN_RB + AN_SLOP (loop iterations overshoot their
|
||||
# deadline by a few nmcli/ping timeouts)
|
||||
# foreground waits AN_WAIT = AN_HELPER_MAX + 30
|
||||
@@ -364,8 +411,9 @@ PY
|
||||
# route in its first loop, so a healthy default route shows up within seconds of a restart; the
|
||||
# 120 s stage 2 deadline is a wide margin for a slow Starlink/cellular probe.
|
||||
AN_KILL_T=10 AN_APPLY_T=90 AN_GEN_T=30 AN_NM_T=5 AN_RESTART_T=30 AN_S1=60 AN_S2=120 AN_RC=60 AN_SLOP=60
|
||||
AN_FWD=$(( AN_GEN_T + AN_APPLY_T + 2 * AN_KILL_T + AN_S1 + AN_RESTART_T + AN_S2 ))
|
||||
AN_RB=$(( AN_GEN_T + AN_APPLY_T + 2 * AN_KILL_T + AN_RESTART_T + AN_RC ))
|
||||
AN_DIAG=$(( 5 * AN_NM_T )) # diag(): five commands, each under timeout AN_NM_T
|
||||
AN_FWD=$(( AN_NM_T + AN_GEN_T + AN_APPLY_T + 2 * AN_KILL_T + AN_NM_T + AN_RESTART_T + AN_S1 + AN_RESTART_T + AN_S2 + AN_NM_T + AN_DIAG ))
|
||||
AN_RB=$(( AN_GEN_T + AN_APPLY_T + 2 * AN_KILL_T + AN_NM_T + AN_RESTART_T + AN_RC ))
|
||||
AN_HELPER_MAX=$(( AN_FWD + AN_RB + AN_SLOP ))
|
||||
AN_WAIT=$(( AN_HELPER_MAX + 30 ))
|
||||
AN_RUNMAX=$(( AN_HELPER_MAX + 60 ))
|
||||
@@ -378,36 +426,61 @@ apply_status_blocks_run() { # apply_status_blocks_run <status-file> -> yes|no:
|
||||
esac
|
||||
}
|
||||
|
||||
# apply_helper_script <backup-dir> [netplan-dir] [log] [status-file] [sysnet-dir] [rendered-file] [deadlines "s1 s2 rc"] [run-stamp]
|
||||
# apply_helper_script <backup-dir> [netplan-dir] [log] [status-file] [sysnet-dir] [rendered-file] [deadlines "s1 s2 rc"] [run-stamp] [nm-conf-dir]
|
||||
# The script the detached unit runs. Paths are parameters only so tests can bake
|
||||
# sandbox paths into the text; the production helper reads no environment.
|
||||
apply_helper_script() {
|
||||
local bak=$1 nd=${2:-/etc/netplan} log=${3:-/var/log/vanlink-apply-network.log}
|
||||
local st=${4:-/var/lib/vanlink/apply-network.status} sn=${5:-/sys/class/net}
|
||||
local rf=${6:-/run/vanlink-50-van-wan.yaml} dl=${7:-"$AN_S1 $AN_S2 $AN_RC"} stamp=${8:-manual}
|
||||
local ncd=${9:-$(dirname "$NM_PENDING_CONF")} pc=${NM_PENDING_CONF##*/}
|
||||
local d1 d2 d3
|
||||
read -r d1 d2 d3 <<< "$dl"
|
||||
# Unquoted heredoc on purpose: values expand now; \$... stays for run time.
|
||||
cat <<HELPER
|
||||
#!/bin/bash
|
||||
# Generated by deploy.sh --apply-network; runs detached. Installs the rendered WAN netplan,
|
||||
# applies it, verifies the uplink in two stages and restores the pre-run netplan on any failure
|
||||
# (including TERM/INT/HUP, e.g. systemd stopping the unit at its RuntimeMaxSec).
|
||||
# removes the NM conf that kept NM off the uplink ($pc), applies, verifies the uplink in two
|
||||
# stages and restores the pre-run netplan (and $pc) on any failure (including TERM/INT/HUP,
|
||||
# e.g. systemd stopping the unit at its RuntimeMaxSec).
|
||||
# Stage 1 (${d1}s): the apply succeeded and, if the Starlink port exists, NM shows it connected
|
||||
# via a netplan-* profile with an IPv4 address whose gateway (IP4.GATEWAY, else the DHCP
|
||||
# routers option: never-default hides IP4.GATEWAY) answers on-link.
|
||||
# Stage 2 (${d2}s): van-failover restarted; default route and internet ping.
|
||||
# Stage 2 (${d2}s): van-failover restarted; default route and internet ping, and (port present)
|
||||
# van-failover's own default route on the port (proto static; the NM profile installs none).
|
||||
# A failed gate logs diagnostics (NM, DHCP routers, networkd, addresses, routes) before restoring.
|
||||
bak='$bak'
|
||||
nd='$nd'
|
||||
log='$log'
|
||||
st='$st'
|
||||
sn='$sn'
|
||||
rf='$rf'
|
||||
ncd='$ncd'
|
||||
exec >>"\$log" 2>&1
|
||||
trap 'rm -f "\$0"' EXIT
|
||||
status() { mkdir -p "\$(dirname "\$st")"; echo "\$1 \$(date -Is)\${2:+ \$2}" > "\$st"; }
|
||||
echo "== apply-network $stamp \$(date -Is) (backup: \$bak)"
|
||||
conn_ok() { ip route show default | grep . >/dev/null && ping -c1 -W2 1.1.1.1 >/dev/null 2>&1; }
|
||||
gate2() {
|
||||
conn_ok || return 1
|
||||
[ -e "\$sn/$STARLINK_IFACE" ] || return 0
|
||||
ip route show default dev '$STARLINK_IFACE' proto static | grep . >/dev/null
|
||||
}
|
||||
diag() { # diag <stage>: uplink state for the log; informational, every command bounded and failure-tolerant
|
||||
echo "--- diagnostics: \$1"
|
||||
echo "--- nmcli device show $STARLINK_IFACE"
|
||||
timeout $AN_NM_T nmcli -g GENERAL.STATE,GENERAL.CONNECTION,IP4.ADDRESS,IP4.GATEWAY device show '$STARLINK_IFACE' 2>&1
|
||||
echo "--- DHCP4 routers"
|
||||
timeout $AN_NM_T nmcli -g DHCP4.OPTION device show '$STARLINK_IFACE' 2>&1 | tr '|' '\\n' | grep -E '^ *routers ='
|
||||
echo "--- networkctl status $STARLINK_IFACE"
|
||||
timeout $AN_NM_T networkctl --no-pager -n 0 status '$STARLINK_IFACE' 2>&1 | head -8
|
||||
echo "--- ip -4 addr show $STARLINK_IFACE"
|
||||
timeout $AN_NM_T ip -4 addr show '$STARLINK_IFACE' 2>&1
|
||||
echo "--- ip route"
|
||||
timeout $AN_NM_T ip route 2>&1
|
||||
echo "--- end diagnostics"
|
||||
return 0
|
||||
}
|
||||
gate1() {
|
||||
[ -e "\$sn/$STARLINK_IFACE" ] || return 0
|
||||
[ "\$(timeout $AN_NM_T nmcli -g GENERAL.STATE device show '$STARLINK_IFACE' 2>/dev/null)" = "100 (connected)" ] || return 1
|
||||
@@ -433,6 +506,9 @@ restore() { # restore <reason>: put the pre-run netplan back, re-check connecti
|
||||
if [ -d "\$bak" ]; then
|
||||
rm -f "\$nd"/* "\$nd"/.[!.]*
|
||||
cp -a "\$bak"/. "\$nd"/ || rb=CP-FAILED
|
||||
rm -f "\$nd/nm-pending.conf" # the backed-up NM conf is not a netplan file
|
||||
# the NM conf that kept NM off the uplink goes back too; NM re-reads it after the re-apply below
|
||||
[ ! -f "\$bak/nm-pending.conf" ] || cp -a "\$bak/nm-pending.conf" "\$ncd/$pc" || echo "WARNING: could not restore \$ncd/$pc"
|
||||
else
|
||||
# No backup to restore from: at least undo this helper's own two changes.
|
||||
rb=BACKUP-MISSING
|
||||
@@ -440,6 +516,7 @@ restore() { # restore <reason>: put the pre-run netplan back, re-check connecti
|
||||
[ ! -f "\$nd/10-dhcp-all-interfaces.yaml.vanlink-disabled" ] || mv "\$nd/10-dhcp-all-interfaces.yaml.vanlink-disabled" "\$nd/10-dhcp-all-interfaces.yaml"
|
||||
fi
|
||||
timeout -k $AN_KILL_T $AN_GEN_T netplan generate && timeout -k $AN_KILL_T $AN_APPLY_T netplan apply || { [ "\$rb" != ok ] || rb=APPLY-FAILED; }
|
||||
[ ! -f "\$ncd/$pc" ] || timeout $AN_NM_T nmcli general reload conf || echo "nmcli general reload conf failed"
|
||||
timeout $AN_RESTART_T systemctl restart van-failover || true
|
||||
end=\$((SECONDS+$d3))
|
||||
while :; do
|
||||
@@ -464,11 +541,18 @@ restore() { # restore <reason>: put the pre-run netplan back, re-check connecti
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
pending_off() { # hand the uplink to NM: drop the NM conf that kept NM off it, right before the apply
|
||||
[ -e "\$ncd/$pc" ] || return 0
|
||||
rm -f "\$ncd/$pc" || return 1
|
||||
timeout $AN_NM_T nmcli general reload conf || echo "nmcli general reload conf failed (continuing; stage 1 decides)"
|
||||
return 0
|
||||
}
|
||||
status running "\$bak"
|
||||
trap 'restore terminated' TERM INT HUP
|
||||
applied=0
|
||||
if install -m0600 "\$rf" "\$nd/50-van-wan.yaml" \\
|
||||
&& { [ ! -f "\$nd/10-dhcp-all-interfaces.yaml" ] || mv "\$nd/10-dhcp-all-interfaces.yaml" "\$nd/10-dhcp-all-interfaces.yaml.vanlink-disabled"; } \\
|
||||
&& pending_off \\
|
||||
&& timeout -k $AN_KILL_T $AN_GEN_T netplan generate && timeout -k $AN_KILL_T $AN_APPLY_T netplan apply; then
|
||||
applied=1
|
||||
else
|
||||
@@ -476,6 +560,10 @@ else
|
||||
fi
|
||||
s1=0
|
||||
if [ "\$applied" = 1 ]; then
|
||||
# netplan apply restarts networkd, which can drop br0's members: re-run the bridge setup (not gating)
|
||||
if timeout $AN_NM_T systemctl cat van-br0-setup >/dev/null 2>&1; then
|
||||
timeout $AN_RESTART_T systemctl restart van-br0-setup || echo "van-br0-setup restart failed (continuing)"
|
||||
fi
|
||||
end=\$((SECONDS+$d1))
|
||||
while :; do
|
||||
gate1 && { s1=1; break; }
|
||||
@@ -483,17 +571,19 @@ if [ "\$applied" = 1 ]; then
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
[ "\$s1" = 1 ] || restore "stage 1: apply or uplink gate failed"
|
||||
[ "\$s1" = 1 ] || { diag "stage 1"; restore "stage 1: apply or uplink gate failed"; }
|
||||
timeout $AN_RESTART_T systemctl restart van-failover || restore "van-failover restart failed"
|
||||
s2=0
|
||||
end=\$((SECONDS+$d2))
|
||||
while :; do
|
||||
conn_ok && { s2=1; break; }
|
||||
gate2 && { s2=1; break; }
|
||||
[ "\$SECONDS" -ge "\$end" ] && break
|
||||
sleep 1
|
||||
done
|
||||
route_log
|
||||
[ "\$s2" = 1 ] || restore "stage 2: no default route or internet"
|
||||
[ "\$s2" = 1 ] || { diag "stage 2"; restore "stage 2: no default route, no van-failover route on the port, or no internet"; }
|
||||
nwl=\$(timeout $AN_NM_T networkctl --no-pager --no-legend list 2>/dev/null | awk -v i='$STARLINK_IFACE' '\$2 == i { print; exit }')
|
||||
echo "networkd (info, not gating): \${nwl:-$STARLINK_IFACE not listed}"
|
||||
rm -f "\$rf"
|
||||
status ok
|
||||
trap - TERM INT HUP
|
||||
@@ -507,8 +597,7 @@ apply_network() { # ./deploy.sh --apply-network [--force] — explicit, root; c
|
||||
[ "${1:-}" = --force ] && force=1
|
||||
[ "$(id -u)" = 0 ] || die "--apply-network needs root (sudo)"
|
||||
[ "$ENABLE_NM_WAN" = 1 ] || die "ENABLE_NM_WAN is 0 in $VANLINK_CONF — nothing to apply"
|
||||
dpkg-query -W -f='${Status}' network-manager 2>/dev/null | grep -q 'install ok installed' \
|
||||
|| die "network-manager is not installed — run ./deploy.sh first"
|
||||
nm_pkg_installed || die "network-manager is not installed — run ./deploy.sh first"
|
||||
systemctl is-active --quiet NetworkManager || die "NetworkManager is not running — handing it the uplink port would flap it"
|
||||
systemctl cat van-failover >/dev/null 2>&1 || die "van-failover.service does not exist — run ./deploy.sh first (stage 2 of the gate needs it)"
|
||||
systemctl is-active --quiet vanlink-apply-network && die "an apply is already running — see /var/log/vanlink-apply-network.log"
|
||||
@@ -516,10 +605,23 @@ apply_network() { # ./deploy.sh --apply-network [--force] — explicit, root; c
|
||||
if [ "$force" = 0 ] && [ "$(apply_status_blocks_run "$st")" = yes ]; then
|
||||
die "the last apply did not finish cleanly: $(cat "$st" 2>/dev/null) — inspect $st and $log, check the backup dir named there, then re-run with --force"
|
||||
fi
|
||||
if [ "$force" = 0 ] && [ "$(VANLINK_WAN_NETPLAN='' nm_wan_state_netplan)" = matches ] && [ ! -e /etc/netplan/10-dhcp-all-interfaces.yaml ]; then
|
||||
local np_state problems
|
||||
np_state=$(VANLINK_WAN_NETPLAN='' nm_wan_state_netplan)
|
||||
if [ "$force" = 0 ] && [ "$np_state" = matches ] && [ ! -e /etc/netplan/10-dhcp-all-interfaces.yaml ]; then
|
||||
echo "nothing to apply (WAN netplan already matches; use --force to re-apply)"
|
||||
return 0
|
||||
fi
|
||||
problems=$(apply_preflight_problems "$VANLINK_CONF" /sys/class/net "$(hostname -s)")
|
||||
if [ -n "$problems" ] && [ "$force" = 0 ]; then
|
||||
die "refusing to apply:
|
||||
$problems
|
||||
(fix it, or re-run with --force)"
|
||||
fi
|
||||
if [ "$force" = 0 ] && { [ "$np_state" = differs ] || [ "$np_state" = unreadable ]; }; then
|
||||
echo "/etc/netplan/50-van-wan.yaml exists and differs from the rendered template (secrets redacted):"
|
||||
diff -u /etc/netplan/50-van-wan.yaml <(render_wan_netplan) | redact_secrets | sed 's/^/ /' || true
|
||||
die "refusing to replace an existing, different WAN netplan without --force (review the diff above)"
|
||||
fi
|
||||
local stamp helper rendered bak pt
|
||||
stamp=$(date +%Y%m%d-%H%M%S)-$$
|
||||
rendered=/run/vanlink-50-van-wan-$stamp.yaml
|
||||
@@ -528,6 +630,10 @@ apply_network() { # ./deploy.sh --apply-network [--force] — explicit, root; c
|
||||
check_rendered ap/50-van-wan.yaml "$rendered" || { rm -f "$rendered"; exit 1; }
|
||||
bak=$(mktemp -d "/var/backups/vanlink-netplan-$stamp.XXXX") || { rm -f "$rendered"; die "cannot create backup dir"; }
|
||||
cp -a /etc/netplan/. "$bak/" || { rm -f "$rendered"; die "backup failed — nothing changed"; }
|
||||
# the NM conf keeping NM off the uplink: the helper removes it right before the apply, a rollback restores it from here
|
||||
if [ -e "$NM_PENDING_CONF" ]; then
|
||||
cp -a "$NM_PENDING_CONF" "$bak/nm-pending.conf" || { rm -f "$rendered"; die "backup of $NM_PENDING_CONF failed — nothing changed"; }
|
||||
fi
|
||||
if [ ! -e /var/backups/vanlink-netplan-orig ] && [ ! -e /etc/netplan/50-van-wan.yaml ]; then
|
||||
pt=/var/backups/vanlink-netplan-orig.tmp.$$
|
||||
rm -rf "$pt"
|
||||
@@ -537,7 +643,7 @@ apply_network() { # ./deploy.sh --apply-network [--force] — explicit, root; c
|
||||
fi
|
||||
install -d -m0755 /var/lib/vanlink
|
||||
rm -f "$st"
|
||||
apply_helper_script "$bak" /etc/netplan "$log" "$st" /sys/class/net "$rendered" "$AN_S1 $AN_S2 $AN_RC" "$stamp" > "$helper"
|
||||
apply_helper_script "$bak" /etc/netplan "$log" "$st" /sys/class/net "$rendered" "$AN_S1 $AN_S2 $AN_RC" "$stamp" "$(dirname "$NM_PENDING_CONF")" > "$helper"
|
||||
chmod 0700 "$helper"
|
||||
echo "backup: $bak log: $log"
|
||||
echo "applying from a detached unit (survives an SSH drop); waiting up to $AN_WAIT s ..."
|
||||
@@ -612,11 +718,11 @@ case "${1:-}" in
|
||||
--apply-status-check) # test seam: --apply-status-check <status-file> prints yes|no (does that status block a new --apply-network run?)
|
||||
apply_status_blocks_run "${2:?status file}"
|
||||
exit 0 ;;
|
||||
--print-apply-helper) # [conf] the helper script for review; test-only flags: --backup D --netplan-dir D --log L --status S --sysnet N --rendered R --deadlines "s1 s2 rc" --stamp T
|
||||
shift; pc= a_bak=/var/backups/vanlink-netplan-EXAMPLE a_nd= a_log= a_st= a_sn= a_rf= a_dl= a_stamp=
|
||||
--print-apply-helper) # [conf] the helper script for review; test-only flags: --backup D --netplan-dir D --log L --status S --sysnet N --rendered R --nm-conf-dir D --deadlines "s1 s2 rc" --stamp T
|
||||
shift; pc= a_bak=/var/backups/vanlink-netplan-EXAMPLE a_nd= a_log= a_st= a_sn= a_rf= a_dl= a_stamp= a_ncd=
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--backup|--netplan-dir|--log|--status|--sysnet|--rendered|--deadlines|--stamp)
|
||||
--backup|--netplan-dir|--log|--status|--sysnet|--rendered|--nm-conf-dir|--deadlines|--stamp)
|
||||
[ $# -ge 2 ] || die "$1 needs a value" ;;
|
||||
esac
|
||||
case "$1" in
|
||||
@@ -626,22 +732,34 @@ case "${1:-}" in
|
||||
--status) a_st=$2; shift 2 ;;
|
||||
--sysnet) a_sn=$2; shift 2 ;;
|
||||
--rendered) a_rf=$2; shift 2 ;;
|
||||
--nm-conf-dir) a_ncd=$2; shift 2 ;;
|
||||
--deadlines) a_dl=$2; shift 2 ;;
|
||||
--stamp) a_stamp=$2; shift 2 ;;
|
||||
*) pc=$1; shift ;;
|
||||
esac
|
||||
done
|
||||
load_conf "${pc:-$VANLINK_CONF}"
|
||||
apply_helper_script "$a_bak" "$a_nd" "$a_log" "$a_st" "$a_sn" "$a_rf" "$a_dl" "$a_stamp"
|
||||
apply_helper_script "$a_bak" "$a_nd" "$a_log" "$a_st" "$a_sn" "$a_rf" "$a_dl" "$a_stamp" "$a_ncd"
|
||||
exit 0 ;;
|
||||
--apply-preflight) # ./deploy.sh --apply-preflight [conf] — the problems --apply-network would refuse on; exit 1 if any
|
||||
c=${2:-$VANLINK_CONF}
|
||||
load_conf "$c"
|
||||
hn=$(hostname -s)
|
||||
[ "$(id -u)" = 0 ] || hn=${VANLINK_HOSTNAME:-$hn} # test knob, ignored as root
|
||||
out=$(apply_preflight_problems "$c" "${VANLINK_SYSNET:-/sys/class/net}" "$hn")
|
||||
[ -z "$out" ] && exit 0
|
||||
echo "$out"
|
||||
exit 1 ;;
|
||||
--render-wan-netplan) load_conf "$VANLINK_CONF"; render_wan_netplan; exit 0 ;;
|
||||
--wan-plan) # ./deploy.sh --wan-plan [conf] — what the NM WAN provisioning would do; changes nothing
|
||||
load_conf "${2:-$VANLINK_CONF}"
|
||||
echo "wan-plan: ENABLE_NM_WAN=$ENABLE_NM_WAN"
|
||||
if [ "$ENABLE_NM_WAN" = 1 ]; then
|
||||
if [ -n "${WIFI_WAN_IFACE:-}" ]; then echo " wifi WAN: $WIFI_WAN_IFACE"; else echo " wifi WAN: none (wifis stanza omitted)"; fi
|
||||
if dpkg -s network-manager >/dev/null 2>&1; then echo " packages: network-manager installed"; else echo " packages: network-manager would install"; fi
|
||||
echo " netplan: /etc/netplan/50-van-wan.yaml $(nm_wan_state_netplan)"
|
||||
if nm_pkg_installed; then echo " packages: network-manager installed"; else echo " packages: network-manager would install"; fi
|
||||
np=$(nm_wan_state_netplan)
|
||||
if [ "$np" = unreadable ]; then echo " netplan: /etc/netplan/50-van-wan.yaml unreadable (run with sudo)"; else echo " netplan: /etc/netplan/50-van-wan.yaml $np"; fi
|
||||
echo " uplink NM pending conf: $(nm_pending_action "$np" "$NM_PENDING_CONF")"
|
||||
if [ -f /etc/netplan/10-dhcp-all-interfaces.yaml ]; then echo " armbian: /etc/netplan/10-dhcp-all-interfaces.yaml present -> would move aside"; else echo " armbian: /etc/netplan/10-dhcp-all-interfaces.yaml absent"; fi
|
||||
if [ -z "${CELLULAR_APN:-}" ]; then
|
||||
echo " cellular profile: skipped (no CELLULAR_APN)"
|
||||
@@ -975,7 +1093,16 @@ if [ "$ENABLE_NM_WAN" = 1 ]; then
|
||||
echo "== networkmanager WANs =="
|
||||
# In place before the package starts NM: no auto-created "Wired connection" profiles.
|
||||
install -D -m0644 ap/nm-no-auto-default.conf /etc/NetworkManager/conf.d/00-van-no-auto-default.conf
|
||||
if ! dpkg -s network-manager >/dev/null 2>&1; then
|
||||
# Also in place before the package starts NM: NM keeps off the uplink port until --apply-network hands it
|
||||
# over (WAN netplan missing), and the conf goes once the netplan matches; see nm_pending_action. NM picks
|
||||
# up either change at the `nmcli general reload conf` below.
|
||||
nm_np_state=$(nm_wan_state_netplan)
|
||||
if [ "$nm_np_state" = missing ]; then
|
||||
install_rendered ap/nm-uplink-pending.conf "$NM_PENDING_CONF"
|
||||
elif [ "$nm_np_state" = matches ]; then
|
||||
rm -f "$NM_PENDING_CONF"
|
||||
fi
|
||||
if ! nm_pkg_installed; then
|
||||
# Installed only now: the package starts NM, which manages Wi-Fi/gsm devices by
|
||||
# default, so van-ap-unmanaged.conf (AP radios, LAN ports) must already be in place.
|
||||
apt-get update -qq || true
|
||||
@@ -1003,7 +1130,6 @@ if [ "$ENABLE_NM_WAN" = 1 ]; then
|
||||
echo " created cellular connection ${CELLULAR_CONN_NAME} (apn ${CELLULAR_APN}) — no modem needed"
|
||||
fi
|
||||
fi
|
||||
nm_np_state=$(nm_wan_state_netplan)
|
||||
if [ "$nm_np_state" != matches ]; then
|
||||
warn "WAN netplan is $nm_np_state (not provisioned/up to date): run ./deploy.sh --apply-network (backs up /etc/netplan and rolls back automatically if the uplink does not come back)"
|
||||
fi
|
||||
|
||||
+155
-11
@@ -35,8 +35,8 @@ fi
|
||||
|
||||
# 2. every file carrying an @TOKEN@ is installed through install_rendered
|
||||
# (plain `install` would ship the placeholder unrendered — the 2026-09-23 failure)
|
||||
# vanrouter.js: installed by the loop in the cockpit block; 50-van-wan.yaml: reference copy,
|
||||
# rendered by hand via `deploy.sh render` (deploy.sh only warns when it differs)
|
||||
# vanrouter.js: installed by the loop in the cockpit block; 50-van-wan.yaml: installed only by
|
||||
# the detached helper of `./deploy.sh --apply-network` (a routine deploy only warns when it differs)
|
||||
# 50-van-wan-wifi.yaml, cellular.nmconnection: installed by dedicated code, not `install_rendered <src> /abs/dst` literal lines
|
||||
exempt=" cockpit/vanrouter/vanrouter.js ap/50-van-wan.yaml ap/50-van-wan-wifi.yaml ap/cellular.nmconnection "
|
||||
for f in $(./deploy.sh --templates); do
|
||||
@@ -266,9 +266,16 @@ elif [ -n "$h" ]; then
|
||||
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"
|
||||
stubs=$(mktemp -d)
|
||||
for c in netplan nmcli ip ping systemctl reboot; do
|
||||
for c in netplan nmcli ip ping systemctl reboot networkctl; do
|
||||
printf '#!/bin/bash\necho "%s $*" >> "$STUB_CALLS"\n' "$c" > "$stubs/$c"
|
||||
done
|
||||
cat >> "$stubs/networkctl" <<'STUB'
|
||||
case " $* " in
|
||||
*" list "*) echo " 3 enP2p33s0 ether routable configured" ;;
|
||||
*" status "*) echo "STUB-NETWORKCTL-STATUS" ;;
|
||||
esac
|
||||
exit 0
|
||||
STUB
|
||||
cat >> "$stubs/netplan" <<'STUB'
|
||||
if [ "$1" = apply ]; then
|
||||
if [ -e "${STUB_HOLD:-/nonexistent}" ]; then
|
||||
@@ -288,14 +295,25 @@ case "$2" in
|
||||
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}" ;;
|
||||
GENERAL.STATE,GENERAL.CONNECTION,IP4.ADDRESS,IP4.GATEWAY) echo "STUB-DIAG-NM" ;;
|
||||
esac
|
||||
exit 0
|
||||
STUB
|
||||
# STUB_ROUTE: yes | after-rollback | none; STUB_ROUTE_PROTO: static (van-failover's own route, default) | dhcp (a leftover networkd route)
|
||||
cat >> "$stubs/ip" <<'STUB'
|
||||
if [ "$2" = get ]; then echo "1.1.1.1 via 192.168.100.1 dev x"; exit 0; fi
|
||||
case "$*" in
|
||||
"route get "*) echo "1.1.1.1 via 192.168.100.1 dev x"; exit 0 ;;
|
||||
"-4 addr show "*) echo "STUB-ADDR inet 192.168.100.10/24"; exit 0 ;;
|
||||
esac
|
||||
r=
|
||||
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" ;;
|
||||
yes) r=1 ;;
|
||||
after-rollback) [ -e "$STUB_ND/50-van-wan.yaml" ] || r=1 ;;
|
||||
esac
|
||||
[ -n "$r" ] || exit 0
|
||||
case "$*" in
|
||||
*"proto static"*) [ "${STUB_ROUTE_PROTO:-static}" = static ] && echo "default via 192.168.100.1 dev enP2p33s0 proto static metric 200" ;;
|
||||
*) echo "default via 192.168.100.1 dev enP2p33s0 proto ${STUB_ROUTE_PROTO:-static} metric 200" ;;
|
||||
esac
|
||||
exit 0
|
||||
STUB
|
||||
@@ -305,26 +323,32 @@ exit "${STUB_NET_PING:-0}"
|
||||
STUB
|
||||
printf '#!/bin/bash\nexec /bin/sleep 0.05\n' > "$stubs/sleep"
|
||||
printf '#!/bin/bash\n[ "$1" = -k ] && shift 2\nshift\nexec "$@"\n' > "$stubs/timeout"
|
||||
printf '#!/bin/bash\nexit 0\n' >> "$stubs/systemctl"
|
||||
printf '#!/bin/bash\n[ "$1" = cat ] && [ -n "${STUB_NO_BR0:-}" ] && exit 1\nexit 0\n' >> "$stubs/systemctl"
|
||||
chmod +x "$stubs"/*
|
||||
stubs_ok=1
|
||||
for c in netplan nmcli ip ping systemctl reboot sleep timeout; do [ -x "$stubs/$c" ] || stubs_ok=0; done
|
||||
for c in netplan nmcli ip ping systemctl reboot networkctl 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_prep() { # fresh sandbox + generated helper; sets sb
|
||||
sb=$(mktemp -d); mkdir -p "$sb/netplan" "$sb/sysnet"
|
||||
sb=$(mktemp -d); mkdir -p "$sb/netplan" "$sb/sysnet" "$sb/nmconf"
|
||||
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"
|
||||
# the uplink-pending NM conf, and its copy in the backup (as apply_network makes it)
|
||||
if [ -z "${AN_NOPEND:-}" ]; then
|
||||
printf '[keyfile]\nunmanaged-devices+=interface-name:enP2p33s0\n' > "$sb/nmconf/zz-van-wan-pending.conf"
|
||||
cp -a "$sb/nmconf/zz-van-wan-pending.conf" "$sb/bak/nm-pending.conf"
|
||||
fi
|
||||
[ -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"
|
||||
--nm-conf-dir "$sb/nmconf" --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; }
|
||||
grep -qF "nd='$sb/netplan'" "$sb/helper.sh" && grep -qF "ncd='$sb/nmconf'" "$sb/helper.sh" && [ "$(id -u)" != 0 ] \
|
||||
|| { bad "helper does not bake the sandbox netplan/NM conf dirs (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; }
|
||||
@@ -342,6 +366,23 @@ STUB
|
||||
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"
|
||||
grep -q '^systemctl restart van-failover' "$sb/calls" && ok "apply helper success: van-failover restarted" || bad "apply helper success: no van-failover restart"
|
||||
[ ! -e "$sb/nmconf/zz-van-wan-pending.conf" ] && ok "apply helper success: uplink-pending NM conf removed" || bad "apply helper success: uplink-pending NM conf left in place"
|
||||
[ ! -e "$sb/netplan/nm-pending.conf" ] && ok "apply helper success: no nm-pending.conf in the netplan dir" || bad "apply helper success: nm-pending.conf leaked into the netplan dir"
|
||||
# order in the call log: NM conf reload -> netplan generate -> netplan apply -> van-br0-setup restart -> van-failover restart
|
||||
ln_of() { grep -n -m1 -- "$1" "$sb/calls" 2>/dev/null | cut -d: -f1 || true; }
|
||||
l_rl=$(ln_of '^nmcli general reload conf$'); l_gen=$(ln_of '^netplan generate'); l_ap=$(ln_of '^netplan apply')
|
||||
l_br=$(ln_of '^systemctl restart van-br0-setup$'); l_fo=$(ln_of '^systemctl restart van-failover')
|
||||
[ -n "$l_rl" ] && [ -n "$l_gen" ] && [ "$l_rl" -lt "$l_gen" ] && ok "apply helper success: NM conf reloaded before netplan generate" || bad "apply helper success: NM conf reload missing or after netplan generate (reload '$l_rl', generate '$l_gen')"
|
||||
[ -n "$l_br" ] && [ -n "$l_ap" ] && [ -n "$l_fo" ] && [ "$l_ap" -lt "$l_br" ] && [ "$l_br" -lt "$l_fo" ] && ok "apply helper success: van-br0-setup restarted after netplan apply, before stage 2" || bad "apply helper success: van-br0-setup restart missing or misplaced (apply '$l_ap', br0 '$l_br', failover '$l_fo')"
|
||||
[ "$(grep -c '^systemctl restart van-br0-setup$' "$sb/calls")" = 1 ] && ok "apply helper success: van-br0-setup restarted once" || bad "apply helper success: van-br0-setup restart count"
|
||||
grep -q '^networkd (info, not gating): *3 enP2p33s0 ether routable configured' "$sb/log" && ok "apply helper success: networkd state of the port logged" || bad "apply helper success: networkd info line missing"
|
||||
grep -q -- '--- diagnostics' "$sb/log" && bad "apply helper success: diagnostics must only be logged on a failed gate" || ok "apply helper success: no failure diagnostics"
|
||||
rm -rf "$sb"
|
||||
# (1b) van-br0-setup unit absent: not restarted, still ok; no pending conf: nothing to reload
|
||||
AN_NOPEND=1 an_run STUB_NO_BR0=1
|
||||
a_is "no van-br0-setup unit: status" "$(st_word)" ok
|
||||
grep -q '^systemctl restart van-br0-setup' "$sb/calls" && bad "apply helper: restarted a van-br0-setup that does not exist" || ok "apply helper: van-br0-setup restart skipped when the unit is absent"
|
||||
grep -q '^nmcli general reload conf$' "$sb/calls" && bad "apply helper: reloaded NM conf with no pending conf" || ok "apply helper: no NM conf reload without a pending conf"
|
||||
rm -rf "$sb"
|
||||
# (2) stage 1 fails: connected (externally)
|
||||
an_run NM_STATE="100 (connected (externally))"
|
||||
@@ -349,6 +390,22 @@ STUB
|
||||
grep -q 'ROLLED BACK (restored, connectivity ok)' "$sb/log" && ok "apply helper stage1: log ROLLED BACK" || bad "apply helper stage1: log"
|
||||
a_is "stage1: Armbian file restored mode" "$(stat -c %a "$sb/netplan/10-dhcp-all-interfaces.yaml" 2>/dev/null)" 600
|
||||
[ ! -e "$sb/netplan/50-van-wan.yaml" ] && [ ! -e "$sb/netplan/10-dhcp-all-interfaces.yaml.vanlink-disabled" ] && [ -e "$sb/netplan/60-other.yaml" ] && ok "apply helper stage1: netplan dir restored wholesale" || bad "apply helper stage1: netplan dir not restored"
|
||||
cmp -s "$sb/nmconf/zz-van-wan-pending.conf" "$sb/bak/nm-pending.conf" && ok "apply helper stage1: uplink-pending NM conf restored" || bad "apply helper stage1: uplink-pending NM conf not restored"
|
||||
[ ! -e "$sb/netplan/nm-pending.conf" ] && ok "apply helper stage1: nm-pending.conf not copied into the netplan dir" || bad "apply helper stage1: nm-pending.conf leaked into the netplan dir"
|
||||
l_ap2=$(grep -n '^netplan apply' "$sb/calls" | sed -n 2p | cut -d: -f1 || true); l_rl2=$(grep -n '^nmcli general reload conf$' "$sb/calls" | sed -n 2p | cut -d: -f1 || true)
|
||||
[ -n "$l_ap2" ] && [ -n "$l_rl2" ] && [ "$l_ap2" -lt "$l_rl2" ] && ok "apply helper stage1: NM conf reloaded after the rollback apply" || bad "apply helper stage1: no NM conf reload after the rollback apply (apply '$l_ap2', reload '$l_rl2')"
|
||||
for m in '--- diagnostics: stage 1' 'STUB-DIAG-NM' 'routers = 192.168.100.1' 'STUB-NETWORKCTL-STATUS' 'STUB-ADDR inet' 'default via 192.168.100.1 dev enP2p33s0'; do
|
||||
sed -n '/--- diagnostics/,/--- end diagnostics/p' "$sb/log" | grep -qF -- "$m" && ok "apply helper stage1: diagnostics contain '$m'" || bad "apply helper stage1: diagnostics lack '$m'"
|
||||
done
|
||||
d_ln=$(grep -n -m1 -- '--- diagnostics' "$sb/log" | cut -d: -f1 || true); r_ln=$(grep -n -m1 'restoring' "$sb/log" | cut -d: -f1 || true)
|
||||
[ -n "$d_ln" ] && [ -n "$r_ln" ] && [ "$d_ln" -lt "$r_ln" ] && ok "apply helper stage1: diagnostics logged before the restore" || bad "apply helper stage1: diagnostics not before the restore"
|
||||
grep -q '^networkctl .*status enP2p33s0' "$sb/calls" && ok "apply helper: networkctl status called" || bad "apply helper: networkctl status not called"
|
||||
grep '^networkctl .*status' "$sb/calls" | grep -qv -- ' -n 0 ' && bad "apply helper: networkctl status must pass -n 0 (no journal)" || ok "apply helper: networkctl status without journal lines"
|
||||
rm -rf "$sb"
|
||||
# (2c) stage 1 fails on a host without a pending conf: nothing to restore there
|
||||
AN_NOPEND=1 an_run NM_STATE=bad
|
||||
a_is "stage1 without pending conf: status" "$(st_word)" rolled-back
|
||||
[ ! -e "$sb/nmconf/zz-van-wan-pending.conf" ] && ok "apply helper stage1 without pending conf: none created" || bad "apply helper stage1 without pending conf: one appeared"
|
||||
rm -rf "$sb"
|
||||
# (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"
|
||||
@@ -383,6 +440,15 @@ STUB
|
||||
a_is "stage2: exit 1" "$rc" 1; a_is "stage2: status" "$(st_word)" rolled-back
|
||||
[ ! -e "$sb/netplan/50-van-wan.yaml" ] && [ -e "$sb/netplan/10-dhcp-all-interfaces.yaml" ] && ok "apply helper stage2: restored" || bad "apply helper stage2: not restored"
|
||||
rm -rf "$sb"
|
||||
# (5b) stage 2: a default route that is only a leftover networkd/DHCP one (not van-failover's proto static) must not pass
|
||||
an_run STUB_ROUTE_PROTO=dhcp
|
||||
a_is "stage2 dhcp-only default route: exit 1" "$rc" 1; a_is "stage2 dhcp-only default route: status" "$(st_word)" rolled-back
|
||||
grep -q '^ip route show default dev enP2p33s0 proto static$' "$sb/calls" && ok "apply helper stage2: checks van-failover's proto static route on the port" || bad "apply helper stage2: no proto static route check"
|
||||
grep -qF -- '--- diagnostics: stage 2' "$sb/log" && ok "apply helper stage2: diagnostics logged" || bad "apply helper stage2: diagnostics missing"
|
||||
[ -e "$sb/netplan/10-dhcp-all-interfaces.yaml" ] && cmp -s "$sb/nmconf/zz-van-wan-pending.conf" "$sb/bak/nm-pending.conf" && ok "apply helper stage2: Armbian file and pending conf restored" || bad "apply helper stage2: Armbian file / pending conf not restored"
|
||||
rm -rf "$sb"
|
||||
# (5c) port absent: stage 2 needs no proto static route on it
|
||||
AN_NOPORT=1 an_run STUB_ROUTE_PROTO=dhcp; a_is "port absent, dhcp route: status" "$(st_word)" ok; rm -rf "$sb"
|
||||
# (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
|
||||
@@ -421,6 +487,10 @@ STUB
|
||||
[ -n "$so" ] && [ -n "$tr" ] && [ "$so" -lt "$tr" ] && ok "apply helper: status ok is written before the trap is removed" || bad "apply helper: status ok must precede trap removal"
|
||||
echo "$h" | grep -q "^trap 'rm -f \"\$0\"' EXIT$" && ok "apply helper removes itself on exit" || bad "apply helper lacks the self-remove EXIT trap"
|
||||
echo "$h" | grep -q 'timeout -k 10 30 netplan generate' && echo "$h" | grep -q 'timeout -k 10 90 netplan apply' && ok "apply helper: netplan under timeout -k" || bad "apply helper: netplan lacks timeout -k"
|
||||
dg=$(echo "$h" | sed -n '/^diag() {/,/^}/p')
|
||||
[ -n "$dg" ] && ! echo "$dg" | grep -E '(^ *|[;|&(] *)(nmcli|networkctl|ip) ' | grep -q . && [ "$(echo "$dg" | grep -c 'timeout 5 ')" -ge 5 ] \
|
||||
&& ok "apply helper: every diagnostic command runs under timeout 5" || bad "apply helper: diag() missing or a diagnostic command without timeout 5"
|
||||
echo "$h" | grep -q journalctl && bad "apply helper must not call journalctl" || ok "apply helper: no journal calls"
|
||||
fi
|
||||
# 18b. status file gate and STARLINK_IFACE validation
|
||||
gs=$(mktemp -d)
|
||||
@@ -501,6 +571,18 @@ echo "$o" | grep -q -- "-> post-deploy: NetworkManager connection $conn not foun
|
||||
echo "$o" | grep -q -- '-> post-deploy: WAN netplan differs' && ok "19e: netplan differs -> warn" || bad "19e: netplan warn missing"
|
||||
printf 'id=%s\n' "$conn" > "$tc/nm/x.nmconnection"
|
||||
nm | grep -q " ok NetworkManager connection $conn exists" && ok "19e: cellular keyfile scan -> ok" || bad "19e: keyfile scan ok missing"
|
||||
# 19g. an installed WAN netplan this user cannot read (the helper installs it 0600) is "unreadable", not "differs"
|
||||
if [ "$(id -u)" = 0 ]; then
|
||||
ok "19g: unreadable-netplan checks skipped (running as root)"
|
||||
else
|
||||
chmod 000 "$tc/np.yaml"
|
||||
o=$(nm)
|
||||
echo "$o" | grep -qxF ' ok WAN netplan: cannot read as non-root (run with sudo to verify)' && ok "19g: post-check unreadable netplan -> ok line" || bad "19g: post-check unreadable netplan ok line missing"
|
||||
echo "$o" | grep -q -- '-> post-deploy: WAN netplan' && bad "19g: post-check must not warn about an unreadable netplan" || ok "19g: post-check does not warn about an unreadable netplan"
|
||||
p=$(VANLINK_WAN_NETPLAN="$tc/np.yaml" ./deploy.sh --wan-plan hosts/van.conf 2>&1)
|
||||
echo "$p" | grep -qxF ' netplan: /etc/netplan/50-van-wan.yaml unreadable (run with sudo)' && ok "19g: wan-plan unreadable netplan" || bad "19g: wan-plan unreadable netplan line"
|
||||
chmod 600 "$tc/np.yaml"
|
||||
fi
|
||||
# 19f. VANLINK_WAN_NETPLAN is a read-only-path test knob: apply_network must neutralise it, and no apply code reads other VANLINK_* vars
|
||||
an=$(sed -n '/^apply_network() {/,/^}/p' deploy.sh)
|
||||
echo "$an" | grep -q "VANLINK_WAN_NETPLAN='' nm_wan_state_netplan" && ! echo "$an" | grep -E 'nm_wan_state_netplan' | grep -qv "VANLINK_WAN_NETPLAN=''" \
|
||||
@@ -508,4 +590,66 @@ echo "$an" | grep -q "VANLINK_WAN_NETPLAN='' nm_wan_state_netplan" && ! echo "$a
|
||||
if { echo "$an"; sed -n '/^apply_helper_script() {/,/^}/p' deploy.sh; } | grep 'VANLINK_' | grep -vE 'VANLINK_CONF|VANLINK_WAN_NETPLAN=' | grep -q .; then bad "19f: apply path reads a VANLINK_* variable"; else ok "19f: apply path reads no VANLINK_* variable besides VANLINK_CONF"; fi
|
||||
rm -rf "$tc"
|
||||
|
||||
# 20. final-review fixes
|
||||
t20=$(mktemp -d)
|
||||
# 20a. (M-7) the files keeping NM off the AP/LAN ports and the uplink are installed before the package that starts NM
|
||||
l_pkg=$(grep -n 'apt-get install -y network-manager' deploy.sh | head -1 | cut -d: -f1 || true)
|
||||
for pat in 'ap/van-ap-unmanaged.conf' 'ap/nm-no-auto-default.conf' 'ap/nm-uplink-pending.conf'; do
|
||||
l=$(grep -n "^[[:space:]]*install[_ -].*$pat" deploy.sh | head -1 | cut -d: -f1 || true)
|
||||
[ -n "$l" ] && [ -n "$l_pkg" ] && [ "$l" -lt "$l_pkg" ] && ok "20a: $pat installed (line $l) before network-manager (line $l_pkg)" || bad "20a: $pat install line '$l' must precede the network-manager install line '$l_pkg'"
|
||||
done
|
||||
# 20b. (M-7) read-only sub-commands never call apt-get
|
||||
mkdir -p "$t20/apt"
|
||||
printf '#!/bin/sh\necho "apt-get $*" >> "$APT_LOG"\nexit 1\n' > "$t20/apt/apt-get"; chmod +x "$t20/apt/apt-get"
|
||||
for sub in --check --diff --post-check --wan-plan --print-apply-helper; do
|
||||
PATH="$t20/apt:$PATH" APT_LOG="$t20/apt.log" POSTCHECK_WAIT=0 ./deploy.sh "$sub" hosts/van.conf >/dev/null 2>&1 || true
|
||||
done
|
||||
[ ! -s "$t20/apt.log" ] && ok "20b: --check/--diff/--post-check/--wan-plan/--print-apply-helper never call apt-get" || { bad "20b: a read-only sub-command called apt-get"; sed 's/^/ /' "$t20/apt.log"; }
|
||||
# 20c. (M-3) network-manager is detected with dpkg-query's Status, in one helper
|
||||
grep -qE 'dpkg -s network-manager' deploy.sh && bad "20c: deploy.sh still uses dpkg -s network-manager" || ok "20c: no dpkg -s network-manager"
|
||||
[ "$(grep -c 'nm_pkg_installed' deploy.sh)" -ge 4 ] && grep -q "dpkg-query -W -f='\${Status}' network-manager" deploy.sh && ok "20c: nm_pkg_installed (dpkg-query) used by deploy, --wan-plan and --apply-network" || bad "20c: nm_pkg_installed missing or not used in all three places"
|
||||
# 20d. (I-2) uplink-pending NM conf template and its --wan-plan line
|
||||
[ "$(grep -v '^[[:space:]]*#' ap/nm-uplink-pending.conf 2>/dev/null | grep -v '^[[:space:]]*$')" = "$(printf '[keyfile]\nunmanaged-devices+=interface-name:@STARLINK_IFACE@')" ] && ok "20d: nm-uplink-pending.conf is [keyfile] + unmanaged-devices+=" || bad "20d: ap/nm-uplink-pending.conf missing or wrong"
|
||||
expect_line van ap/nm-uplink-pending.conf 'unmanaged-devices+=interface-name:enP2p33s0'
|
||||
if [ -e /etc/NetworkManager/conf.d/zz-van-wan-pending.conf ]; then
|
||||
ok "20d: wan-plan pending-conf assertions skipped (this machine has the pending conf)"
|
||||
else
|
||||
VANLINK_CONF=hosts/van.conf ./deploy.sh --render-wan-netplan > "$t20/np.yaml"
|
||||
p=$(VANLINK_WAN_NETPLAN="$t20/none.yaml" ./deploy.sh --wan-plan hosts/van.conf 2>&1)
|
||||
echo "$p" | grep -qxF ' uplink NM pending conf: would install' && ok "20d: netplan missing -> pending conf would install" || bad "20d: netplan missing -> 'would install' expected"
|
||||
p=$(VANLINK_WAN_NETPLAN="$t20/np.yaml" ./deploy.sh --wan-plan hosts/van.conf 2>&1)
|
||||
echo "$p" | grep -qxF ' uplink NM pending conf: absent' && ok "20d: netplan matches -> pending conf absent" || bad "20d: netplan matches -> 'absent' expected"
|
||||
echo '# differs' >> "$t20/np.yaml"
|
||||
p=$(VANLINK_WAN_NETPLAN="$t20/np.yaml" ./deploy.sh --wan-plan hosts/van.conf 2>&1)
|
||||
echo "$p" | grep -qxF ' uplink NM pending conf: absent' && ok "20d: netplan differs (already handed to NM) -> pending conf not installed" || bad "20d: netplan differs -> 'absent' expected"
|
||||
fi
|
||||
# 20e. (I-4) apply preflight: port present and conf named after this host
|
||||
mkdir -p "$t20/sys/enP2p33s0" "$t20/nosys"
|
||||
pf() { VANLINK_SYSNET="$1" VANLINK_HOSTNAME="$2" ./deploy.sh --apply-preflight "$3" 2>&1; }
|
||||
o=$(pf "$t20/sys" van hosts/van.conf) && rc=0 || rc=$?
|
||||
[ -z "$o" ] && [ "$rc" = 0 ] && ok "20e: preflight fine -> no output, exit 0" || bad "20e: preflight fine gave '$o' (exit $rc)"
|
||||
o=$(pf "$t20/sys" wan hosts/van.conf) && rc=0 || rc=$?
|
||||
[ "$rc" = 1 ] && [ "$(echo "$o" | grep -c .)" = 1 ] && echo "$o" | grep -q 'van.conf.*hostname wan' && ok "20e: preflight wrong host -> one problem" || bad "20e: preflight wrong host gave '$o' (exit $rc)"
|
||||
o=$(pf "$t20/nosys" van hosts/van.conf) && rc=0 || rc=$?
|
||||
[ "$rc" = 1 ] && [ "$(echo "$o" | grep -c .)" = 1 ] && echo "$o" | grep -q 'enP2p33s0' && ok "20e: preflight absent port -> one problem" || bad "20e: preflight absent port gave '$o' (exit $rc)"
|
||||
cp hosts/van.conf "$t20/other.conf"
|
||||
o=$(pf "$t20/sys" wan "$t20/other.conf") && rc=0 || rc=$?
|
||||
[ -z "$o" ] && [ "$rc" = 0 ] && ok "20e: preflight skips the host-name check for a conf outside hosts/" || bad "20e: preflight conf outside hosts/ gave '$o' (exit $rc)"
|
||||
an=$(sed -n '/^apply_network() {/,/^}/p' deploy.sh)
|
||||
echo "$an" | grep -q 'apply_preflight_problems "$VANLINK_CONF" /sys/class/net "$(hostname -s)"' && ok "20e: apply_network runs the preflight on the real sysfs/hostname" || bad "20e: apply_network does not call apply_preflight_problems with real paths"
|
||||
# 20f. (M-8) a differing installed netplan is shown redacted and refused without --force
|
||||
psk=$(sed -n 's/^ *password: *"\(.*\)"$/\1/p' ap/50-van-wan-wifi.yaml || true)
|
||||
d=$(diff -u /dev/null <(VANLINK_CONF=hosts/wan.conf ./deploy.sh --render-wan-netplan) | ./deploy.sh --redact) || true
|
||||
[ -n "$psk" ] && ! echo "$d" | grep -qF -- "$psk" && echo "$d" | grep -qE '^\+ +password: <redacted>$' && ok "20f: --redact masks the netplan Wi-Fi password in a diff" || bad "20f: netplan Wi-Fi password not redacted"
|
||||
echo "$an" | grep -q 'diff -u /etc/netplan/50-van-wan.yaml <(render_wan_netplan) | redact_secrets' && echo "$an" | grep -q 'without --force' && ok "20f: apply_network prints a redacted diff and refuses a differing netplan without --force" || bad "20f: apply_network differs handling"
|
||||
# 20g. (M-6) root ignores VANLINK_NM_DIR
|
||||
cpe=$(sed -n '/^cellular_profile_exists() {/,/^}/p' deploy.sh)
|
||||
grep -q '"$(id -u)" = 0 ] || d=${VANLINK_NM_DIR' <<<"$cpe" && ok "20g: cellular_profile_exists ignores VANLINK_NM_DIR as root" || bad "20g: cellular_profile_exists must ignore VANLINK_NM_DIR as root"
|
||||
# 20h. (M-5) documented overrides
|
||||
hdr=$(sed -n '1,/^set -euo pipefail/{/^#/p}' deploy.sh)
|
||||
for k in 'VANLINK_CONF=' 'VANLINK_HOSTNAME=' '--apply-preflight'; do
|
||||
grep -qF -- "$k" <<<"$hdr" && ok "20h: header documents $k" || bad "20h: header lacks $k"
|
||||
done
|
||||
rm -rf "$t20"
|
||||
|
||||
exit "$fail"
|
||||
|
||||
Reference in New Issue
Block a user