Skip to content

Commit 318e3f8

Browse files
balcsidaclaude
andcommitted
feat(linux): add LVM support to guest additions
- Add LVM detection and resize support (pvresize, lvextend) - Support LVM on LUKS (Fedora Workstation default layout) - Update README with LVM documentation and troubleshooting - Bump version to 1.1.0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 41bebd2 commit 318e3f8

File tree

3 files changed

+192
-19
lines changed

3 files changed

+192
-19
lines changed

LinuxGuestAdditions/README.md

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ When you resize a disk in VirtualBuddy, the guest additions will automatically e
77
## Features
88

99
- **Automatic partition resize** using `growpart`
10+
- **LVM support** - automatically extends physical volumes and logical volumes
1011
- **LUKS support** - automatically resizes encrypted containers
12+
- **LVM on LUKS** - full support for Fedora Workstation's default layout
1113
- **Multiple filesystems** - supports ext4, XFS, and Btrfs
1214
- **Safe operation** - only runs when free space is detected
1315

@@ -104,23 +106,38 @@ journalctl -u virtualbuddy-growfs
104106

105107
## How It Works
106108

107-
1. **Detect root device** - Finds the root filesystem mount
108-
2. **Check for LUKS** - Detects if root is on an encrypted volume
109-
3. **Find free space** - Checks if partition can be grown
110-
4. **Grow partition** - Uses `growpart` to extend the GPT partition
111-
5. **Resize LUKS** - If encrypted, runs `cryptsetup resize`
109+
1. **Detect storage stack** - Walks from root filesystem back through LVM, LUKS, to the partition
110+
2. **Find free space** - Checks if partition can be grown
111+
3. **Grow partition** - Uses `growpart` to extend the GPT partition
112+
4. **Resize LUKS** - If encrypted, runs `cryptsetup resize`
113+
5. **Resize LVM** - If using LVM:
114+
- `pvresize` to extend the physical volume
115+
- `lvextend` to extend the logical volume
112116
6. **Resize filesystem** - Runs the appropriate tool:
113117
- ext4: `resize2fs`
114118
- XFS: `xfs_growfs`
115119
- Btrfs: `btrfs filesystem resize max`
116120

121+
## LVM Support
122+
123+
For distributions using LVM (with or without encryption), the guest additions automatically handle:
124+
125+
1. Extending the physical volume (`pvresize`)
126+
2. Extending the logical volume (`lvextend -l +100%FREE`)
127+
3. Resizing the filesystem
128+
129+
This works for both:
130+
- **LVM on partition** - direct partition → LVM → filesystem
131+
- **LVM on LUKS** - partition → LUKS → LVM → filesystem (Fedora Workstation default)
132+
117133
## LUKS Encrypted Disks
118134

119135
For LUKS-encrypted root partitions (common with Fedora Workstation), the guest additions will:
120136

121137
1. Grow the GPT partition containing LUKS
122138
2. Run `cryptsetup resize` to expand the LUKS container
123-
3. Resize the inner filesystem
139+
3. If LVM is on top of LUKS, extend PV and LV
140+
4. Resize the inner filesystem
124141

125142
No manual intervention required!
126143

@@ -177,6 +194,43 @@ sudo apt install xfsprogs # or dnf install xfsprogs
177194
sudo apt install btrfs-progs # or dnf install btrfs-progs
178195
```
179196

197+
### LVM not detected
198+
199+
Ensure LVM tools are installed:
200+
201+
```bash
202+
# Fedora/RHEL
203+
sudo dnf install lvm2
204+
205+
# Ubuntu/Debian
206+
sudo apt install lvm2
207+
```
208+
209+
Check your storage stack:
210+
211+
```bash
212+
# View LVM layout
213+
sudo lsblk
214+
sudo lvs
215+
sudo pvs
216+
sudo vgs
217+
```
218+
219+
### LV not extending
220+
221+
If the logical volume isn't growing, check for free space in the volume group:
222+
223+
```bash
224+
sudo vgs
225+
```
226+
227+
If `VFree` is 0, the physical volume may not have been resized. Try running manually:
228+
229+
```bash
230+
sudo pvresize /dev/mapper/luks-xxx # or your PV device
231+
sudo lvextend -l +100%FREE /dev/mapper/fedora-root
232+
```
233+
180234
## License
181235

182236
MIT License - Same as VirtualBuddy

LinuxGuestAdditions/install.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
set -euo pipefail
1212

1313
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14-
VERSION="1.0.0"
14+
VERSION="1.1.0"
1515

1616
log() {
1717
echo "[virtualbuddy-install] $*"

LinuxGuestAdditions/virtualbuddy-growfs

Lines changed: 131 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
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

1617
set -euo pipefail
1718

18-
VERSION="1.0.0"
19+
VERSION="1.1.0"
1920
DRY_RUN=false
2021
VERBOSE=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)
121195
get_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

Comments
 (0)