File: //scripts/backups_local.sh
#!/bin/bash
# =============================================
# CONFIGURACIÓN
# =============================================
# Directorios locales
HTDOCS_DIR="/var/www/html/"
BACKUP_BASE_DIR="/backups"
LOG_DIR="$HOME/backups_logs"
# Configuración MySQL
MYSQL_USER="dolibarr"
MYSQL_PASS="LXfLpdkhsdsdd334H1$"
MYSQL_HOST="localhost"
# Notificaciones
EMAIL="ame@unboxerp.com"
# Días a mantener (basado en fecha, no en cantidad de archivos)
MAX_BACKUP_DAYS_DB=1 # Mantener backups de bases de datos de los últimos 7 días
MAX_BACKUP_DAYS_FILES=8 # Mantener backups de archivos de los últimos 3 días
# URL para POST
POST_URL="http://mstoluca.com:5678/webhook/4e43236c-fb54-46f1-9f51-eb3a19ad9bff"
# =============================================
# INICIALIZACIÓN
# =============================================
mkdir -p "${BACKUP_BASE_DIR}/files" "${BACKUP_BASE_DIR}/sql" "${LOG_DIR}"
TIMESTAMP=$(date +"%Y%m%d-%H%M%S")
DATE_DIR=$(date +"%Y/%m")
LOG_FILE="${LOG_DIR}/backup_${TIMESTAMP}.log"
LOCAL_IP=$(hostname -I | awk '{print $1}')
# Iniciar log
{
echo "============================================="
echo "INICIO DE BACKUP: $(date '+%Y-%m-%d %H:%M:%S')"
echo "Host: $(hostname)"
echo "IP Local: $LOCAL_IP"
echo "Tipo de backup: $1"
echo "============================================="
} > "${LOG_FILE}"
log() {
local level=$1
local message=$2
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [${level}] ${message}" | tee -a "${LOG_FILE}"
}
enviar_post() {
local archivo=$1
local origen=$2
local destino=$3
local tamanio=$4
local status=$5
# Leer el contenido completo del archivo de log y codificarlo para URL manteniendo saltos de línea
local log_content=$(cat "${LOG_FILE}" 2>/dev/null | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/%0A/g')
log "INFO" "Enviando POST - Archivo: $archivo - Tamaño: '$tamanio' - Status: $status"
curl -s -X POST "$POST_URL" \
-d "fecha=$(date '+%Y-%m-%d %H:%M:%S')" \
-d "archivo=$archivo" \
-d "origen=$origen" \
-d "destino=$destino" \
-d "ip_origen=$LOCAL_IP" \
-d "ip_destino=" \
-d "tamanio=$tamanio" \
-d "status=$status" \
-d "log=${log_content}" > /dev/null 2>&1 || {
log "ERROR" "Fallo al enviar POST"
}
}
# =============================================
# FUNCIONES DE LIMPIEZA
# =============================================
limpiar_backups_antiguos_sql() {
local dir="${BACKUP_BASE_DIR}/sql"
local dias=$1
# Calcular la fecha límite (hace X días)
local fecha_limite=$(date -d "${dias} days ago" +%Y%m%d)
log "INFO" "🧹 Limpiando backups de SQL (manteniendo últimos ${dias} días)"
log "INFO" " Fecha límite: $fecha_limite"
# Obtener lista de bases de datos únicas (el nombre antes del primer _)
for db_prefix in $(find "$dir" -name "*.sql.gz" 2>/dev/null | sed 's/_.*//' | sort -u); do
local db_name=$(basename "$db_prefix")
log "INFO" " Procesando base de datos: $db_name"
# Listar backups de esta base y eliminar los anteriores a la fecha límite
find "$dir" -name "${db_name}_*.sql.gz" 2>/dev/null | while read -r archivo; do
# Extraer fecha del nombre del archivo (formato: nombre_YYYY-MM-DD_HHMMSS.sql.gz)
local fecha_archivo=$(basename "$archivo" | grep -oP '\d{4}-\d{2}-\d{2}' | head -1 | tr -d '-')
if [ -n "$fecha_archivo" ]; then
if [ "$fecha_archivo" -lt "$fecha_limite" ]; then
local file_size=$(du -h "$archivo" 2>/dev/null | awk '{print $1}')
if [ -z "$file_size" ]; then
file_size="0"
fi
if rm "$archivo" 2>/dev/null; then
log "INFO" " ✓ Eliminado: $(basename "$archivo") (fecha: $fecha_archivo)"
# Enviar POST con status 9 indicando eliminación
enviar_post "$(basename "$archivo")" "$archivo" "ELIMINADO" "$file_size" "9"
fi
fi
fi
done
done
}
limpiar_backups_antiguos_files() {
local dir="${BACKUP_BASE_DIR}/files"
local dias=$1
# Calcular la fecha límite (hace X días)
local fecha_limite=$(date -d "${dias} days ago" +%Y%m%d)
log "INFO" "🧹 Limpiando backups de FILES (manteniendo últimos ${dias} días)"
log "INFO" " Fecha límite: $fecha_limite"
# Eliminar archivos ZIP anteriores a la fecha límite
find "$dir" -name "*.zip" 2>/dev/null | while read -r archivo; do
# Extraer fecha del nombre del archivo (formato: nombre_YYYY-MM-DD_HHMMSS.zip)
local fecha_archivo=$(basename "$archivo" | grep -oP '\d{4}-\d{2}-\d{2}' | head -1 | tr -d '-')
if [ -n "$fecha_archivo" ]; then
if [ "$fecha_archivo" -lt "$fecha_limite" ]; then
local file_size=$(du -h "$archivo" 2>/dev/null | awk '{print $1}')
if [ -z "$file_size" ]; then
file_size="0"
fi
if rm "$archivo" 2>/dev/null; then
log "INFO" " ✓ Eliminado: $(basename "$archivo") (fecha: $fecha_archivo)"
# Enviar POST con status 9 indicando eliminación
enviar_post "$(basename "$archivo")" "$archivo" "ELIMINADO" "$file_size" "9"
fi
fi
fi
done
}
# =============================================
# BACKUP DE BASES DE DATOS
# =============================================
backup_databases() {
log "INFO" "=== INICIANDO BACKUP DE BASES DE DATOS ==="
local db_backup_dir="${BACKUP_BASE_DIR}/sql/${DATE_DIR}"
mkdir -p "${db_backup_dir}"
# Obtener lista de bases de datos
DATABASES=$(mysql -h "${MYSQL_HOST}" -u "${MYSQL_USER}" -p"${MYSQL_PASS}" -e "SHOW DATABASES;" 2>/dev/null | grep -Ev "(Database|information_schema|performance_schema|mysql|sys)")
if [ -z "$DATABASES" ]; then
log "ERROR" "No se pudo obtener lista de bases de datos"
log "INFO" "Verifica usuario y contraseña de MySQL"
enviar_post "ERROR_MYSQL" "MySQL" "N/A" "0" "1"
return 1
fi
local count=0
for DB in $DATABASES; do
count=$((count + 1))
# Formato: nombre_base_fecha(año-mes-dia)_hora.sql.gz
local backup_file="${db_backup_dir}/${DB}_$(date +'%Y-%m-%d')_$(date +'%H%M%S').sql.gz"
log "INFO" "📦 [$count] Base de datos: ${DB}"
if mysqldump -h "${MYSQL_HOST}" -u "${MYSQL_USER}" -p"${MYSQL_PASS}" --single-transaction --quick "$DB" 2>/dev/null | gzip > "$backup_file"; then
# Verificar que el archivo existe y obtener tamaño
if [ -f "$backup_file" ]; then
local file_size=$(du -h "$backup_file" 2>/dev/null | awk '{print $1}')
# Si el tamaño está vacío, asignar "0"
if [ -z "$file_size" ]; then
file_size="0"
fi
else
local file_size="0"
fi
log "INFO" " ✓ Backup creado: $(basename "$backup_file") (${file_size})"
enviar_post "$(basename "$backup_file")" "$backup_file" "${BACKUP_BASE_DIR}/sql/" "$file_size" "0"
else
log "ERROR" " ✗ Fallo al crear backup de ${DB}"
enviar_post "${DB}_$(date +'%Y-%m-%d')_$(date +'%H%M%S').sql.gz" "MySQL" "N/A" "0" "1"
fi
done
# Limpiar backups antiguos de SQL usando los días configurados
limpiar_backups_antiguos_sql "$MAX_BACKUP_DAYS_DB"
log "INFO" "=== BACKUP DE BASES DE DATOS COMPLETADO ($count bases) ==="
}
# =============================================
# BACKUP DE ARCHIVOS
# =============================================
backup_files() {
log "INFO" "=== INICIANDO BACKUP DE ARCHIVOS ==="
local files_backup_dir="${BACKUP_BASE_DIR}/files/${DATE_DIR}"
mkdir -p "${files_backup_dir}"
if [ ! -d "$HTDOCS_DIR" ]; then
log "ERROR" "Directorio no encontrado: ${HTDOCS_DIR}"
enviar_post "$(basename "$HTDOCS_DIR")_$(date +'%Y-%m-%d')_$(date +'%H%M%S').zip" "$HTDOCS_DIR" "N/A" "0" "1"
return 1
fi
local folder_name=$(basename "$HTDOCS_DIR")
# Formato: nombre_carpeta_fecha(año-mes-dia)_hora.zip
local backup_file="${files_backup_dir}/${folder_name}_$(date +'%Y-%m-%d')_$(date +'%H%M%S').zip"
log "INFO" "📦 Comprimiendo carpeta: ${folder_name}"
log "INFO" "Esto puede tomar varios minutos dependiendo del tamaño..."
# Obtener tamaño aproximado antes de comprimir
local folder_size=$(du -sh "$HTDOCS_DIR" 2>/dev/null | awk '{print $1}')
log "INFO" "Tamaño aproximado: $folder_size"
# Comprimir
if cd "$(dirname "$HTDOCS_DIR")" && zip -rq "$backup_file" "$(basename "$HTDOCS_DIR")"; then
# Verificar que el archivo existe y obtener tamaño
if [ -f "$backup_file" ]; then
local file_size=$(du -h "$backup_file" 2>/dev/null | awk '{print $1}')
# Si el tamaño está vacío, asignar "0"
if [ -z "$file_size" ]; then
file_size="0"
fi
else
local file_size="0"
fi
log "INFO" " ✓ Backup creado: $(basename "$backup_file") (${file_size})"
enviar_post "$(basename "$backup_file")" "$HTDOCS_DIR" "${BACKUP_BASE_DIR}/files/" "$file_size" "0"
else
log "ERROR" " ✗ Fallo al comprimir carpeta"
enviar_post "${folder_name}_$(date +'%Y-%m-%d')_$(date +'%H%M%S').zip" "$HTDOCS_DIR" "N/A" "0" "1"
fi
# Limpiar backups antiguos de FILES usando los días configurados
limpiar_backups_antiguos_files "$MAX_BACKUP_DAYS_FILES"
log "INFO" "=== BACKUP DE ARCHIVOS COMPLETADO ==="
}
# =============================================
# VERIFICACIONES INICIALES
# =============================================
verificar_comandos() {
log "INFO" "🔍 Verificando comandos necesarios..."
local missing=0
for cmd in mysql mysqldump zip curl; do
if ! command -v "$cmd" &>/dev/null; then
log "ERROR" " ✗ Comando '$cmd' no encontrado"
missing=1
else
log "INFO" " ✓ $cmd encontrado"
fi
done
if [ $missing -eq 1 ]; then
log "ERROR" "Faltan comandos necesarios. Abortando."
enviar_post "ERROR_COMANDOS" "Sistema" "N/A" "0" "1"
exit 1
fi
}
verificar_mysql() {
log "INFO" "🔍 Verificando conexión MySQL..."
if mysql -h "${MYSQL_HOST}" -u "${MYSQL_USER}" -p"${MYSQL_PASS}" -e "SELECT 1" &>/dev/null; then
log "INFO" " ✓ Conexión MySQL establecida"
return 0
else
log "ERROR" " ✗ No se puede conectar a MySQL"
echo ""
echo "=================================================="
echo "ERROR DE CONEXIÓN MYSQL"
echo "=================================================="
echo "Verifica que:"
echo "1. MySQL está corriendo"
echo "2. Usuario y contraseña son correctos"
echo ""
echo "Configuración actual:"
echo " Host: ${MYSQL_HOST}"
echo " User: ${MYSQL_USER}"
echo " Pass: [oculto]"
echo "=================================================="
enviar_post "ERROR_MYSQL" "MySQL" "N/A" "0" "1"
exit 1
fi
}
# =============================================
# EJECUCIÓN PRINCIPAL
# =============================================
# Mostrar banner
echo ""
echo "=================================================="
echo " SCRIPT DE BACKUP AUTOMÁTICO (LOCAL)"
echo "=================================================="
echo ""
# Verificar comandos y MySQL
verificar_comandos
verificar_mysql
# Procesar argumento
case "$1" in
"db")
backup_databases
;;
"files")
backup_files
;;
"full")
backup_databases
backup_files
;;
*)
echo "Uso: $0 [db|files|full]"
echo ""
echo "Opciones:"
echo " db - Solo backup de bases de datos"
echo " files - Solo backup de archivos"
echo " full - Backup completo (bases de datos + archivos)"
echo ""
echo "Ejemplo:"
echo " $0 full"
exit 1
;;
esac
# Finalización
log "INFO" "============================================="
log "INFO" "✅ BACKUP COMPLETADO: $(date '+%Y-%m-%d %H:%M:%S')"
log "INFO" "📝 Log guardado en: ${LOG_FILE}"
log "INFO" "============================================="
# Mostrar resumen final
echo ""
echo "=================================================="
echo "RESUMEN FINAL"
echo "=================================================="
echo "Backups guardados en: ${BACKUP_BASE_DIR}"
echo " - Archivos: ${BACKUP_BASE_DIR}/files/ (mantiene ${MAX_BACKUP_DAYS_FILES} días)"
echo " - SQL: ${BACKUP_BASE_DIR}/sql/ (mantiene ${MAX_BACKUP_DAYS_DB} días)"
echo "Log completo: ${LOG_FILE}"
echo "=================================================="
# Enviar correo si está disponible
if command -v mail &>/dev/null; then
tail -n 30 "${LOG_FILE}" | mail -s "Backup Completado - $(date '+%Y-%m-%d')" "${EMAIL}"
log "INFO" "Correo enviado a ${EMAIL}"
fi
exit 0