Deploy Natali Proxy Worker #11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy Natali Proxy Worker | |
| on: | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| deploy-proxy: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Deploy Natali Proxy Worker | |
| env: | |
| CF_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| run: | | |
| set -x | |
| ZONE_ID="5b3f8237bfa2518bff375eb8163623a5" | |
| WORKER_NAME="natalie-proxy" | |
| cat > /tmp/worker.js << 'WORKEREOF' | |
| addEventListener('fetch', event => { | |
| event.respondWith(handleRequest(event.request)); | |
| }); | |
| async function handleRequest(request) { | |
| try { | |
| const url = new URL(request.url); | |
| const path = url.pathname + url.search; | |
| // Debug: capture request info | |
| let debugInfo = { | |
| path: path, | |
| headers: Object.fromEntries(request.headers), | |
| }; | |
| // Try fetch with the GH Pages domain | |
| const ghUrl = 'https://spivanatalie64.github.io' + path; | |
| // Set the host header to the GH Pages domain to avoid custom domain redirect | |
| const reqHeaders = new Headers(request.headers); | |
| reqHeaders.set('Host', 'spivanatalie64.github.io'); | |
| let response = await fetch(ghUrl, { | |
| method: request.method, | |
| headers: reqHeaders, | |
| body: request.body, | |
| redirect: 'manual', | |
| }); | |
| debugInfo.firstStatus = response.status; | |
| debugInfo.firstLocation = response.headers.get('Location'); | |
| // If we got a redirect, try following it with the custom domain host | |
| if (response.status >= 300 && response.status < 400) { | |
| const location = response.headers.get('Location'); | |
| if (location && location.includes('natalie.acreetionos.org')) { | |
| // Replace our domain with gh pages domain to avoid loop | |
| const locUrl = new URL(location); | |
| locUrl.hostname = 'spivanatalie64.github.io'; | |
| const followHeaders = new Headers(request.headers); | |
| followHeaders.set('Host', 'natalie.acreetionos.org'); | |
| followHeaders.set('Accept', request.headers.get('Accept') || 'text/html'); | |
| const followResponse = await fetch(locUrl.toString(), { | |
| method: 'GET', | |
| headers: followHeaders, | |
| redirect: 'manual', | |
| }); | |
| debugInfo.secondStatus = followResponse.status; | |
| debugInfo.secondLocation = followResponse.headers.get('Location'); | |
| debugInfo.secondContentType = followResponse.headers.get('Content-Type'); | |
| // If the follow succeeded, return its content | |
| if (followResponse.status === 200 || followResponse.status === 404 || followResponse.status === 304) { | |
| const newHeaders = new Headers(followResponse.headers); | |
| newHeaders.set('X-Proxied-By', 'natalie-worker'); | |
| newHeaders.set('X-Debug', JSON.stringify(debugInfo)); | |
| return new Response(followResponse.body, { | |
| status: followResponse.status, | |
| statusText: followResponse.statusText, | |
| headers: newHeaders, | |
| }); | |
| } | |
| // If still a redirect and it's back to natalie, give up and return the content-type info | |
| // Try one more time with raw.githubusercontent.com | |
| const rawUrl = 'https://raw.githubusercontent.com/spivanatalie64/spivanatalie64.github.io/gh-pages' + path; | |
| const rawResponse = await fetch(rawUrl, { | |
| headers: { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' }, | |
| redirect: 'manual', | |
| }); | |
| debugInfo.rawStatus = rawResponse.status; | |
| debugInfo.rawContentType = rawResponse.headers.get('Content-Type'); | |
| if (rawResponse.status === 200) { | |
| // Force the content type to text/html for HTML files | |
| const newHeaders = new Headers(rawResponse.headers); | |
| newHeaders.set('Content-Type', 'text/html; charset=utf-8'); | |
| newHeaders.set('X-Proxied-By', 'natalie-worker'); | |
| newHeaders.set('X-Debug', JSON.stringify(debugInfo)); | |
| return new Response(rawResponse.body, { | |
| status: 200, | |
| statusText: 'OK', | |
| headers: newHeaders, | |
| }); | |
| } | |
| } | |
| } | |
| // Return the response as-is with debug info | |
| const newHeaders = new Headers(response.headers); | |
| newHeaders.set('X-Proxied-By', 'natalie-worker'); | |
| newHeaders.set('X-Debug', JSON.stringify(debugInfo)); | |
| return new Response(response.body, { | |
| status: response.status, | |
| statusText: response.statusText, | |
| headers: newHeaders, | |
| }); | |
| } catch (err) { | |
| return new Response('Worker Error: ' + err.message + '\nStack: ' + (err.stack || ''), { | |
| status: 500, | |
| headers: { 'Content-Type': 'text/plain' }, | |
| }); | |
| } | |
| } | |
| WORKEREOF | |
| cat > /tmp/metadata.json << 'METADATAEOF' | |
| { | |
| "body_part": "script", | |
| "bindings": [], | |
| "compatibility_date": "2026-06-20", | |
| "compatibility_flags": [] | |
| } | |
| METADATAEOF | |
| curl -s -X PUT \ | |
| "https://api.cloudflare.com/client/v4/accounts/6a46b418508c26278ae2d3373f41da81/workers/scripts/$WORKER_NAME" \ | |
| -H "Authorization: Bearer $CF_API_TOKEN" \ | |
| -F "metadata=@/tmp/metadata.json;type=application/json" \ | |
| -F "script=@/tmp/worker.js;type=application/javascript" | jq . | |
| EXISTING_ROUTES=$(curl -s -H "Authorization: Bearer $CF_API_TOKEN" \ | |
| "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/workers/routes") | |
| NATALIE_ROUTE_ID=$(echo "$EXISTING_ROUTES" | jq -r '.result[] | select(.pattern == "natalie.acreetionos.org/*") | .id') | |
| curl -s -X PUT \ | |
| "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/workers/routes/$NATALIE_ROUTE_ID" \ | |
| -H "Authorization: Bearer $CF_API_TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{ | |
| \"pattern\": \"natalie.acreetionos.org/*\", | |
| \"script\": \"$WORKER_NAME\" | |
| }" | jq . | |
| echo "=== Done ===" |