diff --git a/iridium_installer/backend/disk.py b/iridium_installer/backend/disk.py index a0e9575..0ab370e 100644 --- a/iridium_installer/backend/disk.py +++ b/iridium_installer/backend/disk.py @@ -128,123 +128,97 @@ def ensure_unmounted(disk_device): logger.warning(f"Error during pre-partition unmount: {e}") +def is_uefi(): + """Checks if the system is booted in UEFI mode.""" + return os.path.exists("/sys/firmware/efi") + + def auto_partition_disk(disk_device): """ - Automatically partitions the disk: - 1. EFI System Partition (2GB standard, 1GB small) - 2. Root (Remaining) - 3. Swap (RAM + 2GB, or 0 if small) - - Layout: P1=EFI, P3=Swap (End), P2=Root (Middle) + Automatically partitions the disk based on boot mode: + UEFI (GPT): ESP (1GB), Root (Remaining), Swap (RAM+2GB) + BIOS (MBR): Root (Remaining, Bootable), Swap (RAM+2GB) """ - logger.info(f"Starting auto-partitioning on {disk_device}") - log_disk_operation("PARTITION", "start", f"Disk: {disk_device}") + uefi = is_uefi() + logger.info(f"Starting auto-partitioning on {disk_device} (UEFI: {uefi})") + log_disk_operation("PARTITION", "start", f"Disk: {disk_device}, UEFI: {uefi}") - # 0. Ensure unmounted ensure_unmounted(disk_device) - # Calculate sizes disk_size = get_disk_size(disk_device) ram_size = get_total_memory() - # Defaults - efi_mb = 2048 + # Calculate sizes swap_mb = int((ram_size / (1024 * 1024)) + 2048) - min_root_mb = 10240 # 10GB - - total_required_mb = efi_mb + swap_mb + min_root_mb + # Cap swap at 16GB for sanity on large RAM systems unless disk is huge + if disk_size < 100 * 1024**3: # < 100GB + swap_mb = min(swap_mb, 4096) + disk_mb = disk_size / (1024 * 1024) + use_swap = disk_mb > (10240 + swap_mb) # Only use swap if we have at least 10GB for root - use_swap = True + if uefi: + # GPT Layout + logger.info("Using GPT layout for UEFI") + run_command(["sgdisk", "-Z", disk_device]) + run_command(["sgdisk", "-o", disk_device]) + + # 1. ESP (1GB) + run_command(["sgdisk", "-n", "1:0:+1024M", "-t", "1:ef00", "-c", "1:EFI System", disk_device]) + + # 2. Swap (if enabled) + if use_swap: + run_command(["sgdisk", "-n", f"3:-{swap_mb}M:0", "-t", "3:8200", "-c", "3:Swap", disk_device]) + + # 3. Root + run_command(["sgdisk", "-n", "2:0:0", "-t", "2:8300", "-c", "2:Root", disk_device]) + else: + # MBR Layout for BIOS + logger.info("Using MBR layout for BIOS") + # Wipe disk using dd to be sure MBR is cleared + run_command(["dd", "if=/dev/zero", f"of={disk_device}", "bs=512", "count=100"]) + + # Use parted for MBR + run_command(["parted", "-s", disk_device, "mklabel", "msdos"]) + + if use_swap: + # Root first, then swap at end + root_end = int(disk_mb - swap_mb) + run_command(["parted", "-s", disk_device, "mkpart", "primary", "ext4", "1MiB", f"{root_end}MiB"]) + run_command(["parted", "-s", disk_device, "mkpart", "primary", "linux-swap", f"{root_end}MiB", "100%"]) + run_command(["parted", "-s", disk_device, "set", "1", "boot", "on"]) + else: + run_command(["parted", "-s", disk_device, "mkpart", "primary", "ext4", "1MiB", "100%"]) + run_command(["parted", "-s", disk_device, "set", "1", "boot", "on"]) - if disk_mb < total_required_mb: - logger.warning("Disk too small for standard layout. Adjusting...") - efi_mb = 1024 - use_swap = False - - # Check minimal viability - if disk_mb < (efi_mb + min_root_mb): - raise Exception("Disk too small for installation (Need ~11GB)") - - # 1. Zap the disk (destroy all data) - run_command(["sgdisk", "-Z", disk_device]) - - # 2. Create new GPT table - run_command(["sgdisk", "-o", disk_device]) - - # 3. Create EFI Partition (Part 1, Start) - run_command( - [ - "sgdisk", - "-n", - f"1:0:+{efi_mb}M", - "-t", - "1:ef00", - "-c", - "1:EFI System", - disk_device, - ] - ) - - # 4. Create Swap Partition (Part 3, End) - If enabled - if use_swap: - # sgdisk negative start is from end of disk - # We use partition 3 for Swap - run_command( - [ - "sgdisk", - "-n", - f"3:-{swap_mb}M:0", - "-t", - "3:8200", - "-c", - "3:Swap", - disk_device, - ] - ) - - # 5. Create Root Partition (Part 2, Fill Gap) - # This fills the space between P1 and P3 (or end if no swap) - run_command(["sgdisk", "-n", "2:0:0", "-t", "2:8300", "-c", "2:Root", disk_device]) - - # Inform kernel of changes run_command(["partprobe", disk_device]) - import time + time.sleep(2) - time.sleep(1) + # Identify partitions + if uefi: + efi_part = get_partition_device(disk_device, 1) + root_part = get_partition_device(disk_device, 2) + swap_part = get_partition_device(disk_device, 3) if use_swap else None + else: + efi_part = None # BIOS doesn't use ESP + root_part = get_partition_device(disk_device, 1) + swap_part = get_partition_device(disk_device, 2) if use_swap else None - # 6. Format Partitions - efi_part = get_partition_device(disk_device, 1) - root_part = get_partition_device(disk_device, 2) - swap_part = get_partition_device(disk_device, 3) if use_swap else None - - logger.info("Formatting EFI partition...") - run_command(["mkfs.vfat", "-F32", efi_part]) + # Format + if uefi: + logger.info(f"Formatting EFI partition {efi_part}...") + run_command(["mkfs.vfat", "-F32", efi_part]) if use_swap: - logger.info("Formatting Swap partition...") + logger.info(f"Formatting Swap partition {swap_part}...") run_command(["mkswap", swap_part]) - logger.info("Formatting Root partition...") + logger.info(f"Formatting Root partition {root_part}...") run_command(["mkfs.ext4", "-F", root_part]) - logger.info("Partitioning and formatting complete.") - log_disk_operation( - "PARTITION", - "complete", - f"EFI: {efi_part}, Root: {root_part}, Swap: {swap_part or 'none'}", - ) - - result = {"efi": efi_part, "root": root_part} - if use_swap: - result["swap"] = swap_part - else: - # If no swap, we should probably return None or handle it in fstab generation - # backend/os_install.py expects "swap" key for UUID. - # We should probably pass a dummy or None, and update os_install to handle it. - result["swap"] = None - + result = {"efi": efi_part, "root": root_part, "swap": swap_part} + log_disk_operation("PARTITION", "complete", str(result)) return result @@ -253,32 +227,29 @@ def mount_partitions(partition_info, mount_root="/mnt"): Mounts the partitions into mount_root. """ import os - log_disk_operation("MOUNT", "start", f"Mounting to {mount_root}") # 1. Mount Root if not os.path.exists(mount_root): os.makedirs(mount_root) - run_command(["mount", partition_info["root"], mount_root]) - # 2. Mount EFI - efi_mount = os.path.join(mount_root, "boot") - if not os.path.exists(efi_mount): + # 2. Mount EFI (if exists) + if partition_info.get("efi"): + efi_mount = os.path.join(mount_root, "boot/efi") os.makedirs(efi_mount, exist_ok=True) - - run_command(["mount", partition_info["efi"], efi_mount]) + run_command(["mount", partition_info["efi"], efi_mount]) + else: + # For BIOS, /boot is just a directory on root or we could make a separate /boot + # Current logic keeps it on root. + pass # 3. Enable Swap if partition_info.get("swap"): run_command(["swapon", partition_info["swap"]]) logger.info(f"Partitions mounted at {mount_root}") - log_disk_operation( - "MOUNT", - "complete", - f"Root: {partition_info['root']}, EFI: {partition_info['efi']}", - ) + log_disk_operation("MOUNT", "complete", f"Root: {partition_info['root']}") def create_partition( diff --git a/iridium_installer/backend/os_install.py b/iridium_installer/backend/os_install.py index dbf3af1..25d8f4f 100644 --- a/iridium_installer/backend/os_install.py +++ b/iridium_installer/backend/os_install.py @@ -110,6 +110,10 @@ def mount_pseudo_fs(mount_root): logger.warning(f"Failed to unmount {path}: {e}") +def is_uefi(): + return os.path.exists("/sys/firmware/efi") + + def install_minimal_os(mount_root, releasever="43"): """ Installs minimal Fedora packages to mount_root. @@ -117,22 +121,29 @@ def install_minimal_os(mount_root, releasever="43"): logger.info(f"Installing minimal Fedora {releasever} to {mount_root}...") log_os_install("INSTALL", "start", f"Target: {mount_root}, Release: {releasever}") + uefi = is_uefi() + packages = [ "basesystem", "bash", "coreutils", "kernel", "systemd", - "systemd-boot-unsigned", "dnf", "shadow-utils", "util-linux", - "efibootmgr", "passwd", "rootfiles", "vim-minimal", + "grub2-tools", + "grubby", ] + if uefi: + packages += ["grub2-efi-x64", "shim-x64", "efibootmgr"] + else: + packages += ["grub2-pc"] + # Offline installation logic possible_repos = [ "/run/install/repo", @@ -175,7 +186,7 @@ def install_minimal_os(mount_root, releasever="43"): "--cacheonly", ] else: - logger.warning("ISO repository not found in common locations. DNF might try to use network.") + logger.warning("ISO repository not found. DNF might try to use network.") dnf_args = [] cmd = [ @@ -200,56 +211,48 @@ def install_minimal_os(mount_root, releasever="43"): log_os_install("INSTALL", "complete", f"Installed to {mount_root}") -def configure_system(mount_root, partition_info, user_info=None): +def configure_system(mount_root, partition_info, user_info=None, disk_device=None): """ - Basic configuration: fstab, systemd-boot, and user creation. + Basic configuration: fstab, grub2, and user creation. """ logger.info("Configuring system...") log_os_install("CONFIGURE", "start", f"Configuring system in {mount_root}") + uefi = is_uefi() + # 1. Generate fstab def get_uuid(dev): res = run_command(["blkid", "-s", "UUID", "-o", "value", dev]) return res.stdout.strip() root_uuid = get_uuid(partition_info["root"]) - efi_uuid = get_uuid(partition_info["efi"]) + + fstab_lines = [f"UUID={root_uuid} / ext4 defaults 1 1"] + + if uefi and partition_info.get("efi"): + efi_uuid = get_uuid(partition_info["efi"]) + fstab_lines.append(f"UUID={efi_uuid} /boot/efi vfat defaults 0 2") - swap_entry = "" if partition_info.get("swap"): swap_uuid = get_uuid(partition_info["swap"]) - swap_entry = f"UUID={swap_uuid} none swap defaults 0 0\n" + fstab_lines.append(f"UUID={swap_uuid} none swap defaults 0 0") - fstab_content = f""" -UUID={root_uuid} / ext4 defaults 1 1 -UUID={efi_uuid} /boot vfat defaults 0 2 -{swap_entry} -""" 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) + f.write("\n".join(fstab_lines) + "\n") with mount_pseudo_fs(mount_root): # 2. Configure User if user_info: logger.info(f"Creating user {user_info['username']}...") - # Create user and add to wheel group (sudoer) run_command(["chroot", mount_root, "useradd", "-m", "-G", "wheel", user_info["username"]]) - # Ensure changes to /etc/passwd and /etc/shadow are flushed - run_command(["sync"]) - - # Ensure wheel group has sudo privileges - sudoers_dir = os.path.join(mount_root, "etc/sudoers.d") - os.makedirs(sudoers_dir, exist_ok=True) - with open(os.path.join(sudoers_dir, "wheel"), "w") as f: - f.write("%wheel ALL=(ALL) ALL\n") - os.chmod(os.path.join(sudoers_dir, "wheel"), 0o440) + # Set hostname + with open(os.path.join(mount_root, "etc/hostname"), "w") as f: + f.write(user_info["hostname"] + "\n") - # Set user and root password using hashed passwords and usermod + # Set passwords try: - logger.info(f"Setting hashed passwords for {user_info['username']} and root...") - # Generate SHA512 hash using openssl on the host res = subprocess.run( ["openssl", "passwd", "-6", user_info["password"]], capture_output=True, @@ -257,64 +260,38 @@ UUID={efi_uuid} /boot vfat defaults 0 2 check=True ) hashed_pass = res.stdout.strip() - - # Apply hash to user and root using usermod -p (takes encrypted password) run_command(["chroot", mount_root, "usermod", "-p", hashed_pass, user_info["username"]]) run_command(["chroot", mount_root, "usermod", "-p", hashed_pass, "root"]) - - run_command(["sync"]) except Exception as e: logger.error(f"Failed to set passwords: {e}") - raise e - # Set hostname - with open(os.path.join(mount_root, "etc/hostname"), "w") as f: - f.write(user_info["hostname"] + "\n") - - # 3. Configure systemd-boot - # Ensure machine-id exists for kernel-install - if not os.path.exists(os.path.join(mount_root, "etc/machine-id")): - run_command(["chroot", mount_root, "systemd-machine-id-setup"]) - - # Set kernel command line - os.makedirs(os.path.join(mount_root, "etc/kernel"), exist_ok=True) - with open(os.path.join(mount_root, "etc/kernel/cmdline"), "w") as f: - f.write(f"root=UUID={root_uuid} rw quiet\n") + # 3. Configure GRUB2 + logger.info("Configuring GRUB2...") - # Set kernel layout to BLS for systemd-boot - with open(os.path.join(mount_root, "etc/kernel/layout"), "w") as f: - f.write("bls\n") + # Ensure /etc/default/grub exists + grub_default = os.path.join(mount_root, "etc/default/grub") + if not os.path.exists(grub_default): + with open(grub_default, "w") as f: + f.write('GRUB_TIMEOUT=5\nGRUB_DISTRIBUTOR="$(sed \'s, release .*$,,g\' /etc/system-release)"\nGRUB_DEFAULT=saved\nGRUB_DISABLE_SUBMENU=true\nGRUB_TERMINAL_OUTPUT="console"\nGRUB_CMDLINE_LINUX="rhgb quiet"\nGRUB_DISABLE_RECOVERY="true"\nGRUB_ENABLE_BLSCFG=true\n') - # Install systemd-boot to the ESP (mounted at /boot) - # Note: removed --force as it's not supported by all bootctl versions - run_command(["chroot", mount_root, "bootctl", "install", "--path=/boot"]) + if uefi: + run_command(["chroot", mount_root, "grub2-install", "--target=x86_64-efi", "--efi-directory=/boot/efi", "--bootloader-id=iridium", "--recheck"]) + run_command(["chroot", mount_root, "grub2-mkconfig", "-o", "/boot/efi/EFI/iridium/grub.cfg"]) + # Fedora compatibility link + fedora_dir = os.path.join(mount_root, "boot/efi/EFI/fedora") + os.makedirs(fedora_dir, exist_ok=True) + with open(os.path.join(fedora_dir, "grub.cfg"), "w") as f: + f.write(f"configfile /EFI/iridium/grub.cfg\n") + else: + if not disk_device: + # Try to guess disk device from root partition + disk_device = partition_info["root"].rstrip("0123456789") + if disk_device.endswith("p"): disk_device = disk_device[:-1] + + logger.info(f"Installing GRUB to {disk_device} (BIOS)") + run_command(["chroot", mount_root, "grub2-install", "--target=i386-pc", disk_device]) + run_command(["chroot", mount_root, "grub2-mkconfig", "-o", "/boot/grub2/grub.cfg"]) - # Add kernel entries - modules_dir = os.path.join(mount_root, "lib/modules") - if os.path.exists(modules_dir): - kvers = [d for d in os.listdir(modules_dir) if os.path.isdir(os.path.join(modules_dir, d))] - for kver in kvers: - logger.info(f"Adding kernel entry for {kver}...") - - # Ensure initramfs exists. If not, generate it. - initrd_path_rel = f"boot/initramfs-{kver}.img" - initrd_full_path = os.path.join(mount_root, initrd_path_rel) - - if not os.path.exists(initrd_full_path): - logger.info(f"Generating initramfs for {kver}...") - run_command(["chroot", mount_root, "dracut", "--force", f"/boot/initramfs-{kver}.img", kver]) - - kernel_image = f"/lib/modules/{kver}/vmlinuz" - initrd_arg = f"/boot/initramfs-{kver}.img" - - # kernel-install add [initrd] - cmd = ["chroot", mount_root, "kernel-install", "add", kver, kernel_image] - if os.path.exists(os.path.join(mount_root, initrd_arg.lstrip("/"))): - cmd.append(initrd_arg) - - run_command(cmd) - - # Ensure all data is synced to disk run_command(["sync"]) logger.info("System configuration complete.") diff --git a/iridium_installer/main.py b/iridium_installer/main.py index 320fc03..bb41a61 100644 --- a/iridium_installer/main.py +++ b/iridium_installer/main.py @@ -64,7 +64,7 @@ def main(): "password": "password", # Insecure default for CLI dev testing "hostname": "iridium-cli" } - configure_system(mount_root, parts, user_info) + configure_system(mount_root, parts, user_info, disk_device=target) logger.info("INSTALLER_BOOTLOADER_COMPLETE: Bootloader configured") print("Installation complete! You can now reboot.") diff --git a/iridium_installer/ui/pages/partitioning.py b/iridium_installer/ui/pages/partitioning.py index edf3184..cd89526 100644 --- a/iridium_installer/ui/pages/partitioning.py +++ b/iridium_installer/ui/pages/partitioning.py @@ -54,8 +54,13 @@ def get_total_memory() -> int: return 0 +def is_uefi(): + return os.path.exists("/sys/firmware/efi") + + def calculate_auto_partitions(disk_device): disk_size = 0 + uefi = is_uefi() try: # Get disk size in bytes result = subprocess.run( @@ -83,16 +88,21 @@ def calculate_auto_partitions(disk_device): disk_mb = disk_size / (1024 * 1024) # Defaults - efi_size = 2 * 1024 * 1024 * 1024 + efi_size = 1024 * 1024 * 1024 if uefi else 0 swap_size = ram_size + (2 * 1024 * 1024 * 1024) min_root_size = 10 * 1024 * 1024 * 1024 # 10GB + # Cap swap for auto-calc if disk is small + if disk_size < 100 * 1024**3: + swap_size = min(swap_size, 4 * 1024**3) + total_required = efi_size + swap_size + min_root_size use_swap = True if disk_size < total_required: - efi_size = 1 * 1024 * 1024 * 1024 + if uefi: + efi_size = 512 * 1024 * 1024 use_swap = False swap_size = 0 if disk_size < (efi_size + min_root_size): @@ -101,26 +111,28 @@ def calculate_auto_partitions(disk_device): root_size = disk_size - efi_size - swap_size - partitions = [ - { + partitions = [] + + if uefi: + partitions.append({ "type": "partition", "name": "EFI System", "filesystem": "vfat", - "mount_point": "/boot", + "mount_point": "/boot/efi", "size": f"{efi_size / (1024**3):.1f} GB", "bytes": efi_size, "style_class": "part-efi", - }, - { - "type": "partition", - "name": "Root", - "filesystem": "ext4", - "mount_point": "/", - "size": f"{root_size / (1024**3):.1f} GB", - "bytes": root_size, - "style_class": "part-root", - }, - ] + }) + + partitions.append({ + "type": "partition", + "name": "Root", + "filesystem": "ext4", + "mount_point": "/", + "size": f"{root_size / (1024**3):.1f} GB", + "bytes": root_size, + "style_class": "part-root", + }) if use_swap: partitions.append( diff --git a/iridium_installer/ui/window.py b/iridium_installer/ui/window.py index 44e75bd..dd30210 100644 --- a/iridium_installer/ui/window.py +++ b/iridium_installer/ui/window.py @@ -328,7 +328,7 @@ class InstallerWindow(Adw.ApplicationWindow): f"Session: {self.session_id} - Configuring bootloader and user", "installer", ) - configure_system(mount_root, parts, user_info) + configure_system(mount_root, parts, user_info, disk_device=disk) nlog( "INSTALL_PROGRESS", f"Session: {self.session_id} - Configuration complete", diff --git a/run.sh b/run.sh index e67ecc6..1a611a5 100755 --- a/run.sh +++ b/run.sh @@ -29,7 +29,7 @@ fi # Check and install dependencies (Fedora/DNF) if command -v dnf &> /dev/null; then - DEPENDENCIES="python3-gobject gtk4 libadwaita python3-requests gdisk dosfstools e2fsprogs" + DEPENDENCIES="python3-gobject gtk4 libadwaita python3-requests gdisk dosfstools e2fsprogs parted grub2-tools openssl" MISSING_DEPS="" for dep in $DEPENDENCIES; do diff --git a/run_vm_bios.sh b/run_vm_bios.sh new file mode 100755 index 0000000..e5adf5f --- /dev/null +++ b/run_vm_bios.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Configuration +ISO_PATH="${1:-/home/n0va/.local/share/iridium-installer-vm/Fedora.iso}" +DISK_PATH="${2:-/home/n0va/.local/share/iridium-installer-vm/test-disk.qcow2}" + +echo "Starting Iridium VM with BIOS (Legacy) support..." +echo "ISO: $ISO_PATH" +echo "Disk: $DISK_PATH" + +# QEMU Command with BIOS (Standard QEMU behavior) +qemu-system-x86_64 + -enable-kvm + -m 8G + -smp 2 + -cpu host + -drive file="$DISK_PATH",format=qcow2,if=virtio + -cdrom "$ISO_PATH" + -boot once=d + -netdev user,id=net0 + -device virtio-net-pci,netdev=net0 + -vga virtio + -display gtk,gl=on + -monitor unix:$HOME/.local/share/iridium-installer-vm/monitor.sock,server,nowait + -name "Iridium Installer Test VM (BIOS)"