Con el entorno de laboratorio listo, es hora de automatizar rutinas de mantenimiento reales. La parte 3 de la serie te guía a través del diseño de un script de Bash que respalda datos de configuración, limpia artefactos vencidos y registra cada acción en un log con timestamp. Al final de esta lección, entenderás cómo los condicionales, los bucles y los argumentos trabajan juntos para mantener saludables a los servidores Linux.
Objetivos de aprendizaje
Construir un script de Bash que verifique prerrequisitos, cree backups comprimidos y maneje condiciones de fallo con elegancia.
Emplear helpers de logging reutilizables para que cada paso emita mensajes estructurados para las pistas de auditoría.
Iterar sobre colecciones usando bucles para identificar y eliminar archivos obsoletos sin eliminar datos recientes.
Parametrizar el script con argumentos posicionales para adaptarse a distintos entornos.
Reflexionar sobre las estrategias de rutas, la portabilidad y las salvaguardas de seguridad usadas en la automatización de mantenimiento.
Escenario: automatizando el mantenimiento preventivo en Acme
El equipo de seguridad de Acme lanzó recientemente un programa de monitoreo preventivo. En lugar de ejecutar comandos puntuales manualmente, quieren un script confiable que operaciones pueda programar a través de cron. El script debe:
Preparar o reutilizar un directorio de backup.
Archivar /etc (u otro objetivo) con un nombre de archivo con timestamp.
Eliminar los archivos .bak con más de 30 días de antigüedad.
Registrar el progreso y los errores para revisión posterior.
Se te ha encomendado producir la versión inicial, validar su comportamiento y documentar por qué cada decisión de diseño respalda la seguridad y la portabilidad.
Recorrido del script — mantenimiento.sh
La implementación a continuación sigue los requisitos y añade las salvaguardas que aprendimos en partes anteriores de la serie.
#!/usr/bin/env bash# Automate maintenance/auditing workflow with validations and logging.# Args (optional):# 1) SOURCE_DIR - directory to back up (default: /etc)# 2) DEST_FOLDER - destination folder name (default: backups)# ===== Defaults & Args =====SOURCE_DIR="${1:-/etc}"DEST_FOLDER="${2:-backups}"# Validate source directory before doing anything elseif[[!-d"$SOURCE_DIR"]];thenprintf'Error: source directory does not exist: %s\n'"$SOURCE_DIR">&2exit1fi# Resolve an absolute path to avoid surprises with relative inputif!SOURCE_ABS=$(cd"$SOURCE_DIR"2>/dev/null &&pwd-P);thenprintf'Error: unable to access source directory: %s\n'"$SOURCE_DIR">&2exit1fi# Date stampsSTAMP="$(date'+%Y%m%d_%H%M%S')"# PathsSCRIPT_DIR="$(pwd)"DEST_PATH="${SCRIPT_DIR}/${DEST_FOLDER}"if!mkdir-p"$DEST_PATH";thenprintf'Error: failed to create destination folder: %s\n'"$DEST_PATH">&2exit1fiLOG_FILE="${DEST_PATH}/mantenimiento_${STAMP}.log"if!touch"$LOG_FILE"2>/dev/null;thenprintf'Error: cannot write log file: %s\n'"$LOG_FILE">&2exit1fitimestamp(){date'+%Y-%m-%d %H:%M:%S';}log_line(){locallevel="$1"shiftlocaltarget="$1"shiftlocalmessage="$*"local formatted
formatted=$(printf'[%s] %s | %s\n'"$level""$(timestamp)""$message")if[["$target"=="stderr"]];thenprintf'%s'"$formatted"|tee-a"$LOG_FILE">&2elseprintf'%s'"$formatted"|tee-a"$LOG_FILE"fi}log_info(){ log_line "INFO""stdout""$@";}log_warn(){ log_line "WARN""stderr""$@";}log_error(){ log_line "ERROR""stderr""$@";}log_info "=== Maintenance run started ==="log_info "Source: ${SOURCE_ABS}"log_info "Destination: ${DEST_PATH}"# ===== A) Ensure backup folder exists =====log_info "Ensuring backup folder exists: ${DEST_PATH}"if[[-d"$DEST_PATH"]];then log_info "Backup folder ready."else log_error "Backup folder is not available after creation attempt."exit1fi# ===== B) Compressed backup of SOURCE_DIR with date in name =====SOURCE_LABEL="$(basename"$SOURCE_ABS")"if[[-z"$SOURCE_LABEL"||"$SOURCE_LABEL"=="/"]];thenSOURCE_LABEL="root"fiBACKUP_FILE="${DEST_PATH}/${SOURCE_LABEL}_backup_${STAMP}.tar.gz"log_info "Creating compressed backup: ${BACKUP_FILE}"if[["$SOURCE_ABS"=="/"]];thenTAR_BASE="/"TAR_TARGET="."elseTAR_BASE="$(dirname"$SOURCE_ABS")"TAR_TARGET="$(basename"$SOURCE_ABS")"fiiftar-czf"$BACKUP_FILE"-C"$TAR_BASE""$TAR_TARGET">>"$LOG_FILE"2>&1;thenif[[-s"$BACKUP_FILE"]];then log_info "Backup created successfully: ${BACKUP_FILE}"else log_error "Backup file is empty. Aborting."exit1fielse log_error "Backup failed. Aborting."exit1fi# ===== C) Delete .bak files older than 30 days inside SOURCE_DIR =====log_info "Searching .bak files older than 30 days in: ${SOURCE_ABS}"CANDIDATES_RAW=$(find"$SOURCE_ABS"-type f -name'*.bak'-mtime +30 -print2>>"$LOG_FILE"||true)if[[-n"$CANDIDATES_RAW"]];thenCANDIDATE_COUNT=$(printf'%s\n'"$CANDIDATES_RAW"|wc-l|tr-d' ') log_info "${CANDIDATE_COUNT} .bak files scheduled for deletion."printf'%s\n'"$CANDIDATES_RAW"|whileIFS=read-rfile;do[[-z"$file"]]&&continue log_info "Deleting: ${file}"doneiffind"$SOURCE_ABS"-type f -name'*.bak'-mtime +30 -delete>>"$LOG_FILE"2>&1;then log_info ".bak cleanup completed."else log_warn "Cleanup encountered issues. Review log: ${LOG_FILE}"fielse log_info "No .bak files older than 30 days found."fi# ===== D) Log bookkeeping =====log_info "All steps completed."log_info "Log file: ${LOG_FILE}"log_info "Backup file: ${BACKUP_FILE}"log_info "=== Maintenance run finished ==="
Aspectos destacados del concepto
Validaciones y salidas tempranas
El script comprueba inmediatamente si SOURCE_DIR existe. El fallo temprano previene una cascada de errores confusos.
Se evita cd "$SOURCE_DIR"; en su lugar derivamos una ruta absoluta (SOURCE_ABS) usando pwd -P para manejar enlaces simbólicos de forma segura.
Cada operación en el sistema de archivos (mkdir, touch, tar) comprueba su resultado y aborta ante un fallo, manteniendo los logs confiables.
Estrategia de logging
timestamp() proporciona marcas de tiempo en formato ISO, haciendo que los logs sean ordenables en hojas de cálculo o herramientas SIEM.
log_info, log_warn y log_error centralizan el formato de mensajes, reduciendo la duplicación y clarificando los niveles de severidad.
Los mensajes se transmiten a través de tee para que el operador vea el progreso en pantalla mientras el archivo de log captura el mismo contenido.
Iterando sobre los objetivos de limpieza
Un comando find reúne los archivos .bak candidatos con más de 30 días de antigüedad. El script los cuenta y registra cada eliminación para trazabilidad.
Como las eliminaciones son potencialmente destructivas, el script imprime los nombres de directorio antes de invocar find … -delete y captura stdout/stderr para inspección posterior.
Parametrización y portabilidad
Los argumentos predeterminados (/etc, backups) permiten un uso listo para usar, mientras que los parámetros opcionales permiten que otros equipos reutilicen el script.
Las rutas de salida se derivan del directorio de trabajo actual (SCRIPT_DIR), lo cual es útil cuando las operaciones montan el script dentro de trabajos en contenedores.
El script usa construcciones compatibles con POSIX (con extensiones de Bash reducidas al mínimo) para seguir siendo portable entre las distribuciones de Linux más comunes.
Ejecutando el script y capturando evidencia
Después de guardar el archivo como mantenimiento.sh, ejecuta:
chmod +x mantenimiento.sh
./mantenimiento.sh
Para capturar argumentos (por ejemplo, respaldando /var/www en nightly_backups):
./mantenimiento.sh /var/www nightly_backups
Verifica el archivo de backup y el log listando el directorio de destino e inspeccionando la cabecera del log:
Las capturas de pantalla de estos comandos sirven como artefactos de la ejecución exitosa.
Reflexión y justificación teórica
Los condicionales controlan cada acción crítica, asegurando que los pasos posteriores solo se ejecuten si los prerrequisitos tienen éxito. Esto previene estados parciales y mantiene los logs precisos.
Los bucles gestionan conjuntos dinámicos de archivos (resultados de find), reemplazando la eliminación manual con lógica repetible.
Los argumentos hacen que el script sea reutilizable entre servidores con distintas estructuras de directorios, promoviendo consistencia dentro del equipo de operaciones.
Rutas absolutas vs. relativas. El script se apoya en rutas absolutas para los directorios de origen y las ubicaciones de log, para eliminar la ambigüedad. Dentro del directorio de backup, las referencias relativas (por ejemplo, ./mantenimiento_${STAMP}.log) podrían usarse de forma segura.
Medidas de portabilidad y seguridad. Usar set -euo pipefail (que puedes añadir si ejecutas en Bash 4+) y verificar la existencia del directorio defiende contra errores de configuración. Registrar en un archivo dedicado respalda la auditoría, y las estrategias opcionales de rotación o hashing descritas anteriormente refuerzan la detección de manipulaciones.
Siguientes pasos
Para seguir reforzando el flujo de trabajo de mantenimiento, considera:
Añadir notificaciones por correo o webhook cuando los backups fallen.
Incorporar generación y verificación de checksums para los archivos archivados.
Extender el script con flags de línea de comandos (usando getopts) para modo verbose o simulaciones (dry runs).
Programar el script vía cron con wrappers seguros respecto al entorno (/usr/bin/env bash).
Estas iteraciones se construyen sobre las habilidades introducidas aquí, preparándote para gestionar proyectos de automatización más grandes en entornos de operaciones de seguridad.