Refactor interactive module (#4315)
* Refactor interactive module * Refactor interactive module * Update
This commit is contained in:
parent
1764b4971d
commit
e763c1109c
|
|
@ -1,22 +1,42 @@
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
from typing import override
|
from typing import override
|
||||||
|
|
||||||
|
from archinstall.lib.disk.device_handler import device_handler
|
||||||
from archinstall.lib.disk.encryption_menu import DiskEncryptionMenu
|
from archinstall.lib.disk.encryption_menu import DiskEncryptionMenu
|
||||||
from archinstall.lib.interactions.disk_conf import select_disk_config, select_lvm_config
|
from archinstall.lib.disk.partitioning_menu import manual_partitioning
|
||||||
from archinstall.lib.menu.abstract_menu import AbstractSubMenu
|
from archinstall.lib.menu.abstract_menu import AbstractSubMenu
|
||||||
from archinstall.lib.menu.helpers import Selection
|
from archinstall.lib.menu.helpers import Confirmation, Notify, Selection, Table
|
||||||
|
from archinstall.lib.menu.util import prompt_dir
|
||||||
from archinstall.lib.models.device import (
|
from archinstall.lib.models.device import (
|
||||||
DEFAULT_ITER_TIME,
|
DEFAULT_ITER_TIME,
|
||||||
|
BDevice,
|
||||||
|
BtrfsMountOption,
|
||||||
BtrfsOptions,
|
BtrfsOptions,
|
||||||
|
DeviceModification,
|
||||||
DiskEncryption,
|
DiskEncryption,
|
||||||
DiskLayoutConfiguration,
|
DiskLayoutConfiguration,
|
||||||
DiskLayoutType,
|
DiskLayoutType,
|
||||||
EncryptionType,
|
EncryptionType,
|
||||||
|
FilesystemType,
|
||||||
LvmConfiguration,
|
LvmConfiguration,
|
||||||
|
LvmLayoutType,
|
||||||
|
LvmVolume,
|
||||||
|
LvmVolumeGroup,
|
||||||
|
LvmVolumeStatus,
|
||||||
|
ModificationStatus,
|
||||||
|
PartitionFlag,
|
||||||
|
PartitionModification,
|
||||||
|
PartitionType,
|
||||||
|
SectorSize,
|
||||||
|
Size,
|
||||||
SnapshotConfig,
|
SnapshotConfig,
|
||||||
SnapshotType,
|
SnapshotType,
|
||||||
|
SubvolumeModification,
|
||||||
|
Unit,
|
||||||
|
_DeviceInfo,
|
||||||
)
|
)
|
||||||
from archinstall.lib.output import FormattedOutput
|
from archinstall.lib.output import FormattedOutput, debug
|
||||||
from archinstall.lib.translationhandler import tr
|
from archinstall.lib.translationhandler import tr
|
||||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||||
from archinstall.tui.ui.result import ResultType
|
from archinstall.tui.ui.result import ResultType
|
||||||
|
|
@ -275,3 +295,582 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskMenuConfig]):
|
||||||
return output
|
return output
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def select_devices(preset: list[BDevice] | None = []) -> list[BDevice] | None:
|
||||||
|
def _preview_device_selection(item: MenuItem) -> str | None:
|
||||||
|
device: _DeviceInfo = item.value # type: ignore[assignment]
|
||||||
|
dev = device_handler.get_device(device.path)
|
||||||
|
|
||||||
|
if dev and dev.partition_infos:
|
||||||
|
return FormattedOutput.as_table(dev.partition_infos)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if preset is None:
|
||||||
|
preset = []
|
||||||
|
|
||||||
|
devices = device_handler.devices
|
||||||
|
|
||||||
|
items = [
|
||||||
|
MenuItem(
|
||||||
|
str(d.device_info.path),
|
||||||
|
d.device_info,
|
||||||
|
preview_action=_preview_device_selection,
|
||||||
|
)
|
||||||
|
for d in devices
|
||||||
|
]
|
||||||
|
|
||||||
|
presets = [p.device_info for p in preset]
|
||||||
|
|
||||||
|
group = MenuItemGroup(items)
|
||||||
|
group.set_selected_by_value(presets)
|
||||||
|
|
||||||
|
result = await Table[_DeviceInfo](
|
||||||
|
header=tr('Select disks for the installation'),
|
||||||
|
group=group,
|
||||||
|
presets=presets,
|
||||||
|
allow_skip=True,
|
||||||
|
multi=True,
|
||||||
|
preview_location='bottom',
|
||||||
|
preview_header=tr('Partitions'),
|
||||||
|
).show()
|
||||||
|
|
||||||
|
match result.type_:
|
||||||
|
case ResultType.Reset:
|
||||||
|
return None
|
||||||
|
case ResultType.Skip:
|
||||||
|
return None
|
||||||
|
case ResultType.Selection:
|
||||||
|
selected_device_info = result.get_values()
|
||||||
|
selected_devices = []
|
||||||
|
|
||||||
|
for device in devices:
|
||||||
|
if device.device_info in selected_device_info:
|
||||||
|
selected_devices.append(device)
|
||||||
|
|
||||||
|
return selected_devices
|
||||||
|
|
||||||
|
|
||||||
|
async def get_default_partition_layout(
|
||||||
|
devices: list[BDevice],
|
||||||
|
filesystem_type: FilesystemType | None = None,
|
||||||
|
) -> list[DeviceModification]:
|
||||||
|
if len(devices) == 1:
|
||||||
|
device_modification = await suggest_single_disk_layout(
|
||||||
|
devices[0],
|
||||||
|
filesystem_type=filesystem_type,
|
||||||
|
)
|
||||||
|
return [device_modification]
|
||||||
|
else:
|
||||||
|
return await suggest_multi_disk_layout(
|
||||||
|
devices,
|
||||||
|
filesystem_type=filesystem_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _manual_partitioning(
|
||||||
|
preset: list[DeviceModification],
|
||||||
|
devices: list[BDevice],
|
||||||
|
) -> list[DeviceModification] | None:
|
||||||
|
modifications: list[DeviceModification] = []
|
||||||
|
|
||||||
|
for device in devices:
|
||||||
|
mod = next(filter(lambda x: x.device == device, preset), None)
|
||||||
|
if not mod:
|
||||||
|
mod = DeviceModification(device, wipe=False)
|
||||||
|
|
||||||
|
device_mod = await manual_partitioning(mod, device_handler.partition_table)
|
||||||
|
|
||||||
|
if not device_mod:
|
||||||
|
return None
|
||||||
|
|
||||||
|
modifications.append(device_mod)
|
||||||
|
|
||||||
|
return modifications
|
||||||
|
|
||||||
|
|
||||||
|
async def select_disk_config(preset: DiskLayoutConfiguration | None = None) -> DiskLayoutConfiguration | None:
|
||||||
|
default_layout = DiskLayoutType.Default.display_msg()
|
||||||
|
manual_mode = DiskLayoutType.Manual.display_msg()
|
||||||
|
pre_mount_mode = DiskLayoutType.Pre_mount.display_msg()
|
||||||
|
|
||||||
|
items = [
|
||||||
|
MenuItem(default_layout, value=default_layout),
|
||||||
|
MenuItem(manual_mode, value=manual_mode),
|
||||||
|
MenuItem(pre_mount_mode, value=pre_mount_mode),
|
||||||
|
]
|
||||||
|
group = MenuItemGroup(items, sort_items=False)
|
||||||
|
|
||||||
|
if preset:
|
||||||
|
group.set_selected_by_value(preset.config_type.display_msg())
|
||||||
|
|
||||||
|
result = await Selection[str](
|
||||||
|
group,
|
||||||
|
header=tr('Select a disk configuration'),
|
||||||
|
allow_skip=True,
|
||||||
|
allow_reset=True,
|
||||||
|
).show()
|
||||||
|
|
||||||
|
match result.type_:
|
||||||
|
case ResultType.Skip:
|
||||||
|
return preset
|
||||||
|
case ResultType.Reset:
|
||||||
|
return None
|
||||||
|
case ResultType.Selection:
|
||||||
|
selection = result.get_value()
|
||||||
|
|
||||||
|
if selection == pre_mount_mode:
|
||||||
|
output = tr('Enter root mount directory') + '\n\n'
|
||||||
|
output += tr('You will use whatever drive-setup is mounted at the specified directory') + '\n'
|
||||||
|
output += tr("WARNING: Archinstall won't check the suitability of this setup")
|
||||||
|
|
||||||
|
path = await prompt_dir(output, allow_skip=True)
|
||||||
|
|
||||||
|
if path is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
mods = device_handler.detect_pre_mounted_mods(path)
|
||||||
|
|
||||||
|
return DiskLayoutConfiguration(
|
||||||
|
config_type=DiskLayoutType.Pre_mount,
|
||||||
|
device_modifications=mods,
|
||||||
|
mountpoint=path,
|
||||||
|
)
|
||||||
|
|
||||||
|
preset_devices = [mod.device for mod in preset.device_modifications] if preset else []
|
||||||
|
devices = await select_devices(preset_devices)
|
||||||
|
|
||||||
|
if devices is None:
|
||||||
|
return preset
|
||||||
|
|
||||||
|
if result.get_value() == default_layout:
|
||||||
|
modifications = await get_default_partition_layout(devices)
|
||||||
|
if modifications:
|
||||||
|
return DiskLayoutConfiguration(
|
||||||
|
config_type=DiskLayoutType.Default,
|
||||||
|
device_modifications=modifications,
|
||||||
|
)
|
||||||
|
elif result.get_value() == manual_mode:
|
||||||
|
preset_mods = preset.device_modifications if preset else []
|
||||||
|
partitions = await _manual_partitioning(preset_mods, devices)
|
||||||
|
|
||||||
|
if not partitions:
|
||||||
|
return preset
|
||||||
|
|
||||||
|
return DiskLayoutConfiguration(
|
||||||
|
config_type=DiskLayoutType.Manual,
|
||||||
|
device_modifications=partitions,
|
||||||
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def select_lvm_config(
|
||||||
|
disk_config: DiskLayoutConfiguration,
|
||||||
|
preset: LvmConfiguration | None = None,
|
||||||
|
) -> LvmConfiguration | None:
|
||||||
|
preset_value = preset.config_type.display_msg() if preset else None
|
||||||
|
default_mode = LvmLayoutType.Default.display_msg()
|
||||||
|
|
||||||
|
items = [MenuItem(default_mode, value=default_mode)]
|
||||||
|
group = MenuItemGroup(items)
|
||||||
|
group.set_focus_by_value(preset_value)
|
||||||
|
|
||||||
|
result = await Selection[str](
|
||||||
|
group,
|
||||||
|
allow_reset=True,
|
||||||
|
allow_skip=True,
|
||||||
|
).show()
|
||||||
|
|
||||||
|
match result.type_:
|
||||||
|
case ResultType.Skip:
|
||||||
|
return preset
|
||||||
|
case ResultType.Reset:
|
||||||
|
return None
|
||||||
|
case ResultType.Selection:
|
||||||
|
if result.get_value() == default_mode:
|
||||||
|
return await suggest_lvm_layout(disk_config)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _boot_partition(sector_size: SectorSize, using_gpt: bool) -> PartitionModification:
|
||||||
|
flags = [PartitionFlag.BOOT]
|
||||||
|
size = Size(1, Unit.GiB, sector_size)
|
||||||
|
start = Size(1, Unit.MiB, sector_size)
|
||||||
|
if using_gpt:
|
||||||
|
flags.append(PartitionFlag.ESP)
|
||||||
|
|
||||||
|
# boot partition
|
||||||
|
return PartitionModification(
|
||||||
|
status=ModificationStatus.Create,
|
||||||
|
type=PartitionType.Primary,
|
||||||
|
start=start,
|
||||||
|
length=size,
|
||||||
|
mountpoint=Path('/boot'),
|
||||||
|
fs_type=FilesystemType.Fat32,
|
||||||
|
flags=flags,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def select_main_filesystem_format() -> FilesystemType:
|
||||||
|
items = [
|
||||||
|
MenuItem('btrfs', value=FilesystemType.Btrfs),
|
||||||
|
MenuItem('ext4', value=FilesystemType.Ext4),
|
||||||
|
MenuItem('xfs', value=FilesystemType.Xfs),
|
||||||
|
MenuItem('f2fs', value=FilesystemType.F2fs),
|
||||||
|
]
|
||||||
|
|
||||||
|
group = MenuItemGroup(items, sort_items=False)
|
||||||
|
result = await Selection[FilesystemType](
|
||||||
|
group,
|
||||||
|
header=tr('Select main filesystem'),
|
||||||
|
allow_skip=False,
|
||||||
|
).show()
|
||||||
|
|
||||||
|
match result.type_:
|
||||||
|
case ResultType.Selection:
|
||||||
|
return result.get_value()
|
||||||
|
case _:
|
||||||
|
raise ValueError('Unhandled result type')
|
||||||
|
|
||||||
|
|
||||||
|
async def select_mount_options() -> list[str]:
|
||||||
|
prompt = tr('Would you like to use compression or disable CoW?') + '\n'
|
||||||
|
compression = tr('Use compression')
|
||||||
|
disable_cow = tr('Disable Copy-on-Write')
|
||||||
|
|
||||||
|
items = [
|
||||||
|
MenuItem(compression, value=BtrfsMountOption.compress.value),
|
||||||
|
MenuItem(disable_cow, value=BtrfsMountOption.nodatacow.value),
|
||||||
|
]
|
||||||
|
group = MenuItemGroup(items, sort_items=False)
|
||||||
|
|
||||||
|
result = await Selection[str](
|
||||||
|
group,
|
||||||
|
header=prompt,
|
||||||
|
allow_skip=True,
|
||||||
|
).show()
|
||||||
|
|
||||||
|
match result.type_:
|
||||||
|
case ResultType.Skip:
|
||||||
|
return []
|
||||||
|
case ResultType.Selection:
|
||||||
|
return [result.get_value()]
|
||||||
|
case _:
|
||||||
|
raise ValueError('Unhandled result type')
|
||||||
|
|
||||||
|
|
||||||
|
def process_root_partition_size(total_size: Size, sector_size: SectorSize) -> Size:
|
||||||
|
# root partition size processing
|
||||||
|
total_device_size = total_size.convert(Unit.GiB)
|
||||||
|
if total_device_size.value > 500:
|
||||||
|
# maximum size
|
||||||
|
return Size(value=50, unit=Unit.GiB, sector_size=sector_size)
|
||||||
|
elif total_device_size.value < 320:
|
||||||
|
# minimum size
|
||||||
|
return Size(value=32, unit=Unit.GiB, sector_size=sector_size)
|
||||||
|
else:
|
||||||
|
# 10% of total size
|
||||||
|
length = total_device_size.value // 10
|
||||||
|
return Size(value=length, unit=Unit.GiB, sector_size=sector_size)
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_btrfs_subvols() -> list[SubvolumeModification]:
|
||||||
|
# https://btrfs.wiki.kernel.org/index.php/FAQ
|
||||||
|
# https://unix.stackexchange.com/questions/246976/btrfs-subvolume-uuid-clash
|
||||||
|
# https://github.com/classy-giraffe/easy-arch/blob/main/easy-arch.sh
|
||||||
|
return [
|
||||||
|
SubvolumeModification(Path('@'), Path('/')),
|
||||||
|
SubvolumeModification(Path('@home'), Path('/home')),
|
||||||
|
SubvolumeModification(Path('@log'), Path('/var/log')),
|
||||||
|
SubvolumeModification(Path('@pkg'), Path('/var/cache/pacman/pkg')),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def suggest_single_disk_layout(
|
||||||
|
device: BDevice,
|
||||||
|
filesystem_type: FilesystemType | None = None,
|
||||||
|
separate_home: bool | None = None,
|
||||||
|
) -> DeviceModification:
|
||||||
|
if not filesystem_type:
|
||||||
|
filesystem_type = await select_main_filesystem_format()
|
||||||
|
|
||||||
|
sector_size = device.device_info.sector_size
|
||||||
|
total_size = device.device_info.total_size
|
||||||
|
available_space = total_size
|
||||||
|
min_size_to_allow_home_part = Size(64, Unit.GiB, sector_size)
|
||||||
|
|
||||||
|
if filesystem_type == FilesystemType.Btrfs:
|
||||||
|
prompt = tr('Would you like to use BTRFS subvolumes with a default structure?') + '\n'
|
||||||
|
|
||||||
|
result = await Confirmation(
|
||||||
|
header=prompt,
|
||||||
|
allow_skip=False,
|
||||||
|
preset=True,
|
||||||
|
).show()
|
||||||
|
|
||||||
|
using_subvolumes = result.item() == MenuItem.yes()
|
||||||
|
mount_options = await select_mount_options()
|
||||||
|
else:
|
||||||
|
using_subvolumes = False
|
||||||
|
mount_options = []
|
||||||
|
|
||||||
|
device_modification = DeviceModification(device, wipe=True)
|
||||||
|
|
||||||
|
using_gpt = device_handler.partition_table.is_gpt()
|
||||||
|
|
||||||
|
if using_gpt:
|
||||||
|
available_space = available_space.gpt_end()
|
||||||
|
|
||||||
|
available_space = available_space.align()
|
||||||
|
|
||||||
|
# Used for reference: https://wiki.archlinux.org/title/partitioning
|
||||||
|
|
||||||
|
boot_partition = _boot_partition(sector_size, using_gpt)
|
||||||
|
device_modification.add_partition(boot_partition)
|
||||||
|
|
||||||
|
if separate_home is False or using_subvolumes or total_size < min_size_to_allow_home_part:
|
||||||
|
using_home_partition = False
|
||||||
|
elif separate_home:
|
||||||
|
using_home_partition = True
|
||||||
|
else:
|
||||||
|
prompt = tr('Would you like to create a separate partition for /home?') + '\n'
|
||||||
|
|
||||||
|
result = await Confirmation(
|
||||||
|
header=prompt,
|
||||||
|
allow_skip=False,
|
||||||
|
preset=True,
|
||||||
|
).show()
|
||||||
|
|
||||||
|
using_home_partition = result.item() == MenuItem.yes()
|
||||||
|
|
||||||
|
# root partition
|
||||||
|
root_start = boot_partition.start + boot_partition.length
|
||||||
|
|
||||||
|
# Set a size for / (/root)
|
||||||
|
if using_home_partition:
|
||||||
|
root_length = process_root_partition_size(total_size, sector_size)
|
||||||
|
else:
|
||||||
|
root_length = available_space - root_start
|
||||||
|
|
||||||
|
root_partition = PartitionModification(
|
||||||
|
status=ModificationStatus.Create,
|
||||||
|
type=PartitionType.Primary,
|
||||||
|
start=root_start,
|
||||||
|
length=root_length,
|
||||||
|
mountpoint=Path('/') if not using_subvolumes else None,
|
||||||
|
fs_type=filesystem_type,
|
||||||
|
mount_options=mount_options,
|
||||||
|
)
|
||||||
|
|
||||||
|
device_modification.add_partition(root_partition)
|
||||||
|
|
||||||
|
if using_subvolumes:
|
||||||
|
root_partition.btrfs_subvols = get_default_btrfs_subvols()
|
||||||
|
elif using_home_partition:
|
||||||
|
# If we don't want to use subvolumes,
|
||||||
|
# But we want to be able to reuse data between re-installs..
|
||||||
|
# A second partition for /home would be nice if we have the space for it
|
||||||
|
home_start = root_partition.start + root_partition.length
|
||||||
|
home_length = available_space - home_start
|
||||||
|
|
||||||
|
flags = []
|
||||||
|
if using_gpt:
|
||||||
|
flags.append(PartitionFlag.LINUX_HOME)
|
||||||
|
|
||||||
|
home_partition = PartitionModification(
|
||||||
|
status=ModificationStatus.Create,
|
||||||
|
type=PartitionType.Primary,
|
||||||
|
start=home_start,
|
||||||
|
length=home_length,
|
||||||
|
mountpoint=Path('/home'),
|
||||||
|
fs_type=filesystem_type,
|
||||||
|
mount_options=mount_options,
|
||||||
|
flags=flags,
|
||||||
|
)
|
||||||
|
device_modification.add_partition(home_partition)
|
||||||
|
|
||||||
|
return device_modification
|
||||||
|
|
||||||
|
|
||||||
|
async def suggest_multi_disk_layout(
|
||||||
|
devices: list[BDevice],
|
||||||
|
filesystem_type: FilesystemType | None = None,
|
||||||
|
) -> list[DeviceModification]:
|
||||||
|
if not devices:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Not really a rock solid foundation of information to stand on, but it's a start:
|
||||||
|
# https://www.reddit.com/r/btrfs/comments/m287gp/partition_strategy_for_two_physical_disks/
|
||||||
|
# https://www.reddit.com/r/btrfs/comments/9us4hr/what_is_your_btrfs_partitionsubvolumes_scheme/
|
||||||
|
min_home_partition_size = Size(40, Unit.GiB, SectorSize.default())
|
||||||
|
# rough estimate taking in to account user desktops etc. TODO: Catch user packages to detect size?
|
||||||
|
desired_root_partition_size = Size(32, Unit.GiB, SectorSize.default())
|
||||||
|
mount_options = []
|
||||||
|
|
||||||
|
if not filesystem_type:
|
||||||
|
filesystem_type = await select_main_filesystem_format()
|
||||||
|
|
||||||
|
# find proper disk for /home
|
||||||
|
possible_devices = [d for d in devices if d.device_info.total_size >= min_home_partition_size]
|
||||||
|
home_device = max(possible_devices, key=lambda d: d.device_info.total_size) if possible_devices else None
|
||||||
|
|
||||||
|
# find proper device for /root
|
||||||
|
devices_delta = {}
|
||||||
|
for device in devices:
|
||||||
|
if device is not home_device:
|
||||||
|
delta = device.device_info.total_size - desired_root_partition_size
|
||||||
|
devices_delta[device] = delta
|
||||||
|
|
||||||
|
sorted_delta: list[tuple[BDevice, Size]] = sorted(devices_delta.items(), key=lambda x: x[1])
|
||||||
|
root_device: BDevice | None = sorted_delta[0][0]
|
||||||
|
|
||||||
|
if home_device is None or root_device is None:
|
||||||
|
text = tr('The selected drives do not have the minimum capacity required for an automatic suggestion\n')
|
||||||
|
text += tr('Minimum capacity for /home partition: {}GiB\n').format(min_home_partition_size.format_size(Unit.GiB))
|
||||||
|
text += tr('Minimum capacity for Arch Linux partition: {}GiB').format(desired_root_partition_size.format_size(Unit.GiB))
|
||||||
|
|
||||||
|
_ = await Notify(text).show()
|
||||||
|
return []
|
||||||
|
|
||||||
|
if filesystem_type == FilesystemType.Btrfs:
|
||||||
|
mount_options = await select_mount_options()
|
||||||
|
|
||||||
|
device_paths = ', '.join(str(d.device_info.path) for d in devices)
|
||||||
|
|
||||||
|
debug(f'Suggesting multi-disk-layout for devices: {device_paths}')
|
||||||
|
debug(f'/root: {root_device.device_info.path}')
|
||||||
|
debug(f'/home: {home_device.device_info.path}')
|
||||||
|
|
||||||
|
root_device_modification = DeviceModification(root_device, wipe=True)
|
||||||
|
home_device_modification = DeviceModification(home_device, wipe=True)
|
||||||
|
|
||||||
|
root_device_sector_size = root_device_modification.device.device_info.sector_size
|
||||||
|
home_device_sector_size = home_device_modification.device.device_info.sector_size
|
||||||
|
|
||||||
|
using_gpt = device_handler.partition_table.is_gpt()
|
||||||
|
|
||||||
|
# add boot partition to the root device
|
||||||
|
boot_partition = _boot_partition(root_device_sector_size, using_gpt)
|
||||||
|
root_device_modification.add_partition(boot_partition)
|
||||||
|
|
||||||
|
root_start = boot_partition.start + boot_partition.length
|
||||||
|
root_length = root_device.device_info.total_size - root_start
|
||||||
|
|
||||||
|
if using_gpt:
|
||||||
|
root_length = root_length.gpt_end()
|
||||||
|
|
||||||
|
root_length = root_length.align()
|
||||||
|
|
||||||
|
# add root partition to the root device
|
||||||
|
root_partition = PartitionModification(
|
||||||
|
status=ModificationStatus.Create,
|
||||||
|
type=PartitionType.Primary,
|
||||||
|
start=root_start,
|
||||||
|
length=root_length,
|
||||||
|
mountpoint=Path('/'),
|
||||||
|
mount_options=mount_options,
|
||||||
|
fs_type=filesystem_type,
|
||||||
|
)
|
||||||
|
root_device_modification.add_partition(root_partition)
|
||||||
|
|
||||||
|
home_start = Size(1, Unit.MiB, home_device_sector_size)
|
||||||
|
home_length = home_device.device_info.total_size - home_start
|
||||||
|
|
||||||
|
flags = []
|
||||||
|
if using_gpt:
|
||||||
|
home_length = home_length.gpt_end()
|
||||||
|
flags.append(PartitionFlag.LINUX_HOME)
|
||||||
|
|
||||||
|
home_length = home_length.align()
|
||||||
|
|
||||||
|
# add home partition to home device
|
||||||
|
home_partition = PartitionModification(
|
||||||
|
status=ModificationStatus.Create,
|
||||||
|
type=PartitionType.Primary,
|
||||||
|
start=home_start,
|
||||||
|
length=home_length,
|
||||||
|
mountpoint=Path('/home'),
|
||||||
|
mount_options=mount_options,
|
||||||
|
fs_type=filesystem_type,
|
||||||
|
flags=flags,
|
||||||
|
)
|
||||||
|
home_device_modification.add_partition(home_partition)
|
||||||
|
|
||||||
|
return [root_device_modification, home_device_modification]
|
||||||
|
|
||||||
|
|
||||||
|
async def suggest_lvm_layout(
|
||||||
|
disk_config: DiskLayoutConfiguration,
|
||||||
|
filesystem_type: FilesystemType | None = None,
|
||||||
|
vg_grp_name: str = 'ArchinstallVg',
|
||||||
|
) -> LvmConfiguration:
|
||||||
|
if disk_config.config_type != DiskLayoutType.Default:
|
||||||
|
raise ValueError('LVM suggested volumes are only available for default partitioning')
|
||||||
|
|
||||||
|
using_subvolumes = False
|
||||||
|
btrfs_subvols = []
|
||||||
|
home_volume = True
|
||||||
|
mount_options = []
|
||||||
|
|
||||||
|
if not filesystem_type:
|
||||||
|
filesystem_type = await select_main_filesystem_format()
|
||||||
|
|
||||||
|
if filesystem_type == FilesystemType.Btrfs:
|
||||||
|
prompt = tr('Would you like to use BTRFS subvolumes with a default structure?') + '\n'
|
||||||
|
result = await Confirmation(header=prompt, allow_skip=False, preset=True).show()
|
||||||
|
|
||||||
|
using_subvolumes = MenuItem.yes() == result.item()
|
||||||
|
mount_options = await select_mount_options()
|
||||||
|
|
||||||
|
if using_subvolumes:
|
||||||
|
btrfs_subvols = get_default_btrfs_subvols()
|
||||||
|
home_volume = False
|
||||||
|
|
||||||
|
boot_part: PartitionModification | None = None
|
||||||
|
other_part: list[PartitionModification] = []
|
||||||
|
|
||||||
|
for mod in disk_config.device_modifications:
|
||||||
|
for part in mod.partitions:
|
||||||
|
if part.is_boot():
|
||||||
|
boot_part = part
|
||||||
|
else:
|
||||||
|
other_part.append(part)
|
||||||
|
|
||||||
|
if not boot_part:
|
||||||
|
raise ValueError('Unable to find boot partition in partition modifications')
|
||||||
|
|
||||||
|
total_vol_available = sum(
|
||||||
|
[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
|
||||||
|
|
||||||
|
lvm_vol_group = LvmVolumeGroup(vg_grp_name, pvs=other_part)
|
||||||
|
|
||||||
|
root_vol = LvmVolume(
|
||||||
|
status=LvmVolumeStatus.Create,
|
||||||
|
name='root',
|
||||||
|
fs_type=filesystem_type,
|
||||||
|
length=root_vol_size,
|
||||||
|
mountpoint=Path('/'),
|
||||||
|
btrfs_subvols=btrfs_subvols,
|
||||||
|
mount_options=mount_options,
|
||||||
|
)
|
||||||
|
|
||||||
|
lvm_vol_group.volumes.append(root_vol)
|
||||||
|
|
||||||
|
if home_volume:
|
||||||
|
home_vol = LvmVolume(
|
||||||
|
status=LvmVolumeStatus.Create,
|
||||||
|
name='home',
|
||||||
|
fs_type=filesystem_type,
|
||||||
|
length=home_vol_size,
|
||||||
|
mountpoint=Path('/home'),
|
||||||
|
)
|
||||||
|
|
||||||
|
lvm_vol_group.volumes.append(home_vol)
|
||||||
|
|
||||||
|
return LvmConfiguration(LvmLayoutType.Default, [lvm_vol_group])
|
||||||
|
|
|
||||||
|
|
@ -566,7 +566,7 @@ class PartitioningList(ListManager[DiskSegment]):
|
||||||
if not await self._reset_confirmation():
|
if not await self._reset_confirmation():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
from archinstall.lib.interactions.disk_conf import suggest_single_disk_layout
|
from archinstall.lib.disk.disk_menu import suggest_single_disk_layout
|
||||||
|
|
||||||
return await suggest_single_disk_layout(self._device)
|
return await suggest_single_disk_layout(self._device)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
from archinstall.lib.general.general_menu import (
|
||||||
|
add_number_of_parallel_downloads,
|
||||||
|
select_archinstall_language,
|
||||||
|
select_hostname,
|
||||||
|
select_ntp,
|
||||||
|
select_timezone,
|
||||||
|
)
|
||||||
|
from archinstall.lib.general.system_menu import select_driver, select_kernel, select_swap
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'add_number_of_parallel_downloads',
|
||||||
|
'select_archinstall_language',
|
||||||
|
'select_driver',
|
||||||
|
'select_hostname',
|
||||||
|
'select_kernel',
|
||||||
|
'select_ntp',
|
||||||
|
'select_swap',
|
||||||
|
'select_timezone',
|
||||||
|
]
|
||||||
|
|
@ -6,9 +6,9 @@ from archinstall.lib.authentication.authentication_menu import AuthenticationMen
|
||||||
from archinstall.lib.bootloader.bootloader_menu import BootloaderMenu
|
from archinstall.lib.bootloader.bootloader_menu import BootloaderMenu
|
||||||
from archinstall.lib.configuration import save_config
|
from archinstall.lib.configuration import save_config
|
||||||
from archinstall.lib.disk.disk_menu import DiskLayoutConfigurationMenu
|
from archinstall.lib.disk.disk_menu import DiskLayoutConfigurationMenu
|
||||||
|
from archinstall.lib.general.general_menu import add_number_of_parallel_downloads, select_hostname, select_ntp, select_timezone
|
||||||
|
from archinstall.lib.general.system_menu import select_kernel, select_swap
|
||||||
from archinstall.lib.hardware import SysInfo
|
from archinstall.lib.hardware import SysInfo
|
||||||
from archinstall.lib.interactions.general_conf import add_number_of_parallel_downloads, select_hostname, select_ntp, select_timezone
|
|
||||||
from archinstall.lib.interactions.system_conf import select_kernel, select_swap
|
|
||||||
from archinstall.lib.locale.locale_menu import LocaleMenu
|
from archinstall.lib.locale.locale_menu import LocaleMenu
|
||||||
from archinstall.lib.menu.abstract_menu import AbstractMenu, SpecialMenuKey
|
from archinstall.lib.menu.abstract_menu import AbstractMenu, SpecialMenuKey
|
||||||
from archinstall.lib.mirror.mirror_handler import MirrorListHandler
|
from archinstall.lib.mirror.mirror_handler import MirrorListHandler
|
||||||
|
|
@ -232,7 +232,7 @@ class GlobalMenu(AbstractMenu[None]):
|
||||||
return self._validate_bootloader() is None
|
return self._validate_bootloader() is None
|
||||||
|
|
||||||
async def _select_archinstall_language(self, preset: Language) -> Language:
|
async def _select_archinstall_language(self, preset: Language) -> Language:
|
||||||
from archinstall.lib.interactions.general_conf import select_archinstall_language
|
from archinstall.lib.general.general_menu import select_archinstall_language
|
||||||
|
|
||||||
language = await select_archinstall_language(translation_handler.translated_languages, preset)
|
language = await select_archinstall_language(translation_handler.translated_languages, preset)
|
||||||
translation_handler.activate(language)
|
translation_handler.activate(language)
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
from archinstall.lib.interactions.disk_conf import (
|
|
||||||
get_default_partition_layout,
|
|
||||||
select_devices,
|
|
||||||
select_disk_config,
|
|
||||||
select_main_filesystem_format,
|
|
||||||
suggest_multi_disk_layout,
|
|
||||||
suggest_single_disk_layout,
|
|
||||||
)
|
|
||||||
from archinstall.lib.interactions.general_conf import (
|
|
||||||
add_number_of_parallel_downloads,
|
|
||||||
select_archinstall_language,
|
|
||||||
select_hostname,
|
|
||||||
select_ntp,
|
|
||||||
select_timezone,
|
|
||||||
)
|
|
||||||
from archinstall.lib.interactions.system_conf import select_driver, select_kernel, select_swap
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'add_number_of_parallel_downloads',
|
|
||||||
'get_default_partition_layout',
|
|
||||||
'select_archinstall_language',
|
|
||||||
'select_devices',
|
|
||||||
'select_disk_config',
|
|
||||||
'select_driver',
|
|
||||||
'select_hostname',
|
|
||||||
'select_kernel',
|
|
||||||
'select_main_filesystem_format',
|
|
||||||
'select_ntp',
|
|
||||||
'select_swap',
|
|
||||||
'select_timezone',
|
|
||||||
'suggest_multi_disk_layout',
|
|
||||||
'suggest_single_disk_layout',
|
|
||||||
]
|
|
||||||
|
|
@ -1,611 +0,0 @@
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from archinstall.lib.disk.device_handler import device_handler
|
|
||||||
from archinstall.lib.disk.partitioning_menu import manual_partitioning
|
|
||||||
from archinstall.lib.menu.helpers import Confirmation, Notify, Selection, Table
|
|
||||||
from archinstall.lib.menu.util import prompt_dir
|
|
||||||
from archinstall.lib.models.device import (
|
|
||||||
BDevice,
|
|
||||||
BtrfsMountOption,
|
|
||||||
DeviceModification,
|
|
||||||
DiskLayoutConfiguration,
|
|
||||||
DiskLayoutType,
|
|
||||||
FilesystemType,
|
|
||||||
LvmConfiguration,
|
|
||||||
LvmLayoutType,
|
|
||||||
LvmVolume,
|
|
||||||
LvmVolumeGroup,
|
|
||||||
LvmVolumeStatus,
|
|
||||||
ModificationStatus,
|
|
||||||
PartitionFlag,
|
|
||||||
PartitionModification,
|
|
||||||
PartitionType,
|
|
||||||
SectorSize,
|
|
||||||
Size,
|
|
||||||
SubvolumeModification,
|
|
||||||
Unit,
|
|
||||||
_DeviceInfo,
|
|
||||||
)
|
|
||||||
from archinstall.lib.output import FormattedOutput, debug
|
|
||||||
from archinstall.lib.translationhandler import tr
|
|
||||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
|
||||||
from archinstall.tui.ui.result import ResultType
|
|
||||||
|
|
||||||
|
|
||||||
async def select_devices(preset: list[BDevice] | None = []) -> list[BDevice] | None:
|
|
||||||
def _preview_device_selection(item: MenuItem) -> str | None:
|
|
||||||
device: _DeviceInfo = item.value # type: ignore[assignment]
|
|
||||||
dev = device_handler.get_device(device.path)
|
|
||||||
|
|
||||||
if dev and dev.partition_infos:
|
|
||||||
return FormattedOutput.as_table(dev.partition_infos)
|
|
||||||
return None
|
|
||||||
|
|
||||||
if preset is None:
|
|
||||||
preset = []
|
|
||||||
|
|
||||||
devices = device_handler.devices
|
|
||||||
|
|
||||||
items = [
|
|
||||||
MenuItem(
|
|
||||||
str(d.device_info.path),
|
|
||||||
d.device_info,
|
|
||||||
preview_action=_preview_device_selection,
|
|
||||||
)
|
|
||||||
for d in devices
|
|
||||||
]
|
|
||||||
|
|
||||||
presets = [p.device_info for p in preset]
|
|
||||||
|
|
||||||
group = MenuItemGroup(items)
|
|
||||||
group.set_selected_by_value(presets)
|
|
||||||
|
|
||||||
result = await Table[_DeviceInfo](
|
|
||||||
header=tr('Select disks for the installation'),
|
|
||||||
group=group,
|
|
||||||
presets=presets,
|
|
||||||
allow_skip=True,
|
|
||||||
multi=True,
|
|
||||||
preview_location='bottom',
|
|
||||||
preview_header=tr('Partitions'),
|
|
||||||
).show()
|
|
||||||
|
|
||||||
match result.type_:
|
|
||||||
case ResultType.Reset:
|
|
||||||
return None
|
|
||||||
case ResultType.Skip:
|
|
||||||
return None
|
|
||||||
case ResultType.Selection:
|
|
||||||
selected_device_info = result.get_values()
|
|
||||||
selected_devices = []
|
|
||||||
|
|
||||||
for device in devices:
|
|
||||||
if device.device_info in selected_device_info:
|
|
||||||
selected_devices.append(device)
|
|
||||||
|
|
||||||
return selected_devices
|
|
||||||
|
|
||||||
|
|
||||||
async def get_default_partition_layout(
|
|
||||||
devices: list[BDevice],
|
|
||||||
filesystem_type: FilesystemType | None = None,
|
|
||||||
) -> list[DeviceModification]:
|
|
||||||
if len(devices) == 1:
|
|
||||||
device_modification = await suggest_single_disk_layout(
|
|
||||||
devices[0],
|
|
||||||
filesystem_type=filesystem_type,
|
|
||||||
)
|
|
||||||
return [device_modification]
|
|
||||||
else:
|
|
||||||
return await suggest_multi_disk_layout(
|
|
||||||
devices,
|
|
||||||
filesystem_type=filesystem_type,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def _manual_partitioning(
|
|
||||||
preset: list[DeviceModification],
|
|
||||||
devices: list[BDevice],
|
|
||||||
) -> list[DeviceModification] | None:
|
|
||||||
modifications: list[DeviceModification] = []
|
|
||||||
|
|
||||||
for device in devices:
|
|
||||||
mod = next(filter(lambda x: x.device == device, preset), None)
|
|
||||||
if not mod:
|
|
||||||
mod = DeviceModification(device, wipe=False)
|
|
||||||
|
|
||||||
device_mod = await manual_partitioning(mod, device_handler.partition_table)
|
|
||||||
|
|
||||||
if not device_mod:
|
|
||||||
return None
|
|
||||||
|
|
||||||
modifications.append(device_mod)
|
|
||||||
|
|
||||||
return modifications
|
|
||||||
|
|
||||||
|
|
||||||
async def select_disk_config(preset: DiskLayoutConfiguration | None = None) -> DiskLayoutConfiguration | None:
|
|
||||||
default_layout = DiskLayoutType.Default.display_msg()
|
|
||||||
manual_mode = DiskLayoutType.Manual.display_msg()
|
|
||||||
pre_mount_mode = DiskLayoutType.Pre_mount.display_msg()
|
|
||||||
|
|
||||||
items = [
|
|
||||||
MenuItem(default_layout, value=default_layout),
|
|
||||||
MenuItem(manual_mode, value=manual_mode),
|
|
||||||
MenuItem(pre_mount_mode, value=pre_mount_mode),
|
|
||||||
]
|
|
||||||
group = MenuItemGroup(items, sort_items=False)
|
|
||||||
|
|
||||||
if preset:
|
|
||||||
group.set_selected_by_value(preset.config_type.display_msg())
|
|
||||||
|
|
||||||
result = await Selection[str](
|
|
||||||
group,
|
|
||||||
header=tr('Select a disk configuration'),
|
|
||||||
allow_skip=True,
|
|
||||||
allow_reset=True,
|
|
||||||
).show()
|
|
||||||
|
|
||||||
match result.type_:
|
|
||||||
case ResultType.Skip:
|
|
||||||
return preset
|
|
||||||
case ResultType.Reset:
|
|
||||||
return None
|
|
||||||
case ResultType.Selection:
|
|
||||||
selection = result.get_value()
|
|
||||||
|
|
||||||
if selection == pre_mount_mode:
|
|
||||||
output = tr('Enter root mount directory') + '\n\n'
|
|
||||||
output += tr('You will use whatever drive-setup is mounted at the specified directory') + '\n'
|
|
||||||
output += tr("WARNING: Archinstall won't check the suitability of this setup")
|
|
||||||
|
|
||||||
path = await prompt_dir(output, allow_skip=True)
|
|
||||||
|
|
||||||
if path is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
mods = device_handler.detect_pre_mounted_mods(path)
|
|
||||||
|
|
||||||
return DiskLayoutConfiguration(
|
|
||||||
config_type=DiskLayoutType.Pre_mount,
|
|
||||||
device_modifications=mods,
|
|
||||||
mountpoint=path,
|
|
||||||
)
|
|
||||||
|
|
||||||
preset_devices = [mod.device for mod in preset.device_modifications] if preset else []
|
|
||||||
devices = await select_devices(preset_devices)
|
|
||||||
|
|
||||||
if devices is None:
|
|
||||||
return preset
|
|
||||||
|
|
||||||
if result.get_value() == default_layout:
|
|
||||||
modifications = await get_default_partition_layout(devices)
|
|
||||||
if modifications:
|
|
||||||
return DiskLayoutConfiguration(
|
|
||||||
config_type=DiskLayoutType.Default,
|
|
||||||
device_modifications=modifications,
|
|
||||||
)
|
|
||||||
elif result.get_value() == manual_mode:
|
|
||||||
preset_mods = preset.device_modifications if preset else []
|
|
||||||
partitions = await _manual_partitioning(preset_mods, devices)
|
|
||||||
|
|
||||||
if not partitions:
|
|
||||||
return preset
|
|
||||||
|
|
||||||
return DiskLayoutConfiguration(
|
|
||||||
config_type=DiskLayoutType.Manual,
|
|
||||||
device_modifications=partitions,
|
|
||||||
)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
async def select_lvm_config(
|
|
||||||
disk_config: DiskLayoutConfiguration,
|
|
||||||
preset: LvmConfiguration | None = None,
|
|
||||||
) -> LvmConfiguration | None:
|
|
||||||
preset_value = preset.config_type.display_msg() if preset else None
|
|
||||||
default_mode = LvmLayoutType.Default.display_msg()
|
|
||||||
|
|
||||||
items = [MenuItem(default_mode, value=default_mode)]
|
|
||||||
group = MenuItemGroup(items)
|
|
||||||
group.set_focus_by_value(preset_value)
|
|
||||||
|
|
||||||
result = await Selection[str](
|
|
||||||
group,
|
|
||||||
allow_reset=True,
|
|
||||||
allow_skip=True,
|
|
||||||
).show()
|
|
||||||
|
|
||||||
match result.type_:
|
|
||||||
case ResultType.Skip:
|
|
||||||
return preset
|
|
||||||
case ResultType.Reset:
|
|
||||||
return None
|
|
||||||
case ResultType.Selection:
|
|
||||||
if result.get_value() == default_mode:
|
|
||||||
return await suggest_lvm_layout(disk_config)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _boot_partition(sector_size: SectorSize, using_gpt: bool) -> PartitionModification:
|
|
||||||
flags = [PartitionFlag.BOOT]
|
|
||||||
size = Size(1, Unit.GiB, sector_size)
|
|
||||||
start = Size(1, Unit.MiB, sector_size)
|
|
||||||
if using_gpt:
|
|
||||||
flags.append(PartitionFlag.ESP)
|
|
||||||
|
|
||||||
# boot partition
|
|
||||||
return PartitionModification(
|
|
||||||
status=ModificationStatus.Create,
|
|
||||||
type=PartitionType.Primary,
|
|
||||||
start=start,
|
|
||||||
length=size,
|
|
||||||
mountpoint=Path('/boot'),
|
|
||||||
fs_type=FilesystemType.Fat32,
|
|
||||||
flags=flags,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def select_main_filesystem_format() -> FilesystemType:
|
|
||||||
items = [
|
|
||||||
MenuItem('btrfs', value=FilesystemType.Btrfs),
|
|
||||||
MenuItem('ext4', value=FilesystemType.Ext4),
|
|
||||||
MenuItem('xfs', value=FilesystemType.Xfs),
|
|
||||||
MenuItem('f2fs', value=FilesystemType.F2fs),
|
|
||||||
]
|
|
||||||
|
|
||||||
group = MenuItemGroup(items, sort_items=False)
|
|
||||||
result = await Selection[FilesystemType](
|
|
||||||
group,
|
|
||||||
header=tr('Select main filesystem'),
|
|
||||||
allow_skip=False,
|
|
||||||
).show()
|
|
||||||
|
|
||||||
match result.type_:
|
|
||||||
case ResultType.Selection:
|
|
||||||
return result.get_value()
|
|
||||||
case _:
|
|
||||||
raise ValueError('Unhandled result type')
|
|
||||||
|
|
||||||
|
|
||||||
async def select_mount_options() -> list[str]:
|
|
||||||
prompt = tr('Would you like to use compression or disable CoW?') + '\n'
|
|
||||||
compression = tr('Use compression')
|
|
||||||
disable_cow = tr('Disable Copy-on-Write')
|
|
||||||
|
|
||||||
items = [
|
|
||||||
MenuItem(compression, value=BtrfsMountOption.compress.value),
|
|
||||||
MenuItem(disable_cow, value=BtrfsMountOption.nodatacow.value),
|
|
||||||
]
|
|
||||||
group = MenuItemGroup(items, sort_items=False)
|
|
||||||
|
|
||||||
result = await Selection[str](
|
|
||||||
group,
|
|
||||||
header=prompt,
|
|
||||||
allow_skip=True,
|
|
||||||
).show()
|
|
||||||
|
|
||||||
match result.type_:
|
|
||||||
case ResultType.Skip:
|
|
||||||
return []
|
|
||||||
case ResultType.Selection:
|
|
||||||
return [result.get_value()]
|
|
||||||
case _:
|
|
||||||
raise ValueError('Unhandled result type')
|
|
||||||
|
|
||||||
|
|
||||||
def process_root_partition_size(total_size: Size, sector_size: SectorSize) -> Size:
|
|
||||||
# root partition size processing
|
|
||||||
total_device_size = total_size.convert(Unit.GiB)
|
|
||||||
if total_device_size.value > 500:
|
|
||||||
# maximum size
|
|
||||||
return Size(value=50, unit=Unit.GiB, sector_size=sector_size)
|
|
||||||
elif total_device_size.value < 320:
|
|
||||||
# minimum size
|
|
||||||
return Size(value=32, unit=Unit.GiB, sector_size=sector_size)
|
|
||||||
else:
|
|
||||||
# 10% of total size
|
|
||||||
length = total_device_size.value // 10
|
|
||||||
return Size(value=length, unit=Unit.GiB, sector_size=sector_size)
|
|
||||||
|
|
||||||
|
|
||||||
def get_default_btrfs_subvols() -> list[SubvolumeModification]:
|
|
||||||
# https://btrfs.wiki.kernel.org/index.php/FAQ
|
|
||||||
# https://unix.stackexchange.com/questions/246976/btrfs-subvolume-uuid-clash
|
|
||||||
# https://github.com/classy-giraffe/easy-arch/blob/main/easy-arch.sh
|
|
||||||
return [
|
|
||||||
SubvolumeModification(Path('@'), Path('/')),
|
|
||||||
SubvolumeModification(Path('@home'), Path('/home')),
|
|
||||||
SubvolumeModification(Path('@log'), Path('/var/log')),
|
|
||||||
SubvolumeModification(Path('@pkg'), Path('/var/cache/pacman/pkg')),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
async def suggest_single_disk_layout(
|
|
||||||
device: BDevice,
|
|
||||||
filesystem_type: FilesystemType | None = None,
|
|
||||||
separate_home: bool | None = None,
|
|
||||||
) -> DeviceModification:
|
|
||||||
if not filesystem_type:
|
|
||||||
filesystem_type = await select_main_filesystem_format()
|
|
||||||
|
|
||||||
sector_size = device.device_info.sector_size
|
|
||||||
total_size = device.device_info.total_size
|
|
||||||
available_space = total_size
|
|
||||||
min_size_to_allow_home_part = Size(64, Unit.GiB, sector_size)
|
|
||||||
|
|
||||||
if filesystem_type == FilesystemType.Btrfs:
|
|
||||||
prompt = tr('Would you like to use BTRFS subvolumes with a default structure?') + '\n'
|
|
||||||
|
|
||||||
result = await Confirmation(
|
|
||||||
header=prompt,
|
|
||||||
allow_skip=False,
|
|
||||||
preset=True,
|
|
||||||
).show()
|
|
||||||
|
|
||||||
using_subvolumes = result.item() == MenuItem.yes()
|
|
||||||
mount_options = await select_mount_options()
|
|
||||||
else:
|
|
||||||
using_subvolumes = False
|
|
||||||
mount_options = []
|
|
||||||
|
|
||||||
device_modification = DeviceModification(device, wipe=True)
|
|
||||||
|
|
||||||
using_gpt = device_handler.partition_table.is_gpt()
|
|
||||||
|
|
||||||
if using_gpt:
|
|
||||||
available_space = available_space.gpt_end()
|
|
||||||
|
|
||||||
available_space = available_space.align()
|
|
||||||
|
|
||||||
# Used for reference: https://wiki.archlinux.org/title/partitioning
|
|
||||||
|
|
||||||
boot_partition = _boot_partition(sector_size, using_gpt)
|
|
||||||
device_modification.add_partition(boot_partition)
|
|
||||||
|
|
||||||
if separate_home is False or using_subvolumes or total_size < min_size_to_allow_home_part:
|
|
||||||
using_home_partition = False
|
|
||||||
elif separate_home:
|
|
||||||
using_home_partition = True
|
|
||||||
else:
|
|
||||||
prompt = tr('Would you like to create a separate partition for /home?') + '\n'
|
|
||||||
|
|
||||||
result = await Confirmation(
|
|
||||||
header=prompt,
|
|
||||||
allow_skip=False,
|
|
||||||
preset=True,
|
|
||||||
).show()
|
|
||||||
|
|
||||||
using_home_partition = result.item() == MenuItem.yes()
|
|
||||||
|
|
||||||
# root partition
|
|
||||||
root_start = boot_partition.start + boot_partition.length
|
|
||||||
|
|
||||||
# Set a size for / (/root)
|
|
||||||
if using_home_partition:
|
|
||||||
root_length = process_root_partition_size(total_size, sector_size)
|
|
||||||
else:
|
|
||||||
root_length = available_space - root_start
|
|
||||||
|
|
||||||
root_partition = PartitionModification(
|
|
||||||
status=ModificationStatus.Create,
|
|
||||||
type=PartitionType.Primary,
|
|
||||||
start=root_start,
|
|
||||||
length=root_length,
|
|
||||||
mountpoint=Path('/') if not using_subvolumes else None,
|
|
||||||
fs_type=filesystem_type,
|
|
||||||
mount_options=mount_options,
|
|
||||||
)
|
|
||||||
|
|
||||||
device_modification.add_partition(root_partition)
|
|
||||||
|
|
||||||
if using_subvolumes:
|
|
||||||
root_partition.btrfs_subvols = get_default_btrfs_subvols()
|
|
||||||
elif using_home_partition:
|
|
||||||
# If we don't want to use subvolumes,
|
|
||||||
# But we want to be able to reuse data between re-installs..
|
|
||||||
# A second partition for /home would be nice if we have the space for it
|
|
||||||
home_start = root_partition.start + root_partition.length
|
|
||||||
home_length = available_space - home_start
|
|
||||||
|
|
||||||
flags = []
|
|
||||||
if using_gpt:
|
|
||||||
flags.append(PartitionFlag.LINUX_HOME)
|
|
||||||
|
|
||||||
home_partition = PartitionModification(
|
|
||||||
status=ModificationStatus.Create,
|
|
||||||
type=PartitionType.Primary,
|
|
||||||
start=home_start,
|
|
||||||
length=home_length,
|
|
||||||
mountpoint=Path('/home'),
|
|
||||||
fs_type=filesystem_type,
|
|
||||||
mount_options=mount_options,
|
|
||||||
flags=flags,
|
|
||||||
)
|
|
||||||
device_modification.add_partition(home_partition)
|
|
||||||
|
|
||||||
return device_modification
|
|
||||||
|
|
||||||
|
|
||||||
async def suggest_multi_disk_layout(
|
|
||||||
devices: list[BDevice],
|
|
||||||
filesystem_type: FilesystemType | None = None,
|
|
||||||
) -> list[DeviceModification]:
|
|
||||||
if not devices:
|
|
||||||
return []
|
|
||||||
|
|
||||||
# Not really a rock solid foundation of information to stand on, but it's a start:
|
|
||||||
# https://www.reddit.com/r/btrfs/comments/m287gp/partition_strategy_for_two_physical_disks/
|
|
||||||
# https://www.reddit.com/r/btrfs/comments/9us4hr/what_is_your_btrfs_partitionsubvolumes_scheme/
|
|
||||||
min_home_partition_size = Size(40, Unit.GiB, SectorSize.default())
|
|
||||||
# rough estimate taking in to account user desktops etc. TODO: Catch user packages to detect size?
|
|
||||||
desired_root_partition_size = Size(32, Unit.GiB, SectorSize.default())
|
|
||||||
mount_options = []
|
|
||||||
|
|
||||||
if not filesystem_type:
|
|
||||||
filesystem_type = await select_main_filesystem_format()
|
|
||||||
|
|
||||||
# find proper disk for /home
|
|
||||||
possible_devices = [d for d in devices if d.device_info.total_size >= min_home_partition_size]
|
|
||||||
home_device = max(possible_devices, key=lambda d: d.device_info.total_size) if possible_devices else None
|
|
||||||
|
|
||||||
# find proper device for /root
|
|
||||||
devices_delta = {}
|
|
||||||
for device in devices:
|
|
||||||
if device is not home_device:
|
|
||||||
delta = device.device_info.total_size - desired_root_partition_size
|
|
||||||
devices_delta[device] = delta
|
|
||||||
|
|
||||||
sorted_delta: list[tuple[BDevice, Size]] = sorted(devices_delta.items(), key=lambda x: x[1])
|
|
||||||
root_device: BDevice | None = sorted_delta[0][0]
|
|
||||||
|
|
||||||
if home_device is None or root_device is None:
|
|
||||||
text = tr('The selected drives do not have the minimum capacity required for an automatic suggestion\n')
|
|
||||||
text += tr('Minimum capacity for /home partition: {}GiB\n').format(min_home_partition_size.format_size(Unit.GiB))
|
|
||||||
text += tr('Minimum capacity for Arch Linux partition: {}GiB').format(desired_root_partition_size.format_size(Unit.GiB))
|
|
||||||
|
|
||||||
_ = await Notify(text).show()
|
|
||||||
return []
|
|
||||||
|
|
||||||
if filesystem_type == FilesystemType.Btrfs:
|
|
||||||
mount_options = await select_mount_options()
|
|
||||||
|
|
||||||
device_paths = ', '.join(str(d.device_info.path) for d in devices)
|
|
||||||
|
|
||||||
debug(f'Suggesting multi-disk-layout for devices: {device_paths}')
|
|
||||||
debug(f'/root: {root_device.device_info.path}')
|
|
||||||
debug(f'/home: {home_device.device_info.path}')
|
|
||||||
|
|
||||||
root_device_modification = DeviceModification(root_device, wipe=True)
|
|
||||||
home_device_modification = DeviceModification(home_device, wipe=True)
|
|
||||||
|
|
||||||
root_device_sector_size = root_device_modification.device.device_info.sector_size
|
|
||||||
home_device_sector_size = home_device_modification.device.device_info.sector_size
|
|
||||||
|
|
||||||
using_gpt = device_handler.partition_table.is_gpt()
|
|
||||||
|
|
||||||
# add boot partition to the root device
|
|
||||||
boot_partition = _boot_partition(root_device_sector_size, using_gpt)
|
|
||||||
root_device_modification.add_partition(boot_partition)
|
|
||||||
|
|
||||||
root_start = boot_partition.start + boot_partition.length
|
|
||||||
root_length = root_device.device_info.total_size - root_start
|
|
||||||
|
|
||||||
if using_gpt:
|
|
||||||
root_length = root_length.gpt_end()
|
|
||||||
|
|
||||||
root_length = root_length.align()
|
|
||||||
|
|
||||||
# add root partition to the root device
|
|
||||||
root_partition = PartitionModification(
|
|
||||||
status=ModificationStatus.Create,
|
|
||||||
type=PartitionType.Primary,
|
|
||||||
start=root_start,
|
|
||||||
length=root_length,
|
|
||||||
mountpoint=Path('/'),
|
|
||||||
mount_options=mount_options,
|
|
||||||
fs_type=filesystem_type,
|
|
||||||
)
|
|
||||||
root_device_modification.add_partition(root_partition)
|
|
||||||
|
|
||||||
home_start = Size(1, Unit.MiB, home_device_sector_size)
|
|
||||||
home_length = home_device.device_info.total_size - home_start
|
|
||||||
|
|
||||||
flags = []
|
|
||||||
if using_gpt:
|
|
||||||
home_length = home_length.gpt_end()
|
|
||||||
flags.append(PartitionFlag.LINUX_HOME)
|
|
||||||
|
|
||||||
home_length = home_length.align()
|
|
||||||
|
|
||||||
# add home partition to home device
|
|
||||||
home_partition = PartitionModification(
|
|
||||||
status=ModificationStatus.Create,
|
|
||||||
type=PartitionType.Primary,
|
|
||||||
start=home_start,
|
|
||||||
length=home_length,
|
|
||||||
mountpoint=Path('/home'),
|
|
||||||
mount_options=mount_options,
|
|
||||||
fs_type=filesystem_type,
|
|
||||||
flags=flags,
|
|
||||||
)
|
|
||||||
home_device_modification.add_partition(home_partition)
|
|
||||||
|
|
||||||
return [root_device_modification, home_device_modification]
|
|
||||||
|
|
||||||
|
|
||||||
async def suggest_lvm_layout(
|
|
||||||
disk_config: DiskLayoutConfiguration,
|
|
||||||
filesystem_type: FilesystemType | None = None,
|
|
||||||
vg_grp_name: str = 'ArchinstallVg',
|
|
||||||
) -> LvmConfiguration:
|
|
||||||
if disk_config.config_type != DiskLayoutType.Default:
|
|
||||||
raise ValueError('LVM suggested volumes are only available for default partitioning')
|
|
||||||
|
|
||||||
using_subvolumes = False
|
|
||||||
btrfs_subvols = []
|
|
||||||
home_volume = True
|
|
||||||
mount_options = []
|
|
||||||
|
|
||||||
if not filesystem_type:
|
|
||||||
filesystem_type = await select_main_filesystem_format()
|
|
||||||
|
|
||||||
if filesystem_type == FilesystemType.Btrfs:
|
|
||||||
prompt = tr('Would you like to use BTRFS subvolumes with a default structure?') + '\n'
|
|
||||||
result = await Confirmation(header=prompt, allow_skip=False, preset=True).show()
|
|
||||||
|
|
||||||
using_subvolumes = MenuItem.yes() == result.item()
|
|
||||||
mount_options = await select_mount_options()
|
|
||||||
|
|
||||||
if using_subvolumes:
|
|
||||||
btrfs_subvols = get_default_btrfs_subvols()
|
|
||||||
home_volume = False
|
|
||||||
|
|
||||||
boot_part: PartitionModification | None = None
|
|
||||||
other_part: list[PartitionModification] = []
|
|
||||||
|
|
||||||
for mod in disk_config.device_modifications:
|
|
||||||
for part in mod.partitions:
|
|
||||||
if part.is_boot():
|
|
||||||
boot_part = part
|
|
||||||
else:
|
|
||||||
other_part.append(part)
|
|
||||||
|
|
||||||
if not boot_part:
|
|
||||||
raise ValueError('Unable to find boot partition in partition modifications')
|
|
||||||
|
|
||||||
total_vol_available = sum(
|
|
||||||
[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
|
|
||||||
|
|
||||||
lvm_vol_group = LvmVolumeGroup(vg_grp_name, pvs=other_part)
|
|
||||||
|
|
||||||
root_vol = LvmVolume(
|
|
||||||
status=LvmVolumeStatus.Create,
|
|
||||||
name='root',
|
|
||||||
fs_type=filesystem_type,
|
|
||||||
length=root_vol_size,
|
|
||||||
mountpoint=Path('/'),
|
|
||||||
btrfs_subvols=btrfs_subvols,
|
|
||||||
mount_options=mount_options,
|
|
||||||
)
|
|
||||||
|
|
||||||
lvm_vol_group.volumes.append(root_vol)
|
|
||||||
|
|
||||||
if home_volume:
|
|
||||||
home_vol = LvmVolume(
|
|
||||||
status=LvmVolumeStatus.Create,
|
|
||||||
name='home',
|
|
||||||
fs_type=filesystem_type,
|
|
||||||
length=home_vol_size,
|
|
||||||
mountpoint=Path('/home'),
|
|
||||||
)
|
|
||||||
|
|
||||||
lvm_vol_group.volumes.append(home_vol)
|
|
||||||
|
|
||||||
return LvmConfiguration(LvmLayoutType.Default, [lvm_vol_group])
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
from typing import override
|
from typing import override
|
||||||
|
|
||||||
from archinstall.default_profiles.profile import GreeterType, Profile
|
from archinstall.default_profiles.profile import GreeterType, Profile
|
||||||
|
from archinstall.lib.general.system_menu import select_driver
|
||||||
from archinstall.lib.hardware import GfxDriver
|
from archinstall.lib.hardware import GfxDriver
|
||||||
from archinstall.lib.interactions.system_conf import select_driver
|
|
||||||
from archinstall.lib.menu.abstract_menu import AbstractSubMenu
|
from archinstall.lib.menu.abstract_menu import AbstractSubMenu
|
||||||
from archinstall.lib.menu.helpers import Confirmation, Selection
|
from archinstall.lib.menu.helpers import Confirmation, Selection
|
||||||
from archinstall.lib.models.profile import ProfileConfiguration
|
from archinstall.lib.models.profile import ProfileConfiguration
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ from archinstall.lib.authentication.authentication_handler import Authentication
|
||||||
from archinstall.lib.configuration import ConfigurationOutput
|
from archinstall.lib.configuration import ConfigurationOutput
|
||||||
from archinstall.lib.disk.filesystem import FilesystemHandler
|
from archinstall.lib.disk.filesystem import FilesystemHandler
|
||||||
from archinstall.lib.disk.utils import disk_layouts
|
from archinstall.lib.disk.utils import disk_layouts
|
||||||
|
from archinstall.lib.general.general_menu import PostInstallationAction, select_post_installation
|
||||||
from archinstall.lib.global_menu import GlobalMenu
|
from archinstall.lib.global_menu import GlobalMenu
|
||||||
from archinstall.lib.installer import Installer, accessibility_tools_in_use, run_custom_user_commands
|
from archinstall.lib.installer import Installer, accessibility_tools_in_use, run_custom_user_commands
|
||||||
from archinstall.lib.interactions.general_conf import PostInstallationAction, select_post_installation
|
|
||||||
from archinstall.lib.menu.util import delayed_warning
|
from archinstall.lib.menu.util import delayed_warning
|
||||||
from archinstall.lib.mirror.mirror_handler import MirrorListHandler
|
from archinstall.lib.mirror.mirror_handler import MirrorListHandler
|
||||||
from archinstall.lib.models import Bootloader
|
from archinstall.lib.models import Bootloader
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue