# 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`) La porta **3000** è servita dal container **`edge`** (nginx locale), non da Rails direttamente. Durante i rilasci Rails viene mostrata una **pagina di cortesia** invece del 502 bianco di NPM. 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 bash scripts/deploy/release_production.sh ``` Oppure sync manuale: [`scripts/deploy/sync_to_server.sh`](../../scripts/deploy/sync_to_server.sh), poi sul server `bash scripts/deploy/release_production.sh --local`. --- ## 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