From f1c21760a50f8088775e08ae540101be16dbcb35 Mon Sep 17 00:00:00 2001 From: satr14washere <90962949+satr14washere@users.noreply.github.com> Date: Sun, 22 Mar 2026 07:27:32 +0700 Subject: [PATCH] make generic --- .gitignore | 2 +- scripts/compare-zones.sh | 148 +++++++++++++++++++-------------------- 2 files changed, 75 insertions(+), 75 deletions(-) diff --git a/.gitignore b/.gitignore index 770061e..83e780f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,5 @@ creds.json types-dnscontrol.d.ts # Zone files -result +result* part-of.my.id.txt \ No newline at end of file diff --git a/scripts/compare-zones.sh b/scripts/compare-zones.sh index 8e93292..4bf234f 100755 --- a/scripts/compare-zones.sh +++ b/scripts/compare-zones.sh @@ -1,18 +1,16 @@ #!/usr/bin/env bash # -# compare-zones.sh — Compare a Cloudflare zone export against a nix-built zone file +# compare-zones.sh — Compare two BIND-format zone files +# +# Normalizes both files (strips comments, TTLs, SOA records, and whitespace +# differences) then performs a record-by-record comparison. # # Usage: -# ./scripts/compare-zones.sh [nix-result] -# -# Arguments: -# cloudflare-export.txt Path to the Cloudflare zone export (BIND format) -# nix-result Path to the nix-built zone file (default: ./result) +# ./scripts/compare-zones.sh # # Examples: -# ./scripts/compare-zones.sh part-of.my.id.txt +# ./scripts/compare-zones.sh expected.zone generated.zone # ./scripts/compare-zones.sh part-of.my.id.txt result -# nix build .#0 && ./scripts/compare-zones.sh part-of.my.id.txt result set -euo pipefail @@ -24,56 +22,57 @@ BOLD='\033[1m' RESET='\033[0m' usage() { - echo "Usage: $0 [nix-result]" + echo "Usage: $0 " echo "" - echo "Compare a Cloudflare zone export against a nix-built zone file." + echo "Compare two BIND-format zone files." echo "" echo "Arguments:" - echo " cloudflare-export.txt Path to the Cloudflare BIND zone export" - echo " nix-result Path to the nix-built zone file (default: ./result)" + echo " zone-file-a Path to the first zone file" + echo " zone-file-b Path to the second zone file" exit 1 } -if [[ $# -lt 1 ]]; then +if [[ $# -lt 2 ]]; then usage fi -CF_EXPORT="$1" -NIX_RESULT="${2:-./result}" +FILE_A="$1" +FILE_B="$2" -if [[ ! -f "$CF_EXPORT" ]]; then - echo -e "${RED}Error:${RESET} Cloudflare export not found: $CF_EXPORT" - exit 1 -fi +for f in "$FILE_A" "$FILE_B"; do + resolved="$f" + # Resolve symlinks (e.g. nix store results) + if [[ -L "$resolved" ]]; then + resolved="$(readlink -f "$resolved")" + fi + if [[ ! -f "$resolved" ]]; then + echo -e "${RED}Error:${RESET} File not found: $f" + exit 1 + fi +done -if [[ ! -e "$NIX_RESULT" ]]; then - echo -e "${RED}Error:${RESET} Nix result not found: $NIX_RESULT" - echo "Hint: run 'nix build .#0' first" - exit 1 -fi - -# If result is a symlink (nix build output), resolve it -if [[ -L "$NIX_RESULT" ]]; then - NIX_RESULT="$(readlink -f "$NIX_RESULT")" -fi +# Resolve symlinks for display +RESOLVED_A="$FILE_A" +RESOLVED_B="$FILE_B" +[[ -L "$FILE_A" ]] && RESOLVED_A="$(readlink -f "$FILE_A")" +[[ -L "$FILE_B" ]] && RESOLVED_B="$(readlink -f "$FILE_B")" TMPDIR="$(mktemp -d)" trap 'rm -rf "$TMPDIR"' EXIT # normalize_zone # -# Extracts resource records (A, AAAA, CNAME, MX, TXT, SRV, CAA, NS, SOA), -# strips comments, normalizes whitespace and TTLs, and sorts. +# Extracts resource records, strips comments, normalizes whitespace and TTLs, +# ensures FQDNs have trailing dots, skips SOA (which always differs), and sorts. normalize_zone() { local input="$1" local output="$2" - # 1. Remove comment-only lines and blank lines - # 2. Strip inline comments ("; ...") - # 3. Collapse whitespace - # 4. Normalize: (drop TTL) - # 5. Ensure FQDNs on NS/CNAME/MX targets end with a dot - # 6. Sort for stable comparison + # Resolve symlinks + if [[ -L "$input" ]]; then + input="$(readlink -f "$input")" + fi + grep -E '^\S+' "$input" \ | grep -v '^\s*;' \ | grep -v '^\s*$' \ @@ -86,7 +85,7 @@ normalize_zone() { # name TTL IN TYPE rdata... # name IN TYPE rdata... # - # We want to output: name TYPE rdata... + # We output: name TYPE rdata... name = $1 idx = 2 @@ -100,13 +99,13 @@ normalize_zone() { rtype = toupper($idx) idx++ - # Skip SOA — it will always differ (serial, timers) + # Skip SOA — serial and timers will always differ if (rtype == "SOA") next rdata = "" for (i = idx; i <= NF; i++) { val = $i - # Ensure trailing dot on targets for NS, CNAME, MX (last field) + # Ensure trailing dot on targets for NS, CNAME, MX if ((rtype == "NS" || rtype == "CNAME") && i == idx) { if (val !~ /\.$/) val = val "." } @@ -123,76 +122,77 @@ normalize_zone() { | sort > "$output" } +LABEL_A="$(basename "$FILE_A")" +LABEL_B="$(basename "$FILE_B")" + echo -e "${BOLD}Comparing zones${RESET}" -echo -e " Cloudflare export: ${CYAN}$CF_EXPORT${RESET}" -echo -e " Nix result: ${CYAN}$NIX_RESULT${RESET}" +echo -e " A: ${CYAN}${RESOLVED_A}${RESET}" +echo -e " B: ${CYAN}${RESOLVED_B}${RESET}" echo "" -normalize_zone "$CF_EXPORT" "$TMPDIR/cf.norm" -normalize_zone "$NIX_RESULT" "$TMPDIR/nix.norm" +normalize_zone "$FILE_A" "$TMPDIR/a.norm" +normalize_zone "$FILE_B" "$TMPDIR/b.norm" -CF_COUNT=$(wc -l < "$TMPDIR/cf.norm") -NIX_COUNT=$(wc -l < "$TMPDIR/nix.norm") +COUNT_A=$(wc -l < "$TMPDIR/a.norm") +COUNT_B=$(wc -l < "$TMPDIR/b.norm") -echo -e " Cloudflare records: ${BOLD}$CF_COUNT${RESET} (excluding SOA)" -echo -e " Nix records: ${BOLD}$NIX_COUNT${RESET} (excluding SOA)" +echo -e " A records: ${BOLD}$COUNT_A${RESET} (excluding SOA)" +echo -e " B records: ${BOLD}$COUNT_B${RESET} (excluding SOA)" echo "" # Compute differences -# Lines only in Cloudflare = missing from nix -# Lines only in nix = extra in nix -comm -23 "$TMPDIR/cf.norm" "$TMPDIR/nix.norm" > "$TMPDIR/only-cf.txt" -comm -13 "$TMPDIR/cf.norm" "$TMPDIR/nix.norm" > "$TMPDIR/only-nix.txt" -comm -12 "$TMPDIR/cf.norm" "$TMPDIR/nix.norm" > "$TMPDIR/matching.txt" +comm -23 "$TMPDIR/a.norm" "$TMPDIR/b.norm" > "$TMPDIR/only-a.txt" +comm -13 "$TMPDIR/a.norm" "$TMPDIR/b.norm" > "$TMPDIR/only-b.txt" +comm -12 "$TMPDIR/a.norm" "$TMPDIR/b.norm" > "$TMPDIR/matching.txt" MATCH_COUNT=$(wc -l < "$TMPDIR/matching.txt") -ONLY_CF_COUNT=$(wc -l < "$TMPDIR/only-cf.txt") -ONLY_NIX_COUNT=$(wc -l < "$TMPDIR/only-nix.txt") +ONLY_A_COUNT=$(wc -l < "$TMPDIR/only-a.txt") +ONLY_B_COUNT=$(wc -l < "$TMPDIR/only-b.txt") echo -e "${BOLD}Results${RESET}" -echo -e " ${GREEN}✓ Matching:${RESET} $MATCH_COUNT" -echo -e " ${RED}✗ Only in Cloudflare:${RESET} $ONLY_CF_COUNT (missing from nix build)" -echo -e " ${YELLOW}+ Only in Nix:${RESET} $ONLY_NIX_COUNT (extra in nix build)" +echo -e " ${GREEN}✓ Matching:${RESET} $MATCH_COUNT" +echo -e " ${RED}✗ Only in A:${RESET} $ONLY_A_COUNT" +echo -e " ${YELLOW}+ Only in B:${RESET} $ONLY_B_COUNT" echo "" -if [[ "$ONLY_CF_COUNT" -gt 0 ]]; then - echo -e "${RED}${BOLD}Records only in Cloudflare (missing from nix):${RESET}" +if [[ "$ONLY_A_COUNT" -gt 0 ]]; then + echo -e "${RED}${BOLD}Records only in A (${LABEL_A}):${RESET}" while IFS= read -r line; do echo -e " ${RED}-${RESET} $line" - done < "$TMPDIR/only-cf.txt" + done < "$TMPDIR/only-a.txt" echo "" fi -if [[ "$ONLY_NIX_COUNT" -gt 0 ]]; then - echo -e "${YELLOW}${BOLD}Records only in Nix (not in Cloudflare):${RESET}" +if [[ "$ONLY_B_COUNT" -gt 0 ]]; then + echo -e "${YELLOW}${BOLD}Records only in B (${LABEL_B}):${RESET}" while IFS= read -r line; do echo -e " ${YELLOW}+${RESET} $line" - done < "$TMPDIR/only-nix.txt" + done < "$TMPDIR/only-b.txt" echo "" fi -if [[ "$ONLY_CF_COUNT" -eq 0 && "$ONLY_NIX_COUNT" -eq 0 ]]; then +if [[ "$ONLY_A_COUNT" -eq 0 && "$ONLY_B_COUNT" -eq 0 ]]; then echo -e "${GREEN}${BOLD}✓ Zones are identical!${RESET}" exit 0 else - # Show a unified-style diff for a quick overview + # Unified diff echo -e "${BOLD}Diff (unified):${RESET}" diff -u \ - --label "cloudflare" "$TMPDIR/cf.norm" \ - --label "nix" "$TMPDIR/nix.norm" \ + --label "$LABEL_A" "$TMPDIR/a.norm" \ + --label "$LABEL_B" "$TMPDIR/b.norm" \ | head -80 || true echo "" - # Summarize by record type + # Summary by record type echo -e "${BOLD}Summary by record type:${RESET}" - echo -e " ${BOLD}Type CF-only Nix-only Matching${RESET}" + echo -e " ${BOLD}Type A-only B-only Matching${RESET}" { - cat "$TMPDIR/only-cf.txt" "$TMPDIR/only-nix.txt" "$TMPDIR/matching.txt" + cat "$TMPDIR/only-a.txt" "$TMPDIR/only-b.txt" "$TMPDIR/matching.txt" } | awk '{print $2}' | sort -u | while read -r rtype; do - cf_only=$(grep -c "^[^ ]* ${rtype} " "$TMPDIR/only-cf.txt" || true) - nix_only=$(grep -c "^[^ ]* ${rtype} " "$TMPDIR/only-nix.txt" || true) + a_only=$(grep -c "^[^ ]* ${rtype} " "$TMPDIR/only-a.txt" || true) + b_only=$(grep -c "^[^ ]* ${rtype} " "$TMPDIR/only-b.txt" || true) matching=$(grep -c "^[^ ]* ${rtype} " "$TMPDIR/matching.txt" || true) - printf " %-6s %7d %8d %8d\n" "$rtype" "$cf_only" "$nix_only" "$matching" + printf " %-6s %6d %6d %8d\n" "$rtype" "$a_only" "$b_only" "$matching" done echo ""