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

-57
View File
@@ -1,57 +0,0 @@
name: Deploy to Cloudflare Pages
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: 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
# 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}" \
-F "commit_hash=${COMMIT_HASH}" \
-F "commit_message=${COMMIT_MSG}" \
-F "commit_dirty=false" \
$form_args
+34
View File
@@ -0,0 +1,34 @@
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
+1 -1
View File
@@ -109,7 +109,7 @@
- Total: 45 frontend tests passing (2 test files). - Total: 45 frontend tests passing (2 test files).
- Added coverage reporting: `vitest.config.ts` with v8 provider, thresholds at 20/15/15/20 (statements/branches/functions/lines). - Added coverage reporting: `vitest.config.ts` with v8 provider, thresholds at 20/15/15/20 (statements/branches/functions/lines).
- Added `test:coverage` script to `package.json`. - Added `test:coverage` script to `package.json`.
- Fixed CI: bumped Node from 20 to 22 in `ci.yml` and `pages-deploy.yml` (jsdom@30 requires Node 22+). Added `engines` to `package.json`. - Fixed CI: bumped Node from 20 to 22 in `ci.yml` and `workers-deploy.yml` (jsdom@30 requires Node 22+). Added `engines` to `package.json`.
- Updated AGENTS.md: added Required tracking files table with 13 docs and update cadence. - Updated AGENTS.md: added Required tracking files table with 13 docs and update cadence.
- All Go vet, Go tests, frontend test:coverage passing. - All Go vet, Go tests, frontend test:coverage passing.
- CORS on media.linearvoid.tv remains the only streaming pipeline gap (Cloudflare R2 config, not code). - CORS on media.linearvoid.tv remains the only streaming pipeline gap (Cloudflare R2 config, not code).
+5 -5
View File
@@ -6,8 +6,8 @@
### Phase 5: complete manual Cloudflare and external-service setup ### Phase 5: complete manual Cloudflare and external-service setup
- [ ] Apply Cloudflare Pages, Tiered Cache, WAF, rate limiting, and HTTPS/HSTS settings in the dashboard - [ ] Apply Cloudflare Workers, Tiered Cache, WAF, rate limiting, and HTTPS/HSTS settings in the dashboard
- [ ] Cloudflare Pages: link custom domain and cut over frontend hosting - [ ] Cloudflare Workers: link custom domain and cut over frontend hosting
- [ ] Enable Smart Tiered Cache; validate `Cf-Cache-Status: HIT` from upper tier - [ ] Enable Smart Tiered Cache; validate `Cf-Cache-Status: HIT` from upper tier
- [ ] WAF: enable OWASP Managed Ruleset (Simulate → review 48h → Block), enable Bot Fight Mode - [ ] WAF: enable OWASP Managed Ruleset (Simulate → review 48h → Block), enable Bot Fight Mode
- [ ] Rate limiting: `/api/admin/login` + `/api/admin/*` → 10 req/10s per IP, Challenge action - [ ] Rate limiting: `/api/admin/login` + `/api/admin/*` → 10 req/10s per IP, Challenge action
@@ -58,10 +58,10 @@
## Cloudflare Configuration ## Cloudflare Configuration
### DNS & Pages ### DNS & Workers
- [ ] Cloudflare Pages: link custom domain (manual) - [ ] Cloudflare Workers: link custom domain (manual)
- [ ] Create Cloudflare Pages project and cut over frontend hosting (manual) - [ ] Create Cloudflare Worker and cut over frontend hosting (manual)
### Security & TLS ### Security & TLS
+1 -1
View File
@@ -187,7 +187,7 @@ frontend/
Everything needed to deploy and configure the stack (Docker, Nginx, Cloudflare rules, future Terraform). Everything needed to deploy and configure the stack (Docker, Nginx, Cloudflare rules, future Terraform).
- Cloudflare - Cloudflare
- [worker.js](infrastructure/cloudflare/worker.js) — adds immutable cache headers to HLS segments - [worker.js](infrastructure/cloudflare/worker.js) — Cloudflare Worker serving the static SPA and adding immutable cache headers to HLS segments
- Nginx - Nginx
- [Dockerfile](infrastructure/nginx/Dockerfile) — builds Nginx with custom config - [Dockerfile](infrastructure/nginx/Dockerfile) — builds Nginx with custom config
- [nginx.conf](infrastructure/nginx/nginx.conf) — reverse proxy, static SPA, HLS delivery - [nginx.conf](infrastructure/nginx/nginx.conf) — reverse proxy, static SPA, HLS delivery
+23 -22
View File
@@ -14,6 +14,7 @@
- [Target Topology](#target-topology) - [Target Topology](#target-topology)
- [Prerequisites](#prerequisites) - [Prerequisites](#prerequisites)
- [Environment File](#environment-file) - [Environment File](#environment-file)
- [Gitea Secrets](#gitea-secrets)
- [Pull-and-Run Deployment](#pull-and-run-deployment) - [Pull-and-Run Deployment](#pull-and-run-deployment)
- [First-Time or Recovery Provisioning](#first-time-or-recovery-provisioning) - [First-Time or Recovery Provisioning](#first-time-or-recovery-provisioning)
- [Health Verification](#health-verification) - [Health Verification](#health-verification)
@@ -93,12 +94,12 @@ Readiness is not complete until the checklist above and the manual evidence trac
Three Gitea Actions workflows drive the full pipeline: Three Gitea Actions workflows drive the full pipeline:
| Workflow | Trigger | Purpose | | Workflow | Trigger | Purpose |
| ------------------ | ---------------- | ---------------------------------------------------------------------------------------- | | -------------------- | ----------------- | ----------------------------------------------------------------------------------------- |
| `ci.yml` | Push to `main` | Lint, build frontend, vet Go, build & push Docker images (`:latest`, `:main`) | | `ci.yml` | Push to `main` | Lint, build frontend, vet Go, build & push Docker images (`:latest`, `:main`) |
| `cd.yml` | Push to `main` | Deploy to test lab (`192.168.88.40:2375`) — runs **in parallel** with CI | | `cd.yml` | Push to `main` | Deploy to test lab (`192.168.88.40:2375`) — runs **in parallel** with CI |
| `release.yml` | Tag push `*.*.*` | Build & push versioned Docker images (`:X.Y.Z`, `:X.Y`, `:latest`), create Gitea Release | | `release.yml` | Tag push `*.*.*` | Build & push versioned Docker images (`:X.Y.Z`, `:X.Y`, `:latest`), create Gitea Release |
| `pages-deploy.yml` | Push to `main` | Deploy frontend to Cloudflare Pages (see `docs/operations/gitea-cloudflare-pages.md`) | | `workers-deploy.yml` | After CI succeeds | Deploy frontend to Cloudflare Workers (see `docs/operations/gitea-cloudflare-workers.md`) |
CI and CD run simultaneously on every push to `main`. The deploy pulls whatever `:latest` images are available in the registry at that moment. CI and CD run simultaneously on every push to `main`. The deploy pulls whatever `:latest` images are available in the registry at that moment.
@@ -146,7 +147,7 @@ Use `.env` with at least:
- `R2_ENDPOINT` — S3-compatible endpoint URL - `R2_ENDPOINT` — S3-compatible endpoint URL
- `R2_REGION` — set to `auto` - `R2_REGION` — set to `auto`
**Cloudflare (optional, for Workers/Pages):** **Cloudflare (optional, for Workers):**
- `CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_ACCOUNT_ID` - `CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_ACCOUNT_ID`
- `MEDIA_DOMAIN` — split media domain (e.g. `media.linearvoid.tv`) - `MEDIA_DOMAIN` — split media domain (e.g. `media.linearvoid.tv`)
@@ -157,22 +158,22 @@ See `.env.example` and `SETUP.md` for full variable reference.
The CD workflow (`cd.yml`) injects sensitive values from Gitea secrets into the `.env` file at deploy time. The following secrets must be configured in the Gitea repository: The CD workflow (`cd.yml`) injects sensitive values from Gitea secrets into the `.env` file at deploy time. The following secrets must be configured in the Gitea repository:
| Secret Name | Description | | Secret Name | Description |
| ------------------------- | ------------------------------------ | | ---------------------------- | --------------------------------- |
| `DATABASE_URL` | PostgreSQL connection string | | `DATABASE_URL` | PostgreSQL connection string |
| `ADMIN_USERNAME` | Admin dashboard username | | `ADMIN_USERNAME` | Admin dashboard username |
| `ADMIN_PASSWORD` | Admin dashboard password | | `ADMIN_PASSWORD` | Admin dashboard password |
| `R2_ACCESS_KEY_ID` | Cloudflare R2 access key | | `R2_ACCESS_KEY_ID` | Cloudflare R2 access key |
| `R2_SECRET_ACCESS_KEY` | Cloudflare R2 secret key | | `R2_SECRET_ACCESS_KEY` | Cloudflare R2 secret key |
| `R2_BUCKET_NAME` | R2 bucket name | | `R2_BUCKET_NAME` | R2 bucket name |
| `R2_ENDPOINT` | R2 S3-compatible endpoint URL | | `R2_ENDPOINT` | R2 S3-compatible endpoint URL |
| `R2_ADMIN_ACCESS_KEY_ID` | R2 admin access key (optional) | | `R2_ADMIN_ACCESS_KEY_ID` | R2 admin access key (optional) |
| `R2_ADMIN_SECRET_ACCESS_KEY` | R2 admin secret key (optional) | | `R2_ADMIN_SECRET_ACCESS_KEY` | R2 admin secret key (optional) |
| `R2_ADMIN_ENDPOINT` | R2 admin endpoint (optional) | | `R2_ADMIN_ENDPOINT` | R2 admin endpoint (optional) |
| `CLOUDFLARE_API_TOKEN` | Cloudflare API token (optional) | | `CLOUDFLARE_API_TOKEN` | Cloudflare API token (optional) |
| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare account ID (optional) | | `CLOUDFLARE_ACCOUNT_ID` | Cloudflare account ID (optional) |
| `CI_REGISTRY_USER` | Gitea container registry username | | `CI_REGISTRY_USER` | Gitea container registry username |
| `CI_REGISTRY_TOKEN` | Gitea container registry token | | `CI_REGISTRY_TOKEN` | Gitea container registry token |
## Pull-and-Run Deployment ## Pull-and-Run Deployment
+1 -1
View File
@@ -9,7 +9,7 @@ This folder contains the documentation for the Linear Void project. The document
## Infrastructure ## Infrastructure
- Cloudflare - Cloudflare
- [Cache Rules](./infrastructure/cloudflare/cache-rules.md) - Cache rules for Cloudflare Workers and Pages. - [Cache Rules](./infrastructure/cloudflare/cache-rules.md) - Cache rules for Cloudflare Workers.
- [Account Troubleshooting](./infrastructure/cloudflare/cloudflare-account-troubleshooting.md) - Troubleshooting Cloudflare account issues. - [Account Troubleshooting](./infrastructure/cloudflare/cloudflare-account-troubleshooting.md) - Troubleshooting Cloudflare account issues.
- Monitoring - Monitoring
- [Prometheus CURL](./infrastructure/monitoring/prometheus_curl.md) - Prometheus metrics endpoint testing with curl and jq. Doocumentation of available metrics and their meanings. - [Prometheus CURL](./infrastructure/monitoring/prometheus_curl.md) - Prometheus metrics endpoint testing with curl and jq. Doocumentation of available metrics and their meanings.
+10 -15
View File
@@ -188,14 +188,14 @@ flowchart LR
## Cloudflare ## Cloudflare
Use this section for full edge setup (DNS, Pages, cache, security, and monitoring). Use this section for full edge setup (DNS, Workers, cache, security, and monitoring).
Execution tracker: `docs/operations/cloudflare-uptimerobot-checklist.md`. Execution tracker: `docs/operations/cloudflare-uptimerobot-checklist.md`.
### 1. DNS Setup ### 1. DNS Setup
1. Add apex record for `linearvoid.tv` pointing to Cloudflare Pages target. 1. Add apex record for `linearvoid.tv` pointing to the Cloudflare Worker.
2. Add `www` CNAME to Cloudflare Pages and configure redirect to `https://linearvoid.tv`. 2. Add `www` CNAME to the Cloudflare Worker and configure redirect to `https://linearvoid.tv`.
3. Add `media.linearvoid.tv` for split-domain HLS delivery. 3. Add `media.linearvoid.tv` for split-domain HLS delivery.
4. Keep all records proxied (orange cloud) unless troubleshooting requires DNS-only mode. 4. Keep all records proxied (orange cloud) unless troubleshooting requires DNS-only mode.
@@ -230,26 +230,21 @@ Configure Cloudflare Cache Rules:
Reference: [cache-rules](../docs/infrastructure/cloudflare/cache-rules.md). Reference: [cache-rules](../docs/infrastructure/cloudflare/cache-rules.md).
### 4. Cloudflare Pages ### 4. Cloudflare Workers
Also see [the automated Gitea Actions deployment guide](./operations/gitea-cloudflare-pages.md). Also see [the automated Gitea Actions deployment guide](./operations/gitea-cloudflare-workers.md).
Manual setup: Manual setup:
1. Connect Gitea repository to Cloudflare Pages. 1. Create a Cloudflare Worker for the static SPA (Workers & Pages → Create → Worker).
2. Build command: 2. Build the frontend:
```bash ```bash
cd frontend && npm run build cd frontend && npm run build
``` ```
3. Output directory: 3. Bind the build output (`frontend/dist`) as Worker static assets.
4. Configure production domain `linearvoid.tv` on the Worker.
```text
frontend/dist
```
4. Configure production domain to `linearvoid.tv`.
### 5. Tiered Cache ### 5. Tiered Cache
@@ -306,7 +301,7 @@ Create external monitors:
### 10. Verification Checklist ### 10. Verification Checklist
1. DNS resolves for `linearvoid.tv`, `www.linearvoid.tv`, and `media.linearvoid.tv`. 1. DNS resolves for `linearvoid.tv`, `www.linearvoid.tv`, and `media.linearvoid.tv`.
2. Pages serves frontend for apex and `www` redirects correctly. 2. Workers serve frontend for apex and `www` redirects correctly.
3. HLS segments are cacheable long-term, manifests refresh every ~2s. 3. HLS segments are cacheable long-term, manifests refresh every ~2s.
4. Admin paths are rate-limited and WAF events are visible. 4. Admin paths are rate-limited and WAF events are visible.
5. HTTPS and WSS both succeed with valid certificates. 5. HTTPS and WSS both succeed with valid certificates.
+10 -10
View File
@@ -164,7 +164,7 @@ flowchart TD
subgraph "Cloudflare Global Edge" subgraph "Cloudflare Global Edge"
CDN[Cloudflare CDN<br/>Cache Rules + WAF + TLS] CDN[Cloudflare CDN<br/>Cache Rules + WAF + TLS]
Pages[Cloudflare Pages<br/>Static SPA Hosting] Workers[Cloudflare Workers<br/>Static SPA Hosting]
R2[Cloudflare R2<br/>HLS Segments + Source MP4s<br/>enam location hint] R2[Cloudflare R2<br/>HLS Segments + Source MP4s<br/>enam location hint]
end end
@@ -177,17 +177,17 @@ flowchart TD
Admins -->|HTTPS - Schedule/Playlist| Playout Admins -->|HTTPS - Schedule/Playlist| Playout
Playout -->|S3-compatible Upload| R2 Playout -->|S3-compatible Upload| R2
R2 -->|Cached HLS| CDN R2 -->|Cached HLS| CDN
Viewers -->|HTTPS| Pages Viewers -->|HTTPS| Workers
Viewers -->|HLS Requests| CDN Viewers -->|HLS Requests| CDN
Viewers -->|WSS Chat| Chat Viewers -->|WSS Chat| Chat
Chat --> VPS Chat --> VPS
VPS -->|Proxy / Static| Pages VPS -->|Proxy / Static| Workers
CDN -->|Origin Pull on Miss| R2 CDN -->|Origin Pull on Miss| R2
``` ```
**External Interfaces**: **External Interfaces**:
- **Cloudflare**: CDN (cache rules for `.m4s` and `.ts` vs `.m3u8`), R2 (object storage, $0 egress), Pages (static hosting), WAF/TLS. - **Cloudflare**: CDN (cache rules for `.m4s` and `.ts` vs `.m3u8`), R2 (object storage, $0 egress), Workers (static hosting), WAF/TLS.
- **Hetzner VPS**: Compute for playout and chat (low-cost US-East with good peering). - **Hetzner VPS**: Compute for playout and chat (low-cost US-East with good peering).
- **Browsers**: Native HLS (`<video>` + hls.js) and WebSocket APIs. - **Browsers**: Native HLS (`<video>` + hls.js) and WebSocket APIs.
- **Source**: Pre-normalized MP4 files uploaded to R2. - **Source**: Pre-normalized MP4 files uploaded to R2.
@@ -233,7 +233,7 @@ graph TD
subgraph Cloudflare subgraph Cloudflare
CDN[CDN + Cache Rules<br/>.m4s and .ts immutable long TTL<br/>.m3u8 short TTL] CDN[CDN + Cache Rules<br/>.m4s and .ts immutable long TTL<br/>.m3u8 short TTL]
R2[R2 - HLS + Source] R2[R2 - HLS + Source]
Pages[Pages - Static SPA] Workers[Workers - Static SPA]
end end
VPS[VPS Hetzner Ashburn] VPS[VPS Hetzner Ashburn]
Playout[FFmpeg + MediaMTX/SRS<br/>Playlist → HLS] Playout[FFmpeg + MediaMTX/SRS<br/>Playlist → HLS]
@@ -244,7 +244,7 @@ graph TD
Admin --> Playout Admin --> Playout
Playout --> R2 Playout --> R2
R2 --> CDN R2 --> CDN
Viewer --> Pages Viewer --> Workers
Viewer --> CDN Viewer --> CDN
Viewer --> Chat Viewer --> Chat
Chat --> VPS Chat --> VPS
@@ -274,7 +274,7 @@ graph TB
subgraph "Cloudflare Edge" subgraph "Cloudflare Edge"
CF[CDN + WAF<br/>Cache Rules] CF[CDN + WAF<br/>Cache Rules]
R2[R2 Bucket<br/>media + HLS] R2[R2 Bucket<br/>media + HLS]
PAGES[Pages<br/>SPA hosting] WORKERS[Workers<br/>SPA hosting]
end end
subgraph "Docker Host (Hetzner CX22)" subgraph "Docker Host (Hetzner CX22)"
@@ -461,7 +461,7 @@ flowchart LR
### Infrastructure Level 1 ### Infrastructure Level 1
**Overview Diagram**: See Mermaid in Building Block section (VPS + Cloudflare R2/Pages/CDN). **Overview Diagram**: See Mermaid in Building Block section (VPS + Cloudflare R2/Workers/CDN).
**Motivation**: Low-cost US-East origin with global edge caching. Hetzner provides excellent price/performance and included traffic; Cloudflare eliminates egress fees. **Motivation**: Low-cost US-East origin with global edge caching. Hetzner provides excellent price/performance and included traffic; Cloudflare eliminates egress fees.
@@ -470,7 +470,7 @@ flowchart LR
**Mapping of Building Blocks to Infrastructure**: **Mapping of Building Blocks to Infrastructure**:
- Playout + Chat + Nginx → Hetzner Cloud CX22 (Ashburn, VA). - Playout + Chat + Nginx → Hetzner Cloud CX22 (Ashburn, VA).
- HLS storage, caching, static SPA → Cloudflare R2 + CDN + Pages (custom domains, `enam` location hint). - HLS storage, caching, static SPA → Cloudflare R2 + CDN + Workers (custom domains, `enam` location hint).
- All public traffic routed through Cloudflare. - All public traffic routed through Cloudflare.
### Infrastructure Level 2 ### Infrastructure Level 2
@@ -482,7 +482,7 @@ flowchart LR
- R2 bucket for media. - R2 bucket for media.
- Cache Rules (`.m4s` and `.ts`: Cache Everything, immutable, long TTL; `.m3u8`: short TTL/must-revalidate). - Cache Rules (`.m4s` and `.ts`: Cache Everything, immutable, long TTL; `.m3u8`: short TTL/must-revalidate).
- WAF, rate limiting, Tiered Cache. - WAF, rate limiting, Tiered Cache.
- Pages for SPA hosting. - Workers for SPA hosting.
Future evolution: Docker Compose, managed DB, secondary regions (detailed in subsequent ADRs). Future evolution: Docker Compose, managed DB, secondary regions (detailed in subsequent ADRs).
+2 -2
View File
@@ -69,7 +69,7 @@ Adopt a **self-managed US-East playout + Cloudflare R2 + edge-cached HLS** archi
### 3. Frontend ### 3. Frontend
- Static SPA (HTML + custom CSS + vanilla JS) hosted on Cloudflare Pages or same VPS/Nginx. - Static SPA (HTML + custom CSS + vanilla JS) hosted on Cloudflare Workers or same VPS/Nginx.
- Video player: `hls.js` (Chromium/Firefox) or native HLS (Safari). Single live `master.m3u8`. - Video player: `hls.js` (Chromium/Firefox) or native HLS (Safari). Single live `master.m3u8`.
- Embedded chat sidebar with room switcher, private DMs, now-playing overlay. - Embedded chat sidebar with room switcher, private DMs, now-playing overlay.
@@ -82,7 +82,7 @@ Adopt a **self-managed US-East playout + Cloudflare R2 + edge-cached HLS** archi
### 5. Infrastructure Summary & Cost Model ### 5. Infrastructure Summary & Cost Model
- **Primary**: Hetzner Ashburn VPS (playout + chat + Nginx) + Cloudflare R2 + Pages. - **Primary**: Hetzner Ashburn VPS (playout + chat + Nginx) + Cloudflare R2 + Workers.
- **Monitoring**: MediaMTX/SRS dashboard, UptimeRobot, systemd logs, Cloudflare analytics. - **Monitoring**: MediaMTX/SRS dashboard, UptimeRobot, systemd logs, Cloudflare analytics.
- **Backup**: Weekly R2/S3-compatible snapshots of playlist, DB, config. - **Backup**: Weekly R2/S3-compatible snapshots of playlist, DB, config.
- **Fallback**: Emergency slate/loop playlist, automated restart. - **Fallback**: Emergency slate/loop playlist, automated restart.
@@ -2,7 +2,7 @@
Reference for Cloudflare account and API token troubleshooting. Reference for Cloudflare account and API token troubleshooting.
Use `https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects` to verify the account ID and API token are valid. Use `wrangler whoami` to verify the account ID and API token are valid, or query the Workers API at `https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/workers/scripts`.
## Validate Cloudflare account and API token ## Validate Cloudflare account and API token
@@ -12,13 +12,13 @@ Set-Location -Path "E:\Documents\02-Projects\07-Mentoring\Corey\linearvoid"
Get-Content .env | Select-String 'CLOUDFLARE_API_TOKEN' Get-Content .env | Select-String 'CLOUDFLARE_API_TOKEN'
Get-Content .env | Select-String 'CLOUDFLARE_ACCOUNT_ID' Get-Content .env | Select-String 'CLOUDFLARE_ACCOUNT_ID'
# Test API token with curl # Test API token with wrangler
npx wrangler whoami
# Or verify with the Workers API directly
$env:CLOUDFLARE_API_TOKEN = (Get-Content .env | Select-String 'CLOUDFLARE_API_TOKEN' | ForEach-Object { $_.Line.Split('=')[1].Trim() }) $env:CLOUDFLARE_API_TOKEN = (Get-Content .env | Select-String 'CLOUDFLARE_API_TOKEN' | ForEach-Object { $_.Line.Split('=')[1].Trim() })
$env:CLOUDFLARE_ACCOUNT_ID = (Get-Content .env | Select-String 'CLOUDFLARE_ACCOUNT_ID' | ForEach-Object { $_.Line.Split('=')[1].Trim() }) $env:CLOUDFLARE_ACCOUNT_ID = (Get-Content .env | Select-String 'CLOUDFLARE_ACCOUNT_ID' | ForEach-Object { $_.Line.Split('=')[1].Trim() })
# List all Pages projects for the account # List all Workers for the account
curl -X GET "https://api.cloudflare.com/client/v4/accounts/$env:CLOUDFLARE_ACCOUNT_ID/pages/projects" -H "Authorization: Bearer $env:CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json" curl -X GET "https://api.cloudflare.com/client/v4/accounts/$env:CLOUDFLARE_ACCOUNT_ID/workers/scripts" -H "Authorization: Bearer $env:CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json"
# Create a new Pages project (if needed)
# curl -X POST "https://api.cloudflare.com/client/v4/accounts/$env:CLOUDFLARE_ACCOUNT_ID/pages/projects" -H "Authorization: Bearer $env:CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json" -d '{"name":"linearvoid","production_branch":"main"}'
``` ```
@@ -5,7 +5,7 @@ Purpose: execute and record all manual dashboard tasks that cannot be automated
Recommended execution order: Recommended execution order:
1. Bind `media.linearvoid.tv` and confirm the HLS origin is separate from the app origin. 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. 3. Apply WAF, bot, rate limiting, HTTPS, and HSTS settings.
4. Verify monitor targets and alert delivery in UptimeRobot. 4. Verify monitor targets and alert delivery in UptimeRobot.
5. Capture evidence and fill in the sign-off block. 5. Capture evidence and fill in the sign-off block.
@@ -15,18 +15,18 @@ Recommended execution order:
- [ ] Pending - [ ] Pending
- [x] Done - [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` - [ ] Build command set to: `cd frontend && npm run build`
- [ ] Output directory set to: `frontend/dist` - [ ] Output directory set to: `frontend/dist`
- [ ] Production domain attached to `linearvoid.tv` - [ ] Production domain attached to `linearvoid.tv`
Evidence to capture: Evidence to capture:
- Screenshot of Pages deployment settings - Screenshot of Worker deployment settings
- Deployment URL and latest deployment timestamp - 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 ## 2) Tiered Cache Enablement and Validation
@@ -125,7 +125,7 @@ Evidence to capture:
Completion summary: Completion summary:
- Pages build URL: - Worker build URL:
- Cache validation result: - Cache validation result:
- WAF/rate-limit status: - WAF/rate-limit status:
- HTTPS/HSTS 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 | | 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 | | **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. 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` | | Workers & Pages overview | `https://dash.cloudflare.com/deb11491c54ec53a24772e98ac6d8128/workers-and-pages` |
| Worker: linearvoid | `https://dash.cloudflare.com/deb11491c54ec53a24772e98ac6d8128/workers/services/view/linearvoid/production/settings` | | 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> | | API tokens | <https://dash.cloudflare.com/profile/api-tokens> |
## Worker: `linearvoid` (edge cache headers) ## 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). 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 ### 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 | | Type | Name | Value |
| --------- | -------------------------------------- | ----------------------------------------------- | | --------- | -------------------------------------- | ----------------------------------------------- |
@@ -59,11 +60,11 @@ On Cloudflare pages, set the following environment variables (needed to run the
## Custom domains ## Custom domains
- `linearvoid.tv` → Pages project - `linearvoid.tv` → Worker (static SPA)
- `www.linearvoid.tv` → redirect to `linearvoid.tv` - `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 ## 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) - [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
+6
View File
@@ -0,0 +1,6 @@
name = "linearvoid-frontend"
compatibility_date = "2025-08-01"
[assets]
directory = "./dist"
not_found_handling = "single-page-application"