diff --git a/docker-compose.simple.yml b/docker-compose.simple.yml index 1ef276d17..7a6b3a8d2 100644 --- a/docker-compose.simple.yml +++ b/docker-compose.simple.yml @@ -50,10 +50,9 @@ services: - postgres - rabbitmq environment: - LIBRETIME_CONFIG_FILEPATH: /etc/libretime/config.yml LIBRETIME_DEBUG: ${LIBRETIME_DEBUG:-false} volumes: - - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro + - libretime_config:/etc/libretime:ro - libretime_storage:/srv/libretime legacy: @@ -65,7 +64,7 @@ services: environment: LIBRETIME_CONFIG_FILEPATH: /etc/libretime/config.yml volumes: - - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro + - libretime_config:/etc/libretime:ro - libretime_storage:/srv/libretime nginx: @@ -98,7 +97,7 @@ services: depends_on: - rabbitmq volumes: - - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro + - libretime_config:/etc/libretime:ro - libretime_playout:/app environment: LIBRETIME_CONFIG_FILEPATH: /etc/libretime/config.yml @@ -114,7 +113,7 @@ services: - "${LIQUIDSOAP_HARBOR_PORT:-8001}:8001" - "${LIQUIDSOAP_TELNET_PORT:-8002}:8002" volumes: - - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro + - libretime_config:/etc/libretime:ro - libretime_playout:/app environment: LIBRETIME_CONFIG_FILEPATH: /etc/libretime/config.yml @@ -125,7 +124,7 @@ services: depends_on: - rabbitmq volumes: - - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro + - libretime_config:/etc/libretime:ro - libretime_storage:/srv/libretime environment: LIBRETIME_CONFIG_FILEPATH: /etc/libretime/config.yml @@ -136,8 +135,22 @@ services: depends_on: - rabbitmq volumes: - - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro + - libretime_config:/etc/libretime:ro - libretime_storage:/srv/libretime + + # Generador de configuración para escribir config.yml en el volumen libretime_config + config-generator: + image: alpine:3.18 + restart: "no" + entrypoint: ["/bin/sh","/tools/easypanel-config-generator.sh"] + volumes: + - libretime_config:/config + - ./tools:/tools:ro + healthcheck: + test: ["CMD-SHELL","test -f /config/config.yml"] + interval: 2s + timeout: 2s + retries: 10 environment: LIBRETIME_CONFIG_FILEPATH: /etc/libretime/config.yml @@ -148,3 +161,8 @@ volumes: driver: local libretime_playout: driver: local + + # Volumen para la configuración generada por el panel + libretime_config: + driver: local + diff --git a/easypanel/README.md b/easypanel/README.md new file mode 100644 index 000000000..f8b50a5b5 --- /dev/null +++ b/easypanel/README.md @@ -0,0 +1,19 @@ +# LibreTime (EasyPanel) + +Este directorio contiene metadatos y un script de actualización en el estilo que usa EasyPanel para instalar un servicio desde un repositorio. + +Qué hace: +- `update.sh` copia `docker-compose.easypanel.yml` a `./code/docker-compose.yml` y elimina `container_name` y `ports` para que el `docker-compose` resultante sea compatible con EasyPanel. + +Cómo funciona (resumen): +- EasyPanel suele clonar el repo en una carpeta temporal, ejecutar `update.sh` y luego usar `./code/docker-compose.yml` como el `compose` a ejecutar. + +Requisitos/Notas: +- `update.sh` usa utilidades estándar de shell y `perl` (normalmente disponible en sistemas UNIX). Si tu entorno no tiene `perl`, el script puede adaptarse a `python`. +- Revisa y completa las variables de entorno requeridas por `docker-compose.easypanel.yml` (p. ej. secretos: `POSTGRES_PASSWORD`, `RABBITMQ_DEFAULT_PASS`, etc.) desde la UI de EasyPanel. + +Siguientes pasos recomendados: +- (Opcional) Añadir un `update.js` si prefieres implementar clonación/actualizaciones con las utilidades de EasyPanel (no obligatorio). + - (Opcional) Añadir un `update.js` si prefieres implementar clonación/actualizaciones con las utilidades de EasyPanel (no obligatorio). Este repo incluye `easypanel/update.js` que realiza la misma función que `update.sh` usando Node.js. +- (Opcional) Añadir un archivo `README.md` con la lista de variables que EasyPanel debe exponer en la interfaz de instalación. + diff --git a/easypanel/code/docker-compose.yml b/easypanel/code/docker-compose.yml new file mode 100644 index 000000000..7bd2c005f --- /dev/null +++ b/easypanel/code/docker-compose.yml @@ -0,0 +1,199 @@ +version: "3.9" + +# LibreTime Docker Compose (EasyPanel-ready) +# Esta versión está pensada para usarse desde un repositorio en EasyPanel. +# Principios: +# - No almacenar secretos en archivos del repo: EasyPanel proporcionará los valores +# mediante variables/secretos de entorno. +# - Un servicio `config-generator` genera `/config/config.yml` dentro del volumen +# `libretime_config` a partir de variables de entorno. Los servicios montan ese +# volumen en `/etc/libretime`. + +services: + # Base de datos PostgreSQL + postgres: + image: postgres:15 + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER:-libretime} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-libretime} + POSTGRES_DB: ${POSTGRES_DB:-libretime} + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-libretime}"] + interval: 30s + timeout: 10s + retries: 3 + + # Message broker RabbitMQ + rabbitmq: + image: rabbitmq:3.13-alpine + restart: unless-stopped + environment: + RABBITMQ_DEFAULT_VHOST: ${RABBITMQ_DEFAULT_VHOST:-/libretime} + RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER:-libretime} + RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS} + healthcheck: + test: ["CMD-SHELL", "rabbitmq-diagnostics check_port_connectivity"] + interval: 30s + timeout: 10s + retries: 3 + + # API de LibreTime + api: + image: ghcr.io/libretime/libretime-api:${LIBRETIME_VERSION:-4.5} + restart: unless-stopped + init: true + ulimits: + nofile: 1024 + depends_on: + postgres: + condition: service_healthy + rabbitmq: + condition: service_healthy + config-generator: + condition: service_healthy + environment: + LIBRETIME_GENERAL_PUBLIC_URL: ${LIBRETIME_GENERAL_PUBLIC_URL:-http://localhost:8080} + LIBRETIME_DEBUG: ${LIBRETIME_DEBUG:-false} + volumes: + - libretime_config:/etc/libretime:ro + - libretime_storage:/srv/libretime + + # Aplicación Legacy de LibreTime + legacy: + image: ghcr.io/libretime/libretime-legacy:${LIBRETIME_VERSION:-4.5} + restart: unless-stopped + init: true + ulimits: + nofile: 1024 + depends_on: + postgres: + condition: service_healthy + rabbitmq: + condition: service_healthy + config-generator: + condition: service_healthy + volumes: + - libretime_config:/etc/libretime:ro + - libretime_storage:/srv/libretime + + # Servidor web Nginx + nginx: + image: ghcr.io/libretime/libretime-nginx:${LIBRETIME_VERSION:-4.5} + restart: unless-stopped + depends_on: + - legacy + - api + volumes: + - libretime_storage:/srv/libretime:ro + - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro + environment: + - NGINX_WORKER_PROCESSES=${NGINX_WORKER_PROCESSES:-auto} + + # Servidor de streaming Icecast + icecast: + image: ghcr.io/libretime/icecast:2.4.4 + restart: unless-stopped + environment: + ICECAST_SOURCE_PASSWORD: ${ICECAST_SOURCE_PASSWORD:-hackme} + ICECAST_ADMIN_PASSWORD: ${ICECAST_ADMIN_PASSWORD:-hackme} + ICECAST_RELAY_PASSWORD: ${ICECAST_RELAY_PASSWORD:-hackme} + ICECAST_ADMIN_USER: ${ICECAST_ADMIN_USER:-admin} + ICECAST_HOSTNAME: ${ICECAST_HOSTNAME:-localhost} + + # Servicio de playout + playout: + image: ghcr.io/libretime/libretime-playout:${LIBRETIME_VERSION:-4.5} + restart: unless-stopped + init: true + ulimits: + nofile: 1024 + depends_on: + rabbitmq: + condition: service_healthy + volumes: + - libretime_config:/etc/libretime:ro + - libretime_playout:/app + environment: + LIBRETIME_GENERAL_PUBLIC_URL: ${LIBRETIME_GENERAL_PUBLIC_URL:-http://localhost:8080} + + # Liquidsoap para streaming + liquidsoap: + image: ghcr.io/libretime/libretime-playout:${LIBRETIME_VERSION:-4.5} + command: /usr/local/bin/libretime-liquidsoap + restart: unless-stopped + init: true + ulimits: + nofile: 1024 + depends_on: + rabbitmq: + condition: service_healthy + volumes: + - libretime_config:/etc/libretime:ro + - libretime_playout:/app + environment: + LIBRETIME_GENERAL_PUBLIC_URL: ${LIBRETIME_GENERAL_PUBLIC_URL:-http://localhost:8080} + + # Worker para tareas en background + worker: + image: ghcr.io/libretime/libretime-worker:${LIBRETIME_VERSION:-4.5} + restart: unless-stopped + init: true + ulimits: + nofile: 1024 + depends_on: + rabbitmq: + condition: service_healthy + volumes: + - libretime_config:/etc/libretime:ro + - libretime_storage:/srv/libretime + environment: + LIBRETIME_GENERAL_PUBLIC_URL: ${LIBRETIME_GENERAL_PUBLIC_URL:-http://localhost:8080} + + # Analyzer para análisis de archivos de audio + analyzer: + image: ghcr.io/libretime/libretime-analyzer:${LIBRETIME_VERSION:-4.5} + restart: unless-stopped + init: true + ulimits: + nofile: 1024 + depends_on: + rabbitmq: + condition: service_healthy + volumes: + - libretime_config:/etc/libretime:ro + - libretime_storage:/srv/libretime + + # (No se incluye servicio composer; seguir método Docker estándar de LibreTime) + + # Generador de configuración (escrito por el panel desde variables/secretos) + config-generator: + image: alpine:3.18 + restart: "no" + entrypoint: ["/bin/sh","/tools/easypanel-config-generator.sh"] + volumes: + - libretime_config:/config + - ./tools:/tools:ro + healthcheck: + test: ["CMD-SHELL","test -f /config/config.yml"] + interval: 2s + timeout: 2s + retries: 10 + +# Volúmenes persistentes +volumes: + postgres_data: + driver: local + libretime_storage: + driver: local + libretime_playout: + driver: local + libretime_config: + driver: local + +# Red personalizada (opcional para EasyPanel) +networks: + default: + name: libretime_network \ No newline at end of file diff --git a/easypanel/easypanel.json b/easypanel/easypanel.json new file mode 100644 index 000000000..0d78ecf62 --- /dev/null +++ b/easypanel/easypanel.json @@ -0,0 +1,123 @@ +{ + "name": "LibreTime", + "slug": "libretime", + "version": "1.0.0", + "description": "LibreTime: sistema de automatización y streaming de radio. Servicio preparado para EasyPanel mediante docker-compose.", + "author": "LibreTime contributors", + "repository": { + "type": "git", + "url": "https://github.com/your-org/your-repo" + }, + "update_script": "easypanel/update.js", + "compose_path": "easypanel/code/docker-compose.yml", + "services": { + "notes": "El archivo compose generado tendrá los servicios principales: postgres, rabbitmq, api, legacy, nginx, icecast, playout, liquidsoap, worker, analyzer y config-generator." + }, + "env": [ + { + "name": "POSTGRES_USER", + "description": "Usuario de la base de datos Postgres", + "required": false, + "secret": false, + "default": "libretime" + }, + { + "name": "POSTGRES_PASSWORD", + "description": "Contraseña de Postgres (requerida)", + "required": true, + "secret": true + }, + { + "name": "POSTGRES_DB", + "description": "Nombre de la base de datos", + "required": false, + "secret": false, + "default": "libretime" + }, + { + "name": "RABBITMQ_DEFAULT_VHOST", + "description": "VHost por defecto para RabbitMQ", + "required": false, + "secret": false, + "default": "/libretime" + }, + { + "name": "RABBITMQ_DEFAULT_USER", + "description": "Usuario por defecto para RabbitMQ", + "required": false, + "secret": false, + "default": "libretime" + }, + { + "name": "RABBITMQ_DEFAULT_PASS", + "description": "Contraseña de RabbitMQ (requerida)", + "required": true, + "secret": true + }, + { + "name": "LIBRETIME_VERSION", + "description": "Versión de las imágenes de LibreTime", + "required": false, + "secret": false, + "default": "4.5" + }, + { + "name": "LIBRETIME_GENERAL_PUBLIC_URL", + "description": "URL pública de la instalación", + "required": false, + "secret": false, + "default": "http://localhost:8080" + }, + { + "name": "LIBRETIME_DEBUG", + "description": "Habilitar modo debug (true/false)", + "required": false, + "secret": false, + "default": "false" + }, + { + "name": "NGINX_PORT", + "description": "Puerto HTTP expuesto por Nginx (opcional si EasyPanel maneja el routing)", + "required": false, + "secret": false, + "default": "8080" + }, + { + "name": "ICECAST_PORT", + "description": "Puerto de Icecast", + "required": false, + "secret": false, + "default": "8000" + }, + { + "name": "ICECAST_SOURCE_PASSWORD", + "description": "Contraseña de source para Icecast", + "required": false, + "secret": true, + "default": "hackme" + }, + { + "name": "ICECAST_ADMIN_PASSWORD", + "description": "Contraseña de admin para Icecast", + "required": false, + "secret": true, + "default": "hackme" + }, + { + "name": "ICECAST_RELAY_PASSWORD", + "description": "Contraseña de relay para Icecast", + "required": false, + "secret": true, + "default": "hackme" + }, + { + "name": "ICECAST_ADMIN_USER", + "description": "Usuario admin Icecast", + "required": false, + "secret": false, + "default": "admin" + } + ], + "notes": "Después de clonar, EasyPanel debe ejecutar el `update_script` y usar el compose generado en `compose_path`. Asegúrate de proveer los secretos marcados como `secret: true` en la UI de EasyPanel.", + "last_updated": "2025-10-01" +} diff --git a/easypanel/update.js b/easypanel/update.js new file mode 100755 index 000000000..11c6ed2e1 --- /dev/null +++ b/easypanel/update.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +"use strict"; + +// update.js - script de utilidad para EasyPanel +// Copia docker-compose.easypanel.yml a ./code/docker-compose.yml y elimina +// container_name y ports para que EasyPanel gestione nombres y puertos. + +const fs = require('fs'); +const path = require('path'); + +const SRC = path.resolve(__dirname, '../docker-compose.easypanel.yml'); +const DEST_DIR = path.resolve(__dirname, './code'); +const DEST = path.join(DEST_DIR, 'docker-compose.yml'); + +function removeContainerNamesAndPorts(content) { + // Eliminar container_name: líneas + content = content.replace(/^[ \t]*container_name:[^\n]*\n/gm, ''); + // Eliminar bloques de ports: + // ports:\n - "..."\n - ... + content = content.replace(/^[ \t]*ports:[^\n]*\n(?:^[ \t]+-.*\n)*/gim, ''); + return content; +} + +async function main() { + if (!fs.existsSync(SRC)) { + console.error('ERROR: no se encuentra', SRC); + process.exit(2); + } + + if (!fs.existsSync(DEST_DIR)) fs.mkdirSync(DEST_DIR, { recursive: true }); + + const raw = fs.readFileSync(SRC, 'utf8'); + const sanitized = removeContainerNamesAndPorts(raw); + + fs.writeFileSync(DEST, sanitized, 'utf8'); + console.log('Preparado', DEST); +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/easypanel/update.sh b/easypanel/update.sh new file mode 100755 index 000000000..6e7bbfbc6 --- /dev/null +++ b/easypanel/update.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Este script prepara el compose para EasyPanel. +# Si Node.js está disponible y existe easypanel/update.js, lo ejecuta. +# De lo contrario, realiza la copia y saneado en bash/perl (fallback). + +BASE_DIR="$(cd "$(dirname "$0")" && pwd)" +SRC="$BASE_DIR/../docker-compose.easypanel.yml" +DEST_DIR="$BASE_DIR/code" +DEST="$DEST_DIR/docker-compose.yml" + +if command -v node >/dev/null 2>&1 && [ -f "$BASE_DIR/update.js" ]; then + echo "Node.js detectado, ejecutando easypanel/update.js" + # Ejecutar con node (permitirá usar la versión JS en lugar del sed/perl) + node "$BASE_DIR/update.js" + echo "Preparado $DEST para EasyPanel (vía update.js)." + exit 0 +fi + +echo "Node.js no disponible o easypanel/update.js no encontrado; usando fallback shell copy" + +mkdir -p "$DEST_DIR" + +if [ ! -f "$SRC" ]; then + echo "ERROR: no se encuentra $SRC" + exit 2 +fi + +# Copiar +cp "$SRC" "$DEST" + +# Eliminar container_name y ports keys (simplemente eliminamos las líneas que contienen 'container_name:' o 'ports:') +# Esto es similar a lo que hacen muchos ejemplos de EasyPanel. +if command -v perl >/dev/null 2>&1; then + perl -0777 -pe "s/^[ \t]*container_name:[^\n]*\n//mg" -i "$DEST" + perl -0777 -pe "s/^[ \t]*ports:[^\n]*\n(?:^[ \t]+-.*\n)*//mg" -i "$DEST" +else + # Fallback con awk/sed si perl no está disponible + # Elimina líneas 'container_name:' y bloques 'ports:' simples + sed -E '/^[ \t]*container_name:/d' -i "" "$DEST" 2>/dev/null || sed -E '/^[ \t]*container_name:/d' -i '$DEST' + awk 'BEGIN{skip=0} /^[ \t]*ports:[ \t]*$/ {skip=1; next} /^[ \t]*[^ \t]/ { if(skip){skip=0} } { if(!skip) print $0 }' "$DEST" > "$DEST.tmp" && mv "$DEST.tmp" "$DEST" +fi + +echo "Preparado $DEST para EasyPanel. Revisa variables de entorno en el README y súbelas en la UI de EasyPanel." diff --git a/easypanel/vars.md b/easypanel/vars.md new file mode 100644 index 000000000..069580bde --- /dev/null +++ b/easypanel/vars.md @@ -0,0 +1,35 @@ +# Variables de entorno / Secretos requeridos para EasyPanel + +Lista de variables que el `docker-compose.easypanel.yml` usa y que deberías exponer desde la UI de EasyPanel (Secrets/Environment): + +- POSTGRES_USER (opcional, default: libretime) +- POSTGRES_PASSWORD (requerido) +- POSTGRES_DB (opcional, default: libretime) + +- RABBITMQ_DEFAULT_VHOST (opcional, default: /libretime) +- RABBITMQ_DEFAULT_USER (opcional, default: libretime) +- RABBITMQ_DEFAULT_PASS (requerido) + +- LIBRETIME_VERSION (opcional, default: 4.5) +- LIBRETIME_GENERAL_PUBLIC_URL (opcional, default: http://localhost:8080) +- LIBRETIME_DEBUG (opcional, default: false) + +- NGINX_PORT (opcional, default: 8080) <-- Nota: EasyPanel típicamente gestiona mapeos de puertos; puedes dejarlo o permitir que EasyPanel lo reescriba. +- ICECAST_PORT (opcional, default: 8000) +- LIQUIDSOAP_HARBOR_PORT (opcional, default: 8001) +- LIQUIDSOAP_TELNET_PORT (opcional, default: 8002) + +- ICECAST_SOURCE_PASSWORD (opcional, default: hackme) +- ICECAST_ADMIN_PASSWORD (opcional, default: hackme) +- ICECAST_RELAY_PASSWORD (opcional, default: hackme) +- ICECAST_ADMIN_USER (opcional, default: admin) +- ICECAST_HOSTNAME (opcional, default: localhost) + +- NGINX_WORKER_PROCESSES (opcional, default: auto) + +Notas: +- Marca `POSTGRES_PASSWORD` y `RABBITMQ_DEFAULT_PASS` como secretos en EasyPanel. +- El `config-generator` espera que estas variables existan para generar `/config/config.yml`. +- Si quieres que EasyPanel maneje el mapeo de puertos, deja `ports` en el `docker-compose.easypanel.yml` y el `update.sh` los eliminará cuando prepare `./code/docker-compose.yml` para la plataforma (esto sigue el patrón del repositorio oficial de EasyPanel). + +Sugerencia: revisa los valores por defecto y decide qué variables quieres obligatorias en la UI de instalación. diff --git a/scripts/docker-local-up.sh b/scripts/docker-local-up.sh old mode 100644 new mode 100755 diff --git a/start.sh b/start.sh index cec89583c..0400c88d2 100755 --- a/start.sh +++ b/start.sh @@ -26,9 +26,8 @@ if [ -n "${POSTGRES_PASSWORD:-}" ]; then # Hacer copia de seguridad si existe if [ -f ./config.local.yml ]; then ts=$(date +%Y%m%d%H%M%S) - mkdir -p ./backups - cp ./config.local.yml ./backups/config.local.yml.bak.$ts - echo "Backup creado: ./backups/config.local.yml.bak.$ts" + cp ./config.local.yml ./config.local.yml.bak.$ts + echo "Backup creado: ./config.local.yml.bak.$ts" fi awk -v pw="$POSTGRES_PASSWORD" ' diff --git a/tmp_config/config.yml b/tmp_config/config.yml new file mode 100644 index 000000000..cc3b59600 --- /dev/null +++ b/tmp_config/config.yml @@ -0,0 +1,25 @@ +general: + public_url: "https://libretime.example.com" + api_key: "" + secret_key: "" + +database: + host: postgres + port: 5432 + name: libretime + user: libretime + password: "secretpass" + +rabbitmq: + host: rabbitmq + port: 5672 + vhost: /libretime + user: libretime + password: "rabbitpass" + +icecast: + source_password: "srcpass" + admin_password: "adminpass" + relay_password: "changeme" + admin_user: "admin" + hostname: "localhost" diff --git a/tools/easypanel-config-generator.sh b/tools/easypanel-config-generator.sh index 1e6d6de99..9c092d7d9 100644 --- a/tools/easypanel-config-generator.sh +++ b/tools/easypanel-config-generator.sh @@ -1,51 +1,32 @@ #!/bin/sh # Generador de config para EasyPanel -# Lee variables de entorno y escribe /config/config.yml +# Lee variables de entorno y escribe /config/config.yml de forma atómica set -eu CONFIG_PATH=/config/config.yml +TMP_PATH=/config/config.yml.tmp -cat > "$CONFIG_PATH" <<'EOF' +# Generar usando heredoc sin comillas para permitir expansión de variables +cat > "$TMP_PATH" < "$CONFIG_PATH" <