Fleet changelogs · dev.ecs0.net
rdmsm4x-changelog-20260903-1735-fleet-dynamic-dns-cloudflare

rdmsm4x-changelog-20260903-1735-fleet-dynamic-dns-cloudflare

2026-09-03 17:12–17:35 EDT · claude@rdmsm4x · ticket TASK-20260903-31

Replaced hand-maintained fleet DNS with a per-host Cloudflare API updater on all six Macs. Every Mac now publishes its own LAN and Tailscale addresses to dataroo.net and keeps them current, instead of relying on records a human typed once and on UDM DHCP reservations.

Why

Rich asked how to configure macOS's "Use dynamic global hostname" against Cloudflare. That control is Apple's RFC 2136 / TSIG DNS UPDATE client; Cloudflare exposes no DNS UPDATE endpoint, only HTTPS, so the toggle can never work against it and should stay off. The equivalent capability is a small updater calling the Cloudflare API.

Scope

All six fleet Macs: rdmsm4x, rdmbair15m5, rdmbair13m5, jdmbair13m5, rdmpw3265m, rdmpw3275m. Cloudflare zone dataroo.net. No other zone touched; eastcoastscience.com deliberately untouched (it is the live mail/MDM zone).

What changed

New Cloudflare API token. Scoped to Zone:DNS Read + Write on dataroo.net only, token id 26224061762cf405c14847d115e7aa76. Verified to see exactly one zone. Stored as CLOUDFLARE_DDNS_TOKEN in ~/.secrets/global.env on each host, mode 600, with a dated comment. The pre-existing Global API Key was not reused for this. No secret value appears in any file, log, or record.

New script, canonical at rdmsm4x:~/dev/net/cloudflare/ddns/fleet_ddns.sh (v1.0.1), runtime copy at ~/scripts/fleet_ddns.sh on all six hosts, sha256 prefix bde065701909 on every one. bash 3.2, pure ASCII, Blocks theme, exit codes 0/10/20/30.

New LaunchAgent com.eastcoastscience.ddns on all six hosts: ~/Library/LaunchAgents/com.eastcoastscience.ddns.plist, 300s StartInterval, RunAtLoad, WatchPaths on /private/var/run/resolv.conf, ThrottleInterval 30, Nice 10.

DNS records now maintained automatically (TTL 60, DNS-only): <host>.dataroo.net A, <host>.ts.dataroo.net A and AAAA. Six LAN A records were created — two of them (rdmbair15m5, jdmbair13m5) had never existed anywhere. The ts records already existed from the 2026-08-21 normalization and were left as-is where correct.

Files modified: ~/.secrets/global.env (all six hosts, one comment + one key appended; backup ~/.secrets/global.env.bak-20260903-ddns mode 600 on each). Files added: ~/scripts/fleet_ddns.sh (six hosts), ~/Library/LaunchAgents/com.eastcoastscience.ddns.plist (six hosts), ~/dev/net/cloudflare/ddns/{fleet_ddns.sh,README.md,SESSION-STATE.md}, appended a section to ~/dev/net/cloudflare/PROJECT.md.

Verification evidence

Traps found, and the fixes

  1. The UDM intercepts outbound port 53. dig +short @8.8.8.8 rdmbair13m5.dataroo.net returns 192.168.0.131; a public resolver cannot know an RFC1918 address. An earlier report in this session listed <host>.dataroo.net records as existing in Cloudflare when Cloudflare held none — the answers came from the router. Verify this zone over DNS-over-HTTPS only. status does.
  2. Concurrent runs raced. launchd RunAtLoad fired while the rollout ran a manual pass; four hosts logged An identical record already exists. Fixed in v1.0.1 with a mkdir lock (5-minute stale reclaim) and by treating that error as success only after re-reading and confirming content. Records were correct throughout.
  3. A piped exit code lied. ... run | sed 's/^/ /' reported exit=0 from sed while two hosts had failed. Capture, then filter.
  4. command -v jq fails under launchd (minimal PATH omits /opt/homebrew/bin). Resolved by absolute path, as tailscale already was.
  5. Negative caching hid newly created records from the local resolver for minutes.

How to undo

Per host: bash ~/scripts/fleet_ddns.sh uninstall (removes the agent; DNS records are left untouched), then optionally rm ~/scripts/fleet_ddns.sh and restore ~/.secrets/global.env.bak-20260903-ddns. To revoke access entirely, delete the API token in the Cloudflare dashboard (id above) — that alone stops every host from writing. The six created LAN A records can be deleted in the dashboard if the whole idea is abandoned.

Outstanding owner actions

  1. UniFi credential needed. Rich asked to remove the UDM's local DNS entries via the UniFi API so Cloudflare is the single source of truth. ~/.secrets/udm.env holds only a root SSH password (itself flagged for rotation), and sshpass is not installed. The Network API needs a local-admin login or an API key. Endpoint confirmed present, returns 401: https://192.168.1.1/proxy/network/api/s/default/rest/dnsrecord. Entries appear to exist for four Macs only: rdmsm4x, rdmbair13m5, rdmpw3265m, rdmpw3275m.
  2. Private addresses are now in public DNS. <host>.dataroo.net publishes RFC1918 addresses worldwide. That is normal for this pattern and is what makes it work off the UDM, but it does disclose internal addressing. Say the word and the LAN names can be dropped, keeping only the Tailscale names.
  3. UDM_ROOT_PW in ~/.secrets/udm.env is still flagged for rotation from 2026-08-24.