From f00717ff6fd1c72d61b6928444fbf26a3f5e0e64 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Mon, 2 May 2022 21:01:50 +1000 Subject: Fix #1106 (#1119) * Fix #1106 * flake8 * flake8 Co-authored-by: Daniel Girtler --- archinstall/lib/menu/global_menu.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'archinstall/lib/menu/global_menu.py') diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index afccd45b..63b8c03e 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -8,7 +8,6 @@ from ..general import SysCommand, secret from ..hardware import has_uefi from ..models import NetworkConfiguration from ..storage import storage -from ..output import log from ..profiles import is_desktop_profile from ..disk import encrypted_partitions @@ -277,11 +276,12 @@ class GlobalMenu(GeneralMenu): if profile and profile.has_prep_function(): namespace = f'{profile.namespace}.py' with profile.load_instructions(namespace=namespace) as imported: - if not imported._prep_function(): - log(' * Profile\'s preparation requirements was not fulfilled.', fg='red') - exit(1) + if imported._prep_function(): + return profile + else: + return self._select_profile() - return profile + return self._data_store.get('profile', None) def _create_superuser_account(self): superusers = ask_for_superuser_account(str(_('Manage superuser accounts: '))) -- cgit v1.2.3-70-g09d2 From bcd810d2d20e657d96f36e0007facff71e9532bc Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Thu, 5 May 2022 20:48:01 +1000 Subject: Fix 1117 (#1126) * Fix 1117 * Update * flake8 Co-authored-by: Daniel Girtler --- archinstall/lib/disk/user_guides.py | 36 +++++++++++++--------- archinstall/lib/menu/global_menu.py | 4 +-- archinstall/lib/menu/menu.py | 14 +++++++++ archinstall/lib/user_interaction/disk_conf.py | 4 +-- archinstall/lib/user_interaction/general_conf.py | 8 ++--- .../lib/user_interaction/manage_users_conf.py | 8 ++--- .../lib/user_interaction/partitioning_conf.py | 12 +++++--- .../lib/user_interaction/subvolume_config.py | 2 +- archinstall/lib/user_interaction/system_conf.py | 18 +++++------ archinstall/lib/user_interaction/utils.py | 11 +++---- examples/guided.py | 6 ++-- examples/swiss.py | 10 +++--- profiles/sway.py | 5 +-- 13 files changed, 79 insertions(+), 59 deletions(-) (limited to 'archinstall/lib/menu/global_menu.py') diff --git a/archinstall/lib/disk/user_guides.py b/archinstall/lib/disk/user_guides.py index 63ec1d9b..77da52e4 100644 --- a/archinstall/lib/disk/user_guides.py +++ b/archinstall/lib/disk/user_guides.py @@ -5,6 +5,7 @@ from typing import Optional, Dict, Any, List, TYPE_CHECKING # https://stackoverflow.com/a/39757388/929999 if TYPE_CHECKING: from .blockdevice import BlockDevice + _: Any from .helpers import sort_block_devices_based_on_performance, select_largest_device, select_disk_larger_than_or_close_to from ..hardware import has_uefi @@ -26,13 +27,13 @@ def suggest_single_disk_layout(block_device :BlockDevice, compression = False if default_filesystem == 'btrfs': - prompt = 'Would you like to use BTRFS subvolumes with a default structure?' - choice = Menu(prompt, ['yes', 'no'], skip=False, default_option='yes').run() - using_subvolumes = choice == 'yes' + 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 == Menu.yes() - prompt = 'Would you like to use BTRFS compression?' - choice = Menu(prompt, ['yes', 'no'], skip=False, default_option='yes').run() - compression = choice == '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 == Menu.yes() layout = { block_device.path : { @@ -87,9 +88,9 @@ def suggest_single_disk_layout(block_device :BlockDevice, layout[block_device.path]['partitions'][-1]['start'] = '513MiB' if not using_subvolumes and block_device.size >= MIN_SIZE_TO_ALLOW_HOME_PART: - prompt = 'Would you like to create a separate partition for /home?' - choice = Menu(prompt, ['yes', 'no'], skip=False, default_option='yes').run() - using_home_partition = choice == 'yes' + 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 == Menu.yes() # Set a size for / (/root) if using_subvolumes or block_device.size < MIN_SIZE_TO_ALLOW_HOME_PART or not using_home_partition: @@ -138,9 +139,7 @@ def suggest_single_disk_layout(block_device :BlockDevice, return layout -def suggest_multi_disk_layout(block_devices :List[BlockDevice], - default_filesystem :Optional[str] = None, - advanced_options :bool = False) -> Dict[str, Any]: +def suggest_multi_disk_layout(block_devices :List[BlockDevice], default_filesystem :Optional[str] = None, advanced_options :bool = False): if not default_filesystem: from ..user_interaction import ask_for_main_filesystem_format @@ -158,6 +157,13 @@ def suggest_multi_disk_layout(block_devices :List[BlockDevice], home_device = select_largest_device(block_devices, gigabytes=MIN_SIZE_TO_ALLOW_HOME_PART) root_device = select_disk_larger_than_or_close_to(block_devices, gigabytes=ARCH_LINUX_INSTALLED_SIZE, filter_out=[home_device]) + 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: {}GB\n').format(MIN_SIZE_TO_ALLOW_HOME_PART) + text += _('Minimum capacity for Arch Linux partition: {}GB').format(ARCH_LINUX_INSTALLED_SIZE) + Menu(str(text), [str(_('Continue'))], skip=False).run() + return None + compression = False if default_filesystem == 'btrfs': @@ -165,9 +171,9 @@ def suggest_multi_disk_layout(block_devices :List[BlockDevice], # choice = Menu(prompt, ['yes', 'no'], skip=False, default_option='yes').run() # using_subvolumes = choice == 'yes' - prompt = 'Would you like to use BTRFS compression?' - choice = Menu(prompt, ['yes', 'no'], skip=False, default_option='yes').run() - compression = choice == '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 == Menu.yes() log(f"Suggesting multi-disk-layout using {len(block_devices)} disks, where {root_device} will be /root and {home_device} will be /home", level=logging.DEBUG) diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index 63b8c03e..b3c5c6a2 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -262,9 +262,9 @@ class GlobalMenu(GeneralMenu): "Do you wish to continue?" ).format(storage['MOUNT_POINT']) - choice = Menu(prompt, ['yes', 'no'], default_option='yes').run() + choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes()).run() - if choice == 'no': + if choice == Menu.no(): exit(1) return harddrives diff --git a/archinstall/lib/menu/menu.py b/archinstall/lib/menu/menu.py index d254e0f9..48ea4635 100644 --- a/archinstall/lib/menu/menu.py +++ b/archinstall/lib/menu/menu.py @@ -12,7 +12,21 @@ import logging if TYPE_CHECKING: _: Any + class Menu(TerminalMenu): + + @classmethod + def yes(cls): + return str(_('yes')) + + @classmethod + def no(cls): + return str(_('no')) + + @classmethod + def yes_no(cls): + return [cls.yes(), cls.no()] + def __init__( self, title :str, diff --git a/archinstall/lib/user_interaction/disk_conf.py b/archinstall/lib/user_interaction/disk_conf.py index a51f10b8..e84f647a 100644 --- a/archinstall/lib/user_interaction/disk_conf.py +++ b/archinstall/lib/user_interaction/disk_conf.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Dict, TYPE_CHECKING +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 @@ -36,7 +36,7 @@ def select_individual_blockdevice_usage(block_devices: list) -> Dict[str, Any]: return result -def select_disk_layout(block_devices: list, advanced_options=False) -> Dict[str, Any]: +def select_disk_layout(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] diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py index c42e9e27..78f6b460 100644 --- a/archinstall/lib/user_interaction/general_conf.py +++ b/archinstall/lib/user_interaction/general_conf.py @@ -22,11 +22,11 @@ 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 = 'yes' + preset_val = Menu.yes() else: - preset_val = 'no' - choice = Menu(prompt, ['yes', 'no'], skip=False, preset_values=preset_val, default_option='yes').run() - return False if choice == 'no' else True + 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 == Menu.no() else True def ask_hostname(preset: str = None) -> str: diff --git a/archinstall/lib/user_interaction/manage_users_conf.py b/archinstall/lib/user_interaction/manage_users_conf.py index d69ccce9..ea909f35 100644 --- a/archinstall/lib/user_interaction/manage_users_conf.py +++ b/archinstall/lib/user_interaction/manage_users_conf.py @@ -109,11 +109,11 @@ class UserList(ListManager): sudoer = False else: sudoer = False - sudo_choice = Menu(str(_('Should {} be a superuser (sudoer)?')).format(userid), ['yes', 'no'], + sudo_choice = Menu(str(_('Should {} be a superuser (sudoer)?')).format(userid), Menu.yes_no(), skip=False, - preset_values='yes' if sudoer else 'no', - default_option='no').run() - sudoer = True if sudo_choice == 'yes' else False + preset_values=Menu.yes() if sudoer else Menu.no(), + default_option=Menu.no()).run() + sudoer = True if sudo_choice == Menu.yes() else False password = get_password(prompt=str(_('Password for user "{}": ').format(userid))) diff --git a/archinstall/lib/user_interaction/partitioning_conf.py b/archinstall/lib/user_interaction/partitioning_conf.py index 2182c6b3..afca1cef 100644 --- a/archinstall/lib/user_interaction/partitioning_conf.py +++ b/archinstall/lib/user_interaction/partitioning_conf.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import List, Any, Dict, Union, TYPE_CHECKING, Callable +from typing import List, Any, Dict, Union, TYPE_CHECKING, Callable, Optional from ..menu import Menu from ..output import log @@ -97,8 +97,10 @@ def select_partition(title :str, partitions :List[Partition], multiple :bool = F return None -def get_default_partition_layout(block_devices: Union['BlockDevice', List['BlockDevice']], - advanced_options: bool = False) -> Dict[str, Any]: +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: @@ -224,9 +226,9 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, if len(block_device_struct["partitions"]): prompt = _('{} contains queued partitions, this will remove those, are you sure?').format(block_device) - choice = Menu(prompt, ['yes', 'no'], default_option='no').run() + choice = Menu(prompt, Menu.yes_no(), default_option=Menu.no()).run() - if choice == 'no': + if choice == Menu.no(): continue block_device_struct.update(suggest_single_disk_layout(block_device)[block_device.path]) diff --git a/archinstall/lib/user_interaction/subvolume_config.py b/archinstall/lib/user_interaction/subvolume_config.py index adbb7430..bb159a8b 100644 --- a/archinstall/lib/user_interaction/subvolume_config.py +++ b/archinstall/lib/user_interaction/subvolume_config.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Dict from ..menu.list_manager import ListManager from ..menu.selection_menu import Selector, GeneralMenu diff --git a/archinstall/lib/user_interaction/system_conf.py b/archinstall/lib/user_interaction/system_conf.py index 5d4c80dc..17c093c1 100644 --- a/archinstall/lib/user_interaction/system_conf.py +++ b/archinstall/lib/user_interaction/system_conf.py @@ -102,9 +102,9 @@ def select_driver(options: Dict[str, Any] = AVAILABLE_GFX_DRIVERS) -> str: def ask_for_bootloader(advanced_options: bool = False, preset: str = None) -> str: if preset == 'systemd-bootctl': - preset_val = 'systemd-boot' if advanced_options else 'no' + preset_val = 'systemd-boot' if advanced_options else Menu.no() elif preset == 'grub-install': - preset_val = 'grub' if advanced_options else 'yes' + preset_val = 'grub' if advanced_options else Menu.yes() else: preset_val = preset @@ -112,11 +112,11 @@ def ask_for_bootloader(advanced_options: bool = False, preset: str = None) -> st if has_uefi(): if not advanced_options: bootloader_choice = Menu(_('Would you like to use GRUB as a bootloader instead of systemd-boot?'), - ['yes', 'no'], + Menu.yes_no(), preset_values=preset_val, - default_option='no').run() + default_option=Menu.no()).run() - if bootloader_choice == "yes": + if bootloader_choice == Menu.yes(): bootloader = "grub-install" else: # We use the common names for the bootloader as the selection, and map it back to the expected values. @@ -135,9 +135,9 @@ def ask_for_bootloader(advanced_options: bool = False, preset: str = None) -> st def ask_for_swap(preset: bool = True) -> bool: if preset: - preset_val = 'yes' + preset_val = Menu.yes() else: - preset_val = 'no' + preset_val = Menu.no() prompt = _('Would you like to use swap on zram?') - choice = Menu(prompt, ['yes', 'no'], default_option='yes', preset_values=preset_val).run() - return False if choice == 'no' else True + choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes(), preset_values=preset_val).run() + return False if choice == Menu.no() else True diff --git a/archinstall/lib/user_interaction/utils.py b/archinstall/lib/user_interaction/utils.py index 48b55e8c..59f2dfbc 100644 --- a/archinstall/lib/user_interaction/utils.py +++ b/archinstall/lib/user_interaction/utils.py @@ -28,12 +28,9 @@ def check_password_strong(passwd: str) -> bool: symbol_count += 40 if symbol_count**len(passwd) < 10e20: - - prompt = _("The password you are using seems to be weak,") - prompt += _("are you sure you want to use it?") - - choice = Menu(prompt, ["yes", "no"], default_option="yes").run() - return choice == "yes" + prompt = str(_("The password you are using seems to be weak, are you sure you want to use it?")) + choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes()).run() + return choice == Menu.yes() return True @@ -84,7 +81,7 @@ def do_countdown() -> bool: if SIG_TRIGGER: prompt = _('Do you really want to abort?') - choice = Menu(prompt, ['yes', 'no'], skip=False).run() + choice = Menu(prompt, Menu.yes_no(), skip=False).run() if choice == 'yes': exit(0) diff --git a/examples/guided.py b/examples/guided.py index 15226668..1cee499d 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -3,7 +3,7 @@ import os import time import archinstall -from archinstall import ConfigurationOutput +from archinstall import ConfigurationOutput, Menu from archinstall.lib.models.network_configuration import NetworkConfigurationHandler if archinstall.arguments.get('help'): @@ -258,8 +258,8 @@ def perform_installation(mountpoint): installation.log("For post-installation tips, see https://wiki.archlinux.org/index.php/Installation_guide#Post-installation", fg="yellow") if not archinstall.arguments.get('silent'): prompt = 'Would you like to chroot into the newly created installation and perform post-installation configuration?' - choice = archinstall.Menu(prompt, ['yes', 'no'], default_option='yes').run() - if choice == 'yes': + choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes()).run() + if choice == Menu.yes(): try: installation.drop_to_shell() except: diff --git a/examples/swiss.py b/examples/swiss.py index baf7b618..6d357191 100644 --- a/examples/swiss.py +++ b/examples/swiss.py @@ -20,7 +20,7 @@ import pathlib from typing import TYPE_CHECKING, Any import archinstall -from archinstall import ConfigurationOutput, NetworkConfigurationHandler +from archinstall import ConfigurationOutput, NetworkConfigurationHandler, Menu if TYPE_CHECKING: _: Any @@ -38,8 +38,8 @@ TODO exec con return parameter """ def select_activate_NTP(): prompt = "Would you like to use automatic time synchronization (NTP) with the default time servers? [Y/n]: " - choice = archinstall.Menu(prompt, ['yes', 'no'], default_option='yes').run() - if choice == 'yes': + choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes()).run() + if choice == Menu.yes(): return True else: return False @@ -480,8 +480,8 @@ def perform_installation(mountpoint, mode): installation.log("For post-installation tips, see https://wiki.archlinux.org/index.php/Installation_guide#Post-installation", fg="yellow") if not archinstall.arguments.get('silent'): prompt = 'Would you like to chroot into the newly created installation and perform post-installation configuration?' - choice = archinstall.Menu(prompt, ['yes', 'no'], default_option='yes').run() - if choice == 'yes': + choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes()).run() + if choice == Menu.yes(): try: installation.drop_to_shell() except: diff --git a/profiles/sway.py b/profiles/sway.py index e9c71b79..0819db95 100644 --- a/profiles/sway.py +++ b/profiles/sway.py @@ -1,5 +1,6 @@ # A desktop environment using "Sway" import archinstall +from archinstall import Menu is_top_level_profile = False @@ -22,8 +23,8 @@ def _check_driver() -> bool: if packages and "nvidia" in packages: prompt = 'The proprietary Nvidia driver is not supported by Sway. It is likely that you will run into issues, are you okay with that?' - choice = archinstall.Menu(prompt, ['yes', 'no'], default_option='no').run() - if choice == 'no': + choice = Menu(prompt, Menu.yes_no(), default_option=Menu.no()).run() + if choice == Menu.no(): return False return True -- cgit v1.2.3-70-g09d2 From 0fa52a5424e28ed62ef84bdc92868bbfc434e015 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Mon, 9 May 2022 20:02:48 +1000 Subject: Introduce ctrl+c and other bug fixes (#1152) * Intergrate ctrl+c * stash * Update * Fix profile reset * flake8 Co-authored-by: Daniel Girtler --- archinstall/lib/disk/user_guides.py | 8 +- archinstall/lib/menu/global_menu.py | 94 ++++++++++---- archinstall/lib/menu/list_manager.py | 29 +++-- archinstall/lib/menu/menu.py | 144 ++++++++++++++------- archinstall/lib/menu/selection_menu.py | 55 ++++---- archinstall/lib/menu/simple_menu.py | 15 ++- archinstall/lib/profiles.py | 4 + archinstall/lib/user_interaction/disk_conf.py | 32 +++-- archinstall/lib/user_interaction/general_conf.py | 127 +++++++++++------- archinstall/lib/user_interaction/locale_conf.py | 35 +++-- archinstall/lib/user_interaction/network_conf.py | 37 ++++-- .../lib/user_interaction/partitioning_conf.py | 81 ++++++------ archinstall/lib/user_interaction/save_conf.py | 27 ++-- archinstall/lib/user_interaction/system_conf.py | 113 ++++++++++------ archinstall/lib/user_interaction/utils.py | 6 +- examples/guided.py | 5 +- examples/swiss.py | 2 +- profiles/desktop.py | 25 ++-- profiles/i3.py | 13 +- profiles/minimal.py | 2 + profiles/server.py | 17 ++- profiles/sway.py | 5 +- 22 files changed, 569 insertions(+), 307 deletions(-) (limited to 'archinstall/lib/menu/global_menu.py') diff --git a/archinstall/lib/disk/user_guides.py b/archinstall/lib/disk/user_guides.py index 77da52e4..5fa6bfdc 100644 --- a/archinstall/lib/disk/user_guides.py +++ b/archinstall/lib/disk/user_guides.py @@ -29,11 +29,11 @@ def suggest_single_disk_layout(block_device :BlockDevice, if default_filesystem == '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 == Menu.yes() + 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 == Menu.yes() + compression = choice.value == Menu.yes() layout = { block_device.path : { @@ -90,7 +90,7 @@ def suggest_single_disk_layout(block_device :BlockDevice, if not using_subvolumes and block_device.size >= MIN_SIZE_TO_ALLOW_HOME_PART: 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 == Menu.yes() + using_home_partition = choice.value == Menu.yes() # Set a size for / (/root) if using_subvolumes or block_device.size < MIN_SIZE_TO_ALLOW_HOME_PART or not using_home_partition: @@ -173,7 +173,7 @@ def suggest_multi_disk_layout(block_devices :List[BlockDevice], default_filesyst prompt = str(_('Would you like to use BTRFS compression?')) choice = Menu(prompt, Menu.yes_no(), skip=False, default_option=Menu.yes()).run() - compression = choice == Menu.yes() + compression = choice.value == Menu.yes() log(f"Suggesting multi-disk-layout using {len(block_devices)} disks, where {root_device} will be /root and {home_device} will be /home", level=logging.DEBUG) diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index b3c5c6a2..23a8ec11 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -2,13 +2,15 @@ from __future__ import annotations from typing import Any, List, Optional, Union +import archinstall + from ..menu import Menu from ..menu.selection_menu import Selector, GeneralMenu from ..general import SysCommand, secret from ..hardware import has_uefi from ..models import NetworkConfiguration from ..storage import storage -from ..profiles import is_desktop_profile +from ..profiles import is_desktop_profile, Profile from ..disk import encrypted_partitions from ..user_interaction import get_password, ask_for_a_timezone, save_config @@ -41,28 +43,38 @@ class GlobalMenu(GeneralMenu): self._menu_options['archinstall-language'] = \ Selector( _('Select Archinstall language'), - lambda x: self._select_archinstall_language('English'), + lambda x: self._select_archinstall_language(x), default='English') self._menu_options['keyboard-layout'] = \ - Selector(_('Select keyboard layout'), lambda preset: select_language('us',preset), default='us') + Selector( + _('Select keyboard layout'), + lambda preset: select_language(preset), + default='us') self._menu_options['mirror-region'] = \ Selector( _('Select mirror region'), - select_mirror_regions, + lambda preset: select_mirror_regions(preset), display_func=lambda x: list(x.keys()) if x else '[]', default={}) self._menu_options['sys-language'] = \ - Selector(_('Select locale language'), lambda preset: select_locale_lang('en_US',preset), default='en_US') + Selector( + _('Select locale language'), + lambda preset: select_locale_lang(preset), + default='en_US') self._menu_options['sys-encoding'] = \ - Selector(_('Select locale encoding'), lambda preset: select_locale_enc('utf-8',preset), default='utf-8') + Selector( + _('Select locale encoding'), + lambda preset: select_locale_enc(preset), + default='UTF-8') self._menu_options['harddrives'] = \ Selector( _('Select harddrives'), - self._select_harddrives) + lambda preset: self._select_harddrives(preset)) self._menu_options['disk_layouts'] = \ Selector( _('Select disk layout'), - lambda x: select_disk_layout( + lambda preset: select_disk_layout( + preset, storage['arguments'].get('harddrives', []), storage['arguments'].get('advanced', False) ), @@ -112,7 +124,7 @@ class GlobalMenu(GeneralMenu): self._menu_options['profile'] = \ Selector( _('Specify profile'), - lambda x: self._select_profile(), + lambda preset: self._select_profile(preset), display_func=lambda x: x if x else 'None') self._menu_options['audio'] = \ Selector( @@ -247,41 +259,73 @@ class GlobalMenu(GeneralMenu): return ntp def _select_harddrives(self, old_harddrives : list) -> list: - # old_haddrives = storage['arguments'].get('harddrives', []) harddrives = select_harddrives(old_harddrives) - # in case the harddrives got changed we have to reset the disk layout as well - if old_harddrives != harddrives: - self._menu_options.get('disk_layouts').set_current_selection(None) - storage['arguments']['disk_layouts'] = {} - - if not harddrives: + if len(harddrives) == 0: prompt = _( "You decided to skip harddrive selection\nand will use whatever drive-setup is mounted at {} (experimental)\n" "WARNING: Archinstall won't check the suitability of this setup\n" "Do you wish to continue?" ).format(storage['MOUNT_POINT']) - choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes()).run() + choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes(), skip=False).run() - if choice == Menu.no(): - exit(1) + if choice.value == Menu.no(): + return self._select_harddrives(old_harddrives) + + # in case the harddrives got changed we have to reset the disk layout as well + if old_harddrives != harddrives: + self._menu_options.get('disk_layouts').set_current_selection(None) + storage['arguments']['disk_layouts'] = {} return harddrives - def _select_profile(self): - profile = select_profile() + def _select_profile(self, preset): + profile = select_profile(preset) + ret = None + + if profile is None: + if any([ + archinstall.storage.get('profile_minimal', False), + archinstall.storage.get('_selected_servers', None), + archinstall.storage.get('_desktop_profile', None), + archinstall.arguments.get('desktop-environment', None), + archinstall.arguments.get('gfx_driver_packages', None) + ]): + return preset + else: # ctrl+c was actioned and all profile settings have been reset + return None + + servers = archinstall.storage.get('_selected_servers', []) + desktop = archinstall.storage.get('_desktop_profile', None) + desktop_env = archinstall.arguments.get('desktop-environment', None) + gfx_driver = archinstall.arguments.get('gfx_driver_packages', None) # Check the potentially selected profiles preparations to get early checks if some additional questions are needed. if profile and profile.has_prep_function(): namespace = f'{profile.namespace}.py' with profile.load_instructions(namespace=namespace) as imported: - if imported._prep_function(): - return profile + if imported._prep_function(servers=servers, desktop=desktop, desktop_env=desktop_env, gfx_driver=gfx_driver): + ret: Profile = profile + + match ret.name: + case 'minimal': + reset = ['_selected_servers', '_desktop_profile', 'desktop-environment', 'gfx_driver_packages'] + case 'server': + reset = ['_desktop_profile', 'desktop-environment'] + case 'desktop': + reset = ['_selected_servers'] + case 'xorg': + reset = ['_selected_servers', '_desktop_profile', 'desktop-environment'] + + for r in reset: + archinstall.storage[r] = None else: - return self._select_profile() + return self._select_profile(preset) + elif profile: + ret = profile - return self._data_store.get('profile', None) + return ret def _create_superuser_account(self): superusers = ask_for_superuser_account(str(_('Manage superuser accounts: '))) diff --git a/archinstall/lib/menu/list_manager.py b/archinstall/lib/menu/list_manager.py index 377d30f2..f3927e6f 100644 --- a/archinstall/lib/menu/list_manager.py +++ b/archinstall/lib/menu/list_manager.py @@ -86,7 +86,7 @@ The contents in the base class of this methods serve for a very basic usage, and """ from .text_input import TextInput -from .menu import Menu +from .menu import Menu, MenuSelectionType from os import system from copy import copy from typing import Union, Any, TYPE_CHECKING, Dict @@ -167,6 +167,7 @@ class ListManager: options += self.bottom_list system('clear') + target = Menu( self._prompt, options, @@ -174,27 +175,31 @@ class ListManager: clear_screen=False, clear_menu_on_exit=False, header=self.header, - skip_empty_entries=True).run() + skip_empty_entries=True + ).run() + + if target.type_ == MenuSelectionType.Esc: + return self.run() - if not target or target in self.bottom_list: + if not target.value or target.value in self.bottom_list: self.action = target break - if target and target in self._default_action: - self.action = target + if target.value and target.value in self._default_action: + self.action = target.value self.target = None self.exec_action(self._data) continue if isinstance(self._data,dict): - data_key = data_formatted[target] + data_key = data_formatted[target.value] key = self._data[data_key] self.target = {data_key: key} else: - self.target = self._data[data_formatted[target]] + self.target = self._data[data_formatted[target.value]] # Possible enhacement. If run_actions returns false a message line indicating the failure - self.run_actions(target) + self.run_actions(target.value) if not target or target == self.cancel_action: # TODO dubious return self.base_data # return the original list @@ -204,14 +209,18 @@ class ListManager: def run_actions(self,prompt_data=None): options = self.action_list() + self.bottom_item prompt = _("Select an action for < {} >").format(prompt_data if prompt_data else self.target) - self.action = Menu( + choice = Menu( prompt, options, sort=False, clear_screen=False, clear_menu_on_exit=False, preset_values=self.bottom_item, - show_search_hint=False).run() + show_search_hint=False + ).run() + + self.action = choice.value + if not self.action or self.action == self.cancel_action: return False else: diff --git a/archinstall/lib/menu/menu.py b/archinstall/lib/menu/menu.py index b2f4146d..c34814eb 100644 --- a/archinstall/lib/menu/menu.py +++ b/archinstall/lib/menu/menu.py @@ -1,4 +1,6 @@ -from typing import Dict, List, Union, Any, TYPE_CHECKING +from dataclasses import dataclass +from enum import Enum, auto +from typing import Dict, List, Union, Any, TYPE_CHECKING, Optional from archinstall.lib.menu.simple_menu import TerminalMenu @@ -13,6 +15,18 @@ if TYPE_CHECKING: _: Any +class MenuSelectionType(Enum): + Selection = auto() + Esc = auto() + Ctrl_c = auto() + + +@dataclass +class MenuSelection: + type_: MenuSelectionType + value: Optional[Union[str, List[str]]] = None + + class Menu(TerminalMenu): @classmethod @@ -33,14 +47,16 @@ class Menu(TerminalMenu): p_options :Union[List[str], Dict[str, Any]], skip :bool = True, multi :bool = False, - default_option :str = None, + default_option : Optional[str] = None, sort :bool = True, preset_values :Union[str, List[str]] = None, - cursor_index :int = None, + cursor_index : Optional[int] = None, preview_command=None, preview_size=0.75, preview_title='Info', header :Union[List[str],str] = None, + explode_on_interrupt :bool = False, + explode_warning :str = '', **kwargs ): """ @@ -80,9 +96,15 @@ class Menu(TerminalMenu): :param preview_title: Title of the preview window :type preview_title: str - param: header one or more header lines for the menu + param header: one or more header lines for the menu type param: string or list + param explode_on_interrupt: This will explicitly handle a ctrl+c instead and return that specific state + type param: bool + + param explode_warning: If explode_on_interrupt is True and this is non-empty, there will be a warning with a user confirmation displayed + type param: str + :param kwargs : any SimpleTerminal parameter """ # we guarantee the inmutability of the options outside the class. @@ -99,6 +121,8 @@ class Menu(TerminalMenu): log(f"invalid parameter at Menu() call was at <{sys._getframe(1).f_code.co_name}>",level=logging.WARNING) raise RequirementError("Menu() requires an iterable as option.") + self._default_str = str(_('(default)')) + if isinstance(p_options,dict): options = list(p_options.keys()) else: @@ -117,27 +141,40 @@ class Menu(TerminalMenu): if sort: options = sorted(options) - self.menu_options = options - self.skip = skip - self.default_option = default_option - self.multi = multi + self._menu_options = options + self._skip = skip + self._default_option = default_option + self._multi = multi + self._explode_on_interrupt = explode_on_interrupt + self._explode_warning = explode_warning + menu_title = f'\n{title}\n\n' + if header: - separator = '\n ' if not isinstance(header,(list,tuple)): - header = [header,] - if skip: - menu_title += str(_("Use ESC to skip\n")) - menu_title += separator + separator.join(header) - elif skip: - menu_title += str(_("Use ESC to skip\n\n")) + header = [header] + header = '\n'.join(header) + menu_title += f'\n{header}\n' + + action_info = '' + if skip: + action_info += str(_("Use ESC to skip")) + + if self._explode_on_interrupt: + if len(action_info) > 0: + action_info += '\n' + action_info += str(_('Use CTRL+C to reset current selection\n\n')) + + menu_title += action_info + if default_option: # if a default value was specified we move that one # to the top of the list and mark it as default as well - default = f'{default_option} (default)' - self.menu_options = [default] + [o for o in self.menu_options if default_option != o] + default = f'{default_option} {self._default_str}' + self._menu_options = [default] + [o for o in self._menu_options if default_option != o] + + self._preselection(preset_values,cursor_index) - self.preselection(preset_values,cursor_index) cursor = "> " main_menu_cursor_style = ("fg_cyan", "bold") main_menu_style = ("bg_blue", "fg_gray") @@ -145,8 +182,9 @@ class Menu(TerminalMenu): kwargs['clear_screen'] = kwargs.get('clear_screen',True) kwargs['show_search_hint'] = kwargs.get('show_search_hint',True) kwargs['cycle_cursor'] = kwargs.get('cycle_cursor',True) + super().__init__( - menu_entries=self.menu_options, + menu_entries=self._menu_options, title=menu_title, menu_cursor=cursor, menu_cursor_style=main_menu_cursor_style, @@ -160,32 +198,46 @@ class Menu(TerminalMenu): preview_command=preview_command, preview_size=preview_size, preview_title=preview_title, + explode_on_interrupt=self._explode_on_interrupt, multi_select_select_on_accept=False, **kwargs, ) - def _show(self): - idx = self.show() + def _show(self) -> MenuSelection: + try: + idx = self.show() + except KeyboardInterrupt: + return MenuSelection(type_=MenuSelectionType.Ctrl_c) + + def check_default(elem): + if self._default_option is not None and f'{self._default_option} {self._default_str}' in elem: + return self._default_option + else: + return elem + if idx is not None: if isinstance(idx, (list, tuple)): - return [self.default_option if ' (default)' in self.menu_options[i] else self.menu_options[i] for i in idx] + results = [] + for i in idx: + option = check_default(self._menu_options[i]) + results.append(option) + return MenuSelection(type_=MenuSelectionType.Selection, value=results) else: - selected = self.menu_options[idx] - if ' (default)' in selected and self.default_option: - return self.default_option - return selected + result = check_default(self._menu_options[idx]) + return MenuSelection(type_=MenuSelectionType.Selection, value=result) else: - if self.default_option: - if self.multi: - return [self.default_option] - else: - return self.default_option - return None - - def run(self): + return MenuSelection(type_=MenuSelectionType.Esc) + + def run(self) -> MenuSelection: ret = self._show() - if ret is None and not self.skip: + if ret.type_ == MenuSelectionType.Ctrl_c: + if self._explode_on_interrupt and len(self._explode_warning) > 0: + response = Menu(self._explode_warning, Menu.yes_no(), skip=False).run() + if response.value == Menu.no(): + return self.run() + + if ret.type_ is not MenuSelectionType.Selection and not self._skip: return self.run() return ret @@ -200,15 +252,15 @@ class Menu(TerminalMenu): pos = self._menu_entries.index(value) self.set_cursor_pos(pos) - def preselection(self,preset_values :list = [],cursor_index :int = None): + def _preselection(self,preset_values :Union[str, List[str]] = [], cursor_index : Optional[int] = None): def from_preset_to_cursor(): if preset_values: # if the value is not extant return 0 as cursor index try: if isinstance(preset_values,str): - self.cursor_index = self.menu_options.index(self.preset_values) + self.cursor_index = self._menu_options.index(self.preset_values) else: # should return an error, but this is smoother - self.cursor_index = self.menu_options.index(self.preset_values[0]) + self.cursor_index = self._menu_options.index(self.preset_values[0]) except ValueError: self.cursor_index = 0 @@ -218,13 +270,13 @@ class Menu(TerminalMenu): return self.preset_values = preset_values - if self.default_option: - if isinstance(preset_values,str) and self.default_option == preset_values: - self.preset_values = f"{preset_values} (default)" - elif isinstance(preset_values,(list,tuple)) and self.default_option in preset_values: - idx = preset_values.index(self.default_option) - self.preset_values[idx] = f"{preset_values[idx]} (default)" - if cursor_index is None or not self.multi: + if self._default_option: + if isinstance(preset_values,str) and self._default_option == preset_values: + self.preset_values = f"{preset_values} {self._default_str}" + elif isinstance(preset_values,(list,tuple)) and self._default_option in preset_values: + idx = preset_values.index(self._default_option) + self.preset_values[idx] = f"{preset_values[idx]} {self._default_str}" + if cursor_index is None or not self._multi: from_preset_to_cursor() - if not self.multi: # Not supported by the infraestructure + if not self._multi: # Not supported by the infraestructure self.preset_values = None diff --git a/archinstall/lib/menu/selection_menu.py b/archinstall/lib/menu/selection_menu.py index e18d92c2..54b1441b 100644 --- a/archinstall/lib/menu/selection_menu.py +++ b/archinstall/lib/menu/selection_menu.py @@ -4,7 +4,7 @@ import logging import sys from typing import Callable, Any, List, Iterator, Tuple, Optional, Dict, TYPE_CHECKING -from .menu import Menu +from .menu import Menu, MenuSelectionType from ..locale_helpers import set_keyboard_language from ..output import log from ..translation import Translation @@ -12,13 +12,15 @@ from ..translation import Translation if TYPE_CHECKING: _: Any -def select_archinstall_language(default='English'): + +def select_archinstall_language(preset_value: str) -> Optional[str]: """ copied from user_interaction/general_conf.py as a temporary measure """ languages = Translation.get_all_names() - language = Menu(_('Select Archinstall language'), languages, default_option=default).run() - return language + language = Menu(_('Select Archinstall language'), languages, preset_values=preset_value).run() + return language.value + class Selector: def __init__( @@ -307,22 +309,27 @@ class GeneralMenu: skip_empty_entries=True ).run() - if selection and self.auto_cursor: - cursor_pos = menu_options.index(selection) + 1 # before the strip otherwise fails + if selection.type_ == MenuSelectionType.Selection: + value = selection.value + + if self.auto_cursor: + cursor_pos = menu_options.index(value) + 1 # before the strip otherwise fails + + # in case the new position lands on a "placeholder" we'll skip them as well + while True: + if cursor_pos >= len(menu_options): + cursor_pos = 0 + if len(menu_options[cursor_pos]) > 0: + break + cursor_pos += 1 - # in case the new position lands on a "placeholder" we'll skip them as well - while True: - if cursor_pos >= len(menu_options): - cursor_pos = 0 - if len(menu_options[cursor_pos]) > 0: + value = value.strip() + + # if this calls returns false, we exit the menu + # we allow for an callback for special processing on realeasing control + if not self._process_selection(value): break - cursor_pos += 1 - selection = selection.strip() - if selection: - # if this calls returns false, we exit the menu. We allow for an callback for special processing on realeasing control - if not self._process_selection(selection): - break if not self.is_context_mgr: self.__exit__() @@ -443,15 +450,17 @@ class GeneralMenu: def mandatory_overview(self) -> Tuple[int, int]: mandatory_fields = 0 mandatory_waiting = 0 - for field in self._menu_options: - option = self._menu_options[field] + for field, option in self._menu_options.items(): if option.is_mandatory(): mandatory_fields += 1 if not option.has_selection(): mandatory_waiting += 1 return mandatory_fields, mandatory_waiting - def _select_archinstall_language(self, default_lang): - language = select_archinstall_language(default_lang) - self._translation.activate(language) - return language + def _select_archinstall_language(self, preset_value: str) -> str: + language = select_archinstall_language(preset_value) + if language is not None: + self._translation.activate(language) + return language + + return preset_value diff --git a/archinstall/lib/menu/simple_menu.py b/archinstall/lib/menu/simple_menu.py index a0a241bd..947259eb 100644 --- a/archinstall/lib/menu/simple_menu.py +++ b/archinstall/lib/menu/simple_menu.py @@ -596,7 +596,8 @@ class TerminalMenu: status_bar: Optional[Union[str, Iterable[str], Callable[[str], str]]] = None, status_bar_below_preview: bool = DEFAULT_STATUS_BAR_BELOW_PREVIEW, status_bar_style: Optional[Iterable[str]] = DEFAULT_STATUS_BAR_STYLE, - title: Optional[Union[str, Iterable[str]]] = None + title: Optional[Union[str, Iterable[str]]] = None, + explode_on_interrupt: bool = False ): def extract_shortcuts_menu_entries_and_preview_arguments( entries: Iterable[str], @@ -718,6 +719,7 @@ class TerminalMenu: self._search_case_sensitive = search_case_sensitive self._search_highlight_style = tuple(search_highlight_style) if search_highlight_style is not None else () self._search_key = search_key + self._explode_on_interrupt = explode_on_interrupt self._shortcut_brackets_highlight_style = ( tuple(shortcut_brackets_highlight_style) if shortcut_brackets_highlight_style is not None else () ) @@ -1538,7 +1540,9 @@ class TerminalMenu: # Only append `next_key` if it is a printable character and the first character is not the # `search_start` key self._search.search_text += next_key - except KeyboardInterrupt: + except KeyboardInterrupt as e: + if self._explode_on_interrupt: + raise e menu_was_interrupted = True finally: reset_signal_handling() @@ -1841,6 +1845,12 @@ def get_argumentparser() -> argparse.ArgumentParser: ), ) parser.add_argument("-t", "--title", action="store", dest="title", help="menu title") + parser.add_argument( + "--explode-on-interrupt", + action="store_true", + dest="explode_on_interrupt", + help="Instead of quitting the menu, this will raise the KeyboardInterrupt Exception", + ) parser.add_argument( "-V", "--version", action="store_true", dest="print_version", help="print the version number and exit" ) @@ -1971,6 +1981,7 @@ def main() -> None: status_bar_below_preview=args.status_bar_below_preview, status_bar_style=args.status_bar_style, title=args.title, + explode_on_interrupt=args.explode_on_interrupt, ) except (InvalidParameterCombinationError, InvalidStyleError, UnknownMenuEntryError) as e: print(str(e), file=sys.stderr) diff --git a/archinstall/lib/profiles.py b/archinstall/lib/profiles.py index 65a30b0b..33214ee8 100644 --- a/archinstall/lib/profiles.py +++ b/archinstall/lib/profiles.py @@ -207,6 +207,10 @@ class Profile(Script): def __repr__(self, *args :str, **kwargs :str) -> str: return f'Profile({os.path.basename(self.profile)})' + @property + def name(self) -> str: + return os.path.basename(self.profile) + def install(self) -> ModuleType: # Before installing, revert any temporary changes to the namespace. # This ensures that the namespace during installation is the original initiation namespace. diff --git a/archinstall/lib/user_interaction/disk_conf.py b/archinstall/lib/user_interaction/disk_conf.py index e84f647a..41657e1b 100644 --- a/archinstall/lib/user_interaction/disk_conf.py +++ b/archinstall/lib/user_interaction/disk_conf.py @@ -6,13 +6,14 @@ from .partitioning_conf import manage_new_and_existing_partitions, get_default_p from ..disk import BlockDevice from ..exceptions import DiskError from ..menu import Menu +from ..menu.menu import MenuSelectionType from ..output import log if TYPE_CHECKING: _: Any -def ask_for_main_filesystem_format(advanced_options=False): +def ask_for_main_filesystem_format(advanced_options=False) -> str: options = {'btrfs': 'btrfs', 'ext4': 'ext4', 'xfs': 'xfs', 'f2fs': 'f2fs'} advanced = {'ntfs': 'ntfs'} @@ -22,7 +23,7 @@ def ask_for_main_filesystem_format(advanced_options=False): prompt = _('Select which filesystem your main partition should use') choice = Menu(prompt, options, skip=False).run() - return choice + return choice.value def select_individual_blockdevice_usage(block_devices: list) -> Dict[str, Any]: @@ -30,24 +31,33 @@ def select_individual_blockdevice_usage(block_devices: list) -> Dict[str, Any]: for device in block_devices: layout = manage_new_and_existing_partitions(device) - result[device.path] = layout return result -def select_disk_layout(block_devices: list, advanced_options=False) -> Optional[Dict[str, Any]]: +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] - mode = Menu(_('Select what you wish to do with the selected block devices'), modes).run() - - if mode: - if mode == wipe_mode: - return get_default_partition_layout(block_devices, advanced_options) - else: - return select_individual_blockdevice_usage(block_devices) + 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, + explode_on_interrupt=True, + explode_warning=warning + ).run() + + match choice.type_: + case MenuSelectionType.Esc: return preset + case MenuSelectionType.Ctrl_c: 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]) -> BlockDevice: diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py index 78f6b460..af996331 100644 --- a/archinstall/lib/user_interaction/general_conf.py +++ b/archinstall/lib/user_interaction/general_conf.py @@ -3,6 +3,9 @@ from __future__ import annotations import logging from typing import List, Any, Optional, Dict, TYPE_CHECKING +import archinstall + +from ..menu.menu import MenuSelectionType from ..menu.text_input import TextInput from ..locale_helpers import list_keyboard_languages, list_timezones @@ -26,7 +29,8 @@ def ask_ntp(preset: bool = True) -> bool: 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 == Menu.no() else True + + return False if choice.value == Menu.no() else True def ask_hostname(preset: str = None) -> str: @@ -38,23 +42,31 @@ def ask_for_a_timezone(preset: str = None) -> str: timezones = list_timezones() default = 'UTC' - selected_tz = Menu(_('Select a timezone'), - list(timezones), - skip=False, - preset_values=preset, - default_option=default).run() + choice = Menu( + _('Select a timezone'), + list(timezones), + preset_values=preset, + default_option=default + ).run() - return selected_tz + match choice.type_: + case MenuSelectionType.Esc: return preset + case MenuSelectionType.Selection: return choice.value def ask_for_audio_selection(desktop: bool = True, preset: str = None) -> str: - audio = 'pipewire' if desktop else 'none' - choices = ['pipewire', 'pulseaudio'] if desktop else ['pipewire', 'pulseaudio', 'none'] - selected_audio = Menu(_('Choose an audio server'), choices, preset_values=preset, default_option=audio, skip=False).run() - return selected_audio + 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.Esc: return preset + case MenuSelectionType.Selection: return choice.value -def select_language(default_value: str, preset_value: str = None) -> str: + +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`. @@ -64,16 +76,19 @@ def select_language(default_value: str, preset_value: str = None) -> str: """ kb_lang = list_keyboard_languages() # sort alphabetically and then by length - # it's fine if the list is big because the Menu - # allows for searching anyways sorted_kb_lang = sorted(sorted(list(kb_lang)), key=len) - selected_lang = Menu(_('Select Keyboard layout'), - sorted_kb_lang, - default_option=default_value, - preset_values=preset_value, - sort=False).run() - return selected_lang + 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]: @@ -89,15 +104,18 @@ def select_mirror_regions(preset_values: Dict[str, Any] = {}) -> Dict[str, Any]: 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).run() + selected_mirror = Menu( + _('Select one of the regions to download packages from'), + list(mirrors.keys()), + preset_values=preselected, + multi=True, + explode_on_interrupt=True + ).run() - if selected_mirror is not None: - return {selected: mirrors[selected] for selected in selected_mirror} - - return {} + match selected_mirror.type_: + case MenuSelectionType.Ctrl_c: return {} + case MenuSelectionType.Esc: return preset_values + case _: return {selected: mirrors[selected] for selected in selected_mirror.value} def select_archinstall_language(default='English'): @@ -106,7 +124,7 @@ def select_archinstall_language(default='English'): return language -def select_profile() -> Optional[Profile]: +def select_profile(preset) -> Optional[Profile]: """ # Asks the user to select a profile from the available profiles. # @@ -125,12 +143,27 @@ def select_profile() -> Optional[Profile]: title = _('This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments') - selection = Menu(title=title, p_options=list(options.keys())).run() - - if selection is not None: - return options[selection] - - return None + warning = str(_('Are you sure you want to reset this setting?')) + + selection = Menu( + title=title, + p_options=list(options.keys()), + explode_on_interrupt=True, + explode_warning=warning + ).run() + + match selection.type_: + case MenuSelectionType.Selection: + return options[selection.value] if selection.value is not None else None + case MenuSelectionType.Ctrl_c: + archinstall.storage['profile_minimal'] = False + archinstall.storage['_selected_servers'] = [] + archinstall.storage['_desktop_profile'] = None + archinstall.arguments['desktop-environment'] = None + archinstall.arguments['gfx_driver_packages'] = None + return None + case MenuSelectionType.Esc: + return None def ask_additional_packages_to_install(pre_set_packages: List[str] = []) -> List[str]: @@ -171,14 +204,16 @@ def select_additional_repositories(preset: List[str]) -> List[str]: repositories = ["multilib", "testing"] - additional_repositories = Menu(_('Choose which optional additional repositories to enable'), - repositories, - sort=False, - multi=True, - preset_values=preset, - default_option=[]).run() - - if additional_repositories is not None: - return additional_repositories - - return [] + choice = Menu( + _('Choose which optional additional repositories to enable'), + repositories, + sort=False, + multi=True, + preset_values=preset, + explode_on_interrupt=True + ).run() + + match choice.type_: + case MenuSelectionType.Esc: return preset + case MenuSelectionType.Ctrl_c: 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 index d48018cf..15720064 100644 --- a/archinstall/lib/user_interaction/locale_conf.py +++ b/archinstall/lib/user_interaction/locale_conf.py @@ -4,32 +4,39 @@ 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(default: str, preset: str = None) -> str: +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'), - locale_lang, - sort=True, - preset_values=preset, - default_option=default).run() + selected_locale = Menu( + _('Choose which locale language to use'), + list(locale_lang), + sort=True, + preset_values=preset + ).run() - return selected_locale + match selected_locale.type_: + case MenuSelectionType.Selection: return selected_locale.value + case MenuSelectionType.Esc: return preset -def select_locale_enc(default: str, preset: str = None) -> str: +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'), - locale_enc, - sort=True, - preset_values=preset, - default_option=default).run() + selected_locale = Menu( + _('Choose which locale encoding to use'), + list(locale_enc), + sort=True, + preset_values=preset + ).run() - return selected_locale + match selected_locale.type_: + case MenuSelectionType.Selection: return selected_locale.value + case MenuSelectionType.Esc: return preset diff --git a/archinstall/lib/user_interaction/network_conf.py b/archinstall/lib/user_interaction/network_conf.py index 80c9106b..e4e681ce 100644 --- a/archinstall/lib/user_interaction/network_conf.py +++ b/archinstall/lib/user_interaction/network_conf.py @@ -4,6 +4,7 @@ import ipaddress import logging from typing import Any, Optional, TYPE_CHECKING, List, Union +from ..menu.menu import MenuSelectionType from ..menu.text_input import TextInput from ..models.network_configuration import NetworkConfiguration, NicType @@ -66,8 +67,12 @@ class ManualNetworkConfig(ListManager): def _select_iface(self, existing_ifaces: List[str]) -> Optional[str]: all_ifaces = list_interfaces().values() available = set(all_ifaces) - set(existing_ifaces) - iface = Menu(str(_('Select interface to add')), list(available), skip=True).run() - return iface + choice = Menu(str(_('Select interface to add')), list(available), skip=True).run() + + if choice.type_ == MenuSelectionType.Esc: + return None + + return choice.value def _edit_iface(self, edit_iface :NetworkConfiguration): iface_name = edit_iface.iface @@ -75,9 +80,9 @@ class ManualNetworkConfig(ListManager): 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).run() + mode = Menu(prompt, modes, default_option=default_mode, skip=False).run() - if mode == 'IP (static)': + 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() @@ -107,6 +112,7 @@ class ManualNetworkConfig(ListManager): 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(' ') @@ -135,23 +141,28 @@ def ask_to_configure_network(preset: Union[None, NetworkConfiguration, List[Netw elif preset.type == 'network_manager': cursor_idx = 1 - nic = Menu(_( - 'Select one network interface to configure'), + 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 + sort=False, + explode_on_interrupt=True, + explode_warning=warning ).run() - if not nic: - return preset + match choice.type_: + case MenuSelectionType.Esc: return preset + case MenuSelectionType.Ctrl_c: return None - if nic == network_options['none']: + if choice.value == network_options['none']: return None - elif nic == network_options['iso_config']: + elif choice.value == network_options['iso_config']: return NetworkConfiguration(NicType.ISO) - elif nic == network_options['network_manager']: + elif choice.value == network_options['network_manager']: return NetworkConfiguration(NicType.NM) - elif nic == network_options['manual']: + elif choice.value == network_options['manual']: manual = ManualNetworkConfig('Configure interfaces', preset) return manual.run_manual() diff --git a/archinstall/lib/user_interaction/partitioning_conf.py b/archinstall/lib/user_interaction/partitioning_conf.py index afca1cef..c3dc4146 100644 --- a/archinstall/lib/user_interaction/partitioning_conf.py +++ b/archinstall/lib/user_interaction/partitioning_conf.py @@ -1,8 +1,10 @@ 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 from ..disk.validators import fs_types @@ -80,21 +82,26 @@ def _get_partitions(partitions :List[Partition], filter_ :Callable = None) -> Li return partition_indexes -def select_partition(title :str, partitions :List[Partition], multiple :bool = False, filter_ :Callable = None) -> Union[int, List[int], None]: +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 - partition = Menu(title, partition_indexes, multi=multiple).run() + choice = Menu(title, partition_indexes, multi=multiple).run() - if partition is not None: - if isinstance(partition, list): - return [int(p) for p in partition] - else: - return int(partition) + if choice.type_ == MenuSelectionType.Esc: + return None - 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( @@ -114,14 +121,15 @@ def select_individual_blockdevice_usage(block_devices: list) -> Dict[str, Any]: for device in block_devices: layout = manage_new_and_existing_partitions(device) - result[device.path] = layout return result -def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, Any]: # noqa: max-complexity: 50 +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) + # Test code: [part.__dump__() for part in block_device.partitions.values()] # TODO: Squeeze in BTRFS subvolumes here @@ -136,6 +144,8 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, 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] @@ -166,11 +176,15 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, if len(block_device_struct["partitions"]): title += _current_partition_layout(block_device_struct['partitions']) + '\n' - task = Menu(title, modes, sort=False).run() + modes += [save_and_exit, cancel] - if not task: - break + 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 @@ -179,9 +193,9 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, # # https://www.gnu.org/software/parted/manual/html_node/mklabel.html # name = input("Enter a desired name for the partition: ").strip() - fstype = Menu(_('Enter a desired filesystem type for the partition'), fs_types()).run() + fs_choice = Menu(_('Enter a desired filesystem type for the partition'), fs_types()).run() - if not fstype: + if fs_choice.type_ == MenuSelectionType.Esc: continue prompt = _('Enter the start sector (percentage or block number, default: {}): ').format( @@ -214,7 +228,7 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, "mountpoint": None, "wipe": True, "filesystem": { - "format": fstype + "format": fs_choice.value } }) else: @@ -225,16 +239,13 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, from ..disk import suggest_single_disk_layout if len(block_device_struct["partitions"]): - prompt = _('{} contains queued partitions, this will remove those, are you sure?').format(block_device) - choice = Menu(prompt, Menu.yes_no(), default_option=Menu.no()).run() + 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 == Menu.no(): + if choice.value == Menu.no(): continue block_device_struct.update(suggest_single_disk_layout(block_device)[block_device.path]) - - elif task is None: - return block_device_struct else: current_layout = _current_partition_layout(block_device_struct['partitions'], with_idx=True) @@ -265,10 +276,8 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, 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() + 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 @@ -290,10 +299,10 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, if not block_device_struct["partitions"][partition].get('filesystem', None): block_device_struct["partitions"][partition]['filesystem'] = {} - fstype = Menu(_('Enter a desired filesystem type for the partition'), fs_types()).run() + fs_choice = Menu(_('Enter a desired filesystem type for the partition'), fs_types()).run() - if fstype: - block_device_struct["partitions"][partition]['filesystem']['format'] = fstype + 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) @@ -304,16 +313,16 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, if partition is not None: # Negate the current encryption marking - block_device_struct["partitions"][partition][ - 'encrypted'] = not block_device_struct["partitions"][partition].get('encrypted', False) + block_device_struct["partitions"][partition]['encrypted'] = \ + not block_device_struct["partitions"][partition].get('encrypted', 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) + 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) @@ -324,10 +333,10 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, block_device_struct["partitions"][partition]['filesystem'] = {} fstype_title = _('Enter a desired filesystem type for the partition: ') - fstype = Menu(fstype_title, fs_types()).run() + fs_choice = Menu(fstype_title, fs_types()).run() - if fstype: - block_device_struct["partitions"][partition]['filesystem']['format'] = fstype + 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 diff --git a/archinstall/lib/user_interaction/save_conf.py b/archinstall/lib/user_interaction/save_conf.py index c52b97e2..f542bc9b 100644 --- a/archinstall/lib/user_interaction/save_conf.py +++ b/archinstall/lib/user_interaction/save_conf.py @@ -5,6 +5,7 @@ from typing import Any, Dict, TYPE_CHECKING from ..configuration import ConfigurationOutput from ..menu import Menu +from ..menu.menu import MenuSelectionType from ..output import log if TYPE_CHECKING: @@ -45,14 +46,16 @@ def save_config(config: Dict): 'all': str(_('Save all')) } - selection = Menu(_('Choose which configuration to save'), - list(options.values()), - sort=False, - skip=True, - preview_size=0.75, - preview_command=preview).run() + choice = Menu( + _('Choose which configuration to save'), + list(options.values()), + sort=False, + skip=True, + preview_size=0.75, + preview_command=preview + ).run() - if not selection: + if choice.type_ == MenuSelectionType.Esc: return while True: @@ -62,13 +65,13 @@ def save_config(config: Dict): break log(_('Not a valid directory: {}').format(dest_path), fg='red') - if options['user_config'] == selection: + if options['user_config'] == choice.value: config_output.save_user_config(dest_path) - elif options['user_creds'] == selection: + elif options['user_creds'] == choice.value: config_output.save_user_creds(dest_path) - elif options['disk_layout'] == selection: + elif options['disk_layout'] == choice.value: config_output.save_disk_layout(dest_path) - elif options['all'] == selection: + elif options['all'] == choice.value: config_output.save_user_config(dest_path) config_output.save_user_creds(dest_path) - config_output.save_disk_layout + config_output.save_disk_layout(dest_path) diff --git a/archinstall/lib/user_interaction/system_conf.py b/archinstall/lib/user_interaction/system_conf.py index 17c093c1..f4ada14b 100644 --- a/archinstall/lib/user_interaction/system_conf.py +++ b/archinstall/lib/user_interaction/system_conf.py @@ -6,10 +6,9 @@ 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 -from ..translation import DeferredTranslation - if TYPE_CHECKING: _: Any @@ -25,13 +24,22 @@ def select_kernel(preset: List[str] = None) -> List[str]: kernels = ["linux", "linux-lts", "linux-zen", "linux-hardened"] default_kernel = "linux" - selected_kernels = Menu(_('Choose which kernels to use or leave blank for default "{}"').format(default_kernel), - kernels, - sort=True, - multi=True, - preset_values=preset, - default_option=default_kernel).run() - return selected_kernels + 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, + explode_on_interrupt=True, + explode_warning=warning + ).run() + + match choice.type_: + case MenuSelectionType.Esc: return preset + case MenuSelectionType.Ctrl_c: return [] + case MenuSelectionType.Selection: return choice.value def select_harddrives(preset: List[str] = []) -> List[str]: @@ -49,15 +57,24 @@ def select_harddrives(preset: List[str] = []) -> List[str]: else: preset_disks = {} - selected_harddrive = Menu(_('Select one or more hard drives to use and configure'), - list(options.keys()), - preset_values=list(preset_disks.keys()), - multi=True).run() + 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!')) - if selected_harddrive and len(selected_harddrive) > 0: - return [options[i] for i in selected_harddrive] + warning = str(_('If you reset the harddrive selection this will also reset the current disk layout. Are you sure?')) - return [] + selected_harddrive = Menu( + title, + list(options.keys()), + preset_values=list(preset_disks.keys()), + multi=True, + explode_on_interrupt=True, + explode_warning=warning + ).run() + + match selected_harddrive.type_: + case MenuSelectionType.Ctrl_c: return [] + case MenuSelectionType.Esc: 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: @@ -73,34 +90,34 @@ def select_driver(options: Dict[str, Any] = AVAILABLE_GFX_DRIVERS) -> str: if drivers: arguments = storage.get('arguments', {}) - title = DeferredTranslation('') + title = '' if has_amd_graphics(): - title += _( + 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' + )) + '\n' if has_intel_graphics(): - title += _( + 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 += _( + title += str(_( 'For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n' - ) + )) - title += _('\n\nSelect a graphics driver or leave blank to install all open-source drivers') - arguments['gfx_driver'] = Menu(title, drivers).run() + title += str(_('\n\nSelect a graphics driver or leave blank to install all open-source drivers')) + choice = Menu(title, drivers).run() - if arguments.get('gfx_driver', None) is None: - arguments['gfx_driver'] = _("All open-source (default)") + if choice.type_ != MenuSelectionType.Selection: + return arguments.get('gfx_driver') - return options.get(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': @@ -109,26 +126,36 @@ def ask_for_bootloader(advanced_options: bool = False, preset: str = None) -> st preset_val = preset bootloader = "systemd-bootctl" if has_uefi() else "grub-install" + if has_uefi(): if not advanced_options: - bootloader_choice = 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() - - if bootloader_choice == Menu.yes(): - bootloader = "grub-install" + 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.Esc: 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() - if selection != "": - if selection == 'systemd-boot': + + value = '' + match selection.type_: + case MenuSelectionType.Esc: value = preset_val + case MenuSelectionType.Selection: value = selection.value + + if value != "": + if value == 'systemd-boot': bootloader = 'systemd-bootctl' - elif selection == 'grub': + elif value == 'grub': bootloader = 'grub-install' else: - bootloader = selection + bootloader = value return bootloader @@ -138,6 +165,10 @@ def ask_for_swap(preset: bool = True) -> bool: 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() - return False if choice == Menu.no() else True + + match choice.type_: + case MenuSelectionType.Esc: 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 index 59f2dfbc..ce48607d 100644 --- a/archinstall/lib/user_interaction/utils.py +++ b/archinstall/lib/user_interaction/utils.py @@ -30,7 +30,7 @@ def check_password_strong(passwd: str) -> bool: if symbol_count**len(passwd) < 10e20: prompt = str(_("The password you are using seems to be weak, are you sure you want to use it?")) choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes()).run() - return choice == Menu.yes() + return choice.value == Menu.yes() return True @@ -40,7 +40,6 @@ def get_password(prompt: str = '') -> Optional[str]: prompt = _("Enter a password: ") while passwd := getpass.getpass(prompt): - if len(passwd.strip()) <= 0: break @@ -82,11 +81,12 @@ def do_countdown() -> bool: if SIG_TRIGGER: prompt = _('Do you really want to abort?') choice = Menu(prompt, Menu.yes_no(), skip=False).run() - if choice == 'yes': + if choice.value == Menu.yes(): exit(0) if SIG_TRIGGER is False: sys.stdin.read() + SIG_TRIGGER = False signal.signal(signal.SIGINT, sig_handler) diff --git a/examples/guided.py b/examples/guided.py index b653ea2a..f104b7e3 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -45,9 +45,8 @@ def ask_user_questions(): # Set which region to download packages from during the installation global_menu.enable('mirror-region') - if archinstall.arguments.get('advanced', False): - global_menu.enable('sys-language', True) - global_menu.enable('sys-encoding', True) + global_menu.enable('sys-language') + global_menu.enable('sys-encoding') # Ask which harddrives/block-devices we will install to # and convert them into archinstall.BlockDevice() objects. diff --git a/examples/swiss.py b/examples/swiss.py index 6d357191..9c0d469a 100644 --- a/examples/swiss.py +++ b/examples/swiss.py @@ -161,7 +161,7 @@ class SetupMenu(archinstall.GeneralMenu): self.set_option('archinstall-language', archinstall.Selector( _('Select Archinstall language'), - lambda x: self._select_archinstall_language('English'), + lambda x: self._select_archinstall_language(x), default='English', enabled=True)) self.set_option('ntp', diff --git a/profiles/desktop.py b/profiles/desktop.py index eaece9f5..e94d3505 100644 --- a/profiles/desktop.py +++ b/profiles/desktop.py @@ -1,6 +1,12 @@ # A desktop environment selector. +from typing import Any, TYPE_CHECKING + import archinstall -from archinstall import log +from archinstall import log, Menu +from archinstall.lib.menu.menu import MenuSelectionType + +if TYPE_CHECKING: + _: Any is_top_level_profile = True @@ -46,23 +52,26 @@ def _prep_function(*args, **kwargs) -> bool: other code in this stage. So it's a safe way to ask the user for more input before any other installer steps start. """ - desktop = archinstall.Menu(str(_('Select your desired desktop environment')), __supported__).run() + choice = Menu(str(_('Select your desired desktop environment')), __supported__).run() + + if choice.type_ != MenuSelectionType.Selection: + return False - if desktop: + if choice.value: # Temporarily store the selected desktop profile # in a session-safe location, since this module will get reloaded # the next time it gets executed. if not archinstall.storage.get('_desktop_profile', None): - archinstall.storage['_desktop_profile'] = desktop + archinstall.storage['_desktop_profile'] = choice.value if not archinstall.arguments.get('desktop-environment', None): - archinstall.arguments['desktop-environment'] = desktop - profile = archinstall.Profile(None, desktop) + archinstall.arguments['desktop-environment'] = choice.value + profile = archinstall.Profile(None, choice.value) # Loading the instructions with a custom namespace, ensures that a __name__ comparison is never triggered. - with profile.load_instructions(namespace=f"{desktop}.py") as imported: + with profile.load_instructions(namespace=f"{choice.value}.py") as imported: if hasattr(imported, '_prep_function'): return imported._prep_function() else: - log(f"Deprecated (??): {desktop} profile has no _prep_function() anymore") + log(f"Deprecated (??): {choice.value} profile has no _prep_function() anymore") exit(1) return False diff --git a/profiles/i3.py b/profiles/i3.py index 3283848e..37029a02 100644 --- a/profiles/i3.py +++ b/profiles/i3.py @@ -1,6 +1,8 @@ # Common package for i3, lets user select which i3 configuration they want. import archinstall +from archinstall import Menu +from archinstall.lib.menu.menu import MenuSelectionType is_top_level_profile = False @@ -27,13 +29,16 @@ def _prep_function(*args, **kwargs): supported_configurations = ['i3-wm', 'i3-gaps'] - desktop = archinstall.Menu('Select your desired configuration', supported_configurations).run() + choice = Menu('Select your desired configuration', supported_configurations).run() - if desktop: + if choice.type_ != MenuSelectionType.Selection: + return False + + if choice.value: # Temporarily store the selected desktop profile # in a session-safe location, since this module will get reloaded # the next time it gets executed. - archinstall.storage['_i3_configuration'] = desktop + archinstall.storage['_i3_configuration'] = choice.value # i3 requires a functioning Xorg installation. profile = archinstall.Profile(None, 'xorg') @@ -43,6 +48,8 @@ def _prep_function(*args, **kwargs): else: print('Deprecated (??): xorg profile has no _prep_function() anymore') + return False + if __name__ == 'i3': """ diff --git a/profiles/minimal.py b/profiles/minimal.py index 3b161511..a412aa81 100644 --- a/profiles/minimal.py +++ b/profiles/minimal.py @@ -1,4 +1,5 @@ # Used to do a minimal install +import archinstall is_top_level_profile = True @@ -12,6 +13,7 @@ def _prep_function(*args, **kwargs): we don't need to do anything special here, but it needs to exist and return True. """ + archinstall.storage['profile_minimal'] = True return True # Do nothing and just return True diff --git a/profiles/server.py b/profiles/server.py index 91ac7cf2..21681c2f 100644 --- a/profiles/server.py +++ b/profiles/server.py @@ -1,8 +1,14 @@ # Used to select various server application profiles on top of a minimal installation. import logging +from typing import Any, TYPE_CHECKING import archinstall +from archinstall import Menu +from archinstall.lib.menu.menu import MenuSelectionType + +if TYPE_CHECKING: + _: Any is_top_level_profile = True @@ -26,15 +32,18 @@ def _prep_function(*args, **kwargs): Magic function called by the importing installer before continuing any further. """ - servers = archinstall.Menu(str(_( + choice = Menu(str(_( 'Choose which servers to install, if none then a minimal installation wil be done')), available_servers, - preset_values=archinstall.storage.get('_selected_servers', []), + preset_values=kwargs['servers'], multi=True ).run() - if servers: - archinstall.storage['_selected_servers'] = servers + if choice.type_ != MenuSelectionType.Selection: + return False + + if choice.value: + archinstall.storage['_selected_servers'] = choice.value return True return False diff --git a/profiles/sway.py b/profiles/sway.py index 0819db95..b7266da3 100644 --- a/profiles/sway.py +++ b/profiles/sway.py @@ -23,8 +23,9 @@ def _check_driver() -> bool: if packages and "nvidia" in packages: prompt = 'The proprietary Nvidia driver is not supported by Sway. It is likely that you will run into issues, are you okay with that?' - choice = Menu(prompt, Menu.yes_no(), default_option=Menu.no()).run() - if choice == Menu.no(): + choice = Menu(prompt, Menu.yes_no(), default_option=Menu.no(), skip=False).run() + + if choice.value == Menu.no(): return False return True -- cgit v1.2.3-70-g09d2 From 1b9c8f302bdac570ce77951a41499bf11cbe9ef3 Mon Sep 17 00:00:00 2001 From: Alexmelman88 <99257010+Alexmelman88@users.noreply.github.com> Date: Tue, 10 May 2022 22:22:02 +0300 Subject: Updated ru locale and minor fixes (#1157) * Add files via upload * Add files via upload * Update global_menu.py --- archinstall/lib/menu/global_menu.py | 2 +- archinstall/locales/base.pot | 4 +- archinstall/locales/ru/LC_MESSAGES/base.mo | Bin 29871 -> 31612 bytes archinstall/locales/ru/LC_MESSAGES/base.po | 327 +++++++++++++++++++---------- 4 files changed, 223 insertions(+), 110 deletions(-) (limited to 'archinstall/lib/menu/global_menu.py') diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index 23a8ec11..889d4f85 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -200,7 +200,7 @@ class GlobalMenu(GeneralMenu): missing = len(self._missing_configs()) if missing > 0: return _('Install ({} config(s) missing)').format(missing) - return 'Install' + return _('Install') def _prev_network_configuration(self, cur_value: Union[NetworkConfiguration, List[NetworkConfiguration]]) -> str: if not cur_value: diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index d82b7ccd..276b7bad 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -715,8 +715,8 @@ msgstr "" msgid "Edit {}: " msgstr "" -msgid "Add :" +msgid "Add: " msgstr "" -msgid "Value :" +msgid "Value: " msgstr "" diff --git a/archinstall/locales/ru/LC_MESSAGES/base.mo b/archinstall/locales/ru/LC_MESSAGES/base.mo index 912bf476..df260e2a 100644 Binary files a/archinstall/locales/ru/LC_MESSAGES/base.mo and b/archinstall/locales/ru/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/ru/LC_MESSAGES/base.po b/archinstall/locales/ru/LC_MESSAGES/base.po index 9f06d1b8..7280a7f1 100644 --- a/archinstall/locales/ru/LC_MESSAGES/base.po +++ b/archinstall/locales/ru/LC_MESSAGES/base.po @@ -9,14 +9,19 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" "X-Generator: Poedit 3.0.1\n" msgid "[!] A log file has been created here: {} {}" msgstr "[!] Здесь был создан файл журнала: {} {}" -msgid " Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues" -msgstr " Пожалуйста, отправьте эту проблему (и файл) по адресу https://github.com/archlinux/archinstall/issues" +msgid "" +" Please submit this issue (and file) to https://github.com/archlinux/" +"archinstall/issues" +msgstr "" +" Пожалуйста, отправьте эту проблему (и файл) по адресу https://github.com/" +"archlinux/archinstall/issues" msgid "Do you really want to abort?" msgstr "Вы действительно хотите прекратить?" @@ -31,10 +36,13 @@ msgid "Desired hostname for the installation: " msgstr "Желаемое имя хоста для установки: " msgid "Username for required superuser with sudo privileges: " -msgstr "Имя пользователя для требуемого суперпользователя с привилегиями sudo: " +msgstr "" +"Имя пользователя для требуемого суперпользователя с привилегиями sudo: " msgid "Any additional users to install (leave blank for no users): " -msgstr "Любые дополнительные пользователи для установки (оставьте пустым, если пользователей нет): " +msgstr "" +"Любые дополнительные пользователи для установки (оставьте пустым, если " +"пользователей нет): " msgid "Should this user be a superuser (sudoer)?" msgstr "Должен ли этот пользователь быть суперпользователем (sudoer)?" @@ -51,38 +59,59 @@ msgstr "Выберите загрузчик" msgid "Choose an audio server" msgstr "Выберите звуковой сервер" -msgid "Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed." -msgstr "Устанавливаются только такие пакеты, как base, base-devel, linux, linux-firmware, efibootmgr и дополнительные пакеты профиля." +msgid "" +"Only packages such as base, base-devel, linux, linux-firmware, efibootmgr " +"and optional profile packages are installed." +msgstr "" +"Устанавливаются только такие пакеты, как base, base-devel, linux, linux-" +"firmware, efibootmgr и дополнительные пакеты профиля." -msgid "If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt." -msgstr "Если вы хотите использовать веб-браузер, например, firefox или chromium, вы можете указать его в следующем запросе." +msgid "" +"If you desire a web browser, such as firefox or chromium, you may specify it " +"in the following prompt." +msgstr "" +"Если вы хотите использовать веб-браузер, например, firefox или chromium, вы " +"можете указать его в следующем запросе." -msgid "Write additional packages to install (space separated, leave blank to skip): " -msgstr "Напишите дополнительные пакеты для установки (разделите пробелами, оставьте пустым, чтобы пропустить): " +msgid "" +"Write additional packages to install (space separated, leave blank to skip): " +msgstr "" +"Напишите дополнительные пакеты для установки (разделите пробелами, оставьте " +"пустым, чтобы пропустить): " msgid "Copy ISO network configuration to installation" msgstr "Копировать сетевую конфигурацию ISO в установку" -msgid "Use NetworkManager (necessary to configure internet graphically in GNOME and KDE)" -msgstr "Использовать NetworkManager (необходим для графической настройки интернета в GNOME и KDE)" +msgid "" +"Use NetworkManager (necessary to configure internet graphically in GNOME and " +"KDE)" +msgstr "" +"Использовать NetworkManager (необходим для графической настройки интернета в " +"GNOME и KDE)" msgid "Select one network interface to configure" msgstr "Выберите один сетевой интерфейс для настройки" -msgid "Select which mode to configure for \"{}\" or skip to use default mode \"{}\"" -msgstr "Выберите режим для конфигурации \"{}\" или пропустите, чтобы использовать режим по умолчанию \"{}\"." +msgid "" +"Select which mode to configure for \"{}\" or skip to use default mode \"{}\"" +msgstr "" +"Выберите режим для конфигурации \"{}\" или пропустите, чтобы использовать " +"режим по умолчанию \"{}\"." msgid "Enter the IP and subnet for {} (example: 192.168.0.5/24): " msgstr "Введите IP-адрес и подсеть для {} (пример: 192.168.0.5/24): " msgid "Enter your gateway (router) IP address or leave blank for none: " -msgstr "Введите IP-адрес вашего шлюза (маршрутизатора) или оставьте пустым, если его нет: " +msgstr "" +"Введите IP-адрес вашего шлюза (маршрутизатора) или оставьте пустым, если его " +"нет: " msgid "Enter your DNS servers (space separated, blank for none): " msgstr "Введите ваши DNS-серверы (через пробел, пустой - нет): " msgid "Select which filesystem your main partition should use" -msgstr "Выберите, какую файловую систему должен использовать ваш основной раздел" +msgstr "" +"Выберите, какую файловую систему должен использовать ваш основной раздел" msgid "Current partition layout" msgstr "Текущая разметка разделов" @@ -100,8 +129,10 @@ msgstr "Введите желаемый тип файловой системы msgid "Enter the start sector (percentage or block number, default: {}): " msgstr "Введите начальный сектор (процент или номер блока, по умолчанию: {}): " -msgid "Enter the end sector of the partition (percentage or block number, ex: {}): " -msgstr "Введите конечный сектор раздела (процент или номер блока, например: {}): " +msgid "" +"Enter the end sector of the partition (percentage or block number, ex: {}): " +msgstr "" +"Введите конечный сектор раздела (процент или номер блока, например: {}): " msgid "{} contains queued partitions, this will remove those, are you sure?" msgstr "{} содержит разделы в очереди, это удалит их, вы уверены?" @@ -124,11 +155,17 @@ msgstr "" "\n" "Выберите по индексу, какой раздел куда монтировать" -msgid " * Partition mount-points are relative to inside the installation, the boot would be /boot as an example." -msgstr " * Точки монтирования разделов являются относительными внутри установки, например, загрузочный будет /boot." +msgid "" +" * Partition mount-points are relative to inside the installation, the boot " +"would be /boot as an example." +msgstr "" +" * Точки монтирования разделов являются относительными внутри установки, " +"например, загрузочный будет /boot." msgid "Select where to mount partition (leave blank to remove mountpoint): " -msgstr "Выберите куда монтировать раздел (оставьте пустым, чтобы удалить точку монтирования): " +msgstr "" +"Выберите куда монтировать раздел (оставьте пустым, чтобы удалить точку " +"монтирования): " msgid "" "{}\n" @@ -173,16 +210,25 @@ msgid "Select Archinstall language" msgstr "Выберите язык Archinstall" msgid "Wipe all selected drives and use a best-effort default partition layout" -msgstr "Стереть все выбранные диски и использовать оптимальную схему разделов по умолчанию" +msgstr "" +"Стереть все выбранные диски и использовать оптимальную схему разделов по " +"умолчанию" -msgid "Select what to do with each individual drive (followed by partition usage)" -msgstr "Выберите, что делать с каждым отдельным диском (с последующим использованием разделов)" +msgid "" +"Select what to do with each individual drive (followed by partition usage)" +msgstr "" +"Выберите, что делать с каждым отдельным диском (с последующим использованием " +"разделов)" msgid "Select what you wish to do with the selected block devices" msgstr "Выберите, что вы хотите сделать с выбранными блочными устройствами" -msgid "This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments" -msgstr "Это список предварительно запрограммированных профилей, они могут облегчить установку таких вещей, как окружения рабочего стола" +msgid "" +"This is a list of pre-programmed profiles, they might make it easier to " +"install things like desktop environments" +msgstr "" +"Это список предварительно запрограммированных профилей, они могут облегчить " +"установку таких вещей, как окружения рабочего стола" msgid "Select Keyboard layout" msgstr "Выберите раскладку клавиатуры" @@ -191,16 +237,29 @@ msgid "Select one of the regions to download packages from" msgstr "Выберите один из регионов для загрузки пакетов" msgid "Select one or more hard drives to use and configure" -msgstr "Выберите один или несколько жестких дисков для использования и настройте их" +msgstr "" +"Выберите один или несколько жестких дисков для использования и настройте их" -msgid "For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options." -msgstr "Для наилучшей совместимости с оборудованием AMD вы можете использовать либо все варианты с открытым исходным кодом, либо AMD / ATI." +msgid "" +"For the best compatibility with your AMD hardware, you may want to use " +"either the all open-source or AMD / ATI options." +msgstr "" +"Для наилучшей совместимости с оборудованием AMD вы можете использовать либо " +"все варианты с открытым исходным кодом, либо AMD / ATI." -msgid "For the best compatibility with your Intel hardware, you may want to use either the all open-source or Intel options.\n" -msgstr "Для лучшей совместимости с оборудованием Intel вы можете использовать либо все варианты с открытым исходным кодом, либо Intel.\n" +msgid "" +"For the best compatibility with your Intel hardware, you may want to use " +"either the all open-source or Intel options.\n" +msgstr "" +"Для лучшей совместимости с оборудованием Intel вы можете использовать либо " +"все варианты с открытым исходным кодом, либо Intel.\n" -msgid "For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n" -msgstr "Для наилучшей совместимости с оборудованием Nvidia вы можете использовать проприетарный драйвер Nvidia.\n" +msgid "" +"For the best compatibility with your Nvidia hardware, you may want to use " +"the Nvidia proprietary driver.\n" +msgstr "" +"Для наилучшей совместимости с оборудованием Nvidia вы можете использовать " +"проприетарный драйвер Nvidia.\n" msgid "" "\n" @@ -209,13 +268,15 @@ msgid "" msgstr "" "\n" "\n" -"Выберите графический драйвер или оставьте пустым, чтобы установить все драйверы с открытым исходным кодом" +"Выберите графический драйвер или оставьте пустым, чтобы установить все " +"драйверы с открытым исходным кодом" msgid "All open-source (default)" msgstr "Все с открытым исходным кодом (по умолчанию)" msgid "Choose which kernels to use or leave blank for default \"{}\"" -msgstr "Выберите, какие ядра использовать, или оставьте пустым по умолчанию \"{}\"." +msgstr "" +"Выберите, какие ядра использовать, или оставьте пустым по умолчанию \"{}\"." msgid "Choose which locale language to use" msgstr "Выберите, какой язык локали использовать" @@ -232,8 +293,12 @@ msgstr "Выберите один или несколько из приведе msgid "Adding partition...." msgstr "Добавление раздела...." -msgid "You need to enter a valid fs-type in order to continue. See `man parted` for valid fs-type's." -msgstr "Чтобы продолжить, вам нужно ввести действительный fs-тип. Смотрите `man parted` для правильных fs-типов." +msgid "" +"You need to enter a valid fs-type in order to continue. See `man parted` for " +"valid fs-type's." +msgstr "" +"Чтобы продолжить, вам нужно ввести действительный fs-тип. Смотрите `man " +"parted` для правильных fs-типов." msgid "Error: Listing profiles on URL \"{}\" resulted in:" msgstr "Ошибка: Перечисление профилей по URL \"{}\" привело к:" @@ -305,7 +370,8 @@ msgid "" "Do you wish to continue?" msgstr "" "Вы решили пропустить выбор жесткого диска\n" -"и будете использовать любой диск, смонтированный по адресу {} (экспериментально)\n" +"и будете использовать любой диск, смонтированный по адресу {} " +"(экспериментально)\n" "ПРЕДУПРЕЖДЕНИЕ: Archinstall не будет проверять пригодность этой установки.\n" "Вы хотите продолжить?" @@ -325,13 +391,16 @@ msgid "Assign mount-point for a partition" msgstr "Назначить точку монтирования для раздела" msgid "Mark/Unmark a partition to be formatted (wipes data)" -msgstr "Пометить/снять отметку с раздела, который будет отформатирован (стирание данных)" +msgstr "" +"Пометить/снять отметку с раздела, который будет отформатирован (стирание " +"данных)" msgid "Mark/Unmark a partition as encrypted" msgstr "Пометить/снять отметку с раздела как зашифрованный" msgid "Mark/Unmark a partition as bootable (automatic for /boot)" -msgstr "Пометить/снять отметку с раздела как загрузочный (автоматически для /boot)" +msgstr "" +"Пометить/снять отметку с раздела как загрузочный (автоматически для /boot)" msgid "Set desired filesystem for a partition" msgstr "Установите желаемую файловую систему для раздела" @@ -371,7 +440,8 @@ msgid "Enter a encryption password for {}" msgstr "Введите пароль шифрования для {}" msgid "Enter disk encryption password (leave blank for no encryption): " -msgstr "Введите пароль шифрования диска (оставьте пустым для отсутствия шифрования): " +msgstr "" +"Введите пароль шифрования диска (оставьте пустым для отсутствия шифрования): " msgid "Create a required super-user with sudo privileges: " msgstr "Создайте необходимого суперпользователя с привилегиями sudo: " @@ -382,31 +452,43 @@ msgstr "Введите пароль root (оставьте пустым, что msgid "Password for user \"{}\": " msgstr "Пароль для пользователя \"{}\": " -msgid "Verifying that additional packages exist (this might take a few seconds)" -msgstr "Проверка наличия дополнительных пакетов (это может занять несколько секунд)" +msgid "" +"Verifying that additional packages exist (this might take a few seconds)" +msgstr "" +"Проверка наличия дополнительных пакетов (это может занять несколько секунд)" -msgid "Would you like to use automatic time synchronization (NTP) with the default time servers?\n" -msgstr "Вы хотите использовать автоматическую синхронизацию времени (NTP) с серверами времени по умолчанию?\n" +msgid "" +"Would you like to use automatic time synchronization (NTP) with the default " +"time servers?\n" +msgstr "" +"Вы хотите использовать автоматическую синхронизацию времени (NTP) с " +"серверами времени по умолчанию?\n" msgid "" -"Hardware time and other post-configuration steps might be required in order for NTP to work.\n" +"Hardware time and other post-configuration steps might be required in order " +"for NTP to work.\n" "For more information, please check the Arch wiki" msgstr "" -"Для работы NTP может потребоваться аппаратное время и другие шаги после конфигурации.\n" +"Для работы NTP может потребоваться аппаратное время и другие шаги после " +"конфигурации.\n" "Для получения дополнительной информации, пожалуйста, ознакомьтесь с ArchWiki" msgid "Enter a username to create an additional user (leave blank to skip): " -msgstr "Введите имя пользователя для создания дополнительного пользователя (оставьте пустым, чтобы пропустить): " +msgstr "" +"Введите имя пользователя для создания дополнительного пользователя (оставьте " +"пустым, чтобы пропустить): " msgid "Use ESC to skip\n" msgstr "Используйте ESC, чтобы пропустить\n" msgid "" "\n" -" Choose an object from the list, and select one of the available actions for it to execute" +" Choose an object from the list, and select one of the available actions for " +"it to execute" msgstr "" "\n" -" Выберите объект из списка и выберите одно из доступных действий для его выполнения" +" Выберите объект из списка и выберите одно из доступных действий для его " +"выполнения" msgid "Cancel" msgstr "Отменить" @@ -442,11 +524,17 @@ msgstr "" "\n" "Это выбранная вами конфигурация:" -msgid "Pacman is already running, waiting maximum 10 minutes for it to terminate." -msgstr "Pacman уже запущен, ожидание его завершения составляет максимум 10 минут." +msgid "" +"Pacman is already running, waiting maximum 10 minutes for it to terminate." +msgstr "" +"Pacman уже запущен, ожидание его завершения составляет максимум 10 минут." -msgid "Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall." -msgstr "Существовавшая ранее блокировка pacman не завершилась. Пожалуйста, очистите все существующие сессии pacman перед использованием archinstall." +msgid "" +"Pre-existing pacman lock never exited. Please clean up any existing pacman " +"sessions before using archinstall." +msgstr "" +"Существовавшая ранее блокировка pacman не завершилась. Пожалуйста, очистите " +"все существующие сессии pacman перед использованием archinstall." msgid "Choose which optional additional repositories to enable" msgstr "Выберите, какие дополнительные репозитории следует включить" @@ -537,7 +625,8 @@ msgid "Missing configurations:\n" msgstr "Отсутствующие конфигурации:\n" msgid "Either root-password or at least 1 superuser must be specified" -msgstr "Должен быть указан либо пароль root, либо как минимум 1 суперпользователь" +msgstr "" +"Должен быть указан либо пароль root, либо как минимум 1 суперпользователь" msgid "Manage superuser accounts: " msgstr "Управление учетными записями суперпользователей: " @@ -597,8 +686,12 @@ msgstr "Хотите ли вы использовать сжатие BTRFS?" msgid "Would you like to create a separate partition for /home?" msgstr "Хотите ли вы создать отдельный раздел для /home?" -msgid "The selected drives do not have the minimum capacity required for an automatic suggestion\n" -msgstr "Выбранные диски не имеют минимальной емкости, необходимой для автоматического предложения\n" +msgid "" +"The selected drives do not have the minimum capacity required for an " +"automatic suggestion\n" +msgstr "" +"Выбранные диски не имеют минимальной емкости, необходимой для " +"автоматического предложения\n" msgid "Minimum capacity for /home partition: {}GB\n" msgstr "Минимальный размер раздела /home: {}GB\n" @@ -625,7 +718,9 @@ msgid "No iface specified for manual configuration" msgstr "Не указан iface для ручной настройки" msgid "Manual nic configuration with no auto DHCP requires an IP address" -msgstr "Ручная конфигурация сетевого адаптера без автоматического DHCP требует IP-адреса" +msgstr "" +"Ручная конфигурация сетевого адаптера без автоматического DHCP требует IP-" +"адреса" msgid "Add interface" msgstr "Добавить интерфейс" @@ -645,109 +740,127 @@ msgstr "Ручная конфигурация" msgid "Mark/Unmark a partition as compressed (btrfs only)" msgstr "Пометить/снять отметку с раздела как сжатый (только для btrfs)" -msgid "The password you are using seems to be weak, are you sure you want to use it?" -msgstr "Пароль, который вы используете, кажется слабым, вы уверены, что хотите его использовать?" +msgid "" +"The password you are using seems to be weak, are you sure you want to use it?" +msgstr "" +"Пароль, который вы используете, кажется слабым, вы уверены, что хотите его " +"использовать?" -msgid "Provides a selection of desktop environments and tiling window managers, e.g. gnome, kde, sway" -msgstr "Предоставляет выбор окружений рабочего стола и тайловых оконных менеджеров, например, gnome, kde, sway" +msgid "" +"Provides a selection of desktop environments and tiling window managers, e." +"g. gnome, kde, sway" +msgstr "" +"Предоставляет выбор окружений рабочего стола и тайловых оконных менеджеров, " +"например, gnome, kde, sway" msgid "Select your desired desktop environment" msgstr "Выберите желаемое окружение рабочего стола" -msgid "A very basic installation that allows you to customize Arch Linux as you see fit." -msgstr "Очень базовая установка, позволяющая настроить Arch Linux по своему усмотрению." +msgid "" +"A very basic installation that allows you to customize Arch Linux as you see " +"fit." +msgstr "" +"Очень базовая установка, позволяющая настроить Arch Linux по своему " +"усмотрению." -msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" -msgstr "Предоставляет выбор различных пакетов сервера для установки и включения, например, httpd, nginx, mariadb" +msgid "" +"Provides a selection of various server packages to install and enable, e.g. " +"httpd, nginx, mariadb" +msgstr "" +"Предоставляет выбор различных пакетов сервера для установки и включения, " +"например, httpd, nginx, mariadb" -msgid "Choose which servers to install, if none then a minimal installation wil be done" -msgstr "Выберите серверы для установки, если их нет, то будет выполнена минимальная установка" +msgid "" +"Choose which servers to install, if none then a minimal installation wil be " +"done" +msgstr "" +"Выберите серверы для установки, если их нет, то будет выполнена минимальная " +"установка" msgid "Installs a minimal system as well as xorg and graphics drivers." -msgstr "Устанавливает минимальную систему, а также xorg и графические драйверы." +msgstr "" +"Устанавливает минимальную систему, а также xorg и графические драйверы." msgid "Press Enter to continue." msgstr "Нажмите Enter, чтобы продолжить." -msgid "Would you like to chroot into the newly created installation and perform post-installation configuration?" -msgstr "Хотите ли вы использовать chroot в новой созданной установке и выполнить настройку после установки?" +msgid "" +"Would you like to chroot into the newly created installation and perform " +"post-installation configuration?" +msgstr "" +"Хотите ли вы использовать chroot в новой созданной установке и выполнить " +"настройку после установки?" -#, fuzzy msgid "Are you sure you want to reset this setting?" -msgstr "вы уверены, что хотите его использовать?" +msgstr "Вы уверены, что хотите сбросить эту настройку?" -#, fuzzy msgid "Select one or more hard drives to use and configure\n" -msgstr "Выберите один или несколько жестких дисков для использования и настройте их" +msgstr "" +"Выберите один или несколько жестких дисков для использования и настройки\n" msgid "Any modifications to the existing setting will reset the disk layout!" msgstr "" +"Любые изменения существующей настройки приведут к сбросу разметки диска!" -msgid "If you reset the harddrive selection this will also reset the current disk layout. Are you sure?" +msgid "" +"If you reset the harddrive selection this will also reset the current disk " +"layout. Are you sure?" msgstr "" +"Если вы сбросите выбор жесткого диска, это также сбросит текущую разметку " +"диска. Вы уверены?" -#, fuzzy msgid "Save and exit" -msgstr "Подтвердить и выйти" +msgstr "Сохранить и выйти" -#, fuzzy msgid "" "{}\n" "contains queued partitions, this will remove those, are you sure?" -msgstr "{} содержит разделы в очереди, это удалит их, вы уверены?" +msgstr "" +"{}\n" +"содержит разделы в очереди, это удалит их, вы уверены?" -#, fuzzy msgid "No audio server" -msgstr "Выберите звуковой сервер" +msgstr "Отсутствует звуковой сервер" msgid "(default)" -msgstr "" +msgstr "(по умолчанию)" -#, fuzzy msgid "Use ESC to skip" -msgstr "Используйте ESC, чтобы пропустить\n" +msgstr "Используйте ESC, чтобы пропустить" msgid "" "Use CTRL+C to reset current selection\n" "\n" msgstr "" +"Используйте CTRL+C для сброса текущего выбора\n" +"\n" -#, fuzzy msgid "Copy to: " -msgstr "Копировать в:" +msgstr "Копировать в: " -#, fuzzy msgid "Edit: " -msgstr "Редактировать" +msgstr "Редактировать: " msgid "Key: " -msgstr "" +msgstr "Ключ: " -#, fuzzy msgid "Edit {}: " -msgstr "Редактировать:" +msgstr "Редактировать {}: " -msgid "Add :" -msgstr "Добавить:" +msgid "Add: " +msgstr "Добавить: " -msgid "Value :" -msgstr "Значение:" +msgid "Value: " +msgstr "Значение: " #, python-brace-format #~ msgid "Edit {origkey} :" #~ msgstr "Редактировать {origkey}:" -#~ msgid "Copy to :" -#~ msgstr "Копировать в:" - -#~ msgid "Edite :" -#~ msgstr "Редактировать:" - -#~ msgid "Key :" -#~ msgstr "Ключ:" - #~ msgid "Archinstall requires root privileges to run. See --help for more." -#~ msgstr "Для запуска Archinstall требуются привилегии root. Для получения дополнительной информации смотрите --help." +#~ msgstr "" +#~ "Для запуска Archinstall требуются привилегии root. Для получения " +#~ "дополнительной информации смотрите --help." #~ msgid " ! Formatting {archinstall.arguments['harddrives']} in " #~ msgstr " ! Форматирование {archinstall.arguments['harddrives']} в " -- cgit v1.2.3-70-g09d2 From bdf11913d4bbfa84a6008b3a0b707edb78a1c210 Mon Sep 17 00:00:00 2001 From: Reza <3228126+i2@users.noreply.github.com> Date: Mon, 16 May 2022 18:11:09 -0300 Subject: Fix lengthy translations in Main Menu (Important) (#1195) * Fix lengthy translations in Main Menu * rename 'Additional repositories' to 'Optional repositories' * rename 'Drives' to 'Drive(s)' * rename 'Choose keyboard layout' to 'Select keyboard layout' --- archinstall/lib/menu/global_menu.py | 40 ++++++------ archinstall/lib/menu/selection_menu.py | 2 +- archinstall/lib/user_interaction/general_conf.py | 4 +- archinstall/locales/base.pot | 42 ++++++------- archinstall/locales/de/LC_MESSAGES/base.po | 76 +++++++++++----------- archinstall/locales/en/LC_MESSAGES/base.po | 42 ++++++------- archinstall/locales/es/LC_MESSAGES/base.po | 80 ++++++++++++------------ archinstall/locales/fr/LC_MESSAGES/base.po | 80 ++++++++++++------------ archinstall/locales/nl/LC_MESSAGES/base.po | 80 ++++++++++++------------ archinstall/locales/pl/LC_MESSAGES/base.po | 80 ++++++++++++------------ archinstall/locales/pt/LC_MESSAGES/base.po | 80 ++++++++++++------------ archinstall/locales/ru/LC_MESSAGES/base.po | 80 ++++++++++++------------ archinstall/locales/sv/LC_MESSAGES/base.po | 80 ++++++++++++------------ archinstall/locales/ur/LC_MESSAGES/base.po | 80 ++++++++++++------------ examples/swiss.py | 2 +- 15 files changed, 424 insertions(+), 424 deletions(-) (limited to 'archinstall/lib/menu/global_menu.py') diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index 889d4f85..f0f327ee 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -42,33 +42,33 @@ class GlobalMenu(GeneralMenu): # archinstall.Language will not use preset values self._menu_options['archinstall-language'] = \ Selector( - _('Select Archinstall language'), + _('Archinstall language'), lambda x: self._select_archinstall_language(x), default='English') self._menu_options['keyboard-layout'] = \ Selector( - _('Select keyboard layout'), + _('Keyboard layout'), lambda preset: select_language(preset), default='us') self._menu_options['mirror-region'] = \ Selector( - _('Select mirror region'), + _('Mirror region'), lambda preset: select_mirror_regions(preset), display_func=lambda x: list(x.keys()) if x else '[]', default={}) self._menu_options['sys-language'] = \ Selector( - _('Select locale language'), + _('Locale language'), lambda preset: select_locale_lang(preset), default='en_US') self._menu_options['sys-encoding'] = \ Selector( - _('Select locale encoding'), + _('Locale encoding'), lambda preset: select_locale_enc(preset), default='UTF-8') self._menu_options['harddrives'] = \ Selector( - _('Select harddrives'), + _('Drive(s)'), lambda preset: self._select_harddrives(preset)) self._menu_options['disk_layouts'] = \ Selector( @@ -87,28 +87,28 @@ class GlobalMenu(GeneralMenu): dependencies=['harddrives']) self._menu_options['swap'] = \ Selector( - _('Use swap'), + _('Swap'), lambda preset: ask_for_swap(preset), default=True) self._menu_options['bootloader'] = \ Selector( - _('Select bootloader'), + _('Bootloader'), lambda preset: ask_for_bootloader(storage['arguments'].get('advanced', False),preset), default="systemd-bootctl" if has_uefi() else "grub-install") self._menu_options['hostname'] = \ Selector( - _('Specify hostname'), + _('Hostname'), ask_hostname, default='archlinux') # root password won't have preset value self._menu_options['!root-password'] = \ Selector( - _('Set root password'), + _('root password'), lambda preset:self._set_root_password(), display_func=lambda x: secret(x) if x else 'None') self._menu_options['!superusers'] = \ Selector( - _('Specify superuser account'), + _('Superuser account'), lambda preset: self._create_superuser_account(), default={}, exec_func=lambda n,v:self._users_resynch(), @@ -116,53 +116,53 @@ class GlobalMenu(GeneralMenu): display_func=lambda x: self._display_superusers()) self._menu_options['!users'] = \ Selector( - _('Specify user account'), + _('User account'), lambda x: self._create_user_account(), default={}, exec_func=lambda n,v:self._users_resynch(), display_func=lambda x: list(x.keys()) if x else '[]') self._menu_options['profile'] = \ Selector( - _('Specify profile'), + _('Profile'), lambda preset: self._select_profile(preset), display_func=lambda x: x if x else 'None') self._menu_options['audio'] = \ Selector( - _('Select audio'), + _('Audio'), lambda preset: ask_for_audio_selection(is_desktop_profile(storage['arguments'].get('profile', None)),preset), display_func=lambda x: x if x else 'None', default=None ) self._menu_options['kernels'] = \ Selector( - _('Select kernels'), + _('Kernels'), lambda preset: select_kernel(preset), default=['linux']) self._menu_options['packages'] = \ Selector( - _('Additional packages to install'), + _('Additional packages'), # lambda x: ask_additional_packages_to_install(storage['arguments'].get('packages', None)), ask_additional_packages_to_install, default=[]) self._menu_options['additional-repositories'] = \ Selector( - _('Additional repositories to enable'), + _('Optional repositories'), select_additional_repositories, default=[]) self._menu_options['nic'] = \ Selector( - _('Configure network'), + _('Network configuration'), ask_to_configure_network, display_func=lambda x: self._prev_network_configuration(x), default={}) self._menu_options['timezone'] = \ Selector( - _('Select timezone'), + _('Timezone'), lambda preset: ask_for_a_timezone(preset), default='UTC') self._menu_options['ntp'] = \ Selector( - _('Set automatic time sync (NTP)'), + _('Automatic time sync (NTP)'), lambda preset: self._select_ntp(preset), default=True) self._menu_options['__separator__'] = \ diff --git a/archinstall/lib/menu/selection_menu.py b/archinstall/lib/menu/selection_menu.py index 123ddf7e..73ba1148 100644 --- a/archinstall/lib/menu/selection_menu.py +++ b/archinstall/lib/menu/selection_menu.py @@ -18,7 +18,7 @@ def select_archinstall_language(preset_value: str) -> Optional[str]: copied from user_interaction/general_conf.py as a temporary measure """ languages = Translation.get_available_lang() - language = Menu(_('Select Archinstall language'), languages, preset_values=preset_value).run() + language = Menu(_('Archinstall language'), languages, preset_values=preset_value).run() return language.value diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py index 43afef8d..c3a2a7a7 100644 --- a/archinstall/lib/user_interaction/general_conf.py +++ b/archinstall/lib/user_interaction/general_conf.py @@ -79,7 +79,7 @@ def select_language(preset_value: str = None) -> str: sorted_kb_lang = sorted(sorted(list(kb_lang)), key=len) selected_lang = Menu( - _('Select Keyboard layout'), + _('Select keyboard layout'), sorted_kb_lang, preset_values=preset_value, sort=False @@ -120,7 +120,7 @@ def select_mirror_regions(preset_values: Dict[str, Any] = {}) -> Dict[str, Any]: def select_archinstall_language(default='English'): languages = Translation.get_available_lang() - language = Menu(_('Select Archinstall language'), languages, default_option=default).run() + language = Menu(_('Archinstall language'), languages, default_option=default).run() return language diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index 46d3a9e1..61cbac4d 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -154,7 +154,7 @@ msgstr "" msgid "Enter a desired filesystem type for the partition: " msgstr "" -msgid "Select Archinstall language" +msgid "Archinstall language" msgstr "" msgid "Wipe all selected drives and use a best-effort default partition layout" @@ -172,7 +172,7 @@ msgid "" "install things like desktop environments" msgstr "" -msgid "Select Keyboard layout" +msgid "Select keyboard layout" msgstr "" msgid "Select one of the regions to download packages from" @@ -234,19 +234,19 @@ msgstr "" msgid "Error: Could not decode \"{}\" result as JSON:" msgstr "" -msgid "Select keyboard layout" +msgid "Keyboard layout" msgstr "" -msgid "Select mirror region" +msgid "Mirror region" msgstr "" -msgid "Select locale language" +msgid "Locale language" msgstr "" -msgid "Select locale encoding" +msgid "Locale encoding" msgstr "" -msgid "Select harddrives" +msgid "Drive(s)" msgstr "" msgid "Select disk layout" @@ -255,37 +255,37 @@ msgstr "" msgid "Set encryption password" msgstr "" -msgid "Use swap" +msgid "Swap" msgstr "" -msgid "Select bootloader" +msgid "Bootloader" msgstr "" -msgid "Set root password" +msgid "root password" msgstr "" -msgid "Specify superuser account" +msgid "Superuser account" msgstr "" -msgid "Specify user account" +msgid "User account" msgstr "" -msgid "Specify profile" +msgid "Profile" msgstr "" -msgid "Select audio" +msgid "Audio" msgstr "" -msgid "Select kernels" +msgid "Kernels" msgstr "" -msgid "Additional packages to install" +msgid "Additional packages" msgstr "" -msgid "Configure network" +msgid "Network configuration" msgstr "" -msgid "Set automatic time sync (NTP)" +msgid "Automatic time sync (NTP)" msgstr "" msgid "Install ({} config(s) missing)" @@ -328,13 +328,13 @@ msgstr "" msgid "Abort" msgstr "" -msgid "Specify hostname" +msgid "Hostname" msgstr "" msgid "Not configured, unavailable unless setup manually" msgstr "" -msgid "Select timezone" +msgid "Timezone" msgstr "" msgid "Set/Modify the below options" @@ -511,7 +511,7 @@ msgstr "" msgid "are you sure you want to use it?" msgstr "" -msgid "Additional repositories to enable" +msgid "Optional repositories" msgstr "" msgid "Save configuration" diff --git a/archinstall/locales/de/LC_MESSAGES/base.po b/archinstall/locales/de/LC_MESSAGES/base.po index db3a7e40..44920752 100644 --- a/archinstall/locales/de/LC_MESSAGES/base.po +++ b/archinstall/locales/de/LC_MESSAGES/base.po @@ -168,7 +168,7 @@ msgstr "" msgid "Enter a desired filesystem type for the partition: " msgstr "Bitte geben sie einen gewünschten Dateisystemtyp für die Partition ein: " -msgid "Select Archinstall language" +msgid "Archinstall language" msgstr "Sprache für Archinstall" msgid "Wipe all selected drives and use a best-effort default partition layout" @@ -183,7 +183,7 @@ msgstr "Bitte wählen sie was mit dem ausgewählten Gerät geschehen soll" msgid "This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments" msgstr "Dies ist eine Liste von bereits programmierten Profilen, diese ermöglichen es einfacher Desktop Umgebungen einzustellen" -msgid "Select Keyboard layout" +msgid "Select keyboard layout" msgstr "Bitte wählen sie ein Tastaturlayout aus" msgid "Select one of the regions to download packages from" @@ -240,20 +240,20 @@ msgstr "Fehler: Auflistung von Profilen mit URL \"{}\":" msgid "Error: Could not decode \"{}\" result as JSON:" msgstr "Fehler: \"{}\" konnte nicht in ein JSON format dekodiert werden:" -msgid "Select keyboard layout" -msgstr "Tastaturlayout auswählen" +msgid "Keyboard layout" +msgstr "Tastaturlayout" -msgid "Select mirror region" -msgstr "Mirror-region auswählen" +msgid "Mirror region" +msgstr "Mirror-region" -msgid "Select locale language" -msgstr "Lokale Sprache auswählen" +msgid "Locale language" +msgstr "Lokale Sprache" -msgid "Select locale encoding" -msgstr "Lokale Kodierung auswählen" +msgid "Locale encoding" +msgstr "Lokale Kodierung" -msgid "Select harddrives" -msgstr "Laufwerke auswählen" +msgid "Drive(s)" +msgstr "Laufwerke" msgid "Select disk layout" msgstr "Laufwerke-layout auswählen" @@ -261,37 +261,37 @@ msgstr "Laufwerke-layout auswählen" msgid "Set encryption password" msgstr "Verschlüsselungspasswort angeben" -msgid "Use swap" -msgstr "Swap benützen" +msgid "Swap" +msgstr "Swap" -msgid "Select bootloader" -msgstr "Bootloader auswählen" +msgid "Bootloader" +msgstr "Bootloader" -msgid "Set root password" -msgstr "Root Passwort wählen" +msgid "root password" +msgstr "Root Passwort" -msgid "Specify superuser account" -msgstr "Superuser Konto wählen" +msgid "Superuser account" +msgstr "Superuser Konto" -msgid "Specify user account" -msgstr "Benutzerkonto wählen" +msgid "User account" +msgstr "Benutzerkonto" -msgid "Specify profile" -msgstr "Profile auswählen" +msgid "Profile" +msgstr "Profile" -msgid "Select audio" -msgstr "Audio auswählen" +msgid "Audio" +msgstr "Audio" -msgid "Select kernels" -msgstr "Kernel auswählen" +msgid "Kernels" +msgstr "Kernels" -msgid "Additional packages to install" -msgstr "Zus. Packete für die Installation" +msgid "Additional packages" +msgstr "Zus. Packete" -msgid "Configure network" +msgid "Network configuration" msgstr "Netzwerkonfiguration" -msgid "Set automatic time sync (NTP)" +msgid "Automatic time sync (NTP)" msgstr "Autom. Zeitsynchronisierung (NTP)" msgid "Install ({} config(s) missing)" @@ -338,14 +338,14 @@ msgstr "Bitte wählen Sie einen Dateisystemtyp für die Partition aus" msgid "Abort" msgstr "Abbrechen" -msgid "Specify hostname" -msgstr "Hostnamen wählen" +msgid "Hostname" +msgstr "Hostnamen" msgid "Not configured, unavailable unless setup manually" msgstr "Nicht konfiguriert, unverfügbar wenn nicht selber eingestellt" -msgid "Select timezone" -msgstr "Zeitzone wählen" +msgid "Timezone" +msgstr "Zeitzone" msgid "Set/Modify the below options" msgstr "Setzen sie die unten stehenden Einstellungen" @@ -526,8 +526,8 @@ msgstr "Das gewählte Passwort ist sehr schwach," msgid "are you sure you want to use it?" msgstr "wollen sie dieses wirklich verwenden?" -msgid "Additional repositories to enable" -msgstr "Zus. Repositories einzuschalten" +msgid "Optional repositories" +msgstr "Zus. Repositories" msgid "Save configuration" msgstr "Konfiguration speichern" diff --git a/archinstall/locales/en/LC_MESSAGES/base.po b/archinstall/locales/en/LC_MESSAGES/base.po index b7a9364f..8348faec 100644 --- a/archinstall/locales/en/LC_MESSAGES/base.po +++ b/archinstall/locales/en/LC_MESSAGES/base.po @@ -141,7 +141,7 @@ msgstr "" msgid "Enter a desired filesystem type for the partition: " msgstr "" -msgid "Select Archinstall language" +msgid "Archinstall language" msgstr "" msgid "Wipe all selected drives and use a best-effort default partition layout" @@ -156,7 +156,7 @@ msgstr "" msgid "This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments" msgstr "" -msgid "Select Keyboard layout" +msgid "Select keyboard layout" msgstr "" msgid "Select one of the regions to download packages from" @@ -210,19 +210,19 @@ msgstr "" msgid "Error: Could not decode \"{}\" result as JSON:" msgstr "" -msgid "Select keyboard layout" +msgid "Keyboard layout" msgstr "" -msgid "Select mirror region" +msgid "Mirror region" msgstr "" -msgid "Select locale language" +msgid "Locale language" msgstr "" -msgid "Select locale encoding" +msgid "Locale encoding" msgstr "" -msgid "Select harddrives" +msgid "Drive(s)" msgstr "" msgid "Select disk layout" @@ -231,37 +231,37 @@ msgstr "" msgid "Set encryption password" msgstr "" -msgid "Use swap" +msgid "Swap" msgstr "" -msgid "Select bootloader" +msgid "Bootloader" msgstr "" -msgid "Set root password" +msgid "root password" msgstr "" -msgid "Specify superuser account" +msgid "Superuser account" msgstr "" -msgid "Specify user account" +msgid "User account" msgstr "" -msgid "Specify profile" +msgid "Profile" msgstr "" -msgid "Select audio" +msgid "Audio" msgstr "" -msgid "Select kernels" +msgid "Kernels" msgstr "" -msgid "Additional packages to install" +msgid "Additional packages" msgstr "" -msgid "Configure network" +msgid "Network configuration" msgstr "" -msgid "Set automatic time sync (NTP)" +msgid "Automatic time sync (NTP)" msgstr "" msgid "Install ({} config(s) missing)" @@ -304,13 +304,13 @@ msgstr "" msgid "Abort" msgstr "" -msgid "Specify hostname" +msgid "Hostname" msgstr "" msgid "Not configured, unavailable unless setup manually" msgstr "" -msgid "Select timezone" +msgid "Timezone" msgstr "" msgid "Set/Modify the below options" @@ -479,7 +479,7 @@ msgstr "" msgid "are you sure you want to use it?" msgstr "" -msgid "Additional repositories to enable" +msgid "Optional repositories" msgstr "" msgid "Save configuration" diff --git a/archinstall/locales/es/LC_MESSAGES/base.po b/archinstall/locales/es/LC_MESSAGES/base.po index 175cff9e..68e16367 100644 --- a/archinstall/locales/es/LC_MESSAGES/base.po +++ b/archinstall/locales/es/LC_MESSAGES/base.po @@ -39,7 +39,7 @@ msgid "Should this user be a superuser (sudoer)?" msgstr "Debería este usuario ser un superusuario (sudoer)?" msgid "Select a timezone" -msgstr "Selecciona una zona horaria" +msgstr "Zona horaria" msgid "Would you like to use GRUB as a bootloader instead of systemd-boot?" msgstr "Te gustaría usar GRUB como gestor de arranque en lugar de systemd-boot?" @@ -168,8 +168,8 @@ msgstr "" msgid "Enter a desired filesystem type for the partition: " msgstr "Escriba el tipo de sistema de archivos que desea para la partición: " -msgid "Select Archinstall language" -msgstr "Selecciona el idioma de Archinstall" +msgid "Archinstall language" +msgstr "Idioma de Archinstall" msgid "Wipe all selected drives and use a best-effort default partition layout" msgstr "Limpiar todos los discos seleccionados y usar una distribución de particiones por defecto" @@ -183,7 +183,7 @@ msgstr "Selecciona qué quieres hacer con los dispositivos de bloque seleccionad msgid "This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments" msgstr "Esta es una lista de perfiles pre-programados, pueden facilitar la instalación de aplicaciones como entornos de escritorio" -msgid "Select Keyboard layout" +msgid "Select keyboard layout" msgstr "Selecciona la distribución del teclado" msgid "Select one of the regions to download packages from" @@ -240,20 +240,20 @@ msgstr "Error: Enlistar perfiles en la URL \"{}\" resultó en:" msgid "Error: Could not decode \"{}\" result as JSON:" msgstr "Error: No se pudo decodificar el resultado \"{}\" como JSON:" -msgid "Select keyboard layout" -msgstr "Selecciona la distribución del teclado" +msgid "Keyboard layout" +msgstr "Distribución del teclado" -msgid "Select mirror region" -msgstr "Selecciona la región del mirror" +msgid "Mirror region" +msgstr "Región del mirror" -msgid "Select locale language" -msgstr "Selecciona el idioma local" +msgid "Locale language" +msgstr "Idioma local" -msgid "Select locale encoding" -msgstr "Selecciona la codificación local" +msgid "Locale encoding" +msgstr "Codificación local" -msgid "Select harddrives" -msgstr "Selecciona los discos duros" +msgid "Drive(s)" +msgstr "Discos duros" msgid "Select disk layout" msgstr "Selecciona la distribución de los discos" @@ -261,38 +261,38 @@ msgstr "Selecciona la distribución de los discos" msgid "Set encryption password" msgstr "Establecer la contraseña de cifrado" -msgid "Use swap" -msgstr "Usar swap" +msgid "Swap" +msgstr "Swap" -msgid "Select bootloader" -msgstr "Selecciona el cargador de arranque" +msgid "Bootloader" +msgstr "Cargador de arranque" -msgid "Set root password" -msgstr "Establecer la contraseña de root" +msgid "root password" +msgstr "Contraseña de root" -msgid "Specify superuser account" -msgstr "Especificar la cuenta de superusuario" +msgid "Superuser account" +msgstr "Cuenta de superusuario" -msgid "Specify user account" -msgstr "Especificar la cuenta de usuario" +msgid "User account" +msgstr "Cuenta de usuario" -msgid "Specify profile" -msgstr "Especificar el perfil" +msgid "Profile" +msgstr "Perfil" -msgid "Select audio" -msgstr "Selecciona el audio" +msgid "Audio" +msgstr "Audio" -msgid "Select kernels" -msgstr "Selecciona los kernels" +msgid "Kernels" +msgstr "Kernels" -msgid "Additional packages to install" -msgstr "Paquetes adicionales a instalar" +msgid "Additional packages" +msgstr "Paquetes adicionales" -msgid "Configure network" +msgid "Network configuration" msgstr "Configurar la red" -msgid "Set automatic time sync (NTP)" -msgstr "Establecer la sincronización automática de hora (NTP)" +msgid "Automatic time sync (NTP)" +msgstr "Sincronización automática de hora (NTP)" msgid "Install ({} config(s) missing)" msgstr "Instalar ({} ajuste(s) faltantes)" @@ -338,13 +338,13 @@ msgstr "Establecer el sistema de archivos deseado para una partición" msgid "Abort" msgstr "Cancelar" -msgid "Specify hostname" -msgstr "Especificar el nombre del host" +msgid "Hostname" +msgstr "Nombre del host" msgid "Not configured, unavailable unless setup manually" msgstr "No configurado, no disponible a menos que se configure manualmente" -msgid "Select timezone" +msgid "Timezone" msgstr "Selecciona la zona horaria" msgid "Set/Modify the below options" @@ -524,8 +524,8 @@ msgstr "La contraseña que está utilizando parece ser débil," msgid "are you sure you want to use it?" msgstr "¿Estás seguro de que quieres usarlo?" -msgid "Additional repositories to enable" -msgstr "Repositorios adicionales para habilitar" +msgid "Optional repositories" +msgstr "Repositorios adicionales" msgid "Save configuration" msgstr "Guardar configuración" diff --git a/archinstall/locales/fr/LC_MESSAGES/base.po b/archinstall/locales/fr/LC_MESSAGES/base.po index 1d2e39db..09890694 100644 --- a/archinstall/locales/fr/LC_MESSAGES/base.po +++ b/archinstall/locales/fr/LC_MESSAGES/base.po @@ -212,8 +212,8 @@ msgstr "" msgid "Enter a desired filesystem type for the partition: " msgstr "Entrer un type de système de fichiers souhaité pour la partition : " -msgid "Select Archinstall language" -msgstr "Sélectionner la langue d'Archinstall" +msgid "Archinstall language" +msgstr "Langue d'Archinstall" msgid "Wipe all selected drives and use a best-effort default partition layout" msgstr "" @@ -238,7 +238,7 @@ msgstr "" "Ceci est une liste de profils préprogrammés, ils pourraient faciliter " "l'installation d'outils comme les environnements de bureau" -msgid "Select Keyboard layout" +msgid "Select keyboard layout" msgstr "Sélectionner la disposition du clavier" msgid "Select one of the regions to download packages from" @@ -313,20 +313,20 @@ msgstr "Erreur : la liste des profils sur l'URL \"{}\" a entraîné :" msgid "Error: Could not decode \"{}\" result as JSON:" msgstr "Erreur : Impossible de décoder le résultat \"{}\" en tant que JSON :" -msgid "Select keyboard layout" -msgstr "Sélectionner la disposition du clavier" +msgid "Keyboard layout" +msgstr "Disposition du clavier" -msgid "Select mirror region" -msgstr "Sélectionner la région miroir" +msgid "Mirror region" +msgstr "Région miroir" -msgid "Select locale language" -msgstr "Sélectionner la langue locale" +msgid "Locale language" +msgstr "Langue locale" -msgid "Select locale encoding" -msgstr "Sélectionner l'encodage des paramètres régionaux" +msgid "Locale encoding" +msgstr "Encodage des paramètres régionaux" -msgid "Select harddrives" -msgstr "Sélectionner les disques durs" +msgid "Drive(s)" +msgstr "Disques durs" msgid "Select disk layout" msgstr "Sélectionner la disposition du disque" @@ -334,38 +334,38 @@ msgstr "Sélectionner la disposition du disque" msgid "Set encryption password" msgstr "Définir le mot de passe de chiffrement" -msgid "Use swap" -msgstr "Utiliser swap (partition d'échange)" +msgid "Swap" +msgstr "Swap" -msgid "Select bootloader" -msgstr "Sélectionner le chargeur de démarrage" +msgid "Bootloader" +msgstr "Chargeur de démarrage" -msgid "Set root password" -msgstr "Définir le mot de passe root" +msgid "root password" +msgstr "Mot de passe root" -msgid "Specify superuser account" -msgstr "Spécifier le compte superutilisateur" +msgid "Superuser account" +msgstr "Compte superutilisateur" -msgid "Specify user account" -msgstr "Spécifier le compte utilisateur" +msgid "User account" +msgstr "Compte utilisateur" -msgid "Specify profile" -msgstr "Spécifier le profil" +msgid "Profile" +msgstr "Profil" -msgid "Select audio" -msgstr "Sélectionner l'audio" +msgid "Audio" +msgstr "Audio" -msgid "Select kernels" -msgstr "Sélectionner les noyaux" +msgid "Kernels" +msgstr "Noyaux" -msgid "Additional packages to install" -msgstr "Packages supplémentaires à installer" +msgid "Additional packages" +msgstr "Packages supplémentaires" -msgid "Configure network" +msgid "Network configuration" msgstr "Configurer le réseau" -msgid "Set automatic time sync (NTP)" -msgstr "Définir la synchronisation automatique de l'heure (NTP)" +msgid "Automatic time sync (NTP)" +msgstr "Synchronisation automatique de l'heure (NTP)" msgid "Install ({} config(s) missing)" msgstr "Installer ({} configuration(s) manquante(s))" @@ -413,14 +413,14 @@ msgstr "Définir le système de fichiers souhaité pour une partition" msgid "Abort" msgstr "Abandonner" -msgid "Specify hostname" -msgstr "Spécifier le nom d'hôte" +msgid "Hostname" +msgstr "Nom d'hôte" msgid "Not configured, unavailable unless setup manually" msgstr "Non configuré, indisponible sauf configuration manuelle" -msgid "Select timezone" -msgstr "Sélectionner le fuseau horaire" +msgid "Timezone" +msgstr "Fuseau horaire" msgid "Set/Modify the below options" msgstr "Définir/Modifier les options ci-dessous" @@ -623,8 +623,8 @@ msgstr "Le mot de passe que vous utilisez semble faible," msgid "are you sure you want to use it?" msgstr "êtes-vous sûr de vouloir l'utiliser ?" -msgid "Additional repositories to enable" -msgstr "Référentiels supplémentaires à activer" +msgid "Optional repositories" +msgstr "Référentiels supplémentaires" msgid "Save configuration" msgstr "Enregistrer la configuration" diff --git a/archinstall/locales/nl/LC_MESSAGES/base.po b/archinstall/locales/nl/LC_MESSAGES/base.po index 03774c2b..77aaa44f 100644 --- a/archinstall/locales/nl/LC_MESSAGES/base.po +++ b/archinstall/locales/nl/LC_MESSAGES/base.po @@ -169,8 +169,8 @@ msgstr "" msgid "Enter a desired filesystem type for the partition: " msgstr "Voer de naam in van het gewenste bestandssysteem: " -msgid "Select Archinstall language" -msgstr "Kies een Archinstall-taal" +msgid "Archinstall language" +msgstr "Archinstall-taal" msgid "Wipe all selected drives and use a best-effort default partition layout" msgstr "Alle geselecteerde schijven formatteren en best mogelijke partitie-indeling gebruiken" @@ -184,7 +184,7 @@ msgstr "Geef aan wat er moet worden gedaan met de gekozen blokapparaten" msgid "This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments" msgstr "Dit is een vooraf opgestelde lijst met profielen, welke het installeren van zaken als werkomgevingen vereenvoudigt" -msgid "Select Keyboard layout" +msgid "Select keyboard layout" msgstr "Kies een toetsenbordindeling" msgid "Select one of the regions to download packages from" @@ -241,20 +241,20 @@ msgstr "Foutmelding: het opsommen van de profielen op {} leidde tot" msgid "Error: Could not decode \"{}\" result as JSON:" msgstr "Foutmelding: ‘{}’ kan niet gedecodeerd worden als json:" -msgid "Select keyboard layout" -msgstr "Kies een toetsenbordindeling" +msgid "Keyboard layout" +msgstr "Toetsenbordindeling" -msgid "Select mirror region" -msgstr "Kies een spiegelserverregio" +msgid "Mirror region" +msgstr "Spiegelserverregio" -msgid "Select locale language" -msgstr "Kies een taal" +msgid "Locale language" +msgstr "Taal" -msgid "Select locale encoding" -msgstr "Kies een taalvariant" +msgid "Locale encoding" +msgstr "Taalvariant" -msgid "Select harddrives" -msgstr "Selecteer de harde schijven" +msgid "Drive(s)" +msgstr "Harde schijven" msgid "Select disk layout" msgstr "Kies een schijfindeling" @@ -262,38 +262,38 @@ msgstr "Kies een schijfindeling" msgid "Set encryption password" msgstr "Versleutelwachtwoord instellen" -msgid "Use swap" -msgstr "Wisselgeheugen gebruiken" +msgid "Swap" +msgstr "Wisselgeheugen" -msgid "Select bootloader" -msgstr "Kies een opstartlader" +msgid "Bootloader" +msgstr "Opstartlader" -msgid "Set root password" -msgstr "Rootwachtwoord instellen" +msgid "root password" +msgstr "Rootwachtwoord" -msgid "Specify superuser account" -msgstr "Geef aan welk account superuserrechten dient te hebben" +msgid "Superuser account" +msgstr "Superuserrechten" -msgid "Specify user account" -msgstr "Kies een gebruikersaccount" +msgid "User account" +msgstr "Gebruikersaccount" -msgid "Specify profile" -msgstr "Kies een profiel" +msgid "Profile" +msgstr "Profiel" -msgid "Select audio" -msgstr "Kies audio" +msgid "Audio" +msgstr "Audio" -msgid "Select kernels" -msgstr "Selecteer kernels" +msgid "Kernels" +msgstr "Kernels" -msgid "Additional packages to install" -msgstr "Aanvullende te installeren pakketten" +msgid "Additional packages" +msgstr "Aanvullende pakketten" -msgid "Configure network" +msgid "Network configuration" msgstr "Netwerk instellen" -msgid "Set automatic time sync (NTP)" -msgstr "Automatische tijdsynchronisatie (NTP) gebruiken" +msgid "Automatic time sync (NTP)" +msgstr "Automatische tijdsynchronisatie (NTP)" msgid "Install ({} config(s) missing)" msgstr "Installeren ({} confirguratie(s) ontbreekt/ontbreken)" @@ -339,14 +339,14 @@ msgstr "Gewenste bestandssysteem van partitie instellen" msgid "Abort" msgstr "Afbreken" -msgid "Specify hostname" -msgstr "Hostnaam opgeven" +msgid "Hostname" +msgstr "Hostnaam" msgid "Not configured, unavailable unless setup manually" msgstr "Niet ingesteld en dus niet beschikbaar, tenzij handmatig ingesteld" -msgid "Select timezone" -msgstr "Kies een tijdzone" +msgid "Timezone" +msgstr "Tijdzone" msgid "Set/Modify the below options" msgstr "Onderstaande opties instellen/aanpassen" @@ -528,8 +528,8 @@ msgstr "Het gekozen wachtwoord is zwak." msgid "are you sure you want to use it?" msgstr "Weet u zeker dat u het wilt gebruiken?" -msgid "Additional repositories to enable" -msgstr "Aanvullende te gebruiken pakketbronnen" +msgid "Optional repositories" +msgstr "Aanvullende pakketbronnen" msgid "Save configuration" msgstr "Configuratie vastleggen" diff --git a/archinstall/locales/pl/LC_MESSAGES/base.po b/archinstall/locales/pl/LC_MESSAGES/base.po index a21f0511..49c830e2 100644 --- a/archinstall/locales/pl/LC_MESSAGES/base.po +++ b/archinstall/locales/pl/LC_MESSAGES/base.po @@ -167,8 +167,8 @@ msgstr "" msgid "Enter a desired filesystem type for the partition: " msgstr "Wprowadź typ systemu plików dla partycji: " -msgid "Select Archinstall language" -msgstr "Wybierz język Archinstall" +msgid "Archinstall language" +msgstr "Język Archinstall" msgid "Wipe all selected drives and use a best-effort default partition layout" msgstr "Wymaż wszystkie wybrane dyski i użyj najlepszego domyślnego układu partycji" @@ -182,7 +182,7 @@ msgstr "Wybierz, co chcesz zrobić z wybranymi urządzeniami blokowymi" msgid "This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments" msgstr "To jest lista wstępnie zaprogramowanych profili, które mogą ułatwić instalację takich rzeczy jak środowiska graficzne" -msgid "Select Keyboard layout" +msgid "Select keyboard layout" msgstr "Wybierz układ klawiatury" msgid "Select one of the regions to download packages from" @@ -239,20 +239,20 @@ msgstr "Błąd: Lista profili z URL \"{}\":" msgid "Error: Could not decode \"{}\" result as JSON:" msgstr "Błąd: Nie można dekodować \"{}\" jako JSON:" -msgid "Select keyboard layout" -msgstr "Wybierz układ klawiatury" +msgid "Keyboard layout" +msgstr "Układ klawiatury" -msgid "Select mirror region" -msgstr "Wybierz region lustra" +msgid "Mirror region" +msgstr "Region lustra" -msgid "Select locale language" -msgstr "Wybierz locale języka" +msgid "Locale language" +msgstr "Locale języka" -msgid "Select locale encoding" -msgstr "Wybierz locale kodowania" +msgid "Locale encoding" +msgstr "Locale kodowania" -msgid "Select harddrives" -msgstr "Wybierz dyski twarde" +msgid "Drive(s)" +msgstr "Dyski twarde" msgid "Select disk layout" msgstr "Wybierz układ dysku" @@ -260,38 +260,38 @@ msgstr "Wybierz układ dysku" msgid "Set encryption password" msgstr "Ustaw hasło szyfrowania" -msgid "Use swap" -msgstr "Użyj swap-u" +msgid "Swap" +msgstr "Swap" -msgid "Select bootloader" -msgstr "Wybierz program rozruchowy (bootloader)" +msgid "Bootloader" +msgstr "Program rozruchowy" -msgid "Set root password" -msgstr "Ustaw hasło administratora" +msgid "root password" +msgstr "Hasło root" -msgid "Specify superuser account" -msgstr "Określ konto superużytkownika" +msgid "Superuser account" +msgstr "Konto superużytkownika" -msgid "Specify user account" -msgstr "Określ konto użytkownika" +msgid "User account" +msgstr "Konto użytkownika" -msgid "Specify profile" -msgstr "Określ profil" +msgid "Profile" +msgstr "Profil" -msgid "Select audio" -msgstr "Wybierz audio" +msgid "Audio" +msgstr "Audio" -msgid "Select kernels" -msgstr "Wybierz jądra" +msgid "Kernels" +msgstr "Jądra" -msgid "Additional packages to install" -msgstr "Dodatkowe pakiety do instalacji" +msgid "Additional packages" +msgstr "Dodatkowe pakiety" -msgid "Configure network" +msgid "Network configuration" msgstr "Konfiguracja sieci" -msgid "Set automatic time sync (NTP)" -msgstr "Ustawianie automatycznej synchronizacji czasu (NTP)" +msgid "Automatic time sync (NTP)" +msgstr "Automatycznej synchronizacji czasu (NTP)" msgid "Install ({} config(s) missing)" msgstr "Zainstaluj ({} brakuje konfiguracji)" @@ -337,14 +337,14 @@ msgstr "Ustaw system plików dla partycji" msgid "Abort" msgstr "Anuluj" -msgid "Specify hostname" -msgstr "Podaj nazwę hosta" +msgid "Hostname" +msgstr "Nazwę hosta" msgid "Not configured, unavailable unless setup manually" msgstr "Nie skonfigurowana, niedostępna, chyba że zostanie skonfigurowana ręcznie" -msgid "Select timezone" -msgstr "Wybierz strefe czasową" +msgid "Timezone" +msgstr "Strefe czasową" msgid "Set/Modify the below options" msgstr "Ustaw/zmodyfikuj poniższe opcje" @@ -524,8 +524,8 @@ msgstr "Używane przez Ciebie hasło wydaje się być słabe," msgid "are you sure you want to use it?" msgstr "czy na pewno chcesz go używać?" -msgid "Additional repositories to enable" -msgstr "Dodatkowe repozytoria do włączenia" +msgid "Optional repositories" +msgstr "Dodatkowe repozytoria" msgid "Save configuration" msgstr "Zapisz konfiguracje" diff --git a/archinstall/locales/pt/LC_MESSAGES/base.po b/archinstall/locales/pt/LC_MESSAGES/base.po index dfddabde..1e98c159 100644 --- a/archinstall/locales/pt/LC_MESSAGES/base.po +++ b/archinstall/locales/pt/LC_MESSAGES/base.po @@ -167,8 +167,8 @@ msgstr "" msgid "Enter a desired filesystem type for the partition: " msgstr "Escreve o tipo de sistema de ficheiros desejado para a partição: " -msgid "Select Archinstall language" -msgstr "Seleciona o idioma do Archinstall" +msgid "Archinstall language" +msgstr "Idioma do Archinstall" msgid "Wipe all selected drives and use a best-effort default partition layout" msgstr "Limpar todos os discos selecionados e usar um esquema de partições padrão de melhor desempenho" @@ -182,7 +182,7 @@ msgstr "Seleciona o que desejas fazer com os dispositivos de bloco selecionados" msgid "This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments" msgstr "Esta é uma lista de perfis pré-programados, podem facilitar a instalação de ambientes de trabalho" -msgid "Select Keyboard layout" +msgid "Select keyboard layout" msgstr "Seleciona o esquema de teclado" msgid "Select one of the regions to download packages from" @@ -239,20 +239,20 @@ msgstr "Erro: Listando os perfis em URL \"{}\" resulta em:" msgid "Error: Could not decode \"{}\" result as JSON:" msgstr "Erro: Não foi possível decodificar \"{}\" como JSON:" -msgid "Select keyboard layout" -msgstr "Seleciona o esquema do teclado" +msgid "Keyboard layout" +msgstr "Esquema do teclado" -msgid "Select mirror region" -msgstr "Seleciona a região do mirror" +msgid "Mirror region" +msgstr "Região do mirror" -msgid "Select locale language" -msgstr "Seleciona o idioma de localização" +msgid "Locale language" +msgstr "Idioma de localização" -msgid "Select locale encoding" -msgstr "Seleciona a codificação de localização" +msgid "Locale encoding" +msgstr "Codificação de localização" -msgid "Select harddrives" -msgstr "Seleciona os discos rígidos" +msgid "Drive(s)" +msgstr "Discos rígidos" msgid "Select disk layout" msgstr "Seleciona o esquema de disco" @@ -260,38 +260,38 @@ msgstr "Seleciona o esquema de disco" msgid "Set encryption password" msgstr "Define a palavra-passe de encriptação" -msgid "Use swap" -msgstr "Usar swap" +msgid "Swap" +msgstr "Swap" -msgid "Select bootloader" -msgstr "Seleciona o carregador de arranque (bootloader)" +msgid "Bootloader" +msgstr "Carregador de arranque" -msgid "Set root password" -msgstr "Define a palavra-passe de root" +msgid "root password" +msgstr "Palavra-passe de root" -msgid "Specify superuser account" -msgstr "Especifica a conta de superusuário" +msgid "Superuser account" +msgstr "Conta de superusuário" -msgid "Specify user account" -msgstr "Especifica a conta de usuário" +msgid "User account" +msgstr "Conta de usuário" -msgid "Specify profile" -msgstr "Especifica o perfil" +msgid "Profile" +msgstr "Perfil" -msgid "Select audio" -msgstr "Seleciona o áudio" +msgid "Audio" +msgstr "Áudio" -msgid "Select kernels" -msgstr "Seleciona os kernels" +msgid "Kernels" +msgstr "Kernels" -msgid "Additional packages to install" -msgstr "Pacotes adicionais para instalar" +msgid "Additional packages" +msgstr "Pacotes adicionais" -msgid "Configure network" +msgid "Network configuration" msgstr "Configuração de rede" -msgid "Set automatic time sync (NTP)" -msgstr "Define a sincronização automática de tempo (NTP)" +msgid "Automatic time sync (NTP)" +msgstr "Sincronização automática de tempo (NTP)" msgid "Install ({} config(s) missing)" msgstr "Instalar ({} configuração(s) em falta)" @@ -337,14 +337,14 @@ msgstr "Definir o sistema de ficheiros desejado para uma partição" msgid "Abort" msgstr "Cancelar" -msgid "Specify hostname" -msgstr "Especificar nome do computador (hostname)" +msgid "Hostname" +msgstr "Nome do computador" msgid "Not configured, unavailable unless setup manually" msgstr "Não configurado, indisponível a não ser que seja configurado manualmente" -msgid "Select timezone" -msgstr "Selecionar fuso horário" +msgid "Timezone" +msgstr "Fuso horário" msgid "Set/Modify the below options" msgstr "Definir/Modificar as opções abaixo" @@ -541,8 +541,8 @@ msgid "are you sure you want to use it?" msgstr "tens a certeza que quer usar?" #, fuzzy -msgid "Additional repositories to enable" -msgstr "Repositórios adicionais a ativar" +msgid "Optional repositories" +msgstr "Repositórios adicionais" msgid "Save configuration" msgstr "Guardar configuração" diff --git a/archinstall/locales/ru/LC_MESSAGES/base.po b/archinstall/locales/ru/LC_MESSAGES/base.po index f93d2d80..d39817a3 100644 --- a/archinstall/locales/ru/LC_MESSAGES/base.po +++ b/archinstall/locales/ru/LC_MESSAGES/base.po @@ -169,8 +169,8 @@ msgstr "" msgid "Enter a desired filesystem type for the partition: " msgstr "Введите желаемый тип файловой системы для раздела: " -msgid "Select Archinstall language" -msgstr "Выберите язык Archinstall" +msgid "Archinstall language" +msgstr "Язык Archinstall" msgid "Wipe all selected drives and use a best-effort default partition layout" msgstr "Стереть все выбранные диски и использовать оптимальную схему разделов по умолчанию" @@ -184,7 +184,7 @@ msgstr "Выберите, что вы хотите сделать с выбра msgid "This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments" msgstr "Это список предварительно запрограммированных профилей, они могут облегчить установку таких вещей, как окружения рабочего стола" -msgid "Select Keyboard layout" +msgid "Select keyboard layout" msgstr "Выберите раскладку клавиатуры" msgid "Select one of the regions to download packages from" @@ -241,20 +241,20 @@ msgstr "Ошибка: Перечисление профилей по URL \"{}\" msgid "Error: Could not decode \"{}\" result as JSON:" msgstr "Ошибка: Не удалось декодировать результат \"{}\" как JSON:" -msgid "Select keyboard layout" -msgstr "Выберите раскладку клавиатуры" +msgid "Keyboard layout" +msgstr "Раскладку клавиатуры" -msgid "Select mirror region" -msgstr "Выберите регион зеркала" +msgid "Mirror region" +msgstr "Регион зеркала" -msgid "Select locale language" -msgstr "Выберите язык локали" +msgid "Locale language" +msgstr "Язык локали" -msgid "Select locale encoding" -msgstr "Выберите кодировку локали" +msgid "Locale encoding" +msgstr "Кодировку локали" -msgid "Select harddrives" -msgstr "Выберите жесткие диски" +msgid "Drive(s)" +msgstr "Жесткие диски" msgid "Select disk layout" msgstr "Выберите разметку диска" @@ -262,38 +262,38 @@ msgstr "Выберите разметку диска" msgid "Set encryption password" msgstr "Установите пароль шифрования" -msgid "Use swap" -msgstr "Использовать подкачку" +msgid "Swap" +msgstr "Подкачку" -msgid "Select bootloader" -msgstr "Выберите загрузчик" +msgid "Bootloader" +msgstr "Загрузчик" -msgid "Set root password" -msgstr "Установите пароль root" +msgid "root password" +msgstr "Пароль root" -msgid "Specify superuser account" -msgstr "Укажите учетную запись суперпользователя" +msgid "Superuser account" +msgstr "Учетную запись суперпользователя" -msgid "Specify user account" -msgstr "Укажите учетную запись пользователя" +msgid "User account" +msgstr "Учетную запись пользователя" -msgid "Specify profile" -msgstr "Укажите профиль" +msgid "Profile" +msgstr "Профиль" -msgid "Select audio" -msgstr "Выберите аудиоустройство" +msgid "Audio" +msgstr "Аудиоустройство" -msgid "Select kernels" -msgstr "Выберите ядра" +msgid "Kernels" +msgstr "Ядра" -msgid "Additional packages to install" -msgstr "Дополнительные пакеты для установки" +msgid "Additional packages" +msgstr "Дополнительные пакеты" -msgid "Configure network" +msgid "Network configuration" msgstr "Настройте сеть" -msgid "Set automatic time sync (NTP)" -msgstr "Установить автоматическую синхронизацию времени (NTP)" +msgid "Automatic time sync (NTP)" +msgstr "Автоматическая синхронизация времени (NTP)" msgid "Install ({} config(s) missing)" msgstr "Установить ({} конфигурация(и) отсутствует)" @@ -339,14 +339,14 @@ msgstr "Установите желаемую файловую систему д msgid "Abort" msgstr "Прервать" -msgid "Specify hostname" -msgstr "Укажите имя хоста" +msgid "Hostname" +msgstr "Имя хоста" msgid "Not configured, unavailable unless setup manually" msgstr "Не настроен, недоступен, если не настроен вручную" -msgid "Select timezone" -msgstr "Выберите часовой пояс" +msgid "Timezone" +msgstr "Часовой пояс" msgid "Set/Modify the below options" msgstr "Установить/изменить следующие параметры" @@ -527,8 +527,8 @@ msgstr "Пароль, который вы используете, кажется msgid "are you sure you want to use it?" msgstr "вы уверены, что хотите его использовать?" -msgid "Additional repositories to enable" -msgstr "Включить дополнительные репозитории" +msgid "Optional repositories" +msgstr "Дополнительные репозитории" msgid "Save configuration" msgstr "Сохранить конфигурацию" diff --git a/archinstall/locales/sv/LC_MESSAGES/base.po b/archinstall/locales/sv/LC_MESSAGES/base.po index 6fdae425..d6feaad1 100644 --- a/archinstall/locales/sv/LC_MESSAGES/base.po +++ b/archinstall/locales/sv/LC_MESSAGES/base.po @@ -169,8 +169,8 @@ msgstr "" msgid "Enter a desired filesystem type for the partition: " msgstr "Mata in ett önskat filsystem för partitionen: " -msgid "Select Archinstall language" -msgstr "Välj språk för detta gränssnitt" +msgid "Archinstall language" +msgstr "Språk för detta gränssnitt" msgid "Wipe all selected drives and use a best-effort default partition layout" msgstr "Töm alla partitioner och använd en generiskt rekommenderad partitionslayout." @@ -184,7 +184,7 @@ msgstr "Välj vad du önskar göra med valda hårddiskarna" msgid "This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments" msgstr "Detta är en lista med förprogrammerade profiler, dom kan göra installation av exempelvis skrivbordsmiljöer lite enklare." -msgid "Select Keyboard layout" +msgid "Select keyboard layout" msgstr "Välj tangentbordslayout" msgid "Select one of the regions to download packages from" @@ -241,20 +241,20 @@ msgstr "Fel: Listning av profiler på \"{}\" resulterade i: " msgid "Error: Could not decode \"{}\" result as JSON:" msgstr "Fel: Kunde inte tyda \"{}\" resultatet som JSON:" -msgid "Select keyboard layout" -msgstr "Välj en tangentbordslayout" +msgid "Keyboard layout" +msgstr "Tangentbordslayout" -msgid "Select mirror region" -msgstr "Välj en region för paketsynk" +msgid "Mirror region" +msgstr "Region för paketsynk" -msgid "Select locale language" -msgstr "Välj vilket språk du vill använda" +msgid "Locale language" +msgstr "Språk du vill använda" -msgid "Select locale encoding" -msgstr "Välj vilken teckenuppsättning du vill använda" +msgid "Locale encoding" +msgstr "Teckenuppsättning du vill använda" -msgid "Select harddrives" -msgstr "Välj hårddiskar" +msgid "Drive(s)" +msgstr "Hårddiskar" msgid "Select disk layout" msgstr "Välj hårddisk-layout" @@ -262,38 +262,38 @@ msgstr "Välj hårddisk-layout" msgid "Set encryption password" msgstr "Välj ett krypterings-lösenord" -msgid "Use swap" -msgstr "Använda 'swap'?" +msgid "Swap" +msgstr "Swap" -msgid "Select bootloader" -msgstr "Välj en boot-loader" +msgid "Bootloader" +msgstr "Boot-loader" -msgid "Set root password" -msgstr "Välj ett root-lösenord" +msgid "root password" +msgstr "Root-lösenord" -msgid "Specify superuser account" -msgstr "Skapa superanvändar-konto" +msgid "Superuser account" +msgstr "Superanvändar-konto" -msgid "Specify user account" -msgstr "Skapa användarkonto" +msgid "User account" +msgstr "Användarkonto" -msgid "Specify profile" -msgstr "Välj en profil" +msgid "Profile" +msgstr "Profil" -msgid "Select audio" -msgstr "Välj ljud mjukvara" +msgid "Audio" +msgstr "Ljud mjukvara" -msgid "Select kernels" -msgstr "Välj Linux-kernel" +msgid "Kernels" +msgstr "Linux-kernels" -msgid "Additional packages to install" -msgstr "Välj extra paket att installera" +msgid "Additional packages" +msgstr "Extra paket" -msgid "Configure network" +msgid "Network configuration" msgstr "Konfigurera nätverk" -msgid "Set automatic time sync (NTP)" -msgstr "Aktivera automatisk tidssynk (NTP)" +msgid "Automatic time sync (NTP)" +msgstr "Automatisk tidssynk (NTP)" msgid "Install ({} config(s) missing)" msgstr "Installera ({} inställningar saknas)" @@ -339,14 +339,14 @@ msgstr "Sätt önskat filsystem för partitionen" msgid "Abort" msgstr "Avbryt" -msgid "Specify hostname" -msgstr "Sätt ett önskat 'hostname'" +msgid "Hostname" +msgstr "Hostname" msgid "Not configured, unavailable unless setup manually" msgstr "Inte konfigurerad, otillgängligt utan manuell konfigurering" -msgid "Select timezone" -msgstr "Välj en tidszon" +msgid "Timezone" +msgstr "Tidszon" msgid "Set/Modify the below options" msgstr "Sätt eller modifiera nedan alternativ" @@ -538,8 +538,8 @@ msgid "are you sure you want to use it?" msgstr "Vill du verkligen avbryta?" #, fuzzy -msgid "Additional repositories to enable" -msgstr "Välj extra paket att installera" +msgid "Optional repositories" +msgstr "Extra förråden" msgid "Save configuration" msgstr "" diff --git a/archinstall/locales/ur/LC_MESSAGES/base.po b/archinstall/locales/ur/LC_MESSAGES/base.po index 7fd7af49..9f32df88 100644 --- a/archinstall/locales/ur/LC_MESSAGES/base.po +++ b/archinstall/locales/ur/LC_MESSAGES/base.po @@ -168,8 +168,8 @@ msgstr "" msgid "Enter a desired filesystem type for the partition: " msgstr "اس پارٹیشن کے لیے مطلوبہ فائل سسٹم درج کریں" -msgid "Select Archinstall language" -msgstr "آرچ انسٹال کے لیے زبان کا انتخاب کریں" +msgid "Archinstall language" +msgstr "آرچ انسٹال کے لیے زبان" msgid "Wipe all selected drives and use a best-effort default partition layout" msgstr "تمام منتخب ڈرائیوز کو صاف کریں اور ایک بہترین ڈیفالٹ پارٹیشن لے آؤٹ استعمال کریں" @@ -183,8 +183,8 @@ msgstr "انتخاب کریں کہ آپ منتخب بلاک ڈیوائسز کے msgid "This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments" msgstr "یہ پہلے سے پروگرام شدہ پروفائلز کی فہرست ہے، وہ ڈیسک ٹاپ انسٹالیشن جیسی چیزوں کو آسان بناتے ہیں" -msgid "Select Keyboard layout" -msgstr "کی بورڈ لے آؤٹ کو منتخب کریں" +msgid "Select keyboard layout" +msgstr "کی بورڈ لے آؤٹ" msgid "Select one of the regions to download packages from" msgstr "پیکیجز ڈاؤن لوڈ کرنے کے لیے علاقوں میں سے ایک کو منتخب کریں" @@ -241,20 +241,20 @@ msgstr "خرابی: URL \"{}\" پر پروفائلز کی فہرست بنانے msgid "Error: Could not decode \"{}\" result as JSON:" msgstr "خرابی: \"{}\" نتیجہ کو JSON کے بطور ڈی کوڈ نہیں کیا جا سکا:" -msgid "Select keyboard layout" +msgid "Keyboard layout" msgstr "کی بورڈ لے آؤٹ کو منتخب کریں" -msgid "Select mirror region" -msgstr "متبادل علاقہ منتخب کریں" +msgid "Mirror region" +msgstr "متبادل علاقہ" -msgid "Select locale language" -msgstr "منتخب کریں کہ کون سی مقامی زبان استعمال کرنی ہے" +msgid "Locale language" +msgstr "مقامی زبان" -msgid "Select locale encoding" -msgstr "منتخب کریں کہ کون سا مقامی انکوڈنگ استعمال کرنا ہے" +msgid "Locale encoding" +msgstr "مقامی انکوڈنگ" -msgid "Select harddrives" -msgstr "ہارڈ ڈرائیوز کو منتخب کریں" +msgid "Drive(s)" +msgstr "ہارڈ ڈرائیوز" msgid "Select disk layout" msgstr "ڈسک لے آؤٹ کو منتخب کریں" @@ -262,38 +262,38 @@ msgstr "ڈسک لے آؤٹ کو منتخب کریں" msgid "Set encryption password" msgstr "انکرپشن پاس ورڈ سیٹ کریں" -msgid "Use swap" -msgstr "سواپ کا استعمال کریں" +msgid "Swap" +msgstr "سواپ" -msgid "Select bootloader" -msgstr "بوٹ لوڈرکا انتخاب کریں" +msgid "Bootloader" +msgstr "بوٹ لوڈر" -msgid "Set root password" -msgstr "روٹ پاس ورڈ سیٹ کریں" +msgid "root password" +msgstr "روٹ پاس ورڈ" -msgid "Specify superuser account" -msgstr "سپر یوزر اکاؤنٹ کی وضاحت کریں" +msgid "Superuser account" +msgstr "سپر یوزر اکاؤنٹ" -msgid "Specify user account" -msgstr "یوزر اکاؤنٹ کی وضاحت کریں" +msgid "User account" +msgstr "یوزر اکاؤنٹ" -msgid "Specify profile" -msgstr "پروفائل کی وضاحت کریں" +msgid "Profile" +msgstr "پروفائل" -msgid "Select audio" -msgstr "آڈیو کا انتخاب کریں" +msgid "Audio" +msgstr "آڈیو" -msgid "Select kernels" -msgstr "کرنلز منتخب کریں" +msgid "Kernels" +msgstr "کرنلز" -msgid "Additional packages to install" -msgstr "انسٹال کرنے کے لیے اضافی پیکجز" +msgid "Additional packages" +msgstr "اضافی پیکجز" -msgid "Configure network" +msgid "Network configuration" msgstr "نیٹ ورک ترتیب دیں" -msgid "Set automatic time sync (NTP)" -msgstr "خودکار وقت کی مطابقت سیٹ کریں (NTP)" +msgid "Automatic time sync (NTP)" +msgstr "خودکار وقت کی مطابقت (NTP)" msgid "Install ({} config(s) missing)" msgstr "انسٹال کریں ({} کنفیگریشنز غائب ہیں)" @@ -339,14 +339,14 @@ msgstr "اس پارٹیشن کے لیے مطلوبہ فائل سسٹم درج ک msgid "Abort" msgstr "ختم کریں" -msgid "Specify hostname" -msgstr "میزبان نام کی وضاحت کریں" +msgid "Hostname" +msgstr "میزبان نام" msgid "Not configured, unavailable unless setup manually" msgstr "کنفیگر نہیں، دستیاب نہیں جب تک کہ دستی طور پر سیٹ اپ نہ کیا جائے" -msgid "Select timezone" -msgstr "ٹائم زون کا انتخاب کریں" +msgid "Timezone" +msgstr "ٹائم زون" msgid "Set/Modify the below options" msgstr "ذیل کے اختیارات کو سیٹ/ترمیم کریں" @@ -528,8 +528,8 @@ msgstr "آپ جو پاس ورڈ استعمال کر رہے ہیں وہ کمزو msgid "are you sure you want to use it?" msgstr "کیا آپ واقعی اسے استعمال کرنا چاہتے ہیں؟" -msgid "Additional repositories to enable" -msgstr "اضافی ریپوزٹریزکو فعال" +msgid "Optional repositories" +msgstr "اضافی ریپوزٹریز" msgid "Save configuration" msgstr "ترتیب کو محفوظ کریں" diff --git a/examples/swiss.py b/examples/swiss.py index 9c0d469a..2f7d90c4 100644 --- a/examples/swiss.py +++ b/examples/swiss.py @@ -160,7 +160,7 @@ class SetupMenu(archinstall.GeneralMenu): def _setup_selection_menu_options(self): self.set_option('archinstall-language', archinstall.Selector( - _('Select Archinstall language'), + _('Archinstall language'), lambda x: self._select_archinstall_language(x), default='English', enabled=True)) -- cgit v1.2.3-70-g09d2 From 3d102854a7ad31659f65961966665f8c975f8d71 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 17 May 2022 10:06:37 +0200 Subject: Reworking select_encrypted_partitions() to use the new Menu system, (#1201) * Reworking select_encrypted_partitions() to use the new Menu system, and allow granularity. * Listing partitions and enabling a index selection. Also when selecting 'delete all partitions' wipe=True will get set on the blockdevice now. Otherwise the new partitions won't be able to be created without deleting them first. * flake8 fix * Removed old select_encrypted_partitions() --- archinstall/lib/menu/global_menu.py | 11 +++++-- .../lib/user_interaction/partitioning_conf.py | 37 ++++++++++++++-------- 2 files changed, 33 insertions(+), 15 deletions(-) (limited to 'archinstall/lib/menu/global_menu.py') diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index f0f327ee..963766a2 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -193,8 +193,15 @@ class GlobalMenu(GeneralMenu): # If no partitions was marked as encrypted, but a password was supplied and we have some disks to format.. # Then we need to identify which partitions to encrypt. This will default to / (root). if len(list(encrypted_partitions(storage['arguments'].get('disk_layouts', [])))) == 0: - storage['arguments']['disk_layouts'] = select_encrypted_partitions( - storage['arguments']['disk_layouts'], storage['arguments']['!encryption-password']) + for blockdevice in storage['arguments']['disk_layouts']: + for partition_index in select_encrypted_partitions( + title="Select which partitions to encrypt:", + partitions=storage['arguments']['disk_layouts'][blockdevice]['partitions'] + ): + + partition = storage['arguments']['disk_layouts'][blockdevice]['partitions'][partition_index] + partition['encrypted'] = True + partition['!password'] = storage['arguments']['!encryption-password'] def _install_text(self): missing = len(self._missing_configs()) diff --git a/archinstall/lib/user_interaction/partitioning_conf.py b/archinstall/lib/user_interaction/partitioning_conf.py index c3dc4146..741decc1 100644 --- a/archinstall/lib/user_interaction/partitioning_conf.py +++ b/archinstall/lib/user_interaction/partitioning_conf.py @@ -8,7 +8,6 @@ from ..menu.menu import MenuSelectionType from ..output import log from ..disk.validators import fs_types -from ..disk.helpers import has_mountpoint if TYPE_CHECKING: from ..disk import BlockDevice @@ -271,6 +270,7 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, 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"]) @@ -360,19 +360,30 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, return block_device_struct +def select_encrypted_partitions( + title :str, + partitions :List[Partition], + multiple :bool = True, + filter_ :Callable = None +) -> Optional[int, List[int]]: + partition_indexes = _get_partitions(partitions, filter_) -def select_encrypted_partitions(block_devices: dict, password: str) -> dict: - for device in block_devices: - for partition in block_devices[device]['partitions']: - if partition.get('mountpoint', None) != '/boot': - partition['encrypted'] = True - partition['!password'] = password + if len(partition_indexes) == 0: + return None - if not has_mountpoint(partition,'/'): - # Tell the upcoming steps to generate a key-file for non root mounts. - partition['generate-encryption-key-file'] = True + title = _('Select which partitions to mark for formatting:') - return block_devices + # show current partition layout: + if len(partitions): + title += _current_partition_layout(partitions) + '\n' - # TODO: Next version perhaps we can support mixed multiple encrypted partitions - # Users might want to single out a partition for non-encryption to share between dualboot etc. + choice = Menu(title, partition_indexes, multi=multiple).run() + + if choice.type_ == MenuSelectionType.Esc: + return None + + if isinstance(choice.value, list): + for partition_index in choice.value: + yield int(partition_index) + else: + yield (partition_index) \ No newline at end of file -- cgit v1.2.3-70-g09d2 From ea2b7489e42c4e3ff4a5f4b00c0030191cf762ee Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 17 May 2022 07:23:34 -0400 Subject: Fix since consistency issues with 1195 (#1206) --- archinstall/lib/menu/global_menu.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'archinstall/lib/menu/global_menu.py') diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index 963766a2..13d385ef 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -72,7 +72,7 @@ class GlobalMenu(GeneralMenu): lambda preset: self._select_harddrives(preset)) self._menu_options['disk_layouts'] = \ Selector( - _('Select disk layout'), + _('Disk layout'), lambda preset: select_disk_layout( preset, storage['arguments'].get('harddrives', []), @@ -81,7 +81,7 @@ class GlobalMenu(GeneralMenu): dependencies=['harddrives']) self._menu_options['!encryption-password'] = \ Selector( - _('Set encryption password'), + _('Encryption password'), lambda x: self._select_encrypted_password(), display_func=lambda x: secret(x) if x else 'None', dependencies=['harddrives']) @@ -103,7 +103,7 @@ class GlobalMenu(GeneralMenu): # root password won't have preset value self._menu_options['!root-password'] = \ Selector( - _('root password'), + _('Root password'), lambda preset:self._set_root_password(), display_func=lambda x: secret(x) if x else 'None') self._menu_options['!superusers'] = \ -- cgit v1.2.3-70-g09d2 From 493cccc18fa8c77c362b6abee2c3dc89d331c792 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 18 May 2022 11:28:59 +0200 Subject: Added a HSM menu entry (#1196) * Added a HSM menu entry, but also a safety check to make sure a FIDO device is connected * flake8 complaints * Adding FIDO lookup using cryptenroll listing * Added systemd-cryptenroll --fido2-device=list * Removed old _select_hsm call * Fixed flake8 complaints * Added support for locking and unlocking with a HSM * Removed hardcoded paths in favor of PR merge * Removed hardcoded paths in favor of PR merge * Fixed mypy complaint * Flake8 issue * Added sd-encrypt for HSM and revert back to encrypt when HSM is not used (stability reason) * Added /etc/vconsole.conf and tweaked fido2_enroll() to use the proper paths * Spelling error * Using UUID instead of PARTUUID when using HSM. I can't figure out how to get sd-encrypt to use PARTUUID instead. Added a Partition().part_uuid function. Actually renamed .uuid to .part_uuid and created a .uuid instead. * Adding missing package libfido2 and removed tpm2-device=auto as it overrides everything and forces password prompt to be used over FIDO2, no matter the order of the options. * Added some notes to clarify some choices. * Had to move libfido2 package install to later in the chain, as there's not even a base during mounting :P --- .flake8 | 2 +- archinstall/__init__.py | 5 +++ archinstall/lib/configuration.py | 27 ++++++++---- archinstall/lib/disk/blockdevice.py | 2 +- archinstall/lib/disk/filesystem.py | 8 ++-- archinstall/lib/disk/partition.py | 44 ++++++++++++++++++- archinstall/lib/general.py | 2 + archinstall/lib/hsm/__init__.py | 4 ++ archinstall/lib/hsm/fido.py | 47 ++++++++++++++++++++ archinstall/lib/installer.py | 67 +++++++++++++++++++++++------ archinstall/lib/menu/global_menu.py | 6 +++ archinstall/lib/menu/selection_menu.py | 24 +++++++++++ archinstall/lib/udev/__init__.py | 1 + archinstall/lib/udev/udevadm.py | 17 ++++++++ archinstall/locales/en/LC_MESSAGES/base.mo | Bin 148 -> 261 bytes archinstall/locales/en/LC_MESSAGES/base.po | 7 +++ examples/guided.py | 6 +++ 17 files changed, 242 insertions(+), 27 deletions(-) create mode 100644 archinstall/lib/hsm/__init__.py create mode 100644 archinstall/lib/hsm/fido.py create mode 100644 archinstall/lib/udev/__init__.py create mode 100644 archinstall/lib/udev/udevadm.py (limited to 'archinstall/lib/menu/global_menu.py') diff --git a/.flake8 b/.flake8 index 39310a6c..d69ec92e 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,7 @@ [flake8] count = True # Several of the following could be autofixed or improved by running the code through psf/black -ignore = E123,E126,E128,E203,E231,E261,E302,E402,E722,F541,W191,W292,W293 +ignore = E123,E126,E128,E203,E231,E261,E302,E402,E722,F541,W191,W292,W293,W503 max-complexity = 40 max-line-length = 236 show-source = True diff --git a/archinstall/__init__.py b/archinstall/__init__.py index aa644d48..da3deb35 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -45,6 +45,11 @@ from .lib.menu.selection_menu import ( from .lib.translation import Translation, DeferredTranslation from .lib.plugins import plugins, load_plugin # This initiates the plugin loading ceremony from .lib.configuration import * +from .lib.udev import udevadm_info +from .lib.hsm import ( + get_fido2_devices, + fido2_enroll +) parser = ArgumentParser() __version__ = "2.4.2" diff --git a/archinstall/lib/configuration.py b/archinstall/lib/configuration.py index c971768f..f3fe1e1c 100644 --- a/archinstall/lib/configuration.py +++ b/archinstall/lib/configuration.py @@ -1,12 +1,23 @@ import json import logging -from pathlib import Path +import pathlib from typing import Optional, Dict from .storage import storage from .general import JSON, UNSAFE_JSON from .output import log - +from .exceptions import RequirementError +from .hsm import get_fido2_devices + +def configuration_sanity_check(): + if storage['arguments'].get('HSM'): + if not get_fido2_devices(): + raise RequirementError( + f"In order to use HSM to pair with the disk encryption," + + f" one needs to be accessible through /dev/hidraw* and support" + + f" the FIDO2 protocol. You can check this by running" + + f" 'systemd-cryptenroll --fido2-device=list'." + ) class ConfigurationOutput: def __init__(self, config: Dict): @@ -21,7 +32,7 @@ class ConfigurationOutput: self._user_credentials = {} self._disk_layout = None self._user_config = {} - self._default_save_path = Path(storage.get('LOG_PATH', '.')) + self._default_save_path = pathlib.Path(storage.get('LOG_PATH', '.')) self._user_config_file = 'user_configuration.json' self._user_creds_file = "user_credentials.json" self._disk_layout_file = "user_disk_layout.json" @@ -84,7 +95,7 @@ class ConfigurationOutput: print() - def _is_valid_path(self, dest_path :Path) -> bool: + def _is_valid_path(self, dest_path :pathlib.Path) -> bool: if (not dest_path.exists()) or not (dest_path.is_dir()): log( 'Destination directory {} does not exist or is not a directory,\n Configuration files can not be saved'.format(dest_path.resolve()), @@ -93,26 +104,26 @@ class ConfigurationOutput: return False return True - def save_user_config(self, dest_path :Path = None): + def save_user_config(self, dest_path :pathlib.Path = None): if self._is_valid_path(dest_path): with open(dest_path / self._user_config_file, 'w') as config_file: config_file.write(self.user_config_to_json()) - def save_user_creds(self, dest_path :Path = None): + def save_user_creds(self, dest_path :pathlib.Path = None): if self._is_valid_path(dest_path): if user_creds := self.user_credentials_to_json(): target = dest_path / self._user_creds_file with open(target, 'w') as config_file: config_file.write(user_creds) - def save_disk_layout(self, dest_path :Path = None): + def save_disk_layout(self, dest_path :pathlib.Path = None): if self._is_valid_path(dest_path): if disk_layout := self.disk_layout_to_json(): target = dest_path / self._disk_layout_file with target.open('w') as config_file: config_file.write(disk_layout) - def save(self, dest_path :Path = None): + def save(self, dest_path :pathlib.Path = None): if not dest_path: dest_path = self._default_save_path diff --git a/archinstall/lib/disk/blockdevice.py b/archinstall/lib/disk/blockdevice.py index 4978f19c..995ca355 100644 --- a/archinstall/lib/disk/blockdevice.py +++ b/archinstall/lib/disk/blockdevice.py @@ -275,7 +275,7 @@ class BlockDevice: count = 0 while count < 5: for partition_uuid, partition in self.partitions.items(): - if partition.uuid.lower() == uuid.lower(): + if partition.part_uuid.lower() == uuid.lower(): return partition else: log(f"uuid {uuid} not found. Waiting for {count +1} time",level=logging.DEBUG) diff --git a/archinstall/lib/disk/filesystem.py b/archinstall/lib/disk/filesystem.py index db97924f..31929b63 100644 --- a/archinstall/lib/disk/filesystem.py +++ b/archinstall/lib/disk/filesystem.py @@ -150,7 +150,7 @@ class Filesystem: if partition.get('boot', False): log(f"Marking partition {partition['device_instance']} as bootable.") - self.set(self.partuuid_to_index(partition['device_instance'].uuid), 'boot on') + self.set(self.partuuid_to_index(partition['device_instance'].part_uuid), 'boot on') prev_partition = partition @@ -193,7 +193,7 @@ class Filesystem: def add_partition(self, partition_type :str, start :str, end :str, partition_format :Optional[str] = None) -> Partition: log(f'Adding partition to {self.blockdevice}, {start}->{end}', level=logging.INFO) - previous_partition_uuids = {partition.uuid for partition in self.blockdevice.partitions.values()} + previous_partition_uuids = {partition.part_uuid for partition in self.blockdevice.partitions.values()} if self.mode == MBR: if len(self.blockdevice.partitions) > 3: @@ -210,7 +210,7 @@ class Filesystem: count = 0 while count < 10: new_uuid = None - new_uuid_set = (previous_partition_uuids ^ {partition.uuid for partition in self.blockdevice.partitions.values()}) + new_uuid_set = (previous_partition_uuids ^ {partition.part_uuid for partition in self.blockdevice.partitions.values()}) if len(new_uuid_set) > 0: new_uuid = new_uuid_set.pop() @@ -236,7 +236,7 @@ class Filesystem: # TODO: This should never be able to happen log(f"Could not find the new PARTUUID after adding the partition.", level=logging.ERROR, fg="red") log(f"Previous partitions: {previous_partition_uuids}", level=logging.ERROR, fg="red") - log(f"New partitions: {(previous_partition_uuids ^ {partition.uuid for partition in self.blockdevice.partitions.values()})}", level=logging.ERROR, fg="red") + log(f"New partitions: {(previous_partition_uuids ^ {partition.part_uuid for partition in self.blockdevice.partitions.values()})}", level=logging.ERROR, fg="red") raise DiskError(f"Could not add partition using: {parted_string}") def set_name(self, partition: int, name: str) -> bool: diff --git a/archinstall/lib/disk/partition.py b/archinstall/lib/disk/partition.py index e7568258..c52ca434 100644 --- a/archinstall/lib/disk/partition.py +++ b/archinstall/lib/disk/partition.py @@ -184,7 +184,7 @@ class Partition: return device['pttype'] @property - def uuid(self) -> Optional[str]: + def part_uuid(self) -> Optional[str]: """ Returns the PARTUUID as returned by lsblk. This is more reliable than relying on /dev/disk/by-partuuid as @@ -197,6 +197,26 @@ class Partition: time.sleep(max(0.1, storage['DISK_TIMEOUTS'] * i)) + partuuid = self._safe_part_uuid + if partuuid: + return partuuid + + raise DiskError(f"Could not get PARTUUID for {self.path} using 'blkid -s PARTUUID -o value {self.path}'") + + @property + def uuid(self) -> Optional[str]: + """ + Returns the UUID as returned by lsblk for the **partition**. + This is more reliable than relying on /dev/disk/by-uuid as + it doesn't seam to be able to detect md raid partitions. + For bind mounts all the subvolumes share the same uuid + """ + for i in range(storage['DISK_RETRY_ATTEMPTS']): + if not self.partprobe(): + raise DiskError(f"Could not perform partprobe on {self.device_path}") + + time.sleep(max(0.1, storage['DISK_TIMEOUTS'] * i)) + partuuid = self._safe_uuid if partuuid: return partuuid @@ -216,6 +236,28 @@ class Partition: log(f"Could not reliably refresh PARTUUID of partition {self.device_path} due to partprobe error.", level=logging.DEBUG) + try: + return SysCommand(f'blkid -s UUID -o value {self.device_path}').decode('UTF-8').strip() + except SysCallError as error: + if self.block_device.info.get('TYPE') == 'iso9660': + # Parent device is a Optical Disk (.iso dd'ed onto a device for instance) + return None + + log(f"Could not get PARTUUID of partition using 'blkid -s UUID -o value {self.device_path}': {error}") + + @property + def _safe_part_uuid(self) -> Optional[str]: + """ + A near copy of self.uuid but without any delays. + This function should only be used where uuid is not crucial. + For instance when you want to get a __repr__ of the class. + """ + if not self.partprobe(): + if self.block_device.info.get('TYPE') == 'iso9660': + return None + + log(f"Could not reliably refresh PARTUUID of partition {self.device_path} due to partprobe error.", level=logging.DEBUG) + try: return SysCommand(f'blkid -s PARTUUID -o value {self.device_path}').decode('UTF-8').strip() except SysCallError as error: diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py index a4e2a365..44b78777 100644 --- a/archinstall/lib/general.py +++ b/archinstall/lib/general.py @@ -135,6 +135,8 @@ class JsonEncoder: return obj.isoformat() elif isinstance(obj, (list, set, tuple)): return [json.loads(json.dumps(item, cls=JSON)) for item in obj] + elif isinstance(obj, (pathlib.Path)): + return str(obj) else: return obj diff --git a/archinstall/lib/hsm/__init__.py b/archinstall/lib/hsm/__init__.py new file mode 100644 index 00000000..c0888b04 --- /dev/null +++ b/archinstall/lib/hsm/__init__.py @@ -0,0 +1,4 @@ +from .fido import ( + get_fido2_devices, + fido2_enroll +) \ No newline at end of file diff --git a/archinstall/lib/hsm/fido.py b/archinstall/lib/hsm/fido.py new file mode 100644 index 00000000..69f42890 --- /dev/null +++ b/archinstall/lib/hsm/fido.py @@ -0,0 +1,47 @@ +import typing +import pathlib +from ..general import SysCommand, SysCommandWorker, clear_vt100_escape_codes +from ..disk.partition import Partition + +def get_fido2_devices() -> typing.Dict[str, typing.Dict[str, str]]: + """ + Uses systemd-cryptenroll to list the FIDO2 devices + connected that supports FIDO2. + Some devices might show up in udevadm as FIDO2 compliant + when they are in fact not. + + The drawback of systemd-cryptenroll is that it uses human readable format. + That means we get this weird table like structure that is of no use. + + So we'll look for `MANUFACTURER` and `PRODUCT`, we take their index + and we split each line based on those positions. + """ + worker = clear_vt100_escape_codes(SysCommand(f"systemd-cryptenroll --fido2-device=list").decode('UTF-8')) + + MANUFACTURER_POS = 0 + PRODUCT_POS = 0 + devices = {} + for line in worker.split('\r\n'): + if '/dev' not in line: + MANUFACTURER_POS = line.find('MANUFACTURER') + PRODUCT_POS = line.find('PRODUCT') + continue + + path = line[:MANUFACTURER_POS].rstrip() + manufacturer = line[MANUFACTURER_POS:PRODUCT_POS].rstrip() + product = line[PRODUCT_POS:] + + devices[path] = { + 'manufacturer' : manufacturer, + 'product' : product + } + + return devices + +def fido2_enroll(hsm_device_path :pathlib.Path, partition :Partition, password :str) -> bool: + worker = SysCommandWorker(f"systemd-cryptenroll --fido2-device={hsm_device_path} {partition.real_device}", peak_output=True) + pw_inputted = False + while worker.is_alive(): + if pw_inputted is False and bytes(f"please enter current passphrase for disk {partition.real_device}", 'UTF-8') in worker._trace_log.lower(): + worker.write(bytes(password, 'UTF-8')) + pw_inputted = True diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index e94a00c4..292b2c8e 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -23,6 +23,7 @@ from .profiles import Profile from .disk.btrfs import manage_btrfs_subvolumes from .disk.partition import get_mount_fs_type from .exceptions import DiskError, ServiceException, RequirementError, HardwareIncompatibilityError, SysCallError +from .hsm import fido2_enroll if TYPE_CHECKING: _: Any @@ -126,7 +127,9 @@ class Installer: self.MODULES = [] self.BINARIES = [] self.FILES = [] - self.HOOKS = ["base", "udev", "autodetect", "keyboard", "keymap", "modconf", "block", "filesystems", "fsck"] + # systemd, sd-vconsole and sd-encrypt will be replaced by udev, keymap and encrypt + # if HSM is not used to encrypt the root volume. Check mkinitcpio() function for that override. + self.HOOKS = ["base", "systemd", "autodetect", "keyboard", "sd-vconsole", "modconf", "block", "filesystems", "fsck"] self.KERNEL_PARAMS = [] self._zram_enabled = False @@ -241,10 +244,10 @@ class Installer: # open the luks device and all associate stuff if not (password := partition.get('!password', None)): raise RequirementError(f"Missing partition {partition['device_instance'].path} encryption password in layout: {partition}") - # i change a bit the naming conventions for the loop device loopdev = f"{storage.get('ENC_IDENTIFIER', 'ai')}{pathlib.Path(partition['mountpoint']).name}loop" else: loopdev = f"{storage.get('ENC_IDENTIFIER', 'ai')}{pathlib.Path(partition['device_instance'].path).name}" + # note that we DON'T auto_unmount (i.e. close the encrypted device so it can be used with (luks_handle := luks2(partition['device_instance'], loopdev, password, auto_unmount=False)) as unlocked_device: if partition.get('generate-encryption-key-file',False) and not self._has_root(partition): @@ -252,6 +255,10 @@ class Installer: # this way all the requesrs will be to the dm_crypt device and not to the physical partition partition['device_instance'] = unlocked_device + if self._has_root(partition) and partition.get('generate-encryption-key-file', False) is False: + hsm_device_path = storage['arguments']['HSM'] + fido2_enroll(hsm_device_path, partition['device_instance'], password) + # we manage the btrfs partitions for partition in [entry for entry in list_part if entry.get('btrfs', {}).get('subvolumes', {})]: if partition.get('filesystem',{}).get('mount_options',[]): @@ -609,6 +616,15 @@ class Installer: mkinit.write(f"MODULES=({' '.join(self.MODULES)})\n") mkinit.write(f"BINARIES=({' '.join(self.BINARIES)})\n") mkinit.write(f"FILES=({' '.join(self.FILES)})\n") + + if not storage['arguments']['HSM']: + # For now, if we don't use HSM we revert to the old + # way of setting up encryption hooks for mkinitcpio. + # This is purely for stability reasons, we're going away from this. + # * systemd -> udev + # * sd-vconsole -> keymap + self.HOOKS = [hook.replace('systemd', 'udev').replace('sd-vconsole', 'keymap') for hook in self.HOOKS] + mkinit.write(f"HOOKS=({' '.join(self.HOOKS)})\n") return SysCommand(f'/usr/bin/arch-chroot {self.target} mkinitcpio {" ".join(flags)}').exit_code == 0 @@ -643,8 +659,15 @@ class Installer: self.HOOKS.remove('fsck') if self.detect_encryption(partition): - if 'encrypt' not in self.HOOKS: - self.HOOKS.insert(self.HOOKS.index('filesystems'), 'encrypt') + if storage['arguments']['HSM']: + # Required bby mkinitcpio to add support for fido2-device options + self.pacstrap('libfido2') + + if 'sd-encrypt' not in self.HOOKS: + self.HOOKS.insert(self.HOOKS.index('filesystems'), 'sd-encrypt') + else: + if 'encrypt' not in self.HOOKS: + self.HOOKS.insert(self.HOOKS.index('filesystems'), 'encrypt') if not has_uefi(): self.base_packages.append('grub') @@ -700,6 +723,14 @@ class Installer: # TODO: Use python functions for this SysCommand(f'/usr/bin/arch-chroot {self.target} chmod 700 /root') + if storage['arguments']['HSM']: + # TODO: + # A bit of a hack, but we need to get vconsole.conf in there + # before running `mkinitcpio` because it expects it in HSM mode. + if (vconsole := pathlib.Path(f"{self.target}/etc/vconsole.conf")).exists() is False: + with vconsole.open('w') as fh: + fh.write(f"KEYMAP={storage['arguments']['keyboard-layout']}\n") + self.mkinitcpio('-P') self.helper_flags['base'] = True @@ -814,11 +845,23 @@ class Installer: if real_device := self.detect_encryption(root_partition): # TODO: We need to detect if the encrypted device is a whole disk encryption, # or simply a partition encryption. Right now we assume it's a partition (and we always have) - log(f"Identifying root partition by PART-UUID on {real_device}: '{real_device.uuid}'.", level=logging.DEBUG) - entry.write(f'options cryptdevice=PARTUUID={real_device.uuid}:luksdev root=/dev/mapper/luksdev {options_entry}') + log(f"Identifying root partition by PART-UUID on {real_device}: '{real_device.uuid}/{real_device.part_uuid}'.", level=logging.DEBUG) + + kernel_options = f"options" + + if storage['arguments']['HSM']: + # Note: lsblk UUID must be used, not PARTUUID for sd-encrypt to work + kernel_options += f" rd.luks.name={real_device.uuid}=luksdev" + # Note: tpm2-device and fido2-device don't play along very well: + # https://github.com/archlinux/archinstall/pull/1196#issuecomment-1129715645 + kernel_options += f" rd.luks.options=fido2-device=auto,password-echo=no" + else: + kernel_options += f" cryptdevice=PARTUUID={real_device.part_uuid}:luksdev" + + entry.write(f'{kernel_options} root=/dev/mapper/luksdev {options_entry}') else: - log(f"Identifying root partition by PART-UUID on {root_partition}, looking for '{root_partition.uuid}'.", level=logging.DEBUG) - entry.write(f'options root=PARTUUID={root_partition.uuid} {options_entry}') + log(f"Identifying root partition by PARTUUID on {root_partition}, looking for '{root_partition.part_uuid}'.", level=logging.DEBUG) + entry.write(f'options root=PARTUUID={root_partition.part_uuid} {options_entry}') self.helper_flags['bootloader'] = "systemd" @@ -903,11 +946,11 @@ class Installer: if real_device := self.detect_encryption(root_partition): # TODO: We need to detect if the encrypted device is a whole disk encryption, # or simply a partition encryption. Right now we assume it's a partition (and we always have) - log(f"Identifying root partition by PART-UUID on {real_device}: '{real_device.uuid}'.", level=logging.DEBUG) - kernel_parameters.append(f'cryptdevice=PARTUUID={real_device.uuid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}') + log(f"Identifying root partition by PART-UUID on {real_device}: '{real_device.part_uuid}'.", level=logging.DEBUG) + kernel_parameters.append(f'cryptdevice=PARTUUID={real_device.part_uuid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}') else: - log(f"Identifying root partition by PART-UUID on {root_partition}, looking for '{root_partition.uuid}'.", level=logging.DEBUG) - kernel_parameters.append(f'root=PARTUUID={root_partition.uuid} rw intel_pstate=no_hwp rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}') + log(f"Identifying root partition by PART-UUID on {root_partition}, looking for '{root_partition.part_uuid}'.", level=logging.DEBUG) + kernel_parameters.append(f'root=PARTUUID={root_partition.part_uuid} rw intel_pstate=no_hwp rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}') SysCommand(f'efibootmgr --disk {boot_partition.path[:-1]} --part {boot_partition.path[-1]} --create --label "{label}" --loader {loader} --unicode \'{" ".join(kernel_parameters)}\' --verbose') diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index 13d385ef..d807433c 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -85,6 +85,12 @@ class GlobalMenu(GeneralMenu): lambda x: self._select_encrypted_password(), display_func=lambda x: secret(x) if x else 'None', dependencies=['harddrives']) + self._menu_options['HSM'] = Selector( + description=_('Use HSM to unlock encrypted drive'), + func=lambda preset: self._select_hsm(preset), + dependencies=['!encryption-password'], + default=None + ) self._menu_options['swap'] = \ Selector( _('Swap'), diff --git a/archinstall/lib/menu/selection_menu.py b/archinstall/lib/menu/selection_menu.py index 35057e9c..26be4cc7 100644 --- a/archinstall/lib/menu/selection_menu.py +++ b/archinstall/lib/menu/selection_menu.py @@ -2,12 +2,14 @@ from __future__ import annotations import logging import sys +import pathlib from typing import Callable, Any, List, Iterator, Tuple, Optional, Dict, TYPE_CHECKING from .menu import Menu, MenuSelectionType from ..locale_helpers import set_keyboard_language from ..output import log from ..translation import Translation +from ..hsm.fido import get_fido2_devices if TYPE_CHECKING: _: Any @@ -466,3 +468,25 @@ class GeneralMenu: return language return preset_value + + def _select_hsm(self, preset :Optional[pathlib.Path] = None) -> Optional[pathlib.Path]: + title = _('Select which partitions to mark for formatting:') + title += '\n' + + fido_devices = get_fido2_devices() + + indexes = [] + for index, path in enumerate(fido_devices.keys()): + title += f"{index}: {path} ({fido_devices[path]['manufacturer']} - {fido_devices[path]['product']})" + indexes.append(f"{index}|{fido_devices[path]['product']}") + + title += '\n' + + choice = Menu(title, indexes, multi=False).run() + + match choice.type_: + case MenuSelectionType.Esc: return preset + case MenuSelectionType.Selection: + return pathlib.Path(list(fido_devices.keys())[int(choice.value.split('|',1)[0])]) + + return None \ No newline at end of file diff --git a/archinstall/lib/udev/__init__.py b/archinstall/lib/udev/__init__.py new file mode 100644 index 00000000..86c8cc29 --- /dev/null +++ b/archinstall/lib/udev/__init__.py @@ -0,0 +1 @@ +from .udevadm import udevadm_info \ No newline at end of file diff --git a/archinstall/lib/udev/udevadm.py b/archinstall/lib/udev/udevadm.py new file mode 100644 index 00000000..84ec9cfd --- /dev/null +++ b/archinstall/lib/udev/udevadm.py @@ -0,0 +1,17 @@ +import typing +import pathlib +from ..general import SysCommand + +def udevadm_info(path :pathlib.Path) -> typing.Dict[str, str]: + if path.resolve().exists() is False: + return {} + + result = SysCommand(f"udevadm info {path.resolve()}") + data = {} + for line in result: + if b': ' in line and b'=' in line: + _, obj = line.split(b': ', 1) + key, value = obj.split(b'=', 1) + data[key.decode('UTF-8').lower()] = value.decode('UTF-8').strip() + + return data \ No newline at end of file diff --git a/archinstall/locales/en/LC_MESSAGES/base.mo b/archinstall/locales/en/LC_MESSAGES/base.mo index c89651e7..e6ac80c2 100644 Binary files a/archinstall/locales/en/LC_MESSAGES/base.mo and b/archinstall/locales/en/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/en/LC_MESSAGES/base.po b/archinstall/locales/en/LC_MESSAGES/base.po index d883553c..531e20a9 100644 --- a/archinstall/locales/en/LC_MESSAGES/base.po +++ b/archinstall/locales/en/LC_MESSAGES/base.po @@ -1,8 +1,15 @@ msgid "" msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.0.1\n" msgid "[!] A log file has been created here: {} {}" msgstr "" diff --git a/examples/guided.py b/examples/guided.py index f104b7e3..3b762a8b 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -57,6 +57,10 @@ def ask_user_questions(): # Get disk encryption password (or skip if blank) global_menu.enable('!encryption-password') + if archinstall.arguments.get('advanced', False) or archinstall.arguments.get('HSM', None): + # Enables the use of HSM + global_menu.enable('HSM') + # Ask which boot-loader to use (will only ask if we're in UEFI mode, otherwise will default to GRUB) global_menu.enable('bootloader') @@ -130,6 +134,7 @@ def perform_installation(mountpoint): Only requirement is that the block devices are formatted and setup prior to entering this function. """ + with archinstall.Installer(mountpoint, kernels=archinstall.arguments.get('kernels', ['linux'])) as installation: # Mount all the drives to the desired mountpoint # This *can* be done outside of the installation, but the installer can deal with it. @@ -301,5 +306,6 @@ if archinstall.arguments.get('dry_run'): if not archinstall.arguments.get('silent'): input(str(_('Press Enter to continue.'))) +archinstall.configuration_sanity_check() perform_filesystem_operations() perform_installation(archinstall.storage.get('MOUNT_POINT', '/mnt')) -- cgit v1.2.3-70-g09d2 From 65a5a335aa21ea44fd99fb200e238df54b3c2e47 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Wed, 18 May 2022 21:59:49 +1000 Subject: Enhance view (#1210) * Add preview for menu entries * Fix mypy * Update * Update * Fix mypy Co-authored-by: Daniel Girtler --- .github/workflows/mypy.yaml | 2 +- archinstall/lib/disk/blockdevice.py | 42 ++++++++++---- archinstall/lib/hsm/fido.py | 2 +- archinstall/lib/menu/global_menu.py | 66 +++++++++++++++++----- archinstall/lib/menu/list_manager.py | 4 +- archinstall/lib/menu/selection_menu.py | 8 ++- archinstall/lib/user_interaction/general_conf.py | 1 - archinstall/lib/user_interaction/network_conf.py | 12 ++-- .../lib/user_interaction/partitioning_conf.py | 19 ++++--- archinstall/lib/user_interaction/utils.py | 1 + 10 files changed, 110 insertions(+), 47 deletions(-) (limited to 'archinstall/lib/menu/global_menu.py') diff --git a/.github/workflows/mypy.yaml b/.github/workflows/mypy.yaml index 6fd0876f..18c33e67 100644 --- a/.github/workflows/mypy.yaml +++ b/.github/workflows/mypy.yaml @@ -15,4 +15,4 @@ jobs: # one day this will be enabled # run: mypy --strict --module archinstall || exit 0 - name: run mypy - run: mypy --follow-imports=skip archinstall/lib/menu/selection_menu.py archinstall/lib/models/network_configuration.py archinstall/lib/menu/list_manager.py archinstall/lib/user_interaction/network_conf.py + run: mypy --follow-imports=silent archinstall/lib/menu/selection_menu.py archinstall/lib/menu/global_menu.py archinstall/lib/models/network_configuration.py archinstall/lib/menu/list_manager.py archinstall/lib/user_interaction/network_conf.py diff --git a/archinstall/lib/disk/blockdevice.py b/archinstall/lib/disk/blockdevice.py index 995ca355..15f03789 100644 --- a/archinstall/lib/disk/blockdevice.py +++ b/archinstall/lib/disk/blockdevice.py @@ -34,11 +34,29 @@ class BlockDevice: def __repr__(self, *args :str, **kwargs :str) -> str: return self._str_repr - + @cached_property def _str_repr(self) -> str: return f"BlockDevice({self.device_or_backfile}, size={self._safe_size}GB, free_space={self._safe_free_space}, bus_type={self.bus_type})" + @cached_property + def display_info(self) -> str: + columns = { + str(_('Device')): self.device_or_backfile, + str(_('Size')): f'{self._safe_size}GB', + str(_('Free space')): f'{self._safe_free_space}', + str(_('Bus-type')): f'{self.bus_type}' + } + + padding = max([len(k) for k in columns.keys()]) + + pretty = '' + for k, v in columns.items(): + k = k.ljust(padding, ' ') + pretty += f'{k} = {v}\n' + + return pretty.rstrip() + def __iter__(self) -> Iterator[Partition]: for partition in self.partitions: yield self.partitions[partition] @@ -79,7 +97,7 @@ class BlockDevice: for device in output['blockdevices']: return device['pttype'] - @property + @cached_property def device_or_backfile(self) -> str: """ Returns the actual device-endpoint of the BlockDevice. @@ -162,7 +180,7 @@ class BlockDevice: from .filesystem import GPT return GPT - @property + @cached_property def uuid(self) -> str: log('BlockDevice().uuid is untested!', level=logging.WARNING, fg='yellow') """ @@ -172,7 +190,7 @@ class BlockDevice: """ return SysCommand(f'blkid -s PTUUID -o value {self.path}').decode('UTF-8') - @property + @cached_property def _safe_size(self) -> float: from .helpers import convert_size_to_gb @@ -184,7 +202,7 @@ class BlockDevice: for device in output['blockdevices']: return convert_size_to_gb(device['size']) - @property + @cached_property def size(self) -> float: from .helpers import convert_size_to_gb @@ -193,28 +211,28 @@ class BlockDevice: for device in output['blockdevices']: return convert_size_to_gb(device['size']) - @property + @cached_property def bus_type(self) -> str: output = json.loads(SysCommand(f"lsblk --json -o+ROTA,TRAN {self.path}").decode('UTF-8')) for device in output['blockdevices']: return device['tran'] - @property + @cached_property def spinning(self) -> bool: output = json.loads(SysCommand(f"lsblk --json -o+ROTA,TRAN {self.path}").decode('UTF-8')) for device in output['blockdevices']: return device['rota'] is True - @property + @cached_property def _safe_free_space(self) -> Tuple[str, ...]: try: return '+'.join(part[2] for part in self.free_space) except SysCallError: return '?' - @property + @cached_property def free_space(self) -> Tuple[str, ...]: # NOTE: parted -s will default to `cancel` on prompt, skipping any partition # that is "outside" the disk. in /dev/sr0 this is usually the case with Archiso, @@ -228,7 +246,7 @@ class BlockDevice: except SysCallError as error: log(f"Could not get free space on {self.path}: {error}", level=logging.DEBUG) - @property + @cached_property def largest_free_space(self) -> List[str]: info = [] for space_info in self.free_space: @@ -240,7 +258,7 @@ class BlockDevice: info = space_info return info - @property + @cached_property def first_free_sector(self) -> str: if info := self.largest_free_space: start = info[0] @@ -248,7 +266,7 @@ class BlockDevice: start = '512MB' return start - @property + @cached_property def first_end_sector(self) -> str: if info := self.largest_free_space: end = info[1] diff --git a/archinstall/lib/hsm/fido.py b/archinstall/lib/hsm/fido.py index 8707ac52..49f36957 100644 --- a/archinstall/lib/hsm/fido.py +++ b/archinstall/lib/hsm/fido.py @@ -40,7 +40,7 @@ def get_fido2_devices() -> typing.Dict[str, typing.Dict[str, str]]: } return devices - + def fido2_enroll(hsm_device_path :pathlib.Path, partition :Partition, password :str) -> bool: worker = SysCommandWorker(f"systemd-cryptenroll --fido2-device={hsm_device_path} {partition.real_device}", peak_output=True) pw_inputted = False diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index d807433c..53e0941d 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, List, Optional, Union +from typing import Any, List, Optional, Union, Dict, TYPE_CHECKING import archinstall @@ -33,10 +33,15 @@ from ..user_interaction import select_encrypted_partitions from ..user_interaction import select_harddrives from ..user_interaction import select_profile from ..user_interaction import select_additional_repositories +from ..user_interaction.partitioning_conf import current_partition_layout + +if TYPE_CHECKING: + _: Any + class GlobalMenu(GeneralMenu): def __init__(self,data_store): - super().__init__(data_store=data_store, auto_cursor=True) + super().__init__(data_store=data_store, auto_cursor=True, preview_size=0.3) def _setup_selection_menu_options(self): # archinstall.Language will not use preset values @@ -69,7 +74,10 @@ class GlobalMenu(GeneralMenu): self._menu_options['harddrives'] = \ Selector( _('Drive(s)'), - lambda preset: self._select_harddrives(preset)) + lambda preset: self._select_harddrives(preset), + display_func=lambda x: f'{len(x)} ' + str(_('Drive(s)')) if x is not None and len(x) > 0 else '', + preview_func=self._prev_harddrives, + ) self._menu_options['disk_layouts'] = \ Selector( _('Disk layout'), @@ -78,6 +86,8 @@ class GlobalMenu(GeneralMenu): storage['arguments'].get('harddrives', []), storage['arguments'].get('advanced', False) ), + preview_func=self._prev_disk_layouts, + display_func=lambda x: self._display_disk_layout(x), dependencies=['harddrives']) self._menu_options['!encryption-password'] = \ Selector( @@ -131,7 +141,8 @@ class GlobalMenu(GeneralMenu): Selector( _('Profile'), lambda preset: self._select_profile(preset), - display_func=lambda x: x if x else 'None') + display_func=lambda x: x if x else 'None' + ) self._menu_options['audio'] = \ Selector( _('Audio'), @@ -189,7 +200,7 @@ class GlobalMenu(GeneralMenu): def _update_install_text(self, name :str = None, result :Any = None): text = self._install_text() - self._menu_options.get('install').update_description(text) + self._menu_options['install'].update_description(text) def post_callback(self,name :str = None ,result :Any = None): self._update_install_text(name, result) @@ -225,6 +236,35 @@ class GlobalMenu(GeneralMenu): else: return str(cur_value) + def _prev_harddrives(self) -> Optional[str]: + selector = self._menu_options['harddrives'] + if selector.has_selection(): + drives = selector.current_selection + return '\n\n'.join([d.display_info for d in drives]) + return None + + def _prev_disk_layouts(self) -> Optional[str]: + selector = self._menu_options['disk_layouts'] + if selector.has_selection(): + layouts: Dict[str, Dict[str, Any]] = selector.current_selection + + output = '' + for device, layout in layouts.items(): + output += f'{_("Device")}: {device}\n\n' + output += current_partition_layout(layout['partitions'], with_title=False) + output += '\n\n' + + return output.rstrip() + + return None + + def _display_disk_layout(self, current_value: Optional[Dict[str, Any]]) -> str: + if current_value: + total_partitions = [entry['partitions'] for entry in current_value.values()] + total_nr = sum([len(p) for p in total_partitions]) + return f'{total_nr} {_("Partitions")}' + return '' + def _prev_install_missing_config(self) -> Optional[str]: if missing := self._missing_configs(): text = str(_('Missing configurations:\n')) @@ -247,17 +287,17 @@ class GlobalMenu(GeneralMenu): if not check('harddrives'): missing += ['Hard drives'] if check('harddrives'): - if not self._menu_options.get('harddrives').is_empty() and not check('disk_layouts'): + if not self._menu_options['harddrives'].is_empty() and not check('disk_layouts'): missing += ['Disk layout'] return missing - def _set_root_password(self): + def _set_root_password(self) -> Optional[str]: prompt = str(_('Enter root password (leave blank to disable root): ')) password = get_password(prompt=prompt) return password - def _select_encrypted_password(self): + def _select_encrypted_password(self) -> Optional[str]: if passwd := get_password(prompt=str(_('Enter disk encryption password (leave blank for no encryption): '))): return passwd else: @@ -271,7 +311,7 @@ class GlobalMenu(GeneralMenu): return ntp - def _select_harddrives(self, old_harddrives : list) -> list: + def _select_harddrives(self, old_harddrives : list) -> List: harddrives = select_harddrives(old_harddrives) if len(harddrives) == 0: @@ -288,7 +328,7 @@ class GlobalMenu(GeneralMenu): # in case the harddrives got changed we have to reset the disk layout as well if old_harddrives != harddrives: - self._menu_options.get('disk_layouts').set_current_selection(None) + self._menu_options['disk_layouts'].set_current_selection(None) storage['arguments']['disk_layouts'] = {} return harddrives @@ -340,11 +380,11 @@ class GlobalMenu(GeneralMenu): return ret - def _create_superuser_account(self): + def _create_superuser_account(self) -> Optional[Dict[str, Dict[str, str]]]: superusers = ask_for_superuser_account(str(_('Manage superuser accounts: '))) return superusers if superusers else None - def _create_user_account(self): + def _create_user_account(self) -> Dict[str, Dict[str, str | None]]: users = ask_for_additional_users(str(_('Manage ordinary user accounts: '))) return users @@ -356,7 +396,7 @@ class GlobalMenu(GeneralMenu): else: return list(superusers.keys()) if superusers else '' - def _users_resynch(self): + def _users_resynch(self) -> bool: self.synch('!superusers') self.synch('!users') return False diff --git a/archinstall/lib/menu/list_manager.py b/archinstall/lib/menu/list_manager.py index 9faa1c77..7db3b3a9 100644 --- a/archinstall/lib/menu/list_manager.py +++ b/archinstall/lib/menu/list_manager.py @@ -89,7 +89,7 @@ from .text_input import TextInput from .menu import Menu, MenuSelectionType from os import system from copy import copy -from typing import Union, Any, TYPE_CHECKING, Dict +from typing import Union, Any, TYPE_CHECKING, Dict, Optional if TYPE_CHECKING: _: Any @@ -147,7 +147,7 @@ class ListManager: self.base_data = base_list self._data = copy(base_list) # as refs, changes are immediate # default values for the null case - self.target = None + self.target: Optional[Any] = None self.action = self._null_action if len(self._data) == 0 and self._null_action: diff --git a/archinstall/lib/menu/selection_menu.py b/archinstall/lib/menu/selection_menu.py index 26be4cc7..57e290f1 100644 --- a/archinstall/lib/menu/selection_menu.py +++ b/archinstall/lib/menu/selection_menu.py @@ -15,7 +15,7 @@ if TYPE_CHECKING: _: Any -def select_archinstall_language(preset_value: str) -> Optional[str]: +def select_archinstall_language(preset_value: str) -> Optional[Any]: """ copied from user_interaction/general_conf.py as a temporary measure """ @@ -487,6 +487,8 @@ class GeneralMenu: match choice.type_: case MenuSelectionType.Esc: return preset case MenuSelectionType.Selection: - return pathlib.Path(list(fido_devices.keys())[int(choice.value.split('|',1)[0])]) + selection: Any = choice.value + index = int(selection.split('|',1)[0]) + return pathlib.Path(list(fido_devices.keys())[index]) - return None \ No newline at end of file + return None diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py index c3a2a7a7..d4dc60db 100644 --- a/archinstall/lib/user_interaction/general_conf.py +++ b/archinstall/lib/user_interaction/general_conf.py @@ -142,7 +142,6 @@ def select_profile(preset) -> Optional[Profile]: 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( diff --git a/archinstall/lib/user_interaction/network_conf.py b/archinstall/lib/user_interaction/network_conf.py index e4e681ce..25e9d4c6 100644 --- a/archinstall/lib/user_interaction/network_conf.py +++ b/archinstall/lib/user_interaction/network_conf.py @@ -64,7 +64,7 @@ class ManualNetworkConfig(ListManager): elif self.action == self._action_delete: del data[iface_name] - def _select_iface(self, existing_ifaces: List[str]) -> Optional[str]: + def _select_iface(self, existing_ifaces: List[str]) -> Optional[Any]: all_ifaces = list_interfaces().values() available = set(all_ifaces) - set(existing_ifaces) choice = Menu(str(_('Select interface to add')), list(available), skip=True).run() @@ -94,14 +94,14 @@ class ManualNetworkConfig(ListManager): 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: '), + gateway_input = TextInput(_('Enter your gateway (router) IP address or leave blank for none: '), edit_iface.gateway).run().strip() try: - if len(gateway) == 0: - gateway = None - else: - ipaddress.ip_address(gateway) + if len(gateway_input) > 0: + ipaddress.ip_address(gateway_input) break except ValueError: log("You need to enter a valid gateway (router) IP address.", level=logging.WARNING, fg='red') diff --git a/archinstall/lib/user_interaction/partitioning_conf.py b/archinstall/lib/user_interaction/partitioning_conf.py index 741decc1..bfff5705 100644 --- a/archinstall/lib/user_interaction/partitioning_conf.py +++ b/archinstall/lib/user_interaction/partitioning_conf.py @@ -20,9 +20,9 @@ def partition_overlap(partitions: list, start: str, end: str) -> bool: return False -def _current_partition_layout(partitions: List[Partition], with_idx: bool = False) -> str: +def current_partition_layout(partitions: List[Dict[str, Any]], with_idx: bool = False, with_title: bool = True) -> str: - def do_padding(name, max_len): + 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 @@ -62,8 +62,11 @@ def _current_partition_layout(partitions: List[Partition], with_idx: bool = Fals current_layout += f'{row[:-1]}\n' - title = str(_('Current partition layout')) - return f'\n\n{title}:\n\n{current_layout}' + 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]: @@ -173,7 +176,7 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, # show current partition layout: if len(block_device_struct["partitions"]): - title += _current_partition_layout(block_device_struct['partitions']) + '\n' + title += current_partition_layout(block_device_struct['partitions']) + '\n' modes += [save_and_exit, cancel] @@ -246,7 +249,7 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, 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) + 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) @@ -375,7 +378,7 @@ def select_encrypted_partitions( # show current partition layout: if len(partitions): - title += _current_partition_layout(partitions) + '\n' + title += current_partition_layout(partitions) + '\n' choice = Menu(title, partition_indexes, multi=multiple).run() @@ -386,4 +389,4 @@ def select_encrypted_partitions( for partition_index in choice.value: yield int(partition_index) else: - yield (partition_index) \ No newline at end of file + yield (partition_index) diff --git a/archinstall/lib/user_interaction/utils.py b/archinstall/lib/user_interaction/utils.py index ce48607d..fa079bc2 100644 --- a/archinstall/lib/user_interaction/utils.py +++ b/archinstall/lib/user_interaction/utils.py @@ -52,6 +52,7 @@ def get_password(prompt: str = '') -> Optional[str]: continue return passwd + return None -- cgit v1.2.3-70-g09d2 From 870da403e79ab50350803b45f200e0b272334989 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Fri, 27 May 2022 05:48:29 +1000 Subject: Rework user management (#1220) * Rework users * Update user installation * Fix config serialization * Update * Update schemas and documentation * Update * Fix flake8 * Make users mypy compatible * Fix minor copy Co-authored-by: Daniel Girtler Co-authored-by: Anton Hvornum --- .github/workflows/mypy.yaml | 2 +- README.md | 3 +- archinstall/__init__.py | 11 +- archinstall/lib/configuration.py | 2 +- archinstall/lib/installer.py | 16 +- archinstall/lib/menu/global_menu.py | 53 +++--- archinstall/lib/menu/list_manager.py | 37 ++--- archinstall/lib/models/users.py | 77 +++++++++ archinstall/lib/output.py | 38 ++++- archinstall/lib/user_interaction/__init__.py | 2 +- .../lib/user_interaction/manage_users_conf.py | 177 +++++++-------------- archinstall/lib/user_interaction/network_conf.py | 2 + .../lib/user_interaction/subvolume_config.py | 2 + docs/installing/guided.rst | 25 ++- examples/creds-sample.json | 20 ++- examples/guided.py | 10 +- examples/minimal.py | 6 +- examples/swiss.py | 19 ++- profiles/52-54-00-12-34-56.py | 3 +- schema.json | 10 +- 20 files changed, 290 insertions(+), 225 deletions(-) create mode 100644 archinstall/lib/models/users.py (limited to 'archinstall/lib/menu/global_menu.py') diff --git a/.github/workflows/mypy.yaml b/.github/workflows/mypy.yaml index 18c33e67..d14d8553 100644 --- a/.github/workflows/mypy.yaml +++ b/.github/workflows/mypy.yaml @@ -15,4 +15,4 @@ jobs: # one day this will be enabled # run: mypy --strict --module archinstall || exit 0 - name: run mypy - run: mypy --follow-imports=silent archinstall/lib/menu/selection_menu.py archinstall/lib/menu/global_menu.py archinstall/lib/models/network_configuration.py archinstall/lib/menu/list_manager.py archinstall/lib/user_interaction/network_conf.py + run: mypy --follow-imports=silent archinstall/lib/menu/selection_menu.py archinstall/lib/menu/global_menu.py archinstall/lib/models/network_configuration.py archinstall/lib/menu/list_manager.py archinstall/lib/user_interaction/network_conf.py archinstall/lib/models/users.py diff --git a/README.md b/README.md index 05a3c411..79fae095 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,8 @@ with archinstall.Installer('/mnt') as installation: # In this case, we install a minimal profile that is empty installation.install_profile('minimal') - installation.user_create('devel', 'devel') + user = User('devel', 'devel', False) + installation.create_users(user) installation.user_set_pw('root', 'airoot') ``` diff --git a/archinstall/__init__.py b/archinstall/__init__.py index fcb741e6..1e72a60e 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -1,7 +1,4 @@ """Arch Linux installer - guided, templates etc.""" -import urllib.error -import urllib.parse -import urllib.request from argparse import ArgumentParser from .lib.disk import * @@ -13,6 +10,7 @@ from .lib.locale_helpers import * from .lib.luks import * from .lib.mirrors import * from .lib.models.network_configuration import NetworkConfigurationHandler +from .lib.models.users import User from .lib.networking import * from .lib.output import * from .lib.models.dataclasses import ( @@ -160,7 +158,7 @@ def get_arguments() -> Dict[str, Any]: if args.creds is not None: if not json_stream_to_structure('--creds', args.creds, config): exit(1) - + # load the parameters. first the known, then the unknowns config.update(vars(args)) config.update(parse_unspecified_argument_list(unknowns)) @@ -211,6 +209,11 @@ def load_config(): handler = NetworkConfigurationHandler() handler.parse_arguments(arguments.get('nic')) arguments['nic'] = handler.configuration + if arguments.get('!users', None) is not None or arguments.get('!superusers', None) is not None: + users = arguments.get('!users', None) + superusers = arguments.get('!superusers', None) + arguments['!users'] = User.parse_arguments(users, superusers) + def post_process_arguments(arguments): storage['arguments'] = arguments diff --git a/archinstall/lib/configuration.py b/archinstall/lib/configuration.py index f3fe1e1c..510f7103 100644 --- a/archinstall/lib/configuration.py +++ b/archinstall/lib/configuration.py @@ -37,7 +37,7 @@ class ConfigurationOutput: self._user_creds_file = "user_credentials.json" self._disk_layout_file = "user_disk_layout.json" - self._sensitive = ['!users', '!superusers', '!encryption-password'] + self._sensitive = ['!users', '!encryption-password'] self._ignore = ['abort', 'install', 'config', 'creds', 'dry_run'] self._process_config() diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 5cedc9d6..903d33af 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -23,6 +23,7 @@ from .profiles import Profile from .disk.partition import get_mount_fs_type from .exceptions import DiskError, ServiceException, RequirementError, HardwareIncompatibilityError, SysCallError from .hsm import fido2_enroll +from .models.users import User if TYPE_CHECKING: _: Any @@ -251,7 +252,7 @@ class Installer: loopdev = f"{storage.get('ENC_IDENTIFIER', 'ai')}{pathlib.Path(partition['mountpoint']).name}loop" else: loopdev = f"{storage.get('ENC_IDENTIFIER', 'ai')}{pathlib.Path(partition['device_instance'].path).name}" - + # note that we DON'T auto_unmount (i.e. close the encrypted device so it can be used with (luks_handle := luks2(partition['device_instance'], loopdev, password, auto_unmount=False)) as unlocked_device: if partition.get('generate-encryption-key-file',False) and not self._has_root(partition): @@ -426,7 +427,7 @@ class Installer: if storage['arguments'].get('silent', False) is False: if input('Would you like to re-try this download? (Y/n): ').lower().strip() in ('', 'y'): return self.pacstrap(*packages, **kwargs) - + raise RequirementError("Pacstrap failed. See /var/log/archinstall/install.log or above message for error details.") def set_mirrors(self, mirrors :Mapping[str, Iterator[str]]) -> None: @@ -1062,7 +1063,7 @@ class Installer: self.log(f'Installing archinstall profile {profile}', level=logging.INFO) return profile.install() - def enable_sudo(self, entity: str, group :bool = False) -> bool: + def enable_sudo(self, entity: str, group :bool = False): self.log(f'Enabling sudo permissions for {entity}.', level=logging.INFO) sudoers_dir = f"{self.target}/etc/sudoers.d" @@ -1092,9 +1093,14 @@ class Installer: # Guarantees sudoer conf file recommended perms os.chmod(pathlib.Path(rule_file_name), 0o440) - return True + def create_users(self, users: Union[User, List[User]]): + if not isinstance(users, list): + users = [users] + + for user in users: + self.user_create(user.username, user.password, user.groups, user.sudo) - def user_create(self, user :str, password :Optional[str] = None, groups :Optional[str] = None, sudo :bool = False) -> None: + def user_create(self, user :str, password :Optional[str] = None, groups :Optional[List[str]] = None, sudo :bool = False) -> None: if groups is None: groups = [] diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index 53e0941d..5cb27cab 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -21,7 +21,6 @@ from ..user_interaction import ask_hostname from ..user_interaction import ask_for_audio_selection from ..user_interaction import ask_additional_packages_to_install from ..user_interaction import ask_to_configure_network -from ..user_interaction import ask_for_superuser_account from ..user_interaction import ask_for_additional_users from ..user_interaction import select_language from ..user_interaction import select_mirror_regions @@ -33,7 +32,9 @@ from ..user_interaction import select_encrypted_partitions from ..user_interaction import select_harddrives from ..user_interaction import select_profile from ..user_interaction import select_additional_repositories +from ..models.users import User from ..user_interaction.partitioning_conf import current_partition_layout +from ..output import FormattedOutput if TYPE_CHECKING: _: Any @@ -122,21 +123,13 @@ class GlobalMenu(GeneralMenu): _('Root password'), lambda preset:self._set_root_password(), display_func=lambda x: secret(x) if x else 'None') - self._menu_options['!superusers'] = \ - Selector( - _('Superuser account'), - lambda preset: self._create_superuser_account(), - default={}, - exec_func=lambda n,v:self._users_resynch(), - dependencies_not=['!root-password'], - display_func=lambda x: self._display_superusers()) self._menu_options['!users'] = \ Selector( _('User account'), - lambda x: self._create_user_account(), + lambda x: self._create_user_account(x), default={}, - exec_func=lambda n,v:self._users_resynch(), - display_func=lambda x: list(x.keys()) if x else '[]') + display_func=lambda x: f'{len(x)} {_("User(s)")}' if len(x) > 0 else None, + preview_func=self._prev_users) self._menu_options['profile'] = \ Selector( _('Profile'), @@ -273,17 +266,28 @@ class GlobalMenu(GeneralMenu): return text[:-1] # remove last new line return None + def _prev_users(self) -> Optional[str]: + selector = self._menu_options['!users'] + if selector.has_selection(): + users: List[User] = selector.current_selection + return FormattedOutput.as_table(users) + return None + def _missing_configs(self) -> List[str]: def check(s): return self._menu_options.get(s).has_selection() + def has_superuser() -> bool: + users = self._menu_options['!users'].current_selection + return any([u.sudo for u in users]) + missing = [] if not check('bootloader'): missing += ['Bootloader'] if not check('hostname'): missing += ['Hostname'] - if not check('!root-password') and not check('!superusers'): - missing += [str(_('Either root-password or at least 1 superuser must be specified'))] + if not check('!root-password') and not has_superuser(): + missing += [str(_('Either root-password or at least 1 user with sudo privileges must be specified'))] if not check('harddrives'): missing += ['Hard drives'] if check('harddrives'): @@ -380,23 +384,6 @@ class GlobalMenu(GeneralMenu): return ret - def _create_superuser_account(self) -> Optional[Dict[str, Dict[str, str]]]: - superusers = ask_for_superuser_account(str(_('Manage superuser accounts: '))) - return superusers if superusers else None - - def _create_user_account(self) -> Dict[str, Dict[str, str | None]]: - users = ask_for_additional_users(str(_('Manage ordinary user accounts: '))) + def _create_user_account(self, defined_users: List[User]) -> List[User]: + users = ask_for_additional_users(defined_users=defined_users) return users - - def _display_superusers(self): - superusers = self._data_store.get('!superusers', {}) - - if self._menu_options.get('!root-password').has_selection(): - return list(superusers.keys()) if superusers else '[]' - else: - return list(superusers.keys()) if superusers else '' - - def _users_resynch(self) -> bool: - self.synch('!superusers') - self.synch('!users') - return False diff --git a/archinstall/lib/menu/list_manager.py b/archinstall/lib/menu/list_manager.py index 7db3b3a9..cb567093 100644 --- a/archinstall/lib/menu/list_manager.py +++ b/archinstall/lib/menu/list_manager.py @@ -84,13 +84,13 @@ The contents in the base class of this methods serve for a very basic usage, and ``` """ - -from .text_input import TextInput -from .menu import Menu, MenuSelectionType +import copy from os import system -from copy import copy from typing import Union, Any, TYPE_CHECKING, Dict, Optional +from .text_input import TextInput +from .menu import Menu + if TYPE_CHECKING: _: Any @@ -144,14 +144,14 @@ class ListManager: self.bottom_list = [self.confirm_action,self.cancel_action] self.bottom_item = [self.cancel_action] self.base_actions = base_actions if base_actions else [str(_('Add')),str(_('Copy')),str(_('Edit')),str(_('Delete'))] - self.base_data = base_list - self._data = copy(base_list) # as refs, changes are immediate + self._original_data = copy.deepcopy(base_list) + self._data = copy.deepcopy(base_list) # as refs, changes are immediate # default values for the null case self.target: Optional[Any] = None self.action = self._null_action if len(self._data) == 0 and self._null_action: - self.exec_action(self._data) + self._data = self.exec_action(self._data) def run(self): while True: @@ -175,12 +175,10 @@ class ListManager: clear_screen=False, clear_menu_on_exit=False, header=self.header, - skip_empty_entries=True + skip_empty_entries=True, + skip=False ).run() - if target.type_ == MenuSelectionType.Esc: - return self.run() - if not target.value or target.value in self.bottom_list: self.action = target break @@ -188,21 +186,23 @@ class ListManager: if target.value and target.value in self._default_action: self.action = target.value self.target = None - self.exec_action(self._data) + self._data = self.exec_action(self._data) continue if isinstance(self._data,dict): data_key = data_formatted[target.value] key = self._data[data_key] self.target = {data_key: key} + elif isinstance(self._data, list): + self.target = [d for d in self._data if d == data_formatted[target.value]][0] else: self.target = self._data[data_formatted[target.value]] # Possible enhacement. If run_actions returns false a message line indicating the failure self.run_actions(target.value) - if not target or target == self.cancel_action: # TODO dubious - return self.base_data # return the original list + if target.value == self.cancel_action: # TODO dubious + return self._original_data # return the original list else: return self._data @@ -221,10 +221,9 @@ class ListManager: self.action = choice.value - if not self.action or self.action == self.cancel_action: - return False - else: - return self.exec_action(self._data) + if self.action and self.action != self.cancel_action: + self._data = self.exec_action(self._data) + """ The following methods are expected to be overwritten by the user if the needs of the list are beyond the simple case """ @@ -293,3 +292,5 @@ class ListManager: self._data[origkey] = value elif self.action == str(_('Delete')): del self._data[origkey] + + return self._data diff --git a/archinstall/lib/models/users.py b/archinstall/lib/models/users.py new file mode 100644 index 00000000..6052b73a --- /dev/null +++ b/archinstall/lib/models/users.py @@ -0,0 +1,77 @@ +from dataclasses import dataclass +from typing import Dict, List, Union, Any, TYPE_CHECKING + +if TYPE_CHECKING: + _: Any + + +@dataclass +class User: + username: str + password: str + sudo: bool + + @property + def groups(self) -> List[str]: + # this property should be transferred into a class attr instead + # if it's every going to be used + return [] + + def json(self) -> Dict[str, Any]: + return { + 'username': self.username, + '!password': self.password, + 'sudo': self.sudo + } + + def display(self) -> str: + password = '*' * len(self.password) + return f'{_("Username")}: {self.username:16} {_("Password")}: {password:16} sudo: {str(self.sudo)}' + + @classmethod + def _parse(cls, config_users: List[Dict[str, Any]]) -> List['User']: + users = [] + + for entry in config_users: + username = entry.get('username', None) + password = entry.get('!password', '') + sudo = entry.get('sudo', False) + + if username is None: + continue + + user = User(username, password, sudo) + users.append(user) + + return users + + @classmethod + def _parse_backwards_compatible(cls, config_users: Dict, sudo: bool) -> List['User']: + if len(config_users.keys()) > 0: + username = list(config_users.keys())[0] + password = config_users[username]['!password'] + + if password: + return [User(username, password, sudo)] + + return [] + + @classmethod + def parse_arguments( + cls, + config_users: Union[List[Dict[str, str]], Dict[str, str]], + config_superusers: Union[List[Dict[str, str]], Dict[str, str]] + ) -> List['User']: + users = [] + + # backwards compability + if isinstance(config_users, dict): + users += cls._parse_backwards_compatible(config_users, False) + else: + users += cls._parse(config_users) + + # backwards compability + if isinstance(config_superusers, dict): + users += cls._parse_backwards_compatible(config_superusers, True) + + return users diff --git a/archinstall/lib/output.py b/archinstall/lib/output.py index 07747091..29b73bc4 100644 --- a/archinstall/lib/output.py +++ b/archinstall/lib/output.py @@ -2,11 +2,47 @@ import logging import os import sys from pathlib import Path -from typing import Dict, Union +from typing import Dict, Union, List, Any from .storage import storage +class FormattedOutput: + + @classmethod + def values(cls, o: Any) -> Dict[str, Any]: + if hasattr(o, 'json'): + return o.json() + else: + return o.__dict__ + + @classmethod + def as_table(cls, obj: List[Any]) -> str: + column_width: Dict[str, int] = {} + for o in obj: + for k, v in cls.values(o).items(): + column_width.setdefault(k, 0) + column_width[k] = max([column_width[k], len(str(v)), len(k)]) + + output = '' + for key, width in column_width.items(): + key = key.replace('!', '') + output += key.ljust(width) + ' | ' + + output = output[:-3] + '\n' + output += '-' * len(output) + '\n' + + for o in obj: + for k, v in cls.values(o).items(): + if '!' in k: + v = '*' * len(str(v)) + output += str(v).ljust(column_width[k]) + ' | ' + output = output[:-3] + output += '\n' + + return output + + class Journald: @staticmethod def log(message :str, level :int = logging.DEBUG) -> None: diff --git a/archinstall/lib/user_interaction/__init__.py b/archinstall/lib/user_interaction/__init__.py index b0174d94..8aba4b4d 100644 --- a/archinstall/lib/user_interaction/__init__.py +++ b/archinstall/lib/user_interaction/__init__.py @@ -1,5 +1,5 @@ from .save_conf import save_config -from .manage_users_conf import ask_for_superuser_account, ask_for_additional_users +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 diff --git a/archinstall/lib/user_interaction/manage_users_conf.py b/archinstall/lib/user_interaction/manage_users_conf.py index ea909f35..567a2964 100644 --- a/archinstall/lib/user_interaction/manage_users_conf.py +++ b/archinstall/lib/user_interaction/manage_users_conf.py @@ -1,14 +1,12 @@ from __future__ import annotations -import logging import re -from typing import Any, Dict, TYPE_CHECKING, List +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 ..output import log -from ..storage import storage -from .utils import get_password +from ..models.users import User if TYPE_CHECKING: _: Any @@ -19,7 +17,7 @@ class UserList(ListManager): subclass of ListManager for the managing of user accounts """ - def __init__(self, prompt: str, lusers: dict, sudo: bool = None): + def __init__(self, prompt: str, lusers: List[User]): """ param: prompt type: str @@ -27,140 +25,83 @@ class UserList(ListManager): type: Dict param: sudo. boolean to determine if we handle superusers or users. If None handles both types """ - self.sudo = sudo - self.actions = [ + self._actions = [ str(_('Add a user')), str(_('Change password')), str(_('Promote/Demote user')), str(_('Delete User')) ] - super().__init__(prompt, lusers, self.actions, self.actions[0]) - - def reformat(self, data: List) -> Dict: - def format_element(elem :str): - # secret gives away the length of the password - if data[elem].get('!password'): - pwd = '*' * 16 - else: - pwd = '' - if data[elem].get('sudoer'): - super_user = 'Superuser' - else: - super_user = ' ' - return f"{elem:16}: password {pwd:16} {super_user}" + super().__init__(prompt, lusers, self._actions, self._actions[0]) - return {format_element(e): e for e in data} + def reformat(self, data: List[User]) -> Dict[str, User]: + return {e.display(): e for e in data} def action_list(self): - if self.target: - active_user = list(self.target.keys())[0] - else: - active_user = None - sudoer = self.target[active_user].get('sudoer', False) - if self.sudo is None: - return self.actions - if self.sudo and sudoer: - return self.actions - elif self.sudo and not sudoer: - return [self.actions[2]] - elif not self.sudo and sudoer: - return [self.actions[2]] + active_user = self.target if self.target else None + + if active_user is None: + return [self._actions[0]] else: - return self.actions + return self._actions[1:] - def exec_action(self, data: Any): + def exec_action(self, data: List[User]) -> List[User]: if self.target: - active_user = list(self.target.keys())[0] + active_user = self.target else: active_user = None - if self.action == self.actions[0]: # add - new_user = self.add_user() - # no unicity check, if exists will be replaced - data.update(new_user) - elif self.action == self.actions[1]: # change password - data[active_user]['!password'] = get_password( - prompt=str(_('Password for user "{}": ').format(active_user))) - elif self.action == self.actions[2]: # promote/demote - data[active_user]['sudoer'] = not data[active_user]['sudoer'] - elif self.action == self.actions[3]: # delete - del data[active_user] + if self.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 self.action == self._actions[1]: # change password + prompt = str(_('Password for user "{}": ').format(active_user.username)) + new_password = get_password(prompt=prompt) + if new_password: + user = next(filter(lambda x: x == active_user, data), 1) + user.password = new_password + elif self.action == self._actions[2]: # promote/demote + user = next(filter(lambda x: x == active_user, data), 1) + user.sudo = False if user.sudo else True + elif self.action == self._actions[3]: # delete + data = [d for d in data if d != active_user] + + 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 - log("The username you entered is invalid. Try again", level=logging.WARNING, fg='red') return False - def add_user(self): + def _add_user(self) -> Optional[User]: print(_('\nDefine a new user\n')) - prompt = str(_("User Name : ")) + prompt = str(_('Enter username (leave blank to skip): ')) + while True: - userid = input(prompt).strip(' ') - if not userid: - return {} # end - if not self._check_for_correct_username(userid): - pass + 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 - if self.sudo: - sudoer = True - elif self.sudo is not None and not self.sudo: - sudoer = False - else: - sudoer = False - sudo_choice = Menu(str(_('Should {} be a superuser (sudoer)?')).format(userid), Menu.yes_no(), - skip=False, - preset_values=Menu.yes() if sudoer else Menu.no(), - default_option=Menu.no()).run() - sudoer = True if sudo_choice == Menu.yes() else False - - password = get_password(prompt=str(_('Password for user "{}": ').format(userid))) - - return {userid: {"!password": password, "sudoer": sudoer}} - - -def manage_users(prompt: str, sudo: bool) -> tuple[dict, dict]: - # TODO Filtering and some kind of simpler code - lusers = {} - if storage['arguments'].get('!superusers', {}): - lusers.update({ - uid: { - '!password': storage['arguments']['!superusers'][uid].get('!password'), - 'sudoer': True - } - for uid in storage['arguments'].get('!superusers', {}) - }) - if storage['arguments'].get('!users', {}): - lusers.update({ - uid: { - '!password': storage['arguments']['!users'][uid].get('!password'), - 'sudoer': False - } - for uid in storage['arguments'].get('!users', {}) - }) - # processing - lusers = UserList(prompt, lusers, sudo).run() - # return data - superusers = { - uid: { - '!password': lusers[uid].get('!password') - } - for uid in lusers if lusers[uid].get('sudoer', False) - } - users = {uid: {'!password': lusers[uid].get('!password')} for uid in lusers if not lusers[uid].get('sudoer', False)} - storage['arguments']['!superusers'] = superusers - storage['arguments']['!users'] = users - return superusers, users - - -def ask_for_superuser_account(prompt: str) -> Dict[str, Dict[str, str]]: - prompt = prompt if prompt else str(_('Define users with sudo privilege, by username: ')) - superusers, dummy = manage_users(prompt, sudo=True) - return superusers - - -def ask_for_additional_users(prompt: str = '') -> Dict[str, Dict[str, str | None]]: - prompt = prompt if prompt else _('Any additional users to install (leave blank for no users): ') - dummy, users = manage_users(prompt, sudo=False) + + 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() + ).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]: + prompt = prompt if prompt else _('Enter username (leave blank to skip): ') + 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 index 25e9d4c6..5154d8b1 100644 --- a/archinstall/lib/user_interaction/network_conf.py +++ b/archinstall/lib/user_interaction/network_conf.py @@ -64,6 +64,8 @@ class ManualNetworkConfig(ListManager): elif self.action == self._action_delete: del data[iface_name] + return data + def _select_iface(self, existing_ifaces: List[str]) -> Optional[Any]: all_ifaces = list_interfaces().values() available = set(all_ifaces) - set(existing_ifaces) diff --git a/archinstall/lib/user_interaction/subvolume_config.py b/archinstall/lib/user_interaction/subvolume_config.py index 7383b13d..af783639 100644 --- a/archinstall/lib/user_interaction/subvolume_config.py +++ b/archinstall/lib/user_interaction/subvolume_config.py @@ -57,6 +57,8 @@ class SubvolumeList(ListManager): data.update(self.target) + return data + class SubvolumeMenu(GeneralMenu): def __init__(self,parameters,action=None): diff --git a/docs/installing/guided.rst b/docs/installing/guided.rst index ac6254f2..569b2d05 100644 --- a/docs/installing/guided.rst +++ b/docs/installing/guided.rst @@ -163,21 +163,20 @@ Options for ``--creds`` "!root-password" : "SecretSanta2022" } -+----------------------+-----------------------------------------------------+--------------------------------------------------------------------------------------+-----------------------------------------------+ -| Key | Values | Description | Required | -| | | | | -+======================+=====================================================+======================================================================================+===============================================+ -| !encryption-password | any | Password to encrypt disk, not encrypted if password not provided | No | -+----------------------+-----------------------------------------------------+--------------------------------------------------------------------------------------+-----------------------------------------------+ -| !root-password | any | The root account password | No | -+----------------------+-----------------------------------------------------+--------------------------------------------------------------------------------------+-----------------------------------------------+ -| !superusers | { "": { "!password": ""}, ..} | List of superuser credentials, see configuration for reference | Yes[1] | -+----------------------+-----------------------------------------------------+--------------------------------------------------------------------------------------+-----------------------------------------------+ -| !users | { "": { "!password": ""}, ..} | List of regular user credentials, see configuration for reference | No | -+----------------------+-----------------------------------------------------+--------------------------------------------------------------------------------------+-----------------------------------------------+ ++----------------------+--------------------------------------------------------+--------------------------------------------------------------------------------------+-----------------------------------------------+ +| Key | Values | Description | Required | ++======================+========================================================+======================================================================================+===============================================+ +| !encryption-password | any | Password to encrypt disk, not encrypted if password not provided | No | ++----------------------+--------------------------------------------------------+--------------------------------------------------------------------------------------+-----------------------------------------------+ +| !root-password | any | The root account password | No | ++----------------------+--------------------------------------------------------+--------------------------------------------------------------------------------------+-----------------------------------------------+ +| !users | { "username": "" | List of regular user credentials, see configuration for reference | No | +| | "!password": "", | | | +| | "sudo": false|true} | | | ++----------------------+--------------------------------------------------------+--------------------------------------------------------------------------------------+-----------------------------------------------+ .. note:: - [1] ``!superusers`` is optional only if ``!root-password`` was set. ``!superusers`` will be enforced otherwise and the minimum amount of superusers required will be set to 1. + [1] ``!users`` is optional only if ``!root-password`` was set. ``!users`` will be enforced otherwise and the minimum amount of users with sudo privileges required will be set to 1. Options for ``--disk_layouts`` ------------------------------ diff --git a/examples/creds-sample.json b/examples/creds-sample.json index 16aeb8cc..0681e16f 100644 --- a/examples/creds-sample.json +++ b/examples/creds-sample.json @@ -1,9 +1,15 @@ { "!root-password": "", - "!users": { - "username": {"!password": ""} - }, - "!superusers": { - "admin": {"!password": ""} - } -} \ No newline at end of file + "!users": [ + { + "username": "", + "!password": "", + "sudo": false + }, + { + "username": "", + "!password": "", + "sudo": true + } + ] +} diff --git a/examples/guided.py b/examples/guided.py index 3b762a8b..19b0d638 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -72,7 +72,6 @@ def ask_user_questions(): # Ask for a root password (optional, but triggers requirement for super-user if skipped) global_menu.enable('!root-password') - global_menu.enable('!superusers') global_menu.enable('!users') # Ask for archinstall-specific profiles (such as desktop environments etc) @@ -220,13 +219,8 @@ def perform_installation(mountpoint): if archinstall.arguments.get('profile', None): installation.install_profile(archinstall.arguments.get('profile', None)) - if archinstall.arguments.get('!users',{}): - for user, user_info in archinstall.arguments.get('!users', {}).items(): - installation.user_create(user, user_info["!password"], sudo=False) - - if archinstall.arguments.get('!superusers',{}): - for superuser, user_info in archinstall.arguments.get('!superusers', {}).items(): - installation.user_create(superuser, user_info["!password"], sudo=True) + if users := archinstall.arguments.get('!users', None): + installation.create_users(users) if timezone := archinstall.arguments.get('timezone', None): installation.set_timezone(timezone) diff --git a/examples/minimal.py b/examples/minimal.py index d80c4045..8b4c847f 100644 --- a/examples/minimal.py +++ b/examples/minimal.py @@ -1,6 +1,8 @@ import archinstall # Select a harddrive and a disk password +from archinstall import User + archinstall.log("Minimal only supports:") archinstall.log(" * Being installed to a single disk") @@ -28,8 +30,8 @@ def install_on(mountpoint): installation.add_additional_packages(['nano', 'wget', 'git']) installation.install_profile('minimal') - installation.user_create('devel', 'devel') - installation.user_set_pw('root', 'airoot') + user = User('devel', 'devel', False) + installation.create_users(user) # Once this is done, we output some useful information to the user # And the installation is complete. diff --git a/examples/swiss.py b/examples/swiss.py index 2f7d90c4..d0f02dc1 100644 --- a/examples/swiss.py +++ b/examples/swiss.py @@ -219,7 +219,7 @@ class MyMenu(archinstall.GlobalMenu): if self._execution_mode in ('full','lineal'): options_list = ['keyboard-layout', 'mirror-region', 'harddrives', 'disk_layouts', '!encryption-password','swap', 'bootloader', 'hostname', '!root-password', - '!superusers', '!users', 'profile', 'audio', 'kernels', 'packages','additional-repositories','nic', + '!users', 'profile', 'audio', 'kernels', 'packages','additional-repositories','nic', 'timezone', 'ntp'] if archinstall.arguments.get('advanced',False): options_list.extend(['sys-language','sys-encoding']) @@ -229,7 +229,7 @@ class MyMenu(archinstall.GlobalMenu): mandatory_list = ['harddrives'] elif self._execution_mode == 'only_os': options_list = ['keyboard-layout', 'mirror-region','bootloader', 'hostname', - '!root-password', '!superusers', '!users', 'profile', 'audio', 'kernels', + '!root-password', '!users', 'profile', 'audio', 'kernels', 'packages', 'additional-repositories', 'nic', 'timezone', 'ntp'] mandatory_list = ['hostname'] if archinstall.arguments.get('advanced',False): @@ -262,8 +262,12 @@ class MyMenu(archinstall.GlobalMenu): def check(s): return self.option(s).has_selection() + def has_superuser() -> bool: + users = self._menu_options['!users'].current_selection + return any([u.sudo for u in users]) + _, missing = self.mandatory_overview() - if mode in ('full','only_os') and (not check('!root-password') and not check('!superusers')): + if mode in ('full','only_os') and (not check('!root-password') and not has_superuser()): missing += 1 if mode in ('full', 'only_hd') and check('harddrives'): if not self.option('harddrives').is_empty() and not check('disk_layouts'): @@ -420,13 +424,8 @@ def os_setup(installation): if archinstall.arguments.get('profile', None): installation.install_profile(archinstall.arguments.get('profile', None)) - if archinstall.arguments.get('!users',{}): - for user, user_info in archinstall.arguments.get('!users', {}).items(): - installation.user_create(user, user_info["!password"], sudo=False) - - if archinstall.arguments.get('!superusers',{}): - for superuser, user_info in archinstall.arguments.get('!superusers', {}).items(): - installation.user_create(superuser, user_info["!password"], sudo=True) + if users := archinstall.arguments.get('!users', None): + installation.create_users(users) if timezone := archinstall.arguments.get('timezone', None): installation.set_timezone(timezone) diff --git a/profiles/52-54-00-12-34-56.py b/profiles/52-54-00-12-34-56.py index 0a1626d9..3b074629 100644 --- a/profiles/52-54-00-12-34-56.py +++ b/profiles/52-54-00-12-34-56.py @@ -40,7 +40,8 @@ with archinstall.Filesystem(harddrive) as fs: installation.add_additional_packages(__packages__) installation.install_profile('awesome') - installation.user_create('devel', 'devel') + user = User('devel', 'devel', False) + installation.create_users(user) installation.user_set_pw('root', 'toor') print(f'Submitting {archinstall.__version__}: success') diff --git a/schema.json b/schema.json index e6f73c6c..9269e8e8 100644 --- a/schema.json +++ b/schema.json @@ -167,7 +167,15 @@ }, { "required": [ - "!superusers" + "!users": { + "description": "User account", + "type": "object", + "properties": { + "username": "string", + "!password": "string", + "sudo": "boolean" + } + } ] } ] -- cgit v1.2.3-70-g09d2