ptp-proxy — IPTV, archivos y procesamiento multimedia IPTV, almacenamiento y nodo multimedia
ptp-proxy reúne en un único servicio la entrega de canales IPTV, el restream compartido, la transcodificación, el túnel VOD, las grabaciones y la publicación segura de archivos locales o remotos. Puede ejecutarse por sí solo o conectarse como nodo a un servidor PTP.
- Ocultar las credenciales de proveedores IPTV y orígenes de archivos
- Publicar canales HLS detrás de una URL propia
- Compartir una sola conexión de restream entre varios reproductores
- Aplicar perfiles de transcodificación de software o hardware
- Tunelizar VOD y descargas con rangos y seek
- Servir carpetas locales, discos y unidades montadas
- Unificar WebDAV, SFTP, SMB, NFS, FTP/FTPS y catálogos HTTP
- Conectar proveedores externos como S3, BitTorrent, aMule, rclone, IPFS o SABnzbd
- Preparar contenido bajo demanda mediante operaciones asíncronas
- Programar grabaciones de canales o URLs
- Funcionar como nodo local o remoto administrado por PTP
- Ejecutar como servicio de Windows o demonio de Linux
El cliente utiliza URLs de ptp-proxy para canales, VOD o archivos. El servidor valida la identidad y los permisos, accede al origen configurado, transforma el contenido cuando hace falta y entrega una respuesta uniforme sin revelar secretos del proveedor.
- No incluye canales, películas, series ni archivos de terceros
- No descubre automáticamente servicios o credenciales: el administrador debe añadir las fuentes autorizadas
- No sustituye a una VPN: puede usar una red o túnel existente, pero no lo crea
- No convierte un addon en contenido público: todos los proveedores siguen sujetos a permisos, límites y políticas de fuente
Modos de funcionamiento
| Modo | Qué resuelve | Flujo de datos | FFmpeg |
|---|---|---|---|
| Proxy HLS | Reescribe playlists, segmentos y claves sin recodificar el vídeo. | Cada cliente mantiene su propia sesión con el origen. | No |
| Restream | Copia o transcodifica un canal y distribuye una salida compartida. | Una sesión por canal puede atender a varios clientes. | Sí |
| Túnel VOD | Entrega archivos de vídeo remotos con tamaño, descarga y seek. | El cliente recibe un flujo byte-range independiente. | No |
| Servidor de archivos | Publica archivos locales, montados o remotos mediante una API común. | Cliente → ptp-proxy → fuente de almacenamiento. | No |
| Preparación bajo demanda | Inicia un trabajo remoto y publica el resultado cuando está disponible. | Cliente → operación → proveedor externo → objeto listo. | Opcional |
| Nodo PTP | Expone fuentes, perfiles y operaciones a un servidor PTP central. | El nodo inicia la conexión de control; la entrega puede ser directa o mediante relay. | Según la tarea |
Qué puedes construir con ptp-proxy
Las mismas funciones pueden combinarse. Un equipo puede ser a la vez proxy IPTV, servidor de archivos, nodo de transcodificación y agente remoto de PTP.
Publica playlists y streams con tus propias URLs y credenciales de acceso.
- Proxy HLS
- Claims HMAC
- Cabeceras privadas
- CONNECT opcional
Reduce conexiones al proveedor y adapta el códec o bitrate a los dispositivos.
- Perfiles JSON
- CPU o GPU
- Salida disk, memory_fs o pipe_ram
- Grabaciones
Convierte carpetas y almacenamientos remotos en URLs reproducibles con seek.
- Local y unidades montadas
- Protocolos remotos
- Rangos HTTP
- Enlaces temporales
Controla tareas que tardan en completar y publica el archivo solo cuando está listo.
- BitTorrent
- aMule/eD2k
- IPFS
- SABnzbd
Mantén las credenciales y los archivos cerca del origen mientras PTP coordina el catálogo.
- Conexión saliente
- Inventario de fuentes
- Perfiles disponibles
- Entrega directa o relay
Añade nuevos proveedores mediante plugins sin cambiar la API que usan los clientes.
- SDK y ejemplo
- Manifiestos validados
- Integridad
- Estado y reinicio administrativo
Funciones principales de un nodo
| Función | Entrada habitual | Salida para el cliente | Uso típico |
|---|---|---|---|
| Canales IPTV | Playlist o stream del proveedor | HLS propio o restream | TV en directo |
| VOD | URL remota o token claim | Stream con Range o descarga | Películas y episodios |
| Almacenamiento | Carpeta, protocolo remoto o catálogo | Listado y objetos HTTP | Biblioteca de archivos |
| Proveedor externo | Servicio especializado | Objeto listo mediante la API común | S3, P2P, cloud |
| Grabación | Canal o URL | Archivo generado en el nodo | Programas y emisiones |
| Nodo PTP | Trabajos del servidor central | Inventario, resultados y entrega | Instalaciones distribuidas |
Formas de despliegue
| Despliegue | Quién lo administra | Conectividad | Cuándo elegirlo |
|---|---|---|---|
| Independiente | Su propio config.json | Clientes conectan directamente al proxy | Una sola máquina o uso autónomo |
| Local junto a PTP | PTP inicia y supervisa el proceso | Loopback o red local | Instalación conjunta sencilla |
| Nodo remoto de PTP | PTP central mediante conexión saliente | Funciona detrás de NAT o firewall | NAS, vivienda remota, VPS o sede secundaria |
| Gateway público controlado | Administrador del nodo | TLS, tokens y límites obligatorios | Entrega directa a clientes externos |
- Solo IPTV: configura
channelsy prueba/playlist.m3u. - Servidor de archivos: añade
storage.sourcesy prueba/api/storage. - Transcodificación: instala perfiles, configura FFmpeg y elige
profile. - Contenido bajo demanda: habilita una fuente plugin con
allow_prepare. - Integración con PTP: habilita
managed_nodeo deja que PTP inicie el nodo local.
Requisitos del sistema
- FFmpeg — ejecutable
ffmpeg.exeen PATH o en la misma carpeta - Visual C++ Redistributable 2022 (normalmente ya instalado)
- Sin GPU: solo CPU, sin drivers adicionales
- Con GPU: driver NVIDIA ≥ 570.0
- FFmpeg —
sudo apt install ffmpeg - glibc 2.31+ (Ubuntu 20.04 en adelante lo incluye)
- Sin GPU: solo CPU, sin drivers adicionales
- Con GPU: driver NVIDIA + CUDA instalados
Dependencias por función
Instala solo lo que necesita tu despliegue. El binario principal puede trabajar con canales y fuentes nativas sin Python ni herramientas de addons.
| Función | Obligatorio | Opcional | Notas |
|---|---|---|---|
| Proxy HLS y VOD | ptp-proxy y acceso de red | TLS propio | No requiere FFmpeg si no se recodifica. |
| Restream, transcodificación y grabación | FFmpeg | GPU y drivers | El perfil elegido debe existir en el nodo. |
| Servidor de archivos local/remoto | Acceso a la fuente | CA privada o montaje | No requiere FFmpeg para servir el archivo. |
| Plugins Python | Python 3 | Dependencia del proveedor | S3 y los addons incluidos se ejecutan fuera del núcleo. |
| Nodo administrado por PTP | Acceso HTTPS saliente a PTP | URL pública directa | Puede funcionar detrás de NAT y usar relay como fallback. |
| memory_fs | Filesystem en memoria preparado | Límites del sistema | ptp-proxy no monta la unidad automáticamente. |
- aMule/aMuled para el proveedor eD2k.
- rclone para remotos cloud configurados por el administrador.
- Kubo para raíces IPFS/IPNS.
- SABnzbd para trabajos NZB.
- aria2c cuando se usa el proveedor BitTorrent incluido.
- Las dependencias opcionales no son necesarias si no configuras ese proveedor.
- El listener HTTP predeterminado es
127.0.0.1:8180. - Para acceso remoto, configura TLS o un reverse proxy y una clave de acceso.
- Un nodo administrado inicia la conexión hacia PTP; no necesita publicar su API administrativa en Internet.
- Las fuentes remotas deben ser alcanzables desde la máquina que ejecuta ptp-proxy.
- Los addons de control deben permanecer en loopback siempre que sea posible.
Notas importantes sobre FFmpeg
restream. Si solo usas modo proxy o VOD tunnel, FFmpeg no es necesario, pero es recomendable tenerlo disponible.Instalar FFmpeg en Windows
- Ir a gyan.dev/ffmpeg/builds y descargar la versión release-full
- Descomprimir en, por ejemplo,
C:\ffmpeg\ - Añadir
C:\ffmpeg\binal PATH del sistema o bien copiarffmpeg.exea la misma carpeta queptp-proxy.exe - Verificar: abrir terminal y ejecutar
ffmpeg -version
Instalar FFmpeg en Linux / WSL2
sudo apt update && sudo apt install ffmpeg
# Verificar:
ffmpeg -version
¿Qué versión de FFmpeg necesito?
| Función | Versión mínima FFmpeg |
|---|---|
| Copy / proxy / HLS básico | 4.0+ |
| Transcodificación CPU (libx264) | 4.0+ con libx264 compilado |
| GPU NVIDIA (h264_nvenc) | 4.4+ con NVENC support + driver ≥ 570.0 |
| Todo lo anterior (recomendado) | 6.0 o superior |
Verificar soporte NVENC (GPU NVIDIA)
# Debe aparecer "h264_nvenc" en la lista
ffmpeg -hide_banner -encoders | grep nvenc
# Prueba rápida — si da error, el driver no es compatible
ffmpeg -hide_banner -f lavfi -i nullsrc -t 1 -c:v h264_nvenc -f null -
Arquitectura — flujos de conexión
Los primeros diagramas explican IPTV y restream. Los flujos adicionales muestran cómo ptp-proxy publica archivos, coordina proveedores asíncronos y trabaja como nodo administrado por PTP.
1. Conexión directa — sin proxy
Los usuarios se conectan directamente al proveedor IPTV. Cada cliente abre su propio conjunto de conexiones (control, vídeo, EPG). Las credenciales del proveedor viajan en cada petición y son visibles para el cliente.
2. ptp-proxy insertado
ptp-proxy se sitúa entre los clientes y el proveedor. Los clientes solo ven la dirección del proxy. Las credenciales nunca salen del proxy. Cada cliente sigue generando su propia conexión upstream (modo proxy). Las líneas discontinuas indican flujos que el servidor puede activar o desactivar individualmente — el tráfico de control, vídeo y reproductor web puede pasar por el proxy de forma independiente.
3. Modo restream — upstream compartido
En modo restream, FFmpeg abre una sola conexión upstream por canal. Todos los clientes reciben el stream redistribuido. El proveedor ve exactamente una conexión sin importar cuántos espectadores haya.
4. Escenario VPN — proxy dentro de un túnel
Si ptp-proxy corre en un host que está dentro de una VPN (o un túnel Cloudflare, WireGuard, etc.). Todo el tráfico upstream al proveedor sale por la IP de la VPN. Los clientes se conectan normalmente al proxy desde el exterior.
5. Gateway de almacenamiento
↓ lista o solicita un objeto
ptp-proxy — permisos, límites y enlace temporal
↓
Local / NAS / WebDAV / SFTP / SMB / NFS / FTP / HTTP / plugin
El cliente utiliza la misma API sin conocer dónde vive el archivo. ptp-proxy conserva las credenciales, publica metadatos y traduce el seek del reproductor a rangos del origen.
6. Nodo administrado por PTP
PTP → inventario, exploración y trabajos → nodo
nodo → resultados, perfiles y operaciones → PTP
reproductor → nodo directo o reproductor → PTP → relay → nodo
PTP coordina varios nodos sin convertirlos en simples proxies de túnel. Cada nodo mantiene sus fuentes y plugins, y PTP conserva usuarios, catálogo y selección de ubicación.
7. Preparación asíncrona mediante plugins
ptp-proxy → proveedor externo
queued → preparing → ready
resultado ready → /storage/<source>/<path>
cliente → reproducción normal con Range
La operación controla contenido que todavía no existe como archivo reproducible. Cuando termina, el resultado usa exactamente la misma API y permisos que cualquier otra fuente.
Quién conserva cada responsabilidad
| Componente | Responsabilidad principal |
|---|---|
| Cliente | Solicita playlists, objetos, rangos o operaciones autorizadas. |
| ptp-proxy | Acceso a orígenes, seguridad local, streaming, transcodificación y plugins. |
| PTP | Usuarios, catálogo, biblioteca, coordinación de nodos y selección de entrega. |
| Proveedor externo | Conexión a un servicio especializado y publicación del resultado. |
| FFmpeg | Copia, transcodificación o grabación cuando el flujo lo requiere. |
Instalación en Windows
- Descargar
ptp-proxy.exedesde la sección Descargas de esta guía - Crear una carpeta, por ejemplo
C:\ptp-proxy\ - Copiar
ptp-proxy.exea esa carpeta - Copiar el archivo
config.example.jsona la misma carpeta y renombrarlo aconfig.json - Editar
config.jsoncon un editor de texto (Notepad++, VS Code...) - Abrir el terminal (
cmdo PowerShell) en esa carpeta y ejecutar:
ptp-proxy.exe --config config.json
Listening on 0.0.0.0:8180 en la consola (o en el log), el proxy está funcionando.Ejecutar automáticamente al inicio (opcional)
Para que el proxy arranque automáticamente con Windows, usa el soporte de servicio integrado en el propio ejecutable (opción recomendada, sin herramientas adicionales) o bien el Programador de tareas / NSSM (alternativa).
Instalar como Servicio de Windows (recomendado)
sc / panel de Servicios.# Instalar e iniciar el servicio
ptp-proxy.exe --install-service --config "C:\ptp-proxy\config.json"
ptp-proxy.exe --start-service
sc query ptp-proxy
# Detener y desinstalar el servicio
ptp-proxy.exe --stop-service
ptp-proxy.exe --uninstall-service
Por defecto el servicio se instala con inicio automático. Si prefieres iniciarlo a mano, añade --demand:
# Arranque manual (solo cuando se solicite)
ptp-proxy.exe --install-service --demand --config "C:\ptp-proxy\config.json"
Alternativa: NSSM
NSSM sigue siendo válido si ya lo usas para otros servicios.
# NSSM (https://nssm.cc)
nssm install PtpProxy "C:\ptp-proxy\ptp-proxy.exe" "--config C:\ptp-proxy\config.json"
nssm start PtpProxy
Diferencias Windows vs Linux
| Aspecto | Windows | Linux |
|---|---|---|
| Binario | ptp-proxy.exe | ptp-proxy (sin extensión) |
| Rutas en config.json | C:/streams/ o ./streams/ | /home/user/streams/ o ./streams/ |
| GPU NVIDIA | Sí, con driver Windows | Sí, con driver Linux + CUDA |
| Job Object (kill procesos hijo) | Automático — FFmpeg se mata al cerrar el proxy | Señal SIGTERM propagada |
| Separador de rutas | / o \ (ambos válidos en config.json) | Solo / |
| FFmpeg en PATH | Añadir C:\ffmpeg\bin al PATH o poner "path": "C:/ffmpeg/bin/ffmpeg.exe" | Generalmente ya en PATH tras apt install |
Instalación en Linux
- Descargar el binario
ptp-proxy(Linux x64) a, por ejemplo,/opt/ptp-proxy/ - Darle permisos de ejecución:
chmod +x /opt/ptp-proxy/ptp-proxy
- Copiar
config.example.jsoncomoconfig.jsonen la misma carpeta y editar - Ejecutar:
cd /opt/ptp-proxy
./ptp-proxy --config config.json
Ejecutar como servicio systemd (recomendado en Linux)
Crear el archivo /etc/systemd/system/ptp-proxy.service
[Unit]
Description=ptp-proxy
After=network.target
[Service]
ExecStart=/opt/ptp-proxy/ptp-proxy --config /opt/ptp-proxy/config.json
WorkingDirectory=/opt/ptp-proxy
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now ptp-proxy
sudo systemctl status ptp-proxy
journalctl -u ptp-proxy -f
El binario es estático (no requiere nada extra)
Usar el proxy en WSL2 (Linux dentro de Windows)
Cómo funciona la red en WSL2
WSL2 tiene su propia interfaz de red, pero Windows incluye un mecanismo llamado localhost forwarding: si una aplicación dentro de WSL2 escucha en 127.0.0.1, Windows redirige automáticamente las conexiones a 127.0.0.1 del host hacia la VM.
El proxy escucha en WSL2 Linux → Windows lo ve en su propio
127.0.0.1Todo el tráfico saliente del proxy usa la interfaz de Linux/VPN, no la de Windows
Paso 1 — Instalar WSL2 y Ubuntu
wsl --install
wsl --install -d Ubuntu-24.04
Paso 2 — Copiar el binario a WSL2
wsl -d Ubuntu-24.04 -- bash -c "cp /mnt/c/path/ptp-proxy /home/user/ptp-proxy"
wsl -d Ubuntu-24.04 -- bash -c "chmod +x /home/user/ptp-proxy"
O acceder a WSL2 directamente y mover los archivos:
wsl
cp /mnt/c/path/to/binary/ptp-proxy ~/ptp-proxy
chmod +x ~/ptp-proxy
Paso 3 — Configurar el config.json para WSL2
La clave es hacer que el proxy escuche en 127.0.0.1 dentro de WSL2. Windows se encarga del forwarding automáticamente.
"server": {
"listen": "127.0.0.1", // ← IMPORTANTE: no 0.0.0.0
"port": 8180
}
0.0.0.0 en WSL2 si el objetivo es que solo Windows acceda al proxy a través del forwarding. Con 127.0.0.1, el proxy solo es accesible desde el propio host (Windows incluido vía localhost forwarding), no desde la red local.Paso 4 — Iniciar el proxy desde PowerShell
wsl -d Ubuntu-24.04 -- bash -c "cd /path/to/proxy ; nohup ./ptp-proxy > ptp-proxy.log 2>&1 &"
wsl -d Ubuntu-24.04 -- bash -c "tail -5 /path/to/proxy/ptp-proxy.log"
wsl -d Ubuntu-24.04 -- bash -c "pkill ptp-proxy"
Paso 5 — Verificar desde Windows
Invoke-WebRequest http://127.0.0.1:8180/health
curl http://127.0.0.1:8180/health
ok como respuesta, el proxy en WSL2 está funcionando y es accesible desde Windows.Rutas en config.json para WSL2
Puedes poner la carpeta de streams directamente en Windows para ver los archivos fácilmente:
"storage_path": "/mnt/c/path/streams/" // carpeta de Windows accesible desde WSL2
FFmpeg en WSL2
sudo apt update && sudo apt install ffmpeg
libx264) en WSL2.Configuración básica (config.json)
El archivo config.json es el único fichero de configuración. Se edita con cualquier editor de texto. A continuación se explica cada bloque.
Mapa de la configuración
El archivo se divide por responsabilidad. Puedes empezar con servidor, seguridad y una sola fuente o canal, y añadir el resto después.
| Bloque | Controla | Necesario cuando |
|---|---|---|
server | Listener, TLS y límites HTTP | Siempre |
security | Claves, IPs de gestión, SSRF y rate limiting | Siempre |
ffmpeg | Ruta del ejecutable y duración HLS | Restream, transcodificación o grabación |
transcoding | Directorio y perfil predeterminado | Usas perfiles |
restream | Salida HLS, directorio y tiempos de vida | Canales restream |
storage | Fuentes, permisos, tokens, plugins y límites | Servidor de archivos o addons |
core | Claims dinámicos con un servidor de validación | PTP u otro core entrega tokens |
managed_node | Registro y conexión saliente a PTP | El proxy será un nodo administrado |
channels | Canales estáticos proxy/restream | No dependen de un core dinámico |
recordings | Directorio y política de grabación | Usas la API de grabaciones |
Estructura completa con valores por defecto
{
// ── Servidor ──────────────────────────────────────────────
"server": {
"listen": "127.0.0.1",
"port": 8180,
"worker_threads": 0,
"request_header_limit": 32768,
"request_body_limit": 1048576,
"request_timeout_sec": 30,
"write_timeout_sec": 60,
"keep_alive_timeout_sec": 30,
"max_requests_per_connection": 100,
"health_secret": "",
"tls": {
"enabled": false,
"port": 8443,
"cert": "",
"key": ""
}
},
// ── Logging ───────────────────────────────────────────────
"logging": {
"file": "ptp-proxy.log",
"json": true
},
// ── Seguridad ─────────────────────────────────────────────
"security": {
"block_private_ips": true,
"management_ips": ["127.0.0.1", "::1"],
"access_key": "",
"localhost_bypass": true,
"proxy_raw_public": false,
"key_exempt_paths": [],
"allow_unauthenticated_remote": false,
"allow_access_key_query": false,
"allow_legacy_url_query": false,
"verify_upstream_tls": false,
"upstream_ca_file": "",
"rate_limit": {
"enabled": true,
"window_sec": 10,
"max_requests": 300
}
},
// ── FFmpeg ─────────────────────────────────────────────────
"ffmpeg": {
"path": "ffmpeg",
"hls_time_seconds": 4,
"hls_list_size": 10,
"extra_args": []
},
// ── Transcoding profiles ───────────────────────────────────
"transcoding": {
"profiles_dir": "./profiles",
"default_profile": "libopenh264_balanced",
"allow_legacy_extra_args": true
},
// ── Restream output ────────────────────────────────────────
"restream": {
"output": "disk",
"storage_path": "./streams/",
"startup_grace_ms": 30000,
"idle_timeout_sec": 120
},
// ── Storage sources ───────────────────────────────────────────
"storage": {
"sources": [
{
"id": "media",
"type": "local",
"root_path": "./media"
}
]
},
// ── Core (claims integration) ─────────────────────────────────────────────────────────────
"core": {
"url": "",
"secret": ""
},
// ── Canales ────────────────────────────────────────────────
"channels": []
}
Opciones de <code>server.listen</code>
| Valor | Significado | Cuándo usarlo |
|---|---|---|
"0.0.0.0" | Acepta conexiones en todas las interfaces de red | Servidor accesible desde la red local o internet |
"127.0.0.1" | Solo conexiones locales | Uso en WSL2 o detrás de nginx en el mismo servidor |
"192.168.1.x" | Solo conexiones desde esa interfaz de red | Quieres servir solo en la red local, no en la VPN |
Límites HTTP y capacidad
Los límites protegen frente a conexiones lentas, cuerpos descontrolados y sesiones keep-alive excesivas, manteniendo estable el servicio bajo carga.
| Campo | Defecto | Descripción |
|---|---|---|
worker_threads | Número máximo de operaciones simultáneas atendidas. | Concurrencia disponible, con un mínimo de 4. |
request_header_limit | 32768 | Máximo de bytes de cabeceras. |
request_body_limit | 1048576 | Máximo de body de entrada. |
request_timeout_sec | 30 | Tiempo máximo para leer una petición. |
write_timeout_sec | 60 | Tiempo máximo para escribir la respuesta. |
keep_alive_timeout_sec | 30 | Espera máxima entre peticiones keep-alive. |
max_requests_per_connection | 100 | Peticiones máximas por conexión persistente. |
Fuentes de almacenamiento y acceso HTTP público
Las fuentes local, WebDAV, SFTP, SMB, NFS, FTP/FTPS y catálogo HTTP comparten autorización por permisos, IDs públicos opacos, enlaces temporales, límites y reproducción con rangos.
"storage": {
"sources": [
{
"id": "media",
"type": "local",
"root_path": "./media"
}
]
}
local o mediante mount_path. El bypass de localhost sigue desactivado por defecto.| Campo | Uso |
|---|---|
id / public_id | Nombre interno e ID opaco opcional expuesto en URLs públicas. |
type | local, webdav, sftp, smb, nfs, ftp o http_manifest. |
allow_list / allow_stream / allow_download | Operaciones permitidas por fuente para identidades no administradoras. |
admin_only | Restringe la fuente a storage:admin. |
access_tokens | Tokens Bearer con permisos y fuentes internas opcionales. |
link_secret | Secreto HMAC para enlaces temporales de ruta exacta. |
read_buffer_bytes | Buffer de backpressure entre 16 KiB y 4 MiB. |
max_concurrent_reads* | Límites globales, por IP y por fuente. |
idle_timeout_sec / initial_retry_count | Timeout sin progreso y reintentos solo antes de las cabeceras. |
root_path / base_url / mount_path | Raíz local, raíz remota, catálogo HTTP o recurso SMB/NFS ya montado. |
verify_tls / ca_file | Verificación de certificados para conexiones TLS. |
known_hosts / host_key_sha256 | Verificación del host SSH para SFTP. |
domain / require_signing / require_encryption | Autenticación y política de transporte SMB. |
connect_timeout_sec / transfer_timeout_sec | Timeouts positivos de conexión y transferencia total. |
TLS / HTTPS en el listener
El servidor puede escuchar en HTTP y HTTPS simultáneamente (dos puertos independientes). Se soportan TLS 1.2 y TLS 1.3; SSLv2 / SSLv3 siempre desactivados.
"server": {
"listen": "0.0.0.0",
"port": 8180,
"tls": {
"enabled": true,
"port": 8443,
"cert": "/etc/letsencrypt/live/mydomain.com/fullchain.pem",
"key": "/etc/letsencrypt/live/mydomain.com/privkey.pem"
}
}
| Campo | Por defecto | Descripción |
|---|---|---|
enabled | false | Activa el listener HTTPS. El HTTP sigue funcionando en server.port. |
port | 8443 | Puerto HTTPS. Los clientes deben usar https://host:8443/… cuando TLS está activo. |
cert | "" | Ruta al fichero PEM con la cadena de certificados completa (p.ej. fullchain.pem). |
key | "" | Ruta al fichero PEM con la clave privada (p.ej. privkey.pem). |
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost"Luego pon
"cert": "./cert.pem" y "key": "./key.pem" en el config.certbot certonly --standalone -d midominio.comFicheros:
/etc/letsencrypt/live/midominio.com/fullchain.pem y privkey.pem. Renovar automáticamente con certbot renew.Salida HLS: disco, filesystem en memoria o RAM integrada
"output": "memory_fs" — recomendado en memoriaLos segmentos se escriben en un directorio montado sobre memoria, como tmpfs o una unidad RAM. Se conserva el HLS normal y la configuración es igual en PTP y ptp-proxy.
Compatibilidad: pipe_ram mantiene el segmentador integrado para cargas pequeñas; ram sigue siendo su alias heredado.
"output": "disk"Los segmentos se guardan en storage_path sobre un sistema de archivos normal. Es la opción más sencilla y permite conservarlos durante un reinicio del proceso.
El perfil de transcodificación se elige por separado mediante transcoding.default_profile o channels[].profile.
Perfiles de transcodificación compatibles con PTP
ptp-proxy carga perfiles JSON con el mismo formato que PTP. Cada perfil tiene un identificador estable, etiquetas, códec y variantes para reproducción directa, remux y HLS.
profile_mode (hlstrans o hlscopy). ptp-proxy valida ambos y no acepta argumentos de ejecución arbitrarios desde el core.Catálogo y perfil por defecto
Configura el directorio de perfiles y un perfil por defecto. Los paquetes oficiales incluyen perfiles de software y aceleración por hardware.
"transcoding": {
"profiles_dir": "./profiles",
"default_profile": "libopenh264_balanced",
"allow_legacy_extra_args": true
}
ffmpeg.extra_args y channels[].extra_args se mantienen únicamente para migración. Pueden desactivarse con allow_legacy_extra_args=false.Perfil y variante HLS por canal
{
"id": "news",
"mode": "restream",
"url": "https://origin.example/live.m3u8",
"profile": "h264_nvenc_web",
"profile_mode": "hlstrans"
}
curl -H "Authorization: Bearer ACCESS_KEY" http://127.0.0.1:8180/api/transcode/profiles
Qué define un perfil
| Dato | Uso público |
|---|---|
id | Identificador estable compartido con PTP. |
label | Nombre localizado para interfaces. |
codec | Familia de salida que verá el cliente. |
mse_test | Prueba de compatibilidad del navegador. |
hlstrans | Variante de HLS con transcodificación. |
hlscopy | Variante HLS sin recodificación. |
direct / remux | Variantes reservadas para otros flujos de reproducción. |
Cómo se selecciona el perfil
- Un canal puede indicar
profileyprofile_mode. - Un claim dinámico puede solicitar un perfil por identificador.
- Si no se indica, se usa
transcoding.default_profile. - PTP consulta
/api/transcode/profilesy solo ofrece perfiles disponibles en el nodo. - Los argumentos heredados pueden desactivarse después de completar la migración.
Dónde se genera la salida HLS
El perfil define cómo se codifica. El modo de salida define dónde se guardan los segmentos; son decisiones independientes.
| Modo | Qué hace | Ventajas | Uso recomendado |
|---|---|---|---|
disk | Escribe playlists y segmentos en un filesystem normal. | Simple, inspeccionable y persistente mientras no se limpie. | Uso general y discos rápidos. |
memory_fs | Escribe HLS en un tmpfs o unidad RAM preparada externamente. | Mantiene todas las capacidades normales de HLS con baja latencia de I/O. | Opción recomendada cuando se quiere usar RAM. |
pipe_ram | Mantiene segmentos MPEG-TS en la memoria del proceso. | No necesita un filesystem temporal. | Cargas pequeñas y escenarios controlados. |
ram | Alias heredado de pipe_ram. | Compatibilidad de configuración. | Migrar a un nombre explícito. |
"restream": {
"output": "memory_fs",
"storage_path": "/dev/shm/ptp-proxy/streams"
}
memory_fs no monta ni crea la unidad de memoria. Configura restream.storage_path con una ruta ya preparada y limita su tamaño desde el sistema operativo.Disponibilidad real del perfil
Que un archivo de perfil exista no garantiza que el equipo tenga el codificador o driver. Prueba el perfil en cada nodo y deja que PTP almacene la disponibilidad anunciada.
Perfil con aceleración por hardware
Antes de seleccionar el perfil
- Comprueba que el nodo dispone de un codificador compatible
- Prueba el perfil con una retransmisión corta antes de asignarlo en PTP
- Mantén un perfil software disponible como alternativa
Perfil incluido para NVIDIA
"profile": "h264_nvenc_web"
Selección desde PTP
PTP puede consultar /api/transcode/profiles y ofrecer únicamente los perfiles instalados en el nodo seleccionado.
Perfiles NVIDIA incluidos
| Perfil | Compatibilidad | Objetivo | Uso recomendado |
|---|---|---|---|
h264_nvenc_web | H.264 Baseline | Compatibilidad web | Navegadores y dispositivos heterogéneos |
h264_nvenc | H.264 Main | Calidad general | Clientes que admiten el perfil Main |
Perfiles de transcodificación por software
Los perfiles software funcionan sin un codificador de vídeo dedicado. El rendimiento depende del procesador, la resolución y el número de retransmisiones simultáneas.
Perfil software recomendado
"profile": "libopenh264_balanced"
Perfiles software incluidos
| Perfil | Códec | Objetivo | Uso recomendado |
|---|---|---|---|
libopenh264_fast | H.264 | Menor consumo | Equipos limitados o pruebas |
libopenh264_balanced | H.264 | Equilibrio | Opción general recomendada |
libopenh264_quality | H.264 | Mayor calidad | Pocos canales simultáneos |
libx264_fast | H.264 | Rapidez | Instalaciones con FFmpeg GPL |
libx264_quality | H.264 | Calidad | Instalaciones con FFmpeg GPL |
libx265 | HEVC | Eficiencia | Solo clientes compatibles con HEVC |
Configuración de canales
Canal en modo proxy (más sencillo)
El proxy descarga la playlist del proveedor y reescribe los segmentos. No usa FFmpeg.
"channels": [
{
"id": "my_channel",
"name": "My Channel 1",
"mode": "proxy",
"url": "http://panel.provider.com/live/USER/PASS/123.m3u8",
"headers": []
}
]
Canal en modo restream (con FFmpeg)
{
"id": "nvenc_channel",
"mode": "restream",
"url": "http://panel.provider.com/live/USER/PASS/456.m3u8",
"profile": "h264_nvenc_web",
"profile_mode": "hlstrans",
"skip_re": true
}
Canal con headers de autenticación
{
"id": "auth_channel",
"mode": "proxy",
"url": "https://cdn.provider.com/live/stream.m3u8",
"headers": [
{ "key": "Authorization", "value": "Bearer TOKEN_HERE" },
{ "key": "Referer", "value": "https://provider.com/" }
]
}
Una vez configurado, el cliente accede al canal en:
http://your-server:8180/channels/my_channel/playlist.m3u8
Canales estáticos y flujos dinámicos
| Origen | Configuración | Ventaja |
|---|---|---|
| Canal estático proxy | channels[].mode=proxy | Configuración simple y sin recodificación. |
| Canal estático restream | channels[].mode=restream | Salida compartida y perfil controlado. |
| Claim dinámico live | El core devuelve URL y tipo live | No guarda credenciales en el cliente. |
| Claim dinámico restream | El core añade profile y profile_mode | PTP elige un perfil instalado sin enviar comandos. |
| Claim VOD/series | El core devuelve URL y tipo de contenido | Túnel con Range, seek y descarga. |
| Grabación | API /api/recordings | Guarda un canal o URL como archivo administrado. |
Integración con un core
Cuando core.url está configurado, el cliente usa un token corto. ptp-proxy lo valida con el core y recibe la URL real, el tipo de contenido y, si corresponde, el perfil solicitado. Las credenciales reales nunca se devuelven al reproductor.
Grabaciones
La API de grabaciones puede programar un canal configurado o una URL autorizada, consultar el estado y cancelar el trabajo. El resultado se guarda en el directorio configurado y no se publica automáticamente como fuente salvo que lo añadas a almacenamiento.
- Programa por hora de inicio/fin o por duración.
- Usa un nombre de salida controlado y un directorio dedicado.
- Revisa espacio libre y permisos antes de iniciar trabajos largos.
- Las grabaciones y los restreams tienen métricas separadas.
Nodo administrado por PTP
ptp-proxy puede funcionar como agente de un servidor PTP central. El nodo conserva el acceso físico a canales, archivos, perfiles y plugins, mientras PTP coordina usuarios, catálogo, exploración y reproducción.
Tres formas de usar el mismo binario
| Modo | Configuración | Control | Entrega |
|---|---|---|---|
| Independiente | managed_node.enabled=false | Config local | Los clientes acceden directamente. |
| Nodo local de PTP | PTP inicia el proceso y entrega el registro | PTP central | Loopback o red local. |
| Nodo remoto | El proxy se incorpora con un token de un solo uso | PTP central por HTTPS saliente | Directa, relay o ambas. |
ptp-proxy → heartbeat, fuentes, perfiles y plugins → PTP
PTP → trabajos autorizados → ptp-proxy
ptp-proxy → resultados tipados y operaciones → PTP
reproductor → URL directa del nodo o relay de PTP
- El servidor HTTP del proxy sigue funcionando con su configuración local.
- Las fuentes y plugins ya iniciados continúan disponibles localmente.
- El nodo reintenta la conexión con backoff.
- Los trabajos que dependan del control central esperan hasta la reconexión.
- La credencial del nodo puede revocarse individualmente desde PTP.
Qué puede administrar PTP
| Capacidad | Resultado |
|---|---|
| Inventario | Fuentes, perfiles y proveedores disponibles. |
| Exploración | Listados, metadatos y recorridos de directorios. |
| Catálogo | Importación de ubicaciones remotas a la biblioteca central. |
| Operaciones v2 | Preparar, consultar y cancelar contenido bajo demanda. |
| Plugins | Estado y reinicio administrativo de una fuente externa. |
| Transcodificación | Selección de un perfil instalado en el nodo. |
| Entrega directa | URL temporal hacia un nodo accesible. |
| Relay inverso | Lectura por rangos cuando el cliente no puede alcanzar el nodo. |
Configuración del nodo
En una instalación integrada PTP puede proporcionar estos valores mediante el entorno. En un nodo remoto se guardan en config.json y el token de incorporación se sustituye después por una credencial propia del nodo.
"managed_node": {
"enabled": true,
"server_url": "https://ptp.example.com",
"node_name": "NAS home",
"enrollment_token": "ONE_TIME_TOKEN",
"credential_file": "./state/ptp-node.json",
"public_url": "",
"direct_delivery": false,
"reverse_relay": true,
"verify_tls": true
}
Entrega directa y relay
| Modo | Ventaja | Coste o límite |
|---|---|---|
| Directa | El vídeo viaja del nodo al cliente sin pasar por PTP. | El cliente debe poder alcanzar public_url. |
| Relay | Funciona aunque el nodo no acepte conexiones entrantes. | PTP transporta los bytes y consume ancho de banda central. |
| Mixta | PTP elige directa cuando es posible y relay como fallback. | Requiere configurar y comprobar ambos caminos. |
- Solo acepta tipos de trabajo conocidos; no existe un comando shell genérico.
- Las credenciales de las fuentes permanecen en el nodo.
- Cada trabajo incluye identidad, límite y caducidad.
- El relay está acotado por tamaño y usa rangos.
- La administración de plugins sigue exigiendo permisos explícitos.
Almacenamiento local y remoto
ptp-proxy también es un servidor de archivos y gateway de almacenamiento. Publica carpetas locales, unidades montadas, protocolos remotos, catálogos HTTP y proveedores externos mediante la misma API de listado, metadatos y reproducción.
Cuatro formas de presentar contenido
La API pública no cambia aunque el archivo esté en el propio equipo, en un NAS, en un servicio remoto o todavía tenga que prepararse.
| Modelo | Origen | Disponibilidad | Ejemplo |
|---|---|---|---|
| Servidor local | Carpeta, disco o unidad montada | Inmediata | type: local |
| Gateway remoto | WebDAV, SFTP, SMB, NFS o FTP/FTPS | Inmediata si el origen responde | type: webdav |
| Catálogo HTTP | Manifiesto JSON y objetos HTTP/HTTPS | Según el catálogo | type: http_manifest |
| Proveedor externo | S3, P2P, cloud o gestor de descargas | Inmediata o tras una operación | type: plugin |
Cada objeto se referencia mediante un ID público de fuente y una ruta relativa. El ID interno, las credenciales y la URL real del origen permanecen en el servidor.
source.id: identificador interno del administrador.source.public_id: identificador opaco usado en URLs.path: ruta relativa validada dentro de la fuente.etagymodified_at: detectan cambios.range_supported: indica si el reproductor puede hacer seek.
La API describe carpetas y archivos. Un servidor PTP puede importar esas ubicaciones, asociarlas a películas o episodios y mantener metadatos, pero ptp-proxy no inventa títulos ni carátulas por sí solo.
- El cliente lista una fuente o recibe una ruta desde PTP.
- La autorización valida permiso, política de la fuente y límite de concurrencia.
- ptp-proxy consulta los metadatos y decide si puede servir rangos.
- La respuesta usa
GEToHEADcon ETag, tamaño y tipo MIME. - El cliente puede pedir un nuevo rango para hacer seek sin conocer el origen real.
↓ HTTP + Bearer o enlace firmado
ptp-proxy
↓ conexión de solo lectura
Local · WebDAV · SFTP · SMB · NFS · FTP/FTPS · Catálogo HTTP
- Mismos endpoints para todas las fuentes
- Rangos HTTP, seek, ETag y Last-Modified
- Tokens por permisos y fuentes concretas
- IDs públicos opacos y enlaces HMAC temporales
- Límites globales, por IP y por fuente
- Timeouts, cancelación, streaming estable y métricas
Inicio rápido
- Añade una fuente en
storage.sources. - Crea un token con
storage:listystorage:read. - Consulta
GET /api/storage. - Lista con
/api/storage/<public_id>/list. - Reproduce con
/storage/<public_id>/ruta.
Fuentes y modos disponibles
Elige la fuente que corresponda al servicio remoto. Los recursos ya montados pueden exponerse como carpeta o unidad, y los proveedores externos amplían la lista sin cambiar la API pública.
| Fuente | Conexión directa | Recurso montado | Requisito principal | Uso recomendado |
|---|---|---|---|---|
| Local | Sí | — | Carpeta legible por el proceso | Discos, carpetas y unidades montadas. |
| WebDAV | Sí | Opcional | Cuenta de solo lectura y TLS | Nextcloud, ownCloud, servidores DAV y rclone. |
| SFTP | Sí | Opcional | Verificación del host SSH | Servidores de archivos por SSH. |
| SMB | Según plataforma | Sí | Share accesible y credenciales | NAS y recursos Windows/Samba. |
| NFS | Según plataforma | Sí | Export autorizado para el host | Almacenamiento Unix/Linux y NAS. |
| FTP / FTPS | Sí | Opcional | FTPS recomendado | Servidores FTP existentes y sistemas legacy. |
| Catálogo HTTP | Sí | — | Manifiesto JSON versionado | CDN, hosting estático y catálogos generados. |
| S3 compatible | Sí | — | Proveedor instalado y credenciales de solo lectura | Amazon S3, MinIO, Ceph, Wasabi y servicios compatibles. |
{
"id": "local-media",
"public_id": "media",
"type": "local",
"root_path": "D:/Media",
"allow_list": true,
"allow_stream": true,
"allow_download": false
}
D:/Media; en Linux, /srv/media.{
"id": "remote-media",
"public_id": "cloud-a91e",
"type": "webdav",
"base_url": "https://dav.example.test/files/user/",
"username": "reader",
"password": "CHANGE_ME",
"auth_method": "any",
"verify_tls": true,
"allow_list": true,
"allow_stream": true,
"allow_download": false
}
verify_tls=true. Para una CA privada usa ca_file; las redirecciones se limitan al mismo origen.{
"id": "archive",
"public_id": "archive-a91e",
"type": "sftp",
"base_url": "sftp://files.example.test/media/",
"username": "reader",
"private_key": "./keys/id_ed25519",
"known_hosts": "./keys/known_hosts",
"allow_list": true,
"allow_stream": true
}
known_hosts o host_key_sha256 para verificar el servidor. Reserva allow_unknown_host para pruebas controladas.{
"id": "nas",
"public_id": "nas-media",
"type": "smb",
"base_url": "smb://nas.example.test/video/",
"username": "reader",
"password": "CHANGE_ME",
"domain": "MEDIA",
"require_signing": true,
"require_encryption": false
}
{
"id": "nfs-media",
"public_id": "nfs",
"type": "nfs",
"server": "192.168.1.30",
"export_path": "/exports/media",
"remote_path": "/library",
"version": 3,
"uid": 1000,
"gid": 1000
}
mount_path. El montaje es la opción recomendada para configuraciones avanzadas de identidad o Kerberos.{
"id": "legacy-media",
"public_id": "ftp-media",
"type": "ftp",
"base_url": "ftp://ftp.example.test/media/",
"username": "reader",
"password": "CHANGE_ME",
"tls_mode": "explicit",
"verify_tls": true,
"passive_mode": true,
"allow_list": true,
"allow_stream": true,
"allow_download": false
}
allow_insecure_ftp=true; la fuente es de solo lectura y usa modo pasivo por defecto.{
"id": "cdn-catalog",
"public_id": "cdn",
"type": "http_manifest",
"manifest_url": "https://catalog.example.test/media.json",
"base_url": "https://cdn.example.test/media/",
"refresh_sec": 300,
"max_manifest_bytes": 8388608,
"allowed_origins": ["https://cdn.example.test"],
"allow_list": true,
"allow_stream": true,
"allow_download": false
}
base_url o a allowed_origins explícitos.{
"id": "s3-media",
"public_id": "s3",
"type": "plugin",
"plugin_id": "s3-compatible",
"backend_type": "s3",
"plugin_config": {
"endpoint": "https://s3.example.test",
"bucket": "media",
"prefix": "library/",
"region": "us-east-1",
"access_key": "CHANGE_ME",
"secret_key": "CHANGE_ME",
"path_style": true,
"verify_tls": true
},
"allow_list": true,
"allow_stream": true,
"allow_download": false
}
Permisos, políticas y enlaces temporales
Storage tiene autorización propia. El bypass de localhost está desactivado por defecto y conocer una URL no concede acceso.
"storage": {
"localhost_bypass": false,
"inherit_access_key": true,
"allow_unauthenticated_remote": false,
"link_secret": "CHANGE_ME_WITH_A_LONG_RANDOM_SECRET",
"access_tokens": [
{
"token": "CHANGE_ME_READER_TOKEN",
"permissions": ["storage:list", "storage:read", "storage:prepare"],
"sources": ["local-media"]
}
]
}
| Permiso | Acción |
|---|---|
storage:list | Ver fuentes autorizadas y listar directorios. |
storage:read | Consultar HEAD y reproducir con GET. |
storage:download | Usar ?download=1 cuando la fuente lo permita. |
storage:prepare | Iniciar, consultar y cancelar preparaciones asíncronas en fuentes que lo permitan. |
storage:admin | Ver todas las fuentes y crear enlaces HMAC temporales. |
public_idoculta el ID interno en URLs públicas.allow_list,allow_streamyallow_downloadseparan capacidades.admin_onlyreserva una fuente para administradores.max_concurrent_readslimita una fuente concreta.- Los tokens restringidos enumeran IDs internos, no
public_id.
POST /api/storage/sign crea una URL ligada a fuente, ruta, acción y expiración. Alterar cualquiera de esos valores invalida la firma y nunca evita la política de la fuente.
API y reproducción
Todas las fuentes usan las mismas rutas públicas y el mismo comportamiento HTTP.
| Método y ruta | Uso |
|---|---|
GET /api/storage | Lista las fuentes visibles para la identidad. |
POST /api/storage/<source>/prepare | Inicia una preparación asíncrona; requiere storage:prepare. |
GET /api/storage/<source>/operations/<id> | Consulta estado, progreso y ruta resultante. |
DELETE /api/storage/<source>/operations/<id> | Cancela una operación cuando el proveedor lo admite. |
GET /api/storage/<source>/list?path=... | Lista una carpeta relativa. |
POST /api/storage/sign | Crea una URL temporal de ruta exacta. |
GET /api/storage/plugins | Muestra el estado operativo de los proveedores externos; requiere un Bearer explícito con storage:admin. |
GET /api/storage/plugin-providers | Inventaría proveedores instalados, versiones, capacidades, duplicados e integridad; requiere storage:admin. |
POST /api/storage/plugins/<source>/restart | Reinicia una fuente externa; requiere un Bearer explícito con storage:admin. |
GET /storage/<source>/<path> | Transmite el objeto completo o un rango. |
HEAD /storage/<source>/<path> | Devuelve metadatos sin body. |
GET ...?download=1 | Fuerza descarga cuando está autorizada. |
bytes, incluidos rangos abiertos y por sufijo, además de If-Range, If-None-Match e If-Modified-Since. Los rangos múltiples devuelven 416.Ejemplos con curl
curl -H "Authorization: Bearer CHANGE_ME_READER_TOKEN" http://127.0.0.1:8180/api/storage
curl -H "Authorization: Bearer CHANGE_ME_READER_TOKEN" "http://127.0.0.1:8180/api/storage/media/list?path=films"
curl -I -H "Authorization: Bearer CHANGE_ME_READER_TOKEN" http://127.0.0.1:8180/storage/media/films/movie.mp4
curl -H "Authorization: Bearer CHANGE_ME_READER_TOKEN" -H "Range: bytes=1048576-2097151" http://127.0.0.1:8180/storage/media/films/movie.mp4
curl -X POST -H "Authorization: Bearer CHANGE_ME_ADMIN_TOKEN" -H "Content-Type: application/json" --data '{"source":"media","path":"films/movie.mp4","ttl_sec":300}' http://127.0.0.1:8180/api/storage/sign
Operación, métricas y diagnóstico
Límites y códigos de respuesta
| Respuesta | Causa habitual |
|---|---|
401 / 403 | Falta identidad, permiso o política de fuente. |
404 | Fuente u objeto inexistente. |
416 | Rango inválido o la fuente no permite esa operación. |
429 | Límite de transferencias por IP. |
503 | Límite global/por fuente o fuente temporalmente no disponible. |
504 | Timeout del servidor remoto. |
El proxy publica transferencias activas, bytes, duración, errores, cancelaciones, rangos, rechazos por límite y reinicios/fallos de proveedores externos.
ptp_storage_active_transfers
ptp_storage_bytes_total
ptp_storage_transfer_duration_seconds_count
ptp_storage_transfer_duration_seconds_sum
ptp_storage_errors_total{code="..."}
ptp_storage_cancelled_total
ptp_storage_range_requests_total
ptp_storage_limit_rejections_total{reason="..."}
ptp_storage_plugin_restarts_total{plugin="...",reason="..."}
ptp_storage_plugin_failures_total{plugin="...",code="..."}
- Ejecuta
python3 tests/run_all.pyy CTest. - Prueba listado, reproducción completa, seek y desconexión con un servidor real.
- Confirma que los logs no contienen credenciales ni tokens.
- Usa
public_id, permisos mínimos yallow_download=falsepor defecto. - Verifica certificados, identidad del servidor remoto, permisos del share/export y orígenes permitidos del catálogo HTTP.
Las extensiones con protocolo v2 pueden preparar contenido que tarda en estar disponible. El cliente inicia el trabajo, consulta el progreso y reproduce la ruta normal devuelta cuando está lista.
curl -X POST -H "Authorization: Bearer CHANGE_ME_PREPARE_TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: example-1" --data '{"torrent_file":"sample.torrent"}' http://127.0.0.1:8180/api/storage/torrent/prepare
curl -H "Authorization: Bearer CHANGE_ME_PREPARE_TOKEN" http://127.0.0.1:8180/api/storage/torrent/operations/OPERATION_ID
curl -X DELETE -H "Authorization: Bearer CHANGE_ME_PREPARE_TOKEN" http://127.0.0.1:8180/api/storage/torrent/operations/OPERATION_ID
storage:prepare y allow_prepare: true. Un token de lectura no puede iniciar descargas ni trabajos remotos.Estados de una operación asíncrona
| Estado | Significado | Acción del cliente |
|---|---|---|
queued | Aceptada y esperando recursos. | Consultar después del intervalo indicado. |
preparing | El proveedor trabaja y puede informar progreso. | Mostrar progreso y seguir consultando. |
ready | El resultado ya tiene una ruta reproducible. | Usar la URL normal de /storage. |
failed | La operación terminó con un error estable. | Mostrar el error o reintentar con una nueva clave. |
cancelled | El usuario o administrador detuvo el trabajo. | No intentar reproducir el resultado. |
Fuentes nativas, montadas y plugins
| Clase | Quién mantiene la conexión | Cuándo usarla |
|---|---|---|
| Nativa | ptp-proxy | Protocolos comunes con soporte oficial. |
| Montada | Sistema operativo | Unidades, FUSE, shares o exports ya montados. |
| Plugin v1 | Proveedor externo | Contenido disponible inmediatamente. |
| Plugin v2 | Proveedor externo con operaciones | Contenido que debe descargarse, restaurarse o generarse. |
Prepara contenido BitTorrent autorizado y publica el archivo terminado mediante la API común. Conserva operaciones terminadas entre reinicios y permite limitar espacio, reserva libre, retención y limpieza de parciales.
Cuatro proveedores opcionales conectan servicios ya instalados sin ampliar el núcleo ni cambiar la API pública.
| Addon | Dependencia | Uso | Seek |
|---|---|---|---|
| eD2k / aMule | aMule o aMuled y amulecmd | Prepara enlaces eD2k y publica solo archivos completados. | Al terminar |
| rclone | rclone y un remoto configurado | Expone remotos cloud o de archivos en modo de solo lectura. | Según el remoto |
| IPFS / Kubo | Nodo Kubo | Lista raíces CID/IPNS y puede preparar una raíz autorizada. | Sí |
| Usenet / SABnzbd | SABnzbd | Prepara NZB autorizados y publica únicamente el resultado final. | Al terminar |
Los administradores pueden consultar las instancias activas, inventariar los proveedores instalados y reiniciar una fuente concreta sin exponer configuración, credenciales ni rutas internas.
curl -H "Authorization: Bearer CHANGE_ME_ADMIN_TOKEN" http://127.0.0.1:8180/api/storage/plugins
curl -H "Authorization: Bearer CHANGE_ME_ADMIN_TOKEN" http://127.0.0.1:8180/api/storage/plugin-providers
curl -X POST -H "Authorization: Bearer CHANGE_ME_ADMIN_TOKEN" -H "Content-Type: application/json" --data '{}' http://127.0.0.1:8180/api/storage/plugins/s3/restart
Los proveedores externos ya pueden instalarse para conectar servicios adicionales sin cambiar la API pública. ptp-proxy conserva la autenticación, los permisos, los límites, los enlaces firmados y las métricas.
- El paquete incluye proveedores S3, BitTorrent, aMule/eD2k, rclone, IPFS/Kubo y SABnzbd.
- El SDK Python, el proveedor local de ejemplo y el validador permiten crear y comprobar extensiones independientes.
- Cada proveedor declara listado, metadatos, rangos, seek y preparación asíncrona cuando corresponda.
- Las extensiones usan los mismos endpoints, permisos, enlaces y límites que las fuentes nativas.
- Instala únicamente proveedores de confianza y limita sus credenciales y APIs de control.
Diagnóstico rápido
| Síntoma | Revisar |
|---|---|
| La fuente no aparece | Token, storage:list, IDs de fuente, admin_only y allow_list. |
| Seek devuelve 416 | Soporte de rangos, tamaño conocido y capacidades del servidor remoto. |
| WebDAV no lista | Autenticación, certificado, URL raíz, redirecciones y límite de respuesta. |
| SFTP no arranca | Verificación del host, credenciales, clave privada y conectividad SSH. |
| SMB no conecta | Credenciales, dominio, políticas de firma/cifrado o mount_path. |
| NFS no conecta | Permisos del export, versión, identidad o mount_path. |
| FTP/FTPS no conecta | Modo TLS, certificado, modo pasivo, usuario y permisos de lectura. |
| El catálogo HTTP se rechaza | Esquema/versión, rutas duplicadas, tamaños, URLs y allowed_origins. |
| El proveedor externo no arranca | Consulta /api/storage/plugin-providers y /api/storage/plugins; revisa ID duplicado, integridad, Python 3, permisos, endpoint y credenciales. |
| La preparación BitTorrent se rechaza | Límite total, reserva de espacio libre, número máximo de operaciones y tamaño declarado por el torrent. |
| Cliente se corta | Timeout de escritura, timeout remoto, red y métricas de errores. |
El paquete incluye una guía general, referencias para cada fuente nativa, una guía de proveedores externos y documentación del SDK y de la prueba de conformidad.
Cómo probar el proxy
1. Verificar que el servidor arranca
tail -f ptp-proxy.log
curl http://localhost:8180/health
2. Probar un canal con ffplay
curl http://localhost:8180/api/channels
ffplay http://localhost:8180/channels/my_channel/playlist.m3u8
3. Probar VOD tunnel con un token estático (sin servidor de validación)
Útil para probar el proxy sin tener un servidor de validación montado. Añadir al config.json:
"core": {
"url": "", "secret": "",
"static_vod_tokens": { "test": "https://your-cdn.com/movie.mkv" }
}
ffplay http://localhost:8180/stream?t=test
4. Probar con VLC
- Abrir VLC → Medio → Abrir ubicación de red
- Introducir:
http://127.0.0.1:8180/channels/mi_canal/playlist.m3u8 - Click en Reproducir
5. Ver métricas
curl http://localhost:8180/metrics
6. Probar el servidor de archivos
Usa un token con storage:list y storage:read. Comprueba listado, cabeceras y un rango pequeño antes de abrir un vídeo completo.
curl -H "Authorization: Bearer CHANGE_ME_LIST_TOKEN" http://localhost:8180/api/storage
curl -H "Authorization: Bearer CHANGE_ME_LIST_TOKEN" "http://localhost:8180/api/storage/media/list?path="
curl -I -H "Authorization: Bearer CHANGE_ME_READ_TOKEN" http://localhost:8180/storage/media/video.mp4
curl -H "Authorization: Bearer CHANGE_ME_READ_TOKEN" -H "Range: bytes=0-1048575" http://localhost:8180/storage/media/video.mp4
7. Comprobar perfiles de transcodificación
curl -H "Authorization: Bearer CHANGE_ME" http://localhost:8180/api/transcode/profiles
8. Comprobar un nodo administrado
En PTP verifica que el nodo aparece conectado, publica fuentes y perfiles y puede completar un trabajo de listado. En el proxy revisa que el heartbeat no exponga secretos.
9. Ejecutar la suite automatizada
La suite crea servicios simulados locales y comprueba almacenamiento, seguridad, plugins, TLS, CONNECT, perfiles y operaciones asíncronas sin depender de proveedores reales.
cmake -S . -B build-tests -DPTP_BUILD_STORAGE_TESTS=ON
cmake --build build-tests
ctest --test-dir build-tests --output-on-failure
python3 tests/run_all.py
- Reproduce un archivo grande con VLC o ffplay y realiza varios seeks.
- Desconecta un cliente durante la transferencia y confirma que el contador activo vuelve a cero.
- Prueba cada protocolo remoto contra el servidor real que vas a usar.
- Comprueba un perfil CPU y, si procede, uno de GPU.
- Reinicia un plugin y una operación v2 sin perder el catálogo final.
- Valida entrega directa y relay para un nodo PTP remoto.
- Revisa que logs y métricas no contengan tokens ni credenciales.
Solución de problemas comunes
| Síntoma | Causa probable | Solución |
|---|---|---|
| 503 al abrir el canal | FFmpeg todavía arrancando | Normal durante los primeros 5–30 s. Esperar o aumentar startup_grace_ms |
| 503 permanente | FFmpeg falla al arrancar | Revisar el log — buscar líneas "ERROR" de FFmpeg |
| NVENC falla | Driver antiguo o GPU sin soporte | Actualizar driver a ≥ 570.0 o cambiar a libx264 |
| Stream cortado / artifacts | Fuente inestable o GOP incorrecto | Añadir -force_key_frames, probar con libx264 |
No responde /health | El proxy no arrancó o puerto bloqueado | Revisar log, revisar firewall, verificar puerto correcto |
curl: (7) Failed to connect | El proxy no está en ejecución | Iniciar el proxy, verificar listen y port |
| La fuente no aparece | Falta permiso, política o configuración | Revisar storage:list, allow_list, public_id y logs de inicio |
| El archivo abre pero no permite seek | El origen no admite rangos o no informa tamaño | Probar HEAD, revisar range_supported y el servidor remoto |
| La operación queda en preparing | Proveedor externo detenido o sin recursos | Consultar estado del plugin, cuota, espacio libre y dependencia externa |
| El nodo PTP aparece desconectado | URL, TLS o credencial del nodo | Revisar managed_node, reloj, CA y conectividad saliente |
| Perfil no disponible | Codificador o driver ausente | Consultar /api/transcode/profiles y probar el perfil en ese nodo |
Leer los logs
El proxy genera logs estructurados y censura tokens, firmas, nonces, URLs y cabeceras sensibles. Los mensajes de stream muestran solo el origen upstream.
{"lvl":"INFO", "msg":"[stream] type=live mode=hls_proxy upstream=http://cdn:80"}
{"lvl":"WARN", "msg":"proxy_claim HTTP 403: Forbidden IP"}
{"lvl":"ERROR","msg":"ffmpeg exited with code 1"}
Campos de log importantes
| Campo | Descripción |
|---|---|
[stream] mode=hls_proxy | Petición a /stream resuelta como proxy HLS directo |
[stream] mode=ffmpeg_restream | Canal restream lanzado o reutilizado |
[stream] mode=vod_tunnel | Tunnel VOD activado con seek nativo |
[stream] mode=static_bypass | Token estático sin llamada al servidor de validación |
proxy_claim HTTP 403 | El servidor de validación rechazó el token (expirado, no encontrado, IP no autorizada) |
[connect] tunnel open | Túnel CONNECT abierto para un cliente HTTP |
ffmpeg exited | FFmpeg cerró — puede ser normal (stream acabó) o error |
[storage] | Listado, metadatos o transferencia de una fuente de archivos. |
[plugin] | Inicio, reinicio, fallo o operación de un proveedor externo. |
[managed-node] | Incorporación, heartbeat, trabajos o reconexión con PTP. |
[recording] | Programación, inicio, finalización o cancelación de una grabación. |
rate limit | Una identidad o IP superó un límite configurado. |
Qué observar según la función
| Función | Log | Métrica o comprobación |
|---|---|---|
| Canales proxy | Modo y origen redactado | Peticiones y estado HTTP. |
| Restream | Arranque y salida de FFmpeg | Restreams activos y tiempo de actividad. |
| Almacenamiento | Fuente y código de error estable | Transferencias, bytes, rangos y límites. |
| Plugins | Estado y reinicios | Reinicios y fallos por proveedor. |
| Nodo PTP | Heartbeat, trabajo y reconexión | Presencia del nodo en PTP. |
| Grabaciones | Estado del trabajo | Grabaciones activas y espacio libre. |
Métricas útiles
ptp_storage_active_transfersy bytes transferidos.- Duración, cancelaciones, rangos y rechazos por límite.
- Reinicios y fallos de plugins.
- Restreams y grabaciones activas.
- Tiempo de actividad y errores HTTP.
Seguir logs en tiempo real
tail -f ptp-proxy.log
# Con formato legible (requiere jq):
tail -f ptp-proxy.log | jq -r '.lvl + " | " + .msg'
Seguridad — Capas de protección
El proxy combina protecciones HTTP generales con permisos específicos para streams, almacenamiento, plugins, CONNECT y nodos administrados. No todas las rutas usan la misma credencial ni el mismo alcance.
Capa 1 — Rate limiting (anti-hammering)
Limita cuántas peticiones puede hacer una IP en una ventana de tiempo. Si se supera, devuelve 429 Too Many Requests.
"rate_limit": {
"enabled": true,
"window_sec": 10,
"max_requests": 60
}
Capa 2 — IPs de administración
Los endpoints sensibles (/metrics, /api/*, /proxy/raw) solo son accesibles desde las IPs listadas.
"management_ips": ["127.0.0.1", "::1"]
Capa 3 — Access key
Con access_key configurado, los endpoints protegidos requieren Authorization: Bearer. El query ?key= solo funciona si se activa expresamente allow_access_key_query.
# Header HTTP (curl, backends):
curl -H "Authorization: Bearer MY_KEY" http://proxy:8180/channels/my_channel/playlist.m3u8
allow_access_key_query y allow_legacy_url_query están desactivados por defecto. Actívalos solo mientras migras clientes antiguos.localhost_bypass: true, localhost puede omitir la clave. Al escuchar fuera de loopback, el arranque exige access_key salvo que se active explícitamente allow_unauthenticated_remote.Configuración de producción recomendada
"security": {
"block_private_ips": true,
"management_ips": ["127.0.0.1", "::1"],
"access_key": "long-random-key-here",
"localhost_bypass": true,
"allow_unauthenticated_remote": false,
"allow_access_key_query": false,
"allow_legacy_url_query": false,
"verify_upstream_tls": false,
"rate_limit": { "enabled": true, "window_sec": 10, "max_requests": 300 }
}
Superficies y credenciales
| Superficie | Protección habitual | Observación |
|---|---|---|
| Canales y playlist | Access key o claim | No expongas URLs del proveedor. |
| VOD dinámico | Token claim HMAC | El core decide URL, tipo y perfil. |
| Almacenamiento | Scopes storage:* | La política se evalúa además por fuente. |
| Enlaces temporales | Firma HMAC y expiración | Ligados a fuente, ruta y acción. |
| Administración de plugins | storage:admin explícito | El bypass local no basta. |
| Preparación asíncrona | storage:prepare | Requiere también allow_prepare. |
| Nodo PTP | Credencial propia del nodo | Se obtiene tras una incorporación de un solo uso. |
| CONNECT | Secreto o claim específico | Mantén destinos y orígenes limitados. |
Cada fuente define si permite listar, reproducir, descargar o preparar. Los tokens pueden limitarse a fuentes concretas y los IDs públicos evitan revelar identificadores internos.
El nodo se conecta hacia PTP, guarda una credencial propia y solo ejecuta trabajos conocidos. No ofrece una shell, no entrega credenciales de sus fuentes y puede revocarse sin afectar a otros nodos.
Instala solo proveedores de confianza. Verifica manifiestos e integridad, usa credenciales de solo lectura, limita operaciones y revisa las dependencias externas que controlan.
Protecciones adicionales
- SSRF sin doble resolución DNS: se conecta a los mismos endpoints que fueron validados.
- Cada redirección se vuelve a validar contra la política SSRF y
allow_hosts. - Las playlists usan contextos opacos; URL y headers upstream permanecen en memoria del servidor.
- Los secretos y URLs sensibles se censuran en logs; los mensajes de stream solo muestran el origen.
- Los nonces de
/healthno pueden reutilizarse y la caché está limitada a 65.536 entradas. - La verificación TLS saliente es opcional y está desactivada por defecto; puede activarse con
verify_upstream_tls.
Checklist de exposición remota
- Usa TLS o un reverse proxy seguro.
- Configura una clave larga y desactiva compatibilidad por query.
- Limita IPs de administración.
- Mantén la verificación TLS de orígenes cuando sea posible.
- Define tokens de almacenamiento por permiso y fuente.
- Configura límites globales, por IP y por fuente.
- No publiques endpoints de control de addons.
- Prueba la revocación de nodos y enlaces temporales.
- Revisa periódicamente logs, métricas e integridad de plugins.
Sistema claim — Autenticación segura de tokens
¿Por qué es necesario?
Sin un sistema de autenticación, cualquier persona que conozca la URL del proxy podría:
- Adivinar o reutilizar tokens de otros usuarios
- Hacer que el proxy acceda a URLs que tú no autorizaste (SSRF)
- Usar el proxy como relay abierto para sus propias peticiones
El sistema claim resuelve esto: el proxy nunca confía en el token del cliente directamente. Siempre verifica con tu servidor de validación que ese token es válido y autentica la propia petición de verificación con una firma HMAC.
El flujo completo para un cliente IPTV
↓
2. Tu servicio web genera un token corto (ej: UUID, 30s TTL)
devuelve al cliente: http://proxy:8180/stream?t=TOKEN
↓
3. Cliente llama al proxy: GET /stream?t=TOKEN
↓
4. Proxy construye un claim firmado y hace POST a tu servicio:
POST https://your-service.com/api/claim?proxy_claim=1
Body JSON: { token, ts, nonce, sig }
↓
5. Tu servidor verifica la firma HMAC usando el secreto compartido
si OK → devuelve { ok: true, url: "http://panel/live/USER/PASS/123.m3u8", type: "live" }
↓
6. Proxy sirve el contenido al cliente sin exponer la URL real
El cliente nunca ve las credenciales reales. Solo ve http://proxy:8180/stream?t=TOKEN.
Por qué es seguro
| Ataque posible | Protección |
|---|---|
| Interceptar el claim y reenviarlo | El claim incluye un timestamp (ts) con ventana de ±5 minutos. Un claim capturado expira rápidamente. |
| Adivinar el secreto por fuerza bruta | El secreto nunca viaja en claro — solo viaja el resultado HMAC. Sin el secreto, un HMAC-SHA256 es computacionalmente imposible de invertir. |
| Reutilizar un token vencido | Tu servidor controla el TTL del token en su base de datos. Puede invalidarlo tras el primer uso o tras X segundos. |
| Generar un claim falso | Sin la clave secreta (core.secret), es imposible calcular un HMAC válido. Todas las peticiones sin firma válida reciben 403. |
Cálculo del claim HMAC — Paso a paso
Este apartado explica exactamente cómo el proxy construye el claim que envía a tu servidor de validación. Si quieres implementar tu propio servidor de validación (en cualquier lenguaje), esto es todo lo que necesitas.
Ingredientes del claim
| Campo | Tipo | Descripción |
|---|---|---|
token | string | El token que el cliente envió en ?t=TOKEN |
ts | int (Unix) | Timestamp actual en segundos desde epoch |
nonce | string hex | 16 bytes aleatorios criptográficos, codificados en hex. Ejemplo: a3f9c2b1d8e04f72 |
sig | string hex | HMAC-SHA256 del mensaje, en hex minúsculas |
Fórmula exacta
// 1. Construir el mensaje concatenando con "|"
message = token + "|" + ts + "|" + nonce
// Ejemplo concreto:
// token = "e3df09b42ad23f8174eaf28bbe9ad96b"
// ts = 1740698753
// nonce = "a3f9c2b1d8e04f72"
message = "e3df09b42ad23f8174eaf28bbe9ad96b|1740698753|a3f9c2b1d8e04f72"
// 2. Firmar con HMAC-SHA256 usando el secreto compartido:
sig = HMAC-SHA256(secret, message) → resultado en hex minúsculas
JSON que el proxy envía a tu servidor
POST https://your-php.com/api.php?proxy_claim=1
Content-Type: application/json
{
"token": "e3df09b42ad23f8174eaf28bbe9ad96b",
"ts": 1740698753,
"nonce": "a3f9c2b1d8e04f72",
"sig": "2b9f3e7d1c45a8f2..." // 64 hex chars
}
Respuesta que tu servidor debe devolver
// Token válido:
{ "ok": true, "url": "http://panel/live/USER/PASS/123.m3u8", "type": "live" }
// Token inválido:
{ "ok": false, "error": "Token not found" }
Tipos de stream soportados en la respuesta
type | Comportamiento del proxy |
|---|---|
live | Descarga y reescribe el playlist HLS del proveedor. Sin FFmpeg. El cliente nunca ve la URL real. |
restream | Lanza FFmpeg dinámicamente con -c copy. Genera HLS propio. Múltiples clientes comparten un proceso FFmpeg. |
vod | Tunnel Range-aware para MP4/MKV. Seek nativo sin buffering. |
series | Igual que vod. |
Implementar tu propio servidor de claims
Si quieres construir tu propia integración, aquí tienes implementaciones de referencia en varios lenguajes.
<?php
// ── Endpoint: api.php?proxy_claim=1 ───────────────────────────────────────────
if (isset($_GET['proxy_claim'])) {
$body = json_decode(file_get_contents('php://input'), true);
$token = $body['token'] ?? null;
$ts = $body['ts'] ?? null;
$nonce = $body['nonce'] ?? null;
$sig = $body['sig'] ?? null;
if (!$token || !$ts || !$nonce || !$sig) {
http_response_code(400);
echo json_encode(['ok' => false, 'error' => 'Missing fields']);
exit;
}
// Verificar HMAC — el secreto NO viaja nunca en la petición
$secret = 'YOUR_SHARED_SECRET_HERE';
$msg = $token . '|' . $ts . '|' . $nonce;
$expected = hash_hmac('sha256', $msg, $secret);
// hash_equals hash_equals evita timing attacks
if (!hash_equals($expected, $sig)) {
echo json_encode(['ok' => false, 'error' => 'Invalid signature']);
exit;
}
// Anti-replay: rechazar timestamps viejos (±5 min)
if (abs(time() - (int)$ts) > 300) {
echo json_encode(['ok' => false, 'error' => 'Timestamp expired']);
exit;
}
$info = buscar_token_en_db($token); // tu función aquí
if (!$info) {
echo json_encode(['ok' => false, 'error' => 'Token not found']);
exit;
}
echo json_encode(['ok' => true, 'type' => $info['type'], 'url' => $info['url']]);
}
import hmac, hashlib, time, json
from flask import Flask, request, jsonify
app = Flask(__name__)
SECRET = 'YOUR_SHARED_SECRET_HERE'
@app.route('/api', methods=['POST'])
def proxy_claim():
if request.args.get('proxy_claim') != '1':
return jsonify({'ok': False}), 404
body = request.get_json()
token, ts, nonce, sig = body.get('token'), body.get('ts'), body.get('nonce'), body.get('sig')
if not all([token, ts, nonce, sig]):
return jsonify({'ok': False, 'error': 'Missing fields'})
msg = f"{token}|{ts}|{nonce}"
expected = hmac.new(SECRET.encode(), msg.encode(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, sig):
return jsonify({'ok': False, 'error': 'Invalid signature'})
if abs(time.time() - int(ts)) > 300:
return jsonify({'ok': False, 'error': 'Timestamp expired'})
info = buscar_token(token)
if not info: return jsonify({'ok': False, 'error': 'Token not found'})
return jsonify({'ok': True, 'type': info['type'], 'url': info['url']})
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(express.json());
const SECRET = 'YOUR_SHARED_SECRET_HERE';
app.post('/api', (req, res) => {
if (req.query.proxy_claim !== '1') return res.status(404).end();
const { token, ts, nonce, sig } = req.body;
if (!token || !ts || !nonce || !sig) return res.json({ ok: false, error: 'Missing fields' });
const msg = `${token}|${ts}|${nonce}`;
const expected = crypto.createHmac('sha256', SECRET).update(msg).digest('hex');
const a = Buffer.from(sig, 'utf8'), b = Buffer.from(expected, 'utf8');
if (a.length !== b.length || !crypto.timingSafeEqual(a, b))
return res.json({ ok: false, error: 'Invalid signature' });
if (Math.abs(Date.now() / 1000 - +ts) > 300)
return res.json({ ok: false, error: 'Timestamp expired' });
const info = findToken(token);
if (!info) return res.json({ ok: false, error: 'Token not found' });
res.json({ ok: true, type: info.type, url: info.url });
});
package main
import (
"crypto/hmac"; "crypto/sha256"; "crypto/subtle"
"encoding/hex"; "encoding/json"; "fmt"; "math"; "net/http"; "time"
)
const secret = "YOUR_SHARED_SECRET_HERE"
type ClaimBody struct {
Token string `json:"token"`
Ts int64 `json:"ts"`
Nonce string `json:"nonce"`
Sig string `json:"sig"`
}
func proxyClaim(w http.ResponseWriter, r *http.Request) {
var b ClaimBody
json.NewDecoder(r.Body).Decode(&b)
msg := fmt.Sprintf("%s|%d|%s", b.Token, b.Ts, b.Nonce)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(msg))
expected := hex.EncodeToString(mac.Sum(nil))
if subtle.ConstantTimeCompare([]byte(expected), []byte(b.Sig)) != 1 {
json.NewEncoder(w).Encode(map[string]interface{}{"ok": false, "error": "Invalid signature"})
return
}
if math.Abs(float64(time.Now().Unix()-b.Ts)) > 300 {
json.NewEncoder(w).Encode(map[string]interface{}{"ok": false, "error": "Timestamp expired"})
return
}
json.NewEncoder(w).Encode(map[string]interface{}{
"ok": true, "type": "live", "url": "http://panel/live/USER/PASS/123.m3u8",
})
}
hash_equals en PHP, hmac.compare_digest en Python, timingSafeEqual en Node.js, subtle.ConstantTimeCompare en Go). Nunca comparar strings con == para datos criptográficos — un atacante puede medir el tiempo de la comparación.Proxy CONNECT — Tunelizar tráfico saliente a través de ptp-proxy
Además del sistema de claims para los clientes IPTV, el proxy incluye un handler para peticiones HTTP CONNECT. Esto permite que cualquier cliente HTTP (curl, tu app web, u otro servicio) tunelice sus conexiones salientes a través de ptp-proxy, de forma que todo el tráfico salga por la interfaz de red del host donde corre ptp-proxy — independientemente del sistema operativo. Un caso de uso habitual es enrutar las peticiones de tu servicio a través de un host o red diferente.
Sin CONNECT: las peticiones del cliente salen por su propia interfaz de red
Con CONNECT: las peticiones salen por la interfaz de red de ptp-proxy (su IP es la que ve el proveedor)
Configuración en config.json
"connect_proxy": {
"enabled": true,
"use_claim": false,
"secret": "YOUR_SECRET_HERE",
"allowed_origins": ["127.0.0.1", "::1"],
"allowed_hosts": []
}
| Campo | Descripción | Default |
|---|---|---|
enabled | Activa el handler CONNECT. false → 405 en cualquier CONNECT | false |
use_claim | false → secreto estático; true → claim HMAC firmado | false |
secret | Clave compartida. Con use_claim: false es el valor literal; con true es la clave HMAC | "" |
allowed_origins | IPs origen autorizadas. Si la IP del cliente no está aquí → 403 | ["127.0.0.1","::1"] |
allowed_hosts | Hostnames de destino permitidos. Vacío = sin restricción | [] |
Dos modos de autenticación para CONNECT
Modo A — Secreto estático (<code>use_claim: false</code>)
El cliente envía el secreto directamente en cada petición. Más simple. Recomendado cuando el cliente y el proxy están en el mismo host.
function curl_set_ptp_proxy_static(CurlHandle $ch): void {
curl_setopt($ch, CURLOPT_PROXY, 'http://127.0.0.1:8180');
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROTO_HTTP);
curl_setopt($ch, CURLOPT_PROXYHEADER, ['X-Proxy-Secret: YOUR_SECRET_HERE']);
}
Modo B — Claim HMAC (<code>use_claim: true</code>)
El cliente firma cada petición CONNECT con HMAC. El secreto nunca viaja en claro. Necesario si el proxy está expuesto a internet.
function curl_set_ptp_proxy_claim(CurlHandle $ch, string $url): void {
$parsed = parse_url($url);
$host = $parsed['host'];
$port = $parsed['port'] ?? ($parsed['scheme'] === 'https' ? 443 : 80);
$ts = time();
$nonce = bin2hex(random_bytes(8));
$msg = "connect|{$host}:{$port}|{$ts}|{$nonce}";
$sig = hash_hmac('sha256', $msg, 'YOUR_SECRET_HERE');
curl_setopt($ch, CURLOPT_PROXY, 'http://127.0.0.1:8180');
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROTO_HTTP);
curl_setopt($ch, CURLOPT_PROXYHEADER, [
"X-Proxy-Ts: {$ts}",
"X-Proxy-Nonce: {$nonce}",
"X-Proxy-Sig: {$sig}",
]);
}
Fórmula del claim HMAC para CONNECT
message = "connect" + "|" + host + ":" + port + "|" + ts + "|" + nonce
sig = HMAC-SHA256(secret, message) → hex minúsculas
Ejemplo: message = "connect|api.provider.com:443|1740698753|a3f9c2b1d8e04f72"
sig = hash_hmac('sha256', message, secret)
Exponer a internet con seguridad total
Si tu servicio está en un servidor remoto, necesitas que el proxy acepte conexiones desde internet. Con use_claim: true esto es seguro:
"connect_proxy": {
"enabled": true,
"use_claim": true,
"secret": "random-key-minimum-32-chars",
"allowed_origins": [],
"allowed_hosts": ["api.provider1.com", "api.provider2.com"]
}
use_claim: true- Secreto nunca viaja en claro
- Replay bloqueado (ventana ±60 s)
- Destinos restringidos por
allowed_hosts - Sin secreto = sin acceso
allowed_origins: []+use_claim: false+secret: ""→ open proxy públicoallowed_origins: []+use_claim: false→ secreto expuesto en tráfico
Generar un secreto seguro
# Linux / WSL2:
openssl rand -hex 32
Referencia de endpoints HTTP
Referencia pública de canales, VOD, grabaciones, perfiles, almacenamiento y administración. Las rutas de contenido, gestión y permisos de almacenamiento tienen políticas diferentes.
| Endpoint | Método | Acceso | Descripción |
|---|---|---|---|
/ o /health |
GET |
Público | Sonda de disponibilidad del proceso. Puede protegerse con la configuración de health cuando se usa fuera de loopback. |
/playlist.m3u |
GET |
Público | Playlist M3U con los canales habilitados y URLs HLS de ptp-proxy. |
/channels/<id>/playlist.m3u8 |
GET |
Público | Playlist HLS de un canal estático en modo proxy o restream. |
/stream?t=<token>[&dl=1] |
GET |
Público | Resuelve un claim dinámico live, restream, VOD o series y entrega el contenido autorizado. |
/download?t=<token> |
GET |
Público | Alias de descarga para un token de stream autorizado. |
/api/transcode/profiles |
GET |
Solo gestión | Publica IDs, etiquetas, códec y variantes de perfiles instalados sin exponer comandos de ejecución. |
/api/channels |
GET |
Solo gestión | Lista los canales estáticos y el perfil seleccionado. |
/api/recordings |
GET / POST |
Solo gestión | Lista o programa grabaciones de canales y URLs autorizadas. |
/api/recordings/<id> |
GET / DELETE |
Solo gestión | Consulta o cancela una grabación concreta. |
/api/storage |
GET / HEAD |
Permiso de storage | Lista las fuentes visibles para una identidad con storage:list. |
/api/storage/<source>/list?path=<relative> |
GET / HEAD |
Permiso de storage | Lista carpetas y archivos con tamaño, fecha, MIME, ETag y soporte de rangos. |
/storage/<source>/<path>[?download=1] |
GET / HEAD |
Permiso de storage | Entrega un objeto con rangos, seek, condicionales HTTP y política de reproducción o descarga. |
/api/storage/sign |
POST |
Storage admin | Crea un enlace temporal ligado a fuente, ruta, acción y expiración. |
/api/storage/<source>/prepare |
POST |
Permiso de storage | Inicia una operación v2 idempotente en una fuente con preparación habilitada. |
/api/storage/<source>/operations/<id> |
GET / DELETE |
Permiso de storage | Consulta el progreso o cancela una operación asíncrona. |
/api/storage/plugins |
GET / HEAD |
Storage admin | Estado no sensible de las instancias de plugins configuradas. |
/api/storage/plugin-providers |
GET / HEAD |
Storage admin | Inventario de proveedores instalados, versiones, capacidades e integridad. |
/api/storage/plugins/<source>/restart |
POST |
Storage admin | Reinicia administrativamente una fuente plugin. |
/metrics |
GET |
Solo gestión | Métricas Prometheus de servidor, restream, grabaciones, almacenamiento y plugins. |
/proxy/raw?url=<encoded> |
GET |
Solo gestión | Proxy de diagnóstico para una URL permitida. No debe publicarse sin controles de gestión. |
CONNECT <host>:<port> |
CONNECT |
Público | Túnel HTTP CONNECT sujeto a su política de autenticación y destinos. |
PTP managed-node channel |
HTTPS saliente |
Canal saliente | El agente se incorpora, publica heartbeat y recibe trabajos desde PTP. No añade una API administrativa entrante al nodo. |
Las rutas marcadas como gestión usan las IPs y clave generales. Las rutas de almacenamiento usan permisos storage:list, storage:read, storage:download, storage:prepare o storage:admin. El canal de nodo usa una credencial propia y conexión saliente.
La integración con PTP no expone endpoints públicos adicionales en ptp-proxy. El agente inicia incorporación, heartbeat y long polling hacia las rutas de nodo del servidor PTP.
Referencia rápida con curl
# Listar canales
curl -H "Authorization: Bearer CHANGE_ME" http://127.0.0.1:8180/api/channels
# Listar fuentes de almacenamiento
curl -H "Authorization: Bearer CHANGE_ME_LIST_TOKEN" http://127.0.0.1:8180/api/storage
# Leer un rango de un archivo
curl -H "Authorization: Bearer CHANGE_ME_READ_TOKEN" -H "Range: bytes=0-1048575" http://127.0.0.1:8180/storage/media/films/movie.mkv
# Consultar perfiles
curl -H "Authorization: Bearer CHANGE_ME" http://127.0.0.1:8180/api/transcode/profiles
# Consultar plugins
curl -H "Authorization: Bearer CHANGE_ME_ADMIN_TOKEN" http://127.0.0.1:8180/api/storage/plugins
# Iniciar una preparación
curl -X POST -H "Authorization: Bearer CHANGE_ME_PREPARE_TOKEN" -H "Content-Type: application/json" --data '{"torrent_file":"sample.torrent"}' http://127.0.0.1:8180/api/storage/torrent/prepare