diff --git a/README.md b/README.md index c3e5bea..afe1d55 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ bug but the honest answer, because proofs are claims about a specific tree on a |---|---| | 0 Foundations | ServiceDefinition schema + renderer, Kyverno L0 gate, conformance harness, CI | | 1 Cluster bring-up | 3-node k3s HA, embedded etcd, kube-vip VIP, Cilium, Flux, SOPS. Originally Proxmox VMs; **now 3× mini-PC bare metal** (`k3s-prod-1/2/3`, `192.168.2.10/.11/.12`, k3s v1.36.4+k3s1, Debian 13) | -| 2 Platform core | Cilium · Longhorn · cert-manager (LE-prod wildcard) · ExternalDNS · Gateway API · split-horizon DNS, **both halves**: CoreDNS for pods, and **PiHole as the authoritative LAN resolver** for `${BASE_DOMAIN}` on udp/53 + tcp/53 at a pinned VIP (`192.168.2.23` on prod). The LAN half was claimed from the start but did not exist until 2026-09-14 — PiHole's Service was ClusterIP :80/:4180, so LAN clients ran on hand-maintained per-client `dnsmasq` files | +| 2 Platform core | Cilium · Longhorn · cert-manager (LE-prod wildcard) · ExternalDNS · Gateway API · split-horizon DNS, **both halves**: CoreDNS for pods, and **PiHole as the LAN resolver**, answering an explicit list of `${BASE_DOMAIN}` names (LAN-only apps, gitea/nextcloud/keycloak/auth, the L4 VIPs — everything else resolves publicly to Cloudflare, since 2026-11-09) on udp/53 + tcp/53 at a pinned VIP (`192.168.2.23` on prod). The LAN half was claimed from the start but did not exist until 2026-09-14 — PiHole's Service was ClusterIP :80/:4180, so LAN clients ran on hand-maintained per-client `dnsmasq` files | | 3 Egress / VPN | ADR-0015 node-level OpenVPN split-tunnel + Cilium egress-gateway steering + host kill-switch | | 4 Observability | kube-prometheus-stack · Loki · Alloy · Alertmanager. **Velero unblocked 2026-09-13** — backs up to Cloudflare R2 (OQ-BKP closed; the `velero-plugin-for-aws` tagging incompatibility needed a pinned plugin). Longhorn has **no** backup target by decision: volume data is protected by Velero + kopia to R2. image-automation authored but disabled | | 5 IaaS | KubeVirt + CDI on real nested KVM; live-migratable RWX VM storage. The UniFi VM runs, but **UniFi OS Server is not yet installed in the guest** | diff --git a/apps/vms/unifi/README.md b/apps/vms/unifi/README.md index d5093e7..9e7744f 100644 --- a/apps/vms/unifi/README.md +++ b/apps/vms/unifi/README.md @@ -176,8 +176,8 @@ or any UDP (`3478`/`10001`) — exactly the ports adoption rides. So: **UI access:** `https://unifi.home.keithjackson.dev:11443`. That name resolves to the VIP and not to the Gateway on purpose: both `platform/coredns/coredns-custom.yaml` (in-cluster) and -`apps/wave-0/pihole.yaml` (LAN split-horizon) carve `unifi.${BASE_DOMAIN}` out of the -zone-wide `${GATEWAY_VIP}` answer and point it at `${UNIFI_L4_VIP}`. `${BASE_DOMAIN}` on prod is the +`apps/wave-0/pihole.yaml` (LAN split-horizon, an explicit name list since 2026-11-09) point +`unifi.${BASE_DOMAIN}` at `${UNIFI_L4_VIP}` rather than `${GATEWAY_VIP}`. `${BASE_DOMAIN}` on prod is the apex `home.keithjackson.dev` (confirmed live in the `cluster-vars` ConfigMap) after the 2026-09-13 flip, so this hostname is current, not a dev leftover. diff --git a/apps/wave-0/pihole.yaml b/apps/wave-0/pihole.yaml index 16c5b43..60e27ec 100644 --- a/apps/wave-0/pihole.yaml +++ b/apps/wave-0/pihole.yaml @@ -73,7 +73,7 @@ spec: - name: FTLCONF_dns_rateLimit_count value: '0' - name: FTLCONF_misc_dnsmasq_lines - value: local-ttl=60;address=/${BASE_DOMAIN}/${GATEWAY_VIP};address=/unifi.${BASE_DOMAIN}/${UNIFI_L4_VIP};address=/crafty-mc.${BASE_DOMAIN}/${CRAFTY_L4_VIP};address=/pihole-dns.${BASE_DOMAIN}/${PIHOLE_DNS_VIP};address=/plex-vip.${BASE_DOMAIN}/${PLEX_L4_VIP} + value: local-ttl=60;address=/gitea.${BASE_DOMAIN}/${GATEWAY_VIP};address=/nextcloud.${BASE_DOMAIN}/${GATEWAY_VIP};address=/keycloak.${BASE_DOMAIN}/${GATEWAY_VIP};address=/auth.${BASE_DOMAIN}/${GATEWAY_VIP};address=/headlamp.${BASE_DOMAIN}/${GATEWAY_VIP};address=/jackett.${BASE_DOMAIN}/${GATEWAY_VIP};address=/pihole.${BASE_DOMAIN}/${GATEWAY_VIP};address=/plex.${BASE_DOMAIN}/${GATEWAY_VIP};address=/qbittorrent.${BASE_DOMAIN}/${GATEWAY_VIP};address=/radarr.${BASE_DOMAIN}/${GATEWAY_VIP};address=/semaphore.${BASE_DOMAIN}/${GATEWAY_VIP};address=/sonarr.${BASE_DOMAIN}/${GATEWAY_VIP};address=/unifi.${BASE_DOMAIN}/${UNIFI_L4_VIP};address=/crafty-mc.${BASE_DOMAIN}/${CRAFTY_L4_VIP};address=/pihole-dns.${BASE_DOMAIN}/${PIHOLE_DNS_VIP};address=/plex-vip.${BASE_DOMAIN}/${PLEX_L4_VIP} - name: FTLCONF_webserver_api_password valueFrom: secretKeyRef: diff --git a/catalog/pihole.yaml b/catalog/pihole.yaml index 76aa595..9631b77 100644 --- a/catalog/pihole.yaml +++ b/catalog/pihole.yaml @@ -108,41 +108,61 @@ env: # `loadBalancer.mode: dsr`, which preserves the client address. - name: FTLCONF_dns_rateLimit_count value: "0" - # THE SPLIT-HORIZON ANSWERS. A dnsmasq `address=//` entry answers that domain AND - # every subdomain of it, and dnsmasq resolves overlapping entries by LONGEST MATCHING DOMAIN — - # so the first line is a wildcard for the whole zone and each later line is a specific override. - # PRECEDENCE IS THEREFORE STRUCTURAL, not order-dependent: `unifi.${BASE_DOMAIN}` matches both - # line 1 and line 2, line 2 is the longer domain, line 2 wins. Rewriting these in any order - # changes nothing. + # THE LAN ANSWERS — AN EXPLICIT LIST, NOT A ZONE WILDCARD (2026-11-09). A dnsmasq + # `address=//` entry answers that domain AND every subdomain of it (on LABEL + # boundaries: `pihole.` does not match `pihole-dns.`), and dnsmasq resolves overlapping entries by + # LONGEST MATCHING DOMAIN, so the order below is irrelevant. Every name NOT listed here is + # FORWARDED to the upstreams above and gets the PUBLIC answer. # - # A NEWLY ONBOARDED SERVICE NEEDS NO CHANGE HERE. Every catalog ingressRoute host is - # `