orchestrator: install Limine ourselves; stop creating arch-limine entries to delete later
archinstall's add_bootloader creates EFI/arch-limine/{BOOTX64.EFI, BOOTIA32.EFI}, writes a limine.conf with an Arch Linux entry, and registers an efibootmgr label "Arch Linux Limine Bootloader" — only for limine-snapper.sh to delete it all and rebuild with Omarchy branding via limine-mkinitcpio-hook.
Replace the add_bootloader call with _install_limine_omarchy, which does the same work archinstall's _add_limine_bootloader does (UEFI EFI binary copy + efibootmgr register, or BIOS limine-bios.sys + limine bios-install) but with Omarchy-owned paths and labels from the start:
- EFI subdir: EFI/limine (not EFI/arch-limine)
- efibootmgr: label "Limine" (not "Arch Linux Limine Bootloader")
- loader path: \EFI\limine\BOOTX64.EFI
Compute the kernel cmdline directly from disk_config instead of harvesting it from a file archinstall wrote (whose path keeps drifting across versions). Then write /etc/default/limine and /etc/kernel/cmdline, and stage /boot/limine.conf from our styling template — limine-mkinitcpio-hook will add the actual boot entries when it deploys.
Adds limine + efibootmgr to EARLY_PACKAGES (archinstall used to pacstrap these as a side effect of add_bootloader).
Does not modify limine-snapper.sh — its cleanup code will simply no-op because there are no arch-limine paths or "Arch Linux Limine Bootloader" entries to find. That code can be pruned in a follow-up.
This commit is contained in:
2 files changed
+185
-58
No files matched your search
@@ -19,7 +19,6 @@ The canonical call sequence (mirrored from archinstall.scripts.guided.py) is:
|
||||
inst.minimal_installation(...) # base + linux pacstrap
|
||||
inst.set_mirrors(handler, mirror_config, on_target=True)
|
||||
inst.setup_swap(algo=...)
|
||||
inst.add_bootloader(bootloader, uki, removable)
|
||||
inst.create_users(users)
|
||||
inst.add_additional_packages(packages)
|
||||
inst.set_timezone(tz)
|
||||
@@ -28,9 +27,9 @@ The canonical call sequence (mirrored from archinstall.scripts.guided.py) is:
|
||||
inst.enable_service(services)
|
||||
inst.genfstab()
|
||||
|
||||
Our orchestrator interleaves `write_limine_config` between `add_bootloader`
|
||||
and the first `add_additional_packages` so the limine UKI hook fires once,
|
||||
correctly, on its first install.
|
||||
Our orchestrator installs Omarchy's Limine files directly instead of invoking
|
||||
archinstall's bootloader helper, so EFI paths and efibootmgr labels are ours
|
||||
from the start.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -45,6 +44,8 @@ from archinstall.lib.args import ArchConfig, ArchConfigHandler
|
||||
from archinstall.lib.authentication.authentication_handler import AuthenticationHandler
|
||||
from archinstall.lib.configuration import ConfigurationOutput
|
||||
from archinstall.lib.disk.filesystem import FilesystemHandler
|
||||
from archinstall.lib.disk.utils import get_parent_device_path, get_unique_path_for_device
|
||||
from archinstall.lib.hardware import SysInfo
|
||||
from archinstall.lib.installer import Installer
|
||||
from archinstall.lib.mirror.mirror_handler import MirrorListHandler
|
||||
from archinstall.lib.models import Bootloader
|
||||
@@ -121,11 +122,28 @@ def is_pre_mount(arch_config: ArchConfig) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def bootloader_enabled(arch_config: ArchConfig) -> bool:
|
||||
bl = arch_config.bootloader_config
|
||||
return bool(bl and bl.bootloader != Bootloader.NO_BOOTLOADER)
|
||||
|
||||
|
||||
def is_limine(arch_config: ArchConfig) -> bool:
|
||||
bl = arch_config.bootloader_config
|
||||
return bool(bl and bl.bootloader == Bootloader.Limine)
|
||||
|
||||
|
||||
def has_uefi() -> bool:
|
||||
return SysInfo.has_uefi()
|
||||
|
||||
|
||||
def parent_device_path(dev_path: Path) -> Path:
|
||||
return get_parent_device_path(dev_path)
|
||||
|
||||
|
||||
def unique_device_path(dev_path: Path) -> Path | None:
|
||||
return get_unique_path_for_device(dev_path)
|
||||
|
||||
|
||||
def root_user(arch_config: ArchConfig) -> User | None:
|
||||
auth = arch_config.auth_config
|
||||
if not auth or not auth.root_enc_password:
|
||||
|
||||
@@ -6,8 +6,8 @@ Phase ordering (full-disk):
|
||||
handlers (archinstall patch happens in the
|
||||
wrapper before Python imports archinstall)
|
||||
arch_install_full → archinstall-driven install (partition, base,
|
||||
bootloader, write limine config, early omarchy
|
||||
pkgs, useradd, runtime omarchy pkgs)
|
||||
early omarchy pkgs, Omarchy Limine setup,
|
||||
useradd, runtime omarchy pkgs)
|
||||
run_chroot_finalizer → bind mounts + sudoers shim + arch-chroot finalize.sh
|
||||
configure_login → sddm autologin for unencrypted installs
|
||||
validate_boot_full → assert UKI / limine.conf / kernel cmdline are sane
|
||||
@@ -36,6 +36,7 @@ import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
from . import archinstall_adapter as arch
|
||||
@@ -50,6 +51,8 @@ from .ui import confirm, info
|
||||
EARLY_PACKAGES = [
|
||||
"base-devel",
|
||||
"git",
|
||||
"limine",
|
||||
"efibootmgr",
|
||||
"omarchy-keyring",
|
||||
"omarchy-settings",
|
||||
]
|
||||
@@ -98,9 +101,9 @@ def _install_disk(ctx: InstallContext) -> str | None:
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# arch_install_full: everything inside a single Installer context manager.
|
||||
# Reorders guided.py's perform_installation() so our limine config write
|
||||
# lands between add_bootloader and the first add_additional_packages call,
|
||||
# and user creation happens AFTER early omarchy packages populate /etc/skel.
|
||||
# Reorders guided.py's perform_installation() so early Omarchy packages install
|
||||
# before user creation and before our Omarchy-owned Limine setup copies files
|
||||
# from the target's limine package.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def arch_install_full(ctx: InstallContext) -> None:
|
||||
@@ -147,20 +150,20 @@ def arch_install_full(ctx: InstallContext) -> None:
|
||||
if config.swap and config.swap.enabled:
|
||||
installer.setup_swap(algo=config.swap.algorithm)
|
||||
|
||||
info("› installing bootloader (Limine)")
|
||||
if config.bootloader_config:
|
||||
installer.add_bootloader(
|
||||
config.bootloader_config.bootloader,
|
||||
config.bootloader_config.uki,
|
||||
config.bootloader_config.removable,
|
||||
)
|
||||
|
||||
info("› writing Limine config (so limine-mkinitcpio-hook fires correctly)")
|
||||
_write_limine_defaults(ctx)
|
||||
|
||||
info(f"› installing early Omarchy packages: {', '.join(EARLY_PACKAGES)}")
|
||||
installer.add_additional_packages(EARLY_PACKAGES)
|
||||
|
||||
if arch.bootloader_enabled(config):
|
||||
if not arch.is_limine(config):
|
||||
raise RuntimeError(
|
||||
"Omarchy full-disk installs only support Limine bootloader setup"
|
||||
)
|
||||
info("› installing bootloader (Limine)")
|
||||
_install_limine_omarchy(ctx, installer, config)
|
||||
|
||||
info("› writing Limine config (so limine-mkinitcpio-hook fires correctly)")
|
||||
_write_limine_defaults_from_config(ctx, installer, config)
|
||||
|
||||
info("› creating user (with /etc/skel populated)")
|
||||
if config.auth_config and config.auth_config.users:
|
||||
installer.create_users(config.auth_config.users)
|
||||
@@ -179,57 +182,163 @@ def arch_install_full(ctx: InstallContext) -> None:
|
||||
installer.genfstab()
|
||||
|
||||
|
||||
def _write_limine_defaults(ctx: InstallContext) -> None:
|
||||
if not arch.is_limine(ctx.state["arch_config_handler"].config):
|
||||
return
|
||||
def _install_limine_omarchy(ctx: InstallContext, installer, config) -> None:
|
||||
boot_partition = installer._get_boot_partition()
|
||||
efi_partition = installer._get_efi_partition()
|
||||
root = installer._get_root()
|
||||
|
||||
# archinstall 4.3 writes limine.conf to one of:
|
||||
# <target>/<esp_mount>/EFI/arch-limine/limine.conf (UEFI, non-removable)
|
||||
# <target>/<esp_mount>/EFI/BOOT/limine.conf (UEFI, removable)
|
||||
# <target>/boot/limine/limine.conf (BIOS or fallback)
|
||||
# Probe in that order and use whichever exists.
|
||||
candidates = [
|
||||
ctx.target / "boot" / "EFI" / "arch-limine" / "limine.conf",
|
||||
ctx.target / "boot" / "EFI" / "BOOT" / "limine.conf",
|
||||
ctx.target / "boot" / "limine" / "limine.conf",
|
||||
ctx.target / "boot" / "limine.conf", # very-old archinstall path
|
||||
]
|
||||
limine_conf = next((p for p in candidates if p.exists()), None)
|
||||
if limine_conf is None:
|
||||
searched = "\n ".join(str(p) for p in candidates)
|
||||
raise RuntimeError(
|
||||
"limine.conf not found after add_bootloader. Searched:\n " + searched
|
||||
if boot_partition is None:
|
||||
raise RuntimeError(f"Could not detect boot at mountpoint {ctx.target}")
|
||||
if root is None:
|
||||
raise RuntimeError(f"Could not detect root at mountpoint {ctx.target}")
|
||||
|
||||
bootloader_config = config.bootloader_config
|
||||
bootloader_removable = bool(
|
||||
getattr(bootloader_config, "removable", False) if bootloader_config else False
|
||||
)
|
||||
limine_path = ctx.target / "usr" / "share" / "limine"
|
||||
|
||||
if arch.has_uefi():
|
||||
if efi_partition is None:
|
||||
raise RuntimeError("Could not detect EFI partition")
|
||||
if not efi_partition.mountpoint:
|
||||
raise RuntimeError("EFI partition is not mounted")
|
||||
|
||||
parent_dev_path = arch.parent_device_path(efi_partition.safe_dev_path)
|
||||
efi_dir_path = ctx.target / efi_partition.mountpoint.relative_to("/") / "EFI"
|
||||
efi_dir_path_target = efi_partition.mountpoint / "EFI"
|
||||
if bootloader_removable:
|
||||
efi_dir_path = efi_dir_path / "BOOT"
|
||||
efi_dir_path_target = efi_dir_path_target / "BOOT"
|
||||
else:
|
||||
# Non-removable UEFI installs place the x64 binary at EFI/limine/BOOTX64.EFI.
|
||||
efi_dir_path = efi_dir_path / "limine"
|
||||
efi_dir_path_target = efi_dir_path_target / "limine"
|
||||
|
||||
efi_dir_path.mkdir(parents=True, exist_ok=True)
|
||||
for filename in ("BOOTIA32.EFI", "BOOTX64.EFI"):
|
||||
_copy_required(limine_path / filename, efi_dir_path / filename)
|
||||
|
||||
hook_command = (
|
||||
f"/usr/bin/cp /usr/share/limine/BOOTIA32.EFI {efi_dir_path_target}/ && "
|
||||
f"/usr/bin/cp /usr/share/limine/BOOTX64.EFI {efi_dir_path_target}/"
|
||||
)
|
||||
|
||||
cmdline = _extract_cmdline(limine_conf)
|
||||
loader_path = _limine_efi_loader_path(bootloader_removable)
|
||||
subprocess.run(
|
||||
[
|
||||
"efibootmgr",
|
||||
"--create",
|
||||
"--disk", str(parent_dev_path),
|
||||
"--part", str(efi_partition.partn),
|
||||
"--label", "Limine",
|
||||
"--loader", loader_path,
|
||||
"--unicode",
|
||||
"--verbose",
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
else:
|
||||
boot_limine_path = ctx.target / "boot" / "limine"
|
||||
boot_limine_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
parent_dev_path = arch.parent_device_path(boot_partition.safe_dev_path)
|
||||
if unique_path := arch.unique_device_path(parent_dev_path):
|
||||
parent_dev_path = unique_path
|
||||
|
||||
_copy_required(limine_path / "limine-bios.sys", boot_limine_path / "limine-bios.sys")
|
||||
subprocess.run(
|
||||
["arch-chroot", str(ctx.target), "limine", "bios-install", str(parent_dev_path)],
|
||||
check=True,
|
||||
)
|
||||
hook_command = (
|
||||
f"/usr/bin/limine bios-install {parent_dev_path} && "
|
||||
"/usr/bin/cp /usr/share/limine/limine-bios.sys /boot/limine/"
|
||||
)
|
||||
|
||||
_write_limine_pacman_hook(ctx.target, hook_command)
|
||||
installer._helper_flags["bootloader"] = "limine"
|
||||
|
||||
|
||||
def _copy_required(src: Path, dst: Path) -> None:
|
||||
if not src.exists():
|
||||
raise RuntimeError(f"Required Limine file missing: {src}")
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(src, dst)
|
||||
|
||||
|
||||
def _limine_efi_loader_path(bootloader_removable: bool) -> str:
|
||||
try:
|
||||
efi_bitness = Path("/sys/firmware/efi/fw_platform_size").read_text().strip()
|
||||
except Exception as err:
|
||||
raise RuntimeError(
|
||||
"Could not read /sys/firmware/efi/fw_platform_size to determine EFI bitness"
|
||||
) from err
|
||||
|
||||
if efi_bitness == "64":
|
||||
return "\\EFI\\BOOT\\BOOTX64.EFI" if bootloader_removable else "\\EFI\\limine\\BOOTX64.EFI"
|
||||
if efi_bitness == "32":
|
||||
return "\\EFI\\BOOT\\BOOTIA32.EFI" if bootloader_removable else "\\EFI\\limine\\BOOTIA32.EFI"
|
||||
raise RuntimeError(f'EFI bitness is neither 32 nor 64 bits. Found "{efi_bitness}".')
|
||||
|
||||
|
||||
def _write_limine_pacman_hook(target: Path, hook_command: str) -> None:
|
||||
hook_contents = textwrap.dedent(
|
||||
f"""\
|
||||
[Trigger]
|
||||
Operation = Upgrade
|
||||
Type = Package
|
||||
Target = limine
|
||||
|
||||
[Action]
|
||||
Description = Deploying Omarchy Limine after upgrade...
|
||||
When = PostTransaction
|
||||
Exec = /bin/sh -c "{hook_command}"
|
||||
"""
|
||||
)
|
||||
hooks_dir = target / "etc" / "pacman.d" / "hooks"
|
||||
hooks_dir.mkdir(parents=True, exist_ok=True)
|
||||
(hooks_dir / "99-omarchy-limine.hook").write_text(hook_contents)
|
||||
|
||||
|
||||
def _write_limine_defaults_from_config(ctx: InstallContext, installer, config) -> None:
|
||||
if not arch.is_limine(config):
|
||||
return
|
||||
|
||||
root = installer._get_root()
|
||||
if root is None:
|
||||
raise RuntimeError(f"Could not detect root at mountpoint {ctx.target}")
|
||||
|
||||
cmdline = " ".join(installer._get_kernel_params(root))
|
||||
if not cmdline.strip():
|
||||
raise RuntimeError("Could not extract kernel cmdline from limine.conf")
|
||||
raise RuntimeError("Could not compute kernel cmdline from install config")
|
||||
if "root=" not in cmdline:
|
||||
raise RuntimeError(f"Extracted cmdline has no root=: {cmdline!r}")
|
||||
|
||||
# Template lives in omarchy-installer's install tree, present on the live
|
||||
# ISO via the omarchy-installer package.
|
||||
template = ctx.omarchy_path / "install" / "assets" / "limine" / "default.conf"
|
||||
if not template.exists():
|
||||
template = ctx.omarchy_path / "default" / "limine" / "default.conf"
|
||||
if not template.exists():
|
||||
raise RuntimeError(f"Limine template not found at {template}")
|
||||
raise RuntimeError(f"Computed cmdline has no root=: {cmdline!r}")
|
||||
|
||||
default_template = _limine_template(ctx, "default.conf")
|
||||
default_limine = ctx.target / "etc" / "default" / "limine"
|
||||
default_limine.parent.mkdir(parents=True, exist_ok=True)
|
||||
default_limine.write_text(template.read_text().replace("@@CMDLINE@@", cmdline))
|
||||
default_limine.write_text(default_template.read_text().replace("@@CMDLINE@@", cmdline))
|
||||
|
||||
kernel_cmdline = ctx.target / "etc" / "kernel" / "cmdline"
|
||||
kernel_cmdline.parent.mkdir(parents=True, exist_ok=True)
|
||||
kernel_cmdline.write_text(cmdline + "\n")
|
||||
|
||||
limine_conf = ctx.target / "boot" / "limine.conf"
|
||||
limine_conf.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(_limine_template(ctx, "limine.conf"), limine_conf)
|
||||
|
||||
def _extract_cmdline(limine_conf: Path) -> str:
|
||||
for line in limine_conf.read_text().splitlines():
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("cmdline:"):
|
||||
return stripped[len("cmdline:"):].strip()
|
||||
return ""
|
||||
|
||||
def _limine_template(ctx: InstallContext, filename: str) -> Path:
|
||||
candidates = [
|
||||
ctx.omarchy_path / "install" / "assets" / "limine" / filename,
|
||||
ctx.omarchy_path / "default" / "limine" / filename,
|
||||
]
|
||||
for candidate in candidates:
|
||||
if candidate.exists():
|
||||
return candidate
|
||||
searched = "\n ".join(str(p) for p in candidates)
|
||||
raise RuntimeError(f"Limine template {filename} not found. Searched:\n {searched}")
|
||||
|
||||
|
||||
def _runtime_package_list(ctx: InstallContext) -> list[str]:
|
||||
@@ -345,7 +454,7 @@ def arch_install_base(ctx: InstallContext) -> None:
|
||||
info("› installing Omarchy runtime + omarchy-base.packages")
|
||||
installer.add_additional_packages(_runtime_package_list(ctx))
|
||||
|
||||
# Protected mode never calls add_bootloader, so pacstrap limine +
|
||||
# Protected mode owns boot setup separately, so pacstrap limine +
|
||||
# efibootmgr here while the live ISO's offline mirror is still the
|
||||
# active pacman source. Doing it later via arch-chroot would hit
|
||||
# the target's network-mirror pacman.conf and fail offline.
|
||||
|
||||
Reference in new issue
Block a user