Extending Linux LVM partitions script

Here’s a script I wrote a while a go to extend LVM partitions on Linux machines.

The script assumes that you have extended the existing underlying physical (or “virtual” if it’s a VM) storage device prior to execution. It will rescan the disks (skip with -f), resize the existing partition (basically just setting a different end sector), reboot, and run scripts to extend the actual file system after the reboot. There are other ways to extend the disk space including creating a new partition on the additional disk space, but I’ve decided against that approach in favor of a single-partition scheme for management/simplicity’s sake.

This script will work with VMs and physical servers alike. I’ve tested it with RHEL 6/7 and CentOS 6/7, but it should generally work with other Linux distributions as well.

You can get the most recent version of this script on Github here. If you have any suggestions or improvements (which I’m sure there is plenty of room for), feel free to drop a comment or an issue or a pull-request on Github.

 

#!/bin/bash
##########
# Script to resize a LVM Partition after extending the underlying disk device. Can be used on physical or virtual machines alike.
# Tested with CentOS6, RHEL6, CentOS7, RHEL7. This script is only intended for MBR partitioned disks and not for GPT.
#
# The script will first resize the partition by changing the partition end sector of the selected partition, and then after a reboot resize the filesystem.
# By default it rescans the SCSI bus to check a change in disk size if the disk was hot-extended, which is easy with VMs, and only then proceeds.
# If the extended disk size is recognized by the OS already, you can force resizing with the -f flag.
#
# Github: https://github.com/alpacacode/Homebrewn-Scripts
########

usage() {
  echo "Usage:
$0 [-p <LVM physical volume>] [-l <LVM logical volume>] [-f]
 
Options:
 -p physical LVM volume device to extend (check pvdisplay)
 -l logical LVM volume to extend (check lvdisplay)
 -f force extending without a disk rescan. Use this if the OS has detected the enlarged disk already, otherwise we first check whether the underlying disk is larger after a SCSI rescan
    
Example:
./lvmresize.sh -p /dev/sda2 -l /dev/VolGroup/lv_root -f
 
It is highly recommended you backup the boot sector of your disk before as a safety measure with something like the following:
# dd if=/dev/sda of=sda_mbr_backup.mbr bs=512 count=1 
# sfdisk -d /dev/sda > sda_mbr_backup.bak
 
You can restore the partition table then like this:
# dd if=sda_mbr_backup.mbr of=/dev/sda bs=512 count=1
# sfdisk /dev/sda < sda_mbr_backup.bak --force" 1>&2
  exit 1
} 

extenddisk_parted() {
  # Use parted because fdisk behavior can vary between OSes and scripting fdisk is non-deterministic.
  # Using parted resizepart would be easer, but RHEL/CentOS6 parted doesn't support resizepart
  echo -e "\nThis will now extend partition number $partitionnum on disk $disk using start sector $startsector.\nWARNING: Make sure you backup your boot sector prior to this."
  read -r -p "Are you sure? [y/N] " response
  response=${response,,} # tolower 
  if [[ $response =~ ^(yes|y)$ ]]
  then
    echo -e "\n+++Current partition layout of $disk:+++"
    parted $disk --script unit s print
    if [ $logical == 1 ]
    then
      parted $disk --script rm $ext_partitionnum
      parted $disk --script "mkpart extended ${ext_startsector}s -1s"
      parted $disk --script "set $ext_partitionnum lba off"
      parted $disk --script "mkpart logical ext2 ${startsector}s -1s"
    else
      parted $disk --script rm $partitionnum
      parted $disk --script "mkpart primary ext2 ${startsector}s -1s"
    fi
    parted $disk --script set $partitionnum lvm on
    echo -e "\n\n+++New partition layout of $disk:+++"
    parted $disk --script unit s print
    # The 2nd script to expand the filesystem will be automatically executed on the next reboot.
    echo "#!/bin/bash
#Extend Physical Volume first
pvresize $p

#Extend LVM, using 100% of the free allocation units and resize filesystem
lvextend --extents +100%FREE $l --resizefs
chmod -x \$0" > /root/fsresize.sh
    chmod +x /root/fsresize.sh
    # Use a temporary systemd service or a rc.local script for extending the filesystem during next reboot, depending on what the OS is running.
    if(pidof systemd)
    then
      resizefs_systemd
    else
      resizefs_rclocal
    fi
    
    echo -e "Done. The system will reboot automatically in 15 seconds and resize the filesystem during reboot.\n"
    sleep 15
    # Reboot is necessary in most cases for the kernel to read the new partition table.
    reboot
  else
    echo -e "Aborted by user.\n"
    exit 1
  fi
}

resizefs_rclocal() {
  # Resize the filesystem using a script in rc.local if the OS run with sysvinit.
  echo "#Cleanup rc.local again
sed -i /etc/rc.local -e '/\/root\/fsresize\.sh/d' --follow-symlinks
sed -i /etc/rc.local -re 's/^#(exit 0)$/\1/' --follow-symlinks" >> /root/fsresize.sh
  
  sed -i /etc/rc.local -re 's/^(exit 0)$/#\1/' --follow-symlinks
  echo "/root/fsresize.sh" >> /etc/rc.local
}

resizefs_systemd() {
  # Resize the filesystem using a script called by a temporary systemd service file if the OS runs with systemd.
  echo "#Cleanup systemd autostart script again.
systemctl disable fsresize.service
rm -f /etc/systemd/system/fsresize.service" >> /root/fsresize.sh
  
  echo "[Unit]
Description=Filesystem resize script for LVM volume $l

[Service]
ExecStart=/root/fsresize.sh

[Install]
WantedBy=multi-user.target" > /etc/systemd/system/fsresize.service
  systemctl enable fsresize.service
}

# Get options passed to the script.
while getopts ":p:l:f" o; do
  case "${o}" in
    p)
      p=${OPTARG}
      ;;
    l)
      l=${OPTARG}
      ;;
    f)
      f=1
      ;;
    *)
      usage
      ;;
  esac
done
shift $((OPTIND-1))

if [ -z "${p}" ] || [ -z "${l}" ]
then
  usage
fi

command -v fdisk >/dev/null 2>&1 && command -v parted >/dev/null 2>&1 && command -v pvresize >/dev/null 2>&1 || {
  echo -e "Error: Some of the required utilities (fdisk, parted, lvm tools etc) don't seem to be installed on this system.  Aborting.\n" >&2
  exit 1
}

# Check if a valid LVM physical volume was supplied by verifying the pvdisplay exit code ($?).
pvdisplay $p > /dev/null
if [ $? != 0 ] || ( ! (file $p | grep -q "block special"))
then
  echo -e "Error: $p does not look like a block device or LVM physical volume. Aborting.\n"
  usage
fi

# Check if a valid LVM logical volume was supplied by verifying the lvdisplay exit code ($?).
lvdisplay $l > /dev/null
if [ $? != 0 ]
then
  echo -e "Error: $l does not look like a LVM logical volume. Aborting.\n"
  usage
fi

# Fill variables for later use.
disk=$(echo $p | rev | cut -c 2- | rev) # /dev/sda
diskshort=$(echo $disk | grep -Po '[^\/]+$') # sda
partitionnum=$(echo $p | grep -Po '\d$') # 2
startsector=$(fdisk -u -l $disk | grep $p | awk '{print $2}')

# Detect LVM on logical/extended partition
layout=$(parted $disk --script unit s print)
if grep -Pq "^\s$partitionnum\s+.+?logical.+$" <<< "$layout"
then
  echo -e "Detected LVM residing on a logical partition.\n"
  logical=1
  ext_partitionnum=$(parted $disk --script unit s print | grep extended | grep -Po '^\s\d\s' | tr -d ' ')
  ext_startsector=$(parted $disk --script unit s print | grep extended | awk '{print $2}' | tr -d 's')
else
  logical=0
fi

parted $disk --script unit s print | if ! grep -Pq "^\s$partitionnum\s+.+?[^,]+?lvm$"
then
  echo -e "Error: $p seems to have some flags other than the lvm flag set. Other flags are not supported."
  usage
fi

if ! (fdisk -u -l $disk | grep $disk | tail -1 | grep $p | grep -q "Linux LVM")
then
  echo -e "Error: $p is not the last LVM volume on disk $disk. Cannot expand.\n"
  usage
fi

if [ "$f" != 1 ]
then
  oldsize=$(cat /sys/block/${diskshort}/size)
  # Rescan the SCSI bus to detect the grown disk.
  ls /sys/class/scsi_device/*/device/rescan | while read path; do echo 1 > $path; done
  ls /sys/class/scsi_host/host*/scan | while read path; do echo "- - -" > $path; done
  newsize=$(cat /sys/block/${diskshort}/size)

  # Check if the disk is larger now and proceed with the partition expansion if it is. 
  if [ $oldsize -lt $newsize ]
  then
    echo -e "Underlying disk $disk is larger now.\n"
    extenddisk_parted
  else
    echo -e "Disk Size not changed after rescan, already rescanned previously? Force extension with -f. Quitting.\n"
  fi
# When -f (force) flag is set, proceed to extend the disk without checking if it has grown.
else
    extenddisk_parted
fi
0 (0)
Article Rating (No Votes)
Rate this article
Attachments
There are no attachments for this article.
Comments
There are no comments for this article. Be the first to post a comment.
Full Name
Email Address
Security Code Security Code
Related Articles RSS Feed
Linux PAM configuration that allows or deny login via the sshd server
Viewed 421 times since Wed, Oct 3, 2018
How to run command or code in parallel in bash shell under Linux or Unix
Viewed 528 times since Tue, Aug 6, 2019
linux ssh Remotely Initiated Reverse SSH Tunnel
Viewed 123 times since Wed, Apr 22, 2020
LUKS dm-crypt/Device encryption GUIDE
Viewed 567 times since Fri, Jul 13, 2018
YUM CRON Enabling automatic updates in Centos 7 and RHEL 7
Viewed 1270 times since Fri, Oct 26, 2018
How to Register and Enable Red Hat Subscription, Repositories and Updates for RHEL 7.0 Server
Viewed 1016 times since Mon, Oct 29, 2018
SSH: Execute Remote Command or Script – Linux
Viewed 393 times since Mon, Feb 18, 2019
RHEL: How to change a USER/GROUP UID/GID and all owned files
Viewed 2283 times since Sat, Jun 2, 2018
Setting up encrypted tunnel using stunnel
Viewed 409 times since Fri, Sep 28, 2018
systemd Auto-restart a crashed service in systemd
Viewed 309 times since Fri, Jan 17, 2020