chore: remove EasyPanel template artifacts

This commit is contained in:
Cesar Jhoanny Mendivil Rubio 2025-10-01 14:44:28 -07:00
parent 62961f0db4
commit d600eeb321
31 changed files with 1484 additions and 16 deletions

22
.env.example Normal file
View File

@ -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

84
README_SIMPLE.md Normal file
View File

@ -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.

View File

@ -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"

24
config.local.yml Normal file
View File

@ -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"

View File

@ -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"

View File

@ -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:

150
docker-compose.simple.yml Normal file
View File

@ -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

View File

@ -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:

83
docker/nginx/default.conf Normal file
View File

@ -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;
}
}

Binary file not shown.

Binary file not shown.

View File

@ -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:

View File

@ -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;
}
}

View File

@ -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 <<EOF
general:
public_url: ${publicUrl}
api_key: "${values.api_key || ''}"
secret_key: "${values.secret_key || ''}"
database:
host: ${values.database_host || 'postgres'}
port: 5432
name: libretime
user: libretime
password: "$POSTGRES_PASSWORD"
rabbitmq:
host: ${values.rabbitmq_host || 'rabbitmq'}
port: 5672
vhost: /libretime
user: libretime
password: "$RABBITMQ_DEFAULT_PASS"
EOF
`;
return {
files: [
{ path: 'docker-compose.yml', content: compose },
{ path: 'startup/generate-config.sh', content: startupSh },
{ path: 'docker/nginx/default.conf', content: require('fs').readFileSync('docker/nginx/default.conf','utf8') },
],
};
},
};
export default t;

View File

@ -0,0 +1,28 @@
name: libretime-secure
title: LibreTime (EasyPanel) - Secure
description: "Plantilla segura que evita archivos con secretos en claro: genera el config desde variables/secretos del panel mediante un servicio generador."
version: "1.0.0"
author: "Tu nombre"
logo: logo.png
screenshot: screenshot.png
tags:
- media
- radio
- docker
- libretime
form:
- id: postgres_password
type: password
title: Contraseña Postgres
- id: rabbitmq_password
type: password
title: Contraseña RabbitMQ
- id: public_url
type: string
title: URL pública
default: "http://{{hostname}}:8080"
- id: libretime_version
type: string
title: LibreTime image version
default: "4.5"

View File

@ -0,0 +1,15 @@
Plantilla EasyPanel para LibreTime
Pasos rápidos:
1) Copia la carpeta `easypanel-template-libretime` a un repositorio nuevo o en tu instancia de EasyPanel.
2) Si usas el Playground de EasyPanel (`npm run dev` en https://github.com/easypanel-io/templates), duplica una plantilla existente y ajusta `meta.yaml` e `index.ts`.
3) Desde el panel EasyPanel puedes crear una plantilla usando el JSON generado por `index.ts` o subiendo los archivos.
Cómo instalar un template personalizado en EasyPanel (resumen):
- En la UI de EasyPanel ve a "Templates" -> "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.

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -0,0 +1 @@
iVBORw0KGgoAAAANSUhEUgAAAAUA

View File

@ -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');

View File

@ -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.

View File

@ -0,0 +1 @@
iVBORw0KGgoAAAANSUhEUgAAAAUA

View File

@ -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 <<EOF\ngeneral:\n public_url: ${publicUrl}\n api_key: \"${values.api_key || ''}\"\n secret_key: \"${values.secret_key || ''}\"\n\ndatabase:\n host: ${values.database_host || 'postgres'}\n port: 5432\n name: libretime\n user: libretime\n password: \"$POSTGRES_PASSWORD\"\n\nrabbitmq:\n host: ${values.rabbitmq_host || 'rabbitmq'}\n port: 5672\n vhost: /libretime\n user: libretime\n password: \"$RABBITMQ_DEFAULT_PASS\"\nEOF\n`;\n\n return {\n files: [\n { path: 'docker-compose.yml', content: compose },\n { path: 'startup/generate-config.sh', content: startupSh },\n { path: 'docker/nginx/default.conf', content: require('fs').readFileSync('docker/nginx/default.conf','utf8') },\n ],\n };\n },\n};\n\nexport default t;\n"
},
{
"path": "meta.yaml",
"encoding": "utf8",
"content": "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"
}
]
}

50
easypanel-template.json Normal file
View File

@ -0,0 +1,50 @@
{
"meta": "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",
"files": [
{
"path": "README.easypanel.md",
"encoding": "utf8",
"content": "Plantilla EasyPanel para LibreTime\n\nPasos rápidos:\n\n1) Copia la carpeta `easypanel-template-libretime` a un repositorio nuevo o en tu instancia de EasyPanel.\n2) Si usas el Playground de EasyPanel (`npm run dev` en https://github.com/easypanel-io/templates), duplica una plantilla existente y ajusta `meta.yaml` e `index.ts`.\n3) Desde el panel EasyPanel puedes crear una plantilla usando el JSON generado por `index.ts` o subiendo los archivos.\n\nCómo instalar un template personalizado en EasyPanel (resumen):\n- En la UI de EasyPanel ve a \"Templates\" -> \"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"
}
]
}

56
generate-keys.sh Normal file
View File

@ -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 "==================================="

44
scripts/generate-config.sh Executable file
View File

@ -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" <<EOF
general:
public_url: "${LIBRETIME_GENERAL_PUBLIC_URL:-http://localhost:8080}"
api_key: "${API_KEY}"
secret_key: "${SECRET_KEY}"
database:
name: "${POSTGRES_DB:-libretime}"
user: "${POSTGRES_USER:-libretime}"
password: "${POSTGRES_PASSWORD:-libretime}"
host: "${POSTGRES_HOST:-postgres}"
port: ${POSTGRES_PORT:-5432}
rabbitmq:
host: "${RABBITMQ_HOST:-rabbitmq}"
username: "${RABBITMQ_DEFAULT_USER:-libretime}"
password: "${RABBITMQ_DEFAULT_PASS:-libretime}"
vhost: "${RABBITMQ_DEFAULT_VHOST:-/libretime}"
icecast:
source_password: "${ICECAST_SOURCE_PASSWORD:-changeme}"
admin_password: "${ICECAST_ADMIN_PASSWORD:-changeme}"
relay_password: "${ICECAST_RELAY_PASSWORD:-changeme}"
admin_user: "${ICECAST_ADMIN_USER:-admin}"
hostname: "${ICECAST_HOSTNAME:-localhost}"
EOF
echo "Wrote $CFG_PATH"

View File

@ -0,0 +1,60 @@
const fs = require('fs');
const path = require('path');
const templateDirDefault = path.resolve(__dirname, '..', 'easypanel-template-libretime');
const templateDirSecure = path.resolve(__dirname, '..', 'easypanel-template-libretime-secure');
const outFile = path.resolve(__dirname, '..', 'easypanel-template.json');
const outFileSecure = path.resolve(__dirname, '..', 'easypanel-template-secure.json');
function isBinary(buf) {
for (let i = 0; i < buf.length; i++) {
if (buf[i] === 0) return true;
}
return false;
}
function walk(dir, base) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
const files = [];
for (const e of entries) {
const full = path.join(dir, e.name);
const rel = path.join(base, e.name);
if (e.isDirectory()) {
files.push(...walk(full, rel));
} else {
const buf = fs.readFileSync(full);
if (isBinary(buf)) {
files.push({ path: rel.replace(/\\\\/g, '/'), encoding: 'base64', content: buf.toString('base64') });
} else {
files.push({ path: rel.replace(/\\\\/g, '/'), encoding: 'utf8', content: buf.toString('utf8') });
}
}
}
return files;
}
function generateFor(dir, outPath) {
if (!fs.existsSync(dir)) {
console.error('Template dir not found:', dir);
return;
}
const metaPath = path.join(dir, 'meta.yaml');
let metaYaml = '';
if (fs.existsSync(metaPath)) {
metaYaml = fs.readFileSync(metaPath, 'utf8');
}
const files = walk(dir, '');
const out = {
meta: metaYaml,
files: files,
};
fs.writeFileSync(outPath, JSON.stringify(out, null, 2), 'utf8');
console.log('Wrote', outPath);
}
generateFor(templateDirDefault, outFile);
generateFor(templateDirSecure, outFileSecure);

76
start.sh Executable file
View File

@ -0,0 +1,76 @@
#!/usr/bin/env sh
# start.sh - Flujo sencillo para usuarios finales:
# 1) Cargar variables desde .env (si existe)
# 2) Generar config.local.yml seguro desde variables de entorno
# 3) Levantar postgres y rabbitmq
# 4) Ejecutar migraciones
# 5) Levantar el resto de servicios
set -e
if [ -f .env ]; then
echo "Loading .env"
# shellcheck disable=SC1091
. .env
fi
mkdir -p ./data
echo "Generating config.local.yml"
sh ./scripts/generate-config.sh ./config.local.yml
# Si POSTGRES_PASSWORD está definido en .env, asegurarnos de que config.local.yml use la misma contraseña
if [ -n "${POSTGRES_PASSWORD:-}" ]; then
echo "Asegurando que config.local.yml use la contraseña de POSTGRES_PASSWORD desde .env"
# Sustituir la línea password: "..." en la sección database por el valor de POSTGRES_PASSWORD
# Usamos awk para editar de forma segura el fichero yaml simple
# 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"
fi
awk -v pw="$POSTGRES_PASSWORD" '
BEGIN {in_db=0}
/^database:/ {in_db=1; print; next}
in_db && /^[[:space:]]*password:/ {print " password: \"" pw "\""; in_db=0; next}
{print}
' ./config.local.yml > ./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}"

102
tools/restore-config.sh Normal file
View File

@ -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 <<EOF
Usage: $0 [--list] [--latest] [--restore <file>] [--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