Further improvements to dnf robustness and repo discovery for offline installs

This commit is contained in:
2026-02-05 19:39:54 +01:00
parent 951a1b7fdc
commit 5df00a5814

View File

@@ -79,7 +79,8 @@ def mount_pseudo_fs(mount_root):
Context manager to bind mount /dev, /proc, /sys, /run, and efivarfs into 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}...") 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 = [] mounted_paths = []
try: try:
@@ -89,6 +90,14 @@ def mount_pseudo_fs(mount_root):
run_command(["mount", "--bind", f"/{fs}", target]) run_command(["mount", "--bind", f"/{fs}", target])
mounted_paths.append(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 # Mount efivarfs if it exists on the host
efivars_path = "/sys/firmware/efi/efivars" efivars_path = "/sys/firmware/efi/efivars"
if os.path.exists(efivars_path): if os.path.exists(efivars_path):
@@ -135,14 +144,19 @@ def find_iso_repo():
"/run/initramfs/isoscan", "/run/initramfs/isoscan",
] ]
# 1. Check known paths for repodata or media.repo def check_path(p):
for path in possible_paths: if os.path.exists(os.path.join(p, "repodata")) or os.path.exists(os.path.join(p, "media.repo")):
if os.path.exists(os.path.join(path, "repodata")) or os.path.exists(os.path.join(path, "media.repo")): return p
return path
for sub in ["os", "Packages", "BaseOS", "AppStream"]: for sub in ["os", "Packages", "BaseOS", "AppStream"]:
sub_path = os.path.join(path, sub) sub_p = os.path.join(p, sub)
if os.path.exists(os.path.join(sub_path, "repodata")) or os.path.exists(os.path.join(sub_path, "media.repo")): if os.path.exists(os.path.join(sub_p, "repodata")) or os.path.exists(os.path.join(sub_p, "media.repo")):
return sub_path 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 # 2. Check all mounted filesystems
try: try:
@@ -151,48 +165,35 @@ def find_iso_repo():
for mount in res.stdout.splitlines(): for mount in res.stdout.splitlines():
if mount.startswith("/proc") or mount.startswith("/sys") or mount.startswith("/dev"): if mount.startswith("/proc") or mount.startswith("/sys") or mount.startswith("/dev"):
continue continue
if os.path.exists(os.path.join(mount, "repodata")) or os.path.exists(os.path.join(mount, "media.repo")): res = check_path(mount)
return mount if res: return res
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)
except Exception as e: except Exception as e:
logger.debug(f"Error searching mount points: {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"]: for dev in ["/dev/sr0", "/dev/cdrom"]:
try: try:
if os.path.exists(dev): if os.path.exists(dev):
res = subprocess.run(["findmnt", "-n", dev], capture_output=True) res = subprocess.run(["findmnt", "-n", dev], capture_output=True)
if res.returncode != 0: if res.returncode != 0:
logger.info(f"Found {dev} but not mounted. Attempting to mount to /mnt...")
try: try:
subprocess.run(["mount", "-o", "ro", dev, "/mnt"], check=True) os.makedirs("/mnt/iso", exist_ok=True)
if os.path.exists("/mnt/repodata") or os.path.exists("/mnt/media.repo"): subprocess.run(["mount", "-o", "ro", dev, "/mnt/iso"], check=True)
return "/mnt" res = check_path("/mnt/iso")
for sub in ["os", "Packages", "BaseOS", "AppStream"]: if res: return res
if os.path.exists(os.path.join("/mnt", sub, "repodata")) or os.path.exists(os.path.join("/mnt", sub, "media.repo")): except Exception: pass
return os.path.join("/mnt", sub) except Exception: pass
except Exception as e:
logger.debug(f"Failed to mount {dev}: {e}")
except Exception:
pass
# 4. Check /run/media # 4. Check /run/media deeper
if os.path.exists("/run/media"): if os.path.exists("/run/media"):
try: try:
for user in os.listdir("/run/media"): for root, dirs, files in os.walk("/run/media"):
user_path = os.path.join("/run/media", user) if "repodata" in dirs or "media.repo" in files:
if os.path.isdir(user_path): return root
for label in os.listdir(user_path): # Limit depth for performance
label_path = os.path.join(user_path, label) if root.count(os.sep) > 5:
if os.path.exists(os.path.join(label_path, "repodata")) or os.path.exists(os.path.join(label_path, "media.repo")): del dirs[:]
return label_path except Exception: pass
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
# 5. Last resort: any directory with .rpm files # 5. Last resort: any directory with .rpm files
for path in possible_paths: for path in possible_paths:
@@ -243,14 +244,26 @@ def install_minimal_os(mount_root, releasever=None):
# Offline installation logic # Offline installation logic
iso_repo = find_iso_repo() 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: if iso_repo:
logger.info(f"Found ISO repository at {iso_repo}. Using strictly offline mode.") logger.info(f"Found ISO repository at {iso_repo}. Using strictly offline mode.")
dnf_args = [ dnf_args += [
"--disablerepo=*", "--disablerepo=*",
f"--repofrompath=iridium-iso,{iso_repo}", f"--repofrompath=iridium-iso,{iso_repo}",
"--enablerepo=iridium-iso", "--enablerepo=iridium-iso",
"--nogpgcheck", "--nogpgcheck",
"--offline", # Supported by dnf5
"--setopt=iridium-iso.gpgcheck=0", "--setopt=iridium-iso.gpgcheck=0",
"--setopt=metadata_expire=-1", "--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"): 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("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("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.") logger.warning("ISO repository not found. DNF will attempt to use network.")
dnf_args = [] dnf_args.append("--use-host-config")
cmd = [ cmd = [
"dnf", "dnf",
@@ -270,13 +282,17 @@ def install_minimal_os(mount_root, releasever=None):
"-y", "-y",
f"--installroot={mount_root}", f"--installroot={mount_root}",
f"--releasever={releasever}", f"--releasever={releasever}",
"--use-host-config",
"--setopt=install_weak_deps=False",
"--nodocs",
] ]
cmd += dnf_args + packages 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): with mount_pseudo_fs(mount_root):
run_command(cmd) run_command(cmd)