#!/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})"