Initial commit: monorepo Match Live TV.

Rails API, app Flutter, infrastruttura Docker/MediaMTX, sito marketing e documentazione di deploy.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-26 17:45:37 +02:00
commit bba6df52c0
381 changed files with 20599 additions and 0 deletions

34
docs/E2E.md Normal file
View File

@@ -0,0 +1,34 @@
# Test end-to-end Match Live TV
## Prerequisiti
```bash
cd infra && docker compose up -d
```
## 1. API smoke test
```bash
./scripts/e2e_api.sh
```
## 2. Flusso sessione
1. Login → crea sessione su partita seed
2. MediaMTX path `match_{uuid}` con alwaysAvailable
3. Simula webhook disconnect → status `reconnecting`
4. Simula webhook connect → status `live`
## 3. Stream RTMP (device)
1. Avvia app Flutter in Camera Mode
2. `startStream` con `rtmp_ingest_url` dalla API
3. Verifica path online: `curl http://localhost:9997/v3/paths/list`
4. Airplane mode 30s → slate su YouTube
5. Ripristina rete → reconnect automatico
## 4. Regia
1. Secondo device in Controller Mode
2. Scan QR pairing
3. +1 punto → overlay aggiornato su camera via Action Cable

35
docs/PRODUCT_PREMIUM.md Normal file
View File

@@ -0,0 +1,35 @@
# Match Live TV — Piani commerciali
## Piani
| | Free | Premium Light | Premium Full |
|---|------|---------------|----------------|
| Staff trasmissione / anno | 1 | 5 | Illimitato |
| Staff regia / anno | 1 | 5 | Illimitato |
| Partite in contemporanea | 1 | 3 | 10 |
| Live Match Live TV | Sì | Sì | Sì |
| YouTube | No | Match TV Light | Canale società |
| Replay server | No | 30 giorni | 90 giorni |
| Download telefono | No | Sì | Sì |
| Prezzo | €0 | €40/anno o €5/mese | €200/anno o €20/mese |
| Embed sito società | No | No | In arrivo |
Esigenze custom: contatto `info@matchlivetv.eminux.it`.
## Sito
- `/` — Landing
- `/funzionalita` — Feature
- `/prezzi` — Prezzi (statici in pagina)
- `/live` — Dirette pubbliche
- `/signup` — Registrazione → squadra → dashboard
## Stripe
```bash
STRIPE_PREMIUM_LIGHT_PRICE_ID=price_...
STRIPE_PREMIUM_FULL_PRICE_ID=price_...
STRIPE_WEBHOOK_SECRET=whsec_...
```
Checkout: `GET /teams/:id/checkout?plan=premium_light|premium_full`

View File

@@ -0,0 +1,253 @@
# Deploy server — Match Live TV Backend
**Server:** `eminux@192.168.1.146` (Debian 13)
**Path deploy:** `~/matchlivetv` (dopo bootstrap opzionale: `/opt/matchlivetv`)
**Ultimo aggiornamento:** 2026-05-25
---
## Architettura rete
```
Internet
├──► Router WAN :443 ──► Nginx Proxy Manager (LAN) ──► http://192.168.1.146:3000 (Rails API + Action Cable)
└──► Router WAN :1935 ──► MediaMTX RTMP (telefoni, ingest video diretto)
```
Il flusso video **non passa da Rails**: telefono → RTMP :1935 → MediaMTX → relay ffmpeg → YouTube.
---
## Scelte tecniche
| Scelta | Motivazione |
|--------|-------------|
| Docker + Compose | Riproducibilità, stesso stack in dev e prod |
| `docker-compose.prod.yml` separato | Dev monta volumi sorgente; prod usa immagine buildata |
| Rails esposto su `:3000` solo LAN | NPM termina HTTPS; non esporre 3000 su WAN |
| MediaMTX `:1935` su WAN | RTMP non supporta facilmente reverse proxy TLS su NPM |
| API MediaMTX `:9997` non pubblicata | Solo rete Docker interna |
| Postgres/Redis senza porte host | Sicurezza |
| Kamal non usato in questa fase | Compose sufficiente per singolo nodo; Kamal in roadmap Gitea |
---
## Porte router (port forwarding verso 192.168.1.146)
### Aprire su Internet
| Porta | Protocollo | Servizio |
|-------|------------|----------|
| **1935** | TCP | RTMP ingest (obbligatorio per streaming da 4G) |
### Opzionale
| Porta | Protocollo | Servizio |
|-------|------------|----------|
| **8888** | TCP | HLS playback registrazioni |
### Non aprire
| Porta | Motivo |
|-------|--------|
| 443 | Gestito da NPM (altro host LAN), non da questo server |
| 3000 | API raggiungibile via NPM sulla LAN |
| 5432, 6379 | Database interni |
| 9997 | API MediaMTX |
| 8554 | RTSP non usato MVP |
---
## Nginx Proxy Manager (HTTPS)
1. **Proxy Host** → Forward Hostname: `http://192.168.1.146:3000` (dominio es. `matchlivetv.eminux.it`)
2. **Websockets:** ON (obbligatorio per `/cable`)
3. **Custom location HLS** (spettatori):
- Location: `/hls`
- Forward: `http://192.168.1.146:8888`
- Scheme: http
- Così l'HLS è `https://matchlivetv.eminux.it/hls/match_{uuid}/index.m3u8`
4. SSL Let's Encrypt su NPM
5. Aggiorna `infra/.env` sul server:
- `APP_PUBLIC_URL=https://matchlivetv.eminux.it`
- `HLS_PUBLIC_URL=https://matchlivetv.eminux.it/hls`
- `CORS_ORIGINS=https://matchlivetv.eminux.it`
- `ALLOWED_HOSTS=matchlivetv.eminux.it,192.168.1.146`
- `MEDIAMTX_RTMP_URL=rtmp://matchlivetv.eminux.it:1935`
- `YOUTUBE_REDIRECT_URI=https://matchlivetv.eminux.it/api/v1/youtube/callback` (solo tier premium)
---
## Installazione iniziale (una tantum)
### Prerequisito: sudo senza password
L'utente `eminux` deve poter eseguire sudo senza password da SSH, oppure eseguire il bootstrap da console Proxmox:
```bash
echo 'eminux ALL=(ALL) NOPASSWD: ALL' | sudo tee /etc/sudoers.d/eminux-nopasswd
sudo chmod 440 /etc/sudoers.d/eminux-nopasswd
```
### 1. Bootstrap server (Docker, UFW, /opt)
Sul server:
```bash
sudo bash ~/matchlivetv/scripts/deploy/server_bootstrap.sh
```
Installa: Docker Engine, Compose plugin, UFW (22, 1935, 8888, 3000 da LAN), crea `/opt/matchlivetv`.
**Riavviare la sessione SSH** dopo bootstrap per il gruppo `docker`.
### 2. Spostare deploy in /opt (opzionale)
```bash
sudo mv ~/matchlivetv /opt/matchlivetv
sudo chown -R eminux:eminux /opt/matchlivetv
```
### 3. Configurare secret
```bash
cd /opt/matchlivetv/infra # o ~/matchlivetv/infra
nano .env # verificare POSTGRES_PASSWORD, SECRET_KEY_BASE, MEDIAMTX_RTMP_URL, ecc.
```
### 4. Avviare stack
```bash
bash /opt/matchlivetv/scripts/deploy/post_bootstrap_deploy.sh
```
Oppure manualmente:
```bash
cd /opt/matchlivetv/infra
docker compose -f docker-compose.prod.yml --env-file .env up -d --build
```
### 5. Seed dati demo (una tantum)
```bash
docker compose -f docker-compose.prod.yml exec rails bundle exec rails db:seed
```
Credenziali demo: `coach@matchlivetv.test` / `password123`
### 6. Verifica
```bash
curl http://192.168.1.146:3000/up
curl -X POST http://192.168.1.146:3000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"coach@matchlivetv.test","password":"password123"}'
```
---
## Aggiornamento codice (pre-Gitea)
Dalla macchina di sviluppo:
```bash
# Tar + scp (rsync non installato sul server)
tar -C /path/to/MatchLiveTV/src -czf /tmp/matchlivetv-deploy.tar.gz \
--exclude='./mobile/build' --exclude='./backend/log' --exclude='./.git' .
scp /tmp/matchlivetv-deploy.tar.gz eminux@192.168.1.146:~/
ssh eminux@192.168.1.146 'tar -xzf ~/matchlivetv-deploy.tar.gz -C ~/matchlivetv'
# Sul server
cd ~/matchlivetv/infra && docker compose -f docker-compose.prod.yml up -d --build
```
Script: [`scripts/deploy/sync_to_server.sh`](../../scripts/deploy/sync_to_server.sh) (richiede rsync sul server).
---
## Operazioni quotidiane
```bash
cd /opt/matchlivetv/infra
# Stato
docker compose -f docker-compose.prod.yml ps
# Log
docker compose -f docker-compose.prod.yml logs -f rails
docker compose -f docker-compose.prod.yml logs -f mediamtx
# Restart
docker compose -f docker-compose.prod.yml restart rails sidekiq
# Stop
docker compose -f docker-compose.prod.yml down
```
### Backup volumi
- `postgres_data` — database
- `recordings` — HLS MediaMTX
```bash
docker run --rm -v infra_postgres_data:/data -v $(pwd):/backup alpine \
tar czf /backup/postgres_backup_$(date +%F).tar.gz -C /data .
```
---
## Variabili ambiente (`infra/.env`)
| Variabile | Descrizione |
|-----------|-------------|
| `POSTGRES_PASSWORD` | Password DB |
| `SECRET_KEY_BASE` | Rails secret |
| `JWT_SECRET` | Token API mobile |
| `MEDIAMTX_WEBHOOK_SECRET` | HMAC webhook |
| `MEDIAMTX_RTMP_URL` | URL pubblico RTMP per telefoni |
| `APP_PUBLIC_URL` | Base HTTPS per link watch (`/live/:id`) |
| `HLS_PUBLIC_URL` | Base URL HLS (es. `https://dominio/hls`) |
| `CORS_ORIGINS` | Origini app (dominio HTTPS) |
| `ALLOWED_HOSTS` | Host header Rails |
| `YOUTUBE_*` | OAuth YouTube (tier premium) |
Template: [`infra/.env.production.example`](../../infra/.env.production.example)
---
## Roadmap Gitea / CI (fase 2)
1. Repository Gitea con mirror monorepo
2. Pipeline su push `main`:
- `bundle exec rspec` (backend)
- `docker build` immagine `matchlivetv/backend:$CI_COMMIT_SHA`
- Push registry (Gitea packages o Docker Hub privato)
3. Deploy job SSH:
```bash
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d
```
4. Secret in Gitea: tutte le variabili `.env`
---
## Stato deploy 2026-05-25
| Step | Stato |
|------|-------|
| Codice sincronizzato in `~/matchlivetv` | Completato |
| `infra/.env` con secret generati | Completato (sul server) |
| Docker installato | **In attesa** — richiede `sudo bash server_bootstrap.sh` |
| Stack avviato | **In attesa** — dopo Docker |
---
## Informazioni ancora da definire
- IP pubblico WAN o DDNS per `MEDIAMTX_RTMP_URL` da internet
- Dominio per NPM e certificato SSL
- Credenziali YouTube OAuth produzione
- URL repository Gitea