Package management automation: apt, yum, dnf em scripts

1. Fundamentos do Gerenciamento de Pacotes em Scripts

1.1. Diferenças entre apt, yum e dnf: comandos equivalentes e sintaxe

Automatizar o gerenciamento de pacotes exige compreender as diferenças entre os principais gerenciadores. O apt é usado em distribuições baseadas em Debian (Ubuntu, Debian), enquanto yum (legado) e dnf (moderno) são usados em distribuições baseadas em Red Hat (CentOS, Fedora, RHEL).

Tabela de comandos equivalentes:

Operação apt yum dnf
Instalar apt install -y yum install -y dnf install -y
Remover apt remove --purge yum remove dnf remove
Atualizar cache apt update yum makecache dnf makecache
Atualizar sistema apt upgrade -y yum update -y dnf upgrade -y
Buscar pacote apt-cache search yum search dnf search

1.2. Verificação de presença do gerenciador (detecção automática da distribuição)

Para criar scripts portáveis, é essencial detectar automaticamente qual gerenciador está disponível:

#!/bin/bash

detect_package_manager() {
    if command -v apt &> /dev/null; then
        echo "apt"
    elif command -v dnf &> /dev/null; then
        echo "dnf"
    elif command -v yum &> /dev/null; then
        echo "yum"
    else
        echo "unknown"
    fi
}

PM=$(detect_package_manager)
echo "Gerenciador detectado: $PM"

1.3. Estrutura básica de um script de instalação condicional

#!/bin/bash

install_package() {
    local package=$1
    local pm=$2

    case $pm in
        apt)
            apt install -y "$package"
            ;;
        dnf)
            dnf install -y "$package"
            ;;
        yum)
            yum install -y "$package"
            ;;
        *)
            echo "Gerenciador não suportado: $pm"
            return 1
            ;;
    esac
}

PM=$(detect_package_manager)
install_package "curl" "$PM"

2. Instalação e Remoção Automatizada de Pacotes

2.1. Uso de apt install -y, yum install -y e dnf install -y em scripts

A flag -y (ou --assumeyes) é crucial para evitar prompts interativos:

#!/bin/bash

# Instalação silenciosa de múltiplos pacotes
PACOTES=("git" "vim" "htop" "wget")

for pkg in "${PACOTES[@]}"; do
    echo "Instalando $pkg..."
    case $PM in
        apt) apt install -y "$pkg" ;;
        dnf) dnf install -y "$pkg" ;;
        yum) yum install -y "$pkg" ;;
    esac
done

2.2. Remoção segura com apt remove --purge, yum remove, dnf remove

#!/bin/bash

remove_package() {
    local package=$1
    local pm=$2

    case $pm in
        apt)
            apt remove --purge -y "$package"
            apt autoremove -y
            ;;
        dnf)
            dnf remove -y "$package"
            dnf autoremove -y
            ;;
        yum)
            yum remove -y "$package"
            yum autoremove -y
            ;;
    esac
}

2.3. Tratamento de dependências e pacotes não encontrados (exit codes e ||)

#!/bin/bash

install_or_fail() {
    local package=$1
    local pm=$2

    if ! install_package "$package" "$pm"; then
        echo "ERRO: Falha ao instalar $package" >&2
        return 1
    fi

    echo "Sucesso: $package instalado"
}

install_or_fail "nginx" "$PM" || exit 1

3. Atualização e Upgrade do Sistema via Script

3.1. Comandos de atualização

#!/bin/bash

system_update() {
    local pm=$1

    case $pm in
        apt)
            apt update && apt upgrade -y
            ;;
        dnf)
            dnf upgrade -y
            ;;
        yum)
            yum update -y
            ;;
    esac
}

3.2. Estratégias para evitar interatividade (env vars)

Para evitar prompts interativos em sistemas Debian/Ubuntu:

#!/bin/bash

export DEBIAN_FRONTEND=noninteractive
export NEEDRESTART_MODE=a

apt update && apt upgrade -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold"

3.3. Verificação de pacotes retidos e necessidade de reboot

#!/bin/bash

check_reboot_needed() {
    if [ -f /var/run/reboot-required ]; then
        echo "ATENÇÃO: Reboot necessário após atualizações"
        return 0
    fi

    # Verifica pacotes retidos (apt)
    if command -v apt-mark &> /dev/null; then
        local held=$(apt-mark showhold)
        if [ -n "$held" ]; then
            echo "Pacotes retidos: $held"
        fi
    fi

    return 1
}

4. Busca e Consulta de Pacotes Programaticamente

4.1. Uso de apt-cache search, yum search e dnf search com grep

#!/bin/bash

search_package() {
    local term=$1
    local pm=$2

    case $pm in
        apt)
            apt-cache search "$term" | grep -i "$term"
            ;;
        dnf)
            dnf search "$term" 2>/dev/null | grep -i "$term"
            ;;
        yum)
            yum search "$term" 2>/dev/null | grep -i "$term"
            ;;
    esac
}

4.2. Consulta de versão instalada

#!/bin/bash

get_installed_version() {
    local package=$1
    local pm=$2

    case $pm in
        apt)
            dpkg -l "$package" 2>/dev/null | awk '/^ii/ {print $3}'
            ;;
        dnf|yum)
            rpm -q "$package" 2>/dev/null
            ;;
    esac
}

4.3. Validação de disponibilidade antes da instalação

#!/bin/bash

is_available() {
    local package=$1
    local pm=$2

    case $pm in
        apt)
            apt-cache show "$package" &> /dev/null
            ;;
        dnf)
            dnf list available "$package" &> /dev/null
            ;;
        yum)
            yum list available "$package" &> /dev/null
            ;;
    esac
}

if is_available "nginx" "$PM"; then
    install_package "nginx" "$PM"
else
    echo "Pacote nginx não encontrado nos repositórios"
fi

5. Instalação de Pacotes a Partir de Repositórios Customizados

5.1. Adição de repositórios via script

#!/bin/bash

add_repository() {
    local repo=$1
    local pm=$2

    case $pm in
        apt)
            add-apt-repository -y "$repo"
            apt update
            ;;
        dnf)
            dnf config-manager --add-repo "$repo"
            dnf makecache
            ;;
        yum)
            yum-config-manager --add-repo "$repo"
            yum makecache
            ;;
    esac
}

5.2. Instalação de pacotes .deb e .rpm locais

#!/bin/bash

install_local_package() {
    local file=$1
    local pm=$2

    case $pm in
        apt)
            dpkg -i "$file" || apt-get install -f -y
            ;;
        dnf|yum)
            rpm -ivh "$file"
            ;;
    esac
}

5.3. Atualização de cache de repositórios após adição

#!/bin/bash

refresh_cache() {
    local pm=$1

    case $pm in
        apt) apt update ;;
        dnf) dnf makecache ;;
        yum) yum makecache ;;
    esac
}

6. Scripts Idempotentes e Tratamento de Erros

6.1. Verificação de pacote já instalado antes de instalar

#!/bin/bash

is_installed() {
    local package=$1
    local pm=$2

    case $pm in
        apt)
            dpkg -s "$package" &> /dev/null
            ;;
        dnf|yum)
            rpm -q "$package" &> /dev/null
            ;;
    esac
}

install_if_missing() {
    local package=$1
    local pm=$2

    if ! is_installed "$package" "$pm"; then
        install_package "$package" "$pm"
    else
        echo "$package já está instalado"
    fi
}

6.2. Funções genéricas com case

#!/bin/bash

package_operation() {
    local operation=$1
    local package=$2
    local pm=$3

    case $pm:$operation in
        apt:install) apt install -y "$package" ;;
        dnf:install) dnf install -y "$package" ;;
        yum:install) yum install -y "$package" ;;
        apt:remove) apt remove --purge -y "$package" ;;
        dnf:remove) dnf remove -y "$package" ;;
        yum:remove) yum remove -y "$package" ;;
        *) echo "Operação não suportada"; return 1 ;;
    esac
}

6.3. Logging e captura de erros com trap

#!/bin/bash

LOG_FILE="/var/log/package-install.log"
exec 2>> "$LOG_FILE"

error_handler() {
    local line=$1
    local exit_code=$2
    echo "Erro na linha $line (código $exit_code)" >> "$LOG_FILE"
}

trap 'error_handler $LINENO $?' ERR

safe_install() {
    local package=$1
    local pm=$2

    if ! install_package "$package" "$pm" 2>&1 | tee -a "$LOG_FILE"; then
        echo "Falha crítica ao instalar $package" >&2
        return 1
    fi
}

7. Boas Práticas e Exemplos Completos

7.1. Script de provisionamento multi-distribuição

#!/bin/bash

# Script de provisionamento universal
set -euo pipefail

detect_package_manager() {
    if command -v apt &> /dev/null; then echo "apt"
    elif command -v dnf &> /dev/null; then echo "dnf"
    elif command -v yum &> /dev/null; then echo "yum"
    else echo "unknown"; fi
}

PM=$(detect_package_manager)
[[ "$PM" == "unknown" ]] && { echo "Gerenciador não suportado"; exit 1; }

# Configurações específicas por distribuição
case $PM in
    apt)
        export DEBIAN_FRONTEND=noninteractive
        apt update
        apt install -y curl wget git vim htop
        ;;
    dnf)
        dnf install -y epel-release
        dnf install -y curl wget git vim htop
        ;;
    yum)
        yum install -y epel-release
        yum install -y curl wget git vim htop
        ;;
esac

echo "Provisionamento concluído com sucesso!"

7.2. Integração com variáveis de ambiente e argumentos

#!/bin/bash

# Uso: ./script.sh --packages "nginx,redis" --dry-run
DRY_RUN=false
PACKAGES=""

while [[ $# -gt 0 ]]; do
    case $1 in
        --packages) PACKAGES="$2"; shift 2 ;;
        --dry-run) DRY_RUN=true; shift ;;
        *) echo "Argumento desconhecido: $1"; exit 1 ;;
    esac
done

IFS=',' read -ra PKG_LIST <<< "$PACKAGES"

for pkg in "${PKG_LIST[@]}"; do
    if $DRY_RUN; then
        echo "[DRY-RUN] Instalaria: $pkg"
    else
        install_if_missing "$pkg" "$PM"
    fi
done

7.3. Testes e validação pós-instalação

#!/bin/bash

validate_installation() {
    local package=$1
    local binary=$2

    if command -v "$binary" &> /dev/null; then
        echo "✓ $package instalado com sucesso ($binary disponível)"
        return 0
    else
        echo "✗ $package: binário $binary não encontrado" >&2
        return 1
    fi
}

# Exemplo de uso
install_if_missing "nginx" "$PM"
validate_installation "nginx" "nginx"

install_if_missing "redis" "$PM"
validate_installation "redis" "redis-server"

Referências