From a618ebd6111de0cab9c0f04449cf1a8483156615 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 12:34:39 +0100 Subject: [PATCH 001/100] Adding a check to see if the disk contains a parition table already. has_partitions() TBI --- examples/guided.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/examples/guided.py b/examples/guided.py index f0620b05aa..f55047d94f 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -104,6 +104,15 @@ def perform_installation(device, boot_partition, language, mirrors): archinstall.storage['_guided']['disk_encryption'] = True break archinstall.storage['_guided']['harddrive'] = harddrive +print(harddrive) +if archinstall.has_partitions(harddrive): + archinstall.log(f" ! {harddrive} contains existing partitions", fg='red') + if (option := input('Do you wish to keep existing partition setup or format the entire disk? (k/f): ')).lower() in ('k', 'keep'): + print("We're keeping it!") + else: + print('Formatting woop woop!') +exit(1) + # Ask for a hostname hostname = input('Desired hostname for the installation: ') From fa2270a11b833928186727812a3a3f82d18ef5ce Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 12:52:12 +0100 Subject: [PATCH 002/100] Created has_partitions() on BlockDevice's --- archinstall/lib/disk.py | 3 +++ examples/guided.py | 19 ++++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index caf5c4e1bf..1d78d1274c 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -100,6 +100,9 @@ def partition(self): all_partitions = self.partitions return [all_partitions[k] for k in all_partitions] + def has_partitions(self): + return len(self.partitions) + class Partition(): def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None, encrypted=False): diff --git a/examples/guided.py b/examples/guided.py index f55047d94f..1e879316ea 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -96,16 +96,9 @@ def perform_installation(device, boot_partition, language, mirrors): # Ask which harddrive/block-device we will install to harddrive = archinstall.select_disk(archinstall.all_disks()) -while (disk_password := getpass.getpass(prompt='Enter disk encryption password (leave blank for no encryption): ')): - disk_password_verification = getpass.getpass(prompt='And one more time for verification: ') - if disk_password != disk_password_verification: - archinstall.log(' * Passwords did not match * ', bg='black', fg='red') - continue - archinstall.storage['_guided']['disk_encryption'] = True - break archinstall.storage['_guided']['harddrive'] = harddrive -print(harddrive) -if archinstall.has_partitions(harddrive): + +if harddrive.has_partitions(harddrive): archinstall.log(f" ! {harddrive} contains existing partitions", fg='red') if (option := input('Do you wish to keep existing partition setup or format the entire disk? (k/f): ')).lower() in ('k', 'keep'): print("We're keeping it!") @@ -113,6 +106,14 @@ def perform_installation(device, boot_partition, language, mirrors): print('Formatting woop woop!') exit(1) +while (disk_password := getpass.getpass(prompt='Enter disk encryption password (leave blank for no encryption): ')): + disk_password_verification = getpass.getpass(prompt='And one more time for verification: ') + if disk_password != disk_password_verification: + archinstall.log(' * Passwords did not match * ', bg='black', fg='red') + continue + archinstall.storage['_guided']['disk_encryption'] = True + break + # Ask for a hostname hostname = input('Desired hostname for the installation: ') From e06603f0e143360ccb932ce39df37eac22969f90 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 12:53:01 +0100 Subject: [PATCH 003/100] has_partitions() doesn't take any parameters, old reminant of prototype code. --- examples/guided.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/guided.py b/examples/guided.py index 1e879316ea..5268042dda 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -98,7 +98,7 @@ def perform_installation(device, boot_partition, language, mirrors): harddrive = archinstall.select_disk(archinstall.all_disks()) archinstall.storage['_guided']['harddrive'] = harddrive -if harddrive.has_partitions(harddrive): +if harddrive.has_partitions(): archinstall.log(f" ! {harddrive} contains existing partitions", fg='red') if (option := input('Do you wish to keep existing partition setup or format the entire disk? (k/f): ')).lower() in ('k', 'keep'): print("We're keeping it!") From 9c7f689dd620739062b8248bc9798a522fc4c7e2 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 13:29:33 +0100 Subject: [PATCH 004/100] Can't unmount during startup if we want to support existing partitioning schemes. --- examples/guided.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/examples/guided.py b/examples/guided.py index 5268042dda..a529832811 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -75,11 +75,6 @@ def perform_installation(device, boot_partition, language, mirrors): if 'root_pw' in archinstall.storage['_guided_hidden'] and archinstall.storage['_guided_hidden']['root_pw']: installation.user_set_pw('root', archinstall.storage['_guided_hidden']['root_pw']) -# Unmount and close previous runs (in case the installer is restarted) -archinstall.sys_command(f'umount -R /mnt', suppress_errors=True) -archinstall.sys_command(f'cryptsetup close /dev/mapper/luksloop', suppress_errors=True) - - """ First, we'll ask the user for a bunch of user input. Not until we're satisfied with what we want to install From 53cdb607bc9204b69d3f1aac42baea5ddcb94c12 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 13:36:30 +0100 Subject: [PATCH 005/100] Added the ability to check if a harddrive has any mountpoint related to a given parameter. --- archinstall/lib/disk.py | 6 ++++++ archinstall/lib/storage.py | 3 ++- examples/guided.py | 6 +++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 1d78d1274c..aafb07a8ae 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -103,6 +103,12 @@ def partition(self): def has_partitions(self): return len(self.partitions) + def has_mount_point(self, mountpoint): + for partition in self.partitions: + if self.partitions[partition].mountpoint == mountpoint: + return True + return False + class Partition(): def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None, encrypted=False): diff --git a/archinstall/lib/storage.py b/archinstall/lib/storage.py index e881700fab..9bda017d07 100644 --- a/archinstall/lib/storage.py +++ b/archinstall/lib/storage.py @@ -17,5 +17,6 @@ 'UPSTREAM_URL' : 'https://raw.githubusercontent.com/Torxed/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' + 'LOG_FILE' : 'install.log', + 'MOUNT_POINT' : '/mnt' } diff --git a/examples/guided.py b/examples/guided.py index a529832811..b6c6dd45a1 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -96,7 +96,11 @@ def perform_installation(device, boot_partition, language, mirrors): if harddrive.has_partitions(): archinstall.log(f" ! {harddrive} contains existing partitions", fg='red') if (option := input('Do you wish to keep existing partition setup or format the entire disk? (k/f): ')).lower() in ('k', 'keep'): - print("We're keeping it!") + # If we want to keep the existing partitioning table + # Make sure that it's the selected drive mounted under /mnt + # That way, we can rely on genfstab and some manual post-installation steps. + if harddrive.has_mount_point(archinstall.storage['MOUNT_POINT']) is False: + raise archinstall.DiskException(f"The selected drive {harddrive} is not pre-mounted to {archinstall.storage['MOUNT_POINT']}. This is required when keeping a existing partitioning scheme.") else: print('Formatting woop woop!') exit(1) From 7123da1c564bd88f9c3101c6e5e1773d0d2cdf62 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 13:39:39 +0100 Subject: [PATCH 006/100] Threw the wrong exception --- examples/guided.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/guided.py b/examples/guided.py index b6c6dd45a1..ac1e7fe280 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -100,7 +100,7 @@ def perform_installation(device, boot_partition, language, mirrors): # Make sure that it's the selected drive mounted under /mnt # That way, we can rely on genfstab and some manual post-installation steps. if harddrive.has_mount_point(archinstall.storage['MOUNT_POINT']) is False: - raise archinstall.DiskException(f"The selected drive {harddrive} is not pre-mounted to {archinstall.storage['MOUNT_POINT']}. This is required when keeping a existing partitioning scheme.") + raise archinstall.DiskError(f"The selected drive {harddrive} is not pre-mounted to {archinstall.storage['MOUNT_POINT']}. This is required when keeping a existing partitioning scheme.") else: print('Formatting woop woop!') exit(1) From 826119bb998789a8db8c2b96cfd2cd443b41bbae Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 15:00:34 +0100 Subject: [PATCH 007/100] Added partition info on Partition() creation. This will help detect potential mountpoints as well as filesystem types if any --- archinstall/lib/disk.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index aafb07a8ae..588054a005 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -116,11 +116,21 @@ def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None part_id = os.path.basename(path) self.path = path self.part_id = part_id - self.mountpoint = mountpoint + self.mountpoint = None self.filesystem = filesystem # TODO: Autodetect if we're reusing a partition self.size = size # TODO: Refresh? self.encrypted = encrypted + if mountpoint: + self.mount(mountpoint) + + if not self.mountpoint: + # As a last step, check if we've mounted outside of the script + partition_info = get_partition_info(self.path) + self.mountpoint = partition_info['target'] + if partition_info['fstype'] != self.filesystem and filesystem: + raise DiskError(f"{self} was given a filesystem format, but a existing format was detected: {partition_info['fstype']}") + def __repr__(self, *args, **kwargs): if self.encrypted: return f'Partition(path={self.path}, real_device={self.real_device}, fs={self.filesystem}, mounted={self.mountpoint})' @@ -311,3 +321,13 @@ def harddrive(size=None, model=None, fuzzy=False): continue return collection[drive] + +def get_partition_info(path): + output = b''.join(sys_command(f'/usr/bin/findmnt --json {path}')) + output = output.decode('UTF-8') + output = json.loads(output) + if 'filesystems' in output: + if len(output['filesystems']) > 1: + raise DiskError(f"Path '{path}' contains multiple mountpoints: {output['filesystems']}") + + return output['filesystems'][0] \ No newline at end of file From d527e215fce1da04e07556d2dbe787e16dba550b Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 15:03:32 +0100 Subject: [PATCH 008/100] Added some log outputs for existing drives. --- examples/guided.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/guided.py b/examples/guided.py index ac1e7fe280..d6029f46fc 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -101,6 +101,10 @@ def perform_installation(device, boot_partition, language, mirrors): # That way, we can rely on genfstab and some manual post-installation steps. if harddrive.has_mount_point(archinstall.storage['MOUNT_POINT']) is False: raise archinstall.DiskError(f"The selected drive {harddrive} is not pre-mounted to {archinstall.storage['MOUNT_POINT']}. This is required when keeping a existing partitioning scheme.") + + archinstall.log('Using existing partition table:') + for partition in harddrive: + archinstall.log(f" {partition}") else: print('Formatting woop woop!') exit(1) From 2262cd6196dd487b3dfc4524de1eea9b747db0c0 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 15:05:23 +0100 Subject: [PATCH 009/100] Made BlockDevices() iterable, iterting over each partition --- archinstall/lib/disk.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 588054a005..ffd3e04478 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -22,6 +22,10 @@ def __init__(self, path, info): def __repr__(self, *args, **kwargs): return f"BlockDevice({self.device})" + def __iter__(self): + for partition in self.partitions: + yield partition + def __getitem__(self, key, *args, **kwargs): if key not in self.info: raise KeyError(f'{self} does not contain information: "{key}"') From 976709525816a8a47c30ae02abba9062499ad8b2 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 15:05:57 +0100 Subject: [PATCH 010/100] Yielding actual partitions and not just the partition number :) --- archinstall/lib/disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index ffd3e04478..7ffc866c32 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -24,7 +24,7 @@ def __repr__(self, *args, **kwargs): def __iter__(self): for partition in self.partitions: - yield partition + yield self.partitions[partition] def __getitem__(self, key, *args, **kwargs): if key not in self.info: From 759b7787439e49c03cc330cf0500977c0b83696e Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 15:09:40 +0100 Subject: [PATCH 011/100] Added some more failsafe's to the Partition() object. --- archinstall/lib/disk.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 7ffc866c32..93d2461368 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -120,20 +120,23 @@ def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None part_id = os.path.basename(path) self.path = path self.part_id = part_id - self.mountpoint = None - self.filesystem = filesystem # TODO: Autodetect if we're reusing a partition + self.mountpoint = mountpoint + self.filesystem = filesystem self.size = size # TODO: Refresh? self.encrypted = encrypted if mountpoint: self.mount(mountpoint) - if not self.mountpoint: - # As a last step, check if we've mounted outside of the script - partition_info = get_partition_info(self.path) - self.mountpoint = partition_info['target'] - if partition_info['fstype'] != self.filesystem and filesystem: - raise DiskError(f"{self} was given a filesystem format, but a existing format was detected: {partition_info['fstype']}") + partition_info = get_partition_info(self.path) + + if self.mountpoint != partition_info['target'] and mountpoint: + raise DiskError(f"{self} was given a mountpoint but the actual mountpoint differs: {partition_info['target']}") + if partition_info['fstype'] != self.filesystem and filesystem: + raise DiskError(f"{self} was given a filesystem format, but a existing format was detected: {partition_info['fstype']}") + + self.mountpoint = partition_info['target'] + self.filesystem = partition_info['fstype'] def __repr__(self, *args, **kwargs): if self.encrypted: From a5a6ff4d31aa23dfaceca3973166a24dba4ccd0f Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 15:25:34 +0100 Subject: [PATCH 012/100] Added an early check for filesystem compatability. Since we need to handle unique packages etc for certain filesystem formats. This early check can be caught and ignored if the programmer/user wants to override the check and continue anyway. But the default should be to stop all execution to not install a half-working system. --- archinstall/lib/disk.py | 18 +++++++++++++++--- archinstall/lib/exceptions.py | 2 ++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 93d2461368..e23e354c22 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -138,14 +138,26 @@ def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None self.mountpoint = partition_info['target'] self.filesystem = partition_info['fstype'] + # We perform a dummy format on /dev/null with the given filesystem-type + # in order to determain if we support it or not. + try: + self.format(self.filesystem, '/dev/null') + except DiskError: + pass # We supported it, but /dev/null is not formatable as expected + except UnknownFilesystemFormat as err: + raise err + def __repr__(self, *args, **kwargs): if self.encrypted: return f'Partition(path={self.path}, real_device={self.real_device}, fs={self.filesystem}, mounted={self.mountpoint})' else: return f'Partition(path={self.path}, fs={self.filesystem}, mounted={self.mountpoint})' - def format(self, filesystem): - log(f'Formatting {self} -> {filesystem}', level=LOG_LEVELS.Info) + def format(self, filesystem, path=None): + if not path: + path = self.path + + log(f'Formatting {path} -> {filesystem}', level=LOG_LEVELS.Info) if filesystem == 'btrfs': o = b''.join(sys_command(f'/usr/bin/mkfs.btrfs -f {self.path}')) if b'UUID' not in o: @@ -169,7 +181,7 @@ def format(self, filesystem): raise DiskError(f'Could not format {self.path} with {filesystem} because: {b"".join(handle)}') self.filesystem = 'f2fs' else: - raise DiskError(f'Fileformat {filesystem} is not yet implemented.') + raise UnknownFilesystemFormat(f'Fileformat '{filesystem}' is not yet implemented.') return True def find_parent_of(self, data, name, parent=None): diff --git a/archinstall/lib/exceptions.py b/archinstall/lib/exceptions.py index 84e6a766de..a7864a237b 100644 --- a/archinstall/lib/exceptions.py +++ b/archinstall/lib/exceptions.py @@ -2,6 +2,8 @@ class RequirementError(BaseException): pass class DiskError(BaseException): pass +class UnknownFilesystemFormat(BaseException): + pass class ProfileError(BaseException): pass class SysCallError(BaseException): From a88a41abfb519582933e2e4b04a279f0887b91d1 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 15:26:04 +0100 Subject: [PATCH 013/100] Quotation issue --- archinstall/lib/disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index e23e354c22..2ac198cbcd 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -181,7 +181,7 @@ def format(self, filesystem, path=None): raise DiskError(f'Could not format {self.path} with {filesystem} because: {b"".join(handle)}') self.filesystem = 'f2fs' else: - raise UnknownFilesystemFormat(f'Fileformat '{filesystem}' is not yet implemented.') + raise UnknownFilesystemFormat(f"Fileformat '{filesystem}' is not yet implemented.") return True def find_parent_of(self, data, name, parent=None): From 15aa16c4256079de97d530df280def65e92fd30b Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 15:27:12 +0100 Subject: [PATCH 014/100] Renamed fat32 to vfat to work more seamlessly with findmnt and other tools that report fat32 as vfat --- archinstall/lib/disk.py | 6 +++--- examples/guided.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 2ac198cbcd..b416780cf5 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -163,11 +163,11 @@ def format(self, filesystem, path=None): if b'UUID' not in o: raise DiskError(f'Could not format {self.path} with {filesystem} because: {o}') self.filesystem = 'btrfs' - elif filesystem == 'fat32': + elif filesystem == 'vfat': o = b''.join(sys_command(f'/usr/bin/mkfs.vfat -F32 {self.path}')) if (b'mkfs.fat' not in o and b'mkfs.vfat' not in o) or b'command not found' in o: raise DiskError(f'Could not format {self.path} with {filesystem} because: {o}') - self.filesystem = 'fat32' + self.filesystem = 'vfat' elif filesystem == 'ext4': if (handle := sys_command(f'/usr/bin/mkfs.ext4 -F {self.path}')).exit_code != 0: raise DiskError(f'Could not format {self.path} with {filesystem} because: {b"".join(handle)}') @@ -259,7 +259,7 @@ def parted(self, string:str): return self.raw_parted(string).exit_code def use_entire_disk(self, prep_mode=None): - self.add_partition('primary', start='1MiB', end='513MiB', format='fat32') + self.add_partition('primary', start='1MiB', end='513MiB', format='vfat') self.set_name(0, 'EFI') self.set(0, 'boot on') self.set(0, 'esp on') # TODO: Redundant, as in GPT mode it's an alias for "boot on"? https://www.gnu.org/software/parted/manual/html_node/set.html diff --git a/examples/guided.py b/examples/guided.py index d6029f46fc..4f7da99feb 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -285,7 +285,7 @@ def perform_installation(device, boot_partition, language, mirrors): if harddrive.partition[1].size == '512M': raise OSError('Trying to encrypt the boot partition for petes sake..') - harddrive.partition[0].format('fat32') + harddrive.partition[0].format('vfat') if disk_password: # First encrypt and unlock, then format the desired partition inside the encrypted part. From 3dcf8ced6ceed483d1eb6a8212ae5fd79d14ad6c Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 15:28:26 +0100 Subject: [PATCH 015/100] Fixed correct variable usage for path when formatting, enabling temporary override. --- archinstall/lib/disk.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index b416780cf5..c721c90ec0 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -159,26 +159,26 @@ def format(self, filesystem, path=None): log(f'Formatting {path} -> {filesystem}', level=LOG_LEVELS.Info) if filesystem == 'btrfs': - o = b''.join(sys_command(f'/usr/bin/mkfs.btrfs -f {self.path}')) + o = b''.join(sys_command(f'/usr/bin/mkfs.btrfs -f {path}')) if b'UUID' not in o: - raise DiskError(f'Could not format {self.path} with {filesystem} because: {o}') + raise DiskError(f'Could not format {path} with {filesystem} because: {o}') self.filesystem = 'btrfs' elif filesystem == 'vfat': - o = b''.join(sys_command(f'/usr/bin/mkfs.vfat -F32 {self.path}')) + o = b''.join(sys_command(f'/usr/bin/mkfs.vfat -F32 {path}')) if (b'mkfs.fat' not in o and b'mkfs.vfat' not in o) or b'command not found' in o: - raise DiskError(f'Could not format {self.path} with {filesystem} because: {o}') + raise DiskError(f'Could not format {path} with {filesystem} because: {o}') self.filesystem = 'vfat' elif filesystem == 'ext4': - if (handle := sys_command(f'/usr/bin/mkfs.ext4 -F {self.path}')).exit_code != 0: - raise DiskError(f'Could not format {self.path} with {filesystem} because: {b"".join(handle)}') + if (handle := sys_command(f'/usr/bin/mkfs.ext4 -F {path}')).exit_code != 0: + raise DiskError(f'Could not format {path} with {filesystem} because: {b"".join(handle)}') self.filesystem = 'ext4' elif filesystem == 'xfs': - if (handle:= sys_command(f'/usr/bin/mkfs.xfs -f {self.path}')).exit_code != 0: - raise DiskError(f'Could not format {self.path} with {filesystem} because: {b"".join(handle)}') + if (handle:= sys_command(f'/usr/bin/mkfs.xfs -f {path}')).exit_code != 0: + raise DiskError(f'Could not format {path} with {filesystem} because: {b"".join(handle)}') self.filesystem = 'xfs' elif filesystem == 'f2fs': - if (handle:= sys_command(f'/usr/bin/mkfs.f2fs -f {self.path}')).exit_code != 0: - raise DiskError(f'Could not format {self.path} with {filesystem} because: {b"".join(handle)}') + if (handle:= sys_command(f'/usr/bin/mkfs.f2fs -f {path}')).exit_code != 0: + raise DiskError(f'Could not format {path} with {filesystem} because: {b"".join(handle)}') self.filesystem = 'f2fs' else: raise UnknownFilesystemFormat(f"Fileformat '{filesystem}' is not yet implemented.") From 1253982c30c5d2454f8e1b285e586a6f48929e3c Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 15:31:00 +0100 Subject: [PATCH 016/100] Added correct exception handling to the pre-format check. --- archinstall/lib/disk.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index c721c90ec0..8eb4b54db0 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -142,8 +142,8 @@ def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None # in order to determain if we support it or not. try: self.format(self.filesystem, '/dev/null') - except DiskError: - pass # We supported it, but /dev/null is not formatable as expected + 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: raise err From acf39296efd905e6b2a495468555c6b8a8976cbc Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 15:34:11 +0100 Subject: [PATCH 017/100] Added a check in guided to make it more visible that we check for filesystem supportation. --- archinstall/lib/disk.py | 21 +++++++++++---------- examples/guided.py | 3 ++- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 8eb4b54db0..d2bac3d236 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -113,7 +113,6 @@ def has_mount_point(self, mountpoint): return True return False - class Partition(): def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None, encrypted=False): if not part_id: @@ -138,15 +137,6 @@ def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None self.mountpoint = partition_info['target'] self.filesystem = partition_info['fstype'] - # We perform a dummy format on /dev/null with the given filesystem-type - # in order to determain if we support it or not. - try: - self.format(self.filesystem, '/dev/null') - 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: - raise err - def __repr__(self, *args, **kwargs): if self.encrypted: return f'Partition(path={self.path}, real_device={self.real_device}, fs={self.filesystem}, mounted={self.mountpoint})' @@ -217,6 +207,17 @@ def mount(self, target, fs=None, options=''): self.mountpoint = target return True + def filesystem_supported(self): + # We perform a dummy format on /dev/null with the given filesystem-type + # in order to determain if we support it or not. + try: + self.format(self.filesystem, '/dev/null') + 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: + raise err + return True + class Filesystem(): # TODO: # When instance of a HDD is selected, check all usages and gracefully unmount them diff --git a/examples/guided.py b/examples/guided.py index 4f7da99feb..384c4e1752 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -104,7 +104,8 @@ def perform_installation(device, boot_partition, language, mirrors): archinstall.log('Using existing partition table:') for partition in harddrive: - archinstall.log(f" {partition}") + if partition.filesystem_supported(): + archinstall.log(f" {partition}") else: print('Formatting woop woop!') exit(1) From 530edb5ece1350cbba568529cbba1f6c2eb36938 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 15:36:24 +0100 Subject: [PATCH 018/100] Moved the output of the current fileformat structure in guided, as well as added an option to supress the log message from format() in order to hide (for users) the some what confusing formating of /dev/null. --- archinstall/lib/disk.py | 8 +++++--- examples/guided.py | 9 +++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index d2bac3d236..7b3a9b66ab 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -143,11 +143,13 @@ def __repr__(self, *args, **kwargs): else: return f'Partition(path={self.path}, fs={self.filesystem}, mounted={self.mountpoint})' - def format(self, filesystem, path=None): + def format(self, filesystem, path=None, log_formating=True): if not path: path = self.path - log(f'Formatting {path} -> {filesystem}', level=LOG_LEVELS.Info) + if log_formating: + log(f'Formatting {path} -> {filesystem}', level=LOG_LEVELS.Info) + if filesystem == 'btrfs': o = b''.join(sys_command(f'/usr/bin/mkfs.btrfs -f {path}')) if b'UUID' not in o: @@ -211,7 +213,7 @@ def filesystem_supported(self): # We perform a dummy format on /dev/null with the given filesystem-type # in order to determain if we support it or not. try: - self.format(self.filesystem, '/dev/null') + self.format(self.filesystem, '/dev/null', log_formating=False) 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/examples/guided.py b/examples/guided.py index 384c4e1752..d538d5cd5d 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -95,6 +95,10 @@ def perform_installation(device, boot_partition, language, mirrors): if harddrive.has_partitions(): archinstall.log(f" ! {harddrive} contains existing partitions", fg='red') + for partition in harddrive: + if partition.filesystem_supported(): + archinstall.log(f" {partition}") + if (option := input('Do you wish to keep existing partition setup or format the entire disk? (k/f): ')).lower() in ('k', 'keep'): # If we want to keep the existing partitioning table # Make sure that it's the selected drive mounted under /mnt @@ -102,10 +106,7 @@ def perform_installation(device, boot_partition, language, mirrors): if harddrive.has_mount_point(archinstall.storage['MOUNT_POINT']) is False: raise archinstall.DiskError(f"The selected drive {harddrive} is not pre-mounted to {archinstall.storage['MOUNT_POINT']}. This is required when keeping a existing partitioning scheme.") - archinstall.log('Using existing partition table:') - for partition in harddrive: - if partition.filesystem_supported(): - archinstall.log(f" {partition}") + archinstall.log('Using existing partition table reported above.') else: print('Formatting woop woop!') exit(1) From c983976394cda6e0db5f3ae7079e172804d91885 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 17:57:16 +0100 Subject: [PATCH 019/100] Added in argument support to archinstall for easier testing and debugging --- archinstall/__init__.py | 17 ++++++++++++++++- examples/guided.py | 8 +++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/archinstall/__init__.py b/archinstall/__init__.py index ee2d036101..d4452d38ae 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -12,4 +12,19 @@ from .lib.packages import * from .lib.output import * from .lib.storage import * -from .lib.hardware import * \ No newline at end of file +from .lib.hardware import * + +## Basic version of arg.parse() supporting: +## --key=value +## --boolean +arguments = {} +positionals = [] +for arg in sys.argv[1:]: + if '--' == arg[:2]: + if '=' in arg: + key, val = [x.strip() for x in arg[2:].split('=', 1)] + else: + key, val = arg[2:], True + arguments[key] = val + else: + positionals.append(arg) \ No newline at end of file diff --git a/examples/guided.py b/examples/guided.py index d538d5cd5d..8033a8a545 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -5,7 +5,8 @@ # We'll print this right before the user gets informed about the formatting timer. archinstall.storage['_guided'] = {} archinstall.storage['_guided_hidden'] = {} # This will simply be hidden from printouts and things. - +print(archinstall.arguments, archinstall.positionals) +exit(0) """ This signal-handler chain (and global variable) is used to trigger the "Are you sure you want to abort?" question. @@ -99,7 +100,7 @@ def perform_installation(device, boot_partition, language, mirrors): if partition.filesystem_supported(): archinstall.log(f" {partition}") - if (option := input('Do you wish to keep existing partition setup or format the entire disk? (k/f): ')).lower() in ('k', 'keep'): + if (option := input('Do you wish to keep existing disk setup or format entire drive? (k/f): ')).lower() in ('k', 'keep'): # If we want to keep the existing partitioning table # Make sure that it's the selected drive mounted under /mnt # That way, we can rely on genfstab and some manual post-installation steps. @@ -107,9 +108,6 @@ def perform_installation(device, boot_partition, language, mirrors): raise archinstall.DiskError(f"The selected drive {harddrive} is not pre-mounted to {archinstall.storage['MOUNT_POINT']}. This is required when keeping a existing partitioning scheme.") archinstall.log('Using existing partition table reported above.') - else: - print('Formatting woop woop!') -exit(1) while (disk_password := getpass.getpass(prompt='Enter disk encryption password (leave blank for no encryption): ')): disk_password_verification = getpass.getpass(prompt='And one more time for verification: ') From 93985425590a27745445a432fa19a4de92ec5089 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 18:16:00 +0100 Subject: [PATCH 020/100] Added in command line arguments to guided and simplified the laout of guided a bit. Also added some more comments. --- examples/guided.py | 65 +++++++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/examples/guided.py b/examples/guided.py index 8033a8a545..12209858f3 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -5,11 +5,10 @@ # We'll print this right before the user gets informed about the formatting timer. archinstall.storage['_guided'] = {} archinstall.storage['_guided_hidden'] = {} # This will simply be hidden from printouts and things. -print(archinstall.arguments, archinstall.positionals) -exit(0) + """ This signal-handler chain (and global variable) -is used to trigger the "Are you sure you want to abort?" question. +is used to trigger the "Are you sure you want to abort?" question further down. """ SIG_TRIGGER = False def kill_handler(sig, frame): @@ -81,34 +80,46 @@ def perform_installation(device, boot_partition, language, mirrors): Not until we're satisfied with what we want to install will we continue with the actual installation steps. """ +archinstall.arguments['keyboard-language'] = archinstall.arguments.get('keyboard-language', + default=archinstall.select_language(archinstall.list_keyboard_languages()).strip()) -if len(keyboard_language := archinstall.select_language(archinstall.list_keyboard_languages()).strip()): - archinstall.set_keyboard_language(keyboard_language) - archinstall.storage['_guided']['keyboard_layout'] = keyboard_language +# Before continuing, set the preferred keyboard layout/language in the current terminal. +# This will just help the user with the next following questions. +if len(archinstall.arguments['keyboard-language']): + archinstall.set_keyboard_language(archinstall.arguments['keyboard-language']) # Set which region to download packages from during the installation -mirror_regions = archinstall.select_mirror_regions(archinstall.list_mirrors()) -archinstall.storage['_guided']['mirrors'] = mirror_regions +archinstall.arguments['mirror-region'] = archinstall.arguments.get('mirror-region', + default=archinstall.select_mirror_regions(archinstall.list_mirrors())) # Ask which harddrive/block-device we will install to -harddrive = archinstall.select_disk(archinstall.all_disks()) -archinstall.storage['_guided']['harddrive'] = harddrive +archinstall.arguments['harddrive'] = archinstall.arguments.get('harddrive', + default=archinstall.select_disk(archinstall.all_disks())) +# Perform a quick sanity check on the selected harddrive. +# 1. Check if it has partitions +# 3. Check that we support the current partitions +# 2. If so, ask if we should keep them or wipe everything if harddrive.has_partitions(): archinstall.log(f" ! {harddrive} contains existing partitions", fg='red') - for partition in harddrive: - if partition.filesystem_supported(): - archinstall.log(f" {partition}") - - if (option := input('Do you wish to keep existing disk setup or format entire drive? (k/f): ')).lower() in ('k', 'keep'): - # If we want to keep the existing partitioning table - # Make sure that it's the selected drive mounted under /mnt - # That way, we can rely on genfstab and some manual post-installation steps. - if harddrive.has_mount_point(archinstall.storage['MOUNT_POINT']) is False: - raise archinstall.DiskError(f"The selected drive {harddrive} is not pre-mounted to {archinstall.storage['MOUNT_POINT']}. This is required when keeping a existing partitioning scheme.") - - archinstall.log('Using existing partition table reported above.') + try: + for partition in harddrive: + if partition.filesystem_supported(): + archinstall.log(f" {partition}") + + if (option := input('Do you wish to keep existing disk setup or format entire drive? (k/f): ')).lower() in ('k', 'keep'): + # If we want to keep the existing partitioning table + # Make sure that it's the selected drive mounted under /mnt + # That way, we can rely on genfstab and some manual post-installation steps. + if harddrive.has_mount_point(archinstall.storage['MOUNT_POINT']) is False: + raise archinstall.DiskError(f"The selected drive {harddrive} is not pre-mounted to {archinstall.storage['MOUNT_POINT']}. This is required when keeping a existing partitioning scheme.") + + archinstall.log('Using existing partition table reported above.') + except UnknownFilesystemFormat as err: + archinstall.log(f"Current filesystem is not supported: {err}", fg='red') + input(f"Do you wish to erase all data? (y/n):") +exit(0) while (disk_password := getpass.getpass(prompt='Enter disk encryption password (leave blank for no encryption): ')): disk_password_verification = getpass.getpass(prompt='And one more time for verification: ') if disk_password != disk_password_verification: @@ -294,7 +305,13 @@ def perform_installation(device, boot_partition, language, mirrors): with archinstall.luks2(harddrive.partition[1], 'luksloop', disk_password) as unlocked_device: unlocked_device.format('btrfs') - perform_installation(unlocked_device, harddrive.partition[0], keyboard_language, mirror_regions) + perform_installation(unlocked_device, + harddrive.partition[0], + archinstall.arguments['keyboard-language'], + archinstall.arguments['mirror-region']) else: harddrive.partition[1].format('ext4') - perform_installation(harddrive.partition[1], harddrive.partition[0], keyboard_language, mirror_regions) \ No newline at end of file + perform_installation(harddrive.partition[1], + harddrive.partition[0], + archinstall.arguments['keyboard-language'], + archinstall.arguments['mirror-region']) \ No newline at end of file From a320ce3341d898adf71b5893551a7b20551fd91a Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 18:33:27 +0100 Subject: [PATCH 021/100] Thought I could get away with a little default-parameter hack to make the code look nice, but the JIT doesn't do lazy resolving on .get() calls. --- examples/guided.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/guided.py b/examples/guided.py index 12209858f3..d441396995 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -80,8 +80,8 @@ def perform_installation(device, boot_partition, language, mirrors): Not until we're satisfied with what we want to install will we continue with the actual installation steps. """ -archinstall.arguments['keyboard-language'] = archinstall.arguments.get('keyboard-language', - default=archinstall.select_language(archinstall.list_keyboard_languages()).strip()) +if not archinstall.arguments.get('keyboard-language', None): + archinstall.arguments['keyboard-language'] = archinstall.select_language(archinstall.list_keyboard_languages()).strip() # Before continuing, set the preferred keyboard layout/language in the current terminal. # This will just help the user with the next following questions. @@ -89,12 +89,12 @@ def perform_installation(device, boot_partition, language, mirrors): archinstall.set_keyboard_language(archinstall.arguments['keyboard-language']) # Set which region to download packages from during the installation -archinstall.arguments['mirror-region'] = archinstall.arguments.get('mirror-region', - default=archinstall.select_mirror_regions(archinstall.list_mirrors())) +if not archinstall.arguments.get('mirror-region', None): + archinstall.arguments['mirror-region'] = archinstall.select_mirror_regions(archinstall.list_mirrors()) # Ask which harddrive/block-device we will install to -archinstall.arguments['harddrive'] = archinstall.arguments.get('harddrive', - default=archinstall.select_disk(archinstall.all_disks())) +if not archinstall.arguments.get('harddrive', None) + archinstall.arguments['harddrive'] = archinstall.select_disk(archinstall.all_disks()) # Perform a quick sanity check on the selected harddrive. # 1. Check if it has partitions From ea65e3599a21ceb51ec9ed75ea888f98c33f857d Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 18:34:43 +0100 Subject: [PATCH 022/100] Forgot a semicolon --- examples/guided.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/guided.py b/examples/guided.py index d441396995..f86e2d51b4 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -93,7 +93,7 @@ def perform_installation(device, boot_partition, language, mirrors): archinstall.arguments['mirror-region'] = archinstall.select_mirror_regions(archinstall.list_mirrors()) # Ask which harddrive/block-device we will install to -if not archinstall.arguments.get('harddrive', None) +if not archinstall.arguments.get('harddrive', None): archinstall.arguments['harddrive'] = archinstall.select_disk(archinstall.all_disks()) # Perform a quick sanity check on the selected harddrive. From 819a8f742ea3177890077754db04a731ad3cb35a Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 18:43:05 +0100 Subject: [PATCH 023/100] Replacing static variables with more dynamic ones that can live across scopes. No need to pass things around unless strictly nessecary --- examples/guided.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/guided.py b/examples/guided.py index f86e2d51b4..f807e7348e 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -93,17 +93,19 @@ def perform_installation(device, boot_partition, language, mirrors): archinstall.arguments['mirror-region'] = archinstall.select_mirror_regions(archinstall.list_mirrors()) # Ask which harddrive/block-device we will install to -if not archinstall.arguments.get('harddrive', None): +if archinstall.arguments.get('harddrive', None): + archinstall.arguments['harddrive'] = archinstall.BlockDevice(archinstall.arguments['harddrive']) +else: archinstall.arguments['harddrive'] = archinstall.select_disk(archinstall.all_disks()) # Perform a quick sanity check on the selected harddrive. # 1. Check if it has partitions # 3. Check that we support the current partitions # 2. If so, ask if we should keep them or wipe everything -if harddrive.has_partitions(): - archinstall.log(f" ! {harddrive} contains existing partitions", fg='red') +if archinstall.arguments['harddrive'].has_partitions(): + archinstall.log(f" ! {archinstall.arguments['harddrive']} contains existing partitions", fg='red') try: - for partition in harddrive: + for partition in archinstall.arguments['harddrive']: if partition.filesystem_supported(): archinstall.log(f" {partition}") @@ -111,8 +113,8 @@ def perform_installation(device, boot_partition, language, mirrors): # If we want to keep the existing partitioning table # Make sure that it's the selected drive mounted under /mnt # That way, we can rely on genfstab and some manual post-installation steps. - if harddrive.has_mount_point(archinstall.storage['MOUNT_POINT']) is False: - raise archinstall.DiskError(f"The selected drive {harddrive} is not pre-mounted to {archinstall.storage['MOUNT_POINT']}. This is required when keeping a existing partitioning scheme.") + if archinstall.arguments['harddrive'].has_mount_point(archinstall.storage['MOUNT_POINT']) is False: + raise archinstall.DiskError(f"The selected drive {archinstall.arguments['harddrive']} is not pre-mounted to {archinstall.storage['MOUNT_POINT']}. This is required when keeping a existing partitioning scheme.") archinstall.log('Using existing partition table reported above.') except UnknownFilesystemFormat as err: @@ -261,7 +263,7 @@ def perform_installation(device, boot_partition, language, mirrors): We mention the drive one last time, and count from 5 to 0. """ -print(f' ! Formatting {harddrive} in ', end='') +print(f' ! Formatting {archinstall.arguments['harddrive']} in ', end='') for i in range(5, 0, -1): print(f"{i}", end='') From 47649074b8e182505b9def828b8cc344c96a3cb0 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 18:44:26 +0100 Subject: [PATCH 024/100] Quotation issue --- examples/guided.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/guided.py b/examples/guided.py index f807e7348e..a9af24bdef 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -263,7 +263,7 @@ def perform_installation(device, boot_partition, language, mirrors): We mention the drive one last time, and count from 5 to 0. """ -print(f' ! Formatting {archinstall.arguments['harddrive']} in ', end='') +print(f" ! Formatting {archinstall.arguments['harddrive']} in ", end='') for i in range(5, 0, -1): print(f"{i}", end='') From 9db589f10a1ba5e96f2343c25cba8fff32e0332c Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 18:45:25 +0100 Subject: [PATCH 025/100] Added a default no-info value to BlockDevice() --- archinstall/lib/disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 7b3a9b66ab..6c7e63ba3c 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -14,7 +14,7 @@ #libc.mount.argtypes = (ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_ulong, ctypes.c_char_p) class BlockDevice(): - def __init__(self, path, info): + def __init__(self, path, info={}): self.path = path self.info = info self.part_cache = OrderedDict() From 9038fda9911f40ede813c5492f34b68411db65b8 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 18:50:30 +0100 Subject: [PATCH 026/100] Added error handling for get_mount_info() --- archinstall/lib/disk.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 6c7e63ba3c..80e9c740d4 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -127,15 +127,17 @@ def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None if mountpoint: self.mount(mountpoint) - partition_info = get_partition_info(self.path) + mount_information = get_mount_info(self.path) - if self.mountpoint != partition_info['target'] and mountpoint: - raise DiskError(f"{self} was given a mountpoint but the actual mountpoint differs: {partition_info['target']}") - if partition_info['fstype'] != self.filesystem and filesystem: - raise DiskError(f"{self} was given a filesystem format, but a existing format was detected: {partition_info['fstype']}") + if self.mountpoint != mount_information['target'] and mountpoint: + raise DiskError(f"{self} was given a mountpoint but the actual mountpoint differs: {mount_information['target']}") + if mount_information['fstype'] != self.filesystem and filesystem: + raise DiskError(f"{self} was given a filesystem format, but a existing format was detected: {mount_information['fstype']}") - self.mountpoint = partition_info['target'] - self.filesystem = partition_info['fstype'] + if (target := mount_information.get('target', None)): + self.mountpoint = target + if (fstype := mount_information.get('fstype', None)): + self.filesystem = fstype def __repr__(self, *args, **kwargs): if self.encrypted: @@ -344,8 +346,12 @@ def harddrive(size=None, model=None, fuzzy=False): return collection[drive] -def get_partition_info(path): - output = b''.join(sys_command(f'/usr/bin/findmnt --json {path}')) +def get_mount_info(path): + try: + output = b''.join(sys_command(f'/usr/bin/findmnt --json {path}')) + except SysCallError: + return {} + output = output.decode('UTF-8') output = json.loads(output) if 'filesystems' in output: From a9d49a52eca85a471e79018fdc952190e2071e10 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 18:51:45 +0100 Subject: [PATCH 027/100] Corrected two variables. --- archinstall/lib/disk.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 80e9c740d4..d999e6263c 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -129,10 +129,10 @@ def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None mount_information = get_mount_info(self.path) - if self.mountpoint != mount_information['target'] and mountpoint: - raise DiskError(f"{self} was given a mountpoint but the actual mountpoint differs: {mount_information['target']}") - if mount_information['fstype'] != self.filesystem and filesystem: - raise DiskError(f"{self} was given a filesystem format, but a existing format was detected: {mount_information['fstype']}") + if self.mountpoint != mount_information.get('target', None) and mountpoint: + raise DiskError(f"{self} was given a mountpoint but the actual mountpoint differs: {mount_information.get('target', None)}") + if mount_information.get('fstype', None) != self.filesystem and filesystem: + raise DiskError(f"{self} was given a filesystem format, but a existing format was detected: {mount_information.get('fstype', None)}") if (target := mount_information.get('target', None)): self.mountpoint = target From 4349512ef3f24c48c1c0903064d518e44c11101b Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 18:53:55 +0100 Subject: [PATCH 028/100] Added error handling for BlockDevice() that was given no information. --- archinstall/lib/disk.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index d999e6263c..5a2857f39d 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -14,7 +14,12 @@ #libc.mount.argtypes = (ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_ulong, ctypes.c_char_p) class BlockDevice(): - def __init__(self, path, info={}): + def __init__(self, path, info=None): + if not info: + # If we don't give any information, we need to auto-fill it. + # Otherwise any subsequent usage will break. + info = all_disks().get(path, {}) + self.path = path self.info = info self.part_cache = OrderedDict() From d184777a1b8a2cccddf6127d8ea7ce5a38d509f1 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 18:57:47 +0100 Subject: [PATCH 029/100] Reworked fault handling a bit --- archinstall/lib/disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 5a2857f39d..52afffcf6a 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -18,7 +18,7 @@ def __init__(self, path, info=None): if not info: # If we don't give any information, we need to auto-fill it. # Otherwise any subsequent usage will break. - info = all_disks().get(path, {}) + info = all_disks()[path].info self.path = path self.info = info From 62a14d09455c7f7b1b6dbcf5810c8e452d2858d7 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 19:26:47 +0100 Subject: [PATCH 030/100] Forgot importpath for exceptions --- examples/guided.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/guided.py b/examples/guided.py index a9af24bdef..c5e1474ea9 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -117,7 +117,7 @@ def perform_installation(device, boot_partition, language, mirrors): raise archinstall.DiskError(f"The selected drive {archinstall.arguments['harddrive']} is not pre-mounted to {archinstall.storage['MOUNT_POINT']}. This is required when keeping a existing partitioning scheme.") archinstall.log('Using existing partition table reported above.') - except UnknownFilesystemFormat as err: + except archinstall.UnknownFilesystemFormat as err: archinstall.log(f"Current filesystem is not supported: {err}", fg='red') input(f"Do you wish to erase all data? (y/n):") From 03c46cce2b62b41c5b73f86dffb90eff6a1b8eb2 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 7 Feb 2021 20:46:12 +0100 Subject: [PATCH 031/100] Added a dummy function call to grab the partition fstype, since unmounted filesystems won't return the fstype obviously. --- archinstall/lib/disk.py | 1 + 1 file changed, 1 insertion(+) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 52afffcf6a..5cb952269e 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -133,6 +133,7 @@ def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None self.mount(mountpoint) mount_information = get_mount_info(self.path) + actual_fstype = get_filesystem_type(self.path) # blkid -o value -s TYPE self.path if self.mountpoint != mount_information.get('target', None) and mountpoint: raise DiskError(f"{self} was given a mountpoint but the actual mountpoint differs: {mount_information.get('target', None)}") From e2cd617d05d87bbe3fe8f1809faecd8c1ebd230e Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Thu, 11 Feb 2021 14:11:21 +0100 Subject: [PATCH 032/100] Reworked the way partition formatting works. As well as added some flags to the partition if it's locked/unlocked for partitioning. By defaults partitions will now be in a locked state - prohibiting formatting unless set or overridden in the formatting call. This allows us to selectively format partitions individually later on. There's also a target_mountpoint that is the desired relative mount point inside a installation. This can be pre-pended with the installation base directory during mount. These changes also function as indicators for the installation (and guided installation) for which partitions to use and/or wipe. If an entire drive is selected for wiping, these changes will have no affect in the decision making as all partitions will be new and have formatable set to true. --- archinstall/lib/disk.py | 63 +++++++++++++++++++++++++++-------- archinstall/lib/exceptions.py | 2 ++ archinstall/lib/luks.py | 7 +++- examples/guided.py | 16 +++++---- 4 files changed, 67 insertions(+), 21 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 5cb952269e..aa3632d87a 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -125,35 +125,57 @@ def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None self.path = path self.part_id = part_id self.mountpoint = mountpoint + self.target_mountpoint = mountpoint self.filesystem = filesystem self.size = size # TODO: Refresh? self.encrypted = encrypted + self.allow_formatting = False # A fail-safe for unconfigured partitions, such as windows NTFS partitions. if mountpoint: self.mount(mountpoint) mount_information = get_mount_info(self.path) - actual_fstype = get_filesystem_type(self.path) # blkid -o value -s TYPE self.path + fstype = get_filesystem_type(self.path) # blkid -o value -s TYPE self.path if self.mountpoint != mount_information.get('target', None) and mountpoint: raise DiskError(f"{self} was given a mountpoint but the actual mountpoint differs: {mount_information.get('target', None)}") - if mount_information.get('fstype', None) != self.filesystem and filesystem: - raise DiskError(f"{self} was given a filesystem format, but a existing format was detected: {mount_information.get('fstype', None)}") if (target := mount_information.get('target', None)): self.mountpoint = target - if (fstype := mount_information.get('fstype', None)): + if (fstype := mount_information.get('fstype', fstype)): self.filesystem = fstype + def __lt__(self, left_comparitor): + if type(left_comparitor) == Partition: + left_comparitor = left_comparitor.path + else: + left_comparitor = str(left_comparitor) + return self.path < left_comparitor # Not quite sure the order here is correct. But /dev/nvme0n1p1 comes before /dev/nvme0n1p5 so seems correct. + def __repr__(self, *args, **kwargs): + mount_repr = '' + if self.mountpoint: + mount_repr = f", mounted={self.mountpoint}" + elif self.target_mountpoint: + mount_repr = f", rel_mountpoint={self.target_mountpoint}" + if self.encrypted: - return f'Partition(path={self.path}, real_device={self.real_device}, fs={self.filesystem}, mounted={self.mountpoint})' + return f'Partition(path={self.path}, real_device={self.real_device}, fs={self.filesystem}{mount_repr})' else: - return f'Partition(path={self.path}, fs={self.filesystem}, mounted={self.mountpoint})' + return f'Partition(path={self.path}, fs={self.filesystem}{mount_repr})' - def format(self, filesystem, path=None, log_formating=True): - if not path: + def format(self, filesystem, path=None, allow_formatting=None, log_formating=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. + """ + if path is None: path = self.path + if allow_formatting is None: + allow_formatting = self.allow_formatting + + 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: log(f'Formatting {path} -> {filesystem}', level=LOG_LEVELS.Info) @@ -173,13 +195,18 @@ def format(self, filesystem, path=None, log_formating=True): raise DiskError(f'Could not format {path} with {filesystem} because: {b"".join(handle)}') self.filesystem = 'ext4' elif filesystem == 'xfs': - if (handle:= sys_command(f'/usr/bin/mkfs.xfs -f {path}')).exit_code != 0: + if (handle := sys_command(f'/usr/bin/mkfs.xfs -f {path}')).exit_code != 0: raise DiskError(f'Could not format {path} with {filesystem} because: {b"".join(handle)}') self.filesystem = 'xfs' elif filesystem == 'f2fs': - if (handle:= sys_command(f'/usr/bin/mkfs.f2fs -f {path}')).exit_code != 0: + if (handle := sys_command(f'/usr/bin/mkfs.f2fs -f {path}')).exit_code != 0: raise DiskError(f'Could not format {path} with {filesystem} because: {b"".join(handle)}') self.filesystem = 'f2fs' + elif filesystem == 'crypto_LUKS': + from .luks import luks2 + encrypted_partition = luks2(self, None, None) + encrypted_partition.format(path) + self.filesystem = 'crypto_LUKS' else: raise UnknownFilesystemFormat(f"Fileformat '{filesystem}' is not yet implemented.") return True @@ -218,10 +245,14 @@ def mount(self, target, fs=None, options=''): return True def filesystem_supported(self): - # We perform a dummy format on /dev/null with the given filesystem-type - # in order to determain if we support it or not. + """ + The support for a filesystem (this partition) is tested by calling + partition.format() with a path set to '/dev/null' which returns two exceptions: + 1. SysCallError saying that /dev/null is not formattable - but the filesystem is supported + 2. UnknownFilesystemFormat that indicates that we don't support the given filesystem type + """ try: - self.format(self.filesystem, '/dev/null', log_formating=False) + self.format(self.filesystem, '/dev/null', log_formating=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: @@ -364,4 +395,8 @@ def get_mount_info(path): if len(output['filesystems']) > 1: raise DiskError(f"Path '{path}' contains multiple mountpoints: {output['filesystems']}") - return output['filesystems'][0] \ No newline at end of file + return output['filesystems'][0] + +def get_filesystem_type(path): + output = b''.join(sys_command(f"blkid -o value -s TYPE {path}")) + return output.strip().decode('UTF-8') \ No newline at end of file diff --git a/archinstall/lib/exceptions.py b/archinstall/lib/exceptions.py index a7864a237b..5186bfd41a 100644 --- a/archinstall/lib/exceptions.py +++ b/archinstall/lib/exceptions.py @@ -11,4 +11,6 @@ class SysCallError(BaseException): class ProfileNotFound(BaseException): pass class HardwareIncompatibilityError(BaseException): + pass +class PermissionError(BaseException): pass \ No newline at end of file diff --git a/archinstall/lib/luks.py b/archinstall/lib/luks.py index e1f14bab4b..d62c2d4b4d 100644 --- a/archinstall/lib/luks.py +++ b/archinstall/lib/luks.py @@ -12,6 +12,7 @@ def __init__(self, partition, mountpoint, password, *args, **kwargs): self.mountpoint = mountpoint self.args = args self.kwargs = kwargs + self.filesystem = 'crypto_LUKS' def __enter__(self): key_file = self.encrypt(self.partition, self.password, *self.args, **self.kwargs) @@ -57,4 +58,8 @@ def unlock(self, partition, mountpoint, key_file): def close(self, mountpoint): sys_command(f'cryptsetup close /dev/mapper/{mountpoint}') - return os.path.islink(f'/dev/mapper/{mountpoint}') is False \ No newline at end of file + return os.path.islink(f'/dev/mapper/{mountpoint}') is False + + def format(self, path): + if (handle := sys_command(f"/usr/bin/cryptsetup -q -v luksErase {path}")).exit_code != 0: + raise DiskError(f'Could not format {path} with {self.filesystem} because: {b"".join(handle)}') \ No newline at end of file diff --git a/examples/guided.py b/examples/guided.py index c5e1474ea9..5e8a64c1c2 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -105,18 +105,22 @@ def perform_installation(device, boot_partition, language, mirrors): if archinstall.arguments['harddrive'].has_partitions(): archinstall.log(f" ! {archinstall.arguments['harddrive']} contains existing partitions", fg='red') try: + partition_mountpoints = {} for partition in archinstall.arguments['harddrive']: if partition.filesystem_supported(): archinstall.log(f" {partition}") + partition_mountpoints[partition] = None - if (option := input('Do you wish to keep existing disk setup or format entire drive? (k/f): ')).lower() in ('k', 'keep'): - # If we want to keep the existing partitioning table - # Make sure that it's the selected drive mounted under /mnt - # That way, we can rely on genfstab and some manual post-installation steps. - if archinstall.arguments['harddrive'].has_mount_point(archinstall.storage['MOUNT_POINT']) is False: - raise archinstall.DiskError(f"The selected drive {archinstall.arguments['harddrive']} is not pre-mounted to {archinstall.storage['MOUNT_POINT']}. This is required when keeping a existing partitioning scheme.") + if (option := input('Do you wish to keep one/more existing partitions or format entire drive? (k/f): ')).lower() in ('k', 'keep'): + archinstall.arguments['harddrive'].keep_partitions = True + while True: + partition = archinstall.generic_select(partition_mountpoints.values(), "Select a partition to assign mount-point to") + + archinstall.arguments['harddrive'].allocate_partitions(selections) archinstall.log('Using existing partition table reported above.') + else: + archinstall.arguments['harddrive'].keep_partitions = False except archinstall.UnknownFilesystemFormat as err: archinstall.log(f"Current filesystem is not supported: {err}", fg='red') input(f"Do you wish to erase all data? (y/n):") From 76d3976df6d148e31a0aab04037a7c191b4054fe Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 16 Feb 2021 08:51:00 +0100 Subject: [PATCH 033/100] Re-worked guided to allow for unsupported fileformats when checking which are supported, as well as selecting mount-points. --- examples/guided.py | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/examples/guided.py b/examples/guided.py index 5e8a64c1c2..abbecd3afc 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -104,26 +104,35 @@ def perform_installation(device, boot_partition, language, mirrors): # 2. If so, ask if we should keep them or wipe everything if archinstall.arguments['harddrive'].has_partitions(): archinstall.log(f" ! {archinstall.arguments['harddrive']} contains existing partitions", fg='red') - try: - partition_mountpoints = {} - for partition in archinstall.arguments['harddrive']: + partition_mountpoints = {} + for partition in archinstall.arguments['harddrive']: + try: if partition.filesystem_supported(): archinstall.log(f" {partition}") partition_mountpoints[partition] = None + except archinstall.UnknownFilesystemFormat as err: + archinstall.log(f" {partition} (Filesystem not supported)", fg='red') + #archinstall.log(f"Current filesystem is not supported: {err}", fg='red') + #input(f"Do you wish to erase all data? (y/n):") + + if (option := input('Do you wish to keep one/more existing partitions or format entire drive? (k/f): ')).lower() in ('k', 'keep'): + archinstall.arguments['harddrive'].keep_partitions = True + + archinstall.log(f" ** You will now select where (inside the installation) to mount partitions. **") + archinstall.log(f" ** The root would be a simple / and the boot partition /boot as it's relative paths. **") + while True: + partition = archinstall.generic_select(partition_mountpoints.keys(), "Select a partition to assign mount-point to: ") + if not partition: + break - if (option := input('Do you wish to keep one/more existing partitions or format entire drive? (k/f): ')).lower() in ('k', 'keep'): - archinstall.arguments['harddrive'].keep_partitions = True - - while True: - partition = archinstall.generic_select(partition_mountpoints.values(), "Select a partition to assign mount-point to") + mountpoint = input(f"Select a mount-point for {partition}: ") + #partition.mountpoint = mountpoint + partition.allow_formatting = True + partition.target_mountpoint = mountpoint - archinstall.arguments['harddrive'].allocate_partitions(selections) - archinstall.log('Using existing partition table reported above.') - else: - archinstall.arguments['harddrive'].keep_partitions = False - except archinstall.UnknownFilesystemFormat as err: - archinstall.log(f"Current filesystem is not supported: {err}", fg='red') - input(f"Do you wish to erase all data? (y/n):") + archinstall.log('Using existing partition table reported above.') + else: + archinstall.arguments['harddrive'].keep_partitions = False exit(0) while (disk_password := getpass.getpass(prompt='Enter disk encryption password (leave blank for no encryption): ')): From 10f0567ead96655b933598bfb7729b18956ef4dc Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 17 Feb 2021 12:06:15 +0100 Subject: [PATCH 034/100] Adding filesystem selection for partitions --- examples/guided.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/guided.py b/examples/guided.py index abbecd3afc..315be0d0d2 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -121,14 +121,18 @@ def perform_installation(device, boot_partition, language, mirrors): archinstall.log(f" ** You will now select where (inside the installation) to mount partitions. **") archinstall.log(f" ** The root would be a simple / and the boot partition /boot as it's relative paths. **") while True: - partition = archinstall.generic_select(partition_mountpoints.keys(), "Select a partition to assign mount-point to: ") + partition = archinstall.generic_select(partition_mountpoints.keys(), "Select a partition to assign mount-point to (leave blank when done): ") if not partition: break - mountpoint = input(f"Select a mount-point for {partition}: ") - #partition.mountpoint = mountpoint - partition.allow_formatting = True - partition.target_mountpoint = mountpoint + mountpoint = input(f"Enter a mount-point for {partition}: ").strip(' ') + new_filesystem = input(f"Enter a valid filesystem for {partition} (leave blank for {partition.filesystem}): ").strip(' ') + + if len(mountpoint): + partition.allow_formatting = True + partition.target_mountpoint = mountpoint + if len(new_filesystem): + partition.filesystem = new_filesystem archinstall.log('Using existing partition table reported above.') else: From 6e2d0c98eba28fa120c67378611deee442df1e30 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 17 Feb 2021 12:12:49 +0100 Subject: [PATCH 035/100] Added some error handling when selecting a filesystem for partitions. --- examples/guided.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/examples/guided.py b/examples/guided.py index 315be0d0d2..07b345f1d6 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -126,7 +126,18 @@ def perform_installation(device, boot_partition, language, mirrors): break mountpoint = input(f"Enter a mount-point for {partition}: ").strip(' ') - new_filesystem = input(f"Enter a valid filesystem for {partition} (leave blank for {partition.filesystem}): ").strip(' ') + + while 1: + new_filesystem = input(f"Enter a valid filesystem for {partition} (leave blank for {partition.filesystem}): ").strip(' ') + try: + partition.format(new_filesystem, path='/dev/null') + except UnknownFilesystemFormat: + archinstall.log(f"Selected filesystem is not supported yet, if you wish archinstall should support '{new_filesystem}' please create a issue-ticket suggesting it on github at https://github.com/Torxed/archinstall/issues.") + archinstall.log(f"Until then, please enter another supported filesystem.") + continue + except SysCallError: + pass # Supported, but mkfs could not format /dev/null which is the whole point of /dev/null in path :) + break if len(mountpoint): partition.allow_formatting = True From 28adc20a30142ed93c2f730f6a653d76211a1399 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 17 Feb 2021 12:13:59 +0100 Subject: [PATCH 036/100] Added more flags to the dummy-format --- examples/guided.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/guided.py b/examples/guided.py index 07b345f1d6..aca3520bb7 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -130,7 +130,7 @@ def perform_installation(device, boot_partition, language, mirrors): while 1: new_filesystem = input(f"Enter a valid filesystem for {partition} (leave blank for {partition.filesystem}): ").strip(' ') try: - partition.format(new_filesystem, path='/dev/null') + partition.format(new_filesystem, path='/dev/null', log_formating=False, allow_formatting=True) except UnknownFilesystemFormat: archinstall.log(f"Selected filesystem is not supported yet, if you wish archinstall should support '{new_filesystem}' please create a issue-ticket suggesting it on github at https://github.com/Torxed/archinstall/issues.") archinstall.log(f"Until then, please enter another supported filesystem.") From b5c862549a7b52c8eb53227db1a97e1a6114c040 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 17 Feb 2021 12:14:43 +0100 Subject: [PATCH 037/100] Added more error handling for filesystem selection --- examples/guided.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/guided.py b/examples/guided.py index aca3520bb7..7798ca0812 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -129,6 +129,9 @@ def perform_installation(device, boot_partition, language, mirrors): while 1: new_filesystem = input(f"Enter a valid filesystem for {partition} (leave blank for {partition.filesystem}): ").strip(' ') + if len(new_filesystem) <= 0: + break + try: partition.format(new_filesystem, path='/dev/null', log_formating=False, allow_formatting=True) except UnknownFilesystemFormat: From 3a8a25982a273081cd845723a2d794438e960a13 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 17 Feb 2021 12:15:48 +0100 Subject: [PATCH 038/100] Forgot relative path for exceptions. --- examples/guided.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/guided.py b/examples/guided.py index 7798ca0812..0efc438c6b 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -131,14 +131,14 @@ def perform_installation(device, boot_partition, language, mirrors): new_filesystem = input(f"Enter a valid filesystem for {partition} (leave blank for {partition.filesystem}): ").strip(' ') if len(new_filesystem) <= 0: break - + try: partition.format(new_filesystem, path='/dev/null', log_formating=False, allow_formatting=True) - except UnknownFilesystemFormat: + except archinstall.UnknownFilesystemFormat: archinstall.log(f"Selected filesystem is not supported yet, if you wish archinstall should support '{new_filesystem}' please create a issue-ticket suggesting it on github at https://github.com/Torxed/archinstall/issues.") archinstall.log(f"Until then, please enter another supported filesystem.") continue - except SysCallError: + except archinstall.SysCallError: pass # Supported, but mkfs could not format /dev/null which is the whole point of /dev/null in path :) break From 5cb3b0d176fff9d2c2fb814530b29eca5819fe8e Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 17 Feb 2021 12:24:56 +0100 Subject: [PATCH 039/100] Implemented #106 in branch skip-partitioning. Also moving the disk_password from being a local variable to a BlockDevice setting/variable. --- archinstall/lib/disk.py | 4 ++++ archinstall/lib/user_interaction.py | 10 ++++++++++ examples/guided.py | 11 +++-------- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index aa3632d87a..d76c6d7e9a 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -23,6 +23,10 @@ def __init__(self, path, info=None): self.path = path self.info = info self.part_cache = OrderedDict() + # TODO: Currently disk encryption is a BIT missleading. + # It's actually partition-encryption, but for future-proofing this + # I'm placing the encryption password on a BlockDevice level. + self.encryption_passwoed = None def __repr__(self, *args, **kwargs): return f"BlockDevice({self.device})" diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index fdbabe9672..f92cd00832 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -1,10 +1,20 @@ +import getpass from .exceptions import * from .profiles import Profile from .locale_helpers import search_keyboard_layout +from .output import log ## TODO: Some inconsistencies between the selection processes. ## Some return the keys from the options, some the values? +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') + continue + return passwd + 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 diff --git a/examples/guided.py b/examples/guided.py index 0efc438c6b..8467628435 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -152,16 +152,11 @@ def perform_installation(device, boot_partition, language, mirrors): else: archinstall.arguments['harddrive'].keep_partitions = False -exit(0) -while (disk_password := getpass.getpass(prompt='Enter disk encryption password (leave blank for no encryption): ')): - disk_password_verification = getpass.getpass(prompt='And one more time for verification: ') - if disk_password != disk_password_verification: - archinstall.log(' * Passwords did not match * ', bg='black', fg='red') - continue - archinstall.storage['_guided']['disk_encryption'] = True - break +disk_password = archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ') +archinstall.arguments['harddrive'].encryption_passwoed = disk_password +exit(0) # Ask for a hostname hostname = input('Desired hostname for the installation: ') if len(hostname) == 0: From 572d59e5607811c4f7456d09ad1744b2da3ea394 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 17 Feb 2021 13:30:14 +0100 Subject: [PATCH 040/100] Cleaning up guided.py a bit to be less complex and convoluted, while still performing the same task. --- archinstall/lib/exceptions.py | 2 + archinstall/lib/user_interaction.py | 71 +++++++++++ examples/guided.py | 177 ++++++++++------------------ 3 files changed, 133 insertions(+), 117 deletions(-) diff --git a/archinstall/lib/exceptions.py b/archinstall/lib/exceptions.py index 5186bfd41a..5a5d47c6f4 100644 --- a/archinstall/lib/exceptions.py +++ b/archinstall/lib/exceptions.py @@ -13,4 +13,6 @@ class ProfileNotFound(BaseException): class HardwareIncompatibilityError(BaseException): pass class PermissionError(BaseException): + pass +class UserError(BaseException): pass \ No newline at end of file diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index f92cd00832..440e41a181 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -14,6 +14,77 @@ def get_password(prompt="Enter a password: "): log(' * Passwords did not match * ', bg='black', fg='red') continue return passwd + return None + +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') + continue + elif not new_user and not forced: + raise UserError("No superuser was created.") + + password = get_password(prompt=f'Password for user {new_user}: ') + return {new_user: password} + +def ask_for_additional_users(prompt='Any additional users to install (leave blank for no users): '): + users = {} + super_users = {} + + while 1: + new_user = input(prompt).strip(' ') + if not new_user: + break + 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'): + super_users[new_user] = password + else: + users[new_user] = password + + return users, super_users + +def ask_to_configure_network(): + # Optionally configure one network interface. + #while 1: + # {MAC: Ifname} + interfaces = {'ISO-CONFIG' : 'Copy ISO network configuration to installation', **archinstall.list_interfaces()} + archinstall.storage['_guided']['network'] = None + + nic = archinstall.generic_select(interfaces.values(), "Select one network interface to configure (leave blank to skip): ") + if nic and nic != 'Copy ISO network configuration to installation': + mode = archinstall.generic_select(['DHCP (auto detect)', 'IP (static)'], f"Select which mode to configure for {nic}: ") + if mode == 'IP (static)': + while 1: + ip = input(f"Enter the IP and subnet for {nic} (example: 192.168.0.5/24): ").strip() + if ip: + break + else: + ArchInstall.log( + "You need to enter a valid IP in IP-config mode.", + level=archinstall.LOG_LEVELS.Warning, + bg='black', + fg='red' + ) + + if not len(gateway := input('Enter your gateway (router) IP address or leave blank for none: ').strip()): + gateway = None + + dns = None + if len(dns_input := input('Enter your DNS servers (space separated, blank for none): ').strip()): + dns = dns_input.split(' ') + + return {'nic': nic, 'dhcp': False, 'ip': ip, 'gateway' : gateway, 'dns' : dns} + else: + return {'nic': nic} + elif nic: + return nic + + return None def generic_select(options, input_text="Select one of the above by index or absolute value: ", sort=True): """ diff --git a/examples/guided.py b/examples/guided.py index 8467628435..119f22b38b 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -1,14 +1,10 @@ import getpass, time, json, sys, signal, os import archinstall -# Create a storage structure for all our information. -# We'll print this right before the user gets informed about the formatting timer. -archinstall.storage['_guided'] = {} -archinstall.storage['_guided_hidden'] = {} # This will simply be hidden from printouts and things. - """ This signal-handler chain (and global variable) is used to trigger the "Are you sure you want to abort?" question further down. +It might look a bit odd, but have a look at the line: "if SIG_TRIGGER:" """ SIG_TRIGGER = False def kill_handler(sig, frame): @@ -23,13 +19,14 @@ def sig_handler(sig, frame): original_sigint_handler = signal.getsignal(signal.SIGINT) signal.signal(signal.SIGINT, sig_handler) + def perform_installation(device, boot_partition, language, mirrors): """ 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(device, boot_partition=boot_partition, hostname=archinstall.storage['_guided']['hostname']) as installation: + with archinstall.Installer(device, boot_partition=boot_partition, hostname=archinstall.arguments.get('hostname', 'Archinstall')) as 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 @@ -152,137 +149,83 @@ def perform_installation(device, boot_partition, language, mirrors): else: archinstall.arguments['harddrive'].keep_partitions = False +# Get disk encryption password (or skip if blank) +if not archinstall.arguments.get('encryption-password', None): + archinstall.arguments['encryption-password'] = archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ') +archinstall.arguments['harddrive'].encryption_password = archinstall.arguments['encryption-password'] -disk_password = archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ') -archinstall.arguments['harddrive'].encryption_passwoed = disk_password - -exit(0) -# Ask for a hostname -hostname = input('Desired hostname for the installation: ') -if len(hostname) == 0: - hostname = 'ArchInstall' -archinstall.storage['_guided']['hostname'] = hostname +# Get the hostname for the machine +if not archinstall.arguments.get('hostname', None): + archinstall.arguments['hostname'] = input('Desired hostname for the installation: ').strip(' ') # Ask for a root password (optional, but triggers requirement for super-user if skipped) -while (root_pw := getpass.getpass(prompt='Enter root password (leave blank to leave root disabled): ')): - root_pw_verification = getpass.getpass(prompt='And one more time for verification: ') - if root_pw != root_pw_verification: - archinstall.log(' * Passwords did not match * ', bg='black', fg='red') - continue - - # Storing things in _guided_hidden helps us avoid printing it - # when echoing user configuration: archinstall.storage['_guided'] - archinstall.storage['_guided_hidden']['root_pw'] = root_pw - archinstall.storage['_guided']['root_unlocked'] = True - break +if not archinstall.arguments.get('!root-password', None): + archinstall.arguments['!root-password'] = archinstall.get_password(prompt='Enter root password (Recommended: leave blank to leave root disabled): ') + +# # Storing things in _guided_hidden helps us avoid printing it +# # when echoing user configuration: archinstall.storage['_guided'] +# archinstall.storage['_guided_hidden']['root_pw'] = root_pw +# archinstall.storage['_guided']['root_unlocked'] = True +# break # Ask for additional users (super-user if root pw was not set) -users = {} -new_user_text = 'Any additional users to install (leave blank for no users): ' -if len(root_pw.strip()) == 0: - new_user_text = 'Create a super-user with sudo privileges: ' - -archinstall.storage['_guided']['users'] = None -while 1: - new_user = input(new_user_text) - if len(new_user.strip()) == 0: - if len(root_pw.strip()) == 0: - archinstall.log(' * Since root is disabled, you need to create a least one (super) user!', bg='black', fg='red') - continue - break - - if not archinstall.storage['_guided']['users']: - archinstall.storage['_guided']['users'] = [] - archinstall.storage['_guided']['users'].append(new_user) - - new_user_passwd = getpass.getpass(prompt=f'Password for user {new_user}: ') - new_user_passwd_verify = getpass.getpass(prompt=f'Enter password again for verification: ') - if new_user_passwd != new_user_passwd_verify: - archinstall.log(' * Passwords did not match * ', bg='black', fg='red') - continue - - users[new_user] = new_user_passwd - break +archinstall.arguments['users'] = {} +archinstall.arguments['superusers'] = {} +if not archinstall.arguments.get('!root-password', None): + archinstall.arguments['superusers'] = archinstall.ask_for_superuser_account('Create a required super-user with sudo privileges: ', forced=True) + +users, superusers = archinstall.ask_for_additional_users('Any additional users to install (leave blank for no users): ') +archinstall.arguments['users'] = users +archinstall.arguments['superusers'] = {**archinstall.arguments['superusers'], **superusers} # Ask for archinstall-specific profiles (such as desktop environments etc) -while 1: - profile = archinstall.select_profile(archinstall.list_profiles()) - if profile: - archinstall.storage['_guided']['profile'] = profile - - if type(profile) != str: # Got a imported profile - archinstall.storage['_guided']['profile'] = profile[0] # The second return is a module, and not a handle/object. - if not profile[1]._prep_function(): - # TODO: See how we can incorporate this into - # the general log flow. As this is pre-installation - # session setup. Which creates the installation.log file. - archinstall.log( - ' * Profile\'s preparation requirements was not fulfilled.', - bg='black', - fg='red' - ) - continue +if not archinstall.arguments.get('profile', None): + while 1: + profile = archinstall.select_profile(archinstall.list_profiles()) + print(profile) + if profile: + archinstall.storage['_guided']['profile'] = profile + + if type(profile) != str: # Got a imported profile + archinstall.storage['_guided']['profile'] = profile[0] # The second return is a module, and not a handle/object. + if not profile[1]._prep_function(): + # TODO: See how we can incorporate this into + # the general log flow. As this is pre-installation + # session setup. Which creates the installation.log file. + archinstall.log( + ' * Profile\'s preparation requirements was not fulfilled.', + bg='black', + fg='red' + ) + continue + break + else: break - else: - break # Additional packages (with some light weight error handling for invalid package names) -archinstall.storage['_guided']['packages'] = None -while 1: - packages = [package for package in input('Additional packages aside from base (space separated): ').split(' ') if len(package)] +if not archinstall.arguments.get('packages', None): + archinstall.arguments['packages'] = [package for package in input('Additional packages aside from base (space separated): ').split(' ') if len(package)] - if not packages: - break +# Verify packages that were given +try: + archinstall.validate_package_list(archinstall.arguments['packages']) +except archinstall.RequirementError as e: + archinstall.log(e, fg='red') + exit(1) - try: - if archinstall.validate_package_list(packages): - archinstall.storage['_guided']['packages'] = packages - break - except archinstall.RequirementError as e: - print(e) - -# Optionally configure one network interface. -#while 1: -# {MAC: Ifname} -interfaces = {'ISO-CONFIG' : 'Copy ISO network configuration to installation', **archinstall.list_interfaces()} -archinstall.storage['_guided']['network'] = None - -nic = archinstall.generic_select(interfaces.values(), "Select one network interface to configure (leave blank to skip): ") -if nic and nic != 'Copy ISO network configuration to installation': - mode = archinstall.generic_select(['DHCP (auto detect)', 'IP (static)'], f"Select which mode to configure for {nic}: ") - if mode == 'IP (static)': - while 1: - ip = input(f"Enter the IP and subnet for {nic} (example: 192.168.0.5/24): ").strip() - if ip: - break - else: - ArchInstall.log( - "You need to enter a valid IP in IP-config mode.", - level=archinstall.LOG_LEVELS.Warning, - bg='black', - fg='red' - ) - - if not len(gateway := input('Enter your gateway (router) IP address or leave blank for none: ').strip()): - gateway = None - - dns = None - if len(dns_input := input('Enter your DNS servers (space separated, blank for none): ').strip()): - dns = dns_input.split(' ') - - archinstall.storage['_guided']['network'] = {'nic': nic, 'dhcp': False, 'ip': ip, 'gateway' : gateway, 'dns' : dns} - else: - archinstall.storage['_guided']['network'] = {'nic': nic} -elif nic: - archinstall.storage['_guided']['network'] = nic +# Ask or Call the helper function that asks the user to optionally configure a network. +if not archinstall.arguments.get('nic', None): + archinstall.arguments['nic'] = archinstall.ask_to_configure_network() print() print('This is your chosen configuration:') archinstall.log("-- Guided template chosen (with below config) --", level=archinstall.LOG_LEVELS.Debug) archinstall.log(json.dumps(archinstall.storage['_guided'], indent=4, sort_keys=True, cls=archinstall.JSON), level=archinstall.LOG_LEVELS.Info) +archinstall.log(json.dumps(archinstall.arguments, indent=4, sort_keys=True, cls=archinstall.JSON), level=archinstall.LOG_LEVELS.Info) print() input('Press Enter to continue.') +exit(0) """ Issue a final warning before we continue with something un-revertable. From 03a69eba2e882e430fc9c668c9d334e8818a63c3 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 17 Feb 2021 13:54:34 +0100 Subject: [PATCH 041/100] Cleaned up guided.py further by stream-lining the profiles and NIC configuration. --- examples/guided.py | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/examples/guided.py b/examples/guided.py index 119f22b38b..3226c69bbd 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -180,27 +180,19 @@ def perform_installation(device, boot_partition, language, mirrors): # Ask for archinstall-specific profiles (such as desktop environments etc) if not archinstall.arguments.get('profile', None): - while 1: - profile = archinstall.select_profile(archinstall.list_profiles()) - print(profile) - if profile: - archinstall.storage['_guided']['profile'] = profile - - if type(profile) != str: # Got a imported profile - archinstall.storage['_guided']['profile'] = profile[0] # The second return is a module, and not a handle/object. - if not profile[1]._prep_function(): - # TODO: See how we can incorporate this into - # the general log flow. As this is pre-installation - # session setup. Which creates the installation.log file. - archinstall.log( - ' * Profile\'s preparation requirements was not fulfilled.', - bg='black', - fg='red' - ) - continue - break - else: - break + archinstall.arguments['profile'] = archinstall.select_profile(archinstall.list_profiles()) +else: + archinstall.arguments['profile'] = archinstall.list_profiles()[archinstall.arguments['profile']] + +# Check the potentially selected profiles preperations to get early checks if some additional questions are needed. +if archinstall.arguments['profile']: + if not archinstall.arguments['profile']._prep_function(): + archinstall.log( + ' * Profile\'s preparation requirements was not fulfilled.', + bg='black', + fg='red' + ) + exit(1) # Additional packages (with some light weight error handling for invalid package names) if not archinstall.arguments.get('packages', None): From 758b12e6746ac76c57e7725d4e35abbb4805ad23 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 17 Feb 2021 13:59:44 +0100 Subject: [PATCH 042/100] Simplifying the profile loading a bit, and adding some debugging for it. --- archinstall/lib/user_interaction.py | 4 ++-- examples/guided.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 440e41a181..bdf8acaf33 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -188,9 +188,9 @@ def select_profile(options): if '__name__' in source_data and '_prep_function' in source_data: with profile.load_instructions(namespace=f"{selected_profile}.py") as imported: if hasattr(imported, '_prep_function'): - return profile, imported + return imported - return selected_profile + return profile raise RequirementError("Selecting profiles require a least one profile to be given as an option.") diff --git a/examples/guided.py b/examples/guided.py index 3226c69bbd..1758a39791 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -185,6 +185,7 @@ def perform_installation(device, boot_partition, language, mirrors): archinstall.arguments['profile'] = archinstall.list_profiles()[archinstall.arguments['profile']] # Check the potentially selected profiles preperations to get early checks if some additional questions are needed. +print(archinstall.arguments['profile']) if archinstall.arguments['profile']: if not archinstall.arguments['profile']._prep_function(): archinstall.log( From ad4733bbd0b0e889ad902a7d954ec985fc7a24fe Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 17 Feb 2021 14:21:46 +0100 Subject: [PATCH 043/100] Simplified profile prep-execution slightly in guided.py. The code can be improved further but it's now more easily read what's going on. --- archinstall/lib/profiles.py | 17 +++++++++++++++++ archinstall/lib/user_interaction.py | 18 +----------------- examples/guided.py | 18 +++++++++--------- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/archinstall/lib/profiles.py b/archinstall/lib/profiles.py index f9aa206ca8..7b0e78e4b0 100644 --- a/archinstall/lib/profiles.py +++ b/archinstall/lib/profiles.py @@ -157,6 +157,23 @@ def __repr__(self, *args, **kwargs): def install(self): return self.execute() + def has_prep_function(self): + with open(self.path, 'r') as source: + source_data = source.read() + + # Some crude safety checks, make sure the imported profile has + # a __name__ check and if so, check if it's got a _prep_function() + # we can call to ask for more user input. + # + # If the requirements are met, import with .py in the namespace to not + # trigger a traditional: + # if __name__ == 'moduleName' + if '__name__' in source_data and '_prep_function' in source_data: + with profile.load_instructions(namespace=f"{selected_profile}.py") as imported: + if hasattr(imported, '_prep_function'): + return True + return False + class Application(Profile): def __repr__(self, *args, **kwargs): return f'Application({os.path.basename(self.profile)})' diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index bdf8acaf33..5941903d0b 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -174,23 +174,7 @@ def select_profile(options): else: RequirementError("Selected profile does not exist.") - profile = Profile(None, selected_profile) - with open(profile.path, 'r') as source: - source_data = source.read() - - # Some crude safety checks, make sure the imported profile has - # a __name__ check and if so, check if it's got a _prep_function() - # we can call to ask for more user input. - # - # If the requirements are met, import with .py in the namespace to not - # trigger a traditional: - # if __name__ == 'moduleName' - if '__name__' in source_data and '_prep_function' in source_data: - with profile.load_instructions(namespace=f"{selected_profile}.py") as imported: - if hasattr(imported, '_prep_function'): - return imported - - return profile + return Profile(None, selected_profile) raise RequirementError("Selecting profiles require a least one profile to be given as an option.") diff --git a/examples/guided.py b/examples/guided.py index 1758a39791..4cd37972dc 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -185,15 +185,15 @@ def perform_installation(device, boot_partition, language, mirrors): archinstall.arguments['profile'] = archinstall.list_profiles()[archinstall.arguments['profile']] # Check the potentially selected profiles preperations to get early checks if some additional questions are needed. -print(archinstall.arguments['profile']) -if archinstall.arguments['profile']: - if not archinstall.arguments['profile']._prep_function(): - archinstall.log( - ' * Profile\'s preparation requirements was not fulfilled.', - bg='black', - fg='red' - ) - exit(1) +if archinstall.arguments['profile'] and archinstall.arguments['profile'].has_prep_function(): + with archinstall.arguments['profile'].load_instructions(namespace=f"{archinstall.arguments['profile'].namespace}.py") as imported: + if not imported._prep_function(): + archinstall.log( + ' * Profile\'s preparation requirements was not fulfilled.', + bg='black', + fg='red' + ) + exit(1) # Additional packages (with some light weight error handling for invalid package names) if not archinstall.arguments.get('packages', None): From a6bfe54951b09afd73dd61340107bbe5d5747f7f Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 17 Feb 2021 14:22:26 +0100 Subject: [PATCH 044/100] Variable mistake --- archinstall/lib/profiles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/profiles.py b/archinstall/lib/profiles.py index 7b0e78e4b0..1487c27793 100644 --- a/archinstall/lib/profiles.py +++ b/archinstall/lib/profiles.py @@ -169,7 +169,7 @@ def has_prep_function(self): # trigger a traditional: # if __name__ == 'moduleName' if '__name__' in source_data and '_prep_function' in source_data: - with profile.load_instructions(namespace=f"{selected_profile}.py") as imported: + with profile.load_instructions(namespace=f"{self.namespace}.py") as imported: if hasattr(imported, '_prep_function'): return True return False From 196b8884324c2646b7e4b06cf3c711df8582e651 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 17 Feb 2021 14:22:53 +0100 Subject: [PATCH 045/100] Variable mistake --- archinstall/lib/profiles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/profiles.py b/archinstall/lib/profiles.py index 1487c27793..01c3288c13 100644 --- a/archinstall/lib/profiles.py +++ b/archinstall/lib/profiles.py @@ -169,7 +169,7 @@ def has_prep_function(self): # trigger a traditional: # if __name__ == 'moduleName' if '__name__' in source_data and '_prep_function' in source_data: - with profile.load_instructions(namespace=f"{self.namespace}.py") as imported: + with self.load_instructions(namespace=f"{self.namespace}.py") as imported: if hasattr(imported, '_prep_function'): return True return False From 20b343c99376f4132c41e6c3085c33248e7b15b6 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 17 Feb 2021 14:24:48 +0100 Subject: [PATCH 046/100] Added some forgotten imports --- archinstall/lib/user_interaction.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 5941903d0b..0ca4a636bb 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -2,7 +2,9 @@ from .exceptions import * from .profiles import Profile from .locale_helpers import search_keyboard_layout -from .output import log +from .output import log, LOG_LEVELS +from .storage import storage +from .networking import list_interfaces ## TODO: Some inconsistencies between the selection processes. ## Some return the keys from the options, some the values? @@ -52,21 +54,21 @@ def ask_to_configure_network(): # Optionally configure one network interface. #while 1: # {MAC: Ifname} - interfaces = {'ISO-CONFIG' : 'Copy ISO network configuration to installation', **archinstall.list_interfaces()} - archinstall.storage['_guided']['network'] = None + interfaces = {'ISO-CONFIG' : 'Copy ISO network configuration to installation', **list_interfaces()} + storage['_guided']['network'] = None - nic = archinstall.generic_select(interfaces.values(), "Select one network interface to configure (leave blank to skip): ") + nic = generic_select(interfaces.values(), "Select one network interface to configure (leave blank to skip): ") if nic and nic != 'Copy ISO network configuration to installation': - mode = archinstall.generic_select(['DHCP (auto detect)', 'IP (static)'], f"Select which mode to configure for {nic}: ") + mode = generic_select(['DHCP (auto detect)', 'IP (static)'], f"Select which mode to configure for {nic}: ") if mode == 'IP (static)': while 1: ip = input(f"Enter the IP and subnet for {nic} (example: 192.168.0.5/24): ").strip() if ip: break else: - ArchInstall.log( + log( "You need to enter a valid IP in IP-config mode.", - level=archinstall.LOG_LEVELS.Warning, + level=LOG_LEVELS.Warning, bg='black', fg='red' ) From ad8389ccaf403f18f1ed35b51c5f03bc3f86166a Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 17 Feb 2021 14:29:13 +0100 Subject: [PATCH 047/100] Removed redundant variable --- archinstall/lib/user_interaction.py | 1 - 1 file changed, 1 deletion(-) diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 0ca4a636bb..6a55df9cca 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -55,7 +55,6 @@ def ask_to_configure_network(): #while 1: # {MAC: Ifname} interfaces = {'ISO-CONFIG' : 'Copy ISO network configuration to installation', **list_interfaces()} - storage['_guided']['network'] = None nic = generic_select(interfaces.values(), "Select one network interface to configure (leave blank to skip): ") if nic and nic != 'Copy ISO network configuration to installation': From 930fc994d94132fc7676f4297465a2ad90487b75 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 17 Feb 2021 14:29:51 +0100 Subject: [PATCH 048/100] Removed redundant variable --- examples/guided.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/guided.py b/examples/guided.py index 4cd37972dc..5fdc318b4e 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -213,7 +213,6 @@ def perform_installation(device, boot_partition, language, mirrors): print() print('This is your chosen configuration:') archinstall.log("-- Guided template chosen (with below config) --", level=archinstall.LOG_LEVELS.Debug) -archinstall.log(json.dumps(archinstall.storage['_guided'], indent=4, sort_keys=True, cls=archinstall.JSON), level=archinstall.LOG_LEVELS.Info) archinstall.log(json.dumps(archinstall.arguments, indent=4, sort_keys=True, cls=archinstall.JSON), level=archinstall.LOG_LEVELS.Info) print() From a9ce3e539028b2484991d21528a09cc3f41210de Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 17 Feb 2021 14:54:45 +0100 Subject: [PATCH 049/100] Testing auto-filter in the JSON encoder based on ! points markering sensitive data. --- archinstall/lib/general.py | 5 ++++- examples/guided.py | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py index dc94b0631c..97ad15655c 100644 --- a/archinstall/lib/general.py +++ b/archinstall/lib/general.py @@ -45,7 +45,10 @@ def _encode(obj): else: val = JSON_Encoder._encode(val) del(obj[key]) - obj[JSON_Encoder._encode(key)] = val + if type(key) == str and key[0] == '!': + obj[JSON_Encoder._encode(key)] = '******' + else: + obj[JSON_Encoder._encode(key)] = val return obj elif hasattr(obj, 'json'): return obj.json() diff --git a/examples/guided.py b/examples/guided.py index 5fdc318b4e..cf7c3ef32f 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -150,9 +150,9 @@ def perform_installation(device, boot_partition, language, mirrors): archinstall.arguments['harddrive'].keep_partitions = False # Get disk encryption password (or skip if blank) -if not archinstall.arguments.get('encryption-password', None): - archinstall.arguments['encryption-password'] = archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ') -archinstall.arguments['harddrive'].encryption_password = archinstall.arguments['encryption-password'] +if not archinstall.arguments.get('!encryption-password', None): + archinstall.arguments['!encryption-password'] = archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ') +archinstall.arguments['harddrive'].encryption_password = archinstall.arguments['!encryption-password'] # Get the hostname for the machine if not archinstall.arguments.get('hostname', None): From c55fc11cae264445579dc34674c24922c96f63d4 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 17 Feb 2021 15:13:45 +0100 Subject: [PATCH 050/100] Added a comment --- examples/guided.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/guided.py b/examples/guided.py index cf7c3ef32f..bccedb0a89 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -243,6 +243,9 @@ def perform_installation(device, boot_partition, language, mirrors): sys.stdin.read() SIG_TRIGGER = False signal.signal(signal.SIGINT, sig_handler) + +# Put back the default/original signal handler now that we're done catching +# and interrupting SIGINT with "Do you really want to abort". print() signal.signal(signal.SIGINT, original_sigint_handler) From bbf9face05fe573d760eb8ca662fdf5d34ed6902 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 13:08:44 +0100 Subject: [PATCH 051/100] Removing make script for building binary and pypi dist. These should be done separately and should be done with build instructions not a bash script (i think). --- make.sh | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100755 make.sh diff --git a/make.sh b/make.sh deleted file mode 100755 index f733ce10a6..0000000000 --- a/make.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -# Description: Binary builder for https://archlinux.life/bin/ - -VERSION=$(cat VERSION) - -rm -rf archinstall.egg-info/ build/ src/ pkg/ dist/ archinstall.build/ "archinstall-v${VERSION}-x86_64/" *.pkg.*.xz archinstall-*.tar.gz - -#nuitka3 --standalone --show-progress archinstall -#cp -r examples/ archinstall.dist/ -#mv archinstall.dist "archinstall-v${VERSION}-x86_64" -#tar -czvf "archinstall-v${VERSION}.tar.gz" "archinstall-v${VERSION}-x86_64" - -# makepkg -f -python3 setup.py sdist bdist_wheel -echo 'python3 -m twine upload dist/* && rm -rf dist/' -python3 -m twine upload dist/* && rm -rf dist/ - -rm -rf archinstall.egg-info/ build/ src/ pkg/ archinstall.build/ "archinstall-v${VERSION}-x86_64/" From 8da8608e220a62c993f7bdf49c2c3d7c9b4a43aa Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 14:42:43 +0100 Subject: [PATCH 052/100] Added a small menu instead of a one-liner to select what to do with the disk if it has partitions. --- archinstall/lib/user_interaction.py | 8 ++++ examples/guided.py | 59 ++++++++++++++++++----------- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 6a55df9cca..e20e98b18b 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -87,6 +87,14 @@ def ask_to_configure_network(): return None +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.' + } + + return generic_select(options.values(), "Found partitions on the selected drive, (select by number) what do you want to do: ") + 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 diff --git a/examples/guided.py b/examples/guided.py index bccedb0a89..27c005d654 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -101,6 +101,9 @@ def perform_installation(device, boot_partition, language, mirrors): # 2. If so, ask if we should keep them or wipe everything if archinstall.arguments['harddrive'].has_partitions(): archinstall.log(f" ! {archinstall.arguments['harddrive']} contains existing partitions", fg='red') + + # We curate a list pf supported paritions + # and print those that we don't support. partition_mountpoints = {} for partition in archinstall.arguments['harddrive']: try: @@ -109,44 +112,56 @@ def perform_installation(device, boot_partition, language, mirrors): partition_mountpoints[partition] = None except archinstall.UnknownFilesystemFormat as err: archinstall.log(f" {partition} (Filesystem not supported)", fg='red') - #archinstall.log(f"Current filesystem is not supported: {err}", fg='red') - #input(f"Do you wish to erase all data? (y/n):") - if (option := input('Do you wish to keep one/more existing partitions or format entire drive? (k/f): ')).lower() in ('k', 'keep'): + # We then ask what to do with the paritions. + if (option := archinstall.ask_for_disk_layout()) == 'keep-existing': archinstall.arguments['harddrive'].keep_partitions = True - archinstall.log(f" ** You will now select where (inside the installation) to mount partitions. **") - archinstall.log(f" ** The root would be a simple / and the boot partition /boot as it's relative paths. **") + archinstall.log(f" ** You will now select which partitions to use by selecting mount points (inside the installation). **") + archinstall.log(f" ** The root would be a simple / and the boot partition /boot (as all paths are relative inside the installation). **") while True: - partition = archinstall.generic_select(partition_mountpoints.keys(), "Select a partition to assign mount-point to (leave blank when done): ") + # Select a partition + partition = archinstall.generic_select(partition_mountpoints.keys(), + "Select a partition by number that you want to set a mount-point for (leave blank when done): ") if not partition: break + # Select a mount-point mountpoint = input(f"Enter a mount-point for {partition}: ").strip(' ') + if len(mountpoint): - while 1: - new_filesystem = input(f"Enter a valid filesystem for {partition} (leave blank for {partition.filesystem}): ").strip(' ') - if len(new_filesystem) <= 0: + # Get a valid & supported filesystem for the parition: + while 1: + new_filesystem = input(f"Enter a valid filesystem for {partition} (leave blank for {partition.filesystem}): ").strip(' ') + if len(new_filesystem) <= 0: + break + + # Since the potentially new filesystem is new + # we have to check if we support it. We can do this by formatting /dev/null with the partitions filesystem. + # There's a nice wrapper for this on the partition object itself that supports a path-override during .format() + try: + partition.format(new_filesystem, path='/dev/null', log_formating=False, allow_formatting=True) + except archinstall.UnknownFilesystemFormat: + archinstall.log(f"Selected filesystem is not supported yet. If you want archinstall to support '{new_filesystem}', please create a issue-ticket suggesting it on github at https://github.com/Torxed/archinstall/issues.") + archinstall.log(f"Until then, please enter another supported filesystem.") + continue + except archinstall.SysCallError: + pass # Expected exception since mkfs. can not format /dev/null. + # But that means our .format() function supported it. break - try: - partition.format(new_filesystem, path='/dev/null', log_formating=False, allow_formatting=True) - except archinstall.UnknownFilesystemFormat: - archinstall.log(f"Selected filesystem is not supported yet, if you wish archinstall should support '{new_filesystem}' please create a issue-ticket suggesting it on github at https://github.com/Torxed/archinstall/issues.") - archinstall.log(f"Until then, please enter another supported filesystem.") - continue - except archinstall.SysCallError: - pass # Supported, but mkfs could not format /dev/null which is the whole point of /dev/null in path :) - break - - if len(mountpoint): + # When we've selected all three criterias, + # We can safely mark the partition for formatting and where to mount it. + # TODO: allow_formatting might be redundant since target_mountpoint should only be + # set if we actually want to format it anyway. partition.allow_formatting = True partition.target_mountpoint = mountpoint + # Only overwrite the filesystem definition if we selected one: if len(new_filesystem): partition.filesystem = new_filesystem archinstall.log('Using existing partition table reported above.') - else: + elif option == 'format-all': archinstall.arguments['harddrive'].keep_partitions = False # Get disk encryption password (or skip if blank) @@ -217,7 +232,6 @@ def perform_installation(device, boot_partition, language, mirrors): print() input('Press Enter to continue.') -exit(0) """ Issue a final warning before we continue with something un-revertable. @@ -249,6 +263,7 @@ def perform_installation(device, boot_partition, language, mirrors): print() signal.signal(signal.SIGINT, original_sigint_handler) +exit(0) """ Setup the blockdevice, filesystem (and optionally encryption). Once that's done, we'll hand over to perform_installation() From e537a57a8878f71d4592ff030b22ad073a3e64da Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 14:43:48 +0100 Subject: [PATCH 053/100] Fixed indentation on output. --- examples/guided.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/guided.py b/examples/guided.py index 27c005d654..3e86cdf0bf 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -100,7 +100,7 @@ def perform_installation(device, boot_partition, language, mirrors): # 3. Check that we support the current partitions # 2. If so, ask if we should keep them or wipe everything if archinstall.arguments['harddrive'].has_partitions(): - archinstall.log(f" ! {archinstall.arguments['harddrive']} contains existing partitions", fg='red') + archinstall.log(f"! {archinstall.arguments['harddrive']} contains existing partitions", fg='red') # We curate a list pf supported paritions # and print those that we don't support. From dd66aeda9afac70c087d7a399eec456510d4bca8 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 14:44:51 +0100 Subject: [PATCH 054/100] Re-formatted a question a bit. --- examples/guided.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/guided.py b/examples/guided.py index 3e86cdf0bf..2b49d88a0e 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -100,7 +100,7 @@ def perform_installation(device, boot_partition, language, mirrors): # 3. Check that we support the current partitions # 2. If so, ask if we should keep them or wipe everything if archinstall.arguments['harddrive'].has_partitions(): - archinstall.log(f"! {archinstall.arguments['harddrive']} contains existing partitions", fg='red') + archinstall.log(f"{archinstall.arguments['harddrive']} contains the following partitions:", fg='red') # We curate a list pf supported paritions # and print those that we don't support. From 775a26f738002ad124200716b68f92ebc9e91a5c Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 14:47:24 +0100 Subject: [PATCH 055/100] Added a abort message --- archinstall/lib/user_interaction.py | 5 +++-- examples/guided.py | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index e20e98b18b..01e3372cda 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -90,10 +90,11 @@ 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.' + 'format-all' : 'Format entire drive and setup a basic partition scheme.', + 'abort' : 'abort' } - return generic_select(options.values(), "Found partitions on the selected drive, (select by number) what do you want to do: ") + return generic_select(options.values(), "Found partitions on the selected drive, (select by number) what you want to do: ") def generic_select(options, input_text="Select one of the above by index or absolute value: ", sort=True): """ diff --git a/examples/guided.py b/examples/guided.py index 2b49d88a0e..0077b50560 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -114,7 +114,10 @@ def perform_installation(device, boot_partition, language, mirrors): archinstall.log(f" {partition} (Filesystem not supported)", fg='red') # We then ask what to do with the paritions. - if (option := archinstall.ask_for_disk_layout()) == 'keep-existing': + if (option := archinstall.ask_for_disk_layout()) == 'abort': + archinstall.log(f"Safely aborting the installation. No changes to the disk or system has been made.") + exit(1) + elif option == 'keep-existing': archinstall.arguments['harddrive'].keep_partitions = True archinstall.log(f" ** You will now select which partitions to use by selecting mount points (inside the installation). **") From 94daa8b98b8dba328e45b11ba51b25963c5dcf76 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 14:47:41 +0100 Subject: [PATCH 056/100] Added a abort message --- archinstall/lib/user_interaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 01e3372cda..2c8c30f8ce 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -91,7 +91,7 @@ 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' + 'abort' : 'Abort the installation.' } return generic_select(options.values(), "Found partitions on the selected drive, (select by number) what you want to do: ") From 476006abe8060235299fd3bddbfe69e597f7f988 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 14:51:18 +0100 Subject: [PATCH 057/100] Fixed expected return value from ask_for_disk_layout(). I think I have to throw an eye on generic_select() and it's expected return value in general.. But that's later. --- archinstall/lib/user_interaction.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 2c8c30f8ce..5861fff396 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -94,7 +94,8 @@ def ask_for_disk_layout(): 'abort' : 'Abort the installation.' } - return generic_select(options.values(), "Found partitions on the selected drive, (select by number) what you want to do: ") + value = generic_select(options.values(), "Found partitions on the selected drive, (select by number) what you want to do: ") + 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): """ From 1d913f50ed630c7a1a2985e6ed8b9f4aa0bc08dd Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 14:55:21 +0100 Subject: [PATCH 058/100] Added some debugging. --- archinstall/lib/disk.py | 15 ++++++++------- examples/guided.py | 5 +++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index d76c6d7e9a..e77f2cd1e7 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -272,13 +272,14 @@ def __init__(self, blockdevice, mode=GPT): self.mode = mode def __enter__(self, *args, **kwargs): - if self.mode == GPT: - if sys_command(f'/usr/bin/parted -s {self.blockdevice.device} mklabel gpt',).exit_code == 0: - return self - else: - raise DiskError(f'Problem setting the partition format to GPT:', f'/usr/bin/parted -s {self.blockdevice.device} mklabel gpt') - else: - raise DiskError(f'Unknown mode selected to format in: {self.mode}') + #if self.mode == GPT: + # if sys_command(f'/usr/bin/parted -s {self.blockdevice.device} mklabel gpt',).exit_code == 0: + # return self + # else: + # raise DiskError(f'Problem setting the partition format to GPT:', f'/usr/bin/parted -s {self.blockdevice.device} mklabel gpt') + #else: + # raise DiskError(f'Unknown mode selected to format in: {self.mode}') + print('Keep partitions:', self.blockdevice.keep_partitions) def __repr__(self): return f"Filesystem(blockdevice={self.blockdevice}, mode={self.mode})" diff --git a/examples/guided.py b/examples/guided.py index 0077b50560..fbe6ea47f7 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -266,12 +266,13 @@ def perform_installation(device, boot_partition, language, mirrors): print() signal.signal(signal.SIGINT, original_sigint_handler) -exit(0) """ Setup the blockdevice, filesystem (and optionally encryption). Once that's done, we'll hand over to perform_installation() """ -with archinstall.Filesystem(harddrive, archinstall.GPT) as fs: +with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs: + print('Debug') + exit(0) # Use partitioning helper to set up the disk partitions. if disk_password: fs.use_entire_disk('luks2') From 9fe4b7b5c79064e67949bc690c9c9563839d56f9 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 14:56:18 +0100 Subject: [PATCH 059/100] Added some debugging. --- archinstall/lib/disk.py | 1 + 1 file changed, 1 insertion(+) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index e77f2cd1e7..70d2c29fbd 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -279,6 +279,7 @@ def __enter__(self, *args, **kwargs): # raise DiskError(f'Problem setting the partition format to GPT:', f'/usr/bin/parted -s {self.blockdevice.device} mklabel gpt') #else: # raise DiskError(f'Unknown mode selected to format in: {self.mode}') + print(self.blockdevice) print('Keep partitions:', self.blockdevice.keep_partitions) def __repr__(self): From 75eb42be5111697b325a5ca5d400d49dc3e30c45 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 14:57:17 +0100 Subject: [PATCH 060/100] Added some debugging. --- archinstall/lib/disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 70d2c29fbd..f01300b381 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -279,7 +279,7 @@ def __enter__(self, *args, **kwargs): # raise DiskError(f'Problem setting the partition format to GPT:', f'/usr/bin/parted -s {self.blockdevice.device} mklabel gpt') #else: # raise DiskError(f'Unknown mode selected to format in: {self.mode}') - print(self.blockdevice) + print(type(self.blockdevice)) print('Keep partitions:', self.blockdevice.keep_partitions) def __repr__(self): From 30467ca50f289dc474d4cf906d05778c85d8d86d Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 14:57:47 +0100 Subject: [PATCH 061/100] Added some debugging. --- examples/guided.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/guided.py b/examples/guided.py index fbe6ea47f7..364e0a5015 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -94,6 +94,7 @@ def perform_installation(device, boot_partition, language, mirrors): archinstall.arguments['harddrive'] = archinstall.BlockDevice(archinstall.arguments['harddrive']) else: archinstall.arguments['harddrive'] = archinstall.select_disk(archinstall.all_disks()) +print(type(archinstall.arguments['harddrive']), archinstall.arguments['harddrive']) # Perform a quick sanity check on the selected harddrive. # 1. Check if it has partitions From 2fe8a173f487f7bed76a4745a8c957aca10f07f9 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 14:58:31 +0100 Subject: [PATCH 062/100] Added some debugging. --- examples/guided.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/guided.py b/examples/guided.py index 364e0a5015..bfe8b4f3b3 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -94,7 +94,6 @@ def perform_installation(device, boot_partition, language, mirrors): archinstall.arguments['harddrive'] = archinstall.BlockDevice(archinstall.arguments['harddrive']) else: archinstall.arguments['harddrive'] = archinstall.select_disk(archinstall.all_disks()) -print(type(archinstall.arguments['harddrive']), archinstall.arguments['harddrive']) # Perform a quick sanity check on the selected harddrive. # 1. Check if it has partitions @@ -168,6 +167,8 @@ def perform_installation(device, boot_partition, language, mirrors): elif option == 'format-all': archinstall.arguments['harddrive'].keep_partitions = False +print(type(archinstall.arguments['harddrive']), archinstall.arguments['harddrive']) + # Get disk encryption password (or skip if blank) if not archinstall.arguments.get('!encryption-password', None): archinstall.arguments['!encryption-password'] = archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ') From ffbb952eb35b25c7538dca3426854292b67e2cc3 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 14:58:58 +0100 Subject: [PATCH 063/100] Added some debugging. --- examples/guided.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/guided.py b/examples/guided.py index bfe8b4f3b3..264ff44e16 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -167,8 +167,6 @@ def perform_installation(device, boot_partition, language, mirrors): elif option == 'format-all': archinstall.arguments['harddrive'].keep_partitions = False -print(type(archinstall.arguments['harddrive']), archinstall.arguments['harddrive']) - # Get disk encryption password (or skip if blank) if not archinstall.arguments.get('!encryption-password', None): archinstall.arguments['!encryption-password'] = archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ') @@ -230,12 +228,15 @@ def perform_installation(device, boot_partition, language, mirrors): if not archinstall.arguments.get('nic', None): archinstall.arguments['nic'] = archinstall.ask_to_configure_network() +print(type(archinstall.arguments['harddrive']), archinstall.arguments['harddrive']) + print() print('This is your chosen configuration:') archinstall.log("-- Guided template chosen (with below config) --", level=archinstall.LOG_LEVELS.Debug) archinstall.log(json.dumps(archinstall.arguments, indent=4, sort_keys=True, cls=archinstall.JSON), level=archinstall.LOG_LEVELS.Info) print() +print(type(archinstall.arguments['harddrive']), archinstall.arguments['harddrive']) input('Press Enter to continue.') """ From c5694393c6a9d53cbc7a2886e733b354d5055772 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 15:10:58 +0100 Subject: [PATCH 064/100] Fixed the JSON_Encoder. The issue was that dictionaries are mutable, and dumping dictionaries and replacing keys also replaces the original value. --- archinstall/lib/general.py | 9 +++++---- examples/guided.py | 3 --- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py index 97ad15655c..e87e410203 100644 --- a/archinstall/lib/general.py +++ b/archinstall/lib/general.py @@ -37,6 +37,7 @@ def _encode(obj): ## We'll need to iterate not just the value that default() usually gets passed ## But also iterate manually over each key: value pair in order to trap the keys. + copy = {} for key, val in list(obj.items()): if isinstance(val, dict): val = json.loads(json.dumps(val, cls=JSON)) # This, is a EXTREMELY ugly hack.. @@ -44,12 +45,12 @@ def _encode(obj): # trigger a encoding of sub-dictionaries. else: val = JSON_Encoder._encode(val) - del(obj[key]) + if type(key) == str and key[0] == '!': - obj[JSON_Encoder._encode(key)] = '******' + copy[JSON_Encoder._encode(key)] = '******' else: - obj[JSON_Encoder._encode(key)] = val - return obj + copy[JSON_Encoder._encode(key)] = val + return copy elif hasattr(obj, 'json'): return obj.json() elif hasattr(obj, '__dump__'): diff --git a/examples/guided.py b/examples/guided.py index 264ff44e16..fbe6ea47f7 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -228,15 +228,12 @@ def perform_installation(device, boot_partition, language, mirrors): if not archinstall.arguments.get('nic', None): archinstall.arguments['nic'] = archinstall.ask_to_configure_network() -print(type(archinstall.arguments['harddrive']), archinstall.arguments['harddrive']) - print() print('This is your chosen configuration:') archinstall.log("-- Guided template chosen (with below config) --", level=archinstall.LOG_LEVELS.Debug) archinstall.log(json.dumps(archinstall.arguments, indent=4, sort_keys=True, cls=archinstall.JSON), level=archinstall.LOG_LEVELS.Info) print() -print(type(archinstall.arguments['harddrive']), archinstall.arguments['harddrive']) input('Press Enter to continue.') """ From 1ef63147d0eeb002dcc9475f2fcec3abddc1298d Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 15:54:23 +0100 Subject: [PATCH 065/100] Tweaked logging slightly. --- archinstall/lib/disk.py | 29 ++++++++++++++++++++--------- archinstall/lib/output.py | 11 +++++++---- examples/guided.py | 2 -- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index f01300b381..a0fcfddf48 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -7,6 +7,7 @@ ROOT_DIR_PATTERN = re.compile('^.*?/devices') GPT = 0b00000001 +MBR = 0b00000010 #import ctypes #import ctypes.util @@ -113,6 +114,10 @@ def partition(self): all_partitions = self.partitions return [all_partitions[k] for k in all_partitions] + @property + def partition_table_type(self): + return GPT + def has_partitions(self): return len(self.partitions) @@ -272,15 +277,21 @@ def __init__(self, blockdevice, mode=GPT): self.mode = mode def __enter__(self, *args, **kwargs): - #if self.mode == GPT: - # if sys_command(f'/usr/bin/parted -s {self.blockdevice.device} mklabel gpt',).exit_code == 0: - # return self - # else: - # raise DiskError(f'Problem setting the partition format to GPT:', f'/usr/bin/parted -s {self.blockdevice.device} mklabel gpt') - #else: - # raise DiskError(f'Unknown mode selected to format in: {self.mode}') - print(type(self.blockdevice)) - print('Keep partitions:', self.blockdevice.keep_partitions) + if self.blockdevice.keep_partitions is False: + log(f'Wiping {self.blockdevice} by using partition format {self.mode}', level=LOG_LEVELS.Debug) + if self.mode == GPT: + if sys_command(f'/usr/bin/parted -s {self.blockdevice.device} mklabel gpt',).exit_code == 0: + return self + else: + raise DiskError(f'Problem setting the partition format to GPT:', f'/usr/bin/parted -s {self.blockdevice.device} mklabel gpt') + else: + raise DiskError(f'Unknown mode selected to format in: {self.mode}') + + # TODO: partition_table_type is hardcoded to GPT at the moment. This has to be changed. + elif self.mode == self.blockdevice.partition_table_type: + log(f'Kept partition format {self.mode} for {self.blockdevice}', level=LOG_LEVELS.Debug) + else: + raise DiskError(f'The selected partition table format {self.mode} does not match that of {self.blockdevice}.') def __repr__(self): return f"Filesystem(blockdevice={self.blockdevice}, mode={self.mode})" diff --git a/archinstall/lib/output.py b/archinstall/lib/output.py index 956ad0c42b..0e0a295b87 100644 --- a/archinstall/lib/output.py +++ b/archinstall/lib/output.py @@ -5,6 +5,9 @@ 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. +# It's more Pythonistic or w/e you want to call it. class LOG_LEVELS: Critical = 0b001 Error = 0b010 @@ -108,10 +111,10 @@ def log(*args, **kwargs): # In that case, we'll drop it. return None - try: - journald.log(string, level=kwargs['level']) - except ModuleNotFoundError: - pass # Ignore writing to journald + try: + journald.log(string, level=kwargs.get('level', LOG_LEVELS.Info)) + except ModuleNotFoundError: + pass # Ignore writing to journald # Finally, print the log unless we skipped it based on level. # We use sys.stdout.write()+flush() instead of print() to try and diff --git a/examples/guided.py b/examples/guided.py index fbe6ea47f7..15f9ba1cca 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -271,8 +271,6 @@ def perform_installation(device, boot_partition, language, mirrors): Once that's done, we'll hand over to perform_installation() """ with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs: - print('Debug') - exit(0) # Use partitioning helper to set up the disk partitions. if disk_password: fs.use_entire_disk('luks2') From 948878e9e3c2f4e9c8c77b11704d529f952d478c Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 16:09:51 +0100 Subject: [PATCH 066/100] Added some debugging. --- examples/guided.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/guided.py b/examples/guided.py index 15f9ba1cca..9c4f208a77 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -271,6 +271,9 @@ def perform_installation(device, boot_partition, language, mirrors): Once that's done, we'll hand over to perform_installation() """ with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs: + for partition in archinstall.arguments['harddrive'].partitions: + print(partition) + exit(0) # Use partitioning helper to set up the disk partitions. if disk_password: fs.use_entire_disk('luks2') From 979d162551d0796f4cbefd7a450149896e094caf Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 16:14:44 +0100 Subject: [PATCH 067/100] Added some debugging. --- examples/guided.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/guided.py b/examples/guided.py index 9c4f208a77..e9ceae1922 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -271,7 +271,7 @@ def perform_installation(device, boot_partition, language, mirrors): Once that's done, we'll hand over to perform_installation() """ with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs: - for partition in archinstall.arguments['harddrive'].partitions: + for partition in archinstall.arguments['harddrive']: print(partition) exit(0) # Use partitioning helper to set up the disk partitions. From 0b3879ac58d2896ad01270f835fc2819817c6fc1 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 16:18:56 +0100 Subject: [PATCH 068/100] Removing part of the old formatting scheme. New implementation roughly 80% done. --- archinstall/lib/disk.py | 4 +++- examples/guided.py | 22 ++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index a0fcfddf48..2be2658540 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -173,11 +173,13 @@ def __repr__(self, *args, **kwargs): else: return f'Partition(path={self.path}, fs={self.filesystem}{mount_repr})' - def format(self, filesystem, path=None, allow_formatting=None, log_formating=True): + def format(self, filesystem=None, path=None, allow_formatting=None, log_formating=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. """ + if filesystem is None: + filesystem = self.filesystem if path is None: path = self.path if allow_formatting is None: diff --git a/examples/guided.py b/examples/guided.py index e9ceae1922..7aec0611f4 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -271,19 +271,17 @@ def perform_installation(device, boot_partition, language, mirrors): Once that's done, we'll hand over to perform_installation() """ with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs: - for partition in archinstall.arguments['harddrive']: - print(partition) - exit(0) - # Use partitioning helper to set up the disk partitions. - if disk_password: - fs.use_entire_disk('luks2') + if archinstall.arguments['harddrive'].keep_partitions is False: + if disk_password: + fs.use_entire_disk('luks2') + else: + fs.use_entire_disk('ext4') else: - fs.use_entire_disk('ext4') - - if harddrive.partition[1].size == '512M': - raise OSError('Trying to encrypt the boot partition for petes sake..') - harddrive.partition[0].format('vfat') - + for partition in archinstall.arguments['harddrive']: + if partition.allow_formatting: + partition.format() + + exit(0) if disk_password: # First encrypt and unlock, then format the desired partition inside the encrypted part. # archinstall.luks2() encrypts the partition when entering the with context manager, and From 6306de4bfee2d44ab1f362078a47d6d9a05835ef Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 16:52:06 +0100 Subject: [PATCH 069/100] Reworked the guided partitioning logic to better match new expectations of flexability. Still some work to be done and features to be implemented, but the structure is taking place --- archinstall/lib/disk.py | 8 +++++++- archinstall/lib/luks.py | 28 +++++++++++++++++++++++----- archinstall/lib/user_interaction.py | 12 ++++++++++++ examples/guided.py | 16 +++++++++------- 4 files changed, 51 insertions(+), 13 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 2be2658540..2f3d8233f3 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -196,28 +196,34 @@ def format(self, filesystem=None, path=None, allow_formatting=None, log_formatin if b'UUID' not in o: raise DiskError(f'Could not format {path} with {filesystem} because: {o}') self.filesystem = 'btrfs' + elif filesystem == 'vfat': o = b''.join(sys_command(f'/usr/bin/mkfs.vfat -F32 {path}')) if (b'mkfs.fat' not in o and b'mkfs.vfat' not in o) or b'command not found' in o: raise DiskError(f'Could not format {path} with {filesystem} because: {o}') self.filesystem = 'vfat' + elif filesystem == 'ext4': if (handle := sys_command(f'/usr/bin/mkfs.ext4 -F {path}')).exit_code != 0: raise DiskError(f'Could not format {path} with {filesystem} because: {b"".join(handle)}') self.filesystem = 'ext4' + elif filesystem == 'xfs': if (handle := sys_command(f'/usr/bin/mkfs.xfs -f {path}')).exit_code != 0: raise DiskError(f'Could not format {path} with {filesystem} because: {b"".join(handle)}') self.filesystem = 'xfs' + elif filesystem == 'f2fs': if (handle := sys_command(f'/usr/bin/mkfs.f2fs -f {path}')).exit_code != 0: raise DiskError(f'Could not format {path} with {filesystem} because: {b"".join(handle)}') self.filesystem = 'f2fs' + elif filesystem == 'crypto_LUKS': from .luks import luks2 encrypted_partition = luks2(self, None, None) encrypted_partition.format(path) self.filesystem = 'crypto_LUKS' + else: raise UnknownFilesystemFormat(f"Fileformat '{filesystem}' is not yet implemented.") return True @@ -327,7 +333,7 @@ def use_entire_disk(self, prep_mode=None): if prep_mode == 'luks2': self.add_partition('primary', start='513MiB', end='100%') else: - self.add_partition('primary', start='513MiB', end='100%', format='ext4') + self.add_partition('primary', start='513MiB', end='100%', format=prep_mode) def add_partition(self, type, start, end, format=None): log(f'Adding partition to {self.blockdevice}', level=LOG_LEVELS.Info) diff --git a/archinstall/lib/luks.py b/archinstall/lib/luks.py index d62c2d4b4d..b98994efd7 100644 --- a/archinstall/lib/luks.py +++ b/archinstall/lib/luks.py @@ -6,17 +6,28 @@ from .storage import storage class luks2(): - def __init__(self, partition, mountpoint, password, *args, **kwargs): + def __init__(self, partition, mountpoint, password, key_file=None, *args, **kwargs): self.password = password self.partition = partition self.mountpoint = mountpoint self.args = args self.kwargs = kwargs + self.key_file = key_file self.filesystem = 'crypto_LUKS' def __enter__(self): - key_file = self.encrypt(self.partition, self.password, *self.args, **self.kwargs) - return self.unlock(self.partition, self.mountpoint, key_file) + if self.partition.allow_formatting: + self.key_file = self.encrypt(self.partition, *self.args, **self.kwargs) + else: + if not self.key_file: + self.key_file = f"/tmp/{os.path.basename(self.partition.path)}.disk_pw" # TODO: Make disk-pw-file randomly unique? + + if type(self.password) != bytes: self.password = bytes(self.password, 'UTF-8') + + with open(self.key_file, 'wb') as fh: + fh.write(self.password) + + return self.unlock(self.partition, self.mountpoint, self.key_file) def __exit__(self, *args, **kwargs): # TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager @@ -24,13 +35,20 @@ def __exit__(self, *args, **kwargs): raise args[1] return True - def encrypt(self, partition, password, key_size=512, hash_type='sha512', iter_time=10000, key_file=None): + 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? log(f'Encrypting {partition} (This might take a while)', level=LOG_LEVELS.Info) if not key_file: - key_file = f"/tmp/{os.path.basename(self.partition.path)}.disk_pw" # TODO: Make disk-pw-file randomly unique? + if self.key_file: + key_file = self.key_file + else: + key_file = f"/tmp/{os.path.basename(self.partition.path)}.disk_pw" # TODO: Make disk-pw-file randomly unique? + + if not password: + password = self.password + if type(password) != bytes: password = bytes(password, 'UTF-8') with open(key_file, 'wb') as fh: diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 5861fff396..7e7f587380 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -97,6 +97,18 @@ def ask_for_disk_layout(): value = generic_select(options.values(), "Found partitions on the selected drive, (select by number) what you want to do: ") return next((key for key, val in options.items() if val == value), None) +def ask_for_main_filesystem_format(): + options = { + 'btrfs' : 'btrfs', + 'ext4' : 'ext4', + 'xfs' : 'xfs', + 'f2fs' : 'f2fs', + 'vfat' : 'vfat' + } + + value = generic_select(options.values(), "Select your main partitions filesystem by number or free-text: ") + 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): """ A generic select function that does not output anything diff --git a/examples/guided.py b/examples/guided.py index 7aec0611f4..b289016b1b 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -165,6 +165,7 @@ def perform_installation(device, boot_partition, language, mirrors): archinstall.log('Using existing partition table reported above.') elif option == 'format-all': + archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format() archinstall.arguments['harddrive'].keep_partitions = False # Get disk encryption password (or skip if blank) @@ -272,22 +273,23 @@ def perform_installation(device, boot_partition, language, mirrors): """ with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs: if archinstall.arguments['harddrive'].keep_partitions is False: - if disk_password: + if archinstall.arguments.get('!encryption-password', None): + # Set a temporary partition format to indicate that the partitions is encrypted. + # Later on, we'll mount it and put an actual filesystem inside this encrypted container. fs.use_entire_disk('luks2') else: - fs.use_entire_disk('ext4') + fs.use_entire_disk(archinstall.arguments.get('filesystem', 'ext4')) else: for partition in archinstall.arguments['harddrive']: if partition.allow_formatting: partition.format() - - exit(0) - if disk_password: + + if archinstall.arguments.get('!encryption-password', None): # First encrypt and unlock, then format the desired partition inside the encrypted part. # archinstall.luks2() encrypts the partition when entering the with context manager, and # unlocks the drive so that it can be used as a normal block-device within archinstall. - with archinstall.luks2(harddrive.partition[1], 'luksloop', disk_password) as unlocked_device: - unlocked_device.format('btrfs') + with archinstall.luks2(harddrive.partition[1], 'luksloop', archinstall.arguments.get('!encryption-password', None)) as unlocked_device: + unlocked_device.format(archinstall.arguments.get('filesystem', 'btrfs')) perform_installation(unlocked_device, harddrive.partition[0], From fb55e318e5dc87d05da4c636722d24ab9e9cb5b1 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 17:13:21 +0100 Subject: [PATCH 070/100] Added two new functions. partition.safe_to_format() and partition.has_content(). The first does some sanity checks to verify if we can format the partition or not. The second temporarly mounts the parition and checks if there's content inside and returns accordingly. --- archinstall/lib/disk.py | 20 ++++++++++++++++++-- examples/guided.py | 4 +++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 2f3d8233f3..16d6f7045d 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -1,4 +1,4 @@ -import glob, re, os, json, time # Time is only used to gracefully wait for new paritions to come online +import glob, re, os, json, time, hashlib from collections import OrderedDict from .exceptions import DiskError from .general import * @@ -173,6 +173,22 @@ def __repr__(self, *args, **kwargs): else: return f'Partition(path={self.path}, fs={self.filesystem}{mount_repr})' + def has_content(self): + temporary_mountpoint = '/tmp/'+hashlib.md5(bytes(f"{time.time()}", 'UTF-8')+os.urandom(12)).hexdigest() + if (handle := sys_command(f'/usr/bin/mount {self.path} {temporary_mountpoint}')).exit_code != 0: + raise DiskError(f'Could not mount and check for content on {self.path} because: {b"".join(handle)}') + + files = len(glob.glob(f"{temporary_mountpoint}/*")) + sys_command(f'/usr/bin/umount {temporary_mountpoint}') + + return True if files > 0 else False + + def safe_to_format(self): + if self.target_mountpoint == '/boot' and self.has_content(): + return False + + return True + def format(self, filesystem=None, path=None, allow_formatting=None, log_formating=True): """ Format can be given an overriding path, for instance /dev/null to test @@ -223,7 +239,7 @@ def format(self, filesystem=None, path=None, allow_formatting=None, log_formatin encrypted_partition = luks2(self, None, None) encrypted_partition.format(path) self.filesystem = 'crypto_LUKS' - + else: raise UnknownFilesystemFormat(f"Fileformat '{filesystem}' is not yet implemented.") return True diff --git a/examples/guided.py b/examples/guided.py index b289016b1b..fc1fe88d4a 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -281,8 +281,10 @@ def perform_installation(device, boot_partition, language, mirrors): fs.use_entire_disk(archinstall.arguments.get('filesystem', 'ext4')) else: for partition in archinstall.arguments['harddrive']: - if partition.allow_formatting: + if partition.allow_formatting and partition.safe_to_format(): partition.format() + else: + archinstall.log(f"Did not format {partition} because .safe_to_format() returned False or .allow_formatting was False", level=archinstall.LOG_LEVELS.Debug) if archinstall.arguments.get('!encryption-password', None): # First encrypt and unlock, then format the desired partition inside the encrypted part. From 8bf3296749ebfddc5dfdcbb116547395438d371f Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 17:14:21 +0100 Subject: [PATCH 071/100] Optimization, safe_to_format() now checks partition.allow_formatting as a condition. --- archinstall/lib/disk.py | 4 +++- examples/guided.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 16d6f7045d..56c7576758 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -184,7 +184,9 @@ def has_content(self): return True if files > 0 else False def safe_to_format(self): - if self.target_mountpoint == '/boot' and self.has_content(): + if self.allow_formatting is False: + return False + elif self.target_mountpoint == '/boot' and self.has_content(): return False return True diff --git a/examples/guided.py b/examples/guided.py index fc1fe88d4a..fbdba776d8 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -281,7 +281,7 @@ def perform_installation(device, boot_partition, language, mirrors): fs.use_entire_disk(archinstall.arguments.get('filesystem', 'ext4')) else: for partition in archinstall.arguments['harddrive']: - if partition.allow_formatting and partition.safe_to_format(): + if partition.safe_to_format(): partition.format() else: archinstall.log(f"Did not format {partition} because .safe_to_format() returned False or .allow_formatting was False", level=archinstall.LOG_LEVELS.Debug) From 0306209f3a494b00bab6b89b07e8c009b4b3c581 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 17:17:55 +0100 Subject: [PATCH 072/100] Fixed 'mount point does not exist' on temporary mount point. --- archinstall/lib/disk.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 56c7576758..01a736a3b0 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -1,4 +1,5 @@ import glob, re, os, json, time, hashlib +import pathlib from collections import OrderedDict from .exceptions import DiskError from .general import * @@ -175,12 +176,17 @@ def __repr__(self, *args, **kwargs): def has_content(self): temporary_mountpoint = '/tmp/'+hashlib.md5(bytes(f"{time.time()}", 'UTF-8')+os.urandom(12)).hexdigest() + temporary_path = pathlib.Path(temporary_mountpoint) + + temporary_path.mkdir(parents=True, exist_ok=True) if (handle := sys_command(f'/usr/bin/mount {self.path} {temporary_mountpoint}')).exit_code != 0: raise DiskError(f'Could not mount and check for content on {self.path} because: {b"".join(handle)}') files = len(glob.glob(f"{temporary_mountpoint}/*")) sys_command(f'/usr/bin/umount {temporary_mountpoint}') + temporary_path.rmdir() + return True if files > 0 else False def safe_to_format(self): From f1cb3209ad9deb609d9fff35c52ec76027ceffb4 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 8 Mar 2021 17:21:23 +0100 Subject: [PATCH 073/100] Tweaked the guided steps up until the installation. --- examples/guided.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/guided.py b/examples/guided.py index fbdba776d8..91158f9adc 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -290,16 +290,16 @@ def perform_installation(device, boot_partition, language, mirrors): # First encrypt and unlock, then format the desired partition inside the encrypted part. # archinstall.luks2() encrypts the partition when entering the with context manager, and # unlocks the drive so that it can be used as a normal block-device within archinstall. - with archinstall.luks2(harddrive.partition[1], 'luksloop', archinstall.arguments.get('!encryption-password', None)) as unlocked_device: + with archinstall.luks2(archinstall.arguments['harddrive'].partition[1], 'luksloop', archinstall.arguments.get('!encryption-password', None)) as unlocked_device: unlocked_device.format(archinstall.arguments.get('filesystem', 'btrfs')) perform_installation(unlocked_device, - harddrive.partition[0], + archinstall.arguments['harddrive'].partition[0], archinstall.arguments['keyboard-language'], archinstall.arguments['mirror-region']) else: - harddrive.partition[1].format('ext4') - perform_installation(harddrive.partition[1], - harddrive.partition[0], + archinstall.arguments['harddrive'].partition[1].format('ext4') + perform_installation(archinstall.arguments['harddrive'].partition[1], + archinstall.arguments['harddrive'].partition[0], archinstall.arguments['keyboard-language'], archinstall.arguments['mirror-region']) \ No newline at end of file From 7ee48156486101a43e90f834825dfeb0742247bd Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 00:12:03 +0100 Subject: [PATCH 074/100] Adding encryption helpers to the Partition() class, that sets up and utilizes luks2.encrypt() in extension. --- archinstall/lib/disk.py | 36 ++++++++++++++++++++++++++++++------ archinstall/lib/luks.py | 25 +++++++++++++++---------- examples/guided.py | 24 +++++++++++++----------- 3 files changed, 58 insertions(+), 27 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 01a736a3b0..3397d7cb60 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -197,6 +197,21 @@ def safe_to_format(self): return True + def encrypt(self, *args, **kwargs): + """ + A wrapper function for luks2() instances and the .encrypt() method of that instance. + """ + from .luks import luks2 + + if not self.encrypted: + raise DiskError(f"Attempting to encrypt a partition that was not marked for encryption: {self}") + + if not self.safe_to_format(): + return False + + handle = luks2(self, None, None) + return handle.encrypt(self, *args, **kwargs) + def format(self, filesystem=None, path=None, allow_formatting=None, log_formating=True): """ Format can be given an overriding path, for instance /dev/null to test @@ -204,6 +219,7 @@ def format(self, filesystem=None, path=None, allow_formatting=None, log_formatin """ if filesystem is None: filesystem = self.filesystem + if path is None: path = self.path if allow_formatting is None: @@ -349,15 +365,23 @@ def parted(self, string:str): """ return self.raw_parted(string).exit_code - def use_entire_disk(self, prep_mode=None): + def use_entire_disk(self, root_filesystem_type='ext4', encrypt_root_partition=True): self.add_partition('primary', start='1MiB', end='513MiB', format='vfat') self.set_name(0, 'EFI') self.set(0, 'boot on') - self.set(0, 'esp on') # TODO: Redundant, as in GPT mode it's an alias for "boot on"? https://www.gnu.org/software/parted/manual/html_node/set.html - if prep_mode == 'luks2': - self.add_partition('primary', start='513MiB', end='100%') - else: - self.add_partition('primary', start='513MiB', end='100%', format=prep_mode) + # TODO: Probably redundant because in GPT mode 'esp on' is an alias for "boot on"? + # https://www.gnu.org/software/parted/manual/html_node/set.html + self.set(0, 'esp on') + self.add_partition('primary', start='513MiB', end='100%') + + self.blockdevice.partition[0].filesystem = 'vfat' + self.blockdevice.partition[1].filesystem = root_filesystem_type + + self.blockdevice.partition[0].target_mountpoint = '/boot' + self.blockdevice.partition[1].target_mountpoint = '/' + + if encrypt_root_partition: + self.blockdevice.partition[1].encrypted = True def add_partition(self, type, start, end, format=None): log(f'Adding partition to {self.blockdevice}', level=LOG_LEVELS.Info) diff --git a/archinstall/lib/luks.py b/archinstall/lib/luks.py index b98994efd7..c9946239c6 100644 --- a/archinstall/lib/luks.py +++ b/archinstall/lib/luks.py @@ -16,16 +16,17 @@ def __init__(self, partition, mountpoint, password, key_file=None, *args, **kwar self.filesystem = 'crypto_LUKS' def __enter__(self): - if self.partition.allow_formatting: - self.key_file = self.encrypt(self.partition, *self.args, **self.kwargs) - else: - if not self.key_file: - self.key_file = f"/tmp/{os.path.basename(self.partition.path)}.disk_pw" # TODO: Make disk-pw-file randomly unique? - - if type(self.password) != bytes: self.password = bytes(self.password, 'UTF-8') + #if self.partition.allow_formatting: + # self.key_file = self.encrypt(self.partition, *self.args, **self.kwargs) + #else: + if not self.key_file: + self.key_file = f"/tmp/{os.path.basename(self.partition.path)}.disk_pw" # TODO: Make disk-pw-file randomly unique? + + if type(self.password) != bytes: + self.password = bytes(self.password, 'UTF-8') - with open(self.key_file, 'wb') as fh: - fh.write(self.password) + with open(self.key_file, 'wb') as fh: + fh.write(self.password) return self.unlock(self.partition, self.mountpoint, self.key_file) @@ -38,6 +39,9 @@ def __exit__(self, *args, **kwargs): 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.') + log(f'Encrypting {partition} (This might take a while)', level=LOG_LEVELS.Info) if not key_file: @@ -49,7 +53,8 @@ def encrypt(self, partition, password=None, key_size=512, hash_type='sha512', it if not password: password = self.password - if type(password) != bytes: password = bytes(password, 'UTF-8') + if type(password) != bytes: + password = bytes(password, 'UTF-8') with open(key_file, 'wb') as fh: fh.write(password) diff --git a/examples/guided.py b/examples/guided.py index 91158f9adc..13bf241448 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -272,19 +272,21 @@ def perform_installation(device, boot_partition, language, mirrors): Once that's done, we'll hand over to perform_installation() """ with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs: + # Wipe the entire drive if the disk flag `keep_partitions`is False. if archinstall.arguments['harddrive'].keep_partitions is False: - if archinstall.arguments.get('!encryption-password', None): - # Set a temporary partition format to indicate that the partitions is encrypted. - # Later on, we'll mount it and put an actual filesystem inside this encrypted container. - fs.use_entire_disk('luks2') - else: - fs.use_entire_disk(archinstall.arguments.get('filesystem', 'ext4')) - else: - for partition in archinstall.arguments['harddrive']: - if partition.safe_to_format(): - partition.format() + fs.use_entire_disk(root_filesystem_type=archinstall.arguments.get('filesystem', 'btrfs'), + encrypt_root_partition=archinstall.arguments.get('!encryption-password', False)) + + # After the disk is ready, iterate the partitions and check + # which ones are safe to format, and format those. + for partition in archinstall.arguments['harddrive']: + if partition.safe_to_format(): + if partition.encrypted: + partition.encrypt(password=archinstall.arguments.get('!encryption-password', None)) else: - archinstall.log(f"Did not format {partition} because .safe_to_format() returned False or .allow_formatting was False", level=archinstall.LOG_LEVELS.Debug) + partition.format() + else: + archinstall.log(f"Did not format {partition} because .safe_to_format() returned False or .allow_formatting was False.", level=archinstall.LOG_LEVELS.Debug) if archinstall.arguments.get('!encryption-password', None): # First encrypt and unlock, then format the desired partition inside the encrypted part. From 16b0f4a4a48e6e0363db653a6ec1cedbb4741647 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 10:38:04 +0100 Subject: [PATCH 075/100] Fixed an issue where Partition() instances got overwritten every time disk.partitions were called. Causing flags such as .encrypted to be dropped. This should make for a more stable experience when working with the partitions. --- archinstall/lib/disk.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 3397d7cb60..929824999e 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -106,7 +106,8 @@ def partitions(self): part_id = part['name'][len(os.path.basename(self.path)):] if part_id not in self.part_cache: ## TODO: Force over-write even if in cache? - self.part_cache[part_id] = Partition(root_path + part_id, part_id=part_id, size=part['size']) + if part_id not in self.part_cache or self.part_cache[part_id].size != part['size']: + self.part_cache[part_id] = Partition(root_path + part_id, part_id=part_id, size=part['size']) return {k: self.part_cache[k] for k in sorted(self.part_cache)} From 1f62a97c902bf0697e7502ec7f9e17c13147390b Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 10:41:57 +0100 Subject: [PATCH 076/100] Marking the root partitions as encrypted if a disk password is set. In the future, we need to make this a bit more flexible by allowing multiple partitions to be encrypted. But for now, the main partition should be enough. --- archinstall/lib/disk.py | 5 +++++ examples/guided.py | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 929824999e..7d4a34c6a8 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -352,6 +352,11 @@ def __exit__(self, *args, **kwargs): b''.join(sys_command(f'sync')) return True + def find_root_partition(self): + for partition in self.blockdevice: + if partition.target_mountpoint == '/' or partition.mountpoint == '/': + return partition + def raw_parted(self, string:str): x = sys_command(f'/usr/bin/parted -s {string}') o = b''.join(x) diff --git a/examples/guided.py b/examples/guided.py index 13bf241448..ebdd367822 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -276,6 +276,10 @@ def perform_installation(device, boot_partition, language, mirrors): if archinstall.arguments['harddrive'].keep_partitions is False: fs.use_entire_disk(root_filesystem_type=archinstall.arguments.get('filesystem', 'btrfs'), encrypt_root_partition=archinstall.arguments.get('!encryption-password', False)) + # Otherwise, check if encryption is desired and mark the root partition as encrypted. + elif archinstall.arguments.get('!encryption-password', None): + root_partition = fs.find_root_partition() + root_partition.encrypted = True # After the disk is ready, iterate the partitions and check # which ones are safe to format, and format those. From c97d5f12021a8ca8b1e90a750a6f4c95229c6509 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 10:45:45 +0100 Subject: [PATCH 077/100] Forgot to return self in Filesystem() --- archinstall/lib/disk.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 7d4a34c6a8..a8cba53b93 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -342,6 +342,8 @@ def __enter__(self, *args, **kwargs): else: raise DiskError(f'The selected partition table format {self.mode} does not match that of {self.blockdevice}.') + return self + def __repr__(self): return f"Filesystem(blockdevice={self.blockdevice}, mode={self.mode})" From f230140ba99f7c7ddbe8e0f7a6c7f80572cbf2ce Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 10:50:05 +0100 Subject: [PATCH 078/100] Removed hardcoded partition numbers when acessing partitions. As the order is not fixed and more options to disk layouts have been added. --- archinstall/lib/disk.py | 4 ++-- examples/guided.py | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index a8cba53b93..73d913d01f 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -354,9 +354,9 @@ def __exit__(self, *args, **kwargs): b''.join(sys_command(f'sync')) return True - def find_root_partition(self): + def find_partition(self, mountpoint): for partition in self.blockdevice: - if partition.target_mountpoint == '/' or partition.mountpoint == '/': + if partition.target_mountpoint == mountpoint or partition.mountpoint == mountpoint: return partition def raw_parted(self, string:str): diff --git a/examples/guided.py b/examples/guided.py index ebdd367822..722e1e36a4 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -278,7 +278,7 @@ def perform_installation(device, boot_partition, language, mirrors): encrypt_root_partition=archinstall.arguments.get('!encryption-password', False)) # Otherwise, check if encryption is desired and mark the root partition as encrypted. elif archinstall.arguments.get('!encryption-password', None): - root_partition = fs.find_root_partition() + root_partition = fs.find_partition('/') root_partition.encrypted = True # After the disk is ready, iterate the partitions and check @@ -296,16 +296,16 @@ def perform_installation(device, boot_partition, language, mirrors): # First encrypt and unlock, then format the desired partition inside the encrypted part. # archinstall.luks2() encrypts the partition when entering the with context manager, and # unlocks the drive so that it can be used as a normal block-device within archinstall. - with archinstall.luks2(archinstall.arguments['harddrive'].partition[1], 'luksloop', archinstall.arguments.get('!encryption-password', None)) as unlocked_device: - unlocked_device.format(archinstall.arguments.get('filesystem', 'btrfs')) + with archinstall.luks2(fs.find_partition('/'), 'luksloop', archinstall.arguments.get('!encryption-password', None)) as unlocked_device: + unlocked_device.format(fs.find_partition('/').filesystem) - perform_installation(unlocked_device, - archinstall.arguments['harddrive'].partition[0], - archinstall.arguments['keyboard-language'], - archinstall.arguments['mirror-region']) + perform_installation(device=unlocked_device, + boot_partition=fs.find_partition('/boot'), + language=archinstall.arguments['keyboard-language'], + mirrors=archinstall.arguments['mirror-region']) else: archinstall.arguments['harddrive'].partition[1].format('ext4') - perform_installation(archinstall.arguments['harddrive'].partition[1], - archinstall.arguments['harddrive'].partition[0], - archinstall.arguments['keyboard-language'], - archinstall.arguments['mirror-region']) \ No newline at end of file + perform_installation(device=fs.find_partition('/'), + boot_partition=fs.find_partition('/boot'), + language=archinstall.arguments['keyboard-language'], + mirrors=archinstall.arguments['mirror-region']) \ No newline at end of file From 56d673d2a509bdd881fff51892f564a66384c0c2 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 11:07:46 +0100 Subject: [PATCH 079/100] Debugging some tweaks --- archinstall/lib/disk.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 73d913d01f..958284cfa3 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -146,7 +146,7 @@ def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None self.mount(mountpoint) mount_information = get_mount_info(self.path) - fstype = get_filesystem_type(self.path) # blkid -o value -s TYPE self.path + fstype = get_filesystem_type(self.real_device) # blkid -o value -s TYPE self.path if self.mountpoint != mount_information.get('target', None) and mountpoint: raise DiskError(f"{self} was given a mountpoint but the actual mountpoint differs: {mount_information.get('target', None)}") @@ -175,6 +175,16 @@ def __repr__(self, *args, **kwargs): else: return f'Partition(path={self.path}, fs={self.filesystem}{mount_repr})' + @property + def real_device(self): + if not self.encrypted: + return self.path + else: + for blockdevice in json.loads(b''.join(sys_command('lsblk -J')).decode('UTF-8'))['blockdevices']: + if (parent := self.find_parent_of(blockdevice, os.path.basename(self.path))): + return f"/dev/{parent}" + raise DiskError(f'Could not find appropriate parent for encrypted partition {self}') + def has_content(self): temporary_mountpoint = '/tmp/'+hashlib.md5(bytes(f"{time.time()}", 'UTF-8')+os.urandom(12)).hexdigest() temporary_path = pathlib.Path(temporary_mountpoint) @@ -277,16 +287,6 @@ def find_parent_of(self, data, name, parent=None): if (parent := self.find_parent_of(child, name, parent=data['name'])): return parent - @property - def real_device(self): - if not self.encrypted: - return self.path - else: - for blockdevice in json.loads(b''.join(sys_command('lsblk -J')).decode('UTF-8'))['blockdevices']: - if (parent := self.find_parent_of(blockdevice, os.path.basename(self.path))): - return f"/dev/{parent}" - raise DiskError(f'Could not find appropriate parent for encrypted partition {self}') - def mount(self, target, fs=None, options=''): if not self.mountpoint: log(f'Mounting {self} to {target}', level=LOG_LEVELS.Info) From c56d4d958ef1797bbdebb883801a905169d454f6 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 11:36:15 +0100 Subject: [PATCH 080/100] Debugging some tweaks --- archinstall/lib/disk.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 958284cfa3..3bbd9344b3 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -146,7 +146,7 @@ def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None self.mount(mountpoint) mount_information = get_mount_info(self.path) - fstype = get_filesystem_type(self.real_device) # blkid -o value -s TYPE self.path + fstype = get_filesystem_type(self.real_devicecryptse) # blkid -o value -s TYPE self.path if self.mountpoint != mount_information.get('target', None) and mountpoint: raise DiskError(f"{self} was given a mountpoint but the actual mountpoint differs: {mount_information.get('target', None)}") @@ -156,6 +156,12 @@ def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None if (fstype := mount_information.get('fstype', fstype)): self.filesystem = fstype + if self.filesystem == 'crypto_LUKS': + # TODO: Explore other options in terms of handling a two-layer filesystem option. + # Currently if we keep crypto_LUKS then the installer won't know what to format inside. + self.encrypted = True + self.filesystem = None + def __lt__(self, left_comparitor): if type(left_comparitor) == Partition: left_comparitor = left_comparitor.path @@ -269,11 +275,11 @@ def format(self, filesystem=None, path=None, allow_formatting=None, log_formatin raise DiskError(f'Could not format {path} with {filesystem} because: {b"".join(handle)}') self.filesystem = 'f2fs' - elif filesystem == 'crypto_LUKS': - from .luks import luks2 - encrypted_partition = luks2(self, None, None) - encrypted_partition.format(path) - self.filesystem = 'crypto_LUKS' + #elif filesystem == 'crypto_LUKS': + # from .luks import luks2 + # encrypted_partition = luks2(self, None, None) + # encrypted_partition.format(path) + # self.filesystem = 'crypto_LUKS' else: raise UnknownFilesystemFormat(f"Fileformat '{filesystem}' is not yet implemented.") From 6680f744624af5b0be746525ea4ba4bbcf44401d Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 11:36:59 +0100 Subject: [PATCH 081/100] Debugging some tweaks --- archinstall/lib/disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 3bbd9344b3..32dbcbc2b8 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -146,7 +146,7 @@ def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None self.mount(mountpoint) mount_information = get_mount_info(self.path) - fstype = get_filesystem_type(self.real_devicecryptse) # blkid -o value -s TYPE self.path + fstype = get_filesystem_type(self.real_device) # blkid -o value -s TYPE self.path if self.mountpoint != mount_information.get('target', None) and mountpoint: raise DiskError(f"{self} was given a mountpoint but the actual mountpoint differs: {mount_information.get('target', None)}") From 3b849ca07462dd80734373b3428cd0e128812f60 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 11:38:36 +0100 Subject: [PATCH 082/100] Debugging some tweaks --- archinstall/lib/disk.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 32dbcbc2b8..335ec42e11 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -157,10 +157,7 @@ def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None self.filesystem = fstype if self.filesystem == 'crypto_LUKS': - # TODO: Explore other options in terms of handling a two-layer filesystem option. - # Currently if we keep crypto_LUKS then the installer won't know what to format inside. self.encrypted = True - self.filesystem = None def __lt__(self, left_comparitor): if type(left_comparitor) == Partition: @@ -275,11 +272,11 @@ def format(self, filesystem=None, path=None, allow_formatting=None, log_formatin raise DiskError(f'Could not format {path} with {filesystem} because: {b"".join(handle)}') self.filesystem = 'f2fs' - #elif filesystem == 'crypto_LUKS': + elif filesystem == 'crypto_LUKS': # from .luks import luks2 # encrypted_partition = luks2(self, None, None) # encrypted_partition.format(path) - # self.filesystem = 'crypto_LUKS' + self.filesystem = 'crypto_LUKS' else: raise UnknownFilesystemFormat(f"Fileformat '{filesystem}' is not yet implemented.") From 586f8bc32ef81695f31f10f41472443ad5f280cd Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 11:44:17 +0100 Subject: [PATCH 083/100] Adding enforcement to select a filesystem-type for encrypted volumes unless the specified pasword can be used to unlock the device and auto-detect this. --- examples/guided.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/guided.py b/examples/guided.py index 722e1e36a4..3f57ec881e 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -137,6 +137,12 @@ def perform_installation(device, boot_partition, language, mirrors): while 1: new_filesystem = input(f"Enter a valid filesystem for {partition} (leave blank for {partition.filesystem}): ").strip(' ') if len(new_filesystem) <= 0: + if partition.encrypted and partition.filesystem == 'crypto_LUKS': + if (autodetected_filesystem := partition.detect_inner_filesystem(archinstall.arguments.get('!encryption-password', None))): + else: + archinstall.log(f"Could not auto-detect the filesystem inside the encrypted volume.", fg='red') + archinstall.log(f"A filesystem must be defined for the unlocked encrypted partition.") + continue break # Since the potentially new filesystem is new From 9f0d25bce302459efa5ab5b9eaf22bb1f1feb575 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 11:48:40 +0100 Subject: [PATCH 084/100] Added functions to auto-detect filesystems inside encrypted partitions (given a password) --- archinstall/lib/disk.py | 5 +++++ archinstall/lib/luks.py | 9 +++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 335ec42e11..75cecb0b11 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -188,6 +188,11 @@ def real_device(self): return f"/dev/{parent}" raise DiskError(f'Could not find appropriate parent for encrypted partition {self}') + def detect_inner_filesystem(self, password): + from .luks import luks2 + with luks2(self, 'luksloop', password, auto_unmount=True) as unlocked_device: + return unlocked_device.filesystem + def has_content(self): temporary_mountpoint = '/tmp/'+hashlib.md5(bytes(f"{time.time()}", 'UTF-8')+os.urandom(12)).hexdigest() temporary_path = pathlib.Path(temporary_mountpoint) diff --git a/archinstall/lib/luks.py b/archinstall/lib/luks.py index c9946239c6..fa040788f0 100644 --- a/archinstall/lib/luks.py +++ b/archinstall/lib/luks.py @@ -6,13 +6,14 @@ from .storage import storage class luks2(): - def __init__(self, partition, mountpoint, password, key_file=None, *args, **kwargs): + def __init__(self, partition, mountpoint, password, key_file=None, auto_unmount=False, *args, **kwargs): self.password = password self.partition = partition self.mountpoint = mountpoint self.args = args self.kwargs = kwargs self.key_file = key_file + self.auto_unmount = auto_unmount self.filesystem = 'crypto_LUKS' def __enter__(self): @@ -32,6 +33,9 @@ def __enter__(self): def __exit__(self, *args, **kwargs): # TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager + if self.auto_unmount: + self.close() + if len(args) >= 2 and args[1]: raise args[1] return True @@ -73,11 +77,12 @@ def unlock(self, partition, mountpoint, key_file): :param mountpoint: The name without absolute path, for instance "luksdev" will point to /dev/mapper/luksdev :type mountpoint: str """ + from .disk import get_filesystem_type if '/' in mountpoint: os.path.basename(mountpoint) # TODO: Raise exception instead? sys_command(f'/usr/bin/cryptsetup open {partition.path} {mountpoint} --key-file {os.path.abspath(key_file)} --type luks2') if os.path.islink(f'/dev/mapper/{mountpoint}'): - return Partition(f'/dev/mapper/{mountpoint}', encrypted=True) + return Partition(f'/dev/mapper/{mountpoint}', encrypted=True, filesystem=get_filesystem_type(f'/dev/mapper/{mountpoint}')) def close(self, mountpoint): sys_command(f'cryptsetup close /dev/mapper/{mountpoint}') From ce646a93948a6d6f5260a9e6e7f47282f956a56c Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 11:49:20 +0100 Subject: [PATCH 085/100] Forgot a variable. --- examples/guided.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/guided.py b/examples/guided.py index 3f57ec881e..5564393392 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -139,6 +139,7 @@ def perform_installation(device, boot_partition, language, mirrors): if len(new_filesystem) <= 0: if partition.encrypted and partition.filesystem == 'crypto_LUKS': if (autodetected_filesystem := partition.detect_inner_filesystem(archinstall.arguments.get('!encryption-password', None))): + new_filesystem = autodetected_filesystem else: archinstall.log(f"Could not auto-detect the filesystem inside the encrypted volume.", fg='red') archinstall.log(f"A filesystem must be defined for the unlocked encrypted partition.") From 4e8084bddb989646bf74b6c8883fc66622441221 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 11:54:03 +0100 Subject: [PATCH 086/100] Fixed a issue with blkid where it would raise an exception when there was not filesystem on the partition. --- archinstall/lib/disk.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 75cecb0b11..db58f3ce8d 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -487,5 +487,6 @@ def get_mount_info(path): return output['filesystems'][0] def get_filesystem_type(path): - output = b''.join(sys_command(f"blkid -o value -s TYPE {path}")) - return output.strip().decode('UTF-8') \ No newline at end of file + if (handle := sys_command(f"blkid -o value -s TYPE {path}")).exit_code != 0: + return None + return b''.join(handle).strip().decode('UTF-8') \ No newline at end of file From f045462c9a105b6272db4c61e8b29fc1ae059c8a Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 11:56:33 +0100 Subject: [PATCH 087/100] Fixed a issue with blkid where it would raise an exception when there was not filesystem on the partition. --- archinstall/lib/disk.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index db58f3ce8d..3f51733a52 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -487,6 +487,8 @@ def get_mount_info(path): return output['filesystems'][0] def get_filesystem_type(path): - if (handle := sys_command(f"blkid -o value -s TYPE {path}")).exit_code != 0: - return None - return b''.join(handle).strip().decode('UTF-8') \ No newline at end of file + try: + handle = sys_command(f"blkid -o value -s TYPE {path}") + return b''.join(handle).strip().decode('UTF-8') + except SysCallError: + return None \ No newline at end of file From 251624734faa7a01590cb35b3416b72c2271ba55 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 11:58:35 +0100 Subject: [PATCH 088/100] Fixed luks2().close() to properly detect it's own path --- archinstall/lib/luks.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/archinstall/lib/luks.py b/archinstall/lib/luks.py index fa040788f0..283a1b514a 100644 --- a/archinstall/lib/luks.py +++ b/archinstall/lib/luks.py @@ -84,7 +84,10 @@ def unlock(self, partition, mountpoint, key_file): if os.path.islink(f'/dev/mapper/{mountpoint}'): return Partition(f'/dev/mapper/{mountpoint}', encrypted=True, filesystem=get_filesystem_type(f'/dev/mapper/{mountpoint}')) - def close(self, mountpoint): + def close(self, mountpoint=None): + if not mountpoint: + mountpoint = self.partition.path + sys_command(f'cryptsetup close /dev/mapper/{mountpoint}') return os.path.islink(f'/dev/mapper/{mountpoint}') is False From 3bf8ba081967a041f3552b68efff84340edb5138 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 12:00:40 +0100 Subject: [PATCH 089/100] Fixed luks2().close() to properly detect it's own mapped path --- archinstall/lib/luks.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/archinstall/lib/luks.py b/archinstall/lib/luks.py index 283a1b514a..7c5d3e555e 100644 --- a/archinstall/lib/luks.py +++ b/archinstall/lib/luks.py @@ -15,6 +15,7 @@ def __init__(self, partition, mountpoint, password, key_file=None, auto_unmount= self.key_file = key_file self.auto_unmount = auto_unmount self.filesystem = 'crypto_LUKS' + self.mappoint = None def __enter__(self): #if self.partition.allow_formatting: @@ -82,14 +83,15 @@ def unlock(self, partition, mountpoint, key_file): os.path.basename(mountpoint) # TODO: Raise exception instead? sys_command(f'/usr/bin/cryptsetup open {partition.path} {mountpoint} --key-file {os.path.abspath(key_file)} --type luks2') if os.path.islink(f'/dev/mapper/{mountpoint}'): + self.mappoint = f'/dev/mapper/{mountpoint}' return Partition(f'/dev/mapper/{mountpoint}', encrypted=True, filesystem=get_filesystem_type(f'/dev/mapper/{mountpoint}')) def close(self, mountpoint=None): if not mountpoint: - mountpoint = self.partition.path - - sys_command(f'cryptsetup close /dev/mapper/{mountpoint}') - return os.path.islink(f'/dev/mapper/{mountpoint}') is False + mountpoint = self.mappoint + + sys_command(f'/usr/bin/cryptsetup close {self.mappoint}') + return os.path.islink(self.mappoint) is False def format(self, path): if (handle := sys_command(f"/usr/bin/cryptsetup -q -v luksErase {path}")).exit_code != 0: From 747d620596c41094a66a8ae1e39104bc05d90846 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 12:02:14 +0100 Subject: [PATCH 090/100] Added some debugging. --- archinstall/lib/disk.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 3f51733a52..f78907d704 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -189,8 +189,10 @@ def real_device(self): raise DiskError(f'Could not find appropriate parent for encrypted partition {self}') def detect_inner_filesystem(self, password): + log(f'Trying to detect inner filesystem format on {self} (This might take a while)', level=LOG_LEVELS.Info) from .luks import luks2 with luks2(self, 'luksloop', password, auto_unmount=True) as unlocked_device: + print('Found:', unlocked_device.filesystem) return unlocked_device.filesystem def has_content(self): From b3a5afea60ada944905b45a7150c91a5a8a09fcc Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 12:05:39 +0100 Subject: [PATCH 091/100] Tweaked the filesystem definition setup for Partition(). Overriding it programatically should be possible for some meta stuff. --- archinstall/lib/disk.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index f78907d704..d3f4b0692f 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -146,14 +146,13 @@ def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None self.mount(mountpoint) mount_information = get_mount_info(self.path) - fstype = get_filesystem_type(self.real_device) # blkid -o value -s TYPE self.path if self.mountpoint != mount_information.get('target', None) and mountpoint: raise DiskError(f"{self} was given a mountpoint but the actual mountpoint differs: {mount_information.get('target', None)}") if (target := mount_information.get('target', None)): self.mountpoint = target - if (fstype := mount_information.get('fstype', fstype)): + if not self.filesystem and (fstype := mount_information.get('fstype', get_filesystem_type(self.real_device))): self.filesystem = fstype if self.filesystem == 'crypto_LUKS': From 585e0f4b869c270ec0482d628033463af74516a7 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 12:10:30 +0100 Subject: [PATCH 092/100] Added some debugging. --- archinstall/lib/luks.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/archinstall/lib/luks.py b/archinstall/lib/luks.py index 7c5d3e555e..706a8bd987 100644 --- a/archinstall/lib/luks.py +++ b/archinstall/lib/luks.py @@ -15,7 +15,7 @@ def __init__(self, partition, mountpoint, password, key_file=None, auto_unmount= self.key_file = key_file self.auto_unmount = auto_unmount self.filesystem = 'crypto_LUKS' - self.mappoint = None + self.mapdev = None def __enter__(self): #if self.partition.allow_formatting: @@ -83,15 +83,17 @@ def unlock(self, partition, mountpoint, key_file): os.path.basename(mountpoint) # TODO: Raise exception instead? sys_command(f'/usr/bin/cryptsetup open {partition.path} {mountpoint} --key-file {os.path.abspath(key_file)} --type luks2') if os.path.islink(f'/dev/mapper/{mountpoint}'): - self.mappoint = f'/dev/mapper/{mountpoint}' - return Partition(f'/dev/mapper/{mountpoint}', encrypted=True, filesystem=get_filesystem_type(f'/dev/mapper/{mountpoint}')) + self.mapdev = f'/dev/mapper/{mountpoint}' + inner_fs = get_filesystem_type(self.mapdev) + print('Inner FS:', inner_fs) + return Partition(self.mapdev, encrypted=True, filesystem=inner_fs) def close(self, mountpoint=None): if not mountpoint: - mountpoint = self.mappoint + mountpoint = self.mapdev - sys_command(f'/usr/bin/cryptsetup close {self.mappoint}') - return os.path.islink(self.mappoint) is False + sys_command(f'/usr/bin/cryptsetup close {self.mapdev}') + return os.path.islink(self.mapdev) is False def format(self, path): if (handle := sys_command(f"/usr/bin/cryptsetup -q -v luksErase {path}")).exit_code != 0: From 8f42a9f4ff647f84818cc01b84ba79e8256caa5f Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 12:16:40 +0100 Subject: [PATCH 093/100] Added option to skip autodetection of filesystem. This is so for instance luks2() can override any auto-detection that revers back to the parent device of the mapped device, which would be crypto_LUKS instead of None for the inner partition. --- archinstall/lib/disk.py | 8 +++++--- archinstall/lib/luks.py | 5 +---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index d3f4b0692f..fe21dcf7c9 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -130,7 +130,7 @@ def has_mount_point(self, mountpoint): return False class Partition(): - def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None, encrypted=False): + def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None, encrypted=False, autodetect_filesystem=True): if not part_id: part_id = os.path.basename(path) self.path = path @@ -152,8 +152,10 @@ def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None if (target := mount_information.get('target', None)): self.mountpoint = target - if not self.filesystem and (fstype := mount_information.get('fstype', get_filesystem_type(self.real_device))): - self.filesystem = fstype + + if not self.filesystem and autodetect_filesystem: + if (fstype := mount_information.get('fstype', get_filesystem_type(self.real_device))): + self.filesystem = fstype if self.filesystem == 'crypto_LUKS': self.encrypted = True diff --git a/archinstall/lib/luks.py b/archinstall/lib/luks.py index 706a8bd987..e03e26ca76 100644 --- a/archinstall/lib/luks.py +++ b/archinstall/lib/luks.py @@ -83,10 +83,7 @@ def unlock(self, partition, mountpoint, key_file): os.path.basename(mountpoint) # TODO: Raise exception instead? sys_command(f'/usr/bin/cryptsetup open {partition.path} {mountpoint} --key-file {os.path.abspath(key_file)} --type luks2') if os.path.islink(f'/dev/mapper/{mountpoint}'): - self.mapdev = f'/dev/mapper/{mountpoint}' - inner_fs = get_filesystem_type(self.mapdev) - print('Inner FS:', inner_fs) - return Partition(self.mapdev, encrypted=True, filesystem=inner_fs) + return Partition(self.mapdev, encrypted=True, filesystem=get_filesystem_type(self.mapdev), autodetect_filesystem=False) def close(self, mountpoint=None): if not mountpoint: From 3e9031821aa3bb6f85ce33405cf65aa2003d42d1 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 13:36:47 +0100 Subject: [PATCH 094/100] Forgot to set a variable --- archinstall/lib/luks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/archinstall/lib/luks.py b/archinstall/lib/luks.py index e03e26ca76..7873f76b44 100644 --- a/archinstall/lib/luks.py +++ b/archinstall/lib/luks.py @@ -83,6 +83,7 @@ def unlock(self, partition, mountpoint, key_file): os.path.basename(mountpoint) # TODO: Raise exception instead? sys_command(f'/usr/bin/cryptsetup open {partition.path} {mountpoint} --key-file {os.path.abspath(key_file)} --type luks2') if os.path.islink(f'/dev/mapper/{mountpoint}'): + self.mapdev = f'/dev/mapper/{mountpoint}' return Partition(self.mapdev, encrypted=True, filesystem=get_filesystem_type(self.mapdev), autodetect_filesystem=False) def close(self, mountpoint=None): From cf21b477640ec284f1355140694d22a2c9a21ac3 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 14:00:30 +0100 Subject: [PATCH 095/100] Carried over the allow_formatting from the parent device of luks2() devices. --- archinstall/lib/disk.py | 1 - archinstall/lib/luks.py | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index fe21dcf7c9..c05ba75777 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -193,7 +193,6 @@ def detect_inner_filesystem(self, password): log(f'Trying to detect inner filesystem format on {self} (This might take a while)', level=LOG_LEVELS.Info) from .luks import luks2 with luks2(self, 'luksloop', password, auto_unmount=True) as unlocked_device: - print('Found:', unlocked_device.filesystem) return unlocked_device.filesystem def has_content(self): diff --git a/archinstall/lib/luks.py b/archinstall/lib/luks.py index 7873f76b44..e54641b8d1 100644 --- a/archinstall/lib/luks.py +++ b/archinstall/lib/luks.py @@ -84,7 +84,9 @@ def unlock(self, partition, mountpoint, key_file): sys_command(f'/usr/bin/cryptsetup open {partition.path} {mountpoint} --key-file {os.path.abspath(key_file)} --type luks2') if os.path.islink(f'/dev/mapper/{mountpoint}'): self.mapdev = f'/dev/mapper/{mountpoint}' - return Partition(self.mapdev, encrypted=True, filesystem=get_filesystem_type(self.mapdev), autodetect_filesystem=False) + unlocked_partition = Partition(self.mapdev, encrypted=True, filesystem=get_filesystem_type(self.mapdev), autodetect_filesystem=False) + unlocked_partition.allow_formatting = self.partition.allow_formatting + return unlocked_partition def close(self, mountpoint=None): if not mountpoint: From 1167cf589b65e121b8b3322980a83ddba2043b57 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 14:09:17 +0100 Subject: [PATCH 096/100] Fixed mirror-region parameter selection. Converting to actual mirrors. --- archinstall/lib/installer.py | 2 +- examples/guided.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 4fb6b7068e..06bdd05a91 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -78,7 +78,7 @@ def __exit__(self, *args, **kwargs): if len(args) >= 2 and args[1]: #self.log(self.trace_log.decode('UTF-8'), level=LOG_LEVELS.Debug) - self.log(args[1], level=LOG_LEVELS.Error) + self.log(args[1], level=LOG_LEVELS.Error, fg='red') self.sync_log_to_install_medium() diff --git a/examples/guided.py b/examples/guided.py index 5564393392..08ad70d9a8 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -88,6 +88,10 @@ def perform_installation(device, boot_partition, language, mirrors): # Set which region to download packages from during the installation if not archinstall.arguments.get('mirror-region', None): archinstall.arguments['mirror-region'] = archinstall.select_mirror_regions(archinstall.list_mirrors()) +else: + selected_region = archinstall.arguments['mirror-region'] + archinstall.arguments['mirror-region'] = {selected_region : archinstall.list_mirrors()[selected_region]} + # Ask which harddrive/block-device we will install to if archinstall.arguments.get('harddrive', None): From aafe3d19c67eb32ab8559021cc1ab984ca48b5ee Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 16:10:57 +0100 Subject: [PATCH 097/100] Fixed remaining installation steps and remove legacy references to archinstall.storage['_guided'] and archinstall.storage['_guided_hidden']. Which were great at the time, but they are not deprecated and moved into archinstall.arguments instead to support parameters on command-line. As well as being a bit more description, since they are arguments afterall to various setup instructions. --- examples/guided.py | 487 +++++++++++++++++++++++---------------------- 1 file changed, 245 insertions(+), 242 deletions(-) diff --git a/examples/guided.py b/examples/guided.py index 08ad70d9a8..28d07d0ff3 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -43,280 +43,283 @@ def perform_installation(device, boot_partition, language, mirrors): # If user selected to copy the current ISO network configuration # Perform a copy of the config - if archinstall.storage['_guided']['network'] == 'Copy ISO network configuration to installation': + if archinstall.arguments.get('nic', None) == 'Copy ISO network configuration to installation': installation.copy_ISO_network_config(enable_services=True) # Sources the ISO network configuration to the install medium. # Otherwise, if a interface was selected, configure that interface - elif archinstall.storage['_guided']['network']: - installation.configure_nic(**archinstall.storage['_guided']['network']) + elif archinstall.arguments.get('nic', None): + installation.configure_nic(**archinstall.arguments.get('nic', {})) installation.enable_service('systemd-networkd') installation.enable_service('systemd-resolved') - if archinstall.storage['_guided']['packages'] and archinstall.storage['_guided']['packages'][0] != '': - installation.add_additional_packages(archinstall.storage['_guided']['packages']) + if archinstall.arguments.get('packages', None) and archinstall.arguments.get('packages', None)[0] != '': + installation.add_additional_packages(archinstall.arguments.get('packages', None)) - if 'profile' in archinstall.storage['_guided'] and len(profile := archinstall.storage['_guided']['profile']['path'].strip()): + if archinstall.arguments.get('profile', None) and len(profile := archinstall.arguments.get('profile').strip()): installation.install_profile(profile) - if archinstall.storage['_guided']['users']: - for user in archinstall.storage['_guided']['users']: - password = users[user] + if archinstall.arguments.get('users', None): + for user, password in archinstall.arguments.get('users').items(): + installation.user_create(user, password, sudo=False) - sudo = False - if 'root_pw' not in archinstall.storage['_guided_hidden'] or len(archinstall.storage['_guided_hidden']['root_pw'].strip()) == 0: - sudo = True + if archinstall.arguments.get('superusers', None): + for user, password in archinstall.arguments.get('superusers').items(): + installation.user_create(user, password, sudo=True) - installation.user_create(user, password, sudo=sudo) + if (root_pw := archinstall.arguments.get('!root-password', None)) and len(root_pw): + installation.user_set_pw('root', root_pw) - if 'root_pw' in archinstall.storage['_guided_hidden'] and archinstall.storage['_guided_hidden']['root_pw']: - installation.user_set_pw('root', archinstall.storage['_guided_hidden']['root_pw']) +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. + """ + if not archinstall.arguments.get('keyboard-language', None): + archinstall.arguments['keyboard-language'] = archinstall.select_language(archinstall.list_keyboard_languages()).strip() -""" - 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 not archinstall.arguments.get('keyboard-language', None): - archinstall.arguments['keyboard-language'] = archinstall.select_language(archinstall.list_keyboard_languages()).strip() - -# Before continuing, set the preferred keyboard layout/language in the current terminal. -# This will just help the user with the next following questions. -if len(archinstall.arguments['keyboard-language']): - archinstall.set_keyboard_language(archinstall.arguments['keyboard-language']) - -# Set which region to download packages from during the installation -if not archinstall.arguments.get('mirror-region', None): - archinstall.arguments['mirror-region'] = archinstall.select_mirror_regions(archinstall.list_mirrors()) -else: - selected_region = archinstall.arguments['mirror-region'] - archinstall.arguments['mirror-region'] = {selected_region : archinstall.list_mirrors()[selected_region]} - - -# Ask which harddrive/block-device we will install to -if archinstall.arguments.get('harddrive', None): - archinstall.arguments['harddrive'] = archinstall.BlockDevice(archinstall.arguments['harddrive']) -else: - archinstall.arguments['harddrive'] = archinstall.select_disk(archinstall.all_disks()) - -# Perform a quick sanity check on the selected harddrive. -# 1. Check if it has partitions -# 3. Check that we support the current partitions -# 2. If so, ask if we should keep them or wipe everything -if archinstall.arguments['harddrive'].has_partitions(): - archinstall.log(f"{archinstall.arguments['harddrive']} contains the following partitions:", fg='red') - - # We curate a list pf supported paritions - # and print those that we don't support. - partition_mountpoints = {} - for partition in archinstall.arguments['harddrive']: - try: - if partition.filesystem_supported(): - archinstall.log(f" {partition}") - partition_mountpoints[partition] = None - except archinstall.UnknownFilesystemFormat as err: - archinstall.log(f" {partition} (Filesystem not supported)", fg='red') - - # We then ask what to do with the paritions. - if (option := archinstall.ask_for_disk_layout()) == 'abort': - archinstall.log(f"Safely aborting the installation. No changes to the disk or system has been made.") - exit(1) - elif option == 'keep-existing': - archinstall.arguments['harddrive'].keep_partitions = True - - archinstall.log(f" ** You will now select which partitions to use by selecting mount points (inside the installation). **") - 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(), - "Select a partition by number that you want to set a mount-point for (leave blank when done): ") - if not partition: - break - - # Select a mount-point - mountpoint = input(f"Enter a mount-point for {partition}: ").strip(' ') - if len(mountpoint): - - # Get a valid & supported filesystem for the parition: - while 1: - new_filesystem = input(f"Enter a valid filesystem for {partition} (leave blank for {partition.filesystem}): ").strip(' ') - if len(new_filesystem) <= 0: - if partition.encrypted and partition.filesystem == 'crypto_LUKS': - if (autodetected_filesystem := partition.detect_inner_filesystem(archinstall.arguments.get('!encryption-password', None))): - new_filesystem = autodetected_filesystem - else: - archinstall.log(f"Could not auto-detect the filesystem inside the encrypted volume.", fg='red') - archinstall.log(f"A filesystem must be defined for the unlocked encrypted partition.") - continue - break + # Before continuing, set the preferred keyboard layout/language in the current terminal. + # This will just help the user with the next following questions. + if len(archinstall.arguments['keyboard-language']): + archinstall.set_keyboard_language(archinstall.arguments['keyboard-language']) - # Since the potentially new filesystem is new - # we have to check if we support it. We can do this by formatting /dev/null with the partitions filesystem. - # There's a nice wrapper for this on the partition object itself that supports a path-override during .format() - try: - partition.format(new_filesystem, path='/dev/null', log_formating=False, allow_formatting=True) - except archinstall.UnknownFilesystemFormat: - archinstall.log(f"Selected filesystem is not supported yet. If you want archinstall to support '{new_filesystem}', please create a issue-ticket suggesting it on github at https://github.com/Torxed/archinstall/issues.") - archinstall.log(f"Until then, please enter another supported filesystem.") - continue - except archinstall.SysCallError: - pass # Expected exception since mkfs. can not format /dev/null. - # But that means our .format() function supported it. - break + # Set which region to download packages from during the installation + if not archinstall.arguments.get('mirror-region', None): + archinstall.arguments['mirror-region'] = archinstall.select_mirror_regions(archinstall.list_mirrors()) + else: + selected_region = archinstall.arguments['mirror-region'] + archinstall.arguments['mirror-region'] = {selected_region : archinstall.list_mirrors()[selected_region]} - # When we've selected all three criterias, - # We can safely mark the partition for formatting and where to mount it. - # TODO: allow_formatting might be redundant since target_mountpoint should only be - # set if we actually want to format it anyway. - partition.allow_formatting = True - partition.target_mountpoint = mountpoint - # Only overwrite the filesystem definition if we selected one: - if len(new_filesystem): - partition.filesystem = new_filesystem - - archinstall.log('Using existing partition table reported above.') - elif option == 'format-all': - archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format() - archinstall.arguments['harddrive'].keep_partitions = False - -# Get disk encryption password (or skip if blank) -if not archinstall.arguments.get('!encryption-password', None): - archinstall.arguments['!encryption-password'] = archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ') -archinstall.arguments['harddrive'].encryption_password = archinstall.arguments['!encryption-password'] - -# Get the hostname for the machine -if not archinstall.arguments.get('hostname', None): - archinstall.arguments['hostname'] = input('Desired hostname for the installation: ').strip(' ') - -# Ask for a root password (optional, but triggers requirement for super-user if skipped) -if not archinstall.arguments.get('!root-password', None): - archinstall.arguments['!root-password'] = archinstall.get_password(prompt='Enter root password (Recommended: leave blank to leave root disabled): ') - -# # Storing things in _guided_hidden helps us avoid printing it -# # when echoing user configuration: archinstall.storage['_guided'] -# archinstall.storage['_guided_hidden']['root_pw'] = root_pw -# archinstall.storage['_guided']['root_unlocked'] = True -# break - -# Ask for additional users (super-user if root pw was not set) -archinstall.arguments['users'] = {} -archinstall.arguments['superusers'] = {} -if not archinstall.arguments.get('!root-password', None): - archinstall.arguments['superusers'] = archinstall.ask_for_superuser_account('Create a required super-user with sudo privileges: ', forced=True) - -users, superusers = archinstall.ask_for_additional_users('Any additional users to install (leave blank for no users): ') -archinstall.arguments['users'] = users -archinstall.arguments['superusers'] = {**archinstall.arguments['superusers'], **superusers} - -# Ask for archinstall-specific profiles (such as desktop environments etc) -if not archinstall.arguments.get('profile', None): - archinstall.arguments['profile'] = archinstall.select_profile(archinstall.list_profiles()) -else: - archinstall.arguments['profile'] = archinstall.list_profiles()[archinstall.arguments['profile']] - -# Check the potentially selected profiles preperations to get early checks if some additional questions are needed. -if archinstall.arguments['profile'] and archinstall.arguments['profile'].has_prep_function(): - with archinstall.arguments['profile'].load_instructions(namespace=f"{archinstall.arguments['profile'].namespace}.py") as imported: - if not imported._prep_function(): - archinstall.log( - ' * Profile\'s preparation requirements was not fulfilled.', - bg='black', - fg='red' - ) + + # Ask which harddrive/block-device we will install to + if archinstall.arguments.get('harddrive', None): + archinstall.arguments['harddrive'] = archinstall.BlockDevice(archinstall.arguments['harddrive']) + else: + archinstall.arguments['harddrive'] = archinstall.select_disk(archinstall.all_disks()) + + # Perform a quick sanity check on the selected harddrive. + # 1. Check if it has partitions + # 3. Check that we support the current partitions + # 2. If so, ask if we should keep them or wipe everything + if archinstall.arguments['harddrive'].has_partitions(): + archinstall.log(f"{archinstall.arguments['harddrive']} contains the following partitions:", fg='red') + + # We curate a list pf supported paritions + # and print those that we don't support. + partition_mountpoints = {} + for partition in archinstall.arguments['harddrive']: + try: + if partition.filesystem_supported(): + archinstall.log(f" {partition}") + partition_mountpoints[partition] = None + except archinstall.UnknownFilesystemFormat as err: + archinstall.log(f" {partition} (Filesystem not supported)", fg='red') + + # We then ask what to do with the paritions. + if (option := archinstall.ask_for_disk_layout()) == 'abort': + archinstall.log(f"Safely aborting the installation. No changes to the disk or system has been made.") exit(1) + elif option == 'keep-existing': + archinstall.arguments['harddrive'].keep_partitions = True + + archinstall.log(f" ** You will now select which partitions to use by selecting mount points (inside the installation). **") + 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(), + "Select a partition by number that you want to set a mount-point for (leave blank when done): ") + if not partition: + break -# Additional packages (with some light weight error handling for invalid package names) -if not archinstall.arguments.get('packages', None): - archinstall.arguments['packages'] = [package for package in input('Additional packages aside from base (space separated): ').split(' ') if len(package)] + # Select a mount-point + mountpoint = input(f"Enter a mount-point for {partition}: ").strip(' ') + if len(mountpoint): + + # Get a valid & supported filesystem for the parition: + while 1: + new_filesystem = input(f"Enter a valid filesystem for {partition} (leave blank for {partition.filesystem}): ").strip(' ') + if len(new_filesystem) <= 0: + if partition.encrypted and partition.filesystem == 'crypto_LUKS': + if (autodetected_filesystem := partition.detect_inner_filesystem(archinstall.arguments.get('!encryption-password', None))): + new_filesystem = autodetected_filesystem + else: + archinstall.log(f"Could not auto-detect the filesystem inside the encrypted volume.", fg='red') + archinstall.log(f"A filesystem must be defined for the unlocked encrypted partition.") + continue + break + + # Since the potentially new filesystem is new + # we have to check if we support it. We can do this by formatting /dev/null with the partitions filesystem. + # There's a nice wrapper for this on the partition object itself that supports a path-override during .format() + try: + partition.format(new_filesystem, path='/dev/null', log_formating=False, allow_formatting=True) + except archinstall.UnknownFilesystemFormat: + archinstall.log(f"Selected filesystem is not supported yet. If you want archinstall to support '{new_filesystem}', please create a issue-ticket suggesting it on github at https://github.com/Torxed/archinstall/issues.") + archinstall.log(f"Until then, please enter another supported filesystem.") + continue + except archinstall.SysCallError: + pass # Expected exception since mkfs. can not format /dev/null. + # But that means our .format() function supported it. + break -# Verify packages that were given -try: - archinstall.validate_package_list(archinstall.arguments['packages']) -except archinstall.RequirementError as e: - archinstall.log(e, fg='red') - exit(1) + # When we've selected all three criterias, + # We can safely mark the partition for formatting and where to mount it. + # TODO: allow_formatting might be redundant since target_mountpoint should only be + # set if we actually want to format it anyway. + partition.allow_formatting = True + partition.target_mountpoint = mountpoint + # Only overwrite the filesystem definition if we selected one: + if len(new_filesystem): + partition.filesystem = new_filesystem + + archinstall.log('Using existing partition table reported above.') + elif option == 'format-all': + archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format() + archinstall.arguments['harddrive'].keep_partitions = False + + # Get disk encryption password (or skip if blank) + if not archinstall.arguments.get('!encryption-password', None): + archinstall.arguments['!encryption-password'] = archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ') + archinstall.arguments['harddrive'].encryption_password = archinstall.arguments['!encryption-password'] + + # Get the hostname for the machine + if not archinstall.arguments.get('hostname', None): + archinstall.arguments['hostname'] = input('Desired hostname for the installation: ').strip(' ') + + # Ask for a root password (optional, but triggers requirement for super-user if skipped) + if not archinstall.arguments.get('!root-password', None): + archinstall.arguments['!root-password'] = archinstall.get_password(prompt='Enter root password (Recommended: leave blank to leave root disabled): ') + + # # Storing things in _guided_hidden helps us avoid printing it + # # when echoing user configuration: archinstall.storage['_guided'] + # archinstall.storage['_guided_hidden']['root_pw'] = root_pw + # archinstall.storage['_guided']['root_unlocked'] = True + # break + + # Ask for additional users (super-user if root pw was not set) + archinstall.arguments['users'] = {} + archinstall.arguments['superusers'] = {} + if not archinstall.arguments.get('!root-password', None): + archinstall.arguments['superusers'] = archinstall.ask_for_superuser_account('Create a required super-user with sudo privileges: ', forced=True) + + users, superusers = archinstall.ask_for_additional_users('Any additional users to install (leave blank for no users): ') + archinstall.arguments['users'] = users + archinstall.arguments['superusers'] = {**archinstall.arguments['superusers'], **superusers} + + # Ask for archinstall-specific profiles (such as desktop environments etc) + if not archinstall.arguments.get('profile', None): + archinstall.arguments['profile'] = archinstall.select_profile(archinstall.list_profiles()) + else: + archinstall.arguments['profile'] = archinstall.list_profiles()[archinstall.arguments['profile']] + + # Check the potentially selected profiles preperations to get early checks if some additional questions are needed. + if archinstall.arguments['profile'] and archinstall.arguments['profile'].has_prep_function(): + with archinstall.arguments['profile'].load_instructions(namespace=f"{archinstall.arguments['profile'].namespace}.py") as imported: + if not imported._prep_function(): + archinstall.log( + ' * Profile\'s preparation requirements was not fulfilled.', + bg='black', + fg='red' + ) + exit(1) + + # Additional packages (with some light weight error handling for invalid package names) + if not archinstall.arguments.get('packages', None): + archinstall.arguments['packages'] = [package for package in input('Additional packages aside from base (space separated): ').split(' ') if len(package)] + + # Verify packages that were given + try: + archinstall.validate_package_list(archinstall.arguments['packages']) + except archinstall.RequirementError as e: + archinstall.log(e, fg='red') + exit(1) -# Ask or Call the helper function that asks the user to optionally configure a network. -if not archinstall.arguments.get('nic', None): - archinstall.arguments['nic'] = archinstall.ask_to_configure_network() + # Ask or Call the helper function that asks the user to optionally configure a network. + if not archinstall.arguments.get('nic', None): + archinstall.arguments['nic'] = archinstall.ask_to_configure_network() -print() -print('This is your chosen configuration:') -archinstall.log("-- Guided template chosen (with below config) --", level=archinstall.LOG_LEVELS.Debug) -archinstall.log(json.dumps(archinstall.arguments, indent=4, sort_keys=True, cls=archinstall.JSON), level=archinstall.LOG_LEVELS.Info) -print() +def perform_installation_steps(): + print() + print('This is your chosen configuration:') + archinstall.log("-- Guided template chosen (with below config) --", level=archinstall.LOG_LEVELS.Debug) + archinstall.log(json.dumps(archinstall.arguments, indent=4, sort_keys=True, cls=archinstall.JSON), level=archinstall.LOG_LEVELS.Info) + print() -input('Press Enter to continue.') + input('Press Enter to continue.') -""" - Issue a final warning before we continue with something un-revertable. - We mention the drive one last time, and count from 5 to 0. -""" + """ + Issue a final warning before we continue with something un-revertable. + We mention the drive one last time, and count from 5 to 0. + """ -print(f" ! Formatting {archinstall.arguments['harddrive']} in ", end='') + print(f" ! Formatting {archinstall.arguments['harddrive']} in ", end='') -for i in range(5, 0, -1): - print(f"{i}", end='') + for i in range(5, 0, -1): + print(f"{i}", end='') - for x in range(4): - sys.stdout.flush() - time.sleep(0.25) - print(".", end='') + for x in range(4): + sys.stdout.flush() + time.sleep(0.25) + print(".", end='') - if SIG_TRIGGER: - abort = input('\nDo you really want to abort (y/n)? ') - if abort.strip() != 'n': - exit(0) + if SIG_TRIGGER: + abort = input('\nDo you really want to abort (y/n)? ') + if abort.strip() != 'n': + exit(0) - if SIG_TRIGGER is False: - sys.stdin.read() - SIG_TRIGGER = False - signal.signal(signal.SIGINT, sig_handler) + if SIG_TRIGGER is False: + sys.stdin.read() + SIG_TRIGGER = False + signal.signal(signal.SIGINT, sig_handler) -# Put back the default/original signal handler now that we're done catching -# and interrupting SIGINT with "Do you really want to abort". -print() -signal.signal(signal.SIGINT, original_sigint_handler) + # Put back the default/original signal handler now that we're done catching + # and interrupting SIGINT with "Do you really want to abort". + print() + signal.signal(signal.SIGINT, original_sigint_handler) -""" - Setup the blockdevice, filesystem (and optionally encryption). - Once that's done, we'll hand over to perform_installation() -""" -with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs: - # Wipe the entire drive if the disk flag `keep_partitions`is False. - if archinstall.arguments['harddrive'].keep_partitions is False: - fs.use_entire_disk(root_filesystem_type=archinstall.arguments.get('filesystem', 'btrfs'), - encrypt_root_partition=archinstall.arguments.get('!encryption-password', False)) - # Otherwise, check if encryption is desired and mark the root partition as encrypted. - elif archinstall.arguments.get('!encryption-password', None): - root_partition = fs.find_partition('/') - root_partition.encrypted = True - - # After the disk is ready, iterate the partitions and check - # which ones are safe to format, and format those. - for partition in archinstall.arguments['harddrive']: - if partition.safe_to_format(): - if partition.encrypted: - partition.encrypt(password=archinstall.arguments.get('!encryption-password', None)) + """ + Setup the blockdevice, filesystem (and optionally encryption). + Once that's done, we'll hand over to perform_installation() + """ + with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs: + # Wipe the entire drive if the disk flag `keep_partitions`is False. + if archinstall.arguments['harddrive'].keep_partitions is False: + fs.use_entire_disk(root_filesystem_type=archinstall.arguments.get('filesystem', 'btrfs'), + encrypt_root_partition=archinstall.arguments.get('!encryption-password', False)) + # Otherwise, check if encryption is desired and mark the root partition as encrypted. + elif archinstall.arguments.get('!encryption-password', None): + root_partition = fs.find_partition('/') + root_partition.encrypted = True + + # After the disk is ready, iterate the partitions and check + # which ones are safe to format, and format those. + for partition in archinstall.arguments['harddrive']: + if partition.safe_to_format(): + if partition.encrypted: + partition.encrypt(password=archinstall.arguments.get('!encryption-password', None)) + else: + partition.format() else: - partition.format() + archinstall.log(f"Did not format {partition} because .safe_to_format() returned False or .allow_formatting was False.", level=archinstall.LOG_LEVELS.Debug) + + if archinstall.arguments.get('!encryption-password', None): + # First encrypt and unlock, then format the desired partition inside the encrypted part. + # archinstall.luks2() encrypts the partition when entering the with context manager, and + # unlocks the drive so that it can be used as a normal block-device within archinstall. + with archinstall.luks2(fs.find_partition('/'), 'luksloop', archinstall.arguments.get('!encryption-password', None)) as unlocked_device: + unlocked_device.format(fs.find_partition('/').filesystem) + + perform_installation(device=unlocked_device, + boot_partition=fs.find_partition('/boot'), + language=archinstall.arguments['keyboard-language'], + mirrors=archinstall.arguments['mirror-region']) else: - archinstall.log(f"Did not format {partition} because .safe_to_format() returned False or .allow_formatting was False.", level=archinstall.LOG_LEVELS.Debug) - - if archinstall.arguments.get('!encryption-password', None): - # First encrypt and unlock, then format the desired partition inside the encrypted part. - # archinstall.luks2() encrypts the partition when entering the with context manager, and - # unlocks the drive so that it can be used as a normal block-device within archinstall. - with archinstall.luks2(fs.find_partition('/'), 'luksloop', archinstall.arguments.get('!encryption-password', None)) as unlocked_device: - unlocked_device.format(fs.find_partition('/').filesystem) - - perform_installation(device=unlocked_device, + archinstall.arguments['harddrive'].partition[1].format('ext4') + perform_installation(device=fs.find_partition('/'), boot_partition=fs.find_partition('/boot'), language=archinstall.arguments['keyboard-language'], mirrors=archinstall.arguments['mirror-region']) - else: - archinstall.arguments['harddrive'].partition[1].format('ext4') - perform_installation(device=fs.find_partition('/'), - boot_partition=fs.find_partition('/boot'), - language=archinstall.arguments['keyboard-language'], - mirrors=archinstall.arguments['mirror-region']) \ No newline at end of file + +ask_user_questions() +perform_pre_installation_steps() \ No newline at end of file From 2bd220c28016ca46b5e44235c08f384ea1e8f44f Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 16:13:21 +0100 Subject: [PATCH 098/100] Moved everything into a function block rather than having everything on a line by line situation. This just helps separate what the two major components/blocks are of the guided profile. The change was done in the previous commit, but a spelling mistake was made here and I forgot to mention it in the already long commit log. But user-questions is now in one function. Installation steps are now in a two-tier function, one for setup and one for install steps. --- examples/guided.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/examples/guided.py b/examples/guided.py index 28d07d0ff3..9d24284240 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -60,15 +60,21 @@ def perform_installation(device, boot_partition, language, mirrors): installation.install_profile(profile) if archinstall.arguments.get('users', None): - for user, password in archinstall.arguments.get('users').items(): + for user in archinstall.arguments.get('users'): + password = users[user] installation.user_create(user, password, sudo=False) - if archinstall.arguments.get('superusers', None): - for user, password in archinstall.arguments.get('superusers').items(): - installation.user_create(user, password, sudo=True) + for user in archinstall.arguments.get('users'): + password = users[user] + installation.user_create(user, password, sudo=Tru) + + # sudo = False + # if 'root_pw' not in archinstall.storage['_guided_hidden'] or len(archinstall.storage['_guided_hidden']['root_pw'].strip()) == 0: + # sudo = True + - if (root_pw := archinstall.arguments.get('!root-password', None)) and len(root_pw): - installation.user_set_pw('root', root_pw) + if 'root_pw' in archinstall.storage['_guided_hidden'] and archinstall.storage['_guided_hidden']['root_pw']: + installation.user_set_pw('root', archinstall.storage['_guided_hidden']['root_pw']) def ask_user_questions(): """ @@ -322,4 +328,4 @@ def perform_installation_steps(): mirrors=archinstall.arguments['mirror-region']) ask_user_questions() -perform_pre_installation_steps() \ No newline at end of file +perform_installation_steps() \ No newline at end of file From aabdce9286b9b39bbf89bb2ed65c4de5dcf61caa Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 16:16:05 +0100 Subject: [PATCH 099/100] fixed a global variable as well as re-organized the guided.py to better match the flow of steps.. 1) Ask user questions function, 2) Perform installation steps, 3) Perform actual installation --- examples/guided.py | 117 +++++++++++++++++++++++---------------------- 1 file changed, 61 insertions(+), 56 deletions(-) diff --git a/examples/guided.py b/examples/guided.py index 9d24284240..2a7414ea12 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -20,62 +20,6 @@ def sig_handler(sig, frame): signal.signal(signal.SIGINT, sig_handler) -def perform_installation(device, boot_partition, language, mirrors): - """ - 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(device, boot_partition=boot_partition, hostname=archinstall.arguments.get('hostname', 'Archinstall')) as 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(f'Waiting for automatic mirror selection has completed before using custom mirrors.') - while 'dead' not in (status := archinstall.service_state('reflector')): - time.sleep(1) - - archinstall.use_mirrors(mirrors) # Set the mirrors for the live medium - if installation.minimal_installation(): - installation.set_mirrors(mirrors) # Set the mirrors in the installation medium - installation.set_keyboard_language(language) - installation.add_bootloader() - - # If user selected to copy the current ISO network configuration - # Perform a copy of the config - if archinstall.arguments.get('nic', None) == 'Copy ISO network configuration to installation': - installation.copy_ISO_network_config(enable_services=True) # Sources the ISO network configuration to the install medium. - - # Otherwise, if a interface was selected, configure that interface - elif archinstall.arguments.get('nic', None): - installation.configure_nic(**archinstall.arguments.get('nic', {})) - installation.enable_service('systemd-networkd') - installation.enable_service('systemd-resolved') - - - 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) and len(profile := archinstall.arguments.get('profile').strip()): - installation.install_profile(profile) - - if archinstall.arguments.get('users', None): - for user in archinstall.arguments.get('users'): - password = users[user] - installation.user_create(user, password, sudo=False) - if archinstall.arguments.get('superusers', None): - for user in archinstall.arguments.get('users'): - password = users[user] - installation.user_create(user, password, sudo=Tru) - - # sudo = False - # if 'root_pw' not in archinstall.storage['_guided_hidden'] or len(archinstall.storage['_guided_hidden']['root_pw'].strip()) == 0: - # sudo = True - - - if 'root_pw' in archinstall.storage['_guided_hidden'] and archinstall.storage['_guided_hidden']['root_pw']: - installation.user_set_pw('root', archinstall.storage['_guided_hidden']['root_pw']) - def ask_user_questions(): """ First, we'll ask the user for a bunch of user input. @@ -245,7 +189,10 @@ def ask_user_questions(): if not archinstall.arguments.get('nic', None): archinstall.arguments['nic'] = archinstall.ask_to_configure_network() + def perform_installation_steps(): + global SIG_TRIGGER + print() print('This is your chosen configuration:') archinstall.log("-- Guided template chosen (with below config) --", level=archinstall.LOG_LEVELS.Debug) @@ -327,5 +274,63 @@ def perform_installation_steps(): language=archinstall.arguments['keyboard-language'], mirrors=archinstall.arguments['mirror-region']) + +def perform_installation(device, boot_partition, language, mirrors): + """ + 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(device, boot_partition=boot_partition, hostname=archinstall.arguments.get('hostname', 'Archinstall')) as 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(f'Waiting for automatic mirror selection has completed before using custom mirrors.') + while 'dead' not in (status := archinstall.service_state('reflector')): + time.sleep(1) + + archinstall.use_mirrors(mirrors) # Set the mirrors for the live medium + if installation.minimal_installation(): + installation.set_mirrors(mirrors) # Set the mirrors in the installation medium + installation.set_keyboard_language(language) + installation.add_bootloader() + + # If user selected to copy the current ISO network configuration + # Perform a copy of the config + if archinstall.arguments.get('nic', None) == 'Copy ISO network configuration to installation': + installation.copy_ISO_network_config(enable_services=True) # Sources the ISO network configuration to the install medium. + + # Otherwise, if a interface was selected, configure that interface + elif archinstall.arguments.get('nic', None): + installation.configure_nic(**archinstall.arguments.get('nic', {})) + installation.enable_service('systemd-networkd') + installation.enable_service('systemd-resolved') + + + 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) and len(profile := archinstall.arguments.get('profile').strip()): + installation.install_profile(profile) + + if archinstall.arguments.get('users', None): + for user in archinstall.arguments.get('users'): + password = users[user] + installation.user_create(user, password, sudo=False) + if archinstall.arguments.get('superusers', None): + for user in archinstall.arguments.get('users'): + password = users[user] + installation.user_create(user, password, sudo=Tru) + + # sudo = False + # if 'root_pw' not in archinstall.storage['_guided_hidden'] or len(archinstall.storage['_guided_hidden']['root_pw'].strip()) == 0: + # sudo = True + + + if 'root_pw' in archinstall.storage['_guided_hidden'] and archinstall.storage['_guided_hidden']['root_pw']: + installation.user_set_pw('root', archinstall.storage['_guided_hidden']['root_pw']) + + ask_user_questions() perform_installation_steps() \ No newline at end of file From 3f8f0020f5ee8985806b1b62c8826f113ee9df51 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 9 Mar 2021 16:22:36 +0100 Subject: [PATCH 100/100] Purged last legacy, setting the root pw --- examples/guided.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/examples/guided.py b/examples/guided.py index 2a7414ea12..9339f9698d 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -141,12 +141,6 @@ def ask_user_questions(): if not archinstall.arguments.get('!root-password', None): archinstall.arguments['!root-password'] = archinstall.get_password(prompt='Enter root password (Recommended: leave blank to leave root disabled): ') - # # Storing things in _guided_hidden helps us avoid printing it - # # when echoing user configuration: archinstall.storage['_guided'] - # archinstall.storage['_guided_hidden']['root_pw'] = root_pw - # archinstall.storage['_guided']['root_unlocked'] = True - # break - # Ask for additional users (super-user if root pw was not set) archinstall.arguments['users'] = {} archinstall.arguments['superusers'] = {} @@ -323,13 +317,9 @@ def perform_installation(device, boot_partition, language, mirrors): password = users[user] installation.user_create(user, password, sudo=Tru) - # sudo = False - # if 'root_pw' not in archinstall.storage['_guided_hidden'] or len(archinstall.storage['_guided_hidden']['root_pw'].strip()) == 0: - # sudo = True - - if 'root_pw' in archinstall.storage['_guided_hidden'] and archinstall.storage['_guided_hidden']['root_pw']: - installation.user_set_pw('root', archinstall.storage['_guided_hidden']['root_pw']) + if (root_pw := archinstall.arguments.get('!root-password', None)) and len(root_pw): + installation.user_set_pw('root', root_pw) ask_user_questions()