🛠 Script di aggiornamento automatizzato per Debian/Proxmox e Rocky Linux

In ambienti server, mantenere il sistema aggiornato è fondamentale per la sicurezza e la stabilità. Questo articolo presenta due script bash che eseguono l’aggiornamento del sistema con conferma interattiva e inviano il log via email all’amministratore. I log non vengono salvati su disco, ma solo inviati via sendmail.

📦 Funzionalità comuni

FunzioneDebian/ProxmoxRocky Linux
Aggiornamento pacchettiapt-getdnf
Conferma interattiva
Invio log via email✅ (sendmail)✅ (sendmail)
Log solo in memoria
Nessun file temporaneo

🧬 Script per Debian/Proxmox

#!/bin/bash

# Configurazione
DATA=$(date '+%Y-%m-%d %H:%M')
HOSTNAME=$(hostname)
EMAIL="ammin@internal.lan"
SUBJECT="[$HOSTNAME] Log aggiornamento - $DATA"

# Gestione log con file temporaneo
LOGFILE=$(mktemp)
trap "rm -f $LOGFILE" EXIT

# Funzioni di logging con emoji appropriate
info() { echo -e "🟢 $@" >> "$LOGFILE"; }
warning() { echo -e "🟠 $@" >> "$LOGFILE"; }
error() { echo -e "🔴 $@" >> "$LOGFILE"; }

# Gestione interattività con controllo pipe
yn() {
    local msg="$1"
    if [ -t 0 ]; then
        read -r -p "$msg [s/N]" resp
        [[ "$resp" =~ ^[sS]$ ]]
    else
        echo "N"
    fi
}

info "Inizio aggiornamento su $HOSTNAME: $DATA"

# Aggiorna repository con gestione errori
info "🔄 Aggiorna elenco pacchetti"
apt update || { error "Fallito l'aggiornamento della lista pacchetti"; exit 1; }

# Chiedi conferma per upgrade completo
DO_UPGRADE=$(yn "Vuoi effettuare 'apt upgrade per aggiornare tutti i pacchetti'?")

if $DO_UPGRADE; then
    info "⬆️ Esegui aggiornamento pacchetti"
    apt upgrade -y || { warning "Aggiornamento pacchetti completato con errori"; }    
else
    info "⏭️ Salto aggiornamento pacchetti su richiesta utente"
fi

# Chiedi conferma per autoremove
DO_AUTOREMOVE=$(yn "Vuoi eseguire 'apt autoremove' per pulizia?")

if $DO_AUTOREMOVE; then
    info "🧹 Esecuzione apt autoremove"
    apt autoremove -y --purge || { warning "apt autoremove ha segnalato errori"; }
else
    info "⏭️ Salto apt autoremove su richiesta utente"
fi

info "🔚 Fine aggiornamento su $HOSTNAME: $(date)"

# Invio email con gestione completa
if command -v sendmail >/dev/null 2>&1; then
    cat <<EOF | sendmail -t
To: $EMAIL
Subject: $SUBJECT
Content-Type: text/plain; charset=UTF-8

$(cat "$LOGFILE")
EOF
    info "📧 Log inviato a $EMAIL tramite sendmail"
else
    warning "⚠️ Comando 'sendmail' non trovato. Impossibile inviare email."
fi

# Verifica sistema
info "🧠 Verifica sistema"
uname -a >> "$LOGFILE"
free -h >> "$LOGFILE"
df -h >> "$LOGFILE"
apt list --installed --upgradable | grep -v "^Listing$" >> "$LOGFILE"

warning "Importante: Controllare il log per confermare l'esito riuscito"

🧬 Script per Rocky Linux

#!/bin/bash

# Script di aggiornamento per Rocky Linux con invio log via sendmail
# Autore: Francesco

set -euo pipefail

DATA=$(date '+%Y-%m-%d %H:%M')
HOSTNAME=$(hostname)
EMAIL="ammin@internal.lan"
SUBJECT="[$HOSTNAME] Log aggiornamento Rocky Linux - $DATA"

# 📦 Variabile per accumulare il log
LOG="🟢 Inizio aggiornamento su $HOSTNAME (Rocky Linux): $DATA"$'\n\n'

# Funzione per eseguire un comando con log
esegui_comando() {
    local descrizione="$1"
    local comando="$2"

    LOG+="🔧 $descrizione"$'\n'
    if output=$($comando 2>&1); then
        LOG+="$output"$'\n\n'
    else
        LOG+="❌ Errore durante '$comando':"$'\n'"$output"$'\n\n'
    fi
}

# Aggiorna lista pacchetti
esegui_comando "dnf check-update" "dnf check-update"

# Conferma per upgrade
read -p "Vuoi eseguire 'dnf upgrade'? [s/N] " conferma
if [[ "$conferma" =~ ^[sS]$ ]]; then
    esegui_comando "dnf upgrade -y" "dnf upgrade -y"
else
    LOG+="❌ upgrade annullato dall'utente"$'\n\n'
fi

# Conferma per autoremove
read -p "Vuoi eseguire 'dnf autoremove'? [s/N] " conferma_autoremove
if [[ "$conferma_autoremove" =~ ^[sS]$ ]]; then
    esegui_comando "dnf autoremove -y" "dnf autoremove -y"
else
    LOG+="❌ autoremove annullato dall'utente"$'\n\n'
fi

LOG+="✅ Script completato su $HOSTNAME: $(date)"$'\n'

# 📬 Invio log via sendmail
if command -v sendmail >/dev/null 2>&1; then
    {
        echo "To: $EMAIL"
        echo "Subject: $SUBJECT"
        echo "Content-Type: text/plain; charset=UTF-8"
        echo ""
        echo "$LOG"
    } | sendmail -t
    echo "📨 Log inviato a $EMAIL tramite sendmail"
else
    echo "⚠️ Comando 'sendmail' non trovato. Impossibile inviare email."
fi

Altro codice per Debian

#!/usr/bin/env bash
# safe-update-apt.sh
# Aggiornamento sicuro usando apt, evita freeze e rimozione kernel/MTA accidentale.
# Esegui come root.

set -o pipefail
IFS=$'\n\t'

DATA=$(date '+%Y-%m-%d %H:%M')
HOSTNAME=$(hostname)
EMAIL="ammin@internal.lan"
SUBJECT="[$HOSTNAME] Report aggiornamento - $DATA"

LOGTMP=$(mktemp /tmp/safe-update-log.XXXXXX)
trap 'rm -f "$LOGTMP"' EXIT

SHORT_T=120
LONG_T=600
MAIL_T=30

info(){ echo -e "🟢 $*" | tee -a "$LOGTMP"; }
warn(){ echo -e "🟠 $*" | tee -a "$LOGTMP"; }
err(){ echo -e "🔴 $*" | tee -a "$LOGTMP"; }

# Esegui comando con timeout e log
run_timeout(){
  local t="$1"; shift
  local cmd="$*"
  info "▶ Eseguo (timeout ${t}s): $cmd"
  if timeout --preserve-status "${t}" bash -c "$cmd" >>"$LOGTMP" 2>&1; then
    return 0
  else
    warn "⏱️ Timeout o errore su: $cmd"
    return 1
  fi
}

# Attendi lock dpkg/apt con timeout
wait_for_locks(){
  local wait_s="$1"
  local elapsed=0
  local interval=2
  while fuser /var/lib/dpkg/lock >/dev/null 2>&1 || fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 || pgrep -x apt >/dev/null || pgrep -x apt-get >/dev/null || pgrep -x dpkg >/dev/null; do
    if [ "$elapsed" -ge "$wait_s" ]; then
      warn "dpkg/apt ancora occupato dopo ${wait_s}s"
      return 1
    fi
    sleep "$interval"
    elapsed=$((elapsed+interval))
  done
  return 0
}

# Prompt sì/no sicuro
yn(){
  local msg="$1"
  if [ -t 0 ]; then
    read -r -p "$msg [s/N] " resp
    [[ "$resp" =~ ^[sS]$ ]]
  else
    return 1
  fi
}

# Proteggi kernel in uso
protect_kernel(){
  local running_kernel
  running_kernel=$(uname -r)
  local kernel_pkg
  kernel_pkg=$(dpkg -S "/boot/vmlinuz-$running_kernel" 2>/dev/null | cut -d: -f1 || true)
  if [ -n "$kernel_pkg" ]; then
    info "🛡️ Kernel in uso: $running_kernel -> $kernel_pkg (marcato manuale)"
    apt-mark manual "$kernel_pkg" >>"$LOGTMP" 2>&1 || warn "apt-mark manual kernel fallito"
  else
    warn "⚠️ Non ho identificato il pacchetto kernel corrente"
  fi
}

# Simula autoremove e restituisce lista pacchetti candidate
simulate_autoremove(){
  info "🔎 Simulo autoremove"
  apt-get -s autoremove 2>/dev/null | tee /tmp/autoremove-sim.$$ >>"$LOGTMP"
  awk '/^Remv /{print $2} /^Remv:/{for(i=2;i<=NF;i++) print $i}' /tmp/autoremove-sim.$$ 2>/dev/null | sort -u
}

# Invio mail non bloccante con timeout
send_mail_nonblocking(){
  local mailtmp
  mailtmp=$(mktemp)
  {
    echo "To: $EMAIL"
    echo "Subject: $SUBJECT"
    echo "Content-Type: text/plain; charset=UTF-8"
    echo
    cat "$LOGTMP"
  } > "$mailtmp"
  timeout "${MAIL_T}s" sendmail -t < "$mailtmp" >>"$LOGTMP" 2>&1 & disown || warn "sendmail timeout o errore"
  rm -f "$mailtmp"
  info "📧 Tentativo invio log (non bloccante)"
}

# Inizio
info "=== INIZIO aggiornamento su $HOSTNAME : $DATA ==="

export DEBIAN_FRONTEND=noninteractive
export DEBCONF_NONINTERACTIVE_SEEN=true
export APT_LISTCHANGES_FRONTEND=none

wait_for_locks 30 || {
  warn "Lock persistenti: mostro processi rilevanti"
  ps -ef | egrep 'apt-get|apt|dpkg|sendmail' | egrep -v 'egrep|grep' >>"$LOGTMP"
  run_timeout "$LONG_T" "apt-get -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' --fix-broken install -y" || warn "fix-broken timeout/errore"
}

# Update
if ! run_timeout "$SHORT_T" "apt-get update -y"; then
  err "apt-get update fallito: esco"
  send_mail_nonblocking
  exit 1
fi

# Upgrade opzionale
if yn "Vuoi eseguire apt upgrade?"; then
  run_timeout "$LONG_T" "apt-get -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' upgrade -y" || warn "upgrade terminato con errori"
else
  info "⏭️ Upgrade saltato su richiesta"
fi

# Fix broken
run_timeout "$LONG_T" "apt-get --fix-broken install -y" || warn "fix-broken ha segnalato problemi"

# Proteggi kernel e MTA essenziali
protect_kernel
# Proteggi MTA comuni per evitare rimozioni accidentali
apt-mark manual sendmail sendmail-base sendmail-cf sensible-mda 2>/dev/null || true

# Autoremove sicuro
if yn "Vuoi eseguire autoremove sicuro (simulazione + rimozione mirata)?"; then
  to_remove=$(simulate_autoremove)
  safe_list=$(echo "$to_remove" | egrep -v '^(linux-image|linux-headers|linux-modules|sendmail|sendmail-base|sendmail-cf|sensible-mda)' || true)
  if [ -z "$safe_list" ]; then
    warn "Nessun pacchetto sicuro da rimuovere o solo kernel/MTA presenti. Autoremove saltato."
  else
    info "Rimuovo pacchetti sicuri:"
    echo "$safe_list" | tee -a "$LOGTMP"
    run_timeout "$LONG_T" "apt-get -y --purge remove $(echo "$safe_list" | xargs)" || warn "rimozione mirata ha segnalato errori"
  fi
else
  info "Autoremove saltato su richiesta"
fi

info "=== FINE aggiornamento su $HOSTNAME : $(date) ==="

# Report
if [ -f /var/log/apt/history.log ]; then
  echo -e "\n---- /var/log/apt/history.log (ultime 200 righe) ----" >>"$LOGTMP"
  tail -n 200 /var/log/apt/history.log >>"$LOGTMP" 2>&1 || true
fi

{
  echo -e "\n---- Info sistema ----"
  uname -a
  free -h
  df -h
  echo -e "\n---- Pacchetti upgradabili ----"
  apt list --upgradable 2>/dev/null | grep -v "^Listing$" || true
} >>"$LOGTMP"

send_mail_nonblocking

# Se vuoi conservare il log, copia $LOGTMP in /var/log prima che venga rimosso
exit 0

Altro codice per Rocky linux

#!/bin/bash

# 📅 Info iniziali
DATA=$(date '+%Y-%m-%d %H:%M')
HOSTNAME=$(hostname)
EMAIL="ammin@internal.lan"
SUBJECT="[$HOSTNAME] Log aggiornamento - $DATA"
LOGFILE=$(mktemp)
trap "rm -f $LOGFILE" EXIT

# 🔧 Logging
log() { echo -e "$@" >> "$LOGFILE"; }
info() { log "🟢 $@"; }
warn() { log "🟠 $@"; }
err()  { log "🔴 $@"; }

# ❓ Conferma interattiva
ask() {
    local prompt="$1"
    if [ -t 0 ]; then
        read -r -p "$prompt [s/N] " reply
        [[ "$reply" =~ ^[sS]$ ]]
    else
        return 1
    fi
}

info "Inizio aggiornamento su $HOSTNAME: $DATA"

# 🔄 Aggiorna cache
info "Pulizia e aggiornamento cache..."
dnf clean all && dnf makecache || { err "Errore durante 'dnf makecache'"; exit 1; }

# ⬆️ Upgrade
if ask "Vuoi eseguire 'dnf upgrade'?"; then
    info "Eseguo 'dnf upgrade'..."
    dnf upgrade -y || warn "Upgrade completato con errori"
else
    info "Upgrade saltato"
fi

# 🧹 Autoremove
if ask "Vuoi eseguire 'dnf autoremove'?"; then
    info "Eseguo 'dnf autoremove'..."
    dnf autoremove -y || warn "Autoremove ha segnalato errori"
else
    info "Autoremove saltato"
fi

info "Fine aggiornamento: $(date '+%c')"

# 📦 Tracciamento pacchetti aggiornati
info "📜 Storico transazioni DNF"
dnf history list | head -n 10 >> "$LOGFILE"

LAST_ID=$(dnf history | awk '/Upgrade/ {print $1}' | head -n 1)
if [ -n "$LAST_ID" ]; then
    info "📦 Dettagli ultima transazione (ID $LAST_ID)"
    dnf history info "$LAST_ID" >> "$LOGFILE"
else
    warn "Nessuna transazione di upgrade trovata"
fi

# 🧠 Info sistema
info "🧠 Informazioni sistema:"
uname -a >> "$LOGFILE"
free -h >> "$LOGFILE"
df -h >> "$LOGFILE"

# 📌 Aggiornamenti disponibili
info "📌 Aggiornamenti disponibili:"
dnf check-update >> "$LOGFILE"

# 📧 Invio email con gestione dimensione
MAXSIZE=500000  # ~500 KB
LOGSIZE=$(stat -c%s "$LOGFILE")

if command -v sendmail >/dev/null 2>&1; then
    if [ "$LOGSIZE" -lt "$MAXSIZE" ]; then
        {
            echo "To: $EMAIL"
            echo "Subject: $SUBJECT"
            echo "Content-Type: text/plain; charset=UTF-8"
            echo ""
            cat "$LOGFILE"
        } | sendmail -t
        info "📧 Log inviato a $EMAIL"
    else
        {
            echo "To: $EMAIL"
            echo "Subject: $SUBJECT (estratto)"
            echo "Content-Type: text/plain; charset=UTF-8"
            echo ""
            echo "⚠️ Log troppo grande. Invio solo le ultime 500 righe:\n"
            tail -n 500 "$LOGFILE"
        } | sendmail -t
        warn "Log troppo grande: invio parziale"
    fi
else
    warn "Sendmail non disponibile. Email non inviata."
fi

warn "Controlla il log per confermare l'esito e i pacchetti aggiornati"

📌 Considerazioni finali

Questi script sono pensati per ambienti dove è importante avere controllo manuale sull’aggiornamento e una tracciabilità immediata via email. Entrambi sono compatibili con sendmail e non lasciano tracce locali del log.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *