From 76b61eb57aab6673a76c3a1b7ed7596a2f77f21e Mon Sep 17 00:00:00 2001 From: Rudra Saraswat Date: Fri, 3 Apr 2026 21:59:47 +0100 Subject: [PATCH] feat: add initial support for custom bootloaders --- usr/lib/akshara/utils/helpers.py | 7 + usr/lib/akshara/utils/update.py | 130 +++++++++++++++--- .../modules.d/10akshara/handle-update.sh | 2 +- 3 files changed, 120 insertions(+), 19 deletions(-) diff --git a/usr/lib/akshara/utils/helpers.py b/usr/lib/akshara/utils/helpers.py index 4f12652..952923f 100644 --- a/usr/lib/akshara/utils/helpers.py +++ b/usr/lib/akshara/utils/helpers.py @@ -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 diff --git a/usr/lib/akshara/utils/update.py b/usr/lib/akshara/utils/update.py index 1a58cfc..9a69692 100644 --- a/usr/lib/akshara/utils/update.py +++ b/usr/lib/akshara/utils/update.py @@ -15,11 +15,16 @@ def update_cleanup() -> None: """Clean-up from previous rebase/update.""" # FIXME: should not ideally handle /var/cache/blendOS explicitly - subprocess.run( - ["umount", "-l", "/var/cache/akshara/rootfs/var/cache/blendOS"], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) + for path in ( + "/etc/grub.d", + "/etc/default/grub", + "/var/cache/akshara/rootfs/var/cache/blendOS", + ): + subprocess.run( + ["umount", "-l", path], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) subprocess.run( [ @@ -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() diff --git a/usr/lib/dracut/modules.d/10akshara/handle-update.sh b/usr/lib/dracut/modules.d/10akshara/handle-update.sh index 6884750..2cc1fb8 100755 --- a/usr/lib/dracut/modules.d/10akshara/handle-update.sh +++ b/usr/lib/dracut/modules.d/10akshara/handle-update.sh @@ -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