feat(dns-updater): always-on node client (systemd timer self-report)
Reusable dyndns client for always-on, region-mobile nodes (the Prospector PWA on lime): install-client.sh drops dyndns-update.sh + a systemd oneshot/timer that self-reports the node public IP to dns.ct.uvlava.com on boot and every 5 min, so prospector.ct.uvlava.com tracks the node across region moves while the node stays up. Token + host in /etc/dyndns-updater (0600). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
25f58cdc3c
commit
e89cca3dc9
5 changed files with 118 additions and 3 deletions
|
|
@ -101,11 +101,21 @@ curl -fsS -H "Authorization: Bearer $DNS_UPDATER_TOKEN" \
|
|||
"https://dns.ct.uvlava.com/nic/update?hostname=live.ct.uvlava.com&myip=$DROPLET_IP"
|
||||
```
|
||||
|
||||
**Prospector node (on boot / region move)** — a systemd oneshot self-reports its
|
||||
public IP (`myip` omitted → caller IP):
|
||||
**Prospector node (always-on, on boot + every 5 min)** — install the reusable
|
||||
client (`client/`), which self-reports the node's public IP (`myip` omitted →
|
||||
caller IP) via a systemd timer so the record tracks the node across moves:
|
||||
|
||||
```bash
|
||||
curl -fsS -H "Authorization: Bearer $DNS_UPDATER_TOKEN" \
|
||||
# on the node, as root:
|
||||
DNS_HOSTNAME=prospector.ct.uvlava.com NODE_TOKEN=<token> \
|
||||
./client/install-client.sh
|
||||
```
|
||||
|
||||
It installs `dyndns-update.sh` + a `dyndns-updater.timer` (OnBootSec + every
|
||||
5 min). Under the hood each tick is:
|
||||
|
||||
```bash
|
||||
curl -fsS -H "Authorization: Bearer $TOKEN" \
|
||||
"https://dns.ct.uvlava.com/nic/update?hostname=prospector.ct.uvlava.com"
|
||||
```
|
||||
|
||||
|
|
|
|||
36
services/dns-updater/client/dyndns-update.sh
Executable file
36
services/dns-updater/client/dyndns-update.sh
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env bash
|
||||
# Self-report this node's public IP to the dns-updater (dyndns2), repointing its
|
||||
# own A record. For always-on, region-mobile nodes (e.g. the Prospector PWA on
|
||||
# lime): run on boot and on a timer so the record tracks the node across moves.
|
||||
#
|
||||
# Config: /etc/dyndns-updater/dyndns.conf
|
||||
# DNS_UPDATER_URL=https://dns.ct.uvlava.com
|
||||
# DNS_HOSTNAME=prospector.ct.uvlava.com
|
||||
# DNS_TOKEN_FILE=/etc/dyndns-updater/token # file containing the node token
|
||||
#
|
||||
# myip is omitted, so the updater uses the caller's observed public IP.
|
||||
set -euo pipefail
|
||||
|
||||
CONF="${DYNDNS_CONF:-/etc/dyndns-updater/dyndns.conf}"
|
||||
[[ -f "$CONF" ]] || { echo "missing config: $CONF" >&2; exit 1; }
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONF"
|
||||
|
||||
: "${DNS_UPDATER_URL:?DNS_UPDATER_URL not set in $CONF}"
|
||||
: "${DNS_HOSTNAME:?DNS_HOSTNAME not set in $CONF}"
|
||||
TOKEN_FILE="${DNS_TOKEN_FILE:-/etc/dyndns-updater/token}"
|
||||
[[ -f "$TOKEN_FILE" ]] || { echo "missing token file: $TOKEN_FILE" >&2; exit 1; }
|
||||
|
||||
TOKEN="$(tr -d '[:space:]' < "$TOKEN_FILE")"
|
||||
[[ -n "$TOKEN" ]] || { echo "empty token in $TOKEN_FILE" >&2; exit 1; }
|
||||
|
||||
resp="$(curl -fsS --retry 3 --retry-delay 2 --max-time 20 \
|
||||
-H "Authorization: Bearer ${TOKEN}" \
|
||||
"${DNS_UPDATER_URL}/nic/update?hostname=${DNS_HOSTNAME}")"
|
||||
|
||||
echo "dyndns ${DNS_HOSTNAME}: ${resp}"
|
||||
# dyndns2 success bodies start with "good" or "nochg"; anything else is a failure.
|
||||
case "$resp" in
|
||||
good*|nochg*) exit 0 ;;
|
||||
*) echo "unexpected response" >&2; exit 1 ;;
|
||||
esac
|
||||
14
services/dns-updater/client/dyndns-updater.service
Normal file
14
services/dns-updater/client/dyndns-updater.service
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[Unit]
|
||||
Description=Self-report public IP to dns.ct.uvlava.com (dyndns2)
|
||||
Documentation=https://forge.ct.uvlava.com/quinn/uvlava/src/branch/main/services/dns-updater
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/dyndns-update.sh
|
||||
# Don't let a transient DNS/API failure mark the node degraded; the timer retries.
|
||||
SuccessExitStatus=0 1
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
14
services/dns-updater/client/dyndns-updater.timer
Normal file
14
services/dns-updater/client/dyndns-updater.timer
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[Unit]
|
||||
Description=Periodically refresh this node's A record via dns.ct.uvlava.com
|
||||
Documentation=https://forge.ct.uvlava.com/quinn/uvlava/src/branch/main/services/dns-updater
|
||||
|
||||
[Timer]
|
||||
# Fire shortly after boot, then every 5 minutes so the record tracks the node
|
||||
# across IP changes / region moves.
|
||||
OnBootSec=30s
|
||||
OnUnitActiveSec=5min
|
||||
AccuracySec=30s
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
41
services/dns-updater/client/install-client.sh
Executable file
41
services/dns-updater/client/install-client.sh
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
#!/usr/bin/env bash
|
||||
# Install the dyndns self-report client on an always-on, region-mobile node
|
||||
# (e.g. the Prospector PWA node on lime). Run as root ON THE NODE.
|
||||
#
|
||||
# Usage:
|
||||
# DNS_HOSTNAME=prospector.ct.uvlava.com NODE_TOKEN=<token> ./install-client.sh
|
||||
#
|
||||
# After install the node refreshes its A record on boot and every 5 minutes.
|
||||
set -euo pipefail
|
||||
|
||||
DNS_UPDATER_URL="${DNS_UPDATER_URL:-https://dns.ct.uvlava.com}"
|
||||
DNS_HOSTNAME="${DNS_HOSTNAME:?set DNS_HOSTNAME, e.g. prospector.ct.uvlava.com}"
|
||||
NODE_TOKEN="${NODE_TOKEN:?set NODE_TOKEN to this node dyndns token}"
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
install -m 0755 "${HERE}/dyndns-update.sh" /usr/local/bin/dyndns-update.sh
|
||||
install -d -m 0700 /etc/dyndns-updater
|
||||
|
||||
cat >/etc/dyndns-updater/dyndns.conf <<CONF
|
||||
DNS_UPDATER_URL=${DNS_UPDATER_URL}
|
||||
DNS_HOSTNAME=${DNS_HOSTNAME}
|
||||
DNS_TOKEN_FILE=/etc/dyndns-updater/token
|
||||
CONF
|
||||
chmod 0600 /etc/dyndns-updater/dyndns.conf
|
||||
|
||||
printf '%s' "${NODE_TOKEN}" >/etc/dyndns-updater/token
|
||||
chmod 0600 /etc/dyndns-updater/token
|
||||
|
||||
install -m 0644 "${HERE}/dyndns-updater.service" /etc/systemd/system/dyndns-updater.service
|
||||
install -m 0644 "${HERE}/dyndns-updater.timer" /etc/systemd/system/dyndns-updater.timer
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now dyndns-updater.timer
|
||||
# Fire once now so the record is correct immediately.
|
||||
systemctl start dyndns-updater.service || true
|
||||
|
||||
echo "Installed. Status:"
|
||||
systemctl --no-pager status dyndns-updater.service | tail -n 5 || true
|
||||
echo "Timer:"
|
||||
systemctl list-timers dyndns-updater.timer --no-pager || true
|
||||
Loading…
Add table
Reference in a new issue