From 97dd23f5a651dac5647d2012e0c2da2834a80a51 Mon Sep 17 00:00:00 2001 From: Andreas Wrede Date: Wed, 23 Sep 2026 13:08:08 -0400 Subject: [PATCH] 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 --- .../specs/2026-09-23-host-config-design.md | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-23-host-config-design.md diff --git a/docs/superpowers/specs/2026-09-23-host-config-design.md b/docs/superpowers/specs/2026-09-23-host-config-design.md new file mode 100644 index 0000000..091070e --- /dev/null +++ b/docs/superpowers/specs/2026-09-23-host-config-design.md @@ -0,0 +1,156 @@ +# 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 ` 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.