#!/usr/bin/env bash # Cloudflare side: tunnel + DNS + Worker-route exclusions, via API. # (Terraform would also work here; this is 4 API calls, so: curl.) # # Requires an API token with: # Account > Cloudflare Tunnel > Edit # Zone > DNS > Edit # Zone > Workers Routes > Edit (only if you use the exclusion step) # # Usage: CF_TOKEN=... ./setup.sh set -euo pipefail ZONE_NAME=chadtmiller.com SUBDOMAIN=pi # -> pi.chadtmiller.com and *.pi.chadtmiller.com TUNNEL_NAME=pi ORIGIN=http://localhost:2583 HANDLE_HOST=zds # handle zds.chadtmiller.com (first-level: free # Universal SSL covers *.chadtmiller.com but NOT # *.pi.chadtmiller.com — deep wildcards need paid certs) : "${CF_TOKEN:?set CF_TOKEN to a Cloudflare API token}" api() { curl -sf -H "Authorization: Bearer $CF_TOKEN" -H "Content-Type: application/json" "$@"; } CF=https://api.cloudflare.com/client/v4 read -r ZID AID < <(api "$CF/zones?name=$ZONE_NAME" | python3 -c "import json,sys; z=json.load(sys.stdin)['result'][0]; print(z['id'], z['account']['id'])") echo "zone=$ZID account=$AID" # Tunnel: reuse by name if it exists, else create (remotely-managed). TID=$(api "$CF/accounts/$AID/cfd_tunnel?name=$TUNNEL_NAME&is_deleted=false" | python3 -c "import json,sys; r=json.load(sys.stdin)['result']; print(r[0]['id'] if r else '')") if [ -z "$TID" ]; then TID=$(api -X POST "$CF/accounts/$AID/cfd_tunnel" -d "{\"name\":\"$TUNNEL_NAME\",\"config_src\":\"cloudflare\"}" | python3 -c "import json,sys; print(json.load(sys.stdin)['result']['id'])") echo "created tunnel $TID" else echo "reusing tunnel $TID" fi api -X PUT "$CF/accounts/$AID/cfd_tunnel/$TID/configurations" -d "{\"config\":{\"ingress\":[ {\"hostname\":\"$SUBDOMAIN.$ZONE_NAME\",\"service\":\"$ORIGIN\"}, {\"hostname\":\"$HANDLE_HOST.$ZONE_NAME\",\"service\":\"$ORIGIN\"}, {\"hostname\":\"*.$SUBDOMAIN.$ZONE_NAME\",\"service\":\"$ORIGIN\"}, {\"service\":\"http_status:404\"}]}}" >/dev/null && echo "ingress configured" for NAME in "$SUBDOMAIN" "*.$SUBDOMAIN" "$HANDLE_HOST"; do api -X POST "$CF/zones/$ZID/dns_records" \ -d "{\"type\":\"CNAME\",\"name\":\"$NAME\",\"content\":\"$TID.cfargotunnel.com\",\"proxied\":true}" \ >/dev/null 2>&1 && echo "dns $NAME: created" || echo "dns $NAME: exists (ok)" done # The zone has a Worker ("pds") on chadtmiller.com/* and *.chadtmiller.com/*. # Worker routes intercept proxied traffic before tunnels, so carve out our # hostnames with null-script (no-op) routes. for P in "$SUBDOMAIN.$ZONE_NAME/*" "*.$SUBDOMAIN.$ZONE_NAME/*" "$HANDLE_HOST.$ZONE_NAME/*"; do api -X POST "$CF/zones/$ZID/workers/routes" -d "{\"pattern\":\"$P\",\"script\":null}" \ >/dev/null 2>&1 && echo "worker exclusion $P: created" || echo "worker exclusion $P: exists (ok)" done api "$CF/accounts/$AID/cfd_tunnel/$TID/token" | python3 -c "import json,sys; print(json.load(sys.stdin)['result'])" > tunnel-token chmod 600 tunnel-token echo "tunnel token written to cloudflare/tunnel-token (gitignored) — feed to scripts/provision-pi.sh"