Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KqhjP7nud5gdBg25zCiRk9
7.9 KiB
NetworkManager-owned WANs, provisioned by deploy.sh
Date: 2026-09-23. Status: design approved in chat; spec for review. Builds on
2026-09-23-host-config-design.md (host files, ENABLE_* flags, --check/--diff/--post-check).
Problem
van-failover steers multi-WAN through NetworkManager: it reads active connections and
gateways with nmcli, sets route metrics per NM profile, and the cellular WAN is a NM gsm
profile (Koodo). The Pi 5 runs that model. The R5C has no NetworkManager (networkd only;
Armbian's 10-dhcp-all-interfaces.yaml does DHCP on every e* port), so on the R5C
van-failover sees no connections at all (present: false, connection: null); it "works"
only because networkd's single DHCP default route happens to be on the Starlink port. A GSM
modem (Quectel EC25-AF, MBIM via ModemManager) will be attached later, and the home LAN on
enP2p33s0 is a stand-in for the field's Starlink connection, so the R5C needs the same
NM-owned WAN model as the Pi, and it must be provisioned by deploy.sh rather than by hand.
Decisions (approved)
- NetworkManager owns the WAN side on every host that sets
ENABLE_NM_WAN=1;systemd-networkdkeeps br0, the AP radios and the wired LAN port(s), as today. One code path on both hosts. - A routine
deploy.shnever flaps uplinks. Changing the WAN netplan configuration is an explicit step,./deploy.sh --apply-network, with an automatic rollback. deploy.shinstalls the packages it needs.
Design
1. Host variables (add to REQUIRED_VARS / OPTIONAL_VARS; hosts/vanlink.conf.example and both host files)
| Variable | Kind | Meaning |
|---|---|---|
ENABLE_NM_WAN |
required flag 0/1 | NetworkManager owns the WAN ports and the modem. Both hosts/van.conf and hosts/wan.conf set 1. |
CELLULAR_CONN_NAME |
optional | NM connection name of the cellular WAN; failover/config.json uses @CELLULAR_CONN_NAME@ instead of the hardcoded Koodo. Default in both host files: Koodo. |
CELLULAR_APN |
optional | APN for the cellular profile (sp.koodo.com). Empty: no profile is created. |
WIFI_WAN_IFACE |
now optional | Empty means the host has no Wi-Fi uplink (the R5C: both radios are APs); the wifis: stanza of the netplan template is then omitted. |
ENABLE_NM_WAN=0 means none of the below applies (current R5C behaviour).
2. Package installation
- Missing REQUIRED packages are installed by
deploy.shitself (DEBIAN_FRONTEND=noninteractive apt-get install -y) instead of aborting with "apt install X". If apt cannot install one, abort with the apt error (today's outcome).--check/--diff/--post-checknever install anything. network-manageris the exception in ordering: it is installed after the files that keep NM off the AP radios and LAN ports (van-ap-unmanaged.conf) are in place, because the package starts NM on install and NM manages Wi-Fi and gsm devices by default. After installing it,deploy.shchecksnmcli -t -f DEVICE,STATE deviceand warns ifWIFI_5G_IFACE,WIFI_2G_IFACE,LAN_IFACEorONBOARD_LAN_IFACEis notunmanaged.- With
ENABLE_NM_WAN=1:network-manageris required; NetworkManager and ModemManager are enabled.
3. Routine deploy behaviour (ENABLE_NM_WAN=1)
- The cellular NM profile is created if absent, matching the Pi's working
Koodo: typegsm, no interface binding,apn=$CELLULAR_APN, no credentials, autoconnect yes,ipv4.method auto,ipv4.route-metric 300,ipv4.never-default yes,ipv6.method auto. An existing profile is never modified. Creation needs no modem to be present. /etc/netplan/50-van-wan.yaml, the Armbian netplan file and the netplan state are not touched. If/etc/netplan/50-van-wan.yamlis missing or differs from the rendered template, deploy warns and names./deploy.sh --apply-network(this replaces today's "run netplan by hand" text). The Pi 5's live uplinks are therefore never flapped by a routine deploy.
4. ./deploy.sh --apply-network (root, explicit)
- Refuse unless
ENABLE_NM_WAN=1andnetwork-manageris installed. - Back up
/etc/netplanto/var/backups/vanlink-netplan-<timestamp>/. - Install the rendered
50-van-wan.yaml. If/etc/netplan/10-dhcp-all-interfaces.yamlexists (Armbian), move it aside as10-dhcp-all-interfaces.yaml.vanlink-disabledso networkd stops DHCP-ing the WAN port behind NM's back. - Apply from a detached transient unit (
systemd-run), so the sequence survives the SSH session dropping:netplan generate && netplan apply, then wait up to 60 s for: NM reports an active connection onSTARLINK_IFACE(or the WAN is otherwise up), a default route exists, andping -c1 1.1.1.1works. - On success: log, keep the backup. On failure: restore the backup (including the Armbian file), re-apply, and record a warning with the reason. The exit status is non-zero on failure.
- The detached unit writes its outcome to
/var/log/vanlink-apply-network.log.
5. Template changes
ap/50-van-wan.yamlis split: the ethernet (Starlink) part stays; thewifis:stanza (with the Wapana access point) moves to a fragmentap/50-van-wan-wifi.yamlthat--apply-networkappends only whenWIFI_WAN_IFACEis non-empty. The rendered result for the Pi 5 must be byte-identical to today's/etc/netplan/50-van-wan.yamlthere (checked with--diffbefore the Pi is ever touched). The Wapana PSK stays where it is (known, out of scope).failover/config.json: cellularconnectionbecomes@CELLULAR_CONN_NAME@.
6. Post-check additions (only when ENABLE_NM_WAN=1)
NetworkManager active; ModemManager active; the cellular profile exists (if CELLULAR_APN set);
AP/LAN devices are unmanaged in NM; van-failover's /run/van-failover/state.json shows each
WAN whose device exists as present: true.
Testing
tests/check-templates.sh: new variables in the example (existing sync check); host files pass--check; the awk flag-guard check gainsENABLE_NM_WANfornetwork-manager,nmcli,50-van-wanand the netplan-moving code (only inside guards); renderedfailover/config.jsonhasKoodofor both hosts; the rendered netplan forwan.confcontains the wifis stanza and forvan.confdoes not; a--wan-plansub-command prints (no changes, no root) the actions--apply-networkwould take, asserted for both hosts; the rollback decision logic is a small function tested through a seam like--classify-unit.- Not testable without root/live hosts: the apply and rollback themselves. Those are verified on the R5C (Rollout) with a console or alternate access path available.
Rollout
- Build, review, push. Sync to the R5C (excluding
hosts/van.conf). SetENABLE_NM_WAN=1,CELLULAR_CONN_NAME,CELLULAR_APNin itshosts/van.conf(currently an uncommitted local edit). - R5C: routine
sudo ./deploy.sh(installsnetwork-manager, creates the cellular profile, warns about the pending WAN netplan), thensudo ./deploy.sh --apply-networkonce. Uplink today is the home LAN onenP2p33s0; the port changes from networkd to NM, a short blip. - Pi 5: routine deploy only;
--diffmust show no change to50-van-wan.yaml, and no apply is run. - When the modem is attached:
mmcli -L, the profile connects,cat /run/van-failover/state.jsonshows cellular present, route metrics 200 (Starlink) / 300 (cellular).
Out of scope
Moving the Wapana PSK out of git; choosing the modem's USB port; a Wi-Fi WAN on the R5C (needs a spare radio); replacing NetworkManager on either host; cellular credentials/PIN (the Koodo profile has none).
Risks
- The cutover changes the manager of the uplink port on a live host: mitigated by the detached apply, the connectivity gate and automatic rollback; a console or ZeroTier path should exist anyway.
- NM installation must not touch AP/LAN ports: mitigated by the install ordering and the unmanaged check.
- Auto-installing packages changes deploy's contract: only missing required packages are installed and a failure aborts with the apt error.