From 00b0ae7ba439a5a420095175b3bedd52c569db51 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Wed, 19 Apr 2023 20:55:42 +1000 Subject: PyParted and a large rewrite of the underlying partitioning (#1604) * Invert mypy files * Add optional pre-commit hooks * New profile structure * Serialize profiles * Use profile instead of classmethod * Custom profile setup * Separator between back * Support profile import via url * Move profiles module * Refactor files * Remove symlink * Add user to docker group * Update schema description * Handle list services * mypy fixes * mypy fixes * Rename profilesv2 to profiles * flake8 * mypy again * Support selecting DM * Fix mypy * Cleanup * Update greeter setting * Update schema * Revert toml changes * Poc external dependencies * Dependency support * New encryption menu * flake8 * Mypy and flake8 * Unify lsblk command * Update bootloader configuration * Git hooks * Fix import * Pyparted * Remove custom font setting * flake8 * Remove default preview * Manual partitioning menu * Update structure * Disk configuration * Update filesystem * luks2 encryption * Everything works until installation * Btrfsutil * Btrfs handling * Update btrfs * Save encryption config * Fix pipewire issue * Update mypy version * Update all pre-commit * Update package versions * Revert audio/pipewire * Merge master PRs * Add master changes * Merge master changes * Small renaming * Pull master changes * Reset disk enc after disk config change * Generate locals * Update naming * Fix imports * Fix broken sync * Fix pre selection on table menu * Profile menu * Update profile * Fix post_install * Added python-pyparted to PKGBUILD, this requires [testing] to be enabled in order to run makepkg. Package still works via python -m build etc. * Swaped around some setuptools logic in pyproject Since we define `package-data` and `packages` there should be no need for: ``` [tool.setuptools.packages.find] where = ["archinstall", "archinstall.*"] ``` * Removed pyproject collisions. Duplicate definitions. * Made sure pyproject.toml includes languages * Add example and update README * Fix pyproject issues * Generate locale * Refactor imports * Simplify imports * Add profile description and package examples * Align code * Fix mypy * Simplify imports * Fix saving config * Fix wrong luks merge * Refactor installation * Fix cdrom device loading * Fix wrongly merged code * Fix imports and greeter * Don't terminate on partprobe error * Use specific path on partprobe from luks * Update archinstall/lib/disk/device_model.py Co-authored-by: codefiles <11915375+codefiles@users.noreply.github.com> * Update archinstall/lib/disk/device_model.py Co-authored-by: codefiles <11915375+codefiles@users.noreply.github.com> * Update github workflow to test archinstall installation * Update sway merge * Generate locales * Update workflow --------- Co-authored-by: Daniel Girtler Co-authored-by: Anton Hvornum Co-authored-by: Anton Hvornum Co-authored-by: codefiles <11915375+codefiles@users.noreply.github.com> --- README.md | 201 +++++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 127 insertions(+), 74 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index d83a8901..517621bc 100644 --- a/README.md +++ b/README.md @@ -26,44 +26,60 @@ Assuming you are on an Arch Linux live-ISO: # archinstall +#### Advanced Some additional options that are not needed by most users are hidden behind the `--advanced` flag. ## Running from a declarative configuration file or URL -Prerequisites: - 1. Edit the [configuration file](https://github.com/archlinux/archinstall/blob/master/examples/config-sample.json) according to your requirements. +`archinstall` can be run with a JSON configuration file. There are 2 different configuration files to consider, +the `user_configuration.json` contains all general installation configuration, whereas the `user_credentials.json` +contains the sensitive user configuration such as user password, root password and encryption password. -Assuming you are on a Arch Linux live-ISO and booted into EFI mode. +An example of the user configuration file can be found here +[configuration file](https://github.com/archlinux/archinstall/blob/master/examples/config-sample.json) +and example of the credentials configuration here +[credentials file](https://github.com/archlinux/archinstall/blob/master/examples/creds-sample.json). - # archinstall --config --disk-layout --creds +**HINT:** The configuration files can be auto-generated by starting `archisntall`, configuring all desired menu +points and then going to `Save configuration`. + +To load the configuration file into `archinstall` run the following command +``` +archinstall --config --creds +``` # Available Languages -Archinstall is available in different languages which have been contributed and are maintained by the community. +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ñol -Français +Arabic +Brazilian Portuguese +Czech +Dutch +French +Georgian +German Indonesian -Italiano -Nederlands -Polskie -Português do Brasil -Português -Svenska -Türkçe -čeština -Русский -اردو -Ελληνικά -தமிழ் +Italian +Korean +Modern Greek +Polish +Portuguese +Russian +Spanish +Swedish +Tamil +Turkish +Ukrainian +Urdu ``` -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) +Any contributions to the translations are more than welcome, +to get started please follow [the guide](https://github.com/archlinux/archinstall/blob/master/archinstall/locales/README.md) -# Help? +# Help or Issues Submit an issue here on GitHub, or submit a post in the discord help channel.
When doing so, attach the `/var/log/archinstall/install.log` to the issue ticket. This helps us help you! @@ -86,73 +102,111 @@ Therefore, Archinstall will try its best to not introduce any breaking changes e # Scripting your own installation -You could just copy [guided.py](https://github.com/archlinux/archinstall/blob/master/examples/guided.py) as a starting point. - -However, assuming you're building your own ISO and want to create an automated installation process, or you want to install virtual machines onto local disk images, here is a [minimal example](https://github.com/archlinux/archinstall/blob/master/examples/minimal.py) of how to install using archinstall as a Python library:
- -```python -import archinstall, getpass - -# Select a harddrive and a disk password -harddrive = archinstall.select_disk(archinstall.all_blockdevices(partitions=False)) -disk_password = getpass.getpass(prompt='Disk password (won\'t echo): ') - -# We disable safety precautions in the library that protects the partitions -harddrive.keep_partitions = False - -# First, we configure the basic filesystem layout -with archinstall.Filesystem(harddrive, archinstall.GPT) as fs: - # We create a filesystem layout that will use the entire drive - # (this is a helper function, you can partition manually as well) - fs.use_entire_disk(root_filesystem_type='btrfs') - - boot = fs.find_partition('/boot') - root = fs.find_partition('/') - - boot.format('vfat') - - # Set the flag for encrypted to allow for encryption and then encrypt - root.encrypted = True - root.encrypt(password=disk_password) +## Scripting interactive installation -with archinstall.luks2(root, 'luksloop', disk_password) as unlocked_root: - unlocked_root.format(root.filesystem) - unlocked_root.mount('/mnt') +There are some examples in the `examples/` directory that should serve as a starting point. - boot.mount('/mnt/boot') +The following is a small example of how to script your own *interative* installation: -with archinstall.Installer('/mnt') as installation: - if installation.minimal_installation(hostname='minimal-arch'): - installation.add_bootloader() - - installation.add_additional_packages(['nano', 'wget', 'git']) - - # Optionally, install a profile of choice. - # In this case, we install a minimal profile that is empty - installation.install_profile('minimal') - - user = User('devel', 'devel', False) - installation.create_users(user) - installation.user_set_pw('root', 'airoot') +```python +from pathlib import Path + +from archinstall import Installer, ProfileConfiguration, profile_handler, User +from archinstall.default_profiles.minimal import MinimalProfile +from archinstall.lib.disk.device_model import FilesystemType +from archinstall.lib.disk.encryption_menu import DiskEncryptionMenu +from archinstall.lib.disk.filesystem import FilesystemHandler +from archinstall.lib.user_interaction.disk_conf import select_disk_config + +fs_type = FilesystemType('ext4') + +# Select a device to use for the installation +disk_config = select_disk_config() + +# Optional: ask for disk encryption configuration +data_store = {} +disk_encryption = DiskEncryptionMenu(disk_config.device_modifications, data_store).run() + +# initiate file handler with the disk config and the optional disk encryption config +fs_handler = FilesystemHandler(disk_config, disk_encryption) + +# perform all file operations +# WARNING: this will potentially format the filesystem and delete all data +fs_handler.perform_filesystem_operations() + +mountpoint = Path('/tmp') + +with Installer( + mountpoint, + disk_config, + disk_encryption=disk_encryption, + kernels=['linux'] +) as installation: + installation.mount_ordered_layout() + installation.minimal_installation(hostname='minimal-arch') + installation.add_additional_packages(['nano', 'wget', 'git']) + + # Optionally, install a profile of choice. + # In this case, we install a minimal profile that is empty + profile_config = ProfileConfiguration(MinimalProfile()) + profile_handler.install_profile_config(installation, profile_config) + + user = User('archinstall', 'password', True) + installation.create_users(user) ``` This installer will perform the following: -* Prompt the user to select a disk and disk-password -* Proceed to wipe the selected disk with a `GPT` partition table on a UEFI system and MBR on a BIOS system. -* Sets up a default 100% used disk with encryption. +* Prompt the user to configurate the disk partitioning +* Prompt the user to setup disk encryption +* Create a file handler instance for the configured disk and the optional disk encryption +* Perform the disk operations (WARNING: this will potentially format the disks and erase all data) * Installs a basic instance of Arch Linux *(base base-devel linux linux-firmware btrfs-progs efibootmgr)* * Installs and configures a bootloader to partition 0 on uefi. On BIOS, it sets the root to partition 0. * Install additional packages *(nano, wget, git)* +* Create a new user > **Creating your own ISO with this script on it:** Follow [ArchISO](https://wiki.archlinux.org/index.php/archiso)'s guide on how to create your own ISO. +## Script non-interactive automated installation + +For an example of a fully scripted, automated installation please see the example +[full_automated_installation.py](https://github.com/archlinux/archinstall/blob/master/examples/full_automated_installation.py) + ## Unattended installation based on MAC address -Archinstall comes with an [unattended](https://github.com/archlinux/archinstall/blob/master/examples/unattended.py) example which will look for a matching profile for the machine it is being run on, based on any local MAC address. -For instance, if the machine that [unattended](https://github.com/archlinux/archinstall/blob/master/examples/unattended.py) is run on has the MAC address `52:54:00:12:34:56` it will look for a profile called [profiles/52-54-00-12-34-56.py](https://github.com/archlinux/archinstall/blob/master/profiles/52-54-00-12-34-56.py). +Archinstall comes with an [unattended](https://github.com/archlinux/archinstall/blob/master/examples/mac_address_installation.py) +example which will look for a matching profile for the machine it is being run on, based on any local MAC address. +For instance, if the machine the code is executed on has the MAC address `52:54:00:12:34:56` it will look for a profile called +[52-54-00-12-34-56.py](https://github.com/archlinux/archinstall/default_profiles/tailored.py). If it's found, the unattended installation will commence and source that profile as its installation procedure. +# Profiles + +`archinstall` ships with a set of pre-defined profiles that can be chosen during the installation process. + +| *Desktop* | *Server* | +|---------------|------------| +| Awesome | Cockpit | +| Bspwm | Docker | +| Budgie | Lighttpd | +| Cinnamon | Mariadb | +| Cutefish | Nginx | +| Deepin | Postgresql | +| Enlightenment | Tomcat | +| Gnome | httpd | +| Kde | sshd | +| Lxqt | | +| Mate | | +| Qtile | | +| Sway | | +| Xfce4 | | +| i3-wm | | + +The definitions of the profiles and what packages they will install can be seen directly in the menu or +[default profiles](https://github.com/archlinux/archinstall/default_profiles) + + # Testing ## Using a Live ISO Image @@ -167,8 +221,7 @@ you can replace the version of archinstall with a new version and run that with 4. Now clone the latest repository with `git clone https://github.com/archlinux/archinstall` 5. Enter the repository with `cd archinstall` *At this stage, you can choose to check out a feature branch for instance with `git checkout v2.3.1-rc1`* -6. Build the project and install it using `python setup.py install` - *If you get a 'No Module named setuptools' error, run `pacman -S python-setuptools`* +6. Build the project and install it using `pip install` After this, running archinstall with `python -m archinstall` will run against whatever branch you chose in step 5. -- cgit v1.2.3-54-g00ecf From 02af5fb6b2e134b78e45266b82c1689a96606c19 Mon Sep 17 00:00:00 2001 From: Daemon Coder <11915375+codefiles@users.noreply.github.com> Date: Thu, 27 Apr 2023 11:16:05 -0400 Subject: Fix README.md links (#1768) --- README.md | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index 517621bc..720bd487 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ The installer also doubles as a python library to install Arch Linux and manage Or simply `git clone` the repo as it has no external dependencies *(but there are optional ones)*.
Or use `pip install --upgrade archinstall` to use as a library. -## Running the [guided](https://github.com/archlinux/archinstall/blob/master/examples/guided.py) installer +## Running the [guided](https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py) installer Assuming you are on an Arch Linux live-ISO: @@ -86,7 +86,7 @@ When doing so, attach the `/var/log/archinstall/install.log` to the issue ticket # Mission Statement -Archinstall promises to ship a [guided installer](https://github.com/archlinux/archinstall/blob/master/examples/guided.py) that follows +Archinstall promises to ship a [guided installer](https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py) that follows the [Arch Principles](https://wiki.archlinux.org/index.php/Arch_Linux#Principles) as well as a library to manage services, packages and other Arch Linux aspects. The guided installer will provide user-friendly options along the way, but the keyword here is options, they are optional and will never be forced upon anyone. @@ -178,33 +178,18 @@ For an example of a fully scripted, automated installation please see the exampl Archinstall comes with an [unattended](https://github.com/archlinux/archinstall/blob/master/examples/mac_address_installation.py) example which will look for a matching profile for the machine it is being run on, based on any local MAC address. For instance, if the machine the code is executed on has the MAC address `52:54:00:12:34:56` it will look for a profile called -[52-54-00-12-34-56.py](https://github.com/archlinux/archinstall/default_profiles/tailored.py). +[52-54-00-12-34-56.py](https://github.com/archlinux/archinstall/blob/master/archinstall/default_profiles/tailored.py). If it's found, the unattended installation will commence and source that profile as its installation procedure. # Profiles `archinstall` ships with a set of pre-defined profiles that can be chosen during the installation process. -| *Desktop* | *Server* | -|---------------|------------| -| Awesome | Cockpit | -| Bspwm | Docker | -| Budgie | Lighttpd | -| Cinnamon | Mariadb | -| Cutefish | Nginx | -| Deepin | Postgresql | -| Enlightenment | Tomcat | -| Gnome | httpd | -| Kde | sshd | -| Lxqt | | -| Mate | | -| Qtile | | -| Sway | | -| Xfce4 | | -| i3-wm | | +- [Desktop](https://github.com/archlinux/archinstall/tree/master/archinstall/default_profiles/desktops) +- [Server](https://github.com/archlinux/archinstall/tree/master/archinstall/default_profiles/servers) The definitions of the profiles and what packages they will install can be seen directly in the menu or -[default profiles](https://github.com/archlinux/archinstall/default_profiles) +[default profiles](https://github.com/archlinux/archinstall/tree/master/archinstall/default_profiles) # Testing -- cgit v1.2.3-54-g00ecf From 89cefb9a1c7d4c4968e7d8645149078e601c9d1c Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Fri, 12 May 2023 02:30:09 +1000 Subject: Cleanup imports and unused code (#1801) * Cleanup imports and unused code * Update build check * Keep deprecation exception * Simplify logging --------- Co-authored-by: Daniel Girtler --- .github/workflows/python-build.yml | 5 +- README.md | 2 +- archinstall/__init__.py | 85 +++-- archinstall/default_profiles/desktop.py | 4 +- archinstall/default_profiles/server.py | 9 +- archinstall/lib/boot.py | 111 ++++++ archinstall/lib/configuration.py | 114 +++++- archinstall/lib/disk/device_handler.py | 99 +++--- archinstall/lib/disk/device_model.py | 50 +-- archinstall/lib/disk/encryption_menu.py | 2 +- archinstall/lib/disk/fido.py | 11 +- archinstall/lib/disk/filesystem.py | 11 +- archinstall/lib/disk/partitioning_menu.py | 9 +- archinstall/lib/exceptions.py | 23 +- archinstall/lib/general.py | 88 ++--- archinstall/lib/global_menu.py | 36 +- archinstall/lib/hardware.py | 274 +++++++------- archinstall/lib/installer.py | 225 ++++++------ archinstall/lib/interactions/__init__.py | 20 ++ archinstall/lib/interactions/disk_conf.py | 393 +++++++++++++++++++++ archinstall/lib/interactions/general_conf.py | 243 +++++++++++++ archinstall/lib/interactions/locale_conf.py | 43 +++ archinstall/lib/interactions/manage_users_conf.py | 106 ++++++ archinstall/lib/interactions/network_conf.py | 172 +++++++++ archinstall/lib/interactions/system_conf.py | 117 ++++++ archinstall/lib/interactions/utils.py | 34 ++ archinstall/lib/locale.py | 68 ++++ archinstall/lib/locale_helpers.py | 176 --------- archinstall/lib/luks.py | 31 +- archinstall/lib/menu/abstract_menu.py | 9 +- archinstall/lib/menu/menu.py | 27 +- archinstall/lib/mirrors.py | 7 +- archinstall/lib/models/bootloader.py | 9 +- archinstall/lib/models/network_configuration.py | 14 +- archinstall/lib/networking.py | 53 +-- archinstall/lib/output.py | 154 +++++--- archinstall/lib/pacman.py | 7 +- archinstall/lib/plugins.py | 43 ++- archinstall/lib/profile/profile_menu.py | 2 +- archinstall/lib/profile/profiles_handler.py | 21 +- archinstall/lib/services.py | 11 - archinstall/lib/storage.py | 4 +- archinstall/lib/systemd.py | 110 ------ archinstall/lib/translationhandler.py | 14 +- archinstall/lib/user_interaction/__init__.py | 10 - archinstall/lib/user_interaction/disk_conf.py | 391 -------------------- archinstall/lib/user_interaction/general_conf.py | 244 ------------- archinstall/lib/user_interaction/locale_conf.py | 45 --- .../lib/user_interaction/manage_users_conf.py | 106 ------ archinstall/lib/user_interaction/network_conf.py | 173 --------- archinstall/lib/user_interaction/save_conf.py | 113 ------ archinstall/lib/user_interaction/system_conf.py | 117 ------ archinstall/lib/user_interaction/utils.py | 34 -- archinstall/lib/utils/util.py | 4 +- archinstall/scripts/guided.py | 35 +- archinstall/scripts/minimal.py | 30 +- archinstall/scripts/only_hd.py | 29 +- archinstall/scripts/swiss.py | 50 +-- archinstall/scripts/unattended.py | 9 +- examples/full_automated_installation.py | 11 +- examples/interactive_installation.py | 29 +- examples/mac_address_installation.py | 8 +- examples/minimal_installation.py | 21 +- examples/only_hd_installation.py | 6 +- pyproject.toml | 1 + 65 files changed, 2124 insertions(+), 2388 deletions(-) create mode 100644 archinstall/lib/boot.py create mode 100644 archinstall/lib/interactions/__init__.py create mode 100644 archinstall/lib/interactions/disk_conf.py create mode 100644 archinstall/lib/interactions/general_conf.py create mode 100644 archinstall/lib/interactions/locale_conf.py create mode 100644 archinstall/lib/interactions/manage_users_conf.py create mode 100644 archinstall/lib/interactions/network_conf.py create mode 100644 archinstall/lib/interactions/system_conf.py create mode 100644 archinstall/lib/interactions/utils.py create mode 100644 archinstall/lib/locale.py delete mode 100644 archinstall/lib/locale_helpers.py delete mode 100644 archinstall/lib/services.py delete mode 100644 archinstall/lib/systemd.py delete mode 100644 archinstall/lib/user_interaction/__init__.py delete mode 100644 archinstall/lib/user_interaction/disk_conf.py delete mode 100644 archinstall/lib/user_interaction/general_conf.py delete mode 100644 archinstall/lib/user_interaction/locale_conf.py delete mode 100644 archinstall/lib/user_interaction/manage_users_conf.py delete mode 100644 archinstall/lib/user_interaction/network_conf.py delete mode 100644 archinstall/lib/user_interaction/save_conf.py delete mode 100644 archinstall/lib/user_interaction/system_conf.py delete mode 100644 archinstall/lib/user_interaction/utils.py (limited to 'README.md') diff --git a/.github/workflows/python-build.yml b/.github/workflows/python-build.yml index f98ce160..950ff8f4 100644 --- a/.github/workflows/python-build.yml +++ b/.github/workflows/python-build.yml @@ -36,7 +36,10 @@ jobs: - name: Run archinstall run: | python -V - archinstall -v + archinstall --script guided -v + archinstall --script swiss -v + archinstall --script only_hd -v + archinstall --script minimal -v - uses: actions/upload-artifact@v3 with: name: archinstall diff --git a/README.md b/README.md index 720bd487..15646170 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ from archinstall.default_profiles.minimal import MinimalProfile from archinstall.lib.disk.device_model import FilesystemType from archinstall.lib.disk.encryption_menu import DiskEncryptionMenu from archinstall.lib.disk.filesystem import FilesystemHandler -from archinstall.lib.user_interaction.disk_conf import select_disk_config +from archinstall.lib.interactions.disk_conf import select_disk_config fs_type = FilesystemType('ext4') diff --git a/archinstall/__init__.py b/archinstall/__init__.py index 6f67d20f..992bd9fa 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -1,42 +1,75 @@ """Arch Linux installer - guided, templates etc.""" import importlib +import os from argparse import ArgumentParser, Namespace +from pathlib import Path +from typing import TYPE_CHECKING, Any, Dict, Union from .lib import disk from .lib import menu from .lib import models from .lib import packages - -from .lib.exceptions import * -from .lib.general import * -from .lib.hardware import * -from .lib.installer import __packages__, Installer, accessibility_tools_in_use -from .lib.locale_helpers import * -from .lib.luks import * -from .lib.mirrors import * -from .lib.networking import * -from .lib.output import * -from archinstall.lib.profile.profiles_handler import ProfileHandler, profile_handler -from .lib.profile.profile_menu import ProfileConfiguration -from .lib.services import * -from .lib.storage import * -from .lib.systemd import * -from .lib.user_interaction import * +from .lib import exceptions +from .lib import luks +from .lib import locale +from .lib import mirrors +from .lib import networking +from .lib import profile +from .lib import interactions +from . import default_profiles + +from .lib.hardware import SysInfo, AVAILABLE_GFX_DRIVERS +from .lib.installer import Installer, accessibility_tools_in_use +from .lib.output import ( + FormattedOutput, log, error, + check_log_permissions, debug, warn, info +) +from .lib.storage import storage from .lib.global_menu import GlobalMenu -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.boot import Boot +from .lib.translationhandler import TranslationHandler, Language, DeferredTranslation +from .lib.plugins import plugins, load_plugin +from .lib.configuration import ConfigurationOutput +from .lib.general import ( + generate_password, locate_binary, clear_vt100_escape_codes, + JsonEncoder, JSON, UNSAFE_JSON, SysCommandWorker, SysCommand, + run_custom_user_commands, json_stream_to_structure, secret +) + + +if TYPE_CHECKING: + _: Any -parser = ArgumentParser() __version__ = "2.5.6" storage['__version__'] = __version__ + # add the custome _ as a builtin, it can now be used anywhere in the # project to mark strings as translatable with _('translate me') DeferredTranslation.install() +check_log_permissions() + +# Log various information about hardware before starting the installation. This might assist in troubleshooting +debug(f"Hardware model detected: {SysInfo.sys_vendor()} {SysInfo.product_name()}; UEFI mode: {SysInfo.has_uefi()}") +debug(f"Processor model detected: {SysInfo.cpu_model()}") +debug(f"Memory statistics: {SysInfo.mem_available()} available out of {SysInfo.mem_total()} total installed") +debug(f"Virtualization detected: {SysInfo.virtualization()}; is VM: {SysInfo.is_vm()}") +debug(f"Graphics devices detected: {SysInfo._graphics_devices().keys()}") + +# For support reasons, we'll log the disk layout pre installation to match against post-installation layout +debug(f"Disk states before installing: {disk.disk_layouts()}") + + +if os.getuid() != 0: + print(_("Archinstall requires root privileges to run. See --help for more.")) + exit(1) + + +parser = ArgumentParser() + def define_arguments(): """ @@ -61,7 +94,7 @@ def define_arguments(): parser.add_argument("--plugin", nargs="?", type=str) -def parse_unspecified_argument_list(unknowns :list, multiple :bool = False, error :bool = False) -> dict: +def parse_unspecified_argument_list(unknowns :list, multiple :bool = False, err :bool = False) -> dict: """We accept arguments not defined to the parser. (arguments "ad hoc"). Internally argparse return to us a list of words so we have to parse its contents, manually. We accept following individual syntax for each argument @@ -105,14 +138,14 @@ def parse_unspecified_argument_list(unknowns :list, multiple :bool = False, erro config[last_key] = [config[last_key],element] else: config[last_key].append(element) - elif error: + elif err: raise ValueError(f"Entry {element} is not related to any argument") else: print(f" We ignore the entry {element} as it isn't related to any argument") return config -def cleanup_empty_args(args: Union[Namespace, dict]) -> dict: +def cleanup_empty_args(args: Union[Namespace, Dict]) -> Dict: """ Takes arguments (dictionary or argparse Namespace) and removes any None values. This ensures clean mergers during dict.update(args) @@ -190,14 +223,14 @@ def load_config(): arguments['disk_config'] = disk.DiskLayoutConfiguration.parse_arg(disk_config) if profile_config := arguments.get('profile_config', None): - arguments['profile_config'] = ProfileConfiguration.parse_arg(profile_config) + arguments['profile_config'] = profile.ProfileConfiguration.parse_arg(profile_config) 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]} + arguments['mirror-region'] = {selected_region: mirrors.list_mirrors()[selected_region]} if arguments.get('servers', None) is not None: storage['_selected_servers'] = arguments.get('servers', None) @@ -230,7 +263,7 @@ def post_process_arguments(arguments): storage['MOUNT_POINT'] = Path(mountpoint) if arguments.get('debug', False): - log(f"Warning: --debug mode will write certain credentials to {storage['LOG_PATH']}/{storage['LOG_FILE']}!", fg="red", level=logging.WARNING) + warn(f"Warning: --debug mode will write certain credentials to {storage['LOG_PATH']}/{storage['LOG_FILE']}!") if arguments.get('plugin', None): path = arguments['plugin'] diff --git a/archinstall/default_profiles/desktop.py b/archinstall/default_profiles/desktop.py index 2351bd08..9d92f822 100644 --- a/archinstall/default_profiles/desktop.py +++ b/archinstall/default_profiles/desktop.py @@ -1,7 +1,7 @@ from typing import Any, TYPE_CHECKING, List, Optional, Dict from archinstall.lib import menu -from archinstall.lib.output import log +from archinstall.lib.output import info from archinstall.lib.profile.profiles_handler import profile_handler from archinstall.default_profiles.profile import Profile, ProfileType, SelectResult, GreeterType @@ -79,7 +79,7 @@ class DesktopProfile(Profile): install_session.add_additional_packages(self.packages) for profile in self._current_selection: - log(f'Installing profile {profile.name}...') + info(f'Installing profile {profile.name}...') install_session.add_additional_packages(profile.packages) install_session.enable_service(profile.services) diff --git a/archinstall/default_profiles/server.py b/archinstall/default_profiles/server.py index e240b3ef..ab758975 100644 --- a/archinstall/default_profiles/server.py +++ b/archinstall/default_profiles/server.py @@ -1,7 +1,6 @@ -import logging from typing import Any, TYPE_CHECKING, List -from archinstall.lib.output import log +from archinstall.lib.output import info from archinstall.lib.menu import MenuSelectionType from archinstall.lib.profile.profiles_handler import profile_handler from archinstall.default_profiles.profile import ProfileType, Profile, SelectResult, TProfile @@ -46,12 +45,12 @@ class ServerProfile(Profile): def install(self, install_session: 'Installer'): server_info = self.current_selection_names() details = ', '.join(server_info) - log(f'Now installing the selected servers: {details}', level=logging.INFO) + info(f'Now installing the selected servers: {details}') for server in self._current_selection: - log(f'Installing {server.name}...', level=logging.INFO) + info(f'Installing {server.name}...') install_session.add_additional_packages(server.packages) install_session.enable_service(server.services) server.install(install_session) - log('If your selections included multiple servers with the same port, you may have to reconfigure them.', fg="yellow", level=logging.INFO) + info('If your selections included multiple servers with the same port, you may have to reconfigure them.') diff --git a/archinstall/lib/boot.py b/archinstall/lib/boot.py new file mode 100644 index 00000000..62c50df3 --- /dev/null +++ b/archinstall/lib/boot.py @@ -0,0 +1,111 @@ +import time +from typing import Iterator, Optional +from .exceptions import SysCallError +from .general import SysCommand, SysCommandWorker, locate_binary +from .installer import Installer +from .output import error +from .storage import storage + + +class Boot: + def __init__(self, installation: Installer): + self.instance = installation + self.container_name = 'archinstall' + self.session: Optional[SysCommandWorker] = None + self.ready = False + + def __enter__(self) -> 'Boot': + if (existing_session := storage.get('active_boot', None)) and existing_session.instance != self.instance: + raise KeyError("Archinstall only supports booting up one instance, and a active session is already active and it is not this one.") + + if existing_session: + self.session = existing_session.session + self.ready = existing_session.ready + else: + # '-P' or --console=pipe could help us not having to do a bunch + # of os.write() calls, but instead use pipes (stdin, stdout and stderr) as usual. + self.session = SysCommandWorker([ + '/usr/bin/systemd-nspawn', + '-D', str(self.instance.target), + '--timezone=off', + '-b', + '--no-pager', + '--machine', self.container_name + ]) + + if not self.ready and self.session: + while self.session.is_alive(): + if b' login:' in self.session: + self.ready = True + break + + storage['active_boot'] = self + return self + + def __exit__(self, *args :str, **kwargs :str) -> None: + # b''.join(sys_command('sync')) # No need to, since the underlying fs() object will call sync. + # TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager + + if len(args) >= 2 and args[1]: + error( + args[1], + f"The error above occurred in a temporary boot-up of the installation {self.instance}" + ) + + shutdown = None + shutdown_exit_code: Optional[int] = -1 + + try: + shutdown = SysCommand(f'systemd-run --machine={self.container_name} --pty shutdown now') + except SysCallError as err: + shutdown_exit_code = err.exit_code + + if self.session: + while self.session.is_alive(): + time.sleep(0.25) + + if shutdown and shutdown.exit_code: + shutdown_exit_code = shutdown.exit_code + + if self.session and (self.session.exit_code == 0 or shutdown_exit_code == 0): + storage['active_boot'] = None + else: + session_exit_code = self.session.exit_code if self.session else -1 + + raise SysCallError( + f"Could not shut down temporary boot of {self.instance}: {session_exit_code}/{shutdown_exit_code}", + exit_code=next(filter(bool, [session_exit_code, shutdown_exit_code])) + ) + + def __iter__(self) -> Iterator[bytes]: + if self.session: + for value in self.session: + yield value + + def __contains__(self, key: bytes) -> bool: + if self.session is None: + return False + + return key in self.session + + def is_alive(self) -> bool: + if self.session is None: + return False + + return self.session.is_alive() + + def SysCommand(self, cmd: list, *args, **kwargs) -> SysCommand: + if cmd[0][0] != '/' and cmd[0][:2] != './': + # This check is also done in SysCommand & SysCommandWorker. + # However, that check is done for `machinectl` and not for our chroot command. + # So this wrapper for SysCommand will do this additionally. + + cmd[0] = locate_binary(cmd[0]) + + return SysCommand(["systemd-run", f"--machine={self.container_name}", "--pty", *cmd], *args, **kwargs) + + def SysCommandWorker(self, cmd: list, *args, **kwargs) -> SysCommandWorker: + if cmd[0][0] != '/' and cmd[0][:2] != './': + cmd[0] = locate_binary(cmd[0]) + + return SysCommandWorker(["systemd-run", f"--machine={self.container_name}", "--pty", *cmd], *args, **kwargs) diff --git a/archinstall/lib/configuration.py b/archinstall/lib/configuration.py index 22c41c0d..c3af3a83 100644 --- a/archinstall/lib/configuration.py +++ b/archinstall/lib/configuration.py @@ -1,13 +1,13 @@ import os import json import stat -import logging from pathlib import Path from typing import Optional, Dict, Any, TYPE_CHECKING +from .menu import Menu, MenuSelectionType from .storage import storage -from .general import JSON, UNSAFE_JSON -from .output import log +from .general import JSON, UNSAFE_JSON, SysCommand +from .output import debug, info, warn if TYPE_CHECKING: _: Any @@ -69,18 +69,18 @@ class ConfigurationOutput: def show(self): print(_('\nThis is your chosen configuration:')) - log(" -- Chosen configuration --", level=logging.DEBUG) + debug(" -- Chosen configuration --") user_conig = self.user_config_to_json() - log(user_conig, level=logging.INFO) + info(user_conig) print() def _is_valid_path(self, dest_path: Path) -> bool: if (not dest_path.exists()) or not (dest_path.is_dir()): - log( - 'Destination directory {} does not exist or is not a directory,\n Configuration files can not be saved'.format(dest_path.resolve()), - fg="yellow" + warn( + f'Destination directory {dest_path.resolve()} does not exist or is not a directory\n.', + 'Configuration files can not be saved' ) return False return True @@ -111,3 +111,101 @@ class ConfigurationOutput: if self._is_valid_path(dest_path): self.save_user_config(dest_path) self.save_user_creds(dest_path) + + +def save_config(config: Dict): + def preview(selection: str): + if options['user_config'] == selection: + serialized = config_output.user_config_to_json() + return f'{config_output.user_configuration_file}\n{serialized}' + elif options['user_creds'] == selection: + if maybe_serial := config_output.user_credentials_to_json(): + return f'{config_output.user_credentials_file}\n{maybe_serial}' + else: + return str(_('No configuration')) + elif options['all'] == selection: + output = f'{config_output.user_configuration_file}\n' + if config_output.user_credentials_to_json(): + output += f'{config_output.user_credentials_file}\n' + return output[:-1] + return None + + config_output = ConfigurationOutput(config) + + options = { + 'user_config': str(_('Save user configuration')), + 'user_creds': str(_('Save user credentials')), + 'disk_layout': str(_('Save disk layout')), + 'all': str(_('Save all')) + } + + choice = Menu( + _('Choose which configuration to save'), + list(options.values()), + sort=False, + skip=True, + preview_size=0.75, + preview_command=preview + ).run() + + if choice.type_ == MenuSelectionType.Skip: + return + + save_config_value = choice.single_value + saving_key = [k for k, v in options.items() if v == save_config_value][0] + + dirs_to_exclude = [ + '/bin', + '/dev', + '/lib', + '/lib64', + '/lost+found', + '/opt', + '/proc', + '/run', + '/sbin', + '/srv', + '/sys', + '/usr', + '/var', + ] + + debug('Ignore configuration option folders: ' + ','.join(dirs_to_exclude)) + info(_('Finding possible directories to save configuration files ...')) + + find_exclude = '-path ' + ' -prune -o -path '.join(dirs_to_exclude) + ' -prune ' + file_picker_command = f'find / {find_exclude} -o -type d -print0' + + directories = SysCommand(file_picker_command).decode() + + if directories is None: + raise ValueError('Failed to retrieve possible configuration directories') + + possible_save_dirs = list(filter(None, directories.split('\x00'))) + + selection = Menu( + _('Select directory (or directories) for saving configuration files'), + possible_save_dirs, + multi=True, + skip=True, + allow_reset=False, + ).run() + + match selection.type_: + case MenuSelectionType.Skip: + return + + save_dirs = selection.multi_value + + debug(f'Saving {saving_key} configuration files to {save_dirs}') + + if save_dirs is not None: + for save_dir_str in save_dirs: + save_dir = Path(save_dir_str) + if options['user_config'] == save_config_value: + config_output.save_user_config(save_dir) + elif options['user_creds'] == save_config_value: + config_output.save_user_creds(save_dir) + elif options['all'] == save_config_value: + config_output.save_user_config(save_dir) + config_output.save_user_creds(save_dir) diff --git a/archinstall/lib/disk/device_handler.py b/archinstall/lib/disk/device_handler.py index 13bde77a..4341c53c 100644 --- a/archinstall/lib/disk/device_handler.py +++ b/archinstall/lib/disk/device_handler.py @@ -1,7 +1,6 @@ from __future__ import annotations import json -import logging import os import time from pathlib import Path @@ -24,7 +23,7 @@ from .device_model import ( from ..exceptions import DiskError, UnknownFilesystemFormat from ..general import SysCommand, SysCallError, JSON from ..luks import Luks2 -from ..output import log +from ..output import debug, error, info, warn from ..utils.util import is_subpath if TYPE_CHECKING: @@ -48,11 +47,11 @@ class DeviceHandler(object): for device in getAllDevices(): try: disk = Disk(device) - except DiskLabelException as error: - if 'unrecognised disk label' in getattr(error, 'message', str(error)): + except DiskLabelException as err: + if 'unrecognised disk label' in getattr(error, 'message', str(err)): disk = freshDisk(device, PartitionTable.GPT.value) else: - log(f'Unable to get disk from device: {device}', level=logging.DEBUG) + debug(f'Unable to get disk from device: {device}') continue device_info = _DeviceInfo.from_disk(disk) @@ -93,7 +92,7 @@ class DeviceHandler(object): return FilesystemType(lsblk_info.fstype) if lsblk_info.fstype else None return None except ValueError: - log(f'Could not determine the filesystem: {partition.fileSystem}', level=logging.DEBUG) + debug(f'Could not determine the filesystem: {partition.fileSystem}') return None @@ -137,7 +136,7 @@ class DeviceHandler(object): try: result = SysCommand(f'btrfs subvolume list {mountpoint}') except SysCallError as err: - log(f'Failed to read btrfs subvolume information: {err}', level=logging.DEBUG) + debug(f'Failed to read btrfs subvolume information: {err}') return subvol_infos try: @@ -150,7 +149,7 @@ class DeviceHandler(object): sub_vol_mountpoint = lsblk_info.btrfs_subvol_info.get(name, None) subvol_infos.append(_BtrfsSubvolumeInfo(name, sub_vol_mountpoint)) except json.decoder.JSONDecodeError as err: - log(f"Could not decode lsblk JSON: {result}", fg="red", level=logging.ERROR) + error(f"Could not decode lsblk JSON: {result}") raise err if not lsblk_info.mountpoint: @@ -203,14 +202,14 @@ class DeviceHandler(object): options += additional_parted_options options_str = ' '.join(options) - log(f'Formatting filesystem: /usr/bin/{command} {options_str} {path}') + info(f'Formatting filesystem: /usr/bin/{command} {options_str} {path}') try: SysCommand(f"/usr/bin/{command} {options_str} {path}") - except SysCallError as error: - msg = f'Could not format {path} with {fs_type.value}: {error.message}' - log(msg, fg='red') - raise DiskError(msg) from error + except SysCallError as err: + msg = f'Could not format {path} with {fs_type.value}: {err.message}' + error(msg) + raise DiskError(msg) from err def _perform_enc_formatting( self, @@ -227,16 +226,16 @@ class DeviceHandler(object): key_file = luks_handler.encrypt() - log(f'Unlocking luks2 device: {dev_path}', level=logging.DEBUG) + debug(f'Unlocking luks2 device: {dev_path}') luks_handler.unlock(key_file=key_file) if not luks_handler.mapper_dev: raise DiskError('Failed to unlock luks device') - log(f'luks2 formatting mapper dev: {luks_handler.mapper_dev}', level=logging.INFO) + info(f'luks2 formatting mapper dev: {luks_handler.mapper_dev}') self._perform_formatting(fs_type, luks_handler.mapper_dev) - log(f'luks2 locking device: {dev_path}', level=logging.INFO) + info(f'luks2 locking device: {dev_path}') luks_handler.lock() def format( @@ -285,7 +284,7 @@ class DeviceHandler(object): # when we require a delete and the partition to be (re)created # already exists then we have to delete it first if requires_delete and part_mod.status in [ModificationStatus.Modify, ModificationStatus.Delete]: - log(f'Delete existing partition: {part_mod.safe_dev_path}', level=logging.INFO) + info(f'Delete existing partition: {part_mod.safe_dev_path}') part_info = self.find_partition(part_mod.safe_dev_path) if not part_info: @@ -325,9 +324,9 @@ class DeviceHandler(object): for flag in part_mod.flags: partition.setFlag(flag.value) - log(f'\tType: {part_mod.type.value}', level=logging.DEBUG) - log(f'\tFilesystem: {part_mod.fs_type.value}', level=logging.DEBUG) - log(f'\tGeometry: {start_sector.value} start sector, {length_sector.value} length', level=logging.DEBUG) + debug(f'\tType: {part_mod.type.value}') + debug(f'\tFilesystem: {part_mod.fs_type.value}') + debug(f'\tGeometry: {start_sector.value} start sector, {length_sector.value} length') try: disk.addPartition(partition=partition, constraint=disk.device.optimalAlignedConstraint) @@ -339,41 +338,41 @@ class DeviceHandler(object): # the partition has a real path now as it was created part_mod.dev_path = Path(partition.path) - info = self._fetch_partuuid(part_mod.dev_path) + lsblk_info = self._fetch_partuuid(part_mod.dev_path) - part_mod.partuuid = info.partuuid - part_mod.uuid = info.uuid + part_mod.partuuid = lsblk_info.partuuid + part_mod.uuid = lsblk_info.uuid except PartitionException as ex: raise DiskError(f'Unable to add partition, most likely due to overlapping sectors: {ex}') from ex def _fetch_partuuid(self, path: Path) -> LsblkInfo: attempts = 3 - info: Optional[LsblkInfo] = None + lsblk_info: Optional[LsblkInfo] = None self.partprobe(path) for attempt_nr in range(attempts): time.sleep(attempt_nr + 1) - info = get_lsblk_info(path) + lsblk_info = get_lsblk_info(path) - if info.partuuid: + if lsblk_info.partuuid: break self.partprobe(path) - if not info or not info.partuuid: - log(f'Unable to determine new partition uuid: {path}\n{info}', level=logging.DEBUG) + if not lsblk_info or not lsblk_info.partuuid: + debug(f'Unable to determine new partition uuid: {path}\n{lsblk_info}') raise DiskError(f'Unable to determine new partition uuid: {path}') - log(f'partuuid found: {info.json()}', level=logging.DEBUG) + debug(f'partuuid found: {lsblk_info.json()}') - return info + return lsblk_info def create_btrfs_volumes( self, part_mod: PartitionModification, enc_conf: Optional['DiskEncryption'] = None ): - log(f'Creating subvolumes: {part_mod.safe_dev_path}', level=logging.INFO) + info(f'Creating subvolumes: {part_mod.safe_dev_path}') luks_handler = None @@ -396,7 +395,7 @@ class DeviceHandler(object): self.mount(part_mod.safe_dev_path, self._TMP_BTRFS_MOUNT, create_target_mountpoint=True) for sub_vol in part_mod.btrfs_subvols: - log(f'Creating subvolume: {sub_vol.name}', level=logging.DEBUG) + debug(f'Creating subvolume: {sub_vol.name}') if luks_handler is not None: subvol_path = self._TMP_BTRFS_MOUNT / sub_vol.name @@ -408,14 +407,14 @@ class DeviceHandler(object): if sub_vol.nodatacow: try: SysCommand(f'chattr +C {subvol_path}') - except SysCallError as error: - raise DiskError(f'Could not set nodatacow attribute at {subvol_path}: {error}') + except SysCallError as err: + raise DiskError(f'Could not set nodatacow attribute at {subvol_path}: {err}') if sub_vol.compress: try: SysCommand(f'chattr +c {subvol_path}') - except SysCallError as error: - raise DiskError(f'Could not set compress attribute at {subvol_path}: {error}') + except SysCallError as err: + raise DiskError(f'Could not set compress attribute at {subvol_path}: {err}') if luks_handler is not None and luks_handler.mapper_dev is not None: self.umount(luks_handler.mapper_dev) @@ -435,12 +434,12 @@ class DeviceHandler(object): return luks_handler def _umount_all_existing(self, modification: DeviceModification): - log(f'Unmounting all partitions: {modification.device_path}', level=logging.INFO) + info(f'Unmounting all partitions: {modification.device_path}') existing_partitions = self._devices[modification.device_path].partition_infos for partition in existing_partitions: - log(f'Unmounting: {partition.path}', level=logging.DEBUG) + debug(f'Unmounting: {partition.path}') # un-mount for existing encrypted partitions if partition.fs_type == FilesystemType.Crypto_luks: @@ -472,10 +471,10 @@ class DeviceHandler(object): part_table = partition_table.value if partition_table else None disk = freshDisk(modification.device.disk.device, part_table) else: - log(f'Use existing device: {modification.device_path}') + info(f'Use existing device: {modification.device_path}') disk = modification.device.disk - log(f'Creating partitions: {modification.device_path}') + info(f'Creating partitions: {modification.device_path}') # TODO sort by delete first @@ -507,7 +506,7 @@ class DeviceHandler(object): lsblk_info = get_lsblk_info(dev_path) if target_mountpoint in lsblk_info.mountpoints: - log(f'Device already mounted at {target_mountpoint}') + info(f'Device already mounted at {target_mountpoint}') return str_options = ','.join(options) @@ -517,7 +516,7 @@ class DeviceHandler(object): command = f'mount {mount_fs} {str_options} {dev_path} {target_mountpoint}' - log(f'Mounting {dev_path}: command', level=logging.DEBUG) + debug(f'Mounting {dev_path}: command') try: SysCommand(command) @@ -536,10 +535,10 @@ class DeviceHandler(object): raise ex if len(lsblk_info.mountpoints) > 0: - log(f'Partition {mountpoint} is currently mounted at: {[str(m) for m in lsblk_info.mountpoints]}', level=logging.DEBUG) + debug(f'Partition {mountpoint} is currently mounted at: {[str(m) for m in lsblk_info.mountpoints]}') for mountpoint in lsblk_info.mountpoints: - log(f'Unmounting mountpoint: {mountpoint}', level=logging.DEBUG) + debug(f'Unmounting mountpoint: {mountpoint}') command = 'umount' @@ -574,10 +573,10 @@ class DeviceHandler(object): command = 'partprobe' try: - log(f'Calling partprobe: {command}', level=logging.DEBUG) + debug(f'Calling partprobe: {command}') SysCommand(command) - except SysCallError as error: - log(f'"{command}" failed to run: {error}', level=logging.DEBUG) + except SysCallError as err: + error(f'"{command}" failed to run: {err}') def _wipe(self, dev_path: Path): """ @@ -594,7 +593,7 @@ class DeviceHandler(object): This is not intended to be secure, but rather to ensure that auto-discovery tools don't recognize anything here. """ - log(f'Wiping partitions and metadata: {block_device.device_info.path}') + info(f'Wiping partitions and metadata: {block_device.device_info.path}') for partition in block_device.partition_infos: self._wipe(partition.path) @@ -609,8 +608,8 @@ def disk_layouts() -> str: lsblk_info = get_all_lsblk_info() return json.dumps(lsblk_info, indent=4, sort_keys=True, cls=JSON) except SysCallError as err: - log(f"Could not return disk layouts: {err}", level=logging.WARNING, fg="yellow") + warn(f"Could not return disk layouts: {err}") return '' except json.decoder.JSONDecodeError as err: - log(f"Could not return disk layouts: {err}", level=logging.WARNING, fg="yellow") + warn(f"Could not return disk layouts: {err}") return '' diff --git a/archinstall/lib/disk/device_model.py b/archinstall/lib/disk/device_model.py index d57347b7..36dd0c4f 100644 --- a/archinstall/lib/disk/device_model.py +++ b/archinstall/lib/disk/device_model.py @@ -2,7 +2,6 @@ from __future__ import annotations import dataclasses import json -import logging import math import time import uuid @@ -18,7 +17,7 @@ from parted import Disk, Geometry, Partition from ..exceptions import DiskError, SysCallError from ..general import SysCommand -from ..output import log +from ..output import debug, error from ..storage import storage if TYPE_CHECKING: @@ -282,7 +281,7 @@ class _PartitionInfo: btrfs_subvol_infos: List[_BtrfsSubvolumeInfo] = field(default_factory=list) def as_json(self) -> Dict[str, Any]: - info = { + part_info = { 'Name': self.name, 'Type': self.type.value, 'Filesystem': self.fs_type.value if self.fs_type else str(_('Unknown')), @@ -293,9 +292,9 @@ class _PartitionInfo: } if self.btrfs_subvol_infos: - info['Btrfs vol.'] = f'{len(self.btrfs_subvol_infos)} subvolumes' + part_info['Btrfs vol.'] = f'{len(self.btrfs_subvol_infos)} subvolumes' - return info + return part_info @classmethod def from_partition( @@ -392,7 +391,7 @@ class SubvolumeModification: mods = [] for entry in subvol_args: if not entry.get('name', None) or not entry.get('mountpoint', None): - log(f'Subvolume arg is missing name: {entry}', level=logging.DEBUG) + debug(f'Subvolume arg is missing name: {entry}') continue mountpoint = Path(entry['mountpoint']) if entry['mountpoint'] else None @@ -705,7 +704,7 @@ class PartitionModification: """ Called for displaying data in table format """ - info = { + part_mod = { 'Status': self.status.value, 'Device': str(self.dev_path) if self.dev_path else '', 'Type': self.type.value, @@ -718,9 +717,9 @@ class PartitionModification: } if self.btrfs_subvols: - info['Btrfs vol.'] = f'{len(self.btrfs_subvols)} subvolumes' + part_mod['Btrfs vol.'] = f'{len(self.btrfs_subvols)} subvolumes' - return info + return part_mod @dataclass @@ -916,36 +915,36 @@ class LsblkInfo: @classmethod def from_json(cls, blockdevice: Dict[str, Any]) -> LsblkInfo: - info = cls() + lsblk_info = cls() for f in cls.fields(): lsblk_field = _clean_field(f, CleanType.Blockdevice) data_field = _clean_field(f, CleanType.Dataclass) val: Any = None - if isinstance(getattr(info, data_field), Path): + if isinstance(getattr(lsblk_info, data_field), Path): val = Path(blockdevice[lsblk_field]) - elif isinstance(getattr(info, data_field), Size): + elif isinstance(getattr(lsblk_info, data_field), Size): val = Size(blockdevice[lsblk_field], Unit.B) else: val = blockdevice[lsblk_field] - setattr(info, data_field, val) + setattr(lsblk_info, data_field, val) - info.children = [LsblkInfo.from_json(child) for child in blockdevice.get('children', [])] + lsblk_info.children = [LsblkInfo.from_json(child) for child in blockdevice.get('children', [])] # sometimes lsblk returns 'mountpoints': [null] - info.mountpoints = [Path(mnt) for mnt in info.mountpoints if mnt] + lsblk_info.mountpoints = [Path(mnt) for mnt in lsblk_info.mountpoints if mnt] fs_roots = [] - for r in info.fsroots: + for r in lsblk_info.fsroots: if r: path = Path(r) # store the fsroot entries without the leading / fs_roots.append(path.relative_to(path.anchor)) - info.fsroots = fs_roots + lsblk_info.fsroots = fs_roots - return info + return lsblk_info class CleanType(Enum): @@ -978,16 +977,16 @@ def _fetch_lsblk_info(dev_path: Optional[Union[Path, str]] = None, retry: int = try: result = SysCommand(f'lsblk --json -b -o+{lsblk_fields} {dev_path}') break - except SysCallError as error: + except SysCallError as err: # Get the output minus the message/info from lsblk if it returns a non-zero exit code. - if error.worker: - err = error.worker.decode('UTF-8') - log(f'Error calling lsblk: {err}', level=logging.DEBUG) + if err.worker: + err_str = err.worker.decode('UTF-8') + debug(f'Error calling lsblk: {err_str}') else: - raise error + raise err if retry_attempt == retry - 1: - raise error + raise err time.sleep(1) @@ -997,11 +996,12 @@ def _fetch_lsblk_info(dev_path: Optional[Union[Path, str]] = None, retry: int = blockdevices = block_devices['blockdevices'] return [LsblkInfo.from_json(device) for device in blockdevices] except json.decoder.JSONDecodeError as err: - log(f"Could not decode lsblk JSON: {result}", fg="red", level=logging.ERROR) + error(f"Could not decode lsblk JSON: {result}") raise err raise DiskError(f'Failed to read disk "{dev_path}" with lsblk') + def get_lsblk_info(dev_path: Union[Path, str]) -> LsblkInfo: if infos := _fetch_lsblk_info(dev_path): return infos[0] diff --git a/archinstall/lib/disk/encryption_menu.py b/archinstall/lib/disk/encryption_menu.py index 285270fb..8c64e65e 100644 --- a/archinstall/lib/disk/encryption_menu.py +++ b/archinstall/lib/disk/encryption_menu.py @@ -13,7 +13,7 @@ from ..menu import ( MenuSelectionType, TableMenu ) -from ..user_interaction.utils import get_password +from ..interactions.utils import get_password from ..menu import Menu from ..general import secret from .fido import Fido2Device, Fido2 diff --git a/archinstall/lib/disk/fido.py b/archinstall/lib/disk/fido.py index 2a53b551..97c38d84 100644 --- a/archinstall/lib/disk/fido.py +++ b/archinstall/lib/disk/fido.py @@ -1,13 +1,12 @@ from __future__ import annotations import getpass -import logging from pathlib import Path from typing import List, Optional from .device_model import PartitionModification, Fido2Device from ..general import SysCommand, SysCommandWorker, clear_vt100_escape_codes -from ..output import log +from ..output import error, info class Fido2: @@ -39,7 +38,7 @@ class Fido2: if not cls._loaded or reload: ret: Optional[str] = SysCommand(f"systemd-cryptenroll --fido2-device=list").decode('UTF-8') if not ret: - log('Unable to retrieve fido2 devices', level=logging.ERROR) + error('Unable to retrieve fido2 devices') return [] fido_devices: str = clear_vt100_escape_codes(ret) # type: ignore @@ -88,8 +87,4 @@ class Fido2: worker.write(bytes(getpass.getpass(" "), 'UTF-8')) pin_inputted = True - log( - f"You might need to touch the FIDO2 device to unlock it if no prompt comes up after 3 seconds.", - level=logging.INFO, - fg="yellow" - ) + info('You might need to touch the FIDO2 device to unlock it if no prompt comes up after 3 seconds') diff --git a/archinstall/lib/disk/filesystem.py b/archinstall/lib/disk/filesystem.py index 6ea99340..dc99afce 100644 --- a/archinstall/lib/disk/filesystem.py +++ b/archinstall/lib/disk/filesystem.py @@ -1,6 +1,5 @@ from __future__ import annotations -import logging import signal import sys import time @@ -8,8 +7,8 @@ from typing import Any, Optional, TYPE_CHECKING from .device_model import DiskLayoutConfiguration, DiskLayoutType, PartitionTable, FilesystemType, DiskEncryption from .device_handler import device_handler -from ..hardware import has_uefi -from ..output import log +from ..hardware import SysInfo +from ..output import debug from ..menu import Menu if TYPE_CHECKING: @@ -27,13 +26,13 @@ class FilesystemHandler: def perform_filesystem_operations(self, show_countdown: bool = True): if self._disk_config.config_type == DiskLayoutType.Pre_mount: - log('Disk layout configuration is set to pre-mount, not performing any operations', level=logging.DEBUG) + debug('Disk layout configuration is set to pre-mount, not performing any operations') return device_mods = list(filter(lambda x: len(x.partitions) > 0, self._disk_config.device_modifications)) if not device_mods: - log('No modifications required', level=logging.DEBUG) + debug('No modifications required') return device_paths = ', '.join([str(mod.device.device_info.path) for mod in device_mods]) @@ -48,7 +47,7 @@ class FilesystemHandler: # Setup the blockdevice, filesystem (and optionally encryption). # Once that's done, we'll hand over to perform_installation() partition_table = PartitionTable.GPT - if has_uefi() is False: + if SysInfo.has_uefi() is False: partition_table = PartitionTable.MBR for mod in device_mods: diff --git a/archinstall/lib/disk/partitioning_menu.py b/archinstall/lib/disk/partitioning_menu.py index 686e8c29..89cf6293 100644 --- a/archinstall/lib/disk/partitioning_menu.py +++ b/archinstall/lib/disk/partitioning_menu.py @@ -1,13 +1,12 @@ from __future__ import annotations -import logging from pathlib import Path from typing import Any, Dict, TYPE_CHECKING, List, Optional, Tuple from .device_model import PartitionModification, FilesystemType, BDevice, Size, Unit, PartitionType, PartitionFlag, \ ModificationStatus from ..menu import Menu, ListManager, MenuSelection, TextInput -from ..output import FormattedOutput, log +from ..output import FormattedOutput, warn from .subvolume_menu import SubvolumeMenu if TYPE_CHECKING: @@ -229,7 +228,7 @@ class PartitioningList(ListManager): if not start_sector or self._validate_sector(start_sector): break - log(f'Invalid start sector entered: {start_sector}', fg='red', level=logging.INFO) + warn(f'Invalid start sector entered: {start_sector}') if not start_sector: start_sector = str(largest_free_area.start) @@ -245,7 +244,7 @@ class PartitioningList(ListManager): if not end_value or self._validate_sector(start_sector, end_value): break - log(f'Invalid end sector entered: {start_sector}', fg='red', level=logging.INFO) + warn(f'Invalid end sector entered: {start_sector}') # override the default value with the user value if end_value: @@ -300,7 +299,7 @@ class PartitioningList(ListManager): if choice.value == Menu.no(): return [] - from ..user_interaction.disk_conf import suggest_single_disk_layout + from ..interactions.disk_conf import suggest_single_disk_layout device_modification = suggest_single_disk_layout(self._device) return device_modification.partitions diff --git a/archinstall/lib/exceptions.py b/archinstall/lib/exceptions.py index a66e4e04..53458d2c 100644 --- a/archinstall/lib/exceptions.py +++ b/archinstall/lib/exceptions.py @@ -3,6 +3,7 @@ from typing import Optional, TYPE_CHECKING if TYPE_CHECKING: from .general import SysCommandWorker + class RequirementError(BaseException): pass @@ -15,10 +16,6 @@ class UnknownFilesystemFormat(BaseException): pass -class ProfileError(BaseException): - pass - - class SysCallError(BaseException): def __init__(self, message :str, exit_code :Optional[int] = None, worker :Optional['SysCommandWorker'] = None) -> None: super(SysCallError, self).__init__(message) @@ -27,22 +24,10 @@ class SysCallError(BaseException): self.worker = worker -class PermissionError(BaseException): - pass - - -class ProfileNotFound(BaseException): - pass - - class HardwareIncompatibilityError(BaseException): pass -class UserError(BaseException): - pass - - class ServiceException(BaseException): pass @@ -51,9 +36,5 @@ class PackageError(BaseException): pass -class TranslationError(BaseException): - pass - - class Deprecated(BaseException): - pass \ No newline at end of file + pass diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py index 997b7d67..777ee90e 100644 --- a/archinstall/lib/general.py +++ b/archinstall/lib/general.py @@ -1,8 +1,6 @@ from __future__ import annotations -import hashlib import json -import logging import os import secrets import shlex @@ -18,9 +16,10 @@ import urllib.error import pathlib from datetime import datetime, date from typing import Callable, Optional, Dict, Any, List, Union, Iterator, TYPE_CHECKING +from select import epoll, EPOLLIN, EPOLLHUP from .exceptions import RequirementError, SysCallError -from .output import log +from .output import debug, error, info from .storage import storage @@ -28,42 +27,6 @@ if TYPE_CHECKING: from .installer import Installer -if sys.platform == 'linux': - from select import epoll, EPOLLIN, EPOLLHUP -else: - import select - EPOLLIN = 0 - EPOLLHUP = 0 - - class epoll(): - """ #!if windows - Create a epoll() implementation that simulates the epoll() behavior. - This so that the rest of the code doesn't need to worry weither we're using select() or epoll(). - """ - def __init__(self) -> None: - self.sockets: Dict[str, Any] = {} - self.monitoring: Dict[int, Any] = {} - - def unregister(self, fileno :int, *args :List[Any], **kwargs :Dict[str, Any]) -> None: - try: - del(self.monitoring[fileno]) # noqa: E275 - except: - pass - - def register(self, fileno :int, *args :int, **kwargs :Dict[str, Any]) -> None: - self.monitoring[fileno] = True - - def poll(self, timeout: float = 0.05, *args :str, **kwargs :Dict[str, Any]) -> List[Any]: - try: - return [[fileno, 1] for fileno in select.select(list(self.monitoring.keys()), [], [], timeout)[0]] - except OSError: - return [] - - -def gen_uid(entropy_length :int = 256) -> str: - return hashlib.sha512(os.urandom(entropy_length)).hexdigest() - - def generate_password(length :int = 64) -> str: haystack = string.printable # digits, ascii_letters, punctiation (!"#$[] etc) and whitespace return ''.join(secrets.choice(haystack) for i in range(length)) @@ -156,6 +119,7 @@ class JsonEncoder: else: return JsonEncoder._encode(obj) + class JSON(json.JSONEncoder, json.JSONDecoder): """ A safe JSON encoder that will omit private information in dicts (starting with !) @@ -166,6 +130,7 @@ class JSON(json.JSONEncoder, json.JSONDecoder): def encode(self, obj :Any) -> Any: return super(JSON, self).encode(self._encode(obj)) + class UNSAFE_JSON(json.JSONEncoder, json.JSONDecoder): """ UNSAFE_JSON will call/encode and keep private information in dicts (starting with !) @@ -269,7 +234,7 @@ class SysCommandWorker: sys.stdout.flush() if len(args) >= 2 and args[1]: - log(args[1], level=logging.DEBUG, fg='red') + debug(args[1]) if self.exit_code != 0: raise SysCallError( @@ -350,7 +315,7 @@ class SysCommandWorker: self.ended = time.time() break - if self.ended or (got_output is False and pid_exists(self.pid) is False): + if self.ended or (got_output is False and _pid_exists(self.pid) is False): self.ended = time.time() try: wait_status = os.waitpid(self.pid, 0)[1] @@ -396,15 +361,15 @@ class SysCommandWorker: pass except Exception as e: exception_type = type(e).__name__ - log(f"Unexpected {exception_type} occurred in {self.cmd}: {e}", level=logging.ERROR) + error(f"Unexpected {exception_type} occurred in {self.cmd}: {e}") raise e os.execve(self.cmd[0], list(self.cmd), {**os.environ, **self.environment_vars}) if storage['arguments'].get('debug'): - log(f"Executing: {self.cmd}", level=logging.DEBUG) + debug(f"Executing: {self.cmd}") except FileNotFoundError: - log(f"{self.cmd[0]} does not exist.", level=logging.ERROR, fg="red") + error(f"{self.cmd[0]} does not exist.") self.exit_code = 1 return False else: @@ -455,7 +420,7 @@ class SysCommand: # TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager if len(args) >= 2 and args[1]: - log(args[1], level=logging.ERROR, fg='red') + error(args[1]) def __iter__(self, *args :List[Any], **kwargs :Dict[str, Any]) -> Iterator[bytes]: if self.session: @@ -535,22 +500,7 @@ class SysCommand: return None -def prerequisite_check() -> bool: - """ - This function is used as a safety check before - continuing with an installation. - - Could be anything from checking that /boot is big enough - to check if nvidia hardware exists when nvidia driver was chosen. - """ - - return True - -def reboot(): - SysCommand("/usr/bin/reboot") - - -def pid_exists(pid: int) -> bool: +def _pid_exists(pid: int) -> bool: try: return any(subprocess.check_output(['/usr/bin/ps', '--no-headers', '-o', 'pid', '-p', str(pid)]).strip()) except subprocess.CalledProcessError: @@ -559,7 +509,7 @@ def pid_exists(pid: int) -> bool: def run_custom_user_commands(commands :List[str], installation :Installer) -> None: for index, command in enumerate(commands): - log(f'Executing custom command "{command}" ...', level=logging.INFO) + info(f'Executing custom command "{command}" ...') with open(f"{installation.target}/var/tmp/user-command.{index}.sh", "w") as temp_script: temp_script.write(command) @@ -568,6 +518,7 @@ def run_custom_user_commands(commands :List[str], installation :Installer) -> No os.unlink(f"{installation.target}/var/tmp/user-command.{index}.sh") + def json_stream_to_structure(configuration_identifier : str, stream :str, target :dict) -> bool : """ Function to load a stream (file (as name) or valid JSON string into an existing dictionary @@ -582,16 +533,16 @@ def json_stream_to_structure(configuration_identifier : str, stream :str, target 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") + except urllib.error.HTTPError as err: + error(f"Could not load {configuration_identifier} via {parsed_url} due to: {err}") return False else: if pathlib.Path(stream).exists(): try: with pathlib.Path(stream).open() as fh: target.update(json.load(fh)) - except Exception as error: - log(f"{configuration_identifier} = {stream} does not contain a valid JSON format: {error}", level=logging.ERROR, fg="red") + except Exception as err: + error(f"{configuration_identifier} = {stream} does not contain a valid JSON format: {err}") return False else: # NOTE: This is a rudimentary check if what we're trying parse is a dict structure. @@ -600,14 +551,15 @@ def json_stream_to_structure(configuration_identifier : str, stream :str, target try: target.update(json.loads(stream)) except Exception as e: - log(f" {configuration_identifier} Contains an invalid JSON format : {e}",level=logging.ERROR, fg="red") + error(f"{configuration_identifier} Contains an invalid JSON format: {e}") return False else: - log(f" {configuration_identifier} is neither a file nor is a JSON string:",level=logging.ERROR, fg="red") + error(f"{configuration_identifier} is neither a file nor is a JSON string") return False return True + def secret(x :str): """ return * with len equal to to the input string """ return '*' * len(x) diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index a969d93f..13595132 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -11,24 +11,24 @@ from .models.users import User from .output import FormattedOutput from .profile.profile_menu import ProfileConfiguration 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_ntp -from .user_interaction import ask_to_configure_network -from .user_interaction import get_password, ask_for_a_timezone -from .user_interaction import select_additional_repositories -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.disk_conf import select_disk_config -from .user_interaction.save_conf import save_config +from .configuration import save_config +from .interactions import add_number_of_parrallel_downloads +from .interactions import ask_additional_packages_to_install +from .interactions import ask_for_additional_users +from .interactions import ask_for_audio_selection +from .interactions import ask_for_bootloader +from .interactions import ask_for_swap +from .interactions import ask_hostname +from .interactions import ask_to_configure_network +from .interactions import get_password, ask_for_a_timezone +from .interactions import select_additional_repositories +from .interactions import select_kernel +from .interactions import select_language +from .interactions import select_locale_enc +from .interactions import select_locale_lang +from .interactions import select_mirror_regions +from .interactions import ask_ntp +from .interactions.disk_conf import select_disk_config if TYPE_CHECKING: _: Any diff --git a/archinstall/lib/hardware.py b/archinstall/lib/hardware.py index 3759725f..b95301f9 100644 --- a/archinstall/lib/hardware.py +++ b/archinstall/lib/hardware.py @@ -1,27 +1,12 @@ import os -import logging -from functools import partial +from functools import cached_property from pathlib import Path -from typing import Iterator, Optional, Dict +from typing import Optional, Dict from .general import SysCommand from .networking import list_interfaces, enrich_iface_types from .exceptions import SysCallError -from .output import log - -__packages__ = [ - "mesa", - "xf86-video-amdgpu", - "xf86-video-ati", - "xf86-video-nouveau", - "xf86-video-vmware", - "libva-mesa-driver", - "libva-intel-driver", - "intel-media-driver", - "vulkan-radeon", - "vulkan-intel", - "nvidia", -] +from .output import debug AVAILABLE_GFX_DRIVERS = { # Sub-dicts are layer-2 options to be selected @@ -62,136 +47,125 @@ AVAILABLE_GFX_DRIVERS = { } -def cpuinfo() -> Iterator[dict[str, str]]: - """ - Yields information about the CPUs of the system - """ - cpu_info_path = Path("/proc/cpuinfo") - cpu: Dict[str, str] = {} +class _SysInfo: + def __init__(self): + pass - with cpu_info_path.open() as file: - for line in file: - if not (line := line.strip()): - yield cpu - cpu = {} - continue - - key, value = line.split(":", maxsplit=1) - cpu[key.strip()] = value.strip() - - -def all_meminfo() -> Dict[str, int]: - """ - Returns a dict with memory info if called with no args - or the value of the given key of said dict. - """ - mem_info_path = Path("/proc/meminfo") - mem_info: Dict[str, int] = {} - - with mem_info_path.open() as file: - for line in file: - key, value = line.strip().split(':') - num = value.split()[0] - mem_info[key] = int(num) - - return mem_info - - -def meminfo_for_key(key: str) -> int: - info = all_meminfo() - return info[key] - - -def has_wifi() -> bool: - ifaces = list(list_interfaces().values()) - return 'WIRELESS' in enrich_iface_types(ifaces).values() - - -def has_cpu_vendor(vendor_id: str) -> bool: - return any(cpu.get("vendor_id") == vendor_id for cpu in cpuinfo()) - - -has_amd_cpu = partial(has_cpu_vendor, "AuthenticAMD") - - -has_intel_cpu = partial(has_cpu_vendor, "GenuineIntel") - - -def has_uefi() -> bool: - return os.path.isdir('/sys/firmware/efi') - - -def graphics_devices() -> dict: - cards = {} - for line in SysCommand("lspci"): - if b' VGA ' in line or b' 3D ' in line: - _, identifier = line.split(b': ', 1) - cards[identifier.strip().decode('UTF-8')] = line - return cards - - -def has_nvidia_graphics() -> bool: - return any('nvidia' in x.lower() for x in graphics_devices()) - - -def has_amd_graphics() -> bool: - return any('amd' in x.lower() for x in graphics_devices()) - - -def has_intel_graphics() -> bool: - return any('intel' in x.lower() for x in graphics_devices()) - - -def cpu_vendor() -> Optional[str]: - for cpu in cpuinfo(): - return cpu.get("vendor_id") - - return None - - -def cpu_model() -> Optional[str]: - for cpu in cpuinfo(): - return cpu.get("model name") - - return None - - -def sys_vendor() -> Optional[str]: - with open(f"/sys/devices/virtual/dmi/id/sys_vendor") as vendor: - return vendor.read().strip() - - -def product_name() -> Optional[str]: - with open(f"/sys/devices/virtual/dmi/id/product_name") as product: - return product.read().strip() - - -def mem_available() -> Optional[int]: - return meminfo_for_key('MemAvailable') - - -def mem_free() -> Optional[int]: - return meminfo_for_key('MemFree') - - -def mem_total() -> Optional[int]: - return meminfo_for_key('MemTotal') - - -def virtualization() -> Optional[str]: - try: - return str(SysCommand("systemd-detect-virt")).strip('\r\n') - except SysCallError as error: - log(f"Could not detect virtual system: {error}", level=logging.DEBUG) - - return None - - -def is_vm() -> bool: - try: - result = SysCommand("systemd-detect-virt") - return b"none" not in b"".join(result).lower() - except SysCallError as error: - log(f"System is not running in a VM: {error}", level=logging.DEBUG) - - return False + @cached_property + def cpu_info(self) -> Dict[str, str]: + """ + Returns system cpu information + """ + cpu_info_path = Path("/proc/cpuinfo") + cpu: Dict[str, str] = {} + + with cpu_info_path.open() as file: + for line in file: + if line := line.strip(): + key, value = line.split(":", maxsplit=1) + cpu[key.strip()] = value.strip() + + return cpu + + @cached_property + def mem_info(self) -> Dict[str, int]: + """ + Returns system memory information + """ + mem_info_path = Path("/proc/meminfo") + mem_info: Dict[str, int] = {} + + with mem_info_path.open() as file: + for line in file: + key, value = line.strip().split(':') + num = value.split()[0] + mem_info[key] = int(num) + + return mem_info + + def mem_info_by_key(self, key: str) -> int: + return self.mem_info[key] + + +_sys_info = _SysInfo() + + +class SysInfo: + @staticmethod + def has_wifi() -> bool: + ifaces = list(list_interfaces().values()) + return 'WIRELESS' in enrich_iface_types(ifaces).values() + + @staticmethod + def has_uefi() -> bool: + return os.path.isdir('/sys/firmware/efi') + + @staticmethod + def _graphics_devices() -> Dict[str, str]: + cards: Dict[str, str] = {} + for line in SysCommand("lspci"): + if b' VGA ' in line or b' 3D ' in line: + _, identifier = line.split(b': ', 1) + cards[identifier.strip().decode('UTF-8')] = str(line) + return cards + + @staticmethod + def has_nvidia_graphics() -> bool: + return any('nvidia' in x.lower() for x in SysInfo._graphics_devices()) + + @staticmethod + def has_amd_graphics() -> bool: + return any('amd' in x.lower() for x in SysInfo._graphics_devices()) + + @staticmethod + def has_intel_graphics() -> bool: + return any('intel' in x.lower() for x in SysInfo._graphics_devices()) + + @staticmethod + def cpu_vendor() -> Optional[str]: + return _sys_info.cpu_info.get('vendor_id', None) + + @staticmethod + def cpu_model() -> Optional[str]: + return _sys_info.cpu_info.get('model name', None) + + @staticmethod + def sys_vendor() -> str: + with open(f"/sys/devices/virtual/dmi/id/sys_vendor") as vendor: + return vendor.read().strip() + + @staticmethod + def product_name() -> str: + with open(f"/sys/devices/virtual/dmi/id/product_name") as product: + return product.read().strip() + + @staticmethod + def mem_available() -> int: + return _sys_info.mem_info_by_key('MemAvailable') + + @staticmethod + def mem_free() -> int: + return _sys_info.mem_info_by_key('MemFree') + + @staticmethod + def mem_total() -> int: + return _sys_info.mem_info_by_key('MemTotal') + + @staticmethod + def virtualization() -> Optional[str]: + try: + return str(SysCommand("systemd-detect-virt")).strip('\r\n') + except SysCallError as err: + debug(f"Could not detect virtual system: {err}") + + return None + + @staticmethod + def is_vm() -> bool: + try: + result = SysCommand("systemd-detect-virt") + return b"none" not in b"".join(result).lower() + except SysCallError as err: + debug(f"System is not running in a VM: {err}") + + return False diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 726ff3d0..3c427ab2 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -1,5 +1,4 @@ import glob -import logging import os import re import shlex @@ -12,17 +11,16 @@ from typing import Any, List, Optional, TYPE_CHECKING, Union, Dict, Callable, It from . import disk from .exceptions import DiskError, ServiceException, RequirementError, HardwareIncompatibilityError, SysCallError from .general import SysCommand -from .hardware import has_uefi, is_vm, cpu_vendor -from .locale_helpers import verify_keyboard_layout, verify_x11_keyboard_layout +from .hardware import SysInfo +from .locale import verify_keyboard_layout, verify_x11_keyboard_layout from .luks import Luks2 from .mirrors import use_mirrors from .models.bootloader import Bootloader from .models.network_configuration import NetworkConfiguration from .models.users import User -from .output import log +from .output import log, error, info, warn, debug from .pacman import run_pacman from .plugins import plugins -from .services import service_state from .storage import storage if TYPE_CHECKING: @@ -41,28 +39,6 @@ def accessibility_tools_in_use() -> bool: class Installer: - """ - `Installer()` is the wrapper for most basic installation steps. - It also wraps :py:func:`~archinstall.Installer.pacstrap` among other things. - - :param partition: Requires a partition as the first argument, this is - so that the installer can mount to `mountpoint` and strap packages there. - :type partition: class:`archinstall.Partition` - - :param boot_partition: There's two reasons for needing a boot partition argument, - The first being so that `mkinitcpio` can place the `vmlinuz` kernel at the right place - during the `pacstrap` or `linux` and the base packages for a minimal installation. - The second being when :py:func:`~archinstall.Installer.add_bootloader` is called, - A `boot_partition` must be known to the installer before this is called. - :type boot_partition: class:`archinstall.Partition` - - :param profile: A profile to install, this is optional and can be called later manually. - This just simplifies the process by not having to call :py:func:`~archinstall.Installer.install_profile` later on. - :type profile: str, optional - - :param hostname: The given /etc/hostname for the machine. - :type hostname: str, optional - """ def __init__( self, target: Path, @@ -71,6 +47,10 @@ class Installer: base_packages: List[str] = [], kernels: Optional[List[str]] = None ): + """ + `Installer()` is the wrapper for most basic installation steps. + It also wraps :py:func:`~archinstall.Installer.pacstrap` among other things. + """ if not base_packages: base_packages = __packages__[:3] @@ -126,7 +106,7 @@ class Installer: def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is not None: - log(exc_val, fg='red', level=logging.ERROR) + error(exc_val) self.sync_log_to_install_medium() @@ -137,48 +117,41 @@ class Installer: raise exc_val if not (missing_steps := self.post_install_check()): - self.log('Installation completed without any errors. You may now reboot.', fg='green', level=logging.INFO) + log('Installation completed without any errors. You may now reboot.', fg='green') self.sync_log_to_install_medium() return True else: - self.log('Some required steps were not successfully installed/configured before leaving the installer:', fg='red', level=logging.WARNING) + warn('Some required steps were not successfully installed/configured before leaving the installer:') for step in missing_steps: - self.log(f' - {step}', fg='red', level=logging.WARNING) + warn(f' - {step}') - self.log(f"Detailed error logs can be found at: {storage['LOG_PATH']}", level=logging.WARNING) - self.log("Submit this zip file as an issue to https://github.com/archlinux/archinstall/issues", level=logging.WARNING) + warn(f"Detailed error logs can be found at: {storage['LOG_PATH']}") + warn("Submit this zip file as an issue to https://github.com/archlinux/archinstall/issues") self.sync_log_to_install_medium() return False - def log(self, *args :str, level :int = logging.DEBUG, **kwargs :str): - """ - installer.log() wraps output.log() mainly to set a default log-level for this install session. - Any manual override can be done per log() call. - """ - log(*args, level=level, **kwargs) - def _verify_service_stop(self): """ Certain services might be running that affects the system during installation. One such service is "reflector.service" which updates /etc/pacman.d/mirrorlist We need to wait for it before we continue since we opted in to use a custom mirror/region. """ - log('Waiting for time sync (systemd-timesyncd.service) to complete.', level=logging.INFO) + info('Waiting for time sync (systemd-timesyncd.service) to complete.') while SysCommand('timedatectl show --property=NTPSynchronized --value').decode().rstrip() != 'yes': time.sleep(1) - log('Waiting for automatic mirror selection (reflector) to complete.', level=logging.INFO) - while service_state('reflector') not in ('dead', 'failed', 'exited'): + info('Waiting for automatic mirror selection (reflector) to complete.') + while self._service_state('reflector') not in ('dead', 'failed', 'exited'): time.sleep(1) - log('Waiting pacman-init.service to complete.', level=logging.INFO) - while service_state('pacman-init') not in ('dead', 'failed', 'exited'): + info('Waiting pacman-init.service to complete.') + while self._service_state('pacman-init') not in ('dead', 'failed', 'exited'): time.sleep(1) - log('Waiting Arch Linux keyring sync (archlinux-keyring-wkd-sync) to complete.', level=logging.INFO) - while service_state('archlinux-keyring-wkd-sync') not in ('dead', 'failed', 'exited'): + info('Waiting Arch Linux keyring sync (archlinux-keyring-wkd-sync) to complete.') + while self._service_state('archlinux-keyring-wkd-sync') not in ('dead', 'failed', 'exited'): time.sleep(1) def _verify_boot_part(self): @@ -204,7 +177,7 @@ class Installer: self._verify_service_stop() def mount_ordered_layout(self): - log('Mounting partitions in order', level=logging.INFO) + info('Mounting partitions in order') for mod in self._disk_config.device_modifications: # partitions have to mounted in the right order on btrfs the mountpoint will @@ -275,7 +248,7 @@ class Installer: ) if gen_enc_file and not part_mod.is_root(): - log(f'Creating key-file: {part_mod.dev_path}', level=logging.INFO) + info(f'Creating key-file: {part_mod.dev_path}') luks_handler.create_keyfile(self.target) if part_mod.is_root() and not gen_enc_file: @@ -384,25 +357,25 @@ class Installer: if (result := plugin.on_pacstrap(packages)): packages = result - self.log(f'Installing packages: {packages}', level=logging.INFO) + info(f'Installing packages: {packages}') # TODO: We technically only need to run the -Syy once. try: run_pacman('-Syy', default_cmd='/usr/bin/pacman') - except SysCallError as error: - self.log(f'Could not sync a new package database: {error}', level=logging.ERROR, fg="red") + except SysCallError as err: + error(f'Could not sync a new package database: {err}') if storage['arguments'].get('silent', False) is False: if input('Would you like to re-try this download? (Y/n): ').lower().strip() in ('', 'y'): return self._pacstrap(packages) - raise RequirementError(f'Could not sync mirrors: {error}') + raise RequirementError(f'Could not sync mirrors: {err}') try: SysCommand(f'/usr/bin/pacstrap -C /etc/pacman.conf -K {self.target} {" ".join(packages)} --noconfirm', peek_output=True) return True - except SysCallError as error: - self.log(f'Could not strap in packages: {error}', level=logging.ERROR, fg="red") + except SysCallError as err: + error(f'Could not strap in packages: {err}') if storage['arguments'].get('silent', False) is False: if input('Would you like to re-try this download? (Y/n): ').lower().strip() in ('', 'y'): @@ -420,12 +393,12 @@ class Installer: use_mirrors(mirrors, destination=destination) def genfstab(self, flags :str = '-pU'): - self.log(f"Updating {self.target}/etc/fstab", level=logging.INFO) + info(f"Updating {self.target}/etc/fstab") try: gen_fstab = SysCommand(f'/usr/bin/genfstab {flags} {self.target}').decode() - except SysCallError as error: - raise RequirementError(f'Could not generate fstab, strapping in packages most likely failed (disk out of space?)\n Error: {error}') + except SysCallError as err: + raise RequirementError(f'Could not generate fstab, strapping in packages most likely failed (disk out of space?)\n Error: {err}') if not gen_fstab: raise RequirementError(f'Genrating fstab returned empty value') @@ -530,24 +503,20 @@ class Installer: return True else: - self.log( - f"Time zone {zone} does not exist, continuing with system default.", - level=logging.WARNING, - fg='red' - ) + warn(f'Time zone {zone} does not exist, continuing with system default') return False def activate_time_syncronization(self) -> None: - self.log('Activating systemd-timesyncd for time synchronization using Arch Linux and ntp.org NTP servers.', level=logging.INFO) + info('Activating systemd-timesyncd for time synchronization using Arch Linux and ntp.org NTP servers') self.enable_service('systemd-timesyncd') def enable_espeakup(self) -> None: - self.log('Enabling espeakup.service for speech synthesis (accessibility).', level=logging.INFO) + info('Enabling espeakup.service for speech synthesis (accessibility)') self.enable_service('espeakup') def enable_periodic_trim(self) -> None: - self.log("Enabling periodic TRIM") + info("Enabling periodic TRIM") # fstrim is owned by util-linux, a dependency of both base and systemd. self.enable_service("fstrim.timer") @@ -556,12 +525,12 @@ class Installer: services = [services] for service in services: - self.log(f'Enabling service {service}', level=logging.INFO) + info(f'Enabling service {service}') try: self.arch_chroot(f'systemctl enable {service}') - except SysCallError as error: - raise ServiceException(f"Unable to start service {service}: {error}") + except SysCallError as err: + raise ServiceException(f"Unable to start service {service}: {err}") for plugin in plugins.values(): if hasattr(plugin, 'on_service'): @@ -713,11 +682,11 @@ class Installer: if 'encrypt' not in self._hooks: self._hooks.insert(self._hooks.index('filesystems'), 'encrypt') - if not has_uefi(): + if not SysInfo.has_uefi(): self.base_packages.append('grub') - if not is_vm(): - vendor = cpu_vendor() + if not SysInfo.is_vm(): + vendor = SysInfo.cpu_vendor() if vendor == "AuthenticAMD": self.base_packages.append("amd-ucode") if (ucode := Path(f"{self.target}/boot/amd-ucode.img")).exists(): @@ -727,21 +696,21 @@ class Installer: if (ucode := Path(f"{self.target}/boot/intel-ucode.img")).exists(): ucode.unlink() else: - self.log(f"Unknown CPU vendor '{vendor}' detected. Archinstall won't install any ucode.", level=logging.DEBUG) + debug(f"Unknown CPU vendor '{vendor}' detected. Archinstall won't install any ucode") # Determine whether to enable multilib/testing repositories before running pacstrap if testing flag is set. # This action takes place on the host system as pacstrap copies over package repository lists. if multilib: - self.log("The multilib flag is set. This system will be installed with the multilib repository enabled.") + info("The multilib flag is set. This system will be installed with the multilib repository enabled.") self.enable_multilib_repository() else: - self.log("The multilib flag is not set. This system will be installed without multilib repositories enabled.") + info("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.") + info("The testing flag is set. This system will be installed with testing repositories enabled.") self.enable_testing_repositories(multilib) else: - self.log("The testing flag is not set. This system will be installed without testing repositories enabled.") + info("The testing flag is not set. This system will be installed without testing repositories enabled.") self._pacstrap(self.base_packages) self.helper_flags['base-strapped'] = True @@ -773,7 +742,7 @@ class Installer: # Run registered post-install hooks for function in self.post_base_install: - self.log(f"Running post-installation hook: {function}", level=logging.INFO) + info(f"Running post-installation hook: {function}") function(self) for plugin in plugins.values(): @@ -782,7 +751,7 @@ class Installer: def setup_swap(self, kind :str = 'zram'): if kind == 'zram': - self.log(f"Setting up swap on zram") + info(f"Setting up swap on zram") self._pacstrap('zram-generator') # We could use the default example below, but maybe not the best idea: https://github.com/archlinux/archinstall/pull/678#issuecomment-962124813 @@ -812,7 +781,7 @@ class Installer: def _add_systemd_bootloader(self, root_partition: disk.PartitionModification): self._pacstrap('efibootmgr') - if not has_uefi(): + if not SysInfo.has_uefi(): raise HardwareIncompatibilityError # TODO: Ideally we would want to check if another config @@ -862,16 +831,18 @@ class Installer: entry.write(f'# Created on: {self.init_time}\n') entry.write(f'title Arch Linux ({kernel}{variant})\n') entry.write(f"linux /vmlinuz-{kernel}\n") - if not is_vm(): - vendor = cpu_vendor() + if not SysInfo.is_vm(): + vendor = SysInfo.cpu_vendor() if vendor == "AuthenticAMD": entry.write("initrd /amd-ucode.img\n") elif vendor == "GenuineIntel": entry.write("initrd /intel-ucode.img\n") else: - self.log( - f"Unknown CPU vendor '{vendor}' detected. Archinstall won't add any ucode to systemd-boot config.", - level=logging.DEBUG) + debug( + f"Unknown CPU vendor '{vendor}' detected.", + "Archinstall won't add any ucode to systemd-boot config.", + ) + entry.write(f"initrd /initramfs-{kernel}{variant}.img\n") # blkid doesn't trigger on loopback devices really well, # so we'll use the old manual method until we get that sorted out. @@ -890,7 +861,7 @@ class Installer: if root_partition.fs_type.is_crypto(): # TODO: We need to detect if the encrypted device is a whole disk encryption, # or simply a partition encryption. Right now we assume it's a partition (and we always have) - log('Root partition is an encrypted device, identifying by PARTUUID: {root_partition.partuuid}', level=logging.DEBUG) + debug('Root partition is an encrypted device, identifying by PARTUUID: {root_partition.partuuid}') kernel_options = f"options" @@ -905,7 +876,7 @@ class Installer: entry.write(f'{kernel_options} root=/dev/mapper/luksdev {options_entry}') else: - log(f'Identifying root partition by PARTUUID: {root_partition.partuuid}', level=logging.DEBUG) + debug(f'Identifying root partition by PARTUUID: {root_partition.partuuid}') entry.write(f'options root=PARTUUID={root_partition.partuuid} {options_entry}') self.helper_flags['bootloader'] = 'systemd' @@ -920,7 +891,7 @@ class Installer: _file = "/etc/default/grub" if root_partition.fs_type.is_crypto(): - log(f"Using UUID {root_partition.uuid} as encrypted root identifier", level=logging.DEBUG) + debug(f"Using UUID {root_partition.uuid} as encrypted root identifier") cmd_line_linux = f"sed -i 's/GRUB_CMDLINE_LINUX=\"\"/GRUB_CMDLINE_LINUX=\"cryptdevice=UUID={root_partition.uuid}:cryptlvm rootfstype={root_partition.fs_type.value}\"/'" enable_cryptdisk = "sed -i 's/#GRUB_ENABLE_CRYPTODISK=y/GRUB_ENABLE_CRYPTODISK=y/'" @@ -931,9 +902,9 @@ class Installer: SysCommand(f"/usr/bin/arch-chroot {self.target} {cmd_line_linux} {_file}") - log(f"GRUB boot partition: {boot_partition.dev_path}", level=logging.INFO) + info(f"GRUB boot partition: {boot_partition.dev_path}") - if has_uefi(): + if SysInfo.has_uefi(): self._pacstrap('efibootmgr') # TODO: Do we need? Yes, but remove from minimal_installation() instead? try: @@ -941,8 +912,8 @@ class Installer: except SysCallError: try: SysCommand(f'/usr/bin/arch-chroot {self.target} grub-install --debug --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB --removable', peek_output=True) - except SysCallError as error: - raise DiskError(f"Could not install GRUB to {self.target}/boot: {error}") + except SysCallError as err: + raise DiskError(f"Could not install GRUB to {self.target}/boot: {err}") else: device = disk.device_handler.get_device_by_partition_path(boot_partition.safe_dev_path) @@ -958,13 +929,13 @@ class Installer: f' --recheck {device.device_info.path}' SysCommand(cmd, peek_output=True) - except SysCallError as error: - raise DiskError(f"Failed to install GRUB boot on {boot_partition.dev_path}: {error}") + except SysCallError as err: + raise DiskError(f"Failed to install GRUB boot on {boot_partition.dev_path}: {err}") try: SysCommand(f'/usr/bin/arch-chroot {self.target} grub-mkconfig -o /boot/grub/grub.cfg') - except SysCallError as error: - raise DiskError(f"Could not configure GRUB: {error}") + except SysCallError as err: + raise DiskError(f"Could not configure GRUB: {err}") self.helper_flags['bootloader'] = "grub" @@ -975,7 +946,7 @@ class Installer: ): self._pacstrap('efibootmgr') - if not has_uefi(): + if not SysInfo.has_uefi(): raise HardwareIncompatibilityError # TODO: Ideally we would want to check if another config @@ -989,14 +960,14 @@ class Installer: kernel_parameters = [] - if not is_vm(): - vendor = cpu_vendor() + if not SysInfo.is_vm(): + vendor = SysInfo.cpu_vendor() if vendor == "AuthenticAMD": kernel_parameters.append("initrd=\\amd-ucode.img") elif vendor == "GenuineIntel": kernel_parameters.append("initrd=\\intel-ucode.img") else: - self.log(f"Unknown CPU vendor '{vendor}' detected. Archinstall won't add any ucode to firmware boot entry.", level=logging.DEBUG) + debug(f"Unknown CPU vendor '{vendor}' detected. Archinstall won't add any ucode to firmware boot entry.") kernel_parameters.append(f"initrd=\\initramfs-{kernel}.img") @@ -1006,10 +977,10 @@ class Installer: if root_partition.fs_type.is_crypto(): # TODO: We need to detect if the encrypted device is a whole disk encryption, # or simply a partition encryption. Right now we assume it's a partition (and we always have) - log(f'Identifying root partition by PARTUUID: {root_partition.partuuid}', level=logging.DEBUG) + debug(f'Identifying root partition by PARTUUID: {root_partition.partuuid}') kernel_parameters.append(f'cryptdevice=PARTUUID={root_partition.partuuid}:luksdev root=/dev/mapper/luksdev rw rootfstype={root_partition.fs_type.value} {" ".join(self._kernel_params)}') else: - log(f'Root partition is an encrypted device identifying by PARTUUID: {root_partition.partuuid}', level=logging.DEBUG) + debug(f'Root partition is an encrypted device identifying by PARTUUID: {root_partition.partuuid}') kernel_parameters.append(f'root=PARTUUID={root_partition.partuuid} rw rootfstype={root_partition.fs_type.value} {" ".join(self._kernel_params)}') device = disk.device_handler.get_device_by_partition_path(boot_partition.safe_dev_path) @@ -1060,7 +1031,7 @@ class Installer: if root_partition is None: raise ValueError(f'Could not detect root at mountpoint {self.target}') - self.log(f'Adding bootloader {bootloader.value} to {boot_partition.dev_path}', level=logging.INFO) + info(f'Adding bootloader {bootloader.value} to {boot_partition.dev_path}') match bootloader: case Bootloader.Systemd: @@ -1078,7 +1049,7 @@ class Installer: self.arch_chroot(f'systemctl enable --user {service}', run_as=user.username) def enable_sudo(self, entity: str, group :bool = False): - self.log(f'Enabling sudo permissions for {entity}.', level=logging.INFO) + info(f'Enabling sudo permissions for {entity}') sudoers_dir = f"{self.target}/etc/sudoers.d" @@ -1127,11 +1098,11 @@ class Installer: handled_by_plugin = result if not handled_by_plugin: - self.log(f'Creating user {user}', level=logging.INFO) + info(f'Creating user {user}') try: SysCommand(f'/usr/bin/arch-chroot {self.target} useradd -m -G wheel {user}') - except SysCallError as error: - raise SystemError(f"Could not create user inside installation: {error}") + except SysCallError as err: + raise SystemError(f"Could not create user inside installation: {err}") for plugin in plugins.values(): if hasattr(plugin, 'on_user_created'): @@ -1149,7 +1120,7 @@ class Installer: self.helper_flags['user'] = True def user_set_pw(self, user :str, password :str) -> bool: - self.log(f'Setting password for {user}', level=logging.INFO) + info(f'Setting password for {user}') if user == 'root': # This means the root account isn't locked/disabled with * in /etc/passwd @@ -1166,7 +1137,7 @@ class Installer: return False def user_set_shell(self, user :str, shell :str) -> bool: - self.log(f'Setting shell for {user} to {shell}', level=logging.INFO) + info(f'Setting shell for {user} to {shell}') try: SysCommand(f"/usr/bin/arch-chroot {self.target} sh -c \"chsh -s {shell} {user}\"") @@ -1183,49 +1154,59 @@ class Installer: return False def set_keyboard_language(self, language: str) -> bool: - log(f"Setting keyboard language to {language}", level=logging.INFO) + info(f"Setting keyboard language to {language}") + if len(language.strip()): if not verify_keyboard_layout(language): - self.log(f"Invalid keyboard language specified: {language}", fg="red", level=logging.ERROR) + error(f"Invalid keyboard language specified: {language}") return False # In accordance with https://github.com/archlinux/archinstall/issues/107#issuecomment-841701968 # Setting an empty keymap first, allows the subsequent call to set layout for both console and x11. - from .systemd import Boot + from .boot import Boot with Boot(self) as session: os.system('/usr/bin/systemd-run --machine=archinstall --pty localectl set-keymap ""') try: session.SysCommand(["localectl", "set-keymap", language]) - except SysCallError as error: - raise ServiceException(f"Unable to set locale '{language}' for console: {error}") + except SysCallError as err: + raise ServiceException(f"Unable to set locale '{language}' for console: {err}") - self.log(f"Keyboard language for this installation is now set to: {language}") + info(f"Keyboard language for this installation is now set to: {language}") else: - self.log('Keyboard language was not changed from default (no language specified).', fg="yellow", level=logging.INFO) + info('Keyboard language was not changed from default (no language specified)') return True def set_x11_keyboard_language(self, language: str) -> bool: - log(f"Setting x11 keyboard language to {language}", level=logging.INFO) """ A fallback function to set x11 layout specifically and separately from console layout. This isn't strictly necessary since .set_keyboard_language() does this as well. """ + info(f"Setting x11 keyboard language to {language}") + if len(language.strip()): if not verify_x11_keyboard_layout(language): - self.log(f"Invalid x11-keyboard language specified: {language}", fg="red", level=logging.ERROR) + error(f"Invalid x11-keyboard language specified: {language}") return False - from .systemd import Boot + from .boot import Boot with Boot(self) as session: session.SysCommand(["localectl", "set-x11-keymap", '""']) try: session.SysCommand(["localectl", "set-x11-keymap", language]) - except SysCallError as error: - raise ServiceException(f"Unable to set locale '{language}' for X11: {error}") + except SysCallError as err: + raise ServiceException(f"Unable to set locale '{language}' for X11: {err}") else: - self.log(f'X11-Keyboard language was not changed from default (no language specified).', fg="yellow", level=logging.INFO) + info(f'X11-Keyboard language was not changed from default (no language specified)') return True + + def _service_state(self, service_name: str) -> str: + if os.path.splitext(service_name)[1] != '.service': + service_name += '.service' # Just to be safe + + state = b''.join(SysCommand(f'systemctl show --no-pager -p SubState --value {service_name}', environment_vars={'SYSTEMD_COLORS': '0'})) + + return state.strip().decode('UTF-8') diff --git a/archinstall/lib/interactions/__init__.py b/archinstall/lib/interactions/__init__.py new file mode 100644 index 00000000..b5691a10 --- /dev/null +++ b/archinstall/lib/interactions/__init__.py @@ -0,0 +1,20 @@ +from .locale_conf import select_locale_lang, select_locale_enc +from .manage_users_conf import UserList, ask_for_additional_users +from .network_conf import ManualNetworkConfig, ask_to_configure_network +from .utils import get_password + +from .disk_conf import ( + select_devices, select_disk_config, get_default_partition_layout, + select_main_filesystem_format, suggest_single_disk_layout, + suggest_multi_disk_layout +) + +from .general_conf import ( + ask_ntp, ask_hostname, ask_for_a_timezone, ask_for_audio_selection, select_language, + select_mirror_regions, select_archinstall_language, ask_additional_packages_to_install, + add_number_of_parrallel_downloads, select_additional_repositories +) + +from .system_conf import ( + select_kernel, ask_for_bootloader, select_driver, ask_for_swap +) diff --git a/archinstall/lib/interactions/disk_conf.py b/archinstall/lib/interactions/disk_conf.py new file mode 100644 index 00000000..78e4cff4 --- /dev/null +++ b/archinstall/lib/interactions/disk_conf.py @@ -0,0 +1,393 @@ +from __future__ import annotations + +from pathlib import Path +from typing import Any, TYPE_CHECKING +from typing import Optional, List, Tuple + +from .. import disk +from ..hardware import SysInfo +from ..menu import Menu +from ..menu import TableMenu +from ..menu.menu import MenuSelectionType +from ..output import FormattedOutput, debug +from ..utils.util import prompt_dir + +if TYPE_CHECKING: + _: Any + + +def select_devices(preset: List[disk.BDevice] = []) -> List[disk.BDevice]: + """ + Asks the user to select one or multiple devices + + :return: List of selected devices + :rtype: list + """ + + def _preview_device_selection(selection: disk._DeviceInfo) -> Optional[str]: + dev = disk.device_handler.get_device(selection.path) + if dev and dev.partition_infos: + return FormattedOutput.as_table(dev.partition_infos) + return None + + if preset is None: + preset = [] + + title = str(_('Select one or more devices to use and configure')) + warning = str(_('If you reset the device selection this will also reset the current disk layout. Are you sure?')) + + devices = disk.device_handler.devices + options = [d.device_info for d in devices] + preset_value = [p.device_info for p in preset] + + choice = TableMenu( + title, + data=options, + multi=True, + preset=preset_value, + preview_command=_preview_device_selection, + preview_title=str(_('Existing Partitions')), + preview_size=0.2, + allow_reset=True, + allow_reset_warning_msg=warning + ).run() + + match choice.type_: + case MenuSelectionType.Reset: return [] + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Selection: + selected_device_info: List[disk._DeviceInfo] = choice.value # type: ignore + selected_devices = [] + + for device in devices: + if device.device_info in selected_device_info: + selected_devices.append(device) + + return selected_devices + + +def get_default_partition_layout( + devices: List[disk.BDevice], + filesystem_type: Optional[disk.FilesystemType] = None, + advanced_option: bool = False +) -> List[disk.DeviceModification]: + + if len(devices) == 1: + device_modification = suggest_single_disk_layout( + devices[0], + filesystem_type=filesystem_type, + advanced_options=advanced_option + ) + return [device_modification] + else: + return suggest_multi_disk_layout( + devices, + filesystem_type=filesystem_type, + advanced_options=advanced_option + ) + + +def _manual_partitioning( + preset: List[disk.DeviceModification], + devices: List[disk.BDevice] +) -> List[disk.DeviceModification]: + modifications = [] + for device in devices: + mod = next(filter(lambda x: x.device == device, preset), None) + if not mod: + mod = disk.DeviceModification(device, wipe=False) + + if partitions := disk.manual_partitioning(device, preset=mod.partitions): + mod.partitions = partitions + modifications.append(mod) + + return modifications + + +def select_disk_config( + preset: Optional[disk.DiskLayoutConfiguration] = None, + advanced_option: bool = False +) -> Optional[disk.DiskLayoutConfiguration]: + default_layout = disk.DiskLayoutType.Default.display_msg() + manual_mode = disk.DiskLayoutType.Manual.display_msg() + pre_mount_mode = disk.DiskLayoutType.Pre_mount.display_msg() + + options = [default_layout, manual_mode, pre_mount_mode] + preset_value = preset.config_type.display_msg() if preset else None + warning = str(_('Are you sure you want to reset this setting?')) + + choice = Menu( + _('Select a partitioning option'), + options, + allow_reset=True, + allow_reset_warning_msg=warning, + sort=False, + preview_size=0.2, + preset_values=preset_value + ).run() + + match choice.type_: + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Reset: return None + case MenuSelectionType.Selection: + if choice.single_value == pre_mount_mode: + output = "You will use whatever drive-setup is mounted at the specified directory\n" + output += "WARNING: Archinstall won't check the suitability of this setup\n" + + path = prompt_dir(str(_('Enter the root directory of the mounted devices: ')), output) + mods = disk.device_handler.detect_pre_mounted_mods(path) + + return disk.DiskLayoutConfiguration( + config_type=disk.DiskLayoutType.Pre_mount, + relative_mountpoint=path, + device_modifications=mods + ) + + preset_devices = [mod.device for mod in preset.device_modifications] if preset else [] + + devices = select_devices(preset_devices) + + if not devices: + return None + + if choice.value == default_layout: + modifications = get_default_partition_layout(devices, advanced_option=advanced_option) + if modifications: + return disk.DiskLayoutConfiguration( + config_type=disk.DiskLayoutType.Default, + device_modifications=modifications + ) + elif choice.value == manual_mode: + preset_mods = preset.device_modifications if preset else [] + modifications = _manual_partitioning(preset_mods, devices) + + if modifications: + return disk.DiskLayoutConfiguration( + config_type=disk.DiskLayoutType.Manual, + device_modifications=modifications + ) + + return None + + +def _boot_partition() -> disk.PartitionModification: + if SysInfo.has_uefi(): + start = disk.Size(1, disk.Unit.MiB) + size = disk.Size(512, disk.Unit.MiB) + else: + start = disk.Size(3, disk.Unit.MiB) + size = disk.Size(203, disk.Unit.MiB) + + # boot partition + return disk.PartitionModification( + status=disk.ModificationStatus.Create, + type=disk.PartitionType.Primary, + start=start, + length=size, + mountpoint=Path('/boot'), + fs_type=disk.FilesystemType.Fat32, + flags=[disk.PartitionFlag.Boot] + ) + + +def select_main_filesystem_format(advanced_options=False) -> disk.FilesystemType: + options = { + 'btrfs': disk.FilesystemType.Btrfs, + 'ext4': disk.FilesystemType.Ext4, + 'xfs': disk.FilesystemType.Xfs, + 'f2fs': disk.FilesystemType.F2fs + } + + if advanced_options: + options.update({'ntfs': disk.FilesystemType.Ntfs}) + + prompt = _('Select which filesystem your main partition should use') + choice = Menu(prompt, options, skip=False, sort=False).run() + return options[choice.single_value] + + +def suggest_single_disk_layout( + device: disk.BDevice, + filesystem_type: Optional[disk.FilesystemType] = None, + advanced_options: bool = False, + separate_home: Optional[bool] = None +) -> disk.DeviceModification: + if not filesystem_type: + filesystem_type = select_main_filesystem_format(advanced_options) + + min_size_to_allow_home_part = disk.Size(40, disk.Unit.GiB) + root_partition_size = disk.Size(20, disk.Unit.GiB) + using_subvolumes = False + using_home_partition = False + compression = False + device_size_gib = device.device_info.total_size + + if filesystem_type == disk.FilesystemType.Btrfs: + prompt = str(_('Would you like to use BTRFS subvolumes with a default structure?')) + choice = Menu(prompt, Menu.yes_no(), skip=False, default_option=Menu.yes()).run() + using_subvolumes = choice.value == Menu.yes() + + prompt = str(_('Would you like to use BTRFS compression?')) + choice = Menu(prompt, Menu.yes_no(), skip=False, default_option=Menu.yes()).run() + compression = choice.value == Menu.yes() + + device_modification = disk.DeviceModification(device, wipe=True) + + # Used for reference: https://wiki.archlinux.org/title/partitioning + # 2 MiB is unallocated for GRUB on BIOS. Potentially unneeded for other bootloaders? + + # TODO: On BIOS, /boot partition is only needed if the drive will + # be encrypted, otherwise it is not recommended. We should probably + # add a check for whether the drive will be encrypted or not. + + # Increase the UEFI partition if UEFI is detected. + # Also re-align the start to 1MiB since we don't need the first sectors + # like we do in MBR layouts where the boot loader is installed traditionally. + + boot_partition = _boot_partition() + device_modification.add_partition(boot_partition) + + if not using_subvolumes: + if device_size_gib >= min_size_to_allow_home_part: + if separate_home is None: + prompt = str(_('Would you like to create a separate partition for /home?')) + choice = Menu(prompt, Menu.yes_no(), skip=False, default_option=Menu.yes()).run() + using_home_partition = choice.value == Menu.yes() + elif separate_home is True: + using_home_partition = True + else: + using_home_partition = False + + # root partition + start = disk.Size(513, disk.Unit.MiB) if SysInfo.has_uefi() else disk.Size(206, disk.Unit.MiB) + + # Set a size for / (/root) + if using_subvolumes or device_size_gib < min_size_to_allow_home_part or not using_home_partition: + length = disk.Size(100, disk.Unit.Percent, total_size=device.device_info.total_size) + else: + length = min(device.device_info.total_size, root_partition_size) + + root_partition = disk.PartitionModification( + status=disk.ModificationStatus.Create, + type=disk.PartitionType.Primary, + start=start, + length=length, + mountpoint=Path('/') if not using_subvolumes else None, + fs_type=filesystem_type, + mount_options=['compress=zstd'] if compression else [], + ) + device_modification.add_partition(root_partition) + + if using_subvolumes: + # https://btrfs.wiki.kernel.org/index.php/FAQ + # https://unix.stackexchange.com/questions/246976/btrfs-subvolume-uuid-clash + # https://github.com/classy-giraffe/easy-arch/blob/main/easy-arch.sh + subvolumes = [ + disk.SubvolumeModification(Path('@'), Path('/')), + disk.SubvolumeModification(Path('@home'), Path('/home')), + disk.SubvolumeModification(Path('@log'), Path('/var/log')), + disk.SubvolumeModification(Path('@pkg'), Path('/var/cache/pacman/pkg')), + disk.SubvolumeModification(Path('@.snapshots'), Path('/.snapshots')) + ] + root_partition.btrfs_subvols = subvolumes + 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.. + # A second partition for /home would be nice if we have the space for it + home_partition = disk.PartitionModification( + status=disk.ModificationStatus.Create, + type=disk.PartitionType.Primary, + start=root_partition.length, + length=disk.Size(100, disk.Unit.Percent, total_size=device.device_info.total_size), + mountpoint=Path('/home'), + fs_type=filesystem_type, + mount_options=['compress=zstd'] if compression else [] + ) + device_modification.add_partition(home_partition) + + return device_modification + + +def suggest_multi_disk_layout( + devices: List[disk.BDevice], + filesystem_type: Optional[disk.FilesystemType] = None, + advanced_options: bool = False +) -> List[disk.DeviceModification]: + if not devices: + return [] + + # Not really a rock solid foundation of information to stand on, but it's a start: + # https://www.reddit.com/r/btrfs/comments/m287gp/partition_strategy_for_two_physical_disks/ + # https://www.reddit.com/r/btrfs/comments/9us4hr/what_is_your_btrfs_partitionsubvolumes_scheme/ + min_home_partition_size = disk.Size(40, disk.Unit.GiB) + # rough estimate taking in to account user desktops etc. TODO: Catch user packages to detect size? + desired_root_partition_size = disk.Size(20, disk.Unit.GiB) + compression = False + + if not filesystem_type: + filesystem_type = select_main_filesystem_format(advanced_options) + + # find proper disk for /home + possible_devices = list(filter(lambda x: x.device_info.total_size >= min_home_partition_size, devices)) + home_device = max(possible_devices, key=lambda d: d.device_info.total_size) if possible_devices else None + + # find proper device for /root + devices_delta = {} + for device in devices: + if device is not home_device: + delta = device.device_info.total_size - desired_root_partition_size + devices_delta[device] = delta + + sorted_delta: List[Tuple[disk.BDevice, Any]] = sorted(devices_delta.items(), key=lambda x: x[1]) + root_device: Optional[disk.BDevice] = sorted_delta[0][0] + + if home_device is None or root_device is None: + text = _('The selected drives do not have the minimum capacity required for an automatic suggestion\n') + text += _('Minimum capacity for /home partition: {}GiB\n').format(min_home_partition_size.format_size(disk.Unit.GiB)) + text += _('Minimum capacity for Arch Linux partition: {}GiB').format(desired_root_partition_size.format_size(disk.Unit.GiB)) + Menu(str(text), [str(_('Continue'))], skip=False).run() + return [] + + if filesystem_type == disk.FilesystemType.Btrfs: + prompt = str(_('Would you like to use BTRFS compression?')) + choice = Menu(prompt, Menu.yes_no(), skip=False, default_option=Menu.yes()).run() + compression = choice.value == Menu.yes() + + device_paths = ', '.join([str(d.device_info.path) for d in devices]) + + debug(f"Suggesting multi-disk-layout for devices: {device_paths}") + debug(f"/root: {root_device.device_info.path}") + debug(f"/home: {home_device.device_info.path}") + + root_device_modification = disk.DeviceModification(root_device, wipe=True) + home_device_modification = disk.DeviceModification(home_device, wipe=True) + + # add boot partition to the root device + boot_partition = _boot_partition() + root_device_modification.add_partition(boot_partition) + + # add root partition to the root device + root_partition = disk.PartitionModification( + status=disk.ModificationStatus.Create, + type=disk.PartitionType.Primary, + start=disk.Size(513, disk.Unit.MiB) if SysInfo.has_uefi() else disk.Size(206, disk.Unit.MiB), + length=disk.Size(100, disk.Unit.Percent, total_size=root_device.device_info.total_size), + mountpoint=Path('/'), + mount_options=['compress=zstd'] if compression else [], + fs_type=filesystem_type + ) + root_device_modification.add_partition(root_partition) + + # add home partition to home device + home_partition = disk.PartitionModification( + status=disk.ModificationStatus.Create, + type=disk.PartitionType.Primary, + start=disk.Size(1, disk.Unit.MiB), + length=disk.Size(100, disk.Unit.Percent, total_size=home_device.device_info.total_size), + mountpoint=Path('/home'), + mount_options=['compress=zstd'] if compression else [], + fs_type=filesystem_type, + ) + home_device_modification.add_partition(home_partition) + + return [root_device_modification, home_device_modification] diff --git a/archinstall/lib/interactions/general_conf.py b/archinstall/lib/interactions/general_conf.py new file mode 100644 index 00000000..5fcfa633 --- /dev/null +++ b/archinstall/lib/interactions/general_conf.py @@ -0,0 +1,243 @@ +from __future__ import annotations + +import pathlib +from typing import List, Any, Optional, Dict, TYPE_CHECKING + +from ..locale import list_keyboard_languages, list_timezones +from ..menu import MenuSelectionType, Menu, TextInput +from ..mirrors import list_mirrors +from ..output import warn +from ..packages.packages import validate_package_list +from ..storage import storage +from ..translationhandler import Language + +if TYPE_CHECKING: + _: Any + + +def ask_ntp(preset: bool = True) -> bool: + prompt = str(_('Would you like to use automatic time synchronization (NTP) with the default time servers?\n')) + prompt += str(_('Hardware time and other post-configuration steps might be required in order for NTP to work.\nFor more information, please check the Arch wiki')) + if preset: + preset_val = Menu.yes() + else: + preset_val = Menu.no() + choice = Menu(prompt, Menu.yes_no(), skip=False, preset_values=preset_val, default_option=Menu.yes()).run() + + return False if choice.value == Menu.no() else True + + +def ask_hostname(preset: str = '') -> str: + while True: + hostname = TextInput( + str(_('Desired hostname for the installation: ')), + preset + ).run().strip() + + if hostname: + return hostname + + +def ask_for_a_timezone(preset: Optional[str] = None) -> Optional[str]: + timezones = list_timezones() + default = 'UTC' + + choice = Menu( + _('Select a timezone'), + list(timezones), + preset_values=preset, + default_option=default + ).run() + + match choice.type_: + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Selection: return choice.single_value + + return None + + +def ask_for_audio_selection(desktop: bool = True, preset: Optional[str] = None) -> Optional[str]: + no_audio = str(_('No audio server')) + choices = ['pipewire', 'pulseaudio'] if desktop else ['pipewire', 'pulseaudio', no_audio] + default = 'pipewire' if desktop else no_audio + + choice = Menu(_('Choose an audio server'), choices, preset_values=preset, default_option=default).run() + + match choice.type_: + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Selection: return choice.single_value + + return None + + +def select_language(preset: Optional[str] = None) -> Optional[str]: + """ + Asks the user to select a language + Usually this is combined with :ref:`archinstall.list_keyboard_languages`. + + :return: The language/dictionary key of the selected language + :rtype: str + """ + kb_lang = list_keyboard_languages() + # sort alphabetically and then by length + sorted_kb_lang = sorted(sorted(list(kb_lang)), key=len) + + choice = Menu( + _('Select keyboard layout'), + sorted_kb_lang, + preset_values=preset, + sort=False + ).run() + + match choice.type_: + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Selection: return choice.single_value + + return None + + +def select_mirror_regions(preset_values: Dict[str, Any] = {}) -> Dict[str, Any]: + """ + Asks the user to select a mirror or region + Usually this is combined with :ref:`archinstall.list_mirrors`. + + :return: The dictionary information about a mirror/region. + :rtype: dict + """ + if preset_values is None: + preselected = None + else: + preselected = list(preset_values.keys()) + + mirrors = list_mirrors() + + choice = Menu( + _('Select one of the regions to download packages from'), + list(mirrors.keys()), + preset_values=preselected, + multi=True, + allow_reset=True + ).run() + + match choice.type_: + case MenuSelectionType.Reset: + return {} + case MenuSelectionType.Skip: + return preset_values + case MenuSelectionType.Selection: + return {selected: mirrors[selected] for selected in choice.multi_value} + + return {} + + +def select_archinstall_language(languages: List[Language], preset: 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} + + title = 'NOTE: If a language can not displayed properly, a proper font must be set manually in the console.\n' + title += 'All available fonts can be found in "/usr/share/kbd/consolefonts"\n' + title += 'e.g. setfont LatGrkCyr-8x16 (to display latin/greek/cyrillic characters)\n' + + choice = Menu( + title, + list(options.keys()), + default_option=preset.display_name, + preview_size=0.5 + ).run() + + match choice.type_: + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Selection: return options[choice.single_value] + + raise ValueError('Language selection not handled') + + +def ask_additional_packages_to_install(pre_set_packages: List[str] = []) -> List[str]: + # Additional packages (with some light weight error handling for invalid package names) + print(_('Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed.')) + print(_('If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt.')) + + 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().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) + + 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: + warn(f"Some packages could not be found in the repository: {invalid}") + packages = read_packages(valid) + continue + break + + return packages + + +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(_("[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(_(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: + 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+1}\n") if not input_number == 0 else fwrite.write("#ParallelDownloads = 0\n") + else: + fwrite.write(f"{line}\n") + + return input_number + + +def select_additional_repositories(preset: List[str]) -> List[str]: + """ + Allows the user to select additional repositories (multilib, and testing) if desired. + + :return: The string as a selected repository + :rtype: string + """ + + repositories = ["multilib", "testing"] + + choice = Menu( + _('Choose which optional additional repositories to enable'), + repositories, + sort=False, + multi=True, + preset_values=preset, + allow_reset=True + ).run() + + match choice.type_: + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Reset: return [] + case MenuSelectionType.Selection: return choice.single_value + + return [] diff --git a/archinstall/lib/interactions/locale_conf.py b/archinstall/lib/interactions/locale_conf.py new file mode 100644 index 00000000..de115202 --- /dev/null +++ b/archinstall/lib/interactions/locale_conf.py @@ -0,0 +1,43 @@ +from typing import Any, TYPE_CHECKING, Optional + +from ..locale import list_locales +from ..menu import Menu, MenuSelectionType + +if TYPE_CHECKING: + _: Any + + +def select_locale_lang(preset: Optional[str] = None) -> Optional[str]: + locales = list_locales() + locale_lang = set([locale.split()[0] for locale in locales]) + + choice = Menu( + _('Choose which locale language to use'), + list(locale_lang), + sort=True, + preset_values=preset + ).run() + + match choice.type_: + case MenuSelectionType.Selection: return choice.single_value + case MenuSelectionType.Skip: return preset + + return None + + +def select_locale_enc(preset: Optional[str] = None) -> Optional[str]: + locales = list_locales() + locale_enc = set([locale.split()[1] for locale in locales]) + + choice = Menu( + _('Choose which locale encoding to use'), + list(locale_enc), + sort=True, + preset_values=preset + ).run() + + match choice.type_: + case MenuSelectionType.Selection: return choice.single_value + case MenuSelectionType.Skip: return preset + + return None diff --git a/archinstall/lib/interactions/manage_users_conf.py b/archinstall/lib/interactions/manage_users_conf.py new file mode 100644 index 00000000..879578da --- /dev/null +++ b/archinstall/lib/interactions/manage_users_conf.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +import re +from typing import Any, Dict, TYPE_CHECKING, List, Optional + +from .utils import get_password +from ..menu import Menu, ListManager +from ..models.users import User +from ..output import FormattedOutput + +if TYPE_CHECKING: + _: Any + + +class UserList(ListManager): + """ + subclass of ListManager for the managing of user accounts + """ + + def __init__(self, prompt: str, lusers: List[User]): + self._actions = [ + str(_('Add a user')), + str(_('Change password')), + str(_('Promote/Demote user')), + str(_('Delete User')) + ] + super().__init__(prompt, lusers, [self._actions[0]], self._actions[1:]) + + def reformat(self, data: List[User]) -> Dict[str, Any]: + table = FormattedOutput.as_table(data) + rows = table.split('\n') + + # 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[User]] = {f' {rows[0]}': None, f' {rows[1]}': None} + + for row, user in zip(rows[2:], data): + row = row.replace('|', '\\|') + display_data[row] = user + + return display_data + + def selected_action_display(self, user: User) -> str: + return user.username + + 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 action == self._actions[1] and entry: # 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 == entry, data)) + user.password = new_password + elif action == self._actions[2] and entry: # promote/demote + user = next(filter(lambda x: x == entry, data)) + user.sudo = False if user.sudo else True + elif action == self._actions[3] and entry: # delete + data = [d for d in data if d != entry] + + return data + + def _check_for_correct_username(self, username: str) -> bool: + if re.match(r'^[a-z_][a-z0-9_-]*\$?$', username) and len(username) <= 32: + return True + return False + + def _add_user(self) -> Optional[User]: + prompt = '\n\n' + str(_('Enter username (leave blank to skip): ')) + + while True: + username = input(prompt).strip(' ') + if not username: + return None + if not self._check_for_correct_username(username): + error_prompt = str(_("The username you entered is invalid. Try again")) + print(error_prompt) + else: + break + + password = get_password(prompt=str(_('Password for user "{}": ').format(username))) + + if not password: + return None + + choice = Menu( + str(_('Should "{}" be a superuser (sudo)?')).format(username), Menu.yes_no(), + skip=False, + default_option=Menu.yes(), + clear_screen=False, + show_search_hint=False + ).run() + + sudo = True if choice.value == Menu.yes() else False + return User(username, password, sudo) + + +def ask_for_additional_users(prompt: str = '', defined_users: List[User] = []) -> List[User]: + users = UserList(prompt, defined_users).run() + return users diff --git a/archinstall/lib/interactions/network_conf.py b/archinstall/lib/interactions/network_conf.py new file mode 100644 index 00000000..18a834a1 --- /dev/null +++ b/archinstall/lib/interactions/network_conf.py @@ -0,0 +1,172 @@ +from __future__ import annotations + +import ipaddress +from typing import Any, Optional, TYPE_CHECKING, List, Union, Dict + +from ..menu import MenuSelectionType, TextInput +from ..models.network_configuration import NetworkConfiguration, NicType + +from ..networking import list_interfaces +from ..output import FormattedOutput, warn +from ..menu import ListManager, Menu + +if TYPE_CHECKING: + _: Any + + +class ManualNetworkConfig(ListManager): + """ + subclass of ListManager for the managing of network configurations + """ + + def __init__(self, prompt: str, ifaces: List[NetworkConfiguration]): + self._actions = [ + str(_('Add interface')), + str(_('Edit interface')), + str(_('Delete interface')) + ] + + 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) + rows = table.split('\n') + + # 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} + + for row, iface in zip(rows[2:], data): + row = row.replace('|', '\\|') + display_data[row] = iface + + return display_data + + def selected_action_display(self, iface: NetworkConfiguration) -> str: + return iface.iface if iface.iface else '' + + 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 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 + + 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() + + if choice.type_ == MenuSelectionType.Skip: + return None + + return choice.value + + def _edit_iface(self, edit_iface: NetworkConfiguration): + iface_name = edit_iface.iface + modes = ['DHCP (auto detect)', 'IP (static)'] + default_mode = 'DHCP (auto detect)' + + prompt = _('Select which mode to configure for "{}" or skip to use default mode "{}"').format(iface_name, default_mode) + mode = Menu(prompt, modes, default_option=default_mode, skip=False).run() + + if mode.value == 'IP (static)': + while 1: + prompt = _('Enter the IP and subnet for {} (example: 192.168.0.5/24): ').format(iface_name) + ip = TextInput(prompt, edit_iface.ip).run().strip() + # Implemented new check for correct IP/subnet input + try: + ipaddress.ip_interface(ip) + break + except ValueError: + warn("You need to enter a valid IP in IP-config mode") + + # Implemented new check for correct gateway IP address + gateway = None + + while 1: + gateway = TextInput( + _('Enter your gateway (router) IP address or leave blank for none: '), + edit_iface.gateway + ).run().strip() + try: + if len(gateway) > 0: + ipaddress.ip_address(gateway) + break + except ValueError: + warn("You need to enter a valid gateway (router) IP address") + + if edit_iface.dns: + display_dns = ' '.join(edit_iface.dns) + else: + display_dns = None + dns_input = TextInput(_('Enter your DNS servers (space separated, blank for none): '), display_dns).run().strip() + + dns = [] + if len(dns_input): + dns = dns_input.split(' ') + + return NetworkConfiguration(NicType.MANUAL, iface=iface_name, ip=ip, gateway=gateway, dns=dns, dhcp=False) + else: + # this will contain network iface names + return NetworkConfiguration(NicType.MANUAL, iface=iface_name) + + +def ask_to_configure_network( + preset: Union[NetworkConfiguration, List[NetworkConfiguration]] +) -> Optional[NetworkConfiguration | List[NetworkConfiguration]]: + """ + Configure the network on the newly installed system + """ + network_options = { + 'none': str(_('No network configuration')), + 'iso_config': str(_('Copy ISO network configuration to installation')), + 'network_manager': str(_('Use NetworkManager (necessary to configure internet graphically in GNOME and KDE)')), + 'manual': str(_('Manual configuration')) + } + # for this routine it's easier to set the cursor position rather than a preset value + cursor_idx = None + + if preset and not isinstance(preset, list): + if preset.type == 'iso_config': + cursor_idx = 0 + elif preset.type == 'network_manager': + cursor_idx = 1 + + warning = str(_('Are you sure you want to reset this setting?')) + + choice = Menu( + _('Select one network interface to configure'), + list(network_options.values()), + cursor_index=cursor_idx, + sort=False, + allow_reset=True, + allow_reset_warning_msg=warning + ).run() + + match choice.type_: + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Reset: return None + + if choice.value == network_options['none']: + return None + elif choice.value == network_options['iso_config']: + return NetworkConfiguration(NicType.ISO) + elif choice.value == network_options['network_manager']: + return NetworkConfiguration(NicType.NM) + elif choice.value == network_options['manual']: + preset_ifaces = preset if isinstance(preset, list) else [] + return ManualNetworkConfig('Configure interfaces', preset_ifaces).run() + + return preset diff --git a/archinstall/lib/interactions/system_conf.py b/archinstall/lib/interactions/system_conf.py new file mode 100644 index 00000000..bbcb5b23 --- /dev/null +++ b/archinstall/lib/interactions/system_conf.py @@ -0,0 +1,117 @@ +from __future__ import annotations + +from typing import List, Any, Dict, TYPE_CHECKING, Optional + +from ..hardware import AVAILABLE_GFX_DRIVERS, SysInfo +from ..menu import MenuSelectionType, Menu +from ..models.bootloader import Bootloader + +if TYPE_CHECKING: + _: Any + + +def select_kernel(preset: List[str] = []) -> List[str]: + """ + Asks the user to select a kernel for system. + + :return: The string as a selected kernel + :rtype: string + """ + + kernels = ["linux", "linux-lts", "linux-zen", "linux-hardened"] + default_kernel = "linux" + + warning = str(_('Are you sure you want to reset this setting?')) + + choice = Menu( + _('Choose which kernels to use or leave blank for default "{}"').format(default_kernel), + kernels, + sort=True, + multi=True, + preset_values=preset, + allow_reset=True, + allow_reset_warning_msg=warning + ).run() + + match choice.type_: + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Reset: return [] + case MenuSelectionType.Selection: return choice.value # type: ignore + + +def ask_for_bootloader(preset: Bootloader) -> Bootloader: + # when the system only supports grub + if not SysInfo.has_uefi(): + options = [Bootloader.Grub.value] + default = Bootloader.Grub.value + else: + options = Bootloader.values() + default = Bootloader.Systemd.value + + preset_value = preset.value if preset else None + + choice = Menu( + _('Choose a bootloader'), + options, + preset_values=preset_value, + sort=False, + default_option=default + ).run() + + match choice.type_: + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Selection: return Bootloader(choice.value) + + return preset + + +def select_driver(options: Dict[str, Any] = {}, current_value: Optional[str] = None) -> Optional[str]: + """ + Some what convoluted function, whose job is simple. + Select a graphics driver from a pre-defined set of popular options. + + (The template xorg is for beginner users, not advanced, and should + there for appeal to the general public first and edge cases later) + """ + + if not options: + options = AVAILABLE_GFX_DRIVERS + + drivers = sorted(list(options.keys())) + + if drivers: + title = '' + if SysInfo.has_amd_graphics(): + title += str(_('For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options.')) + '\n' + if SysInfo.has_intel_graphics(): + title += str(_('For the best compatibility with your Intel hardware, you may want to use either the all open-source or Intel options.\n')) + if SysInfo.has_nvidia_graphics(): + title += str(_('For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n')) + + title += str(_('\nSelect a graphics driver or leave blank to install all open-source drivers')) + + preset = current_value if current_value else None + choice = Menu(title, drivers, preset_values=preset).run() + + if choice.type_ != MenuSelectionType.Selection: + return None + + return choice.value # type: ignore + + return current_value + + +def ask_for_swap(preset: bool = True) -> bool: + if preset: + preset_val = Menu.yes() + else: + preset_val = Menu.no() + + prompt = _('Would you like to use swap on zram?') + choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes(), preset_values=preset_val).run() + + match choice.type_: + case MenuSelectionType.Skip: return preset + case MenuSelectionType.Selection: return False if choice.value == Menu.no() else True + + return preset diff --git a/archinstall/lib/interactions/utils.py b/archinstall/lib/interactions/utils.py new file mode 100644 index 00000000..f6b5b2d3 --- /dev/null +++ b/archinstall/lib/interactions/utils.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +import getpass +from typing import Any, Optional, TYPE_CHECKING + +from ..models import PasswordStrength +from ..output import log, error + +if TYPE_CHECKING: + _: Any + +# used for signal handler +SIG_TRIGGER = None + + +def get_password(prompt: str = '') -> Optional[str]: + if not prompt: + prompt = _("Enter a password: ") + + while password := getpass.getpass(prompt): + if len(password.strip()) <= 0: + break + + 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 password != passwd_verification: + error(' * Passwords did not match * ') + continue + + return password + + return None diff --git a/archinstall/lib/locale.py b/archinstall/lib/locale.py new file mode 100644 index 00000000..0a36c072 --- /dev/null +++ b/archinstall/lib/locale.py @@ -0,0 +1,68 @@ +from typing import Iterator, List + +from .exceptions import ServiceException, SysCallError +from .general import SysCommand +from .output import error + + +def list_keyboard_languages() -> Iterator[str]: + for line in SysCommand("localectl --no-pager list-keymaps", environment_vars={'SYSTEMD_COLORS': '0'}): + yield line.decode('UTF-8').strip() + + +def list_locales() -> List[str]: + with open('/etc/locale.gen', 'r') as fp: + locales = [] + # before the list of locales begins there's an empty line with a '#' in front + # so we'll collect the localels from bottom up and halt when we're donw + entries = fp.readlines() + entries.reverse() + + for entry in entries: + text = entry.replace('#', '').strip() + if text == '': + break + locales.append(text) + + locales.reverse() + return locales + + +def list_x11_keyboard_languages() -> Iterator[str]: + for line in SysCommand("localectl --no-pager list-x11-keymap-layouts", environment_vars={'SYSTEMD_COLORS': '0'}): + yield line.decode('UTF-8').strip() + + +def verify_keyboard_layout(layout :str) -> bool: + for language in list_keyboard_languages(): + if layout.lower() == language.lower(): + return True + return False + + +def verify_x11_keyboard_layout(layout :str) -> bool: + for language in list_x11_keyboard_languages(): + if layout.lower() == language.lower(): + return True + return False + + +def set_keyboard_language(locale :str) -> bool: + if len(locale.strip()): + if not verify_keyboard_layout(locale): + error(f"Invalid keyboard locale specified: {locale}") + return False + + try: + SysCommand(f'localectl set-keymap {locale}') + except SysCallError as err: + raise ServiceException(f"Unable to set locale '{locale}' for console: {err}") + + return True + + return False + + +def list_timezones() -> Iterator[str]: + for line in SysCommand("timedatectl --no-pager list-timezones", environment_vars={'SYSTEMD_COLORS': '0'}): + yield line.decode('UTF-8').strip() diff --git a/archinstall/lib/locale_helpers.py b/archinstall/lib/locale_helpers.py deleted file mode 100644 index efb0365f..00000000 --- a/archinstall/lib/locale_helpers.py +++ /dev/null @@ -1,176 +0,0 @@ -import logging -from typing import Iterator, List, Callable, Optional - -from .exceptions import ServiceException, SysCallError -from .general import SysCommand -from .output import log -from .storage import storage - - -def list_keyboard_languages() -> Iterator[str]: - for line in SysCommand("localectl --no-pager list-keymaps", environment_vars={'SYSTEMD_COLORS': '0'}): - yield line.decode('UTF-8').strip() - - -def list_locales() -> List[str]: - with open('/etc/locale.gen', 'r') as fp: - locales = [] - # before the list of locales begins there's an empty line with a '#' in front - # so we'll collect the localels from bottom up and halt when we're donw - entries = fp.readlines() - entries.reverse() - - for entry in entries: - text = entry.replace('#', '').strip() - if text == '': - break - locales.append(text) - - locales.reverse() - return locales - -def get_locale_mode_text(mode): - if mode == 'LC_ALL': - mode_text = "general (LC_ALL)" - elif mode == "LC_CTYPE": - mode_text = "Character set" - elif mode == "LC_NUMERIC": - mode_text = "Numeric values" - elif mode == "LC_TIME": - mode_text = "Time Values" - elif mode == "LC_COLLATE": - mode_text = "sort order" - elif mode == "LC_MESSAGES": - mode_text = "text messages" - else: - mode_text = "Unassigned" - return mode_text - - -def reset_cmd_locale(): - """ sets the cmd_locale to its saved default """ - storage['CMD_LOCALE'] = storage.get('CMD_LOCALE_DEFAULT',{}) - - -def unset_cmd_locale(): - """ archinstall will use the execution environment default """ - storage['CMD_LOCALE'] = {} - - -def set_cmd_locale( - general: Optional[str] = None, - charset :str = 'C', - numbers :str = 'C', - time :str = 'C', - collate :str = 'C', - messages :str = 'C' -): - """ - Set the cmd locale. - If the parameter general is specified, it takes precedence over the rest (might as well not exist) - The rest define some specific settings above the installed default language. If anyone of this parameters is none means the installation default - """ - installed_locales = list_installed_locales() - result = {} - if general: - if general in installed_locales: - storage['CMD_LOCALE'] = {'LC_ALL':general} - else: - log(f"{get_locale_mode_text('LC_ALL')} {general} is not installed. Defaulting to C",fg="yellow",level=logging.WARNING) - return - - if numbers: - if numbers in installed_locales: - result["LC_NUMERIC"] = numbers - else: - log(f"{get_locale_mode_text('LC_NUMERIC')} {numbers} is not installed. Defaulting to installation language",fg="yellow",level=logging.WARNING) - if charset: - if charset in installed_locales: - result["LC_CTYPE"] = charset - else: - log(f"{get_locale_mode_text('LC_CTYPE')} {charset} is not installed. Defaulting to installation language",fg="yellow",level=logging.WARNING) - if time: - if time in installed_locales: - result["LC_TIME"] = time - else: - log(f"{get_locale_mode_text('LC_TIME')} {time} is not installed. Defaulting to installation language",fg="yellow",level=logging.WARNING) - if collate: - if collate in installed_locales: - result["LC_COLLATE"] = collate - else: - log(f"{get_locale_mode_text('LC_COLLATE')} {collate} is not installed. Defaulting to installation language",fg="yellow",level=logging.WARNING) - if messages: - if messages in installed_locales: - result["LC_MESSAGES"] = messages - else: - log(f"{get_locale_mode_text('LC_MESSAGES')} {messages} is not installed. Defaulting to installation language",fg="yellow",level=logging.WARNING) - storage['CMD_LOCALE'] = result - -def host_locale_environ(func :Callable): - """ decorator when we want a function executing in the host's locale environment """ - def wrapper(*args, **kwargs): - unset_cmd_locale() - result = func(*args,**kwargs) - reset_cmd_locale() - return result - return wrapper - -def c_locale_environ(func :Callable): - """ decorator when we want a function executing in the C locale environment """ - def wrapper(*args, **kwargs): - set_cmd_locale(general='C') - result = func(*args,**kwargs) - reset_cmd_locale() - return result - return wrapper - -def list_installed_locales() -> List[str]: - lista = [] - for line in SysCommand('locale -a'): - lista.append(line.decode('UTF-8').strip()) - return lista - -def list_x11_keyboard_languages() -> Iterator[str]: - for line in SysCommand("localectl --no-pager list-x11-keymap-layouts", environment_vars={'SYSTEMD_COLORS': '0'}): - yield line.decode('UTF-8').strip() - - -def verify_keyboard_layout(layout :str) -> bool: - for language in list_keyboard_languages(): - if layout.lower() == language.lower(): - return True - return False - - -def verify_x11_keyboard_layout(layout :str) -> bool: - for language in list_x11_keyboard_languages(): - if layout.lower() == language.lower(): - return True - return False - - -def search_keyboard_layout(layout :str) -> Iterator[str]: - for language in list_keyboard_languages(): - if layout.lower() in language.lower(): - yield language - - -def set_keyboard_language(locale :str) -> bool: - if len(locale.strip()): - if not verify_keyboard_layout(locale): - log(f"Invalid keyboard locale specified: {locale}", fg="red", level=logging.ERROR) - return False - - try: - SysCommand(f'localectl set-keymap {locale}') - except SysCallError as error: - raise ServiceException(f"Unable to set locale '{locale}' for console: {error}") - - return True - - return False - - -def list_timezones() -> Iterator[str]: - for line in SysCommand("timedatectl --no-pager list-timezones", environment_vars={'SYSTEMD_COLORS': '0'}): - yield line.decode('UTF-8').strip() diff --git a/archinstall/lib/luks.py b/archinstall/lib/luks.py index 53a5e8d2..f9b09b53 100644 --- a/archinstall/lib/luks.py +++ b/archinstall/lib/luks.py @@ -1,6 +1,5 @@ from __future__ import annotations -import logging import shlex import time from dataclasses import dataclass @@ -9,7 +8,7 @@ from typing import Optional, List from . import disk from .general import SysCommand, generate_password, SysCommandWorker -from .output import log +from .output import info, debug from .exceptions import SysCallError, DiskError from .storage import storage @@ -61,7 +60,7 @@ class Luks2: iter_time: int = 10000, key_file: Optional[Path] = None ) -> Path: - log(f'Luks2 encrypting: {self.luks_dev_path}', level=logging.INFO) + info(f'Luks2 encrypting: {self.luks_dev_path}') byte_password = self._password_bytes() @@ -95,21 +94,21 @@ class Luks2: try: SysCommand(cryptsetup_args) break - except SysCallError as error: + except SysCallError as err: time.sleep(storage['DISK_TIMEOUTS']) if retry_attempt != storage['DISK_RETRY_ATTEMPTS'] - 1: continue - if error.exit_code == 1: - log(f'luks2 partition currently in use: {self.luks_dev_path}') - log('Attempting to unmount, crypt-close and trying encryption again') + if err.exit_code == 1: + info(f'luks2 partition currently in use: {self.luks_dev_path}') + info('Attempting to unmount, crypt-close and trying encryption again') self.lock() # Then try again to set up the crypt-device SysCommand(cryptsetup_args) else: - raise DiskError(f'Could not encrypt volume "{self.luks_dev_path}": {error}') + raise DiskError(f'Could not encrypt volume "{self.luks_dev_path}": {err}') return key_file @@ -119,7 +118,7 @@ class Luks2: try: return SysCommand(command).decode().strip() # type: ignore except SysCallError as err: - log(f'Unable to get UUID for Luks device: {self.luks_dev_path}', level=logging.INFO) + info(f'Unable to get UUID for Luks device: {self.luks_dev_path}') raise err def is_unlocked(self) -> bool: @@ -133,7 +132,7 @@ class Luks2: :param key_file: An alternative key file :type key_file: Path """ - log(f'Unlocking luks2 device: {self.luks_dev_path}', level=logging.DEBUG) + debug(f'Unlocking luks2 device: {self.luks_dev_path}') if not self.mapper_name: raise ValueError('mapper name missing') @@ -170,11 +169,11 @@ class Luks2: for child in lsblk_info.children: # Unmount the child location for mountpoint in child.mountpoints: - log(f'Unmounting {mountpoint}', level=logging.DEBUG) + debug(f'Unmounting {mountpoint}') disk.device_handler.umount(mountpoint, recursive=True) # And close it if possible. - log(f"Closing crypt device {child.name}", level=logging.DEBUG) + debug(f"Closing crypt device {child.name}") SysCommand(f"cryptsetup close {child.name}") self._mapper_dev = None @@ -194,10 +193,10 @@ class Luks2: if key_file.exists(): if not override: - log(f'Key file {key_file} already exists, keeping existing') + info(f'Key file {key_file} already exists, keeping existing') return else: - log(f'Key file {key_file} already exists, overriding') + info(f'Key file {key_file} already exists, overriding') key_file_path.mkdir(parents=True, exist_ok=True) @@ -210,7 +209,7 @@ class Luks2: self._crypttab(crypttab_path, key_file, options=["luks", "key-slot=1"]) def _add_key(self, key_file: Path): - log(f'Adding additional key-file {key_file}', level=logging.INFO) + info(f'Adding additional key-file {key_file}') command = f'/usr/bin/cryptsetup -q -v luksAddKey {self.luks_dev_path} {key_file}' worker = SysCommandWorker(command, environment_vars={'LC_ALL': 'C'}) @@ -230,7 +229,7 @@ class Luks2: key_file: Path, options: List[str] ) -> None: - log(f'Adding crypttab entry for key {key_file}', level=logging.INFO) + info(f'Adding crypttab entry for key {key_file}') with open(crypttab_path, 'a') as crypttab: opt = ','.join(options) diff --git a/archinstall/lib/menu/abstract_menu.py b/archinstall/lib/menu/abstract_menu.py index e44d65a4..2bd56374 100644 --- a/archinstall/lib/menu/abstract_menu.py +++ b/archinstall/lib/menu/abstract_menu.py @@ -1,11 +1,10 @@ from __future__ import annotations -import logging from typing import Callable, Any, List, Iterator, Tuple, Optional, Dict, TYPE_CHECKING from .menu import Menu, MenuSelectionType -from ..locale_helpers import set_keyboard_language -from ..output import log +from ..locale import set_keyboard_language +from ..output import error from ..translationhandler import TranslationHandler, Language if TYPE_CHECKING: @@ -211,7 +210,7 @@ class AbstractMenu: # TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager # TODO: skip processing when it comes from a planified exit if len(args) >= 2 and args[1]: - log(args[1], level=logging.ERROR, fg='red') + error(args[1]) print(" Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues") raise args[1] @@ -483,7 +482,7 @@ class AbstractMenu: yield item def _select_archinstall_language(self, preset: Language) -> Language: - from ..user_interaction.general_conf import select_archinstall_language + from ..interactions.general_conf import select_archinstall_language language = select_archinstall_language(self.translation_handler.translated_languages, preset) self._translation_handler.activate(language) return language diff --git a/archinstall/lib/menu/menu.py b/archinstall/lib/menu/menu.py index f3fdb85f..768dfe55 100644 --- a/archinstall/lib/menu/menu.py +++ b/archinstall/lib/menu/menu.py @@ -6,11 +6,8 @@ from typing import Dict, List, Union, Any, TYPE_CHECKING, Optional, Callable from simple_term_menu import TerminalMenu # type: ignore from ..exceptions import RequirementError -from ..output import log +from ..output import debug -from collections.abc import Iterable -import sys -import logging if TYPE_CHECKING: _: Any @@ -127,33 +124,15 @@ class Menu(TerminalMenu): :param extra_bottom_space: Add an extra empty line at the end of the menu :type extra_bottom_space: bool """ - # we guarantee the inmutability of the options outside the class. - # an unknown number of iterables (.keys(),.values(),generator,...) can't be directly copied, in this case - # we recourse to make them lists before, but thru an exceptions - # this is the old code, which is not maintenable with more types - # options = copy(list(p_options) if isinstance(p_options,(type({}.keys()),type({}.values()))) else p_options) - # 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 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) - raise RequirementError("Menu() requires an iterable as option.") - - if isinstance(p_options,dict): + if isinstance(p_options, Dict): options = list(p_options.keys()) else: options = list(p_options) if not options: - log(" * Menu didn't find any options to choose from * ", fg='red') - log(f"invalid parameter at Menu() call was at <{sys._getframe(1).f_code.co_name}>",level=logging.WARNING) raise RequirementError('Menu.__init__() requires at least one option to proceed.') if any([o for o in options if not isinstance(o, str)]): - log(" * Menu options must be of type string * ", fg='red') - log(f"invalid parameter at Menu() call was at <{sys._getframe(1).f_code.co_name}>",level=logging.WARNING) raise RequirementError('Menu.__init__() requires the options to be of type string') if sort: @@ -343,7 +322,7 @@ class Menu(TerminalMenu): idx = self._menu_options.index(self._default_menu_value) indexes.append(idx) except (IndexError, ValueError): - log(f'Error finding index of {p}: {self._menu_options}', level=logging.DEBUG) + debug(f'Error finding index of {p}: {self._menu_options}') if len(indexes) == 0: indexes.append(0) diff --git a/archinstall/lib/mirrors.py b/archinstall/lib/mirrors.py index c6c5c8e4..62a0b081 100644 --- a/archinstall/lib/mirrors.py +++ b/archinstall/lib/mirrors.py @@ -1,4 +1,3 @@ -import logging import pathlib import urllib.error import urllib.request @@ -6,7 +5,7 @@ from typing import Union, Iterable, Dict, Any, List from dataclasses import dataclass from .general import SysCommand -from .output import log +from .output import info, warn from .exceptions import SysCallError from .storage import storage @@ -136,7 +135,7 @@ def use_mirrors( regions: Dict[str, Iterable[str]], destination: str = '/etc/pacman.d/mirrorlist' ): - log(f'A new package mirror-list has been created: {destination}', level=logging.INFO) + info(f'A new package mirror-list has been created: {destination}') with open(destination, 'w') as mirrorlist: for region, mirrors in regions.items(): for mirror in mirrors: @@ -170,7 +169,7 @@ def list_mirrors(sort_order :List[str] = ["https", "http"]) -> Dict[str, Any]: 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") + warn(f'Could not fetch an active mirror-list: {err}') return regions mirrorlist = response.read() diff --git a/archinstall/lib/models/bootloader.py b/archinstall/lib/models/bootloader.py index 38254c99..e21cda33 100644 --- a/archinstall/lib/models/bootloader.py +++ b/archinstall/lib/models/bootloader.py @@ -1,12 +1,11 @@ from __future__ import annotations -import logging import sys from enum import Enum from typing import List -from ..hardware import has_uefi -from ..output import log +from ..hardware import SysInfo +from ..output import warn class Bootloader(Enum): @@ -23,7 +22,7 @@ class Bootloader(Enum): @classmethod def get_default(cls) -> Bootloader: - if has_uefi(): + if SysInfo.has_uefi(): return Bootloader.Systemd else: return Bootloader.Grub @@ -35,6 +34,6 @@ class Bootloader(Enum): if bootloader not in cls.values(): values = ', '.join(cls.values()) - log(f'Invalid bootloader value "{bootloader}". Allowed values: {values}', level=logging.WARN) + warn(f'Invalid bootloader value "{bootloader}". Allowed values: {values}') sys.exit(1) return Bootloader(bootloader) diff --git a/archinstall/lib/models/network_configuration.py b/archinstall/lib/models/network_configuration.py index a8795fc1..93dd1c44 100644 --- a/archinstall/lib/models/network_configuration.py +++ b/archinstall/lib/models/network_configuration.py @@ -1,11 +1,10 @@ from __future__ import annotations -import logging from dataclasses import dataclass, field from enum import Enum from typing import List, Optional, Dict, Union, Any, TYPE_CHECKING, Tuple -from ..output import log +from ..output import debug from ..profile import ProfileConfiguration if TYPE_CHECKING: @@ -138,8 +137,7 @@ class NetworkConfigurationHandler: iface = manual_config.get('iface', None) if iface is None: - log(_('No iface specified for manual configuration')) - exit(1) + raise ValueError('No iface specified for manual configuration') if manual_config.get('dhcp', False) or not any([manual_config.get(v, '') for v in ['ip', 'gateway', 'dns']]): configurations.append( @@ -148,8 +146,7 @@ class NetworkConfigurationHandler: else: ip = manual_config.get('ip', '') if not ip: - log(_('Manual nic configuration with no auto DHCP requires an IP address'), fg='red') - exit(1) + raise ValueError('Manual nic configuration with no auto DHCP requires an IP address') dns = manual_config.get('dns', []) if not isinstance(dns, list): @@ -173,8 +170,7 @@ class NetworkConfigurationHandler: 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) + raise ValueError(f'Unknown nic type: {nic_type}. Possible values are {options}') def parse_arguments(self, config: Any): if isinstance(config, list): # new data format @@ -187,4 +183,4 @@ class NetworkConfigurationHandler: else: # manual configuration settings self._configuration = self._parse_manual_config([config]) else: - log(f'Unable to parse network configuration: {config}', level=logging.DEBUG) + debug(f'Unable to parse network configuration: {config}') diff --git a/archinstall/lib/networking.py b/archinstall/lib/networking.py index b858daaf..6906c320 100644 --- a/archinstall/lib/networking.py +++ b/archinstall/lib/networking.py @@ -1,4 +1,3 @@ -import logging import os import socket import ssl @@ -8,18 +7,16 @@ from urllib.error import URLError from urllib.parse import urlencode from urllib.request import urlopen -from .exceptions import HardwareIncompatibilityError, SysCallError -from .general import SysCommand -from .output import log +from .exceptions import SysCallError +from .output import error, info, debug from .pacman import run_pacman -from .storage import storage def get_hw_addr(ifname :str) -> str: import fcntl s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', bytes(ifname, 'utf-8')[:15])) - return ':'.join('%02x' % b for b in info[18:24]) + ret = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', bytes(ifname, 'utf-8')[:15])) + return ':'.join('%02x' % b for b in ret[18:24]) def list_interfaces(skip_loopback :bool = True) -> Dict[str, str]: @@ -36,26 +33,26 @@ 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) + info("Testing connectivity to the Arch Linux mirrors...") try: run_pacman("-Sy") return True except SysCallError as err: if os.geteuid() != 0: - log("check_mirror_reachable() uses 'pacman -Sy' which requires root.", level=logging.ERROR, fg="red") - log(f'exit_code: {err.exit_code}, Error: {err.message}', level=logging.DEBUG) + error("check_mirror_reachable() uses 'pacman -Sy' which requires root.") + debug(f'exit_code: {err.exit_code}, Error: {err.message}') return False def update_keyring() -> bool: - log("Updating archlinux-keyring ...", level=logging.INFO) + info("Updating archlinux-keyring ...") try: run_pacman("-Sy --noconfirm archlinux-keyring") return True except SysCallError: if os.geteuid() != 0: - log("update_keyring() uses 'pacman -Sy archlinux-keyring' which requires root.", level=logging.ERROR, fg="red") + error("update_keyring() uses 'pacman -Sy archlinux-keyring' which requires root.") return False @@ -80,38 +77,6 @@ def enrich_iface_types(interfaces: Union[Dict[str, Any], List[str]]) -> Dict[str return result -def wireless_scan(interface :str) -> None: - interfaces = enrich_iface_types(list(list_interfaces().values())) - if interfaces[interface] != 'WIRELESS': - raise HardwareIncompatibilityError(f"Interface {interface} is not a wireless interface: {interfaces}") - - try: - SysCommand(f"iwctl station {interface} scan") - except SysCallError as error: - raise SystemError(f"Could not scan for wireless networks: {error}") - - if '_WIFI' not in storage: - storage['_WIFI'] = {} - if interface not in storage['_WIFI']: - storage['_WIFI'][interface] = {} - - storage['_WIFI'][interface]['scanning'] = True - - -# TODO: Full WiFi experience might get evolved in the future, pausing for now 2021-01-25 -def get_wireless_networks(interface :str) -> None: - # TODO: Make this oneliner pritter to check if the interface is scanning or not. - # TODO: Rename this to list_wireless_networks() as it doesn't return anything - if '_WIFI' not in storage or interface not in storage['_WIFI'] or storage['_WIFI'][interface].get('scanning', False) is False: - import time - - wireless_scan(interface) - time.sleep(5) - - for line in SysCommand(f"iwctl station {interface} get-networks"): - print(line) - - def fetch_data_from_url(url: str, params: Optional[Dict] = None) -> str: ssl_context = ssl.create_default_context() ssl_context.check_hostname = False diff --git a/archinstall/lib/output.py b/archinstall/lib/output.py index d65f835f..bd31b5b3 100644 --- a/archinstall/lib/output.py +++ b/archinstall/lib/output.py @@ -1,15 +1,16 @@ import logging import os import sys +from enum import Enum + from pathlib import Path from typing import Dict, Union, List, Any, Callable, Optional +from dataclasses import asdict, is_dataclass from .storage import storage -from dataclasses import asdict, is_dataclass class FormattedOutput: - @classmethod def values( cls, @@ -118,7 +119,7 @@ class FormattedOutput: class Journald: @staticmethod - def log(message :str, level :int = logging.DEBUG) -> None: + def log(message: str, level: int = logging.DEBUG) -> None: try: import systemd.journal # type: ignore except ModuleNotFoundError: @@ -134,16 +135,37 @@ class Journald: log_adapter.log(level, message) -# TODO: Replace log() for session based logging. -class SessionLogging: - def __init__(self): - pass +def check_log_permissions(): + filename = storage.get('LOG_FILE', None) + + if not filename: + return + + log_dir = storage.get('LOG_PATH', Path('./')) + absolute_logfile = log_dir / filename + + try: + log_dir.mkdir(exist_ok=True, parents=True) + with absolute_logfile.open('a') as fp: + fp.write('') + except PermissionError: + # Fallback to creating the log file in the current folder + fallback_log_file = Path('./').absolute() / filename + absolute_logfile = fallback_log_file + absolute_logfile.mkdir(exist_ok=True, parents=True) + storage['LOG_PATH'] = Path('./').absolute() + err_string = f"Not enough permission to place log file at {absolute_logfile}, creating it in {fallback_log_file} instead." + warn(err_string) -# Found first reference here: https://stackoverflow.com/questions/7445658/how-to-detect-if-the-console-does-support-ansi-escape-codes-in-python -# And re-used this: https://github.com/django/django/blob/master/django/core/management/color.py#L12 -def supports_color() -> bool: + +def _supports_color() -> bool: """ + Found first reference here: + https://stackoverflow.com/questions/7445658/how-to-detect-if-the-console-does-support-ansi-escape-codes-in-python + And re-used this: + https://github.com/django/django/blob/master/django/core/management/color.py#L12 + Return True if the running system's terminal supports color, and False otherwise. """ @@ -154,13 +176,30 @@ def supports_color() -> bool: return supported_platform and is_a_tty -# Heavily influenced by: https://github.com/django/django/blob/ae8338daf34fd746771e0678081999b656177bae/django/utils/termcolors.py#L13 -# Color options here: https://askubuntu.com/questions/528928/how-to-do-underline-bold-italic-strikethrough-color-background-and-size-i -def stylize_output(text: str, *opts :str, **kwargs) -> str: +class Font(Enum): + bold = '1' + italic = '3' + underscore = '4' + blink = '5' + reverse = '7' + conceal = '8' + + +def _stylize_output( + text: str, + fg: str, + bg: Optional[str], + reset: bool, + font: List[Font] = [], +) -> str: """ + Heavily influenced by: + https://github.com/django/django/blob/ae8338daf34fd746771e0678081999b656177bae/django/utils/termcolors.py#L13 + Color options here: + https://askubuntu.com/questions/528928/how-to-do-underline-bold-italic-strikethrough-color-background-and-size-i + Adds styling to a text given a set of color arguments. """ - opt_dict = {'bold': '1', 'italic': '3', 'underscore': '4', 'blink': '5', 'reverse': '7', 'conceal': '8'} colors = { 'black' : '0', 'red' : '1', @@ -178,65 +217,72 @@ def stylize_output(text: str, *opts :str, **kwargs) -> str: 'darkgray' : '8;5;240', 'lightgray' : '8;5;256' } + foreground = {key: f'3{colors[key]}' for key in colors} background = {key: f'4{colors[key]}' for key in colors} - reset = '0' - code_list = [] - if text == '' and len(opts) == 1 and opts[0] == 'reset': - return '\x1b[%sm' % reset - for k, v in kwargs.items(): - if k == 'fg': - code_list.append(foreground[str(v)]) - elif k == 'bg': - code_list.append(background[str(v)]) + if text == '' and reset: + return '\x1b[%sm' % '0' + + code_list.append(foreground[str(fg)]) + + if bg: + code_list.append(background[str(bg)]) + + for o in font: + code_list.append(o.value) + + ansi = ';'.join(code_list) + + return f'\033[{ansi}m{text}\033[0m' + - for o in opts: - if o in opt_dict: - code_list.append(opt_dict[o]) +def info(*msgs: str): + log(*msgs, level=logging.INFO) - if 'noreset' not in opts: - text = '%s\x1b[%sm' % (text or '', reset) - return '%s%s' % (('\x1b[%sm' % ';'.join(code_list)), text or '') +def debug(*msgs: str): + log(*msgs, level=logging.DEBUG) -def log(*args :str, **kwargs :Union[str, int, Dict[str, Union[str, int]]]) -> None: - string = orig_string = ' '.join([str(x) for x in args]) +def error(*msgs: str): + log(*msgs, level=logging.ERROR, fg='red') + + +def warn(*msgs: str): + log(*msgs, level=logging.WARNING, fg='yellow') + + +def log( + *msgs: str, + level: int = logging.INFO, + fg: str = 'white', + bg: Optional[str] = None, + reset: bool = False, + font: List[Font] = [] +): + text = orig_string = ' '.join([str(x) for x in msgs]) # Attempt to colorize the output if supported # Insert default colors and override with **kwargs - if supports_color(): - kwargs = {'fg': 'white', **kwargs} - string = stylize_output(string, **kwargs) + if _supports_color(): + text = _stylize_output(text, fg, bg, reset, font) # If a logfile is defined in storage, # we use that one to output everything if filename := storage.get('LOG_FILE', None): - absolute_logfile = os.path.join(storage.get('LOG_PATH', './'), filename) + log_dir = storage.get('LOG_PATH', Path('./')) + absolute_logfile = log_dir / filename - try: - Path(absolute_logfile).parents[0].mkdir(exist_ok=True, parents=True) - with open(absolute_logfile, 'a') as log_file: - log_file.write("") - except PermissionError: - # Fallback to creating the log file in the current folder - err_string = f"Not enough permission to place log file at {absolute_logfile}, creating it in {Path('./').absolute() / filename} instead." - absolute_logfile = Path('./').absolute() / filename - absolute_logfile.parents[0].mkdir(exist_ok=True) - absolute_logfile = str(absolute_logfile) - storage['LOG_PATH'] = './' - log(err_string, fg="red") - - with open(absolute_logfile, 'a') as log_file: - log_file.write(f"{orig_string}\n") - - Journald.log(string, level=int(str(kwargs.get('level', logging.INFO)))) + with open(absolute_logfile, 'a') as fp: + fp.write(f"{orig_string}\n") + + Journald.log(text, level=level) # Finally, print the log unless we skipped it based on level. # We use sys.stdout.write()+flush() instead of print() to try and # fix issue #94 - if kwargs.get('level', logging.INFO) != logging.DEBUG or storage.get('arguments', {}).get('verbose', False): - sys.stdout.write(f"{string}\n") + if level != logging.DEBUG or storage.get('arguments', {}).get('verbose', False): + sys.stdout.write(f"{text}\n") sys.stdout.flush() diff --git a/archinstall/lib/pacman.py b/archinstall/lib/pacman.py index 0dfd5afa..f5514f05 100644 --- a/archinstall/lib/pacman.py +++ b/archinstall/lib/pacman.py @@ -1,10 +1,9 @@ -import logging import pathlib import time from typing import TYPE_CHECKING, Any from .general import SysCommand -from .output import log +from .output import warn, error if TYPE_CHECKING: _: Any @@ -19,14 +18,14 @@ def run_pacman(args :str, default_cmd :str = 'pacman') -> SysCommand: pacman_db_lock = pathlib.Path('/var/lib/pacman/db.lck') if pacman_db_lock.exists(): - log(_('Pacman is already running, waiting maximum 10 minutes for it to terminate.'), level=logging.WARNING, fg="red") + warn(_('Pacman is already running, waiting maximum 10 minutes for it to terminate.')) started = time.time() while pacman_db_lock.exists(): time.sleep(0.25) if time.time() - started > (60 * 10): - log(_('Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall.'), level=logging.WARNING, fg="red") + error(_('Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall.')) exit(1) return SysCommand(f'{default_cmd} {args}') diff --git a/archinstall/lib/plugins.py b/archinstall/lib/plugins.py index b1ece04f..4ccb0666 100644 --- a/archinstall/lib/plugins.py +++ b/archinstall/lib/plugins.py @@ -1,6 +1,5 @@ import hashlib import importlib -import logging import os import sys import urllib.parse @@ -9,7 +8,7 @@ from importlib import metadata from pathlib import Path from typing import Optional, List -from .output import log +from .output import error, info, warn from .storage import storage plugins = {} @@ -24,11 +23,13 @@ for plugin_definition in metadata.entry_points().select(group='archinstall.plugi try: plugins[plugin_definition.name] = plugin_entrypoint() except Exception as err: - log(f'Error: {err}', level=logging.ERROR) - log(f"The above error was detected when loading the plugin: {plugin_definition}", fg="red", level=logging.ERROR) + error( + f'Error: {err}', + f"The above error was detected when loading the plugin: {plugin_definition}" + ) -def localize_path(path: Path) -> Path: +def _localize_path(path: Path) -> Path: """ Support structures for load_plugin() """ @@ -45,7 +46,7 @@ def localize_path(path: Path) -> Path: return path -def import_via_path(path: Path, namespace: Optional[str] = None) -> Optional[str]: +def _import_via_path(path: Path, namespace: Optional[str] = None) -> Optional[str]: if not namespace: namespace = os.path.basename(path) @@ -61,8 +62,10 @@ def import_via_path(path: Path, namespace: Optional[str] = None) -> Optional[str return namespace except Exception as err: - log(f'Error: {err}', level=logging.ERROR) - log(f"The above error was detected when loading the plugin: {path}", fg="red", level=logging.ERROR) + error( + f'Error: {err}', + f"The above error was detected when loading the plugin: {path}" + ) try: del sys.modules[namespace] @@ -72,7 +75,7 @@ def import_via_path(path: Path, namespace: Optional[str] = None) -> Optional[str return namespace -def find_nth(haystack: List[str], needle: str, n: int) -> Optional[int]: +def _find_nth(haystack: List[str], needle: str, n: int) -> Optional[int]: indices = [idx for idx, elem in enumerate(haystack) if elem == needle] if n <= len(indices): return indices[n - 1] @@ -82,34 +85,36 @@ def find_nth(haystack: List[str], needle: str, n: int) -> Optional[int]: def load_plugin(path: Path): namespace: Optional[str] = None parsed_url = urllib.parse.urlparse(str(path)) - log(f"Loading plugin from url {parsed_url}.", level=logging.INFO) + info(f"Loading plugin from url {parsed_url}") # The Profile was not a direct match on a remote URL if not parsed_url.scheme: # Path was not found in any known examples, check if it's an absolute path if os.path.isfile(path): - namespace = import_via_path(path) + namespace = _import_via_path(path) elif parsed_url.scheme in ('https', 'http'): - localized = localize_path(path) - namespace = import_via_path(localized) + localized = _localize_path(path) + namespace = _import_via_path(localized) if namespace and namespace in sys.modules: # Version dependency via __archinstall__version__ variable (if present) in the plugin # Any errors in version inconsistency will be handled through normal error handling if not defined. if hasattr(sys.modules[namespace], '__archinstall__version__'): - archinstall_major_and_minor_version = float(storage['__version__'][:find_nth(storage['__version__'], '.', 2)]) + archinstall_major_and_minor_version = float(storage['__version__'][:_find_nth(storage['__version__'], '.', 2)]) if sys.modules[namespace].__archinstall__version__ < archinstall_major_and_minor_version: - log(f"Plugin {sys.modules[namespace]} does not support the current Archinstall version.", fg="red", level=logging.ERROR) + error(f"Plugin {sys.modules[namespace]} does not support the current Archinstall version.") # Locate the plugin entry-point called Plugin() # This in accordance with the entry_points() from setup.cfg above 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) + info(f"Plugin {plugins[namespace]} has been loaded.") except Exception as err: - log(f'Error: {err}', level=logging.ERROR) - log(f"The above error was detected when initiating the plugin: {path}", fg="red", level=logging.ERROR) + error( + f'Error: {err}', + f"The above error was detected when initiating the plugin: {path}" + ) else: - log(f"Plugin '{path}' is missing a valid entry-point or is corrupt.", fg="yellow", level=logging.WARNING) + warn(f"Plugin '{path}' is missing a valid entry-point or is corrupt.") diff --git a/archinstall/lib/profile/profile_menu.py b/archinstall/lib/profile/profile_menu.py index 6462685a..213466a6 100644 --- a/archinstall/lib/profile/profile_menu.py +++ b/archinstall/lib/profile/profile_menu.py @@ -6,7 +6,7 @@ from archinstall.default_profiles.profile import Profile, GreeterType from .profile_model import ProfileConfiguration from ..hardware import AVAILABLE_GFX_DRIVERS from ..menu import Menu, MenuSelectionType, AbstractSubMenu, Selector -from ..user_interaction.system_conf import select_driver +from ..interactions.system_conf import select_driver if TYPE_CHECKING: _: Any diff --git a/archinstall/lib/profile/profiles_handler.py b/archinstall/lib/profile/profiles_handler.py index 6ed95f8e..16fef251 100644 --- a/archinstall/lib/profile/profiles_handler.py +++ b/archinstall/lib/profile/profiles_handler.py @@ -1,7 +1,6 @@ from __future__ import annotations import importlib.util -import logging import sys from collections import Counter from functools import cached_property @@ -15,7 +14,7 @@ from .profile_model import ProfileConfiguration from ..hardware import AVAILABLE_GFX_DRIVERS from ..menu import MenuSelectionType, Menu, MenuSelection from ..networking import list_interfaces, fetch_data_from_url -from ..output import log +from ..output import error, debug, info, warn from ..storage import storage if TYPE_CHECKING: @@ -106,7 +105,7 @@ class ProfileHandler: invalid = ', '.join([k for k, v in resolved.items() if v is None]) if invalid: - log(f'No profile definition found: {invalid}') + info(f'No profile definition found: {invalid}') custom_settings = profile_config.get('custom_settings', {}) for profile in valid: @@ -216,7 +215,7 @@ class ProfileHandler: install_session.add_additional_packages(additional_pkg) except Exception as err: - log(f"Could not handle nvidia and linuz-zen specific situations during xorg installation: {err}", level=logging.WARNING, fg="yellow") + warn(f"Could not handle nvidia and linuz-zen specific situations during xorg installation: {err}") # Prep didn't run, so there's no driver to install install_session.add_additional_packages(['xorg-server', 'xorg-xinit']) @@ -250,7 +249,7 @@ class ProfileHandler: self.add_custom_profiles(profiles) except ValueError: err = str(_('Unable to fetch profile from specified url: {}')).format(url) - log(err, level=logging.ERROR, fg="red") + error(err) def _load_profile_class(self, module: ModuleType) -> List[Profile]: """ @@ -264,7 +263,7 @@ class ProfileHandler: if isinstance(cls_, Profile): profiles.append(cls_) except Exception: - log(f'Cannot import {module}, it does not appear to be a Profile class', level=logging.DEBUG) + debug(f'Cannot import {module}, it does not appear to be a Profile class') return profiles @@ -278,7 +277,7 @@ class ProfileHandler: if len(duplicates) > 0: err = str(_('Profiles must have unique name, but profile definitions with duplicate name found: {}')).format(duplicates[0][0]) - log(err, level=logging.ERROR, fg="red") + error(err) sys.exit(1) def _is_legacy(self, file: Path) -> bool: @@ -297,15 +296,15 @@ class ProfileHandler: Process a file for profile definitions """ if self._is_legacy(file): - log(f'Cannot import {file} because it is no longer supported, please use the new profile format') + info(f'Cannot import {file} because it is no longer supported, please use the new profile format') return [] if not file.is_file(): - log(f'Cannot find profile file {file}') + info(f'Cannot find profile file {file}') return [] name = file.name.removesuffix(file.suffix) - log(f'Importing profile: {file}', level=logging.DEBUG) + debug(f'Importing profile: {file}') try: spec = importlib.util.spec_from_file_location(name, file) @@ -315,7 +314,7 @@ class ProfileHandler: spec.loader.exec_module(imported) return self._load_profile_class(imported) except Exception as e: - log(f'Unable to parse file {file}: {e}', level=logging.ERROR) + error(f'Unable to parse file {file}: {e}') return [] diff --git a/archinstall/lib/services.py b/archinstall/lib/services.py deleted file mode 100644 index b177052b..00000000 --- a/archinstall/lib/services.py +++ /dev/null @@ -1,11 +0,0 @@ -import os -from .general import SysCommand - - -def service_state(service_name: str) -> str: - if os.path.splitext(service_name)[1] != '.service': - service_name += '.service' # Just to be safe - - state = b''.join(SysCommand(f'systemctl show --no-pager -p SubState --value {service_name}', environment_vars={'SYSTEMD_COLORS': '0'})) - - return state.strip().decode('UTF-8') diff --git a/archinstall/lib/storage.py b/archinstall/lib/storage.py index 5a54d816..2f256e5d 100644 --- a/archinstall/lib/storage.py +++ b/archinstall/lib/storage.py @@ -11,8 +11,8 @@ from pathlib import Path storage: Dict[str, Any] = { 'PROFILE': Path(__file__).parent.parent.joinpath('default_profiles'), - 'LOG_PATH': '/var/log/archinstall', - 'LOG_FILE': 'install.log', + 'LOG_PATH': Path('/var/log/archinstall'), + 'LOG_FILE': Path('install.log'), 'MOUNT_POINT': Path('/mnt/archinstall'), 'ENC_IDENTIFIER': 'ainst', 'DISK_TIMEOUTS' : 1, # seconds diff --git a/archinstall/lib/systemd.py b/archinstall/lib/systemd.py deleted file mode 100644 index 6ccbc5f6..00000000 --- a/archinstall/lib/systemd.py +++ /dev/null @@ -1,110 +0,0 @@ -import logging -import time -from typing import Iterator, Optional -from .exceptions import SysCallError -from .general import SysCommand, SysCommandWorker, locate_binary -from .installer import Installer -from .output import log -from .storage import storage - - -class Boot: - def __init__(self, installation: Installer): - self.instance = installation - self.container_name = 'archinstall' - self.session: Optional[SysCommandWorker] = None - self.ready = False - - def __enter__(self) -> 'Boot': - if (existing_session := storage.get('active_boot', None)) and existing_session.instance != self.instance: - raise KeyError("Archinstall only supports booting up one instance, and a active session is already active and it is not this one.") - - if existing_session: - self.session = existing_session.session - self.ready = existing_session.ready - else: - # '-P' or --console=pipe could help us not having to do a bunch - # of os.write() calls, but instead use pipes (stdin, stdout and stderr) as usual. - self.session = SysCommandWorker([ - '/usr/bin/systemd-nspawn', - '-D', str(self.instance.target), - '--timezone=off', - '-b', - '--no-pager', - '--machine', self.container_name - ]) - - if not self.ready and self.session: - while self.session.is_alive(): - if b' login:' in self.session: - self.ready = True - break - - storage['active_boot'] = self - return self - - def __exit__(self, *args :str, **kwargs :str) -> None: - # b''.join(sys_command('sync')) # No need to, since the underlying fs() object will call sync. - # TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager - - if len(args) >= 2 and args[1]: - log(args[1], 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: Optional[int] = -1 - - try: - shutdown = SysCommand(f'systemd-run --machine={self.container_name} --pty shutdown now') - except SysCallError as error: - shutdown_exit_code = error.exit_code - - if self.session: - while self.session.is_alive(): - time.sleep(0.25) - - if shutdown and shutdown.exit_code: - shutdown_exit_code = shutdown.exit_code - - if self.session and (self.session.exit_code == 0 or shutdown_exit_code == 0): - storage['active_boot'] = None - else: - session_exit_code = self.session.exit_code if self.session else -1 - - raise SysCallError( - f"Could not shut down temporary boot of {self.instance}: {session_exit_code}/{shutdown_exit_code}", - exit_code=next(filter(bool, [session_exit_code, shutdown_exit_code])) - ) - - def __iter__(self) -> Iterator[bytes]: - if self.session: - for value in self.session: - yield value - - def __contains__(self, key: bytes) -> bool: - if self.session is None: - return False - - return key in self.session - - def is_alive(self) -> bool: - if self.session is None: - return False - - return self.session.is_alive() - - def SysCommand(self, cmd: list, *args, **kwargs) -> SysCommand: - if cmd[0][0] != '/' and cmd[0][:2] != './': - # This check is also done in SysCommand & SysCommandWorker. - # However, that check is done for `machinectl` and not for our chroot command. - # So this wrapper for SysCommand will do this additionally. - - cmd[0] = locate_binary(cmd[0]) - - return SysCommand(["systemd-run", f"--machine={self.container_name}", "--pty", *cmd], *args, **kwargs) - - def SysCommandWorker(self, cmd: list, *args, **kwargs) -> SysCommandWorker: - if cmd[0][0] != '/' and cmd[0][:2] != './': - cmd[0] = locate_binary(cmd[0]) - - return SysCommandWorker(["systemd-run", f"--machine={self.container_name}", "--pty", *cmd], *args, **kwargs) diff --git a/archinstall/lib/translationhandler.py b/archinstall/lib/translationhandler.py index 0d74f974..5f0f0695 100644 --- a/archinstall/lib/translationhandler.py +++ b/archinstall/lib/translationhandler.py @@ -1,14 +1,14 @@ 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 + +from .output import error, debug if TYPE_CHECKING: _: Any @@ -80,8 +80,8 @@ class TranslationHandler: 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}") + except FileNotFoundError as err: + raise FileNotFoundError(f"Could not locate language file for '{lang}': {err}") return languages @@ -89,12 +89,12 @@ class TranslationHandler: """ Set the provided font as the new terminal font """ - from .general import SysCommand, log + from .general import SysCommand try: - log(f'Setting font: {font}', level=logging.DEBUG) + debug(f'Setting font: {font}') SysCommand(f'setfont {font}') except Exception: - log(f'Unable to set font {font}', level=logging.ERROR) + error(f'Unable to set font {font}') def _load_language_mappings(self) -> List[Dict[str, Any]]: """ diff --git a/archinstall/lib/user_interaction/__init__.py b/archinstall/lib/user_interaction/__init__.py deleted file mode 100644 index 5ee89de0..00000000 --- a/archinstall/lib/user_interaction/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from .manage_users_conf import ask_for_additional_users -from .locale_conf import select_locale_lang, select_locale_enc -from .system_conf import select_kernel, select_driver, ask_for_bootloader, ask_for_swap -from .network_conf import ask_to_configure_network -from .general_conf import ( - ask_ntp, ask_for_a_timezone, ask_for_audio_selection, select_language, select_mirror_regions, - select_archinstall_language, ask_additional_packages_to_install, - select_additional_repositories, ask_hostname, add_number_of_parrallel_downloads -) -from .utils import get_password diff --git a/archinstall/lib/user_interaction/disk_conf.py b/archinstall/lib/user_interaction/disk_conf.py deleted file mode 100644 index a77e950a..00000000 --- a/archinstall/lib/user_interaction/disk_conf.py +++ /dev/null @@ -1,391 +0,0 @@ -from __future__ import annotations - -import logging -from pathlib import Path -from typing import Any, TYPE_CHECKING, Optional, List, Tuple - -from .. import disk -from ..hardware import has_uefi -from ..menu import Menu, MenuSelectionType, TableMenu -from ..output import FormattedOutput -from ..output import log -from ..utils.util import prompt_dir - -if TYPE_CHECKING: - _: Any - - -def select_devices(preset: List[disk.BDevice] = []) -> List[disk.BDevice]: - """ - Asks the user to select one or multiple devices - - :return: List of selected devices - :rtype: list - """ - - def _preview_device_selection(selection: disk._DeviceInfo) -> Optional[str]: - dev = disk.device_handler.get_device(selection.path) - if dev and dev.partition_infos: - return FormattedOutput.as_table(dev.partition_infos) - return None - - if preset is None: - preset = [] - - title = str(_('Select one or more devices to use and configure')) - warning = str(_('If you reset the device selection this will also reset the current disk layout. Are you sure?')) - - devices = disk.device_handler.devices - options = [d.device_info for d in devices] - preset_value = [p.device_info for p in preset] - - choice = TableMenu( - title, - data=options, - multi=True, - preset=preset_value, - preview_command=_preview_device_selection, - preview_title=str(_('Existing Partitions')), - preview_size=0.2, - allow_reset=True, - allow_reset_warning_msg=warning - ).run() - - match choice.type_: - case MenuSelectionType.Reset: return [] - case MenuSelectionType.Skip: return preset - case MenuSelectionType.Selection: - selected_device_info: List[disk._DeviceInfo] = choice.value # type: ignore - selected_devices = [] - - for device in devices: - if device.device_info in selected_device_info: - selected_devices.append(device) - - return selected_devices - - -def get_default_partition_layout( - devices: List[disk.BDevice], - filesystem_type: Optional[disk.FilesystemType] = None, - advanced_option: bool = False -) -> List[disk.DeviceModification]: - - if len(devices) == 1: - device_modification = suggest_single_disk_layout( - devices[0], - filesystem_type=filesystem_type, - advanced_options=advanced_option - ) - return [device_modification] - else: - return suggest_multi_disk_layout( - devices, - filesystem_type=filesystem_type, - advanced_options=advanced_option - ) - - -def _manual_partitioning( - preset: List[disk.DeviceModification], - devices: List[disk.BDevice] -) -> List[disk.DeviceModification]: - modifications = [] - for device in devices: - mod = next(filter(lambda x: x.device == device, preset), None) - if not mod: - mod = disk.DeviceModification(device, wipe=False) - - if partitions := disk.manual_partitioning(device, preset=mod.partitions): - mod.partitions = partitions - modifications.append(mod) - - return modifications - - -def select_disk_config( - preset: Optional[disk.DiskLayoutConfiguration] = None, - advanced_option: bool = False -) -> Optional[disk.DiskLayoutConfiguration]: - default_layout = disk.DiskLayoutType.Default.display_msg() - manual_mode = disk.DiskLayoutType.Manual.display_msg() - pre_mount_mode = disk.DiskLayoutType.Pre_mount.display_msg() - - options = [default_layout, manual_mode, pre_mount_mode] - preset_value = preset.config_type.display_msg() if preset else None - warning = str(_('Are you sure you want to reset this setting?')) - - choice = Menu( - _('Select a partitioning option'), - options, - allow_reset=True, - allow_reset_warning_msg=warning, - sort=False, - preview_size=0.2, - preset_values=preset_value - ).run() - - match choice.type_: - case MenuSelectionType.Skip: return preset - case MenuSelectionType.Reset: return None - case MenuSelectionType.Selection: - if choice.single_value == pre_mount_mode: - output = "You will use whatever drive-setup is mounted at the specified directory\n" - output += "WARNING: Archinstall won't check the suitability of this setup\n" - - path = prompt_dir(str(_('Enter the root directory of the mounted devices: ')), output) - mods = disk.device_handler.detect_pre_mounted_mods(path) - - return disk.DiskLayoutConfiguration( - config_type=disk.DiskLayoutType.Pre_mount, - relative_mountpoint=path, - device_modifications=mods - ) - - preset_devices = [mod.device for mod in preset.device_modifications] if preset else [] - - devices = select_devices(preset_devices) - - if not devices: - return None - - if choice.value == default_layout: - modifications = get_default_partition_layout(devices, advanced_option=advanced_option) - if modifications: - return disk.DiskLayoutConfiguration( - config_type=disk.DiskLayoutType.Default, - device_modifications=modifications - ) - elif choice.value == manual_mode: - preset_mods = preset.device_modifications if preset else [] - modifications = _manual_partitioning(preset_mods, devices) - - if modifications: - return disk.DiskLayoutConfiguration( - config_type=disk.DiskLayoutType.Manual, - device_modifications=modifications - ) - - return None - - -def _boot_partition() -> disk.PartitionModification: - if has_uefi(): - start = disk.Size(1, disk.Unit.MiB) - size = disk.Size(512, disk.Unit.MiB) - else: - start = disk.Size(3, disk.Unit.MiB) - size = disk.Size(203, disk.Unit.MiB) - - # boot partition - return disk.PartitionModification( - status=disk.ModificationStatus.Create, - type=disk.PartitionType.Primary, - start=start, - length=size, - mountpoint=Path('/boot'), - fs_type=disk.FilesystemType.Fat32, - flags=[disk.PartitionFlag.Boot] - ) - - -def ask_for_main_filesystem_format(advanced_options=False) -> disk.FilesystemType: - options = { - 'btrfs': disk.FilesystemType.Btrfs, - 'ext4': disk.FilesystemType.Ext4, - 'xfs': disk.FilesystemType.Xfs, - 'f2fs': disk.FilesystemType.F2fs - } - - if advanced_options: - options.update({'ntfs': disk.FilesystemType.Ntfs}) - - prompt = _('Select which filesystem your main partition should use') - choice = Menu(prompt, options, skip=False, sort=False).run() - return options[choice.single_value] - - -def suggest_single_disk_layout( - device: disk.BDevice, - filesystem_type: Optional[disk.FilesystemType] = None, - advanced_options: bool = False, - separate_home: Optional[bool] = None -) -> disk.DeviceModification: - if not filesystem_type: - filesystem_type = ask_for_main_filesystem_format(advanced_options) - - min_size_to_allow_home_part = disk.Size(40, disk.Unit.GiB) - root_partition_size = disk.Size(20, disk.Unit.GiB) - using_subvolumes = False - using_home_partition = False - compression = False - device_size_gib = device.device_info.total_size - - if filesystem_type == disk.FilesystemType.Btrfs: - prompt = str(_('Would you like to use BTRFS subvolumes with a default structure?')) - choice = Menu(prompt, Menu.yes_no(), skip=False, default_option=Menu.yes()).run() - using_subvolumes = choice.value == Menu.yes() - - prompt = str(_('Would you like to use BTRFS compression?')) - choice = Menu(prompt, Menu.yes_no(), skip=False, default_option=Menu.yes()).run() - compression = choice.value == Menu.yes() - - device_modification = disk.DeviceModification(device, wipe=True) - - # Used for reference: https://wiki.archlinux.org/title/partitioning - # 2 MiB is unallocated for GRUB on BIOS. Potentially unneeded for other bootloaders? - - # TODO: On BIOS, /boot partition is only needed if the drive will - # be encrypted, otherwise it is not recommended. We should probably - # add a check for whether the drive will be encrypted or not. - - # Increase the UEFI partition if UEFI is detected. - # Also re-align the start to 1MiB since we don't need the first sectors - # like we do in MBR layouts where the boot loader is installed traditionally. - - boot_partition = _boot_partition() - device_modification.add_partition(boot_partition) - - if not using_subvolumes: - if device_size_gib >= min_size_to_allow_home_part: - if separate_home is None: - prompt = str(_('Would you like to create a separate partition for /home?')) - choice = Menu(prompt, Menu.yes_no(), skip=False, default_option=Menu.yes()).run() - using_home_partition = choice.value == Menu.yes() - elif separate_home is True: - using_home_partition = True - else: - using_home_partition = False - - # root partition - start = disk.Size(513, disk.Unit.MiB) if has_uefi() else disk.Size(206, disk.Unit.MiB) - - # Set a size for / (/root) - if using_subvolumes or device_size_gib < min_size_to_allow_home_part or not using_home_partition: - length = disk.Size(100, disk.Unit.Percent, total_size=device.device_info.total_size) - else: - length = min(device.device_info.total_size, root_partition_size) - - root_partition = disk.PartitionModification( - status=disk.ModificationStatus.Create, - type=disk.PartitionType.Primary, - start=start, - length=length, - mountpoint=Path('/') if not using_subvolumes else None, - fs_type=filesystem_type, - mount_options=['compress=zstd'] if compression else [], - ) - device_modification.add_partition(root_partition) - - if using_subvolumes: - # https://btrfs.wiki.kernel.org/index.php/FAQ - # https://unix.stackexchange.com/questions/246976/btrfs-subvolume-uuid-clash - # https://github.com/classy-giraffe/easy-arch/blob/main/easy-arch.sh - subvolumes = [ - disk.SubvolumeModification(Path('@'), Path('/')), - disk.SubvolumeModification(Path('@home'), Path('/home')), - disk.SubvolumeModification(Path('@log'), Path('/var/log')), - disk.SubvolumeModification(Path('@pkg'), Path('/var/cache/pacman/pkg')), - disk.SubvolumeModification(Path('@.snapshots'), Path('/.snapshots')) - ] - root_partition.btrfs_subvols = subvolumes - 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.. - # A second partition for /home would be nice if we have the space for it - home_partition = disk.PartitionModification( - status=disk.ModificationStatus.Create, - type=disk.PartitionType.Primary, - start=root_partition.length, - length=disk.Size(100, disk.Unit.Percent, total_size=device.device_info.total_size), - mountpoint=Path('/home'), - fs_type=filesystem_type, - mount_options=['compress=zstd'] if compression else [] - ) - device_modification.add_partition(home_partition) - - return device_modification - - -def suggest_multi_disk_layout( - devices: List[disk.BDevice], - filesystem_type: Optional[disk.FilesystemType] = None, - advanced_options: bool = False -) -> List[disk.DeviceModification]: - if not devices: - return [] - - # Not really a rock solid foundation of information to stand on, but it's a start: - # https://www.reddit.com/r/btrfs/comments/m287gp/partition_strategy_for_two_physical_disks/ - # https://www.reddit.com/r/btrfs/comments/9us4hr/what_is_your_btrfs_partitionsubvolumes_scheme/ - min_home_partition_size = disk.Size(40, disk.Unit.GiB) - # rough estimate taking in to account user desktops etc. TODO: Catch user packages to detect size? - desired_root_partition_size = disk.Size(20, disk.Unit.GiB) - compression = False - - if not filesystem_type: - filesystem_type = ask_for_main_filesystem_format(advanced_options) - - # find proper disk for /home - possible_devices = list(filter(lambda x: x.device_info.total_size >= min_home_partition_size, devices)) - home_device = max(possible_devices, key=lambda d: d.device_info.total_size) if possible_devices else None - - # find proper device for /root - devices_delta = {} - for device in devices: - if device is not home_device: - delta = device.device_info.total_size - desired_root_partition_size - devices_delta[device] = delta - - sorted_delta: List[Tuple[disk.BDevice, Any]] = sorted(devices_delta.items(), key=lambda x: x[1]) - root_device: Optional[disk.BDevice] = sorted_delta[0][0] - - if home_device is None or root_device is None: - text = _('The selected drives do not have the minimum capacity required for an automatic suggestion\n') - text += _('Minimum capacity for /home partition: {}GiB\n').format(min_home_partition_size.format_size(disk.Unit.GiB)) - text += _('Minimum capacity for Arch Linux partition: {}GiB').format(desired_root_partition_size.format_size(disk.Unit.GiB)) - Menu(str(text), [str(_('Continue'))], skip=False).run() - return [] - - if filesystem_type == disk.FilesystemType.Btrfs: - prompt = str(_('Would you like to use BTRFS compression?')) - choice = Menu(prompt, Menu.yes_no(), skip=False, default_option=Menu.yes()).run() - compression = choice.value == Menu.yes() - - device_paths = ', '.join([str(d.device_info.path) for d in devices]) - log(f"Suggesting multi-disk-layout for devices: {device_paths}", level=logging.DEBUG) - log(f"/root: {root_device.device_info.path}", level=logging.DEBUG) - log(f"/home: {home_device.device_info.path}", level=logging.DEBUG) - - root_device_modification = disk.DeviceModification(root_device, wipe=True) - home_device_modification = disk.DeviceModification(home_device, wipe=True) - - # add boot partition to the root device - boot_partition = _boot_partition() - root_device_modification.add_partition(boot_partition) - - # add root partition to the root device - root_partition = disk.PartitionModification( - status=disk.ModificationStatus.Create, - type=disk.PartitionType.Primary, - start=disk.Size(513, disk.Unit.MiB) if has_uefi() else disk.Size(206, disk.Unit.MiB), - length=disk.Size(100, disk.Unit.Percent, total_size=root_device.device_info.total_size), - mountpoint=Path('/'), - mount_options=['compress=zstd'] if compression else [], - fs_type=filesystem_type - ) - root_device_modification.add_partition(root_partition) - - # add home partition to home device - home_partition = disk.PartitionModification( - status=disk.ModificationStatus.Create, - type=disk.PartitionType.Primary, - start=disk.Size(1, disk.Unit.MiB), - length=disk.Size(100, disk.Unit.Percent, total_size=home_device.device_info.total_size), - mountpoint=Path('/home'), - mount_options=['compress=zstd'] if compression else [], - fs_type=filesystem_type, - ) - home_device_modification.add_partition(home_partition) - - return [root_device_modification, home_device_modification] diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py deleted file mode 100644 index 9722dc4d..00000000 --- a/archinstall/lib/user_interaction/general_conf.py +++ /dev/null @@ -1,244 +0,0 @@ -from __future__ import annotations - -import logging -import pathlib -from typing import List, Any, Optional, Dict, TYPE_CHECKING - -from ..locale_helpers import list_keyboard_languages, list_timezones -from ..menu import MenuSelectionType, Menu, TextInput -from ..mirrors import list_mirrors -from ..output import log -from ..packages.packages import validate_package_list -from ..storage import storage -from ..translationhandler import Language - -if TYPE_CHECKING: - _: Any - - -def ask_ntp(preset: bool = True) -> bool: - prompt = str(_('Would you like to use automatic time synchronization (NTP) with the default time servers?\n')) - prompt += str(_('Hardware time and other post-configuration steps might be required in order for NTP to work.\nFor more information, please check the Arch wiki')) - if preset: - preset_val = Menu.yes() - else: - preset_val = Menu.no() - choice = Menu(prompt, Menu.yes_no(), skip=False, preset_values=preset_val, default_option=Menu.yes()).run() - - return False if choice.value == Menu.no() else True - - -def ask_hostname(preset: str = '') -> str: - while True: - hostname = TextInput( - str(_('Desired hostname for the installation: ')), - preset - ).run().strip() - - if hostname: - return hostname - - -def ask_for_a_timezone(preset: Optional[str] = None) -> Optional[str]: - timezones = list_timezones() - default = 'UTC' - - choice = Menu( - _('Select a timezone'), - list(timezones), - preset_values=preset, - default_option=default - ).run() - - match choice.type_: - case MenuSelectionType.Skip: return preset - case MenuSelectionType.Selection: return choice.single_value - - return None - - -def ask_for_audio_selection(desktop: bool = True, preset: Optional[str] = None) -> Optional[str]: - no_audio = str(_('No audio server')) - choices = ['pipewire', 'pulseaudio'] if desktop else ['pipewire', 'pulseaudio', no_audio] - default = 'pipewire' if desktop else no_audio - - choice = Menu(_('Choose an audio server'), choices, preset_values=preset, default_option=default).run() - - match choice.type_: - case MenuSelectionType.Skip: return preset - case MenuSelectionType.Selection: return choice.single_value - - return None - - -def select_language(preset: Optional[str] = None) -> Optional[str]: - """ - Asks the user to select a language - Usually this is combined with :ref:`archinstall.list_keyboard_languages`. - - :return: The language/dictionary key of the selected language - :rtype: str - """ - kb_lang = list_keyboard_languages() - # sort alphabetically and then by length - sorted_kb_lang = sorted(sorted(list(kb_lang)), key=len) - - choice = Menu( - _('Select keyboard layout'), - sorted_kb_lang, - preset_values=preset, - sort=False - ).run() - - match choice.type_: - case MenuSelectionType.Skip: return preset - case MenuSelectionType.Selection: return choice.single_value - - return None - - -def select_mirror_regions(preset_values: Dict[str, Any] = {}) -> Dict[str, Any]: - """ - Asks the user to select a mirror or region - Usually this is combined with :ref:`archinstall.list_mirrors`. - - :return: The dictionary information about a mirror/region. - :rtype: dict - """ - if preset_values is None: - preselected = None - else: - preselected = list(preset_values.keys()) - - mirrors = list_mirrors() - - choice = Menu( - _('Select one of the regions to download packages from'), - list(mirrors.keys()), - preset_values=preselected, - multi=True, - allow_reset=True - ).run() - - match choice.type_: - case MenuSelectionType.Reset: - return {} - case MenuSelectionType.Skip: - return preset_values - case MenuSelectionType.Selection: - return {selected: mirrors[selected] for selected in choice.multi_value} - - return {} - - -def select_archinstall_language(languages: List[Language], preset: 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} - - title = 'NOTE: If a language can not displayed properly, a proper font must be set manually in the console.\n' - title += 'All available fonts can be found in "/usr/share/kbd/consolefonts"\n' - title += 'e.g. setfont LatGrkCyr-8x16 (to display latin/greek/cyrillic characters)\n' - - choice = Menu( - title, - list(options.keys()), - default_option=preset.display_name, - preview_size=0.5 - ).run() - - match choice.type_: - case MenuSelectionType.Skip: return preset - case MenuSelectionType.Selection: return options[choice.single_value] - - raise ValueError('Language selection not handled') - - -def ask_additional_packages_to_install(pre_set_packages: List[str] = []) -> List[str]: - # Additional packages (with some light weight error handling for invalid package names) - print(_('Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed.')) - print(_('If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt.')) - - 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().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) - - 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 - - -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(_("[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(_(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: - 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+1}\n") if not input_number == 0 else fwrite.write("#ParallelDownloads = 0\n") - else: - fwrite.write(f"{line}\n") - - return input_number - - -def select_additional_repositories(preset: List[str]) -> List[str]: - """ - Allows the user to select additional repositories (multilib, and testing) if desired. - - :return: The string as a selected repository - :rtype: string - """ - - repositories = ["multilib", "testing"] - - choice = Menu( - _('Choose which optional additional repositories to enable'), - repositories, - sort=False, - multi=True, - preset_values=preset, - allow_reset=True - ).run() - - match choice.type_: - case MenuSelectionType.Skip: return preset - case MenuSelectionType.Reset: return [] - case MenuSelectionType.Selection: return choice.single_value - - return [] diff --git a/archinstall/lib/user_interaction/locale_conf.py b/archinstall/lib/user_interaction/locale_conf.py deleted file mode 100644 index cdc3423a..00000000 --- a/archinstall/lib/user_interaction/locale_conf.py +++ /dev/null @@ -1,45 +0,0 @@ -from __future__ import annotations - -from typing import Any, TYPE_CHECKING, Optional - -from ..locale_helpers import list_locales -from ..menu import Menu, MenuSelectionType - -if TYPE_CHECKING: - _: Any - - -def select_locale_lang(preset: Optional[str] = None) -> Optional[str]: - locales = list_locales() - locale_lang = set([locale.split()[0] for locale in locales]) - - choice = Menu( - _('Choose which locale language to use'), - list(locale_lang), - sort=True, - preset_values=preset - ).run() - - match choice.type_: - case MenuSelectionType.Selection: return choice.single_value - case MenuSelectionType.Skip: return preset - - return None - - -def select_locale_enc(preset: Optional[str] = None) -> Optional[str]: - locales = list_locales() - locale_enc = set([locale.split()[1] for locale in locales]) - - choice = Menu( - _('Choose which locale encoding to use'), - list(locale_enc), - sort=True, - preset_values=preset - ).run() - - match choice.type_: - case MenuSelectionType.Selection: return choice.single_value - case MenuSelectionType.Skip: return preset - - return None diff --git a/archinstall/lib/user_interaction/manage_users_conf.py b/archinstall/lib/user_interaction/manage_users_conf.py deleted file mode 100644 index 879578da..00000000 --- a/archinstall/lib/user_interaction/manage_users_conf.py +++ /dev/null @@ -1,106 +0,0 @@ -from __future__ import annotations - -import re -from typing import Any, Dict, TYPE_CHECKING, List, Optional - -from .utils import get_password -from ..menu import Menu, ListManager -from ..models.users import User -from ..output import FormattedOutput - -if TYPE_CHECKING: - _: Any - - -class UserList(ListManager): - """ - subclass of ListManager for the managing of user accounts - """ - - def __init__(self, prompt: str, lusers: List[User]): - self._actions = [ - str(_('Add a user')), - str(_('Change password')), - str(_('Promote/Demote user')), - str(_('Delete User')) - ] - super().__init__(prompt, lusers, [self._actions[0]], self._actions[1:]) - - def reformat(self, data: List[User]) -> Dict[str, Any]: - table = FormattedOutput.as_table(data) - rows = table.split('\n') - - # 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[User]] = {f' {rows[0]}': None, f' {rows[1]}': None} - - for row, user in zip(rows[2:], data): - row = row.replace('|', '\\|') - display_data[row] = user - - return display_data - - def selected_action_display(self, user: User) -> str: - return user.username - - 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 action == self._actions[1] and entry: # 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 == entry, data)) - user.password = new_password - elif action == self._actions[2] and entry: # promote/demote - user = next(filter(lambda x: x == entry, data)) - user.sudo = False if user.sudo else True - elif action == self._actions[3] and entry: # delete - data = [d for d in data if d != entry] - - return data - - def _check_for_correct_username(self, username: str) -> bool: - if re.match(r'^[a-z_][a-z0-9_-]*\$?$', username) and len(username) <= 32: - return True - return False - - def _add_user(self) -> Optional[User]: - prompt = '\n\n' + str(_('Enter username (leave blank to skip): ')) - - while True: - username = input(prompt).strip(' ') - if not username: - return None - if not self._check_for_correct_username(username): - error_prompt = str(_("The username you entered is invalid. Try again")) - print(error_prompt) - else: - break - - password = get_password(prompt=str(_('Password for user "{}": ').format(username))) - - if not password: - return None - - choice = Menu( - str(_('Should "{}" be a superuser (sudo)?')).format(username), Menu.yes_no(), - skip=False, - default_option=Menu.yes(), - clear_screen=False, - show_search_hint=False - ).run() - - sudo = True if choice.value == Menu.yes() else False - return User(username, password, sudo) - - -def ask_for_additional_users(prompt: str = '', defined_users: List[User] = []) -> List[User]: - 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 deleted file mode 100644 index b682c1d2..00000000 --- a/archinstall/lib/user_interaction/network_conf.py +++ /dev/null @@ -1,173 +0,0 @@ -from __future__ import annotations - -import ipaddress -import logging -from typing import Any, Optional, TYPE_CHECKING, List, Union, Dict - -from ..menu import MenuSelectionType, TextInput -from ..models.network_configuration import NetworkConfiguration, NicType - -from ..networking import list_interfaces -from ..output import log, FormattedOutput -from ..menu import ListManager, Menu - -if TYPE_CHECKING: - _: Any - - -class ManualNetworkConfig(ListManager): - """ - subclass of ListManager for the managing of network configurations - """ - - def __init__(self, prompt: str, ifaces: List[NetworkConfiguration]): - self._actions = [ - str(_('Add interface')), - str(_('Edit interface')), - str(_('Delete interface')) - ] - - 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) - rows = table.split('\n') - - # 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} - - for row, iface in zip(rows[2:], data): - row = row.replace('|', '\\|') - display_data[row] = iface - - return display_data - - def selected_action_display(self, iface: NetworkConfiguration) -> str: - return iface.iface if iface.iface else '' - - 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 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 - - 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() - - if choice.type_ == MenuSelectionType.Skip: - return None - - return choice.value - - def _edit_iface(self, edit_iface: NetworkConfiguration): - iface_name = edit_iface.iface - modes = ['DHCP (auto detect)', 'IP (static)'] - default_mode = 'DHCP (auto detect)' - - prompt = _('Select which mode to configure for "{}" or skip to use default mode "{}"').format(iface_name, default_mode) - mode = Menu(prompt, modes, default_option=default_mode, skip=False).run() - - if mode.value == 'IP (static)': - while 1: - prompt = _('Enter the IP and subnet for {} (example: 192.168.0.5/24): ').format(iface_name) - ip = TextInput(prompt, edit_iface.ip).run().strip() - # Implemented new check for correct IP/subnet input - try: - ipaddress.ip_interface(ip) - break - except ValueError: - log("You need to enter a valid IP in IP-config mode.", level=logging.WARNING, fg='red') - - # Implemented new check for correct gateway IP address - gateway = None - - while 1: - gateway = TextInput( - _('Enter your gateway (router) IP address or leave blank for none: '), - edit_iface.gateway - ).run().strip() - try: - 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') - - if edit_iface.dns: - display_dns = ' '.join(edit_iface.dns) - else: - display_dns = None - dns_input = TextInput(_('Enter your DNS servers (space separated, blank for none): '), display_dns).run().strip() - - dns = [] - if len(dns_input): - dns = dns_input.split(' ') - - return NetworkConfiguration(NicType.MANUAL, iface=iface_name, ip=ip, gateway=gateway, dns=dns, dhcp=False) - else: - # this will contain network iface names - return NetworkConfiguration(NicType.MANUAL, iface=iface_name) - - -def ask_to_configure_network( - preset: Union[NetworkConfiguration, List[NetworkConfiguration]] -) -> Optional[NetworkConfiguration | List[NetworkConfiguration]]: - """ - Configure the network on the newly installed system - """ - network_options = { - 'none': str(_('No network configuration')), - 'iso_config': str(_('Copy ISO network configuration to installation')), - 'network_manager': str(_('Use NetworkManager (necessary to configure internet graphically in GNOME and KDE)')), - 'manual': str(_('Manual configuration')) - } - # for this routine it's easier to set the cursor position rather than a preset value - cursor_idx = None - - if preset and not isinstance(preset, list): - if preset.type == 'iso_config': - cursor_idx = 0 - elif preset.type == 'network_manager': - cursor_idx = 1 - - warning = str(_('Are you sure you want to reset this setting?')) - - choice = Menu( - _('Select one network interface to configure'), - list(network_options.values()), - cursor_index=cursor_idx, - sort=False, - allow_reset=True, - allow_reset_warning_msg=warning - ).run() - - match choice.type_: - case MenuSelectionType.Skip: return preset - case MenuSelectionType.Reset: return None - - if choice.value == network_options['none']: - return None - elif choice.value == network_options['iso_config']: - return NetworkConfiguration(NicType.ISO) - elif choice.value == network_options['network_manager']: - return NetworkConfiguration(NicType.NM) - elif choice.value == network_options['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/save_conf.py b/archinstall/lib/user_interaction/save_conf.py deleted file mode 100644 index e05b9afe..00000000 --- a/archinstall/lib/user_interaction/save_conf.py +++ /dev/null @@ -1,113 +0,0 @@ -from __future__ import annotations - -import logging - -from pathlib import Path -from typing import Any, Dict, TYPE_CHECKING - -from ..general import SysCommand -from ..menu import Menu -from ..menu.menu import MenuSelectionType -from ..output import log -from ..configuration import ConfigurationOutput - -if TYPE_CHECKING: - _: Any - - -def save_config(config: Dict): - def preview(selection: str): - if options['user_config'] == selection: - serialized = config_output.user_config_to_json() - return f'{config_output.user_configuration_file}\n{serialized}' - elif options['user_creds'] == selection: - if maybe_serial := config_output.user_credentials_to_json(): - return f'{config_output.user_credentials_file}\n{maybe_serial}' - else: - return str(_('No configuration')) - elif options['all'] == selection: - output = f'{config_output.user_configuration_file}\n' - if config_output.user_credentials_to_json(): - output += f'{config_output.user_credentials_file}\n' - return output[:-1] - return None - - config_output = ConfigurationOutput(config) - - options = { - 'user_config': str(_('Save user configuration')), - 'user_creds': str(_('Save user credentials')), - 'disk_layout': str(_('Save disk layout')), - 'all': str(_('Save all')) - } - - choice = Menu( - _('Choose which configuration to save'), - list(options.values()), - sort=False, - skip=True, - preview_size=0.75, - preview_command=preview - ).run() - - if choice.type_ == MenuSelectionType.Skip: - return - - save_config_value = choice.single_value - saving_key = [k for k, v in options.items() if v == save_config_value][0] - - dirs_to_exclude = [ - '/bin', - '/dev', - '/lib', - '/lib64', - '/lost+found', - '/opt', - '/proc', - '/run', - '/sbin', - '/srv', - '/sys', - '/usr', - '/var', - ] - - log('Ignore configuration option folders: ' + ','.join(dirs_to_exclude), level=logging.DEBUG) - log(_('Finding possible directories to save configuration files ...'), level=logging.INFO) - - find_exclude = '-path ' + ' -prune -o -path '.join(dirs_to_exclude) + ' -prune ' - file_picker_command = f'find / {find_exclude} -o -type d -print0' - - directories = SysCommand(file_picker_command).decode() - - if directories is None: - raise ValueError('Failed to retrieve possible configuration directories') - - possible_save_dirs = list(filter(None, directories.split('\x00'))) - - selection = Menu( - _('Select directory (or directories) for saving configuration files'), - possible_save_dirs, - multi=True, - skip=True, - allow_reset=False, - ).run() - - match selection.type_: - case MenuSelectionType.Skip: - return - - save_dirs = selection.multi_value - - log(f'Saving {saving_key} configuration files to {save_dirs}', level=logging.DEBUG) - - if save_dirs is not None: - for save_dir_str in save_dirs: - save_dir = Path(save_dir_str) - if options['user_config'] == save_config_value: - config_output.save_user_config(save_dir) - elif options['user_creds'] == save_config_value: - config_output.save_user_creds(save_dir) - elif options['all'] == save_config_value: - config_output.save_user_config(save_dir) - config_output.save_user_creds(save_dir) diff --git a/archinstall/lib/user_interaction/system_conf.py b/archinstall/lib/user_interaction/system_conf.py deleted file mode 100644 index 3f57d0e7..00000000 --- a/archinstall/lib/user_interaction/system_conf.py +++ /dev/null @@ -1,117 +0,0 @@ -from __future__ import annotations - -from typing import List, Any, Dict, TYPE_CHECKING, Optional - -from ..hardware import AVAILABLE_GFX_DRIVERS, has_uefi, has_amd_graphics, has_intel_graphics, has_nvidia_graphics -from ..menu import MenuSelectionType, Menu -from ..models.bootloader import Bootloader - -if TYPE_CHECKING: - _: Any - - -def select_kernel(preset: List[str] = []) -> List[str]: - """ - Asks the user to select a kernel for system. - - :return: The string as a selected kernel - :rtype: string - """ - - kernels = ["linux", "linux-lts", "linux-zen", "linux-hardened"] - default_kernel = "linux" - - warning = str(_('Are you sure you want to reset this setting?')) - - choice = Menu( - _('Choose which kernels to use or leave blank for default "{}"').format(default_kernel), - kernels, - sort=True, - multi=True, - preset_values=preset, - allow_reset=True, - allow_reset_warning_msg=warning - ).run() - - match choice.type_: - case MenuSelectionType.Skip: return preset - case MenuSelectionType.Reset: return [] - case MenuSelectionType.Selection: return choice.value # type: ignore - - -def ask_for_bootloader(preset: Bootloader) -> Bootloader: - # when the system only supports grub - if not has_uefi(): - options = [Bootloader.Grub.value] - default = Bootloader.Grub.value - else: - options = Bootloader.values() - default = Bootloader.Systemd.value - - preset_value = preset.value if preset else None - - choice = Menu( - _('Choose a bootloader'), - options, - preset_values=preset_value, - sort=False, - default_option=default - ).run() - - match choice.type_: - case MenuSelectionType.Skip: return preset - case MenuSelectionType.Selection: return Bootloader(choice.value) - - return preset - - -def select_driver(options: Dict[str, Any] = {}, current_value: Optional[str] = None) -> Optional[str]: - """ - Some what convoluted function, whose job is simple. - Select a graphics driver from a pre-defined set of popular options. - - (The template xorg is for beginner users, not advanced, and should - there for appeal to the general public first and edge cases later) - """ - - if not options: - options = AVAILABLE_GFX_DRIVERS - - drivers = sorted(list(options.keys())) - - if drivers: - title = '' - if has_amd_graphics(): - title += str(_('For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options.')) + '\n' - if has_intel_graphics(): - title += str(_('For the best compatibility with your Intel hardware, you may want to use either the all open-source or Intel options.\n')) - if has_nvidia_graphics(): - title += str(_('For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n')) - - title += str(_('\nSelect a graphics driver or leave blank to install all open-source drivers')) - - preset = current_value if current_value else None - choice = Menu(title, drivers, preset_values=preset).run() - - if choice.type_ != MenuSelectionType.Selection: - return None - - return choice.value # type: ignore - - return current_value - - -def ask_for_swap(preset: bool = True) -> bool: - if preset: - preset_val = Menu.yes() - else: - preset_val = Menu.no() - - prompt = _('Would you like to use swap on zram?') - choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes(), preset_values=preset_val).run() - - match choice.type_: - case MenuSelectionType.Skip: return preset - case MenuSelectionType.Selection: return False if choice.value == Menu.no() else True - - return preset diff --git a/archinstall/lib/user_interaction/utils.py b/archinstall/lib/user_interaction/utils.py deleted file mode 100644 index 918945c0..00000000 --- a/archinstall/lib/user_interaction/utils.py +++ /dev/null @@ -1,34 +0,0 @@ -from __future__ import annotations - -import getpass -from typing import Any, Optional, TYPE_CHECKING - -from ..models import PasswordStrength -from ..output import log - -if TYPE_CHECKING: - _: Any - -# used for signal handler -SIG_TRIGGER = None - - -def get_password(prompt: str = '') -> Optional[str]: - if not prompt: - prompt = _("Enter a password: ") - - while password := getpass.getpass(prompt): - if len(password.strip()) <= 0: - break - - 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 password != passwd_verification: - log(' * Passwords did not match * ', fg='red') - continue - - return password - - return None diff --git a/archinstall/lib/utils/util.py b/archinstall/lib/utils/util.py index ded480ae..34716f4a 100644 --- a/archinstall/lib/utils/util.py +++ b/archinstall/lib/utils/util.py @@ -1,7 +1,7 @@ from pathlib import Path from typing import Any, TYPE_CHECKING, Optional -from ..output import log +from ..output import info if TYPE_CHECKING: _: Any @@ -16,7 +16,7 @@ def prompt_dir(text: str, header: Optional[str] = None) -> Path: dest_path = Path(path) if dest_path.exists() and dest_path.is_dir(): return dest_path - log(_('Not a valid directory: {}').format(dest_path), fg='red') + info(_('Not a valid directory: {}').format(dest_path)) def is_subpath(first: Path, second: Path): diff --git a/archinstall/scripts/guided.py b/archinstall/scripts/guided.py index 9906e0a9..37cc1cad 100644 --- a/archinstall/scripts/guided.py +++ b/archinstall/scripts/guided.py @@ -1,9 +1,10 @@ -import logging import os from pathlib import Path from typing import Any, TYPE_CHECKING import archinstall +from archinstall import info, debug +from archinstall import SysInfo from archinstall.lib import disk from archinstall.lib.global_menu import GlobalMenu from archinstall.default_profiles.applications.pipewire import PipewireProfile @@ -13,7 +14,7 @@ from archinstall.lib.menu import Menu from archinstall.lib.mirrors import use_mirrors from archinstall.lib.models.bootloader import Bootloader from archinstall.lib.models.network_configuration import NetworkConfigurationHandler -from archinstall.lib.output import log +from archinstall.lib.networking import check_mirror_reachable from archinstall.lib.profile.profiles_handler import profile_handler if TYPE_CHECKING: @@ -24,20 +25,6 @@ if archinstall.arguments.get('help'): print("See `man archinstall` for help.") exit(0) -if os.getuid() != 0: - print(_("Archinstall requires root privileges to run. See --help for more.")) - exit(1) - -# Log various information about hardware before starting the installation. This might assist in troubleshooting -archinstall.log(f"Hardware model detected: {archinstall.sys_vendor()} {archinstall.product_name()}; UEFI mode: {archinstall.has_uefi()}", level=logging.DEBUG) -archinstall.log(f"Processor model detected: {archinstall.cpu_model()}", level=logging.DEBUG) -archinstall.log(f"Memory statistics: {archinstall.mem_available()} available out of {archinstall.mem_total()} total installed", level=logging.DEBUG) -archinstall.log(f"Virtualization detected: {archinstall.virtualization()}; is VM: {archinstall.is_vm()}", level=logging.DEBUG) -archinstall.log(f"Graphics devices detected: {archinstall.graphics_devices().keys()}", level=logging.DEBUG) - -# For support reasons, we'll log the disk layout pre installation to match against post-installation layout -archinstall.log(f"Disk states before installing: {disk.disk_layouts()}", level=logging.DEBUG) - def ask_user_questions(): """ @@ -121,7 +108,7 @@ def perform_installation(mountpoint: Path): Only requirement is that the block devices are formatted and setup prior to entering this function. """ - log('Starting installation', level=logging.INFO) + info('Starting installation') disk_config: disk.DiskLayoutConfiguration = archinstall.arguments['disk_config'] # Retrieve list of additional repositories and set boolean values appropriately @@ -167,7 +154,7 @@ def perform_installation(mountpoint: Path): if archinstall.arguments.get('swap'): installation.setup_swap('zram') - if archinstall.arguments.get("bootloader") == Bootloader.Grub and archinstall.has_uefi(): + if archinstall.arguments.get("bootloader") == Bootloader.Grub and SysInfo.has_uefi(): installation.add_additional_packages("grub") installation.add_bootloader(archinstall.arguments["bootloader"]) @@ -190,13 +177,13 @@ def perform_installation(mountpoint: Path): installation.create_users(users) if audio := archinstall.arguments.get('audio', None): - log(f'Installing audio server: {audio}', level=logging.INFO) + info(f'Installing audio server: {audio}') if audio == 'pipewire': PipewireProfile().install(installation) elif audio == 'pulseaudio': installation.add_additional_packages("pulseaudio") else: - installation.log("No audio server will be installed.", level=logging.INFO) + info("No audio server will be installed") if profile_config := archinstall.arguments.get('profile_config', None): profile_handler.install_profile_config(installation, profile_config) @@ -231,7 +218,7 @@ def perform_installation(mountpoint: Path): installation.genfstab() - installation.log("For post-installation tips, see https://wiki.archlinux.org/index.php/Installation_guide#Post-installation", fg="yellow") + info("For post-installation tips, see https://wiki.archlinux.org/index.php/Installation_guide#Post-installation") if not archinstall.arguments.get('silent'): prompt = str(_('Would you like to chroot into the newly created installation and perform post-installation configuration?')) @@ -242,12 +229,12 @@ def perform_installation(mountpoint: Path): except: pass - archinstall.log(f"Disk states after installing: {disk.disk_layouts()}", level=logging.DEBUG) + debug(f"Disk states after installing: {disk.disk_layouts()}") -if archinstall.arguments.get('skip-mirror-check', False) is False and archinstall.check_mirror_reachable() is False: +if archinstall.arguments.get('skip-mirror-check', False) is False and check_mirror_reachable() is False: log_file = os.path.join(archinstall.storage.get('LOG_PATH', None), archinstall.storage.get('LOG_FILE', None)) - 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") + info(f"Arch Linux mirrors are not reachable. Please check your internet connection and the log file '{log_file}'.") exit(1) if not archinstall.arguments.get('silent'): diff --git a/archinstall/scripts/minimal.py b/archinstall/scripts/minimal.py index 0cdbdcef..704759fc 100644 --- a/archinstall/scripts/minimal.py +++ b/archinstall/scripts/minimal.py @@ -2,23 +2,25 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, List import archinstall -from archinstall import ConfigurationOutput, Installer, ProfileConfiguration, profile_handler +from archinstall import info +from archinstall import Installer, ConfigurationOutput from archinstall.default_profiles.minimal import MinimalProfile -from archinstall import disk -from archinstall import models -from archinstall.lib.user_interaction.disk_conf import select_devices, suggest_single_disk_layout +from archinstall.lib.interactions import suggest_single_disk_layout, select_devices +from archinstall.lib.models import Bootloader, User +from archinstall.lib.profile import ProfileConfiguration, profile_handler +from archinstall.lib import disk if TYPE_CHECKING: _: Any -archinstall.log("Minimal only supports:") -archinstall.log(" * Being installed to a single disk") +info("Minimal only supports:") +info(" * Being installed to a single disk") if archinstall.arguments.get('help', None): - archinstall.log(" - Optional disk encryption via --!encryption-password=") - archinstall.log(" - Optional filesystem type via --filesystem=") - archinstall.log(" - Optional systemd network via --network") + info(" - Optional disk encryption via --!encryption-password=") + info(" - Optional filesystem type via --filesystem=") + info(" - Optional systemd network via --network") def perform_installation(mountpoint: Path): @@ -35,7 +37,7 @@ def perform_installation(mountpoint: Path): # some other minor details as specified by this profile and user. if installation.minimal_installation(): installation.set_hostname('minimal-arch') - installation.add_bootloader(models.Bootloader.Systemd) + installation.add_bootloader(Bootloader.Systemd) # Optionally enable networking: if archinstall.arguments.get('network', None): @@ -46,14 +48,14 @@ def perform_installation(mountpoint: Path): profile_config = ProfileConfiguration(MinimalProfile()) profile_handler.install_profile_config(installation, profile_config) - user = models.User('devel', 'devel', False) + user = User('devel', 'devel', False) installation.create_users(user) # Once this is done, we output some useful information to the user # And the installation is complete. - archinstall.log("There are two new accounts in your installation after reboot:") - archinstall.log(" * root (password: airoot)") - archinstall.log(" * devel (password: devel)") + info("There are two new accounts in your installation after reboot:") + info(" * root (password: airoot)") + info(" * devel (password: devel)") def prompt_disk_layout(): diff --git a/archinstall/scripts/only_hd.py b/archinstall/scripts/only_hd.py index a903c5fe..d0ee1e39 100644 --- a/archinstall/scripts/only_hd.py +++ b/archinstall/scripts/only_hd.py @@ -1,22 +1,18 @@ -import logging import os from pathlib import Path import archinstall -from archinstall import Installer +from archinstall import info, debug +from archinstall.lib.installer import Installer from archinstall.lib.configuration import ConfigurationOutput -from archinstall import disk +from archinstall.lib import disk +from archinstall.lib.networking import check_mirror_reachable if archinstall.arguments.get('help'): print("See `man archinstall` for help.") exit(0) -if os.getuid() != 0: - print("Archinstall requires root privileges to run. See --help for more.") - exit(1) - - def ask_user_questions(): global_menu = archinstall.GlobalMenu(data_store=archinstall.arguments) @@ -59,23 +55,12 @@ def perform_installation(mountpoint: Path): target.parent.mkdir(parents=True) # For support reasons, we'll log the disk layout post installation (crash or no crash) - archinstall.log(f"Disk states after installing: {disk.disk_layouts()}", level=logging.DEBUG) - - -# Log various information about hardware before starting the installation. This might assist in troubleshooting -archinstall.log(f"Hardware model detected: {archinstall.sys_vendor()} {archinstall.product_name()}; UEFI mode: {archinstall.has_uefi()}", level=logging.DEBUG) -archinstall.log(f"Processor model detected: {archinstall.cpu_model()}", level=logging.DEBUG) -archinstall.log(f"Memory statistics: {archinstall.mem_available()} available out of {archinstall.mem_total()} total installed", level=logging.DEBUG) -archinstall.log(f"Virtualization detected: {archinstall.virtualization()}; is VM: {archinstall.is_vm()}", level=logging.DEBUG) -archinstall.log(f"Graphics devices detected: {archinstall.graphics_devices().keys()}", level=logging.DEBUG) - -# For support reasons, we'll log the disk layout pre installation to match against post-installation layout -archinstall.log(f"Disk states before installing: {disk.disk_layouts()}", level=logging.DEBUG) + debug(f"Disk states after installing: {disk.disk_layouts()}") -if archinstall.arguments.get('skip-mirror-check', False) is False and archinstall.check_mirror_reachable() is False: +if archinstall.arguments.get('skip-mirror-check', False) is False and check_mirror_reachable() is False: log_file = os.path.join(archinstall.storage.get('LOG_PATH', None), archinstall.storage.get('LOG_FILE', None)) - 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") + info(f"Arch Linux mirrors are not reachable. Please check your internet connection and the log file '{log_file}'") exit(1) if not archinstall.arguments.get('silent'): diff --git a/archinstall/scripts/swiss.py b/archinstall/scripts/swiss.py index 3bf847b1..a49f568d 100644 --- a/archinstall/scripts/swiss.py +++ b/archinstall/scripts/swiss.py @@ -1,18 +1,18 @@ -import logging import os from enum import Enum from pathlib import Path from typing import TYPE_CHECKING, Any, Dict import archinstall +from archinstall import SysInfo, info, debug from archinstall.lib.mirrors import use_mirrors -from archinstall import models -from archinstall import disk +from archinstall.lib import models +from archinstall.lib import disk +from archinstall.lib.networking import check_mirror_reachable from archinstall.lib.profile.profiles_handler import profile_handler -from archinstall import menu +from archinstall.lib import menu from archinstall.lib.global_menu import GlobalMenu -from archinstall.lib.output import log -from archinstall import Installer +from archinstall.lib.installer import Installer from archinstall.lib.configuration import ConfigurationOutput from archinstall.default_profiles.applications.pipewire import PipewireProfile @@ -25,11 +25,6 @@ if archinstall.arguments.get('help'): exit(0) -if os.getuid() != 0: - print("Archinstall requires root privileges to run. See --help for more.") - exit(1) - - class ExecutionMode(Enum): Full = 'full' Lineal = 'lineal' @@ -76,7 +71,7 @@ class SetupMenu(GlobalMenu): def exit_callback(self): if self._data_store.get('mode', None): archinstall.arguments['mode'] = self._data_store['mode'] - log(f"Archinstall will execute under {archinstall.arguments['mode']} mode") + info(f"Archinstall will execute under {archinstall.arguments['mode']} mode") class SwissMainMenu(GlobalMenu): @@ -124,7 +119,7 @@ class SwissMainMenu(GlobalMenu): case ExecutionMode.Minimal: pass case _: - archinstall.log(f' Execution mode {self._execution_mode} not supported') + info(f' Execution mode {self._execution_mode} not supported') exit(1) if self._execution_mode != ExecutionMode.Lineal: @@ -219,7 +214,7 @@ def perform_installation(mountpoint: Path, exec_mode: ExecutionMode): if archinstall.arguments.get('swap'): installation.setup_swap('zram') - if archinstall.arguments.get("bootloader") == models.Bootloader.Grub and archinstall.has_uefi(): + if archinstall.arguments.get("bootloader") == models.Bootloader.Grub and SysInfo.has_uefi(): installation.add_additional_packages("grub") installation.add_bootloader(archinstall.arguments["bootloader"]) @@ -242,13 +237,13 @@ def perform_installation(mountpoint: Path, exec_mode: ExecutionMode): installation.create_users(users) if audio := archinstall.arguments.get('audio', None): - log(f'Installing audio server: {audio}', level=logging.INFO) + info(f'Installing audio server: {audio}') if audio == 'pipewire': PipewireProfile().install(installation) elif audio == 'pulseaudio': installation.add_additional_packages("pulseaudio") else: - installation.log("No audio server will be installed.", level=logging.INFO) + info("No audio server will be installed.") if profile_config := archinstall.arguments.get('profile_config', None): profile_handler.install_profile_config(installation, profile_config) @@ -283,9 +278,7 @@ def perform_installation(mountpoint: Path, exec_mode: ExecutionMode): installation.genfstab() - installation.log( - "For post-installation tips, see https://wiki.archlinux.org/index.php/Installation_guide#Post-installation", - fg="yellow") + info("For post-installation tips, see https://wiki.archlinux.org/index.php/Installation_guide#Post-installation") if not archinstall.arguments.get('silent'): prompt = str( @@ -297,23 +290,12 @@ def perform_installation(mountpoint: Path, exec_mode: ExecutionMode): except: pass - archinstall.log(f"Disk states after installing: {disk.disk_layouts()}", level=logging.DEBUG) - - -# Log various information about hardware before starting the installation. This might assist in troubleshooting -archinstall.log(f"Hardware model detected: {archinstall.sys_vendor()} {archinstall.product_name()}; UEFI mode: {archinstall.has_uefi()}", level=logging.DEBUG) -archinstall.log(f"Processor model detected: {archinstall.cpu_model()}", level=logging.DEBUG) -archinstall.log(f"Memory statistics: {archinstall.mem_available()} available out of {archinstall.mem_total()} total installed", level=logging.DEBUG) -archinstall.log(f"Virtualization detected: {archinstall.virtualization()}; is VM: {archinstall.is_vm()}", level=logging.DEBUG) -archinstall.log(f"Graphics devices detected: {archinstall.graphics_devices().keys()}", level=logging.DEBUG) - -# For support reasons, we'll log the disk layout pre installation to match against post-installation layout -archinstall.log(f"Disk states before installing: {disk.disk_layouts()}", level=logging.DEBUG) + debug(f"Disk states after installing: {disk.disk_layouts()}") -if not archinstall.check_mirror_reachable(): +if not check_mirror_reachable(): log_file = os.path.join(archinstall.storage.get('LOG_PATH', None), archinstall.storage.get('LOG_FILE', None)) - 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") + info(f"Arch Linux mirrors are not reachable. Please check your internet connection and the log file '{log_file}'") exit(1) param_mode = archinstall.arguments.get('mode', ExecutionMode.Full.value).lower() @@ -321,7 +303,7 @@ param_mode = archinstall.arguments.get('mode', ExecutionMode.Full.value).lower() try: mode = ExecutionMode(param_mode) except KeyError: - log(f'Mode "{param_mode}" is not supported') + info(f'Mode "{param_mode}" is not supported') exit(1) if not archinstall.arguments.get('silent'): diff --git a/archinstall/scripts/unattended.py b/archinstall/scripts/unattended.py index 0a1c5160..5ae4ae3d 100644 --- a/archinstall/scripts/unattended.py +++ b/archinstall/scripts/unattended.py @@ -1,13 +1,14 @@ import time import archinstall -from archinstall.lib.profile.profiles_handler import profile_handler +from archinstall import info +from archinstall import profile -for profile in profile_handler.get_mac_addr_profiles(): +for p in profile.profile_handler.get_mac_addr_profiles(): # Tailored means it's a match for this machine # based on it's MAC address (or some other criteria # that fits the requirements for this machine specifically). - archinstall.log(f'Found a tailored profile for this machine called: "{profile.name}"') + info(f'Found a tailored profile for this machine called: "{p.name}"') print('Starting install in:') for i in range(10, 0, -1): @@ -15,4 +16,4 @@ for profile in profile_handler.get_mac_addr_profiles(): time.sleep(1) install_session = archinstall.storage['installation_session'] - profile.install(install_session) + p.install(install_session) diff --git a/examples/full_automated_installation.py b/examples/full_automated_installation.py index a169dd50..dcef731a 100644 --- a/examples/full_automated_installation.py +++ b/examples/full_automated_installation.py @@ -1,9 +1,10 @@ from pathlib import Path -from archinstall import Installer, ProfileConfiguration, profile_handler +from archinstall import Installer +from archinstall import profile from archinstall.default_profiles.minimal import MinimalProfile from archinstall import disk -from archinstall.lib.models import User +from archinstall import models # we're creating a new ext4 filesystem installation fs_type = disk.FilesystemType('ext4') @@ -88,8 +89,8 @@ with Installer( # Optionally, install a profile of choice. # In this case, we install a minimal profile that is empty -profile_config = ProfileConfiguration(MinimalProfile()) -profile_handler.install_profile_config(installation, profile_config) +profile_config = profile.ProfileConfiguration(MinimalProfile()) +profile.profile_handler.install_profile_config(installation, profile_config) -user = User('archinstall', 'password', True) +user = models.User('archinstall', 'password', True) installation.create_users(user) diff --git a/examples/interactive_installation.py b/examples/interactive_installation.py index a27ec0f9..72595048 100644 --- a/examples/interactive_installation.py +++ b/examples/interactive_installation.py @@ -1,13 +1,16 @@ -import logging from pathlib import Path from typing import TYPE_CHECKING, Any import archinstall -from archinstall import log, Installer, use_mirrors, profile_handler +from archinstall import Installer +from archinstall import profile +from archinstall import SysInfo +from archinstall import mirrors from archinstall.default_profiles.applications.pipewire import PipewireProfile from archinstall import disk from archinstall import menu -from archinstall.lib.models import Bootloader, NetworkConfigurationHandler +from archinstall import models +from archinstall import info, debug if TYPE_CHECKING: _: Any @@ -84,7 +87,7 @@ def perform_installation(mountpoint: Path): Only requirement is that the block devices are formatted and setup prior to entering this function. """ - log('Starting installation', level=logging.INFO) + info('Starting installation') disk_config: disk.DiskLayoutConfiguration = archinstall.arguments['disk_config'] # Retrieve list of additional repositories and set boolean values appropriately @@ -114,7 +117,7 @@ def perform_installation(mountpoint: Path): # Set mirrors used by pacstrap (outside of installation) if archinstall.arguments.get('mirror-region', None): - use_mirrors(archinstall.arguments['mirror-region']) # Set the mirrors for the live medium + mirrors.use_mirrors(archinstall.arguments['mirror-region']) # Set the mirrors for the live medium installation.minimal_installation( testing=enable_testing, @@ -130,7 +133,7 @@ def perform_installation(mountpoint: Path): if archinstall.arguments.get('swap'): installation.setup_swap('zram') - if archinstall.arguments.get("bootloader") == Bootloader.Grub and archinstall.has_uefi(): + if archinstall.arguments.get("bootloader") == models.Bootloader.Grub and SysInfo.has_uefi(): installation.add_additional_packages("grub") installation.add_bootloader(archinstall.arguments["bootloader"]) @@ -140,7 +143,7 @@ def perform_installation(mountpoint: Path): network_config = archinstall.arguments.get('nic', None) if network_config: - handler = NetworkConfigurationHandler(network_config) + handler = models.NetworkConfigurationHandler(network_config) handler.config_installer( installation, archinstall.arguments.get('profile_config', None) @@ -153,16 +156,16 @@ def perform_installation(mountpoint: Path): installation.create_users(users) if audio := archinstall.arguments.get('audio', None): - log(f'Installing audio server: {audio}', level=logging.INFO) + info(f'Installing audio server: {audio}') if audio == 'pipewire': PipewireProfile().install(installation) elif audio == 'pulseaudio': installation.add_additional_packages("pulseaudio") else: - installation.log("No audio server will be installed.", level=logging.INFO) + info("No audio server will be installed.") if profile_config := archinstall.arguments.get('profile_config', None): - profile_handler.install_profile_config(installation, profile_config) + profile.profile_handler.install_profile_config(installation, profile_config) if timezone := archinstall.arguments.get('timezone', None): installation.set_timezone(timezone) @@ -194,7 +197,7 @@ def perform_installation(mountpoint: Path): installation.genfstab() - installation.log("For post-installation tips, see https://wiki.archlinux.org/index.php/Installation_guide#Post-installation", fg="yellow") + info("For post-installation tips, see https://wiki.archlinux.org/index.php/Installation_guide#Post-installation") if not archinstall.arguments.get('silent'): prompt = str(_('Would you like to chroot into the newly created installation and perform post-installation configuration?')) @@ -202,10 +205,10 @@ def perform_installation(mountpoint: Path): if choice.value == menu.Menu.yes(): try: installation.drop_to_shell() - except: + except Exception: pass - archinstall.log(f"Disk states after installing: {disk.disk_layouts()}", level=logging.DEBUG) + debug(f"Disk states after installing: {disk.disk_layouts()}") ask_user_questions() diff --git a/examples/mac_address_installation.py b/examples/mac_address_installation.py index 0a1c5160..74a123c7 100644 --- a/examples/mac_address_installation.py +++ b/examples/mac_address_installation.py @@ -1,13 +1,13 @@ import time import archinstall -from archinstall.lib.profile.profiles_handler import profile_handler +from archinstall import profile, info -for profile in profile_handler.get_mac_addr_profiles(): +for _profile in profile.profile_handler.get_mac_addr_profiles(): # Tailored means it's a match for this machine # based on it's MAC address (or some other criteria # that fits the requirements for this machine specifically). - archinstall.log(f'Found a tailored profile for this machine called: "{profile.name}"') + info(f'Found a tailored profile for this machine called: "{_profile.name}"') print('Starting install in:') for i in range(10, 0, -1): @@ -15,4 +15,4 @@ for profile in profile_handler.get_mac_addr_profiles(): time.sleep(1) install_session = archinstall.storage['installation_session'] - profile.install(install_session) + _profile.install(install_session) diff --git a/examples/minimal_installation.py b/examples/minimal_installation.py index 8bd6fd55..e31adea4 100644 --- a/examples/minimal_installation.py +++ b/examples/minimal_installation.py @@ -2,11 +2,12 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, List import archinstall -from archinstall.lib import disk -from archinstall import Installer, ProfileConfiguration, profile_handler +from archinstall import disk +from archinstall import Installer +from archinstall import profile +from archinstall import models +from archinstall import interactions from archinstall.default_profiles.minimal import MinimalProfile -from archinstall.lib.models import Bootloader, User -from archinstall.lib.user_interaction.disk_conf import select_devices, suggest_single_disk_layout if TYPE_CHECKING: _: Any @@ -26,7 +27,7 @@ def perform_installation(mountpoint: Path): # some other minor details as specified by this profile and user. if installation.minimal_installation(): installation.set_hostname('minimal-arch') - installation.add_bootloader(Bootloader.Systemd) + installation.add_bootloader(models.Bootloader.Systemd) # Optionally enable networking: if archinstall.arguments.get('network', None): @@ -34,10 +35,10 @@ def perform_installation(mountpoint: Path): installation.add_additional_packages(['nano', 'wget', 'git']) - profile_config = ProfileConfiguration(MinimalProfile()) - profile_handler.install_profile_config(installation, profile_config) + profile_config = profile.ProfileConfiguration(MinimalProfile()) + profile.profile_handler.install_profile_config(installation, profile_config) - user = User('devel', 'devel', False) + user = models.User('devel', 'devel', False) installation.create_users(user) @@ -46,8 +47,8 @@ def prompt_disk_layout(): if filesystem := archinstall.arguments.get('filesystem', None): fs_type = disk.FilesystemType(filesystem) - devices = select_devices() - modifications = suggest_single_disk_layout(devices[0], filesystem_type=fs_type) + devices = interactions.select_devices() + modifications = interactions.suggest_single_disk_layout(devices[0], filesystem_type=fs_type) archinstall.arguments['disk_config'] = disk.DiskLayoutConfiguration( config_type=disk.DiskLayoutType.Default, diff --git a/examples/only_hd_installation.py b/examples/only_hd_installation.py index 2fc74bf0..075bde20 100644 --- a/examples/only_hd_installation.py +++ b/examples/only_hd_installation.py @@ -1,9 +1,7 @@ -import logging from pathlib import Path import archinstall -from archinstall import Installer -from archinstall.lib import disk +from archinstall import Installer, disk, debug def ask_user_questions(): @@ -48,7 +46,7 @@ def perform_installation(mountpoint: Path): target.parent.mkdir(parents=True) # For support reasons, we'll log the disk layout post installation (crash or no crash) - archinstall.log(f"Disk states after installing: {disk.disk_layouts()}", level=logging.DEBUG) + debug(f"Disk states after installing: {disk.disk_layouts()}") ask_user_questions() diff --git a/pyproject.toml b/pyproject.toml index f837ebdf..8b6ae4c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ Source = "https://github.com/archlinux/archinstall" [project.optional-dependencies] dev = [ "mypy==1.1.1", + "pre-commit==3.3.1", ] doc = ["sphinx"] -- cgit v1.2.3-54-g00ecf From d65359896a60dc57eb9f18c86f692c9eced7f644 Mon Sep 17 00:00:00 2001 From: Pedro Dutra <93670432+pdutra145@users.noreply.github.com> Date: Fri, 12 May 2023 15:32:11 -0300 Subject: simple fix in README.md (#1813) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'README.md') diff --git a/README.md b/README.md index 15646170..7f48bfba 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ An example of the user configuration file can be found here and example of the credentials configuration here [credentials file](https://github.com/archlinux/archinstall/blob/master/examples/creds-sample.json). -**HINT:** The configuration files can be auto-generated by starting `archisntall`, configuring all desired menu +**HINT:** The configuration files can be auto-generated by starting `archinstall`, configuring all desired menu points and then going to `Save configuration`. To load the configuration file into `archinstall` run the following command -- cgit v1.2.3-54-g00ecf From 7af5490fef7e9d33103c0dad94a45ff118ac18d4 Mon Sep 17 00:00:00 2001 From: Hegert Taresalu <57842113+HegertTaresalu@users.noreply.github.com> Date: Mon, 5 Jun 2023 11:16:37 +0300 Subject: Add Estonian translation (#1827) * create et locale and added 56 translations * add more et translations(55-67) * add et translations * add translated_lang * update readme language list * Fixed merge conflicts --------- Co-authored-by: Hegert Taresalu Co-authored-by: Anton Hvornum --- README.md | 1 + archinstall/locales/ar/LC_MESSAGES/base.po | 12 +- archinstall/locales/de/LC_MESSAGES/base.po | 60 ++ archinstall/locales/en/LC_MESSAGES/base.mo | Bin 261 -> 259 bytes archinstall/locales/en/LC_MESSAGES/base.po | 2 +- archinstall/locales/et/LC_MESSAGES/base.mo | Bin 0 -> 35633 bytes archinstall/locales/et/LC_MESSAGES/base.po | 1141 ++++++++++++++++++++++++++++ archinstall/locales/languages.json | 2 +- archinstall/locales/pl/LC_MESSAGES/base.po | 8 + 9 files changed, 1218 insertions(+), 8 deletions(-) create mode 100644 archinstall/locales/et/LC_MESSAGES/base.mo create mode 100644 archinstall/locales/et/LC_MESSAGES/base.po (limited to 'README.md') diff --git a/README.md b/README.md index 7f48bfba..9893ddc3 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ Arabic Brazilian Portuguese Czech Dutch +Estonian French Georgian German diff --git a/archinstall/locales/ar/LC_MESSAGES/base.po b/archinstall/locales/ar/LC_MESSAGES/base.po index d6d07b06..6c37bdff 100644 --- a/archinstall/locales/ar/LC_MESSAGES/base.po +++ b/archinstall/locales/ar/LC_MESSAGES/base.po @@ -1056,6 +1056,12 @@ msgstr "" msgid "Defined" msgstr "" +msgid "Mirrors" +msgstr "" + +msgid "Mirror regions" +msgstr "" + msgid "Save user configuration (including disk layout)" msgstr "" @@ -1072,9 +1078,3 @@ msgstr "" msgid "Saving {} configuration files to {}" msgstr "" - -msgid "Mirrors" -msgstr "" - -msgid "Mirror regions" -msgstr "" diff --git a/archinstall/locales/de/LC_MESSAGES/base.po b/archinstall/locales/de/LC_MESSAGES/base.po index c1f32f0f..b20d6e90 100644 --- a/archinstall/locales/de/LC_MESSAGES/base.po +++ b/archinstall/locales/de/LC_MESSAGES/base.po @@ -1128,6 +1128,66 @@ msgstr "Spiegelserver" msgid "Mirror regions" msgstr "Spiegelserverregionen" +#, fuzzy +msgid "Add a custom mirror" +msgstr "Benutzerkonto hinzufügen" + +msgid "Change custom mirror" +msgstr "" + +msgid "Delete custom mirror" +msgstr "" + +#, fuzzy +msgid "Enter name (leave blank to skip): " +msgstr "Geben sie einen weiteren Benutzernamen an der angelegt werden soll (leer lassen um zu Überspringen): " + +#, fuzzy +msgid "Enter url (leave blank to skip): " +msgstr "Geben sie einen weiteren Benutzernamen an der angelegt werden soll (leer lassen um zu Überspringen): " + +#, fuzzy +msgid "Select signature check option" +msgstr "Laufwerke-layout auswählen" + +#, fuzzy +msgid "Select signature option" +msgstr "Laufwerke-layout auswählen" + +msgid "Custom mirrors" +msgstr "" + +msgid "Defined" +msgstr "" + +#, fuzzy +msgid "Mirrors" +msgstr "Mirror-region" + +#, fuzzy +msgid "Mirror regions" +msgstr "Mirror-region" + +#, fuzzy +msgid "Save user configuration (including disk layout)" +msgstr "Benutzerkonfiguration speichern" + +#, fuzzy +msgid "" +"Enter a directory for the configuration(s) to be saved (tab completion enabled)\n" +"Save directory: " +msgstr "Geben sie eine Ordner an wo die Konfigurationen gespeichert werden sollen: " + +msgid "" +"Do you want to save {} configuration file(s) in the following location?\n" +"\n" +"{}" +msgstr "" + +#, fuzzy +msgid "Saving {} configuration files to {}" +msgstr "Konfiguration speichern" + #~ msgid "Add :" #~ msgstr "Hinzufügen :" diff --git a/archinstall/locales/en/LC_MESSAGES/base.mo b/archinstall/locales/en/LC_MESSAGES/base.mo index e6ac80c2..7275098e 100644 Binary files a/archinstall/locales/en/LC_MESSAGES/base.mo and b/archinstall/locales/en/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/en/LC_MESSAGES/base.po b/archinstall/locales/en/LC_MESSAGES/base.po index b3e31383..f08c5ffc 100644 --- a/archinstall/locales/en/LC_MESSAGES/base.po +++ b/archinstall/locales/en/LC_MESSAGES/base.po @@ -9,7 +9,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 3.0.1\n" +"X-Generator: Poedit 3.3\n" msgid "[!] A log file has been created here: {} {}" msgstr "" diff --git a/archinstall/locales/et/LC_MESSAGES/base.mo b/archinstall/locales/et/LC_MESSAGES/base.mo new file mode 100644 index 00000000..f9c9f7ce Binary files /dev/null and b/archinstall/locales/et/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/et/LC_MESSAGES/base.po b/archinstall/locales/et/LC_MESSAGES/base.po new file mode 100644 index 00000000..8c2582f9 --- /dev/null +++ b/archinstall/locales/et/LC_MESSAGES/base.po @@ -0,0 +1,1141 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: et\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.3\n" + +msgid "[!] A log file has been created here: {} {}" +msgstr "[!] Siia on loodud logifail: {} {}" + +msgid " Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues" +msgstr " Esitage see probleem (ja fail) aadressil https://github.com/archlinux/archinstall/issues" + +msgid "Do you really want to abort?" +msgstr "Kas sa tahad katkestada?" + +msgid "And one more time for verification: " +msgstr "Ja veel kord kinnitamiseks: " + +msgid "Would you like to use swap on zram?" +msgstr "Kas soovite zramis kasutada swapi?" + +msgid "Desired hostname for the installation: " +msgstr "Installimiseks soovitud hostinimi: " + +msgid "Username for required superuser with sudo privileges: " +msgstr "Kasutajanimi nõutud superkasutajale sudo õigustega: " + +msgid "Any additional users to install (leave blank for no users): " +msgstr "Lisa kasutajate lisamine(jäta tühjaks kui ei soovi lisa kasutajaid): " + +msgid "Should this user be a superuser (sudoer)?" +msgstr "Kas see kasutaja peaks olema superkasutaja (sudo õigustega)" + +msgid "Select a timezone" +msgstr "Vali ajatsoon" + +msgid "Would you like to use GRUB as a bootloader instead of systemd-boot?" +msgstr "Kas soovite systemd-booti asemel kasutada GRUBi buudilaadijana?" + +msgid "Choose a bootloader" +msgstr "Vali buudilaadija" + +msgid "Choose an audio server" +msgstr "Vali audio server" + +msgid "Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed." +msgstr "Paigaldatakse ainult sellised paketid nagu base, base-devel, linux, linux-firmware, efibootmgr ja valikulised profiilipaketid." + +msgid "If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt." +msgstr "Kui soovite veebibrauserit, näiteks firefox või chromium, saate selle määrata järgmises viipas." + +msgid "Write additional packages to install (space separated, leave blank to skip): " +msgstr "Kirjutage paigaldatavad lisapaketid (tühikutega eraldatuna, jätke tühjaks, kui soovite vahele jätta): " + +msgid "Copy ISO network configuration to installation" +msgstr "ISO-võrgu konfiguratsiooni kopeerimine paigaldusse" + +msgid "Use NetworkManager (necessary to configure internet graphically in GNOME and KDE)" +msgstr "Kasutage NetworkManagerit (vajalik interneti graafiliseks konfigureerimiseks GNOME-s ja KDE-s)." + +msgid "Select one network interface to configure" +msgstr "Valige üks võrguliides konfigureerimiseks" + +msgid "Select which mode to configure for \"{}\" or skip to use default mode \"{}\"" +msgstr "Valige, millist režiimi soovite konfigureerida \"{}\" või jätke vahele, kasutada vaikimisi režiimi \"{}\"" + +msgid "Enter the IP and subnet for {} (example: 192.168.0.5/24): " +msgstr "Sisestage IP ja alamvõrk {} (näide: 192.168.0.5/24): " + +msgid "Enter your gateway (router) IP address or leave blank for none: " +msgstr "Sisestage oma võrguvärava (ruuteri) IP-aadress või jätke tühjaks, kui see puudub: " + +msgid "Enter your DNS servers (space separated, blank for none): " +msgstr "Sisestage oma DNS-serverid (tühikuga eraldatud, jätke tühjaks, kui neid ei ole): " + +msgid "Select which filesystem your main partition should use" +msgstr "Valige, millist failisüsteemi teie peamine partitsioon peaks kasutama" + +msgid "Current partition layout" +msgstr "Praegune partitsiooni paigutus" + +msgid "" +"Select what to do with\n" +"{}" +msgstr "" +"Valige, mida teha\n" +"{}" + +msgid "Enter a desired filesystem type for the partition" +msgstr "Sisestage partitsiooni jaoks soovitud failisüsteemi tüüp" + +msgid "Enter the start location (in parted units: s, GB, %, etc. ; default: {}): " +msgstr "Sisestage alguskoht (parted ühikutes: s, GB, % jne; vaikimisi: {}): " + +msgid "Enter the end location (in parted units: s, GB, %, etc. ; ex: {}): " +msgstr "Sisestage lõpu asukoht (parted ühikutes: s, GB, % jne; näiteks: {}): " + +msgid "{} contains queued partitions, this will remove those, are you sure?" +msgstr "{} sisaldab järjekorras olevaid partitsioone, see eemaldab need, kas olete kindel?" + +msgid "" +"{}\n" +"\n" +"Select by index which partitions to delete" +msgstr "" +"{}\n" +"\n" +"\n" +"Valige indeksi järgi, milliseid partitsioone kustutada" + +msgid "" +"{}\n" +"\n" +"Select by index which partition to mount where" +msgstr "" +"{}\n" +"\n" +"\n" +"Valige indeksi järgi, millise partitsiooni kuhu paigaldada" + +msgid " * Partition mount-points are relative to inside the installation, the boot would be /boot as an example." +msgstr " * Partitsioonide kinnituspunktid on suhtelised installeerimise sees, näiteks boot oleks /boot." + +msgid "Select where to mount partition (leave blank to remove mountpoint): " +msgstr "Valige, kuhu partitsiooni paigaldada (jätke tühjaks, et eemaldada paigalduspunkt): " + +msgid "" +"{}\n" +"\n" +"Select which partition to mask for formatting" +msgstr "" +"{}\n" +"\n" +"Valige, millist partitsiooni soovite vormindamiseks maskeerida" + +msgid "" +"{}\n" +"\n" +"Select which partition to mark as encrypted" +msgstr "" +"{}\n" +"\n" +"\n" +"Valige, millist partitsiooni soovite krüpteerituks märkida" + +msgid "" +"{}\n" +"\n" +"Select which partition to mark as bootable" +msgstr "" +"{}\n" +"\n" +"\n" +"Valige, millist partitsiooni soovite märkida käivitatavaks" + +msgid "" +"{}\n" +"\n" +"Select which partition to set a filesystem on" +msgstr "" +"{}\n" +"\n" +"\n" +"Valige, millisele partitsioonile failisüsteemi määrata" + +msgid "Enter a desired filesystem type for the partition: " +msgstr "Sisestage partitsiooni jaoks soovitud failisüsteemi tüüp: " + +msgid "Archinstall language" +msgstr "Archinstalli keel" + +msgid "Wipe all selected drives and use a best-effort default partition layout" +msgstr "Pühkige kõik valitud kettad ja kasutage parimat võimalikku vaikimisi partitsiooni paigutust" + +msgid "Select what to do with each individual drive (followed by partition usage)" +msgstr "Valige, mida teha iga üksiku kettaga (järgneb partitsiooni kasutamine)" + +msgid "Select what you wish to do with the selected block devices" +msgstr "Valige, mida soovite valitud plokkseadmetega teha" + +msgid "This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments" +msgstr "See on nimekiri eelprogrammeeritud profiilidest, need võivad lihtsustada selliste asjade nagu töölauakeskkondade paigaldamist" + +msgid "Select keyboard layout" +msgstr "Valige klaviatuuri paigutus" + +msgid "Select one of the regions to download packages from" +msgstr "Valige üks piirkondadest, kust pakette alla laadida" + +msgid "Select one or more hard drives to use and configure" +msgstr "Valige üks või mitu kõvaketast kasutamiseks ja konfigureerimiseks" + +msgid "For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options." +msgstr "Parima ühilduvuse saavutamiseks oma AMD riistvaraga võiksite kasutada kas kõiki avatud lähtekoodiga või AMD/ATI valikuid." + +msgid "For the best compatibility with your Intel hardware, you may want to use either the all open-source or Intel options.\n" +msgstr "" +"Parima ühilduvuse saavutamiseks oma Inteli riistvaraga võiksite kasutada kas kõiki avatud lähtekoodiga või Inteli valikuid.\n" +"\n" + +msgid "For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n" +msgstr "" +"Parima ühilduvuse saavutamiseks oma Nvidia riistvaraga peaksite kasutama Nvidia enda toodetud draiverit.\n" +"\n" +"\n" + +msgid "" +"\n" +"\n" +"Select a graphics driver or leave blank to install all open-source drivers" +msgstr "" +"\n" +"\n" +"Valige graafikadraiver või jätke tühjaks, et paigaldada kõik avatud lähtekoodiga draiverid" + +msgid "All open-source (default)" +msgstr "Kõik avatud lähtekoodiga (vaikimisi)" + +msgid "Choose which kernels to use or leave blank for default \"{}\"" +msgstr "Valige, milliseid tuumasid soovite kasutada või jätke vaikimisi tühjaks \"{}\"" + +msgid "Choose which locale language to use" +msgstr "Valige, millist asukoha keelt kasutada" + +msgid "Choose which locale encoding to use" +msgstr "Valige, millist asukoha kodeeringut kasutada" + +msgid "Select one of the values shown below: " +msgstr "Valige üks allpool esitatud väärtustest: " + +msgid "Select one or more of the options below: " +msgstr "Valige üks või mitu järgmistest võimalustest: " + +msgid "Adding partition...." +msgstr "Partitsiooni lisamine...." + +msgid "You need to enter a valid fs-type in order to continue. See `man parted` for valid fs-type's." +msgstr "Partitsiooni lisamine...." + +msgid "Error: Listing profiles on URL \"{}\" resulted in:" +msgstr "Viga: URL-i \"{}\" profiilide loetlemine andis tulemuseks:" + +msgid "Error: Could not decode \"{}\" result as JSON:" +msgstr "Viga: {}\" tulemust JSON-ina dekodeerida:" + +msgid "Keyboard layout" +msgstr "Klaviatuuri paigutus" + +msgid "Mirror region" +msgstr "Peegelregioon" + +msgid "Locale language" +msgstr "Kohalik keel" + +msgid "Locale encoding" +msgstr "Kohaliku keele kodeering" + +msgid "Drive(s)" +msgstr "Draiv(id)" + +msgid "Disk layout" +msgstr "Ketta paigutus" + +msgid "Encryption password" +msgstr "Krüpteerimise parool" + +msgid "Swap" +msgstr "Swap" + +msgid "Bootloader" +msgstr "Buudilaadija" + +msgid "Root password" +msgstr "Juur parool" + +msgid "Superuser account" +msgstr "Superkasutaja konto" + +msgid "User account" +msgstr "Kasutaja konto" + +msgid "Profile" +msgstr "Profiil" + +msgid "Audio" +msgstr "Audio" + +msgid "Kernels" +msgstr "Kernelid" + +msgid "Additional packages" +msgstr "Täiendavad paketid" + +msgid "Network configuration" +msgstr "Võrgu konfiguratsioon" + +msgid "Automatic time sync (NTP)" +msgstr "Automaatne ajasünkroonimine" + +msgid "Install ({} config(s) missing)" +msgstr "Install ({} config(id) puudu(vad)" + +msgid "" +"You decided to skip harddrive selection\n" +"and 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?" +msgstr "" +"Sa otsustasid kõvaketta valiku vahele jätta\n" +"ja kasutad mis tahes draivi seadistust, mis on paigaldatud {} (eksperimentaalne)\n" +"HOIATUS: Archinstall ei kontrolli selle seadistuse sobivust\n" +"Kas soovite jätkata?" + +msgid "Re-using partition instance: {}" +msgstr "Partitsiooni instantsi taaskasutamine: {}" + +msgid "Create a new partition" +msgstr "Uue partitsiooni loomine" + +msgid "Delete a partition" +msgstr "Kustuta paritsioon" + +msgid "Clear/Delete all partitions" +msgstr "Puhasta/Kustuta kõik partitsioonid" + +msgid "Assign mount-point for a partition" +msgstr "Määrake partitsioonile kinnituspunkt" + +msgid "Mark/Unmark a partition to be formatted (wipes data)" +msgstr "Märgistage/mittemärgistage vormindatav partitsioon (kustutab andmed)" + +msgid "Mark/Unmark a partition as encrypted" +msgstr "Märgistage/mittemärgistage partitsiooni krüpteerituks" + +msgid "Mark/Unmark a partition as bootable (automatic for /boot)" +msgstr "Märgistage/mittemärgistage partitsiooni käivitatavaks (automaatne /boot jaoks)" + +msgid "Set desired filesystem for a partition" +msgstr "Soovitud failisüsteemi määramine partitsiooni jaoks" + +msgid "Abort" +msgstr "Katkesta" + +msgid "Hostname" +msgstr "Hostinimi" + +msgid "Not configured, unavailable unless setup manually" +msgstr "Ei ole konfigureeritud, ei ole saadaval, kui seda ei ole käsitsi seadistatud" + +msgid "Timezone" +msgstr "Ajatsoon" + +msgid "Set/Modify the below options" +msgstr "Määrake/muutke järgmisi valikuid" + +msgid "Install" +msgstr "Install" + +msgid "" +"Use ESC to skip\n" +"\n" +msgstr "" +"Kasutage ESC vahelejätmiseks\n" +"\n" +"\n" +"\n" +"\n" + +msgid "Suggest partition layout" +msgstr "Soovitage partitsiooni paigutust" + +msgid "Enter a password: " +msgstr "Sisestage parool: " + +msgid "Enter a encryption password for {}" +msgstr "Sisestage {}'le krüpteerimis parool" + +msgid "Enter disk encryption password (leave blank for no encryption): " +msgstr "Sisestage ketta krüpteerimise parool (krüpteerimise puudumisel jätke see tühjaks): " + +msgid "Create a required super-user with sudo privileges: " +msgstr "Looge nõutav sudo õigustega superkasutaja: " + +msgid "Enter root password (leave blank to disable root): " +msgstr "Sisestage juur parool (jätke tühjaks, et keelata root): " + +msgid "Password for user \"{}\": " +msgstr "Kasutaja \"{}\" parool: " + +msgid "Verifying that additional packages exist (this might take a few seconds)" +msgstr "Täiendavate pakettide olemasolu kontrollimine (see võib võtta paar sekundit)" + +msgid "Would you like to use automatic time synchronization (NTP) with the default time servers?\n" +msgstr "Kas soovite kasutada automaatset ajasünkroniseerimist (NTP) vaikimisi ajaserveritega?\n" + +msgid "" +"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 toimimiseks võib olla vaja riistvara aega ja muid konfiguratsioonijärgseid samme.\n" +"Lisateavet leiate Archi wikist" + +msgid "Enter a username to create an additional user (leave blank to skip): " +msgstr "Sisestage kasutajanimi, et luua lisakasutaja (jätke tühjaks, et vahele jätta): " + +msgid "Use ESC to skip\n" +msgstr "Kasutage ESC'i et vahele jätta\n" + +msgid "" +"\n" +" Choose an object from the list, and select one of the available actions for it to execute" +msgstr "" +"\n" +" Valige loetelust objekt ja valige selle täitmiseks üks olemasolevatest toimingutest" + +msgid "Cancel" +msgstr "Tühista" + +msgid "Confirm and exit" +msgstr "Kinnita ja lahku" + +msgid "Add" +msgstr "Lisa" + +msgid "Copy" +msgstr "Kopeeri" + +msgid "Edit" +msgstr "Muuda" + +msgid "Delete" +msgstr "Kustuta" + +msgid "Select an action for '{}'" +msgstr "Valige tegevus '{}' jaoks" + +msgid "Copy to new key:" +msgstr "Kopeeri uude võtmesse:" + +msgid "Unknown nic type: {}. Possible values are {}" +msgstr "Tundmatu nic-tüüp: {}. Võimalikud väärtused on {}" + +msgid "" +"\n" +"This is your chosen configuration:" +msgstr "" +"\n" +"See on teie valitud konfiguratsioon:" + +msgid "Pacman is already running, waiting maximum 10 minutes for it to terminate." +msgstr "Pacman on juba käivitatud ja ootab maksimaalselt 10 minutit, kuni see lõpeb." + +msgid "Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall." +msgstr "Eelnev pacmani lukk ei ole kunagi väljunud. Enne archinstall'i kasutamist puhastage olemasolevad pacmani sessioonid." + +msgid "Choose which optional additional repositories to enable" +msgstr "Valige, milliseid valikulisi lisa repositooriumeid soovite lubada" + +msgid "Add a user" +msgstr "Lisa kasutaja" + +msgid "Change password" +msgstr "Muuda parool" + +msgid "Promote/Demote user" +msgstr "Edenda/alanda kasutajat" + +msgid "Delete User" +msgstr "Kustuta kasutaja" + +msgid "" +"\n" +"Define a new user\n" +msgstr "" +"\n" +"Uue kasutaja määramine\n" + +msgid "User Name : " +msgstr "Kasutajanimi : " + +msgid "Should {} be a superuser (sudoer)?" +msgstr "Kas {} peaks olema superkasutaja (sudoer)?" + +msgid "Define users with sudo privilege: " +msgstr "Määrake sudo privileegiga kasutajad: " + +msgid "No network configuration" +msgstr "Puudub võrgu konfiguratsioon" + +msgid "Set desired subvolumes on a btrfs partition" +msgstr "Määrake soovitud alamköited btrfs-i partitsioonile" + +msgid "" +"{}\n" +"\n" +"Select which partition to set subvolumes on" +msgstr "" +"{}\n" +"\n" +"Valige, millisele partitsioonile alamkogumid määrata" + +msgid "Manage btrfs subvolumes for current partition" +msgstr "Hallake praeguse partitsiooni btrfs-i alamköiteid" + +msgid "No configuration" +msgstr "Puudub konfiguratsioon" + +msgid "Save user configuration" +msgstr "Salvesta kasutaja konfiguratsioon" + +msgid "Save user credentials" +msgstr "Salvesta kasutaja volitused" + +msgid "Save disk layout" +msgstr "Salvesta ketta paigutus" + +msgid "Save all" +msgstr "Salvesta kõik" + +msgid "Choose which configuration to save" +msgstr "Valige milline konfiguratsioon salvestada" + +msgid "Enter a directory for the configuration(s) to be saved: " +msgstr "Sisestage salvestatava(te) konfiguratsiooni(de) kataloog: " + +msgid "Not a valid directory: {}" +msgstr "Ei ole sobiv kataloog: {}" + +msgid "The password you are using seems to be weak," +msgstr "Teie kasutatav salasõna näib olevat nõrk," + +msgid "are you sure you want to use it?" +msgstr "oled sa kindel et soovid seda kasutada?" + +msgid "Optional repositories" +msgstr "Valikulised repositooriumid" + +msgid "Save configuration" +msgstr "Salvesta konfiguratsioon" + +msgid "Missing configurations:\n" +msgstr "Puuduvad konfiguratsioonid:\n" + +msgid "Either root-password or at least 1 superuser must be specified" +msgstr "Tuleb määrata kas juur-parool või vähemalt 1 superkasutaja" + +msgid "Manage superuser accounts: " +msgstr "Hallake superkasutaja kontosi " + +msgid "Manage ordinary user accounts: " +msgstr "Hallake tavalisi kasutajaid: " + +msgid " Subvolume :{:16}" +msgstr " Alamköide :{:16}" + +msgid " mounted at {:16}" +msgstr " paigaldatud {:16}" + +msgid " with option {}" +msgstr " koos valikuga {}" + +msgid "" +"\n" +" Fill the desired values for a new subvolume \n" +msgstr "" +"\n" +" Täida uue alamhulga soovitud väärtused \n" + +msgid "Subvolume name " +msgstr "Alamhulga nimi " + +msgid "Subvolume mountpoint" +msgstr "Alamhulga kinnituspunkt" + +msgid "Subvolume options" +msgstr "Alamhulga valikud" + +msgid "Save" +msgstr "Salvesta" + +msgid "Subvolume name :" +msgstr "Alamhulga nimi:" + +msgid "Select a mount point :" +msgstr "Valige kinnitus punkt :" + +msgid "Select the desired subvolume options " +msgstr "Valige soovitud alamhulga valikud " + +msgid "Define users with sudo privilege, by username: " +msgstr "Määrake sudo õigustega kasutajad kasutajanime järgi: " + +msgid "[!] A log file has been created here: {}" +msgstr "[!] Siin on loodud logifail: {}" + +msgid "Would you like to use BTRFS subvolumes with a default structure?" +msgstr "Kas soovite kasutada BTRFS-i alamkogusid vaikimisi struktuuriga?" + +msgid "Would you like to use BTRFS compression?" +msgstr "Kas soovite kasutada BTRFS-i tihendamist?" + +msgid "Would you like to create a separate partition for /home?" +msgstr "Kas soovite luua eraldi partitsiooni /home jaoks?" + +msgid "The selected drives do not have the minimum capacity required for an automatic suggestion\n" +msgstr "Valitud ketastel ei ole automaatseks soovituseks vajalikku minimaalset mahtu\n" + +msgid "Minimum capacity for /home partition: {}GB\n" +msgstr "Minimaalne mahutavus /home partitsioonile: {}GB\n" + +msgid "Minimum capacity for Arch Linux partition: {}GB" +msgstr "Arch Linuxi partitsiooni minimaalne mahutavus: {}GB" + +msgid "Continue" +msgstr "Jätka" + +msgid "yes" +msgstr "jah" + +msgid "no" +msgstr "ei" + +msgid "set: {}" +msgstr "seadista: {}" + +msgid "Manual configuration setting must be a list" +msgstr "Käsitsi konfiguratsiooniseade peab olema loend" + +msgid "No iface specified for manual configuration" +msgstr "Käsitsi seadistamiseks pole iface'i määratud" + +msgid "Manual nic configuration with no auto DHCP requires an IP address" +msgstr "Manuaalne nici konfigureerimine ilma automaatse DHCPta nõuab IP-aadressi" + +msgid "Add interface" +msgstr "Lisa liides" + +msgid "Edit interface" +msgstr "Muuda liidest" + +msgid "Delete interface" +msgstr "Kustutage liides" + +msgid "Select interface to add" +msgstr "Valige millist liidest lisada" + +msgid "Manual configuration" +msgstr "Manuaalne konfiguratsioon" + +msgid "Mark/Unmark a partition as compressed (btrfs only)" +msgstr "Märgistada/mittemärgistada partitsiooni kompressiooniks (ainult btrfs)" + +msgid "The password you are using seems to be weak, are you sure you want to use it?" +msgstr "Teie kasutatav salasõna tundub olevat nõrk, kas olete kindel, et soovite seda kasutada?" + +msgid "Provides a selection of desktop environments and tiling window managers, e.g. gnome, kde, sway" +msgstr "Pakub valikut töölauakeskkondi ja plaadistatavaid aknahaldureid, nt gnome, kde, sway" + +msgid "Select your desired desktop environment" +msgstr "Valige soovitud töölauakeskkonda" + +msgid "A very basic installation that allows you to customize Arch Linux as you see fit." +msgstr "Väga lihtne paigaldus, mis võimaldab teil Arch Linuxi kohandada vastavalt oma äranägemisele." + +msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" +msgstr "Annab valiku erinevate serveripakettide paigaldamiseks ja aktiveerimiseks, nt httpd, nginx, mariadb" + +msgid "Choose which servers to install, if none then a minimal installation will be done" +msgstr "Valige, milliseid servereid paigaldada, kui ühtegi ei ole, siis tehakse minimaalne paigaldus" + +msgid "Installs a minimal system as well as xorg and graphics drivers." +msgstr "Installeerib minimaalse süsteemi ning xorgi ja graafikadraiverid." + +msgid "Press Enter to continue." +msgstr "Jätkamiseks vajutage Enter." + +msgid "Would you like to chroot into the newly created installation and perform post-installation configuration?" +msgstr "Kas soovite chrootida äsja loodud installatsiooni ja teostada installeerimisjärgset konfigureerimist?" + +msgid "Are you sure you want to reset this setting?" +msgstr "Kas olete kindel, et soovite seda seadistust lähtestada?" + +msgid "Select one or more hard drives to use and configure\n" +msgstr "Valige üks või mitu kasutatavat kõvaketast ja konfigureerige\n" + +msgid "Any modifications to the existing setting will reset the disk layout!" +msgstr "Olemasoleva seadistuse muutmine lähtestab ketta paigutuse!" + +msgid "If you reset the harddrive selection this will also reset the current disk layout. Are you sure?" +msgstr "Kui lähtestate kõvaketta valiku, lähtestab see ka praeguse kettapaigutuse. Oled sa kindel?" + +msgid "Save and exit" +msgstr "Salvesta ja lahku" + +msgid "" +"{}\n" +"contains queued partitions, this will remove those, are you sure?" +msgstr "" +"{}\n" +"sisaldab järjekorras olevaid partitsioone, see eemaldab need, oled sa kindel?" + +msgid "No audio server" +msgstr "Puudub audio server" + +msgid "(default)" +msgstr "(vaikimisi)" + +msgid "Use ESC to skip" +msgstr "Kasutage ESC vahelejätmiseks" + +msgid "" +"Use CTRL+C to reset current selection\n" +"\n" +msgstr "" +"Kasuta CTRL+C praeguse valiku lähtestamiseks\n" +"\n" + +msgid "Copy to: " +msgstr "Kopeeri: " + +msgid "Edit: " +msgstr "Muuda: " + +msgid "Key: " +msgstr "Võti: " + +msgid "Edit {}: " +msgstr "Muuda {}: " + +msgid "Add: " +msgstr "Lisa: " + +msgid "Value: " +msgstr "Väärtus: " + +msgid "You can skip selecting a drive and partitioning and use whatever drive-setup is mounted at /mnt (experimental)" +msgstr "Saad vahele jätta ketta valimise ja partitsioneerimise ning kasutada mis tahes draivi-komplekti, mis on paigaldatud /mnt (eksperimentaalne)." + +msgid "Select one of the disks or skip and use /mnt as default" +msgstr "Valige üks ketastest või jätke vahele ja kasutage vaikimisi /mnt" + +msgid "Select which partitions to mark for formatting:" +msgstr "Valige, milliseid partitsioone soovite vormindamiseks märkida:" + +msgid "Use HSM to unlock encrypted drive" +msgstr "Kasutage HSM-i krüpteeritud draivi avamiseks" + +msgid "Device" +msgstr "Seade" + +msgid "Size" +msgstr "Suurus" + +msgid "Free space" +msgstr "Vaba mälu" + +msgid "Bus-type" +msgstr "Bussi tüüp" + +msgid "Either root-password or at least 1 user with sudo privileges must be specified" +msgstr "Tuleb määrata kas root-sõna või vähemalt 1 kasutaja, kellel on sudo õigused" + +msgid "Enter username (leave blank to skip): " +msgstr "Sisestage kasutajanimi (jätke tühjaks, et vahele jätta): " + +msgid "The username you entered is invalid. Try again" +msgstr "Teie sisestatud kasutajanimi ei sobi. Proovige uuesti" + +msgid "Should \"{}\" be a superuser (sudo)?" +msgstr "Kas \"{}\" peaks olema superkasutaja (sudo)?" + +msgid "Select which partitions to encrypt" +msgstr "Valige, milliseid partitsioone krüpteerida" + +msgid "very weak" +msgstr "väga nõrk" + +msgid "weak" +msgstr "nõrk" + +msgid "moderate" +msgstr "keskmine" + +msgid "strong" +msgstr "tugev" + +msgid "Add subvolume" +msgstr "Lisage alamhulk" + +msgid "Edit subvolume" +msgstr "Osahulga redigeerimine" + +msgid "Delete subvolume" +msgstr "Kustuta osahulk" + +msgid "Configured {} interfaces" +msgstr "Konfigureeritud {} liidesed" + +msgid "This option enables the number of parallel downloads that can occur during installation" +msgstr "See valik võimaldab installimise ajal valida paralleelsete allalaadimiste arvu" + +#, 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 "" +"Sisestage lubatavate paralleelsete allalaadimiste arv.\n" +" (Sisestage väärtus vahemikus 1 kuni {max_downloads})\n" +"note:" + +msgid " - Maximum value : {max_downloads} ( Allows {max_downloads} parallel downloads, allows {max_downloads+1} downloads at a time )" +msgstr " - Maksimaalne väärtus : {max_downloads} ( Võimaldab {max_downloads} paralleelset allalaadimist, lubab {max_downloads+1} allalaadimist korraga )" + +msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" +msgstr " - Minimaalne väärtus : 1 ( Võimaldab 1 paralleelset allalaadimist, võimaldab 2 allalaadimist korraga )" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" +msgstr " - Keela/Vaikimisi : 0 ( keelab paralleelse allalaadimise, võimaldab ainult 1 allalaadimist korraga )" + +#, python-brace-format +msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]" +msgstr "Vale sisestus! Proovige uuesti kehtiva sisendiga [1 {max_downloads} või 0 keelamiseks]." + +msgid "Parallel Downloads" +msgstr "Paralleelsed allalaadimised" + +msgid "ESC to skip" +msgstr "ESC vahelejätmiseks" + +msgid "CTRL+C to reset" +msgstr "CTRL+C lähtestamiseks" + +msgid "TAB to select" +msgstr "TAB valimiseks" + +msgid "[Default value: 0] > " +msgstr "[Vaikimisi väärtus: 0] > " + +msgid "To be able to use this translation, please install a font manually that supports the language." +msgstr "Selleks, et seda tõlget kasutada, paigaldage käsitsi fondi, mis toetab seda keelt." + +msgid "The font should be stored as {}" +msgstr "Font tuleks salvestada kujul {}" + +msgid "Archinstall requires root privileges to run. See --help for more." +msgstr "Archinstall vajab käivitamiseks juurkasutaja õigusi. Vaata --help." + +msgid "Select an execution mode" +msgstr "Valige täitmisrežiim" + +msgid "Unable to fetch profile from specified url: {}" +msgstr "Ei õnnestu profiili hankida määratud url'ist: {}" + +msgid "Profiles must have unique name, but profile definitions with duplicate name found: {}" +msgstr "Profiilidel peab olema unikaalne nimi, kuid leiti topeltnimega profiilide määratlusi: {}" + +msgid "Select one or more devices to use and configure" +msgstr "Valige üks või mitu seadet mida kasutada ja konfigureerida" + +msgid "If you reset the device selection this will also reset the current disk layout. Are you sure?" +msgstr "Kui lähtestate seadme valiku, siis lähtestate sellega ka praeguse draivi paigutuse. Kas olete kindel?" + +msgid "Existing Partitions" +msgstr "Olemasolevad Jaotused" + +msgid "Select a partitioning option" +msgstr "Valige jaotuste valik" + +msgid "Enter the root directory of the mounted devices: " +msgstr "Sisestage paigaldatud seadmete juurkataloog: " + +msgid "Minimum capacity for /home partition: {}GiB\n" +msgstr "Minimaalne mahutavus /home partitsioonile: {}GiB\n" + +msgid "Minimum capacity for Arch Linux partition: {}GiB" +msgstr "Arch Linuxi partitsiooni minimaalne mahutavus: {}GiB" + +msgid "This is a list of pre-programmed profiles_bck, they might make it easier to install things like desktop environments" +msgstr "See on nimekiri eelprogrammeeritud profiilidest_bck, need võivad lihtsustada selliste asjade nagu töölauakeskkondade paigaldamist" + +msgid "Current profile selection" +msgstr "Praegune profiilivalik" + +msgid "Remove all newly added partitions" +msgstr "Eemaldage kõik äsja lisatud partitsioonid" + +msgid "Assign mountpoint" +msgstr "Määra paigaldamis punkt" + +msgid "Mark/Unmark to be formatted (wipes data)" +msgstr "Märgistada/mittemärgistada vormindamiseks (kustutab andmed)" + +msgid "Mark/Unmark as bootable" +msgstr "Märgistada/mittemärgistada käivitatavak" + +msgid "Change filesystem" +msgstr "Failisüsteemi muutmine" + +msgid "Mark/Unmark as compressed" +msgstr "Märgistada/mittemärgistada kui tihendatud" + +msgid "Set subvolumes" +msgstr "Alamköite määramine" + +msgid "Delete partition" +msgstr "Kustuta partitsioon" + +msgid "Partition" +msgstr "Partitsioon" + +msgid "This partition is currently encrypted, to format it a filesystem has to be specified" +msgstr "See partitsioon on praegu krüpteeritud, selle vormindamiseks tuleb määrata failisüsteem" + +msgid "Partition mount-points are relative to inside the installation, the boot would be /boot as an example." +msgstr "Partitsiooni kinnituspunktid on suhtelised installeerimise sees, näiteks boot oleks /boot." + +msgid "If mountpoint /boot is set, then the partition will also be marked as bootable." +msgstr "Kui paigalduspunkt /boot on määratud, siis märgitakse partitsioon ka käivitatavaks." + +msgid "Mountpoint: " +msgstr "Paigalduspunkt: " + +msgid "Current free sectors on device {}:" +msgstr "Praegused vabad sektorid seadmes {}:" + +msgid "Total sectors: {}" +msgstr "Sektorid kokku: {}" + +msgid "Enter the start sector (default: {}): " +msgstr "Sisestage algussektor (vaikimisi: {}): " + +msgid "Enter the end sector of the partition (percentage or block number, default: {}): " +msgstr "Sisestage partitsiooni lõppsektor (protsent või plokkide number, vaikimisi: {}): " + +msgid "This will remove all newly added partitions, continue?" +msgstr "See eemaldab kõik äsja lisatud partitsioonid, jätka?" + +msgid "Partition management: {}" +msgstr "Partitsiooni haldamine:{}" + +msgid "Total length: {}" +msgstr "Kogupikkus: {}" + +msgid "Encryption type" +msgstr "Krüpteerimise tüüp" + +msgid "Partitions" +msgstr "Partitsioonid" + +msgid "No HSM devices available" +msgstr "Puuduvad HSM seadmed" + +msgid "Partitions to be encrypted" +msgstr "Krüpteeritavad partitsioonid" + +msgid "Select disk encryption option" +msgstr "Valige ketta krüpteerimise valik" + +msgid "Select a FIDO2 device to use for HSM" +msgstr "Valige HSM-i jaoks kasutatav FIDO2-seade" + +msgid "Use a best-effort default partition layout" +msgstr "Kasutage parimat võimalikku vaikimisi partitsiooni paigutust" + +msgid "Manual Partitioning" +msgstr "Käsitsi partitsioneerimine" + +msgid "Pre-mounted configuration" +msgstr "Eelnevalt paigaldatud konfiguratsioon" + +msgid "Unknown" +msgstr "Tundmatu" + +msgid "Partition encryption" +msgstr "Partitsiooni krüpteerimine" + +msgid " ! Formatting {} in " +msgstr " ! Vormindamine {} " + +msgid "← Back" +msgstr "Tagasi" + +msgid "Disk encryption" +msgstr "Ketta krüpteerimine" + +msgid "Configuration" +msgstr "Konfiguratsioon" + +msgid "Password" +msgstr "Parool" + +msgid "All settings will be reset, are you sure?" +msgstr "Kõik seaded lähtestatakse, kas sa oled kindel?" + +msgid "Back" +msgstr "Tagasi" + +msgid "Please chose which greeter to install for the chosen profiles: {}" +msgstr "Palun valige millist tervitajat installida valitud profiilidele" + +msgid "Environment type: {}" +msgstr "Keskkonna tüüp: {}" + +msgid "The proprietary Nvidia driver is not supported by Sway. It is likely that you will run into issues, are you okay with that?" +msgstr "Sway ei toeta Nvidia enda draiverit. Tõenäoliselt tekib teil probleeme, kas teile sobib see?" + +msgid "Installed packages" +msgstr "Installeeritud paketid" + +msgid "Add profile" +msgstr "Lisa profiil" + +msgid "Edit profile" +msgstr "Muuda profiili" + +msgid "Delete profile" +msgstr "Kustuta profiil" + +msgid "Profile name: " +msgstr "Profiili nimi: " + +msgid "The profile name you entered is already in use. Try again" +msgstr "Sisestatud profiili nimi on juba kasutuses. Proovi uuesti" + +msgid "Packages to be install with this profile (space separated, leave blank to skip): " +msgstr "Selle profiiliga paigaldatavad paketid (tühikuga eraldatud, jäta tühjaks, et jätta vahele): " + +msgid "Services to be enabled with this profile (space separated, leave blank to skip): " +msgstr "Selle profiiliga lubatavad teenused (tühikuga eraldatud, jäta tühjaks, kui soovid vahele jätta): " + +msgid "Should this profile be enabled for installation?" +msgstr "Kas see profiil peaks olema paigaldamiseks lubatud?" + +msgid "Create your own" +msgstr "Loo oma" + +msgid "" +"\n" +"Select a graphics driver or leave blank to install all open-source drivers" +msgstr "" +"\n" +"Vali graafikadraiver või jäta tühjaks, et paigaldada kõik avatud lähtekoodiga draiverid" + +msgid "Sway needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" +msgstr "Sway vajab juurdepääsu teie seatile (riistvaraseadmete kogum, st klaviatuur, hiir jne." + +msgid "" +"\n" +"\n" +"Choose an option to give Sway access to your hardware" +msgstr "" +"\n" +"\n" +"Valige valik, et anda Sway'le juurdepääs teie riistvarale" + +msgid "Graphics driver" +msgstr "Graafika draiver" + +msgid "Greeter" +msgstr "Tervitaja" + +msgid "Please chose which greeter to install" +msgstr "Palun valige millist tervitajat installida" + +msgid "This is a list of pre-programmed default_profiles" +msgstr "See on eelnevalt programmeeritud vaikimisi_profiilide nimekiri" + +msgid "Disk configuration" +msgstr "Ketta konfiguratsioon" + +msgid "Profiles" +msgstr "Profiilid" + +msgid "Finding possible directories to save configuration files ..." +msgstr "Võimalike kataloogide leidmine konfiguratsioonifailide salvestamiseks ..." + +msgid "Select directory (or directories) for saving configuration files" +msgstr "Konfigureerimisfailide salvestamise kataloogi (või kataloogide) valimine" + +msgid "Add a custom mirror" +msgstr "Lisa kohandatud peegel" + +msgid "Change custom mirror" +msgstr "Muuda kohandatud peeglit" + +msgid "Delete custom mirror" +msgstr "Kustuta kohandadtud peeglit" + +msgid "Enter name (leave blank to skip): " +msgstr "Sisesta nimi (jätke tühjaks, et vahele jätta): " + +msgid "Enter url (leave blank to skip): " +msgstr "Sisesta url (jätke tühjaks, et vahele jätta): " + +msgid "Select signature check option" +msgstr "Valige allkirja kontrollimise võimalus" + +msgid "Select signature option" +msgstr "Valige allkirja valik" + +msgid "Custom mirrors" +msgstr "Kohandatud peegel" + +msgid "Defined" +msgstr "Defineeritud" + +msgid "Mirrors" +msgstr "Peeglid" + +msgid "Mirror regions" +msgstr "Peegel regioonid" + +msgid "Save user configuration (including disk layout)" +msgstr "Salvesta kasutaja konfiguratsioon (kaasa arvatud plaadi paigutus)" + +msgid "" +"Enter a directory for the configuration(s) to be saved (tab completion enabled)\n" +"Save directory: " +msgstr "" +"Sisestage salvestatava(te) konfiguratsiooni(de) kataloog (vahekaartide täitmine lubatud)\n" +"Salvesta kataloog: " + +msgid "" +"Do you want to save {} configuration file(s) in the following location?\n" +"\n" +"{}" +msgstr "" +"Kas soovite salvestada {} konfiguratsioonifaili(d) järgmisesse asukohta?\n" +"\n" +"{}" + +msgid "Saving {} configuration files to {}" +msgstr "{} konfiguratsioonifailide salvestamine {}" diff --git a/archinstall/locales/languages.json b/archinstall/locales/languages.json index 954d5a26..271fbb36 100644 --- a/archinstall/locales/languages.json +++ b/archinstall/locales/languages.json @@ -36,7 +36,7 @@ {"abbr": "el", "lang": "Modern Greek (1453-)", "translated_lang": "Ελληνικά"}, {"abbr": "en", "lang": "English"}, {"abbr": "eo", "lang": "Esperanto"}, - {"abbr": "et", "lang": "Estonian"}, + {"abbr": "et", "lang": "Estonian", "translated_lang": "Eesti" }, {"abbr": "eu", "lang": "Basque"}, {"abbr": "ee", "lang": "Ewe"}, {"abbr": "fo", "lang": "Faroese"}, diff --git a/archinstall/locales/pl/LC_MESSAGES/base.po b/archinstall/locales/pl/LC_MESSAGES/base.po index 50348715..8c3ce4f5 100644 --- a/archinstall/locales/pl/LC_MESSAGES/base.po +++ b/archinstall/locales/pl/LC_MESSAGES/base.po @@ -1145,6 +1145,14 @@ msgstr "" msgid "Defined" msgstr "" +#, fuzzy +msgid "Mirrors" +msgstr "Region lustra" + +#, fuzzy +msgid "Mirror regions" +msgstr "Region lustra" + #, fuzzy msgid "Save user configuration (including disk layout)" msgstr "Zapisz konfiguracje użytkownika" -- cgit v1.2.3-54-g00ecf From 03d228fee8f850bb58249d23fd7c17f742a7701b Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Wed, 21 Jun 2023 17:45:17 +1000 Subject: Update contributing doc (#1873) Co-authored-by: Daniel Girtler --- CONTRIBUTING.md | 13 +++++++++++-- README.md | 5 +++++ 2 files changed, 16 insertions(+), 2 deletions(-) (limited to 'README.md') diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5ed5142d..3faa8ae8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,8 +27,7 @@ The exceptions to PEP8 are: * Archinstall uses [tabs instead of spaces](https://www.python.org/dev/peps/pep-0008/#tabs-or-spaces) simply to make it easier for non-IDE developers to navigate the code *(Tab display-width should be equal to 4 spaces)*. Exception to the rule are comments that need fine-tuned indentation for documentation purposes. -* [Line length](https://www.python.org/dev/peps/pep-0008/#maximum-line-length) should aim for no more than 100 - characters, but not strictly enforced. +* [Line length](https://www.python.org/dev/peps/pep-0008/#maximum-line-length) a maximum line length is enforced via flake8 with 236 characters * [Line breaks before/after binary operator](https://www.python.org/dev/peps/pep-0008/#should-a-line-break-before-or-after-a-binary-operator) is not enforced, as long as the style of line breaks is consistent within the same code block. * Archinstall should always be saved with **Unix-formatted line endings** and no other platform-specific formats. @@ -39,6 +38,16 @@ The exceptions to PEP8 are: Most of these style guidelines have been put into place after the fact *(in an attempt to clean up the code)*.
There might therefore be older code which does not follow the coding convention and the code is subject to change. +## Git hooks + +`archinstall` ships pre-commit hooks that make it easier to run check such as `mypy` and `flake8` locally. +The checks are listed in `.pre-commit-config.yaml` and can be installed via +``` +pre-commit install +``` + +This will install the pre-commit hook and run it every time a `git commit` is executed. + ## Documentation If you'd like to contribute to the documentation, refer to [this guide](docs/README.md) on how to build the documentation locally. diff --git a/README.md b/README.md index 9893ddc3..15afdda4 100644 --- a/README.md +++ b/README.md @@ -229,3 +229,8 @@ This will create a *20 GB* `testimage.img` and create a loop device which we can There's also a [Building and Testing](https://github.com/archlinux/archinstall/wiki/Building-and-Testing) guide.
It will go through everything from packaging, building and running *(with qemu)* the installer against a dev branch. + + +# Contributing + +Please see [CONTRIBUTING.md](https://github.com/archlinux/archinstall/blob/master/CONTRIBUTING.md) -- cgit v1.2.3-54-g00ecf From 82a53207647bca3d8aa797619b22b0a2dd68897b Mon Sep 17 00:00:00 2001 From: Rodrigo Broggi Date: Sun, 25 Jun 2023 06:06:52 -0300 Subject: Document new pip install flag (#1897) See [this thead](https://github.com/archlinux/archinstall/issues/1734#issuecomment-1605709792) Including `--break-operating-system` flag in `pip install` instructions in doc. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'README.md') diff --git a/README.md b/README.md index 15afdda4..9d6fecd9 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,7 @@ you can replace the version of archinstall with a new version and run that with 4. Now clone the latest repository with `git clone https://github.com/archlinux/archinstall` 5. Enter the repository with `cd archinstall` *At this stage, you can choose to check out a feature branch for instance with `git checkout v2.3.1-rc1`* -6. Build the project and install it using `pip install` +6. Build the project and install it using `pip install --break-operating-system .` After this, running archinstall with `python -m archinstall` will run against whatever branch you chose in step 5. -- cgit v1.2.3-54-g00ecf From 5b102b0228e8ffce81341bb2bf771a082d8263c4 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 28 Jun 2023 14:22:07 +0200 Subject: Adding python-simple-term-menu to the dependency list (#1901) * Adding python-simple-term-menu to the dependency list * Added dependencies to all binaries we call, such as 'ps' and 'mkfs' etc * Sorted the depends list - just for peace of mind * Bumped version in prep for release of rc1, also updated README a bit * Removed older python versions from classifiers --- PKGBUILD | 14 ++++++++++++-- README.md | 11 ++++++++--- archinstall/__init__.py | 2 +- pyproject.toml | 8 +++----- 4 files changed, 24 insertions(+), 11 deletions(-) (limited to 'README.md') diff --git a/PKGBUILD b/PKGBUILD index 03160a1c..315256e3 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -4,16 +4,26 @@ # Contributor: demostanis worlds pkgname=archinstall -pkgver=2.5.6 +pkgver=2.6.0 pkgrel=1 pkgdesc="Just another guided/automated Arch Linux installer with a twist" arch=(any) url="https://github.com/archlinux/archinstall" license=(GPL3) depends=( + 'arch-install-scripts' + 'btrfs-progs' + 'coreutils' + 'cryptsetup' + 'e2fsprogs' + 'kbd' + 'pciutils' + 'procps-ng' 'python' - 'systemd' 'python-pyparted' + 'python-simple-term-menu' + 'systemd' + 'util-linux' ) makedepends=( 'python-setuptools' diff --git a/README.md b/README.md index 9d6fecd9..9ec40d17 100644 --- a/README.md +++ b/README.md @@ -17,15 +17,20 @@ The installer also doubles as a python library to install Arch Linux and manage $ sudo pacman -S archinstall -Or simply `git clone` the repo as it has no external dependencies *(but there are optional ones)*.
-Or use `pip install --upgrade archinstall` to use as a library. +Alternative ways to install are `git clone` the repository or `pip install --upgrade archinstall`. ## Running the [guided](https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py) installer -Assuming you are on an Arch Linux live-ISO: +Assuming you are on an Arch Linux live-ISO or installed via `pip`: # archinstall +## Running the [guided](https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py) installer using `git` + + # cd archinstall-git + # cp archinstall/scripts/guided.py + # python guided.py + #### Advanced Some additional options that are not needed by most users are hidden behind the `--advanced` flag. diff --git a/archinstall/__init__.py b/archinstall/__init__.py index e5ec462a..af811465 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -39,7 +39,7 @@ if TYPE_CHECKING: _: Any -__version__ = "2.5.6" +__version__ = "2.6.0rc1" storage['__version__'] = __version__ diff --git a/pyproject.toml b/pyproject.toml index f67f1eca..83c68e1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,12 +11,10 @@ authors = [ ] license = {text = "GPL-3.0-only"} readme = "README.md" -requires-python = ">=3.10" +requires-python = ">=3.11" keywords = ["linux", "arch", "archinstall", "installer"] classifiers = [ - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Operating System :: POSIX :: Linux", ] dependencies = [ @@ -60,7 +58,7 @@ packages = ["archinstall"] # where = ["archinstall"] [tool.mypy] -python_version = "3.10" +python_version = "3.11" files = "archinstall/" exclude = "tests" #check_untyped_defs=true -- cgit v1.2.3-54-g00ecf From 64fc585fa7624060d406b9fcb877baf968ff0f24 Mon Sep 17 00:00:00 2001 From: uranderu <71091366+uranderu@users.noreply.github.com> Date: Fri, 30 Jun 2023 20:37:39 +0200 Subject: Update README.md (#1755) Co-authored-by: Anton Hvornum --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'README.md') diff --git a/README.md b/README.md index 9ec40d17..4bc3104e 100644 --- a/README.md +++ b/README.md @@ -206,7 +206,7 @@ If you want to test a commit, branch or bleeding edge release from the repositor you can replace the version of archinstall with a new version and run that with the steps described below: 1. You need a working network connection -2. Install the build requirements with `pacman -Sy; pacman -S git python-pip` +2. Install the build requirements with `pacman -Sy; pacman -S git python-pip gcc pkgconf` *(note that this may or may not work depending on your RAM and current state of the squashfs maximum filesystem free space)* 3. Uninstall the previous version of archinstall with `pip uninstall archinstall` 4. Now clone the latest repository with `git clone https://github.com/archlinux/archinstall` -- cgit v1.2.3-54-g00ecf From 2252dcf9bb0f07f5f331b66395d24dce54d50a44 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Thu, 14 Sep 2023 19:03:59 +0900 Subject: Fix typo in README.md (#2050) interative -> interactive --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'README.md') diff --git a/README.md b/README.md index 4bc3104e..9d5c3935 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ Therefore, Archinstall will try its best to not introduce any breaking changes e There are some examples in the `examples/` directory that should serve as a starting point. -The following is a small example of how to script your own *interative* installation: +The following is a small example of how to script your own *interactive* installation: ```python from pathlib import Path -- cgit v1.2.3-54-g00ecf From 9e079020b1c7301ee5a645b6eda3cb2bfdaec920 Mon Sep 17 00:00:00 2001 From: yenaras <89593949+yenaras@users.noreply.github.com> Date: Thu, 14 Sep 2023 07:49:46 -0400 Subject: fix instructions for Using a Live ISO Images (#2021) replaced `--break-operating-system` with the correct flag `--break-system-packages` and also added the flag as required for `pip uninstall --break-system-packages` --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index 9d5c3935..5fb0b92a 100644 --- a/README.md +++ b/README.md @@ -208,11 +208,11 @@ you can replace the version of archinstall with a new version and run that with 1. You need a working network connection 2. Install the build requirements with `pacman -Sy; pacman -S git python-pip gcc pkgconf` *(note that this may or may not work depending on your RAM and current state of the squashfs maximum filesystem free space)* -3. Uninstall the previous version of archinstall with `pip uninstall archinstall` +3. Uninstall the previous version of archinstall with `pip uninstall --break-system-packages archinstall` 4. Now clone the latest repository with `git clone https://github.com/archlinux/archinstall` 5. Enter the repository with `cd archinstall` *At this stage, you can choose to check out a feature branch for instance with `git checkout v2.3.1-rc1`* -6. Build the project and install it using `pip install --break-operating-system .` +6. Build the project and install it using `pip install --break-system-packages .` After this, running archinstall with `python -m archinstall` will run against whatever branch you chose in step 5. -- cgit v1.2.3-54-g00ecf From f6acdf5b5e30a3bf3b8c38c3221295126afff088 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Tue, 19 Sep 2023 06:47:24 +1000 Subject: Update README with instructions (#2066) * Update README with instructions * Update * Update --------- Co-authored-by: Daniel Girtler --- README.md | 95 ++++++++++++++++++++++++++++++++------------------------------- 1 file changed, 49 insertions(+), 46 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index 5fb0b92a..a90fead5 100644 --- a/README.md +++ b/README.md @@ -53,58 +53,25 @@ To load the configuration file into `archinstall` run the following command archinstall --config --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 -Arabic -Brazilian Portuguese -Czech -Dutch -Estonian -French -Georgian -German -Indonesian -Italian -Korean -Modern Greek -Polish -Portuguese -Russian -Spanish -Swedish -Tamil -Turkish -Ukrainian -Urdu -``` - -Any contributions to the translations are more than welcome, -to get started please follow [the guide](https://github.com/archlinux/archinstall/blob/master/archinstall/locales/README.md) - # Help or Issues -Submit an issue here on GitHub, or submit a post in the discord help channel.
-When doing so, attach the `/var/log/archinstall/install.log` to the issue ticket. This helps us help you! +If any issues are encountered please submit an issue here on Github or submit a post in the discord help channel. +When submitting an issue, pleasee: +* Provide the stacktrace of the output if there is any +* Attach the `/var/log/archinstall/install.log` to the issue ticket. This helps us help you! + * To extract the log from the ISO image, one way is to use
+ ```curl -F'file=@/var/log/archinstall/install.log' https://0x0.st``` -# Mission Statement -Archinstall promises to ship a [guided installer](https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py) that follows -the [Arch Principles](https://wiki.archlinux.org/index.php/Arch_Linux#Principles) as well as a library to manage services, packages and other Arch Linux aspects. - -The guided installer will provide user-friendly options along the way, but the keyword here is options, they are optional and will never be forced upon anyone. -The guided installer itself is also optional to use if so desired and not forced upon anyone. - ---- - -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. +# Available Languages -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. +Archinstall is available in different languages which have been contributed and are maintained by the community. +The language can be switched inside the installer (first menu entry). Bare in mind that not all languages provide +full translations as we rely on contributors to do the translations. Each language has an indicator that shows +how much has been translated. +Any contributions to the translations are more than welcome, +to get started please follow [the guide](https://github.com/archlinux/archinstall/blob/master/archinstall/locales/README.md) # Scripting your own installation @@ -236,6 +203,42 @@ There's also a [Building and Testing](https://github.com/archlinux/archinstall/w It will go through everything from packaging, building and running *(with qemu)* the installer against a dev branch. +# FAQ + +## How to dual boot with Windows + +`archinstall` can be used to install Arch alongside an existing Windows installation. +Below are the necessary steps: +* After the Windows installation make sure there is some unallocated space for a Linux installation available +* Boot into the ISO and run`archinstall` +* Select `Disk configuration` -> `Manual partitioning` +* Select the disk on which Windows resides +* Chose `Create a new partition` +* Select a filesystem type +* Now the location of the new partition has to be specified as start and end sectors (values can be suffixed with various units) +* Assign mountpoint `/` +* Back in the partitioning menu, assign the `Boot/ESP` partition the mountpoint `/boot` +* This is all for the partitioning menu, select `Confirm and exit` to return to the main menu +* Set any additional settings you would like to have for the installation +* After completing the setup start the installation + + +# Mission Statement + +Archinstall promises to ship a [guided installer](https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py) that follows +the [Arch Principles](https://wiki.archlinux.org/index.php/Arch_Linux#Principles) as well as a library to manage services, packages and other Arch Linux aspects. + +The guided installer will provide user-friendly options along the way, but the keyword here is options, they are optional and will never be forced upon anyone. +The guided installer itself is also optional to use if so desired and not forced upon anyone. + +--- + +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 compatibility after notifying about such changes. + + # Contributing Please see [CONTRIBUTING.md](https://github.com/archlinux/archinstall/blob/master/CONTRIBUTING.md) -- cgit v1.2.3-54-g00ecf From 0e93d5b468291bccd4d4df9b07cffa1ccf7bb21e Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Thu, 21 Sep 2023 18:34:11 +1000 Subject: Update instructions to run archinstall from ISO (#2076) Update instructions to run archinstall from ISO --- README.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index a90fead5..acc377ec 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,13 @@ The definitions of the profiles and what packages they will install can be seen ## Using a Live ISO Image If you want to test a commit, branch or bleeding edge release from the repository using the vanilla Arch Live ISO image, -you can replace the version of archinstall with a new version and run that with the steps described below: +you can replace the version of archinstall with a new version and run that with the steps described below. + +*Note: When booting from a live USB then the space on the ramdisk is limited and may not be sufficient to allow +running a re-installation or upgrade of the installer. In case one runs into this issue, any of the following can be used +- Resize the root partition on the fly https://wiki.archlinux.org/title/Archiso#Adjusting_the_size_of_root_partition_on_the_fly +- The boot parameter `copytoram=y` (https://gitlab.archlinux.org/archlinux/mkinitcpio/mkinitcpio-archiso/-/blob/master/docs/README.bootparams#L26) +can be specified which will copy the root filesystem to tmpfs.* 1. You need a working network connection 2. Install the build requirements with `pacman -Sy; pacman -S git python-pip gcc pkgconf` @@ -179,9 +185,10 @@ you can replace the version of archinstall with a new version and run that with 4. Now clone the latest repository with `git clone https://github.com/archlinux/archinstall` 5. Enter the repository with `cd archinstall` *At this stage, you can choose to check out a feature branch for instance with `git checkout v2.3.1-rc1`* -6. Build the project and install it using `pip install --break-system-packages .` - -After this, running archinstall with `python -m archinstall` will run against whatever branch you chose in step 5. +6. To run the source code, there are 2 different options: + - Run a specific branch version from source directly using `python -m archinstall`, in most cases this will work just fine, the + rare case it will not work is if the source has introduced any new dependencies that are not installed yet + - Installing the branch version with `pip install --break-system-packages .` and `archinstall` ## Without a Live ISO Image @@ -193,7 +200,9 @@ This can be done by installing `pacman -S arch-install-scripts util-linux` local # losetup -a | grep "testimage.img" | awk -F ":" '{print $1}' # pip install --upgrade archinstall # python -m archinstall --script guided - # qemu-system-x86_64 -enable-kvm -machine q35,accel=kvm -device intel-iommu -cpu host -m 4096 -boot order=d -drive file=./testimage.img,format=raw -drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF_CODE.fd -drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF_VARS.fd + # qemu-system-x86_64 -enable-kvm -machine q35,accel=kvm -device intel-iommu -cpu host -m 4096 -boot order=d -drive +file=./testimage.img,format=raw -drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF_CODE.fd +-drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF_VARS.fd This will create a *20 GB* `testimage.img` and create a loop device which we can use to format and install to.
`archinstall` is installed and executed in [guided mode](#docs-todo). Once the installation is complete, ~~you can use qemu/kvm to boot the test media.~~
-- cgit v1.2.3-54-g00ecf From f078c74692e64e4316107175b3b0d8f021fff3ba Mon Sep 17 00:00:00 2001 From: Niko Hoffrén Date: Sun, 24 Sep 2023 01:48:09 +0300 Subject: Fix small typo in README.md (#2095) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'README.md') diff --git a/README.md b/README.md index acc377ec..2420673f 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ archinstall --config --creds -- cgit v1.2.3-54-g00ecf From 877e34de6353691fb9707828a5880c27655f1d1b Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Sun, 24 Sep 2023 17:36:32 +1000 Subject: Fix line break (#2101) Co-authored-by: Daniel Girtler --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index 2420673f..84520d6b 100644 --- a/README.md +++ b/README.md @@ -200,9 +200,7 @@ This can be done by installing `pacman -S arch-install-scripts util-linux` local # losetup -a | grep "testimage.img" | awk -F ":" '{print $1}' # pip install --upgrade archinstall # python -m archinstall --script guided - # qemu-system-x86_64 -enable-kvm -machine q35,accel=kvm -device intel-iommu -cpu host -m 4096 -boot order=d -drive -file=./testimage.img,format=raw -drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF_CODE.fd --drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF_VARS.fd + # qemu-system-x86_64 -enable-kvm -machine q35,accel=kvm -device intel-iommu -cpu host -m 4096 -boot order=d -drive file=./testimage.img,format=raw -drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF_CODE.fd -drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF_VARS.fd This will create a *20 GB* `testimage.img` and create a loop device which we can use to format and install to.
`archinstall` is installed and executed in [guided mode](#docs-todo). Once the installation is complete, ~~you can use qemu/kvm to boot the test media.~~
-- cgit v1.2.3-54-g00ecf From 072519ad11448053cc062ac4efa66c624b4aa887 Mon Sep 17 00:00:00 2001 From: codefiles <11915375+codefiles@users.noreply.github.com> Date: Wed, 27 Sep 2023 11:20:13 -0400 Subject: Simplify command in `README.md` example (#2122) --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index 84520d6b..5c799116 100644 --- a/README.md +++ b/README.md @@ -196,8 +196,7 @@ To test this without a live ISO, the simplest approach is to use a local image a This can be done by installing `pacman -S arch-install-scripts util-linux` locally and doing the following: # truncate -s 20G testimage.img - # losetup -fP ./testimage.img - # losetup -a | grep "testimage.img" | awk -F ":" '{print $1}' + # losetup --partscan --show --find ./testimage.img # pip install --upgrade archinstall # python -m archinstall --script guided # qemu-system-x86_64 -enable-kvm -machine q35,accel=kvm -device intel-iommu -cpu host -m 4096 -boot order=d -drive file=./testimage.img,format=raw -drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF_CODE.fd -drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF_VARS.fd -- cgit v1.2.3-54-g00ecf From 1e296b263714017596beeca27744a51c75f29504 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Thu, 28 Sep 2023 08:44:18 +1000 Subject: Fix 2118 (#2119) * Update locales generation * Update README * Disable translation check --------- Co-authored-by: Daniel Girtler --- .github/workflows/translation-check.yaml | 56 ++++++++++++++++---------------- README.md | 8 +++++ archinstall/locales/README.md | 10 +++--- archinstall/locales/locales_generator.sh | 43 ++++++++++++++++++++++-- 4 files changed, 81 insertions(+), 36 deletions(-) (limited to 'README.md') diff --git a/.github/workflows/translation-check.yaml b/.github/workflows/translation-check.yaml index c0abbaa6..188eeeb8 100644 --- a/.github/workflows/translation-check.yaml +++ b/.github/workflows/translation-check.yaml @@ -1,28 +1,28 @@ -on: - push: - paths: - - 'archinstall/locales/**' - pull_request: - paths: - - 'archinstall/locales/**' -name: Verify local_generate script was run on translation changes -jobs: - translation-check: - runs-on: ubuntu-latest - container: - image: archlinux:latest - steps: - - uses: actions/checkout@v4 - - run: pacman --noconfirm -Syu python git diffutils - - name: Verify all translation scripts are up to date - run: | - cd .. - cp -r archinstall archinstall_orig - cd archinstall/archinstall/locales - bash locales_generator.sh 1> /dev/null - cd ../../.. - git diff \ - --quiet --no-index --name-only \ - archinstall_orig/archinstall/locales \ - archinstall/archinstall/locales \ - || (echo "Translation files have not been updated after translation, please run ./locales_generator.sh once more and commit" && exit 1) +#on: +# push: +# paths: +# - 'archinstall/locales/**' +# pull_request: +# paths: +# - 'archinstall/locales/**' +#name: Verify local_generate script was run on translation changes +#jobs: +# translation-check: +# runs-on: ubuntu-latest +# container: +# image: archlinux:latest +# steps: +# - uses: actions/checkout@v4 +# - run: pacman --noconfirm -Syu python git diffutils +# - name: Verify all translation scripts are up to date +# run: | +# cd .. +# cp -r archinstall archinstall_orig +# cd archinstall/archinstall/locales +# bash locales_generator.sh 1> /dev/null +# cd ../../.. +# git diff \ +# --quiet --no-index --name-only \ +# archinstall_orig/archinstall/locales \ +# archinstall/archinstall/locales \ +# || (echo "Translation files have not been updated after translation, please run ./locales_generator.sh once more and commit" && exit 1) diff --git a/README.md b/README.md index 5c799116..741d57e7 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,14 @@ how much has been translated. Any contributions to the translations are more than welcome, to get started please follow [the guide](https://github.com/archlinux/archinstall/blob/master/archinstall/locales/README.md) +## Fonts +The ISO does not ship with ship with all fonts needed for different languages. +Fonts that are using a different character set than Latin will not be displayed correctly. If those languages +want to be selected than a proper font has to be set manually in the console. + +All available console fonts can be found in `/usr/share/kbd/consolefonts` and can be set with `setfont LatGrkCyr-8x16`. + + # Scripting your own installation ## Scripting interactive installation diff --git a/archinstall/locales/README.md b/archinstall/locales/README.md index e1266209..de1aa83a 100644 --- a/archinstall/locales/README.md +++ b/archinstall/locales/README.md @@ -14,15 +14,15 @@ can be set with `setfont LatGrkCyr-8x16` ## 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). +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 ``` -After that run the script `./locales_generator.sh` it will automatically populate the new `base.po` file with the strings that -need to be translated into the new language. -For example the `base.po` might contain something like the following now +After that run the script `./locales_generator.sh ` it will automatically populate the new `base.po` file with the strings that +need to be translated into the new language. +For example the `base.po` might contain something like the following now ``` #: lib/user_interaction.py:82 msgid "Do you really want to abort?" @@ -30,7 +30,7 @@ msgstr "" ``` The `msgid` is the identifier of the string in the code as well as the default text to be displayed, meaning that if no -translation is provided for a language then this is the text that is going to be shown. +translation is provided for a language then this is the text that is going to be shown. To perform translations for a language this file can be edited manually or the neat `poedit` can be used (https://poedit.net/). If editing the file manually, write the translation in the `msgstr` part diff --git a/archinstall/locales/locales_generator.sh b/archinstall/locales/locales_generator.sh index cdd5be31..5386c3e6 100755 --- a/archinstall/locales/locales_generator.sh +++ b/archinstall/locales/locales_generator.sh @@ -2,11 +2,48 @@ cd $(dirname "$0")/.. -find . -type f -iname "*.py" | xargs xgettext --join-existing --no-location --omit-header -d base -o locales/base.pot +function update_lang() { + file=$1 -for file in $(find locales/ -name "base.po"); do echo "Updating: $file" path=$(dirname $file) msgmerge --quiet --no-location --width 512 --backup none --update $file locales/base.pot msgfmt -o $path/base.mo $file -done +} + + +function generate_all() { + for file in $(find locales/ -name "base.po"); do + update_lang "$file" + done +} + +function generate_single_lang() { + lang_file="locales/$1/LC_MESSAGES/base.po" + + if [ ! -f "$lang_file" ]; then + echo "Language files not found: $lang_file" + exit 1 + fi + + update_lang "$lang_file" +} + + + +if [ $# -eq 0 ] + then + echo "Usage: locales_generator.sh " + exit 1 +fi + +lang=$1 + +# Update the base file containing all translatable string +find . -type f -iname "*.py" | xargs xgettext --join-existing --no-location --omit-header -d base -o locales/base.pot + +case "$lang" in + "all") generate_all + ;; + *) generate_single_lang "$lang" +esac -- cgit v1.2.3-54-g00ecf From 4955b64a8c596d3eafa1b96b74e915ad12b3fe63 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 29 Nov 2023 13:04:56 +0100 Subject: Added --skip-ntp to docs (#2273) --- README.md | 2 +- docs/help/known_issues.rst | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index 741d57e7..6d6a26f8 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ The installer also doubles as a python library to install Arch Linux and manage * archinstall [discord](https://discord.gg/cqXU88y) server * archinstall [matrix.org](https://app.element.io/#/room/#archinstall:matrix.org) channel * archinstall [#archinstall@irc.libera.chat](irc://#archinstall@irc.libera.chat:6697) -* archinstall [documentation](https://archinstall.readthedocs.io/) +* archinstall [documentation](https://archinstall.archlinux.page/) # Installation & Usage diff --git a/docs/help/known_issues.rst b/docs/help/known_issues.rst index 2f1f62cb..425829e5 100644 --- a/docs/help/known_issues.rst +++ b/docs/help/known_issues.rst @@ -13,12 +13,11 @@ Waiting for time sync `#2144`_ | The usual root cause of this is the network topology. | More specifically `timedatectl show`_ cannot perform a proper time sync against the default servers. -| A *"fix"* for this is mentioned in the issue above. -| That is to configure ``/etc/systemd/timesyncd.conf`` and restart ``systemd-timesyncd.service``. +| Restarting ``systemd-timesyncd.service`` might work but most often you need to configure ``/etc/systemd/timesyncd.conf`` to match your network design. .. note:: - A proposal to override the time sync check has been put up for discussion in `#2144`_. + If you know your time is correct on the machine, you can run ``archinstall --skip-ntp`` to ignore time sync. Missing Nvidia Proprietary Driver `#2002`_ ------------------------------------------ -- cgit v1.2.3-54-g00ecf From fbc005d2f1fce70e99958c99a45456b632c9b5ac Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Thu, 7 Mar 2024 13:58:19 +0200 Subject: Added Hebrew translation. (#2348) * Added Hebrew translation. * Updated README with simpler instructions * cd instruction restored Restored the required instruction. --------- Co-authored-by: Yaron Shahrabani --- README.md | 3 +- archinstall/locales/he/LC_MESSAGES/base.mo | Bin 0 -> 44907 bytes archinstall/locales/he/LC_MESSAGES/base.po | 1235 ++++++++++++++++++++++++++++ 3 files changed, 1236 insertions(+), 2 deletions(-) create mode 100644 archinstall/locales/he/LC_MESSAGES/base.mo create mode 100644 archinstall/locales/he/LC_MESSAGES/base.po (limited to 'README.md') diff --git a/README.md b/README.md index 6d6a26f8..188b2db8 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,7 @@ Assuming you are on an Arch Linux live-ISO or installed via `pip`: ## Running the [guided](https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py) installer using `git` # cd archinstall-git - # cp archinstall/scripts/guided.py - # python guided.py + # python -m archinstall #### Advanced Some additional options that are not needed by most users are hidden behind the `--advanced` flag. diff --git a/archinstall/locales/he/LC_MESSAGES/base.mo b/archinstall/locales/he/LC_MESSAGES/base.mo new file mode 100644 index 00000000..5b554862 Binary files /dev/null and b/archinstall/locales/he/LC_MESSAGES/base.mo differ diff --git a/archinstall/locales/he/LC_MESSAGES/base.po b/archinstall/locales/he/LC_MESSAGES/base.po new file mode 100644 index 00000000..be116703 --- /dev/null +++ b/archinstall/locales/he/LC_MESSAGES/base.po @@ -0,0 +1,1235 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: Yaron Shahrabani \n" +"Language-Team: \n" +"Language: he\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.4.2\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 "Do you really want to abort?" +msgstr "לבטל את התהליך?" + +msgid "And one more time for verification: " +msgstr "ופעם נוספת לאימות: " + +msgid "Would you like to use swap on zram?" +msgstr "להשתמש בשטח החלפה ב־zram?" + +msgid "Desired hostname for the installation: " +msgstr "שם מארח רצוי להתקנה: " + +msgid "Username for required superuser with sudo privileges: " +msgstr "שם למשתמש העל הנחוץ עם הרשאות על: " + +msgid "Any additional users to install (leave blank for no users): " +msgstr "משתמשים נוספים להתקנה (ריק משמעו אין עוד משתמשים): " + +msgid "Should this user be a superuser (sudoer)?" +msgstr "זה אמור להיות משתמש על (sudoer)?" + +msgid "Select a timezone" +msgstr "בחירת אזור זמן" + +msgid "Would you like to use GRUB as a bootloader instead of systemd-boot?" +msgstr "להשתמש ב־GRUB כמנהל טעינה על פני systemd-boot?" + +msgid "Choose a bootloader" +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 "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): " +msgstr "נא לכתוב חבילות נוספות להתקנה (להפריד ברווחים, להשאיר ריק כדי לדלג): " + +msgid "Copy ISO network configuration to installation" +msgstr "העתקת הגדרות הרשת מה־ISO להתקנה" + +msgid "Use NetworkManager (necessary for configuring internet graphically in GNOME and KDE)" +msgstr "להשתמש ב־NetworkManager (חיוני להגדרת האינטרנט עם כלים חזותיים ב־GNOME וב־KDE)" + +msgid "Select one network interface to configure" +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): " + +msgid "Enter your gateway (router) IP address or leave blank for none: " +msgstr "נא למלא את כתובת ה־IP של שער הגישה (ראוטר) או להשאיר ריק כדי לא להגדיר: " + +msgid "Enter your DNS servers (space separated, blank for none): " +msgstr "נא למלא את שרתי ה־DNS שלך (להפריד ברווחים, להשאיר ריק אם אין): " + +msgid "Select which filesystem your main partition should use" +msgstr "נא לבחור באיזו מערכת קבצים צריכה להשתמש המחיצה הראשית שלך" + +msgid "Current partition layout" +msgstr "פריסת מחיצות נוכחית" + +msgid "" +"Select what to do with\n" +"{}" +msgstr "" +"נא לבחור מה לעשות עם‬\n" +"‫{}" + +msgid "Enter a desired filesystem type for the partition" +msgstr "נא לציין את סוג מערכת הקבצים הרצויה במחיצה" + +msgid "Enter the start location (in parted units: s, GB, %, etc. ; default: {}): " +msgstr "נא למלא את מיקום ההתחלה (ביחידות של parted‏: s,‏ GB, %, וכו׳ ; ברירת מחדל: {}): " + +msgid "Enter the end location (in parted units: s, GB, %, etc. ; ex: {}): " +msgstr "נא למלא את מיקום הסוף (ביחידות של parted‏: s,‏ GB, %, וכו׳ ; למשל: {}): " + +msgid "{} contains queued partitions, this will remove those, are you sure?" +msgstr "{} מכיל מחיצות ממתינות, פעולה זו תסיר אותן, להמשיך?" + +msgid "" +"{}\n" +"\n" +"Select by index which partitions to delete" +msgstr "" +"{}‬\n" +"\n" +"‫בחירה לפי מפתח אילו מחיצות למחוק" + +msgid "" +"{}\n" +"\n" +"Select by index which partition to mount where" +msgstr "" +"{}‬\n" +"\n" +"‫בחירה לפי מפתח אילו מחיצות לעגן ואיפה" + +msgid " * Partition mount-points are relative to inside the installation, the boot would be /boot as an example." +msgstr " * נקודות העגינה של המחיצה הן יחסיות למערכת ההתקנה, מחיצת הטעינה למשל תהיה ‎/boot." + +msgid "Select where to mount partition (leave blank to remove mountpoint): " +msgstr "נא לבחור להיכן לעגן את המחיצה (להשאיר ריק כדי להסיר את נקודת העגינה): " + +msgid "" +"{}\n" +"\n" +"Select which partition to mask for formatting" +msgstr "" +"‫{}‬‬\n" +"\n" +"‫נא לבחור איזו מחיצה לסמן לפרמוט" + +msgid "" +"{}\n" +"\n" +"Select which partition to mark as encrypted" +msgstr "" +"‫{}‬‬‬\n" +"\n" +"‫‫נא לבחור איזו מחיצה לסמן להצפנה" + +msgid "" +"{}\n" +"\n" +"Select which partition to mark as bootable" +msgstr "" +"‫{}‬‬‬\n" +"\n" +"‫נא לבחור איזו מחיצה לסמן כזמינה לטעינה" + +msgid "" +"{}\n" +"\n" +"Select which partition to set a filesystem on" +msgstr "" +"‫{}‬\n" +"\n" +"‫נא לבחור על איזו מחיצה להגדיר מערכת קבצים" + +msgid "Enter a desired filesystem type for the partition: " +msgstr "נא לציין סוג מערכת קבצים רצויה למחיצה: " + +msgid "Archinstall language" +msgstr "השפה של Archinstall" + +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)" +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" +msgstr "זאת רשימה של פרופילים שנכתבו מראש, הם עשויים להקל על התקנת דברים כמו סביבות שולחן עבודה" + +msgid "Select keyboard layout" +msgstr "נא לבחור פריסת מקלדת" + +msgid "Select one of the regions to download packages from" +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." +msgstr "לתאימות המיטבית עם חומרת ה־AMD שלך, כדאי להשתמש או באפשרות של קוד פתוח לחלוטין או ב־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 "לתאימות המיטבית עם חומרת האינטל שלך, כדאי להשתמש או באפשרות של קוד פתוח לחלוטין או באינטל.‬\n" + +msgid "For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n" +msgstr "לתאימות המיטבית עם חומרת ה־Nvidia שלך, כדאי להשתמש במנהל ההתקן הקנייני של Nvidia.‬\n" + +msgid "" +"\n" +"\n" +"Select a graphics driver or leave blank to install all open-source drivers" +msgstr "" +"\n" +"‫‬‬\n" +"‫‫נא לבחור מנהל התקן גרפי או להשאיר ריק כדי להתקין מנהלי התקנים בקוד פתוח לגמרי" + +msgid "All open-source (default)" +msgstr "הכול בקוד פתוח (ברירת מחדל)" + +msgid "Choose which kernels to use or leave blank for default \"{}\"" +msgstr "נא לבחור אילו ליבות להשתמש או להשאיר ריק לברירת המחדל „{}”" + +msgid "Choose which locale language to use" +msgstr "נא לבחור באיזו שפה של הגדרה אזורית להשתמש" + +msgid "Choose which locale encoding to use" +msgstr "נא לבחור איזה קידוד של הגדרה אזורית להשתמש" + +msgid "Select one of the values shown below: " +msgstr "נא לבחור את אחד מהערכים שמופיע להלן: " + +msgid "Select one or more of the options below: " +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." +msgstr "יש למלא סוג מערכת קבצים תקפה כדי להמשיך. ניתן לעיין ב־`man parted` לקבלת רשימת סוגי מערכת הקבצים התקפות." + +msgid "Error: Listing profiles on URL \"{}\" resulted in:" +msgstr "שגיאה: הצגת הפרופילים בכתובת „{}“ הניבה:" + +msgid "Error: Could not decode \"{}\" result as JSON:" +msgstr "לא ניתן לפענח את התוצאה „{}“ כ־JSON:" + +msgid "Keyboard layout" +msgstr "פריסת מקלדת" + +msgid "Mirror region" +msgstr "אזור אתר מראה" + +msgid "Locale language" +msgstr "שפת ההגדרה האזורית" + +msgid "Locale encoding" +msgstr "קידוד ההגדרה האזורית" + +msgid "Drive(s)" +msgstr "כוננים" + +msgid "Disk layout" +msgstr "פריסת כוננים" + +msgid "Encryption password" +msgstr "סיסמת הצפנה" + +msgid "Swap" +msgstr "שטח החלפה" + +msgid "Bootloader" +msgstr "מנהל טעינה" + +msgid "Root password" +msgstr "סיסמת root (משתמש עליון)" + +msgid "Superuser account" +msgstr "חשבון משתמש־על" + +msgid "User account" +msgstr "חשבון משתמש" + +msgid "Profile" +msgstr "פרופיל" + +msgid "Audio" +msgstr "שמע" + +msgid "Kernels" +msgstr "ליבות" + +msgid "Additional packages" +msgstr "חבילות נוספות" + +msgid "Network configuration" +msgstr "הגדרות רשת" + +msgid "Automatic time sync (NTP)" +msgstr "סנכרון זמן אוטומטי (NTP)" + +msgid "Install ({} config(s) missing)" +msgstr "התקנה ({} הגדרות חסרות)" + +msgid "" +"You decided to skip harddrive selection\n" +"and 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?" +msgstr "" +"החלטת לוותר על בחירת כוננים קשיחים\n" +"‬\n" +"‫ולהשתמש בתצורת הכוננים שמעוגנת על {} כמו שהיא (ניסיוני)\n" +"‬\n" +"‫אזהרה: Archinstall won't check the suitability of this setup\n" +"‬\n" +"‏‫Do you wish to continue?" + +msgid "Re-using partition instance: {}" +msgstr "שימוש מחדש בעותק של מחיצה: {}" + +msgid "Create a new partition" +msgstr "יצירת מחיצה חדשה" + +msgid "Delete a partition" +msgstr "מחיקת מחיצה" + +msgid "Clear/Delete all partitions" +msgstr "פינוי/מחיקה של כל המחיצות" + +msgid "Assign mount-point for a partition" +msgstr "הקצאת נקודת עגינה למחיצה" + +msgid "Mark/Unmark a partition to be formatted (wipes data)" +msgstr "סימון/ביטול סימון מחיצה לפרמוט (מחיקה מוחלטת של הנתונים)" + +msgid "Mark/Unmark a partition as encrypted" +msgstr "סימון/ביטול סימון מחיצה כמוצפנת" + +msgid "Mark/Unmark a partition as bootable (automatic for /boot)" +msgstr "סימון/ביטול סימון מחיצה כרשאית טעינה (אוטומטית ל־‎/boot)" + +msgid "Set desired filesystem for a partition" +msgstr "נא להגדיר מערכת קבצים רצויה למחיצה" + +msgid "Abort" +msgstr "ביטול" + +msgid "Hostname" +msgstr "שם מארח" + +msgid "Not configured, unavailable unless setup manually" +msgstr "לא מוגדר, לא זמין למעט במקרה של התקנה ידנית" + +msgid "Timezone" +msgstr "אזור זמן" + +msgid "Set/Modify the below options" +msgstr "הגדרת/שינוי האפשרויות הבאות" + +msgid "Install" +msgstr "התקנה" + +msgid "" +"Use ESC to skip\n" +"\n" +msgstr "" +"להשתמש ב־ESC כדי לצאת‬\n" +"\n" + +msgid "Suggest partition layout" +msgstr "הצעת פריסת מחיצות" + +msgid "Enter a password: " +msgstr "נא למלא סיסמה: " + +msgid "Enter a encryption password for {}" +msgstr "נא למלא סיסמת הצפנה עבור {}" + +msgid "Enter disk encryption password (leave blank for no encryption): " +msgstr "נא למלא סיסמה להצפנת הכונן (להשאיר ריק כדי לא להגדיר הצפנה): " + +msgid "Create a required super-user with sudo privileges: " +msgstr "יצירת משתמש על נחוץ עם הרשאות sudo: " + +msgid "Enter root password (leave blank to disable root): " +msgstr "נא למלא סיסמה למשתמש העליון (יש להשאיר ריק כדי להשבית את root): " + +msgid "Password for user \"{}\": " +msgstr "סיסמה למשתמש „{}“: " + +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" +msgstr "להשתמש בסנכרון שעון אוטומטי (NTP) מול שרתי התזמון כברירת מחדל?‬\n" + +msgid "" +"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 יעבוד.‬\n" +"‫למידע נוסף, נא לפנות לוויקי של Arch" + +msgid "Enter a username to create an additional user (leave blank to skip): " +msgstr "נא למלא שם משתמש כדי ליצור משתמש נוסף (ריק כדי לדלג): " + +msgid "Use ESC to skip\n" +msgstr "יש להשתמש ב־ESC כדי לדלג‬\n" + +msgid "" +"\n" +" Choose an object from the list, and select one of the available actions for it to execute" +msgstr "" +"\n" +"‫נא לבחור עצם מהרשימה ולבחור באחת מהאפשרויות הזמינות עבורו כדי להפעיל אותו" + +msgid "Cancel" +msgstr "הסגה" + +msgid "Confirm and exit" +msgstr "אישור ויציאה" + +msgid "Add" +msgstr "הוספה" + +msgid "Copy" +msgstr "העתקה" + +msgid "Edit" +msgstr "עריכה" + +msgid "Delete" +msgstr "מחיקה" + +msgid "Select an action for '{}'" +msgstr "נא לבחור פעולה עבור ‚{}’" + +msgid "Copy to new key:" +msgstr "העתקה למפתח חדש:" + +msgid "Unknown nic type: {}. Possible values are {}" +msgstr "סוג מתאם תקשורת לא ידוע: {}. הערכים האפשריים הם {}" + +msgid "" +"\n" +"This is your chosen configuration:" +msgstr "" +"\n" +"‫זאת ההגדרה שבחרת:" + +msgid "Pacman is already running, waiting maximum 10 minutes for it to terminate." +msgstr "‫Pacman כבר פועל, נמתין למשך 10 דקות לכל היותר עד לסיומו." + +msgid "Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall." +msgstr "נעילת ה־pacman הקודמת מעולם לא נסגרה. נא לנקות הפעלות קיימות של pacman בטרם הפעלת archinstall." + +msgid "Choose which optional additional repositories to enable" +msgstr "נא לבחור אילו מאגרים נוספים להפעיל כרשות" + +msgid "Add a user" +msgstr "הוספת משתמש" + +msgid "Change password" +msgstr "החלפת סיסמה" + +msgid "Promote/Demote user" +msgstr "קידום/הסגת משתמש" + +msgid "Delete User" +msgstr "מחיקת משתמש" + +msgid "" +"\n" +"Define a new user\n" +msgstr "" +"\n" +"‫הגדרת משתמש חדש‬\n" + +msgid "User Name : " +msgstr "שם משתמש : " + +msgid "Should {} be a superuser (sudoer)?" +msgstr "האם {} אמור לקבל הרשאות על (sudoer)?" + +msgid "Define users with sudo privilege: " +msgstr "הגדרת משתמשים עם הרשאת sudo: " + +msgid "No network configuration" +msgstr "אין הגדרות רשת" + +msgid "Set desired subvolumes on a btrfs partition" +msgstr "הגדרת תת־כרכים רצויים במחיצת BTRFS" + +msgid "" +"{}\n" +"\n" +"Select which partition to set subvolumes on" +msgstr "" +"{}‬\n" +"\n" +"‫נא לבחור תחת איזו מחיצה להגדיר תת־כרכים" + +msgid "Manage btrfs subvolumes for current partition" +msgstr "ניהול תת־כרכי BTRFS למחיצה הנוכחית" + +msgid "No configuration" +msgstr "אין הגדרה" + +msgid "Save user configuration" +msgstr "שמירת הגדרת משתמש" + +msgid "Save user credentials" +msgstr "שמירת פרטי משתמש" + +msgid "Save disk layout" +msgstr "שמירת פריסת כונן" + +msgid "Save all" +msgstr "לשמור הכול" + +msgid "Choose which configuration to save" +msgstr "נא לבחור אילו הגדרות לשמור" + +msgid "Enter a directory for the configuration(s) to be saved: " +msgstr "נא למלא את התיקייה להגדרות לשמירה: " + +msgid "Not a valid directory: {}" +msgstr "אינה תיקייה תקפה: {}" + +msgid "The password you are using seems to be weak," +msgstr "הסיסמה שבחרת נראית חלשה," + +msgid "are you sure you want to use it?" +msgstr "בכל זאת להשתמש בה?" + +msgid "Optional repositories" +msgstr "מאגרי רשות" + +msgid "Save configuration" +msgstr "שמירת הגדרה" + +msgid "Missing configurations:\n" +msgstr "" +"הגדרות חסרות:\n" +"‬\n" + +msgid "Either root-password or at least 1 superuser must be specified" +msgstr "יש לציין או סיסמה למשתמש העליון (root) או לפחות משתמש אחד עם הרשאות על (sudo)" + +msgid "Manage superuser accounts: " +msgstr "ניהול משתמשים עם הרשאות על: " + +msgid "Manage ordinary user accounts: " +msgstr "ניהול חשבונות משתמשים רגילים: " + +msgid " Subvolume :{:16}" +msgstr " תת־כרך :{:16}" + +msgid " mounted at {:16}" +msgstr " מעוגן תחת {:16}" + +msgid " with option {}" +msgstr " עם האפשרות {}" + +msgid "" +"\n" +" Fill the desired values for a new subvolume \n" +msgstr "" +"\n" +"‫‫‫‫ נא למלא את הערכים הרצויים לתת־כרך חדש ‬\n" + +msgid "Subvolume name " +msgstr "שם תת־כרך " + +msgid "Subvolume mountpoint" +msgstr "נק׳ עיגון תת־כרך" + +msgid "Subvolume options" +msgstr "אפשרויות תת־כרך" + +msgid "Save" +msgstr "שמירה" + +msgid "Subvolume name :" +msgstr "שם תת־כרך :" + +msgid "Select a mount point :" +msgstr "נא לבחור נק׳ עגינה :" + +msgid "Select the desired subvolume options " +msgstr "נא לבחור את אפשרויות התת־כרך הרצויות " + +msgid "Define users with sudo privilege, by username: " +msgstr "הגדרת משתמשים עם הרשאת על (sudo), לפי שם משתמש: " + +msgid "[!] A log file has been created here: {}" +msgstr "[!] כאן נוצר קובץ יומן: {}" + +msgid "Would you like to use BTRFS subvolumes with a default structure?" +msgstr "להשתמש בתת־כרכים של BTRFS במבנה ברירת המחדל?" + +msgid "Would you like to use BTRFS compression?" +msgstr "להשתמש בדחיסה של BTRFS?" + +msgid "Would you like to create a separate partition for /home?" +msgstr "ליצור מחיצה נפרדת ל־‎/home?" + +msgid "The selected drives do not have the minimum capacity required for an automatic suggestion\n" +msgstr "לכוננים הנבחרים אין את הקיבולת המזערית הנחוצה להצעות אוטומטיות\n" + +msgid "Minimum capacity for /home partition: {}GB\n" +msgstr "הקיבולת המזערית למחיצת ‎/home:‏ {}ג״ב\n" + +msgid "Minimum capacity for Arch Linux partition: {}GB" +msgstr "הקיבולת המזערית למחיצת ‎/home:‏ {}ג״ב" + +msgid "Continue" +msgstr "המשך" + +msgid "yes" +msgstr "כן" + +msgid "no" +msgstr "לא" + +msgid "set: {}" +msgstr "הוגדר: {}" + +msgid "Manual configuration setting must be a list" +msgstr "רשומת ההגדרה הידנית חייבת להיות רשימה" + +msgid "No iface specified for manual configuration" +msgstr "לא צוין iface להגדרה ידנית" + +msgid "Manual nic configuration with no auto DHCP requires an IP address" +msgstr "הגדרת מתאם תקשורת ללא DHCP אוטומטי דורש כתובת IP" + +msgid "Add interface" +msgstr "הוספת מנשק" + +msgid "Edit interface" +msgstr "עריכת מנשק" + +msgid "Delete interface" +msgstr "מחיקת מנשק" + +msgid "Select interface to add" +msgstr "נא לבחור מנשק להוספה" + +msgid "Manual configuration" +msgstr "הגדרה ידנית" + +msgid "Mark/Unmark a partition as compressed (btrfs only)" +msgstr "סימון/ביטול סימון מחיצה כדחוסה (btrfs בלבד)" + +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" +msgstr "מספק מבחר סביבות שולחן עבודה ומנהלי ריצוף חלונות, למשל: gnome,‏ kde,‏ sway" + +msgid "Select your desired desktop environment" +msgstr "נא לבחור את סביבת שולחן העבודה הרצויה לך" + +msgid "A very basic installation that allows you to customize Arch Linux as you see fit." +msgstr "התקנה בסיסית מאוד שמאפשרת לך להתאים את Arch Linux בדיוק לדרך שנוחה לך." + +msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb" +msgstr "מספק מבחר חבילות שרת להתקנה ולהפעלה, למשל: httpd,‏ nginx,‏ mariadb" + +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." +msgstr "מתקין מערכת מזערית בנוסף ל־xorg ולמנהלי התקנים גרפיים." + +msgid "Press Enter to continue." +msgstr "נא ללחוץ על Enter כדי להמשיך." + +msgid "Would you like to chroot into the newly created installation and perform post-installation configuration?" +msgstr "האם להיכנס להתקנה החדשה שיצרת עם chroot (העמסת סביבה) לביצוע הגדרות שלאחר התקנה?" + +msgid "Are you sure you want to reset this setting?" +msgstr "לאפס את ההגדרה הזאת?" + +msgid "Select one or more hard drives to use and configure\n" +msgstr "נא לבחור כונן אחד או או יותר לשימוש ולהגדרה‬\n" + +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?" +msgstr "איפוס בחירת הכוננים יאפס גם את פריסת הכוננים הנוכחית. להמשיך?" + +msgid "Save and exit" +msgstr "לשמור ולצאת" + +msgid "" +"{}\n" +"contains queued partitions, this will remove those, are you sure?" +msgstr "" +"‫{}‬\n" +"‫‬‫מכיל מחיצות שממתינות בתור, הפעולה הזאת תסיר אותן, להמשיך?" + +msgid "No audio server" +msgstr "אין שרת שמע" + +msgid "(default)" +msgstr "(ברירת מחדל)" + +msgid "Use ESC to skip" +msgstr "יש להשתמש ב־ESC כדי לדלג" + +msgid "" +"Use CTRL+C to reset current selection\n" +"\n" +msgstr "" +"יש להשתמש ב־CTRL+C כדי לאפס את הבחירה הנוכחית\n" +"‬\n" +"‏‫\n" +"‬\n" + +msgid "Copy to: " +msgstr "העתקה אל: " + +msgid "Edit: " +msgstr "עריכה: " + +msgid "Key: " +msgstr "מפתח: " + +msgid "Edit {}: " +msgstr "עריכת {}: " + +msgid "Add: " +msgstr "הוספה: " + +msgid "Value: " +msgstr "ערך: " + +msgid "You can skip selecting a drive and partitioning and use whatever drive-setup is mounted at /mnt (experimental)" +msgstr "אפשר לדלג על בחירת כונן וחלוקה למחיצות ולהשתמש בתצורת הכוננים שמעוגנת תחת ‎/mnt כפי שהיא (ניסיוני)" + +msgid "Select one of the disks or skip and use /mnt as default" +msgstr "נא לבחור אחד מהכוננים או לדלג ולהשתמש ב־‎/mnt כברירת מחדל" + +msgid "Select which partitions to mark for formatting:" +msgstr "נא לבחור אילו מחיצות לסמן לפרמוט:" + +msgid "Use HSM to unlock encrypted drive" +msgstr "להשתמש ב־HSM לשחרור כונן מוצפן" + +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 "יש לציין או סיסמה למשתמש העליון (root) או לפחות משתמש אחד עם הרשאות על (sudo)" + +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 "האם ל־„{}“ אמורות להיות הרשאות על (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 "מחיקת תת־כרך" + +msgid "Configured {} interfaces" +msgstr "הוגדרו {} מנשקים" + +msgid "This option enables the number of parallel downloads that can occur during installation" +msgstr "האפשרות הזאת מאפשרת מספר הורדות במקביל שיכולות להתרחש במהלך התקנה" + +msgid "" +"Enter the number of parallel downloads to be enabled.\n" +" (Enter a value between 1 to {})\n" +"Note:" +msgstr "" +"נא למלא את מספר ההורדות המקביליות שתהיינה פעילות.‬\n" +"‫ (אמור להיות ערך בין 1 ל־{})‬\n" +"‫הערה:" + +msgid " - Maximum value : {} ( Allows {} parallel downloads, allows {} downloads at a time )" +msgstr " - ערך מרבי : {} ( מאפשר {} הורדות במקביל, מאפשר {} הורדות בבת אחת )" + +msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )" +msgstr " - ערך מזערי : 1 ( מאפשר כל הורדות במקביל, מאפשר שתי הורדות בו־זמנית )" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )" +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 "הורדות במקביל" + +msgid "ESC to skip" +msgstr "‫ESC לדילוג" + +msgid "CTRL+C to reset" +msgstr "CTRL+C לאיפוס" + +msgid "TAB to select" +msgstr "TAB לבחירה" + +msgid "[Default value: 0] > " +msgstr "[ערך ברירת מחדל: 0] > " + +msgid "To be able to use this translation, please install a font manually that supports the language." +msgstr "כדי להשתמש בתרגום הזה, נא להתקין את הגופן שתומך בשפה הזאת ידנית." + +msgid "The font should be stored as {}" +msgstr "יש לאחסן את הגופן בתור {}" + +msgid "Archinstall requires root privileges to run. See --help for more." +msgstr "‫Archinstall דורש הרשאות עליונות (root) כדי לעלות. ‎--help לקבלת מידע נוסף." + +msgid "Select an execution mode" +msgstr "נא לבחור מצב הפעלה" + +msgid "Unable to fetch profile from specified url: {}" +msgstr "לא ניתן למשוך את הפרופיל מהכתובת שצוינה: {}" + +msgid "Profiles must have unique name, but profile definitions with duplicate name found: {}" +msgstr "השמות של הפרופילים חייבים להיות יחודיים, אך נמצאו הגדרות פרופילים עם שמות כפולים: {}" + +msgid "Select one or more devices to use and configure" +msgstr "נא לבחור התקן או יותר לשימוש ולהגדרה" + +msgid "If you reset the device selection this will also reset the current disk layout. Are you sure?" +msgstr "איפוס בחירת ההתקנים יאפס גם את פריסת הכוננים הנוכחית. להמשיך?" + +msgid "Existing Partitions" +msgstr "מחיצות קיימות" + +msgid "Select a partitioning option" +msgstr "נא לבחור אפשרויות מחיצות" + +msgid "Enter the root directory of the mounted devices: " +msgstr "נא למלא את תיקיית השורש של ההתקנים המעוגנים: " + +msgid "Minimum capacity for /home partition: {}GiB\n" +msgstr "קיבולת מזערית למחיצת ‎/home: {} ג״ב\n" + +msgid "Minimum capacity for Arch Linux partition: {}GiB" +msgstr "קיבולת מזערית למחיצת Arch Linux: {} ג״ב" + +msgid "This is a list of pre-programmed profiles_bck, they might make it easier to install things like desktop environments" +msgstr "זאת רשימה של פרופילי גיבוי שנכתבו מראש, הם עשויים להקל על התקנת דברים כמו סביבות שולחן עבודה" + +msgid "Current profile selection" +msgstr "בחירת הפרופיל הנוכחי" + +msgid "Remove all newly added partitions" +msgstr "הסרת כל המחיצות החדשות שנוספו" + +msgid "Assign mountpoint" +msgstr "הקצאת נקודת עגינה" + +msgid "Mark/Unmark to be formatted (wipes data)" +msgstr "סימון/ביטול סימן לפרמוט (מוחה את הנתונים)" + +msgid "Mark/Unmark as bootable" +msgstr "סימון/ביטול סימון כזמין לטעינה" + +msgid "Change filesystem" +msgstr "החלפת מערכת קבצים" + +msgid "Mark/Unmark as compressed" +msgstr "סימון/ביטול סימון כמכווץ" + +msgid "Set subvolumes" +msgstr "הגדרת תת־כרכים" + +msgid "Delete partition" +msgstr "מחיקת מחיצה" + +msgid "Partition" +msgstr "מחיצה" + +msgid "This partition is currently encrypted, to format it a filesystem has to be specified" +msgstr "המחיצה הזאת מוצפנת כרגע, כדי לפרמט אותה צריך להגדיר מערכת קבצים" + +msgid "Partition mount-points are relative to inside the installation, the boot would be /boot as an example." +msgstr "נקודות העגינה של המחיצה הן יחסיות למערכת ההתקנה, מחיצת הטעינה למשל תהיה ‎/boot." + +msgid "If mountpoint /boot is set, then the partition will also be marked as bootable." +msgstr "אם מוגדרת נקודת עגינה על ‎/boot, אז המחיצה תסומן כזמינה לטעינה." + +msgid "Mountpoint: " +msgstr "נקודת עגינה: " + +msgid "Current free sectors on device {}:" +msgstr "כמות המקטעים (סקטורים) הפנויים כרגע בכונן {}:" + +msgid "Total sectors: {}" +msgstr "סך כל המקטעים (סקטורים): {}" + +msgid "Enter the start sector (default: {}): " +msgstr "נא למלא את מקטע (סקטור) ההתחלה (ברירת מחדל: {}): " + +msgid "Enter the end sector of the partition (percentage or block number, default: {}): " +msgstr "נא למלא את מקטע (סקטור) הסוף של המחיצה (אחוז או מספר בלוק, ברירת מחדל: {}): " + +msgid "This will remove all newly added partitions, continue?" +msgstr "הפעולה הזאת תסיר את המחיצות שנוספו, להמשיך?" + +msgid "Partition management: {}" +msgstr "ניהול מחיצות: {}" + +msgid "Total length: {}" +msgstr "אורך כולל: {}" + +msgid "Encryption type" +msgstr "סוג הצפנה" + +msgid "Partitions" +msgstr "מחיצות" + +msgid "No HSM devices available" +msgstr "אין התקני HSM זמינים" + +msgid "Partitions to be encrypted" +msgstr "מחיצות להצפנה" + +msgid "Select disk encryption option" +msgstr "נא לבחור אפשרויות להצפנת כונן" + +msgid "Select a FIDO2 device to use for HSM" +msgstr "נא לבחור את התקן ה־FIDO2 לשימוש עבור HSM" + +msgid "Use a best-effort default partition layout" +msgstr "להשתמש בפריסת מחיצות כברירת מחדל על בסיס מאמץ מיטבי" + +msgid "Manual Partitioning" +msgstr "חלוקה ידנית למחיצות" + +msgid "Pre-mounted configuration" +msgstr "הגדרות לעיגון שבוצע" + +msgid "Unknown" +msgstr "לא ידוע" + +msgid "Partition encryption" +msgstr "הצפנת מחיצה" + +msgid " ! Formatting {} in " +msgstr " ! {} מפורמט תחת " + +msgid "← Back" +msgstr "→ חזרה" + +msgid "Disk encryption" +msgstr "הצפנת כונן" + +msgid "Configuration" +msgstr "הגדרות" + +msgid "Password" +msgstr "סיסמה" + +msgid "All settings will be reset, are you sure?" +msgstr "כל ההגדרות תאופסנה, להמשיך?" + +msgid "Back" +msgstr "חזרה" + +msgid "Please chose which greeter to install for the chosen profiles: {}" +msgstr "נא לבחור את מערכת קבלת הפנים לפרופילים הנבחרים: {}" + +msgid "Environment type: {}" +msgstr "סוג סביבה: {}" + +msgid "The proprietary Nvidia driver is not supported by Sway. It is likely that you will run into issues, are you okay with that?" +msgstr "ב־Sway אין תמיכה במנהל ההתקן של Nvidia. כנראה שזה יוביל לבעיות, זה בסדר מבחינתך?" + +msgid "Installed packages" +msgstr "חבילות מותקנות" + +msgid "Add profile" +msgstr "הוספת פרופיל" + +msgid "Edit profile" +msgstr "עריכת פרופיל" + +msgid "Delete profile" +msgstr "מחיקת פרופיל" + +msgid "Profile name: " +msgstr "שם הפרופיל: " + +msgid "The profile name you entered is already in use. Try again" +msgstr "שם הפרופיל שמילאת כבר קיים. נא לנסות שוב" + +msgid "Packages to be install with this profile (space separated, leave blank to skip): " +msgstr "חבילות להתקנה עם הפרופיל הזה (להפריד ברווחים, ריק יוביל לדילוג על השלב הזה): " + +msgid "Services to be enabled with this profile (space separated, leave blank to skip): " +msgstr "שירותים להפעלה עם הפרופיל הזה (להפריד ברווחים, ריק יוביל לדילוג על השלב הזה): " + +msgid "Should this profile be enabled for installation?" +msgstr "להפעיל את הפרופיל הזה להתקנה?" + +msgid "Create your own" +msgstr "יצירת אחד משלך" + +msgid "" +"\n" +"Select a graphics driver or leave blank to install all open-source drivers" +msgstr "" +"\n" +"‫נא לבחור מנהל התקן גרפי או להשאיר ריק כדי להתקין את מנהל ההתקן שכולו בקוד פתוח" + +msgid "Sway needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" +msgstr "‫Sway צריך גישה למושב (אוסף של התקני חומרה כמו למשל מקלדת, עכבר וכו׳) שלך" + +msgid "" +"\n" +"\n" +"Choose an option to give Sway access to your hardware" +msgstr "" +"\n" +"\n" +"‫נא לבחור אפשרות לתת ל־Sway גישה לחומרה שלך" + +msgid "Graphics driver" +msgstr "מנהלי התקני גרפיים" + +msgid "Greeter" +msgstr "מערכת קבלת פנים" + +msgid "Please chose which greeter to install" +msgstr "נא לבחור איזו מערכת קבלת פנים להתקין" + +msgid "This is a list of pre-programmed default_profiles" +msgstr "זאת רשימה של פרופילים ברירת מחדל שנכתבו מראש" + +msgid "Disk configuration" +msgstr "הגדרת כונן" + +msgid "Profiles" +msgstr "פרופילים" + +msgid "Finding possible directories to save configuration files ..." +msgstr "מתבצע חיפוש אחר תיקיות אפשריות לשמירת קובצי ההגדרות…" + +msgid "Select directory (or directories) for saving configuration files" +msgstr "נא לבחור תיקייה (או תיקיות) לשמירת קובצי ההגדרות" + +msgid "Add a custom mirror" +msgstr "הוספת אתר מראה משלך" + +msgid "Change custom mirror" +msgstr "החלפת אתר מראה משלך" + +msgid "Delete custom mirror" +msgstr "מחיקת אתר מראה משלך" + +msgid "Enter name (leave blank to skip): " +msgstr "נא למלא שם (ריק לדילוג): " + +msgid "Enter url (leave blank to skip): " +msgstr "נא למלא כתובת (ריק לדילוג): " + +msgid "Select signature check option" +msgstr "בחירת אפשרות בדיקת חתימות" + +msgid "Select signature option" +msgstr "בחירת אפשרות חתימות" + +msgid "Custom mirrors" +msgstr "אתרי מראה משלך" + +msgid "Defined" +msgstr "מוגדר" + +msgid "Save user configuration (including disk layout)" +msgstr "שמירת הגדרות משתמש (כולל פריסת כוננים)" + +msgid "" +"Enter a directory for the configuration(s) to be saved (tab completion enabled)\n" +"Save directory: " +msgstr "" +"נא למלא תיקייה לשמירת ההגדרות (אפשר להשלים עם tab)\n" +"‬\n" +"‫תיקיית השמירה: " + +msgid "" +"Do you want to save {} configuration file(s) in the following location?\n" +"\n" +"{}" +msgstr "" +"לשמור את {} קובצי ההגדרות בתיקייה הבאה?‬\n" +"\n" +"‫{}" + +msgid "Saving {} configuration files to {}" +msgstr "קובצי ההגדרות {} נשמרים אל {}" + +msgid "Mirrors" +msgstr "אתרי מראה" + +msgid "Mirror regions" +msgstr "אזורי אתרי מראה" + +msgid " - Maximum value : {} ( Allows {} parallel downloads, allows {max_downloads+1} downloads at a time )" +msgstr " - ערך מרבי : {} ( מאפשר {} הורדות במקביל, מאפשר {max_downloads+1} הורדות במקביל )" + +msgid "Invalid input! Try again with a valid input [1 to {}, or 0 to disable]" +msgstr "קלט שגוי! נא לנסות שוב עם קלט תקין [1 עד {}, או 0 להשבתה]" + +msgid "Locales" +msgstr "הגדרות אזוריות" + +msgid "Use NetworkManager (necessary to configure internet graphically in GNOME and KDE)" +msgstr "להשתמש ב־NetworkManager (נחוץ להגדרת האינטרנט באופן גרפי ב־GNOME וב־KDE)" + +msgid "Total: {} / {}" +msgstr "סך הכול: {} / {}" + +msgid "All entered values can be suffixed with a unit: B, KB, KiB, MB, MiB..." +msgstr "אפשר להוסיף יחידה לכל ערך שהוא שהזנת: %, B,‏ KB,‏ KiB,‏ MB,‏ MiB…" + +msgid "If no unit is provided, the value is interpreted as sectors" +msgstr "אם לא צוין ערך, הערך יפורש בתוך סקטורים (מגזרים)" + +msgid "Enter start (default: sector {}): " +msgstr "נא למלא התחלה (ברירת מחדל: סקטור {}): " + +msgid "Enter end (default: {}): " +msgstr "נא למלא סוף (ברירת מחדל: {}): " + +msgid "Unable to determine fido2 devices. Is libfido2 installed?" +msgstr "לא ניתן למצוא התקני fido2.‏ libfido2 מותקן?" + +msgid "Path" +msgstr "נתיב" + +msgid "Manufacturer" +msgstr "יצרן" + +msgid "Product" +msgstr "מוצר" + +#, python-brace-format +msgid "Invalid configuration: {error}" +msgstr "הגדרה שגויה: {error}" + +msgid "Type" +msgstr "סוג" + +msgid "This option enables the number of parallel downloads that can occur during package downloads" +msgstr "אפשרות זו מאפשרת כמה הורדות במקביל לטובת הורדות של חבילות" + +msgid "" +"Enter the number of parallel downloads to be enabled.\n" +"\n" +"Note:\n" +msgstr "" +"נא למלא את מספר ההורדות המקביליות להפעלה.‬\n" +"\n" +"‫הערה:‬\n" + +msgid " - Maximum recommended value : {} ( Allows {} parallel downloads at a time )" +msgstr " - הערך המרבי המומלץ: {} ( מאפשר {} הורדות במקביל בכל עת )" + +msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )\n" +msgstr " - השבתה/ברירת מחדל : 0 ( משבית הורדות מקביליות, מאפשר רק הורדה אחת כל פעם )‬\n" + +msgid "Invalid input! Try again with a valid input [or 0 to disable]" +msgstr "קלט שגוי! נא לנסות שוב עם קלט תקין [או 0 להשבתה]" + +msgid "Hyprland needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)" +msgstr "‫Hyprland צריך גישה למושב שלך (seat - אוסף של התקני חומרה, כלומר מקלדת, עכבר וכו׳)" + +msgid "" +"\n" +"\n" +"Choose an option to give Hyprland access to your hardware" +msgstr "" +"\n" +"\n" +"‫נא לבחור אפשרות כדי לתת ל־Hyprland גישה לחומרה שלך" + +msgid "All entered values can be suffixed with a unit: %, B, KB, KiB, MB, MiB..." +msgstr "אפשר להוסיף יחידה לכל ערך שהוא: %, B,‏ KB,‏ KiB,‏ MB,‏ MiB…" + +msgid "Would you like to use unified kernel images?" +msgstr "להשתמש בדמויות ליבה אחודות (UKI)?" + +msgid "Unified kernel images" +msgstr "דמויות ליבה אחודות" + +msgid "Waiting for time sync (timedatectl show) to complete." +msgstr "בהמתנה להשלמת סנכרון השעון (timedatectl show)." + +msgid "Time syncronization not completing, while you wait - check the docs for workarounds: https://archinstall.readthedocs.io/" +msgstr "סנכרון זמן לא מסתיים, בזמן ההמתנה - כדאי לחפש צורות לעקוף את זה במסמכים: https://archinstall.readthedocs.io/‎" + +msgid "Skipping waiting for automatic time sync (this can cause issues if time is out of sync during installation)" +msgstr "לדלג על המתנה לסנכרון השעון אוטומטית (יכול לגרום לבעיות אם השעה לא מסונכרנת במהלך ההתקנה)" + +msgid "Waiting for Arch Linux keyring sync (archlinux-keyring-wkd-sync) to complete." +msgstr "בהמתנה לסיום סנכרון מחזיק מפתחות של Arch Linux‏ (archlinux-keyring-wkd-sync)." -- cgit v1.2.3-54-g00ecf From 1bf68311c281d49641f6e1868b49dbaf40b00470 Mon Sep 17 00:00:00 2001 From: aidanthewiz <9968011+aidanthewiz@users.noreply.github.com> Date: Thu, 7 Mar 2024 07:47:45 -0500 Subject: Improve the README (#2174) Co-authored-by: Anton Hvornum --- README.md | 106 +++++++++++++++++++++++++++++++++----------------------------- 1 file changed, 57 insertions(+), 49 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index 188b2db8..2e9c622b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Lint Python and Find Syntax Errors](https://github.com/archlinux/archinstall/actions/workflows/flake8.yaml/badge.svg)](https://github.com/archlinux/archinstall/actions/workflows/flake8.yaml) Just another guided/automated [Arch Linux](https://wiki.archlinux.org/index.php/Arch_Linux) installer with a twist. -The installer also doubles as a python library to install Arch Linux and manage services, packages and other things inside the installed system *(Usually from a live medium)*. +The installer also doubles as a python library to install Arch Linux and manage services, packages, and other things inside the installed system *(Usually from a live medium)*. * archinstall [discord](https://discord.gg/cqXU88y) server * archinstall [matrix.org](https://app.element.io/#/room/#archinstall:matrix.org) channel @@ -14,58 +14,66 @@ The installer also doubles as a python library to install Arch Linux and manage * archinstall [documentation](https://archinstall.archlinux.page/) # Installation & Usage - - $ sudo pacman -S archinstall +```shell +sudo pacman -S archinstall +``` Alternative ways to install are `git clone` the repository or `pip install --upgrade archinstall`. ## Running the [guided](https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py) installer Assuming you are on an Arch Linux live-ISO or installed via `pip`: - - # archinstall +```shell +archinstall +``` ## Running the [guided](https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py) installer using `git` +```shell # cd archinstall-git # python -m archinstall +``` #### Advanced -Some additional options that are not needed by most users are hidden behind the `--advanced` flag. +Some additional options that most users do not need are hidden behind the `--advanced` flag. ## Running from a declarative configuration file or URL `archinstall` can be run with a JSON configuration file. There are 2 different configuration files to consider, the `user_configuration.json` contains all general installation configuration, whereas the `user_credentials.json` -contains the sensitive user configuration such as user password, root password and encryption password. +contains the sensitive user configuration such as user password, root password, and encryption password. An example of the user configuration file can be found here [configuration file](https://github.com/archlinux/archinstall/blob/master/examples/config-sample.json) -and example of the credentials configuration here +and an example of the credentials configuration here [credentials file](https://github.com/archlinux/archinstall/blob/master/examples/creds-sample.json). **HINT:** The configuration files can be auto-generated by starting `archinstall`, configuring all desired menu points and then going to `Save configuration`. To load the configuration file into `archinstall` run the following command -``` +```shell archinstall --config --creds ``` # Help or Issues -If any issues are encountered please submit an issue here on Github or submit a post in the discord help channel. +If you come across any issues, kindly submit your issue here on Github or post your query in the +[discord](https://discord.gg/cqXU88y) help channel. + When submitting an issue, please: -* Provide the stacktrace of the output if there is any +* Provide the stacktrace of the output if applicable * Attach the `/var/log/archinstall/install.log` to the issue ticket. This helps us help you! * To extract the log from the ISO image, one way is to use
- ```curl -F'file=@/var/log/archinstall/install.log' https://0x0.st``` + ```shell + curl -F'file=@/var/log/archinstall/install.log' https://0x0.st + ``` # Available Languages Archinstall is available in different languages which have been contributed and are maintained by the community. -The language can be switched inside the installer (first menu entry). Bare in mind that not all languages provide +The language can be switched inside the installer (first menu entry). Bear in mind that not all languages provide full translations as we rely on contributors to do the translations. Each language has an indicator that shows how much has been translated. @@ -73,11 +81,11 @@ Any contributions to the translations are more than welcome, to get started please follow [the guide](https://github.com/archlinux/archinstall/blob/master/archinstall/locales/README.md) ## Fonts -The ISO does not ship with ship with all fonts needed for different languages. -Fonts that are using a different character set than Latin will not be displayed correctly. If those languages -want to be selected than a proper font has to be set manually in the console. +The ISO does not ship with all fonts needed for different languages. +Fonts that use a different character set than Latin will not be displayed correctly. If those languages +want to be selected then a proper font has to be set manually in the console. -All available console fonts can be found in `/usr/share/kbd/consolefonts` and can be set with `setfont LatGrkCyr-8x16`. +All available console fonts can be found in `/usr/share/kbd/consolefonts` and set with `setfont LatGrkCyr-8x16`. # Scripting your own installation @@ -135,22 +143,22 @@ with Installer( installation.create_users(user) ``` -This installer will perform the following: +This installer will perform the following actions: -* Prompt the user to configurate the disk partitioning +* Prompt the user to configure the disk partitioning * Prompt the user to setup disk encryption * Create a file handler instance for the configured disk and the optional disk encryption * Perform the disk operations (WARNING: this will potentially format the disks and erase all data) -* Installs a basic instance of Arch Linux *(base base-devel linux linux-firmware btrfs-progs efibootmgr)* -* Installs and configures a bootloader to partition 0 on uefi. On BIOS, it sets the root to partition 0. +* Install a basic instance of Arch Linux *(base base-devel linux linux-firmware btrfs-progs efibootmgr)* +* Install and configures a bootloader to partition 0 on UEFI. On BIOS, it sets the root to partition 0. * Install additional packages *(nano, wget, git)* * Create a new user -> **Creating your own ISO with this script on it:** Follow [ArchISO](https://wiki.archlinux.org/index.php/archiso)'s guide on how to create your own ISO. +> **To create your own ISO with this script in it:** Follow [ArchISO](https://wiki.archlinux.org/index.php/archiso)'s guide on creating your own ISO. ## Script non-interactive automated installation -For an example of a fully scripted, automated installation please see the example +For an example of a fully scripted, automated installation please refer to the example [full_automated_installation.py](https://github.com/archlinux/archinstall/blob/master/examples/full_automated_installation.py) ## Unattended installation based on MAC address @@ -159,16 +167,16 @@ Archinstall comes with an [unattended](https://github.com/archlinux/archinstall/ example which will look for a matching profile for the machine it is being run on, based on any local MAC address. For instance, if the machine the code is executed on has the MAC address `52:54:00:12:34:56` it will look for a profile called [52-54-00-12-34-56.py](https://github.com/archlinux/archinstall/blob/master/archinstall/default_profiles/tailored.py). -If it's found, the unattended installation will commence and source that profile as its installation procedure. +If it's found, the unattended installation will begin and source that profile as its installation procedure. # Profiles -`archinstall` ships with a set of pre-defined profiles that can be chosen during the installation process. +`archinstall` comes with a set of pre-configured profiles available for selection during the installation process. - [Desktop](https://github.com/archlinux/archinstall/tree/master/archinstall/default_profiles/desktops) - [Server](https://github.com/archlinux/archinstall/tree/master/archinstall/default_profiles/servers) -The definitions of the profiles and what packages they will install can be seen directly in the menu or +The profiles' definitions and the packages they will install can be directly viewed in the menu, or [default profiles](https://github.com/archlinux/archinstall/tree/master/archinstall/default_profiles) @@ -176,10 +184,10 @@ The definitions of the profiles and what packages they will install can be seen ## Using a Live ISO Image -If you want to test a commit, branch or bleeding edge release from the repository using the vanilla Arch Live ISO image, -you can replace the version of archinstall with a new version and run that with the steps described below. +If you want to test a commit, branch, or bleeding edge release from the repository using the standard Arch Linux Live ISO image, +replace the archinstall version with a newer one and execute the subsequent steps defined below. -*Note: When booting from a live USB then the space on the ramdisk is limited and may not be sufficient to allow +*Note: When booting from a live USB, the space on the ramdisk is limited and may not be sufficient to allow running a re-installation or upgrade of the installer. In case one runs into this issue, any of the following can be used - Resize the root partition on the fly https://wiki.archlinux.org/title/Archiso#Adjusting_the_size_of_root_partition_on_the_fly - The boot parameter `copytoram=y` (https://gitlab.archlinux.org/archlinux/mkinitcpio/mkinitcpio-archiso/-/blob/master/docs/README.bootparams#L26) @@ -220,36 +228,36 @@ It will go through everything from packaging, building and running *(with qemu)* ## How to dual boot with Windows -`archinstall` can be used to install Arch alongside an existing Windows installation. -Below are the necessary steps: -* After the Windows installation make sure there is some unallocated space for a Linux installation available -* Boot into the ISO and run`archinstall` -* Select `Disk configuration` -> `Manual partitioning` -* Select the disk on which Windows resides -* Chose `Create a new partition` -* Select a filesystem type -* Now the location of the new partition has to be specified as start and end sectors (values can be suffixed with various units) -* Assign mountpoint `/` -* Back in the partitioning menu, assign the `Boot/ESP` partition the mountpoint `/boot` -* This is all for the partitioning menu, select `Confirm and exit` to return to the main menu -* Set any additional settings you would like to have for the installation -* After completing the setup start the installation +To install Arch Linux alongside an existing Windows installation using `archinstall`, follow these steps: + +1. Ensure some unallocated space is available for the Linux installation after the Windows installation. +2. Boot into the ISO and run `archinstall`. +3. Choose `Disk configuration` -> `Manual partitioning`. +4. Select the disk on which Windows resides. +5. Select `Create a new partition`. +6. Choose a filesystem type. +7. Determine the start and end sectors for the new partition location (values can be suffixed with various units). +8. Assign the mountpoint `/` to the new partition. +9. Assign the `Boot/ESP` partition the mountpoint `/boot` from the partitioning menu. +10. Confirm your settings and exit to the main menu by choosing `Confirm and exit`. +11. Modify any additional settings for your installation as necessary. +12. Start the installation upon completion of setup. # Mission Statement Archinstall promises to ship a [guided installer](https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py) that follows -the [Arch Principles](https://wiki.archlinux.org/index.php/Arch_Linux#Principles) as well as a library to manage services, packages and other Arch Linux aspects. +the [Arch Linux Principles](https://wiki.archlinux.org/index.php/Arch_Linux#Principles) as well as a library to manage services, packages, and other Arch Linux aspects. -The guided installer will provide user-friendly options along the way, but the keyword here is options, they are optional and will never be forced upon anyone. -The guided installer itself is also optional to use if so desired and not forced upon anyone. +The guided installer ensures a user-friendly experience, offering optional selections throughout the process. Emphasizing its flexible nature, these options are never obligatory. +In addition, the decision to use the guided installer remains entirely with the user, reflecting the Linux philosophy of providing full freedom and flexibility. --- -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. +Archinstall primarily functions as a flexible library for managing services, packages, and other elements within an Arch Linux system. +This core library is the backbone for the guided installer that Archinstall provides. It is also designed to be used by those who wish to script their own custom installations. -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. +Therefore, Archinstall will try its best to not introduce any breaking changes except for major releases which may break backward compatibility after notifying about such changes. # Contributing -- cgit v1.2.3-54-g00ecf From 0e8d7aac168287ed7da54481c5673daf047ed129 Mon Sep 17 00:00:00 2001 From: fortifiedhill <24689525+fortifiedhill@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:09:57 -0600 Subject: Update resize root partition ArchWiki link (#2385) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'README.md') diff --git a/README.md b/README.md index 2e9c622b..1cad9da3 100644 --- a/README.md +++ b/README.md @@ -189,7 +189,7 @@ replace the archinstall version with a newer one and execute the subsequent step *Note: When booting from a live USB, the space on the ramdisk is limited and may not be sufficient to allow running a re-installation or upgrade of the installer. In case one runs into this issue, any of the following can be used -- Resize the root partition on the fly https://wiki.archlinux.org/title/Archiso#Adjusting_the_size_of_root_partition_on_the_fly +- Resize the root partition https://wiki.archlinux.org/title/Archiso#Adjusting_the_size_of_the_root_file_system - The boot parameter `copytoram=y` (https://gitlab.archlinux.org/archlinux/mkinitcpio/mkinitcpio-archiso/-/blob/master/docs/README.bootparams#L26) can be specified which will copy the root filesystem to tmpfs.* -- cgit v1.2.3-54-g00ecf