diff --git a/iridium_installer/backend/os_install.py b/iridium_installer/backend/os_install.py index e114618..5f9d204 100644 --- a/iridium_installer/backend/os_install.py +++ b/iridium_installer/backend/os_install.py @@ -1,6 +1,7 @@ import subprocess import logging import os +from contextlib import contextmanager logger = logging.getLogger(__name__) @@ -13,6 +14,30 @@ def run_command(cmd, check=True): logger.error(f"Command failed: {e.stderr}") raise +@contextmanager +def mount_pseudo_fs(mount_root): + """ + Context manager to bind mount /dev, /proc, and /sys into mount_root. + """ + logger.info(f"Mounting pseudo-filesystems to {mount_root}...") + mounts = ["dev", "proc", "sys"] + mounted_paths = [] + + try: + for fs in mounts: + target = os.path.join(mount_root, fs) + os.makedirs(target, exist_ok=True) + run_command(["mount", "--bind", f"/{fs}", target]) + mounted_paths.append(target) + yield + finally: + logger.info(f"Unmounting pseudo-filesystems from {mount_root}...") + for path in reversed(mounted_paths): + try: + run_command(["umount", "-l", path]) + except Exception as e: + logger.warning(f"Failed to unmount {path}: {e}") + def install_minimal_os(mount_root, releasever="41"): """ Installs minimal Fedora packages to mount_root. @@ -34,9 +59,6 @@ def install_minimal_os(mount_root, releasever="41"): "vim-minimal" ] - # Fedora 41 (or whatever is current) - # We use --releasever to ensure dnf knows which version to fetch - # We use --use-host-config because the installroot is initially empty and has no repo config cmd = [ "dnf", "install", "-y", f"--installroot={mount_root}", @@ -46,7 +68,9 @@ def install_minimal_os(mount_root, releasever="41"): "--nodocs" ] + packages - run_command(cmd) + with mount_pseudo_fs(mount_root): + run_command(cmd) + logger.info("Base system installation complete.") def configure_system(mount_root, partition_info): @@ -56,7 +80,6 @@ def configure_system(mount_root, partition_info): logger.info("Configuring system...") # 1. Generate fstab - # We can get UUIDs using blkid def get_uuid(dev): res = run_command(["blkid", "-s", "UUID", "-o", "value", dev]) return res.stdout.strip() @@ -70,30 +93,14 @@ UUID={root_uuid} / ext4 defaults 1 1 UUID={efi_uuid} /boot/efi vfat defaults 0 2 UUID={swap_uuid} none swap defaults 0 0 """ + os.makedirs(os.path.join(mount_root, "etc"), exist_ok=True) with open(os.path.join(mount_root, "etc/fstab"), "w") as f: f.write(fstab_content) # 2. Configure GRUB - # This is tricky because we need to chroot or use --boot-directory - # Simplest for "barely bootable shell" is to try running grub2-mkconfig inside chroot - - # Need to bind mount dev, proc, sys for chroot - for dev in ["dev", "proc", "sys"]: - target = os.path.join(mount_root, dev) - run_command(["mount", "--bind", f"/{dev}", target]) - - try: - # grub2-mkconfig -o /boot/grub2/grub.cfg (or wherever Fedora puts it) - # On UEFI Fedora: /boot/efi/EFI/fedora/grub.cfg usually just redirects to /boot/grub2/grub.cfg - # Let's just do both or standard one. + with mount_pseudo_fs(mount_root): + # grub2-mkconfig -o /boot/grub2/grub.cfg chroot_cmd = ["chroot", mount_root, "grub2-mkconfig", "-o", "/boot/grub2/grub.cfg"] run_command(chroot_cmd) - - # Also need to make sure EFI boot entry is there, but shim/grub2 packages usually handle it in %post? - # Maybe not in --installroot. - finally: - # Unmount binds - for dev in ["sys", "proc", "dev"]: - run_command(["umount", os.path.join(mount_root, dev)]) logger.info("System configuration complete.") diff --git a/iridium_installer/ui/pages/partitioning.py b/iridium_installer/ui/pages/partitioning.py index 1f16044..a0e3a87 100644 --- a/iridium_installer/ui/pages/partitioning.py +++ b/iridium_installer/ui/pages/partitioning.py @@ -504,11 +504,6 @@ class PartitioningPage(Adw.Bin): 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) - # Name entry - name_entry = Gtk.Entry(text="Linux filesystem") - box.append(Gtk.Label(label="Partition Name:")) - box.append(name_entry) - # Size entry size_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12) size_entry = Gtk.Entry(text=str(int(data["bytes"] / (1024*1024)))) @@ -530,18 +525,15 @@ class PartitioningPage(Adw.Bin): box.append(Gtk.Label(label="Filesystem:")) box.append(fs_dropdown) - # Auto-update FS and Name based on Type + # Auto-update FS based on Type def on_type_changed(dropdown, pspec): selected_type = type_names[dropdown.get_selected()] if selected_type == "EFI System": fs_dropdown.set_selected(1) # fat32 - name_entry.set_text("EFI System") elif selected_type == "Swap": fs_dropdown.set_selected(2) # swap - name_entry.set_text("Swap") else: fs_dropdown.set_selected(0) # ext4 - name_entry.set_text("Linux filesystem") type_dropdown.connect("notify::selected", on_type_changed) @@ -551,18 +543,19 @@ class PartitioningPage(Adw.Bin): def on_create(b): from ...backend.disk import create_partition - name = name_entry.get_text() selected_type = type_names[type_dropdown.get_selected()] - if selected_type == "EFI System" and name == "Linux filesystem": - error_label.set_text("Can't set partition name to Linux filesystem") - return + # Default name based on type + if selected_type == "EFI System": + name = "EFI System" + elif selected_type == "Swap": + name = "Swap" + else: + name = "Linux filesystem" size_text = size_entry.get_text() try: size_mb = int(size_text) - # If they are using the max proposed size, use 0 to avoid alignment issues - # with sgdisk +sizeM logic exceeding disk boundaries. max_mb = int(data["bytes"] / (1024*1024)) if size_mb >= max_mb: size_mb = 0 diff --git a/iridium_installer/ui/pages/summary.py b/iridium_installer/ui/pages/summary.py index 4adc301..231b578 100644 --- a/iridium_installer/ui/pages/summary.py +++ b/iridium_installer/ui/pages/summary.py @@ -27,6 +27,15 @@ class SummaryPage(Adw.Bin): content_box.set_spacing(24) clamp.set_child(content_box) + # Installation Settings + self.install_group = Adw.PreferencesGroup() + self.install_group.set_title("Installation Settings") + content_box.append(self.install_group) + + self.mount_row = Adw.ActionRow() + self.mount_row.set_title("Installation Root (Mount Point)") + self.install_group.add(self.mount_row) + # Storage and partitioning self.storage_group = Adw.PreferencesGroup() self.storage_group.set_title("Storage & Partitioning") @@ -66,7 +75,10 @@ class SummaryPage(Adw.Bin): self.modules_row.set_title("Additional Modules") self.software_group.add(self.modules_row) - def update_summary(self, disk_info, mode, partitions, user_info, modules): + def update_summary(self, disk_info, mode, partitions, user_info, modules, mount_root): + # Update Mount Root + self.mount_row.set_subtitle(mount_root) + # Update Disk self.disk_row.set_subtitle(str(disk_info)) diff --git a/iridium_installer/ui/pages/welcome.py b/iridium_installer/ui/pages/welcome.py index 906b376..11e7e88 100644 --- a/iridium_installer/ui/pages/welcome.py +++ b/iridium_installer/ui/pages/welcome.py @@ -50,6 +50,19 @@ class WelcomePage(Adw.Bin): dropdown = Gtk.DropDown.new_from_strings(languages) box.append(dropdown) + # Mount Point Entry + mount_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) + mount_box.set_margin_top(24) + mount_label = Gtk.Label(label="Installation Root (Mount Point):") + mount_label.set_halign(Gtk.Align.START) + self.mount_entry = Gtk.Entry(text="/mnt") + mount_box.append(mount_label) + mount_box.append(self.mount_entry) + box.append(mount_box) + page.set_child(box) self.set_child(page) + + def get_mount_root(self): + return self.mount_entry.get_text() diff --git a/iridium_installer/ui/window.py b/iridium_installer/ui/window.py index efbbb2f..42d65ba 100644 --- a/iridium_installer/ui/window.py +++ b/iridium_installer/ui/window.py @@ -145,100 +145,6 @@ class InstallerWindow(Adw.ApplicationWindow): self.stack.set_visible_child_name(self.page_ids[self.current_page_index]) self.update_buttons() - def on_next_clicked(self, button): - # Logic before transition - current_page_name = self.page_ids[self.current_page_index] - - if current_page_name == "storage": - selected_disk = self.storage_page.get_selected_disk() - self.partitioning_page.load_partitions(selected_disk) - - next_index = self.current_page_index + 1 - - if current_page_name == "install_mode": - mode = self.install_mode_page.get_mode() - if mode == "automatic": - # Skip partitioning page - next_index = self.page_ids.index("modules") - else: - # Go to partitioning page - next_index = self.page_ids.index("partitioning") - - if current_page_name == "user": - # Prepare summary instead of installing immediately - disk = self.storage_page.get_selected_disk() - mode = self.install_mode_page.get_mode() - modules = self.modules_page.get_modules() - user_info = self.user_page.get_user_info() - - partitions_config = {} - if mode == "manual": - partitions_config = self.partitioning_page.get_config() - elif mode == "automatic": - partitions = calculate_auto_partitions(disk) - partitions_config = {"partitions": partitions} - - # Update summary page - self.summary_page.update_summary( - disk_info=disk, - mode=mode, - partitions=partitions_config, - user_info=user_info, - modules=modules, - ) - # Proceed to summary page (which is next_index after user) - - if current_page_name == "summary": - # THIS IS THE REAL INSTALL TRIGGER - print("Install process triggered!") - disk = self.storage_page.get_selected_disk() - mode = self.install_mode_page.get_mode() - modules = self.modules_page.get_modules() - user_info = self.user_page.get_user_info() - - if self.mock_mode: - print("!!! MOCK MODE ENABLED - NO CHANGES WILL BE MADE !!!") - print(f"Target Disk: {disk}") - print(f"Mode: {mode}") - print(f"Modules: {modules}") - print(f"User: {user_info}") - print("Simulation complete.") - # Show success in UI even in mock - self.show_finish_page("Mock Installation Complete!") - else: - from ..backend.disk import auto_partition_disk, mount_partitions - from ..backend.os_install import install_minimal_os, configure_system - - # Simple progress UI transition - self.show_progress_page("Installing Iridium OS...") - - # We should really run this in a thread to keep UI responsive, - # but for "barely bootable" requirement and simplicity: - try: - # Step 1: Partitioning - print("Step 1: Partitioning...") - parts = auto_partition_disk(disk) - - # Step 2: Mounting - print("Step 2: Mounting...") - mount_root = "/mnt" - mount_partitions(parts, mount_root) - - # Step 3: OS Installation - print("Step 3: OS Installation...") - install_minimal_os(mount_root) - - # Step 4: Configure - print("Step 4: Configuration...") - configure_system(mount_root, parts) - - self.show_finish_page("Installation Successful!") - except Exception as e: - print(f"Installation failed: {e}") - self.show_finish_page(f"Installation Failed: {e}", success=False) - - return - def show_progress_page(self, message): prog_page = Adw.StatusPage() prog_page.set_title(message) @@ -282,10 +188,6 @@ class InstallerWindow(Adw.ApplicationWindow): name = "finish_install" self.stack.add_named(finish_page, name) self.stack.set_visible_child_name(name) - - # Make bottom bar visible but maybe just with a close button if we didn't have the child btn - # self.bottom_bar.set_visible(True) - # self.next_button.set_label("Finish") def on_next_clicked(self, button): # Logic before transition @@ -312,6 +214,7 @@ class InstallerWindow(Adw.ApplicationWindow): mode = self.install_mode_page.get_mode() modules = self.modules_page.get_modules() user_info = self.user_page.get_user_info() + mount_root = self.welcome_page.get_mount_root() partitions_config = {} if mode == "manual": @@ -327,6 +230,7 @@ class InstallerWindow(Adw.ApplicationWindow): partitions=partitions_config, user_info=user_info, modules=modules, + mount_root=mount_root, ) # Proceed to summary page (which is next_index after user) @@ -337,6 +241,7 @@ class InstallerWindow(Adw.ApplicationWindow): mode = self.install_mode_page.get_mode() modules = self.modules_page.get_modules() user_info = self.user_page.get_user_info() + mount_root = self.welcome_page.get_mount_root() if self.mock_mode: print("!!! MOCK MODE ENABLED - NO CHANGES WILL BE MADE !!!") @@ -344,6 +249,7 @@ class InstallerWindow(Adw.ApplicationWindow): print(f"Mode: {mode}") print(f"Modules: {modules}") print(f"User: {user_info}") + print(f"Mount Root: {mount_root}") print("Simulation complete.") # Show success in UI even in mock self.show_finish_page("Mock Installation Complete!") @@ -363,7 +269,6 @@ class InstallerWindow(Adw.ApplicationWindow): # Step 2: Mounting print("Step 2: Mounting...") - mount_root = "/mnt" mount_partitions(parts, mount_root) # Step 3: OS Installation @@ -384,4 +289,4 @@ class InstallerWindow(Adw.ApplicationWindow): if next_index < len(self.page_ids): self.current_page_index = next_index self.stack.set_visible_child_name(self.page_ids[self.current_page_index]) - self.update_buttons() + self.update_buttons() \ No newline at end of file