[object Object]

← back to Ga Allsites

grant script: add GoDaddy DNS support (append TXT via API) so non-Cloudflare microsites can be verified too

a648e3c716e894400acd95323a316f6b3af53818 · 2026-08-17 09:59:26 -0700 · Steve Abrams

Files touched

Diff

commit a648e3c716e894400acd95323a316f6b3af53818
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Aug 17 09:59:26 2026 -0700

    grant script: add GoDaddy DNS support (append TXT via API) so non-Cloudflare microsites can be verified too
---
 grant_microsites_gsc.py | 84 ++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 65 insertions(+), 19 deletions(-)

diff --git a/grant_microsites_gsc.py b/grant_microsites_gsc.py
index 07fce90..fdadf0a 100644
--- a/grant_microsites_gsc.py
+++ b/grant_microsites_gsc.py
@@ -54,17 +54,68 @@ RECOMMENDED = [
 ]
 
 
-def cf_token() -> str:
-    for k in ("CLOUDFLARE_API_TOKEN", "CF_API_TOKEN", "CF_TOKEN"):
+def _env(*keys):
+    """First matching value from process env, else from the secrets .env."""
+    for k in keys:
         if os.environ.get(k):
             return os.environ[k]
     if SECRETS_ENV.exists():
         for line in SECRETS_ENV.read_text().splitlines():
             line = line.strip()
-            for k in ("CLOUDFLARE_API_TOKEN", "CF_API_TOKEN", "CF_TOKEN"):
+            for k in keys:
                 if line.startswith(k + "="):
                     return line.split("=", 1)[1].strip().strip('"').strip("'")
-    raise SystemExit("ERROR: no Cloudflare API token in env or secrets-manager/.env")
+    return ""
+
+
+def cf_token() -> str:
+    t = _env("CLOUDFLARE_API_TOKEN", "CF_API_TOKEN", "CF_TOKEN")
+    if not t:
+        raise SystemExit("ERROR: no Cloudflare API token in env or secrets-manager/.env")
+    return t
+
+
+def godaddy_creds():
+    key = _env("GODADDY_API_KEY", "GODADDY_KEY")
+    sec = _env("GODADDY_API_SECRET", "GODADDY_SECRET")
+    return (key, sec) if key and sec else (None, None)
+
+
+def write_txt(domain: str, txt: str, cftok: str, gd) -> str:
+    """Upsert the apex TXT to whichever host is authoritative. Returns
+    'present' | 'written' | 'no-host'."""
+    # Cloudflare — only if the zone is ACTIVE (pending zones aren't authoritative)
+    zones = cf(f"https://api.cloudflare.com/client/v4/zones?name={domain}", cftok)
+    z = (zones.get("result") or [None])[0]
+    if z and z.get("status") == "active":
+        zid = z["id"]
+        existing = cf(f"https://api.cloudflare.com/client/v4/zones/{zid}/dns_records"
+                      f"?type=TXT&name={domain}", cftok)
+        if any(r["content"].strip('"') == txt for r in existing.get("result", [])):
+            return "present"
+        cf(f"https://api.cloudflare.com/client/v4/zones/{zid}/dns_records", cftok,
+           {"type": "TXT", "name": domain, "content": txt, "ttl": 120})
+        print(f"  [{domain}] TXT written to Cloudflare (zone {zid[:8]}…)")
+        return "written"
+    # GoDaddy (domaincontrol.com nameservers) — APPEND via PATCH (never clobbers SPF/etc.)
+    gkey, gsec = gd
+    if gkey and gsec:
+        hdr = {"Authorization": f"sso-key {gkey}:{gsec}", "Content-Type": "application/json"}
+        base = f"https://api.godaddy.com/v1/domains/{domain}/records"
+        # read existing @ TXT records (GoDaddy uses sso-key auth, not bearer)
+        req = urllib.request.Request(f"{base}/TXT/%40", headers=hdr)
+        try:
+            cur = json.loads(urllib.request.urlopen(req, timeout=30).read() or "[]")
+        except Exception:
+            cur = []
+        if any(r.get("data", "").strip('"') == txt for r in cur):
+            return "present"
+        body = json.dumps([{"type": "TXT", "name": "@", "data": txt, "ttl": 600}]).encode()
+        preq = urllib.request.Request(base, data=body, headers=hdr, method="PATCH")
+        urllib.request.urlopen(preq, timeout=30).read()
+        print(f"  [{domain}] TXT appended at GoDaddy")
+        return "written"
+    return "no-host"
 
 
 def google_creds():
@@ -88,7 +139,7 @@ def cf(url, tok, payload=None, method=None):
     return gget(url, tok, payload, method)
 
 
-def grant(domain: str, gtoken: str, cftok: str) -> str:
+def grant(domain: str, gtoken: str, cftok: str, gd) -> str:
     # 1. mint DNS TXT token
     tok = gget("https://www.googleapis.com/siteVerification/v1/token", gtoken, {
         "verificationMethod": "DNS_TXT",
@@ -97,19 +148,12 @@ def grant(domain: str, gtoken: str, cftok: str) -> str:
     txt = tok["token"]
     print(f"  [{domain}] token: {txt}")
 
-    # 2. find CF zone + upsert the TXT record on the apex
-    zones = cf(f"https://api.cloudflare.com/client/v4/zones?name={domain}", cftok)
-    if not zones.get("result"):
-        return "SKIP (no Cloudflare zone)"
-    zid = zones["result"][0]["id"]
-    existing = cf(f"https://api.cloudflare.com/client/v4/zones/{zid}/dns_records"
-                  f"?type=TXT&name={domain}", cftok)
-    already = any(r["content"].strip('"') == txt for r in existing.get("result", []))
-    if not already:
-        cf(f"https://api.cloudflare.com/client/v4/zones/{zid}/dns_records", cftok,
-           {"type": "TXT", "name": domain, "content": txt, "ttl": 120})
-        print(f"  [{domain}] TXT written to Cloudflare (zone {zid[:8]}…)")
-        time.sleep(8)  # let DNS propagate before asking Google to check
+    # 2. write the TXT to whichever host is authoritative (Cloudflare or GoDaddy)
+    state = write_txt(domain, txt, cftok, gd)
+    if state == "no-host":
+        return "SKIP (DNS not on active Cloudflare or GoDaddy)"
+    if state == "written":
+        time.sleep(12)  # let DNS propagate before asking Google to check
     else:
         print(f"  [{domain}] TXT already present")
 
@@ -148,13 +192,15 @@ def main():
     creds = google_creds()
     gtoken = creds.token
     cftok = cf_token()
+    gd = godaddy_creds()
     print(f"SA: {json.loads(KEY.read_text()).get('client_email')}")
+    print(f"GoDaddy API: {'available' if gd[0] else 'NOT configured'}")
     print(f"Granting {len(targets)} domain(s): {', '.join(targets)}\n")
 
     results = {}
     for d in targets:
         try:
-            results[d] = grant(d, gtoken, cftok)
+            results[d] = grant(d, gtoken, cftok, gd)
         except urllib.error.HTTPError as e:
             results[d] = f"ERROR {e.code}: {e.read().decode()[:160]}"
         except Exception as e:  # noqa: BLE001

← 3709923 auto-data-snapshot: 2026-08-17T09:56:21 (1 data files) — etl  ·  back to Ga Allsites  ·  grant script: --all-fleet mode (proven — took GSC coverage 1 7f57305 →