Volver
Módulo 8 - Administración del Sistema AVANZADO

Gestión de Paquetes

Automatiza la instalación y gestión de software en diferentes distribuciones Linux

La gestión automatizada de paquetes es esencial para administradores de sistemas que manejan múltiples servidores y distribuciones. Bash permite crear scripts universales que detecten automáticamente el gestor de paquetes y ejecuten las operaciones correspondientes.

Gestores de Paquetes por Distribución

Distribución Gestor Principal Instalar Actualizar Buscar
Ubuntu/Debian apt apt install apt update && apt upgrade apt search
RHEL/CentOS 8+ dnf dnf install dnf update dnf search
CentOS 7/RHEL 7 yum yum install yum update yum search
Arch Linux pacman pacman -S pacman -Syu pacman -Ss
openSUSE zypper zypper install zypper update zypper search
Alpine Linux apk apk add apk upgrade apk search

Detector Universal de Distribución

Detección de distribución - detect_distro.sh
bash
#!/bin/bash

# Detector universal de distribución Linux

detect_distro() {
    local distro=""
    local version=""
    local package_manager=""
    
    # Método 1: /etc/os-release (estándar moderno)
    if [ -f /etc/os-release ]; then
        source /etc/os-release
        distro="$ID"
        version="$VERSION_ID"
    # Método 2: lsb_release (si está disponible)
    elif command -v lsb_release >/dev/null 2>&1; then
        distro=$(lsb_release -si | tr '[:upper:]' '[:lower:]')
        version=$(lsb_release -sr)
    # Método 3: Archivos específicos de distribución
    elif [ -f /etc/debian_version ]; then
        distro="debian"
        version=$(cat /etc/debian_version)
    elif [ -f /etc/redhat-release ]; then
        if grep -q "CentOS" /etc/redhat-release; then
            distro="centos"
        elif grep -q "Red Hat" /etc/redhat-release; then
            distro="rhel"
        elif grep -q "Fedora" /etc/redhat-release; then
            distro="fedora"
        fi
        version=$(grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release | head -1)
    elif [ -f /etc/arch-release ]; then
        distro="arch"
        version="rolling"
    elif [ -f /etc/alpine-release ]; then
        distro="alpine"
        version=$(cat /etc/alpine-release)
    else
        distro="unknown"
        version="unknown"
    fi
    
    # Determinar gestor de paquetes basado en la distribución
    case "$distro" in
        ubuntu|debian)
            package_manager="apt"
            ;;
        centos|rhel)
            if [ -n "$version" ] && [[ "$version" =~ ^[8-9] ]]; then
                package_manager="dnf"
            else
                package_manager="yum"
            fi
            ;;
        fedora)
            package_manager="dnf"
            ;;
        arch|manjaro)
            package_manager="pacman"
            ;;
        opensuse*|suse)
            package_manager="zypper"
            ;;
        alpine)
            package_manager="apk"
            ;;
        *)
            package_manager="unknown"
            ;;
    esac
    
    # Verificar que el gestor de paquetes exista
    if [ "$package_manager" != "unknown" ] && ! command -v "$package_manager" >/dev/null 2>&1; then
        package_manager="unknown"
    fi
    
    # Exportar variables globales
    export DISTRO="$distro"
    export DISTRO_VERSION="$version"
    export PACKAGE_MANAGER="$package_manager"
    
    return 0
}

# Función para mostrar información del sistema
show_system_info() {
    detect_distro
    
    echo "=== INFORMACIÓN DEL SISTEMA ==="
    echo "Distribución: $DISTRO"
    echo "Versión: $DISTRO_VERSION"
    echo "Gestor de paquetes: $PACKAGE_MANAGER"
    echo "Kernel: $(uname -r)"
    echo "Arquitectura: $(uname -m)"
    echo "Hostname: $(hostname)"
    echo "Uptime: $(uptime -p 2>/dev/null || uptime)"
}

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

Gestor Universal de Paquetes

Gestor universal - universal_package_manager.sh
bash
#!/bin/bash

# Gestor universal de paquetes para múltiples distribuciones Linux
# Versión: 2.0

set -euo pipefail

# Importar detector de distribución
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Función para detectar distribución (integrada)
detect_package_manager() {
    if command -v apt >/dev/null 2>&1; then
        echo "apt"
    elif command -v dnf >/dev/null 2>&1; then
        echo "dnf"
    elif command -v yum >/dev/null 2>&1; then
        echo "yum"
    elif command -v pacman >/dev/null 2>&1; then
        echo "pacman"
    elif command -v zypper >/dev/null 2>&1; then
        echo "zypper"
    elif command -v apk >/dev/null 2>&1; then
        echo "apk"
    else
        echo "unknown"
    fi
}

# Variables globales
declare -g PM=""
declare -g VERBOSE=false
declare -g DRY_RUN=false
declare -g LOG_FILE="/var/log/package_manager.log"

# Función de logging
log() {
    local level="$1"
    shift
    local message="$*"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE" >&2
}

log_info() { log "INFO" "$@"; }
log_warn() { log "WARN" "$@"; }
log_error() { log "ERROR" "$@"; }

# Función para ejecutar comandos con logging
execute_command() {
    local cmd="$*"
    
    log_info "Ejecutando: $cmd"
    
    if [ "$DRY_RUN" = true ]; then
        log_info "DRY RUN: Se ejecutaría: $cmd"
        return 0
    fi
    
    if [ "$VERBOSE" = true ]; then
        eval "$cmd"
    else
        eval "$cmd" >/dev/null 2>&1
    fi
    
    local exit_code=$?
    if [ $exit_code -eq 0 ]; then
        log_info "✓ Comando ejecutado exitosamente"
    else
        log_error "✗ Comando falló con código: $exit_code"
    fi
    
    return $exit_code
}

# Actualizar lista de paquetes/repositorios
update_package_list() {
    log_info "Actualizando lista de paquetes..."
    
    case "$PM" in
        apt)
            execute_command "apt update"
            ;;
        dnf)
            execute_command "dnf check-update" || true  # dnf check-update devuelve 100 si hay actualizaciones
            ;;
        yum)
            execute_command "yum check-update" || true
            ;;
        pacman)
            execute_command "pacman -Sy"
            ;;
        zypper)
            execute_command "zypper refresh"
            ;;
        apk)
            execute_command "apk update"
            ;;
        *)
            log_error "Gestor de paquetes no soportado: $PM"
            return 1
            ;;
    esac
}

# Instalar paquete(s)
install_package() {
    local packages=("$@")
    
    if [ ${#packages[@]} -eq 0 ]; then
        log_error "No se especificaron paquetes para instalar"
        return 1
    fi
    
    log_info "Instalando paquetes: ${packages[*]}"
    
    case "$PM" in
        apt)
            execute_command "apt install -y ${packages[*]}"
            ;;
        dnf)
            execute_command "dnf install -y ${packages[*]}"
            ;;
        yum)
            execute_command "yum install -y ${packages[*]}"
            ;;
        pacman)
            execute_command "pacman -S --noconfirm ${packages[*]}"
            ;;
        zypper)
            execute_command "zypper install -y ${packages[*]}"
            ;;
        apk)
            execute_command "apk add ${packages[*]}"
            ;;
        *)
            log_error "Gestor de paquetes no soportado: $PM"
            return 1
            ;;
    esac
}

# Desinstalar paquete(s)
remove_package() {
    local packages=("$@")
    
    if [ ${#packages[@]} -eq 0 ]; then
        log_error "No se especificaron paquetes para desinstalar"
        return 1
    fi
    
    log_info "Desinstalando paquetes: ${packages[*]}"
    
    case "$PM" in
        apt)
            execute_command "apt remove -y ${packages[*]}"
            ;;
        dnf)
            execute_command "dnf remove -y ${packages[*]}"
            ;;
        yum)
            execute_command "yum remove -y ${packages[*]}"
            ;;
        pacman)
            execute_command "pacman -R --noconfirm ${packages[*]}"
            ;;
        zypper)
            execute_command "zypper remove -y ${packages[*]}"
            ;;
        apk)
            execute_command "apk del ${packages[*]}"
            ;;
        *)
            log_error "Gestor de paquetes no soportado: $PM"
            return 1
            ;;
    esac
}

# Actualizar todos los paquetes
upgrade_system() {
    log_info "Actualizando todo el sistema..."
    
    case "$PM" in
        apt)
            execute_command "apt upgrade -y"
            ;;
        dnf)
            execute_command "dnf upgrade -y"
            ;;
        yum)
            execute_command "yum update -y"
            ;;
        pacman)
            execute_command "pacman -Syu --noconfirm"
            ;;
        zypper)
            execute_command "zypper update -y"
            ;;
        apk)
            execute_command "apk upgrade"
            ;;
        *)
            log_error "Gestor de paquetes no soportado: $PM"
            return 1
            ;;
    esac
}

# Buscar paquetes
search_package() {
    local query="$1"
    
    if [ -z "$query" ]; then
        log_error "Debe especificar un término de búsqueda"
        return 1
    fi
    
    log_info "Buscando paquetes: $query"
    
    case "$PM" in
        apt)
            apt search "$query"
            ;;
        dnf)
            dnf search "$query"
            ;;
        yum)
            yum search "$query"
            ;;
        pacman)
            pacman -Ss "$query"
            ;;
        zypper)
            zypper search "$query"
            ;;
        apk)
            apk search "$query"
            ;;
        *)
            log_error "Gestor de paquetes no soportado: $PM"
            return 1
            ;;
    esac
}

# Listar paquetes instalados
list_installed() {
    local pattern="${1:-}"
    
    log_info "Listando paquetes instalados..."
    
    case "$PM" in
        apt)
            if [ -n "$pattern" ]; then
                dpkg -l | grep "$pattern"
            else
                dpkg -l
            fi
            ;;
        dnf)
            if [ -n "$pattern" ]; then
                dnf list installed | grep "$pattern"
            else
                dnf list installed
            fi
            ;;
        yum)
            if [ -n "$pattern" ]; then
                yum list installed | grep "$pattern"
            else
                yum list installed
            fi
            ;;
        pacman)
            if [ -n "$pattern" ]; then
                pacman -Q | grep "$pattern"
            else
                pacman -Q
            fi
            ;;
        zypper)
            if [ -n "$pattern" ]; then
                zypper search -i | grep "$pattern"
            else
                zypper search -i
            fi
            ;;
        apk)
            if [ -n "$pattern" ]; then
                apk info | grep "$pattern"
            else
                apk info
            fi
            ;;
        *)
            log_error "Gestor de paquetes no soportado: $PM"
            return 1
            ;;
    esac
}

# Limpiar caché de paquetes
clean_cache() {
    log_info "Limpiando caché de paquetes..."
    
    case "$PM" in
        apt)
            execute_command "apt autoclean"
            execute_command "apt autoremove -y"
            ;;
        dnf)
            execute_command "dnf clean all"
            execute_command "dnf autoremove -y"
            ;;
        yum)
            execute_command "yum clean all"
            execute_command "yum autoremove -y"
            ;;
        pacman)
            execute_command "pacman -Sc --noconfirm"
            ;;
        zypper)
            execute_command "zypper clean -a"
            ;;
        apk)
            execute_command "apk cache clean"
            ;;
        *)
            log_error "Gestor de paquetes no soportado: $PM"
            return 1
            ;;
    esac
}

# Información de paquete
package_info() {
    local package="$1"
    
    if [ -z "$package" ]; then
        log_error "Debe especificar un paquete"
        return 1
    fi
    
    log_info "Mostrando información de: $package"
    
    case "$PM" in
        apt)
            apt show "$package"
            ;;
        dnf)
            dnf info "$package"
            ;;
        yum)
            yum info "$package"
            ;;
        pacman)
            pacman -Si "$package" || pacman -Qi "$package"
            ;;
        zypper)
            zypper info "$package"
            ;;
        apk)
            apk info "$package"
            ;;
        *)
            log_error "Gestor de paquetes no soportado: $PM"
            return 1
            ;;
    esac
}

# Función de ayuda
show_help() {
    cat << EOF
Gestor Universal de Paquetes v2.0

USO:
    $0 [OPCIONES] COMANDO [ARGUMENTOS]

OPCIONES:
    -v, --verbose    Salida detallada
    -n, --dry-run    Simular operaciones sin ejecutar
    -h, --help       Mostrar esta ayuda

COMANDOS:
    update           Actualizar lista de paquetes
    install PKG...   Instalar paquete(s)
    remove PKG...    Desinstalar paquete(s)
    upgrade          Actualizar todo el sistema
    search QUERY     Buscar paquetes
    list [PATTERN]   Listar paquetes instalados
    info PACKAGE     Información detallada de paquete
    clean            Limpiar caché
    status           Mostrar información del sistema

EJEMPLOS:
    # Actualizar sistema
    $0 update && $0 upgrade
    
    # Instalar múltiples paquetes
    $0 install vim git curl htop
    
    # Buscar paquetes relacionados con python
    $0 search python
    
    # Ver información de un paquete
    $0 info nginx
    
    # Modo dry-run para ver qué se haría
    $0 --dry-run install docker.io

GESTOR DETECTADO: $PM
EOF
}

# Función principal
main() {
    # Detectar gestor de paquetes
    PM=$(detect_package_manager)
    
    if [ "$PM" = "unknown" ]; then
        log_error "No se pudo detectar un gestor de paquetes compatible"
        exit 1
    fi
    
    # Verificar privilegios para operaciones que los requieren
    check_root_for_command() {
        local cmd="$1"
        case "$cmd" in
            install|remove|upgrade|update|clean)
                if [ "$EUID" -ne 0 ] && [ "$DRY_RUN" = false ]; then
                    log_error "El comando '$cmd' requiere privilegios de root"
                    exit 1
                fi
                ;;
        esac
    }
    
    # Procesar argumentos
    while [ $# -gt 0 ]; do
        case $1 in
            -v|--verbose)
                VERBOSE=true
                shift
                ;;
            -n|--dry-run)
                DRY_RUN=true
                shift
                ;;
            -h|--help)
                show_help
                exit 0
                ;;
            update)
                check_root_for_command "update"
                update_package_list
                exit $?
                ;;
            install)
                check_root_for_command "install"
                shift
                if [ $# -eq 0 ]; then
                    log_error "Debe especificar al menos un paquete para instalar"
                    exit 1
                fi
                install_package "$@"
                exit $?
                ;;
            remove)
                check_root_for_command "remove"
                shift
                if [ $# -eq 0 ]; then
                    log_error "Debe especificar al menos un paquete para desinstalar"
                    exit 1
                fi
                remove_package "$@"
                exit $?
                ;;
            upgrade)
                check_root_for_command "upgrade"
                upgrade_system
                exit $?
                ;;
            search)
                shift
                if [ $# -eq 0 ]; then
                    log_error "Debe especificar un término de búsqueda"
                    exit 1
                fi
                search_package "$1"
                exit $?
                ;;
            list)
                shift
                list_installed "$1"
                exit $?
                ;;
            info)
                shift
                if [ $# -eq 0 ]; then
                    log_error "Debe especificar un paquete"
                    exit 1
                fi
                package_info "$1"
                exit $?
                ;;
            clean)
                check_root_for_command "clean"
                clean_cache
                exit $?
                ;;
            status)
                echo "Sistema: $(uname -s) $(uname -r)"
                echo "Gestor de paquetes: $PM"
                if command -v "$PM" >/dev/null 2>&1; then
                    echo "Versión del gestor: $($PM --version 2>/dev/null | head -1)"
                fi
                exit 0
                ;;
            *)
                log_error "Comando desconocido: $1"
                show_help
                exit 1
                ;;
        esac
    done
    
    # Si no se proporciona comando, mostrar ayuda
    show_help
}

# Ejecutar función principal
main "$@"

Instalador de Software Personalizado

Instalador de aplicaciones - app_installer.sh
bash
#!/bin/bash

# Instalador automatizado de aplicaciones comunes
# Soporte para múltiples distribuciones

set -euo pipefail

# Configuración
declare -g LOG_FILE="/var/log/app_installer.log"
declare -g CONFIG_DIR="$HOME/.app_installer"
declare -g PROFILES_DIR="$CONFIG_DIR/profiles"

# Crear directorios necesarios
mkdir -p "$PROFILES_DIR"

# Función de logging
log() {
    local message="[$(date '+%Y-%m-%d %H:%M:%S')] $*"
    echo "$message" | tee -a "$LOG_FILE"
}

# Detectar gestor de paquetes
detect_pm() {
    if command -v apt >/dev/null 2>&1; then
        echo "apt"
    elif command -v dnf >/dev/null 2>&1; then
        echo "dnf"
    elif command -v yum >/dev/null 2>&1; then
        echo "yum"
    elif command -v pacman >/dev/null 2>&1; then
        echo "pacman"
    else
        echo "unknown"
    fi
}

PM=$(detect_pm)

# Definición de aplicaciones por categoría
declare -A DEVELOPMENT_APPS=(
    ["git"]="Control de versiones Git"
    ["vim"]="Editor de texto Vim"
    ["code"]="Visual Studio Code"
    ["nodejs"]="Node.js runtime"
    ["python3"]="Python 3"
    ["docker"]="Containerización Docker"
    ["postgresql"]="Base de datos PostgreSQL"
    ["mysql"]="Base de datos MySQL"
)

declare -A SYSTEM_APPS=(
    ["htop"]="Monitor de procesos"
    ["curl"]="Cliente HTTP"
    ["wget"]="Descargador de archivos"
    ["unzip"]="Descompresor ZIP"
    ["tree"]="Visualizador de directorios"
    ["tmux"]="Terminal multiplexer"
    ["fail2ban"]="Protección contra ataques"
    ["ufw"]="Firewall simplificado"
)

declare -A MULTIMEDIA_APPS=(
    ["vlc"]="Reproductor multimedia VLC"
    ["gimp"]="Editor de imágenes GIMP"
    ["audacity"]="Editor de audio"
    ["ffmpeg"]="Procesador multimedia"
)

declare -A WEB_APPS=(
    ["nginx"]="Servidor web Nginx"
    ["apache2"]="Servidor web Apache"
    ["certbot"]="Cliente Let's Encrypt"
    ["wordpress"]="CMS WordPress"
)

# Función para normalizar nombres de paquetes
normalize_package_name() {
    local app="$1"
    local pm="$2"
    
    case "$pm" in
        apt)
            case "$app" in
                "code") echo "code" ;;
                "nodejs") echo "nodejs npm" ;;
                "docker") echo "docker.io docker-compose" ;;
                "mysql") echo "mysql-server mysql-client" ;;
                "apache2") echo "apache2" ;;
                *) echo "$app" ;;
            esac
            ;;
        dnf|yum)
            case "$app" in
                "code") echo "code" ;;
                "nodejs") echo "nodejs npm" ;;
                "docker") echo "docker docker-compose" ;;
                "mysql") echo "mysql-server mysql" ;;
                "apache2") echo "httpd" ;;
                *) echo "$app" ;;
            esac
            ;;
        pacman)
            case "$app" in
                "code") echo "code" ;;
                "nodejs") echo "nodejs npm" ;;
                "docker") echo "docker docker-compose" ;;
                "mysql") echo "mysql" ;;
                "apache2") echo "apache" ;;
                *) echo "$app" ;;
            esac
            ;;
        *)
            echo "$app"
            ;;
    esac
}

# Instalar aplicación individual
install_app() {
    local app="$1"
    local pm="$2"
    
    log "Instalando aplicación: $app"
    
    local package_names
    package_names=$(normalize_package_name "$app" "$pm")
    
    case "$pm" in
        apt)
            # Casos especiales para apt
            case "$app" in
                "code")
                    # Instalar VS Code desde repositorio oficial
                    if ! dpkg -l | grep -q "code"; then
                        curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > packages.microsoft.gpg
                        install -o root -g root -m 644 packages.microsoft.gpg /etc/apt/trusted.gpg.d/
                        echo "deb [arch=amd64,arm64,armhf signed-by=/etc/apt/trusted.gpg.d/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list
                        apt update
                        apt install -y code
                    fi
                    ;;
                "docker")
                    # Instalar Docker desde repositorio oficial
                    if ! command -v docker >/dev/null 2>&1; then
                        curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
                        echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
                        apt update
                        apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
                        systemctl enable docker
                        systemctl start docker
                    fi
                    ;;
                *)
                    apt install -y $package_names
                    ;;
            esac
            ;;
        dnf)
            dnf install -y $package_names
            ;;
        yum)
            yum install -y $package_names
            ;;
        pacman)
            pacman -S --noconfirm $package_names
            ;;
        *)
            log "Gestor de paquetes no soportado: $pm"
            return 1
            ;;
    esac
    
    log "✓ Aplicación $app instalada exitosamente"
}

# Mostrar menú de categorías
show_categories() {
    echo "=== CATEGORÍAS DE APLICACIONES ==="
    echo "1. Desarrollo (${#DEVELOPMENT_APPS[@]} apps)"
    echo "2. Sistema (${#SYSTEM_APPS[@]} apps)"
    echo "3. Multimedia (${#MULTIMEDIA_APPS[@]} apps)"
    echo "4. Web (${#WEB_APPS[@]} apps)"
    echo "5. Perfil personalizado"
    echo "6. Instalar desde lista"
    echo ""
}

# Mostrar aplicaciones de una categoría
show_apps() {
    local category="$1"
    local -n apps_ref=$2
    
    echo "=== APLICACIONES DE $category ==="
    local i=1
    for app in "${!apps_ref[@]}"; do
        echo "$i. $app - ${apps_ref[$app]}"
        ((i++))
    done
    echo ""
}

# Instalar categoría completa
install_category() {
    local category="$1"
    local -n apps_ref=$2
    
    log "Instalando categoría: $category"
    
    echo "Las siguientes aplicaciones serán instaladas:"
    for app in "${!apps_ref[@]}"; do
        echo "- $app: ${apps_ref[$app]}"
    done
    
    read -p "¿Continuar? (s/N): " confirm
    if [[ ! "$confirm" =~ ^[Ss]$ ]]; then
        log "Instalación cancelada por el usuario"
        return 0
    fi
    
    # Actualizar repositorios
    case "$PM" in
        apt) apt update ;;
        dnf) dnf check-update || true ;;
        yum) yum check-update || true ;;
        pacman) pacman -Sy ;;
    esac
    
    # Instalar cada aplicación
    for app in "${!apps_ref[@]}"; do
        if install_app "$app" "$PM"; then
            log "✓ $app instalado"
        else
            log "✗ Error al instalar $app"
        fi
    done
    
    log "Instalación de categoría $category completada"
}

# Crear perfil personalizado
create_profile() {
    local profile_name="$1"
    local profile_file="$PROFILES_DIR/$profile_name.profile"
    
    echo "Creando perfil personalizado: $profile_name"
    echo "Ingrese las aplicaciones una por línea (línea vacía para terminar):"
    
    > "$profile_file"  # Limpiar archivo
    
    while true; do
        read -p "App: " app
        if [ -z "$app" ]; then
            break
        fi
        echo "$app" >> "$profile_file"
    done
    
    echo "Perfil $profile_name creado en: $profile_file"
}

# Instalar desde perfil
install_from_profile() {
    local profile_name="$1"
    local profile_file="$PROFILES_DIR/$profile_name.profile"
    
    if [ ! -f "$profile_file" ]; then
        log "Perfil no encontrado: $profile_file"
        return 1
    fi
    
    log "Instalando desde perfil: $profile_name"
    
    echo "Aplicaciones en el perfil:"
    cat "$profile_file"
    
    read -p "¿Continuar con la instalación? (s/N): " confirm
    if [[ ! "$confirm" =~ ^[Ss]$ ]]; then
        return 0
    fi
    
    # Actualizar repositorios
    case "$PM" in
        apt) apt update ;;
        dnf) dnf check-update || true ;;
        yum) yum check-update || true ;;
        pacman) pacman -Sy ;;
    esac
    
    # Instalar cada aplicación del perfil
    while IFS= read -r app; do
        [ -n "$app" ] && install_app "$app" "$PM"
    done < "$profile_file"
    
    log "Instalación desde perfil $profile_name completada"
}

# Listar perfiles disponibles
list_profiles() {
    echo "=== PERFILES DISPONIBLES ==="
    if [ -d "$PROFILES_DIR" ]; then
        for profile in "$PROFILES_DIR"/*.profile; do
            if [ -f "$profile" ]; then
                local name=$(basename "$profile" .profile)
                local count=$(wc -l < "$profile")
                echo "- $name ($count aplicaciones)"
            fi
        done
    else
        echo "No hay perfiles creados"
    fi
    echo ""
}

# Función principal interactiva
interactive_mode() {
    while true; do
        clear
        echo "=== INSTALADOR DE APLICACIONES ==="
        echo "Gestor de paquetes detectado: $PM"
        echo ""
        
        show_categories
        
        read -p "Seleccione una opción (1-6, q para salir): " choice
        
        case "$choice" in
            1)
                show_apps "DESARROLLO" DEVELOPMENT_APPS
                read -p "¿Instalar toda la categoría? (s/N): " install_all
                if [[ "$install_all" =~ ^[Ss]$ ]]; then
                    install_category "DESARROLLO" DEVELOPMENT_APPS
                fi
                read -p "Presione Enter para continuar..."
                ;;
            2)
                show_apps "SISTEMA" SYSTEM_APPS
                read -p "¿Instalar toda la categoría? (s/N): " install_all
                if [[ "$install_all" =~ ^[Ss]$ ]]; then
                    install_category "SISTEMA" SYSTEM_APPS
                fi
                read -p "Presione Enter para continuar..."
                ;;
            3)
                show_apps "MULTIMEDIA" MULTIMEDIA_APPS
                read -p "¿Instalar toda la categoría? (s/N): " install_all
                if [[ "$install_all" =~ ^[Ss]$ ]]; then
                    install_category "MULTIMEDIA" MULTIMEDIA_APPS
                fi
                read -p "Presione Enter para continuar..."
                ;;
            4)
                show_apps "WEB" WEB_APPS
                read -p "¿Instalar toda la categoría? (s/N): " install_all
                if [[ "$install_all" =~ ^[Ss]$ ]]; then
                    install_category "WEB" WEB_APPS
                fi
                read -p "Presione Enter para continuar..."
                ;;
            5)
                list_profiles
                read -p "¿Crear nuevo perfil (c) o usar existente (nombre)?: " profile_action
                if [ "$profile_action" = "c" ]; then
                    read -p "Nombre del nuevo perfil: " profile_name
                    create_profile "$profile_name"
                else
                    install_from_profile "$profile_action"
                fi
                read -p "Presione Enter para continuar..."
                ;;
            6)
                echo "Ingrese aplicaciones separadas por espacios:"
                read -p "Apps: " -a apps_list
                for app in "${apps_list[@]}"; do
                    install_app "$app" "$PM"
                done
                read -p "Presione Enter para continuar..."
                ;;
            q|Q)
                echo "Saliendo..."
                exit 0
                ;;
            *)
                echo "Opción inválida"
                read -p "Presione Enter para continuar..."
                ;;
        esac
    done
}

# Verificar privilegios
check_root() {
    if [ "$EUID" -ne 0 ]; then
        echo "Error: Este script debe ejecutarse como root"
        exit 1
    fi
}

# Función principal
main() {
    check_root
    
    if [ "$PM" = "unknown" ]; then
        echo "Error: Gestor de paquetes no soportado"
        exit 1
    fi
    
    if [ $# -eq 0 ]; then
        interactive_mode
    else
        # Modo línea de comandos
        case "$1" in
            install)
                shift
                for app in "$@"; do
                    install_app "$app" "$PM"
                done
                ;;
            profile)
                if [ "$2" = "create" ]; then
                    create_profile "$3"
                elif [ "$2" = "list" ]; then
                    list_profiles
                elif [ "$2" = "install" ]; then
                    install_from_profile "$3"
                fi
                ;;
            category)
                case "$2" in
                    dev|development)
                        install_category "DESARROLLO" DEVELOPMENT_APPS
                        ;;
                    sys|system)
                        install_category "SISTEMA" SYSTEM_APPS
                        ;;
                    media|multimedia)
                        install_category "MULTIMEDIA" MULTIMEDIA_APPS
                        ;;
                    web)
                        install_category "WEB" WEB_APPS
                        ;;
                    *)
                        echo "Categoría no válida: $2"
                        exit 1
                        ;;
                esac
                ;;
            *)
                echo "Comando no válido: $1"
                exit 1
                ;;
        esac
    fi
}

# Ejecutar función principal
main "$@"

Mejores Prácticas

  • Detección automática: Siempre detecta automáticamente la distribución y gestor
  • Respaldos antes de cambios: Crea snapshots del sistema antes de actualizaciones grandes
  • Logging completo: Registra todas las operaciones para auditoría
  • Manejo de errores: Implementa recuperación automática cuando sea posible
  • Actualizaciones programadas: Automatiza actualizaciones de seguridad
  • Verificación de integridad: Verifica checksums y firmas de paquetes

Consideraciones de Seguridad

  • Repositorios oficiales: Prioriza siempre repositorios oficiales de la distribución
  • Actualizaciones de seguridad: Aplica parches de seguridad inmediatamente
  • Verificación de firmas: Siempre verifica firmas GPG de repositorios externos
  • Dependencias: Revisa cuidadosamente las dependencias antes de instalar
  • Rollback plan: Ten siempre un plan para revertir cambios problemáticos

Ejercicios Prácticos

Ejercicio 1: Automatización de Servidor Web

Crea un script que configure automáticamente un servidor web:

  • Detecte la distribución y instale Nginx o Apache
  • Configure SSL con Let's Encrypt
  • Instale PHP, MySQL y herramientas de desarrollo
  • Configure firewall básico
  • Cree un script de monitoreo de servicios

Ejercicio 2: Centro de Control de Paquetes

Desarrolla un sistema centralizado para gestión de paquetes:

  • Interfaz web simple para gestionar múltiples servidores
  • Programación de actualizaciones automáticas
  • Reportes de vulnerabilidades de seguridad
  • Sistema de rollback automático en caso de fallos
  • Notificaciones por email o Slack