diff --git a/iridium_installer/backend/os_install.py b/iridium_installer/backend/os_install.py index 8a01116..7c5df15 100644 --- a/iridium_installer/backend/os_install.py +++ b/iridium_installer/backend/os_install.py @@ -79,7 +79,8 @@ def mount_pseudo_fs(mount_root): Context manager to bind mount /dev, /proc, /sys, /run, and efivarfs into mount_root. """ logger.info(f"Mounting pseudo-filesystems to {mount_root}...") - mounts = ["dev", "proc", "sys", "run"] + # dev/pts and dev/shm are often needed by scriptlets + mounts = ["dev", "proc", "sys", "run", "dev/pts", "dev/shm"] mounted_paths = [] try: @@ -89,6 +90,14 @@ def mount_pseudo_fs(mount_root): run_command(["mount", "--bind", f"/{fs}", target]) mounted_paths.append(target) + # Bind mount RPM GPG keys from host + gpg_keys_host = "/etc/pki/rpm-gpg" + if os.path.exists(gpg_keys_host): + gpg_keys_target = os.path.join(mount_root, "etc/pki/rpm-gpg") + os.makedirs(gpg_keys_target, exist_ok=True) + run_command(["mount", "--bind", gpg_keys_host, gpg_keys_target]) + mounted_paths.append(gpg_keys_target) + # Mount efivarfs if it exists on the host efivars_path = "/sys/firmware/efi/efivars" if os.path.exists(efivars_path): @@ -135,14 +144,19 @@ def find_iso_repo(): "/run/initramfs/isoscan", ] - # 1. Check known paths for repodata or media.repo - for path in possible_paths: - if os.path.exists(os.path.join(path, "repodata")) or os.path.exists(os.path.join(path, "media.repo")): - return path + def check_path(p): + if os.path.exists(os.path.join(p, "repodata")) or os.path.exists(os.path.join(p, "media.repo")): + return p for sub in ["os", "Packages", "BaseOS", "AppStream"]: - sub_path = os.path.join(path, sub) - if os.path.exists(os.path.join(sub_path, "repodata")) or os.path.exists(os.path.join(sub_path, "media.repo")): - return sub_path + sub_p = os.path.join(p, sub) + if os.path.exists(os.path.join(sub_p, "repodata")) or os.path.exists(os.path.join(sub_p, "media.repo")): + return sub_p + return None + + # 1. Check known paths + for path in possible_paths: + res = check_path(path) + if res: return res # 2. Check all mounted filesystems try: @@ -151,48 +165,35 @@ def find_iso_repo(): for mount in res.stdout.splitlines(): if mount.startswith("/proc") or mount.startswith("/sys") or mount.startswith("/dev"): continue - if os.path.exists(os.path.join(mount, "repodata")) or os.path.exists(os.path.join(mount, "media.repo")): - return mount - for sub in ["os", "Packages", "BaseOS", "AppStream"]: - if os.path.exists(os.path.join(mount, sub, "repodata")) or os.path.exists(os.path.join(mount, sub, "media.repo")): - return os.path.join(mount, sub) + res = check_path(mount) + if res: return res except Exception as e: logger.debug(f"Error searching mount points: {e}") - # 3. Try to mount /dev/sr0 or /dev/cdrom if not already mounted + # 3. Try to mount /dev/sr0 or /dev/cdrom for dev in ["/dev/sr0", "/dev/cdrom"]: try: if os.path.exists(dev): res = subprocess.run(["findmnt", "-n", dev], capture_output=True) if res.returncode != 0: - logger.info(f"Found {dev} but not mounted. Attempting to mount to /mnt...") try: - subprocess.run(["mount", "-o", "ro", dev, "/mnt"], check=True) - if os.path.exists("/mnt/repodata") or os.path.exists("/mnt/media.repo"): - return "/mnt" - for sub in ["os", "Packages", "BaseOS", "AppStream"]: - if os.path.exists(os.path.join("/mnt", sub, "repodata")) or os.path.exists(os.path.join("/mnt", sub, "media.repo")): - return os.path.join("/mnt", sub) - except Exception as e: - logger.debug(f"Failed to mount {dev}: {e}") - except Exception: - pass + os.makedirs("/mnt/iso", exist_ok=True) + subprocess.run(["mount", "-o", "ro", dev, "/mnt/iso"], check=True) + res = check_path("/mnt/iso") + if res: return res + except Exception: pass + except Exception: pass - # 4. Check /run/media + # 4. Check /run/media deeper if 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")) or os.path.exists(os.path.join(label_path, "media.repo")): - return label_path - for sub in ["os", "Packages", "BaseOS", "AppStream"]: - if os.path.exists(os.path.join(label_path, sub, "repodata")) or os.path.exists(os.path.join(label_path, sub, "media.repo")): - return os.path.join(label_path, sub) - except Exception: - pass + for root, dirs, files in os.walk("/run/media"): + if "repodata" in dirs or "media.repo" in files: + return root + # Limit depth for performance + if root.count(os.sep) > 5: + del dirs[:] + except Exception: pass # 5. Last resort: any directory with .rpm files for path in possible_paths: @@ -243,14 +244,26 @@ def install_minimal_os(mount_root, releasever=None): # Offline installation logic iso_repo = find_iso_repo() - dnf_args = [] + # Create a temporary dnf.conf to be strictly local if repo found + dnf_conf_path = "/tmp/iridium_dnf.conf" + with open(dnf_conf_path, "w") as f: + f.write("[main]\ngpgcheck=0\ninstallroot_managed_by_dnf=True\n") + + dnf_args = [ + f"--config={dnf_conf_path}", + "--setopt=cachedir=/tmp/dnf-cache", + "--setopt=install_weak_deps=False", + "--nodocs", + ] + if iso_repo: logger.info(f"Found ISO repository at {iso_repo}. Using strictly offline mode.") - dnf_args = [ + dnf_args += [ "--disablerepo=*", f"--repofrompath=iridium-iso,{iso_repo}", "--enablerepo=iridium-iso", "--nogpgcheck", + "--offline", # Supported by dnf5 "--setopt=iridium-iso.gpgcheck=0", "--setopt=metadata_expire=-1", ] @@ -259,10 +272,9 @@ def install_minimal_os(mount_root, releasever=None): if os.path.exists("/run/initramfs/live/LiveOS/squashfs.img"): logger.warning("Detected Fedora Live environment, but no RPM repository was found on the media.") logger.warning("Workstation Live ISOs usually do not contain a DNF repository for offline installation.") - logger.warning("Consider using an 'Everything' or 'Server' ISO for offline package-based install.") logger.warning("ISO repository not found. DNF will attempt to use network.") - dnf_args = [] + dnf_args.append("--use-host-config") cmd = [ "dnf", @@ -270,13 +282,17 @@ def install_minimal_os(mount_root, releasever=None): "-y", f"--installroot={mount_root}", f"--releasever={releasever}", - "--use-host-config", - "--setopt=install_weak_deps=False", - "--nodocs", ] cmd += dnf_args + packages + # Copy resolv.conf if it exists (some scriptlets might need it for UID/GID lookups) + try: + os.makedirs(os.path.join(mount_root, "etc"), exist_ok=True) + if os.path.exists("/etc/resolv.conf"): + subprocess.run(["cp", "/etc/resolv.conf", os.path.join(mount_root, "etc/resolv.conf")]) + except Exception: pass + with mount_pseudo_fs(mount_root): run_command(cmd)