orchestrator: implement protected-mode boot (Limine in EFI/Omarchy, fstab/crypttab/mkinitcpio, efibootmgr with BootOrder preservation)
This commit is contained in:
1 file changed
+350
-7
@@ -33,6 +33,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
@@ -340,12 +341,312 @@ def arch_install_base(ctx: InstallContext) -> None:
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# configure_protected_boot: Omarchy-owned fstab/crypttab/mkinitcpio/bootloader.
|
||||
# Implemented in Step 8.
|
||||
#
|
||||
# Order:
|
||||
# 1. fstab — written from our known mount intent (no genfstab)
|
||||
# 2. crypttab + mkinitcpio.conf — encrypt/btrfs hooks + initramfs btrfs deps
|
||||
# 3. mkinitcpio -P — generate /boot/initramfs-{kernel}{,-fallback}.img
|
||||
# 4. Limine into EFI/Omarchy — never touches EFI/Microsoft or EFI/BOOT
|
||||
# 5. efibootmgr entry, BootOrder preserves whatever was there + Omarchy first
|
||||
# 6. Sanity: Windows entry must survive
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def configure_protected_boot(ctx: InstallContext) -> None:
|
||||
raise NotImplementedError(
|
||||
"Implemented in Step 8 — see REFACTOR-PLAN-C3-EXECUTION.md"
|
||||
protected = ctx.state["protected"]
|
||||
|
||||
info("› ensuring limine + efibootmgr installed in target")
|
||||
subprocess.run(
|
||||
["arch-chroot", str(ctx.target), "pacman", "-S",
|
||||
"--needed", "--noconfirm", "limine", "efibootmgr"],
|
||||
check=True,
|
||||
)
|
||||
|
||||
info("› writing /etc/fstab")
|
||||
_write_fstab(ctx, protected)
|
||||
|
||||
if protected.get("luks_uuid"):
|
||||
info("› writing /etc/crypttab.initramfs")
|
||||
_write_crypttab(ctx, protected)
|
||||
|
||||
info("› editing /etc/mkinitcpio.conf")
|
||||
_edit_mkinitcpio_conf(ctx, protected)
|
||||
|
||||
info("› running mkinitcpio -P")
|
||||
_run_mkinitcpio(ctx)
|
||||
|
||||
info("› capturing efibootmgr state pre-install")
|
||||
pre_state = _read_efibootmgr()
|
||||
windows_before = _find_label_entries(pre_state["entries"], "Windows")
|
||||
|
||||
info("› installing Limine into EFI/Omarchy")
|
||||
_install_limine(ctx, protected)
|
||||
|
||||
info("› registering efibootmgr entry")
|
||||
_register_efibootmgr_entry(ctx, protected, pre_state)
|
||||
|
||||
info("› verifying Windows boot entry survived")
|
||||
post_state = _read_efibootmgr()
|
||||
windows_after = _find_label_entries(post_state["entries"], "Windows")
|
||||
if windows_before and not windows_after:
|
||||
raise RuntimeError(
|
||||
"Windows boot entry disappeared during Limine install — aborting"
|
||||
)
|
||||
|
||||
|
||||
# ── fstab ────────────────────────────────────────────────────────────────────
|
||||
|
||||
def _btrfs_root_device(protected: dict) -> str:
|
||||
"""Device that holds the btrfs filesystem (mapper for encrypted, raw
|
||||
partition for plain)."""
|
||||
if protected.get("luks_uuid"):
|
||||
return "/dev/mapper/omarchy_root"
|
||||
return protected["root_device"]
|
||||
|
||||
|
||||
def _blkid_uuid(device: str) -> str:
|
||||
res = subprocess.run(
|
||||
["blkid", "-s", "UUID", "-o", "value", device],
|
||||
capture_output=True, text=True, check=True,
|
||||
)
|
||||
uuid = res.stdout.strip()
|
||||
if not uuid:
|
||||
raise RuntimeError(f"blkid returned no UUID for {device}")
|
||||
return uuid
|
||||
|
||||
|
||||
def _esp_device(ctx: InstallContext, protected: dict) -> str:
|
||||
esp_mp = ctx.target / protected["esp_mount"].lstrip("/")
|
||||
res = subprocess.run(
|
||||
["findmnt", "-n", "-o", "SOURCE", str(esp_mp)],
|
||||
capture_output=True, text=True, check=True,
|
||||
)
|
||||
dev = res.stdout.strip()
|
||||
if not dev:
|
||||
raise RuntimeError(f"could not resolve ESP device at {esp_mp}")
|
||||
return dev
|
||||
|
||||
|
||||
def _write_fstab(ctx: InstallContext, protected: dict) -> None:
|
||||
btrfs_dev = _btrfs_root_device(protected)
|
||||
btrfs_uuid = _blkid_uuid(btrfs_dev)
|
||||
esp_dev = _esp_device(ctx, protected)
|
||||
esp_uuid = _blkid_uuid(esp_dev)
|
||||
esp_mount = protected["esp_mount"]
|
||||
|
||||
btrfs_opts = "noatime,compress=zstd,subvol="
|
||||
lines = [
|
||||
"# /etc/fstab — generated by omarchy installer (protected mode)",
|
||||
"# <device> <mount> <fs> <options> <dump> <pass>",
|
||||
f"UUID={btrfs_uuid} / btrfs {btrfs_opts}@ 0 0",
|
||||
f"UUID={btrfs_uuid} /home btrfs {btrfs_opts}@home 0 0",
|
||||
f"UUID={btrfs_uuid} /var/log btrfs {btrfs_opts}@log 0 0",
|
||||
f"UUID={btrfs_uuid} /var/cache/pacman/pkg btrfs {btrfs_opts}@pkg 0 0",
|
||||
f"UUID={esp_uuid} {esp_mount} vfat umask=0077 0 2",
|
||||
"",
|
||||
]
|
||||
fstab = ctx.target / "etc" / "fstab"
|
||||
fstab.write_text("\n".join(lines))
|
||||
|
||||
|
||||
# ── crypttab ─────────────────────────────────────────────────────────────────
|
||||
|
||||
def _write_crypttab(ctx: InstallContext, protected: dict) -> None:
|
||||
luks_uuid = protected["luks_uuid"]
|
||||
crypttab = ctx.target / "etc" / "crypttab.initramfs"
|
||||
crypttab.write_text(f"omarchy_root UUID={luks_uuid} none luks,discard\n")
|
||||
|
||||
|
||||
# ── mkinitcpio.conf ──────────────────────────────────────────────────────────
|
||||
|
||||
def _edit_mkinitcpio_conf(ctx: InstallContext, protected: dict) -> None:
|
||||
conf = ctx.target / "etc" / "mkinitcpio.conf"
|
||||
text = conf.read_text()
|
||||
|
||||
if protected.get("luks_uuid"):
|
||||
hooks = (
|
||||
"HOOKS=(base udev autodetect microcode modconf kms keyboard "
|
||||
"keymap consolefont block encrypt filesystems fsck)"
|
||||
)
|
||||
else:
|
||||
hooks = (
|
||||
"HOOKS=(base udev autodetect microcode modconf kms keyboard "
|
||||
"keymap consolefont block filesystems fsck)"
|
||||
)
|
||||
|
||||
text = _replace_assignment(text, "HOOKS", hooks)
|
||||
text = _replace_assignment(text, "MODULES", "MODULES=(btrfs)")
|
||||
text = _replace_assignment(text, "BINARIES", "BINARIES=(/usr/bin/btrfs)")
|
||||
|
||||
conf.write_text(text)
|
||||
|
||||
|
||||
def _replace_assignment(text: str, key: str, new_line: str) -> str:
|
||||
"""Replace a `KEY=(...)` line in mkinitcpio.conf-style text. If the key
|
||||
isn't present, append the line."""
|
||||
pattern = re.compile(rf"^{re.escape(key)}=\(.*\)\s*$", re.MULTILINE)
|
||||
if pattern.search(text):
|
||||
return pattern.sub(new_line, text, count=1)
|
||||
if not text.endswith("\n"):
|
||||
text += "\n"
|
||||
return text + new_line + "\n"
|
||||
|
||||
|
||||
# ── mkinitcpio ───────────────────────────────────────────────────────────────
|
||||
|
||||
def _run_mkinitcpio(ctx: InstallContext) -> None:
|
||||
subprocess.run(
|
||||
["arch-chroot", str(ctx.target), "mkinitcpio", "-P"],
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
# ── Limine ───────────────────────────────────────────────────────────────────
|
||||
|
||||
LIMINE_CONF_TEMPLATE = """\
|
||||
timeout: 3
|
||||
default_entry: 1
|
||||
interface_branding: Omarchy Bootloader
|
||||
|
||||
/Omarchy Linux
|
||||
protocol: linux
|
||||
path: uuid({btrfs_uuid}):/@/boot/vmlinuz-{kernel}
|
||||
cmdline: {cmdline}
|
||||
module_path: uuid({btrfs_uuid}):/@/boot/initramfs-{kernel}.img
|
||||
|
||||
/Omarchy Linux (fallback)
|
||||
protocol: linux
|
||||
path: uuid({btrfs_uuid}):/@/boot/vmlinuz-{kernel}
|
||||
cmdline: {cmdline}
|
||||
module_path: uuid({btrfs_uuid}):/@/boot/initramfs-{kernel}-fallback.img
|
||||
"""
|
||||
|
||||
|
||||
def _omarchy_esp_path(ctx: InstallContext, protected: dict) -> Path:
|
||||
esp_mount = ctx.target / protected["esp_mount"].lstrip("/")
|
||||
return esp_mount / protected["esp_path"].lstrip("/")
|
||||
|
||||
|
||||
def _build_cmdline(protected: dict, btrfs_uuid: str) -> str:
|
||||
if protected.get("luks_uuid"):
|
||||
return (
|
||||
f"cryptdevice=UUID={protected['luks_uuid']}:omarchy_root "
|
||||
"root=/dev/mapper/omarchy_root rw "
|
||||
"rootflags=subvol=@ rootfstype=btrfs quiet splash"
|
||||
)
|
||||
return (
|
||||
f"root=UUID={btrfs_uuid} rw "
|
||||
"rootflags=subvol=@ rootfstype=btrfs quiet splash"
|
||||
)
|
||||
|
||||
|
||||
def _install_limine(ctx: InstallContext, protected: dict) -> None:
|
||||
src = ctx.target / "usr" / "share" / "limine" / "BOOTX64.EFI"
|
||||
if not src.exists():
|
||||
raise RuntimeError(
|
||||
f"Limine EFI binary not found at {src} — limine package missing in target"
|
||||
)
|
||||
|
||||
omarchy_esp = _omarchy_esp_path(ctx, protected)
|
||||
omarchy_esp.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
dst = omarchy_esp / "BOOTX64.EFI"
|
||||
shutil.copy2(src, dst)
|
||||
|
||||
btrfs_uuid = _blkid_uuid(_btrfs_root_device(protected))
|
||||
cmdline = _build_cmdline(protected, btrfs_uuid)
|
||||
conf = LIMINE_CONF_TEMPLATE.format(
|
||||
btrfs_uuid=btrfs_uuid,
|
||||
kernel=protected["kernel"],
|
||||
cmdline=cmdline,
|
||||
)
|
||||
(omarchy_esp / "limine.conf").write_text(conf)
|
||||
|
||||
|
||||
# ── efibootmgr ───────────────────────────────────────────────────────────────
|
||||
|
||||
_BOOT_ENTRY_RE = re.compile(r"^Boot([0-9A-Fa-f]{4})\*?\s+(.*)$")
|
||||
_BOOT_ORDER_RE = re.compile(r"^BootOrder:\s*(.*)$")
|
||||
|
||||
|
||||
def _read_efibootmgr() -> dict:
|
||||
res = subprocess.run(
|
||||
["efibootmgr"],
|
||||
capture_output=True, text=True, check=True,
|
||||
)
|
||||
entries: dict[str, str] = {}
|
||||
order: list[str] = []
|
||||
for line in res.stdout.splitlines():
|
||||
m = _BOOT_ENTRY_RE.match(line)
|
||||
if m:
|
||||
entries[m.group(1).upper()] = m.group(2).strip()
|
||||
continue
|
||||
m = _BOOT_ORDER_RE.match(line)
|
||||
if m:
|
||||
order = [n.strip().upper() for n in m.group(1).split(",") if n.strip()]
|
||||
return {"entries": entries, "order": order, "raw": res.stdout}
|
||||
|
||||
|
||||
def _find_label_entries(entries: dict[str, str], needle: str) -> list[str]:
|
||||
return [num for num, label in entries.items() if needle.lower() in label.lower()]
|
||||
|
||||
|
||||
def _split_partition_device(part_dev: str) -> tuple[str, int]:
|
||||
parent = subprocess.run(
|
||||
["lsblk", "-ndo", "PKNAME", part_dev],
|
||||
capture_output=True, text=True, check=True,
|
||||
).stdout.strip()
|
||||
if not parent:
|
||||
raise RuntimeError(f"could not find parent disk for {part_dev}")
|
||||
part_num = subprocess.run(
|
||||
["lsblk", "-ndo", "PARTN", part_dev],
|
||||
capture_output=True, text=True, check=True,
|
||||
).stdout.strip()
|
||||
if not part_num:
|
||||
raise RuntimeError(f"could not find partition number for {part_dev}")
|
||||
return f"/dev/{parent}", int(part_num)
|
||||
|
||||
|
||||
def _register_efibootmgr_entry(
|
||||
ctx: InstallContext, protected: dict, pre_state: dict
|
||||
) -> None:
|
||||
esp_dev = _esp_device(ctx, protected)
|
||||
disk, part_num = _split_partition_device(esp_dev)
|
||||
|
||||
# Clean up any stale "Omarchy Linux" entries so we don't accumulate dupes
|
||||
# across re-installs.
|
||||
for num in _find_label_entries(pre_state["entries"], "Omarchy Linux"):
|
||||
subprocess.run(
|
||||
["efibootmgr", "--bootnum", num, "--delete-bootnum"],
|
||||
check=False, capture_output=True,
|
||||
)
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
"efibootmgr",
|
||||
"--create",
|
||||
"--disk", disk,
|
||||
"--part", str(part_num),
|
||||
"--label", "Omarchy Linux",
|
||||
"--loader", "\\EFI\\Omarchy\\BOOTX64.EFI",
|
||||
"--unicode",
|
||||
],
|
||||
check=True, capture_output=True, text=True,
|
||||
)
|
||||
|
||||
post = _read_efibootmgr()
|
||||
new_omarchy = _find_label_entries(post["entries"], "Omarchy Linux")
|
||||
if not new_omarchy:
|
||||
raise RuntimeError(
|
||||
"efibootmgr --create reported success but no Omarchy Linux entry found"
|
||||
)
|
||||
omarchy_num = new_omarchy[0]
|
||||
|
||||
# Preserve the original BootOrder, but put Omarchy first.
|
||||
keep = [n for n in pre_state["order"] if n not in new_omarchy]
|
||||
new_order = ",".join([omarchy_num, *keep])
|
||||
subprocess.run(
|
||||
["efibootmgr", "--bootorder", new_order],
|
||||
check=True, capture_output=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -498,13 +799,55 @@ def validate_boot_full(ctx: InstallContext) -> None:
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# validate_boot_protected: hard checks for the dualboot/protected path.
|
||||
# Implemented in Step 8.
|
||||
# Mirrors validate_boot_full but checks the Omarchy ESP subdir, fstab/crypttab,
|
||||
# kernel/initramfs presence, and efibootmgr entry registration.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def validate_boot_protected(ctx: InstallContext) -> None:
|
||||
raise NotImplementedError(
|
||||
"Implemented in Step 8 — see REFACTOR-PLAN-C3-EXECUTION.md"
|
||||
)
|
||||
protected = ctx.state["protected"]
|
||||
kernel = protected["kernel"]
|
||||
|
||||
omarchy_esp = _omarchy_esp_path(ctx, protected)
|
||||
|
||||
bootx64 = omarchy_esp / "BOOTX64.EFI"
|
||||
if not bootx64.exists() or bootx64.stat().st_size == 0:
|
||||
raise RuntimeError(f"{bootx64} missing or empty")
|
||||
|
||||
limine_conf = omarchy_esp / "limine.conf"
|
||||
if not limine_conf.exists():
|
||||
raise RuntimeError(f"{limine_conf} missing")
|
||||
if "/Omarchy Linux" not in limine_conf.read_text():
|
||||
raise RuntimeError(f"{limine_conf} has no /Omarchy Linux entry")
|
||||
|
||||
fstab = ctx.target / "etc" / "fstab"
|
||||
if not fstab.exists():
|
||||
raise RuntimeError(f"{fstab} missing")
|
||||
fstab_text = fstab.read_text()
|
||||
btrfs_uuid = _blkid_uuid(_btrfs_root_device(protected))
|
||||
esp_uuid = _blkid_uuid(_esp_device(ctx, protected))
|
||||
if btrfs_uuid not in fstab_text:
|
||||
raise RuntimeError(f"{fstab} missing btrfs UUID {btrfs_uuid}")
|
||||
if esp_uuid not in fstab_text:
|
||||
raise RuntimeError(f"{fstab} missing ESP UUID {esp_uuid}")
|
||||
|
||||
if protected.get("luks_uuid"):
|
||||
crypttab = ctx.target / "etc" / "crypttab.initramfs"
|
||||
if not crypttab.exists():
|
||||
raise RuntimeError(f"{crypttab} missing")
|
||||
if protected["luks_uuid"] not in crypttab.read_text():
|
||||
raise RuntimeError(f"{crypttab} missing LUKS UUID {protected['luks_uuid']}")
|
||||
|
||||
vmlinuz = ctx.target / "boot" / f"vmlinuz-{kernel}"
|
||||
if not vmlinuz.exists():
|
||||
raise RuntimeError(f"{vmlinuz} missing")
|
||||
|
||||
initramfs = ctx.target / "boot" / f"initramfs-{kernel}.img"
|
||||
if not initramfs.exists():
|
||||
raise RuntimeError(f"{initramfs} missing")
|
||||
|
||||
post = _read_efibootmgr()
|
||||
if not _find_label_entries(post["entries"], "Omarchy Linux"):
|
||||
raise RuntimeError("no 'Omarchy Linux' entry registered in efibootmgr")
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in new issue
Block a user