Remove the module "xxx" { source = "./module" } indirection layer
from all 66 service stacks. Resources are now defined directly in
each stack's main.tf instead of through a wrapper module.
- Merge module/main.tf contents into stack main.tf
- Apply variable replacements (var.tier -> local.tiers.X, renamed vars)
- Fix shared module paths (one fewer ../ at each level)
- Move extra files/dirs (factory/, chart_values, subdirs) to stack root
- Update state files to strip module.<name>. prefix
- Update CLAUDE.md to reflect flat structure
Verified: terragrunt plan shows 0 add, 0 destroy across all stacks.
4.1 KiB
4.1 KiB
F1 Stream
What This Is
A self-hosted web app that aggregates live Formula 1 streaming links from Reddit and user submissions, presenting them in a clean UI with embedded iframes. It scrapes r/motorsportsstreams2, allows users to submit their own stream URLs, and provides admin controls for content moderation. Built in Go with vanilla JS frontend, deployed on Kubernetes.
Core Value
Users can find working F1 streams quickly — the app automatically discovers, validates, and surfaces healthy streams while removing dead ones.
Requirements
Validated
- ✓ Reddit scraper polls r/motorsportsstreams2 for F1-related posts — existing
- ✓ URL extraction from post bodies and comment trees — existing
- ✓ F1 keyword filtering with negative keyword exclusion — existing
- ✓ Domain filtering (reddit, imgur, youtube, twitter excluded) — existing
- ✓ Deduplication via normalized URLs — existing
- ✓ User stream submission (anonymous + authenticated) — existing
- ✓ WebAuthn passwordless authentication — existing
- ✓ Admin approval workflow for user-submitted streams — existing
- ✓ HTTP proxy with rate limiting, private IP blocking, CSP stripping — existing
- ✓ Static frontend with iframe-based stream viewing — existing
- ✓ Default seed streams on first run — existing
- ✓ Stale link cleanup (24h) — existing
- ✓ Client-side health sort (reorder by reachability) — existing
Active
- Scraper validates extracted URLs look like actual streams (video/player content), not random links
- Server-side health checker runs every 5 minutes against all known streams
- Health check: HTTP reachability check first, then proxy-fetch to detect video/player markers
- Configurable health check timeout
- Streams marked unhealthy after 5 consecutive check failures get hidden from public page
- Unhealthy streams retried on each check cycle — restored if they recover
- Scraped streams that pass health checks auto-published to main streams page
- Dead streams dynamically removed from the page without manual intervention
- Health status persisted (failure count, last check time, healthy/unhealthy state)
Out of Scope
- Database migration (SQLite/PostgreSQL) — file-based storage is fine for this scope
- Multiple subreddit sources — stick with r/motorsportsstreams2 for now
- Real-time WebSocket push of stream status — polling is sufficient
- Mobile app — web-only
- OAuth/social login — WebAuthn is sufficient
Context
- The app runs on a personal Kubernetes cluster, deployed via Terraform
- Single-user / small-group usage — performance at scale is not a concern
- The existing client-side
sortStreamsByHealthdoes a basicno-corsfetch but can't inspect content; server-side checks via the proxy can do deeper validation - Reddit's public JSON API requires no auth but rate-limits aggressively; the scraper already handles 429s with backoff
- Stream sites frequently go down, change URLs, or get taken down — health checking is essential for a good UX
Constraints
- Tech stack: Go backend, vanilla JS frontend — no new frameworks or dependencies unless strictly necessary
- Storage: File-based JSON — no database
- Deployment: Docker container on Kubernetes, single replica
- Reddit API: Public JSON endpoints, must respect rate limits (1 req/sec delay already in place)
Key Decisions
| Decision | Rationale | Outcome |
|---|---|---|
| Server-side health checks over client-side only | Client can't inspect response content (CORS); server proxy can detect video markers | — Pending |
| 5 consecutive failures before hiding | Avoids flapping — streams that are temporarily down aren't immediately removed | — Pending |
| Auto-publish scraped streams that pass health | Reduces manual admin work; the health check is the quality gate | — Pending |
| Health check every 5 minutes | Balances freshness vs. load — streams don't change status that frequently | — Pending |
Last updated: 2026-02-17 after initialization