# 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
