#!/bin/bash # Keep headscale.phasefinal.com's A record on NH3's current WAN v4. Token from the vault at run time. set -u SECRET=/home/lkraven/development/eshpfi-management/services/secrets-broker/secret # ⚠ SAY WHY. Both failure paths below used to `|| exit 1` in silence, and on # 2026-09-22 15:28 this unit failed for real: the failed-START alarm fired # correctly and carried NO CAUSE, because the script had printed nothing. An # alarm you cannot act on costs the same triage as no alarm at all. T=$($SECRET get nh3-dev/.config/cloudflare/infra-ops-dns-token 2>/dev/null) || { echo "FATAL: vault read failed for nh3-dev/.config/cloudflare/infra-ops-dns-token" >&2; exit 1; } [ -n "$T" ] || { echo "FATAL: vault returned an EMPTY token (read succeeded, value blank)" >&2; exit 1; } # The WAN lookup leans on a third party, so one blip should not page a human. # Measured 2026-09-22: the whole script takes ~18s of which ~17s is the vault # read, so three tries at a 10s cap is bounded and still well inside the 10-min # timer. Retries are announced -- a silent retry hides a degrading dependency. IP="" for try in 1 2 3; do IP=$(curl -s -m 10 -4 https://icanhazip.com | tr -d '[:space:]') [[ "$IP" =~ ^[0-9.]+$ ]] && break echo "WARN: WAN lookup attempt $try/3 returned [$IP]" >&2 sleep 2 done [[ "$IP" =~ ^[0-9.]+$ ]] || { echo "FATAL: could not determine WAN v4 after 3 attempts (icanhazip.com unreachable or returning junk); DNS left unchanged" >&2; exit 1; } ZID=$(curl -s -m 15 -H "Authorization: Bearer $T" "https://api.cloudflare.com/client/v4/zones?name=phasefinal.com" | python3 -c 'import sys,json; print(json.load(sys.stdin)["result"][0]["id"])') read -r RID CUR < <(curl -s -m 15 -H "Authorization: Bearer $T" "https://api.cloudflare.com/client/v4/zones/$ZID/dns_records?type=A&name=headscale.phasefinal.com" | python3 -c 'import sys,json; r=json.load(sys.stdin)["result"][0]; print(r["id"], r["content"])') [ "$CUR" = "$IP" ] && { echo "unchanged $IP"; exit 0; } curl -s -m 15 -X PATCH -H "Authorization: Bearer $T" -H "Content-Type: application/json" "https://api.cloudflare.com/client/v4/zones/$ZID/dns_records/$RID" -d "{\"content\":\"$IP\"}" | python3 -c 'import sys,json; d=json.load(sys.stdin); print("updated", d["success"], d["result"]["content"])'