Files
vanlink/docs/superpowers/specs/2026-09-23-host-config-design.md
T
andreasandClaude Sonnet 5 97dd23f5a6 Add design spec for host-local /etc/vanlink.conf
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>
2026-09-23 13:08:08 -04:00

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:

  1. van-ap-dnsmasq.conf and 21-van-br0.network contain @TOKEN@ placeholders but deploy.sh installed them with plain install, so they reached /etc unrendered (dnsmasq failed to parse, br0 got no address);
  2. nftables.conf on the host was an unrendered copy for the same reason;
  3. Armbian's netplan-generated 10-netplan-all-eth-interfaces.network sorts before 22-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.conf on the host, outside the repo.
  • Per-host optional services are controlled by explicit feature flags in that file.
  • deploy.sh keeps rendering @TOKEN@ placeholders at deploy time (approach A); services do not read /etc/vanlink.conf at 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.conf is 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 --check and as the starting point on a host: sudo install -m0644 hosts/r5c.conf /etc/vanlink.conf. deploy.sh never 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.json and ap/nftables.conf become templates (nftables currently hardcodes 10.42.1.0/24, which is wrong on the Pi 5).
  • README.md header ("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_rendered refuses 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.network is installed as 05-van-lan.network (sorts before the netplan-generated 10- file) and any old 22-van-lan.network is removed. Same fix for 23-van-lan-eth0.network when ONBOARD_LAN_IFACE is set; when empty it is neither installed nor left behind.
  • Feature-flagged sections: install, enable and restart are all skipped when the flag is 0 (today systemctl enable/restart names 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 nftables or van-ap-dnsmasq is 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):

  1. bash -n deploy.sh and the backup scripts.
  2. Every repo file containing @TOKEN@ is installed by deploy.sh via install_rendered (fails on the class of bug from 2026-09-23).
  3. For each hosts/*.conf: ./deploy.sh --check hosts/x.conf passes, and rendering all templates leaves no @…@.
  4. hosts/vanlink.conf.example defines exactly the variables deploy.sh requires (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

  1. Implement and merge on wan, run the test script, push.
  2. R5C: write /etc/vanlink.conf from its current deploy.conf (interfaces as currently swapped), git clone the repo next to the stale copy, keep the old directory as vanlink.old, run --check, then deploy.
  3. Pi 5: write /etc/vanlink.conf from its deploy.conf (its real interfaces, 10.42.0.x, ENABLE_LI3=0, ENABLE_COCKPIT=0, ENABLE_HA etc. as it is today); copy its 4 modified files to a backup directory; git checkout -- . and fast-forward to the shared wan; --check; deploy. Its existing behaviour must not change: diff the rendered files against what is currently in /etc before restarting anything.
  4. main is brought up to date with wan afterwards (separate decision; hosts follow wan).

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.yaml contains 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.json hardcodes 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 .bak files in ap/ and failover/ are unrelated clutter.

Open questions for review

  1. Keep LAN_USB_IFACE as the name even though the R5C's port is PCIe, or rename to LAN_IFACE (touches 5 templates)? Draft keeps it.
  2. Are hosts/r5c.conf / hosts/pi5.conf acceptable in the repo as reference values (interface names and IPs, no secrets)? They deliberately are not read by deploy.sh.