diff --git a/.gitea/workflows/pages-deploy.yml b/.gitea/workflows/pages-deploy.yml
deleted file mode 100644
index f9a4e68..0000000
--- a/.gitea/workflows/pages-deploy.yml
+++ /dev/null
@@ -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
diff --git a/.gitea/workflows/workers-deploy.yml b/.gitea/workflows/workers-deploy.yml
new file mode 100644
index 0000000..b9c0eae
--- /dev/null
+++ b/.gitea/workflows/workers-deploy.yml
@@ -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
diff --git a/.github/MEMORY.md b/.github/MEMORY.md
index 7e2b59c..544bacf 100644
--- a/.github/MEMORY.md
+++ b/.github/MEMORY.md
@@ -109,7 +109,7 @@
- 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 `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.
- 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).
diff --git a/.github/instructions/BACKLOG.md b/.github/instructions/BACKLOG.md
index 32dfd93..a28b567 100644
--- a/.github/instructions/BACKLOG.md
+++ b/.github/instructions/BACKLOG.md
@@ -6,8 +6,8 @@
### Phase 5: complete manual Cloudflare and external-service setup
-- [ ] Apply Cloudflare Pages, Tiered Cache, WAF, rate limiting, and HTTPS/HSTS settings in the dashboard
-- [ ] Cloudflare Pages: link custom domain and cut over frontend hosting
+- [ ] Apply Cloudflare Workers, Tiered Cache, WAF, rate limiting, and HTTPS/HSTS settings in the dashboard
+- [ ] Cloudflare Workers: link custom domain and cut over frontend hosting
- [ ] 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
- [ ] Rate limiting: `/api/admin/login` + `/api/admin/*` → 10 req/10s per IP, Challenge action
@@ -58,10 +58,10 @@
## Cloudflare Configuration
-### DNS & Pages
+### DNS & Workers
-- [ ] Cloudflare Pages: link custom domain (manual)
-- [ ] Create Cloudflare Pages project and cut over frontend hosting (manual)
+- [ ] Cloudflare Workers: link custom domain (manual)
+- [ ] Create Cloudflare Worker and cut over frontend hosting (manual)
### Security & TLS
diff --git a/README.md b/README.md
index a80c84c..c94c49f 100644
--- a/README.md
+++ b/README.md
@@ -187,7 +187,7 @@ frontend/
Everything needed to deploy and configure the stack (Docker, Nginx, Cloudflare rules, future Terraform).
- 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
- [Dockerfile](infrastructure/nginx/Dockerfile) — builds Nginx with custom config
- [nginx.conf](infrastructure/nginx/nginx.conf) — reverse proxy, static SPA, HLS delivery
diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md
index 30e5b74..f95abfd 100644
--- a/docs/DEPLOYMENT.md
+++ b/docs/DEPLOYMENT.md
@@ -14,6 +14,7 @@
- [Target Topology](#target-topology)
- [Prerequisites](#prerequisites)
- [Environment File](#environment-file)
+ - [Gitea Secrets](#gitea-secrets)
- [Pull-and-Run Deployment](#pull-and-run-deployment)
- [First-Time or Recovery Provisioning](#first-time-or-recovery-provisioning)
- [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:
-| Workflow | Trigger | Purpose |
-| ------------------ | ---------------- | ---------------------------------------------------------------------------------------- |
-| `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 |
-| `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`) |
+| Workflow | Trigger | Purpose |
+| -------------------- | ----------------- | ----------------------------------------------------------------------------------------- |
+| `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 |
+| `release.yml` | Tag push `*.*.*` | Build & push versioned Docker images (`:X.Y.Z`, `:X.Y`, `:latest`), create Gitea Release |
+| `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.
@@ -146,7 +147,7 @@ Use `.env` with at least:
- `R2_ENDPOINT` — S3-compatible endpoint URL
- `R2_REGION` — set to `auto`
-**Cloudflare (optional, for Workers/Pages):**
+**Cloudflare (optional, for Workers):**
- `CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_ACCOUNT_ID`
- `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:
-| Secret Name | Description |
-| ------------------------- | ------------------------------------ |
-| `DATABASE_URL` | PostgreSQL connection string |
-| `ADMIN_USERNAME` | Admin dashboard username |
-| `ADMIN_PASSWORD` | Admin dashboard password |
-| `R2_ACCESS_KEY_ID` | Cloudflare R2 access key |
-| `R2_SECRET_ACCESS_KEY` | Cloudflare R2 secret key |
-| `R2_BUCKET_NAME` | R2 bucket name |
-| `R2_ENDPOINT` | R2 S3-compatible endpoint URL |
-| `R2_ADMIN_ACCESS_KEY_ID` | R2 admin access key (optional) |
+| Secret Name | Description |
+| ---------------------------- | --------------------------------- |
+| `DATABASE_URL` | PostgreSQL connection string |
+| `ADMIN_USERNAME` | Admin dashboard username |
+| `ADMIN_PASSWORD` | Admin dashboard password |
+| `R2_ACCESS_KEY_ID` | Cloudflare R2 access key |
+| `R2_SECRET_ACCESS_KEY` | Cloudflare R2 secret key |
+| `R2_BUCKET_NAME` | R2 bucket name |
+| `R2_ENDPOINT` | R2 S3-compatible endpoint URL |
+| `R2_ADMIN_ACCESS_KEY_ID` | R2 admin access key (optional) |
| `R2_ADMIN_SECRET_ACCESS_KEY` | R2 admin secret key (optional) |
-| `R2_ADMIN_ENDPOINT` | R2 admin endpoint (optional) |
-| `CLOUDFLARE_API_TOKEN` | Cloudflare API token (optional) |
-| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare account ID (optional) |
-| `CI_REGISTRY_USER` | Gitea container registry username |
-| `CI_REGISTRY_TOKEN` | Gitea container registry token |
+| `R2_ADMIN_ENDPOINT` | R2 admin endpoint (optional) |
+| `CLOUDFLARE_API_TOKEN` | Cloudflare API token (optional) |
+| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare account ID (optional) |
+| `CI_REGISTRY_USER` | Gitea container registry username |
+| `CI_REGISTRY_TOKEN` | Gitea container registry token |
## Pull-and-Run Deployment
diff --git a/docs/README.md b/docs/README.md
index fcfd0eb..c870ac8 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -9,7 +9,7 @@ This folder contains the documentation for the Linear Void project. The document
## Infrastructure
- 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.
- Monitoring
- [Prometheus CURL](./infrastructure/monitoring/prometheus_curl.md) - Prometheus metrics endpoint testing with curl and jq. Doocumentation of available metrics and their meanings.
diff --git a/docs/SETUP.md b/docs/SETUP.md
index 4160fbb..9a63024 100644
--- a/docs/SETUP.md
+++ b/docs/SETUP.md
@@ -188,14 +188,14 @@ flowchart LR
## 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`.
### 1. DNS Setup
-1. Add apex record for `linearvoid.tv` pointing to Cloudflare Pages target.
-2. Add `www` CNAME to Cloudflare Pages and configure redirect to `https://linearvoid.tv`.
+1. Add apex record for `linearvoid.tv` pointing to the Cloudflare Worker.
+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.
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).
-### 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:
-1. Connect Gitea repository to Cloudflare Pages.
-2. Build command:
+1. Create a Cloudflare Worker for the static SPA (Workers & Pages → Create → Worker).
+2. Build the frontend:
```bash
cd frontend && npm run build
```
-3. Output directory:
-
- ```text
- frontend/dist
- ```
-
-4. Configure production domain to `linearvoid.tv`.
+3. Bind the build output (`frontend/dist`) as Worker static assets.
+4. Configure production domain `linearvoid.tv` on the Worker.
### 5. Tiered Cache
@@ -306,7 +301,7 @@ Create external monitors:
### 10. Verification Checklist
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.
4. Admin paths are rate-limited and WAF events are visible.
5. HTTPS and WSS both succeed with valid certificates.
diff --git a/docs/architecture/README.md b/docs/architecture/README.md
index d75ad30..9e0354b 100644
--- a/docs/architecture/README.md
+++ b/docs/architecture/README.md
@@ -164,7 +164,7 @@ flowchart TD
subgraph "Cloudflare Global Edge"
CDN[Cloudflare CDN
Cache Rules + WAF + TLS]
- Pages[Cloudflare Pages
Static SPA Hosting]
+ Workers[Cloudflare Workers
Static SPA Hosting]
R2[Cloudflare R2
HLS Segments + Source MP4s
enam location hint]
end
@@ -177,17 +177,17 @@ flowchart TD
Admins -->|HTTPS - Schedule/Playlist| Playout
Playout -->|S3-compatible Upload| R2
R2 -->|Cached HLS| CDN
- Viewers -->|HTTPS| Pages
+ Viewers -->|HTTPS| Workers
Viewers -->|HLS Requests| CDN
Viewers -->|WSS Chat| Chat
Chat --> VPS
- VPS -->|Proxy / Static| Pages
+ VPS -->|Proxy / Static| Workers
CDN -->|Origin Pull on Miss| R2
```
**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).
- **Browsers**: Native HLS (`