From d600eeb321ad52a697b03fca6652aaf3ea0e4eab Mon Sep 17 00:00:00 2001 From: Cesar Jhoanny Mendivil Rubio Date: Wed, 1 Oct 2025 14:44:28 -0700 Subject: [PATCH] chore: remove EasyPanel template artifacts --- .env.example | 22 +++ README_SIMPLE.md | 84 ++++++++++ backups/config.local.yml.bak.20251001110140 | 24 +++ config.local.yml | 24 +++ config.local.yml.bak.20251001102251 | 25 +++ docker-compose.easypanel.yml | 21 +-- docker-compose.simple.yml | 150 +++++++++++++++++ docker-compose.yml | 14 +- docker/nginx/default.conf | 83 ++++++++++ easypanel-libretime-secure.zip | Bin 0 -> 2077 bytes easypanel-libretime-template.zip | Bin 0 -> 5858 bytes .../docker-compose.yml | 53 ++++++ .../docker/nginx/default.conf | 72 +++++++++ easypanel-template-libretime-secure/index.ts | 104 ++++++++++++ easypanel-template-libretime-secure/meta.yaml | 28 ++++ .../README.easypanel.md | 15 ++ .../docker-compose.yml | 151 ++++++++++++++++++ .../docker/nginx/default.conf | 72 +++++++++ .../docker_nginx_default.conf | 73 +++++++++ easypanel-template-libretime/index.ts | 38 +++++ easypanel-template-libretime/logo.png | 1 + easypanel-template-libretime/meta.ts | 2 + easypanel-template-libretime/meta.yaml | 30 ++++ easypanel-template-libretime/screenshot.png | 1 + easypanel-template-secure.json | 25 +++ easypanel-template.json | 50 ++++++ generate-keys.sh | 56 +++++++ scripts/generate-config.sh | 44 +++++ scripts/generate_easypanel_json.js | 60 +++++++ start.sh | 76 +++++++++ tools/restore-config.sh | 102 ++++++++++++ 31 files changed, 1484 insertions(+), 16 deletions(-) create mode 100644 .env.example create mode 100644 README_SIMPLE.md create mode 100644 backups/config.local.yml.bak.20251001110140 create mode 100644 config.local.yml create mode 100644 config.local.yml.bak.20251001102251 create mode 100644 docker-compose.simple.yml create mode 100644 docker/nginx/default.conf create mode 100644 easypanel-libretime-secure.zip create mode 100644 easypanel-libretime-template.zip create mode 100644 easypanel-template-libretime-secure/docker-compose.yml create mode 100644 easypanel-template-libretime-secure/docker/nginx/default.conf create mode 100644 easypanel-template-libretime-secure/index.ts create mode 100644 easypanel-template-libretime-secure/meta.yaml create mode 100644 easypanel-template-libretime/README.easypanel.md create mode 100644 easypanel-template-libretime/docker-compose.yml create mode 100644 easypanel-template-libretime/docker/nginx/default.conf create mode 100644 easypanel-template-libretime/docker_nginx_default.conf create mode 100644 easypanel-template-libretime/index.ts create mode 100644 easypanel-template-libretime/logo.png create mode 100644 easypanel-template-libretime/meta.ts create mode 100644 easypanel-template-libretime/meta.yaml create mode 100644 easypanel-template-libretime/screenshot.png create mode 100644 easypanel-template-secure.json create mode 100644 easypanel-template.json create mode 100644 generate-keys.sh create mode 100755 scripts/generate-config.sh create mode 100644 scripts/generate_easypanel_json.js create mode 100755 start.sh create mode 100644 tools/restore-config.sh diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..8fd52a579 --- /dev/null +++ b/.env.example @@ -0,0 +1,22 @@ +POSTGRES_USER=libretime +POSTGRES_PASSWORD=libretime +POSTGRES_DB=libretime + +RABBITMQ_DEFAULT_VHOST=/libretime +RABBITMQ_DEFAULT_USER=libretime +RABBITMQ_DEFAULT_PASS=libretime + +LIBRETIME_VERSION=4.5 +LIBRETIME_GENERAL_PUBLIC_URL=http://localhost:8080 + +# Claves para la aplicación (se generan automáticamente si no se proveen) +LIBRETIME_API_KEY= +LIBRETIME_SECRET_KEY= + +NGINX_PORT=8080 +ICECAST_PORT=8000 + +# Icecast passwords (cambiar en producción) +ICECAST_SOURCE_PASSWORD=changeme +ICECAST_ADMIN_PASSWORD=changeme +ICECAST_RELAY_PASSWORD=changeme diff --git a/README_SIMPLE.md b/README_SIMPLE.md new file mode 100644 index 000000000..a4de1e59d --- /dev/null +++ b/README_SIMPLE.md @@ -0,0 +1,84 @@ +LibreTime - Instalación Docker sencilla + +Objetivo +-------- +Proveer un conjunto mínimo de ficheros para que un usuario final pueda levantar LibreTime con Docker Compose sin tocar demasiadas cosas. + +Archivos añadidos +- `docker-compose.simple.yml` - Compose reducido y comentado. +- `.env.example` - Ejemplo de variables de entorno. +- `scripts/generate-config.sh` - Genera `config.local.yml` desde variables de entorno. +- `start.sh` - Flujo recomendado: cargar .env, generar config, iniciar infra y aplicar migraciones. + +Cómo usar +--------- +1) Copiar `.env.example` a `.env` y editar las contraseñas si lo desea. + +2) Generar `config.local.yml` (opcional — `start.sh` lo hace): + +```sh +cp .env.example .env +sh ./scripts/generate-config.sh ./config.local.yml +``` + +3) Ejecutar el flujo automático: + +```sh +sh ./start.sh +``` + +Esto levantará Postgres y RabbitMQ, aplicará las migraciones (necesario la primera vez) y luego arrancará el resto de servicios. El sitio estará disponible en `http://localhost:8080` por defecto. + + +Notas y recomendaciones + +- Para producción cambie todas las contraseñas por valores fuertes en `.env`. +- Si ya existe un volumen de Postgres creado con otra contraseña, borre el volumen antes de usar las nuevas credenciales: `docker compose -f docker-compose.simple.yml down -v`. +- Puede ejecutar migraciones manualmente con: + +```sh +docker compose -f docker-compose.simple.yml run --rm migrate +``` + +Corregir desajuste de contraseña (opción A) +----------------------------------------- +Si al hacer peticiones la API devuelve 500 por errores de conexión a la base de datos, normalmente significa que la contraseña de Postgres en `.env` y la que está escrita en `config.local.yml` no coinciden. Para arreglarlo manualmente: + +1. Edita `.env` y establece `POSTGRES_PASSWORD` con la contraseña que quieras usar para Postgres (ejemplo seguro). + +2. Regenera `config.local.yml` a partir de `.env` (esto sincroniza la contraseña usada por la app con la de Postgres): + +```sh +cp .env.example .env # si aún no tienes .env +sh ./scripts/generate-config.sh ./config.local.yml +``` + +3. Reinicia el servicio API para que recargue el archivo de configuración montado: + +```sh +docker compose -f docker-compose.simple.yml restart api +``` + +4. Prueba internamente en el contenedor API y desde Nginx: + +```sh +docker exec -it $(docker compose -f docker-compose.simple.yml ps -q api) \ + curl -sS http://127.0.0.1:9001/api/v2/info + +curl -sS http://localhost:8080/api/v2/info +``` + +Si ambas llamadas devuelven HTTP 200 con JSON (por ejemplo {"station_name":"LibreTime"}) entonces el desajuste está resuelto. + +Si persiste el problema, considera eliminar el volumen de Postgres (perderás datos de desarrollo) y volver a inicializar: + +```sh +docker compose -f docker-compose.simple.yml down -v +sh ./start.sh +``` + +Nota sobre `start.sh` +--------------------- +`start.sh` genera `config.local.yml` desde las variables de entorno y, si detecta que `POSTGRES_PASSWORD` está definido en `.env`, actualizará automáticamente la entrada `database.password` dentro de `config.local.yml` para que coincida. Esto evita desajustes entre la contraseña usada por el contenedor Postgres y la que usa la aplicación. Si prefieres no sobrescribir `config.local.yml`, edita manualmente `.env` y/o `config.local.yml` antes de ejecutar `start.sh`. + +Antes de sobrescribir `config.local.yml`, `start.sh` crea una copia de seguridad con prefijo `config.local.yml.bak.YYYYMMDDHHMMSS` en la raíz del proyecto. diff --git a/backups/config.local.yml.bak.20251001110140 b/backups/config.local.yml.bak.20251001110140 new file mode 100644 index 000000000..72f744916 --- /dev/null +++ b/backups/config.local.yml.bak.20251001110140 @@ -0,0 +1,24 @@ +general: + public_url: "http://localhost:8080" + api_key: "bKUFZWDmjXUdHWcJpHbOdSqeXyqwsYwtRWmcnqBw" + secret_key: "ZlHiWa87oPXvWlHfF9w9AH0WATtGJXONmwv4RVPn" + +database: + name: "libretime" + user: "libretime" + password: "libretime" + host: "postgres" + port: 5432 + +rabbitmq: + host: "rabbitmq" + username: "libretime" + password: "libretime" + vhost: "/libretime" + +icecast: + source_password: "changeme" + admin_password: "changeme" + relay_password: "changeme" + admin_user: "admin" + hostname: "localhost" diff --git a/config.local.yml b/config.local.yml new file mode 100644 index 000000000..4c7755c36 --- /dev/null +++ b/config.local.yml @@ -0,0 +1,24 @@ +general: + public_url: "http://localhost:8080" + api_key: "dJA8shP8Epyx5TX5Ye4EYxCJ2RaGCBP2Dl8lcv6T" + secret_key: "PnEuyp3Ibko6O6tAArchoEHR7SNxH5XvFrp6xi9x" + +database: + name: "libretime" + user: "libretime" + password: "libretime" + host: "postgres" + port: 5432 + +rabbitmq: + host: "rabbitmq" + username: "libretime" + password: "libretime" + vhost: "/libretime" + +icecast: + source_password: "changeme" + admin_password: "changeme" + relay_password: "changeme" + admin_user: "admin" + hostname: "localhost" diff --git a/config.local.yml.bak.20251001102251 b/config.local.yml.bak.20251001102251 new file mode 100644 index 000000000..1f7cb17cd --- /dev/null +++ b/config.local.yml.bak.20251001102251 @@ -0,0 +1,25 @@ +general: + public_url: "http://localhost:8080" + api_key: "dJA8shP8Epyx5TX5Ye4EYxCJ2RaGCBP2Dl8lcv6T" + secret_key: "PnEuyp3Ibko6O6tAArchoEHR7SNxH5XvFrp6xi9x" + +database: + engine: postgres + name: "libretime" + user: "libretime" + password: "libretime" + host: "postgres" + port: 5432 + +rabbitmq: + host: "rabbitmq" + username: "libretime" + password: "libretime" + vhost: "/libretime" + +icecast: + source_password: "changeme" + admin_password: "changeme" + relay_password: "changeme" + admin_user: "admin" + hostname: "localhost" diff --git a/docker-compose.easypanel.yml b/docker-compose.easypanel.yml index ac790babd..34f4800c3 100644 --- a/docker-compose.easypanel.yml +++ b/docker-compose.easypanel.yml @@ -35,7 +35,7 @@ services: # API de LibreTime api: - image: ghcr.io/libretime/libretime-api:${LIBRETIME_VERSION:-latest} + image: ghcr.io/libretime/libretime-api:${LIBRETIME_VERSION:-4.5} restart: unless-stopped init: true ulimits: @@ -54,7 +54,7 @@ services: # Aplicación Legacy de LibreTime legacy: - image: ghcr.io/libretime/libretime-legacy:${LIBRETIME_VERSION:-latest} + image: ghcr.io/libretime/libretime-legacy:${LIBRETIME_VERSION:-4.5} restart: unless-stopped init: true ulimits: @@ -70,7 +70,7 @@ services: # Servidor web Nginx nginx: - image: ghcr.io/libretime/libretime-nginx:${LIBRETIME_VERSION:-latest} + image: ghcr.io/libretime/libretime-nginx:${LIBRETIME_VERSION:-4.5} restart: unless-stopped depends_on: - legacy @@ -79,6 +79,7 @@ services: - "${NGINX_PORT:-8080}:8080" 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} @@ -97,7 +98,7 @@ services: # Servicio de playout playout: - image: ghcr.io/libretime/libretime-playout:${LIBRETIME_VERSION:-latest} + image: ghcr.io/libretime/libretime-playout:${LIBRETIME_VERSION:-4.5} restart: unless-stopped init: true ulimits: @@ -113,7 +114,7 @@ services: # Liquidsoap para streaming liquidsoap: - image: ghcr.io/libretime/libretime-playout:${LIBRETIME_VERSION:-latest} + image: ghcr.io/libretime/libretime-playout:${LIBRETIME_VERSION:-4.5} command: /usr/local/bin/libretime-liquidsoap restart: unless-stopped init: true @@ -133,7 +134,7 @@ services: # Worker para tareas en background worker: - image: ghcr.io/libretime/libretime-worker:${LIBRETIME_VERSION:-latest} + image: ghcr.io/libretime/libretime-worker:${LIBRETIME_VERSION:-4.5} restart: unless-stopped init: true ulimits: @@ -142,14 +143,14 @@ services: rabbitmq: condition: service_healthy volumes: - - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro + - ./config.local.yml:/etc/libretime/config.yml: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:-latest} + image: ghcr.io/libretime/libretime-analyzer:${LIBRETIME_VERSION:-4.5} restart: unless-stopped init: true ulimits: @@ -158,9 +159,11 @@ services: rabbitmq: condition: service_healthy volumes: - - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro + - ./config.local.yml:/etc/libretime/config.yml:ro - libretime_storage:/srv/libretime + # (No se incluye servicio composer; seguir método Docker estándar de LibreTime) + # Volúmenes persistentes volumes: postgres_data: diff --git a/docker-compose.simple.yml b/docker-compose.simple.yml new file mode 100644 index 000000000..1ef276d17 --- /dev/null +++ b/docker-compose.simple.yml @@ -0,0 +1,150 @@ +services: + 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: 5s + timeout: 5s + retries: 12 + + 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:-libretime} + healthcheck: + test: ["CMD-SHELL", "rabbitmq-diagnostics status"] + interval: 5s + timeout: 5s + retries: 12 + + # Servicio que aplica migraciones (uno-por-vez). Ejecutar como "docker compose run --rm migrate" o usar start.sh + migrate: + image: ghcr.io/libretime/libretime-api:${LIBRETIME_VERSION:-4.5} + entrypoint: ["/bin/sh", "-c"] + # start.sh ahora espera a Postgres/rabbit; aquí solo aplicamos migraciones + command: "python3 /src/libretime_api/manage.py migrate --noinput" + depends_on: + - postgres + - rabbitmq + environment: + POSTGRES_USER: ${POSTGRES_USER:-libretime} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-libretime} + POSTGRES_DB: ${POSTGRES_DB:-libretime} + RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER:-libretime} + RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS:-libretime} + + api: + image: ghcr.io/libretime/libretime-api:${LIBRETIME_VERSION:-4.5} + restart: unless-stopped + depends_on: + - 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_storage:/srv/libretime + + legacy: + image: ghcr.io/libretime/libretime-legacy:${LIBRETIME_VERSION:-4.5} + restart: unless-stopped + depends_on: + - postgres + - rabbitmq + environment: + LIBRETIME_CONFIG_FILEPATH: /etc/libretime/config.yml + volumes: + - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro + - libretime_storage:/srv/libretime + + nginx: + image: ghcr.io/libretime/libretime-nginx:${LIBRETIME_VERSION:-4.5} + restart: unless-stopped + depends_on: + - legacy + - api + ports: + - "${NGINX_PORT:-8080}:8080" + volumes: + - libretime_storage:/srv/libretime:ro + - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro + + icecast: + image: ghcr.io/libretime/icecast:2.4.4 + restart: unless-stopped + ports: + - "${ICECAST_PORT:-8000}:8000" + 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} + + playout: + image: ghcr.io/libretime/libretime-playout:${LIBRETIME_VERSION:-4.5} + restart: unless-stopped + depends_on: + - rabbitmq + volumes: + - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro + - libretime_playout:/app + environment: + LIBRETIME_CONFIG_FILEPATH: /etc/libretime/config.yml + LIBRETIME_GENERAL_PUBLIC_URL: ${LIBRETIME_GENERAL_PUBLIC_URL:-http://localhost:8080} + + liquidsoap: + image: ghcr.io/libretime/libretime-playout:${LIBRETIME_VERSION:-4.5} + command: /usr/local/bin/libretime-liquidsoap + restart: unless-stopped + depends_on: + - rabbitmq + ports: + - "${LIQUIDSOAP_HARBOR_PORT:-8001}:8001" + - "${LIQUIDSOAP_TELNET_PORT:-8002}:8002" + volumes: + - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro + - libretime_playout:/app + environment: + LIBRETIME_CONFIG_FILEPATH: /etc/libretime/config.yml + + worker: + image: ghcr.io/libretime/libretime-worker:${LIBRETIME_VERSION:-4.5} + restart: unless-stopped + depends_on: + - rabbitmq + volumes: + - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro + - libretime_storage:/srv/libretime + environment: + LIBRETIME_CONFIG_FILEPATH: /etc/libretime/config.yml + + analyzer: + image: ghcr.io/libretime/libretime-analyzer:${LIBRETIME_VERSION:-4.5} + restart: unless-stopped + depends_on: + - rabbitmq + volumes: + - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro + - libretime_storage:/srv/libretime + environment: + LIBRETIME_CONFIG_FILEPATH: /etc/libretime/config.yml + +volumes: + postgres_data: + driver: local + libretime_storage: + driver: local + libretime_playout: + driver: local diff --git a/docker-compose.yml b/docker-compose.yml index d23fb059f..7d16e4367 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,7 +19,7 @@ services: test: nc -z 127.0.0.1 5672 playout: - image: ghcr.io/libretime/libretime-playout:${LIBRETIME_VERSION:-latest} + image: ghcr.io/libretime/libretime-playout:${LIBRETIME_VERSION:-4.5} init: true ulimits: nofile: 1024 @@ -32,7 +32,7 @@ services: LIBRETIME_GENERAL_PUBLIC_URL: http://nginx:8080 liquidsoap: - image: ghcr.io/libretime/libretime-playout:${LIBRETIME_VERSION:-latest} + image: ghcr.io/libretime/libretime-playout:${LIBRETIME_VERSION:-4.5} command: /usr/local/bin/libretime-liquidsoap init: true ulimits: @@ -49,7 +49,7 @@ services: LIBRETIME_GENERAL_PUBLIC_URL: http://nginx:8080 analyzer: - image: ghcr.io/libretime/libretime-analyzer:${LIBRETIME_VERSION:-latest} + image: ghcr.io/libretime/libretime-analyzer:${LIBRETIME_VERSION:-4.5} init: true ulimits: nofile: 1024 @@ -62,7 +62,7 @@ services: LIBRETIME_GENERAL_PUBLIC_URL: http://nginx:8080 worker: - image: ghcr.io/libretime/libretime-worker:${LIBRETIME_VERSION:-latest} + image: ghcr.io/libretime/libretime-worker:${LIBRETIME_VERSION:-4.5} init: true ulimits: nofile: 1024 @@ -74,7 +74,7 @@ services: LIBRETIME_GENERAL_PUBLIC_URL: http://nginx:8080 api: - image: ghcr.io/libretime/libretime-api:${LIBRETIME_VERSION:-latest} + image: ghcr.io/libretime/libretime-api:${LIBRETIME_VERSION:-4.5} init: true ulimits: nofile: 1024 @@ -86,7 +86,7 @@ services: - libretime_storage:/srv/libretime legacy: - image: ghcr.io/libretime/libretime-legacy:${LIBRETIME_VERSION:-latest} + image: ghcr.io/libretime/libretime-legacy:${LIBRETIME_VERSION:-4.5} init: true ulimits: nofile: 1024 @@ -98,7 +98,7 @@ services: - libretime_storage:/srv/libretime nginx: - image: ghcr.io/libretime/libretime-nginx:${LIBRETIME_VERSION:-latest} + image: ghcr.io/libretime/libretime-nginx:${LIBRETIME_VERSION:-4.5} ports: - 8080:8080 depends_on: diff --git a/docker/nginx/default.conf b/docker/nginx/default.conf new file mode 100644 index 000000000..bcc21de52 --- /dev/null +++ b/docker/nginx/default.conf @@ -0,0 +1,83 @@ +server { + listen 8080; + listen [::]:8080; + + root /var/www/html/public; + + index index.php index.html index.htm; + + client_max_body_size 512M; + client_body_timeout 300s; + + location ~ \.php$ { + fastcgi_buffers 64 4K; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + + #try_files $uri =404; + try_files $fastcgi_script_name =404; + + include fastcgi_params; + + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + set $path_info $fastcgi_path_info; + fastcgi_param PATH_INFO $path_info; + include fastcgi_params; + + fastcgi_index index.php; + fastcgi_pass legacy:9000; + } + + location / { + try_files $uri $uri/ /index.php$is_args$args; + } + + # Proxy todas las rutas /api (excepto /api/_media) a la API Python + location ^~ /api/_media { + internal; + alias /srv/libretime; + } + + # Compatibilidad: interceptar /api/version antes de la copia proxy general + # Usamos ^~ para dar prioridad sobre la regla general /api/ + location ^~ /api/version { + return 307 /api/v2/version; + } + + # Variante con slash final + location ^~ /api/version/ { + return 307 /api/v2/version; + } + + # Compatibilidad: rutas legacy que deben ser manejadas por el código PHP + # En la configuración original, peticiones /api/* que no eran /api/v2 + # caían en el enrutador PHP (try_files -> /index.php). Restauramos + # ese comportamiento para endpoints concretos usados por playout. + location = /api/register-component { + try_files $uri $uri/ /index.php$is_args$args; + } + + location = /api/register-component/ { + try_files $uri $uri/ /index.php$is_args$args; + } + + # Proxy por defecto para todas las rutas /api/ (excepto las interceptadas arriba) + location /api/ { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_redirect off; + proxy_pass http://api:9001; + } + + # Soporte para peticiones antiguas específicas a /api/v2 o /api/browser + location ~ ^/api/(v2|browser) { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_redirect off; + proxy_pass http://api:9001; + } +} \ No newline at end of file diff --git a/easypanel-libretime-secure.zip b/easypanel-libretime-secure.zip new file mode 100644 index 0000000000000000000000000000000000000000..aef19e0d0594aab96f0d0e132721561f9a5fc9a9 GIT binary patch literal 2077 zcma)-c{H2p9>){GElQ~>f+!QBy+W$x(jpa2shSb7w3eDC8e1huA{A{jhSt6l)6!y8 zD8}?QE{c+7QjDF7twUWV6D4Md+G?qZcFvhOGk5OszUO(~=l$pRIlt%p&i8}40Fssm z>^{e9ZGAo*e%qh`Z2$pBp@-v0gitt@KnxGXQ3>$SU_6;X4JH!c6oP*wnPBSSE(ZW9 zamIsGBr7sX0RWU<0RjNhha`0{lIpwPeE>wAtxsyo=gDf)0Dw>eG~Q$NVA54WlnIsc zfg*Z79we5mKT%pey(m9eC^KdrkDe;TS^KLbO`L=nI{mYqU8Oljj(mE6m=N#ii0bJA zJUaVukrT*u;}1k^Vntkm-})+#e4VcBfZ~GNj1zBFYLp^WT>(PV)J_j)d!eew8}FAl z%#a$gdI-O8x$K6dE0{`LFUoMyF4h~dsK1t!l&3YNk_}X}afHrYdMePD2OU0k3Co@3 z2V&cERW@8%Joz#xCROL$`nURA>;LL?Tjo#42_Rj)iRk5jQJ6B#m}BRf-NGM+x6Pc= zmNEv?V{onyjU0forFSWLhOq-js2H7n@bW5acJH{^{^e}>+{>!`_*$Sc0!>t&x0;G= zwt$z&3@6LB(8croh4%WUfT3oL74p!>YI?7NQcy65ae< zv>+*}L_}Y;!eFMeaYIV~0QghBub>ZLT}%lW`L2G!Ox)Hgqyg6A2G#>cb*yq+uLs#P zC+ap@PJp*A^1( z_C861=dHs9|U17!j#Boz$7DJufrfaMsFPa9h5IZ9;wkCt)DGl_0P&06WJNL zTP>9rjYD{&F=L^J8?|Z5S{Z4!1wHqn5ID8`)(Ys}Qs(z}#PX+_a?TTYLu%HZi|BA0 zOpS9$=i8%!)lwX=e|*Y`Uj~sHjIB`}U#)kJ%QetS`qwwmKVIYAd;qKa&JYusgvw1j zd}2ubtG-!clehOha6aoO)48a!eBfNzF^}}TK!`zoK!||p&uh}{>!Ti@R=!3<`Wl^e z!>%F}g9D!($?Xv~%dqC+)N-+Gxu43{z@27m2B!b0sXwo)x$#-2lO5;m?Xmdc9X7E& z9+`acIUHUV^5xrx2JhJ)%xiiaW=*hZ%gfY}8Y6ow>0EWm83FzT>hd?6c$q7cFqs%m4y>#Lkz`ZF=7~e{XQH+EvkMh* zo5(nq*eu&gm0^eZB+MI>}FIC5_A$`yq!IpHG^E4Xtp%FdvKdgm>lqcQX+g)r>m|-CmI%)t-Z0 z3?SwLPznP2J9BCL<}hWz&vv`Vy?IRLqsRVamye{sv)D(HWU>IIU;6El^t;m}#9gQT oAL3t!_m9LeiB0#?+aE?*g4`YDzaV8~cP|Pgd0iykJpJ(ZE1afutpET3 literal 0 HcmV?d00001 diff --git a/easypanel-libretime-template.zip b/easypanel-libretime-template.zip new file mode 100644 index 0000000000000000000000000000000000000000..75ec0d5808c04db8e2f0c1b19286788e7c3d92e4 GIT binary patch literal 5858 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 Xu}BGVPv(boCINWj3gRl&kE?$HqZ+@B literal 0 HcmV?d00001 diff --git a/easypanel-template-libretime-secure/docker-compose.yml b/easypanel-template-libretime-secure/docker-compose.yml new file mode 100644 index 000000000..654c15d36 --- /dev/null +++ b/easypanel-template-libretime-secure/docker-compose.yml @@ -0,0 +1,53 @@ +version: '3.8' +services: + postgres: + image: postgres:15 + environment: + POSTGRES_USER: libretime + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: libretime + volumes: + - postgres_data:/var/lib/postgresql/data + + rabbitmq: + image: rabbitmq:3.13-alpine + environment: + RABBITMQ_DEFAULT_VHOST: /libretime + RABBITMQ_DEFAULT_USER: libretime + RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS} + + api: + image: ghcr.io/libretime/libretime-api:${LIBRETIME_VERSION:-4.5} + environment: + - LIBRETIME_GENERAL_PUBLIC_URL=${LIBRETIME_GENERAL_PUBLIC_URL:-http://localhost:8080} + - LIBRETIME_CONFIG_FROM_ENV=true + volumes: + - libretime_storage:/srv/libretime + depends_on: + - postgres + - rabbitmq + + legacy: + image: ghcr.io/libretime/libretime-legacy:${LIBRETIME_VERSION:-4.5} + environment: + - LIBRETIME_CONFIG_FROM_ENV=true + volumes: + - libretime_storage:/srv/libretime + depends_on: + - postgres + - rabbitmq + + nginx: + image: ghcr.io/libretime/libretime-nginx:${LIBRETIME_VERSION:-4.5} + ports: + - "8080:8080" + volumes: + - libretime_storage:/srv/libretime:ro + - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro + depends_on: + - legacy + - api + +volumes: + postgres_data: + libretime_storage: diff --git a/easypanel-template-libretime-secure/docker/nginx/default.conf b/easypanel-template-libretime-secure/docker/nginx/default.conf new file mode 100644 index 000000000..3ac3f0ed4 --- /dev/null +++ b/easypanel-template-libretime-secure/docker/nginx/default.conf @@ -0,0 +1,72 @@ +server { + listen 8080; + listen [::]:8080; + + root /var/www/html/public; + + index index.php index.html index.htm; + + client_max_body_size 512M; + client_body_timeout 300s; + + location ~ \.php$ { + fastcgi_buffers 64 4K; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + + try_files $fastcgi_script_name =404; + + include fastcgi_params; + + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + set $path_info $fastcgi_path_info; + fastcgi_param PATH_INFO $path_info; + include fastcgi_params; + + fastcgi_index index.php; + fastcgi_pass legacy:9000; + } + + location / { + try_files $uri $uri/ /index.php$is_args$args; + } + + location ^~ /api/_media { + internal; + alias /srv/libretime; + } + + location ^~ /api/version { + return 307 /api/v2/version; + } + + location ^~ /api/version/ { + return 307 /api/v2/version; + } + + location = /api/register-component { + try_files $uri $uri/ /index.php$is_args$args; + } + + location = /api/register-component/ { + try_files $uri $uri/ /index.php$is_args$args; + } + + location /api/ { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_redirect off; + proxy_pass http://api:9001; + } + + location ~ ^/api/(v2|browser) { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_redirect off; + proxy_pass http://api:9001; + } +} diff --git a/easypanel-template-libretime-secure/index.ts b/easypanel-template-libretime-secure/index.ts new file mode 100644 index 000000000..22269d87c --- /dev/null +++ b/easypanel-template-libretime-secure/index.ts @@ -0,0 +1,104 @@ +import { Template } from '@easypanel/template-sdk'; + +// This secure template generates the docker-compose and uses environment variables +// so that secrets are not stored in files inside the project. Instead, config +// is generated at container start from env vars injected by the panel. + +const t: Template = { + meta: require('./meta.yaml'), + render: (values: any) => { + const libVersion = values.libretime_version || '4.5'; + const publicUrl = values.public_url || 'http://{{hostname}}:8080'; + + const compose = `version: '3.8' +services: + postgres: + image: postgres:15 + environment: + POSTGRES_USER: libretime + POSTGRES_PASSWORD: ${values.postgres_password ? `'${values.postgres_password}'` : "\"\""} + POSTGRES_DB: libretime + volumes: + - postgres_data:/var/lib/postgresql/data + + rabbitmq: + image: rabbitmq:3.13-alpine + environment: + RABBITMQ_DEFAULT_VHOST: /libretime + RABBITMQ_DEFAULT_USER: libretime + RABBITMQ_DEFAULT_PASS: ${values.rabbitmq_password ? `'${values.rabbitmq_password}'` : "\"\""} + + api: + image: ghcr.io/libretime/libretime-api:${libVersion} + environment: + - LIBRETIME_GENERAL_PUBLIC_URL=${publicUrl} + - LIBRETIME_CONFIG_FROM_ENV=true + volumes: + - libretime_storage:/srv/libretime + depends_on: + - postgres + - rabbitmq + + # composer not included; follow standard LibreTime docker installation + + legacy: + image: ghcr.io/libretime/libretime-legacy:${libVersion} + environment: + - LIBRETIME_CONFIG_FROM_ENV=true + volumes: + - libretime_storage:/srv/libretime + depends_on: + - postgres + - rabbitmq + + nginx: + image: ghcr.io/libretime/libretime-nginx:${libVersion} + ports: + - "8080:8080" + volumes: + - libretime_storage:/srv/libretime:ro + - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro + depends_on: + - legacy + - api + +volumes: + postgres_data: + libretime_storage: +`; + + const startupSh = `#!/bin/sh +# At container start this script writes /etc/libretime/config.yml from env +cat > /etc/libretime/config.yml < "Create from JSON" y pega el JSON del template (o sube el ZIP con los archivos generados). +- Alternativamente, añade la carpeta al repositorio oficial de templates y abre un Pull Request siguiendo las pautas en https://github.com/easypanel-io/templates. + +Notas de seguridad: +- Sustituye contraseñas por secretos gestionados o variables del entorno en producción. +- No uses `latest` en imágenes en producción; fija versiones. diff --git a/easypanel-template-libretime/docker-compose.yml b/easypanel-template-libretime/docker-compose.yml new file mode 100644 index 000000000..ced04886a --- /dev/null +++ b/easypanel-template-libretime/docker-compose.yml @@ -0,0 +1,151 @@ +version: '3.8' +services: + 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 + + 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:-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 + environment: + LIBRETIME_GENERAL_PUBLIC_URL: ${LIBRETIME_GENERAL_PUBLIC_URL:-http://localhost:8080} + volumes: + - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro + - libretime_storage:/srv/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 + volumes: + - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro + - libretime_storage:/srv/libretime + + nginx: + image: ghcr.io/libretime/libretime-nginx:${LIBRETIME_VERSION:-4.5} + restart: unless-stopped + depends_on: + - legacy + - api + ports: + - "${NGINX_PORT:-8080}:8080" + 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} + + icecast: + image: ghcr.io/libretime/icecast:2.4.4 + restart: unless-stopped + ports: + - "${ICECAST_PORT:-8000}:8000" + 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} + + 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_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro + - libretime_playout:/app + environment: + LIBRETIME_GENERAL_PUBLIC_URL: ${LIBRETIME_GENERAL_PUBLIC_URL:-http://localhost:8080} + + 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 + ports: + - "${LIQUIDSOAP_HARBOR_PORT:-8001}:8001" + - "${LIQUIDSOAP_TELNET_PORT:-8002}:8002" + volumes: + - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro + - libretime_playout:/app + environment: + LIBRETIME_GENERAL_PUBLIC_URL: ${LIBRETIME_GENERAL_PUBLIC_URL:-http://localhost:8080} + + 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: + - ./config.local.yml:/etc/libretime/config.yml:ro + - libretime_storage:/srv/libretime + environment: + LIBRETIME_GENERAL_PUBLIC_URL: ${LIBRETIME_GENERAL_PUBLIC_URL:-http://localhost:8080} + + 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: + - ./config.local.yml:/etc/libretime/config.yml:ro + - libretime_storage:/srv/libretime + +volumes: + postgres_data: + driver: local + libretime_storage: + driver: local + libretime_playout: + driver: local + +networks: + default: + name: libretime_network diff --git a/easypanel-template-libretime/docker/nginx/default.conf b/easypanel-template-libretime/docker/nginx/default.conf new file mode 100644 index 000000000..3ac3f0ed4 --- /dev/null +++ b/easypanel-template-libretime/docker/nginx/default.conf @@ -0,0 +1,72 @@ +server { + listen 8080; + listen [::]:8080; + + root /var/www/html/public; + + index index.php index.html index.htm; + + client_max_body_size 512M; + client_body_timeout 300s; + + location ~ \.php$ { + fastcgi_buffers 64 4K; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + + try_files $fastcgi_script_name =404; + + include fastcgi_params; + + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + set $path_info $fastcgi_path_info; + fastcgi_param PATH_INFO $path_info; + include fastcgi_params; + + fastcgi_index index.php; + fastcgi_pass legacy:9000; + } + + location / { + try_files $uri $uri/ /index.php$is_args$args; + } + + location ^~ /api/_media { + internal; + alias /srv/libretime; + } + + location ^~ /api/version { + return 307 /api/v2/version; + } + + location ^~ /api/version/ { + return 307 /api/v2/version; + } + + location = /api/register-component { + try_files $uri $uri/ /index.php$is_args$args; + } + + location = /api/register-component/ { + try_files $uri $uri/ /index.php$is_args$args; + } + + location /api/ { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_redirect off; + proxy_pass http://api:9001; + } + + location ~ ^/api/(v2|browser) { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_redirect off; + proxy_pass http://api:9001; + } +} diff --git a/easypanel-template-libretime/docker_nginx_default.conf b/easypanel-template-libretime/docker_nginx_default.conf new file mode 100644 index 000000000..bcd2a77cf --- /dev/null +++ b/easypanel-template-libretime/docker_nginx_default.conf @@ -0,0 +1,73 @@ +server { + listen 8080; + listen [::]:8080; + + root /var/www/html/public; + + index index.php index.html index.htm; + + client_max_body_size 512M; + client_body_timeout 300s; + + location ~ \.php$ { + fastcgi_buffers 64 4K; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + + #try_files $uri =404; + try_files $fastcgi_script_name =404; + + include fastcgi_params; + + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + set $path_info $fastcgi_path_info; + fastcgi_param PATH_INFO $path_info; + include fastcgi_params; + + fastcgi_index index.php; + fastcgi_pass legacy:9000; + } + + location / { + try_files $uri $uri/ /index.php$is_args$args; + } + + location ^~ /api/_media { + internal; + alias /srv/libretime; + } + + location ^~ /api/version { + return 307 /api/v2/version; + } + + location ^~ /api/version/ { + return 307 /api/v2/version; + } + + location = /api/register-component { + try_files $uri $uri/ /index.php$is_args$args; + } + + location = /api/register-component/ { + try_files $uri $uri/ /index.php$is_args$args; + } + + location /api/ { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_redirect off; + proxy_pass http://api:9001; + } + + location ~ ^/api/(v2|browser) { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_redirect off; + proxy_pass http://api:9001; + } +} diff --git a/easypanel-template-libretime/index.ts b/easypanel-template-libretime/index.ts new file mode 100644 index 000000000..4ade6dd5a --- /dev/null +++ b/easypanel-template-libretime/index.ts @@ -0,0 +1,38 @@ +// Minimal EasyPanel template generator that emits the canonical docker-compose +// and an .env file. This follows the standard LibreTime Docker deployment and +// does not add any extra services (e.g. composer). + +import { Template } from '@easypanel/template-sdk'; +const fs = require('fs'); + +const t: Template = { + meta: require('./meta.yaml'), + render: (values: any) => { + const composePath = './docker-compose.yml'; + let composeContent = ''; + try { + composeContent = fs.readFileSync(composePath, 'utf8'); + } catch (e) { + composeContent = '# docker-compose source not found: ' + composePath + '\n'; + } + + const env = `POSTGRES_PASSWORD=${values.postgres_password || ''}\nRABBITMQ_DEFAULT_PASS=${values.rabbitmq_password || ''}\nLIBRETIME_GENERAL_PUBLIC_URL=${values.public_url || 'http://{{hostname}}:8080'}\n`; + + let nginxConf = ''; + try { + nginxConf = fs.readFileSync('./docker/nginx/default.conf', 'utf8'); + } catch (e) { + nginxConf = ''; + } + + return { + files: [ + { path: 'docker-compose.yml', content: composeContent }, + { path: '.env', content: env }, + { path: 'docker/nginx/default.conf', content: nginxConf }, + ], + }; + }, +}; + +export default t; diff --git a/easypanel-template-libretime/logo.png b/easypanel-template-libretime/logo.png new file mode 100644 index 000000000..5d22890d2 --- /dev/null +++ b/easypanel-template-libretime/logo.png @@ -0,0 +1 @@ +iVBORw0KGgoAAAANSUhEUgAAAAUA diff --git a/easypanel-template-libretime/meta.ts b/easypanel-template-libretime/meta.ts new file mode 100644 index 000000000..fe070003f --- /dev/null +++ b/easypanel-template-libretime/meta.ts @@ -0,0 +1,2 @@ +// meta.ts is generated by the template tooling from meta.yaml. It's included as a placeholder. +export default require('./meta.yaml'); diff --git a/easypanel-template-libretime/meta.yaml b/easypanel-template-libretime/meta.yaml new file mode 100644 index 000000000..815a162ed --- /dev/null +++ b/easypanel-template-libretime/meta.yaml @@ -0,0 +1,30 @@ +name: libretime +title: LibreTime (EasyPanel) +description: "Plantilla para desplegar LibreTime usando docker-compose adaptado a EasyPanel. Incluye variables para credenciales y el archivo de configuración montado." +version: "1.0.0" +author: "Tu nombre" +logo: logo.png +screenshot: screenshot.png +tags: + - media + - radio + - docker + - libretime + +# Formulario que EasyPanel mostrará al crear una instancia desde la plantilla. +form: + - id: postgres_password + type: password + title: Contraseña Postgres + default: "P@5sW0rd!8xYv7Q2z9L" + - id: rabbitmq_password + type: password + title: Contraseña RabbitMQ + default: "R@bbiTmq!2025xYz" + - id: public_url + type: string + title: URL pública + default: "http://{{hostname}}:8080" + +# Nota: este archivo es solo metadatos y ejemplo. El motor de plantillas de Easypanel +# usa además un archivo TypeScript (`index.ts`) para generar artefactos dinámicos. diff --git a/easypanel-template-libretime/screenshot.png b/easypanel-template-libretime/screenshot.png new file mode 100644 index 000000000..5d22890d2 --- /dev/null +++ b/easypanel-template-libretime/screenshot.png @@ -0,0 +1 @@ +iVBORw0KGgoAAAANSUhEUgAAAAUA diff --git a/easypanel-template-secure.json b/easypanel-template-secure.json new file mode 100644 index 000000000..f1b085f0c --- /dev/null +++ b/easypanel-template-secure.json @@ -0,0 +1,25 @@ +{ + "meta": "name: libretime-secure\ntitle: LibreTime (EasyPanel) - Secure\ndescription: \"Plantilla segura que evita archivos con secretos en claro: genera el config desde variables/secretos del panel mediante un servicio generador.\"\nversion: \"1.0.0\"\nauthor: \"Tu nombre\"\nlogo: logo.png\nscreenshot: screenshot.png\ntags:\n - media\n - radio\n - docker\n - libretime\n\nform:\n - id: postgres_password\n type: password\n title: Contraseña Postgres\n - id: rabbitmq_password\n type: password\n title: Contraseña RabbitMQ\n - id: public_url\n type: string\n title: URL pública\n default: \"http://{{hostname}}:8080\"\n - id: libretime_version\n type: string\n title: LibreTime image version\n default: \"4.5\"\n", + "files": [ + { + "path": "docker/nginx/default.conf", + "encoding": "utf8", + "content": "server {\n listen 8080;\n listen [::]:8080;\n\n root /var/www/html/public;\n\n index index.php index.html index.htm;\n\n client_max_body_size 512M;\n client_body_timeout 300s;\n\n location ~ \\.php$ {\n fastcgi_buffers 64 4K;\n fastcgi_split_path_info ^(.+\\.php)(/.+)$;\n\n try_files $fastcgi_script_name =404;\n\n include fastcgi_params;\n\n fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n set $path_info $fastcgi_path_info;\n fastcgi_param PATH_INFO $path_info;\n include fastcgi_params;\n\n fastcgi_index index.php;\n fastcgi_pass legacy:9000;\n }\n\n location / {\n try_files $uri $uri/ /index.php$is_args$args;\n }\n\n location ^~ /api/_media {\n internal;\n alias /srv/libretime;\n }\n\n location ^~ /api/version {\n return 307 /api/v2/version;\n }\n\n location ^~ /api/version/ {\n return 307 /api/v2/version;\n }\n\n location = /api/register-component {\n try_files $uri $uri/ /index.php$is_args$args;\n }\n\n location = /api/register-component/ {\n try_files $uri $uri/ /index.php$is_args$args;\n }\n\n location /api/ {\n proxy_set_header Host $http_host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_redirect off;\n proxy_pass http://api:9001;\n }\n\n location ~ ^/api/(v2|browser) {\n proxy_set_header Host $http_host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n\n proxy_redirect off;\n proxy_pass http://api:9001;\n }\n}\n" + }, + { + "path": "docker-compose.yml", + "encoding": "utf8", + "content": "version: '3.8'\nservices:\n postgres:\n image: postgres:15\n environment:\n POSTGRES_USER: libretime\n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}\n POSTGRES_DB: libretime\n volumes:\n - postgres_data:/var/lib/postgresql/data\n\n rabbitmq:\n image: rabbitmq:3.13-alpine\n environment:\n RABBITMQ_DEFAULT_VHOST: /libretime\n RABBITMQ_DEFAULT_USER: libretime\n RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}\n\n api:\n image: ghcr.io/libretime/libretime-api:${LIBRETIME_VERSION:-4.5}\n environment:\n - LIBRETIME_GENERAL_PUBLIC_URL=${LIBRETIME_GENERAL_PUBLIC_URL:-http://localhost:8080}\n - LIBRETIME_CONFIG_FROM_ENV=true\n volumes:\n - libretime_storage:/srv/libretime\n depends_on:\n - postgres\n - rabbitmq\n\n legacy:\n image: ghcr.io/libretime/libretime-legacy:${LIBRETIME_VERSION:-4.5}\n environment:\n - LIBRETIME_CONFIG_FROM_ENV=true\n volumes:\n - libretime_storage:/srv/libretime\n depends_on:\n - postgres\n - rabbitmq\n\n nginx:\n image: ghcr.io/libretime/libretime-nginx:${LIBRETIME_VERSION:-4.5}\n ports:\n - \"8080:8080\"\n volumes:\n - libretime_storage:/srv/libretime:ro\n - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro\n depends_on:\n - legacy\n - api\n\nvolumes:\n postgres_data:\n libretime_storage:\n" + }, + { + "path": "index.ts", + "encoding": "utf8", + "content": "import { Template } from '@easypanel/template-sdk';\n\n// This secure template generates the docker-compose and uses environment variables\n// so that secrets are not stored in files inside the project. Instead, config\n// is generated at container start from env vars injected by the panel.\n\nconst t: Template = {\n meta: require('./meta.yaml'),\n render: (values: any) => {\n const libVersion = values.libretime_version || '4.5';\n const publicUrl = values.public_url || 'http://{{hostname}}:8080';\n\n const compose = `version: '3.8'\nservices:\n postgres:\n image: postgres:15\n environment:\n POSTGRES_USER: libretime\n POSTGRES_PASSWORD: ${values.postgres_password ? `'${values.postgres_password}'` : \"\\\"\\\"\"}\n POSTGRES_DB: libretime\n volumes:\n - postgres_data:/var/lib/postgresql/data\n\n rabbitmq:\n image: rabbitmq:3.13-alpine\n environment:\n RABBITMQ_DEFAULT_VHOST: /libretime\n RABBITMQ_DEFAULT_USER: libretime\n RABBITMQ_DEFAULT_PASS: ${values.rabbitmq_password ? `'${values.rabbitmq_password}'` : \"\\\"\\\"\"}\n\n api:\n image: ghcr.io/libretime/libretime-api:${libVersion}\n environment:\n - LIBRETIME_GENERAL_PUBLIC_URL=${publicUrl}\n - LIBRETIME_CONFIG_FROM_ENV=true\n volumes:\n - libretime_storage:/srv/libretime\n depends_on:\n - postgres\n - rabbitmq\n\n # composer not included; follow standard LibreTime docker installation\n\n legacy:\n image: ghcr.io/libretime/libretime-legacy:${libVersion}\n environment:\n - LIBRETIME_CONFIG_FROM_ENV=true\n volumes:\n - libretime_storage:/srv/libretime\n depends_on:\n - postgres\n - rabbitmq\n\n nginx:\n image: ghcr.io/libretime/libretime-nginx:${libVersion}\n ports:\n - \"8080:8080\"\n volumes:\n - libretime_storage:/srv/libretime:ro\n - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro\n depends_on:\n - legacy\n - api\n\nvolumes:\n postgres_data:\n libretime_storage:\n`;\n\n const startupSh = `#!/bin/sh\n# At container start this script writes /etc/libretime/config.yml from env\ncat > /etc/libretime/config.yml < \"Create from JSON\" y pega el JSON del template (o sube el ZIP con los archivos generados).\n- Alternativamente, añade la carpeta al repositorio oficial de templates y abre un Pull Request siguiendo las pautas en https://github.com/easypanel-io/templates.\n\nNotas de seguridad:\n- Sustituye contraseñas por secretos gestionados o variables del entorno en producción.\n- No uses `latest` en imágenes en producción; fija versiones.\n" + }, + { + "path": "docker/nginx/default.conf", + "encoding": "utf8", + "content": "server {\n listen 8080;\n listen [::]:8080;\n\n root /var/www/html/public;\n\n index index.php index.html index.htm;\n\n client_max_body_size 512M;\n client_body_timeout 300s;\n\n location ~ \\.php$ {\n fastcgi_buffers 64 4K;\n fastcgi_split_path_info ^(.+\\.php)(/.+)$;\n\n try_files $fastcgi_script_name =404;\n\n include fastcgi_params;\n\n fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n set $path_info $fastcgi_path_info;\n fastcgi_param PATH_INFO $path_info;\n include fastcgi_params;\n\n fastcgi_index index.php;\n fastcgi_pass legacy:9000;\n }\n\n location / {\n try_files $uri $uri/ /index.php$is_args$args;\n }\n\n location ^~ /api/_media {\n internal;\n alias /srv/libretime;\n }\n\n location ^~ /api/version {\n return 307 /api/v2/version;\n }\n\n location ^~ /api/version/ {\n return 307 /api/v2/version;\n }\n\n location = /api/register-component {\n try_files $uri $uri/ /index.php$is_args$args;\n }\n\n location = /api/register-component/ {\n try_files $uri $uri/ /index.php$is_args$args;\n }\n\n location /api/ {\n proxy_set_header Host $http_host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_redirect off;\n proxy_pass http://api:9001;\n }\n\n location ~ ^/api/(v2|browser) {\n proxy_set_header Host $http_host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n\n proxy_redirect off;\n proxy_pass http://api:9001;\n }\n}\n" + }, + { + "path": "docker-compose.yml", + "encoding": "utf8", + "content": "version: '3.8'\nservices:\n postgres:\n image: postgres:15\n restart: unless-stopped\n environment:\n POSTGRES_USER: ${POSTGRES_USER:-libretime}\n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-libretime}\n POSTGRES_DB: ${POSTGRES_DB:-libretime}\n volumes:\n - postgres_data:/var/lib/postgresql/data\n\n rabbitmq:\n image: rabbitmq:3.13-alpine\n restart: unless-stopped\n environment:\n RABBITMQ_DEFAULT_VHOST: ${RABBITMQ_DEFAULT_VHOST:-/libretime}\n RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER:-libretime}\n RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS:-libretime}\n\n api:\n image: ghcr.io/libretime/libretime-api:${LIBRETIME_VERSION:-4.5}\n restart: unless-stopped\n init: true\n ulimits:\n nofile: 1024\n depends_on:\n postgres:\n condition: service_healthy\n rabbitmq:\n condition: service_healthy\n environment:\n LIBRETIME_GENERAL_PUBLIC_URL: ${LIBRETIME_GENERAL_PUBLIC_URL:-http://localhost:8080}\n volumes:\n - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro\n - libretime_storage:/srv/libretime\n\n legacy:\n image: ghcr.io/libretime/libretime-legacy:${LIBRETIME_VERSION:-4.5}\n restart: unless-stopped\n init: true\n ulimits:\n nofile: 1024\n depends_on:\n postgres:\n condition: service_healthy\n rabbitmq:\n condition: service_healthy\n volumes:\n - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro\n - libretime_storage:/srv/libretime\n\n nginx:\n image: ghcr.io/libretime/libretime-nginx:${LIBRETIME_VERSION:-4.5}\n restart: unless-stopped\n depends_on:\n - legacy\n - api\n ports:\n - \"${NGINX_PORT:-8080}:8080\"\n volumes:\n - libretime_storage:/srv/libretime:ro\n - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro\n environment:\n - NGINX_WORKER_PROCESSES=${NGINX_WORKER_PROCESSES:-auto}\n\n icecast:\n image: ghcr.io/libretime/icecast:2.4.4\n restart: unless-stopped\n ports:\n - \"${ICECAST_PORT:-8000}:8000\"\n environment:\n ICECAST_SOURCE_PASSWORD: ${ICECAST_SOURCE_PASSWORD:-hackme}\n ICECAST_ADMIN_PASSWORD: ${ICECAST_ADMIN_PASSWORD:-hackme}\n ICECAST_RELAY_PASSWORD: ${ICECAST_RELAY_PASSWORD:-hackme}\n ICECAST_ADMIN_USER: ${ICECAST_ADMIN_USER:-admin}\n ICECAST_HOSTNAME: ${ICECAST_HOSTNAME:-localhost}\n\n playout:\n image: ghcr.io/libretime/libretime-playout:${LIBRETIME_VERSION:-4.5}\n restart: unless-stopped\n init: true\n ulimits:\n nofile: 1024\n depends_on:\n rabbitmq:\n condition: service_healthy\n volumes:\n - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro\n - libretime_playout:/app\n environment:\n LIBRETIME_GENERAL_PUBLIC_URL: ${LIBRETIME_GENERAL_PUBLIC_URL:-http://localhost:8080}\n\n liquidsoap:\n image: ghcr.io/libretime/libretime-playout:${LIBRETIME_VERSION:-4.5}\n command: /usr/local/bin/libretime-liquidsoap\n restart: unless-stopped\n init: true\n ulimits:\n nofile: 1024\n depends_on:\n rabbitmq:\n condition: service_healthy\n ports:\n - \"${LIQUIDSOAP_HARBOR_PORT:-8001}:8001\"\n - \"${LIQUIDSOAP_TELNET_PORT:-8002}:8002\"\n volumes:\n - ${LIBRETIME_CONFIG_FILEPATH:-./config.local.yml}:/etc/libretime/config.yml:ro\n - libretime_playout:/app\n environment:\n LIBRETIME_GENERAL_PUBLIC_URL: ${LIBRETIME_GENERAL_PUBLIC_URL:-http://localhost:8080}\n\n worker:\n image: ghcr.io/libretime/libretime-worker:${LIBRETIME_VERSION:-4.5}\n restart: unless-stopped\n init: true\n ulimits:\n nofile: 1024\n depends_on:\n rabbitmq:\n condition: service_healthy\n volumes:\n - ./config.local.yml:/etc/libretime/config.yml:ro\n - libretime_storage:/srv/libretime\n environment:\n LIBRETIME_GENERAL_PUBLIC_URL: ${LIBRETIME_GENERAL_PUBLIC_URL:-http://localhost:8080}\n\n analyzer:\n image: ghcr.io/libretime/libretime-analyzer:${LIBRETIME_VERSION:-4.5}\n restart: unless-stopped\n init: true\n ulimits:\n nofile: 1024\n depends_on:\n rabbitmq:\n condition: service_healthy\n volumes:\n - ./config.local.yml:/etc/libretime/config.yml:ro\n - libretime_storage:/srv/libretime\n\nvolumes:\n postgres_data:\n driver: local\n libretime_storage:\n driver: local\n libretime_playout:\n driver: local\n\nnetworks:\n default:\n name: libretime_network\n" + }, + { + "path": "docker_nginx_default.conf", + "encoding": "utf8", + "content": "server {\n listen 8080;\n listen [::]:8080;\n\n root /var/www/html/public;\n\n index index.php index.html index.htm;\n\n client_max_body_size 512M;\n client_body_timeout 300s;\n\n location ~ \\.php$ {\n fastcgi_buffers 64 4K;\n fastcgi_split_path_info ^(.+\\.php)(/.+)$;\n\n #try_files $uri =404;\n try_files $fastcgi_script_name =404;\n\n include fastcgi_params;\n\n fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n set $path_info $fastcgi_path_info;\n fastcgi_param PATH_INFO $path_info;\n include fastcgi_params;\n\n fastcgi_index index.php;\n fastcgi_pass legacy:9000;\n }\n\n location / {\n try_files $uri $uri/ /index.php$is_args$args;\n }\n\n location ^~ /api/_media {\n internal;\n alias /srv/libretime;\n }\n\n location ^~ /api/version {\n return 307 /api/v2/version;\n }\n\n location ^~ /api/version/ {\n return 307 /api/v2/version;\n }\n\n location = /api/register-component {\n try_files $uri $uri/ /index.php$is_args$args;\n }\n\n location = /api/register-component/ {\n try_files $uri $uri/ /index.php$is_args$args;\n }\n\n location /api/ {\n proxy_set_header Host $http_host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_redirect off;\n proxy_pass http://api:9001;\n }\n\n location ~ ^/api/(v2|browser) {\n proxy_set_header Host $http_host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n\n proxy_redirect off;\n proxy_pass http://api:9001;\n }\n}\n" + }, + { + "path": "index.ts", + "encoding": "utf8", + "content": "// Minimal EasyPanel template generator that emits the canonical docker-compose\n// and an .env file. This follows the standard LibreTime Docker deployment and\n// does not add any extra services (e.g. composer).\n\nimport { Template } from '@easypanel/template-sdk';\nconst fs = require('fs');\n\nconst t: Template = {\n meta: require('./meta.yaml'),\n render: (values: any) => {\n const composePath = './docker-compose.yml';\n let composeContent = '';\n try {\n composeContent = fs.readFileSync(composePath, 'utf8');\n } catch (e) {\n composeContent = '# docker-compose source not found: ' + composePath + '\\n';\n }\n\n const env = `POSTGRES_PASSWORD=${values.postgres_password || ''}\\nRABBITMQ_DEFAULT_PASS=${values.rabbitmq_password || ''}\\nLIBRETIME_GENERAL_PUBLIC_URL=${values.public_url || 'http://{{hostname}}:8080'}\\n`;\n\n let nginxConf = '';\n try {\n nginxConf = fs.readFileSync('./docker/nginx/default.conf', 'utf8');\n } catch (e) {\n nginxConf = '';\n }\n\n return {\n files: [\n { path: 'docker-compose.yml', content: composeContent },\n { path: '.env', content: env },\n { path: 'docker/nginx/default.conf', content: nginxConf },\n ],\n };\n },\n};\n\nexport default t;\n" + }, + { + "path": "logo.png", + "encoding": "utf8", + "content": "iVBORw0KGgoAAAANSUhEUgAAAAUA\n" + }, + { + "path": "meta.ts", + "encoding": "utf8", + "content": "// meta.ts is generated by the template tooling from meta.yaml. It's included as a placeholder.\nexport default require('./meta.yaml');\n" + }, + { + "path": "meta.yaml", + "encoding": "utf8", + "content": "name: libretime\ntitle: LibreTime (EasyPanel)\ndescription: \"Plantilla para desplegar LibreTime usando docker-compose adaptado a EasyPanel. Incluye variables para credenciales y el archivo de configuración montado.\"\nversion: \"1.0.0\"\nauthor: \"Tu nombre\"\nlogo: logo.png\nscreenshot: screenshot.png\ntags:\n - media\n - radio\n - docker\n - libretime\n\n# Formulario que EasyPanel mostrará al crear una instancia desde la plantilla.\nform:\n - id: postgres_password\n type: password\n title: Contraseña Postgres\n default: \"P@5sW0rd!8xYv7Q2z9L\"\n - id: rabbitmq_password\n type: password\n title: Contraseña RabbitMQ\n default: \"R@bbiTmq!2025xYz\"\n - id: public_url\n type: string\n title: URL pública\n default: \"http://{{hostname}}:8080\"\n\n# Nota: este archivo es solo metadatos y ejemplo. El motor de plantillas de Easypanel\n# usa además un archivo TypeScript (`index.ts`) para generar artefactos dinámicos.\n" + }, + { + "path": "screenshot.png", + "encoding": "utf8", + "content": "iVBORw0KGgoAAAANSUhEUgAAAAUA\n" + } + ] +} \ No newline at end of file diff --git a/generate-keys.sh b/generate-keys.sh new file mode 100644 index 000000000..d0d029ce3 --- /dev/null +++ b/generate-keys.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# Script para generar claves seguras para LibreTime +# Uso: ./generate-keys.sh + +echo "Generando claves seguras para LibreTime..." +echo "" + +# Generar API key (64 caracteres) +API_KEY=$(openssl rand -hex 32) +echo "API_KEY (copia esto a config.local.yml):" +echo "$API_KEY" +echo "" + +# Generar Django secret key (50 caracteres) +SECRET_KEY=$(openssl rand -base64 50 | tr -d "=+/" | cut -c1-50) +echo "SECRET_KEY (copia esto a config.local.yml):" +echo "$SECRET_KEY" +echo "" + +# Generar contraseñas para la base de datos +DB_PASSWORD=$(openssl rand -base64 24 | tr -d "=+/" | cut -c1-20) +echo "POSTGRES_PASSWORD (copia esto a .env):" +echo "$DB_PASSWORD" +echo "" + +# Generar contraseña para RabbitMQ +RABBITMQ_PASSWORD=$(openssl rand -base64 24 | tr -d "=+/" | cut -c1-20) +echo "RABBITMQ_DEFAULT_PASS (copia esto a .env):" +echo "$RABBITMQ_PASSWORD" +echo "" + +# Generar contraseñas para Icecast +ICECAST_SOURCE=$(openssl rand -base64 16 | tr -d "=+/" | cut -c1-12) +ICECAST_ADMIN=$(openssl rand -base64 16 | tr -d "=+/" | cut -c1-12) +ICECAST_RELAY=$(openssl rand -base64 16 | tr -d "=+/" | cut -c1-12) + +echo "ICECAST_SOURCE_PASSWORD (copia esto a .env):" +echo "$ICECAST_SOURCE" +echo "" + +echo "ICECAST_ADMIN_PASSWORD (copia esto a .env):" +echo "$ICECAST_ADMIN" +echo "" + +echo "ICECAST_RELAY_PASSWORD (copia esto a .env):" +echo "$ICECAST_RELAY" +echo "" + +echo "===================================" +echo "INSTRUCCIONES:" +echo "1. Copia estas claves a tus archivos de configuración" +echo "2. Actualiza config.local.yml con API_KEY y SECRET_KEY" +echo "3. Actualiza .env con las contraseñas generadas" +echo "4. También actualiza las contraseñas en config.local.yml para database y rabbitmq" +echo "===================================" \ No newline at end of file diff --git a/scripts/generate-config.sh b/scripts/generate-config.sh new file mode 100755 index 000000000..ab3188b96 --- /dev/null +++ b/scripts/generate-config.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env sh +# Genera config.local.yml para LibreTime desde variables de entorno. +# No guarda secretos en el repositorio. + +CFG_PATH=${1:-./config.local.yml} + +# Generar claves si no están provistas +API_KEY=${LIBRETIME_API_KEY:-} +SECRET_KEY=${LIBRETIME_SECRET_KEY:-} +if [ -z "$API_KEY" ]; then + API_KEY=$(head -c 48 /dev/urandom | base64 | tr -d '/+=' | cut -c1-40) +fi +if [ -z "$SECRET_KEY" ]; then + SECRET_KEY=$(head -c 48 /dev/urandom | base64 | tr -d '/+=' | cut -c1-40) +fi + +cat > "$CFG_PATH" < ./config.local.yml.tmp && mv ./config.local.yml.tmp ./config.local.yml + echo "config.local.yml actualizada con la contraseña de .env" +fi + +echo "Starting postgres and rabbitmq..." +docker compose -f docker-compose.simple.yml up -d postgres rabbitmq + +echo "Waiting for postgres container id..." +POSTGRES_CID="" +for i in $(seq 1 30); do + POSTGRES_CID=$(docker compose -f docker-compose.simple.yml ps -q postgres || true) + if [ -n "$POSTGRES_CID" ]; then + break + fi + sleep 1 +done +if [ -z "$POSTGRES_CID" ]; then + echo "Error: no pude encontrar el contenedor postgres" >&2 + exit 1 +fi + +echo "Esperando a que Postgres acepte conexiones (pg_isready dentro del contenedor)..." +for i in $(seq 1 120); do + if docker exec "$POSTGRES_CID" pg_isready -U ${POSTGRES_USER:-libretime} >/dev/null 2>&1; then + echo "Postgres listo" + break + fi + sleep 1 +done + + +echo "Applying migrations" +docker compose -f docker-compose.simple.yml run --rm migrate + +echo "Starting remaining services" +docker compose -f docker-compose.simple.yml up -d --remove-orphans nginx api legacy playout liquidsoap worker analyzer icecast + +echo "Done. Browse to ${LIBRETIME_GENERAL_PUBLIC_URL:-http://localhost:8080}" diff --git a/tools/restore-config.sh b/tools/restore-config.sh new file mode 100644 index 000000000..e71fd0320 --- /dev/null +++ b/tools/restore-config.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env sh +# restore-config.sh +# Lista y restaura backups creados por start.sh (config.local.yml.bak.YYYYMMDDHHMMSS) + +set -e + +WORKDIR="$(pwd)" +BACKUP_GLOB="$WORKDIR/config.local.yml.bak.*" +DEST="$WORKDIR/config.local.yml" + +usage() { + cat <] [--yes] + +Options: + --list List available backup files + --latest Show the latest backup file (implies --list) + --restore FILE Restore the specified backup into config.local.yml + --yes Skip confirmation when restoring + +Examples: + $0 --list + $0 --latest + $0 --restore ./config.local.yml.bak.20251001102251 + $0 --restore latest --yes +EOF +} + +list_backups() { + ls -1 $BACKUP_GLOB 2>/dev/null | sort || true +} + +latest_backup() { + # sort by name (timestamp suffix) and pick last + ls -1 $BACKUP_GLOB 2>/dev/null | sort | tail -n 1 || true +} + +if [ "$#" -eq 0 ]; then + usage + exit 0 +fi + +FORCE=0 +MODE="" +TARGET="" + +while [ "$#" -gt 0 ]; do + case "$1" in + --list) + MODE=list; shift ;; + --latest) + MODE=latest; shift ;; + --restore) + MODE=restore; TARGET="$2"; shift 2 ;; + --yes) + FORCE=1; shift ;; + -h|--help) + usage; exit 0 ;; + *) + echo "Unknown arg: $1" >&2; usage; exit 1 ;; + esac +done + +case "$MODE" in + list) + echo "Backups:"; + list_backups; + exit 0 + ;; + latest) + echo "Latest backup:"; + latest_backup; + exit 0 + ;; + restore) + if [ "$TARGET" = "latest" ] || [ -z "$TARGET" ]; then + TARGET=$(latest_backup) + fi + if [ -z "$TARGET" ] || [ ! -f "$TARGET" ]; then + echo "No backup found to restore: $TARGET" >&2 + exit 1 + fi + + echo "About to restore backup: $TARGET -> $DEST" + if [ "$FORCE" -ne 1 ]; then + printf "Continue and overwrite %s? [y/N]: " "$DEST" + read ans || true + case "$ans" in + y|Y|yes|YES) + ;; + *) + echo "Aborted."; exit 1 ;; + esac + fi + + cp "$TARGET" "$DEST" + echo "Restored $TARGET -> $DEST" + exit 0 + ;; + *) + usage; exit 1 ;; +esac