From 83974f731d27fb34c1ead2757064748eaf76e28b Mon Sep 17 00:00:00 2001 From: Andreas Wrede Date: Wed, 23 Sep 2026 14:34:13 -0400 Subject: [PATCH] deploy: feature flags, --diff, tolerate failed restarts Li3, NVMe watchdog, Home Assistant/ESPHome, Frigate and Cockpit are installed, enabled and restarted only when their ENABLE_* flag is 1. --diff shows what would change in /etc. A unit that fails to restart is recorded as a warning instead of aborting the deploy. Co-Authored-By: Claude Sonnet 5 --- deploy.sh | 114 +++++++++++++++++++++++++++------------ tests/check-templates.sh | 28 ++++++++++ 2 files changed, 107 insertions(+), 35 deletions(-) diff --git a/deploy.sh b/deploy.sh index d93d622..9a14a46 100755 --- a/deploy.sh +++ b/deploy.sh @@ -105,6 +105,17 @@ case "${1:-}" in rm -f "$tmp" [ "$rc" -eq 0 ] && echo "check OK: $(template_files | wc -l) templates render cleanly" exit "$rc" ;; + --diff) # ./deploy.sh --diff [conf] — what would change in /etc, without installing + load_conf "${2:-$VANLINK_CONF}" + grep -E '^[[:space:]]*install_rendered +[^" ]+ +/' deploy.sh | while read -r _ src dst _; do + if [ ! -e "$dst" ]; then + echo "NEW $dst" + elif ! diff -q <(render "$src") "$dst" >/dev/null; then + echo "CHANGED $dst" + diff -u "$dst" <(render "$src") | sed 's/^/ /' || true + fi + done + exit 0 ;; render) [ -n "${2:-}" ] || { echo "Usage: $0 render "; exit 1; } load_conf "$VANLINK_CONF" @@ -135,8 +146,10 @@ REQUIRED_PACKAGES=( python3-paho-mqtt mobile-broadband-provider-info zerotier-one - podman ) +if [ "$ENABLE_HA" = 1 ]; then + REQUIRED_PACKAGES+=(podman) +fi OPTIONAL_PACKAGES=( ) for pkg in "${REQUIRED_PACKAGES[@]}"; do @@ -175,6 +188,12 @@ write_warnings() { } > "$WARNINGS_FILE" } +# A host file with uncommitted edits means this deploy uses unreviewed values. +conf_real=$(readlink -f "$VANLINK_CONF") +if git -c safe.directory='*' -C "$(dirname "$conf_real")" status --porcelain -- "$conf_real" 2>/dev/null | grep -q .; then + warn "$conf_real has uncommitted changes — commit it so the host config stays reviewable" +fi + echo "== netplan drift check ==" # Netplan is never installed by this script (applying it flaps the uplinks — # see note below), so it's easy to edit ap/50-van-wan.yaml and forget the @@ -300,7 +319,7 @@ if [ "$ENABLE_COCKPIT" = 1 ]; then done # Bridge fd headroom (Python bridge frees spawn pipes only at GC; 1024 is too tight) install -D -m0644 cockpit/cockpit-session-nofile.conf /etc/systemd/system/cockpit-session@.service.d/nofile.conf -fi +fi # The Starlink card queries the dish's gRPC API; grpcurl isn't packaged in apt. command -v grpcurl >/dev/null 2>&1 \ @@ -321,7 +340,6 @@ python3 -c 'import paho.mqtt' 2>/dev/null \ install -D -m0644 router-status/mosquitto-listener.conf /etc/mosquitto/conf.d/van-listener.conf # Restart limit drop-ins: prevent infinite restart loops that trigger hardware watchdog install -D -m0644 router-status/van-gps-owntracks-restart-limit.conf /etc/systemd/system/van-gps-owntracks.service.d/restart-limit.conf -install -D -m0644 li3/van-li3-battery-restart-limit.conf /etc/systemd/system/van-li3-battery.service.d/restart-limit.conf echo "== gps (cellular modem GNSS -> gpsd) ==" if dpkg -s gpsd >/dev/null 2>&1; then @@ -344,20 +362,23 @@ python3 -c 'import gps' 2>/dev/null \ python3 -c 'import paho.mqtt' 2>/dev/null \ || warn "python3-paho-mqtt missing (apt install python3-paho-mqtt) — van-gps-owntracks won't start" -echo "== li3 (RV house battery BMS -> MQTT/HA) ==" -# NOT the laptop's own AC/battery monitor — that's power/van-battery (different -# hardware, different concern). This is the Lithionics Li3 12V LiFePO4 house -# battery, over its BLE HM-10 UART module (no pairing) -> Home Assistant MQTT -# discovery. Broker + creds live only on the system (0600, seeded from -# li3/config.json.example — same pattern as gps/pushover). -install -D -m0755 li3/van-li3-battery /usr/local/sbin/van-li3-battery -install -D -m0644 li3/van-li3-battery.service /etc/systemd/system/van-li3-battery.service -if [ ! -f /etc/van-li3/config.json ]; then - install -D -m0600 li3/config.json.example /etc/van-li3/config.json - warn "seeded /etc/van-li3/config.json (EDIT IT: add MQTT username + password)" +if [ "$ENABLE_LI3" = 1 ]; then + echo "== li3 (RV house battery BMS -> MQTT/HA) ==" + # NOT the laptop's own AC/battery monitor — that's power/van-battery (different + # hardware, different concern). This is the Lithionics Li3 12V LiFePO4 house + # battery, over its BLE HM-10 UART module (no pairing) -> Home Assistant MQTT + # discovery. Broker + creds live only on the system (0600, seeded from + # li3/config.json.example — same pattern as gps/pushover). + install -D -m0755 li3/van-li3-battery /usr/local/sbin/van-li3-battery + install -D -m0644 li3/van-li3-battery.service /etc/systemd/system/van-li3-battery.service + install -D -m0644 li3/van-li3-battery-restart-limit.conf /etc/systemd/system/van-li3-battery.service.d/restart-limit.conf + if [ ! -f /etc/van-li3/config.json ]; then + install -D -m0600 li3/config.json.example /etc/van-li3/config.json + warn "seeded /etc/van-li3/config.json (EDIT IT: add MQTT username + password)" + fi + python3 -c 'import bleak' 2>/dev/null \ + || warn "python3-bleak missing (apt install python3-bleak) — van-li3-battery won't start" fi -python3 -c 'import bleak' 2>/dev/null \ - || warn "python3-bleak missing (apt install python3-bleak) — van-li3-battery won't start" echo "== thermal monitor ==" install -D -m0755 power/van-thermal /usr/local/sbin/van-thermal @@ -370,24 +391,30 @@ if [ ! -f /etc/van-battery/pushover.json ]; then warn "seeded /etc/van-battery/pushover.json (EDIT IT: add Pushover token + user)" fi -echo "== nvme watchdog ==" -install -D -m0755 power/van-nvme-watch /usr/local/sbin/van-nvme-watch -install_rendered power/nvme-watch-config.json /etc/van-nvme-watch/config.json -install -D -m0644 power/van-nvme-watch.service /etc/systemd/system/van-nvme-watch.service +if [ "$ENABLE_NVME_WATCH" = 1 ]; then + echo "== nvme watchdog ==" + install -D -m0755 power/van-nvme-watch /usr/local/sbin/van-nvme-watch + install_rendered power/nvme-watch-config.json /etc/van-nvme-watch/config.json + install -D -m0644 power/van-nvme-watch.service /etc/systemd/system/van-nvme-watch.service +fi echo "== home assistant ==" # Native HA (Podman Quadlet, replaced the ha_van VM). daemon-reload below # regenerates homeassistant.service; started (not restarted) at the end so a # deploy never bounces HA — after editing the .container, restart it manually. -install -D -m0644 ha/homeassistant.container /etc/containers/systemd/homeassistant.container -install -d -m0755 /srv/homeassistant -# ESPHome dashboard (sibling container, same rationale as HA above — no -# Supervisor/add-on store here). -install -D -m0644 ha/esphome.container /etc/containers/systemd/esphome.container -install -d -m0755 /srv/esphome +if [ "$ENABLE_HA" = 1 ]; then + install -D -m0644 ha/homeassistant.container /etc/containers/systemd/homeassistant.container + install -d -m0755 /srv/homeassistant + # ESPHome dashboard (sibling container, same rationale as HA above — no + # Supervisor/add-on store here). + install -D -m0644 ha/esphome.container /etc/containers/systemd/esphome.container + install -d -m0755 /srv/esphome +fi # Frigate NVR (sibling container, same rationale as HA/ESPHome above). -install -D -m0644 ha/frigate.container /etc/containers/systemd/frigate.container -install -d -m0755 /srv/frigate/config /srv/frigate/storage +if [ "$ENABLE_FRIGATE" = 1 ]; then + install -D -m0644 ha/frigate.container /etc/containers/systemd/frigate.container + install -d -m0755 /srv/frigate/config /srv/frigate/storage +fi echo "== hardware watchdog ==" install -D -m0644 power/10-vanlink-watchdog.conf /etc/systemd/system.conf.d/10-vanlink-watchdog.conf @@ -410,24 +437,41 @@ systemctl mask systemd-networkd-wait-online.service >/dev/null 2>&1 || true systemctl unmask hostapd >/dev/null 2>&1 || true # Disable system dnsmasq.service to avoid port 53 conflicts — van-ap-dnsmasq owns AP DHCP systemctl disable dnsmasq.service >/dev/null 2>&1 || true -systemctl enable regdomain.service van-br0-setup.service hostapd hostapd-2g van-ap-dnsmasq nftables systemd-networkd van-failover van-thermal van-nvme-watch van-ap-watchdog van-ap-watchdog-2g van-wlan-watchdog van-modem-watch van-sms-watch van-gps-owntracks van-li3-battery van-router-mqtt >/dev/null 2>&1 || true +ENABLE_UNITS=(regdomain.service van-br0-setup.service hostapd hostapd-2g van-ap-dnsmasq nftables systemd-networkd van-failover van-thermal van-ap-watchdog van-ap-watchdog-2g van-wlan-watchdog van-modem-watch van-sms-watch van-gps-owntracks van-router-mqtt) +if [ "$ENABLE_NVME_WATCH" = 1 ]; then + ENABLE_UNITS+=(van-nvme-watch) +fi +if [ "$ENABLE_LI3" = 1 ]; then + ENABLE_UNITS+=(van-li3-battery) +fi +systemctl enable "${ENABLE_UNITS[@]}" >/dev/null 2>&1 || true # bluetooth: host BlueZ serves the onboard hci0 to the HA container over D-Bus systemctl enable --now bluetooth >/dev/null 2>&1 || true -systemctl start homeassistant || warn "homeassistant failed to start (podman/quadlet — check journalctl -u homeassistant)" -systemctl start esphome || warn "esphome failed to start (podman/quadlet — check journalctl -u esphome)" -systemctl start frigate || warn "frigate failed to start (podman/quadlet — check journalctl -u frigate)" +if [ "$ENABLE_HA" = 1 ]; then + systemctl start homeassistant || warn "homeassistant failed to start (podman/quadlet — check journalctl -u homeassistant)" + systemctl start esphome || warn "esphome failed to start (podman/quadlet — check journalctl -u esphome)" +fi +if [ "$ENABLE_FRIGATE" = 1 ]; then + systemctl start frigate || warn "frigate failed to start (podman/quadlet — check journalctl -u frigate)" +fi systemctl restart van-thermal -systemctl restart van-nvme-watch +if [ "$ENABLE_NVME_WATCH" = 1 ]; then + systemctl restart van-nvme-watch +fi systemctl restart van-sms-watch systemctl restart van-gps-owntracks -systemctl restart van-li3-battery +if [ "$ENABLE_LI3" = 1 ]; then + systemctl restart van-li3-battery +fi systemctl restart van-router-mqtt # Pick up unmanaged-devices changes so NM releases/keeps the right interfaces. nmcli general reload 2>/dev/null || systemctl reload NetworkManager 2>/dev/null || true # restart in dependency order: bridge + members first, then hostapd enslaves the # radios, then dnsmasq binds br0, then NAT/failover systemctl restart systemd-networkd -systemctl restart van-ap-dnsmasq nftables van-failover van-wlan-watchdog +for u in van-ap-dnsmasq nftables van-failover van-wlan-watchdog; do + systemctl restart "$u" || warn "$u failed to restart (journalctl -xeu $u)" +done # The AP radios live on the USB hub and may be absent; the start then fails but # Restart=always keeps retrying and claims them the moment they enumerate. systemctl restart hostapd hostapd-2g \ diff --git a/tests/check-templates.sh b/tests/check-templates.sh index 4e70721..f1a62fa 100755 --- a/tests/check-templates.sh +++ b/tests/check-templates.sh @@ -69,4 +69,32 @@ else ok "van: unmanaged-devices has no eth0" fi +# 6. flagged units/paths only appear inside their ENABLE_* guard in deploy.sh. +# Convention: guards are column-0 `if [ "$ENABLE_X" = 1 ]; then` ... `fi`, +# contents indented (so a nested `fi` never sits in column 0). +if awk ' + BEGIN { + need["van-li3-battery"]="ENABLE_LI3" + need["van-nvme-watch"]="ENABLE_NVME_WATCH" + need["nvme-watch-config"]="ENABLE_NVME_WATCH" + need["homeassistant"]="ENABLE_HA" + need["esphome"]="ENABLE_HA" + need["frigate"]="ENABLE_FRIGATE" + need["cockpit"]="ENABLE_COCKPIT" + } + /^if \[ "\$ENABLE_[A-Z0-9_]+" = 1 \]/ { match($0, /ENABLE_[A-Z0-9_]+/); guard=substr($0, RSTART, RLENGTH); next } + /^fi([[:space:]]|$)/ { guard=""; next } + /^[[:space:]]*(#|echo)/ { next } + { for (u in need) if (index($0, u) && guard != need[u]) { + printf " deploy.sh:%d mentions %s outside an %s guard: %s\n", NR, u, need[u], $0; bad=1 } } + END { exit bad } +' deploy.sh; then ok "flagged units are only touched inside their ENABLE_* guard"; else bad "flag gating"; fi + +# 8. ./deploy.sh --diff runs (no root) and lists rendered files +if out=$(./deploy.sh --diff hosts/van.conf 2>&1) && echo "$out" | grep -qE '^(NEW|CHANGED) '; then + ok "deploy.sh --diff hosts/van.conf" +else + bad "deploy.sh --diff hosts/van.conf"; echo "$out" | sed 's/^/ /' +fi + exit "$fail"