index : archinstall32 | |
Archlinux32 installer | gitolite user |
summaryrefslogtreecommitdiff |
-rw-r--r-- | archinstall/lib/user_interaction/__init__.py | 12 | ||||
-rw-r--r-- | archinstall/lib/user_interaction/backwards_compatible_conf.py | 95 | ||||
-rw-r--r-- | archinstall/lib/user_interaction/disk_conf.py | 86 | ||||
-rw-r--r-- | archinstall/lib/user_interaction/general_conf.py | 271 | ||||
-rw-r--r-- | archinstall/lib/user_interaction/locale_conf.py | 42 | ||||
-rw-r--r-- | archinstall/lib/user_interaction/manage_users_conf.py | 103 | ||||
-rw-r--r-- | archinstall/lib/user_interaction/network_conf.py | 175 | ||||
-rw-r--r-- | archinstall/lib/user_interaction/partitioning_conf.py | 362 | ||||
-rw-r--r-- | archinstall/lib/user_interaction/save_conf.py | 135 | ||||
-rw-r--r-- | archinstall/lib/user_interaction/subvolume_config.py | 98 | ||||
-rw-r--r-- | archinstall/lib/user_interaction/system_conf.py | 168 | ||||
-rw-r--r-- | archinstall/lib/user_interaction/utils.py | 79 |
diff --git a/archinstall/lib/user_interaction/__init__.py b/archinstall/lib/user_interaction/__init__.py deleted file mode 100644 index 2bc46759..00000000 --- a/archinstall/lib/user_interaction/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -from .save_conf import save_config -from .manage_users_conf import ask_for_additional_users -from .backwards_compatible_conf import generic_select, generic_multi_select -from .locale_conf import select_locale_lang, select_locale_enc -from .system_conf import select_kernel, select_harddrives, select_driver, ask_for_bootloader, ask_for_swap -from .network_conf import ask_to_configure_network -from .partitioning_conf import select_partition -from .general_conf import (ask_ntp, ask_for_a_timezone, ask_for_audio_selection, select_language, select_mirror_regions, - select_profile, select_archinstall_language, ask_additional_packages_to_install, - select_additional_repositories, ask_hostname, add_number_of_parrallel_downloads) -from .disk_conf import ask_for_main_filesystem_format, select_individual_blockdevice_usage, select_disk_layout, select_disk -from .utils import get_password, do_countdown diff --git a/archinstall/lib/user_interaction/backwards_compatible_conf.py b/archinstall/lib/user_interaction/backwards_compatible_conf.py deleted file mode 100644 index 296572d2..00000000 --- a/archinstall/lib/user_interaction/backwards_compatible_conf.py +++ /dev/null @@ -1,95 +0,0 @@ -from __future__ import annotations - -import logging -import sys -from collections.abc import Iterable -from typing import Any, Union, TYPE_CHECKING - -from ..exceptions import RequirementError -from ..menu import Menu -from ..output import log - -if TYPE_CHECKING: - _: Any - - -def generic_select( - p_options: Union[list, dict], - input_text: str = '', - allow_empty_input: bool = True, - options_output: bool = True, # function not available - sort: bool = False, - multi: bool = False, - default: Any = None) -> Any: - """ - A generic select function that does not output anything - other than the options and their indexes. As an example: - - generic_select(["first", "second", "third option"]) - > first - second - third option - When the user has entered the option correctly, - this function returns an item from list, a string, or None - - Options can be any iterable. - Duplicate entries are not checked, but the results with them are unreliable. Which element to choose from the duplicates depends on the return of the index() - Default value if not on the list of options will be added as the first element - sort will be handled by Menu() - """ - # We check that the options are iterable. If not we abort. Else we copy them to lists - # it options is a dictionary we use the values as entries of the list - # if options is a string object, each character becomes an entry - # if options is a list, we implictily build a copy to maintain immutability - if not isinstance(p_options, Iterable): - log(f"Objects of type {type(p_options)} is not iterable, and are not supported at generic_select", fg="red") - log(f"invalid parameter at Menu() call was at <{sys._getframe(1).f_code.co_name}>", level=logging.WARNING) - raise RequirementError("generic_select() requires an iterable as option.") - - input_text = input_text if input_text else _('Select one of the values shown below: ') - - if isinstance(p_options, dict): - options = list(p_options.values()) - else: - options = list(p_options) - # check that the default value is in the list. If not it will become the first entry - if default and default not in options: - options.insert(0, default) - - # one of the drawbacks of the new interface is that in only allows string like options, so we do a conversion - # also for the default value if it exists - soptions = list(map(str, options)) - default_value = options[options.index(default)] if default else None - - selected_option = Menu(input_text, - soptions, - skip=allow_empty_input, - multi=multi, - default_option=default_value, - sort=sort).run() - # we return the original objects, not the strings. - # options is the list with the original objects and soptions the list with the string values - # thru the map, we get from the value selected in soptions it index, and thu it the original object - if not selected_option: - return selected_option - elif isinstance(selected_option, list): # for multi True - selected_option = list(map(lambda x: options[soptions.index(x)], selected_option)) - else: # for multi False - selected_option = options[soptions.index(selected_option)] - return selected_option - - -def generic_multi_select(p_options: Union[list, dict], - text: str = '', - sort: bool = False, - default: Any = None, - allow_empty: bool = False) -> Any: - - text = text if text else _("Select one or more of the options below: ") - - return generic_select(p_options, - input_text=text, - allow_empty_input=allow_empty, - sort=sort, - multi=True, - default=default) diff --git a/archinstall/lib/user_interaction/disk_conf.py b/archinstall/lib/user_interaction/disk_conf.py deleted file mode 100644 index 554d13ef..00000000 --- a/archinstall/lib/user_interaction/disk_conf.py +++ /dev/null @@ -1,86 +0,0 @@ -from __future__ import annotations - -from typing import Any, Dict, TYPE_CHECKING, Optional - -from .partitioning_conf import manage_new_and_existing_partitions, get_default_partition_layout -from ..disk import BlockDevice -from ..exceptions import DiskError -from ..menu import Menu -from ..menu.menu import MenuSelectionType - -if TYPE_CHECKING: - _: Any - - -def ask_for_main_filesystem_format(advanced_options=False) -> str: - options = {'btrfs': 'btrfs', 'ext4': 'ext4', 'xfs': 'xfs', 'f2fs': 'f2fs'} - - advanced = {'ntfs': 'ntfs'} - - if advanced_options: - options.update(advanced) - - prompt = _('Select which filesystem your main partition should use') - choice = Menu(prompt, options, skip=False).run() - return choice.value - - -def select_individual_blockdevice_usage(block_devices: list) -> Dict[str, Any]: - result = {} - - for device in block_devices: - layout = manage_new_and_existing_partitions(device) - result[device.path] = layout - - return result - - -def select_disk_layout(preset: Optional[Dict[str, Any]], block_devices: list, advanced_options=False) -> Optional[Dict[str, Any]]: - wipe_mode = str(_('Wipe all selected drives and use a best-effort default partition layout')) - custome_mode = str(_('Select what to do with each individual drive (followed by partition usage)')) - modes = [wipe_mode, custome_mode] - - warning = str(_('Are you sure you want to reset this setting?')) - - choice = Menu( - _('Select what you wish to do with the selected block devices'), - modes, - allow_reset=True, - allow_reset_warning_msg=warning - ).run() - - match choice.type_: - case MenuSelectionType.Skip: return preset - case MenuSelectionType.Reset: return None - case MenuSelectionType.Selection: - if choice.value == wipe_mode: - return get_default_partition_layout(block_devices, advanced_options) - else: - return select_individual_blockdevice_usage(block_devices) - - -def select_disk(dict_o_disks: Dict[str, BlockDevice]) -> Optional[BlockDevice]: - """ - Asks the user to select a harddrive from the `dict_o_disks` selection. - Usually this is combined with :ref:`archinstall.list_drives`. - - :param dict_o_disks: A `dict` where keys are the drive-name, value should be a dict containing drive information. - :type dict_o_disks: dict - - :return: The name/path (the dictionary key) of the selected drive - :rtype: str - """ - drives = sorted(list(dict_o_disks.keys())) - if len(drives) >= 1: - title = str(_('You can skip selecting a drive and partitioning and use whatever drive-setup is mounted at /mnt (experimental)')) + '\n' - title += str(_('Select one of the disks or skip and use /mnt as default')) - - choice = Menu(title, drives).run() - - if choice.type_ == MenuSelectionType.Skip: - return None - - drive = dict_o_disks[choice.value] - return drive - - raise DiskError('select_disk() requires a non-empty dictionary of disks to select from.') diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py deleted file mode 100644 index fc7ded45..00000000 --- a/archinstall/lib/user_interaction/general_conf.py +++ /dev/null @@ -1,271 +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 Menu -from ..menu.menu import MenuSelectionType -from ..menu.text_input import TextInput -from ..mirrors import list_mirrors -from ..output import log -from ..packages.packages import validate_package_list -from ..profiles import Profile, list_profiles -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 = None) -> str: - hostname = TextInput(_('Desired hostname for the installation: '), preset).run().strip(' ') - return hostname - - -def ask_for_a_timezone(preset: str = None) -> 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.value - - -def ask_for_audio_selection(desktop: bool = True, preset: str = None) -> 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.value - - -def select_language(preset_value: str = None) -> 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) - - selected_lang = Menu( - _('Select keyboard layout'), - sorted_kb_lang, - preset_values=preset_value, - sort=False - ).run() - - if selected_lang.value is None: - return preset_value - - return selected_lang.value - - -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() - selected_mirror = Menu( - _('Select one of the regions to download packages from'), - list(mirrors.keys()), - preset_values=preselected, - multi=True, - allow_reset=True - ).run() - - match selected_mirror.type_: - case MenuSelectionType.Reset: return {} - case MenuSelectionType.Skip: return preset_values - case _: return {selected: mirrors[selected] for selected in selected_mirror.value} - - -def select_archinstall_language(languages: List[Language], preset_value: 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_value.display_name, - preview_size=0.5 - ).run() - - match choice.type_: - case MenuSelectionType.Skip: - return preset_value - case MenuSelectionType.Selection: - return options[choice.value] - - -def select_profile(preset) -> Optional[Profile]: - """ - # Asks the user to select a profile from the available profiles. - # - # :return: The name/dictionary key of the selected profile - # :rtype: str - # """ - top_level_profiles = sorted(list(list_profiles(filter_top_level_profiles=True))) - options = {} - - for profile in top_level_profiles: - profile = Profile(None, profile) - description = profile.get_profile_description() - - option = f'{profile.profile}: {description}' - options[option] = profile - - title = _('This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments') - warning = str(_('Are you sure you want to reset this setting?')) - - selection = Menu( - title=title, - p_options=list(options.keys()), - allow_reset=True, - allow_reset_warning_msg=warning - ).run() - - match selection.type_: - case MenuSelectionType.Selection: - return options[selection.value] if selection.value is not None else None - case MenuSelectionType.Reset: - storage['profile_minimal'] = False - storage['_selected_servers'] = [] - storage['_desktop_profile'] = None - storage['sway_sys_priv_ctrl'] = None - storage['arguments']['sway_sys_priv_ctrl'] = None - storage['arguments']['desktop-environment'] = None - storage['arguments']['gfx_driver'] = None - storage['arguments']['gfx_driver_packages'] = None - return None - case MenuSelectionType.Skip: - return None - - -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.value diff --git a/archinstall/lib/user_interaction/locale_conf.py b/archinstall/lib/user_interaction/locale_conf.py deleted file mode 100644 index bbbe070b..00000000 --- a/archinstall/lib/user_interaction/locale_conf.py +++ /dev/null @@ -1,42 +0,0 @@ -from __future__ import annotations - -from typing import Any, TYPE_CHECKING - -from ..locale_helpers import list_locales -from ..menu import Menu -from ..menu.menu import MenuSelectionType - -if TYPE_CHECKING: - _: Any - - -def select_locale_lang(preset: str = None) -> str: - locales = list_locales() - locale_lang = set([locale.split()[0] for locale in locales]) - - selected_locale = Menu( - _('Choose which locale language to use'), - list(locale_lang), - sort=True, - preset_values=preset - ).run() - - match selected_locale.type_: - case MenuSelectionType.Selection: return selected_locale.value - case MenuSelectionType.Skip: return preset - - -def select_locale_enc(preset: str = None) -> str: - locales = list_locales() - locale_enc = set([locale.split()[1] for locale in locales]) - - selected_locale = Menu( - _('Choose which locale encoding to use'), - list(locale_enc), - sort=True, - preset_values=preset - ).run() - - match selected_locale.type_: - case MenuSelectionType.Selection: return selected_locale.value - case MenuSelectionType.Skip: return preset 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 84ce3556..00000000 --- a/archinstall/lib/user_interaction/manage_users_conf.py +++ /dev/null @@ -1,103 +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 -from ..menu.list_manager import 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, User]: - 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 = {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]: # 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]: # promote/demote - user = next(filter(lambda x: x == entry, data)) - user.sudo = False if user.sudo else True - elif action == self._actions[3]: # 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): - prompt = str(_("The username you entered is invalid. Try again")) + '\n' + prompt - else: - break - - password = get_password(prompt=str(_('Password for user "{}": ').format(username))) - - choice = Menu( - str(_('Should "{}" be a superuser (sudo)?')).format(username), Menu.yes_no(), - skip=False, - default_option=Menu.no(), - 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 5e637f23..00000000 --- a/archinstall/lib/user_interaction/network_conf.py +++ /dev/null @@ -1,175 +0,0 @@ -from __future__ import annotations - -import ipaddress -import logging -from typing import Any, Optional, TYPE_CHECKING, List, Union, Dict - -from ..menu.menu import MenuSelectionType -from ..menu.text_input import TextInput -from ..models.network_configuration import NetworkConfiguration, NicType - -from ..networking import list_interfaces -from ..menu import Menu -from ..output import log, FormattedOutput -from ..menu.list_manager import ListManager - -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/partitioning_conf.py b/archinstall/lib/user_interaction/partitioning_conf.py deleted file mode 100644 index 0a5ede51..00000000 --- a/archinstall/lib/user_interaction/partitioning_conf.py +++ /dev/null @@ -1,362 +0,0 @@ -from __future__ import annotations - -import copy -from typing import List, Any, Dict, Union, TYPE_CHECKING, Callable, Optional - -from ..menu import Menu -from ..menu.menu import MenuSelectionType -from ..output import log, FormattedOutput - -from ..disk.validators import fs_types - -if TYPE_CHECKING: - from ..disk import BlockDevice - from ..disk.partition import Partition - _: Any - - -def partition_overlap(partitions: list, start: str, end: str) -> bool: - # TODO: Implement sanity check - return False - - -def current_partition_layout(partitions: List[Dict[str, Any]], with_idx: bool = False, with_title: bool = True) -> str: - - def do_padding(name: str, max_len: int): - spaces = abs(len(str(name)) - max_len) + 2 - pad_left = int(spaces / 2) - pad_right = spaces - pad_left - return f'{pad_right * " "}{name}{pad_left * " "}|' - - def flatten_data(data: Dict[str, Any]) -> Dict[str, Any]: - flattened = {} - for k, v in data.items(): - if k == 'filesystem': - flat = flatten_data(v) - flattened.update(flat) - elif k == 'btrfs': - # we're going to create a separate table for the btrfs subvolumes - pass - else: - flattened[k] = v - return flattened - - display_data: List[Dict[str, Any]] = [flatten_data(entry) for entry in partitions] - - column_names = {} - - # this will add an initial index to the table for each partition - if with_idx: - column_names['index'] = max([len(str(len(display_data))), len('index')]) - - # determine all attribute names and the max length - # of the value among all display_data to know the width - # of the table cells - for p in display_data: - for attribute, value in p.items(): - if attribute in column_names.keys(): - column_names[attribute] = max([column_names[attribute], len(str(value)), len(attribute)]) - else: - column_names[attribute] = max([len(str(value)), len(attribute)]) - - current_layout = '' - for name, max_len in column_names.items(): - current_layout += do_padding(name, max_len) - - current_layout = f'{current_layout[:-1]}\n{"-" * len(current_layout)}\n' - - for idx, p in enumerate(display_data): - row = '' - for name, max_len in column_names.items(): - if name == 'index': - row += do_padding(str(idx), max_len) - elif name in p: - row += do_padding(p[name], max_len) - else: - row += ' ' * (max_len + 2) + '|' - - current_layout += f'{row[:-1]}\n' - - # we'll create a separate table for the btrfs subvolumes - btrfs_subvolumes = [partition['btrfs']['subvolumes'] for partition in partitions if partition.get('btrfs', None)] - if len(btrfs_subvolumes) > 0: - for subvolumes in btrfs_subvolumes: - output = FormattedOutput.as_table(subvolumes) - current_layout += f'\n{output}' - - if with_title: - title = str(_('Current partition layout')) - return f'\n\n{title}:\n\n{current_layout}' - - return current_layout - - -def _get_partitions(partitions :List[Partition], filter_ :Callable = None) -> List[str]: - """ - filter allows to filter out the indexes once they are set. Should return True if element is to be included - """ - partition_indexes = [] - for i in range(len(partitions)): - if filter_: - if filter_(partitions[i]): - partition_indexes.append(str(i)) - else: - partition_indexes.append(str(i)) - - return partition_indexes - - -def select_partition( - title :str, - partitions :List[Partition], - multiple :bool = False, - filter_ :Callable = None -) -> Optional[int, List[int]]: - partition_indexes = _get_partitions(partitions, filter_) - - if len(partition_indexes) == 0: - return None - - choice = Menu(title, partition_indexes, multi=multiple).run() - - if choice.type_ == MenuSelectionType.Skip: - return None - - if isinstance(choice.value, list): - return [int(p) for p in choice.value] - else: - return int(choice.value) - - -def get_default_partition_layout( - block_devices: Union['BlockDevice', List['BlockDevice']], - advanced_options: bool = False -) -> Optional[Dict[str, Any]]: - from ..disk import suggest_single_disk_layout, suggest_multi_disk_layout - - if len(block_devices) == 1: - return suggest_single_disk_layout(block_devices[0], advanced_options=advanced_options) - else: - return suggest_multi_disk_layout(block_devices, advanced_options=advanced_options) - - -def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, Any]: # noqa: max-complexity: 50 - block_device_struct = {"partitions": [partition.__dump__() for partition in block_device.partitions.values()]} - original_layout = copy.deepcopy(block_device_struct) - - new_partition = str(_('Create a new partition')) - suggest_partition_layout = str(_('Suggest partition layout')) - delete_partition = str(_('Delete a partition')) - delete_all_partitions = str(_('Clear/Delete all partitions')) - assign_mount_point = str(_('Assign mount-point for a partition')) - mark_formatted = str(_('Mark/Unmark a partition to be formatted (wipes data)')) - mark_compressed = str(_('Mark/Unmark a partition as compressed (btrfs only)')) - mark_bootable = str(_('Mark/Unmark a partition as bootable (automatic for /boot)')) - set_filesystem_partition = str(_('Set desired filesystem for a partition')) - set_btrfs_subvolumes = str(_('Set desired subvolumes on a btrfs partition')) - save_and_exit = str(_('Save and exit')) - cancel = str(_('Cancel')) - - while True: - modes = [new_partition, suggest_partition_layout] - - if len(block_device_struct['partitions']) > 0: - modes += [ - delete_partition, - delete_all_partitions, - assign_mount_point, - mark_formatted, - mark_bootable, - mark_compressed, - set_filesystem_partition, - ] - - indexes = _get_partitions( - block_device_struct["partitions"], - filter_=lambda x: True if x.get('filesystem', {}).get('format') == 'btrfs' else False - ) - - if len(indexes) > 0: - modes += [set_btrfs_subvolumes] - - title = _('Select what to do with\n{}').format(block_device) - - # show current partition layout: - if len(block_device_struct["partitions"]): - title += current_partition_layout(block_device_struct['partitions']) + '\n' - - modes += [save_and_exit, cancel] - - task = Menu(title, modes, sort=False, skip=False).run() - task = task.value - - if task == cancel: - return original_layout - elif task == save_and_exit: - break - - if task == new_partition: - from ..disk import valid_parted_position - - # if partition_type == 'gpt': - # # https://www.gnu.org/software/parted/manual/html_node/mkpart.html - # # https://www.gnu.org/software/parted/manual/html_node/mklabel.html - # name = input("Enter a desired name for the partition: ").strip() - - fs_choice = Menu(_('Enter a desired filesystem type for the partition'), fs_types()).run() - - if fs_choice.type_ == MenuSelectionType.Skip: - continue - - prompt = str(_('Enter the start location (in parted units: s, GB, %, etc. ; default: {}): ')).format( - block_device.first_free_sector - ) - start = input(prompt).strip() - - if not start.strip(): - start = block_device.first_free_sector - end_suggested = block_device.first_end_sector - else: - end_suggested = '100%' - - prompt = str(_('Enter the end location (in parted units: s, GB, %, etc. ; ex: {}): ')).format( - end_suggested - ) - end = input(prompt).strip() - - if not end.strip(): - end = end_suggested - - if valid_parted_position(start) and valid_parted_position(end): - if partition_overlap(block_device_struct["partitions"], start, end): - log(f"This partition overlaps with other partitions on the drive! Ignoring this partition creation.", - fg="red") - continue - - block_device_struct["partitions"].append({ - "type": "primary", # Strictly only allowed under MS-DOS, but GPT accepts it so it's "safe" to inject - "start": start, - "size": end, - "mountpoint": None, - "wipe": True, - "filesystem": { - "format": fs_choice.value - } - }) - else: - log(f"Invalid start ({valid_parted_position(start)}) or end ({valid_parted_position(end)}) for this partition. Ignoring this partition creation.", - fg="red") - continue - elif task == suggest_partition_layout: - from ..disk import suggest_single_disk_layout - - if len(block_device_struct["partitions"]): - prompt = _('{}\ncontains queued partitions, this will remove those, are you sure?').format(block_device) - choice = Menu(prompt, Menu.yes_no(), default_option=Menu.no(), skip=False).run() - - if choice.value == Menu.no(): - continue - - block_device_struct.update(suggest_single_disk_layout(block_device)[block_device.path]) - else: - current_layout = current_partition_layout(block_device_struct['partitions'], with_idx=True) - - if task == delete_partition: - title = _('{}\n\nSelect by index which partitions to delete').format(current_layout) - to_delete = select_partition(title, block_device_struct["partitions"], multiple=True) - - if to_delete: - block_device_struct['partitions'] = [ - p for idx, p in enumerate(block_device_struct['partitions']) if idx not in to_delete - ] - elif task == mark_compressed: - title = _('{}\n\nSelect which partition to mark as bootable').format(current_layout) - partition = select_partition(title, block_device_struct["partitions"]) - - if partition is not None: - if "filesystem" not in block_device_struct["partitions"][partition]: - block_device_struct["partitions"][partition]["filesystem"] = {} - if "mount_options" not in block_device_struct["partitions"][partition]["filesystem"]: - block_device_struct["partitions"][partition]["filesystem"]["mount_options"] = [] - - if "compress=zstd" not in block_device_struct["partitions"][partition]["filesystem"]["mount_options"]: - block_device_struct["partitions"][partition]["filesystem"]["mount_options"].append("compress=zstd") - elif task == delete_all_partitions: - block_device_struct["partitions"] = [] - block_device_struct["wipe"] = True - elif task == assign_mount_point: - title = _('{}\n\nSelect by index which partition to mount where').format(current_layout) - partition = select_partition(title, block_device_struct["partitions"]) - - if partition is not None: - print(_(' * Partition mount-points are relative to inside the installation, the boot would be /boot as an example.')) - mountpoint = input(_('Select where to mount partition (leave blank to remove mountpoint): ')).strip() - - if len(mountpoint): - block_device_struct["partitions"][partition]['mountpoint'] = mountpoint - if mountpoint == '/boot': - log(f"Marked partition as bootable because mountpoint was set to /boot.", fg="yellow") - block_device_struct["partitions"][partition]['boot'] = True - else: - del (block_device_struct["partitions"][partition]['mountpoint']) - - elif task == mark_formatted: - title = _('{}\n\nSelect which partition to mask for formatting').format(current_layout) - partition = select_partition(title, block_device_struct["partitions"]) - - if partition is not None: - # If we mark a partition for formatting, but the format is CRYPTO LUKS, there's no point in formatting it really - # without asking the user which inner-filesystem they want to use. Since the flag 'encrypted' = True is already set, - # it's safe to change the filesystem for this partition. - if block_device_struct["partitions"][partition].get('filesystem',{}).get('format', 'crypto_LUKS') == 'crypto_LUKS': - if not block_device_struct["partitions"][partition].get('filesystem', None): - block_device_struct["partitions"][partition]['filesystem'] = {} - - fs_choice = Menu(_('Enter a desired filesystem type for the partition'), fs_types()).run() - - if fs_choice.type_ == MenuSelectionType.Selection: - block_device_struct["partitions"][partition]['filesystem']['format'] = fs_choice.value - - # Negate the current wipe marking - block_device_struct["partitions"][partition]['wipe'] = not block_device_struct["partitions"][partition].get('wipe', False) - - elif task == mark_bootable: - title = _('{}\n\nSelect which partition to mark as bootable').format(current_layout) - partition = select_partition(title, block_device_struct["partitions"]) - - if partition is not None: - block_device_struct["partitions"][partition]['boot'] = \ - not block_device_struct["partitions"][partition].get('boot', False) - - elif task == set_filesystem_partition: - title = _('{}\n\nSelect which partition to set a filesystem on').format(current_layout) - partition = select_partition(title, block_device_struct["partitions"]) - - if partition is not None: - if not block_device_struct["partitions"][partition].get('filesystem', None): - block_device_struct["partitions"][partition]['filesystem'] = {} - - fstype_title = _('Enter a desired filesystem type for the partition: ') - fs_choice = Menu(fstype_title, fs_types()).run() - - if fs_choice.type_ == MenuSelectionType.Selection: - block_device_struct["partitions"][partition]['filesystem']['format'] = fs_choice.value - - elif task == set_btrfs_subvolumes: - from .subvolume_config import SubvolumeList - - # TODO get preexisting partitions - title = _('{}\n\nSelect which partition to set subvolumes on').format(current_layout) - partition = select_partition(title, block_device_struct["partitions"],filter_=lambda x:True if x.get('filesystem',{}).get('format') == 'btrfs' else False) - - if partition is not None: - if not block_device_struct["partitions"][partition].get('btrfs', {}): - block_device_struct["partitions"][partition]['btrfs'] = {} - if not block_device_struct["partitions"][partition]['btrfs'].get('subvolumes', []): - block_device_struct["partitions"][partition]['btrfs']['subvolumes'] = [] - - prev = block_device_struct["partitions"][partition]['btrfs']['subvolumes'] - result = SubvolumeList(_("Manage btrfs subvolumes for current partition"), prev).run() - block_device_struct["partitions"][partition]['btrfs']['subvolumes'] = result - - return block_device_struct diff --git a/archinstall/lib/user_interaction/save_conf.py b/archinstall/lib/user_interaction/save_conf.py deleted file mode 100644 index 5b4ae2b3..00000000 --- a/archinstall/lib/user_interaction/save_conf.py +++ /dev/null @@ -1,135 +0,0 @@ -from __future__ import annotations - -import logging - -from pathlib import Path -from typing import Any, Dict, TYPE_CHECKING - -from ..configuration import ConfigurationOutput -from ..general import SysCommand -from ..menu import Menu -from ..menu.menu import MenuSelectionType -from ..output import log - -if TYPE_CHECKING: - _: Any - - -def save_config(config: Dict): - - def preview(selection: str): - if options['user_config'] == selection: - json_config = config_output.user_config_to_json() - return f'{config_output.user_configuration_file}\n{json_config}' - elif options['user_creds'] == selection: - if json_config := config_output.user_credentials_to_json(): - return f'{config_output.user_credentials_file}\n{json_config}' - else: - return str(_('No configuration')) - elif options['disk_layout'] == selection: - if json_config := config_output.disk_layout_to_json(): - return f'{config_output.disk_layout_file}\n{json_config}' - else: - return str(_('No configuration')) - elif options['all'] == selection: - output = f'{config_output.user_configuration_file}\n' - if json_config := config_output.user_credentials_to_json(): - output += f'{config_output.user_credentials_file}\n' - if json_config := config_output.disk_layout_to_json(): - output += f'{config_output.disk_layout_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 - - dirs_to_exclude = [ - '/bin', - '/dev', - '/lib', - '/lib64', - '/lost+found', - '/opt', - '/proc', - '/run', - '/sbin', - '/srv', - '/sys', - '/usr', - '/var', - ] - log( - _('When picking a directory to save configuration files to,' - ' by default we will ignore the following 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' - possible_save_dirs = list( - filter(None, SysCommand(file_picker_command).decode().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 - case _: - save_dirs = selection.value - - prompt = _('Do you want to save {} configuration file(s) in the following locations?\n\n{}').format( - list(options.keys())[list(options.values()).index(choice.value)], - save_dirs - ) - save_confirmation = Menu(prompt, Menu.yes_no(), default_option=Menu.yes()).run() - if save_confirmation == Menu.no(): - return - - log( - _('Saving {} configuration files to {}').format( - list(options.keys())[list(options.values()).index(choice.value)], - 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'] == choice.value: - config_output.save_user_config(save_dir) - elif options['user_creds'] == choice.value: - config_output.save_user_creds(save_dir) - elif options['disk_layout'] == choice.value: - config_output.save_disk_layout(save_dir) - elif options['all'] == choice.value: - config_output.save_user_config(save_dir) - config_output.save_user_creds(save_dir) - config_output.save_disk_layout(save_dir) diff --git a/archinstall/lib/user_interaction/subvolume_config.py b/archinstall/lib/user_interaction/subvolume_config.py deleted file mode 100644 index 94150dee..00000000 --- a/archinstall/lib/user_interaction/subvolume_config.py +++ /dev/null @@ -1,98 +0,0 @@ -from typing import Dict, List, Optional, Any, TYPE_CHECKING - -from ..menu.list_manager import ListManager -from ..menu.menu import MenuSelectionType -from ..menu.text_input import TextInput -from ..menu import Menu -from ..models.subvolume import Subvolume -from ... import FormattedOutput - -if TYPE_CHECKING: - _: Any - - -class SubvolumeList(ListManager): - def __init__(self, prompt: str, subvolumes: List[Subvolume]): - self._actions = [ - str(_('Add subvolume')), - str(_('Edit subvolume')), - str(_('Delete subvolume')) - ] - super().__init__(prompt, subvolumes, [self._actions[0]], self._actions[1:]) - - def reformat(self, data: List[Subvolume]) -> Dict[str, Optional[Subvolume]]: - 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[Subvolume]] = {f' {rows[0]}': None, f' {rows[1]}': None} - - for row, subvol in zip(rows[2:], data): - row = row.replace('|', '\\|') - display_data[row] = subvol - - return display_data - - def selected_action_display(self, subvolume: Subvolume) -> str: - return subvolume.name - - def _prompt_options(self, editing: Optional[Subvolume] = None) -> List[str]: - preset_options = [] - if editing: - preset_options = editing.options - - choice = Menu( - str(_("Select the desired subvolume options ")), - ['nodatacow','compress'], - skip=True, - preset_values=preset_options, - multi=True - ).run() - - if choice.type_ == MenuSelectionType.Selection: - return choice.value # type: ignore - - return [] - - def _add_subvolume(self, editing: Optional[Subvolume] = None) -> Optional[Subvolume]: - name = TextInput(f'\n\n{_("Subvolume name")}: ', editing.name if editing else '').run() - - if not name: - return None - - mountpoint = TextInput(f'\n{_("Subvolume mountpoint")}: ', editing.mountpoint if editing else '').run() - - if not mountpoint: - return None - - options = self._prompt_options(editing) - - subvolume = Subvolume(name, mountpoint) - subvolume.compress = 'compress' in options - subvolume.nodatacow = 'nodatacow' in options - - return subvolume - - def handle_action(self, action: str, entry: Optional[Subvolume], data: List[Subvolume]) -> List[Subvolume]: - if action == self._actions[0]: # add - new_subvolume = self._add_subvolume() - - if new_subvolume 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.name != new_subvolume.name] - data += [new_subvolume] - elif entry is not None: - if action == self._actions[1]: # edit subvolume - new_subvolume = self._add_subvolume(entry) - - if new_subvolume is not None: - # we'll remove the original subvolume and add the modified version - data = [d for d in data if d.name != entry.name and d.name != new_subvolume.name] - data += [new_subvolume] - elif action == self._actions[2]: # delete - data = [d for d in data if d != entry] - - return data diff --git a/archinstall/lib/user_interaction/system_conf.py b/archinstall/lib/user_interaction/system_conf.py deleted file mode 100644 index 68a1a7d2..00000000 --- a/archinstall/lib/user_interaction/system_conf.py +++ /dev/null @@ -1,168 +0,0 @@ -from __future__ import annotations - -from typing import List, Any, Dict, TYPE_CHECKING - -from ..disk import all_blockdevices -from ..exceptions import RequirementError -from ..hardware import AVAILABLE_GFX_DRIVERS, has_uefi, has_amd_graphics, has_intel_graphics, has_nvidia_graphics -from ..menu import Menu -from ..menu.menu import MenuSelectionType -from ..storage import storage - -if TYPE_CHECKING: - _: Any - - -def select_kernel(preset: List[str] = None) -> 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-pae"] - 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 - - -def select_harddrives(preset: List[str] = []) -> List[str]: - """ - Asks the user to select one or multiple hard drives - - :return: List of selected hard drives - :rtype: list - """ - hard_drives = all_blockdevices(partitions=False).values() - options = {f'{option}': option for option in hard_drives} - - title = str(_('Select one or more hard drives to use and configure\n')) - title += str(_('Any modifications to the existing setting will reset the disk layout!')) - - warning = str(_('If you reset the harddrive selection this will also reset the current disk layout. Are you sure?')) - - selected_harddrive = Menu( - title, - list(options.keys()), - multi=True, - allow_reset=True, - allow_reset_warning_msg=warning - ).run() - - match selected_harddrive.type_: - case MenuSelectionType.Reset: return [] - case MenuSelectionType.Skip: return preset - case MenuSelectionType.Selection: return [options[i] for i in selected_harddrive.value] - - -def select_driver(options: Dict[str, Any] = AVAILABLE_GFX_DRIVERS) -> 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) - """ - - drivers = sorted(list(options)) - - if drivers: - arguments = storage.get('arguments', {}) - 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(_('\n\nSelect a graphics driver or leave blank to install all open-source drivers')) - choice = Menu(title, drivers).run() - - if choice.type_ != MenuSelectionType.Selection: - return arguments.get('gfx_driver') - - arguments['gfx_driver'] = choice.value - return options.get(choice.value) - - raise RequirementError("Selecting drivers require a least one profile to be given as an option.") - - -def ask_for_bootloader(advanced_options: bool = False, preset: str = None) -> str: - if preset == 'systemd-bootctl': - preset_val = 'systemd-boot' if advanced_options else Menu.no() - elif preset == 'grub-install': - preset_val = 'grub' if advanced_options else Menu.yes() - else: - preset_val = preset - - bootloader = "systemd-bootctl" if has_uefi() else "grub-install" - - if has_uefi(): - if not advanced_options: - selection = Menu( - _('Would you like to use GRUB as a bootloader instead of systemd-boot?'), - Menu.yes_no(), - preset_values=preset_val, - default_option=Menu.no() - ).run() - - match selection.type_: - case MenuSelectionType.Skip: return preset - case MenuSelectionType.Selection: bootloader = 'grub-install' if selection.value == Menu.yes() else bootloader - else: - # We use the common names for the bootloader as the selection, and map it back to the expected values. - choices = ['systemd-boot', 'grub', 'efistub'] - selection = Menu(_('Choose a bootloader'), choices, preset_values=preset_val).run() - - value = '' - match selection.type_: - case MenuSelectionType.Skip: value = preset_val - case MenuSelectionType.Selection: value = selection.value - - if value != "": - if value == 'systemd-boot': - bootloader = 'systemd-bootctl' - elif value == 'grub': - bootloader = 'grub-install' - else: - bootloader = value - - return bootloader - - -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 diff --git a/archinstall/lib/user_interaction/utils.py b/archinstall/lib/user_interaction/utils.py deleted file mode 100644 index 7ee6fc07..00000000 --- a/archinstall/lib/user_interaction/utils.py +++ /dev/null @@ -1,79 +0,0 @@ -from __future__ import annotations - -import getpass -import signal -import sys -import time -from typing import Any, Optional, TYPE_CHECKING - -from ..menu import Menu -from ..models.password_strength 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 - - -def do_countdown() -> bool: - SIG_TRIGGER = False - - def kill_handler(sig: int, frame: Any) -> None: - print() - exit(0) - - def sig_handler(sig: int, frame: Any) -> None: - global SIG_TRIGGER - SIG_TRIGGER = True - signal.signal(signal.SIGINT, kill_handler) - - original_sigint_handler = signal.getsignal(signal.SIGINT) - signal.signal(signal.SIGINT, sig_handler) - - for i in range(5, 0, -1): - print(f"{i}", end='') - - for x in range(4): - sys.stdout.flush() - time.sleep(0.25) - print(".", end='') - - if SIG_TRIGGER: - prompt = _('Do you really want to abort?') - choice = Menu(prompt, Menu.yes_no(), skip=False).run() - if choice.value == Menu.yes(): - exit(0) - - if SIG_TRIGGER is False: - sys.stdin.read() - - SIG_TRIGGER = False - signal.signal(signal.SIGINT, sig_handler) - - print() - signal.signal(signal.SIGINT, original_sigint_handler) - - return True |