5241998eee
It seems that I just can't stop finding weird btrfs setups that break the current script. When you use btrfs with a default subvolume, GRUB will use normal paths, like on ext4. So instead of /@/boot/vmlinuz you have /boot/vmlinuz. The issue is that grub2-mkrelpath doesn't know about that. The script would have to parse the GRUB configuration and fstab, to figure out if btrfs is used with a default subvolume, and then add a flag to the command depending on that. However, instead of adding yet another workaround, let's just forget about grubby and do what we want to do ourselves. Because, as it turns out, not taking a weird detour over the path that GRUB uses to load the kernel makes everything much easier. Since this means that we are dealing with even more filepath and string mangling, I decided to rewrite the script in python. It would work fine in bash, but it would be even more spaghetti code that I don't really want to write. And python is always available in Fedora anyway, because DNF is written in it.
88 lines
2.3 KiB
Python
Executable file
88 lines
2.3 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
def grub2_editenv(*args: Any, **kwargs: Any) -> str:
|
|
subprocess.run(["grub2-editenv", *args], check=True, **kwargs)
|
|
|
|
|
|
def main() -> int:
|
|
boot: Path = Path("/boot")
|
|
mid: Path = Path("/etc/machine-id")
|
|
|
|
if not boot.exists():
|
|
print("ERROR: /boot directory does not exist")
|
|
return 1
|
|
|
|
if not mid.exists():
|
|
print("ERROR: /etc/machine-id does not exist")
|
|
return 1
|
|
|
|
blsdir: Path = boot / "loader" / "entries"
|
|
|
|
if not blsdir.exists():
|
|
print("ERROR: /boot/loader/entries does not exist")
|
|
return 1
|
|
|
|
try:
|
|
grub2_editenv("--help", capture_output=True)
|
|
except:
|
|
print("ERROR: grub2-editenv is not working")
|
|
return 1
|
|
|
|
# Get list of surface kernels sorted by timestamp.
|
|
#
|
|
# We use creation time here because it represents when the kernel was installed.
|
|
# Modification time can be a bit wonky and seems to correspond to the build date.
|
|
kernels: list[Path] = sorted(
|
|
boot.glob("vmlinuz-*.surface.*"),
|
|
key=lambda x: x.stat().st_ctime,
|
|
reverse=True,
|
|
)
|
|
|
|
if len(kernels) == 0:
|
|
print("ERROR: Failed to find a surface kernel")
|
|
return 1
|
|
|
|
# The saved_entry property from grubenv determines what kernel is booted by default.
|
|
# Its value is the filename of the BLS entry in /boot/loader/entries minus the file extension.
|
|
#
|
|
# The BLS files are named using a combination of the machine ID and the version string
|
|
# of the kernel that is being booted. Since we have the vmlinux, we can get the version
|
|
# from its name, and the machine ID from /etc/machine-id.
|
|
#
|
|
# This allows setting the default kernel without calling grubby or having to figure out
|
|
# which path GRUB will use to boot the kernel.
|
|
|
|
kernel: Path = kernels[0]
|
|
|
|
machineid: str = mid.read_text().strip()
|
|
version: str = kernel.name.lstrip("vmlinuz-")
|
|
|
|
blscfg: Path = blsdir / "{}-{}.conf".format(machineid, version)
|
|
|
|
# Make sure the config really exists
|
|
if not blscfg.exists():
|
|
print("ERROR: {} does not exist".format(blscfg))
|
|
return 1
|
|
|
|
print("Kernel: {}".format(kernel))
|
|
print("BLS entry: {}".format(blscfg))
|
|
|
|
grub2_editenv("-", "set", "saved_entry={}".format(blscfg.stem))
|
|
|
|
# Update timestamp for rEFInd and ensure it is marked as latest across all kernels
|
|
kernel.touch(exist_ok=True)
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|