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:
34
docs/E2E.md
Normal file
34
docs/E2E.md
Normal 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
35
docs/PRODUCT_PREMIUM.md
Normal 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`
|
||||
253
docs/infrastructure/SERVER_DEPLOYMENT.md
Normal file
253
docs/infrastructure/SERVER_DEPLOYMENT.md
Normal 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
|
||||
Reference in New Issue
Block a user