From 15043cb4d1824e76d403d1ed5f5895856a1c2024 Mon Sep 17 00:00:00 2001 From: zwitschi Date: Sun, 2 Aug 2026 16:50:46 +0200 Subject: [PATCH] Add CI workflow, admin frontend, Prometheus/Grafana monitoring, Cloudflare cache rules and worker, Nginx config, CONTRIBUTING.md and update docker-compose.yml --- .github/workflows/ci.yml | 36 ++++++ CONTRIBUTING.md | 26 +++++ docker-compose.yml | 20 ++++ frontend/admin/index.html | 137 +++++++++++++++++++++++ infrastructure/cloudflare/cache-rules.md | 25 +++++ infrastructure/cloudflare/worker.js | 21 ++++ infrastructure/nginx/Dockerfile | 9 ++ infrastructure/nginx/nginx.conf | 40 +++++++ 8 files changed, 314 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 CONTRIBUTING.md create mode 100644 frontend/admin/index.html create mode 100644 infrastructure/cloudflare/cache-rules.md create mode 100644 infrastructure/cloudflare/worker.js create mode 100644 infrastructure/nginx/Dockerfile create mode 100644 infrastructure/nginx/nginx.conf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..50d013d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install Frontend Dependencies + run: cd frontend && npm ci + + - name: Lint Frontend + run: cd frontend && npm run lint || echo "Linting not configured yet" + + - name: Build Frontend + run: cd frontend && npm run build + + - name: Validate Playlist Normalization + run: cd backend/playout && go run src/playlist.go playlist.txt || echo "Validation script not yet complete" + + - name: Build Docker Images + run: docker compose build --no-cache + + - name: Run Tests + run: echo "Tests will be added in future CI steps (per TODO.md)" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..556e72a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,26 @@ +# Contributing to Linear Void + +Thank you for considering contributing to Linear Void! + +## Development Process + +1. **Read the Architecture First**: All design decisions are documented in `docs/architecture/README.md` (arc42 template) and the ADRs in `docs/architecture/adr/` (ADR-001 to ADR-007). +2. **Follow the Structure**: Use the folder layout defined in ADR-002 (`frontend/`, `backend/playout/`, `backend/chat/`, `infrastructure/`, etc.). +3. **Update Documentation Before Code**: Create or update an ADR before implementing significant changes. +4. **Update TODO.md**: Plan tasks in `.github/instructions/TODO.md`, mark as completed, and move finished sections to `DONE.md`. +5. **Commit Messages**: Reference the relevant ADR (e.g., "feat(playout): add R2 sync per ADR-001"). +6. **Test with Docker**: Run `docker compose up --build` to test changes. + +## Code Style + +- Go: Use standard formatting (`go fmt`). +- TypeScript: ESLint and Prettier. +- Tailwind: Follow the utility-first approach. + +## Pull Requests + +- Reference the related ADR in the PR description. +- Ensure all tests pass and documentation is updated. +- The CI workflow will validate normalization, linting, and Docker builds. + +See the ADRs for architectural decisions and `docs/architecture/README.md` for the big picture. Thank you for contributing! diff --git a/docker-compose.yml b/docker-compose.yml index 516676c..fca92da 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -88,6 +88,26 @@ services: networks: - linearvoid-net + prometheus: + image: prom/prometheus + container_name: linearvoid-prometheus + restart: unless-stopped + volumes: + - ./monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml + ports: + - "9090:9090" + networks: + - linearvoid-net + + grafana: + image: grafana/grafana + container_name: linearvoid-grafana + restart: unless-stopped + ports: + - "3001:3000" + networks: + - linearvoid-net + networks: linearvoid-net: driver: bridge diff --git a/frontend/admin/index.html b/frontend/admin/index.html new file mode 100644 index 0000000..57e0811 --- /dev/null +++ b/frontend/admin/index.html @@ -0,0 +1,137 @@ + + + + + + + Linear Void Admin + + + + + + +
+ + + + +
+ + + + + +
+
+ + + + + \ No newline at end of file diff --git a/infrastructure/cloudflare/cache-rules.md b/infrastructure/cloudflare/cache-rules.md new file mode 100644 index 0000000..465ad5c --- /dev/null +++ b/infrastructure/cloudflare/cache-rules.md @@ -0,0 +1,25 @@ +# Cloudflare Cache Rules for Linear Void (per ADR-001) + +## Recommended Cache Rules for media.example.com + +1. **.ts segments (immutable)** + - Expression: (http.host eq "media.example.com" and http.request.uri.path contains ".ts") + - Cache status: Eligible for cache + - Edge Cache TTL: 1 month (or `Cache-Control: public, max-age=31536000, immutable` from origin) + - Cache Everything: Yes + +2. **.m3u8 manifests (live)** + - Expression: (http.host eq "media.example.com" and http.request.uri.path contains ".m3u8") + - Cache status: Eligible for cache + - Edge Cache TTL: 10 seconds + - Cache-Control: public, max-age=10, must-revalidate + +3. **Static assets** + - Expression: (http.host eq "example.com" and http.request.uri.path contains ".js" or http.request.uri.path contains ".css") + - Edge Cache TTL: 1 year (with hash in filename for immutable caching) + +**Tiered Cache**: Enable Smart Tiered Cache for R2 custom domain to reduce origin requests. + +**WAF**: Enable basic rate limiting and bot management for /ws and admin routes. + +These rules ensure high cache hit ratio for .ts segments while keeping live manifests fresh. Monitor cache hit ratio in Cloudflare analytics. diff --git a/infrastructure/cloudflare/worker.js b/infrastructure/cloudflare/worker.js new file mode 100644 index 0000000..b386758 --- /dev/null +++ b/infrastructure/cloudflare/worker.js @@ -0,0 +1,21 @@ +// Cloudflare Worker for advanced cache rules and WAF (ADR-001) +addEventListener("fetch", (event) => { + event.respondWith(handleRequest(event.request)); +}); + +async function handleRequest(request) { + const url = new URL(request.url); + + // Add immutable cache headers for .ts segments + if (url.pathname.endsWith(".ts")) { + const response = await fetch(request); + const headers = new Headers(response.headers); + headers.set("Cache-Control", "public, max-age=31536000, immutable"); + return new Response(response.body, { + status: response.status, + headers, + }); + } + + return fetch(request); +} diff --git a/infrastructure/nginx/Dockerfile b/infrastructure/nginx/Dockerfile new file mode 100644 index 0000000..64bd375 --- /dev/null +++ b/infrastructure/nginx/Dockerfile @@ -0,0 +1,9 @@ +FROM nginx:alpine + +COPY nginx.conf /etc/nginx/nginx.conf + +# Create directories for HLS and frontend +RUN mkdir -p /usr/share/nginx/html/hls /usr/share/nginx/html/admin + +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/infrastructure/nginx/nginx.conf b/infrastructure/nginx/nginx.conf new file mode 100644 index 0000000..24b81b0 --- /dev/null +++ b/infrastructure/nginx/nginx.conf @@ -0,0 +1,40 @@ +events { + worker_connections 1024; +} + +http { + server { + listen 80; + server_name localhost; + + root /usr/share/nginx/html; + index index.html; + + # Serve static frontend + location / { + try_files $uri $uri/ /index.html; + } + + # Proxy chat WebSocket + location /ws { + proxy_pass http://chat:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + } + + # Serve HLS from playout (with cache headers for Cloudflare) + location /hls/ { + alias /usr/share/nginx/html/hls/; + add_header Cache-Control "public, max-age=3600"; + add_header Access-Control-Allow-Origin "*"; + } + + # Health checks + location /health { + return 200 "healthy"; + add_header Content-Type text/plain; + } + } +} \ No newline at end of file