index : archinstall32 | |
Archlinux32 installer | gitolite user |
summaryrefslogtreecommitdiff |
-rw-r--r-- | archinstall/__init__.py | 33 | ||||
-rw-r--r-- | archinstall/__main__.py | 30 | ||||
-rw-r--r-- | archinstall/lib/disk.py | 13 | ||||
-rw-r--r-- | archinstall/lib/hardware.py | 37 | ||||
-rw-r--r-- | archinstall/lib/installer.py | 20 | ||||
-rw-r--r-- | archinstall/lib/luks.py | 2 | ||||
-rw-r--r-- | archinstall/lib/networking.py | 4 | ||||
-rw-r--r-- | archinstall/lib/output.py | 6 | ||||
-rw-r--r-- | archinstall/lib/profiles.py | 14 | ||||
-rw-r--r-- | archinstall/lib/storage.py | 2 | ||||
-rw-r--r-- | archinstall/lib/user_interaction.py | 25 |
diff --git a/archinstall/__init__.py b/archinstall/__init__.py index 91cf17be..8a49bef6 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -14,6 +14,8 @@ from .lib.output import * from .lib.storage import * from .lib.hardware import * +__version__ = "2.1.3" + ## Basic version of arg.parse() supporting: ## --key=value ## --boolean @@ -27,4 +29,33 @@ for arg in sys.argv[1:]: key, val = arg[2:], True arguments[key] = val else: - positionals.append(arg)
\ No newline at end of file + positionals.append(arg) + + +# TODO: Learn the dark arts of argparse... +# (I summon thee dark spawn of cPython) + +def run_as_a_module(): + """ + Since we're running this as a 'python -m archinstall' module OR + a nuitka3 compiled version of the project. + This function and the file __main__ acts as a entry point. + """ + + # Add another path for finding profiles, so that list_profiles() in Script() can find guided.py, unattended.py etc. + storage['PROFILE_PATH'].append(os.path.abspath(f'{os.path.dirname(__file__)}/examples')) + + if len(sys.argv) == 1: + sys.argv.append('guided') + + try: + script = Script(sys.argv[1]) + except ProfileNotFound as err: + print(f"Couldn't find file: {err}") + sys.exit(1) + + os.chdir(os.path.abspath(os.path.dirname(__file__))) + + # Remove the example directory from the PROFILE_PATH, to avoid guided.py etc shows up in user input questions. + storage['PROFILE_PATH'].pop() + script.execute() diff --git a/archinstall/__main__.py b/archinstall/__main__.py index 63c2f715..86ed0108 100644 --- a/archinstall/__main__.py +++ b/archinstall/__main__.py @@ -2,33 +2,5 @@ import archinstall import sys import os -# TODO: Learn the dark arts of argparse... -# (I summon thee dark spawn of cPython) - -def run_as_a_module(): - """ - Since we're running this as a 'python -m archinstall' module OR - a nuitka3 compiled version of the project. - This function and the file __main__ acts as a entry point. - """ - - # Add another path for finding profiles, so that list_profiles() in Script() can find guided.py, unattended.py etc. - archinstall.storage['PROFILE_PATH'].append(os.path.abspath(f'{os.path.dirname(__file__)}/examples')) - - if len(sys.argv) == 1: - sys.argv.append('guided') - - try: - script = archinstall.Script(sys.argv[1]) - except archinstall.ProfileNotFound as err: - print(f"Couldn't find file: {err}") - sys.exit(1) - - os.chdir(os.path.abspath(os.path.dirname(__file__))) - - # Remove the example directory from the PROFILE_PATH, to avoid guided.py etc shows up in user input questions. - archinstall.storage['PROFILE_PATH'].pop() - script.execute() - if __name__ == '__main__': - run_as_a_module() + archinstall.run_as_a_module() diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 4ea9f214..e2f4d76e 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -25,11 +25,12 @@ class BlockDevice(): self.path = path self.info = info + self.keep_partitions = True self.part_cache = OrderedDict() - # TODO: Currently disk encryption is a BIT missleading. + # TODO: Currently disk encryption is a BIT misleading. # It's actually partition-encryption, but for future-proofing this # I'm placing the encryption password on a BlockDevice level. - self.encryption_passwoed = None + self.encryption_password = None def __repr__(self, *args, **kwargs): return f"BlockDevice({self.device})" @@ -285,10 +286,10 @@ class Partition(): handle = luks2(self, None, None) return handle.encrypt(self, *args, **kwargs) - def format(self, filesystem=None, path=None, allow_formatting=None, log_formating=True): + def format(self, filesystem=None, path=None, allow_formatting=None, log_formatting=True): """ Format can be given an overriding path, for instance /dev/null to test - the formating functionality and in essence the support for the given filesystem. + the formatting functionality and in essence the support for the given filesystem. """ if filesystem is None: filesystem = self.filesystem @@ -306,7 +307,7 @@ class Partition(): if not allow_formatting: raise PermissionError(f"{self} is not formatable either because instance is locked ({self.allow_formatting}) or a blocking flag was given ({allow_formatting})") - if log_formating: + if log_formatting: log(f'Formatting {path} -> {filesystem}', level=LOG_LEVELS.Info) if filesystem == 'btrfs': @@ -401,7 +402,7 @@ class Partition(): 2. UnknownFilesystemFormat that indicates that we don't support the given filesystem type """ try: - self.format(self.filesystem, '/dev/null', log_formating=False, allow_formatting=True) + self.format(self.filesystem, '/dev/null', log_formatting=False, allow_formatting=True) except SysCallError: pass # We supported it, but /dev/null is not formatable as expected so the mkfs call exited with an error code except UnknownFilesystemFormat as err: diff --git a/archinstall/lib/hardware.py b/archinstall/lib/hardware.py index 10f3970f..3da333de 100644 --- a/archinstall/lib/hardware.py +++ b/archinstall/lib/hardware.py @@ -1,19 +1,23 @@ -import os, subprocess +import os, subprocess, json from .general import sys_command from .networking import list_interfaces, enrichIfaceTypes - -def hasWifi(): +from typing import Optional +def hasWifi()->bool: return 'WIRELESS' in enrichIfaceTypes(list_interfaces().values()).values() -def hasAMDCPU(): +def hasAMDCPU()->bool: if subprocess.check_output("lscpu | grep AMD", shell=True).strip().decode(): return True return False +def hasIntelCPU()->bool: + if subprocess.check_output("lscpu | grep Intel", shell=True).strip().decode(): + return True + return False -def hasUEFI(): +def hasUEFI()->bool: return os.path.isdir('/sys/firmware/efi') -def graphicsDevices(): +def graphicsDevices()->dict: cards = {} for line in sys_command(f"lspci"): if b' VGA ' in line: @@ -21,13 +25,28 @@ def graphicsDevices(): cards[identifier.strip().lower().decode('UTF-8')] = line return cards -def hasNvidiaGraphics(): +def hasNvidiaGraphics()->bool: return any('nvidia' in x for x in graphicsDevices()) -def hasAmdGraphics(): +def hasAmdGraphics()->bool: return any('amd' in x for x in graphicsDevices()) -def hasIntelGraphics(): +def hasIntelGraphics()->bool: return any('intel' in x for x in graphicsDevices()) + +def cpuVendor()-> Optional[str]: + cpu_info = json.loads(subprocess.check_output("lscpu -J", shell=True).decode('utf-8'))['lscpu'] + for info in cpu_info: + if info.get('field',None): + if info.get('field',None) == "Vendor ID:": + return info.get('data',None) + +def isVM() -> bool: + try: + subprocess.check_call(["systemd-detect-virt"]) # systemd-detect-virt issues a none 0 exit code if it is not on a virtual machine + return True + except: + return False + # TODO: Add more identifiers diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index a99bc944..e02bbf75 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -79,7 +79,7 @@ class Installer(): return self def __exit__(self, *args, **kwargs): - # b''.join(sys_command(f'sync')) # No need to, since the underlaying fs() object will call sync. + # b''.join(sys_command(f'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]: @@ -91,7 +91,7 @@ class Installer(): # We avoid printing /mnt/<log path> because that might confuse people if they note it down # and then reboot, and a identical log file will be found in the ISO medium anyway. print(f"[!] A log file has been created here: {os.path.join(storage['LOG_PATH'], storage['LOG_FILE'])}") - print(f" Please submit this issue (and file) to https://github.com/Torxed/archinstall/issues") + print(f" Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues") raise args[1] self.genfstab() @@ -104,8 +104,10 @@ class Installer(): self.log('Some required steps were not successfully installed/configured before leaving the installer:', bg='black', fg='red', level=LOG_LEVELS.Warning) for step in missing_steps: self.log(f' - {step}', bg='black', fg='red', level=LOG_LEVELS.Warning) - self.log(f"Detailed error logs can be found at: {log_path}", level=LOG_LEVELS.Warning) - self.log(f"Submit this zip file as an issue to https://github.com/Torxed/archinstall/issues", level=LOG_LEVELS.Warning) + + self.log(f"Detailed error logs can be found at: {storage['LOG_PATH']}", level=LOG_LEVELS.Warning) + self.log(f"Submit this zip file as an issue to https://github.com/archlinux/archinstall/issues", level=LOG_LEVELS.Warning) + self.sync_log_to_install_medium() return False @@ -155,7 +157,7 @@ class Installer(): fstab_fh.write(fstab) if not os.path.isfile(f'{self.mountpoint}/etc/fstab'): - raise RequirementError(f'Could not generate fstab, strapping in packages most likely failed (disk out of space?)\n{o}') + raise RequirementError(f'Could not generate fstab, strapping in packages most likely failed (disk out of space?)\n{fstab}') return True @@ -274,7 +276,7 @@ class Installer(): return True def minimal_installation(self): - ## Add nessecary packages if encrypting the drive + ## Add necessary packages if encrypting the drive ## (encrypted partitions default to btrfs for now, so we need btrfs-progs) ## TODO: Perhaps this should be living in the function which dictates ## the partitioning. Leaving here for now. @@ -312,14 +314,14 @@ class Installer(): mkinit.write('MODULES=(btrfs)\n') mkinit.write('BINARIES=(/usr/bin/btrfs)\n') mkinit.write('FILES=()\n') - mkinit.write('HOOKS=(base udev autodetect modconf block encrypt filesystems keymap keyboard fsck)\n') + mkinit.write('HOOKS=(base udev autodetect keyboard keymap modconf block encrypt filesystems fsck)\n') sys_command(f'/usr/bin/arch-chroot {self.mountpoint} mkinitcpio -p linux') elif self.partition.encrypted: with open(f'{self.mountpoint}/etc/mkinitcpio.conf', 'w') as mkinit: mkinit.write('MODULES=()\n') mkinit.write('BINARIES=()\n') mkinit.write('FILES=()\n') - mkinit.write('HOOKS=(base udev autodetect modconf block encrypt filesystems keymap keyboard fsck)\n') + mkinit.write('HOOKS=(base udev autodetect keyboard keymap modconf block encrypt filesystems fsck)\n') sys_command(f'/usr/bin/arch-chroot {self.mountpoint} mkinitcpio -p linux') self.helper_flags['base'] = True @@ -419,7 +421,7 @@ class Installer(): # The tricky thing with doing the import archinstall.session instead is that # profiles might be run from a different chroot, and there's no way we can # guarantee file-path safety when accessing the installer object that way. - # Doing the __builtins__ replacement, ensures that the global vriable "installation" + # Doing the __builtins__ replacement, ensures that the global variable "installation" # is always kept up to date. It's considered a nasty hack - but it's a safe way # of ensuring 100% accuracy of archinstall session variables. __builtins__['installation'] = self diff --git a/archinstall/lib/luks.py b/archinstall/lib/luks.py index 62067ec1..a1d42196 100644 --- a/archinstall/lib/luks.py +++ b/archinstall/lib/luks.py @@ -43,8 +43,6 @@ class luks2(): return True def encrypt(self, partition, password=None, key_size=512, hash_type='sha512', iter_time=10000, key_file=None): - # TODO: We should be able to integrate this into the main log some how. - # Perhaps post-mortem? if not self.partition.allow_formatting: raise DiskError(f'Could not encrypt volume {self.partition} due to it having a formatting lock.') diff --git a/archinstall/lib/networking.py b/archinstall/lib/networking.py index 882bcff3..2dc8be9b 100644 --- a/archinstall/lib/networking.py +++ b/archinstall/lib/networking.py @@ -56,7 +56,7 @@ def wirelessScan(interface): storage['_WIFI'][interface]['scanning'] = True -# TOOD: Full WiFi experience might get evolved in the future, pausing for now 2021-01-25 +# TODO: Full WiFi experience might get evolved in the future, pausing for now 2021-01-25 def getWirelessNetworks(interface): # TODO: Make this oneliner pritter to check if the interface is scanning or not. if not '_WIFI' in storage or interface not in storage['_WIFI'] or storage['_WIFI'][interface].get('scanning', False) is False: @@ -65,4 +65,4 @@ def getWirelessNetworks(interface): time.sleep(5) for line in sys_command(f"iwctl station {interface} get-networks"): - print(line)
\ No newline at end of file + print(line) diff --git a/archinstall/lib/output.py b/archinstall/lib/output.py index 537fb695..6b184b4b 100644 --- a/archinstall/lib/output.py +++ b/archinstall/lib/output.py @@ -6,7 +6,7 @@ from pathlib import Path from .storage import storage # TODO: use logging's built in levels instead. -# Altough logging is threaded and I wish to avoid that. +# Although logging is threaded and I wish to avoid that. # It's more Pythonistic or w/e you want to call it. class LOG_LEVELS: Critical = 0b001 @@ -88,7 +88,7 @@ def log(*args, **kwargs): # Attempt to colorize the output if supported # Insert default colors and override with **kwargs if supports_color(): - kwargs = {'bg' : 'black', 'fg': 'white', **kwargs} + kwargs = {'fg': 'white', **kwargs} string = stylize_output(string, **kwargs) # If a logfile is defined in storage, @@ -130,4 +130,4 @@ def log(*args, **kwargs): # We use sys.stdout.write()+flush() instead of print() to try and # fix issue #94 sys.stdout.write(f"{string}\n") - sys.stdout.flush()
\ No newline at end of file + sys.stdout.flush() diff --git a/archinstall/lib/profiles.py b/archinstall/lib/profiles.py index 70c21a67..39411553 100644 --- a/archinstall/lib/profiles.py +++ b/archinstall/lib/profiles.py @@ -112,11 +112,11 @@ class Script(): if f"{self.profile}" in self.examples: return self.localize_path(self.examples[self.profile]['path']) - # TODO: Redundant, the below block shouldnt be needed as profiles are stripped of their .py, but just in case for now: + # TODO: Redundant, the below block shouldn't be needed as profiles are stripped of their .py, but just in case for now: elif f"{self.profile}.py" in self.examples: return self.localize_path(self.examples[f"{self.profile}.py"]['path']) - # Path was not found in any known examples, check if it's an abolute path + # Path was not found in any known examples, check if it's an absolute path if os.path.isfile(self.profile): return self.profile @@ -156,7 +156,7 @@ class Profile(Script): def install(self): # Before installing, revert any temporary changes to the namespace. - # This ensures that the namespace during installation is the original initation namespace. + # This ensures that the namespace during installation is the original initiation namespace. # (For instance awesome instead of aweosme.py or app-awesome.py) self.namespace = self.original_namespace return self.execute() @@ -231,11 +231,11 @@ class Application(Profile): if f"{self.profile}" in self.examples: return self.localize_path(self.examples[self.profile]['path']) - # TODO: Redundant, the below block shouldnt be needed as profiles are stripped of their .py, but just in case for now: + # TODO: Redundant, the below block shouldn't be needed as profiles are stripped of their .py, but just in case for now: elif f"{self.profile}.py" in self.examples: return self.localize_path(self.examples[f"{self.profile}.py"]['path']) - # Path was not found in any known examples, check if it's an abolute path + # Path was not found in any known examples, check if it's an absolute path if os.path.isfile(self.profile): return os.path.basename(self.profile) @@ -247,7 +247,7 @@ class Application(Profile): def install(self): # Before installing, revert any temporary changes to the namespace. - # This ensures that the namespace during installation is the original initation namespace. + # This ensures that the namespace during installation is the original initiation namespace. # (For instance awesome instead of aweosme.py or app-awesome.py) self.namespace = self.original_namespace - return self.execute()
\ No newline at end of file + return self.execute() diff --git a/archinstall/lib/storage.py b/archinstall/lib/storage.py index 9bda017d..43d088bb 100644 --- a/archinstall/lib/storage.py +++ b/archinstall/lib/storage.py @@ -14,7 +14,7 @@ storage = { os.path.join(os.path.dirname(os.path.abspath(__file__)), 'profiles'), #os.path.abspath(f'{os.path.dirname(__file__)}/../examples') ], - 'UPSTREAM_URL' : 'https://raw.githubusercontent.com/Torxed/archinstall/master/profiles', + 'UPSTREAM_URL' : 'https://raw.githubusercontent.com/archlinux/archinstall/master/profiles', 'PROFILE_DB' : None, # Used in cases when listing profiles is desired, not mandatory for direct profile grabing. 'LOG_PATH' : '/var/log/archinstall', 'LOG_FILE' : 'install.log', diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 4ab9b9a9..c5ff17ca 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -1,4 +1,4 @@ -import getpass, pathlib, os, shutil +import getpass, pathlib, os, shutil, re from .exceptions import * from .profiles import Profile from .locale_helpers import search_keyboard_layout @@ -18,11 +18,21 @@ def get_terminal_width(): def get_longest_option(options): return max([len(x) for x in options]) +def check_for_correct_username(username): + if re.match(r'^[a-z_][a-z0-9_-]*\$?$', username) and len(username) <= 32: + return True + log( + "The username you entered is invalid. Try again", + level=LOG_LEVELS.Warning, + fg='red' + ) + return False + def get_password(prompt="Enter a password: "): while (passwd := getpass.getpass(prompt)): passwd_verification = getpass.getpass(prompt='And one more time for verification: ') if passwd != passwd_verification: - log(' * Passwords did not match * ', bg='black', fg='red') + log(' * Passwords did not match * ', fg='red') continue if len(passwd.strip()) <= 0: @@ -50,14 +60,16 @@ def print_large_list(options, padding=5, margin_bottom=0, separator=': '): def ask_for_superuser_account(prompt='Create a required super-user with sudo privileges: ', forced=False): while 1: new_user = input(prompt).strip(' ') - + if not new_user and forced: # TODO: make this text more generic? # It's only used to create the first sudo user when root is disabled in guided.py - log(' * Since root is disabled, you need to create a least one (super) user!', bg='black', fg='red') + log(' * Since root is disabled, you need to create a least one (super) user!', fg='red') continue elif not new_user and not forced: raise UserError("No superuser was created.") + elif not check_for_correct_username(new_user): + continue password = get_password(prompt=f'Password for user {new_user}: ') return {new_user: {"!password" : password}} @@ -70,6 +82,8 @@ def ask_for_additional_users(prompt='Any additional users to install (leave blan new_user = input(prompt).strip(' ') if not new_user: break + if not check_for_correct_username(new_user): + continue password = get_password(prompt=f'Password for user {new_user}: ') if input("Should this user be a sudo (super) user (y/N): ").strip(' ').lower() in ('y', 'yes'): @@ -118,7 +132,6 @@ def ask_to_configure_network(): log( "You need to enter a valid IP in IP-config mode.", level=LOG_LEVELS.Warning, - bg='black', fg='red' ) @@ -161,7 +174,7 @@ def ask_for_main_filesystem_format(): def generic_select(options, input_text="Select one of the above by index or absolute value: ", sort=True): """ A generic select function that does not output anything - other than the options and their indexs. As an example: + other than the options and their indexes. As an example: generic_select(["first", "second", "third option"]) 1: first |