New encryption menu (#1520)

* New encryption menu

Co-authored-by: Daniel Girtler <girtler.daniel@gmail.com>
Co-authored-by: Anton Hvornum <anton@hvornum.se>
This commit is contained in:
Daniel Girtler 2022-11-11 19:40:05 +11:00 committed by GitHub
parent ee1eea2130
commit c3862c5779
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 629 additions and 355 deletions

View File

@ -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

View File

@ -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"

View File

@ -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()

View File

@ -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 []

View File

@ -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']}")

View File

@ -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']:

View File

@ -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'

View File

@ -1,4 +1 @@
from .fido import (
get_fido2_devices,
fido2_enroll
)
from .fido import Fido2

View File

@ -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.
@dataclass
class Fido2Device:
path: Path
manufacturer: str
product: str
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
class Fido2:
_loaded: bool = False
_fido2_devices: List[Fido2Device] = []
path = line[:MANUFACTURER_POS].rstrip()
manufacturer = line[MANUFACTURER_POS:PRODUCT_POS].rstrip()
product = line[PRODUCT_POS:]
@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.
devices[path] = {
'manufacturer' : manufacturer,
'product' : product
}
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.
return devices
So we'll look for `MANUFACTURER` and `PRODUCT`, we take their index
and we split each line based on those positions.
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
Output example:
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
PATH MANUFACTURER PRODUCT
/dev/hidraw1 Yubico YubiKey OTP+FIDO+CCID
"""
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")
# 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")

View File

@ -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:

View File

@ -2,15 +2,12 @@ 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:
@ -167,8 +164,9 @@ class Selector:
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):
class AbstractMenu:
def __init__(self, data_store: Dict[str, Any] = None, auto_cursor=False, preview_size :float = 0.2):
"""
Create a new selection menu.
@ -196,7 +194,7 @@ class GeneralMenu:
def last_choice(self):
return self._last_choice
def __enter__(self, *args :Any, **kwargs :Any) -> GeneralMenu:
def __enter__(self, *args :Any, **kwargs :Any) -> AbstractMenu:
self.is_context_mgr = True
return self
@ -209,9 +207,9 @@ class GeneralMenu:
raise args[1]
for key in self._menu_options:
sel = self._menu_options[key]
selector = self._menu_options[key]
if key and key not in self._data_store:
self._data_store[key] = sel._current_selection
self._data_store[key] = selector.current_selection
self.exit_callback()
@ -286,7 +284,7 @@ class GeneralMenu:
selector = option[0][1]
return config_name, selector
def run(self):
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():
@ -304,6 +302,8 @@ class GeneralMenu:
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,
@ -312,33 +312,39 @@ class GeneralMenu:
preview_command=self._preview_display,
preview_size=self.preview_size,
skip_empty_entries=True,
skip=False
skip=False,
allow_reset=allow_reset,
allow_reset_warning_msg=warning_msg
).run()
if selection.type_ == MenuSelectionType.Selection:
value = selection.value
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
if self.auto_cursor:
cursor_pos = menu_options.index(value) + 1 # before the strip otherwise fails
# in case the new position lands on a "placeholder" we'll skip them as well
while True:
if cursor_pos >= len(menu_options):
cursor_pos = 0
if len(menu_options[cursor_pos]) > 0:
break
cursor_pos += 1
# in case the new position lands on a "placeholder" we'll skip them as well
while True:
if cursor_pos >= len(menu_options):
cursor_pos = 0
if len(menu_options[cursor_pos]) > 0:
break
cursor_pos += 1
value = value.strip()
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
# 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()]
self._last_choice = actions[selection.value.strip()] # type: ignore
if not self.is_context_mgr:
self.__exit__()
@ -472,26 +478,16 @@ class GeneralMenu:
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()
class AbstractSubMenu(AbstractMenu):
def __init__(self, data_store: Dict[str, Any] = None):
super().__init__(data_store=data_store)
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
self._menu_options['__separator__'] = Selector('')
self._menu_options['back'] = \
Selector(
_('Back'),
no_store=True,
enabled=True,
exec_func=lambda n, v: True,
)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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'] != '/'

View File

@ -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)

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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é"

View File

@ -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 ""

View File

@ -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"

View File

@ -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"

View File

@ -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 ""

View File

@ -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 ""

View File

@ -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"

View File

@ -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"

View File

@ -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 ""

View File

@ -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"

View File

@ -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 ""

View File

@ -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"

View File

@ -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 "очень слабый"

View File

@ -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 ""

View File

@ -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 "மிகவும் பலவீனமானது"

View File

@ -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 ""

View File

@ -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 ""

View File

@ -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

View File

@ -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'])

View File

@ -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',