Volver
Módulo 11 - Integración y Automatización EXPERTO

Instaladores con Bash

Crea instaladores profesionales y asistentes de configuración interactivos

Los instaladores creados con Bash permiten automatizar completamente el proceso de instalación y configuración de aplicaciones. Desde simples scripts de setup hasta complejos asistentes interactivos, Bash proporciona todas las herramientas necesarias para crear experiencias de instalación profesionales.

Flujo Típico de un Instalador

Fases de un Instalador Profesional:

1
Pre-instalación: Verificar requisitos del sistema, permisos y dependencias
2
Configuración: Recopilar información del usuario y configurar parámetros
3
Descarga: Descargar archivos necesarios y verificar integridad
4
Instalación: Copiar archivos, crear directorios y establecer permisos
5
Configuración: Aplicar configuraciones y crear archivos de config
6
Post-instalación: Iniciar servicios, crear usuarios y finalizar setup
7
Verificación: Probar la instalación y reportar el estado final

Instalador Básico

Instalador simple - basic_installer.sh
bash
#!/bin/bash

# Instalador básico de aplicación
# Versión: 1.0

set -euo pipefail

# Configuración del instalador
APP_NAME="MiApp"
APP_VERSION="1.0.0"
INSTALL_DIR="/opt/miapp"
CONFIG_DIR="/etc/miapp"
LOG_DIR="/var/log/miapp"
SERVICE_USER="miapp"

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

# Función de logging
log() {
    local level="$1"
    shift
    local message="$*"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    case "$level" in
        INFO)
            echo -e "${BLUE}[INFO]${NC} $message"
            ;;
        WARN)
            echo -e "${YELLOW}[WARN]${NC} $message"
            ;;
        ERROR)
            echo -e "${RED}[ERROR]${NC} $message" >&2
            ;;
        SUCCESS)
            echo -e "${GREEN}[SUCCESS]${NC} $message"
            ;;
    esac
    
    echo "[$timestamp] [$level] $message" >> "/tmp/install_${APP_NAME,,}.log"
}

# Banner del instalador
show_banner() {
    clear
    echo -e "${BLUE}"
    cat << "EOF"
╔══════════════════════════════════════════════════════════════╗
║                    INSTALADOR DE MIAPP                      ║
║                        Versión 1.0                          ║
╚══════════════════════════════════════════════════════════════╝
EOF
    echo -e "${NC}"
    echo "Bienvenido al instalador de $APP_NAME v$APP_VERSION"
    echo ""
}

# Verificar privilegios de root
check_root() {
    if [ "$EUID" -ne 0 ]; then
        log ERROR "Este instalador debe ejecutarse con privilegios de root"
        echo "Por favor, ejecute: sudo $0"
        exit 1
    fi
}

# Verificar requisitos del sistema
check_requirements() {
    log INFO "Verificando requisitos del sistema..."
    
    # Verificar OS
    if ! command -v lsb_release >/dev/null 2>&1; then
        log WARN "No se puede determinar la distribución exacta"
    else
        local distro=$(lsb_release -si)
        local version=$(lsb_release -sr)
        log INFO "Detectado: $distro $version"
    fi
    
    # Verificar espacio en disco (al menos 100MB)
    local available_space=$(df / | tail -1 | awk '{print $4}')
    local required_space=102400  # 100MB en KB
    
    if [ "$available_space" -lt "$required_space" ]; then
        log ERROR "Espacio insuficiente en disco. Requerido: 100MB"
        exit 1
    fi
    
    # Verificar memoria RAM (al menos 512MB)
    local total_mem=$(free -m | awk '/^Mem:/ {print $2}')
    if [ "$total_mem" -lt 512 ]; then
        log WARN "RAM limitada detectada: ${total_mem}MB (recomendado: 1GB+)"
    fi
    
    # Verificar dependencias
    local deps=("curl" "wget" "tar" "systemctl")
    local missing_deps=()
    
    for dep in "${deps[@]}"; do
        if ! command -v "$dep" >/dev/null 2>&1; then
            missing_deps+=("$dep")
        fi
    done
    
    if [ ${#missing_deps[@]} -gt 0 ]; then
        log ERROR "Dependencias faltantes: ${missing_deps[*]}"
        log INFO "Instale las dependencias con:"
        log INFO "  apt install ${missing_deps[*]}  # Ubuntu/Debian"
        log INFO "  yum install ${missing_deps[*]}  # CentOS/RHEL"
        exit 1
    fi
    
    log SUCCESS "Requisitos del sistema verificados"
}

# Crear directorios necesarios
create_directories() {
    log INFO "Creando estructura de directorios..."
    
    local dirs=(
        "$INSTALL_DIR"
        "$INSTALL_DIR/bin"
        "$INSTALL_DIR/lib"
        "$INSTALL_DIR/share"
        "$CONFIG_DIR"
        "$LOG_DIR"
    )
    
    for dir in "${dirs[@]}"; do
        if [ ! -d "$dir" ]; then
            mkdir -p "$dir"
            log INFO "Directorio creado: $dir"
        else
            log INFO "Directorio ya existe: $dir"
        fi
    done
    
    log SUCCESS "Estructura de directorios creada"
}

# Crear usuario del sistema
create_service_user() {
    log INFO "Creando usuario del servicio: $SERVICE_USER"
    
    if id "$SERVICE_USER" &>/dev/null; then
        log INFO "Usuario $SERVICE_USER ya existe"
    else
        useradd -r -s /bin/false -d "$INSTALL_DIR" "$SERVICE_USER"
        log SUCCESS "Usuario $SERVICE_USER creado"
    fi
    
    # Establecer permisos
    chown -R "$SERVICE_USER:$SERVICE_USER" "$INSTALL_DIR"
    chown -R "$SERVICE_USER:$SERVICE_USER" "$LOG_DIR"
    chmod 755 "$INSTALL_DIR"
    chmod 755 "$CONFIG_DIR"
    chmod 755 "$LOG_DIR"
    
    log SUCCESS "Permisos establecidos"
}

# Descargar e instalar archivos
install_files() {
    log INFO "Descargando e instalando archivos de la aplicación..."
    
    local temp_dir=$(mktemp -d)
    cd "$temp_dir"
    
    # Simular descarga de aplicación
    log INFO "Descargando $APP_NAME..."
    
    # En un instalador real, aquí descargarías los archivos
    # wget "https://releases.example.com/miapp-${APP_VERSION}.tar.gz"
    
    # Para este ejemplo, creamos archivos de muestra
    cat > "miapp.py" << 'EOF'
#!/usr/bin/env python3
import time
import sys

def main():
    print("MiApp v1.0.0 iniciado")
    while True:
        print(f"MiApp ejecutándose... {time.strftime('%Y-%m-%d %H:%M:%S')}")
        time.sleep(60)

if __name__ == "__main__":
    main()
EOF
    
    cat > "config.yml" << EOF
app:
  name: "$APP_NAME"
  version: "$APP_VERSION"
  log_level: "INFO"
  port: 8080

database:
  host: "localhost"
  port: 5432
  name: "miapp_db"
EOF
    
    # Copiar archivos
    cp "miapp.py" "$INSTALL_DIR/bin/"
    cp "config.yml" "$CONFIG_DIR/"
    chmod +x "$INSTALL_DIR/bin/miapp.py"
    
    # Crear script wrapper
    cat > "$INSTALL_DIR/bin/miapp" << EOF
#!/bin/bash
cd "$INSTALL_DIR"
exec python3 "$INSTALL_DIR/bin/miapp.py" "\$@"
EOF
    chmod +x "$INSTALL_DIR/bin/miapp"
    
    # Limpiar
    cd /
    rm -rf "$temp_dir"
    
    log SUCCESS "Archivos instalados correctamente"
}

# Crear servicio systemd
create_systemd_service() {
    log INFO "Creando servicio systemd..."
    
    cat > "/etc/systemd/system/miapp.service" << EOF
[Unit]
Description=$APP_NAME Service
After=network.target
Wants=network.target

[Service]
Type=simple
User=$SERVICE_USER
Group=$SERVICE_USER
WorkingDirectory=$INSTALL_DIR
ExecStart=$INSTALL_DIR/bin/miapp
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF
    
    systemctl daemon-reload
    systemctl enable miapp.service
    
    log SUCCESS "Servicio systemd creado y habilitado"
}

# Configuración post-instalación
post_install_config() {
    log INFO "Ejecutando configuración post-instalación..."
    
    # Crear archivo de entorno
    cat > "$CONFIG_DIR/environment" << EOF
MIAPP_HOME="$INSTALL_DIR"
MIAPP_CONFIG="$CONFIG_DIR"
MIAPP_LOG="$LOG_DIR"
EOF
    
    # Crear log rotación
    cat > "/etc/logrotate.d/miapp" << EOF
$LOG_DIR/*.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    create 640 $SERVICE_USER $SERVICE_USER
    postrotate
        systemctl reload miapp.service > /dev/null 2>&1 || true
    endscript
}
EOF
    
    log SUCCESS "Configuración post-instalación completada"
}

# Verificar instalación
verify_installation() {
    log INFO "Verificando instalación..."
    
    local errors=0
    
    # Verificar archivos
    local required_files=(
        "$INSTALL_DIR/bin/miapp"
        "$INSTALL_DIR/bin/miapp.py"
        "$CONFIG_DIR/config.yml"
        "/etc/systemd/system/miapp.service"
    )
    
    for file in "${required_files[@]}"; do
        if [ ! -f "$file" ]; then
            log ERROR "Archivo faltante: $file"
            ((errors++))
        fi
    done
    
    # Verificar permisos
    if [ ! -x "$INSTALL_DIR/bin/miapp" ]; then
        log ERROR "Archivo no ejecutable: $INSTALL_DIR/bin/miapp"
        ((errors++))
    fi
    
    # Verificar usuario
    if ! id "$SERVICE_USER" &>/dev/null; then
        log ERROR "Usuario de servicio no existe: $SERVICE_USER"
        ((errors++))
    fi
    
    # Verificar servicio
    if ! systemctl is-enabled miapp.service &>/dev/null; then
        log ERROR "Servicio no habilitado: miapp.service"
        ((errors++))
    fi
    
    if [ $errors -eq 0 ]; then
        log SUCCESS "Verificación completada - Sin errores"
        return 0
    else
        log ERROR "Verificación falló con $errors errores"
        return 1
    fi
}

# Mostrar información final
show_final_info() {
    echo ""
    echo -e "${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}"
    echo -e "${GREEN}║                   INSTALACIÓN COMPLETADA                    ║${NC}"
    echo -e "${GREEN}╚══════════════════════════════════════════════════════════════╝${NC}"
    echo ""
    echo "🎉 $APP_NAME v$APP_VERSION ha sido instalado exitosamente!"
    echo ""
    echo "📁 Archivos de instalación:"
    echo "   Aplicación: $INSTALL_DIR"
    echo "   Configuración: $CONFIG_DIR"
    echo "   Logs: $LOG_DIR"
    echo ""
    echo "👤 Usuario del servicio: $SERVICE_USER"
    echo ""
    echo "🔧 Comandos útiles:"
    echo "   Iniciar servicio:    sudo systemctl start miapp"
    echo "   Detener servicio:    sudo systemctl stop miapp"
    echo "   Estado del servicio: sudo systemctl status miapp"
    echo "   Ver logs:            sudo journalctl -u miapp -f"
    echo ""
    echo "📝 Archivo de configuración: $CONFIG_DIR/config.yml"
    echo "📋 Log de instalación: /tmp/install_${APP_NAME,,}.log"
    echo ""
    
    read -p "¿Desea iniciar el servicio ahora? (s/N): " start_service
    if [[ "$start_service" =~ ^[Ss]$ ]]; then
        log INFO "Iniciando servicio..."
        systemctl start miapp.service
        
        # Esperar y verificar estado
        sleep 2
        if systemctl is-active miapp.service &>/dev/null; then
            log SUCCESS "Servicio iniciado correctamente"
        else
            log ERROR "Error al iniciar el servicio"
            log INFO "Verifique los logs con: journalctl -u miapp"
        fi
    fi
    
    echo ""
    echo "¡Gracias por usar $APP_NAME!"
}

# Función de desinstalación
uninstall() {
    echo -e "${YELLOW}=== DESINSTALADOR DE $APP_NAME ===${NC}"
    echo ""
    
    read -p "¿Está seguro de que desea desinstalar $APP_NAME? (s/N): " confirm
    if [[ ! "$confirm" =~ ^[Ss]$ ]]; then
        echo "Desinstalación cancelada."
        exit 0
    fi
    
    log INFO "Iniciando desinstalación..."
    
    # Detener y deshabilitar servicio
    if systemctl is-active miapp.service &>/dev/null; then
        systemctl stop miapp.service
        log INFO "Servicio detenido"
    fi
    
    if systemctl is-enabled miapp.service &>/dev/null; then
        systemctl disable miapp.service
        log INFO "Servicio deshabilitado"
    fi
    
    # Eliminar archivos de systemd
    rm -f /etc/systemd/system/miapp.service
    rm -f /etc/logrotate.d/miapp
    systemctl daemon-reload
    
    # Eliminar directorios
    rm -rf "$INSTALL_DIR"
    rm -rf "$CONFIG_DIR"
    
    read -p "¿Eliminar logs en $LOG_DIR? (s/N): " remove_logs
    if [[ "$remove_logs" =~ ^[Ss]$ ]]; then
        rm -rf "$LOG_DIR"
        log INFO "Logs eliminados"
    fi
    
    # Eliminar usuario (opcional)
    read -p "¿Eliminar usuario del sistema '$SERVICE_USER'? (s/N): " remove_user
    if [[ "$remove_user" =~ ^[Ss]$ ]]; then
        userdel "$SERVICE_USER" 2>/dev/null || true
        log INFO "Usuario eliminado"
    fi
    
    log SUCCESS "Desinstalación completada"
}

# Función principal
main() {
    case "${1:-install}" in
        install)
            show_banner
            check_root
            check_requirements
            create_directories
            create_service_user
            install_files
            create_systemd_service
            post_install_config
            
            if verify_installation; then
                show_final_info
            else
                log ERROR "La instalación no pudo ser verificada completamente"
                exit 1
            fi
            ;;
        uninstall)
            check_root
            uninstall
            ;;
        verify)
            verify_installation
            ;;
        *)
            echo "Uso: $0 [install|uninstall|verify]"
            echo ""
            echo "Comandos:"
            echo "  install   - Instalar $APP_NAME (por defecto)"
            echo "  uninstall - Desinstalar $APP_NAME"
            echo "  verify    - Verificar instalación existente"
            exit 1
            ;;
    esac
}

# Manejador de señales para limpieza
cleanup() {
    log WARN "Instalación interrumpida por el usuario"
    exit 130
}

trap cleanup SIGINT SIGTERM

# Ejecutar función principal
main "$@"
sudo ./basic_installer.sh
sudo ./basic_installer.sh uninstall
./basic_installer.sh verify

Instalador Interactivo Avanzado

Instalador con wizard - interactive_installer.sh
bash
#!/bin/bash

# Instalador interactivo avanzado con wizard de configuración
# Versión: 2.0

set -euo pipefail

# Configuración global
declare -g APP_NAME="WebServer Pro"
declare -g APP_VERSION="2.0.0"
declare -g INSTALL_BASE="/opt"
declare -g INSTALL_DIR=""
declare -g CONFIG_DIR=""
declare -g DATA_DIR=""
declare -g LOG_DIR=""
declare -g SERVICE_USER=""
declare -g HTTP_PORT="80"
declare -g HTTPS_PORT="443"
declare -g ADMIN_PORT="8080"
declare -g SSL_ENABLED=false
declare -g DB_TYPE=""
declare -g DB_HOST="localhost"
declare -g DB_PORT=""
declare -g DB_NAME=""
declare -g DB_USER=""
declare -g DB_PASS=""

# Colores y estilos
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
WHITE='\033[1;37m'
NC='\033[0m'

# Función para limpiar pantalla y mostrar header
show_header() {
    clear
    echo -e "${BLUE}"
    cat << "EOF"
╔══════════════════════════════════════════════════════════════════════╗
║                        WEBSERVER PRO INSTALLER                      ║
║                              v2.0.0                                  ║
║                                                                      ║
║                   Asistente de Configuración Avanzado               ║
╚══════════════════════════════════════════════════════════════════════╝
EOF
    echo -e "${NC}\n"
}

# Función para mostrar progreso
show_progress() {
    local current="$1"
    local total="$2"
    local description="$3"
    local percent=$((current * 100 / total))
    local filled=$((percent * 50 / 100))
    
    printf "${CYAN}Progreso: [${NC}"
    for ((i=1; i<=50; i++)); do
        if [ $i -le $filled ]; then
            printf "${GREEN}█${NC}"
        else
            printf " "
        fi
    done
    printf "${CYAN}] %d%% - %s${NC}\n" "$percent" "$description"
}

# Función para input con validación
get_input() {
    local prompt="$1"
    local default="$2"
    local validation="$3"
    local input=""
    
    while true; do
        if [ -n "$default" ]; then
            read -p "$prompt [$default]: " input
            input="${input:-$default}"
        else
            read -p "$prompt: " input
        fi
        
        case "$validation" in
            "required")
                if [ -n "$input" ]; then
                    echo "$input"
                    return 0
                else
                    echo -e "${RED}Este campo es obligatorio${NC}"
                fi
                ;;
            "port")
                if [[ "$input" =~ ^[0-9]+$ ]] && [ "$input" -ge 1 ] && [ "$input" -le 65535 ]; then
                    echo "$input"
                    return 0
                else
                    echo -e "${RED}Debe ser un número entre 1 y 65535${NC}"
                fi
                ;;
            "yesno")
                if [[ "$input" =~ ^[SsNn]$ ]] || [[ "$input" =~ ^[YyNn]$ ]]; then
                    echo "$input"
                    return 0
                else
                    echo -e "${RED}Responda 's' para sí o 'n' para no${NC}"
                fi
                ;;
            "path")
                if [[ "$input" =~ ^/.* ]]; then
                    echo "$input"
                    return 0
                else
                    echo -e "${RED}Debe ser una ruta absoluta que comience con /${NC}"
                fi
                ;;
            *)
                echo "$input"
                return 0
                ;;
        esac
    done
}

# Función para selección múltiple
select_option() {
    local title="$1"
    shift
    local options=("$@")
    
    echo -e "${YELLOW}$title${NC}"
    for i in "${!options[@]}"; do
        echo "$((i+1)). ${options[i]}"
    done
    
    while true; do
        read -p "Seleccione una opción [1-${#options[@]}]: " choice
        if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le ${#options[@]} ]; then
            echo "${options[$((choice-1))]}"
            return 0
        else
            echo -e "${RED}Opción inválida. Seleccione un número entre 1 y ${#options[@]}${NC}"
        fi
    done
}

# Wizard de configuración inicial
configuration_wizard() {
    show_header
    echo -e "${WHITE}¡Bienvenido al asistente de configuración!${NC}"
    echo "Este wizard le guiará a través del proceso de configuración."
    echo ""
    read -p "Presione Enter para continuar..."
    
    # Paso 1: Configuración básica
    show_header
    echo -e "${WHITE}PASO 1 DE 5: Configuración Básica${NC}"
    show_progress 1 5 "Configuración básica"
    echo ""
    
    INSTALL_DIR=$(get_input "Directorio de instalación" "$INSTALL_BASE/webserverpro" "path")
    CONFIG_DIR=$(get_input "Directorio de configuración" "/etc/webserverpro" "path")
    DATA_DIR=$(get_input "Directorio de datos" "/var/lib/webserverpro" "path")
    LOG_DIR=$(get_input "Directorio de logs" "/var/log/webserverpro" "path")
    SERVICE_USER=$(get_input "Usuario del servicio" "webserverpro" "required")
    
    # Paso 2: Configuración de red
    show_header
    echo -e "${WHITE}PASO 2 DE 5: Configuración de Red${NC}"
    show_progress 2 5 "Configuración de red"
    echo ""
    
    HTTP_PORT=$(get_input "Puerto HTTP" "80" "port")
    HTTPS_PORT=$(get_input "Puerto HTTPS" "443" "port")
    ADMIN_PORT=$(get_input "Puerto de administración" "8080" "port")
    
    local ssl_choice
    ssl_choice=$(get_input "¿Habilitar SSL/TLS? (s/n)" "s" "yesno")
    if [[ "$ssl_choice" =~ ^[Ss]$ ]]; then
        SSL_ENABLED=true
    fi
    
    # Paso 3: Base de datos
    show_header
    echo -e "${WHITE}PASO 3 DE 5: Base de Datos${NC}"
    show_progress 3 5 "Configuración de base de datos"
    echo ""
    
    DB_TYPE=$(select_option "Seleccione el tipo de base de datos:" \
        "MySQL/MariaDB" \
        "PostgreSQL" \
        "SQLite" \
        "Sin base de datos")
    
    if [ "$DB_TYPE" != "Sin base de datos" ] && [ "$DB_TYPE" != "SQLite" ]; then
        DB_HOST=$(get_input "Host de la base de datos" "localhost" "required")
        
        case "$DB_TYPE" in
            "MySQL/MariaDB")
                DB_PORT=$(get_input "Puerto de la base de datos" "3306" "port")
                ;;
            "PostgreSQL")
                DB_PORT=$(get_input "Puerto de la base de datos" "5432" "port")
                ;;
        esac
        
        DB_NAME=$(get_input "Nombre de la base de datos" "webserverpro" "required")
        DB_USER=$(get_input "Usuario de la base de datos" "webserverpro" "required")
        DB_PASS=$(get_input "Contraseña de la base de datos" "" "required")
    fi
    
    # Paso 4: Configuración adicional
    show_header
    echo -e "${WHITE}PASO 4 DE 5: Configuración Adicional${NC}"
    show_progress 4 5 "Configuración adicional"
    echo ""
    
    local backup_choice
    backup_choice=$(get_input "¿Configurar backups automáticos? (s/n)" "s" "yesno")
    
    local monitoring_choice
    monitoring_choice=$(get_input "¿Habilitar monitoreo? (s/n)" "s" "yesno")
    
    local firewall_choice
    firewall_choice=$(get_input "¿Configurar firewall automáticamente? (s/n)" "s" "yesno")
    
    # Paso 5: Resumen
    show_header
    echo -e "${WHITE}PASO 5 DE 5: Confirmación${NC}"
    show_progress 5 5 "Revisión de configuración"
    echo ""
    
    echo -e "${YELLOW}RESUMEN DE CONFIGURACIÓN:${NC}"
    echo "════════════════════════════════════════"
    echo "Instalación:"
    echo "  Directorio: $INSTALL_DIR"
    echo "  Configuración: $CONFIG_DIR"
    echo "  Datos: $DATA_DIR"
    echo "  Logs: $LOG_DIR"
    echo "  Usuario: $SERVICE_USER"
    echo ""
    echo "Red:"
    echo "  HTTP: puerto $HTTP_PORT"
    echo "  HTTPS: puerto $HTTPS_PORT"
    echo "  Admin: puerto $ADMIN_PORT"
    echo "  SSL/TLS: $([ "$SSL_ENABLED" = true ] && echo "Habilitado" || echo "Deshabilitado")"
    echo ""
    echo "Base de datos:"
    echo "  Tipo: $DB_TYPE"
    if [ "$DB_TYPE" != "Sin base de datos" ] && [ "$DB_TYPE" != "SQLite" ]; then
        echo "  Host: $DB_HOST:$DB_PORT"
        echo "  Base de datos: $DB_NAME"
        echo "  Usuario: $DB_USER"
    fi
    echo ""
    echo "Servicios adicionales:"
    echo "  Backups: $([ "$backup_choice" = "s" ] && echo "Sí" || echo "No")"
    echo "  Monitoreo: $([ "$monitoring_choice" = "s" ] && echo "Sí" || echo "No")"
    echo "  Firewall: $([ "$firewall_choice" = "s" ] && echo "Sí" || echo "No")"
    echo "════════════════════════════════════════"
    echo ""
    
    local confirm
    confirm=$(get_input "¿Confirma la configuración y desea proceder con la instalación? (s/n)" "s" "yesno")
    
    if [[ ! "$confirm" =~ ^[Ss]$ ]]; then
        echo -e "${YELLOW}Instalación cancelada por el usuario.${NC}"
        exit 0
    fi
    
    # Guardar configuración
    save_configuration
}

# Guardar configuración en archivo
save_configuration() {
    local config_file="/tmp/webserverpro_install_config.conf"
    
    cat > "$config_file" << EOF
# Configuración de instalación de WebServer Pro
# Generado el $(date)

APP_NAME="$APP_NAME"
APP_VERSION="$APP_VERSION"
INSTALL_DIR="$INSTALL_DIR"
CONFIG_DIR="$CONFIG_DIR"
DATA_DIR="$DATA_DIR"
LOG_DIR="$LOG_DIR"
SERVICE_USER="$SERVICE_USER"
HTTP_PORT="$HTTP_PORT"
HTTPS_PORT="$HTTPS_PORT"
ADMIN_PORT="$ADMIN_PORT"
SSL_ENABLED="$SSL_ENABLED"
DB_TYPE="$DB_TYPE"
DB_HOST="$DB_HOST"
DB_PORT="$DB_PORT"
DB_NAME="$DB_NAME"
DB_USER="$DB_USER"
DB_PASS="$DB_PASS"
EOF
    
    echo "Configuración guardada en: $config_file"
}

# Función principal de instalación
perform_installation() {
    show_header
    echo -e "${WHITE}INICIANDO INSTALACIÓN...${NC}"
    echo ""
    
    local steps=(
        "Verificando sistema"
        "Creando directorios"
        "Creando usuario de servicio"
        "Descargando archivos"
        "Configurando aplicación"
        "Creando servicio systemd"
        "Configurando base de datos"
        "Configurando SSL"
        "Configurando firewall"
        "Verificando instalación"
    )
    
    for i in "${!steps[@]}"; do
        echo -e "${CYAN}► ${steps[i]}...${NC}"
        
        case $i in
            0) verify_system ;;
            1) create_directories_advanced ;;
            2) create_service_user_advanced ;;
            3) download_and_install_files ;;
            4) configure_application ;;
            5) create_systemd_service_advanced ;;
            6) setup_database ;;
            7) setup_ssl ;;
            8) setup_firewall ;;
            9) verify_installation_advanced ;;
        esac
        
        if [ $? -eq 0 ]; then
            echo -e "${GREEN}  ✓ ${steps[i]} completado${NC}"
        else
            echo -e "${RED}  ✗ Error en: ${steps[i]}${NC}"
            exit 1
        fi
        
        sleep 1
    done
    
    show_installation_complete
}

# Verificar sistema avanzado
verify_system() {
    # Verificar OS
    if [ ! -f /etc/os-release ]; then
        return 1
    fi
    
    # Verificar espacio (500MB mínimo)
    local available=$(df / | tail -1 | awk '{print $4}')
    if [ "$available" -lt 512000 ]; then
        return 1
    fi
    
    # Verificar memoria (1GB mínimo)
    local mem=$(free -m | awk '/^Mem:/ {print $2}')
    if [ "$mem" -lt 1000 ]; then
        return 1
    fi
    
    return 0
}

# Crear directorios avanzado
create_directories_advanced() {
    local dirs=(
        "$INSTALL_DIR"
        "$INSTALL_DIR/bin"
        "$INSTALL_DIR/lib"
        "$INSTALL_DIR/share"
        "$INSTALL_DIR/templates"
        "$CONFIG_DIR"
        "$CONFIG_DIR/sites-available"
        "$CONFIG_DIR/sites-enabled"
        "$DATA_DIR"
        "$DATA_DIR/www"
        "$DATA_DIR/ssl"
        "$LOG_DIR"
        "$LOG_DIR/access"
        "$LOG_DIR/error"
    )
    
    for dir in "${dirs[@]}"; do
        mkdir -p "$dir" || return 1
    done
    
    return 0
}

# Crear usuario avanzado
create_service_user_advanced() {
    if ! id "$SERVICE_USER" &>/dev/null; then
        useradd -r -s /bin/false -d "$INSTALL_DIR" -c "WebServer Pro Service User" "$SERVICE_USER" || return 1
    fi
    
    # Establecer permisos
    chown -R "$SERVICE_USER:$SERVICE_USER" "$INSTALL_DIR" "$DATA_DIR" "$LOG_DIR"
    chmod 755 "$CONFIG_DIR"
    chmod 700 "$DATA_DIR/ssl"
    
    return 0
}

# Configurar base de datos
setup_database() {
    if [ "$DB_TYPE" = "Sin base de datos" ]; then
        return 0
    fi
    
    case "$DB_TYPE" in
        "MySQL/MariaDB")
            # Verificar si MySQL/MariaDB está instalado
            if ! command -v mysql >/dev/null 2>&1; then
                return 1
            fi
            ;;
        "PostgreSQL")
            # Verificar si PostgreSQL está instalado
            if ! command -v psql >/dev/null 2>&1; then
                return 1
            fi
            ;;
        "SQLite")
            # Crear base de datos SQLite
            touch "$DATA_DIR/webserverpro.db"
            chown "$SERVICE_USER:$SERVICE_USER" "$DATA_DIR/webserverpro.db"
            ;;
    esac
    
    return 0
}

# Configurar SSL
setup_ssl() {
    if [ "$SSL_ENABLED" = false ]; then
        return 0
    fi
    
    # Generar certificado auto-firmado para desarrollo
    openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
        -keyout "$DATA_DIR/ssl/server.key" \
        -out "$DATA_DIR/ssl/server.crt" \
        -subj "/C=ES/ST=State/L=City/O=Organization/CN=localhost" &>/dev/null
    
    chown "$SERVICE_USER:$SERVICE_USER" "$DATA_DIR/ssl/"*
    chmod 600 "$DATA_DIR/ssl/"*
    
    return 0
}

# Configurar firewall
setup_firewall() {
    if command -v ufw >/dev/null 2>&1; then
        ufw allow "$HTTP_PORT"/tcp &>/dev/null
        ufw allow "$HTTPS_PORT"/tcp &>/dev/null
        ufw allow "$ADMIN_PORT"/tcp &>/dev/null
    fi
    
    return 0
}

# Resto de funciones placeholder
download_and_install_files() { return 0; }
configure_application() { return 0; }
create_systemd_service_advanced() { return 0; }
verify_installation_advanced() { return 0; }

# Mostrar instalación completada
show_installation_complete() {
    show_header
    echo -e "${GREEN}"
    cat << "EOF"
╔══════════════════════════════════════════════════════════════════════╗
║                         ¡INSTALACIÓN COMPLETADA!                    ║
╚══════════════════════════════════════════════════════════════════════╝
EOF
    echo -e "${NC}"
    
    echo "🎉 WebServer Pro v2.0.0 se ha instalado exitosamente"
    echo ""
    echo "🌐 Acceso Web:"
    echo "   HTTP:  http://localhost:$HTTP_PORT"
    echo "   HTTPS: https://localhost:$HTTPS_PORT"
    echo "   Admin: http://localhost:$ADMIN_PORT"
    echo ""
    echo "📁 Directorios importantes:"
    echo "   Instalación:   $INSTALL_DIR"
    echo "   Configuración: $CONFIG_DIR"
    echo "   Datos:         $DATA_DIR"
    echo "   Logs:          $LOG_DIR"
    echo ""
    echo "🔧 Comandos de gestión:"
    echo "   Iniciar:  sudo systemctl start webserverpro"
    echo "   Parar:    sudo systemctl stop webserverpro"
    echo "   Estado:   sudo systemctl status webserverpro"
    echo "   Logs:     sudo journalctl -u webserverpro -f"
    echo ""
    echo "¡Gracias por elegir WebServer Pro!"
}

# Función principal
main() {
    case "${1:-install}" in
        install)
            configuration_wizard
            perform_installation
            ;;
        config-only)
            configuration_wizard
            ;;
        silent)
            if [ -f "/tmp/webserverpro_install_config.conf" ]; then
                source "/tmp/webserverpro_install_config.conf"
                perform_installation
            else
                echo "Error: No se encontró archivo de configuración"
                exit 1
            fi
            ;;
        *)
            echo "Uso: $0 [install|config-only|silent]"
            echo ""
            echo "Modos:"
            echo "  install     - Instalación interactiva completa (por defecto)"
            echo "  config-only - Solo ejecutar el wizard de configuración"
            echo "  silent      - Instalación silenciosa usando configuración existente"
            exit 1
            ;;
    esac
}

# Manejador de señales
cleanup_handler() {
    echo -e "\n${YELLOW}Instalación interrumpida por el usuario${NC}"
    exit 130
}

trap cleanup_handler SIGINT SIGTERM

# Verificar privilegios de root
if [ "$EUID" -ne 0 ]; then
    echo -e "${RED}Error: Este instalador debe ejecutarse con privilegios de root${NC}"
    echo "Ejecute: sudo $0"
    exit 1
fi

# Ejecutar función principal
main "$@"

Mejores Prácticas para Instaladores

  • UX intuitiva: Crea interfaces claras con progreso visual y mensajes informativos
  • Validación robusta: Verifica todos los requisitos antes de comenzar la instalación
  • Rollback automático: Implementa mecanismos para deshacer cambios en caso de error
  • Configuración flexible: Permite personalización manteniendo valores por defecto sensatos
  • Logging detallado: Registra todos los pasos para facilitar el debugging
  • Desinstalador incluido: Siempre proporciona una forma limpia de remover la aplicación

Consideraciones de Seguridad

  • Verificación de integridad: Valida checksums y firmas de archivos descargados
  • Principio de menor privilegio: No solicites más permisos de los necesarios
  • Sanitización de entrada: Valida y limpia todas las entradas del usuario
  • Conexiones seguras: Usa HTTPS para todas las descargas
  • Permisos apropiados: Establece permisos restrictivos en archivos y directorios

Ejercicios Prácticos

Ejercicio 1: Instalador de Stack LAMP

Crea un instalador completo para un stack LAMP:

  • Detección automática de la distribución Linux
  • Instalación de Apache, MySQL/MariaDB y PHP
  • Configuración de virtual hosts
  • Setup de SSL con Let's Encrypt
  • Configuración de firewall y seguridad básica

Ejercicio 2: Instalador de Aplicación Docker

Desarrolla un instalador para aplicaciones containerizadas:

  • Instalación automática de Docker y Docker Compose
  • Wizard para configuración de contenedores
  • Gestión de volúmenes y redes
  • Configuración de proxy reverso
  • Sistema de backup y restore de datos