Dynadot DDNS Pipeline (Discovery)
Why: Documenting an active (but currently broken) service that pre-dates the journal — captured during the 2026-05 audit so the cheat sheet has a citation target and the known bug has a permanent record.
This server sits behind a residential ISP connection with a dynamic public IP. To keep DNS records pointing at the right address, a custom DDNS pipeline runs every five minutes from the user crontab. The system has two moving parts: a Bash poller that detects IP changes and a Python script that talks to the Dynadot API. A separate ddns-go container also exists from an earlier implementation; the relationship between the two is a known open question (see below).
1. ip-watch.sh and dynadot-update.py
/home/plex/bin/ip-watch.sh runs on a */5 * * * * cron schedule (user crontab, plex). It compares the current public IP against a state file at /home/plex/.cache/ip-watch.last. When a change is detected it calls /home/plex/bin/dynadot-update.py, passing the domain list and new IP. dynadot-update.py authenticates to the Dynadot API and updates the A record(s). The domain list and Telegram credentials consumed by both scripts are stored in /home/plex/.messaging-keys (mode 0600, plex:plex) — refer to that file for the actual values; they are not reproduced here.
Logs go to /home/plex/.cache/ip-watch.log. A Telegram notification is sent on a successful update via /usr/local/bin/telegram_notify.sh.
As of the audit, .messaging-keys lists only skyhouse.dev in DOMAINS. The runbook describes the expected configuration as skyhouse.dev,room101.com; room101.com is currently unmanaged and its A record is stale.
2. ddns-go container
A jeessy/ddns-go container is running and exposes a management UI on port 9876 (LAN only — no external port forward). Per the February 2026 monitoring-setup doc, ddns-go was the original DDNS path. Today, ip-watch.sh is the active authority for Dynadot updates. The audit did not determine whether ddns-go is still configured to manage any records or is effectively idle. Resolving which system is authoritative is a pending Phase B decision — until then, both coexist.
3. Known issue: set -e / ((x++)) bug stalls state-file writes
The audit found a bug in /home/plex/bin/ip-watch.sh that causes the script to silently exit before writing the state file. The line ((success_count++)) uses Bash's arithmetic command form, which exits with code 1 when the expression evaluates to 0 (i.e., on the very first iteration, before any increment). Because the script opens with set -euo pipefail, this non-zero exit kills the process immediately after calling dynadot-update.py.
Operational impact: DNS for skyhouse.dev happens to be correct because the first-ever run updated it successfully before the bug fires. However, /home/plex/.cache/ip-watch.last is permanently stuck at the pre-update IP (174.21.177.8), so any future IP change will not auto-update — the script will re-detect the change every five minutes but never advance past the python call. The fix (success_count=$((success_count + 1))) is straightforward but has not yet been applied; a later session will patch it.
Until the bug is fixed, DDNS auto-update is effectively broken for any future IP change — manual intervention would be required.
← Back to Admin Hub