feat(ops): replace Cloudflare Pages with Workers for frontend hosting
This commit is contained in:
1 parent
9ac1a8b79d
commit
9e56ff7859
16 files changed
+196
-265
No files matched your search
@@ -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
|
|
||||||
@@ -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
@@ -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).
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+5
-4
@@ -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)
|
||||||
@@ -94,11 +95,11 @@ 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`)
|
||||||
@@ -158,7 +159,7 @@ 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 |
|
||||||
|
|||||||
+1
-1
@@ -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
@@ -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
@@ -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).
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
+13
-12
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
name = "linearvoid-frontend"
|
||||||
|
compatibility_date = "2025-08-01"
|
||||||
|
|
||||||
|
[assets]
|
||||||
|
directory = "./dist"
|
||||||
|
not_found_handling = "single-page-application"
|
||||||
Reference in new issue
Block a user