4 Commits

Author SHA1 Message Date
f5e23bea0a DNS Phase 4 COMPLETE: single zone — managed zone removed
After upgrading the Mac client 0.68.3 -> 0.71.3 (matching mgmt) and clearing
its NetBird resolver cache (netbird service restart), the managed-zone
deletion works: with 0 managed-zone records, the Mac resolves all
hubris.network names by forwarding to Technitium (192.168.8.2). iPhone
confirmed on cellular (no LAN path -> proves mesh-forward).

The first deletion "failure" was a misdiagnosis: the old 0.68.3 resolver
cache held stale answers and wouldn't clear on down/up (needs daemon
restart); the Mac's dual LAN+mesh paths muddied it. A direct
dig @100.122.255.254 of an unsynced name had shown forwarding working.

Done:
- Deleted all 23 NetBird managed-zone A-records.
- Removed the */10 dns-sync cron. Kept /opt/dns-sync/sync.py + token +
  pre-deletion backup as an emergency-restore tool only.

End state: Technitium is the single DNS source. Mesh peers forward to it
(Core route -> 192.168.8.0/24); LAN/household query it directly. No replica,
no sync. Requires mesh clients on 0.71.x+.

Docs: dns.md + 107-dns.md updated to single-source; subdomain recipe no
longer references the sync.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 15:49:36 +02:00
0ff592d237 DNS Phase 4: attempted single-zone, reverted (managed zone load-bearing)
Deleting the NetBird managed-zone replica broke mesh-peer DNS: the Mac
(NetBird 0.68.3) could not resolve hubris.network via the home-lab-dns
nameserver group (-> 192.168.8.2) even though it shows "Available" and the
192.168.8.0/24 route is present. Forwarding to the routed-LAN IP does not
actually serve queries on the current client. Restored the managed zone via
dns-sync.py and re-enabled the cron; resolution recovered.

Correction to Phase 2: the route fix delivered roaming-peer *service
connectivity* (the real iPhone win) but did NOT enable DNS forwarding. The
original "NetBird won't forward to Technitium for mesh peers" finding
stands; managed zone + sync are retained as load-bearing.

To finish single-source later: upgrade clients to 0.71.x, or point the
nameserver group at a mesh-native DNS IP (join CT 107 to the mesh).

Docs: dns.md + 107-dns.md corrected to reflect retained managed zone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 15:33:33 +02:00
f9f2e8ab5e DNS Phase 2: fix roaming-peer resolution via route distribution
Root cause of "NetBird won't forward to Technitium" was NOT a nameserver
bug — it was a missing route. The home-lab-dns nameserver group
(-> 192.168.8.2, domain hubris.network) was applied to all peers, but the
192.168.8.0/24 route (home-lab-network resource) was distributed only to
the Services group. Roaming peers (Core: iphone + laptops) had no route to
192.168.8.2, so forwarding silently failed (Networks: -).

Fix: added Core to the home-lab-network resource distribution via the
NetBird API. Roaming peers now get the subnet route + the already-applied
nameserver forwarding -> *.hubris.network resolves off-LAN. Also grants
roaming devices full homelab service access. No CT 107 mesh-join needed
(original Phase 2 hypothesis obsolete).

Docs: corrected dns.md + 107-dns.md root-cause claims. Managed zone kept
as fallback pending Phase 4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 15:28:08 +02:00
85457095e4 DNS single-source Phase 1 + decommission Tailscale fleet-wide
Phase 1 of the DNS-redundancy cleanup (keep NetBird, collapse toward one
zone) — the safe, mesh-independent half:

- Every homelab LXC now resolves via Technitium (192.168.8.2). Fixed 8
  boxes on a dead resolver (.180), the router (.1), or Tailscale MagicDNS
  (100.100.100.100): 101,102,104,105,106,114,119,126.
- Removed the redundant /etc/hosts auth/mcp/secrets overrides (Technitium
  returns identical-or-better answers); disabled hubris-hosts-override.
- Net effect: on-prem DNS (LXCs + household via Fritz!Box->Technitium) is
  now NetBird-independent, so dropping the managed zone later can't break
  on-LAN resolution. Phases 2-4 still pending.

Tailscale decommissioned (was legacy/being-phased-out):
- Removed from the 6 LXCs still running it (101,103,104,105,114,119):
  logout, disable tailscaled, apt purge, state cleared.
- inventory.yaml: dropped tailscale from accepted + all mesh blocks;
  regenerated hosts/*.yaml (also pruned orphan authentik/claudio-bot).
- Tightened secrets-issuance MESH_SUBNETS: removed the now-vestigial
  Tailscale CGNAT range 100.64.0.0/10.
- Updated narrative docs (mesh, dns, network, README, AGENTS,
  agent-enrollment, homelab-context, 105-apps, 107-dns).

Live infra changed on the fleet + Mac; this commit records the docs/inventory.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 15:14:07 +02:00
31 changed files with 89 additions and 168 deletions

View File

@@ -19,8 +19,8 @@ the operator to run `homelab client add <hostname>` from an existing client.
- `/opt/homelab-context/inventory.yaml` — every host, LXC, VM, and workstation - `/opt/homelab-context/inventory.yaml` — every host, LXC, VM, and workstation
with their mesh addresses, roles, and service mappings. Treat this file as with their mesh addresses, roles, and service mappings. Treat this file as
authoritative; anything you read in narrative pages should agree with it. authoritative; anything you read in narrative pages should agree with it.
- `/opt/homelab-context/infrastructure/mesh.md` Tailscale → Netbird state. - `/opt/homelab-context/infrastructure/mesh.md` — Netbird mesh state.
Both meshes are accepted today; Netbird is preferred for new traffic. Tailscale was decommissioned 2026-06-21; the fleet is Netbird-only.
- `/opt/homelab-context/infrastructure/dns.md` — split-horizon DNS via - `/opt/homelab-context/infrastructure/dns.md` — split-horizon DNS via
dnsmasq on LXC 124. `*.hubris.network` resolves to 192.168.x.x on the LAN dnsmasq on LXC 124. `*.hubris.network` resolves to 192.168.x.x on the LAN
and to mesh addresses off-LAN. and to mesh addresses off-LAN.

View File

@@ -35,7 +35,7 @@ See the full table in [`containers/index.md`](containers/index.md). Quick links:
### Cross-cutting infrastructure ### Cross-cutting infrastructure
- [DNS — split-horizon](infrastructure/dns.md) - [DNS — split-horizon](infrastructure/dns.md)
- [Ingress — Caddy + VPS traefik](infrastructure/ingress.md) - [Ingress — Caddy + VPS traefik](infrastructure/ingress.md)
- [Mesh — Tailscale → Netbird migration](infrastructure/mesh.md) - [Mesh — Netbird overlay](infrastructure/mesh.md)
- [Monitoring — Hermes health watchdog](infrastructure/monitoring.md) - [Monitoring — Hermes health watchdog](infrastructure/monitoring.md)
- [Media permissions — `media` GID 10000](infrastructure/media-permissions.md) - [Media permissions — `media` GID 10000](infrastructure/media-permissions.md)
- [SSH access](infrastructure/ssh-access.md) - [SSH access](infrastructure/ssh-access.md)

View File

@@ -87,9 +87,10 @@ same key. Mesh+LAN source-IP gated via the `MESH_SUBNETS` env in
- `/revoke` is admin-token-gated by `/etc/secrets-issuance/admin-token`; - `/revoke` is admin-token-gated by `/etc/secrets-issuance/admin-token`;
shreds the local key file and adds the hostname to the denylist. shreds the local key file and adds the hostname to the denylist.
Called by `homelab client remove`. Called by `homelab client remove`.
- Trust subnets today: `100.122.0.0/16` (Netbird), `100.64.0.0/10` - Trust subnets today: `100.122.0.0/16` (Netbird), `192.168.8.0/24` (LAN).
(Tailscale), `192.168.8.0/24` (LAN). Tighten if the LAN gets (The legacy Tailscale CGNAT range `100.64.0.0/10` was removed from
untrusted devices. `secrets-issuance`'s `MESH_SUBNETS` on 2026-06-21 when Tailscale was
decommissioned; service restarted.) Tighten if the LAN gets untrusted devices.
#### Auto-deploy pipeline (secrets-issuance) #### Auto-deploy pipeline (secrets-issuance)
Receiver at `/opt/secrets-issuance/secrets-issuance/deploy/webhook/`, Receiver at `/opt/secrets-issuance/secrets-issuance/deploy/webhook/`,

View File

@@ -24,12 +24,14 @@ Authoritative split-horizon DNS for `hubris.network` on the LAN/mesh, plus recur
- API: `http://192.168.8.2:5380/api/...` (token via `/api/user/login`). Zone was built via the API. - API: `http://192.168.8.2:5380/api/...` (token via `/api/user/login`). Zone was built via the API.
## Who points here ## Who points here
- **NetBird mesh peers:** resolve via the **NetBird managed DNS zone**, kept in sync *from* this Technitium (see dns-sync below). The `home-lab-dns` nameserver group (`→ 192.168.8.2`) is a thin fallback forwarder. - **NetBird mesh peers:** resolve `hubris.network` by **forwarding to Technitium** via the `home-lab-dns` nameserver group (`→ 192.168.8.2`, domain `hubris.network`, applied to all peers). The **NetBird managed DNS zone was removed 2026-06-21 (Phase 4)** — Technitium is now the single DNS source for the mesh too. This works because: (a) roaming peers (`Core`) have the `192.168.8.0/24` route to reach `192.168.8.2` (added 2026-06-21), and (b) clients run NetBird **0.71.x** — on the old 0.68.3 client, forwarding reported `Available` but didn't serve queries, and the resolver cache (`100.122.255.254`) wouldn't clear on `down/up`; a `netbird service restart` (or app toggle) clears it. See [dns.md changelog 2026-06-21](../infrastructure/dns.md).
- **Homelab DHCP clients:** Technitium's own DHCP scope hands out `192.168.8.2` as the DNS server for `192.168.8.x` leases (see DHCP section below). - **Homelab DHCP clients:** Technitium's own DHCP scope hands out `192.168.8.2` as the DNS server for `192.168.8.x` leases (see DHCP section below).
- **Plain LAN clients (`192.168.178.x`):** Fritz!Box DHCP still hands out Fritz!Box itself (`192.168.178.1`) as DNS — no split-horizon for non-mesh clients. Changing this requires a secondary DNS fallback, which Fritz!OS 8.x doesn't expose in a single DHCP field. - **Plain LAN clients (`192.168.178.x`):** Fritz!Box DHCP still hands out Fritz!Box itself (`192.168.178.1`) as DNS, **but** the Fritz!Box now *forwards* upstream to Technitium — DNSv4 server set to `192.168.8.2` (Internet → Filter → DNS Server, 2026-06-17). So household clients get split-horizon `*.hubris.network` answers via Fritz!Box→Technitium, with **no NetBird dependency**. (This is the change that decoupled the on-prem tier from the mesh — see [dns.md](../infrastructure/dns.md) 2026-06-17.)
## dns-sync (Technitium = authoring source) ## dns-sync (RETIRED 2026-06-21 — Phase 4 complete)
`/opt/dns-sync/sync.py` (cron `*/10`, logs `/var/log/dns-sync.log`) reconciles this zone's named A-records → the NetBird managed DNS zone via the NetBird API (`/api/dns/zones/{id}/records`). Token at `/opt/dns-sync/netbird-token` (mode 600; source of truth in sops `secrets/netbird-pat.yaml`). **Edit DNS only here**; the sync propagates to the mesh. It deletes NetBird records absent from Technitium. Tracked: [scripts/dns-sync.py](../scripts/dns-sync.py). *Why this exists:* NetBird won't forward to Technitium for mesh peers (self-IP / nameserver-group quirks), so we sync into the managed zone instead — see [dns.md](../infrastructure/dns.md). **The managed-zone sync is no longer scheduled.** `/opt/dns-sync/sync.py` reconciled this zone's named A-records → the NetBird managed DNS zone; the `*/10` cron (`/etc/cron.d/dns-sync`) was **removed 2026-06-21** when the managed zone was retired. Technitium is now the **single** DNS source — mesh peers forward to it (see "Who points here" above), LAN/household clients query it directly.
The script + token + a pre-deletion record backup remain at `/opt/dns-sync/` **as an emergency-restore tool only**: running `python3 /opt/dns-sync/sync.py` once re-creates the managed zone from Technitium (used during the Phase 4 rollback). Do not re-add the cron unless reverting Phase 4. Tracked: [scripts/dns-sync.py](../scripts/dns-sync.py).
## DHCP ## DHCP

View File

@@ -8,15 +8,10 @@ role: docker-apps
host: hubris host: hubris
pve_id: 105 pve_id: 105
lan_ip: 192.168.8.205 lan_ip: 192.168.8.205
mesh:
tailscale:
ip: 100.121.171.122
fqdn: apps
mesh_globals: mesh_globals:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale
mounts: mounts:
- /mnt/library - /mnt/library
public_hosts: public_hosts:

View File

@@ -8,14 +8,10 @@ role: arr-stack
host: hubris host: hubris
pve_id: 122 pve_id: 122
lan_ip: 192.168.8.132 lan_ip: 192.168.8.132
mesh:
tailscale:
fqdn: arr
mesh_globals: mesh_globals:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale
mounts: mounts:
- /mnt/library - /mnt/library
public_hosts: public_hosts:

View File

@@ -1,32 +0,0 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: authentik
kind: lxc
os: linux
role: idp
host: hubris
pve_id: 124
lan_ip: 192.168.8.180
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
public_host: auth.hubris.network
runs:
- authentik
- dnsmasq
services_hosted:
- name: authentik
url: https://auth.hubris.network
backend: authentik
- name: dnsmasq
backend: authentik
note: split-horizon DNS, /etc/dnsmasq.d/hubris-split.conf
notes:
- 'Also hosts split-horizon dnsmasq: /etc/dnsmasq.d/hubris-split.conf'
see_also:
- containers/124-authentik.md
mcp_endpoint: https://mcp.hubris.network/mcp
secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -12,7 +12,6 @@ mesh_globals:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale
peers: peers:
- authentik - authentik
- gitea - gitea

View File

@@ -1,23 +0,0 @@
# DEPRECATED — LXC 123 destroyed 2026-06-04. Replaced by Hermes Agent.
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: claudio-bot
kind: lxc
os: linux
role: matrix-agent
host: hubris
pve_id: 123
lan_ip: 192.168.8.230
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
notes:
- Reads /opt/homelab-context/ on startup
age_pubkey: age1xmkeq968areza2necqyq0065dpeegngzyr6dhagh0n6pl33lccfqe5mqn9
see_also:
- containers/123-claudio-bot.md
mcp_endpoint: https://mcp.hubris.network/mcp
secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -7,15 +7,11 @@ os: linux
role: matrix-server role: matrix-server
host: hubris host: hubris
pve_id: 118 pve_id: 118
lan_ip: 192.168.8.239 # static; was DHCP drifting to .244 — fixed by setting iface eth0 inet static lan_ip: 192.168.8.239
mesh:
tailscale:
fqdn: elementsynapse
mesh_globals: mesh_globals:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale
public_host: matrix.hubris.network public_host: matrix.hubris.network
runs: runs:
- matrix - matrix

View File

@@ -8,14 +8,10 @@ role: git-server
host: hubris host: hubris
pve_id: 104 pve_id: 104
lan_ip: 192.168.8.121 lan_ip: 192.168.8.121
mesh:
tailscale:
fqdn: gitea
mesh_globals: mesh_globals:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale
mounts: mounts:
- /mnt/library - /mnt/library
public_host: git.hubris.network public_host: git.hubris.network

View File

@@ -8,14 +8,10 @@ role: home-automation
host: hubris host: hubris
pve_id: 108 pve_id: 108
lan_ip: 192.168.8.101 lan_ip: 192.168.8.101
mesh:
tailscale:
fqdn: homeassistant
mesh_globals: mesh_globals:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale
runs: runs:
- haos - haos
services_hosted: services_hosted:

View File

@@ -14,7 +14,6 @@ mesh_globals:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale
mounts: mounts:
- /mnt/library - /mnt/library
ssh: ssh:

View File

@@ -8,14 +8,10 @@ role: media-server
host: hubris host: hubris
pve_id: 101 pve_id: 101
lan_ip: 192.168.8.206 lan_ip: 192.168.8.206
mesh:
tailscale:
fqdn: jellyfin
mesh_globals: mesh_globals:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale
mounts: mounts:
- /mnt/library - /mnt/library
public_host: media.hubris.network public_host: media.hubris.network

View File

@@ -5,6 +5,7 @@ name: ludo-mini
kind: workstation kind: workstation
os: linux os: linux
role: dev role: dev
lan_ip: 192.168.178.181
mesh: mesh:
netbird: netbird:
fqdn: ludo-mini.netbird.selfhosted fqdn: ludo-mini.netbird.selfhosted
@@ -12,6 +13,7 @@ mesh_globals:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale ssh:
user: dtoro
mcp_endpoint: https://mcp.hubris.network/mcp mcp_endpoint: https://mcp.hubris.network/mcp
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -5,7 +5,7 @@ name: mac-mini
kind: workstation kind: workstation
os: macos os: macos
role: dev role: dev
lan_ip: 192.168.8.174 lan_ip: 192.168.178.182
mesh: mesh:
netbird: netbird:
fqdn: mac-mini-234-17.netbird.selfhosted fqdn: mac-mini-234-17.netbird.selfhosted
@@ -13,7 +13,6 @@ mesh_globals:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale
ssh: ssh:
user: dtoro user: dtoro
notes: notes:

View File

@@ -8,14 +8,10 @@ role: photo-management
host: hubris host: hubris
pve_id: 120 pve_id: 120
lan_ip: 192.168.8.136 lan_ip: 192.168.8.136
mesh:
tailscale:
fqdn: muleimage
mesh_globals: mesh_globals:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale
mounts: mounts:
- /mnt/library - /mnt/library
public_host: photos.hubris.network public_host: photos.hubris.network

View File

@@ -13,7 +13,6 @@ mesh_globals:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale
ssh: ssh:
user: root user: root
notes: notes:

View File

@@ -8,14 +8,10 @@ role: file-sync
host: hubris host: hubris
pve_id: 114 pve_id: 114
lan_ip: 192.168.8.224 lan_ip: 192.168.8.224
mesh:
tailscale:
fqdn: nextcloud
mesh_globals: mesh_globals:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale
mounts: mounts:
- /mnt/library - /mnt/library
public_host: cloud.hubris.network public_host: cloud.hubris.network

View File

@@ -12,7 +12,6 @@ mesh_globals:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale
see_also: see_also:
- containers/102-nfs-export.md - containers/102-nfs-export.md
mcp_endpoint: https://mcp.hubris.network/mcp mcp_endpoint: https://mcp.hubris.network/mcp

View File

@@ -8,14 +8,10 @@ role: document-archive
host: hubris host: hubris
pve_id: 103 pve_id: 103
lan_ip: 192.168.8.130 lan_ip: 192.168.8.130
mesh:
tailscale:
fqdn: paperless
mesh_globals: mesh_globals:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale
mounts: mounts:
- /mnt/library - /mnt/library
public_host: paperless.hubris.network public_host: paperless.hubris.network

View File

@@ -12,7 +12,6 @@ mesh_globals:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale
mounts: mounts:
- /mnt/library/documents/plato - /mnt/library/documents/plato
public_host: plato.hubris.network public_host: plato.hubris.network

View File

@@ -12,7 +12,6 @@ mesh_globals:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale
ssh: ssh:
user: dtoro user: dtoro
mcp_endpoint: https://mcp.hubris.network/mcp mcp_endpoint: https://mcp.hubris.network/mcp

View File

@@ -8,14 +8,10 @@ role: workshop
host: hubris host: hubris
pve_id: 119 pve_id: 119
lan_ip: 192.168.8.109 lan_ip: 192.168.8.109
mesh:
tailscale:
fqdn: sophia
mesh_globals: mesh_globals:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale
mounts: mounts:
- /mnt/library - /mnt/library
see_also: see_also:

View File

@@ -12,7 +12,6 @@ mesh_globals:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale
public_host: zimaos.hubris.network public_host: zimaos.hubris.network
runs: runs:
- zimaos - zimaos

View File

@@ -7,9 +7,9 @@ There is **no wildcard on the LAN side**. Every subdomain needs an explicit entr
## Components ## Components
- **Authoritative public DNS:** IONOS. `*.hubris.network → 82.165.190.79` (was `74.118.126.4` until 2026-04-22). - **Authoritative public DNS:** IONOS. `*.hubris.network → 82.165.190.79` (was `74.118.126.4` until 2026-04-22).
- **LAN authoritative for `hubris.network` records:** [Technitium DNS](https://technitium.com) on [dns (107)](../containers/107-dns.md) at `192.168.8.2:53`. Syncs A records to the NetBird managed DNS zone via cron (see [dns-sync.py](../scripts/dns-sync.py)). Formerly dnsmasq on [authentik (124)](../containers/124-authentik.md) (decommissioned 2026-06-04). - **LAN authoritative for `hubris.network` records:** [Technitium DNS](https://technitium.com) on [dns (107)](../containers/107-dns.md) at `192.168.8.2:53` — the **single DNS source** for LAN, household, and mesh. (Through 2026-06-21 it also synced A-records into a NetBird managed zone; that replica was retired in Phase 4, see changelog.) Formerly dnsmasq on [authentik (124)](../containers/124-authentik.md) (decommissioned 2026-06-04).
- **PVE host** (`192.168.8.77`): resolver is the local Netbird daemon at `100.122.38.109:53`, which forwards to the LAN/upstream and learns hubris.network answers via that path. `netbird status` says "Nameservers: 0/0 Available" — confirming netbird does NOT manage a hubris.network zone; it just caches whatever the system resolver returns. - **PVE host** (`192.168.8.77`): resolver is the local Netbird daemon at `100.122.38.109:53`, which forwards to the LAN/upstream and learns hubris.network answers via that path. `netbird status` says "Nameservers: 0/0 Available" — confirming netbird does NOT manage a hubris.network zone; it just caches whatever the system resolver returns.
- **Some LXCs** keep router DNS (`192.168.8.1`) or Tailscale MagicDNS (`100.100.100.100`), both of which return the public IONOS A record. Those LXCs need either a `/etc/hosts` override or local dnsmasq — see [mesh migration](mesh.md) for which technique applies where. - **All LXCs** now point at Technitium (`192.168.8.2`) directly (since 2026-06-21 — see changelog). The earlier mix of router DNS (`192.168.8.1`) / Tailscale MagicDNS (`100.100.100.100`) which returned the public IONOS A record and forced `/etc/hosts` overrides — has been removed.
## Live entries (as of 2026-06-04) ## Live entries (as of 2026-06-04)
@@ -50,11 +50,11 @@ Creating a new Caddyfile site block is necessary but **not sufficient**. Without
## Recipe — adding a new subdomain ## Recipe — adding a new subdomain
1. Edit `/etc/caddy/Caddyfile` on [caddy (121)](../containers/121-caddy.md), commit + push to `dtoro/caddy-conf`. Webhook reloads caddy. See [auto-deploy](auto-deploy.md). 1. Edit `/etc/caddy/Caddyfile` on [caddy (121)](../containers/121-caddy.md), commit + push to `dtoro/caddy-conf`. Webhook reloads caddy. See [auto-deploy](auto-deploy.md).
2. Add the A record in the [Technitium UI](http://192.168.8.2) at `dns (107)` — the NetBird managed DNS zone sync picks it up within ~10 minutes via cron. Or add directly to the NetBird managed zone via API if you need it faster. 2. Add the A record in the [Technitium UI](http://192.168.8.2) at `dns (107)`. That's it — Technitium is the single DNS source; mesh peers forward to it live and LAN/household query it directly. (No managed-zone sync to wait for — retired 2026-06-21, Phase 4.)
3. Verify: `dig @192.168.8.2 +short <new>.hubris.network``192.168.8.175`. 3. Verify: `dig @192.168.8.2 +short <new>.hubris.network``192.168.8.175`.
4. On macOS clients, flush: `sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder`. 4. On macOS clients, flush: `sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder`. On a NetBird peer that caches a stale answer, `netbird service restart` clears its resolver cache.
> The Technitium config on LXC 107 is the single source of truth. Never hand-edit the NetBird managed zone directly — the [`scripts/dns-sync.py`](../scripts/dns-sync.py) cron on 107 reconciles them and reaps stale records. See [dns.md changelog 2026-06-03](#2026-06-03--single-authoring-source-technitium--netbird-managed-zone-sync). > Technitium on LXC 107 is the single source of truth. (Through 2026-06-21 a `dns-sync.py` cron mirrored it into a NetBird managed zone; that replica + cron were removed in Phase 4 once mesh peers were forwarding directly — see [changelog](#2026-06-21--dns-single-source-phase-4-complete--managed-zone-removed-technitium-is-the-single-source).)
## Public path — what does and doesn't follow the LAN map ## Public path — what does and doesn't follow the LAN map
@@ -76,6 +76,54 @@ Either:
## Changelog ## Changelog
### 2026-06-21 — DNS single-source, Phase 4: COMPLETE — managed zone removed, Technitium is the single source
After upgrading the Mac client `0.68.3 → 0.71.3` (matching mgmt) and clearing its NetBird resolver cache (`netbird service restart`), the managed-zone deletion was retried and **works**: with 0 managed-zone records, the Mac resolves `git`/`cloud`/`nfs-export`/`auth`.hubris.network entirely by forwarding to Technitium (`192.168.8.2`). The iPhone confirmed the same on **cellular** (no LAN/Fritz path — proof it's the mesh-forward path).
So the first deletion attempt (the REVERTED entry below) was a **misdiagnosis**: forwarding wasn't broken — the old 0.68.3 client's resolver cache (`100.122.255.254`) held stale/empty answers and wouldn't clear on `down/up` (only a full daemon restart clears it), and the Mac's dual LAN+mesh resolver paths muddied the test. A direct query (`dig @100.122.255.254 <unsynced-name>`) had in fact shown forwarding working all along.
**Done:**
- Deleted all 23 NetBird managed-zone A-records via API.
- Removed the `*/10` sync cron (`/etc/cron.d/dns-sync`). Kept `/opt/dns-sync/sync.py` + token + a pre-deletion backup as an emergency-restore tool only (run it once to rebuild the managed zone if ever reverting).
**End state — one zone:** Technitium (`192.168.8.2`) is the single authoring + serving source. Mesh peers forward to it (route via `Core``192.168.8.0/24`); LAN/household query it directly (Fritz!Box DNSv4 → Technitium). No managed-zone replica, no sync.
**Prereqs for it to keep working:** mesh clients on NetBird **0.71.x+**, and roaming peers in the `Core` group (route) — both satisfied. Other peers (laptops, proxmox-server) pick up forwarding as their resolver caches expire, or after a `netbird service restart`.
**Optional, not done:** flip the Technitium wildcard `*.hubris.network` from `→ 82.165.190.79` (VPS, mirrors public) to `→ 192.168.8.175` (Caddy) so new Caddy services need zero DNS entries. Deferred — decide separately.
### 2026-06-21 — DNS single-source, Phase 4: ATTEMPTED + REVERTED — managed zone is load-bearing
Tried to collapse to a single zone by deleting the NetBird managed-zone replica (and pausing `dns-sync`). Result: **mesh-peer DNS broke.** With the managed zone gone, the Mac (NetBird 0.68.3) failed to resolve `git`/`cloud`/`nfs-export`.hubris.network via the NetBird resolver (`100.122.255.254`) — the `home-lab-dns` nameserver group (`→ 192.168.8.2`) reports `Available` but does **not** actually serve forwarded queries. Restored the managed zone via `dns-sync.py` (23 records) and re-enabled the cron; resolution recovered immediately.
**Correction to the Phase 2 entry below:** the missing route was *a* real problem (it blocked roaming-peer *connectivity* to services — the actual user-facing win), but it was **not** the whole story. DNS forwarding to the routed-LAN IP `192.168.8.2` still does not work for mesh peers, so the original "NetBird won't forward to Technitium" finding stands and the managed zone stays.
**To actually finish single-source later**, forwarding must first be made to work. Two candidates, untried:
- Upgrade NetBird clients to 0.71.x (the Mac is 0.68.3 — version skew with mgmt 0.71.3 is a known source of resolver bugs), then retest.
- Point `home-lab-dns` at a **mesh-native** DNS IP — join CT 107 to the mesh and use its `100.122.x` address instead of the routed-LAN `192.168.8.2` (the original Phase 2 hypothesis; needs a brief 107 restart for `/dev/net/tun`).
Net state after today: Phase 1 (on-prem single resolver) and Phase 2 (roaming route → iPhone reaches the homelab) stand and deliver the practical goals. The managed-zone replica + sync are **retained** as load-bearing.
### 2026-06-21 — DNS single-source, Phase 2: roaming-peer route fixed (see Phase 4 correction above — forwarding still doesn't serve queries; this fixed *connectivity*, not DNS)
The long-standing belief that "NetBird won't forward to Technitium for mesh peers" (which is *why* the managed-zone sync was built) turned out to be **wrong**. The NetBird API showed the real cause:
- The `home-lab-dns` nameserver group (→ `192.168.8.2`, match-domain `hubris.network`) was already applied to **all** peers (via the `All` group). So every peer *had* the forwarding rule.
- But the `192.168.8.0/24` route (network resource `home-lab-network`) was distributed to the **`Services`** group only = `{netbird-ionos, proxmox-server}`. Roaming peers (`dtoro-iphone`, `mac-mini`, `republic-laptop`, `muli-laptop`, `ludo-mini`) are in **`Core`**, which had **no route to the homelab subnet** → they couldn't reach `192.168.8.2` → forwarding silently failed (`Networks: -`).
**Fix:** added the `Core` group to the `home-lab-network` resource distribution (now `[Services, Core]`) via `PUT /api/networks/.../resources/...`. Roaming peers immediately picked up `Networks: 192.168.8.0/24` and `[192.168.8.2:53] for [hubris.network] is Available`. No CT 107 mesh-join / TUN / restart was needed (the original Phase 2 hypothesis is obsolete). This also gives roaming devices full homelab **service** access, not just DNS.
The managed zone is still in place as a fallback pending the Phase 3 roaming test (iPhone on cellular); Phase 4 then deletes the managed zone + `dns-sync` cron. Rollback: PUT the resource back to `[Services]` only.
### 2026-06-21 — DNS single-source, Phase 1: on-prem LXCs decoupled from NetBird
Goal: collapse the three overlapping DNS sources (Technitium + NetBird managed zone + per-LXC band-aids) toward **one zone**, keeping NetBird. Phase 1 (the safe, mesh-independent half) is done:
- **Every homelab LXC now resolves via Technitium (`192.168.8.2`).** Fixed 8 boxes that were on a dead resolver, the router, or Tailscale MagicDNS:
- `192.168.8.180` (dead ex-Authentik): 102, 106, 126
- `192.168.8.1` (router → public answer): 101
- `100.100.100.100` (Tailscale MagicDNS): 104, 105, 114, 119 — also ran `tailscale set --accept-dns=false` so `tailscaled` stops rewriting `/etc/resolv.conf`.
- Already correct (`.2`): 103, 118, 120, 121, 122. CT 107 stays on `1.1.1.1` by design (no self-dependency).
- **Removed the redundant `/etc/hosts` band-aids** (`auth`/`mcp`/`secrets``192.168.8.175`) on 101, 103, 104, 105, 114, 118, 120, 121, 122, 126; disabled `hubris-hosts-override.service` where enabled. Backups at `/etc/hosts.bak-dnsplan`. PVE-managed lines and self-hostname maps preserved. Technitium already returns identical-or-better answers (verified: `auth → 82.165.190.79`, `mcp`/`secrets`/`cloud`/`git``192.168.8.175`).
- **Nextcloud (114):** its documented Guzzle-workaround dnsmasq is not running; resolves correctly straight from Technitium, so no special-casing remains.
- Net effect: **NetBird's DNS now only matters for off-LAN roaming peers** (Tier 2). On-prem (LXCs + household via Fritz!Box→Technitium) is fully NetBird-independent — so dropping the managed zone later can no longer break on-LAN resolution. Phases 24 (mesh-IP forwarding, roaming test, managed-zone + sync deletion) still pending.
### 2026-06-17 — Fritz!Box DNSv4 server set to Technitium; old limitation resolved ### 2026-06-17 — Fritz!Box DNSv4 server set to Technitium; old limitation resolved
Household LAN clients (192.168.178.x) now resolve `*.hubris.network` to LAN IPs — the limitation noted below is resolved. Configured at Fritz!Box Internet → Filter → DNS Server → DNSv4 Server = `192.168.8.2` (User-defined). Household LAN clients (192.168.178.x) now resolve `*.hubris.network` to LAN IPs — the limitation noted below is resolved. Configured at Fritz!Box Internet → Filter → DNS Server → DNSv4 Server = `192.168.8.2` (User-defined).
Authentik LXC 124 (192.168.8.180) destroyed — Authentik runs on VPS, DNS on Technitium (107). Authentik LXC 124 (192.168.8.180) destroyed — Authentik runs on VPS, DNS on Technitium (107).

View File

@@ -64,9 +64,11 @@ The MCP server and secrets-issuance each have their own clone
- Both services bind `0.0.0.0:<port>`. The trust boundary is - Both services bind `0.0.0.0:<port>`. The trust boundary is
`MESH_SUBNETS` in the service's environment + nftables (planned). Today `MESH_SUBNETS` in the service's environment + nftables (planned). Today
`MESH_SUBNETS=100.122.0.0/16,100.64.0.0/10,192.168.8.0/24` — Netbird + `MESH_SUBNETS=100.122.0.0/16,192.168.8.0/24` — Netbird + the homelab LAN.
Tailscale + the homelab LAN. Adjust if the LAN ever has untrusted (The legacy Tailscale CGNAT range `100.64.0.0/10` was dropped 2026-06-21
devices. when Tailscale was decommissioned; secrets-issuance restarted. Only
secrets-issuance reads `MESH_SUBNETS` — homelab-mcp does not.) Adjust if
the LAN ever has untrusted devices.
- Caddy fronts both with Let's Encrypt certs via the IONOS DNS challenge: - Caddy fronts both with Let's Encrypt certs via the IONOS DNS challenge:
`mcp.hubris.network``192.168.8.205:9810`, `mcp.hubris.network``192.168.8.205:9810`,
`secrets.hubris.network``192.168.8.205:9820`. Off-LAN clients on `secrets.hubris.network``192.168.8.205:9820`. Off-LAN clients on
@@ -125,7 +127,7 @@ The MCP server and secrets-issuance each have their own clone
step-by-step for adding a new client step-by-step for adding a new client
- [Auto-deploy](auto-deploy.md) — the `homelab-mcp` + `secrets-issuance` - [Auto-deploy](auto-deploy.md) — the `homelab-mcp` + `secrets-issuance`
pipelines (and the rest of the lab's webhook pipelines) pipelines (and the rest of the lab's webhook pipelines)
- [Mesh](mesh.md) — Netbird / Tailscale paths and the `192.168.8.0/24` - [Mesh](mesh.md) — Netbird paths and the `192.168.8.0/24`
network resource network resource
- [Apps (105)](../containers/105-apps.md) — where both services run - [Apps (105)](../containers/105-apps.md) — where both services run
- [Gitea (104)](../containers/104-gitea.md) — the source of truth - [Gitea (104)](../containers/104-gitea.md) — the source of truth

View File

@@ -1,12 +1,12 @@
# Mesh — Tailscale → Netbird migration # Mesh — Netbird
The hubris fleet is migrating from Tailscale to Netbird. Netbird is the target end-state. In-progress as of 2026-04-21. The hubris fleet runs on Netbird. Tailscale — the previous overlay — was **fully decommissioned on 2026-06-21**: removed from the 6 LXCs that still ran it (101, 103, 104, 105, 114, 119), apt package + state purged, `tailscaled` disabled. The fleet is now Netbird-only. (Historical migration notes below are kept for context.)
## Current state ## Current state
- **PVE host** uses Netbird (`wt0`, `100.122.38.109/16`). Its resolver is the local netbird daemon, which forwards to LAN/upstream — so the PVE host gets `*.hubris.network → 192.168.8.175` via the system resolver chain. - **PVE host** uses Netbird (`wt0`, `100.122.38.109/16`). Its resolver is the local netbird daemon, which forwards to LAN/upstream — so the PVE host gets `*.hubris.network → 192.168.8.175` via the system resolver chain.
- **Netbird mgmt host** (`82.165.190.79`, FQDN `inspiring-ramanujan.netbird.selfhosted`, NB IP `100.122.165.149`) is now itself a peer on the mesh (joined 2026-04-22 via setup key, netbird 0.69.0). Routes the homelab network (`192.168.8.0/24`) via the PVE peer. This gives the mgmt host LAN access *and* split-horizon DNS for `*.hubris.network`. Useful independently of any Authentik integration. - **Netbird mgmt host** (`82.165.190.79`, FQDN `inspiring-ramanujan.netbird.selfhosted`, NB IP `100.122.165.149`) is now itself a peer on the mesh (joined 2026-04-22 via setup key, netbird 0.69.0). Routes the homelab network (`192.168.8.0/24`) via the PVE peer. This gives the mgmt host LAN access *and* split-horizon DNS for `*.hubris.network`. Useful independently of any Authentik integration.
- **Most LXCs** still run Tailscale or use router DNS (`192.168.8.1`) / Tailscale MagicDNS (`100.100.100.100`), both of which return the *public* IONOS A record `*.hubris.network → 82.165.190.79`. The VPS only routes hostnames it actually publishes (today, `artifacto` + `blog`), so this path is a dead end for any LAN-only service. - **All LXCs** now resolve via Technitium (`192.168.8.2`) directly — as of the 2026-06-21 DNS single-source work (Phase 1). The previous mix of router DNS (`192.168.8.1`) / Tailscale MagicDNS (`100.100.100.100`) returned the *public* IONOS A record and is gone. See [dns.md changelog 2026-06-21](dns.md).
## ICE / STUN / TURN ## ICE / STUN / TURN
@@ -45,6 +45,8 @@ Pre-migration, the bundled `netbirdio/netbird-server` combined image silently ig
## Consequence — every LXC wired to Authentik needs an internal override ## Consequence — every LXC wired to Authentik needs an internal override
> **RESOLVED 2026-06-21 (DNS single-source, Phase 1).** Every homelab LXC now points its resolver directly at **Technitium (`192.168.8.2`)**, which answers the full split-horizon zone (`auth → 82.165.190.79`, everything else → Caddy `192.168.8.175`). The per-LXC `/etc/hosts` overrides and Tailscale-MagicDNS/dead-`.180`/router resolvers below were removed; `hubris-hosts-override.service` disabled where present. The section is kept for history. See [dns.md changelog 2026-06-21](dns.md).
Until each LXC is migrated to Netbird, anything that needs to reach `auth.hubris.network` (Authentik), `cloud.hubris.network` (Nextcloud), etc., must override the public answer with `192.168.8.175`. Until each LXC is migrated to Netbird, anything that needs to reach `auth.hubris.network` (Authentik), `cloud.hubris.network` (Nextcloud), etc., must override the public answer with `192.168.8.175`.
Two techniques. Pick by HTTP-client behavior. Two techniques. Pick by HTTP-client behavior.
@@ -90,7 +92,7 @@ pct set <id> --nameserver "127.0.0.1 192.168.8.1 1.1.1.1"
## Adding new LXCs ## Adding new LXCs
- Don't add new LXCs to Tailscale; add them to Netbird. Tailscale is being decommissioned on hubris. - Don't add new LXCs to Tailscale; add them to Netbird. Tailscale is being decommissioned on hubris.
- When wiring a new app into Authentik: `cat /etc/resolv.conf` on the target LXC. If nameserver is `192.168.8.1` or `100.100.100.100`, add the hosts override. If it's the netbird daemon IP, skip. - When wiring a new app into Authentik: `cat /etc/resolv.conf` on the target LXC. It should be `192.168.8.2` (Technitium), which returns correct split-horizon answers — no `/etc/hosts` override needed. (Historically, boxes on `192.168.8.1`/`100.100.100.100` needed an override; those resolvers were removed 2026-06-21.)
## Long-term fix ## Long-term fix

View File

@@ -64,14 +64,14 @@ No NAT on Proxmox — traffic flows without double-NAT.
## Remote access ## Remote access
- **NetBird mesh** — primary path for remote administration. Authenticated via [Authentik on the VPS](../vps/). - **NetBird mesh** — the remote-administration path. Authenticated via [Authentik on the VPS](../vps/).
- **Tailscale** — legacy, being phased out. See [mesh.md](mesh.md). - Tailscale (the previous overlay) was **decommissioned 2026-06-21**. See [mesh.md](mesh.md).
## Related ## Related
- [DNS](dns.md) — split-horizon config and entry list - [DNS](dns.md) — split-horizon config and entry list
- [Ingress](ingress.md) — public entry points via VPS traefik - [Ingress](ingress.md) — public entry points via VPS traefik
- [Mesh](mesh.md) — NetBird / Tailscale VPN overlay - [Mesh](mesh.md) — NetBird VPN overlay
- [hosts/hubris.md](../hosts/hubris.md) — Proxmox host (vmbr0/vmbr1 config) - [hosts/hubris.md](../hosts/hubris.md) — Proxmox host (vmbr0/vmbr1 config)
- [CT 107 — dns](../containers/107-dns.md) — Technitium DNS + DHCP server - [CT 107 — dns](../containers/107-dns.md) — Technitium DNS + DHCP server

View File

@@ -11,8 +11,8 @@
# ("external" is reserved for hosts the homelab CLI manages via ssh but # ("external" is reserved for hosts the homelab CLI manages via ssh but
# that aren't homelab clients themselves — e.g. the IONOS netbird VPS # that aren't homelab clients themselves — e.g. the IONOS netbird VPS
# with no /etc/age/key.txt and no /opt/homelab-context clone.) # with no /etc/age/key.txt and no /opt/homelab-context clone.)
# - `mesh:` lists addresses the host is reachable at. Both `netbird` and # - `mesh:` lists addresses the host is reachable at via `netbird`.
# `tailscale` are accepted during the migration (see infrastructure/mesh.md). # (Tailscale was decommissioned 2026-06-21 — see infrastructure/mesh.md.)
# Prefer netbird FQDNs over raw IPs. # Prefer netbird FQDNs over raw IPs.
# - `age_pubkey:` provisioned by secrets-issuance on first bootstrap and # - `age_pubkey:` provisioned by secrets-issuance on first bootstrap and
# committed back via `homelab client add --finalize-pubkey <key>`. # committed back via `homelab client add --finalize-pubkey <key>`.
@@ -32,7 +32,6 @@ mesh:
primary: netbird primary: netbird
accepted: accepted:
- netbird - netbird
- tailscale
netbird_subnet: 100.122.0.0/16 netbird_subnet: 100.122.0.0/16
netbird_domain: netbird.selfhosted netbird_domain: netbird.selfhosted
services: services:
@@ -125,9 +124,6 @@ hosts:
role: media-server role: media-server
lan_ip: 192.168.8.206 lan_ip: 192.168.8.206
public_host: media.hubris.network public_host: media.hubris.network
mesh:
tailscale:
fqdn: jellyfin
mounts: mounts:
- /mnt/library - /mnt/library
age_pubkey: '' age_pubkey: ''
@@ -146,9 +142,6 @@ hosts:
role: document-archive role: document-archive
lan_ip: 192.168.8.130 lan_ip: 192.168.8.130
public_host: paperless.hubris.network public_host: paperless.hubris.network
mesh:
tailscale:
fqdn: paperless
mounts: mounts:
- /mnt/library - /mnt/library
age_pubkey: '' age_pubkey: ''
@@ -161,9 +154,6 @@ hosts:
lan_ip: 192.168.8.121 lan_ip: 192.168.8.121
public_host: git.hubris.network public_host: git.hubris.network
backend_port: 3000 backend_port: 3000
mesh:
tailscale:
fqdn: gitea
mounts: mounts:
- /mnt/library - /mnt/library
notes: notes:
@@ -178,10 +168,6 @@ hosts:
lan_ip: 192.168.8.205 lan_ip: 192.168.8.205
public_hosts: public_hosts:
- artifacto.hubris.network - artifacto.hubris.network
mesh:
tailscale:
ip: 100.121.171.122
fqdn: apps
mounts: mounts:
- /mnt/library - /mnt/library
runs: runs:
@@ -198,9 +184,6 @@ hosts:
role: file-sync role: file-sync
lan_ip: 192.168.8.224 lan_ip: 192.168.8.224
public_host: cloud.hubris.network public_host: cloud.hubris.network
mesh:
tailscale:
fqdn: nextcloud
mounts: mounts:
- /mnt/library - /mnt/library
age_pubkey: '' age_pubkey: ''
@@ -212,9 +195,6 @@ hosts:
role: matrix-server role: matrix-server
lan_ip: 192.168.8.239 lan_ip: 192.168.8.239
public_host: matrix.hubris.network public_host: matrix.hubris.network
mesh:
tailscale:
fqdn: elementsynapse
sophia: sophia:
kind: lxc kind: lxc
pve_id: 119 pve_id: 119
@@ -222,9 +202,6 @@ hosts:
os: linux os: linux
role: workshop role: workshop
lan_ip: 192.168.8.109 lan_ip: 192.168.8.109
mesh:
tailscale:
fqdn: sophia
mounts: mounts:
- /mnt/library - /mnt/library
age_pubkey: '' age_pubkey: ''
@@ -236,9 +213,6 @@ hosts:
role: photo-management role: photo-management
lan_ip: 192.168.8.136 lan_ip: 192.168.8.136
public_host: photos.hubris.network public_host: photos.hubris.network
mesh:
tailscale:
fqdn: muleimage
mounts: mounts:
- /mnt/library - /mnt/library
age_pubkey: '' age_pubkey: ''
@@ -266,9 +240,6 @@ hosts:
- jellyseerr.hubris.network - jellyseerr.hubris.network
- qbit.hubris.network - qbit.hubris.network
- sab.hubris.network - sab.hubris.network
mesh:
tailscale:
fqdn: arr
mounts: mounts:
- /mnt/library - /mnt/library
age_pubkey: '' age_pubkey: ''
@@ -299,9 +270,6 @@ hosts:
os: linux os: linux
role: home-automation role: home-automation
lan_ip: 192.168.8.101 lan_ip: 192.168.8.101
mesh:
tailscale:
fqdn: homeassistant
republic-laptop: republic-laptop:
kind: workstation kind: workstation
os: linux os: linux

View File

@@ -18,7 +18,7 @@ operational reference is here.
| --- | --- | --- | | --- | --- | --- |
| Hostname matches an entry in `inventory.yaml` | The bootstrap looks up `hosts/$(hostname).yaml`. | `hostname` (Linux) / `scutil --get LocalHostName` (macOS) | | Hostname matches an entry in `inventory.yaml` | The bootstrap looks up `hosts/$(hostname).yaml`. | `hostname` (Linux) / `scutil --get LocalHostName` (macOS) |
| OS is Linux or macOS | bootstrap detects via `uname -s` | `uname -s` | | OS is Linux or macOS | bootstrap detects via `uname -s` | `uname -s` |
| On the mesh (Netbird or Tailscale) **or** on the LAN | issuance is gated to mesh + LAN subnets. **For Netbird: use a setup-key, not interactive auth** — see "Getting onto Netbird" below. | `netbird status` / `tailscale status` | | On the Netbird mesh **or** on the LAN | issuance is gated to mesh + LAN subnets. **For Netbird: use a setup-key, not interactive auth** — see "Getting onto Netbird" below. | `netbird status` |
| `git`, `python3`, `python3-yaml`, `age`, `sops` | bootstrap preflight; `homelab` CLI imports yaml | See per-OS commands below | | `git`, `python3`, `python3-yaml`, `age`, `sops` | bootstrap preflight; `homelab` CLI imports yaml | See per-OS commands below |
| Can resolve `*.hubris.network` | bootstrap calls `https://secrets.hubris.network/issue` and writes `https://mcp.hubris.network/mcp` | `dig +short mcp.hubris.network` (should return `192.168.8.175`) | | Can resolve `*.hubris.network` | bootstrap calls `https://secrets.hubris.network/issue` and writes `https://mcp.hubris.network/mcp` | `dig +short mcp.hubris.network` (should return `192.168.8.175`) |