feat: verbose installation logs, improved auto-partitioning logic, and UI tweaks
This commit is contained in:
@@ -1,16 +1,55 @@
|
||||
import subprocess
|
||||
import logging
|
||||
import os
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class CommandResult:
|
||||
def __init__(self, stdout, stderr, returncode):
|
||||
self.stdout = stdout
|
||||
self.stderr = stderr
|
||||
self.returncode = returncode
|
||||
|
||||
def run_command(cmd, check=True):
|
||||
logger.info(f"Running command: {' '.join(cmd)}")
|
||||
try:
|
||||
result = subprocess.run(cmd, check=check, capture_output=True, text=True)
|
||||
return result
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.error(f"Command failed: {e.stderr}")
|
||||
raise
|
||||
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
bufsize=1
|
||||
)
|
||||
|
||||
stdout_lines = []
|
||||
stderr_lines = []
|
||||
|
||||
def read_stream(stream, line_list, log_level):
|
||||
for line in stream:
|
||||
line_clean = line.strip()
|
||||
if line_clean:
|
||||
log_level(line_clean)
|
||||
line_list.append(line)
|
||||
|
||||
import threading
|
||||
t1 = threading.Thread(target=read_stream, args=(process.stdout, stdout_lines, logger.info))
|
||||
t2 = threading.Thread(target=read_stream, args=(process.stderr, stderr_lines, logger.error))
|
||||
|
||||
t1.start()
|
||||
t2.start()
|
||||
|
||||
t1.join()
|
||||
t2.join()
|
||||
|
||||
returncode = process.wait()
|
||||
|
||||
stdout_str = "".join(stdout_lines)
|
||||
stderr_str = "".join(stderr_lines)
|
||||
|
||||
if check and returncode != 0:
|
||||
raise subprocess.CalledProcessError(returncode, cmd, output=stdout_str, stderr=stderr_str)
|
||||
|
||||
return CommandResult(stdout_str, stderr_str, returncode)
|
||||
|
||||
def get_partition_device(disk_device, partition_number):
|
||||
"""
|
||||
@@ -21,46 +60,95 @@ def get_partition_device(disk_device, partition_number):
|
||||
return f"{disk_device}p{partition_number}"
|
||||
return f"{disk_device}{partition_number}"
|
||||
|
||||
def get_total_memory():
|
||||
"""Returns total system memory in bytes."""
|
||||
try:
|
||||
return os.sysconf("SC_PAGE_SIZE") * os.sysconf("SC_PHYS_PAGES")
|
||||
except (ValueError, OSError):
|
||||
with open("/proc/meminfo", "r") as f:
|
||||
for line in f:
|
||||
if line.startswith("MemTotal:"):
|
||||
parts = line.split()
|
||||
return int(parts[1]) * 1024
|
||||
return 0
|
||||
|
||||
def get_disk_size(disk_device):
|
||||
"""Returns disk size in bytes using blockdev."""
|
||||
try:
|
||||
res = run_command(["blockdev", "--getsize64", disk_device])
|
||||
return int(res.stdout.strip())
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get disk size: {e}")
|
||||
return 0
|
||||
|
||||
def auto_partition_disk(disk_device):
|
||||
"""
|
||||
Automatically partitions the disk with a standard layout:
|
||||
1. EFI System Partition (1GB)
|
||||
2. Swap (4GB) - simpler fixed size for now
|
||||
3. Root (Remaining)
|
||||
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)
|
||||
"""
|
||||
logger.info(f"Starting auto-partitioning on {disk_device}")
|
||||
|
||||
# Calculate sizes
|
||||
disk_size = get_disk_size(disk_device)
|
||||
ram_size = get_total_memory()
|
||||
|
||||
# Defaults
|
||||
efi_mb = 2048
|
||||
swap_mb = int((ram_size / (1024*1024)) + 2048)
|
||||
min_root_mb = 10240 # 10GB
|
||||
|
||||
total_required_mb = efi_mb + swap_mb + min_root_mb
|
||||
disk_mb = disk_size / (1024*1024)
|
||||
|
||||
use_swap = True
|
||||
|
||||
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, 1GB, Type EF00)
|
||||
# -n <partnum>:<start>:<end>
|
||||
run_command(["sgdisk", "-n", "1:0:+1024M", "-t", "1:ef00", "-c", "1:EFI System", 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 2, 4GB, Type 8200)
|
||||
run_command(["sgdisk", "-n", "2:0:+4096M", "-t", "2:8200", "-c", "2:Swap", 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 3, Rest, Type 8300)
|
||||
run_command(["sgdisk", "-n", "3:0:0", "-t", "3:8300", "-c", "3:Root", 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])
|
||||
|
||||
# Wait a bit for nodes to appear? Usually partprobe handles it but sometimes there's a race.
|
||||
import time
|
||||
time.sleep(1)
|
||||
|
||||
# 6. Format Partitions
|
||||
efi_part = get_partition_device(disk_device, 1)
|
||||
swap_part = get_partition_device(disk_device, 2)
|
||||
root_part = get_partition_device(disk_device, 3)
|
||||
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])
|
||||
|
||||
if use_swap:
|
||||
logger.info("Formatting Swap partition...")
|
||||
run_command(["mkswap", swap_part])
|
||||
|
||||
@@ -69,16 +157,23 @@ def auto_partition_disk(disk_device):
|
||||
|
||||
logger.info("Partitioning and formatting complete.")
|
||||
|
||||
return {
|
||||
result = {
|
||||
"efi": efi_part,
|
||||
"swap": swap_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
|
||||
|
||||
return result
|
||||
|
||||
def mount_partitions(partition_info, mount_root="/mnt"):
|
||||
"""
|
||||
Mounts the partitions into mount_root.
|
||||
partition_info is the dict returned by auto_partition_disk.
|
||||
"""
|
||||
import os
|
||||
|
||||
@@ -95,7 +190,8 @@ def mount_partitions(partition_info, mount_root="/mnt"):
|
||||
|
||||
run_command(["mount", partition_info["efi"], efi_mount])
|
||||
|
||||
# 3. Enable Swap (optional, but might as well)
|
||||
# 3. Enable Swap
|
||||
if partition_info.get("swap"):
|
||||
run_command(["swapon", partition_info["swap"]])
|
||||
|
||||
logger.info(f"Partitions mounted at {mount_root}")
|
||||
|
||||
@@ -1,18 +1,58 @@
|
||||
import subprocess
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class CommandResult:
|
||||
def __init__(self, stdout, stderr, returncode):
|
||||
self.stdout = stdout
|
||||
self.stderr = stderr
|
||||
self.returncode = returncode
|
||||
|
||||
def run_command(cmd, check=True):
|
||||
logger.info(f"Running command: {' '.join(cmd)}")
|
||||
try:
|
||||
result = subprocess.run(cmd, check=check, capture_output=True, text=True)
|
||||
return result
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.error(f"Command failed: {e.stderr}")
|
||||
raise
|
||||
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
bufsize=1 # Line buffered
|
||||
)
|
||||
|
||||
stdout_lines = []
|
||||
stderr_lines = []
|
||||
|
||||
# Helper to read stream
|
||||
def read_stream(stream, line_list, log_level):
|
||||
for line in stream:
|
||||
line_clean = line.strip()
|
||||
if line_clean:
|
||||
log_level(line_clean)
|
||||
line_list.append(line)
|
||||
|
||||
import threading
|
||||
t1 = threading.Thread(target=read_stream, args=(process.stdout, stdout_lines, logger.info))
|
||||
t2 = threading.Thread(target=read_stream, args=(process.stderr, stderr_lines, logger.error))
|
||||
|
||||
t1.start()
|
||||
t2.start()
|
||||
|
||||
t1.join()
|
||||
t2.join()
|
||||
|
||||
returncode = process.wait()
|
||||
|
||||
stdout_str = "".join(stdout_lines)
|
||||
stderr_str = "".join(stderr_lines)
|
||||
|
||||
if check and returncode != 0:
|
||||
raise subprocess.CalledProcessError(returncode, cmd, output=stdout_str, stderr=stderr_str)
|
||||
|
||||
return CommandResult(stdout_str, stderr_str, returncode)
|
||||
|
||||
@contextmanager
|
||||
def mount_pseudo_fs(mount_root):
|
||||
@@ -86,12 +126,16 @@ def configure_system(mount_root, partition_info):
|
||||
|
||||
root_uuid = get_uuid(partition_info["root"])
|
||||
efi_uuid = get_uuid(partition_info["efi"])
|
||||
|
||||
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_content = f"""
|
||||
UUID={root_uuid} / ext4 defaults 1 1
|
||||
UUID={efi_uuid} /boot/efi vfat defaults 0 2
|
||||
UUID={swap_uuid} none swap defaults 0 0
|
||||
{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:
|
||||
|
||||
@@ -55,12 +55,12 @@ def get_total_memory() -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def calculate_auto_partitions(disk_device):
|
||||
def calculate_auto_partitions(disk_device):
|
||||
"""
|
||||
Generates an automatic partition layout.
|
||||
- 2GB EFI
|
||||
- RAM + 2GB Swap
|
||||
- Rest Root (ext4)
|
||||
- 2GB EFI (or 1GB if disk < required)
|
||||
- Root (Rest)
|
||||
- RAM + 2GB Swap (at end) (or 0 if disk < required)
|
||||
"""
|
||||
disk_size = 0
|
||||
try:
|
||||
@@ -87,14 +87,22 @@ def calculate_auto_partitions(disk_device):
|
||||
return []
|
||||
|
||||
ram_size = get_total_memory()
|
||||
disk_mb = disk_size / (1024*1024)
|
||||
|
||||
# Sizes in bytes
|
||||
# Defaults
|
||||
efi_size = 2 * 1024 * 1024 * 1024
|
||||
swap_size = ram_size + (2 * 1024 * 1024 * 1024)
|
||||
min_root_size = 10 * 1024 * 1024 * 1024 # 10GB
|
||||
|
||||
# Check if disk is large enough
|
||||
min_root = 10 * 1024 * 1024 * 1024 # minimum 10GB for root
|
||||
if disk_size < (efi_size + swap_size + min_root):
|
||||
total_required = efi_size + swap_size + min_root_size
|
||||
|
||||
use_swap = True
|
||||
|
||||
if disk_size < total_required:
|
||||
efi_size = 1 * 1024 * 1024 * 1024
|
||||
use_swap = False
|
||||
swap_size = 0
|
||||
if disk_size < (efi_size + min_root_size):
|
||||
print("Disk too small for automatic partitioning scheme.")
|
||||
return []
|
||||
|
||||
@@ -110,15 +118,6 @@ def calculate_auto_partitions(disk_device):
|
||||
"bytes": efi_size,
|
||||
"style_class": "part-efi",
|
||||
},
|
||||
{
|
||||
"type": "partition",
|
||||
"name": "Swap",
|
||||
"filesystem": "swap",
|
||||
"mount_point": "[SWAP]",
|
||||
"size": f"{swap_size / (1024**3):.1f} GB",
|
||||
"bytes": swap_size,
|
||||
"style_class": "part-swap",
|
||||
},
|
||||
{
|
||||
"type": "partition",
|
||||
"name": "Root",
|
||||
@@ -127,9 +126,20 @@ def calculate_auto_partitions(disk_device):
|
||||
"size": f"{root_size / (1024**3):.1f} GB",
|
||||
"bytes": root_size,
|
||||
"style_class": "part-root",
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
if use_swap:
|
||||
partitions.append({
|
||||
"type": "partition",
|
||||
"name": "Swap",
|
||||
"filesystem": "swap",
|
||||
"mount_point": "[SWAP]",
|
||||
"size": f"{swap_size / (1024**3):.1f} GB",
|
||||
"bytes": swap_size,
|
||||
"style_class": "part-swap",
|
||||
})
|
||||
|
||||
return partitions
|
||||
|
||||
|
||||
@@ -525,6 +535,7 @@ class PartitioningPage(Adw.Bin):
|
||||
# Size entry
|
||||
size_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12)
|
||||
size_entry = Gtk.Entry(text=str(int(data["bytes"] / (1024*1024))))
|
||||
size_entry.set_width_chars(15)
|
||||
size_box.append(size_entry)
|
||||
size_box.append(Gtk.Label(label="MB"))
|
||||
box.append(Gtk.Label(label="Size:"))
|
||||
|
||||
Reference in New Issue
Block a user