From c864e36208ab59b2a2a9368cd53a38849c4144e6 Mon Sep 17 00:00:00 2001 From: Selby Mashiki Date: Sun, 25 Oct 2026 19:18:46 +0200 Subject: [PATCH] fix: allocate full VG size to root LV when using Btrfs subvolumes on LVM When Btrfs with default subvolumes is selected in suggest_lvm_layout(), home_volume is set to False because @home is created as a subvolume on the root logical volume. However, suggest_lvm_layout() unconditionally sized root_vol using process_root_partition_size() (capped at 20-50 GiB) and discarded home_vol_size, leaving the rest of the volume group unallocated. It also set mountpoint=Path('/') on root_vol even when Btrfs subvolumes were used, and DiskLayoutConfiguration.has_default_btrfs_vols() did not check LVM volumes. When home_volume is False, allocate total_vol_available to root_vol, set mountpoint=None when using_subvolumes is True (matching suggest_single_disk_layout()), and check LVM volumes in has_default_btrfs_vols(). --- archinstall/lib/disk/default_layouts.py | 11 ++- archinstall/lib/models/device.py | 8 +++ tests/test_lvm_btrfs_layout.py | 90 +++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 tests/test_lvm_btrfs_layout.py diff --git a/archinstall/lib/disk/default_layouts.py b/archinstall/lib/disk/default_layouts.py index 2df92982fb..09aa88eb5f 100644 --- a/archinstall/lib/disk/default_layouts.py +++ b/archinstall/lib/disk/default_layouts.py @@ -397,8 +397,13 @@ async def suggest_lvm_layout( (p.length for p in other_part), Size(0, Unit.B, SectorSize.default()), ) - root_vol_size = process_root_partition_size(total_vol_available, SectorSize.default()) - home_vol_size = total_vol_available - root_vol_size + + if home_volume: + root_vol_size = process_root_partition_size(total_vol_available, SectorSize.default()) + home_vol_size = total_vol_available - root_vol_size + else: + root_vol_size = total_vol_available + home_vol_size = Size(0, Unit.B, SectorSize.default()) lvm_vol_group = LvmVolumeGroup(vg_grp_name, pvs=other_part) @@ -407,7 +412,7 @@ async def suggest_lvm_layout( name='root', fs_type=filesystem_type, length=root_vol_size, - mountpoint=Path('/'), + mountpoint=Path('/') if not using_subvolumes else None, btrfs_subvols=btrfs_subvols, mount_options=mount_options, ) diff --git a/archinstall/lib/models/device.py b/archinstall/lib/models/device.py index b4c069e659..8d5040e2ab 100644 --- a/archinstall/lib/models/device.py +++ b/archinstall/lib/models/device.py @@ -262,6 +262,14 @@ def has_default_btrfs_vols(self) -> bool: if any(subvol.is_default_root() for subvol in part.btrfs_subvols): return True + if self.lvm_config: + for vol in self.lvm_config.get_all_volumes(): + if not (vol.status in [ModificationStatus.CREATE, ModificationStatus.MODIFY] and vol.fs_type == FilesystemType.BTRFS): + continue + + if any(subvol.is_default_root() for subvol in vol.btrfs_subvols): + return True + return False diff --git a/tests/test_lvm_btrfs_layout.py b/tests/test_lvm_btrfs_layout.py new file mode 100644 index 0000000000..c1e88e4656 --- /dev/null +++ b/tests/test_lvm_btrfs_layout.py @@ -0,0 +1,90 @@ +import asyncio +from pathlib import Path +from unittest.mock import MagicMock + +from pytest import MonkeyPatch + +from archinstall.lib.disk.default_layouts import suggest_lvm_layout +from archinstall.lib.menu.abstract_menu import MenuItem +from archinstall.lib.models.device import ( + BDevice, + DeviceModification, + DiskLayoutConfiguration, + DiskLayoutType, + FilesystemType, + ModificationStatus, + PartitionFlag, + PartitionModification, + PartitionType, + SectorSize, + Size, + Unit, +) + + +def test_suggest_lvm_layout_with_btrfs_subvolumes_uses_full_vg_size( + monkeypatch: MonkeyPatch, +) -> None: + sector_size = SectorSize.default() + mock_bdevice = MagicMock(spec=BDevice) + mock_bdevice.device_info = MagicMock(path=Path('/dev/sda')) + + boot_part = PartitionModification( + status=ModificationStatus.CREATE, + type=PartitionType.PRIMARY, + start=Size(1, Unit.MiB, sector_size), + length=Size(1, Unit.GiB, sector_size), + fs_type=FilesystemType.FAT32, + mountpoint=Path('/boot'), + flags=[PartitionFlag.BOOT, PartitionFlag.ESP], + ) + pv_part = PartitionModification( + status=ModificationStatus.CREATE, + type=PartitionType.PRIMARY, + start=Size(1025, Unit.MiB, sector_size), + length=Size(500, Unit.GiB, sector_size), + fs_type=FilesystemType.EXT4, + ) + + dev_mod = DeviceModification( + device=mock_bdevice, + wipe=True, + partitions=[boot_part, pv_part], + ) + disk_config = DiskLayoutConfiguration( + config_type=DiskLayoutType.Default, + device_modifications=[dev_mod], + ) + + class FakeConfirmation: + def __init__(self, **_kwargs: object) -> None: + pass + + async def show(self) -> MagicMock: + res = MagicMock() + res.item.return_value = MenuItem.yes() + return res + + async def fake_select_mount_options() -> list[str]: + return ['compress=zstd'] + + monkeypatch.setattr('archinstall.lib.disk.default_layouts.Confirmation', FakeConfirmation) + monkeypatch.setattr('archinstall.lib.disk.default_layouts.select_mount_options', fake_select_mount_options) + + lvm_config = asyncio.run( + suggest_lvm_layout( + disk_config, + filesystem_type=FilesystemType.BTRFS, + ) + ) + + disk_config.lvm_config = lvm_config + + volumes = lvm_config.get_all_volumes() + assert len(volumes) == 1 + root_vol = volumes[0] + assert root_vol.name == 'root' + assert root_vol.length == Size(500, Unit.GiB, sector_size) + assert root_vol.mountpoint is None + assert len(root_vol.btrfs_subvols) > 0 + assert disk_config.has_default_btrfs_vols() is True