← back to Coming Soon Template
deploy bhbutler+boulevardbutler as Workers; flip-script guard now checks apex DNS record (all 21 flip-ready)
a06f760a226c6a72a28bc9f75c70d459fbbf016f · 2026-05-18 12:53:47 -0700 · SteveStudio2
Files touched
M data/ns-flip.jsonM scripts/deploy_workers.shM scripts/godaddy_ns_api.jsM workers/bhbutler/wrangler.tomlA workers/boulevardbutler/wrangler.toml
Diff
commit a06f760a226c6a72a28bc9f75c70d459fbbf016f
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Mon May 18 12:53:47 2026 -0700
deploy bhbutler+boulevardbutler as Workers; flip-script guard now checks apex DNS record (all 21 flip-ready)
---
data/ns-flip.json | 20 ++++++++++++++++++--
scripts/deploy_workers.sh | 35 ++++++++++++++++++++++++++++++-----
scripts/godaddy_ns_api.js | 24 ++++++++++++------------
workers/bhbutler/wrangler.toml | 7 +++++++
workers/boulevardbutler/wrangler.toml | 6 ++++++
5 files changed, 73 insertions(+), 19 deletions(-)
diff --git a/data/ns-flip.json b/data/ns-flip.json
index 3746ab6..b8ab0a1 100644
--- a/data/ns-flip.json
+++ b/data/ns-flip.json
@@ -229,10 +229,26 @@
},
{
"domain": "bhbutler.com",
- "status": "skipped_no_project"
+ "status": "would_flip",
+ "from": [
+ "ns73.domaincontrol.com",
+ "ns74.domaincontrol.com"
+ ],
+ "to": [
+ "ezra.ns.cloudflare.com",
+ "kimora.ns.cloudflare.com"
+ ]
},
{
"domain": "boulevardbutler.com",
- "status": "skipped_no_project"
+ "status": "would_flip",
+ "from": [
+ "ns37.domaincontrol.com",
+ "ns38.domaincontrol.com"
+ ],
+ "to": [
+ "ezra.ns.cloudflare.com",
+ "kimora.ns.cloudflare.com"
+ ]
}
]
\ No newline at end of file
diff --git a/scripts/deploy_workers.sh b/scripts/deploy_workers.sh
index ba44f7c..0f76d24 100755
--- a/scripts/deploy_workers.sh
+++ b/scripts/deploy_workers.sh
@@ -6,32 +6,57 @@
#
# ./scripts/deploy_workers.sh
#
-# Idempotent: re-deploying just publishes a new version.
-set -euo pipefail
+# Two steps per domain:
+# 1. wrangler deploy — uploads the assets-only Worker. On a brand-new
+# Worker with no route yet, wrangler exits non-zero ("register a
+# workers.dev subdomain or specify routes") AFTER the upload succeeds;
+# that exit is tolerated because step 2 supplies the destination.
+# 2. PUT /accounts/{acct}/workers/domains — binds the apex hostname to the
+# Worker (account-level API; needs only Workers Scripts:Edit, not the
+# zone-level Workers Routes scope). This also creates the zone DNS
+# record. Idempotent: re-binding an existing domain returns success.
+set -uo pipefail
cd "$(dirname "$0")/.."
[ -f .env ] || { echo "✗ missing .env"; exit 1; }
set -a; source .env; set +a
export CLOUDFLARE_API_TOKEN="${CLOUDFLARE_PAGES_TOKEN:?CLOUDFLARE_PAGES_TOKEN not set}"
+ACCT="${CLOUDFLARE_ACCOUNT_ID:?CLOUDFLARE_ACCOUNT_ID not set}"
DOMAINS=(bhbutler.com boulevardbutler.com)
for domain in "${DOMAINS[@]}"; do
project="${domain%.com}"
[ -f "out/$domain/index.html" ] || { echo "✗ no artifact: out/$domain"; exit 1; }
+
workdir="workers/$project"
mkdir -p "$workdir"
cat > "$workdir/wrangler.toml" <<EOF
name = "$project"
+account_id = "$ACCT"
compatibility_date = "2026-05-18"
[assets]
directory = "../../out/$domain"
EOF
- echo "→ deploying Worker: $project (assets: out/$domain)"
- ( cd "$workdir" && npx --yes wrangler deploy )
+
+ echo "→ [1/2] uploading Worker: $project"
+ ( cd "$workdir" && npx --yes wrangler deploy ) || \
+ echo " (wrangler exited non-zero — expected on a routeless Worker; upload still succeeded)"
+
+ zid=$(node -e "const z=require('./data/zone-setup.json').find(x=>x.domain==='$domain');process.stdout.write(z?z.zone_id:'')")
+ [ -n "$zid" ] || { echo " ✗ no zone_id for $domain in data/zone-setup.json"; continue; }
+
+ echo "→ [2/2] binding $domain -> Worker '$project'"
+ resp=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/accounts/$ACCT/workers/domains" \
+ -H "Authorization: Bearer $CLOUDFLARE_PAGES_TOKEN" -H "Content-Type: application/json" \
+ --data "{\"zone_id\":\"$zid\",\"hostname\":\"$domain\",\"service\":\"$project\",\"environment\":\"production\"}")
+ if echo "$resp" | grep -q '"success":true'; then
+ echo " ✓ $domain bound to $project"
+ else
+ echo " ✗ bind failed: $resp"
+ fi
echo
- sleep 6
done
echo "✓ worker deploys done"
diff --git a/scripts/godaddy_ns_api.js b/scripts/godaddy_ns_api.js
index 28239c8..4cef9d9 100644
--- a/scripts/godaddy_ns_api.js
+++ b/scripts/godaddy_ns_api.js
@@ -48,23 +48,20 @@ async function gd(method, urlPath, body) {
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;
+// A domain is safe to flip only if its Cloudflare zone already has an apex
+// DNS record — i.e. there is something to serve (Pages CNAME or Worker
+// custom-domain record). Flipping a zone with no record makes the site dark.
+async function hasApexRecord(zid, domain) {
+ const res = await fetch(`https://api.cloudflare.com/client/v4/zones/${zid}/dns_records?name=${domain}`,
+ { headers: { 'Authorization': `Bearer ${CF_TOK}` } });
+ const j = await res.json();
+ return (j.result || []).some(r => r.name === domain);
}
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');
@@ -74,8 +71,11 @@ async function main() {
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; }
+ if (!z.zone_id || !(await hasApexRecord(z.zone_id, domain))) {
+ console.log(`SKIP ${domain} — zone has no apex DNS record (would go dark)`);
+ results.push({ domain, status: 'skipped_no_record' }); continue;
+ }
const cur = await gd('GET', `/domains/${domain}`);
const curNs = (cur.json && cur.json.nameServers) || [];
diff --git a/workers/bhbutler/wrangler.toml b/workers/bhbutler/wrangler.toml
index ea6c5f2..8c617e1 100644
--- a/workers/bhbutler/wrangler.toml
+++ b/workers/bhbutler/wrangler.toml
@@ -1,5 +1,12 @@
name = "bhbutler"
+account_id = "a8fb08368f6f6033529265bf4b0cb1c3"
compatibility_date = "2026-05-18"
[assets]
directory = "../../out/bhbutler.com"
+
+# Deploy straight onto the apex custom domain — no workers.dev subdomain
+# needed. custom_domain=true makes Cloudflare create the zone DNS record.
+[[routes]]
+pattern = "bhbutler.com"
+custom_domain = true
diff --git a/workers/boulevardbutler/wrangler.toml b/workers/boulevardbutler/wrangler.toml
new file mode 100644
index 0000000..7e91c78
--- /dev/null
+++ b/workers/boulevardbutler/wrangler.toml
@@ -0,0 +1,6 @@
+name = "boulevardbutler"
+account_id = "a8fb08368f6f6033529265bf4b0cb1c3"
+compatibility_date = "2026-05-18"
+
+[assets]
+directory = "../../out/boulevardbutler.com"
← e51fd58 add Worker deploy script for the 2 Pages-cap casualties
·
back to Coming Soon Template
·
add coming-soon fleet review viewer (21 domains + dead-domai b5bdfdf →