[object Object]

← back to Wallco Ai

Add API-path tunnel finish-script (DTD-C: scoped CF token, no cloudflared login/cert)

6a41ddacdc0e51cd2508aafcb83204a933fc4ab2 · 2026-06-24 12:26:50 -0700 · Steve

Files touched

Diff

commit 6a41ddacdc0e51cd2508aafcb83204a933fc4ab2
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Jun 24 12:26:50 2026 -0700

    Add API-path tunnel finish-script (DTD-C: scoped CF token, no cloudflared login/cert)
---
 deploy/curator-tunnel-finish-api.sh | 82 +++++++++++++++++++++++++++++++++++++
 1 file changed, 82 insertions(+)

diff --git a/deploy/curator-tunnel-finish-api.sh b/deploy/curator-tunnel-finish-api.sh
new file mode 100755
index 0000000..a3fdf53
--- /dev/null
+++ b/deploy/curator-tunnel-finish-api.sh
@@ -0,0 +1,82 @@
+#!/bin/bash
+# curator-tunnel-finish-api.sh — API path (DTD verdict C, 2026-06-24).
+# Creates the wpb-curator tunnel WITHOUT cloudflared login/cert.pem, using a
+# narrowly-scoped CF API token (Account:Cloudflare-Tunnel:Edit + Zone:DNS:Edit).
+# Idempotent. No sudo. Installs a token-run LaunchAgent (survives reboot).
+#
+# Usage:  CF_TUNNEL_TOKEN=<scoped-api-token> bash curator-tunnel-finish-api.sh
+set -euo pipefail
+
+: "${CF_TUNNEL_TOKEN:?Set CF_TUNNEL_TOKEN to the scoped Cloudflare API token}"
+ACCT=$(grep -E "^CLOUDFLARE_ACCOUNT_ID=" "$HOME/Projects/secrets-manager/.env" | cut -d= -f2)
+ZONE=70cac5396b39727ac107663b45452038          # wallpapersback.com
+NAME=wpb-curator
+HOSTNAME=curator.wallpapersback.com
+SERVICE=http://127.0.0.1:9916                    # local nginx (basic-auth wall)
+API=https://api.cloudflare.com/client/v4
+H=(-H "Authorization: Bearer $CF_TUNNEL_TOKEN" -H "Content-Type: application/json")
+PLIST="$HOME/Library/LaunchAgents/com.steve.wpb-curator-tunnel.plist"
+
+jqget(){ python3 -c "import sys,json;d=json.load(sys.stdin);print($1)"; }
+
+# 1. Create (or find) the remotely-managed tunnel.
+EXIST=$(curl -s "${H[@]}" "$API/accounts/$ACCT/cfd_tunnel?name=$NAME&is_deleted=false")
+TID=$(echo "$EXIST" | jqget "(d['result'][0]['id'] if d.get('result') else '')" 2>/dev/null || echo "")
+if [ -z "$TID" ]; then
+  echo "→ creating tunnel $NAME"
+  RESP=$(curl -s -X POST "${H[@]}" "$API/accounts/$ACCT/cfd_tunnel" \
+    -d "{\"name\":\"$NAME\",\"config_src\":\"cloudflare\"}")
+  echo "$RESP" | jqget "('ok' if d.get('success') else 'ERR: '+str(d.get('errors')))"
+  TID=$(echo "$RESP" | jqget "d['result']['id']")
+else
+  echo "→ tunnel $NAME already exists"
+fi
+echo "  tunnel id: $TID"
+
+# 2. Set ingress config (remotely-managed): hostname → local nginx, catch-all 404.
+echo "→ setting ingress"
+curl -s -X PUT "${H[@]}" "$API/accounts/$ACCT/cfd_tunnel/$TID/configurations" \
+  -d "{\"config\":{\"ingress\":[{\"hostname\":\"$HOSTNAME\",\"service\":\"$SERVICE\"},{\"service\":\"http_status:404\"}]}}" \
+  | jqget "('ok' if d.get('success') else 'ERR: '+str(d.get('errors')))"
+
+# 3. Proxied CNAME: curator → <id>.cfargotunnel.com (upsert).
+echo "→ DNS route $HOSTNAME"
+REC=$(curl -s "${H[@]}" "$API/zones/$ZONE/dns_records?type=CNAME&name=$HOSTNAME")
+RID=$(echo "$REC" | jqget "(d['result'][0]['id'] if d.get('result') else '')" 2>/dev/null || echo "")
+BODY="{\"type\":\"CNAME\",\"name\":\"$HOSTNAME\",\"content\":\"$TID.cfargotunnel.com\",\"proxied\":true,\"ttl\":1}"
+if [ -z "$RID" ]; then
+  curl -s -X POST "${H[@]}" "$API/zones/$ZONE/dns_records" -d "$BODY" \
+    | jqget "('ok' if d.get('success') else 'ERR: '+str(d.get('errors')))"
+else
+  curl -s -X PUT "${H[@]}" "$API/zones/$ZONE/dns_records/$RID" -d "$BODY" \
+    | jqget "('ok(updated)' if d.get('success') else 'ERR: '+str(d.get('errors')))"
+fi
+
+# 4. Get the run token + install a token-run LaunchAgent.
+RUNTOK=$(curl -s "${H[@]}" "$API/accounts/$ACCT/cfd_tunnel/$TID/token" | jqget "d['result']")
+[ -n "$RUNTOK" ] && [ "$RUNTOK" != "None" ] || { echo "ERROR: no run token (check token scope)"; exit 1; }
+TOKFILE="$HOME/.cloudflared/wpb-curator.token"; mkdir -p "$HOME/.cloudflared"
+printf '%s' "$RUNTOK" > "$TOKFILE"; chmod 600 "$TOKFILE"
+
+cat > "$PLIST" <<PLISTEOF
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0"><dict>
+  <key>Label</key><string>com.steve.wpb-curator-tunnel</string>
+  <key>ProgramArguments</key>
+  <array>
+    <string>/bin/sh</string><string>-c</string>
+    <string>exec $(command -v cloudflared) tunnel --no-autoupdate run --token "\$(cat $TOKFILE)"</string>
+  </array>
+  <key>RunAtLoad</key><true/>
+  <key>KeepAlive</key><true/>
+  <key>StandardOutPath</key><string>$HOME/Library/Logs/wpb-curator-tunnel.log</string>
+  <key>StandardErrorPath</key><string>$HOME/Library/Logs/wpb-curator-tunnel.log</string>
+</dict></plist>
+PLISTEOF
+launchctl bootout "gui/$(id -u)/com.steve.wpb-curator-tunnel" 2>/dev/null || true
+launchctl bootstrap "gui/$(id -u)" "$PLIST"
+launchctl kickstart -k "gui/$(id -u)/com.steve.wpb-curator-tunnel" 2>/dev/null || true
+echo "✓ tunnel up via API + LaunchAgent installed. Log: ~/Library/Logs/wpb-curator-tunnel.log"
+echo "  curator.wallpapersback.com resolves after the GoDaddy→Cloudflare NS cutover."
+echo "  (You may revoke the scoped CF token now — the run token is what keeps the tunnel alive.)"

← a641549 Add curator.wallpapersback.com tunnel finish-script (post cl  ·  back to Wallco Ai  ·  curator: stage Kamatera reverse-proxy fallback vhost (DTD-A, 94ea8f3 →