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>
This commit is contained in:
Andreas Wrede
2026-07-06 09:38:53 -04:00
co-authored by Claude Fable 5
parent 4117802f69
commit a7819edbc2
4 changed files with 123 additions and 12 deletions
+24
View File
@@ -55,6 +55,7 @@ This directory is the source of truth. The live system files live under `/etc`,
- Cockpit: `https://192.168.10.251:9090` (or `.27`, or ZeroTier). Log in with a Unix account; enable *Administrative access* for action buttons.
- Regulatory domain **CA** (unlocks 5GHz ch149161 @30dBm, no DFS).
- ZeroTier network `d3ecf5726d041b2a`, pushed DNS domain `wrede.pvt` via `192.168.196.115` + `192.168.10.5`.
- Home Assistant (HAOS in libvirt VM `ha_van`) is bridged into `br0` at **`10.42.0.50:8123`** (pinned DHCP lease, name `homeassistant`); legacy URL `http://10.42.0.1:8123` still works via DNAT.
---
@@ -127,6 +128,14 @@ This directory is the source of truth. The live system files live under `/etc`,
The `hbc` binary itself (venv at `~/venvs/hbd`, symlink `~/bin/hbc`) is installed once via the
heartbeat project's own installer — see §4. `deploy.sh` only starts the service once it exists.
### `ha/` — Home Assistant VM
| file | → installs to | purpose |
|---|---|---|
| `ha_van.xml` | *(reference only — `virsh define ha/ha_van.xml` to restore)* | libvirt domain: HAOS VM, virtio NIC bridged onto `br0` (MAC `52:54:00:ad:0a:01`). Disk image lives outside the repo. |
Not touched by `deploy.sh` — the VM's LAN address/name come from `ap/van-ap-dnsmasq.conf`
(`dhcp-host``10.42.0.50`, `homeassistant`) and the legacy-URL DNAT from `ap/nftables.conf`.
---
## 3. Components & how to operate
@@ -185,6 +194,21 @@ heartbeat project's own installer — see §4. `deploy.sh` only starts the servi
- Read-only status works as any user; Prefer/Up/Down/Restart need *Administrative access* (polkit).
- **Prefer** sets the manual WAN preference (see *Manual preference* above); **Up/Down** connect/disconnect the NM device; **Restart** restarts hostapd.
### Home Assistant VM (`ha_van`)
- HAOS runs as a libvirt KVM VM whose NIC is **bridged into `br0`** — it is a first-class LAN
device, not NAT'd behind libvirt's `virbr0`. VanLink + wired clients reach it directly at
`http://10.42.0.50:8123` (or `http://homeassistant:8123` / `homeassistant.local` via mDNS);
ZT clients route in via the ZT-managed `10.42.0.0/24` route. mDNS/SSDP device discovery works
because the VM shares the clients' L2 segment.
- The old NAT-era URL `http://10.42.0.1:8123` keeps working: nftables DNATs it to `10.42.0.50`,
with a hairpin masquerade for same-subnet clients (see comments in `ap/nftables.conf`).
- History: the VM used to sit on libvirt's `default` NAT net (`192.168.122.50`) with a
`/etc/libvirt/hooks/network` hook inserting FORWARD accepts above libvirt's REJECT. That broke
whenever libvirtd re-inserted its chains on restart (hook doesn't fire then) — bridging removed
the whole failure mode. The hook and the libvirt DHCP reservation are gone.
- Operate: `virsh {start,shutdown,domstate} ha_van`; autostart is per libvirt config. Verify:
`curl -s -o /dev/null -w '%{http_code}' http://10.42.0.50:8123/``200`.
### Never sleep (lid-closed operation)
- wayback lives lid-closed in the van and must stay up. Stock logind `HandleLidSwitch=suspend` would sleep it on lid close (even on AC). The `power/10-vanlink-nolid.conf` drop-in sets all three lid actions to `ignore`; `deploy.sh` also masks every sleep target so idle / GUI / a stray `systemctl suspend` can't suspend it.
- Verify: `busctl get-property org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager HandleLidSwitch``"ignore"`, and `systemctl is-enabled suspend.target``masked`. `busctl call ... CanSuspend` should return `"no"`.