Send patches - preferably formatted by git format-patch - to patches at archlinux32 dot org.
summaryrefslogtreecommitdiff
path: root/archinstall/lib/user_interaction
diff options
context:
space:
mode:
Diffstat (limited to 'archinstall/lib/user_interaction')
-rw-r--r--archinstall/lib/user_interaction/__init__.py10
-rw-r--r--archinstall/lib/user_interaction/disk_conf.py391
-rw-r--r--archinstall/lib/user_interaction/general_conf.py244
-rw-r--r--archinstall/lib/user_interaction/locale_conf.py45
-rw-r--r--archinstall/lib/user_interaction/manage_users_conf.py106
-rw-r--r--archinstall/lib/user_interaction/network_conf.py173
-rw-r--r--archinstall/lib/user_interaction/save_conf.py113
-rw-r--r--archinstall/lib/user_interaction/system_conf.py117
-rw-r--r--archinstall/lib/user_interaction/utils.py34
9 files changed, 0 insertions, 1233 deletions
diff --git a/archinstall/lib/user_interaction/__init__.py b/archinstall/lib/user_interaction/__init__.py
deleted file mode 100644
index 5ee89de0..00000000
--- a/archinstall/lib/user_interaction/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from .manage_users_conf import ask_for_additional_users
-from .locale_conf import select_locale_lang, select_locale_enc
-from .system_conf import select_kernel, select_driver, ask_for_bootloader, ask_for_swap
-from .network_conf import ask_to_configure_network
-from .general_conf import (
- ask_ntp, ask_for_a_timezone, ask_for_audio_selection, select_language, select_mirror_regions,
- select_archinstall_language, ask_additional_packages_to_install,
- select_additional_repositories, ask_hostname, add_number_of_parrallel_downloads
-)
-from .utils import get_password
diff --git a/archinstall/lib/user_interaction/disk_conf.py b/archinstall/lib/user_interaction/disk_conf.py
deleted file mode 100644
index a77e950a..00000000
--- a/archinstall/lib/user_interaction/disk_conf.py
+++ /dev/null
@@ -1,391 +0,0 @@
-from __future__ import annotations
-
-import logging
-from pathlib import Path
-from typing import Any, TYPE_CHECKING, Optional, List, Tuple
-
-from .. import disk
-from ..hardware import has_uefi
-from ..menu import Menu, MenuSelectionType, TableMenu
-from ..output import FormattedOutput
-from ..output import log
-from ..utils.util import prompt_dir
-
-if TYPE_CHECKING:
- _: Any
-
-
-def select_devices(preset: List[disk.BDevice] = []) -> List[disk.BDevice]:
- """
- Asks the user to select one or multiple devices
-
- :return: List of selected devices
- :rtype: list
- """
-
- def _preview_device_selection(selection: disk._DeviceInfo) -> Optional[str]:
- dev = disk.device_handler.get_device(selection.path)
- if dev and dev.partition_infos:
- return FormattedOutput.as_table(dev.partition_infos)
- return None
-
- if preset is None:
- preset = []
-
- title = str(_('Select one or more devices to use and configure'))
- warning = str(_('If you reset the device selection this will also reset the current disk layout. Are you sure?'))
-
- devices = disk.device_handler.devices
- options = [d.device_info for d in devices]
- preset_value = [p.device_info for p in preset]
-
- choice = TableMenu(
- title,
- data=options,
- multi=True,
- preset=preset_value,
- preview_command=_preview_device_selection,
- preview_title=str(_('Existing Partitions')),
- preview_size=0.2,
- allow_reset=True,
- allow_reset_warning_msg=warning
- ).run()
-
- match choice.type_:
- case MenuSelectionType.Reset: return []
- case MenuSelectionType.Skip: return preset
- case MenuSelectionType.Selection:
- selected_device_info: List[disk._DeviceInfo] = choice.value # type: ignore
- selected_devices = []
-
- for device in devices:
- if device.device_info in selected_device_info:
- selected_devices.append(device)
-
- return selected_devices
-
-
-def get_default_partition_layout(
- devices: List[disk.BDevice],
- filesystem_type: Optional[disk.FilesystemType] = None,
- advanced_option: bool = False
-) -> List[disk.DeviceModification]:
-
- if len(devices) == 1:
- device_modification = suggest_single_disk_layout(
- devices[0],
- filesystem_type=filesystem_type,
- advanced_options=advanced_option
- )
- return [device_modification]
- else:
- return suggest_multi_disk_layout(
- devices,
- filesystem_type=filesystem_type,
- advanced_options=advanced_option
- )
-
-
-def _manual_partitioning(
- preset: List[disk.DeviceModification],
- devices: List[disk.BDevice]
-) -> List[disk.DeviceModification]:
- modifications = []
- for device in devices:
- mod = next(filter(lambda x: x.device == device, preset), None)
- if not mod:
- mod = disk.DeviceModification(device, wipe=False)
-
- if partitions := disk.manual_partitioning(device, preset=mod.partitions):
- mod.partitions = partitions
- modifications.append(mod)
-
- return modifications
-
-
-def select_disk_config(
- preset: Optional[disk.DiskLayoutConfiguration] = None,
- advanced_option: bool = False
-) -> Optional[disk.DiskLayoutConfiguration]:
- default_layout = disk.DiskLayoutType.Default.display_msg()
- manual_mode = disk.DiskLayoutType.Manual.display_msg()
- pre_mount_mode = disk.DiskLayoutType.Pre_mount.display_msg()
-
- options = [default_layout, manual_mode, pre_mount_mode]
- preset_value = preset.config_type.display_msg() if preset else None
- warning = str(_('Are you sure you want to reset this setting?'))
-
- choice = Menu(
- _('Select a partitioning option'),
- options,
- allow_reset=True,
- allow_reset_warning_msg=warning,
- sort=False,
- preview_size=0.2,
- preset_values=preset_value
- ).run()
-
- match choice.type_:
- case MenuSelectionType.Skip: return preset
- case MenuSelectionType.Reset: return None
- case MenuSelectionType.Selection:
- if choice.single_value == pre_mount_mode:
- output = "You will use whatever drive-setup is mounted at the specified directory\n"
- output += "WARNING: Archinstall won't check the suitability of this setup\n"
-
- path = prompt_dir(str(_('Enter the root directory of the mounted devices: ')), output)
- mods = disk.device_handler.detect_pre_mounted_mods(path)
-
- return disk.DiskLayoutConfiguration(
- config_type=disk.DiskLayoutType.Pre_mount,
- relative_mountpoint=path,
- device_modifications=mods
- )
-
- preset_devices = [mod.device for mod in preset.device_modifications] if preset else []
-
- devices = select_devices(preset_devices)
-
- if not devices:
- return None
-
- if choice.value == default_layout:
- modifications = get_default_partition_layout(devices, advanced_option=advanced_option)
- if modifications:
- return disk.DiskLayoutConfiguration(
- config_type=disk.DiskLayoutType.Default,
- device_modifications=modifications
- )
- elif choice.value == manual_mode:
- preset_mods = preset.device_modifications if preset else []
- modifications = _manual_partitioning(preset_mods, devices)
-
- if modifications:
- return disk.DiskLayoutConfiguration(
- config_type=disk.DiskLayoutType.Manual,
- device_modifications=modifications
- )
-
- return None
-
-
-def _boot_partition() -> disk.PartitionModification:
- if has_uefi():
- start = disk.Size(1, disk.Unit.MiB)
- size = disk.Size(512, disk.Unit.MiB)
- else:
- start = disk.Size(3, disk.Unit.MiB)
- size = disk.Size(203, disk.Unit.MiB)
-
- # boot partition
- return disk.PartitionModification(
- status=disk.ModificationStatus.Create,
- type=disk.PartitionType.Primary,
- start=start,
- length=size,
- mountpoint=Path('/boot'),
- fs_type=disk.FilesystemType.Fat32,
- flags=[disk.PartitionFlag.Boot]
- )
-
-
-def ask_for_main_filesystem_format(advanced_options=False) -> disk.FilesystemType:
- options = {
- 'btrfs': disk.FilesystemType.Btrfs,
- 'ext4': disk.FilesystemType.Ext4,
- 'xfs': disk.FilesystemType.Xfs,
- 'f2fs': disk.FilesystemType.F2fs
- }
-
- if advanced_options:
- options.update({'ntfs': disk.FilesystemType.Ntfs})
-
- prompt = _('Select which filesystem your main partition should use')
- choice = Menu(prompt, options, skip=False, sort=False).run()
- return options[choice.single_value]
-
-
-def suggest_single_disk_layout(
- device: disk.BDevice,
- filesystem_type: Optional[disk.FilesystemType] = None,
- advanced_options: bool = False,
- separate_home: Optional[bool] = None
-) -> disk.DeviceModification:
- if not filesystem_type:
- filesystem_type = ask_for_main_filesystem_format(advanced_options)
-
- min_size_to_allow_home_part = disk.Size(40, disk.Unit.GiB)
- root_partition_size = disk.Size(20, disk.Unit.GiB)
- using_subvolumes = False
- using_home_partition = False
- compression = False
- device_size_gib = device.device_info.total_size
-
- if filesystem_type == disk.FilesystemType.Btrfs:
- prompt = str(_('Would you like to use BTRFS subvolumes with a default structure?'))
- choice = Menu(prompt, Menu.yes_no(), skip=False, default_option=Menu.yes()).run()
- using_subvolumes = choice.value == Menu.yes()
-
- prompt = str(_('Would you like to use BTRFS compression?'))
- choice = Menu(prompt, Menu.yes_no(), skip=False, default_option=Menu.yes()).run()
- compression = choice.value == Menu.yes()
-
- device_modification = disk.DeviceModification(device, wipe=True)
-
- # Used for reference: https://wiki.archlinux.org/title/partitioning
- # 2 MiB is unallocated for GRUB on BIOS. Potentially unneeded for other bootloaders?
-
- # TODO: On BIOS, /boot partition is only needed if the drive will
- # be encrypted, otherwise it is not recommended. We should probably
- # add a check for whether the drive will be encrypted or not.
-
- # Increase the UEFI partition if UEFI is detected.
- # Also re-align the start to 1MiB since we don't need the first sectors
- # like we do in MBR layouts where the boot loader is installed traditionally.
-
- boot_partition = _boot_partition()
- device_modification.add_partition(boot_partition)
-
- if not using_subvolumes:
- if device_size_gib >= min_size_to_allow_home_part:
- if separate_home is None:
- prompt = str(_('Would you like to create a separate partition for /home?'))
- choice = Menu(prompt, Menu.yes_no(), skip=False, default_option=Menu.yes()).run()
- using_home_partition = choice.value == Menu.yes()
- elif separate_home is True:
- using_home_partition = True
- else:
- using_home_partition = False
-
- # root partition
- start = disk.Size(513, disk.Unit.MiB) if has_uefi() else disk.Size(206, disk.Unit.MiB)
-
- # Set a size for / (/root)
- if using_subvolumes or device_size_gib < min_size_to_allow_home_part or not using_home_partition:
- length = disk.Size(100, disk.Unit.Percent, total_size=device.device_info.total_size)
- else:
- length = min(device.device_info.total_size, root_partition_size)
-
- root_partition = disk.PartitionModification(
- status=disk.ModificationStatus.Create,
- type=disk.PartitionType.Primary,
- start=start,
- length=length,
- mountpoint=Path('/') if not using_subvolumes else None,
- fs_type=filesystem_type,
- mount_options=['compress=zstd'] if compression else [],
- )
- device_modification.add_partition(root_partition)
-
- if using_subvolumes:
- # 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
- subvolumes = [
- disk.SubvolumeModification(Path('@'), Path('/')),
- disk.SubvolumeModification(Path('@home'), Path('/home')),
- disk.SubvolumeModification(Path('@log'), Path('/var/log')),
- disk.SubvolumeModification(Path('@pkg'), Path('/var/cache/pacman/pkg')),
- disk.SubvolumeModification(Path('@.snapshots'), Path('/.snapshots'))
- ]
- root_partition.btrfs_subvols = subvolumes
- elif using_home_partition:
- # If we don't want to use subvolumes,
- # But we want to be able to re-use data between re-installs..
- # A second partition for /home would be nice if we have the space for it
- home_partition = disk.PartitionModification(
- status=disk.ModificationStatus.Create,
- type=disk.PartitionType.Primary,
- start=root_partition.length,
- length=disk.Size(100, disk.Unit.Percent, total_size=device.device_info.total_size),
- mountpoint=Path('/home'),
- fs_type=filesystem_type,
- mount_options=['compress=zstd'] if compression else []
- )
- device_modification.add_partition(home_partition)
-
- return device_modification
-
-
-def suggest_multi_disk_layout(
- devices: List[disk.BDevice],
- filesystem_type: Optional[disk.FilesystemType] = None,
- advanced_options: bool = False
-) -> List[disk.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 = disk.Size(40, disk.Unit.GiB)
- # rough estimate taking in to account user desktops etc. TODO: Catch user packages to detect size?
- desired_root_partition_size = disk.Size(20, disk.Unit.GiB)
- compression = False
-
- if not filesystem_type:
- filesystem_type = ask_for_main_filesystem_format(advanced_options)
-
- # find proper disk for /home
- possible_devices = list(filter(lambda x: x.device_info.total_size >= min_home_partition_size, devices))
- 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[disk.BDevice, Any]] = sorted(devices_delta.items(), key=lambda x: x[1])
- root_device: Optional[disk.BDevice] = sorted_delta[0][0]
-
- if home_device is None or root_device is None:
- text = _('The selected drives do not have the minimum capacity required for an automatic suggestion\n')
- text += _('Minimum capacity for /home partition: {}GiB\n').format(min_home_partition_size.format_size(disk.Unit.GiB))
- text += _('Minimum capacity for Arch Linux partition: {}GiB').format(desired_root_partition_size.format_size(disk.Unit.GiB))
- Menu(str(text), [str(_('Continue'))], skip=False).run()
- return []
-
- if filesystem_type == disk.FilesystemType.Btrfs:
- prompt = str(_('Would you like to use BTRFS compression?'))
- choice = Menu(prompt, Menu.yes_no(), skip=False, default_option=Menu.yes()).run()
- compression = choice.value == Menu.yes()
-
- device_paths = ', '.join([str(d.device_info.path) for d in devices])
- log(f"Suggesting multi-disk-layout for devices: {device_paths}", level=logging.DEBUG)
- log(f"/root: {root_device.device_info.path}", level=logging.DEBUG)
- log(f"/home: {home_device.device_info.path}", level=logging.DEBUG)
-
- root_device_modification = disk.DeviceModification(root_device, wipe=True)
- home_device_modification = disk.DeviceModification(home_device, wipe=True)
-
- # add boot partition to the root device
- boot_partition = _boot_partition()
- root_device_modification.add_partition(boot_partition)
-
- # add root partition to the root device
- root_partition = disk.PartitionModification(
- status=disk.ModificationStatus.Create,
- type=disk.PartitionType.Primary,
- start=disk.Size(513, disk.Unit.MiB) if has_uefi() else disk.Size(206, disk.Unit.MiB),
- length=disk.Size(100, disk.Unit.Percent, total_size=root_device.device_info.total_size),
- mountpoint=Path('/'),
- mount_options=['compress=zstd'] if compression else [],
- fs_type=filesystem_type
- )
- root_device_modification.add_partition(root_partition)
-
- # add home partition to home device
- home_partition = disk.PartitionModification(
- status=disk.ModificationStatus.Create,
- type=disk.PartitionType.Primary,
- start=disk.Size(1, disk.Unit.MiB),
- length=disk.Size(100, disk.Unit.Percent, total_size=home_device.device_info.total_size),
- mountpoint=Path('/home'),
- mount_options=['compress=zstd'] if compression else [],
- fs_type=filesystem_type,
- )
- home_device_modification.add_partition(home_partition)
-
- return [root_device_modification, home_device_modification]
diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py
deleted file mode 100644
index 9722dc4d..00000000
--- a/archinstall/lib/user_interaction/general_conf.py
+++ /dev/null
@@ -1,244 +0,0 @@
-from __future__ import annotations
-
-import logging
-import pathlib
-from typing import List, Any, Optional, Dict, TYPE_CHECKING
-
-from ..locale_helpers import list_keyboard_languages, list_timezones
-from ..menu import MenuSelectionType, Menu, TextInput
-from ..mirrors import list_mirrors
-from ..output import log
-from ..packages.packages import validate_package_list
-from ..storage import storage
-from ..translationhandler import Language
-
-if TYPE_CHECKING:
- _: Any
-
-
-def ask_ntp(preset: bool = True) -> bool:
- prompt = str(_('Would you like to use automatic time synchronization (NTP) with the default time servers?\n'))
- prompt += str(_('Hardware time and other post-configuration steps might be required in order for NTP to work.\nFor more information, please check the Arch wiki'))
- if preset:
- preset_val = Menu.yes()
- else:
- preset_val = Menu.no()
- choice = Menu(prompt, Menu.yes_no(), skip=False, preset_values=preset_val, default_option=Menu.yes()).run()
-
- return False if choice.value == Menu.no() else True
-
-
-def ask_hostname(preset: str = '') -> str:
- while True:
- hostname = TextInput(
- str(_('Desired hostname for the installation: ')),
- preset
- ).run().strip()
-
- if hostname:
- return hostname
-
-
-def ask_for_a_timezone(preset: Optional[str] = None) -> Optional[str]:
- timezones = list_timezones()
- default = 'UTC'
-
- choice = Menu(
- _('Select a timezone'),
- list(timezones),
- preset_values=preset,
- default_option=default
- ).run()
-
- match choice.type_:
- case MenuSelectionType.Skip: return preset
- case MenuSelectionType.Selection: return choice.single_value
-
- return None
-
-
-def ask_for_audio_selection(desktop: bool = True, preset: Optional[str] = None) -> Optional[str]:
- no_audio = str(_('No audio server'))
- choices = ['pipewire', 'pulseaudio'] if desktop else ['pipewire', 'pulseaudio', no_audio]
- default = 'pipewire' if desktop else no_audio
-
- choice = Menu(_('Choose an audio server'), choices, preset_values=preset, default_option=default).run()
-
- match choice.type_:
- case MenuSelectionType.Skip: return preset
- case MenuSelectionType.Selection: return choice.single_value
-
- return None
-
-
-def select_language(preset: Optional[str] = None) -> Optional[str]:
- """
- Asks the user to select a language
- Usually this is combined with :ref:`archinstall.list_keyboard_languages`.
-
- :return: The language/dictionary key of the selected language
- :rtype: str
- """
- kb_lang = list_keyboard_languages()
- # sort alphabetically and then by length
- sorted_kb_lang = sorted(sorted(list(kb_lang)), key=len)
-
- choice = Menu(
- _('Select keyboard layout'),
- sorted_kb_lang,
- preset_values=preset,
- sort=False
- ).run()
-
- match choice.type_:
- case MenuSelectionType.Skip: return preset
- case MenuSelectionType.Selection: return choice.single_value
-
- return None
-
-
-def select_mirror_regions(preset_values: Dict[str, Any] = {}) -> Dict[str, Any]:
- """
- Asks the user to select a mirror or region
- Usually this is combined with :ref:`archinstall.list_mirrors`.
-
- :return: The dictionary information about a mirror/region.
- :rtype: dict
- """
- if preset_values is None:
- preselected = None
- else:
- preselected = list(preset_values.keys())
-
- mirrors = list_mirrors()
-
- choice = Menu(
- _('Select one of the regions to download packages from'),
- list(mirrors.keys()),
- preset_values=preselected,
- multi=True,
- allow_reset=True
- ).run()
-
- match choice.type_:
- case MenuSelectionType.Reset:
- return {}
- case MenuSelectionType.Skip:
- return preset_values
- case MenuSelectionType.Selection:
- return {selected: mirrors[selected] for selected in choice.multi_value}
-
- return {}
-
-
-def select_archinstall_language(languages: List[Language], preset: Language) -> Language:
- # these are the displayed language names which can either be
- # the english name of a language or, if present, the
- # name of the language in its own language
- options = {lang.display_name: lang for lang in languages}
-
- title = 'NOTE: If a language can not displayed properly, a proper font must be set manually in the console.\n'
- title += 'All available fonts can be found in "/usr/share/kbd/consolefonts"\n'
- title += 'e.g. setfont LatGrkCyr-8x16 (to display latin/greek/cyrillic characters)\n'
-
- choice = Menu(
- title,
- list(options.keys()),
- default_option=preset.display_name,
- preview_size=0.5
- ).run()
-
- match choice.type_:
- case MenuSelectionType.Skip: return preset
- case MenuSelectionType.Selection: return options[choice.single_value]
-
- raise ValueError('Language selection not handled')
-
-
-def ask_additional_packages_to_install(pre_set_packages: List[str] = []) -> List[str]:
- # Additional packages (with some light weight error handling for invalid package names)
- print(_('Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed.'))
- print(_('If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt.'))
-
- def read_packages(already_defined: list = []) -> list:
- display = ' '.join(already_defined)
- input_packages = TextInput(_('Write additional packages to install (space separated, leave blank to skip): '), display).run().strip()
- return input_packages.split() if input_packages else []
-
- pre_set_packages = pre_set_packages if pre_set_packages else []
- packages = read_packages(pre_set_packages)
-
- if not storage['arguments']['offline'] and not storage['arguments']['no_pkg_lookups']:
- while True:
- if len(packages):
- # Verify packages that were given
- print(_("Verifying that additional packages exist (this might take a few seconds)"))
- valid, invalid = validate_package_list(packages)
-
- if invalid:
- log(f"Some packages could not be found in the repository: {invalid}", level=logging.WARNING, fg='red')
- packages = read_packages(valid)
- continue
- break
-
- return packages
-
-
-def add_number_of_parrallel_downloads(input_number :Optional[int] = None) -> Optional[int]:
- max_downloads = 5
- print(_(f"This option enables the number of parallel downloads that can occur during installation"))
- print(_(f"Enter the number of parallel downloads to be enabled.\n (Enter a value between 1 to {max_downloads})\nNote:"))
- print(_(f" - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )"))
- print(_(f" - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )"))
- print(_(f" - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )"))
-
- while True:
- try:
- input_number = int(TextInput(_("[Default value: 0] > ")).run().strip() or 0)
- if input_number <= 0:
- input_number = 0
- elif input_number > max_downloads:
- input_number = max_downloads
- break
- except:
- print(_(f"Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]"))
-
- pacman_conf_path = pathlib.Path("/etc/pacman.conf")
- with pacman_conf_path.open() as f:
- pacman_conf = f.read().split("\n")
-
- with pacman_conf_path.open("w") as fwrite:
- for line in pacman_conf:
- if "ParallelDownloads" in line:
- fwrite.write(f"ParallelDownloads = {input_number+1}\n") if not input_number == 0 else fwrite.write("#ParallelDownloads = 0\n")
- else:
- fwrite.write(f"{line}\n")
-
- return input_number
-
-
-def select_additional_repositories(preset: List[str]) -> List[str]:
- """
- Allows the user to select additional repositories (multilib, and testing) if desired.
-
- :return: The string as a selected repository
- :rtype: string
- """
-
- repositories = ["multilib", "testing"]
-
- choice = Menu(
- _('Choose which optional additional repositories to enable'),
- repositories,
- sort=False,
- multi=True,
- preset_values=preset,
- allow_reset=True
- ).run()
-
- match choice.type_:
- case MenuSelectionType.Skip: return preset
- case MenuSelectionType.Reset: return []
- case MenuSelectionType.Selection: return choice.single_value
-
- return []
diff --git a/archinstall/lib/user_interaction/locale_conf.py b/archinstall/lib/user_interaction/locale_conf.py
deleted file mode 100644
index cdc3423a..00000000
--- a/archinstall/lib/user_interaction/locale_conf.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from __future__ import annotations
-
-from typing import Any, TYPE_CHECKING, Optional
-
-from ..locale_helpers import list_locales
-from ..menu import Menu, MenuSelectionType
-
-if TYPE_CHECKING:
- _: Any
-
-
-def select_locale_lang(preset: Optional[str] = None) -> Optional[str]:
- locales = list_locales()
- locale_lang = set([locale.split()[0] for locale in locales])
-
- choice = Menu(
- _('Choose which locale language to use'),
- list(locale_lang),
- sort=True,
- preset_values=preset
- ).run()
-
- match choice.type_:
- case MenuSelectionType.Selection: return choice.single_value
- case MenuSelectionType.Skip: return preset
-
- return None
-
-
-def select_locale_enc(preset: Optional[str] = None) -> Optional[str]:
- locales = list_locales()
- locale_enc = set([locale.split()[1] for locale in locales])
-
- choice = Menu(
- _('Choose which locale encoding to use'),
- list(locale_enc),
- sort=True,
- preset_values=preset
- ).run()
-
- match choice.type_:
- case MenuSelectionType.Selection: return choice.single_value
- case MenuSelectionType.Skip: return preset
-
- return None
diff --git a/archinstall/lib/user_interaction/manage_users_conf.py b/archinstall/lib/user_interaction/manage_users_conf.py
deleted file mode 100644
index 879578da..00000000
--- a/archinstall/lib/user_interaction/manage_users_conf.py
+++ /dev/null
@@ -1,106 +0,0 @@
-from __future__ import annotations
-
-import re
-from typing import Any, Dict, TYPE_CHECKING, List, Optional
-
-from .utils import get_password
-from ..menu import Menu, ListManager
-from ..models.users import User
-from ..output import FormattedOutput
-
-if TYPE_CHECKING:
- _: Any
-
-
-class UserList(ListManager):
- """
- subclass of ListManager for the managing of user accounts
- """
-
- def __init__(self, prompt: str, lusers: List[User]):
- self._actions = [
- str(_('Add a user')),
- str(_('Change password')),
- str(_('Promote/Demote user')),
- str(_('Delete User'))
- ]
- super().__init__(prompt, lusers, [self._actions[0]], self._actions[1:])
-
- def reformat(self, data: List[User]) -> Dict[str, Any]:
- table = FormattedOutput.as_table(data)
- rows = table.split('\n')
-
- # these are the header rows of the table and do not map to any User obviously
- # we're adding 2 spaces as prefix because the menu selector '> ' will be put before
- # the selectable rows so the header has to be aligned
- display_data: Dict[str, Optional[User]] = {f' {rows[0]}': None, f' {rows[1]}': None}
-
- for row, user in zip(rows[2:], data):
- row = row.replace('|', '\\|')
- display_data[row] = user
-
- return display_data
-
- def selected_action_display(self, user: User) -> str:
- return user.username
-
- def handle_action(self, action: str, entry: Optional[User], data: List[User]) -> List[User]:
- if action == self._actions[0]: # add
- new_user = self._add_user()
- if new_user is not None:
- # in case a user with the same username as an existing user
- # was created we'll replace the existing one
- data = [d for d in data if d.username != new_user.username]
- data += [new_user]
- elif action == self._actions[1] and entry: # change password
- prompt = str(_('Password for user "{}": ').format(entry.username))
- new_password = get_password(prompt=prompt)
- if new_password:
- user = next(filter(lambda x: x == entry, data))
- user.password = new_password
- elif action == self._actions[2] and entry: # promote/demote
- user = next(filter(lambda x: x == entry, data))
- user.sudo = False if user.sudo else True
- elif action == self._actions[3] and entry: # delete
- data = [d for d in data if d != entry]
-
- return data
-
- def _check_for_correct_username(self, username: str) -> bool:
- if re.match(r'^[a-z_][a-z0-9_-]*\$?$', username) and len(username) <= 32:
- return True
- return False
-
- def _add_user(self) -> Optional[User]:
- prompt = '\n\n' + str(_('Enter username (leave blank to skip): '))
-
- while True:
- username = input(prompt).strip(' ')
- if not username:
- return None
- if not self._check_for_correct_username(username):
- error_prompt = str(_("The username you entered is invalid. Try again"))
- print(error_prompt)
- else:
- break
-
- password = get_password(prompt=str(_('Password for user "{}": ').format(username)))
-
- if not password:
- return None
-
- choice = Menu(
- str(_('Should "{}" be a superuser (sudo)?')).format(username), Menu.yes_no(),
- skip=False,
- default_option=Menu.yes(),
- clear_screen=False,
- show_search_hint=False
- ).run()
-
- sudo = True if choice.value == Menu.yes() else False
- return User(username, password, sudo)
-
-
-def ask_for_additional_users(prompt: str = '', defined_users: List[User] = []) -> List[User]:
- users = UserList(prompt, defined_users).run()
- return users
diff --git a/archinstall/lib/user_interaction/network_conf.py b/archinstall/lib/user_interaction/network_conf.py
deleted file mode 100644
index b682c1d2..00000000
--- a/archinstall/lib/user_interaction/network_conf.py
+++ /dev/null
@@ -1,173 +0,0 @@
-from __future__ import annotations
-
-import ipaddress
-import logging
-from typing import Any, Optional, TYPE_CHECKING, List, Union, Dict
-
-from ..menu import MenuSelectionType, TextInput
-from ..models.network_configuration import NetworkConfiguration, NicType
-
-from ..networking import list_interfaces
-from ..output import log, FormattedOutput
-from ..menu import ListManager, Menu
-
-if TYPE_CHECKING:
- _: Any
-
-
-class ManualNetworkConfig(ListManager):
- """
- subclass of ListManager for the managing of network configurations
- """
-
- def __init__(self, prompt: str, ifaces: List[NetworkConfiguration]):
- self._actions = [
- str(_('Add interface')),
- str(_('Edit interface')),
- str(_('Delete interface'))
- ]
-
- super().__init__(prompt, ifaces, [self._actions[0]], self._actions[1:])
-
- def reformat(self, data: List[NetworkConfiguration]) -> Dict[str, Optional[NetworkConfiguration]]:
- table = FormattedOutput.as_table(data)
- rows = table.split('\n')
-
- # these are the header rows of the table and do not map to any User obviously
- # we're adding 2 spaces as prefix because the menu selector '> ' will be put before
- # the selectable rows so the header has to be aligned
- display_data: Dict[str, Optional[NetworkConfiguration]] = {f' {rows[0]}': None, f' {rows[1]}': None}
-
- for row, iface in zip(rows[2:], data):
- row = row.replace('|', '\\|')
- display_data[row] = iface
-
- return display_data
-
- def selected_action_display(self, iface: NetworkConfiguration) -> str:
- return iface.iface if iface.iface else ''
-
- def handle_action(self, action: str, entry: Optional[NetworkConfiguration], data: List[NetworkConfiguration]):
- if action == self._actions[0]: # add
- iface_name = self._select_iface(data)
- if iface_name:
- iface = NetworkConfiguration(NicType.MANUAL, iface=iface_name)
- iface = self._edit_iface(iface)
- data += [iface]
- elif entry:
- if action == self._actions[1]: # edit interface
- data = [d for d in data if d.iface != entry.iface]
- data.append(self._edit_iface(entry))
- elif action == self._actions[2]: # delete
- data = [d for d in data if d != entry]
-
- return data
-
- def _select_iface(self, data: List[NetworkConfiguration]) -> Optional[Any]:
- all_ifaces = list_interfaces().values()
- existing_ifaces = [d.iface for d in data]
- available = set(all_ifaces) - set(existing_ifaces)
- choice = Menu(str(_('Select interface to add')), list(available), skip=True).run()
-
- if choice.type_ == MenuSelectionType.Skip:
- return None
-
- return choice.value
-
- def _edit_iface(self, edit_iface: NetworkConfiguration):
- iface_name = edit_iface.iface
- modes = ['DHCP (auto detect)', 'IP (static)']
- default_mode = 'DHCP (auto detect)'
-
- prompt = _('Select which mode to configure for "{}" or skip to use default mode "{}"').format(iface_name, default_mode)
- mode = Menu(prompt, modes, default_option=default_mode, skip=False).run()
-
- if mode.value == 'IP (static)':
- while 1:
- prompt = _('Enter the IP and subnet for {} (example: 192.168.0.5/24): ').format(iface_name)
- ip = TextInput(prompt, edit_iface.ip).run().strip()
- # Implemented new check for correct IP/subnet input
- try:
- ipaddress.ip_interface(ip)
- break
- except ValueError:
- log("You need to enter a valid IP in IP-config mode.", level=logging.WARNING, fg='red')
-
- # Implemented new check for correct gateway IP address
- gateway = None
-
- while 1:
- gateway = TextInput(
- _('Enter your gateway (router) IP address or leave blank for none: '),
- edit_iface.gateway
- ).run().strip()
- try:
- if len(gateway) > 0:
- ipaddress.ip_address(gateway)
- break
- except ValueError:
- log("You need to enter a valid gateway (router) IP address.", level=logging.WARNING, fg='red')
-
- if edit_iface.dns:
- display_dns = ' '.join(edit_iface.dns)
- else:
- display_dns = None
- dns_input = TextInput(_('Enter your DNS servers (space separated, blank for none): '), display_dns).run().strip()
-
- dns = []
- if len(dns_input):
- dns = dns_input.split(' ')
-
- return NetworkConfiguration(NicType.MANUAL, iface=iface_name, ip=ip, gateway=gateway, dns=dns, dhcp=False)
- else:
- # this will contain network iface names
- return NetworkConfiguration(NicType.MANUAL, iface=iface_name)
-
-
-def ask_to_configure_network(
- preset: Union[NetworkConfiguration, List[NetworkConfiguration]]
-) -> Optional[NetworkConfiguration | List[NetworkConfiguration]]:
- """
- Configure the network on the newly installed system
- """
- network_options = {
- 'none': str(_('No network configuration')),
- 'iso_config': str(_('Copy ISO network configuration to installation')),
- 'network_manager': str(_('Use NetworkManager (necessary to configure internet graphically in GNOME and KDE)')),
- 'manual': str(_('Manual configuration'))
- }
- # for this routine it's easier to set the cursor position rather than a preset value
- cursor_idx = None
-
- if preset and not isinstance(preset, list):
- if preset.type == 'iso_config':
- cursor_idx = 0
- elif preset.type == 'network_manager':
- cursor_idx = 1
-
- warning = str(_('Are you sure you want to reset this setting?'))
-
- choice = Menu(
- _('Select one network interface to configure'),
- list(network_options.values()),
- cursor_index=cursor_idx,
- sort=False,
- allow_reset=True,
- allow_reset_warning_msg=warning
- ).run()
-
- match choice.type_:
- case MenuSelectionType.Skip: return preset
- case MenuSelectionType.Reset: return None
-
- if choice.value == network_options['none']:
- return None
- elif choice.value == network_options['iso_config']:
- return NetworkConfiguration(NicType.ISO)
- elif choice.value == network_options['network_manager']:
- return NetworkConfiguration(NicType.NM)
- elif choice.value == network_options['manual']:
- preset_ifaces = preset if isinstance(preset, list) else []
- return ManualNetworkConfig('Configure interfaces', preset_ifaces).run()
-
- return preset
diff --git a/archinstall/lib/user_interaction/save_conf.py b/archinstall/lib/user_interaction/save_conf.py
deleted file mode 100644
index e05b9afe..00000000
--- a/archinstall/lib/user_interaction/save_conf.py
+++ /dev/null
@@ -1,113 +0,0 @@
-from __future__ import annotations
-
-import logging
-
-from pathlib import Path
-from typing import Any, Dict, TYPE_CHECKING
-
-from ..general import SysCommand
-from ..menu import Menu
-from ..menu.menu import MenuSelectionType
-from ..output import log
-from ..configuration import ConfigurationOutput
-
-if TYPE_CHECKING:
- _: Any
-
-
-def save_config(config: Dict):
- def preview(selection: str):
- if options['user_config'] == selection:
- serialized = config_output.user_config_to_json()
- return f'{config_output.user_configuration_file}\n{serialized}'
- elif options['user_creds'] == selection:
- if maybe_serial := config_output.user_credentials_to_json():
- return f'{config_output.user_credentials_file}\n{maybe_serial}'
- else:
- return str(_('No configuration'))
- elif options['all'] == selection:
- output = f'{config_output.user_configuration_file}\n'
- if config_output.user_credentials_to_json():
- output += f'{config_output.user_credentials_file}\n'
- return output[:-1]
- return None
-
- config_output = ConfigurationOutput(config)
-
- options = {
- 'user_config': str(_('Save user configuration')),
- 'user_creds': str(_('Save user credentials')),
- 'disk_layout': str(_('Save disk layout')),
- 'all': str(_('Save all'))
- }
-
- choice = Menu(
- _('Choose which configuration to save'),
- list(options.values()),
- sort=False,
- skip=True,
- preview_size=0.75,
- preview_command=preview
- ).run()
-
- if choice.type_ == MenuSelectionType.Skip:
- return
-
- save_config_value = choice.single_value
- saving_key = [k for k, v in options.items() if v == save_config_value][0]
-
- dirs_to_exclude = [
- '/bin',
- '/dev',
- '/lib',
- '/lib64',
- '/lost+found',
- '/opt',
- '/proc',
- '/run',
- '/sbin',
- '/srv',
- '/sys',
- '/usr',
- '/var',
- ]
-
- log('Ignore configuration option folders: ' + ','.join(dirs_to_exclude), level=logging.DEBUG)
- log(_('Finding possible directories to save configuration files ...'), level=logging.INFO)
-
- find_exclude = '-path ' + ' -prune -o -path '.join(dirs_to_exclude) + ' -prune '
- file_picker_command = f'find / {find_exclude} -o -type d -print0'
-
- directories = SysCommand(file_picker_command).decode()
-
- if directories is None:
- raise ValueError('Failed to retrieve possible configuration directories')
-
- possible_save_dirs = list(filter(None, directories.split('\x00')))
-
- selection = Menu(
- _('Select directory (or directories) for saving configuration files'),
- possible_save_dirs,
- multi=True,
- skip=True,
- allow_reset=False,
- ).run()
-
- match selection.type_:
- case MenuSelectionType.Skip:
- return
-
- save_dirs = selection.multi_value
-
- log(f'Saving {saving_key} configuration files to {save_dirs}', level=logging.DEBUG)
-
- if save_dirs is not None:
- for save_dir_str in save_dirs:
- save_dir = Path(save_dir_str)
- if options['user_config'] == save_config_value:
- config_output.save_user_config(save_dir)
- elif options['user_creds'] == save_config_value:
- config_output.save_user_creds(save_dir)
- elif options['all'] == save_config_value:
- config_output.save_user_config(save_dir)
- config_output.save_user_creds(save_dir)
diff --git a/archinstall/lib/user_interaction/system_conf.py b/archinstall/lib/user_interaction/system_conf.py
deleted file mode 100644
index 3f57d0e7..00000000
--- a/archinstall/lib/user_interaction/system_conf.py
+++ /dev/null
@@ -1,117 +0,0 @@
-from __future__ import annotations
-
-from typing import List, Any, Dict, TYPE_CHECKING, Optional
-
-from ..hardware import AVAILABLE_GFX_DRIVERS, has_uefi, has_amd_graphics, has_intel_graphics, has_nvidia_graphics
-from ..menu import MenuSelectionType, Menu
-from ..models.bootloader import Bootloader
-
-if TYPE_CHECKING:
- _: Any
-
-
-def select_kernel(preset: List[str] = []) -> List[str]:
- """
- Asks the user to select a kernel for system.
-
- :return: The string as a selected kernel
- :rtype: string
- """
-
- kernels = ["linux", "linux-lts", "linux-zen", "linux-hardened"]
- default_kernel = "linux"
-
- warning = str(_('Are you sure you want to reset this setting?'))
-
- choice = Menu(
- _('Choose which kernels to use or leave blank for default "{}"').format(default_kernel),
- kernels,
- sort=True,
- multi=True,
- preset_values=preset,
- allow_reset=True,
- allow_reset_warning_msg=warning
- ).run()
-
- match choice.type_:
- case MenuSelectionType.Skip: return preset
- case MenuSelectionType.Reset: return []
- case MenuSelectionType.Selection: return choice.value # type: ignore
-
-
-def ask_for_bootloader(preset: Bootloader) -> Bootloader:
- # when the system only supports grub
- if not has_uefi():
- options = [Bootloader.Grub.value]
- default = Bootloader.Grub.value
- else:
- options = Bootloader.values()
- default = Bootloader.Systemd.value
-
- preset_value = preset.value if preset else None
-
- choice = Menu(
- _('Choose a bootloader'),
- options,
- preset_values=preset_value,
- sort=False,
- default_option=default
- ).run()
-
- match choice.type_:
- case MenuSelectionType.Skip: return preset
- case MenuSelectionType.Selection: return Bootloader(choice.value)
-
- return preset
-
-
-def select_driver(options: Dict[str, Any] = {}, current_value: Optional[str] = None) -> Optional[str]:
- """
- Some what convoluted function, whose job is simple.
- Select a graphics driver from a pre-defined set of popular options.
-
- (The template xorg is for beginner users, not advanced, and should
- there for appeal to the general public first and edge cases later)
- """
-
- if not options:
- options = AVAILABLE_GFX_DRIVERS
-
- drivers = sorted(list(options.keys()))
-
- if drivers:
- title = ''
- if has_amd_graphics():
- title += str(_('For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options.')) + '\n'
- if has_intel_graphics():
- title += str(_('For the best compatibility with your Intel hardware, you may want to use either the all open-source or Intel options.\n'))
- if has_nvidia_graphics():
- title += str(_('For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n'))
-
- title += str(_('\nSelect a graphics driver or leave blank to install all open-source drivers'))
-
- preset = current_value if current_value else None
- choice = Menu(title, drivers, preset_values=preset).run()
-
- if choice.type_ != MenuSelectionType.Selection:
- return None
-
- return choice.value # type: ignore
-
- return current_value
-
-
-def ask_for_swap(preset: bool = True) -> bool:
- if preset:
- preset_val = Menu.yes()
- else:
- preset_val = Menu.no()
-
- prompt = _('Would you like to use swap on zram?')
- choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes(), preset_values=preset_val).run()
-
- match choice.type_:
- case MenuSelectionType.Skip: return preset
- case MenuSelectionType.Selection: return False if choice.value == Menu.no() else True
-
- return preset
diff --git a/archinstall/lib/user_interaction/utils.py b/archinstall/lib/user_interaction/utils.py
deleted file mode 100644
index 918945c0..00000000
--- a/archinstall/lib/user_interaction/utils.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from __future__ import annotations
-
-import getpass
-from typing import Any, Optional, TYPE_CHECKING
-
-from ..models import PasswordStrength
-from ..output import log
-
-if TYPE_CHECKING:
- _: Any
-
-# used for signal handler
-SIG_TRIGGER = None
-
-
-def get_password(prompt: str = '') -> Optional[str]:
- if not prompt:
- prompt = _("Enter a password: ")
-
- while password := getpass.getpass(prompt):
- if len(password.strip()) <= 0:
- break
-
- strength = PasswordStrength.strength(password)
- log(f'Password strength: {strength.value}', fg=strength.color())
-
- passwd_verification = getpass.getpass(prompt=_('And one more time for verification: '))
- if password != passwd_verification:
- log(' * Passwords did not match * ', fg='red')
- continue
-
- return password
-
- return None