Scripting en Bash: La Guía Completa para Automatizar Tareas en DevOps

Diagrama de flujo de automatización con Bash

En el ecosistema DevOps actual, donde la velocidad y confiabilidad son fundamentales, el scripting en Bash se mantiene como una herramienta esencial para la automatización. A pesar del surgimiento de lenguajes modernos como Python y Go, Bash sigue siendo el estándar de facto para operaciones del sistema, orquestación de herramientas y automatización de pipelines en entornos Unix/Linux.

Esta guía completa te llevará desde los fundamentos hasta técnicas avanzadas, con ejemplos prácticos de producción que podrás implementar inmediatamente en tu flujo de trabajo DevOps.

¿Por Qué Bash Sigue Siendo Relevante en DevOps Moderno?

Ventajas Clave de Bash en DevOps

1. Ubicuidad Universal

  • Instalado por defecto en prácticamente todos los sistemas Unix/Linux
  • Disponible en contenedores Docker sin dependencias adicionales
  • Soporte nativo en pipelines CI/CD (GitHub Actions, GitLab CI, Jenkins)

2. Integración Directa con el Sistema

  • Acceso directo a comandos del sistema operativo
  • Manipulación eficiente de archivos y procesos
  • Control granular sobre permisos y recursos

3. Glue Code Perfecto

  • Excelente para orquestar herramientas heterogéneas
  • Combinación fluida de utilidades de línea de comandos
  • Interfaz natural con APIs REST mediante curl

4. Performance y Overhead Mínimo

  • Tiempo de inicio ultra rápido comparado con lenguajes interpretados
  • Consumo mínimo de memoria
  • Ideal para scripts ligeros y operaciones frecuentes

Casos de Uso Críticos en DevOps

ÁreaAplicaciones de BashEjemplos Prácticos
CI/CDScripts de build, testing, deploymentPipeline de compilación, validación de artefactos
InfraestructuraProvisioning, configuración, monitoringInicialización de servidores, health checks
OperacionesBackup, recovery, maintenanceRotación de logs, limpieza de recursos
SeguridadAuditoría, compliance, hardeningEscaneo de vulnerabilidades, aplicación de políticas
MonitoreoMétricas, alertas, troubleshootingRecolección de datos, notificaciones automatizadas

Fundamentos Avanzados de Bash Scripting

Estructura Profesional de Scripts

Un script de Bash bien estructurado debe seguir estas convenciones:

#!/bin/bash
#
# deploy-app.sh - Deployment automatizado de aplicaciones
# 
# Descripción: Script para desplegar aplicaciones con validación y rollback
# Autor: DevOps Team
# Versión: 2.1.0
# Última actualización: 2025-04-27
#
# Uso: ./deploy-app.sh [opciones] <environment> <version>
# 
# Requiere: docker, kubectl, jq
#

# Configuración estricta para prevenir errores
set -euo pipefail  # Exit on error, undefined vars, pipe failures
IFS=$'\n\t'       # Secure Internal Field Separator

# Variables de configuración
readonly SCRIPT_NAME="$(basename "${0}")"
readonly SCRIPT_DIR="$(cd "$(dirname "${0}")" && pwd)"
readonly SCRIPT_VERSION="2.1.0"
readonly LOG_FILE="/var/log/deployment.log"

# Colores para output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[0;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m' # No Color

# Función de limpieza
cleanup() {
    local exit_code=$?
    log "INFO" "Ejecutando limpieza..."
    # Limpiar archivos temporales, restaurar estado, etc.
    exit $exit_code
}

# Configurar trap para limpieza automática
trap cleanup EXIT INT TERM

# Resto del script...

Manejo Avanzado de Variables y Parámetros

#!/bin/bash

# Configuración con valores por defecto y validación
declare -r DEFAULT_TIMEOUT=300
declare -r DEFAULT_RETRIES=3
declare -r DEFAULT_ENVIRONMENT="staging"

# Procesamiento de argumentos con getopts
usage() {
    cat <EOF
Uso: $0 [OPCIONES] <aplicación> <versión>

OPCIONES:
    -e, --env ENVIRONMENT    Entorno de despliegue (dev|staging|prod)
    -t, --timeout SECONDS   Timeout para operaciones (default: $DEFAULT_TIMEOUT)
    -r, --retries NUMBER    Número de reintentos (default: $DEFAULT_RETRIES)
    -v, --verbose           Modo verboso
    -d, --dry-run           Simular ejecución sin cambios
    -h, --help              Mostrar esta ayuda

EJEMPLOS:
    $0 -e prod myapp v1.2.3
    $0 --dry-run --verbose -t 600 myapp latest
EOF
}

# Variables con configuración por defecto
ENVIRONMENT="$DEFAULT_ENVIRONMENT"
TIMEOUT="$DEFAULT_TIMEOUT"
RETRIES="$DEFAULT_RETRIES"
VERBOSE=false
DRY_RUN=false

# Procesamiento de argumentos
while [[ $# -gt 0 ]]; do
    case $1 in
        -e|--env)
            ENVIRONMENT="$2"
            shift 2
            ;;
        -t|--timeout)
            TIMEOUT="$2"
            shift 2
            ;;
        -r|--retries)
            RETRIES="$2"
            shift 2
            ;;
        -v|--verbose)
            VERBOSE=true
            shift
            ;;
        -d|--dry-run)
            DRY_RUN=true
            shift
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        -*)
            echo "Opción desconocida: $1" >&2
            usage >&2
            exit 1
            ;;
        *)
            break
            ;;
    esac
done

# Validación de argumentos obligatorios
if [[ $# -lt 2 ]]; then
    echo "Error: Se requieren aplicación y versión" >&2
    usage >&2
    exit 1
fi

readonly APP_NAME="$1"
readonly APP_VERSION="$2"

# Validación de entorno
case "$ENVIRONMENT" in
    dev|staging|prod)
        log "INFO" "Desplegando en entorno: $ENVIRONMENT"
        ;;
    *)
        log "ERROR" "Entorno inválido: $ENVIRONMENT"
        exit 1
        ;;
esac

Sistema de Logging Robusto

#!/bin/bash

# Sistema de logging con diferentes niveles
declare -r LOG_LEVELS=("DEBUG" "INFO" "WARN" "ERROR" "FATAL")
declare -i LOG_LEVEL=1  # Por defecto INFO (1)

# Función de logging mejorada
log() {
    local level="$1"
    local message="$2"
    local timestamp
    timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    local caller="${BASH_SOURCE[2]##*/}:${BASH_LINENO[1]}"
    
    # Determinar nivel numérico
    local level_num
    case "$level" in
        DEBUG) level_num=0 ;;
        INFO)  level_num=1 ;;
        WARN)  level_num=2 ;;
        ERROR) level_num=3 ;;
        FATAL) level_num=4 ;;
        *) level_num=1 ;;
    esac
    
    # Solo mostrar si el nivel es suficiente
    if [[ $level_num -ge $LOG_LEVEL ]]; then
        # Color según nivel
        local color=""
        case "$level" in
            DEBUG) color="$BLUE" ;;
            INFO)  color="$GREEN" ;;
            WARN)  color="$YELLOW" ;;
            ERROR|FATAL) color="$RED" ;;
        esac
        
        # Formato de salida
        printf "${color}[%s] %-5s %s: %s${NC}\n" \
               "$timestamp" "$level" "$caller" "$message" | tee -a "$LOG_FILE"
        
        # Para errores fatales, enviar alertas
        if [[ "$level" == "FATAL" ]]; then
            send_alert "FATAL" "$message"
            exit 1
        fi
    fi
}

# Función para enviar alertas
send_alert() {
    local level="$1"
    local message="$2"
    local hostname
    hostname=$(hostname)
    
    # Slack webhook (si está configurado)
    if [[ -n "${SLACK_WEBHOOK:-}" ]]; then
        curl -X POST -H 'Content-type: application/json' \
             --data "{\"text\":\"[$level] $hostname: $message\"}" \
             "$SLACK_WEBHOOK" &>/dev/null &
    fi
    
    # Email (si está configurado)
    if [[ -n "${ALERT_EMAIL:-}" ]] && command -v mail &>/dev/null; then
        echo "$message" | mail -s "[$level] Alert from $hostname" "$ALERT_EMAIL" &
    fi
    
    # PagerDuty, OpsGenie, etc. según configuración
}

Automatización de Pipelines CI/CD

Script de Build Completo con Validaciones

#!/bin/bash
#
# build-pipeline.sh - Pipeline de build completo con validaciones
#

set -euo pipefail

# Configuración
readonly BUILD_DIR="$(mktemp -d)"
readonly ARTIFACT_DIR="/artifacts"
readonly REGISTRY="registry.company.com"
readonly SONAR_PROJECT_KEY="myapp"

# Función principal de build
main() {
    log "INFO" "Iniciando pipeline de build para $APP_NAME:$APP_VERSION"
    
    validate_environment
    setup_workspace
    run_security_checks
    run_tests
    build_application
    build_container_image
    run_security_scan
    publish_artifacts
    update_documentation
    cleanup_workspace
    
    log "INFO" "Pipeline de build completado exitosamente"
}

# Validación del entorno de build
validate_environment() {
    log "INFO" "Validando entorno de build"
    
    # Verificar herramientas requeridas
    local tools=("docker" "git" "node" "npm" "sonar-scanner" "trivy" "cosign")
    for tool in "${tools[@]}"; do
        if ! command -v "$tool" &>/dev/null; then
            log "FATAL" "Herramienta requerida no encontrada: $tool"
        fi
    done
    
    # Verificar variables de entorno críticas
    local env_vars=("REGISTRY_USERNAME" "REGISTRY_PASSWORD" "SONAR_TOKEN")
    for var in "${env_vars[@]}"; do
        if [[ -z "${!var:-}" ]]; then
            log "FATAL" "Variable de entorno requerida: $var"
        fi
    done
    
    # Verificar espacio en disco (mínimo 5GB)
    local available_space
    available_space=$(df "$PWD" | awk 'NR==2 {print $4}')
    if [[ $available_space -lt 5242880 ]]; then  # 5GB en KB
        log "FATAL" "Espacio insuficiente en disco: ${available_space}KB disponible"
    fi
    
    log "INFO" "Entorno validado correctamente"
}

# Configuración del workspace
setup_workspace() {
    log "INFO" "Configurando workspace en $BUILD_DIR"
    
    # Limpiar workspace previo si existe
    if [[ -d "$BUILD_DIR" ]]; then
        rm -rf "$BUILD_DIR"
    fi
    
    mkdir -p "$BUILD_DIR" "$ARTIFACT_DIR"
    cd "$BUILD_DIR"
    
    # Clonar repositorio con shallow clone para velocidad
    git clone --depth 1 --branch "$GIT_BRANCH" "$GIT_REPO_URL" .
    
    # Verificar integridad del código
    if [[ -f ".git-integrity" ]]; then
        local expected_hash
        expected_hash=$(cat .git-integrity)
        local actual_hash
        actual_hash=$(git rev-parse HEAD)
        
        if [[ "$expected_hash" != "$actual_hash" ]]; then
            log "WARN" "Hash de integridad no coincide. Esperado: $expected_hash, Actual: $actual_hash"
        fi
    fi
    
    log "INFO" "Workspace configurado en $BUILD_DIR"
}

# Verificaciones de seguridad del código
run_security_checks() {
    log "INFO" "Ejecutando verificaciones de seguridad"
    
    # Escaneo de secretos con gitleaks
    if command -v gitleaks &>/dev/null; then
        log "INFO" "Escaneando secretos con gitleaks"
        if ! gitleaks detect --source . --verbose; then
            log "FATAL" "Se encontraron secretos en el código"
        fi
    fi
    
    # Análisis de dependencias vulnerables
    if [[ -f "package.json" ]]; then
        log "INFO" "Analizando vulnerabilidades en dependencias npm"
        npm audit --audit-level=moderate
        
        # Verificar licencias
        if command -v license-checker &>/dev/null; then
            license-checker --onlyAllow 'MIT;BSD;Apache-2.0;ISC' --production
        fi
    fi
    
    # SAST con SonarQube
    if [[ -n "${SONAR_TOKEN:-}" ]]; then
        log "INFO" "Ejecutando análisis SAST con SonarQube"
        sonar-scanner \
            -Dsonar.projectKey="$SONAR_PROJECT_KEY" \
            -Dsonar.sources=. \
            -Dsonar.host.url="$SONAR_HOST_URL" \
            -Dsonar.login="$SONAR_TOKEN"
    fi
    
    log "INFO" "Verificaciones de seguridad completadas"
}

# Ejecución de tests
run_tests() {
    log "INFO" "Ejecutando suite de tests"
    
    # Tests unitarios
    log "INFO" "Ejecutando tests unitarios"
    npm run test:unit -- --coverage --ci
    
    # Tests de integración
    if [[ "$ENVIRONMENT" != "dev" ]]; then
        log "INFO" "Ejecutando tests de integración"
        npm run test:integration
    fi
    
    # Tests de carga (solo en staging/prod)
    if [[ "$ENVIRONMENT" =~ ^(staging|prod)$ ]]; then
        log "INFO" "Ejecutando tests de carga"
        npm run test:load
    fi
    
    # Verificar cobertura mínima
    local coverage
    coverage=$(grep -o '"pct":[0-9.]*' coverage/coverage-summary.json | head -1 | cut -d: -f2)
    if (( $(echo "$coverage 80" | bc -l) )); then
        log "FATAL" "Cobertura insuficiente: ${coverage}% (mínimo: 80%)"
    fi
    
    log "INFO" "Tests completados exitosamente. Cobertura: ${coverage}%"
}

# Build de la aplicación
build_application() {
    log "INFO" "Construyendo aplicación"
    
    # Instalar dependencias
    npm ci --production
    
    # Build optimizado
    NODE_ENV=production npm run build
    
    # Verificar que el build fue exitoso
    if [[ ! -d "dist" ]] || [[ ! "$(ls -A dist)" ]]; then
        log "FATAL" "El directorio dist está vacío o no existe"
    fi
    
    # Optimizar assets
    if command -v terser &>/dev/null; then
        find dist -name "*.js" -exec terser {} -o {} \;
    fi
    
    log "INFO" "Aplicación construida exitosamente"
}

# Build de imagen de contenedor
build_container_image() {
    local image_tag="$REGISTRY/$APP_NAME:$APP_VERSION"
    local latest_tag="$REGISTRY/$APP_NAME:latest"
    
    log "INFO" "Construyendo imagen Docker: $image_tag"
    
    # Build multi-stage con cache
    docker build \
        --target production \
        --build-arg BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
        --build-arg VCS_REF="$(git rev-parse --short HEAD)" \
        --build-arg VERSION="$APP_VERSION" \
        --cache-from "$latest_tag" \
        --tag "$image_tag" \
        --tag "$latest_tag" \
        .
    
    # Verificar que la imagen se creó correctamente
    if ! docker images "$image_tag" | grep -q "$APP_VERSION"; then
        log "FATAL" "Error al crear la imagen Docker"
    fi
    
    # Test rápido de la imagen
    log "INFO" "Verificando imagen Docker"
    if ! docker run --rm "$image_tag" sh -c 'echo "Image OK"'; then
        log "FATAL" "La imagen Docker no ejecuta correctamente"
    fi
    
    log "INFO" "Imagen Docker creada: $image_tag"
}

# Escaneo de seguridad de la imagen
run_security_scan() {
    local image_tag="$REGISTRY/$APP_NAME:$APP_VERSION"
    
    log "INFO" "Escaneando imagen por vulnerabilidades"
    
    # Trivy para vulnerabilidades
    if command -v trivy &>/dev/null; then
        if ! trivy image --exit-code 1 --severity HIGH,CRITICAL "$image_tag"; then
            log "FATAL" "Se encontraron vulnerabilidades críticas en la imagen"
        fi
    fi
    
    # Grype como alternativa
    if command -v grype &>/dev/null; then
        grype "$image_tag" --fail-on high
    fi
    
    log "INFO" "Escaneo de seguridad completado"
}

# Publicación de artefactos
publish_artifacts() {
    local image_tag="$REGISTRY/$APP_NAME:$APP_VERSION"
    
    log "INFO" "Publicando artefactos"
    
    # Login en registry
    echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY" -u "$REGISTRY_USERNAME" --password-stdin
    
    # Push de imagen
    docker push "$image_tag"
    
    # Firmar imagen con cosign (si está configurado)
    if command -v cosign &>/dev/null && [[ -n "${COSIGN_PRIVATE_KEY:-}" ]]; then
        log "INFO" "Firmando imagen con cosign"
        cosign sign --key "$COSIGN_PRIVATE_KEY" "$image_tag"
    fi
    
    # Generar SBOM (Software Bill of Materials)
    if command -v syft &>/dev/null; then
        syft "$image_tag" -o json > "$ARTIFACT_DIR/sbom.json"
    fi
    
    # Artefactos adicionales
    cp -r dist "$ARTIFACT_DIR/"
    cp package.json "$ARTIFACT_DIR/"
    
    log "INFO" "Artefactos publicados exitosamente"
}

# Actualización de documentación
update_documentation() {
    log "INFO" "Actualizando documentación"
    
    # Generar changelog automático
    if command -v conventional-changelog &>/dev/null; then
        conventional-changelog -p angular -i CHANGELOG.md -s
    fi
    
    # Actualizar versión en archivos
    sed -i "s/\"version\": \".*\"/\"version\": \"$APP_VERSION\"/" package.json
    
    # Commit de cambios de versión
    if [[ "$ENVIRONMENT" == "prod" ]]; then
        git add package.json CHANGELOG.md
        git commit -m "chore: update version to $APP_VERSION"
        git tag -a "v$APP_VERSION" -m "Release version $APP_VERSION"
    fi
    
    log "INFO" "Documentación actualizada"
}

# Limpieza del workspace
cleanup_workspace() {
    log "INFO" "Limpiando workspace"
    
    # Limpiar imágenes Docker temporales
    docker image prune -f --filter "until=24h"
    
    # Limpiar build directory
    if [[ -d "$BUILD_DIR" ]]; then
        rm -rf "$BUILD_DIR"
    fi
    
    log "INFO" "Workspace limpio"
}

# Verificar si se ejecuta directamente
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi

Monitoreo y Observabilidad Avanzada

Sistema de Monitoreo Integral

#!/bin/bash
#
# monitoring-agent.sh - Agente de monitoreo integral para infraestructura
#

set -euo pipefail

# Configuración
readonly METRICS_DIR="/var/metrics"
readonly ALERT_THRESHOLD_CPU=80
readonly ALERT_THRESHOLD_MEMORY=85
readonly ALERT_THRESHOLD_DISK=90
readonly ALERT_THRESHOLD_IOWAIT=30
readonly CHECK_INTERVAL=60

# Servicios críticos a monitorear
readonly CRITICAL_SERVICES=(
    "nginx"
    "postgresql"
    "redis-server"
    "elasticsearch"
    "prometheus"
    "grafana-server"
)

# URLs para health checks
readonly HEALTH_ENDPOINTS=(
    "http://localhost/health"
    "http://localhost:3000/api/health"
    "http://localhost:9200/_cluster/health"
)

# Inicialización
init_monitoring() {
    log "INFO" "Inicializando sistema de monitoreo"
    
    mkdir -p "$METRICS_DIR"
    
    # Crear archivos de métricas si no existen
    touch "$METRICS_DIR/system.csv"
    touch "$METRICS_DIR/services.csv"
    touch "$METRICS_DIR/network.csv"
    touch "$METRICS_DIR/disk_io.csv"
    
    # Headers para archivos CSV
    if [[ ! -s "$METRICS_DIR/system.csv" ]]; then
        echo "timestamp,cpu_usage,memory_usage,load_avg,disk_usage,swap_usage,iowait" > "$METRICS_DIR/system.csv"
    fi
    
    if [[ ! -s "$METRICS_DIR/services.csv" ]]; then
        echo "timestamp,service,status,cpu_percent,memory_mb,connections" > "$METRICS_DIR/services.csv"
    fi
    
    log "INFO" "Sistema de monitoreo inicializado"
}

# Recolección de métricas del sistema
collect_system_metrics() {
    local timestamp
    timestamp=$(date +%s)
    
    # CPU Usage
    local cpu_usage
    cpu_usage=$(grep 'cpu ' /proc/stat | awk '{usage=($2+$4)*100/($2+$3+$4+$5)} END {print usage}')
    
    # Memory Usage
    local memory_info
    memory_info=$(free | awk '/^Mem:/ {printf "%.2f %.2f", $3*100/$2, $4*100/$2}')
    local memory_usage
    memory_usage=$(echo "$memory_info" | cut -d' ' -f1)
    local memory_free
    memory_free=$(echo "$memory_info" | cut -d' ' -f2)
    
    # Load Average
    local load_avg
    load_avg=$(uptime | awk -F'load average:' '{print $2}' | cut -d, -f1 | tr -d ' ')
    
    # Disk Usage (root filesystem)
    local disk_usage
    disk_usage=$(df / | awk 'NR==2 {print substr($5, 1, length($5)-1)}')
    
    # Swap Usage
    local swap_usage
    swap_usage=$(free | awk '/^Swap:/ { if($2 > 0) print $3*100/$2; else print 0}')
    
    # I/O Wait
    local iowait
    iowait=$(iostat -c 1 2 | awk '/^avg-cpu:/ { getline; print $4 }' | tail -1)
    
    # Guardar métricas
    echo "$timestamp,$cpu_usage,$memory_usage,$load_avg,$disk_usage,$swap_usage,$iowait" >> "$METRICS_DIR/system.csv"
    
    # Verificar umbrales y enviar alertas
    check_system_thresholds "$cpu_usage" "$memory_usage" "$disk_usage" "$iowait"
    
    # Log métricas actuales
    log "DEBUG" "Métricas del sistema - CPU: ${cpu_usage}%, Mem: ${memory_usage}%, Disk: ${disk_usage}%, Load: $load_avg, IOWait: ${iowait}%"
}

# Verificación de umbrales críticos
check_system_thresholds() {
    local cpu_usage="$1"
    local memory_usage="$2"
    local disk_usage="$3"
    local iowait="$4"
    
    # CPU
    if (( $(echo "$cpu_usage > $ALERT_THRESHOLD_CPU" | bc -l) )); then
        send_alert "WARNING" "Alto uso de CPU: ${cpu_usage}% (umbral: ${ALERT_THRESHOLD_CPU}%)"
    fi
    
    # Memory
    if (( $(echo "$memory_usage > $ALERT_THRESHOLD_MEMORY" | bc -l) )); then
        send_alert "WARNING" "Alto uso de memoria: ${memory_usage}% (umbral: ${ALERT_THRESHOLD_MEMORY}%)"
    fi
    
    # Disk
    if [[ ${disk_usage%.*} -gt $ALERT_THRESHOLD_DISK ]]; then
        send_alert "CRITICAL" "Alto uso de disco: ${disk_usage}% (umbral: ${ALERT_THRESHOLD_DISK}%)"
    fi
    
    # I/O Wait
    if (( $(echo "$iowait > $ALERT_THRESHOLD_IOWAIT" | bc -l) )); then
        send_alert "WARNING" "Alto I/O wait: ${iowait}% (umbral: ${ALERT_THRESHOLD_IOWAIT}%)"
    fi
}

# Monitoreo de servicios críticos
monitor_services() {
    local timestamp
    timestamp=$(date +%s)
    
    for service in "${CRITICAL_SERVICES[@]}"; do
        local status="DOWN"
        local cpu_percent=0
        local memory_mb=0
        local connections=0
        
        # Verificar estado del servicio
        if systemctl is-active --quiet "$service"; then
            status="UP"
            
            # Obtener PID del servicio
            local pid
            pid=$(systemctl show "$service" --property=MainPID --value)
            
            if [[ "$pid" != "0" ]] && [[ -n "$pid" ]]; then
                # CPU y Memory del proceso
                local proc_stats
                proc_stats=$(ps -p "$pid" -o %cpu,%mem --no-headers 2>/dev/null || echo "0.0 0.0")
                cpu_percent=$(echo "$proc_stats" | awk '{print $1}')
                memory_mb=$(echo "$proc_stats" | awk '{print $2}')
                
                # Convertir memoria de porcentaje a MB
                local total_memory_kb
                total_memory_kb=$(grep MemTotal /proc/meminfo | awk '{print $2}')
                memory_mb=$(echo "scale=0; $memory_mb * $total_memory_kb / 100 / 1024" | bc)
                
                # Conexiones de red (para servicios de red)
                case "$service" in
                    nginx|apache2)
                        connections=$(ss -tuln | grep -c ':80\|:443' || echo 0)
                        ;;
                    postgresql)
                        connections=$(ss -tuln | grep -c ':5432' || echo 0)
                        ;;
                    redis-server)
                        connections=$(ss -tuln | grep -c ':6379' || echo 0)
                        ;;
                esac
            fi
        else
            send_alert "CRITICAL" "Servicio crítico caído: $service"
        fi
        
        # Guardar métricas del servicio
        echo "$timestamp,$service,$status,$cpu_percent,$memory_mb,$connections" >> "$METRICS_DIR/services.csv"
        
        log "DEBUG" "Servicio $service - Estado: $status, CPU: ${cpu_percent}%, Mem: ${memory_mb}MB, Conn: $connections"
    done
}

# Health checks de endpoints HTTP
check_health_endpoints() {
    local timestamp
    timestamp=$(date +%s)
    
    for endpoint in "${HEALTH_ENDPOINTS[@]}"; do
        local status_code=0
        local response_time=0
        local status="DOWN"
        
        # Realizar health check con timeout
        local curl_result
        curl_result=$(curl -s -w "%{http_code} %{time_total}" -m 10 "$endpoint" 2>/dev/null || echo "000 0")
        
        status_code=$(echo "$curl_result" | awk '{print $(NF-1)}')
        response_time=$(echo "$curl_result" | awk '{print $NF}')
        
        if [[ "$status_code" =~ ^2[0-9][0-9]$ ]]; then
            status="UP"
        else
            status="DOWN"
            send_alert "WARNING" "Health check falló para $endpoint (HTTP $status_code)"
        fi
        
        # Alerta por tiempo de respuesta lento
        if (( $(echo "$response_time > 5.0" | bc -l) )); then
            send_alert "WARNING" "Respuesta lenta en $endpoint: ${response_time}s"
        fi
        
        log "DEBUG" "Health check $endpoint - Status: $status_code, Time: ${response_time}s"
    done
}

# Monitoreo de red
monitor_network() {
    local timestamp
    timestamp=$(date +%s)
    
    # Estadísticas de interfaces de red
    local network_stats
    network_stats=$(cat /proc/net/dev | grep -E '(eth0|ens|enp)' | head -1)
    
    if [[ -n "$network_stats" ]]; then
        local interface
        interface=$(echo "$network_stats" | cut -d: -f1 | tr -d ' ')
        local rx_bytes
        rx_bytes=$(echo "$network_stats" | awk '{print $2}')
        local tx_bytes
        tx_bytes=$(echo "$network_stats" | awk '{print $10}')
        
        echo "$timestamp,$interface,$rx_bytes,$tx_bytes" >> "$METRICS_DIR/network.csv"
    fi
    
    # Verificar conectividad externa
    if ! ping -c 1 8.8.8.8 &>/dev/null; then
        send_alert "WARNING" "Sin conectividad externa - DNS público no accesible"
    fi
}

# Monitoreo de I/O de disco
monitor_disk_io() {
    local timestamp
    timestamp=$(date +%s)
    
    # Estadísticas de I/O por dispositivo
    while IFS= read -r line; do
        if [[ "$line" =~ ^[[:space:]]*[0-9]+[[:space:]]+[0-9]+[[:space:]]+(sd[a-z]|nvme[0-9]n[0-9]) ]]; then
            local device
            device=$(echo "$line" | awk '{print $3}')
            local reads
            reads=$(echo "$line" | awk '{print $4}')
            local writes
            writes=$(echo "$line" | awk '{print $8}')
            
            echo "$timestamp,$device,$reads,$writes" >> "$METRICS_DIR/disk_io.csv"
        fi
    done /proc/diskstats
}

# Análisis de logs en tiempo real
analyze_logs() {
    local error_patterns=(
        "ERROR"
        "FATAL"
        "CRITICAL"
        "Out of memory"
        "Connection refused"
        "Too many open files"
    )
    
    # Verificar logs del sistema
    for pattern in "${error_patterns[@]}"; do
        local recent_errors
        recent_errors=$(grep "$pattern" /var/log/syslog | tail -10)
        
        if [[ -n "$recent_errors" ]]; then
            send_alert "WARNING" "Errores detectados en syslog: $pattern"
        fi
    done
    
    # Análisis específico por servicio
    if [[ -f "/var/log/nginx/error.log" ]]; then
        local nginx_errors
        nginx_errors=$(tail -100 /var/log/nginx/error.log | grep -c "error" || echo 0)
        
        if [[ "$nginx_errors" -gt 10 ]]; then
            send_alert "WARNING" "Alto número de errores en nginx: $nginx_errors en los últimos 100 logs"
        fi
    fi
}

# Reporte de métricas para exportar
generate_metrics_report() {
    local report_file="$METRICS_DIR/report_$(date +%Y%m%d_%H%M).json"
    
    # Resumen de métricas recientes
    local latest_system
    latest_system=$(tail -1 "$METRICS_DIR/system.csv" | cut -d, -f2-)
    
    local services_up
    services_up=$(grep "$(date +%s)" "$METRICS_DIR/services.csv" | grep -c "UP" || echo 0)
    
    local services_down
    services_down=$(grep "$(date +%s)" "$METRICS_DIR/services.csv" | grep -c "DOWN" || echo 0)
    
    # Generar JSON del reporte
    cat > "$report_file" <EOF
{
  "timestamp": "$(date -Iseconds)",
  "hostname": "$(hostname)",
  "system": {
    "cpu_usage": $(echo "$latest_system" | cut -d, -f1),
    "memory_usage": $(echo "$latest_system" | cut -d, -f2),
    "disk_usage": $(echo "$latest_system" | cut -d, -f4)
  },
  "services": {
    "up": $services_up,
    "down": $services_down
  },
  "alerts_last_hour": $(grep "$(date -d '1 hour ago' +%s)" /var/log/monitoring.log | wc -l || echo 0)
}
EOF
    
    log "INFO" "Reporte generado: $report_file"
}

# Función principal de monitoreo
main_monitoring_loop() {
    log "INFO" "Iniciando bucle principal de monitoreo"
    
    while true; do
        collect_system_metrics
        monitor_services
        check_health_endpoints
        monitor_network
        monitor_disk_io
        analyze_logs
        
        # Generar reporte cada 10 minutos
        if (( $(date +%M) % 10 == 0 )); then
            generate_metrics_report
        fi
        
        log "DEBUG" "Ciclo de monitoreo completado, esperando $CHECK_INTERVAL segundos"
        sleep "$CHECK_INTERVAL"
    done
}

# Función principal
main() {
    log "INFO" "Iniciando agente de monitoreo"
    
    init_monitoring
    
    # Verificar si se ejecuta como daemon
    if [[ "${1:-}" == "--daemon" ]]; then
        log "INFO" "Ejecutando como daemon"
        main_monitoring_loop &
        echo $! > /var/run/monitoring-agent.pid
        log "INFO" "Daemon iniciado con PID: $(cat /var/run/monitoring-agent.pid)"
    else
        # Ejecutar una vez
        collect_system_metrics
        monitor_services
        check_health_endpoints
        generate_metrics_report
    fi
}

# Ejecutar si es el script principal
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi

Automatización de Deployments con Rollback

Sistema de Deployment con Rollback Automático

#!/bin/bash
#
# smart-deploy.sh - Sistema inteligente de deployment con rollback automático
#

set -euo pipefail

# Configuración
readonly DEPLOY_TIMEOUT=900  # 15 minutos
readonly HEALTH_CHECK_TIMEOUT=300  # 5 minutos
readonly ROLLBACK_TIMEOUT=180  # 3 minutos
readonly MIN_HEALTHY_INSTANCES=2

# Estados de deployment
readonly DEPLOY_STATES=("PENDING" "IN_PROGRESS" "VERIFYING" "COMPLETED" "FAILED" "ROLLING_BACK" "ROLLED_BACK")

# Función principal de deployment
deploy_application() {
    local environment="$1"
    local version="$2"
    local strategy="${3:-blue-green}"  # blue-green, rolling, canary
    
    log "INFO" "Iniciando deployment de $APP_NAME:$version en $environment con estrategia $strategy"
    
    validate_deployment_preconditions "$environment" "$version"
    
    case "$strategy" in
        "blue-green")
            deploy_blue_green "$environment" "$version"
            ;;
        "rolling")
            deploy_rolling_update "$environment" "$version"
            ;;
        "canary")
            deploy_canary "$environment" "$version"
            ;;
        *)
            log "FATAL" "Estrategia de deployment desconocida: $strategy"
            ;;
    esac
}

# Validación de precondiciones
validate_deployment_preconditions() {
    local environment="$1"
    local version="$2"
    
    log "INFO" "Validando precondiciones para deployment"
    
    # Verificar que la imagen existe
    if ! docker manifest inspect "$REGISTRY/$APP_NAME:$version" &>/dev/null; then
        log "FATAL" "La imagen $REGISTRY/$APP_NAME:$version no existe"
    fi
    
    # Verificar conectividad con Kubernetes
    if ! kubectl cluster-info &>/dev/null; then
        log "FATAL" "No hay conectividad con el cluster de Kubernetes"
    fi
    
    # Verificar que el namespace existe
    if ! kubectl get namespace "$environment" &>/dev/null; then
        log "FATAL" "El namespace '$environment' no existe"
    fi
    
    # Verificar recursos disponibles
    local node_resources
    node_resources=$(kubectl top nodes --no-headers | awk '{sum+=$3} END {print sum}')
    if [[ ${node_resources%m} -lt 1000 ]]; then  # Menos de 1 CPU disponible
        log "WARN" "Recursos limitados en el cluster: ${node_resources}m CPU disponible"
    fi
    
    # Verificar que no hay otro deployment en curso
    local current_rollout
    current_rollout=$(kubectl rollout status deployment/"$APP_NAME" -n "$environment" --timeout=5s 2>&1 || echo "")
    if [[ "$current_rollout" == *"Waiting for"* ]]; then
        log "FATAL" "Ya hay un deployment en curso para $APP_NAME en $environment"
    fi
    
    log "INFO" "Precondiciones validadas correctamente"
}

# Deployment Blue-Green
deploy_blue_green() {
    local environment="$1"
    local version="$2"
    local blue_deployment="$APP_NAME-blue"
    local green_deployment="$APP_NAME-green"
    
    log "INFO" "Ejecutando deployment Blue-Green"
    
    # Determinar cuál es el deployment activo actualmente
    local active_deployment
    active_deployment=$(get_active_deployment "$environment")
    
    local target_deployment
    if [[ "$active_deployment" == "$blue_deployment" ]]; then
        target_deployment="$green_deployment"
    else
        target_deployment="$blue_deployment"
    fi
    
    log "INFO" "Deployment activo: $active_deployment, Target: $target_deployment"
    
    # Crear/actualizar el target deployment
    create_deployment "$environment" "$target_deployment" "$version"
    
    # Esperar a que esté listo
    if wait_for_deployment_ready "$environment" "$target_deployment"; then
        # Verificar health checks
        if verify_application_health "$environment" "$target_deployment"; then
            # Cambiar tráfico al nuevo deployment
            switch_traffic "$environment" "$target_deployment"
            
            # Verificar estabilidad post-switch
            if verify_post_deployment_stability "$environment" "$target_deployment"; then
                log "INFO" "Deployment Blue-Green completado exitosamente"
                cleanup_old_deployment "$environment" "$active_deployment"
                return 0
            else
                log "ERROR" "Verificación post-deployment falló, iniciando rollback"
                rollback_blue_green "$environment" "$active_deployment"
                return 1
            fi
        else
            log "ERROR" "Health checks fallaron, iniciando rollback"
            cleanup_deployment "$environment" "$target_deployment"
            return 1
        fi
    else
        log "ERROR" "Deployment no alcanzó estado ready, iniciando limpieza"
        cleanup_deployment "$environment" "$target_deployment"
        return 1
    fi
}

# Deployment Rolling Update
deploy_rolling_update() {
    local environment="$1"
    local version="$2"
    
    log "INFO" "Ejecutando Rolling Update"
    
    # Guardar estado actual para rollback
    local current_image
    current_image=$(kubectl get deployment "$APP_NAME" -n "$environment" -o jsonpath='{.spec.template.spec.containers[0].image}')
    
    # Actualizar imagen
    kubectl set image deployment/"$APP_NAME" -n "$environment" "$APP_NAME"="$REGISTRY/$APP_NAME:$version"
    
    # Monitorear el rollout
    if monitor_rolling_update "$environment"; then
        if verify_application_health "$environment" "$APP_NAME"; then
            log "INFO" "Rolling update completado exitosamente"
            return 0
        else
            log "ERROR" "Health checks fallaron post-deployment"
            rollback_deployment "$environment" "$current_image"
            return 1
        fi
    else
        log "ERROR" "Rolling update falló"
        rollback_deployment "$environment" "$current_image"
        return 1
    fi
}

# Deployment Canary
deploy_canary() {
    local environment="$1"
    local version="$2"
    local canary_percentage="${CANARY_PERCENTAGE:-10}"
    
    log "INFO" "Ejecutando Canary Deployment ($canary_percentage% de tráfico)"
    
    # Crear deployment canary
    local canary_deployment="$APP_NAME-canary"
    create_deployment "$environment" "$canary_deployment" "$version" 1  # 1 replica para canary
    
    if wait_for_deployment_ready "$environment" "$canary_deployment"; then
        # Configurar traffic splitting
        configure_canary_traffic "$environment" "$canary_deployment" "$canary_percentage"
        
        # Monitorear métricas durante el período de evaluación
        if monitor_canary_metrics "$environment" "$canary_deployment"; then
            log "INFO" "Canary exitoso, promoviendo a producción"
            promote_canary_to_production "$environment" "$version"
            cleanup_deployment "$environment" "$canary_deployment"
            return 0
        else
            log "ERROR" "Canary falló las validaciones, eliminando"
            cleanup_deployment "$environment" "$canary_deployment"
            return 1
        fi
    else
        log "ERROR" "Canary deployment no alcanzó estado ready"
        cleanup_deployment "$environment" "$canary_deployment"
        return 1
    fi
}

# Crear deployment
create_deployment() {
    local environment="$1"
    local deployment_name="$2"
    local version="$3"
    local replicas="${4:-3}"
    
    log "INFO" "Creando deployment $deployment_name con $replicas réplicas"
    
    cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: $deployment_name
  namespace: $environment
  labels:
    app: $APP_NAME
    version: $version
spec:
  replicas: $replicas
  selector:
    matchLabels:
      app: $APP_NAME
      deployment: $deployment_name
  template:
    metadata:
      labels:
        app: $APP_NAME
        deployment: $deployment_name
        version: $version
    spec:
      containers:
      - name: $APP_NAME
        image: $REGISTRY/$APP_NAME:$version
        ports:
        - containerPort: 8080
        readinessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
          timeoutSeconds: 3
          failureThreshold: 3
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        env:
        - name: ENVIRONMENT
          value: "$environment"
EOF
}

# Esperar a que el deployment esté listo
wait_for_deployment_ready() {
    local environment="$1"
    local deployment_name="$2"
    local timeout="$DEPLOY_TIMEOUT"
    
    log "INFO" "Esperando a que $deployment_name esté listo (timeout: ${timeout}s)"
    
    local start_time
    start_time=$(date +%s)
    
    while true; do
        local current_time
        current_time=$(date +%s)
        local elapsed=$((current_time - start_time))
        
        if [[ $elapsed -gt $timeout ]]; then
            log "ERROR" "Timeout esperando a que $deployment_name esté listo"
            return 1
        fi
        
        # Verificar estado del deployment
        local ready_replicas
        ready_replicas=$(kubectl get deployment "$deployment_name" -n "$environment" -o jsonpath='{.status.readyReplicas}' 2>/dev/null || echo "0")
        local desired_replicas
        desired_replicas=$(kubectl get deployment "$deployment_name" -n "$environment" -o jsonpath='{.spec.replicas}' 2>/dev/null || echo "0")
        
        if [[ "$ready_replicas" == "$desired_replicas" ]] && [[ "$ready_replicas" != "0" ]]; then
            log "INFO" "$deployment_name está listo ($ready_replicas/$desired_replicas réplicas)"
            return 0
        fi
        
        log "DEBUG" "Esperando deployment $deployment_name: $ready_replicas/$desired_replicas réplicas listas"
        sleep 10
    done
}

# Verificar salud de la aplicación
verify_application_health() {
    local environment="$1"
    local deployment_name="$2"
    local timeout="$HEALTH_CHECK_TIMEOUT"
    
    log "INFO" "Verificando salud de $deployment_name"
    
    # Obtener IP del servicio o usar port-forward para test
    local service_ip
    service_ip=$(kubectl get service "$APP_NAME" -n "$environment" -o jsonpath='{.spec.clusterIP}' 2>/dev/null || echo "")
    
    if [[ -z "$service_ip" ]]; then
        log "WARN" "No se pudo obtener IP del servicio, usando port-forward para verificación"
        # Crear port-forward temporal
        kubectl port-forward deployment/"$deployment_name" -n "$environment" 8080:8080 &
        local pf_pid=$!
        sleep 5
        service_ip="localhost"
    fi
    
    local start_time
    start_time=$(date +%s)
    local success_count=0
    local required_success=5  # Requiere 5 checks exitosos consecutivos
    
    while true; do
        local current_time
        current_time=$(date +%s)
        local elapsed=$((current_time - start_time))
        
        if [[ $elapsed -gt $timeout ]]; then
            log "ERROR" "Timeout en verificación de salud de $deployment_name"
            [[ -n "${pf_pid:-}" ]] && kill "$pf_pid" 2>/dev/null || true
            return 1
        fi
        
        # Health check
        if curl -s --max-time 5 "http://$service_ip:8080/health" | grep -q "ok"; then
            ((success_count++))
            log "DEBUG" "Health check exitoso para $deployment_name ($success_count/$required_success)"
            
            if [[ $success_count -ge $required_success ]]; then
                log "INFO" "Verificación de salud completada para $deployment_name"
                [[ -n "${pf_pid:-}" ]] && kill "$pf_pid" 2>/dev/null || true
                return 0
            fi
        else
            log "DEBUG" "Health check falló para $deployment_name, reiniciando contador"
            success_count=0
        fi
        
        sleep 5
    done
}

# Monitorear métricas de canary
monitor_canary_metrics() {
    local environment="$1"
    local canary_deployment="$2"
    local monitoring_duration="${CANARY_MONITORING_DURATION:-300}"  # 5 minutos por defecto
    
    log "INFO" "Monitoreando métricas de canary por ${monitoring_duration} segundos"
    
    local start_time
    start_time=$(date +%s)
    local error_threshold=5  # Máximo 5% de errores
    local response_time_threshold=2000  # Máximo 2 segundos
    
    while true; do
        local current_time
        current_time=$(date +%s)
        local elapsed=$((current_time - start_time))
        
        if [[ $elapsed -gt $monitoring_duration ]]; then
            log "INFO" "Monitoreo de canary completado exitosamente"
            return 0
        fi
        
        # Verificar métricas (requiere Prometheus/métricas disponibles)
        local error_rate
        error_rate=$(get_error_rate_for_deployment "$environment" "$canary_deployment" || echo "0")
        
        local avg_response_time
        avg_response_time=$(get_avg_response_time_for_deployment "$environment" "$canary_deployment" || echo "0")
        
        log "DEBUG" "Canary métricas - Error rate: ${error_rate}%, Response time: ${avg_response_time}ms"
        
        # Verificar umbrales
        if (( $(echo "$error_rate > $error_threshold" | bc -l) )); then
            log "ERROR" "Canary falló: error rate ${error_rate}% > ${error_threshold}%"
            return 1
        fi
        
        if [[ ${avg_response_time%.*} -gt $response_time_threshold ]]; then
            log "ERROR" "Canary falló: tiempo de respuesta ${avg_response_time}ms > ${response_time_threshold}ms"
            return 1
        fi
        
        sleep 30
    done
}

# Rollback automático
rollback_deployment() {
    local environment="$1"
    local previous_image="$2"
    
    log "INFO" "Iniciando rollback automático a imagen: $previous_image"
    
    # Rollback usando kubectl
    kubectl set image deployment/"$APP_NAME" -n "$environment" "$APP_NAME"="$previous_image"
    
    # Esperar a que el rollback complete
    if kubectl rollout status deployment/"$APP_NAME" -n "$environment" --timeout="${ROLLBACK_TIMEOUT}s"; then
        log "INFO" "Rollback completado exitosamente"
        
        # Verificar que la aplicación está funcionando
        if verify_application_health "$environment" "$APP_NAME"; then
            log "INFO" "Aplicación funcionando correctamente post-rollback"
            send_alert "INFO" "Rollback exitoso para $APP_NAME en $environment"
            return 0
        else
            log "FATAL" "Aplicación no funciona correctamente post-rollback"
            send_alert "CRITICAL" "Rollback falló para $APP_NAME en $environment - intervención manual requerida"
            return 1
        fi
    else
        log "FATAL" "Rollback no completó dentro del timeout"
        send_alert "CRITICAL" "Rollback timeout para $APP_NAME en $environment - intervención manual requerida"
        return 1
    fi
}

# Función principal
main() {
    local environment="${1:-}"
    local version="${2:-}"
    local strategy="${3:-blue-green}"
    
    if [[ -z "$environment" ]] || [[ -z "$version" ]]; then
        log "ERROR" "Uso: $0 <environment> <version> [strategy]"
        echo "Estrategias disponibles: blue-green, rolling, canary"
        exit 1
    fi
    
    # Configuración específica por entorno
    case "$environment" in
        dev)
            DEPLOY_TIMEOUT=300
            MIN_HEALTHY_INSTANCES=1
            ;;
        staging)
            DEPLOY_TIMEOUT=600
            MIN_HEALTHY_INSTANCES=2
            ;;
        prod)
            DEPLOY_TIMEOUT=900
            MIN_HEALTHY_INSTANCES=3
            
            # Confirmación adicional para producción
            read -p "⚠️ Confirmación requerida para deployment en PRODUCCIÓN de $APP_NAME:$version. ¿Continuar? (yes/no) " -r
            if [[ ! $REPLY == "yes" ]]; then
                log "INFO" "Deployment cancelado por el usuario"
                exit 0
            fi
            ;;
        *)
            log "FATAL" "Entorno desconocido: $environment"
            ;;
    esac
    
    # Ejecutar deployment
    if deploy_application "$environment" "$version" "$strategy"; then
        log "INFO" "✅ Deployment exitoso de $APP_NAME:$version en $environment"
        send_alert "INFO" "Deployment exitoso: $APP_NAME:$version en $environment"
        exit 0
    else
        log "ERROR" "❌ Deployment falló para $APP_NAME:$version en $environment"
        send_alert "ERROR" "Deployment falló: $APP_NAME:$version en $environment"
        exit 1
    fi
}

# Ejecutar si es el script principal
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi

Automatización de Infraestructura y Configuración

Script para Provisioning Completo

#!/bin/bash
#
# provision-infrastructure.sh - Provisioning completo de infraestructura
#

set -euo pipefail

# Configuración
readonly TERRAFORM_DIR="./terraform"
readonly ANSIBLE_PLAYBOOK_DIR="./ansible"
readonly INVENTORY_FILE="./inventory/hosts.yml"
readonly STATE_BUCKET="company-terraform-state"

# Función principal de provisioning
provision_infrastructure() {
    local environment="$1"
    local action="${2:-apply}"  # plan, apply, destroy
    
    log "INFO" "Iniciando provisioning de infraestructura para $environment"
    
    validate_prerequisites
    setup_terraform_backend "$environment"
    
    case "$action" in
        plan)
            terraform_plan "$environment"
            ;;
        apply)
            terraform_plan "$environment"
            confirm_terraform_apply "$environment"
            terraform_apply "$environment"
            configure_infrastructure "$environment"
            validate_infrastructure "$environment"
            ;;
        destroy)
            confirm_terraform_destroy "$environment"
            terraform_destroy "$environment"
            ;;
        *)
            log "FATAL" "Acción desconocida: $action. Usar: plan, apply, destroy"
            ;;
    esac
}

# Validación de prerrequisitos
validate_prerequisites() {
    log "INFO" "Validando prerrequisitos"
    
    local required_tools=("terraform" "ansible" "aws" "jq" "yq")
    for tool in "${required_tools[@]}"; do
        if ! command -v "$tool" &>/dev/null; then
            log "FATAL" "Herramienta requerida no encontrada: $tool"
        fi
    done
    
    # Verificar credenciales AWS
    if ! aws sts get-caller-identity &>/dev/null; then
        log "FATAL" "Credenciales AWS no configuradas o inválidas"
    fi
    
    # Verificar versiones
    local terraform_version
    terraform_version=$(terraform version -json | jq -r '.terraform_version')
    log "INFO" "Terraform versión: $terraform_version"
    
    local ansible_version
    ansible_version=$(ansible --version | head -1 | awk '{print $2}')
    log "INFO" "Ansible versión: $ansible_version"
    
    log "INFO" "Prerrequisitos validados"
}

# Configurar backend de Terraform
setup_terraform_backend() {
    local environment="$1"
    local backend_config="./terraform/backend-${environment}.hcl"
    
    log "INFO" "Configurando backend de Terraform para $environment"
    
    # Crear configuración de backend si no existe
    if [[ ! -f "$backend_config" ]]; then
        cat > "$backend_config" <EOF
bucket = "$STATE_BUCKET"
key    = "terraform/$environment/terraform.tfstate"
region = "us-west-2"
dynamodb_table = "terraform-state-lock"
encrypt = true
EOF
    fi
    
    # Inicializar Terraform
    cd "$TERRAFORM_DIR"
    terraform init -backend-config="$backend_config" -reconfigure
    cd ..
    
    log "INFO" "Backend de Terraform configurado"
}

# Plan de Terraform
terraform_plan() {
    local environment="$1"
    local var_file="./terraform/environments/${environment}.tfvars"
    local plan_file="./terraform/plans/${environment}.tfplan"
    
    log "INFO" "Generando plan de Terraform para $environment"
    
    mkdir -p "./terraform/plans"
    
    # Verificar que existe el archivo de variables
    if [[ ! -f "$var_file" ]]; then
        log "FATAL" "Archivo de variables no encontrado: $var_file"
    fi
    
    cd "$TERRAFORM_DIR"
    
    # Generar plan
    terraform plan \
        -var-file="$var_file" \
        -var="environment=$environment" \
        -out="$plan_file" \
        -detailed-exitcode
    
    local plan_exit_code=$?
    
    cd ..
    
    case $plan_exit_code in
        0)
            log "INFO" "No hay cambios en la infraestructura"
            ;;
        1)
            log "FATAL" "Error al generar plan de Terraform"
            ;;
        2)
            log "INFO" "Plan generado con cambios pendientes"
            ;;
    esac
    
    # Mostrar resumen del plan
    terraform -chdir="$TERRAFORM_DIR" show -json "$plan_file" | \
        jq -r '.resource_changes[] | "\(.change.actions[0]) \(.address)"' | \
        sort | uniq -c | sort -nr
    
    return $plan_exit_code
}

# Confirmación para aplicar cambios
confirm_terraform_apply() {
    local environment="$1"
    
    # Para producción, requiere confirmación explícita
    if [[ "$environment" == "prod" ]]; then
        echo "⚠️ ATENCIÓN: Vas a aplicar cambios en PRODUCCIÓN"
        echo "Plan de cambios:"
        terraform -chdir="$TERRAFORM_DIR" show "./plans/${environment}.tfplan"
        
        read -p "¿Confirmas que quieres aplicar estos cambios en PRODUCCIÓN? Escribe 'yes' para continuar: " -r
        if [[ "$REPLY" != "yes" ]]; then
            log "INFO" "Operación cancelada por el usuario"
            exit 0
        fi
    else
        echo "Aplicando cambios en entorno: $environment"
        read -p "¿Continuar? (y/N) " -n 1 -r
        echo
        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
            log "INFO" "Operación cancelada por el usuario"
            exit 0
        fi
    fi
}

# Aplicar cambios de Terraform
terraform_apply() {
    local environment="$1"
    local plan_file="./terraform/plans/${environment}.tfplan"
    
    log "INFO" "Aplicando cambios de Terraform para $environment"
    
    cd "$TERRAFORM_DIR"
    
    # Aplicar el plan pre-generado
    if ! terraform apply "$plan_file"; then
        log "FATAL" "Falló la aplicación de cambios de Terraform"
    fi
    
    cd ..
    
    # Generar inventario para Ansible
    generate_ansible_inventory "$environment"
    
    log "INFO" "Cambios de Terraform aplicados exitosamente"
}

# Generar inventario de Ansible
generate_ansible_inventory() {
    local environment="$1"
    
    log "INFO" "Generando inventario de Ansible para $environment"
    
    mkdir -p "$(dirname "$INVENTORY_FILE")"
    
    # Obtener outputs de Terraform
    cd "$TERRAFORM_DIR"
    local terraform_outputs
    terraform_outputs=$(terraform output -json)
    cd ..
    
    # Generar inventario YAML
    cat > "$INVENTORY_FILE" <EOF
all:
  vars:
    environment: $environment
    ansible_user: ec2-user
    ansible_ssh_private_key_file: ~/.ssh/${environment}-key.pem
  children:
    web_servers:
      hosts:
EOF
    
    # Extraer IPs de servidores web
    echo "$terraform_outputs" | jq -r '.web_server_ips.value[]?' | while read -r ip; do
        echo "        $ip:" >> "$INVENTORY_FILE"
    done
    
    cat >> "$INVENTORY_FILE" <EOF
    app_servers:
      hosts:
EOF
    
    # Extraer IPs de servidores de aplicación
    echo "$terraform_outputs" | jq -r '.app_server_ips.value[]?' | while read -r ip; do
        echo "        $ip:" >> "$INVENTORY_FILE"
    done
    
    cat >> "$INVENTORY_FILE" <EOF
    db_servers:
      hosts:
EOF
    
    # Extraer IPs de servidores de base de datos
    echo "$terraform_outputs" | jq -r '.db_server_ips.value[]?' | while read -r ip; do
        echo "        $ip:" >> "$INVENTORY_FILE"
    done
    
    log "INFO" "Inventario de Ansible generado: $INVENTORY_FILE"
}

# Configurar infraestructura con Ansible
configure_infrastructure() {
    local environment="$1"
    
    log "INFO" "Configurando infraestructura con Ansible para $environment"
    
    # Esperar a que las instancias estén accesibles
    wait_for_instances_ready "$environment"
    
    # Ejecutar playbooks de Ansible
    local playbooks=(
        "common.yml"
        "security-hardening.yml"
        "web-servers.yml"
        "app-servers.yml"
        "db-servers.yml"
        "monitoring.yml"
    )
    
    for playbook in "${playbooks[@]}"; do
        local playbook_path="$ANSIBLE_PLAYBOOK_DIR/$playbook"
        
        if [[ -f "$playbook_path" ]]; then
            log "INFO" "Ejecutando playbook: $playbook"
            
            ansible-playbook \
                -i "$INVENTORY_FILE" \
                -e "environment=$environment" \
                "$playbook_path"
            
            if [[ $? -ne 0 ]]; then
                log "ERROR" "Falló el playbook: $playbook"
                return 1
            fi
        else
            log "WARN" "Playbook no encontrado: $playbook_path"
        fi
    done
    
    log "INFO" "Configuración con Ansible completada"
}

# Esperar a que las instancias estén listas
wait_for_instances_ready() {
    local environment="$1"
    local timeout=300  # 5 minutos
    
    log "INFO" "Esperando a que las instancias estén accesibles"
    
    # Obtener IPs de todas las instancias
    local all_ips
    all_ips=$(yq eval '.all.children.*.hosts | keys | .[]' "$INVENTORY_FILE")
    
    local start_time
    start_time=$(date +%s)
    
    for ip in $all_ips; do
        log "INFO" "Verificando conectividad con $ip"
        
        while true; do
            local current_time
            current_time=$(date +%s)
            local elapsed=$((current_time - start_time))
            
            if [[ $elapsed -gt $timeout ]]; then
                log "ERROR" "Timeout esperando conectividad con $ip"
                return 1
            fi
            
            if ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no ec2-user@"$ip" "echo 'OK'" &>/dev/null; then
                log "INFO" "Conectividad establecida con $ip"
                break
            fi
            
            log "DEBUG" "Esperando conectividad con $ip..."
            sleep 10
        done
    done
    
    log "INFO" "Todas las instancias están accesibles"
}

# Validar infraestructura
validate_infrastructure() {
    local environment="$1"
    
    log "INFO" "Validando infraestructura para $environment"
    
    # Verificar servicios críticos
    local validation_playbook="$ANSIBLE_PLAYBOOK_DIR/validate-infrastructure.yml"
    
    if [[ -f "$validation_playbook" ]]; then
        log "INFO" "Ejecutando validaciones con Ansible"
        ansible-playbook -i "$INVENTORY_FILE" "$validation_playbook"
    fi
    
    # Verificar conectividad de red
    validate_network_connectivity "$environment"
    
    # Verificar servicios web
    validate_web_services "$environment"
    
    log "INFO" "Validación de infraestructura completada"
}

# Validar conectividad de red
validate_network_connectivity() {
    local environment="$1"
    
    log "INFO" "Validando conectividad de red"
    
    # Obtener Load Balancer URL de Terraform outputs
    cd "$TERRAFORM_DIR"
    local lb_url
    lb_url=$(terraform output -raw load_balancer_url 2>/dev/null || echo "")
    cd ..
    
    if [[ -n "$lb_url" ]]; then
        log "INFO" "Verificando Load Balancer: $lb_url"
        
        if curl -s --max-time 10 "$lb_url/health" | grep -q "ok"; then
            log "INFO" "Load Balancer respondiendo correctamente"
        else
            log "ERROR" "Load Balancer no está respondiendo correctamente"
            return 1
        fi
    fi
}

# Validar servicios web
validate_web_services() {
    local environment="$1"
    
    log "INFO" "Validando servicios web"
    
    # Verificar cada servidor web individualmente
    local web_ips
    web_ips=$(yq eval '.all.children.web_servers.hosts | keys | .[]' "$INVENTORY_FILE")
    
    for ip in $web_ips; do
        log "INFO" "Verificando servidor web: $ip"
        
        if curl -s --max-time 10 "http://$ip/health" | grep -q "ok"; then
            log "INFO" "Servidor web $ip respondiendo correctamente"
        else
            log "WARN" "Servidor web $ip no está respondiendo"
        fi
    done
}

# Destruir infraestructura
confirm_terraform_destroy() {
    local environment="$1"
    
    echo "⚠️ PELIGRO: Vas a DESTRUIR toda la infraestructura de $environment"
    echo "Esta acción es IRREVERSIBLE y eliminará:"
    echo "- Todas las instancias EC2"
    echo "- Todos los datos en volúmenes"
    echo "- Configuraciones de red"
    echo "- Load Balancers y otros recursos"
    
    read -p "¿Estás ABSOLUTAMENTE SEGURO de que quieres destruir la infraestructura de $environment? Escribe 'destroy-$environment' para confirmar: " -r
    
    if [[ "$REPLY" != "destroy-$environment" ]]; then
        log "INFO" "Operación de destrucción cancelada"
        exit 0
    fi
    
    # Segunda confirmación para producción
    if [[ "$environment" == "prod" ]]; then
        read -p "ÚLTIMA CONFIRMACIÓN: Escribe 'DESTROY-PRODUCTION-NOW' para continuar: " -r
        if [[ "$REPLY" != "DESTROY-PRODUCTION-NOW" ]]; then
            log "INFO" "Operación de destrucción cancelada"
            exit 0
        fi
    fi
}

# Destruir con Terraform
terraform_destroy() {
    local environment="$1"
    local var_file="./terraform/environments/${environment}.tfvars"
    
    log "INFO" "Destruyendo infraestructura para $environment"
    
    cd "$TERRAFORM_DIR"
    
    terraform destroy \
        -var-file="$var_file" \
        -var="environment=$environment" \
        -auto-approve
    
    cd ..
    
    # Limpiar archivos locales
    rm -f "$INVENTORY_FILE"
    rm -f "./terraform/plans/${environment}.tfplan"
    
    log "INFO" "Infraestructura de $environment destruida"
    send_alert "WARNING" "Infraestructura de $environment ha sido destruida"
}

# Función principal
main() {
    local environment="${1:-}"
    local action="${2:-apply}"
    
    if [[ -z "$environment" ]]; then
        cat <EOF
Uso: $0 <environment> [action]

Environments: dev, staging, prod
Actions: plan, apply, destroy

Ejemplos:
  $0 dev plan          # Generar plan para desarrollo
  $0 staging apply     # Aplicar cambios en staging
  $0 prod destroy      # Destruir infraestructura de producción

EOF
        exit 1
    fi
    
    # Validar entorno
    case "$environment" in
        dev|staging|prod)
            log "INFO" "Procesando entorno: $environment"
            ;;
        *)
            log "FATAL" "Entorno inválido: $environment. Usar: dev, staging, prod"
            ;;
    esac
    
    # Ejecutar provisioning
    provision_infrastructure "$environment" "$action"
    
    log "INFO" "✅ Operación completada para $environment"
}

# Ejecutar si es el script principal
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi

Mejores Prácticas de Seguridad en Bash

Hardening y Configuración Segura

#!/bin/bash
#
# security-hardening.sh - Script para hardening de sistemas Linux
#

set -euo pipefail

# Configuración de seguridad
readonly AUDIT_LOG="/var/log/security-hardening.log"
readonly BACKUP_DIR="/root/security-backups"
readonly HARDENING_LEVEL="${HARDENING_LEVEL:-medium}"  # basic, medium, strict

# Función principal de hardening
main_hardening() {
    log "INFO" "Iniciando hardening de seguridad - Nivel: $HARDENING_LEVEL"
    
    create_security_backup
    system_updates
    configure_ssh_hardening
    configure_firewall
    disable_unnecessary_services
    configure_file_permissions
    configure_audit_logging
    configure_login_security
    install_security_tools
    configure_network_security
    generate_security_report
    
    log "INFO" "Hardening de seguridad completado"
}

# Crear backup de configuraciones críticas
create_security_backup() {
    log "INFO" "Creando backup de configuraciones de seguridad"
    
    mkdir -p "$BACKUP_DIR"
    local timestamp
    timestamp=$(date +%Y%m%d_%H%M%S)
    
    # Backup de archivos críticos
    local critical_files=(
        "/etc/ssh/sshd_config"
        "/etc/sudoers"
        "/etc/passwd"
        "/etc/shadow"
        "/etc/group"
        "/etc/fstab"
        "/etc/hosts"
        "/etc/sysctl.conf"
    )
    
    for file in "${critical_files[@]}"; do
        if [[ -f "$file" ]]; then
            cp "$file" "$BACKUP_DIR/$(basename $file).${timestamp}"
            log "DEBUG" "Backup creado: $file"
        fi
    done
}

# Actualización del sistema
system_updates() {
    log "INFO" "Actualizando sistema y aplicando parches de seguridad"
    
    # Detectar distribución
    if command -v apt-get &>/dev/null; then
        # Ubuntu/Debian
        apt-get update
        apt-get upgrade -y
        apt-get autoremove -y
        
        # Instalar actualizaciones de seguridad automáticas
        apt-get install -y unattended-upgrades
        dpkg-reconfigure -plow unattended-upgrades
        
    elif command -v yum &>/dev/null; then
        # RHEL/CentOS
        yum update -y
        yum install -y yum-cron
        systemctl enable yum-cron
        systemctl start yum-cron
        
    elif command -v dnf &>/dev/null; then
        # Fedora
        dnf update -y
        dnf install -y dnf-automatic
        systemctl enable dnf-automatic.timer
        systemctl start dnf-automatic.timer
    fi
    
    log "INFO" "Actualizaciones del sistema completadas"
}

# Hardening de SSH
configure_ssh_hardening() {
    log "INFO" "Configurando hardening de SSH"
    
    local ssh_config="/etc/ssh/sshd_config"
    
    # Crear configuración SSH segura
    cat > "$ssh_config" <'EOF'
# SSH Hardening Configuration
Port 22
Protocol 2

# Authentication
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
PermitEmptyPasswords no
ChallengeResponseAuthentication no
KerberosAuthentication no
GSSAPIAuthentication no

# Security settings
AllowUsers deploy ubuntu ec2-user
DenyUsers root
MaxAuthTries 3
MaxStartups 10:30:60
ClientAliveInterval 600
ClientAliveCountMax 3

# Disable unused features
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
PermitTunnel no
PermitUserEnvironment no

# Logging
SyslogFacility AUTH
LogLevel INFO

# Crypto settings
Ciphers aes256-gcm@openssh.com,chacha20-poly1305@openssh.com,aes256-ctr
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com
KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521

# Banner
Banner /etc/ssh/ssh_banner
EOF
    
    # Crear banner de seguridad
    cat > "/etc/ssh/ssh_banner" <'EOF'
***********************************************************************
*                      AUTHORIZED ACCESS ONLY                        *
*                                                                     *
* This system is for authorized users only. All activity is          *
**monitored and recorded. Unauthorized access is prohibited and      *
**will be prosecuted to the full extent of the law.                  *
***********************************************************************
EOF
    
    # Verificar configuración y reiniciar
    if sshd -t; then
        systemctl reload sshd
        log "INFO" "SSH hardening aplicado exitosamente"
    else
        log "ERROR" "Error en configuración SSH, restaurando backup"
        cp "$BACKUP_DIR/sshd_config."* /etc/ssh/sshd_config
        systemctl reload sshd
    fi
}

# Configurar firewall
configure_firewall() {
    log "INFO" "Configurando firewall"
    
    # Instalar y configurar UFW (Ubuntu) o firewalld (RHEL)
    if command -v ufw &>/dev/null; then
        # Ubuntu UFW
        ufw --force reset
        ufw default deny incoming
        ufw default allow outgoing
        
        # Permitir SSH
        ufw allow 22/tcp
        
        # Permitir servicios web si están instalados
        if systemctl is-active --quiet nginx apache2; then
            ufw allow 80/tcp
            ufw allow 443/tcp
        fi
        
        # Habilitar UFW
        ufw --force enable
        
        log "INFO" "UFW configurado y habilitado"
        
    elif command -v firewall-cmd &>/dev/null; then
        # RHEL firewalld
        systemctl enable firewalld
        systemctl start firewalld
        
        # Configuración básica
        firewall-cmd --set-default-zone=public
        firewall-cmd --permanent --remove-service=dhcpv6-client
        firewall-cmd --permanent --add-service=ssh
        
        # Servicios web si están activos
        if systemctl is-active --quiet nginx httpd; then
            firewall-cmd --permanent --add-service=http
            firewall-cmd --permanent --add-service=https
        fi
        
        firewall-cmd --reload
        log "INFO" "firewalld configurado y habilitado"
    else
        # Fallback a iptables básico
        log "WARN" "Configurando iptables básico"
        configure_basic_iptables
    fi
}

# Configuración básica de iptables
configure_basic_iptables() {
    # Limpiar reglas existentes
    iptables -F
    iptables -X
    iptables -t nat -F
    iptables -t nat -X
    iptables -t mangle -F
    iptables -t mangle -X
    
    # Políticas por defecto
    iptables -P INPUT DROP
    iptables -P FORWARD DROP
    iptables -P OUTPUT ACCEPT
    
    # Permitir loopback
    iptables -A INPUT -i lo -j ACCEPT
    iptables -A OUTPUT -o lo -j ACCEPT
    
    # Permitir conexiones establecidas
    iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
    
    # Permitir SSH
    iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
    
    # Permitir HTTP/HTTPS si hay servidor web
    if systemctl is-active --quiet nginx apache2 httpd; then
        iptables -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
        iptables -A INPUT -p tcp --dport 443 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
    fi
    
    # Protección contra ataques
    iptables -A INPUT -p tcp --tcp-flags ALL NONE -j DROP
    iptables -A INPUT -p tcp --tcp-flags ALL ALL -j DROP
    iptables -A INPUT -p tcp --tcp-flags ALL FIN,URG,PSH -j DROP
    iptables -A INPUT -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP
    
    # Guardar reglas
    if command -v iptables-save &>/dev/null; then
        iptables-save > /etc/iptables/rules.v4
    fi
    
    log "INFO" "iptables básico configurado"
}

# Deshabilitar servicios innecesarios
disable_unnecessary_services() {
    log "INFO" "Deshabilitando servicios innecesarios"
    
    local services_to_disable=(
        "avahi-daemon"
        "cups"
        "nfs-client.target"
        "rpcbind"
        "telnet"
        "rsh"
        "rlogin"
        "tftp"
        "xinetd"
        "ypbind"
        "bluetooth"
    )
    
    for service in "${services_to_disable[@]}"; do
        if systemctl is-enabled "$service" &>/dev/null; then
            systemctl disable "$service"
            systemctl stop "$service"
            log "DEBUG" "Servicio deshabilitado: $service"
        fi
    done
    
    # Remover paquetes innecesarios
    if command -v apt-get &>/dev/null; then
        local packages_to_remove=(
            "telnet"
            "rsh-client"
            "rsh-redone-client"
            "talk"
            "ntalk"
            "finger"
            "xinetd"
        )
        
        for package in "${packages_to_remove[@]}"; do
            if dpkg -l | grep -q "^ii.*$package"; then
                apt-get remove -y "$package"
                log "DEBUG" "Paquete removido: $package"
            fi
        done
    fi
}

# Configurar permisos de archivos críticos
configure_file_permissions() {
    log "INFO" "Configurando permisos de archivos críticos"
    
    # Archivos de configuración críticos
    local critical_files=(
        "/etc/passwd:644"
        "/etc/shadow:600"
        "/etc/group:644"
        "/etc/gshadow:600"
        "/etc/ssh/sshd_config:600"
        "/etc/sudoers:440"
        "/boot/grub/grub.cfg:600"
        "/etc/crontab:600"
    )
    
    for file_perm in "${critical_files[@]}"; do
        local file="${file_perm%:*}"
        local perm="${file_perm#*:}"
        
        if [[ -f "$file" ]]; then
            chmod "$perm" "$file"
            log "DEBUG" "Permisos configurados: $file ($perm)"
        fi
    done
    
    # Directorios críticos
    local critical_dirs=(
        "/etc/ssh:755"
        "/etc/cron.d:700"
        "/etc/cron.daily:700"
        "/etc/cron.hourly:700"
        "/etc/cron.monthly:700"
        "/etc/cron.weekly:700"
        "/var/log:750"
    )
    
    for dir_perm in "${critical_dirs[@]}"; do
        local dir="${dir_perm%:*}"
        local perm="${dir_perm#*:}"
        
        if [[ -d "$dir" ]]; then
            chmod "$perm" "$dir"
            log "DEBUG" "Permisos de directorio configurados: $dir ($perm)"
        fi
    done
    
    # Remover archivos world-writable peligrosos
    find / -type f -perm -002 -exec ls -la {} \; 2>/dev/null | \
        grep -v "/proc\|/sys\|/dev" | head -20 | \
        while read -r line; do
            log "WARN" "Archivo world-writable encontrado: $line"
        done
}

# Configurar logging de auditoría
configure_audit_logging() {
    log "INFO" "Configurando sistema de auditoría"
    
    # Instalar auditd
    if command -v apt-get &>/dev/null; then
        apt-get install -y auditd audispd-plugins
    elif command -v yum &>/dev/null; then
        yum install -y audit
    elif command -v dnf &>/dev/null; then
        dnf install -y audit
    fi
    
    # Configurar reglas de auditoría
    cat > "/etc/audit/rules.d/audit.rules" <'EOF'
# Audit Rules for Security Monitoring

# Delete all existing rules
-D

# Buffer size
-b 8192

# Failure mode (0=silent, 1=printk, 2=panic)
-f 1

# Monitor authentication events
-w /var/log/auth.log -p wa -k authentication
-w /var/log/secure -p wa -k authentication

# Monitor system configuration changes
-w /etc/passwd -p wa -k passwd_changes
-w /etc/group -p wa -k group_changes
-w /etc/shadow -p wa -k shadow_changes
-w /etc/sudoers -p wa -k sudoers_changes

# Monitor SSH configuration
-w /etc/ssh/sshd_config -p wa -k ssh_config

# Monitor system calls
-a always,exit -F arch=b64 -S adjtimex -S settimeofday -k time_change
-a always,exit -F arch=b32 -S adjtimex -S settimeofday -S stime -k time_change

# Monitor file deletions
-a always,exit -F arch=b64 -S unlink -S unlinkat -S rename -S renameat -k delete

# Monitor privilege escalation
-a always,exit -F arch=b64 -S setuid -S setgid -S setreuid -S setregid -k privilege_escalation

# Make configuration immutable
-e 2
EOF
    
    # Reiniciar servicio
    systemctl enable auditd
    systemctl restart auditd
    
    log "INFO" "Sistema de auditoría configurado"
}

# Configurar seguridad de login
configure_login_security() {
    log "INFO" "Configurando seguridad de login"
    
    # Configurar PAM para bloqueo de cuentas
    if [[ -f "/etc/pam.d/common-auth" ]]; then
        # Ubuntu/Debian
        if ! grep -q "pam_tally2" /etc/pam.d/common-auth; then
            sed -i '1i auth required pam_tally2.so deny=5 unlock_time=900 onerr=fail' /etc/pam.d/common-auth
        fi
    elif [[ -f "/etc/pam.d/system-auth" ]]; then
        # RHEL/CentOS
        if ! grep -q "pam_faillock" /etc/pam.d/system-auth; then
            authconfig --enablefaillock --faillockargs="deny=5 unlock_time=900" --update
        fi
    fi
    
    # Configurar política de contraseñas
    cat > "/etc/security/pwquality.conf" <'EOF'
# Password Quality Configuration
minlen = 12
minclass = 3
maxrepeat = 2
maxclasschars = 4
difok = 8
gecoscheck = 1
dictcheck = 1
usercheck = 1
enforcing = 1
EOF
    
    # Configurar límites de recursos
    cat > "/etc/security/limits.conf" <'EOF'
# Resource Limits Configuration

# Core dumps disabled for all users
**hard core 0

# Maximum number of processes
**hard nproc 10000
**soft nproc 5000

# Maximum number of open files
**hard nofile 65536
**soft nofile 32768

# Maximum file size
**hard fsize unlimited
**soft fsize unlimited

# Maximum memory locked
**hard memlock unlimited
**soft memlock unlimited
EOF
    
    # Configurar timeout de sesión
    echo 'export TMOUT=1800' >> /etc/profile
    echo 'readonly TMOUT' >> /etc/profile
    
    log "INFO" "Seguridad de login configurada"
}

# Instalar herramientas de seguridad
install_security_tools() {
    log "INFO" "Instalando herramientas de seguridad"
    
    local security_tools=()
    
    if command -v apt-get &>/dev/null; then
        security_tools=(
            "fail2ban"
            "rkhunter"
            "chkrootkit"
            "clamav"
            "aide"
            "lynis"
            "nmap"
            "netstat-nat"
        )
        
        apt-get update
        for tool in "${security_tools[@]}"; do
            if ! dpkg -l | grep -q "^ii.*$tool"; then
                apt-get install -y "$tool"
                log "DEBUG" "Herramienta instalada: $tool"
            fi
        done
        
    elif command -v yum &>/dev/null || command -v dnf &>/dev/null; then
        security_tools=(
            "fail2ban"
            "rkhunter"
            "chkrootkit"
            "clamav"
            "aide"
            "nmap"
        )
        
        local installer="yum"
        command -v dnf &>/dev/null && installer="dnf"
        
        for tool in "${security_tools[@]}"; do
            if ! rpm -qa | grep -q "$tool"; then
                $installer install -y "$tool"
                log "DEBUG" "Herramienta instalada: $tool"
            fi
        done
    fi
    
    # Configurar fail2ban
    configure_fail2ban
    
    log "INFO" "Herramientas de seguridad instaladas"
}

# Configurar fail2ban
configure_fail2ban() {
    log "INFO" "Configurando fail2ban"
    
    cat > "/etc/fail2ban/jail.local" <'EOF'
[DEFAULT]
# Ban hosts for 1 hour
bantime = 3600

# A host is banned if it has generated "maxretry" during the last "findtime"
findtime = 600
maxretry = 5

# "ignoreip" can be an IP address, a CIDR mask or a DNS host
ignoreip = 127.0.0.1/8 ::1

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3

[nginx-http-auth]
enabled = true
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 6

[nginx-limit-req]
enabled = true
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 10

[apache-auth]
enabled = true
filter = apache-auth
logpath = /var/log/apache*/*error.log
maxretry = 6

[apache-badbots]
enabled = true
filter = apache-badbots
logpath = /var/log/apache*/*access.log
maxretry = 2
EOF
    
    # Reiniciar fail2ban
    systemctl enable fail2ban
    systemctl restart fail2ban
    
    log "INFO" "fail2ban configurado y habilitado"
}

# Configurar seguridad de red
configure_network_security() {
    log "INFO" "Configurando seguridad de red"
    
    # Configurar parámetros del kernel para seguridad
    cat > "/etc/sysctl.d/99-security.conf" <'EOF'
# Network Security Configuration

# IP Spoofing protection
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.rp_filter = 1

# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0

# Ignore send redirects
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0

# Disable source packet routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0

# Log Martians
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1

# Ignore ICMP ping requests
net.ipv4.icmp_echo_ignore_all = 1

# Ignore Directed pings
net.ipv4.icmp_echo_ignore_broadcasts = 1

# Disable IPv6 if not needed
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1

# TCP SYN flood protection
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 5

# Control memory allocation
vm.mmap_min_addr = 65536

# Hide kernel pointers
kernel.kptr_restrict = 1

# Disable core dumps
fs.suid_dumpable = 0

# Control dmesg access
kernel.dmesg_restrict = 1
EOF
    
    # Aplicar configuración
    sysctl --system
    
    log "INFO" "Seguridad de red configurada"
}

# Generar reporte de seguridad
generate_security_report() {
    local report_file="/root/security-report-$(date +%Y%m%d_%H%M%S).txt"
    
    log "INFO" "Generando reporte de seguridad: $report_file"
    
    cat > "$report_file" <EOF
SECURITY HARDENING REPORT
Generated: $(date)
Hostname: $(hostname)
Hardening Level: $HARDENING_LEVEL

=== SYSTEM INFORMATION ===
OS: $(cat /etc/os-release | grep PRETTY_NAME | cut -d'"' -f2)
Kernel: $(uname -r)
Uptime: $(uptime -p)

=== SERVICES STATUS ===
SSH Status: $(systemctl is-active sshd || echo "inactive")
Firewall Status: $(systemctl is-active ufw || systemctl is-active firewalld || echo "inactive")
Fail2ban Status: $(systemctl is-active fail2ban || echo "inactive")
Audit Status: $(systemctl is-active auditd || echo "inactive")

=== NETWORK CONFIGURATION ===
Open Ports:
$(ss -tuln)

=== USER ACCOUNTS ===
Regular Users:
$(awk -F: '$3 >= 1000 && $1 != "nobody" {print $1}' /etc/passwd)

Users with sudo privileges:
$(grep -Po '^sudo.+:\K.*$' /etc/group 2>/dev/null || echo "N/A")

=== SECURITY TOOLS ===
EOF
    
    # Verificar herramientas instaladas
    local security_tools=("fail2ban" "rkhunter" "chkrootkit" "clamav" "aide" "lynis")
    for tool in "${security_tools[@]}"; do
        if command -v "$tool" &>/dev/null; then
            echo "$tool: INSTALLED" >> "$report_file"
        else
            echo "$tool: NOT INSTALLED" >> "$report_file"
        fi
    done
    
    cat >> "$report_file" <EOF

=== RECENT SECURITY EVENTS ===
SSH Login Attempts (last 24h):
$(grep "$(date --date='1 day ago' '+%b %d')" /var/log/auth.log 2>/dev/null | grep "sshd" | wc -l || echo "N/A")

Failed Login Attempts:
$(grep "Failed password" /var/log/auth.log 2>/dev/null | tail -5 || echo "N/A")

=== RECOMMENDATIONS ===
1. Regularly update system packages
2. Review user accounts and remove unused ones
3. Monitor logs for suspicious activity
4. Run security scans with installed tools
5. Backup critical configuration files

EOF
    
    log "INFO" "Reporte de seguridad generado: $report_file"
    
    # Enviar reporte por email si está configurado
    if [[ -n "${SECURITY_EMAIL:-}" ]] && command -v mail &>/dev/null; then
        mail -s "Security Hardening Report - $(hostname)" "$SECURITY_EMAIL" "$report_file"
        log "INFO" "Reporte enviado por email a $SECURITY_EMAIL"
    fi
}

# Función principal
main() {
    local action="${1:-hardening}"
    
    case "$action" in
        hardening)
            main_hardening
            ;;
        report)
            generate_security_report
            ;;
        *)
            cat <EOF
Uso: $0 [action]

Actions:
  hardening  - Ejecutar hardening completo del sistema (por defecto)
  report     - Generar solo reporte de seguridad

Variables de entorno:
  HARDENING_LEVEL    - Nivel de hardening (basic|medium|strict) [default: medium]
  SECURITY_EMAIL     - Email para enviar reportes de seguridad

Ejemplo:
  HARDENING_LEVEL=strict $0 hardening
  $0 report

EOF
            exit 1
            ;;
    esac
}

# Ejecutar si es el script principal
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi

Debugging y Optimización de Scripts

Herramientas y Técnicas de Debugging

#!/bin/bash
#
# debug-helper.sh - Herramientas y técnicas de debugging para Bash
#

# Habilitar debugging estricto
set -euo pipefail

# Variables de debugging
DEBUG_LEVEL="${DEBUG_LEVEL:-1}"  # 0=off, 1=basic, 2=verbose, 3=trace
DEBUG_LOG="${DEBUG_LOG:-/tmp/debug.log}"

# Función de debugging mejorada
debug() {
    local level="$1"
    local message="$2"
    local caller="${BASH_SOURCE[2]##*/}:${BASH_LINENO[1]}"
    
    if [[ $level -le $DEBUG_LEVEL ]]; then
        local timestamp
        timestamp=$(date '+%Y-%m-%d %H:%M:%S.%3N')
        echo "[$timestamp] DEBUG[$level] $caller: $message" | tee -a "$DEBUG_LOG"
    fi
}

# Profiling de funciones
profile_function() {
    local func_name="$1"
    shift
    
    local start_time
    start_time=$(date +%s.%N)
    
    # Ejecutar función
    "$func_name" "$@"
    local result=$?
    
    local end_time
    end_time=$(date +%s.%N)
    local duration
    duration=$(echo "$end_time - $start_time" | bc -l)
    
    debug 1 "Función $func_name ejecutada en ${duration}s"
    
    return $result
}

# Validación de entrada con debugging
validate_input() {
    local input="$1"
    local pattern="$2"
    local description="${3:-input}"
    
    debug 2 "Validando $description: '$input' contra patrón '$pattern'"
    
    if [[ ! $input =~ $pattern ]]; then
        debug 1 "Validación falló para $description"
        echo "Error: $description no válido: '$input'" >&2
        echo "Patrón esperado: $pattern" >&2
        return 1
    fi
    
    debug 2 "Validación exitosa para $description"
    return 0
}

# Ejemplo de uso de debugging
example_function() {
    debug 3 "Iniciando example_function con parámetros: $*"
    
    local param1="${1:-default}"
    local param2="${2:-}"
    
    debug 2 "param1='$param1', param2='$param2'"
    
    # Validar parámetros
    if ! validate_input "$param1" '^[a-zA-Z0-9_-]+$' "param1"; then
        return 1
    fi
    
    if [[ -n "$param2" ]] && ! validate_input "$param2" '^[0-9]+$' "param2"; then
        return 1
    fi
    
    # Simular procesamiento
    debug 2 "Procesando datos..."
    sleep 1
    
    debug 1 "Procesamiento completado exitosamente"
    echo "Resultado: $param1 procesado"
}

# Mostrar ejemplos de debugging
show_debug_examples() {
    echo "=== Ejemplos de Debugging ==="
    echo
    
    echo "1. Debugging básico (DEBUG_LEVEL=1):"
    DEBUG_LEVEL=1 example_function "test123"
    echo
    
    echo "2. Debugging verbose (DEBUG_LEVEL=2):"
    DEBUG_LEVEL=2 example_function "test456" "789"
    echo
    
    echo "3. Debugging con trace (DEBUG_LEVEL=3):"
    DEBUG_LEVEL=3 example_function "test789"
    echo
    
    echo "4. Profiling de función:"
    profile_function example_function "profile_test"
    echo
    
    echo "5. Validación con error:"
    example_function "invalid@param" "not_a_number" || true
    echo
}

# Ejecutar ejemplos si se llama directamente
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    show_debug_examples
fi

Conclusión

El scripting en Bash sigue siendo una competencia fundamental en el arsenal de cualquier profesional DevOps moderno. A través de esta guía completa, hemos explorado desde los fundamentos hasta técnicas avanzadas que te permitirán automatizar eficientemente tus flujos de trabajo.

Puntos Clave para el Éxito

1. Fundamentos Sólidos

  • Estructura de scripts profesional con validación de errores
  • Manejo robusto de variables y parámetros
  • Sistema de logging comprehensivo con diferentes niveles

2. Automatización Integral

  • Pipelines CI/CD completos con validaciones de seguridad
  • Deployments inteligentes con rollback automático
  • Monitoreo proactivo con alertas multi-canal

3. Seguridad por Diseño

  • Hardening sistemático de infraestructura
  • Manejo seguro de secretos y credenciales
  • Auditoría y compliance automatizados

4. Operaciones a Escala

  • Provisioning de infraestructura con Terraform
  • Configuración masiva con Ansible
  • Monitoreo y observabilidad comprehensivos

Mejores Prácticas para Implementar

  1. Comienza Simple: Inicia con scripts básicos y evoluciona gradualmente hacia soluciones más complejas
  2. Testing Continuo: Implementa validaciones automáticas en cada etapa
  3. Documentación Viva: Mantén documentación actualizada y ejemplos prácticos
  4. Seguridad Primero: Nunca compromas la seguridad por conveniencia
  5. Monitoreo Constante: Implementa observabilidad desde el primer día

El Futuro de Bash en DevOps

Aunque emergen nuevas tecnologías como contenedores, Kubernetes y herramientas cloud-native, Bash mantiene su relevancia por:

  • Compatibilidad Universal: Funciona en cualquier sistema Unix/Linux
  • Integración Natural: Se conecta seamlessly con todas las herramientas DevOps
  • Performance: Overhead mínimo para operaciones del sistema
  • Simplicidad: Curva de aprendizaje accesible para cualquier nivel

La combinación de Bash con herramientas modernas como Docker, Kubernetes, y plataformas cloud crea un ecosistema poderoso que puede manejar desde tareas simples hasta orquestación compleja de infraestructura empresarial.

Próximos Pasos

  1. Practica Regularmente: Implementa los ejemplos en tu entorno
  2. Contribuye a la Comunidad: Comparte tus scripts y aprende de otros
  3. Mantente Actualizado: Sigue las mejores prácticas de la industria
  4. Integra con Moderno: Combina Bash con herramientas cloud-native

El dominio del scripting en Bash te convertirá en un profesional DevOps más eficiente, capaz de automatizar cualquier tarea y resolver problemas complejos con soluciones elegantes y mantenibles.

Recursos Adicionales

Documentación y Referencias

Herramientas de Desarrollo

  • Bats - Framework de testing para Bash
  • bashdb - Debugger interactivo para Bash
  • shellspec - Testing framework con sintaxis BDD
  • shfmt - Formateador de código shell

Colecciones de Scripts

Integración con DevOps