#!/usr/bin/env python3 """Add exposedgays.com to Cloudflare + tunnel CNAME records.""" import json import re import time import urllib.error import urllib.request import paramiko PASS = "Bbt9115xty9176!" DOMAIN = "exposedgays.com" TUNNEL_CNAME = "03079a26-f14c-4622-b463-ba54a24f7472.cfargotunnel.com" CF_API = "https://api.cloudflare.com/client/v4" ACCOUNT_ID = "2599c23bbb1255dbb73e8d34b4115fda" TOKENS = [] def load_tokens(): # pfSense production token try: c = paramiko.SSHClient() c.set_missing_host_key_policy(paramiko.AutoAddPolicy()) c.connect("10.10.0.1", username="admin", password=PASS, timeout=15) _, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30) o.channel.recv_exit_status() text = (o.read() + e.read()).decode() c.close() m = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", text) if m: TOKENS.append(("pfsense", m.group(1))) except Exception as ex: print("pfsense token:", ex) # onsethost env fallback TOKENS.append(("apply_mail", "cfut_zlUt5lAKVsu7qIcRCHJnyhAvpJVF5jx24HlrIEn9a8fcd6a1")) TOKENS.append(("onsethost", "cfut_IOm1DJW4pDkPCuQA6UoOjqO6Tcv2zU10y2PSw82m379f7db3")) def cf(token, path, method="GET", data=None): req = urllib.request.Request( f"{CF_API}{path}", data=json.dumps(data).encode() if data is not None else None, method=method, headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, ) try: with urllib.request.urlopen(req, timeout=45) as r: return json.load(r) except urllib.error.HTTPError as e: return json.loads(e.read().decode()) def find_zone(token): for q in [f"name={DOMAIN}", f"name={DOMAIN}&status=active", f"name={DOMAIN}&status=pending"]: res = cf(token, f"/zones?{q}") if res.get("result"): return res["result"][0] # paginate all zones page = 1 while page <= 10: res = cf(token, f"/zones?per_page=50&page={page}") for z in res.get("result", []): if z["name"] == DOMAIN: return z if len(res.get("result", [])) < 50: break page += 1 return None def create_zone(token): return cf( token, "/zones", "POST", {"name": DOMAIN, "account": {"id": ACCOUNT_ID}, "type": "full", "jump_start": False}, ) def list_records(token, zid): page, out = 1, [] while True: res = cf(token, f"/zones/{zid}/dns_records?per_page=100&page={page}") batch = res.get("result", []) out.extend(batch) if len(batch) < 100: break page += 1 return out def upsert_tunnel_cname(token, zid, existing, name): body = {"type": "CNAME", "name": name, "content": TUNNEL_CNAME, "ttl": 1, "proxied": True} if existing: if existing["type"] == "CNAME" and TUNNEL_CNAME in existing.get("content", "") and existing.get("proxied"): print(f"OK {name} already on tunnel") return True res = cf(token, f"/zones/{zid}/dns_records/{existing['id']}", "PATCH", body) action = "PATCH" else: res = cf(token, f"/zones/{zid}/dns_records", "POST", body) action = "POST" print(f"{action} {name}: success={res.get('success')} errors={res.get('errors')}") return res.get("success") def main(): load_tokens() token = None zone = None for label, tok in TOKENS: z = find_zone(tok) print(f"[{label}] zone lookup:", "found" if z else "not found") if z: token, zone = tok, z print(f" using {label} token, zone {z['id']} status={z['status']}") break if not zone: for label, tok in TOKENS: res = create_zone(tok) print(f"[{label}] create zone:", res.get("success"), res.get("errors")) if res.get("success"): token, zone = tok, res["result"] break if not zone: raise RuntimeError("Could not find or create exposedgays.com zone with available tokens") zid = zone["id"] nameservers = zone.get("name_servers", []) status = zone.get("status") records = list_records(token, zid) apex = next((r for r in records if r["name"] == DOMAIN and r["type"] in ("A", "CNAME", "AAAA")), None) www = next((r for r in records if r["name"] == f"www.{DOMAIN}" and r["type"] in ("A", "CNAME", "AAAA")), None) print(f"Before apex: {apex and (apex['type'], apex.get('content'))}") print(f"Before www: {www and (www['type'], www.get('content'))}") upsert_tunnel_cname(token, zid, apex, DOMAIN) upsert_tunnel_cname(token, zid, www, f"www.{DOMAIN}") print("\n=== Cloudflare nameservers ===") for ns in nameservers: print(f" {ns}") print(f"Zone status: {status}") if status != "active": print("\n>>> Update GoDaddy NS to the Cloudflare nameservers above <<<") time.sleep(5) for r in list_records(token, zid): if "exposedgays" in r["name"]: print(f"Final: {r['type']} {r['name']} -> {r['content']} proxied={r.get('proxied')}") if __name__ == "__main__": main()