Commit Graph
18 Commits
Author SHA1 Message Date
Andreas WredeandClaude Sonnet 5 a99a39a859 ap: fix driver_name() silently caching wrong value on startup race
Path.resolve() doesn't raise on a nonexistent path — it just returns the
syntactic path unchanged — so when driver_name() ran before the radio's
netdev had enumerated (startup race with USB re-enum), it silently
returned the literal string "driver" instead of None. The loop's retry
guard (`if driver is None: retry`) never fired since "driver" is truthy,
so the bogus value was cached for the service's entire uptime and the
queue-flush wedge regex could never match real driver names — the 3rd
check added 2026-08-19 was silently inert. Fixes 2026-08-23 recurrence
where the 5GHz SSID went invisible and hostapd never got restarted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJfEELeh3ercpRBp8yYrYS
2026-08-23 14:49:27 -04:00
Andreas WredeandClaude Sonnet 5 6d5184e2e7 failover: ipv4.never-default + per-WAN probe scheduling; tune probe cadence
van-failover is now the sole owner of default-route selection: netplan/NM
profiles get ipv4.never-default so NM's own DHCP/lease renewals stop
reinstalling competing default routes (was racing van-failover's enforce_route
on Starlink's 16s lease). set_profile_metric() also pins never-default on
NM-managed connections it doesn't own the netplan source for (e.g. cellular),
and nm_gateway() falls back to the raw DHCP4 lease's `routers` option since NM
stops populating IP4.GATEWAY once never-default is set.

Probing moved from one shared round to independent per-WAN retry scheduling,
so a flaky WAN retries on its own clock instead of dragging healthy WANs into
extra probes or throttling a failing one to the slow steady-state cadence.

config.json: probe_interval 4->60, fail/ok_threshold 3/2->2/1, single probe
URL — verified live across this reboot (cellular took ~2min after NM reported
"activated" to actually pass traffic; van-failover correctly withheld/
deprioritized the default route until then, then installed it automatically).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 14:22:46 -04:00
Andreas WredeandClaude Sonnet 5 8efb8dba5f dns: fixed 1.1.1.1/8.8.8.8 lockdown + scoped mDNS, esphome sibling container
- deploy.conf: DNS_RESOLVERS, always 1.1.1.1/8.8.8.8, never a WAN's own
  DHCP/RA-provided servers (previously whatever Wapana handed out).
- ap/99-van-router-dns.conf: global resolved config (fixed DNS, Domains=~.,
  global MulticastDNS=yes — a prerequisite for any per-link mDNS to work at
  all, not just an on/off toggle).
- failover/60-van-wan-dns: NM dispatcher that strips each WAN's DNS/search-
  domain and disables its mDNS via resolvectl on every connect/lease event
  (NM's own ipv4/ipv6.ignore-auto-dns can't be set as a config-file default —
  confirmed rejected as an unknown key — so this enforces it directly
  instead), retried over ~5s to beat NM's own async DNS commit. Also logs
  what each WAN advertised, never used, to /run/van-wan-dns/.
- ap/21-van-br0.network: MulticastDNS=yes, scoped to the van's own LAN only
  — .local/mDNS now resolves for ESPHome and other LAN devices without
  leaking mDNS onto Wapana/Starlink/cellular.
- dns/: ZeroTier-managed DNS (zt.wrede.pvt) made reproducible — installed
  the official zerotier-systemd-manager package (verified against upstream
  checksums), additive to the above so *.zt.wrede.pvt keeps resolving over
  the overlay independent of WAN.
- ha/esphome.container: ESPHome dashboard as a sibling Podman Quadlet to
  Home Assistant, same host-network/config-volume pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 16:09:01 -04:00
Andreas WredeandClaude Sonnet 5 5d88e1b30c deploy: hardware-instance templating, wlan0 boot watchdog, deploy-time warnings in Cockpit
- deploy.conf templates interface names/USB IDs (@TOKEN@ substitution) across
  ap/* configs so a dongle swap only needs deploy.conf edited, not the repo
  configs themselves; drops ap/rtw88.conf (old 2.4GHz dongle retired for the
  DWA-171, which needs no such power-save override).
- failover/van-wlan-watchdog: recovers wlan0 from NM's post-boot no-secrets
  wedge (a boot-time supplicant race, not a real credential failure).
- deploy.sh: warn() collects dependency/config warnings (missing python3-gps,
  python3-paho-mqtt, mobile-broadband-provider-info, grpcurl, gpsd; netplan
  drift; unedited example configs) into /var/lib/vanlink/deploy-warnings.json,
  rendered as an amber Cockpit card so they're visible without reading deploy
  output.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 11:25:11 -04:00
Andreas WredeandClaude Fable 5 7c36757d35 sysctl: correct the use_tempaddr comment with the real root cause
The rotating privacy address was not itself the packet-loss mechanism:
the 2026-07-13 discrimination experiment (4 flows, dual-ended capture)
showed the loss keyed to hbc's exact 5-tuple — upstream per-flow state
poisoned when the flow was created during boot/apply address churn, kept
alive forever by the 10s heartbeat cadence. Fresh flows to the same
host/port were clean; restarting hbc fixed it instantly. use_tempaddr=0
stays: it removes one source of the churn that poisons newborn flows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NuPg8vz6FmDUj4SgEJ525C
2026-07-13 13:13:08 -04:00
Andreas WredeandClaude Sonnet 5 09259eb6cc sysctl: force use_tempaddr=0 to beat Ubuntu's 55-ipv6-privacy.conf default
Root cause of the flow-selective IPv6 heartbeat loss investigated
2026-07-12/13: Ubuntu ships /usr/lib/sysctl.d/55-ipv6-privacy.conf with
use_tempaddr=2 system-wide. Nothing on wlan0 overrode it before this
morning's netplan fix, so the interface carried a rotating privacy
address alongside the stable one. hbc's long-lived flow eventually
straddled a temp-address deprecation event mid-flight, which looked
exactly like random upstream packet loss (in-transit, IPv6-only,
flow-selective) and cost a day of packet captures before the cause
turned out to be this default fighting the WAN config instead.

The netplan/NM ipv6.ip6-privacy=false fix already forces this per
connection, but that's a timing-dependent override racing a package
default. This makes it explicit and permanent: 99- loads after (and
wins over) 55- the next time sysctl --system runs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-13 12:06:33 -04:00
andreas 4aae62372e deument last change 2026-07-12 12:22:45 -04:00
Andreas WredeandClaude Fable 5 a36b64c7c4 lan: repurpose onboard eth0 as an internal LAN port on br0
With wifi + Starlink + cellular as uplinks, the onboard GbE earns its keep
as a wired LAN port instead of the metric-150 ethernet WAN: eth0 leaves
netplan/NM (added to van-ap-unmanaged.conf) and joins br0 via networkd
(23-van-lan-eth0.network), so wired clients get 10.42.0.x DHCP/DNS and NAT
exactly like Wi-Fi clients. The eth WAN is gone from van-failover's config.

Cutover notes: delete the old netplan-eth0 / stray eth0 NM profiles, then
nmcli general reload + networkctl reload/reconfigure eth0. NEVER cable this
port (or the USB LAN dongle) back into an upstream LAN — dnsmasq on br0
would serve rogue DHCP there; STP stays off, so don't cable both wired LAN
ports to the same switch either.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 07:44:44 -04:00
Andreas WredeandClaude Fable 5 751facd511 starlink: dish route + status card on the Van Router page
The Starlink RTL8153 gets a declarative NM profile in 50-van-wan.yaml
(replaces the auto 'Wired connection 1') with a 192.168.100.1/32 link route,
so the dish's management address stays reachable from the router and — via
the existing !br0 masquerade — from the van LAN, regardless of which WAN
holds the default route.

New Cockpit "Starlink" card queries the dish's gRPC API (:9200, get_status
via grpcurl — not packaged in apt, deploy.sh warns when missing) inside the
existing single batched spawn: online/obstructed/outage pill, alerts,
uptime, sw version, PoP latency, down/up throughput, obstruction %.
Sentinels distinguish adapter-absent / no-grpcurl / dish-unreachable.

Gotcha captured while cutting over: a WAN profile without ipv4.route-metric
makes NM re-assert its DHCP default (metric ~101) against van-failover's
enforce_route pruning every cycle; the profile metric must match (failover's
set_profile_metric does this on health transitions).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 07:06:43 -04:00
Andreas WredeandClaude Fable 5 3add42a0cb ha: replace the HAOS VM with a native Podman Quadlet container
The fixed 2 GiB ha_van allocation starved the 4 GB Pi. HA Container now
runs on the host network (http://10.42.0.1:8123): ha/homeassistant.container
installs to /etc/containers/systemd/, config in /srv/homeassistant, host
D-Bus mounted for onboard Bluetooth (needs apparmor=unconfined — Ubuntu's
dbus-daemon mediates per AppArmor label and denies AddMatch to BlueZ —
plus NET_ADMIN/NET_RAW for habluetooth adapter recovery).

Drop the VM-era plumbing: the 10.42.0.50 dhcp-host pin becomes a
host-record for 10.42.0.1, and the legacy-URL DNAT + hairpin masquerade
go away entirely. ha_van.xml retired to git history.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:46:30 -04:00
Andreas WredeandClaude Fable 5 c40662361f ha: move the Home Assistant VM onto the Pi as an aarch64 guest
The x86 ha_van stayed behind on wayback at the port (9bf1420 dropped
ha/ and its DNAT/lease); with the hub moved and the Pi owning
10.42.0.0/24, HA follows. New domain XML: machine=virt + AAVMF EFI,
virtio-scsi HAOS 18.1 aarch64 image, 2 GiB (data restored from an HA
full backup — the x86 qcow2 can't cross architectures). Same MAC, so
the dnsmasq pin (10.42.0.50 / "homeassistant") and the legacy
10.42.0.1:8123 DNAT + br0 hairpin port back verbatim.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 19:19:56 -04:00
Andreas WredeandClaude Fable 5 9bf142074a port to Pi 4 'wan': onboard eth0+wlan0 as NM WANs, AP stack verbatim
The USB hub (5GHz + 2.4GHz AP dongles, Starlink + LAN RTL8153s) moves over
from wayback; MAC-derived wlx*/enx* names travel with it, so hostapd/
networkd/cockpit configs are unchanged. Pi diffs only: failover WAN list
(wlan0 wifi 100, eth0 150, starlink USB 200, Koodo 300), cpu_thermal
sensor, bcm2835 watchdog 10s, no HA DNAT/lease, and deploy.sh drops
battery/lid/heartbeat/ZT-dns. Netplan reference in ap/50-van-wan.yaml.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 16:59:34 -04:00
Andreas WredeandClaude Fable 5 e652b0f7bd ap: parallel 2.4GHz AP (hostapd-2g) on the Linksys WUSB6300v2 dongle
Second radio (wlxd8ec5e2faa8c, RTL8822BU/rtw88), same VanLink SSID+PSK,
ch6 HT20, bridged into br0 next to the 5GHz AP. Runs as its own hostapd
instance + own watchdog so a USB wedge on one radio never darkens the
other; van-ap-watchdog now takes conf path + unit name as args (defaults
unchanged). rtw88.conf disables deep power-save (rtw89 gotcha analog)
and the driver's USB2->3 self-upgrade (USB3 noise in the 2.4GHz band —
note: dongle currently sits in a USB3 port, so it still enumerates
SuperSpeed; move to a USB2 port if 2.4GHz range disappoints).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 10:03:16 -04:00
Andreas WredeandClaude Fable 5 a7819edbc2 ha: bridge the Home Assistant VM onto br0 as a first-class LAN device
The VM lived on libvirt's default NAT net (192.168.122.50) behind a
/etc/libvirt/hooks/network hook that inserted FORWARD accepts above
libvirt's REJECT. libvirtd restarts re-inserted its chains on top
without firing the hook, silently cutting clients off from HA.

Now the VM's NIC (virtio, was e1000) bridges into br0: pinned lease
10.42.0.50 / name 'homeassistant' via van-ap dnsmasq, mDNS discovery
works, and no firewall hook can rot. Legacy http://10.42.0.1:8123
kept working via DNAT + same-subnet hairpin masquerade. Hook, libvirt
DHCP reservation, and iptables rules removed; domain XML in ha/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 09:38:53 -04:00
Andreas WredeandClaude Opus 4.8 4117802f69 ap: watchdog checks link ground-truth, not just hostapd state
After a USB re-enumeration the rtw89 radio can come back as a fresh DOWN
netdev, dropped from br0, while the still-running hostapd never notices and
never exits — so Restart=always never fires. hostapd_cli keeps answering
state=ENABLED off stale in-memory state, so the watchdog considered the AP
healthy and left it dark (observed 2026-07-05: AP down ~16:41 until manual
hostapd restart).

Add link_healthy(): require the netdev to be operstate=up and still a port of
its bridge, alongside the existing state=ENABLED check. That catches the wedged
case the control socket can't see.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:56:12 -04:00
Andreas WredeandClaude Opus 4.8 877fa14d0c ap: keep AP beaconing across USB re-enumeration
Starlink's USB ethernet shares a hub with the rtw89 Wi-Fi adapter; when Starlink
flaps, the hub re-enumerates and tears the radio down, so hostapd loses its
interface and exits. Two stock defaults left the AP dark: Restart=on-failure
misses clean exits, and the 5-in-10s start limit makes systemd give up during a
re-enumeration storm.

- hostapd-restart.conf drop-in: Restart=always, RestartSec=5, StartLimitIntervalSec=0
  so hostapd retries forever until the interface returns (verified: SIGKILL -> back
  to state=ENABLED in ~7s).
- van-ap-watchdog daemon: backstop for the case systemd can't see (hostapd running
  but radio wedged). Polls hostapd_cli status; restarts hostapd if the iface is
  present but not ENABLED, waits it out if the iface is mid-re-enumeration.
- hostapd.conf: add ctrl_interface so the watchdog can read the real AP state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:36:14 -04:00
Andreas WredeandClaude Opus 4.8 830ef52641 ap: bridge wired LAN port (enx00e04c331140) into br0 with the AP
Add a wired LAN port on the same 10.42.0.0/24 segment as VanLink WiFi by
bridging both into br0. The gateway IP, dnsmasq, and NAT move off the wlan name
onto br0; hostapd (not networkd) enslaves the wlan via bridge=br0, since a
station-mode wlan can't be bridged until it's in AP mode. NM leaves the wired
port unmanaged so networkd owns it. Failover/Starlink (enxd8ec5eeb3512)
untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:46:03 -04:00
Andreas Wrede cc25dee7e2 initial commit 2026-06-28 15:09:28 -04:00