feat: runtime config, Matomo analytics, chat accounts, DM history/search, and Prometheus retention
This commit is contained in:
1 parent
06ab5b4655
commit
121fdbacdc
27 files changed
+1841
-161
No files matched your search
+94
-38
@@ -39,7 +39,9 @@
|
||||
- [Video Normalization \& Playout](#video-normalization--playout)
|
||||
- [Required Source Profile (Linear Void v1)](#required-source-profile-linear-void-v1)
|
||||
- [Reference Normalization Command](#reference-normalization-command)
|
||||
- [Verification Gate (ffprobe)](#verification-gate-ffprobe)
|
||||
- [Automatic Normalization \& Verification (ffprobe)](#automatic-normalization--verification-ffprobe)
|
||||
- [Runtime Configuration Store](#runtime-configuration-store)
|
||||
- [Analytics (Matomo)](#analytics-matomo)
|
||||
- [Monitoring \& Resilience](#monitoring--resilience)
|
||||
- [Prometheus Baseline](#prometheus-baseline)
|
||||
- [Grafana Dashboards](#grafana-dashboards)
|
||||
@@ -48,6 +50,7 @@
|
||||
- [Chat WebSocket API](#chat-websocket-api)
|
||||
- [Playout and Delivery REST API](#playout-and-delivery-rest-api)
|
||||
- [Admin REST API](#admin-rest-api)
|
||||
- [Viewer REST API](#viewer-rest-api)
|
||||
- [Cloudflare Configuration Reference](#cloudflare-configuration-reference)
|
||||
- [DNS and Zone Routing](#dns-and-zone-routing)
|
||||
- [Cache Rules Baseline](#cache-rules-baseline)
|
||||
@@ -58,6 +61,7 @@
|
||||
- [Transport and Edge Protection](#transport-and-edge-protection)
|
||||
- [Reverse Proxy Enforcement (nginx)](#reverse-proxy-enforcement-nginx)
|
||||
- [Admin Authentication and Session Security](#admin-authentication-and-session-security)
|
||||
- [Viewer User Accounts](#viewer-user-accounts)
|
||||
- [Rate Limiting and Abuse Controls](#rate-limiting-and-abuse-controls)
|
||||
- [CORS and Cross-Origin Behavior](#cors-and-cross-origin-behavior)
|
||||
- [Moderation and Privacy](#moderation-and-privacy)
|
||||
@@ -305,7 +309,10 @@ graph TB
|
||||
- **Input**: `data/playlist/playlist.txt` — ordered list of MP4 file paths
|
||||
- **Output**: HLS segments (`.m4s` and `.ts`) + live manifest (`.m3u8`) written to `data/hls/`, mirrored to R2
|
||||
- **Resilience**: Restart counter (>5/60s → activate slate), Prometheus metrics on port 8081
|
||||
- **Validation**: ffprobe gate on all ingested files (codec, fps, resolution, keyframe interval)
|
||||
- **Validation**: ffprobe gate on all ingested files (codec, fps, resolution, keyframe interval); `-validate` mode checks the playlist without running the loop
|
||||
- **Seamless looping**: Only resets HLS output when the playlist fingerprint (SHA-256) changes, on crash/slate recovery, or first run; otherwise continues segment numbering via `-start_number`
|
||||
- **R2 retention**: `cleanupOldSegments` deletes `hls/r-*/*` objects older than `R2_SEGMENT_RETENTION_HOURS` (default 24h) hourly + at startup
|
||||
- **Shared config**: Reads the runtime `config.json` (`loadSharedHLSConfig`) as an env fallback for HLS segment duration/window
|
||||
|
||||
#### Chat Service (`backend/chat/src/main.go`)
|
||||
|
||||
@@ -334,43 +341,50 @@ Module-level breakdown per service.
|
||||
|
||||
#### Chat Service Modules
|
||||
|
||||
| Module | File/Lines | Responsibility |
|
||||
| ---------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| **Connection Manager** | `main.go` (hub, clients map) | Track active WebSocket connections per room, fan-out messages |
|
||||
| **Message Parser** | `main.go` (readPump, handleMessage) | Parse JSON envelope `{type, room?, ...}`, validate fields, route to handler |
|
||||
| **Room Registry** | `main.go` (rooms map, room broadcasts) | Map room name → client set, handle join/leave/close |
|
||||
| **DM Router** | `main.go` (handleDirectMessage) | Resolve target user in clients map, deliver private message |
|
||||
| **Persistence Layer** | `main.go` (GORM models: Message, DirectMessage, ModerationLog) | Store/retrieve chat messages, DMs, moderation events |
|
||||
| **Redis Subsystem** | `main.go` (redisClient, rate limiting, pub/sub, presence) | INCR+EXPIRE rate limit, SETEX presence, PSUBSCRIBE room fan-out |
|
||||
| **Auth Handler** | `main.go` (handleAdminLogin, adminAuthMiddleware) | Session cookie creation/validation, login rate limiting |
|
||||
| **Admin API Router** | `main.go` (handleAdmin\*) | Upload, playlist CRUD, schedule overrides, moderation actions, analytics, system summary |
|
||||
| **Metrics Exporter** | `main.go` (prometheus.MustRegister) | Expose connected_clients, messages_total, dms_total, moderation_actions, redis_up |
|
||||
| Module | File/Lines | Responsibility |
|
||||
| ---------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| **Connection Manager** | `main.go` (hub, clients map) | Track active WebSocket connections per room, fan-out messages |
|
||||
| **Message Parser** | `main.go` (readPump, handleMessage) | Parse JSON envelope `{type, room?, ...}`, validate fields, route to handler |
|
||||
| **Room Registry** | `main.go` (rooms map, room broadcasts) | Map room name → client set, handle join/leave/close |
|
||||
| **DM Router** | `main.go` (handleDirectMessage) | Resolve target user in clients map, deliver private message |
|
||||
| **Persistence Layer** | `main.go` (GORM models: Message, DirectMessage, ModerationLog) | Store/retrieve chat messages, DMs, moderation events |
|
||||
| **Redis Subsystem** | `main.go` (redisClient, rate limiting, pub/sub, presence) | INCR+EXPIRE rate limit, SETEX presence, PSUBSCRIBE room fan-out |
|
||||
| **Auth Handler** | `main.go` (handleAdminLogin, adminAuthMiddleware) | Session cookie creation/validation, login rate limiting |
|
||||
| **User Auth Handler** | `handlers/handlers.go` (HandleUserRegister/Login/Session/Logout) | Viewer register/login/logout/session (bcrypt, user session cookie) |
|
||||
| **DM History/Search** | `handlers/handlers.go` (HandleUserDMHistory, HandleUserDMSearch) | Paginated DM history + search for authenticated users |
|
||||
| **Runtime Config** | `config/runtime.go` (GetRuntime, SetRuntime, typed setters) | Thread-safe JSON runtime config (HLS duration/window, Matomo) |
|
||||
| **Admin API Router** | `main.go` (handleAdmin\*) | Upload, playlist CRUD, schedule overrides, moderation actions, analytics, system summary |
|
||||
| **Metrics Exporter** | `main.go` (prometheus.MustRegister) | Expose connected_clients, messages_total, dms_total, moderation_actions, redis_up |
|
||||
|
||||
#### Playout Service Modules
|
||||
|
||||
| Module | File/Lines | Responsibility |
|
||||
| -------------------- | ---------------------------------------------- | ------------------------------------------------------------------ |
|
||||
| **Playlist Parser** | `playlist.go` (parsePlaylist) | Read `playlist.txt`, skip comments/blank lines, resolve file paths |
|
||||
| **FFmpeg Manager** | `playlist.go` (runPlayout, restart logic) | Spawn/monitor FFmpeg concat process, restart on crash |
|
||||
| **Slate Controller** | `playlist.go` (activateSlate, deactivateSlate) | Switch output to slate loop on excessive failures |
|
||||
| **R2 Sync** | `playlist.go` (syncToR2) | Upload HLS segments + manifest to R2 bucket |
|
||||
| **Health Endpoint** | `playlist.go` (healthHandler) | `/health` → JSON with mode, segment count, uptime |
|
||||
| **Metrics Exporter** | `playlist.go` (prometheus) | Expose restarts_total, mode (live/slate) |
|
||||
| Module | File/Lines | Responsibility |
|
||||
| -------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
||||
| **Playlist Parser** | `playlist.go` (parsePlaylist) | Read `playlist.txt`, skip comments/blank lines, resolve file paths |
|
||||
| **FFmpeg Manager** | `playlist.go` (runPlayout, restart logic) | Spawn/monitor FFmpeg concat process, restart on crash |
|
||||
| **Loop Controller** | `playlist.go` (playlistFingerprint, hlsNextStartNumber) | Detect playlist changes (SHA-256), continue segment numbering on clean loops |
|
||||
| **Slate Controller** | `playlist.go` (activateSlate, deactivateSlate) | Switch output to slate loop on excessive failures |
|
||||
| **R2 Sync** | `playlist.go` (syncToR2) | Upload HLS segments + manifest to R2 bucket |
|
||||
| **R2 Cleanup** | `playlist.go` (cleanupOldSegments) | Delete stale `hls/r-*/*` segments older than the retention window |
|
||||
| **Health Endpoint** | `playlist.go` (healthHandler) | `/health` → JSON with mode, segment count, uptime |
|
||||
| **Metrics Exporter** | `playlist.go` (prometheus) | Expose restarts_total, mode (live/slate), loops_total, run_duration_seconds, r2_cleanup_objects_total |
|
||||
|
||||
#### Frontend Modules
|
||||
|
||||
| Module | File | Responsibility |
|
||||
| ----------------------- | ----------------------------------------------- | ----------------------------------------------------------------- |
|
||||
| **HLS Player** | `src/main.ts` | hls.js initialization, attach to video element, error recovery |
|
||||
| **WebSocket Client** | `src/main.ts` | Connect/disconnect, parse envelope, dispatch to UI handlers |
|
||||
| **Chat UI** | `src/main.ts` | Message list rendering, room tabs, DM panel, presence dots |
|
||||
| **Schedule Overlay** | `src/main.ts` | Poll `/api/schedule`, render current/next program |
|
||||
| **Admin Router** | `src/admin.ts` | Tab-based navigation, dynamic page loading |
|
||||
| **Upload Manager** | `src/admin.ts` | Multipart upload with chunk progress, ffprobe validation feedback |
|
||||
| **Playlist Editor** | `src/admin.ts` | Drag-reorder, add/remove items, save to `/api/admin/playlist` |
|
||||
| **Moderation Panel** | `src/admin.ts` + `public/admin/moderation.html` | Inline mute/ban, action log viewer |
|
||||
| **Analytics Dashboard** | `src/admin.ts` + `public/admin/analytics.html` | Message counts, top rooms, moderation stats |
|
||||
| **System Config** | `src/admin.ts` + `public/admin/system.html` | Service health, config metadata, restart buttons |
|
||||
| Module | File | Responsibility |
|
||||
| ----------------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| **HLS Player** | `src/main.ts` | hls.js initialization, attach to video element, error recovery |
|
||||
| **WebSocket Client** | `src/main.ts` | Connect/disconnect, parse envelope, dispatch to UI handlers |
|
||||
| **Chat UI** | `src/main.ts` | Message list rendering, room tabs, DM panel, presence dots, typing indicator, user-list toggle |
|
||||
| **DM Search** | `src/main.ts` | Search DM history (`/api/dm/search`) and render results |
|
||||
| **Auth UI** | `src/main.ts` + `index.html` | Viewer login/register/logout, session refresh, user label |
|
||||
| **Schedule Overlay** | `src/main.ts` | Poll `/api/schedule`, render current/next program |
|
||||
| **Admin Router** | `src/admin.ts` | Tab-based navigation, dynamic page loading |
|
||||
| **Upload Manager** | `src/admin.ts` | Multipart upload with chunk progress, normalization status badges |
|
||||
| **Playlist Editor** | `src/admin.ts` | Drag-reorder, add/remove items (gated to `ready` media), save to `/api/admin/playlist` |
|
||||
| **Moderation Panel** | `src/admin.ts` + `public/admin/moderation.html` | Inline mute/ban, action log viewer |
|
||||
| **Analytics Dashboard** | `src/admin.ts` + `public/admin/analytics.html` | Message counts, top rooms, moderation stats |
|
||||
| **System Config** | `src/admin.ts` + `public/admin/system.html` | Service health, runtime config form (HLS duration/window, Matomo), restart buttons |
|
||||
|
||||
## Runtime View
|
||||
|
||||
@@ -480,7 +494,7 @@ Immutable segment naming + targeted Cloudflare Cache Rules + R2 ($0 egress) = ne
|
||||
|
||||
### Video Normalization & Playout
|
||||
|
||||
All source assets must be pre-normalized before they are accepted into production playlists. This prevents decode resets, timestamp drift, and audio/video discontinuities during continuous concat playout.
|
||||
All source assets must conform to the Linear Void source profile before they are accepted into production playlists. The chat backend **auto-normalizes** non-conforming uploads on ingest (`media.NormalizeVideo`), so admins no longer need to pre-process files manually. This prevents decode resets, timestamp drift, and audio/video discontinuities during continuous concat playout.
|
||||
|
||||
#### Required Source Profile (Linear Void v1)
|
||||
|
||||
@@ -508,12 +522,27 @@ ffmpeg -i input.mov \
|
||||
output-normalized.mp4
|
||||
```
|
||||
|
||||
#### Verification Gate (ffprobe)
|
||||
#### Automatic Normalization & Verification (ffprobe)
|
||||
|
||||
- Validate codec, fps, resolution, and keyframe cadence before upload acceptance.
|
||||
- Reject non-conforming files from admin ingest with actionable error feedback.
|
||||
- `media.NormalizeVideo` runs ffmpeg with the Linear Void profile (H.264/AAC, 30fps, 2s keyframes, yuv420p, 48kHz stereo), writes `*.normalized.mp4`, and re-verifies the output with ffprobe.
|
||||
- Media records carry a status: `ready`, `needs_normalization`, `normalizing`, or `normalization_failed`; the admin UI shows a "Normalize" action with status polling.
|
||||
- `POST /api/admin/media/{id}/normalize` re-triggers normalization manually for failed/re-queued media.
|
||||
- Keep verification logic aligned with runtime playout assumptions to preserve `-c copy` efficiency where possible.
|
||||
|
||||
### Runtime Configuration Store
|
||||
|
||||
A thread-safe, JSON-persisted runtime config store lives in `backend/chat/src/config/runtime.go` and is shared with the playout service via the playlist volume (`/app/playlist/config.json`).
|
||||
|
||||
- Editable via `GET/PUT /api/admin/config` from the admin UI.
|
||||
- Fields: `hlsSegmentDuration` (2–15s, default 6), `hlsWindowSize` (3–100, default 24), `matomoUrl`, `matomoSiteId`.
|
||||
- Playout reads the same file (`loadSharedHLSConfig`) as an env fallback for HLS segment duration/window.
|
||||
- Typed setters validate values; invalid input is rejected with `400`.
|
||||
|
||||
### Analytics (Matomo)
|
||||
|
||||
- The viewer sends pageviews to Matomo driven by the public `GET /api/matomo` endpoint, which returns `matomoUrl` + `matomoSiteId` from the runtime config store.
|
||||
- Analytics is optional per deployment: leave `matomoUrl` empty to disable tracking.
|
||||
|
||||
### Monitoring & Resilience
|
||||
|
||||
Linear Void uses a two-layer monitoring model:
|
||||
@@ -533,6 +562,7 @@ Linear Void uses a two-layer monitoring model:
|
||||
- `postgres` -> `postgres-exporter:9187`
|
||||
- `node` -> `node-exporter:9100`
|
||||
- `prometheus` -> `localhost:9090`
|
||||
- TSDB retention: `--storage.tsdb.retention.time=30d` (delete metrics older than 30 days) + `--storage.tsdb.retention.size=15GB` (disk cap), set on the `prometheus` service in `docker-compose.yml` and `docker-compose.prod.yml`
|
||||
|
||||
#### Grafana Dashboards
|
||||
|
||||
@@ -545,7 +575,7 @@ Use the app dashboard for service-level behavior and the Prometheus overview das
|
||||
|
||||
Rules are implemented in `monitoring/prometheus/alerts.yml` and loaded via `rule_files` from `prometheus.yml`.
|
||||
|
||||
Current rule set (8):
|
||||
Current rule set (10):
|
||||
|
||||
- `ChatDown`
|
||||
- `PlayoutDown`
|
||||
@@ -554,6 +584,8 @@ Current rule set (8):
|
||||
- `HighChatLatency`
|
||||
- `PrometheusWALFailing`
|
||||
- `HighCPU`
|
||||
- `LowCacheHitRatio`
|
||||
- `HighRedisMemoryUsage`
|
||||
- `LowDiskSpace`
|
||||
|
||||
Route warning/critical severities to the operations channel and keep runbook links attached to each alert definition.
|
||||
@@ -606,6 +638,8 @@ Linear Void currently exposes three operational API surfaces. The canonical deta
|
||||
- `/api/admin/session`
|
||||
- Operational/admin domains:
|
||||
- overview, analytics, users, moderation, schedule, playlist, media
|
||||
- runtime config: `GET/PUT /api/admin/config` (HLS segment duration/window, Matomo)
|
||||
- media normalization: `POST /api/admin/media/{id}/normalize`
|
||||
- Media upload domain:
|
||||
- `/api/admin/media/uploads` (init/resume)
|
||||
- `/api/admin/media/uploads/{uploadId}/chunk`
|
||||
@@ -615,6 +649,19 @@ Linear Void currently exposes three operational API surfaces. The canonical deta
|
||||
- media metadata + upload orchestration
|
||||
- moderation/audit and system status management
|
||||
|
||||
#### Viewer REST API
|
||||
|
||||
- Auth/session:
|
||||
- `POST /api/auth/register`
|
||||
- `POST /api/auth/login`
|
||||
- `POST /api/auth/logout`
|
||||
- `GET /api/auth/session`
|
||||
- Private messaging:
|
||||
- `GET /api/dm/history` (paginated)
|
||||
- `GET /api/dm/search`
|
||||
- Analytics:
|
||||
- `GET /api/matomo` (public; returns Matomo URL + site ID for the viewer pageview snippet)
|
||||
|
||||
### Cloudflare Configuration Reference
|
||||
|
||||
Cloudflare is part of the production control plane, not only a CDN edge. The following configuration set is required to maintain cost, latency, and reliability targets.
|
||||
@@ -680,6 +727,12 @@ Security is split between edge controls (Cloudflare), reverse proxy controls (ng
|
||||
- Session cookie controls in backend include `HttpOnly`, `SameSite=Strict`, and `Secure` when request is HTTPS (derived from TLS or `X-Forwarded-Proto`).
|
||||
- Session expiration is enforced server-side.
|
||||
|
||||
#### Viewer User Accounts
|
||||
|
||||
- Register/login/logout via `POST /api/auth/register`, `POST /api/auth/login`, `POST /api/auth/logout`; current session via `GET /api/auth/session`.
|
||||
- Passwords are hashed with bcrypt; sessions use a `linearvoid_user_session` cookie with a 30-day default TTL (`USER_SESSION_TTL_HOURS`).
|
||||
- Viewer sign-in UI (username/password) lives in `frontend/index.html`.
|
||||
|
||||
#### Rate Limiting and Abuse Controls
|
||||
|
||||
- Chat message rate limiting uses Redis `INCR + EXPIRE` windowing.
|
||||
@@ -701,6 +754,9 @@ Security is split between edge controls (Cloudflare), reverse proxy controls (ng
|
||||
|
||||
WebSockets + Redis (presence, pub/sub, rate limits) + DB (history, audit). Public rooms + on-demand private chats. Scales independently of video.
|
||||
|
||||
- Private DM history (`GET /api/dm/history`, paginated) and search (`GET /api/dm/search`) are backed by persisted DMs.
|
||||
- Viewer UI includes a typing indicator, user-list toggle, and DM search.
|
||||
|
||||
Full details, cache rule table, bitrate/storage calculations, cost model, example configs, ToS notes, and implementation phases are in [`adr/ADR-001.md`](adr/ADR-001.md).
|
||||
|
||||
## Architecture Decisions
|
||||
|
||||
Reference in new issue
Block a user