Add expand-disk.sh

This commit is contained in:
Mattia Tadini 2025-11-17 10:39:20 +00:00
commit b62bf0c7cb

178
expand-disk.sh Normal file
View File

@ -0,0 +1,178 @@
#!/bin/bash
# Script per espansione disco VM (auto-detect)
# Autore: Mattia Tadini
# Nome File: expand_disk.sh
# Revisione: 3.3.2
# expand-disk.sh — ONLY growpart: hot-extend LVM (EXT4/XFS) su MBR/GPT
# Flow: rescan → (MBR+logica) growpart <estesa> → growpart <pvpart> → partprobe/reread → pvresize → lvextend → resize FS
set -euo pipefail
log(){ echo "[$(date +'%F %T')] $*"; }
warn(){ echo "[$(date +'%F %T')] [WARN] $*" >&2; }
die(){ echo "ERROR: $*" >&2; exit 1; }
# ----- CLI -----
DRY_RUN=false; AUTO_YES=false; FORCE_DISK=""; FORCE_PART=""; LV_TARGET=""; VG_TARGET=""
while (( "$#" )); do
case "$1" in
-n|--dry-run) DRY_RUN=true; shift;;
-y|--yes) AUTO_YES=true; shift;;
--disk) FORCE_DISK="${2:-}"; shift 2;;
--part) FORCE_PART="${2:-}"; shift 2;;
--lv) LV_TARGET="${2:-}"; shift 2;;
--vg) VG_TARGET="${2:-}"; shift 2;;
-h|--help)
cat <<'EOF'
Uso: expand_disk.sh [opzioni]
-n, --dry-run Simula (growpart --dry-run), nessuna modifica.
-y, --yes Con --dry-run applica subito dopo senza prompt.
--disk /dev/sdX Forza disco del PV (es. /dev/sda, /dev/nvme0n1).
--part N Forza numero partizione del PV (es. 5).
--lv /dev/VG/LV Forza LV (default: LV montato su /).
--vg NOME Forza VG (default: VG dell'LV di /).
Note: usa SOLO growpart per le partizioni.
EOF
exit 0;;
*) die "Opzione sconosciuta: $1";;
esac
done
# ----- prerequisiti -----
need(){ command -v "$1" >/dev/null 2>&1 || die "Comando non trovato: $1"; }
[ "$(id -u)" -eq 0 ] || die "Esegui come root."
for c in findmnt lsblk sfdisk partprobe blockdev pvs vgs lvs pvresize lvextend growpart; do need "$c"; done
FSTYPE="$(findmnt -no FSTYPE /)"
case "$FSTYPE" in
ext4) need resize2fs ;;
xfs) need xfs_growfs ;;
*) warn "Filesystem '$FSTYPE' non gestito automaticamente: dopo lvextend ridimensiona manualmente." ;;
esac
# ----- util -----
pretty(){ command -v numfmt >/dev/null 2>&1 && numfmt --to=iec --suffix=B "$1" || echo "${1}B"; }
parse_disk_part(){
local pv="$1"
if [[ "$pv" =~ ^/dev/(sd[a-z]+)([0-9]+)$ ]]; then echo "${BASH_REMATCH[1]} ${BASH_REMATCH[2]}"; return; fi
if [[ "$pv" =~ ^/dev/(vd[a-z]+)([0-9]+)$ ]]; then echo "${BASH_REMATCH[1]} ${BASH_REMATCH[2]}"; return; fi
if [[ "$pv" =~ ^/dev/(xvd[a-z]+)([0-9]+)$ ]]; then echo "${BASH_REMATCH[1]} ${BASH_REMATCH[2]}"; return; fi
if [[ "$pv" =~ ^/dev/(nvme[0-9]+n[0-9]+)p([0-9]+)$ ]]; then echo "${BASH_REMATCH[1]} ${BASH_REMATCH[2]}"; return; fi
echo " "
}
pttype(){ lsblk -no PTTYPE "/dev/$1" 2>/dev/null | head -n1; }
# detection estesa MBR: solo LETTURA, niente awk complicato
find_extended_part(){
local disk="$1"
# Prendi solo righe con /dev/<disk>[1-4]
# Poi matcha (case-insensitive) type= 0x05 / 0x0f / 0x85 / 5 / f / extended
local line
while IFS= read -r line; do
case "$line" in
/dev/"$disk"[1-4]*)
if echo "$line" | grep -Eiq 'type *= *(0x0?f|0x05|0x85|f|5|extended)'; then
# Estrai numero finale della partizione
echo "${line##*/$disk}" | sed -E 's/[^0-9]*([0-9]+).*/\1/'; return 0
fi
;;
esac
done < <(sfdisk -d "/dev/$disk" 2>/dev/null | sed -n 's/^[[:space:]]*//;p')
return 1
}
sync_pt(){ partprobe "/dev/$1" || true; blockdev --rereadpt "/dev/$1" || true; udevadm settle || true; }
# ----- discovery -----
ROOT_DEV="$(findmnt -no SOURCE /)"
LV_PATH="${LV_TARGET:-$(lvs --noheadings -o lv_path "$ROOT_DEV" | xargs)}"
VG_NAME="${VG_TARGET:-$(lvs --noheadings -o vg_name "$ROOT_DEV" | xargs)}"
[ -n "$LV_PATH" ] && [ -n "$VG_NAME" ] || die "Impossibile determinare LV/VG (usa --lv/--vg)."
mapfile -t PVS < <(pvs --noheadings -o pv_name,vg_name | awk -v vg="$VG_NAME" '$2==vg {print $1}')
[ "${#PVS[@]}" -ge 1 ] || die "Nessun PV nel VG '$VG_NAME'."
PV_DEV="${PVS[0]}"
if [[ -n "$FORCE_DISK" && -n "$FORCE_PART" ]]; then
DISK="$(basename "$FORCE_DISK")"; PART="$FORCE_PART"
else
read -r DISK PART < <(parse_disk_part "$PV_DEV")
fi
[ -n "$DISK" ] && [ -n "$PART" ] || die "PV $PV_DEV non è una partizione; specifica --disk /dev/XXX e --part N."
PTTYPE="$(pttype "$DISK")"
LV_SIZE_B="$(lvs --noheadings --units b --nosuffix -o lv_size "$LV_PATH" | xargs)"
VG_FREE_B="$(vgs --noheadings --units b --nosuffix -o vg_free "$VG_NAME" | xargs)"
log "Filesystem di root: $ROOT_DEV (tipo: $FSTYPE)"
log "Target LV: $LV_PATH — VG: $VG_NAME"
log "PV: $PV_DEV → /dev/$DISK part $PART (tabella: ${PTTYPE:-unknown})"
log "LV size attuale: $(pretty "$LV_SIZE_B")"
log "VG free attuale: $(pretty "$VG_FREE_B")"
# ----- piano -----
echo
log "=== PIANO (ONLY growpart) ==="
echo "1) Rescan /dev/$DISK"
EXT_PART=""
if [[ "$PTTYPE" == "dos" && "$PART" -ge 5 ]]; then
if EXT_PART="$(find_extended_part "$DISK")"; then
echo "2) growpart /dev/$DISK $EXT_PART (estesa)"
else
warn "MBR rilevato ma estesa non trovata: tenterò /dev/${DISK}2 (default comune)."
EXT_PART="2"
echo "2) growpart /dev/$DISK 2 (estesa, default)"
fi
fi
echo "3) growpart /dev/$DISK $PART (PV)"
echo "4) partprobe + rereadpt + udevadm settle"
echo "5) pvresize $PV_DEV"
echo "6) lvextend -l +100%FREE $LV_PATH"
echo "7) resize FS ($FSTYPE)"
echo
# ----- dry-run -----
if "$DRY_RUN"; then
echo "[SIM] echo 1 > /sys/class/block/$DISK/device/rescan"
if [[ "$PTTYPE" == "dos" && "$PART" -ge 5 ]]; then
growpart --dry-run "/dev/$DISK" "$EXT_PART" || warn "[SIM] growpart estesa ha segnalato problemi."
fi
growpart --dry-run "/dev/$DISK" "$PART" || warn "[SIM] growpart PV ha segnalato problemi."
echo "[SIM] partprobe /dev/$DISK && blockdev --rereadpt /dev/$DISK && udevadm settle"
echo "[SIM] pvresize $PV_DEV"
echo "[SIM] lvextend -l +100%FREE $LV_PATH"
[[ "$FSTYPE" == "ext4" ]] && echo "[SIM] resize2fs $LV_PATH" || { [[ "$FSTYPE" == "xfs" ]] && echo "[SIM] xfs_growfs /"; }
if ! "$AUTO_YES"; then
read -r -p "Procedere davvero con l'ESPANSIONE? [y/N] " ans
[[ "${ans,,}" =~ ^(y|yes)$ ]] || { log "Dry-run completato. Nessuna modifica."; exit 0; }
fi
fi
# ----- esecuzione reale -----
log "Rescan /dev/$DISK"; echo 1 > "/sys/class/block/$DISK/device/rescan" || true
if [[ "$PTTYPE" == "dos" && "$PART" -ge 5 ]]; then
log "growpart /dev/$DISK $EXT_PART # estesa"
growpart "/dev/$DISK" "$EXT_PART"
fi
log "growpart /dev/$DISK $PART # PV"
growpart "/dev/$DISK" "$PART"
log "Sync tabella partizioni"
sync_pt "$DISK"
log "pvresize $PV_DEV"; pvresize "$PV_DEV"
log "lvextend -l +100%FREE $LV_PATH"; lvextend -l +100%FREE "$LV_PATH"
case "$FSTYPE" in
ext4) log "resize2fs $LV_PATH"; resize2fs "$LV_PATH" ;;
xfs) log "xfs_growfs /"; xfs_growfs / ;;
esac
NEW_VG_FREE="$(vgs --noheadings --units b --nosuffix -o vg_free "$VG_NAME" | xargs)"
NEW_LV_SIZE="$(lvs --noheadings --units b --nosuffix -o lv_size "$LV_PATH" | xargs)"
log "=== COMPLETATO ==="
log "LV: $(pretty "$LV_SIZE_B")$(pretty "$NEW_LV_SIZE")"
log "VG free: $(pretty "$VG_FREE_B")$(pretty "$NEW_VG_FREE")"
df -h /