docs: fix filename typo and add Cloudflare Pages deploy details across docs
This commit is contained in:
1 parent
7be66a703c
commit
1019972e25
13 files changed
+393
-403
No files matched your search
+4
-115
@@ -1,116 +1,5 @@
|
||||
# DEVELOPMENT.md — Linear Void Remaining Tasks
|
||||
# DEVELOPMENT.md
|
||||
|
||||
## Test Fixtures (COMPLETE)
|
||||
|
||||
- [x] Create test media fixtures in `backend/playout/testdata/`:
|
||||
- `valid_720p30.mp4` — small (1-2s) H.264/AAC compliant file
|
||||
- `invalid_h265.mp4` — wrong codec
|
||||
- `invalid_24fps.mp4` — wrong framerate
|
||||
- Generate via `ffmpeg` commands documented in test file headers
|
||||
- Windows dev path: `C:\Users\zwits\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-9.0-full_build\bin\ffmpeg.exe`
|
||||
|
||||
- [x] Create test playlist fixtures in `backend/playout/testdata/`:
|
||||
- `valid_playlist.txt`, `empty_playlist.txt`, `comments_only.txt`, `mixed_valid_invalid.txt`
|
||||
|
||||
- [x] Document ffmpeg fixture-generation commands in `playlist_test.go` header
|
||||
|
||||
## User Frontend Tests (PARTIALLY COMPLETE)
|
||||
|
||||
- [x] Video player behavior (play, pause, seek, buffering, error handling) - HLS player startup, native/fallback, URL resolution, error recovery with retries/backoff/reconnecting overlay
|
||||
- [x] Chat functionality (send/receive messages, presence, rate limiting, moderation) - send, receive, presence join/leave, DM flow, reconnect covered
|
||||
- [x] State management and data flow
|
||||
- [x] Responsive design and layout across different screen sizes - chat-collapsed toggle, CSS grid, now-playing aria
|
||||
- [x] Accessibility testing (keyboard navigation, screen reader support) - video controls, aria-labels, aria-expanded, input labels, reconnecting overlay aria-live/role
|
||||
- [x] Integration with backend APIs (mocking API responses for testing)
|
||||
- [ ] Test coverage for all critical frontend components and interactions
|
||||
- [ ] Add code coverage reporting and enforce minimum coverage threshold in CI/CD pipeline
|
||||
- [ ] Document test cases and expected outcomes
|
||||
|
||||
## Admin Frontend Tests (PARTIALLY COMPLETE)
|
||||
|
||||
- [x] Playlist management UI (add/remove videos, reorder, normalize) - save/error handling, sortable rendering
|
||||
- [ ] User moderation tools (mute/ban/report users, view moderation logs)
|
||||
- [x] Metrics and monitoring dashboards (Prometheus/Grafana integration) - overview + analytics rendering
|
||||
- [x] State management and data flow
|
||||
- [x] Responsive design and layout across different screen sizes - tab content visibility, essential tabs
|
||||
- [x] Accessibility testing (keyboard navigation, screen reader support) - nav aria-label, active tab aria-current, nav button focusability
|
||||
- [x] Integration with backend APIs (mocking API responses for testing)
|
||||
- [ ] Test coverage for all critical admin UI components and interactions
|
||||
- [ ] Add code coverage reporting and enforce minimum coverage threshold in CI/CD pipeline
|
||||
- [ ] Document test cases and expected outcomes
|
||||
|
||||
## Cloudflare R2 Integration Test (PARTIALLY COMPLETE)
|
||||
|
||||
- [x] Upload test HLS segments and manifests to R2 bucket (SyncToR2 with mock, verified bucket/key/body)
|
||||
- [x] Verify correct cache headers are applied (immutable segments via segmentCacheControl, short-lived manifests via manifestCacheControl)
|
||||
- [ ] Test retrieval of segments and manifests from R2 via Cloudflare edge
|
||||
- [ ] Validate cache hit/miss behavior and performance metrics
|
||||
- [x] Ensure integration test can run in CI/CD pipeline with proper mocking/stubbing of R2 API (putObject mock, no network calls)
|
||||
- [x] Document test cases and expected outcomes (TestSyncToR2 + TestSyncToR2_ManifestCacheHeaders + TestSyncToR2_NoCacheHeadersForUnknownExt)
|
||||
- [x] Document required stubs or mocks (putObject variable injection pattern)
|
||||
|
||||
## Cloudflare Configuration (manual — Cloudflare dashboard)
|
||||
|
||||
> Evidence tracker: `docs/operations/cloudflare-uptimerobot-checklist.md`
|
||||
> Cloudflare Pages deploy workflow created (`.gitea/workflows/pages-deploy.yml`); setup docs written (`docs/operations/cloudflare-pages-setup.md`, `docs/operations/gitea-cloudflare.pages.md`).
|
||||
|
||||
- [ ] Cloudflare Pages: create project in dashboard, link custom domain, or deploy via Gitea Actions Direct Upload
|
||||
- [ ] Tiered Cache: enable Smart Tiered Cache, validate `Cf-Cache-Status: HIT` from upper tier
|
||||
- [ ] WAF: enable OWASP Managed Ruleset, set to Simulate → review 48h → Block, enable Bot Fight Mode
|
||||
- [ ] Rate limiting: `/api/admin/login` + `/api/admin/*` → 10 req/10s per IP, Challenge action
|
||||
- [ ] HTTPS/WSS: Always Use HTTPS, HSTS max-age=31536000 includeSubDomains preload, min TLS 1.2
|
||||
- [ ] HTTPS/WSS: enforce at edge (Cloudflare) and origin (nginx), redirect HTTP → HTTPS
|
||||
|
||||
## External Services
|
||||
|
||||
> Evidence tracker: `docs/operations/cloudflare-uptimerobot-checklist.md`
|
||||
|
||||
- [ ] UptimeRobot: add HTTP monitor for `https://linearvoid.tv/health` (5min interval)
|
||||
- [ ] UptimeRobot: add keyword monitor for `https://linearvoid.tv/hls/playlist.m3u8` (keyword: `#EXTM3U`, 5min)
|
||||
- [ ] Alert on 2 consecutive failures, notify via email + Discord/Slack webhook
|
||||
|
||||
## Production Deployment (Hetzner — not provisioned)
|
||||
|
||||
- [ ] Provision Hetzner CX22 (2 vCPU, 4 GB, 40 GB) in Ashburn VA, Ubuntu 24.04
|
||||
- [ ] Install Docker Engine + Compose plugin, open firewall: 80/443 (public), 2375 (runner IP only)
|
||||
- [ ] Configure Docker daemon for remote TLS access (`-H tcp://0.0.0.0:2376` with certs)
|
||||
- [ ] Create `/srv/linearvoid/data/{playlist,media,hls,chat,slate}` directories
|
||||
- [ ] Add `CI_PROD_*` secrets to Gitea: DOCKER_HOST, R2 keys, admin password, DB password, Grafana password
|
||||
- [ ] Trigger CD workflow with `DOCKER_HOST` pointing to Hetzner IP
|
||||
- [ ] Verify all 11 containers start healthy, check `/health`, `/hls/playlist.m3u8`, WebSocket
|
||||
- [ ] Update Cloudflare A record `linearvoid.tv` → Hetzner IP, SSL Full (strict), test HTTPS + WSS
|
||||
- [ ] Smoke test: viewer SPA loads, HLS plays, chat connects, admin login → upload → playlist → playout
|
||||
- [ ] Set up Prometheus alerting notifications + UptimeRobot on production URL
|
||||
- [ ] Run `scripts/backup-db.sh` + `scripts/verify-backups.sh` on production host
|
||||
- [ ] Document production IP, SSH access, rollback procedure, scaling thresholds
|
||||
|
||||
## Scaling & Performance (planning only)
|
||||
|
||||
- [ ] Tune manifest TTL: measure `Cf-Cache-Status` distribution, adjust to 0.5× segment duration
|
||||
- [ ] Measure TTFB: WebPageTest from 3+ US regions, target <4s warm cache, document baseline
|
||||
- [ ] Load-test WebSocket: `artillery` or Go script, ramp 10→100→500→1000 connections, measure p95 latency + errors (scaffolding complete; benchmark execution pending)
|
||||
- [ ] Benchmark admin API: `wrk`/`k6` against `/api/admin/*`, target p95 <1s, identify slow queries (scaffolding complete; benchmark execution pending)
|
||||
- [x] Load-test scaffolding: ws-chat.yml, admin-api.js, run-loadtests.ps1, summarizers, README
|
||||
- [x] Cache-hit ratio exporter (scripts/cache-hit-ratio.js + .ps1)
|
||||
- [x] Saturation alerts: chat p95 latency, Redis memory usage, cache-hit ratio
|
||||
- [ ] Track cache hit ratio: Cloudflare GraphQL → Grafana panel, alert <90%
|
||||
- [ ] Capacity triggers: Prometheus rules for cache-miss >10%/15min, CPU >80%/10min (chat p95, Redis memory, cache-hit already done)
|
||||
- [ ] Phase 2 plan: document boundaries (playout CPU, chat latency, DB state, monitoring), estimate per-service resources
|
||||
- [ ] Phase 3 plan: research second Hetzner location, design warm standby + DNS failover + DB/Redis replication
|
||||
|
||||
## Streaming Pipeline (2026-08-10 audit → 2026-08-10 implemented)
|
||||
|
||||
Gaps between STREAMING.md contract and playlist.go/main.ts implementation:
|
||||
|
||||
- [x] **Continuous R2 sync during playout (HIGH)**: fsnotify watcher uploads segments in real-time (~7s delay)
|
||||
- [x] **Globally unique segment naming (HIGH)**: `r-{ISOTIME}/seg-{seq}.m4s` via run ID + monotonic sequence
|
||||
- [x] **Atomic manifest publication (MEDIUM)**: Upload to `.tmp`, then `CopyObject` to final key
|
||||
- [x] **Manifest Cache-Control alignment (LOW)**: `public, max-age=1, s-maxage=2, must-revalidate`
|
||||
- [ ] **CORS headers on HLS responses (MEDIUM)**: Needs Cloudflare R2 CORS config (infrastructure, not code)
|
||||
- [x] **Player error recovery (MEDIUM)**: `hls.on(ERROR)` handler with 5x retries, exponential backoff (2s→32s), reconnecting overlay
|
||||
- [x] **Health endpoint detail (LOW)**: Added run_id, active_item, playlist_revision, last_segment_seq, last_r2_pub, consecutive_failures, unhealthy detection
|
||||
- [x] Source media contract + validation — implemented
|
||||
- [x] fMP4 HLS packaging (6s, 24 window) — implemented
|
||||
- [x] Slate fallback + recovery — implemented
|
||||
- [x] R2 cache headers (immutable segments, short-lived manifest) — implemented
|
||||
- [x] Cloudflare Worker immutable `.m4s` headers — implemented
|
||||
> **All task lists have been consolidated into [`.github/instructions/BACKLOG.md`](../.github/instructions/BACKLOG.md).**
|
||||
>
|
||||
> This file is kept as a reference for development setup instructions. See [`SETUP.md`](SETUP.md) for environment setup.
|
||||
@@ -87,12 +87,7 @@
|
||||
|
||||
## Companion Documentation
|
||||
|
||||
- [API.md](../API.md) - Implemented WebSocket and REST API reference.
|
||||
- [DEPLOYMENT.md](../DEPLOYMENT.md) - Production deployment and operational runbook.
|
||||
- [MONITORING.md](../MONITORING.md) - Metrics, dashboards, and monitoring troubleshooting.
|
||||
- [SECURITY.md](../SECURITY.md) - Security model, controls, and hardening baseline.
|
||||
- [SETUP.md](../SETUP.md) - Test lab setup, local development, and CI/CD workflow.
|
||||
- [STREAMING.md](../STREAMING.md) - Streaming architecture, playout, and delivery details.
|
||||
see [Documentation README](../README.md) for an overview of all documentation, including architecture, ADRs, operational procedures, and setup guides.
|
||||
|
||||
## Introduction and Goals
|
||||
|
||||
@@ -727,7 +722,7 @@ These ADRs together form the canonical decision log.
|
||||
- Self-managed US-East playout (Hetzner Ashburn) with FFmpeg + MediaMTX/SRS → HLS → Cloudflare R2 + edge caching.
|
||||
- Module structure that directly reflects the arc42 building blocks and context diagram.
|
||||
- Go preferred for performance-critical services; TypeScript for frontend; Docker for all deployment.
|
||||
- Admin interfaces for video management, moderation, analytics, and system configuration using HTMX + Tailwind.
|
||||
- Admin interfaces for video management, moderation, analytics, and system configuration using HTMX + custom CSS.
|
||||
- SQLite as primary database for v1 with PostgreSQL migration path if needed.
|
||||
- Redis for rate limiting, presence, pub/sub, session storage with defined TTL policies and fallback behavior.
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ Adopt a **self-managed US-East playout + Cloudflare R2 + edge-cached HLS** archi
|
||||
|
||||
### 3. Frontend
|
||||
|
||||
- Static SPA (HTML/CSS/JS + Tailwind + Alpine.js or vanilla) hosted on Cloudflare Pages or same VPS/Nginx.
|
||||
- Static SPA (HTML + custom CSS + vanilla JS) hosted on Cloudflare Pages or same VPS/Nginx.
|
||||
- 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.
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ We must explicitly document language/runtime choices per module and the assumpti
|
||||
|
||||
| Layer | Technology | Language | Rationale |
|
||||
| ------------------- | ------------------------------------------ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Frontend SPA** | HTML + Tailwind + Alpine.js + hls.js | TypeScript / JavaScript | Minimal bundle, excellent HLS support, fast iteration. Vite for build. |
|
||||
| **Frontend SPA** | HTML + custom CSS + vanilla JS + hls.js | TypeScript / JavaScript | Minimal bundle, excellent HLS support, fast iteration. Vite for build. |
|
||||
| **Playout Service** | FFmpeg + MediaMTX (or SRS) | Bash + Go (wrapper) | FFmpeg is the proven tool for continuous playlist HLS. Go wrapper for reliability and monitoring. |
|
||||
| **Chat Backend** | Go (preferred) or Node.js + Socket.io | Go | High-performance WebSockets, low memory, excellent Redis/PostgreSQL clients. Node.js acceptable for rapid prototyping. |
|
||||
| **Shared Code** | TypeScript definitions + Go structs | TS + Go | Clear contracts between frontend and backend. |
|
||||
|
||||
@@ -19,7 +19,7 @@ This interface must be lightweight, secure, and integrated with the playout serv
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
1. **Lightweight & Maintainable** – Prefer server-rendered or minimal JS (HTMX + Tailwind) over heavy SPA frameworks to align with ADR-003's "lightweight" principle.
|
||||
1. **Lightweight & Maintainable** – Prefer server-rendered or minimal JS (HTMX + custom CSS) over heavy SPA frameworks to align with ADR-003's "lightweight" principle.
|
||||
2. **Security** – Strict authentication, file type/size validation, and R2 upload permissions.
|
||||
3. **Usability** – Drag-and-drop playlist editor, progress indicators for normalization and upload, real-time playout status.
|
||||
4. **Integration** – Must call playout service APIs for validation and playlist updates; upload directly to R2 with metadata.
|
||||
@@ -31,7 +31,7 @@ Create a dedicated **admin web interface** in a new `frontend/admin/` directory
|
||||
|
||||
**Tech Stack (per ADR-003)**:
|
||||
|
||||
- **Frontend**: HTMX + Tailwind CSS + Alpine.js (minimal JS, server-rendered HTML). Alternative: React if complex interactions are needed later.
|
||||
- **Frontend**: HTMX + custom CSS + minimal JS (server-rendered HTML). Alternative: React if complex interactions are needed later.
|
||||
- **Backend**: Go (or Node.js) API endpoints exposed by the playout service or a dedicated `backend/admin/` service.
|
||||
- **Authentication**: JWT or session-based (admin roles only). Use Cloudflare Access or simple OAuth for production.
|
||||
- **File Handling**: Upload → validate with ffprobe (codec, fps, keyframes, duration) → normalize if needed → store in R2 with metadata.
|
||||
@@ -48,7 +48,7 @@ Create a dedicated **admin web interface** in a new `frontend/admin/` directory
|
||||
|
||||
**Folder Placement** (per ADR-002):
|
||||
|
||||
- `frontend/admin/` – Admin-specific HTML/JS components (HTMX + Tailwind).
|
||||
- `frontend/admin/` – Admin-specific HTML/JS components (HTMX + custom CSS).
|
||||
- `backend/admin/` – Dedicated API handlers for validation, playlist updates, and configuration (or extend playout/chat services).
|
||||
- Protected route in nginx with authentication (JWT or Cloudflare Access).
|
||||
- Documentation and API specs added to `docs/architecture/`.
|
||||
@@ -95,7 +95,7 @@ Create a dedicated **admin web interface** in a new `frontend/admin/` directory
|
||||
|
||||
- ADR-001: Normalization requirements, HLS playout, R2 usage.
|
||||
- ADR-002: Folder structure (admin UI belongs under frontend or a protected route).
|
||||
- ADR-003: Lightweight frontend preference (HTMX/Tailwind over heavy frameworks).
|
||||
- ADR-003: Lightweight frontend preference (HTMX/custom CSS over heavy frameworks).
|
||||
- arc42 documentation: Building Block View (admin as part of the overall system).
|
||||
|
||||
This ADR ensures all admin interface decisions are documented before code is written.
|
||||
@@ -17,7 +17,7 @@ These interfaces must be secure, auditable, and integrated with the chat backend
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
1. **Consistency** – Use the same lightweight tech stack decided in ADR-004 (HTMX + Tailwind + Alpine.js) to avoid introducing new frameworks.
|
||||
1. **Consistency** – Use the same lightweight tech stack decided in ADR-004 (HTMX + custom CSS + vanilla JS) to avoid introducing new frameworks.
|
||||
2. **Security & Auditability** – All actions must require admin authentication and be logged immutably.
|
||||
3. **Real-time Needs** – Moderation dashboard should show live chat activity (WebSocket integration).
|
||||
4. **Simplicity** – Analytics should use existing Prometheus/Grafana data where possible rather than building a duplicate system.
|
||||
@@ -29,7 +29,7 @@ Extend the admin interface started in ADR-004 with additional protected sections
|
||||
|
||||
**Tech Stack (consistent with ADR-003 and ADR-004)**:
|
||||
|
||||
- **Frontend**: HTMX + Tailwind + Alpine.js for all admin pages. Shared layout with sidebar navigation.
|
||||
- **Frontend**: HTMX + custom CSS + vanilla JS for all admin pages. Shared layout with sidebar navigation.
|
||||
- **Backend**: Go API endpoints (protected by JWT/session middleware) in `backend/admin/` or extended from the chat/playout services.
|
||||
- **Authentication**: Role-based (admin, moderator, viewer). Use Cloudflare Access or JWT with short expiry for production.
|
||||
- **Data Sources**:
|
||||
@@ -54,7 +54,7 @@ Extend the admin interface started in ADR-004 with additional protected sections
|
||||
|
||||
## Rationale
|
||||
|
||||
- Reusing HTMX/Tailwind from ADR-004 keeps the admin interface lightweight and consistent.
|
||||
- Reusing HTMX/custom CSS from ADR-004 keeps the admin interface lightweight and consistent.
|
||||
- Centralizing admin functionality under `/admin/` reduces maintenance overhead.
|
||||
- Leveraging Prometheus/Grafana for analytics avoids duplicating metrics collection.
|
||||
- Immutable audit logging supports future compliance and moderation review (key requirement for a public chat system).
|
||||
@@ -94,7 +94,7 @@ Extend the admin interface started in ADR-004 with additional protected sections
|
||||
|
||||
- ADR-001: Chat features, moderation requirements, monitoring needs.
|
||||
- ADR-002: Folder structure – admin UI belongs under `frontend/admin/`.
|
||||
- ADR-003: Lightweight frontend (HTMX/Tailwind) and Go backend preference.
|
||||
- ADR-003: Lightweight frontend (HTMX/custom CSS) and Go backend preference.
|
||||
- ADR-004: Video admin interface (shared layout and authentication approach).
|
||||
- arc42 documentation: Cross-cutting Concepts (security, monitoring) and Building Block View (admin as extension of the system).
|
||||
|
||||
|
||||
@@ -20,6 +20,5 @@ $env:CLOUDFLARE_ACCOUNT_ID = (Get-Content .env | Select-String 'CLOUDFLARE_ACCOU
|
||||
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"
|
||||
|
||||
# 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"}'
|
||||
|
||||
# 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"}'
|
||||
```
|
||||
@@ -11,14 +11,14 @@ Both are managed under **Workers & Pages** in the Cloudflare dashboard.
|
||||
|
||||
## Dashboard URLs
|
||||
|
||||
| Resource | URL |
|
||||
| ------------------------ | ---------------------------------------------------------------------------------------------- |
|
||||
| Workers & Pages overview | `https://dash.cloudflare.com/{accountId}/workers-and-pages` |
|
||||
| Worker: linearvoid | `https://dash.cloudflare.com/{accountId}/workers/services/view/linearvoid/production/settings` |
|
||||
| Pages project | `https://dash.cloudflare.com/{accountId}/pages/view/linearvoid` |
|
||||
| API tokens | <https://dash.cloudflare.com/profile/api-tokens> |
|
||||
| Resource | URL |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| Workers & Pages overview | `https://dash.cloudflare.com/deb11491c54ec53a24772e98ac6d8128/workers-and-pages` |
|
||||
| Worker: linearvoid | `https://dash.cloudflare.com/deb11491c54ec53a24772e98ac6d8128/workers/services/view/linearvoid/production/settings` |
|
||||
| Pages project | `https://dash.cloudflare.com/deb11491c54ec53a24772e98ac6d8128/pages/view/linearvoid` |
|
||||
| API tokens | <https://dash.cloudflare.com/profile/api-tokens> |
|
||||
|
||||
## 1) Worker: `linearvoid` (edge cache headers)
|
||||
## Worker: `linearvoid` (edge cache headers)
|
||||
|
||||
The Worker at `infrastructure/cloudflare/worker.js` runs on the `linearvoid` Workers service. It intercepts requests and adds immutable `Cache-Control` headers to `.m4s` segment files.
|
||||
|
||||
@@ -30,31 +30,34 @@ npx wrangler deploy infrastructure/cloudflare/worker.js
|
||||
|
||||
Or paste into the Workers dashboard at the URL above (Quick Edit).
|
||||
|
||||
## 2) Pages: Static frontend
|
||||
## Pages: Static frontend
|
||||
|
||||
Two approaches — pick one:
|
||||
|
||||
### Option A: Git integration (recommended)
|
||||
|
||||
Connect the Gitea repository directly in Cloudflare Pages:
|
||||
|
||||
1. Pages → Create a project → Connect to Git
|
||||
2. Link `git.allucanget.biz/zwitschi/linearvoid`
|
||||
3. Build command: `cd frontend && npm run build`
|
||||
4. Output directory: `frontend/dist`
|
||||
5. Add custom domain: `linearvoid.tv`
|
||||
|
||||
Cloudflare handles builds and deploys automatically on push.
|
||||
|
||||
> **Naming:** If `linearvoid` is already used by the Worker service, name the Pages project `linearvoid-site` (or similar). Workers and Pages share a namespace.
|
||||
|
||||
### Option B: Direct Upload via Gitea Actions
|
||||
### Direct Upload via Gitea Actions
|
||||
|
||||
Use `.gitea/workflows/pages-deploy.yml` to build in Gitea Actions and upload via the Cloudflare API.
|
||||
|
||||
See [gitea-cloudflare-pages.md](./gitea-cloudflare-pages.md) for the workflow.
|
||||
|
||||
## 3) Custom domains
|
||||
### Variables and secrets
|
||||
|
||||
On Cloudflare pages, set the following environment variables (needed to run the frontend)
|
||||
|
||||
| Type | Name | Value |
|
||||
| --------- | -------------------------------------- | ----------------------------------------------- |
|
||||
| Secret | ADMIN_PASSWORD | `not-a-real-admin-password` |
|
||||
| Plaintext | ADMIN_SESSION_TTL_HOURS | `12` |
|
||||
| Plaintext | ADMIN_USERNAME | `boogers` |
|
||||
| Plaintext | HLS_SEGMENT_DURATION | `6` |
|
||||
| Plaintext | HLS_WINDOW_SIZE | `24` |
|
||||
| Plaintext | PLAYLIST_PATH | `/app/playlist/playlist.txt` |
|
||||
| Plaintext | VITE_PUBLIC_ADMIN_DEFAULT_MODERATOR | `boogers` |
|
||||
| Plaintext | VITE_PUBLIC_ADMIN_DEFAULT_ROOM | `main` |
|
||||
| Plaintext | VITE_PUBLIC_ADMIN_MODERATION_LOG_LIMIT | `50` |
|
||||
| Plaintext | VITE_PUBLIC_APP_URL | `https://linearvoid.tv` |
|
||||
| Plaintext | VITE_PUBLIC_HLS_URL | `https://media.linearvoid.tv/hls/playlist.m3u8` |
|
||||
| Plaintext | VITE_PUBLIC_WS_URL | `wss://linearvoid.tv/ws` |
|
||||
|
||||
## Custom domains
|
||||
|
||||
- `linearvoid.tv` → Pages project
|
||||
- `www.linearvoid.tv` → redirect to `linearvoid.tv`
|
||||
|
||||
@@ -6,7 +6,12 @@ Deploy the Linear Void frontend (Vite + TypeScript SPA) from Gitea to Cloudflare
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Cloudflare Pages project** created (Dashboard → Workers & Pages → Create → Pages), see [Cloudflare Pages Setup](./cloudflare-pages-setup.md).
|
||||
> **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>
|
||||
@@ -14,6 +19,8 @@ Deploy the Linear Void frontend (Vite + TypeScript SPA) from Gitea to Cloudflare
|
||||
- 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 |
|
||||
@@ -46,7 +53,7 @@ jobs:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd frontend && npm ci
|
||||
@@ -64,24 +71,50 @@ jobs:
|
||||
gitHubToken: ${{ secrets.GITEA_TOKEN }}
|
||||
```
|
||||
|
||||
If the `cloudflare/pages-action@v1` action is unavailable from Gitea, use the direct API approach:
|
||||
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: Create deployment artifact
|
||||
run: tar -czf site.tgz -C frontend/dist .
|
||||
- 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}" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary @site.tgz
|
||||
-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 |
|
||||
@@ -102,8 +135,10 @@ If the `cloudflare/pages-action@v1` action is unavailable from Gitea, use the di
|
||||
- Multi-page SPA (`vite.config.ts`) produces three entry points — Cloudflare Pages serves them correctly.
|
||||
- For SPA fallback routing, add a `frontend/public/_redirects` file with `/* /index.html 200` before building.
|
||||
- If deployments fail with 403, verify the API token has `Account → Cloudflare Pages → Edit` permission.
|
||||
- If you get error **8000007** ("Project not found"), the Pages project hasn't been created yet. Create it via the Cloudflare dashboard (Workers & Pages → Create → Pages → Direct Upload) first, then re-run.
|
||||
- If `linearvoid` is already taken by a Worker, use a different project name like `linearvoid-site` and update the `projectName` and `PROJECT_NAME` values in the workflow.
|
||||
|
||||
## Related resources
|
||||
|
||||
- Cloudflare Workers dashboard: `https://dash.cloudflare.com/{accountId}/workers/services/view/linearvoid/production/settings` — for managing the `infrastructure/cloudflare/worker.js` deployment
|
||||
- Cloudflare Pages project: `https://dash.cloudflare.com/{accountId}/pages/view/linearvoid` — deployment history and custom domain settings
|
||||
- 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
|
||||
@@ -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/{accountId}/workers/services/view/linearvoid/production/settings` — for managing the `infrastructure/cloudflare/worker.js` deployment
|
||||
- Cloudflare Pages project: `https://dash.cloudflare.com/{accountId}/pages/view/linearvoid` — deployment history and custom domain settings
|
||||
Reference in new issue
Block a user