From eeb6c2cec58bca707edc96c2f0b8e095d9b33023 Mon Sep 17 00:00:00 2001 From: ions21 <117031376+ions21@users.noreply.github.com> Date: Fri, 11 Nov 2022 08:14:33 +0000 Subject: Update Polish translations (#1543) --- archinstall/locales/pl/LC_MESSAGES/base.mo | Bin 23373 -> 25880 bytes archinstall/locales/pl/LC_MESSAGES/base.po | 149 ++++++++++++++--------------- 2 files changed, 70 insertions(+), 79 deletions(-) (limited to 'archinstall/locales/pl') diff --git a/archinstall/locales/pl/LC_MESSAGES/base.mo b/archinstall/locales/pl/LC_MESSAGES/base.mo index e9051d78..0e8e7bef 100644 Binary files a/archinstall/locales/pl/LC_MESSAGES/base.mo and b/archinstall/locales/pl/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/pl/LC_MESSAGES/base.po b/archinstall/locales/pl/LC_MESSAGES/base.po index d1c50634..2d9968dc 100644 --- a/archinstall/locales/pl/LC_MESSAGES/base.po +++ b/archinstall/locales/pl/LC_MESSAGES/base.po @@ -6,6 +6,10 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Project-Id-Version: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"X-Generator: Poedit 2.4.2\n" msgid "[!] A log file has been created here: {} {}" msgstr "[!] Plik dziennika został stworzony tutaj: {} {}" @@ -26,13 +30,13 @@ msgid "Desired hostname for the installation: " msgstr "Nazwa hosta użyta do instalacji: " msgid "Username for required superuser with sudo privileges: " -msgstr "Nazwa użytkownika dla wymaganego superużytkownika z uprawnieniami sudo: " +msgstr "Nazwa użytkownika dla wymaganego superusera z uprawnieniami sudo: " msgid "Any additional users to install (leave blank for no users): " msgstr "Ewentualni użytkownicy do instalacji (pozostaw puste jeśli nie chcesz tworzyć użytkowników): " msgid "Should this user be a superuser (sudoer)?" -msgstr "Czy użytkownik powinien być superużytkownikiem (sudoer)?" +msgstr "Czy użytkownik powinien być superuserem (sudo)?" msgid "Select a timezone" msgstr "Wybierz strefę czasową" @@ -251,26 +255,23 @@ msgstr "Locale kodowania" msgid "Drive(s)" msgstr "Dyski twarde" -#, fuzzy msgid "Disk layout" -msgstr "Zapisz układ dysku" +msgstr "Układ dysku" -#, fuzzy msgid "Encryption password" -msgstr "Ustaw hasło szyfrowania" +msgstr "Hasło szyfrujące" msgid "Swap" -msgstr "Swap" +msgstr "Pamięć wymiany (swap)" msgid "Bootloader" msgstr "Program rozruchowy" -#, fuzzy msgid "Root password" -msgstr "Hasło root" +msgstr "Hasło użytkownika root" msgid "Superuser account" -msgstr "Konto superużytkownika" +msgstr "Konto superusera" msgid "User account" msgstr "Konto użytkownika" @@ -294,7 +295,7 @@ msgid "Automatic time sync (NTP)" msgstr "Automatycznej synchronizacji czasu (NTP)" msgid "Install ({} config(s) missing)" -msgstr "Zainstaluj ({} brakuje konfiguracji)" +msgstr "Zainstaluj ({} brakujących konfiguracji)" msgid "" "You decided to skip harddrive selection\n" @@ -303,18 +304,18 @@ msgid "" "Do you wish to continue?" msgstr "" "Zdecydowałeś się pominąć wybór dysku twardego\n" -"i użyje konfiguracji dysku zamontowanego w {} (eksperymentalne)\n" -"OSTRZEŻENIE: Archinstall nie sprawdzi przydatności tej konfiguracji\n" +"i użyć konfiguracji dysku zamontowanego w {} (eksperymentalne)\n" +"OSTRZEŻENIE: Archinstall nie sprawdzi poprawności tej konfiguracji\n" "Czy chcesz kontynuować?" msgid "Re-using partition instance: {}" -msgstr "Ponowne wykorzystanie instancji partycji" +msgstr "Ponowne wykorzystanie instancji partycji: {}" msgid "Create a new partition" msgstr "Utwórz nową partycję" msgid "Delete a partition" -msgstr "Usuń partycje" +msgstr "Usuń partycję" msgid "Clear/Delete all partitions" msgstr "Wyczyść/Usuń wszystkie partycje" @@ -338,13 +339,13 @@ msgid "Abort" msgstr "Anuluj" msgid "Hostname" -msgstr "Nazwę hosta" +msgstr "Nazwa hosta" msgid "Not configured, unavailable unless setup manually" -msgstr "Nie skonfigurowana, niedostępna, chyba że zostanie skonfigurowana ręcznie" +msgstr "Niedostępna, chyba że zostanie skonfigurowana ręcznie" msgid "Timezone" -msgstr "Strefe czasową" +msgstr "Strefa czasowa" msgid "Set/Modify the below options" msgstr "Ustaw/zmodyfikuj poniższe opcje" @@ -372,13 +373,13 @@ msgid "Enter disk encryption password (leave blank for no encryption): " msgstr "Wprowadź hasło do szyfrowania dysku (pozostaw puste aby nie ustawiać szyfrowania): " msgid "Create a required super-user with sudo privileges: " -msgstr "Utwórz wymaganego superużytkownika z uprawnieniami sudo: " +msgstr "Utwórz wymaganego superusera z uprawnieniami sudo: " msgid "Enter root password (leave blank to disable root): " msgstr "Wprowadź hasło roota (pozostaw puste, aby wyłączyć roota): " msgid "Password for user \"{}\": " -msgstr "Hasło dla użytkownika \"{}\": " +msgstr "Hasło użytkownika \"{}\": " msgid "Verifying that additional packages exist (this might take a few seconds)" msgstr "Sprawdzenie, czy istnieją dodatkowe pakiety (może to potrwać kilka sekund)" @@ -438,7 +439,7 @@ msgid "" "This is your chosen configuration:" msgstr "" "\n" -"To jest wybrana konfiguracja:" +"Wybrana konfiguracja:" msgid "Pacman is already running, waiting maximum 10 minutes for it to terminate." msgstr "Pacman jest już uruchomiony i czeka maksymalnie 10 minut na zakończenie pracy." @@ -449,7 +450,6 @@ msgstr "Istniejąca wcześniej blokada programu pacman nie została zakończona. msgid "Choose which optional additional repositories to enable" msgstr "Wybierz, które z opcjonalnych dodatkowych repozytoriów mają być włączone" -#, fuzzy msgid "Add a user" msgstr "Dodaj użytkownika" @@ -533,10 +533,10 @@ msgid "Missing configurations:\n" msgstr "Brak konfiguracji:\n" msgid "Either root-password or at least 1 superuser must be specified" -msgstr "Musi być podane albo hasło root, albo co najmniej jednego superużytkownika" +msgstr "Musi być podane albo hasło root, albo co najmniej jednego superusera" msgid "Manage superuser accounts: " -msgstr "Zarządzaj kontami superużytkowników: " +msgstr "Zarządzaj kontami superusera: " msgid "Manage ordinary user accounts: " msgstr "Zarządzaj kontami zwykłych użytkowników: " @@ -594,13 +594,13 @@ msgid "Would you like to create a separate partition for /home?" msgstr "Czy chcesz stworzyć oddzielną partycje dla /home?" msgid "The selected drives do not have the minimum capacity required for an automatic suggestion\n" -msgstr "Wybrane dyski nie mają wymaganej objętości dla automatycznej sugestii\n" +msgstr "Wybrane dyski nie mają wymaganej pojemności dla automatycznej sugestii\n" msgid "Minimum capacity for /home partition: {}GB\n" -msgstr "Maksymalna objętość dla partycji /home: {}GB\n" +msgstr "Maksymalna pojemność dla partycji /home: {}GB\n" msgid "Minimum capacity for Arch Linux partition: {}GB" -msgstr "Minimalna objętośc dla partycji Arch Linux: {}GB" +msgstr "Minimalna pojemność dla partycji Arch Linux: {}GB" msgid "Continue" msgstr "Kontynuuj" @@ -618,10 +618,10 @@ msgid "Manual configuration setting must be a list" msgstr "Konfiguracja ustawiona manualnie musi być listą" msgid "No iface specified for manual configuration" -msgstr "Iface nie zostało ustawione w manualnej konfiguracji" +msgstr "Iface nie zostało ustawione w ręcznej konfiguracji" msgid "Manual nic configuration with no auto DHCP requires an IP address" -msgstr "" +msgstr "Ręczna konfiguracja nic bez automatycznego DHCP wymaga podania adresu IP" msgid "Add interface" msgstr "Dodaj interfejs" @@ -629,17 +629,14 @@ msgstr "Dodaj interfejs" msgid "Edit interface" msgstr "Edytuj interfejs" -#, fuzzy msgid "Delete interface" msgstr "Usuń interfejs" -#, fuzzy msgid "Select interface to add" -msgstr "Wybierz jeden interfejs sieciowy do skonfigurowania" +msgstr "Wybierz interfejs sieciowy do skonfigurowania" -#, fuzzy msgid "Manual configuration" -msgstr "Zapisz konfiguracje" +msgstr "Ręczna konfiguracja" msgid "Mark/Unmark a partition as compressed (btrfs only)" msgstr "Oznacz/odznacz partycje jako skompresowaną (tylko w btrfs)" @@ -648,43 +645,41 @@ msgid "The password you are using seems to be weak, are you sure you want to use msgstr "Używane przez Ciebie hasło wydaje się być słabe, czy na pewno chcesz go użyć?" msgid "Provides a selection of desktop environments and tiling window managers, e.g. gnome, kde, sway" -msgstr "Dostarcza wybór środowisk desktopowych oraz tiling window managerów, np. gnome, kde, sway" +msgstr "Dostarcza wybór środowisk graficznych oraz menedżerów okien, np. gnome, kde, sway" msgid "Select your desired desktop environment" -msgstr "Wybierz pożądane środowisko desktopowe" +msgstr "Wybierz środowisko graficzne" msgid "A very basic installation that allows you to customize Arch Linux as you see fit." -msgstr "Bardzo podstawowa instalacja pozwalająca ci dostosowanie Arch Linuxa do twoich upodobań." +msgstr "Bardzo ograniczona instalacja pozwalająca ci dostosowanie Arch Linuxa do twoich upodobań." msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" msgstr "Dostarcza wybór różnych pakietów serwerowych do zainstalowania i uruchomienia, np. httpd, nginx, mariadb" -#, fuzzy msgid "Choose which servers to install, if none then a minimal installation will be done" -msgstr "Wybierz jakie serwery zainstalować, jeżeli żadne, wtedy minimalna instalacja będzie użyta" +msgstr "Wybierz jakie serwery zainstalować. Jeżeli żadne, wtedy będzie użyta minimalna instalacja" msgid "Installs a minimal system as well as xorg and graphics drivers." -msgstr "Instaluje minimalną wersje systemu z xorg i sterownikami graficznymi." +msgstr "Instaluje system podstawowy z xorg i sterownikami graficznymi." msgid "Press Enter to continue." -msgstr "Naciśnij Enter aby kontynuować." +msgstr "Naciśnij Enter, aby kontynuować." msgid "Would you like to chroot into the newly created installation and perform post-installation configuration?" -msgstr "Czy chciałbyś zchrootować do nowostworzonej instalacji i przeprowadzić po-instalacyjną konfiguracje?" +msgstr "Czy chciałbyś zchrootować do nowej instalacji i przeprowadzić wstępną konfigurację?" msgid "Are you sure you want to reset this setting?" -msgstr "Czy napewno chcesz zresetować to ustawienie?" +msgstr "Czy na pewno chcesz zresetować to ustawienie?" msgid "Select one or more hard drives to use and configure\n" msgstr "Wybierz jeden lub więcej dysków twardych do użycia i skonfiguruj je\n" msgid "Any modifications to the existing setting will reset the disk layout!" -msgstr "Jakiekolwiek modyfikacje do istniejących ustawień zresetują układ dysków!" +msgstr "Każda zmiana istniejących ustawień zresetuje układ dysków!" msgid "If you reset the harddrive selection this will also reset the current disk layout. Are you sure?" -msgstr "Jeżeli zresetujesz wybór dysków, zresetujesz także obecny układ dysków. Jesteś pewny że chcesz to zrobić?" +msgstr "Jeżeli zresetujesz wybór dysków, zresetujesz także obecny układ dysków. Jesteś pewny, że chcesz to zrobić?" -#, fuzzy msgid "Save and exit" msgstr "Zapisz i wyjdź" @@ -700,17 +695,16 @@ msgid "No audio server" msgstr "Brak serwera dźwięku" msgid "(default)" -msgstr "(domyslne)" +msgstr "(domyślne)" -#, fuzzy msgid "Use ESC to skip" -msgstr "Kliknij ESC aby pominąć" +msgstr "Naciśnij ESC, aby pominąć" msgid "" "Use CTRL+C to reset current selection\n" "\n" msgstr "" -"Użyj CTRL+C aby zresetować obecny wybór\n" +"Użyj CTRL+C, aby zresetować obecny wybór\n" "\n" msgid "Copy to: " @@ -729,18 +723,18 @@ msgstr "Edytuj {}: " msgid "Add: " msgstr "Dodaj: " -#, fuzzy msgid "Value: " msgstr "Wartość: " +#, fuzzy msgid "You can skip selecting a drive and partitioning and use whatever drive-setup is mounted at /mnt (experimental)" -msgstr "Możesz pominąć wybów dysków i partycji i użyć dowolnego zestawu dysku zamontowanego w /mnt (eksperymentalne)" +msgstr "Możesz pominąć wybór dysków i partycji i użyć dowolnego zestawu dysku zamontowanego w /mnt (eksperymentalne)" msgid "Select one of the disks or skip and use /mnt as default" msgstr "Wybierz jeden z dysków lub pomiń i użyj /mnt jako domyślnego" msgid "Select which partitions to mark for formatting:" -msgstr "Wybierz partycja która ma zostać sformatowana:" +msgstr "Wybierz partycje, które mają zostać sformatowane:" msgid "Use HSM to unlock encrypted drive" msgstr "Użyj HSM do odblokowania zaszyfrowanego dysku" @@ -757,27 +751,23 @@ msgstr "Wolne miejsce" msgid "Bus-type" msgstr "Typ magistrali" -#, fuzzy msgid "Either root-password or at least 1 user with sudo privileges must be specified" -msgstr "Musi być podane albo hasło root, albo co najmniej jednego superużytkownika" +msgstr "Musisz podać hasło root lub utworzyć co najmniej jednego superusera" -#, fuzzy msgid "Enter username (leave blank to skip): " -msgstr "Wprowadź nazwę użytkownika, aby utworzyć dodatkowego użytkownika (pozostaw puste, aby pominąć): " +msgstr "Wprowadź nazwę użytkownika (pozostaw puste, aby pominąć): " msgid "The username you entered is invalid. Try again" msgstr "Wprowadzona nazwa użytkownika jest nieprawidłowa. Spróbuj ponownie" -#, fuzzy msgid "Should \"{}\" be a superuser (sudo)?" -msgstr "Czy {} powinien być superuserem (sudoer)?" +msgstr "Czy \"{}\" powinien być superuserem (sudo)?" -#, fuzzy msgid "Select which partitions to encrypt:" msgstr "" "{}\n" "\n" -"Wybierz partycja która ma zostać zaszyfrowana" +"Wybierz partycje, które mają zostać zaszyfrowane:" msgid "very weak" msgstr "bardzo słabe" @@ -791,55 +781,56 @@ msgstr "umiarkowane" msgid "strong" msgstr "mocne" -#, fuzzy msgid "Add subvolume" -msgstr "Subwolumin :{:16}" +msgstr "Dodaj podwolumen" msgid "Edit subvolume" -msgstr "Edytuj wolumen" +msgstr "Edytuj podwolumen" -#, fuzzy msgid "Delete subvolume" -msgstr "Usuń użytkownika" +msgstr "Usuń podwolumen" msgid "Configured {} interfaces" -msgstr "" +msgstr "Skonfigurowano {} interfejsów" msgid "This option enables the number of parallel downloads that can occur during installation" -msgstr "" +msgstr "Ta opcja pozwala określić maksymalną liczbę pobieranych plików podczas instalacji" -#, python-brace-format +#, fuzzy, python-brace-format msgid "" "Enter the number of parallel downloads to be enabled.\n" " (Enter a value between 1 to {max_downloads})\n" "Note:" msgstr "" +"Wprowadź maksymalną liczbę plików pobieranych jednocześnie.\n" +" (Liczba z zakresu od 1 do {max_downloads})\n" +"Zauważ:" msgid " - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )" -msgstr "" +msgstr " - Maksymalna wartość : {max_downloads} ( Zwiększa liczbę zadań o {max_downloads}, co pozwala na pobieranie {max_downloads+1} plików jednocześnie )" msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" -msgstr "" +msgstr " - Minimalna wartość : 1 ( Zwiększa liczbę zadań o 1, co pozwala na pobieranie 2 plików jednocześnie )" msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" -msgstr "" +msgstr " - Wyłącz/domyślnie : 0 ( Wyłącza pobieranie równoległe, więc tylko 1 plik może być pobierany w tym czasie )" -#, python-brace-format +#, fuzzy, python-brace-format msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" -msgstr "" +msgstr "Nieprawidłowa wartość! Spróbuj wprowadzić wartość od 1 do {max_downloads}, lub 0 aby wyłączyć." msgid "Parallel Downloads" -msgstr "" +msgstr "Pobieranie równoległe" -#, fuzzy msgid "ESC to skip" -msgstr "Kliknij ESC aby pominąć" +msgstr "Naciśnij ESC, aby pominąć" msgid "CTRL+C to reset" -msgstr "" +msgstr "Naciśnij Ctrl+C, aby zresetować" +#, fuzzy msgid "TAB to select" -msgstr "" +msgstr "Naciśnij Tab, aby wybrać" #~ msgid "Select disk layout" #~ msgstr "Wybierz układ dysku" -- cgit v1.2.3-70-g09d2 From c3862c5779194f5e93f9fd2518bb15706c93ad2b Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Fri, 11 Nov 2022 19:40:05 +1100 Subject: New encryption menu (#1520) * New encryption menu Co-authored-by: Daniel Girtler Co-authored-by: Anton Hvornum --- .github/workflows/mypy.yaml | 6 +- archinstall/__init__.py | 8 +- archinstall/lib/configuration.py | 14 +- archinstall/lib/disk/encryption.py | 162 +++++++ archinstall/lib/disk/filesystem.py | 23 +- archinstall/lib/disk/helpers.py | 6 - archinstall/lib/disk/partition.py | 1 - archinstall/lib/hsm/__init__.py | 5 +- archinstall/lib/hsm/fido.py | 137 +++--- archinstall/lib/installer.py | 36 +- archinstall/lib/menu/abstract_menu.py | 493 ++++++++++++++++++++ archinstall/lib/menu/global_menu.py | 94 ++-- archinstall/lib/menu/list_manager.py | 2 +- archinstall/lib/menu/menu.py | 43 +- archinstall/lib/menu/selection_menu.py | 497 --------------------- archinstall/lib/menu/table_selection_menu.py | 107 +++++ archinstall/lib/models/disk_encryption.py | 43 ++ archinstall/lib/user_interaction/__init__.py | 2 +- archinstall/lib/user_interaction/disk_conf.py | 10 +- archinstall/lib/user_interaction/general_conf.py | 26 +- archinstall/lib/user_interaction/locale_conf.py | 4 +- archinstall/lib/user_interaction/network_conf.py | 10 +- .../lib/user_interaction/partitioning_conf.py | 42 +- archinstall/lib/user_interaction/save_conf.py | 2 +- archinstall/lib/user_interaction/system_conf.py | 22 +- archinstall/locales/ar/LC_MESSAGES/base.po | 2 +- archinstall/locales/base.pot | 2 +- archinstall/locales/cs/LC_MESSAGES/base.po | 4 +- archinstall/locales/de/LC_MESSAGES/base.po | 7 +- archinstall/locales/el/LC_MESSAGES/base.po | 2 +- archinstall/locales/en/LC_MESSAGES/base.po | 2 +- archinstall/locales/es/LC_MESSAGES/base.po | 4 +- archinstall/locales/fr/LC_MESSAGES/base.po | 7 +- archinstall/locales/id/LC_MESSAGES/base.po | 4 +- archinstall/locales/it/LC_MESSAGES/base.po | 4 +- archinstall/locales/nl/LC_MESSAGES/base.po | 7 +- archinstall/locales/pl/LC_MESSAGES/base.po | 8 +- archinstall/locales/pt/LC_MESSAGES/base.po | 7 +- archinstall/locales/pt_BR/LC_MESSAGES/base.po | 4 +- archinstall/locales/ru/LC_MESSAGES/base.po | 4 +- archinstall/locales/sv/LC_MESSAGES/base.po | 7 +- archinstall/locales/ta/LC_MESSAGES/base.po | 4 +- archinstall/locales/tr/LC_MESSAGES/base.po | 7 +- archinstall/locales/ur/LC_MESSAGES/base.po | 7 +- examples/guided.py | 4 +- examples/only_hd.py | 2 +- examples/swiss.py | 6 +- 47 files changed, 1087 insertions(+), 813 deletions(-) create mode 100644 archinstall/lib/disk/encryption.py create mode 100644 archinstall/lib/menu/abstract_menu.py delete mode 100644 archinstall/lib/menu/selection_menu.py create mode 100644 archinstall/lib/menu/table_selection_menu.py create mode 100644 archinstall/lib/models/disk_encryption.py (limited to 'archinstall/locales/pl') diff --git a/.github/workflows/mypy.yaml b/.github/workflows/mypy.yaml index 6a4a9860..20c98f3b 100644 --- a/.github/workflows/mypy.yaml +++ b/.github/workflows/mypy.yaml @@ -15,4 +15,8 @@ jobs: # one day this will be enabled # run: mypy --strict --module archinstall || exit 0 - name: run mypy - run: mypy --follow-imports=silent archinstall/lib/menu/selection_menu.py archinstall/lib/menu/global_menu.py archinstall/lib/models/network_configuration.py archinstall/lib/menu/list_manager.py archinstall/lib/user_interaction/network_conf.py archinstall/lib/models/users.py archinstall/lib/disk/blockdevice.py archinstall/lib/user_interaction/subvolume_config.py archinstall/lib/disk/btrfs/btrfs_helpers.py archinstall/lib/translationhandler.py archinstall/lib/disk/diskinfo.py + run: mypy --follow-imports=silent archinstall/lib/menu/abstract_menu.py archinstall/lib/menu/global_menu.py + archinstall/lib/models/network_configuration.py archinstall/lib/menu/list_manager.py archinstall/lib/user_interaction/network_conf.py archinstall/lib/models/users.py + archinstall/lib/disk/blockdevice.py archinstall/lib/user_interaction/subvolume_config.py archinstall/lib/disk/btrfs/btrfs_helpers.py + archinstall/lib/translationhandler.py archinstall/lib/disk/diskinfo.py archinstall/lib/menu/table_selection_menu.py archinstall/lib/hsm + archinstall/lib/disk/encryption.py archinstall/lib/models/disk_encryption.py diff --git a/archinstall/__init__.py b/archinstall/__init__.py index c6135e74..e496d213 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -37,18 +37,14 @@ from .lib.menu import Menu from .lib.menu.list_manager import ListManager from .lib.menu.text_input import TextInput from .lib.menu.global_menu import GlobalMenu -from .lib.menu.selection_menu import ( +from .lib.menu.abstract_menu import ( Selector, - GeneralMenu + AbstractMenu ) from .lib.translationhandler import TranslationHandler, DeferredTranslation from .lib.plugins import plugins, load_plugin # This initiates the plugin loading ceremony from .lib.configuration import * from .lib.udev import udevadm_info -from .lib.hsm import ( - get_fido2_devices, - fido2_enroll -) parser = ArgumentParser() __version__ = "2.5.2" diff --git a/archinstall/lib/configuration.py b/archinstall/lib/configuration.py index 2a43174d..ce782f6c 100644 --- a/archinstall/lib/configuration.py +++ b/archinstall/lib/configuration.py @@ -5,15 +5,18 @@ import logging import pathlib from typing import Optional, Dict +from .hsm.fido import Fido2 +from .models.disk_encryption import DiskEncryption from .storage import storage from .general import JSON, UNSAFE_JSON from .output import log from .exceptions import RequirementError -from .hsm import get_fido2_devices + def configuration_sanity_check(): - if storage['arguments'].get('HSM'): - if not get_fido2_devices(): + disk_encryption: DiskEncryption = storage['arguments'].get('disk_encryption') + if disk_encryption.hsm_device: + if not Fido2.get_fido2_devices(): raise RequirementError( f"In order to use HSM to pair with the disk encryption," + f" one needs to be accessible through /dev/hidraw* and support" @@ -21,6 +24,7 @@ def configuration_sanity_check(): + f" 'systemd-cryptenroll --fido2-device=list'." ) + class ConfigurationOutput: def __init__(self, config: Dict): """ @@ -39,8 +43,8 @@ class ConfigurationOutput: self._user_creds_file = "user_credentials.json" self._disk_layout_file = "user_disk_layout.json" - self._sensitive = ['!users', '!encryption-password'] - self._ignore = ['abort', 'install', 'config', 'creds', 'dry_run'] + self._sensitive = ['!users'] + self._ignore = ['abort', 'install', 'config', 'creds', 'dry_run', 'disk_encryption'] self._process_config() diff --git a/archinstall/lib/disk/encryption.py b/archinstall/lib/disk/encryption.py new file mode 100644 index 00000000..67f656c8 --- /dev/null +++ b/archinstall/lib/disk/encryption.py @@ -0,0 +1,162 @@ +from typing import Dict, Optional, Any, TYPE_CHECKING, List + +from ..menu.abstract_menu import Selector, AbstractSubMenu +from ..menu.menu import MenuSelectionType +from ..menu.table_selection_menu import TableMenu +from ..models.disk_encryption import EncryptionType, DiskEncryption +from ..user_interaction.partitioning_conf import current_partition_layout +from ..user_interaction.utils import get_password +from ..menu import Menu +from ..general import secret +from ..hsm.fido import Fido2Device, Fido2 + +if TYPE_CHECKING: + _: Any + + +class DiskEncryptionMenu(AbstractSubMenu): + def __init__(self, data_store: Dict[str, Any], preset: Optional[DiskEncryption], disk_layouts: Dict[str, Any]): + if preset: + self._preset = preset + else: + self._preset = DiskEncryption() + + self._disk_layouts = disk_layouts + super().__init__(data_store=data_store) + + def _setup_selection_menu_options(self): + self._menu_options['encryption_password'] = \ + Selector( + _('Encryption password'), + lambda x: select_encrypted_password(), + display_func=lambda x: secret(x) if x else '', + default=self._preset.encryption_password, + enabled=True + ) + self._menu_options['encryption_type'] = \ + Selector( + _('Encryption type'), + func=lambda preset: select_encryption_type(preset), + display_func=lambda x: EncryptionType.type_to_text(x) if x else None, + dependencies=['encryption_password'], + default=self._preset.encryption_type, + enabled=True + ) + self._menu_options['partitions'] = \ + Selector( + _('Partitions'), + func=lambda preset: select_partitions_to_encrypt(self._disk_layouts, preset), + display_func=lambda x: f'{len(x)} {_("Partitions")}' if x else None, + dependencies=['encryption_password'], + default=self._preset.partitions, + preview_func=self._prev_disk_layouts, + enabled=True + ) + self._menu_options['HSM'] = \ + Selector( + description=_('Use HSM to unlock encrypted drive'), + func=lambda preset: select_hsm(preset), + display_func=lambda x: self._display_hsm(x), + dependencies=['encryption_password'], + default=self._preset.hsm_device, + enabled=True + ) + + def run(self, allow_reset: bool = True) -> Optional[DiskEncryption]: + super().run(allow_reset=allow_reset) + + if self._data_store.get('encryption_password', None): + return DiskEncryption( + encryption_password=self._data_store.get('encryption_password', None), + encryption_type=self._data_store['encryption_type'], + partitions=self._data_store.get('partitions', None), + hsm_device=self._data_store.get('HSM', None) + ) + + return None + + def _display_hsm(self, device: Optional[Fido2Device]) -> Optional[str]: + if device: + return device.manufacturer + + if not Fido2.get_fido2_devices(): + return str(_('No HSM devices available')) + return None + + def _prev_disk_layouts(self) -> Optional[str]: + selector = self._menu_options['partitions'] + if selector.has_selection(): + partitions: List[Any] = selector.current_selection + output = str(_('Partitions to be encrypted')) + '\n' + output += current_partition_layout(partitions, with_title=False) + return output.rstrip() + return None + + +def select_encryption_type(preset: EncryptionType) -> Optional[EncryptionType]: + title = str(_('Select disk encryption option')) + options = [ + # _type_to_text(EncryptionType.FullDiskEncryption), + EncryptionType.type_to_text(EncryptionType.Partition) + ] + + preset_value = EncryptionType.type_to_text(preset) + choice = Menu(title, options, preset_values=preset_value).run() + + match choice.type_: + case MenuSelectionType.Reset: return None + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Selection: return EncryptionType.text_to_type(choice.value) # type: ignore + + +def select_encrypted_password() -> Optional[str]: + if passwd := get_password(prompt=str(_('Enter disk encryption password (leave blank for no encryption): '))): + return passwd + return None + + +def select_hsm(preset: Optional[Fido2Device] = None) -> Optional[Fido2Device]: + title = _('Select a FIDO2 device to use for HSM') + fido_devices = Fido2.get_fido2_devices() + + if fido_devices: + choice = TableMenu(title, data=fido_devices).run() + match choice.type_: + case MenuSelectionType.Reset: + return None + case MenuSelectionType.Skip: + return preset + case MenuSelectionType.Selection: + return choice.value # type: ignore + + return None + + +def select_partitions_to_encrypt(disk_layouts: Dict[str, Any], preset: List[Any]) -> List[Any]: + # If no partitions was marked as encrypted, but a password was supplied and we have some disks to format.. + # Then we need to identify which partitions to encrypt. This will default to / (root). + all_partitions = [] + for blockdevice in disk_layouts.values(): + if partitions := blockdevice.get('partitions'): + partitions = [p for p in partitions if p['mountpoint'] != '/boot'] + all_partitions += partitions + + if all_partitions: + title = str(_('Select which partitions to encrypt')) + partition_table = current_partition_layout(all_partitions, with_title=False).strip() + + choice = TableMenu( + title, + table_data=(all_partitions, partition_table), + multi=True + ).run() + + match choice.type_: + case MenuSelectionType.Reset: + return [] + case MenuSelectionType.Skip: + return preset + case MenuSelectionType.Selection: + return choice.value # type: ignore + + return [] diff --git a/archinstall/lib/disk/filesystem.py b/archinstall/lib/disk/filesystem.py index af5879aa..bdfa502a 100644 --- a/archinstall/lib/disk/filesystem.py +++ b/archinstall/lib/disk/filesystem.py @@ -5,6 +5,8 @@ import json import pathlib from typing import Optional, Dict, Any, TYPE_CHECKING # https://stackoverflow.com/a/39757388/929999 +from ..models.disk_encryption import DiskEncryption + if TYPE_CHECKING: from .blockdevice import BlockDevice _: Any @@ -107,33 +109,22 @@ class Filesystem: continue if partition.get('filesystem', {}).get('format', False): - # needed for backward compatibility with the introduction of the new "format_options" format_options = partition.get('options',[]) + partition.get('filesystem',{}).get('format_options',[]) - if partition.get('encrypted', False): + disk_encryption: DiskEncryption = storage['arguments'].get('disk_encryption') + + if partition in disk_encryption.partitions: if not partition['device_instance']: raise DiskError(f"Internal error caused us to loose the partition. Please report this issue upstream!") - if not partition.get('!password'): - if not storage['arguments'].get('!encryption-password'): - if storage['arguments'] == 'silent': - raise ValueError(f"Missing encryption password for {partition['device_instance']}") - - from ..user_interaction import get_password - - prompt = str(_('Enter a encryption password for {}').format(partition['device_instance'])) - storage['arguments']['!encryption-password'] = get_password(prompt) - - partition['!password'] = storage['arguments']['!encryption-password'] - if partition.get('mountpoint',None): loopdev = f"{storage.get('ENC_IDENTIFIER', 'ai')}{pathlib.Path(partition['mountpoint']).name}loop" else: loopdev = f"{storage.get('ENC_IDENTIFIER', 'ai')}{pathlib.Path(partition['device_instance'].path).name}" - partition['device_instance'].encrypt(password=partition['!password']) + partition['device_instance'].encrypt(password=disk_encryption.encryption_password) # Immediately unlock the encrypted device to format the inner volume - with luks2(partition['device_instance'], loopdev, partition['!password'], auto_unmount=True) as unlocked_device: + with luks2(partition['device_instance'], loopdev, disk_encryption.encryption_password, auto_unmount=True) as unlocked_device: if not partition.get('wipe'): if storage['arguments'] == 'silent': raise ValueError(f"Missing fs-type to format on newly created encrypted partition {partition['device_instance']}") diff --git a/archinstall/lib/disk/helpers.py b/archinstall/lib/disk/helpers.py index 256f7abb..a5164b76 100644 --- a/archinstall/lib/disk/helpers.py +++ b/archinstall/lib/disk/helpers.py @@ -469,12 +469,6 @@ def disk_layouts() -> Optional[Dict[str, Any]]: return None -def encrypted_partitions(blockdevices :Dict[str, Any]) -> bool: - for blockdevice in blockdevices.values(): - for partition in blockdevice.get('partitions', []): - if partition.get('encrypted', False): - yield partition - def find_partition_by_mountpoint(block_devices :List[BlockDevice], relative_mountpoint :str) -> Partition: for device in block_devices: for partition in block_devices[device]['partitions']: diff --git a/archinstall/lib/disk/partition.py b/archinstall/lib/disk/partition.py index 04d33453..9febf102 100644 --- a/archinstall/lib/disk/partition.py +++ b/archinstall/lib/disk/partition.py @@ -91,7 +91,6 @@ class Partition: self._path = path self._part_id = part_id self._target_mountpoint = mountpoint - self._encrypted = None self._encrypted = encrypted self._wipe = False self._type = 'primary' diff --git a/archinstall/lib/hsm/__init__.py b/archinstall/lib/hsm/__init__.py index c0888b04..a3f64019 100644 --- a/archinstall/lib/hsm/__init__.py +++ b/archinstall/lib/hsm/__init__.py @@ -1,4 +1 @@ -from .fido import ( - get_fido2_devices, - fido2_enroll -) \ No newline at end of file +from .fido import Fido2 diff --git a/archinstall/lib/hsm/fido.py b/archinstall/lib/hsm/fido.py index 49f36957..4cd956a3 100644 --- a/archinstall/lib/hsm/fido.py +++ b/archinstall/lib/hsm/fido.py @@ -1,57 +1,92 @@ -import typing -import pathlib import getpass import logging + +from dataclasses import dataclass +from pathlib import Path +from typing import List + from ..general import SysCommand, SysCommandWorker, clear_vt100_escape_codes from ..disk.partition import Partition from ..general import log -def get_fido2_devices() -> typing.Dict[str, typing.Dict[str, str]]: - """ - Uses systemd-cryptenroll to list the FIDO2 devices - connected that supports FIDO2. - Some devices might show up in udevadm as FIDO2 compliant - when they are in fact not. - - The drawback of systemd-cryptenroll is that it uses human readable format. - That means we get this weird table like structure that is of no use. - - So we'll look for `MANUFACTURER` and `PRODUCT`, we take their index - and we split each line based on those positions. - """ - worker = clear_vt100_escape_codes(SysCommand(f"systemd-cryptenroll --fido2-device=list").decode('UTF-8')) - - MANUFACTURER_POS = 0 - PRODUCT_POS = 0 - devices = {} - for line in worker.split('\r\n'): - if '/dev' not in line: - MANUFACTURER_POS = line.find('MANUFACTURER') - PRODUCT_POS = line.find('PRODUCT') - continue - - path = line[:MANUFACTURER_POS].rstrip() - manufacturer = line[MANUFACTURER_POS:PRODUCT_POS].rstrip() - product = line[PRODUCT_POS:] - - devices[path] = { - 'manufacturer' : manufacturer, - 'product' : product - } - - return devices - -def fido2_enroll(hsm_device_path :pathlib.Path, partition :Partition, password :str) -> bool: - worker = SysCommandWorker(f"systemd-cryptenroll --fido2-device={hsm_device_path} {partition.real_device}", peak_output=True) - pw_inputted = False - pin_inputted = False - while worker.is_alive(): - if pw_inputted is False and bytes(f"please enter current passphrase for disk {partition.real_device}", 'UTF-8') in worker._trace_log.lower(): - worker.write(bytes(password, 'UTF-8')) - pw_inputted = True - - elif pin_inputted is False and bytes(f"please enter security token pin", 'UTF-8') in worker._trace_log.lower(): - worker.write(bytes(getpass.getpass(" "), 'UTF-8')) - pin_inputted = True - - log(f"You might need to touch the FIDO2 device to unlock it if no prompt comes up after 3 seconds.", level=logging.INFO, fg="yellow") \ No newline at end of file + +@dataclass +class Fido2Device: + path: Path + manufacturer: str + product: str + + +class Fido2: + _loaded: bool = False + _fido2_devices: List[Fido2Device] = [] + + @classmethod + def get_fido2_devices(cls, reload: bool = False) -> List[Fido2Device]: + """ + Uses systemd-cryptenroll to list the FIDO2 devices + connected that supports FIDO2. + Some devices might show up in udevadm as FIDO2 compliant + when they are in fact not. + + The drawback of systemd-cryptenroll is that it uses human readable format. + That means we get this weird table like structure that is of no use. + + So we'll look for `MANUFACTURER` and `PRODUCT`, we take their index + and we split each line based on those positions. + + Output example: + + PATH MANUFACTURER PRODUCT + /dev/hidraw1 Yubico YubiKey OTP+FIDO+CCID + """ + + # to prevent continous reloading which will slow + # down moving the cursor in the menu + if not cls._loaded or reload: + ret = SysCommand(f"systemd-cryptenroll --fido2-device=list").decode('UTF-8') + if not ret: + log('Unable to retrieve fido2 devices', level=logging.ERROR) + return [] + + fido_devices = clear_vt100_escape_codes(ret) + + manufacturer_pos = 0 + product_pos = 0 + devices = [] + + for line in fido_devices.split('\r\n'): + if '/dev' not in line: + manufacturer_pos = line.find('MANUFACTURER') + product_pos = line.find('PRODUCT') + continue + + path = line[:manufacturer_pos].rstrip() + manufacturer = line[manufacturer_pos:product_pos].rstrip() + product = line[product_pos:] + + devices.append( + Fido2Device(path, manufacturer, product) + ) + + cls._loaded = True + cls._fido2_devices = devices + + return cls._fido2_devices + + @classmethod + def fido2_enroll(cls, hsm_device: Fido2Device, partition :Partition, password :str): + worker = SysCommandWorker(f"systemd-cryptenroll --fido2-device={hsm_device.path} {partition.real_device}", peak_output=True) + pw_inputted = False + pin_inputted = False + + while worker.is_alive(): + if pw_inputted is False and bytes(f"please enter current passphrase for disk {partition.real_device}", 'UTF-8') in worker._trace_log.lower(): + worker.write(bytes(password, 'UTF-8')) + pw_inputted = True + + elif pin_inputted is False and bytes(f"please enter security token pin", 'UTF-8') in worker._trace_log.lower(): + worker.write(bytes(getpass.getpass(" "), 'UTF-8')) + pin_inputted = True + + log(f"You might need to touch the FIDO2 device to unlock it if no prompt comes up after 3 seconds.", level=logging.INFO, fg="yellow") diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 18f6e244..1926f593 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -15,16 +15,16 @@ from .hardware import has_uefi, is_vm, cpu_vendor from .locale_helpers import verify_keyboard_layout, verify_x11_keyboard_layout from .disk.helpers import findmnt from .mirrors import use_mirrors +from .models.disk_encryption import DiskEncryption from .plugins import plugins from .storage import storage -# from .user_interaction import * from .output import log from .profiles import Profile from .disk.partition import get_mount_fs_type from .exceptions import DiskError, ServiceException, RequirementError, HardwareIncompatibilityError, SysCallError -from .hsm import fido2_enroll from .models.users import User from .models.subvolume import Subvolume +from .hsm import Fido2 if TYPE_CHECKING: _: Any @@ -135,6 +135,8 @@ class Installer: self._zram_enabled = False + self._disk_encryption: DiskEncryption = storage['arguments'].get('disk_encryption') + def log(self, *args :str, level :int = logging.DEBUG, **kwargs :str): """ installer.log() wraps output.log() mainly to set a default log-level for this install session. @@ -196,7 +198,7 @@ class Installer: def _create_keyfile(self,luks_handle , partition :dict, password :str): """ roiutine to create keyfiles, so it can be moved elsewhere """ - if partition.get('generate-encryption-key-file'): + if self._disk_encryption.generate_encryption_file(partition): if not (cryptkey_dir := pathlib.Path(f"{self.target}/etc/cryptsetup-keys.d")).exists(): cryptkey_dir.mkdir(parents=True) # Once we store the key as ../xyzloop.key systemd-cryptsetup can automatically load this key @@ -244,26 +246,20 @@ class Installer: mount_queue = {} # we manage the encrypted partititons - for partition in [entry for entry in list_part if entry.get('encrypted', False)]: + for partition in self._disk_encryption.partitions: # open the luks device and all associate stuff - if not (password := partition.get('!password', None)) and storage['arguments'].get('!encryption-password'): - password = storage['arguments'].get('!encryption-password') - elif not password: - raise RequirementError(f"Missing partition encryption password in layout: {partition}") - loopdev = f"{storage.get('ENC_IDENTIFIER', 'ai')}{pathlib.Path(partition['device_instance'].path).name}" # note that we DON'T auto_unmount (i.e. close the encrypted device so it can be used - with (luks_handle := luks2(partition['device_instance'], loopdev, password, auto_unmount=False)) as unlocked_device: - if partition.get('generate-encryption-key-file', False) and not self._has_root(partition): - list_luks_handles.append([luks_handle, partition, password]) + with (luks_handle := luks2(partition['device_instance'], loopdev, self._disk_encryption.encryption_password, auto_unmount=False)) as unlocked_device: + if self._disk_encryption.generate_encryption_file(partition) and not self._has_root(partition): + list_luks_handles.append([luks_handle, partition, self._disk_encryption.encryption_password]) # this way all the requesrs will be to the dm_crypt device and not to the physical partition partition['device_instance'] = unlocked_device - if self._has_root(partition) and partition.get('generate-encryption-key-file', False) is False: - if storage['arguments'].get('HSM'): - hsm_device_path = storage['arguments']['HSM'] - fido2_enroll(hsm_device_path, partition['device_instance'], password) + if self._has_root(partition) and self._disk_encryption.generate_encryption_file(partition) is False: + if self._disk_encryption.hsm_device: + Fido2.fido2_enroll(self._disk_encryption.hsm_device, partition['device_instance'], self._disk_encryption.encryption_password) btrfs_subvolumes = [entry for entry in list_part if entry.get('btrfs', {}).get('subvolumes', [])] @@ -650,7 +646,7 @@ class Installer: mkinit.write(f"BINARIES=({' '.join(self.BINARIES)})\n") mkinit.write(f"FILES=({' '.join(self.FILES)})\n") - if not storage['arguments'].get('HSM'): + if not self._disk_encryption.hsm_device: # For now, if we don't use HSM we revert to the old # way of setting up encryption hooks for mkinitcpio. # This is purely for stability reasons, we're going away from this. @@ -694,7 +690,7 @@ class Installer: self.HOOKS.remove('fsck') if self.detect_encryption(partition): - if storage['arguments'].get('HSM'): + if self._disk_encryption.hsm_device: # Required bby mkinitcpio to add support for fido2-device options self.pacstrap('libfido2') @@ -758,7 +754,7 @@ class Installer: # TODO: Use python functions for this SysCommand(f'/usr/bin/arch-chroot {self.target} chmod 700 /root') - if storage['arguments'].get('HSM'): + if self._disk_encryption.hsm_device: # TODO: # A bit of a hack, but we need to get vconsole.conf in there # before running `mkinitcpio` because it expects it in HSM mode. @@ -886,7 +882,7 @@ class Installer: kernel_options = f"options" - if storage['arguments'].get('HSM'): + if self._disk_encryption.hsm_device: # Note: lsblk UUID must be used, not PARTUUID for sd-encrypt to work kernel_options += f" rd.luks.name={real_device.uuid}=luksdev" # Note: tpm2-device and fido2-device don't play along very well: diff --git a/archinstall/lib/menu/abstract_menu.py b/archinstall/lib/menu/abstract_menu.py new file mode 100644 index 00000000..61466e3e --- /dev/null +++ b/archinstall/lib/menu/abstract_menu.py @@ -0,0 +1,493 @@ +from __future__ import annotations + +import logging +import sys +from typing import Callable, Any, List, Iterator, Tuple, Optional, Dict, TYPE_CHECKING + +from .menu import Menu, MenuSelectionType +from ..locale_helpers import set_keyboard_language +from ..output import log +from ..translationhandler import TranslationHandler, Language +from ..user_interaction.general_conf import select_archinstall_language + +if TYPE_CHECKING: + _: Any + + +class Selector: + def __init__( + self, + description :str, + func :Callable = None, + display_func :Callable = None, + default :Any = None, + enabled :bool = False, + dependencies :List = [], + dependencies_not :List = [], + exec_func :Callable = None, + preview_func :Callable = None, + mandatory :bool = False, + no_store :bool = False + ): + """ + Create a new menu selection entry + + :param description: Text that will be displayed as the menu entry + :type description: str + + :param func: Function that is called when the menu entry is selected + :type func: Callable + + :param display_func: After specifying a setting for a menu item it is displayed + on the right side of the item as is; with this function one can modify the entry + to be displayed; e.g. when specifying a password one can display **** instead + :type display_func: Callable + + :param default: Default value for this menu entry + :type default: Any + + :param enabled: Specify if this menu entry should be displayed + :type enabled: bool + + :param dependencies: Specify dependencies for this menu entry; if the dependencies + are not set yet, then this item is not displayed; e.g. disk_layout depends on selectiong + harddrive(s) first + :type dependencies: list + + :param dependencies_not: These are the exclusive options; the menu item will only be + displayed if non of the entries in the list have been specified + :type dependencies_not: list + + :param exec_func: A function with the name and the result of the selection as input parameter and which returns boolean. + Can be used for any action deemed necessary after selection. If it returns True, exits the menu loop, if False, + menu returns to the selection screen. If not specified it is assumed the return is False + :type exec_func: Callable + + :param preview_func: A callable which invokws a preview screen + :type preview_func: Callable + + :param mandatory: A boolean which determines that the field is mandatory, i.e. menu can not be exited if it is not set + :type mandatory: bool + + :param no_store: A boolean which determines that the field should or shouldn't be stored in the data storage + :type no_store: bool + """ + self._description = description + self.func = func + self._display_func = display_func + self._current_selection = default + self.enabled = enabled + self._dependencies = dependencies + self._dependencies_not = dependencies_not + self.exec_func = exec_func + self._preview_func = preview_func + self.mandatory = mandatory + self._no_store = no_store + + @property + def description(self) -> str: + return self._description + + @property + def dependencies(self) -> List: + return self._dependencies + + @property + def dependencies_not(self) -> List: + return self._dependencies_not + + @property + def current_selection(self): + return self._current_selection + + @property + def preview_func(self): + return self._preview_func + + def do_store(self) -> bool: + return self._no_store is False + + def set_enabled(self, status :bool = True): + self.enabled = status + + def update_description(self, description :str): + self._description = description + + def menu_text(self, padding: int = 0) -> str: + if self._description == '': # special menu option for __separator__ + return '' + + current = '' + + if self._display_func: + current = self._display_func(self._current_selection) + else: + if self._current_selection is not None: + current = str(self._current_selection) + + if current: + padding += 5 + description = str(self._description).ljust(padding, ' ') + current = str(_('set: {}').format(current)) + else: + description = self._description + current = '' + + return f'{description} {current}' + + def set_current_selection(self, current :Optional[str]): + self._current_selection = current + + def has_selection(self) -> bool: + if not self._current_selection: + return False + return True + + def get_selection(self) -> Any: + return self._current_selection + + def is_empty(self) -> bool: + if self._current_selection is None: + return True + elif isinstance(self._current_selection, (str, list, dict)) and len(self._current_selection) == 0: + return True + return False + + def is_enabled(self) -> bool: + return self.enabled + + def is_mandatory(self) -> bool: + return self.mandatory + + def set_mandatory(self, status :bool = True): + self.mandatory = status + if status and not self.is_enabled(): + self.set_enabled(True) + + +class AbstractMenu: + def __init__(self, data_store: Dict[str, Any] = None, auto_cursor=False, preview_size :float = 0.2): + """ + Create a new selection menu. + + :param data_store: Area (Dict) where the resulting data will be held. At least an entry for each option. Default area is self._data_store (not preset in the call, due to circular references + :type data_store: Dict + + :param auto_cursor: Boolean which determines if the cursor stays on the first item (false) or steps each invocation of a selection entry (true) + :type auto_cursor: bool + + :param preview_size. Size in fractions of screen size of the preview window + ;type preview_size: float (range 0..1) + + """ + self._enabled_order :List[str] = [] + self._translation_handler = TranslationHandler() + self.is_context_mgr = False + self._data_store = data_store if data_store is not None else {} + self.auto_cursor = auto_cursor + self._menu_options: Dict[str, Selector] = {} + self._setup_selection_menu_options() + self.preview_size = preview_size + self._last_choice = None + + @property + def last_choice(self): + return self._last_choice + + def __enter__(self, *args :Any, **kwargs :Any) -> AbstractMenu: + self.is_context_mgr = True + return self + + def __exit__(self, *args :Any, **kwargs :Any) -> None: + # TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager + # TODO: skip processing when it comes from a planified exit + if len(args) >= 2 and args[1]: + log(args[1], level=logging.ERROR, fg='red') + print(" Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues") + raise args[1] + + for key in self._menu_options: + selector = self._menu_options[key] + if key and key not in self._data_store: + self._data_store[key] = selector.current_selection + + self.exit_callback() + + @property + def translation_handler(self) -> TranslationHandler: + return self._translation_handler + + def _setup_selection_menu_options(self): + """ Define the menu options. + Menu options can be defined here in a subclass or done per program calling self.set_option() + """ + return + + def pre_callback(self, selector_name): + """ will be called before each action in the menu """ + return + + def post_callback(self, selection_name: str = None, value: Any = None): + """ will be called after each action in the menu """ + return True + + def exit_callback(self): + """ will be called at the end of the processing of the menu """ + return + + def synch(self, selector_name :str, omit_if_set :bool = False,omit_if_disabled :bool = False): + """ loads menu options with data_store value """ + arg = self._data_store.get(selector_name, None) + # don't display the menu option if it was defined already + if arg is not None and omit_if_set: + return + + if not self.option(selector_name).is_enabled() and omit_if_disabled: + return + + if arg is not None: + self._menu_options[selector_name].set_current_selection(arg) + + def _update_enabled_order(self, selector_name: str): + self._enabled_order.append(selector_name) + + def enable(self, selector_name :str, omit_if_set :bool = False , mandatory :bool = False): + """ activates menu options """ + if self._menu_options.get(selector_name, None): + self._menu_options[selector_name].set_enabled(True) + self._update_enabled_order(selector_name) + + if mandatory: + self._menu_options[selector_name].set_mandatory(True) + self.synch(selector_name,omit_if_set) + else: + print(f'No selector found: {selector_name}') + sys.exit(1) + + def _preview_display(self, selection_name: str) -> Optional[str]: + config_name, selector = self._find_selection(selection_name) + if preview := selector.preview_func: + return preview() + return None + + def _get_menu_text_padding(self, entries: List[Selector]): + return max([len(str(selection.description)) for selection in entries]) + + def _find_selection(self, selection_name: str) -> Tuple[str, Selector]: + enabled_menus = self._menus_to_enable() + padding = self._get_menu_text_padding(list(enabled_menus.values())) + option = [(k, v) for k, v in self._menu_options.items() if v.menu_text(padding).strip() == selection_name.strip()] + + if len(option) != 1: + raise ValueError(f'Selection not found: {selection_name}') + config_name = option[0][0] + selector = option[0][1] + return config_name, selector + + def run(self, allow_reset: bool = False): + """ Calls the Menu framework""" + # we synch all the options just in case + for item in self.list_options(): + self.synch(item) + + self.post_callback() # as all the values can vary i have to exec this callback + cursor_pos = None + + while True: + # Before continuing, set the preferred keyboard layout/language in the current terminal. + # This will just help the user with the next following questions. + self._set_kb_language() + enabled_menus = self._menus_to_enable() + + padding = self._get_menu_text_padding(list(enabled_menus.values())) + menu_options = [m.menu_text(padding) for m in enabled_menus.values()] + + warning_msg = str(_('All settings will be reset, are you sure?')) + + selection = Menu( + _('Set/Modify the below options'), + menu_options, + sort=False, + cursor_index=cursor_pos, + preview_command=self._preview_display, + preview_size=self.preview_size, + skip_empty_entries=True, + skip=False, + allow_reset=allow_reset, + allow_reset_warning_msg=warning_msg + ).run() + + match selection.type_: + case MenuSelectionType.Reset: + self._data_store = {} + return + case MenuSelectionType.Selection: + value: str = selection.value # type: ignore + + if self.auto_cursor: + cursor_pos = menu_options.index(value) + 1 # before the strip otherwise fails + + # in case the new position lands on a "placeholder" we'll skip them as well + while True: + if cursor_pos >= len(menu_options): + cursor_pos = 0 + if len(menu_options[cursor_pos]) > 0: + break + cursor_pos += 1 + + value = value.strip() + + # if this calls returns false, we exit the menu + # we allow for an callback for special processing on realeasing control + if not self._process_selection(value): + break + + # we get the last action key + actions = {str(v.description):k for k,v in self._menu_options.items()} + self._last_choice = actions[selection.value.strip()] # type: ignore + + if not self.is_context_mgr: + self.__exit__() + + def _process_selection(self, selection_name :str) -> bool: + """ determines and executes the selection y + Can / Should be extended to handle specific selection issues + Returns true if the menu shall continue, False if it has ended + """ + # find the selected option in our option list + config_name, selector = self._find_selection(selection_name) + return self.exec_option(config_name, selector) + + def exec_option(self, config_name :str, p_selector :Selector = None) -> bool: + """ processes the execution of a given menu entry + - pre process callback + - selection function + - post process callback + - exec action + returns True if the loop has to continue, false if the loop can be closed + """ + if not p_selector: + selector = self.option(config_name) + else: + selector = p_selector + + self.pre_callback(config_name) + + result = None + if selector.func: + presel_val = self.option(config_name).get_selection() + result = selector.func(presel_val) + self._menu_options[config_name].set_current_selection(result) + if selector.do_store(): + self._data_store[config_name] = result + exec_ret_val = selector.exec_func(config_name,result) if selector.exec_func else False + self.post_callback(config_name,result) + + if exec_ret_val and self._check_mandatory_status(): + return False + return True + + def _set_kb_language(self): + """ general for ArchInstall""" + # Before continuing, set the preferred keyboard layout/language in the current terminal. + # This will just help the user with the next following questions. + if self._data_store.get('keyboard-layout', None) and len(self._data_store['keyboard-layout']): + set_keyboard_language(self._data_store['keyboard-layout']) + + def _verify_selection_enabled(self, selection_name :str) -> bool: + """ general """ + if selection := self._menu_options.get(selection_name, None): + if not selection.enabled: + return False + + if len(selection.dependencies) > 0: + for d in selection.dependencies: + if not self._verify_selection_enabled(d) or self._menu_options[d].is_empty(): + return False + + if len(selection.dependencies_not) > 0: + for d in selection.dependencies_not: + if not self._menu_options[d].is_empty(): + return False + return True + + raise ValueError(f'No selection found: {selection_name}') + + def _menus_to_enable(self) -> dict: + """ general """ + enabled_menus = {} + + for name, selection in self._menu_options.items(): + if self._verify_selection_enabled(name): + enabled_menus[name] = selection + + # sort the enabled menu by the order we enabled them in + # we'll add the entries that have been enabled via the selector constructor at the top + enabled_keys = [i for i in enabled_menus.keys() if i not in self._enabled_order] + # and then we add the ones explicitly enabled by the enable function + enabled_keys += [i for i in self._enabled_order if i in enabled_menus.keys()] + + ordered_menus = {k: enabled_menus[k] for k in enabled_keys} + + return ordered_menus + + def option(self,name :str) -> Selector: + # TODO check inexistent name + return self._menu_options[name] + + def list_options(self) -> Iterator: + """ Iterator to retrieve the enabled menu option names + """ + for item in self._menu_options: + yield item + + def list_enabled_options(self) -> Iterator: + """ Iterator to retrieve the enabled menu options at a given time. + The results are dynamic (if between calls to the iterator some elements -still not retrieved- are (de)activated + """ + for item in self._menu_options: + if item in self._menus_to_enable(): + yield item + + def set_option(self, name :str, selector :Selector): + self._menu_options[name] = selector + self.synch(name) + + def _check_mandatory_status(self) -> bool: + for field in self._menu_options: + option = self._menu_options[field] + if option.is_mandatory() and not option.has_selection(): + return False + return True + + def set_mandatory(self, field :str, status :bool): + self.option(field).set_mandatory(status) + + def mandatory_overview(self) -> Tuple[int, int]: + mandatory_fields = 0 + mandatory_waiting = 0 + for field, option in self._menu_options.items(): + if option.is_mandatory(): + mandatory_fields += 1 + if not option.has_selection(): + mandatory_waiting += 1 + return mandatory_fields, mandatory_waiting + + def _select_archinstall_language(self, preset_value: Language) -> Language: + language = select_archinstall_language(self.translation_handler.translated_languages, preset_value) + self._translation_handler.activate(language) + return language + + +class AbstractSubMenu(AbstractMenu): + def __init__(self, data_store: Dict[str, Any] = None): + super().__init__(data_store=data_store) + + self._menu_options['__separator__'] = Selector('') + self._menu_options['back'] = \ + Selector( + _('Back'), + no_store=True, + enabled=True, + exec_func=lambda n, v: True, + ) diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index 444ba7ee..0d348227 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -3,12 +3,13 @@ from __future__ import annotations from typing import Any, List, Optional, Union, Dict, TYPE_CHECKING import archinstall -from ..disk import encrypted_partitions +from ..disk.encryption import DiskEncryptionMenu from ..general import SysCommand, secret from ..hardware import has_uefi from ..menu import Menu -from ..menu.selection_menu import Selector, GeneralMenu +from ..menu.abstract_menu import Selector, AbstractMenu from ..models import NetworkConfiguration +from ..models.disk_encryption import DiskEncryption, EncryptionType from ..models.users import User from ..output import FormattedOutput from ..profiles import is_desktop_profile, Profile @@ -25,7 +26,6 @@ from ..user_interaction import ask_to_configure_network from ..user_interaction import get_password, ask_for_a_timezone, save_config from ..user_interaction import select_additional_repositories from ..user_interaction import select_disk_layout -from ..user_interaction import select_encrypted_partitions from ..user_interaction import select_harddrives from ..user_interaction import select_kernel from ..user_interaction import select_language @@ -39,7 +39,7 @@ if TYPE_CHECKING: _: Any -class GlobalMenu(GeneralMenu): +class GlobalMenu(AbstractMenu): def __init__(self,data_store): self._disk_check = True super().__init__(data_store=data_store, auto_cursor=True, preview_size=0.3) @@ -91,18 +91,13 @@ class GlobalMenu(GeneralMenu): preview_func=self._prev_disk_layouts, display_func=lambda x: self._display_disk_layout(x), dependencies=['harddrives']) - self._menu_options['!encryption-password'] = \ + self._menu_options['disk_encryption'] = \ Selector( - _('Encryption password'), - lambda x: self._select_encrypted_password(), - display_func=lambda x: secret(x) if x else 'None', - dependencies=['harddrives']) - self._menu_options['HSM'] = Selector( - description=_('Use HSM to unlock encrypted drive'), - func=lambda preset: self._select_hsm(preset), - dependencies=['!encryption-password'], - default=None - ) + _('Disk encryption'), + lambda preset: self._disk_encryption(preset), + preview_func=self._prev_disk_encryption, + display_func=lambda x: self._display_disk_encryption(x), + dependencies=['disk_layouts']) self._menu_options['swap'] = \ Selector( _('Swap'), @@ -209,28 +204,6 @@ class GlobalMenu(GeneralMenu): def post_callback(self,name :str = None ,result :Any = None): self._update_install_text(name, result) - def exit_callback(self): - if self._data_store.get('harddrives', None) and self._data_store.get('!encryption-password', None): - # If no partitions was marked as encrypted, but a password was supplied and we have some disks to format.. - # Then we need to identify which partitions to encrypt. This will default to / (root). - if len(list(encrypted_partitions(storage['arguments'].get('disk_layouts', [])))) == 0: - for blockdevice in storage['arguments']['disk_layouts']: - if storage['arguments']['disk_layouts'][blockdevice].get('partitions'): - for partition_index in select_encrypted_partitions( - title=_('Select which partitions to encrypt:'), - partitions=storage['arguments']['disk_layouts'][blockdevice]['partitions'], - filter_=(lambda p: p['mountpoint'] != '/boot') - ): - - partition = storage['arguments']['disk_layouts'][blockdevice]['partitions'][partition_index] - partition['encrypted'] = True - partition['!password'] = storage['arguments']['!encryption-password'] - - # We make sure generate-encryption-key-file is set on additional partitions - # other than the root partition. Otherwise they won't unlock properly #1279 - if partition['mountpoint'] != '/': - partition['generate-encryption-key-file'] = True - def _install_text(self): missing = len(self._missing_configs()) if missing > 0: @@ -246,6 +219,20 @@ class GlobalMenu(GeneralMenu): else: return str(cur_value) + def _disk_encryption(self, preset: Optional[DiskEncryption]) -> Optional[DiskEncryption]: + data_store: Dict[str, Any] = {} + + selector = self._menu_options['disk_layouts'] + + if selector.has_selection(): + layouts: Dict[str, Dict[str, Any]] = selector.current_selection + else: + # this should not happen as the encryption menu has the disk layout as dependency + raise ValueError('No disk layout specified') + + disk_encryption = DiskEncryptionMenu(data_store, preset, layouts).run() + return disk_encryption + def _prev_network_config(self) -> Optional[str]: selector = self._menu_options['nic'] if selector.has_selection(): @@ -283,6 +270,30 @@ class GlobalMenu(GeneralMenu): return f'{total_nr} {_("Partitions")}' return '' + def _prev_disk_encryption(self) -> Optional[str]: + selector = self._menu_options['disk_encryption'] + if selector.has_selection(): + encryption: DiskEncryption = selector.current_selection + + enc_type = EncryptionType.type_to_text(encryption.encryption_type) + output = str(_('Encryption type')) + f': {enc_type}\n' + output += str(_('Password')) + f': {secret(encryption.encryption_password)}\n' + + if encryption.partitions: + output += 'Partitions: {} selected'.format(len(encryption.partitions)) + '\n' + + if encryption.hsm_device: + output += f'HSM: {encryption.hsm_device.manufacturer}' + + return output + + return None + + def _display_disk_encryption(self, current_value: Optional[DiskEncryption]) -> str: + if current_value: + return EncryptionType.type_to_text(current_value.encryption_type) + return '' + def _prev_install_missing_config(self) -> Optional[str]: if missing := self._missing_configs(): text = str(_('Missing configurations:\n')) @@ -327,11 +338,10 @@ class GlobalMenu(GeneralMenu): password = get_password(prompt=prompt) return password - def _select_encrypted_password(self) -> Optional[str]: - if passwd := get_password(prompt=str(_('Enter disk encryption password (leave blank for no encryption): '))): - return passwd - else: - return None + # def _select_encrypted_password(self) -> Optional[str]: + # if passwd := get_password(prompt=str(_('Enter disk encryption password (leave blank for no encryption): '))): + # return passwd + # return None def _select_ntp(self, preset :bool = True) -> bool: ntp = ask_ntp(preset) diff --git a/archinstall/lib/menu/list_manager.py b/archinstall/lib/menu/list_manager.py index ae3a6eb5..1e09d987 100644 --- a/archinstall/lib/menu/list_manager.py +++ b/archinstall/lib/menu/list_manager.py @@ -104,7 +104,7 @@ class ListManager: return options, header def _run_actions_on_entry(self, entry: Any): - options = self.filter_options(entry,self._sub_menu_actions) + [self._cancel_action] + options = self.filter_options(entry, self._sub_menu_actions) + [self._cancel_action] display_value = self.selected_action_display(entry) prompt = _("Select an action for '{}'").format(display_value) diff --git a/archinstall/lib/menu/menu.py b/archinstall/lib/menu/menu.py index 773ff1de..09685c55 100644 --- a/archinstall/lib/menu/menu.py +++ b/archinstall/lib/menu/menu.py @@ -18,8 +18,8 @@ if TYPE_CHECKING: class MenuSelectionType(Enum): Selection = auto() - Esc = auto() - Ctrl_c = auto() + Skip = auto() + Reset = auto() @dataclass @@ -56,8 +56,8 @@ class Menu(TerminalMenu): preview_size: float = 0.0, preview_title: str = 'Info', header :Union[List[str],str] = None, - raise_error_on_interrupt :bool = False, - raise_error_warning_msg :str = '', + allow_reset :bool = False, + allow_reset_warning_msg :str = '', clear_screen: bool = True, show_search_hint: bool = True, cycle_cursor: bool = True, @@ -150,17 +150,10 @@ class Menu(TerminalMenu): self._skip = skip self._default_option = default_option self._multi = multi - self._raise_error_on_interrupt = raise_error_on_interrupt - self._raise_error_warning_msg = raise_error_warning_msg + self._raise_error_on_interrupt = allow_reset + self._raise_error_warning_msg = allow_reset_warning_msg self._preview_command = preview_command - menu_title = f'\n{title}\n\n' - - if header: - if not isinstance(header,(list,tuple)): - header = [header] - menu_title += '\n'.join(header) - action_info = '' if skip: action_info += str(_('ESC to skip')) @@ -173,7 +166,15 @@ class Menu(TerminalMenu): action_info += ', ' if len(action_info) > 0 else '' action_info += str(_('TAB to select')) - menu_title += action_info + '\n' + if action_info: + action_info += '\n\n' + + menu_title = f'\n{action_info}{title}\n' + + if header: + if not isinstance(header,(list,tuple)): + header = [header] + menu_title += '\n' + '\n'.join(header) if default_option: # if a default value was specified we move that one @@ -215,7 +216,7 @@ class Menu(TerminalMenu): try: idx = self.show() except KeyboardInterrupt: - return MenuSelection(type_=MenuSelectionType.Ctrl_c) + return MenuSelection(type_=MenuSelectionType.Reset) def check_default(elem): if self._default_option is not None and f'{self._default_option} {self._default_str}' in elem: @@ -234,7 +235,7 @@ class Menu(TerminalMenu): result = check_default(self._menu_options[idx]) return MenuSelection(type_=MenuSelectionType.Selection, value=result) else: - return MenuSelection(type_=MenuSelectionType.Esc) + return MenuSelection(type_=MenuSelectionType.Skip) def _preview_wrapper(self, preview_command: Optional[Callable], current_selection: str) -> Optional[str]: if preview_command: @@ -246,15 +247,15 @@ class Menu(TerminalMenu): def run(self) -> MenuSelection: ret = self._show() - if ret.type_ == MenuSelectionType.Ctrl_c: + if ret.type_ == MenuSelectionType.Reset: if self._raise_error_on_interrupt and len(self._raise_error_warning_msg) > 0: response = Menu(self._raise_error_warning_msg, Menu.yes_no(), skip=False).run() if response.value == Menu.no(): return self.run() - - if ret.type_ is not MenuSelectionType.Selection and not self._skip: - system('clear') - return self.run() + elif ret.type_ is MenuSelectionType.Skip: + if not self._skip: + system('clear') + return self.run() return ret diff --git a/archinstall/lib/menu/selection_menu.py b/archinstall/lib/menu/selection_menu.py deleted file mode 100644 index 8a08812c..00000000 --- a/archinstall/lib/menu/selection_menu.py +++ /dev/null @@ -1,497 +0,0 @@ -from __future__ import annotations - -import logging -import sys -import pathlib -from typing import Callable, Any, List, Iterator, Tuple, Optional, Dict, TYPE_CHECKING - -from .menu import Menu, MenuSelectionType -from ..locale_helpers import set_keyboard_language -from ..output import log -from ..translationhandler import TranslationHandler, Language -from ..hsm.fido import get_fido2_devices - -from ..user_interaction.general_conf import select_archinstall_language - -if TYPE_CHECKING: - _: Any - - -class Selector: - def __init__( - self, - description :str, - func :Callable = None, - display_func :Callable = None, - default :Any = None, - enabled :bool = False, - dependencies :List = [], - dependencies_not :List = [], - exec_func :Callable = None, - preview_func :Callable = None, - mandatory :bool = False, - no_store :bool = False - ): - """ - Create a new menu selection entry - - :param description: Text that will be displayed as the menu entry - :type description: str - - :param func: Function that is called when the menu entry is selected - :type func: Callable - - :param display_func: After specifying a setting for a menu item it is displayed - on the right side of the item as is; with this function one can modify the entry - to be displayed; e.g. when specifying a password one can display **** instead - :type display_func: Callable - - :param default: Default value for this menu entry - :type default: Any - - :param enabled: Specify if this menu entry should be displayed - :type enabled: bool - - :param dependencies: Specify dependencies for this menu entry; if the dependencies - are not set yet, then this item is not displayed; e.g. disk_layout depends on selectiong - harddrive(s) first - :type dependencies: list - - :param dependencies_not: These are the exclusive options; the menu item will only be - displayed if non of the entries in the list have been specified - :type dependencies_not: list - - :param exec_func: A function with the name and the result of the selection as input parameter and which returns boolean. - Can be used for any action deemed necessary after selection. If it returns True, exits the menu loop, if False, - menu returns to the selection screen. If not specified it is assumed the return is False - :type exec_func: Callable - - :param preview_func: A callable which invokws a preview screen - :type preview_func: Callable - - :param mandatory: A boolean which determines that the field is mandatory, i.e. menu can not be exited if it is not set - :type mandatory: bool - - :param no_store: A boolean which determines that the field should or shouldn't be stored in the data storage - :type no_store: bool - """ - self._description = description - self.func = func - self._display_func = display_func - self._current_selection = default - self.enabled = enabled - self._dependencies = dependencies - self._dependencies_not = dependencies_not - self.exec_func = exec_func - self._preview_func = preview_func - self.mandatory = mandatory - self._no_store = no_store - - @property - def description(self) -> str: - return self._description - - @property - def dependencies(self) -> List: - return self._dependencies - - @property - def dependencies_not(self) -> List: - return self._dependencies_not - - @property - def current_selection(self): - return self._current_selection - - @property - def preview_func(self): - return self._preview_func - - def do_store(self) -> bool: - return self._no_store is False - - def set_enabled(self, status :bool = True): - self.enabled = status - - def update_description(self, description :str): - self._description = description - - def menu_text(self, padding: int = 0) -> str: - if self._description == '': # special menu option for __separator__ - return '' - - current = '' - - if self._display_func: - current = self._display_func(self._current_selection) - else: - if self._current_selection is not None: - current = str(self._current_selection) - - if current: - padding += 5 - description = str(self._description).ljust(padding, ' ') - current = str(_('set: {}').format(current)) - else: - description = self._description - current = '' - - return f'{description} {current}' - - def set_current_selection(self, current :Optional[str]): - self._current_selection = current - - def has_selection(self) -> bool: - if not self._current_selection: - return False - return True - - def get_selection(self) -> Any: - return self._current_selection - - def is_empty(self) -> bool: - if self._current_selection is None: - return True - elif isinstance(self._current_selection, (str, list, dict)) and len(self._current_selection) == 0: - return True - return False - - def is_enabled(self) -> bool: - return self.enabled - - def is_mandatory(self) -> bool: - return self.mandatory - - def set_mandatory(self, status :bool = True): - self.mandatory = status - if status and not self.is_enabled(): - self.set_enabled(True) - -class GeneralMenu: - def __init__(self, data_store :dict = None, auto_cursor=False, preview_size :float = 0.2): - """ - Create a new selection menu. - - :param data_store: Area (Dict) where the resulting data will be held. At least an entry for each option. Default area is self._data_store (not preset in the call, due to circular references - :type data_store: Dict - - :param auto_cursor: Boolean which determines if the cursor stays on the first item (false) or steps each invocation of a selection entry (true) - :type auto_cursor: bool - - :param preview_size. Size in fractions of screen size of the preview window - ;type preview_size: float (range 0..1) - - """ - self._enabled_order :List[str] = [] - self._translation_handler = TranslationHandler() - self.is_context_mgr = False - self._data_store = data_store if data_store is not None else {} - self.auto_cursor = auto_cursor - self._menu_options: Dict[str, Selector] = {} - self._setup_selection_menu_options() - self.preview_size = preview_size - self._last_choice = None - - @property - def last_choice(self): - return self._last_choice - - def __enter__(self, *args :Any, **kwargs :Any) -> GeneralMenu: - self.is_context_mgr = True - return self - - def __exit__(self, *args :Any, **kwargs :Any) -> None: - # TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager - # TODO: skip processing when it comes from a planified exit - if len(args) >= 2 and args[1]: - log(args[1], level=logging.ERROR, fg='red') - print(" Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues") - raise args[1] - - for key in self._menu_options: - sel = self._menu_options[key] - if key and key not in self._data_store: - self._data_store[key] = sel._current_selection - - self.exit_callback() - - @property - def translation_handler(self) -> TranslationHandler: - return self._translation_handler - - def _setup_selection_menu_options(self): - """ Define the menu options. - Menu options can be defined here in a subclass or done per program calling self.set_option() - """ - return - - def pre_callback(self, selector_name): - """ will be called before each action in the menu """ - return - - def post_callback(self, selection_name: str = None, value: Any = None): - """ will be called after each action in the menu """ - return True - - def exit_callback(self): - """ will be called at the end of the processing of the menu """ - return - - def synch(self, selector_name :str, omit_if_set :bool = False,omit_if_disabled :bool = False): - """ loads menu options with data_store value """ - arg = self._data_store.get(selector_name, None) - # don't display the menu option if it was defined already - if arg is not None and omit_if_set: - return - - if not self.option(selector_name).is_enabled() and omit_if_disabled: - return - - if arg is not None: - self._menu_options[selector_name].set_current_selection(arg) - - def _update_enabled_order(self, selector_name: str): - self._enabled_order.append(selector_name) - - def enable(self, selector_name :str, omit_if_set :bool = False , mandatory :bool = False): - """ activates menu options """ - if self._menu_options.get(selector_name, None): - self._menu_options[selector_name].set_enabled(True) - self._update_enabled_order(selector_name) - - if mandatory: - self._menu_options[selector_name].set_mandatory(True) - self.synch(selector_name,omit_if_set) - else: - print(f'No selector found: {selector_name}') - sys.exit(1) - - def _preview_display(self, selection_name: str) -> Optional[str]: - config_name, selector = self._find_selection(selection_name) - if preview := selector.preview_func: - return preview() - return None - - def _get_menu_text_padding(self, entries: List[Selector]): - return max([len(str(selection.description)) for selection in entries]) - - def _find_selection(self, selection_name: str) -> Tuple[str, Selector]: - enabled_menus = self._menus_to_enable() - padding = self._get_menu_text_padding(list(enabled_menus.values())) - option = [(k, v) for k, v in self._menu_options.items() if v.menu_text(padding).strip() == selection_name.strip()] - - if len(option) != 1: - raise ValueError(f'Selection not found: {selection_name}') - config_name = option[0][0] - selector = option[0][1] - return config_name, selector - - def run(self): - """ Calls the Menu framework""" - # we synch all the options just in case - for item in self.list_options(): - self.synch(item) - - self.post_callback() # as all the values can vary i have to exec this callback - cursor_pos = None - - while True: - # Before continuing, set the preferred keyboard layout/language in the current terminal. - # This will just help the user with the next following questions. - self._set_kb_language() - enabled_menus = self._menus_to_enable() - - padding = self._get_menu_text_padding(list(enabled_menus.values())) - menu_options = [m.menu_text(padding) for m in enabled_menus.values()] - - selection = Menu( - _('Set/Modify the below options'), - menu_options, - sort=False, - cursor_index=cursor_pos, - preview_command=self._preview_display, - preview_size=self.preview_size, - skip_empty_entries=True, - skip=False - ).run() - - if selection.type_ == MenuSelectionType.Selection: - value = selection.value - - if self.auto_cursor: - cursor_pos = menu_options.index(value) + 1 # before the strip otherwise fails - - # in case the new position lands on a "placeholder" we'll skip them as well - while True: - if cursor_pos >= len(menu_options): - cursor_pos = 0 - if len(menu_options[cursor_pos]) > 0: - break - cursor_pos += 1 - - value = value.strip() - - # if this calls returns false, we exit the menu - # we allow for an callback for special processing on realeasing control - if not self._process_selection(value): - break - - # we get the last action key - actions = {str(v.description):k for k,v in self._menu_options.items()} - self._last_choice = actions[selection.value.strip()] - - if not self.is_context_mgr: - self.__exit__() - - def _process_selection(self, selection_name :str) -> bool: - """ determines and executes the selection y - Can / Should be extended to handle specific selection issues - Returns true if the menu shall continue, False if it has ended - """ - # find the selected option in our option list - config_name, selector = self._find_selection(selection_name) - return self.exec_option(config_name, selector) - - def exec_option(self, config_name :str, p_selector :Selector = None) -> bool: - """ processes the execution of a given menu entry - - pre process callback - - selection function - - post process callback - - exec action - returns True if the loop has to continue, false if the loop can be closed - """ - if not p_selector: - selector = self.option(config_name) - else: - selector = p_selector - - self.pre_callback(config_name) - - result = None - if selector.func: - presel_val = self.option(config_name).get_selection() - result = selector.func(presel_val) - self._menu_options[config_name].set_current_selection(result) - if selector.do_store(): - self._data_store[config_name] = result - exec_ret_val = selector.exec_func(config_name,result) if selector.exec_func else False - self.post_callback(config_name,result) - - if exec_ret_val and self._check_mandatory_status(): - return False - return True - - def _set_kb_language(self): - """ general for ArchInstall""" - # Before continuing, set the preferred keyboard layout/language in the current terminal. - # This will just help the user with the next following questions. - if self._data_store.get('keyboard-layout', None) and len(self._data_store['keyboard-layout']): - set_keyboard_language(self._data_store['keyboard-layout']) - - def _verify_selection_enabled(self, selection_name :str) -> bool: - """ general """ - if selection := self._menu_options.get(selection_name, None): - if not selection.enabled: - return False - - if len(selection.dependencies) > 0: - for d in selection.dependencies: - if not self._verify_selection_enabled(d) or self._menu_options[d].is_empty(): - return False - - if len(selection.dependencies_not) > 0: - for d in selection.dependencies_not: - if not self._menu_options[d].is_empty(): - return False - return True - - raise ValueError(f'No selection found: {selection_name}') - - def _menus_to_enable(self) -> dict: - """ general """ - enabled_menus = {} - - for name, selection in self._menu_options.items(): - if self._verify_selection_enabled(name): - enabled_menus[name] = selection - - # sort the enabled menu by the order we enabled them in - # we'll add the entries that have been enabled via the selector constructor at the top - enabled_keys = [i for i in enabled_menus.keys() if i not in self._enabled_order] - # and then we add the ones explicitly enabled by the enable function - enabled_keys += [i for i in self._enabled_order if i in enabled_menus.keys()] - - ordered_menus = {k: enabled_menus[k] for k in enabled_keys} - - return ordered_menus - - def option(self,name :str) -> Selector: - # TODO check inexistent name - return self._menu_options[name] - - def list_options(self) -> Iterator: - """ Iterator to retrieve the enabled menu option names - """ - for item in self._menu_options: - yield item - - def list_enabled_options(self) -> Iterator: - """ Iterator to retrieve the enabled menu options at a given time. - The results are dynamic (if between calls to the iterator some elements -still not retrieved- are (de)activated - """ - for item in self._menu_options: - if item in self._menus_to_enable(): - yield item - - def set_option(self, name :str, selector :Selector): - self._menu_options[name] = selector - self.synch(name) - - def _check_mandatory_status(self) -> bool: - for field in self._menu_options: - option = self._menu_options[field] - if option.is_mandatory() and not option.has_selection(): - return False - return True - - def set_mandatory(self, field :str, status :bool): - self.option(field).set_mandatory(status) - - def mandatory_overview(self) -> Tuple[int, int]: - mandatory_fields = 0 - mandatory_waiting = 0 - for field, option in self._menu_options.items(): - if option.is_mandatory(): - mandatory_fields += 1 - if not option.has_selection(): - mandatory_waiting += 1 - return mandatory_fields, mandatory_waiting - - def _select_archinstall_language(self, preset_value: Language) -> Language: - language = select_archinstall_language(self.translation_handler.translated_languages, preset_value) - self._translation_handler.activate(language) - return language - - def _select_hsm(self, preset :Optional[pathlib.Path] = None) -> Optional[pathlib.Path]: - title = _('Select which partitions to mark for formatting:') - title += '\n' - - fido_devices = get_fido2_devices() - - indexes = [] - for index, path in enumerate(fido_devices.keys()): - title += f"{index}: {path} ({fido_devices[path]['manufacturer']} - {fido_devices[path]['product']})" - indexes.append(f"{index}|{fido_devices[path]['product']}") - - title += '\n' - - choice = Menu(title, indexes, multi=False).run() - - match choice.type_: - case MenuSelectionType.Esc: return preset - case MenuSelectionType.Selection: - selection: Any = choice.value - index = int(selection.split('|',1)[0]) - return pathlib.Path(list(fido_devices.keys())[index]) - - return None diff --git a/archinstall/lib/menu/table_selection_menu.py b/archinstall/lib/menu/table_selection_menu.py new file mode 100644 index 00000000..09cd6ee2 --- /dev/null +++ b/archinstall/lib/menu/table_selection_menu.py @@ -0,0 +1,107 @@ +from typing import Any, Tuple, List, Dict, Optional + +from .menu import MenuSelectionType, MenuSelection +from ..output import FormattedOutput +from ..menu import Menu + + +class TableMenu(Menu): + def __init__( + self, + title: str, + data: List[Any] = [], + table_data: Optional[Tuple[List[Any], str]] = None, + custom_menu_options: List[str] = [], + default: Any = None, + multi: bool = False + ): + """ + param title: Text that will be displayed above the menu + :type title: str + + param data: List of objects that will be displayed as rows + :type data: List + + param table_data: Tuple containing a list of objects and the corresponding + Table representation of the data as string; this can be used in case the table + has to be crafted in a more sophisticated manner + :type table_data: Optional[Tuple[List[Any], str]] + + param custom_options: List of custom options that will be displayed under the table + :type custom_menu_options: List + """ + if not data and not table_data: + raise ValueError('Either "data" or "table_data" must be provided') + + self._custom_options = custom_menu_options + self._multi = multi + + if multi: + header_padding = 7 + else: + header_padding = 2 + + if len(data): + table_text = FormattedOutput.as_table(data) + rows = table_text.split('\n') + table = self._create_table(data, rows, header_padding=header_padding) + elif table_data is not None: + # we assume the table to be + # h1 | h2 + # ----------- + # r1 | r2 + data = table_data[0] + rows = table_data[1].split('\n') + table = self._create_table(data, rows, header_padding=header_padding) + + self._options, header = self._prepare_selection(table) + + super().__init__( + title, + self._options, + header=header, + skip_empty_entries=True, + show_search_hint=False, + allow_reset=True, + multi=multi, + default_option=default + ) + + def run(self) -> MenuSelection: + choice = super().run() + + match choice.type_: + case MenuSelectionType.Selection: + if self._multi: + choice.value = [self._options[val] for val in choice.value] # type: ignore + else: + choice.value = self._options[choice.value] # type: ignore + + return choice + + def _create_table(self, data: List[Any], rows: List[str], header_padding: int = 2) -> Dict[str, Any]: + # these are the header rows of the table and do not map to any data obviously + # we're adding 2 spaces as prefix because the menu selector '> ' will be put before + # the selectable rows so the header has to be aligned + padding = ' ' * header_padding + display_data = {f'{padding}{rows[0]}': None, f'{padding}{rows[1]}': None} + + for row, entry in zip(rows[2:], data): + row = row.replace('|', '\\|') + display_data[row] = entry + + return display_data + + def _prepare_selection(self, table: Dict[str, Any]) -> Tuple[Dict[str, Any], str]: + # header rows are mapped to None so make sure to exclude those from the selectable data + options = {key: val for key, val in table.items() if val is not None} + header = '' + + if len(options) > 0: + table_header = [key for key, val in table.items() if val is None] + header = '\n'.join(table_header) + + custom = {key: None for key in self._custom_options} + options.update(custom) + + return options, header diff --git a/archinstall/lib/models/disk_encryption.py b/archinstall/lib/models/disk_encryption.py new file mode 100644 index 00000000..80627767 --- /dev/null +++ b/archinstall/lib/models/disk_encryption.py @@ -0,0 +1,43 @@ +from dataclasses import dataclass, field +from enum import Enum, auto +from typing import Optional, List, Dict, TYPE_CHECKING, Any + +from archinstall.lib.hsm.fido import Fido2Device + +if TYPE_CHECKING: + _: Any + + +class EncryptionType(Enum): + Partition = auto() + # FullDiskEncryption = auto() + + @classmethod + def _encryption_type_mapper(cls) -> Dict[str, 'EncryptionType']: + return { + # str(_('Full disk encryption')): EncryptionType.FullDiskEncryption, + str(_('Partition encryption')): EncryptionType.Partition + } + + @classmethod + def text_to_type(cls, text: str) -> 'EncryptionType': + mapping = cls._encryption_type_mapper() + return mapping[text] + + @classmethod + def type_to_text(cls, type_: 'EncryptionType') -> str: + mapping = cls._encryption_type_mapper() + type_to_text = {type_: text for text, type_ in mapping.items()} + return type_to_text[type_] + + +@dataclass +class DiskEncryption: + encryption_type: EncryptionType = EncryptionType.Partition + encryption_password: str = '' + partitions: List[str] = field(default_factory=list) + hsm_device: Optional[Fido2Device] = None + + def generate_encryption_file(self, partition) -> bool: + return partition in self.partitions and partition['mountpoint'] != '/' + diff --git a/archinstall/lib/user_interaction/__init__.py b/archinstall/lib/user_interaction/__init__.py index a1ca2652..2bc46759 100644 --- a/archinstall/lib/user_interaction/__init__.py +++ b/archinstall/lib/user_interaction/__init__.py @@ -4,7 +4,7 @@ from .backwards_compatible_conf import generic_select, generic_multi_select from .locale_conf import select_locale_lang, select_locale_enc from .system_conf import select_kernel, select_harddrives, select_driver, ask_for_bootloader, ask_for_swap from .network_conf import ask_to_configure_network -from .partitioning_conf import select_partition, select_encrypted_partitions +from .partitioning_conf import select_partition from .general_conf import (ask_ntp, ask_for_a_timezone, ask_for_audio_selection, select_language, select_mirror_regions, select_profile, select_archinstall_language, ask_additional_packages_to_install, select_additional_repositories, ask_hostname, add_number_of_parrallel_downloads) diff --git a/archinstall/lib/user_interaction/disk_conf.py b/archinstall/lib/user_interaction/disk_conf.py index b5ed6967..554d13ef 100644 --- a/archinstall/lib/user_interaction/disk_conf.py +++ b/archinstall/lib/user_interaction/disk_conf.py @@ -45,13 +45,13 @@ def select_disk_layout(preset: Optional[Dict[str, Any]], block_devices: list, ad choice = Menu( _('Select what you wish to do with the selected block devices'), modes, - raise_error_on_interrupt=True, - raise_error_warning_msg=warning + allow_reset=True, + allow_reset_warning_msg=warning ).run() match choice.type_: - case MenuSelectionType.Esc: return preset - case MenuSelectionType.Ctrl_c: return None + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Reset: return None case MenuSelectionType.Selection: if choice.value == wipe_mode: return get_default_partition_layout(block_devices, advanced_options) @@ -77,7 +77,7 @@ def select_disk(dict_o_disks: Dict[str, BlockDevice]) -> Optional[BlockDevice]: choice = Menu(title, drives).run() - if choice.type_ == MenuSelectionType.Esc: + if choice.type_ == MenuSelectionType.Skip: return None drive = dict_o_disks[choice.value] diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py index efd746a4..76631a98 100644 --- a/archinstall/lib/user_interaction/general_conf.py +++ b/archinstall/lib/user_interaction/general_conf.py @@ -48,7 +48,7 @@ def ask_for_a_timezone(preset: str = None) -> str: ).run() match choice.type_: - case MenuSelectionType.Esc: return preset + case MenuSelectionType.Skip: return preset case MenuSelectionType.Selection: return choice.value @@ -60,7 +60,7 @@ def ask_for_audio_selection(desktop: bool = True, preset: str = None) -> str: choice = Menu(_('Choose an audio server'), choices, preset_values=preset, default_option=default).run() match choice.type_: - case MenuSelectionType.Esc: return preset + case MenuSelectionType.Skip: return preset case MenuSelectionType.Selection: return choice.value @@ -107,12 +107,12 @@ def select_mirror_regions(preset_values: Dict[str, Any] = {}) -> Dict[str, Any]: list(mirrors.keys()), preset_values=preselected, multi=True, - raise_error_on_interrupt=True + allow_reset=True ).run() match selected_mirror.type_: - case MenuSelectionType.Ctrl_c: return {} - case MenuSelectionType.Esc: return preset_values + case MenuSelectionType.Reset: return {} + case MenuSelectionType.Skip: return preset_values case _: return {selected: mirrors[selected] for selected in selected_mirror.value} @@ -134,7 +134,7 @@ def select_archinstall_language(languages: List[Language], preset_value: Languag ).run() match choice.type_: - case MenuSelectionType.Esc: + case MenuSelectionType.Skip: return preset_value case MenuSelectionType.Selection: return options[choice.value] @@ -163,21 +163,21 @@ def select_profile(preset) -> Optional[Profile]: selection = Menu( title=title, p_options=list(options.keys()), - raise_error_on_interrupt=True, - raise_error_warning_msg=warning + allow_reset=True, + allow_reset_warning_msg=warning ).run() match selection.type_: case MenuSelectionType.Selection: return options[selection.value] if selection.value is not None else None - case MenuSelectionType.Ctrl_c: + case MenuSelectionType.Reset: storage['profile_minimal'] = False storage['_selected_servers'] = [] storage['_desktop_profile'] = None storage['arguments']['desktop-environment'] = None storage['arguments']['gfx_driver_packages'] = None return None - case MenuSelectionType.Esc: + case MenuSelectionType.Skip: return None @@ -259,10 +259,10 @@ def select_additional_repositories(preset: List[str]) -> List[str]: sort=False, multi=True, preset_values=preset, - raise_error_on_interrupt=True + allow_reset=True ).run() match choice.type_: - case MenuSelectionType.Esc: return preset - case MenuSelectionType.Ctrl_c: return [] + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Reset: return [] case MenuSelectionType.Selection: return choice.value diff --git a/archinstall/lib/user_interaction/locale_conf.py b/archinstall/lib/user_interaction/locale_conf.py index 15720064..bbbe070b 100644 --- a/archinstall/lib/user_interaction/locale_conf.py +++ b/archinstall/lib/user_interaction/locale_conf.py @@ -23,7 +23,7 @@ def select_locale_lang(preset: str = None) -> str: match selected_locale.type_: case MenuSelectionType.Selection: return selected_locale.value - case MenuSelectionType.Esc: return preset + case MenuSelectionType.Skip: return preset def select_locale_enc(preset: str = None) -> str: @@ -39,4 +39,4 @@ def select_locale_enc(preset: str = None) -> str: match selected_locale.type_: case MenuSelectionType.Selection: return selected_locale.value - case MenuSelectionType.Esc: return preset + case MenuSelectionType.Skip: return preset diff --git a/archinstall/lib/user_interaction/network_conf.py b/archinstall/lib/user_interaction/network_conf.py index 557e8ed8..5e637f23 100644 --- a/archinstall/lib/user_interaction/network_conf.py +++ b/archinstall/lib/user_interaction/network_conf.py @@ -71,7 +71,7 @@ class ManualNetworkConfig(ListManager): available = set(all_ifaces) - set(existing_ifaces) choice = Menu(str(_('Select interface to add')), list(available), skip=True).run() - if choice.type_ == MenuSelectionType.Esc: + if choice.type_ == MenuSelectionType.Skip: return None return choice.value @@ -154,13 +154,13 @@ def ask_to_configure_network( list(network_options.values()), cursor_index=cursor_idx, sort=False, - raise_error_on_interrupt=True, - raise_error_warning_msg=warning + allow_reset=True, + allow_reset_warning_msg=warning ).run() match choice.type_: - case MenuSelectionType.Esc: return preset - case MenuSelectionType.Ctrl_c: return None + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Reset: return None if choice.value == network_options['none']: return None diff --git a/archinstall/lib/user_interaction/partitioning_conf.py b/archinstall/lib/user_interaction/partitioning_conf.py index f2e6b881..cff76dc2 100644 --- a/archinstall/lib/user_interaction/partitioning_conf.py +++ b/archinstall/lib/user_interaction/partitioning_conf.py @@ -119,7 +119,7 @@ def select_partition( choice = Menu(title, partition_indexes, multi=multiple).run() - if choice.type_ == MenuSelectionType.Esc: + if choice.type_ == MenuSelectionType.Skip: return None if isinstance(choice.value, list): @@ -150,7 +150,6 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, delete_all_partitions = str(_('Clear/Delete all partitions')) assign_mount_point = str(_('Assign mount-point for a partition')) mark_formatted = str(_('Mark/Unmark a partition to be formatted (wipes data)')) - mark_encrypted = str(_('Mark/Unmark a partition as encrypted')) mark_compressed = str(_('Mark/Unmark a partition as compressed (btrfs only)')) mark_bootable = str(_('Mark/Unmark a partition as bootable (automatic for /boot)')) set_filesystem_partition = str(_('Set desired filesystem for a partition')) @@ -167,7 +166,6 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, delete_all_partitions, assign_mount_point, mark_formatted, - mark_encrypted, mark_bootable, mark_compressed, set_filesystem_partition, @@ -207,7 +205,7 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, fs_choice = Menu(_('Enter a desired filesystem type for the partition'), fs_types()).run() - if fs_choice.type_ == MenuSelectionType.Esc: + if fs_choice.type_ == MenuSelectionType.Skip: continue prompt = str(_('Enter the start sector (percentage or block number, default: {}): ')).format( @@ -322,15 +320,6 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, # Negate the current wipe marking block_device_struct["partitions"][partition]['wipe'] = not block_device_struct["partitions"][partition].get('wipe', False) - elif task == mark_encrypted: - title = _('{}\n\nSelect which partition to mark as encrypted').format(current_layout) - partition = select_partition(title, block_device_struct["partitions"]) - - if partition is not None: - # Negate the current encryption marking - block_device_struct["partitions"][partition]['encrypted'] = \ - not block_device_struct["partitions"][partition].get('encrypted', False) - elif task == mark_bootable: title = _('{}\n\nSelect which partition to mark as bootable').format(current_layout) partition = select_partition(title, block_device_struct["partitions"]) @@ -371,30 +360,3 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, block_device_struct["partitions"][partition]['btrfs']['subvolumes'] = result return block_device_struct - - -def select_encrypted_partitions( - title :str, - partitions :List[Partition], - multiple :bool = True, - filter_ :Callable = None -) -> Optional[int, List[int]]: - partition_indexes = _get_partitions(partitions, filter_) - - if len(partition_indexes) == 0: - return None - - # show current partition layout: - if len(partitions): - title += current_partition_layout(partitions, with_idx=True) + '\n' - - choice = Menu(title, partition_indexes, multi=multiple).run() - - if choice.type_ == MenuSelectionType.Esc: - return None - - if isinstance(choice.value, list): - for partition_index in choice.value: - yield int(partition_index) - else: - yield (partition_index) diff --git a/archinstall/lib/user_interaction/save_conf.py b/archinstall/lib/user_interaction/save_conf.py index f542bc9b..d60ef995 100644 --- a/archinstall/lib/user_interaction/save_conf.py +++ b/archinstall/lib/user_interaction/save_conf.py @@ -55,7 +55,7 @@ def save_config(config: Dict): preview_command=preview ).run() - if choice.type_ == MenuSelectionType.Esc: + if choice.type_ == MenuSelectionType.Skip: return while True: diff --git a/archinstall/lib/user_interaction/system_conf.py b/archinstall/lib/user_interaction/system_conf.py index 8b574b2c..42a6cec7 100644 --- a/archinstall/lib/user_interaction/system_conf.py +++ b/archinstall/lib/user_interaction/system_conf.py @@ -32,13 +32,13 @@ def select_kernel(preset: List[str] = None) -> List[str]: sort=True, multi=True, preset_values=preset, - raise_error_on_interrupt=True, - raise_error_warning_msg=warning + allow_reset=True, + allow_reset_warning_msg=warning ).run() match choice.type_: - case MenuSelectionType.Esc: return preset - case MenuSelectionType.Ctrl_c: return [] + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Reset: return [] case MenuSelectionType.Selection: return choice.value @@ -62,13 +62,13 @@ def select_harddrives(preset: List[str] = []) -> List[str]: list(options.keys()), preset_values=preset, multi=True, - raise_error_on_interrupt=True, - raise_error_warning_msg=warning + allow_reset=True, + allow_reset_warning_msg=warning ).run() match selected_harddrive.type_: - case MenuSelectionType.Ctrl_c: return [] - case MenuSelectionType.Esc: return preset + case MenuSelectionType.Reset: return [] + case MenuSelectionType.Skip: return preset case MenuSelectionType.Selection: return [options[i] for i in selected_harddrive.value] @@ -132,7 +132,7 @@ def ask_for_bootloader(advanced_options: bool = False, preset: str = None) -> st ).run() match selection.type_: - case MenuSelectionType.Esc: return preset + case MenuSelectionType.Skip: return preset case MenuSelectionType.Selection: bootloader = 'grub-install' if selection.value == Menu.yes() else bootloader else: # We use the common names for the bootloader as the selection, and map it back to the expected values. @@ -141,7 +141,7 @@ def ask_for_bootloader(advanced_options: bool = False, preset: str = None) -> st value = '' match selection.type_: - case MenuSelectionType.Esc: value = preset_val + case MenuSelectionType.Skip: value = preset_val case MenuSelectionType.Selection: value = selection.value if value != "": @@ -165,5 +165,5 @@ def ask_for_swap(preset: bool = True) -> bool: choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes(), preset_values=preset_val).run() match choice.type_: - case MenuSelectionType.Esc: return preset + case MenuSelectionType.Skip: return preset case MenuSelectionType.Selection: return False if choice.value == Menu.no() else True diff --git a/archinstall/locales/ar/LC_MESSAGES/base.po b/archinstall/locales/ar/LC_MESSAGES/base.po index 51b6c3d6..1a9fc1aa 100644 --- a/archinstall/locales/ar/LC_MESSAGES/base.po +++ b/archinstall/locales/ar/LC_MESSAGES/base.po @@ -721,7 +721,7 @@ msgstr "" msgid "Should \"{}\" be a superuser (sudo)?" msgstr "" -msgid "Select which partitions to encrypt:" +msgid "Select which partitions to encrypt" msgstr "" msgid "very weak" diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index 7398d33a..2becbbf3 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -761,7 +761,7 @@ msgstr "" msgid "Should \"{}\" be a superuser (sudo)?" msgstr "" -msgid "Select which partitions to encrypt:" +msgid "Select which partitions to encrypt" msgstr "" msgid "very weak" diff --git a/archinstall/locales/cs/LC_MESSAGES/base.po b/archinstall/locales/cs/LC_MESSAGES/base.po index 733c9cae..b3dea244 100644 --- a/archinstall/locales/cs/LC_MESSAGES/base.po +++ b/archinstall/locales/cs/LC_MESSAGES/base.po @@ -756,8 +756,8 @@ msgstr "Zadané uživatelské jméno není platné. Zkuste to znovu" msgid "Should \"{}\" be a superuser (sudo)?" msgstr "Má být \"{}\" superuživatelem (sudoer)?" -msgid "Select which partitions to encrypt:" -msgstr "Zvolte oddíl, který bude označen jako šifrovaný:" +msgid "Select which partitions to encrypt" +msgstr "Zvolte oddíl, který bude označen jako šifrovaný" msgid "very weak" msgstr "velmi slabé" diff --git a/archinstall/locales/de/LC_MESSAGES/base.po b/archinstall/locales/de/LC_MESSAGES/base.po index 46782bc3..dee2b481 100644 --- a/archinstall/locales/de/LC_MESSAGES/base.po +++ b/archinstall/locales/de/LC_MESSAGES/base.po @@ -776,11 +776,8 @@ msgid "Should \"{}\" be a superuser (sudo)?" msgstr "Soll {} ein superuser sein (sudoer)?" #, fuzzy -msgid "Select which partitions to encrypt:" -msgstr "" -"{}\n" -"\n" -"Bitte wählen sie welche Partition verschlüsselt werden soll" +msgid "Select which partitions to encrypt" +msgstr "Bitte wählen sie welche Partition verschlüsselt werden soll" msgid "very weak" msgstr "" diff --git a/archinstall/locales/el/LC_MESSAGES/base.po b/archinstall/locales/el/LC_MESSAGES/base.po index 6425ba96..c41dbb7e 100644 --- a/archinstall/locales/el/LC_MESSAGES/base.po +++ b/archinstall/locales/el/LC_MESSAGES/base.po @@ -763,7 +763,7 @@ msgstr "Το όνομα χρήστη που εισάγατε δεν είναι msgid "Should \"{}\" be a superuser (sudo)?" msgstr "Θα έπρεπε ο \"{}\" να είναι υπερχρήστης (sudo);" -msgid "Select which partitions to encrypt:" +msgid "Select which partitions to encrypt" msgstr "Επιλέξτε ποιες διαμερίσεις να κρυπτογραφηθούν." msgid "very weak" diff --git a/archinstall/locales/en/LC_MESSAGES/base.po b/archinstall/locales/en/LC_MESSAGES/base.po index 01f59274..62543eaa 100644 --- a/archinstall/locales/en/LC_MESSAGES/base.po +++ b/archinstall/locales/en/LC_MESSAGES/base.po @@ -717,7 +717,7 @@ msgstr "" msgid "Should \"{}\" be a superuser (sudo)?" msgstr "" -msgid "Select which partitions to encrypt:" +msgid "Select which partitions to encrypt" msgstr "" msgid "very weak" diff --git a/archinstall/locales/es/LC_MESSAGES/base.po b/archinstall/locales/es/LC_MESSAGES/base.po index 3bdbe72f..f744daae 100644 --- a/archinstall/locales/es/LC_MESSAGES/base.po +++ b/archinstall/locales/es/LC_MESSAGES/base.po @@ -762,8 +762,8 @@ msgstr "El nombre de usuario que ingresó no es válido. Intente nuevamente" msgid "Should \"{}\" be a superuser (sudo)?" msgstr "¿Debe \"{}\" ser un superusuario (sudo)?" -msgid "Select which partitions to encrypt:" -msgstr "Seleccione qué particiones cifrar:" +msgid "Select which partitions to encrypt" +msgstr "Seleccione qué particiones cifrar" msgid "very weak" msgstr "" diff --git a/archinstall/locales/fr/LC_MESSAGES/base.po b/archinstall/locales/fr/LC_MESSAGES/base.po index e58592bf..f5946503 100644 --- a/archinstall/locales/fr/LC_MESSAGES/base.po +++ b/archinstall/locales/fr/LC_MESSAGES/base.po @@ -765,11 +765,8 @@ msgid "Should \"{}\" be a superuser (sudo)?" msgstr "\"{}\" devrait-il être un superutilisateur (sudo) ?" #, fuzzy -msgid "Select which partitions to encrypt:" -msgstr "" -"{}\n" -"\n" -"Sélectionner la partition à marquer comme chiffrée" +msgid "Select which partitions to encrypt" +msgstr "Sélectionner la partition à marquer comme chiffrée" msgid "very weak" msgstr "" diff --git a/archinstall/locales/id/LC_MESSAGES/base.po b/archinstall/locales/id/LC_MESSAGES/base.po index 0ace1b09..85479389 100644 --- a/archinstall/locales/id/LC_MESSAGES/base.po +++ b/archinstall/locales/id/LC_MESSAGES/base.po @@ -763,8 +763,8 @@ msgstr "Nama pengguna yang Anda masukkan tidak valid. Coba lagi" msgid "Should \"{}\" be a superuser (sudo)?" msgstr "Haruskah \"{}\" menjadi superuser (sudo)?" -msgid "Select which partitions to encrypt:" -msgstr "Pilih partisi mana yang akan dienkripsi:" +msgid "Select which partitions to encrypt" +msgstr "Pilih partisi mana yang akan dienkripsi" msgid "very weak" msgstr "sangat lemah" diff --git a/archinstall/locales/it/LC_MESSAGES/base.po b/archinstall/locales/it/LC_MESSAGES/base.po index d122b87f..176d3959 100644 --- a/archinstall/locales/it/LC_MESSAGES/base.po +++ b/archinstall/locales/it/LC_MESSAGES/base.po @@ -763,8 +763,8 @@ msgstr "Il nome utente inserito non è valido. Riprova" msgid "Should \"{}\" be a superuser (sudo)?" msgstr "\"{}\" dovrebbe essere un superutente? (sudo)" -msgid "Select which partitions to encrypt:" -msgstr "Seleziona le partizioni da crittografare:" +msgid "Select which partitions to encrypt" +msgstr "Seleziona le partizioni da crittografare" msgid "very weak" msgstr "molto debole" diff --git a/archinstall/locales/nl/LC_MESSAGES/base.po b/archinstall/locales/nl/LC_MESSAGES/base.po index 4eb93e22..b7323059 100644 --- a/archinstall/locales/nl/LC_MESSAGES/base.po +++ b/archinstall/locales/nl/LC_MESSAGES/base.po @@ -791,11 +791,8 @@ msgid "Should \"{}\" be a superuser (sudo)?" msgstr "Moet {} gebruiker een beheerder (sudoer) worden?" #, fuzzy -msgid "Select which partitions to encrypt:" -msgstr "" -"{}\n" -"\n" -"Kies welke partitie moet worden versleuteld" +msgid "Select which partitions to encrypt" +msgstr "Kies welke partitie moet worden versleuteld" msgid "very weak" msgstr "" diff --git a/archinstall/locales/pl/LC_MESSAGES/base.po b/archinstall/locales/pl/LC_MESSAGES/base.po index 2d9968dc..6655bbbd 100644 --- a/archinstall/locales/pl/LC_MESSAGES/base.po +++ b/archinstall/locales/pl/LC_MESSAGES/base.po @@ -763,11 +763,9 @@ msgstr "Wprowadzona nazwa użytkownika jest nieprawidłowa. Spróbuj ponownie" msgid "Should \"{}\" be a superuser (sudo)?" msgstr "Czy \"{}\" powinien być superuserem (sudo)?" -msgid "Select which partitions to encrypt:" -msgstr "" -"{}\n" -"\n" -"Wybierz partycje, które mają zostać zaszyfrowane:" +#, fuzzy +msgid "Select which partitions to encrypt" +msgstr "Wybierz partycja która ma zostać zaszyfrowana" msgid "very weak" msgstr "bardzo słabe" diff --git a/archinstall/locales/pt/LC_MESSAGES/base.po b/archinstall/locales/pt/LC_MESSAGES/base.po index 55569546..683de0ad 100644 --- a/archinstall/locales/pt/LC_MESSAGES/base.po +++ b/archinstall/locales/pt/LC_MESSAGES/base.po @@ -811,11 +811,8 @@ msgid "Should \"{}\" be a superuser (sudo)?" msgstr "Deve {} ser um superutilizador (sudoer)?" #, fuzzy -msgid "Select which partitions to encrypt:" -msgstr "" -"{}\n" -"\n" -"Seleciona a partição a marcar como encriptada" +msgid "Select which partitions to encrypt" +msgstr "Seleciona a partição a marcar como encriptada" msgid "very weak" msgstr "" diff --git a/archinstall/locales/pt_BR/LC_MESSAGES/base.po b/archinstall/locales/pt_BR/LC_MESSAGES/base.po index 1981c799..938f5068 100644 --- a/archinstall/locales/pt_BR/LC_MESSAGES/base.po +++ b/archinstall/locales/pt_BR/LC_MESSAGES/base.po @@ -761,8 +761,8 @@ msgstr "O nome de usuário que você digitou é inválido. Tente novamente" msgid "Should \"{}\" be a superuser (sudo)?" msgstr "\"{}\" deve ser um superusuário (sudo)?" -msgid "Select which partitions to encrypt:" -msgstr "Selecione quais partições encriptar:" +msgid "Select which partitions to encrypt" +msgstr "Selecione quais partições encriptar" msgid "very weak" msgstr "muito fraca" diff --git a/archinstall/locales/ru/LC_MESSAGES/base.po b/archinstall/locales/ru/LC_MESSAGES/base.po index 6d5799eb..a62a8385 100644 --- a/archinstall/locales/ru/LC_MESSAGES/base.po +++ b/archinstall/locales/ru/LC_MESSAGES/base.po @@ -764,8 +764,8 @@ msgstr "Введенное вами имя пользователя недейс msgid "Should \"{}\" be a superuser (sudo)?" msgstr "Должен ли \"{}\" быть суперпользователем (sudo)?" -msgid "Select which partitions to encrypt:" -msgstr "Выберите разделы для шифрования:" +msgid "Select which partitions to encrypt" +msgstr "Выберите разделы для шифрования" msgid "very weak" msgstr "очень слабый" diff --git a/archinstall/locales/sv/LC_MESSAGES/base.po b/archinstall/locales/sv/LC_MESSAGES/base.po index 9d7f7455..fc311551 100644 --- a/archinstall/locales/sv/LC_MESSAGES/base.po +++ b/archinstall/locales/sv/LC_MESSAGES/base.po @@ -772,11 +772,8 @@ msgid "Should \"{}\" be a superuser (sudo)?" msgstr "Är detta en superanvändare (sudo-rättigheter)?" #, fuzzy -msgid "Select which partitions to encrypt:" -msgstr "" -"{}\n" -"\n" -"Välj vilken partition som skall markeras för kryptering" +msgid "Select which partitions to encrypt" +msgstr "Välj vilken partition som skall markeras för kryptering" msgid "very weak" msgstr "" diff --git a/archinstall/locales/ta/LC_MESSAGES/base.po b/archinstall/locales/ta/LC_MESSAGES/base.po index 3ea3dd19..4375f477 100644 --- a/archinstall/locales/ta/LC_MESSAGES/base.po +++ b/archinstall/locales/ta/LC_MESSAGES/base.po @@ -763,8 +763,8 @@ msgstr "நீங்கள் உள்ளிட்ட பயனர்பெய msgid "Should \"{}\" be a superuser (sudo)?" msgstr "\"{}\" ஒரு சூப்பர் யூசராக (sudo) இருக்க வேண்டுமா?" -msgid "Select which partitions to encrypt:" -msgstr "குறியாக்கம் செய்ய வேண்டிய பகிர்வுகளைத் தேர்ந்தெடுக்கவும்:" +msgid "Select which partitions to encrypt" +msgstr "குறியாக்கம் செய்ய வேண்டிய பகிர்வுகளைத் தேர்ந்தெடுக்கவும்" msgid "very weak" msgstr "மிகவும் பலவீனமானது" diff --git a/archinstall/locales/tr/LC_MESSAGES/base.po b/archinstall/locales/tr/LC_MESSAGES/base.po index c38f467e..fd1e2393 100644 --- a/archinstall/locales/tr/LC_MESSAGES/base.po +++ b/archinstall/locales/tr/LC_MESSAGES/base.po @@ -772,11 +772,8 @@ msgid "Should \"{}\" be a superuser (sudo)?" msgstr "{} bir süper kullanıcı (sudoer) olmalı mı?" #, fuzzy -msgid "Select which partitions to encrypt:" -msgstr "" -"{}\n" -"\n" -"Hangi disk bölümünün şifrelenmiş olarak işaretleneceğini seçin" +msgid "Select which partitions to encrypt" +msgstr "Hangi disk bölümünün şifrelenmiş olarak işaretleneceğini seçin" msgid "very weak" msgstr "" diff --git a/archinstall/locales/ur/LC_MESSAGES/base.po b/archinstall/locales/ur/LC_MESSAGES/base.po index 8ab6c4e2..0b024031 100644 --- a/archinstall/locales/ur/LC_MESSAGES/base.po +++ b/archinstall/locales/ur/LC_MESSAGES/base.po @@ -794,11 +794,8 @@ msgid "Should \"{}\" be a superuser (sudo)?" msgstr "کیا {} کو سپر یوزر (sudoer) ہونا چاہیے؟" #, fuzzy -msgid "Select which partitions to encrypt:" -msgstr "" -"{}\n" -"\n" -"منتخب کریں کہ کس پارٹیشن کو انکرپٹڈ یا خفیہ رکھنا ہے" +msgid "Select which partitions to encrypt" +msgstr "منتخب کریں کہ کس پارٹیشن کو انکرپٹڈ یا خفیہ رکھنا ہے" msgid "very weak" msgstr "" diff --git a/examples/guided.py b/examples/guided.py index eba78a1a..4b655240 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -54,8 +54,8 @@ def ask_user_questions(): global_menu.enable('disk_layouts') - # Get disk encryption password (or skip if blank) - global_menu.enable('!encryption-password') + # Specify disk encryption options + global_menu.enable('disk_encryption') if archinstall.arguments.get('advanced', False) or archinstall.arguments.get('HSM', None): # Enables the use of HSM diff --git a/examples/only_hd.py b/examples/only_hd.py index b3379601..e3d18f0a 100644 --- a/examples/only_hd.py +++ b/examples/only_hd.py @@ -12,7 +12,7 @@ class OnlyHDMenu(archinstall.GlobalMenu): super()._setup_selection_menu_options() options_list = [] mandatory_list = [] - options_list = ['harddrives', 'disk_layouts', '!encryption-password','swap'] + options_list = ['harddrives', 'disk_layouts', 'disk_encryption','swap'] mandatory_list = ['harddrives'] options_list.extend(['save_config','install','abort']) diff --git a/examples/swiss.py b/examples/swiss.py index 419bd859..442281de 100644 --- a/examples/swiss.py +++ b/examples/swiss.py @@ -153,7 +153,7 @@ def select_installed_locale(mode): _menus """ -class SetupMenu(archinstall.GeneralMenu): +class SetupMenu(archinstall.AbstractMenu): def __init__(self,storage_area): super().__init__(data_store=storage_area) @@ -230,14 +230,14 @@ class MyMenu(archinstall.GlobalMenu): mandatory_list = [] if self._execution_mode in ('full','lineal'): options_list = ['keyboard-layout', 'mirror-region', 'harddrives', 'disk_layouts', - '!encryption-password','swap', 'bootloader', 'hostname', '!root-password', + 'disk_encryption','swap', 'bootloader', 'hostname', '!root-password', '!users', 'profile', 'audio', 'kernels', 'packages','additional-repositories','nic', 'timezone', 'ntp'] if archinstall.arguments.get('advanced',False): options_list.extend(['sys-language','sys-encoding']) mandatory_list = ['harddrives','bootloader','hostname'] elif self._execution_mode == 'only_hd': - options_list = ['harddrives', 'disk_layouts', '!encryption-password','swap'] + options_list = ['harddrives', 'disk_layouts', 'disk_encryption','swap'] mandatory_list = ['harddrives'] elif self._execution_mode == 'only_os': options_list = ['keyboard-layout', 'mirror-region','bootloader', 'hostname', -- cgit v1.2.3-70-g09d2