From c20be61e124ccb3f2434c6dc61016524ed936c7c Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Thu, 6 May 2021 14:20:20 +0200 Subject: Removed obsolete variable in prep for multi-disk support. --- archinstall/lib/disk.py | 1 - 1 file changed, 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 44462a21..1865c4fb 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -30,7 +30,6 @@ class BlockDevice(): # TODO: Currently disk encryption is a BIT misleading. # It's actually partition-encryption, but for future-proofing this # I'm placing the encryption password on a BlockDevice level. - self.encryption_password = None def __repr__(self, *args, **kwargs): return f"BlockDevice({self.device})" -- cgit v1.2.3-70-g09d2 From 8925be6c8725c76b4a5f13f0b110759f9c4eecc8 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Thu, 6 May 2021 15:18:57 +0200 Subject: Adding in partition layout structure --- archinstall/lib/user_interaction.py | 42 +++++++++++++++++++++++++++++++++++++ examples/guided.py | 14 +++++++++++++ 2 files changed, 56 insertions(+) (limited to 'archinstall/lib') diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index be01594e..981e1b29 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -484,6 +484,48 @@ def generic_select(options, input_text="Select one of the above by index or abso return selected_option +def select_partitions(block_devices :list): + return { + "/dev/sda": { # Block Device level + "wipe": False, # Safety flags + "partitions" : [ # Affected / New partitions + { + "PARTUUID" : "654bb317-1b73-4339-9a00-7222792f4ba9", # If existing partition + "wipe" : False, # Safety flags + "boot" : True, # Safety flags / new flags + "ESP" : True # Safety flags / new flags + } + ] + }, + "/dev/sdb" : { + "wipe" : True, + "partitions" : [ + { + # No PARTUUID required here since it's a new partition + "type" : "primary", # parted options + "size" : "100%", + "filesystem" : { + "encrypted" : True, # TODO: Not sure about this here + "format": "btrfs", # mkfs options + } + } + ] + } + } + +def select_disk_layout(block_devices :list): + modes = [ + "Wipe all selected drives and use a best-effort default partition layout", + "Select which partitions to use (and what to do with them)" + ] + + mode = input("Do you wish to ") + + if mode == 'Select which partitions to use (and what to do with them)': + return select_partitions(block_devices) + else: + return get_default_partition_layout(block_devices) + def select_disk(dict_o_disks): """ Asks the user to select a harddrive from the `dict_o_disks` selection. diff --git a/examples/guided.py b/examples/guided.py index 069a89d5..8e267df9 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -24,11 +24,13 @@ def ask_user_questions(): except archinstall.RequirementError as err: archinstall.log(err, fg="red") + # 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): while True: @@ -56,20 +58,27 @@ def ask_user_questions(): if archinstall.arguments.get('harddrives', None): archinstall.storage['disk_layouts'] = archinstall.select_disk_layout(archinstall.arguments['harddrives']) + # Get disk encryption password (or skip if blank) if archinstall.arguments['harddrives'] and archinstall.arguments.get('!encryption-password', None) is None: if (passwd := archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ')): archinstall.arguments['!encryption-password'] = passwd + + # Ask which boot-loader to use (will only ask if we're in BIOS (non-efi) mode) archinstall.arguments["bootloader"] = archinstall.ask_for_bootloader() + + # 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 (Recommendation: leave blank to leave root disabled): ') + # Ask for additional users (super-user if root pw was not set) archinstall.arguments['users'] = {} archinstall.arguments['superusers'] = {} @@ -80,12 +89,14 @@ def ask_user_questions(): 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(filter_top_level_profiles=True)) else: archinstall.arguments['profile'] = archinstall.list_profiles()[archinstall.arguments['profile']] + # Check the potentially selected profiles preparations 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: @@ -96,6 +107,7 @@ def ask_user_questions(): ) exit(1) + # Ask about audio server selection if one is not already set if not archinstall.arguments.get('audio', None): # only ask for audio server selection on a desktop profile @@ -106,11 +118,13 @@ def ask_user_questions(): # we will not try to remove packages post-installation to not have audio, as that may cause multiple issues archinstall.arguments['audio'] = None + # Ask for preferred kernel: if not archinstall.arguments.get("kernels", None): kernels = ["linux", "linux-lts", "linux-zen", "linux-hardened"] archinstall.arguments['kernels'] = archinstall.select_kernel(kernels) + # Additional packages (with some light weight error handling for invalid package names) print("Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed.") print("If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt.") -- cgit v1.2.3-70-g09d2 From d6f63375c65917fda3458d7efb1ed787b9f0d1ed Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Fri, 7 May 2021 16:38:46 +0200 Subject: Splitting up partitioning logic some more. --- archinstall/lib/user_interaction.py | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 981e1b29..82d4d5ca 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -484,7 +484,7 @@ def generic_select(options, input_text="Select one of the above by index or abso return selected_option -def select_partitions(block_devices :list): +def select_partition_layout(block_device): return { "/dev/sda": { # Block Device level "wipe": False, # Safety flags @@ -493,7 +493,8 @@ def select_partitions(block_devices :list): "PARTUUID" : "654bb317-1b73-4339-9a00-7222792f4ba9", # If existing partition "wipe" : False, # Safety flags "boot" : True, # Safety flags / new flags - "ESP" : True # Safety flags / new flags + "ESP" : True, # Safety flags / new flags + "mountpoint" : "/mnt/boot" } ] }, @@ -507,24 +508,43 @@ def select_partitions(block_devices :list): "filesystem" : { "encrypted" : True, # TODO: Not sure about this here "format": "btrfs", # mkfs options - } + }, + "mountpoint" : "/mnt" } ] } } +def select_individual_blockdevice_usage(block_devices :list): + result = {} + + for device in block_devices: + print(f'Select what to do with {device}') + modes = [ + "Wipe and create new partitions", + "Re-use partitions" + ] + + device_mode = generic_select(modes) + if device_mode == "Re-use partitions": + layout = select_partition_layout(device) + result[device.path] = layout + else: + ... + + def select_disk_layout(block_devices :list): modes = [ "Wipe all selected drives and use a best-effort default partition layout", - "Select which partitions to use (and what to do with them)" + "Select what to do with each individual drive (followed by partition usage)" ] mode = input("Do you wish to ") - if mode == 'Select which partitions to use (and what to do with them)': - return select_partitions(block_devices) - else: + if mode == 'Wipe all selected drives and use a best-effort default partition layout': return get_default_partition_layout(block_devices) + else: + partitions = select_individual_blockdevice_usage(block_devices) def select_disk(dict_o_disks): """ -- cgit v1.2.3-70-g09d2 From 3e601ff9ab32947cc5a12b6059cde360b9191477 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 10 May 2021 10:14:33 +0200 Subject: Added a soft-wrapper around parted logic. This logic will guide users through setting up partitions, if they chose to wipe a drive. We'll avoid doing to much auto-magic, and this is just a start. --- archinstall/lib/user_interaction.py | 78 ++++++++++++++++++++++++++++++++++++- examples/guided.py | 10 +++-- 2 files changed, 82 insertions(+), 6 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 82d4d5ca..57ea5349 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -444,6 +444,7 @@ def generic_select(options, input_text="Select one of the above by index or abso # Therefore, now we can only provide the dictionary itself if type(options) == dict: options = list(options.values()) if sort: options = sorted(options) # As we pass only list and dict (converted to list), we can skip converting to list + options = [x for x in options if x] # Clean it up from empty options if len(options) == 0: log(f" * Generic select didn't find any options to choose from * ", fg='red') log(" * If problem persists, please create an issue on https://github.com/archlinux/archinstall/issues * ", fg='yellow') @@ -515,6 +516,77 @@ def select_partition_layout(block_device): } } +def valid_fs_type(fstype :str) -> bool: + # https://www.gnu.org/software/parted/manual/html_node/mkpart.html + return fstype in [ + "ext2", + "fat16", "fat32", + "hfs", "hfs+", "hfsx", + "linux-swap", + "NTFS", + "reiserfs", + "ufs", + "btrfs", + ] + +def valid_parted_position(pos :str): + return len(pos) and (start.isdigit() or (start[-1] == '%' and start[:-1].isdigit())) + +def partition_overlap(partitions :list, start :str, end :str) -> bool: + # TODO: Implement sanity check + return False + +def wipe_and_create_partitions(block_device): + if hasUEFI(): + partition_type = 'gpt' + else: + partition_type = 'msdos' + + partitions_result = [] + + while True: + modes = [ + "Create new partition", + "Delete partition" if len(block_device) else "", + "Assign mount-point for partition" if len(block_device) else "", + "Mark a partition as encrypted" if len(block_device) else "", + "Mark a partition as bootable (automatic for /boot)" if len(block_device) else "" + ] + + task = generic_select(modes, + input_text=f"Select what to do with {block_device}: ") + + if task == 'Create new partition': + if partition_type == 'gpt': + # https://www.gnu.org/software/parted/manual/html_node/mkpart.html + # https://www.gnu.org/software/parted/manual/html_node/mklabel.html + name = input("Enter a desired name for the partition: ").strip() + fstype = input("Enter a desired filesystem type for the partition: ").strip() + start = input("Enter the start sector of the partition (percentage or block number, ex: 0%): ").strip() + end = input("Enter the end sector of the partition (percentage or block number, ex: 100%): ").strip() + + if valid_parted_position(start) and valid_parted_position(end) and valid_fs_type(fstype): + if partition_overlap(partitions_result, start, end): + log(f"This partition overlaps with other partitions on the drive! Ignoring this partition creation.", fg="red") + continue + + partitions_result.append({ + "type" : "primary", # Strictly only allowed under MSDOS, but GPT accepts it so it's "safe" to inject + "start" : start, + "size" : end, + "filesystem" : { + "format" : fstype + } + }) + else: + log(f"Invalid start, end or fstype for this partition. Ignoring this partition creation.", fg="red") + continue + + elif task == "Delete partition": + elif task == "Assign mount-point for partition": + elif task == "Mark a partition as encrypted": + elif task == "Mark a partition as bootable (automatic for /boot)": + def select_individual_blockdevice_usage(block_devices :list): result = {} @@ -526,11 +598,13 @@ def select_individual_blockdevice_usage(block_devices :list): ] device_mode = generic_select(modes) + if device_mode == "Re-use partitions": layout = select_partition_layout(device) - result[device.path] = layout else: - ... + layout = wipe_and_create_partitions(device) + + result[device.path] = layout def select_disk_layout(block_devices :list): diff --git a/examples/guided.py b/examples/guided.py index 8e267df9..d65df218 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -44,6 +44,12 @@ def ask_user_questions(): archinstall.arguments['mirror-region'] = {selected_region : archinstall.list_mirrors()[selected_region]} + # Ask which boot-loader to use (will only ask if we're in BIOS (non-efi) mode) + # We do this before the disk selection process because there are some soft dependencies + # in retards to which boot loader/mode we're in. + archinstall.arguments["bootloader"] = archinstall.ask_for_bootloader() + + # Ask which harddrives/block-devices we will install to # and convert them into archinstall.BlockDevice() objects. if archinstall.arguments.get('harddrives', None): @@ -65,10 +71,6 @@ def ask_user_questions(): archinstall.arguments['!encryption-password'] = passwd - # Ask which boot-loader to use (will only ask if we're in BIOS (non-efi) mode) - archinstall.arguments["bootloader"] = archinstall.ask_for_bootloader() - - # Get the hostname for the machine if not archinstall.arguments.get('hostname', None): archinstall.arguments['hostname'] = input('Desired hostname for the installation: ').strip(' ') -- cgit v1.2.3-70-g09d2 From 6d5d9a1798e97b5e2d1db3339197ca2a767a6715 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 10 May 2021 14:32:39 +0200 Subject: Added Partition() properties: sector_size, start, end, boot, partition_type and a __dump__() function. As well as kept working on the partition logic of guided to have a more traditional workflow of adding/deleting partitions in a guided manner, as well as the ability to mark partitions as encrypted/boot and set target mountpoints. --- archinstall/lib/disk.py | 96 +++++++++++++++++++++++++++++++++++-- archinstall/lib/user_interaction.py | 34 ++++++++++--- examples/guided.py | 10 ++-- 3 files changed, 125 insertions(+), 15 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 1865c4fb..dd704261 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -27,6 +27,7 @@ class BlockDevice(): self.info = info self.keep_partitions = True self.part_cache = OrderedDict() + # TODO: Currently disk encryption is a BIT misleading. # It's actually partition-encryption, but for future-proofing this # I'm placing the encryption password on a BlockDevice level. @@ -43,6 +44,9 @@ class BlockDevice(): raise KeyError(f'{self} does not contain information: "{key}"') return self.info[key] + def __len__(self): + return len(self.partitions) + def json(self): """ json() has precedence over __dump__, so this is a way @@ -56,11 +60,21 @@ class BlockDevice(): def __dump__(self): return { - 'path': self.path, - 'info': self.info, - 'partition_cache': self.part_cache + self.path : { + 'partuuid' : self.uuid, + 'wipe' : self.info.get('wipe', None), + 'partitions' : [part.__dump__() for part in self.partitions.values()] + } } + @property + def partition_type(self): + output = b"".join(sys_command(f"lsblk --json -o+PTTYPE {self.path}")) + output = json.loads(output.decode('UTF-8')) + + for device in output['blockdevices']: + return device['pttype'] + @property def device(self): """ @@ -203,6 +217,82 @@ class Partition(): else: return f'Partition(path={self.path}, size={self.size}, fs={self.filesystem}{mount_repr})' + def __dump__(self): + return { + 'type' : 'primary', + 'PARTUUID' : self.uuid, + 'wipe' : self.allow_formatting, + 'boot' : self.boot, + 'ESP' : self.boot, + 'mountpoint' : self.target_mountpoint, + 'encrypted' : self._encrypted, + 'start' : self.start, + 'size' : self.end, + 'filesystem' : { + 'format' : get_filesystem_type(self.path) + } + } + + @property + def sector_size(self): + output = b"".join(sys_command(f"lsblk --json -o+LOG-SEC {self.path}")) + output = json.loads(output.decode('UTF-8')) + + for device in output['blockdevices']: + return device.get('log-sec', None) + + @property + def start(self): + output = b"".join(sys_command(f"sfdisk --json {self.block_device.path}")) + output = json.loads(output.decode('UTF-8')) + + for partition in output.get('partitionstable', {}).get('partitions', []): + if partition['node'] == self.path: + return partition['start']# * self.sector_size + + @property + def end(self): + # TODO: Verify that the logic holds up, that 'size' is the size without 'start' added to it. + output = b"".join(sys_command(f"sfdisk --json {self.block_device.path}")) + output = json.loads(output.decode('UTF-8')) + + for partition in output.get('partitionstable', {}).get('partitions', []): + if partition['node'] == self.path: + return partition['size']# * self.sector_size + + @property + def boot(self): + output = b"".join(sys_command(f"sfdisk --json {self.block_device.path}")) + output = json.loads(output.decode('UTF-8')) + + # Get the bootable flag from the sfdisk output: + # { + # "partitiontable": { + # "label":"dos", + # "id":"0xd202c10a", + # "device":"/dev/loop0", + # "unit":"sectors", + # "sectorsize":512, + # "partitions": [ + # {"node":"/dev/loop0p1", "start":2048, "size":10483712, "type":"83", "bootable":true} + # ] + # } + # } + + for partition in output.get('partitionstable', {}).get('partitions', []): + if partition['node'] == self.path: + return partition.get('bootable', False) + + return False + + @property + def partition_type(self): + output = b"".join(sys_command(f"lsblk --json -o+PTTYPE {self.path}")) + output = json.loads(output.decode('UTF-8')) + + for device in output['blockdevices']: + return device['pttype'] + @property def uuid(self) -> str: """ diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 57ea5349..050825cb 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -542,7 +542,7 @@ def wipe_and_create_partitions(block_device): else: partition_type = 'msdos' - partitions_result = [] + partitions_result = [block_device.__dump__()] while True: modes = [ @@ -581,11 +581,33 @@ def wipe_and_create_partitions(block_device): else: log(f"Invalid start, end or fstype for this partition. Ignoring this partition creation.", fg="red") continue - - elif task == "Delete partition": - elif task == "Assign mount-point for partition": - elif task == "Mark a partition as encrypted": - elif task == "Mark a partition as bootable (automatic for /boot)": + else: + for index, partition in enumerate(partitions_result): + print(partition) + print(f"{index}: {partition['start']} -> {partition['size']} ({partition['filesystem']['format']}{', mounting at: '+partition['mountpoint'] if partition['mountpoint'] else ''})") + + if task == "Delete partition": + partition = generic_select(partitions_result, 'Select which partition to delete: ', options_output=False) + del(partitions_result[partition]) + elif task == "Assign mount-point for partition": + partition = generic_select(partitions_result, 'Select which partition to mount where: ', options_output=False) + mountpoint = input('Select where to mount partition (leave blank to remove mountpoint): ').strip() + + if len(mountpoint): + partitions_result[partition]['mountpoint'] = mountpoint + if mountpoint == '/boot': + log(f"Marked partition as bootable because mountpoint was set to /boot.", fg="yellow") + partitions_result[partition]['boot'] = True + else: + del(partitions_result[partition]['mountpoint']) + + elif task == "Mark a partition as encrypted": + partition = generic_select(partitions_result, 'Select which partition to mark as encrypted: ', options_output=False) + partitions_result[partition]['encrypted'] = True + + elif task == "Mark a partition as bootable (automatic for /boot)": + partition = generic_select(partitions_result, 'Select which partition to mark as bootable: ', options_output=False) + partitions_result[partition]['boot'] = True def select_individual_blockdevice_usage(block_devices :list): result = {} diff --git a/examples/guided.py b/examples/guided.py index d65df218..8e267df9 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -44,12 +44,6 @@ def ask_user_questions(): archinstall.arguments['mirror-region'] = {selected_region : archinstall.list_mirrors()[selected_region]} - # Ask which boot-loader to use (will only ask if we're in BIOS (non-efi) mode) - # We do this before the disk selection process because there are some soft dependencies - # in retards to which boot loader/mode we're in. - archinstall.arguments["bootloader"] = archinstall.ask_for_bootloader() - - # Ask which harddrives/block-devices we will install to # and convert them into archinstall.BlockDevice() objects. if archinstall.arguments.get('harddrives', None): @@ -71,6 +65,10 @@ def ask_user_questions(): archinstall.arguments['!encryption-password'] = passwd + # Ask which boot-loader to use (will only ask if we're in BIOS (non-efi) mode) + archinstall.arguments["bootloader"] = archinstall.ask_for_bootloader() + + # Get the hostname for the machine if not archinstall.arguments.get('hostname', None): archinstall.arguments['hostname'] = input('Desired hostname for the installation: ').strip(' ') -- cgit v1.2.3-70-g09d2 From 4aaaa3208bedc110cef4b4545031e8e43103161a Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 10 May 2021 16:44:01 +0200 Subject: Tested creating partitions and deleting them. Kinda works now. --- archinstall/lib/user_interaction.py | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 050825cb..21295537 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -518,6 +518,7 @@ def select_partition_layout(block_device): def valid_fs_type(fstype :str) -> bool: # https://www.gnu.org/software/parted/manual/html_node/mkpart.html + return fstype in [ "ext2", "fat16", "fat32", @@ -530,7 +531,19 @@ def valid_fs_type(fstype :str) -> bool: ] def valid_parted_position(pos :str): - return len(pos) and (start.isdigit() or (start[-1] == '%' and start[:-1].isdigit())) + if not len(pos): + return False + + if pos.isdigit(): + return True + + if pos[-1] == '%' and pos[:-1].isdigit(): + return True + + if pos[-3:].lower() in ['mib', 'kib', 'b', 'tib'] and pos[:-3].isdigit(): + return True + + return False def partition_overlap(partitions :list, start :str, end :str) -> bool: # TODO: Implement sanity check @@ -542,17 +555,24 @@ def wipe_and_create_partitions(block_device): else: partition_type = 'msdos' - partitions_result = [block_device.__dump__()] + partitions_result = [part.__dump__() for part in block_device.partitions] while True: modes = [ "Create new partition", - "Delete partition" if len(block_device) else "", - "Assign mount-point for partition" if len(block_device) else "", - "Mark a partition as encrypted" if len(block_device) else "", - "Mark a partition as bootable (automatic for /boot)" if len(block_device) else "" + "Delete partition" if len(partitions_result) else "", + "Assign mount-point for partition" if len(partitions_result) else "", + "Mark a partition as encrypted" if len(partitions_result) else "", + "Mark a partition as bootable (automatic for /boot)" if len(partitions_result) else "" ] + # Print current partition layout: + if len(partitions_result): + print('Current partition layout:') + for partition in partitions_result: + print(partition) + print() + task = generic_select(modes, input_text=f"Select what to do with {block_device}: ") @@ -574,6 +594,7 @@ def wipe_and_create_partitions(block_device): "type" : "primary", # Strictly only allowed under MSDOS, but GPT accepts it so it's "safe" to inject "start" : start, "size" : end, + "mountpoint" : None, "filesystem" : { "format" : fstype } @@ -584,11 +605,11 @@ def wipe_and_create_partitions(block_device): else: for index, partition in enumerate(partitions_result): print(partition) - print(f"{index}: {partition['start']} -> {partition['size']} ({partition['filesystem']['format']}{', mounting at: '+partition['mountpoint'] if partition['mountpoint'] else ''})") + print(f"{index}: Start: {partition['start']}, End: {partition['size']} ({partition['filesystem']['format']}{', mounting at: '+partition['mountpoint'] if partition['mountpoint'] else ''})") if task == "Delete partition": partition = generic_select(partitions_result, 'Select which partition to delete: ', options_output=False) - del(partitions_result[partition]) + del(partitions_result[partitions_result.index(partition)]) elif task == "Assign mount-point for partition": partition = generic_select(partitions_result, 'Select which partition to mount where: ', options_output=False) mountpoint = input('Select where to mount partition (leave blank to remove mountpoint): ').strip() -- cgit v1.2.3-70-g09d2 From dd52bfb3a7f6f7a150e46f7c99d538e26de60276 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 10 May 2021 19:21:05 +0200 Subject: Tested the workflow, and it works pretty decent. There's some kinks to work out. Added BlockDevice().size as well. --- archinstall/lib/disk.py | 10 +++ archinstall/lib/user_interaction.py | 138 +++++++++++++++++++++++++++++------- 2 files changed, 122 insertions(+), 26 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index dd704261..ab3560a9 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -152,6 +152,16 @@ class BlockDevice(): for partition in json.loads(lsblk.decode('UTF-8'))['blockdevices']: return partition.get('uuid', None) + @property + def size(self): + output = b"".join(sys_command(f"lsblk --json -o+SIZE {self.path}")) + output = json.loads(output.decode('UTF-8')) + + for device in output['blockdevices']: + assert device['size'][-1] == 'G' # Make sure we're counting in Gigabytes, otherwise the next logic fails. + + return float(device['size'][:-1]) + def has_partitions(self): return len(self.partitions) diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 21295537..f51fb4a4 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -549,6 +549,43 @@ def partition_overlap(partitions :list, start :str, end :str) -> bool: # TODO: Implement sanity check return False +def get_default_partition_layout(block_devices): + if len(block_devices) == 1: + return { + block_devices[0] : [ + { # Boot + "type" : "primary", + "start" : "0MiB", + "size" : "513MiB", + "boot" : True, + "mountpoint" : "/boot", + "filesystem" : { + "format" : "fat32" + } + }, + { # Root + "type" : "primary", + "start" : "513MiB", + "encrypted" : True, + "size" : f"{max(block_devices[0].size*0.2, 20)}GiB", + "mountpoint" : "", + "filesystem" : { + "format" : "btrfs" + } + }, + { # Home + "type" : "primary", + "encrypted" : True, + "start" : f"{max(block_devices[0].size*0.2, 20)}GiB", + "size" : "100%", + "mountpoint" : "/home", + "filesystem" : { + "format" : "btrfs" + } + } + ] + } + def wipe_and_create_partitions(block_device): if hasUEFI(): partition_type = 'gpt' @@ -556,14 +593,48 @@ def wipe_and_create_partitions(block_device): partition_type = 'msdos' partitions_result = [part.__dump__() for part in block_device.partitions] + suggested_layout = [ + { # Boot + "type" : "primary", + "start" : "0MiB", + "size" : "513MiB", + "boot" : True, + "mountpoint" : "/boot", + "filesystem" : { + "format" : "fat32" + } + }, + { # Root + "type" : "primary", + "start" : "513MiB", + "encrypted" : True, + "size" : f"{max(block_device.size*0.2, 20)}GiB", + "mountpoint" : "", + "filesystem" : { + "format" : "btrfs" + } + }, + { # Home + "type" : "primary", + "encrypted" : True, + "start" : f"{max(block_device.size*0.2, 20)}GiB", + "size" : "100%", + "mountpoint" : "/home", + "filesystem" : { + "format" : "btrfs" + } + } + ] + # TODO: Squeeze in BTRFS subvolumes here while True: modes = [ "Create new partition", + "Suggest partition layout", "Delete partition" if len(partitions_result) else "", "Assign mount-point for partition" if len(partitions_result) else "", - "Mark a partition as encrypted" if len(partitions_result) else "", - "Mark a partition as bootable (automatic for /boot)" if len(partitions_result) else "" + "Mark/Unmark a partition as encrypted" if len(partitions_result) else "", + "Mark/Unmark a partition as bootable (automatic for /boot)" if len(partitions_result) else "" ] # Print current partition layout: @@ -574,7 +645,7 @@ def wipe_and_create_partitions(block_device): print() task = generic_select(modes, - input_text=f"Select what to do with {block_device}: ") + input_text=f"Select what to do with {block_device} (leave blank when done): ") if task == 'Create new partition': if partition_type == 'gpt': @@ -602,39 +673,50 @@ def wipe_and_create_partitions(block_device): else: log(f"Invalid start, end or fstype for this partition. Ignoring this partition creation.", fg="red") continue + elif task == "Suggest partition layout": + if len(partitions_result): + if input(f"{block_device} contains queued partitions, this will remove those, are you sure? y/N: ").strip().lower() in ('', 'n'): + continue + + partitions_result = [*suggested_layout] + elif task is None: + return partitions_result else: for index, partition in enumerate(partitions_result): - print(partition) print(f"{index}: Start: {partition['start']}, End: {partition['size']} ({partition['filesystem']['format']}{', mounting at: '+partition['mountpoint'] if partition['mountpoint'] else ''})") if task == "Delete partition": - partition = generic_select(partitions_result, 'Select which partition to delete: ', options_output=False) - del(partitions_result[partitions_result.index(partition)]) + if (partition := generic_select(partitions_result, 'Select which partition to delete: ', options_output=False)): + del(partitions_result[partitions_result.index(partition)]) elif task == "Assign mount-point for partition": - partition = generic_select(partitions_result, 'Select which partition to mount where: ', options_output=False) - mountpoint = input('Select where to mount partition (leave blank to remove mountpoint): ').strip() + if (partition := generic_select(partitions_result, 'Select which partition to mount where: ', options_output=False)): + print(' * Partition mount-points are relative to inside the installation, the boot would be /boot as an example.') + mountpoint = input('Select where to mount partition (leave blank to remove mountpoint): ').strip() + + if len(mountpoint): + partitions_result[partitions_result.index(partition)]['mountpoint'] = mountpoint + if mountpoint == '/boot': + log(f"Marked partition as bootable because mountpoint was set to /boot.", fg="yellow") + partitions_result[partitions_result.index(partition)]['boot'] = True + else: + del(partitions_result[partitions_result.index(partition)]['mountpoint']) - if len(mountpoint): - partitions_result[partition]['mountpoint'] = mountpoint - if mountpoint == '/boot': - log(f"Marked partition as bootable because mountpoint was set to /boot.", fg="yellow") - partitions_result[partition]['boot'] = True - else: - del(partitions_result[partition]['mountpoint']) + elif task == "Mark/Unmark a partition as encrypted": + if (partition := generic_select(partitions_result, 'Select which partition to mark as encrypted: ', options_output=False)): + # Negate the current encryption marking + partitions_result[partitions_result.index(partition)]['encrypted'] = not partitions_result[partitions_result.index(partition)].get('encrypted', False) - elif task == "Mark a partition as encrypted": - partition = generic_select(partitions_result, 'Select which partition to mark as encrypted: ', options_output=False) - partitions_result[partition]['encrypted'] = True + elif task == "Mark/Unmark a partition as bootable (automatic for /boot)": + if (partition := generic_select(partitions_result, 'Select which partition to mark as bootable: ', options_output=False)): + partitions_result[partitions_result.index(partition)]['boot'] = not partitions_result[partitions_result.index(partition)].get('boot', False) - elif task == "Mark a partition as bootable (automatic for /boot)": - partition = generic_select(partitions_result, 'Select which partition to mark as bootable: ', options_output=False) - partitions_result[partition]['boot'] = True + return partitions_result def select_individual_blockdevice_usage(block_devices :list): result = {} for device in block_devices: - print(f'Select what to do with {device}') + log(f'Select what to do with {device}', fg="yellow") modes = [ "Wipe and create new partitions", "Re-use partitions" @@ -644,10 +726,14 @@ def select_individual_blockdevice_usage(block_devices :list): if device_mode == "Re-use partitions": layout = select_partition_layout(device) - else: + elif device_mode == "Wipe and create new partitions": layout = wipe_and_create_partitions(device) + else: + continue - result[device.path] = layout + result[device] = layout + + return result def select_disk_layout(block_devices :list): @@ -656,12 +742,12 @@ def select_disk_layout(block_devices :list): "Select what to do with each individual drive (followed by partition usage)" ] - mode = input("Do you wish to ") + mode = generic_select(modes, input_text=f"Select what you wish to do with the selected block devices: ") if mode == 'Wipe all selected drives and use a best-effort default partition layout': return get_default_partition_layout(block_devices) else: - partitions = select_individual_blockdevice_usage(block_devices) + return select_individual_blockdevice_usage(block_devices) def select_disk(dict_o_disks): """ -- cgit v1.2.3-70-g09d2 From 20d9858cf608047aeb4f7d2511e2163c04f921de Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 11 May 2021 09:53:18 +0200 Subject: Spelling errors, and filtered the output a bit. --- archinstall/lib/disk.py | 8 ++++---- archinstall/lib/user_interaction.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index ab3560a9..30b66835 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -256,7 +256,7 @@ class Partition(): output = b"".join(sys_command(f"sfdisk --json {self.block_device.path}")) output = json.loads(output.decode('UTF-8')) - for partition in output.get('partitionstable', {}).get('partitions', []): + for partition in output.get('partitiontable', {}).get('partitions', []): if partition['node'] == self.path: return partition['start']# * self.sector_size @@ -265,8 +265,8 @@ class Partition(): # TODO: Verify that the logic holds up, that 'size' is the size without 'start' added to it. output = b"".join(sys_command(f"sfdisk --json {self.block_device.path}")) output = json.loads(output.decode('UTF-8')) - - for partition in output.get('partitionstable', {}).get('partitions', []): + + for partition in output.get('partitiontable', {}).get('partitions', []): if partition['node'] == self.path: return partition['size']# * self.sector_size @@ -289,7 +289,7 @@ class Partition(): # } # } - for partition in output.get('partitionstable', {}).get('partitions', []): + for partition in output.get('partitiontable', {}).get('partitions', []): if partition['node'] == self.path: return partition.get('bootable', False) diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index f51fb4a4..8eaf753e 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -592,7 +592,7 @@ def wipe_and_create_partitions(block_device): else: partition_type = 'msdos' - partitions_result = [part.__dump__() for part in block_device.partitions] + partitions_result = [part.__dump__() for part in block_device.partitions.values()] suggested_layout = [ { # Boot "type" : "primary", @@ -641,7 +641,7 @@ def wipe_and_create_partitions(block_device): if len(partitions_result): print('Current partition layout:') for partition in partitions_result: - print(partition) + print({key: val for key, val in partition.items() if val}) print() task = generic_select(modes, -- cgit v1.2.3-70-g09d2 From 4b6e312cfa73974304d7b52c25aa7d165e4d61a7 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 11 May 2021 11:41:43 +0200 Subject: Flipped log level logic. --- archinstall/lib/output.py | 2 +- examples/guided.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/output.py b/archinstall/lib/output.py index 06d99778..13e6ce20 100644 --- a/archinstall/lib/output.py +++ b/archinstall/lib/output.py @@ -148,7 +148,7 @@ def log(*args, **kwargs): log("Deprecated level detected in log message, please use new logging. instead for the following log message:", fg="red", level=logging.ERROR, force=True) kwargs['level'] = logging.DEBUG - if kwargs['level'] > storage.get('LOG_LEVEL', logging.INFO) and not 'force' in kwargs: + if kwargs['level'] < storage.get('LOG_LEVEL', logging.INFO) and not 'force' in kwargs: # Level on log message was Debug, but output level is set to Info. # In that case, we'll drop it. return None diff --git a/examples/guided.py b/examples/guided.py index 8e267df9..06b6bc13 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -51,7 +51,7 @@ def ask_user_questions(): else: archinstall.arguments['harddrives'] = [ archinstall.BlockDevice(BlockDev) for BlockDev in archinstall.generic_multi_select(archinstall.all_disks(), - text="Select one or more harddrives to use and configure (leave blank to skip this step): " + text="Select one or more harddrives to use and configure (leave blank to skip this step): ", allow_empty=True) ] -- cgit v1.2.3-70-g09d2 From e6c28a94ee42dad37cc69f8ebd3e6edebc33b938 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 11 May 2021 11:48:44 +0200 Subject: Fixed line-ending issue after using generic_multi_select() --- archinstall/lib/user_interaction.py | 2 ++ examples/guided.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 8eaf753e..ab95909f 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -146,6 +146,8 @@ def generic_multi_select(options, text="Select one or more of the options above else: selected_options.append(selected_option) + sys.stdout.write('\n') + sys.stdout.flush() return selected_options diff --git a/examples/guided.py b/examples/guided.py index 06b6bc13..9e56aa44 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -8,7 +8,7 @@ if archinstall.arguments.get('help'): exit(0) # For support reasons, we'll log the disk layout pre installation to match against post-installation layout -archinstall.log(f"Disk states before installing: {archinstall.disk_layouts()}", level=archinstall.LOG_LEVELS.Debug) +archinstall.log(f"Disk states before installing: {archinstall.disk_layouts()}", level=logging.DEBUG) def ask_user_questions(): """ -- cgit v1.2.3-70-g09d2 From 129ceaea8be14362e2b22cbbf8b83ae0e392d1e8 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 11 May 2021 13:37:08 +0200 Subject: Renamed keyboard-language to keyboard-layout to avoid confusion. Added encryption checks for disk layout selection, if disk encryption password is given - but no partitions were found using encryption, the user will be asked which partitions to encrypt - unless there's only /boot and / then we'll automatically select / because that's what we support for now. --- archinstall/lib/disk.py | 12 +++++++++++- archinstall/lib/user_interaction.py | 16 +++++++++++++++- examples/guided.py | 13 +++++++++---- 3 files changed, 35 insertions(+), 6 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 30b66835..3241c455 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -730,4 +730,14 @@ def disk_layouts(): return json.loads(b''.join(handle).decode('UTF-8')) except SysCallError as err: log(f"Could not return disk layouts: {err}") - return None \ No newline at end of file + return None + +def encrypted_partitions(blockdevices :dict) -> bool: + for partition in blockdevices.values(): + if partition.get('encrypted', False): + yield partition + +def find_partition_by_mountpoint(partitions, relative_mountpoint :str): + for partition in partitions: + if partition.get('mountpoint', None) == relative_mountpoint: + return partition \ No newline at end of file diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index ab95909f..91720065 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -150,6 +150,20 @@ def generic_multi_select(options, text="Select one or more of the options above sys.stdout.flush() return selected_options +def select_encrypted_partitions(blockdevices :dict) -> dict: + print(blockdevices[0]) + + if len(blockdevices) == 1: + if len(blockdevices[0]['partitions']) == 2: + root = find_partition_by_mountpoint(blockdevices[0]['partitions'], '/') + blockdevices[0]['partitions'][root]['encrypted'] = True + return True + + options = [] + for partition in blockdevices.values(): + options.append({key: val for key, val in partition.items() if val}) + + print(generic_multi_select(options, f"Choose which partitions to encrypt (leave blank when done): ")) class MiniCurses(): def __init__(self, width, height): @@ -594,7 +608,7 @@ def wipe_and_create_partitions(block_device): else: partition_type = 'msdos' - partitions_result = [part.__dump__() for part in block_device.partitions.values()] + partitions_result = [] # Test code: [part.__dump__() for part in block_device.partitions.values()] suggested_layout = [ { # Boot "type" : "primary", diff --git a/examples/guided.py b/examples/guided.py index 9e56aa44..3f854f4c 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -16,10 +16,10 @@ def ask_user_questions(): 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): + if not archinstall.arguments.get('keyboard-layout', None): while True: try: - archinstall.arguments['keyboard-language'] = archinstall.select_language(archinstall.list_keyboard_languages()).strip() + archinstall.arguments['keyboard-layout'] = archinstall.select_language(archinstall.list_keyboard_languages()).strip() break except archinstall.RequirementError as err: archinstall.log(err, fg="red") @@ -27,8 +27,8 @@ def ask_user_questions(): # 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']) + if len(archinstall.arguments['keyboard-layout']): + archinstall.set_keyboard_language(archinstall.arguments['keyboard-layout']) # Set which region to download packages from during the installation @@ -64,6 +64,11 @@ def ask_user_questions(): if (passwd := archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ')): archinstall.arguments['!encryption-password'] = passwd + # If no partitions was marked as encrypted (rare), but a password was supplied - + # then we need to identify which partitions to encrypt. This will default to / (root) if only + # root and boot are detected. + if len(list(archinstall.encrypted_partitions(archinstall.storage['disk_layouts']))) == 0: + archinstall.storage['disk_layouts'] = archinstall.select_encrypted_partitions(archinstall.storage['disk_layouts']) # Ask which boot-loader to use (will only ask if we're in BIOS (non-efi) mode) archinstall.arguments["bootloader"] = archinstall.ask_for_bootloader() -- cgit v1.2.3-70-g09d2 From f9774af2cc4dc113293664d44de1a7598348326e Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 6 Jun 2021 15:31:51 +0200 Subject: As of Python 3.6 and 3.7, dictionaries are ordered by insertion by default. We there for no longer need to use OrderedDict for our dictionaries where this logic is required. --- archinstall/lib/disk.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 3fda5da6..1ac6d9b0 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -2,7 +2,6 @@ import glob import pathlib import re import time -from collections import OrderedDict from typing import Optional from .general import * @@ -30,7 +29,7 @@ class BlockDevice: self.path = path self.info = info self.keep_partitions = True - self.part_cache = OrderedDict() + self.part_cache = {} # TODO: Currently disk encryption is a BIT misleading. # It's actually partition-encryption, but for future-proofing this @@ -177,7 +176,7 @@ class BlockDevice: return False def flush_cache(self): - self.part_cache = OrderedDict() + self.part_cache = {} class Partition: @@ -673,7 +672,7 @@ def device_state(name, *args, **kwargs): # lsblk --json -l -n -o path def all_disks(*args, **kwargs): kwargs.setdefault("partitions", False) - drives = OrderedDict() + drives = {} # for drive in json.loads(sys_command(f'losetup --json', *args, **lkwargs, hide_from_log=True)).decode('UTF_8')['loopdevices']: for drive in json.loads(b''.join(SysCommand('lsblk --json -l -n -o path,size,type,mountpoint,label,pkname,model')).decode('UTF_8'))['blockdevices']: if not kwargs['partitions'] and drive['type'] == 'part': -- cgit v1.2.3-70-g09d2 From ce4b1fbcff00a3029de06f4b20e60dcaa9e3af93 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 6 Jun 2021 16:19:53 +0200 Subject: Added sorting logic for BlockDevice. Also swapped sys_command() to SysCommand() and refined the logic around those calls. --- archinstall/lib/disk.py | 97 +++++++++++++++++++++---------------------------- examples/guided.py | 9 ++--- 2 files changed, 46 insertions(+), 60 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 1ac6d9b0..58e2eb59 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -50,6 +50,9 @@ class BlockDevice: def __len__(self): return len(self.partitions) + def __lt__(self, left_comparitor): + return self.path < left_comparitor.path + def json(self): """ json() has precedence over __dump__, so this is a way @@ -72,8 +75,7 @@ class BlockDevice: @property def partition_type(self): - output = b"".join(sys_command(f"lsblk --json -o+PTTYPE {self.path}")) - output = json.loads(output.decode('UTF-8')) + output = json.loads(SysCommand(f"lsblk --json -o+PTTYPE {self.path}").decode('UTF-8')) for device in output['blockdevices']: return device['pttype'] @@ -90,7 +92,7 @@ class BlockDevice: raise DiskError(f'Could not locate backplane info for "{self.path}"') if self.info['type'] == 'loop': - for drive in json.loads(b''.join(SysCommand(['losetup', '--json'])).decode('UTF_8'))['loopdevices']: + for drive in json.loads(SysCommand(['losetup', '--json']).decode('UTF_8'))['loopdevices']: if not drive['name'] == self.path: continue @@ -112,18 +114,17 @@ class BlockDevice: @property def partitions(self): - o = b''.join(SysCommand(['partprobe', self.path])) + SysCommand(['partprobe', self.path]) - # o = b''.join(sys_command('/usr/bin/lsblk -o name -J -b {dev}'.format(dev=dev))) - o = b''.join(SysCommand(['/usr/bin/lsblk', '-J', self.path])) + result = SysCommand(['/usr/bin/lsblk', '-J', self.path]) - if b'not a block device' in o: + if b'not a block device' in result: raise DiskError(f'Can not read partitions off something that isn\'t a block device: {self.path}') - if not o[:1] == b'{': + if not result[:1] == b'{': raise DiskError('Error getting JSON output from:', f'/usr/bin/lsblk -J {self.path}') - r = json.loads(o.decode('UTF-8')) + r = json.loads(result.decode('UTF-8')) if len(r['blockdevices']) and 'children' in r['blockdevices'][0]: root_path = f"/dev/{r['blockdevices'][0]['name']}" for part in r['blockdevices'][0]['children']: @@ -152,14 +153,12 @@ class BlockDevice: This is more reliable than relying on /dev/disk/by-partuuid as it doesn't seam to be able to detect md raid partitions. """ - lsblk = b''.join(SysCommand(f'lsblk -J -o+UUID {self.path}')) - for partition in json.loads(lsblk.decode('UTF-8'))['blockdevices']: + for partition in json.loads(SysCommand(f'lsblk -J -o+UUID {self.path}').decode('UTF-8'))['blockdevices']: return partition.get('uuid', None) @property def size(self): - output = b"".join(sys_command(f"lsblk --json -o+SIZE {self.path}")) - output = json.loads(output.decode('UTF-8')) + output = json.loads(SysCommand(f"lsblk --json -o+SIZE {self.path}").decode('UTF-8')) for device in output['blockdevices']: assert device['size'][-1] == 'G' # Make sure we're counting in Gigabytes, otherwise the next logic fails. @@ -250,16 +249,14 @@ class Partition: @property def sector_size(self): - output = b"".join(sys_command(f"lsblk --json -o+LOG-SEC {self.path}")) - output = json.loads(output.decode('UTF-8')) + output = json.loads(SysCommand(f"lsblk --json -o+LOG-SEC {self.path}").decode('UTF-8')) for device in output['blockdevices']: return device.get('log-sec', None) @property def start(self): - output = b"".join(sys_command(f"sfdisk --json {self.block_device.path}")) - output = json.loads(output.decode('UTF-8')) + output = json.loads(SysCommand(f"sfdisk --json {self.block_device.path}").decode('UTF-8')) for partition in output.get('partitiontable', {}).get('partitions', []): if partition['node'] == self.path: @@ -268,8 +265,7 @@ class Partition: @property def end(self): # TODO: Verify that the logic holds up, that 'size' is the size without 'start' added to it. - output = b"".join(sys_command(f"sfdisk --json {self.block_device.path}")) - output = json.loads(output.decode('UTF-8')) + output = json.loads(SysCommand(f"sfdisk --json {self.block_device.path}").decode('UTF-8')) for partition in output.get('partitiontable', {}).get('partitions', []): if partition['node'] == self.path: @@ -277,8 +273,7 @@ class Partition: @property def boot(self): - output = b"".join(sys_command(f"sfdisk --json {self.block_device.path}")) - output = json.loads(output.decode('UTF-8')) + output = json.loads(SysCommand(f"sfdisk --json {self.block_device.path}").decode('UTF-8')) # Get the bootable flag from the sfdisk output: # { @@ -302,10 +297,9 @@ class Partition: @property def partition_type(self): - output = b"".join(sys_command(f"lsblk --json -o+PTTYPE {self.path}")) - output = json.loads(output.decode('UTF-8')) + lsblk = json.loads(SysCommand(f"lsblk --json -o+PTTYPE {self.path}").decode('UTF-8')) - for device in output['blockdevices']: + for device in lsblk['blockdevices']: return device['pttype'] @property @@ -315,8 +309,8 @@ class Partition: This is more reliable than relying on /dev/disk/by-partuuid as it doesn't seam to be able to detect md raid partitions. """ - lsblk = b''.join(SysCommand(f'lsblk -J -o+PARTUUID {self.path}')) - for partition in json.loads(lsblk.decode('UTF-8'))['blockdevices']: + lsblk = json.loads(SysCommand(f'lsblk -J -o+PARTUUID {self.path}').decode('UTF-8')) + for partition in lsblk['blockdevices']: return partition.get('partuuid', None) return None @@ -335,7 +329,7 @@ class Partition: @property def real_device(self): - for blockdevice in json.loads(b''.join(SysCommand('lsblk -J')).decode('UTF-8'))['blockdevices']: + for blockdevice in json.loads(SysCommand('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}') @@ -429,30 +423,29 @@ class Partition: log(f'Formatting {path} -> {filesystem}', level=logging.INFO) if filesystem == 'btrfs': - o = b''.join(SysCommand(f'/usr/bin/mkfs.btrfs -f {path}')) - if b'UUID' not in o: - raise DiskError(f'Could not format {path} with {filesystem} because: {o}') + if b'UUID' not in (mkfs := SysCommand(f'/usr/bin/mkfs.btrfs -f {path}')): + raise DiskError(f'Could not format {path} with {filesystem} because: {mkfs}') self.filesystem = 'btrfs' elif filesystem == 'vfat': - o = b''.join(SysCommand(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}') + mkfs = SysCommand(f'/usr/bin/mkfs.vfat -F32 {path}') + if (b'mkfs.fat' not in mkfs and b'mkfs.vfat' not in mkfs) or b'command not found' in mkfs: + raise DiskError(f"Could not format {path} with {filesystem} because: {mkfs}") self.filesystem = 'vfat' elif filesystem == 'ext4': if (handle := SysCommand(f'/usr/bin/mkfs.ext4 -F {path}')).exit_code != 0: - raise DiskError(f'Could not format {path} with {filesystem} because: {b"".join(handle)}') + raise DiskError(f"Could not format {path} with {filesystem} because: {handle.decode('UTF-8')}") self.filesystem = 'ext4' elif filesystem == 'xfs': if (handle := SysCommand(f'/usr/bin/mkfs.xfs -f {path}')).exit_code != 0: - raise DiskError(f'Could not format {path} with {filesystem} because: {b"".join(handle)}') + raise DiskError(f"Could not format {path} with {filesystem} because: {handle.decode('UTF-8')}") self.filesystem = 'xfs' elif filesystem == 'f2fs': if (handle := SysCommand(f'/usr/bin/mkfs.f2fs -f {path}')).exit_code != 0: - raise DiskError(f'Could not format {path} with {filesystem} because: {b"".join(handle)}') + raise DiskError(f"Could not format {path} with {filesystem} because: {handle.decode('UTF-8')}") self.filesystem = 'f2fs' elif filesystem == 'crypto_LUKS': @@ -491,9 +484,9 @@ class Partition: try: if options: - SysCommand(f'/usr/bin/mount -o {options} {self.path} {target}') + SysCommand(f"/usr/bin/mount -o {options} {self.path} {target}") else: - SysCommand(f'/usr/bin/mount {self.path} {target}') + SysCommand(f"/usr/bin/mount {self.path} {target}") except SysCallError as err: raise err @@ -502,7 +495,7 @@ class Partition: def unmount(self): try: - exit_code = SysCommand(f'/usr/bin/umount {self.path}').exit_code + SysCommand(f"/usr/bin/umount {self.path}") except SysCallError as err: exit_code = err.exit_code @@ -552,7 +545,7 @@ class Filesystem: else: raise DiskError('Problem setting the partition format to GPT:', f'/usr/bin/parted -s {self.blockdevice.device} mklabel gpt') elif self.mode == MBR: - if SysCommand(f'/usr/bin/parted -s {self.blockdevice.device} mklabel msdos').exit_code == 0: + if SysCommand(f"/usr/bin/parted -s {self.blockdevice.device} mklabel msdos").exit_code == 0: return self else: raise DiskError('Problem setting the partition format to MBR:', f'/usr/bin/parted -s {self.blockdevice.device} mklabel msdos') @@ -574,7 +567,7 @@ class Filesystem: # TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager if len(args) >= 2 and args[1]: raise args[1] - b''.join(SysCommand('sync')) + SysCommand('sync') return True def find_partition(self, mountpoint): @@ -583,8 +576,7 @@ class Filesystem: return partition def raw_parted(self, string: str): - x = SysCommand(f'/usr/bin/parted -s {string}') - return x + return SysCommand(f'/usr/bin/parted -s {string}') def parted(self, string: str): """ @@ -673,8 +665,9 @@ def device_state(name, *args, **kwargs): def all_disks(*args, **kwargs): kwargs.setdefault("partitions", False) drives = {} - # for drive in json.loads(sys_command(f'losetup --json', *args, **lkwargs, hide_from_log=True)).decode('UTF_8')['loopdevices']: - for drive in json.loads(b''.join(SysCommand('lsblk --json -l -n -o path,size,type,mountpoint,label,pkname,model')).decode('UTF_8'))['blockdevices']: + + lsblk = json.loads(SysCommand('lsblk --json -l -n -o path,size,type,mountpoint,label,pkname,model').decode('UTF_8')) + for drive in lsblk['blockdevices']: if not kwargs['partitions'] and drive['type'] == 'part': continue @@ -707,12 +700,10 @@ def harddrive(size=None, model=None, fuzzy=False): def get_mount_info(path) -> dict: try: - output = SysCommand(f'/usr/bin/findmnt --json {path}') + output = SysCommand(f'/usr/bin/findmnt --json {path}').decode('UTF-8') except SysCallError: return {} - output = output.decode('UTF-8') - if not output: return {} @@ -726,14 +717,12 @@ def get_mount_info(path) -> dict: def get_partitions_in_use(mountpoint) -> list: try: - output = SysCommand(f'/usr/bin/findmnt --json -R {mountpoint}') + output = SysCommand(f"/usr/bin/findmnt --json -R {mountpoint}").decode('UTF-8') except SysCallError: return [] mounts = [] - output = output.decode('UTF-8') - if not output: return [] @@ -749,16 +738,14 @@ def get_partitions_in_use(mountpoint) -> list: def get_filesystem_type(path): try: - handle = SysCommand(f"blkid -o value -s TYPE {path}") - return b''.join(handle).strip().decode('UTF-8') + return SysCommand(f"blkid -o value -s TYPE {path}").decode('UTF-8').strip() except SysCallError: return None def disk_layouts(): try: - handle = SysCommand("lsblk -f -o+TYPE,SIZE -J") - return json.loads(b''.join(handle).decode('UTF-8')) + return json.loads(SysCommand("lsblk -f -o+TYPE,SIZE -J").decode('UTF-8')) except SysCallError as err: log(f"Could not return disk layouts: {err}") return None diff --git a/examples/guided.py b/examples/guided.py index f48b5d0a..0bcc1fcc 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -75,15 +75,14 @@ def ask_user_questions(): if archinstall.arguments.get('harddrives', None): archinstall.arguments['harddrives'] = [archinstall.BlockDevice(BlockDev) for BlockDev in archinstall.arguments['harddrives']] else: - archinstall.arguments['harddrives'] = [ - archinstall.BlockDevice(BlockDev) for BlockDev in archinstall.generic_multi_select(archinstall.all_disks(), - text="Select one or more harddrives to use and configure (leave blank to skip this step): ", - allow_empty=True) - ] + archinstall.arguments['harddrives'] = archinstall.generic_multi_select(archinstall.all_disks(), + text="Select one or more harddrives to use and configure (leave blank to skip this step): ", + allow_empty=True) if archinstall.arguments.get('harddrives', None): archinstall.storage['disk_layouts'] = archinstall.select_disk_layout(archinstall.arguments['harddrives']) + exit(0) # Get disk encryption password (or skip if blank) if archinstall.arguments['harddrives'] and archinstall.arguments.get('!encryption-password', None) is None: -- cgit v1.2.3-70-g09d2 From 0552d040ac8fb4517d77f3fa86ec3039ab71fb4b Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 6 Jun 2021 17:13:42 +0200 Subject: Added a json.dumps() helper that wraps JSON cls. Also tweaked the logic for the size creation so that they don't overlap --- archinstall/lib/general.py | 3 ++- archinstall/lib/user_interaction.py | 6 +++--- examples/guided.py | 2 ++ 3 files changed, 7 insertions(+), 4 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py index f72311c9..6bb3b101 100644 --- a/archinstall/lib/general.py +++ b/archinstall/lib/general.py @@ -44,6 +44,8 @@ def locate_binary(name): raise RequirementError(f"Binary {name} does not exist.") +def json_dumps(*args, **kwargs): + return json.dumps(*args, **{**kwargs, 'cls': JSON}) class JsonEncoder: def _encode(obj): @@ -86,7 +88,6 @@ class JSON(json.JSONEncoder, json.JSONDecoder): def encode(self, obj): return super(JSON, self).encode(self._encode(obj)) - class SysCommandWorker: def __init__(self, cmd, callbacks=None, peak_output=False, environment_vars=None, logfile=None, working_directory='./'): if not callbacks: diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index e28b66b2..30fb7e51 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -633,8 +633,8 @@ def get_default_partition_layout(block_devices): "type" : "primary", "start" : "513MiB", "encrypted" : True, - "size" : f"{max(block_devices[0].size*0.2, 20)}GiB", - "mountpoint" : "", + "size" : f"{max(block_devices[0].size*0.2, 20)*1024}MiB", # Warning: Won't work on small where max size is 16GB for instance. + "mountpoint" : "/", "filesystem" : { "format" : "btrfs" } @@ -642,7 +642,7 @@ def get_default_partition_layout(block_devices): { # Home "type" : "primary", "encrypted" : True, - "start" : f"{max(block_devices[0].size*0.2, 20)}GiB", + "start" : f"{513 + (max(block_devices[0].size*0.2, 20)*1024)}MiB", "size" : "100%", "mountpoint" : "/home", "filesystem" : { diff --git a/examples/guided.py b/examples/guided.py index 0bcc1fcc..1dfe79fb 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -82,6 +82,8 @@ def ask_user_questions(): if archinstall.arguments.get('harddrives', None): archinstall.storage['disk_layouts'] = archinstall.select_disk_layout(archinstall.arguments['harddrives']) + print(archinstall.arguments['harddrives']) + print(archinstall.storage['disk_layouts']) exit(0) # Get disk encryption password (or skip if blank) -- cgit v1.2.3-70-g09d2 From 25e835ce3e42d2c04cca9b2379723af7984c6fee Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 6 Jun 2021 17:35:44 +0200 Subject: Removed the bulk of disk-operations from guided, and will move the logic into the Filesystem() class instead. --- archinstall/lib/user_interaction.py | 1 + examples/guided.py | 43 ++++--------------------------------- 2 files changed, 5 insertions(+), 39 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 30fb7e51..0ce2cafd 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -651,6 +651,7 @@ def get_default_partition_layout(block_devices): } ] } + # TODO: Implement sane generic layout for 2+ drives def wipe_and_create_partitions(block_device): if hasUEFI(): diff --git a/examples/guided.py b/examples/guided.py index 1dfe79fb..a2cd29fc 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -82,9 +82,6 @@ def ask_user_questions(): if archinstall.arguments.get('harddrives', None): archinstall.storage['disk_layouts'] = archinstall.select_disk_layout(archinstall.arguments['harddrives']) - print(archinstall.arguments['harddrives']) - print(archinstall.storage['disk_layouts']) - exit(0) # Get disk encryption password (or skip if blank) if archinstall.arguments['harddrives'] and archinstall.arguments.get('!encryption-password', None) is None: @@ -208,7 +205,7 @@ def perform_filesystem_operations(): We mention the drive one last time, and count from 5 to 0. """ - if archinstall.arguments.get('harddrive', None): + if archinstall.arguments.get('harddrives', None): print(f" ! Formatting {archinstall.arguments['harddrive']} in ", end='') archinstall.do_countdown() @@ -219,42 +216,10 @@ def perform_filesystem_operations(): mode = archinstall.GPT if has_uefi() is False: mode = archinstall.MBR - with archinstall.Filesystem(archinstall.arguments['harddrive'], mode) 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')) - - # Check if encryption is desired and mark the root partition as encrypted. - if 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(): - # Partition might be marked as encrypted due to the filesystem type crypt_LUKS - # But we might have omitted the encryption password question to skip encryption. - # In which case partition.encrypted will be true, but passwd will be false. - if partition.encrypted and (passwd := archinstall.arguments.get('!encryption-password', None)): - partition.encrypt(password=passwd) - else: - partition.format() - else: - archinstall.log(f"Did not format {partition} because .safe_to_format() returned False or .allow_formatting was False.", level=logging.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) - unlocked_device.mount(archinstall.storage.get('MOUNT_POINT', '/mnt')) - else: - fs.find_partition('/').mount(archinstall.storage.get('MOUNT_POINT', '/mnt')) - if has_uefi(): - fs.find_partition('/boot').mount(archinstall.storage.get('MOUNT_POINT', '/mnt') + '/boot') + for drive in archinstall.arguments['harddrives']: + with archinstall.Filesystem(drive, mode) as fs: + fs.load_layout(archinstall.arguments['harddrives'][drive]) perform_installation(archinstall.storage.get('MOUNT_POINT', '/mnt')) -- cgit v1.2.3-70-g09d2 From 5701ef953919230f1478294cabcc66ccdbe95e34 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 7 Jun 2021 12:40:24 +0200 Subject: Started working on the load_layout function --- archinstall/lib/disk.py | 6 ++++++ examples/guided.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 58e2eb59..0c46e779 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -570,6 +570,12 @@ class Filesystem: SysCommand('sync') return True + def load_layout(self, layout :dict): + for partition in layout: + print(partition) + + exit(0) + def find_partition(self, mountpoint): for partition in self.blockdevice: if partition.target_mountpoint == mountpoint or partition.mountpoint == mountpoint: diff --git a/examples/guided.py b/examples/guided.py index a2cd29fc..b4c12fd6 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -206,7 +206,7 @@ def perform_filesystem_operations(): """ if archinstall.arguments.get('harddrives', None): - print(f" ! Formatting {archinstall.arguments['harddrive']} in ", end='') + print(f" ! Formatting {archinstall.arguments['harddrives']} in ", end='') archinstall.do_countdown() """ -- cgit v1.2.3-70-g09d2 From e8d38ea1a75a33d820ac32c995a80c1bc833a44d Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Thu, 10 Jun 2021 13:39:50 +0200 Subject: Started working on partitioning logic from declarative layouts. --- archinstall/lib/disk.py | 9 ++++++++- archinstall/lib/user_interaction.py | 16 ++++++++++------ examples/guided.py | 5 ++--- 3 files changed, 20 insertions(+), 10 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 0c46e779..efcf1844 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -572,7 +572,14 @@ class Filesystem: def load_layout(self, layout :dict): for partition in layout: - print(partition) + # We don't want to re-add an existing partition (those containing a UUID already) + if 'UUID' not in partition: + self.add_partition(partition.get('type', 'primary'), + start=partition.get('start', '1MiB'), # TODO: Revisit sane block starts (4MB for memorycards for instance) + end=partition.get('size', '100%'), + partition_format=partition.get('filesystem', {}).get('format', 'btrfs')) + + exit(0) diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 0ce2cafd..50f3be9a 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -11,6 +11,7 @@ import termios import time import tty +from .disk import BlockDevice from .exceptions import * from .general import SysCommand from .hardware import AVAILABLE_GFX_DRIVERS, has_uefi @@ -19,7 +20,6 @@ from .networking import list_interfaces from .output import log from .profiles import Profile, list_profiles - # TODO: Some inconsistencies between the selection processes. # Some return the keys from the options, some the values? @@ -553,7 +553,7 @@ def generic_select(options, input_text="Select one of the above by index or abso def select_partition_layout(block_device): return { - "/dev/sda": { # Block Device level + BlockDevice("/dev/sda"): { # Block Device level "wipe": False, # Safety flags "partitions" : [ # Affected / New partitions { @@ -565,7 +565,7 @@ def select_partition_layout(block_device): } ] }, - "/dev/sdb" : { + BlockDevice("/dev/sdb") : { "wipe" : True, "partitions" : [ { @@ -653,7 +653,7 @@ def get_default_partition_layout(block_devices): } # TODO: Implement sane generic layout for 2+ drives -def wipe_and_create_partitions(block_device): +def wipe_and_create_partitions(block_device :BlockDevice) -> dict: if hasUEFI(): partition_type = 'gpt' else: @@ -747,7 +747,9 @@ def wipe_and_create_partitions(block_device): partitions_result = [*suggested_layout] elif task is None: - return partitions_result + return { + block_device : partitions_result + } else: for index, partition in enumerate(partitions_result): print(f"{index}: Start: {partition['start']}, End: {partition['size']} ({partition['filesystem']['format']}{', mounting at: '+partition['mountpoint'] if partition['mountpoint'] else ''})") @@ -777,7 +779,9 @@ def wipe_and_create_partitions(block_device): if (partition := generic_select(partitions_result, 'Select which partition to mark as bootable: ', options_output=False)): partitions_result[partitions_result.index(partition)]['boot'] = not partitions_result[partitions_result.index(partition)].get('boot', False) - return partitions_result + return { + block_device : partitions_result + } def select_individual_blockdevice_usage(block_devices :list): result = {} diff --git a/examples/guided.py b/examples/guided.py index b4c12fd6..57d4818b 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -73,7 +73,7 @@ def ask_user_questions(): # Ask which harddrives/block-devices we will install to # and convert them into archinstall.BlockDevice() objects. if archinstall.arguments.get('harddrives', None): - archinstall.arguments['harddrives'] = [archinstall.BlockDevice(BlockDev) for BlockDev in archinstall.arguments['harddrives']] + archinstall.arguments['harddrives'] = [archinstall.BlockDevice(BlockDev) for BlockDev in archinstall.arguments['harddrives'].split(',')] else: archinstall.arguments['harddrives'] = archinstall.generic_multi_select(archinstall.all_disks(), text="Select one or more harddrives to use and configure (leave blank to skip this step): ", @@ -82,7 +82,6 @@ def ask_user_questions(): if archinstall.arguments.get('harddrives', None): archinstall.storage['disk_layouts'] = archinstall.select_disk_layout(archinstall.arguments['harddrives']) - # Get disk encryption password (or skip if blank) if archinstall.arguments['harddrives'] and archinstall.arguments.get('!encryption-password', None) is None: if (passwd := archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ')): @@ -219,7 +218,7 @@ def perform_filesystem_operations(): for drive in archinstall.arguments['harddrives']: with archinstall.Filesystem(drive, mode) as fs: - fs.load_layout(archinstall.arguments['harddrives'][drive]) + fs.load_layout(archinstall.storage['disk_layouts'][drive]) perform_installation(archinstall.storage.get('MOUNT_POINT', '/mnt')) -- cgit v1.2.3-70-g09d2 From 4691bad46b0cc7c4a04ce401ff2c7de93128d717 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Thu, 10 Jun 2021 19:29:10 +0200 Subject: Added wipe support to layout definitions. Also changed default start positions of partitions to 1MiB in. --- archinstall/lib/disk.py | 48 ++++++++------------- archinstall/lib/general.py | 9 ++++ archinstall/lib/user_interaction.py | 83 +++++++++++++++++++++---------------- 3 files changed, 73 insertions(+), 67 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index e0d9b423..211bbcb5 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -571,15 +571,23 @@ class Filesystem: return True def load_layout(self, layout :dict): - for partition in layout: + # If the layout tells us to wipe the drive, we do so + if layout.get('wipe', False): + if self.mode == GPT: + if not self.parted_mklabel(self.blockdevice.device, "gpt"): + raise KeyError(f"Could not create a GPT label on {self}") + elif self.mode == MBR: + if not self.parted_mklabel(self.blockdevice.device, "msdos"): + raise KeyError(f"Could not create a MSDOS label on {self}") + + # We then iterate the partitions in order + for partition in layout.get('partitions', []): # We don't want to re-add an existing partition (those containing a UUID already) if 'UUID' not in partition: self.add_partition(partition.get('type', 'primary'), start=partition.get('start', '1MiB'), # TODO: Revisit sane block starts (4MB for memorycards for instance) end=partition.get('size', '100%'), partition_format=partition.get('filesystem', {}).get('format', 'btrfs')) - - exit(0) @@ -589,7 +597,9 @@ class Filesystem: return partition def raw_parted(self, string: str): - return SysCommand(f'/usr/bin/parted -s {string}') + if (cmd_handle := SysCommand(f'/usr/bin/parted -s {string}')).exit_code != 0: + log(f"Could not generate partition: {cmd_handle}", level=logging.ERROR, fg="red") + return cmd_handle def parted(self, string: str): """ @@ -601,35 +611,11 @@ class Filesystem: return self.raw_parted(string).exit_code def use_entire_disk(self, root_filesystem_type='ext4'): - log(f"Using and formatting the entire {self.blockdevice}.", level=logging.DEBUG) - if has_uefi(): - self.add_partition('primary', start='1MiB', end='513MiB', partition_format='fat32') - self.set_name(0, 'EFI') - self.set(0, 'boot on') - # 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 - log(f"Set the root partition {self.blockdevice.partition[1]} to use filesystem {root_filesystem_type}.", level=logging.DEBUG) - - self.blockdevice.partition[0].target_mountpoint = '/boot' - self.blockdevice.partition[1].target_mountpoint = '/' - - self.blockdevice.partition[0].allow_formatting = True - self.blockdevice.partition[1].allow_formatting = True - else: - # we don't need a seprate boot partition it would be a waste of space - self.add_partition('primary', start='1MB', end='100%') - self.blockdevice.partition[0].filesystem = root_filesystem_type - log(f"Set the root partition {self.blockdevice.partition[0]} to use filesystem {root_filesystem_type}.", level=logging.DEBUG) - self.blockdevice.partition[0].target_mountpoint = '/' - self.blockdevice.partition[0].allow_formatting = True + # TODO: Implement this with declarative profiles instead. + raise ValueError("Installation().use_entire_disk() has to be re-worked.") def add_partition(self, partition_type, start, end, partition_format=None): - log(f'Adding partition to {self.blockdevice}', level=logging.INFO) + log(f'Adding partition to {self.blockdevice}, {start}->{end}', level=logging.INFO) previous_partitions = self.blockdevice.partitions if self.mode == MBR: diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py index 4d3257ba..f0be7972 100644 --- a/archinstall/lib/general.py +++ b/archinstall/lib/general.py @@ -322,6 +322,15 @@ class SysCommand: for line in self.session: yield line + def __getitem__(self, key): + if type(key) is slice: + start = key.start if key.start else 0 + end = key.stop if key.stop else len(self.session._trace_log) + + return self.session._trace_log[start:end] + else: + raise ValueError("SysCommand() doesn't have key & value pairs, only slices, SysCommand('ls')[:10] as an example.") + def __repr__(self, *args, **kwargs): return self.session._trace_log.decode('UTF-8') diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 4f99820d..2c333d3a 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -191,8 +191,6 @@ def generic_multi_select(options, text="Select one or more of the options above return selected_options def select_encrypted_partitions(blockdevices :dict) -> dict: - print(blockdevices[0]) - if len(blockdevices) == 1: if len(blockdevices[0]['partitions']) == 2: root = find_partition_by_mountpoint(blockdevices[0]['partitions'], '/') @@ -617,40 +615,53 @@ def partition_overlap(partitions :list, start :str, end :str) -> bool: def get_default_partition_layout(block_devices): if len(block_devices) == 1: - return { - block_devices[0] : [ - { # Boot - "type" : "primary", - "start" : "0MiB", - "size" : "513MiB", - "boot" : True, - "mountpoint" : "/boot", - "filesystem" : { - "format" : "fat32" - } - }, - { # Root - "type" : "primary", - "start" : "513MiB", - "encrypted" : True, - "size" : f"{max(block_devices[0].size*0.2, 20)*1024}MiB", # Warning: Won't work on small where max size is 16GB for instance. - "mountpoint" : "/", - "filesystem" : { - "format" : "btrfs" - } - }, - { # Home - "type" : "primary", - "encrypted" : True, - "start" : f"{513 + (max(block_devices[0].size*0.2, 20)*1024)}MiB", - "size" : "100%", - "mountpoint" : "/home", - "filesystem" : { - "format" : "btrfs" - } - } - ] + MIN_SIZE_TO_ALLOW_HOME_PART = 40 # Gb + + layout = { + block_devices[0] : { + "wipe" : True, + "partitions" : [] + } } + + layout[block_devices[0]]['partitions'].append({ + # Boot + "type" : "primary", + "start" : "1MiB", + "size" : "513MiB", + "boot" : True, + "mountpoint" : "/boot", + "filesystem" : { + "format" : "fat32" + } + }) + layout[block_devices[0]]['partitions'].append({ + # Root + "type" : "primary", + "start" : "513MiB", + "encrypted" : True, + "size" : "100%" if block_devices[0].size < MIN_SIZE_TO_ALLOW_HOME_PART else f"{min(block_devices[0].size, 20)*1024}MiB", + "mountpoint" : "/", + "filesystem" : { + "format" : "btrfs" + } + }) + + if block_devices[0].size > MIN_SIZE_TO_ALLOW_HOME_PART: + layout[block_devices[0]]['partitions'].append({ + # Home + "type" : "primary", + "encrypted" : True, + "start" : f"{min(block_devices[0].size*0.2, 20)*1024}MiB", + "size" : "100%", + "mountpoint" : "/home", + "filesystem" : { + "format" : "btrfs" + } + }) + + return layout + # TODO: Implement sane generic layout for 2+ drives def wipe_and_create_partitions(block_device :BlockDevice) -> dict: @@ -663,7 +674,7 @@ def wipe_and_create_partitions(block_device :BlockDevice) -> dict: suggested_layout = [ { # Boot "type" : "primary", - "start" : "0MiB", + "start" : "1MiB", "size" : "513MiB", "boot" : True, "mountpoint" : "/boot", -- cgit v1.2.3-70-g09d2 From 4e9b1c163574b44517c6e09de4c91c4ef5995969 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Thu, 10 Jun 2021 20:38:35 +0200 Subject: Renamed vfat -> fat32 for the purpose of consistency. Most actions are referring to fat32, it's only mkfs that has the notion vfat and then -F32 for format 32. And I think vfat confuses more people than it does good, so sticking with fat32 which works better with parted as well. Also added the partitioning logic, started on the mounting logic --- archinstall/lib/disk.py | 79 +++++++++++++++++++++++++++---------- archinstall/lib/user_interaction.py | 19 ++------- examples/guided.py | 1 + 3 files changed, 63 insertions(+), 36 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 211bbcb5..4fb3d1e0 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -13,10 +13,19 @@ GPT = 0b00000001 MBR = 0b00000010 -# import ctypes -# import ctypes.util -# libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True) -# libc.mount.argtypes = (ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_ulong, ctypes.c_char_p) +def valid_fs_type(fstype :str) -> bool: + # https://www.gnu.org/software/parted/manual/html_node/mkpart.html + + return fstype in [ + "ext2", + "fat16", "fat32", + "hfs", "hfs+", "hfsx", + "linux-swap", + "NTFS", + "reiserfs", + "ufs", + "btrfs", + ] class BlockDevice: @@ -177,6 +186,11 @@ class BlockDevice: def flush_cache(self): self.part_cache = {} + def get_partition(self, uuid): + for partition in self: + if partition.uuid == uuid: + return partition + class Partition: def __init__(self, path: str, block_device: BlockDevice, part_id=None, size=-1, filesystem=None, mountpoint=None, encrypted=False, autodetect_filesystem=True): @@ -425,34 +439,34 @@ class Partition: if filesystem == 'btrfs': if b'UUID' not in (mkfs := SysCommand(f'/usr/bin/mkfs.btrfs -f {path}')): raise DiskError(f'Could not format {path} with {filesystem} because: {mkfs}') - self.filesystem = 'btrfs' + self.filesystem = filesystem - elif filesystem == 'vfat': + elif filesystem == 'fat32': mkfs = SysCommand(f'/usr/bin/mkfs.vfat -F32 {path}') if (b'mkfs.fat' not in mkfs and b'mkfs.vfat' not in mkfs) or b'command not found' in mkfs: raise DiskError(f"Could not format {path} with {filesystem} because: {mkfs}") - self.filesystem = 'vfat' + self.filesystem = filesystem elif filesystem == 'ext4': if (handle := SysCommand(f'/usr/bin/mkfs.ext4 -F {path}')).exit_code != 0: raise DiskError(f"Could not format {path} with {filesystem} because: {handle.decode('UTF-8')}") - self.filesystem = 'ext4' + self.filesystem = filesystem elif filesystem == 'xfs': if (handle := SysCommand(f'/usr/bin/mkfs.xfs -f {path}')).exit_code != 0: raise DiskError(f"Could not format {path} with {filesystem} because: {handle.decode('UTF-8')}") - self.filesystem = 'xfs' + self.filesystem = filesystem elif filesystem == 'f2fs': if (handle := SysCommand(f'/usr/bin/mkfs.f2fs -f {path}')).exit_code != 0: raise DiskError(f"Could not format {path} with {filesystem} because: {handle.decode('UTF-8')}") - self.filesystem = 'f2fs' + self.filesystem = filesystem elif filesystem == 'crypto_LUKS': # from .luks import luks2 # encrypted_partition = luks2(self, None, None) # encrypted_partition.format(path) - self.filesystem = 'crypto_LUKS' + self.filesystem = filesystem else: raise UnknownFilesystemFormat(f"Fileformat '{filesystem}' is not yet implemented.") @@ -583,12 +597,32 @@ class Filesystem: # We then iterate the partitions in order for partition in layout.get('partitions', []): # We don't want to re-add an existing partition (those containing a UUID already) - if 'UUID' not in partition: - self.add_partition(partition.get('type', 'primary'), - start=partition.get('start', '1MiB'), # TODO: Revisit sane block starts (4MB for memorycards for instance) - end=partition.get('size', '100%'), - partition_format=partition.get('filesystem', {}).get('format', 'btrfs')) + if partition.get('format', False) and not partition.get('uuid', None): + partition['device_instance'] = self.add_partition(partition.get('type', 'primary'), + start=partition.get('start', '1MiB'), # TODO: Revisit sane block starts (4MB for memorycards for instance) + end=partition.get('size', '100%'), + partition_format=partition.get('filesystem', {}).get('format', 'btrfs')) + + elif partition_uuid := partition.get('uuid'): + if partition_instance := self.blockdevice.get_partition(uuid=partition_uuid): + partition['device_instance'] = partition_instance + else: + raise ValueError("BlockDevice().load_layout() doesn't know how to continue without either a UUID or creation of partition.") + + if partition.get('filesystem', {}).get('format', None): + if partition.get('encrypt', False): + assert partition.get('password') + partition['device_instance'].encrypt(password=partition['password']) + with archinstall.luks2(partition['device_instance'], 'luksloop', partition['password']) as unlocked_device: + unlocked_device.format(partition['filesystem']['format'], allow_formatting=partition.get('format', False)) + else: + partition['device_instance'].format(partition['filesystem']['format'], allow_formatting=partition.get('format', False)) + + def mount_ordered_layout(self, layout :dict): + mountpoints = {} + for partition in layout['partitions']: + print(partition) exit(0) def find_partition(self, mountpoint): @@ -610,17 +644,19 @@ class Filesystem: """ return self.raw_parted(string).exit_code - def use_entire_disk(self, root_filesystem_type='ext4'): + def use_entire_disk(self, root_filesystem_type='ext4') -> Partition: # TODO: Implement this with declarative profiles instead. raise ValueError("Installation().use_entire_disk() has to be re-worked.") def add_partition(self, partition_type, start, end, partition_format=None): log(f'Adding partition to {self.blockdevice}, {start}->{end}', level=logging.INFO) - previous_partitions = self.blockdevice.partitions + previous_partition_uuids = {partition.uuid for partition in self.blockdevice.partitions.values()} + if self.mode == MBR: if len(self.blockdevice.partitions) > 3: DiskError("Too many partitions on disk, MBR disks can only have 3 parimary partitions") + if partition_format: partitioning = self.parted(f'{self.blockdevice.device} mkpart {partition_type} {partition_format} {start} {end}') == 0 else: @@ -628,12 +664,13 @@ class Filesystem: if partitioning: start_wait = time.time() - while previous_partitions == self.blockdevice.partitions: - time.sleep(0.025) # Let the new partition come up in the kernel + while previous_partition_uuids == {partition.uuid for partition in self.blockdevice.partitions.values()}: if time.time() - start_wait > 10: raise DiskError(f"New partition never showed up after adding new partition on {self} (timeout 10 seconds).") + time.sleep(0.025) - return True + time.sleep(0.5) # Let the kernel catch up with quick block devices (nvme for instance) + return self.blockdevice.get_partition(uuid=(previous_partition_uuids ^ {partition.uuid for partition in self.blockdevice.partitions.values()}).pop()) def set_name(self, partition: int, name: str): return self.parted(f'{self.blockdevice.device} name {partition + 1} "{name}"') == 0 diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 2c333d3a..6860f00b 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -9,7 +9,7 @@ import signal import sys import time -from .disk import BlockDevice +from .disk import BlockDevice, valid_fs_type from .exceptions import * from .general import SysCommand from .hardware import AVAILABLE_GFX_DRIVERS, has_uefi @@ -580,20 +580,6 @@ def select_partition_layout(block_device): } } -def valid_fs_type(fstype :str) -> bool: - # https://www.gnu.org/software/parted/manual/html_node/mkpart.html - - return fstype in [ - "ext2", - "fat16", "fat32", - "hfs", "hfs+", "hfsx", - "linux-swap", - "NTFS", - "reiserfs", - "ufs", - "btrfs", - ] - def valid_parted_position(pos :str): if not len(pos): return False @@ -630,6 +616,7 @@ def get_default_partition_layout(block_devices): "start" : "1MiB", "size" : "513MiB", "boot" : True, + "format" : True, "mountpoint" : "/boot", "filesystem" : { "format" : "fat32" @@ -640,6 +627,7 @@ def get_default_partition_layout(block_devices): "type" : "primary", "start" : "513MiB", "encrypted" : True, + "format" : True, "size" : "100%" if block_devices[0].size < MIN_SIZE_TO_ALLOW_HOME_PART else f"{min(block_devices[0].size, 20)*1024}MiB", "mountpoint" : "/", "filesystem" : { @@ -652,6 +640,7 @@ def get_default_partition_layout(block_devices): # Home "type" : "primary", "encrypted" : True, + "format" : True, "start" : f"{min(block_devices[0].size*0.2, 20)*1024}MiB", "size" : "100%", "mountpoint" : "/home", diff --git a/examples/guided.py b/examples/guided.py index f477a738..2e2d0d98 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -223,6 +223,7 @@ def perform_filesystem_operations(): for drive in archinstall.arguments['harddrives']: with archinstall.Filesystem(drive, mode) as fs: fs.load_layout(archinstall.storage['disk_layouts'][drive]) + fs.mount_ordered_layout(archinstall.storage['disk_layouts'][drive]) perform_installation(archinstall.storage.get('MOUNT_POINT', '/mnt')) -- cgit v1.2.3-70-g09d2 From 0a8c061ab405e244a187b4615654ecca2e538156 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Thu, 10 Jun 2021 21:00:33 +0200 Subject: Fixed format detection on commands, should be using exit codes instead? --- archinstall/lib/disk.py | 12 +++--------- archinstall/lib/installer.py | 8 ++++++++ examples/guided.py | 8 +++++++- 3 files changed, 18 insertions(+), 10 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 4fb3d1e0..8e9d0d3f 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -437,13 +437,13 @@ class Partition: log(f'Formatting {path} -> {filesystem}', level=logging.INFO) if filesystem == 'btrfs': - if b'UUID' not in (mkfs := SysCommand(f'/usr/bin/mkfs.btrfs -f {path}')): + if 'UUID:' not in (mkfs := SysCommand(f'/usr/bin/mkfs.btrfs -f {path}').decode('UTF-8')): raise DiskError(f'Could not format {path} with {filesystem} because: {mkfs}') self.filesystem = filesystem elif filesystem == 'fat32': - mkfs = SysCommand(f'/usr/bin/mkfs.vfat -F32 {path}') - if (b'mkfs.fat' not in mkfs and b'mkfs.vfat' not in mkfs) or b'command not found' in mkfs: + mkfs = SysCommand(f'/usr/bin/mkfs.vfat -F32 {path}').decode('UTF-8') + if ('mkfs.fat' not in mkfs and 'mkfs.vfat' not in mkfs) or 'command not found' in mkfs: raise DiskError(f"Could not format {path} with {filesystem} because: {mkfs}") self.filesystem = filesystem @@ -619,12 +619,6 @@ class Filesystem: else: partition['device_instance'].format(partition['filesystem']['format'], allow_formatting=partition.get('format', False)) - def mount_ordered_layout(self, layout :dict): - mountpoints = {} - for partition in layout['partitions']: - print(partition) - exit(0) - def find_partition(self, mountpoint): for partition in self.blockdevice: if partition.target_mountpoint == mountpoint or partition.mountpoint == mountpoint: diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index da6f6a9b..b62b9595 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -122,6 +122,14 @@ class Installer: return True + def mount_ordered_layout(self, layout :dict): + mountpoints = {} + for partition in layout['partitions']: + mountpoints[partition['mountpoint']] = partition['device_instance'] + + for mountpoint in sorted(mountpoints.keys()): + mountpoints[mountpoint].mount(f"{self.target}{mountpoint}") + def mount(self, partition, mountpoint, create_mountpoint=True): if create_mountpoint and not os.path.isdir(f'{self.target}{mountpoint}'): os.makedirs(f'{self.target}{mountpoint}') diff --git a/examples/guided.py b/examples/guided.py index 2e2d0d98..527fc67c 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -223,7 +223,6 @@ def perform_filesystem_operations(): for drive in archinstall.arguments['harddrives']: with archinstall.Filesystem(drive, mode) as fs: fs.load_layout(archinstall.storage['disk_layouts'][drive]) - fs.mount_ordered_layout(archinstall.storage['disk_layouts'][drive]) perform_installation(archinstall.storage.get('MOUNT_POINT', '/mnt')) @@ -234,7 +233,14 @@ def perform_installation(mountpoint): Only requirement is that the block devices are formatted and setup prior to entering this function. """ + + with archinstall.Installer(mountpoint, kernels=archinstall.arguments.get('kernels', 'linux')) as installation: + # Mount all the drives to the desired mountpoint + # This *can* be done outside of the installation, but the installer can deal with it. + for drive in archinstall.arguments['harddrives']: + installation.mount_ordered_layout(archinstall.storage['disk_layouts'][drive]) + # 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 -- cgit v1.2.3-70-g09d2 From 24476ac1f696c882fb2f741cb8c5fa858f786f46 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Fri, 11 Jun 2021 17:22:20 +0200 Subject: Made it so that the .partitions property of Install() fetches from live data, rather than storing and caching partitions on initation. Since it now supports mounting a partition layout given by external usage. --- archinstall/lib/disk.py | 11 +++++++---- archinstall/lib/installer.py | 5 ++++- examples/guided.py | 6 ++---- 3 files changed, 13 insertions(+), 9 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 8e9d0d3f..7a7fce5e 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -636,7 +636,7 @@ class Filesystem: :param string: A raw string passed to /usr/bin/parted -s :type string: str """ - return self.raw_parted(string).exit_code + return self.raw_parted(string).exit_code == 0 def use_entire_disk(self, root_filesystem_type='ext4') -> Partition: # TODO: Implement this with declarative profiles instead. @@ -652,20 +652,22 @@ class Filesystem: DiskError("Too many partitions on disk, MBR disks can only have 3 parimary partitions") if partition_format: - partitioning = self.parted(f'{self.blockdevice.device} mkpart {partition_type} {partition_format} {start} {end}') == 0 + parted_string = f'{self.blockdevice.device} mkpart {partition_type} {partition_format} {start} {end}' else: - partitioning = self.parted(f'{self.blockdevice.device} mkpart {partition_type} {start} {end}') == 0 + parted_string = f'{self.blockdevice.device} mkpart {partition_type} {start} {end}' - if partitioning: + if self.parted(parted_string): start_wait = time.time() while previous_partition_uuids == {partition.uuid for partition in self.blockdevice.partitions.values()}: if time.time() - start_wait > 10: raise DiskError(f"New partition never showed up after adding new partition on {self} (timeout 10 seconds).") time.sleep(0.025) + time.sleep(0.5) # Let the kernel catch up with quick block devices (nvme for instance) return self.blockdevice.get_partition(uuid=(previous_partition_uuids ^ {partition.uuid for partition in self.blockdevice.partitions.values()}).pop()) + def set_name(self, partition: int, name: str): return self.parted(f'{self.blockdevice.device} name {partition + 1} "{name}"') == 0 @@ -673,6 +675,7 @@ class Filesystem: return self.parted(f'{self.blockdevice.device} set {partition + 1} {string}') == 0 def parted_mklabel(self, device: str, disk_label: str): + log(f"Creating a new partition labling on {device}", level=logging.INFO, fg="yellow") # Try to unmount devices before attempting to run mklabel try: SysCommand(f'bash -c "umount {device}?"') diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index b62b9595..91c55b33 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -57,7 +57,6 @@ class Installer: self.post_base_install = [] storage['session'] = self - self.partitions = get_partitions_in_use(self.target) self.MODULES = [] self.BINARIES = [] @@ -108,6 +107,10 @@ class Installer: self.sync_log_to_install_medium() return False + @property + def partitions(self): + return get_partitions_in_use(self.target) + def sync_log_to_install_medium(self): # Copy over the install log (if there is one) to the install medium if # at least the base has been strapped in, otherwise we won't have a filesystem/structure to copy to. diff --git a/examples/guided.py b/examples/guided.py index 527fc67c..5a9f2b49 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -224,8 +224,6 @@ def perform_filesystem_operations(): with archinstall.Filesystem(drive, mode) as fs: fs.load_layout(archinstall.storage['disk_layouts'][drive]) - perform_installation(archinstall.storage.get('MOUNT_POINT', '/mnt')) - def perform_installation(mountpoint): """ @@ -308,7 +306,7 @@ def perform_installation(mountpoint): # This step must be after profile installs to allow profiles to install language pre-requisits. # After which, this step will set the language both for console and x11 if x11 was installed for instance. - installation.set_keyboard_language(archinstall.arguments['keyboard-language']) + installation.set_keyboard_language(archinstall.arguments['keyboard-layout']) if archinstall.arguments['profile'] and archinstall.arguments['profile'].has_post_install(): with archinstall.arguments['profile'].load_instructions(namespace=f"{archinstall.arguments['profile'].namespace}.py") as imported: @@ -375,4 +373,4 @@ else: archinstall.storage['gfx_driver_packages'] = AVAILABLE_GFX_DRIVERS.get(archinstall.arguments.get('gfx_driver', None), None) perform_filesystem_operations() -perform_installation(archinstall.arguments.get('target-mountpoint', None)) +perform_installation(archinstall.storage.get('MOUNT_POINT', '/mnt')) \ No newline at end of file -- cgit v1.2.3-70-g09d2 From ba60d82bb91fcbcb1ae168709812bf0b71c35523 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Fri, 11 Jun 2021 21:33:37 +0200 Subject: Added BlockDevice.spinning and BlockDevice.bus_type --- archinstall/lib/disk.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 7a7fce5e..153ee428 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -174,6 +174,20 @@ class BlockDevice: return float(device['size'][:-1]) + @property + def bus_type(self): + output = json.loads(SysCommand(f"lsblk --json -o+ROTA,TRAN {self.path}").decode('UTF-8')) + + for device in output['blockdevices']: + return device['tran'] + + @property + def spinning(self): + output = json.loads(SysCommand(f"lsblk --json -o+ROTA,TRAN {self.path}").decode('UTF-8')) + + for device in output['blockdevices']: + return device['rota'] is True + def has_partitions(self): return len(self.partitions) -- cgit v1.2.3-70-g09d2 From 858171986c2b1111c52df4b7398c366dd15921f0 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 13 Jun 2021 10:34:08 +0200 Subject: Working suggested single disk layout, preparing for multiple selections. --- archinstall/lib/disk.py | 55 ++++++++++++++++++++++++ archinstall/lib/user_interaction.py | 86 ++----------------------------------- 2 files changed, 59 insertions(+), 82 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 7a7fce5e..abe34af1 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -27,6 +27,60 @@ def valid_fs_type(fstype :str) -> bool: "btrfs", ] +def suggest_single_disk_layout(blockdevice): + MIN_SIZE_TO_ALLOW_HOME_PART = 40 # Gb + + layout = { + blockdevice : { + "wipe" : True, + "partitions" : [] + } + } + + layout[blockdevice]['partitions'].append({ + # Boot + "type" : "primary", + "start" : "1MiB", + "size" : "513MiB", + "boot" : True, + "format" : True, + "mountpoint" : "/boot", + "filesystem" : { + "format" : "fat32" + } + }) + layout[blockdevice]['partitions'].append({ + # Root + "type" : "primary", + "start" : "513MiB", + "encrypted" : True, + "format" : True, + "size" : "100%" if blockdevice.size < MIN_SIZE_TO_ALLOW_HOME_PART else f"{min(blockdevice.size, 20)*1024}MiB", + "mountpoint" : "/", + "filesystem" : { + "format" : "btrfs" + } + }) + + if blockdevice.size > MIN_SIZE_TO_ALLOW_HOME_PART: + layout[blockdevice]['partitions'].append({ + # Home + "type" : "primary", + "encrypted" : True, + "format" : True, + "start" : f"{min(blockdevice.size*0.2, 20)*1024}MiB", + "size" : "100%", + "mountpoint" : "/home", + "filesystem" : { + "format" : "btrfs" + } + }) + + return layout + +def suggest_multi_disk_layout(blockdevices): + pass + class BlockDevice: def __init__(self, path, info=None): @@ -323,6 +377,7 @@ class Partition: This is more reliable than relying on /dev/disk/by-partuuid as it doesn't seam to be able to detect md raid partitions. """ + lsblk = json.loads(SysCommand(f'lsblk -J -o+PARTUUID {self.path}').decode('UTF-8')) for partition in lsblk['blockdevices']: return partition.get('partuuid', None) diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 6860f00b..228fa568 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -601,55 +601,9 @@ def partition_overlap(partitions :list, start :str, end :str) -> bool: def get_default_partition_layout(block_devices): if len(block_devices) == 1: - MIN_SIZE_TO_ALLOW_HOME_PART = 40 # Gb - - layout = { - block_devices[0] : { - "wipe" : True, - "partitions" : [] - } - } - - layout[block_devices[0]]['partitions'].append({ - # Boot - "type" : "primary", - "start" : "1MiB", - "size" : "513MiB", - "boot" : True, - "format" : True, - "mountpoint" : "/boot", - "filesystem" : { - "format" : "fat32" - } - }) - layout[block_devices[0]]['partitions'].append({ - # Root - "type" : "primary", - "start" : "513MiB", - "encrypted" : True, - "format" : True, - "size" : "100%" if block_devices[0].size < MIN_SIZE_TO_ALLOW_HOME_PART else f"{min(block_devices[0].size, 20)*1024}MiB", - "mountpoint" : "/", - "filesystem" : { - "format" : "btrfs" - } - }) - - if block_devices[0].size > MIN_SIZE_TO_ALLOW_HOME_PART: - layout[block_devices[0]]['partitions'].append({ - # Home - "type" : "primary", - "encrypted" : True, - "format" : True, - "start" : f"{min(block_devices[0].size*0.2, 20)*1024}MiB", - "size" : "100%", - "mountpoint" : "/home", - "filesystem" : { - "format" : "btrfs" - } - }) - - return layout + return suggest_single_disk_layout(blockdevices[0]) + else: + return suggest_multi_disk_layout(blockdevices) # TODO: Implement sane generic layout for 2+ drives @@ -660,38 +614,6 @@ def wipe_and_create_partitions(block_device :BlockDevice) -> dict: partition_type = 'msdos' partitions_result = [] # Test code: [part.__dump__() for part in block_device.partitions.values()] - suggested_layout = [ - { # Boot - "type" : "primary", - "start" : "1MiB", - "size" : "513MiB", - "boot" : True, - "mountpoint" : "/boot", - "filesystem" : { - "format" : "fat32" - } - }, - { # Root - "type" : "primary", - "start" : "513MiB", - "encrypted" : True, - "size" : f"{max(block_device.size*0.2, 20)}GiB", - "mountpoint" : "", - "filesystem" : { - "format" : "btrfs" - } - }, - { # Home - "type" : "primary", - "encrypted" : True, - "start" : f"{max(block_device.size*0.2, 20)}GiB", - "size" : "100%", - "mountpoint" : "/home", - "filesystem" : { - "format" : "btrfs" - } - } - ] # TODO: Squeeze in BTRFS subvolumes here while True: @@ -745,7 +667,7 @@ def wipe_and_create_partitions(block_device :BlockDevice) -> dict: if input(f"{block_device} contains queued partitions, this will remove those, are you sure? y/N: ").strip().lower() in ('', 'n'): continue - partitions_result = [*suggested_layout] + partitions_result = suggest_single_disk_layout(block_device)[block_device] elif task is None: return { block_device : partitions_result -- cgit v1.2.3-70-g09d2 From 9b6d7021a89116f09ad5324f19d7d08b9ec2856b Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 13 Jun 2021 10:37:30 +0200 Subject: This fixes https://github.com/archlinux/archinstall/pull/426#discussion_r650372664 --- archinstall/lib/installer.py | 7 ++++--- examples/guided.py | 3 +-- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 91c55b33..b1e38dc6 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -125,10 +125,11 @@ class Installer: return True - def mount_ordered_layout(self, layout :dict): + def mount_ordered_layout(self, layouts :dict): mountpoints = {} - for partition in layout['partitions']: - mountpoints[partition['mountpoint']] = partition['device_instance'] + for blockdevice in layouts: + for partition in layouts[blockdevice]['partitions']: + mountpoints[partition['mountpoint']] = partition['device_instance'] for mountpoint in sorted(mountpoints.keys()): mountpoints[mountpoint].mount(f"{self.target}{mountpoint}") diff --git a/examples/guided.py b/examples/guided.py index 5a9f2b49..14cbe0a1 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -236,8 +236,7 @@ def perform_installation(mountpoint): with archinstall.Installer(mountpoint, kernels=archinstall.arguments.get('kernels', 'linux')) as installation: # Mount all the drives to the desired mountpoint # This *can* be done outside of the installation, but the installer can deal with it. - for drive in archinstall.arguments['harddrives']: - installation.mount_ordered_layout(archinstall.storage['disk_layouts'][drive]) + installation.mount_ordered_layout(archinstall.storage['disk_layouts']) # if len(mirrors): # Certain services might be running that affects the system during installation. -- cgit v1.2.3-70-g09d2 From af790faf7af3412f13bb77b7c5ea2c85f72a1a47 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 13 Jun 2021 11:25:33 +0200 Subject: Added multi-disk suggested layout. It's sorted on performance, and the first relevant disk with the closest size to a desired size will be used for root, and the same (exluding the one already used) will be used for /home --- archinstall/lib/disk.py | 111 ++++++++++++++++++++++++++++++++---- archinstall/lib/user_interaction.py | 6 +- 2 files changed, 104 insertions(+), 13 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 1a592690..97b982d8 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -27,17 +27,50 @@ def valid_fs_type(fstype :str) -> bool: "btrfs", ] -def suggest_single_disk_layout(blockdevice): + +def sort_block_devices_based_on_performance(block_devices): + result = {device: 0 for device in block_devices} + + for device, weight in result.items(): + if device.spinning: + weight -= 10 + else: + weight += 5 + + if device.bus_type == 'nvme': + weight += 20 + elif device.bus_type == 'sata': + weight += 10 + + result[device] = weight + + return result + +def select_disk_larger_than_or_close_to(devices, gigabytes, filter_out=None): + if not filter_out: + filter_out = [] + + copy_devices = [*devices] + for filter_device in filter_out: + if filter_device in copy_devices: + copy_devices.pop(copy_devices.index(filter_device)) + + if not len(copy_devices): + return None + + return min(copy_devices, key=(lambda device : abs(device.size - 40))) + +def suggest_single_disk_layout(block_device): MIN_SIZE_TO_ALLOW_HOME_PART = 40 # Gb layout = { - blockdevice : { + block_device : { "wipe" : True, "partitions" : [] } } - layout[blockdevice]['partitions'].append({ + layout[block_device]['partitions'].append({ # Boot "type" : "primary", "start" : "1MiB", @@ -49,26 +82,26 @@ def suggest_single_disk_layout(blockdevice): "format" : "fat32" } }) - layout[blockdevice]['partitions'].append({ + layout[block_device]['partitions'].append({ # Root "type" : "primary", "start" : "513MiB", "encrypted" : True, "format" : True, - "size" : "100%" if blockdevice.size < MIN_SIZE_TO_ALLOW_HOME_PART else f"{min(blockdevice.size, 20)*1024}MiB", + "size" : "100%" if block_device.size < MIN_SIZE_TO_ALLOW_HOME_PART else f"{min(block_device.size, 20)*1024}MiB", "mountpoint" : "/", "filesystem" : { "format" : "btrfs" } }) - if blockdevice.size > MIN_SIZE_TO_ALLOW_HOME_PART: - layout[blockdevice]['partitions'].append({ + if block_device.size > MIN_SIZE_TO_ALLOW_HOME_PART: + layout[block_device]['partitions'].append({ # Home "type" : "primary", "encrypted" : True, "format" : True, - "start" : f"{min(blockdevice.size*0.2, 20)*1024}MiB", + "start" : f"{min(block_device.size*0.2, 20)*1024}MiB", "size" : "100%", "mountpoint" : "/home", "filesystem" : { @@ -78,8 +111,66 @@ def suggest_single_disk_layout(blockdevice): return layout -def suggest_multi_disk_layout(blockdevices): - pass + +def suggest_multi_disk_layout(block_devices): + MIN_SIZE_TO_ALLOW_HOME_PART = 40 # Gb + + block_devices = sort_block_devices_based_on_performance(block_devices).keys() + + root_device = select_disk_larger_than_or_close_to(block_devices, gigabytes=MIN_SIZE_TO_ALLOW_HOME_PART) + home_device = select_disk_larger_than_or_close_to(block_devices, gigabytes=MIN_SIZE_TO_ALLOW_HOME_PART, filter_out=[root_device]) + + + layout = { + root_device : { + "wipe" : True, + "partitions" : [] + }, + home_device : { + "wipe" : True, + "partitions" : [] + }, + } + + layout[root_device]['partitions'].append({ + # Boot + "type" : "primary", + "start" : "1MiB", + "size" : "513MiB", + "boot" : True, + "format" : True, + "mountpoint" : "/boot", + "filesystem" : { + "format" : "fat32" + } + }) + layout[root_device]['partitions'].append({ + # Root + "type" : "primary", + "start" : "513MiB", + "encrypted" : True, + "format" : True, + "size" : "100%", + "mountpoint" : "/", + "filesystem" : { + "format" : "btrfs" + } + }) + + layout[home_device]['partitions'].append({ + # Home + "type" : "primary", + "encrypted" : True, + "format" : True, + "start" : "4MiB", + "size" : "100%", + "mountpoint" : "/home", + "filesystem" : { + "format" : "btrfs" + } + }) + + return layout class BlockDevice: diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 228fa568..07f98328 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -9,7 +9,7 @@ import signal import sys import time -from .disk import BlockDevice, valid_fs_type +from .disk import BlockDevice, valid_fs_type, suggest_single_disk_layout, suggest_multi_disk_layout from .exceptions import * from .general import SysCommand from .hardware import AVAILABLE_GFX_DRIVERS, has_uefi @@ -601,9 +601,9 @@ def partition_overlap(partitions :list, start :str, end :str) -> bool: def get_default_partition_layout(block_devices): if len(block_devices) == 1: - return suggest_single_disk_layout(blockdevices[0]) + return suggest_single_disk_layout(block_devices[0]) else: - return suggest_multi_disk_layout(blockdevices) + return suggest_multi_disk_layout(block_devices) # TODO: Implement sane generic layout for 2+ drives -- cgit v1.2.3-70-g09d2 From d76760b45fcc085f1d878465e5293de2740a70b4 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 13 Jun 2021 14:25:07 +0200 Subject: Removed old safety logics for partitions. Partitions will now always be formatted when .format() is called on them. The safety now lay in the code parsing the declerative partition layouts. Also added the encrypt/mount logic for encrypted partitions, which by default will be unencrypted unless a password is specified. --- archinstall/lib/disk.py | 50 ++++++++++++------------------------- archinstall/lib/installer.py | 15 +++++++++-- archinstall/lib/luks.py | 7 ------ archinstall/lib/storage.py | 3 ++- archinstall/lib/user_interaction.py | 23 +++++++++-------- examples/guided.py | 10 ++++---- 6 files changed, 48 insertions(+), 60 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 97b982d8..7ef65a5c 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -76,6 +76,7 @@ def suggest_single_disk_layout(block_device): "start" : "1MiB", "size" : "513MiB", "boot" : True, + "encrypted" : False, "format" : True, "mountpoint" : "/boot", "filesystem" : { @@ -86,7 +87,7 @@ def suggest_single_disk_layout(block_device): # Root "type" : "primary", "start" : "513MiB", - "encrypted" : True, + "encrypted" : False, "format" : True, "size" : "100%" if block_device.size < MIN_SIZE_TO_ALLOW_HOME_PART else f"{min(block_device.size, 20)*1024}MiB", "mountpoint" : "/", @@ -99,7 +100,7 @@ def suggest_single_disk_layout(block_device): layout[block_device]['partitions'].append({ # Home "type" : "primary", - "encrypted" : True, + "encrypted" : False, "format" : True, "start" : f"{min(block_device.size*0.2, 20)*1024}MiB", "size" : "100%", @@ -138,6 +139,7 @@ def suggest_multi_disk_layout(block_devices): "start" : "1MiB", "size" : "513MiB", "boot" : True, + "encrypted" : False, "format" : True, "mountpoint" : "/boot", "filesystem" : { @@ -148,7 +150,7 @@ def suggest_multi_disk_layout(block_devices): # Root "type" : "primary", "start" : "513MiB", - "encrypted" : True, + "encrypted" : False, "format" : True, "size" : "100%", "mountpoint" : "/", @@ -160,7 +162,7 @@ def suggest_multi_disk_layout(block_devices): layout[home_device]['partitions'].append({ # Home "type" : "primary", - "encrypted" : True, + "encrypted" : False, "format" : True, "start" : "4MiB", "size" : "100%", @@ -514,7 +516,7 @@ class Partition: from .luks import luks2 try: - with luks2(self, 'luksloop', password, auto_unmount=True) as unlocked_device: + with luks2(self, storage.get('ENC_IDENTIFIER', 'ai')+'loop', password, auto_unmount=True) as unlocked_device: return unlocked_device.filesystem except SysCallError: return None @@ -540,35 +542,12 @@ class Partition: return True if files > 0 else False - def safe_to_format(self): - if self.allow_formatting is False: - log(f"Partition {self} is not marked for formatting.", level=logging.DEBUG) - return False - elif self.target_mountpoint == '/boot': - try: - if self.has_content(): - log(f"Partition {self} is a boot partition and has content inside.", level=logging.DEBUG) - return False - except SysCallError as err: - log(err.message, logging.DEBUG) - log(f"Partition {self} was identified as /boot but we could not mount to check for content, continuing!", level=logging.DEBUG) - pass - - 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(): - log(f"Partition {self} was marked as protected but encrypt() was called on it!", level=logging.ERROR, fg="red") - return False - handle = luks2(self, None, None) return handle.encrypt(self, *args, **kwargs) @@ -745,6 +724,8 @@ class Filesystem: return True def load_layout(self, layout :dict): + from .luks import luks2 + # If the layout tells us to wipe the drive, we do so if layout.get('wipe', False): if self.mode == GPT: @@ -770,11 +751,11 @@ class Filesystem: raise ValueError("BlockDevice().load_layout() doesn't know how to continue without either a UUID or creation of partition.") if partition.get('filesystem', {}).get('format', None): - if partition.get('encrypt', False): + if partition.get('encrypted', False): assert partition.get('password') partition['device_instance'].encrypt(password=partition['password']) - with archinstall.luks2(partition['device_instance'], 'luksloop', partition['password']) as unlocked_device: + with luks2(partition['device_instance'], storage.get('ENC_IDENTIFIER', 'ai')+'loop', partition['password']) as unlocked_device: unlocked_device.format(partition['filesystem']['format'], allow_formatting=partition.get('format', False)) else: partition['device_instance'].format(partition['filesystem']['format'], allow_formatting=partition.get('format', False)) @@ -957,7 +938,8 @@ def encrypted_partitions(blockdevices :dict) -> bool: if partition.get('encrypted', False): yield partition -def find_partition_by_mountpoint(partitions, relative_mountpoint :str): - for partition in partitions: - if partition.get('mountpoint', None) == relative_mountpoint: - return partition \ No newline at end of file +def find_partition_by_mountpoint(block_devices, relative_mountpoint :str): + for device in block_devices: + for partition in block_devices[device]['partitions']: + if partition.get('mountpoint', None) == relative_mountpoint: + return partition \ No newline at end of file diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index b1e38dc6..00d3a001 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -126,13 +126,21 @@ class Installer: return True def mount_ordered_layout(self, layouts :dict): + from .luks import luks2 + mountpoints = {} for blockdevice in layouts: for partition in layouts[blockdevice]['partitions']: - mountpoints[partition['mountpoint']] = partition['device_instance'] + mountpoints[partition['mountpoint']] = partition for mountpoint in sorted(mountpoints.keys()): - mountpoints[mountpoint].mount(f"{self.target}{mountpoint}") + if mountpoints[mountpoint]['encrypted']: + loopdev = storage.get('ENC_IDENTIFIER', 'ai')+'loop' + password = mountpoints[mountpoint]['password'] + with luks2(mountpoints[mountpoint]['device_instance'], loopdev, password, auto_unmount=False) as unlocked_device: + unlocked_device.mount(f"{self.target}{mountpoint}") + else: + mountpoints[mountpoint]['device_instance'].mount(f"{self.target}{mountpoint}") def mount(self, partition, mountpoint, create_mountpoint=True): if create_mountpoint and not os.path.isdir(f'{self.target}{mountpoint}'): @@ -437,6 +445,9 @@ class Installer: elif partition.mountpoint == self.target: root_partition = partition + if boot_partition is None and root_partition is None: + raise ValueError(f"Could not detect root (/) or boot (/boot) in {self.target} based on: {self.partitions}") + self.log(f'Adding bootloader {bootloader} to {boot_partition if boot_partition else root_partition}', level=logging.INFO) if bootloader == 'systemd-bootctl': diff --git a/archinstall/lib/luks.py b/archinstall/lib/luks.py index b910bfb2..781bed43 100644 --- a/archinstall/lib/luks.py +++ b/archinstall/lib/luks.py @@ -18,9 +18,6 @@ class luks2: self.mapdev = None 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? @@ -42,9 +39,6 @@ class luks2: return True def encrypt(self, partition, password=None, key_size=512, hash_type='sha512', iter_time=10000, key_file=None): - if not self.partition.allow_formatting: - raise DiskError(f'Could not encrypt volume {partition} due to it having a formatting lock.') - log(f'Encrypting {partition} (This might take a while)', level=logging.INFO) if not key_file: @@ -132,7 +126,6 @@ class luks2: if os.path.islink(f'/dev/mapper/{mountpoint}'): self.mapdev = f'/dev/mapper/{mountpoint}' unlocked_partition = Partition(self.mapdev, None, 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): diff --git a/archinstall/lib/storage.py b/archinstall/lib/storage.py index 4e19e4d4..67f8e716 100644 --- a/archinstall/lib/storage.py +++ b/archinstall/lib/storage.py @@ -18,5 +18,6 @@ storage = { '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', - 'MOUNT_POINT': '/mnt', + 'MOUNT_POINT': '/mnt/archinstall', + 'ENC_IDENTIFIER': 'ainst' } diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 07f98328..da9f9809 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -9,7 +9,7 @@ import signal import sys import time -from .disk import BlockDevice, valid_fs_type, suggest_single_disk_layout, suggest_multi_disk_layout +from .disk import BlockDevice, valid_fs_type, find_partition_by_mountpoint, suggest_single_disk_layout, suggest_multi_disk_layout from .exceptions import * from .general import SysCommand from .hardware import AVAILABLE_GFX_DRIVERS, has_uefi @@ -190,18 +190,19 @@ def generic_multi_select(options, text="Select one or more of the options above sys.stdout.flush() return selected_options -def select_encrypted_partitions(blockdevices :dict) -> dict: - if len(blockdevices) == 1: - if len(blockdevices[0]['partitions']) == 2: - root = find_partition_by_mountpoint(blockdevices[0]['partitions'], '/') - blockdevices[0]['partitions'][root]['encrypted'] = True - return True +def select_encrypted_partitions(block_devices :dict, password :str) -> dict: + root = find_partition_by_mountpoint(block_devices, '/') + root['encrypted'] = True + root['password'] = password + + return block_devices - options = [] - for partition in blockdevices.values(): - options.append({key: val for key, val in partition.items() if val}) + # TODO: Next version perhaps we can support multiple encrypted partitions + #options = [] + #for partition in block_devices.values(): + # options.append({key: val for key, val in partition.items() if val}) - print(generic_multi_select(options, f"Choose which partitions to encrypt (leave blank when done): ")) + #print(generic_multi_select(options, f"Choose which partitions to encrypt (leave blank when done): ")) class MiniCurses: def __init__(self, width, height): diff --git a/examples/guided.py b/examples/guided.py index 14cbe0a1..4136ca6e 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -87,11 +87,11 @@ def ask_user_questions(): if (passwd := archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ')): archinstall.arguments['!encryption-password'] = passwd - # If no partitions was marked as encrypted (rare), but a password was supplied - - # then we need to identify which partitions to encrypt. This will default to / (root) if only - # root and boot are detected. - if len(list(archinstall.encrypted_partitions(archinstall.storage['disk_layouts']))) == 0: - archinstall.storage['disk_layouts'] = archinstall.select_encrypted_partitions(archinstall.storage['disk_layouts']) + if archinstall.arguments['harddrives'] and archinstall.arguments.get('!encryption-password', None): + # If no partitions was marked as encrypted, but a password was supplied and we have some disks to format.. + # Then we need to identify which partitions to encrypt. This will default to / (root). + if len(list(archinstall.encrypted_partitions(archinstall.storage['disk_layouts']))) == 0: + archinstall.storage['disk_layouts'] = archinstall.select_encrypted_partitions(archinstall.storage['disk_layouts'], archinstall.arguments['!encryption-password']) # Ask which boot-loader to use (will only ask if we're in BIOS (non-efi) mode) if not archinstall.arguments.get("bootloader", None): -- cgit v1.2.3-70-g09d2 From 1450387fae158610dfe40a489579951ce071c260 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 13 Jun 2021 22:06:40 +0200 Subject: Started on multiple-disk-re-usage selection process. --- archinstall/lib/disk.py | 8 ++--- archinstall/lib/user_interaction.py | 61 ++++++++++++++++++------------------- 2 files changed, 34 insertions(+), 35 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 7ef65a5c..aa52c7af 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -402,9 +402,9 @@ class Partition: mount_repr = f", rel_mountpoint={self.target_mountpoint}" if self._encrypted: - return f'Partition(path={self.path}, size={self.size}, real_device={self.real_device}, fs={self.filesystem}{mount_repr})' + return f'Partition(path={self.path}, size={self.size}, PARTUUID={self.uuid}, parent={self.real_device}, fs={self.filesystem}{mount_repr})' else: - return f'Partition(path={self.path}, size={self.size}, fs={self.filesystem}{mount_repr})' + return f'Partition(path={self.path}, size={self.size}, PARTUUID={self.uuid}, fs={self.filesystem}{mount_repr})' def __dump__(self): return { @@ -738,13 +738,13 @@ class Filesystem: # We then iterate the partitions in order for partition in layout.get('partitions', []): # We don't want to re-add an existing partition (those containing a UUID already) - if partition.get('format', False) and not partition.get('uuid', None): + if partition.get('format', False) and not partition.get('PARTUUID', None): partition['device_instance'] = self.add_partition(partition.get('type', 'primary'), start=partition.get('start', '1MiB'), # TODO: Revisit sane block starts (4MB for memorycards for instance) end=partition.get('size', '100%'), partition_format=partition.get('filesystem', {}).get('format', 'btrfs')) - elif partition_uuid := partition.get('uuid'): + elif partition_uuid := partition.get('PARTUUID'): if partition_instance := self.blockdevice.get_partition(uuid=partition_uuid): partition['device_instance'] = partition_instance else: diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index da9f9809..f85bf64f 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -119,7 +119,7 @@ def print_large_list(options, padding=5, margin_bottom=0, separator=': '): def generic_multi_select(options, text="Select one or more of the options above (leave blank to continue): ", sort=True, default=None, allow_empty=False): # Checking if the options are different from `list` or `dict` or if they are empty - if type(options) not in [list, dict]: + if type(options) not in [list, dict, type({}.keys()), type({}.values())]: log(f" * Generic multi-select doesn't support ({type(options)}) as type of options * ", fg='red') log(" * If problem persists, please create an issue on https://github.com/archlinux/archinstall/issues * ", fg='yellow') raise RequirementError("generic_multi_select() requires list or dictionary as options.") @@ -130,6 +130,8 @@ def generic_multi_select(options, text="Select one or more of the options above # After passing the checks, function continues to work if type(options) == dict: options = list(options.values()) + elif type(options) in (type({}.keys()), type({}.values())): + options = list(options) if sort: options = sorted(options) @@ -550,36 +552,33 @@ def generic_select(options, input_text="Select one of the above by index or abso return selected_option -def select_partition_layout(block_device): - return { - BlockDevice("/dev/sda"): { # Block Device level - "wipe": False, # Safety flags - "partitions" : [ # Affected / New partitions - { - "PARTUUID" : "654bb317-1b73-4339-9a00-7222792f4ba9", # If existing partition - "wipe" : False, # Safety flags - "boot" : True, # Safety flags / new flags - "ESP" : True, # Safety flags / new flags - "mountpoint" : "/mnt/boot" - } - ] - }, - BlockDevice("/dev/sdb") : { - "wipe" : True, - "partitions" : [ - { - # No PARTUUID required here since it's a new partition - "type" : "primary", # parted options - "size" : "100%", - "filesystem" : { - "encrypted" : True, # TODO: Not sure about this here - "format": "btrfs", # mkfs options - }, - "mountpoint" : "/mnt" - } - ] - } +def select_reusage_of_partitions(block_device): + log(f"Selecting which partitions to re-use on {block_device}...", fg="yellow", level=logging.INFO) + partitions = generic_multi_select(block_device.partitions.values(), "Select which partitions to re-use (the rest will be left alone): ", sort=True) + partitions_to_wipe = generic_multi_select(partitions, "Which partitions do you wish to wipe (multiple can be selected): ", sort=True) + + mountpoints = {} + struct = { + "partitions" : [] } + for partition in partitions: + mountpoint = input(f"Select a mountpoint (or skip) for {partition}: ").strip() + + part_struct = {} + if mountpoint: + part_struct['mountpoint'] = mountpoint + if mountpoint == '/boot': + part_struct['boot'] = True + if has_uefi(): + part_struct['ESP'] = True + if partition.uuid: + part_struct['PARTUUID'] = partition.uuid + if partition in partitions_to_wipe: + part_struct['wipe'] = True + + struct['partitions'].append(part_struct) + + return struct def valid_parted_position(pos :str): if not len(pos): @@ -719,7 +718,7 @@ def select_individual_blockdevice_usage(block_devices :list): device_mode = generic_select(modes) if device_mode == "Re-use partitions": - layout = select_partition_layout(device) + layout = select_reusage_of_partitions(device) elif device_mode == "Wipe and create new partitions": layout = wipe_and_create_partitions(device) else: -- cgit v1.2.3-70-g09d2 From 630dedadd115ff865a17811028f80dec96e18e0e Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 14 Jun 2021 14:49:49 +0200 Subject: Legacy re-name of function. --- archinstall/lib/user_interaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 17a91332..9a1d2d3d 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -610,7 +610,7 @@ def get_default_partition_layout(block_devices): # TODO: Implement sane generic layout for 2+ drives def wipe_and_create_partitions(block_device :BlockDevice) -> dict: - if hasUEFI(): + if has_uefi(): partition_type = 'gpt' else: partition_type = 'msdos' -- cgit v1.2.3-70-g09d2 From 0c203384e0d9b856eb57b34b700f937e645920da Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 14 Jun 2021 16:04:43 +0200 Subject: Re-structuring partition flow --- archinstall/lib/user_interaction.py | 75 +++++++++++++++---------------------- 1 file changed, 31 insertions(+), 44 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index f85bf64f..2903152f 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -552,34 +552,6 @@ def generic_select(options, input_text="Select one of the above by index or abso return selected_option -def select_reusage_of_partitions(block_device): - log(f"Selecting which partitions to re-use on {block_device}...", fg="yellow", level=logging.INFO) - partitions = generic_multi_select(block_device.partitions.values(), "Select which partitions to re-use (the rest will be left alone): ", sort=True) - partitions_to_wipe = generic_multi_select(partitions, "Which partitions do you wish to wipe (multiple can be selected): ", sort=True) - - mountpoints = {} - struct = { - "partitions" : [] - } - for partition in partitions: - mountpoint = input(f"Select a mountpoint (or skip) for {partition}: ").strip() - - part_struct = {} - if mountpoint: - part_struct['mountpoint'] = mountpoint - if mountpoint == '/boot': - part_struct['boot'] = True - if has_uefi(): - part_struct['ESP'] = True - if partition.uuid: - part_struct['PARTUUID'] = partition.uuid - if partition in partitions_to_wipe: - part_struct['wipe'] = True - - struct['partitions'].append(part_struct) - - return struct - def valid_parted_position(pos :str): if not len(pos): return False @@ -607,12 +579,40 @@ def get_default_partition_layout(block_devices): # TODO: Implement sane generic layout for 2+ drives -def wipe_and_create_partitions(block_device :BlockDevice) -> dict: - if hasUEFI(): +def manage_new_and_existing_partitions(block_device :BlockDevice) -> dict: + if has_uefi(): partition_type = 'gpt' else: partition_type = 'msdos' + # log(f"Selecting which partitions to re-use on {block_device}...", fg="yellow", level=logging.INFO) + # partitions = generic_multi_select(block_device.partitions.values(), "Select which partitions to re-use (the rest will be left alone): ", sort=True) + # partitions_to_wipe = generic_multi_select(partitions, "Which partitions do you wish to wipe (multiple can be selected): ", sort=True) + + # mountpoints = {} + # struct = { + # "partitions" : [] + # } + # for partition in partitions: + # mountpoint = input(f"Select a mountpoint (or skip) for {partition}: ").strip() + + # part_struct = {} + # if mountpoint: + # part_struct['mountpoint'] = mountpoint + # if mountpoint == '/boot': + # part_struct['boot'] = True + # if has_uefi(): + # part_struct['ESP'] = True + # elif mountpoint == '/' and + # if partition.uuid: + # part_struct['PARTUUID'] = partition.uuid + # if partition in partitions_to_wipe: + # part_struct['wipe'] = True + + # struct['partitions'].append(part_struct) + + # return struct + partitions_result = [] # Test code: [part.__dump__() for part in block_device.partitions.values()] # TODO: Squeeze in BTRFS subvolumes here @@ -709,20 +709,7 @@ def select_individual_blockdevice_usage(block_devices :list): result = {} for device in block_devices: - log(f'Select what to do with {device}', fg="yellow") - modes = [ - "Wipe and create new partitions", - "Re-use partitions" - ] - - device_mode = generic_select(modes) - - if device_mode == "Re-use partitions": - layout = select_reusage_of_partitions(device) - elif device_mode == "Wipe and create new partitions": - layout = wipe_and_create_partitions(device) - else: - continue + layout = manage_new_and_existing_partitions(device) result[device] = layout -- cgit v1.2.3-70-g09d2 From eb4c134c8001888dc775e7af5dad419fa77bc134 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 14 Jun 2021 16:20:26 +0200 Subject: Unifying partioning logic into one function when managing partitions. Rather than giving the option between wiping and creating, and re-using and creating --- archinstall/lib/user_interaction.py | 69 ++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 31 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index c2ac7b33..83b85d02 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -615,30 +615,35 @@ def manage_new_and_existing_partitions(block_device :BlockDevice) -> dict: # return struct - partitions_result = [] # Test code: [part.__dump__() for part in block_device.partitions.values()] + mountpoints = {} + block_device_struct = { + "partitions" : [] + } + # Test code: [part.__dump__() for part in block_device.partitions.values()] # TODO: Squeeze in BTRFS subvolumes here while True: modes = [ - "Create new partition", - "Suggest partition layout", - "Delete partition" if len(partitions_result) else "", - "Assign mount-point for partition" if len(partitions_result) else "", - "Mark/Unmark a partition as encrypted" if len(partitions_result) else "", - "Mark/Unmark a partition as bootable (automatic for /boot)" if len(partitions_result) else "" + "Create a new partition", + f"Suggest partition layout for {block_device}", + "Delete a partition" if len(block_device_struct) else "", + "Clear/Delete all partitions" if len(block_device_struct) else "", + "Assign mount-point for a partition" if len(block_device_struct) else "", + "Mark/Unmark a partition as encrypted" if len(block_device_struct) else "", + "Mark/Unmark a partition as bootable (automatic for /boot)" if len(block_device_struct) else "" ] # Print current partition layout: - if len(partitions_result): + if len(block_device_struct["partitions"]): print('Current partition layout:') - for partition in partitions_result: - print({key: val for key, val in partition.items() if val}) + for partition in block_device_struct["partitions"]: + print(partition) print() task = generic_select(modes, input_text=f"Select what to do with {block_device} (leave blank when done): ") - if task == 'Create new partition': + if task == 'Create a new partition': if partition_type == 'gpt': # https://www.gnu.org/software/parted/manual/html_node/mkpart.html # https://www.gnu.org/software/parted/manual/html_node/mklabel.html @@ -648,11 +653,11 @@ def manage_new_and_existing_partitions(block_device :BlockDevice) -> dict: end = input("Enter the end sector of the partition (percentage or block number, ex: 100%): ").strip() if valid_parted_position(start) and valid_parted_position(end) and valid_fs_type(fstype): - if partition_overlap(partitions_result, start, end): + if partition_overlap(block_device_struct, start, end): log(f"This partition overlaps with other partitions on the drive! Ignoring this partition creation.", fg="red") continue - partitions_result.append({ + block_device_struct.append({ "type" : "primary", # Strictly only allowed under MSDOS, but GPT accepts it so it's "safe" to inject "start" : start, "size" : end, @@ -664,47 +669,49 @@ def manage_new_and_existing_partitions(block_device :BlockDevice) -> dict: else: log(f"Invalid start, end or fstype for this partition. Ignoring this partition creation.", fg="red") continue - elif task == "Suggest partition layout": - if len(partitions_result): + elif task[:len("Suggest partition layout")] == "Suggest partition layout": + if len(block_device_struct): if input(f"{block_device} contains queued partitions, this will remove those, are you sure? y/N: ").strip().lower() in ('', 'n'): continue - partitions_result = suggest_single_disk_layout(block_device)[block_device] + block_device_struct = suggest_single_disk_layout(block_device)[block_device] elif task is None: return { - block_device : partitions_result + block_device : block_device_struct } else: - for index, partition in enumerate(partitions_result): + for index, partition in enumerate(block_device_struct): print(f"{index}: Start: {partition['start']}, End: {partition['size']} ({partition['filesystem']['format']}{', mounting at: '+partition['mountpoint'] if partition['mountpoint'] else ''})") - if task == "Delete partition": - if (partition := generic_select(partitions_result, 'Select which partition to delete: ', options_output=False)): - del(partitions_result[partitions_result.index(partition)]) - elif task == "Assign mount-point for partition": - if (partition := generic_select(partitions_result, 'Select which partition to mount where: ', options_output=False)): + if task == "Delete a partition": + if (partition := generic_select(block_device_struct, 'Select which partition to delete: ', options_output=False)): + del(block_device_struct[block_device_struct.index(partition)]) + elif task == "Clear/Delete all partitions": + block_device_struct = [] + elif task == "Assign mount-point for a partition": + if (partition := generic_select(block_device_struct, 'Select which partition to mount where: ', options_output=False)): print(' * Partition mount-points are relative to inside the installation, the boot would be /boot as an example.') mountpoint = input('Select where to mount partition (leave blank to remove mountpoint): ').strip() if len(mountpoint): - partitions_result[partitions_result.index(partition)]['mountpoint'] = mountpoint + block_device_struct[block_device_struct.index(partition)]['mountpoint'] = mountpoint if mountpoint == '/boot': log(f"Marked partition as bootable because mountpoint was set to /boot.", fg="yellow") - partitions_result[partitions_result.index(partition)]['boot'] = True + block_device_struct[block_device_struct.index(partition)]['boot'] = True else: - del(partitions_result[partitions_result.index(partition)]['mountpoint']) + del(block_device_struct[block_device_struct.index(partition)]['mountpoint']) elif task == "Mark/Unmark a partition as encrypted": - if (partition := generic_select(partitions_result, 'Select which partition to mark as encrypted: ', options_output=False)): + if (partition := generic_select(block_device_struct, 'Select which partition to mark as encrypted: ', options_output=False)): # Negate the current encryption marking - partitions_result[partitions_result.index(partition)]['encrypted'] = not partitions_result[partitions_result.index(partition)].get('encrypted', False) + block_device_struct[block_device_struct.index(partition)]['encrypted'] = not block_device_struct[block_device_struct.index(partition)].get('encrypted', False) elif task == "Mark/Unmark a partition as bootable (automatic for /boot)": - if (partition := generic_select(partitions_result, 'Select which partition to mark as bootable: ', options_output=False)): - partitions_result[partitions_result.index(partition)]['boot'] = not partitions_result[partitions_result.index(partition)].get('boot', False) + if (partition := generic_select(block_device_struct, 'Select which partition to mark as bootable: ', options_output=False)): + block_device_struct[block_device_struct.index(partition)]['boot'] = not block_device_struct[block_device_struct.index(partition)].get('boot', False) return { - block_device : partitions_result + block_device : block_device_struct } def select_individual_blockdevice_usage(block_devices :list): -- cgit v1.2.3-70-g09d2 From 2ac1f453cf2f12baeb562b0ff47911e1358d988d Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 14 Jun 2021 18:53:34 +0200 Subject: Added BlockDevice.largest_free_space and BlockDevice.free_space (iterator). Also added additional supported filesystems to parted. Apparently the online manpages doesn't agree with the local manpages, my previous statement that these gets ignored is false so added those in and removed some that isn't supported by my local manpages, 'ufs' for instance. --- archinstall/lib/disk.py | 38 +++++++++++++++++++++++++++++++++---- archinstall/lib/user_interaction.py | 18 +++++++++++++++--- 2 files changed, 49 insertions(+), 7 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index aa52c7af..1911110a 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -15,16 +15,27 @@ MBR = 0b00000010 def valid_fs_type(fstype :str) -> bool: # https://www.gnu.org/software/parted/manual/html_node/mkpart.html + # Above link doesn't agree with `man parted` /mkpart documentation: + """ + fs-type can + be one of "btrfs", "ext2", + "ext3", "ext4", "fat16", + "fat32", "hfs", "hfs+", + "linux-swap", "ntfs", "reis‐ + erfs", "udf", or "xfs". + """ return fstype in [ + "btrfs", "ext2", + "ext3", "ext4", # `man parted` allows these "fat16", "fat32", - "hfs", "hfs+", "hfsx", + "hfs", "hfs+", # "hfsx", not included in `man parted` "linux-swap", - "NTFS", + "ntfs", "reiserfs", - "ufs", - "btrfs", + "udf", # "ufs", not included in `man parted` + "xfs", # `man parted` allows this ] @@ -335,6 +346,25 @@ class BlockDevice: for device in output['blockdevices']: return device['rota'] is True + @property + def free_space(self): + for line in SysCommand(f"parted --machine {self.path} print free"): + if 'free' in (free_space := line.decode('UTF-8')): + _, start, end, size, *_ = free_space.strip('\r\n;').split(':') + yield (start, end, size) + + @property + def largest_free_space(self): + info = None + for space_info in self.free_space: + if not info: + info = space_info + else: + # [-1] = size + if space_info[-1] > info[-1]: + info = space_info + return info + def has_partitions(self): return len(self.partitions) diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 83b85d02..d4275b43 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -567,6 +567,9 @@ def valid_parted_position(pos :str): if pos[-3:].lower() in ['mib', 'kib', 'b', 'tib'] and pos[:-3].isdigit(): return True + if pos[-2:].lower() in ['kb', 'mb', 'gb', 'tb'] and pos[:-2].isdigit(): + return True + return False def partition_overlap(partitions :list, start :str, end :str) -> bool: @@ -617,7 +620,7 @@ def manage_new_and_existing_partitions(block_device :BlockDevice) -> dict: mountpoints = {} block_device_struct = { - "partitions" : [] + "partitions" : [partition.__dump__() for partition in block_device.partitions.values()] } # Test code: [part.__dump__() for part in block_device.partitions.values()] # TODO: Squeeze in BTRFS subvolumes here @@ -648,9 +651,18 @@ def manage_new_and_existing_partitions(block_device :BlockDevice) -> dict: # https://www.gnu.org/software/parted/manual/html_node/mkpart.html # https://www.gnu.org/software/parted/manual/html_node/mklabel.html name = input("Enter a desired name for the partition: ").strip() + fstype = input("Enter a desired filesystem type for the partition: ").strip() - start = input("Enter the start sector of the partition (percentage or block number, ex: 0%): ").strip() - end = input("Enter the end sector of the partition (percentage or block number, ex: 100%): ").strip() + + start = input(f"Enter the start sector (percentage or block number, default: {block_device.largest_free_space[0]}): ").strip() + if not start.strip(): + start = block_device.largest_free_space[0] + end_suggested = block_device.largest_free_space[1] + else: + end_suggested = '100%' + end = input(f"Enter the end sector of the partition (percentage or block number, ex: {end_suggested}): ").strip() + if not end.strip(): + end = end_suggested if valid_parted_position(start) and valid_parted_position(end) and valid_fs_type(fstype): if partition_overlap(block_device_struct, start, end): -- cgit v1.2.3-70-g09d2 From 6176ac5e5d6fc52740c2af73af89db7e413d3d2f Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 14 Jun 2021 19:01:06 +0200 Subject: Since all fs-type's appear to be lower-case in 'man parted', we'll check against a lowered list in general for supported fs-types. --- archinstall/lib/disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 1911110a..8424bc24 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -25,7 +25,7 @@ def valid_fs_type(fstype :str) -> bool: erfs", "udf", or "xfs". """ - return fstype in [ + return fstype.lower() in [ "btrfs", "ext2", "ext3", "ext4", # `man parted` allows these -- cgit v1.2.3-70-g09d2 From fa8862a46078a9684144853461e2c3ba99bda71c Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 14 Jun 2021 19:04:51 +0200 Subject: moved valid_parted_position to disk. And made it handle float numbers. --- archinstall/lib/disk.py | 18 ++++++++++++++++++ archinstall/lib/user_interaction.py | 22 ++-------------------- 2 files changed, 20 insertions(+), 20 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 8424bc24..c63a7b09 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -13,6 +13,24 @@ GPT = 0b00000001 MBR = 0b00000010 +def valid_parted_position(pos :str): + if not len(pos): + return False + + if pos.isdigit(): + return True + + if pos[-1] == '%' and pos[:-1].isdigit(): + return True + + if pos[-3:].lower() in ['mib', 'kib', 'b', 'tib'] and pos[:-3].replace(".", "", 1).isdigit(): + return True + + if pos[-2:].lower() in ['kb', 'mb', 'gb', 'tb'] and pos[:-2].replace(".", "", 1).isdigit(): + return True + + return False + def valid_fs_type(fstype :str) -> bool: # https://www.gnu.org/software/parted/manual/html_node/mkpart.html # Above link doesn't agree with `man parted` /mkpart documentation: diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index d4275b43..9d5c1e6f 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -9,7 +9,7 @@ import signal import sys import time -from .disk import BlockDevice, valid_fs_type, find_partition_by_mountpoint, suggest_single_disk_layout, suggest_multi_disk_layout +from .disk import BlockDevice, valid_fs_type, find_partition_by_mountpoint, suggest_single_disk_layout, suggest_multi_disk_layout, valid_parted_position from .exceptions import * from .general import SysCommand from .hardware import AVAILABLE_GFX_DRIVERS, has_uefi, has_amd_graphics, has_intel_graphics, has_nvidia_graphics @@ -554,24 +554,6 @@ def generic_select(options, input_text="Select one of the above by index or abso return selected_option -def valid_parted_position(pos :str): - if not len(pos): - return False - - if pos.isdigit(): - return True - - if pos[-1] == '%' and pos[:-1].isdigit(): - return True - - if pos[-3:].lower() in ['mib', 'kib', 'b', 'tib'] and pos[:-3].isdigit(): - return True - - if pos[-2:].lower() in ['kb', 'mb', 'gb', 'tb'] and pos[:-2].isdigit(): - return True - - return False - def partition_overlap(partitions :list, start :str, end :str) -> bool: # TODO: Implement sanity check return False @@ -679,7 +661,7 @@ def manage_new_and_existing_partitions(block_device :BlockDevice) -> dict: } }) else: - log(f"Invalid start, end or fstype for this partition. Ignoring this partition creation.", fg="red") + log(f"Invalid start ({valid_parted_position(start)}), end ({valid_parted_position(end)}) or fstype ({valid_fs_type(fstype)}) for this partition. Ignoring this partition creation.", fg="red") continue elif task[:len("Suggest partition layout")] == "Suggest partition layout": if len(block_device_struct): -- cgit v1.2.3-70-g09d2 From 6cf50618b1e7f00509ce5664341d558d0bb85141 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Mon, 14 Jun 2021 21:37:34 +0200 Subject: Change of variable --- archinstall/lib/user_interaction.py | 44 ++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 22 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 9d5c1e6f..91456626 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -628,6 +628,9 @@ def manage_new_and_existing_partitions(block_device :BlockDevice) -> dict: task = generic_select(modes, input_text=f"Select what to do with {block_device} (leave blank when done): ") + if not task: + break + if task == 'Create a new partition': if partition_type == 'gpt': # https://www.gnu.org/software/parted/manual/html_node/mkpart.html @@ -647,15 +650,16 @@ def manage_new_and_existing_partitions(block_device :BlockDevice) -> dict: end = end_suggested if valid_parted_position(start) and valid_parted_position(end) and valid_fs_type(fstype): - if partition_overlap(block_device_struct, start, end): + if partition_overlap(block_device_struct["partitions"], start, end): log(f"This partition overlaps with other partitions on the drive! Ignoring this partition creation.", fg="red") continue - block_device_struct.append({ + block_device_struct["partitions"].append({ "type" : "primary", # Strictly only allowed under MSDOS, but GPT accepts it so it's "safe" to inject "start" : start, "size" : end, "mountpoint" : None, + "wipe" : True, "filesystem" : { "format" : fstype } @@ -664,49 +668,45 @@ def manage_new_and_existing_partitions(block_device :BlockDevice) -> dict: log(f"Invalid start ({valid_parted_position(start)}), end ({valid_parted_position(end)}) or fstype ({valid_fs_type(fstype)}) for this partition. Ignoring this partition creation.", fg="red") continue elif task[:len("Suggest partition layout")] == "Suggest partition layout": - if len(block_device_struct): + if len(block_device_struct["partitions"]): if input(f"{block_device} contains queued partitions, this will remove those, are you sure? y/N: ").strip().lower() in ('', 'n'): continue - block_device_struct = suggest_single_disk_layout(block_device)[block_device] + block_device_struct["partitions"] = suggest_single_disk_layout(block_device)[block_device] elif task is None: - return { - block_device : block_device_struct - } + return block_device_struct else: - for index, partition in enumerate(block_device_struct): + for index, partition in enumerate(block_device_struct["partitions"]): print(f"{index}: Start: {partition['start']}, End: {partition['size']} ({partition['filesystem']['format']}{', mounting at: '+partition['mountpoint'] if partition['mountpoint'] else ''})") if task == "Delete a partition": - if (partition := generic_select(block_device_struct, 'Select which partition to delete: ', options_output=False)): - del(block_device_struct[block_device_struct.index(partition)]) + if (partition := generic_select(block_device_struct["partitions"], 'Select which partition to delete: ', options_output=False)): + del(block_device_struct["partitions"][block_device_struct["partitions"].index(partition)]) elif task == "Clear/Delete all partitions": - block_device_struct = [] + block_device_struct["partitions"] = [] elif task == "Assign mount-point for a partition": - if (partition := generic_select(block_device_struct, 'Select which partition to mount where: ', options_output=False)): + if (partition := generic_select(block_device_struct["partitions"], 'Select which partition to mount where: ', options_output=False)): print(' * Partition mount-points are relative to inside the installation, the boot would be /boot as an example.') mountpoint = input('Select where to mount partition (leave blank to remove mountpoint): ').strip() if len(mountpoint): - block_device_struct[block_device_struct.index(partition)]['mountpoint'] = mountpoint + block_device_struct["partitions"][block_device_struct["partitions"].index(partition)]['mountpoint'] = mountpoint if mountpoint == '/boot': log(f"Marked partition as bootable because mountpoint was set to /boot.", fg="yellow") - block_device_struct[block_device_struct.index(partition)]['boot'] = True + block_device_struct["partitions"][block_device_struct["partitions"].index(partition)]['boot'] = True else: - del(block_device_struct[block_device_struct.index(partition)]['mountpoint']) + del(block_device_struct["partitions"][block_device_struct["partitions"].index(partition)]['mountpoint']) elif task == "Mark/Unmark a partition as encrypted": - if (partition := generic_select(block_device_struct, 'Select which partition to mark as encrypted: ', options_output=False)): + if (partition := generic_select(block_device_struct["partitions"], 'Select which partition to mark as encrypted: ', options_output=False)): # Negate the current encryption marking - block_device_struct[block_device_struct.index(partition)]['encrypted'] = not block_device_struct[block_device_struct.index(partition)].get('encrypted', False) + block_device_struct["partitions"][block_device_struct["partitions"].index(partition)]['encrypted'] = not block_device_struct["partitions"][block_device_struct["partitions"].index(partition)].get('encrypted', False) elif task == "Mark/Unmark a partition as bootable (automatic for /boot)": - if (partition := generic_select(block_device_struct, 'Select which partition to mark as bootable: ', options_output=False)): - block_device_struct[block_device_struct.index(partition)]['boot'] = not block_device_struct[block_device_struct.index(partition)].get('boot', False) + if (partition := generic_select(block_device_struct["partitions"], 'Select which partition to mark as bootable: ', options_output=False)): + block_device_struct["partitions"][block_device_struct["partitions"].index(partition)]['boot'] = not block_device_struct["partitions"][block_device_struct["partitions"].index(partition)].get('boot', False) - return { - block_device : block_device_struct - } + return block_device_struct def select_individual_blockdevice_usage(block_devices :list): result = {} -- cgit v1.2.3-70-g09d2 From 0079f3d95c2f8ac99dfb608a3c71ddaefa0c74b1 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 15 Jun 2021 10:55:36 +0200 Subject: Added fs-type and formatting on encrypted partitions that haven't been given a fs-type prior when re-using partitions. --- archinstall/lib/disk.py | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index c63a7b09..c0d961b9 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -415,7 +415,7 @@ class Partition: self.size = size # TODO: Refresh? self._encrypted = None self.encrypted = encrypted - self.allow_formatting = False # A fail-safe for unconfigured partitions, such as windows NTFS partitions. + self.allow_formatting = False if mountpoint: self.mount(mountpoint) @@ -599,7 +599,7 @@ class Partition: handle = luks2(self, None, None) return handle.encrypt(self, *args, **kwargs) - def format(self, filesystem=None, path=None, allow_formatting=None, log_formatting=True): + def format(self, filesystem=None, path=None, log_formatting=True): """ Format can be given an overriding path, for instance /dev/null to test the formatting functionality and in essence the support for the given filesystem. @@ -609,17 +609,12 @@ class Partition: if path is None: path = self.path - if allow_formatting is None: - allow_formatting = self.allow_formatting # To avoid "unable to open /dev/x: No such file or directory" start_wait = time.time() while pathlib.Path(path).exists() is False and time.time() - start_wait < 10: time.sleep(0.025) - 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_formatting: log(f'Formatting {path} -> {filesystem}', level=logging.INFO) @@ -792,21 +787,39 @@ class Filesystem: end=partition.get('size', '100%'), partition_format=partition.get('filesystem', {}).get('format', 'btrfs')) - elif partition_uuid := partition.get('PARTUUID'): - if partition_instance := self.blockdevice.get_partition(uuid=partition_uuid): - partition['device_instance'] = partition_instance + elif (partition_uuid := partition.get('PARTUUID')) and (partition_instance := self.blockdevice.get_partition(uuid=partition_uuid)): + partition['device_instance'] = partition_instance else: - raise ValueError("BlockDevice().load_layout() doesn't know how to continue without either a UUID or creation of partition.") + raise ValueError(f"{self}.load_layout() doesn't know how to continue without a new partition definition or a UUID ({partition.get('PARTUUID')}) on the device ({self.blockdevice.get_partition(uuid=partition_uuid)}).") if partition.get('filesystem', {}).get('format', None): if partition.get('encrypted', False): - assert partition.get('password') + if not partition.get('password'): + if storage['arguments'] == 'silent': + raise ValueError(f"Missing encryption password for {partition['device_instance']}") + else: + from .user_interaction import get_password + partition['password'] = get_password(f"Enter a encryption password for {partition['device_instance']}") partition['device_instance'].encrypt(password=partition['password']) with luks2(partition['device_instance'], storage.get('ENC_IDENTIFIER', 'ai')+'loop', partition['password']) as unlocked_device: - unlocked_device.format(partition['filesystem']['format'], allow_formatting=partition.get('format', False)) - else: - partition['device_instance'].format(partition['filesystem']['format'], allow_formatting=partition.get('format', False)) + if not partition.get('format'): + if storage['arguments'] == 'silent': + raise ValueError(f"Missing fs-type to format on newly created encrypted partition {partition['device_instance']}") + else: + if not partition.get('filesystem'): + partition['filesystem'] = {} + + while True: + partition['filesystem']['format'] = input(f"Enter a valid fs-type for newly encrypted partition {partition['filesystem']['format']}: ").strip() + if not partition['filesystem']['format'] or valid_fs_type(partition['filesystem']['format']) is False: + pint("You need to enter a valid fs-type in order to continue. See `man parted` for valid fs-type's.") + continue + break + + unlocked_device.format(partition['filesystem']['format']) + elif partition.get('format', False): + partition['device_instance'].format(partition['filesystem']['format']) def find_partition(self, mountpoint): for partition in self.blockdevice: -- cgit v1.2.3-70-g09d2 From 20e759d4cc60e78d8d769ec2239aab1b0233a59d Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 15 Jun 2021 11:51:29 +0200 Subject: Added more detail to BlockDevice.__repr__ --- archinstall/lib/disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index c0d961b9..76871352 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -221,7 +221,7 @@ class BlockDevice: # I'm placing the encryption password on a BlockDevice level. def __repr__(self, *args, **kwargs): - return f"BlockDevice({self.device})" + return f"BlockDevice({self.device}, size={self.size}GB, free_space={'+'.join(part[2] for part in self.free_space)}, bus_type={self.bus_type})" def __iter__(self): for partition in self.partitions: -- cgit v1.2.3-70-g09d2 From e965eaf546a73f76f5e928aa03154ff5c482fb4c Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sat, 3 Jul 2021 14:00:03 +0200 Subject: Missing .format() on 'use entire disk' step --- archinstall/lib/disk.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 76871352..3bc2fa0f 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -810,14 +810,15 @@ class Filesystem: if not partition.get('filesystem'): partition['filesystem'] = {} - while True: - partition['filesystem']['format'] = input(f"Enter a valid fs-type for newly encrypted partition {partition['filesystem']['format']}: ").strip() - if not partition['filesystem']['format'] or valid_fs_type(partition['filesystem']['format']) is False: - pint("You need to enter a valid fs-type in order to continue. See `man parted` for valid fs-type's.") - continue - break - - unlocked_device.format(partition['filesystem']['format']) + if not partition['filesystem'].get('format', False): + while True: + partition['filesystem']['format'] = input(f"Enter a valid fs-type for newly encrypted partition {partition['filesystem']['format']}: ").strip() + if not partition['filesystem']['format'] or valid_fs_type(partition['filesystem']['format']) is False: + pint("You need to enter a valid fs-type in order to continue. See `man parted` for valid fs-type's.") + continue + break + + unlocked_device.format(partition['filesystem']['format']) elif partition.get('format', False): partition['device_instance'].format(partition['filesystem']['format']) -- cgit v1.2.3-70-g09d2 From 51f2eca60e9d77a2675ed8828c2fdadfd065b7a2 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sat, 3 Jul 2021 14:27:49 +0200 Subject: Saving partitioning layout in a layout file (JSON format) --- archinstall/lib/disk.py | 6 +----- examples/guided.py | 4 ++++ 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 3bc2fa0f..e804e0eb 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -243,11 +243,7 @@ class BlockDevice: json() has precedence over __dump__, so this is a way to give less/partial information for user readability. """ - return { - 'path': self.path, - 'size': self.info['size'] if 'size' in self.info else '', - 'model': self.info['model'] if 'model' in self.info else '' - } + return self.path def __dump__(self): return { diff --git a/examples/guided.py b/examples/guided.py index 0d409657..206a776b 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -190,6 +190,10 @@ def perform_filesystem_operations(): archinstall.log(user_configuration, level=logging.INFO) with open("/var/log/archinstall/user_configuration.json", "w") as config_file: config_file.write(user_configuration) + user_disk_layout = json.dumps(archinstall.storage['disk_layouts'], indent=4, sort_keys=True, cls=archinstall.JSON) + archinstall.log(user_disk_layout, level=logging.INFO) + with open("/var/log/archinstall/user_disk_layout.json", "w") as disk_layout_file: + disk_layout_file.write(user_disk_layout) print() if archinstall.arguments.get('dry_run'): -- cgit v1.2.3-70-g09d2 From 57bad26553166d6c1bfa81576089a29d56c970a7 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sat, 3 Jul 2021 15:10:23 +0200 Subject: Added options to mark/unmark partitions for formatting (useful when re-using partitions, and fine tune which data to save and which to wipe). Setting a desired filesystem for a partition (both new ones and the ones being re-used). --- archinstall/lib/disk.py | 2 +- archinstall/lib/user_interaction.py | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index e804e0eb..d6f6ebde 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -788,7 +788,7 @@ class Filesystem: else: raise ValueError(f"{self}.load_layout() doesn't know how to continue without a new partition definition or a UUID ({partition.get('PARTUUID')}) on the device ({self.blockdevice.get_partition(uuid=partition_uuid)}).") - if partition.get('filesystem', {}).get('format', None): + if partition.get('filesystem', {}).get('format', False): if partition.get('encrypted', False): if not partition.get('password'): if storage['arguments'] == 'silent': diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 91456626..9f4ddc6d 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -614,8 +614,10 @@ def manage_new_and_existing_partitions(block_device :BlockDevice) -> dict: "Delete a partition" if len(block_device_struct) else "", "Clear/Delete all partitions" if len(block_device_struct) else "", "Assign mount-point for a partition" if len(block_device_struct) else "", + "Mark/Unmark a partition to be formatted (wipes data)" if len(block_device_struct) else "", "Mark/Unmark a partition as encrypted" if len(block_device_struct) else "", - "Mark/Unmark a partition as bootable (automatic for /boot)" if len(block_device_struct) else "" + "Mark/Unmark a partition as bootable (automatic for /boot)" if len(block_device_struct) else "", + "Set desired filesystem for a partition" if len(block_device_struct) else "", ] # Print current partition layout: @@ -697,6 +699,11 @@ def manage_new_and_existing_partitions(block_device :BlockDevice) -> dict: else: del(block_device_struct["partitions"][block_device_struct["partitions"].index(partition)]['mountpoint']) + elif task == "Mark/Unmark a partition to be formatted (wipes data)": + if (partition := generic_select(block_device_struct["partitions"], 'Select which partition to mask for formatting: ', options_output=False)): + # Negate the current encryption marking + block_device_struct["partitions"][block_device_struct["partitions"].index(partition)]['format'] = not block_device_struct["partitions"][block_device_struct["partitions"].index(partition)].get('format', False) + elif task == "Mark/Unmark a partition as encrypted": if (partition := generic_select(block_device_struct["partitions"], 'Select which partition to mark as encrypted: ', options_output=False)): # Negate the current encryption marking @@ -706,6 +713,20 @@ def manage_new_and_existing_partitions(block_device :BlockDevice) -> dict: if (partition := generic_select(block_device_struct["partitions"], 'Select which partition to mark as bootable: ', options_output=False)): block_device_struct["partitions"][block_device_struct["partitions"].index(partition)]['boot'] = not block_device_struct["partitions"][block_device_struct["partitions"].index(partition)].get('boot', False) + elif task == "Set desired filesystem for a partition": + if (partition := generic_select(block_device_struct["partitions"], 'Select which partition to set a filesystem on: ', options_output=False)): + if not block_device_struct["partitions"][block_device_struct["partitions"].index(partition)].get('filesystem', None): + block_device_struct["partitions"][block_device_struct["partitions"].index(partition)]['filesystem'] = {} + + while True: + fstype = input("Enter a desired filesystem type for the partition: ").strip() + if not valid_fs_type(fstype): + log(f"Desired filesystem {fstype} is not a valid filesystem.", level=logging.ERROR, fg="red") + continue + break + + block_device_struct["partitions"][block_device_struct["partitions"].index(partition)]['filesystem']['format'] = fstype + return block_device_struct def select_individual_blockdevice_usage(block_devices :list): -- cgit v1.2.3-70-g09d2 From f2b0fcc6524226c99e38edd3fa03a3a68af33738 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 4 Jul 2021 15:15:07 +0200 Subject: Added a filesystem check when marking for formatting, this should ensure that encrypted volumes get a proper filesystem without having to go through an extra step of selecting filesystem. --- archinstall/lib/disk.py | 1 + archinstall/lib/user_interaction.py | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index d6f6ebde..99e6da19 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -395,6 +395,7 @@ class BlockDevice: for partition in self: if partition.uuid == uuid: return partition + print('Returning False on get_partition()') class Partition: diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 9f4ddc6d..197666a4 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -701,7 +701,23 @@ def manage_new_and_existing_partitions(block_device :BlockDevice) -> dict: elif task == "Mark/Unmark a partition to be formatted (wipes data)": if (partition := generic_select(block_device_struct["partitions"], 'Select which partition to mask for formatting: ', options_output=False)): - # Negate the current encryption marking + # If we mark a partition for formatting, but the format is CRYPTO LUKS, there's no point in formatting it really + # without asking the user which inner-filesystem they want to use. Since the flag 'encrypted' = True is already set, + # it's safe to change the filesystem for this partition. + if block_device_struct["partitions"][block_device_struct["partitions"].index(partition)].get('filesystem', {}).get('format', 'crypto_LUKS') == 'crypto_LUKS': + if not block_device_struct["partitions"][block_device_struct["partitions"].index(partition)].get('filesystem', None): + block_device_struct["partitions"][block_device_struct["partitions"].index(partition)]['filesystem'] = {} + + while True: + fstype = input("Enter a desired filesystem type for the partition: ").strip() + if not valid_fs_type(fstype): + log(f"Desired filesystem {fstype} is not a valid filesystem.", level=logging.ERROR, fg="red") + continue + break + + block_device_struct["partitions"][block_device_struct["partitions"].index(partition)]['filesystem']['format'] = fstype + + # Negate the current wipe marking block_device_struct["partitions"][block_device_struct["partitions"].index(partition)]['format'] = not block_device_struct["partitions"][block_device_struct["partitions"].index(partition)].get('format', False) elif task == "Mark/Unmark a partition as encrypted": -- cgit v1.2.3-70-g09d2 From a53ee624ef31afe54d6a8b491d9ea204d7ee3cb3 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 4 Jul 2021 15:15:21 +0200 Subject: Removed debugging --- archinstall/lib/disk.py | 1 - 1 file changed, 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 99e6da19..d6f6ebde 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -395,7 +395,6 @@ class BlockDevice: for partition in self: if partition.uuid == uuid: return partition - print('Returning False on get_partition()') class Partition: -- cgit v1.2.3-70-g09d2 From 59c366da3574bf211dcbc4a1542991232233cbdf Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Tue, 17 Aug 2021 20:23:20 +0200 Subject: Fixed a prompt error in one of the parted calls. Also started on a more reliable size-conversion that isn't limited to Gigabytes in free_space(). --- archinstall/lib/disk.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index d6f6ebde..2adb4ee5 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -337,14 +337,24 @@ class BlockDevice: for partition in json.loads(SysCommand(f'lsblk -J -o+UUID {self.path}').decode('UTF-8'))['blockdevices']: return partition.get('uuid', None) + def convert_size_to_gb(self, size): + units = { + 'P' : lambda s : float(s) * 2048, + 'T' : lambda s : float(s) * 1024, + 'G' : lambda s : float(s), + 'M' : lambda s : float(s) / 1024, + 'K' : lambda s : float(s) / 2048, + 'B' : lambda s : float(s) / 3072, + } + unit = size[-1] + return float(units.get(unit, lambda s : None)(size[:-1])) + @property def size(self): output = json.loads(SysCommand(f"lsblk --json -o+SIZE {self.path}").decode('UTF-8')) for device in output['blockdevices']: - assert device['size'][-1] == 'G' # Make sure we're counting in Gigabytes, otherwise the next logic fails. - - return float(device['size'][:-1]) + return self.convert_size_to_gb(device['size']) @property def bus_type(self): @@ -362,7 +372,11 @@ class BlockDevice: @property def free_space(self): - for line in SysCommand(f"parted --machine {self.path} print free"): + # NOTE: parted -s will default to `cancel` on prompt, skipping any partition + # that is "outside" the disk. in /dev/sr0 this is usually the case with Archiso, + # so the free will ignore the ESP partition and just give the "free" space. + # Doesn't harm us, but worth noting in case something weird happens. + for line in SysCommand(f"parted -s --machine {self.path} print free"): if 'free' in (free_space := line.decode('UTF-8')): _, start, end, size, *_ = free_space.strip('\r\n;').split(':') yield (start, end, size) @@ -912,6 +926,7 @@ def all_disks(*args, **kwargs): continue drives[drive['path']] = BlockDevice(drive['path'], drive) + return drives -- cgit v1.2.3-70-g09d2 From 81e52bc3fffa4bef38af0cd06888066425c4bdb5 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 18 Aug 2021 08:17:50 +0200 Subject: Reworking logic that selects / and /home for multi-disk configurations. Also added some more debugging --- archinstall/lib/disk.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 2adb4ee5..d4cabc67 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -75,6 +75,20 @@ def sort_block_devices_based_on_performance(block_devices): return result +def select_largest_device(devices, gigabytes, filter_out=None): + if not filter_out: + filter_out = [] + + copy_devices = [*devices] + for filter_device in filter_out: + if filter_device in copy_devices: + copy_devices.pop(copy_devices.index(filter_device)) + + if not len(copy_devices): + return None + + return max(copy_devices, key=(lambda device : abs(device.size - gigabytes))) + def select_disk_larger_than_or_close_to(devices, gigabytes, filter_out=None): if not filter_out: filter_out = [] @@ -87,7 +101,7 @@ def select_disk_larger_than_or_close_to(devices, gigabytes, filter_out=None): if not len(copy_devices): return None - return min(copy_devices, key=(lambda device : abs(device.size - 40))) + return min(copy_devices, key=(lambda device : abs(device.size - gigabytes))) def suggest_single_disk_layout(block_device): MIN_SIZE_TO_ALLOW_HOME_PART = 40 # Gb @@ -144,12 +158,14 @@ def suggest_single_disk_layout(block_device): def suggest_multi_disk_layout(block_devices): MIN_SIZE_TO_ALLOW_HOME_PART = 40 # Gb + ARCH_LINUX_INSTALLED_SIZE = 20 # Gb, rough estimate taking in to account user desktops etc. TODO: Catch user packages to detect size? block_devices = sort_block_devices_based_on_performance(block_devices).keys() - root_device = select_disk_larger_than_or_close_to(block_devices, gigabytes=MIN_SIZE_TO_ALLOW_HOME_PART) - home_device = select_disk_larger_than_or_close_to(block_devices, gigabytes=MIN_SIZE_TO_ALLOW_HOME_PART, filter_out=[root_device]) + home_device = select_largest_device(block_devices, gigabytes=MIN_SIZE_TO_ALLOW_HOME_PART) + root_device = select_disk_larger_than_or_close_to(block_devices, gigabytes=ARCH_LINUX_INSTALLED_SIZE, filter_out=[home_device]) + log(f"Suggesting multi-disk-layout using {len(block_devices)} disks, where {root_device} will be /root and {home_device} will be /home", level=logging.DEBUG) layout = { root_device : { @@ -792,12 +808,14 @@ class Filesystem: for partition in layout.get('partitions', []): # We don't want to re-add an existing partition (those containing a UUID already) if partition.get('format', False) and not partition.get('PARTUUID', None): + print("Adding partition....") partition['device_instance'] = self.add_partition(partition.get('type', 'primary'), start=partition.get('start', '1MiB'), # TODO: Revisit sane block starts (4MB for memorycards for instance) end=partition.get('size', '100%'), partition_format=partition.get('filesystem', {}).get('format', 'btrfs')) elif (partition_uuid := partition.get('PARTUUID')) and (partition_instance := self.blockdevice.get_partition(uuid=partition_uuid)): + print("Re-using partition_instance:", partition_instance) partition['device_instance'] = partition_instance else: raise ValueError(f"{self}.load_layout() doesn't know how to continue without a new partition definition or a UUID ({partition.get('PARTUUID')}) on the device ({self.blockdevice.get_partition(uuid=partition_uuid)}).") @@ -830,6 +848,7 @@ class Filesystem: unlocked_device.format(partition['filesystem']['format']) elif partition.get('format', False): + print(partition) partition['device_instance'].format(partition['filesystem']['format']) def find_partition(self, mountpoint): -- cgit v1.2.3-70-g09d2 From 1814a19d6adb3e465df2a276cc87f85974f613c3 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sat, 4 Sep 2021 16:39:15 +0200 Subject: Fixed filtering if largest disk selection --- archinstall/lib/disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index d4cabc67..4a5b2f4c 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -87,7 +87,7 @@ def select_largest_device(devices, gigabytes, filter_out=None): if not len(copy_devices): return None - return max(copy_devices, key=(lambda device : abs(device.size - gigabytes))) + return max(copy_devices, key=(lambda device : device.size if device.size > gigabytes else 0)) def select_disk_larger_than_or_close_to(devices, gigabytes, filter_out=None): if not filter_out: -- cgit v1.2.3-70-g09d2 From 429006fe4b2775d4f28cfaa4ad86028fac56dccd Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sat, 4 Sep 2021 17:03:52 +0200 Subject: Simplified lambda and made it filter out non relevant disks for the selection process. --- archinstall/lib/disk.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 4a5b2f4c..bac46c3a 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -75,6 +75,11 @@ def sort_block_devices_based_on_performance(block_devices): return result +def filter_disks_below_size_in_gb(devices, gigabytes): + for disk in devices: + if disk.size >= gigabytes: + yield disk + def select_largest_device(devices, gigabytes, filter_out=None): if not filter_out: filter_out = [] @@ -84,10 +89,12 @@ def select_largest_device(devices, gigabytes, filter_out=None): if filter_device in copy_devices: copy_devices.pop(copy_devices.index(filter_device)) + copy_devices = list(filter_disks_below_size_in_gb(copy_devices, gigabytes)) + if not len(copy_devices): return None - return max(copy_devices, key=(lambda device : device.size if device.size > gigabytes else 0)) + return max(copy_devices, key=(lambda device : device.size)) def select_disk_larger_than_or_close_to(devices, gigabytes, filter_out=None): if not filter_out: -- cgit v1.2.3-70-g09d2 From 278ded8e74fda4451c42a800c75400a24e003309 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sat, 4 Sep 2021 19:09:16 +0200 Subject: Added a PARTUUID -> parted index, this in order to properly set the boot flag on the correct partition. Perhaps there's a smarter way. I suspect parted can operate on a given partition ID, but haven't found the docs for it yet. --- archinstall/lib/disk.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index bac46c3a..325a7ca9 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -799,6 +799,14 @@ class Filesystem: SysCommand('sync') return True + def partuuid_to_index(self, uuid): + output = json.loads(SysCommand(f"lsblk --json -o+PARTUUID {self.blockdevice.device}").decode('UTF-8')) + + for device in output['blockdevices']: + for index, partition in enumerate(device['children']): + if partition['partuuid'].lower() == uuid: + return index + def load_layout(self, layout :dict): from .luks import luks2 @@ -820,6 +828,8 @@ class Filesystem: start=partition.get('start', '1MiB'), # TODO: Revisit sane block starts (4MB for memorycards for instance) end=partition.get('size', '100%'), partition_format=partition.get('filesystem', {}).get('format', 'btrfs')) + # TODO: device_instance some times become None + # print('Device instance:', partition['device_instance']) elif (partition_uuid := partition.get('PARTUUID')) and (partition_instance := self.blockdevice.get_partition(uuid=partition_uuid)): print("Re-using partition_instance:", partition_instance) @@ -855,9 +865,11 @@ class Filesystem: unlocked_device.format(partition['filesystem']['format']) elif partition.get('format', False): - print(partition) partition['device_instance'].format(partition['filesystem']['format']) + if partition.get('boot', False): + self.set(self.partuuid_to_index(partition['device_instance'].uuid), 'boot on') + def find_partition(self, mountpoint): for partition in self.blockdevice: if partition.target_mountpoint == mountpoint or partition.mountpoint == mountpoint: @@ -865,7 +877,7 @@ class Filesystem: def raw_parted(self, string: str): if (cmd_handle := SysCommand(f'/usr/bin/parted -s {string}')).exit_code != 0: - log(f"Could not generate partition: {cmd_handle}", level=logging.ERROR, fg="red") + log(f"Parted ended with a bad exit code: {cmd_handle}", level=logging.ERROR, fg="red") return cmd_handle def parted(self, string: str): @@ -911,6 +923,7 @@ class Filesystem: return self.parted(f'{self.blockdevice.device} name {partition + 1} "{name}"') == 0 def set(self, partition: int, string: str): + log(f"Setting {string} on (parted) partition index {partition+1}", level=logging.INFO) return self.parted(f'{self.blockdevice.device} set {partition + 1} {string}') == 0 def parted_mklabel(self, device: str, disk_label: str): -- cgit v1.2.3-70-g09d2 From e88ac430f2b0cced961e91325297117120f6bfdb Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sat, 4 Sep 2021 20:54:25 +0200 Subject: Fixed edge case where size of disks could be exactly 40GB and a /home would never be created on single devices. --- archinstall/lib/disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'archinstall/lib') diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 325a7ca9..e8deb619 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -146,7 +146,7 @@ def suggest_single_disk_layout(block_device): } }) - if block_device.size > MIN_SIZE_TO_ALLOW_HOME_PART: + if block_device.size >= MIN_SIZE_TO_ALLOW_HOME_PART: layout[block_device]['partitions'].append({ # Home "type" : "primary", -- cgit v1.2.3-70-g09d2