Files
homelab-optimized/docs/guides/dns-audit.md
Gitea Mirror Bot e8924ce168
Some checks failed
Documentation / Build Docusaurus (push) Failing after 4m59s
Documentation / Deploy to GitHub Pages (push) Has been skipped
Sanitized mirror from private repository - 2026-04-05 12:45:10 UTC
2026-04-05 12:45:11 +00:00

5.2 KiB

DNS Audit Script

Script: .gitea/scripts/dns-audit.py Workflow: .gitea/workflows/dns-audit.yml (runs daily at 08:00 UTC, or manually)

Audits DNS consistency across three systems that must stay in sync:

  1. DDNS updater containers (favonia/cloudflare-ddns) — the source of truth for which domains exist and their proxy setting
  2. NPM proxy hosts — every DDNS domain should have a corresponding NPM rule
  3. Cloudflare DNS records — proxy settings in CF must match the DDNS config

What It Checks

Step What Pass condition
1 Parse DDNS compose files Finds all managed domains + proxy flags
2 Query NPM API Fetches all proxy host domains
3 DNS resolution Proxied domains resolve to CF IPs; unproxied to direct IPs
4 NPM ↔ DDNS cross-reference Every DDNS domain has an NPM rule and vice versa
5 Cloudflare audit CF proxy settings match DDNS config; flags unrecognised records
6 ntfy alert Sends notification if any check fails (only when NTFY_URL is set)

Running Manually

From the Gitea UI

Actions → DNS Audit & NPM Cross-ReferenceRun workflow

Locally (dry run — no changes made)

Run from the repo root:

cd /home/homelab/organized/repos/homelab

CF_TOKEN=<token> \
NPM_EMAIL=<email> \
NPM_PASSWORD="REDACTED_PASSWORD" \
python3 .gitea/scripts/dns-audit.py

CF_TOKEN is the CLOUDFLARE_API_TOKEN value from any of the DDNS compose files. NPM credentials are stored as Gitea secrets — check the Gitea Secrets UI to retrieve them.

Without NPM credentials

The script degrades gracefully — steps 1, 3, and 5 still run fully:

CF_TOKEN=<token> python3 .gitea/scripts/dns-audit.py

This still checks all DNS resolutions and audits all Cloudflare records. The NPM cross-reference (step 4) is skipped and the "DDNS-only" summary count will be inflated (it treats all DDNS domains as unmatched) — ignore it.

With auto-fix enabled

To automatically patch Cloudflare proxy mismatches (sets proxied to match DDNS):

CF_TOKEN=<token> CF_SYNC=true python3 .gitea/scripts/dns-audit.py

This makes live changes to Cloudflare DNS. Only use it when the DDNS config is correct and Cloudflare has drifted out of sync.


Environment Variables

Variable Required Description
CF_TOKEN Yes Cloudflare API token (same one used by DDNS containers)
NPM_EMAIL No NPM admin email — enables step 4 cross-reference
NPM_PASSWORD No NPM admin password
CF_SYNC No Set to true to auto-patch CF proxy mismatches
NTFY_URL No ntfy endpoint for failure alerts

DDNS Files Scanned

The script reads these compose files to build its domain list:

File Host Services
hosts/synology/atlantis/dynamicdnsupdater.yaml Atlantis vish.gg proxied, thevish.io proxied + unproxied
hosts/physical/concord-nuc/dyndns_updater.yaml concord-nuc api.vish.gg unproxied
hosts/physical/guava/portainer_yaml/dynamic_dns.yaml Guava crista.love
hosts/vms/seattle/ddns-updater.yaml Seattle st.vish.gg, stoatchat subdomains

Output Guide

OK   domain.vish.gg [CF] -> 104.21.x.x     # Proxied domain resolving to Cloudflare ✓
OK   api.vish.gg [direct] -> YOUR_WAN_IP     # Unproxied resolving to direct IP ✓
WARN domain: expected CF IP, got 1.2.3.4    # Proxied in DDNS but resolving directly ✗
ERR  domain: NXDOMAIN                        # Record missing entirely ✗
MISMATCH domain: CF=true DDNS=false          # Proxy flag out of sync — fix with CF_SYNC=true
INFO *.vish.gg [unmanaged-ok] [direct]       # Known manually-managed record, ignored
NEW? sub.vish.gg [proxied] ip=1.2.3.4       # In CF but not in any DDNS config — investigate

Known Exceptions

Domains in DDNS with no NPM rule (DDNS_ONLY_EXCEPTIONS)

These are legitimately in DDNS but don't need an NPM proxy entry:

  • mx.vish.gg — mail server
  • turn.thevish.io — TURN/STUN server
  • www.vish.gg, vish.gg, www.thevish.io, crista.love — root/www records

Cloudflare records not tracked by DDNS (CF_UNMANAGED_OK)

These are in Cloudflare but intentionally absent from DDNS configs:

  • *.vish.gg, *.crista.love, *.vps.thevish.io — wildcard catch-alls

To add a new exception, edit the DDNS_ONLY_EXCEPTIONS or CF_UNMANAGED_OK sets at the top of .gitea/scripts/dns-audit.py.


Last Run (2026-03-07)

57 domains across 4 DDNS files
32 NPM proxy hosts, 32 unique domains
57/57 DNS checks: all OK
✓  All NPM domains covered by DDNS
✓  All DDNS domains have an NPM proxy rule
Cloudflare: 60 A records audited, 0 proxy mismatches
✅ All 57 DDNS domains OK, CF and DDNS are in sync

Notes from this session

  • mx.vish.gg was moved from proxied → unproxied DDNS service (CF proxy breaks Matrix federation on port 8448). The CF record was patched with CF_SYNC=true.
  • CF cross-reference confirmed working end-to-end in CI (run 441, 2026-02-28): NPM credentials (NPM_EMAIL / NPM_PASSWORD) are stored as Gitea Actions secrets and are already injected into the dns-audit.yml workflow — no further setup needed.