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]>
49 lines
2.3 KiB
Bash
Executable File
49 lines
2.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# install.sh — install this repo's workstation dnsmasq profiles and reload the live resolver.
|
|
#
|
|
# Needs root (writes /etc/NetworkManager/dnsmasq.d). Run it as:
|
|
# sudo infra/workstation/dnsmasq/install.sh
|
|
#
|
|
# WHY A SCRIPT AND NOT A DOC LINE: these rules decide which CLUSTER this workstation talks to. Getting
|
|
# them into the wrong directory is silent — the file exists, looks installed, and changes nothing —
|
|
# so the destination and the reload are pinned here rather than retyped. Idempotent; safe to re-run.
|
|
#
|
|
set -euo pipefail
|
|
|
|
src_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
# THE LIVE RESOLVER IS NETWORKMANAGER'S dnsmasq, not the standalone service. NetworkManager.conf sets
|
|
# `dns=dnsmasq`, and the process it spawns runs with `--conf-file=/dev/null` plus
|
|
# `--conf-dir=/etc/NetworkManager/dnsmasq.d` — so /etc/dnsmasq.d is read ONLY by the standalone
|
|
# dnsmasq.service, which is inactive here. Installing to the wrong one is the failure this pins down.
|
|
dest_dir=/etc/NetworkManager/dnsmasq.d
|
|
|
|
[ "$(id -u)" = "0" ] || { echo "ERROR: must run as root — try: sudo $0" >&2; exit 1; }
|
|
|
|
if ! pgrep -af 'dnsmasq.*NetworkManager' >/dev/null 2>&1; then
|
|
echo "WARN: no NetworkManager-spawned dnsmasq is running." >&2
|
|
echo " Check 'dns=dnsmasq' in /etc/NetworkManager/NetworkManager.conf before trusting this." >&2
|
|
fi
|
|
|
|
install -d -m 0755 "${dest_dir}"
|
|
shopt -s nullglob
|
|
count=0
|
|
for f in "${src_dir}"/*.conf; do
|
|
install -m 0644 "$f" "${dest_dir}/$(basename "$f")"
|
|
echo "installed: ${dest_dir}/$(basename "$f")"
|
|
count=$((count+1))
|
|
done
|
|
[ "${count}" -gt 0 ] || { echo "ERROR: no .conf files found in ${src_dir}" >&2; exit 1; }
|
|
|
|
# dnsmasq does NOT re-read its config on SIGHUP (SIGHUP only reloads /etc/hosts and the caches), so a
|
|
# reload of NetworkManager — which respawns dnsmasq — is what actually applies these files.
|
|
echo "reloading NetworkManager (respawns dnsmasq with the new config)"
|
|
systemctl reload NetworkManager || systemctl restart NetworkManager
|
|
|
|
echo
|
|
echo "verify (expect prod's own VIPs, NOT dev's 192.168.2.50):"
|
|
echo " getent hosts gitea.prod.home.keithjackson.dev # -> 192.168.2.20"
|
|
echo " getent hosts unifi.prod.home.keithjackson.dev # -> 192.168.2.21"
|
|
echo " getent hosts gitea.home.keithjackson.dev # -> 192.168.2.50 (dev, unchanged)"
|