Pubblica App Links Android e prepara la release 2.0.7.
Serve assetlinks su edge/Rails, documenta overflow Hetzner e bumpa l'AAB a 28 con simboli nativi per Play. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,16 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"relation": [
|
||||||
|
"delegate_permission/common.handle_all_urls",
|
||||||
|
"delegate_permission/common.get_login_creds"
|
||||||
|
],
|
||||||
|
"target": {
|
||||||
|
"namespace": "android_app",
|
||||||
|
"package_name": "com.matchlivetv.match_live_tv",
|
||||||
|
"sha256_cert_fingerprints": [
|
||||||
|
"C3:F0:89:51:0B:00:3A:FE:10:BD:ED:68:45:1B:4F:1D:B8:5A:63:EE:8A:DA:F7:2F:5A:D6:1D:97:C9:A3:95:47",
|
||||||
|
"09:69:25:CD:8B:EC:BA:75:9A:5E:49:32:E1:35:BD:F1:AF:1E:5A:29:93:E2:65:85:8A:41:E2:11:DA:64:97:F0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Android App Links / Digital Asset Links
|
||||||
|
|
||||||
|
Play Console verifica `https://www.matchlivetv.it/.well-known/assetlinks.json`.
|
||||||
|
|
||||||
|
## File
|
||||||
|
|
||||||
|
- Servito da **edge** (nginx): `infra/nginx-edge/well-known/assetlinks.json`
|
||||||
|
- Specchio in Rails public: `backend/public/.well-known/assetlinks.json`
|
||||||
|
|
||||||
|
Package: `com.matchlivetv.match_live_tv`
|
||||||
|
Path App Links: `https://www.matchlivetv.it/join…` (`pathPrefix="/join"`)
|
||||||
|
|
||||||
|
Nel JSON ci sono:
|
||||||
|
|
||||||
|
1. SHA-256 del **upload key** (`native/android/keystore/matchlivetv-upload.jks`)
|
||||||
|
2. SHA-256 del certificato **App signing** di Play (da Play Console → Integrità dell’app)
|
||||||
|
|
||||||
|
Relazioni richieste da Play per la condivisione credenziali:
|
||||||
|
|
||||||
|
- `delegate_permission/common.handle_all_urls`
|
||||||
|
- `delegate_permission/common.get_login_creds`
|
||||||
|
|
||||||
|
Se Play rigenera il JSON, sostituisci `infra/nginx-edge/well-known/assetlinks.json` (e lo specchio in `backend/public/.well-known/`) e ricarica edge.
|
||||||
|
## Deploy edge (senza rebuild Rails)
|
||||||
|
|
||||||
|
Sul server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/matchlivetv/infra
|
||||||
|
# dopo git pull del repo
|
||||||
|
docker compose -f docker-compose.prod.yml --env-file .env up -d edge
|
||||||
|
curl -sS -D- https://www.matchlivetv.it/.well-known/assetlinks.json | head
|
||||||
|
```
|
||||||
|
|
||||||
|
Verifica Google:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS "https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://www.matchlivetv.it&relation=delegate_permission/common.handle_all_urls"
|
||||||
|
```
|
||||||
|
|
||||||
|
Poi in Play Console → Link diretti → ripeti i controlli dominio.
|
||||||
@@ -397,6 +397,8 @@ cd infra && cp .env.example .env && docker compose up -d --build
|
|||||||
| Documento | Contenuto |
|
| Documento | Contenuto |
|
||||||
|-----------|-----------|
|
|-----------|-----------|
|
||||||
| [`infrastructure/SERVER_DEPLOYMENT.md`](infrastructure/SERVER_DEPLOYMENT.md) | Bootstrap server, NPM, cron, backup |
|
| [`infrastructure/SERVER_DEPLOYMENT.md`](infrastructure/SERVER_DEPLOYMENT.md) | Bootstrap server, NPM, cron, backup |
|
||||||
|
| [`infrastructure/HETZNER_CLOUD_RELAY_OVERFLOW.md`](infrastructure/HETZNER_CLOUD_RELAY_OVERFLOW.md) | Piano overflow relay YouTube su Hetzner Cloud (picchi) |
|
||||||
|
| [`ANDROID_APP_LINKS.md`](ANDROID_APP_LINKS.md) | Digital Asset Links / deep link Play (`assetlinks.json`) |
|
||||||
| [`LIVE_STREAMING.md`](LIVE_STREAMING.md) | MediaMTX, pausa, HLS, ruolo ffmpeg |
|
| [`LIVE_STREAMING.md`](LIVE_STREAMING.md) | MediaMTX, pausa, HLS, ruolo ffmpeg |
|
||||||
| [`REPLAY_MODULE.md`](REPLAY_MODULE.md) | Garage, retention, YouTube VOD |
|
| [`REPLAY_MODULE.md`](REPLAY_MODULE.md) | Garage, retention, YouTube VOD |
|
||||||
| [`OPS_MONITORING.md`](OPS_MONITORING.md) | ntfy, variabili ops, troubleshooting |
|
| [`OPS_MONITORING.md`](OPS_MONITORING.md) | ntfy, variabili ops, troubleshooting |
|
||||||
|
|||||||
@@ -0,0 +1,408 @@
|
|||||||
|
# Piano: overflow relay YouTube su Hetzner Cloud
|
||||||
|
|
||||||
|
**Stato:** piano / design — non implementato
|
||||||
|
**Ultimo aggiornamento:** 2026-07-29
|
||||||
|
**Contesto:** produzione attuale su host dedicato (es. Proxmox/Hetzner) con MediaMTX + Sidekiq sullo stesso box; obiettivo >10 live YouTube contemporanee senza abbandonare il middle-tier.
|
||||||
|
|
||||||
|
Documenti correlati: [`LIVE_STREAMING.md`](../LIVE_STREAMING.md), [`ARCHITECTURE.md`](../ARCHITECTURE.md), [`OPS_MONITORING.md`](../OPS_MONITORING.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Scenario
|
||||||
|
|
||||||
|
### Problema di prodotto
|
||||||
|
|
||||||
|
Match Live TV disaccoppia il telefono da YouTube:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Telefono (rete mobile instabile)
|
||||||
|
│ RTMP
|
||||||
|
▼
|
||||||
|
MediaMTX (sempre attivo, slate / alwaysAvailable)
|
||||||
|
│
|
||||||
|
├── HLS → sito
|
||||||
|
└── YoutubeRelay (ffmpeg) → RTMPS YouTube
|
||||||
|
```
|
||||||
|
|
||||||
|
Se cade solo il tratto telefono→MediaMTX, YouTube resta in onda (slate).
|
||||||
|
Se il telefono andasse diretto a YouTube (o a un ingest cloud senza hold), la live pubblica cadrebbe con la rete mobile. Inoltre YouTube non è pensato come “Go Live dall’app mobile” senza vincoli di canale: noi usiamo API + RTMPS dal server.
|
||||||
|
|
||||||
|
### Problema di capacità
|
||||||
|
|
||||||
|
Ogni diretta `platform=youtube` avvia un processo `ffmpeg` in Sidekiq (`Streams::YoutubeRelay`: video **copy**, audio **AAC**).
|
||||||
|
Il carico scala con **N processi**, non con gli spettatori HLS.
|
||||||
|
|
||||||
|
Sul box attuale (~4 CPU / 8 GB, stack completo sullo stesso host):
|
||||||
|
|
||||||
|
| Live YT contemporanee | Situazione attesa |
|
||||||
|
|-----------------------|-------------------|
|
||||||
|
| 1–4 | Comodo |
|
||||||
|
| 5–8 | Teso (CPU/RAM, latenza job Sidekiq) |
|
||||||
|
| ≥10 | Rischioso (relay instabili, watchdog, slate prolungate su YT) |
|
||||||
|
|
||||||
|
**Obiettivo:** mantenere MediaMTX (e Rails) sul dedicated; quando N supera la soglia comoda, **accendere capacità Hetzner Cloud solo per i relay**, per il tempo del picco, poi spegnerla.
|
||||||
|
|
||||||
|
### Cosa NON è questo piano
|
||||||
|
|
||||||
|
- Non sostituisce MediaMTX con Mux/AWS MediaLive.
|
||||||
|
- Non sposta l’ingest RTMP del telefono sul cloud (cold start e sticky URL sono incompatibili con “zero nodi a riposo”).
|
||||||
|
- Non è autoscaling del monolite Docker Compose intero.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Principio di progettazione
|
||||||
|
|
||||||
|
| Componente | Dove resta | Perché |
|
||||||
|
|------------|------------|--------|
|
||||||
|
| MediaMTX (RTMP ingest + slate + HLS) | **Dedicated sempre acceso** | URL stabile per l’app; continuità YouTube via slate |
|
||||||
|
| Rails / Postgres / Redis / Garage / edge | **Dedicated** (o gestiti fissi) | Stato, auth, replay; non sono il collo di bottiglia video live |
|
||||||
|
| `YoutubeRelay` (ffmpeg) | **Dedicated fino a soglia + overflow Cloud** | Unico pezzo che moltiplica CPU con N live YT |
|
||||||
|
| Job Sidekiq non-video (mail, ops, post-process) | Dedicated (coda separata) | Non devono competere con ffmpeg sui worker overflow |
|
||||||
|
|
||||||
|
Formula mentale:
|
||||||
|
|
||||||
|
```text
|
||||||
|
capacità_yt ≈ core_dedicati_relay + Σ core_vm_overflow_calde
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Architettura target (overflow)
|
||||||
|
|
||||||
|
```text
|
||||||
|
[ telefoni RTMP ]
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────┐
|
||||||
|
│ Dedicated (sempre on) │
|
||||||
|
│ MediaMTX · Rails · DB │
|
||||||
|
│ Redis · Garage · edge │
|
||||||
|
│ Sidekiq "core" │
|
||||||
|
│ (+ pochi relay locali) │
|
||||||
|
└───────────┬─────────────┘
|
||||||
|
│
|
||||||
|
intake RTMP/HLS │ (rete privata / tunnel / IP allowlist)
|
||||||
|
│
|
||||||
|
┌───────────────┼───────────────┐
|
||||||
|
▼ ▼ ▼
|
||||||
|
[relay-local] [overflow-1] [overflow-N]
|
||||||
|
Sidekiq+ffmpeg Hetzner Cloud Hetzner Cloud
|
||||||
|
YOUTUBE_RELAY=1 a ore a ore
|
||||||
|
│ │ │
|
||||||
|
└───────────────┴───────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
YouTube RTMPS
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ruolo delle VM overflow
|
||||||
|
|
||||||
|
Immagine minimale (o Compose snello):
|
||||||
|
|
||||||
|
- Sidekiq con `YOUTUBE_RELAY_WORKER=1`
|
||||||
|
- `ffmpeg` disponibile
|
||||||
|
- Connessione a **Redis** del dedicated (coda + lock `youtube_relay:owner:*`)
|
||||||
|
- Lettura intake da MediaMTX (RTMP preferito, HLS fallback come oggi)
|
||||||
|
- **Niente** Postgres scrittura diretta obbligatoria se i job relay usano già Redis + API; in pratica oggi Rails/Sidekiq legge Postgres → le VM overflow devono raggiungere anche DB **oppure** si isola una coda “relay-only” con worker che riceve payload già risolto (vedi §5.2)
|
||||||
|
|
||||||
|
Stato attuale del codice: `YoutubeRelay` gira in processo Sidekiq Rails completo (accesso a `StreamSession`, MediaMTX client, Redis PID/owner). Le VM overflow sono quindi **worker Sidekiq Rails**, non demoni ffmpeg nudi — almeno nella fase 1.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Colli di bottiglia (cosa può rompersi comunque)
|
||||||
|
|
||||||
|
Anche con overflow infinito di CPU, restano limiti. Ordine di probabilità/impatto:
|
||||||
|
|
||||||
|
### 4.1 MediaMTX sul dedicated (alto)
|
||||||
|
|
||||||
|
- N publisher RTMP + N reader (HLS siti + N ffmpeg che rileggono lo stesso path).
|
||||||
|
- Ogni relay in più è un **reader** su MediaMTX (RTMP pull o HLS).
|
||||||
|
- Sintomi: path `ready` flaky, HLS a scatti sul sito, relay che rientrano in fallback HLS, CPU MediaMTX alta anche con relay scarichi.
|
||||||
|
|
||||||
|
**Mitigazione futura (fuori da questo overflow):** secondo nodo MediaMTX o sharding path; per ora monitorare `readers`, CPU `mediamtx`, bitrate aggregato.
|
||||||
|
|
||||||
|
### 4.2 Uplink Internet del dedicated (alto)
|
||||||
|
|
||||||
|
- Telefoni → dedicated (ingresso RTMP).
|
||||||
|
- Dedicated → YouTube **se** i relay restano locali.
|
||||||
|
- Con overflow Cloud: il dedicated manda **copia dello stream** verso le VM (pull dalle VM = traffico **uscita** dal dedicated verso Hetzner Cloud), poi le VM mandano a YouTube.
|
||||||
|
|
||||||
|
Attenzione al verso:
|
||||||
|
|
||||||
|
| Dove gira il relay | Traffico tipico |
|
||||||
|
|--------------------|-----------------|
|
||||||
|
| Sul dedicated | In: telefoni. Out: N× verso YouTube |
|
||||||
|
| Su Hetzner Cloud | Out dedicated → Cloud (intake). Out Cloud → YouTube |
|
||||||
|
|
||||||
|
Se l’uplink casa/datacenter verso Internet è stretto, spostare i relay sul Cloud **sposta** il carico out YouTube fuori dal dedicated, ma **aggiunge** out dedicated→Cloud (stesso ordine di grandezza del video).
|
||||||
|
Vantaggio reale solo se:
|
||||||
|
|
||||||
|
- l’uplink del dedicated satura soprattutto per **CPU** (oggi sì), oppure
|
||||||
|
- dedicated e Cloud sono in **stessa regione Hetzner con rete privata** (traffico interno economico/veloce) e l’uscita verso YouTube esce dalla rete Hetzner Cloud (migliore peering).
|
||||||
|
|
||||||
|
**Decisione di rete obbligatoria prima di implementare** (vedi §6).
|
||||||
|
|
||||||
|
### 4.3 Redis / lock owner (medio)
|
||||||
|
|
||||||
|
Oggi:
|
||||||
|
|
||||||
|
- `youtube_relay:pid:%s` — PID locale al worker
|
||||||
|
- `youtube_relay:owner:%s` — `HOSTNAME` del worker
|
||||||
|
- `running?` considera attivo un owner remoto se TTL > 30s
|
||||||
|
|
||||||
|
Con più worker:
|
||||||
|
|
||||||
|
- Due ensure concorrenti possono avviare due ffmpeg (debounce 5s aiuta poco cross-host).
|
||||||
|
- `stop` da Rails manda job: deve raggiungere il worker **owner**, non un overflow a caso.
|
||||||
|
- Kill del PID funziona solo sulla macchina owner (`/proc`).
|
||||||
|
|
||||||
|
Serve disciplina di coda / routing (§5.3).
|
||||||
|
|
||||||
|
### 4.4 Cold start VM (medio)
|
||||||
|
|
||||||
|
Creare una CPX/CCX Hetzner: ordine di **30–90+ secondi** (API + boot + pull image + Sidekiq ready).
|
||||||
|
Se la 15ª live parte e non c’è capacità, la broadcast YouTube resta “in attesa” finché non c’è worker.
|
||||||
|
|
||||||
|
**Requisito prodotto:** overflow **a caldo** = pool minimo già acceso nei weekend / fasce note, non “da zero al fischio”.
|
||||||
|
|
||||||
|
### 4.5 YouTube / account / quote (basso–medio, esterno)
|
||||||
|
|
||||||
|
- Limiti API, token OAuth, ban temporanei, ingest RTMPS rifiutato.
|
||||||
|
- Non risolvibili con più CPU; restano nel runbook ops esistente.
|
||||||
|
|
||||||
|
### 4.6 Post-process replay (basso in live, alto a fine giornata)
|
||||||
|
|
||||||
|
`UploadFromSession` / thumbnail competono su Sidekiq e disco.
|
||||||
|
I worker overflow **non** devono prendere coda `default` di merge se sono pensati solo per relay — altrimenti spegnendo le VM a fine picco uccidete job a metà.
|
||||||
|
|
||||||
|
### 4.7 Costo e idle (operativo)
|
||||||
|
|
||||||
|
VM dimenticate accese = bolletta. Serve TTL, scale-in aggressivo, alert “overflow acceso da >Xh con 0 relay”.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Complessità di implementazione
|
||||||
|
|
||||||
|
### 5.1 Rete: come le VM leggono MediaMTX
|
||||||
|
|
||||||
|
Opzioni (sceglierne **una** in fase 0):
|
||||||
|
|
||||||
|
| Opzione | Pro | Contro |
|
||||||
|
|---------|-----|--------|
|
||||||
|
| **A. Stessa location Hetzner + private network** (dedicated Robot + Cloud nella stessa rete) | Bassa latenza, traffico interno, modello pulito | Richiede che il dedicated sia (o diventi) in ecosistema Hetzner collegabile |
|
||||||
|
| **B. WireGuard/Tailscale** dedicated ↔ Cloud | Funziona anche da Proxmox casa | Ops tunnel, MTU, failover; latenza |
|
||||||
|
| **C. Esporre RTMP/HLS intake in lettura** (IP allowlist VM overflow) | Semplice | Superficie attacco; non esporre senza auth/TLS dove possibile |
|
||||||
|
| **D. Relay locale che pusha a Cloud** | Dedicated controlla egress | Doppia hop, più pezzi |
|
||||||
|
|
||||||
|
Raccomandazione di piano: **A se il dedicated è/andrà su Hetzner; altrimenti B**. Evitare C in chiaro su WAN.
|
||||||
|
|
||||||
|
Variabili da prevedere sulle VM:
|
||||||
|
|
||||||
|
- `MEDIAMTX_INTERNAL_RTMP_URL` → URL raggiungibile dalle VM (non `rtmp://mediamtx:1935` Docker-locale)
|
||||||
|
- `MEDIAMTX_HLS_URL` → idem
|
||||||
|
- MediaMTX API (`:9997`) raggiungibile in privato per `intake_available?` / PublisherOnline, **mai** su Internet aperto
|
||||||
|
|
||||||
|
### 5.2 Accesso Postgres e segreti
|
||||||
|
|
||||||
|
Worker Sidekiq Rails oggi necessitano:
|
||||||
|
|
||||||
|
- `DATABASE_URL` (o replica read + job che non scrivono — irrealistico all’inizio)
|
||||||
|
- `REDIS_URL`
|
||||||
|
- `SECRET_KEY_BASE` / credenziali YouTube via DB
|
||||||
|
- stessi env di produzione (ridotti)
|
||||||
|
|
||||||
|
Complessità: ogni overflow è un **nodo fidato** della rete app.
|
||||||
|
Immagine Docker identica a `sidekiq` prod, env da secret manager o file scp/cloud-init, **nessuna** porta Rails pubblica sulle VM.
|
||||||
|
|
||||||
|
### 5.3 Code Sidekiq e affinità relay
|
||||||
|
|
||||||
|
Stato oggi: ensure/stop via job + processo locale con owner in Redis.
|
||||||
|
|
||||||
|
Per multi-host serve esplicitare:
|
||||||
|
|
||||||
|
1. **Coda dedicata** `youtube_relay` (solo worker con `YOUTUBE_RELAY_WORKER=1`).
|
||||||
|
2. **Cap locale**: ogni worker ha `RELAY_MAX_CONCURRENT` (es. cores−1).
|
||||||
|
3. **Scheduling**:
|
||||||
|
- Fase 1 (manuale): overflow sempre in ascolto sulla coda; Sidekiq distribuisce i job; `ensure_on_worker!` no-op se a cap (re-enqueue o lascia ad altro worker).
|
||||||
|
- Fase 2: controller di capacità che alza/abbassa N VM in base a `relay_attivi` e profondità coda.
|
||||||
|
4. **Stop**: job `YoutubeRelayStopJob` deve eseguire **solo sull’owner** (Sidekiq unique + check `owner == HOSTNAME`, altrimenti requeue con delay breve).
|
||||||
|
|
||||||
|
Nota: i PID in Redis non sono globalmente killabili — il modello owner è già abbozzato; va reso robusto cross-host.
|
||||||
|
|
||||||
|
### 5.4 Scale-out / scale-in (lifecycle VM)
|
||||||
|
|
||||||
|
```text
|
||||||
|
Metriche → Decisione → hcloud CLI/API → cloud-init → Sidekiq ready → in coda
|
||||||
|
↘ fallisce → alert ops, non spegnere dedicated
|
||||||
|
```
|
||||||
|
|
||||||
|
**Scale-out trigger (esempi):**
|
||||||
|
|
||||||
|
- `youtube_relay` depth > 0 per >60s **oppure**
|
||||||
|
- `relay_attivi_local >= RELAY_SOFT_CAP` **e** nuove sessioni YT in `connecting|live`
|
||||||
|
|
||||||
|
**Scale-in trigger:**
|
||||||
|
|
||||||
|
- `relay_attivi` sulle VM overflow = 0 per >15–30 min **e**
|
||||||
|
- fuori dalla finestra “weekend caldo” (opzionale)
|
||||||
|
|
||||||
|
**Warm pool:** sabato 8:00 → min 1–2 VM già up; domenica 23:00 → min 0.
|
||||||
|
|
||||||
|
### 5.5 Idempotenza e split-brain
|
||||||
|
|
||||||
|
Scenario da evitare: dedicated e overflow avviano entrambi ffmpeg sulla stessa `stream_key` → YouTube flappa.
|
||||||
|
|
||||||
|
Mitigazioni:
|
||||||
|
|
||||||
|
- Lock Redis con fencing token / TTL heartbeat rinnovato dal processo owner ogni N secondi
|
||||||
|
- Watchdog (`YoutubeIngestWatchdogJob`) deve rispettare owner remoto (già parzialmente così via TTL)
|
||||||
|
- Un solo writer della broadcast activate
|
||||||
|
|
||||||
|
### 5.6 Observability
|
||||||
|
|
||||||
|
Senza metriche l’overflow è cieco. Minimo:
|
||||||
|
|
||||||
|
| Metrica | Dove |
|
||||||
|
|---------|------|
|
||||||
|
| N relay ffmpeg vivi (local / per host) | Redis + `pgrep` / heartbeat |
|
||||||
|
| CPU MediaMTX, CPU Sidekiq | node exporter / docker stats |
|
||||||
|
| Profondità coda `youtube_relay` | Sidekiq API |
|
||||||
|
| Bitrate / errori ffmpeg per session | log già in `log/youtube_relay_*.log` |
|
||||||
|
| VM overflow accese, € stimati | tag Hetzner + cron report |
|
||||||
|
| Latenza intake Cloud (RTT dedicated↔VM) | check periodico |
|
||||||
|
|
||||||
|
Alert ntfy esistenti: estendere con `relay_overflow_saturated`, `mediamtx_cpu_high`, `overflow_vm_orphan`.
|
||||||
|
|
||||||
|
### 5.7 Sicurezza
|
||||||
|
|
||||||
|
- Firewall: VM → solo Redis, Postgres, MediaMTX (porte interne), YouTube egress 443
|
||||||
|
- Nessun SSH password; chiave o console Hetzner
|
||||||
|
- Stream key YouTube solo in DB/encrypted; non in user-data in chiaro se evitabile
|
||||||
|
- Image aggiornata; spegnimento = destroy, non “pausare” dischi dimenticati per mesi
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Decisioni aperte (bloccare prima del codice)
|
||||||
|
|
||||||
|
| # | Domanda | Impatto |
|
||||||
|
|---|---------|---------|
|
||||||
|
| D1 | Dedicated resta su LAN casa/Proxmox o migra/affianca Hetzner Robot? | Sceglie rete A vs B (§5.1) |
|
||||||
|
| D2 | Soft cap relay sul dedicated (es. 4? 6?) | Quando scatta overflow |
|
||||||
|
| D3 | Warm pool fisso weekend vs solo reactive | Cold start vs costo |
|
||||||
|
| D4 | Tipo VM (CPX shared vs CCX dedicated CPU) | €/live e jitter AAC |
|
||||||
|
| D5 | Stessa immagine `sidekiq` vs worker slim | Tempo boot e superficie |
|
||||||
|
| D6 | Chi orchestra le VM? (script cron su dedicated, Terraform, small Go/Ruby service) | Ops ownership |
|
||||||
|
|
||||||
|
Finché D1 non è chiusa, non stimare bandwitdh né SLA del picco.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Dimensionamento indicativo
|
||||||
|
|
||||||
|
Ipotesi: 720p, video copy, AAC mono — **~0.3–0.7 vCPU** medi per relay (picchi più alti su analyze/reconnect).
|
||||||
|
|
||||||
|
| Target live YT | Dedicated (relay) | Overflow tipico | Note |
|
||||||
|
|----------------|-------------------|-----------------|------|
|
||||||
|
| ≤6 | Tutto locale | 0 | Situazione attuale “comoda” se box dedicato ai relay |
|
||||||
|
| 10–15 | soft cap 4–6 | 1× VM 4–8 vCPU | Warm consigliato in giornata evento |
|
||||||
|
| 20–30 | soft cap 4–6 | 2–4× VM | MediaMTX e uplink diventano critici |
|
||||||
|
| 50+ | softire anche ingest | N VM + 2° MediaMTX | Fuori scope overflow-only |
|
||||||
|
|
||||||
|
Questi numeri vanno **calibati** con un load test (N sessioni fake + ffmpeg contro slate MediaMTX) prima di promettere SLA commerciali.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Piano a fasi (pronti a reagire, non a over-build)
|
||||||
|
|
||||||
|
### Fase 0 — Misura e soglie (1–2 giorni ops)
|
||||||
|
|
||||||
|
- [ ] Dashboard/manuale: N live YT, CPU `sidekiq`, CPU `mediamtx`, uplink
|
||||||
|
- [ ] Definire `RELAY_SOFT_CAP` empirico sul dedicated
|
||||||
|
- [ ] Chiudere **D1** (rete)
|
||||||
|
- [ ] Load test controllato: 8–12 relay solo slate (senza telefoni) per vedere dove rompe
|
||||||
|
|
||||||
|
**Criterio go/no-go overflow:** saturazione CPU Sidekiq/ffmpeg **prima** di MediaMTX/uplink. Se saturano prima rete o MediaMTX, overflow Cloud non basta.
|
||||||
|
|
||||||
|
### Fase 1 — Overflow manuale “a caldo” (MVP ops)
|
||||||
|
|
||||||
|
- [ ] Snapshot/immagine Sidekiq relay-ready su Hetzner Cloud
|
||||||
|
- [ ] cloud-init: env, WireGuard o private net, `docker compose up sidekiq`
|
||||||
|
- [ ] Runbook: “accendi 1 VM”, verifica `YoutubeRelay` su HOSTNAME nuovo, “spegni”
|
||||||
|
- [ ] Soft cap sul dedicated: nuovi ensure preferiscono coda se locali pieni (anche patch minima)
|
||||||
|
- [ ] Alert se VM overflow up da >3h con 0 owner keys
|
||||||
|
|
||||||
|
**Reazione a incident:** operatore umano accende/spegne; nessun autoscaler ancora.
|
||||||
|
|
||||||
|
### Fase 2 — Routing coda robusto
|
||||||
|
|
||||||
|
- [ ] Coda `youtube_relay` isolata
|
||||||
|
- [ ] Heartbeat owner Redis
|
||||||
|
- [ ] Stop/ensure sticky all’owner
|
||||||
|
- [ ] Cap per worker + requeue
|
||||||
|
|
||||||
|
### Fase 3 — Autoscaling controllato
|
||||||
|
|
||||||
|
- [ ] Job/cron: se saturazione → `hcloud server create` fino a `OVERFLOW_MAX`
|
||||||
|
- [ ] Scale-in solo se idle e fuori warm window
|
||||||
|
- [ ] Budget mensile + kill switch
|
||||||
|
|
||||||
|
### Fase 4 — Solo se necessario
|
||||||
|
|
||||||
|
- Secondo MediaMTX / sharding
|
||||||
|
- Audio copy se l’encoder telefono è già YouTube-compatibile (meno CPU)
|
||||||
|
- Dedicated più grosso come baseline (spesso più economico dell’overflow cronico ogni weekend)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Runbook di reazione (anche prima dell’autoscaler)
|
||||||
|
|
||||||
|
### Sintomo: YouTube “in programma” / niente video con molte live
|
||||||
|
|
||||||
|
1. `docker stats` / CPU Sidekiq sul dedicated
|
||||||
|
2. Contare relay: log `[YoutubeRelay] started` vs sessioni `platform=youtube` live
|
||||||
|
3. Se CPU ~100% e MediaMTX ok → **accendere overflow** (Fase 1) o abbassare nuove live YT
|
||||||
|
4. Se MediaMTX alto / path non ready → overflow **non** aiuta; ridurre reader o spostare HLS, non aggiungere pull Cloud ciechi
|
||||||
|
|
||||||
|
### Sintomo: live YT che si spezza solo sulle VM Cloud
|
||||||
|
|
||||||
|
1. RTT e packet loss dedicated↔VM
|
||||||
|
2. Verificare che intake usi RTMP interno, non HLS pubblico via Internet
|
||||||
|
3. Controllare doppio owner / due ffmpeg sulla stessa key
|
||||||
|
|
||||||
|
### Sintomo: bolletta Cloud anomala
|
||||||
|
|
||||||
|
1. Lista server con label `matchlivetv-overflow`
|
||||||
|
2. Destroy idle
|
||||||
|
3. Abbassare warm pool
|
||||||
|
|
||||||
|
### Sintomo: job replay morti dopo scale-in
|
||||||
|
|
||||||
|
1. Verificare che overflow **non** consumi coda `default` / `recordings`
|
||||||
|
2. Riprocessare dead set Sidekiq
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Criteri di successo
|
||||||
|
|
||||||
|
| Criterio | Misura |
|
||||||
|
|----------|--------|
|
||||||
|
| Picco gestito | ≥15 live YT contemporanee stabili in test |
|
||||||
|
| Continuità prodotto | Drop telefono → slate YouTube ancora attiva (invariato) |
|
||||||
|
| Costo | Overflow acceso solo in finestre picco; €/h documentato |
|
||||||
|
| Ops | Runbook Fase 1 eseguibile in <10 min da operatore |
|
||||||
|
| Non regressione | Live solo-sito (`matchlivetv`) e replay invariati |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Riepilogo esecutivo
|
||||||
|
|
||||||
|
Il pezzo che manca oltre ~10 live YouTube è **CPU dei relay**, non l’idea MediaMTX.
|
||||||
|
Hetzner Cloud è la leva economica giusta se:
|
||||||
|
|
||||||
|
1. scalate **solo** i worker `YoutubeRelay`,
|
||||||
|
2. tenete MediaMTX caldo sul dedicated,
|
||||||
|
3. risolvete **rete privata** dedicated↔Cloud,
|
||||||
|
4. partite da **warm pool + runbook manuale**, poi automate.
|
||||||
|
|
||||||
|
Il rischio principale non è “creare una VM”, è **leggere N volte MediaMTX e saturare uplink/MediaMTX** mentre pensate di aver risolto solo la CPU. La Fase 0 (misura) decide se l’overflow è sufficiente o se serve anche più ingest/baseline hardware.
|
||||||
|
)
|
||||||
@@ -150,6 +150,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./nginx-edge/nginx.conf:/etc/nginx/nginx.conf:ro
|
- ./nginx-edge/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
- ./nginx-edge/maintenance.html:/usr/share/nginx/maintenance/index.html:ro
|
- ./nginx-edge/maintenance.html:/usr/share/nginx/maintenance/index.html:ro
|
||||||
|
- ./nginx-edge/well-known:/usr/share/nginx/well-known:ro
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:80/edge-health"]
|
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:80/edge-health"]
|
||||||
interval: 15s
|
interval: 15s
|
||||||
|
|||||||
@@ -98,6 +98,13 @@ http {
|
|||||||
return 200 "ok\n";
|
return 200 "ok\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Android App Links (Play Console / Digital Asset Links)
|
||||||
|
location = /.well-known/assetlinks.json {
|
||||||
|
default_type application/json;
|
||||||
|
add_header Cache-Control "public, max-age=300";
|
||||||
|
alias /usr/share/nginx/well-known/assetlinks.json;
|
||||||
|
}
|
||||||
|
|
||||||
# Health check ops: pass-through senza pagina di cortesia.
|
# Health check ops: pass-through senza pagina di cortesia.
|
||||||
location = /up {
|
location = /up {
|
||||||
proxy_pass http://$rails_backend;
|
proxy_pass http://$rails_backend;
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"relation": [
|
||||||
|
"delegate_permission/common.handle_all_urls",
|
||||||
|
"delegate_permission/common.get_login_creds"
|
||||||
|
],
|
||||||
|
"target": {
|
||||||
|
"namespace": "android_app",
|
||||||
|
"package_name": "com.matchlivetv.match_live_tv",
|
||||||
|
"sha256_cert_fingerprints": [
|
||||||
|
"C3:F0:89:51:0B:00:3A:FE:10:BD:ED:68:45:1B:4F:1D:B8:5A:63:EE:8A:DA:F7:2F:5A:D6:1D:97:C9:A3:95:47",
|
||||||
|
"09:69:25:CD:8B:EC:BA:75:9A:5E:49:32:E1:35:BD:F1:AF:1E:5A:29:93:E2:65:85:8A:41:E2:11:DA:64:97:F0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -20,8 +20,8 @@ android {
|
|||||||
applicationId = "com.matchlivetv.match_live_tv"
|
applicationId = "com.matchlivetv.match_live_tv"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 27
|
versionCode = 28
|
||||||
versionName = "2.0.6-native"
|
versionName = "2.0.7-native"
|
||||||
|
|
||||||
val apiBaseUrl = project.findProperty("API_BASE_URL") as String?
|
val apiBaseUrl = project.findProperty("API_BASE_URL") as String?
|
||||||
?: "https://www.matchlivetv.it"
|
?: "https://www.matchlivetv.it"
|
||||||
@@ -47,6 +47,10 @@ android {
|
|||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro",
|
"proguard-rules.pro",
|
||||||
)
|
)
|
||||||
|
// Simboli .so per crash/ANR su Play Console (evita l'avviso "native code senza debug symbols")
|
||||||
|
ndk {
|
||||||
|
debugSymbolLevel = "SYMBOL_TABLE"
|
||||||
|
}
|
||||||
signingConfig = if (keystorePropertiesFile.exists()) {
|
signingConfig = if (keystorePropertiesFile.exists()) {
|
||||||
signingConfigs.getByName("release")
|
signingConfigs.getByName("release")
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user