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:
- DDNS updater containers (
favonia/cloudflare-ddns) — the source of truth for which domains exist and their proxy setting - NPM proxy hosts — every DDNS domain should have a corresponding NPM rule
- 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-Reference → Run 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 serverturn.thevish.io— TURN/STUN serverwww.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.ggwas moved from proxied → unproxied DDNS service (CF proxy breaks Matrix federation on port 8448). The CF record was patched withCF_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 thedns-audit.ymlworkflow — no further setup needed.