Media stack (Jellyfin + *arr)
Decomisionado el 2026-08-04. Los siete contenedores, sus imágenes, la librería en /data (movies/tv/downloads) y el túnel Cloudflare dedicado (cloudflared-estebanalcocer.service, hostnames tv./buscartv.) se eliminaron por completo de vm-main. Esta página se conserva como referencia histórica de la arquitectura, las decisiones de diseño y los bugs encontrados — no describe nada que siga corriendo.
Servidor de medios personal que corrió en vm-main: Jellyfin para reproducir, Jellyseerr para pedir contenido, y el trío Prowlarr/Radarr/Sonarr + qBittorrent para buscar y descargar automáticamente. Bazarr agregaba subtítulos. Siete contenedores en /opt/docker/media/docker-compose.yml, red Docker propia (media_default), separada de los stacks de Conkafecito que corren en el mismo host.
Servicios y puertos
| Servicio | Rol | Puerto | Expuesto a internet |
|---|---|---|---|
| Jellyfin | Reproducción | 8096 | ✅ tv.estebanalcocer.cloud |
| Jellyseerr | Solicitudes de contenido | 5055 | ✅ buscartv.estebanalcocer.cloud |
| Prowlarr | Indexador centralizado | 9696 | ❌ solo 127.0.0.1 |
| Radarr | Gestión de películas | 7878 | ❌ solo 127.0.0.1 |
| Sonarr | Gestión de series | 8989 | ❌ solo 127.0.0.1 |
| qBittorrent | Cliente de descarga | 8090 | ❌ solo 127.0.0.1 (torrent 6881 sí público) |
| Bazarr | Subtítulos automáticos | 6767 | ❌ solo 127.0.0.1 |
Los cuatro servicios de administración (Prowlarr, Radarr, Sonarr, Bazarr) y qBittorrent solo escuchan en 127.0.0.1 del host — se accede vía túnel SSH (ssh -L <puerto>:localhost:<puerto> ...), no por Cloudflare. Solo Jellyfin y Jellyseerr, las dos superficies de uso diario, están publicadas.
Estructura de carpetas y hardlinks
/data/movies, /data/tv, /data/downloads viven en el mismo filesystem que /opt/docker/media/*/config, siguiendo la convención de linuxserver.io. Radarr/Sonarr importan por hardlink en vez de copiar — solo funciona si el volumen de descargas y el de la biblioteca final están en el mismo filesystem, por eso ambos cuelgan del mismo /data.
Indexadores: por qué solo Internet Archive
Prowlarr trae un catálogo de 622 definiciones de indexadores integradas. Se revisó el catálogo completo buscando fuentes de contenido de video que fueran legales, y solo una calificó sin ambigüedad: Internet Archive (archive.org), biblioteca digital sin fines de lucro, contenido de dominio público/licencia libre, sin categoría XXX en absoluto.
El resto del catálogo con contenido de video —1337x, The Pirate Bay, YTS, EZTV, Nyaa.si, los clones de KickassTorrents, LimeTorrents, ExtraTorrent, etc.— existe para distribuir contenido con copyright sin autorización del titular. Excluir la categoría XXX de un indexador no resuelve eso; el problema es el copyright del contenido en general, no el contenido adulto. Por decisión explícita, ninguno de esos se agregó, incluso cuando se pidió directamente hacerlo.
Nota de estado: en algún momento posterior alguien agregó The Pirate Bay, YTS y Nyaa.si directamente en Prowlarr (vía su UI, no por este agente) — quedan activos porque es decisión del dueño de la infraestructura, no algo que este documento recomiende ni haya configurado.
Cómo agregar un indexador nuevo: Prowlarr → Indexers → Add Indexer → buscar por nombre → llenar credenciales si pide → Test → Save. Prowlarr sincroniza solo a Radarr/Sonarr (quedaron conectados como “Applications” con fullSync). Si el indexador tiene categoría XXX/Adult, desmarcarla en la pestaña Categories antes de guardar.
Interconexión entre servicios
Todo conectado por API, no solo levantado:
- Prowlarr → Radarr y Prowlarr → Sonarr: “Applications” con
fullSync, cada indexador nuevo se propaga solo. - Radarr → qBittorrent y Sonarr → qBittorrent: download client, categorías
radarr/sonarrseparadas, mismosavePath(/data/downloads) para permitir el hardlink. - Bazarr → Radarr, Sonarr y Jellyfin: por API key, incluye refresco de biblioteca de Jellyfin al insertar subtítulos, y
use_subsync: true(ffsubsync) para sincronización automática. - Jellyseerr → Jellyfin, Radarr y Sonarr: autenticado con el usuario admin de Jellyfin, bibliotecas Movies/TV Shows habilitadas, perfiles de calidad enlazados.
Notificaciones por Telegram
Mismo bot (@envdev54bot) conectado en varios puntos, cada uno con solo los eventos que valen la pena — nada de ruido por cada búsqueda de indexador (onGrab deliberadamente desactivado en todos):
| Origen | Vía | Eventos |
|---|---|---|
| Jellyseerr | Notification agent nativo | Solicitud pendiente/auto-aprobada, título disponible, solicitud fallida |
| Radarr / Sonarr | Connect → Telegram nativo | Import/upgrade completado, health issue (indexador caído, disco lleno), health restaurado, intervención manual requerida (ej. descarga atorada) |
| Prowlarr | Connect → Telegram nativo | Health issue / restaurado (ej. un indexador entra en backoff) |
| Los 7 contenedores | Script propio + systemd timer cada 2 min, sin equivalente nativo en ningún *arr | Contenedor cae a exited/dead/restarting/unhealthy → alerta; recuperación → alerta de vuelta |
El watcher de contenedores (/opt/docker/media/scripts/stack-healthcheck.sh, unidad media-stack-healthcheck.timer) compara el estado actual de docker compose ps contra la corrida anterior y solo notifica en cambios de estado, para no llenar el chat de mensajes repetidos.
Bugs encontrados y su fix
qBittorrent con ruta de descarga por defecto rota. La imagen linuxserver trae Session\DefaultSavePath=/downloads/ de fábrica, que no corresponde a ningún volumen montado (el compose monta /data/downloads). El contenedor corre como usuario abc sin permisos de escritura ahí, así que las descargas quedaban en estado error con Permission denied. Fix: setPreferences con save_path/temp_path apuntando a /data/downloads/.
Torrents de Internet Archive que nunca llegan a 100% aunque el video ya esté completo. Los archivos de metadata que archive.org empaqueta en el torrent (.xml, .sqlite, thumbnail) se regeneran con el tiempo y dejan de coincidir con el hash de la última pieza del torrent — el propio torrent lo advierte en su comentario (“Files may have changed”). El video en sí puede estar 100% descargado y el cliente igual lo reporta stalledDL al 99%. No tiene arreglo del lado del cliente BitTorrent; la solución fue traer el archivo directo por HTTP desde archive.org/download/<item>/ e importarlo manualmente en Radarr (/api/v3/manualimport), sin depender de que qBittorrent marque el torrent como completo.
Releases de tamaño ridículo que Radarr agarra solos. Un indexador puede tener un ítem mal etiquetado (ej. un clip de 8MB con nombre de película de 96 minutos). Radarr lo detecta como posible “Sample” en el manual import (rejections: [{reason: "Sample"}]), pero en automático lo grababa igual. Vale la pena revisar el tamaño cuando una descarga “termina” sospechosamente rápido antes de asumir que está lista.
Jellyseerr NO_ADMIN_USER al conectar con Jellyfin 10.11.x. El endpoint /api/v1/auth/jellyfin requiere un campo serverType (2 = Jellyfin) en el body — sin él, aunque el login a Jellyfin sea válido y el usuario sí sea admin, Jellyseerr tira NO_ADMIN_USER porque nunca llega a comparar el flag de admin. Además, la imagen fallenbagel/jellyseerr:latest en Docker Hub está desactualizada (servía v2.7.3) — la imagen que el proyecto mantiene al día es ghcr.io/fallenbagel/jellyseerr:latest.
Bazarr sin subtítulos en español — cadena de tres problemas independientes. Bazarr quedó conectado a Radarr/Sonarr/Jellyfin desde el despliegue inicial pero nunca se configuró ningún idioma ni proveedor de subtítulos (ninguno de los dos es un default; hay que activarlos a mano). Al arreglar eso aparecieron dos problemas más: el proveedor subf2m (el elegido por no requerir cuenta) se autobloquea 12 horas si no tiene un user_agent configurado en subf2m.user_agent; y el minimum_score_movie por defecto (70%) rechaza subtítulos correctos que solo matchean por título/año/IMDB en vez de por hash exacto de release — normal en archivos que no vienen de un release “de escena” (bajado a 50%). Un cuarto detalle a evitar: mezclar “Spanish” (es) y “Spanish (Latino)” (ea) en el mismo perfil de idiomas hace que Bazarr colapse ambos en uno solo (se queda con la variante regional spa;country=MX), que subf2m no reconoce — el proveedor se salta entero y no baja nada. Si se quiere Latino además de Spanish, tiene que ir en un perfil separado.
Túnel Cloudflare (histórico)
El stack tenía su propio túnel, cloudflared-estebanalcocer.service (túnel vm-main, cuenta estebanalcocer.cloud), que publicaba tv.estebanalcocer.cloud → Jellyfin y buscartv.estebanalcocer.cloud → Jellyseerr. Se eliminó junto con el resto del stack el 2026-08-04 (servicio systemd, registros DNS y el túnel en sí, vía cloudflared tunnel delete).
Jellyseerr necesitó subdominio propio, no un path dentro de tv.estebanalcocer.cloud. Cloudflare Tunnel sí soporta ingress por path, pero Jellyseerr (Next.js) no tiene soporte de base path — hay un feature request abierto sin resolver en su repo (Fallenbagel/jellyseerr#97) y reportes de login roto al ponerlo detrás de reverse proxy sin subdominio propio. El propio linuxserver.io, que mantiene configs de referencia para decenas de apps, solo publica variante de subdominio para Jellyseerr, nunca de subcarpeta.
Acceso a paneles de administración (histórico)
ssh -L 9696:localhost:9696 -L 7878:localhost:7878 -L 8989:localhost:8989 \
-L 8090:localhost:8090 -L 6767:localhost:6767 usuario@<host>Y entrar por http://localhost:<puerto> en el navegador local.
Véase también
- Túneles de Cloudflare — infraestructura de túneles vigente en vm-main; ya no incluye el túnel de este stack
- Docker — patrón de Docker en el otro host del hub (vm-playground); este stack usaba red bridge propia en vez de
network_mode: hostporque no necesitaba llegar a nada bindeado en localhost del host, salvo el propio túnel