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> --- examples/__init__.py | 0 examples/auto_discovery_mounted.py | 13 + examples/config-sample.json | 126 +++++++- examples/creds-sample.json | 19 +- examples/full_automated_installation.py | 95 ++++++ examples/guided.py | 306 ------------------- examples/interactive_installation.py | 220 +++++++++++++ examples/mac_address_installation.py | 18 ++ examples/minimal.py | 75 ----- examples/minimal_installation.py | 85 ++++++ examples/only_hd.py | 151 --------- examples/only_hd_installation.py | 63 ++++ examples/swiss.py | 526 -------------------------------- examples/unattended.py | 21 -- 14 files changed, 615 insertions(+), 1103 deletions(-) delete mode 100644 examples/__init__.py create mode 100644 examples/auto_discovery_mounted.py create mode 100644 examples/full_automated_installation.py delete mode 100644 examples/guided.py create mode 100644 examples/interactive_installation.py create mode 100644 examples/mac_address_installation.py delete mode 100644 examples/minimal.py create mode 100644 examples/minimal_installation.py delete mode 100644 examples/only_hd.py create mode 100644 examples/only_hd_installation.py delete mode 100644 examples/swiss.py delete mode 100644 examples/unattended.py (limited to 'examples') diff --git a/examples/__init__.py b/examples/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/auto_discovery_mounted.py b/examples/auto_discovery_mounted.py new file mode 100644 index 00000000..0bd30cd1 --- /dev/null +++ b/examples/auto_discovery_mounted.py @@ -0,0 +1,13 @@ +from pathlib import Path + +from archinstall import disk + +root_mount_dir = Path('/mnt/archinstall') + +mods = disk.device_handler.detect_pre_mounted_mods(root_mount_dir) + +disk_config = disk.DiskLayoutConfiguration( + disk.DiskLayoutType.Pre_mount, + device_modifications=mods, + relative_mountpoint=Path('/mnt/archinstall') +) diff --git a/examples/config-sample.json b/examples/config-sample.json index dc8693a7..a7c5d537 100644 --- a/examples/config-sample.json +++ b/examples/config-sample.json @@ -1,28 +1,132 @@ { - "audio": null, - "bootloader": "systemd-bootctl", - "harddrives": [ - "/dev/loop0" - ], - "hostname": "", + "config_version": "2.5.2", + "additional-repositories": [], + "archinstall-language": "English", + "audio": "pipewire", + "bootloader": "Systemd-boot", + "debug": false, + "disk_config": { + "config_type": "default_layout", + "device_modifications": [ + { + "device": "/dev/sda", + "partitions": [ + { + "btrfs": [], + "flags": [ + "Boot" + ], + "fs_type": "fat32", + "length": { + "sector_size": null, + "total_size": null, + "unit": "MiB", + "value": 512 + }, + "mount_options": [], + "mountpoint": "/boot", + "obj_id": "2c3fa2d5-2c79-4fab-86ec-22d0ea1543c0", + "start": { + "sector_size": null, + "total_size": null, + "unit": "MiB", + "value": 1 + }, + "status": "create", + "type": "primary" + }, + { + "btrfs": [], + "flags": [], + "fs_type": "ext4", + "length": { + "sector_size": null, + "total_size": null, + "unit": "GiB", + "value": 20 + }, + "mount_options": [], + "mountpoint": "/", + "obj_id": "3e7018a0-363b-4d05-ab83-8e82d13db208", + "start": { + "sector_size": null, + "total_size": null, + "unit": "MiB", + "value": 513 + }, + "status": "create", + "type": "primary" + }, + { + "btrfs": [], + "flags": [], + "fs_type": "ext4", + "length": { + "sector_size": null, + "total_size": { + "sector_size": null, + "total_size": null, + "unit": "B", + "value": 250148290560 + }, + "unit": "Percent", + "value": 100 + }, + "mount_options": [], + "mountpoint": "/home", + "obj_id": "ce58b139-f041-4a06-94da-1f8bad775d3f", + "start": { + "sector_size": null, + "total_size": null, + "unit": "GiB", + "value": 20 + }, + "status": "create", + "type": "primary" + } + ], + "wipe": true + } + ] + }, + "hostname": "archlinux", "kernels": [ "linux" ], "keyboard-layout": "us", "mirror-region": { - "Worldwide": { - "https://mirror.rackspace.com/archlinux/$repo/os/$arch": true + "Australia": { + "http://archlinux.mirror.digitalpacific.com.au/$repo/os/$arch": true, } }, "nic": { - "type": "NM" + "dhcp": true, + "dns": null, + "gateway": null, + "iface": null, + "ip": null, + "type": "nm" }, + "no_pkg_lookups": false, "ntp": true, + "offline": false, "packages": [], - "profile": null, + "parallel downloads": 0, + "profile_config": { + "gfx_driver": "All open-source (default)", + "greeter": "sddm", + "profile": { + "details": [ + "Kde" + ], + "main": "Desktop" + } + }, "script": "guided", + "silent": false, "swap": true, "sys-encoding": "utf-8", "sys-language": "en_US", - "timezone": "UTC" + "timezone": "UTC", + "version": "2.5.2" } diff --git a/examples/creds-sample.json b/examples/creds-sample.json index 0681e16f..530a2465 100644 --- a/examples/creds-sample.json +++ b/examples/creds-sample.json @@ -1,15 +1,8 @@ { - "!root-password": "", - "!users": [ - { - "username": "", - "!password": "", - "sudo": false - }, - { - "username": "", - "!password": "", - "sudo": true - } - ] + "!users": [ + { + "sudo": true, + "username": "archinstall" + } + ] } diff --git a/examples/full_automated_installation.py b/examples/full_automated_installation.py new file mode 100644 index 00000000..a169dd50 --- /dev/null +++ b/examples/full_automated_installation.py @@ -0,0 +1,95 @@ +from pathlib import Path + +from archinstall import Installer, ProfileConfiguration, profile_handler +from archinstall.default_profiles.minimal import MinimalProfile +from archinstall import disk +from archinstall.lib.models import User + +# we're creating a new ext4 filesystem installation +fs_type = disk.FilesystemType('ext4') +device_path = Path('/dev/sda') + +# get the physical disk device +device = disk.device_handler.get_device(device_path) + +if not device: + raise ValueError('No device found for given path') + +# create a new modification for the specific device +device_modification = disk.DeviceModification(device, wipe=True) + +# create a new boot partition +boot_partition = disk.PartitionModification( + status=disk.ModificationStatus.Create, + type=disk.PartitionType.Primary, + start=disk.Size(1, disk.Unit.MiB), + length=disk.Size(512, disk.Unit.MiB), + mountpoint=Path('/boot'), + fs_type=disk.FilesystemType.Fat32, + flags=[disk.PartitionFlag.Boot] +) +device_modification.add_partition(boot_partition) + +# create a root partition +root_partition = disk.PartitionModification( + status=disk.ModificationStatus.Create, + type=disk.PartitionType.Primary, + start=disk.Size(513, disk.Unit.MiB), + length=disk.Size(20, disk.Unit.GiB), + mountpoint=None, + fs_type=fs_type, + mount_options=[], +) +device_modification.add_partition(root_partition) + +# create a new home partition +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=fs_type, + mount_options=[] +) +device_modification.add_partition(home_partition) + +disk_config = disk.DiskLayoutConfiguration( + config_type=disk.DiskLayoutType.Default, + device_modifications=[device_modification] +) + +# disk encryption configuration (Optional) +disk_encryption = disk.DiskEncryption( + encryption_password="enc_password", + encryption_type=disk.EncryptionType.Partition, + partitions=[home_partition], + hsm_device=None +) + +# initiate file handler with the disk config and the optional disk encryption config +fs_handler = disk.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(show_countdown=False) + +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) diff --git a/examples/guided.py b/examples/guided.py deleted file mode 100644 index e9240c03..00000000 --- a/examples/guided.py +++ /dev/null @@ -1,306 +0,0 @@ -import logging -import os -import time - -import archinstall -from archinstall import ConfigurationOutput, Menu -from archinstall.lib.models.network_configuration import NetworkConfigurationHandler - -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: {archinstall.disk_layouts()}", level=logging.DEBUG) - - -def ask_user_questions(): - """ - First, we'll ask the user for a bunch of user input. - Not until we're satisfied with what we want to install - will we continue with the actual installation steps. - """ - - # ref: https://github.com/archlinux/archinstall/pull/831 - # we'll set NTP to true by default since this is also - # the default value specified in the menu options; in - # case it will be changed by the user we'll also update - # the system immediately - global_menu = archinstall.GlobalMenu(data_store=archinstall.arguments) - - global_menu.enable('archinstall-language') - - global_menu.enable('keyboard-layout') - - # Set which region to download packages from during the installation - global_menu.enable('mirror-region') - - global_menu.enable('sys-language') - global_menu.enable('sys-encoding') - - # Ask which harddrives/block-devices we will install to - # and convert them into archinstall.BlockDevice() objects. - global_menu.enable('harddrives') - - global_menu.enable('disk_layouts') - - # Specify disk encryption options - global_menu.enable('disk_encryption') - - # Ask which boot-loader to use (will only ask if we're in UEFI mode, otherwise will default to GRUB) - global_menu.enable('bootloader') - - global_menu.enable('swap') - - # Get the hostname for the machine - global_menu.enable('hostname') - - # Ask for a root password (optional, but triggers requirement for super-user if skipped) - global_menu.enable('!root-password') - - global_menu.enable('!users') - - # Ask for archinstall-specific profiles (such as desktop environments etc) - global_menu.enable('profile') - - # Ask about audio server selection if one is not already set - global_menu.enable('audio') - - # Ask for preferred kernel: - global_menu.enable('kernels') - - global_menu.enable('packages') - - if archinstall.arguments.get('advanced', False): - # Enable parallel downloads - global_menu.enable('parallel downloads') - - # Ask or Call the helper function that asks the user to optionally configure a network. - global_menu.enable('nic') - - global_menu.enable('timezone') - - global_menu.enable('ntp') - - global_menu.enable('additional-repositories') - - global_menu.enable('__separator__') - - global_menu.enable('save_config') - global_menu.enable('install') - global_menu.enable('abort') - - global_menu.run() - - -def perform_filesystem_operations(): - """ - Issue a final warning before we continue with something un-revertable. - We mention the drive one last time, and count from 5 to 0. - """ - - if archinstall.arguments.get('harddrives', None): - print(_(f" ! Formatting {archinstall.arguments['harddrives']} in "), end='') - archinstall.do_countdown() - - """ - Setup the blockdevice, filesystem (and optionally encryption). - Once that's done, we'll hand over to perform_installation() - """ - mode = archinstall.GPT - if archinstall.has_uefi() is False: - mode = archinstall.MBR - - for drive in archinstall.arguments.get('harddrives', []): - if archinstall.arguments.get('disk_layouts', {}).get(drive.path): - with archinstall.Filesystem(drive, mode) as fs: - fs.load_layout(archinstall.arguments['disk_layouts'][drive.path]) - - -def perform_installation(mountpoint): - """ - Performs the installation steps on a block device. - Only requirement is that the block devices are - formatted and setup prior to entering this function. - """ - - with archinstall.Installer(mountpoint, kernels=archinstall.arguments.get('kernels', ['linux'])) as installation: - # Mount all the drives to the desired mountpoint - # This *can* be done outside of the installation, but the installer can deal with it. - if archinstall.arguments.get('disk_layouts'): - installation.mount_ordered_layout(archinstall.arguments['disk_layouts']) - - # Placing /boot check during installation because this will catch both re-use and wipe scenarios. - for partition in installation.partitions: - if partition.mountpoint == installation.target + '/boot': - if partition.size < 0.19: # ~200 MiB in GiB - raise archinstall.DiskError(f"The selected /boot partition in use is not large enough to properly install a boot loader. Please resize it to at least 200MiB and re-run the installation.") - - # If we've activated NTP, make sure it's active in the ISO too and - # make sure at least one time-sync finishes before we continue with the installation - if archinstall.arguments.get('ntp', False): - # Activate NTP in the ISO - archinstall.SysCommand('timedatectl set-ntp true') - - # TODO: This block might be redundant, but this service is not activated unless - # `timedatectl set-ntp true` is executed. - logged = False - while archinstall.service_state('dbus-org.freedesktop.timesync1.service') not in ('running'): - if not logged: - installation.log(f"Waiting for dbus-org.freedesktop.timesync1.service to enter running state", level=logging.INFO) - logged = True - time.sleep(1) - - logged = False - while 'Server: n/a' in archinstall.SysCommand('timedatectl timesync-status --no-pager --property=Server --value'): - if not logged: - installation.log(f"Waiting for timedatectl timesync-status to report a timesync against a server", level=logging.INFO) - logged = True - time.sleep(1) - - # if len(mirrors): - # Certain services might be running that affects the system during installation. - # Currently, only 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. - installation.log('Waiting for automatic mirror selection (reflector) to complete.', level=logging.INFO) - while archinstall.service_state('reflector') not in ('dead', 'failed', 'exited'): - time.sleep(1) - - installation.log('Waiting pacman-init.service to complete.', level=logging.INFO) - while archinstall.service_state('pacman-init') not in ('dead', 'failed', 'exited'): - time.sleep(1) - - installation.log('Waiting Arch Linux keyring sync (archlinux-keyring-wkd-sync) to complete.', level=logging.INFO) - while archinstall.service_state('archlinux-keyring-wkd-sync') not in ('dead', 'failed', 'exited'): - time.sleep(1) - - # Set mirrors used by pacstrap (outside of installation) - if archinstall.arguments.get('mirror-region', None): - archinstall.use_mirrors(archinstall.arguments['mirror-region']) # Set the mirrors for the live medium - - # Retrieve list of additional repositories and set boolean values appropriately - if archinstall.arguments.get('additional-repositories', None) is not None: - enable_testing = 'testing' in archinstall.arguments.get('additional-repositories', None) - enable_multilib = 'multilib' in archinstall.arguments.get('additional-repositories', None) - else: - enable_testing = False - enable_multilib = False - - if installation.minimal_installation( - testing=enable_testing, multilib=enable_multilib, hostname=archinstall.arguments['hostname'], - locales=[f"{archinstall.arguments['sys-language']} {archinstall.arguments['sys-encoding'].upper()}"]): - if archinstall.arguments.get('mirror-region') is not None: - if archinstall.arguments.get("mirrors", None) is not None: - installation.set_mirrors(archinstall.arguments['mirror-region']) # Set the mirrors in the installation medium - if archinstall.arguments.get('swap'): - installation.setup_swap('zram') - if archinstall.arguments.get("bootloader") == "grub-install" and archinstall.has_uefi(): - installation.add_additional_packages("grub") - installation.add_bootloader(archinstall.arguments["bootloader"]) - - # If user selected to copy the current ISO network configuration - # Perform a copy of the config - network_config = archinstall.arguments.get('nic', None) - - if network_config: - handler = NetworkConfigurationHandler(network_config) - handler.config_installer(installation) - - if archinstall.arguments.get('audio', None) is not None: - installation.log(f"This audio server will be used: {archinstall.arguments.get('audio', None)}", level=logging.INFO) - if archinstall.arguments.get('audio', None) == 'pipewire': - archinstall.Application(installation, 'pipewire').install() - elif archinstall.arguments.get('audio', None) == 'pulseaudio': - print('Installing pulseaudio ...') - installation.add_additional_packages("pulseaudio") - else: - installation.log("No audio server will be installed.", level=logging.INFO) - - if archinstall.arguments.get('packages', None) and archinstall.arguments.get('packages', None)[0] != '': - installation.add_additional_packages(archinstall.arguments.get('packages', None)) - - if archinstall.arguments.get('profile', None): - installation.install_profile(archinstall.arguments.get('profile', None)) - - if users := archinstall.arguments.get('!users', None): - installation.create_users(users) - - if timezone := archinstall.arguments.get('timezone', None): - installation.set_timezone(timezone) - - if archinstall.arguments.get('ntp', False): - installation.activate_time_syncronization() - - if archinstall.accessibility_tools_in_use(): - installation.enable_espeakup() - - if (root_pw := archinstall.arguments.get('!root-password', None)) and len(root_pw): - installation.user_set_pw('root', root_pw) - - # This step must be after profile installs to allow profiles to install language pre-requisits. - # After which, this step will set the language both for console and x11 if x11 was installed for instance. - installation.set_keyboard_language(archinstall.arguments['keyboard-layout']) - - if archinstall.arguments['profile'] and archinstall.arguments['profile'].has_post_install(): - with archinstall.arguments['profile'].load_instructions(namespace=f"{archinstall.arguments['profile'].namespace}.py") as imported: - if not imported._post_install(): - archinstall.log(' * Profile\'s post configuration requirements was not fulfilled.', fg='red') - exit(1) - - # If the user provided a list of services to be enabled, pass the list to the enable_service function. - # Note that while it's called enable_service, it can actually take a list of services and iterate it. - if archinstall.arguments.get('services', None): - installation.enable_service(*archinstall.arguments['services']) - - # If the user provided custom commands to be run post-installation, execute them now. - if archinstall.arguments.get('custom-commands', None): - archinstall.run_custom_user_commands(archinstall.arguments['custom-commands'], installation) - - installation.genfstab() - - installation.log("For post-installation tips, see https://wiki.archlinux.org/index.php/Installation_guide#Post-installation", fg="yellow") - if not archinstall.arguments.get('silent'): - prompt = str(_('Would you like to chroot into the newly created installation and perform post-installation configuration?')) - choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes()).run() - if choice.value == Menu.yes(): - try: - installation.drop_to_shell() - except: - pass - - # For support reasons, we'll log the disk layout post installation (crash or no crash) - archinstall.log(f"Disk states after installing: {archinstall.disk_layouts()}", level=logging.DEBUG) - - -if archinstall.arguments.get('skip-mirror-check', False) is False and archinstall.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") - exit(1) - -if not archinstall.arguments.get('silent'): - ask_user_questions() - -config_output = ConfigurationOutput(archinstall.arguments) -if not archinstall.arguments.get('silent'): - config_output.show() -config_output.save() - -if archinstall.arguments.get('dry_run'): - exit(0) - -if not archinstall.arguments.get('silent'): - input(str(_('Press Enter to continue.'))) - -archinstall.configuration_sanity_check() -perform_filesystem_operations() -perform_installation(archinstall.storage.get('MOUNT_POINT', '/mnt')) diff --git a/examples/interactive_installation.py b/examples/interactive_installation.py new file mode 100644 index 00000000..a78b1712 --- /dev/null +++ b/examples/interactive_installation.py @@ -0,0 +1,220 @@ +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.default_profiles.applications.pipewire import PipewireProfile +from archinstall import disk +from archinstall import menu +from archinstall.lib.models import Bootloader, NetworkConfigurationHandler + +if TYPE_CHECKING: + _: Any + + +def ask_user_questions(): + global_menu = archinstall.GlobalMenu(data_store=archinstall.arguments) + + global_menu.enable('archinstall-language') + + global_menu.enable('keyboard-layout') + + # Set which region to download packages from during the installation + global_menu.enable('mirror-region') + + global_menu.enable('sys-language') + + global_menu.enable('sys-encoding') + + global_menu.enable('disk_config', mandatory=True) + + # Specify disk encryption options + global_menu.enable('disk_encryption') + + # Ask which boot-loader to use (will only ask if we're in UEFI mode, otherwise will default to GRUB) + global_menu.enable('bootloader') + + global_menu.enable('swap') + + # Get the hostname for the machine + global_menu.enable('hostname') + + # Ask for a root password (optional, but triggers requirement for super-user if skipped) + global_menu.enable('!root-password', mandatory=True) + + global_menu.enable('!users', mandatory=True) + + # Ask for archinstall-specific profiles_bck (such as desktop environments etc) + global_menu.enable('profile_config') + + # Ask about audio server selection if one is not already set + global_menu.enable('audio') + + # Ask for preferred kernel: + global_menu.enable('kernels') + + global_menu.enable('packages') + + if archinstall.arguments.get('advanced', False): + # Enable parallel downloads + global_menu.enable('parallel downloads') + + # Ask or Call the helper function that asks the user to optionally configure a network. + global_menu.enable('nic') + + global_menu.enable('timezone') + + global_menu.enable('ntp') + + global_menu.enable('additional-repositories') + + global_menu.enable('__separator__') + + global_menu.enable('save_config') + global_menu.enable('install') + global_menu.enable('abort') + + global_menu.run() + + +def perform_installation(mountpoint: Path): + """ + Performs the installation steps on a block device. + Only requirement is that the block devices are + formatted and setup prior to entering this function. + """ + log('Starting installation', level=logging.INFO) + disk_config: disk.DiskLayoutConfiguration = archinstall.arguments['disk_config'] + + # Retrieve list of additional repositories and set boolean values appropriately + enable_testing = 'testing' in archinstall.arguments.get('additional-repositories', []) + enable_multilib = 'multilib' in archinstall.arguments.get('additional-repositories', []) + + locale = f"{archinstall.arguments.get('sys-language', 'en_US')} {archinstall.arguments.get('sys-encoding', 'UTF-8').upper()}" + + disk_encryption: disk.DiskEncryption = archinstall.arguments.get('disk_encryption', None) + + with Installer( + mountpoint, + disk_config, + disk_encryption=disk_encryption, + kernels=archinstall.arguments.get('kernels', ['linux']) + ) as installation: + # Mount all the drives to the desired mountpoint + if disk_config.config_type != disk.DiskLayoutType.Pre_mount: + installation.mount_ordered_layout() + + installation.sanity_check() + + if disk_config.config_type != disk.DiskLayoutType.Pre_mount: + if disk_encryption and disk_encryption.encryption_type != disk.EncryptionType.NoEncryption: + # generate encryption key files for the mounted luks devices + installation.generate_key_files() + + if archinstall.arguments.get('ntp', False): + installation.activate_ntp() + + # 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 + + installation.minimal_installation( + testing=enable_testing, + multilib=enable_multilib, + hostname=archinstall.arguments.get('hostname', 'archlinux'), + locales=[locale] + ) + + if archinstall.arguments.get('mirror-region') is not None: + if archinstall.arguments.get("mirrors", None) is not None: + installation.set_mirrors(archinstall.arguments['mirror-region']) # Set the mirrors in the installation medium + + if archinstall.arguments.get('swap'): + installation.setup_swap('zram') + + if archinstall.arguments.get("bootloader") == Bootloader.Grub and archinstall.has_uefi(): + installation.add_additional_packages("grub") + + installation.add_bootloader(archinstall.arguments["bootloader"]) + + # If user selected to copy the current ISO network configuration + # Perform a copy of the config + network_config = archinstall.arguments.get('nic', None) + + if network_config: + handler = NetworkConfigurationHandler(network_config) + handler.config_installer(installation) + + if archinstall.arguments.get('packages', None) and archinstall.arguments.get('packages', None)[0] != '': + installation.add_additional_packages(archinstall.arguments.get('packages', None)) + + if users := archinstall.arguments.get('!users', None): + installation.create_users(users) + + if audio := archinstall.arguments.get('audio', None): + log(f'Installing audio server: {audio}', level=logging.INFO) + 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) + + if profile_config := archinstall.arguments.get('profile_config', None): + profile_handler.install_profile_config(installation, profile_config) + + if timezone := archinstall.arguments.get('timezone', None): + installation.set_timezone(timezone) + + if archinstall.arguments.get('ntp', False): + installation.activate_time_syncronization() + + if archinstall.accessibility_tools_in_use(): + installation.enable_espeakup() + + if (root_pw := archinstall.arguments.get('!root-password', None)) and len(root_pw): + installation.user_set_pw('root', root_pw) + + # This step must be after profile installs to allow profiles_bck to install language pre-requisits. + # After which, this step will set the language both for console and x11 if x11 was installed for instance. + installation.set_keyboard_language(archinstall.arguments['keyboard-layout']) + + if profile_config := archinstall.arguments.get('profile_config', None): + profile_config.profile.post_install(installation) + + # If the user provided a list of services to be enabled, pass the list to the enable_service function. + # Note that while it's called enable_service, it can actually take a list of services and iterate it. + if archinstall.arguments.get('services', None): + installation.enable_service(*archinstall.arguments['services']) + + # If the user provided custom commands to be run post-installation, execute them now. + if archinstall.arguments.get('custom-commands', None): + archinstall.run_custom_user_commands(archinstall.arguments['custom-commands'], installation) + + installation.genfstab() + + installation.log("For post-installation tips, see https://wiki.archlinux.org/index.php/Installation_guide#Post-installation", fg="yellow") + + if not archinstall.arguments.get('silent'): + prompt = str(_('Would you like to chroot into the newly created installation and perform post-installation configuration?')) + choice = menu.Menu(prompt, menu.Menu.yes_no(), default_option=menu.Menu.yes()).run() + if choice.value == menu.Menu.yes(): + try: + installation.drop_to_shell() + except: + pass + + archinstall.log(f"Disk states after installing: {disk.disk_layouts()}", level=logging.DEBUG) + + +ask_user_questions() + +fs_handler = disk.FilesystemHandler( + archinstall.arguments['disk_config'], + archinstall.arguments.get('disk_encryption', None) +) + +fs_handler.perform_filesystem_operations() + +perform_installation(archinstall.storage.get('MOUNT_POINT', Path('/mnt'))) diff --git a/examples/mac_address_installation.py b/examples/mac_address_installation.py new file mode 100644 index 00000000..0a1c5160 --- /dev/null +++ b/examples/mac_address_installation.py @@ -0,0 +1,18 @@ +import time + +import archinstall +from archinstall.lib.profile.profiles_handler import profile_handler + +for profile in 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}"') + + print('Starting install in:') + for i in range(10, 0, -1): + print(f'{i}...') + time.sleep(1) + + install_session = archinstall.storage['installation_session'] + profile.install(install_session) diff --git a/examples/minimal.py b/examples/minimal.py deleted file mode 100644 index 8b4c847f..00000000 --- a/examples/minimal.py +++ /dev/null @@ -1,75 +0,0 @@ -import archinstall - -# Select a harddrive and a disk password -from archinstall import User - -archinstall.log("Minimal only supports:") -archinstall.log(" * 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") - -archinstall.arguments['harddrive'] = archinstall.select_disk(archinstall.all_blockdevices()) - - -def install_on(mountpoint): - # We kick off the installer by telling it where the - with archinstall.Installer(mountpoint) as installation: - # Strap in the base system, add a boot loader and configure - # some other minor details as specified by this profile and user. - if installation.minimal_installation(): - installation.set_hostname('minimal-arch') - installation.add_bootloader() - - # Optionally enable networking: - if archinstall.arguments.get('network', None): - installation.copy_iso_network_config(enable_services=True) - - installation.add_additional_packages(['nano', 'wget', 'git']) - installation.install_profile('minimal') - - 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)") - - -if archinstall.arguments['harddrive']: - archinstall.arguments['harddrive'].keep_partitions = False - - print(f" ! Formatting {archinstall.arguments['harddrive']} in ", end='') - archinstall.do_countdown() - - # First, we configure the basic filesystem layout - with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs: - # We use the entire disk instead of setting up partitions on your own - if archinstall.arguments['harddrive'].keep_partitions is False: - fs.use_entire_disk(root_filesystem_type=archinstall.arguments.get('filesystem', 'btrfs')) - - boot = fs.find_partition('/boot') - root = fs.find_partition('/') - - boot.format('fat32') - - # We encrypt the root partition if we got a password to do so with, - # Otherwise we just skip straight to formatting and installation - if archinstall.arguments.get('!encryption-password', None): - root.encrypted = True - root.encrypt(password=archinstall.arguments.get('!encryption-password', None)) - - with archinstall.luks2(root, 'luksloop', archinstall.arguments.get('!encryption-password', None)) as unlocked_root: - unlocked_root.format(root.filesystem) - unlocked_root.mount('/mnt') - else: - root.format(root.filesystem) - root.mount('/mnt') - - boot.mount('/mnt/boot') - -install_on('/mnt') diff --git a/examples/minimal_installation.py b/examples/minimal_installation.py new file mode 100644 index 00000000..8bd6fd55 --- /dev/null +++ b/examples/minimal_installation.py @@ -0,0 +1,85 @@ +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.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 + + +def perform_installation(mountpoint: Path): + disk_config: disk.DiskLayoutConfiguration = archinstall.arguments['disk_config'] + disk_encryption: disk.DiskEncryption = archinstall.arguments.get('disk_encryption', None) + + with Installer( + mountpoint, + disk_config, + disk_encryption=disk_encryption, + kernels=archinstall.arguments.get('kernels', ['linux']) + ) as installation: + # Strap in the base system, add a boot loader and configure + # 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) + + # Optionally enable networking: + if archinstall.arguments.get('network', None): + installation.copy_iso_network_config(enable_services=True) + + installation.add_additional_packages(['nano', 'wget', 'git']) + + profile_config = ProfileConfiguration(MinimalProfile()) + profile_handler.install_profile_config(installation, profile_config) + + user = User('devel', 'devel', False) + installation.create_users(user) + + +def prompt_disk_layout(): + fs_type = None + 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) + + archinstall.arguments['disk_config'] = disk.DiskLayoutConfiguration( + config_type=disk.DiskLayoutType.Default, + device_modifications=[modifications] + ) + + +def parse_disk_encryption(): + if enc_password := archinstall.arguments.get('!encryption-password', None): + modification: List[disk.DeviceModification] = archinstall.arguments['disk_config'] + partitions: List[disk.PartitionModification] = [] + + # encrypt all partitions except the /boot + for mod in modification: + partitions += list(filter(lambda x: x.mountpoint != Path('/boot'), mod.partitions)) + + archinstall.arguments['disk_encryption'] = disk.DiskEncryption( + encryption_type=disk.EncryptionType.Partition, + encryption_password=enc_password, + partitions=partitions + ) + + +prompt_disk_layout() +parse_disk_encryption() + +fs_handler = disk.FilesystemHandler( + archinstall.arguments['disk_config'], + archinstall.arguments.get('disk_encryption', None) +) + +fs_handler.perform_filesystem_operations() + +mount_point = Path('/mnt') +perform_installation(mount_point) diff --git a/examples/only_hd.py b/examples/only_hd.py deleted file mode 100644 index e3d18f0a..00000000 --- a/examples/only_hd.py +++ /dev/null @@ -1,151 +0,0 @@ - -import logging -import os -import pathlib - -import archinstall -from archinstall import ConfigurationOutput - - -class OnlyHDMenu(archinstall.GlobalMenu): - def _setup_selection_menu_options(self): - super()._setup_selection_menu_options() - options_list = [] - mandatory_list = [] - options_list = ['harddrives', 'disk_layouts', 'disk_encryption','swap'] - mandatory_list = ['harddrives'] - options_list.extend(['save_config','install','abort']) - - for entry in self._menu_options: - if entry in options_list: - # for not lineal executions, only self.option(entry).set_enabled and set_mandatory are necessary - if entry in mandatory_list: - self.enable(entry,mandatory=True) - else: - self.enable(entry) - else: - self.option(entry).set_enabled(False) - self._update_install_text() - - def mandatory_lacking(self) -> [int, list]: - mandatory_fields = [] - mandatory_waiting = 0 - for field in self._menu_options: - option = self._menu_options[field] - if option.is_mandatory(): - if not option.has_selection(): - mandatory_waiting += 1 - mandatory_fields += [field,] - return mandatory_fields, mandatory_waiting - - def _missing_configs(self): - """ overloaded method """ - def check(s): - return self.option(s).has_selection() - - missing, missing_cnt = self.mandatory_lacking() - if check('harddrives'): - if not self.option('harddrives').is_empty() and not check('disk_layouts'): - missing_cnt += 1 - missing += ['disk_layout'] - return missing - -def ask_user_questions(): - """ - First, we'll ask the user for a bunch of user input. - Not until we're satisfied with what we want to install - will we continue with the actual installation steps. - """ - with OnlyHDMenu(data_store=archinstall.arguments) as menu: - # We select the execution language separated - menu.exec_option('archinstall-language') - menu.option('archinstall-language').set_enabled(False) - menu.run() - -def perform_disk_operations(): - """ - Issue a final warning before we continue with something un-revertable. - We mention the drive one last time, and count from 5 to 0. - """ - if archinstall.arguments.get('harddrives', None): - print(f" ! Formatting {archinstall.arguments['harddrives']} in ", end='') - archinstall.do_countdown() - """ - Setup the blockdevice, filesystem (and optionally encryption). - Once that's done, we'll hand over to perform_installation() - """ - mode = archinstall.GPT - if archinstall.has_uefi() is False: - mode = archinstall.MBR - - for drive in archinstall.arguments.get('harddrives', []): - if archinstall.arguments.get('disk_layouts', {}).get(drive.path): - with archinstall.Filesystem(drive, mode) as fs: - fs.load_layout(archinstall.arguments['disk_layouts'][drive.path]) - -def perform_installation(mountpoint): - """ - Performs the installation steps on a block device. - Only requirement is that the block devices are - formatted and setup prior to entering this function. - """ - with archinstall.Installer(mountpoint, kernels=None) as installation: - # Mount all the drives to the desired mountpoint - # This *can* be done outside of the installation, but the installer can deal with it. - if archinstall.arguments.get('disk_layouts'): - installation.mount_ordered_layout(archinstall.arguments['disk_layouts']) - - # Placing /boot check during installation because this will catch both re-use and wipe scenarios. - for partition in installation.partitions: - if partition.mountpoint == installation.target + '/boot': - if partition.size <= 0.25: # in GB - raise archinstall.DiskError(f"The selected /boot partition in use is not large enough to properly install a boot loader. Please resize it to at least 256MB and re-run the installation.") - # to generate a fstab directory holder. Avoids an error on exit and at the same time checks the procedure - target = pathlib.Path(f"{mountpoint}/etc/fstab") - if not target.parent.exists(): - 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: {archinstall.disk_layouts()}", level=logging.DEBUG) - -def log_execution_environment(): - # 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: {archinstall.disk_layouts()}", level=logging.DEBUG) - - -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_execution_environment() - -if not archinstall.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") - exit(1) - -if not archinstall.arguments.get('silent'): - ask_user_questions() - -config_output = ConfigurationOutput(archinstall.arguments) -if not archinstall.arguments.get('silent'): - config_output.show() -config_output.save() - -if archinstall.arguments.get('dry_run'): - exit(0) -if not archinstall.arguments.get('silent'): - input('Press Enter to continue.') - -perform_disk_operations() -perform_installation(archinstall.storage.get('MOUNT_POINT', '/mnt')) diff --git a/examples/only_hd_installation.py b/examples/only_hd_installation.py new file mode 100644 index 00000000..2fc74bf0 --- /dev/null +++ b/examples/only_hd_installation.py @@ -0,0 +1,63 @@ +import logging +from pathlib import Path + +import archinstall +from archinstall import Installer +from archinstall.lib import disk + + +def ask_user_questions(): + global_menu = archinstall.GlobalMenu(data_store=archinstall.arguments) + + global_menu.enable('archinstall-language') + + global_menu.enable('disk_config', mandatory=True) + global_menu.enable('disk_encryption') + global_menu.enable('swap') + + global_menu.enable('save_config') + global_menu.enable('install') + global_menu.enable('abort') + + global_menu.run() + + +def perform_installation(mountpoint: Path): + """ + Performs the installation steps on a block device. + Only requirement is that the block devices are + formatted and setup prior to entering this function. + """ + disk_config: disk.DiskLayoutConfiguration = archinstall.arguments['disk_config'] + disk_encryption: disk.DiskEncryption = archinstall.arguments.get('disk_encryption', None) + + with Installer( + mountpoint, + disk_config, + disk_encryption=disk_encryption, + kernels=archinstall.arguments.get('kernels', ['linux']) + ) as installation: + # Mount all the drives to the desired mountpoint + # This *can* be done outside of the installation, but the installer can deal with it. + if archinstall.arguments.get('disk_config'): + installation.mount_ordered_layout() + + # to generate a fstab directory holder. Avoids an error on exit and at the same time checks the procedure + target = Path(f"{mountpoint}/etc/fstab") + if not target.parent.exists(): + 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) + + +ask_user_questions() + +fs_handler = disk.FilesystemHandler( + archinstall.arguments['disk_config'], + archinstall.arguments.get('disk_encryption', None) +) + +fs_handler.perform_filesystem_operations() + +perform_installation(archinstall.storage.get('MOUNT_POINT', Path('/mnt'))) diff --git a/examples/swiss.py b/examples/swiss.py deleted file mode 100644 index 442281de..00000000 --- a/examples/swiss.py +++ /dev/null @@ -1,526 +0,0 @@ -""" - -Script swiss (army knife) -Designed to make different workflows for the installation process. Which is controlled by the argument --mode -mode full guides the full process of installation -mode only_hd only proceeds to the creation of the disk infraestructure (partition, mount points, encryption) -mode only_os processes only the installation of Archlinux and software at --mountpoint (or /mnt/archinstall) -mode minimal (still not implemented) -mode lineal. Instead of a menu, shows a sequence of selection screens (eq. to the old mode for guided.py) - -When using the argument --advanced. an additional menu for several special parameters needed during installation appears - -This script respects the --dry_run argument - -""" -import logging -import os -import time -import pathlib -from typing import TYPE_CHECKING, Any - -import archinstall -from archinstall import ConfigurationOutput, NetworkConfigurationHandler, Menu - -if TYPE_CHECKING: - _: Any - -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) - -""" -particular routines to SetupMenu -TODO exec con return parameter -""" -def select_activate_NTP(): - prompt = "Would you like to use automatic time synchronization (NTP) with the default time servers? [Y/n]: " - choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes()).run() - if choice == Menu.yes(): - return True - else: - return False - - -def select_mode(): - return archinstall.generic_select(['full','only_hd','only_os','minimal','lineal'], - 'Select one execution mode', - default=archinstall.arguments.get('mode','full')) - - -""" -following functions will be at locale_helpers, so they will have to be called prefixed by archinstall -""" -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 """ - archinstall.storage['CMD_LOCALE'] = archinstall.storage.get('CMD_LOCALE_DEFAULT',{}) - -def unset_cmd_locale(): - """ archinstall will use the execution environment default """ - archinstall.storage['CMD_LOCALE'] = {} - -def set_cmd_locale(general :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: - archinstall.storage['CMD_LOCALE'] = {'LC_ALL':general} - else: - archinstall.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: - archinstall.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: - archinstall.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: - archinstall.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: - archinstall.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: - archinstall.log(f"{get_locale_mode_text('LC_MESSAGES')} {messages} is not installed. Defaulting to installation language",fg="yellow",level=logging.WARNING) - archinstall.storage['CMD_LOCALE'] = result - -def list_installed_locales() -> list[str]: - lista = [] - for line in archinstall.SysCommand('locale -a'): - lista.append(line.decode('UTF-8').strip()) - return lista - - -""" -end of locale helpers -""" - -def select_installed_locale(mode): - mode_text = get_locale_mode_text(mode) - if mode == 'LC_ALL': - texto = "Select the default execution locale \nIf none, you will be prompted for specific settings" - else: - texto = f"Select the {mode_text} ({mode}) execution locale \nIf none, you will get the installation default" - return archinstall.generic_select([None] + list_installed_locales(), - texto, - allow_empty_input=True, - default=archinstall.storage.get('CMD_LOCALE',{}).get(mode,'C')) - - -""" - _menus -""" - -class SetupMenu(archinstall.AbstractMenu): - def __init__(self,storage_area): - super().__init__(data_store=storage_area) - - def _setup_selection_menu_options(self): - self.set_option( - 'archinstall-language', - archinstall.Selector( - _('Archinstall language'), - lambda x: self._select_archinstall_language(x), - display_func=lambda x: x.display_name, - default=self.translation_handler.get_language_by_abbr('en'), - enabled=True - ) - ) - - self.set_option( - 'ntp', - archinstall.Selector( - 'Activate NTP', - lambda x: select_activate_NTP(), - default='Y', - enabled=True - ) - ) - - self.set_option( - 'mode', - archinstall.Selector( - 'Excution mode', - lambda x : select_mode(), - default='full', - enabled=True) - ) - - for item in ['LC_ALL','LC_CTYPE','LC_NUMERIC','LC_TIME','LC_MESSAGES','LC_COLLATE']: - self.set_option(item, - archinstall.Selector( - f'{get_locale_mode_text(item)} locale', - lambda x,item=item: select_installed_locale(item), # the parameter is needed for the lambda in the loop - enabled=True, - dependencies_not=['LC_ALL'] if item != 'LC_ALL' else [])) - self.option('LC_ALL').set_enabled(True) - self.set_option('continue', - archinstall.Selector( - 'Continue', - exec_func=lambda n,v: True, - enabled=True)) - - def exit_callback(self): - if self._data_store.get('ntp',False): - archinstall.log("Hardware time and other post-configuration steps might be required in order for NTP to work. For more information, please check the Arch wiki.", fg="yellow") - archinstall.SysCommand('timedatectl set-ntp true') - if self._data_store.get('mode',None): - archinstall.arguments['mode'] = self._data_store['mode'] - archinstall.log(f"Archinstall will execute under {archinstall.arguments['mode']} mode") - if self._data_store.get('LC_ALL',None): - archinstall.storage['CMD_LOCALE'] = {'LC_ALL':self._data_store['LC_ALL']} - else: - exec_locale = {} - for item in ['LC_COLLATE','LC_CTYPE','LC_MESSAGES','LC_NUMERIC','LC_TIME']: - if self._data_store.get(item,None): - exec_locale[item] = self._data_store[item] - archinstall.storage['CMD_LOCALE'] = exec_locale - archinstall.log(f"Archinstall will execute with {archinstall.storage.get('CMD_LOCALE',None)} locale") - -class MyMenu(archinstall.GlobalMenu): - def __init__(self,data_store=archinstall.arguments,mode='full'): - self._execution_mode = mode - super().__init__(data_store) - - def _setup_selection_menu_options(self): - super()._setup_selection_menu_options() - options_list = [] - mandatory_list = [] - if self._execution_mode in ('full','lineal'): - options_list = ['keyboard-layout', 'mirror-region', 'harddrives', 'disk_layouts', - 'disk_encryption','swap', 'bootloader', 'hostname', '!root-password', - '!users', 'profile', 'audio', 'kernels', 'packages','additional-repositories','nic', - 'timezone', 'ntp'] - if archinstall.arguments.get('advanced',False): - options_list.extend(['sys-language','sys-encoding']) - mandatory_list = ['harddrives','bootloader','hostname'] - elif self._execution_mode == 'only_hd': - options_list = ['harddrives', 'disk_layouts', 'disk_encryption','swap'] - mandatory_list = ['harddrives'] - elif self._execution_mode == 'only_os': - options_list = ['keyboard-layout', 'mirror-region','bootloader', 'hostname', - '!root-password', '!users', 'profile', 'audio', 'kernels', - 'packages', 'additional-repositories', 'nic', 'timezone', 'ntp'] - mandatory_list = ['hostname'] - if archinstall.arguments.get('advanced',False): - options_list.expand(['sys-language','sys-encoding']) - elif self._execution_mode == 'minimal': - pass - else: - archinstall.log(f"self._execution_mode {self._execution_mode} not supported") - exit(1) - if self._execution_mode != 'lineal': - options_list.extend(['save_config','install','abort']) - if not archinstall.arguments.get('advanced'): - options_list.append('archinstall-language') - - for entry in self._menu_options: - if entry in options_list: - # for not lineal executions, only self.option(entry).set_enabled and set_mandatory are necessary - if entry in mandatory_list: - self.enable(entry,mandatory=True) - else: - self.enable(entry) - else: - self.option(entry).set_enabled(False) - self._update_install_text() - - def post_callback(self,option=None,value=None): - self._update_install_text(self._execution_mode) - - def _missing_configs(self,mode='full'): - def check(s): - return self.option(s).has_selection() - - def has_superuser() -> bool: - users = self._menu_options['!users'].current_selection - return any([u.sudo for u in users]) - - _, missing = self.mandatory_overview() - if mode in ('full','only_os') and (not check('!root-password') and not has_superuser()): - missing += 1 - if mode in ('full', 'only_hd') and check('harddrives'): - if not self.option('harddrives').is_empty() and not check('disk_layouts'): - missing += 1 - return missing - - def _install_text(self,mode='full'): - missing = self._missing_configs(mode) - if missing > 0: - return f'Instalation ({missing} config(s) missing)' - return 'Install' - - def _update_install_text(self, mode='full'): - text = self._install_text(mode) - self.option('install').update_description(text) - - -""" -Installation general subroutines -""" - -def get_current_status(): - # 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: {archinstall.disk_layouts()}", level=logging.DEBUG) - -def ask_user_questions(mode): - """ - First, we'll ask the user for a bunch of user input. - Not until we're satisfied with what we want to install - will we continue with the actual installation steps. - """ - if archinstall.arguments.get('advanced',None): - # 3.9 syntax. former x = {**y,**z} or x.update(y) - set_cmd_locale(charset='es_ES.utf8',collate='es_ES.utf8') - setup_area = archinstall.storage.get('CMD_LOCALE',{}) | {} - with SetupMenu(setup_area) as setup: - if mode == 'lineal': - for entry in setup.list_enabled_options(): - if entry in ('continue','abort'): - continue - if not setup.option(entry).enabled: - continue - setup.exec_option(entry) - else: - setup.run() - archinstall.arguments['archinstall-language'] = setup_area.get('archinstall-language') - else: - archinstall.log("Hardware time and other post-configuration steps might be required in order for NTP to work. For more information, please check the Arch wiki.", fg="yellow") - archinstall.SysCommand('timedatectl set-ntp true') - - with MyMenu(data_store=archinstall.arguments,mode=mode) as global_menu: - - if mode == 'lineal': - for entry in global_menu.list_enabled_options(): - if entry in ('install','abort'): - continue - global_menu.exec_option(entry) - archinstall.arguments[entry] = global_menu.option(entry).get_selection() - else: - global_menu.set_option('install', - archinstall.Selector( - global_menu._install_text(mode), - exec_func=lambda n,v: True if global_menu._missing_configs(mode) == 0 else False, - enabled=True)) - - global_menu.run() - -def perform_filesystem_operations(): - """ - Issue a final warning before we continue with something un-revertable. - We mention the drive one last time, and count from 5 to 0. - """ - - if archinstall.arguments.get('harddrives', None): - print(f" ! Formatting {archinstall.arguments['harddrives']} in ", end='') - archinstall.do_countdown() - - """ - Setup the blockdevice, filesystem (and optionally encryption). - Once that's done, we'll hand over to perform_installation() - """ - - mode = archinstall.GPT - if archinstall.has_uefi() is False: - mode = archinstall.MBR - - for drive in archinstall.arguments.get('harddrives', []): - if archinstall.arguments.get('disk_layouts', {}).get(drive.path): - with archinstall.Filesystem(drive, mode) as fs: - fs.load_layout(archinstall.arguments['disk_layouts'][drive.path]) - -def disk_setup(installation): - # Mount all the drives to the desired mountpoint - # This *can* be done outside of the installation, but the installer can deal with it. - if archinstall.arguments.get('disk_layouts'): - installation.mount_ordered_layout(archinstall.arguments['disk_layouts']) - - # Placing /boot check during installation because this will catch both re-use and wipe scenarios. - for partition in installation.partitions: - if partition.mountpoint == installation.target + '/boot': - if partition.size < 0.19: # ~200 MiB in GiB - raise archinstall.DiskError( - f"The selected /boot partition in use is not large enough to properly install a boot loader. Please resize it to at least 200MiB and re-run the installation.") - -def os_setup(installation): - # if len(mirrors): - # Certain services might be running that affects the system during installation. - # Currently, only 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. - installation.log('Waiting for automatic mirror selection (reflector) to complete.', level=logging.INFO) - while archinstall.service_state('reflector') not in ('dead', 'failed'): - time.sleep(1) - # Set mirrors used by pacstrap (outside of installation) - if archinstall.arguments.get('mirror-region', None): - archinstall.use_mirrors(archinstall.arguments['mirror-region']) # Set the mirrors for the live medium - if installation.minimal_installation( - hostname=archinstall.arguments['hostname'], - locales=[f"{archinstall.arguments['sys-language']} {archinstall.arguments['sys-encoding'].upper()}"]): - if archinstall.arguments['mirror-region'].get("mirrors", None) is not None: - installation.set_mirrors( - archinstall.arguments['mirror-region']) # Set the mirrors in the installation medium - if archinstall.arguments["bootloader"] == "grub-install" and archinstall.has_uefi(): - installation.add_additional_packages("grub") - installation.add_bootloader(archinstall.arguments["bootloader"]) - if archinstall.arguments['swap']: - installation.setup_swap('zram') - - network_config = archinstall.arguments.get('nic', None) - - if network_config: - handler = NetworkConfigurationHandler(network_config) - handler.config_installer(installation) - - if archinstall.arguments.get('audio', None) is not None: - installation.log(f"This audio server will be used: {archinstall.arguments.get('audio', None)}",level=logging.INFO) - if archinstall.arguments.get('audio', None) == 'pipewire': - archinstall.Application(installation, 'pipewire').install() - elif archinstall.arguments.get('audio', None) == 'pulseaudio': - print('Installing pulseaudio ...') - installation.add_additional_packages("pulseaudio") - else: - installation.log("No audio server will be installed.", level=logging.INFO) - - if archinstall.arguments.get('packages', None) and archinstall.arguments.get('packages', None)[0] != '': - installation.add_additional_packages(archinstall.arguments.get('packages', None)) - - if archinstall.arguments.get('profile', None): - installation.install_profile(archinstall.arguments.get('profile', None)) - - if users := archinstall.arguments.get('!users', None): - installation.create_users(users) - - if timezone := archinstall.arguments.get('timezone', None): - installation.set_timezone(timezone) - - if archinstall.arguments.get('ntp', False): - installation.activate_time_syncronization() - - if archinstall.accessibility_tools_in_use(): - installation.enable_espeakup() - - if (root_pw := archinstall.arguments.get('!root-password', None)) and len(root_pw): - installation.user_set_pw('root', root_pw) - - # This step must be after profile installs to allow profiles to install language pre-requisits. - # After which, this step will set the language both for console and x11 if x11 was installed for instance. - installation.set_keyboard_language(archinstall.arguments['keyboard-layout']) - - if archinstall.arguments['profile'] and archinstall.arguments['profile'].has_post_install(): - with archinstall.arguments['profile'].load_instructions( - namespace=f"{archinstall.arguments['profile'].namespace}.py") as imported: - if not imported._post_install(): - archinstall.log(' * Profile\'s post configuration requirements was not fulfilled.', fg='red') - exit(1) - - # If the user provided a list of services to be enabled, pass the list to the enable_service function. - # Note that while it's called enable_service, it can actually take a list of services and iterate it. - if archinstall.arguments.get('services', None): - installation.enable_service(*archinstall.arguments['services']) - - # If the user provided custom commands to be run post-installation, execute them now. - if archinstall.arguments.get('custom-commands', None): - archinstall.run_custom_user_commands(archinstall.arguments['custom-commands'], installation) - - -def perform_installation(mountpoint, mode): - """ - Performs the installation steps on a block device. - Only requirement is that the block devices are - formatted and setup prior to entering this function. - """ - with archinstall.Installer(mountpoint, kernels=archinstall.arguments.get('kernels', ['linux'])) as installation: - if mode in ('full','only_hd'): - disk_setup(installation) - if mode == 'only_hd': - target = pathlib.Path(f"{mountpoint}/etc/fstab") - if not target.parent.exists(): - target.parent.mkdir(parents=True) - - if mode in ('full','only_os'): - os_setup(installation) - installation.log("For post-installation tips, see https://wiki.archlinux.org/index.php/Installation_guide#Post-installation", fg="yellow") - if not archinstall.arguments.get('silent'): - prompt = 'Would you like to chroot into the newly created installation and perform post-installation configuration?' - choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes()).run() - if choice == Menu.yes(): - try: - installation.drop_to_shell() - except: - pass - - # For support reasons, we'll log the disk layout post installation (crash or no crash) - archinstall.log(f"Disk states after installing: {archinstall.disk_layouts()}", level=logging.DEBUG) - - -if not archinstall.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") - exit(1) - -mode = archinstall.arguments.get('mode', 'full').lower() -if not archinstall.arguments.get('silent'): - ask_user_questions(mode) - -config_output = ConfigurationOutput(archinstall.arguments) -if not archinstall.arguments.get('silent'): - config_output.show() -config_output.save() - -if archinstall.arguments.get('dry_run'): - exit(0) -if not archinstall.arguments.get('silent'): - input('Press Enter to continue.') - -if mode in ('full','only_hd'): - perform_filesystem_operations() -perform_installation(archinstall.storage.get('MOUNT_POINT', '/mnt'), mode) diff --git a/examples/unattended.py b/examples/unattended.py deleted file mode 100644 index f1ed4c94..00000000 --- a/examples/unattended.py +++ /dev/null @@ -1,21 +0,0 @@ -import time - -import archinstall - -archinstall.storage['UPSTREAM_URL'] = 'https://archlinux.life/profiles' -archinstall.storage['PROFILE_DB'] = 'index.json' - -for name, info in archinstall.list_profiles().items(): - # 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). - if info['tailored']: - print(f'Found a tailored profile for this machine called: "{name}".') - print('Starting install in:') - for i in range(10, 0, -1): - print(f'{i}...') - time.sleep(1) - - profile = archinstall.Profile(None, info['path']) - profile.install() - break -- cgit v1.2.3-70-g09d2