Send patches - preferably formatted by git format-patch - to patches at archlinux32 dot org.
summaryrefslogtreecommitdiff
path: root/archinstall/lib
diff options
context:
space:
mode:
Diffstat (limited to 'archinstall/lib')
-rw-r--r--archinstall/lib/configuration.py14
-rw-r--r--archinstall/lib/disk/encryption.py162
-rw-r--r--archinstall/lib/disk/filesystem.py23
-rw-r--r--archinstall/lib/disk/helpers.py6
-rw-r--r--archinstall/lib/disk/partition.py1
-rw-r--r--archinstall/lib/hsm/__init__.py5
-rw-r--r--archinstall/lib/hsm/fido.py137
-rw-r--r--archinstall/lib/installer.py44
-rw-r--r--archinstall/lib/menu/abstract_menu.py (renamed from archinstall/lib/menu/selection_menu.py)96
-rw-r--r--archinstall/lib/menu/global_menu.py94
-rw-r--r--archinstall/lib/menu/list_manager.py2
-rw-r--r--archinstall/lib/menu/menu.py47
-rw-r--r--archinstall/lib/menu/table_selection_menu.py107
-rw-r--r--archinstall/lib/models/disk_encryption.py43
-rw-r--r--archinstall/lib/translationhandler.py27
-rw-r--r--archinstall/lib/user_interaction/__init__.py2
-rw-r--r--archinstall/lib/user_interaction/disk_conf.py10
-rw-r--r--archinstall/lib/user_interaction/general_conf.py61
-rw-r--r--archinstall/lib/user_interaction/locale_conf.py4
-rw-r--r--archinstall/lib/user_interaction/network_conf.py10
-rw-r--r--archinstall/lib/user_interaction/partitioning_conf.py42
-rw-r--r--archinstall/lib/user_interaction/save_conf.py2
-rw-r--r--archinstall/lib/user_interaction/system_conf.py22
23 files changed, 610 insertions, 351 deletions
diff --git a/archinstall/lib/configuration.py b/archinstall/lib/configuration.py
index 2a43174d..ad537b21 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 is not None and 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 49ce4d7f..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.
@@ -865,9 +861,9 @@ class Installer:
root_fs_type = get_mount_fs_type(root_partition.filesystem)
if root_fs_type is not None:
- options_entry = f'rw intel_pstate=no_hwp rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}\n'
+ options_entry = f'rw rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}\n'
else:
- options_entry = f'rw intel_pstate=no_hwp {" ".join(self.KERNEL_PARAMS)}\n'
+ options_entry = f'rw {" ".join(self.KERNEL_PARAMS)}\n'
for subvolume in root_partition.subvolumes:
if subvolume.root is True and subvolume.name != '<FS_TREE>':
@@ -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:
@@ -984,10 +980,10 @@ class Installer:
# TODO: We need to detect if the encrypted device is a whole disk encryption,
# or simply a partition encryption. Right now we assume it's a partition (and we always have)
log(f"Identifying root partition by PART-UUID on {real_device}: '{real_device.part_uuid}'.", level=logging.DEBUG)
- kernel_parameters.append(f'cryptdevice=PARTUUID={real_device.part_uuid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}')
+ kernel_parameters.append(f'cryptdevice=PARTUUID={real_device.part_uuid}:luksdev root=/dev/mapper/luksdev rw rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}')
else:
log(f"Identifying root partition by PART-UUID on {root_partition}, looking for '{root_partition.part_uuid}'.", level=logging.DEBUG)
- kernel_parameters.append(f'root=PARTUUID={root_partition.part_uuid} rw intel_pstate=no_hwp rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}')
+ kernel_parameters.append(f'root=PARTUUID={root_partition.part_uuid} rw rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}')
SysCommand(f'efibootmgr --disk {boot_partition.path[:-1]} --part {boot_partition.path[-1]} --create --label "{label}" --loader {loader} --unicode \'{" ".join(kernel_parameters)}\' --verbose')
diff --git a/archinstall/lib/menu/selection_menu.py b/archinstall/lib/menu/abstract_menu.py
index 8a08812c..5a7ca03a 100644
--- a/archinstall/lib/menu/selection_menu.py
+++ b/archinstall/lib/menu/abstract_menu.py
@@ -1,16 +1,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 +163,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 +193,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 +206,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()
@@ -263,8 +260,7 @@ class GeneralMenu:
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)
@@ -286,7 +282,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 +300,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 +310,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 +476,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()
- 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']}")
+class AbstractSubMenu(AbstractMenu):
+ def __init__(self, data_store: Dict[str, Any] = None):
+ super().__init__(data_store=data_store)
- 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,
+ )
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 112bc0ae..09685c55 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
@@ -18,8 +18,8 @@ if TYPE_CHECKING:
class MenuSelectionType(Enum):
Selection = auto()
- Esc = auto()
- Ctrl_c = auto()
+ Skip = auto()
+ Reset = auto()
@dataclass
@@ -53,11 +53,11 @@ 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,
- 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/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..3edab93e
--- /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 ..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/translationhandler.py b/archinstall/lib/translationhandler.py
index ef33b8ec..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}")
@@ -110,7 +89,7 @@ class TranslationHandler:
"""
Set the provided font as the new terminal font
"""
- from archinstall import SysCommand, log
+ from .general import SysCommand, log
try:
log(f'Setting font: {font}', level=logging.DEBUG)
SysCommand(f'setfont {font}')
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 6365014d..76631a98 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
@@ -51,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
@@ -63,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
@@ -110,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}
@@ -125,34 +122,22 @@ 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()
match choice.type_:
- case MenuSelectionType.Esc:
+ case MenuSelectionType.Skip:
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]:
@@ -178,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
@@ -274,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 44402a69..8454a3da 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