Defer the target's UKI rebuild until the final Limine build
The existing hook mask only covers pacstrap. pacman.conf(5) HookDir is absolute, so it never applied to the pacman runs inside arch-chroot, and during omarchy-setup-system the target's own hooks are live. limine-entry-tool's 90-mkinitcpio-install.hook triggers on usr/lib/firmware/*, usr/src/*/dkms.conf and usr/lib/modules/*/pkgbase, and collect_pkgbase_files sets rebuild_all for any trigger outside usr/lib/modules — so it rebuilds the initramfs and UKI for every installed kernel. The hardware scripts hit that routinely: sof-firmware on Intel audio, nvidia-open-dkms, linux-ptl on Panther Lake, linux-t2 on Macs. Every one of those builds is discarded. finalize_limine_boot runs limine-update immediately after, which pipes "rebuild" into the same script and rebuilds unconditionally. They are stale as well as redundant: hardware/nvidia.sh writes its mkinitcpio drop-in after installing the driver. Mask that one hook inside the target for the duration of the phase. The kernel-removal hooks stay live — they only prune Limine entries, which is what ptl-kernel.sh's "pacman -Rdd linux" needs. A mask that survived would be invisible until a later kernel update shipped a UKI-less boot, so it is restored in a finally, again in main()'s finally, and validate_boot repairs and then insists the real hook is back before reboot. limine-mkinitcpio-hook is a hard dependency of the Omarchy runtime package, so that file is always on disk before the mask goes up. Worth several seconds per avoided build on real hardware. It is invisible in the VM harness, which installs no hardware packages, so measure it on an Intel laptop rather than in CI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
436f37f926
commit
c2b93afe01
2 files changed
+88
-17
No files matched your search
@@ -64,7 +64,11 @@ def main() -> int:
|
||||
|
||||
info(f"Installing Omarchy for {ctx.username} → {ctx.target}")
|
||||
|
||||
from .phases_impl import cleanup_bind_mounts, cleanup_protected_state
|
||||
from .phases_impl import (
|
||||
cleanup_bind_mounts,
|
||||
cleanup_protected_state,
|
||||
cleanup_target_hook_masks,
|
||||
)
|
||||
|
||||
success = False
|
||||
try:
|
||||
@@ -82,6 +86,7 @@ def main() -> int:
|
||||
return 0
|
||||
finally:
|
||||
cleanup_bind_mounts(ctx)
|
||||
cleanup_target_hook_masks(ctx)
|
||||
if not success:
|
||||
cleanup_protected_state(ctx)
|
||||
|
||||
|
||||
@@ -566,6 +566,22 @@ DEFERRED_BOOT_HOOKS = (
|
||||
"90-mkinitcpio-install.hook",
|
||||
)
|
||||
|
||||
# Inside the target chroot, only the install hook is worth deferring.
|
||||
# limine-entry-tool's 90-mkinitcpio-install.hook triggers on usr/lib/firmware/*,
|
||||
# usr/src/*/dkms.conf and usr/lib/modules/*/pkgbase, and anything but a
|
||||
# usr/lib/modules path makes it rebuild the initramfs and UKI for EVERY
|
||||
# installed kernel. omarchy-setup-system's hardware scripts routinely install
|
||||
# such packages (sof-firmware on Intel audio, nvidia-open-dkms, linux-ptl on
|
||||
# Panther Lake, linux-t2 on Macs), so the phase can pay for several full UKI
|
||||
# builds. finalize_limine_boot runs limine-update right after, which pipes
|
||||
# "rebuild" into the same script and rebuilds every kernel unconditionally —
|
||||
# those mid-phase builds are always thrown away, and are stale anyway (nvidia.sh
|
||||
# writes its mkinitcpio drop-in after installing the driver).
|
||||
#
|
||||
# The kernel-removal hooks stay live: they only prune Limine entries, which is
|
||||
# exactly what ptl-kernel.sh's "pacman -Rdd linux" needs.
|
||||
TARGET_DEFERRED_BOOT_HOOKS = ("90-mkinitcpio-install.hook",)
|
||||
|
||||
|
||||
def _drop_archinstall_zram_conf(ctx: InstallContext) -> None:
|
||||
"""Remove the zram-generator.conf archinstall's setup_swap writes directly.
|
||||
@@ -630,18 +646,26 @@ def _is_devnull_symlink(path: Path) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _mask_mkinitcpio_pacman_hooks(ctx: InstallContext) -> None:
|
||||
"""Temporarily suppress boot-image pacman hooks during pacstrap.
|
||||
def _mask_mkinitcpio_pacman_hooks(
|
||||
ctx: InstallContext,
|
||||
root: Path = Path("/"),
|
||||
names: tuple[str, ...] = DEFERRED_BOOT_HOOKS,
|
||||
) -> None:
|
||||
"""Temporarily suppress boot-image pacman hooks around a package install.
|
||||
|
||||
pacstrap uses the live system's /etc/pacman.conf. pacman.conf(5) notes that
|
||||
HookDir is absolute and the target root is not prepended, so target-side
|
||||
/mnt/etc/pacman.d/hooks masks do not override target /usr/share/libalpm
|
||||
hooks during installation. Mask the live HookDir instead; the target's real
|
||||
hooks still get installed and become active after reboot.
|
||||
With the default root this masks the LIVE hook dir, which is what pacstrap
|
||||
reads: pacstrap uses the live system's /etc/pacman.conf, and pacman.conf(5)
|
||||
notes that HookDir is absolute and the target root is not prepended, so
|
||||
target-side /mnt/etc/pacman.d/hooks masks do not override target
|
||||
/usr/share/libalpm hooks during installation. The target's real hooks still
|
||||
get installed and become active after reboot.
|
||||
|
||||
Passing ctx.target masks the same way inside the target, for pacman runs
|
||||
that happen under arch-chroot (see TARGET_DEFERRED_BOOT_HOOKS).
|
||||
"""
|
||||
hooks_dir = Path("/etc/pacman.d/hooks")
|
||||
hooks_dir = root / "etc/pacman.d/hooks"
|
||||
hooks_dir.mkdir(parents=True, exist_ok=True)
|
||||
for name in DEFERRED_BOOT_HOOKS:
|
||||
for name in names:
|
||||
path = hooks_dir / name
|
||||
backup = hooks_dir / f"{name}.omarchy-backup"
|
||||
if _is_devnull_symlink(path):
|
||||
@@ -652,9 +676,13 @@ def _mask_mkinitcpio_pacman_hooks(ctx: InstallContext) -> None:
|
||||
path.symlink_to("/dev/null")
|
||||
|
||||
|
||||
def _unmask_mkinitcpio_pacman_hooks(ctx: InstallContext) -> None:
|
||||
hooks_dir = Path("/etc/pacman.d/hooks")
|
||||
for name in DEFERRED_BOOT_HOOKS:
|
||||
def _unmask_mkinitcpio_pacman_hooks(
|
||||
ctx: InstallContext,
|
||||
root: Path = Path("/"),
|
||||
names: tuple[str, ...] = DEFERRED_BOOT_HOOKS,
|
||||
) -> None:
|
||||
hooks_dir = root / "etc/pacman.d/hooks"
|
||||
for name in names:
|
||||
path = hooks_dir / name
|
||||
backup = hooks_dir / f"{name}.omarchy-backup"
|
||||
try:
|
||||
@@ -1080,10 +1108,14 @@ def _run_target_setup_command(ctx: InstallContext, cmd: list[str], *, user: str
|
||||
|
||||
|
||||
def run_system_finalizer(ctx: InstallContext) -> None:
|
||||
_run_target_setup_command(
|
||||
ctx,
|
||||
["/usr/bin/omarchy-setup-system", "--install-user", ctx.username, "--first-install"],
|
||||
)
|
||||
_mask_mkinitcpio_pacman_hooks(ctx, ctx.target, TARGET_DEFERRED_BOOT_HOOKS)
|
||||
try:
|
||||
_run_target_setup_command(
|
||||
ctx,
|
||||
["/usr/bin/omarchy-setup-system", "--install-user", ctx.username, "--first-install"],
|
||||
)
|
||||
finally:
|
||||
_unmask_mkinitcpio_pacman_hooks(ctx, ctx.target, TARGET_DEFERRED_BOOT_HOOKS)
|
||||
|
||||
|
||||
def finalize_limine_boot(ctx: InstallContext) -> None:
|
||||
@@ -1261,6 +1293,8 @@ def configure_login(ctx: InstallContext) -> None:
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def validate_boot(ctx: InstallContext) -> None:
|
||||
_assert_boot_hooks_restored(ctx)
|
||||
|
||||
boot = _boot_intent(ctx)
|
||||
storage = _storage_intent(ctx)
|
||||
esp_mount = ctx.target / boot["esp_mount"].lstrip("/")
|
||||
@@ -1306,6 +1340,30 @@ def validate_boot(ctx: InstallContext) -> None:
|
||||
_validate_pre_mounted_filesystems(ctx)
|
||||
|
||||
|
||||
def _assert_boot_hooks_restored(ctx: InstallContext) -> None:
|
||||
"""Never hand over a system whose UKI rebuild hook is still masked.
|
||||
|
||||
run_system_finalizer defers 90-mkinitcpio-install.hook inside the target and
|
||||
restores it in a finally, but a mask that survived would be invisible until
|
||||
the first kernel update shipped a UKI-less boot. Repair, then insist.
|
||||
"""
|
||||
cleanup_target_hook_masks(ctx)
|
||||
|
||||
hooks_dir = ctx.target / "etc/pacman.d/hooks"
|
||||
for name in TARGET_DEFERRED_BOOT_HOOKS:
|
||||
path = hooks_dir / name
|
||||
if _is_devnull_symlink(path):
|
||||
raise RuntimeError(f"{path} is still masked to /dev/null")
|
||||
backup = hooks_dir / f"{name}.omarchy-backup"
|
||||
if backup.exists() or backup.is_symlink():
|
||||
raise RuntimeError(f"{backup} left behind by the install-time hook mask")
|
||||
# limine-mkinitcpio-hook is a hard dependency of the Omarchy runtime
|
||||
# package, so the real hook is on disk before the mask ever goes up and
|
||||
# must be on disk again now.
|
||||
if not path.is_file():
|
||||
raise RuntimeError(f"{path} is missing — future kernel updates would ship no UKI")
|
||||
|
||||
|
||||
# Every kernel package leaves its pkgbase next to its modules, which is also
|
||||
# the name limine-mkinitcpio-hook builds the UKI under.
|
||||
def _installed_kernels(ctx: InstallContext) -> list[str]:
|
||||
@@ -1349,6 +1407,14 @@ def cleanup_bind_mounts(ctx: InstallContext) -> None:
|
||||
ctx.state["bind_mounts"] = []
|
||||
|
||||
|
||||
def cleanup_target_hook_masks(ctx: InstallContext) -> None:
|
||||
"""Restore the target's deferred boot hooks. Idempotent, and a no-op when
|
||||
nothing was masked, so main()'s finally can call it on any exit path: an
|
||||
interrupt must never leave the installed system with its UKI rebuild hook
|
||||
pointing at /dev/null."""
|
||||
_unmask_mkinitcpio_pacman_hooks(ctx, ctx.target, TARGET_DEFERRED_BOOT_HOOKS)
|
||||
|
||||
|
||||
def cleanup_protected_state(ctx: InstallContext) -> None:
|
||||
"""Tear down protected-mode mounts and LUKS mapper after a failed install.
|
||||
|
||||
|
||||
Reference in new issue
Block a user