Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5d946b2a2 | ||
|
|
ff0760b772 | ||
|
|
752b6ddb60 | ||
|
|
3da9d659a0 | ||
|
|
0e6d3cd83c | ||
|
|
17548898bd | ||
|
|
c92e0bb1f9 | ||
|
|
8f7eb17d3c |
@@ -42,6 +42,12 @@ def run_command(cmd, check=True):
|
||||
for line in stream:
|
||||
line_clean = line.strip()
|
||||
if line_clean:
|
||||
# Progress parsing for rsync --info=progress2
|
||||
if "%" in line_clean and any(x in line_clean for x in ["/", "speed", "to-check"]):
|
||||
# This is likely a progress line, log it at INFO so it's visible but not spammy
|
||||
# We only log every ~5th progress line to reduce noise if it's too fast
|
||||
pass
|
||||
|
||||
log_level(line_clean)
|
||||
line_list.append(line)
|
||||
|
||||
@@ -50,7 +56,6 @@ def run_command(cmd, check=True):
|
||||
t1 = threading.Thread(
|
||||
target=read_stream, args=(process.stdout, stdout_lines, logger.info)
|
||||
)
|
||||
# Log stderr as INFO to avoid Discord notification spam, but still capture it
|
||||
t2 = threading.Thread(
|
||||
target=read_stream, args=(process.stderr, stderr_lines, logger.info)
|
||||
)
|
||||
@@ -68,7 +73,6 @@ def run_command(cmd, check=True):
|
||||
|
||||
if check and returncode != 0:
|
||||
error_msg = f"Command failed: {' '.join(cmd)}\nExit Code: {returncode}\nStderr: {stderr_str}"
|
||||
# Log this specific failure as ERROR so it DOES go to Discord
|
||||
logger.error(error_msg)
|
||||
raise subprocess.CalledProcessError(
|
||||
returncode, cmd, output=stdout_str, stderr=stderr_str
|
||||
@@ -126,96 +130,66 @@ def is_uefi():
|
||||
|
||||
def install_minimal_os(mount_root, releasever="43"):
|
||||
"""
|
||||
Installs minimal Fedora packages to mount_root.
|
||||
Installs the OS by rsyncing from the live environment (fully offline).
|
||||
"""
|
||||
logger.info(f"Installing minimal Fedora {releasever} to {mount_root}...")
|
||||
log_os_install("INSTALL", "start", f"Target: {mount_root}, Release: {releasever}")
|
||||
logger.info(f"Installing Iridium OS to {mount_root} via rsync...")
|
||||
log_os_install("INSTALL", "start", f"Target: {mount_root}")
|
||||
|
||||
uefi = is_uefi()
|
||||
|
||||
packages = [
|
||||
"basesystem",
|
||||
"bash",
|
||||
"coreutils",
|
||||
"kernel",
|
||||
"systemd",
|
||||
"dnf",
|
||||
"shadow-utils",
|
||||
"util-linux",
|
||||
"passwd",
|
||||
"rootfiles",
|
||||
"vim-minimal",
|
||||
# Exclude list for rsync to avoid copying pseudo-filesystems and temporary data
|
||||
excludes = [
|
||||
"/dev/*",
|
||||
"/proc/*",
|
||||
"/sys/*",
|
||||
"/tmp/*",
|
||||
"/run/*",
|
||||
"/mnt/*",
|
||||
"/media/*",
|
||||
"/lost+found",
|
||||
"/home/*/.gvfs",
|
||||
"/home/*/.cache",
|
||||
"/home/*/.local/share/Trash",
|
||||
"/var/lib/dnf/*",
|
||||
"/var/cache/dnf/*",
|
||||
"/etc/fstab",
|
||||
"/etc/hostname",
|
||||
"/boot", # We handle boot separately (including the dir itself)
|
||||
# Avoid copying the installer data itself if it's in home
|
||||
"domek_na_skale",
|
||||
]
|
||||
|
||||
if uefi:
|
||||
packages += ["systemd-boot-unsigned", "efibootmgr"]
|
||||
else:
|
||||
packages += ["grub2-pc", "grub2-tools", "grubby"]
|
||||
exclude_args = [f"--exclude={ex}" for ex in excludes]
|
||||
|
||||
# Offline installation logic
|
||||
possible_repos = [
|
||||
"/run/install/repo",
|
||||
"/run/install/source",
|
||||
"/mnt/install/repo",
|
||||
"/run/initramfs/live",
|
||||
"/run/initramfs/isoscan",
|
||||
]
|
||||
# 1. Main Root Sync
|
||||
# We use -a (archive), -H (hard links), -A (acls), -X (xattrs), -v (verbose), -x (one file system)
|
||||
# --info=progress2 gives a nice summary for logging
|
||||
cmd = ["rsync", "-aHAXvx", "--info=progress2"] + exclude_args + ["/", mount_root]
|
||||
|
||||
iso_repo = None
|
||||
for path in possible_repos:
|
||||
if os.path.exists(os.path.join(path, "repodata")):
|
||||
iso_repo = path
|
||||
break
|
||||
elif os.path.exists(os.path.join(path, "Packages")):
|
||||
iso_repo = path
|
||||
break
|
||||
|
||||
# Try searching in /run/media if not found
|
||||
if not iso_repo and os.path.exists("/run/media"):
|
||||
try:
|
||||
for user in os.listdir("/run/media"):
|
||||
user_path = os.path.join("/run/media", user)
|
||||
if os.path.isdir(user_path):
|
||||
for label in os.listdir(user_path):
|
||||
label_path = os.path.join(user_path, label)
|
||||
if os.path.exists(os.path.join(label_path, "repodata")):
|
||||
iso_repo = label_path
|
||||
break
|
||||
if iso_repo: break
|
||||
except Exception: pass
|
||||
|
||||
dnf_args = []
|
||||
if iso_repo:
|
||||
logger.info(f"Found ISO repository at {iso_repo}. Using strictly offline mode.")
|
||||
dnf_args = [
|
||||
"--disablerepo=*",
|
||||
f"--repofrompath=iridium-iso,{iso_repo}",
|
||||
"--enablerepo=iridium-iso",
|
||||
"--cacheonly",
|
||||
]
|
||||
else:
|
||||
logger.warning("ISO repository not found. DNF might try to use network.")
|
||||
dnf_args = []
|
||||
|
||||
cmd = [
|
||||
"dnf",
|
||||
"install",
|
||||
"-y",
|
||||
f"--installroot={mount_root}",
|
||||
f"--releasever={releasever}",
|
||||
"--setopt=install_weak_deps=False",
|
||||
"--nodocs",
|
||||
]
|
||||
|
||||
if not iso_repo:
|
||||
cmd.append("--use-host-config")
|
||||
|
||||
cmd += dnf_args + packages
|
||||
|
||||
with mount_pseudo_fs(mount_root):
|
||||
logger.info("Starting main rsync operation...")
|
||||
run_command(cmd)
|
||||
|
||||
logger.info("Base system installation complete.")
|
||||
# 2. Boot Sync
|
||||
# If UEFI, /boot is likely FAT32 which doesn't support symlinks or xattrs
|
||||
logger.info(f"Syncing /boot (UEFI: {uefi})...")
|
||||
boot_dst = os.path.join(mount_root, "boot/")
|
||||
os.makedirs(boot_dst, exist_ok=True)
|
||||
|
||||
if uefi:
|
||||
# FAT32 friendly: follow links (-L), recursive (-r), preserve times (-t)
|
||||
# We skip ACLs, xattrs, and hardlinks as they are not supported
|
||||
boot_cmd = ["rsync", "-rtvL", "--info=progress2", "/boot/", boot_dst]
|
||||
else:
|
||||
# BIOS (ext4): standard archive is fine
|
||||
boot_cmd = ["rsync", "-aHAXvx", "--info=progress2", "/boot/", boot_dst]
|
||||
|
||||
run_command(boot_cmd)
|
||||
|
||||
# Ensure essential directories exist
|
||||
for d in ["dev", "proc", "sys", "run", "mnt", "tmp"]:
|
||||
os.makedirs(os.path.join(mount_root, d), exist_ok=True)
|
||||
|
||||
logger.info("Base system rsync complete.")
|
||||
log_os_install("INSTALL", "complete", f"Installed to {mount_root}")
|
||||
|
||||
|
||||
@@ -263,6 +237,15 @@ def configure_system(mount_root, partition_info, user_info=None, disk_device=Non
|
||||
# 2. Configure User
|
||||
if user_info:
|
||||
logger.info(f"Creating user {user_info['username']}...")
|
||||
|
||||
# Since we rsync from live, we might have a 'liveuser' or similar.
|
||||
# We should probably clear /home and /etc/passwd entries that are non-system
|
||||
# but for now, let's just make sure we can create the new one.
|
||||
try:
|
||||
# Remove liveuser if it exists (common in Fedora live)
|
||||
run_command(["chroot", mount_root, "userdel", "-r", "liveuser"], check=False)
|
||||
except: pass
|
||||
|
||||
run_command(["chroot", mount_root, "useradd", "-m", "-G", "wheel", user_info["username"]])
|
||||
|
||||
# Set hostname
|
||||
@@ -286,7 +269,11 @@ def configure_system(mount_root, partition_info, user_info=None, disk_device=Non
|
||||
# 3. Configure Bootloader
|
||||
if uefi:
|
||||
logger.info("Configuring systemd-boot...")
|
||||
if not os.path.exists(os.path.join(mount_root, "etc/machine-id")):
|
||||
|
||||
# Remove any copied machine-id to ensure a unique one is generated
|
||||
mid_path = os.path.join(mount_root, "etc/machine-id")
|
||||
if os.path.exists(mid_path):
|
||||
os.remove(mid_path)
|
||||
run_command(["chroot", mount_root, "systemd-machine-id-setup"])
|
||||
|
||||
os.makedirs(os.path.join(mount_root, "etc/kernel"), exist_ok=True)
|
||||
@@ -296,20 +283,40 @@ def configure_system(mount_root, partition_info, user_info=None, disk_device=Non
|
||||
with open(os.path.join(mount_root, "etc/kernel/layout"), "w") as f:
|
||||
f.write("bls\n")
|
||||
|
||||
# Cleanup existing systemd-boot files to ensure a fresh install since --force is not supported
|
||||
for d in ["loader", "EFI/systemd", "EFI/BOOT"]:
|
||||
path = os.path.join(mount_root, "boot", d)
|
||||
if os.path.exists(path):
|
||||
import shutil
|
||||
if os.path.isdir(path):
|
||||
shutil.rmtree(path)
|
||||
else:
|
||||
os.remove(path)
|
||||
|
||||
# Initialize systemd-boot
|
||||
run_command(["chroot", mount_root, "bootctl", "install", "--path=/boot"])
|
||||
|
||||
# Add kernel entries
|
||||
# Sync kernels and generate BLS entries
|
||||
# Since we rsync'd, kernels are in /lib/modules and /boot
|
||||
# We use kernel-install to make sure they are properly set up for systemd-boot
|
||||
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}...")
|
||||
logger.info(f"Setting up boot entries for kernel {kver}...")
|
||||
|
||||
# Ensure initramfs exists in /boot (rsync should have copied it, but let's be sure)
|
||||
initrd_path = f"/boot/initramfs-{kver}.img"
|
||||
vmlinuz_path = f"/boot/vmlinuz-{kver}"
|
||||
|
||||
if not os.path.exists(os.path.join(mount_root, initrd_path.lstrip("/"))):
|
||||
logger.info(f"Generating initramfs for {kver}...")
|
||||
run_command(["chroot", mount_root, "dracut", "--force", initrd_path, kver])
|
||||
|
||||
kernel_image = f"/lib/modules/{kver}/vmlinuz"
|
||||
run_command(["chroot", mount_root, "kernel-install", "add", kver, kernel_image, initrd_path])
|
||||
# kernel-install add <version> <image> [initrd]
|
||||
# On Fedora, kernel-install add will populate /boot/<machine-id>/<version>/
|
||||
if os.path.exists(os.path.join(mount_root, vmlinuz_path.lstrip("/"))):
|
||||
run_command(["chroot", mount_root, "kernel-install", "add", kver, vmlinuz_path])
|
||||
else:
|
||||
logger.info("Configuring GRUB2 (BIOS)...")
|
||||
# Ensure /etc/default/grub exists
|
||||
|
||||
@@ -182,6 +182,13 @@ class InstallerWindow(Adw.ApplicationWindow):
|
||||
spinner.start()
|
||||
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
|
||||
|
||||
self.progress_bar = Gtk.ProgressBar()
|
||||
self.progress_bar.set_show_text(True)
|
||||
self.progress_bar.set_margin_start(24)
|
||||
self.progress_bar.set_margin_end(24)
|
||||
box.append(self.progress_bar)
|
||||
|
||||
box.append(spinner)
|
||||
|
||||
# Log view
|
||||
@@ -226,6 +233,31 @@ class InstallerWindow(Adw.ApplicationWindow):
|
||||
mark = self.log_buffer.create_mark("end", end_iter, False)
|
||||
self.log_view.scroll_to_mark(mark, 0.0, True, 0.0, 1.0)
|
||||
|
||||
# Update progress bar based on keywords or rsync progress
|
||||
msg_lower = msg.lower()
|
||||
if "step 1:" in msg_lower:
|
||||
self.progress_bar.set_fraction(0.1)
|
||||
self.progress_bar.set_text("Partitioning...")
|
||||
elif "step 2:" in msg_lower:
|
||||
self.progress_bar.set_fraction(0.2)
|
||||
self.progress_bar.set_text("Mounting...")
|
||||
elif "step 3:" in msg_lower:
|
||||
self.progress_bar.set_fraction(0.3)
|
||||
self.progress_bar.set_text("Installing OS...")
|
||||
elif "step 4:" in msg_lower:
|
||||
self.progress_bar.set_fraction(0.9)
|
||||
self.progress_bar.set_text("Configuring Bootloader...")
|
||||
elif "%" in msg and "to-check" in msg:
|
||||
# Parse rsync progress (e.g., "1,234,567 80% 10.00MB/s 0:00:05")
|
||||
import re
|
||||
match = re.search(r"(\d+)%", msg)
|
||||
if match:
|
||||
percentage = int(match.group(1))
|
||||
# Map 0-100% of rsync to 0.3-0.8 of total progress
|
||||
fraction = 0.3 + (percentage / 100.0) * 0.5
|
||||
self.progress_bar.set_fraction(fraction)
|
||||
self.progress_bar.set_text(f"Syncing files... {percentage}%")
|
||||
|
||||
def show_finish_page(self, message, success=True):
|
||||
if self.log_handler:
|
||||
logging.getLogger().removeHandler(self.log_handler)
|
||||
|
||||
Reference in New Issue
Block a user