176 lines
9.8 KiB
Markdown
176 lines
9.8 KiB
Markdown
# Host-local configuration for multi-host vanlink
|
|
|
|
Date: 2026-09-23. Status: approved by user; revised 2026-09-23 (LAN_IFACE rename, symlinked host config).
|
|
|
|
## 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 one file per host under `hosts/` in the repo, and
|
|
`/etc/vanlink.conf` on each host is a symlink to that host's file. (Revised from
|
|
"outside the repo": one versioned source of truth per host, no drift, `git pull`
|
|
carries config changes.)
|
|
- 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. Host config file and the `/etc/vanlink.conf` symlink
|
|
|
|
Each host has `hosts/<hostname>.conf` (named by `hostname -s`, so `hosts/van.conf`
|
|
for the R5C and `hosts/wan.conf` for the Pi 5; a hardware swap does not rename a
|
|
host). `/etc/vanlink.conf` is an absolute symlink to it:
|
|
`sudo ln -sfn "$PWD/hosts/$(hostname -s).conf" /etc/vanlink.conf`. Everything reads
|
|
`/etc/vanlink.conf`, so there is one fixed path. Consequences, accepted:
|
|
|
|
- Editing a host's values means committing to `hosts/<hostname>.conf` (on the host or
|
|
the laptop) and pulling; there is no invisible per-host drift, but a dirty checkout
|
|
on a host is still possible and `deploy.sh` reports it (see 3).
|
|
- Root `source`s a file writable by the checkout's owner. Fine on these single-user
|
|
boxes (that user has sudo anyway); it is also why the file must never hold secrets.
|
|
- If the checkout is moved the symlink dangles; `deploy.sh` says so and prints the
|
|
`ln` command.
|
|
|
|
Plain `KEY=value` lines, comments allowed, **no secrets** (it is in git and read by
|
|
root). 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_IFACE` | wired LAN port bridged into br0 (renamed from `LAN_USB_IFACE`; the R5C's is PCIe) |
|
|
| | `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/van.conf` (R5C), `hosts/wan.conf` (Pi 5): the real, versioned values for the
|
|
two hosts, also the test input for `--check`. `deploy.sh` never creates the symlink or
|
|
edits these files.
|
|
- 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 or dangling symlink:
|
|
abort with the `ln` command for this host. Missing/empty required variable: abort
|
|
naming it. It prints the resolved file it is using, and warns if that file has
|
|
uncommitted changes (`git status`) so a deploy from unreviewed values is visible.
|
|
- `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:** create `hosts/van.conf` from its current `deploy.conf` (interfaces as
|
|
currently swapped) in the repo, commit and push; on the R5C `git clone` next to the
|
|
stale copy, keep the old directory as `vanlink.old`, make the symlink, run
|
|
`--check`, then deploy.
|
|
3. **Pi 5:** create `hosts/wan.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`; make the symlink; `--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
|
|
|
|
None open. Resolved: `LAN_USB_IFACE` becomes `LAN_IFACE` (the `lan_usb_iface` key in
|
|
`router-status/config.json` is a consumer-facing name and stays); host files live in
|
|
the repo and are reached through the `/etc/vanlink.conf` symlink.
|