#!/bin/bash
#===============================================================================
# backup_total.sh — Copia nocturna del servidor (Ebenezer IT · Guía 13)
#-------------------------------------------------------------------------------
# Se ejecuta como root desde su crontab (03:00). Hace una copia local en
# /home/TU_USUARIO/backups/FECHA, guarda 7 días en el servidor y sube todo cifrado a
# Google Drive (rclone, remoto gdrive-cifrado), donde guarda 14 días.
#
# Código de salida: 0 = todo bien | 1 = algún paso falló (mira el registro)
# Versión 2.1 · noviembre de 2026
#===============================================================================
set -uo pipefail     # -u: error si se usa una variable sin definir
                     # pipefail: una tubería falla si falla cualquiera de sus partes
umask 077            # todo lo que se crea, solo legible por root

# --- CONFIGURACIÓN ---
FECHA=$(date +%Y-%m-%d_%H%M)
BASE="/home/TU_USUARIO/backups"
DIR_BACKUP="$BASE/$FECHA"
LOG_FILE="$BASE/backup_log_$(date +%F).txt"
REMOTO="gdrive-cifrado:servidor-backups"
DIAS_LOCAL=7
DIAS_REMOTO=14
ERRORES=0

mkdir -p "$DIR_BACKUP"

# --- FUNCIONES ---
# log: escribe en pantalla y en el registro
log() { echo "$*" | tee -a "$LOG_FILE"; }

# paso: ejecuta un comando; si falla, lo anota y suma un error (pero sigue)
paso() {
    local desc="$1"; shift
    if "$@" >> "$LOG_FILE" 2>&1; then
        log "      OK - $desc"
    else
        log "      ERROR - $desc"
        ERRORES=$((ERRORES + 1))
    fi
}

# empaqueta: tar.gz tolerante con archivos que cambian o no existen
#   (código 1 de tar = "algún archivo cambió mientras se leía": es normal en
#    registros y buzones vivos; código 2 = error de verdad)
empaqueta() {
    local salida="$1"; shift
    tar -czf "$DIR_BACKUP/$salida" --ignore-failed-read --warning=no-file-changed "$@"
    [ $? -le 1 ]
}

tamano() { du -h "$DIR_BACKUP/$1" 2>/dev/null | cut -f1; }

log "=== INICIO COPIA: $FECHA ==="

# --- 1. BASES DE DATOS (correo, WordPress, Roundcube...) ---
log "[1/10] MariaDB..."
# --single-transaction: una foto coherente de las tablas InnoDB sin bloquearlas
# --quick: lee fila a fila, sin cargar tablas enteras en memoria
paso "MariaDB" bash -c "set -o pipefail; mysqldump --all-databases --single-transaction --quick --routines --events --triggers | gzip > '$DIR_BACKUP/db_full_dump.sql.gz'"
log "      $(tamano db_full_dump.sql.gz)"

# --- 2. CONFIGURACIONES DEL SISTEMA ---
log "[2/10] Configuraciones..."
paso "Configuraciones" empaqueta configs_sistema.tar.gz \
    /etc/nginx /etc/postfix /etc/dovecot /etc/opendkim /etc/opendkim.conf \
    /etc/rspamd /etc/redis /etc/unbound /etc/mysql /etc/php \
    /etc/ufw /etc/letsencrypt /etc/ssh /etc/fail2ban /etc/crowdsec \
    /etc/ejabberd /etc/apparmor.d/local /etc/systemd/system /etc/rsyslog.d \
    /etc/logwatch /etc/crontab /etc/cron.d /etc/cron.daily /etc/cron.hourly \
    /etc/hosts /etc/hostname /etc/resolv.conf /etc/aliases /etc/mailname \
    /usr/local/bin /opt/monitoring
log "      $(tamano configs_sistema.tar.gz)"

# --- 3. REDIS (lo que Rspamd ha aprendido del spam) ---
log "[3/10] Redis..."
# SAVE vuelca a disco lo que Redis tiene en memoria; después se copia el archivo
if command -v redis-cli > /dev/null; then
    paso "Redis" sh -c "redis-cli -h 127.0.0.1 SAVE > /dev/null && cp /var/lib/redis/dump.rdb '$DIR_BACKUP/redis_dump.rdb'"
    log "      $(tamano redis_dump.rdb)"
else
    log "      (sin Redis)"
fi

# --- 4. ARCHIVOS WEB ---
log "[4/10] Webs..."
paso "Webs" empaqueta web_files.tar.gz \
    /var/www \
    /usr/share/roundcube/plugins /usr/share/roundcube/skins /etc/roundcube
log "      $(tamano web_files.tar.gz)"
# Roundcube guarda contactos, identidades y ajustes en SQLite (no en MariaDB).
# Con el programa sqlite3, ".backup" hace una copia coherente aunque esté en uso.
RC_DB=/var/lib/dbconfig-common/sqlite3/roundcube/roundcube
if [ ! -f "$RC_DB" ]; then
    log "      (Roundcube sin SQLite: su base, si la tiene, va en MariaDB)"
elif command -v sqlite3 > /dev/null; then
    paso "Roundcube (SQLite)" sqlite3 "$RC_DB" ".backup '$DIR_BACKUP/roundcube.sqlite'"
else
    paso "Roundcube (SQLite, copia simple)" cp "$RC_DB" "$DIR_BACKUP/roundcube.sqlite"
fi

# --- 5. CORREOS ---
log "[5/10] Correos..."
paso "Correos" empaqueta emails.tar.gz /var/mail/vhosts
log "      $(tamano emails.tar.gz)"

# --- 6. DOCKER ---
log "[6/10] Docker..."
# Fuera: las métricas de Prometheus (se regeneran y pesan mucho) y la carpeta de
# Postgres de Chatwoot en caliente (su copia buena la hace pg_dump: Guía 04)
paso "Volúmenes de Docker" empaqueta docker_data.tar.gz \
    --exclude='*/monitoring_prometheus-data' \
    --exclude='*/chatwoot_postgres_data' \
    /var/lib/docker/volumes /etc/docker
log "      $(tamano docker_data.tar.gz)"
paso "Archivos docker-compose" bash -c "find /home /opt /root \( -name 'docker-compose*.yml' -o -name 'docker-compose*.yaml' \) -print0 2>/dev/null | tar -czf '$DIR_BACKUP/docker_compose_files.tar.gz' --null -T -"

# --- 7. EJABBERD (chat) ---
log "[7/10] ejabberd..."
# Copia coherente de su base de datos (Mnesia) antes de empaquetar la carpeta
if [ -x /usr/sbin/ejabberdctl ]; then
    paso "ejabberd: copia de la base de datos" /usr/sbin/ejabberdctl backup /var/lib/ejabberd/copia-ejabberd.backup
    paso "ejabberd: carpeta" empaqueta ejabberd_data.tar.gz /var/lib/ejabberd /etc/ejabberd
    log "      $(tamano ejabberd_data.tar.gz)"
else
    log "      (sin ejabberd)"
fi

# --- 8. TAREAS PROGRAMADAS DE USUARIOS ---
log "[8/10] Crontabs..."
paso "Crontabs" empaqueta cron_jobs.tar.gz /var/spool/cron/crontabs

# --- 9. CHATWOOT Y /opt/apps ---
log "[9/10] Chatwoot y /opt/apps..."
# Si tienes Chatwoot (Guía 04), su copia de las 02:45 deja bd-, config- y archivos-FECHA
if [ -d /var/backups/chatwoot ]; then
    paso "Copia de Chatwoot de hoy" sh -c "ls /var/backups/chatwoot/*-$(date +%F).* > /dev/null 2>&1 && cp /var/backups/chatwoot/*-$(date +%F).* '$DIR_BACKUP/'"
fi
[ -d /opt/apps ] && paso "/opt/apps" empaqueta opt_apps.tar.gz /opt/apps

# --- 10. ESTE SCRIPT ---
log "[10/10] El propio script..."
paso "Script" cp "$0" "$DIR_BACKUP/backup_total.sh"

log ""
log "=== ARCHIVOS GENERADOS ==="
ls -lh "$DIR_BACKUP/" | tee -a "$LOG_FILE"
log "Total: $(du -sh "$DIR_BACKUP" | cut -f1)"

# --- LIMPIEZA LOCAL ---
log ""
log "Borrando copias locales de más de $DIAS_LOCAL días..."
find "$BASE" -mindepth 1 -maxdepth 1 -type d -mtime +"$DIAS_LOCAL" -exec rm -rf {} + 2>> "$LOG_FILE"
find "$BASE" -maxdepth 1 -name "backup_log_*.txt" -mtime +"$DIAS_REMOTO" -delete 2>> "$LOG_FILE"

# --- COPIA A GOOGLE DRIVE (cifrada) ---
log ""
log "Subiendo a Google Drive..."
rclone mkdir "$REMOTO" >> "$LOG_FILE" 2>&1
paso "Subida a Google Drive" rclone copy "$BASE" "$REMOTO" --exclude "last_log.txt"

log "Borrando de Google Drive las copias de más de $DIAS_REMOTO días..."
LIMITE=$(date -d "$DIAS_REMOTO days ago" +%F)
for d in $(rclone lsf "$REMOTO" --dirs-only 2>> "$LOG_FILE"); do
    dia=${d%%_*}
    if [[ "$dia" < "$LIMITE" ]]; then
        log "  Borrando de Drive: $d"
        rclone purge "$REMOTO/$d" --drive-use-trash=false >> "$LOG_FILE" 2>&1
    fi
done
rclone delete "$REMOTO" --max-depth 1 --include "backup_log_*.txt" --min-age "${DIAS_REMOTO}d" \
    --drive-use-trash=false --tpslimit 2 >> "$LOG_FILE" 2>&1

# --- RESULTADO ---
log ""
if [ "$ERRORES" -eq 0 ]; then
    log "=== COPIA COMPLETADA SIN ERRORES: $FECHA ==="
    exit 0
else
    log "=== COPIA CON $ERRORES ERROR(ES): $FECHA — revisa las líneas ERROR ==="
    exit 1
fi
