Add expand-disk.sh
This commit is contained in:
commit
b62bf0c7cb
178
expand-disk.sh
Normal file
178
expand-disk.sh
Normal 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 /
|
||||
Loading…
Reference in New Issue
Block a user