From e8fa2e025f461de858cadd0bfc1c5816708daa63 Mon Sep 17 00:00:00 2001 From: Andreas Wrede Date: Wed, 23 Sep 2026 18:30:17 -0400 Subject: [PATCH] deploy: install missing packages, NetworkManager WAN section, --wan-plan Pure-bash SHA-1 derived CELLULAR_UUID (no uuidgen); skip keyfile if empty. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01KqhjP7nud5gdBg25zCiRk9 --- deploy.sh | 95 +++++++++++++++++++++++++++++++++----- hosts/vanlink.conf.example | 4 +- tests/check-templates.sh | 14 ++++++ 3 files changed, 100 insertions(+), 13 deletions(-) diff --git a/deploy.sh b/deploy.sh index 063c477..d84515b 100755 --- a/deploy.sh +++ b/deploy.sh @@ -10,6 +10,7 @@ # ./deploy.sh --classify-unit # prints ok|finished|wait|warn (used by post_check; for tests) # ./deploy.sh --classify-radio # reads `iw dev info` text on stdin, prints beaconing|silent (no ssid line = silent; used by post_check; for tests) # ./deploy.sh --render-wan-netplan # rendered /etc/netplan/50-van-wan.yaml (ethernet part + wifis stanza when WIFI_WAN_IFACE is set) +# ./deploy.sh --wan-plan [conf] # what the NetworkManager WAN provisioning would do (read-only, no root) # ./deploy.sh render # preview a templated file on stdout # ./deploy.sh --redact # filter stdin, masking secret values (used by --diff; for tests) # Idempotent. Netplan (wlan0 + Starlink NIC = NM-managed WANs; eth0 is a LAN @@ -67,7 +68,10 @@ load_conf() { # load_conf — source and validate a host config || die "$f: ENABLE_NVME_WATCH=1 needs NVME_DEVICE and NVME_FS_DEVICE" fi ONBOARD_LAN_UNMANAGED=${ONBOARD_LAN_IFACE:+;interface-name:$ONBOARD_LAN_IFACE} - CELLULAR_UUID=$(uuidgen -s -n @dns -N "vanlink-${CELLULAR_CONN_NAME:-}" 2>/dev/null || true) + # Stable per connection name; pure bash + sha1sum (coreutils), no uuid-runtime dependency. + local h + h=$(printf 'vanlink-%s' "${CELLULAR_CONN_NAME:-}" | sha1sum | cut -c1-32) + CELLULAR_UUID="${h:0:8}-${h:8:4}-5${h:13:3}-8${h:17:3}-${h:20:12}" } render() { # render -> stdout, with @TOKEN@ placeholders substituted @@ -88,6 +92,12 @@ 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) + if [ ! -f /etc/netplan/50-van-wan.yaml ]; then echo missing + elif diff -q <(render_wan_netplan) /etc/netplan/50-van-wan.yaml >/dev/null 2>&1; then echo matches + else echo differs; fi +} + check_rendered() { # check_rendered — fail on a leftover @TOKEN@ local left left=$(grep -oE "$TOKEN_RE" "$2" | sort -u | tr '\n' ' ') || true @@ -139,6 +149,9 @@ fi if [ "$ENABLE_LI3" = 1 ]; then ENABLE_UNITS+=(van-li3-battery) fi +if [ "$ENABLE_NM_WAN" = 1 ]; then + ENABLE_UNITS+=(NetworkManager ModemManager) +fi if [ "$ENABLE_HA" = 1 ]; then EXTRA_CHECK_UNITS+=(homeassistant esphome) fi @@ -292,6 +305,23 @@ case "${1:-}" in redact_secrets exit 0 ;; --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 [ -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)" + elif [ -e "/etc/NetworkManager/system-connections/${CELLULAR_CONN_NAME}.nmconnection" ]; then + echo " cellular profile: ${CELLULAR_CONN_NAME} apn=${CELLULAR_APN} exists" + else + echo " cellular profile: ${CELLULAR_CONN_NAME} apn=${CELLULAR_APN} would create" + fi + fi + exit 0 ;; --classify-unit) # ./deploy.sh --classify-unit classify_unit "${2:-}" "${3:-}" "${4:-}" exit 0 ;; @@ -344,9 +374,16 @@ if [ "$ENABLE_HA" = 1 ]; then fi OPTIONAL_PACKAGES=( ) +missing_pkgs=() for pkg in "${REQUIRED_PACKAGES[@]}"; do - dpkg -s "$pkg" >/dev/null 2>&1 || { echo "MISSING REQUIRED: $pkg (apt install $pkg)"; exit 1; } + dpkg -s "$pkg" >/dev/null 2>&1 || missing_pkgs+=("$pkg") done +if [ ${#missing_pkgs[@]} -gt 0 ]; then + echo " installing missing required packages: ${missing_pkgs[*]}" + apt-get update -qq || echo " -> apt-get update failed (continuing with the cached lists)" + DEBIAN_FRONTEND=noninteractive apt-get install -y "${missing_pkgs[@]}" \ + || { echo "deploy: apt-get install failed for: ${missing_pkgs[*]}" >&2; exit 1; } +fi # Check optional packages for pkg in "${OPTIONAL_PACKAGES[@]}"; do @@ -375,15 +412,17 @@ if git -c safe.directory='*' -C "$(dirname "$conf_real")" status --porcelain -- warn "$conf_real has uncommitted changes — commit it so the host config stays reviewable" fi -echo "== netplan drift check ==" -# Netplan is never installed by this script (applying it flaps the uplinks — -# see note below), so it's easy to edit ap/50-van-wan.yaml and forget the -# manual `netplan apply` step. Warn loudly rather than silently drifting. -if [ ! -f /etc/netplan/50-van-wan.yaml ]; then - warn "netplan: /etc/netplan/50-van-wan.yaml is missing — repo config was never deployed. Run: sudo ./deploy.sh --render-wan-netplan | sudo tee /etc/netplan/50-van-wan.yaml && sudo netplan generate && sudo netplan apply" -elif ! diff -q <(render_wan_netplan) /etc/netplan/50-van-wan.yaml >/dev/null 2>&1; then - warn "netplan: /etc/netplan/50-van-wan.yaml differs from ap/50-van-wan.yaml (rendered). Deploy manually: sudo ./deploy.sh --render-wan-netplan | sudo tee /etc/netplan/50-van-wan.yaml && sudo netplan generate && sudo netplan apply" - diff -u /etc/netplan/50-van-wan.yaml <(render_wan_netplan) || true +if [ "$ENABLE_NM_WAN" != 1 ]; then + echo "== netplan drift check ==" + # Netplan is never installed by this script (applying it flaps the uplinks — + # see note below), so it's easy to edit ap/50-van-wan.yaml and forget the + # manual `netplan apply` step. Warn loudly rather than silently drifting. + if [ ! -f /etc/netplan/50-van-wan.yaml ]; then + warn "netplan: /etc/netplan/50-van-wan.yaml is missing — repo config was never deployed. Run: sudo ./deploy.sh --render-wan-netplan | sudo tee /etc/netplan/50-van-wan.yaml && sudo netplan generate && sudo netplan apply" + elif ! diff -q <(render_wan_netplan) /etc/netplan/50-van-wan.yaml >/dev/null 2>&1; then + warn "netplan: /etc/netplan/50-van-wan.yaml differs from ap/50-van-wan.yaml (rendered). Deploy manually: sudo ./deploy.sh --render-wan-netplan | sudo tee /etc/netplan/50-van-wan.yaml && sudo netplan generate && sudo netplan apply" + diff -u /etc/netplan/50-van-wan.yaml <(render_wan_netplan) || true + fi fi echo "== access point ==" @@ -602,6 +641,40 @@ fi echo "== hardware watchdog ==" install -D -m0644 power/10-vanlink-watchdog.conf /etc/systemd/system.conf.d/10-vanlink-watchdog.conf +if [ "$ENABLE_NM_WAN" = 1 ]; then + echo "== networkmanager WANs ==" + if ! dpkg -s network-manager >/dev/null 2>&1; 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 + DEBIAN_FRONTEND=noninteractive apt-get install -y network-manager \ + || { echo "deploy: apt-get install network-manager failed" >&2; exit 1; } + fi + systemctl enable --now NetworkManager ModemManager >/dev/null 2>&1 || true + nmcli general reload conf 2>/dev/null || true + for nm_if in "$WIFI_5G_IFACE" "$WIFI_2G_IFACE" "$LAN_IFACE" ${ONBOARD_LAN_IFACE:-}; do + [ -e "/sys/class/net/$nm_if" ] || continue + nm_state=$(nmcli -t -f DEVICE,STATE device 2>/dev/null | awk -F: -v d="$nm_if" '$1 == d { print $2 }') || true + [ "$nm_state" = unmanaged ] \ + || warn "NetworkManager manages $nm_if (state: ${nm_state:-unknown}) — AP/LAN ports must be unmanaged (see ap/van-ap-unmanaged.conf)" + done + if [ -n "${CELLULAR_APN:-}" ]; then + nm_keyfile="/etc/NetworkManager/system-connections/${CELLULAR_CONN_NAME}.nmconnection" + if [ -e "$nm_keyfile" ]; then + echo " cellular connection ${CELLULAR_CONN_NAME} exists — left unchanged" + elif [ -z "${CELLULAR_UUID:-}" ]; then + warn "CELLULAR_UUID is empty — not creating the cellular keyfile $nm_keyfile" + else + install_rendered ap/cellular.nmconnection "$nm_keyfile" 0600 + nmcli connection reload 2>/dev/null || true + echo " created cellular connection ${CELLULAR_CONN_NAME} (apn ${CELLULAR_APN}) — no modem needed" + fi + fi + if [ "$(nm_wan_state_netplan)" != matches ]; then + warn "WAN netplan is 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 +fi + echo "== apply ==" sysctl --system >/dev/null systemctl daemon-reload diff --git a/hosts/vanlink.conf.example b/hosts/vanlink.conf.example index 20a02dc..1b0fc0f 100644 --- a/hosts/vanlink.conf.example +++ b/hosts/vanlink.conf.example @@ -49,5 +49,5 @@ ENABLE_HA=0 # Home Assistant + ESPHome (podman quadlets) ENABLE_FRIGATE=0 # Frigate NVR (podman quadlet) ENABLE_NM_WAN=1 # NetworkManager owns the WANs (netplan, cellular profile) via --apply-network -CELLULAR_CONN_NAME=Koodo # optional: NM name of the cellular connection (also used by van-failover) -CELLULAR_APN=sp.koodo.com # optional: cellular APN +CELLULAR_CONN_NAME=Koodo # NM name of the cellular connection (also used by van-failover); needed with ENABLE_NM_WAN=1 when a cellular WAN is wanted +CELLULAR_APN=sp.koodo.com # cellular APN; needed with ENABLE_NM_WAN=1 when a cellular WAN is wanted (no APN = no cellular profile) diff --git a/tests/check-templates.sh b/tests/check-templates.sh index ae1b41f..84c5711 100755 --- a/tests/check-templates.sh +++ b/tests/check-templates.sh @@ -228,4 +228,18 @@ for h in van wan; do done done +# 17. --wan-plan describes what would happen (config-derived lines only; no root, no changes) +for h in van wan; do + if ! p=$(VANLINK_CONF=hosts/$h.conf ./deploy.sh --wan-plan 2>&1); then bad "$h: --wan-plan failed"; echo "$p" | sed 's/^/ /'; continue; fi + echo "$p" | grep -q '^wan-plan: ENABLE_NM_WAN=1$' && ok "$h: wan-plan header" || bad "$h: wan-plan header" + echo "$p" | grep -q 'cellular profile: Koodo apn=sp.koodo.com' && ok "$h: wan-plan cellular" || bad "$h: wan-plan cellular" +done +p=$(VANLINK_CONF=hosts/van.conf ./deploy.sh --wan-plan); echo "$p" | grep -q 'wifi WAN: none' && ok "van: wan-plan says no Wi-Fi WAN" || bad "van: wan-plan Wi-Fi WAN" +p=$(VANLINK_CONF=hosts/wan.conf ./deploy.sh --wan-plan); echo "$p" | grep -q 'wifi WAN: wlan0' && ok "wan: wan-plan Wi-Fi WAN wlan0" || bad "wan: wan-plan Wi-Fi WAN" +# CELLULAR_UUID is derived in pure bash (no uuidgen dependency) and stable +u1=$(VANLINK_CONF=hosts/van.conf ./deploy.sh render ap/cellular.nmconnection | grep '^uuid=') +u2=$(VANLINK_CONF=hosts/wan.conf ./deploy.sh render ap/cellular.nmconnection | grep '^uuid=') +[ -n "$u1" ] && [ "$u1" != "uuid=" ] && [ "$u1" = "$u2" ] && ok "cellular uuid non-empty and stable across hosts" || bad "cellular uuid: '$u1' vs '$u2'" +[ "$(grep -v '^[[:space:]]*#' deploy.sh | grep -c uuidgen)" = 0 ] && ok "deploy.sh does not call uuidgen" || bad "deploy.sh calls uuidgen" + exit "$fail"