From 9db6a25a08cf9a28bd848a416b105495ad9a5b0d Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Fri, 4 Nov 2022 17:25:14 +0100 Subject: Fixed broken imports (#1548) --- archinstall/lib/menu/menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'archinstall/lib/menu') diff --git a/archinstall/lib/menu/menu.py b/archinstall/lib/menu/menu.py index 112bc0ae..13ce8c0e 100644 --- a/archinstall/lib/menu/menu.py +++ b/archinstall/lib/menu/menu.py @@ -3,7 +3,7 @@ from enum import Enum, auto from os import system from typing import Dict, List, Union, Any, TYPE_CHECKING, Optional, Callable -from archinstall.lib.menu.simple_menu import TerminalMenu +from .simple_menu import TerminalMenu from ..exceptions import RequirementError from ..output import log -- cgit v1.2.3-70-g09d2 From dee79265897f67689e626d5c85f5b48b8e623a4a Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Fri, 11 Nov 2022 19:11:41 +1100 Subject: Remove custom font setting (#1552) * Remove custom font setting * flake8 * Remove default preview Co-authored-by: Daniel Girtler --- archinstall/lib/menu/menu.py | 2 +- archinstall/lib/translationhandler.py | 25 ++--------------- archinstall/lib/user_interaction/general_conf.py | 35 +++++++----------------- archinstall/locales/README.md | 23 ++++------------ archinstall/locales/languages.json | 4 +-- 5 files changed, 20 insertions(+), 69 deletions(-) (limited to 'archinstall/lib/menu') diff --git a/archinstall/lib/menu/menu.py b/archinstall/lib/menu/menu.py index 13ce8c0e..773ff1de 100644 --- a/archinstall/lib/menu/menu.py +++ b/archinstall/lib/menu/menu.py @@ -53,7 +53,7 @@ class Menu(TerminalMenu): preset_values :Union[str, List[str]] = None, cursor_index : Optional[int] = None, preview_command: Optional[Callable] = None, - preview_size: float = 0.75, + preview_size: float = 0.0, preview_title: str = 'Info', header :Union[List[str],str] = None, raise_error_on_interrupt :bool = False, diff --git a/archinstall/lib/translationhandler.py b/archinstall/lib/translationhandler.py index 1a3ed85f..0d74f974 100644 --- a/archinstall/lib/translationhandler.py +++ b/archinstall/lib/translationhandler.py @@ -21,15 +21,10 @@ class Language: translation: gettext.NullTranslations translation_percent: int translated_lang: Optional[str] - external_dep: Optional[str] @property def display_name(self) -> str: - if not self.external_dep and self.translated_lang: - name = self.translated_lang - else: - name = self.name_en - + name = self.name_en return f'{name} ({self.translation_percent}%)' def is_match(self, lang_or_translated_lang: str) -> bool: @@ -48,24 +43,9 @@ class TranslationHandler: self._base_pot = 'base.pot' self._languages = 'languages.json' - # check if a custom font was provided, otherwise we'll - # use one that can display latin, greek, cyrillic characters - if self.is_custom_font_enabled(): - self._set_font(self.custom_font_path().name) - else: - self._set_font('LatGrkCyr-8x16') - self._total_messages = self._get_total_active_messages() self._translated_languages = self._get_translations() - @classmethod - def custom_font_path(cls) -> Path: - return Path('/usr/share/kbd/consolefonts/archinstall_font.psfu.gz') - - @classmethod - def is_custom_font_enabled(cls) -> bool: - return cls.custom_font_path().exists() - @property def translated_languages(self) -> List[Language]: return self._translated_languages @@ -84,7 +64,6 @@ class TranslationHandler: abbr = mapping_entry['abbr'] lang = mapping_entry['lang'] translated_lang = mapping_entry.get('translated_lang', None) - external_dep = mapping_entry.get('external_dep', False) try: # get a translation for a specific language @@ -99,7 +78,7 @@ class TranslationHandler: # prevent cases where the .pot file is out of date and the percentage is above 100 percent = min(100, percent) - language = Language(abbr, lang, translation, percent, translated_lang, external_dep) + language = Language(abbr, lang, translation, percent, translated_lang) languages.append(language) except FileNotFoundError as error: raise TranslationError(f"Could not locate language file for '{lang}': {error}") diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py index 6365014d..efd746a4 100644 --- a/archinstall/lib/user_interaction/general_conf.py +++ b/archinstall/lib/user_interaction/general_conf.py @@ -4,19 +4,16 @@ import logging import pathlib from typing import List, Any, Optional, Dict, TYPE_CHECKING -from ..menu.menu import MenuSelectionType -from ..menu.text_input import TextInput - from ..locale_helpers import list_keyboard_languages, list_timezones from ..menu import Menu -from ..output import log -from ..profiles import Profile, list_profiles +from ..menu.menu import MenuSelectionType +from ..menu.text_input import TextInput from ..mirrors import list_mirrors - -from ..translationhandler import Language, TranslationHandler +from ..output import log from ..packages.packages import validate_package_list - +from ..profiles import Profile, list_profiles from ..storage import storage +from ..translationhandler import Language if TYPE_CHECKING: _: Any @@ -125,21 +122,14 @@ def select_archinstall_language(languages: List[Language], preset_value: Languag # name of the language in its own language options = {lang.display_name: lang for lang in languages} - def dependency_preview(current_selection: str) -> Optional[str]: - current_lang = options[current_selection] - - if current_lang.external_dep and not TranslationHandler.is_custom_font_enabled(): - font_file = TranslationHandler.custom_font_path() - text = str(_('To be able to use this translation, please install a font manually that supports the language.')) + '\n' - text += str(_('The font should be stored as {}')).format(font_file) - return text - return None + title = 'NOTE: If a language can not displayed properly, a proper font must be set manually in the console.\n' + title += 'All available fonts can be found in "/usr/share/kbd/consolefonts"\n' + title += 'e.g. setfont LatGrkCyr-8x16 (to display latin/greek/cyrillic characters)\n' choice = Menu( - _('Archinstall language'), + title, list(options.keys()), default_option=preset_value.display_name, - preview_command=lambda x: dependency_preview(x), preview_size=0.5 ).run() @@ -147,12 +137,7 @@ def select_archinstall_language(languages: List[Language], preset_value: Languag case MenuSelectionType.Esc: return preset_value case MenuSelectionType.Selection: - language: Language = options[choice.value] - # we have to make sure that the proper AUR dependency is - # present to be able to use this language - if not language.external_dep or TranslationHandler.is_custom_font_enabled(): - return language - return select_archinstall_language(languages, preset_value) + return options[choice.value] def select_profile(preset) -> Optional[Profile]: diff --git a/archinstall/locales/README.md b/archinstall/locales/README.md index cd7d794a..e1266209 100644 --- a/archinstall/locales/README.md +++ b/archinstall/locales/README.md @@ -4,19 +4,13 @@ Archinstall supports multiple languages, which depend on translations coming fro ## Important Note Before starting a new language translation be aware that a font for that language may not be -available on the ISO. We are using the pre-installed font `/usr/share/kbd/consolefonts/LatGrkCyr-8x16.psfu.gz` in archinstall -which should cover a fair amount of different languages but unfortunately not all of them. +available on the ISO. -We have the option to provide a custom font in case the above is not covering a specific language, which can -be achieved by installing the font yourself on the ISO and saving it to `/usr/share/kbd/consolefonts/archinstall_font.psfu.gz`. -If this font is present it will be automatically loaded and all languages which are not supported by the default font will -be enabled (but only some might actually work). +Fonts that are using a different character set than Latin will not be displayed correctly. If those languages +want to be selected than a proper font has to be set manually in the console. -Please make sure that the provided language works with the default font on the ISO, and if not mark it in the `languages.json` -that it needs an external dependency -``` -{"abbr": "ur", "lang": "Urdu", "translated_lang": "اردو", "external_dep": true}, -``` +All available console fonts can be found in `/usr/share/kbd/consolefonts` and they +can be set with `setfont LatGrkCyr-8x16` ## Adding new languages @@ -49,10 +43,3 @@ msgstr "Wollen sie wirklich abbrechen?" After the translations have been written, run the script once more `./locales_generator.sh` and it will auto-generate the `base.mo` file with the included translations. After that you're all ready to go and enjoy Archinstall in the new language :) - -To display the language inside Archinstall in your own tongue, please edit the file `languages.json` and -add a `translated_lang` entry to the respective language, e.g. - -``` - {"abbr": "pl", "lang": "Polish", "translated_lang": "Polski"} -``` diff --git a/archinstall/locales/languages.json b/archinstall/locales/languages.json index 2a7fab2e..12228b47 100644 --- a/archinstall/locales/languages.json +++ b/archinstall/locales/languages.json @@ -155,7 +155,7 @@ {"abbr": "sw", "lang": "Swahili (macrolanguage)"}, {"abbr": "sv", "lang": "Swedish", "translated_lang": "Svenska"}, {"abbr": "ty", "lang": "Tahitian"}, - {"abbr": "ta", "lang": "Tamil", "translated_lang": "தமிழ்", "external_dep": true}, + {"abbr": "ta", "lang": "Tamil", "translated_lang": "தமிழ்"}, {"abbr": "tt", "lang": "Tatar"}, {"abbr": "te", "lang": "Telugu"}, {"abbr": "tg", "lang": "Tajik"}, @@ -170,7 +170,7 @@ {"abbr": "tw", "lang": "Twi"}, {"abbr": "ug", "lang": "Uighur"}, {"abbr": "uk", "lang": "Ukrainian"}, - {"abbr": "ur", "lang": "Urdu", "translated_lang": "اردو", "external_dep": true}, + {"abbr": "ur", "lang": "Urdu", "translated_lang": "اردو"}, {"abbr": "uz", "lang": "Uzbek"}, {"abbr": "ve", "lang": "Venda"}, {"abbr": "vi", "lang": "Vietnamese"}, -- cgit v1.2.3-70-g09d2 From c3862c5779194f5e93f9fd2518bb15706c93ad2b Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Fri, 11 Nov 2022 19:40:05 +1100 Subject: New encryption menu (#1520) * New encryption menu Co-authored-by: Daniel Girtler Co-authored-by: Anton Hvornum --- .github/workflows/mypy.yaml | 6 +- archinstall/__init__.py | 8 +- archinstall/lib/configuration.py | 14 +- archinstall/lib/disk/encryption.py | 162 +++++++ archinstall/lib/disk/filesystem.py | 23 +- archinstall/lib/disk/helpers.py | 6 - archinstall/lib/disk/partition.py | 1 - archinstall/lib/hsm/__init__.py | 5 +- archinstall/lib/hsm/fido.py | 137 +++--- archinstall/lib/installer.py | 36 +- archinstall/lib/menu/abstract_menu.py | 493 ++++++++++++++++++++ archinstall/lib/menu/global_menu.py | 94 ++-- archinstall/lib/menu/list_manager.py | 2 +- archinstall/lib/menu/menu.py | 43 +- archinstall/lib/menu/selection_menu.py | 497 --------------------- archinstall/lib/menu/table_selection_menu.py | 107 +++++ archinstall/lib/models/disk_encryption.py | 43 ++ archinstall/lib/user_interaction/__init__.py | 2 +- archinstall/lib/user_interaction/disk_conf.py | 10 +- archinstall/lib/user_interaction/general_conf.py | 26 +- archinstall/lib/user_interaction/locale_conf.py | 4 +- archinstall/lib/user_interaction/network_conf.py | 10 +- .../lib/user_interaction/partitioning_conf.py | 42 +- archinstall/lib/user_interaction/save_conf.py | 2 +- archinstall/lib/user_interaction/system_conf.py | 22 +- archinstall/locales/ar/LC_MESSAGES/base.po | 2 +- archinstall/locales/base.pot | 2 +- archinstall/locales/cs/LC_MESSAGES/base.po | 4 +- archinstall/locales/de/LC_MESSAGES/base.po | 7 +- archinstall/locales/el/LC_MESSAGES/base.po | 2 +- archinstall/locales/en/LC_MESSAGES/base.po | 2 +- archinstall/locales/es/LC_MESSAGES/base.po | 4 +- archinstall/locales/fr/LC_MESSAGES/base.po | 7 +- archinstall/locales/id/LC_MESSAGES/base.po | 4 +- archinstall/locales/it/LC_MESSAGES/base.po | 4 +- archinstall/locales/nl/LC_MESSAGES/base.po | 7 +- archinstall/locales/pl/LC_MESSAGES/base.po | 8 +- archinstall/locales/pt/LC_MESSAGES/base.po | 7 +- archinstall/locales/pt_BR/LC_MESSAGES/base.po | 4 +- archinstall/locales/ru/LC_MESSAGES/base.po | 4 +- archinstall/locales/sv/LC_MESSAGES/base.po | 7 +- archinstall/locales/ta/LC_MESSAGES/base.po | 4 +- archinstall/locales/tr/LC_MESSAGES/base.po | 7 +- archinstall/locales/ur/LC_MESSAGES/base.po | 7 +- examples/guided.py | 4 +- examples/only_hd.py | 2 +- examples/swiss.py | 6 +- 47 files changed, 1087 insertions(+), 813 deletions(-) create mode 100644 archinstall/lib/disk/encryption.py create mode 100644 archinstall/lib/menu/abstract_menu.py delete mode 100644 archinstall/lib/menu/selection_menu.py create mode 100644 archinstall/lib/menu/table_selection_menu.py create mode 100644 archinstall/lib/models/disk_encryption.py (limited to 'archinstall/lib/menu') diff --git a/.github/workflows/mypy.yaml b/.github/workflows/mypy.yaml index 6a4a9860..20c98f3b 100644 --- a/.github/workflows/mypy.yaml +++ b/.github/workflows/mypy.yaml @@ -15,4 +15,8 @@ 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 archinstall/lib/models/users.py archinstall/lib/disk/blockdevice.py archinstall/lib/user_interaction/subvolume_config.py archinstall/lib/disk/btrfs/btrfs_helpers.py archinstall/lib/translationhandler.py archinstall/lib/disk/diskinfo.py + run: mypy --follow-imports=silent archinstall/lib/menu/abstract_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 + archinstall/lib/disk/blockdevice.py archinstall/lib/user_interaction/subvolume_config.py archinstall/lib/disk/btrfs/btrfs_helpers.py + archinstall/lib/translationhandler.py archinstall/lib/disk/diskinfo.py archinstall/lib/menu/table_selection_menu.py archinstall/lib/hsm + archinstall/lib/disk/encryption.py archinstall/lib/models/disk_encryption.py diff --git a/archinstall/__init__.py b/archinstall/__init__.py index c6135e74..e496d213 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -37,18 +37,14 @@ from .lib.menu import Menu from .lib.menu.list_manager import ListManager from .lib.menu.text_input import TextInput from .lib.menu.global_menu import GlobalMenu -from .lib.menu.selection_menu import ( +from .lib.menu.abstract_menu import ( Selector, - GeneralMenu + AbstractMenu ) from .lib.translationhandler import TranslationHandler, 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.5.2" diff --git a/archinstall/lib/configuration.py b/archinstall/lib/configuration.py index 2a43174d..ce782f6c 100644 --- a/archinstall/lib/configuration.py +++ b/archinstall/lib/configuration.py @@ -5,15 +5,18 @@ import logging import pathlib from typing import Optional, Dict +from .hsm.fido import Fido2 +from .models.disk_encryption import DiskEncryption 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(): + disk_encryption: DiskEncryption = storage['arguments'].get('disk_encryption') + if disk_encryption.hsm_device: + if not Fido2.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" @@ -21,6 +24,7 @@ def configuration_sanity_check(): + f" 'systemd-cryptenroll --fido2-device=list'." ) + class ConfigurationOutput: def __init__(self, config: Dict): """ @@ -39,8 +43,8 @@ class ConfigurationOutput: self._user_creds_file = "user_credentials.json" self._disk_layout_file = "user_disk_layout.json" - self._sensitive = ['!users', '!encryption-password'] - self._ignore = ['abort', 'install', 'config', 'creds', 'dry_run'] + self._sensitive = ['!users'] + self._ignore = ['abort', 'install', 'config', 'creds', 'dry_run', 'disk_encryption'] self._process_config() diff --git a/archinstall/lib/disk/encryption.py b/archinstall/lib/disk/encryption.py new file mode 100644 index 00000000..67f656c8 --- /dev/null +++ b/archinstall/lib/disk/encryption.py @@ -0,0 +1,162 @@ +from typing import Dict, Optional, Any, TYPE_CHECKING, List + +from ..menu.abstract_menu import Selector, AbstractSubMenu +from ..menu.menu import MenuSelectionType +from ..menu.table_selection_menu import TableMenu +from ..models.disk_encryption import EncryptionType, DiskEncryption +from ..user_interaction.partitioning_conf import current_partition_layout +from ..user_interaction.utils import get_password +from ..menu import Menu +from ..general import secret +from ..hsm.fido import Fido2Device, Fido2 + +if TYPE_CHECKING: + _: Any + + +class DiskEncryptionMenu(AbstractSubMenu): + def __init__(self, data_store: Dict[str, Any], preset: Optional[DiskEncryption], disk_layouts: Dict[str, Any]): + if preset: + self._preset = preset + else: + self._preset = DiskEncryption() + + self._disk_layouts = disk_layouts + super().__init__(data_store=data_store) + + def _setup_selection_menu_options(self): + self._menu_options['encryption_password'] = \ + Selector( + _('Encryption password'), + lambda x: select_encrypted_password(), + display_func=lambda x: secret(x) if x else '', + default=self._preset.encryption_password, + enabled=True + ) + self._menu_options['encryption_type'] = \ + Selector( + _('Encryption type'), + func=lambda preset: select_encryption_type(preset), + display_func=lambda x: EncryptionType.type_to_text(x) if x else None, + dependencies=['encryption_password'], + default=self._preset.encryption_type, + enabled=True + ) + self._menu_options['partitions'] = \ + Selector( + _('Partitions'), + func=lambda preset: select_partitions_to_encrypt(self._disk_layouts, preset), + display_func=lambda x: f'{len(x)} {_("Partitions")}' if x else None, + dependencies=['encryption_password'], + default=self._preset.partitions, + preview_func=self._prev_disk_layouts, + enabled=True + ) + self._menu_options['HSM'] = \ + Selector( + description=_('Use HSM to unlock encrypted drive'), + func=lambda preset: select_hsm(preset), + display_func=lambda x: self._display_hsm(x), + dependencies=['encryption_password'], + default=self._preset.hsm_device, + enabled=True + ) + + def run(self, allow_reset: bool = True) -> Optional[DiskEncryption]: + super().run(allow_reset=allow_reset) + + if self._data_store.get('encryption_password', None): + return DiskEncryption( + encryption_password=self._data_store.get('encryption_password', None), + encryption_type=self._data_store['encryption_type'], + partitions=self._data_store.get('partitions', None), + hsm_device=self._data_store.get('HSM', None) + ) + + return None + + def _display_hsm(self, device: Optional[Fido2Device]) -> Optional[str]: + if device: + return device.manufacturer + + if not Fido2.get_fido2_devices(): + return str(_('No HSM devices available')) + return None + + def _prev_disk_layouts(self) -> Optional[str]: + selector = self._menu_options['partitions'] + if selector.has_selection(): + partitions: List[Any] = selector.current_selection + output = str(_('Partitions to be encrypted')) + '\n' + output += current_partition_layout(partitions, with_title=False) + return output.rstrip() + return None + + +def select_encryption_type(preset: EncryptionType) -> Optional[EncryptionType]: + title = str(_('Select disk encryption option')) + options = [ + # _type_to_text(EncryptionType.FullDiskEncryption), + EncryptionType.type_to_text(EncryptionType.Partition) + ] + + preset_value = EncryptionType.type_to_text(preset) + choice = Menu(title, options, preset_values=preset_value).run() + + match choice.type_: + case MenuSelectionType.Reset: return None + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Selection: return EncryptionType.text_to_type(choice.value) # type: ignore + + +def select_encrypted_password() -> Optional[str]: + if passwd := get_password(prompt=str(_('Enter disk encryption password (leave blank for no encryption): '))): + return passwd + return None + + +def select_hsm(preset: Optional[Fido2Device] = None) -> Optional[Fido2Device]: + title = _('Select a FIDO2 device to use for HSM') + fido_devices = Fido2.get_fido2_devices() + + if fido_devices: + choice = TableMenu(title, data=fido_devices).run() + match choice.type_: + case MenuSelectionType.Reset: + return None + case MenuSelectionType.Skip: + return preset + case MenuSelectionType.Selection: + return choice.value # type: ignore + + return None + + +def select_partitions_to_encrypt(disk_layouts: Dict[str, Any], preset: List[Any]) -> List[Any]: + # 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). + all_partitions = [] + for blockdevice in disk_layouts.values(): + if partitions := blockdevice.get('partitions'): + partitions = [p for p in partitions if p['mountpoint'] != '/boot'] + all_partitions += partitions + + if all_partitions: + title = str(_('Select which partitions to encrypt')) + partition_table = current_partition_layout(all_partitions, with_title=False).strip() + + choice = TableMenu( + title, + table_data=(all_partitions, partition_table), + multi=True + ).run() + + match choice.type_: + case MenuSelectionType.Reset: + return [] + case MenuSelectionType.Skip: + return preset + case MenuSelectionType.Selection: + return choice.value # type: ignore + + return [] diff --git a/archinstall/lib/disk/filesystem.py b/archinstall/lib/disk/filesystem.py index af5879aa..bdfa502a 100644 --- a/archinstall/lib/disk/filesystem.py +++ b/archinstall/lib/disk/filesystem.py @@ -5,6 +5,8 @@ import json import pathlib from typing import Optional, Dict, Any, TYPE_CHECKING # https://stackoverflow.com/a/39757388/929999 +from ..models.disk_encryption import DiskEncryption + if TYPE_CHECKING: from .blockdevice import BlockDevice _: Any @@ -107,33 +109,22 @@ class Filesystem: continue if partition.get('filesystem', {}).get('format', False): - # needed for backward compatibility with the introduction of the new "format_options" format_options = partition.get('options',[]) + partition.get('filesystem',{}).get('format_options',[]) - if partition.get('encrypted', False): + disk_encryption: DiskEncryption = storage['arguments'].get('disk_encryption') + + if partition in disk_encryption.partitions: if not partition['device_instance']: raise DiskError(f"Internal error caused us to loose the partition. Please report this issue upstream!") - if not partition.get('!password'): - if not storage['arguments'].get('!encryption-password'): - if storage['arguments'] == 'silent': - raise ValueError(f"Missing encryption password for {partition['device_instance']}") - - from ..user_interaction import get_password - - prompt = str(_('Enter a encryption password for {}').format(partition['device_instance'])) - storage['arguments']['!encryption-password'] = get_password(prompt) - - partition['!password'] = storage['arguments']['!encryption-password'] - if partition.get('mountpoint',None): 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}" - partition['device_instance'].encrypt(password=partition['!password']) + partition['device_instance'].encrypt(password=disk_encryption.encryption_password) # Immediately unlock the encrypted device to format the inner volume - with luks2(partition['device_instance'], loopdev, partition['!password'], auto_unmount=True) as unlocked_device: + with luks2(partition['device_instance'], loopdev, disk_encryption.encryption_password, auto_unmount=True) as unlocked_device: if not partition.get('wipe'): if storage['arguments'] == 'silent': raise ValueError(f"Missing fs-type to format on newly created encrypted partition {partition['device_instance']}") diff --git a/archinstall/lib/disk/helpers.py b/archinstall/lib/disk/helpers.py index 256f7abb..a5164b76 100644 --- a/archinstall/lib/disk/helpers.py +++ b/archinstall/lib/disk/helpers.py @@ -469,12 +469,6 @@ def disk_layouts() -> Optional[Dict[str, Any]]: return None -def encrypted_partitions(blockdevices :Dict[str, Any]) -> bool: - for blockdevice in blockdevices.values(): - for partition in blockdevice.get('partitions', []): - if partition.get('encrypted', False): - yield partition - def find_partition_by_mountpoint(block_devices :List[BlockDevice], relative_mountpoint :str) -> Partition: for device in block_devices: for partition in block_devices[device]['partitions']: diff --git a/archinstall/lib/disk/partition.py b/archinstall/lib/disk/partition.py index 04d33453..9febf102 100644 --- a/archinstall/lib/disk/partition.py +++ b/archinstall/lib/disk/partition.py @@ -91,7 +91,6 @@ class Partition: self._path = path self._part_id = part_id self._target_mountpoint = mountpoint - self._encrypted = None self._encrypted = encrypted self._wipe = False self._type = 'primary' diff --git a/archinstall/lib/hsm/__init__.py b/archinstall/lib/hsm/__init__.py index c0888b04..a3f64019 100644 --- a/archinstall/lib/hsm/__init__.py +++ b/archinstall/lib/hsm/__init__.py @@ -1,4 +1 @@ -from .fido import ( - get_fido2_devices, - fido2_enroll -) \ No newline at end of file +from .fido import Fido2 diff --git a/archinstall/lib/hsm/fido.py b/archinstall/lib/hsm/fido.py index 49f36957..4cd956a3 100644 --- a/archinstall/lib/hsm/fido.py +++ b/archinstall/lib/hsm/fido.py @@ -1,57 +1,92 @@ -import typing -import pathlib import getpass import logging + +from dataclasses import dataclass +from pathlib import Path +from typing import List + from ..general import SysCommand, SysCommandWorker, clear_vt100_escape_codes from ..disk.partition import Partition from ..general import log -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 - pin_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 - - elif pin_inputted is False and bytes(f"please enter security token pin", 'UTF-8') in worker._trace_log.lower(): - worker.write(bytes(getpass.getpass(" "), 'UTF-8')) - pin_inputted = True - - log(f"You might need to touch the FIDO2 device to unlock it if no prompt comes up after 3 seconds.", level=logging.INFO, fg="yellow") \ No newline at end of file + +@dataclass +class Fido2Device: + path: Path + manufacturer: str + product: str + + +class Fido2: + _loaded: bool = False + _fido2_devices: List[Fido2Device] = [] + + @classmethod + def get_fido2_devices(cls, reload: bool = False) -> List[Fido2Device]: + """ + 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. + + Output example: + + PATH MANUFACTURER PRODUCT + /dev/hidraw1 Yubico YubiKey OTP+FIDO+CCID + """ + + # to prevent continous reloading which will slow + # down moving the cursor in the menu + if not cls._loaded or reload: + ret = SysCommand(f"systemd-cryptenroll --fido2-device=list").decode('UTF-8') + if not ret: + log('Unable to retrieve fido2 devices', level=logging.ERROR) + return [] + + fido_devices = clear_vt100_escape_codes(ret) + + manufacturer_pos = 0 + product_pos = 0 + devices = [] + + for line in fido_devices.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.append( + Fido2Device(path, manufacturer, product) + ) + + cls._loaded = True + cls._fido2_devices = devices + + return cls._fido2_devices + + @classmethod + def fido2_enroll(cls, hsm_device: Fido2Device, partition :Partition, password :str): + worker = SysCommandWorker(f"systemd-cryptenroll --fido2-device={hsm_device.path} {partition.real_device}", peak_output=True) + pw_inputted = False + pin_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 + + elif pin_inputted is False and bytes(f"please enter security token pin", 'UTF-8') in worker._trace_log.lower(): + worker.write(bytes(getpass.getpass(" "), 'UTF-8')) + pin_inputted = True + + log(f"You might need to touch the FIDO2 device to unlock it if no prompt comes up after 3 seconds.", level=logging.INFO, fg="yellow") diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 18f6e244..1926f593 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -15,16 +15,16 @@ from .hardware import has_uefi, is_vm, cpu_vendor from .locale_helpers import verify_keyboard_layout, verify_x11_keyboard_layout from .disk.helpers import findmnt from .mirrors import use_mirrors +from .models.disk_encryption import DiskEncryption from .plugins import plugins from .storage import storage -# from .user_interaction import * from .output import log 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 from .models.subvolume import Subvolume +from .hsm import Fido2 if TYPE_CHECKING: _: Any @@ -135,6 +135,8 @@ class Installer: self._zram_enabled = False + self._disk_encryption: DiskEncryption = storage['arguments'].get('disk_encryption') + def log(self, *args :str, level :int = logging.DEBUG, **kwargs :str): """ installer.log() wraps output.log() mainly to set a default log-level for this install session. @@ -196,7 +198,7 @@ class Installer: def _create_keyfile(self,luks_handle , partition :dict, password :str): """ roiutine to create keyfiles, so it can be moved elsewhere """ - if partition.get('generate-encryption-key-file'): + if self._disk_encryption.generate_encryption_file(partition): if not (cryptkey_dir := pathlib.Path(f"{self.target}/etc/cryptsetup-keys.d")).exists(): cryptkey_dir.mkdir(parents=True) # Once we store the key as ../xyzloop.key systemd-cryptsetup can automatically load this key @@ -244,26 +246,20 @@ class Installer: mount_queue = {} # we manage the encrypted partititons - for partition in [entry for entry in list_part if entry.get('encrypted', False)]: + for partition in self._disk_encryption.partitions: # open the luks device and all associate stuff - if not (password := partition.get('!password', None)) and storage['arguments'].get('!encryption-password'): - password = storage['arguments'].get('!encryption-password') - elif not password: - raise RequirementError(f"Missing partition encryption password in layout: {partition}") - 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): - list_luks_handles.append([luks_handle, partition, password]) + with (luks_handle := luks2(partition['device_instance'], loopdev, self._disk_encryption.encryption_password, auto_unmount=False)) as unlocked_device: + if self._disk_encryption.generate_encryption_file(partition) and not self._has_root(partition): + list_luks_handles.append([luks_handle, partition, self._disk_encryption.encryption_password]) # 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: - if storage['arguments'].get('HSM'): - hsm_device_path = storage['arguments']['HSM'] - fido2_enroll(hsm_device_path, partition['device_instance'], password) + if self._has_root(partition) and self._disk_encryption.generate_encryption_file(partition) is False: + if self._disk_encryption.hsm_device: + Fido2.fido2_enroll(self._disk_encryption.hsm_device, partition['device_instance'], self._disk_encryption.encryption_password) btrfs_subvolumes = [entry for entry in list_part if entry.get('btrfs', {}).get('subvolumes', [])] @@ -650,7 +646,7 @@ class Installer: mkinit.write(f"BINARIES=({' '.join(self.BINARIES)})\n") mkinit.write(f"FILES=({' '.join(self.FILES)})\n") - if not storage['arguments'].get('HSM'): + if not self._disk_encryption.hsm_device: # 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. @@ -694,7 +690,7 @@ class Installer: self.HOOKS.remove('fsck') if self.detect_encryption(partition): - if storage['arguments'].get('HSM'): + if self._disk_encryption.hsm_device: # Required bby mkinitcpio to add support for fido2-device options self.pacstrap('libfido2') @@ -758,7 +754,7 @@ class Installer: # TODO: Use python functions for this SysCommand(f'/usr/bin/arch-chroot {self.target} chmod 700 /root') - if storage['arguments'].get('HSM'): + if self._disk_encryption.hsm_device: # 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. @@ -886,7 +882,7 @@ class Installer: kernel_options = f"options" - if storage['arguments'].get('HSM'): + if self._disk_encryption.hsm_device: # 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: diff --git a/archinstall/lib/menu/abstract_menu.py b/archinstall/lib/menu/abstract_menu.py new file mode 100644 index 00000000..61466e3e --- /dev/null +++ b/archinstall/lib/menu/abstract_menu.py @@ -0,0 +1,493 @@ +from __future__ import annotations + +import logging +import sys +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 ..translationhandler import TranslationHandler, Language +from ..user_interaction.general_conf import select_archinstall_language + +if TYPE_CHECKING: + _: Any + + +class Selector: + def __init__( + self, + description :str, + func :Callable = None, + display_func :Callable = None, + default :Any = None, + enabled :bool = False, + dependencies :List = [], + dependencies_not :List = [], + exec_func :Callable = None, + preview_func :Callable = None, + mandatory :bool = False, + no_store :bool = False + ): + """ + Create a new menu selection entry + + :param description: Text that will be displayed as the menu entry + :type description: str + + :param func: Function that is called when the menu entry is selected + :type func: Callable + + :param display_func: After specifying a setting for a menu item it is displayed + on the right side of the item as is; with this function one can modify the entry + to be displayed; e.g. when specifying a password one can display **** instead + :type display_func: Callable + + :param default: Default value for this menu entry + :type default: Any + + :param enabled: Specify if this menu entry should be displayed + :type enabled: bool + + :param dependencies: Specify dependencies for this menu entry; if the dependencies + are not set yet, then this item is not displayed; e.g. disk_layout depends on selectiong + harddrive(s) first + :type dependencies: list + + :param dependencies_not: These are the exclusive options; the menu item will only be + displayed if non of the entries in the list have been specified + :type dependencies_not: list + + :param exec_func: A function with the name and the result of the selection as input parameter and which returns boolean. + Can be used for any action deemed necessary after selection. If it returns True, exits the menu loop, if False, + menu returns to the selection screen. If not specified it is assumed the return is False + :type exec_func: Callable + + :param preview_func: A callable which invokws a preview screen + :type preview_func: Callable + + :param mandatory: A boolean which determines that the field is mandatory, i.e. menu can not be exited if it is not set + :type mandatory: bool + + :param no_store: A boolean which determines that the field should or shouldn't be stored in the data storage + :type no_store: bool + """ + self._description = description + self.func = func + self._display_func = display_func + self._current_selection = default + self.enabled = enabled + self._dependencies = dependencies + self._dependencies_not = dependencies_not + self.exec_func = exec_func + self._preview_func = preview_func + self.mandatory = mandatory + self._no_store = no_store + + @property + def description(self) -> str: + return self._description + + @property + def dependencies(self) -> List: + return self._dependencies + + @property + def dependencies_not(self) -> List: + return self._dependencies_not + + @property + def current_selection(self): + return self._current_selection + + @property + def preview_func(self): + return self._preview_func + + def do_store(self) -> bool: + return self._no_store is False + + def set_enabled(self, status :bool = True): + self.enabled = status + + def update_description(self, description :str): + self._description = description + + def menu_text(self, padding: int = 0) -> str: + if self._description == '': # special menu option for __separator__ + return '' + + current = '' + + if self._display_func: + current = self._display_func(self._current_selection) + else: + if self._current_selection is not None: + current = str(self._current_selection) + + if current: + padding += 5 + description = str(self._description).ljust(padding, ' ') + current = str(_('set: {}').format(current)) + else: + description = self._description + current = '' + + return f'{description} {current}' + + def set_current_selection(self, current :Optional[str]): + self._current_selection = current + + def has_selection(self) -> bool: + if not self._current_selection: + return False + return True + + def get_selection(self) -> Any: + return self._current_selection + + def is_empty(self) -> bool: + if self._current_selection is None: + return True + elif isinstance(self._current_selection, (str, list, dict)) and len(self._current_selection) == 0: + return True + return False + + def is_enabled(self) -> bool: + return self.enabled + + def is_mandatory(self) -> bool: + return self.mandatory + + def set_mandatory(self, status :bool = True): + self.mandatory = status + if status and not self.is_enabled(): + self.set_enabled(True) + + +class AbstractMenu: + def __init__(self, data_store: Dict[str, Any] = None, auto_cursor=False, preview_size :float = 0.2): + """ + Create a new selection menu. + + :param data_store: Area (Dict) where the resulting data will be held. At least an entry for each option. Default area is self._data_store (not preset in the call, due to circular references + :type data_store: Dict + + :param auto_cursor: Boolean which determines if the cursor stays on the first item (false) or steps each invocation of a selection entry (true) + :type auto_cursor: bool + + :param preview_size. Size in fractions of screen size of the preview window + ;type preview_size: float (range 0..1) + + """ + self._enabled_order :List[str] = [] + self._translation_handler = TranslationHandler() + self.is_context_mgr = False + self._data_store = data_store if data_store is not None else {} + self.auto_cursor = auto_cursor + self._menu_options: Dict[str, Selector] = {} + self._setup_selection_menu_options() + self.preview_size = preview_size + self._last_choice = None + + @property + def last_choice(self): + return self._last_choice + + def __enter__(self, *args :Any, **kwargs :Any) -> AbstractMenu: + self.is_context_mgr = True + return self + + def __exit__(self, *args :Any, **kwargs :Any) -> None: + # TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager + # TODO: skip processing when it comes from a planified exit + if len(args) >= 2 and args[1]: + log(args[1], level=logging.ERROR, fg='red') + print(" Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues") + raise args[1] + + for key in self._menu_options: + selector = self._menu_options[key] + if key and key not in self._data_store: + self._data_store[key] = selector.current_selection + + self.exit_callback() + + @property + def translation_handler(self) -> TranslationHandler: + return self._translation_handler + + def _setup_selection_menu_options(self): + """ Define the menu options. + Menu options can be defined here in a subclass or done per program calling self.set_option() + """ + return + + def pre_callback(self, selector_name): + """ will be called before each action in the menu """ + return + + def post_callback(self, selection_name: str = None, value: Any = None): + """ will be called after each action in the menu """ + return True + + def exit_callback(self): + """ will be called at the end of the processing of the menu """ + return + + def synch(self, selector_name :str, omit_if_set :bool = False,omit_if_disabled :bool = False): + """ loads menu options with data_store value """ + arg = self._data_store.get(selector_name, None) + # don't display the menu option if it was defined already + if arg is not None and omit_if_set: + return + + if not self.option(selector_name).is_enabled() and omit_if_disabled: + return + + if arg is not None: + self._menu_options[selector_name].set_current_selection(arg) + + def _update_enabled_order(self, selector_name: str): + self._enabled_order.append(selector_name) + + def enable(self, selector_name :str, omit_if_set :bool = False , mandatory :bool = False): + """ activates menu options """ + if self._menu_options.get(selector_name, None): + self._menu_options[selector_name].set_enabled(True) + self._update_enabled_order(selector_name) + + if mandatory: + self._menu_options[selector_name].set_mandatory(True) + self.synch(selector_name,omit_if_set) + else: + print(f'No selector found: {selector_name}') + sys.exit(1) + + def _preview_display(self, selection_name: str) -> Optional[str]: + config_name, selector = self._find_selection(selection_name) + if preview := selector.preview_func: + return preview() + return None + + def _get_menu_text_padding(self, entries: List[Selector]): + return max([len(str(selection.description)) for selection in entries]) + + def _find_selection(self, selection_name: str) -> Tuple[str, Selector]: + enabled_menus = self._menus_to_enable() + padding = self._get_menu_text_padding(list(enabled_menus.values())) + option = [(k, v) for k, v in self._menu_options.items() if v.menu_text(padding).strip() == selection_name.strip()] + + if len(option) != 1: + raise ValueError(f'Selection not found: {selection_name}') + config_name = option[0][0] + selector = option[0][1] + return config_name, selector + + def run(self, allow_reset: bool = False): + """ Calls the Menu framework""" + # we synch all the options just in case + for item in self.list_options(): + self.synch(item) + + self.post_callback() # as all the values can vary i have to exec this callback + cursor_pos = None + + while True: + # Before continuing, set the preferred keyboard layout/language in the current terminal. + # This will just help the user with the next following questions. + self._set_kb_language() + enabled_menus = self._menus_to_enable() + + padding = self._get_menu_text_padding(list(enabled_menus.values())) + menu_options = [m.menu_text(padding) for m in enabled_menus.values()] + + warning_msg = str(_('All settings will be reset, are you sure?')) + + selection = Menu( + _('Set/Modify the below options'), + menu_options, + sort=False, + cursor_index=cursor_pos, + preview_command=self._preview_display, + preview_size=self.preview_size, + skip_empty_entries=True, + skip=False, + allow_reset=allow_reset, + allow_reset_warning_msg=warning_msg + ).run() + + match selection.type_: + case MenuSelectionType.Reset: + self._data_store = {} + return + case MenuSelectionType.Selection: + value: str = selection.value # type: ignore + + 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 + + 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 + + # we get the last action key + actions = {str(v.description):k for k,v in self._menu_options.items()} + self._last_choice = actions[selection.value.strip()] # type: ignore + + if not self.is_context_mgr: + self.__exit__() + + def _process_selection(self, selection_name :str) -> bool: + """ determines and executes the selection y + Can / Should be extended to handle specific selection issues + Returns true if the menu shall continue, False if it has ended + """ + # find the selected option in our option list + config_name, selector = self._find_selection(selection_name) + return self.exec_option(config_name, selector) + + def exec_option(self, config_name :str, p_selector :Selector = None) -> bool: + """ processes the execution of a given menu entry + - pre process callback + - selection function + - post process callback + - exec action + returns True if the loop has to continue, false if the loop can be closed + """ + if not p_selector: + selector = self.option(config_name) + else: + selector = p_selector + + self.pre_callback(config_name) + + result = None + if selector.func: + presel_val = self.option(config_name).get_selection() + result = selector.func(presel_val) + self._menu_options[config_name].set_current_selection(result) + if selector.do_store(): + self._data_store[config_name] = result + exec_ret_val = selector.exec_func(config_name,result) if selector.exec_func else False + self.post_callback(config_name,result) + + if exec_ret_val and self._check_mandatory_status(): + return False + return True + + def _set_kb_language(self): + """ general for ArchInstall""" + # Before continuing, set the preferred keyboard layout/language in the current terminal. + # This will just help the user with the next following questions. + if self._data_store.get('keyboard-layout', None) and len(self._data_store['keyboard-layout']): + set_keyboard_language(self._data_store['keyboard-layout']) + + def _verify_selection_enabled(self, selection_name :str) -> bool: + """ general """ + if selection := self._menu_options.get(selection_name, None): + if not selection.enabled: + return False + + if len(selection.dependencies) > 0: + for d in selection.dependencies: + if not self._verify_selection_enabled(d) or self._menu_options[d].is_empty(): + return False + + if len(selection.dependencies_not) > 0: + for d in selection.dependencies_not: + if not self._menu_options[d].is_empty(): + return False + return True + + raise ValueError(f'No selection found: {selection_name}') + + def _menus_to_enable(self) -> dict: + """ general """ + enabled_menus = {} + + for name, selection in self._menu_options.items(): + if self._verify_selection_enabled(name): + enabled_menus[name] = selection + + # sort the enabled menu by the order we enabled them in + # we'll add the entries that have been enabled via the selector constructor at the top + enabled_keys = [i for i in enabled_menus.keys() if i not in self._enabled_order] + # and then we add the ones explicitly enabled by the enable function + enabled_keys += [i for i in self._enabled_order if i in enabled_menus.keys()] + + ordered_menus = {k: enabled_menus[k] for k in enabled_keys} + + return ordered_menus + + def option(self,name :str) -> Selector: + # TODO check inexistent name + return self._menu_options[name] + + def list_options(self) -> Iterator: + """ Iterator to retrieve the enabled menu option names + """ + for item in self._menu_options: + yield item + + def list_enabled_options(self) -> Iterator: + """ Iterator to retrieve the enabled menu options at a given time. + The results are dynamic (if between calls to the iterator some elements -still not retrieved- are (de)activated + """ + for item in self._menu_options: + if item in self._menus_to_enable(): + yield item + + def set_option(self, name :str, selector :Selector): + self._menu_options[name] = selector + self.synch(name) + + def _check_mandatory_status(self) -> bool: + for field in self._menu_options: + option = self._menu_options[field] + if option.is_mandatory() and not option.has_selection(): + return False + return True + + def set_mandatory(self, field :str, status :bool): + self.option(field).set_mandatory(status) + + def mandatory_overview(self) -> Tuple[int, int]: + mandatory_fields = 0 + mandatory_waiting = 0 + 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, preset_value: Language) -> Language: + language = select_archinstall_language(self.translation_handler.translated_languages, preset_value) + self._translation_handler.activate(language) + return language + + +class AbstractSubMenu(AbstractMenu): + def __init__(self, data_store: Dict[str, Any] = None): + super().__init__(data_store=data_store) + + self._menu_options['__separator__'] = Selector('') + self._menu_options['back'] = \ + Selector( + _('Back'), + no_store=True, + enabled=True, + exec_func=lambda n, v: True, + ) diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index 444ba7ee..0d348227 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -3,12 +3,13 @@ from __future__ import annotations from typing import Any, List, Optional, Union, Dict, TYPE_CHECKING import archinstall -from ..disk import encrypted_partitions +from ..disk.encryption import DiskEncryptionMenu from ..general import SysCommand, secret from ..hardware import has_uefi from ..menu import Menu -from ..menu.selection_menu import Selector, GeneralMenu +from ..menu.abstract_menu import Selector, AbstractMenu from ..models import NetworkConfiguration +from ..models.disk_encryption import DiskEncryption, EncryptionType from ..models.users import User from ..output import FormattedOutput from ..profiles import is_desktop_profile, Profile @@ -25,7 +26,6 @@ from ..user_interaction import ask_to_configure_network from ..user_interaction import get_password, ask_for_a_timezone, save_config from ..user_interaction import select_additional_repositories from ..user_interaction import select_disk_layout -from ..user_interaction import select_encrypted_partitions from ..user_interaction import select_harddrives from ..user_interaction import select_kernel from ..user_interaction import select_language @@ -39,7 +39,7 @@ if TYPE_CHECKING: _: Any -class GlobalMenu(GeneralMenu): +class GlobalMenu(AbstractMenu): def __init__(self,data_store): self._disk_check = True super().__init__(data_store=data_store, auto_cursor=True, preview_size=0.3) @@ -91,18 +91,13 @@ class GlobalMenu(GeneralMenu): preview_func=self._prev_disk_layouts, display_func=lambda x: self._display_disk_layout(x), dependencies=['harddrives']) - self._menu_options['!encryption-password'] = \ + self._menu_options['disk_encryption'] = \ Selector( - _('Encryption password'), - 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 - ) + _('Disk encryption'), + lambda preset: self._disk_encryption(preset), + preview_func=self._prev_disk_encryption, + display_func=lambda x: self._display_disk_encryption(x), + dependencies=['disk_layouts']) self._menu_options['swap'] = \ Selector( _('Swap'), @@ -209,28 +204,6 @@ class GlobalMenu(GeneralMenu): def post_callback(self,name :str = None ,result :Any = None): self._update_install_text(name, result) - def exit_callback(self): - if self._data_store.get('harddrives', None) and self._data_store.get('!encryption-password', None): - # 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: - for blockdevice in storage['arguments']['disk_layouts']: - if storage['arguments']['disk_layouts'][blockdevice].get('partitions'): - for partition_index in select_encrypted_partitions( - title=_('Select which partitions to encrypt:'), - partitions=storage['arguments']['disk_layouts'][blockdevice]['partitions'], - filter_=(lambda p: p['mountpoint'] != '/boot') - ): - - partition = storage['arguments']['disk_layouts'][blockdevice]['partitions'][partition_index] - partition['encrypted'] = True - partition['!password'] = storage['arguments']['!encryption-password'] - - # We make sure generate-encryption-key-file is set on additional partitions - # other than the root partition. Otherwise they won't unlock properly #1279 - if partition['mountpoint'] != '/': - partition['generate-encryption-key-file'] = True - def _install_text(self): missing = len(self._missing_configs()) if missing > 0: @@ -246,6 +219,20 @@ class GlobalMenu(GeneralMenu): else: return str(cur_value) + def _disk_encryption(self, preset: Optional[DiskEncryption]) -> Optional[DiskEncryption]: + data_store: Dict[str, Any] = {} + + selector = self._menu_options['disk_layouts'] + + if selector.has_selection(): + layouts: Dict[str, Dict[str, Any]] = selector.current_selection + else: + # this should not happen as the encryption menu has the disk layout as dependency + raise ValueError('No disk layout specified') + + disk_encryption = DiskEncryptionMenu(data_store, preset, layouts).run() + return disk_encryption + def _prev_network_config(self) -> Optional[str]: selector = self._menu_options['nic'] if selector.has_selection(): @@ -283,6 +270,30 @@ class GlobalMenu(GeneralMenu): return f'{total_nr} {_("Partitions")}' return '' + def _prev_disk_encryption(self) -> Optional[str]: + selector = self._menu_options['disk_encryption'] + if selector.has_selection(): + encryption: DiskEncryption = selector.current_selection + + enc_type = EncryptionType.type_to_text(encryption.encryption_type) + output = str(_('Encryption type')) + f': {enc_type}\n' + output += str(_('Password')) + f': {secret(encryption.encryption_password)}\n' + + if encryption.partitions: + output += 'Partitions: {} selected'.format(len(encryption.partitions)) + '\n' + + if encryption.hsm_device: + output += f'HSM: {encryption.hsm_device.manufacturer}' + + return output + + return None + + def _display_disk_encryption(self, current_value: Optional[DiskEncryption]) -> str: + if current_value: + return EncryptionType.type_to_text(current_value.encryption_type) + return '' + def _prev_install_missing_config(self) -> Optional[str]: if missing := self._missing_configs(): text = str(_('Missing configurations:\n')) @@ -327,11 +338,10 @@ class GlobalMenu(GeneralMenu): password = get_password(prompt=prompt) return password - 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: - return None + # def _select_encrypted_password(self) -> Optional[str]: + # if passwd := get_password(prompt=str(_('Enter disk encryption password (leave blank for no encryption): '))): + # return passwd + # return None def _select_ntp(self, preset :bool = True) -> bool: ntp = ask_ntp(preset) diff --git a/archinstall/lib/menu/list_manager.py b/archinstall/lib/menu/list_manager.py index ae3a6eb5..1e09d987 100644 --- a/archinstall/lib/menu/list_manager.py +++ b/archinstall/lib/menu/list_manager.py @@ -104,7 +104,7 @@ class ListManager: return options, header def _run_actions_on_entry(self, entry: Any): - options = self.filter_options(entry,self._sub_menu_actions) + [self._cancel_action] + options = self.filter_options(entry, self._sub_menu_actions) + [self._cancel_action] display_value = self.selected_action_display(entry) prompt = _("Select an action for '{}'").format(display_value) diff --git a/archinstall/lib/menu/menu.py b/archinstall/lib/menu/menu.py index 773ff1de..09685c55 100644 --- a/archinstall/lib/menu/menu.py +++ b/archinstall/lib/menu/menu.py @@ -18,8 +18,8 @@ if TYPE_CHECKING: class MenuSelectionType(Enum): Selection = auto() - Esc = auto() - Ctrl_c = auto() + Skip = auto() + Reset = auto() @dataclass @@ -56,8 +56,8 @@ class Menu(TerminalMenu): preview_size: float = 0.0, preview_title: str = 'Info', header :Union[List[str],str] = None, - raise_error_on_interrupt :bool = False, - raise_error_warning_msg :str = '', + allow_reset :bool = False, + allow_reset_warning_msg :str = '', clear_screen: bool = True, show_search_hint: bool = True, cycle_cursor: bool = True, @@ -150,17 +150,10 @@ class Menu(TerminalMenu): self._skip = skip self._default_option = default_option self._multi = multi - self._raise_error_on_interrupt = raise_error_on_interrupt - self._raise_error_warning_msg = raise_error_warning_msg + self._raise_error_on_interrupt = allow_reset + self._raise_error_warning_msg = allow_reset_warning_msg self._preview_command = preview_command - menu_title = f'\n{title}\n\n' - - if header: - if not isinstance(header,(list,tuple)): - header = [header] - menu_title += '\n'.join(header) - action_info = '' if skip: action_info += str(_('ESC to skip')) @@ -173,7 +166,15 @@ class Menu(TerminalMenu): action_info += ', ' if len(action_info) > 0 else '' action_info += str(_('TAB to select')) - menu_title += action_info + '\n' + if action_info: + action_info += '\n\n' + + menu_title = f'\n{action_info}{title}\n' + + if header: + if not isinstance(header,(list,tuple)): + header = [header] + menu_title += '\n' + '\n'.join(header) if default_option: # if a default value was specified we move that one @@ -215,7 +216,7 @@ class Menu(TerminalMenu): try: idx = self.show() except KeyboardInterrupt: - return MenuSelection(type_=MenuSelectionType.Ctrl_c) + return MenuSelection(type_=MenuSelectionType.Reset) def check_default(elem): if self._default_option is not None and f'{self._default_option} {self._default_str}' in elem: @@ -234,7 +235,7 @@ class Menu(TerminalMenu): result = check_default(self._menu_options[idx]) return MenuSelection(type_=MenuSelectionType.Selection, value=result) else: - return MenuSelection(type_=MenuSelectionType.Esc) + return MenuSelection(type_=MenuSelectionType.Skip) def _preview_wrapper(self, preview_command: Optional[Callable], current_selection: str) -> Optional[str]: if preview_command: @@ -246,15 +247,15 @@ class Menu(TerminalMenu): def run(self) -> MenuSelection: ret = self._show() - if ret.type_ == MenuSelectionType.Ctrl_c: + if ret.type_ == MenuSelectionType.Reset: if self._raise_error_on_interrupt and len(self._raise_error_warning_msg) > 0: response = Menu(self._raise_error_warning_msg, 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: - system('clear') - return self.run() + elif ret.type_ is MenuSelectionType.Skip: + if not self._skip: + system('clear') + return self.run() return ret diff --git a/archinstall/lib/menu/selection_menu.py b/archinstall/lib/menu/selection_menu.py deleted file mode 100644 index 8a08812c..00000000 --- a/archinstall/lib/menu/selection_menu.py +++ /dev/null @@ -1,497 +0,0 @@ -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 ..translationhandler import TranslationHandler, Language -from ..hsm.fido import get_fido2_devices - -from ..user_interaction.general_conf import select_archinstall_language - -if TYPE_CHECKING: - _: Any - - -class Selector: - def __init__( - self, - description :str, - func :Callable = None, - display_func :Callable = None, - default :Any = None, - enabled :bool = False, - dependencies :List = [], - dependencies_not :List = [], - exec_func :Callable = None, - preview_func :Callable = None, - mandatory :bool = False, - no_store :bool = False - ): - """ - Create a new menu selection entry - - :param description: Text that will be displayed as the menu entry - :type description: str - - :param func: Function that is called when the menu entry is selected - :type func: Callable - - :param display_func: After specifying a setting for a menu item it is displayed - on the right side of the item as is; with this function one can modify the entry - to be displayed; e.g. when specifying a password one can display **** instead - :type display_func: Callable - - :param default: Default value for this menu entry - :type default: Any - - :param enabled: Specify if this menu entry should be displayed - :type enabled: bool - - :param dependencies: Specify dependencies for this menu entry; if the dependencies - are not set yet, then this item is not displayed; e.g. disk_layout depends on selectiong - harddrive(s) first - :type dependencies: list - - :param dependencies_not: These are the exclusive options; the menu item will only be - displayed if non of the entries in the list have been specified - :type dependencies_not: list - - :param exec_func: A function with the name and the result of the selection as input parameter and which returns boolean. - Can be used for any action deemed necessary after selection. If it returns True, exits the menu loop, if False, - menu returns to the selection screen. If not specified it is assumed the return is False - :type exec_func: Callable - - :param preview_func: A callable which invokws a preview screen - :type preview_func: Callable - - :param mandatory: A boolean which determines that the field is mandatory, i.e. menu can not be exited if it is not set - :type mandatory: bool - - :param no_store: A boolean which determines that the field should or shouldn't be stored in the data storage - :type no_store: bool - """ - self._description = description - self.func = func - self._display_func = display_func - self._current_selection = default - self.enabled = enabled - self._dependencies = dependencies - self._dependencies_not = dependencies_not - self.exec_func = exec_func - self._preview_func = preview_func - self.mandatory = mandatory - self._no_store = no_store - - @property - def description(self) -> str: - return self._description - - @property - def dependencies(self) -> List: - return self._dependencies - - @property - def dependencies_not(self) -> List: - return self._dependencies_not - - @property - def current_selection(self): - return self._current_selection - - @property - def preview_func(self): - return self._preview_func - - def do_store(self) -> bool: - return self._no_store is False - - def set_enabled(self, status :bool = True): - self.enabled = status - - def update_description(self, description :str): - self._description = description - - def menu_text(self, padding: int = 0) -> str: - if self._description == '': # special menu option for __separator__ - return '' - - current = '' - - if self._display_func: - current = self._display_func(self._current_selection) - else: - if self._current_selection is not None: - current = str(self._current_selection) - - if current: - padding += 5 - description = str(self._description).ljust(padding, ' ') - current = str(_('set: {}').format(current)) - else: - description = self._description - current = '' - - return f'{description} {current}' - - def set_current_selection(self, current :Optional[str]): - self._current_selection = current - - def has_selection(self) -> bool: - if not self._current_selection: - return False - return True - - def get_selection(self) -> Any: - return self._current_selection - - def is_empty(self) -> bool: - if self._current_selection is None: - return True - elif isinstance(self._current_selection, (str, list, dict)) and len(self._current_selection) == 0: - return True - return False - - def is_enabled(self) -> bool: - return self.enabled - - def is_mandatory(self) -> bool: - return self.mandatory - - def set_mandatory(self, status :bool = True): - self.mandatory = status - if status and not self.is_enabled(): - self.set_enabled(True) - -class GeneralMenu: - def __init__(self, data_store :dict = None, auto_cursor=False, preview_size :float = 0.2): - """ - Create a new selection menu. - - :param data_store: Area (Dict) where the resulting data will be held. At least an entry for each option. Default area is self._data_store (not preset in the call, due to circular references - :type data_store: Dict - - :param auto_cursor: Boolean which determines if the cursor stays on the first item (false) or steps each invocation of a selection entry (true) - :type auto_cursor: bool - - :param preview_size. Size in fractions of screen size of the preview window - ;type preview_size: float (range 0..1) - - """ - self._enabled_order :List[str] = [] - self._translation_handler = TranslationHandler() - self.is_context_mgr = False - self._data_store = data_store if data_store is not None else {} - self.auto_cursor = auto_cursor - self._menu_options: Dict[str, Selector] = {} - self._setup_selection_menu_options() - self.preview_size = preview_size - self._last_choice = None - - @property - def last_choice(self): - return self._last_choice - - def __enter__(self, *args :Any, **kwargs :Any) -> GeneralMenu: - self.is_context_mgr = True - return self - - def __exit__(self, *args :Any, **kwargs :Any) -> None: - # TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager - # TODO: skip processing when it comes from a planified exit - if len(args) >= 2 and args[1]: - log(args[1], level=logging.ERROR, fg='red') - print(" Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues") - raise args[1] - - for key in self._menu_options: - sel = self._menu_options[key] - if key and key not in self._data_store: - self._data_store[key] = sel._current_selection - - self.exit_callback() - - @property - def translation_handler(self) -> TranslationHandler: - return self._translation_handler - - def _setup_selection_menu_options(self): - """ Define the menu options. - Menu options can be defined here in a subclass or done per program calling self.set_option() - """ - return - - def pre_callback(self, selector_name): - """ will be called before each action in the menu """ - return - - def post_callback(self, selection_name: str = None, value: Any = None): - """ will be called after each action in the menu """ - return True - - def exit_callback(self): - """ will be called at the end of the processing of the menu """ - return - - def synch(self, selector_name :str, omit_if_set :bool = False,omit_if_disabled :bool = False): - """ loads menu options with data_store value """ - arg = self._data_store.get(selector_name, None) - # don't display the menu option if it was defined already - if arg is not None and omit_if_set: - return - - if not self.option(selector_name).is_enabled() and omit_if_disabled: - return - - if arg is not None: - self._menu_options[selector_name].set_current_selection(arg) - - def _update_enabled_order(self, selector_name: str): - self._enabled_order.append(selector_name) - - def enable(self, selector_name :str, omit_if_set :bool = False , mandatory :bool = False): - """ activates menu options """ - if self._menu_options.get(selector_name, None): - self._menu_options[selector_name].set_enabled(True) - self._update_enabled_order(selector_name) - - if mandatory: - self._menu_options[selector_name].set_mandatory(True) - self.synch(selector_name,omit_if_set) - else: - print(f'No selector found: {selector_name}') - sys.exit(1) - - def _preview_display(self, selection_name: str) -> Optional[str]: - config_name, selector = self._find_selection(selection_name) - if preview := selector.preview_func: - return preview() - return None - - def _get_menu_text_padding(self, entries: List[Selector]): - return max([len(str(selection.description)) for selection in entries]) - - def _find_selection(self, selection_name: str) -> Tuple[str, Selector]: - enabled_menus = self._menus_to_enable() - padding = self._get_menu_text_padding(list(enabled_menus.values())) - option = [(k, v) for k, v in self._menu_options.items() if v.menu_text(padding).strip() == selection_name.strip()] - - if len(option) != 1: - raise ValueError(f'Selection not found: {selection_name}') - config_name = option[0][0] - selector = option[0][1] - return config_name, selector - - def run(self): - """ Calls the Menu framework""" - # we synch all the options just in case - for item in self.list_options(): - self.synch(item) - - self.post_callback() # as all the values can vary i have to exec this callback - cursor_pos = None - - while True: - # Before continuing, set the preferred keyboard layout/language in the current terminal. - # This will just help the user with the next following questions. - self._set_kb_language() - enabled_menus = self._menus_to_enable() - - padding = self._get_menu_text_padding(list(enabled_menus.values())) - menu_options = [m.menu_text(padding) for m in enabled_menus.values()] - - selection = Menu( - _('Set/Modify the below options'), - menu_options, - sort=False, - cursor_index=cursor_pos, - preview_command=self._preview_display, - preview_size=self.preview_size, - skip_empty_entries=True, - skip=False - ).run() - - 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 - - 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 - - # we get the last action key - actions = {str(v.description):k for k,v in self._menu_options.items()} - self._last_choice = actions[selection.value.strip()] - - if not self.is_context_mgr: - self.__exit__() - - def _process_selection(self, selection_name :str) -> bool: - """ determines and executes the selection y - Can / Should be extended to handle specific selection issues - Returns true if the menu shall continue, False if it has ended - """ - # find the selected option in our option list - config_name, selector = self._find_selection(selection_name) - return self.exec_option(config_name, selector) - - def exec_option(self, config_name :str, p_selector :Selector = None) -> bool: - """ processes the execution of a given menu entry - - pre process callback - - selection function - - post process callback - - exec action - returns True if the loop has to continue, false if the loop can be closed - """ - if not p_selector: - selector = self.option(config_name) - else: - selector = p_selector - - self.pre_callback(config_name) - - result = None - if selector.func: - presel_val = self.option(config_name).get_selection() - result = selector.func(presel_val) - self._menu_options[config_name].set_current_selection(result) - if selector.do_store(): - self._data_store[config_name] = result - exec_ret_val = selector.exec_func(config_name,result) if selector.exec_func else False - self.post_callback(config_name,result) - - if exec_ret_val and self._check_mandatory_status(): - return False - return True - - def _set_kb_language(self): - """ general for ArchInstall""" - # Before continuing, set the preferred keyboard layout/language in the current terminal. - # This will just help the user with the next following questions. - if self._data_store.get('keyboard-layout', None) and len(self._data_store['keyboard-layout']): - set_keyboard_language(self._data_store['keyboard-layout']) - - def _verify_selection_enabled(self, selection_name :str) -> bool: - """ general """ - if selection := self._menu_options.get(selection_name, None): - if not selection.enabled: - return False - - if len(selection.dependencies) > 0: - for d in selection.dependencies: - if not self._verify_selection_enabled(d) or self._menu_options[d].is_empty(): - return False - - if len(selection.dependencies_not) > 0: - for d in selection.dependencies_not: - if not self._menu_options[d].is_empty(): - return False - return True - - raise ValueError(f'No selection found: {selection_name}') - - def _menus_to_enable(self) -> dict: - """ general """ - enabled_menus = {} - - for name, selection in self._menu_options.items(): - if self._verify_selection_enabled(name): - enabled_menus[name] = selection - - # sort the enabled menu by the order we enabled them in - # we'll add the entries that have been enabled via the selector constructor at the top - enabled_keys = [i for i in enabled_menus.keys() if i not in self._enabled_order] - # and then we add the ones explicitly enabled by the enable function - enabled_keys += [i for i in self._enabled_order if i in enabled_menus.keys()] - - ordered_menus = {k: enabled_menus[k] for k in enabled_keys} - - return ordered_menus - - def option(self,name :str) -> Selector: - # TODO check inexistent name - return self._menu_options[name] - - def list_options(self) -> Iterator: - """ Iterator to retrieve the enabled menu option names - """ - for item in self._menu_options: - yield item - - def list_enabled_options(self) -> Iterator: - """ Iterator to retrieve the enabled menu options at a given time. - The results are dynamic (if between calls to the iterator some elements -still not retrieved- are (de)activated - """ - for item in self._menu_options: - if item in self._menus_to_enable(): - yield item - - def set_option(self, name :str, selector :Selector): - self._menu_options[name] = selector - self.synch(name) - - def _check_mandatory_status(self) -> bool: - for field in self._menu_options: - option = self._menu_options[field] - if option.is_mandatory() and not option.has_selection(): - return False - return True - - def set_mandatory(self, field :str, status :bool): - self.option(field).set_mandatory(status) - - def mandatory_overview(self) -> Tuple[int, int]: - mandatory_fields = 0 - mandatory_waiting = 0 - 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, preset_value: Language) -> Language: - language = select_archinstall_language(self.translation_handler.translated_languages, preset_value) - self._translation_handler.activate(language) - return language - - 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: - selection: Any = choice.value - index = int(selection.split('|',1)[0]) - return pathlib.Path(list(fido_devices.keys())[index]) - - return None diff --git a/archinstall/lib/menu/table_selection_menu.py b/archinstall/lib/menu/table_selection_menu.py new file mode 100644 index 00000000..09cd6ee2 --- /dev/null +++ b/archinstall/lib/menu/table_selection_menu.py @@ -0,0 +1,107 @@ +from typing import Any, Tuple, List, Dict, Optional + +from .menu import MenuSelectionType, MenuSelection +from ..output import FormattedOutput +from ..menu import Menu + + +class TableMenu(Menu): + def __init__( + self, + title: str, + data: List[Any] = [], + table_data: Optional[Tuple[List[Any], str]] = None, + custom_menu_options: List[str] = [], + default: Any = None, + multi: bool = False + ): + """ + param title: Text that will be displayed above the menu + :type title: str + + param data: List of objects that will be displayed as rows + :type data: List + + param table_data: Tuple containing a list of objects and the corresponding + Table representation of the data as string; this can be used in case the table + has to be crafted in a more sophisticated manner + :type table_data: Optional[Tuple[List[Any], str]] + + param custom_options: List of custom options that will be displayed under the table + :type custom_menu_options: List + """ + if not data and not table_data: + raise ValueError('Either "data" or "table_data" must be provided') + + self._custom_options = custom_menu_options + self._multi = multi + + if multi: + header_padding = 7 + else: + header_padding = 2 + + if len(data): + table_text = FormattedOutput.as_table(data) + rows = table_text.split('\n') + table = self._create_table(data, rows, header_padding=header_padding) + elif table_data is not None: + # we assume the table to be + # h1 | h2 + # ----------- + # r1 | r2 + data = table_data[0] + rows = table_data[1].split('\n') + table = self._create_table(data, rows, header_padding=header_padding) + + self._options, header = self._prepare_selection(table) + + super().__init__( + title, + self._options, + header=header, + skip_empty_entries=True, + show_search_hint=False, + allow_reset=True, + multi=multi, + default_option=default + ) + + def run(self) -> MenuSelection: + choice = super().run() + + match choice.type_: + case MenuSelectionType.Selection: + if self._multi: + choice.value = [self._options[val] for val in choice.value] # type: ignore + else: + choice.value = self._options[choice.value] # type: ignore + + return choice + + def _create_table(self, data: List[Any], rows: List[str], header_padding: int = 2) -> Dict[str, Any]: + # these are the header rows of the table and do not map to any data obviously + # we're adding 2 spaces as prefix because the menu selector '> ' will be put before + # the selectable rows so the header has to be aligned + padding = ' ' * header_padding + display_data = {f'{padding}{rows[0]}': None, f'{padding}{rows[1]}': None} + + for row, entry in zip(rows[2:], data): + row = row.replace('|', '\\|') + display_data[row] = entry + + return display_data + + def _prepare_selection(self, table: Dict[str, Any]) -> Tuple[Dict[str, Any], str]: + # header rows are mapped to None so make sure to exclude those from the selectable data + options = {key: val for key, val in table.items() if val is not None} + header = '' + + if len(options) > 0: + table_header = [key for key, val in table.items() if val is None] + header = '\n'.join(table_header) + + custom = {key: None for key in self._custom_options} + options.update(custom) + + return options, header diff --git a/archinstall/lib/models/disk_encryption.py b/archinstall/lib/models/disk_encryption.py new file mode 100644 index 00000000..80627767 --- /dev/null +++ b/archinstall/lib/models/disk_encryption.py @@ -0,0 +1,43 @@ +from dataclasses import dataclass, field +from enum import Enum, auto +from typing import Optional, List, Dict, TYPE_CHECKING, Any + +from archinstall.lib.hsm.fido import Fido2Device + +if TYPE_CHECKING: + _: Any + + +class EncryptionType(Enum): + Partition = auto() + # FullDiskEncryption = auto() + + @classmethod + def _encryption_type_mapper(cls) -> Dict[str, 'EncryptionType']: + return { + # str(_('Full disk encryption')): EncryptionType.FullDiskEncryption, + str(_('Partition encryption')): EncryptionType.Partition + } + + @classmethod + def text_to_type(cls, text: str) -> 'EncryptionType': + mapping = cls._encryption_type_mapper() + return mapping[text] + + @classmethod + def type_to_text(cls, type_: 'EncryptionType') -> str: + mapping = cls._encryption_type_mapper() + type_to_text = {type_: text for text, type_ in mapping.items()} + return type_to_text[type_] + + +@dataclass +class DiskEncryption: + encryption_type: EncryptionType = EncryptionType.Partition + encryption_password: str = '' + partitions: List[str] = field(default_factory=list) + hsm_device: Optional[Fido2Device] = None + + def generate_encryption_file(self, partition) -> bool: + return partition in self.partitions and partition['mountpoint'] != '/' + diff --git a/archinstall/lib/user_interaction/__init__.py b/archinstall/lib/user_interaction/__init__.py index a1ca2652..2bc46759 100644 --- a/archinstall/lib/user_interaction/__init__.py +++ b/archinstall/lib/user_interaction/__init__.py @@ -4,7 +4,7 @@ from .backwards_compatible_conf import generic_select, generic_multi_select from .locale_conf import select_locale_lang, select_locale_enc from .system_conf import select_kernel, select_harddrives, select_driver, ask_for_bootloader, ask_for_swap from .network_conf import ask_to_configure_network -from .partitioning_conf import select_partition, select_encrypted_partitions +from .partitioning_conf import select_partition from .general_conf import (ask_ntp, ask_for_a_timezone, ask_for_audio_selection, select_language, select_mirror_regions, select_profile, select_archinstall_language, ask_additional_packages_to_install, select_additional_repositories, ask_hostname, add_number_of_parrallel_downloads) diff --git a/archinstall/lib/user_interaction/disk_conf.py b/archinstall/lib/user_interaction/disk_conf.py index b5ed6967..554d13ef 100644 --- a/archinstall/lib/user_interaction/disk_conf.py +++ b/archinstall/lib/user_interaction/disk_conf.py @@ -45,13 +45,13 @@ def select_disk_layout(preset: Optional[Dict[str, Any]], block_devices: list, ad choice = Menu( _('Select what you wish to do with the selected block devices'), modes, - raise_error_on_interrupt=True, - raise_error_warning_msg=warning + allow_reset=True, + allow_reset_warning_msg=warning ).run() match choice.type_: - case MenuSelectionType.Esc: return preset - case MenuSelectionType.Ctrl_c: return None + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Reset: return None case MenuSelectionType.Selection: if choice.value == wipe_mode: return get_default_partition_layout(block_devices, advanced_options) @@ -77,7 +77,7 @@ def select_disk(dict_o_disks: Dict[str, BlockDevice]) -> Optional[BlockDevice]: choice = Menu(title, drives).run() - if choice.type_ == MenuSelectionType.Esc: + if choice.type_ == MenuSelectionType.Skip: return None drive = dict_o_disks[choice.value] diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py index efd746a4..76631a98 100644 --- a/archinstall/lib/user_interaction/general_conf.py +++ b/archinstall/lib/user_interaction/general_conf.py @@ -48,7 +48,7 @@ def ask_for_a_timezone(preset: str = None) -> str: ).run() match choice.type_: - case MenuSelectionType.Esc: return preset + case MenuSelectionType.Skip: return preset case MenuSelectionType.Selection: return choice.value @@ -60,7 +60,7 @@ def ask_for_audio_selection(desktop: bool = True, preset: str = None) -> str: choice = Menu(_('Choose an audio server'), choices, preset_values=preset, default_option=default).run() match choice.type_: - case MenuSelectionType.Esc: return preset + case MenuSelectionType.Skip: return preset case MenuSelectionType.Selection: return choice.value @@ -107,12 +107,12 @@ def select_mirror_regions(preset_values: Dict[str, Any] = {}) -> Dict[str, Any]: list(mirrors.keys()), preset_values=preselected, multi=True, - raise_error_on_interrupt=True + allow_reset=True ).run() match selected_mirror.type_: - case MenuSelectionType.Ctrl_c: return {} - case MenuSelectionType.Esc: return preset_values + case MenuSelectionType.Reset: return {} + case MenuSelectionType.Skip: return preset_values case _: return {selected: mirrors[selected] for selected in selected_mirror.value} @@ -134,7 +134,7 @@ def select_archinstall_language(languages: List[Language], preset_value: Languag ).run() match choice.type_: - case MenuSelectionType.Esc: + case MenuSelectionType.Skip: return preset_value case MenuSelectionType.Selection: return options[choice.value] @@ -163,21 +163,21 @@ def select_profile(preset) -> Optional[Profile]: selection = Menu( title=title, p_options=list(options.keys()), - raise_error_on_interrupt=True, - raise_error_warning_msg=warning + allow_reset=True, + allow_reset_warning_msg=warning ).run() match selection.type_: case MenuSelectionType.Selection: return options[selection.value] if selection.value is not None else None - case MenuSelectionType.Ctrl_c: + case MenuSelectionType.Reset: storage['profile_minimal'] = False storage['_selected_servers'] = [] storage['_desktop_profile'] = None storage['arguments']['desktop-environment'] = None storage['arguments']['gfx_driver_packages'] = None return None - case MenuSelectionType.Esc: + case MenuSelectionType.Skip: return None @@ -259,10 +259,10 @@ def select_additional_repositories(preset: List[str]) -> List[str]: sort=False, multi=True, preset_values=preset, - raise_error_on_interrupt=True + allow_reset=True ).run() match choice.type_: - case MenuSelectionType.Esc: return preset - case MenuSelectionType.Ctrl_c: return [] + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Reset: return [] case MenuSelectionType.Selection: return choice.value diff --git a/archinstall/lib/user_interaction/locale_conf.py b/archinstall/lib/user_interaction/locale_conf.py index 15720064..bbbe070b 100644 --- a/archinstall/lib/user_interaction/locale_conf.py +++ b/archinstall/lib/user_interaction/locale_conf.py @@ -23,7 +23,7 @@ def select_locale_lang(preset: str = None) -> str: match selected_locale.type_: case MenuSelectionType.Selection: return selected_locale.value - case MenuSelectionType.Esc: return preset + case MenuSelectionType.Skip: return preset def select_locale_enc(preset: str = None) -> str: @@ -39,4 +39,4 @@ def select_locale_enc(preset: str = None) -> str: match selected_locale.type_: case MenuSelectionType.Selection: return selected_locale.value - case MenuSelectionType.Esc: return preset + case MenuSelectionType.Skip: return preset diff --git a/archinstall/lib/user_interaction/network_conf.py b/archinstall/lib/user_interaction/network_conf.py index 557e8ed8..5e637f23 100644 --- a/archinstall/lib/user_interaction/network_conf.py +++ b/archinstall/lib/user_interaction/network_conf.py @@ -71,7 +71,7 @@ class ManualNetworkConfig(ListManager): available = set(all_ifaces) - set(existing_ifaces) choice = Menu(str(_('Select interface to add')), list(available), skip=True).run() - if choice.type_ == MenuSelectionType.Esc: + if choice.type_ == MenuSelectionType.Skip: return None return choice.value @@ -154,13 +154,13 @@ def ask_to_configure_network( list(network_options.values()), cursor_index=cursor_idx, sort=False, - raise_error_on_interrupt=True, - raise_error_warning_msg=warning + allow_reset=True, + allow_reset_warning_msg=warning ).run() match choice.type_: - case MenuSelectionType.Esc: return preset - case MenuSelectionType.Ctrl_c: return None + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Reset: return None if choice.value == network_options['none']: return None diff --git a/archinstall/lib/user_interaction/partitioning_conf.py b/archinstall/lib/user_interaction/partitioning_conf.py index f2e6b881..cff76dc2 100644 --- a/archinstall/lib/user_interaction/partitioning_conf.py +++ b/archinstall/lib/user_interaction/partitioning_conf.py @@ -119,7 +119,7 @@ def select_partition( choice = Menu(title, partition_indexes, multi=multiple).run() - if choice.type_ == MenuSelectionType.Esc: + if choice.type_ == MenuSelectionType.Skip: return None if isinstance(choice.value, list): @@ -150,7 +150,6 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, delete_all_partitions = str(_('Clear/Delete all partitions')) assign_mount_point = str(_('Assign mount-point for a partition')) mark_formatted = str(_('Mark/Unmark a partition to be formatted (wipes data)')) - mark_encrypted = str(_('Mark/Unmark a partition as encrypted')) mark_compressed = str(_('Mark/Unmark a partition as compressed (btrfs only)')) mark_bootable = str(_('Mark/Unmark a partition as bootable (automatic for /boot)')) set_filesystem_partition = str(_('Set desired filesystem for a partition')) @@ -167,7 +166,6 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, delete_all_partitions, assign_mount_point, mark_formatted, - mark_encrypted, mark_bootable, mark_compressed, set_filesystem_partition, @@ -207,7 +205,7 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, fs_choice = Menu(_('Enter a desired filesystem type for the partition'), fs_types()).run() - if fs_choice.type_ == MenuSelectionType.Esc: + if fs_choice.type_ == MenuSelectionType.Skip: continue prompt = str(_('Enter the start sector (percentage or block number, default: {}): ')).format( @@ -322,15 +320,6 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, # Negate the current wipe marking block_device_struct["partitions"][partition]['wipe'] = not block_device_struct["partitions"][partition].get('wipe', False) - elif task == mark_encrypted: - title = _('{}\n\nSelect which partition to mark as encrypted').format(current_layout) - partition = select_partition(title, block_device_struct["partitions"]) - - 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) - elif task == mark_bootable: title = _('{}\n\nSelect which partition to mark as bootable').format(current_layout) partition = select_partition(title, block_device_struct["partitions"]) @@ -371,30 +360,3 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, block_device_struct["partitions"][partition]['btrfs']['subvolumes'] = result 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_) - - if len(partition_indexes) == 0: - return None - - # show current partition layout: - if len(partitions): - title += current_partition_layout(partitions, with_idx=True) + '\n' - - 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) diff --git a/archinstall/lib/user_interaction/save_conf.py b/archinstall/lib/user_interaction/save_conf.py index f542bc9b..d60ef995 100644 --- a/archinstall/lib/user_interaction/save_conf.py +++ b/archinstall/lib/user_interaction/save_conf.py @@ -55,7 +55,7 @@ def save_config(config: Dict): preview_command=preview ).run() - if choice.type_ == MenuSelectionType.Esc: + if choice.type_ == MenuSelectionType.Skip: return while True: diff --git a/archinstall/lib/user_interaction/system_conf.py b/archinstall/lib/user_interaction/system_conf.py index 8b574b2c..42a6cec7 100644 --- a/archinstall/lib/user_interaction/system_conf.py +++ b/archinstall/lib/user_interaction/system_conf.py @@ -32,13 +32,13 @@ def select_kernel(preset: List[str] = None) -> List[str]: sort=True, multi=True, preset_values=preset, - raise_error_on_interrupt=True, - raise_error_warning_msg=warning + allow_reset=True, + allow_reset_warning_msg=warning ).run() match choice.type_: - case MenuSelectionType.Esc: return preset - case MenuSelectionType.Ctrl_c: return [] + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Reset: return [] case MenuSelectionType.Selection: return choice.value @@ -62,13 +62,13 @@ def select_harddrives(preset: List[str] = []) -> List[str]: list(options.keys()), preset_values=preset, multi=True, - raise_error_on_interrupt=True, - raise_error_warning_msg=warning + allow_reset=True, + allow_reset_warning_msg=warning ).run() match selected_harddrive.type_: - case MenuSelectionType.Ctrl_c: return [] - case MenuSelectionType.Esc: return preset + case MenuSelectionType.Reset: return [] + case MenuSelectionType.Skip: return preset case MenuSelectionType.Selection: return [options[i] for i in selected_harddrive.value] @@ -132,7 +132,7 @@ def ask_for_bootloader(advanced_options: bool = False, preset: str = None) -> st ).run() match selection.type_: - case MenuSelectionType.Esc: return preset + case MenuSelectionType.Skip: return preset case MenuSelectionType.Selection: bootloader = 'grub-install' if selection.value == Menu.yes() else bootloader else: # We use the common names for the bootloader as the selection, and map it back to the expected values. @@ -141,7 +141,7 @@ def ask_for_bootloader(advanced_options: bool = False, preset: str = None) -> st value = '' match selection.type_: - case MenuSelectionType.Esc: value = preset_val + case MenuSelectionType.Skip: value = preset_val case MenuSelectionType.Selection: value = selection.value if value != "": @@ -165,5 +165,5 @@ def ask_for_swap(preset: bool = True) -> bool: choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes(), preset_values=preset_val).run() match choice.type_: - case MenuSelectionType.Esc: return preset + case MenuSelectionType.Skip: return preset case MenuSelectionType.Selection: return False if choice.value == Menu.no() else True diff --git a/archinstall/locales/ar/LC_MESSAGES/base.po b/archinstall/locales/ar/LC_MESSAGES/base.po index 51b6c3d6..1a9fc1aa 100644 --- a/archinstall/locales/ar/LC_MESSAGES/base.po +++ b/archinstall/locales/ar/LC_MESSAGES/base.po @@ -721,7 +721,7 @@ msgstr "" msgid "Should \"{}\" be a superuser (sudo)?" msgstr "" -msgid "Select which partitions to encrypt:" +msgid "Select which partitions to encrypt" msgstr "" msgid "very weak" diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index 7398d33a..2becbbf3 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -761,7 +761,7 @@ msgstr "" msgid "Should \"{}\" be a superuser (sudo)?" msgstr "" -msgid "Select which partitions to encrypt:" +msgid "Select which partitions to encrypt" msgstr "" msgid "very weak" diff --git a/archinstall/locales/cs/LC_MESSAGES/base.po b/archinstall/locales/cs/LC_MESSAGES/base.po index 733c9cae..b3dea244 100644 --- a/archinstall/locales/cs/LC_MESSAGES/base.po +++ b/archinstall/locales/cs/LC_MESSAGES/base.po @@ -756,8 +756,8 @@ msgstr "Zadané uživatelské jméno není platné. Zkuste to znovu" msgid "Should \"{}\" be a superuser (sudo)?" msgstr "Má být \"{}\" superuživatelem (sudoer)?" -msgid "Select which partitions to encrypt:" -msgstr "Zvolte oddíl, který bude označen jako šifrovaný:" +msgid "Select which partitions to encrypt" +msgstr "Zvolte oddíl, který bude označen jako šifrovaný" msgid "very weak" msgstr "velmi slabé" diff --git a/archinstall/locales/de/LC_MESSAGES/base.po b/archinstall/locales/de/LC_MESSAGES/base.po index 46782bc3..dee2b481 100644 --- a/archinstall/locales/de/LC_MESSAGES/base.po +++ b/archinstall/locales/de/LC_MESSAGES/base.po @@ -776,11 +776,8 @@ msgid "Should \"{}\" be a superuser (sudo)?" msgstr "Soll {} ein superuser sein (sudoer)?" #, fuzzy -msgid "Select which partitions to encrypt:" -msgstr "" -"{}\n" -"\n" -"Bitte wählen sie welche Partition verschlüsselt werden soll" +msgid "Select which partitions to encrypt" +msgstr "Bitte wählen sie welche Partition verschlüsselt werden soll" msgid "very weak" msgstr "" diff --git a/archinstall/locales/el/LC_MESSAGES/base.po b/archinstall/locales/el/LC_MESSAGES/base.po index 6425ba96..c41dbb7e 100644 --- a/archinstall/locales/el/LC_MESSAGES/base.po +++ b/archinstall/locales/el/LC_MESSAGES/base.po @@ -763,7 +763,7 @@ msgstr "Το όνομα χρήστη που εισάγατε δεν είναι msgid "Should \"{}\" be a superuser (sudo)?" msgstr "Θα έπρεπε ο \"{}\" να είναι υπερχρήστης (sudo);" -msgid "Select which partitions to encrypt:" +msgid "Select which partitions to encrypt" msgstr "Επιλέξτε ποιες διαμερίσεις να κρυπτογραφηθούν." msgid "very weak" diff --git a/archinstall/locales/en/LC_MESSAGES/base.po b/archinstall/locales/en/LC_MESSAGES/base.po index 01f59274..62543eaa 100644 --- a/archinstall/locales/en/LC_MESSAGES/base.po +++ b/archinstall/locales/en/LC_MESSAGES/base.po @@ -717,7 +717,7 @@ msgstr "" msgid "Should \"{}\" be a superuser (sudo)?" msgstr "" -msgid "Select which partitions to encrypt:" +msgid "Select which partitions to encrypt" msgstr "" msgid "very weak" diff --git a/archinstall/locales/es/LC_MESSAGES/base.po b/archinstall/locales/es/LC_MESSAGES/base.po index 3bdbe72f..f744daae 100644 --- a/archinstall/locales/es/LC_MESSAGES/base.po +++ b/archinstall/locales/es/LC_MESSAGES/base.po @@ -762,8 +762,8 @@ msgstr "El nombre de usuario que ingresó no es válido. Intente nuevamente" msgid "Should \"{}\" be a superuser (sudo)?" msgstr "¿Debe \"{}\" ser un superusuario (sudo)?" -msgid "Select which partitions to encrypt:" -msgstr "Seleccione qué particiones cifrar:" +msgid "Select which partitions to encrypt" +msgstr "Seleccione qué particiones cifrar" msgid "very weak" msgstr "" diff --git a/archinstall/locales/fr/LC_MESSAGES/base.po b/archinstall/locales/fr/LC_MESSAGES/base.po index e58592bf..f5946503 100644 --- a/archinstall/locales/fr/LC_MESSAGES/base.po +++ b/archinstall/locales/fr/LC_MESSAGES/base.po @@ -765,11 +765,8 @@ msgid "Should \"{}\" be a superuser (sudo)?" msgstr "\"{}\" devrait-il être un superutilisateur (sudo) ?" #, fuzzy -msgid "Select which partitions to encrypt:" -msgstr "" -"{}\n" -"\n" -"Sélectionner la partition à marquer comme chiffrée" +msgid "Select which partitions to encrypt" +msgstr "Sélectionner la partition à marquer comme chiffrée" msgid "very weak" msgstr "" diff --git a/archinstall/locales/id/LC_MESSAGES/base.po b/archinstall/locales/id/LC_MESSAGES/base.po index 0ace1b09..85479389 100644 --- a/archinstall/locales/id/LC_MESSAGES/base.po +++ b/archinstall/locales/id/LC_MESSAGES/base.po @@ -763,8 +763,8 @@ msgstr "Nama pengguna yang Anda masukkan tidak valid. Coba lagi" msgid "Should \"{}\" be a superuser (sudo)?" msgstr "Haruskah \"{}\" menjadi superuser (sudo)?" -msgid "Select which partitions to encrypt:" -msgstr "Pilih partisi mana yang akan dienkripsi:" +msgid "Select which partitions to encrypt" +msgstr "Pilih partisi mana yang akan dienkripsi" msgid "very weak" msgstr "sangat lemah" diff --git a/archinstall/locales/it/LC_MESSAGES/base.po b/archinstall/locales/it/LC_MESSAGES/base.po index d122b87f..176d3959 100644 --- a/archinstall/locales/it/LC_MESSAGES/base.po +++ b/archinstall/locales/it/LC_MESSAGES/base.po @@ -763,8 +763,8 @@ msgstr "Il nome utente inserito non è valido. Riprova" msgid "Should \"{}\" be a superuser (sudo)?" msgstr "\"{}\" dovrebbe essere un superutente? (sudo)" -msgid "Select which partitions to encrypt:" -msgstr "Seleziona le partizioni da crittografare:" +msgid "Select which partitions to encrypt" +msgstr "Seleziona le partizioni da crittografare" msgid "very weak" msgstr "molto debole" diff --git a/archinstall/locales/nl/LC_MESSAGES/base.po b/archinstall/locales/nl/LC_MESSAGES/base.po index 4eb93e22..b7323059 100644 --- a/archinstall/locales/nl/LC_MESSAGES/base.po +++ b/archinstall/locales/nl/LC_MESSAGES/base.po @@ -791,11 +791,8 @@ msgid "Should \"{}\" be a superuser (sudo)?" msgstr "Moet {} gebruiker een beheerder (sudoer) worden?" #, fuzzy -msgid "Select which partitions to encrypt:" -msgstr "" -"{}\n" -"\n" -"Kies welke partitie moet worden versleuteld" +msgid "Select which partitions to encrypt" +msgstr "Kies welke partitie moet worden versleuteld" msgid "very weak" msgstr "" diff --git a/archinstall/locales/pl/LC_MESSAGES/base.po b/archinstall/locales/pl/LC_MESSAGES/base.po index 2d9968dc..6655bbbd 100644 --- a/archinstall/locales/pl/LC_MESSAGES/base.po +++ b/archinstall/locales/pl/LC_MESSAGES/base.po @@ -763,11 +763,9 @@ msgstr "Wprowadzona nazwa użytkownika jest nieprawidłowa. Spróbuj ponownie" msgid "Should \"{}\" be a superuser (sudo)?" msgstr "Czy \"{}\" powinien być superuserem (sudo)?" -msgid "Select which partitions to encrypt:" -msgstr "" -"{}\n" -"\n" -"Wybierz partycje, które mają zostać zaszyfrowane:" +#, fuzzy +msgid "Select which partitions to encrypt" +msgstr "Wybierz partycja która ma zostać zaszyfrowana" msgid "very weak" msgstr "bardzo słabe" diff --git a/archinstall/locales/pt/LC_MESSAGES/base.po b/archinstall/locales/pt/LC_MESSAGES/base.po index 55569546..683de0ad 100644 --- a/archinstall/locales/pt/LC_MESSAGES/base.po +++ b/archinstall/locales/pt/LC_MESSAGES/base.po @@ -811,11 +811,8 @@ msgid "Should \"{}\" be a superuser (sudo)?" msgstr "Deve {} ser um superutilizador (sudoer)?" #, fuzzy -msgid "Select which partitions to encrypt:" -msgstr "" -"{}\n" -"\n" -"Seleciona a partição a marcar como encriptada" +msgid "Select which partitions to encrypt" +msgstr "Seleciona a partição a marcar como encriptada" msgid "very weak" msgstr "" diff --git a/archinstall/locales/pt_BR/LC_MESSAGES/base.po b/archinstall/locales/pt_BR/LC_MESSAGES/base.po index 1981c799..938f5068 100644 --- a/archinstall/locales/pt_BR/LC_MESSAGES/base.po +++ b/archinstall/locales/pt_BR/LC_MESSAGES/base.po @@ -761,8 +761,8 @@ msgstr "O nome de usuário que você digitou é inválido. Tente novamente" msgid "Should \"{}\" be a superuser (sudo)?" msgstr "\"{}\" deve ser um superusuário (sudo)?" -msgid "Select which partitions to encrypt:" -msgstr "Selecione quais partições encriptar:" +msgid "Select which partitions to encrypt" +msgstr "Selecione quais partições encriptar" msgid "very weak" msgstr "muito fraca" diff --git a/archinstall/locales/ru/LC_MESSAGES/base.po b/archinstall/locales/ru/LC_MESSAGES/base.po index 6d5799eb..a62a8385 100644 --- a/archinstall/locales/ru/LC_MESSAGES/base.po +++ b/archinstall/locales/ru/LC_MESSAGES/base.po @@ -764,8 +764,8 @@ msgstr "Введенное вами имя пользователя недейс msgid "Should \"{}\" be a superuser (sudo)?" msgstr "Должен ли \"{}\" быть суперпользователем (sudo)?" -msgid "Select which partitions to encrypt:" -msgstr "Выберите разделы для шифрования:" +msgid "Select which partitions to encrypt" +msgstr "Выберите разделы для шифрования" msgid "very weak" msgstr "очень слабый" diff --git a/archinstall/locales/sv/LC_MESSAGES/base.po b/archinstall/locales/sv/LC_MESSAGES/base.po index 9d7f7455..fc311551 100644 --- a/archinstall/locales/sv/LC_MESSAGES/base.po +++ b/archinstall/locales/sv/LC_MESSAGES/base.po @@ -772,11 +772,8 @@ msgid "Should \"{}\" be a superuser (sudo)?" msgstr "Är detta en superanvändare (sudo-rättigheter)?" #, fuzzy -msgid "Select which partitions to encrypt:" -msgstr "" -"{}\n" -"\n" -"Välj vilken partition som skall markeras för kryptering" +msgid "Select which partitions to encrypt" +msgstr "Välj vilken partition som skall markeras för kryptering" msgid "very weak" msgstr "" diff --git a/archinstall/locales/ta/LC_MESSAGES/base.po b/archinstall/locales/ta/LC_MESSAGES/base.po index 3ea3dd19..4375f477 100644 --- a/archinstall/locales/ta/LC_MESSAGES/base.po +++ b/archinstall/locales/ta/LC_MESSAGES/base.po @@ -763,8 +763,8 @@ msgstr "நீங்கள் உள்ளிட்ட பயனர்பெய msgid "Should \"{}\" be a superuser (sudo)?" msgstr "\"{}\" ஒரு சூப்பர் யூசராக (sudo) இருக்க வேண்டுமா?" -msgid "Select which partitions to encrypt:" -msgstr "குறியாக்கம் செய்ய வேண்டிய பகிர்வுகளைத் தேர்ந்தெடுக்கவும்:" +msgid "Select which partitions to encrypt" +msgstr "குறியாக்கம் செய்ய வேண்டிய பகிர்வுகளைத் தேர்ந்தெடுக்கவும்" msgid "very weak" msgstr "மிகவும் பலவீனமானது" diff --git a/archinstall/locales/tr/LC_MESSAGES/base.po b/archinstall/locales/tr/LC_MESSAGES/base.po index c38f467e..fd1e2393 100644 --- a/archinstall/locales/tr/LC_MESSAGES/base.po +++ b/archinstall/locales/tr/LC_MESSAGES/base.po @@ -772,11 +772,8 @@ msgid "Should \"{}\" be a superuser (sudo)?" msgstr "{} bir süper kullanıcı (sudoer) olmalı mı?" #, fuzzy -msgid "Select which partitions to encrypt:" -msgstr "" -"{}\n" -"\n" -"Hangi disk bölümünün şifrelenmiş olarak işaretleneceğini seçin" +msgid "Select which partitions to encrypt" +msgstr "Hangi disk bölümünün şifrelenmiş olarak işaretleneceğini seçin" msgid "very weak" msgstr "" diff --git a/archinstall/locales/ur/LC_MESSAGES/base.po b/archinstall/locales/ur/LC_MESSAGES/base.po index 8ab6c4e2..0b024031 100644 --- a/archinstall/locales/ur/LC_MESSAGES/base.po +++ b/archinstall/locales/ur/LC_MESSAGES/base.po @@ -794,11 +794,8 @@ msgid "Should \"{}\" be a superuser (sudo)?" msgstr "کیا {} کو سپر یوزر (sudoer) ہونا چاہیے؟" #, fuzzy -msgid "Select which partitions to encrypt:" -msgstr "" -"{}\n" -"\n" -"منتخب کریں کہ کس پارٹیشن کو انکرپٹڈ یا خفیہ رکھنا ہے" +msgid "Select which partitions to encrypt" +msgstr "منتخب کریں کہ کس پارٹیشن کو انکرپٹڈ یا خفیہ رکھنا ہے" msgid "very weak" msgstr "" diff --git a/examples/guided.py b/examples/guided.py index eba78a1a..4b655240 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -54,8 +54,8 @@ def ask_user_questions(): global_menu.enable('disk_layouts') - # Get disk encryption password (or skip if blank) - global_menu.enable('!encryption-password') + # Specify disk encryption options + global_menu.enable('disk_encryption') if archinstall.arguments.get('advanced', False) or archinstall.arguments.get('HSM', None): # Enables the use of HSM diff --git a/examples/only_hd.py b/examples/only_hd.py index b3379601..e3d18f0a 100644 --- a/examples/only_hd.py +++ b/examples/only_hd.py @@ -12,7 +12,7 @@ class OnlyHDMenu(archinstall.GlobalMenu): super()._setup_selection_menu_options() options_list = [] mandatory_list = [] - options_list = ['harddrives', 'disk_layouts', '!encryption-password','swap'] + options_list = ['harddrives', 'disk_layouts', 'disk_encryption','swap'] mandatory_list = ['harddrives'] options_list.extend(['save_config','install','abort']) diff --git a/examples/swiss.py b/examples/swiss.py index 419bd859..442281de 100644 --- a/examples/swiss.py +++ b/examples/swiss.py @@ -153,7 +153,7 @@ def select_installed_locale(mode): _menus """ -class SetupMenu(archinstall.GeneralMenu): +class SetupMenu(archinstall.AbstractMenu): def __init__(self,storage_area): super().__init__(data_store=storage_area) @@ -230,14 +230,14 @@ class MyMenu(archinstall.GlobalMenu): mandatory_list = [] if self._execution_mode in ('full','lineal'): options_list = ['keyboard-layout', 'mirror-region', 'harddrives', 'disk_layouts', - '!encryption-password','swap', 'bootloader', 'hostname', '!root-password', + 'disk_encryption','swap', 'bootloader', 'hostname', '!root-password', '!users', 'profile', 'audio', 'kernels', 'packages','additional-repositories','nic', 'timezone', 'ntp'] if archinstall.arguments.get('advanced',False): options_list.extend(['sys-language','sys-encoding']) mandatory_list = ['harddrives','bootloader','hostname'] elif self._execution_mode == 'only_hd': - options_list = ['harddrives', 'disk_layouts', '!encryption-password','swap'] + options_list = ['harddrives', 'disk_layouts', 'disk_encryption','swap'] mandatory_list = ['harddrives'] elif self._execution_mode == 'only_os': options_list = ['keyboard-layout', 'mirror-region','bootloader', 'hostname', -- cgit v1.2.3-70-g09d2 From e05df22986d2adbe9041be91884f8bb577a330a1 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Sun, 13 Nov 2022 23:56:21 +1100 Subject: Remove HSM from global menu (#1559) * Remove HSM from global menu * Update * Update * Removed sys import Unused import Co-authored-by: Daniel Girtler Co-authored-by: Anton Hvornum --- archinstall/lib/menu/abstract_menu.py | 4 +--- examples/guided.py | 4 ---- 2 files changed, 1 insertion(+), 7 deletions(-) (limited to 'archinstall/lib/menu') diff --git a/archinstall/lib/menu/abstract_menu.py b/archinstall/lib/menu/abstract_menu.py index 61466e3e..5a7ca03a 100644 --- a/archinstall/lib/menu/abstract_menu.py +++ b/archinstall/lib/menu/abstract_menu.py @@ -1,7 +1,6 @@ from __future__ import annotations import logging -import sys from typing import Callable, Any, List, Iterator, Tuple, Optional, Dict, TYPE_CHECKING from .menu import Menu, MenuSelectionType @@ -261,8 +260,7 @@ class AbstractMenu: self._menu_options[selector_name].set_mandatory(True) self.synch(selector_name,omit_if_set) else: - print(f'No selector found: {selector_name}') - sys.exit(1) + raise ValueError(f'No selector found: {selector_name}') def _preview_display(self, selection_name: str) -> Optional[str]: config_name, selector = self._find_selection(selection_name) diff --git a/examples/guided.py b/examples/guided.py index 4b655240..e242138e 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -57,10 +57,6 @@ def ask_user_questions(): # Specify disk encryption options global_menu.enable('disk_encryption') - 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') -- cgit v1.2.3-70-g09d2