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 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) (limited to 'archinstall/lib/user_interaction.py') 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. -- 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/user_interaction.py') 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/user_interaction.py') 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/user_interaction.py') 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/user_interaction.py') 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/user_interaction.py') 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/user_interaction.py') 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 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/user_interaction.py') 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/user_interaction.py') 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 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/user_interaction.py') 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/user_interaction.py') 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 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/user_interaction.py') 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/user_interaction.py') 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/user_interaction.py') 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 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/user_interaction.py') 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 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/user_interaction.py') 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/user_interaction.py') 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/user_interaction.py') 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/user_interaction.py') 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/user_interaction.py') 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/user_interaction.py') 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/user_interaction.py') 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 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/user_interaction.py') 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/user_interaction.py') 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 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/user_interaction.py') 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/user_interaction.py') 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