From af2e94aa461db6aa405d76d2a34c4faae6e56e6e Mon Sep 17 00:00:00 2001 From: zwitschi Date: Mon, 10 Aug 2026 21:11:00 +0200 Subject: [PATCH] fix: switch Cloudflare Pages deploy from tar+octet-stream to multipart+manifest API --- .gitea/workflows/pages-deploy.yml | 38 +++++++++++++++++-- docs/operations/gitea-cloudflare.pages.md | 46 ++++++++++++++++------- 2 files changed, 66 insertions(+), 18 deletions(-) diff --git a/.gitea/workflows/pages-deploy.yml b/.gitea/workflows/pages-deploy.yml index da20d3e..544b58f 100644 --- a/.gitea/workflows/pages-deploy.yml +++ b/.gitea/workflows/pages-deploy.yml @@ -25,16 +25,46 @@ jobs: - name: Build run: cd frontend && npm run build - - name: Create deployment artifact - run: tar -czf site.tgz -C frontend/dist . + - name: Generate deployment manifest + run: | + cd frontend/dist + echo "{" > manifest.json + first=true + for file in $(find . -type f | sed 's|^\./||'); do + hash=$(sha256sum "$file" | awk '{print $1}') + if [ "$first" = true ]; then + first=false + else + echo "," >> manifest.json + fi + printf ' "%s": "%s"' "$file" "$hash" >> manifest.json + done + echo "" >> manifest.json + echo "}" >> manifest.json + echo "Generated manifest with $(grep -c ':' manifest.json) files" - name: Deploy to Cloudflare Pages env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} run: | + cd frontend/dist + + # Build multipart form arguments from all files + manifest + form_args="-F manifest=@manifest.json" + for file in $(find . -type f ! -name manifest.json | sed 's|^\./||'); do + form_args="$form_args -F $file=@$file" + done + + # Get short commit hash for deployment metadata + COMMIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") + COMMIT_MSG=$(git log -1 --pretty=%B 2>/dev/null | head -1 || echo "Gitea Actions deploy") + curl -sS -X POST \ "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/linearvoid/deployments" \ -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ - -H "Content-Type: application/octet-stream" \ - --data-binary @site.tgz + -H "Content-Type: multipart/form-data" \ + -F "commit_hash=${COMMIT_HASH}" \ + -F "commit_message=${COMMIT_MSG}" \ + -F "commit_dirty=false" \ + $form_args diff --git a/docs/operations/gitea-cloudflare.pages.md b/docs/operations/gitea-cloudflare.pages.md index 58a2e20..7669374 100644 --- a/docs/operations/gitea-cloudflare.pages.md +++ b/docs/operations/gitea-cloudflare.pages.md @@ -53,7 +53,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 - name: Install dependencies run: cd frontend && npm ci @@ -71,24 +71,42 @@ jobs: gitHubToken: ${{ secrets.GITEA_TOKEN }} ``` -If the `cloudflare/pages-action@v1` action is unavailable from Gitea, use the direct API approach: +If the `cloudflare/pages-action@v1` action is unavailable from Gitea, use the direct Cloudflare Pages API (multipart/form-data with manifest): ```yaml -- name: Create deployment artifact - run: tar -czf site.tgz -C frontend/dist . + - name: Generate deployment manifest + run: | + cd frontend/dist + echo "{" > manifest.json + first=true + for file in $(find . -type f | sed 's|^\./||'); do + hash=$(sha256sum "$file" | awk '{print $1}') + if [ "$first" = true ]; then first=false; else echo "," >> manifest.json; fi + printf ' "%s": "%s"' "$file" "$hash" >> manifest.json + done + echo "" >> manifest.json && echo "}" >> manifest.json -- name: Deploy to Cloudflare Pages - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - run: | - curl -sS -X POST \ - "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/linearvoid/deployments" \ - -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ - -H "Content-Type: application/octet-stream" \ - --data-binary @site.tgz + - name: Deploy to Cloudflare Pages + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + run: | + cd frontend/dist + form_args="-F manifest=@manifest.json" + for file in $(find . -type f ! -name manifest.json | sed 's|^\./||'); do + form_args="$form_args -F $file=@$file" + done + COMMIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") + curl -sS -X POST \ + "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/linearvoid/deployments" \ + -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ + -H "Content-Type: multipart/form-data" \ + -F "commit_hash=${COMMIT_HASH}" \ + $form_args ``` +This matches the Cloudflare Pages Direct Upload API: `Content-Type: multipart/form-data`, each file as a separate form field, and a `manifest` JSON mapping filenames to SHA-256 hashes. + ## Secrets to add in Gitea | Secret | Value |