secondary: scaffold public CoreDNS secondary on ns.supported.systems
Adds a second non-HE public secondary that pulls AXFR from dell01 (the
hidden primary at 154.27.180.210) and answers public queries on
ns.supported.systems (64.177.113.227, 2001:19f0:5c00:4daa:5400:6ff:fe2d:38fa).
secondary/
Corefile generated, 84 zones + REFUSED catch-all
docker-compose.yml CoreDNS in host-net mode
Makefile up/down/logs/regen/test/axfr-test
.env / .env.example image pin + bind IPs
scripts/generate-secondary-corefile.sh reads ../zones/*.zone
scripts/notify-he.py → notify-secondaries.py
adds 64.177.113.227 as a second
NOTIFY target alongside HE's
216.218.130.2
Uses CoreDNS's `bind` plugin to avoid colliding with systemd-resolved
on loopback :53. Authoritative-only — non-listed zones get REFUSED, no
recursion. AXFR pull requires opening TCP/53 on dell01's FortiWiFi for
the secondary's IP (manual step, separate from this commit).
This commit is contained in:
parent
94f2bdc68a
commit
618e9504e7
9 changed files with 276 additions and 22 deletions
109
secondary/scripts/generate-secondary-corefile.sh
Executable file
109
secondary/scripts/generate-secondary-corefile.sh
Executable file
|
|
@ -0,0 +1,109 @@
|
|||
#!/usr/bin/env bash
|
||||
# Generate secondary/Corefile from the master zone list (../zones/*.zone).
|
||||
#
|
||||
# This server is a public-facing secondary — it pulls AXFR from dell01
|
||||
# (the hidden primary) and answers public queries. It is NOT recursive
|
||||
# and NOT a forwarder; queries for any zone outside the generated list
|
||||
# return REFUSED, which is correct behavior for an authoritative-only NS.
|
||||
#
|
||||
# Re-run this script whenever a zone is added to or removed from
|
||||
# zones/. It is idempotent — same input always produces the same Corefile.
|
||||
#
|
||||
# Source-of-truth precedence: zones/ in the parent repo is the canonical
|
||||
# list. zones-prepared/ on dell01 is a derived artifact; we don't read
|
||||
# from it to avoid coupling the secondary to dell01's prep state.
|
||||
set -euo pipefail
|
||||
|
||||
# Resolve relative to this script's location, not $PWD.
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SECONDARY_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
REPO_ROOT="$(cd "${SECONDARY_DIR}/.." && pwd)"
|
||||
|
||||
ZONES_DIR="${ZONES_DIR:-${REPO_ROOT}/zones}"
|
||||
COREFILE="${COREFILE:-${SECONDARY_DIR}/Corefile}"
|
||||
|
||||
# dell01's public IP — the address CoreDNS will send AXFR/IXFR requests
|
||||
# to and the source IP of inbound NOTIFY (dell01 NATs through this).
|
||||
PRIMARY_IP="${PRIMARY_IP:-154.27.180.210}"
|
||||
|
||||
# Per-deployment bind addresses. When set, the generated Corefile uses
|
||||
# CoreDNS's `bind` plugin to listen ONLY on these IPs. Required on hosts
|
||||
# where another service already binds loopback :53 (e.g. systemd-resolved
|
||||
# on 127.0.0.53). Leave unset to bind all interfaces (the default).
|
||||
#
|
||||
# Pass via env or set in secondary/.env so the Makefile auto-exports.
|
||||
BIND_V4="${BIND_V4:-}"
|
||||
BIND_V6="${BIND_V6:-}"
|
||||
|
||||
if [[ ! -d "$ZONES_DIR" ]]; then
|
||||
echo "ERROR: zones dir $ZONES_DIR not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Collect zone names — strip the .zone suffix, sort for deterministic output.
|
||||
mapfile -t zones < <(find "$ZONES_DIR" -maxdepth 1 -name '*.zone' -printf '%f\n' \
|
||||
| sed 's/\.zone$//' | sort)
|
||||
|
||||
if [[ ${#zones[@]} -eq 0 ]]; then
|
||||
echo "ERROR: no zones found in $ZONES_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build the space-separated zone list. CoreDNS accepts an arbitrary number
|
||||
# of zones in a single block header; with 84 zones this is one long line
|
||||
# but the parser handles it cleanly and a single block is more efficient
|
||||
# than 84 individual blocks (one plugin chain instead of 84).
|
||||
#
|
||||
# Each zone gets a trailing dot to remove ambiguity from the parser
|
||||
# (otherwise CoreDNS appends the default origin, which we don't want).
|
||||
zone_list=""
|
||||
for z in "${zones[@]}"; do
|
||||
zone_list+="${z}. "
|
||||
done
|
||||
zone_list="${zone_list% }"
|
||||
|
||||
{
|
||||
echo "# AUTO-GENERATED by secondary/scripts/generate-secondary-corefile.sh"
|
||||
echo "# Source: ${ZONES_DIR}/*.zone (${#zones[@]} zones)"
|
||||
echo "# Re-generated: $(date -Iseconds)"
|
||||
echo "# DO NOT EDIT BY HAND — re-run the generator instead."
|
||||
echo ""
|
||||
echo "# Public secondary for ${#zones[@]} zones. Pulls AXFR/IXFR from"
|
||||
echo "# ${PRIMARY_IP} (dell01 hidden primary) and serves the public face."
|
||||
echo "# Inbound NOTIFY from the same IP triggers immediate re-poll."
|
||||
echo "${zone_list} {"
|
||||
if [[ -n "$BIND_V4" || -n "$BIND_V6" ]]; then
|
||||
# Bind only to specified addresses. Required when systemd-resolved
|
||||
# or similar already owns loopback :53 — we share the port number
|
||||
# across different IPs but the kernel needs explicit non-overlapping
|
||||
# binds to allow it.
|
||||
echo " bind ${BIND_V4} ${BIND_V6}"
|
||||
fi
|
||||
echo " secondary {"
|
||||
echo " transfer from ${PRIMARY_IP}"
|
||||
echo " }"
|
||||
echo " log"
|
||||
echo " errors"
|
||||
echo " # No \`cache\` plugin — authoritative answers don't need it"
|
||||
echo " # and caching authoritative responses muddies TTL semantics."
|
||||
echo "}"
|
||||
echo ""
|
||||
echo "# Catch-all block: anything outside the authoritative zone list"
|
||||
echo "# returns REFUSED. We're not a recursive resolver — public clients"
|
||||
echo "# asking us to recurse get an explicit no."
|
||||
echo ". {"
|
||||
if [[ -n "$BIND_V4" || -n "$BIND_V6" ]]; then
|
||||
echo " bind ${BIND_V4} ${BIND_V6}"
|
||||
fi
|
||||
echo " errors"
|
||||
echo " log"
|
||||
echo " # No plugins that answer — empty chain → REFUSED."
|
||||
echo " # (The \`errors\` + \`log\` plugins record the attempt for visibility.)"
|
||||
echo "}"
|
||||
} > "$COREFILE"
|
||||
|
||||
bind_summary="all interfaces"
|
||||
if [[ -n "$BIND_V4" || -n "$BIND_V6" ]]; then
|
||||
bind_summary="${BIND_V4:-(none)} ${BIND_V6:-(none)}"
|
||||
fi
|
||||
echo "Wrote $COREFILE (${#zones[@]} zones, primary=${PRIMARY_IP}, bind=${bind_summary})"
|
||||
Loading…
Add table
Add a link
Reference in a new issue