Files
MatchLiveTv/docs/infrastructure/SERVER_DEPLOYMENT.md
Emiliano Frascaro bba6df52c0 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>
2026-05-26 17:45:37 +02:00

254 lines
7.0 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-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