feat(ops): replace Cloudflare Pages with Workers for frontend hosting
CI / test (push) Successful in 2m55s
CI / docker (push) Successful in 46s

This commit is contained in:
zwitschi committed 2026-08-16 08:17:23 +02:00
1 parent 9ac1a8b79d
commit 9e56ff7859
16 files changed
+214 -283

No files matched your search

@@ -5,7 +5,7 @@ Purpose: execute and record all manual dashboard tasks that cannot be automated
Recommended execution order:
1. Bind `media.linearvoid.tv` and confirm the HLS origin is separate from the app origin.
2. Enable Pages, Tiered Cache, and cache behavior for the media zone.
2. Enable Workers, Tiered Cache, and cache behavior for the media zone.
3. Apply WAF, bot, rate limiting, HTTPS, and HSTS settings.
4. Verify monitor targets and alert delivery in UptimeRobot.
5. Capture evidence and fill in the sign-off block.
@@ -15,18 +15,18 @@ Recommended execution order:
- [ ] Pending
- [x] Done
## 1) Cloudflare Pages Link and Build
## 1) Cloudflare Workers Deploy and Custom Domain
- [ ] Connect repo to Cloudflare Pages project
- [ ] Create a Cloudflare Worker for the SPA and bind the build output
- [ ] Build command set to: `cd frontend && npm run build`
- [ ] Output directory set to: `frontend/dist`
- [ ] Production domain attached to `linearvoid.tv`
Evidence to capture:
- Screenshot of Pages deployment settings
- Screenshot of Worker deployment settings
- Deployment URL and latest deployment timestamp
- Confirmed production build output path in the Pages project
- Confirmed production build output path in the Worker
## 2) Tiered Cache Enablement and Validation
@@ -125,7 +125,7 @@ Evidence to capture:
Completion summary:
- Pages build URL:
- Worker build URL:
- Cache validation result:
- WAF/rate-limit status:
- HTTPS/HSTS status:
@@ -1,10 +1,10 @@
# Cloudflare Pages & Workers Setup — Linear Void
# Cloudflare Workers Setup — Linear Void
Linear Void uses two Cloudflare products:
Linear Void uses Cloudflare Workers for two purposes:
| Product | Purpose | What |
| ----------- | ------------------ | ------------------------------------------------------------------------------------ |
| **Pages** | Static SPA hosting | Vite-built frontend (`frontend/dist`) deployed via Git integration or Direct Upload |
| **Workers** | Static SPA hosting | Vite-built frontend (`frontend/dist`) served as Worker static assets |
| **Workers** | Edge cache logic | `infrastructure/cloudflare/worker.js` — adds immutable cache headers to HLS segments |
Both are managed under **Workers & Pages** in the Cloudflare dashboard.
@@ -15,7 +15,6 @@ Both are managed under **Workers & Pages** in the Cloudflare dashboard.
| ------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| Workers & Pages overview | `https://dash.cloudflare.com/deb11491c54ec53a24772e98ac6d8128/workers-and-pages` |
| Worker: linearvoid | `https://dash.cloudflare.com/deb11491c54ec53a24772e98ac6d8128/workers/services/view/linearvoid/production/settings` |
| Pages project | `https://dash.cloudflare.com/deb11491c54ec53a24772e98ac6d8128/pages/view/linearvoid` |
| API tokens | <https://dash.cloudflare.com/profile/api-tokens> |
## Worker: `linearvoid` (edge cache headers)
@@ -30,17 +29,19 @@ npx wrangler deploy infrastructure/cloudflare/worker.js
Or paste into the Workers dashboard at the URL above (Quick Edit).
## Pages: Static frontend
## Worker: static frontend
### Direct Upload via Gitea Actions
Serve the Vite-built SPA (`frontend/dist`) as Worker static assets:
Use `.gitea/workflows/pages-deploy.yml` to build in Gitea Actions and upload via the Cloudflare API.
1. Build the frontend: `cd frontend && npm run build`.
2. Bind `frontend/dist` to the Worker as static assets (Workers Static Assets / `assets` in `wrangler.toml`).
3. Attach the `linearvoid.tv` custom domain to the Worker.
See [gitea-cloudflare-pages.md](./gitea-cloudflare-pages.md) for the workflow.
See [gitea-cloudflare-workers.md](./gitea-cloudflare-workers.md) for the automated deployment workflow.
### Variables and secrets
On Cloudflare pages, set the following environment variables (needed to run the frontend)
On the Worker, set the following environment variables (needed to run the frontend):
| Type | Name | Value |
| --------- | -------------------------------------- | ----------------------------------------------- |
@@ -59,11 +60,11 @@ On Cloudflare pages, set the following environment variables (needed to run the
## Custom domains
- `linearvoid.tv` → Pages project
- `linearvoid.tv` → Worker (static SPA)
- `www.linearvoid.tv` → redirect to `linearvoid.tv`
- `media.linearvoid.tv` → R2 bucket (separate; not Pages)
- `media.linearvoid.tv` → R2 bucket (separate; not a Worker)
## Related
- [Gitea to Cloudflare Pages deployment guide](./gitea-cloudflare-pages.md)
- [Gitea to Cloudflare Workers deployment guide](./gitea-cloudflare-workers.md)
- [Cloudflare & UptimeRobot checklist](./cloudflare-uptimerobot-checklist.md)
-144
View File
@@ -1,144 +0,0 @@
# 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):
Snippet to generate a manifest in format `-F manifest='{"index.html": "abc123", "style.css": "def456"}'`
```bash
# Build manifest form argument from all files in frontend/dist
form_args="-F manifest={"
for file in $(find frontend/dist -type f | sed 's|^frontend/dist/||'); do
hash=$(sha256sum "frontend/dist/$file" | awk '{print $1}')
form_args="$form_args\"$file\":\"$hash\","
done
form_args="${form_args%,}}" # remove trailing comma and close JSON
```
```yaml
- name: Generate deployment manifest
run: |
cd frontend/dist
form_args="-F manifest={"
for file in $(find . -type f ! -name manifest.json | sed 's|^\./||'); do
hash=$(sha256sum "$file" | awk '{print $1}')
form_args="$form_args\"$file\":\"$hash\","
done
form_args="${form_args%,}}" # remove trailing comma and close JSON
echo "Form arguments: $form_args"
echo "FORM_ARGS=$form_args" >> $GITHUB_ENV
- 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="$FORM_ARGS" # use form arguments generated in previous step
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}" \
-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/deb11491c54ec53a24772e98ac6d8128/workers/services/view/linearvoid/production/settings` — for managing the `infrastructure/cloudflare/worker.js` deployment
- Cloudflare Pages project: `https://dash.cloudflare.com/deb11491c54ec53a24772e98ac6d8128/pages/view/linearvoid` — deployment history and custom domain settings
@@ -0,0 +1,95 @@
# Gitea to Cloudflare Workers — Linear Void
## Goal
Deploy the Linear Void frontend (Vite + TypeScript SPA) from Gitea to Cloudflare Workers using Gitea Actions and Wrangler.
## Prerequisites
1. **Cloudflare Worker** for the SPA:
- Dashboard → Workers & Pages → Create → Worker (or Workers Static Assets).
- Name it `linearvoid-frontend` (the edge-cache Worker already uses the `linearvoid` name).
2. **API token** with Workers permissions:
- Create at <https://dash.cloudflare.com/profile/api-tokens>
- Permission: `Account → Workers Scripts → Edit` and `Account → Workers Routes → Edit`
- Store in Gitea → Settings → Secrets as `CLOUDFLARE_API_TOKEN`
3. **Account ID** from Cloudflare dashboard overview → store as `CLOUDFLARE_ACCOUNT_ID`
4. **Wrangler config** (`frontend/wrangler.toml`) binding the `frontend/dist` output as static assets.
> **Naming conflict:** Workers share a namespace under "Workers & Pages". The edge-cache Worker (`infrastructure/cloudflare/worker.js`) already uses the `linearvoid` name, so the SPA Worker is named `linearvoid-frontend` (see `frontend/wrangler.toml`).
## Project-specific values
| Setting | Value |
| ------------------ | ---------------------------------------------------------- |
| Framework | Vite + TypeScript |
| Build command | `cd frontend && npm install && npm run build` |
| Output directory | `frontend/dist` |
| Node version | 22 |
| Multi-page entries | `index.html`, `admin/index.html`, `admin/login/index.html` |
## Gitea Actions workflow
Build the SPA and deploy with Wrangler:
```yaml
name: Deploy to Cloudflare Workers
on:
workflow_run:
workflows: [CI]
types: [completed]
workflow_dispatch: {}
jobs:
deploy:
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
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 Workers
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
cd frontend
npx wrangler deploy
```
## Secrets to add in Gitea
| Secret | Value |
| ----------------------- | ------------------------------------ |
| `CLOUDFLARE_API_TOKEN` | API token with Workers permissions |
| `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 Workers** → `linearvoid-frontend` → Deployments for the new version.
4. Visit `https://linearvoid-frontend.zwitschi82.workers.dev`, then attach `linearvoid.tv` when ready.
## Notes
- The existing `ci.yml` already builds the frontend; the Workers deploy workflow is additive and independent.
- Multi-page SPA (`vite.config.ts`) produces three entry points — Workers static assets serve them correctly.
- For SPA fallback routing, set Workers static assets `not_found_handling` to `single-page-application` (serve `index.html` for unknown paths).
- The deploy workflow lives in `.gitea/workflows/workers-deploy.yml` (triggered after CI succeeds).
## Related resources
- Cloudflare Workers dashboard: `https://dash.cloudflare.com/deb11491c54ec53a24772e98ac6d8128/workers/services/view/linearvoid/production/settings` — for managing the `infrastructure/cloudflare/worker.js` deployment and the SPA Worker