diff --git a/changelogs/fragments/btrfs_bigboot.yml b/changelogs/fragments/btrfs_bigboot.yml new file mode 100644 index 0000000..b7aa5ed --- /dev/null +++ b/changelogs/fragments/btrfs_bigboot.yml @@ -0,0 +1,5 @@ +major_changes: +- add bigboot support for Btrfs next partition +minor_changes: +- show console log output from bigboot even if quiet kernel arg is set +- do bigboot LVM changes with Ansible instead of pre-mount hook diff --git a/roles/bigboot/README.md b/roles/bigboot/README.md index a712b20..90abdf7 100644 --- a/roles/bigboot/README.md +++ b/roles/bigboot/README.md @@ -6,8 +6,7 @@ The role is designed to support the automation of RHEL in-place upgrades, but ca ## Contents -The role contains the shell scripts to increase the size of the boot partition, as well as the script wrapping it to run as part of the pre-mount step during the boot process. -Finally, there is a copy of the [`sfdisk`](https://man7.org/linux/man-pages/man8/sfdisk.8.html) binary with version `2.38.1` to ensure the extend script will work regardless of the `util-linux` package installed in the target host. +The role configures a dracut pre-mount hook that executes during a reboot to increase the size of the boot partition and filesystem. To make room for the boot size increase, the role first shrinks the size of the next partition after the boot partition. This next partition must contain either an LVM physical volume or a Btrfs filesystem volume. There must be sufficient free space in the LVM volume group or Btrfs filesystem to accommodate the reduced size. > **WARNING!** > @@ -25,31 +24,62 @@ The variable `bigboot_partition_size` specifies the minimum required size of the This variable is deprecated and will be removed in a future release. Use `bigboot_partition_size` instead. -The variable `bigboot_size` specifies by how much the size of the boot partition is to be increased. The value can be either in bytes or with optional single letter suffix (1024 bases) using [human_to_bytes](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/human_to_bytes_filter.html) filter plugin. +The variable `bigboot_size` specifies by how much the size of the boot partition is to be increased. The value can be either in bytes or with optional single letter suffix (1024 bases) using [human_to_bytes](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/human_to_bytes_filter.html) filter plugin. > **Note** > -> The size increase may be slightly less than the specified value as the role will round down to the nearest multiple of the extent size of the LVM physical volume in the partition above the boot partition. +> The size increase may be slightly less than the specified value as the role will round down to the nearest multiple of the LVM volume group extent size or Btrfs sector size used for the next partition after the boot partition. -## Example of a playbook to run the role -The following yaml is an example of a playbook that runs the role against a group of hosts named `rhel` and increasing the size of its boot partition by 1G. -The boot partition is automatically retrieved by the role by identifying the existing mounted partition to `/boot` and passing the information to the script using the `kernel_opts`. +## Example playbook +The following yaml demonstrates an example playbook that runs the role to increase the size of the target hosts boot partition to 1.5G: ```yaml - name: Extend boot partition playbook hosts: all vars: - bigboot_partition_size: 1G + bigboot_partition_size: 1.5G roles: - bigboot ``` # Validate execution -The script will add an entry to the kernel messages (`/dev/kmsg`) with success or failure and the time it took to process. -In case of failure, it may also include an error message retrieved from the execution of the script. +The "Validate boot filesystem new size" task at the end of the run will indicate success or failure of the boot partition size increase. For example: -A successful execution will look similar to this: +``` +TASK [bigboot : Validate boot filesystem new size] **************************************** +ok: [fedora] => { + "changed": false, + "msg": "Boot filesystem size is now 1.44 GB (503.46 MB increase)" +``` + +If the boot partition was already equal to or greater than the given size, the bigboot pre-mount hook configuration is skipped and the host will not reboot. In this case, the run will end with the "Validate increase requested" task indicating nothing happened. For example: + +``` +TASK [bigboot : Validate increase requested] ********************************************** +ok: [fedora] => { + "msg": "Nothing to do! Boot partition already equal to or greater than requested size." +} +``` + +During the reboot, the bigboot pre-mount hook logs progress messages to the console. After the reboot, `journalctl` can be used to review the log output. For example, a successful run will look similar to this: ```bash -[root@localhost ~]# dmesg |grep pre-mount -[ 357.163522] [dracut-pre-mount] Boot partition /dev/vda1 successfully increased by 1G (356 seconds) +# journalctl --boot --unit=dracut-pre-mount +Jul 02 09:40:12 fedora systemd[1]: Starting dracut-pre-mount.service - dracut pre-mount hook... +Jul 02 09:40:12 fedora dracut-pre-mount[498]: bigboot: Shrinking partition vda3 by 536870912 +Jul 02 09:40:12 fedora dracut-pre-mount[498]: bigboot: Moving up partition vda3 by 536870912 +Jul 02 09:40:16 fedora dracut-pre-mount[508]: bigboot: Partition move is progressing, please wait! (00:00:01) +Jul 02 09:40:48 fedora dracut-pre-mount[498]: bigboot: Increasing boot partition vda2 by 536870912 +Jul 02 09:40:49 fedora dracut-pre-mount[498]: bigboot: Updating kernel partition table +Jul 02 09:40:50 fedora dracut-pre-mount[498]: bigboot: Growing the /boot ext4 filesystem +Jul 02 09:40:50 fedora dracut-pre-mount[528]: e2fsck 1.47.0 (5-Feb-2023) +Jul 02 09:40:50 fedora dracut-pre-mount[528]: Pass 1: Checking inodes, blocks, and sizes +Jul 02 09:40:50 fedora dracut-pre-mount[528]: Pass 2: Checking directory structure +Jul 02 09:40:50 fedora dracut-pre-mount[528]: Pass 3: Checking directory connectivity +Jul 02 09:40:50 fedora dracut-pre-mount[528]: Pass 4: Checking reference counts +Jul 02 09:40:50 fedora dracut-pre-mount[528]: Pass 5: Checking group summary information +Jul 02 09:40:50 fedora dracut-pre-mount[528]: /dev/vda2: 38/65536 files (10.5% non-contiguous), 83665/262144 blocks +Jul 02 09:40:50 fedora dracut-pre-mount[529]: resize2fs 1.47.0 (5-Feb-2023) +Jul 02 09:40:50 fedora dracut-pre-mount[529]: Resizing the filesystem on /dev/vda2 to 393216 (4k) blocks. +Jul 02 09:40:50 fedora dracut-pre-mount[529]: The filesystem on /dev/vda2 is now 393216 (4k) blocks long. +Jul 02 09:40:50 fedora dracut-pre-mount[493]: Boot partition vda2 successfully increased by 536870912 (38 seconds) ``` diff --git a/roles/bigboot/files/bigboot.sh b/roles/bigboot/files/bigboot.sh index 351919e..35618d0 100644 --- a/roles/bigboot/files/bigboot.sh +++ b/roles/bigboot/files/bigboot.sh @@ -1,485 +1,121 @@ #!/bin/bash # -# Script to increase the ext/xfs boot partition in a BIOS system by shifting -# the adjacent partition to the boot partition by the parametrized size. It -# expects the device to have enough free space to shift to the right of the -# adjacent partition, that is towards the end of the device. It only works -# with ext and xfs filesystems and supports adjacent partitions as primary -# or logical partitions and LVM in the partition. +# This is the new bigboot reboot script. Unlike the old script, this one +# only deals with the partitioning and boot filesystem changes required. +# The preparations to reduce the LVM physical volume or Btrfs filesystem +# volume are now done in advance by Ansible before rebooting. # -# The parametrized size supports M for MiB and G for GiB. If no units is given, -# it is interpreted as bytes +# This script performs the following steps in this order: # -# Usage: bigboot.sh -d= -s= -b= -p= +# 1. Move the end of the next partition to make it smaller +# 2. Use sfdisk to copy the blocks of the next partition +# 3. Move the end of the boot partition making it bigger +# 4. Grow the boot filesystem # -# Example -# Given this device partition: -# Number Start End Size Type File system Flags -# 32.3kB 1049kB 1016kB Free Space -# 1 1049kB 11.1GB 11.1GB primary ext4 boot -# 2 11.1GB 32.2GB 21.1GB extended -# 5 11.1GB 32.2GB 21.1GB logical ext4 +# Usage: bigboot.sh boot_partition_name next_partition_name boot_size_increase_in_bytes # -# Running the command: -# $>bigboot.sh -d=/dev/sda -s=1G -b=1 +# For example, this command would increase a /boot filesystem on /dev/sda1 by 500M: # -# Will increase the boot partition in /dev/vdb by 1G and shift the adjacent -# partition in the device by the equal amount. +# bigboot.sh sda1 sda2 524288000 # -# Number Start End Size Type File system Flags -# 32.3kB 1049kB 1016kB Free Space -# 1 1049kB 12.2GB 12.2GB primary ext4 boot -# 2 12.2GB 32.2GB 20.0GB extended -# 5 12.2GB 32.2GB 20.0GB logical ext4 -# - -# Command parameters -INCREMENT_BOOT_PARTITION_SIZE= -DEVICE_NAME= -BOOT_PARTITION_NUMBER= -PARTITION_PREFIX= - -# Script parameters -NOLOCKING= -ADJACENT_PARTITION_NUMBER= -BOOT_FS_TYPE= -EXTENDED_PARTITION_TYPE=extended -INCREMENT_BOOT_PARTITION_SIZE_IN_BYTES= -SHRINK_SIZE_IN_BYTES= - -print_help() { - echo "Usage: $(basename "$0") -d= -s= -b= -p=" -} - -get_device_type() { - local device=$1 - if /usr/sbin/lvm pvs "$device" > /dev/null 2>&1; then - echo "lvm" - else - echo "other" - fi -} - -ensure_device_not_mounted() { - local device=$1 - local devices_to_check - device_type=$(get_device_type "$device") - if [[ $device_type == "lvm" ]]; then - # It's an LVM block device - # Capture the LV device names. Since we'll have to shift the partition, we need to make sure all LVs are not mounted in the adjacent partition. - devices_to_check=$(/usr/sbin/lvm pvdisplay "$device" -m | /usr/bin/grep "Logical volume" | /usr/bin/awk '{print $3}') - else - # Use the device and partition number instead - devices_to_check=$device - fi - for device_name in $devices_to_check; do - /usr/bin/findmnt --source "$device_name" 1>&2>/dev/null - status=$? - if [[ status -eq 0 ]]; then - echo "Device $device_name is mounted" - exit 1 - fi - done -} - -validate_device() { - local device=$1 - if [[ -z "${device}" ]]; then - echo "Missing device name" - print_help - exit 1 - fi - if [[ ! -e "${device}" ]]; then - echo "Device ${device} not found" - exit 1 - fi - ret=$(/usr/sbin/fdisk -l "${device}" 2>&1) - status=$? - if [[ $status -ne 0 ]]; then - echo "Failed to open device ${device}: $ret" - exit 1 - fi -} - -validate_increment_partition_size() { - if [[ -z "$INCREMENT_BOOT_PARTITION_SIZE" ]]; then - echo "Missing incremental size for boot partition" - print_help - exit 1 - fi - ret=$(/usr/bin/numfmt --from=iec "$INCREMENT_BOOT_PARTITION_SIZE" 2>&1) - status=$? - if [[ $status -ne 0 ]]; then - echo "Invalid size value for '$INCREMENT_BOOT_PARTITION_SIZE': $ret" - exit $status - fi - INCREMENT_BOOT_PARTITION_SIZE_IN_BYTES=$ret -} - -# Capture all parameters: -# Mandatory: Device, Size and Boot Partition Number -# Optional: Partition Prefix (e.g. "p" for nvme based volumes) -parse_flags() { - for i in "$@" - do - case $i in - -d=*|--device=*) - DEVICE_NAME="${i#*=}" - ;; - -s=*|--size=*) - INCREMENT_BOOT_PARTITION_SIZE="${i#*=}" - ;; - -b=*|--boot=*) - BOOT_PARTITION_NUMBER="${i#*=}" - ;; - -p=*|--prefix=*) - PARTITION_PREFIX="${i#*=}" - ;; - -h) - print_help - exit 0 - ;; - *) - # unknown option - echo "Unknown flag $i" - print_help - exit 1 - ;; - esac - done -} - -validate_parameters() { - validate_device "${DEVICE_NAME}" - validate_increment_partition_size - - # Make sure BOOT_PARTITION_NUMBER is set to avoid passing only DEVICE_NAME - if [[ -z "$BOOT_PARTITION_NUMBER" ]]; then - echo "Boot partition number was not set" - print_help - exit 1 - fi - validate_device "${DEVICE_NAME}${PARTITION_PREFIX}${BOOT_PARTITION_NUMBER}" - - ensure_device_not_mounted "${DEVICE_NAME}${PARTITION_PREFIX}${BOOT_PARTITION_NUMBER}" - ensure_extendable_fs_type "${DEVICE_NAME}${PARTITION_PREFIX}${BOOT_PARTITION_NUMBER}" -} - -get_fs_type() { - local device=$1 - ret=$(/usr/sbin/blkid "$device" -o udev | sed -n -e 's/ID_FS_TYPE=//p' 2>&1) - status=$? - if [[ $status -ne 0 ]]; then - exit $status - fi - echo "$ret" -} - -ensure_extendable_fs_type() { - local device=$1 - ret=$(get_fs_type "$device") - if [[ ! "$ret" =~ ^ext[2-4]$|^xfs$ ]]; then - echo "Boot filesystem type $ret is not extendable" - exit 1 - fi - BOOT_FS_TYPE=$ret -} - -get_nolocking_opts() { - local lvm_version - lvm_version="$(/usr/sbin/lvm version | /usr/bin/grep 'LVM version:')" - status=$? - if [[ $status -ne 0 ]]; then - echo "Error getting LVM version '$lvm_version'" - exit $status - fi - # true when LVM version is older than 2.03 - if echo -e "${lvm_version##*:}\n2.03" | /usr/bin/sed 's/^ *//' | /usr/bin/sort -V -C; then - NOLOCKING='--config=global{locking_type=0}' - else - NOLOCKING='--nolocking' - fi -} - -get_successive_partition_number() { - boot_line_number=$(/usr/sbin/parted -m "$DEVICE_NAME" print | /usr/bin/sed -n '/^'"$BOOT_PARTITION_NUMBER"':/ {=}') - status=$? - if [[ $status -ne 0 ]]; then - echo "Unable to identify boot partition number for '$DEVICE_NAME'" - exit $status - fi - if [[ -z "$boot_line_number" ]]; then - echo "No boot partition found" - exit 1 - fi - # get the extended partition number in case there is one, we will need to shrink it as well - EXTENDED_PARTITION_NUMBER=$(/usr/sbin/parted "$DEVICE_NAME" print | /usr/bin/sed -n '/'"$EXTENDED_PARTITION_TYPE"'/p' | awk '{print $1}') - if [[ -n "$EXTENDED_PARTITION_NUMBER" ]]; then - # if there's an extended partition, use the last one as the target partition to shrink - ADJACENT_PARTITION_NUMBER=$(/usr/sbin/parted "$DEVICE_NAME" print | grep -v "^$" | awk 'END{print$1}') - else - # get the partition number from the next line after the boot partition - ADJACENT_PARTITION_NUMBER=$(/usr/sbin/parted -m "$DEVICE_NAME" print | /usr/bin/awk -F ':' '/'"^$BOOT_PARTITION_NUMBER:"'/{getline;print $1}') - fi - if ! [[ $ADJACENT_PARTITION_NUMBER == +([[:digit:]]) ]]; then - echo "Invalid successive partition number '$ADJACENT_PARTITION_NUMBER'" - exit 1 - fi - ensure_device_not_mounted "${DEVICE_NAME}${PARTITION_PREFIX}${ADJACENT_PARTITION_NUMBER}" -} - -init_variables() { - parse_flags "$@" - validate_parameters - get_nolocking_opts - get_successive_partition_number -} - -check_filesystem() { - local device=$1 - fstype=$(get_fs_type "$device") - if [[ "$fstype" == "swap" ]]; then - echo "Warning: cannot run fsck to a swap partition for $device" - return 0 - fi - if [[ "$BOOT_FS_TYPE" =~ ^ext[2-4] ]]; then - # Retrieve the estimated minimum size in bytes that the device can be shrank - ret=$(/usr/sbin/e2fsck -fy "$device" 2>&1) - local status=$? - if [[ status -ne 0 ]]; then - echo "Warning: Filesystem check failed for $device: $ret" - fi - fi -} - -convert_size_to_fs_blocks() { - local device=$1 - local size=$2 - block_size_in_bytes=$(/usr/sbin/tune2fs -l "$device" | /usr/bin/awk '/Block size:/{print $3}') - echo $(( size / block_size_in_bytes )) -} - -deactivate_volume_group() { - ret=$(/usr/sbin/lvm vgchange -an "$LVM2_VG_NAME" 2>&1) - status=$? - if [[ $status -ne 0 ]]; then - echo "Failed to deactivate volume group $LVM2_VG_NAME: $ret" - exit $status - fi - # avoid potential deadlocks with udev rules before continuing - sleep 1 -} - -check_available_free_space() { - local device="${DEVICE_NAME}${PARTITION_PREFIX}${ADJACENT_PARTITION_NUMBER}" - # Get LVM details - eval "$(/usr/sbin/lvm pvs --noheadings --nameprefixes --nosuffix --units b -o vg_name,vg_extent_size,pv_pe_count,pv_pe_alloc_count,vg_free_count "$device")" - status=$? - if [[ $status -ne 0 ]]; then - echo "Failed getting LVM details for $device: $status" - exit $status - fi - # Make shrink size a multiple of extent size - SHRINK_SIZE_IN_BYTES=$((INCREMENT_BOOT_PARTITION_SIZE_IN_BYTES/LVM2_VG_EXTENT_SIZE*LVM2_VG_EXTENT_SIZE)) - if [[ $INCREMENT_BOOT_PARTITION_SIZE_IN_BYTES -ne $SHRINK_SIZE_IN_BYTES ]]; then - echo "Requested size increase rounded down to nearest extent multiple." >&2 - INCREMENT_BOOT_PARTITION_SIZE="$(numfmt --to=iec $SHRINK_SIZE_IN_BYTES)" - fi - # Quit if shrink size is zero - if [[ $SHRINK_SIZE_IN_BYTES -le 0 ]]; then - echo "Boot size increase is $SHRINK_SIZE_IN_BYTES after rounding down to nearest extent multiple. Nothing to do." - exit 1 - fi - # Calculate free extents required - required_pe_count=$((SHRINK_SIZE_IN_BYTES/LVM2_VG_EXTENT_SIZE)) - if [[ $required_pe_count -gt $LVM2_VG_FREE_COUNT ]]; then - echo "Not enough available free PE in VG $LVM2_VG_NAME: Required $required_pe_count but found $LVM2_VG_FREE_COUNT" - exit 1 - fi -} - -resolve_device_name() { - local device="${DEVICE_NAME}${PARTITION_PREFIX}${ADJACENT_PARTITION_NUMBER}" - device_type=$(get_device_type "$device") - if [[ $device_type != "lvm" ]]; then - echo "Next partition after /boot is not LVM: $device is type $device_type" - exit 1 - fi -} - -check_device() { - local device="${DEVICE_NAME}${PARTITION_PREFIX}${ADJACENT_PARTITION_NUMBER}" - resolve_device_name - ensure_device_not_mounted "$device" - check_available_free_space -} - -evict_end_PV() { - local device="${DEVICE_NAME}${PARTITION_PREFIX}${ADJACENT_PARTITION_NUMBER}" - local shrinking_start_PE=$1 - ret=$(/usr/sbin/lvm pvmove "$NOLOCKING" --alloc anywhere "$device":"$shrinking_start_PE"- 2>&1) - status=$? - if [[ $status -ne 0 ]]; then - echo "Failed to evict PEs in PV $device: $ret" - exit $status - fi -} - -shrink_physical_volume() { - local device="${DEVICE_NAME}${PARTITION_PREFIX}${ADJACENT_PARTITION_NUMBER}" - partition_size_in_bytes=$(/usr/sbin/parted -m "$DEVICE_NAME" unit b print | /usr/bin/awk '/^'"$ADJACENT_PARTITION_NUMBER"':/ {split($0,value,":"); print value[4]}' | /usr/bin/sed -e's/B//g') - pv_new_size_in_bytes=$((partition_size_in_bytes-SHRINK_SIZE_IN_BYTES)) - shrink_start_PE=$((LVM2_PV_PE_COUNT-1-(SHRINK_SIZE_IN_BYTES/LVM2_VG_EXTENT_SIZE))) - # Test mode pvresize - ret=$(/usr/sbin/lvm pvresize "$NOLOCKING" --setphysicalvolumesize "$pv_new_size_in_bytes"B -t "$device" -y 2>&1) - status=$? - if [[ $status -ne 0 ]]; then - if [[ $status -eq 5 ]]; then - # ERRNO 5 is equivalent to command failed: https://github.com/lvmteam/lvm2/blob/2eb34edeba8ffc9e22b6533e9cb20e0b5e93606b/tools/errors.h#L23 - # Try to recover by evicting the ending PEs elsewhere in the PV, in case it's a failure due to ending PE's being inside the shrinking area. - evict_end_PV $shrink_start_PE - else - echo "Failed to resize PV $device: $ret" - exit $status - fi - fi - echo "Shrinking PV $device to $pv_new_size_in_bytes bytes" >&2 - ret=$(/usr/sbin/lvm pvresize "$NOLOCKING" --setphysicalvolumesize "$pv_new_size_in_bytes"B "$device" -y 2>&1) - status=$? - if [[ $status -ne 0 ]]; then - echo "Failed to resize PV $device during retry: $ret" - exit $status - fi -} - -calculate_new_end_partition_in_bytes() { - local partition_number=$1 - local device="${DEVICE_NAME}${PARTITION_PREFIX}${partition_number}" - current_end=$(/usr/sbin/parted -m "$DEVICE_NAME" unit b print | /usr/bin/awk '/^'"$partition_number"':/ {split($0,value,":"); print value[3]}' | /usr/bin/sed -e's/B//g') - status=$? - if [[ $status -ne 0 ]]; then - echo "Failed to get new end partition size in bytes for $device: $ret" - exit 1 - fi - - new_end=$((current_end-SHRINK_SIZE_IN_BYTES)) - echo "$new_end" -} - -shrink_partition() { - local partition_number=$1 - new_end_partition_in_bytes=$(calculate_new_end_partition_in_bytes "$partition_number") - echo "Shrinking partition $partition_number in $DEVICE_NAME by $INCREMENT_BOOT_PARTITION_SIZE" >&2 - ret=$(echo Yes | /usr/sbin/parted "$DEVICE_NAME" ---pretend-input-tty unit B resizepart "$partition_number" "$new_end_partition_in_bytes" 2>&1 ) - status=$? - if [[ $status -ne 0 ]]; then - echo "Failed to resize device $DEVICE_NAME$partition_number to size: $ret" - exit 1 - fi -} - -shrink_adjacent_partition() { - shrink_physical_volume - shrink_partition "$ADJACENT_PARTITION_NUMBER" - if [[ -n "$EXTENDED_PARTITION_NUMBER" ]]; then - # resize the extended partition - shrink_partition "$EXTENDED_PARTITION_NUMBER" - fi -} - -shift_adjacent_partition() { - # Move the partition following boot up to make room for increasing the boot partition - local target_partition=$ADJACENT_PARTITION_NUMBER - if [[ -n "$EXTENDED_PARTITION_NUMBER" ]]; then - target_partition=$EXTENDED_PARTITION_NUMBER - fi - # Output progress messages to help impatient operators recognize the server is not "hung" - ( sleep 4 - while t="$(ps -C sfdisk -o cputime=)"; do - echo "Bigboot partition move is progressing, please wait! ($t)" >&2 - sleep 120 - done ) & - echo "Moving up partition $target_partition in $DEVICE_NAME by $INCREMENT_BOOT_PARTITION_SIZE" >&2 - # Default units for sfdisk are 512 byte sectors - sectors_to_move_up_count=$((SHRINK_SIZE_IN_BYTES/512)) - ret=$(echo "+$sectors_to_move_up_count," | /usr/sbin/sfdisk --move-data "$DEVICE_NAME" -N "$target_partition" --force 2>&1) - status=$? - if [[ status -ne 0 ]]; then - echo "Failed to shift $DEVICE_NAME partition $target_partition up $sectors_to_move_up_count sectors': $ret" - exit $status - fi -} - -update_kernel_partition_tables() { - # ensure that the VG is not active so that the changes to the kernel PT are reflected by the partprobe command - deactivate_volume_group - /usr/sbin/partprobe "$DEVICE_NAME" 2>&1 - sleep 1 - activate_volume_group -} - -increase_boot_partition() { - local device="${DEVICE_NAME}${PARTITION_PREFIX}${BOOT_PARTITION_NUMBER}" - echo "Increasing boot partition $BOOT_PARTITION_NUMBER in $DEVICE_NAME by $INCREMENT_BOOT_PARTITION_SIZE" >&2 - ret=$(echo "- +" | /usr/sbin/sfdisk "$DEVICE_NAME" -N "$BOOT_PARTITION_NUMBER" --no-reread --force 2>&1) - status=$? - if [[ $status -ne 0 ]]; then - echo "Failed to increase boot partition '$device': $ret" - return - fi - update_kernel_partition_tables - # Increase the /boot filesystem - if [[ "$BOOT_FS_TYPE" =~ ^ext[2-4] ]]; then - check_filesystem "$device" - ret=$(/usr/sbin/resize2fs "$device" 2>&1) - # Capture the status - status=$? - elif [[ "$BOOT_FS_TYPE" == "xfs" ]]; then - # xfs_growfs requires the filesystem to be mounted in order to change its size - # Create a temporal directory - tmp_dir=$(/usr/bin/mktemp -d) - # Mount the boot filesystem in the temporal directory - /usr/bin/mount "$device" "$tmp_dir" - ret=$(/usr/sbin/xfs_growfs "$device" 2>&1) - # Capture the status - status=$? - # Unmount the filesystem - /usr/bin/umount "$device" - else - echo "Device $device does not contain an ext4 or xfs filesystem: $BOOT_FS_TYPE" - status=1 - fi - if [[ $status -ne 0 ]]; then - echo "Failed to resize boot partition '$device': $ret" - return - fi - echo "Boot filesystem increased by $INCREMENT_BOOT_PARTITION_SIZE" >&2 -} - -activate_volume_group() { - ret=$(/usr/sbin/lvm vgchange -ay "$LVM2_VG_NAME" 2>&1) - status=$? - if [[ $status -ne 0 ]]; then - echo "Failed to activate volume group $LVM2_VG_NAME: $ret" - exit $status - fi - # avoid potential deadlocks with udev rules before continuing - sleep 1 -} - -# last steps are to run the fsck on boot partition and activate the volume group if necessary -cleanup() { - # run a filesystem check to the boot filesystem - check_filesystem "${DEVICE_NAME}${PARTITION_PREFIX}${BOOT_PARTITION_NUMBER}" -} - -main() { - init_variables "$@" - check_device - shrink_adjacent_partition - shift_adjacent_partition - increase_boot_partition - cleanup -} -main "$@" +# Get input values +boot_part_name="$1" +next_part_name="$2" +boot_size_increase_in_bytes="$3" + +# Validate inputs +name="bigboot" +if [[ ! -b "/dev/$boot_part_name" ]]; then + echo "$name: Boot partition is not a block device: $boot_part_name" + exit 1 +fi +if [[ ! -b "/dev/$next_part_name" ]]; then + echo "$name: Next partition is not a block device: $next_part_name" + exit 1 +fi +if [[ ! $boot_size_increase_in_bytes -gt 0 ]]; then + echo "$name: Invalid size increase value: $boot_size_increase_in_bytes" + exit 1 +fi + +# Calculate device and partition details +boot_disk_device=/dev/"$(/usr/bin/basename "$(readlink -f /sys/class/block/"$boot_part_name"/..)")" +boot_part_num="$(&1); then + echo "$name: Failed shrinking partition $next_part_name: $ret" + exit 1 +fi + +# Output progress messages to help impatient operators recognize the server is not "hung" +( sleep 4 + while t="$(ps -C sfdisk -o cputime=)"; do + echo "$name: Partition move is progressing, please wait! ($t)" + sleep 120 + done ) & + +# Shift next partition +echo "$name: Moving up partition $next_part_name by $boot_size_increase_in_bytes" +if ! ret=$(echo "+$((boot_size_increase_in_bytes/512))," | /usr/sbin/sfdisk --move-data "$boot_disk_device" -N "$next_part_num" --force 2>&1); then + echo "$name: Failed moving up partition $next_part_name: $ret" + exit 1 +fi + +# Increase boot partition +echo "$name: Increasing boot partition $boot_part_name by $boot_size_increase_in_bytes" +if ! ret=$(echo "- +" | /usr/sbin/sfdisk "$boot_disk_device" -N "$boot_part_num" --no-reread --force 2>&1); then + echo "$name: Failed increasing boot partition $boot_part_name: $ret" + exit 1 +fi + +# Update kernel partition table +echo "$name: Updating kernel partition table" +[[ "$next_part_vg" ]] && /usr/sbin/lvm vgchange -an "$next_part_vg" && sleep 1 +/usr/sbin/partprobe "$boot_disk_device" && sleep 1 +[[ "$next_part_vg" ]] && /usr/sbin/lvm vgchange -ay "$next_part_vg" && sleep 1 + +# Grow the /boot filesystem +echo "$name: Growing the /boot $boot_fs_type filesystem" +if [[ "$boot_fs_type" =~ ^ext[2-4]$ ]]; then + /usr/sbin/e2fsck -fy "/dev/$boot_part_name" + if ! /usr/sbin/resize2fs "/dev/$boot_part_name"; then + echo "$name: resize2fs error while growing the /boot filesystem" + exit 1 + fi +fi +if [[ "$boot_fs_type" == "xfs" ]]; then + tmp_dir=$(/usr/bin/mktemp -d) + /usr/bin/mount -t xfs "/dev/$boot_part_name" "$tmp_dir" + /usr/sbin/xfs_growfs "/dev/$boot_part_name" + status=$? + /usr/bin/umount "/dev/$boot_part_name" + if [[ $status -ne 0 ]]; then + echo "$name: xfs_growfs error while growing the /boot filesystem" + exit 1 + fi +fi + +exit 0 diff --git a/roles/bigboot/files/module-setup.sh b/roles/bigboot/files/module-setup.sh index 1652ec0..2c1fe32 100644 --- a/roles/bigboot/files/module-setup.sh +++ b/roles/bigboot/files/module-setup.sh @@ -7,7 +7,7 @@ check(){ } install() { - inst_multiple -o /usr/bin/mount /usr/bin/umount /usr/sbin/parted /usr/bin/mktemp /usr/bin/wc /usr/bin/date /usr/bin/sed /usr/bin/awk /usr/bin/sort /usr/bin/basename /usr/sbin/resize2fs /usr/sbin/tune2fs /usr/sbin/partprobe /usr/bin/numfmt /usr/sbin/lvm /usr/bin/lsblk /usr/sbin/e2fsck /usr/sbin/fdisk /usr/bin/findmnt /usr/bin/tail /usr/ /usr/sbin/xfs_growfs /usr/sbin/xfs_db + inst_multiple -o /usr/bin/mount /usr/bin/umount /usr/sbin/parted /usr/bin/mktemp /usr/bin/date /usr/bin/basename /usr/sbin/resize2fs /usr/sbin/partprobe /usr/sbin/lvm /usr/sbin/blkid /usr/sbin/e2fsck /usr/sbin/xfs_growfs /usr/sbin/xfs_db # shellcheck disable=SC2154 inst_hook pre-mount 99 "$moddir/increase-boot-partition.sh" inst_binary "$moddir/sfdisk.static" "/usr/sbin/sfdisk" diff --git a/roles/bigboot/tasks/do_bigboot_reboot.yml b/roles/bigboot/tasks/do_bigboot_reboot.yml new file mode 100644 index 0000000..733a445 --- /dev/null +++ b/roles/bigboot/tasks/do_bigboot_reboot.yml @@ -0,0 +1,48 @@ +- name: Copy dracut pre-mount hook files + ansible.builtin.copy: + src: "{{ item }}" + dest: /usr/lib/dracut/modules.d/99extend_boot/ + mode: "0554" + loop: + - bigboot.sh + - module-setup.sh + - sfdisk.static + +- name: Resolve and copy pre-mount hook wrapper script + ansible.builtin.template: + src: increase-boot-partition.sh.j2 + dest: /usr/lib/dracut/modules.d/99extend_boot/increase-boot-partition.sh + mode: '0554' + +- name: Create the initramfs and reboot to run the module + vars: + initramfs_add_modules: "extend_boot" + ansible.builtin.include_role: + name: initramfs + +- name: Remove dracut extend boot module + ansible.builtin.file: + path: /usr/lib/dracut/modules.d/99extend_boot + state: absent + +- name: Retrieve mount points + ansible.builtin.setup: + gather_subset: + - "!all" + - "!min" + - mounts + +- name: Capture boot filesystem new size + ansible.builtin.set_fact: + bigboot_boot_fs_new_size: "{{ (ansible_facts.mounts | selectattr('mount', 'equalto', '/boot') | first).size_total | int }}" + +- name: Validate boot filesystem new size + ansible.builtin.assert: + that: + - bigboot_boot_fs_new_size != bigboot_boot_fs_original_size + fail_msg: >- + Boot filesystem size '{{ bigboot_boot_fs_new_size }}' did not change + success_msg: >- + Boot filesystem size is now + {{ bigboot_boot_fs_new_size | int | human_readable }} + ({{ (bigboot_boot_fs_new_size | int - bigboot_boot_fs_original_size | int) | human_readable }} increase) diff --git a/roles/bigboot/tasks/get_boot_device_info.yml b/roles/bigboot/tasks/get_boot_device_info.yml index e722a6f..e849788 100644 --- a/roles/bigboot/tasks/get_boot_device_info.yml +++ b/roles/bigboot/tasks/get_boot_device_info.yml @@ -1,6 +1,12 @@ - name: Find the boot mount entry ansible.builtin.set_fact: - bigboot_boot_mount_entry: "{{ ansible_facts.mounts | selectattr('mount', 'equalto', '/boot') | first }}" + bigboot_boot_mount_entry: "{{ ansible_facts.mounts | selectattr('mount', 'equalto', '/boot') | first | default('', true) }}" + +- name: Validate boot mount entry + ansible.builtin.assert: + that: + - bigboot_boot_mount_entry.device is defined + fail_msg: "No /boot mount point found." - name: Calculate the partition to look for ansible.builtin.set_fact: @@ -14,8 +20,6 @@ - name: Capture boot device details ansible.builtin.set_fact: - bigboot_boot_device_partition_prefix: "{{ bigboot_boot_partition_name[(bigboot_boot_disk | length) : -1] }}" - bigboot_boot_partition_number: "{{ bigboot_boot_partition_name[-1] }}" bigboot_boot_device_name: "/dev/{{ bigboot_boot_disk }}" bigboot_boot_fs_original_size: "{{ bigboot_boot_mount_entry.size_total | int }}" bigboot_boot_device_sectors: "{{ ansible_facts.devices[bigboot_boot_disk].partitions[bigboot_boot_partition_name].sectors | int }}" @@ -24,3 +28,30 @@ - name: Calculate boot device current size ansible.builtin.set_fact: bigboot_boot_device_bytes: "{{ bigboot_boot_device_sectors | int * bigboot_boot_device_sectorsize | int }}" + +- name: Find the next partition + ansible.builtin.set_fact: + bigboot_next_partition_name: "{{ ansible_loop.nextitem.0 | default(omit, true) }}" + when: item.0 == bigboot_boot_partition_name + loop: "{{ ansible_facts.devices[bigboot_boot_disk].partitions | dictsort }}" + loop_control: + extended: true + +- name: Validate next partition exists + ansible.builtin.assert: + that: + - bigboot_next_partition_name is defined + fail_msg: "There is no partition found after the /boot partition." + +- name: Find Btrfs or LVM + ansible.builtin.set_fact: + bigboot_next_partition_btrfs: "{{ ansible_facts.mounts | selectattr('device', 'equalto', '/dev/' + bigboot_next_partition_name) | + selectattr('fstype', 'equalto', 'btrfs') | map(attribute='mount') | first | default(omit, true) }}" + bigboot_next_partition_vg: "{{ ansible_facts.lvm.pvs['/dev/' + bigboot_next_partition_name].vg | default(omit, true) }}" + bigboot_next_partition_type_checked: true + +- name: Validate next partition type + ansible.builtin.assert: + that: + - bigboot_next_partition_btrfs is defined or bigboot_next_partition_vg is defined + fail_msg: "The partition after the /boot partition is neither LVM or Btrfs." diff --git a/roles/bigboot/tasks/main.yaml b/roles/bigboot/tasks/main.yaml index 5da1bf2..72f70c3 100644 --- a/roles/bigboot/tasks/main.yaml +++ b/roles/bigboot/tasks/main.yaml @@ -32,52 +32,25 @@ bigboot_boot_device_bytes | int + bigboot_size_bytes | default('0', true) | int }}" -- name: Do bigboot tasks if increase requested - when: bigboot_increase_bytes | int > 0 - block: - - name: Copy extend boot dracut module - ansible.builtin.copy: - src: "{{ item }}" - dest: /usr/lib/dracut/modules.d/99extend_boot/ - mode: "0554" - loop: - - bigboot.sh - - module-setup.sh - - sfdisk.static - - - name: Resolve and copy the shrink-start script - ansible.builtin.template: - src: increase-boot-partition.sh.j2 - dest: /usr/lib/dracut/modules.d/99extend_boot/increase-boot-partition.sh - mode: '0554' - - - name: Create the initramfs and reboot to run the module - vars: - initramfs_add_modules: "extend_boot" - ansible.builtin.include_role: - name: initramfs - - - name: Remove dracut extend boot module - ansible.builtin.file: - path: /usr/lib/dracut/modules.d/99extend_boot - state: absent - - - name: Retrieve mount points - ansible.builtin.setup: - gather_subset: - - "!all" - - "!min" - - mounts +- name: Prepare Btrfs for bigboot + ansible.builtin.include_tasks: + file: prep_btrfs.yml + when: + - bigboot_increase_bytes | int > 0 + - bigboot_next_partition_btrfs is defined - - name: Capture boot filesystem new size - ansible.builtin.set_fact: - bigboot_boot_fs_new_size: "{{ (ansible_facts.mounts | selectattr('mount', 'equalto', '/boot') | first).size_total | int }}" +- name: Prepare LVM for bigboot + ansible.builtin.include_tasks: + file: prep_lvm.yml + when: + - bigboot_increase_bytes | int > 0 + - bigboot_next_partition_vg is defined - - name: Validate boot filesystem new size - ansible.builtin.assert: - that: - - bigboot_boot_fs_new_size != bigboot_boot_fs_original_size - fail_msg: "Boot filesystem size '{{ bigboot_boot_fs_new_size }}' did not change" +- name: Configure pre-mount hook and reboot + ansible.builtin.include_tasks: + file: do_bigboot_reboot.yml + when: + - bigboot_increase_bytes | int > 0 - name: Validate increase requested ansible.builtin.debug: diff --git a/roles/bigboot/tasks/prep_btrfs.yml b/roles/bigboot/tasks/prep_btrfs.yml new file mode 100644 index 0000000..4bcdb4e --- /dev/null +++ b/roles/bigboot/tasks/prep_btrfs.yml @@ -0,0 +1,19 @@ +- name: Find Btrfs sector size + ansible.builtin.slurp: + src: "/sys/fs/btrfs/{{ ansible_facts.mounts | selectattr('mount', 'equalto', bigboot_next_partition_btrfs) | map(attribute='uuid') | first }}/sectorsize" + register: sectorsize + +- name: Align bigboot increase to sector size + ansible.builtin.set_fact: + bigboot_increase_bytes: "{{ bigboot_increase_bytes | int - (bigboot_increase_bytes | int % sectorsize.content | b64decode | int) }}" + +- name: Btrfs volume reduce + ansible.builtin.command: + cmd: >- + /usr/sbin/btrfs + filesystem resize + 1:-{{ bigboot_increase_bytes }} + {{ bigboot_next_partition_btrfs }} + when: bigboot_increase_bytes | int > 0 + changed_when: true + register: resize_cmd diff --git a/roles/bigboot/tasks/prep_lvm.yml b/roles/bigboot/tasks/prep_lvm.yml new file mode 100644 index 0000000..4e3e310 --- /dev/null +++ b/roles/bigboot/tasks/prep_lvm.yml @@ -0,0 +1,54 @@ +- name: Find physical volume size + ansible.builtin.command: + cmd: >- + /usr/sbin/lvm pvs + --noheadings --nosuffix --units b + -o pv_size /dev/{{ bigboot_next_partition_name }} + changed_when: false + register: pv_size + +- name: Find volume group extent size + ansible.builtin.command: + cmd: > + /usr/sbin/lvm vgs + --noheadings --nosuffix --units b + -o vg_extent_size {{ bigboot_next_partition_vg }} + changed_when: false + register: vg_extent_size + +- name: Align bigboot increase to extent size + ansible.builtin.set_fact: + bigboot_increase_bytes: "{{ bigboot_increase_bytes | int - (bigboot_increase_bytes | int % vg_extent_size.stdout | int) }}" + +- name: Test mode pvresize + ansible.builtin.command: + cmd: >- + /usr/sbin/lvm pvresize + --test --yes + --setphysicalvolumesize {{ pv_size.stdout | int - bigboot_increase_bytes | int }}B + /dev/{{ bigboot_next_partition_name }} + when: bigboot_increase_bytes | int > 0 + changed_when: false + failed_when: pvresize_test.rc not in [0, 5] + register: pvresize_test + +- name: Evict extents from end of physical volume + ansible.builtin.command: + cmd: >- + /usr/sbin/lvm pvmove + --alloc anywhere + /dev/{{ bigboot_next_partition_name }}:{{ (((pv_size.stdout | int - bigboot_increase_bytes | int) / vg_extent_size.stdout | int) - 1) | int }}- + when: pvresize_test.rc | default(0, true) == 5 + changed_when: true + register: pvmove + +- name: Real pvresize + ansible.builtin.command: + cmd: >- + /usr/sbin/lvm pvresize + --yes + --setphysicalvolumesize {{ pv_size.stdout | int - bigboot_increase_bytes | int }}B + /dev/{{ bigboot_next_partition_name }} + when: bigboot_increase_bytes | int > 0 + changed_when: true + register: pvresize_real diff --git a/roles/bigboot/templates/increase-boot-partition.sh.j2 b/roles/bigboot/templates/increase-boot-partition.sh.j2 index 1970589..cc1cb36 100644 --- a/roles/bigboot/templates/increase-boot-partition.sh.j2 +++ b/roles/bigboot/templates/increase-boot-partition.sh.j2 @@ -1,25 +1,17 @@ #!/bin/bash -activate_volume_groups(){ - for vg in `/usr/sbin/lvm vgs -o name --noheading 2>/dev/null`; do - /usr/sbin/lvm vgchange -ay $vg - done -} - main() { - name=$(basename "$0") start=$(/usr/bin/date +%s) - activate_volume_groups # run bigboot.sh to increase boot partition and file system size - ret=$(sh /usr/bin/bigboot.sh -d="{{ bigboot_boot_device_name }}" -s="{{ bigboot_increase_bytes }}" -b="{{ bigboot_boot_partition_number }}" -p="{{ bigboot_boot_device_partition_prefix }}" 2>/dev/kmsg) + sh /usr/bin/bigboot.sh "{{ bigboot_boot_partition_name }}" "{{ bigboot_next_partition_name }}" "{{ bigboot_increase_bytes }}" status=$? end=$(/usr/bin/date +%s) # write the log file if [[ $status -eq 0 ]]; then - echo "[$name] Boot partition {{ bigboot_boot_device_name }} successfully increased by {{ bigboot_increase_bytes }} ("$((end-start))" seconds) " >/dev/kmsg + echo "Boot partition {{ bigboot_boot_partition_name }} successfully increased by {{ bigboot_increase_bytes }} ("$((end-start))" seconds)" else - echo "[$name] Failed to extend boot partition: $ret ("$((end-start))" seconds)" >/dev/kmsg + echo "Failed to extend boot partition ("$((end-start))" seconds)" fi } -main "$0" +main "$0" >&2