diff --git a/warzone/deploy/docker/docker-compose.yml b/warzone/deploy/docker/docker-compose.yml index 6b5ab01..911952d 100644 --- a/warzone/deploy/docker/docker-compose.yml +++ b/warzone/deploy/docker/docker-compose.yml @@ -82,6 +82,18 @@ services: networks: - backend + # ─── Dynamic DNS updater (keeps A + AAAA current) ─── + dns-updater: + image: python:3-alpine + restart: unless-stopped + volumes: + - ./update-dns.sh:/update-dns.sh:ro + secrets: + - cf_api_token + entrypoint: ["/bin/sh", "/update-dns.sh"] + environment: + DNS_UPDATE_INTERVAL: "300" # 5 minutes + secrets: cf_api_token: file: ./cf_api_token.txt diff --git a/warzone/deploy/docker/update-dns.sh b/warzone/deploy/docker/update-dns.sh new file mode 100755 index 0000000..569cedf --- /dev/null +++ b/warzone/deploy/docker/update-dns.sh @@ -0,0 +1,98 @@ +#!/bin/sh +# Updates voip.manko.yoga DNS records with current public IPs. +# Runs once on startup, then every 5 minutes. +# Reads CF token from /run/secrets/cf_api_token or CF_API_TOKEN env. + +DOMAIN="voip.manko.yoga" +ZONE="manko.yoga" +INTERVAL="${DNS_UPDATE_INTERVAL:-300}" + +get_token() { + if [ -f /run/secrets/cf_api_token ]; then + cat /run/secrets/cf_api_token | tr -d '\n' + else + echo "$CF_API_TOKEN" + fi +} + +get_zone_id() { + curl -s "https://api.cloudflare.com/client/v4/zones?name=$ZONE" \ + -H "Authorization: Bearer $(get_token)" | \ + python3 -c "import sys,json; print(json.load(sys.stdin)['result'][0]['id'])" 2>/dev/null +} + +get_public_ipv4() { + curl -4 -s --connect-timeout 5 https://api.ipify.org 2>/dev/null || \ + curl -4 -s --connect-timeout 5 https://ifconfig.me 2>/dev/null +} + +get_public_ipv6() { + curl -6 -s --connect-timeout 5 https://api6.ipify.org 2>/dev/null || \ + curl -6 -s --connect-timeout 5 https://ifconfig.co 2>/dev/null +} + +upsert_record() { + local zone_id="$1" type="$2" content="$3" token + token=$(get_token) + + # Check existing + local existing + existing=$(curl -s "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?name=$DOMAIN&type=$type" \ + -H "Authorization: Bearer $token") + + local rec_id current + rec_id=$(echo "$existing" | python3 -c "import sys,json; r=json.load(sys.stdin)['result']; print(r[0]['id'] if r else '')" 2>/dev/null) + current=$(echo "$existing" | python3 -c "import sys,json; r=json.load(sys.stdin)['result']; print(r[0]['content'] if r else '')" 2>/dev/null) + + if [ "$current" = "$content" ]; then + echo " $type: $content (unchanged)" + return + fi + + if [ -n "$rec_id" ]; then + curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$rec_id" \ + -H "Authorization: Bearer $token" \ + -H "Content-Type: application/json" \ + --data "{\"type\":\"$type\",\"name\":\"$DOMAIN\",\"content\":\"$content\",\"ttl\":120,\"proxied\":false}" > /dev/null + echo " $type: $current -> $content (updated)" + else + curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records" \ + -H "Authorization: Bearer $token" \ + -H "Content-Type: application/json" \ + --data "{\"type\":\"$type\",\"name\":\"$DOMAIN\",\"content\":\"$content\",\"ttl\":120,\"proxied\":false}" > /dev/null + echo " $type: $content (created)" + fi +} + +update() { + echo "[$(date -u +%H:%M:%S)] Checking DNS for $DOMAIN..." + local zone_id + zone_id=$(get_zone_id) + if [ -z "$zone_id" ]; then + echo " ERROR: cannot get zone ID" + return + fi + + local ipv4 ipv6 + ipv4=$(get_public_ipv4) + ipv6=$(get_public_ipv6) + + if [ -n "$ipv4" ]; then + upsert_record "$zone_id" "A" "$ipv4" + else + echo " A: no IPv4 detected" + fi + + if [ -n "$ipv6" ]; then + upsert_record "$zone_id" "AAAA" "$ipv6" + else + echo " AAAA: no IPv6 detected" + fi +} + +# Run immediately, then loop +update +while true; do + sleep "$INTERVAL" + update +done