178 lines
5.8 KiB
Python
178 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Delegate exposedgays.com from GoDaddy to Cloudflare.
|
|
Requires exposedgays.com zone to exist in Cloudflare (dashboard add if API create fails).
|
|
"""
|
|
import json
|
|
import re
|
|
import sys
|
|
import time
|
|
import urllib.error
|
|
import urllib.request
|
|
import paramiko
|
|
|
|
PASS = "Bbt9115xty9176!"
|
|
DOMAIN = "exposedgays.com"
|
|
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
|
TUNNEL_CNAME = "03079a26-f14c-4622-b463-ba54a24f7472.cfargotunnel.com"
|
|
CF = "https://api.cloudflare.com/client/v4"
|
|
GD = "https://api.godaddy.com/v1"
|
|
GODADDY_KEY = "2wYVpc3VT4_Gi9AsNqzJ6BtSfNz6nLDuo"
|
|
GODADDY_SECRET = "NvC3qexGAU7Rd5QExUTH6z"
|
|
|
|
|
|
def load_cf_token():
|
|
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()
|
|
return re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", text).group(1)
|
|
|
|
|
|
def cf(tok, path, method="GET", body=None):
|
|
req = urllib.request.Request(
|
|
CF + path,
|
|
data=json.dumps(body).encode() if body is not None else None,
|
|
method=method,
|
|
headers={"Authorization": f"Bearer {tok}", "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 gd(path, method="GET", body=None):
|
|
req = urllib.request.Request(
|
|
GD + path,
|
|
data=json.dumps(body).encode() if body is not None else None,
|
|
method=method,
|
|
headers={
|
|
"Authorization": f"sso-key {GODADDY_KEY}:{GODADDY_SECRET}",
|
|
"Content-Type": "application/json",
|
|
"Accept": "application/json",
|
|
},
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=45) as r:
|
|
raw = r.read().decode()
|
|
return r.status, json.loads(raw) if raw else {}
|
|
except urllib.error.HTTPError as e:
|
|
raw = e.read().decode()
|
|
try:
|
|
return e.code, json.loads(raw)
|
|
except Exception:
|
|
return e.code, {"raw": raw[:800]}
|
|
|
|
|
|
def find_zone(tok):
|
|
for status in ["active", "pending", ""]:
|
|
q = f"name={DOMAIN}" + (f"&status={status}" if status else "")
|
|
res = cf(tok, f"/zones?{q}")
|
|
if res.get("result"):
|
|
return res["result"][0]
|
|
page = 1
|
|
while page <= 20:
|
|
res = cf(tok, 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 upsert_tunnel_cnames(tok, zid):
|
|
records = cf(tok, f"/zones/{zid}/dns_records?per_page=100").get("result", [])
|
|
for name in [DOMAIN, f"www.{DOMAIN}"]:
|
|
body = {"type": "CNAME", "name": name, "content": TUNNEL_CNAME, "ttl": 1, "proxied": True}
|
|
existing = next(
|
|
(r for r in records if r["name"] == name and r["type"] in ("A", "CNAME", "AAAA")),
|
|
None,
|
|
)
|
|
if existing:
|
|
res = cf(tok, f"/zones/{zid}/dns_records/{existing['id']}", "PATCH", body)
|
|
action = "PATCH"
|
|
else:
|
|
res = cf(tok, f"/zones/{zid}/dns_records", "POST", body)
|
|
action = "POST"
|
|
print(f" CF {action} {name}: success={res.get('success')} errors={res.get('errors')}")
|
|
|
|
|
|
def verify():
|
|
c = paramiko.SSHClient()
|
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
|
cmds = [
|
|
f"dig @1.1.1.1 {DOMAIN} NS +short",
|
|
f"dig @1.1.1.1 www.{DOMAIN} A +short",
|
|
f"curl -sSI https://www.{DOMAIN}/ | head -10",
|
|
f"curl -sSL https://www.{DOMAIN}/ | head -c 200",
|
|
]
|
|
for cmd in cmds:
|
|
print(f"\n$ {cmd}")
|
|
_, o, e = c.exec_command(cmd, timeout=30)
|
|
o.channel.recv_exit_status()
|
|
print((o.read() + e.read()).decode(errors="replace"))
|
|
c.close()
|
|
|
|
|
|
def main():
|
|
tok = load_cf_token()
|
|
zone = find_zone(tok)
|
|
|
|
if not zone:
|
|
created = cf(
|
|
tok,
|
|
"/zones",
|
|
"POST",
|
|
{"name": DOMAIN, "account": {"id": ACCOUNT}, "type": "full", "jump_start": False},
|
|
)
|
|
if created.get("success"):
|
|
zone = created["result"]
|
|
print("Created CF zone via API")
|
|
else:
|
|
print("CF zone not found and API create failed:")
|
|
print(" ", created.get("errors"))
|
|
print("\nAdd exposedgays.com at https://dash.cloudflare.com then re-run:")
|
|
print(" python exposedgays/deploy/delegate-godaddy-to-cf.py")
|
|
return 1
|
|
|
|
zid = zone["id"]
|
|
nameservers = zone.get("name_servers", [])
|
|
status = zone.get("status")
|
|
print(f"CF zone {zid} status={status}")
|
|
print(f"CF nameservers: {nameservers}")
|
|
|
|
print("\n=== GoDaddy before ===")
|
|
code, before = gd(f"/domains/{DOMAIN}")
|
|
print(f"HTTP {code} NS={before.get('nameServers') if code == 200 else before}")
|
|
|
|
print("\n=== Cloudflare tunnel DNS ===")
|
|
upsert_tunnel_cnames(tok, zid)
|
|
|
|
if not nameservers:
|
|
print("No nameservers on zone — cannot delegate GoDaddy")
|
|
return 1
|
|
|
|
print("\n=== GoDaddy PATCH nameservers -> Cloudflare ===")
|
|
code, res = gd(f"/domains/{DOMAIN}", "PATCH", {"nameServers": nameservers})
|
|
print(f"HTTP {code}: {json.dumps(res, indent=2) if res else '{}'}")
|
|
|
|
code, after = gd(f"/domains/{DOMAIN}")
|
|
if code == 200:
|
|
print(f"\nGoDaddy after NS={after.get('nameServers')}")
|
|
|
|
print("\nWaiting 15s for propagation...")
|
|
time.sleep(15)
|
|
verify()
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main()) |