index : archinstall32 | |
Archlinux32 installer | gitolite user |
summaryrefslogtreecommitdiff |
-rw-r--r-- | archinstall/lib/profiles.py | 7 | ||||
-rw-r--r-- | archinstall/lib/user_interaction.py | 219 | ||||
-rw-r--r-- | examples/guided.py | 6 | ||||
-rw-r--r-- | profiles/desktop.py | 3 | ||||
-rw-r--r-- | profiles/i3.py | 3 | ||||
-rw-r--r-- | profiles/xorg.py | 36 |
diff --git a/archinstall/lib/profiles.py b/archinstall/lib/profiles.py index 21ec5f6f..95962fd6 100644 --- a/archinstall/lib/profiles.py +++ b/archinstall/lib/profiles.py @@ -15,7 +15,7 @@ def grab_url_data(path): response = urllib.request.urlopen(safe_path, context=ssl_context) return response.read() -def list_profiles(filter_irrelevant_macs=True, subpath=''): +def list_profiles(filter_irrelevant_macs=True, subpath='', filter_top_level_profiles=False): # TODO: Grab from github page as well, not just local static files if filter_irrelevant_macs: local_macs = list_interfaces() @@ -63,6 +63,11 @@ def list_profiles(filter_irrelevant_macs=True, subpath=''): cache[profile[:-3]] = {'path' : os.path.join(storage["UPSTREAM_URL"]+subpath, profile), 'description' : profile_list[profile], 'tailored' : tailored} + if filter_top_level_profiles: + for profile in list(cache.keys()): + if Profile(None, profile).is_top_level_profile() is False: + del(cache[profile]) + return cache class Script(): diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index dfc3553b..484205fb 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -1,5 +1,5 @@ import getpass, pathlib, os, shutil, re -import sys, time, signal +import sys, time, signal, ipaddress from .exceptions import * from .profiles import Profile from .locale_helpers import search_keyboard_layout @@ -154,27 +154,56 @@ def ask_to_configure_network(): # Optionally configure one network interface. #while 1: # {MAC: Ifname} - interfaces = {'ISO-CONFIG' : 'Copy ISO network configuration to installation','NetworkManager':'Use NetworkManager to control and manage your internet connection', **list_interfaces()} + interfaces = { + 'ISO-CONFIG' : 'Copy ISO network configuration to installation', + 'NetworkManager':'Use NetworkManager to control and manage your internet connection', + **list_interfaces() + } - nic = generic_select(interfaces.values(), "Select one network interface to configure (leave blank to skip): ") + nic = generic_select(interfaces, "Select one network interface to configure (leave blank to skip): ") if nic and nic != 'Copy ISO network configuration to installation': if nic == 'Use NetworkManager to control and manage your internet connection': return {'nic': nic,'NetworkManager':True} - mode = generic_select(['DHCP (auto detect)', 'IP (static)'], f"Select which mode to configure for {nic}: ") - if mode == 'IP (static)': + + # Current workaround: + # For selecting modes without entering text within brackets, + # printing out this part separate from options, passed in + # `generic_select` + modes = ['DHCP (auto detect)', 'IP (static)'] + for index, mode in enumerate(modes): + print(f"{index}: {mode}") + + mode = generic_select(['DHCP', 'IP'], f"Select which mode to configure for {nic} or leave blank for DHCP: ", + options_output=False) + if mode == 'IP': while 1: ip = input(f"Enter the IP and subnet for {nic} (example: 192.168.0.5/24): ").strip() - if ip: + # Implemented new check for correct IP/subnet input + try: + ipaddress.ip_interface(ip) break - else: + except ValueError: log( "You need to enter a valid IP in IP-config mode.", level=LOG_LEVELS.Warning, fg='red' ) - if not len(gateway := input('Enter your gateway (router) IP address or leave blank for none: ').strip()): - gateway = None + # Implemented new check for correct gateway IP address + while 1: + gateway = input('Enter your gateway (router) IP address or leave blank for none: ').strip() + try: + if len(gateway) == 0: + gateway = None + else: + ipaddress.ip_address(gateway) + break + except ValueError: + log( + "You need to enter a valid gateway (router) IP address.", + level=LOG_LEVELS.Warning, + fg='red' + ) dns = None if len(dns_input := input('Enter your DNS servers (space separated, blank for none): ').strip()): @@ -190,12 +219,13 @@ def ask_to_configure_network(): def ask_for_disk_layout(): options = { - 'keep-existing' : 'Keep existing partition layout and select which ones to use where.', - 'format-all' : 'Format entire drive and setup a basic partition scheme.', - 'abort' : 'Abort the installation.' + 'keep-existing' : 'Keep existing partition layout and select which ones to use where', + 'format-all' : 'Format entire drive and setup a basic partition scheme', + 'abort' : 'Abort the installation' } - value = generic_select(options.values(), "Found partitions on the selected drive, (select by number) what you want to do: ") + value = generic_select(options, "Found partitions on the selected drive, (select by number) what you want to do: ", + allow_empty_input=False, sort=True) return next((key for key, val in options.items() if val == value), None) def ask_for_main_filesystem_format(): @@ -206,40 +236,72 @@ def ask_for_main_filesystem_format(): 'f2fs' : 'f2fs' } - value = generic_select(options.values(), "Select which filesystem your main partition should use (by number or name): ") + value = generic_select(options, "Select which filesystem your main partition should use (by number or name): ", + allow_empty_input=False) return next((key for key, val in options.items() if val == value), None) -def generic_select(options, input_text="Select one of the above by index or absolute value: ", sort=True): +def generic_select(options, input_text="Select one of the above by index or absolute value: ", allow_empty_input=True, options_output=True, sort=False): """ A generic select function that does not output anything other than the options and their indexes. As an example: generic_select(["first", "second", "third option"]) - 1: first - 2: second - 3: third option + 0: first + 1: second + 2: third option + + When the user has entered the option correctly, + this function returns an item from list, a string, or None """ - if type(options) == dict: options = list(options) - if sort: options = sorted(list(options)) - if len(options) <= 0: raise RequirementError('generic_select() requires at least one option to operate.') - - for index, option in enumerate(options): - print(f"{index}: {option}") - - selected_option = input(input_text) - if len(selected_option.strip()) <= 0: - return None - elif selected_option.isdigit(): - selected_option = int(selected_option) - if selected_option > len(options): - raise RequirementError(f'Selected option "{selected_option}" is out of range') - selected_option = options[selected_option] - elif selected_option in options: - pass # We gave a correct absolute value - else: - raise RequirementError(f'Selected option "{selected_option}" does not exist in available options: {options}') + # Checking if options are different from `list` or `dict` + if type(options) not in [list, dict]: + log(f" * Generic select doesn't support ({type(options)}) as type of options * ", fg='red') + log(" * If problem persists, please create an issue on https://github.com/archlinux/archinstall/issues * ", fg='yellow') + raise RequirementError("generic_select() requires list or dictionary as options.") + # To allow only `list` and `dict`, converting values of options here. + # Therefore, now we can only provide the dictionary itself + if type(options) == dict: options = list(options.values()) + if sort: options = sorted(options) # As we pass only list and dict (converted to list), we can skip converting to list + if len(options) == 0: + log(f" * Generic select didn't find any options to choose from * ", fg='red') + log(" * If problem persists, please create an issue on https://github.com/archlinux/archinstall/issues * ", fg='yellow') + raise RequirementError('generic_select() requires at least one option to proceed.') + + # Added ability to disable the output of options items, + # if another function displays something different from this + if options_output: + for index, option in enumerate(options): + print(f"{index}: {option}") + + # The new changes introduce a single while loop for all inputs processed by this function + # Now the try...except...else block handles validation for invalid input from the user + while True: + try: + selected_option = input(input_text) + if len(selected_option.strip()) == 0: + # `allow_empty_input` parameter handles return of None on empty input, if necessary + # Otherwise raise `RequirementError` + if allow_empty_input: + return None + raise RequirementError('Please select an option to continue') + # Replaced `isdigit` with` isnumeric` to discard all negative numbers + elif selected_option.isnumeric(): + selected_option = int(selected_option) + if selected_option >= len(options): + raise RequirementError(f'Selected option "{selected_option}" is out of range') + selected_option = options[selected_option] + elif selected_option in options: + break # We gave a correct absolute value + else: + raise RequirementError(f'Selected option "{selected_option}" does not exist in available options') + except RequirementError as err: + log(f" * {err} * ", fg='red') + continue + else: + break + return selected_option def select_disk(dict_o_disks): @@ -257,18 +319,11 @@ def select_disk(dict_o_disks): if len(drives) >= 1: for index, drive in enumerate(drives): print(f"{index}: {drive} ({dict_o_disks[drive]['size'], dict_o_disks[drive].device, dict_o_disks[drive]['label']})") - drive = input('Select one of the above disks (by number or full path) or write /mnt to skip partitioning: ') - if drive.strip() == '/mnt': - return None - elif drive.isdigit(): - drive = int(drive) - if drive >= len(drives): - raise DiskError(f'Selected option "{drive}" is out of range') - drive = dict_o_disks[drives[drive]] - elif drive in dict_o_disks: - drive = dict_o_disks[drive] - else: - raise DiskError(f'Selected drive does not exist: "{drive}"') + drive = generic_select(drives, 'Select one of the above disks (by number or full path) or leave blank to skip partitioning: ', + options_output=False) + if not drive: + return drive + drive = dict_o_disks[drive] return drive raise DiskError('select_disk() requires a non-empty dictionary of disks to select from.') @@ -293,21 +348,13 @@ def select_profile(options): print(' -- The above list is a set of pre-programmed profiles. --') print(' -- They might make it easier to install things like desktop environments. --') print(' -- (Leave blank and hit enter to skip this step and continue) --') - selected_profile = input('Enter a pre-programmed profile name if you want to install one: ') - - if len(selected_profile.strip()) <= 0: - return None - - if selected_profile.isdigit() and (pos := int(selected_profile)) <= len(profiles)-1: - selected_profile = profiles[pos] - elif selected_profile in options: - selected_profile = options[options.index(selected_profile)] - else: - RequirementError("Selected profile does not exist.") - - return Profile(None, selected_profile) - raise RequirementError("Selecting profiles require a least one profile to be given as an option.") + selected_profile = generic_select(profiles, 'Enter a pre-programmed profile name if you want to install one: ', + options_output=False) + if selected_profile: + return Profile(None, selected_profile) + else: + raise RequirementError("Selecting profiles require a least one profile to be given as an option.") def select_language(options, show_only_country_codes=True): """ @@ -334,12 +381,18 @@ def select_language(options, show_only_country_codes=True): for index, language in enumerate(languages): print(f"{index}: {language}") - print(' -- You can enter ? or help to search for more languages, or skip to use US layout --') - selected_language = input('Select one of the above keyboard languages (by number or full name): ') + # Current workaround for passing `generic_select`, + # if these values are provided as input + languages.extend(['?', 'help']) + languages_length = len(languages) + + print(f' -- You can enter ? ({languages_length - 2}) or help ({languages_length - 1}) to search for more languages, or skip to use US layout --') + selected_language = generic_select(languages, 'Select one of the above keyboard languages (by number or full name): ', + options_output=False) - if len(selected_language.strip()) == 0: + if not selected_language: return DEFAULT_KEYBOARD_LANGUAGE - elif selected_language.lower() in ('?', 'help'): + elif selected_language in ('?', 'help'): while True: filter_string = input('Search for layout containing (example: "sv-"): ') new_options = list(search_keyboard_layout(filter_string)) @@ -350,18 +403,13 @@ def select_language(options, show_only_country_codes=True): return select_language(new_options, show_only_country_codes=False) - elif selected_language.isdigit() and (pos := int(selected_language)) <= len(languages)-1: - selected_language = languages[pos] - return selected_language # I'm leaving "options" on purpose here. # Since languages possibly contains a filtered version of # all possible language layouts, and we might want to write # for instance sv-latin1 (if we know that exists) without having to # go through the search step. - elif selected_language in languages: - return selected_language - else: - raise RequirementError("Selected language does not exist.") + + return selected_language raise RequirementError("Selecting languages require a least one language to be given as an option.") @@ -388,23 +436,18 @@ def select_mirror_regions(mirrors, show_top_mirrors=True): print_large_list(regions, margin_bottom=4) print(' -- You can skip this step by leaving the option blank --') - selected_mirror = input('Select one of the above regions to download packages from (by number or full name): ') - if len(selected_mirror.strip()) == 0: + selected_mirror = generic_select(regions, 'Select one of the above regions to download packages from (by number or full name): ', + options_output=False) + if not selected_mirror: # Returning back empty options which can be both used to # do "if x:" logic as well as do `x.get('mirror', {}).get('sub', None)` chaining return {} - elif selected_mirror.isdigit() and int(selected_mirror) <= len(regions)-1: - # I'm leaving "mirrors" on purpose here. - # Since region possibly contains a known region of - # all possible regions, and we might want to write - # for instance Sweden (if we know that exists) without having to - # go through the search step. - region = regions[int(selected_mirror)] - selected_mirrors[region] = mirrors[region] - elif selected_mirror in mirrors: - selected_mirrors[selected_mirror] = mirrors[selected_mirror] - else: - raise RequirementError("Selected region does not exist.") + # I'm leaving "mirrors" on purpose here. + # Since region possibly contains a known region of + # all possible regions, and we might want to write + # for instance Sweden (if we know that exists) without having to + # go through the search step. + selected_mirrors[selected_mirror] = mirrors[selected_mirror] return selected_mirrors diff --git a/examples/guided.py b/examples/guided.py index 7bf088ca..ef447abb 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -76,7 +76,9 @@ def ask_user_questions(): archinstall.log(f" ** The root would be a simple / and the boot partition /boot (as all paths are relative inside the installation). **") while True: # Select a partition - partition = archinstall.generic_select(partition_mountpoints.keys(), + # If we provide keys as options, it's better to convert them to list and sort before passing + mountpoints_list = sorted(list(partition_mountpoints.keys())) + partition = archinstall.generic_select(mountpoints_list, "Select a partition by number that you want to set a mount-point for (leave blank when done): ") if not partition: break @@ -162,7 +164,7 @@ def ask_user_questions(): # Ask for archinstall-specific profiles (such as desktop environments etc) if not archinstall.arguments.get('profile', None): - archinstall.arguments['profile'] = archinstall.select_profile(filter(lambda profile: (Profile(None, profile).is_top_level_profile()), archinstall.list_profiles())) + archinstall.arguments['profile'] = archinstall.select_profile(archinstall.list_profiles(filter_top_level_profiles=True)) else: archinstall.arguments['profile'] = archinstall.list_profiles()[archinstall.arguments['profile']] diff --git a/profiles/desktop.py b/profiles/desktop.py index 846182e2..2aea6d30 100644 --- a/profiles/desktop.py +++ b/profiles/desktop.py @@ -17,7 +17,8 @@ def _prep_function(*args, **kwargs): """ supported_desktops = ['gnome', 'kde', 'awesome', 'sway', 'cinnamon', 'xfce4', 'lxqt', 'i3', 'budgie', 'mate'] - desktop = archinstall.generic_select(supported_desktops, 'Select your desired desktop environment: ') + desktop = archinstall.generic_select(supported_desktops, 'Select your desired desktop environment: ', + allow_empty_input=False, sort=True) # Temporarily store the selected desktop profile # in a session-safe location, since this module will get reloaded diff --git a/profiles/i3.py b/profiles/i3.py index 67028b2d..b82c03d6 100644 --- a/profiles/i3.py +++ b/profiles/i3.py @@ -17,7 +17,8 @@ def _prep_function(*args, **kwargs): """ supported_configurations = ['i3-wm', 'i3-gaps'] - desktop = archinstall.generic_select(supported_configurations, 'Select your desired configuration: ') + desktop = archinstall.generic_select(supported_configurations, 'Select your desired configuration: ', + allow_empty_input=False, sort=True) # Temporarily store the selected desktop profile # in a session-safe location, since this module will get reloaded diff --git a/profiles/xorg.py b/profiles/xorg.py index e905d533..130f3ed0 100644 --- a/profiles/xorg.py +++ b/profiles/xorg.py @@ -1,6 +1,7 @@ # A system with "xorg" installed -import archinstall, os +import os +from archinstall import generic_select, sys_command, RequirementError is_top_level_profile = True @@ -39,7 +40,7 @@ def select_driver(options): print(' -- The above list are supported graphic card drivers. --') print(' -- You need to select (and read about) which one you need. --') - lspci = archinstall.sys_command(f'/usr/bin/lspci') + lspci = sys_command(f'/usr/bin/lspci') for line in lspci.trace_log.split(b'\r\n'): if b' vga ' in line.lower(): if b'nvidia' in line.lower(): @@ -47,7 +48,8 @@ def select_driver(options): elif b'amd' in line.lower(): print(' ** AMD card detected, suggested driver: AMD / ATI **') - selected_driver = input('Select your graphics card driver: ') + selected_driver = generic_select(drivers, 'Select your graphics card driver: ', + allow_empty_input=False, options_output=False) initial_option = selected_driver # Disabled search for now, only a few profiles exist anyway @@ -57,35 +59,21 @@ def select_driver(options): # filter_string = input('Search for layout containing (example: "sv-"): ') # new_options = search_keyboard_layout(filter_string) # return select_language(new_options) - if selected_driver.isdigit() and (pos := int(selected_driver)) <= len(drivers)-1: - selected_driver = options[drivers[pos]] - elif selected_driver in options: - selected_driver = options[options.index(selected_driver)] - elif len(selected_driver) == 0: - raise archinstall.RequirementError("At least one graphics driver is needed to support a graphical environment. Please restart the installer and try again.") - else: - raise archinstall.RequirementError("Selected driver does not exist.") + + selected_driver = options[selected_driver] if type(selected_driver) == dict: driver_options = sorted(list(selected_driver)) - for index, driver_package_group in enumerate(driver_options): - print(f"{index}: {driver_package_group}") - selected_driver_package_group = input(f'Which driver-type do you want for {initial_option}: ') - if selected_driver_package_group.isdigit() and (pos := int(selected_driver_package_group)) <= len(driver_options)-1: - selected_driver_package_group = selected_driver[driver_options[pos]] - elif selected_driver_package_group in selected_driver: - selected_driver_package_group = selected_driver[selected_driver.index(selected_driver_package_group)] - elif len(selected_driver_package_group) == 0: - raise archinstall.RequirementError(f"At least one driver package is required for a graphical environment using {selected_driver}. Please restart the installer and try again.") - else: - raise archinstall.RequirementError(f"Selected driver-type does not exist for {initial_option}.") + driver_package_group = generic_select(driver_options, f'Which driver-type do you want for {initial_option}: ', + allow_empty_input=False) + driver_package_group = selected_driver[driver_package_group] - return selected_driver_package_group + return driver_package_group return selected_driver - raise archinstall.RequirementError("Selecting drivers require a least one profile to be given as an option.") + raise RequirementError("Selecting drivers require a least one profile to be given as an option.") def _prep_function(*args, **kwargs): """ |