feat(easypanel): agregar instrucciones y validaciones para secretos en EasyPanel, incluyendo generación de contraseñas seguras y actualización de archivos de configuración
Some checks are pending
Container / meta (analyzer) (push) Waiting to run
Container / meta (api) (push) Waiting to run
Container / meta (legacy) (push) Waiting to run
Container / meta (nginx) (push) Waiting to run
Container / meta (playout) (push) Waiting to run
Container / meta (worker) (push) Waiting to run
Container / build (push) Blocked by required conditions
Project / pre-commit (push) Waiting to run
Project / test-tools (push) Waiting to run
Release-Please / release-please (push) Waiting to run

This commit is contained in:
Cesar Jhoanny Mendivil Rubio 2025-10-01 17:33:30 -07:00
parent ac2806e2f6
commit 83724ddc26
7 changed files with 238 additions and 8 deletions

View File

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

View File

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

View File

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

View File

@ -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 - <<PY
import hashlib, sys
print(hashlib.sha256(sys.stdin.read().encode()).hexdigest())
PY
fi
}
echo "Generando contraseñas seguras...\n"
POSTGRES_PASSWORD=$(rand_pass)
RABBITMQ_DEFAULT_PASS=$(rand_pass)
ICECAST_SOURCE_PASSWORD=$(rand_pass)
ICECAST_ADMIN_PASSWORD=$(rand_pass)
ICECAST_RELAY_PASSWORD=$(rand_pass)
# Mostrar en pantalla con hashes
cat <<EOF
POSTGRES_PASSWORD: $POSTGRES_PASSWORD
POSTGRES_PASSWORD.sha256: $(sha256 "$POSTGRES_PASSWORD")
RABBITMQ_DEFAULT_PASS: $RABBITMQ_DEFAULT_PASS
RABBITMQ_DEFAULT_PASS.sha256: $(sha256 "$RABBITMQ_DEFAULT_PASS")
ICECAST_SOURCE_PASSWORD: $ICECAST_SOURCE_PASSWORD
ICECAST_SOURCE_PASSWORD.sha256: $(sha256 "$ICECAST_SOURCE_PASSWORD")
ICECAST_ADMIN_PASSWORD: $ICECAST_ADMIN_PASSWORD
ICECAST_ADMIN_PASSWORD.sha256: $(sha256 "$ICECAST_ADMIN_PASSWORD")
ICECAST_RELAY_PASSWORD: $ICECAST_RELAY_PASSWORD
ICECAST_RELAY_PASSWORD.sha256: $(sha256 "$ICECAST_RELAY_PASSWORD")
# Bloque .env para pegar en EasyPanel (REEMPLAZA/NO COMMIT)
cat <<ENDF
# ------------------ EasyPanel .env snippet (DO NOT COMMIT) ------------------
POSTGRES_PASSWORD=$POSTGRES_PASSWORD
RABBITMQ_DEFAULT_PASS=$RABBITMQ_DEFAULT_PASS
ICECAST_SOURCE_PASSWORD=$ICECAST_SOURCE_PASSWORD
ICECAST_ADMIN_PASSWORD=$ICECAST_ADMIN_PASSWORD
ICECAST_RELAY_PASSWORD=$ICECAST_RELAY_PASSWORD
# ---------------------------------------------------------------------------
ENDF
EOF
printf "\nAVISO: No dejes estas contraseñas en el repositorio. Copia y pégalas en la UI de EasyPanel y marca las variables como SECRET.\n"

59
easypanel/service.env Normal file
View File

@ -0,0 +1,59 @@
# Archivo de variables para EasyPanel (alineado con docker-compose.easypanel.yml)
# Copia/pega este bloque en la sección Environment/Secrets del servicio en EasyPanel.
# Marca como SECRET las variables indicadas (POSTGRES_PASSWORD, RABBITMQ_DEFAULT_PASS, ICECAST_*_PASSWORD, etc.).
# ------------------ General ------------------
# Versión de las imágenes de LibreTime (usa la etiqueta de la imagen)
LIBRETIME_VERSION=4.5
# URL pública de la instalación (REEMPLAZA por tu dominio)
LIBRETIME_GENERAL_PUBLIC_URL=https://tu-dominio.example
# Habilitar debug (false en producción)
LIBRETIME_DEBUG=false
# Claves opcionales para la API/secretos internos (opcional)
LIBRETIME_API_KEY=
LIBRETIME_SECRET_KEY=
# ------------------ Postgres ------------------
POSTGRES_USER=libretime
# SECRET (REQUIRED): contraseña de Postgres
POSTGRES_PASSWORD=Jz/XxRUodVl2g0HE59DszTBJVY8Sdmv7
POSTGRES_DB=libretime
# Opcional: host/puerto si los cambias
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
# ------------------ RabbitMQ ------------------
RABBITMQ_DEFAULT_VHOST=/libretime
RABBITMQ_DEFAULT_USER=libretime
# SECRET (REQUIRED): contraseña de RabbitMQ
RABBITMQ_DEFAULT_PASS=Bn321PQSRXanvmZlppuulVCB0ShN5Dz2
RABBITMQ_HOST=rabbitmq
RABBITMQ_PORT=5672
# ------------------ Nginx / puertos ------------------
# EasyPanel suele gestionar ruteo; ajusta si es necesario
NGINX_PORT=8080
NGINX_WORKER_PROCESSES=auto
# ------------------ Icecast ------------------
ICECAST_PORT=8000
# SECRET: contraseñas de Icecast (marcar como secret)
ICECAST_SOURCE_PASSWORD=dna1g1GcaaHakSN6C9X7rcPRpIIc/jV2
ICECAST_ADMIN_PASSWORD=BLoYLPlUXfmkxrsvGF7LP0TtVtuKNuzJ
ICECAST_RELAY_PASSWORD=jYzhEjwdiJlTk30QOYHum6UE61FHo+sd
ICECAST_ADMIN_USER=admin
ICECAST_HOSTNAME=stream.tu-dominio.example
# ------------------ Liquidsoap ------------------
LIQUIDSOAP_HARBOR_PORT=8001
LIQUIDSOAP_TELNET_PORT=8002
# ------------------ Opcionales / Compatibilidad ------------------
# Estas variables las usa el generador de config; es seguro incluirlas aquí
RABBITMQ_HOST=${RABBITMQ_HOST:-rabbitmq}
RABBITMQ_PORT=${RABBITMQ_PORT:-5672}
POSTGRES_HOST=${POSTGRES_HOST:-postgres}
POSTGRES_PORT=${POSTGRES_PORT:-5432}
# FIN: reemplaza los valores marcados con 'CAMBIA...' por secretos reales y
# marca las variables sensibles como SECRET en la UI de EasyPanel antes del deploy.

View File

@ -12,9 +12,53 @@ const SRC = path.resolve(__dirname, '../docker-compose.easypanel.yml');
const DEST_DIR = path.resolve(__dirname, './code');
const DEST = path.join(DEST_DIR, 'docker-compose.yml');
// Variables secret requeridas para evitar despliegues con valores vacíos
const REQUIRED_SECRETS = ['POSTGRES_PASSWORD', 'RABBITMQ_DEFAULT_PASS'];
function parseEnvFile(filePath) {
const out = {};
if (!fs.existsSync(filePath)) return out;
const raw = fs.readFileSync(filePath, 'utf8');
raw.split(/\n/).forEach(line => {
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: <valor> 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);

View File

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