#!/usr/bin/env python3 """DDNS updater — keep the proxy hostname pointed at the home WAN IP. The SOCKS5/HTTP gateway is exposed on the home WAN IP (dynamic Comcast). The storefront advertises proxy.clean-proxys.thetempleofdoom.com instead of the raw IP; this updater keeps that A record in sync. DNS-only (proxied=false) because Cloudflare's orange-cloud cannot carry raw TCP (SOCKS5/HTTP CONNECT). Runs on CT777 (egresses via the home WAN, so ipify returns the real home IP — NOT the MacBook's Nord egress). Scheduled by ddns.timer every 15 min. """ import json import requests CFG = json.load(open("/opt/pleiades/config.json")) CF = CFG["cloudflare"] API = "https://api.cloudflare.com/client/v4" HDR = {"X-Auth-Email": CF["email"], "X-Auth-Key": CF["api_key"], "Content-Type": "application/json"} def get_wan_ip(): r = requests.get("https://api.ipify.org", timeout=10) r.raise_for_status() return r.text.strip() def find_record(): r = requests.get(f"{API}/zones/{CF['zone_id']}/dns_records", params={"name": CF["record_name"], "type": "A"}, headers=HDR, timeout=15) r.raise_for_status() recs = r.json().get("result", []) return recs[0] if recs else None def main(): wan = get_wan_ip() rec = find_record() body = {"type": "A", "name": CF["record_name"], "content": wan, "ttl": 120, "proxied": False} if rec is None: r = requests.post(f"{API}/zones/{CF['zone_id']}/dns_records", headers=HDR, json=body, timeout=15) ok = r.json().get("success") print(f"created {CF['record_name']} -> {wan} (success={ok})") return cur = rec.get("content") if cur == wan: print(f"no change: {CF['record_name']} -> {wan}") return r = requests.put(f"{API}/zones/{CF['zone_id']}/dns_records/{rec['id']}", headers=HDR, json=body, timeout=15) ok = r.json().get("success") print(f"updated {CF['record_name']}: {cur} -> {wan} (success={ok})") if __name__ == "__main__": main()