Script para Backup Automático de Icecast: protege tus streams 24/7
Nos dimos cuenta de que el Script para Backup Automático de Icecast era una necesidad urgente cuando una caída inesperada dejó sin acceso a nuestras transmisiones. Cada madrugada trabajamos en La MIX Radio y Radio OnE, y el miedo a perder configuraciones y logs nos mantenía despiertos.
Aquí vamos a mostrar cómo lo implementamos en nuestro homelab Proxmox usando Python y rsync.
El objetivo es que, con un solo archivo, tengas copias diarias en una ubicación segura y puedas restaurarlas en minutos.
El problema: necesitábamos un Script para Backup Automático de Icecast fiable
Sin backups, una actualización de Icecast que falla o una corrupción del disco puede dejar sin emisión a la radio.
Los síntomas fueron claros: al reiniciar el servicio después de un corte de energía, Icecast no encontraba su archivo icecast.xml, y los logs se habían perdido.
Además, los archivos de audio temporales en /var/lib/icecast desaparecían, provocando interrupciones en la cadena de streaming.
Cada minuto sin transmitir se traduce en oyentes que se van y en pérdida de reputación.
Por eso decidimos automatizar la tarea y no depender de recordatorios manuales.
Qué necesitamos antes de comenzar
- Una VM en Proxmox (usamos la VM 111) con Python 3.10 o superior.
- Acceso root o sudo a la máquina que ejecuta Icecast.
- Rutas clave:
/etc/icecast2/icecast.xml,/var/log/icecast,/var/lib/icecast. - Un destino de backup: disco externo montado en
/mnt/backupo un share NFS. - rsync instalado y configurado.
- Un crontab para programar la ejecución diaria.
Cómo crear el Script para Backup Automático de Icecast paso a paso
Primero preparamos la estructura de carpetas en el destino.
Creamos una carpeta con la fecha del día para mantener versiones separadas.
#!/usr/bin/env python3
import os
import subprocess
import datetime
import tarfile
import logging
# Configuración básica
ICECAST_CONF = "/etc/icecast2/icecast.xml"
ICECAST_LOG = "/var/log/icecast"
ICECAST_DATA = "/var/lib/icecast"
BACKUP_ROOT = "/mnt/backup/icecast"
RETENTION_DAYS = 30
logging.basicConfig(
filename="/var/log/icecast_backup.log",
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s"
)
def run_cmd(command):
"""Ejecuta un comando del sistema y devuelve stdout."""
result = subprocess.run(command, shell=True, capture_output=True, text=True)
if result.returncode != 0:
logging.error(f"Error ejecutando: {command} - {result.stderr.strip()}")
raise RuntimeError(result.stderr.strip())
return result.stdout.strip()
def create_snapshot_dir():
"""Crea la carpeta del snapshot con la fecha actual."""
today = datetime.date.today().isoformat()
path = os.path.join(BACKUP_ROOT, today)
os.makedirs(path, exist_ok=True)
return path
def backup_file(src, dst_dir):
"""Copia un archivo único usando rsync."""
cmd = f"rsync -a --delete {src} {dst_dir}"
run_cmd(cmd)
def backup_folder(src, dst_dir):
"""Copia una carpeta completa usando rsync."""
cmd = f"rsync -a --delete {src}/ {dst_dir}/"
run_cmd(cmd)
def create_tarball(src_dir, output_path):
"""Comprime la carpeta en un tar.gz."""
with tarfile.open(output_path, "w:gz") as tar:
tar.add(src_dir, arcname=os.path.basename(src_dir))
def clean_old_backups():
"""Elimina snapshots más antiguos que RETENTION_DAYS."""
cutoff = datetime.date.today() - datetime.timedelta(days=RETENTION_DAYS)
for entry in os.listdir(BACKUP_ROOT):
entry_path = os.path.join(BACKUP_ROOT, entry)
if os.path.isdir(entry_path):
try:
entry_date = datetime.datetime.strptime(entry, "%Y-%m-%d").date()
if entry_date < cutoff:
run_cmd(f"rm -rf {entry_path}")
logging.info(f"Removed old backup: {entry_path}")
except ValueError:
# Ignorar carpetas que no siguen el formato de fecha
continue
def main():
logging.info("Inicio del backup de Icecast")
snapshot_dir = create_snapshot_dir()
# Backup de la configuración
backup_file(ICECAST_CONF, snapshot_dir)
# Backup de logs y datos
backup_folder(ICECAST_LOG, os.path.join(snapshot_dir, "logs"))
backup_folder(ICECAST_DATA, os.path.join(snapshot_dir, "data"))
# Crear tarball comprimido
tar_path = f"{snapshot_dir}.tar.gz"
create_tarball(snapshot_dir, tar_path)
# Limpiar directorios temporales
run_cmd(f"rm -rf {snapshot_dir}")
# Eliminar backups viejos
clean_old_backups()
logging.info(f"Backup completado: {tar_path}")
if __name__ == "__main__":
main()
El script hace lo siguiente: crea una carpeta con la fecha, copia la configuración, logs y datos usando rsync, comprime todo en un .tar.gz y elimina los snapshots más antiguos que 30 días.
Guardamos el registro en /var/log/icecast_backup.log para poder revisar cualquier incidencia.
Una vez guardado como /usr/local/bin/icecast_backup.py y marcado como ejecutable, añadimos la tarea al crontab del usuario root:
# m h dom mon dow command
0 2 * * * /usr/bin/python3 /usr/local/bin/icecast_backup.py >/dev/null 2>&1
Con esta línea, el backup se ejecuta todos los días a las 2 AM, justo cuando el tráfico es bajo.
El script envía su salida a /dev/null porque ya registra todo en su propio log.
Errores que nos costaron tiempo
- Síntoma: El archivo
icecast_backup.logmostraba “Permission denied” al intentar escribir. - Causa: El script se ejecutaba como usuario sin permisos de escritura en
/var/log. - Solución: Cambiamos la ruta del log a
/var/log/icecast_backup.logy le dimos permisos 664 al archivo. - Síntoma: Los backups estaban vacíos.
- Causa: Usamos
rsync -a --delete src/ dst/pero la barra final ensrc/faltaba, provocando que rsync copyara la carpeta completa dentro dedst/src. - Solución: Añadimos la barra al final del origen para copiar el contenido directamente.
- Síntoma: El script fallaba al crear el tarball con “File name too long”.
- Causa: El nombre del snapshot incluía la ruta completa
/mnt/backup/icecast/2023-09-01y al comprimir se duplicaba la ruta. - Solución: Usamos
arcname=os.path.basename(src_dir)entar.addpara acortar la ruta dentro del archivo.
Cómo saber que funciona
Revisamos /var/log/icecast_backup.log y buscamos la línea “Backup completado”.
Luego verificamos que el archivo .tar.gz exista en /mnt/backup/icecast. Un tar -tzf del archivo nos muestra la estructura esperada (icecast.xml, logs/, data/).
Finalmente, simulamos una restauración copiando el tarball a una VM de prueba y arrancando Icecast con la configuración restaurada.
Qué sigue
Ahora que el backup automático funciona, añadimos una notificación a Telegram usando un webhook de n8n.
Así recibimos un mensaje cada vez que el script termina, y podemos reaccionar rápido si algo falla.
El Script para Backup Automático de Icecast nos devolvió la tranquilidad de saber que, aunque el servidor se caiga, nuestras emisoras pueden volver en minutos.

