Radio en Linode ([IP]) — estado 2026-09-06
Migrada de GCP ([IP], instancia muerta; bundles locales eran única copia).
URLs públicas
- Mixta: http://[IP]/radio
- Solo español: http://[IP]/es
- Subtítulos en vivo: http://[IP]/subtitulos
- Comparar voces: http://[IP]/muestras/
- FreshRSS: http://[IP]/ (user escucha)
Servicios (todos —restart unless-stopped salvo icecast sysv)
- icecast2 nativo :8000 (pass source
tryifyoucan1, adminhf_TtjxEAXEAsy1!), mounts /radio + /es - docker freshrss :8080, data en /var/freshrss/data
- docker liquidsoap (savonet/liquidsoap:v2.4.4, network host), script /opt/radio/radio.liq
- nginx :80 proxy;
location = /es,/radio,/api/estado→status-json,/textos/→/var/radio/textos/,/muestras/,= /subtitulos - Rearranque:
/opt/radio/start.sh
Pipeline
- Crons: refresh FreshRSS cada 30min (
docker exec freshrss php ./app/actualize_script.php), ingestor cada 20min (/opt/radio/venv/bin/python /opt/radio/radio_ingestor.py) - Ingestor: FreshRSS + 3 feeds ES directos (BBC Mundo, Xataka, El País) en round-robin, MAX_ITEMS=12. Detect: feedparser no trae
language→ se fuerzaesa lo que venga de EXTRA_FEEDS_ES. - TTS: Edge TTS gratis. es-MX-DaliaNeural / en-US-GuyNeural, rate +5%. Nota:
es-US-AriaNeuralNO existe. - MP3→/var/radio/queue/, ES linkeado en queue_es/, transcript en /var/radio/textos/{base}.txt. Subtítulos vía
map_metadata(file_as_title)en radio.liq → title en status-json.
Pendientes / decisiones abiertas
- Voz ES final: Dalia actual; comparar en /muestras/ (Paloma, Ximena). Cambiar en
VOICESde radio_ingestor.py. - Audios viejos (pre-2026-09-06) no tienen .txt → subtítulos muestran fallback.
- Siguiente: dominio + HTTPS (certbot sobre nginx existente).
Marca 2026-09-06
- Nombre: Surtidito Tech Radio (/radio) y Surtidito Tech Radio ES (/es).
- stream-name en icecast.xml (mounts), location Mexico.
- Cortinillas TTS con marca en queue/ y queue_es/ + txt.
- /subtitulos con H1 de marca.
- Fix loop: playlist reload_mode=rounds reload=1 (reload=10s reiniciaba la ronda).
- Rotacion en ingestor: conserva 80 nuevos, resto a done/, limpia txt huerfanos y symlinks rotos.
Voces y marca 2026-09-06 (tarde)
- Cortinillas: archivos del usuario (Neural2, rate 1.1/pitch +2): surtidito-es.mp3 y surtido-en.mp3 desplegados como cortinilla-es/en. Anteriores Edge respaldadas en /var/radio/done/cortinilla-*-edge.bak.mp3.
- Marca EN se queda como “Surtido Radio” (decision usuario).
- Voz ES noticias: es-US-PalomaNeural (gano en /muestras/ vs Dalia/Ximena). Cortinilla ES sigue siendo archivo Neural2 del usuario.
- HTTPS pendiente: sin dominio aun.
Sonido 2026-09-06 (tarde)
- Stingers sintetizados local (gen_stingers.py, numpy+ffmpeg): tun-tun cinematico 2.4s (swell+golpe) e intro 8-bit 4.7s (square arps). Pico -1.5dB, fades integrados.
- Al aire como stinger-tuntun.mp3 y stinger-8bit.mp3 en queue/ y queue_es/ (excluidos de rotacion). demo-corte.mp3 = 8bit+tuntun+cortinilla.
- Fade general: crossfade(duration=1.5) + mksafe en radio.liq (cross solo falla: pide funcion; crossfade directo falla: fuente falible para icecast).
- Escuchar: http://[IP]/muestras/
Subtitulos por canal 2026-09-06 (noche)
- /subtitulos: vista ambas + ?canal=radio y ?canal=es (vistas individuales).
- Poll 5s con deteccion de cambio de titulo; solo actualiza texto (antes re-render cada 15s cortaba el audio en reproduccion).
- Historial de 5 segmentos anteriores por canal. Transcripts nuevos 200 OK en ambos mounts.
Integracion surtido → radio (push) 2026-09-06
- Modelo: render local (gcloud + voces oficiales) + push MP3. Cero secretos en Linode.
- Llaves: ~/.ssh/linode_root (admin) y ~/.ssh/surtido_radio_deploy (forced-command receptor).
- Receptor: /opt/radio/bin/recibir-parrilla.sh (allowlist + tope 100MB, instala en queue/ + symlink queue_es/).
- Sender: surtido/scripts/radio_push.sh (tar —no-xattrs para silenciar warnings macOS).
- SSH hardenizado: PasswordAuthentication no, root prohibit-password. Password de .env solo sirve para consola Lish.
- Queda local: Ollama, whisper, RSSHub/X, DeepSeek, Resend, Supabase (Linode 2CPU/3.8GB no da).
Musica y voz 2026-09-06 (noche 2)
- Fuera 8-bit del aire (stinger-8bit + descanso-8bit de colas). Receptor lo rechaza y sender ya no lo manda (si volviera, tumba el push entero).
- Al aire: stinger-tuntun + stinger-descanso (pad calido + chimes, 8s, gen_stingers.py:descanso_suave).
- Voz ES noticias: es-ES-XimenaNeural (Paloma fuera).
- Nota operativa: hardening mato auth por password; todo acceso por ~/.ssh/linode_root y deploy key.
surtido.xyz/radio 2026-09-06
- Decision: /radio (pagina parrilla) SOLO enlaza al server Linode; sin proxy del stream.
- asgi.py: seccion En vivo con players + links directos a https://radio.surtido.xyz/es y /radio. Verificado 200 en local (/radio y /radio/en).
- Pendiente usuario: (1) registro DNS A radio→[IP] gris en CF (token actual solo-lectura), (2) git push para deploy Vercel. Luego yo: certbot + verify.
surtido.xyz sin subdominio 2026-09-06
- Subdominio pausado. Diagnostico: pagina /radio desplegada con En vivo pero apuntaba a radio.surtido.xyz (inexistente).
- Solucion: rewrites Vercel en vercel.json (/vivo→[IP]/radio, /vivo-es→[IP]/es) + asgi.py VIVO same-origin. Sin mixed-content, sin DNS.
- Pendiente: git push (deploy) y luego verificar estabilidad del proxy (stress 3min).
Verificacion + tanda nocturna 2026-09-06 (15:30)
- Deploy 515b9ff via vercel —prod (Git auto-deploy no habia entrado; CLI si).
- Verificado: /radio con player /vivo-es, stream por proxy Vercel sostiene 60s+ (988KB, bitrate correcto).
- Cron one-shot hoy 19:30 CST: scripts/tanda_radio.sh (actualizar_hn —no-deploy + radio_parrilla + arma + push + vercel —prod; se auto-desinstala). Requiere Mac despierta. Log /tmp/tanda-radio-*.log
Limpieza 2026-09-06
- /muestras eliminado (301 → /subtitulos); dir y tmps borrados; link muerto fuera de subtitulos.
- Stingers viven en colas + gen_stingers.py los regenera.
- tanda_radio.sh commiteado y pusheado. Quedan en working tree solo cambios del usuario (analitica, hn, leer, etc. — no tocar).
Bilingue ES/EN 2026-09-06
- Un programa, dos idiomas: /radio=ES (queue/), /en=EN (queue_en/). Ingestor rutea por idioma + txt ambos.
- Receptor: EN (en-*, parrilla-en, fallback-en) → queue_en; sender incluye parrilla/en/.
- queue_es eliminada; nginx /es → /radio (compat). Icecast mounts con nombre ES/EN.
- Subtitulos: vistas /radio + /en (?canal=), base() acepta en-seg/parrilla-en/fallback-en.
- Sitio: /radio player /vivo-es, /radio/en player /vivo-en (rewrite nuevo). Deploy verificado: los 3 vivos fluyen.
Horario nocturno + anti-vacio 2026-09-06
- Apagado 23:00-07:00 CST (cron UTC 05:00 stop / 13:00 start de liquidsoap; mounts dan 404 de noche, probado).
- De noche siguen corriendo freshrss+ingestor: la manana arranca con cola fresca.
- Blindaje: JINGLE() excluye cortinilla/stinger/fallback-loop/parrilla-7min de rotacion; piso_minimo() repone desde done/ si cola ES < 15; salud.sh horario a /var/log/radio-salud.log.
Prensa nocturna 2026-09-06
- scripts/prensa_noche.py: P12 (portada/el-pais/el-mundo/economia) + 5 independientes (Periodistas Unidos, Chiapas, Espejo, Contralinea, Riodoce) + Rompeviento via YouTube RSS. 130 notas, 0 fallas.
- Riodoce /feed da 500/0 items (feed roto, queda en monitoreo). Rompeviento.tv tomado por spam TR → se usa su canal YT (UCBdM6w8S6kn90P0or3Z0kxQ).
- Cron diario 03:00 → candidatas/prensa-Fecha.json para curaduria matutina. feedparser agregado a requirements-local.
Auditoria + hardening 2026-09-06
- Hallazgos: sin firewall, sin fail2ban, freshrss:8080 publico, pass icecast debil, 19 updates, version nginx expuesta.
- Fix: passwords 32hex (en /root/.radio-secrets), freshrss a [IP] + start.sh actualizado, UFW (22/80/8000), fail2ban sshd, server_tokens off, updates al dia, MaxAuthTries 3.
- Veredicto Cloudflare: NO proxy naranja (corta streams largos); con host endurecido no hace falta. Solo si hay DDoS real (ahi Spectrum, de paga).