← back to Claude Webdev Accelerator
scripts/preview-tunnel.sh
59 lines
#!/bin/bash
# Stand up <slug>.agentabrams.com as a preview of a local app on <port>, through the
# shared dw-followup cloudflared tunnel. Reversible local work runs automatically;
# the DNS write (Cloudflare proxied CNAME) only runs with --dns (Steve-gated step).
#
# Usage: scripts/preview-tunnel.sh <slug> <port> [--dns]
set -euo pipefail
SLUG="${1:?usage: preview-tunnel.sh <slug> <port> [--dns]}"
PORT="${2:?need a local port}"
DO_DNS=""; [ "${3:-}" = "--dns" ] && DO_DNS=1
HOST="${SLUG}.agentabrams.com"
CFG="$HOME/.cloudflared/followup.yml"
TUNNEL_ID="0e1edf5a-f5b4-48c9-af62-639645519983" # dw-followup
ZONE_NAME="agentabrams.com"
[ -f "$CFG" ] || { echo "no tunnel config at $CFG"; exit 1; }
# 1. Idempotently add the ingress rule before the 404 catch-all.
if grep -q "hostname: ${HOST}" "$CFG"; then
echo "ingress for ${HOST} already present"
else
# insert two lines before the first "- service: http_status:404"
awk -v host="$HOST" -v port="$PORT" '
/- service: http_status:404/ && !done {
print " - hostname: " host; print " service: http://127.0.0.1:" port; done=1 }
{ print }' "$CFG" > "$CFG.tmp" && mv "$CFG.tmp" "$CFG"
echo "added ingress ${HOST} -> 127.0.0.1:${PORT}"
fi
# 2. Restart the tunnel to pick up the new ingress.
launchctl kickstart -k "gui/$(id -u)/com.steve.followup-tunnel" 2>/dev/null && echo "tunnel restarted" || echo "WARN: could not kickstart tunnel (start it manually)"
# 3. DNS (gated). Repoint/point the CF record to a proxied CNAME on the tunnel.
if [ -n "$DO_DNS" ]; then
# Read ONLY the token (never `source` the whole .env — one malformed value aborts it under set -e).
CLOUDFLARE_API_TOKEN=$(grep -E '^CLOUDFLARE_API_TOKEN=' "$HOME/Projects/secrets-manager/.env" | head -1 | cut -d= -f2- | tr -d "\"'\r")
[ -n "$CLOUDFLARE_API_TOKEN" ] || { echo "CLOUDFLARE_API_TOKEN not found in secrets .env"; exit 1; }
ZONE=$(curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/zones?name=${ZONE_NAME}" | python3 -c "import sys,json;print(json.load(sys.stdin)['result'][0]['id'])")
REC=$(curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/zones/${ZONE}/dns_records?name=${HOST}" | python3 -c "import sys,json;r=json.load(sys.stdin)['result'];print(r[0]['id'] if r else '')")
BODY="{\"type\":\"CNAME\",\"name\":\"${HOST}\",\"content\":\"${TUNNEL_ID}.cfargotunnel.com\",\"proxied\":true,\"comment\":\"preview via dw-followup tunnel\"}"
if [ -n "$REC" ]; then
curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${ZONE}/dns_records/${REC}" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json" -d "$BODY" >/dev/null
echo "updated existing DNS record → proxied CNAME to tunnel"
else
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE}/dns_records" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json" -d "$BODY" >/dev/null
echo "created DNS record → proxied CNAME to tunnel"
fi
echo "live (after CF propagation): https://${HOST}"
else
echo "DNS NOT changed (gated). To publish: re-run with --dns, or add a proxied CNAME"
echo " ${HOST} -> ${TUNNEL_ID}.cfargotunnel.com in Cloudflare (zone ${ZONE_NAME})."
fi
echo "REMINDER: gate the app — run it with BASIC_AUTH=\"admin:DW2024!\" since previews are public."