coredns/scripts/prepare-zones.sh
Ryan Malloy 5afdb05667 zones: replace all A 100.79.95.190 with CNAME rpm-bullet.mer.idahomuellers.net
27 records across 15 zones converted from direct A records pointing at
the Tailscale endpoint (100.79.95.190) to CNAMEs pointing at the
Tailscale-named alias. Now if the underlying Tailscale node's IP
changes, only the rpm-bullet record needs updating instead of
chasing 27 zones.

Affected zones (all *.l labels + a handful of dev / dev.mary names):
  acrazy.org      copper-springs.online   demostar.io      flonhoney.com
  homestar.ink    kg7q.cc                 malloys.us       ourjob.site
  qubeseptic.com  ryanmalloy.com          septic.report    sidejob.pro
  supported.systems  warehack.ing         zmesh.systems

No CNAME collisions: none of the converted names had other records
(MX/TXT/SRV/CAA/AAAA) at the same exact name. _acme-challenge.<sub>.l
records sit at distinct subdomains and continue to resolve independently
(verified: TXT lookups for known _acme-challenge.l.* names still return
the original values).

Also fixed prepare-zones.sh: added `|| true` after the serial-detection
grep so a zero-match (first run of a new day) doesn't trip `set -e`
and abort the whole prep.
2026-05-17 03:29:34 -06:00

126 lines
4.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# Injects an SOA record + canonical NS records into each raw Vultr zone
# file, then writes the result to zones-prepared/. Source files in zones/
# are never modified.
#
# Corrections applied to each zone:
# 1. Synthesize SOA — Vultr's export omits it. SOA mname is ns1.he.net
# because Hurricane Electric secondaries serve the public face of
# these zones (hidden-primary architecture).
# 2. Strip the source's ns1.vultr.com / ns2.vultr.com NS records and
# replace with the five HE nameservers, so AXFR-pulled zones at HE
# advertise the correct delegation.
# 3. Apex disambiguation: lines starting with leading-TAB-then-TTL get
# "@" prepended (Vultr's apex convention vs. RFC 1035 inheritance).
# 4. Dot-terminate NS/MX/CNAME rdata (Vultr exports unqualified names).
set -euo pipefail
SRC_DIR="${SRC_DIR:-zones}"
DST_DIR="${DST_DIR:-zones-prepared}"
ADMIN_EMAIL="${ADMIN_EMAIL:-admin}" # becomes admin.<zone>.
# Serial number generation — YYYYMMDDNN format (RFC 1912 §2.2).
#
# Strategy: every `make prep` run produces a strictly-increasing serial
# so that HE slaves notice the change on their next poll. If today's
# previous serials exist in zones-prepared/, increment the 2-digit
# counter. Otherwise start at NN=01.
#
# Honors an explicit override: `SERIAL=2026051699 make prep` skips the
# auto-detection.
TODAY=$(date +%Y%m%d)
if [[ -z "${SERIAL:-}" ]]; then
# Pull the highest YYYYMMDDNN serial from currently-prepared zones
# that starts with today's date. If none, default to NN=01.
# `|| true` so a zero-match grep doesn't trip `set -e`. Empty $highest
# then triggers the "first run of the day" branch below.
highest=$(grep -hE '^[[:space:]]+'"${TODAY}"'[0-9]{2}[[:space:]]+;' "$DST_DIR"/*.zone 2>/dev/null \
| awk '{print $1}' | sort -un | tail -1 || true)
if [[ -n "$highest" ]]; then
nn=$((10#${highest:8:2}))
next_nn=$((nn + 1))
if (( next_nn > 99 )); then
echo "ERROR: serial counter exhausted for ${TODAY} (NN=99 reached)." >&2
echo "Set SERIAL manually or wait until tomorrow." >&2
exit 1
fi
SERIAL=$(printf "%s%02d" "$TODAY" "$next_nn")
else
SERIAL="${TODAY}01"
fi
fi
# Public-facing nameservers (Hurricane Electric free secondary service).
# These appear in NS records inside every zone so that recursive
# resolvers fetching the zone learn the correct delegation.
HE_NAMESERVERS=(
"ns1.he.net."
"ns2.he.net."
"ns3.he.net."
"ns4.he.net."
"ns5.he.net."
)
mkdir -p "$DST_DIR"
count=0
for src in "$SRC_DIR"/*.zone; do
fname=$(basename "$src")
zone="${fname%.zone}"
dst="$DST_DIR/$fname"
{
echo "; Auto-prepared by scripts/prepare-zones.sh on $(date -Iseconds)"
echo "; Source: $src"
echo "\$ORIGIN ${zone}."
echo "\$TTL 3600"
echo "@ 3600 IN SOA ns1.he.net. ${ADMIN_EMAIL}.${zone}. ("
echo " ${SERIAL} ; serial — bump per change (SERIAL=YYYYMMDDNN make prep)"
echo " 3600 ; refresh (1 hour)"
echo " 1800 ; retry (30 minutes)"
echo " 604800 ; expire (1 week)"
echo " 300 ; minimum (5 minutes)"
echo " )"
echo ""
# Inject HE nameservers as the authoritative NS set.
for ns in "${HE_NAMESERVERS[@]}"; do
echo "@ 3600 IN NS ${ns}"
done
echo ""
# Strip source's own $ORIGIN / $TTL / comments AND drop ns?.vultr.com
# NS records (we just emitted HE's NS set above). Then run the awk
# transformations for apex disambiguation and rdata dot-termination.
grep -vE '^\$(ORIGIN|TTL)|^;' "$src" \
| grep -vE '[[:space:]]NS[[:space:]]+ns[12]\.vultr\.com\.?[[:space:]]*$' \
| awk '
NF == 0 { print; next }
{
# (a) Detect Vultr-style apex line: leading whitespace, then TTL,
# then "IN". Prepend "@" so the owner is explicit.
if ($0 ~ /^[[:space:]]+[0-9]+[[:space:]]+IN[[:space:]]/) {
sub(/^[[:space:]]+/, "@\t", $0)
}
# (b) Dot-terminate trailing hostname for NS/CNAME/MX rdata.
type = ""
for (i = 1; i <= NF; i++) {
if ($i == "NS" || $i == "CNAME" || $i == "MX") { type = $i; break }
}
if (type != "") {
target = $NF
if (index(target, ".") > 0 && substr(target, length(target), 1) != ".") {
sub(/[[:space:]]+$/, "", $0)
print $0 "."
next
}
}
print
}
'
} > "$dst"
count=$((count + 1))
done
echo "Prepared ${count} zone files in ${DST_DIR}/ (serial=${SERIAL})"