feat: add initial support for custom bootloaders

This commit is contained in:
Rudra Saraswat 2026-04-03 21:59:47 +01:00
parent f17c272b65
commit 76b61eb57a
3 changed files with 120 additions and 19 deletions

View file

@ -35,6 +35,7 @@ def resolve_config(system_config: dict) -> dict:
"auto-update": system_config["auto-update"]
if isinstance(system_config.get("auto-update"), dict)
else {"enabled": False},
"boot": system_config["boot"],
}
except IndexError:
output.error("base track must include distro-config")
@ -96,6 +97,12 @@ def resolve_config(system_config: dict) -> dict:
else base_config["auto-update"]
)
base_config["boot"] = (
system_config["boot"]
if isinstance(system_config.get("boot"), dict)
else base_config["boot"]
)
return base_config

View file

@ -15,8 +15,13 @@ def update_cleanup() -> None:
"""Clean-up from previous rebase/update."""
# FIXME: should not ideally handle /var/cache/blendOS explicitly
for path in (
"/etc/grub.d",
"/etc/default/grub",
"/var/cache/akshara/rootfs/var/cache/blendOS",
):
subprocess.run(
["umount", "-l", "/var/cache/akshara/rootfs/var/cache/blendOS"],
["umount", "-l", path],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
@ -135,21 +140,108 @@ def merge_var(new_rootfs: RootFS, overrides_keep_new: dict) -> None:
)
def replace_boot_files() -> None:
"""Replace files in /boot with those from new rootfs."""
new_boot_files = []
def is_bind_mnt(src, mountpoint) -> bool:
"""Checks if /boot presently has /.tmp.boot bind-mounted to it.
for f in os.listdir("/.update_rootfs/boot"):
if not os.path.isdir(f"/.update_rootfs/boot/{f}"):
subprocess.run(["mv", f"/.update_rootfs/boot/{f}", "/boot"])
new_boot_files.append(f)
Args:
src: Source directory of bind-mount.
mountpoint: Mountpoint for bind-mount.
"""
with open("/proc/self/mountinfo") as mountinfo_file:
for line in mountinfo_file:
parts = line.strip().split()
if parts[3] != "/" and parts[3] == src and parts[4] == mountpoint:
return True
return False
for f in os.listdir("/boot"):
if not os.path.isdir(f"/boot/{f}"):
if f not in new_boot_files:
subprocess.run(["rm", "-f", f"/boot/{f}"])
subprocess.run(["grub-mkconfig", "-o", "/boot/grub/grub.cfg"])
def handle_boot(new_rootfs, boot_config) -> None:
"""Handles /boot partition."""
if os.path.isdir("/.tmp.boot"):
if is_bind_mnt("/.tmp.boot", "/boot"):
subprocess.run(["umount", "-l", "/boot"])
subprocess.run(["rm", "-rf", "/.tmp.boot"])
subprocess.run(["cp", "-ax", os.path.join(str(new_rootfs), "boot"), "/.tmp.boot"])
subprocess.run(["mount", "--bind", "/.tmp.boot", "/boot"])
if boot_config["type"] == "bios":
if boot_config["loader"] == "grub":
if (
subprocess.run(
[
"grub-install",
f"--directory={os.path.join(str(new_rootfs), 'usr/lib/grub/i386-pc')}",
"--target=i386-pc",
boot_config["device"],
]
).returncode
!= 0
):
output.error("aborting update...")
subprocess.run(["umount", "-l", "/boot"])
subprocess.run(["rm", "-rf", "/.tmp.boot"])
output.error("failed to install GRUB")
exit(1)
else:
subprocess.run(["umount", "-l", "/boot"])
subprocess.run(["rm", "-rf", "/.tmp.boot"])
output.error("unsupported bootloader for BIOS configuration")
exit(1)
elif boot_config["type"] == "uefi":
if boot_config["loader"] == "grub":
if (
subprocess.run(
[
"grub-install",
f"--directory={os.path.join(str(new_rootfs), 'usr/lib/grub/x86_64-efi')}",
"--efi-directory=/boot",
"--target=x86_64-efi",
"--removable",
"--bootloader-id=blendOS",
]
)
!= 0
):
output.error("aborting update...")
subprocess.run(["umount", "-l", "/boot"])
subprocess.run(["rm", "-rf", "/.tmp.boot"])
output.error("failed to install GRUB")
exit(1)
else:
subprocess.run(["umount", "-l", "/boot"])
subprocess.run(["rm", "-rf", "/.tmp.boot"])
output.error("unsupported bootloader for BIOS configuration")
exit(1)
else:
subprocess.run(["umount", "-l", "/boot"])
subprocess.run(["rm", "-rf", "/.tmp.boot"])
output.error(f"unsupported system type - {boot_config['type']}")
exit(1)
if boot_config["loader"] == "grub":
new_rootfs.exec(["grub-mkconfig", "-o", "/grub.cfg"])
subprocess.run(["cp", f"{new_rootfs}/grub.cfg", "/boot/grub/grub.cfg"])
subprocess.run(["umount", "-l", "/boot"])
# Replace /boot with /.tmp.boot
# FIXME: should be atomic
for tmp_boot, dirs, files in os.walk("/.tmp.boot"):
boot = tmp_boot.replace("/tmp.boot", "/boot", 1)
for path in dirs + files:
subprocess.run(["rm", "-rf", "--", os.path.join(boot, path)])
subprocess.run(
[
"cp",
"-ax",
os.path.join(tmp_boot, path),
os.path.join(boot, path),
]
)
subprocess.run(["rm", "-rf", "/.tmp.boot"])
def update() -> None:
@ -194,6 +286,7 @@ def update() -> None:
else {}
)
output.info("merging /etc...")
merge_etc(new_rootfs, overrides_keep_new)
try:
@ -221,6 +314,7 @@ def update() -> None:
output.error("malformed /etc/shadow")
sys.exit(1)
output.info("merging /var...")
merge_var(new_rootfs, overrides_keep_new)
with open(
@ -242,6 +336,6 @@ def update() -> None:
subprocess.run(["cp", "-ax", str(new_rootfs), "/.update_rootfs"])
replace_boot_files()
handle_boot(new_rootfs, system_config["boot"])
print()

View file

@ -6,7 +6,7 @@ echo
mount -o remount,rw "$NEWROOT" >/dev/null 2>&1 || true
# Remove "$NEWROOT"/.successful-update if exists
rm -f "$NEWROOT"/.successful-update "$NEWROOT"/.update
rm -f "$NEWROOT"/.successful-update "$NEWROOT"/.update "$NEWROOT"/.tmp.boot
# Check if there is an available update
if [ -d "$NEWROOT"/.update_rootfs ]; then