Moves host-specific configuration out of the repo so the Pi 5 and R5C can share one git checkout, with feature flags and template checks. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
8.5 KiB
Host-local configuration for multi-host vanlink
Date: 2026-09-23. Status: draft for review.
Problem
Two hosts run vanlink from this repo: the Pi 5 (10.42.0.1) and the FriendlyElec
R5C (10.42.1.1). Host-specific values (interface names, addressing, storage
devices, which optional services to run) live in deploy.conf, which is tracked in git
and was hand-edited per host. The hosts drifted: each has its own uncommitted
deploy.conf, and the R5C runs a non-git copy of the repo. On 2026-09-23 a deploy on
the R5C failed because:
van-ap-dnsmasq.confand21-van-br0.networkcontain@TOKEN@placeholders butdeploy.shinstalled them with plaininstall, so they reached/etcunrendered (dnsmasq failed to parse, br0 got no address);nftables.confon the host was an unrendered copy for the same reason;- Armbian's netplan-generated
10-netplan-all-eth-interfaces.networksorts before22-van-lan.network, so networkd never applied the bridge config to the LAN port.
Other host-specific values are hardcoded outside deploy.conf: van-br0-setup.service
(enP2p33s0, wlp1s0), failover/config.json and router-status/config.json
(wlan0), ap/50-van-wan.yaml (wlan0), ap/van-ap-unmanaged.conf (eth0),
ap/23-van-lan-eth0.network, power/nvme-watch-config.json (NVMe device),
failover/sync-usb-backup.sh (/dev/sda2, NFS target), and failover/sync-sd-backup.sh
(SD UUID).
Decisions already made
- One git repo, cloned on every host, same branch; no per-host copies or branches.
- All host-specific values live in
/etc/vanlink.confon the host, outside the repo. - Per-host optional services are controlled by explicit feature flags in that file.
deploy.shkeeps rendering@TOKEN@placeholders at deploy time (approach A); services do not read/etc/vanlink.confat runtime, except the two backup shell scripts.
Design
1. /etc/vanlink.conf
Plain KEY=value lines, comments allowed, no secrets (mode 0644). It is valid shell,
so deploy.sh and the backup scripts source it. Every variable below is required
unless marked optional: deploy.sh aborts naming any missing one, so a variable added
in a later release fails loudly on hosts that have not adopted it instead of silently
defaulting.
| Group | Variable | Meaning / used by |
|---|---|---|
| Interfaces | WIFI_5G_IFACE, WIFI_2G_IFACE |
AP radios (hostapd, networkd, br0-setup) |
LAN_USB_IFACE |
wired LAN port bridged into br0 (name kept to limit churn) | |
STARLINK_IFACE |
Starlink uplink (netplan, failover, status) | |
WIFI_WAN_IFACE (new) |
Wi-Fi uplink, currently hardcoded wlan0 |
|
ONBOARD_LAN_IFACE (new, optional) |
extra onboard LAN port bridged into br0 (Pi: eth0); empty = none |
|
| Addressing | GATEWAY_IP, GATEWAY_PREFIX, GATEWAY_NETWORK, DHCP_RANGE_START, DHCP_RANGE_END |
br0, dnsmasq, nftables |
DNS_RESOLVERS |
fixed upstream resolvers | |
| Hardware | MODEM_USB_VENDOR |
modem watch / udev |
NVME_DEVICE, NVME_FS_DEVICE (new) |
NVMe watchdog; only read when ENABLE_NVME_WATCH=1 |
|
| Backup | USB_BACKUP_ROOT_PART, USB_BACKUP_MOUNT (new) |
sync-usb-backup.sh |
SD_BACKUP_UUID (new) |
sync-sd-backup.sh |
|
| Features | ENABLE_COCKPIT (replaces SKIP_COCKPIT) |
Cockpit plugin |
ENABLE_LI3 (replaces the Pi's uncommitted SKIP_LI3) |
Li3 battery service | |
ENABLE_NVME_WATCH |
NVMe watchdog | |
ENABLE_HA |
podman quadlets: Home Assistant + ESPHome; adds podman to required packages |
|
ENABLE_FRIGATE |
Frigate quadlet |
Backup variables are only validated by the script that uses them, not by deploy.sh,
because a host uses one backup method or neither.
2. Repo changes
deploy.confis deleted.hosts/vanlink.conf.example: every variable with a comment (the reference).hosts/r5c.conf,hosts/pi5.conf: known-good values for the two hosts. Used as test input for--checkand as the starting point on a host:sudo install -m0644 hosts/r5c.conf /etc/vanlink.conf.deploy.shnever writes or overwrites/etc/vanlink.conf.- New tokens:
@WIFI_WAN_IFACE@,@ONBOARD_LAN_IFACE@,@NVME_DEVICE@,@NVME_FS_DEVICE@.van-br0-setup.service,failover/config.json,router-status/config.json,ap/50-van-wan.yaml,ap/van-ap-unmanaged.conf,power/nvme-watch-config.jsonandap/nftables.confbecome templates (nftables currently hardcodes10.42.1.0/24, which is wrong on the Pi 5). README.mdheader ("Pi 4 port") updated to describe the shared model.
3. deploy.sh behaviour
- Config path is
${VANLINK_CONF:-/etc/vanlink.conf}. Missing file: abort with the install command above. Missing/empty required variable: abort naming it. install_renderedrefuses to install any output that still matches@[A-Z][A-Z0-9_]*@and aborts before anything is restarted. Every file containing a token must be installed through it (enforced by the test below).ap/22-van-lan.networkis installed as05-van-lan.network(sorts before the netplan-generated10-file) and any old22-van-lan.networkis removed. Same fix for23-van-lan-eth0.networkwhenONBOARD_LAN_IFACEis set; when empty it is neither installed nor left behind.- Feature-flagged sections: install,
enableandrestartare all skipped when the flag is 0 (todaysystemctl enable/restartnames van-li3-battery unconditionally). ./deploy.sh --check [conf]: no root needed, no installs. Loads the conf (default/etc/vanlink.conf), validates variables, renders every token-bearing file to a temp dir, and reports leftover tokens. Exit non-zero on any problem../deploy.sh render <file>keeps working.- Failure while restarting
nftablesorvan-ap-dnsmasqis reported as a warning in the summary (it is today printed and lost).
4. Backup scripts
sync-usb-backup.sh and sync-sd-backup.sh source /etc/vanlink.conf and use
${VAR:?set in /etc/vanlink.conf}. Hardcoded devices, UUIDs and NFS targets are
removed. The failover/dormant-boot.scr logic of sync-sd-backup.sh is unchanged.
Testing
tests/check-templates.sh (plain bash, run from the repo root, no root needed):
bash -n deploy.shand the backup scripts.- Every repo file containing
@TOKEN@is installed bydeploy.shviainstall_rendered(fails on the class of bug from 2026-09-23). - For each
hosts/*.conf:./deploy.sh --check hosts/x.confpasses, and rendering all templates leaves no@…@. hosts/vanlink.conf.exampledefines exactly the variablesdeploy.shrequires (no drift between docs and code).
On the hosts: --check first, then a real deploy. The R5C deploy is done at a console
or with the cabling settled (see Migration); a deploy restarts systemd-networkd.
Migration
- Implement and merge on
wan, run the test script, push. - R5C: write
/etc/vanlink.conffrom its currentdeploy.conf(interfaces as currently swapped),git clonethe repo next to the stale copy, keep the old directory asvanlink.old, run--check, then deploy. - Pi 5: write
/etc/vanlink.conffrom itsdeploy.conf(its real interfaces,10.42.0.x,ENABLE_LI3=0,ENABLE_COCKPIT=0,ENABLE_HAetc. as it is today); copy its 4 modified files to a backup directory;git checkout -- .and fast-forward to the sharedwan;--check; deploy. Its existing behaviour must not change:diffthe rendered files against what is currently in/etcbefore restarting anything. mainis brought up to date withwanafterwards (separate decision; hosts followwan).
Rollback per host: restore the old directory and, if needed, the previous files from
/etc (deploy.sh does not currently back them up; the pre-deploy diff in step 3 is
the safety net for the Pi 5).
Out of scope / follow-ups
ap/50-van-wan.yamlcontains the Wi-Fi PSK hash for "Wapana", committed to git. Should move to the host (and the value be considered exposed); separate change.power/thermal-config.jsonhardcodes Pi sensor names (rp1_adc,nvme); the R5C needs its own sensor list. A per-host mechanism for it is deferred until wanted.- Netplan (
ap/50-van-wan.yaml) remains a manual step; the drift check stays as is. - The
.bakfiles inap/andfailover/are unrelated clutter.
Open questions for review
- Keep
LAN_USB_IFACEas the name even though the R5C's port is PCIe, or rename toLAN_IFACE(touches 5 templates)? Draft keeps it. - Are
hosts/r5c.conf/hosts/pi5.confacceptable in the repo as reference values (interface names and IPs, no secrets)? They deliberately are not read by deploy.sh.