88# Supports:
99# - Unencrypted ext4/xfs/btrfs partitions
1010# - LUKS-encrypted partitions (with ext4/xfs/btrfs inside)
11+ # - LVM logical volumes (with or without LUKS)
1112# - GPT partition tables
1213#
1314# Usage: virtualbuddy-growfs [--dry-run] [--verbose]
1415#
1516
1617set -euo pipefail
1718
18- VERSION=" 1.0 .0"
19+ VERSION=" 1.1 .0"
1920DRY_RUN=false
2021VERBOSE=false
2122
@@ -90,8 +91,8 @@ is_luks() {
9091 cryptsetup isLuks " $device " 2> /dev/null
9192}
9293
93- # Get the underlying partition for a LUKS device
94- get_luks_backing_device () {
94+ # Get the underlying device for a device-mapper device (LUKS or LVM)
95+ get_dm_backing_device () {
9596 local mapper_name=" $1 "
9697 # Get the slave device from sysfs
9798 local dm_name
@@ -117,6 +118,79 @@ get_luks_backing_device() {
117118 return 1
118119}
119120
121+ # Alias for backward compatibility
122+ get_luks_backing_device () {
123+ get_dm_backing_device " $1 "
124+ }
125+
126+ # Check if device is an LVM logical volume
127+ is_lvm_lv () {
128+ local device=" $1 "
129+ if command -v lvs & > /dev/null; then
130+ lvs " $device " & > /dev/null
131+ return $?
132+ fi
133+ return 1
134+ }
135+
136+ # Get LVM volume group name from logical volume
137+ get_lvm_vg () {
138+ local lv_device=" $1 "
139+ lvs --noheadings -o vg_name " $lv_device " 2> /dev/null | tr -d ' '
140+ }
141+
142+ # Get LVM logical volume name
143+ get_lvm_lv_name () {
144+ local lv_device=" $1 "
145+ lvs --noheadings -o lv_name " $lv_device " 2> /dev/null | tr -d ' '
146+ }
147+
148+ # Get physical volume device(s) for a volume group
149+ get_lvm_pv () {
150+ local vg_name=" $1 "
151+ # Get the first PV (most common case is single PV)
152+ pvs --noheadings -o pv_name -S " vg_name=$vg_name " 2> /dev/null | head -1 | tr -d ' '
153+ }
154+
155+ # Resize LVM physical volume
156+ resize_lvm_pv () {
157+ local pv_device=" $1 "
158+
159+ log " Resizing LVM physical volume $pv_device ..."
160+
161+ if $DRY_RUN ; then
162+ log " [DRY-RUN] Would run: pvresize $pv_device "
163+ return 0
164+ fi
165+
166+ pvresize " $pv_device "
167+ }
168+
169+ # Extend LVM logical volume to use all free space
170+ resize_lvm_lv () {
171+ local lv_device=" $1 "
172+
173+ log " Extending LVM logical volume $lv_device ..."
174+
175+ if $DRY_RUN ; then
176+ log " [DRY-RUN] Would run: lvextend -l +100%FREE $lv_device "
177+ return 0
178+ fi
179+
180+ # Check if there's free space in the VG first
181+ local vg_name
182+ vg_name=$( get_lvm_vg " $lv_device " )
183+ local free_extents
184+ free_extents=$( vgs --noheadings -o vg_free_count " $vg_name " 2> /dev/null | tr -d ' ' )
185+
186+ if [[ -z " $free_extents " ]] || [[ " $free_extents " -eq 0 ]]; then
187+ log_verbose " No free extents in VG $vg_name , skipping LV extend"
188+ return 0
189+ fi
190+
191+ lvextend -l +100%FREE " $lv_device "
192+ }
193+
120194# Get the disk device from a partition (e.g., /dev/vda2 -> /dev/vda)
121195get_disk_from_partition () {
122196 local partition=" $1 "
@@ -250,20 +324,54 @@ main() {
250324 log_verbose " Root device: $root_device "
251325
252326 local fs_device=" $root_device "
327+ local lv_device=" "
328+ local pv_device=" "
253329 local luks_mapper_name=" "
254330 local partition_device=" "
255331
256- # Check if root is on a LUKS device
257- if [[ " $root_device " == /dev/mapper/* ]]; then
258- luks_mapper_name=$( basename " $root_device " )
259- log_verbose " Detected LUKS mapper: $luks_mapper_name "
332+ # Detect storage stack: partition -> [LUKS] -> [LVM] -> filesystem
333+ # We need to walk backwards from the filesystem to find the partition
260334
261- partition_device=$( get_luks_backing_device " $luks_mapper_name " )
262- if [[ -z " $partition_device " ]]; then
263- die " Could not determine backing device for LUKS container"
335+ if [[ " $root_device " == /dev/mapper/* ]]; then
336+ # Root is on a device-mapper device (could be LVM, LUKS, or both)
337+
338+ # Check if it's an LVM logical volume
339+ if is_lvm_lv " $root_device " ; then
340+ lv_device=" $root_device "
341+ local vg_name
342+ vg_name=$( get_lvm_vg " $lv_device " )
343+ log_verbose " Detected LVM: LV=$lv_device , VG=$vg_name "
344+
345+ # Find the physical volume
346+ pv_device=$( get_lvm_pv " $vg_name " )
347+ if [[ -z " $pv_device " ]]; then
348+ die " Could not determine physical volume for VG $vg_name "
349+ fi
350+ log_verbose " LVM physical volume: $pv_device "
351+
352+ # Check if PV is on LUKS
353+ if [[ " $pv_device " == /dev/mapper/* ]]; then
354+ luks_mapper_name=$( basename " $pv_device " )
355+ partition_device=$( get_dm_backing_device " $luks_mapper_name " )
356+ if [[ -z " $partition_device " ]]; then
357+ die " Could not determine backing device for LUKS container"
358+ fi
359+ log_verbose " LUKS container: $luks_mapper_name , backing partition: $partition_device "
360+ else
361+ # PV is directly on a partition (no LUKS)
362+ partition_device=" $pv_device "
363+ fi
364+ else
365+ # Not LVM, assume it's LUKS directly on a partition
366+ luks_mapper_name=$( basename " $root_device " )
367+ partition_device=$( get_dm_backing_device " $luks_mapper_name " )
368+ if [[ -z " $partition_device " ]]; then
369+ die " Could not determine backing device for LUKS container"
370+ fi
371+ log_verbose " LUKS container: $luks_mapper_name , backing partition: $partition_device "
264372 fi
265- log_verbose " LUKS backing partition: $partition_device "
266373 else
374+ # Root is directly on a partition (no LUKS, no LVM)
267375 partition_device=" $root_device "
268376 fi
269377
@@ -296,7 +404,18 @@ main() {
296404 resize_luks " $partition_device " " $luks_mapper_name "
297405 fi
298406
299- # Step 3: Resize the filesystem
407+ # Step 3: If LVM, resize PV and extend LV
408+ if [[ -n " $lv_device " ]]; then
409+ # Resize the physical volume
410+ if [[ -n " $pv_device " ]]; then
411+ resize_lvm_pv " $pv_device "
412+ fi
413+
414+ # Extend the logical volume
415+ resize_lvm_lv " $lv_device "
416+ fi
417+
418+ # Step 4: Resize the filesystem
300419 local fs_type
301420 fs_type=$( detect_fs_type " $fs_device " )
302421 log_verbose " Filesystem type: $fs_type "
0 commit comments