deploy: host config from /etc/vanlink.conf (hosts/<hostname>.conf)
deploy.conf is replaced by one versioned file per host plus an example. deploy.sh validates the config, renders every template with a generic token loop, refuses to install output with leftover @TOKENs and gains --check/--templates. LAN_USB_IFACE is renamed LAN_IFACE. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
# gets DHCP/DNS from the same dnsmasq as Wi-Fi clients. NM must leave this device
|
||||
# alone (see van-ap-unmanaged.conf) for networkd to own it here.
|
||||
[Match]
|
||||
Name=@LAN_USB_IFACE@
|
||||
Name=@LAN_IFACE@
|
||||
|
||||
[Link]
|
||||
RequiredForOnline=no
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
# The AP wlans (5GHz + 2.4GHz) and the wired LAN ports (USB dongle + onboard
|
||||
# eth0) are all owned by systemd-networkd/hostapd (bridged into br0), so
|
||||
# NetworkManager must not touch them.
|
||||
unmanaged-devices=interface-name:@WIFI_5G_IFACE@;interface-name:@WIFI_2G_IFACE@;interface-name:@LAN_USB_IFACE@;interface-name:eth0
|
||||
unmanaged-devices=interface-name:@WIFI_5G_IFACE@;interface-name:@WIFI_2G_IFACE@;interface-name:@LAN_IFACE@;interface-name:eth0
|
||||
|
||||
@@ -15,7 +15,7 @@ const APS = [
|
||||
];
|
||||
const PREFER_FILE = "/run/van-failover/prefer"; // van-failover reads this to pick the preferred WAN
|
||||
// Wired LAN bridge ports: clients behind them are found via the bridge FDB.
|
||||
const LAN_PORTS = { "eth0": "LAN (eth0)", "@LAN_USB_IFACE@": "LAN (USB)" };
|
||||
const LAN_PORTS = { "eth0": "LAN (eth0)", "@LAN_IFACE@": "LAN (USB)" };
|
||||
// Starlink dish: gRPC status API on the fixed management IP (reached via the /32
|
||||
// link route the netplan profile installs on the RTL8153 uplink).
|
||||
const STARLINK = { iface: "@STARLINK_IFACE@", dish: "192.168.100.1:9200" };
|
||||
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
# deploy.conf — hardware-instance identifiers for this Pi's vanlink deployment.
|
||||
#
|
||||
# These are the values that change when a USB Wi-Fi dongle, LAN adapter, or
|
||||
# modem gets physically swapped (MAC-derived interface names are stable per
|
||||
# physical device, but change when the device changes). Edit here and run
|
||||
# `sudo ./deploy.sh` — it substitutes @TOKEN@ placeholders in the repo's
|
||||
# config templates with these values before installing them.
|
||||
#
|
||||
# Find a new device's interface name after plugging it in: `iw dev` (wifi) or
|
||||
# `ip -br link` (wired). USB vendor ID: `lsusb`.
|
||||
# Preview a rendered file without deploying: `sudo ./deploy.sh render <file>`
|
||||
|
||||
SKIP_COCKPIT=1
|
||||
|
||||
# 5GHz AP radio (currently: Mediatek MT7925E PCIe on FriendlyElec R5C, driver mt76 on Armbian 6.18)
|
||||
# Note: RTL8822CE (rtw88 driver) does not support bridge mode in AP; MT7925E solved this
|
||||
WIFI_5G_IFACE=wlp1s0
|
||||
|
||||
# 2.4GHz AP radio (currently: same as 5GHz on Armbian R5C; on Debian was D-Link DWA-171)
|
||||
WIFI_2G_IFACE=wlx3c3332002066
|
||||
|
||||
# Wired LAN port, USB-attached gigabit adapter (RTL8153-family)
|
||||
LAN_USB_IFACE=enP2p33s0
|
||||
|
||||
# Starlink dish uplink, USB-attached gigabit adapter (RTL8153-family)
|
||||
#STARLINK_IFACE=enxd8ec5eeb3512 # White Belkin dongle
|
||||
#STARLINK_IFACE=enx6c1ff7d210a5 # UGreen 8-port hub's built-in Ethernet (ASIX AX88179B)
|
||||
STARLINK_IFACE=enP1p17s0
|
||||
|
||||
# Cellular modem USB vendor ID (Quectel EC25-AF)
|
||||
MODEM_USB_VENDOR=2c7c
|
||||
|
||||
# Fixed upstream DNS resolvers — this router always uses these, never a WAN's
|
||||
# own DHCP/RA-provided servers (NetworkManager is told to ignore those
|
||||
# entirely; see ap/van-wan-dns.conf + ap/99-van-router-dns.conf). Keeps
|
||||
# resolution identical on Wapana, Starlink, or cellular.
|
||||
DNS_RESOLVERS="1.1.1.1 8.8.8.8"
|
||||
|
||||
# IP address configuration for this router
|
||||
# When deploying to different hardware, change GATEWAY_IP, DHCP_RANGE_START/END, and GATEWAY_NETWORK.
|
||||
# Pi 5 (main): 10.42.0.1/24 with DHCP 10.42.0.10-254
|
||||
# R5C (failover): 10.42.1.1/24 with DHCP 10.42.1.10-254
|
||||
GATEWAY_IP="10.42.1.1"
|
||||
GATEWAY_PREFIX="24"
|
||||
GATEWAY_NETWORK="10.42.1.0/24"
|
||||
DHCP_RANGE_START="10.42.1.10"
|
||||
DHCP_RANGE_END="10.42.1.254"
|
||||
@@ -2,49 +2,119 @@
|
||||
# Deploy vanlink configs/scripts (Pi 4 "wan" port) to their system locations.
|
||||
# Usage: cd ~/vanlink && sudo ./deploy.sh
|
||||
# sudo ./deploy.sh render <file> # preview a templated file on stdout
|
||||
# ./deploy.sh --check [conf] # validate conf + render all templates (no root)
|
||||
# ./deploy.sh --templates # list templated files
|
||||
# Idempotent. Netplan (wlan0 + Starlink NIC = NM-managed WANs; eth0 is a LAN
|
||||
# port on br0) is NOT deployed here — reference copy in ap/50-van-wan.yaml,
|
||||
# applied once manually (apply flaps uplinks). We do check it for drift below.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$(readlink -f "$0")")"
|
||||
|
||||
# deploy.conf holds hardware-instance identifiers (interface names, USB vendor
|
||||
# IDs) that change when a dongle/adapter gets physically swapped. Config files
|
||||
# below carry @TOKEN@ placeholders substituted from these variables via
|
||||
# render()/install_rendered() — edit deploy.conf, not the individual configs.
|
||||
set -a
|
||||
source ./deploy.conf
|
||||
set +a
|
||||
# Host-specific values (interface names, addressing, feature flags) live in
|
||||
# /etc/vanlink.conf — a symlink to hosts/<hostname>.conf in this repo. Config
|
||||
# files below carry @TOKEN@ placeholders substituted from those variables via
|
||||
# render()/install_rendered() — edit hosts/<hostname>.conf, not the configs.
|
||||
VANLINK_CONF=${VANLINK_CONF:-/etc/vanlink.conf}
|
||||
|
||||
# Must be defined (non-empty) in the host file; deploy aborts naming any missing.
|
||||
REQUIRED_VARS=(
|
||||
WIFI_5G_IFACE WIFI_2G_IFACE LAN_IFACE STARLINK_IFACE WIFI_WAN_IFACE
|
||||
GATEWAY_IP GATEWAY_PREFIX GATEWAY_NETWORK DHCP_RANGE_START DHCP_RANGE_END
|
||||
DNS_RESOLVERS MODEM_USB_VENDOR
|
||||
ENABLE_COCKPIT ENABLE_LI3 ENABLE_NVME_WATCH ENABLE_HA ENABLE_FRIGATE
|
||||
)
|
||||
# May be empty or absent; NVME_* are required only when ENABLE_NVME_WATCH=1.
|
||||
OPTIONAL_VARS=(
|
||||
ONBOARD_LAN_IFACE NVME_DEVICE NVME_FS_DEVICE
|
||||
USB_BACKUP_ROOT_PART USB_BACKUP_MOUNT SD_BACKUP_UUID
|
||||
)
|
||||
# Computed in load_conf; usable as @TOKEN@ like the rest.
|
||||
DERIVED_VARS=(ONBOARD_LAN_UNMANAGED)
|
||||
TOKEN_VARS=("${REQUIRED_VARS[@]}" "${OPTIONAL_VARS[@]}" "${DERIVED_VARS[@]}")
|
||||
TOKEN_RE='@[A-Z][A-Z0-9_]*@'
|
||||
|
||||
die() { echo "deploy: $*" >&2; exit 1; }
|
||||
|
||||
load_conf() { # load_conf <file> — source and validate a host config
|
||||
local f=$1 v flag missing=()
|
||||
[ -e "$f" ] || die "$f not found (missing or dangling symlink). Create it for this host with:
|
||||
sudo ln -sfn \"$PWD/hosts/$(hostname -s).conf\" /etc/vanlink.conf"
|
||||
for v in "${TOKEN_VARS[@]}"; do unset "$v"; done
|
||||
# shellcheck disable=SC1090
|
||||
. "$f"
|
||||
for v in "${REQUIRED_VARS[@]}"; do
|
||||
[ -n "${!v:-}" ] || missing+=("$v")
|
||||
done
|
||||
[ ${#missing[@]} -eq 0 ] || die "$f is missing required variable(s): ${missing[*]}"
|
||||
for flag in ENABLE_COCKPIT ENABLE_LI3 ENABLE_NVME_WATCH ENABLE_HA ENABLE_FRIGATE; do
|
||||
case ${!flag} in
|
||||
0|1) ;;
|
||||
*) die "$f: $flag must be 0 or 1 (got '${!flag}')" ;;
|
||||
esac
|
||||
done
|
||||
if [ "$ENABLE_NVME_WATCH" = 1 ]; then
|
||||
{ [ -n "${NVME_DEVICE:-}" ] && [ -n "${NVME_FS_DEVICE:-}" ]; } \
|
||||
|| die "$f: ENABLE_NVME_WATCH=1 needs NVME_DEVICE and NVME_FS_DEVICE"
|
||||
fi
|
||||
ONBOARD_LAN_UNMANAGED=${ONBOARD_LAN_IFACE:+;interface-name:$ONBOARD_LAN_IFACE}
|
||||
}
|
||||
|
||||
render() { # render <file> -> stdout, with @TOKEN@ placeholders substituted
|
||||
sed -e "s|@WIFI_5G_IFACE@|$WIFI_5G_IFACE|g" \
|
||||
-e "s|@WIFI_2G_IFACE@|$WIFI_2G_IFACE|g" \
|
||||
-e "s|@LAN_USB_IFACE@|$LAN_USB_IFACE|g" \
|
||||
-e "s|@STARLINK_IFACE@|$STARLINK_IFACE|g" \
|
||||
-e "s|@MODEM_USB_VENDOR@|$MODEM_USB_VENDOR|g" \
|
||||
-e "s|@DNS_RESOLVERS@|$DNS_RESOLVERS|g" \
|
||||
-e "s|@GATEWAY_IP@|$GATEWAY_IP|g" \
|
||||
-e "s|@GATEWAY_PREFIX@|$GATEWAY_PREFIX|g" \
|
||||
-e "s|@GATEWAY_NETWORK@|$GATEWAY_NETWORK|g" \
|
||||
-e "s|@DHCP_RANGE_START@|$DHCP_RANGE_START|g" \
|
||||
-e "s|@DHCP_RANGE_END@|$DHCP_RANGE_END|g" \
|
||||
"$1"
|
||||
local args=() v
|
||||
for v in "${TOKEN_VARS[@]}"; do
|
||||
args+=(-e "s|@${v}@|${!v-}|g")
|
||||
done
|
||||
sed "${args[@]}" "$1"
|
||||
}
|
||||
|
||||
check_rendered() { # check_rendered <src> <rendered-file> — fail on a leftover @TOKEN@
|
||||
local left
|
||||
left=$(grep -oE "$TOKEN_RE" "$2" | sort -u | tr '\n' ' ') || true
|
||||
[ -z "$left" ] && return 0
|
||||
echo "deploy: $1: unrendered token(s) after render: $left" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
install_rendered() { # install_rendered <src> <dst> [mode]
|
||||
local tmp
|
||||
tmp=$(mktemp)
|
||||
render "$1" > "$tmp"
|
||||
check_rendered "$1" "$tmp" || { rm -f "$tmp"; exit 1; }
|
||||
install -D -m"${3:-0644}" "$tmp" "$2"
|
||||
rm -f "$tmp"
|
||||
}
|
||||
|
||||
if [ "${1:-}" = "render" ]; then
|
||||
template_files() { # every repo file that carries an @TOKEN@ placeholder
|
||||
grep -rlE "$TOKEN_RE" --exclude-dir=.git --exclude-dir=__pycache__ --exclude-dir=.superpowers \
|
||||
--exclude-dir=docs --exclude-dir=tests --exclude-dir=hosts \
|
||||
--exclude='*.bak' --exclude='*.md' --exclude=deploy.sh . | sed 's|^\./||' | sort
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
--templates)
|
||||
template_files
|
||||
exit 0 ;;
|
||||
--check) # ./deploy.sh --check [conf] — validate conf, render every template, install nothing
|
||||
load_conf "${2:-$VANLINK_CONF}"
|
||||
rc=0
|
||||
tmp=$(mktemp)
|
||||
for f in $(template_files); do
|
||||
render "$f" > "$tmp"
|
||||
check_rendered "$f" "$tmp" || rc=1
|
||||
done
|
||||
rm -f "$tmp"
|
||||
[ "$rc" -eq 0 ] && echo "check OK: $(template_files | wc -l) templates render cleanly"
|
||||
exit "$rc" ;;
|
||||
render)
|
||||
[ -n "${2:-}" ] || { echo "Usage: $0 render <file>"; exit 1; }
|
||||
load_conf "$VANLINK_CONF"
|
||||
render "$2"
|
||||
exit 0
|
||||
fi
|
||||
exit 0 ;;
|
||||
esac
|
||||
|
||||
[ "$(id -u)" = 0 ] || { echo "Run with sudo (writes to /etc, /usr)."; exit 1; }
|
||||
load_conf "$VANLINK_CONF"
|
||||
echo "== host config: $(readlink -f "$VANLINK_CONF") =="
|
||||
|
||||
# Check for essential package dependencies
|
||||
echo "== dependency check =="
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# hosts/van.conf — FriendlyElec NanoPi R5C, hostname "van" (Armbian).
|
||||
# /etc/vanlink.conf -> this file. See hosts/vanlink.conf.example for the variable list.
|
||||
|
||||
WIFI_5G_IFACE=wlp1s0 # MediaTek MT7925E (PCIe); also carries the 2.4GHz AP
|
||||
WIFI_2G_IFACE=wlx3c3332002066
|
||||
LAN_IFACE=enP1p17s0
|
||||
STARLINK_IFACE=enP2p33s0
|
||||
WIFI_WAN_IFACE=wlan0 # not present on this host (kept for the failover config)
|
||||
ONBOARD_LAN_IFACE=
|
||||
|
||||
GATEWAY_IP="10.42.1.1"
|
||||
GATEWAY_PREFIX="24"
|
||||
GATEWAY_NETWORK="10.42.1.0/24"
|
||||
DHCP_RANGE_START="10.42.1.10"
|
||||
DHCP_RANGE_END="10.42.1.254"
|
||||
DNS_RESOLVERS="1.1.1.1 8.8.8.8"
|
||||
|
||||
MODEM_USB_VENDOR=2c7c
|
||||
NVME_DEVICE=
|
||||
NVME_FS_DEVICE=
|
||||
|
||||
USB_BACKUP_ROOT_PART=
|
||||
USB_BACKUP_MOUNT=
|
||||
SD_BACKUP_UUID=39118305-4927-47a4-acc7-ba022f37af22
|
||||
|
||||
ENABLE_COCKPIT=0
|
||||
ENABLE_LI3=1
|
||||
ENABLE_NVME_WATCH=0 # no NVMe on this board
|
||||
ENABLE_HA=1
|
||||
ENABLE_FRIGATE=1
|
||||
@@ -0,0 +1,41 @@
|
||||
# hosts/vanlink.conf.example — reference for /etc/vanlink.conf.
|
||||
#
|
||||
# Each host has hosts/<hostname>.conf (hostname -s) in this repo, and
|
||||
# /etc/vanlink.conf is a symlink to it:
|
||||
# sudo ln -sfn "$PWD/hosts/$(hostname -s).conf" /etc/vanlink.conf
|
||||
# Plain KEY=value (it is sourced by bash). NO SECRETS — this file is in git.
|
||||
# Validate without root: ./deploy.sh --check hosts/<hostname>.conf
|
||||
# Find interface names with `ip -br link` (wired) / `iw dev` (wifi); USB IDs with lsusb.
|
||||
|
||||
# --- Interfaces -------------------------------------------------------------
|
||||
WIFI_5G_IFACE=wlp1s0 # 5GHz AP radio (hostapd)
|
||||
WIFI_2G_IFACE=wlx000000000000 # 2.4GHz AP radio (hostapd-2g)
|
||||
LAN_IFACE=enp1s0 # wired LAN port, bridged into br0
|
||||
STARLINK_IFACE=enp2s0 # Starlink uplink (NetworkManager WAN)
|
||||
WIFI_WAN_IFACE=wlan0 # Wi-Fi uplink (NetworkManager WAN)
|
||||
ONBOARD_LAN_IFACE= # optional: extra onboard LAN port bridged into br0 (e.g. eth0); empty = none
|
||||
|
||||
# --- Addressing ---------------------------------------------------------------
|
||||
GATEWAY_IP="10.42.9.1"
|
||||
GATEWAY_PREFIX="24"
|
||||
GATEWAY_NETWORK="10.42.9.0/24"
|
||||
DHCP_RANGE_START="10.42.9.10"
|
||||
DHCP_RANGE_END="10.42.9.254"
|
||||
DNS_RESOLVERS="1.1.1.1 8.8.8.8"
|
||||
|
||||
# --- Hardware -----------------------------------------------------------------
|
||||
MODEM_USB_VENDOR=2c7c # cellular modem USB vendor id
|
||||
NVME_DEVICE= # only when ENABLE_NVME_WATCH=1, e.g. nvme0
|
||||
NVME_FS_DEVICE= # only when ENABLE_NVME_WATCH=1, e.g. /dev/nvme0n1p2
|
||||
|
||||
# --- Backup (read by failover/sync-*-backup.sh; leave empty if unused) ----------
|
||||
USB_BACKUP_ROOT_PART= # sync-usb-backup.sh: partition to mount, e.g. /dev/sda2
|
||||
USB_BACKUP_MOUNT= # sync-usb-backup.sh: local mount point, or host:/path for NFS
|
||||
SD_BACKUP_UUID= # sync-sd-backup.sh: filesystem UUID of the backup SD card
|
||||
|
||||
# --- Feature flags (0 or 1) ---------------------------------------------------
|
||||
ENABLE_COCKPIT=0 # Cockpit "Van Router" plugin
|
||||
ENABLE_LI3=0 # Li3 house-battery BLE -> MQTT service
|
||||
ENABLE_NVME_WATCH=0 # NVMe timeout watchdog (needs an NVMe drive)
|
||||
ENABLE_HA=0 # Home Assistant + ESPHome (podman quadlets)
|
||||
ENABLE_FRIGATE=0 # Frigate NVR (podman quadlet)
|
||||
@@ -0,0 +1,30 @@
|
||||
# hosts/wan.conf — Raspberry Pi 5, hostname "wan".
|
||||
# /etc/vanlink.conf -> this file. See hosts/vanlink.conf.example for the variable list.
|
||||
|
||||
WIFI_5G_IFACE=wlxc83a35a4ee55 # Realtek RTL8852BU, rtw89
|
||||
WIFI_2G_IFACE=wlxf4f26d1760a7 # D-Link DWA-171, RTL8821CU
|
||||
LAN_IFACE=enx00e04c331140
|
||||
STARLINK_IFACE=enx3c8cf861752b # TrendNet USB dongle
|
||||
WIFI_WAN_IFACE=wlan0
|
||||
ONBOARD_LAN_IFACE=eth0
|
||||
|
||||
GATEWAY_IP="10.42.0.1"
|
||||
GATEWAY_PREFIX="24"
|
||||
GATEWAY_NETWORK="10.42.0.0/24"
|
||||
DHCP_RANGE_START="10.42.0.10"
|
||||
DHCP_RANGE_END="10.42.0.254"
|
||||
DNS_RESOLVERS="1.1.1.1 8.8.8.8"
|
||||
|
||||
MODEM_USB_VENDOR=2c7c
|
||||
NVME_DEVICE=
|
||||
NVME_FS_DEVICE=
|
||||
|
||||
USB_BACKUP_ROOT_PART=/dev/sda2
|
||||
USB_BACKUP_MOUNT=10.42.0.44:/media/andreas/writable-nvme
|
||||
SD_BACKUP_UUID=
|
||||
|
||||
ENABLE_COCKPIT=0
|
||||
ENABLE_LI3=0
|
||||
ENABLE_NVME_WATCH=0 # boots from mmcblk0; no NVMe fitted
|
||||
ENABLE_HA=1
|
||||
ENABLE_FRIGATE=0
|
||||
@@ -9,7 +9,7 @@
|
||||
"poll_interval_s": 5,
|
||||
"wifi_5g_iface": "@WIFI_5G_IFACE@",
|
||||
"wifi_2g_iface": "@WIFI_2G_IFACE@",
|
||||
"lan_usb_iface": "@LAN_USB_IFACE@",
|
||||
"lan_usb_iface": "@LAN_IFACE@",
|
||||
"starlink_iface": "@STARLINK_IFACE@",
|
||||
"starlink_dish": "192.168.100.1:9200",
|
||||
"modem_usb_vendor": "@MODEM_USB_VENDOR@",
|
||||
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
# Static checks for the host-config templating. No root needed.
|
||||
# ./tests/check-templates.sh
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$(readlink -f "$0")")/.."
|
||||
|
||||
fail=0
|
||||
ok() { echo "ok - $*"; }
|
||||
bad() { echo "FAIL - $*"; fail=1; }
|
||||
|
||||
# 1. shell syntax
|
||||
for f in deploy.sh failover/sync-usb-backup.sh failover/sync-sd-backup.sh tests/check-templates.sh; do
|
||||
if bash -n "$f"; then ok "bash -n $f"; else bad "bash -n $f"; fi
|
||||
done
|
||||
|
||||
# 3. every host file passes deploy.sh --check (validates vars, renders all templates)
|
||||
for c in hosts/*.conf; do
|
||||
if out=$(./deploy.sh --check "$c" 2>&1); then
|
||||
ok "--check $c"
|
||||
else
|
||||
bad "--check $c"; echo "$out" | sed 's/^/ /'
|
||||
fi
|
||||
done
|
||||
|
||||
# 4. the example defines exactly the variables deploy.sh knows about
|
||||
known=$(sed -n '/^REQUIRED_VARS=(/,/^)/p;/^OPTIONAL_VARS=(/,/^)/p' deploy.sh \
|
||||
| grep -vE '^[A-Z_]+=\(|^\)' | tr -s ' \t' '\n' | grep -v '^$' | sort || true)
|
||||
defined=$(grep -oE '^[A-Z][A-Z0-9_]*=' hosts/vanlink.conf.example 2>/dev/null | tr -d = | sort || true)
|
||||
if [ -n "$known" ] && [ "$known" = "$defined" ]; then
|
||||
ok "hosts/vanlink.conf.example defines exactly the variables deploy.sh knows"
|
||||
else
|
||||
bad "hosts/vanlink.conf.example differs from deploy.sh's variable lists"
|
||||
diff <(echo "$known") <(echo "$defined") | sed 's/^/ /' || true
|
||||
fi
|
||||
|
||||
exit "$fail"
|
||||
Reference in New Issue
Block a user