OPERATOR CORRECTION (2026-09-05). I had been calling the resolver that shadows
prod "a pfSense override" throughout this repo. Both halves of that were wrong:
- It is the WORKSTATION'S OWN dnsmasq, not pfSense. The live resolver is the
one NetworkManager spawns (dns=dnsmasq, --conf-dir=/etc/NetworkManager/
dnsmasq.d); the standalone dnsmasq.service is INACTIVE, so /etc/dnsmasq.d is
dormant. The dev profile's `address=/.home.keithjackson.dev/192.168.2.50`
matches the domain AND EVERY SUBDOMAIN, so it also swallowed the whole new
prod.home.* tree and answered it with dev's address.
- The k3s prod cluster is NOT behind pfSense. It sits on the SOHO router
network 192.168.2.0/24, which is pfSense's WAN; pfSense CONTAINS the dev LAN
192.168.1.0/24. This workstation is on 192.168.2.0/24 too, which is why the
rules below use prod's real VIPs and need no NAT hop, unlike dev's .50.
New infra/workstation/dnsmasq/ — authored, not click-ops:
20-prod.home.keithjackson.dev.conf prod.home.* -> 192.168.2.20 (Gateway),
unifi -> .21, crafty-mc -> .22 (L4 VIPs)
install.sh installs + reloads; refuses non-root
dnsmasq resolves `address=` by LONGEST MATCH, so the prod rules win WITHOUT
editing or reordering the dev profile. PROVEN, not assumed: ran a throwaway
dnsmasq on 127.0.0.1:5354 loaded with both profiles —
prod.home / gitea.prod / keycloak.prod -> 192.168.2.20
unifi.prod -> 192.168.2.21 crafty-mc.prod -> 192.168.2.22
gitea.home / home.keithjackson.dev -> 192.168.2.50 (DEV, UNCHANGED)
`dnsmasq --test` on the new file: syntax OK.
install.sh pins the destination because getting it wrong is SILENT: a file in
/etc/dnsmasq.d exists, looks installed, and changes nothing. It also uses
`systemctl reload NetworkManager` rather than SIGHUP — dnsmasq does not re-read
its config on SIGHUP, so a HUP would look like it worked and not apply anything.
WHY THE MISLABEL MATTERED: the trap PARTIALLY works. Dev serves a
*.home.keithjackson.dev wildcard cert, which matches prod.home.keithjackson.dev
(one label) so the prod apex returns 200 over VALID TLS, while
keycloak.prod.home.keithjackson.dev (two labels) fails with a certificate-name
error. It reads as a broken prod cert; it is the wrong server answering. I made
exactly that misreading during verification.
Also recorded, because it changes what the apex flip IS: the SOHO router NATs
inbound 443 to the pfSense WAN (192.168.2.102) to serve the EXISTING NON-K3S
production environment, which must stay live until the switch is deliberately
made. So §10a sequence P is not just a k3s-side DNS/TLS change — it is the
legacy-prod cutover, and P as written does NOT cover repointing that NAT. The
two must be planned together or the apex resolves to k3s while inbound 443 still
lands on the legacy box. Phase C touches none of it: prod's Gateway is
192.168.2.20:443 (a different address) and its cert is DNS-01, needing no
inbound 443. And the MinIO timeout is now correctly described as expected
topology — crossing INTO pfSense from its WAN — not a misconfiguration.
VERIFIED: full CI gate PASS (spec-immutability, lint, l0, render-golden, l1).
Refs R-DNS-02, R-ENV-02.
Co-Authored-By: Claude Opus 5 <[email protected]>
50 lines
3.1 KiB
Plaintext
50 lines
3.1 KiB
Plaintext
# Workstation split-horizon DNS for the k3s PROD cluster.
|
|
#
|
|
# INSTALL TO: /etc/NetworkManager/dnsmasq.d/20-prod.home.keithjackson.dev.conf
|
|
# NOT /etc/dnsmasq.d/ — the standalone dnsmasq.service is INACTIVE on this workstation. The live
|
|
# resolver is the one NetworkManager spawns (NetworkManager.conf `dns=dnsmasq`), and it reads
|
|
# ONLY --conf-dir=/etc/NetworkManager/dnsmasq.d. A file dropped in /etc/dnsmasq.d/ changes nothing
|
|
# and looks like it should — verify with: pgrep -a dnsmasq
|
|
#
|
|
# WHY THIS FILE EXISTS
|
|
# The dev profile (10-home.keithjackson.dev.conf) contains:
|
|
# address=/.home.keithjackson.dev/192.168.2.50
|
|
# dnsmasq's `address=` matches the named domain AND EVERY SUBDOMAIN, so that one line also
|
|
# captures the entire `prod.home.keithjackson.dev` tree and answers it with DEV's address. Every
|
|
# prod hostname therefore resolved to dev from this workstation, while Cloudflare correctly
|
|
# returned prod's gateway.
|
|
#
|
|
# That failure is deceptive because it PARTIALLY works: dev serves a `*.home.keithjackson.dev`
|
|
# wildcard certificate, which matches `prod.home.keithjackson.dev` (one label) — so the prod apex
|
|
# returns 200 over valid TLS — but NOT `keycloak.prod.home.keithjackson.dev` (two labels), which
|
|
# fails with a certificate-name error. It reads like a broken prod certificate. It is not; it is
|
|
# the wrong server answering. NEVER verify prod's DNS or TLS through this workstation's resolver
|
|
# without confirming these rules are installed.
|
|
#
|
|
# dnsmasq resolves `address=` by LONGEST MATCH, so the more specific rules below win over the dev
|
|
# line without editing or reordering it. The dev profile stays untouched.
|
|
#
|
|
# TOPOLOGY (corrected 2026-09-05 by the operator — the earlier "pfSense override" framing was wrong)
|
|
# The k3s prod cluster is NOT behind pfSense. It sits on the SOHO router network,
|
|
# 192.168.2.0/24, which is pfSense's WAN side; pfSense contains 192.168.1.0/24 (the dev LAN).
|
|
# This workstation is on 192.168.2.0/24 too, so it reaches prod's VIPs DIRECTLY — no NAT hop,
|
|
# which is why these addresses are prod's real VIPs and not a forwarded address like dev's
|
|
# 192.168.2.50.
|
|
#
|
|
# The SOHO router NATs inbound 443 to the pfSense WAN address (192.168.2.102) to serve the
|
|
# EXISTING, NON-K3S production environment. That NAT is untouched by this file: these rules are
|
|
# local to this workstation's resolver only and change nothing about inbound traffic. The legacy
|
|
# prod stays live on 443 until the k3s cutover is deliberately made.
|
|
|
|
# The prod cluster's shared ingress Gateway (Cilium LB-IPAM; == CILIUM_LB_POOL_START).
|
|
# Covers the apex and every service hostname not overridden below.
|
|
address=/prod.home.keithjackson.dev/192.168.2.20
|
|
|
|
# L4 (non-HTTP) services get their OWN LoadBalancer VIPs, so they must NOT be sent to the Gateway.
|
|
# These are longer matches than the line above and therefore take precedence.
|
|
address=/unifi.prod.home.keithjackson.dev/192.168.2.21
|
|
address=/crafty-mc.prod.home.keithjackson.dev/192.168.2.22
|
|
|
|
# NOTE: no `local=` line is needed. The dev profile's `local=/home.keithjackson.dev/` already stops
|
|
# the whole tree — including prod.home.* — from being forwarded upstream.
|