← back to Wallco Ai
deploy/curator-tunnel-finish-api.sh
83 lines
#!/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.)"