137 lines
6.1 KiB
Markdown
137 lines
6.1 KiB
Markdown
# Gitea to Cloudflare Pages — Linear Void
|
|
|
|
## Goal
|
|
|
|
Deploy the Linear Void frontend (Vite + TypeScript SPA) from Gitea to Cloudflare Pages using Gitea Actions.
|
|
|
|
## Prerequisites
|
|
|
|
> **Important:** The Pages project must exist before the API deploy call works. You'll get error 8000007 ("Project not found") if you skip this step.
|
|
|
|
1. **Cloudflare Pages project** created first:
|
|
- Dashboard → Workers & Pages → Create → Pages → Direct Upload
|
|
- Name it `linearvoid` (or a different name if `linearvoid` is already taken by a Worker — see note below)
|
|
- See [Cloudflare Pages Setup](./cloudflare-pages-setup.md) for full details.
|
|
2. **Project name**: note the exact name (e.g., `linearvoid`).
|
|
3. **API token** with Pages permissions:
|
|
- Create at <https://dash.cloudflare.com/profile/api-tokens>
|
|
- Permission: `Account → Cloudflare Pages → Edit`
|
|
- Store in Gitea → Settings → Secrets as `CLOUDFLARE_API_TOKEN`
|
|
4. **Account ID** from Cloudflare dashboard overview → store as `CLOUDFLARE_ACCOUNT_ID`
|
|
|
|
> **Naming conflict:** If a Worker already exists with the name `linearvoid`, the Pages project must use a different name (e.g., `linearvoid-site`). Update the `projectName` in the workflow accordingly. Workers and Pages share a namespace under "Workers & Pages".
|
|
|
|
## Project-specific values
|
|
|
|
| Setting | Value |
|
|
| ------------------ | ---------------------------------------------------------- |
|
|
| Framework | Vite + TypeScript |
|
|
| Build command | `cd frontend && npm install && npm run build` |
|
|
| Output directory | `frontend/dist` |
|
|
| Node version | 20 |
|
|
| Multi-page entries | `index.html`, `admin/index.html`, `admin/login/index.html` |
|
|
|
|
## Gitea Actions workflow
|
|
|
|
Create `.gitea/workflows/pages-deploy.yml`:
|
|
|
|
```yaml
|
|
name: Deploy to Cloudflare Pages
|
|
|
|
on:
|
|
push:
|
|
branches: ["main"]
|
|
workflow_dispatch: {}
|
|
|
|
jobs:
|
|
deploy:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
|
|
- name: Install dependencies
|
|
run: cd frontend && npm ci
|
|
|
|
- name: Build
|
|
run: cd frontend && npm run build
|
|
|
|
- name: Deploy to Cloudflare Pages
|
|
uses: cloudflare/pages-action@v1
|
|
with:
|
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
projectName: linearvoid
|
|
directory: frontend/dist
|
|
gitHubToken: ${{ secrets.GITEA_TOKEN }}
|
|
```
|
|
|
|
If the `cloudflare/pages-action@v1` action is unavailable from Gitea, use the direct Cloudflare Pages API (multipart/form-data with manifest):
|
|
|
|
```yaml
|
|
- 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: |
|
|
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 |
|
|
| ----------------------- | ------------------------------------ |
|
|
| `CLOUDFLARE_API_TOKEN` | API token with Pages:Edit permission |
|
|
| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare account ID from dashboard |
|
|
|
|
## Verify deployment
|
|
|
|
1. Push to `main` or trigger manually from Gitea Actions.
|
|
2. Check **Gitea Actions** → job logs for the deploy step.
|
|
3. Check **Cloudflare Pages** → Deployments for the new deployment.
|
|
4. Visit the `.pages.dev` preview URL, then promote to `linearvoid.tv` when ready.
|
|
|
|
## Notes
|
|
|
|
- The existing `ci.yml` already builds the frontend; the Pages deploy workflow is additive and independent.
|
|
- Multi-page SPA (`vite.config.ts`) produces three entry points — Cloudflare Pages serves them correctly.
|
|
- For SPA fallback routing, add a `frontend/public/_redirects` file with `/* /index.html 200` before building.
|
|
- If deployments fail with 403, verify the API token has `Account → Cloudflare Pages → Edit` permission.
|
|
- If you get error **8000007** ("Project not found"), the Pages project hasn't been created yet. Create it via the Cloudflare dashboard (Workers & Pages → Create → Pages → Direct Upload) first, then re-run.
|
|
- If `linearvoid` is already taken by a Worker, use a different project name like `linearvoid-site` and update the `projectName` and `PROJECT_NAME` values in the workflow.
|
|
|
|
## Related resources
|
|
|
|
- Cloudflare Workers dashboard: `https://dash.cloudflare.com/{accountId}/workers/services/view/linearvoid/production/settings` — for managing the `infrastructure/cloudflare/worker.js` deployment
|
|
- Cloudflare Pages project: `https://dash.cloudflare.com/{accountId}/pages/view/linearvoid` — deployment history and custom domain settings
|