[object Object]

← back to Coming Soon Template

GoDaddy NS-flip via API (replaces fragile Playwright scraper)

c4cad7b627b754dc337e37af06daa5aeb7efc0a3 · 2026-05-18 12:42:04 -0700 · SteveStudio2

Files touched

Diff

commit c4cad7b627b754dc337e37af06daa5aeb7efc0a3
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Mon May 18 12:42:04 2026 -0700

    GoDaddy NS-flip via API (replaces fragile Playwright scraper)
---
 data/ns-flip.json         | 238 ++++++++++++++++++++++++++++++++++++++++++++++
 scripts/godaddy_ns_api.js | 102 ++++++++++++++++++++
 2 files changed, 340 insertions(+)

diff --git a/data/ns-flip.json b/data/ns-flip.json
new file mode 100644
index 0000000..3746ab6
--- /dev/null
+++ b/data/ns-flip.json
@@ -0,0 +1,238 @@
+[
+  {
+    "domain": "abramsos.com",
+    "status": "would_flip",
+    "from": [
+      "ns55.domaincontrol.com",
+      "ns56.domaincontrol.com"
+    ],
+    "to": [
+      "ezra.ns.cloudflare.com",
+      "kimora.ns.cloudflare.com"
+    ]
+  },
+  {
+    "domain": "abramsintel.com",
+    "status": "would_flip",
+    "from": [
+      "ns03.domaincontrol.com",
+      "ns04.domaincontrol.com"
+    ],
+    "to": [
+      "ezra.ns.cloudflare.com",
+      "kimora.ns.cloudflare.com"
+    ]
+  },
+  {
+    "domain": "abramsintelligence.com",
+    "status": "would_flip",
+    "from": [
+      "ns03.domaincontrol.com",
+      "ns04.domaincontrol.com"
+    ],
+    "to": [
+      "ezra.ns.cloudflare.com",
+      "kimora.ns.cloudflare.com"
+    ]
+  },
+  {
+    "domain": "abramsspace.com",
+    "status": "would_flip",
+    "from": [
+      "ns71.domaincontrol.com",
+      "ns72.domaincontrol.com"
+    ],
+    "to": [
+      "ezra.ns.cloudflare.com",
+      "kimora.ns.cloudflare.com"
+    ]
+  },
+  {
+    "domain": "abramsterminal.com",
+    "status": "would_flip",
+    "from": [
+      "ns55.domaincontrol.com",
+      "ns56.domaincontrol.com"
+    ],
+    "to": [
+      "ezra.ns.cloudflare.com",
+      "kimora.ns.cloudflare.com"
+    ]
+  },
+  {
+    "domain": "abramsvc.com",
+    "status": "would_flip",
+    "from": [
+      "ns75.domaincontrol.com",
+      "ns76.domaincontrol.com"
+    ],
+    "to": [
+      "ezra.ns.cloudflare.com",
+      "kimora.ns.cloudflare.com"
+    ]
+  },
+  {
+    "domain": "abramsindustries.com",
+    "status": "would_flip",
+    "from": [
+      "ns03.domaincontrol.com",
+      "ns04.domaincontrol.com"
+    ],
+    "to": [
+      "ezra.ns.cloudflare.com",
+      "kimora.ns.cloudflare.com"
+    ]
+  },
+  {
+    "domain": "abramsprotection.com",
+    "status": "would_flip",
+    "from": [
+      "ns29.domaincontrol.com",
+      "ns30.domaincontrol.com"
+    ],
+    "to": [
+      "ezra.ns.cloudflare.com",
+      "kimora.ns.cloudflare.com"
+    ]
+  },
+  {
+    "domain": "abramscivic.com",
+    "status": "would_flip",
+    "from": [
+      "ns29.domaincontrol.com",
+      "ns30.domaincontrol.com"
+    ],
+    "to": [
+      "ezra.ns.cloudflare.com",
+      "kimora.ns.cloudflare.com"
+    ]
+  },
+  {
+    "domain": "abramslive.com",
+    "status": "would_flip",
+    "from": [
+      "ns75.domaincontrol.com",
+      "ns76.domaincontrol.com"
+    ],
+    "to": [
+      "ezra.ns.cloudflare.com",
+      "kimora.ns.cloudflare.com"
+    ]
+  },
+  {
+    "domain": "abramsatlas.com",
+    "status": "would_flip",
+    "from": [
+      "ns71.domaincontrol.com",
+      "ns72.domaincontrol.com"
+    ],
+    "to": [
+      "ezra.ns.cloudflare.com",
+      "kimora.ns.cloudflare.com"
+    ]
+  },
+  {
+    "domain": "abramsdirectory.com",
+    "status": "would_flip",
+    "from": [
+      "ns29.domaincontrol.com",
+      "ns30.domaincontrol.com"
+    ],
+    "to": [
+      "ezra.ns.cloudflare.com",
+      "kimora.ns.cloudflare.com"
+    ]
+  },
+  {
+    "domain": "abramsguide.com",
+    "status": "would_flip",
+    "from": [
+      "ns29.domaincontrol.com",
+      "ns30.domaincontrol.com"
+    ],
+    "to": [
+      "ezra.ns.cloudflare.com",
+      "kimora.ns.cloudflare.com"
+    ]
+  },
+  {
+    "domain": "abramsindex.com",
+    "status": "would_flip",
+    "from": [
+      "ns71.domaincontrol.com",
+      "ns72.domaincontrol.com"
+    ],
+    "to": [
+      "ezra.ns.cloudflare.com",
+      "kimora.ns.cloudflare.com"
+    ]
+  },
+  {
+    "domain": "abramslocal.com",
+    "status": "would_flip",
+    "from": [
+      "ns71.domaincontrol.com",
+      "ns72.domaincontrol.com"
+    ],
+    "to": [
+      "ezra.ns.cloudflare.com",
+      "kimora.ns.cloudflare.com"
+    ]
+  },
+  {
+    "domain": "abramsmaps.com",
+    "status": "would_flip",
+    "from": [
+      "ns55.domaincontrol.com",
+      "ns56.domaincontrol.com"
+    ],
+    "to": [
+      "ezra.ns.cloudflare.com",
+      "kimora.ns.cloudflare.com"
+    ]
+  },
+  {
+    "domain": "abramsmarkets.com",
+    "status": "would_flip",
+    "from": [
+      "ns75.domaincontrol.com",
+      "ns76.domaincontrol.com"
+    ],
+    "to": [
+      "ezra.ns.cloudflare.com",
+      "kimora.ns.cloudflare.com"
+    ]
+  },
+  {
+    "domain": "818butler.com",
+    "status": "would_flip",
+    "from": [
+      "ns37.domaincontrol.com",
+      "ns38.domaincontrol.com"
+    ],
+    "to": [
+      "ezra.ns.cloudflare.com",
+      "kimora.ns.cloudflare.com"
+    ]
+  },
+  {
+    "domain": "beverlyhillsbutler.com",
+    "status": "would_flip",
+    "from": [
+      "ns47.domaincontrol.com",
+      "ns48.domaincontrol.com"
+    ],
+    "to": [
+      "ezra.ns.cloudflare.com",
+      "kimora.ns.cloudflare.com"
+    ]
+  },
+  {
+    "domain": "bhbutler.com",
+    "status": "skipped_no_project"
+  },
+  {
+    "domain": "boulevardbutler.com",
+    "status": "skipped_no_project"
+  }
+]
\ No newline at end of file
diff --git a/scripts/godaddy_ns_api.js b/scripts/godaddy_ns_api.js
new file mode 100644
index 0000000..28239c8
--- /dev/null
+++ b/scripts/godaddy_ns_api.js
@@ -0,0 +1,102 @@
+#!/usr/bin/env node
+/* Flip GoDaddy nameservers -> Cloudflare via the GoDaddy API.
+ * Replaces the fragile Playwright UI-scraper (godaddy_flip.js): the API
+ * PATCH /v1/domains/{domain} { nameServers } is deterministic and needs
+ * no interactive login.
+ *
+ * Only flips domains that (a) have a Cloudflare zone with cf_ns and
+ * (b) have a live Cloudflare Pages project — so project-limit casualties
+ * (no project, no DNS record) are NOT flipped and never go dark.
+ * Domains with live MX in zone-setup.json are HELD BACK (email safety).
+ *
+ * Usage:
+ *   node godaddy_ns_api.js            # dry run — prints the plan, changes nothing
+ *   node godaddy_ns_api.js --apply    # perform the nameserver flip
+ *
+ * Writes data/ns-flip.json.
+ */
+'use strict';
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+
+const ENV = path.join(os.homedir(), 'Projects/secrets-manager/.env');
+const ROOT = path.join(os.homedir(), 'Projects/coming-soon-template');
+const OUT = path.join(ROOT, 'data/ns-flip.json');
+
+function envVal(key) {
+  const line = fs.readFileSync(ENV, 'utf8').split('\n').find(l => l.startsWith(key + '='));
+  return line ? line.slice(key.length + 1).trim() : '';
+}
+const GD_KEY = envVal('GODADDY_API_KEY');
+const GD_SECRET = envVal('GODADDY_API_SECRET');
+const CF_TOK = envVal('CLOUDFLARE_API_TOKEN');
+const ACCT = envVal('CLOUDFLARE_ACCOUNT_ID');
+if (!GD_KEY || !GD_SECRET) { console.error('missing GODADDY_API_KEY / GODADDY_API_SECRET in', ENV); process.exit(1); }
+
+const GD = 'https://api.godaddy.com/v1';
+const GD_HEADERS = { 'Authorization': `sso-key ${GD_KEY}:${GD_SECRET}`, 'Content-Type': 'application/json' };
+const APPLY = process.argv.includes('--apply');
+
+async function gd(method, urlPath, body) {
+  const res = await fetch(GD + urlPath, {
+    method, headers: GD_HEADERS, body: body ? JSON.stringify(body) : undefined,
+  });
+  let json = null;
+  const txt = await res.text();
+  if (txt) { try { json = JSON.parse(txt); } catch { json = { raw: txt }; } }
+  return { http: res.status, json };
+}
+
+async function cfProjects() {
+  const names = new Set();
+  for (let page = 1; page <= 10; page++) {
+    const res = await fetch(`https://api.cloudflare.com/client/v4/accounts/${ACCT}/pages/projects?page=${page}`,
+      { headers: { 'Authorization': `Bearer ${CF_TOK}` } });
+    const j = await res.json();
+    (j.result || []).forEach(p => names.add(p.name));
+    if (!(j.result || []).length || (j.result_info && page >= j.result_info.total_pages)) break;
+  }
+  return names;
+}
+
+const isCfNs = ns => Array.isArray(ns) && ns.every(h => /\.ns\.cloudflare\.com$/i.test(h));
+
+async function main() {
+  const zones = JSON.parse(fs.readFileSync(path.join(ROOT, 'data/zone-setup.json'), 'utf8'));
+  const projects = await cfProjects();
+  const results = [];
+  console.log(APPLY ? '=== APPLYING nameserver flip ===\n' : '=== DRY RUN (use --apply to flip) ===\n');
+
+  for (const z of zones) {
+    const domain = z.domain;
+    const project = domain.replace(/\.com$/, '');
+    const ns = z.cf_ns || [];
+
+    if (z.live_mx) { console.log(`HOLD  ${domain}  — live MX (${z.live_mx})`); results.push({ domain, status: 'held_live_mx' }); continue; }
+    if (!projects.has(project)) { console.log(`SKIP  ${domain}  — no Pages project (would go dark)`); results.push({ domain, status: 'skipped_no_project' }); continue; }
+    if (ns.length < 2) { console.log(`SKIP  ${domain}  — zone has no cf_ns`); results.push({ domain, status: 'skipped_no_ns' }); continue; }
+
+    const cur = await gd('GET', `/domains/${domain}`);
+    const curNs = (cur.json && cur.json.nameServers) || [];
+    if (isCfNs(curNs)) { console.log(`OK    ${domain}  — already on Cloudflare NS`); results.push({ domain, status: 'already_cf', ns: curNs }); continue; }
+
+    if (!APPLY) { console.log(`FLIP  ${domain}  ${curNs.join(',')}  ->  ${ns.join(',')}`); results.push({ domain, status: 'would_flip', from: curNs, to: ns }); continue; }
+
+    const upd = await gd('PATCH', `/domains/${domain}`, { nameServers: ns });
+    if (upd.http === 200 || upd.http === 204) {
+      console.log(`OK    ${domain}  flipped -> ${ns.join(', ')}`);
+      results.push({ domain, status: 'flipped', from: curNs, to: ns });
+    } else {
+      const msg = (upd.json && (upd.json.message || JSON.stringify(upd.json))) || `HTTP ${upd.http}`;
+      console.log(`FAIL  ${domain}  [HTTP ${upd.http}]  ${msg}`);
+      results.push({ domain, status: 'flip_failed', http: upd.http, error: msg });
+    }
+  }
+
+  fs.writeFileSync(OUT, JSON.stringify(results, null, 2));
+  const flipped = results.filter(r => r.status === 'flipped').length;
+  const wf = results.filter(r => r.status === 'would_flip').length;
+  console.log(`\n--- ${APPLY ? `${flipped} flipped` : `${wf} would flip`} --- wrote ${OUT}`);
+}
+main().catch(e => { console.error('FATAL', e); process.exit(1); });

← 39851b2 deploy 19/21 to CF Pages + apex CNAME records (bhbutler/boul  ·  back to Coming Soon Template  ·  add Worker deploy script for the 2 Pages-cap casualties e51fd58 →