diff --git a/backend/public/.well-known/assetlinks.json b/backend/public/.well-known/assetlinks.json new file mode 100644 index 0000000..195632c --- /dev/null +++ b/backend/public/.well-known/assetlinks.json @@ -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" + ] + } + } +] diff --git a/docs/ANDROID_APP_LINKS.md b/docs/ANDROID_APP_LINKS.md new file mode 100644 index 0000000..d28add2 --- /dev/null +++ b/docs/ANDROID_APP_LINKS.md @@ -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. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 2750f42..b26162d 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -397,6 +397,8 @@ cd infra && cp .env.example .env && docker compose up -d --build | Documento | Contenuto | |-----------|-----------| | [`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 | | [`REPLAY_MODULE.md`](REPLAY_MODULE.md) | Garage, retention, YouTube VOD | | [`OPS_MONITORING.md`](OPS_MONITORING.md) | ntfy, variabili ops, troubleshooting | diff --git a/docs/infrastructure/HETZNER_CLOUD_RELAY_OVERFLOW.md b/docs/infrastructure/HETZNER_CLOUD_RELAY_OVERFLOW.md new file mode 100644 index 0000000..0c66c06 --- /dev/null +++ b/docs/infrastructure/HETZNER_CLOUD_RELAY_OVERFLOW.md @@ -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. +) diff --git a/infra/docker-compose.prod.yml b/infra/docker-compose.prod.yml index 28ce7c1..99f3360 100644 --- a/infra/docker-compose.prod.yml +++ b/infra/docker-compose.prod.yml @@ -150,6 +150,7 @@ services: volumes: - ./nginx-edge/nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx-edge/maintenance.html:/usr/share/nginx/maintenance/index.html:ro + - ./nginx-edge/well-known:/usr/share/nginx/well-known:ro healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:80/edge-health"] interval: 15s diff --git a/infra/nginx-edge/nginx.conf b/infra/nginx-edge/nginx.conf index 84b266d..3544970 100644 --- a/infra/nginx-edge/nginx.conf +++ b/infra/nginx-edge/nginx.conf @@ -98,6 +98,13 @@ http { 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. location = /up { proxy_pass http://$rails_backend; diff --git a/infra/nginx-edge/well-known/assetlinks.json b/infra/nginx-edge/well-known/assetlinks.json new file mode 100644 index 0000000..195632c --- /dev/null +++ b/infra/nginx-edge/well-known/assetlinks.json @@ -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" + ] + } + } +] diff --git a/native/android/app/build.gradle.kts b/native/android/app/build.gradle.kts index 81603c5..034b8e9 100644 --- a/native/android/app/build.gradle.kts +++ b/native/android/app/build.gradle.kts @@ -20,8 +20,8 @@ android { applicationId = "com.matchlivetv.match_live_tv" minSdk = 24 targetSdk = 36 - versionCode = 27 - versionName = "2.0.6-native" + versionCode = 28 + versionName = "2.0.7-native" val apiBaseUrl = project.findProperty("API_BASE_URL") as String? ?: "https://www.matchlivetv.it" @@ -47,6 +47,10 @@ android { getDefaultProguardFile("proguard-android-optimize.txt"), "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()) { signingConfigs.getByName("release") } else {