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
| Funzione | Debian/Proxmox | Rocky Linux |
|---|---|---|
| Aggiornamento pacchetti | apt-get | dnf |
| 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.