inventory: real mesh state per host (no more placeholder netbird FQDNs)

Audit against actual netbird+tailscale peer lists:
  - hubris is the only LXC-host on Netbird; only workstations + hubris
    have netbird entries
  - 10 LXCs+VMs have real Tailscale FQDNs: apps, jellyfin, paperless,
    gitea, nextcloud, elementsynapse, sophia, mule-images→muleimage,
    arriman→arr, haos→homeassistant
  - 7 hosts are LAN-only (no mesh block): nfs-export, caddy, claudio-bot,
    authentik, plato, mule-photos-new, zimaos
  - mac-mini's netbird FQDN corrected to the actual peer name
    (mac-mini-234-17.netbird.selfhosted)

Also: bin/homelab host_address() now prefers lan_ip first — universally
reachable from any LAN client and from any Netbird peer via the
192.168.8.0/24 network resource routed through hubris. Mesh FQDNs are
fallbacks for roaming workstations without a fixed lan_ip.

This makes 'homelab status' from republic show all backends 'ok' instead
of falsely reporting them 'down' against unresolvable netbird FQDNs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
root
2026-05-20 21:28:03 +02:00
parent e7a74f795d
commit 36a686d953
21 changed files with 56 additions and 103 deletions

View File

@@ -55,18 +55,25 @@ def host(name: str) -> dict:
def host_address(name: str, prefer_lan: bool = False) -> str:
"""Best-effort single address for `name`.
Preference order is lan_ip first because:
- From any LAN client it's a direct route.
- From any Netbird peer it's routed through hubris's
192.168.8.0/24 network resource.
- From any Tailscale peer with subnet routing it works too.
Mesh FQDNs are fallbacks for hosts without a lan_ip (e.g. roaming
workstations).
"""
h = host(name)
mesh = h.get("mesh", {})
candidates = []
if prefer_lan and h.get("lan_ip"):
candidates.append(h["lan_ip"])
candidates.extend([
mesh.get("netbird", {}).get("fqdn") if isinstance(mesh.get("netbird"), dict) else None,
mesh.get("netbird", {}).get("ip") if isinstance(mesh.get("netbird"), dict) else None,
nb = mesh.get("netbird") if isinstance(mesh.get("netbird"), dict) else {}
ts = mesh.get("tailscale") if isinstance(mesh.get("tailscale"), dict) else {}
candidates = [
h.get("lan_ip"),
mesh.get("tailscale", {}).get("fqdn") if isinstance(mesh.get("tailscale"), dict) else None,
mesh.get("tailscale", {}).get("ip") if isinstance(mesh.get("tailscale"), dict) else None,
])
nb.get("fqdn"), nb.get("ip"),
ts.get("fqdn"), ts.get("ip"),
]
for c in candidates:
if c:
return c