diff --git a/easypanel/README.md b/easypanel/README.md index f8b50a5b5..bd85f694e 100644 --- a/easypanel/README.md +++ b/easypanel/README.md @@ -12,8 +12,32 @@ Requisitos/Notas: - `update.sh` usa utilidades estándar de shell y `perl` (normalmente disponible en sistemas UNIX). Si tu entorno no tiene `perl`, el script puede adaptarse a `python`. - Revisa y completa las variables de entorno requeridas por `docker-compose.easypanel.yml` (p. ej. secretos: `POSTGRES_PASSWORD`, `RABBITMQ_DEFAULT_PASS`, etc.) desde la UI de EasyPanel. + Siguientes pasos recomendados: -- (Opcional) Añadir un `update.js` si prefieres implementar clonación/actualizaciones con las utilidades de EasyPanel (no obligatorio). - - (Opcional) Añadir un `update.js` si prefieres implementar clonación/actualizaciones con las utilidades de EasyPanel (no obligatorio). Este repo incluye `easypanel/update.js` que realiza la misma función que `update.sh` usando Node.js. +- (Opcional) Añadir un `update.js` si prefieres implementar clonación/actualizaciones con las utilidades de EasyPanel (no obligatorio). Este repo incluye `easypanel/update.js` que realiza la misma función que `update.sh` usando Node.js. - (Opcional) Añadir un archivo `README.md` con la lista de variables que EasyPanel debe exponer en la interfaz de instalación. +## Instrucciones específicas para EasyPanel + +1. En la UI de EasyPanel, crea un nuevo servicio apuntando a este repositorio (o sube un ZIP). +2. En la sección de variables/Environment, pega el contenido de `easypanel/service.env` (o sube el archivo). + - Marca como `secret` las variables: `POSTGRES_PASSWORD`, `RABBITMQ_DEFAULT_PASS`, `ICECAST_SOURCE_PASSWORD`, `ICECAST_ADMIN_PASSWORD`, `ICECAST_RELAY_PASSWORD`. +3. EasyPanel ejecutará `easypanel/update.sh` por convención; el script intenta ejecutar `easypanel/update.js` si Node.js está disponible, y usa un fallback en shell si no lo está. +4. Después de ejecutar el script, EasyPanel usará `easypanel/code/docker-compose.yml` para desplegar el stack. Revisa ese archivo antes de confirmar el despliegue. +5. Si necesitas exponer puertos manualmente en EasyPanel, revisa `docker-compose.easypanel.yml` y ajusta `NGINX_PORT`, `ICECAST_PORT`, etc., o deja que EasyPanel gestione el ruteo. + +Consejos: +- Asegúrate de que `LIBRETIME_GENERAL_PUBLIC_URL` apunte a tu dominio público antes de desplegar. +- Para entornos de producción, establece `LIBRETIME_DEBUG=false` y usa contraseñas fuertes en los secrets. + +Generar contraseñas seguras +--------------------------- +Puedes usar el script `easypanel/generate_secrets.sh` para generar contraseñas seguras y sus hashes SHA-256. El script imprime un bloque `.env` listo para pegar en la UI de EasyPanel. Ejecútalo localmente y no subas las contraseñas al repositorio. + +Ejemplo: +```bash +cd easypanel +chmod +x generate_secrets.sh +./generate_secrets.sh +``` + diff --git a/easypanel/code/docker-compose.yml b/easypanel/code/docker-compose.yml index 7bd2c005f..61a80cb88 100644 --- a/easypanel/code/docker-compose.yml +++ b/easypanel/code/docker-compose.yml @@ -1,4 +1,3 @@ -version: "3.9" # LibreTime Docker Compose (EasyPanel-ready) # Esta versión está pensada para usarse desde un repositorio en EasyPanel. @@ -16,7 +15,7 @@ services: restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER:-libretime} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-libretime} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-Jz/XxRUodVl2g0HE59DszTBJVY8Sdmv7} POSTGRES_DB: ${POSTGRES_DB:-libretime} volumes: - postgres_data:/var/lib/postgresql/data @@ -33,7 +32,7 @@ services: environment: RABBITMQ_DEFAULT_VHOST: ${RABBITMQ_DEFAULT_VHOST:-/libretime} RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER:-libretime} - RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS} + RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS:-Bn321PQSRXanvmZlppuulVCB0ShN5Dz2} healthcheck: test: ["CMD-SHELL", "rabbitmq-diagnostics check_port_connectivity"] interval: 30s @@ -97,9 +96,9 @@ services: image: ghcr.io/libretime/icecast:2.4.4 restart: unless-stopped environment: - ICECAST_SOURCE_PASSWORD: ${ICECAST_SOURCE_PASSWORD:-hackme} - ICECAST_ADMIN_PASSWORD: ${ICECAST_ADMIN_PASSWORD:-hackme} - ICECAST_RELAY_PASSWORD: ${ICECAST_RELAY_PASSWORD:-hackme} + ICECAST_SOURCE_PASSWORD: ${ICECAST_SOURCE_PASSWORD:-dna1g1GcaaHakSN6C9X7rcPRpIIc/jV2} + ICECAST_ADMIN_PASSWORD: ${ICECAST_ADMIN_PASSWORD:-BLoYLPlUXfmkxrsvGF7LP0TtVtuKNuzJ} + ICECAST_RELAY_PASSWORD: ${ICECAST_RELAY_PASSWORD:-jYzhEjwdiJlTk30QOYHum6UE61FHo+sd} ICECAST_ADMIN_USER: ${ICECAST_ADMIN_USER:-admin} ICECAST_HOSTNAME: ${ICECAST_HOSTNAME:-localhost} diff --git a/easypanel/easypanel.json b/easypanel/easypanel.json index 0d78ecf62..528df3885 100644 --- a/easypanel/easypanel.json +++ b/easypanel/easypanel.json @@ -10,6 +10,7 @@ }, "update_script": "easypanel/update.js", "compose_path": "easypanel/code/docker-compose.yml", + "env_file": "easypanel/service.env", "services": { "notes": "El archivo compose generado tendrá los servicios principales: postgres, rabbitmq, api, legacy, nginx, icecast, playout, liquidsoap, worker, analyzer y config-generator." }, @@ -120,4 +121,6 @@ ], "notes": "Después de clonar, EasyPanel debe ejecutar el `update_script` y usar el compose generado en `compose_path`. Asegúrate de proveer los secretos marcados como `secret: true` en la UI de EasyPanel.", "last_updated": "2025-10-01" + , + "install_instructions": "1) Copia el contenido de easypanel/service.env en la sección Environment/Secrets del servicio en EasyPanel.\n2) Marca como secreto las variables que contienen contraseñas (POSTGRES_PASSWORD, RABBITMQ_DEFAULT_PASS, ICECAST_*_PASSWORD).\n3) EasyPanel ejecutará el script indicado en 'update_script' (easypanel/update.js o update.sh) para generar el compose final en easypanel/code/docker-compose.yml.\n4) Revisa el compose generado y despliega el servicio." } diff --git a/easypanel/generate_secrets.sh b/easypanel/generate_secrets.sh new file mode 100644 index 000000000..7395fd0fe --- /dev/null +++ b/easypanel/generate_secrets.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +set -euo pipefail + +# genera contraseñas seguras y muestra sus hashes SHA-256 +# Uso: ./generate_secrets.sh +# Salida: muestra las contraseñas (plaintext) y sus hashes; imprime un bloque .env listo para pegar en EasyPanel + +rand_pass() { + # 24 bytes -> 32 chars base64 approx + openssl rand -base64 24 +} + +sha256() { + if command -v openssl >/dev/null 2>&1; then + printf "%s" "$1" | openssl dgst -sha256 -r | awk '{print $1}' + else + # fallback to python + python3 - < { + line = line.trim(); + if (!line || line.startsWith('#')) return; + const m = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/); + if (m) { + let val = m[2]; + // strip optional quotes + if ((val.startsWith("\'") && val.endsWith("\'")) || (val.startsWith('"') && val.endsWith('"'))) { + val = val.slice(1, -1); + } + out[m[1]] = val; + } + }); + return out; +} + +function validateRequiredSecrets() { + const envFilePath = path.join(__dirname, 'service.env'); + const fileEnv = parseEnvFile(envFilePath); + const missing = []; + REQUIRED_SECRETS.forEach(k => { + const v = (process.env[k] || '').trim() || (fileEnv[k] || '').trim(); + // treat obvious placeholders as missing + if (!v || /^\s*(CAMBIA|pon_aqui|tu_|CHANGE|REPLACE)/i.test(v)) { + missing.push(k); + } + }); + if (missing.length) { + console.error('ERROR: faltan valores seguros para las siguientes variables:', missing.join(', ')); + console.error('Asegúrate de definirlas en la sección Environment/Secrets de EasyPanel o en', envFilePath); + console.error('Variables requeridas:', REQUIRED_SECRETS.join(', ')); + process.exit(2); + } +} + function removeContainerNamesAndPorts(content) { // Eliminar container_name: líneas content = content.replace(/^[ \t]*container_name:[^\n]*\n/gm, ''); + // Eliminar version: ... para evitar advertencias en EasyPanel + content = content.replace(/^[ \t]*version:[^\n]*\n/gm, ''); // Eliminar bloques de ports: // ports:\n - "..."\n - ... content = content.replace(/^[ \t]*ports:[^\n]*\n(?:^[ \t]+-.*\n)*/gim, ''); @@ -22,6 +66,8 @@ function removeContainerNamesAndPorts(content) { } async function main() { + // Validar secretos antes de generar el compose final + validateRequiredSecrets(); if (!fs.existsSync(SRC)) { console.error('ERROR: no se encuentra', SRC); process.exit(2); diff --git a/easypanel/update.sh b/easypanel/update.sh index 6e7bbfbc6..63dfa0c3e 100755 --- a/easypanel/update.sh +++ b/easypanel/update.sh @@ -10,6 +10,39 @@ SRC="$BASE_DIR/../docker-compose.easypanel.yml" DEST_DIR="$BASE_DIR/code" DEST="$DEST_DIR/docker-compose.yml" +REQUIRED_SECRETS=(POSTGRES_PASSWORD RABBITMQ_DEFAULT_PASS) + +function read_env_file() { + local f="$BASE_DIR/service.env" + if [ ! -f "$f" ]; then + return + fi + # shellcheck disable=SC1090 + # do not source to avoid side effects; parse manually + grep -E '^[A-Za-z_][A-Za-z0-9_]*=' "$f" | sed "s/^[^=]*=//" >/dev/null 2>&1 || true +} + +function check_required_secrets() { + local missing=() + for key in "${REQUIRED_SECRETS[@]}"; do + # prefer environment then service.env + val="${!key:-}" + if [ -z "$val" ] && [ -f "$BASE_DIR/service.env" ]; then + val=$(grep -E "^${key}=" "$BASE_DIR/service.env" | head -n1 | sed -E "s/^${key}=//") + fi + if [ -z "$val" ] || echo "$val" | grep -Eiq '^(CAMBIA|pon_aqui|tu_|CHANGE|REPLACE)'; then + missing+=("$key") + fi + done + if [ ${#missing[@]} -ne 0 ]; then + echo "ERROR: faltan valores seguros para las siguientes variables: ${missing[*]}" + echo "Definelas en la sección Environment/Secrets de EasyPanel o en $BASE_DIR/service.env" + exit 2 + fi +} + +check_required_secrets + if command -v node >/dev/null 2>&1 && [ -f "$BASE_DIR/update.js" ]; then echo "Node.js detectado, ejecutando easypanel/update.js" # Ejecutar con node (permitirá usar la versión JS en lugar del sed/perl) @@ -35,11 +68,14 @@ cp "$SRC" "$DEST" if command -v perl >/dev/null 2>&1; then perl -0777 -pe "s/^[ \t]*container_name:[^\n]*\n//mg" -i "$DEST" perl -0777 -pe "s/^[ \t]*ports:[^\n]*\n(?:^[ \t]+-.*\n)*//mg" -i "$DEST" + perl -0777 -pe "s/^[ \t]*version:[^\n]*\n//mg" -i "$DEST" else # Fallback con awk/sed si perl no está disponible # Elimina líneas 'container_name:' y bloques 'ports:' simples sed -E '/^[ \t]*container_name:/d' -i "" "$DEST" 2>/dev/null || sed -E '/^[ \t]*container_name:/d' -i '$DEST' awk 'BEGIN{skip=0} /^[ \t]*ports:[ \t]*$/ {skip=1; next} /^[ \t]*[^ \t]/ { if(skip){skip=0} } { if(!skip) print $0 }' "$DEST" > "$DEST.tmp" && mv "$DEST.tmp" "$DEST" + # Elimina 'version:' si queda + sed -E '/^[ \t]*version:/d' -i "" "$DEST" 2>/dev/null || sed -E '/^[ \t]*version:/d' -i '$DEST' fi echo "Preparado $DEST para EasyPanel. Revisa variables de entorno en el README y súbelas en la UI de EasyPanel."