Merge branch 'fix-boot-paths' into ubuntu/master

This commit is contained in:
michael.hudson@canonical.com 2026-02-20 12:44:49 +13:00
commit 92c29ecbf2
No known key found for this signature in database
GPG Key ID: 80E627A0AB757E23
10 changed files with 241 additions and 273 deletions

2
debian/changelog vendored
View File

@ -10,6 +10,8 @@ livecd-rootfs (26.04.20) UNRELEASED; urgency=medium
[ Michael Hudson-Doyle ] [ Michael Hudson-Doyle ]
* 030-ubuntu-live-system-seed.binary: do not run if there is no layer to * 030-ubuntu-live-system-seed.binary: do not run if there is no layer to
install the system, in particular on arm64. install the system, in particular on arm64.
* Fix some path confusion in the new isobuilder.boot package and refactor
grub config generation to be more string based.
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Thu, 19 Feb 2026 19:55:26 +1300 -- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Thu, 19 Feb 2026 19:55:26 +1300

View File

@ -17,6 +17,7 @@ def make_boot_configurator_for_arch(
arch: str, arch: str,
logger: "Logger", logger: "Logger",
apt_state: "AptStateManager", apt_state: "AptStateManager",
workdir: pathlib.Path,
iso_root: pathlib.Path, iso_root: pathlib.Path,
) -> "BaseBootConfigurator": ) -> "BaseBootConfigurator":
"""Factory function to create boot configurator for a specific architecture.""" """Factory function to create boot configurator for a specific architecture."""
@ -24,23 +25,23 @@ def make_boot_configurator_for_arch(
case "amd64": case "amd64":
from .amd64 import AMD64BootConfigurator from .amd64 import AMD64BootConfigurator
return AMD64BootConfigurator(logger, apt_state, iso_root) return AMD64BootConfigurator(logger, apt_state, workdir, iso_root)
case "arm64": case "arm64":
from .arm64 import ARM64BootConfigurator from .arm64 import ARM64BootConfigurator
return ARM64BootConfigurator(logger, apt_state, iso_root) return ARM64BootConfigurator(logger, apt_state, workdir, iso_root)
case "ppc64el": case "ppc64el":
from .ppc64el import PPC64ELBootConfigurator from .ppc64el import PPC64ELBootConfigurator
return PPC64ELBootConfigurator(logger, apt_state, iso_root) return PPC64ELBootConfigurator(logger, apt_state, workdir, iso_root)
case "riscv64": case "riscv64":
from .riscv64 import RISCV64BootConfigurator from .riscv64 import RISCV64BootConfigurator
return RISCV64BootConfigurator(logger, apt_state, iso_root) return RISCV64BootConfigurator(logger, apt_state, workdir, iso_root)
case "s390x": case "s390x":
from .s390x import S390XBootConfigurator from .s390x import S390XBootConfigurator
return S390XBootConfigurator(logger, apt_state, iso_root) return S390XBootConfigurator(logger, apt_state, workdir, iso_root)
case _: case _:
raise ValueError(f"Unsupported architecture: {arch}") raise ValueError(f"Unsupported architecture: {arch}")

View File

@ -3,8 +3,9 @@
import pathlib import pathlib
import shutil import shutil
from .uefi import UEFIBootConfigurator
from .base import default_kernel_params from .base import default_kernel_params
from .grub import copy_grub_modules
from .uefi import UEFIBootConfigurator
CALAMARES_PROJECTS = ["kubuntu", "lubuntu"] CALAMARES_PROJECTS = ["kubuntu", "lubuntu"]
@ -51,16 +52,13 @@ class AMD64BootConfigurator(UEFIBootConfigurator):
opts.extend( opts.extend(
[ [
"--grub2-mbr", "--grub2-mbr",
self.grub_dir.joinpath("usr/lib/grub/i386-pc/boot_hybrid.img"), self.scratch.joinpath("boot_hybrid.img"),
] ]
) )
# ## Set up the mkisofs options for UEFI boot. # ## Set up the mkisofs options for UEFI boot.
opts.extend(self.get_uefi_mkisofs_opts()) opts.extend(self.get_uefi_mkisofs_opts())
# ## Add cd-boot-tree to the ISO
opts.append(str(self.boot_tree))
return opts return opts
def extract_files(self) -> None: def extract_files(self) -> None:
@ -71,115 +69,113 @@ class AMD64BootConfigurator(UEFIBootConfigurator):
# AMD64-specific: Add BIOS/legacy boot files # AMD64-specific: Add BIOS/legacy boot files
with self.logger.logged("adding BIOS/legacy boot files"): with self.logger.logged("adding BIOS/legacy boot files"):
self.download_and_extract_package("grub-pc-bin", self.grub_dir) grub_pc_pkg_dir = self.scratch.joinpath("grub-pc-pkg")
self.download_and_extract_package("grub-pc-bin", grub_pc_pkg_dir)
grub_boot_dir = self.boot_tree.joinpath("boot", "grub", "i386-pc") grub_boot_dir = self.iso_root.joinpath("boot", "grub", "i386-pc")
grub_boot_dir.mkdir(parents=True, exist_ok=True) grub_boot_dir.mkdir(parents=True, exist_ok=True)
src_grub_dir = self.grub_dir.joinpath("usr", "lib", "grub", "i386-pc") src_grub_dir = grub_pc_pkg_dir.joinpath("usr", "lib", "grub", "i386-pc")
shutil.copy(src_grub_dir.joinpath("eltorito.img"), grub_boot_dir) shutil.copy(src_grub_dir.joinpath("eltorito.img"), grub_boot_dir)
shutil.copy(src_grub_dir.joinpath("boot_hybrid.img"), self.scratch)
self.copy_grub_modules( copy_grub_modules(
src_grub_dir, grub_boot_dir, ["*.mod", "*.lst", "*.o"] grub_pc_pkg_dir,
self.iso_root,
"i386-pc",
["*.mod", "*.lst", "*.o"],
) )
def generate_grub_config(self) -> None: def generate_grub_config(self) -> str:
"""Generate grub.cfg and loopback.cfg for the boot tree.""" """Generate grub.cfg content for AMD64."""
boot_grub_dir = self.boot_tree.joinpath("boot", "grub") result = self.grub_header()
boot_grub_dir.mkdir(parents=True, exist_ok=True)
grub_cfg = boot_grub_dir.joinpath("grub.cfg")
if self.project == "ubuntu-mini-iso": if self.project == "ubuntu-mini-iso":
self.write_grub_header(grub_cfg) result += """\
with grub_cfg.open("a") as f: menuentry "Choose an Ubuntu version to install" {
f.write(
"""menuentry "Choose an Ubuntu version to install" {
set gfxpayload=keep set gfxpayload=keep
linux /casper/vmlinuz iso-chooser-menu ip=dhcp --- linux /casper/vmlinuz iso-chooser-menu ip=dhcp ---
initrd /casper/initrd initrd /casper/initrd
} }
""" """
) return result
return
# Generate grub.cfg
kernel_params = default_kernel_params(self.project) kernel_params = default_kernel_params(self.project)
# Write common GRUB header
self.write_grub_header(grub_cfg)
# Main menu entry # Main menu entry
with grub_cfg.open("a") as f: result += f"""\
f.write( menuentry "Try or Install {self.humanproject}" {{
f"""menuentry "Try or Install {self.humanproject}" {{
set gfxpayload=keep set gfxpayload=keep
linux /casper/vmlinuz {kernel_params} linux /casper/vmlinuz {kernel_params}
initrd /casper/initrd initrd /casper/initrd
}} }}
""" """
)
# All but server get safe-graphics mode # All but server get safe-graphics mode
if self.project != "ubuntu-server": if self.project != "ubuntu-server":
with grub_cfg.open("a") as f: result += f"""\
f.write( menuentry "{self.humanproject} (safe graphics)" {{
f"""menuentry "{self.humanproject} (safe graphics)" {{
set gfxpayload=keep set gfxpayload=keep
linux /casper/vmlinuz nomodeset {kernel_params} linux /casper/vmlinuz nomodeset {kernel_params}
initrd /casper/initrd initrd /casper/initrd
}} }}
""" """
)
# ubiquity based projects get OEM mode # ubiquity based projects get OEM mode
if "maybe-ubiquity" in kernel_params: if "maybe-ubiquity" in kernel_params:
oem_kernel_params = kernel_params.replace( oem_kernel_params = kernel_params.replace(
"maybe-ubiquity", "only-ubiquity oem-config/enable=true" "maybe-ubiquity", "only-ubiquity oem-config/enable=true"
) )
with grub_cfg.open("a") as f: result += f"""\
f.write( menuentry "OEM install (for manufacturers)" {{
f"""menuentry "OEM install (for manufacturers)" {{
set gfxpayload=keep set gfxpayload=keep
linux /casper/vmlinuz {oem_kernel_params} linux /casper/vmlinuz {oem_kernel_params}
initrd /casper/initrd initrd /casper/initrd
}} }}
""" """
)
# Calamares-based projects get OEM mode # Calamares-based projects get OEM mode
if self.project in CALAMARES_PROJECTS: if self.project in CALAMARES_PROJECTS:
with grub_cfg.open("a") as f: result += f"""\
f.write( menuentry "OEM install (for manufacturers)" {{
f"""menuentry "OEM install (for manufacturers)" {{
set gfxpayload=keep set gfxpayload=keep
linux /casper/vmlinuz {kernel_params} oem-config/enable=true linux /casper/vmlinuz {kernel_params} oem-config/enable=true
initrd /casper/initrd initrd /casper/initrd
}} }}
""" """
)
# Currently only server is built with HWE, hence no safe-graphics/OEM # Currently only server is built with HWE, hence no safe-graphics/OEM
if self.hwe: if self.hwe:
with grub_cfg.open("a") as f: result += f"""\
f.write( menuentry "{self.humanproject} with the HWE kernel" {{
f"""menuentry "{self.humanproject} with the HWE kernel" {{
set gfxpayload=keep set gfxpayload=keep
linux /casper/hwe-vmlinuz {kernel_params} linux /casper/hwe-vmlinuz {kernel_params}
initrd /casper/hwe-initrd initrd /casper/hwe-initrd
}} }}
""" """
)
# Create the loopback config, based on the main config # UEFI Entries (wrapped in grub_platform check for dual BIOS/UEFI support)
with grub_cfg.open("r") as f: uefi_menu_entries = self.uefi_menu_entries()
content = f.read()
# sed: delete from line 1 to menu_color_highlight, delete from result += f"""\
# grub_platform to end and replace '---' with grub_platform
# 'iso-scan/filename=${iso_path} ---' in lines with 'linux' if [ "$grub_platform" = "efi" ]; then
lines = content.split("\n") {uefi_menu_entries}\
fi
"""
return result
@staticmethod
def generate_loopback_config(grub_content: str) -> str:
"""Derive loopback.cfg from grub.cfg content.
Strips the header (up to menu_color_highlight) and the UEFI
trailer (from grub_platform to end), and adds iso-scan/filename
to linux lines.
"""
lines = grub_content.split("\n")
start_idx = 0 start_idx = 0
for i, line in enumerate(lines): for i, line in enumerate(lines):
if "menu_color_highlight" in line: if "menu_color_highlight" in line:
@ -202,16 +198,19 @@ class AMD64BootConfigurator(UEFIBootConfigurator):
for line in loopback_lines for line in loopback_lines
] ]
loopback_cfg = boot_grub_dir.joinpath("loopback.cfg") return "\n".join(loopback_lines)
with loopback_cfg.open("w") as f:
f.write("\n".join(loopback_lines))
# UEFI Entries (wrapped in grub_platform check for dual BIOS/UEFI support) def make_bootable(
with grub_cfg.open("a") as f: self,
f.write("grub_platform\n") project: str,
f.write('if [ "$grub_platform" = "efi" ]; then\n') capproject: str,
subarch: str,
self.write_uefi_menu_entries(grub_cfg) hwe: bool,
) -> None:
with grub_cfg.open("a") as f: """Make the ISO bootable, including generating loopback.cfg."""
f.write("fi\n") super().make_bootable(project, capproject, subarch, hwe)
grub_cfg = self.iso_root.joinpath("boot", "grub", "grub.cfg")
grub_content = grub_cfg.read_text()
self.iso_root.joinpath("boot", "grub", "loopback.cfg").write_text(
self.generate_loopback_config(grub_content)
)

View File

@ -26,7 +26,6 @@ class ARM64BootConfigurator(UEFIBootConfigurator):
opts.extend(self.get_uefi_mkisofs_opts()) opts.extend(self.get_uefi_mkisofs_opts())
# ARM64-specific: partition cylinder alignment # ARM64-specific: partition cylinder alignment
opts.extend(["-partition_cyl_align", "all"]) opts.extend(["-partition_cyl_align", "all"])
opts.append(self.boot_tree)
return opts return opts
def extract_files(self) -> None: def extract_files(self) -> None:
@ -34,19 +33,15 @@ class ARM64BootConfigurator(UEFIBootConfigurator):
with self.logger.logged("extracting ARM64 boot files"): with self.logger.logged("extracting ARM64 boot files"):
self.extract_uefi_files() self.extract_uefi_files()
def generate_grub_config(self) -> None: def generate_grub_config(self) -> str:
"""Generate grub.cfg for ARM64.""" """Generate grub.cfg for ARM64."""
kernel_params = default_kernel_params(self.project) kernel_params = default_kernel_params(self.project)
grub_cfg = self.grub_dir.joinpath("grub.cfg") result = self.grub_header()
# Write common GRUB header
self.write_grub_header(grub_cfg)
# ARM64-specific: Snapdragon workarounds # ARM64-specific: Snapdragon workarounds
with grub_cfg.open("a") as f: result += f"""\
f.write( set cmdline=
"""set cmdline=
smbios --type 4 --get-string 5 --set proc_version smbios --type 4 --get-string 5 --set proc_version
regexp "Snapdragon.*" "$proc_version" regexp "Snapdragon.*" "$proc_version"
if [ $? = 0 ]; then if [ $? = 0 ]; then
@ -59,16 +54,14 @@ if [ $? = 0 ]; then
fi fi
menuentry "Try or Install {self.humanproject}" {{ menuentry "Try or Install {self.humanproject}" {{
\tset gfxpayload=keep set gfxpayload=keep
\tlinux\t/casper/vmlinuz $cmdline {kernel_params} console=tty0 linux /casper/vmlinuz $cmdline {kernel_params} console=tty0
\tinitrd\t/casper/initrd initrd /casper/initrd
}} }}
""" """
)
# HWE kernel option if available # HWE kernel option if available
self.write_hwe_menu_entry( result += self.hwe_menu_entry(
grub_cfg,
"vmlinuz", "vmlinuz",
f"{kernel_params} console=tty0", f"{kernel_params} console=tty0",
extra_params="$cmdline ", extra_params="$cmdline ",
@ -78,4 +71,6 @@ menuentry "Try or Install {self.humanproject}" {{
# but it's not actually set anywhere in the grub.cfg, so we omit it here # but it's not actually set anywhere in the grub.cfg, so we omit it here
# UEFI Entries (ARM64 is UEFI-only, no grub_platform check needed) # UEFI Entries (ARM64 is UEFI-only, no grub_platform check needed)
self.write_uefi_menu_entries(grub_cfg) result += self.uefi_menu_entries()
return result

View File

@ -1,7 +1,6 @@
"""Base classes and helper functions for boot configuration.""" """Base classes and helper functions for boot configuration."""
import pathlib import pathlib
import shutil
import subprocess import subprocess
import tempfile import tempfile
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
@ -34,16 +33,13 @@ class BaseBootConfigurator(ABC):
self, self,
logger: Logger, logger: Logger,
apt_state: AptStateManager, apt_state: AptStateManager,
workdir: pathlib.Path,
iso_root: pathlib.Path, iso_root: pathlib.Path,
) -> None: ) -> None:
self.logger = logger self.logger = logger
self.apt_state = apt_state self.apt_state = apt_state
self.iso_root = iso_root
def create_dirs(self, workdir):
self.scratch = workdir.joinpath("boot-stuff") self.scratch = workdir.joinpath("boot-stuff")
self.scratch.mkdir(exist_ok=True) self.iso_root = iso_root
self.boot_tree = self.scratch.joinpath("cd-boot-tree")
def download_and_extract_package( def download_and_extract_package(
self, pkg_name: str, target_dir: pathlib.Path self, pkg_name: str, target_dir: pathlib.Path
@ -65,14 +61,6 @@ class BaseBootConfigurator(ABC):
dpkg_proc.stdout.close() dpkg_proc.stdout.close()
tar_proc.communicate() tar_proc.communicate()
def copy_grub_modules(
self, src_dir: pathlib.Path, dest_dir: pathlib.Path, extensions: list[str]
) -> None:
"""Copy GRUB module files matching given extensions from src to dest."""
for ext in extensions:
for file in src_dir.glob(ext):
shutil.copy(file, dest_dir)
@abstractmethod @abstractmethod
def extract_files(self) -> None: def extract_files(self) -> None:
"""Download and extract bootloader packages to the boot tree. """Download and extract bootloader packages to the boot tree.
@ -95,7 +83,6 @@ class BaseBootConfigurator(ABC):
def make_bootable( def make_bootable(
self, self,
workdir: pathlib.Path,
project: str, project: str,
capproject: str, capproject: str,
subarch: str, subarch: str,
@ -106,6 +93,6 @@ class BaseBootConfigurator(ABC):
self.humanproject = capproject.replace("-", " ") self.humanproject = capproject.replace("-", " ")
self.subarch = subarch self.subarch = subarch
self.hwe = hwe self.hwe = hwe
self.create_dirs(workdir) self.scratch.mkdir(exist_ok=True)
with self.logger.logged("configuring boot"): with self.logger.logged("configuring boot"):
self.extract_files() self.extract_files()

View File

@ -7,17 +7,31 @@ from abc import abstractmethod
from .base import BaseBootConfigurator from .base import BaseBootConfigurator
def copy_grub_common_files_to_boot_tree( def copy_grub_common_files(grub_pkg_dir: pathlib.Path, iso_root: pathlib.Path) -> None:
grub_dir: pathlib.Path, boot_tree: pathlib.Path fonts_dir = iso_root.joinpath("boot", "grub", "fonts")
) -> None:
fonts_dir = boot_tree.joinpath("boot", "grub", "fonts")
fonts_dir.mkdir(parents=True, exist_ok=True) fonts_dir.mkdir(parents=True, exist_ok=True)
src = grub_dir.joinpath("usr", "share", "grub", "unicode.pf2") src = grub_pkg_dir.joinpath("usr", "share", "grub", "unicode.pf2")
dst = fonts_dir.joinpath("unicode.pf2") dst = fonts_dir.joinpath("unicode.pf2")
shutil.copy(src, dst) shutil.copy(src, dst)
def copy_grub_modules(
grub_pkg_dir: pathlib.Path,
iso_root: pathlib.Path,
grub_target: str,
patterns: list[str],
) -> None:
"""Copy GRUB module files matching given patterns from src to dest."""
src_dir = grub_pkg_dir.joinpath("usr", "lib", "grub", grub_target)
dest_dir = iso_root.joinpath("boot", "grub", grub_target)
dest_dir.mkdir(parents=True, exist_ok=True)
for pat in patterns:
for file in src_dir.glob(pat):
shutil.copy(file, dest_dir)
class GrubBootConfigurator(BaseBootConfigurator): class GrubBootConfigurator(BaseBootConfigurator):
"""Base class for architectures that use GRUB (all except S390X). """Base class for architectures that use GRUB (all except S390X).
@ -25,79 +39,66 @@ class GrubBootConfigurator(BaseBootConfigurator):
Subclasses must implement generate_grub_config(). Subclasses must implement generate_grub_config().
""" """
def create_dirs(self, workdir): def grub_header(self, include_loadfont: bool = True) -> str:
super().create_dirs(workdir) """Return common GRUB config header (timeout, colors).
self.grub_dir = self.boot_tree.joinpath("grub")
def setup_grub_common_files(self) -> None:
"""Copy common GRUB files (fonts, etc.) to boot tree."""
copy_grub_common_files_to_boot_tree(self.grub_dir, self.boot_tree)
def write_grub_header(
self, grub_cfg: pathlib.Path, include_loadfont: bool = True
) -> None:
"""Write common GRUB config header (timeout, colors).
Args: Args:
grub_cfg: Path to grub.cfg file
include_loadfont: Whether to include 'loadfont unicode' include_loadfont: Whether to include 'loadfont unicode'
(not needed for RISC-V) (not needed for RISC-V)
""" """
with grub_cfg.open("a") as f: result = "set timeout=30\n\n"
f.write("set timeout=30\n\n") if include_loadfont:
if include_loadfont: result += "loadfont unicode\n\n"
f.write("loadfont unicode\n\n") result += """\
f.write( set menu_color_normal=white/black
"""set menu_color_normal=white/black
set menu_color_highlight=black/light-gray set menu_color_highlight=black/light-gray
""" """
) return result
def write_hwe_menu_entry( def hwe_menu_entry(
self, self,
grub_cfg: pathlib.Path,
kernel_name: str, kernel_name: str,
kernel_params: str, kernel_params: str,
extra_params: str = "", extra_params: str = "",
) -> None: ) -> str:
"""Write HWE kernel menu entry if HWE is enabled. """Return HWE kernel menu entry if HWE is enabled.
Args: Args:
grub_cfg: Path to grub.cfg file
kernel_name: Kernel binary name (vmlinuz or vmlinux) kernel_name: Kernel binary name (vmlinuz or vmlinux)
kernel_params: Kernel parameters to append kernel_params: Kernel parameters to append
extra_params: Additional parameters (e.g., console=tty0, $cmdline) extra_params: Additional parameters (e.g., console=tty0, $cmdline)
""" """
if self.hwe: if not self.hwe:
with grub_cfg.open("a") as f: return ""
f.write( return f"""\
f"""menuentry "{self.humanproject} with the HWE kernel" {{ menuentry "{self.humanproject} with the HWE kernel" {{
\tset gfxpayload=keep set gfxpayload=keep
\tlinux\t/casper/hwe-{kernel_name} {extra_params}{kernel_params} linux /casper/hwe-{kernel_name} {extra_params}{kernel_params}
\tinitrd\t/casper/hwe-initrd initrd /casper/hwe-initrd
}} }}
""" """
)
@abstractmethod @abstractmethod
def generate_grub_config(self) -> None: def generate_grub_config(self) -> str:
"""Generate grub.cfg configuration file. """Generate grub.cfg content.
Each GRUB-based architecture must implement this to create its Each GRUB-based architecture must implement this to return the
specific GRUB configuration. GRUB configuration.
""" """
... ...
def make_bootable( def make_bootable(
self, self,
workdir: pathlib.Path,
project: str, project: str,
capproject: str, capproject: str,
subarch: str, subarch: str,
hwe: bool, hwe: bool,
) -> None: ) -> None:
"""Make the ISO bootable by extracting files and generating GRUB config.""" """Make the ISO bootable by extracting files and generating GRUB config."""
super().make_bootable(workdir, project, capproject, subarch, hwe) super().make_bootable(project, capproject, subarch, hwe)
with self.logger.logged("generating grub config"): with self.logger.logged("generating grub config"):
self.generate_grub_config() content = self.generate_grub_config()
grub_dir = self.iso_root.joinpath("boot", "grub")
grub_dir.mkdir(parents=True, exist_ok=True)
grub_dir.joinpath("grub.cfg").write_text(content)

View File

@ -3,7 +3,11 @@
import pathlib import pathlib
import shutil import shutil
from .grub import GrubBootConfigurator from .grub import (
copy_grub_common_files,
copy_grub_modules,
GrubBootConfigurator,
)
from .base import default_kernel_params from .base import default_kernel_params
@ -12,28 +16,26 @@ class PPC64ELBootConfigurator(GrubBootConfigurator):
def mkisofs_opts(self) -> list[str | pathlib.Path]: def mkisofs_opts(self) -> list[str | pathlib.Path]:
"""Return mkisofs options for PPC64EL.""" """Return mkisofs options for PPC64EL."""
# Add cd-boot-tree to the ISO return []
return [self.boot_tree]
def extract_files(self) -> None: def extract_files(self) -> None:
"""Download and extract bootloader packages for PPC64EL.""" """Download and extract bootloader packages for PPC64EL."""
self.logger.log("extracting PPC64EL boot files") self.logger.log("extracting PPC64EL boot files")
grub_pkg_dir = self.scratch.joinpath("grub-pkg")
# Download and extract bootloader packages # Download and extract bootloader packages
self.download_and_extract_package("grub2-common", self.grub_dir) self.download_and_extract_package("grub2-common", grub_pkg_dir)
self.download_and_extract_package("grub-ieee1275-bin", self.grub_dir) self.download_and_extract_package("grub-ieee1275-bin", grub_pkg_dir)
# Add common files for GRUB to tree # Add common files for GRUB to tree
self.setup_grub_common_files() copy_grub_common_files(grub_pkg_dir, self.iso_root)
# Add IEEE1275 ppc boot files # Add IEEE1275 ppc boot files
ppc_dir = self.boot_tree.joinpath("ppc") ppc_dir = self.iso_root.joinpath("ppc")
ppc_dir.mkdir(parents=True, exist_ok=True) ppc_dir.mkdir()
grub_boot_dir = self.boot_tree.joinpath("boot", "grub", "powerpc-ieee1275") src_grub_dir = grub_pkg_dir.joinpath("usr", "lib", "grub", "powerpc-ieee1275")
grub_boot_dir.mkdir(parents=True, exist_ok=True)
src_grub_dir = self.grub_dir.joinpath("usr", "lib", "grub", "powerpc-ieee1275")
# Copy bootinfo.txt to ppc directory # Copy bootinfo.txt to ppc directory
shutil.copy( shutil.copy(
@ -43,33 +45,30 @@ class PPC64ELBootConfigurator(GrubBootConfigurator):
# Copy eltorito.elf to boot/grub as powerpc.elf # Copy eltorito.elf to boot/grub as powerpc.elf
shutil.copy( shutil.copy(
src_grub_dir.joinpath("eltorito.elf"), src_grub_dir.joinpath("eltorito.elf"),
self.boot_tree.joinpath("boot", "grub", "powerpc.elf"), self.iso_root.joinpath("boot", "grub", "powerpc.elf"),
) )
# Copy GRUB modules # Copy GRUB modules
self.copy_grub_modules(src_grub_dir, grub_boot_dir, ["*.mod", "*.lst"]) copy_grub_modules(
grub_pkg_dir, self.iso_root, "powerpc-ieee1275", ["*.mod", "*.lst"]
)
def generate_grub_config(self) -> None: def generate_grub_config(self) -> str:
"""Generate grub.cfg for PPC64EL.""" """Generate grub.cfg for PPC64EL."""
kernel_params = default_kernel_params(self.project) kernel_params = default_kernel_params(self.project)
grub_cfg = self.grub_dir.joinpath("grub.cfg") result = self.grub_header()
# Write common GRUB header
self.write_grub_header(grub_cfg)
# Main menu entry # Main menu entry
with grub_cfg.open("a") as f: result += f"""\
f.write( menuentry "Try or Install {self.humanproject}" {{
f"""menuentry "Try or Install {self.humanproject}" {{ set gfxpayload=keep
\tset gfxpayload=keep linux /casper/vmlinux quiet {kernel_params}
\tlinux\t/casper/vmlinux quiet {kernel_params} initrd /casper/initrd
\tinitrd\t/casper/initrd
}} }}
""" """
)
# HWE kernel option if available # HWE kernel option if available
self.write_hwe_menu_entry( result += self.hwe_menu_entry("vmlinux", kernel_params, extra_params="quiet ")
grub_cfg, "vmlinux", kernel_params, extra_params="quiet "
) return result

View File

@ -3,35 +3,31 @@
import pathlib import pathlib
import shutil import shutil
from .grub import GrubBootConfigurator from .grub import GrubBootConfigurator, copy_grub_common_files, copy_grub_modules
def copy_unsigned_monolithic_grub_to_boot_tree( def copy_unsigned_monolithic_grub(
grub_dir: pathlib.Path, efi_suffix: str, grub_target: str, boot_tree: pathlib.Path grub_pkg_dir: pathlib.Path,
efi_suffix: str,
grub_target: str,
iso_root: pathlib.Path,
) -> None: ) -> None:
efi_boot_dir = boot_tree.joinpath("EFI", "boot") efi_boot_dir = iso_root.joinpath("EFI", "boot")
efi_boot_dir.mkdir(parents=True, exist_ok=True) efi_boot_dir.mkdir(parents=True, exist_ok=True)
shutil.copy( shutil.copy(
grub_dir.joinpath( grub_pkg_dir.joinpath(
"usr", "usr",
"lib", "lib",
"grub", "grub",
f"{grub_target}-efi", grub_target,
"monolithic", "monolithic",
f"gcd{efi_suffix}.efi", f"gcd{efi_suffix}.efi",
), ),
efi_boot_dir.joinpath(f"boot{efi_suffix}.efi"), efi_boot_dir.joinpath(f"boot{efi_suffix}.efi"),
) )
grub_boot_dir = boot_tree.joinpath("boot", "grub", f"{grub_target}-efi") copy_grub_modules(grub_pkg_dir, iso_root, grub_target, ["*.mod", "*.lst"])
grub_boot_dir.mkdir(parents=True, exist_ok=True)
src_grub_dir = grub_dir.joinpath("usr", "lib", "grub", f"{grub_target}-efi")
for mod_file in src_grub_dir.glob("*.mod"):
shutil.copy(mod_file, grub_boot_dir)
for lst_file in src_grub_dir.glob("*.lst"):
shutil.copy(lst_file, grub_boot_dir)
class RISCV64BootConfigurator(GrubBootConfigurator): class RISCV64BootConfigurator(GrubBootConfigurator):
@ -74,16 +70,19 @@ class RISCV64BootConfigurator(GrubBootConfigurator):
self.logger.log("extracting RISC-V64 boot files") self.logger.log("extracting RISC-V64 boot files")
u_boot_dir = self.scratch.joinpath("u-boot-sifive") u_boot_dir = self.scratch.joinpath("u-boot-sifive")
grub_pkg_dir = self.scratch.joinpath("grub-pkg")
# Download and extract bootloader packages # Download and extract bootloader packages
self.download_and_extract_package("grub2-common", self.grub_dir) self.download_and_extract_package("grub2-common", grub_pkg_dir)
self.download_and_extract_package("grub-efi-riscv64-bin", self.grub_dir) self.download_and_extract_package("grub-efi-riscv64-bin", grub_pkg_dir)
self.download_and_extract_package("grub-efi-riscv64-unsigned", self.grub_dir) self.download_and_extract_package("grub-efi-riscv64-unsigned", grub_pkg_dir)
self.download_and_extract_package("u-boot-sifive", u_boot_dir) self.download_and_extract_package("u-boot-sifive", u_boot_dir)
# Add GRUB to tree # Add GRUB to tree
self.setup_grub_common_files() copy_grub_common_files(grub_pkg_dir, self.iso_root)
copy_unsigned_monolithic_grub_to_boot_tree(
self.grub_dir, "riscv64", "riscv64", self.boot_tree copy_unsigned_monolithic_grub(
grub_pkg_dir, "riscv64", "riscv64-efi", self.iso_root
) )
# Extract DTBs to tree # Extract DTBs to tree
@ -107,22 +106,14 @@ class RISCV64BootConfigurator(GrubBootConfigurator):
) )
# Copy DTBs if they exist # Copy DTBs if they exist
dtb_dir = self.boot_tree.joinpath("dtb") dtb_dir = self.iso_root.joinpath("dtb")
dtb_dir.mkdir(parents=True, exist_ok=True) dtb_dir.mkdir(parents=True, exist_ok=True)
firmware_dir = kernel_layer.joinpath("usr", "lib", "firmware") firmware_dir = kernel_layer.joinpath("usr", "lib", "firmware")
device_tree_files = list(firmware_dir.glob("*/device-tree/*"))
if device_tree_files: for dtb_file in firmware_dir.glob("*/device-tree/*"):
for dtb_file in device_tree_files: if dtb_file.is_file():
if dtb_file.is_file(): shutil.copy(dtb_file, dtb_dir)
shutil.copy(dtb_file, dtb_dir)
# Clean up kernel layer
shutil.rmtree(kernel_layer)
# Copy tree contents to live-media rootfs
self.logger.run(["cp", "-aT", self.boot_tree, self.iso_root], check=True)
# Create ESP image with GRUB and dtbs # Create ESP image with GRUB and dtbs
efi_img = self.scratch.joinpath("efi.img") efi_img = self.scratch.joinpath("efi.img")
@ -131,41 +122,34 @@ class RISCV64BootConfigurator(GrubBootConfigurator):
) )
# Add EFI files to ESP # Add EFI files to ESP
efi_dir = self.boot_tree.joinpath("EFI") efi_dir = self.iso_root.joinpath("EFI")
self.logger.run(["mcopy", "-s", "-i", efi_img, efi_dir, "::/."], check=True) self.logger.run(["mcopy", "-s", "-i", efi_img, efi_dir, "::/."], check=True)
# Add DTBs to ESP # Add DTBs to ESP
self.logger.run(["mcopy", "-s", "-i", efi_img, dtb_dir, "::/."], check=True) self.logger.run(["mcopy", "-s", "-i", efi_img, dtb_dir, "::/."], check=True)
def generate_grub_config(self) -> None: def generate_grub_config(self) -> str:
"""Generate grub.cfg for RISC-V64.""" """Generate grub.cfg for RISC-V64."""
grub_dir = self.iso_root.joinpath("boot", "grub") result = self.grub_header(include_loadfont=False)
grub_dir.mkdir(parents=True, exist_ok=True)
grub_cfg = grub_dir.joinpath("grub.cfg")
# Write GRUB header (without loadfont for RISC-V)
self.write_grub_header(grub_cfg, include_loadfont=False)
# Main menu entry # Main menu entry
with grub_cfg.open("a") as f: result += f"""\
f.write( menuentry "Try or Install {self.humanproject}" {{
f"""menuentry "Try or Install {self.humanproject}" {{ set gfxpayload=keep
\tset gfxpayload=keep linux /casper/vmlinux efi=debug sysctl.kernel.watchdog_thresh=60 ---
\tlinux\t/casper/vmlinux efi=debug sysctl.kernel.watchdog_thresh=60 --- initrd /casper/initrd
\tinitrd\t/casper/initrd
}} }}
""" """
)
# HWE kernel option if available # HWE kernel option if available
self.write_hwe_menu_entry( result += self.hwe_menu_entry(
grub_cfg,
"vmlinux", "vmlinux",
"---", "---",
extra_params="efi=debug sysctl.kernel.watchdog_thresh=60 ", extra_params="efi=debug sysctl.kernel.watchdog_thresh=60 ",
) )
return result
def post_process_iso(self, iso_path: pathlib.Path) -> None: def post_process_iso(self, iso_path: pathlib.Path) -> None:
"""Add GPT partitions with U-Boot for SiFive Unmatched board. """Add GPT partitions with U-Boot for SiFive Unmatched board.

View File

@ -4,29 +4,31 @@ import pathlib
import shutil import shutil
from ..builder import Logger from ..builder import Logger
from .grub import GrubBootConfigurator from .grub import copy_grub_common_files, GrubBootConfigurator
def copy_signed_shim_grub_to_boot_tree( def copy_signed_shim_grub(
shim_dir: pathlib.Path, shim_pkg_dir: pathlib.Path,
grub_dir: pathlib.Path, grub_pkg_dir: pathlib.Path,
efi_suffix: str, efi_suffix: str,
grub_target: str, grub_target: str,
boot_tree: pathlib.Path, iso_root: pathlib.Path,
) -> None: ) -> None:
efi_boot_dir = boot_tree.joinpath("EFI", "boot") efi_boot_dir = iso_root.joinpath("EFI", "boot")
efi_boot_dir.mkdir(parents=True, exist_ok=True) efi_boot_dir.mkdir(parents=True, exist_ok=True)
shutil.copy( shutil.copy(
shim_dir.joinpath("usr", "lib", "shim", f"shim{efi_suffix}.efi.signed.latest"), shim_pkg_dir.joinpath(
"usr", "lib", "shim", f"shim{efi_suffix}.efi.signed.latest"
),
efi_boot_dir.joinpath(f"boot{efi_suffix}.efi"), efi_boot_dir.joinpath(f"boot{efi_suffix}.efi"),
) )
shutil.copy( shutil.copy(
shim_dir.joinpath("usr", "lib", "shim", f"mm{efi_suffix}.efi"), shim_pkg_dir.joinpath("usr", "lib", "shim", f"mm{efi_suffix}.efi"),
efi_boot_dir.joinpath(f"mm{efi_suffix}.efi"), efi_boot_dir.joinpath(f"mm{efi_suffix}.efi"),
) )
shutil.copy( shutil.copy(
grub_dir.joinpath( grub_pkg_dir.joinpath(
"usr", "usr",
"lib", "lib",
"grub", "grub",
@ -36,10 +38,10 @@ def copy_signed_shim_grub_to_boot_tree(
efi_boot_dir.joinpath(f"grub{efi_suffix}.efi"), efi_boot_dir.joinpath(f"grub{efi_suffix}.efi"),
) )
grub_boot_dir = boot_tree.joinpath("boot", "grub", f"{grub_target}-efi") grub_boot_dir = iso_root.joinpath("boot", "grub", f"{grub_target}-efi")
grub_boot_dir.mkdir(parents=True, exist_ok=True) grub_boot_dir.mkdir(parents=True, exist_ok=True)
src_grub_dir = grub_dir.joinpath("usr", "lib", "grub", f"{grub_target}-efi") src_grub_dir = grub_pkg_dir.joinpath("usr", "lib", "grub", f"{grub_target}-efi")
for mod_file in src_grub_dir.glob("*.mod"): for mod_file in src_grub_dir.glob("*.mod"):
shutil.copy(mod_file, grub_boot_dir) shutil.copy(mod_file, grub_boot_dir)
for lst_file in src_grub_dir.glob("*.lst"): for lst_file in src_grub_dir.glob("*.lst"):
@ -47,10 +49,10 @@ def copy_signed_shim_grub_to_boot_tree(
def create_eltorito_esp_image( def create_eltorito_esp_image(
logger: Logger, boot_tree: pathlib.Path, target_file: pathlib.Path logger: Logger, iso_root: pathlib.Path, target_file: pathlib.Path
) -> None: ) -> None:
logger.log("creating El Torito ESP image") logger.log("creating El Torito ESP image")
efi_dir = boot_tree.joinpath("EFI") efi_dir = iso_root.joinpath("EFI")
# Calculate size: du -s --apparent-size --block-size=1024 + 1024 # Calculate size: du -s --apparent-size --block-size=1024 + 1024
result = logger.run( result = logger.run(
@ -84,10 +86,6 @@ class UEFIBootConfigurator(GrubBootConfigurator):
grub_target: str = "" grub_target: str = ""
arch: str = "" arch: str = ""
def create_dirs(self, workdir):
super().create_dirs(workdir)
self.shim_dir = self.boot_tree.joinpath("shim")
def get_uefi_grub_packages(self) -> list[str]: def get_uefi_grub_packages(self) -> list[str]:
"""Return list of UEFI GRUB packages to download.""" """Return list of UEFI GRUB packages to download."""
return [ return [
@ -98,40 +96,42 @@ class UEFIBootConfigurator(GrubBootConfigurator):
def extract_uefi_files(self) -> None: def extract_uefi_files(self) -> None:
"""Extract common UEFI files to boot tree.""" """Extract common UEFI files to boot tree."""
shim_pkg_dir = self.scratch.joinpath("shim-pkg")
grub_pkg_dir = self.scratch.joinpath("grub-pkg")
# Download UEFI packages # Download UEFI packages
self.download_and_extract_package("shim-signed", self.shim_dir) self.download_and_extract_package("shim-signed", shim_pkg_dir)
for pkg in self.get_uefi_grub_packages(): for pkg in self.get_uefi_grub_packages():
self.download_and_extract_package(pkg, self.grub_dir) self.download_and_extract_package(pkg, grub_pkg_dir)
# Add common files for GRUB to tree # Add common files for GRUB to tree
self.setup_grub_common_files() copy_grub_common_files(grub_pkg_dir, self.iso_root)
# Add EFI GRUB to tree # Add EFI GRUB to tree
copy_signed_shim_grub_to_boot_tree( copy_signed_shim_grub(
self.shim_dir, shim_pkg_dir,
self.grub_dir, grub_pkg_dir,
self.efi_suffix, self.efi_suffix,
self.grub_target, self.grub_target,
self.boot_tree, self.iso_root,
) )
# Create ESP image for El-Torito catalog and hybrid boot # Create ESP image for El-Torito catalog and hybrid boot
create_eltorito_esp_image( create_eltorito_esp_image(
self.logger, self.boot_tree, self.scratch.joinpath("cd-boot-efi.img") self.logger, self.iso_root, self.scratch.joinpath("cd-boot-efi.img")
) )
def write_uefi_menu_entries(self, grub_cfg: pathlib.Path) -> None: def uefi_menu_entries(self) -> str:
"""Write UEFI firmware menu entries.""" """Return UEFI firmware menu entries."""
with grub_cfg.open("a") as f: return """\
f.write( menuentry 'Boot from next volume' {
"""menuentry 'Boot from next volume' { exit 1
\texit 1
} }
menuentry 'UEFI Firmware Settings' { menuentry 'UEFI Firmware Settings' {
\tfwsetup fwsetup
} }
""" """
)
def get_uefi_mkisofs_opts(self) -> list[str | pathlib.Path]: def get_uefi_mkisofs_opts(self) -> list[str | pathlib.Path]:
"""Return common UEFI mkisofs options.""" """Return common UEFI mkisofs options."""

View File

@ -298,10 +298,10 @@ class ISOBuilder:
self.arch, self.arch,
self.logger, self.logger,
self.apt_state, self.apt_state,
self.workdir,
self.iso_root, self.iso_root,
) )
configurator.make_bootable( configurator.make_bootable(
self.workdir,
project, project,
capproject, capproject,
subarch, subarch,
@ -341,9 +341,9 @@ class ISOBuilder:
self.arch, self.arch,
self.logger, self.logger,
self.apt_state, self.apt_state,
self.workdir,
self.iso_root, self.iso_root,
) )
configurator.create_dirs(self.workdir)
mkisofs_opts = configurator.mkisofs_opts() mkisofs_opts = configurator.mkisofs_opts()
cmd: list[str | pathlib.Path] = ["xorriso"] cmd: list[str | pathlib.Path] = ["xorriso"]
if self.arch == "riscv64": if self.arch == "riscv64":