From 22e1fa8f626b488350079d9755bfa34e89819b8a Mon Sep 17 00:00:00 2001 From: N0VA Date: Tue, 3 Feb 2026 17:07:45 +0100 Subject: [PATCH] Implement manual partition management (create/delete/mountpoints) --- iridium_installer/backend/disk.py | 39 ++++++++ iridium_installer/ui/pages/partitioning.py | 106 ++++++++++++++++----- 2 files changed, 123 insertions(+), 22 deletions(-) diff --git a/iridium_installer/backend/disk.py b/iridium_installer/backend/disk.py index bd9ac8e..3167af6 100644 --- a/iridium_installer/backend/disk.py +++ b/iridium_installer/backend/disk.py @@ -99,3 +99,42 @@ def mount_partitions(partition_info, mount_root="/mnt"): run_command(["swapon", partition_info["swap"]]) logger.info(f"Partitions mounted at {mount_root}") + +def create_partition(disk_device, size_mb, type_code="8300", name="Linux filesystem"): + """ + Creates a new partition on the disk. + type_code: ef00 (EFI), 8200 (Swap), 8300 (Linux) + """ + # Find next available partition number + res = run_command(["sgdisk", "-p", disk_device]) + # Very basic parsing to find the next number + existing_nums = [] + for line in res.stdout.splitlines(): + parts = line.split() + if parts and parts[0].isdigit(): + existing_nums.append(int(parts[0])) + + next_num = 1 + while next_num in existing_nums: + next_num += 1 + + run_command([ + "sgdisk", + "-n", f"{next_num}:0:+{size_mb}M", + "-t", f"{next_num}:{type_code}", + "-c", f"{next_num}:{name}", + disk_device + ]) + run_command(["partprobe", disk_device]) + return next_num + +def delete_partition(disk_device, part_num): + """Deletes a partition by number.""" + run_command(["sgdisk", "-d", str(part_num), disk_device]) + run_command(["partprobe", disk_device]) + +def wipe_disk(disk_device): + """Zaps the disk and creates a new GPT table.""" + run_command(["sgdisk", "-Z", disk_device]) + run_command(["sgdisk", "-o", disk_device]) + run_command(["partprobe", disk_device]) diff --git a/iridium_installer/ui/pages/partitioning.py b/iridium_installer/ui/pages/partitioning.py index e0f8ab6..ef5d1e4 100644 --- a/iridium_installer/ui/pages/partitioning.py +++ b/iridium_installer/ui/pages/partitioning.py @@ -236,6 +236,7 @@ class PartitioningPage(Adw.Bin): # Initially empty until loaded with a specific disk def load_partitions(self, disk_device=None): + self.current_disk_path = disk_device target_disk = None try: @@ -428,46 +429,107 @@ class PartitioningPage(Adw.Bin): def show_context_menu(self, widget, x, y): data = widget.part_data + self.selected_disk_path = self.current_disk_path # Assumes we store this popover = Gtk.Popover() popover.set_parent(widget) menu_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - menu_box.set_spacing(0) popover.set_child(menu_box) - def add_menu_item(label, icon_name=None, destructive=False): + def add_menu_item(label, icon_name, callback, destructive=False): btn = Gtk.Button(label=label) btn.add_css_class("flat") - btn.set_halign(Gtk.Align.FILL) if destructive: btn.add_css_class("destructive-action") - - if icon_name: - content = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) - content.set_spacing(12) - icon = Gtk.Image.new_from_icon_name(icon_name) - lbl = Gtk.Label(label=label) - content.append(icon) - content.append(lbl) - btn.set_child(content) - + + content = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12) + icon = Gtk.Image.new_from_icon_name(icon_name) + lbl = Gtk.Label(label=label) + content.append(icon) + content.append(lbl) + btn.set_child(content) + + btn.connect("clicked", lambda b: [popover.popdown(), callback()]) menu_box.append(btn) - return btn if data.get("type") == "partition": - add_menu_item("Select Mount Point", "folder-open-symbolic") - add_menu_item("Format", "drive-harddisk-symbolic") - add_menu_item("Resize", "object-resize-symbolic") + add_menu_item("Select Mount Point", "folder-open-symbolic", + lambda: self.select_mount_point(data)) separator = Gtk.Separator() menu_box.append(separator) - btn_del = add_menu_item("Delete", "user-trash-symbolic", destructive=True) - btn_del.add_css_class("error") + add_menu_item("Delete", "user-trash-symbolic", + lambda: self.delete_part(data), destructive=True) elif data.get("type") == "empty": - add_menu_item("Create Partition", "list-add-symbolic") + add_menu_item("Create Partition", "list-add-symbolic", + lambda: self.create_part_dialog(data)) popover.popup() - def get_config(self): - return {"partitions": self.partitions} + def select_mount_point(self, data): + win = Adw.Window(title="Select Mount Point", modal=True, transient_for=self.get_root()) + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12, margin_top=24, margin_bottom=24, margin_start=24, margin_end=24) + win.set_content(box) + + options = ["/", "/boot/efi", "[SWAP]", "None"] + dropdown = Gtk.DropDown.new_from_strings(options) + box.append(Gtk.Label(label=f"Mount point for {data['name']}:")) + box.append(dropdown) + + def on_apply(b): + selected = options[dropdown.get_selected()] + data["mount_point"] = selected if selected != "None" else "" + win.close() + self.refresh_bar() + + btn = Gtk.Button(label="Apply", add_css_class="suggested-action") + btn.connect("clicked", on_apply) + box.append(btn) + win.present() + + def delete_part(self, data): + from ...backend.disk import delete_partition + # Extract partition number from name (e.g., nvme0n1p3 -> 3) + import re + match = re.search(r"(\d+)$", data["name"]) + if match: + part_num = int(match.group(1)) + delete_partition(self.current_disk_path, part_num) + self.load_partitions(self.current_disk_path) + + def create_part_dialog(self, data): + win = Adw.Window(title="Create Partition", modal=True, transient_for=self.get_root()) + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12, margin_top=24, margin_bottom=24, margin_start=24, margin_end=24) + win.set_content(box) + + # Size entry + size_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12) + size_entry = Gtk.Entry(text=str(int(data["bytes"] / (1024*1024)))) + size_box.append(size_entry) + size_box.append(Gtk.Label(label="MB")) + box.append(Gtk.Label(label="Size:")) + box.append(size_box) + + # Type dropdown + types = {"Root (Linux)": "8300", "EFI System": "ef00", "Swap": "8200"} + type_names = list(types.keys()) + type_dropdown = Gtk.DropDown.new_from_strings(type_names) + box.append(Gtk.Label(label="Partition Type:")) + box.append(type_dropdown) + + def on_create(b): + from ...backend.disk import create_partition + size_mb = int(size_entry.get_text()) + type_code = types[type_names[type_dropdown.get_selected()]] + create_partition(self.current_disk_path, size_mb, type_code) + win.close() + self.load_partitions(self.current_disk_path) + + btn = Gtk.Button(label="Create", add_css_class="suggested-action") + btn.connect("clicked", on_create) + box.append(btn) + win.present() + + def load_partitions(self, disk_device=None): + self.current_disk_path = disk_device