Introduce clubs con abbonamento, branding Active Storage e flusso registrazione società; corregge healthcheck MediaMTX (immagine distroless) e allinea dominio produzione matchlivetv.it. Co-authored-by: Cursor <cursoragent@cursor.com>
254 lines
7.1 KiB
Markdown
254 lines
7.1 KiB
Markdown
# 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-27
|
|
|
|
---
|
|
|
|
## 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 principale `www.matchlivetv.it`, redirect apex `matchlivetv.it` → `www`)
|
|
2. **Websockets:** ON (obbligatorio per `/cable`)
|
|
3. **Custom location HLS** (spettatori):
|
|
- Location: `/hls`
|
|
- Forward: `http://192.168.1.146:8888` (in NPM usare proxy pass che **rimuove** il prefisso `/hls`, così MediaMTX riceve `/live/match_{uuid}/index.m3u8`)
|
|
- Scheme: http
|
|
- URL pubblico: `https://www.matchlivetv.it/hls/live/match_{uuid}/index.m3u8`
|
|
4. SSL Let's Encrypt su NPM
|
|
5. Aggiorna `infra/.env` sul server:
|
|
- `APP_PUBLIC_URL=https://www.matchlivetv.it`
|
|
- `HLS_PUBLIC_URL=https://www.matchlivetv.it/hls`
|
|
- `CORS_ORIGINS=https://www.matchlivetv.it,https://matchlivetv.it`
|
|
- `ALLOWED_HOSTS=www.matchlivetv.it,matchlivetv.it,192.168.1.146`
|
|
- `MEDIAMTX_RTMP_URL=rtmp://www.matchlivetv.it:1935`
|
|
- `YOUTUBE_REDIRECT_URI=https://www.matchlivetv.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
|