From 5c9bd235d3d0e653ba8d643748fd3d776359978b Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sat, 28 May 2022 10:06:22 +0200 Subject: Fixes additional encryption prompt even tho partitions was marked for encryption (#1264) Corrected the check for partitions marked with `encrypt: true` --- archinstall/lib/disk/helpers.py | 7 ++++--- archinstall/lib/menu/global_menu.py | 17 +++++++++-------- archinstall/lib/user_interaction/partitioning_conf.py | 2 -- 3 files changed, 13 insertions(+), 13 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk/helpers.py b/archinstall/lib/disk/helpers.py index 99856aad..65d7a006 100644 --- a/archinstall/lib/disk/helpers.py +++ b/archinstall/lib/disk/helpers.py @@ -433,9 +433,10 @@ def disk_layouts() -> Optional[Dict[str, Any]]: def encrypted_partitions(blockdevices :Dict[str, Any]) -> bool: - for partition in blockdevices.values(): - if partition.get('encrypted', False): - yield partition + 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: diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index 5cb27cab..3ffb6f15 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -204,14 +204,15 @@ class GlobalMenu(GeneralMenu): # 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']: - for partition_index in select_encrypted_partitions( - title="Select which partitions to encrypt:", - partitions=storage['arguments']['disk_layouts'][blockdevice]['partitions'] - ): - - partition = storage['arguments']['disk_layouts'][blockdevice]['partitions'][partition_index] - partition['encrypted'] = True - partition['!password'] = storage['arguments']['!encryption-password'] + 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'] + ): + + partition = storage['arguments']['disk_layouts'][blockdevice]['partitions'][partition_index] + partition['encrypted'] = True + partition['!password'] = storage['arguments']['!encryption-password'] def _install_text(self): missing = len(self._missing_configs()) diff --git a/archinstall/lib/user_interaction/partitioning_conf.py b/archinstall/lib/user_interaction/partitioning_conf.py index bfff5705..1f41f9b0 100644 --- a/archinstall/lib/user_interaction/partitioning_conf.py +++ b/archinstall/lib/user_interaction/partitioning_conf.py @@ -374,8 +374,6 @@ def select_encrypted_partitions( if len(partition_indexes) == 0: return None - title = _('Select which partitions to mark for formatting:') - # show current partition layout: if len(partitions): title += current_partition_layout(partitions) + '\n' -- cgit v1.2.3-70-g09d2 From 486ad7dd6d195dd435c5da58d241e14742f60485 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sat, 28 May 2022 15:40:36 +0200 Subject: Removes btrfs subvolume warnings on incorrect subvolume locations (#1267) * Adding debug information * Adding debug information * Adding debug information * Removed a 'already-a-subvolume' check as it requires more information. * Adding debug information * Adding debug information * Made sure Partition().subvolumes() only attempts to retrieve btrfs subvolume information if fstype==btrfs. * Removed debug information --- archinstall/lib/disk/btrfs/btrfspartition.py | 8 ++++++-- archinstall/lib/disk/partition.py | 10 ++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk/btrfs/btrfspartition.py b/archinstall/lib/disk/btrfs/btrfspartition.py index 5020133d..299357b8 100644 --- a/archinstall/lib/disk/btrfs/btrfspartition.py +++ b/archinstall/lib/disk/btrfs/btrfspartition.py @@ -108,9 +108,13 @@ class BTRFSPartition(Partition): if glob.glob(str(subvolume / '*')): raise DiskError(f"Cannot create subvolume at {subvolume} because it contains data (non-empty folder target is not supported by BTRFS)") - elif subvolinfo := subvolume_info_from_path(subvolume): - raise DiskError(f"Destination {subvolume} is already a subvolume: {subvolinfo}") + # Ideally we would like to check if the destination is already a subvolume. + # But then we would need the mount-point at this stage as well. + # So we'll comment out this check: + # elif subvolinfo := subvolume_info_from_path(subvolume): + # raise DiskError(f"Destination {subvolume} is already a subvolume: {subvolinfo}") + # And deal with it here: SysCommand(f"btrfs subvolume create {subvolume}") return subvolume_info_from_path(subvolume) \ No newline at end of file diff --git a/archinstall/lib/disk/partition.py b/archinstall/lib/disk/partition.py index 73c88597..151775b1 100644 --- a/archinstall/lib/disk/partition.py +++ b/archinstall/lib/disk/partition.py @@ -310,8 +310,9 @@ class Partition: def iterate_children_recursively(information): for child in information.get('children', []): if target := child.get('target'): - if subvolume := subvolume_info_from_path(pathlib.Path(target)): - yield subvolume + if child.get('fstype') == 'btrfs': + if subvolume := subvolume_info_from_path(pathlib.Path(target)): + yield subvolume if child.get('children'): for subchild in iterate_children_recursively(child): @@ -320,8 +321,9 @@ class Partition: for mountpoint in self.mount_information: if result := findmnt(pathlib.Path(mountpoint['target'])): for filesystem in result.get('filesystems', []): - if subvolume := subvolume_info_from_path(pathlib.Path(mountpoint['target'])): - yield subvolume + if mountpoint.get('fstype') == 'btrfs': + if subvolume := subvolume_info_from_path(pathlib.Path(mountpoint['target'])): + yield subvolume for child in iterate_children_recursively(filesystem): yield child -- cgit v1.2.3-70-g09d2 From 2e77393cf8397197e8006293c230dd93c4378bad Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Sat, 28 May 2022 10:29:46 -0400 Subject: Fix issue with multiples spaces in additional packages (#1262) * Try to fix issue 1259 * trim -> strip --- archinstall/lib/user_interaction/general_conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py index d4dc60db..f1d56fc1 100644 --- a/archinstall/lib/user_interaction/general_conf.py +++ b/archinstall/lib/user_interaction/general_conf.py @@ -172,8 +172,8 @@ def ask_additional_packages_to_install(pre_set_packages: List[str] = []) -> List def read_packages(already_defined: list = []) -> list: display = ' '.join(already_defined) - input_packages = TextInput(_('Write additional packages to install (space separated, leave blank to skip): '), display).run() - return input_packages.split(' ') if input_packages else [] + input_packages = TextInput(_('Write additional packages to install (space separated, leave blank to skip): '), display).run().strip() + return input_packages.split() if input_packages else [] pre_set_packages = pre_set_packages if pre_set_packages else [] packages = read_packages(pre_set_packages) -- cgit v1.2.3-70-g09d2 From b2f85889a7a935a4d9638fe0fec5aac45e721b09 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Sun, 29 May 2022 03:25:22 -0400 Subject: Don't offer to encrypt /boot, exlude it from the set available to the user (#1273) * WIP: Don't offer to encrypt /boot * This filter might work * Ref: https://github.com/archlinux/archinstall/blob/master/archinstall/lib/storage.py * Use list comprehension * I wonder if I can use this filter_ argument that exists already * flake8 fix * Show index --- archinstall/lib/menu/global_menu.py | 5 +++-- archinstall/lib/user_interaction/partitioning_conf.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index 3ffb6f15..a758d8c6 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -206,8 +206,9 @@ class GlobalMenu(GeneralMenu): 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'] + 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] diff --git a/archinstall/lib/user_interaction/partitioning_conf.py b/archinstall/lib/user_interaction/partitioning_conf.py index 1f41f9b0..1ecba8be 100644 --- a/archinstall/lib/user_interaction/partitioning_conf.py +++ b/archinstall/lib/user_interaction/partitioning_conf.py @@ -376,7 +376,7 @@ def select_encrypted_partitions( # show current partition layout: if len(partitions): - title += current_partition_layout(partitions) + '\n' + title += current_partition_layout(partitions, with_idx=True) + '\n' choice = Menu(title, partition_indexes, multi=multiple).run() -- cgit v1.2.3-70-g09d2 From 2de153003ed5de1018639070fabc9c9e583c49d1 Mon Sep 17 00:00:00 2001 From: Kian-Meng Ang Date: Sun, 29 May 2022 15:31:18 +0800 Subject: Fix typos (#1265) --- README.md | 4 ++-- archinstall/__init__.py | 4 ++-- archinstall/lib/disk/btrfs/__init__.py | 4 ++-- archinstall/lib/disk/btrfs/btrfs_helpers.py | 4 ++-- archinstall/lib/disk/btrfs/btrfspartition.py | 4 ++-- archinstall/lib/disk/btrfs/btrfssubvolume.py | 4 ++-- archinstall/lib/disk/filesystem.py | 4 ++-- archinstall/lib/disk/helpers.py | 6 +++--- archinstall/lib/general.py | 6 +++--- archinstall/lib/installer.py | 4 ++-- archinstall/lib/menu/list_manager.py | 4 ++-- archinstall/lib/menu/menu.py | 2 +- archinstall/lib/menu/selection_menu.py | 4 ++-- archinstall/lib/menu/simple_menu.py | 2 +- archinstall/lib/models/users.py | 4 ++-- archinstall/lib/storage.py | 4 ++-- archinstall/lib/systemd.py | 2 +- archinstall/lib/translation.py | 2 +- archinstall/lib/user_interaction/backwards_compatible_conf.py | 2 +- archinstall/lib/user_interaction/partitioning_conf.py | 2 +- docs/index.rst | 2 +- docs/installing/guided.rst | 4 ++-- examples/swiss.py | 8 ++++---- profiles/server.py | 2 +- 24 files changed, 44 insertions(+), 44 deletions(-) (limited to 'archinstall/lib') diff --git a/README.md b/README.md index 79fae095..20224eea 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Some additional options that are not needed by most users are hidden behind the ## Running from a declarative configuration file or URL -Prequisites: +Prerequisites: 1. Edit the [configuration file](https://github.com/archlinux/archinstall/blob/master/examples/config-sample.json) according to your requirements. Assuming you are on a Arch Linux live-ISO and booted into EFI mode. @@ -55,7 +55,7 @@ The guided installer itself is also optional to use if so desired and not forced Archinstall has one fundamental function which is to be a flexible library to manage services, packages and other aspects inside the installed system. This library is in turn used by the provided guided installer but is also for anyone who wants to script their own installations. -Therefore, Archinstall will try its best to not introduce any breaking changes except for major releases which may break backwards compability after notifying about such changes. +Therefore, Archinstall will try its best to not introduce any breaking changes except for major releases which may break backwards compatibility after notifying about such changes. # Scripting your own installation diff --git a/archinstall/__init__.py b/archinstall/__init__.py index 7edeaa80..786be1c5 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -91,7 +91,7 @@ def parse_unspecified_argument_list(unknowns :list, multiple :bool = False, erro --argument=value --argument = value --argument (boolean as default) - the optional paramters to the function alter a bit its behaviour: + the optional parameters to the function alter a bit its behaviour: * multiple allows multivalued arguments, each value separated by whitespace. They're returned as a list * error. If set any non correctly specified argument-value pair to raise an exception. Else, simply notifies the existence of a problem and continues processing. @@ -104,7 +104,7 @@ def parse_unspecified_argument_list(unknowns :list, multiple :bool = False, erro key = None last_key = None while tmp_list: - element = tmp_list.pop(0) # retreive an element of the list + element = tmp_list.pop(0) # retrieve an element of the list if element.startswith('--'): # is an argument ? if '=' in element: # uses the arg=value syntax ? key, value = [x.strip() for x in element[2:].split('=', 1)] diff --git a/archinstall/lib/disk/btrfs/__init__.py b/archinstall/lib/disk/btrfs/__init__.py index 84b9c0f6..90c58145 100644 --- a/archinstall/lib/disk/btrfs/__init__.py +++ b/archinstall/lib/disk/btrfs/__init__.py @@ -73,7 +73,7 @@ def create_subvolume(installation :Installer, subvolume_location :Union[pathlib. def _has_option(option :str,options :list) -> bool: """ auxiliary routine to check if an option is present in a list. - we check if the string appears in one of the options, 'cause it can appear in severl forms (option, option=val,...) + we check if the string appears in one of the options, 'cause it can appear in several forms (option, option=val,...) """ if not options: return False @@ -110,7 +110,7 @@ def manage_btrfs_subvolumes(installation :Installer, subvolumes = partition['btrfs']['subvolumes'] for name, right_hand in subvolumes.items(): try: - # we normalize the subvolume name (getting rid of slash at the start if exists. In our implemenation has no semantic load - every subvolume is created from the top of the hierarchy- and simplifies its further use + # we normalize the subvolume name (getting rid of slash at the start if exists. In our implementation has no semantic load - every subvolume is created from the top of the hierarchy- and simplifies its further use if name.startswith('/'): name = name[1:] # renormalize the right hand. diff --git a/archinstall/lib/disk/btrfs/btrfs_helpers.py b/archinstall/lib/disk/btrfs/btrfs_helpers.py index d577d82b..5fa94314 100644 --- a/archinstall/lib/disk/btrfs/btrfs_helpers.py +++ b/archinstall/lib/disk/btrfs/btrfs_helpers.py @@ -10,7 +10,7 @@ from .btrfssubvolume import BtrfsSubvolume def mount_subvolume(installation, device, name, subvolume_information): - # we normalize the subvolume name (getting rid of slash at the start if exists. In our implemenation has no semantic load. + # we normalize the subvolume name (getting rid of slash at the start if exists. In our implementation has no semantic load. # Every subvolume is created from the top of the hierarchy- and simplifies its further use name = name.lstrip('/') @@ -53,7 +53,7 @@ def setup_subvolumes(installation, partition_dict): """ log(f"Setting up subvolumes: {partition_dict['btrfs']['subvolumes']}", level=logging.INFO, fg="gray") for name, right_hand in partition_dict['btrfs']['subvolumes'].items(): - # we normalize the subvolume name (getting rid of slash at the start if exists. In our implemenation has no semantic load. + # we normalize the subvolume name (getting rid of slash at the start if exists. In our implementation has no semantic load. # Every subvolume is created from the top of the hierarchy- and simplifies its further use name = name.lstrip('/') diff --git a/archinstall/lib/disk/btrfs/btrfspartition.py b/archinstall/lib/disk/btrfs/btrfspartition.py index 299357b8..6f7487e4 100644 --- a/archinstall/lib/disk/btrfs/btrfspartition.py +++ b/archinstall/lib/disk/btrfs/btrfspartition.py @@ -62,13 +62,13 @@ class BTRFSPartition(Partition): if not installation: installation = storage.get('installation_session') - # Determain if the path given, is an absolute path or a releative path. + # Determain if the path given, is an absolute path or a relative path. # We do this by checking if the path contains a known mountpoint. if str(subvolume)[0] == '/': if filesystems := findmnt(subvolume, traverse=True).get('filesystems'): if (target := filesystems[0].get('target')) and target != '/' and str(subvolume).startswith(target): # Path starts with a known mountpoint which isn't / - # Which means it's an absolut path to a mounted location. + # Which means it's an absolute path to a mounted location. pass else: # Since it's not an absolute position with a known start. diff --git a/archinstall/lib/disk/btrfs/btrfssubvolume.py b/archinstall/lib/disk/btrfs/btrfssubvolume.py index a96e2a94..bc7db612 100644 --- a/archinstall/lib/disk/btrfs/btrfssubvolume.py +++ b/archinstall/lib/disk/btrfs/btrfssubvolume.py @@ -68,9 +68,9 @@ class BtrfsSubvolume: from .btrfs_helpers import subvolume_info_from_path # TODO: Make this function traverse storage['MOUNT_POINT'] and find the first - # occurance of a mountpoint that is a btrfs volume instead of lazy assume / is a subvolume. + # occurrence of a mountpoint that is a btrfs volume instead of lazy assume / is a subvolume. # It would also be nice if it could use findmnt(self.full_path) and traverse backwards - # finding the last occurance of a subvolume which 'self' belongs to. + # finding the last occurrence of a subvolume which 'self' belongs to. if volume := subvolume_info_from_path(storage['MOUNT_POINT']): return self.full_path == volume.full_path diff --git a/archinstall/lib/disk/filesystem.py b/archinstall/lib/disk/filesystem.py index f94b4b47..cc29a491 100644 --- a/archinstall/lib/disk/filesystem.py +++ b/archinstall/lib/disk/filesystem.py @@ -74,7 +74,7 @@ class Filesystem: raise KeyError(f"Could not create a GPT label on {self}") elif self.mode == MBR: if not self.parted_mklabel(self.blockdevice.device, "msdos"): - raise KeyError(f"Could not create a MSDOS label on {self}") + raise KeyError(f"Could not create a MS-DOS label on {self}") self.blockdevice.flush_cache() time.sleep(3) @@ -221,7 +221,7 @@ class Filesystem: raise KeyError(f"Could not create a GPT label on {self}") elif self.mode == MBR: if not self.parted_mklabel(self.blockdevice.device, "msdos"): - raise KeyError(f"Could not create a MSDOS label on {self}") + raise KeyError(f"Could not create a MS-DOS label on {self}") self.blockdevice.flush_cache() diff --git a/archinstall/lib/disk/helpers.py b/archinstall/lib/disk/helpers.py index 65d7a006..47cc81c4 100644 --- a/archinstall/lib/disk/helpers.py +++ b/archinstall/lib/disk/helpers.py @@ -112,7 +112,7 @@ def cleanup_bash_escapes(data :str) -> str: def blkid(cmd :str) -> Dict[str, Any]: if '-o' in cmd and '-o export' not in cmd: - raise ValueError(f"blkid() requires '-o export' to be used and can therefor not continue reliably.") + raise ValueError(f"blkid() requires '-o export' to be used and can therefore not continue reliably.") elif '-o' not in cmd: cmd += ' -o export' @@ -133,7 +133,7 @@ def blkid(cmd :str) -> Dict[str, Any]: key, val = line.split('=', 1) if key.lower() == 'devname': devname = val - # Lowercase for backwards compatability with all_disks() previous use cases + # Lowercase for backwards compatibility with all_disks() previous use cases result[devname] = { "path": devname, "PATH": devname @@ -475,7 +475,7 @@ def has_mountpoint(partition: Union[dict,Partition,MapperDev], target: str, stri Input parms: :parm partition the partition we check - :type Either a Partition object or a dict with the contents of a partition definiton in the disk_layouts schema + :type Either a Partition object or a dict with the contents of a partition definition in the disk_layouts schema :parm target (a string representing a mount path we want to check for. :type str diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py index b99e4a45..3ec1d685 100644 --- a/archinstall/lib/general.py +++ b/archinstall/lib/general.py @@ -207,7 +207,7 @@ class SysCommandWorker: self.cmd = cmd self.callbacks = callbacks self.peak_output = peak_output - # define the standard locale for command outputs. For now the C ascii one. Can be overriden + # define the standard locale for command outputs. For now the C ascii one. Can be overridden self.environment_vars = {**storage.get('CMD_LOCALE',{}),**environment_vars} self.logfile = logfile self.working_directory = working_directory @@ -354,7 +354,7 @@ class SysCommandWorker: # Note: If for any reason, we get a Python exception between here # and until os.close(), the traceback will get locked inside # stdout of the child_fd object. `os.read(self.child_fd, 8192)` is the - # only way to get the traceback without loosing it. + # only way to get the traceback without losing it. self.pid, self.child_fd = pty.fork() @@ -547,7 +547,7 @@ def json_stream_to_structure(configuration_identifier : str, stream :str, target parsed_url = urllib.parse.urlparse(stream) - if parsed_url.scheme: # The stream is in fact a URL that should be grabed + if parsed_url.scheme: # The stream is in fact a URL that should be grabbed with urllib.request.urlopen(urllib.request.Request(stream, headers={'User-Agent': 'ArchInstall'})) as response: target.update(json.loads(response.read())) else: diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index b2cd6306..7af6006f 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -195,7 +195,7 @@ class Installer: return True def _create_keyfile(self,luks_handle , partition :dict, password :str): - """ roiutine to create keyfiles, so it can be moved elsewere + """ roiutine to create keyfiles, so it can be moved elsewhere """ if partition.get('generate-encryption-key-file'): if not (cryptkey_dir := pathlib.Path(f"{self.target}/etc/cryptsetup-keys.d")).exists(): @@ -413,7 +413,7 @@ class Installer: try: run_pacman('-Syy', default_cmd='/usr/bin/pacman') except SysCallError as error: - self.log(f'Could not sync a new package databse: {error}', level=logging.ERROR, fg="red") + self.log(f'Could not sync a new package database: {error}', level=logging.ERROR, fg="red") if storage['arguments'].get('silent', False) is False: if input('Would you like to re-try this download? (Y/n): ').lower().strip() in ('', 'y'): diff --git a/archinstall/lib/menu/list_manager.py b/archinstall/lib/menu/list_manager.py index cb567093..7e051528 100644 --- a/archinstall/lib/menu/list_manager.py +++ b/archinstall/lib/menu/list_manager.py @@ -54,7 +54,7 @@ The default implementation can handle simple lists and a key:value dictionary. T A sample of basic usage is included at the end of the source. More sophisticaded uses can be achieved by -* changing the action list and the null_action during intialization +* changing the action list and the null_action during initialization ``` opciones = ListManager('Vamos alla',opciones,[str(_('Add')),str(_('Delete'))],_('Add')).run() ``` @@ -198,7 +198,7 @@ class ListManager: else: self.target = self._data[data_formatted[target.value]] - # Possible enhacement. If run_actions returns false a message line indicating the failure + # Possible enhancement. If run_actions returns false a message line indicating the failure self.run_actions(target.value) if target.value == self.cancel_action: # TODO dubious diff --git a/archinstall/lib/menu/menu.py b/archinstall/lib/menu/menu.py index c34814eb..3a26f6e7 100644 --- a/archinstall/lib/menu/menu.py +++ b/archinstall/lib/menu/menu.py @@ -115,7 +115,7 @@ class Menu(TerminalMenu): # We check that the options are iterable. If not we abort. Else we copy them to lists # it options is a dictionary we use the values as entries of the list # if options is a string object, each character becomes an entry - # if options is a list, we implictily build a copy to mantain immutability + # if options is a list, we implictily build a copy to maintain immutability if not isinstance(p_options,Iterable): log(f"Objects of type {type(p_options)} is not iterable, and are not supported at Menu",fg="red") log(f"invalid parameter at Menu() call was at <{sys._getframe(1).f_code.co_name}>",level=logging.WARNING) diff --git a/archinstall/lib/menu/selection_menu.py b/archinstall/lib/menu/selection_menu.py index 57e290f1..6a693730 100644 --- a/archinstall/lib/menu/selection_menu.py +++ b/archinstall/lib/menu/selection_menu.py @@ -219,7 +219,7 @@ class GeneralMenu: def _setup_selection_menu_options(self): """ Define the menu options. - Menu options can be defined here in a subclass or done per progam calling self.set_option() + Menu options can be defined here in a subclass or done per program calling self.set_option() """ return @@ -347,7 +347,7 @@ class GeneralMenu: return self.exec_option(config_name, selector) def exec_option(self, config_name :str, p_selector :Selector = None) -> bool: - """ processes the exection of a given menu entry + """ processes the execution of a given menu entry - pre process callback - selection function - post process callback diff --git a/archinstall/lib/menu/simple_menu.py b/archinstall/lib/menu/simple_menu.py index 947259eb..f7a2cf23 100644 --- a/archinstall/lib/menu/simple_menu.py +++ b/archinstall/lib/menu/simple_menu.py @@ -619,7 +619,7 @@ class TerminalMenu: else: unit_separated_entry = escaped_separator_pattern.sub("|", separator_pattern.sub("\\1\x1F", entry)) match_obj = menu_entry_pattern.match(unit_separated_entry) - # this is none in case the entry was an emtpy string which + # this is none in case the entry was an empty string which # will be interpreted as a separator assert match_obj is not None shortcut_key = match_obj.group(1) diff --git a/archinstall/lib/models/users.py b/archinstall/lib/models/users.py index 6052b73a..a3057291 100644 --- a/archinstall/lib/models/users.py +++ b/archinstall/lib/models/users.py @@ -64,13 +64,13 @@ class User: ) -> List['User']: users = [] - # backwards compability + # backwards compatibility if isinstance(config_users, dict): users += cls._parse_backwards_compatible(config_users, False) else: users += cls._parse(config_users) - # backwards compability + # backwards compatibility if isinstance(config_superusers, dict): users += cls._parse_backwards_compatible(config_superusers, True) diff --git a/archinstall/lib/storage.py b/archinstall/lib/storage.py index dd7ddc88..8c358161 100644 --- a/archinstall/lib/storage.py +++ b/archinstall/lib/storage.py @@ -17,13 +17,13 @@ storage: Dict[str, Any] = { # os.path.abspath(f'{os.path.dirname(__file__)}/../examples') ], 'UPSTREAM_URL': 'https://raw.githubusercontent.com/archlinux/archinstall/master/profiles', - 'PROFILE_DB': None, # Used in cases when listing profiles is desired, not mandatory for direct profile grabing. + 'PROFILE_DB': None, # Used in cases when listing profiles is desired, not mandatory for direct profile grabbing. 'LOG_PATH': '/var/log/archinstall', 'LOG_FILE': 'install.log', 'MOUNT_POINT': '/mnt/archinstall', 'ENC_IDENTIFIER': 'ainst', 'DISK_TIMEOUTS' : 1, # seconds 'DISK_RETRY_ATTEMPTS' : 5, # RETRY_ATTEMPTS * DISK_TIMEOUTS is used in disk operations - 'CMD_LOCALE':{'LC_ALL':'C'}, # default locale for execution commands. Can be overriden with set_cmd_locale() + 'CMD_LOCALE':{'LC_ALL':'C'}, # default locale for execution commands. Can be overridden with set_cmd_locale() 'CMD_LOCALE_DEFAULT':{'LC_ALL':'C'}, # should be the same as the former. Not be used except in reset_cmd_locale() } diff --git a/archinstall/lib/systemd.py b/archinstall/lib/systemd.py index 3d2f0385..f459f94b 100644 --- a/archinstall/lib/systemd.py +++ b/archinstall/lib/systemd.py @@ -88,7 +88,7 @@ class Boot: if len(args) >= 2 and args[1]: log(args[1], level=logging.ERROR, fg='red') - log(f"The error above occured in a temporary boot-up of the installation {self.instance}", level=logging.ERROR, fg="red") + log(f"The error above occurred in a temporary boot-up of the installation {self.instance}", level=logging.ERROR, fg="red") shutdown = None shutdown_exit_code = -1 diff --git a/archinstall/lib/translation.py b/archinstall/lib/translation.py index 1a0e94e4..79e0198a 100644 --- a/archinstall/lib/translation.py +++ b/archinstall/lib/translation.py @@ -28,7 +28,7 @@ class LanguageDefinitions: if entry['abbr'] == abbr: return entry['lang'] - raise ValueError(f'No language with abbrevation "{abbr}" found') + raise ValueError(f'No language with abbreviation "{abbr}" found') class DeferredTranslation: diff --git a/archinstall/lib/user_interaction/backwards_compatible_conf.py b/archinstall/lib/user_interaction/backwards_compatible_conf.py index d91690eb..296572d2 100644 --- a/archinstall/lib/user_interaction/backwards_compatible_conf.py +++ b/archinstall/lib/user_interaction/backwards_compatible_conf.py @@ -40,7 +40,7 @@ def generic_select( # We check that the options are iterable. If not we abort. Else we copy them to lists # it options is a dictionary we use the values as entries of the list # if options is a string object, each character becomes an entry - # if options is a list, we implictily build a copy to mantain immutability + # if options is a list, we implictily build a copy to maintain immutability if not isinstance(p_options, Iterable): log(f"Objects of type {type(p_options)} is not iterable, and are not supported at generic_select", fg="red") log(f"invalid parameter at Menu() call was at <{sys._getframe(1).f_code.co_name}>", level=logging.WARNING) diff --git a/archinstall/lib/user_interaction/partitioning_conf.py b/archinstall/lib/user_interaction/partitioning_conf.py index 1ecba8be..caf5f5df 100644 --- a/archinstall/lib/user_interaction/partitioning_conf.py +++ b/archinstall/lib/user_interaction/partitioning_conf.py @@ -224,7 +224,7 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, continue block_device_struct["partitions"].append({ - "type": "primary", # Strictly only allowed under MSDOS, but GPT accepts it so it's "safe" to inject + "type": "primary", # Strictly only allowed under MS-DOS, but GPT accepts it so it's "safe" to inject "start": start, "size": end, "mountpoint": None, diff --git a/docs/index.rst b/docs/index.rst index 3e4b5203..a76a58d6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,7 +12,7 @@ Some of the features of Archinstall are: * **Context friendly.** The library always executes calls in sequential order to ensure installation-steps don't overlap or execute in the wrong order. It also supports *(and uses)* context wrappers to ensure cleanup and final tasks such as ``mkinitcpio`` are called when needed. -* **Full transparancy** Logs and insights can be found at ``/var/log/archinstall`` both in the live ISO and the installed system. +* **Full transparency** Logs and insights can be found at ``/var/log/archinstall`` both in the live ISO and the installed system. * **Accessibility friendly** Archinstall works with ``espeakup`` and other accessibility tools thanks to the use of a TUI. diff --git a/docs/installing/guided.rst b/docs/installing/guided.rst index 569b2d05..b7d4db4f 100644 --- a/docs/installing/guided.rst +++ b/docs/installing/guided.rst @@ -115,7 +115,7 @@ Options for ``--config`` +----------------------+--------------------------------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------+ | hostname | any | Hostname of machine after installation. Default will be ``archinstall`` | No | +----------------------+--------------------------------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------+ -| kernels | [ "kernel1", "kernel2"] | List of kernels to install eg: linux, linux-lts, linux-zen etc | Atleast 1 | +| kernels | [ "kernel1", "kernel2"] | List of kernels to install eg: linux, linux-lts, linux-zen etc | At least 1 | +----------------------+--------------------------------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------+ | keyboard-language | Any valid layout given by ``localectl list-keymaps`` | eg: ``us``, ``de`` or ``de-latin1`` etc. Defaults to ``us`` | No | +----------------------+--------------------------------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------+ @@ -149,7 +149,7 @@ Options for ``--config`` +----------------------+--------------------------------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------+ .. note:: - [1] If no entires are found in ``harddrives``, archinstall guided installation will use whatever is mounted currently under ``/mnt/archinstall``. + [1] If no entries are found in ``harddrives``, archinstall guided installation will use whatever is mounted currently under ``/mnt/archinstall``. Options for ``--creds`` ----------------------- diff --git a/examples/swiss.py b/examples/swiss.py index d0f02dc1..6742358e 100644 --- a/examples/swiss.py +++ b/examples/swiss.py @@ -1,14 +1,14 @@ """ Script swiss (army knife) -Designed to make different workflows for the installation process. Which is controled by the argument --mode +Designed to make different workflows for the installation process. Which is controlled by the argument --mode mode full guides the full process of installation mode only_hd only proceeds to the creation of the disk infraestructure (partition, mount points, encryption) mode only_os processes only the installation of Archlinux and software at --mountpoint (or /mnt/archinstall) mode minimal (still not implemented) mode lineal. Instead of a menu, shows a sequence of selection screens (eq. to the old mode for guided.py) -When using the argument --advanced. an aditional menu for several special parameters needed during installation appears +When using the argument --advanced. an additional menu for several special parameters needed during installation appears This script respects the --dry_run argument @@ -180,7 +180,7 @@ class SetupMenu(archinstall.GeneralMenu): self.set_option(item, archinstall.Selector( f'{get_locale_mode_text(item)} locale', - lambda x,item=item: select_installed_locale(item), # the parmeter is needed for the lambda in the loop + lambda x,item=item: select_installed_locale(item), # the parameter is needed for the lambda in the loop enabled=True, dependencies_not=['LC_ALL'] if item != 'LC_ALL' else [])) self.option('LC_ALL').set_enabled(True) @@ -286,7 +286,7 @@ class MyMenu(archinstall.GlobalMenu): """ -Instalation general subroutines +Installation general subroutines """ def get_current_status(): diff --git a/profiles/server.py b/profiles/server.py index 21681c2f..f3e32d26 100644 --- a/profiles/server.py +++ b/profiles/server.py @@ -33,7 +33,7 @@ def _prep_function(*args, **kwargs): before continuing any further. """ choice = Menu(str(_( - 'Choose which servers to install, if none then a minimal installation wil be done')), + 'Choose which servers to install, if none then a minimal installation will be done')), available_servers, preset_values=kwargs['servers'], multi=True -- cgit v1.2.3-70-g09d2 From 7daf9b32d0c32d9fa1ba5aa9750376ad47c6d7e6 Mon Sep 17 00:00:00 2001 From: demostanis <40673815+demostanis@users.noreply.github.com> Date: Sun, 29 May 2022 07:34:32 +0000 Subject: Fix "Unknown device" error when using erofs (#1232) (#1275) --- archinstall/lib/disk/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk/helpers.py b/archinstall/lib/disk/helpers.py index 47cc81c4..85c0390f 100644 --- a/archinstall/lib/disk/helpers.py +++ b/archinstall/lib/disk/helpers.py @@ -244,7 +244,7 @@ def all_blockdevices(mappers=False, partitions=False, error=False) -> Dict[str, instances[path] = Partition(path, block_device=BlockDevice(get_parent_of_partition(pathlib.Path(path)))) elif path_info.get('PTTYPE', False) is not False or path_info.get('TYPE') == 'loop': instances[path] = BlockDevice(path, path_info) - elif path_info.get('TYPE') == 'squashfs': + elif path_info.get('TYPE') in ('squashfs', 'erofs'): # We can ignore squashfs devices (usually /dev/loop0 on Arch ISO) continue else: -- cgit v1.2.3-70-g09d2 From 0601956b5bdf27ec497a94e7313ea22cf21ecbe0 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 29 May 2022 09:44:31 +0200 Subject: Moved genfstab() to guided.py instead of __exit__ of Installer(). This is a breaking change. --- archinstall/lib/installer.py | 2 -- examples/guided.py | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 7af6006f..7e66559f 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -158,8 +158,6 @@ class Installer: print(_(" Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues")) raise args[1] - self.genfstab() - if not (missing_steps := self.post_install_check()): self.log('Installation completed without any errors. You may now reboot.', fg='green', level=logging.INFO) self.sync_log_to_install_medium() diff --git a/examples/guided.py b/examples/guided.py index 19b0d638..635baf6a 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -253,6 +253,8 @@ def perform_installation(mountpoint): if archinstall.arguments.get('custom-commands', None): archinstall.run_custom_user_commands(archinstall.arguments['custom-commands'], installation) + installation.genfstab() + installation.log("For post-installation tips, see https://wiki.archlinux.org/index.php/Installation_guide#Post-installation", fg="yellow") if not archinstall.arguments.get('silent'): prompt = str(_('Would you like to chroot into the newly created installation and perform post-installation configuration?')) -- cgit v1.2.3-70-g09d2 From 51714587962570c71fc2dd70de526a5172ad5b5c Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Sun, 29 May 2022 18:59:55 +1000 Subject: Fix subvol selection (#1277) * Fix subvolume selection * Update Co-authored-by: Daniel Girtler --- archinstall/lib/menu/selection_menu.py | 12 ++++---- .../lib/user_interaction/subvolume_config.py | 33 ++++++++++++++-------- 2 files changed, 27 insertions(+), 18 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/menu/selection_menu.py b/archinstall/lib/menu/selection_menu.py index 6a693730..d4a7ceef 100644 --- a/archinstall/lib/menu/selection_menu.py +++ b/archinstall/lib/menu/selection_menu.py @@ -227,7 +227,7 @@ class GeneralMenu: """ will be called before each action in the menu """ return - def post_callback(self, selector_name :str, value :Any): + def post_callback(self, selection_name: str = None, value: Any = None): """ will be called after each action in the menu """ return True @@ -327,12 +327,12 @@ class GeneralMenu: break cursor_pos += 1 - value = value.strip() + value = value.strip() - # if this calls returns false, we exit the menu - # we allow for an callback for special processing on realeasing control - if not self._process_selection(value): - break + # if this calls returns false, we exit the menu + # we allow for an callback for special processing on realeasing control + if not self._process_selection(value): + break if not self.is_context_mgr: self.__exit__() diff --git a/archinstall/lib/user_interaction/subvolume_config.py b/archinstall/lib/user_interaction/subvolume_config.py index af783639..94e6f5d7 100644 --- a/archinstall/lib/user_interaction/subvolume_config.py +++ b/archinstall/lib/user_interaction/subvolume_config.py @@ -80,24 +80,33 @@ class SubvolumeMenu(GeneralMenu): super().__init__(data_store=self.ds) def _setup_selection_menu_options(self): - # [str(_('Add')),str(_('Copy')),str(_('Edit')),str(_('Delete'))] - self._menu_options['name'] = Selector(str(_('Subvolume name ')), - self._select_subvolume_name if not self.action or self.action in (str(_('Add')),str(_('Copy'))) else None, + self._menu_options['name'] = Selector( + str(_('Subvolume name ')), + self._select_subvolume_name if not self.action or self.action in (str(_('Add')), str(_('Copy'))) else None, mandatory=True, enabled=True) - self._menu_options['mountpoint'] = Selector(str(_('Subvolume mountpoint')), + + self._menu_options['mountpoint'] = Selector( + str(_('Subvolume mountpoint')), self._select_subvolume_mount_point if not self.action or self.action in (str(_('Add')),str(_('Edit'))) else None, enabled=True) - self._menu_options['options'] = Selector(str(_('Subvolume options')), + + self._menu_options['options'] = Selector( + str(_('Subvolume options')), self._select_subvolume_options if not self.action or self.action in (str(_('Add')),str(_('Edit'))) else None, enabled=True) - self._menu_options['save'] = Selector(str(_('Save')), - exec_func=lambda n,v:True, - enabled=True) - self._menu_options['cancel'] = Selector(str(_('Cancel')), - # func = lambda pre:True, - exec_func=lambda n,v:self.fast_exit(n), - enabled=True) + + self._menu_options['save'] = Selector( + str(_('Save')), + exec_func=lambda n,v:True, + enabled=True) + + self._menu_options['cancel'] = Selector( + str(_('Cancel')), + # func = lambda pre:True, + exec_func=lambda n,v:self.fast_exit(n), + enabled=True) + self.cancel_action = 'cancel' self.save_action = 'save' self.bottom_list = [self.save_action,self.cancel_action] -- cgit v1.2.3-70-g09d2 From 7dbea73514b35cbaa18c156895bf6f416b2345ca Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 29 May 2022 11:25:28 +0200 Subject: Cleanup and version changes in prep for release --- PKGBUILD | 2 +- archinstall/lib/disk/partition.py | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) (limited to 'archinstall/lib') diff --git a/PKGBUILD b/PKGBUILD index 5821bee8..d8e89ae2 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -4,7 +4,7 @@ # Contributor: demostanis worlds pkgname=archinstall -pkgver=2.4.3rc1 +pkgver=2.5.0 #pkgver=$(git describe --long | sed 's/\([^-]*-g\)/r\1/;s/-/./g') pkgrel=1 pkgdesc="Just another guided/automated Arch Linux installer with a twist" diff --git a/archinstall/lib/disk/partition.py b/archinstall/lib/disk/partition.py index 151775b1..2c9f50c2 100644 --- a/archinstall/lib/disk/partition.py +++ b/archinstall/lib/disk/partition.py @@ -160,16 +160,6 @@ class Partition: def boot(self) -> bool: output = json.loads(SysCommand(f"sfdisk --json {self.block_device.path}").decode('UTF-8')) - # Get the bootable flag from the sfdisk output: - # { - # "partitiontable": { - # "device":"/dev/loop0", - # "partitions": [ - # {"node":"/dev/loop0p1", "start":2048, "size":10483712, "type":"83", "bootable":true} - # ] - # } - # } - for partition in output.get('partitiontable', {}).get('partitions', []): if partition['node'] == self.path: # first condition is for MBR disks, second for GPT disks -- cgit v1.2.3-70-g09d2 From c75e6a1da3c243126eaf59ee12f0b2464449e5e2 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 29 May 2022 13:59:25 +0200 Subject: Made sure generate-encryption-key-file is set for supplementary partitions to / (#1281) --- archinstall/lib/installer.py | 4 ++-- archinstall/lib/menu/global_menu.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 7e66559f..bf296c2e 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -253,8 +253,8 @@ class Installer: # 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]) + if partition.get('generate-encryption-key-file', False) and not self._has_root(partition): + list_luks_handles.append([luks_handle, partition, password]) # this way all the requesrs will be to the dm_crypt device and not to the physical partition partition['device_instance'] = unlocked_device diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index a758d8c6..cb61168d 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -215,6 +215,11 @@ class GlobalMenu(GeneralMenu): 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: -- cgit v1.2.3-70-g09d2 From 7943dd82365fd9fb5034a0f1c05de3ccabda468a Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Thu, 2 Jun 2022 13:32:42 +0200 Subject: Added more offline functionality, such as skipping package search (#1296) * Added more offline functionality, such as skipping package search * Disabled list_mirrors() from going online if --offline is given. Defaults to /etc/pacman.d/mirrorlist instead. * Forgot import of pathlib * Made list_mirrors() open /etc/pacman.d/mirrorlist in byte mode to better emulate the result of urllib response reading. * Forgot variable declaration * Made list_mirrors include activated server definitions --- archinstall/__init__.py | 3 ++ archinstall/lib/mirrors.py | 27 ++++++++++++----- archinstall/lib/user_interaction/general_conf.py | 37 ++++++++++++------------ examples/guided.py | 2 +- 4 files changed, 43 insertions(+), 26 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/__init__.py b/archinstall/__init__.py index 786be1c5..ee5e5f45 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -81,6 +81,8 @@ def define_arguments(): parser.add_argument("--script", default="guided", nargs="?", help="Script to run for installation", type=str) parser.add_argument("--mount-point","--mount_point", nargs="?", type=str, help="Define an alternate mount point for installation") parser.add_argument("--debug", action="store_true", default=False, help="Adds debug info into the log") + parser.add_argument("--offline", action="store_true", default=False, help="Disabled online upstream services such as package search and key-ring auto update.") + parser.add_argument("--no-pkg-lookups", action="store_true", default=False, help="Disabled package validation specifically prior to starting installation.") parser.add_argument("--plugin", nargs="?", type=str) def parse_unspecified_argument_list(unknowns :list, multiple :bool = False, error :bool = False) -> dict: @@ -172,6 +174,7 @@ def get_arguments() -> Dict[str, Any]: # avoiding a compatibility issue if 'dry-run' in config: del config['dry-run'] + return config def load_config(): diff --git a/archinstall/lib/mirrors.py b/archinstall/lib/mirrors.py index 73921cef..d76e0473 100644 --- a/archinstall/lib/mirrors.py +++ b/archinstall/lib/mirrors.py @@ -1,10 +1,12 @@ import logging +import pathlib import urllib.error import urllib.request from typing import Union, Mapping, Iterable, Dict, Any, List from .general import SysCommand from .output import log +from .storage import storage def sort_mirrorlist(raw_data :bytes, sort_order=["https", "http"]) -> bytes: """ @@ -144,16 +146,22 @@ def re_rank_mirrors( def list_mirrors(sort_order :List[str] = ["https", "http"]) -> Dict[str, Any]: - url = "https://archlinux.org/mirrorlist/?protocol=https&protocol=http&ip_version=4&ip_version=6&use_mirror_status=on" regions = {} - try: - response = urllib.request.urlopen(url) - except urllib.error.URLError as err: - log(f'Could not fetch an active mirror-list: {err}', level=logging.WARNING, fg="orange") - return regions + if storage['arguments']['offline']: + with pathlib.Path('/etc/pacman.d/mirrorlist').open('rb') as fh: + mirrorlist = fh.read() + else: + url = "https://archlinux.org/mirrorlist/?protocol=https&protocol=http&ip_version=4&ip_version=6&use_mirror_status=on" + + try: + response = urllib.request.urlopen(url) + except urllib.error.URLError as err: + log(f'Could not fetch an active mirror-list: {err}', level=logging.WARNING, fg="orange") + return regions + + mirrorlist = response.read() - mirrorlist = response.read() if sort_order: mirrorlist = sort_mirrorlist(mirrorlist, sort_order=sort_order) @@ -170,5 +178,10 @@ def list_mirrors(sort_order :List[str] = ["https", "http"]) -> Dict[str, Any]: url = line.lstrip('#Server = ') regions[region][url] = True + elif line.startswith('Server = '): + regions.setdefault(region, {}) + + url = line.lstrip('Server = ') + regions[region][url] = True return regions diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py index f1d56fc1..70a0e73f 100644 --- a/archinstall/lib/user_interaction/general_conf.py +++ b/archinstall/lib/user_interaction/general_conf.py @@ -3,8 +3,6 @@ from __future__ import annotations import logging from typing import List, Any, Optional, Dict, TYPE_CHECKING -import archinstall - from ..menu.menu import MenuSelectionType from ..menu.text_input import TextInput @@ -17,6 +15,8 @@ from ..mirrors import list_mirrors from ..translation import Translation from ..packages.packages import validate_package_list +from ..storage import storage + if TYPE_CHECKING: _: Any @@ -155,11 +155,11 @@ def select_profile(preset) -> Optional[Profile]: case MenuSelectionType.Selection: return options[selection.value] if selection.value is not None else None case MenuSelectionType.Ctrl_c: - archinstall.storage['profile_minimal'] = False - archinstall.storage['_selected_servers'] = [] - archinstall.storage['_desktop_profile'] = None - archinstall.arguments['desktop-environment'] = None - archinstall.arguments['gfx_driver_packages'] = None + 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: return None @@ -178,17 +178,18 @@ def ask_additional_packages_to_install(pre_set_packages: List[str] = []) -> List pre_set_packages = pre_set_packages if pre_set_packages else [] packages = read_packages(pre_set_packages) - while True: - if len(packages): - # Verify packages that were given - print(_("Verifying that additional packages exist (this might take a few seconds)")) - valid, invalid = validate_package_list(packages) - - if invalid: - log(f"Some packages could not be found in the repository: {invalid}", level=logging.WARNING, fg='red') - packages = read_packages(valid) - continue - break + if not storage['arguments']['offline'] and not storage['arguments']['no_pkg_lookups']: + while True: + if len(packages): + # Verify packages that were given + print(_("Verifying that additional packages exist (this might take a few seconds)")) + valid, invalid = validate_package_list(packages) + + if invalid: + log(f"Some packages could not be found in the repository: {invalid}", level=logging.WARNING, fg='red') + packages = read_packages(valid) + continue + break return packages diff --git a/examples/guided.py b/examples/guided.py index 635baf6a..8693b98f 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -274,7 +274,7 @@ if not (archinstall.check_mirror_reachable() or archinstall.arguments.get('skip- archinstall.log(f"Arch Linux mirrors are not reachable. Please check your internet connection and the log file '{log_file}'.", level=logging.INFO, fg="red") exit(1) -if not archinstall.arguments.get('offline', False): +if not archinstall.arguments['offline']: latest_version_archlinux_keyring = max([k.pkg_version for k in archinstall.find_package('archlinux-keyring')]) # If we want to check for keyring updates -- cgit v1.2.3-70-g09d2 From f2492ca574448fe4bd44604316da322720e70040 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Mon, 6 Jun 2022 21:04:50 +1000 Subject: Fix #1304 - Make password validation less intrusive (#1308) * Make password validation less intrusive * Update Co-authored-by: Daniel Girtler --- archinstall/lib/models/password_strength.py | 85 +++++++++++++++++++++++++++++ archinstall/lib/models/users.py | 7 ++- archinstall/lib/user_interaction/utils.py | 32 +++-------- 3 files changed, 97 insertions(+), 27 deletions(-) create mode 100644 archinstall/lib/models/password_strength.py (limited to 'archinstall/lib') diff --git a/archinstall/lib/models/password_strength.py b/archinstall/lib/models/password_strength.py new file mode 100644 index 00000000..61986bf0 --- /dev/null +++ b/archinstall/lib/models/password_strength.py @@ -0,0 +1,85 @@ +from enum import Enum + + +class PasswordStrength(Enum): + VERY_WEAK = 'very weak' + WEAK = 'weak' + MODERATE = 'moderate' + STRONG = 'strong' + + @property + def value(self): + match self: + case PasswordStrength.VERY_WEAK: return str(_('very weak')) + case PasswordStrength.WEAK: return str(_('weak')) + case PasswordStrength.MODERATE: return str(_('moderate')) + case PasswordStrength.STRONG: return str(_('strong')) + + def color(self): + match self: + case PasswordStrength.VERY_WEAK: return 'red' + case PasswordStrength.WEAK: return 'red' + case PasswordStrength.MODERATE: return 'yellow' + case PasswordStrength.STRONG: return 'green' + + @classmethod + def strength(cls, password: str) -> 'PasswordStrength': + digit = any(character.isdigit() for character in password) + upper = any(character.isupper() for character in password) + lower = any(character.islower() for character in password) + symbol = any(not character.isalnum() for character in password) + return cls._check_password_strength(digit, upper, lower, symbol, len(password)) + + @classmethod + def _check_password_strength( + cls, + digit: bool, + upper: bool, + lower: bool, + symbol: bool, + length: int + ) -> 'PasswordStrength': + # suggested evaluation + # https://github.com/archlinux/archinstall/issues/1304#issuecomment-1146768163 + if digit and upper and lower and symbol: + match length: + case num if 13 <= num: + return PasswordStrength.STRONG + case num if 11 <= num <= 12: + return PasswordStrength.MODERATE + case num if 7 <= num <= 10: + return PasswordStrength.WEAK + case num if num <= 6: + return PasswordStrength.VERY_WEAK + elif digit and upper and lower: + match length: + case num if 14 <= num: + return PasswordStrength.STRONG + case num if 11 <= num <= 13: + return PasswordStrength.MODERATE + case num if 7 <= num <= 10: + return PasswordStrength.WEAK + case num if num <= 6: + return PasswordStrength.VERY_WEAK + elif upper and lower: + match length: + case num if 15 <= num: + return PasswordStrength.STRONG + case num if 12 <= num <= 14: + return PasswordStrength.MODERATE + case num if 7 <= num <= 11: + return PasswordStrength.WEAK + case num if num <= 6: + return PasswordStrength.VERY_WEAK + elif lower or upper: + match length: + case num if 18 <= num: + return PasswordStrength.STRONG + case num if 14 <= num <= 17: + return PasswordStrength.MODERATE + case num if 9 <= num <= 13: + return PasswordStrength.WEAK + case num if num <= 8: + return PasswordStrength.VERY_WEAK + + return PasswordStrength.VERY_WEAK diff --git a/archinstall/lib/models/users.py b/archinstall/lib/models/users.py index a3057291..f72cabde 100644 --- a/archinstall/lib/models/users.py +++ b/archinstall/lib/models/users.py @@ -1,6 +1,8 @@ from dataclasses import dataclass from typing import Dict, List, Union, Any, TYPE_CHECKING +from .password_strength import PasswordStrength + if TYPE_CHECKING: _: Any @@ -25,8 +27,9 @@ class User: } def display(self) -> str: - password = '*' * len(self.password) - return f'{_("Username")}: {self.username:16} {_("Password")}: {password:16} sudo: {str(self.sudo)}' + strength = PasswordStrength.strength(self.password) + password = '*' * len(self.password) + f' ({strength.value})' + return f'{_("Username")}: {self.username:16} {_("Password")}: {password:20} sudo: {str(self.sudo)}' @classmethod def _parse(cls, config_users: List[Dict[str, Any]]) -> List['User']: diff --git a/archinstall/lib/user_interaction/utils.py b/archinstall/lib/user_interaction/utils.py index fa079bc2..7ee6fc07 100644 --- a/archinstall/lib/user_interaction/utils.py +++ b/archinstall/lib/user_interaction/utils.py @@ -7,6 +7,7 @@ import time from typing import Any, Optional, TYPE_CHECKING from ..menu import Menu +from ..models.password_strength import PasswordStrength from ..output import log if TYPE_CHECKING: @@ -16,42 +17,23 @@ if TYPE_CHECKING: SIG_TRIGGER = None -def check_password_strong(passwd: str) -> bool: - symbol_count = 0 - if any(character.isdigit() for character in passwd): - symbol_count += 10 - if any(character.isupper() for character in passwd): - symbol_count += 26 - if any(character.islower() for character in passwd): - symbol_count += 26 - if any(not character.isalnum() for character in passwd): - symbol_count += 40 - - if symbol_count**len(passwd) < 10e20: - prompt = str(_("The password you are using seems to be weak, are you sure you want to use it?")) - choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes()).run() - return choice.value == Menu.yes() - - return True - - def get_password(prompt: str = '') -> Optional[str]: if not prompt: prompt = _("Enter a password: ") - while passwd := getpass.getpass(prompt): - if len(passwd.strip()) <= 0: + while password := getpass.getpass(prompt): + if len(password.strip()) <= 0: break - if not check_password_strong(passwd): - continue + strength = PasswordStrength.strength(password) + log(f'Password strength: {strength.value}', fg=strength.color()) passwd_verification = getpass.getpass(prompt=_('And one more time for verification: ')) - if passwd != passwd_verification: + if password != passwd_verification: log(' * Passwords did not match * ', fg='red') continue - return passwd + return password return None -- cgit v1.2.3-70-g09d2 From 2d4b2620462a0fb4c9496ed0629d7ab8930fc73a Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Tue, 7 Jun 2022 01:26:27 +1000 Subject: Handle cyrillic characters (#1309) * Handle cyrillic characters * Update Co-authored-by: Daniel Girtler --- .github/workflows/mypy.yaml | 2 +- archinstall/__init__.py | 7 ----- archinstall/lib/menu/selection_menu.py | 10 +------ archinstall/lib/translation.py | 34 +++++++++++++++++++++++- archinstall/lib/user_interaction/general_conf.py | 9 ++++--- archinstall/locales/cyrillic.json | 19 +++++++++++++ 6 files changed, 60 insertions(+), 21 deletions(-) create mode 100644 archinstall/locales/cyrillic.json (limited to 'archinstall/lib') diff --git a/.github/workflows/mypy.yaml b/.github/workflows/mypy.yaml index d14d8553..8463afda 100644 --- a/.github/workflows/mypy.yaml +++ b/.github/workflows/mypy.yaml @@ -15,4 +15,4 @@ jobs: # one day this will be enabled # run: mypy --strict --module archinstall || exit 0 - name: run mypy - run: mypy --follow-imports=silent archinstall/lib/menu/selection_menu.py archinstall/lib/menu/global_menu.py archinstall/lib/models/network_configuration.py archinstall/lib/menu/list_manager.py archinstall/lib/user_interaction/network_conf.py archinstall/lib/models/users.py + run: mypy --follow-imports=silent archinstall/lib/menu/selection_menu.py archinstall/lib/menu/global_menu.py archinstall/lib/models/network_configuration.py archinstall/lib/menu/list_manager.py archinstall/lib/user_interaction/network_conf.py archinstall/lib/models/users.py archinstall/lib/translation.py diff --git a/archinstall/__init__.py b/archinstall/__init__.py index ee5e5f45..abcad3ba 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -58,10 +58,6 @@ storage['__version__'] = __version__ DeferredTranslation.install() -def set_unicode_font(): - SysCommand('setfont UniCyr_8x16') - - def define_arguments(): """ Define which explicit arguments do we allow. @@ -249,9 +245,6 @@ def post_process_arguments(arguments): load_config() -# to ensure that cyrillic characters work in the installer -# set_unicode_font() - define_arguments() arguments = get_arguments() post_process_arguments(arguments) diff --git a/archinstall/lib/menu/selection_menu.py b/archinstall/lib/menu/selection_menu.py index d4a7ceef..8dd6fcce 100644 --- a/archinstall/lib/menu/selection_menu.py +++ b/archinstall/lib/menu/selection_menu.py @@ -15,15 +15,6 @@ if TYPE_CHECKING: _: Any -def select_archinstall_language(preset_value: str) -> Optional[Any]: - """ - copied from user_interaction/general_conf.py as a temporary measure - """ - languages = Translation.get_available_lang() - language = Menu(_('Archinstall language'), languages, preset_values=preset_value).run() - return language.value - - class Selector: def __init__( self, @@ -462,6 +453,7 @@ class GeneralMenu: return mandatory_fields, mandatory_waiting def _select_archinstall_language(self, preset_value: str) -> str: + from ... import select_archinstall_language language = select_archinstall_language(preset_value) if language is not None: self._translation.activate(language) diff --git a/archinstall/lib/translation.py b/archinstall/lib/translation.py index 79e0198a..c20a4285 100644 --- a/archinstall/lib/translation.py +++ b/archinstall/lib/translation.py @@ -1,6 +1,7 @@ from __future__ import annotations import json +import logging import os import gettext @@ -13,12 +14,19 @@ if TYPE_CHECKING: class LanguageDefinitions: + _languages = 'languages.json' + _cyrillic = 'cyrillic.json' + def __init__(self): self._mappings = self._get_language_mappings() + self._cyrillic_languages = self._get_cyrillic_languages() + + def is_cyrillic(self, language: str) -> bool: + return language in self._cyrillic_languages def _get_language_mappings(self) -> List[Dict[str, str]]: locales_dir = Translation.get_locales_dir() - languages = Path.joinpath(locales_dir, 'languages.json') + languages = Path.joinpath(locales_dir, self._languages) with open(languages, 'r') as fp: return json.load(fp) @@ -30,6 +38,14 @@ class LanguageDefinitions: raise ValueError(f'No language with abbreviation "{abbr}" found') + def _get_cyrillic_languages(self) -> List[str]: + locales_dir = Translation.get_locales_dir() + languages = Path.joinpath(locales_dir, self._cyrillic) + + with open(languages, 'r') as fp: + data = json.load(fp) + return data['languages'] + class DeferredTranslation: def __init__(self, message: str): @@ -78,10 +94,26 @@ class Translation: def activate(self, name): if language := self._languages.get(name, None): + languages = LanguageDefinitions() + + if languages.is_cyrillic(name): + self._set_font('UniCyr_8x16') + else: + # this will reset a possible previously set font to a default font + self._set_font('') + language.install() else: raise ValueError(f'Language not supported: {name}') + def _set_font(self, font: str): + from archinstall import SysCommand, log + try: + log(f'Setting new font: {font}', level=logging.DEBUG) + SysCommand(f'setfont {font}') + except Exception: + log(f'Unable to set font {font}', level=logging.ERROR) + @classmethod def load_nationalization(cls) -> Translation: locales_dir = cls.get_locales_dir() diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py index 70a0e73f..15c42b86 100644 --- a/archinstall/lib/user_interaction/general_conf.py +++ b/archinstall/lib/user_interaction/general_conf.py @@ -118,10 +118,13 @@ def select_mirror_regions(preset_values: Dict[str, Any] = {}) -> Dict[str, Any]: case _: return {selected: mirrors[selected] for selected in selected_mirror.value} -def select_archinstall_language(default='English'): +def select_archinstall_language(preset_values: str): languages = Translation.get_available_lang() - language = Menu(_('Archinstall language'), languages, default_option=default).run() - return language + choice = Menu(_('Archinstall language'), languages, default_option=preset_values).run() + + match choice.type_: + case MenuSelectionType.Esc: return preset_values + case MenuSelectionType.Selection: return choice.value def select_profile(preset) -> Optional[Profile]: diff --git a/archinstall/locales/cyrillic.json b/archinstall/locales/cyrillic.json new file mode 100644 index 00000000..13f11ad0 --- /dev/null +++ b/archinstall/locales/cyrillic.json @@ -0,0 +1,19 @@ +{ + "languages": [ + "Abkhazian", + "Azerbaijani", + "Bashkir", + "Belarusian", + "Bulgarian", + "Chuvash", + "Komi", + "Macedonian", + "Mongolian", + "Russian", + "Serbo-Croatian", + "Tajik", + "Tatar", + "Ukrainian", + "Uzbek" + ] +} -- cgit v1.2.3-70-g09d2 From a7ca037a26de53fd242f89bc6a90fd53337b4d13 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Tue, 7 Jun 2022 01:28:46 +1000 Subject: Update the subvolume menu - fix for #1278 (#1297) * Update subvolume * Add mypy compliance Co-authored-by: Daniel Girtler Co-authored-by: Anton Hvornum --- .github/workflows/mypy.yaml | 2 +- archinstall/__init__.py | 6 +- archinstall/lib/disk/btrfs/__init__.py | 130 +------------ archinstall/lib/disk/btrfs/btrfs_helpers.py | 94 ++++------ archinstall/lib/disk/btrfs/btrfspartition.py | 6 +- archinstall/lib/disk/btrfs/btrfssubvolume.py | 191 -------------------- archinstall/lib/disk/btrfs/btrfssubvolumeinfo.py | 192 ++++++++++++++++++++ archinstall/lib/disk/helpers.py | 9 +- archinstall/lib/disk/mapperdev.py | 10 +- archinstall/lib/disk/partition.py | 10 +- archinstall/lib/disk/user_guides.py | 19 +- archinstall/lib/installer.py | 55 ++---- archinstall/lib/menu/global_menu.py | 33 ++-- archinstall/lib/menu/list_manager.py | 39 ++-- archinstall/lib/models/subvolume.py | 68 +++++++ archinstall/lib/models/users.py | 6 +- .../lib/user_interaction/partitioning_conf.py | 12 +- .../lib/user_interaction/subvolume_config.py | 201 +++++++-------------- 18 files changed, 462 insertions(+), 621 deletions(-) delete mode 100644 archinstall/lib/disk/btrfs/btrfssubvolume.py create mode 100644 archinstall/lib/disk/btrfs/btrfssubvolumeinfo.py create mode 100644 archinstall/lib/models/subvolume.py (limited to 'archinstall/lib') diff --git a/.github/workflows/mypy.yaml b/.github/workflows/mypy.yaml index 8463afda..b0901b38 100644 --- a/.github/workflows/mypy.yaml +++ b/.github/workflows/mypy.yaml @@ -15,4 +15,4 @@ jobs: # one day this will be enabled # run: mypy --strict --module archinstall || exit 0 - name: run mypy - run: mypy --follow-imports=silent archinstall/lib/menu/selection_menu.py archinstall/lib/menu/global_menu.py archinstall/lib/models/network_configuration.py archinstall/lib/menu/list_manager.py archinstall/lib/user_interaction/network_conf.py archinstall/lib/models/users.py archinstall/lib/translation.py + run: mypy --follow-imports=silent archinstall/lib/menu/selection_menu.py archinstall/lib/menu/global_menu.py archinstall/lib/models/network_configuration.py archinstall/lib/menu/list_manager.py archinstall/lib/user_interaction/network_conf.py archinstall/lib/models/users.py archinstall/lib/user_interaction/subvolume_config.py archinstall/lib/disk/btrfs/btrfs_helpers.py archinstall/lib/translation.py diff --git a/archinstall/__init__.py b/archinstall/__init__.py index abcad3ba..1a360c67 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -226,8 +226,6 @@ def post_process_arguments(arguments): load_plugin(arguments['plugin']) if arguments.get('disk_layouts', None) is not None: - # if 'disk_layouts' not in storage: - # storage['disk_layouts'] = {} layout_storage = {} if not json_stream_to_structure('--disk_layouts',arguments['disk_layouts'],layout_storage): exit(1) @@ -236,10 +234,12 @@ def post_process_arguments(arguments): arguments['harddrives'] = [disk for disk in layout_storage] # backward compatibility. Change partition.format for partition.wipe for disk in layout_storage: - for i,partition in enumerate(layout_storage[disk].get('partitions',[])): + for i, partition in enumerate(layout_storage[disk].get('partitions',[])): if 'format' in partition: partition['wipe'] = partition['format'] del partition['format'] + elif 'btrfs' in partition: + partition['btrfs']['subvolumes'] = Subvolume.parse_arguments(partition['btrfs']['subvolumes']) arguments['disk_layouts'] = layout_storage load_config() diff --git a/archinstall/lib/disk/btrfs/__init__.py b/archinstall/lib/disk/btrfs/__init__.py index 90c58145..3c183112 100644 --- a/archinstall/lib/disk/btrfs/__init__.py +++ b/archinstall/lib/disk/btrfs/__init__.py @@ -2,8 +2,7 @@ from __future__ import annotations import pathlib import glob import logging -import re -from typing import Union, Dict, TYPE_CHECKING, Any, Iterator +from typing import Union, Dict, TYPE_CHECKING # https://stackoverflow.com/a/39757388/929999 if TYPE_CHECKING: @@ -15,30 +14,15 @@ from .btrfs_helpers import ( setup_subvolumes as setup_subvolumes, mount_subvolume as mount_subvolume ) -from .btrfssubvolume import BtrfsSubvolume as BtrfsSubvolume +from .btrfssubvolumeinfo import BtrfsSubvolumeInfo as BtrfsSubvolume from .btrfspartition import BTRFSPartition as BTRFSPartition -from ..helpers import get_mount_info from ...exceptions import DiskError, Deprecated from ...general import SysCommand from ...output import log -from ...exceptions import SysCallError -def get_subvolume_info(path :pathlib.Path) -> Dict[str, Any]: - try: - output = SysCommand(f"btrfs subvol show {path}").decode() - except SysCallError as error: - print('Error:', error) - result = {} - for line in output.replace('\r\n', '\n').split('\n'): - if ':' in line: - key, val = line.replace('\t', '').split(':', 1) - result[key.strip().lower().replace(' ', '_')] = val.strip() - - return result - -def create_subvolume(installation :Installer, subvolume_location :Union[pathlib.Path, str]) -> bool: +def create_subvolume(installation: Installer, subvolume_location :Union[pathlib.Path, str]) -> bool: """ This function uses btrfs to create a subvolume. @@ -71,112 +55,6 @@ def create_subvolume(installation :Installer, subvolume_location :Union[pathlib. if (cmd := SysCommand(f"btrfs subvolume create {target}")).exit_code != 0: raise DiskError(f"Could not create a subvolume at {target}: {cmd}") -def _has_option(option :str,options :list) -> bool: - """ auxiliary routine to check if an option is present in a list. - we check if the string appears in one of the options, 'cause it can appear in several forms (option, option=val,...) - """ - if not options: - return False - - for item in options: - if option in item: - return True - - return False - -def manage_btrfs_subvolumes(installation :Installer, - partition :Dict[str, str],) -> list: +def manage_btrfs_subvolumes(installation :Installer, partition :Dict[str, str]) -> list: raise Deprecated("Use setup_subvolumes() instead.") - - from copy import deepcopy - """ we do the magic with subvolumes in a centralized place - parameters: - * the installation object - * the partition dictionary entry which represents the physical partition - returns - * mountpoinst, the list which contains all the "new" partititon to be mounted - - We expect the partition has been mounted as / , and it to be unmounted after the processing - Then we create all the subvolumes inside btrfs as demand - We clone then, both the partition dictionary and the object inside it and adapt it to the subvolume needs - Then we return a list of "new" partitions to be processed as "normal" partitions - # TODO For encrypted devices we need some special processing prior to it - """ - # We process each of the pairs - # th mount info dict has an entry for the path of the mountpoint (named 'mountpoint') and 'options' which is a list - # of mount options (or similar used by brtfs) - mountpoints = [] - subvolumes = partition['btrfs']['subvolumes'] - for name, right_hand in subvolumes.items(): - try: - # we normalize the subvolume name (getting rid of slash at the start if exists. In our implementation has no semantic load - every subvolume is created from the top of the hierarchy- and simplifies its further use - if name.startswith('/'): - name = name[1:] - # renormalize the right hand. - location = None - subvol_options = [] - # no contents, so it is not to be mounted - if not right_hand: - location = None - # just a string. per backward compatibility the mount point - elif isinstance(right_hand,str): - location = right_hand - # a dict. two elements 'mountpoint' (obvious) and and a mount options list ¿? - elif isinstance(right_hand,dict): - location = right_hand.get('mountpoint',None) - subvol_options = right_hand.get('options',[]) - # we create the subvolume - create_subvolume(installation,name) - # Make the nodatacow processing now - # It will be the main cause of creation of subvolumes which are not to be mounted - # it is not an options which can be established by subvolume (but for whole file systems), and can be - # set up via a simple attribute change in a directory (if empty). And here the directories are brand new - if 'nodatacow' in subvol_options: - if (cmd := SysCommand(f"chattr +C {installation.target}/{name}")).exit_code != 0: - raise DiskError(f"Could not set nodatacow attribute at {installation.target}/{name}: {cmd}") - # entry is deleted so nodatacow doesn't propagate to the mount options - del subvol_options[subvol_options.index('nodatacow')] - # Make the compress processing now - # it is not an options which can be established by subvolume (but for whole file systems), and can be - # set up via a simple attribute change in a directory (if empty). And here the directories are brand new - # in this way only zstd compression is activaded - # TODO WARNING it is not clear if it should be a standard feature, so it might need to be deactivated - if 'compress' in subvol_options: - if not _has_option('compress',partition.get('filesystem',{}).get('mount_options',[])): - if (cmd := SysCommand(f"chattr +c {installation.target}/{name}")).exit_code != 0: - raise DiskError(f"Could not set compress attribute at {installation.target}/{name}: {cmd}") - # entry is deleted so compress doesn't propagate to the mount options - del subvol_options[subvol_options.index('compress')] - # END compress processing. - # we do not mount if THE basic partition will be mounted or if we exclude explicitly this subvolume - if not partition['mountpoint'] and location is not None: - # we begin to create a fake partition entry. First we copy the original -the one that corresponds to - # the primary partition. We make a deepcopy to avoid altering the original content in any case - fake_partition = deepcopy(partition) - # we start to modify entries in the "fake partition" to match the needs of the subvolumes - # to avoid any chance of entering in a loop (not expected) we delete the list of subvolumes in the copy - del fake_partition['btrfs'] - fake_partition['encrypted'] = False - fake_partition['generate-encryption-key-file'] = False - # Mount destination. As of now the right hand part - fake_partition['mountpoint'] = location - # we load the name in an attribute called subvolume, but i think it is not needed anymore, 'cause the mount logic uses a different path. - fake_partition['subvolume'] = name - # here we add the special mount options for the subvolume, if any. - # if the original partition['options'] is not a list might give trouble - if fake_partition.get('filesystem',{}).get('mount_options',[]): - fake_partition['filesystem']['mount_options'].extend(subvol_options) - else: - fake_partition['filesystem']['mount_options'] = subvol_options - # Here comes the most exotic part. The dictionary attribute 'device_instance' contains an instance of Partition. This instance will be queried along the mount process at the installer. - # As the rest will query there the path of the "partition" to be mounted, we feed it with the bind name needed to mount subvolumes - # As we made a deepcopy we have a fresh instance of this object we can manipulate problemless - fake_partition['device_instance'].path = f"{partition['device_instance'].path}[/{name}]" - - # Well, now that this "fake partition" is ready, we add it to the list of the ones which are to be mounted, - # as "normal" ones - mountpoints.append(fake_partition) - except Exception as e: - raise e - return mountpoints diff --git a/archinstall/lib/disk/btrfs/btrfs_helpers.py b/archinstall/lib/disk/btrfs/btrfs_helpers.py index 5fa94314..ab528388 100644 --- a/archinstall/lib/disk/btrfs/btrfs_helpers.py +++ b/archinstall/lib/disk/btrfs/btrfs_helpers.py @@ -1,72 +1,42 @@ -import pathlib import logging -from typing import Optional +from pathlib import Path +from typing import Optional, Dict, Any, TYPE_CHECKING +from ...models.subvolume import Subvolume from ...exceptions import SysCallError, DiskError from ...general import SysCommand from ...output import log from ..helpers import get_mount_info -from .btrfssubvolume import BtrfsSubvolume +from .btrfssubvolumeinfo import BtrfsSubvolumeInfo +if TYPE_CHECKING: + from .btrfspartition import BTRFSPartition + from ...installer import Installer -def mount_subvolume(installation, device, name, subvolume_information): - # we normalize the subvolume name (getting rid of slash at the start if exists. In our implementation has no semantic load. - # Every subvolume is created from the top of the hierarchy- and simplifies its further use - name = name.lstrip('/') - - # renormalize the right hand. - mountpoint = subvolume_information.get('mountpoint', None) - if not mountpoint: - return None - - if type(mountpoint) == str: - mountpoint = pathlib.Path(mountpoint) - installation_target = installation.target - if type(installation_target) == str: - installation_target = pathlib.Path(installation_target) +def mount_subvolume(installation: 'Installer', device: 'BTRFSPartition', subvolume: Subvolume): + # we normalize the subvolume name (getting rid of slash at the start if exists. + # In our implementation has no semantic load. + # Every subvolume is created from the top of the hierarchy- and simplifies its further use + name = subvolume.name.lstrip('/') + mountpoint = Path(subvolume.mountpoint) + installation_target = Path(installation.target) mountpoint = installation_target / mountpoint.relative_to(mountpoint.anchor) mountpoint.mkdir(parents=True, exist_ok=True) - - mount_options = subvolume_information.get('options', []) - if not any('subvol=' in x for x in mount_options): - mount_options += [f'subvol={name}'] + mount_options = subvolume.options + [f'subvol={name}'] log(f"Mounting subvolume {name} on {device} to {mountpoint}", level=logging.INFO, fg="gray") SysCommand(f"mount {device.path} {mountpoint} -o {','.join(mount_options)}") -def setup_subvolumes(installation, partition_dict): - """ - Taken from: ..user_guides.py - - partition['btrfs'] = { - "subvolumes" : { - "@": "/", - "@home": "/home", - "@log": "/var/log", - "@pkg": "/var/cache/pacman/pkg", - "@.snapshots": "/.snapshots" - } - } - """ +def setup_subvolumes(installation: 'Installer', partition_dict: Dict[str, Any]): log(f"Setting up subvolumes: {partition_dict['btrfs']['subvolumes']}", level=logging.INFO, fg="gray") - for name, right_hand in partition_dict['btrfs']['subvolumes'].items(): + + for subvolume in partition_dict['btrfs']['subvolumes']: # we normalize the subvolume name (getting rid of slash at the start if exists. In our implementation has no semantic load. # Every subvolume is created from the top of the hierarchy- and simplifies its further use - name = name.lstrip('/') - - # renormalize the right hand. - # mountpoint = None - subvol_options = [] - - match right_hand: - # case str(): # backwards-compatability - # mountpoint = right_hand - case dict(): - # mountpoint = right_hand.get('mountpoint', None) - subvol_options = right_hand.get('options', []) + name = subvolume.name.lstrip('/') # We create the subvolume using the BTRFSPartition instance. # That way we ensure not only easy access, but also accurate mount locations etc. @@ -76,27 +46,25 @@ def setup_subvolumes(installation, partition_dict): # It will be the main cause of creation of subvolumes which are not to be mounted # it is not an options which can be established by subvolume (but for whole file systems), and can be # set up via a simple attribute change in a directory (if empty). And here the directories are brand new - if 'nodatacow' in subvol_options: + if subvolume.nodatacow: if (cmd := SysCommand(f"chattr +C {installation.target}/{name}")).exit_code != 0: raise DiskError(f"Could not set nodatacow attribute at {installation.target}/{name}: {cmd}") - # entry is deleted so nodatacow doesn't propagate to the mount options - del subvol_options[subvol_options.index('nodatacow')] + # Make the compress processing now # it is not an options which can be established by subvolume (but for whole file systems), and can be # set up via a simple attribute change in a directory (if empty). And here the directories are brand new # in this way only zstd compression is activaded # TODO WARNING it is not clear if it should be a standard feature, so it might need to be deactivated - if 'compress' in subvol_options: + if subvolume.compress: if not any(['compress' in filesystem_option for filesystem_option in partition_dict.get('filesystem', {}).get('mount_options', [])]): if (cmd := SysCommand(f"chattr +c {installation.target}/{name}")).exit_code != 0: raise DiskError(f"Could not set compress attribute at {installation.target}/{name}: {cmd}") - # entry is deleted so compress doesn't propagate to the mount options - del subvol_options[subvol_options.index('compress')] -def subvolume_info_from_path(path :pathlib.Path) -> Optional[BtrfsSubvolume]: + +def subvolume_info_from_path(path: Path) -> Optional[BtrfsSubvolumeInfo]: try: - subvolume_name = None + subvolume_name = '' result = {} for index, line in enumerate(SysCommand(f"btrfs subvolume show {path}")): if index == 0: @@ -110,14 +78,14 @@ def subvolume_info_from_path(path :pathlib.Path) -> Optional[BtrfsSubvolume]: # allows for hooking in a pre-processor to do this we have to do it here: result[key.lower().replace(' ', '_').replace('(s)', 's')] = value.strip() - return BtrfsSubvolume(**{'full_path' : path, 'name' : subvolume_name, **result}) - + return BtrfsSubvolumeInfo(**{'full_path' : path, 'name' : subvolume_name, **result}) # type: ignore except SysCallError as error: log(f"Could not retrieve subvolume information from {path}: {error}", level=logging.WARNING, fg="orange") return None -def find_parent_subvolume(path :pathlib.Path, filters=[]): + +def find_parent_subvolume(path: Path, filters=[]) -> Optional[BtrfsSubvolumeInfo]: # A root path cannot have a parent if str(path) == '/': return None @@ -127,6 +95,8 @@ def find_parent_subvolume(path :pathlib.Path, filters=[]): if found_mount['target'] == '/': return None - return find_parent_subvolume(path.parent, traverse=True, filters=[*filters, found_mount['target']]) + return find_parent_subvolume(path.parent, filters=[*filters, found_mount['target']]) + + return subvolume - return subvolume \ No newline at end of file + return None diff --git a/archinstall/lib/disk/btrfs/btrfspartition.py b/archinstall/lib/disk/btrfs/btrfspartition.py index 6f7487e4..a05f1527 100644 --- a/archinstall/lib/disk/btrfs/btrfspartition.py +++ b/archinstall/lib/disk/btrfs/btrfspartition.py @@ -15,7 +15,7 @@ from .btrfs_helpers import ( if TYPE_CHECKING: from ...installer import Installer - from .btrfssubvolume import BtrfsSubvolume + from .btrfssubvolumeinfo import BtrfsSubvolumeInfo class BTRFSPartition(Partition): def __init__(self, *args, **kwargs): @@ -50,7 +50,7 @@ class BTRFSPartition(Partition): for child in iterate_children(filesystem): yield child - def create_subvolume(self, subvolume :pathlib.Path, installation :Optional['Installer'] = None) -> 'BtrfsSubvolume': + def create_subvolume(self, subvolume :pathlib.Path, installation :Optional['Installer'] = None) -> 'BtrfsSubvolumeInfo': """ Subvolumes have to be created within a mountpoint. This means we need to get the current installation target. @@ -117,4 +117,4 @@ class BTRFSPartition(Partition): # And deal with it here: SysCommand(f"btrfs subvolume create {subvolume}") - return subvolume_info_from_path(subvolume) \ No newline at end of file + return subvolume_info_from_path(subvolume) diff --git a/archinstall/lib/disk/btrfs/btrfssubvolume.py b/archinstall/lib/disk/btrfs/btrfssubvolume.py deleted file mode 100644 index bc7db612..00000000 --- a/archinstall/lib/disk/btrfs/btrfssubvolume.py +++ /dev/null @@ -1,191 +0,0 @@ -import pathlib -import datetime -import logging -import string -import random -import shutil -from dataclasses import dataclass -from typing import Optional, List# , TYPE_CHECKING -from functools import cached_property - -# if TYPE_CHECKING: -# from ..blockdevice import BlockDevice - -from ...exceptions import DiskError -from ...general import SysCommand -from ...output import log -from ...storage import storage - -@dataclass -class BtrfsSubvolume: - full_path :pathlib.Path - name :str - uuid :str - parent_uuid :str - creation_time :datetime.datetime - subvolume_id :int - generation :int - gen_at_creation :int - parent_id :int - top_level_id :int - send_transid :int - send_time :datetime.datetime - receive_transid :int - received_uuid :Optional[str] = None - flags :Optional[str] = None - receive_time :Optional[datetime.datetime] = None - snapshots :Optional[List] = None - - def __post_init__(self): - self.full_path = pathlib.Path(self.full_path) - - # Convert "-" entries to `None` - if self.parent_uuid == "-": - self.parent_uuid = None - if self.received_uuid == "-": - self.received_uuid = None - if self.flags == "-": - self.flags = None - if self.receive_time == "-": - self.receive_time = None - if self.snapshots == "": - self.snapshots = [] - - # Convert timestamps into datetime workable objects (and preserve timezone by using ISO formats) - self.creation_time = datetime.datetime.fromisoformat(self.convert_to_ISO_format(self.creation_time)) - self.send_time = datetime.datetime.fromisoformat(self.convert_to_ISO_format(self.send_time)) - if self.receive_time: - self.receive_time = datetime.datetime.fromisoformat(self.convert_to_ISO_format(self.receive_time)) - - @property - def parent_subvolume(self): - from .btrfs_helpers import find_parent_subvolume - - return find_parent_subvolume(self.full_path) - - @property - def root(self) -> bool: - from .btrfs_helpers import subvolume_info_from_path - - # TODO: Make this function traverse storage['MOUNT_POINT'] and find the first - # occurrence of a mountpoint that is a btrfs volume instead of lazy assume / is a subvolume. - # It would also be nice if it could use findmnt(self.full_path) and traverse backwards - # finding the last occurrence of a subvolume which 'self' belongs to. - if volume := subvolume_info_from_path(storage['MOUNT_POINT']): - return self.full_path == volume.full_path - - return False - - @cached_property - def partition(self): - from ..helpers import findmnt, get_parent_of_partition, all_blockdevices - from ..partition import Partition - from ..blockdevice import BlockDevice - from ..mapperdev import MapperDev - from .btrfspartition import BTRFSPartition - from .btrfs_helpers import subvolume_info_from_path - - try: - # If the subvolume is mounted, it's pretty trivial to lookup the partition (parent) device. - if filesystem := findmnt(self.full_path).get('filesystems', []): - if source := filesystem[0].get('source', None): - # Strip away subvolume definitions from findmnt - if '[' in source: - source = source[:source.find('[')] - - if filesystem[0].get('fstype', '') == 'btrfs': - return BTRFSPartition(source, BlockDevice(get_parent_of_partition(pathlib.Path(source)))) - elif filesystem[0].get('source', '').startswith('/dev/mapper'): - return MapperDev(source) - else: - return Partition(source, BlockDevice(get_parent_of_partition(pathlib.Path(source)))) - except DiskError: - # Subvolume has never been mounted, we have no reliable way of finding where it is. - # But we have the UUID of the partition, and can begin looking for it by mounting - # all blockdevices that we can reliably support.. This is taxing tho and won't cover all devices. - - log(f"Looking up {self}, this might take time.", fg="orange", level=logging.WARNING) - for blockdevice, instance in all_blockdevices(mappers=True, partitions=True, error=True).items(): - if type(instance) in (Partition, MapperDev): - we_mounted_it = False - detection_mountpoint = instance.mountpoint - if not detection_mountpoint: - if type(instance) == Partition and instance.encrypted: - # TODO: Perhaps support unlocking encrypted volumes? - # This will cause a lot of potential user interactions tho. - log(f"Ignoring {blockdevice} because it's encrypted.", fg="gray", level=logging.DEBUG) - continue - - detection_mountpoint = pathlib.Path(f"/tmp/{''.join([random.choice(string.ascii_letters) for x in range(20)])}") - detection_mountpoint.mkdir(parents=True, exist_ok=True) - - instance.mount(str(detection_mountpoint)) - we_mounted_it = True - - if (filesystem := findmnt(detection_mountpoint)) and (filesystem := filesystem.get('filesystems', [])): - if subvolume := subvolume_info_from_path(filesystem[0]['target']): - if subvolume.uuid == self.uuid: - # The top level subvolume matched of ourselves, - # which means the instance we're iterating has the subvol we're looking for. - log(f"Found the subvolume on device {instance}", level=logging.DEBUG, fg="gray") - return instance - - def iterate_children(struct): - for child in struct.get('children', []): - if '[' in child.get('source', ''): - yield subvolume_info_from_path(child['target']) - - for sub_child in iterate_children(child): - yield sub_child - - for child in iterate_children(filesystem[0]): - if child.uuid == self.uuid: - # We found a child within the instance that has the subvol we're looking for. - log(f"Found the subvolume on device {instance}", level=logging.DEBUG, fg="gray") - return instance - - if we_mounted_it: - instance.unmount() - shutil.rmtree(detection_mountpoint) - - @cached_property - def mount_options(self) -> Optional[List[str]]: - from ..helpers import findmnt - - if filesystem := findmnt(self.full_path).get('filesystems', []): - return filesystem[0].get('options').split(',') - - def convert_to_ISO_format(self, time_string): - time_string_almost_done = time_string.replace(' ', 'T', 1).replace(' ', '') - iso_string = f"{time_string_almost_done[:-2]}:{time_string_almost_done[-2:]}" - return iso_string - - def mount(self, mountpoint :pathlib.Path, options=None, include_previously_known_options=True): - from ..helpers import findmnt - - try: - if mnt_info := findmnt(pathlib.Path(mountpoint), traverse=False): - log(f"Unmounting {mountpoint} as it was already mounted using {mnt_info}") - SysCommand(f"umount {mountpoint}") - except DiskError: - # No previously mounted device at the mountpoint - pass - - if not options: - options = [] - - try: - if include_previously_known_options and (cached_options := self.mount_options): - options += cached_options - except DiskError: - pass - - if not any('subvol=' in x for x in options): - options += f'subvol={self.name}' - - SysCommand(f"mount {self.partition.path} {mountpoint} -o {','.join(options)}") - log(f"{self} has successfully been mounted to {mountpoint}", level=logging.INFO, fg="gray") - - def unmount(self, recurse :bool = True): - SysCommand(f"umount {'-R' if recurse else ''} {self.full_path}") - log(f"Successfully unmounted {self}", level=logging.INFO, fg="gray") \ No newline at end of file diff --git a/archinstall/lib/disk/btrfs/btrfssubvolumeinfo.py b/archinstall/lib/disk/btrfs/btrfssubvolumeinfo.py new file mode 100644 index 00000000..5f5bdea6 --- /dev/null +++ b/archinstall/lib/disk/btrfs/btrfssubvolumeinfo.py @@ -0,0 +1,192 @@ +import pathlib +import datetime +import logging +import string +import random +import shutil +from dataclasses import dataclass +from typing import Optional, List# , TYPE_CHECKING +from functools import cached_property + +# if TYPE_CHECKING: +# from ..blockdevice import BlockDevice + +from ...exceptions import DiskError +from ...general import SysCommand +from ...output import log +from ...storage import storage + + +@dataclass +class BtrfsSubvolumeInfo: + full_path :pathlib.Path + name :str + uuid :str + parent_uuid :str + creation_time :datetime.datetime + subvolume_id :int + generation :int + gen_at_creation :int + parent_id :int + top_level_id :int + send_transid :int + send_time :datetime.datetime + receive_transid :int + received_uuid :Optional[str] = None + flags :Optional[str] = None + receive_time :Optional[datetime.datetime] = None + snapshots :Optional[List] = None + + def __post_init__(self): + self.full_path = pathlib.Path(self.full_path) + + # Convert "-" entries to `None` + if self.parent_uuid == "-": + self.parent_uuid = None + if self.received_uuid == "-": + self.received_uuid = None + if self.flags == "-": + self.flags = None + if self.receive_time == "-": + self.receive_time = None + if self.snapshots == "": + self.snapshots = [] + + # Convert timestamps into datetime workable objects (and preserve timezone by using ISO formats) + self.creation_time = datetime.datetime.fromisoformat(self.convert_to_ISO_format(self.creation_time)) + self.send_time = datetime.datetime.fromisoformat(self.convert_to_ISO_format(self.send_time)) + if self.receive_time: + self.receive_time = datetime.datetime.fromisoformat(self.convert_to_ISO_format(self.receive_time)) + + @property + def parent_subvolume(self): + from .btrfs_helpers import find_parent_subvolume + + return find_parent_subvolume(self.full_path) + + @property + def root(self) -> bool: + from .btrfs_helpers import subvolume_info_from_path + + # TODO: Make this function traverse storage['MOUNT_POINT'] and find the first + # occurrence of a mountpoint that is a btrfs volume instead of lazy assume / is a subvolume. + # It would also be nice if it could use findmnt(self.full_path) and traverse backwards + # finding the last occurrence of a subvolume which 'self' belongs to. + if volume := subvolume_info_from_path(storage['MOUNT_POINT']): + return self.full_path == volume.full_path + + return False + + @cached_property + def partition(self): + from ..helpers import findmnt, get_parent_of_partition, all_blockdevices + from ..partition import Partition + from ..blockdevice import BlockDevice + from ..mapperdev import MapperDev + from .btrfspartition import BTRFSPartition + from .btrfs_helpers import subvolume_info_from_path + + try: + # If the subvolume is mounted, it's pretty trivial to lookup the partition (parent) device. + if filesystem := findmnt(self.full_path).get('filesystems', []): + if source := filesystem[0].get('source', None): + # Strip away subvolume definitions from findmnt + if '[' in source: + source = source[:source.find('[')] + + if filesystem[0].get('fstype', '') == 'btrfs': + return BTRFSPartition(source, BlockDevice(get_parent_of_partition(pathlib.Path(source)))) + elif filesystem[0].get('source', '').startswith('/dev/mapper'): + return MapperDev(source) + else: + return Partition(source, BlockDevice(get_parent_of_partition(pathlib.Path(source)))) + except DiskError: + # Subvolume has never been mounted, we have no reliable way of finding where it is. + # But we have the UUID of the partition, and can begin looking for it by mounting + # all blockdevices that we can reliably support.. This is taxing tho and won't cover all devices. + + log(f"Looking up {self}, this might take time.", fg="orange", level=logging.WARNING) + for blockdevice, instance in all_blockdevices(mappers=True, partitions=True, error=True).items(): + if type(instance) in (Partition, MapperDev): + we_mounted_it = False + detection_mountpoint = instance.mountpoint + if not detection_mountpoint: + if type(instance) == Partition and instance.encrypted: + # TODO: Perhaps support unlocking encrypted volumes? + # This will cause a lot of potential user interactions tho. + log(f"Ignoring {blockdevice} because it's encrypted.", fg="gray", level=logging.DEBUG) + continue + + detection_mountpoint = pathlib.Path(f"/tmp/{''.join([random.choice(string.ascii_letters) for x in range(20)])}") + detection_mountpoint.mkdir(parents=True, exist_ok=True) + + instance.mount(str(detection_mountpoint)) + we_mounted_it = True + + if (filesystem := findmnt(detection_mountpoint)) and (filesystem := filesystem.get('filesystems', [])): + if subvolume := subvolume_info_from_path(filesystem[0]['target']): + if subvolume.uuid == self.uuid: + # The top level subvolume matched of ourselves, + # which means the instance we're iterating has the subvol we're looking for. + log(f"Found the subvolume on device {instance}", level=logging.DEBUG, fg="gray") + return instance + + def iterate_children(struct): + for child in struct.get('children', []): + if '[' in child.get('source', ''): + yield subvolume_info_from_path(child['target']) + + for sub_child in iterate_children(child): + yield sub_child + + for child in iterate_children(filesystem[0]): + if child.uuid == self.uuid: + # We found a child within the instance that has the subvol we're looking for. + log(f"Found the subvolume on device {instance}", level=logging.DEBUG, fg="gray") + return instance + + if we_mounted_it: + instance.unmount() + shutil.rmtree(detection_mountpoint) + + @cached_property + def mount_options(self) -> Optional[List[str]]: + from ..helpers import findmnt + + if filesystem := findmnt(self.full_path).get('filesystems', []): + return filesystem[0].get('options').split(',') + + def convert_to_ISO_format(self, time_string): + time_string_almost_done = time_string.replace(' ', 'T', 1).replace(' ', '') + iso_string = f"{time_string_almost_done[:-2]}:{time_string_almost_done[-2:]}" + return iso_string + + def mount(self, mountpoint :pathlib.Path, options=None, include_previously_known_options=True): + from ..helpers import findmnt + + try: + if mnt_info := findmnt(pathlib.Path(mountpoint), traverse=False): + log(f"Unmounting {mountpoint} as it was already mounted using {mnt_info}") + SysCommand(f"umount {mountpoint}") + except DiskError: + # No previously mounted device at the mountpoint + pass + + if not options: + options = [] + + try: + if include_previously_known_options and (cached_options := self.mount_options): + options += cached_options + except DiskError: + pass + + if not any('subvol=' in x for x in options): + options += f'subvol={self.name}' + + SysCommand(f"mount {self.partition.path} {mountpoint} -o {','.join(options)}") + log(f"{self} has successfully been mounted to {mountpoint}", level=logging.INFO, fg="gray") + + def unmount(self, recurse :bool = True): + SysCommand(f"umount {'-R' if recurse else ''} {self.full_path}") + log(f"Successfully unmounted {self}", level=logging.INFO, fg="gray") diff --git a/archinstall/lib/disk/helpers.py b/archinstall/lib/disk/helpers.py index 85c0390f..660594ed 100644 --- a/archinstall/lib/disk/helpers.py +++ b/archinstall/lib/disk/helpers.py @@ -8,6 +8,8 @@ import time import glob from typing import Union, List, Iterator, Dict, Optional, Any, TYPE_CHECKING # https://stackoverflow.com/a/39757388/929999 +from ..models.subvolume import Subvolume + if TYPE_CHECKING: from .partition import Partition @@ -469,6 +471,7 @@ def convert_device_to_uuid(path :str) -> str: raise DiskError(f"Could not retrieve the UUID of {path} within a timely manner.") + def has_mountpoint(partition: Union[dict,Partition,MapperDev], target: str, strict: bool = True) -> bool: """ Determine if a certain partition is mounted (or has a mountpoint) as specific target (path) Coded for clarity rather than performance @@ -485,10 +488,12 @@ def has_mountpoint(partition: Union[dict,Partition,MapperDev], target: str, stri """ # we create the mountpoint list if isinstance(partition,dict): - subvols = partition.get('btrfs',{}).get('subvolumes',{}) - mountpoints = [partition.get('mountpoint'),] + [subvols[subvol] if isinstance(subvols[subvol],str) or not subvols[subvol] else subvols[subvol].get('mountpoint') for subvol in subvols] + subvolumes: List[Subvolume] = partition.get('btrfs',{}).get('subvolumes', []) + mountpoints = [partition.get('mountpoint')] + mountpoints += [volume.mountpoint for volume in subvolumes] else: mountpoints = [partition.mountpoint,] + [subvol.target for subvol in partition.subvolumes] + # we check if strict or target == '/': if target in mountpoints: diff --git a/archinstall/lib/disk/mapperdev.py b/archinstall/lib/disk/mapperdev.py index 913dbc13..49137ae9 100644 --- a/archinstall/lib/disk/mapperdev.py +++ b/archinstall/lib/disk/mapperdev.py @@ -10,7 +10,7 @@ from ..general import SysCommand from ..output import log if TYPE_CHECKING: - from .btrfs import BtrfsSubvolume + from .btrfs import BtrfsSubvolumeInfo @dataclass class MapperDev: @@ -37,12 +37,12 @@ class MapperDev: for slave in glob.glob(f"/sys/class/block/{dm_device.name}/slaves/*"): partition_belonging_to_dmcrypt_device = pathlib.Path(slave).name - + try: uevent_data = SysCommand(f"blkid -o export /dev/{partition_belonging_to_dmcrypt_device}").decode() except SysCallError as error: log(f"Could not get information on device /dev/{partition_belonging_to_dmcrypt_device}: {error}", level=logging.ERROR, fg="red") - + information = uevent(uevent_data) block_device = BlockDevice(get_parent_of_partition('/dev/' / pathlib.Path(information['DEVNAME']))) @@ -75,10 +75,10 @@ class MapperDev: return get_filesystem_type(self.path) @property - def subvolumes(self) -> Iterator['BtrfsSubvolume']: + def subvolumes(self) -> Iterator['BtrfsSubvolumeInfo']: from .btrfs import subvolume_info_from_path for mountpoint in self.mount_information: if target := mountpoint.get('target'): if subvolume := subvolume_info_from_path(pathlib.Path(target)): - yield subvolume \ No newline at end of file + yield subvolume diff --git a/archinstall/lib/disk/partition.py b/archinstall/lib/disk/partition.py index 2c9f50c2..6f25a5f7 100644 --- a/archinstall/lib/disk/partition.py +++ b/archinstall/lib/disk/partition.py @@ -14,7 +14,7 @@ from ..exceptions import DiskError, SysCallError, UnknownFilesystemFormat from ..output import log from ..general import SysCommand from .btrfs.btrfs_helpers import subvolume_info_from_path -from .btrfs.btrfssubvolume import BtrfsSubvolume +from .btrfs.btrfssubvolumeinfo import BtrfsSubvolumeInfo class Partition: def __init__(self, @@ -185,7 +185,7 @@ class Partition: for i in range(storage['DISK_RETRY_ATTEMPTS']): if not self.partprobe(): raise DiskError(f"Could not perform partprobe on {self.device_path}") - + time.sleep(max(0.1, storage['DISK_TIMEOUTS'] * i)) partuuid = self._safe_part_uuid @@ -294,9 +294,9 @@ class Partition: return bind_name @property - def subvolumes(self) -> Iterator[BtrfsSubvolume]: + def subvolumes(self) -> Iterator[BtrfsSubvolumeInfo]: from .helpers import findmnt - + def iterate_children_recursively(information): for child in information.get('children', []): if target := child.get('target'): @@ -452,7 +452,7 @@ class Partition: if retry is True: log(f"Retrying in {storage.get('DISK_TIMEOUTS', 1)} seconds.", level=logging.WARNING, fg="orange") time.sleep(storage.get('DISK_TIMEOUTS', 1)) - + return self.format(filesystem, path, log_formatting, options, retry=False) if get_filesystem_type(path) == 'crypto_LUKS' or get_filesystem_type(self.real_device) == 'crypto_LUKS': diff --git a/archinstall/lib/disk/user_guides.py b/archinstall/lib/disk/user_guides.py index 5fa6bfdc..5809c073 100644 --- a/archinstall/lib/disk/user_guides.py +++ b/archinstall/lib/disk/user_guides.py @@ -3,6 +3,8 @@ import logging from typing import Optional, Dict, Any, List, TYPE_CHECKING # https://stackoverflow.com/a/39757388/929999 +from ..models.subvolume import Subvolume + if TYPE_CHECKING: from .blockdevice import BlockDevice _: Any @@ -107,17 +109,14 @@ def suggest_single_disk_layout(block_device :BlockDevice, # https://unix.stackexchange.com/questions/246976/btrfs-subvolume-uuid-clash # https://github.com/classy-giraffe/easy-arch/blob/main/easy-arch.sh layout[block_device.path]['partitions'][1]['btrfs'] = { - "subvolumes" : { - "@":"/", - "@home": "/home", - "@log": "/var/log", - "@pkg": "/var/cache/pacman/pkg", - "@.snapshots": "/.snapshots" - } + 'subvolumes': [ + Subvolume('@', '/'), + Subvolume('@home', '/home'), + Subvolume('@log', '/var/log'), + Subvolume('@pkg', '/var/cache/pacman/pkg'), + Subvolume('@.snapshots', '/.snapshots') + ] } - # else: - # pass # ... implement a guided setup - elif using_home_partition: # If we don't want to use subvolumes, # But we want to be able to re-use data between re-installs.. diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index bf296c2e..97c2492d 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -24,6 +24,7 @@ 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 if TYPE_CHECKING: _: Any @@ -263,47 +264,25 @@ class Installer: hsm_device_path = storage['arguments']['HSM'] fido2_enroll(hsm_device_path, partition['device_instance'], password) - # we manage the btrfs partitions - if any(btrfs_subvolumes := [entry for entry in list_part if entry.get('btrfs', {}).get('subvolumes', {})]): - for partition in btrfs_subvolumes: - if mount_options := ','.join(partition.get('filesystem',{}).get('mount_options',[])): - self.mount(partition['device_instance'], "/", options=mount_options) - else: - self.mount(partition['device_instance'], "/") - - setup_subvolumes( - installation=self, - partition_dict=partition - ) + btrfs_subvolumes = [entry for entry in list_part if entry.get('btrfs', {}).get('subvolumes', [])] - partition['device_instance'].unmount() + for partition in btrfs_subvolumes: + device_instance = partition['device_instance'] + mount_options = partition.get('filesystem', {}).get('mount_options', []) + self.mount(device_instance, "/", options=','.join(mount_options)) + setup_subvolumes(installation=self, partition_dict=partition) + device_instance.unmount() # We then handle any special cases, such as btrfs - if any(btrfs_subvolumes := [entry for entry in list_part if entry.get('btrfs', {}).get('subvolumes', {})]): - for partition_information in btrfs_subvolumes: - for name, mountpoint in sorted(partition_information['btrfs']['subvolumes'].items(), key=lambda item: item[1]): - btrfs_subvolume_information = {} - - match mountpoint: - case str(): # backwards-compatability - btrfs_subvolume_information['mountpoint'] = mountpoint - btrfs_subvolume_information['options'] = [] - case dict(): - btrfs_subvolume_information['mountpoint'] = mountpoint.get('mountpoint', None) - btrfs_subvolume_information['options'] = mountpoint.get('options', []) - case _: - continue - - if mountpoint_parsed := btrfs_subvolume_information.get('mountpoint'): - # We cache the mount call for later - mount_queue[mountpoint_parsed] = lambda device=partition_information['device_instance'], \ - name=name, \ - subvolume_information=btrfs_subvolume_information: mount_subvolume( - installation=self, - device=device, - name=name, - subvolume_information=subvolume_information - ) + for partition in btrfs_subvolumes: + subvolumes: List[Subvolume] = partition['btrfs']['subvolumes'] + for subvolume in sorted(subvolumes, key=lambda item: item.mountpoint): + # We cache the mount call for later + mount_queue[subvolume.mountpoint] = lambda sub_vol=subvolume, device=partition['device_instance']: mount_subvolume( + installation=self, + device=device, + subvolume=sub_vol + ) # We mount ordinary partitions, and we sort them by the mountpoint for partition in sorted([entry for entry in list_part if entry.get('mountpoint', False)], key=lambda part: part['mountpoint']): diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index cb61168d..49083517 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -325,22 +325,23 @@ class GlobalMenu(GeneralMenu): def _select_harddrives(self, old_harddrives : list) -> List: harddrives = select_harddrives(old_harddrives) - if len(harddrives) == 0: - prompt = _( - "You decided to skip harddrive selection\nand will use whatever drive-setup is mounted at {} (experimental)\n" - "WARNING: Archinstall won't check the suitability of this setup\n" - "Do you wish to continue?" - ).format(storage['MOUNT_POINT']) - - choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes(), skip=False).run() - - if choice.value == Menu.no(): - return self._select_harddrives(old_harddrives) - - # in case the harddrives got changed we have to reset the disk layout as well - if old_harddrives != harddrives: - self._menu_options['disk_layouts'].set_current_selection(None) - storage['arguments']['disk_layouts'] = {} + if harddrives: + if len(harddrives) == 0: + prompt = _( + "You decided to skip harddrive selection\nand will use whatever drive-setup is mounted at {} (experimental)\n" + "WARNING: Archinstall won't check the suitability of this setup\n" + "Do you wish to continue?" + ).format(storage['MOUNT_POINT']) + + choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes(), skip=False).run() + + if choice.value == Menu.no(): + return self._select_harddrives(old_harddrives) + + # in case the harddrives got changed we have to reset the disk layout as well + if old_harddrives != harddrives: + self._menu_options['disk_layouts'].set_current_selection(None) + storage['arguments']['disk_layouts'] = {} return harddrives diff --git a/archinstall/lib/menu/list_manager.py b/archinstall/lib/menu/list_manager.py index 7e051528..40d01ce3 100644 --- a/archinstall/lib/menu/list_manager.py +++ b/archinstall/lib/menu/list_manager.py @@ -137,34 +137,35 @@ class ListManager: else: self._default_action = [str(default_action),] - self.header = header if header else None - self.cancel_action = str(_('Cancel')) - self.confirm_action = str(_('Confirm and exit')) - self.separator = '' - self.bottom_list = [self.confirm_action,self.cancel_action] - self.bottom_item = [self.cancel_action] - self.base_actions = base_actions if base_actions else [str(_('Add')),str(_('Copy')),str(_('Edit')),str(_('Delete'))] + self._header = header if header else None + self._cancel_action = str(_('Cancel')) + self._confirm_action = str(_('Confirm and exit')) + self._separator = '' + self._bottom_list = [self._confirm_action, self._cancel_action] + self._bottom_item = [self._cancel_action] + self._base_actions = base_actions if base_actions else [str(_('Add')), str(_('Copy')), str(_('Edit')), str(_('Delete'))] self._original_data = copy.deepcopy(base_list) self._data = copy.deepcopy(base_list) # as refs, changes are immediate + # default values for the null case self.target: Optional[Any] = None self.action = self._null_action - if len(self._data) == 0 and self._null_action: - self._data = self.exec_action(self._data) - def run(self): while True: # this will return a dictionary with the key as the menu entry to be displayed # and the value is the original value from the self._data container + data_formatted = self.reformat(self._data) options = list(data_formatted.keys()) - options.append(self.separator) + + if len(options) > 0: + options.append(self._separator) if self._default_action: options += self._default_action - options += self.bottom_list + options += self._bottom_list system('clear') @@ -174,12 +175,12 @@ class ListManager: sort=False, clear_screen=False, clear_menu_on_exit=False, - header=self.header, + header=self._header, skip_empty_entries=True, skip=False ).run() - if not target.value or target.value in self.bottom_list: + if not target.value or target.value in self._bottom_list: self.action = target break @@ -201,13 +202,13 @@ class ListManager: # Possible enhancement. If run_actions returns false a message line indicating the failure self.run_actions(target.value) - if target.value == self.cancel_action: # TODO dubious + if target.value == self._cancel_action: # TODO dubious return self._original_data # return the original list else: return self._data def run_actions(self,prompt_data=None): - options = self.action_list() + self.bottom_item + options = self.action_list() + self._bottom_item prompt = _("Select an action for < {} >").format(prompt_data if prompt_data else self.target) choice = Menu( prompt, @@ -215,13 +216,13 @@ class ListManager: sort=False, clear_screen=False, clear_menu_on_exit=False, - preset_values=self.bottom_item, + preset_values=self._bottom_item, show_search_hint=False ).run() self.action = choice.value - if self.action and self.action != self.cancel_action: + if self.action and self.action != self._cancel_action: self._data = self.exec_action(self._data) """ @@ -243,7 +244,7 @@ class ListManager: can define alternate action list or customize the list for each item. Executed after any item is selected, contained in self.target """ - return self.base_actions + return self._base_actions def exec_action(self, data: Any): """ diff --git a/archinstall/lib/models/subvolume.py b/archinstall/lib/models/subvolume.py new file mode 100644 index 00000000..34a09227 --- /dev/null +++ b/archinstall/lib/models/subvolume.py @@ -0,0 +1,68 @@ +from dataclasses import dataclass +from typing import List, Any, Dict + + +@dataclass +class Subvolume: + name: str + mountpoint: str + compress: bool = False + nodatacow: bool = False + + def display(self) -> str: + options_str = ','.join(self.options) + return f'{_("Subvolume")}: {self.name:15} {_("Mountpoint")}: {self.mountpoint:20} {_("Options")}: {options_str}' + + @property + def options(self) -> List[str]: + options = [ + 'compress' if self.compress else '', + 'nodatacow' if self.nodatacow else '' + ] + return [o for o in options if len(o)] + + def json(self) -> Dict[str, Any]: + return { + 'name': self.name, + 'mountpoint': self.mountpoint, + 'compress': self.compress, + 'nodatacow': self.nodatacow + } + + @classmethod + def _parse(cls, config_subvolumes: List[Dict[str, Any]]) -> List['Subvolume']: + subvolumes = [] + for entry in config_subvolumes: + if not entry.get('name', None) or not entry.get('mountpoint', None): + continue + + subvolumes.append( + Subvolume( + entry['name'], + entry['mountpoint'], + entry.get('compress', False), + entry.get('nodatacow', False) + ) + ) + + return subvolumes + + @classmethod + def _parse_backwards_compatible(cls, config_subvolumes) -> List['Subvolume']: + subvolumes = [] + for name, mountpoint in config_subvolumes.items(): + if not name or not mountpoint: + continue + + subvolumes.append(Subvolume(name, mountpoint)) + + return subvolumes + + @classmethod + def parse_arguments(cls, config_subvolumes: Any) -> List['Subvolume']: + if isinstance(config_subvolumes, list): + return cls._parse(config_subvolumes) + elif isinstance(config_subvolumes, dict): + return cls._parse_backwards_compatible(config_subvolumes) + + raise ValueError('Unknown disk layout btrfs subvolume format') diff --git a/archinstall/lib/models/users.py b/archinstall/lib/models/users.py index f72cabde..a8feb9ef 100644 --- a/archinstall/lib/models/users.py +++ b/archinstall/lib/models/users.py @@ -27,8 +27,10 @@ class User: } def display(self) -> str: - strength = PasswordStrength.strength(self.password) - password = '*' * len(self.password) + f' ({strength.value})' + password = '*' * (len(self.password) if self.password else 0) + if password: + strength = PasswordStrength.strength(self.password) + password += f' ({strength.value})' return f'{_("Username")}: {self.username:16} {_("Password")}: {password:20} sudo: {str(self.sudo)}' @classmethod diff --git a/archinstall/lib/user_interaction/partitioning_conf.py b/archinstall/lib/user_interaction/partitioning_conf.py index caf5f5df..63b7c7df 100644 --- a/archinstall/lib/user_interaction/partitioning_conf.py +++ b/archinstall/lib/user_interaction/partitioning_conf.py @@ -351,18 +351,16 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, if partition is not None: if not block_device_struct["partitions"][partition].get('btrfs', {}): block_device_struct["partitions"][partition]['btrfs'] = {} - if not block_device_struct["partitions"][partition]['btrfs'].get('subvolumes', {}): - block_device_struct["partitions"][partition]['btrfs']['subvolumes'] = {} + if not block_device_struct["partitions"][partition]['btrfs'].get('subvolumes', []): + block_device_struct["partitions"][partition]['btrfs']['subvolumes'] = [] prev = block_device_struct["partitions"][partition]['btrfs']['subvolumes'] - result = SubvolumeList(_("Manage btrfs subvolumes for current partition"),prev).run() - if result: - block_device_struct["partitions"][partition]['btrfs']['subvolumes'] = result - else: - del block_device_struct["partitions"][partition]['btrfs'] + result = SubvolumeList(_("Manage btrfs subvolumes for current partition"), prev).run() + block_device_struct["partitions"][partition]['btrfs']['subvolumes'] = result return block_device_struct + def select_encrypted_partitions( title :str, partitions :List[Partition], diff --git a/archinstall/lib/user_interaction/subvolume_config.py b/archinstall/lib/user_interaction/subvolume_config.py index 94e6f5d7..a54ec891 100644 --- a/archinstall/lib/user_interaction/subvolume_config.py +++ b/archinstall/lib/user_interaction/subvolume_config.py @@ -1,155 +1,94 @@ -from typing import Dict, List +from typing import Dict, List, Optional, Any, TYPE_CHECKING from ..menu.list_manager import ListManager from ..menu.menu import MenuSelectionType -from ..menu.selection_menu import Selector, GeneralMenu from ..menu.text_input import TextInput from ..menu import Menu +from ..models.subvolume import Subvolume + +if TYPE_CHECKING: + _: Any -""" -UI classes -""" class SubvolumeList(ListManager): - def __init__(self,prompt,list): - self.ObjectNullAction = None # str(_('Add')) - self.ObjectDefaultAction = str(_('Add')) - super().__init__(prompt,list,None,self.ObjectNullAction,self.ObjectDefaultAction) - - def reformat(self, data: Dict) -> Dict: - def presentation(key :str, value :Dict): - text = _(" Subvolume :{:16}").format(key) - if isinstance(value,str): - text += _(" mounted at {:16}").format(value) - else: - if value.get('mountpoint'): - text += _(" mounted at {:16}").format(value['mountpoint']) - else: - text += (' ' * 28) - - if value.get('options',[]): - text += _(" with option {}").format(', '.join(value['options'])) - return text - - formatted = {presentation(k, v): k for k, v in data.items()} - return {k: v for k, v in sorted(formatted.items(), key=lambda e: e[0])} + def __init__(self, prompt: str, current_volumes: List[Subvolume]): + self._actions = [ + str(_('Add subvolume')), + str(_('Edit subvolume')), + str(_('Delete subvolume')) + ] + super().__init__(prompt, current_volumes, self._actions, self._actions[0]) - def action_list(self): - return super().action_list() + def reformat(self, data: List[Subvolume]) -> Dict[str, Subvolume]: + return {e.display(): e for e in data} - def exec_action(self, data: Dict): - if self.target: - origkey, origval = list(self.target.items())[0] - else: - origkey = None + def action_list(self): + active_user = self.target if self.target else None - if self.action == str(_('Delete')): - del data[origkey] + if active_user is None: + return [self._actions[0]] else: - if self.action == str(_('Add')): - self.target = {} - print(_('\n Fill the desired values for a new subvolume \n')) - with SubvolumeMenu(self.target,self.action) as add_menu: - for elem in ['name','mountpoint','options']: - add_menu.exec_option(elem) - else: - SubvolumeMenu(self.target,self.action).run() + return self._actions[1:] - data.update(self.target) + def _prompt_options(self, editing: Optional[Subvolume] = None) -> List[str]: + preset_options = [] + if editing: + preset_options = editing.options - return data - - -class SubvolumeMenu(GeneralMenu): - def __init__(self,parameters,action=None): - self.data = parameters - self.action = action - self.ds = {} - self.ds['name'] = None - self.ds['mountpoint'] = None - self.ds['options'] = None - if self.data: - origkey,origval = list(self.data.items())[0] - self.ds['name'] = origkey - if isinstance(origval,str): - self.ds['mountpoint'] = origval - else: - self.ds['mountpoint'] = self.data[origkey].get('mountpoint') - self.ds['options'] = self.data[origkey].get('options') - - super().__init__(data_store=self.ds) - - def _setup_selection_menu_options(self): - self._menu_options['name'] = Selector( - str(_('Subvolume name ')), - self._select_subvolume_name if not self.action or self.action in (str(_('Add')), str(_('Copy'))) else None, - mandatory=True, - enabled=True) - - self._menu_options['mountpoint'] = Selector( - str(_('Subvolume mountpoint')), - self._select_subvolume_mount_point if not self.action or self.action in (str(_('Add')),str(_('Edit'))) else None, - enabled=True) - - self._menu_options['options'] = Selector( - str(_('Subvolume options')), - self._select_subvolume_options if not self.action or self.action in (str(_('Add')),str(_('Edit'))) else None, - enabled=True) - - self._menu_options['save'] = Selector( - str(_('Save')), - exec_func=lambda n,v:True, - enabled=True) - - self._menu_options['cancel'] = Selector( - str(_('Cancel')), - # func = lambda pre:True, - exec_func=lambda n,v:self.fast_exit(n), - enabled=True) - - self.cancel_action = 'cancel' - self.save_action = 'save' - self.bottom_list = [self.save_action,self.cancel_action] - - def fast_exit(self,accion): - if self.option(accion).get_selection(): - for item in self.list_options(): - if self.option(item).is_mandatory(): - self.option(item).set_mandatory(False) - return True - - def exit_callback(self): - # we exit without moving data - if self.option(self.cancel_action).get_selection(): - return - if not self.ds['name']: - return - else: - key = self.ds['name'] - value = {} - if self.ds['mountpoint']: - value['mountpoint'] = self.ds['mountpoint'] - if self.ds['options']: - value['options'] = self.ds['options'] - self.data.update({key : value}) - - def _select_subvolume_name(self,value): - return TextInput(str(_("Subvolume name :")),value).run() - - def _select_subvolume_mount_point(self,value): - return TextInput(str(_("Select a mount point :")),value).run() - - def _select_subvolume_options(self,value) -> List[str]: - # def __init__(self, title, p_options, skip=True, multi=False, default_option=None, sort=True): choice = Menu( str(_("Select the desired subvolume options ")), ['nodatacow','compress'], skip=True, - preset_values=value, + preset_values=preset_options, multi=True ).run() if choice.type_ == MenuSelectionType.Selection: - return choice.value + return choice.value # type: ignore return [] + + def _add_subvolume(self, editing: Optional[Subvolume] = None) -> Optional[Subvolume]: + name = TextInput(f'\n\n{_("Subvolume name")}: ', editing.name if editing else '').run() + + if not name: + return None + + mountpoint = TextInput(f'\n{_("Subvolume mountpoint")}: ', editing.mountpoint if editing else '').run() + + if not mountpoint: + return None + + options = self._prompt_options(editing) + + subvolume = Subvolume(name, mountpoint) + subvolume.compress = 'compress' in options + subvolume.nodatacow = 'nodatacow' in options + + return subvolume + + def exec_action(self, data: List[Subvolume]) -> List[Subvolume]: + if self.target: + active_subvolume = self.target + else: + active_subvolume = None + + if self.action == self._actions[0]: # add + new_subvolume = self._add_subvolume() + + if new_subvolume is not None: + # in case a user with the same username as an existing user + # was created we'll replace the existing one + data = [d for d in data if d.name != new_subvolume.name] + data += [new_subvolume] + elif self.action == self._actions[1]: # edit subvolume + new_subvolume = self._add_subvolume(active_subvolume) + + if new_subvolume is not None: + # we'll remove the original subvolume and add the modified version + data = [d for d in data if d.name != active_subvolume.name and d.name != new_subvolume.name] + data += [new_subvolume] + elif self.action == self._actions[2]: # delete + data = [d for d in data if d != active_subvolume] + + return data -- cgit v1.2.3-70-g09d2 From fd131c8fe956858fef802f8e53a530daa0b5df47 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Tue, 7 Jun 2022 19:00:48 +1000 Subject: Update blockdevice (#1289) * Update blockdevice class Co-authored-by: Daniel Girtler --- .github/workflows/mypy.yaml | 2 +- archinstall/lib/disk/blockdevice.py | 344 +++++++++++++++++------------------- archinstall/lib/disk/filesystem.py | 2 +- archinstall/lib/disk/partition.py | 14 +- archinstall/lib/menu/global_menu.py | 2 +- archinstall/lib/output.py | 4 +- 6 files changed, 177 insertions(+), 191 deletions(-) (limited to 'archinstall/lib') diff --git a/.github/workflows/mypy.yaml b/.github/workflows/mypy.yaml index b0901b38..01d4741f 100644 --- a/.github/workflows/mypy.yaml +++ b/.github/workflows/mypy.yaml @@ -15,4 +15,4 @@ jobs: # one day this will be enabled # run: mypy --strict --module archinstall || exit 0 - name: run mypy - run: mypy --follow-imports=silent archinstall/lib/menu/selection_menu.py archinstall/lib/menu/global_menu.py archinstall/lib/models/network_configuration.py archinstall/lib/menu/list_manager.py archinstall/lib/user_interaction/network_conf.py archinstall/lib/models/users.py archinstall/lib/user_interaction/subvolume_config.py archinstall/lib/disk/btrfs/btrfs_helpers.py archinstall/lib/translation.py + run: mypy --follow-imports=silent archinstall/lib/menu/selection_menu.py archinstall/lib/menu/global_menu.py archinstall/lib/models/network_configuration.py archinstall/lib/menu/list_manager.py archinstall/lib/user_interaction/network_conf.py archinstall/lib/models/users.py archinstall/lib/disk/blockdevice.py archinstall/lib/user_interaction/subvolume_config.py archinstall/lib/disk/btrfs/btrfs_helpers.py archinstall/lib/translation.py diff --git a/archinstall/lib/disk/blockdevice.py b/archinstall/lib/disk/blockdevice.py index c7b69205..4e207bf4 100644 --- a/archinstall/lib/disk/blockdevice.py +++ b/archinstall/lib/disk/blockdevice.py @@ -1,13 +1,11 @@ from __future__ import annotations -import os import json import logging import time -from functools import cached_property -from typing import Optional, Dict, Any, Iterator, Tuple, List, TYPE_CHECKING -# https://stackoverflow.com/a/39757388/929999 -if TYPE_CHECKING: - from .partition import Partition + +from collections import OrderedDict +from dataclasses import dataclass +from typing import Optional, Dict, Any, Iterator, List, TYPE_CHECKING from ..exceptions import DiskError, SysCallError from ..output import log @@ -15,18 +13,44 @@ from ..general import SysCommand from ..storage import storage +if TYPE_CHECKING: + from .partition import Partition + _: Any + + +@dataclass +class BlockSizeInfo: + start: str + end: str + size: str + + +@dataclass +class BlockInfo: + pttype: str + ptuuid: str + size: int + tran: Optional[str] + rota: bool + free_space: Optional[List[BlockSizeInfo]] + + class BlockDevice: def __init__(self, path :str, info :Optional[Dict[str, Any]] = None): if not info: from .helpers import all_blockdevices # If we don't give any information, we need to auto-fill it. # Otherwise any subsequent usage will break. - info = all_blockdevices(partitions=False)[path].info + self.info = all_blockdevices(partitions=False)[path].info + else: + self.info = info - self.path = path - self.info = info + self._path = path self.keep_partitions = True - self.part_cache = {} + self._block_info = self._fetch_information() + self._partitions: Dict[str, 'Partition'] = {} + + self._load_partitions() # TODO: Currently disk encryption is a BIT misleading. # It's actually partition-encryption, but for future-proofing this @@ -35,70 +59,114 @@ class BlockDevice: def __repr__(self, *args :str, **kwargs :str) -> str: return self._str_repr - @cached_property + @property + def path(self) -> str: + return self._path + + @property def _str_repr(self) -> str: - return f"BlockDevice({self.device_or_backfile}, size={self._safe_size}GB, free_space={self._safe_free_space}, bus_type={self.bus_type})" - - @cached_property - def display_info(self) -> str: - columns = { - str(_('Device')): self.device_or_backfile, - str(_('Size')): f'{self._safe_size}GB', - str(_('Free space')): f'{self._safe_free_space}', + return f"BlockDevice({self._device_or_backfile}, size={self.size}GB, free_space={self._safe_free_space()}, bus_type={self.bus_type})" + + def as_json(self) -> Dict[str, Any]: + return { + str(_('Device')): self._device_or_backfile, + str(_('Size')): f'{self.size}GB', + str(_('Free space')): f'{self._safe_free_space()}', str(_('Bus-type')): f'{self.bus_type}' } - padding = max([len(k) for k in columns.keys()]) - - pretty = '' - for k, v in columns.items(): - k = k.ljust(padding, ' ') - pretty += f'{k} = {v}\n' - - return pretty.rstrip() - - def __iter__(self) -> Iterator[Partition]: + def __iter__(self) -> Iterator['Partition']: for partition in self.partitions: yield self.partitions[partition] def __getitem__(self, key :str, *args :str, **kwargs :str) -> Any: if hasattr(self, key): return getattr(self, key) - elif key not in self.info: - raise KeyError(f'{self} does not contain information: "{key}"') - return self.info[key] + + if self.info and key in self.info: + return self.info[key] + + raise KeyError(f'{self.info} does not contain information: "{key}"') def __len__(self) -> int: return len(self.partitions) def __lt__(self, left_comparitor :'BlockDevice') -> bool: - return self.path < left_comparitor.path + return self._path < left_comparitor.path def json(self) -> str: """ json() has precedence over __dump__, so this is a way to give less/partial information for user readability. """ - return self.path + return self._path def __dump__(self) -> Dict[str, Dict[str, Any]]: return { - self.path : { - 'partuuid' : self.uuid, - 'wipe' : self.info.get('wipe', None), - 'partitions' : [part.__dump__() for part in self.partitions.values()] + self._path: { + 'partuuid': self.uuid, + 'wipe': self.info.get('wipe', None), + 'partitions': [part.__dump__() for part in self.partitions.values()] } } - @property - def partition_type(self) -> str: - output = json.loads(SysCommand(f"lsblk --json -o+PTTYPE {self.path}").decode('UTF-8')) + def _call_lsblk(self, path: str) -> Dict[str, Any]: + output = SysCommand(f'lsblk --json -b -o+SIZE,PTTYPE,ROTA,TRAN,PTUUID {self._path}').decode('UTF-8') + if output: + lsblk_info = json.loads(output) + return lsblk_info + + raise DiskError(f'Failed to read disk "{self.path}" with lsblk') + + def _load_partitions(self): + from .partition import Partition - for device in output['blockdevices']: - return device['pttype'] + lsblk_info = self._call_lsblk(self._path) + device = lsblk_info['blockdevices'][0] + self._partitions.clear() - @cached_property - def device_or_backfile(self) -> str: + if children := device.get('children', None): + root = f'/dev/{device["name"]}' + for child in children: + part_id = child['name'].removeprefix(device['name']) + self._partitions[part_id] = Partition(root + part_id, block_device=self, part_id=part_id) + + def _get_free_space(self) -> Optional[List[BlockSizeInfo]]: + # NOTE: parted -s will default to `cancel` on prompt, skipping any partition + # that is "outside" the disk. in /dev/sr0 this is usually the case with Archiso, + # so the free will ignore the ESP partition and just give the "free" space. + # Doesn't harm us, but worth noting in case something weird happens. + try: + output = SysCommand(f"parted -s --machine {self._path} print free").decode('utf-8') + if output: + free_lines = [line for line in output.split('\n') if 'free' in line] + sizes = [] + for free_space in free_lines: + _, start, end, size, *_ = free_space.strip('\r\n;').split(':') + sizes.append(BlockSizeInfo(start, end, size)) + + return sizes + except SysCallError as error: + log(f"Could not get free space on {self._path}: {error}", level=logging.DEBUG) + + return None + + def _fetch_information(self) -> BlockInfo: + lsblk_info = self._call_lsblk(self._path) + device = lsblk_info['blockdevices'][0] + free_space = self._get_free_space() + + return BlockInfo( + pttype=device['pttype'], + ptuuid=device['ptuuid'], + size=device['size'], + tran=device['tran'], + rota=device['rota'], + free_space=free_space + ) + + @property + def _device_or_backfile(self) -> Optional[str]: """ Returns the actual device-endpoint of the BlockDevice. If it's a loop-back-device it returns the back-file, @@ -118,7 +186,7 @@ class BlockDevice: return None @property - def device(self) -> str: + def device(self) -> Optional[str]: """ Returns the device file of the BlockDevice. If it's a loop-back-device it returns the /dev/X device, @@ -126,168 +194,84 @@ class BlockDevice: And if it's a crypto-device it returns the parent device """ if "DEVTYPE" not in self.info: - raise DiskError(f'Could not locate backplane info for "{self.path}"') + raise DiskError(f'Could not locate backplane info for "{self._path}"') if self.info['DEVTYPE'] in ['disk','loop']: - return self.path + return self._path elif self.info['DEVTYPE'][:4] == 'raid': # This should catch /dev/md## raid devices - return self.path + return self._path elif self.info['DEVTYPE'] == 'crypt': if 'pkname' not in self.info: - raise DiskError(f'A crypt device ({self.path}) without a parent kernel device name.') + raise DiskError(f'A crypt device ({self._path}) without a parent kernel device name.') return f"/dev/{self.info['pkname']}" else: - log(f"Unknown blockdevice type for {self.path}: {self.info['DEVTYPE']}", level=logging.DEBUG) + log(f"Unknown blockdevice type for {self._path}: {self.info['DEVTYPE']}", level=logging.DEBUG) - # if not stat.S_ISBLK(os.stat(full_path).st_mode): - # raise DiskError(f'Selected disk "{full_path}" is not a block device.') - - @property - def partitions(self) -> Dict[str, Partition]: - from .filesystem import Partition - - self.partprobe() - result = SysCommand(['/usr/bin/lsblk', '-J', self.path]) - - if b'not a block device' in result: - raise DiskError(f'Can not read partitions off something that isn\'t a block device: {self.path}') - - if not result[:1] == b'{': - raise DiskError('Error getting JSON output from:', f'/usr/bin/lsblk -J {self.path}') - - r = json.loads(result.decode('UTF-8')) - if len(r['blockdevices']) and 'children' in r['blockdevices'][0]: - root_path = f"/dev/{r['blockdevices'][0]['name']}" - for part in r['blockdevices'][0]['children']: - part_id = part['name'][len(os.path.basename(self.path)):] - if part_id not in self.part_cache: - # TODO: Force over-write even if in cache? - if part_id not in self.part_cache or self.part_cache[part_id].size != part['size']: - self.part_cache[part_id] = Partition(root_path + part_id, block_device=self, part_id=part_id) - - return {k: self.part_cache[k] for k in sorted(self.part_cache)} + return None @property - def partition(self) -> Partition: - all_partitions = self.partitions - return [all_partitions[k] for k in all_partitions] + def partition_type(self) -> str: + return self._block_info.pttype @property - def partition_table_type(self) -> int: - # TODO: Don't hardcode :) - # Remove if we don't use this function anywhere - from .filesystem import GPT - return GPT - - @cached_property def uuid(self) -> str: - log('BlockDevice().uuid is untested!', level=logging.WARNING, fg='yellow') - """ - Returns the disk UUID as returned by lsblk. - This is more reliable than relying on /dev/disk/by-partuuid as - it doesn't seam to be able to detect md raid partitions. - """ - return SysCommand(f'blkid -s PTUUID -o value {self.path}').decode('UTF-8') - - @cached_property - def _safe_size(self) -> float: - from .helpers import convert_size_to_gb - - try: - output = json.loads(SysCommand(f"lsblk --json -b -o+SIZE {self.path}").decode('UTF-8')) - except SysCallError: - return -1.0 + return self._block_info.ptuuid - for device in output['blockdevices']: - return convert_size_to_gb(device['size']) - - @cached_property + @property def size(self) -> float: from .helpers import convert_size_to_gb + return convert_size_to_gb(self._block_info.size) - output = json.loads(SysCommand(f"lsblk --json -b -o+SIZE {self.path}").decode('UTF-8')) - - for device in output['blockdevices']: - return convert_size_to_gb(device['size']) - - @cached_property - def bus_type(self) -> str: - output = json.loads(SysCommand(f"lsblk --json -o+ROTA,TRAN {self.path}").decode('UTF-8')) - - for device in output['blockdevices']: - return device['tran'] + @property + def bus_type(self) -> Optional[str]: + return self._block_info.tran - @cached_property + @property def spinning(self) -> bool: - output = json.loads(SysCommand(f"lsblk --json -o+ROTA,TRAN {self.path}").decode('UTF-8')) + return self._block_info.rota - for device in output['blockdevices']: - return device['rota'] is True + @property + def partitions(self) -> Dict[str, 'Partition']: + self._partprobe() + self._load_partitions() + return OrderedDict(sorted(self._partitions.items())) - @cached_property - def _safe_free_space(self) -> Tuple[str, ...]: - try: - return '+'.join(part[2] for part in self.free_space) - except SysCallError: - return '?' + @property + def partition(self) -> List['Partition']: + return list(self.partitions.values()) - @cached_property - def free_space(self) -> Tuple[str, ...]: - # NOTE: parted -s will default to `cancel` on prompt, skipping any partition - # that is "outside" the disk. in /dev/sr0 this is usually the case with Archiso, - # so the free will ignore the ESP partition and just give the "free" space. - # Doesn't harm us, but worth noting in case something weird happens. - try: - for line in SysCommand(f"parted -s --machine {self.path} print free"): - if 'free' in (free_space := line.decode('UTF-8')): - _, start, end, size, *_ = free_space.strip('\r\n;').split(':') - yield (start, end, size) - except SysCallError as error: - log(f"Could not get free space on {self.path}: {error}", level=logging.DEBUG) - - @cached_property - def largest_free_space(self) -> List[str]: - info = [] - for space_info in self.free_space: - if not info: - info = space_info - else: - # [-1] = size - if space_info[-1] > info[-1]: - info = space_info - return info - - @cached_property + @property def first_free_sector(self) -> str: - if info := self.largest_free_space: - start = info[0] + if block_size := self._largest_free_space(): + return block_size.start else: - start = '512MB' - return start + return '512MB' - @cached_property + @property def first_end_sector(self) -> str: - if info := self.largest_free_space: - end = info[1] + if block_size := self._largest_free_space(): + return block_size.end else: - end = f"{self.size}GB" - return end - - def partprobe(self) -> bool: - return SysCommand(['partprobe', self.path]).exit_code == 0 + return f"{self.size}GB" + + def _safe_free_space(self) -> str: + if self._block_info.free_space: + sizes = [free_space.size for free_space in self._block_info.free_space] + return '+'.join(sizes) + return '?' + + def _largest_free_space(self) -> Optional[BlockSizeInfo]: + if self._block_info.free_space: + sorted_sizes = sorted(self._block_info.free_space, key=lambda x: x.size, reverse=True) + return sorted_sizes[0] + return None - def has_partitions(self) -> int: - return len(self.partitions) - - def has_mount_point(self, mountpoint :str) -> bool: - for partition in self.partitions: - if self.partitions[partition].mountpoint == mountpoint: - return True - return False + def _partprobe(self) -> bool: + return SysCommand(['partprobe', self._path]).exit_code == 0 def flush_cache(self) -> None: - self.part_cache = {} + self._load_partitions() def get_partition(self, uuid :Optional[str] = None, partuuid :Optional[str] = None) -> Partition: if not uuid and not partuuid: @@ -296,7 +280,7 @@ class BlockDevice: for count in range(storage.get('DISK_RETRY_ATTEMPTS', 5)): for partition_index, partition in self.partitions.items(): try: - if uuid and partition.uuid.lower() == uuid.lower(): + if uuid and partition.uuid and partition.uuid.lower() == uuid.lower(): return partition elif partuuid and partition.part_uuid.lower() == partuuid.lower(): return partition @@ -308,8 +292,8 @@ class BlockDevice: log(f"uuid {uuid} or {partuuid} not found. Waiting {storage.get('DISK_TIMEOUTS', 1) * count}s for next attempt",level=logging.DEBUG) time.sleep(storage.get('DISK_TIMEOUTS', 1) * count) - + log(f"Could not find {uuid}/{partuuid} in disk after 5 retries", level=logging.INFO) - log(f"Cache: {self.part_cache}") + log(f"Cache: {self._partitions}") log(f"Partitions: {self.partitions.items()}") raise DiskError(f"Partition {uuid}/{partuuid} was never found on {self} despite several attempts.") diff --git a/archinstall/lib/disk/filesystem.py b/archinstall/lib/disk/filesystem.py index cc29a491..1c7a801b 100644 --- a/archinstall/lib/disk/filesystem.py +++ b/archinstall/lib/disk/filesystem.py @@ -100,7 +100,7 @@ class Filesystem: partition['device_instance'] = self.blockdevice.get_partition(uuid=partition_uuid) except DiskError: partition['device_instance'] = self.blockdevice.get_partition(partuuid=partition_uuid) - + log(_("Re-using partition instance: {}").format(partition['device_instance']), level=logging.DEBUG, fg="gray") else: log(f"{self}.load_layout() doesn't know how to work without 'wipe' being set or UUID ({partition.get('PARTUUID')}) was given and found.", fg="yellow", level=logging.WARNING) diff --git a/archinstall/lib/disk/partition.py b/archinstall/lib/disk/partition.py index 6f25a5f7..062c79ab 100644 --- a/archinstall/lib/disk/partition.py +++ b/archinstall/lib/disk/partition.py @@ -175,7 +175,7 @@ class Partition: return device['pttype'] @property - def part_uuid(self) -> Optional[str]: + def part_uuid(self) -> str: """ Returns the PARTUUID as returned by lsblk. This is more reliable than relying on /dev/disk/by-partuuid as @@ -222,7 +222,7 @@ class Partition: For instance when you want to get a __repr__ of the class. """ if not self.partprobe(): - if self.block_device.info.get('TYPE') == 'iso9660': + if self.block_device.partition_type == 'iso9660': return None log(f"Could not reliably refresh PARTUUID of partition {self.device_path} due to partprobe error.", level=logging.DEBUG) @@ -230,7 +230,7 @@ class Partition: try: return SysCommand(f'blkid -s UUID -o value {self.device_path}').decode('UTF-8').strip() except SysCallError as error: - if self.block_device.info.get('TYPE') == 'iso9660': + if self.block_device.partition_type == 'iso9660': # Parent device is a Optical Disk (.iso dd'ed onto a device for instance) return None @@ -244,15 +244,15 @@ class Partition: For instance when you want to get a __repr__ of the class. """ if not self.partprobe(): - if self.block_device.info.get('TYPE') == 'iso9660': + if self.block_device.partition_type == 'iso9660': return None log(f"Could not reliably refresh PARTUUID of partition {self.device_path} due to partprobe error.", level=logging.DEBUG) try: - return SysCommand(f'blkid -s PARTUUID -o value {self.device_path}').decode('UTF-8').strip() + return self.block_device.uuid except SysCallError as error: - if self.block_device.info.get('TYPE') == 'iso9660': + if self.block_device.partition_type == 'iso9660': # Parent device is a Optical Disk (.iso dd'ed onto a device for instance) return None @@ -399,7 +399,7 @@ class Partition: elif filesystem == 'vfat': options = ['-F32'] + options - + log(f"/usr/bin/mkfs.vfat {' '.join(options)} {path}") if (handle := SysCommand(f"/usr/bin/mkfs.vfat {' '.join(options)} {path}")).exit_code != 0: raise DiskError(f"Could not format {path} with {filesystem} because: {handle.decode('UTF-8')}") self.filesystem = filesystem diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index 49083517..abd652a9 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -240,7 +240,7 @@ class GlobalMenu(GeneralMenu): selector = self._menu_options['harddrives'] if selector.has_selection(): drives = selector.current_selection - return '\n\n'.join([d.display_info for d in drives]) + return FormattedOutput.as_table(drives) return None def _prev_disk_layouts(self) -> Optional[str]: diff --git a/archinstall/lib/output.py b/archinstall/lib/output.py index 29b73bc4..e2b38ce6 100644 --- a/archinstall/lib/output.py +++ b/archinstall/lib/output.py @@ -11,7 +11,9 @@ class FormattedOutput: @classmethod def values(cls, o: Any) -> Dict[str, Any]: - if hasattr(o, 'json'): + if hasattr(o, 'as_json'): + return o.as_json() + elif hasattr(o, 'json'): return o.json() else: return o.__dict__ -- cgit v1.2.3-70-g09d2 From 99a9d3ff375cc5e419a34a6fda847cb62762144d Mon Sep 17 00:00:00 2001 From: Alexmelman88 <99257010+Alexmelman88@users.noreply.github.com> Date: Tue, 7 Jun 2022 12:01:42 +0300 Subject: Updated base.pot, languages.json, global_menu.py, ru locale (#1292) * Add files via upload * Add files via upload * Add files via upload * Add files via upload * Add files via upload --- archinstall/lib/menu/global_menu.py | 4 ++-- archinstall/locales/base.pot | 26 +++++++++++++++++++++++++- archinstall/locales/languages.json | 1 + archinstall/locales/ru/LC_MESSAGES/base.mo | Bin 32928 -> 33402 bytes archinstall/locales/ru/LC_MESSAGES/base.po | 26 +++++++++++++++++++++++++- 5 files changed, 53 insertions(+), 4 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index abd652a9..b73fb48f 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -296,10 +296,10 @@ class GlobalMenu(GeneralMenu): if not check('!root-password') and not has_superuser(): missing += [str(_('Either root-password or at least 1 user with sudo privileges must be specified'))] if not check('harddrives'): - missing += ['Hard drives'] + missing += [str(_('Drive(s)'))] if check('harddrives'): if not self._menu_options['harddrives'].is_empty() and not check('disk_layouts'): - missing += ['Disk layout'] + missing += [str(_('Disk layout'))] return missing diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index 6db91789..54ddbba3 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -652,7 +652,7 @@ msgid "" msgstr "" msgid "" -"Choose which servers to install, if none then a minimal installation wil be " +"Choose which servers to install, if none then a minimal installation will be " "done" msgstr "" @@ -760,3 +760,27 @@ msgstr "" msgid "Should \"{}\" be a superuser (sudo)?" msgstr "" + +msgid "Select which partitions to encrypt:" +msgstr "" + +msgid "very weak" +msgstr "" + +msgid "weak" +msgstr "" + +msgid "moderate" +msgstr "" + +msgid "strong" +msgstr "" + +msgid "Add subvolume" +msgstr "" + +msgid "Edit subvolume" +msgstr "" + +msgid "Delete subvolume" +msgstr "" diff --git a/archinstall/locales/languages.json b/archinstall/locales/languages.json index bf076f94..c3c9d2c2 100644 --- a/archinstall/locales/languages.json +++ b/archinstall/locales/languages.json @@ -128,6 +128,7 @@ {"abbr": "pi", "lang": "Pali"}, {"abbr": "pl", "lang": "Polish"}, {"abbr": "pt", "lang": "Portuguese"}, + {"abbr": "pt_BR", "lang": "Brazilian Portuguese"}, {"abbr": "ps", "lang": "Pushto"}, {"abbr": "qu", "lang": "Quechua"}, {"abbr": "rm", "lang": "Romansh"}, diff --git a/archinstall/locales/ru/LC_MESSAGES/base.mo b/archinstall/locales/ru/LC_MESSAGES/base.mo index be3405fe..8c0dc224 100644 Binary files a/archinstall/locales/ru/LC_MESSAGES/base.mo and b/archinstall/locales/ru/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/ru/LC_MESSAGES/base.po b/archinstall/locales/ru/LC_MESSAGES/base.po index 27f86e23..5a6f440a 100644 --- a/archinstall/locales/ru/LC_MESSAGES/base.po +++ b/archinstall/locales/ru/LC_MESSAGES/base.po @@ -771,7 +771,7 @@ msgstr "" "например, httpd, nginx, mariadb" msgid "" -"Choose which servers to install, if none then a minimal installation wil be " +"Choose which servers to install, if none then a minimal installation will be " "done" msgstr "" "Выберите серверы для установки, если их нет, то будет выполнена минимальная " @@ -897,6 +897,30 @@ msgstr "Введенное вами имя пользователя недейс msgid "Should \"{}\" be a superuser (sudo)?" msgstr "Должен ли \"{}\" быть суперпользователем (sudo)?" +msgid "Select which partitions to encrypt:" +msgstr "Выберите разделы для шифрования:" + +msgid "very weak" +msgstr "очень слабый" + +msgid "weak" +msgstr "слабый" + +msgid "moderate" +msgstr "умеренный" + +msgid "strong" +msgstr "надежный" + +msgid "Add subvolume" +msgstr "Добавить подтом" + +msgid "Edit subvolume" +msgstr "Редактировать подтом" + +msgid "Delete subvolume" +msgstr "Удалить подтом" + #, python-brace-format #~ msgid "Edit {origkey} :" #~ msgstr "Редактировать {origkey}:" -- cgit v1.2.3-70-g09d2 From aec86eb04e96f4c178cf89f7e72a257d12019fdc Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Tue, 7 Jun 2022 20:15:39 +1000 Subject: Fix disk layout display (#1314) Co-authored-by: Daniel Girtler --- .../lib/user_interaction/partitioning_conf.py | 32 ++++++++++++++++++---- 1 file changed, 27 insertions(+), 5 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/user_interaction/partitioning_conf.py b/archinstall/lib/user_interaction/partitioning_conf.py index 63b7c7df..0f4784b6 100644 --- a/archinstall/lib/user_interaction/partitioning_conf.py +++ b/archinstall/lib/user_interaction/partitioning_conf.py @@ -5,7 +5,7 @@ from typing import List, Any, Dict, Union, TYPE_CHECKING, Callable, Optional from ..menu import Menu from ..menu.menu import MenuSelectionType -from ..output import log +from ..output import log, FormattedOutput from ..disk.validators import fs_types @@ -28,16 +28,31 @@ def current_partition_layout(partitions: List[Dict[str, Any]], with_idx: bool = pad_right = spaces - pad_left return f'{pad_right * " "}{name}{pad_left * " "}|' + def flatten_data(data: Dict[str, Any]) -> Dict[str, Any]: + flattened = {} + for k, v in data.items(): + if k == 'filesystem': + flat = flatten_data(v) + flattened.update(flat) + elif k == 'btrfs': + # we're going to create a separate table for the btrfs subvolumes + pass + else: + flattened[k] = v + return flattened + + display_data: List[Dict[str, Any]] = [flatten_data(entry) for entry in partitions] + column_names = {} # this will add an initial index to the table for each partition if with_idx: - column_names['index'] = max([len(str(len(partitions))), len('index')]) + column_names['index'] = max([len(str(len(display_data))), len('index')]) # determine all attribute names and the max length - # of the value among all partitions to know the width + # of the value among all display_data to know the width # of the table cells - for p in partitions: + for p in display_data: for attribute, value in p.items(): if attribute in column_names.keys(): column_names[attribute] = max([column_names[attribute], len(str(value)), len(attribute)]) @@ -50,7 +65,7 @@ def current_partition_layout(partitions: List[Dict[str, Any]], with_idx: bool = current_layout = f'{current_layout[:-1]}\n{"-" * len(current_layout)}\n' - for idx, p in enumerate(partitions): + for idx, p in enumerate(display_data): row = '' for name, max_len in column_names.items(): if name == 'index': @@ -62,6 +77,13 @@ def current_partition_layout(partitions: List[Dict[str, Any]], with_idx: bool = current_layout += f'{row[:-1]}\n' + # we'll create a separate table for the btrfs subvolumes + btrfs_subvolumes = [partition['btrfs']['subvolumes'] for partition in partitions if partition.get('btrfs', None)] + if len(btrfs_subvolumes) > 0: + for subvolumes in btrfs_subvolumes: + output = FormattedOutput.as_table(subvolumes) + current_layout += f'\n{output}' + if with_title: title = str(_('Current partition layout')) return f'\n\n{title}:\n\n{current_layout}' -- cgit v1.2.3-70-g09d2 From 0bdb46f3081aaed095f87e35b18b3714d8782ed1 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Thu, 9 Jun 2022 22:54:12 +1000 Subject: Fancy user interface (#1320) * Display submenus as tables * Update * Update * Update * Update Co-authored-by: Daniel Girtler --- archinstall/lib/menu/global_menu.py | 16 +- archinstall/lib/menu/list_manager.py | 100 +++--- archinstall/lib/menu/menu.py | 21 +- archinstall/lib/models/network_configuration.py | 67 ++-- .../lib/user_interaction/manage_users_conf.py | 42 ++- archinstall/lib/user_interaction/network_conf.py | 96 +++--- .../lib/user_interaction/subvolume_config.py | 24 +- archinstall/locales/base.pot | 2 +- archinstall/locales/cs/LC_MESSAGES/base.mo | Bin 23958 -> 23739 bytes archinstall/locales/cs/LC_MESSAGES/base.po | 52 +++- archinstall/locales/de/LC_MESSAGES/base.mo | Bin 23664 -> 23285 bytes archinstall/locales/de/LC_MESSAGES/base.po | 81 ++++- archinstall/locales/en/LC_MESSAGES/base.po | 61 +++- archinstall/locales/es/LC_MESSAGES/base.mo | Bin 24988 -> 24797 bytes archinstall/locales/es/LC_MESSAGES/base.po | 30 +- archinstall/locales/fr/LC_MESSAGES/base.mo | Bin 25735 -> 25482 bytes archinstall/locales/fr/LC_MESSAGES/base.po | 342 +++++++-------------- archinstall/locales/it/LC_MESSAGES/base.mo | Bin 23979 -> 23841 bytes archinstall/locales/it/LC_MESSAGES/base.po | 52 +++- archinstall/locales/nl/LC_MESSAGES/base.mo | Bin 18181 -> 18002 bytes archinstall/locales/nl/LC_MESSAGES/base.po | 80 ++++- archinstall/locales/pl/LC_MESSAGES/base.mo | Bin 22542 -> 22346 bytes archinstall/locales/pl/LC_MESSAGES/base.po | 67 +++- archinstall/locales/pt/LC_MESSAGES/base.mo | Bin 16887 -> 16667 bytes archinstall/locales/pt/LC_MESSAGES/base.po | 80 ++++- archinstall/locales/pt_BR/LC_MESSAGES/base.mo | Bin 23641 -> 23362 bytes archinstall/locales/pt_BR/LC_MESSAGES/base.po | 280 ++++++++--------- archinstall/locales/ru/LC_MESSAGES/base.mo | Bin 33402 -> 33398 bytes archinstall/locales/ru/LC_MESSAGES/base.po | 4 +- archinstall/locales/sv/LC_MESSAGES/base.mo | Bin 23439 -> 23045 bytes archinstall/locales/sv/LC_MESSAGES/base.po | 81 ++++- archinstall/locales/tr/LC_MESSAGES/base.mo | Bin 24972 -> 24757 bytes archinstall/locales/tr/LC_MESSAGES/base.po | 330 ++++++++------------ archinstall/locales/ur/LC_MESSAGES/base.mo | Bin 21216 -> 20880 bytes archinstall/locales/ur/LC_MESSAGES/base.po | 80 ++++- 35 files changed, 1184 insertions(+), 804 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index b73fb48f..1a292476 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -163,7 +163,8 @@ class GlobalMenu(GeneralMenu): Selector( _('Network configuration'), ask_to_configure_network, - display_func=lambda x: self._prev_network_configuration(x), + display_func=lambda x: self._display_network_conf(x), + preview_func=self._prev_network_config, default={}) self._menu_options['timezone'] = \ Selector( @@ -226,16 +227,23 @@ class GlobalMenu(GeneralMenu): return _('Install ({} config(s) missing)').format(missing) return _('Install') - def _prev_network_configuration(self, cur_value: Union[NetworkConfiguration, List[NetworkConfiguration]]) -> str: + def _display_network_conf(self, cur_value: Union[NetworkConfiguration, List[NetworkConfiguration]]) -> str: if not cur_value: return _('Not configured, unavailable unless setup manually') else: if isinstance(cur_value, list): - ifaces = [x.iface for x in cur_value] - return f'Configured ifaces: {ifaces}' + return str(_('Configured {} interfaces')).format(len(cur_value)) else: return str(cur_value) + def _prev_network_config(self) -> Optional[str]: + selector = self._menu_options['nic'] + if selector.has_selection(): + ifaces = selector.current_selection + if isinstance(ifaces, list): + return FormattedOutput.as_table(ifaces) + return None + def _prev_harddrives(self) -> Optional[str]: selector = self._menu_options['harddrives'] if selector.has_selection(): diff --git a/archinstall/lib/menu/list_manager.py b/archinstall/lib/menu/list_manager.py index 40d01ce3..fe491caa 100644 --- a/archinstall/lib/menu/list_manager.py +++ b/archinstall/lib/menu/list_manager.py @@ -86,7 +86,7 @@ The contents in the base class of this methods serve for a very basic usage, and """ import copy from os import system -from typing import Union, Any, TYPE_CHECKING, Dict, Optional +from typing import Union, Any, TYPE_CHECKING, Dict, Optional, Tuple, List from .text_input import TextInput from .menu import Menu @@ -135,9 +135,9 @@ class ListManager: elif isinstance(default_action,(list,tuple)): self._default_action = default_action else: - self._default_action = [str(default_action),] + self._default_action = [str(default_action)] - self._header = header if header else None + self._header = header if header else '' self._cancel_action = str(_('Cancel')) self._confirm_action = str(_('Confirm and exit')) self._separator = '' @@ -155,61 +155,81 @@ class ListManager: while True: # this will return a dictionary with the key as the menu entry to be displayed # and the value is the original value from the self._data container - data_formatted = self.reformat(self._data) - options = list(data_formatted.keys()) - - if len(options) > 0: - options.append(self._separator) + options, header = self._prepare_selection(data_formatted) - if self._default_action: - options += self._default_action + menu_header = self._header - options += self._bottom_list + if header: + menu_header += header system('clear') - target = Menu( + choice = Menu( self._prompt, options, sort=False, clear_screen=False, clear_menu_on_exit=False, - header=self._header, + header=header, skip_empty_entries=True, - skip=False + skip=False, + show_search_hint=False ).run() - if not target.value or target.value in self._bottom_list: - self.action = target + if not choice.value or choice.value in self._bottom_list: + self.action = choice break - if target.value and target.value in self._default_action: - self.action = target.value + if choice.value and choice.value in self._default_action: + self.action = choice.value self.target = None self._data = self.exec_action(self._data) continue - if isinstance(self._data,dict): - data_key = data_formatted[target.value] + if isinstance(self._data, dict): + data_key = data_formatted[choice.value] key = self._data[data_key] self.target = {data_key: key} elif isinstance(self._data, list): - self.target = [d for d in self._data if d == data_formatted[target.value]][0] + self.target = [d for d in self._data if d == data_formatted[choice.value]][0] else: - self.target = self._data[data_formatted[target.value]] + self.target = self._data[data_formatted[choice.value]] # Possible enhancement. If run_actions returns false a message line indicating the failure - self.run_actions(target.value) + self.run_actions(choice.value) - if target.value == self._cancel_action: # TODO dubious + if choice.value == self._cancel_action: return self._original_data # return the original list else: return self._data - def run_actions(self,prompt_data=None): + def _prepare_selection(self, data_formatted: Dict[str, Any]) -> Tuple[List[str], str]: + # header rows are mapped to None so make sure + # to exclude those from the selectable data + options: List[str] = [key for key, val in data_formatted.items() if val is not None] + header = '' + + if len(options) > 0: + table_header = [key for key, val in data_formatted.items() if val is None] + header = '\n'.join(table_header) + + if len(options) > 0: + options.append(self._separator) + + if self._default_action: + # done only for mypy -> todo fix the self._default_action declaration + options += [action for action in self._default_action if action] + + options += self._bottom_list + return options, header + + def run_actions(self,prompt_data=''): options = self.action_list() + self._bottom_item - prompt = _("Select an action for < {} >").format(prompt_data if prompt_data else self.target) + display_value = self.selected_action_display(self.target) if self.target else prompt_data + + prompt = _("Select an action for '{}'").format(display_value) + choice = Menu( prompt, options, @@ -225,26 +245,28 @@ class ListManager: if self.action and self.action != self._cancel_action: self._data = self.exec_action(self._data) - """ - The following methods are expected to be overwritten by the user if the needs of the list are beyond the simple case - """ + def selected_action_display(self, selection: Any) -> str: + # this will return the value to be displayed in the + # "Select an action for '{}'" string + raise NotImplementedError('Please implement me in the child class') - def reformat(self, data: Any) -> Dict[str, Any]: - """ - method to get the data in a format suitable to be shown - It is executed once for run loop and processes the whole self._data structure - """ - if isinstance(data,dict): - return {f'{k}: {v}': k for k, v in data.items()} - else: - return {str(k): k for k in data} + def reformat(self, data: List[Any]) -> Dict[str, Any]: + # this should return a dictionary of display string to actual data entry + # mapping; if the value for a given display string is None it will be used + # in the header value (useful when displaying tables) + raise NotImplementedError('Please implement me in the child class') def action_list(self): """ can define alternate action list or customize the list for each item. Executed after any item is selected, contained in self.target """ - return self._base_actions + active_entry = self.target if self.target else None + + if active_entry is None: + return [self._base_actions[0]] + else: + return self._base_actions[1:] def exec_action(self, data: Any): """ diff --git a/archinstall/lib/menu/menu.py b/archinstall/lib/menu/menu.py index 3a26f6e7..80982db0 100644 --- a/archinstall/lib/menu/menu.py +++ b/archinstall/lib/menu/menu.py @@ -1,5 +1,6 @@ from dataclasses import dataclass from enum import Enum, auto +from os import system from typing import Dict, List, Union, Any, TYPE_CHECKING, Optional from archinstall.lib.menu.simple_menu import TerminalMenu @@ -57,7 +58,11 @@ class Menu(TerminalMenu): header :Union[List[str],str] = None, explode_on_interrupt :bool = False, explode_warning :str = '', - **kwargs + clear_screen: bool = True, + show_search_hint: bool = True, + cycle_cursor: bool = True, + clear_menu_on_exit: bool = True, + skip_empty_entries: bool = False ): """ Creates a new menu @@ -153,8 +158,7 @@ class Menu(TerminalMenu): if header: if not isinstance(header,(list,tuple)): header = [header] - header = '\n'.join(header) - menu_title += f'\n{header}\n' + menu_title += '\n'.join(header) action_info = '' if skip: @@ -178,10 +182,6 @@ class Menu(TerminalMenu): cursor = "> " main_menu_cursor_style = ("fg_cyan", "bold") main_menu_style = ("bg_blue", "fg_gray") - # defaults that can be changed up the stack - kwargs['clear_screen'] = kwargs.get('clear_screen',True) - kwargs['show_search_hint'] = kwargs.get('show_search_hint',True) - kwargs['cycle_cursor'] = kwargs.get('cycle_cursor',True) super().__init__( menu_entries=self._menu_options, @@ -200,7 +200,11 @@ class Menu(TerminalMenu): preview_title=preview_title, explode_on_interrupt=self._explode_on_interrupt, multi_select_select_on_accept=False, - **kwargs, + clear_screen=clear_screen, + show_search_hint=show_search_hint, + cycle_cursor=cycle_cursor, + clear_menu_on_exit=clear_menu_on_exit, + skip_empty_entries=skip_empty_entries ) def _show(self) -> MenuSelection: @@ -238,6 +242,7 @@ class Menu(TerminalMenu): return self.run() if ret.type_ is not MenuSelectionType.Selection and not self._skip: + system('clear') return self.run() return ret diff --git a/archinstall/lib/models/network_configuration.py b/archinstall/lib/models/network_configuration.py index 4f135da5..e026e97b 100644 --- a/archinstall/lib/models/network_configuration.py +++ b/archinstall/lib/models/network_configuration.py @@ -39,8 +39,22 @@ class NetworkConfiguration: else: return 'Unknown type' - # for json serialization when calling json.dumps(...) on this class - def json(self): + def as_json(self) -> Dict: + exclude_fields = ['type'] + data = {} + for k, v in self.__dict__.items(): + if k not in exclude_fields: + if isinstance(v, list) and len(v) == 0: + v = '' + elif v is None: + v = '' + + data[k] = v + + return data + + def json(self) -> Dict: + # for json serialization when calling json.dumps(...) on this class return self.__dict__ def is_iso(self) -> bool: @@ -111,19 +125,10 @@ class NetworkConfigurationHandler: else: # not recognized return None - def _parse_manual_config(self, config: Dict[str, Any]) -> Union[None, List[NetworkConfiguration]]: - manual_configs: List = config.get('config', []) - - if not manual_configs: - return None - - if not isinstance(manual_configs, list): - log(_('Manual configuration setting must be a list')) - exit(1) - + def _parse_manual_config(self, configs: List[Dict[str, Any]]) -> Optional[List[NetworkConfiguration]]: configurations = [] - for manual_config in manual_configs: + for manual_config in configs: iface = manual_config.get('iface', None) if iface is None: @@ -135,7 +140,7 @@ class NetworkConfigurationHandler: NetworkConfiguration(NicType.MANUAL, iface=iface) ) else: - ip = config.get('ip', '') + ip = manual_config.get('ip', '') if not ip: log(_('Manual nic configuration with no auto DHCP requires an IP address'), fg='red') exit(1) @@ -145,32 +150,34 @@ class NetworkConfigurationHandler: NicType.MANUAL, iface=iface, ip=ip, - gateway=config.get('gateway', ''), - dns=config.get('dns', []), + gateway=manual_config.get('gateway', ''), + dns=manual_config.get('dns', []), dhcp=False ) ) return configurations - def parse_arguments(self, config: Any): - nic_type = config.get('type', None) - - if not nic_type: - # old style definitions - network_config = self._backwards_compability_config(config) - if network_config: - return network_config - return None - + def _parse_nic_type(self, nic_type: str) -> NicType: try: - type_ = NicType(nic_type) + return NicType(nic_type) except ValueError: options = [e.value for e in NicType] log(_('Unknown nic type: {}. Possible values are {}').format(nic_type, options), fg='red') exit(1) - if type_ != NicType.MANUAL: - self._configuration = NetworkConfiguration(type_) - else: # manual configuration settings + def parse_arguments(self, config: Any): + if isinstance(config, list): # new data format self._configuration = self._parse_manual_config(config) + elif nic_type := config.get('type', None): # new data format + type_ = self._parse_nic_type(nic_type) + + if type_ != NicType.MANUAL: + self._configuration = NetworkConfiguration(type_) + else: # manual configuration settings + self._configuration = self._parse_manual_config([config]) + else: # old style definitions + network_config = self._backwards_compability_config(config) + if network_config: + return network_config + return None diff --git a/archinstall/lib/user_interaction/manage_users_conf.py b/archinstall/lib/user_interaction/manage_users_conf.py index 567a2964..33c31342 100644 --- a/archinstall/lib/user_interaction/manage_users_conf.py +++ b/archinstall/lib/user_interaction/manage_users_conf.py @@ -7,6 +7,7 @@ from .utils import get_password from ..menu import Menu from ..menu.list_manager import ListManager from ..models.users import User +from ..output import FormattedOutput if TYPE_CHECKING: _: Any @@ -18,13 +19,6 @@ class UserList(ListManager): """ def __init__(self, prompt: str, lusers: List[User]): - """ - param: prompt - type: str - param: lusers dict with the users already defined for the system - type: Dict - param: sudo. boolean to determine if we handle superusers or users. If None handles both types - """ self._actions = [ str(_('Add a user')), str(_('Change password')), @@ -34,21 +28,25 @@ class UserList(ListManager): super().__init__(prompt, lusers, self._actions, self._actions[0]) def reformat(self, data: List[User]) -> Dict[str, User]: - return {e.display(): e for e in data} + table = FormattedOutput.as_table(data) + rows = table.split('\n') - def action_list(self): - active_user = self.target if self.target else None + # these are the header rows of the table and do not map to any User 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 + display_data = {f' {rows[0]}': None, f' {rows[1]}': None} + + for row, user in zip(rows[2:], data): + row = row.replace('|', '\\|') + display_data[row] = user - if active_user is None: - return [self._actions[0]] - else: - return self._actions[1:] + return display_data + + def selected_action_display(self, user: User) -> str: + return user.username def exec_action(self, data: List[User]) -> List[User]: - if self.target: - active_user = self.target - else: - active_user = None + active_user = self.target if self.target else None if self.action == self._actions[0]: # add new_user = self._add_user() @@ -77,8 +75,7 @@ class UserList(ListManager): return False def _add_user(self) -> Optional[User]: - print(_('\nDefine a new user\n')) - prompt = str(_('Enter username (leave blank to skip): ')) + prompt = '\n\n' + str(_('Enter username (leave blank to skip): ')) while True: username = input(prompt).strip(' ') @@ -94,7 +91,9 @@ class UserList(ListManager): choice = Menu( str(_('Should "{}" be a superuser (sudo)?')).format(username), Menu.yes_no(), skip=False, - default_option=Menu.no() + default_option=Menu.no(), + clear_screen=False, + show_search_hint=False ).run() sudo = True if choice.value == Menu.yes() else False @@ -102,6 +101,5 @@ class UserList(ListManager): def ask_for_additional_users(prompt: str = '', defined_users: List[User] = []) -> List[User]: - prompt = prompt if prompt else _('Enter username (leave blank to skip): ') users = UserList(prompt, defined_users).run() return users diff --git a/archinstall/lib/user_interaction/network_conf.py b/archinstall/lib/user_interaction/network_conf.py index 5154d8b1..4f22b790 100644 --- a/archinstall/lib/user_interaction/network_conf.py +++ b/archinstall/lib/user_interaction/network_conf.py @@ -2,7 +2,7 @@ from __future__ import annotations import ipaddress import logging -from typing import Any, Optional, TYPE_CHECKING, List, Union +from typing import Any, Optional, TYPE_CHECKING, List, Union, Dict from ..menu.menu import MenuSelectionType from ..menu.text_input import TextInput @@ -10,7 +10,7 @@ from ..models.network_configuration import NetworkConfiguration, NicType from ..networking import list_interfaces from ..menu import Menu -from ..output import log +from ..output import log, FormattedOutput from ..menu.list_manager import ListManager if TYPE_CHECKING: @@ -19,55 +19,57 @@ if TYPE_CHECKING: class ManualNetworkConfig(ListManager): """ - subclass of ListManager for the managing of network configuration accounts + subclass of ListManager for the managing of network configurations """ - def __init__(self, prompt: str, ifaces: Union[None, NetworkConfiguration, List[NetworkConfiguration]]): - """ - param: prompt - type: str - param: ifaces already defined previously - type: Dict - """ + def __init__(self, prompt: str, ifaces: List[NetworkConfiguration]): + self._actions = [ + str(_('Add interface')), + str(_('Edit interface')), + str(_('Delete interface')) + ] - if ifaces is not None and isinstance(ifaces, list): - display_values = {iface.iface: iface for iface in ifaces} - else: - display_values = {} + super().__init__(prompt, ifaces, self._actions, self._actions[0]) + + def reformat(self, data: List[NetworkConfiguration]) -> Dict[str, Optional[NetworkConfiguration]]: + table = FormattedOutput.as_table(data) + rows = table.split('\n') - self._action_add = str(_('Add interface')) - self._action_edit = str(_('Edit interface')) - self._action_delete = str(_('Delete interface')) + # these are the header rows of the table and do not map to any User 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 + display_data: Dict[str, Optional[NetworkConfiguration]] = {f' {rows[0]}': None, f' {rows[1]}': None} - self._iface_actions = [self._action_edit, self._action_delete] + for row, iface in zip(rows[2:], data): + row = row.replace('|', '\\|') + display_data[row] = iface - super().__init__(prompt, display_values, self._iface_actions, self._action_add) + return display_data - def run_manual(self) -> List[NetworkConfiguration]: - ifaces = super().run() - if ifaces is not None: - return list(ifaces.values()) - return [] + def selected_action_display(self, iface: NetworkConfiguration) -> str: + return iface.iface if iface.iface else '' - def exec_action(self, data: Any): - if self.action == self._action_add: - iface_name = self._select_iface(data.keys()) + def exec_action(self, data: List[NetworkConfiguration]): + active_iface: Optional[NetworkConfiguration] = self.target if self.target else None + + if self.action == self._actions[0]: # add + iface_name = self._select_iface(data) if iface_name: iface = NetworkConfiguration(NicType.MANUAL, iface=iface_name) - data[iface_name] = self._edit_iface(iface) - elif self.target: - iface_name = list(self.target.keys())[0] - iface = data[iface_name] - - if self.action == self._action_edit: - data[iface_name] = self._edit_iface(iface) - elif self.action == self._action_delete: - del data[iface_name] + iface = self._edit_iface(iface) + data += [iface] + elif active_iface: + if self.action == self._actions[1]: # edit interface + data = [d for d in data if d.iface != active_iface.iface] + data.append(self._edit_iface(active_iface)) + elif self.action == self._actions[2]: # delete + data = [d for d in data if d != active_iface] return data - def _select_iface(self, existing_ifaces: List[str]) -> Optional[Any]: + def _select_iface(self, data: List[NetworkConfiguration]) -> Optional[Any]: all_ifaces = list_interfaces().values() + existing_ifaces = [d.iface for d in data] available = set(all_ifaces) - set(existing_ifaces) choice = Menu(str(_('Select interface to add')), list(available), skip=True).run() @@ -76,7 +78,7 @@ class ManualNetworkConfig(ListManager): return choice.value - def _edit_iface(self, edit_iface :NetworkConfiguration): + def _edit_iface(self, edit_iface: NetworkConfiguration): iface_name = edit_iface.iface modes = ['DHCP (auto detect)', 'IP (static)'] default_mode = 'DHCP (auto detect)' @@ -99,11 +101,13 @@ class ManualNetworkConfig(ListManager): gateway = None while 1: - gateway_input = TextInput(_('Enter your gateway (router) IP address or leave blank for none: '), - edit_iface.gateway).run().strip() + gateway = TextInput( + _('Enter your gateway (router) IP address or leave blank for none: '), + edit_iface.gateway + ).run().strip() try: - if len(gateway_input) > 0: - ipaddress.ip_address(gateway_input) + if len(gateway) > 0: + ipaddress.ip_address(gateway) break except ValueError: log("You need to enter a valid gateway (router) IP address.", level=logging.WARNING, fg='red') @@ -124,7 +128,9 @@ class ManualNetworkConfig(ListManager): return NetworkConfiguration(NicType.MANUAL, iface=iface_name) -def ask_to_configure_network(preset: Union[None, NetworkConfiguration, List[NetworkConfiguration]]) -> Optional[Union[List[NetworkConfiguration], NetworkConfiguration]]: +def ask_to_configure_network( + preset: Union[NetworkConfiguration, List[NetworkConfiguration]] +) -> Optional[NetworkConfiguration | List[NetworkConfiguration]]: """ Configure the network on the newly installed system """ @@ -165,7 +171,7 @@ def ask_to_configure_network(preset: Union[None, NetworkConfiguration, List[Netw elif choice.value == network_options['network_manager']: return NetworkConfiguration(NicType.NM) elif choice.value == network_options['manual']: - manual = ManualNetworkConfig('Configure interfaces', preset) - return manual.run_manual() + preset_ifaces = preset if isinstance(preset, list) else [] + return ManualNetworkConfig('Configure interfaces', preset_ifaces).run() return preset diff --git a/archinstall/lib/user_interaction/subvolume_config.py b/archinstall/lib/user_interaction/subvolume_config.py index a54ec891..e2797bba 100644 --- a/archinstall/lib/user_interaction/subvolume_config.py +++ b/archinstall/lib/user_interaction/subvolume_config.py @@ -5,6 +5,7 @@ from ..menu.menu import MenuSelectionType from ..menu.text_input import TextInput from ..menu import Menu from ..models.subvolume import Subvolume +from ... import FormattedOutput if TYPE_CHECKING: _: Any @@ -19,16 +20,23 @@ class SubvolumeList(ListManager): ] super().__init__(prompt, current_volumes, self._actions, self._actions[0]) - def reformat(self, data: List[Subvolume]) -> Dict[str, Subvolume]: - return {e.display(): e for e in data} + def reformat(self, data: List[Subvolume]) -> Dict[str, Optional[Subvolume]]: + table = FormattedOutput.as_table(data) + rows = table.split('\n') - def action_list(self): - active_user = self.target if self.target else None + # these are the header rows of the table and do not map to any User 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 + display_data: Dict[str, Optional[Subvolume]] = {f' {rows[0]}': None, f' {rows[1]}': None} - if active_user is None: - return [self._actions[0]] - else: - return self._actions[1:] + for row, subvol in zip(rows[2:], data): + row = row.replace('|', '\\|') + display_data[row] = subvol + + return display_data + + def selected_action_display(self, subvolume: Subvolume) -> str: + return subvolume.name def _prompt_options(self, editing: Optional[Subvolume] = None) -> List[str]: preset_options = [] diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index 54ddbba3..baaf622c 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -414,7 +414,7 @@ msgstr "" msgid "Delete" msgstr "" -msgid "Select an action for < {} >" +msgid "Select an action for '{}'" msgstr "" msgid "Copy to new key:" diff --git a/archinstall/locales/cs/LC_MESSAGES/base.mo b/archinstall/locales/cs/LC_MESSAGES/base.mo index 0f9ad704..6a54b145 100644 Binary files a/archinstall/locales/cs/LC_MESSAGES/base.mo and b/archinstall/locales/cs/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/cs/LC_MESSAGES/base.po b/archinstall/locales/cs/LC_MESSAGES/base.po index 51cce8ac..143e815c 100644 --- a/archinstall/locales/cs/LC_MESSAGES/base.po +++ b/archinstall/locales/cs/LC_MESSAGES/base.po @@ -423,8 +423,8 @@ msgstr "Upravit" msgid "Delete" msgstr "Smazat" -msgid "Select an action for < {} >" -msgstr "Zvolte akci pro < {} >" +msgid "Select an action for '{}'" +msgstr "Zvolte akci pro '{}'" msgid "Copy to new key:" msgstr "Zkopírovat k novému klíči:" @@ -654,7 +654,8 @@ msgstr "Základní instalace, která vám umožní si nastavit Arch Linux jakkol msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" msgstr "Nabízí výběr různých serverových balíčků k instalaci a aktivaci, např. httpd, nginx, mariadb" -msgid "Choose which servers to install, if none then a minimal installation wil be done" +#, fuzzy +msgid "Choose which servers to install, if none then a minimal installation will be done" msgstr "Vyberte servery, které mají být nainstalovány, pokud nezvolíte žádné, bude provedena jen základní instalace" msgid "Installs a minimal system as well as xorg and graphics drivers." @@ -743,3 +744,48 @@ msgstr "Volné místo" msgid "Bus-type" msgstr "Typ sběrnice" + +#, fuzzy +msgid "Either root-password or at least 1 user with sudo privileges must be specified" +msgstr "Musí být zadáno heslo správce (root) nebo musí existovat alespoň jeden superuživatel" + +#, fuzzy +msgid "Enter username (leave blank to skip): " +msgstr "Zadejte uživatelské jméno k přidání dalšího uživatele (ponechte prázdné k přeskočení): " + +msgid "The username you entered is invalid. Try again" +msgstr "" + +#, fuzzy +msgid "Should \"{}\" be a superuser (sudo)?" +msgstr "Má být {} superuživatelem (sudoer)?" + +#, fuzzy +msgid "Select which partitions to encrypt:" +msgstr "" +"{}\n" +"\n" +"Zvolte oddíl, který bude označen jako šifrovaný" + +msgid "very weak" +msgstr "" + +msgid "weak" +msgstr "" + +msgid "moderate" +msgstr "" + +msgid "strong" +msgstr "" + +#, fuzzy +msgid "Add subvolume" +msgstr " Podsvazek :{:16}" + +msgid "Edit subvolume" +msgstr "" + +#, fuzzy +msgid "Delete subvolume" +msgstr "Smazat uživatele" diff --git a/archinstall/locales/de/LC_MESSAGES/base.mo b/archinstall/locales/de/LC_MESSAGES/base.mo index 3d58d749..ca3f2971 100644 Binary files a/archinstall/locales/de/LC_MESSAGES/base.mo and b/archinstall/locales/de/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/de/LC_MESSAGES/base.po b/archinstall/locales/de/LC_MESSAGES/base.po index 51ecd8e9..ee40cea5 100644 --- a/archinstall/locales/de/LC_MESSAGES/base.po +++ b/archinstall/locales/de/LC_MESSAGES/base.po @@ -255,10 +255,12 @@ msgstr "Lokale Kodierung" msgid "Drive(s)" msgstr "Laufwerke" -msgid "Select disk layout" -msgstr "Laufwerke-layout auswählen" +#, fuzzy +msgid "Disk layout" +msgstr "Laufwerke-layout speichern" -msgid "Set encryption password" +#, fuzzy +msgid "Encryption password" msgstr "Verschlüsselungspasswort angeben" msgid "Swap" @@ -267,7 +269,8 @@ msgstr "Swap" msgid "Bootloader" msgstr "Bootloader" -msgid "root password" +#, fuzzy +msgid "Root password" msgstr "Root Passwort" msgid "Superuser account" @@ -425,8 +428,8 @@ msgstr "Bearbeiten" msgid "Delete" msgstr "Löschen" -msgid "Select an action for < {} >" -msgstr "Wählen sie eine Aktion aus für < {} >" +msgid "Select an action for '{}'" +msgstr "Wählen sie eine Aktion aus für '{}'" msgid "Copy to new key:" msgstr "Kopieren nach neuem Schlüssel:" @@ -659,7 +662,8 @@ msgstr "Eine sehr minimale Installation welche es erlaubt Arch Linux weitgehend msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" msgstr "Auswahl von Serverpaketen welche installiert werden sollen, z.B. httpd, nginx, mariadb" -msgid "Choose which servers to install, if none then a minimal installation wil be done" +#, fuzzy +msgid "Choose which servers to install, if none then a minimal installation will be done" msgstr "Wählen sie die gewünschten Server aus welche installiert werden sollen" msgid "Installs a minimal system as well as xorg and graphics drivers." @@ -741,6 +745,69 @@ msgstr "" "\n" "Bitte wählen sie welche Partition formatiert werden soll" +msgid "Use HSM to unlock encrypted drive" +msgstr "" + +msgid "Device" +msgstr "" + +msgid "Size" +msgstr "" + +msgid "Free space" +msgstr "" + +msgid "Bus-type" +msgstr "" + +#, fuzzy +msgid "Either root-password or at least 1 user with sudo privileges must be specified" +msgstr "Entweder root Passwort oder wenigstens 1 super-user muss konfiguriert sein" + +#, fuzzy +msgid "Enter username (leave blank to skip): " +msgstr "Geben sie einen weiteren Benutzernamen an der angelegt werden soll (leer lassen um zu Überspringen): " + +msgid "The username you entered is invalid. Try again" +msgstr "" + +#, fuzzy +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 "very weak" +msgstr "" + +msgid "weak" +msgstr "" + +msgid "moderate" +msgstr "" + +msgid "strong" +msgstr "" + +#, fuzzy +msgid "Add subvolume" +msgstr " Subvolume :{:16}" + +msgid "Edit subvolume" +msgstr "" + +#, fuzzy +msgid "Delete subvolume" +msgstr "Benutzerkonto löschen" + +#~ msgid "Select disk layout" +#~ msgstr "Laufwerke-layout auswählen" + #~ msgid "Add :" #~ msgstr "Hinzufügen :" diff --git a/archinstall/locales/en/LC_MESSAGES/base.po b/archinstall/locales/en/LC_MESSAGES/base.po index 531e20a9..63e9f804 100644 --- a/archinstall/locales/en/LC_MESSAGES/base.po +++ b/archinstall/locales/en/LC_MESSAGES/base.po @@ -232,10 +232,10 @@ msgstr "" msgid "Drive(s)" msgstr "" -msgid "Select disk layout" +msgid "Disk layout" msgstr "" -msgid "Set encryption password" +msgid "Encryption password" msgstr "" msgid "Swap" @@ -244,7 +244,7 @@ msgstr "" msgid "Bootloader" msgstr "" -msgid "root password" +msgid "Root password" msgstr "" msgid "Superuser account" @@ -392,7 +392,7 @@ msgstr "" msgid "Delete" msgstr "" -msgid "Select an action for < {} >" +msgid "Select an action for '{}'" msgstr "" msgid "Copy to new key:" @@ -617,7 +617,7 @@ msgstr "" msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" msgstr "" -msgid "Choose which servers to install, if none then a minimal installation wil be done" +msgid "Choose which servers to install, if none then a minimal installation will be done" msgstr "" msgid "Installs a minimal system as well as xorg and graphics drivers." @@ -689,3 +689,54 @@ msgstr "" msgid "Select which partitions to mark for formatting:" msgstr "" + +msgid "Use HSM to unlock encrypted drive" +msgstr "" + +msgid "Device" +msgstr "" + +msgid "Size" +msgstr "" + +msgid "Free space" +msgstr "" + +msgid "Bus-type" +msgstr "" + +msgid "Either root-password or at least 1 user with sudo privileges must be specified" +msgstr "" + +msgid "Enter username (leave blank to skip): " +msgstr "" + +msgid "The username you entered is invalid. Try again" +msgstr "" + +msgid "Should \"{}\" be a superuser (sudo)?" +msgstr "" + +msgid "Select which partitions to encrypt:" +msgstr "" + +msgid "very weak" +msgstr "" + +msgid "weak" +msgstr "" + +msgid "moderate" +msgstr "" + +msgid "strong" +msgstr "" + +msgid "Add subvolume" +msgstr "" + +msgid "Edit subvolume" +msgstr "" + +msgid "Delete subvolume" +msgstr "" diff --git a/archinstall/locales/es/LC_MESSAGES/base.mo b/archinstall/locales/es/LC_MESSAGES/base.mo index d5efa958..6006c274 100644 Binary files a/archinstall/locales/es/LC_MESSAGES/base.mo and b/archinstall/locales/es/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/es/LC_MESSAGES/base.po b/archinstall/locales/es/LC_MESSAGES/base.po index 0373a40d..6f18c5f8 100644 --- a/archinstall/locales/es/LC_MESSAGES/base.po +++ b/archinstall/locales/es/LC_MESSAGES/base.po @@ -423,8 +423,8 @@ msgstr "Editar" msgid "Delete" msgstr "Eliminar" -msgid "Select an action for < {} >" -msgstr "Seleccione una acción para < {} >" +msgid "Select an action for '{}'" +msgstr "Seleccione una acción para '{}'" msgid "Copy to new key:" msgstr "Copiar a nueva clave:" @@ -657,7 +657,8 @@ msgstr "Una instalación muy básica que te permite personalizar Arch Linux como msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" msgstr "Proporciona una selección de varios paquetes de servidor para instalar y habilitar, p.e. httpd, nginx, mariadb" -msgid "Choose which servers to install, if none then a minimal installation wil be done" +#, fuzzy +msgid "Choose which servers to install, if none then a minimal installation will be done" msgstr "Elija qué servidores instalar, si no hay ninguno, se realizará una instalación mínima" msgid "Installs a minimal system as well as xorg and graphics drivers." @@ -764,6 +765,29 @@ msgstr "¿Debe \"{}\" ser un superusuario (sudo)?" msgid "Select which partitions to encrypt:" msgstr "Seleccione qué particiones cifrar:" +msgid "very weak" +msgstr "" + +msgid "weak" +msgstr "" + +msgid "moderate" +msgstr "" + +msgid "strong" +msgstr "" + +#, fuzzy +msgid "Add subvolume" +msgstr " Subvolumen :{:16}" + +msgid "Edit subvolume" +msgstr "" + +#, fuzzy +msgid "Delete subvolume" +msgstr "Eliminar usuario" + #~ msgid "Select disk layout" #~ msgstr "Seleccione el diseño del disco" diff --git a/archinstall/locales/fr/LC_MESSAGES/base.mo b/archinstall/locales/fr/LC_MESSAGES/base.mo index 364b8964..b8f19aa7 100644 Binary files a/archinstall/locales/fr/LC_MESSAGES/base.mo and b/archinstall/locales/fr/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/fr/LC_MESSAGES/base.po b/archinstall/locales/fr/LC_MESSAGES/base.po index 0e783a58..359dbdd7 100644 --- a/archinstall/locales/fr/LC_MESSAGES/base.po +++ b/archinstall/locales/fr/LC_MESSAGES/base.po @@ -14,12 +14,8 @@ msgstr "" msgid "[!] A log file has been created here: {} {}" msgstr "[!] Un fichier journal a été créé ici : {} {}" -msgid "" -" Please submit this issue (and file) to https://github.com/archlinux/" -"archinstall/issues" -msgstr "" -" Veuillez soumettre ce problème (et le fichier) à https://github.com/" -"archlinux/archinstall/issues" +msgid " Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues" +msgstr " Veuillez soumettre ce problème (et le fichier) à https://github.com/archlinux/archinstall/issues" msgid "Do you really want to abort?" msgstr "Voulez-vous vraiment abandonner ?" @@ -34,13 +30,10 @@ msgid "Desired hostname for the installation: " msgstr "Nom d'hôte souhaité pour l'installation : " msgid "Username for required superuser with sudo privileges: " -msgstr "" -"Nom d'utilisateur pour le superutilisateur requis avec les privilèges sudo : " +msgstr "Nom d'utilisateur pour le superutilisateur requis avec les privilèges sudo : " msgid "Any additional users to install (leave blank for no users): " -msgstr "" -"Utilisateur supplémentaire à installer (laisser vide pour aucun " -"utilisateur) : " +msgstr "Utilisateur supplémentaire à installer (laisser vide pour aucun utilisateur) : " msgid "Should this user be a superuser (sudoer)?" msgstr "Cet utilisateur doit-il être un superutilisateur (sudoer) ?" @@ -49,9 +42,7 @@ msgid "Select a timezone" msgstr "Sélectionner un fuseau horaire" msgid "Would you like to use GRUB as a bootloader instead of systemd-boot?" -msgstr "" -"Souhaitez-vous utiliser GRUB comme chargeur de démarrage au lieu de systemd-" -"boot ?" +msgstr "Souhaitez-vous utiliser GRUB comme chargeur de démarrage au lieu de systemd-boot ?" msgid "Choose a bootloader" msgstr "Choisir un chargeur de démarrage" @@ -59,60 +50,38 @@ msgstr "Choisir un chargeur de démarrage" msgid "Choose an audio server" msgstr "Choisir un serveur audio" -msgid "" -"Only packages such as base, base-devel, linux, linux-firmware, efibootmgr " -"and optional profile packages are installed." -msgstr "" -"Seuls les packages tels que base, base-devel, linux, linux-firmware, " -"efibootmgr et les packages de profil optionnels sont installés." +msgid "Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed." +msgstr "Seuls les packages tels que base, base-devel, linux, linux-firmware, efibootmgr et les packages de profil optionnels sont installés." -msgid "" -"If you desire a web browser, such as firefox or chromium, you may specify it " -"in the following prompt." -msgstr "" -"Si vous désirez un navigateur Web, tel que firefox ou chrome, vous pouvez le " -"spécifier dans l'invite suivante." +msgid "If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt." +msgstr "Si vous désirez un navigateur Web, tel que firefox ou chrome, vous pouvez le spécifier dans l'invite suivante." -msgid "" -"Write additional packages to install (space separated, leave blank to skip): " -msgstr "" -"Écrire des packages supplémentaires à installer (espaces séparés, laisser " -"vide pour ignorer) : " +msgid "Write additional packages to install (space separated, leave blank to skip): " +msgstr "Écrire des packages supplémentaires à installer (espaces séparés, laisser vide pour ignorer) : " msgid "Copy ISO network configuration to installation" msgstr "Copier la configuration réseau ISO dans l'installation" -msgid "" -"Use NetworkManager (necessary to configure internet graphically in GNOME and " -"KDE)" -msgstr "" -"Utiliser NetworkManager (nécessaire pour configurer graphiquement Internet " -"dans GNOME et KDE)" +msgid "Use NetworkManager (necessary to configure internet graphically in GNOME and KDE)" +msgstr "Utiliser NetworkManager (nécessaire pour configurer graphiquement Internet dans GNOME et KDE)" msgid "Select one network interface to configure" msgstr "Sélectionner une interface réseau à configurer" -msgid "" -"Select which mode to configure for \"{}\" or skip to use default mode \"{}\"" -msgstr "" -"Sélectionner le mode à configurer pour \"{}\" ou ignorer pour utiliser le " -"mode par défaut \"{}\"" +msgid "Select which mode to configure for \"{}\" or skip to use default mode \"{}\"" +msgstr "Sélectionner le mode à configurer pour \"{}\" ou ignorer pour utiliser le mode par défaut \"{}\"" msgid "Enter the IP and subnet for {} (example: 192.168.0.5/24): " msgstr "Entrer l'IP et le sous-réseau pour {} (exemple : 192.168.0.5/24) : " msgid "Enter your gateway (router) IP address or leave blank for none: " -msgstr "" -"Entrer l'adresse IP de votre passerelle (routeur) ou laisser vide pour " -"aucune : " +msgstr "Entrer l'adresse IP de votre passerelle (routeur) ou laisser vide pour aucune : " msgid "Enter your DNS servers (space separated, blank for none): " msgstr "Entrer vos serveurs DNS (séparés par des espaces, vide pour aucun) : " msgid "Select which filesystem your main partition should use" -msgstr "" -"Sélectionner le système de fichiers que votre partition principale doit " -"utiliser" +msgstr "Sélectionner le système de fichiers que votre partition principale doit utiliser" msgid "Current partition layout" msgstr "Disposition actuelle des partitions" @@ -128,20 +97,13 @@ msgid "Enter a desired filesystem type for the partition" msgstr "Entrer un type de système de fichiers souhaité pour la partition" msgid "Enter the start sector (percentage or block number, default: {}): " -msgstr "" -"Entrer le secteur de début (pourcentage ou numéro de bloc, par défaut : " -"{}) : " +msgstr "Entrer le secteur de début (pourcentage ou numéro de bloc, par défaut : {}) : " -msgid "" -"Enter the end sector of the partition (percentage or block number, ex: {}): " -msgstr "" -"Entrer le secteur de fin de la partition (pourcentage ou numéro de bloc, " -"ex : {}) : " +msgid "Enter the end sector of the partition (percentage or block number, ex: {}): " +msgstr "Entrer le secteur de fin de la partition (pourcentage ou numéro de bloc, ex : {}) : " msgid "{} contains queued partitions, this will remove those, are you sure?" -msgstr "" -"{} contient des partitions en file d'attente, cela les supprimera, êtes-vous " -"sûr ?" +msgstr "{} contient des partitions en file d'attente, cela les supprimera, êtes-vous sûr ?" msgid "" "{}\n" @@ -161,17 +123,11 @@ msgstr "" "\n" "Sélectionner par index où et quelle partition montée" -msgid "" -" * Partition mount-points are relative to inside the installation, the boot " -"would be /boot as an example." -msgstr "" -" * Les points de montage de la partition sont relatifs à l'intérieur de " -"l'installation, le démarrage serait /boot par exemple." +msgid " * Partition mount-points are relative to inside the installation, the boot would be /boot as an example." +msgstr " * Les points de montage de la partition sont relatifs à l'intérieur de l'installation, le démarrage serait /boot par exemple." msgid "Select where to mount partition (leave blank to remove mountpoint): " -msgstr "" -"Sélectionner où monter la partition (laisser vide pour supprimer le point de " -"montage) : " +msgstr "Sélectionner où monter la partition (laisser vide pour supprimer le point de montage) : " msgid "" "{}\n" @@ -216,58 +172,34 @@ msgid "Archinstall language" msgstr "Langue d'Archinstall" msgid "Wipe all selected drives and use a best-effort default partition layout" -msgstr "" -"Effacer tous les lecteurs sélectionnés et utiliser une disposition de " -"partition par défaut optimale" +msgstr "Effacer tous les lecteurs sélectionnés et utiliser une disposition de partition par défaut optimale" -msgid "" -"Select what to do with each individual drive (followed by partition usage)" -msgstr "" -"Sélectionner ce qu'il faut faire avec chaque lecteur individuel (suivi de " -"l'utilisation de la partition)" +msgid "Select what to do with each individual drive (followed by partition usage)" +msgstr "Sélectionner ce qu'il faut faire avec chaque lecteur individuel (suivi de l'utilisation de la partition)" msgid "Select what you wish to do with the selected block devices" -msgstr "" -"Sélectionner ce que vous souhaitez faire avec les périphériques de bloc " -"sélectionnés" +msgstr "Sélectionner ce que vous souhaitez faire avec les périphériques de bloc sélectionnés" -msgid "" -"This is a list of pre-programmed profiles, they might make it easier to " -"install things like desktop environments" -msgstr "" -"Ceci est une liste de profils préprogrammés, ils pourraient faciliter " -"l'installation d'outils comme les environnements de bureau" +msgid "This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments" +msgstr "Ceci est une liste de profils préprogrammés, ils pourraient faciliter l'installation d'outils comme les environnements de bureau" msgid "Select keyboard layout" msgstr "Sélectionner la disposition du clavier" msgid "Select one of the regions to download packages from" -msgstr "" -"Sélectionner l'une des régions depuis lesquelles télécharger les packages" +msgstr "Sélectionner l'une des régions depuis lesquelles télécharger les packages" msgid "Select one or more hard drives to use and configure" msgstr "Sélectionner un ou plusieurs disques durs à utiliser et à configurer" -msgid "" -"For the best compatibility with your AMD hardware, you may want to use " -"either the all open-source or AMD / ATI options." -msgstr "" -"Pour une meilleure compatibilité avec votre matériel AMD, vous pouvez " -"utiliser les options entièrement open source ou AMD / ATI." +msgid "For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options." +msgstr "Pour une meilleure compatibilité avec votre matériel AMD, vous pouvez utiliser les options entièrement open source ou AMD / ATI." -msgid "" -"For the best compatibility with your Intel hardware, you may want to use " -"either the all open-source or Intel options.\n" -msgstr "" -"Pour une compatibilité optimale avec votre matériel Intel, vous pouvez " -"utiliser les options entièrement open source ou Intel.\n" +msgid "For the best compatibility with your Intel hardware, you may want to use either the all open-source or Intel options.\n" +msgstr "Pour une compatibilité optimale avec votre matériel Intel, vous pouvez utiliser les options entièrement open source ou Intel.\n" -msgid "" -"For the best compatibility with your Nvidia hardware, you may want to use " -"the Nvidia proprietary driver.\n" -msgstr "" -"Pour une meilleure compatibilité avec votre matériel Nvidia, vous pouvez " -"utiliser le pilote propriétaire Nvidia.\n" +msgid "For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n" +msgstr "Pour une meilleure compatibilité avec votre matériel Nvidia, vous pouvez utiliser le pilote propriétaire Nvidia.\n" msgid "" "\n" @@ -276,8 +208,7 @@ msgid "" msgstr "" "\n" "\n" -"Sélectionner un pilote graphique ou laisser vide pour installer tous les " -"pilotes open-source" +"Sélectionner un pilote graphique ou laisser vide pour installer tous les pilotes open-source" msgid "All open-source (default)" msgstr "Tout open-source (par défaut)" @@ -300,12 +231,8 @@ msgstr "Sélectionner une ou plusieurs des options ci-dessous : " msgid "Adding partition...." msgstr "Ajout de la partition...." -msgid "" -"You need to enter a valid fs-type in order to continue. See `man parted` for " -"valid fs-type's." -msgstr "" -"Vous devez entrer un type de fs valide pour continuer. Voir `man parted` " -"pour les types de fs valides." +msgid "You need to enter a valid fs-type in order to continue. See `man parted` for valid fs-type's." +msgstr "Vous devez entrer un type de fs valide pour continuer. Voir `man parted` pour les types de fs valides." msgid "Error: Listing profiles on URL \"{}\" resulted in:" msgstr "Erreur : la liste des profils sur l'URL \"{}\" a entraîné :" @@ -378,8 +305,7 @@ msgid "" msgstr "" "Vous avez décidé d'ignorer la sélection du disque dur\n" "et vous utiliserez la configuration de disque montée sur {} (expérimental)\n" -"ATTENTION : Archinstall ne vérifiera pas l'adéquation de cette " -"configuration\n" +"ATTENTION : Archinstall ne vérifiera pas l'adéquation de cette configuration\n" "Souhaitez-vous continuer ?" msgid "Re-using partition instance: {}" @@ -404,8 +330,7 @@ msgid "Mark/Unmark a partition as encrypted" msgstr "Marquer/Démarquer une partition comme chiffrée" msgid "Mark/Unmark a partition as bootable (automatic for /boot)" -msgstr "" -"Marquer/Démarquer une partition comme amorçable (automatique pour /boot)" +msgstr "Marquer/Démarquer une partition comme amorçable (automatique pour /boot)" msgid "Set desired filesystem for a partition" msgstr "Définir le système de fichiers souhaité pour une partition" @@ -445,9 +370,7 @@ msgid "Enter a encryption password for {}" msgstr "Entrer un mot de passe de cryptage pour {}" msgid "Enter disk encryption password (leave blank for no encryption): " -msgstr "" -"Entrer le mot de passe de chiffrement du disque (laisser vide pour aucun " -"chiffrement) : " +msgstr "Entrer le mot de passe de chiffrement du disque (laisser vide pour aucun chiffrement) : " msgid "Create a required super-user with sudo privileges: " msgstr "Créer un super-utilisateur requis avec les privilèges sudo : " @@ -458,44 +381,31 @@ msgstr "Entrer le mot de passe root (laisser vide pour désactiver root) : " msgid "Password for user \"{}\": " msgstr "Mot de passe pour l'utilisateur \"{}\" : " -msgid "" -"Verifying that additional packages exist (this might take a few seconds)" -msgstr "" -"Vérifier que des packages supplémentaires existent (cela peut prendre " -"quelques secondes)" +msgid "Verifying that additional packages exist (this might take a few seconds)" +msgstr "Vérifier que des packages supplémentaires existent (cela peut prendre quelques secondes)" -msgid "" -"Would you like to use automatic time synchronization (NTP) with the default " -"time servers?\n" -msgstr "" -"Souhaitez-vous utiliser la synchronisation automatique de l'heure (NTP) avec " -"les serveurs de temps par défaut ?\n" +msgid "Would you like to use automatic time synchronization (NTP) with the default time servers?\n" +msgstr "Souhaitez-vous utiliser la synchronisation automatique de l'heure (NTP) avec les serveurs de temps par défaut ?\n" msgid "" -"Hardware time and other post-configuration steps might be required in order " -"for NTP to work.\n" +"Hardware time and other post-configuration steps might be required in order for NTP to work.\n" "For more information, please check the Arch wiki" msgstr "" -"Le temps matériel et d'autres étapes de post-configuration peuvent être " -"nécessaires pour que NTP fonctionne.\n" +"Le temps matériel et d'autres étapes de post-configuration peuvent être nécessaires pour que NTP fonctionne.\n" "Pour plus d'informations, veuillez consulter le wiki Arch" msgid "Enter a username to create an additional user (leave blank to skip): " -msgstr "" -"Entrer un nom d'utilisateur pour créer un utilisateur supplémentaire " -"(laisser vide pour ignorer) : " +msgstr "Entrer un nom d'utilisateur pour créer un utilisateur supplémentaire (laisser vide pour ignorer) : " msgid "Use ESC to skip\n" msgstr "Utiliser ESC pour ignorer\n" msgid "" "\n" -" Choose an object from the list, and select one of the available actions for " -"it to execute" +" Choose an object from the list, and select one of the available actions for it to execute" msgstr "" "\n" -"Choisir un objet dans la liste et sélectionner l'une des actions disponibles " -"pour qu'il s'exécute" +"Choisir un objet dans la liste et sélectionner l'une des actions disponibles pour qu'il s'exécute" msgid "Cancel" msgstr "Annuler" @@ -515,8 +425,8 @@ msgstr "Modifier" msgid "Delete" msgstr "Supprimer" -msgid "Select an action for < {} >" -msgstr "Sélectionner une action pour < {} >" +msgid "Select an action for '{}'" +msgstr "Sélectionner une action pour '{}'" msgid "Copy to new key:" msgstr "Copier vers une nouvelle clé :" @@ -531,18 +441,11 @@ msgstr "" "\n" "Voici la configuration choisie :" -msgid "" -"Pacman is already running, waiting maximum 10 minutes for it to terminate." -msgstr "" -"Pacman est déjà en cours d'exécution, attendez au maximum 10 minutes pour " -"qu'il se termine." +msgid "Pacman is already running, waiting maximum 10 minutes for it to terminate." +msgstr "Pacman est déjà en cours d'exécution, attendez au maximum 10 minutes pour qu'il se termine." -msgid "" -"Pre-existing pacman lock never exited. Please clean up any existing pacman " -"sessions before using archinstall." -msgstr "" -"Le verrou pacman préexistant n'a jamais été fermé. Veuillez nettoyer toutes " -"les sessions pacman existantes avant d'utiliser archinstall." +msgid "Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall." +msgstr "Le verrou pacman préexistant n'a jamais été fermé. Veuillez nettoyer toutes les sessions pacman existantes avant d'utiliser archinstall." msgid "Choose which optional additional repositories to enable" msgstr "Choisir les référentiels supplémentaires en option à activer" @@ -679,16 +582,13 @@ msgid "Select the desired subvolume options " msgstr "Sélectionner les options de sous-volume souhaitées " msgid "Define users with sudo privilege, by username: " -msgstr "" -"Définir les utilisateurs avec le privilège sudo, par nom d'utilisateur : " +msgstr "Définir les utilisateurs avec le privilège sudo, par nom d'utilisateur : " msgid "[!] A log file has been created here: {}" msgstr "[!] Un fichier journal a été créé ici : {}" msgid "Would you like to use BTRFS subvolumes with a default structure?" -msgstr "" -"Souhaitez-vous utiliser des sous-volumes BTRFS avec une structure par " -"défaut ?" +msgstr "Souhaitez-vous utiliser des sous-volumes BTRFS avec une structure par défaut ?" msgid "Would you like to use BTRFS compression?" msgstr "Souhaitez-vous utiliser la compression BTRFS ?" @@ -696,12 +596,8 @@ msgstr "Souhaitez-vous utiliser la compression BTRFS ?" msgid "Would you like to create a separate partition for /home?" msgstr "Souhaitez-vous créer une partition séparée pour /home ?" -msgid "" -"The selected drives do not have the minimum capacity required for an " -"automatic suggestion\n" -msgstr "" -"Les disques sélectionnés n'ont pas la capacité minimale requise pour une " -"suggestion automatique\n" +msgid "The selected drives do not have the minimum capacity required for an automatic suggestion\n" +msgstr "Les disques sélectionnés n'ont pas la capacité minimale requise pour une suggestion automatique\n" msgid "Minimum capacity for /home partition: {}GB\n" msgstr "Capacité minimale pour la partition /home : {} Go\n" @@ -728,9 +624,7 @@ msgid "No iface specified for manual configuration" msgstr "Aucun iface spécifié pour la configuration manuelle" msgid "Manual nic configuration with no auto DHCP requires an IP address" -msgstr "" -"La configuration manuelle de la carte réseau sans DHCP automatique nécessite " -"une adresse IP" +msgstr "La configuration manuelle de la carte réseau sans DHCP automatique nécessite une adresse IP" msgid "Add interface" msgstr "Ajouter une interface" @@ -750,42 +644,24 @@ msgstr "Configuration manuelle" msgid "Mark/Unmark a partition as compressed (btrfs only)" msgstr "Marquer/Démarquer une partition comme compressée (btrfs uniquement)" -msgid "" -"The password you are using seems to be weak, are you sure you want to use it?" -msgstr "" -"Le mot de passe que vous utilisez semble faible, êtes-vous sûr de vouloir " -"l'utiliser ?" +msgid "The password you are using seems to be weak, are you sure you want to use it?" +msgstr "Le mot de passe que vous utilisez semble faible, êtes-vous sûr de vouloir l'utiliser ?" -msgid "" -"Provides a selection of desktop environments and tiling window managers, e." -"g. gnome, kde, sway" -msgstr "" -"Fournit une sélection d'environnements de bureau et de gestionnaires de " -"fenêtres en mosaïque, par ex. gnome, kde, sway" +msgid "Provides a selection of desktop environments and tiling window managers, e.g. gnome, kde, sway" +msgstr "Fournit une sélection d'environnements de bureau et de gestionnaires de fenêtres en mosaïque, par ex. gnome, kde, sway" msgid "Select your desired desktop environment" msgstr "Sélectionner l'environnement de bureau souhaité" -msgid "" -"A very basic installation that allows you to customize Arch Linux as you see " -"fit." -msgstr "" -"Une installation très basique qui vous permet de personnaliser Arch Linux " -"comme bon vous semble." +msgid "A very basic installation that allows you to customize Arch Linux as you see fit." +msgstr "Une installation très basique qui vous permet de personnaliser Arch Linux comme bon vous semble." -msgid "" -"Provides a selection of various server packages to install and enable, e.g. " -"httpd, nginx, mariadb" -msgstr "" -"Fournit une sélection de divers paquets de serveur à installer et à activer, " -"par ex. httpd, nginx, mariadb" +msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" +msgstr "Fournit une sélection de divers paquets de serveur à installer et à activer, par ex. httpd, nginx, mariadb" -msgid "" -"Choose which servers to install, if none then a minimal installation wil be " -"done" -msgstr "" -"Choisir les serveurs à installer, s'il n'y en a pas, une installation " -"minimale sera effectuée" +#, fuzzy +msgid "Choose which servers to install, if none then a minimal installation will be done" +msgstr "Choisir les serveurs à installer, s'il n'y en a pas, une installation minimale sera effectuée" msgid "Installs a minimal system as well as xorg and graphics drivers." msgstr "Installe un système minimal ainsi que les pilotes graphiques et xorg." @@ -793,12 +669,8 @@ msgstr "Installe un système minimal ainsi que les pilotes graphiques et xorg." msgid "Press Enter to continue." msgstr "Appuyer sur Entrée pour continuer." -msgid "" -"Would you like to chroot into the newly created installation and perform " -"post-installation configuration?" -msgstr "" -"Souhaitez-vous chrooter dans l'installation nouvellement créée et effectuer " -"la configuration post-installation ?" +msgid "Would you like to chroot into the newly created installation and perform post-installation configuration?" +msgstr "Souhaitez-vous chrooter dans l'installation nouvellement créée et effectuer la configuration post-installation ?" msgid "Are you sure you want to reset this setting?" msgstr "Voulez-vous vraiment réinitialiser ce paramètre ?" @@ -807,16 +679,10 @@ msgid "Select one or more hard drives to use and configure\n" msgstr "Sélectionner un ou plusieurs disques durs à utiliser et à configurer\n" msgid "Any modifications to the existing setting will reset the disk layout!" -msgstr "" -"Toute modification du paramètre existant réinitialisera la disposition du " -"disque !" +msgstr "Toute modification du paramètre existant réinitialisera la disposition du disque !" -msgid "" -"If you reset the harddrive selection this will also reset the current disk " -"layout. Are you sure?" -msgstr "" -"Si vous réinitialisez la sélection du disque dur, cela réinitialisera " -"également la disposition actuelle du disque. Êtes-vous sûr ?" +msgid "If you reset the harddrive selection this will also reset the current disk layout. Are you sure?" +msgstr "Si vous réinitialisez la sélection du disque dur, cela réinitialisera également la disposition actuelle du disque. Êtes-vous sûr ?" msgid "Save and exit" msgstr "Sauvegarder et quitter" @@ -826,8 +692,7 @@ msgid "" "contains queued partitions, this will remove those, are you sure?" msgstr "" "{}\n" -"contient des partitions en file d'attente, cela les supprimera, êtes-vous " -"sûr ?" +"contient des partitions en file d'attente, cela les supprimera, êtes-vous sûr ?" msgid "No audio server" msgstr "Pas de serveur audio" @@ -863,13 +728,8 @@ msgstr "Ajouter: " msgid "Value: " msgstr "Valeur: " -msgid "" -"You can skip selecting a drive and partitioning and use whatever drive-setup " -"is mounted at /mnt (experimental)" -msgstr "" -"Vous pouvez ignorer la sélection d'un lecteur et le partitionnement et " -"utiliser n'importe quelle configuration de lecteur montée sur /mnt " -"(expérimental)" +msgid "You can skip selecting a drive and partitioning and use whatever drive-setup is mounted at /mnt (experimental)" +msgstr "Vous pouvez ignorer la sélection d'un lecteur et le partitionnement et utiliser n'importe quelle configuration de lecteur montée sur /mnt (expérimental)" msgid "Select one of the disks or skip and use /mnt as default" msgstr "Sélectionner l'un des disques ou ignorer et utiliser /mnt par défaut" @@ -892,12 +752,8 @@ msgstr "Espace libre" msgid "Bus-type" msgstr "Type de bus" -msgid "" -"Either root-password or at least 1 user with sudo privileges must be " -"specified" -msgstr "" -"Le mot de passe root ou au moins 1 utilisateur avec des privilèges sudo doit " -"être spécifié" +msgid "Either root-password or at least 1 user with sudo privileges must be specified" +msgstr "Le mot de passe root ou au moins 1 utilisateur avec des privilèges sudo doit être spécifié" msgid "Enter username (leave blank to skip): " msgstr "Entrer le nom d'utilisateur (laisser vide pour passer) :" @@ -908,6 +764,36 @@ msgstr "Le nom d'utilisateur que vous avez saisi n'est pas valide. Réessayer" 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 "very weak" +msgstr "" + +msgid "weak" +msgstr "" + +msgid "moderate" +msgstr "" + +msgid "strong" +msgstr "" + +#, fuzzy +msgid "Add subvolume" +msgstr " Sous-volume : {:16}" + +msgid "Edit subvolume" +msgstr "" + +#, fuzzy +msgid "Delete subvolume" +msgstr "Supprimer l'utilisateur" + #~ msgid "Select disk layout" #~ msgstr "Sélectionner la disposition du disque" diff --git a/archinstall/locales/it/LC_MESSAGES/base.mo b/archinstall/locales/it/LC_MESSAGES/base.mo index 34a6c898..c6d984c3 100644 Binary files a/archinstall/locales/it/LC_MESSAGES/base.mo and b/archinstall/locales/it/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/it/LC_MESSAGES/base.po b/archinstall/locales/it/LC_MESSAGES/base.po index 36e8d5b4..529fb47b 100644 --- a/archinstall/locales/it/LC_MESSAGES/base.po +++ b/archinstall/locales/it/LC_MESSAGES/base.po @@ -425,8 +425,8 @@ msgstr "Modifica" msgid "Delete" msgstr "Elimina" -msgid "Select an action for < {} >" -msgstr "Seleziona un'azione per < {} >" +msgid "Select an action for '{}'" +msgstr "Seleziona un'azione per '{}'" msgid "Copy to new key:" msgstr "Copia su nuova chiave:" @@ -659,7 +659,8 @@ msgstr "Un'installazione molto semplice che ti consente di personalizzare Arch L msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" msgstr "Fornisce una selezione di vari pacchetti server da installare e abilitare, per esempio httpd, nginx, mariadb" -msgid "Choose which servers to install, if none then a minimal installation wil be done" +#, fuzzy +msgid "Choose which servers to install, if none then a minimal installation will be done" msgstr "Scegli quali server installare, se nessuno verrà eseguita un'installazione minima" msgid "Installs a minimal system as well as xorg and graphics drivers." @@ -750,3 +751,48 @@ msgstr "Spazio libero" msgid "Bus-type" msgstr "Tipo di bus" + +#, fuzzy +msgid "Either root-password or at least 1 user with sudo privileges must be specified" +msgstr "È necessario specificare la password di root o almeno 1 superuser" + +#, fuzzy +msgid "Enter username (leave blank to skip): " +msgstr "Inserisci un nome utente per creare un utente aggiuntivo (lascia vuoto per saltare): " + +msgid "The username you entered is invalid. Try again" +msgstr "" + +#, fuzzy +msgid "Should \"{}\" be a superuser (sudo)?" +msgstr "{} dovrebbe essere un superutente? (sudoer)" + +#, fuzzy +msgid "Select which partitions to encrypt:" +msgstr "" +"{}\n" +"\n" +"Seleziona quale partizione contrassegnare come crittografata" + +msgid "very weak" +msgstr "" + +msgid "weak" +msgstr "" + +msgid "moderate" +msgstr "" + +msgid "strong" +msgstr "" + +#, fuzzy +msgid "Add subvolume" +msgstr " Sottovolume :{:16}" + +msgid "Edit subvolume" +msgstr "" + +#, fuzzy +msgid "Delete subvolume" +msgstr "Elimina utente" diff --git a/archinstall/locales/nl/LC_MESSAGES/base.mo b/archinstall/locales/nl/LC_MESSAGES/base.mo index d7dcabc6..62b7af82 100644 Binary files a/archinstall/locales/nl/LC_MESSAGES/base.mo and b/archinstall/locales/nl/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/nl/LC_MESSAGES/base.po b/archinstall/locales/nl/LC_MESSAGES/base.po index b712d8ca..72547f7a 100644 --- a/archinstall/locales/nl/LC_MESSAGES/base.po +++ b/archinstall/locales/nl/LC_MESSAGES/base.po @@ -256,10 +256,12 @@ msgstr "Taalvariant" msgid "Drive(s)" msgstr "Harde schijven" -msgid "Select disk layout" -msgstr "Kies een schijfindeling" +#, fuzzy +msgid "Disk layout" +msgstr "Schijfindeling vastleggen" -msgid "Set encryption password" +#, fuzzy +msgid "Encryption password" msgstr "Versleutelwachtwoord instellen" msgid "Swap" @@ -268,7 +270,8 @@ msgstr "Wisselgeheugen" msgid "Bootloader" msgstr "Opstartlader" -msgid "root password" +#, fuzzy +msgid "Root password" msgstr "Rootwachtwoord" msgid "Superuser account" @@ -426,8 +429,8 @@ msgstr "Bewerken" msgid "Delete" msgstr "Verwijderen" -msgid "Select an action for < {} >" -msgstr "Kies een actie voor < {} >" +msgid "Select an action for '{}'" +msgstr "Kies een actie voor '{}'" msgid "Copy to new key:" msgstr "Kopiëren naar nieuwe sleutel:" @@ -670,7 +673,7 @@ msgstr "" msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" msgstr "" -msgid "Choose which servers to install, if none then a minimal installation wil be done" +msgid "Choose which servers to install, if none then a minimal installation will be done" msgstr "" msgid "Installs a minimal system as well as xorg and graphics drivers." @@ -757,6 +760,69 @@ msgstr "" "\n" "Kies welke partitie moet worden gemaskeerd alvorens te formatteren" +msgid "Use HSM to unlock encrypted drive" +msgstr "" + +msgid "Device" +msgstr "" + +msgid "Size" +msgstr "" + +msgid "Free space" +msgstr "" + +msgid "Bus-type" +msgstr "" + +#, fuzzy +msgid "Either root-password or at least 1 user with sudo privileges must be specified" +msgstr "Stel een rootwachtwoord of minimaal één beheerder in" + +#, fuzzy +msgid "Enter username (leave blank to skip): " +msgstr "Voer een gebruikersnaam in om een tweede account toe te voegen (laat leeg om over te slaan): " + +msgid "The username you entered is invalid. Try again" +msgstr "" + +#, fuzzy +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 "very weak" +msgstr "" + +msgid "weak" +msgstr "" + +msgid "moderate" +msgstr "" + +msgid "strong" +msgstr "" + +#, fuzzy +msgid "Add subvolume" +msgstr " Subvolume :{:16}" + +msgid "Edit subvolume" +msgstr "" + +#, fuzzy +msgid "Delete subvolume" +msgstr "Gebruiker verwijderen" + +#~ msgid "Select disk layout" +#~ msgstr "Kies een schijfindeling" + #~ msgid "Add :" #~ msgstr "Toevoegen:" diff --git a/archinstall/locales/pl/LC_MESSAGES/base.mo b/archinstall/locales/pl/LC_MESSAGES/base.mo index dc26f1d7..d60d939a 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 d92637ad..f6730462 100644 --- a/archinstall/locales/pl/LC_MESSAGES/base.po +++ b/archinstall/locales/pl/LC_MESSAGES/base.po @@ -428,8 +428,8 @@ msgstr "Edytuj" msgid "Delete" msgstr "Usuń" -msgid "Select an action for < {} >" -msgstr "Wybierz akcję dla < {} >" +msgid "Select an action for '{}'" +msgstr "Wybierz akcję dla '{}'" msgid "Copy to new key:" msgstr "Skopiuj do nowego klucza:" @@ -663,7 +663,8 @@ msgstr "Bardzo podstawowa instalacja pozwalająca ci dostosowanie Arch Linuxa do 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" -msgid "Choose which servers to install, if none then a minimal installation wil be done" +#, 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" msgid "Installs a minimal system as well as xorg and graphics drivers." @@ -745,6 +746,66 @@ msgstr "" msgid "Select which partitions to mark for formatting:" msgstr "Wybierz partycja która ma zostać sformatowana:" +msgid "Use HSM to unlock encrypted drive" +msgstr "" + +msgid "Device" +msgstr "" + +msgid "Size" +msgstr "" + +msgid "Free space" +msgstr "" + +msgid "Bus-type" +msgstr "" + +#, 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" + +#, fuzzy +msgid "Enter username (leave blank to skip): " +msgstr "Wprowadź nazwę użytkownika, aby utworzyć dodatkowego użytkownika (pozostaw puste, aby pominąć): " + +msgid "The username you entered is invalid. Try again" +msgstr "" + +#, fuzzy +msgid "Should \"{}\" be a superuser (sudo)?" +msgstr "Czy {} powinien być superuserem (sudoer)?" + +#, fuzzy +msgid "Select which partitions to encrypt:" +msgstr "" +"{}\n" +"\n" +"Wybierz partycja która ma zostać zaszyfrowana" + +msgid "very weak" +msgstr "" + +msgid "weak" +msgstr "" + +msgid "moderate" +msgstr "" + +msgid "strong" +msgstr "" + +#, fuzzy +msgid "Add subvolume" +msgstr " Subwolumin :{:16}" + +msgid "Edit subvolume" +msgstr "" + +#, fuzzy +msgid "Delete subvolume" +msgstr "Usuń użytkownika" + #~ msgid "Select disk layout" #~ msgstr "Wybierz układ dysku" diff --git a/archinstall/locales/pt/LC_MESSAGES/base.mo b/archinstall/locales/pt/LC_MESSAGES/base.mo index 3b9f7c26..113b26a6 100644 Binary files a/archinstall/locales/pt/LC_MESSAGES/base.mo and b/archinstall/locales/pt/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/pt/LC_MESSAGES/base.po b/archinstall/locales/pt/LC_MESSAGES/base.po index 4352cf34..e44a0133 100644 --- a/archinstall/locales/pt/LC_MESSAGES/base.po +++ b/archinstall/locales/pt/LC_MESSAGES/base.po @@ -254,10 +254,12 @@ msgstr "Codificação de localização" msgid "Drive(s)" msgstr "Discos rígidos" -msgid "Select disk layout" -msgstr "Seleciona o esquema de disco" +#, fuzzy +msgid "Disk layout" +msgstr "Selecionar esquema de disco" -msgid "Set encryption password" +#, fuzzy +msgid "Encryption password" msgstr "Define a palavra-passe de encriptação" msgid "Swap" @@ -266,7 +268,8 @@ msgstr "Swap" msgid "Bootloader" msgstr "Carregador de arranque" -msgid "root password" +#, fuzzy +msgid "Root password" msgstr "Palavra-passe de root" msgid "Superuser account" @@ -429,8 +432,8 @@ msgid "Delete" msgstr "Eliminar" #, fuzzy -msgid "Select an action for < {} >" -msgstr "Selecione uma ação para < {} >" +msgid "Select an action for '{}'" +msgstr "Selecione uma ação para '{}'" msgid "Copy to new key:" msgstr "Copiar para nova chave:" @@ -688,7 +691,7 @@ msgstr "" msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" msgstr "" -msgid "Choose which servers to install, if none then a minimal installation wil be done" +msgid "Choose which servers to install, if none then a minimal installation will be done" msgstr "" msgid "Installs a minimal system as well as xorg and graphics drivers." @@ -777,6 +780,69 @@ msgstr "" "\n" "Seleciona a partição a mascar para formatar" +msgid "Use HSM to unlock encrypted drive" +msgstr "" + +msgid "Device" +msgstr "" + +msgid "Size" +msgstr "" + +msgid "Free space" +msgstr "" + +msgid "Bus-type" +msgstr "" + +#, fuzzy +msgid "Either root-password or at least 1 user with sudo privileges must be specified" +msgstr "É necessário especificar uma senha de root ou pelo menos 1 super-utilizador" + +#, fuzzy +msgid "Enter username (leave blank to skip): " +msgstr "Introduzir um nome de utilizador para criar um utilizador adicional (deixar em branco para saltar): " + +msgid "The username you entered is invalid. Try again" +msgstr "" + +#, fuzzy +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 "very weak" +msgstr "" + +msgid "weak" +msgstr "" + +msgid "moderate" +msgstr "" + +msgid "strong" +msgstr "" + +#, fuzzy +msgid "Add subvolume" +msgstr " Subvolume :{:16}" + +msgid "Edit subvolume" +msgstr "" + +#, fuzzy +msgid "Delete subvolume" +msgstr "Eliminar Utilizador" + +#~ msgid "Select disk layout" +#~ msgstr "Seleciona o esquema de disco" + #~ msgid "Add :" #~ msgstr "Adicionar :" diff --git a/archinstall/locales/pt_BR/LC_MESSAGES/base.mo b/archinstall/locales/pt_BR/LC_MESSAGES/base.mo index 9c71d906..bfc9645c 100644 Binary files a/archinstall/locales/pt_BR/LC_MESSAGES/base.mo and b/archinstall/locales/pt_BR/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/pt_BR/LC_MESSAGES/base.po b/archinstall/locales/pt_BR/LC_MESSAGES/base.po index 35db5f95..004ad022 100644 --- a/archinstall/locales/pt_BR/LC_MESSAGES/base.po +++ b/archinstall/locales/pt_BR/LC_MESSAGES/base.po @@ -9,12 +9,8 @@ msgstr "" msgid "[!] A log file has been created here: {} {}" msgstr "[!] Um arquivo de log foi criado aqui: {} {}" -msgid "" -" Please submit this issue (and file) to https://github.com/archlinux/" -"archinstall/issues" -msgstr "" -" Por favor, envie este problema (e o arquivo) para: " -"https://github.com/archlinux/archinstall/issues" +msgid " Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues" +msgstr " Por favor, envie este problema (e o arquivo) para: https://github.com/archlinux/archinstall/issues" msgid "Do you really want to abort?" msgstr "Deseja realmente abortar?" @@ -49,42 +45,26 @@ msgstr "Escolha um bootloader" msgid "Choose an audio server" msgstr "Escolha um servidor de áudio" -msgid "" -"Only packages such as base, base-devel, linux, linux-firmware, efibootmgr " -"and optional profile packages are installed." -msgstr "" -"Apenas pacotes como base, base-devel, linux, linux-firmware, efibootmgr " -"e pacotes opcionais de perfil são instalados." +msgid "Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed." +msgstr "Apenas pacotes como base, base-devel, linux, linux-firmware, efibootmgr e pacotes opcionais de perfil são instalados." -msgid "" -"If you desire a web browser, such as firefox or chromium, you may specify it " -"in the following prompt." -msgstr "" -"Se deseja um navegador web, como firefox ou chromium, pode especificá-lo " -"no próximo prompt." +msgid "If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt." +msgstr "Se deseja um navegador web, como firefox ou chromium, pode especificá-lo no próximo prompt." -msgid "" -"Write additional packages to install (space separated, leave blank to skip): " -msgstr "" -"Digite pacotes adicionais para instalar (separados por espaço, deixe em branco para pular): " +msgid "Write additional packages to install (space separated, leave blank to skip): " +msgstr "Digite pacotes adicionais para instalar (separados por espaço, deixe em branco para pular): " msgid "Copy ISO network configuration to installation" msgstr "Copiar a configuração de rede da ISO para a instalação" -msgid "" -"Use NetworkManager (necessary to configure internet graphically in GNOME and " -"KDE)" -msgstr "" -"Usar NetworkManager (necessário para configurar internet graficamente no GNOME e " -"KDE)" +msgid "Use NetworkManager (necessary to configure internet graphically in GNOME and KDE)" +msgstr "Usar NetworkManager (necessário para configurar internet graficamente no GNOME e KDE)" msgid "Select one network interface to configure" msgstr "Selecione uma interface de rede para configurar" -msgid "" -"Select which mode to configure for \"{}\" or skip to use default mode \"{}\"" -msgstr "" -"Selecione qual modo configurar para \"{}\" ou ignore para usar o modo padrão \"{}\"" +msgid "Select which mode to configure for \"{}\" or skip to use default mode \"{}\"" +msgstr "Selecione qual modo configurar para \"{}\" ou ignore para usar o modo padrão \"{}\"" msgid "Enter the IP and subnet for {} (example: 192.168.0.5/24): " msgstr "Digite o IP e a subrede para {} (exemplo: 192.168.0.5/24): " @@ -114,10 +94,8 @@ msgstr "Digite o tipo de sistema de arquivos desejado para a partição" msgid "Enter the start sector (percentage or block number, default: {}): " msgstr "Digite o setor de início (porcentagem ou número do bloco, padrão: {}): " -msgid "" -"Enter the end sector of the partition (percentage or block number, ex: {}): " -msgstr "" -"Digite o setor final da partição (porcentagem ou número de bloco, ex: {}): " +msgid "Enter the end sector of the partition (percentage or block number, ex: {}): " +msgstr "Digite o setor final da partição (porcentagem ou número de bloco, ex: {}): " msgid "{} contains queued partitions, this will remove those, are you sure?" msgstr "{} contém partições em fila, isto irá removê-las, tem certeza?" @@ -140,13 +118,8 @@ msgstr "" "\n" "Selecione por índice quais partições montar em" -msgid "" -" * Partition mount-points are relative to inside the installation, the boot " -"would be /boot as an example." -msgstr "" -" * Os pontos de montagem das partições são relativos aos de dentro da instalação, boot " -"por exemplo seria /boot." - +msgid " * Partition mount-points are relative to inside the installation, the boot would be /boot as an example." +msgstr " * Os pontos de montagem das partições são relativos aos de dentro da instalação, boot por exemplo seria /boot." msgid "Select where to mount partition (leave blank to remove mountpoint): " msgstr "Selecione onde montar a partição (deixe em branco para remover o ponto de montagem): " @@ -196,19 +169,14 @@ msgstr "Idioma do Archinstall" msgid "Wipe all selected drives and use a best-effort default partition layout" msgstr "Apagar todos os discos selecionados e usar um esquema de partições padrão de melhor desempenho" -msgid "" -"Select what to do with each individual drive (followed by partition usage)" -msgstr "" -"Selecione o que fazer com cada disco individual (seguido de uso da partição)" +msgid "Select what to do with each individual drive (followed by partition usage)" +msgstr "Selecione o que fazer com cada disco individual (seguido de uso da partição)" msgid "Select what you wish to do with the selected block devices" msgstr "Selecione o que deseja fazer com os dispositivos de bloco selecionados" -msgid "" -"This is a list of pre-programmed profiles, they might make it easier to " -"install things like desktop environments" -msgstr "Esta é uma lista de perfis pré-programados, que podem por exemplo " -"facilitar a instalação de ambientes gráficos" +msgid "This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments" +msgstr "Esta é uma lista de perfis pré-programados, que podem por exemplo facilitar a instalação de ambientes gráficos" msgid "Select keyboard layout" msgstr "Selecione o layout de teclado" @@ -219,26 +187,14 @@ msgstr "Selecione uma das regiões de onde baixar os pacotes" msgid "Select one or more hard drives to use and configure" msgstr "Selecione um ou mais discos rígidos para usar e configurar" -msgid "" -"For the best compatibility with your AMD hardware, you may want to use " -"either the all open-source or AMD / ATI options." -msgstr "" -"Para melhor compatibilidade com o seu hardware AMD, talvez queira usar " -"a opção de drivers completamente open-source ou as opções da AMD / ATI." +msgid "For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options." +msgstr "Para melhor compatibilidade com o seu hardware AMD, talvez queira usar a opção de drivers completamente open-source ou as opções da AMD / ATI." -msgid "" -"For the best compatibility with your Intel hardware, you may want to use " -"either the all open-source or Intel options.\n" -msgstr "" -"Para melhor compatibilidade com o seu hardware Intel, talvez queira usar " -"a opção de drivers completamente open-source ou as opções da Intel.\n" +msgid "For the best compatibility with your Intel hardware, you may want to use either the all open-source or Intel options.\n" +msgstr "Para melhor compatibilidade com o seu hardware Intel, talvez queira usar a opção de drivers completamente open-source ou as opções da Intel.\n" -msgid "" -"For the best compatibility with your Nvidia hardware, you may want to use " -"the Nvidia proprietary driver.\n" -msgstr "" -"Para melhor compatibilidade com o seu hardware Nvidia, talvez queira usar " -"o driver proprietário da Nvidia.\n" +msgid "For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n" +msgstr "Para melhor compatibilidade com o seu hardware Nvidia, talvez queira usar o driver proprietário da Nvidia.\n" msgid "" "\n" @@ -270,13 +226,8 @@ msgstr "Selecione uma ou mais das opções abaixo: " msgid "Adding partition...." msgstr "Adicionando partição...." -msgid "" -"You need to enter a valid fs-type in order to continue. See `man parted` for " -"valid fs-type's." -msgstr "" -"Você precisa definir um tipo de sistema de arquivo válido. Consulte o `man parted` para " -"verificar os tipos de sistemas de arquivo válido." - +msgid "You need to enter a valid fs-type in order to continue. See `man parted` for valid fs-type's." +msgstr "Você precisa definir um tipo de sistema de arquivo válido. Consulte o `man parted` para verificar os tipos de sistemas de arquivo válido." msgid "Error: Listing profiles on URL \"{}\" resulted in:" msgstr "Erro: Listando os perfis em URL \"{}\" resulta em:" @@ -425,25 +376,17 @@ msgstr "Digite uma senha de root (deixe em branco para desativar root): " msgid "Password for user \"{}\": " msgstr "Senha para o usuário \"{}\": " -msgid "" -"Verifying that additional packages exist (this might take a few seconds)" -msgstr "" -"Verificando se existem pacotes adicionais (isto pode demorar alguns segundos)" +msgid "Verifying that additional packages exist (this might take a few seconds)" +msgstr "Verificando se existem pacotes adicionais (isto pode demorar alguns segundos)" -msgid "" -"Would you like to use automatic time synchronization (NTP) with the default " -"time servers?\n" -msgstr "" -"Deseja usar sincronização de tempo automática (NTP) " -"com os servidores de tempo padrão?\n" +msgid "Would you like to use automatic time synchronization (NTP) with the default time servers?\n" +msgstr "Deseja usar sincronização de tempo automática (NTP) com os servidores de tempo padrão?\n" msgid "" -"Hardware time and other post-configuration steps might be required in order " -"for NTP to work.\n" +"Hardware time and other post-configuration steps might be required in order for NTP to work.\n" "For more information, please check the Arch wiki" msgstr "" -"A hora de hardware e outros passos de pós-configuração podem ser necessários " -"para que o NTP funcione.\n" +"A hora de hardware e outros passos de pós-configuração podem ser necessários para que o NTP funcione.\n" "Para mais informações, por favor visite a Arch wiki" msgid "Enter a username to create an additional user (leave blank to skip): " @@ -454,12 +397,10 @@ msgstr "Use ESC para pular\n" msgid "" "\n" -" Choose an object from the list, and select one of the available actions for " -"it to execute" +" Choose an object from the list, and select one of the available actions for it to execute" msgstr "" "\n" -" Escolha um objeto da lista, e selecione uma das ações disponíveis " -"para executar" +" Escolha um objeto da lista, e selecione uma das ações disponíveis para executar" msgid "Cancel" msgstr "Cancelar" @@ -479,8 +420,8 @@ msgstr "Editar" msgid "Delete" msgstr "Deletar" -msgid "Select an action for < {} >" -msgstr "Selecione uma ação para < {} >" +msgid "Select an action for '{}'" +msgstr "Selecione uma ação para '{}'" msgid "Copy to new key:" msgstr "Copiar para nova chave:" @@ -495,17 +436,11 @@ msgstr "" "\n" "Esta é a configuração escolhida escolhida por você:" -msgid "" -"Pacman is already running, waiting maximum 10 minutes for it to terminate." -msgstr "" -"O Pacman já está a rodando, aguarde no máximo até 10 minutos para terminar." +msgid "Pacman is already running, waiting maximum 10 minutes for it to terminate." +msgstr "O Pacman já está a rodando, aguarde no máximo até 10 minutos para terminar." -msgid "" -"Pre-existing pacman lock never exited. Please clean up any existing pacman " -"sessions before using archinstall." -msgstr "" -"Pacman lock não terminou. Por favor, limpe as sessões " -"de pacman existentes antes de usar o archinstall." +msgid "Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall." +msgstr "Pacman lock não terminou. Por favor, limpe as sessões de pacman existentes antes de usar o archinstall." msgid "Choose which optional additional repositories to enable" msgstr "Escolha quais repositórios adicionais opcionais ativar" @@ -656,12 +591,8 @@ msgstr "Deseja usar a compressão BTRFS?" msgid "Would you like to create a separate partition for /home?" msgstr "Deseja criar uma partição separada para /home?" -msgid "" -"The selected drives do not have the minimum capacity required for an " -"automatic suggestion\n" -msgstr "" -"As unidades selecionadas não tem a capacidade mínima para " -"sugestão automática\n" +msgid "The selected drives do not have the minimum capacity required for an automatic suggestion\n" +msgstr "As unidades selecionadas não tem a capacidade mínima para sugestão automática\n" msgid "Minimum capacity for /home partition: {}GB\n" msgstr "Capacidade mínima para partição /home : {}GB\n" @@ -708,41 +639,24 @@ msgstr "Configuração manual" msgid "Mark/Unmark a partition as compressed (btrfs only)" msgstr "Marcar/desmarcar a partição como comprimida (apenas btrfs)" -msgid "" -"The password you are using seems to be weak, are you sure you want to use it?" -msgstr "" -"A senha que você está usando parece ser fraca, tem certeza que deseja utilizá-la?" +msgid "The password you are using seems to be weak, are you sure you want to use it?" +msgstr "A senha que você está usando parece ser fraca, tem certeza que deseja utilizá-la?" -msgid "" -"Provides a selection of desktop environments and tiling window managers, e." -"g. gnome, kde, sway" -msgstr "" -"Proporciona uma seleção de ambientes gráficos e gerenciadores de janela como por exemplo " -"gnome, kde, sway" +msgid "Provides a selection of desktop environments and tiling window managers, e.g. gnome, kde, sway" +msgstr "Proporciona uma seleção de ambientes gráficos e gerenciadores de janela como por exemplo gnome, kde, sway" msgid "Select your desired desktop environment" msgstr "Selecione o ambiente gráfico desejado" -msgid "" -"A very basic installation that allows you to customize Arch Linux as you see " -"fit." -msgstr "" -"Uma instalação bem básica que permite a você customizar o Arch Linux " -"como desejar." +msgid "A very basic installation that allows you to customize Arch Linux as you see fit." +msgstr "Uma instalação bem básica que permite a você customizar o Arch Linux como desejar." -msgid "" -"Provides a selection of various server packages to install and enable, e.g. " -"httpd, nginx, mariadb" -msgstr "" -"Proporciona uma seleção de diversos pacotes de servidor para instalar e habilitar " -"como por exemplo httpd, nginx, mariadb" +msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" +msgstr "Proporciona uma seleção de diversos pacotes de servidor para instalar e habilitar como por exemplo httpd, nginx, mariadb" -msgid "" -"Choose which servers to install, if none then a minimal installation wil be " -"done" -msgstr "" -"Selecione quais servidores instalar, se há nenhum uma instalação mínima será " -"feita" +#, fuzzy +msgid "Choose which servers to install, if none then a minimal installation will be done" +msgstr "Selecione quais servidores instalar, se há nenhum uma instalação mínima será feita" msgid "Installs a minimal system as well as xorg and graphics drivers." msgstr "Instala um sistema mínimo assim como xorg e drivers de vídeo." @@ -750,12 +664,8 @@ msgstr "Instala um sistema mínimo assim como xorg e drivers de vídeo." msgid "Press Enter to continue." msgstr "Tecle Enter para continuar." -msgid "" -"Would you like to chroot into the newly created installation and perform " -"post-installation configuration?" -msgstr "" -"Deseja fazer chroot para a nova instalação e realizar " -"configurações pós-instalação?" +msgid "Would you like to chroot into the newly created installation and perform post-installation configuration?" +msgstr "Deseja fazer chroot para a nova instalação e realizar configurações pós-instalação?" msgid "Are you sure you want to reset this setting?" msgstr "Tem certeza que desejar resetar essa configuração?" @@ -766,12 +676,8 @@ msgstr "Selecione uma ou mais unidades para usar e configurar\n" msgid "Any modifications to the existing setting will reset the disk layout!" msgstr "Quaisquer modificações para configurações existentes vão resetar o layout de disco!" -msgid "" -"If you reset the harddrive selection this will also reset the current disk " -"layout. Are you sure?" -msgstr "" -"Se você resetar a seleção de unidades isso também resetará o layout " -"da unidade atual. Tem certeza?" +msgid "If you reset the harddrive selection this will also reset the current disk layout. Are you sure?" +msgstr "Se você resetar a seleção de unidades isso também resetará o layout da unidade atual. Tem certeza?" msgid "Save and exit" msgstr "Salvar e sair" @@ -817,15 +723,71 @@ msgstr "Adicionar: " msgid "Value: " msgstr "Valor: " -msgid "" -"You can skip selecting a drive and partitioning and use whatever drive-setup " -"is mounted at /mnt (experimental)" -msgstr "" -"Você pode ignorar a seleção de unidade e particionar seja lá o que estiver " -"montado em /mnt (experimental)" +msgid "You can skip selecting a drive and partitioning and use whatever drive-setup is mounted at /mnt (experimental)" +msgstr "Você pode ignorar a seleção de unidade e particionar seja lá o que estiver montado em /mnt (experimental)" msgid "Select one of the disks or skip and use /mnt as default" msgstr "Selecione um dos discos ou ignore e use /mnt como padrão" msgid "Select which partitions to mark for formatting:" msgstr "Selecione quais partições marcar para formatar:" + +msgid "Use HSM to unlock encrypted drive" +msgstr "" + +msgid "Device" +msgstr "" + +msgid "Size" +msgstr "" + +msgid "Free space" +msgstr "" + +msgid "Bus-type" +msgstr "" + +#, fuzzy +msgid "Either root-password or at least 1 user with sudo privileges must be specified" +msgstr "Deve se especificar uma senha de root ou pelo menos 1 superusuário" + +#, fuzzy +msgid "Enter username (leave blank to skip): " +msgstr "Digite um nome de usuário para criar um usuário adicional (deixe em branco para pular): " + +msgid "The username you entered is invalid. Try again" +msgstr "" + +#, fuzzy +msgid "Should \"{}\" be a superuser (sudo)?" +msgstr "{} deve ser um superusuário (sudoer)?" + +#, fuzzy +msgid "Select which partitions to encrypt:" +msgstr "" +"{}\n" +"\n" +"Selecione qual partição marcar como encriptada" + +msgid "very weak" +msgstr "" + +msgid "weak" +msgstr "" + +msgid "moderate" +msgstr "" + +msgid "strong" +msgstr "" + +#, fuzzy +msgid "Add subvolume" +msgstr " Subvolume :{:16}" + +msgid "Edit subvolume" +msgstr "" + +#, fuzzy +msgid "Delete subvolume" +msgstr "Deletar usuário" diff --git a/archinstall/locales/ru/LC_MESSAGES/base.mo b/archinstall/locales/ru/LC_MESSAGES/base.mo index 8c0dc224..0930b800 100644 Binary files a/archinstall/locales/ru/LC_MESSAGES/base.mo and b/archinstall/locales/ru/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/ru/LC_MESSAGES/base.po b/archinstall/locales/ru/LC_MESSAGES/base.po index 5a6f440a..2a88a85f 100644 --- a/archinstall/locales/ru/LC_MESSAGES/base.po +++ b/archinstall/locales/ru/LC_MESSAGES/base.po @@ -508,8 +508,8 @@ msgstr "Редактировать" msgid "Delete" msgstr "Удалить" -msgid "Select an action for < {} >" -msgstr "Выберите действие для < {} >" +msgid "Select an action for '{}'" +msgstr "Выберите действие для '{}'" msgid "Copy to new key:" msgstr "Копировать в новый ключ:" diff --git a/archinstall/locales/sv/LC_MESSAGES/base.mo b/archinstall/locales/sv/LC_MESSAGES/base.mo index b7999fbb..f2798bd4 100644 Binary files a/archinstall/locales/sv/LC_MESSAGES/base.mo and b/archinstall/locales/sv/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/sv/LC_MESSAGES/base.po b/archinstall/locales/sv/LC_MESSAGES/base.po index 2c587af5..747e28a1 100644 --- a/archinstall/locales/sv/LC_MESSAGES/base.po +++ b/archinstall/locales/sv/LC_MESSAGES/base.po @@ -256,10 +256,12 @@ msgstr "Teckenuppsättning du vill använda" msgid "Drive(s)" msgstr "Hårddiskar" -msgid "Select disk layout" -msgstr "Välj hårddisk-layout" +#, fuzzy +msgid "Disk layout" +msgstr "Spara disk konfigurering" -msgid "Set encryption password" +#, fuzzy +msgid "Encryption password" msgstr "Välj ett krypterings-lösenord" msgid "Swap" @@ -268,7 +270,8 @@ msgstr "Swap" msgid "Bootloader" msgstr "Boot-loader" -msgid "root password" +#, fuzzy +msgid "Root password" msgstr "root-lösenord" msgid "Superuser account" @@ -426,8 +429,8 @@ msgstr "Editera" msgid "Delete" msgstr "Ta bort" -msgid "Select an action for < {} >" -msgstr "Välj vad du vill göra med < {} >" +msgid "Select an action for '{}'" +msgstr "Välj vad du vill göra med '{}'" msgid "Copy to new key:" msgstr "Kopiera till en ny nyckel:" @@ -660,7 +663,8 @@ msgstr "En väldigt minimal installation som möjliggör konfigurering av Arch L msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" msgstr "Erbjuder ett urval av olika server-packeteringar, t.ex. httpd, nginx och mariadb" -msgid "Choose which servers to install, if none then a minimal installation wil be done" +#, fuzzy +msgid "Choose which servers to install, if none then a minimal installation will be done" msgstr "Välj vilka servrar du vill installera, en minimal installation sker om du hoppar över detta" msgid "Installs a minimal system as well as xorg and graphics drivers." @@ -736,3 +740,66 @@ msgstr "Välj en disk eller hoppa över och använd /mnt/archinstall utan format msgid "Select which partitions to mark for formatting:" msgstr "Välj vilken partition som skall markeras för formatering:" + +msgid "Use HSM to unlock encrypted drive" +msgstr "" + +msgid "Device" +msgstr "" + +msgid "Size" +msgstr "" + +msgid "Free space" +msgstr "" + +msgid "Bus-type" +msgstr "" + +#, fuzzy +msgid "Either root-password or at least 1 user with sudo privileges must be specified" +msgstr "Antingen måste ett root-lösenord sättas eller 1 superanvändarkonto skapas" + +#, fuzzy +msgid "Enter username (leave blank to skip): " +msgstr "Mata in ett användarnamn för att skapa ytterligare användare (lämna tom för att hoppa över): " + +msgid "The username you entered is invalid. Try again" +msgstr "" + +#, fuzzy +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 "very weak" +msgstr "" + +msgid "weak" +msgstr "" + +msgid "moderate" +msgstr "" + +msgid "strong" +msgstr "" + +#, fuzzy +msgid "Add subvolume" +msgstr " Sub-volym :{:16}" + +msgid "Edit subvolume" +msgstr "" + +#, fuzzy +msgid "Delete subvolume" +msgstr "Ta bort användare" + +#~ msgid "Select disk layout" +#~ msgstr "Välj hårddisk-layout" diff --git a/archinstall/locales/tr/LC_MESSAGES/base.mo b/archinstall/locales/tr/LC_MESSAGES/base.mo index 6eb05010..6a205da9 100644 Binary files a/archinstall/locales/tr/LC_MESSAGES/base.mo and b/archinstall/locales/tr/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/tr/LC_MESSAGES/base.po b/archinstall/locales/tr/LC_MESSAGES/base.po index 03125510..60475156 100644 --- a/archinstall/locales/tr/LC_MESSAGES/base.po +++ b/archinstall/locales/tr/LC_MESSAGES/base.po @@ -15,12 +15,8 @@ msgstr "" msgid "[!] A log file has been created here: {} {}" msgstr "[!] Burada bir günlük (log) dosyası oluşturuldu: {} {}" -msgid "" -" Please submit this issue (and file) to https://github.com/archlinux/" -"archinstall/issues" -msgstr "" -" Lütfen bu sorunu (ve dosyayı) https://github.com/archlinux/archinstall/" -"issues 'a bildirin" +msgid " Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues" +msgstr " Lütfen bu sorunu (ve dosyayı) https://github.com/archlinux/archinstall/issues 'a bildirin" msgid "Do you really want to abort?" msgstr "Gerçekten iptal etmek istiyor musunuz?" @@ -35,12 +31,10 @@ msgid "Desired hostname for the installation: " msgstr "Kurulum için tercih edilen bilgisayar ismi: " msgid "Username for required superuser with sudo privileges: " -msgstr "" -"Sudo yetkilerine sahip, gerekli bir süper kullanıcı için kullanıcı adı: " +msgstr "Sudo yetkilerine sahip, gerekli bir süper kullanıcı için kullanıcı adı: " msgid "Any additional users to install (leave blank for no users): " -msgstr "" -"Kurulum için ek kullanıcılar (ek kullanıcı istemiyorsanız boş bırakın): " +msgstr "Kurulum için ek kullanıcılar (ek kullanıcı istemiyorsanız boş bırakın): " msgid "Should this user be a superuser (sudoer)?" msgstr "Bu kullanıcı bir süper kullanıcı (sudoer) olmalı mı?" @@ -57,55 +51,35 @@ msgstr "Bir önyükleyici seçin" msgid "Choose an audio server" msgstr "Bir ses sunucusu seçin" -msgid "" -"Only packages such as base, base-devel, linux, linux-firmware, efibootmgr " -"and optional profile packages are installed." -msgstr "" -"Sadece base, base-devel, linux, linux-firmware, efibootmgr ve tercihi profil " -"paketleri kuruldu." +msgid "Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed." +msgstr "Sadece base, base-devel, linux, linux-firmware, efibootmgr ve tercihi profil paketleri kuruldu." -msgid "" -"If you desire a web browser, such as firefox or chromium, you may specify it " -"in the following prompt." -msgstr "" -"Eğer bir internet tarayıcısı arzu ediyorsanız, Firefox ya da Chromium gibi, " -"sıra gelen ekranda belirtebilirsiniz." +msgid "If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt." +msgstr "Eğer bir internet tarayıcısı arzu ediyorsanız, Firefox ya da Chromium gibi, sıra gelen ekranda belirtebilirsiniz." -msgid "" -"Write additional packages to install (space separated, leave blank to skip): " -msgstr "" -"Kurulacak ek paketleri yazınız (boşlukla ayrılmış, geçmek için boş bırakın): " +msgid "Write additional packages to install (space separated, leave blank to skip): " +msgstr "Kurulacak ek paketleri yazınız (boşlukla ayrılmış, geçmek için boş bırakın): " msgid "Copy ISO network configuration to installation" msgstr "Kuruluma ISO'dan ağ yapılandırmasını kopyala" -msgid "" -"Use NetworkManager (necessary to configure internet graphically in GNOME and " -"KDE)" -msgstr "" -"NetworkManager'ı kullan (GNOME ya da KDE'de interneti grafik olarak " -"yapılandırmak için gerekli)" +msgid "Use NetworkManager (necessary to configure internet graphically in GNOME and KDE)" +msgstr "NetworkManager'ı kullan (GNOME ya da KDE'de interneti grafik olarak yapılandırmak için gerekli)" msgid "Select one network interface to configure" msgstr "Yapılandırmak için bir ağ arayüzü seçin" -msgid "" -"Select which mode to configure for \"{}\" or skip to use default mode \"{}\"" -msgstr "" -"\"{}\"i yapılandırmak için bir yöntem seçin ya da varsayılan yöntemi \"{}\" " -"kullanmak için geçin" +msgid "Select which mode to configure for \"{}\" or skip to use default mode \"{}\"" +msgstr "\"{}\"i yapılandırmak için bir yöntem seçin ya da varsayılan yöntemi \"{}\" kullanmak için geçin" msgid "Enter the IP and subnet for {} (example: 192.168.0.5/24): " msgstr "{} için IP ve altağ girin (örnek: 192.168.0.5/24): " msgid "Enter your gateway (router) IP address or leave blank for none: " -msgstr "" -"Ağ geçidi (yönlendirici) IP adresini girin ya da kullanılmaması için boş " -"bırakın: " +msgstr "Ağ geçidi (yönlendirici) IP adresini girin ya da kullanılmaması için boş bırakın: " msgid "Enter your DNS servers (space separated, blank for none): " -msgstr "" -"DNS sunucularını girin (boşlukla ayrılmış, kullanılmaması için boş bırakın): " +msgstr "DNS sunucularını girin (boşlukla ayrılmış, kullanılmaması için boş bırakın): " msgid "Select which filesystem your main partition should use" msgstr "Ana disk bölümünde kullanması gereken dosya sistemini seçin" @@ -126,15 +100,11 @@ msgstr "Disk bölümü için arzu edilen bir dosya systemi tipi girin" msgid "Enter the start sector (percentage or block number, default: {}): " msgstr "Başlangıç kesimini girin (yüzde ya da blok numarası, varsayılan: {}): " -msgid "" -"Enter the end sector of the partition (percentage or block number, ex: {}): " -msgstr "" -"Disk bölümünün bitiş kesimini girin (yüzde ya da blok numarası, ör: {}): " +msgid "Enter the end sector of the partition (percentage or block number, ex: {}): " +msgstr "Disk bölümünün bitiş kesimini girin (yüzde ya da blok numarası, ör: {}): " msgid "{} contains queued partitions, this will remove those, are you sure?" -msgstr "" -"{} işlem sırasında bekleyen disk bölümleri bulunduruyor, bu onları " -"kaldıracak, emin misiniz?" +msgstr "{} işlem sırasında bekleyen disk bölümleri bulunduruyor, bu onları kaldıracak, emin misiniz?" msgid "" "{}\n" @@ -154,17 +124,11 @@ msgstr "" "\n" "Dizinden hangi disk bölümünün nereye mount (monte) edileceğini seçin" -msgid "" -" * Partition mount-points are relative to inside the installation, the boot " -"would be /boot as an example." -msgstr "" -" * Disk bölümü mount (monte) noktaları iç kurulumla ilişkilidir, örnek " -"olarak boot, /boot olacaktır." +msgid " * Partition mount-points are relative to inside the installation, the boot would be /boot as an example." +msgstr " * Disk bölümü mount (monte) noktaları iç kurulumla ilişkilidir, örnek olarak boot, /boot olacaktır." msgid "Select where to mount partition (leave blank to remove mountpoint): " -msgstr "" -"Disk bölümünün nereye mount (monte) edileceğini seçin (mount noktasını " -"kaldırmak için boş bırakın): " +msgstr "Disk bölümünün nereye mount (monte) edileceğini seçin (mount noktasını kaldırmak için boş bırakın): " msgid "" "{}\n" @@ -209,25 +173,16 @@ msgid "Archinstall language" msgstr "Archinstall dili" msgid "Wipe all selected drives and use a best-effort default partition layout" -msgstr "" -"Bütün seçilmiş diskleri temizle ve elden gelen en iyi varsayılan disk bölümü " -"düzenini kullan" +msgstr "Bütün seçilmiş diskleri temizle ve elden gelen en iyi varsayılan disk bölümü düzenini kullan" -msgid "" -"Select what to do with each individual drive (followed by partition usage)" -msgstr "" -"Her bir disk ile ne yapılacağını seçin (disk bölümü kullanımı ile takip " -"edilir)" +msgid "Select what to do with each individual drive (followed by partition usage)" +msgstr "Her bir disk ile ne yapılacağını seçin (disk bölümü kullanımı ile takip edilir)" msgid "Select what you wish to do with the selected block devices" msgstr "Seçili blok cihazları ile ne yapmak istediğinizi seçin" -msgid "" -"This is a list of pre-programmed profiles, they might make it easier to " -"install things like desktop environments" -msgstr "" -"Bu ön-programlanmış profillerin bir listesidir, bunlar masaüstü ortamları " -"gibi şeyler kurmayı kolaylaştırabilir" +msgid "This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments" +msgstr "Bu ön-programlanmış profillerin bir listesidir, bunlar masaüstü ortamları gibi şeyler kurmayı kolaylaştırabilir" msgid "Select keyboard layout" msgstr "Klavye düzeni seçin" @@ -238,26 +193,14 @@ msgstr "Paketleri indirmek için bölgelerden birini seçin" msgid "Select one or more hard drives to use and configure" msgstr "Kullanmak ve yapılandırmak için bir veya daha fazla sabit disk seçin" -msgid "" -"For the best compatibility with your AMD hardware, you may want to use " -"either the all open-source or AMD / ATI options." -msgstr "" -"AMD donanımınızla en iyi uyumluluk için, tam açık-kaynak ya da AMD / ATI " -"ayarlarından birini kullanmak isteyebilirsiniz." +msgid "For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options." +msgstr "AMD donanımınızla en iyi uyumluluk için, tam açık-kaynak ya da AMD / ATI ayarlarından birini kullanmak isteyebilirsiniz." -msgid "" -"For the best compatibility with your Intel hardware, you may want to use " -"either the all open-source or Intel options.\n" -msgstr "" -"Intel donanımınızla en iyi uyumluluk için, tam açık-kaynak ya da Intel " -"ayarlarından birini kullanmak isteyebilirsiniz.\n" +msgid "For the best compatibility with your Intel hardware, you may want to use either the all open-source or Intel options.\n" +msgstr "Intel donanımınızla en iyi uyumluluk için, tam açık-kaynak ya da Intel ayarlarından birini kullanmak isteyebilirsiniz.\n" -msgid "" -"For the best compatibility with your Nvidia hardware, you may want to use " -"the Nvidia proprietary driver.\n" -msgstr "" -"Nvidia donanımınızla en iyi uyumluluk için, Nvidia patentli sürücüyü " -"kullanmak isteyebilirsiniz.\n" +msgid "For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n" +msgstr "Nvidia donanımınızla en iyi uyumluluk için, Nvidia patentli sürücüyü kullanmak isteyebilirsiniz.\n" msgid "" "\n" @@ -266,16 +209,13 @@ msgid "" msgstr "" "\n" "\n" -"Bir grafik sürücüsü seçin ya da bütün açık-kaynak sürücüleri kurmak için boş " -"bırakın" +"Bir grafik sürücüsü seçin ya da bütün açık-kaynak sürücüleri kurmak için boş bırakın" msgid "All open-source (default)" msgstr "Tam açık-kaynak (varsayılan)" msgid "Choose which kernels to use or leave blank for default \"{}\"" -msgstr "" -"Hangi çekirdekleri kullanmak istediğinizi seçin ya da varsayılan \"{}\" için " -"boş bırakın" +msgstr "Hangi çekirdekleri kullanmak istediğinizi seçin ya da varsayılan \"{}\" için boş bırakın" # Burada "locale" hesaba özgü yani profil içinde yerel anlamına geliyor olabilir. Kurulumda ilgili metin ile karşılaşmadığımdan karar veremiyorum. msgid "Choose which locale language to use" @@ -295,12 +235,8 @@ msgstr "Aşağıdaki seçeneklerden bir ya da daha fazlasını seçin: " msgid "Adding partition...." msgstr "Disk bölümü ekleniyor…." -msgid "" -"You need to enter a valid fs-type in order to continue. See `man parted` for " -"valid fs-type's." -msgstr "" -"Devam etmek için geçerli bir ds-tipi (fs-type) girmeniz gerekiyor. Geçerli " -"ds-tiplerini görmek için `man parted`a bakın." +msgid "You need to enter a valid fs-type in order to continue. See `man parted` for valid fs-type's." +msgstr "Devam etmek için geçerli bir ds-tipi (fs-type) girmeniz gerekiyor. Geçerli ds-tiplerini görmek için `man parted`a bakın." msgid "Error: Listing profiles on URL \"{}\" resulted in:" msgstr "Hata: Bağlantı \"{}\"daki profilleri listeleme şöyle sonuçlandı:" @@ -394,17 +330,13 @@ msgid "Assign mount-point for a partition" msgstr "Bir disk bölümü için mount (monte) noktası ata" msgid "Mark/Unmark a partition to be formatted (wipes data)" -msgstr "" -"Bir disk bölümünü biçimlendirilmek üzere işaretle/işareti kaldır (veriyi " -"temizler)" +msgstr "Bir disk bölümünü biçimlendirilmek üzere işaretle/işareti kaldır (veriyi temizler)" msgid "Mark/Unmark a partition as encrypted" msgstr "Bir disk bölümünü şifrelenmiş olarak işaretle/işareti kaldır" msgid "Mark/Unmark a partition as bootable (automatic for /boot)" -msgstr "" -"Bir disk bölümünü boot edilebilir olarak işaretle/işareti kaldır (/boot için " -"otomatik)" +msgstr "Bir disk bölümünü boot edilebilir olarak işaretle/işareti kaldır (/boot için otomatik)" msgid "Set desired filesystem for a partition" msgstr "Bir disk bölümü için arzu edilen dosya sistemini ayarla" @@ -448,48 +380,36 @@ msgid "Create a required super-user with sudo privileges: " msgstr "Gerekli bir sudo yetkilerine sahip süper-kullanıcı oluşturun: " msgid "Enter root password (leave blank to disable root): " -msgstr "" -"Root (kök) şifresi girin (root'u hizmet dışı bırakmak için boş bırakın): " +msgstr "Root (kök) şifresi girin (root'u hizmet dışı bırakmak için boş bırakın): " msgid "Password for user \"{}\": " msgstr "Kullanıcı \"{}\" için şifre: " -msgid "" -"Verifying that additional packages exist (this might take a few seconds)" +msgid "Verifying that additional packages exist (this might take a few seconds)" msgstr "Ek paketlerin varlığı doğrulanıyor (bu bir kaç saniye alabilir)" -msgid "" -"Would you like to use automatic time synchronization (NTP) with the default " -"time servers?\n" -msgstr "" -"Varsayılan zaman sunucularıyla otomatik zaman eşzamanlamasını (NTP) " -"kullanmak ister misiniz?\n" +msgid "Would you like to use automatic time synchronization (NTP) with the default time servers?\n" +msgstr "Varsayılan zaman sunucularıyla otomatik zaman eşzamanlamasını (NTP) kullanmak ister misiniz?\n" msgid "" -"Hardware time and other post-configuration steps might be required in order " -"for NTP to work.\n" +"Hardware time and other post-configuration steps might be required in order for NTP to work.\n" "For more information, please check the Arch wiki" msgstr "" -"NTP'nin çalışması için donanım zamanı ve diğer yapılandırma sonrası adımlar " -"gerekebilir.\n" +"NTP'nin çalışması için donanım zamanı ve diğer yapılandırma sonrası adımlar gerekebilir.\n" "Daha fazla bilgi için, lütfen Arch wikiye göz atın" msgid "Enter a username to create an additional user (leave blank to skip): " -msgstr "" -"Ek kullanıcı oluşturmak için bir kullanıcı adı girin (geçmek için boş " -"bırakın): " +msgstr "Ek kullanıcı oluşturmak için bir kullanıcı adı girin (geçmek için boş bırakın): " msgid "Use ESC to skip\n" msgstr "Geçmek için ESC'yi kullanın\n" msgid "" "\n" -" Choose an object from the list, and select one of the available actions for " -"it to execute" +" Choose an object from the list, and select one of the available actions for it to execute" msgstr "" "\n" -"Listeden bir obje seçin ve çalıştırılmak üzere mevcut eylemlerden birini " -"seçin" +"Listeden bir obje seçin ve çalıştırılmak üzere mevcut eylemlerden birini seçin" msgid "Cancel" msgstr "İptal et" @@ -509,8 +429,8 @@ msgstr "Düzenle" msgid "Delete" msgstr "Sil" -msgid "Select an action for < {} >" -msgstr "< {} > için bir eylem seçin" +msgid "Select an action for '{}'" +msgstr "'{}' için bir eylem seçin" msgid "Copy to new key:" msgstr "Yeni anahtara kopyala:" @@ -525,22 +445,14 @@ msgstr "" "\n" "Bu sizin seçilmiş yapılandırmanız:" -msgid "" -"Pacman is already running, waiting maximum 10 minutes for it to terminate." -msgstr "" -"Pacman hâlihazırda çalışıyor, işlemin sonlandırılması için en fazla 10 " -"dakika beklenecek." +msgid "Pacman is already running, waiting maximum 10 minutes for it to terminate." +msgstr "Pacman hâlihazırda çalışıyor, işlemin sonlandırılması için en fazla 10 dakika beklenecek." -msgid "" -"Pre-existing pacman lock never exited. Please clean up any existing pacman " -"sessions before using archinstall." -msgstr "" -"Önceden var olan pacman kilidi çıkış yapmadı. Lütfen archinstall'ı " -"kullanmadan once mevcut olan pacman oturumlarını temizleyin." +msgid "Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall." +msgstr "Önceden var olan pacman kilidi çıkış yapmadı. Lütfen archinstall'ı kullanmadan once mevcut olan pacman oturumlarını temizleyin." msgid "Choose which optional additional repositories to enable" -msgstr "" -"Hangi tercihi ek repositorylerin (depoların) aktifleştirileceğini seçin" +msgstr "Hangi tercihi ek repositorylerin (depoların) aktifleştirileceğini seçin" msgid "Add a user" msgstr "Kullanıcı ekle" @@ -628,9 +540,7 @@ msgid "Missing configurations:\n" msgstr "Eksik yapılandırmalar:\n" msgid "Either root-password or at least 1 superuser must be specified" -msgstr "" -"Root (kök) şifresi ya da en azından 1 adet süper-kullanıcı belirtilmek " -"zorunda" +msgstr "Root (kök) şifresi ya da en azından 1 adet süper-kullanıcı belirtilmek zorunda" msgid "Manage superuser accounts: " msgstr "Süper-kullanıcı hesaplarını yönet: " @@ -690,12 +600,8 @@ msgstr "BTRFS sıkıştırmasını kullanmak ister misiniz?" msgid "Would you like to create a separate partition for /home?" msgstr "/home dizini için ayrı bir disk bölümü oluşturmak ister misiniz?" -msgid "" -"The selected drives do not have the minimum capacity required for an " -"automatic suggestion\n" -msgstr "" -"Seçilmiş diskler otomatik öneriler için gerekli minimum kapasiteye sahip " -"değil\n" +msgid "The selected drives do not have the minimum capacity required for an automatic suggestion\n" +msgstr "Seçilmiş diskler otomatik öneriler için gerekli minimum kapasiteye sahip değil\n" msgid "Minimum capacity for /home partition: {}GB\n" msgstr "/home disk bölümü için minimum kapasite: {}GB\n" @@ -740,43 +646,26 @@ msgid "Manual configuration" msgstr "Manuel yapılandırma" msgid "Mark/Unmark a partition as compressed (btrfs only)" -msgstr "" -"Bir disk bölümünü sıkıştırılmış olarak işaretle/işareti kaldır (sadece btrfs)" +msgstr "Bir disk bölümünü sıkıştırılmış olarak işaretle/işareti kaldır (sadece btrfs)" -msgid "" -"The password you are using seems to be weak, are you sure you want to use it?" -msgstr "" -"Kullandığınız şifre zayıf gözüküyor, kullanmak istediğinize emin misiniz?" +msgid "The password you are using seems to be weak, are you sure you want to use it?" +msgstr "Kullandığınız şifre zayıf gözüküyor, kullanmak istediğinize emin misiniz?" -msgid "" -"Provides a selection of desktop environments and tiling window managers, e." -"g. gnome, kde, sway" -msgstr "" -"Masaüstü ortamları ve otomatik hizalamalı pencere yöneticilerine bir seçenek " -"sunar, örnek olarak gnome, kde, sway" +msgid "Provides a selection of desktop environments and tiling window managers, e.g. gnome, kde, sway" +msgstr "Masaüstü ortamları ve otomatik hizalamalı pencere yöneticilerine bir seçenek sunar, örnek olarak gnome, kde, sway" msgid "Select your desired desktop environment" msgstr "Arzu ettiğiniz masaüstü ortamını seçin" -msgid "" -"A very basic installation that allows you to customize Arch Linux as you see " -"fit." -msgstr "" -"Arch Linux'u istediğin gibi kişiselleştirmeni sağlayan çok basit bir kurulum." +msgid "A very basic installation that allows you to customize Arch Linux as you see fit." +msgstr "Arch Linux'u istediğin gibi kişiselleştirmeni sağlayan çok basit bir kurulum." -msgid "" -"Provides a selection of various server packages to install and enable, e.g. " -"httpd, nginx, mariadb" -msgstr "" -"Kurmak ve aktif etmek için bazı seçilmiş çeşitli sunucu paketleri sunar, " -"örnek olarak httpd, nginx, mariadb" +msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" +msgstr "Kurmak ve aktif etmek için bazı seçilmiş çeşitli sunucu paketleri sunar, örnek olarak httpd, nginx, mariadb" -msgid "" -"Choose which servers to install, if none then a minimal installation wil be " -"done" -msgstr "" -"Hangi sunucuların kurulacağını seçin, eğer hiçbiri seçilmezse minimal bir " -"kurulum gerçekleştirilecektir" +#, fuzzy +msgid "Choose which servers to install, if none then a minimal installation will be done" +msgstr "Hangi sunucuların kurulacağını seçin, eğer hiçbiri seçilmezse minimal bir kurulum gerçekleştirilecektir" msgid "Installs a minimal system as well as xorg and graphics drivers." msgstr "Xorg ve grafik sürücüleri ile minimal bir sistem kurar." @@ -784,29 +673,20 @@ msgstr "Xorg ve grafik sürücüleri ile minimal bir sistem kurar." msgid "Press Enter to continue." msgstr "Devam etmek için Enter'a bas." -msgid "" -"Would you like to chroot into the newly created installation and perform " -"post-installation configuration?" -msgstr "" -"Yeni oluşturulmuş kuruluma chroot etmek ve kurulum sonrası konfigürasyon " -"gerçekleştirmek ister misiniz?" +msgid "Would you like to chroot into the newly created installation and perform post-installation configuration?" +msgstr "Yeni oluşturulmuş kuruluma chroot etmek ve kurulum sonrası konfigürasyon gerçekleştirmek ister misiniz?" msgid "Are you sure you want to reset this setting?" msgstr "Bu ayarı sıfırlamak istediğinize emin misiniz?" msgid "Select one or more hard drives to use and configure\n" -msgstr "" -"Kullanmak ve yapılandırmak için bir ya da daha fazla sabit disk seçin\n" +msgstr "Kullanmak ve yapılandırmak için bir ya da daha fazla sabit disk seçin\n" msgid "Any modifications to the existing setting will reset the disk layout!" msgstr "Mevcut ayara herhangi bir modifikasyon disk şemasını sıfırlayacaktır!" -msgid "" -"If you reset the harddrive selection this will also reset the current disk " -"layout. Are you sure?" -msgstr "" -"Eğer sabit disk seçimini sıfırlarsanız bu ayrıca mevcut disk şemasını da " -"sıfırlayacaktır. Emin misiniz?" +msgid "If you reset the harddrive selection this will also reset the current disk layout. Are you sure?" +msgstr "Eğer sabit disk seçimini sıfırlarsanız bu ayrıca mevcut disk şemasını da sıfırlayacaktır. Emin misiniz?" msgid "Save and exit" msgstr "Kaydet ve çık" @@ -816,8 +696,7 @@ msgid "" "contains queued partitions, this will remove those, are you sure?" msgstr "" "{}\n" -"işlem sırasında bekleyen disk bölümleri bulunduruyor, bu onları kaldıracak, " -"emin misiniz?" +"işlem sırasında bekleyen disk bölümleri bulunduruyor, bu onları kaldıracak, emin misiniz?" msgid "No audio server" msgstr "Ses sunucusu yok" @@ -853,17 +732,11 @@ msgstr "Ekle: " msgid "Value: " msgstr "Değer: " -msgid "" -"You can skip selecting a drive and partitioning and use whatever drive-setup " -"is mounted at /mnt (experimental)" -msgstr "" -"Disk seçmeyi ve disk bölümlendirmeyi geçebilir ve disk kurulumu /mnt " -"dizininde neye mount (monte) ediliyse onu kullanabilirsiniz (deneysel)" +msgid "You can skip selecting a drive and partitioning and use whatever drive-setup is mounted at /mnt (experimental)" +msgstr "Disk seçmeyi ve disk bölümlendirmeyi geçebilir ve disk kurulumu /mnt dizininde neye mount (monte) ediliyse onu kullanabilirsiniz (deneysel)" msgid "Select one of the disks or skip and use /mnt as default" -msgstr "" -"Disklerden birini seçin ya da geçin ve /mnt dizinini varsayılan olarak " -"kullanın" +msgstr "Disklerden birini seçin ya da geçin ve /mnt dizinini varsayılan olarak kullanın" msgid "Select which partitions to mark for formatting:" msgstr "Biçimlendirme için işaretlenecek disk bölümünü seçin:" @@ -882,3 +755,48 @@ msgstr "Boş alan" msgid "Bus-type" msgstr "Bus(veri yolu)-tipi" + +#, fuzzy +msgid "Either root-password or at least 1 user with sudo privileges must be specified" +msgstr "Root (kök) şifresi ya da en azından 1 adet süper-kullanıcı belirtilmek zorunda" + +#, fuzzy +msgid "Enter username (leave blank to skip): " +msgstr "Ek kullanıcı oluşturmak için bir kullanıcı adı girin (geçmek için boş bırakın): " + +msgid "The username you entered is invalid. Try again" +msgstr "" + +#, fuzzy +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 "very weak" +msgstr "" + +msgid "weak" +msgstr "" + +msgid "moderate" +msgstr "" + +msgid "strong" +msgstr "" + +#, fuzzy +msgid "Add subvolume" +msgstr " alt disk bölümü :{:16}" + +msgid "Edit subvolume" +msgstr "" + +#, fuzzy +msgid "Delete subvolume" +msgstr "Kullanıcı Sil" diff --git a/archinstall/locales/ur/LC_MESSAGES/base.mo b/archinstall/locales/ur/LC_MESSAGES/base.mo index dad80f9d..44e38ad4 100644 Binary files a/archinstall/locales/ur/LC_MESSAGES/base.mo and b/archinstall/locales/ur/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/ur/LC_MESSAGES/base.po b/archinstall/locales/ur/LC_MESSAGES/base.po index 9baa850c..710c08d7 100644 --- a/archinstall/locales/ur/LC_MESSAGES/base.po +++ b/archinstall/locales/ur/LC_MESSAGES/base.po @@ -256,10 +256,12 @@ msgstr "مقامی انکوڈنگ" msgid "Drive(s)" msgstr "ہارڈ ڈرائیوز" -msgid "Select disk layout" -msgstr "ڈسک لے آؤٹ کو منتخب کریں" +#, fuzzy +msgid "Disk layout" +msgstr "ڈسک لے آؤٹ کو محفوظ کریں" -msgid "Set encryption password" +#, fuzzy +msgid "Encryption password" msgstr "انکرپشن پاس ورڈ سیٹ کریں" msgid "Swap" @@ -268,7 +270,8 @@ msgstr "سواپ" msgid "Bootloader" msgstr "بوٹ لوڈر" -msgid "root password" +#, fuzzy +msgid "Root password" msgstr "روٹ پاس ورڈ" msgid "Superuser account" @@ -424,8 +427,9 @@ msgstr "ترمیم" msgid "Delete" msgstr "حذف" -msgid "Select an action for < {} >" -msgstr "< {} > کے لیے ایک عمل منتخب کریں" +#, fuzzy +msgid "Select an action for '{}'" +msgstr "'{}' کے لیے ایک عمل منتخب کریں" msgid "Copy to new key:" msgstr "نئی کلید میں کاپی کریں:" @@ -672,7 +676,7 @@ msgstr "" msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" msgstr "" -msgid "Choose which servers to install, if none then a minimal installation wil be done" +msgid "Choose which servers to install, if none then a minimal installation will be done" msgstr "" msgid "Installs a minimal system as well as xorg and graphics drivers." @@ -759,6 +763,68 @@ msgstr "" "\n" "فارمیٹنگ کے لیے کس پارٹیشن کو ماسک کرنا ہے" +msgid "Use HSM to unlock encrypted drive" +msgstr "" + +msgid "Device" +msgstr "" + +msgid "Size" +msgstr "" + +msgid "Free space" +msgstr "" + +msgid "Bus-type" +msgstr "" + +#, fuzzy +msgid "Either root-password or at least 1 user with sudo privileges must be specified" +msgstr "روٹ پاس ورڈ یا کم از کم ۱ سپر یوزر کی وضاحت ہونی چاہیے" + +#, fuzzy +msgid "Enter username (leave blank to skip): " +msgstr "ایک اضافی صارف بنانے کے لیے صارف نام درج کریں (چھوڑنے کے لیے خالی چھوڑیں):" + +msgid "The username you entered is invalid. Try again" +msgstr "" + +#, fuzzy +msgid "Should \"{}\" be a superuser (sudo)?" +msgstr "کیا {} کو سپر یوزر (sudoer) ہونا چاہیے؟" + +#, fuzzy +msgid "Select which partitions to encrypt:" +msgstr "" +"{}\n" +"\n" +"منتخب کریں کہ کس پارٹیشن کو انکرپٹڈ یا خفیہ رکھنا ہے" + +msgid "very weak" +msgstr "" + +msgid "weak" +msgstr "" + +msgid "moderate" +msgstr "" + +msgid "strong" +msgstr "" + +msgid "Add subvolume" +msgstr "" + +msgid "Edit subvolume" +msgstr "" + +#, fuzzy +msgid "Delete subvolume" +msgstr "صارف کو حذف کریں" + +#~ msgid "Select disk layout" +#~ msgstr "ڈسک لے آؤٹ کو منتخب کریں" + #~ msgid "Add :" #~ msgstr "شامل:" -- cgit v1.2.3-70-g09d2 From 5c3c1312a49e1c110d4c5825fbb8242868544900 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Tue, 14 Jun 2022 22:38:39 +1000 Subject: Update list manager (#1331) * Rework partition management * Update * Update list manager * Update * Update Co-authored-by: Daniel Girtler --- archinstall/lib/disk/partition.py | 35 ++- archinstall/lib/menu/list_manager.py | 262 +++------------------ .../lib/user_interaction/manage_users_conf.py | 22 +- archinstall/lib/user_interaction/network_conf.py | 20 +- .../lib/user_interaction/partitioning_conf.py | 14 +- .../lib/user_interaction/subvolume_config.py | 32 ++- 6 files changed, 109 insertions(+), 276 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk/partition.py b/archinstall/lib/disk/partition.py index 062c79ab..17c24d57 100644 --- a/archinstall/lib/disk/partition.py +++ b/archinstall/lib/disk/partition.py @@ -17,14 +17,16 @@ from .btrfs.btrfs_helpers import subvolume_info_from_path from .btrfs.btrfssubvolumeinfo import BtrfsSubvolumeInfo class Partition: - def __init__(self, + def __init__( + self, path: str, block_device: BlockDevice, part_id :Optional[str] = None, filesystem :Optional[str] = None, mountpoint :Optional[str] = None, encrypted :bool = False, - autodetect_filesystem :bool = True): + autodetect_filesystem :bool = True + ): if not part_id: part_id = os.path.basename(path) @@ -76,7 +78,30 @@ class Partition: else: return f'Partition(path={self.path}, size={self.size}, PARTUUID={self._safe_uuid}, fs={self.filesystem}{mount_repr})' + def as_json(self) -> Dict[str, Any]: + """ + this is used for the table representation of the partition (see FormattedOutput) + """ + partition_info = { + 'type': 'primary', + 'PARTUUID': self._safe_uuid, + 'wipe': self.allow_formatting, + 'boot': self.boot, + 'ESP': self.boot, + 'mountpoint': self.target_mountpoint, + 'encrypted': self._encrypted, + 'start': self.start, + 'size': self.end, + 'filesystem': self.filesystem_type + } + + return partition_info + def __dump__(self) -> Dict[str, Any]: + # TODO remove this in favour of as_json + + log(get_filesystem_type(self.path)) + return { 'type': 'primary', 'PARTUUID': self._safe_uuid, @@ -88,10 +113,14 @@ class Partition: 'start': self.start, 'size': self.end, 'filesystem': { - 'format': get_filesystem_type(self.path) + 'format': self.filesystem_type } } + @property + def filesystem_type(self) -> Optional[str]: + return get_filesystem_type(self.path) + @property def mountpoint(self) -> Optional[str]: try: diff --git a/archinstall/lib/menu/list_manager.py b/archinstall/lib/menu/list_manager.py index fe491caa..e7a9c2ac 100644 --- a/archinstall/lib/menu/list_manager.py +++ b/archinstall/lib/menu/list_manager.py @@ -1,94 +1,7 @@ -#!/usr/bin/python -""" -# Purpose -ListManager is a widget based on `menu` which allows the handling of repetitive operations in a list. -Imagine you have a list and want to add/copy/edit/delete their elements. With this widget you will be shown the list -``` -Vamos alla - -Use ESC to skip - - -> uno : 1 -dos : 2 -tres : 3 -cuatro : 4 -==> -Confirm and exit -Cancel -(Press "/" to search) -``` -Once you select one of the elements of the list, you will be promted with the action to be done to the selected element -``` - -uno : 1 -dos : 2 -> tres : 3 -cuatro : 4 -==> -Confirm and exit -Cancel -(Press "/" to search) - -Select an action for < {'tres': 3} > - - -Add -Copy -Edit -Delete -> Cancel -``` -You execute the action for this element (which might or not involve user interaction) and return to the list main page -till you call one of the options `confirm and exit` which returns the modified list or `cancel` which returns the original list unchanged. -If the list is empty one action can be defined as default (usually Add). We call it **null_action** -YOu can also define a **default_action** which will appear below the separator, not tied to any element of the list. Barring explicit definition, default_action will be the null_action -``` -==> -Add -Confirm and exit -Cancel -(Press "/" to search) -``` -The default implementation can handle simple lists and a key:value dictionary. The default actions are the shown above. -A sample of basic usage is included at the end of the source. - -More sophisticaded uses can be achieved by -* changing the action list and the null_action during initialization -``` - opciones = ListManager('Vamos alla',opciones,[str(_('Add')),str(_('Delete'))],_('Add')).run() -``` -* And using following methods to overwrite/define user actions and other details: -* * `reformat`. To change the appearance of the list elements -* * `action_list`. To modify the content of the action list once an element is defined. F.i. to avoid Delete to appear for certain elements, or to add/modify action based in the value of the element. -* * `exec_action` which contains the actual code to be executed when an action is selected - -The contents in the base class of this methods serve for a very basic usage, and are to be taken as samples. Thus the best use of this class would be to subclass in your code - -``` - class ObjectList(archinstall.ListManager): - def __init__(prompt,list): - self.ObjectAction = [... list of actions ...] - self.ObjectNullAction = one ObjectAction - super().__init__(prompt,list,ObjectActions,ObjectNullAction) - def reformat(self): - ... beautfy the output of the list - def action_list(self): - ... if you need some changes to the action list based on self.target - def exec_action(self): - if self.action == self.ObjectAction[0]: - performFirstAction(self.target, ...) - - ... - resultList = ObjectList(prompt,originallist).run() -``` - -""" import copy from os import system -from typing import Union, Any, TYPE_CHECKING, Dict, Optional, Tuple, List +from typing import Any, TYPE_CHECKING, Dict, Optional, Tuple, List -from .text_input import TextInput from .menu import Menu if TYPE_CHECKING: @@ -98,58 +11,38 @@ if TYPE_CHECKING: class ListManager: def __init__( self, - prompt :str, - base_list :Union[list,dict] , - base_actions :list = None, - null_action :str = None, - default_action :Union[str,list] = None, - header :Union[str,list] = None): + prompt: str, + entries: List[Any], + base_actions: List[str], + sub_menu_actions: List[str] + ): """ - param :prompt Text which will appear at the header + :param prompt: Text which will appear at the header type param: string | DeferredTranslation - param :base:_list list/dict of option to be shown / mainpulated - type param: list | dict - - param base_actions an alternate list of actions to the items of the object + :param entries: list/dict of option to be shown / manipulated type param: list - param: null_action action which will be taken (if any) when base_list is empty - type param: string - - param: default_action action which will be presented at the bottom of the list. Shouldn't need a target. If not present, null_action is set there. - Both Null and Default actions can be defined outside the base_actions list, as long as they are launched in exec_action - type param: string or list + :param base_actions: list of actions that is displayed in the main list manager, + usually global actions such as 'Add...' + type param: list - param: header one or more header lines for the list - type param: string or list + :param sub_menu_actions: list of actions available for a chosen entry + type param: list """ + self._original_data = copy.deepcopy(entries) + self._data = copy.deepcopy(entries) explainer = str(_('\n Choose an object from the list, and select one of the available actions for it to execute')) self._prompt = prompt + explainer if prompt else explainer - self._null_action = str(null_action) if null_action else None - - if not default_action: - self._default_action = [self._null_action] - elif isinstance(default_action,(list,tuple)): - self._default_action = default_action - else: - self._default_action = [str(default_action)] - - self._header = header if header else '' - self._cancel_action = str(_('Cancel')) - self._confirm_action = str(_('Confirm and exit')) self._separator = '' - self._bottom_list = [self._confirm_action, self._cancel_action] - self._bottom_item = [self._cancel_action] - self._base_actions = base_actions if base_actions else [str(_('Add')), str(_('Copy')), str(_('Edit')), str(_('Delete'))] - self._original_data = copy.deepcopy(base_list) - self._data = copy.deepcopy(base_list) # as refs, changes are immediate + self._confirm_action = str(_('Confirm and exit')) + self._cancel_action = str(_('Cancel')) - # default values for the null case - self.target: Optional[Any] = None - self.action = self._null_action + self._terminate_actions = [self._confirm_action, self._cancel_action] + self._base_actions = base_actions + self._sub_menu_actions = sub_menu_actions def run(self): while True: @@ -158,11 +51,6 @@ class ListManager: data_formatted = self.reformat(self._data) options, header = self._prepare_selection(data_formatted) - menu_header = self._header - - if header: - menu_header += header - system('clear') choice = Menu( @@ -177,27 +65,13 @@ class ListManager: show_search_hint=False ).run() - if not choice.value or choice.value in self._bottom_list: - self.action = choice + if choice.value in self._base_actions: + self._data = self.handle_action(choice.value, None, self._data) + elif choice.value in self._terminate_actions: break - - if choice.value and choice.value in self._default_action: - self.action = choice.value - self.target = None - self._data = self.exec_action(self._data) - continue - - if isinstance(self._data, dict): - data_key = data_formatted[choice.value] - key = self._data[data_key] - self.target = {data_key: key} - elif isinstance(self._data, list): - self.target = [d for d in self._data if d == data_formatted[choice.value]][0] - else: - self.target = self._data[data_formatted[choice.value]] - - # Possible enhancement. If run_actions returns false a message line indicating the failure - self.run_actions(choice.value) + else: # an entry of the existing selection was choosen + selected_entry = data_formatted[choice.value] + self._run_actions_on_entry(selected_entry) if choice.value == self._cancel_action: return self._original_data # return the original list @@ -217,16 +91,14 @@ class ListManager: if len(options) > 0: options.append(self._separator) - if self._default_action: - # done only for mypy -> todo fix the self._default_action declaration - options += [action for action in self._default_action if action] + options += self._base_actions + options += self._terminate_actions - options += self._bottom_list return options, header - def run_actions(self,prompt_data=''): - options = self.action_list() + self._bottom_item - display_value = self.selected_action_display(self.target) if self.target else prompt_data + def _run_actions_on_entry(self, entry: Any): + options = self._sub_menu_actions + [self._cancel_action] + display_value = self.selected_action_display(entry) prompt = _("Select an action for '{}'").format(display_value) @@ -236,14 +108,11 @@ class ListManager: sort=False, clear_screen=False, clear_menu_on_exit=False, - preset_values=self._bottom_item, show_search_hint=False ).run() - self.action = choice.value - - if self.action and self.action != self._cancel_action: - self._data = self.exec_action(self._data) + if choice.value and choice.value != self._cancel_action: + self._data = self.handle_action(choice.value, entry, self._data) def selected_action_display(self, selection: Any) -> str: # this will return the value to be displayed in the @@ -256,64 +125,7 @@ class ListManager: # in the header value (useful when displaying tables) raise NotImplementedError('Please implement me in the child class') - def action_list(self): - """ - can define alternate action list or customize the list for each item. - Executed after any item is selected, contained in self.target - """ - active_entry = self.target if self.target else None - - if active_entry is None: - return [self._base_actions[0]] - else: - return self._base_actions[1:] - - def exec_action(self, data: Any): - """ - what's executed one an item (self.target) and one action (self.action) is selected. - Should be overwritten by the user - The result is expected to update self._data in this routine, else it is ignored - The basic code is useful for simple lists and dictionaries (key:value pairs, both strings) - """ - # TODO guarantee unicity - if isinstance(self._data,list): - if self.action == str(_('Add')): - self.target = TextInput(_('Add: '),None).run() - self._data.append(self.target) - if self.action == str(_('Copy')): - while True: - target = TextInput(_('Copy to: '),self.target).run() - if target != self.target: - self._data.append(self.target) - break - elif self.action == str(_('Edit')): - tgt = self.target - idx = self._data.index(self.target) - result = TextInput(_('Edit: '),tgt).run() - self._data[idx] = result - elif self.action == str(_('Delete')): - del self._data[self._data.index(self.target)] - elif isinstance(self._data,dict): - # allows overwrites - if self.target: - origkey,origval = list(self.target.items())[0] - else: - origkey = None - origval = None - if self.action == str(_('Add')): - key = TextInput(_('Key: '),None).run() - value = TextInput(_('Value: '),None).run() - self._data[key] = value - if self.action == str(_('Copy')): - while True: - key = TextInput(_('Copy to new key:'),origkey).run() - if key != origkey: - self._data[key] = origval - break - elif self.action == str(_('Edit')): - value = TextInput(_('Edit {}: ').format(origkey), origval).run() - self._data[origkey] = value - elif self.action == str(_('Delete')): - del self._data[origkey] - - return self._data + def handle_action(self, action: Any, entry: Optional[Any], data: List[Any]) -> List[Any]: + # this function is called when a base action or + # a specific action for an entry is triggered + raise NotImplementedError('Please implement me in the child class') diff --git a/archinstall/lib/user_interaction/manage_users_conf.py b/archinstall/lib/user_interaction/manage_users_conf.py index 33c31342..a97328c2 100644 --- a/archinstall/lib/user_interaction/manage_users_conf.py +++ b/archinstall/lib/user_interaction/manage_users_conf.py @@ -25,7 +25,7 @@ class UserList(ListManager): str(_('Promote/Demote user')), str(_('Delete User')) ] - super().__init__(prompt, lusers, self._actions, self._actions[0]) + super().__init__(prompt, lusers, [self._actions[0]], self._actions[1:]) def reformat(self, data: List[User]) -> Dict[str, User]: table = FormattedOutput.as_table(data) @@ -45,27 +45,25 @@ class UserList(ListManager): def selected_action_display(self, user: User) -> str: return user.username - def exec_action(self, data: List[User]) -> List[User]: - active_user = self.target if self.target else None - - if self.action == self._actions[0]: # add + def handle_action(self, action: str, entry: Optional[User], data: List[User]) -> List[User]: + if action == self._actions[0]: # add new_user = self._add_user() if new_user is not None: # in case a user with the same username as an existing user # was created we'll replace the existing one data = [d for d in data if d.username != new_user.username] data += [new_user] - elif self.action == self._actions[1]: # change password - prompt = str(_('Password for user "{}": ').format(active_user.username)) + elif action == self._actions[1]: # change password + prompt = str(_('Password for user "{}": ').format(entry.username)) new_password = get_password(prompt=prompt) if new_password: - user = next(filter(lambda x: x == active_user, data), 1) + user = next(filter(lambda x: x == entry, data), 1) user.password = new_password - elif self.action == self._actions[2]: # promote/demote - user = next(filter(lambda x: x == active_user, data), 1) + elif action == self._actions[2]: # promote/demote + user = next(filter(lambda x: x == entry, data), 1) user.sudo = False if user.sudo else True - elif self.action == self._actions[3]: # delete - data = [d for d in data if d != active_user] + elif action == self._actions[3]: # delete + data = [d for d in data if d != entry] return data diff --git a/archinstall/lib/user_interaction/network_conf.py b/archinstall/lib/user_interaction/network_conf.py index 4f22b790..1908603e 100644 --- a/archinstall/lib/user_interaction/network_conf.py +++ b/archinstall/lib/user_interaction/network_conf.py @@ -29,7 +29,7 @@ class ManualNetworkConfig(ListManager): str(_('Delete interface')) ] - super().__init__(prompt, ifaces, self._actions, self._actions[0]) + super().__init__(prompt, ifaces, [self._actions[0]], self._actions[1:]) def reformat(self, data: List[NetworkConfiguration]) -> Dict[str, Optional[NetworkConfiguration]]: table = FormattedOutput.as_table(data) @@ -49,21 +49,19 @@ class ManualNetworkConfig(ListManager): def selected_action_display(self, iface: NetworkConfiguration) -> str: return iface.iface if iface.iface else '' - def exec_action(self, data: List[NetworkConfiguration]): - active_iface: Optional[NetworkConfiguration] = self.target if self.target else None - - if self.action == self._actions[0]: # add + def handle_action(self, action: str, entry: Optional[NetworkConfiguration], data: List[NetworkConfiguration]): + if action == self._actions[0]: # add iface_name = self._select_iface(data) if iface_name: iface = NetworkConfiguration(NicType.MANUAL, iface=iface_name) iface = self._edit_iface(iface) data += [iface] - elif active_iface: - if self.action == self._actions[1]: # edit interface - data = [d for d in data if d.iface != active_iface.iface] - data.append(self._edit_iface(active_iface)) - elif self.action == self._actions[2]: # delete - data = [d for d in data if d != active_iface] + elif entry: + if action == self._actions[1]: # edit interface + data = [d for d in data if d.iface != entry.iface] + data.append(self._edit_iface(entry)) + elif action == self._actions[2]: # delete + data = [d for d in data if d != entry] return data diff --git a/archinstall/lib/user_interaction/partitioning_conf.py b/archinstall/lib/user_interaction/partitioning_conf.py index 0f4784b6..8bf76121 100644 --- a/archinstall/lib/user_interaction/partitioning_conf.py +++ b/archinstall/lib/user_interaction/partitioning_conf.py @@ -154,9 +154,6 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, block_device_struct = {"partitions": [partition.__dump__() for partition in block_device.partitions.values()]} original_layout = copy.deepcopy(block_device_struct) - # Test code: [part.__dump__() for part in block_device.partitions.values()] - # TODO: Squeeze in BTRFS subvolumes here - new_partition = str(_('Create a new partition')) suggest_partition_layout = str(_('Suggest partition layout')) delete_partition = str(_('Delete a partition')) @@ -209,6 +206,7 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, return original_layout elif task == save_and_exit: break + if task == new_partition: from ..disk import valid_parted_position @@ -222,8 +220,9 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, if fs_choice.type_ == MenuSelectionType.Esc: continue - prompt = _('Enter the start sector (percentage or block number, default: {}): ').format( - block_device.first_free_sector) + prompt = str(_('Enter the start sector (percentage or block number, default: {}): ')).format( + block_device.first_free_sector + ) start = input(prompt).strip() if not start.strip(): @@ -232,8 +231,9 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, else: end_suggested = '100%' - prompt = _('Enter the end sector of the partition (percentage or block number, ex: {}): ').format( - end_suggested) + prompt = str(_('Enter the end sector of the partition (percentage or block number, ex: {}): ')).format( + end_suggested + ) end = input(prompt).strip() if not end.strip(): diff --git a/archinstall/lib/user_interaction/subvolume_config.py b/archinstall/lib/user_interaction/subvolume_config.py index e2797bba..94150dee 100644 --- a/archinstall/lib/user_interaction/subvolume_config.py +++ b/archinstall/lib/user_interaction/subvolume_config.py @@ -12,13 +12,13 @@ if TYPE_CHECKING: class SubvolumeList(ListManager): - def __init__(self, prompt: str, current_volumes: List[Subvolume]): + def __init__(self, prompt: str, subvolumes: List[Subvolume]): self._actions = [ str(_('Add subvolume')), str(_('Edit subvolume')), str(_('Delete subvolume')) ] - super().__init__(prompt, current_volumes, self._actions, self._actions[0]) + super().__init__(prompt, subvolumes, [self._actions[0]], self._actions[1:]) def reformat(self, data: List[Subvolume]) -> Dict[str, Optional[Subvolume]]: table = FormattedOutput.as_table(data) @@ -75,13 +75,8 @@ class SubvolumeList(ListManager): return subvolume - def exec_action(self, data: List[Subvolume]) -> List[Subvolume]: - if self.target: - active_subvolume = self.target - else: - active_subvolume = None - - if self.action == self._actions[0]: # add + def handle_action(self, action: str, entry: Optional[Subvolume], data: List[Subvolume]) -> List[Subvolume]: + if action == self._actions[0]: # add new_subvolume = self._add_subvolume() if new_subvolume is not None: @@ -89,14 +84,15 @@ class SubvolumeList(ListManager): # was created we'll replace the existing one data = [d for d in data if d.name != new_subvolume.name] data += [new_subvolume] - elif self.action == self._actions[1]: # edit subvolume - new_subvolume = self._add_subvolume(active_subvolume) - - if new_subvolume is not None: - # we'll remove the original subvolume and add the modified version - data = [d for d in data if d.name != active_subvolume.name and d.name != new_subvolume.name] - data += [new_subvolume] - elif self.action == self._actions[2]: # delete - data = [d for d in data if d != active_subvolume] + elif entry is not None: + if action == self._actions[1]: # edit subvolume + new_subvolume = self._add_subvolume(entry) + + if new_subvolume is not None: + # we'll remove the original subvolume and add the modified version + data = [d for d in data if d.name != entry.name and d.name != new_subvolume.name] + data += [new_subvolume] + elif action == self._actions[2]: # delete + data = [d for d in data if d != entry] return data -- cgit v1.2.3-70-g09d2 From 9194f6d85965f435f8d0ae44ba20e73cc761eb44 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Tue, 26 Jul 2022 18:46:50 +1000 Subject: Cleanup partition (#1333) * Cleanup partition * Update * Remove unused method * Update partitioning * Update * Update * Fix mypy Co-authored-by: Daniel Girtler --- archinstall/lib/disk/blockdevice.py | 10 +- archinstall/lib/disk/btrfs/btrfspartition.py | 19 +- archinstall/lib/disk/filesystem.py | 26 +- archinstall/lib/disk/helpers.py | 10 +- archinstall/lib/disk/partition.py | 429 +++++++++------------ archinstall/lib/luks.py | 6 +- .../lib/user_interaction/partitioning_conf.py | 10 - 7 files changed, 227 insertions(+), 283 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk/blockdevice.py b/archinstall/lib/disk/blockdevice.py index 4e207bf4..736bacbc 100644 --- a/archinstall/lib/disk/blockdevice.py +++ b/archinstall/lib/disk/blockdevice.py @@ -88,9 +88,6 @@ class BlockDevice: raise KeyError(f'{self.info} does not contain information: "{key}"') - def __len__(self) -> int: - return len(self.partitions) - def __lt__(self, left_comparitor :'BlockDevice') -> bool: return self._path < left_comparitor.path @@ -121,6 +118,8 @@ class BlockDevice: def _load_partitions(self): from .partition import Partition + self._partitions.clear() + lsblk_info = self._call_lsblk(self._path) device = lsblk_info['blockdevices'][0] self._partitions.clear() @@ -233,8 +232,6 @@ class BlockDevice: @property def partitions(self) -> Dict[str, 'Partition']: - self._partprobe() - self._load_partitions() return OrderedDict(sorted(self._partitions.items())) @property @@ -282,7 +279,7 @@ class BlockDevice: try: if uuid and partition.uuid and partition.uuid.lower() == uuid.lower(): return partition - elif partuuid and partition.part_uuid.lower() == partuuid.lower(): + elif partuuid and partition.part_uuid and partition.part_uuid.lower() == partuuid.lower(): return partition except DiskError as error: # Most likely a blockdevice that doesn't support or use UUID's @@ -291,6 +288,7 @@ class BlockDevice: pass log(f"uuid {uuid} or {partuuid} not found. Waiting {storage.get('DISK_TIMEOUTS', 1) * count}s for next attempt",level=logging.DEBUG) + self.flush_cache() time.sleep(storage.get('DISK_TIMEOUTS', 1) * count) log(f"Could not find {uuid}/{partuuid} in disk after 5 retries", level=logging.INFO) diff --git a/archinstall/lib/disk/btrfs/btrfspartition.py b/archinstall/lib/disk/btrfs/btrfspartition.py index a05f1527..d04c9b98 100644 --- a/archinstall/lib/disk/btrfs/btrfspartition.py +++ b/archinstall/lib/disk/btrfs/btrfspartition.py @@ -17,22 +17,11 @@ if TYPE_CHECKING: from ...installer import Installer from .btrfssubvolumeinfo import BtrfsSubvolumeInfo + class BTRFSPartition(Partition): def __init__(self, *args, **kwargs): Partition.__init__(self, *args, **kwargs) - def __repr__(self, *args :str, **kwargs :str) -> str: - mount_repr = '' - if self.mountpoint: - mount_repr = f", mounted={self.mountpoint}" - elif self.target_mountpoint: - mount_repr = f", rel_mountpoint={self.target_mountpoint}" - - if self._encrypted: - return f'BTRFSPartition(path={self.path}, size={self.size}, PARTUUID={self._safe_uuid}, parent={self.real_device}, fs={self.filesystem}{mount_repr})' - else: - return f'BTRFSPartition(path={self.path}, size={self.size}, PARTUUID={self._safe_uuid}, fs={self.filesystem}{mount_repr})' - @property def subvolumes(self): for filesystem in findmnt(pathlib.Path(self.path), recurse=True).get('filesystems', []): @@ -40,11 +29,11 @@ class BTRFSPartition(Partition): yield subvolume_info_from_path(filesystem['target']) def iterate_children(struct): - for child in struct.get('children', []): + for c in struct.get('children', []): if '[' in child.get('source', ''): - yield subvolume_info_from_path(child['target']) + yield subvolume_info_from_path(c['target']) - for sub_child in iterate_children(child): + for sub_child in iterate_children(c): yield sub_child for child in iterate_children(filesystem): diff --git a/archinstall/lib/disk/filesystem.py b/archinstall/lib/disk/filesystem.py index 1c7a801b..90656308 100644 --- a/archinstall/lib/disk/filesystem.py +++ b/archinstall/lib/disk/filesystem.py @@ -210,7 +210,14 @@ class Filesystem: # TODO: Implement this with declarative profiles instead. raise ValueError("Installation().use_entire_disk() has to be re-worked.") - def add_partition(self, partition_type :str, start :str, end :str, partition_format :Optional[str] = None, skip_mklabel :bool = False) -> Partition: + def add_partition( + self, + partition_type :str, + start :str, + end :str, + partition_format :Optional[str] = None, + skip_mklabel :bool = False + ) -> Partition: log(f'Adding partition to {self.blockdevice}, {start}->{end}', level=logging.INFO) if len(self.blockdevice.partitions) == 0 and skip_mklabel is False: @@ -232,6 +239,7 @@ class Filesystem: except DiskError: pass + # TODO this check should probably run in the setup process rather than during the installation if self.mode == MBR: if len(self.blockdevice.partitions) > 3: DiskError("Too many partitions on disk, MBR disks can only have 3 primary partitions") @@ -246,14 +254,9 @@ class Filesystem: if self.parted(parted_string): for count in range(storage.get('DISK_RETRY_ATTEMPTS', 3)): self.partprobe() + self.blockdevice.flush_cache() - new_partition_uuids = [] - for partition in self.blockdevice.partitions.values(): - try: - new_partition_uuids.append(partition.part_uuid) - except DiskError: - pass - + new_partition_uuids = [partition.part_uuid for partition in self.blockdevice.partitions.values()] new_partuuid_set = (set(previous_partuuids) ^ set(new_partition_uuids)) if len(new_partuuid_set) and (new_partuuid := new_partuuid_set.pop()): @@ -263,17 +266,20 @@ class Filesystem: log(f'Blockdevice: {self.blockdevice}', level=logging.ERROR, fg="red") log(f'Partitions: {self.blockdevice.partitions}', level=logging.ERROR, fg="red") log(f'Partition set: {new_partuuid_set}', level=logging.ERROR, fg="red") - log(f'New UUID: {[new_partuuid]}', level=logging.ERROR, fg="red") + log(f'New PARTUUID: {[new_partuuid]}', level=logging.ERROR, fg="red") log(f'get_partition(): {self.blockdevice.get_partition}', level=logging.ERROR, fg="red") raise err else: log(f"Could not get UUID for partition. Waiting {storage.get('DISK_TIMEOUTS', 1) * count}s before retrying.",level=logging.DEBUG) time.sleep(storage.get('DISK_TIMEOUTS', 1) * count) + total_partitions = set([partition.part_uuid for partition in self.blockdevice.partitions.values()]) + total_partitions.update(previous_partuuids) + # TODO: This should never be able to happen log(f"Could not find the new PARTUUID after adding the partition.", level=logging.ERROR, fg="red") log(f"Previous partitions: {previous_partuuids}", level=logging.ERROR, fg="red") - log(f"New partitions: {(previous_partuuids ^ {partition.part_uuid for partition in self.blockdevice.partitions.values()})}", level=logging.ERROR, fg="red") + log(f"New partitions: {total_partitions}", level=logging.ERROR, fg="red") raise DiskError(f"Could not add partition using: {parted_string}") def set_name(self, partition: int, name: str) -> bool: diff --git a/archinstall/lib/disk/helpers.py b/archinstall/lib/disk/helpers.py index 660594ed..c8ac564e 100644 --- a/archinstall/lib/disk/helpers.py +++ b/archinstall/lib/disk/helpers.py @@ -370,7 +370,7 @@ def get_all_targets(data :Dict[str, Any], filters :Dict[str, None] = {}) -> Dict return filters -def get_partitions_in_use(mountpoint :str) -> List[Partition]: +def get_partitions_in_use(mountpoint :str) -> Dict[str, Any]: from .partition import Partition try: @@ -393,8 +393,12 @@ def get_partitions_in_use(mountpoint :str) -> List[Partition]: if not type(blockdev) in (Partition, MapperDev): continue - for blockdev_mountpoint in blockdev.mount_information: - block_devices_mountpoints[blockdev_mountpoint['target']] = blockdev + if isinstance(blockdev, Partition): + for blockdev_mountpoint in blockdev.mountpoints: + block_devices_mountpoints[blockdev_mountpoint] = blockdev + else: + for blockdev_mountpoint in blockdev.mount_information: + block_devices_mountpoints[blockdev_mountpoint['target']] = blockdev log(f'Filtering available mounts {block_devices_mountpoints} to those under {mountpoint}', level=logging.DEBUG) diff --git a/archinstall/lib/disk/partition.py b/archinstall/lib/disk/partition.py index 17c24d57..4028f114 100644 --- a/archinstall/lib/disk/partition.py +++ b/archinstall/lib/disk/partition.py @@ -1,14 +1,16 @@ import glob -import pathlib import time import logging import json import os import hashlib +import typing +from dataclasses import dataclass +from pathlib import Path from typing import Optional, Dict, Any, List, Union, Iterator from .blockdevice import BlockDevice -from .helpers import find_mountpoint, get_filesystem_type, convert_size_to_gb, split_bind_name +from .helpers import get_filesystem_type, convert_size_to_gb, split_bind_name from ..storage import storage from ..exceptions import DiskError, SysCallError, UnknownFilesystemFormat from ..output import log @@ -16,6 +18,26 @@ from ..general import SysCommand from .btrfs.btrfs_helpers import subvolume_info_from_path from .btrfs.btrfssubvolumeinfo import BtrfsSubvolumeInfo + +@dataclass +class PartitionInfo: + pttype: str + partuuid: str + uuid: str + start: Optional[int] + end: Optional[int] + bootable: bool + size: float + sector_size: int + filesystem_type: str + mountpoints: List[Path] + + def get_first_mountpoint(self) -> Optional[Path]: + if len(self.mountpoints) > 0: + return self.mountpoints[0] + return None + + class Partition: def __init__( self, @@ -25,38 +47,37 @@ class Partition: filesystem :Optional[str] = None, mountpoint :Optional[str] = None, encrypted :bool = False, - autodetect_filesystem :bool = True + autodetect_filesystem :bool = True, ): - if not part_id: part_id = os.path.basename(path) - self.block_device = block_device - if type(self.block_device) is str: + if type(block_device) is str: raise ValueError(f"Partition()'s 'block_device' parameter has to be a archinstall.BlockDevice() instance!") - self.path = path - self.part_id = part_id - self.target_mountpoint = mountpoint - self.filesystem = filesystem + self.block_device = block_device + self._path = path + self._part_id = part_id + self._target_mountpoint = mountpoint self._encrypted = None - self.encrypted = encrypted - self.allow_formatting = False + self._encrypted = encrypted + self._wipe = False + self._type = 'primary' if mountpoint: self.mount(mountpoint) - try: - self.mount_information = list(find_mountpoint(self.path)) - except DiskError: - self.mount_information = [{}] + self._partition_info = self._fetch_information() - if not self.filesystem and autodetect_filesystem: - self.filesystem = get_filesystem_type(path) + if not autodetect_filesystem and filesystem: + self._partition_info.filesystem_type = filesystem - if self.filesystem == 'crypto_LUKS': - self.encrypted = True + if self._partition_info.filesystem_type == 'crypto_LUKS': + self._encrypted = True + # I hate doint this but I'm currently unsure where this + # is acutally used to be able to fix the typing issues properly + @typing.no_type_check def __lt__(self, left_comparitor :BlockDevice) -> bool: if type(left_comparitor) == Partition: left_comparitor = left_comparitor.path @@ -64,254 +85,191 @@ class Partition: left_comparitor = str(left_comparitor) # The goal is to check if /dev/nvme0n1p1 comes before /dev/nvme0n1p5 - return self.path < left_comparitor + return self._path < left_comparitor def __repr__(self, *args :str, **kwargs :str) -> str: mount_repr = '' - if self.mountpoint: - mount_repr = f", mounted={self.mountpoint}" - elif self.target_mountpoint: - mount_repr = f", rel_mountpoint={self.target_mountpoint}" + if mountpoint := self._partition_info.get_first_mountpoint(): + mount_repr = f", mounted={mountpoint}" + elif self._target_mountpoint: + mount_repr = f", rel_mountpoint={self._target_mountpoint}" + + classname = self.__class__.__name__ if self._encrypted: - return f'Partition(path={self.path}, size={self.size}, PARTUUID={self._safe_uuid}, parent={self.real_device}, fs={self.filesystem}{mount_repr})' + return f'{classname}(path={self._path}, size={self.size}, PARTUUID={self.part_uuid}, parent={self.real_device}, fs={self._partition_info.filesystem_type}{mount_repr})' else: - return f'Partition(path={self.path}, size={self.size}, PARTUUID={self._safe_uuid}, fs={self.filesystem}{mount_repr})' + return f'{classname}(path={self._path}, size={self.size}, PARTUUID={self.part_uuid}, fs={self._partition_info.filesystem_type}{mount_repr})' def as_json(self) -> Dict[str, Any]: """ this is used for the table representation of the partition (see FormattedOutput) """ partition_info = { - 'type': 'primary', - 'PARTUUID': self._safe_uuid, - 'wipe': self.allow_formatting, + 'type': self._type, + 'PARTUUID': self.part_uuid, + 'wipe': self._wipe, 'boot': self.boot, 'ESP': self.boot, - 'mountpoint': self.target_mountpoint, + 'mountpoint': self._target_mountpoint, 'encrypted': self._encrypted, 'start': self.start, 'size': self.end, - 'filesystem': self.filesystem_type + 'filesystem': self._partition_info.filesystem_type } return partition_info def __dump__(self) -> Dict[str, Any]: # TODO remove this in favour of as_json - - log(get_filesystem_type(self.path)) - return { - 'type': 'primary', - 'PARTUUID': self._safe_uuid, - 'wipe': self.allow_formatting, + 'type': self._type, + 'PARTUUID': self.part_uuid, + 'wipe': self._wipe, 'boot': self.boot, 'ESP': self.boot, - 'mountpoint': self.target_mountpoint, + 'mountpoint': self._target_mountpoint, 'encrypted': self._encrypted, 'start': self.start, 'size': self.end, 'filesystem': { - 'format': self.filesystem_type + 'format': self._partition_info.filesystem_type } } - @property - def filesystem_type(self) -> Optional[str]: - return get_filesystem_type(self.path) + def _call_lsblk(self) -> Dict[str, Any]: + self.partprobe() + output = SysCommand(f"lsblk --json -b -o+LOG-SEC,SIZE,PTTYPE,PARTUUID,UUID,FSTYPE {self.device_path}").decode('UTF-8') - @property - def mountpoint(self) -> Optional[str]: - try: - data = json.loads(SysCommand(f"findmnt --json -R {self.path}").decode()) - for filesystem in data['filesystems']: - return pathlib.Path(filesystem.get('target')) + if output: + lsblk_info = json.loads(output) + return lsblk_info - except SysCallError as error: - # Not mounted anywhere most likely - log(f"Could not locate mount information for {self.path}: {error}", level=logging.DEBUG, fg="grey") - pass - - return None + raise DiskError(f'Failed to read disk "{self.device_path}" with lsblk') - @property - def sector_size(self) -> Optional[int]: - output = json.loads(SysCommand(f"lsblk --json -o+LOG-SEC {self.device_path}").decode('UTF-8')) + def _call_sfdisk(self) -> Dict[str, Any]: + output = SysCommand(f"sfdisk --json {self.block_device.path}").decode('UTF-8') - for device in output['blockdevices']: - return device.get('log-sec', None) + if output: + sfdisk_info = json.loads(output) + partitions = sfdisk_info.get('partitiontable', {}).get('partitions', []) + node = list(filter(lambda x: x['node'] == self._path, partitions)) - @property - def start(self) -> Optional[str]: - output = json.loads(SysCommand(f"sfdisk --json {self.block_device.path}").decode('UTF-8')) + if len(node) > 0: + return node[0] - for partition in output.get('partitiontable', {}).get('partitions', []): - if partition['node'] == self.path: - return partition['start'] # * self.sector_size + return {} - @property - def end(self) -> Optional[str]: - # TODO: actually this is size in sectors unit - # TODO: Verify that the logic holds up, that 'size' is the size without 'start' added to it. - output = json.loads(SysCommand(f"sfdisk --json {self.block_device.path}").decode('UTF-8')) + raise DiskError(f'Failed to read disk "{self.block_device.path}" with sfdisk') - for partition in output.get('partitiontable', {}).get('partitions', []): - if partition['node'] == self.path: - return partition['size'] # * self.sector_size + def _fetch_information(self) -> PartitionInfo: + lsblk_info = self._call_lsblk() + sfdisk_info = self._call_sfdisk() + device = lsblk_info['blockdevices'][0] - @property - def end_sectors(self) -> Optional[str]: - output = json.loads(SysCommand(f"sfdisk --json {self.block_device.path}").decode('UTF-8')) + mountpoints = [Path(mountpoint) for mountpoint in device['mountpoints'] if mountpoint] + bootable = sfdisk_info.get('bootable', False) or sfdisk_info.get('type', '') == 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B' - for partition in output.get('partitiontable', {}).get('partitions', []): - if partition['node'] == self.path: - return partition['start'] + partition['size'] + return PartitionInfo( + pttype=device['pttype'], + partuuid=device['partuuid'], + uuid=device['uuid'], + sector_size=device['log-sec'], + size=convert_size_to_gb(device['size']), + start=sfdisk_info.get('start', None), + end=sfdisk_info.get('size', None), + bootable=bootable, + filesystem_type=device['fstype'], + mountpoints=mountpoints + ) @property - def size(self) -> Optional[float]: - for i in range(storage['DISK_RETRY_ATTEMPTS']): - self.partprobe() - time.sleep(max(0.1, storage['DISK_TIMEOUTS'] * i)) - - try: - lsblk = json.loads(SysCommand(f"lsblk --json -b -o+SIZE {self.device_path}").decode()) - - for device in lsblk['blockdevices']: - return convert_size_to_gb(device['size']) - except SysCallError as error: - if error.exit_code == 8192: - return None - else: - raise error + def target_mountpoint(self) -> Optional[str]: + return self._target_mountpoint @property - def boot(self) -> bool: - output = json.loads(SysCommand(f"sfdisk --json {self.block_device.path}").decode('UTF-8')) - - for partition in output.get('partitiontable', {}).get('partitions', []): - if partition['node'] == self.path: - # first condition is for MBR disks, second for GPT disks - return partition.get('bootable', False) or partition.get('type','') == 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B' - - return False + def path(self) -> str: + return self._path @property - def partition_type(self) -> Optional[str]: - lsblk = json.loads(SysCommand(f"lsblk --json -o+PTTYPE {self.device_path}").decode('UTF-8')) - - for device in lsblk['blockdevices']: - return device['pttype'] + def filesystem(self) -> str: + return self._partition_info.filesystem_type @property - def part_uuid(self) -> str: - """ - Returns the PARTUUID as returned by lsblk. - This is more reliable than relying on /dev/disk/by-partuuid as - it doesn't seam to be able to detect md raid partitions. - For bind mounts all the subvolumes share the same uuid - """ - for i in range(storage['DISK_RETRY_ATTEMPTS']): - if not self.partprobe(): - raise DiskError(f"Could not perform partprobe on {self.device_path}") - - time.sleep(max(0.1, storage['DISK_TIMEOUTS'] * i)) - - partuuid = self._safe_part_uuid - if partuuid: - return partuuid - - raise DiskError(f"Could not get PARTUUID for {self.path} using 'blkid -s PARTUUID -o value {self.path}'") + def mountpoint(self) -> Optional[Path]: + if len(self.mountpoints) > 0: + return self.mountpoints[0] + return None @property - def uuid(self) -> Optional[str]: - """ - Returns the UUID as returned by lsblk for the **partition**. - This is more reliable than relying on /dev/disk/by-uuid as - it doesn't seam to be able to detect md raid partitions. - For bind mounts all the subvolumes share the same uuid - """ - for i in range(storage['DISK_RETRY_ATTEMPTS']): - if not self.partprobe(): - raise DiskError(f"Could not perform partprobe on {self.device_path}") + def mountpoints(self) -> List[Path]: + return self._partition_info.mountpoints - time.sleep(storage.get('DISK_TIMEOUTS', 1) * i) - - partuuid = self._safe_uuid - if partuuid: - return partuuid - - raise DiskError(f"Could not get PARTUUID for {self.path} using 'blkid -s PARTUUID -o value {self.path}'") + @property + def sector_size(self) -> int: + return self._partition_info.sector_size @property - def _safe_uuid(self) -> Optional[str]: - """ - A near copy of self.uuid but without any delays. - This function should only be used where uuid is not crucial. - For instance when you want to get a __repr__ of the class. - """ - if not self.partprobe(): - if self.block_device.partition_type == 'iso9660': - return None + def start(self) -> Optional[int]: + return self._partition_info.start - log(f"Could not reliably refresh PARTUUID of partition {self.device_path} due to partprobe error.", level=logging.DEBUG) + @property + def end(self) -> Optional[int]: + return self._partition_info.end - try: - return SysCommand(f'blkid -s UUID -o value {self.device_path}').decode('UTF-8').strip() - except SysCallError as error: - if self.block_device.partition_type == 'iso9660': - # Parent device is a Optical Disk (.iso dd'ed onto a device for instance) - return None + @property + def end_sectors(self) -> Optional[int]: + start = self._partition_info.start + end = self._partition_info.end + if start and end: + return start + end + return None - log(f"Could not get PARTUUID of partition using 'blkid -s UUID -o value {self.device_path}': {error}") + @property + def size(self) -> Optional[float]: + return self._partition_info.size @property - def _safe_part_uuid(self) -> Optional[str]: - """ - A near copy of self.uuid but without any delays. - This function should only be used where uuid is not crucial. - For instance when you want to get a __repr__ of the class. - """ - if not self.partprobe(): - if self.block_device.partition_type == 'iso9660': - return None + def boot(self) -> bool: + return self._partition_info.bootable - log(f"Could not reliably refresh PARTUUID of partition {self.device_path} due to partprobe error.", level=logging.DEBUG) + @property + def partition_type(self) -> Optional[str]: + return self._partition_info.pttype - try: - return self.block_device.uuid - except SysCallError as error: - if self.block_device.partition_type == 'iso9660': - # Parent device is a Optical Disk (.iso dd'ed onto a device for instance) - return None + @property + def part_uuid(self) -> str: + return self._partition_info.partuuid - log(f"Could not get PARTUUID of partition using 'blkid -s PARTUUID -o value {self.device_path}': {error}") + @property + def uuid(self) -> Optional[str]: + return self._partition_info.uuid @property def encrypted(self) -> Union[bool, None]: return self._encrypted - @encrypted.setter - def encrypted(self, value: bool) -> None: - self._encrypted = value - @property def parent(self) -> str: return self.real_device @property def real_device(self) -> str: - for blockdevice in json.loads(SysCommand('lsblk -J').decode('UTF-8'))['blockdevices']: - if parent := self.find_parent_of(blockdevice, os.path.basename(self.device_path)): - return f"/dev/{parent}" - # raise DiskError(f'Could not find appropriate parent for encrypted partition {self}') - return self.path + output = SysCommand('lsblk -J').decode('UTF-8') + + if output: + for blockdevice in json.loads(output)['blockdevices']: + if parent := self.find_parent_of(blockdevice, os.path.basename(self.device_path)): + return f"/dev/{parent}" + return self._path + + raise DiskError('Unable to get disk information for command "lsblk -J"') @property def device_path(self) -> str: - """ for bind mounts returns the phisical path of the partition + """ for bind mounts returns the physical path of the partition """ - device_path, bind_name = split_bind_name(self.path) + device_path, bind_name = split_bind_name(self._path) return device_path @property @@ -319,7 +277,7 @@ class Partition: """ for bind mounts returns the bind name (subvolume path). Returns none if this property does not exist """ - device_path, bind_name = split_bind_name(self.path) + device_path, bind_name = split_bind_name(self._path) return bind_name @property @@ -330,29 +288,29 @@ class Partition: for child in information.get('children', []): if target := child.get('target'): if child.get('fstype') == 'btrfs': - if subvolume := subvolume_info_from_path(pathlib.Path(target)): + if subvolume := subvolume_info_from_path(Path(target)): yield subvolume if child.get('children'): for subchild in iterate_children_recursively(child): yield subchild - for mountpoint in self.mount_information: - if result := findmnt(pathlib.Path(mountpoint['target'])): - for filesystem in result.get('filesystems', []): - if mountpoint.get('fstype') == 'btrfs': - if subvolume := subvolume_info_from_path(pathlib.Path(mountpoint['target'])): + if self._partition_info.filesystem_type == 'btrfs': + for mountpoint in self._partition_info.mountpoints: + if result := findmnt(mountpoint): + for filesystem in result.get('filesystems', []): + if subvolume := subvolume_info_from_path(mountpoint): yield subvolume - for child in iterate_children_recursively(filesystem): - yield child + for child in iterate_children_recursively(filesystem): + yield child def partprobe(self) -> bool: try: if self.block_device: return 0 == SysCommand(f'partprobe {self.block_device.device}').exit_code except SysCallError as error: - log(f"Unreliable results might be given for {self.path} due to partprobe error: {error}", level=logging.DEBUG) + log(f"Unreliable results might be given for {self._path} due to partprobe error: {error}", level=logging.DEBUG) return False @@ -364,19 +322,20 @@ class Partition: with luks2(self, storage.get('ENC_IDENTIFIER', 'ai') + 'loop', password, auto_unmount=True) as unlocked_device: return unlocked_device.filesystem except SysCallError: - return None + pass + return None def has_content(self) -> bool: - fs_type = get_filesystem_type(self.path) + fs_type = self._partition_info.filesystem_type if not fs_type or "swap" in fs_type: return False temporary_mountpoint = '/tmp/' + hashlib.md5(bytes(f"{time.time()}", 'UTF-8') + os.urandom(12)).hexdigest() - temporary_path = pathlib.Path(temporary_mountpoint) + temporary_path = Path(temporary_mountpoint) temporary_path.mkdir(parents=True, exist_ok=True) - if (handle := SysCommand(f'/usr/bin/mount {self.path} {temporary_mountpoint}')).exit_code != 0: - raise DiskError(f'Could not mount and check for content on {self.path} because: {b"".join(handle)}') + if (handle := SysCommand(f'/usr/bin/mount {self._path} {temporary_mountpoint}')).exit_code != 0: + raise DiskError(f'Could not mount and check for content on {self._path} because: {handle}') files = len(glob.glob(f"{temporary_mountpoint}/*")) iterations = 0 @@ -387,14 +346,14 @@ class Partition: return True if files > 0 else False - def encrypt(self, *args :str, **kwargs :str) -> str: + def encrypt(self, password: Optional[str] = None) -> str: """ A wrapper function for luks2() instances and the .encrypt() method of that instance. """ from ..luks import luks2 handle = luks2(self, None, None) - return handle.encrypt(self, *args, **kwargs) + return handle.encrypt(self, password=password) def format(self, filesystem :Optional[str] = None, path :Optional[str] = None, log_formatting :bool = True, options :List[str] = [], retry :bool = True) -> bool: """ @@ -402,17 +361,17 @@ class Partition: the formatting functionality and in essence the support for the given filesystem. """ if filesystem is None: - filesystem = self.filesystem + filesystem = self._partition_info.filesystem_type if path is None: - path = self.path + path = self._path # This converts from fat32 -> vfat to unify filesystem names filesystem = get_mount_fs_type(filesystem) # To avoid "unable to open /dev/x: No such file or directory" start_wait = time.time() - while pathlib.Path(path).exists() is False and time.time() - start_wait < 10: + while Path(path).exists() is False and time.time() - start_wait < 10: time.sleep(0.025) if log_formatting: @@ -422,57 +381,57 @@ class Partition: if filesystem == 'btrfs': options = ['-f'] + options - if 'UUID:' not in (mkfs := SysCommand(f"/usr/bin/mkfs.btrfs {' '.join(options)} {path}").decode('UTF-8')): + mkfs = SysCommand(f"/usr/bin/mkfs.btrfs {' '.join(options)} {path}").decode('UTF-8') + if mkfs and 'UUID:' not in mkfs: raise DiskError(f'Could not format {path} with {filesystem} because: {mkfs}') - self.filesystem = filesystem + self._partition_info.filesystem_type = filesystem elif filesystem == 'vfat': options = ['-F32'] + options log(f"/usr/bin/mkfs.vfat {' '.join(options)} {path}") if (handle := SysCommand(f"/usr/bin/mkfs.vfat {' '.join(options)} {path}")).exit_code != 0: raise DiskError(f"Could not format {path} with {filesystem} because: {handle.decode('UTF-8')}") - self.filesystem = filesystem + self._partition_info.filesystem_type = filesystem elif filesystem == 'ext4': options = ['-F'] + options if (handle := SysCommand(f"/usr/bin/mkfs.ext4 {' '.join(options)} {path}")).exit_code != 0: raise DiskError(f"Could not format {path} with {filesystem} because: {handle.decode('UTF-8')}") - self.filesystem = filesystem + self._partition_info.filesystem_type = filesystem elif filesystem == 'ext2': options = ['-F'] + options if (handle := SysCommand(f"/usr/bin/mkfs.ext2 {' '.join(options)} {path}")).exit_code != 0: - raise DiskError(f'Could not format {path} with {filesystem} because: {b"".join(handle)}') - self.filesystem = 'ext2' - + raise DiskError(f"Could not format {path} with {filesystem} because: {handle.decode('UTF-8')}") + self._partition_info.filesystem_type = 'ext2' elif filesystem == 'xfs': options = ['-f'] + options if (handle := SysCommand(f"/usr/bin/mkfs.xfs {' '.join(options)} {path}")).exit_code != 0: raise DiskError(f"Could not format {path} with {filesystem} because: {handle.decode('UTF-8')}") - self.filesystem = filesystem + self._partition_info.filesystem_type = filesystem elif filesystem == 'f2fs': options = ['-f'] + options if (handle := SysCommand(f"/usr/bin/mkfs.f2fs {' '.join(options)} {path}")).exit_code != 0: raise DiskError(f"Could not format {path} with {filesystem} because: {handle.decode('UTF-8')}") - self.filesystem = filesystem + self._partition_info.filesystem_type = filesystem elif filesystem == 'ntfs3': options = ['-f'] + options if (handle := SysCommand(f"/usr/bin/mkfs.ntfs -Q {' '.join(options)} {path}")).exit_code != 0: raise DiskError(f"Could not format {path} with {filesystem} because: {handle.decode('UTF-8')}") - self.filesystem = filesystem + self._partition_info.filesystem_type = filesystem elif filesystem == 'crypto_LUKS': # from ..luks import luks2 # encrypted_partition = luks2(self, None, None) # encrypted_partition.format(path) - self.filesystem = filesystem + self._partition_info.filesystem_type = filesystem else: raise UnknownFilesystemFormat(f"Fileformat '{filesystem}' is not yet implemented.") @@ -485,9 +444,9 @@ class Partition: return self.format(filesystem, path, log_formatting, options, retry=False) if get_filesystem_type(path) == 'crypto_LUKS' or get_filesystem_type(self.real_device) == 'crypto_LUKS': - self.encrypted = True + self._encrypted = True else: - self.encrypted = False + self._encrypted = False return True @@ -499,18 +458,18 @@ class Partition: if parent := self.find_parent_of(child, name, parent=data['name']): return parent + return None + def mount(self, target :str, fs :Optional[str] = None, options :str = '') -> bool: - if not self.mountpoint: + if not self._partition_info.get_first_mountpoint(): log(f'Mounting {self} to {target}', level=logging.INFO) if not fs: - if not self.filesystem: - raise DiskError(f'Need to format (or define) the filesystem on {self} before mounting.') - fs = self.filesystem + fs = self._partition_info.filesystem_type fs_type = get_mount_fs_type(fs) - pathlib.Path(target).mkdir(parents=True, exist_ok=True) + Path(target).mkdir(parents=True, exist_ok=True) if self.bind_name: device_path = self.device_path @@ -520,7 +479,7 @@ class Partition: else: options = f"subvol={self.bind_name}" else: - device_path = self.path + device_path = self._path try: if options: mnt_handle = SysCommand(f"/usr/bin/mount -t {fs_type} -o {options} {device_path} {target}") @@ -529,7 +488,7 @@ class Partition: # TODO: Should be redundant to check for exit_code if mnt_handle.exit_code != 0: - raise DiskError(f"Could not mount {self.path} to {target} using options {options}") + raise DiskError(f"Could not mount {self._path} to {target} using options {options}") except SysCallError as err: raise err @@ -538,19 +497,17 @@ class Partition: return False def unmount(self) -> bool: - worker = SysCommand(f"/usr/bin/umount {self.path}") + worker = SysCommand(f"/usr/bin/umount {self._path}") + exit_code = worker.exit_code # Without to much research, it seams that low error codes are errors. # And above 8k is indicators such as "/dev/x not mounted.". # So anything in between 0 and 8k are errors (?). - if 0 < worker.exit_code < 8000: - raise SysCallError(f"Could not unmount {self.path} properly: {worker}", exit_code=worker.exit_code) + if exit_code and 0 < exit_code < 8000: + raise SysCallError(f"Could not unmount {self._path} properly: {worker}", exit_code=exit_code) return True - def umount(self) -> bool: - return self.unmount() - def filesystem_supported(self) -> bool: """ The support for a filesystem (this partition) is tested by calling @@ -559,7 +516,7 @@ class Partition: 2. UnknownFilesystemFormat that indicates that we don't support the given filesystem type """ try: - self.format(self.filesystem, '/dev/null', log_formatting=False, allow_formatting=True) + self.format(self._partition_info.filesystem_type, '/dev/null', log_formatting=False) except (SysCallError, DiskError): pass # We supported it, but /dev/null is not formattable as expected so the mkfs call exited with an error code except UnknownFilesystemFormat as err: diff --git a/archinstall/lib/luks.py b/archinstall/lib/luks.py index ac480b11..7e4534d8 100644 --- a/archinstall/lib/luks.py +++ b/archinstall/lib/luks.py @@ -22,9 +22,9 @@ from .disk.btrfs import BTRFSPartition class luks2: def __init__(self, - partition :Partition, - mountpoint :str, - password :str, + partition: Partition, + mountpoint: Optional[str], + password: Optional[str], key_file :Optional[str] = None, auto_unmount :bool = False, *args :str, diff --git a/archinstall/lib/user_interaction/partitioning_conf.py b/archinstall/lib/user_interaction/partitioning_conf.py index 8bf76121..f2e6b881 100644 --- a/archinstall/lib/user_interaction/partitioning_conf.py +++ b/archinstall/lib/user_interaction/partitioning_conf.py @@ -140,16 +140,6 @@ def get_default_partition_layout( return suggest_multi_disk_layout(block_devices, advanced_options=advanced_options) -def select_individual_blockdevice_usage(block_devices: list) -> Dict[str, Any]: - result = {} - - for device in block_devices: - layout = manage_new_and_existing_partitions(device) - result[device.path] = layout - - return result - - def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, Any]: # noqa: max-complexity: 50 block_device_struct = {"partitions": [partition.__dump__() for partition in block_device.partitions.values()]} original_layout = copy.deepcopy(block_device_struct) -- cgit v1.2.3-70-g09d2 From d2f58362c9035be57670784584a666eea8c09e60 Mon Sep 17 00:00:00 2001 From: Fabian Bornschein <18744080+fabiscafe@users.noreply.github.com> Date: Mon, 1 Aug 2022 09:42:58 +0200 Subject: Remove the hash/number sign at the line-start (#1345) Currently the helper remove the first character of each line, this can lead to cases, where important characters are removed. For example if the locale is already set up. (hash is already removed) in that case the helper would remove the first character of the locale and lead to a broken attempt to set the locale later on. This change should avoid that and only remove the hash. Co-authored-by: Fabian Bornschein <2440175-fabiscafe@users.noreply.gitlab.com> --- archinstall/lib/locale_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/locale_helpers.py b/archinstall/lib/locale_helpers.py index b48c3bc4..5580fa91 100644 --- a/archinstall/lib/locale_helpers.py +++ b/archinstall/lib/locale_helpers.py @@ -20,7 +20,7 @@ def list_locales() -> List[str]: entries.reverse() for entry in entries: - text = entry[1:].strip() + text = entry.replace('#', '').strip() if text == '': break locales.append(text) -- cgit v1.2.3-70-g09d2 From 3bc39225458b32fcd43b8a5b76dbb6797723a86c Mon Sep 17 00:00:00 2001 From: Werner Llácer Date: Mon, 1 Aug 2022 09:44:26 +0200 Subject: Enhacements to list_manager: (#1346) * Enhacements to list_manager: method filter_option. To filter options based on selected entry attrib. last_choice. Which is the last action executed before exiting the loop * last_choice is now a calculated attribute, therefore readonly * Added last_choice to selection_menu * bug at selection_menu handling. Translations can be a problem --- archinstall/lib/menu/list_manager.py | 13 ++++++++++++- archinstall/lib/menu/selection_menu.py | 9 +++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/menu/list_manager.py b/archinstall/lib/menu/list_manager.py index e7a9c2ac..ae3a6eb5 100644 --- a/archinstall/lib/menu/list_manager.py +++ b/archinstall/lib/menu/list_manager.py @@ -44,6 +44,12 @@ class ListManager: self._base_actions = base_actions self._sub_menu_actions = sub_menu_actions + self._last_choice = None + + @property + def last_choice(self): + return self._last_choice + def run(self): while True: # this will return a dictionary with the key as the menu entry to be displayed @@ -73,6 +79,7 @@ class ListManager: selected_entry = data_formatted[choice.value] self._run_actions_on_entry(selected_entry) + self._last_choice = choice if choice.value == self._cancel_action: return self._original_data # return the original list else: @@ -97,7 +104,7 @@ class ListManager: return options, header def _run_actions_on_entry(self, entry: Any): - options = 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) @@ -129,3 +136,7 @@ class ListManager: # this function is called when a base action or # a specific action for an entry is triggered raise NotImplementedError('Please implement me in the child class') + + def filter_options(self, selection :Any, options :List[str]) -> List[str]: + # filter which actions to show for an specific selection + return options diff --git a/archinstall/lib/menu/selection_menu.py b/archinstall/lib/menu/selection_menu.py index 8dd6fcce..c6ac5852 100644 --- a/archinstall/lib/menu/selection_menu.py +++ b/archinstall/lib/menu/selection_menu.py @@ -188,6 +188,11 @@ class GeneralMenu: 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 @@ -325,6 +330,10 @@ class GeneralMenu: 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__() -- cgit v1.2.3-70-g09d2 From cfea0d6d1a6f6b82fd4b65abd2124c8fc0530949 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Mon, 1 Aug 2022 17:44:57 +1000 Subject: Update translations (#1348) * Show translations in own tongue * Fix flake8 * Update * Update * Update * Update * fix mypy * Update * Update Co-authored-by: Daniel Girtler --- .github/workflows/mypy.yaml | 2 +- README.md | 24 +++ archinstall/__init__.py | 2 +- archinstall/lib/menu/global_menu.py | 3 +- archinstall/lib/menu/selection_menu.py | 22 +-- archinstall/lib/translation.py | 144 ------------------ archinstall/lib/translationhandler.py | 165 +++++++++++++++++++++ archinstall/lib/user_interaction/general_conf.py | 21 ++- .../lib/user_interaction/manage_users_conf.py | 4 +- archinstall/locales/README.md | 9 +- archinstall/locales/cyrillic.json | 19 --- archinstall/locales/languages.json | 28 ++-- examples/swiss.py | 34 +++-- 13 files changed, 267 insertions(+), 210 deletions(-) delete mode 100644 archinstall/lib/translation.py create mode 100644 archinstall/lib/translationhandler.py delete mode 100644 archinstall/locales/cyrillic.json (limited to 'archinstall/lib') diff --git a/.github/workflows/mypy.yaml b/.github/workflows/mypy.yaml index 01d4741f..c03cd3cf 100644 --- a/.github/workflows/mypy.yaml +++ b/.github/workflows/mypy.yaml @@ -15,4 +15,4 @@ jobs: # one day this will be enabled # run: mypy --strict --module archinstall || exit 0 - name: run mypy - run: mypy --follow-imports=silent archinstall/lib/menu/selection_menu.py archinstall/lib/menu/global_menu.py archinstall/lib/models/network_configuration.py archinstall/lib/menu/list_manager.py archinstall/lib/user_interaction/network_conf.py 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/translation.py + run: mypy --follow-imports=silent archinstall/lib/menu/selection_menu.py archinstall/lib/menu/global_menu.py archinstall/lib/models/network_configuration.py archinstall/lib/menu/list_manager.py archinstall/lib/user_interaction/network_conf.py archinstall/lib/models/users.py archinstall/lib/disk/blockdevice.py archinstall/lib/user_interaction/subvolume_config.py archinstall/lib/disk/btrfs/btrfs_helpers.py archinstall/lib/translationhandler.py diff --git a/README.md b/README.md index 20224eea..b1df757a 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,29 @@ Assuming you are on a Arch Linux live-ISO and booted into EFI mode. # archinstall --config --disk-layout --creds +# Available Languages + +Archinstall is available in different languages which have been contributed and are maintained by the community. +Current translations are listed below and vary in the amount of translations per language +``` +English +Deutsch +Española +Française +Italiano +Nederlands +Polskie +Portugues do Brasil +Português +Svenska +Türk +čeština +русский +اردو +``` + +Any contributions to the translations are more than welcome, and to get started please follow [the guide](https://github.com/archlinux/archinstall/blob/master/archinstall/locales/README.md) + # Help? Submit an issue here on GitHub, or submit a post in the discord help channel.
@@ -57,6 +80,7 @@ This library is in turn used by the provided guided installer but is also for an Therefore, Archinstall will try its best to not introduce any breaking changes except for major releases which may break backwards compatibility after notifying about such changes. + # Scripting your own installation You could just copy [guided.py](https://github.com/archlinux/archinstall/blob/master/examples/guided.py) as a starting point. diff --git a/archinstall/__init__.py b/archinstall/__init__.py index 1a360c67..f607a922 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -40,7 +40,7 @@ from .lib.menu.selection_menu import ( Selector, GeneralMenu ) -from .lib.translation import Translation, DeferredTranslation +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 diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index 1a292476..1badc052 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -50,7 +50,8 @@ class GlobalMenu(GeneralMenu): Selector( _('Archinstall language'), lambda x: self._select_archinstall_language(x), - default='English') + display_func=lambda x: x.display_name, + default=self.translation_handler.get_language('en')) self._menu_options['keyboard-layout'] = \ Selector( _('Keyboard layout'), diff --git a/archinstall/lib/menu/selection_menu.py b/archinstall/lib/menu/selection_menu.py index c6ac5852..8a08812c 100644 --- a/archinstall/lib/menu/selection_menu.py +++ b/archinstall/lib/menu/selection_menu.py @@ -8,9 +8,11 @@ from typing import Callable, Any, List, Iterator, Tuple, Optional, Dict, TYPE_CH from .menu import Menu, MenuSelectionType from ..locale_helpers import set_keyboard_language from ..output import log -from ..translation import Translation +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 @@ -181,7 +183,7 @@ class GeneralMenu: """ self._enabled_order :List[str] = [] - self._translation = Translation.load_nationalization() + 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 @@ -213,6 +215,10 @@ class GeneralMenu: 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() @@ -461,14 +467,10 @@ class GeneralMenu: mandatory_waiting += 1 return mandatory_fields, mandatory_waiting - def _select_archinstall_language(self, preset_value: str) -> str: - from ... import select_archinstall_language - language = select_archinstall_language(preset_value) - if language is not None: - self._translation.activate(language) - return language - - return preset_value + 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:') diff --git a/archinstall/lib/translation.py b/archinstall/lib/translation.py deleted file mode 100644 index c20a4285..00000000 --- a/archinstall/lib/translation.py +++ /dev/null @@ -1,144 +0,0 @@ -from __future__ import annotations - -import json -import logging -import os -import gettext - -from pathlib import Path -from typing import List, Dict, Any, TYPE_CHECKING, Tuple -from .exceptions import TranslationError - -if TYPE_CHECKING: - _: Any - - -class LanguageDefinitions: - _languages = 'languages.json' - _cyrillic = 'cyrillic.json' - - def __init__(self): - self._mappings = self._get_language_mappings() - self._cyrillic_languages = self._get_cyrillic_languages() - - def is_cyrillic(self, language: str) -> bool: - return language in self._cyrillic_languages - - def _get_language_mappings(self) -> List[Dict[str, str]]: - locales_dir = Translation.get_locales_dir() - languages = Path.joinpath(locales_dir, self._languages) - - with open(languages, 'r') as fp: - return json.load(fp) - - def get_language(self, abbr: str) -> str: - for entry in self._mappings: - if entry['abbr'] == abbr: - return entry['lang'] - - raise ValueError(f'No language with abbreviation "{abbr}" found') - - def _get_cyrillic_languages(self) -> List[str]: - locales_dir = Translation.get_locales_dir() - languages = Path.joinpath(locales_dir, self._cyrillic) - - with open(languages, 'r') as fp: - data = json.load(fp) - return data['languages'] - - -class DeferredTranslation: - def __init__(self, message: str): - self.message = message - - def __len__(self) -> int: - return len(self.message) - - def __str__(self) -> str: - translate = _ - if translate is DeferredTranslation: - return self.message - return translate(self.message) - - def __lt__(self, other) -> bool: - return self.message < other - - def __gt__(self, other) -> bool: - return self.message > other - - def __add__(self, other) -> DeferredTranslation: - if isinstance(other, str): - other = DeferredTranslation(other) - - concat = self.message + other.message - return DeferredTranslation(concat) - - def format(self, *args) -> str: - return self.message.format(*args) - - @classmethod - def install(cls): - import builtins - builtins._ = cls - - -class Translation: - def __init__(self, locales_dir): - self._languages = {} - - for names in self._get_translation_lang(): - try: - self._languages[names[0]] = gettext.translation('base', localedir=locales_dir, languages=names) - except FileNotFoundError as error: - raise TranslationError(f"Could not locate language file for '{names}': {error}") - - def activate(self, name): - if language := self._languages.get(name, None): - languages = LanguageDefinitions() - - if languages.is_cyrillic(name): - self._set_font('UniCyr_8x16') - else: - # this will reset a possible previously set font to a default font - self._set_font('') - - language.install() - else: - raise ValueError(f'Language not supported: {name}') - - def _set_font(self, font: str): - from archinstall import SysCommand, log - try: - log(f'Setting new font: {font}', level=logging.DEBUG) - SysCommand(f'setfont {font}') - except Exception: - log(f'Unable to set font {font}', level=logging.ERROR) - - @classmethod - def load_nationalization(cls) -> Translation: - locales_dir = cls.get_locales_dir() - return Translation(locales_dir) - - @classmethod - def get_locales_dir(cls) -> Path: - cur_path = Path(__file__).parent.parent - locales_dir = Path.joinpath(cur_path, 'locales') - return locales_dir - - @classmethod - def _defined_languages(cls) -> List[str]: - locales_dir = cls.get_locales_dir() - filenames = os.listdir(locales_dir) - return list(filter(lambda x: len(x) == 2, filenames)) - - @classmethod - def _get_translation_lang(cls) -> List[Tuple[str, str]]: - def_languages = cls._defined_languages() - languages = LanguageDefinitions() - return [(languages.get_language(lang), lang) for lang in def_languages] - - @classmethod - def get_available_lang(cls) -> List[str]: - def_languages = cls._defined_languages() - languages = LanguageDefinitions() - return [languages.get_language(lang) for lang in def_languages] diff --git a/archinstall/lib/translationhandler.py b/archinstall/lib/translationhandler.py new file mode 100644 index 00000000..12c8da4a --- /dev/null +++ b/archinstall/lib/translationhandler.py @@ -0,0 +1,165 @@ +from __future__ import annotations + +import json +import logging +import os +import gettext +from dataclasses import dataclass + +from pathlib import Path +from typing import List, Dict, Any, TYPE_CHECKING, Optional +from .exceptions import TranslationError + +if TYPE_CHECKING: + _: Any + + +@dataclass +class Language: + abbr: str + lang: str + translation: gettext.NullTranslations + translation_percent: int + translated_lang: Optional[str] + + @property + def display_name(self) -> str: + if self.translated_lang: + name = self.translated_lang + else: + name = self.lang + return f'{name} ({self.translation_percent}%)' + + def is_match(self, lang_or_translated_lang: str) -> bool: + if self.lang == lang_or_translated_lang: + return True + elif self.translated_lang == lang_or_translated_lang: + return True + return False + + +class TranslationHandler: + _base_pot = 'base.pot' + _languages = 'languages.json' + + def __init__(self): + # to display cyrillic languages correctly + self._set_font('UniCyr_8x16') + + self._total_messages = self._get_total_messages() + self._translated_languages = self._get_translations() + + @property + def translated_languages(self) -> List[Language]: + return self._translated_languages + + def _get_translations(self) -> List[Language]: + mappings = self._load_language_mappings() + defined_languages = self._defined_languages() + + languages = [] + + for short_form in defined_languages: + mapping_entry: Dict[str, Any] = next(filter(lambda x: x['abbr'] == short_form, mappings)) + abbr = mapping_entry['abbr'] + lang = mapping_entry['lang'] + translated_lang = mapping_entry.get('translated_lang', None) + + try: + translation = gettext.translation('base', localedir=self._get_locales_dir(), languages=(abbr, lang)) + + if abbr == 'en': + percent = 100 + else: + num_translations = self._get_catalog_size(translation) + percent = int((num_translations / self._total_messages) * 100) + + language = Language(abbr, lang, translation, percent, translated_lang) + languages.append(language) + except FileNotFoundError as error: + raise TranslationError(f"Could not locate language file for '{lang}': {error}") + + return languages + + def _set_font(self, font: str): + from archinstall import SysCommand, log + try: + log(f'Setting font: {font}', level=logging.DEBUG) + SysCommand(f'setfont {font}') + except Exception: + log(f'Unable to set font {font}', level=logging.ERROR) + + def _load_language_mappings(self) -> List[Dict[str, Any]]: + locales_dir = self._get_locales_dir() + languages = Path.joinpath(locales_dir, self._languages) + + with open(languages, 'r') as fp: + return json.load(fp) + + def _get_catalog_size(self, translation: gettext.NullTranslations) -> int: + # this is a ery naughty way of retrieving the data but + # there's no alternative method exposed unfortunately + catalog = translation._catalog # type: ignore + messages = {k: v for k, v in catalog.items() if k and v} + return len(messages) + + def _get_total_messages(self) -> int: + locales = self._get_locales_dir() + with open(f'{locales}/{self._base_pot}', 'r') as fp: + lines = fp.readlines() + msgid_lines = [line for line in lines if 'msgid' in line] + return len(msgid_lines) - 1 # don't count the first line which contains the metadata + + def get_language(self, abbr: str) -> Language: + try: + return next(filter(lambda x: x.abbr == abbr, self._translated_languages)) + except Exception: + raise ValueError(f'No language with abbreviation "{abbr}" found') + + def activate(self, language: Language): + language.translation.install() + + def _get_locales_dir(self) -> Path: + cur_path = Path(__file__).parent.parent + locales_dir = Path.joinpath(cur_path, 'locales') + return locales_dir + + def _defined_languages(self) -> List[str]: + locales_dir = self._get_locales_dir() + filenames = os.listdir(locales_dir) + return list(filter(lambda x: len(x) == 2 or x == 'pt_BR', filenames)) + + +class DeferredTranslation: + def __init__(self, message: str): + self.message = message + + def __len__(self) -> int: + return len(self.message) + + def __str__(self) -> str: + translate = _ + if translate is DeferredTranslation: + return self.message + return translate(self.message) + + def __lt__(self, other) -> bool: + return self.message < other + + def __gt__(self, other) -> bool: + return self.message > other + + def __add__(self, other) -> DeferredTranslation: + if isinstance(other, str): + other = DeferredTranslation(other) + + concat = self.message + other.message + return DeferredTranslation(concat) + + def format(self, *args) -> str: + return self.message.format(*args) + + @classmethod + def install(cls): + import builtins + builtins._ = cls diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py index 15c42b86..bdc602b3 100644 --- a/archinstall/lib/user_interaction/general_conf.py +++ b/archinstall/lib/user_interaction/general_conf.py @@ -12,7 +12,7 @@ from ..output import log from ..profiles import Profile, list_profiles from ..mirrors import list_mirrors -from ..translation import Translation +from ..translationhandler import Language from ..packages.packages import validate_package_list from ..storage import storage @@ -118,13 +118,22 @@ def select_mirror_regions(preset_values: Dict[str, Any] = {}) -> Dict[str, Any]: case _: return {selected: mirrors[selected] for selected in selected_mirror.value} -def select_archinstall_language(preset_values: str): - languages = Translation.get_available_lang() - choice = Menu(_('Archinstall language'), languages, default_option=preset_values).run() +def select_archinstall_language(languages: List[Language], preset_value: Language) -> Language: + # these are the displayed language names which can either be + # the english name of a language or, if present, the + # name of the language in its own language + options = {lang.display_name: lang for lang in languages} + + choice = Menu( + _('Archinstall language'), + list(options.keys()), + default_option=preset_value.display_name + ).run() match choice.type_: - case MenuSelectionType.Esc: return preset_values - case MenuSelectionType.Selection: return choice.value + case MenuSelectionType.Esc: return preset_value + case MenuSelectionType.Selection: + return options[choice.value] def select_profile(preset) -> Optional[Profile]: diff --git a/archinstall/lib/user_interaction/manage_users_conf.py b/archinstall/lib/user_interaction/manage_users_conf.py index a97328c2..84ce3556 100644 --- a/archinstall/lib/user_interaction/manage_users_conf.py +++ b/archinstall/lib/user_interaction/manage_users_conf.py @@ -57,10 +57,10 @@ class UserList(ListManager): prompt = str(_('Password for user "{}": ').format(entry.username)) new_password = get_password(prompt=prompt) if new_password: - user = next(filter(lambda x: x == entry, data), 1) + user = next(filter(lambda x: x == entry, data)) user.password = new_password elif action == self._actions[2]: # promote/demote - user = next(filter(lambda x: x == entry, data), 1) + user = next(filter(lambda x: x == entry, data)) user.sudo = False if user.sudo else True elif action == self._actions[3]: # delete data = [d for d in data if d != entry] diff --git a/archinstall/locales/README.md b/archinstall/locales/README.md index 70822c05..51662702 100644 --- a/archinstall/locales/README.md +++ b/archinstall/locales/README.md @@ -5,7 +5,7 @@ Archinstall supports multiple languages, which depend on translations coming fro New languages can be added simply by creating a new folder with the proper language abbrevation (see list `languages.json` if unsure). Run the following command to create a new template for a language ``` - mkdir -p /LC_MESSAGES/ && touch /LC_MESSAGES/base.po +mkdir -p /LC_MESSAGES/ && touch /LC_MESSAGES/base.po ``` After that run the script `./locales_generator.sh` it will automatically populate the new `base.po` file with the strings that @@ -31,3 +31,10 @@ msgstr "Wollen sie wirklich abbrechen?" After the translations have been written, run the script once more `./locales_generator.sh` and it will auto-generate the `base.mo` file with the included translations. After that you're all ready to go and enjoy Archinstall in the new language :) + +To display the language inside Archinstall in your own tongue, please edit the file `languages.json` and +add a `translated_lang` entry to the respective language, e.g. + +``` + {"abbr": "pl", "lang": "Polish", "translated_lang": "Polskie"} +``` diff --git a/archinstall/locales/cyrillic.json b/archinstall/locales/cyrillic.json deleted file mode 100644 index 13f11ad0..00000000 --- a/archinstall/locales/cyrillic.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "languages": [ - "Abkhazian", - "Azerbaijani", - "Bashkir", - "Belarusian", - "Bulgarian", - "Chuvash", - "Komi", - "Macedonian", - "Mongolian", - "Russian", - "Serbo-Croatian", - "Tajik", - "Tatar", - "Ukrainian", - "Uzbek" - ] -} diff --git a/archinstall/locales/languages.json b/archinstall/locales/languages.json index c3c9d2c2..883042e8 100644 --- a/archinstall/locales/languages.json +++ b/archinstall/locales/languages.json @@ -20,7 +20,7 @@ {"abbr": "br", "lang": "Breton"}, {"abbr": "bg", "lang": "Bulgarian"}, {"abbr": "ca", "lang": "Catalan"}, - {"abbr": "cs", "lang": "Czech"}, + {"abbr": "cs", "lang": "Czech", "translated_lang": "čeština"}, {"abbr": "ch", "lang": "Chamorro"}, {"abbr": "ce", "lang": "Chechen"}, {"abbr": "cu", "lang": "Church Slavic"}, @@ -29,8 +29,8 @@ {"abbr": "co", "lang": "Corsican"}, {"abbr": "cr", "lang": "Cree"}, {"abbr": "cy", "lang": "Welsh"}, - {"abbr": "da", "lang": "Danish"}, - {"abbr": "de", "lang": "German"}, + {"abbr": "da", "lang": "Danish", "translated_lang": "Dansk"}, + {"abbr": "de", "lang": "German", "translated_lang": "Deutsch"}, {"abbr": "dv", "lang": "Dhivehi"}, {"abbr": "dz", "lang": "Dzongkha"}, {"abbr": "el", "lang": "Modern Greek (1453-)"}, @@ -43,7 +43,7 @@ {"abbr": "fa", "lang": "Persian"}, {"abbr": "fj", "lang": "Fijian"}, {"abbr": "fi", "lang": "Finnish"}, - {"abbr": "fr", "lang": "French"}, + {"abbr": "fr", "lang": "French", "translated_lang": "Française"}, {"abbr": "fy", "lang": "Western Frisian"}, {"abbr": "ff", "lang": "Fulah"}, {"abbr": "gd", "lang": "Scottish Gaelic"}, @@ -71,7 +71,7 @@ {"abbr": "id", "lang": "Indonesian"}, {"abbr": "ik", "lang": "Inupiaq"}, {"abbr": "is", "lang": "Icelandic"}, - {"abbr": "it", "lang": "Italian"}, + {"abbr": "it", "lang": "Italian", "translated_lang": "Italiano"}, {"abbr": "jv", "lang": "Javanese"}, {"abbr": "ja", "lang": "Japanese"}, {"abbr": "kl", "lang": "Kalaallisut"}, @@ -114,7 +114,7 @@ {"abbr": "nd", "lang": "North Ndebele"}, {"abbr": "ng", "lang": "Ndonga"}, {"abbr": "ne", "lang": "Nepali (macrolanguage)"}, - {"abbr": "nl", "lang": "Dutch"}, + {"abbr": "nl", "lang": "Dutch", "translated_lang": "Nederlands"}, {"abbr": "nn", "lang": "Norwegian Nynorsk"}, {"abbr": "nb", "lang": "Norwegian Bokmål"}, {"abbr": "no", "lang": "Norwegian"}, @@ -126,15 +126,15 @@ {"abbr": "os", "lang": "Ossetian"}, {"abbr": "pa", "lang": "Panjabi"}, {"abbr": "pi", "lang": "Pali"}, - {"abbr": "pl", "lang": "Polish"}, - {"abbr": "pt", "lang": "Portuguese"}, - {"abbr": "pt_BR", "lang": "Brazilian Portuguese"}, + {"abbr": "pl", "lang": "Polish", "translated_lang": "Polskie"}, + {"abbr": "pt", "lang": "Portuguese", "translated_lang": "Português"}, + {"abbr": "pt_BR", "lang": "Brazilian Portuguese", "translated_lang": "Portugues do Brasil"}, {"abbr": "ps", "lang": "Pushto"}, {"abbr": "qu", "lang": "Quechua"}, {"abbr": "rm", "lang": "Romansh"}, {"abbr": "ro", "lang": "Romanian"}, {"abbr": "rn", "lang": "Rundi"}, - {"abbr": "ru", "lang": "Russian"}, + {"abbr": "ru", "lang": "Russian", "translated_lang": "русский"}, {"abbr": "sg", "lang": "Sango"}, {"abbr": "sa", "lang": "Sanskrit"}, {"abbr": "si", "lang": "Sinhala"}, @@ -146,14 +146,14 @@ {"abbr": "sd", "lang": "Sindhi"}, {"abbr": "so", "lang": "Somali"}, {"abbr": "st", "lang": "Southern Sotho"}, - {"abbr": "es", "lang": "Spanish"}, + {"abbr": "es", "lang": "Spanish", "translated_lang": "Española"}, {"abbr": "sq", "lang": "Albanian"}, {"abbr": "sc", "lang": "Sardinian"}, {"abbr": "sr", "lang": "Serbian"}, {"abbr": "ss", "lang": "Swati"}, {"abbr": "su", "lang": "Sundanese"}, {"abbr": "sw", "lang": "Swahili (macrolanguage)"}, - {"abbr": "sv", "lang": "Swedish"}, + {"abbr": "sv", "lang": "Swedish", "translated_lang": "Svenska"}, {"abbr": "ty", "lang": "Tahitian"}, {"abbr": "ta", "lang": "Tamil"}, {"abbr": "tt", "lang": "Tatar"}, @@ -166,11 +166,11 @@ {"abbr": "tn", "lang": "Tswana"}, {"abbr": "ts", "lang": "Tsonga"}, {"abbr": "tk", "lang": "Turkmen"}, - {"abbr": "tr", "lang": "Turkish"}, + {"abbr": "tr", "lang": "Turkish", "translated_lang" : "Türk"}, {"abbr": "tw", "lang": "Twi"}, {"abbr": "ug", "lang": "Uighur"}, {"abbr": "uk", "lang": "Ukrainian"}, - {"abbr": "ur", "lang": "Urdu"}, + {"abbr": "ur", "lang": "Urdu", "translated_lang": "اردو"}, {"abbr": "uz", "lang": "Uzbek"}, {"abbr": "ve", "lang": "Venda"}, {"abbr": "vi", "lang": "Vietnamese"}, diff --git a/examples/swiss.py b/examples/swiss.py index 83b79c09..5d40dc68 100644 --- a/examples/swiss.py +++ b/examples/swiss.py @@ -158,24 +158,36 @@ class SetupMenu(archinstall.GeneralMenu): super().__init__(data_store=storage_area) def _setup_selection_menu_options(self): - self.set_option('archinstall-language', + self.set_option( + 'archinstall-language', archinstall.Selector( _('Archinstall language'), lambda x: self._select_archinstall_language(x), - default='English', - enabled=True)) - self.set_option('ntp', - archinstall.Selector( - 'Activate NTP', - lambda x: select_activate_NTP(), - default='Y', - enabled=True)) - self.set_option('mode', + display_func=lambda x: x.display_name, + default=self.translation_handler.get_language('en'), + enabled=True + ) + ) + + self.set_option( + 'ntp', + archinstall.Selector( + 'Activate NTP', + lambda x: select_activate_NTP(), + default='Y', + enabled=True + ) + ) + + self.set_option( + 'mode', archinstall.Selector( 'Excution mode', lambda x : select_mode(), default='full', - enabled=True)) + enabled=True) + ) + for item in ['LC_ALL','LC_CTYPE','LC_NUMERIC','LC_TIME','LC_MESSAGES','LC_COLLATE']: self.set_option(item, archinstall.Selector( -- cgit v1.2.3-70-g09d2 From 956b34905b730d32eab839b7a4627ca0bb9e5b06 Mon Sep 17 00:00:00 2001 From: virtual-meme-machine <46010615+virtual-meme-machine@users.noreply.github.com> Date: Mon, 1 Aug 2022 07:56:08 +0000 Subject: - Fix KeyError exceptions (#1354) --- archinstall/lib/installer.py | 8 ++++---- examples/guided.py | 11 ++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 97c2492d..f874c7fa 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -631,7 +631,7 @@ class Installer: mkinit.write(f"BINARIES=({' '.join(self.BINARIES)})\n") mkinit.write(f"FILES=({' '.join(self.FILES)})\n") - if not storage['arguments']['HSM']: + if not storage['arguments'].get('HSM'): # For now, if we don't use HSM we revert to the old # way of setting up encryption hooks for mkinitcpio. # This is purely for stability reasons, we're going away from this. @@ -673,7 +673,7 @@ class Installer: self.HOOKS.remove('fsck') if self.detect_encryption(partition): - if storage['arguments']['HSM']: + if storage['arguments'].get('HSM'): # Required bby mkinitcpio to add support for fido2-device options self.pacstrap('libfido2') @@ -737,7 +737,7 @@ class Installer: # TODO: Use python functions for this SysCommand(f'/usr/bin/arch-chroot {self.target} chmod 700 /root') - if storage['arguments']['HSM']: + if storage['arguments'].get('HSM'): # TODO: # A bit of a hack, but we need to get vconsole.conf in there # before running `mkinitcpio` because it expects it in HSM mode. @@ -865,7 +865,7 @@ class Installer: kernel_options = f"options" - if storage['arguments']['HSM']: + if storage['arguments'].get('HSM'): # Note: lsblk UUID must be used, not PARTUUID for sd-encrypt to work kernel_options += f" rd.luks.name={real_device.uuid}=luksdev" # Note: tpm2-device and fido2-device don't play along very well: diff --git a/examples/guided.py b/examples/guided.py index 8693b98f..0bb377a4 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -187,11 +187,12 @@ def perform_installation(mountpoint): if installation.minimal_installation(testing=enable_testing, multilib=enable_multilib): installation.set_locale(archinstall.arguments['sys-language'], archinstall.arguments['sys-encoding'].upper()) installation.set_hostname(archinstall.arguments['hostname']) - if archinstall.arguments['mirror-region'].get("mirrors", None) is not None: - installation.set_mirrors(archinstall.arguments['mirror-region']) # Set the mirrors in the installation medium - if archinstall.arguments['swap']: + if archinstall.arguments.get('mirror-region') is not None: + if archinstall.arguments.get("mirrors", None) is not None: + installation.set_mirrors(archinstall.arguments['mirror-region']) # Set the mirrors in the installation medium + if archinstall.arguments.get('swap'): installation.setup_swap('zram') - if archinstall.arguments["bootloader"] == "grub-install" and archinstall.has_uefi(): + if archinstall.arguments.get("bootloader") == "grub-install" and archinstall.has_uefi(): installation.add_additional_packages("grub") installation.add_bootloader(archinstall.arguments["bootloader"]) @@ -274,7 +275,7 @@ if not (archinstall.check_mirror_reachable() or archinstall.arguments.get('skip- archinstall.log(f"Arch Linux mirrors are not reachable. Please check your internet connection and the log file '{log_file}'.", level=logging.INFO, fg="red") exit(1) -if not archinstall.arguments['offline']: +if not archinstall.arguments.get('offline'): latest_version_archlinux_keyring = max([k.pkg_version for k in archinstall.find_package('archlinux-keyring')]) # If we want to check for keyring updates -- cgit v1.2.3-70-g09d2 From 3da03a192e3dc47c0e0c08302d28e9f3a62bcd0f Mon Sep 17 00:00:00 2001 From: Werner Llácer Date: Mon, 1 Aug 2022 10:26:51 +0200 Subject: Solves issue 1343. Could not locate partition after creation (#1355) * Solves issue 1343. Could not locate partition after creation * Added some flake fixes. Co-authored-by: Anton Hvornum --- archinstall/lib/disk/partition.py | 62 +++++++++++++++++++++++++++++++++++++++ archinstall/lib/general.py | 2 +- archinstall/lib/plugins.py | 2 +- 3 files changed, 64 insertions(+), 2 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk/partition.py b/archinstall/lib/disk/partition.py index 4028f114..f70bf907 100644 --- a/archinstall/lib/disk/partition.py +++ b/archinstall/lib/disk/partition.py @@ -243,6 +243,68 @@ class Partition: @property def uuid(self) -> Optional[str]: + """ + Returns the UUID as returned by lsblk for the **partition**. + This is more reliable than relying on /dev/disk/by-uuid as + it doesn't seam to be able to detect md raid partitions. + For bind mounts all the subvolumes share the same uuid + """ + for i in range(storage['DISK_RETRY_ATTEMPTS']): + if not self.partprobe(): + raise DiskError(f"Could not perform partprobe on {self.device_path}") + + time.sleep(storage.get('DISK_TIMEOUTS', 1) * i) + + partuuid = self._safe_uuid + if partuuid: + return partuuid + + raise DiskError(f"Could not get PARTUUID for {self.path} using 'blkid -s PARTUUID -o value {self.path}'") + + @property + def _safe_uuid(self) -> Optional[str]: + """ + A near copy of self.uuid but without any delays. + This function should only be used where uuid is not crucial. + For instance when you want to get a __repr__ of the class. + """ + if not self.partprobe(): + if self.block_device.partition_type == 'iso9660': + return None + + log(f"Could not reliably refresh PARTUUID of partition {self.device_path} due to partprobe error.", level=logging.DEBUG) + + try: + return SysCommand(f'blkid -s UUID -o value {self.device_path}').decode('UTF-8').strip() + except SysCallError as error: + if self.block_device.partition_type == 'iso9660': + # Parent device is a Optical Disk (.iso dd'ed onto a device for instance) + return None + + log(f"Could not get PARTUUID of partition using 'blkid -s UUID -o value {self.device_path}': {error}") + + @property + def _safe_part_uuid(self) -> Optional[str]: + """ + A near copy of self.uuid but without any delays. + This function should only be used where uuid is not crucial. + For instance when you want to get a __repr__ of the class. + """ + if not self.partprobe(): + if self.block_device.partition_type == 'iso9660': + return None + + log(f"Could not reliably refresh PARTUUID of partition {self.device_path} due to partprobe error.", level=logging.DEBUG) + + try: + return SysCommand(f'blkid -s PARTUUID -o value {self.device_path}').decode('UTF-8').strip() + except SysCallError as error: + if self.block_device.partition_type == 'iso9660': + # Parent device is a Optical Disk (.iso dd'ed onto a device for instance) + return None + + log(f"Could not get PARTUUID of partition using 'blkid -s PARTUUID -o value {self.device_path}': {error}") + return self._partition_info.uuid @property diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py index 3ec1d685..27f444e8 100644 --- a/archinstall/lib/general.py +++ b/archinstall/lib/general.py @@ -37,7 +37,7 @@ else: def unregister(self, fileno :int, *args :List[Any], **kwargs :Dict[str, Any]) -> None: try: - del(self.monitoring[fileno]) + del(self.monitoring[fileno]) # noqa: E275 except: pass diff --git a/archinstall/lib/plugins.py b/archinstall/lib/plugins.py index 99e3811c..f771aacb 100644 --- a/archinstall/lib/plugins.py +++ b/archinstall/lib/plugins.py @@ -60,7 +60,7 @@ def import_via_path(path :str, namespace :Optional[str] = None) -> ModuleType: log(f"The above error was detected when loading the plugin: {path}", fg="red", level=logging.ERROR) try: - del(sys.modules[namespace]) + del(sys.modules[namespace]) # noqa: E275 except: pass -- cgit v1.2.3-70-g09d2 From 1bd2210e5f100ed96411c65a25c7f89dd854b35b Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Mon, 1 Aug 2022 18:28:41 +1000 Subject: Downstream merged simple menu changes (#1356) Co-authored-by: Daniel Girtler --- archinstall/lib/menu/menu.py | 20 +++++++++---------- archinstall/lib/menu/simple_menu.py | 25 +++++++++++------------- archinstall/lib/user_interaction/disk_conf.py | 4 ++-- archinstall/lib/user_interaction/general_conf.py | 8 ++++---- archinstall/lib/user_interaction/network_conf.py | 4 ++-- archinstall/lib/user_interaction/system_conf.py | 8 ++++---- 6 files changed, 33 insertions(+), 36 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/menu/menu.py b/archinstall/lib/menu/menu.py index 80982db0..1e6f0110 100644 --- a/archinstall/lib/menu/menu.py +++ b/archinstall/lib/menu/menu.py @@ -56,8 +56,8 @@ class Menu(TerminalMenu): preview_size=0.75, preview_title='Info', header :Union[List[str],str] = None, - explode_on_interrupt :bool = False, - explode_warning :str = '', + raise_error_on_interrupt :bool = False, + raise_error_warning_msg :str = '', clear_screen: bool = True, show_search_hint: bool = True, cycle_cursor: bool = True, @@ -104,10 +104,10 @@ class Menu(TerminalMenu): param header: one or more header lines for the menu type param: string or list - param explode_on_interrupt: This will explicitly handle a ctrl+c instead and return that specific state + param raise_error_on_interrupt: This will explicitly handle a ctrl+c instead and return that specific state type param: bool - param explode_warning: If explode_on_interrupt is True and this is non-empty, there will be a warning with a user confirmation displayed + param raise_error_warning_msg: If raise_error_on_interrupt is True and this is non-empty, there will be a warning with a user confirmation displayed type param: str :param kwargs : any SimpleTerminal parameter @@ -150,8 +150,8 @@ class Menu(TerminalMenu): self._skip = skip self._default_option = default_option self._multi = multi - self._explode_on_interrupt = explode_on_interrupt - self._explode_warning = explode_warning + self._raise_error_on_interrupt = raise_error_on_interrupt + self._raise_error_warning_msg = raise_error_warning_msg menu_title = f'\n{title}\n\n' @@ -164,7 +164,7 @@ class Menu(TerminalMenu): if skip: action_info += str(_("Use ESC to skip")) - if self._explode_on_interrupt: + if self._raise_error_on_interrupt: if len(action_info) > 0: action_info += '\n' action_info += str(_('Use CTRL+C to reset current selection\n\n')) @@ -198,7 +198,7 @@ class Menu(TerminalMenu): preview_command=preview_command, preview_size=preview_size, preview_title=preview_title, - explode_on_interrupt=self._explode_on_interrupt, + raise_error_on_interrupt=self._raise_error_on_interrupt, multi_select_select_on_accept=False, clear_screen=clear_screen, show_search_hint=show_search_hint, @@ -236,8 +236,8 @@ class Menu(TerminalMenu): ret = self._show() if ret.type_ == MenuSelectionType.Ctrl_c: - if self._explode_on_interrupt and len(self._explode_warning) > 0: - response = Menu(self._explode_warning, Menu.yes_no(), skip=False).run() + if 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() diff --git a/archinstall/lib/menu/simple_menu.py b/archinstall/lib/menu/simple_menu.py index f7a2cf23..1980e2ce 100644 --- a/archinstall/lib/menu/simple_menu.py +++ b/archinstall/lib/menu/simple_menu.py @@ -65,7 +65,7 @@ __author__ = "Ingo Meyer" __email__ = "i.meyer@fz-juelich.de" __copyright__ = "Copyright © 2021 Forschungszentrum Jülich GmbH. All rights reserved." __license__ = "MIT" -__version_info__ = (1, 4, 1) +__version_info__ = (1, 5, 0) __version__ = ".".join(map(str, __version_info__)) @@ -86,6 +86,7 @@ DEFAULT_MULTI_SELECT_SELECT_ON_ACCEPT = True DEFAULT_PREVIEW_BORDER = True DEFAULT_PREVIEW_SIZE = 0.25 DEFAULT_PREVIEW_TITLE = "preview" +DEFAULT_QUIT_KEYS = ("escape", "q") DEFAULT_SEARCH_CASE_SENSITIVE = False DEFAULT_SEARCH_HIGHLIGHT_STYLE = ("fg_black", "bg_yellow", "bold") DEFAULT_SEARCH_KEY = "/" @@ -581,6 +582,8 @@ class TerminalMenu: preview_command: Optional[Union[str, Callable[[str], str]]] = None, preview_size: float = DEFAULT_PREVIEW_SIZE, preview_title: str = DEFAULT_PREVIEW_TITLE, + quit_keys: Iterable[str] = DEFAULT_QUIT_KEYS, + raise_error_on_interrupt: bool = False, search_case_sensitive: bool = DEFAULT_SEARCH_CASE_SENSITIVE, search_highlight_style: Optional[Iterable[str]] = DEFAULT_SEARCH_HIGHLIGHT_STYLE, search_key: Optional[str] = DEFAULT_SEARCH_KEY, @@ -596,8 +599,7 @@ class TerminalMenu: status_bar: Optional[Union[str, Iterable[str], Callable[[str], str]]] = None, status_bar_below_preview: bool = DEFAULT_STATUS_BAR_BELOW_PREVIEW, status_bar_style: Optional[Iterable[str]] = DEFAULT_STATUS_BAR_STYLE, - title: Optional[Union[str, Iterable[str]]] = None, - explode_on_interrupt: bool = False + title: Optional[Union[str, Iterable[str]]] = None ): def extract_shortcuts_menu_entries_and_preview_arguments( entries: Iterable[str], @@ -619,7 +621,7 @@ class TerminalMenu: else: unit_separated_entry = escaped_separator_pattern.sub("|", separator_pattern.sub("\\1\x1F", entry)) match_obj = menu_entry_pattern.match(unit_separated_entry) - # this is none in case the entry was an empty string which + # this is none in case the entry was an emtpy string which # will be interpreted as a separator assert match_obj is not None shortcut_key = match_obj.group(1) @@ -716,10 +718,11 @@ class TerminalMenu: self._preview_command = preview_command self._preview_size = preview_size self._preview_title = preview_title + self._quit_keys = tuple(quit_keys) + self._raise_error_on_interrupt = raise_error_on_interrupt self._search_case_sensitive = search_case_sensitive self._search_highlight_style = tuple(search_highlight_style) if search_highlight_style is not None else () self._search_key = search_key - self._explode_on_interrupt = explode_on_interrupt self._shortcut_brackets_highlight_style = ( tuple(shortcut_brackets_highlight_style) if shortcut_brackets_highlight_style is not None else () ) @@ -787,6 +790,7 @@ class TerminalMenu: # backspace can be queried from the terminal database but is unreliable, query the terminal directly instead self._init_backspace_control_character() self._add_missing_control_characters_for_keys(self._accept_keys) + self._add_missing_control_characters_for_keys(self._quit_keys) self._init_terminal_codes() @staticmethod @@ -1477,7 +1481,7 @@ class TerminalMenu: "menu_down": set(("down", "ctrl-j", "j")), "accept": set(self._accept_keys), "multi_select": set(self._multi_select_keys), - "quit": set(("escape", "q")), + "quit": set(self._quit_keys), "search_start": set((self._search_key,)), "backspace": set(("backspace",)), } # type: Dict[str, Set[Optional[str]]] @@ -1541,7 +1545,7 @@ class TerminalMenu: # `search_start` key self._search.search_text += next_key except KeyboardInterrupt as e: - if self._explode_on_interrupt: + if self._raise_error_on_interrupt: raise e menu_was_interrupted = True finally: @@ -1845,12 +1849,6 @@ def get_argumentparser() -> argparse.ArgumentParser: ), ) parser.add_argument("-t", "--title", action="store", dest="title", help="menu title") - parser.add_argument( - "--explode-on-interrupt", - action="store_true", - dest="explode_on_interrupt", - help="Instead of quitting the menu, this will raise the KeyboardInterrupt Exception", - ) parser.add_argument( "-V", "--version", action="store_true", dest="print_version", help="print the version number and exit" ) @@ -1981,7 +1979,6 @@ def main() -> None: status_bar_below_preview=args.status_bar_below_preview, status_bar_style=args.status_bar_style, title=args.title, - explode_on_interrupt=args.explode_on_interrupt, ) except (InvalidParameterCombinationError, InvalidStyleError, UnknownMenuEntryError) as e: print(str(e), file=sys.stderr) diff --git a/archinstall/lib/user_interaction/disk_conf.py b/archinstall/lib/user_interaction/disk_conf.py index 371d052f..b5ed6967 100644 --- a/archinstall/lib/user_interaction/disk_conf.py +++ b/archinstall/lib/user_interaction/disk_conf.py @@ -45,8 +45,8 @@ 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, - explode_on_interrupt=True, - explode_warning=warning + raise_error_on_interrupt=True, + raise_error_warning_msg=warning ).run() match choice.type_: diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py index bdc602b3..754ffa29 100644 --- a/archinstall/lib/user_interaction/general_conf.py +++ b/archinstall/lib/user_interaction/general_conf.py @@ -109,7 +109,7 @@ def select_mirror_regions(preset_values: Dict[str, Any] = {}) -> Dict[str, Any]: list(mirrors.keys()), preset_values=preselected, multi=True, - explode_on_interrupt=True + raise_error_on_interrupt=True ).run() match selected_mirror.type_: @@ -159,8 +159,8 @@ def select_profile(preset) -> Optional[Profile]: selection = Menu( title=title, p_options=list(options.keys()), - explode_on_interrupt=True, - explode_warning=warning + raise_error_on_interrupt=True, + raise_error_warning_msg=warning ).run() match selection.type_: @@ -222,7 +222,7 @@ def select_additional_repositories(preset: List[str]) -> List[str]: sort=False, multi=True, preset_values=preset, - explode_on_interrupt=True + raise_error_on_interrupt=True ).run() match choice.type_: diff --git a/archinstall/lib/user_interaction/network_conf.py b/archinstall/lib/user_interaction/network_conf.py index 1908603e..557e8ed8 100644 --- a/archinstall/lib/user_interaction/network_conf.py +++ b/archinstall/lib/user_interaction/network_conf.py @@ -154,8 +154,8 @@ def ask_to_configure_network( list(network_options.values()), cursor_index=cursor_idx, sort=False, - explode_on_interrupt=True, - explode_warning=warning + raise_error_on_interrupt=True, + raise_error_warning_msg=warning ).run() match choice.type_: diff --git a/archinstall/lib/user_interaction/system_conf.py b/archinstall/lib/user_interaction/system_conf.py index f4ada14b..0416e91f 100644 --- a/archinstall/lib/user_interaction/system_conf.py +++ b/archinstall/lib/user_interaction/system_conf.py @@ -32,8 +32,8 @@ def select_kernel(preset: List[str] = None) -> List[str]: sort=True, multi=True, preset_values=preset, - explode_on_interrupt=True, - explode_warning=warning + raise_error_on_interrupt=True, + raise_error_warning_msg=warning ).run() match choice.type_: @@ -67,8 +67,8 @@ def select_harddrives(preset: List[str] = []) -> List[str]: list(options.keys()), preset_values=list(preset_disks.keys()), multi=True, - explode_on_interrupt=True, - explode_warning=warning + raise_error_on_interrupt=True, + raise_error_warning_msg=warning ).run() match selected_harddrive.type_: -- cgit v1.2.3-70-g09d2 From 31e6eca3af607bbfa79cda30c9e0ff16bb2b66c3 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Mon, 1 Aug 2022 18:29:58 +1000 Subject: Handle no internet connection gracefully (#1361) * Handle no internet connection gracefully * Update * flake8 * Update * Update Co-authored-by: Daniel Girtler --- archinstall/lib/networking.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/networking.py b/archinstall/lib/networking.py index 3e883a7b..96e8f3a1 100644 --- a/archinstall/lib/networking.py +++ b/archinstall/lib/networking.py @@ -4,7 +4,7 @@ import socket import struct from typing import Union, Dict, Any, List -from .exceptions import HardwareIncompatibilityError +from .exceptions import HardwareIncompatibilityError, SysCallError from .general import SysCommand from .output import log from .pacman import run_pacman @@ -33,14 +33,17 @@ def list_interfaces(skip_loopback :bool = True) -> Dict[str, str]: def check_mirror_reachable() -> bool: log("Testing connectivity to the Arch Linux mirrors ...", level=logging.INFO) - if run_pacman("-Sy").exit_code == 0: - return True - - elif os.geteuid() != 0: - log("check_mirror_reachable() uses 'pacman -Sy' which requires root.", level=logging.ERROR, fg="red") + try: + if run_pacman("-Sy").exit_code == 0: + return True + elif os.geteuid() != 0: + log("check_mirror_reachable() uses 'pacman -Sy' which requires root.", level=logging.ERROR, fg="red") + except SysCallError as err: + log(err, level=logging.DEBUG) return False + def update_keyring() -> bool: log("Updating archlinux-keyring ...", level=logging.INFO) if run_pacman("-Sy --noconfirm archlinux-keyring").exit_code == 0: -- cgit v1.2.3-70-g09d2 From 94d611d22f178474d1f3a491cfe60e99bd3293b3 Mon Sep 17 00:00:00 2001 From: 0xShree <49250702+0xShree@users.noreply.github.com> Date: Mon, 1 Aug 2022 08:33:38 +0000 Subject: Change regex expression only to match #[multilib] string (#1364) * Changed regex expression only to match [multilib] string * Update multilib regex expression * Update regex expression Add raw string format --- archinstall/lib/installer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index f874c7fa..42f71678 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -325,7 +325,7 @@ class Installer: def enable_multilib_repository(self): # Set up a regular expression pattern of a commented line containing 'multilib' within [] - pattern = re.compile("^#\\[.*multilib.*\\]$") + pattern = re.compile(r"^#\s*\[multilib\]$") # This is used to track if the previous line is a match, so we end up uncommenting the line after the block. matched = False -- cgit v1.2.3-70-g09d2 From 7bf0fe3c56ed1e8ca9a55ab3dbf6943f94e2a1af Mon Sep 17 00:00:00 2001 From: Werner Llácer Date: Mon, 1 Aug 2022 10:38:22 +0200 Subject: restoring ability to use a predefined installation directory (#1373) --- archinstall/lib/menu/global_menu.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index 1badc052..d9943945 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -42,6 +42,7 @@ if TYPE_CHECKING: class GlobalMenu(GeneralMenu): def __init__(self,data_store): + self._disk_check = True super().__init__(data_store=data_store, auto_cursor=True, preview_size=0.3) def _setup_selection_menu_options(self): @@ -304,11 +305,12 @@ class GlobalMenu(GeneralMenu): missing += ['Hostname'] if not check('!root-password') and not has_superuser(): missing += [str(_('Either root-password or at least 1 user with sudo privileges must be specified'))] - if not check('harddrives'): - missing += [str(_('Drive(s)'))] - if check('harddrives'): - if not self._menu_options['harddrives'].is_empty() and not check('disk_layouts'): - missing += [str(_('Disk layout'))] + if self._disk_check: + if not check('harddrives'): + missing += [str(_('Drive(s)'))] + if check('harddrives'): + if not self._menu_options['harddrives'].is_empty() and not check('disk_layouts'): + missing += [str(_('Disk layout'))] return missing @@ -334,7 +336,7 @@ class GlobalMenu(GeneralMenu): def _select_harddrives(self, old_harddrives : list) -> List: harddrives = select_harddrives(old_harddrives) - if harddrives: + if harddrives is not None: if len(harddrives) == 0: prompt = _( "You decided to skip harddrive selection\nand will use whatever drive-setup is mounted at {} (experimental)\n" @@ -345,7 +347,10 @@ class GlobalMenu(GeneralMenu): choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes(), skip=False).run() if choice.value == Menu.no(): + self._disk_check = True return self._select_harddrives(old_harddrives) + else: + self._disk_check = False # in case the harddrives got changed we have to reset the disk layout as well if old_harddrives != harddrives: -- cgit v1.2.3-70-g09d2 From 68d89a07dfbf298918dc5575bec00976f3ae5dd6 Mon Sep 17 00:00:00 2001 From: Werner Llácer Date: Mon, 1 Aug 2022 10:41:44 +0200 Subject: enhacements to FormattedOutput (#1376) * enhacements to FormattedOutput * flake8 complain --- archinstall/lib/output.py | 78 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 20 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/output.py b/archinstall/lib/output.py index e2b38ce6..709a7382 100644 --- a/archinstall/lib/output.py +++ b/archinstall/lib/output.py @@ -2,45 +2,83 @@ import logging import os import sys from pathlib import Path -from typing import Dict, Union, List, Any +from typing import Dict, Union, List, Any, Callable from .storage import storage +from dataclasses import asdict, is_dataclass class FormattedOutput: @classmethod - def values(cls, o: Any) -> Dict[str, Any]: - if hasattr(o, 'as_json'): + def values(cls, o: Any, class_formatter: str = None, filter_list: List[str] = None) -> Dict[str, Any]: + """ the original values returned a dataclass as dict thru the call to some specific methods + this version allows thru the parameter class_formatter to call a dynamicly selected formatting method. + Can transmit a filter list to the class_formatter, + """ + if class_formatter: + # if invoked per reference it has to be a standard function or a classmethod. + # A method of an instance does not make sense + if callable(class_formatter): + return class_formatter(o, filter_list) + # if is invoked by name we restrict it to a method of the class. No need to mess more + elif hasattr(o, class_formatter) and callable(getattr(o, class_formatter)): + func = getattr(o, class_formatter) + return func(filter_list) + # kept as to make it backward compatible + elif hasattr(o, 'as_json'): return o.as_json() elif hasattr(o, 'json'): return o.json() + elif is_dataclass(o): + return asdict(o) else: return o.__dict__ @classmethod - def as_table(cls, obj: List[Any]) -> str: + def as_table(cls, obj: List[Any], class_formatter: Union[str, Callable] = None, filter_list: List[str] = None) -> str: + """ variant of as_table (subtly different code) which has two additional parameters + filter which is a list of fields which will be shon + class_formatter a special method to format the outgoing data + + A general comment, the format selected for the output (a string where every data record is separated by newline) + is for compatibility with a print statement + As_table_filter can be a drop in replacement for as_table + """ + raw_data = [cls.values(o, class_formatter, filter_list) for o in obj] + # determine the maximum column size column_width: Dict[str, int] = {} - for o in obj: - for k, v in cls.values(o).items(): - column_width.setdefault(k, 0) - column_width[k] = max([column_width[k], len(str(v)), len(k)]) - + for o in raw_data: + for k, v in o.items(): + if not filter_list or k in filter_list: + column_width.setdefault(k, 0) + column_width[k] = max([column_width[k], len(str(v)), len(k)]) + + if not filter_list: + filter_list = (column_width.keys()) + # create the header lines output = '' - for key, width in column_width.items(): + key_list = [] + for key in filter_list: + width = column_width[key] key = key.replace('!', '') - output += key.ljust(width) + ' | ' - - output = output[:-3] + '\n' + key_list.append(key.ljust(width)) + output += ' | '.join(key_list) + '\n' output += '-' * len(output) + '\n' - for o in obj: - for k, v in cls.values(o).items(): - if '!' in k: - v = '*' * len(str(v)) - output += str(v).ljust(column_width[k]) + ' | ' - output = output[:-3] - output += '\n' + # create the data lines + for record in raw_data: + obj_data = [] + for key in filter_list: + width = column_width.get(key, len(key)) + value = record.get(key, '') + if '!' in key: + value = '*' * width + if isinstance(value,(int, float)) or (isinstance(value, str) and value.isnumeric()): + obj_data.append(str(value).rjust(width)) + else: + obj_data.append(str(value).ljust(width)) + output += ' | '.join(obj_data) + '\n' return output -- cgit v1.2.3-70-g09d2 From 5626c109272b7d34d25facbd2b22f466bf1f4ce2 Mon Sep 17 00:00:00 2001 From: Samaoo Date: Mon, 1 Aug 2022 14:07:04 +0200 Subject: fix `valid_parted_position()` (#1382) * fix `valid_parted_position()` * make lines shorter * change `pos` to `pos.lower()` * revert changing `if not len(pos):` to `if not pos:` * `b` can not have decimal places * add `.lower()` --- archinstall/lib/disk/validators.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk/validators.py b/archinstall/lib/disk/validators.py index fd1b7f33..54808886 100644 --- a/archinstall/lib/disk/validators.py +++ b/archinstall/lib/disk/validators.py @@ -7,13 +7,11 @@ def valid_parted_position(pos :str) -> bool: if pos.isdigit(): return True - if pos[-1] == '%' and pos[:-1].isdigit(): + if pos.lower().endswith('b') and pos[:-1].isdigit(): return True - if pos[-3:].lower() in ['mib', 'kib', 'b', 'tib'] and pos[:-3].replace(".", "", 1).isdigit(): - return True - - if pos[-2:].lower() in ['kb', 'mb', 'gb', 'tb'] and pos[:-2].replace(".", "", 1).isdigit(): + if any(pos.lower().endswith(size) and pos[:-len(size)].replace(".", "", 1).isdigit() + for size in ['%', 'kb', 'mb', 'gb', 'tb', 'kib', 'mib', 'gib', 'tib']): return True return False -- cgit v1.2.3-70-g09d2 From 463114356cd195b5402fc7f280508fea6566a7df Mon Sep 17 00:00:00 2001 From: Abhay Mohandas <80393938+abhay-mohandas@users.noreply.github.com> Date: Mon, 1 Aug 2022 19:09:39 +0530 Subject: Option for Parallel Downloads (#1397) * Adding menu * Working on parallel downloads * error updates * updates * update * Few more updates * bug fixes * More bug fixes * Minor bug fixes * Few changes * Minor changes * Cleaned up add_number_of_parrallel_downloads() and hid it behind --advanced * Forgot one import * Fixed flake8 Co-authored-by: Anton Hvornum --- archinstall/lib/menu/global_menu.py | 10 ++++++++++ archinstall/lib/user_interaction/__init__.py | 2 +- archinstall/lib/user_interaction/general_conf.py | 24 ++++++++++++++++++++++++ examples/guided.py | 4 ++++ 4 files changed, 39 insertions(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index d9943945..b518ac22 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -32,6 +32,7 @@ from ..user_interaction import select_encrypted_partitions from ..user_interaction import select_harddrives from ..user_interaction import select_profile from ..user_interaction import select_additional_repositories +from ..user_interaction import add_number_of_parrallel_downloads from ..models.users import User from ..user_interaction.partitioning_conf import current_partition_layout from ..output import FormattedOutput @@ -145,6 +146,15 @@ class GlobalMenu(GeneralMenu): display_func=lambda x: x if x else 'None', default=None ) + + self._menu_options['parallel downloads'] = \ + Selector( + _('Parallel Downloads'), + add_number_of_parrallel_downloads, + display_func=lambda x: x if x else '0', + default=None + ) + self._menu_options['kernels'] = \ Selector( _('Kernels'), diff --git a/archinstall/lib/user_interaction/__init__.py b/archinstall/lib/user_interaction/__init__.py index 8aba4b4d..a1ca2652 100644 --- a/archinstall/lib/user_interaction/__init__.py +++ b/archinstall/lib/user_interaction/__init__.py @@ -7,6 +7,6 @@ from .network_conf import ask_to_configure_network from .partitioning_conf import select_partition, select_encrypted_partitions 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) + select_additional_repositories, ask_hostname, add_number_of_parrallel_downloads) from .disk_conf import ask_for_main_filesystem_format, select_individual_blockdevice_usage, select_disk_layout, select_disk from .utils import get_password, do_countdown diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py index 754ffa29..44147afa 100644 --- a/archinstall/lib/user_interaction/general_conf.py +++ b/archinstall/lib/user_interaction/general_conf.py @@ -1,6 +1,7 @@ from __future__ import annotations import logging +import pathlib from typing import List, Any, Optional, Dict, TYPE_CHECKING from ..menu.menu import MenuSelectionType @@ -205,6 +206,29 @@ def ask_additional_packages_to_install(pre_set_packages: List[str] = []) -> List return packages +def add_number_of_parrallel_downloads(input_number :Optional[int] = None) -> Optional[int]: + print(_("Enter the number of parallel downloads to be enabled.\n [Default value is 0]")) + + while input_number is None: + try: + input_number = int(TextInput("> ").run().strip() or 0) + break + except: + print(_("Invalid input! Try again with a valid input")) + + pacman_conf_path = pathlib.Path("/etc/pacman.conf") + with pacman_conf_path.open() as f: + pacman_conf = f.read().split("\n") + + with pacman_conf_path.open("w") as fwrite: + for line in pacman_conf: + if "ParallelDownloads" in line: + fwrite.write(f"ParallelDownloads = {input_number}\n") + else: + fwrite.write(f"{line}\n") + + return input_number + def select_additional_repositories(preset: List[str]) -> List[str]: """ diff --git a/examples/guided.py b/examples/guided.py index 0bb377a4..bbf19bf9 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -85,6 +85,10 @@ def ask_user_questions(): global_menu.enable('packages') + if archinstall.arguments.get('advanced', False): + # Enable parallel downloads + global_menu.enable('parallel downloads') + # Ask or Call the helper function that asks the user to optionally configure a network. global_menu.enable('nic') -- cgit v1.2.3-70-g09d2 From a31bf94fa3727ef2c13e37d33560f6acf1141902 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Aug 2022 14:58:09 +0200 Subject: Adding a .json() call for our JSON serializer for the Language class (#1410) * Adding a .json() call for our JSON serializer * Update translationhandler.py --- archinstall/lib/translationhandler.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'archinstall/lib') diff --git a/archinstall/lib/translationhandler.py b/archinstall/lib/translationhandler.py index 12c8da4a..58b7ebd4 100644 --- a/archinstall/lib/translationhandler.py +++ b/archinstall/lib/translationhandler.py @@ -37,6 +37,8 @@ class Language: return True return False + def json(self) -> str: + return self.lang class TranslationHandler: _base_pot = 'base.pot' -- cgit v1.2.3-70-g09d2 From 437ac124c6f02bc4f1c7e319a8ad407acffe9d8f Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Aug 2022 20:05:05 +0200 Subject: Converted paths to pathlib.Path and mount points to detect properly (#1412) * Converted a path to pathlib.Path * Using Partition.mountpoints instead of Partition.mountpoint * Update mapperdev.py * Added .mountpoints to MapperDev * Spelling error --- archinstall/lib/disk/helpers.py | 4 ++++ archinstall/lib/disk/mapperdev.py | 6 +++++- archinstall/lib/installer.py | 4 ++-- 3 files changed, 11 insertions(+), 3 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk/helpers.py b/archinstall/lib/disk/helpers.py index c8ac564e..60efe724 100644 --- a/archinstall/lib/disk/helpers.py +++ b/archinstall/lib/disk/helpers.py @@ -403,6 +403,10 @@ def get_partitions_in_use(mountpoint :str) -> Dict[str, Any]: log(f'Filtering available mounts {block_devices_mountpoints} to those under {mountpoint}', level=logging.DEBUG) for mountpoint in list(get_all_targets(output['filesystems']).keys()): + # Since all_blockdevices() returns PosixPath objects, we need to convert + # findmnt paths to pathlib.Path() first: + mountpoint = pathlib.Path(mountpoint) + if mountpoint in block_devices_mountpoints: if mountpoint not in mounts: mounts[mountpoint] = block_devices_mountpoints[mountpoint] diff --git a/archinstall/lib/disk/mapperdev.py b/archinstall/lib/disk/mapperdev.py index 49137ae9..71ef2a79 100644 --- a/archinstall/lib/disk/mapperdev.py +++ b/archinstall/lib/disk/mapperdev.py @@ -64,10 +64,14 @@ class MapperDev: return None + @property + def mountpoints(self) -> List[Dict[str, Any]]: + return [obj['target'] for obj in self.mount_information] + @property def mount_information(self) -> List[Dict[str, Any]]: from .helpers import find_mountpoint - return list(find_mountpoint(self.path)) + return [{**obj, 'target' : pathlib.Path(obj.get('target', '/dev/null'))} for obj in find_mountpoint(self.path)] @property def filesystem(self) -> Optional[str]: diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 42f71678..a0bcc2a6 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -1000,9 +1000,9 @@ class Installer: root_partition = None for partition in self.partitions: print(partition, [partition.mountpoint], [self.target]) - if partition.mountpoint == self.target / 'boot': + if self.target / 'boot' in partition.mountpoints: boot_partition = partition - elif partition.mountpoint == self.target: + elif self.target in partition.mountpoints: root_partition = partition if boot_partition is None or root_partition is None: -- cgit v1.2.3-70-g09d2 From 6213560a0543b11eb007020b044255edaf009d4b Mon Sep 17 00:00:00 2001 From: Abhay Mohandas <80393938+abhay-mohandas@users.noreply.github.com> Date: Tue, 9 Aug 2022 23:45:49 +0530 Subject: Fixes for known issues in the Parallel Download option (#1403) * Adding menu * Working on parallel downloads * error updates * updates * update * Few more updates * bug fixes * More bug fixes * Minor bug fixes * Few changes * Minor changes * Cleaned up add_number_of_parrallel_downloads() and hid it behind --advanced * Forgot one import * Fixed flake8 * Bug fixes * I'm trying... * trying again * trying even more * Bug fixes * Fixed known issues * Code improvements * Few fixes * Minor changes * Minor changes * Trying to fix flake8 Co-authored-by: Anton Hvornum --- archinstall/lib/menu/global_menu.py | 2 +- archinstall/lib/user_interaction/general_conf.py | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index b518ac22..f631c086 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -152,7 +152,7 @@ class GlobalMenu(GeneralMenu): _('Parallel Downloads'), add_number_of_parrallel_downloads, display_func=lambda x: x if x else '0', - default=None + default=0 ) self._menu_options['kernels'] = \ diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py index 44147afa..a121f368 100644 --- a/archinstall/lib/user_interaction/general_conf.py +++ b/archinstall/lib/user_interaction/general_conf.py @@ -206,15 +206,25 @@ def ask_additional_packages_to_install(pre_set_packages: List[str] = []) -> List return packages -def add_number_of_parrallel_downloads(input_number :Optional[int] = None) -> Optional[int]: - print(_("Enter the number of parallel downloads to be enabled.\n [Default value is 0]")) - while input_number is None: +def add_number_of_parrallel_downloads(input_number :Optional[int] = None) -> Optional[int]: + max_downloads = 5 + print(_(f"This option enables the number of parallel downloads that can occur during installation")) + print(_(f"Enter the number of parallel downloads to be enabled.\n (Enter a value between 1 to {max_downloads})\nNote:")) + print(_(f" - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )")) + print(_(f" - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )")) + print(_(f" - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )")) + + while True: try: - input_number = int(TextInput("> ").run().strip() or 0) + input_number = int(TextInput("[Default value: 0] > ").run().strip() or 0) + if input_number <= 0: + input_number = 0 + elif input_number > max_downloads: + input_number = max_downloads break except: - print(_("Invalid input! Try again with a valid input")) + print(_(f"Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]")) pacman_conf_path = pathlib.Path("/etc/pacman.conf") with pacman_conf_path.open() as f: @@ -223,7 +233,7 @@ def add_number_of_parrallel_downloads(input_number :Optional[int] = None) -> Opt with pacman_conf_path.open("w") as fwrite: for line in pacman_conf: if "ParallelDownloads" in line: - fwrite.write(f"ParallelDownloads = {input_number}\n") + fwrite.write(f"ParallelDownloads = {input_number+1}\n") if not input_number == 0 else fwrite.write("#ParallelDownloads = 0\n") else: fwrite.write(f"{line}\n") -- cgit v1.2.3-70-g09d2 From 242f8076bb7679a565433e30c61662c70f37b06f Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 10 Aug 2022 11:02:24 +0200 Subject: Adding better error output for when loading remote configurations goes wrong. (#1415) --- archinstall/lib/general.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py index 27f444e8..9edbaea8 100644 --- a/archinstall/lib/general.py +++ b/archinstall/lib/general.py @@ -12,6 +12,7 @@ import time import re import urllib.parse import urllib.request +import urllib.error import pathlib from datetime import datetime, date from typing import Callable, Optional, Dict, Any, List, Union, Iterator, TYPE_CHECKING @@ -548,8 +549,12 @@ def json_stream_to_structure(configuration_identifier : str, stream :str, target parsed_url = urllib.parse.urlparse(stream) if parsed_url.scheme: # The stream is in fact a URL that should be grabbed - with urllib.request.urlopen(urllib.request.Request(stream, headers={'User-Agent': 'ArchInstall'})) as response: - target.update(json.loads(response.read())) + try: + with urllib.request.urlopen(urllib.request.Request(stream, headers={'User-Agent': 'ArchInstall'})) as response: + target.update(json.loads(response.read())) + except urllib.error.HTTPError as error: + log(f"Could not load {configuration_identifier} via {parsed_url} due to: {error}", level=logging.ERROR, fg="red") + return False else: if pathlib.Path(stream).exists(): try: -- cgit v1.2.3-70-g09d2 From 259aaa488428aa5647d27ebb8e4678829e793dc2 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 10 Aug 2022 12:57:32 +0200 Subject: Fixing a list access issue (#1416) --- archinstall/lib/installer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index a0bcc2a6..798801aa 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -219,7 +219,7 @@ class Installer: """ if partition.get("mountpoint") is None: if (sub_list := partition.get("btrfs",{}).get('subvolumes',{})): - for mountpoint in [sub_list[subvolume] if isinstance(sub_list[subvolume],str) else sub_list[subvolume].get("mountpoint") for subvolume in sub_list if sub_list[subvolume]]: + for mountpoint in [sub_list[subvolume].get("mountpoint") if isinstance(subvolume, dict) else subvolume.mountpoint for subvolume in sub_list]: if mountpoint == '/': return True return False -- cgit v1.2.3-70-g09d2 From 397cceca90592ec39a594cd8fa5215b64d7238ae Mon Sep 17 00:00:00 2001 From: codefiles <11915375+codefiles@users.noreply.github.com> Date: Thu, 11 Aug 2022 05:32:33 -0400 Subject: Fix check for additional repositories (#1391) * Fix typos * Fix check for additional repositories --- archinstall/lib/installer.py | 2 +- examples/guided.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 798801aa..8b87b072 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -705,7 +705,7 @@ class Installer: self.log("The multilib flag is set. This system will be installed with the multilib repository enabled.") self.enable_multilib_repository() else: - self.log("The testing flag is not set. This system will be installed without testing repositories enabled.") + self.log("The multilib flag is not set. This system will be installed without multilib repositories enabled.") if testing: self.log("The testing flag is set. This system will be installed with testing repositories enabled.") diff --git a/examples/guided.py b/examples/guided.py index bbf19bf9..ffbc6226 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -185,8 +185,12 @@ def perform_installation(mountpoint): archinstall.use_mirrors(archinstall.arguments['mirror-region']) # Set the mirrors for the live medium # Retrieve list of additional repositories and set boolean values appropriately - enable_testing = 'testing' in archinstall.arguments.get('additional-repositories', None) - enable_multilib = 'multilib' in archinstall.arguments.get('additional-repositories', None) + if archinstall.arguments.get('additional-repositories', None) is not None: + enable_testing = 'testing' in archinstall.arguments.get('additional-repositories', None) + enable_multilib = 'multilib' in archinstall.arguments.get('additional-repositories', None) + else: + enable_testing = False + enable_multilib = False if installation.minimal_installation(testing=enable_testing, multilib=enable_multilib): installation.set_locale(archinstall.arguments['sys-language'], archinstall.arguments['sys-encoding'].upper()) -- cgit v1.2.3-70-g09d2 From 1086fd686d28317851c7f09eb00898648ecaaad7 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Thu, 11 Aug 2022 18:06:02 +0200 Subject: Cleaned up argument loading slighly. (#1419) * Cleaned up argument loading slighly. Also flipped some --silent logic to avoid double negatives. --plugin and --conf {'plugin': ...} should now both work. * Tweaked xorg profile to use list instead of strings. Because strings causes some issues through add_additional_packages() as it ends up as [(xorg, xorg-xinit), nano] instead of a flat list of packages or string. * Tweaked xorg profile to use list instead of strings. Because strings causes some issues through add_additional_packages() as it ends up as [(xorg, xorg-xinit), nano] instead of a flat list of packages or string. --- archinstall/__init__.py | 30 +++++++++++++++++++++++++----- archinstall/lib/plugins.py | 2 ++ profiles/xorg.py | 10 +++++----- 3 files changed, 32 insertions(+), 10 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/__init__.py b/archinstall/__init__.py index 3e9f8391..090ad7de 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -1,5 +1,6 @@ """Arch Linux installer - guided, templates etc.""" -from argparse import ArgumentParser +import typing +from argparse import ArgumentParser, Namespace from .lib.disk import * from .lib.exceptions import * @@ -132,6 +133,24 @@ def parse_unspecified_argument_list(unknowns :list, multiple :bool = False, erro print(f" We ignore the entry {element} as it isn't related to any argument") return config +def cleanup_empty_args(args :typing.Union[Namespace, dict]) -> dict: + """ + Takes arguments (dictionary or argparse Namespace) and removes any + None values. This ensures clean mergers during dict.update(args) + """ + if type(args) == Namespace: + args = vars(args) + + clean_args = {} + for key, val in args.items(): + if type(val) == dict: + val = cleanup_empty_args(val) + + if val is not None: + clean_args[key] = val + + return clean_args + def get_arguments() -> Dict[str, Any]: """ The handling of parameters from the command line Is done on following steps: @@ -159,14 +178,15 @@ def get_arguments() -> Dict[str, Any]: exit(1) # load the parameters. first the known, then the unknowns - config.update(vars(args)) + args = cleanup_empty_args(args) + config.update(args) config.update(parse_unspecified_argument_list(unknowns)) # amend the parameters (check internal consistency) # Installation can't be silent if config is not passed - if args.config is not None : - config["silent"] = args.silent - else: + if args.get('config') is None: config["silent"] = False + else: + config["silent"] = args.get('silent') # avoiding a compatibility issue if 'dry-run' in config: diff --git a/archinstall/lib/plugins.py b/archinstall/lib/plugins.py index f771aacb..0ff63610 100644 --- a/archinstall/lib/plugins.py +++ b/archinstall/lib/plugins.py @@ -73,6 +73,7 @@ def find_nth(haystack :List[str], needle :str, n :int) -> int: def load_plugin(path :str) -> ModuleType: parsed_url = urllib.parse.urlparse(path) + log(f"Loading plugin {parsed_url}.", fg="gray", level=logging.INFO) # The Profile was not a direct match on a remote URL if not parsed_url.scheme: @@ -96,6 +97,7 @@ def load_plugin(path :str) -> ModuleType: if hasattr(sys.modules[namespace], 'Plugin'): try: plugins[namespace] = sys.modules[namespace].Plugin() + log(f"Plugin {plugins[namespace]} has been loaded.", fg="gray", level=logging.INFO) except Exception as err: log(err, level=logging.ERROR) log(f"The above error was detected when initiating the plugin: {path}", fg="red", level=logging.ERROR) diff --git a/profiles/xorg.py b/profiles/xorg.py index 2ce8dcc2..de45acd3 100644 --- a/profiles/xorg.py +++ b/profiles/xorg.py @@ -47,9 +47,9 @@ if __name__ == 'xorg': for kernel in archinstall.storage['installation_session'].kernels: archinstall.storage['installation_session'].add_additional_packages(f"{kernel}-headers") # Fixes https://github.com/archlinux/archinstall/issues/585 archinstall.storage['installation_session'].add_additional_packages("dkms") # I've had kernel regen fail if it wasn't installed before nvidia-dkms - archinstall.storage['installation_session'].add_additional_packages("xorg-server xorg-xinit nvidia-dkms") + archinstall.storage['installation_session'].add_additional_packages("xorg-server", "xorg-xinit", "nvidia-dkms") else: - archinstall.storage['installation_session'].add_additional_packages(f"xorg-server xorg-xinit {' '.join(archinstall.storage.get('gfx_driver_packages', []))}") + archinstall.storage['installation_session'].add_additional_packages(f"xorg-server", "xorg-xinit", *archinstall.storage.get('gfx_driver_packages', [])) elif 'amdgpu' in archinstall.storage.get("gfx_driver_packages", []): # The order of these two are important if amdgpu is installed #808 if 'amdgpu' in archinstall.storage['installation_session'].MODULES: @@ -60,9 +60,9 @@ if __name__ == 'xorg': archinstall.storage['installation_session'].MODULES.remove('radeon') archinstall.storage['installation_session'].MODULES.append('radeon') - archinstall.storage['installation_session'].add_additional_packages(f"xorg-server xorg-xinit {' '.join(archinstall.storage.get('gfx_driver_packages', []))}") + archinstall.storage['installation_session'].add_additional_packages(f"xorg-server", "xorg-xinit", *archinstall.storage.get('gfx_driver_packages', [])) else: - archinstall.storage['installation_session'].add_additional_packages(f"xorg-server xorg-xinit {' '.join(archinstall.storage.get('gfx_driver_packages', []))}") + archinstall.storage['installation_session'].add_additional_packages(f"xorg-server", "xorg-xinit", *archinstall.storage.get('gfx_driver_packages', [])) except Exception as err: archinstall.log(f"Could not handle nvidia and linuz-zen specific situations during xorg installation: {err}", level=logging.WARNING, fg="yellow") - archinstall.storage['installation_session'].add_additional_packages("xorg-server xorg-xinit") # Prep didn't run, so there's no driver to install + archinstall.storage['installation_session'].add_additional_packages("xorg-server", "xorg-xinit") # Prep didn't run, so there's no driver to install -- cgit v1.2.3-70-g09d2 From a78f7d193b4681ee5a4532982387844a749353d3 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Fri, 12 Aug 2022 16:58:41 +0200 Subject: Fixed small issues with how Installation.chown() was called. --- archinstall/lib/installer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 8b87b072..5b6177bb 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -1131,7 +1131,7 @@ class Installer: return SysCommand(f"/usr/bin/arch-chroot {self.target} sh -c \"chsh -s {shell} {user}\"").exit_code == 0 def chown(self, owner :str, path :str, options :List[str] = []) -> bool: - return SysCommand(f"/usr/bin/arch-chroot {self.target} sh -c 'chown {' '.join(options)} {owner} {path}").exit_code == 0 + return SysCommand(f"/usr/bin/arch-chroot {self.target} sh -c 'chown {' '.join(options)} {owner} {path.repalce('\'', '\\\'')}'").exit_code == 0 def create_file(self, filename :str, owner :Optional[str] = None) -> InstallationFile: return InstallationFile(self, filename, owner) -- cgit v1.2.3-70-g09d2 From 7b06da280dd90421b233f1403b7bfe344bc11037 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Fri, 12 Aug 2022 18:11:27 +0200 Subject: Fix flake8 issue that slinked in. --- archinstall/lib/installer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 5b6177bb..05a74e14 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -1131,7 +1131,8 @@ class Installer: return SysCommand(f"/usr/bin/arch-chroot {self.target} sh -c \"chsh -s {shell} {user}\"").exit_code == 0 def chown(self, owner :str, path :str, options :List[str] = []) -> bool: - return SysCommand(f"/usr/bin/arch-chroot {self.target} sh -c 'chown {' '.join(options)} {owner} {path.repalce('\'', '\\\'')}'").exit_code == 0 + cleaned_path = path.repalce('\'', '\\\'') + return SysCommand(f"/usr/bin/arch-chroot {self.target} sh -c 'chown {' '.join(options)} {owner} {cleaned_path}'").exit_code == 0 def create_file(self, filename :str, owner :Optional[str] = None) -> InstallationFile: return InstallationFile(self, filename, owner) -- cgit v1.2.3-70-g09d2 From 7b4940ef6d2ec7631e948cffe55518e75609dcf3 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Fri, 12 Aug 2022 19:32:42 +0200 Subject: Spelling error on .replace() --- archinstall/lib/installer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 05a74e14..62257642 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -1131,7 +1131,7 @@ class Installer: return SysCommand(f"/usr/bin/arch-chroot {self.target} sh -c \"chsh -s {shell} {user}\"").exit_code == 0 def chown(self, owner :str, path :str, options :List[str] = []) -> bool: - cleaned_path = path.repalce('\'', '\\\'') + cleaned_path = path.replace('\'', '\\\'') return SysCommand(f"/usr/bin/arch-chroot {self.target} sh -c 'chown {' '.join(options)} {owner} {cleaned_path}'").exit_code == 0 def create_file(self, filename :str, owner :Optional[str] = None) -> InstallationFile: -- cgit v1.2.3-70-g09d2 From b1ab5ba3723ef0d7c04b061189d5ad74cba0de8b Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Fri, 12 Aug 2022 22:36:06 +0200 Subject: Fixing double insertion of encoding in locale.gen/locale.conf (#1421) * A temporary fix for #1200, in the long run we need something like what was mentioned in the issue comments: https://github.com/archlinux/archinstall/issues/1200#issuecomment-1212754806 * Enabled the use of modifier detection and getting it in right * Mistaken a split * Adding less strict decoding of output log, this in order to handle the more correct locale generation introduced in this PR. --- archinstall/lib/general.py | 2 +- archinstall/lib/installer.py | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py index 9edbaea8..1dc37994 100644 --- a/archinstall/lib/general.py +++ b/archinstall/lib/general.py @@ -444,7 +444,7 @@ class SysCommand: def __repr__(self, *args :List[Any], **kwargs :Dict[str, Any]) -> str: if self.session: - return self.session._trace_log.decode('UTF-8') + return self.session._trace_log.decode('UTF-8', errors='backslashreplace') return '' def __json__(self) -> Dict[str, Union[str, bool, List[str], Dict[str, Any], Optional[bool], Optional[Dict[str, Any]]]]: diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 62257642..08896289 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -443,10 +443,27 @@ class Installer: if not len(locale): return True + modifier = '' + + # This is a temporary patch to fix #1200 + if '.' in locale: + locale, potential_encoding = locale.split('.', 1) + + # Override encoding if encoding is set to the default parameter + # and the "found" encoding differs. + if encoding == 'UTF-8' and encoding != potential_encoding: + encoding = potential_encoding + + # Make sure we extract the modifier, that way we can put it in if needed. + if '@' in locale: + locale, modifier = locale.split('@', 1) + modifier = f"@{modifier}" + # - End patch + with open(f'{self.target}/etc/locale.gen', 'a') as fh: - fh.write(f'{locale}.{encoding} {encoding}\n') + fh.write(f'{locale}.{encoding}{modifier} {encoding}\n') with open(f'{self.target}/etc/locale.conf', 'w') as fh: - fh.write(f'LANG={locale}.{encoding}\n') + fh.write(f'LANG={locale}.{encoding}{modifier}\n') return True if SysCommand(f'/usr/bin/arch-chroot {self.target} locale-gen').exit_code == 0 else False -- cgit v1.2.3-70-g09d2 From 65212a46aa43fadb2b0ee88b46b573765d7be9f9 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 14 Aug 2022 11:56:24 +0200 Subject: Fix boot entry containing subvolume definition when no subvolumes were selected (#1424) * Attempting fix by looking at the subvolume 'name', should indicate it's a root block and not a root subvolume --- archinstall/lib/installer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 08896289..0f298eba 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -866,7 +866,7 @@ class Installer: options_entry = f'rw intel_pstate=no_hwp {" ".join(self.KERNEL_PARAMS)}\n' for subvolume in root_partition.subvolumes: - if subvolume.root is True: + if subvolume.root is True and subvolume.name != '': options_entry = f"rootflags=subvol={subvolume.name} " + options_entry # Zswap should be disabled when using zram. -- cgit v1.2.3-70-g09d2 From 13703fbb04d7bc7975368b035342e104c26e3f35 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 28 Aug 2022 22:04:25 +0200 Subject: Fix permission flags on all the log files created (#1440) * Changed permissions on the logs stored in /var/log/archinstall. Also cleaned up one of the saves to have the same syntax as the others * Tweaked secondary encryption password detection logic, as it wouldn't take it from the main arguments[] otherwise. * Changed permission on cmd_output.txt * Changed permission on cmd_history.txt --- archinstall/lib/configuration.py | 14 +++++++++++++- archinstall/lib/general.py | 22 ++++++++++++++++++++-- archinstall/lib/installer.py | 11 ++++++----- 3 files changed, 39 insertions(+), 8 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/configuration.py b/archinstall/lib/configuration.py index 510f7103..2a43174d 100644 --- a/archinstall/lib/configuration.py +++ b/archinstall/lib/configuration.py @@ -1,4 +1,6 @@ +import os import json +import stat import logging import pathlib from typing import Optional, Dict @@ -106,23 +108,33 @@ class ConfigurationOutput: def save_user_config(self, dest_path :pathlib.Path = None): if self._is_valid_path(dest_path): - with open(dest_path / self._user_config_file, 'w') as config_file: + target = dest_path / self._user_config_file + + with open(target, 'w') as config_file: config_file.write(self.user_config_to_json()) + os.chmod(str(dest_path / self._user_config_file), stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP) + def save_user_creds(self, dest_path :pathlib.Path = None): if self._is_valid_path(dest_path): if user_creds := self.user_credentials_to_json(): target = dest_path / self._user_creds_file + with open(target, 'w') as config_file: config_file.write(user_creds) + os.chmod(str(target), stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP) + def save_disk_layout(self, dest_path :pathlib.Path = None): if self._is_valid_path(dest_path): if disk_layout := self.disk_layout_to_json(): target = dest_path / self._disk_layout_file + with target.open('w') as config_file: config_file.write(disk_layout) + os.chmod(str(target), stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP) + def save(self, dest_path :pathlib.Path = None): if not dest_path: dest_path = self._default_save_path diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py index 1dc37994..61c4358e 100644 --- a/archinstall/lib/general.py +++ b/archinstall/lib/general.py @@ -6,6 +6,7 @@ import os import secrets import shlex import subprocess +import stat import string import sys import time @@ -313,9 +314,18 @@ class SysCommandWorker: except UnicodeDecodeError: return False - with open(f"{storage['LOG_PATH']}/cmd_output.txt", "a") as peak_output_log: + peak_logfile = pathlib.Path(f"{storage['LOG_PATH']}/cmd_output.txt") + + change_perm = False + if peak_logfile.exists() is False: + change_perm = True + + with peak_logfile.open("a") as peak_output_log: peak_output_log.write(output) + if change_perm: + os.chmod(str(peak_logfile), stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP) + sys.stdout.write(str(output)) sys.stdout.flush() @@ -361,10 +371,18 @@ class SysCommandWorker: # https://stackoverflow.com/questions/4022600/python-pty-fork-how-does-it-work if not self.pid: + history_logfile = pathlib.Path(f"{storage['LOG_PATH']}/cmd_history.txt") try: + change_perm = False + if history_logfile.exists() is False: + change_perm = True + try: - with open(f"{storage['LOG_PATH']}/cmd_history.txt", "a") as cmd_log: + with history_logfile.open("a") as cmd_log: cmd_log.write(f"{self.cmd}\n") + + if change_perm: + os.chmod(str(history_logfile), stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP) except PermissionError: pass diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 0f298eba..a8346cb6 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -246,11 +246,12 @@ class Installer: # we manage the encrypted partititons for partition in [entry for entry in list_part if entry.get('encrypted', False)]: # open the luks device and all associate stuff - if not (password := partition.get('!password', None)): - raise RequirementError(f"Missing partition {partition['device_instance'].path} encryption password in layout: {partition}") - 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}" + 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: -- cgit v1.2.3-70-g09d2 From 6f412622292f64587848e0f03e65eab7eb4cd0e3 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 28 Aug 2022 22:30:19 +0200 Subject: Removed debugging --- archinstall/lib/installer.py | 1 - 1 file changed, 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index a8346cb6..c7682655 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -1017,7 +1017,6 @@ class Installer: boot_partition = None root_partition = None for partition in self.partitions: - print(partition, [partition.mountpoint], [self.target]) if self.target / 'boot' in partition.mountpoints: boot_partition = partition elif self.target in partition.mountpoints: -- cgit v1.2.3-70-g09d2 From f6e695871cdf55dd5277a955ff63a1ee1fd14348 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 30 Aug 2022 19:34:25 +0200 Subject: Homogenize language option during handling (#1446) * Adding a more elaborate fix * Added recovery function to selecting language too * Tweaked return value of display_language() to return the Language() object. --- archinstall/lib/menu/global_menu.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index f631c086..fc7c90bc 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -36,10 +36,21 @@ from ..user_interaction import add_number_of_parrallel_downloads from ..models.users import User from ..user_interaction.partitioning_conf import current_partition_layout from ..output import FormattedOutput +from ..translationhandler import Language if TYPE_CHECKING: _: Any +def display_language(global_menu, x): + if type(x) == Language: + return x + elif type(x) == str: + translation_handler = global_menu._translation_handler + for language in translation_handler._get_translations(): + if language.lang == x: + return language + else: + raise ValueError(f"Language entry needs to Language() object or string of full language like 'English'.") class GlobalMenu(GeneralMenu): def __init__(self,data_store): @@ -51,8 +62,8 @@ class GlobalMenu(GeneralMenu): self._menu_options['archinstall-language'] = \ Selector( _('Archinstall language'), - lambda x: self._select_archinstall_language(x), - display_func=lambda x: x.display_name, + lambda x: self._select_archinstall_language(display_language(self, x)), + display_func=lambda x: display_language(self, x).display_name, default=self.translation_handler.get_language('en')) self._menu_options['keyboard-layout'] = \ Selector( -- cgit v1.2.3-70-g09d2 From 0f5b91c7d733e94ffcad2fd8dd01774631b0c15a Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 30 Aug 2022 22:59:23 +0200 Subject: Fixing issue where blkid causes SysCallException (#1445) * Moving a partprobe() call to better allow for cache updates * Trying to improve Partition()._fetch_information() * Removed a sleep() for debugging purposes * Tweaked a sleep --- archinstall/lib/disk/filesystem.py | 6 ++++-- archinstall/lib/disk/helpers.py | 5 +++++ archinstall/lib/disk/partition.py | 18 ++++++++++++++++-- archinstall/lib/exceptions.py | 8 ++++++-- archinstall/lib/general.py | 2 +- 5 files changed, 32 insertions(+), 7 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk/filesystem.py b/archinstall/lib/disk/filesystem.py index 90656308..5d5952a0 100644 --- a/archinstall/lib/disk/filesystem.py +++ b/archinstall/lib/disk/filesystem.py @@ -253,7 +253,6 @@ class Filesystem: if self.parted(parted_string): for count in range(storage.get('DISK_RETRY_ATTEMPTS', 3)): - self.partprobe() self.blockdevice.flush_cache() new_partition_uuids = [partition.part_uuid for partition in self.blockdevice.partitions.values()] @@ -271,7 +270,10 @@ class Filesystem: raise err else: log(f"Could not get UUID for partition. Waiting {storage.get('DISK_TIMEOUTS', 1) * count}s before retrying.",level=logging.DEBUG) - time.sleep(storage.get('DISK_TIMEOUTS', 1) * count) + self.partprobe() + time.sleep(max(0.1, storage.get('DISK_TIMEOUTS', 1))) + else: + print("Parted did not return True during partition creation") total_partitions = set([partition.part_uuid for partition in self.blockdevice.partitions.values()]) total_partitions.update(previous_partuuids) diff --git a/archinstall/lib/disk/helpers.py b/archinstall/lib/disk/helpers.py index 60efe724..a148a5db 100644 --- a/archinstall/lib/disk/helpers.py +++ b/archinstall/lib/disk/helpers.py @@ -229,12 +229,17 @@ def all_blockdevices(mappers=False, partitions=False, error=False) -> Dict[str, try: information = get_loop_info(device_path) if not information: + print("Exit code for blkid -p -o export was:", ex.exit_code) raise SysCallError("Could not get loop information", exit_code=1) except SysCallError: + print("Not a loop device, trying uevent rules.") information = get_blockdevice_uevent(pathlib.Path(block_device).readlink().name) else: + # We could not reliably get any information, perhaps the disk is clean of information? + print("Raising ex because:", ex.exit_code) raise ex + # return instances information = enrich_blockdevice_information(information) diff --git a/archinstall/lib/disk/partition.py b/archinstall/lib/disk/partition.py index f70bf907..56a7d436 100644 --- a/archinstall/lib/disk/partition.py +++ b/archinstall/lib/disk/partition.py @@ -139,7 +139,19 @@ class Partition: def _call_lsblk(self) -> Dict[str, Any]: self.partprobe() - output = SysCommand(f"lsblk --json -b -o+LOG-SEC,SIZE,PTTYPE,PARTUUID,UUID,FSTYPE {self.device_path}").decode('UTF-8') + # This sleep might be overkill, but lsblk is known to + # work against a chaotic cache that can change during call + # causing no information to be returned (blkid is better) + # time.sleep(1) + + # TODO: Maybe incorporate a re-try system here based on time.sleep(max(0.1, storage.get('DISK_TIMEOUTS', 1))) + + try: + output = SysCommand(f"lsblk --json -b -o+LOG-SEC,SIZE,PTTYPE,PARTUUID,UUID,FSTYPE {self.device_path}").decode('UTF-8') + except SysCallError as error: + # It appears as if lsblk can return exit codes like 8192 to indicate something. + # But it does return output so we'll try to catch it. + output = error.worker.decode('UTF-8') if output: lsblk_info = json.loads(output) @@ -165,7 +177,9 @@ class Partition: def _fetch_information(self) -> PartitionInfo: lsblk_info = self._call_lsblk() sfdisk_info = self._call_sfdisk() - device = lsblk_info['blockdevices'][0] + + if not (device := lsblk_info.get('blockdevices', [None])[0]): + raise DiskError(f'Failed to retrieve information for "{self.device_path}" with lsblk') mountpoints = [Path(mountpoint) for mountpoint in device['mountpoints'] if mountpoint] bootable = sfdisk_info.get('bootable', False) or sfdisk_info.get('type', '') == 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B' diff --git a/archinstall/lib/exceptions.py b/archinstall/lib/exceptions.py index a16faa3f..a66e4e04 100644 --- a/archinstall/lib/exceptions.py +++ b/archinstall/lib/exceptions.py @@ -1,4 +1,7 @@ -from typing import Optional +from typing import Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from .general import SysCommandWorker class RequirementError(BaseException): pass @@ -17,10 +20,11 @@ class ProfileError(BaseException): class SysCallError(BaseException): - def __init__(self, message :str, exit_code :Optional[int] = None) -> None: + def __init__(self, message :str, exit_code :Optional[int] = None, worker :Optional['SysCommandWorker'] = None) -> None: super(SysCallError, self).__init__(message) self.message = message self.exit_code = exit_code + self.worker = worker class PermissionError(BaseException): diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py index 61c4358e..d76b7036 100644 --- a/archinstall/lib/general.py +++ b/archinstall/lib/general.py @@ -272,7 +272,7 @@ class SysCommandWorker: log(args[1], level=logging.DEBUG, fg='red') if self.exit_code != 0: - raise SysCallError(f"{self.cmd} exited with abnormal exit code [{self.exit_code}]: {self._trace_log[-500:]}", self.exit_code) + raise SysCallError(f"{self.cmd} exited with abnormal exit code [{self.exit_code}]: {self._trace_log[-500:]}", self.exit_code, worker=self) def is_alive(self) -> bool: self.poll() -- cgit v1.2.3-70-g09d2 From 4dcd5e684f9461145c5b8656b1a91f99ace26b27 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Tue, 6 Sep 2022 16:31:08 +1000 Subject: Move deserialization into init (#1456) Co-authored-by: Daniel Girtler --- archinstall/__init__.py | 15 +- archinstall/lib/menu/global_menu.py | 57 +++----- archinstall/lib/translationhandler.py | 50 ++++++- archinstall/locales/ar/LC_MESSAGES/base.po | 192 ++++++++++++-------------- archinstall/locales/base.pot | 36 +++++ archinstall/locales/cs/LC_MESSAGES/base.po | 26 ++++ archinstall/locales/de/LC_MESSAGES/base.po | 26 ++++ archinstall/locales/el/LC_MESSAGES/base.po | 6 +- archinstall/locales/en/LC_MESSAGES/base.po | 26 ++++ archinstall/locales/es/LC_MESSAGES/base.po | 26 ++++ archinstall/locales/fr/LC_MESSAGES/base.po | 26 ++++ archinstall/locales/it/LC_MESSAGES/base.po | 26 ++++ archinstall/locales/nl/LC_MESSAGES/base.po | 26 ++++ archinstall/locales/pl/LC_MESSAGES/base.po | 26 ++++ archinstall/locales/pt/LC_MESSAGES/base.po | 26 ++++ archinstall/locales/pt_BR/LC_MESSAGES/base.po | 27 +++- archinstall/locales/ru/LC_MESSAGES/base.po | 26 ++++ archinstall/locales/sv/LC_MESSAGES/base.po | 26 ++++ archinstall/locales/ta/LC_MESSAGES/base.po | 6 +- archinstall/locales/tr/LC_MESSAGES/base.po | 26 ++++ archinstall/locales/ur/LC_MESSAGES/base.po | 26 ++++ examples/swiss.py | 2 +- 22 files changed, 581 insertions(+), 148 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/__init__.py b/archinstall/__init__.py index 184097b1..4e1e6d6d 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -195,40 +195,53 @@ def get_arguments() -> Dict[str, Any]: return config def load_config(): - from .lib.models import NetworkConfiguration """ refine and set some arguments. Formerly at the scripts """ + from .lib.models import NetworkConfiguration + + if (archinstall_lang := arguments.get('archinstall-language', None)) is not None: + arguments['archinstall-language'] = TranslationHandler().get_language_by_name(archinstall_lang) + if arguments.get('harddrives', None) is not None: if type(arguments['harddrives']) is str: arguments['harddrives'] = arguments['harddrives'].split(',') arguments['harddrives'] = [BlockDevice(BlockDev) for BlockDev in arguments['harddrives']] # Temporarily disabling keep_partitions if config file is loaded # Temporary workaround to make Desktop Environments work + if arguments.get('profile', None) is not None: if type(arguments.get('profile', None)) is dict: arguments['profile'] = Profile(None, arguments.get('profile', None)['path']) else: arguments['profile'] = Profile(None, arguments.get('profile', None)) + storage['_desktop_profile'] = arguments.get('desktop-environment', None) + if arguments.get('mirror-region', None) is not None: if type(arguments.get('mirror-region', None)) is dict: arguments['mirror-region'] = arguments.get('mirror-region', None) else: selected_region = arguments.get('mirror-region', None) arguments['mirror-region'] = {selected_region: list_mirrors()[selected_region]} + if arguments.get('sys-language', None) is not None: arguments['sys-language'] = arguments.get('sys-language', 'en_US') + if arguments.get('sys-encoding', None) is not None: arguments['sys-encoding'] = arguments.get('sys-encoding', 'utf-8') + if arguments.get('gfx_driver', None) is not None: storage['gfx_driver_packages'] = AVAILABLE_GFX_DRIVERS.get(arguments.get('gfx_driver', None), None) + if arguments.get('servers', None) is not None: storage['_selected_servers'] = arguments.get('servers', None) + if arguments.get('nic', None) is not None: handler = NetworkConfigurationHandler() handler.parse_arguments(arguments.get('nic')) arguments['nic'] = handler.configuration + if arguments.get('!users', None) is not None or arguments.get('!superusers', None) is not None: users = arguments.get('!users', None) superusers = arguments.get('!superusers', None) diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index fc7c90bc..d1bec189 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -3,54 +3,41 @@ from __future__ import annotations from typing import Any, List, Optional, Union, Dict, TYPE_CHECKING import archinstall - -from ..menu import Menu -from ..menu.selection_menu import Selector, GeneralMenu +from ..disk import encrypted_partitions from ..general import SysCommand, secret from ..hardware import has_uefi +from ..menu import Menu +from ..menu.selection_menu import Selector, GeneralMenu from ..models import NetworkConfiguration -from ..storage import storage +from ..models.users import User +from ..output import FormattedOutput from ..profiles import is_desktop_profile, Profile -from ..disk import encrypted_partitions - -from ..user_interaction import get_password, ask_for_a_timezone, save_config -from ..user_interaction import ask_ntp -from ..user_interaction import ask_for_swap +from ..storage import storage +from ..user_interaction import add_number_of_parrallel_downloads +from ..user_interaction import ask_additional_packages_to_install +from ..user_interaction import ask_for_additional_users +from ..user_interaction import ask_for_audio_selection from ..user_interaction import ask_for_bootloader +from ..user_interaction import ask_for_swap from ..user_interaction import ask_hostname -from ..user_interaction import ask_for_audio_selection -from ..user_interaction import ask_additional_packages_to_install +from ..user_interaction import ask_ntp from ..user_interaction import ask_to_configure_network -from ..user_interaction import ask_for_additional_users -from ..user_interaction import select_language -from ..user_interaction import select_mirror_regions -from ..user_interaction import select_locale_lang -from ..user_interaction import select_locale_enc +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_kernel 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 +from ..user_interaction import select_locale_enc +from ..user_interaction import select_locale_lang +from ..user_interaction import select_mirror_regions from ..user_interaction import select_profile -from ..user_interaction import select_additional_repositories -from ..user_interaction import add_number_of_parrallel_downloads -from ..models.users import User from ..user_interaction.partitioning_conf import current_partition_layout -from ..output import FormattedOutput -from ..translationhandler import Language if TYPE_CHECKING: _: Any -def display_language(global_menu, x): - if type(x) == Language: - return x - elif type(x) == str: - translation_handler = global_menu._translation_handler - for language in translation_handler._get_translations(): - if language.lang == x: - return language - else: - raise ValueError(f"Language entry needs to Language() object or string of full language like 'English'.") class GlobalMenu(GeneralMenu): def __init__(self,data_store): @@ -62,9 +49,9 @@ class GlobalMenu(GeneralMenu): self._menu_options['archinstall-language'] = \ Selector( _('Archinstall language'), - lambda x: self._select_archinstall_language(display_language(self, x)), - display_func=lambda x: display_language(self, x).display_name, - default=self.translation_handler.get_language('en')) + lambda x: self._select_archinstall_language(x), + display_func=lambda x: x.display_name, + default=self.translation_handler.get_language_by_abbr('en')) self._menu_options['keyboard-layout'] = \ Selector( _('Keyboard layout'), diff --git a/archinstall/lib/translationhandler.py b/archinstall/lib/translationhandler.py index 58b7ebd4..d6b3ccb6 100644 --- a/archinstall/lib/translationhandler.py +++ b/archinstall/lib/translationhandler.py @@ -40,6 +40,7 @@ class Language: def json(self) -> str: return self.lang + class TranslationHandler: _base_pot = 'base.pot' _languages = 'languages.json' @@ -48,7 +49,7 @@ class TranslationHandler: # to display cyrillic languages correctly self._set_font('UniCyr_8x16') - self._total_messages = self._get_total_messages() + self._total_messages = self._get_total_active_messages() self._translated_languages = self._get_translations() @property @@ -56,6 +57,9 @@ class TranslationHandler: return self._translated_languages def _get_translations(self) -> List[Language]: + """ + Load all translated languages and return a list of such + """ mappings = self._load_language_mappings() defined_languages = self._defined_languages() @@ -68,13 +72,17 @@ class TranslationHandler: translated_lang = mapping_entry.get('translated_lang', None) try: + # get a translation for a specific language translation = gettext.translation('base', localedir=self._get_locales_dir(), languages=(abbr, lang)) + # calculate the percentage of total translated text to total number of messages if abbr == 'en': percent = 100 else: num_translations = self._get_catalog_size(translation) percent = int((num_translations / self._total_messages) * 100) + # prevent cases where the .pot file is out of date and the percentage is above 100 + percent = min(100, percent) language = Language(abbr, lang, translation, percent, translated_lang) languages.append(language) @@ -84,6 +92,9 @@ class TranslationHandler: return languages def _set_font(self, font: str): + """ + Set the provided font as the new terminal font + """ from archinstall import SysCommand, log try: log(f'Setting font: {font}', level=logging.DEBUG) @@ -92,6 +103,9 @@ class TranslationHandler: log(f'Unable to set font {font}', level=logging.ERROR) def _load_language_mappings(self) -> List[Dict[str, Any]]: + """ + Load the mapping table of all known languages + """ locales_dir = self._get_locales_dir() languages = Path.joinpath(locales_dir, self._languages) @@ -99,34 +113,62 @@ class TranslationHandler: return json.load(fp) def _get_catalog_size(self, translation: gettext.NullTranslations) -> int: - # this is a ery naughty way of retrieving the data but + """ + Get the number of translated messages for a translations + """ + # this is a very naughty way of retrieving the data but # there's no alternative method exposed unfortunately catalog = translation._catalog # type: ignore messages = {k: v for k, v in catalog.items() if k and v} return len(messages) - def _get_total_messages(self) -> int: + def _get_total_active_messages(self) -> int: + """ + Get total messages that could be translated + """ locales = self._get_locales_dir() with open(f'{locales}/{self._base_pot}', 'r') as fp: lines = fp.readlines() msgid_lines = [line for line in lines if 'msgid' in line] + return len(msgid_lines) - 1 # don't count the first line which contains the metadata - def get_language(self, abbr: str) -> Language: + def get_language_by_name(self, name: str) -> Language: + """ + Get a language object by it's name, e.g. English + """ + try: + return next(filter(lambda x: x.lang == name, self._translated_languages)) + except Exception: + raise ValueError(f'No language with name found: {name}') + + def get_language_by_abbr(self, abbr: str) -> Language: + """ + Get a language object by its abbrevation, e.g. en + """ try: return next(filter(lambda x: x.abbr == abbr, self._translated_languages)) except Exception: raise ValueError(f'No language with abbreviation "{abbr}" found') def activate(self, language: Language): + """ + Set the provided language as the current translation + """ language.translation.install() def _get_locales_dir(self) -> Path: + """ + Get the locales directory path + """ cur_path = Path(__file__).parent.parent locales_dir = Path.joinpath(cur_path, 'locales') return locales_dir def _defined_languages(self) -> List[str]: + """ + Get a list of all known languages + """ locales_dir = self._get_locales_dir() filenames = os.listdir(locales_dir) return list(filter(lambda x: len(x) == 2 or x == 'pt_BR', filenames)) diff --git a/archinstall/locales/ar/LC_MESSAGES/base.po b/archinstall/locales/ar/LC_MESSAGES/base.po index aad0f928..ac14f102 100644 --- a/archinstall/locales/ar/LC_MESSAGES/base.po +++ b/archinstall/locales/ar/LC_MESSAGES/base.po @@ -3,26 +3,22 @@ # zer0-x, 2022. msgid "" msgstr "" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Last-Translator: zer0-x\n" -"PO-Revision-Date: 2022-06-16 03:35+0300\n" "Project-Id-Version: \n" +"PO-Revision-Date: 2022-06-16 03:35+0300\n" +"Last-Translator: zer0-x\n" "Language-Team: Arabic\n" "Language: ar\n" "MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" "X-Generator: Lokalize 22.04.2\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" msgid "[!] A log file has been created here: {} {}" msgstr "[!] مِلَف سِجِل أُنشِأ هُنا: {} {}" -msgid "" -" Please submit this issue (and file) to" -" https://github.com/archlinux/archinstall/issues" -msgstr "" -" يُرجى تسليم تقرير عن هذا الخلل (مع المِلَف) إلى" -" https://github.com/archlinux/archinstall/issues" +msgid " Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues" +msgstr " يُرجى تسليم تقرير عن هذا الخلل (مع المِلَف) إلى https://github.com/archlinux/archinstall/issues" msgid "Do you really want to abort?" msgstr "هل تُريدُ حقًا إجهاضَ العَملِيَّة؟" @@ -57,41 +53,26 @@ msgstr "اختر مُحمّل الإقلاع" msgid "Choose an audio server" msgstr "اختر خادِم صوتيات" -msgid "" -"Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and" -" optional profile packages are installed." -msgstr "" -"فقط الحزم مثل base وbase-devel وlinux وlinux-firmware وefibootmgr و" -" حِزم مِلف اختيارية سوف تُثَبَّت." +msgid "Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed." +msgstr "فقط الحزم مثل base وbase-devel وlinux وlinux-firmware وefibootmgr و حِزم مِلف اختيارية سوف تُثَبَّت." -msgid "" -"If you desire a web browser, such as firefox or chromium, you may specify it" -" in the following prompt." -msgstr "" -"إذا كنت ترغب في متصفح الويب ، مثل Firefox أو chromium، فيمكنك تحديده" -" في موضِع الكتابة التالي." +msgid "If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt." +msgstr "إذا كنت ترغب في متصفح الويب ، مثل Firefox أو chromium، فيمكنك تحديده في موضِع الكتابة التالي." -msgid "" -"Write additional packages to install (space separated, leave blank to skip): " +msgid "Write additional packages to install (space separated, leave blank to skip): " msgstr "اكتب حزمًا إضافية لتثبيتها (تُفصَل بالمسافات، اتركها فارغة للتخطي):" msgid "Copy ISO network configuration to installation" msgstr "انسخ إعداد شبكة الـISO للتثبيت" -msgid "" -"Use NetworkManager (necessary to configure internet graphically in GNOME and" -" KDE)" -msgstr "" -"استخدم مُدير الشبكة (ضروري لإعداد الإنترنت باستخدام واجهة رسومية في جنوم و" -" كيدي)" +msgid "Use NetworkManager (necessary to configure internet graphically in GNOME and KDE)" +msgstr "استخدم مُدير الشبكة (ضروري لإعداد الإنترنت باستخدام واجهة رسومية في جنوم و كيدي)" msgid "Select one network interface to configure" msgstr "حدِّد واجهة شبكة واحدة للإعداد" -msgid "" -"Select which mode to configure for \"{}\" or skip to use default mode \"{}\"" -msgstr "" -"حدد الوضع المراد تهيئته لـ\"{}\" أو تخطى لاستخدام الوضع الافتراضي \"{}\"" +msgid "Select which mode to configure for \"{}\" or skip to use default mode \"{}\"" +msgstr "حدد الوضع المراد تهيئته لـ\"{}\" أو تخطى لاستخدام الوضع الافتراضي \"{}\"" msgid "Enter the IP and subnet for {} (example: 192.168.0.5/24): " msgstr "أدخِل الIP مع تجزئة الشبكة لـ{} (على سبيل المثال: 192.168.0.5/24): " @@ -119,8 +100,7 @@ msgstr "" msgid "Enter the start sector (percentage or block number, default: {}): " msgstr "" -msgid "" -"Enter the end sector of the partition (percentage or block number, ex: {}): " +msgid "Enter the end sector of the partition (percentage or block number, ex: {}): " msgstr "" msgid "{} contains queued partitions, this will remove those, are you sure?" @@ -138,9 +118,7 @@ msgid "" "Select by index which partition to mount where" msgstr "" -msgid "" -" * Partition mount-points are relative to inside the installation, the boot" -" would be /boot as an example." +msgid " * Partition mount-points are relative to inside the installation, the boot would be /boot as an example." msgstr "" msgid "Select where to mount partition (leave blank to remove mountpoint): " @@ -179,16 +157,13 @@ msgstr "" msgid "Wipe all selected drives and use a best-effort default partition layout" msgstr "" -msgid "" -"Select what to do with each individual drive (followed by partition usage)" +msgid "Select what to do with each individual drive (followed by partition usage)" msgstr "" msgid "Select what you wish to do with the selected block devices" msgstr "" -msgid "" -"This is a list of pre-programmed profiles, they might make it easier to" -" install things like desktop environments" +msgid "This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments" msgstr "" msgid "Select keyboard layout" @@ -200,19 +175,13 @@ msgstr "" msgid "Select one or more hard drives to use and configure" msgstr "" -msgid "" -"For the best compatibility with your AMD hardware, you may want to use either" -" the all open-source or AMD / ATI options." +msgid "For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options." msgstr "" -msgid "" -"For the best compatibility with your Intel hardware, you may want to use" -" either the all open-source or Intel options.\n" +msgid "For the best compatibility with your Intel hardware, you may want to use either the all open-source or Intel options.\n" msgstr "" -msgid "" -"For the best compatibility with your Nvidia hardware, you may want to use the" -" Nvidia proprietary driver.\n" +msgid "For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n" msgstr "" msgid "" @@ -242,9 +211,7 @@ msgstr "" msgid "Adding partition...." msgstr "" -msgid "" -"You need to enter a valid fs-type in order to continue. See `man parted` for" -" valid fs-type's." +msgid "You need to enter a valid fs-type in order to continue. See `man parted` for valid fs-type's." msgstr "" msgid "Error: Listing profiles on URL \"{}\" resulted in:" @@ -388,18 +355,14 @@ msgstr "" msgid "Password for user \"{}\": " msgstr "" -msgid "" -"Verifying that additional packages exist (this might take a few seconds)" +msgid "Verifying that additional packages exist (this might take a few seconds)" msgstr "" -msgid "" -"Would you like to use automatic time synchronization (NTP) with the default" -" time servers?\n" +msgid "Would you like to use automatic time synchronization (NTP) with the default time servers?\n" msgstr "" msgid "" -"Hardware time and other post-configuration steps might be required in order" -" for NTP to work.\n" +"Hardware time and other post-configuration steps might be required in order for NTP to work.\n" "For more information, please check the Arch wiki" msgstr "" @@ -411,8 +374,7 @@ msgstr "" msgid "" "\n" -" Choose an object from the list, and select one of the available actions for" -" it to execute" +" Choose an object from the list, and select one of the available actions for it to execute" msgstr "" msgid "Cancel" @@ -433,8 +395,9 @@ msgstr "" msgid "Delete" msgstr "" -msgid "Select an action for < {} >" -msgstr "" +#, fuzzy +msgid "Select an action for '{}'" +msgstr "حدِّد منطقة زمنية" msgid "Copy to new key:" msgstr "" @@ -447,13 +410,10 @@ msgid "" "This is your chosen configuration:" msgstr "" -msgid "" -"Pacman is already running, waiting maximum 10 minutes for it to terminate." +msgid "Pacman is already running, waiting maximum 10 minutes for it to terminate." msgstr "" -msgid "" -"Pre-existing pacman lock never exited. Please clean up any existing pacman" -" sessions before using archinstall." +msgid "Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall." msgstr "" msgid "Choose which optional additional repositories to enable" @@ -598,9 +558,7 @@ msgstr "" msgid "Would you like to create a separate partition for /home?" msgstr "" -msgid "" -"The selected drives do not have the minimum capacity required for an" -" automatic suggestion\n" +msgid "The selected drives do not have the minimum capacity required for an automatic suggestion\n" msgstr "" msgid "Minimum capacity for /home partition: {}GB\n" @@ -648,31 +606,22 @@ msgstr "" msgid "Mark/Unmark a partition as compressed (btrfs only)" msgstr "" -msgid "" -"The password you are using seems to be weak, are you sure you want to use it?" +msgid "The password you are using seems to be weak, are you sure you want to use it?" msgstr "" -msgid "" -"Provides a selection of desktop environments and tiling window managers, e.g." -" gnome, kde, sway" +msgid "Provides a selection of desktop environments and tiling window managers, e.g. gnome, kde, sway" msgstr "" msgid "Select your desired desktop environment" msgstr "" -msgid "" -"A very basic installation that allows you to customize Arch Linux as you see" -" fit." +msgid "A very basic installation that allows you to customize Arch Linux as you see fit." msgstr "" -msgid "" -"Provides a selection of various server packages to install and enable, e.g." -" httpd, nginx, mariadb" +msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" msgstr "" -msgid "" -"Choose which servers to install, if none then a minimal installation wil be" -" done" +msgid "Choose which servers to install, if none then a minimal installation will be done" msgstr "" msgid "Installs a minimal system as well as xorg and graphics drivers." @@ -681,9 +630,7 @@ msgstr "" msgid "Press Enter to continue." msgstr "" -msgid "" -"Would you like to chroot into the newly created installation and perform" -" post-installation configuration?" +msgid "Would you like to chroot into the newly created installation and perform post-installation configuration?" msgstr "" msgid "Are you sure you want to reset this setting?" @@ -695,9 +642,7 @@ msgstr "" msgid "Any modifications to the existing setting will reset the disk layout!" msgstr "" -msgid "" -"If you reset the harddrive selection this will also reset the current disk" -" layout. Are you sure?" +msgid "If you reset the harddrive selection this will also reset the current disk layout. Are you sure?" msgstr "" msgid "Save and exit" @@ -740,9 +685,7 @@ msgstr "" msgid "Value: " msgstr "" -msgid "" -"You can skip selecting a drive and partitioning and use whatever drive-setup" -" is mounted at /mnt (experimental)" +msgid "You can skip selecting a drive and partitioning and use whatever drive-setup is mounted at /mnt (experimental)" msgstr "" msgid "Select one of the disks or skip and use /mnt as default" @@ -766,8 +709,7 @@ msgstr "" msgid "Bus-type" msgstr "" -msgid "" -"Either root-password or at least 1 user with sudo privileges must be specified" +msgid "Either root-password or at least 1 user with sudo privileges must be specified" msgstr "" msgid "Enter username (leave blank to skip): " @@ -781,3 +723,53 @@ msgstr "" msgid "Select which partitions to encrypt:" msgstr "" + +msgid "very weak" +msgstr "" + +msgid "weak" +msgstr "" + +msgid "moderate" +msgstr "" + +msgid "strong" +msgstr "" + +msgid "Add subvolume" +msgstr "" + +msgid "Edit subvolume" +msgstr "" + +msgid "Delete subvolume" +msgstr "" + +msgid "Configured {} interfaces" +msgstr "" + +msgid "This option enables the number of parallel downloads that can occur during installation" +msgstr "" + +#, 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 "" + +msgid " - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )" +msgstr "" + +msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" +msgstr "" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" +msgstr "" + +#, python-brace-format +msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" +msgstr "" + +msgid "Parallel Downloads" +msgstr "" diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index 7a259336..b2be65f8 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -787,3 +787,39 @@ msgstr "" msgid "Configured {} interfaces" msgstr "" + +msgid "" +"This option enables the number of parallel downloads that can occur during " +"installation" +msgstr "" + +#, 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 "" + +msgid "" +" - Maximum value : {max_downloads} ( Allows {max_downloads} parallel " +"downloads, allows {max_downloads+1} downloads at a time )" +msgstr "" + +msgid "" +" - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a " +"time )" +msgstr "" + +msgid "" +" - Disable/Default : 0 ( Disables parallel downloading, allows only 1 " +"download at a time )" +msgstr "" + +#, python-brace-format +msgid "" +"Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to " +"disable]" +msgstr "" + +msgid "Parallel Downloads" +msgstr "" diff --git a/archinstall/locales/cs/LC_MESSAGES/base.po b/archinstall/locales/cs/LC_MESSAGES/base.po index 7a2a197f..194bbf78 100644 --- a/archinstall/locales/cs/LC_MESSAGES/base.po +++ b/archinstall/locales/cs/LC_MESSAGES/base.po @@ -792,3 +792,29 @@ msgstr "Smazat uživatele" msgid "Configured {} interfaces" msgstr "" + +msgid "This option enables the number of parallel downloads that can occur during installation" +msgstr "" + +#, 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 "" + +msgid " - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )" +msgstr "" + +msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" +msgstr "" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" +msgstr "" + +#, python-brace-format +msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" +msgstr "" + +msgid "Parallel Downloads" +msgstr "" diff --git a/archinstall/locales/de/LC_MESSAGES/base.po b/archinstall/locales/de/LC_MESSAGES/base.po index a601326e..11fc821d 100644 --- a/archinstall/locales/de/LC_MESSAGES/base.po +++ b/archinstall/locales/de/LC_MESSAGES/base.po @@ -808,6 +808,32 @@ msgstr "Benutzerkonto löschen" msgid "Configured {} interfaces" msgstr "" +msgid "This option enables the number of parallel downloads that can occur during installation" +msgstr "" + +#, 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 "" + +msgid " - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )" +msgstr "" + +msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" +msgstr "" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" +msgstr "" + +#, python-brace-format +msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" +msgstr "" + +msgid "Parallel Downloads" +msgstr "" + #~ msgid "Select disk layout" #~ msgstr "Laufwerke-layout auswählen" diff --git a/archinstall/locales/el/LC_MESSAGES/base.po b/archinstall/locales/el/LC_MESSAGES/base.po index 2561c9f2..9b342ac9 100644 --- a/archinstall/locales/el/LC_MESSAGES/base.po +++ b/archinstall/locales/el/LC_MESSAGES/base.po @@ -787,6 +787,9 @@ msgstr "Επεξεργασία υποόγκου" msgid "Delete subvolume" msgstr "Διαγραφή υποόγκου" +msgid "Configured {} interfaces" +msgstr "Διαμορφωμένες {} διεπαφές" + msgid "This option enables the number of parallel downloads that can occur during installation" msgstr "Αυτή η επιλογή θέτει τον αριθμό των παράλληλων λήψεων που μπορούν να συμβούν κατά την εγκατάσταση" @@ -815,6 +818,3 @@ msgstr "Μη έγκυρη είσοδος! Προσπαθήστε ξανά με msgid "Parallel Downloads" msgstr "Παράλληλες Λήψεις" - -msgid "Configured {} interfaces" -msgstr "Διαμορφωμένες {} διεπαφές" diff --git a/archinstall/locales/en/LC_MESSAGES/base.po b/archinstall/locales/en/LC_MESSAGES/base.po index 1825d501..01d4414f 100644 --- a/archinstall/locales/en/LC_MESSAGES/base.po +++ b/archinstall/locales/en/LC_MESSAGES/base.po @@ -743,3 +743,29 @@ msgstr "" msgid "Configured {} interfaces" msgstr "" + +msgid "This option enables the number of parallel downloads that can occur during installation" +msgstr "" + +#, 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 "" + +msgid " - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )" +msgstr "" + +msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" +msgstr "" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" +msgstr "" + +#, python-brace-format +msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" +msgstr "" + +msgid "Parallel Downloads" +msgstr "" diff --git a/archinstall/locales/es/LC_MESSAGES/base.po b/archinstall/locales/es/LC_MESSAGES/base.po index 47a64b0a..6186b6d8 100644 --- a/archinstall/locales/es/LC_MESSAGES/base.po +++ b/archinstall/locales/es/LC_MESSAGES/base.po @@ -791,6 +791,32 @@ msgstr "Eliminar usuario" msgid "Configured {} interfaces" msgstr "" +msgid "This option enables the number of parallel downloads that can occur during installation" +msgstr "" + +#, 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 "" + +msgid " - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )" +msgstr "" + +msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" +msgstr "" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" +msgstr "" + +#, python-brace-format +msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" +msgstr "" + +msgid "Parallel Downloads" +msgstr "" + #~ msgid "Select disk layout" #~ msgstr "Seleccione el diseño del disco" diff --git a/archinstall/locales/fr/LC_MESSAGES/base.po b/archinstall/locales/fr/LC_MESSAGES/base.po index 877b793f..2d7de615 100644 --- a/archinstall/locales/fr/LC_MESSAGES/base.po +++ b/archinstall/locales/fr/LC_MESSAGES/base.po @@ -797,6 +797,32 @@ msgstr "Supprimer l'utilisateur" msgid "Configured {} interfaces" msgstr "" +msgid "This option enables the number of parallel downloads that can occur during installation" +msgstr "" + +#, 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 "" + +msgid " - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )" +msgstr "" + +msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" +msgstr "" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" +msgstr "" + +#, python-brace-format +msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" +msgstr "" + +msgid "Parallel Downloads" +msgstr "" + #~ msgid "Select disk layout" #~ msgstr "Sélectionner la disposition du disque" diff --git a/archinstall/locales/it/LC_MESSAGES/base.po b/archinstall/locales/it/LC_MESSAGES/base.po index baf55a28..893fe292 100644 --- a/archinstall/locales/it/LC_MESSAGES/base.po +++ b/archinstall/locales/it/LC_MESSAGES/base.po @@ -799,3 +799,29 @@ msgstr "Elimina utente" msgid "Configured {} interfaces" msgstr "" + +msgid "This option enables the number of parallel downloads that can occur during installation" +msgstr "" + +#, 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 "" + +msgid " - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )" +msgstr "" + +msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" +msgstr "" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" +msgstr "" + +#, python-brace-format +msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" +msgstr "" + +msgid "Parallel Downloads" +msgstr "" diff --git a/archinstall/locales/nl/LC_MESSAGES/base.po b/archinstall/locales/nl/LC_MESSAGES/base.po index 8553595e..aa7754d2 100644 --- a/archinstall/locales/nl/LC_MESSAGES/base.po +++ b/archinstall/locales/nl/LC_MESSAGES/base.po @@ -823,6 +823,32 @@ msgstr "Gebruiker verwijderen" msgid "Configured {} interfaces" msgstr "" +msgid "This option enables the number of parallel downloads that can occur during installation" +msgstr "" + +#, 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 "" + +msgid " - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )" +msgstr "" + +msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" +msgstr "" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" +msgstr "" + +#, python-brace-format +msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" +msgstr "" + +msgid "Parallel Downloads" +msgstr "" + #~ msgid "Select disk layout" #~ msgstr "Kies een schijfindeling" diff --git a/archinstall/locales/pl/LC_MESSAGES/base.po b/archinstall/locales/pl/LC_MESSAGES/base.po index 1bd4b47e..c65126ea 100644 --- a/archinstall/locales/pl/LC_MESSAGES/base.po +++ b/archinstall/locales/pl/LC_MESSAGES/base.po @@ -805,6 +805,32 @@ msgstr "Usuń użytkownika" msgid "Configured {} interfaces" msgstr "" +msgid "This option enables the number of parallel downloads that can occur during installation" +msgstr "" + +#, 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 "" + +msgid " - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )" +msgstr "" + +msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" +msgstr "" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" +msgstr "" + +#, python-brace-format +msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" +msgstr "" + +msgid "Parallel Downloads" +msgstr "" + #~ msgid "Select disk layout" #~ msgstr "Wybierz układ dysku" diff --git a/archinstall/locales/pt/LC_MESSAGES/base.po b/archinstall/locales/pt/LC_MESSAGES/base.po index 2e9b461c..98d261a9 100644 --- a/archinstall/locales/pt/LC_MESSAGES/base.po +++ b/archinstall/locales/pt/LC_MESSAGES/base.po @@ -843,6 +843,32 @@ msgstr "Eliminar Utilizador" msgid "Configured {} interfaces" msgstr "" +msgid "This option enables the number of parallel downloads that can occur during installation" +msgstr "" + +#, 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 "" + +msgid " - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )" +msgstr "" + +msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" +msgstr "" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" +msgstr "" + +#, python-brace-format +msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" +msgstr "" + +msgid "Parallel Downloads" +msgstr "" + #~ msgid "Select disk layout" #~ msgstr "Seleciona o esquema de disco" diff --git a/archinstall/locales/pt_BR/LC_MESSAGES/base.po b/archinstall/locales/pt_BR/LC_MESSAGES/base.po index 69f8c902..3acbdb95 100644 --- a/archinstall/locales/pt_BR/LC_MESSAGES/base.po +++ b/archinstall/locales/pt_BR/LC_MESSAGES/base.po @@ -1,7 +1,6 @@ # Translators: # @Cain-dev (cain-dev.github.io) # Rafael Fontenelle - msgid "" msgstr "" "Last-Translator: Rafael Fontenelle \n" @@ -788,3 +787,29 @@ msgstr "Deletar subvolume" msgid "Configured {} interfaces" msgstr "{} interfaces configuradas" + +msgid "This option enables the number of parallel downloads that can occur during installation" +msgstr "" + +#, 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 "" + +msgid " - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )" +msgstr "" + +msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" +msgstr "" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" +msgstr "" + +#, python-brace-format +msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" +msgstr "" + +msgid "Parallel Downloads" +msgstr "" diff --git a/archinstall/locales/ru/LC_MESSAGES/base.po b/archinstall/locales/ru/LC_MESSAGES/base.po index a88c58a1..8aeb57ff 100644 --- a/archinstall/locales/ru/LC_MESSAGES/base.po +++ b/archinstall/locales/ru/LC_MESSAGES/base.po @@ -791,6 +791,32 @@ msgstr "Удалить подтом" msgid "Configured {} interfaces" msgstr "Настроено интерфейсов: {}" +msgid "This option enables the number of parallel downloads that can occur during installation" +msgstr "" + +#, 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 "" + +msgid " - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )" +msgstr "" + +msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" +msgstr "" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" +msgstr "" + +#, python-brace-format +msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" +msgstr "" + +msgid "Parallel Downloads" +msgstr "" + #, python-brace-format #~ msgid "Edit {origkey} :" #~ msgstr "Редактировать {origkey}:" diff --git a/archinstall/locales/sv/LC_MESSAGES/base.po b/archinstall/locales/sv/LC_MESSAGES/base.po index b3712972..590929c6 100644 --- a/archinstall/locales/sv/LC_MESSAGES/base.po +++ b/archinstall/locales/sv/LC_MESSAGES/base.po @@ -804,5 +804,31 @@ msgstr "Ta bort användare" msgid "Configured {} interfaces" msgstr "" +msgid "This option enables the number of parallel downloads that can occur during installation" +msgstr "" + +#, 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 "" + +msgid " - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )" +msgstr "" + +msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" +msgstr "" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" +msgstr "" + +#, python-brace-format +msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" +msgstr "" + +msgid "Parallel Downloads" +msgstr "" + #~ msgid "Select disk layout" #~ msgstr "Välj hårddisk-layout" diff --git a/archinstall/locales/ta/LC_MESSAGES/base.po b/archinstall/locales/ta/LC_MESSAGES/base.po index 9cf261f2..3c1cd848 100644 --- a/archinstall/locales/ta/LC_MESSAGES/base.po +++ b/archinstall/locales/ta/LC_MESSAGES/base.po @@ -790,9 +790,6 @@ msgstr "துணைத்தொகுதியை நீக்கவும்" msgid "Configured {} interfaces" msgstr "கட்டமைக்கப்பட்ட {} இடைமுகங்கள்" -msgid "Parallel Downloads" -msgstr "இணையான பதிவிறக்கங்கள்" - msgid "This option enables the number of parallel downloads that can occur during installation" msgstr "இந்த விருப்பம் நிறுவலின் போது நிகழக்கூடிய இணையான பதிவிறக்கங்களின் எண்ணிக்கையை செயல்படுத்துகிறது" @@ -818,3 +815,6 @@ msgstr " - முடக்கு/இயல்புநிலை: 0 (இணை #, python-brace-format msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" msgstr "தவறான உள்ளீடு! சரியான உள்ளீட்டில் [1 முதல் {max_downloads} வரை அல்லது முடக்க 0 வரை] மீண்டும் முயற்சிக்கவும்" + +msgid "Parallel Downloads" +msgstr "இணையான பதிவிறக்கங்கள்" diff --git a/archinstall/locales/tr/LC_MESSAGES/base.po b/archinstall/locales/tr/LC_MESSAGES/base.po index 4d978ee0..63f9dee0 100644 --- a/archinstall/locales/tr/LC_MESSAGES/base.po +++ b/archinstall/locales/tr/LC_MESSAGES/base.po @@ -803,3 +803,29 @@ msgstr "Kullanıcı Sil" msgid "Configured {} interfaces" msgstr "" + +msgid "This option enables the number of parallel downloads that can occur during installation" +msgstr "" + +#, 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 "" + +msgid " - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )" +msgstr "" + +msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" +msgstr "" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" +msgstr "" + +#, python-brace-format +msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" +msgstr "" + +msgid "Parallel Downloads" +msgstr "" diff --git a/archinstall/locales/ur/LC_MESSAGES/base.po b/archinstall/locales/ur/LC_MESSAGES/base.po index 2927c3ea..6494ae28 100644 --- a/archinstall/locales/ur/LC_MESSAGES/base.po +++ b/archinstall/locales/ur/LC_MESSAGES/base.po @@ -825,6 +825,32 @@ msgstr "صارف کو حذف کریں" msgid "Configured {} interfaces" msgstr "" +msgid "This option enables the number of parallel downloads that can occur during installation" +msgstr "" + +#, 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 "" + +msgid " - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )" +msgstr "" + +msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" +msgstr "" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" +msgstr "" + +#, python-brace-format +msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" +msgstr "" + +msgid "Parallel Downloads" +msgstr "" + #~ msgid "Select disk layout" #~ msgstr "ڈسک لے آؤٹ کو منتخب کریں" diff --git a/examples/swiss.py b/examples/swiss.py index 5d40dc68..da45cd18 100644 --- a/examples/swiss.py +++ b/examples/swiss.py @@ -164,7 +164,7 @@ class SetupMenu(archinstall.GeneralMenu): _('Archinstall language'), lambda x: self._select_archinstall_language(x), display_func=lambda x: x.display_name, - default=self.translation_handler.get_language('en'), + default=self.translation_handler.get_language_by_abbr('en'), enabled=True ) ) -- cgit v1.2.3-70-g09d2 From a2adeca5eba17afc36d964879ce30e88a0e6a3ba Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Wed, 7 Sep 2022 23:48:14 +1000 Subject: Fix translation fonts (#1461) Co-authored-by: Daniel Girtler --- archinstall/lib/translationhandler.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/translationhandler.py b/archinstall/lib/translationhandler.py index d6b3ccb6..08deb3e7 100644 --- a/archinstall/lib/translationhandler.py +++ b/archinstall/lib/translationhandler.py @@ -46,8 +46,8 @@ class TranslationHandler: _languages = 'languages.json' def __init__(self): - # to display cyrillic languages correctly - self._set_font('UniCyr_8x16') + # to display latin, greek, cyrillic characters + self._set_font('LatGrkCyr-8x16') self._total_messages = self._get_total_active_messages() self._translated_languages = self._get_translations() @@ -61,7 +61,7 @@ class TranslationHandler: Load all translated languages and return a list of such """ mappings = self._load_language_mappings() - defined_languages = self._defined_languages() + defined_languages = self._provided_translations() languages = [] @@ -165,13 +165,20 @@ class TranslationHandler: locales_dir = Path.joinpath(cur_path, 'locales') return locales_dir - def _defined_languages(self) -> List[str]: + def _provided_translations(self) -> List[str]: """ Get a list of all known languages """ locales_dir = self._get_locales_dir() filenames = os.listdir(locales_dir) - return list(filter(lambda x: len(x) == 2 or x == 'pt_BR', filenames)) + + translation_files = [] + for filename in filenames: + if len(filename) == 2 or filename == 'pt_BR': + if filename not in ['ur', 'ta']: + translation_files.append(filename) + + return translation_files class DeferredTranslation: -- cgit v1.2.3-70-g09d2 From d2484f67cbe9a87d8448d61dd9caaf3a99d232de Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Thu, 8 Sep 2022 00:29:04 +1000 Subject: Update the action text (#1462) Co-authored-by: Daniel Girtler --- archinstall/lib/menu/menu.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/menu/menu.py b/archinstall/lib/menu/menu.py index 1e6f0110..16153e2c 100644 --- a/archinstall/lib/menu/menu.py +++ b/archinstall/lib/menu/menu.py @@ -162,14 +162,17 @@ class Menu(TerminalMenu): action_info = '' if skip: - action_info += str(_("Use ESC to skip")) + action_info += str(_('ESC to skip')) if self._raise_error_on_interrupt: - if len(action_info) > 0: - action_info += '\n' - action_info += str(_('Use CTRL+C to reset current selection\n\n')) + action_info += ', ' if len(action_info) > 0 else '' + action_info += str(_('CTRL+C to reset')) - menu_title += action_info + if multi: + action_info += ', ' if len(action_info) > 0 else '' + action_info += str(_('TAB to select')) + + menu_title += action_info + '\n' if default_option: # if a default value was specified we move that one -- cgit v1.2.3-70-g09d2 From c373607f8c9548bf9de55988629b747f32d67b3d Mon Sep 17 00:00:00 2001 From: Alexmelman88 <99257010+Alexmelman88@users.noreply.github.com> Date: Sat, 10 Sep 2022 11:26:10 +0300 Subject: Update pot file, ru locale (#1465) * Update general_conf.py * Add files via upload * Add files via upload --- archinstall/lib/user_interaction/general_conf.py | 2 +- archinstall/locales/base.pot | 3 +++ archinstall/locales/ru/LC_MESSAGES/base.mo | Bin 33485 -> 35756 bytes archinstall/locales/ru/LC_MESSAGES/base.po | 27 ++++++++++++++--------- 4 files changed, 20 insertions(+), 12 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py index a121f368..34c80c21 100644 --- a/archinstall/lib/user_interaction/general_conf.py +++ b/archinstall/lib/user_interaction/general_conf.py @@ -217,7 +217,7 @@ def add_number_of_parrallel_downloads(input_number :Optional[int] = None) -> Opt while True: try: - input_number = int(TextInput("[Default value: 0] > ").run().strip() or 0) + input_number = int(TextInput(_("[Default value: 0] > ")).run().strip() or 0) if input_number <= 0: input_number = 0 elif input_number > max_downloads: diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index e08dfdc3..0b08b337 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -832,3 +832,6 @@ msgstr "" msgid "TAB to select" msgstr "" + +msgid "[Default value: 0] > " +msgstr "" diff --git a/archinstall/locales/ru/LC_MESSAGES/base.mo b/archinstall/locales/ru/LC_MESSAGES/base.mo index 9d9a0c17..8b3f0ae4 100644 Binary files a/archinstall/locales/ru/LC_MESSAGES/base.mo and b/archinstall/locales/ru/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/ru/LC_MESSAGES/base.po b/archinstall/locales/ru/LC_MESSAGES/base.po index e643e82b..6d5799eb 100644 --- a/archinstall/locales/ru/LC_MESSAGES/base.po +++ b/archinstall/locales/ru/LC_MESSAGES/base.po @@ -10,7 +10,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" -"X-Generator: Poedit 3.0.1\n" +"X-Generator: Poedit 3.1\n" msgid "[!] A log file has been created here: {} {}" msgstr "[!] Здесь был создан файл журнала: {} {}" @@ -792,7 +792,7 @@ msgid "Configured {} interfaces" msgstr "Настроено интерфейсов: {}" msgid "This option enables the number of parallel downloads that can occur during installation" -msgstr "" +msgstr "Этот параметр определяет количество параллельных загрузок, которые могут происходить во время установки" #, python-brace-format msgid "" @@ -800,32 +800,37 @@ msgid "" " (Enter a value between 1 to {max_downloads})\n" "Note:" msgstr "" +"Введите количество параллельных загрузок, которые будут включены.\n" +" (Введите значение от 1 до {max_downloads})\n" +"Примечание:" msgid " - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )" -msgstr "" +msgstr " - Максимальное значение: {max_downloads} ( Позволяет {max_downloads} параллельных загрузок, позволяет {max_downloads+1} загрузок одновременно )" msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" -msgstr "" +msgstr " - Минимальное значение: 1 ( Позволяет 1 параллельную загрузку, позволяет 2 загрузки одновременно )" msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" -msgstr "" +msgstr " - Отключить/по умолчанию: 0 ( Отключает параллельную загрузку, позволяет только 1 загрузку за один раз )" #, python-brace-format msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" -msgstr "" +msgstr "Неверный ввод! Повторите попытку с правильным вводом [1 - {max_downloads}, или 0 - отключить]" msgid "Parallel Downloads" -msgstr "" +msgstr "Параллельные загрузки" -#, fuzzy msgid "ESC to skip" -msgstr "Используйте ESC, чтобы пропустить" +msgstr "ESC, чтобы пропустить" msgid "CTRL+C to reset" -msgstr "" +msgstr "CTRL+C, чтобы сбросить" msgid "TAB to select" -msgstr "" +msgstr "TAB, чтобы выбрать" + +msgid "[Default value: 0] > " +msgstr "[Значение по умолчанию: 0] > " #, python-brace-format #~ msgid "Edit {origkey} :" -- cgit v1.2.3-70-g09d2 From 94df913e0f2e68178ad64d385bbc453416b7e4b0 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Mon, 12 Sep 2022 05:23:21 +1000 Subject: Update handling of unsupported translations (#1467) * Handle unsupported fonts * Update archinstall/locales/README.md Co-authored-by: codefiles <11915375+codefiles@users.noreply.github.com> Co-authored-by: Daniel Girtler Co-authored-by: codefiles <11915375+codefiles@users.noreply.github.com> --- archinstall/lib/menu/menu.py | 18 +++++++--- archinstall/lib/translationhandler.py | 42 ++++++++++++++++-------- archinstall/lib/user_interaction/general_conf.py | 26 ++++++++++++--- archinstall/locales/README.md | 22 +++++++++++-- archinstall/locales/languages.json | 4 +-- 5 files changed, 85 insertions(+), 27 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/menu/menu.py b/archinstall/lib/menu/menu.py index 16153e2c..112bc0ae 100644 --- a/archinstall/lib/menu/menu.py +++ b/archinstall/lib/menu/menu.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from enum import Enum, auto from os import system -from typing import Dict, List, Union, Any, TYPE_CHECKING, Optional +from typing import Dict, List, Union, Any, TYPE_CHECKING, Optional, Callable from archinstall.lib.menu.simple_menu import TerminalMenu @@ -52,9 +52,9 @@ class Menu(TerminalMenu): sort :bool = True, preset_values :Union[str, List[str]] = None, cursor_index : Optional[int] = None, - preview_command=None, - preview_size=0.75, - preview_title='Info', + preview_command: Optional[Callable] = None, + preview_size: float = 0.75, + preview_title: str = 'Info', header :Union[List[str],str] = None, raise_error_on_interrupt :bool = False, raise_error_warning_msg :str = '', @@ -152,6 +152,7 @@ class Menu(TerminalMenu): self._multi = multi self._raise_error_on_interrupt = raise_error_on_interrupt self._raise_error_warning_msg = raise_error_warning_msg + self._preview_command = preview_command menu_title = f'\n{title}\n\n' @@ -198,7 +199,7 @@ class Menu(TerminalMenu): # show_search_hint=True, preselected_entries=self.preset_values, cursor_index=self.cursor_index, - preview_command=preview_command, + preview_command=lambda x: self._preview_wrapper(preview_command, x), preview_size=preview_size, preview_title=preview_title, raise_error_on_interrupt=self._raise_error_on_interrupt, @@ -235,6 +236,13 @@ class Menu(TerminalMenu): else: return MenuSelection(type_=MenuSelectionType.Esc) + def _preview_wrapper(self, preview_command: Optional[Callable], current_selection: str) -> Optional[str]: + if preview_command: + if self._default_option is not None and f'{self._default_option} {self._default_str}' == current_selection: + current_selection = self._default_option + return preview_command(current_selection) + return None + def run(self) -> MenuSelection: ret = self._show() diff --git a/archinstall/lib/translationhandler.py b/archinstall/lib/translationhandler.py index 08deb3e7..ef33b8ec 100644 --- a/archinstall/lib/translationhandler.py +++ b/archinstall/lib/translationhandler.py @@ -17,41 +17,55 @@ if TYPE_CHECKING: @dataclass class Language: abbr: str - lang: str + name_en: str translation: gettext.NullTranslations translation_percent: int translated_lang: Optional[str] + external_dep: Optional[str] @property def display_name(self) -> str: - if self.translated_lang: + if not self.external_dep and self.translated_lang: name = self.translated_lang else: - name = self.lang + name = self.name_en + return f'{name} ({self.translation_percent}%)' def is_match(self, lang_or_translated_lang: str) -> bool: - if self.lang == lang_or_translated_lang: + if self.name_en == lang_or_translated_lang: return True elif self.translated_lang == lang_or_translated_lang: return True return False def json(self) -> str: - return self.lang + return self.name_en class TranslationHandler: - _base_pot = 'base.pot' - _languages = 'languages.json' - def __init__(self): - # to display latin, greek, cyrillic characters - self._set_font('LatGrkCyr-8x16') + self._base_pot = 'base.pot' + self._languages = 'languages.json' + + # check if a custom font was provided, otherwise we'll + # use one that can display latin, greek, cyrillic characters + if self.is_custom_font_enabled(): + self._set_font(self.custom_font_path().name) + else: + self._set_font('LatGrkCyr-8x16') self._total_messages = self._get_total_active_messages() self._translated_languages = self._get_translations() + @classmethod + def custom_font_path(cls) -> Path: + return Path('/usr/share/kbd/consolefonts/archinstall_font.psfu.gz') + + @classmethod + def is_custom_font_enabled(cls) -> bool: + return cls.custom_font_path().exists() + @property def translated_languages(self) -> List[Language]: return self._translated_languages @@ -70,6 +84,7 @@ class TranslationHandler: abbr = mapping_entry['abbr'] lang = mapping_entry['lang'] translated_lang = mapping_entry.get('translated_lang', None) + external_dep = mapping_entry.get('external_dep', False) try: # get a translation for a specific language @@ -84,7 +99,7 @@ class TranslationHandler: # prevent cases where the .pot file is out of date and the percentage is above 100 percent = min(100, percent) - language = Language(abbr, lang, translation, percent, translated_lang) + language = Language(abbr, lang, translation, percent, translated_lang, external_dep) languages.append(language) except FileNotFoundError as error: raise TranslationError(f"Could not locate language file for '{lang}': {error}") @@ -138,7 +153,7 @@ class TranslationHandler: Get a language object by it's name, e.g. English """ try: - return next(filter(lambda x: x.lang == name, self._translated_languages)) + return next(filter(lambda x: x.name_en == name, self._translated_languages)) except Exception: raise ValueError(f'No language with name found: {name}') @@ -175,8 +190,7 @@ class TranslationHandler: translation_files = [] for filename in filenames: if len(filename) == 2 or filename == 'pt_BR': - if filename not in ['ur', 'ta']: - translation_files.append(filename) + translation_files.append(filename) return translation_files diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py index 34c80c21..6365014d 100644 --- a/archinstall/lib/user_interaction/general_conf.py +++ b/archinstall/lib/user_interaction/general_conf.py @@ -13,7 +13,7 @@ from ..output import log from ..profiles import Profile, list_profiles from ..mirrors import list_mirrors -from ..translationhandler import Language +from ..translationhandler import Language, TranslationHandler from ..packages.packages import validate_package_list from ..storage import storage @@ -125,16 +125,34 @@ def select_archinstall_language(languages: List[Language], preset_value: Languag # name of the language in its own language options = {lang.display_name: lang for lang in languages} + def dependency_preview(current_selection: str) -> Optional[str]: + current_lang = options[current_selection] + + if current_lang.external_dep and not TranslationHandler.is_custom_font_enabled(): + font_file = TranslationHandler.custom_font_path() + text = str(_('To be able to use this translation, please install a font manually that supports the language.')) + '\n' + text += str(_('The font should be stored as {}')).format(font_file) + return text + return None + choice = Menu( _('Archinstall language'), list(options.keys()), - default_option=preset_value.display_name + default_option=preset_value.display_name, + preview_command=lambda x: dependency_preview(x), + preview_size=0.5 ).run() match choice.type_: - case MenuSelectionType.Esc: return preset_value + case MenuSelectionType.Esc: + return preset_value case MenuSelectionType.Selection: - return options[choice.value] + language: Language = options[choice.value] + # we have to make sure that the proper AUR dependency is + # present to be able to use this language + if not language.external_dep or TranslationHandler.is_custom_font_enabled(): + return language + return select_archinstall_language(languages, preset_value) def select_profile(preset) -> Optional[Profile]: diff --git a/archinstall/locales/README.md b/archinstall/locales/README.md index 51662702..37dc32e3 100644 --- a/archinstall/locales/README.md +++ b/archinstall/locales/README.md @@ -1,8 +1,26 @@ # Nationalization -Archinstall supports multiple languages, which depend on translations coming from the community :) +Archinstall supports multiple languages, which depend on translations coming from the community :) -New languages can be added simply by creating a new folder with the proper language abbrevation (see list `languages.json` if unsure). +## Important Note +Before starting a new language translation be aware that a font for that language may not be +available on the ISO. We are using the pre-installed font `/usr/share/kbd/consolefonts/LatGrkCyr-8x16.psfu.gz` in archinstall +which should cover a fair amount of different languages but unfortunately not all of them. + +We have the option to provide a custom font in case the above is not covering a specific language, which can +be achieved by installing the font yourself on the ISO and saving it to `/usr/share/kbd/consolefonts/archinstall_font.psfu.gz`. +If this font is present it will be automatically loaded and all languages which are not supported by the default font will +be enabled (but only some might actually work). + +Please make sure that the provided language works with the default font on the ISO, and if not mark it in the `languages.json` +that it needs an external dependency +``` +{"abbr": "ur", "lang": "Urdu", "translated_lang": "اردو", "external_dep": true}, +``` + +## Adding new languages + +New languages can be added simply by creating a new folder with the proper language abbreviation (see list `languages.json` if unsure). Run the following command to create a new template for a language ``` mkdir -p /LC_MESSAGES/ && touch /LC_MESSAGES/base.po diff --git a/archinstall/locales/languages.json b/archinstall/locales/languages.json index 55ddf57e..344d3d51 100644 --- a/archinstall/locales/languages.json +++ b/archinstall/locales/languages.json @@ -155,7 +155,7 @@ {"abbr": "sw", "lang": "Swahili (macrolanguage)"}, {"abbr": "sv", "lang": "Swedish", "translated_lang": "Svenska"}, {"abbr": "ty", "lang": "Tahitian"}, - {"abbr": "ta", "lang": "Tamil", "translated_lang": "தமிழ்"}, + {"abbr": "ta", "lang": "Tamil", "translated_lang": "தமிழ்", "external_dep": true}, {"abbr": "tt", "lang": "Tatar"}, {"abbr": "te", "lang": "Telugu"}, {"abbr": "tg", "lang": "Tajik"}, @@ -170,7 +170,7 @@ {"abbr": "tw", "lang": "Twi"}, {"abbr": "ug", "lang": "Uighur"}, {"abbr": "uk", "lang": "Ukrainian"}, - {"abbr": "ur", "lang": "Urdu", "translated_lang": "اردو"}, + {"abbr": "ur", "lang": "Urdu", "translated_lang": "اردو", "external_dep": true}, {"abbr": "uz", "lang": "Uzbek"}, {"abbr": "ve", "lang": "Venda"}, {"abbr": "vi", "lang": "Vietnamese"}, -- cgit v1.2.3-70-g09d2 From c1f21e7ca437e1e8a712ec9bed3bfdf3f4c893e5 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 19 Sep 2022 23:42:08 +0200 Subject: Add compression to /etc/fstab for btrfs subvolumes (#1473) * Adding a btrfs compression plugin to genfstab * Allowing the genfstab plugin to break on success --- archinstall/lib/disk/btrfs/__init__.py | 4 ---- archinstall/lib/disk/btrfs/btrfs_helpers.py | 34 +++++++++++++++++++++++++++++ archinstall/lib/installer.py | 3 ++- 3 files changed, 36 insertions(+), 5 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk/btrfs/__init__.py b/archinstall/lib/disk/btrfs/__init__.py index 3c183112..a26e0160 100644 --- a/archinstall/lib/disk/btrfs/__init__.py +++ b/archinstall/lib/disk/btrfs/__init__.py @@ -54,7 +54,3 @@ def create_subvolume(installation: Installer, subvolume_location :Union[pathlib. log(f"Creating a subvolume on {target}", level=logging.INFO) if (cmd := SysCommand(f"btrfs subvolume create {target}")).exit_code != 0: raise DiskError(f"Could not create a subvolume at {target}: {cmd}") - - -def manage_btrfs_subvolumes(installation :Installer, partition :Dict[str, str]) -> list: - raise Deprecated("Use setup_subvolumes() instead.") diff --git a/archinstall/lib/disk/btrfs/btrfs_helpers.py b/archinstall/lib/disk/btrfs/btrfs_helpers.py index ab528388..f6d2734a 100644 --- a/archinstall/lib/disk/btrfs/btrfs_helpers.py +++ b/archinstall/lib/disk/btrfs/btrfs_helpers.py @@ -1,4 +1,5 @@ import logging +import re from pathlib import Path from typing import Optional, Dict, Any, TYPE_CHECKING @@ -6,6 +7,7 @@ from ...models.subvolume import Subvolume from ...exceptions import SysCallError, DiskError from ...general import SysCommand from ...output import log +from ...plugins import plugins from ..helpers import get_mount_info from .btrfssubvolumeinfo import BtrfsSubvolumeInfo @@ -14,6 +16,35 @@ if TYPE_CHECKING: from ...installer import Installer +class fstab_btrfs_compression_plugin(): + def __init__(self, partition_dict): + self.partition_dict = partition_dict + + def on_genfstab(self, installation): + with open(f"{installation.target}/etc/fstab", 'r') as fh: + fstab = fh.read() + + # Replace the {installation}/etc/fstab with entries + # using the compress=zstd where the mountpoint has compression set. + with open(f"{installation.target}/etc/fstab", 'w') as fh: + for line in fstab.split('\n'): + # So first we grab the mount options by using subvol=.*? as a locator. + # And we also grab the mountpoint for the entry, for instance /var/log + if (subvoldef := re.findall(',.*?subvol=.*?[\t ]', line)) and (mountpoint := re.findall('[\t ]/.*?[\t ]', line)): + for subvolume in self.partition_dict.get('btrfs', {}).get('subvolumes', []): + # We then locate the correct subvolume and check if it's compressed + if subvolume.compress and subvolume.mountpoint == mountpoint[0].strip(): + # We then sneak in the compress=zstd option if it doesn't already exist: + # We skip entries where compression is already defined + if ',compress=zstd,' not in line: + line = line.replace(subvoldef[0], f",compress=zstd{subvoldef[0]}") + break + + fh.write(f"{line}\n") + + return True + + def mount_subvolume(installation: 'Installer', device: 'BTRFSPartition', subvolume: Subvolume): # we normalize the subvolume name (getting rid of slash at the start if exists. # In our implementation has no semantic load. @@ -61,6 +92,9 @@ def setup_subvolumes(installation: 'Installer', partition_dict: Dict[str, Any]): if (cmd := SysCommand(f"chattr +c {installation.target}/{name}")).exit_code != 0: raise DiskError(f"Could not set compress attribute at {installation.target}/{name}: {cmd}") + if 'fstab_btrfs_compression_plugin' not in plugins: + plugins['fstab_btrfs_compression_plugin'] = fstab_btrfs_compression_plugin(partition_dict) + def subvolume_info_from_path(path: Path) -> Optional[BtrfsSubvolumeInfo]: try: diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index c7682655..1270959e 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -432,7 +432,8 @@ class Installer: for plugin in plugins.values(): if hasattr(plugin, 'on_genfstab'): - plugin.on_genfstab(self) + if plugin.on_genfstab(self) is True: + break return True -- cgit v1.2.3-70-g09d2 From 985ff449db6d681d852c4fa558d2a90ecf2a2bc3 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 21 Sep 2022 12:36:54 +0200 Subject: On certain hardware where virtual and physical harddrives share the same common name, a smart mapping is done and block devices will show up under /sys/class/block/ but not always under /dev/* and thus breaking the all_blockdevices() logic. This should fix that. (#1475) --- archinstall/lib/disk/helpers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk/helpers.py b/archinstall/lib/disk/helpers.py index a148a5db..f19125f4 100644 --- a/archinstall/lib/disk/helpers.py +++ b/archinstall/lib/disk/helpers.py @@ -220,7 +220,12 @@ def all_blockdevices(mappers=False, partitions=False, error=False) -> Dict[str, # we'll iterate the /sys/class definitions and find the information # from there. for block_device in glob.glob("/sys/class/block/*"): - device_path = f"/dev/{pathlib.Path(block_device).readlink().name}" + device_path = pathlib.Path(f"/dev/{pathlib.Path(block_device).readlink().name}") + + if device_path.exists() is False: + log(f"Unknown device found by '/sys/class/block/*', ignoring: {device_path}", level=logging.WARNING, fg="yellow") + continue + try: information = blkid(f'blkid -p -o export {device_path}') except SysCallError as ex: -- cgit v1.2.3-70-g09d2