mirror of
https://git.launchpad.net/livecd-rootfs
synced 2026-03-13 19:17:42 +00:00
Compare commits
No commits in common. "ubuntu/master" and "26.04.18" have entirely different histories.
ubuntu/mas
...
26.04.18
56
debian/changelog
vendored
56
debian/changelog
vendored
@ -1,59 +1,3 @@
|
|||||||
livecd-rootfs (26.04.23) resolute; urgency=medium
|
|
||||||
|
|
||||||
[ Tobias Heider ]
|
|
||||||
* Fix ISO builds when KERNEL_FLAVOUR != generic.
|
|
||||||
|
|
||||||
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Mon, 02 Mar 2026 10:51:47 +1300
|
|
||||||
|
|
||||||
livecd-rootfs (26.04.22) resolute; urgency=medium
|
|
||||||
|
|
||||||
[ Oliver Gayot ]
|
|
||||||
* Pull the model from Launchpad's lp:canonical-models
|
|
||||||
repo, instead of having it uploaded as part of livecd-rootfs. This
|
|
||||||
indirection makes it possible to update the models without requiring a new
|
|
||||||
upload of livecd-rootfs every time.
|
|
||||||
|
|
||||||
[ Michael Hudson-Doyle ]
|
|
||||||
* Fix two more problems with livefs-built ISOs:
|
|
||||||
- Generate the for-iso squashfs in the right place for Kubuntu.
|
|
||||||
- Fix confusion about the kernel path on the ISO on riscv64.
|
|
||||||
|
|
||||||
[ Tobias Heider ]
|
|
||||||
* Fix pool generation when using extra_ppas.
|
|
||||||
|
|
||||||
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Thu, 26 Feb 2026 10:56:42 +1300
|
|
||||||
|
|
||||||
livecd-rootfs (26.04.21) resolute; urgency=medium
|
|
||||||
|
|
||||||
[ Dan Bungert ]
|
|
||||||
* Update new signed models to ship latest nvidia drivers for ubuntu hybrid.
|
|
||||||
|
|
||||||
-- Didier Roche-Tolomelli <didrocks@ubuntu.com> Wed, 25 Feb 2026 08:38:32 +0100
|
|
||||||
|
|
||||||
livecd-rootfs (26.04.20) resolute; urgency=medium
|
|
||||||
|
|
||||||
[ Michael Raymond ]
|
|
||||||
* Bug-fix: Only use main archive keyring when building with debootstrap
|
|
||||||
so EOL release signatures can be verified after EOL.
|
|
||||||
|
|
||||||
[ Allen Abraham ]
|
|
||||||
* Make SBOM generation optional in create_manifest function.
|
|
||||||
|
|
||||||
[ Michael Hudson-Doyle ]
|
|
||||||
* 030-ubuntu-live-system-seed.binary: do not run if there is no layer to
|
|
||||||
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> Fri, 20 Feb 2026 12:45:41 +1300
|
|
||||||
|
|
||||||
livecd-rootfs (26.04.19) resolute; urgency=medium
|
|
||||||
|
|
||||||
* Translate the debian-cd tools/boot/$series/boot-$arch scripts to Python
|
|
||||||
and use that to make ISOs bootable rather than cloning debian-cd.
|
|
||||||
|
|
||||||
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Tue, 17 Feb 2026 11:16:43 +1300
|
|
||||||
|
|
||||||
livecd-rootfs (26.04.18) resolute; urgency=medium
|
livecd-rootfs (26.04.18) resolute; urgency=medium
|
||||||
|
|
||||||
[ Michael Hudson-Doyle ]
|
[ Michael Hudson-Doyle ]
|
||||||
|
|||||||
@ -576,7 +576,7 @@ if [ "${MAKE_ISO}" = "yes" ]; then
|
|||||||
# create a for-iso.filesystem.squashfs that does.
|
# create a for-iso.filesystem.squashfs that does.
|
||||||
if [ -z "$PASSES" ]; then
|
if [ -z "$PASSES" ]; then
|
||||||
isobuild generate-sources --mountpoint=/cdrom > chroot/etc/apt/sources.list.d/cdrom.sources
|
isobuild generate-sources --mountpoint=/cdrom > chroot/etc/apt/sources.list.d/cdrom.sources
|
||||||
create_squashfs chroot ${PWD}/for-iso.filesystem.squashfs
|
create_squashfs chroot for-iso.filesystem.squashfs
|
||||||
fi
|
fi
|
||||||
# Link kernel and initrd files. The ${thing#${PREFIX}} expansion strips
|
# Link kernel and initrd files. The ${thing#${PREFIX}} expansion strips
|
||||||
# the PREFIX, so "livecd.ubuntu-server.kernel-generic" becomes
|
# the PREFIX, so "livecd.ubuntu-server.kernel-generic" becomes
|
||||||
|
|||||||
@ -1166,7 +1166,6 @@ case $PROJECT in
|
|||||||
OPTS="${OPTS:+$OPTS }--linux-packages=none --initramfs=none"
|
OPTS="${OPTS:+$OPTS }--linux-packages=none --initramfs=none"
|
||||||
KERNEL_FLAVOURS=none
|
KERNEL_FLAVOURS=none
|
||||||
BINARY_REMOVE_LINUX=false
|
BINARY_REMOVE_LINUX=false
|
||||||
MAKE_ISO=yes
|
|
||||||
|
|
||||||
add_package install mini-iso-tools linux-generic
|
add_package install mini-iso-tools linux-generic
|
||||||
case $ARCH in
|
case $ARCH in
|
||||||
@ -1397,8 +1396,6 @@ if [ -n "$PASSES" ] && [ -z "$LIVE_PASSES" ]; then
|
|||||||
"Either set \$LIVE_PASSES or add a pass ending with '.live'."
|
"Either set \$LIVE_PASSES or add a pass ending with '.live'."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "DEBOOTSTRAP_OPTIONS=\"--keyring=/usr/share/keyrings/ubuntu-archive-keyring.gpg\"" >> config/bootstrap
|
|
||||||
|
|
||||||
echo "LB_CHROOT_HOOKS=\"$CHROOT_HOOKS\"" >> config/chroot
|
echo "LB_CHROOT_HOOKS=\"$CHROOT_HOOKS\"" >> config/chroot
|
||||||
echo "SUBPROJECT=\"${SUBPROJECT:-}\"" >> config/chroot
|
echo "SUBPROJECT=\"${SUBPROJECT:-}\"" >> config/chroot
|
||||||
echo "LB_DISTRIBUTION=\"$SUITE\"" >> config/chroot
|
echo "LB_DISTRIBUTION=\"$SUITE\"" >> config/chroot
|
||||||
|
|||||||
@ -44,7 +44,6 @@ create_manifest() {
|
|||||||
local base_default_sbom_name="ubuntu-cloud-image-$(grep "VERSION_ID" $chroot_root/etc/os-release | cut --delimiter "=" --field 2 | tr -d '"')-${ARCH}-$(date +%Y%m%dT%H:%M:%S)"
|
local base_default_sbom_name="ubuntu-cloud-image-$(grep "VERSION_ID" $chroot_root/etc/os-release | cut --delimiter "=" --field 2 | tr -d '"')-${ARCH}-$(date +%Y%m%dT%H:%M:%S)"
|
||||||
local sbom_file_name=${3:-"${base_default_sbom_name}.spdx"}
|
local sbom_file_name=${3:-"${base_default_sbom_name}.spdx"}
|
||||||
local sbom_document_name=${4:-"${base_default_sbom_name}"}
|
local sbom_document_name=${4:-"${base_default_sbom_name}"}
|
||||||
local should_include_sbom=${5:-"true"}
|
|
||||||
local sbom_log=${sbom_document_name}.log
|
local sbom_log=${sbom_document_name}.log
|
||||||
echo "create_manifest chroot_root: ${chroot_root}"
|
echo "create_manifest chroot_root: ${chroot_root}"
|
||||||
dpkg-query --show --admindir="${chroot_root}/var/lib/dpkg" > ${target_file}
|
dpkg-query --show --admindir="${chroot_root}/var/lib/dpkg" > ${target_file}
|
||||||
@ -55,25 +54,21 @@ create_manifest() {
|
|||||||
echo "create_manifest creating file listing."
|
echo "create_manifest creating file listing."
|
||||||
local target_filelist=${2%.manifest}.filelist
|
local target_filelist=${2%.manifest}.filelist
|
||||||
(cd "${chroot_root}" && find -xdev) | sort > "${target_filelist}"
|
(cd "${chroot_root}" && find -xdev) | sort > "${target_filelist}"
|
||||||
if [ "$should_include_sbom" = "true" ]; then
|
# only creating sboms for CPC project at this time
|
||||||
# only creating sboms for CPC project at this time
|
if [[ ! $(which cpc-sbom) ]]; then
|
||||||
if [[ ! $(which cpc-sbom) ]]; then
|
# ensure the tool is installed
|
||||||
# ensure the tool is installed
|
sudo snap install --classic --edge cpc-sbom
|
||||||
sudo snap install --classic --edge cpc-sbom
|
fi
|
||||||
fi
|
# generate the SBOM
|
||||||
# generate the SBOM
|
cpc-sbom --rootdir ${chroot_root} --ignore-copyright-parsing-errors --ignore-copyright-file-not-found-errors --document-name ${sbom_document_name} >"${sbom_file_name}" 2>"${sbom_log}"
|
||||||
cpc-sbom --rootdir ${chroot_root} --ignore-copyright-parsing-errors --ignore-copyright-file-not-found-errors --document-name ${sbom_document_name} >"${sbom_file_name}" 2>"${sbom_log}"
|
SBOM_GENERATION_EXIT_CODE=$?
|
||||||
SBOM_GENERATION_EXIT_CODE=$?
|
if [[ ${SBOM_GENERATION_EXIT_CODE} != "0" ]]; then
|
||||||
if [[ ${SBOM_GENERATION_EXIT_CODE} != "0" ]]; then
|
# check for failure and print log
|
||||||
# check for failure and print log
|
echo "ERROR: SBOM generation failed. See ${sbom_log}"
|
||||||
echo "ERROR: SBOM generation failed. See ${sbom_log}"
|
cat "$sbom_log"
|
||||||
cat "$sbom_log"
|
exit 1
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "SBOM generation succeeded. see ${sbom_log} for details"
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
echo "SBOM generation skipped"
|
echo "SBOM generation succeeded. see ${sbom_log} for details"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
echo "create_manifest finished"
|
echo "create_manifest finished"
|
||||||
|
|||||||
@ -82,7 +82,7 @@ class AptStateManager:
|
|||||||
if params:
|
if params:
|
||||||
yield PackageInfo(**params)
|
yield PackageInfo(**params)
|
||||||
|
|
||||||
def download(self, rootdir: pathlib.Path, pkg_info: PackageInfo) -> None:
|
def download(self, rootdir: pathlib.Path, pkg_info: PackageInfo):
|
||||||
"""Download the package specified by `pkg_info` under `rootdir`.
|
"""Download the package specified by `pkg_info` under `rootdir`.
|
||||||
|
|
||||||
The package is saved to the same path under `rootdir` as it is
|
The package is saved to the same path under `rootdir` as it is
|
||||||
@ -90,17 +90,9 @@ class AptStateManager:
|
|||||||
"""
|
"""
|
||||||
target_dir = rootdir.joinpath(pkg_info.filename).parent
|
target_dir = rootdir.joinpath(pkg_info.filename).parent
|
||||||
target_dir.mkdir(parents=True, exist_ok=True)
|
target_dir.mkdir(parents=True, exist_ok=True)
|
||||||
self.download_direct(pkg_info.spec, target_dir)
|
|
||||||
|
|
||||||
def download_direct(self, spec: str, target: pathlib.Path) -> None:
|
|
||||||
"""Download the package specified by spec to target directory.
|
|
||||||
|
|
||||||
The package is downloaded using apt-get download and saved directly
|
|
||||||
in the target directory.
|
|
||||||
"""
|
|
||||||
self.logger.run(
|
self.logger.run(
|
||||||
["apt-get", "download", spec],
|
["apt-get", "download", pkg_info.spec],
|
||||||
cwd=target,
|
cwd=target_dir,
|
||||||
check=True,
|
check=True,
|
||||||
env=self._apt_env(),
|
env=self._apt_env(),
|
||||||
)
|
)
|
||||||
@ -108,10 +100,10 @@ class AptStateManager:
|
|||||||
def in_release_path(self) -> pathlib.Path:
|
def in_release_path(self) -> pathlib.Path:
|
||||||
"""Return the path to the InRelease file.
|
"""Return the path to the InRelease file.
|
||||||
|
|
||||||
This ignores all but the first path.
|
This assumes exactly one InRelease file matches the pattern.
|
||||||
Will raise Error if there isn't at least one match.
|
Will raise ValueError if there are 0 or multiple matches.
|
||||||
"""
|
"""
|
||||||
[path] = self.apt_root.joinpath("var/lib/apt/lists").glob(
|
[path] = self.apt_root.joinpath("var/lib/apt/lists").glob(
|
||||||
f"*ubuntu.com*_dists_{self.series}_InRelease"
|
f"*_dists_{self.series}_InRelease"
|
||||||
)
|
)
|
||||||
return path
|
return path
|
||||||
|
|||||||
@ -1,49 +0,0 @@
|
|||||||
"""Boot configuration package for ISO builder.
|
|
||||||
|
|
||||||
This package contains architecture-specific boot configurators for building
|
|
||||||
bootable ISOs for different architectures.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pathlib
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from ..apt_state import AptStateManager
|
|
||||||
from ..builder import Logger
|
|
||||||
from .base import BaseBootConfigurator
|
|
||||||
|
|
||||||
|
|
||||||
def make_boot_configurator_for_arch(
|
|
||||||
arch: str,
|
|
||||||
logger: "Logger",
|
|
||||||
apt_state: "AptStateManager",
|
|
||||||
workdir: pathlib.Path,
|
|
||||||
iso_root: pathlib.Path,
|
|
||||||
) -> "BaseBootConfigurator":
|
|
||||||
"""Factory function to create boot configurator for a specific architecture."""
|
|
||||||
match arch:
|
|
||||||
case "amd64":
|
|
||||||
from .amd64 import AMD64BootConfigurator
|
|
||||||
|
|
||||||
return AMD64BootConfigurator(logger, apt_state, workdir, iso_root)
|
|
||||||
case "arm64":
|
|
||||||
from .arm64 import ARM64BootConfigurator
|
|
||||||
|
|
||||||
return ARM64BootConfigurator(logger, apt_state, workdir, iso_root)
|
|
||||||
case "ppc64el":
|
|
||||||
from .ppc64el import PPC64ELBootConfigurator
|
|
||||||
|
|
||||||
return PPC64ELBootConfigurator(logger, apt_state, workdir, iso_root)
|
|
||||||
case "riscv64":
|
|
||||||
from .riscv64 import RISCV64BootConfigurator
|
|
||||||
|
|
||||||
return RISCV64BootConfigurator(logger, apt_state, workdir, iso_root)
|
|
||||||
case "s390x":
|
|
||||||
from .s390x import S390XBootConfigurator
|
|
||||||
|
|
||||||
return S390XBootConfigurator(logger, apt_state, workdir, iso_root)
|
|
||||||
case _:
|
|
||||||
raise ValueError(f"Unsupported architecture: {arch}")
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["make_boot_configurator_for_arch"]
|
|
||||||
@ -1,216 +0,0 @@
|
|||||||
"""AMD64/x86_64 architecture boot configuration."""
|
|
||||||
|
|
||||||
import pathlib
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
from .base import default_kernel_params
|
|
||||||
from .grub import copy_grub_modules
|
|
||||||
from .uefi import UEFIBootConfigurator
|
|
||||||
|
|
||||||
|
|
||||||
CALAMARES_PROJECTS = ["kubuntu", "lubuntu"]
|
|
||||||
|
|
||||||
|
|
||||||
class AMD64BootConfigurator(UEFIBootConfigurator):
|
|
||||||
"""Boot setup for AMD64/x86_64 architecture."""
|
|
||||||
|
|
||||||
efi_suffix = "x64"
|
|
||||||
grub_target = "x86_64"
|
|
||||||
arch = "amd64"
|
|
||||||
|
|
||||||
def mkisofs_opts(self) -> list[str | pathlib.Path]:
|
|
||||||
# Boring mkisofs options that should be set somewhere architecture independent.
|
|
||||||
opts: list[str | pathlib.Path] = ["-J", "-joliet-long", "-l"]
|
|
||||||
|
|
||||||
# Generalities on booting
|
|
||||||
#
|
|
||||||
# There is a 2x2 matrix of boot modes we care about: legacy or UEFI
|
|
||||||
# boot modes and having the installer be on a cdrom or a disk. Booting
|
|
||||||
# from cdrom uses the el torito standard and booting from disk expects
|
|
||||||
# a MBR or GPT partition table.
|
|
||||||
#
|
|
||||||
# https://wiki.osdev.org/El-Torito has a lot more background on this.
|
|
||||||
|
|
||||||
# ## Set up the mkisofs options for legacy boot.
|
|
||||||
|
|
||||||
# Set the el torito boot image "name", i.e. the path on the ISO
|
|
||||||
# containing the bootloader for legacy-cdrom boot.
|
|
||||||
opts.extend(["-b", "boot/grub/i386-pc/eltorito.img"])
|
|
||||||
|
|
||||||
# Back in the day, el torito booting worked by emulating a floppy
|
|
||||||
# drive. This hasn't been a useful way of operating for a long time.
|
|
||||||
opts.append("-no-emul-boot")
|
|
||||||
|
|
||||||
# Misc options to make the legacy-cdrom boot work.
|
|
||||||
opts.extend(["-boot-load-size", "4", "-boot-info-table", "--grub2-boot-info"])
|
|
||||||
|
|
||||||
# The bootloader to write to the MBR for legacy-disk boot.
|
|
||||||
#
|
|
||||||
# We use the grub stage1 bootloader, boot_hybrid.img, which then jumps
|
|
||||||
# to the eltorito image based on the information xorriso provides it
|
|
||||||
# via the --grub2-boot-info option.
|
|
||||||
opts.extend(
|
|
||||||
[
|
|
||||||
"--grub2-mbr",
|
|
||||||
self.scratch.joinpath("boot_hybrid.img"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
# ## Set up the mkisofs options for UEFI boot.
|
|
||||||
opts.extend(self.get_uefi_mkisofs_opts())
|
|
||||||
|
|
||||||
return opts
|
|
||||||
|
|
||||||
def extract_files(self) -> None:
|
|
||||||
with self.logger.logged("extracting AMD64 boot files"):
|
|
||||||
|
|
||||||
# Extract UEFI files (common with ARM64)
|
|
||||||
self.extract_uefi_files()
|
|
||||||
|
|
||||||
# AMD64-specific: Add BIOS/legacy boot files
|
|
||||||
with self.logger.logged("adding BIOS/legacy boot files"):
|
|
||||||
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.iso_root.joinpath("boot", "grub", "i386-pc")
|
|
||||||
grub_boot_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
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("boot_hybrid.img"), self.scratch)
|
|
||||||
|
|
||||||
copy_grub_modules(
|
|
||||||
grub_pc_pkg_dir,
|
|
||||||
self.iso_root,
|
|
||||||
"i386-pc",
|
|
||||||
["*.mod", "*.lst", "*.o"],
|
|
||||||
)
|
|
||||||
|
|
||||||
def generate_grub_config(self) -> str:
|
|
||||||
"""Generate grub.cfg content for AMD64."""
|
|
||||||
result = self.grub_header()
|
|
||||||
|
|
||||||
if self.project == "ubuntu-mini-iso":
|
|
||||||
result += """\
|
|
||||||
menuentry "Choose an Ubuntu version to install" {
|
|
||||||
set gfxpayload=keep
|
|
||||||
linux /casper/vmlinuz iso-chooser-menu ip=dhcp ---
|
|
||||||
initrd /casper/initrd
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
return result
|
|
||||||
|
|
||||||
kernel_params = default_kernel_params(self.project)
|
|
||||||
|
|
||||||
# Main menu entry
|
|
||||||
result += f"""\
|
|
||||||
menuentry "Try or Install {self.humanproject}" {{
|
|
||||||
set gfxpayload=keep
|
|
||||||
linux /casper/vmlinuz {kernel_params}
|
|
||||||
initrd /casper/initrd
|
|
||||||
}}
|
|
||||||
"""
|
|
||||||
|
|
||||||
# All but server get safe-graphics mode
|
|
||||||
if self.project != "ubuntu-server":
|
|
||||||
result += f"""\
|
|
||||||
menuentry "{self.humanproject} (safe graphics)" {{
|
|
||||||
set gfxpayload=keep
|
|
||||||
linux /casper/vmlinuz nomodeset {kernel_params}
|
|
||||||
initrd /casper/initrd
|
|
||||||
}}
|
|
||||||
"""
|
|
||||||
|
|
||||||
# ubiquity based projects get OEM mode
|
|
||||||
if "maybe-ubiquity" in kernel_params:
|
|
||||||
oem_kernel_params = kernel_params.replace(
|
|
||||||
"maybe-ubiquity", "only-ubiquity oem-config/enable=true"
|
|
||||||
)
|
|
||||||
result += f"""\
|
|
||||||
menuentry "OEM install (for manufacturers)" {{
|
|
||||||
set gfxpayload=keep
|
|
||||||
linux /casper/vmlinuz {oem_kernel_params}
|
|
||||||
initrd /casper/initrd
|
|
||||||
}}
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Calamares-based projects get OEM mode
|
|
||||||
if self.project in CALAMARES_PROJECTS:
|
|
||||||
result += f"""\
|
|
||||||
menuentry "OEM install (for manufacturers)" {{
|
|
||||||
set gfxpayload=keep
|
|
||||||
linux /casper/vmlinuz {kernel_params} oem-config/enable=true
|
|
||||||
initrd /casper/initrd
|
|
||||||
}}
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Currently only server is built with HWE, hence no safe-graphics/OEM
|
|
||||||
if self.hwe:
|
|
||||||
result += f"""\
|
|
||||||
menuentry "{self.humanproject} with the HWE kernel" {{
|
|
||||||
set gfxpayload=keep
|
|
||||||
linux /casper/hwe-vmlinuz {kernel_params}
|
|
||||||
initrd /casper/hwe-initrd
|
|
||||||
}}
|
|
||||||
"""
|
|
||||||
|
|
||||||
# UEFI Entries (wrapped in grub_platform check for dual BIOS/UEFI support)
|
|
||||||
uefi_menu_entries = self.uefi_menu_entries()
|
|
||||||
|
|
||||||
result += f"""\
|
|
||||||
grub_platform
|
|
||||||
if [ "$grub_platform" = "efi" ]; then
|
|
||||||
{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
|
|
||||||
for i, line in enumerate(lines):
|
|
||||||
if "menu_color_highlight" in line:
|
|
||||||
start_idx = i + 1
|
|
||||||
break
|
|
||||||
|
|
||||||
end_idx = len(lines)
|
|
||||||
for i, line in enumerate(lines):
|
|
||||||
if "grub_platform" in line:
|
|
||||||
end_idx = i
|
|
||||||
break
|
|
||||||
|
|
||||||
loopback_lines = lines[start_idx:end_idx]
|
|
||||||
loopback_lines = [
|
|
||||||
(
|
|
||||||
line.replace("---", "iso-scan/filename=${iso_path} ---")
|
|
||||||
if "linux" in line
|
|
||||||
else line
|
|
||||||
)
|
|
||||||
for line in loopback_lines
|
|
||||||
]
|
|
||||||
|
|
||||||
return "\n".join(loopback_lines)
|
|
||||||
|
|
||||||
def make_bootable(
|
|
||||||
self,
|
|
||||||
project: str,
|
|
||||||
capproject: str,
|
|
||||||
subarch: str,
|
|
||||||
hwe: bool,
|
|
||||||
) -> None:
|
|
||||||
"""Make the ISO bootable, including generating loopback.cfg."""
|
|
||||||
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)
|
|
||||||
)
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
"""ARM 64-bit architecture boot configuration."""
|
|
||||||
|
|
||||||
import pathlib
|
|
||||||
|
|
||||||
from .uefi import UEFIBootConfigurator
|
|
||||||
from .base import default_kernel_params
|
|
||||||
|
|
||||||
|
|
||||||
class ARM64BootConfigurator(UEFIBootConfigurator):
|
|
||||||
"""Boot setup for ARM 64-bit architecture."""
|
|
||||||
|
|
||||||
efi_suffix = "aa64"
|
|
||||||
grub_target = "arm64"
|
|
||||||
arch = "arm64"
|
|
||||||
|
|
||||||
def mkisofs_opts(self) -> list[str | pathlib.Path]:
|
|
||||||
"""Return mkisofs options for ARM64."""
|
|
||||||
opts: list[str | pathlib.Path] = [
|
|
||||||
"-J",
|
|
||||||
"-joliet-long",
|
|
||||||
"-l",
|
|
||||||
"-c",
|
|
||||||
"boot/boot.cat",
|
|
||||||
]
|
|
||||||
# Add common UEFI options
|
|
||||||
opts.extend(self.get_uefi_mkisofs_opts())
|
|
||||||
# ARM64-specific: partition cylinder alignment
|
|
||||||
opts.extend(["-partition_cyl_align", "all"])
|
|
||||||
return opts
|
|
||||||
|
|
||||||
def extract_files(self) -> None:
|
|
||||||
"""Download and extract bootloader packages for ARM64."""
|
|
||||||
with self.logger.logged("extracting ARM64 boot files"):
|
|
||||||
self.extract_uefi_files()
|
|
||||||
|
|
||||||
def generate_grub_config(self) -> str:
|
|
||||||
"""Generate grub.cfg for ARM64."""
|
|
||||||
kernel_params = default_kernel_params(self.project)
|
|
||||||
|
|
||||||
result = self.grub_header()
|
|
||||||
|
|
||||||
# ARM64-specific: Snapdragon workarounds
|
|
||||||
result += f"""\
|
|
||||||
set cmdline=
|
|
||||||
smbios --type 4 --get-string 5 --set proc_version
|
|
||||||
regexp "Snapdragon.*" "$proc_version"
|
|
||||||
if [ $? = 0 ]; then
|
|
||||||
# Work around Snapdragon X firmware bug. cutmem is not allowed in lockdown mode.
|
|
||||||
if [ $lockdown != "y" ]; then
|
|
||||||
cutmem 0x8800000000 0x8fffffffff
|
|
||||||
fi
|
|
||||||
# arm64.nopauth works around 8cx Gen 3 firmware bug
|
|
||||||
cmdline="clk_ignore_unused pd_ignore_unused arm64.nopauth"
|
|
||||||
fi
|
|
||||||
|
|
||||||
menuentry "Try or Install {self.humanproject}" {{
|
|
||||||
set gfxpayload=keep
|
|
||||||
linux /casper/vmlinuz $cmdline {kernel_params} console=tty0
|
|
||||||
initrd /casper/initrd
|
|
||||||
}}
|
|
||||||
"""
|
|
||||||
|
|
||||||
# HWE kernel option if available
|
|
||||||
result += self.hwe_menu_entry(
|
|
||||||
"vmlinuz",
|
|
||||||
f"{kernel_params} console=tty0",
|
|
||||||
extra_params="$cmdline ",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Note: ARM64 HWE also includes $dtb in the original shell script,
|
|
||||||
# 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)
|
|
||||||
result += self.uefi_menu_entries()
|
|
||||||
|
|
||||||
return result
|
|
||||||
@ -1,98 +0,0 @@
|
|||||||
"""Base classes and helper functions for boot configuration."""
|
|
||||||
|
|
||||||
import pathlib
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
|
|
||||||
from ..builder import Logger
|
|
||||||
from ..apt_state import AptStateManager
|
|
||||||
|
|
||||||
|
|
||||||
def default_kernel_params(project: str) -> str:
|
|
||||||
if project == "ubuntukylin":
|
|
||||||
return (
|
|
||||||
"file=/cdrom/preseed/ubuntu.seed locale=zh_CN "
|
|
||||||
"keyboard-configuration/layoutcode?=cn quiet splash --- "
|
|
||||||
)
|
|
||||||
if project == "ubuntu-server":
|
|
||||||
return " --- "
|
|
||||||
else:
|
|
||||||
return " --- quiet splash"
|
|
||||||
|
|
||||||
|
|
||||||
class BaseBootConfigurator(ABC):
|
|
||||||
"""Abstract base class for architecture-specific boot configurators.
|
|
||||||
|
|
||||||
Subclasses must implement:
|
|
||||||
- extract_files(): Download and extract bootloader packages
|
|
||||||
- mkisofs_opts(): Return mkisofs command-line options
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
logger: Logger,
|
|
||||||
apt_state: AptStateManager,
|
|
||||||
workdir: pathlib.Path,
|
|
||||||
iso_root: pathlib.Path,
|
|
||||||
) -> None:
|
|
||||||
self.logger = logger
|
|
||||||
self.apt_state = apt_state
|
|
||||||
self.scratch = workdir.joinpath("boot-stuff")
|
|
||||||
self.iso_root = iso_root
|
|
||||||
|
|
||||||
def download_and_extract_package(
|
|
||||||
self, pkg_name: str, target_dir: pathlib.Path
|
|
||||||
) -> None:
|
|
||||||
"""Download a Debian package and extract its contents to target directory."""
|
|
||||||
self.logger.log(f"downloading and extracting {pkg_name}")
|
|
||||||
target_dir.mkdir(exist_ok=True, parents=True)
|
|
||||||
with tempfile.TemporaryDirectory() as tdir_str:
|
|
||||||
tdir = pathlib.Path(tdir_str)
|
|
||||||
self.apt_state.download_direct(pkg_name, tdir)
|
|
||||||
[deb] = tdir.glob("*.deb")
|
|
||||||
dpkg_proc = subprocess.Popen(
|
|
||||||
["dpkg-deb", "--fsys-tarfile", deb], stdout=subprocess.PIPE
|
|
||||||
)
|
|
||||||
tar_proc = subprocess.Popen(
|
|
||||||
["tar", "xf", "-", "-C", target_dir], stdin=dpkg_proc.stdout
|
|
||||||
)
|
|
||||||
assert dpkg_proc.stdout is not None
|
|
||||||
dpkg_proc.stdout.close()
|
|
||||||
tar_proc.communicate()
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def extract_files(self) -> None:
|
|
||||||
"""Download and extract bootloader packages to the boot tree.
|
|
||||||
|
|
||||||
Each architecture must implement this to set up its specific bootloader files.
|
|
||||||
"""
|
|
||||||
...
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def mkisofs_opts(self) -> list[str | pathlib.Path]:
|
|
||||||
"""Return mkisofs command-line options for this architecture.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of command-line options to pass to mkisofs/xorriso.
|
|
||||||
"""
|
|
||||||
...
|
|
||||||
|
|
||||||
def post_process_iso(self, iso_path: pathlib.Path) -> None:
|
|
||||||
"""Post-process the ISO image after xorriso creates it."""
|
|
||||||
|
|
||||||
def make_bootable(
|
|
||||||
self,
|
|
||||||
project: str,
|
|
||||||
capproject: str,
|
|
||||||
subarch: str,
|
|
||||||
hwe: bool,
|
|
||||||
) -> None:
|
|
||||||
"""Make the ISO bootable by extracting bootloader files."""
|
|
||||||
self.project = project
|
|
||||||
self.humanproject = capproject.replace("-", " ")
|
|
||||||
self.subarch = subarch
|
|
||||||
self.hwe = hwe
|
|
||||||
self.scratch.mkdir(exist_ok=True)
|
|
||||||
with self.logger.logged("configuring boot"):
|
|
||||||
self.extract_files()
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
"""GRUB boot configuration for multiple architectures."""
|
|
||||||
|
|
||||||
import pathlib
|
|
||||||
import shutil
|
|
||||||
from abc import abstractmethod
|
|
||||||
|
|
||||||
from .base import BaseBootConfigurator
|
|
||||||
|
|
||||||
|
|
||||||
def copy_grub_common_files(grub_pkg_dir: pathlib.Path, iso_root: pathlib.Path) -> None:
|
|
||||||
fonts_dir = iso_root.joinpath("boot", "grub", "fonts")
|
|
||||||
fonts_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
src = grub_pkg_dir.joinpath("usr", "share", "grub", "unicode.pf2")
|
|
||||||
dst = fonts_dir.joinpath("unicode.pf2")
|
|
||||||
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):
|
|
||||||
"""Base class for architectures that use GRUB (all except S390X).
|
|
||||||
|
|
||||||
Common GRUB functionality shared across AMD64, ARM64, PPC64EL, and RISC-V64.
|
|
||||||
Subclasses must implement generate_grub_config().
|
|
||||||
"""
|
|
||||||
|
|
||||||
def grub_header(self, include_loadfont: bool = True) -> str:
|
|
||||||
"""Return common GRUB config header (timeout, colors).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
include_loadfont: Whether to include 'loadfont unicode'
|
|
||||||
(not needed for RISC-V)
|
|
||||||
"""
|
|
||||||
result = "set timeout=30\n\n"
|
|
||||||
if include_loadfont:
|
|
||||||
result += "loadfont unicode\n\n"
|
|
||||||
result += """\
|
|
||||||
set menu_color_normal=white/black
|
|
||||||
set menu_color_highlight=black/light-gray
|
|
||||||
|
|
||||||
"""
|
|
||||||
return result
|
|
||||||
|
|
||||||
def hwe_menu_entry(
|
|
||||||
self,
|
|
||||||
kernel_name: str,
|
|
||||||
kernel_params: str,
|
|
||||||
extra_params: str = "",
|
|
||||||
) -> str:
|
|
||||||
"""Return HWE kernel menu entry if HWE is enabled.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
kernel_name: Kernel binary name (vmlinuz or vmlinux)
|
|
||||||
kernel_params: Kernel parameters to append
|
|
||||||
extra_params: Additional parameters (e.g., console=tty0, $cmdline)
|
|
||||||
"""
|
|
||||||
if not self.hwe:
|
|
||||||
return ""
|
|
||||||
return f"""\
|
|
||||||
menuentry "{self.humanproject} with the HWE kernel" {{
|
|
||||||
set gfxpayload=keep
|
|
||||||
linux /casper/hwe-{kernel_name} {extra_params}{kernel_params}
|
|
||||||
initrd /casper/hwe-initrd
|
|
||||||
}}
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def generate_grub_config(self) -> str:
|
|
||||||
"""Generate grub.cfg content.
|
|
||||||
|
|
||||||
Each GRUB-based architecture must implement this to return the
|
|
||||||
GRUB configuration.
|
|
||||||
"""
|
|
||||||
...
|
|
||||||
|
|
||||||
def make_bootable(
|
|
||||||
self,
|
|
||||||
project: str,
|
|
||||||
capproject: str,
|
|
||||||
subarch: str,
|
|
||||||
hwe: bool,
|
|
||||||
) -> None:
|
|
||||||
"""Make the ISO bootable by extracting files and generating GRUB config."""
|
|
||||||
super().make_bootable(project, capproject, subarch, hwe)
|
|
||||||
with self.logger.logged("generating 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)
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
"""PowerPC 64-bit Little Endian architecture boot configuration."""
|
|
||||||
|
|
||||||
import pathlib
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
from .grub import (
|
|
||||||
copy_grub_common_files,
|
|
||||||
copy_grub_modules,
|
|
||||||
GrubBootConfigurator,
|
|
||||||
)
|
|
||||||
from .base import default_kernel_params
|
|
||||||
|
|
||||||
|
|
||||||
class PPC64ELBootConfigurator(GrubBootConfigurator):
|
|
||||||
"""Boot setup for PowerPC 64-bit Little Endian architecture."""
|
|
||||||
|
|
||||||
def mkisofs_opts(self) -> list[str | pathlib.Path]:
|
|
||||||
"""Return mkisofs options for PPC64EL."""
|
|
||||||
return []
|
|
||||||
|
|
||||||
def extract_files(self) -> None:
|
|
||||||
"""Download and extract bootloader packages for PPC64EL."""
|
|
||||||
self.logger.log("extracting PPC64EL boot files")
|
|
||||||
|
|
||||||
grub_pkg_dir = self.scratch.joinpath("grub-pkg")
|
|
||||||
|
|
||||||
# Download and extract bootloader packages
|
|
||||||
self.download_and_extract_package("grub2-common", grub_pkg_dir)
|
|
||||||
self.download_and_extract_package("grub-ieee1275-bin", grub_pkg_dir)
|
|
||||||
|
|
||||||
# Add common files for GRUB to tree
|
|
||||||
copy_grub_common_files(grub_pkg_dir, self.iso_root)
|
|
||||||
|
|
||||||
# Add IEEE1275 ppc boot files
|
|
||||||
ppc_dir = self.iso_root.joinpath("ppc")
|
|
||||||
ppc_dir.mkdir()
|
|
||||||
|
|
||||||
src_grub_dir = grub_pkg_dir.joinpath("usr", "lib", "grub", "powerpc-ieee1275")
|
|
||||||
|
|
||||||
# Copy bootinfo.txt to ppc directory
|
|
||||||
shutil.copy(
|
|
||||||
src_grub_dir.joinpath("bootinfo.txt"), ppc_dir.joinpath("bootinfo.txt")
|
|
||||||
)
|
|
||||||
|
|
||||||
# Copy eltorito.elf to boot/grub as powerpc.elf
|
|
||||||
shutil.copy(
|
|
||||||
src_grub_dir.joinpath("eltorito.elf"),
|
|
||||||
self.iso_root.joinpath("boot", "grub", "powerpc.elf"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Copy GRUB modules
|
|
||||||
copy_grub_modules(
|
|
||||||
grub_pkg_dir, self.iso_root, "powerpc-ieee1275", ["*.mod", "*.lst"]
|
|
||||||
)
|
|
||||||
|
|
||||||
def generate_grub_config(self) -> str:
|
|
||||||
"""Generate grub.cfg for PPC64EL."""
|
|
||||||
kernel_params = default_kernel_params(self.project)
|
|
||||||
|
|
||||||
result = self.grub_header()
|
|
||||||
|
|
||||||
# Main menu entry
|
|
||||||
result += f"""\
|
|
||||||
menuentry "Try or Install {self.humanproject}" {{
|
|
||||||
set gfxpayload=keep
|
|
||||||
linux /casper/vmlinux quiet {kernel_params}
|
|
||||||
initrd /casper/initrd
|
|
||||||
}}
|
|
||||||
"""
|
|
||||||
|
|
||||||
# HWE kernel option if available
|
|
||||||
result += self.hwe_menu_entry("vmlinux", kernel_params, extra_params="quiet ")
|
|
||||||
|
|
||||||
return result
|
|
||||||
@ -1,207 +0,0 @@
|
|||||||
"""RISC-V 64-bit architecture boot configuration."""
|
|
||||||
|
|
||||||
import pathlib
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
from .grub import GrubBootConfigurator, copy_grub_common_files, copy_grub_modules
|
|
||||||
|
|
||||||
|
|
||||||
def copy_unsigned_monolithic_grub(
|
|
||||||
grub_pkg_dir: pathlib.Path,
|
|
||||||
efi_suffix: str,
|
|
||||||
grub_target: str,
|
|
||||||
iso_root: pathlib.Path,
|
|
||||||
) -> None:
|
|
||||||
efi_boot_dir = iso_root.joinpath("EFI", "boot")
|
|
||||||
efi_boot_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
shutil.copy(
|
|
||||||
grub_pkg_dir.joinpath(
|
|
||||||
"usr",
|
|
||||||
"lib",
|
|
||||||
"grub",
|
|
||||||
grub_target,
|
|
||||||
"monolithic",
|
|
||||||
f"gcd{efi_suffix}.efi",
|
|
||||||
),
|
|
||||||
efi_boot_dir.joinpath(f"boot{efi_suffix}.efi"),
|
|
||||||
)
|
|
||||||
|
|
||||||
copy_grub_modules(grub_pkg_dir, iso_root, grub_target, ["*.mod", "*.lst"])
|
|
||||||
|
|
||||||
|
|
||||||
class RISCV64BootConfigurator(GrubBootConfigurator):
|
|
||||||
"""Boot setup for RISC-V 64-bit architecture."""
|
|
||||||
|
|
||||||
def mkisofs_opts(self) -> list[str | pathlib.Path]:
|
|
||||||
"""Return mkisofs options for RISC-V64."""
|
|
||||||
efi_img = self.scratch.joinpath("efi.img")
|
|
||||||
|
|
||||||
return [
|
|
||||||
"-joliet",
|
|
||||||
"on",
|
|
||||||
"-compliance",
|
|
||||||
"joliet_long_names",
|
|
||||||
"--append_partition",
|
|
||||||
"2",
|
|
||||||
"0xef",
|
|
||||||
efi_img,
|
|
||||||
"-boot_image",
|
|
||||||
"any",
|
|
||||||
"partition_offset=10240",
|
|
||||||
"-boot_image",
|
|
||||||
"any",
|
|
||||||
"partition_cyl_align=all",
|
|
||||||
"-boot_image",
|
|
||||||
"any",
|
|
||||||
"efi_path=--interval:appended_partition_2:all::",
|
|
||||||
"-boot_image",
|
|
||||||
"any",
|
|
||||||
"appended_part_as=gpt",
|
|
||||||
"-boot_image",
|
|
||||||
"any",
|
|
||||||
"cat_path=/boot/boot.cat",
|
|
||||||
"-fs",
|
|
||||||
"64m",
|
|
||||||
]
|
|
||||||
|
|
||||||
def extract_files(self) -> None:
|
|
||||||
"""Download and extract bootloader packages for RISC-V64."""
|
|
||||||
self.logger.log("extracting RISC-V64 boot files")
|
|
||||||
u_boot_dir = self.scratch.joinpath("u-boot-sifive")
|
|
||||||
|
|
||||||
grub_pkg_dir = self.scratch.joinpath("grub-pkg")
|
|
||||||
|
|
||||||
# Download and extract bootloader packages
|
|
||||||
self.download_and_extract_package("grub2-common", grub_pkg_dir)
|
|
||||||
self.download_and_extract_package("grub-efi-riscv64-bin", grub_pkg_dir)
|
|
||||||
self.download_and_extract_package("grub-efi-riscv64-unsigned", grub_pkg_dir)
|
|
||||||
self.download_and_extract_package("u-boot-sifive", u_boot_dir)
|
|
||||||
|
|
||||||
# Add GRUB to tree
|
|
||||||
copy_grub_common_files(grub_pkg_dir, self.iso_root)
|
|
||||||
|
|
||||||
copy_unsigned_monolithic_grub(
|
|
||||||
grub_pkg_dir, "riscv64", "riscv64-efi", self.iso_root
|
|
||||||
)
|
|
||||||
|
|
||||||
# Extract DTBs to tree
|
|
||||||
self.logger.log("extracting device tree files")
|
|
||||||
kernel_layer = self.scratch.joinpath("kernel-layer")
|
|
||||||
squashfs_path = self.iso_root.joinpath(
|
|
||||||
"casper", "ubuntu-server-minimal.squashfs"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Extract device tree firmware from squashfs
|
|
||||||
self.logger.run(
|
|
||||||
[
|
|
||||||
"unsquashfs",
|
|
||||||
"-no-xattrs",
|
|
||||||
"-d",
|
|
||||||
kernel_layer,
|
|
||||||
squashfs_path,
|
|
||||||
"usr/lib/firmware",
|
|
||||||
],
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Copy DTBs if they exist
|
|
||||||
dtb_dir = self.iso_root.joinpath("dtb")
|
|
||||||
dtb_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
firmware_dir = kernel_layer.joinpath("usr", "lib", "firmware")
|
|
||||||
|
|
||||||
for dtb_file in firmware_dir.glob("*/device-tree/*"):
|
|
||||||
if dtb_file.is_file():
|
|
||||||
shutil.copy(dtb_file, dtb_dir)
|
|
||||||
|
|
||||||
# Create ESP image with GRUB and dtbs
|
|
||||||
efi_img = self.scratch.joinpath("efi.img")
|
|
||||||
self.logger.run(
|
|
||||||
["mkfs.msdos", "-n", "ESP", "-C", "-v", efi_img, "32768"], check=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add EFI files to ESP
|
|
||||||
efi_dir = self.iso_root.joinpath("EFI")
|
|
||||||
self.logger.run(["mcopy", "-s", "-i", efi_img, efi_dir, "::/."], check=True)
|
|
||||||
|
|
||||||
# Add DTBs to ESP
|
|
||||||
self.logger.run(["mcopy", "-s", "-i", efi_img, dtb_dir, "::/."], check=True)
|
|
||||||
|
|
||||||
def generate_grub_config(self) -> str:
|
|
||||||
"""Generate grub.cfg for RISC-V64."""
|
|
||||||
result = self.grub_header(include_loadfont=False)
|
|
||||||
|
|
||||||
# Main menu entry
|
|
||||||
result += f"""\
|
|
||||||
menuentry "Try or Install {self.humanproject}" {{
|
|
||||||
set gfxpayload=keep
|
|
||||||
linux /casper/vmlinux efi=debug sysctl.kernel.watchdog_thresh=60 ---
|
|
||||||
initrd /casper/initrd
|
|
||||||
}}
|
|
||||||
"""
|
|
||||||
|
|
||||||
# HWE kernel option if available
|
|
||||||
result += self.hwe_menu_entry(
|
|
||||||
"vmlinux",
|
|
||||||
"---",
|
|
||||||
extra_params="efi=debug sysctl.kernel.watchdog_thresh=60 ",
|
|
||||||
)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def post_process_iso(self, iso_path: pathlib.Path) -> None:
|
|
||||||
"""Add GPT partitions with U-Boot for SiFive Unmatched board.
|
|
||||||
|
|
||||||
The SiFive Unmatched board needs a GPT table containing U-Boot in
|
|
||||||
order to boot. U-Boot does not currently support booting from a CD,
|
|
||||||
so the GPT table also contains an entry pointing to the ESP so that
|
|
||||||
U-Boot can find it.
|
|
||||||
"""
|
|
||||||
u_boot_dir = self.scratch.joinpath(
|
|
||||||
"u-boot-sifive", "usr", "lib", "u-boot", "sifive_unmatched"
|
|
||||||
)
|
|
||||||
self.logger.run(
|
|
||||||
[
|
|
||||||
"sgdisk",
|
|
||||||
iso_path,
|
|
||||||
"--set-alignment=2",
|
|
||||||
"-d",
|
|
||||||
"1",
|
|
||||||
"-n",
|
|
||||||
"1:2082:10273",
|
|
||||||
"-c",
|
|
||||||
"1:loader2",
|
|
||||||
"-t",
|
|
||||||
"1:2E54B353-1271-4842-806F-E436D6AF6985",
|
|
||||||
"-n",
|
|
||||||
"3:10274:12321",
|
|
||||||
"-c",
|
|
||||||
"3:loader1",
|
|
||||||
"-t",
|
|
||||||
"3:5B193300-FC78-40CD-8002-E86C45580B47",
|
|
||||||
"-c",
|
|
||||||
"2:ESP",
|
|
||||||
"-r=2:3",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
self.logger.run(
|
|
||||||
[
|
|
||||||
"dd",
|
|
||||||
f"if={u_boot_dir / 'u-boot.itb'}",
|
|
||||||
f"of={iso_path}",
|
|
||||||
"bs=512",
|
|
||||||
"seek=2082",
|
|
||||||
"conv=notrunc",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
self.logger.run(
|
|
||||||
[
|
|
||||||
"dd",
|
|
||||||
f"if={u_boot_dir / 'u-boot-spl.bin'}",
|
|
||||||
f"of={iso_path}",
|
|
||||||
"bs=512",
|
|
||||||
"seek=10274",
|
|
||||||
"conv=notrunc",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
@ -1,206 +0,0 @@
|
|||||||
"""IBM S/390 architecture boot configuration."""
|
|
||||||
|
|
||||||
import pathlib
|
|
||||||
import shutil
|
|
||||||
import struct
|
|
||||||
|
|
||||||
from .base import BaseBootConfigurator
|
|
||||||
|
|
||||||
|
|
||||||
README_dot_boot = """\
|
|
||||||
About the S/390 installation CD
|
|
||||||
===============================
|
|
||||||
|
|
||||||
It is possible to "boot" the installation system off this CD using
|
|
||||||
the files provided in the /boot directory.
|
|
||||||
|
|
||||||
Although you can boot the installer from this CD, the installation
|
|
||||||
itself is *not* actually done from the CD. Once the initrd is loaded,
|
|
||||||
the installer will ask you to configure your network connection and
|
|
||||||
uses the network-console component to allow you to continue the
|
|
||||||
installation over SSH. The rest of the installation is done over the
|
|
||||||
network: all installer components and Debian packages are retrieved
|
|
||||||
from a mirror.
|
|
||||||
|
|
||||||
Instead of SSH, one can also use the ASCII terminal available in HMC.
|
|
||||||
|
|
||||||
Exporting full .iso contents (including the hidden .disk directory)
|
|
||||||
allows one to use the result as a valid mirror for installation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
ubuntu_dot_exec = """\
|
|
||||||
/* REXX EXEC TO IPL Ubuntu for */
|
|
||||||
/* z Systems FROM THE VM READER. */
|
|
||||||
/* */
|
|
||||||
'CP CLOSE RDR'
|
|
||||||
'PURGE RDR ALL'
|
|
||||||
'SPOOL PUNCH * RDR'
|
|
||||||
'PUNCH KERNEL UBUNTU * (NOHEADER'
|
|
||||||
'PUNCH PARMFILE UBUNTU * (NOHEADER'
|
|
||||||
'PUNCH INITRD UBUNTU * (NOHEADER'
|
|
||||||
'CHANGE RDR ALL KEEP NOHOLD'
|
|
||||||
'CP IPL 000C CLEAR'
|
|
||||||
"""
|
|
||||||
|
|
||||||
ubuntu_dot_ins = """\
|
|
||||||
* Ubuntu for IBM Z (default kernel)
|
|
||||||
kernel.ubuntu 0x00000000
|
|
||||||
initrd.off 0x0001040c
|
|
||||||
initrd.siz 0x00010414
|
|
||||||
parmfile.ubuntu 0x00010480
|
|
||||||
initrd.ubuntu 0x01000000
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def gen_s390_cd_kernel(
|
|
||||||
kernel: pathlib.Path, initrd: pathlib.Path, cmdline: str, outfile: pathlib.Path
|
|
||||||
) -> None:
|
|
||||||
"""Generate a bootable S390X CD kernel image.
|
|
||||||
|
|
||||||
This is a Python translation of gen-s390-cd-kernel.pl from debian-cd.
|
|
||||||
It creates a bootable image for S/390 architecture by combining kernel,
|
|
||||||
initrd, and boot parameters in a specific format.
|
|
||||||
"""
|
|
||||||
# Calculate sizes
|
|
||||||
initrd_size = initrd.stat().st_size
|
|
||||||
|
|
||||||
# The initrd is placed at a fixed offset of 16 MiB
|
|
||||||
initrd_offset = 0x1000000
|
|
||||||
|
|
||||||
# Calculate total boot image size (rounded up to 4K blocks)
|
|
||||||
boot_size = ((initrd_offset + initrd_size) >> 12) + 1
|
|
||||||
boot_size = boot_size << 12
|
|
||||||
|
|
||||||
# Validate cmdline length (max 896 bytes)
|
|
||||||
if len(cmdline) >= 896:
|
|
||||||
raise ValueError(f"Kernel commandline too long ({len(cmdline)} bytes)")
|
|
||||||
|
|
||||||
# Create output file and fill with zeros
|
|
||||||
with outfile.open("wb") as out_fh:
|
|
||||||
# Fill entire file with zeros
|
|
||||||
out_fh.write(b"\x00" * boot_size)
|
|
||||||
|
|
||||||
# Copy kernel to offset 0
|
|
||||||
out_fh.seek(0)
|
|
||||||
with kernel.open("rb") as kernel_fh:
|
|
||||||
out_fh.write(kernel_fh.read())
|
|
||||||
|
|
||||||
# Copy initrd to offset 0x1000000 (16 MiB)
|
|
||||||
out_fh.seek(initrd_offset)
|
|
||||||
with initrd.open("rb") as initrd_fh:
|
|
||||||
out_fh.write(initrd_fh.read())
|
|
||||||
|
|
||||||
# Write boot loader control value at offset 4
|
|
||||||
# This tells the S/390 boot loader where to find the kernel
|
|
||||||
out_fh.seek(4)
|
|
||||||
out_fh.write(struct.pack("!I", 0x80010000))
|
|
||||||
|
|
||||||
# Write kernel command line at offset 0x10480
|
|
||||||
out_fh.seek(0x10480)
|
|
||||||
out_fh.write(cmdline.encode("utf-8"))
|
|
||||||
|
|
||||||
# Write initrd parameters
|
|
||||||
# Initrd offset at 0x1040C
|
|
||||||
out_fh.seek(0x1040C)
|
|
||||||
out_fh.write(struct.pack("!I", initrd_offset))
|
|
||||||
|
|
||||||
# Initrd size at 0x10414
|
|
||||||
out_fh.seek(0x10414)
|
|
||||||
out_fh.write(struct.pack("!I", initrd_size))
|
|
||||||
|
|
||||||
|
|
||||||
class S390XBootConfigurator(BaseBootConfigurator):
|
|
||||||
"""Boot setup for IBM S/390 architecture."""
|
|
||||||
|
|
||||||
def mkisofs_opts(self) -> list[str | pathlib.Path]:
|
|
||||||
"""Return mkisofs options for S390X."""
|
|
||||||
return [
|
|
||||||
"-J",
|
|
||||||
"-no-emul-boot",
|
|
||||||
"-b",
|
|
||||||
"boot/ubuntu.ikr",
|
|
||||||
]
|
|
||||||
|
|
||||||
def extract_files(self) -> None:
|
|
||||||
"""Set up boot files for S390X."""
|
|
||||||
self.logger.log("extracting S390X boot files")
|
|
||||||
boot_dir = self.iso_root.joinpath("boot")
|
|
||||||
boot_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
# Copy static .ins & exec scripts, docs from data directory
|
|
||||||
self.iso_root.joinpath("README.boot").write_text(README_dot_boot)
|
|
||||||
boot_dir.joinpath("ubuntu.exec").write_text(ubuntu_dot_exec)
|
|
||||||
boot_dir.joinpath("ubuntu.ins").write_text(ubuntu_dot_ins)
|
|
||||||
|
|
||||||
# Move kernel image to the name used in .ins & exec scripts
|
|
||||||
kernel_src = self.iso_root.joinpath("casper", "vmlinuz")
|
|
||||||
kernel_dst = boot_dir.joinpath("kernel.ubuntu")
|
|
||||||
kernel_src.replace(kernel_dst)
|
|
||||||
|
|
||||||
# Move initrd to the name used in .ins & exec scripts
|
|
||||||
initrd_src = self.iso_root.joinpath("casper", "initrd")
|
|
||||||
initrd_dst = boot_dir.joinpath("initrd.ubuntu")
|
|
||||||
initrd_src.replace(initrd_dst)
|
|
||||||
|
|
||||||
# Compute initrd offset & size, store in files used by .ins & exec scripts
|
|
||||||
# Offset is always 0x1000000 (16 MiB)
|
|
||||||
initrd_offset_file = boot_dir.joinpath("initrd.off")
|
|
||||||
with initrd_offset_file.open("wb") as f:
|
|
||||||
f.write(struct.pack("!I", 0x1000000))
|
|
||||||
|
|
||||||
# Size is the actual size of the initrd
|
|
||||||
initrd_size = initrd_dst.stat().st_size
|
|
||||||
initrd_size_file = boot_dir.joinpath("initrd.siz")
|
|
||||||
with initrd_size_file.open("wb") as f:
|
|
||||||
f.write(struct.pack("!I", initrd_size))
|
|
||||||
|
|
||||||
# Compute cmdline, store in parmfile used by .ins & exec scripts
|
|
||||||
parmfile = boot_dir.joinpath("parmfile.ubuntu")
|
|
||||||
with parmfile.open("w") as f:
|
|
||||||
f.write(" --- ")
|
|
||||||
|
|
||||||
# Generate secondary top-level ubuntu.ins file
|
|
||||||
# This transforms lines not starting with * by prepending "boot/"
|
|
||||||
ubuntu_ins_src = boot_dir.joinpath("ubuntu.ins")
|
|
||||||
ubuntu_ins_dst = self.iso_root.joinpath("ubuntu.ins")
|
|
||||||
if ubuntu_ins_src.exists():
|
|
||||||
self.logger.run(
|
|
||||||
["sed", "-e", "s,^[^*],boot/&,g", ubuntu_ins_src],
|
|
||||||
stdout=ubuntu_ins_dst.open("w"),
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate QEMU-KVM boot image using gen_s390_cd_kernel
|
|
||||||
cmdline = parmfile.read_text().strip()
|
|
||||||
ikr_file = boot_dir.joinpath("ubuntu.ikr")
|
|
||||||
gen_s390_cd_kernel(kernel_dst, initrd_dst, cmdline, ikr_file)
|
|
||||||
|
|
||||||
# Extract bootloader signing certificate
|
|
||||||
installed_pem = pathlib.Path("/usr/lib/s390-tools/stage3.pem")
|
|
||||||
squashfs_root = self.iso_root.joinpath("squashfs-root")
|
|
||||||
squashfs_path = self.iso_root.joinpath(
|
|
||||||
"casper", "ubuntu-server-minimal.squashfs"
|
|
||||||
)
|
|
||||||
|
|
||||||
if squashfs_path.exists():
|
|
||||||
self.logger.run(
|
|
||||||
[
|
|
||||||
"unsquashfs",
|
|
||||||
"-no-xattrs",
|
|
||||||
"-i",
|
|
||||||
"-d",
|
|
||||||
squashfs_root,
|
|
||||||
squashfs_path,
|
|
||||||
installed_pem,
|
|
||||||
],
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Move certificate to iso root
|
|
||||||
cert_src = squashfs_root.joinpath(str(installed_pem).lstrip("/"))
|
|
||||||
cert_dst = self.iso_root.joinpath("ubuntu.pem")
|
|
||||||
if cert_src.exists():
|
|
||||||
cert_src.replace(cert_dst)
|
|
||||||
|
|
||||||
# Clean up squashfs extraction
|
|
||||||
shutil.rmtree(squashfs_root)
|
|
||||||
@ -1,168 +0,0 @@
|
|||||||
"""UEFI boot configuration for AMD64 and ARM64 architectures."""
|
|
||||||
|
|
||||||
import pathlib
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
from ..builder import Logger
|
|
||||||
from .grub import copy_grub_common_files, GrubBootConfigurator
|
|
||||||
|
|
||||||
|
|
||||||
def copy_signed_shim_grub(
|
|
||||||
shim_pkg_dir: pathlib.Path,
|
|
||||||
grub_pkg_dir: pathlib.Path,
|
|
||||||
efi_suffix: str,
|
|
||||||
grub_target: str,
|
|
||||||
iso_root: pathlib.Path,
|
|
||||||
) -> None:
|
|
||||||
efi_boot_dir = iso_root.joinpath("EFI", "boot")
|
|
||||||
efi_boot_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
shutil.copy(
|
|
||||||
shim_pkg_dir.joinpath(
|
|
||||||
"usr", "lib", "shim", f"shim{efi_suffix}.efi.signed.latest"
|
|
||||||
),
|
|
||||||
efi_boot_dir.joinpath(f"boot{efi_suffix}.efi"),
|
|
||||||
)
|
|
||||||
shutil.copy(
|
|
||||||
shim_pkg_dir.joinpath("usr", "lib", "shim", f"mm{efi_suffix}.efi"),
|
|
||||||
efi_boot_dir.joinpath(f"mm{efi_suffix}.efi"),
|
|
||||||
)
|
|
||||||
shutil.copy(
|
|
||||||
grub_pkg_dir.joinpath(
|
|
||||||
"usr",
|
|
||||||
"lib",
|
|
||||||
"grub",
|
|
||||||
f"{grub_target}-efi-signed",
|
|
||||||
f"gcd{efi_suffix}.efi.signed",
|
|
||||||
),
|
|
||||||
efi_boot_dir.joinpath(f"grub{efi_suffix}.efi"),
|
|
||||||
)
|
|
||||||
|
|
||||||
grub_boot_dir = iso_root.joinpath("boot", "grub", f"{grub_target}-efi")
|
|
||||||
grub_boot_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
src_grub_dir = grub_pkg_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)
|
|
||||||
|
|
||||||
|
|
||||||
def create_eltorito_esp_image(
|
|
||||||
logger: Logger, iso_root: pathlib.Path, target_file: pathlib.Path
|
|
||||||
) -> None:
|
|
||||||
logger.log("creating El Torito ESP image")
|
|
||||||
efi_dir = iso_root.joinpath("EFI")
|
|
||||||
|
|
||||||
# Calculate size: du -s --apparent-size --block-size=1024 + 1024
|
|
||||||
result = logger.run(
|
|
||||||
["du", "-s", "--apparent-size", "--block-size=1024", efi_dir],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
size_kb = int(result.stdout.split()[0]) + 1024
|
|
||||||
|
|
||||||
# Create filesystem: mkfs.msdos -n ESP -C -v
|
|
||||||
logger.run(
|
|
||||||
["mkfs.msdos", "-n", "ESP", "-C", "-v", target_file, str(size_kb)],
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Copy files: mcopy -s -i target_file EFI ::/.
|
|
||||||
logger.run(["mcopy", "-s", "-i", target_file, efi_dir, "::/."], check=True)
|
|
||||||
|
|
||||||
|
|
||||||
class UEFIBootConfigurator(GrubBootConfigurator):
|
|
||||||
"""Base class for UEFI-based architectures (AMD64, ARM64).
|
|
||||||
|
|
||||||
Subclasses should set:
|
|
||||||
- efi_suffix: EFI binary suffix (e.g., "x64", "aa64")
|
|
||||||
- grub_target: GRUB target name (e.g., "x86_64", "arm64")
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Subclasses must override these
|
|
||||||
efi_suffix: str = ""
|
|
||||||
grub_target: str = ""
|
|
||||||
arch: str = ""
|
|
||||||
|
|
||||||
def get_uefi_grub_packages(self) -> list[str]:
|
|
||||||
"""Return list of UEFI GRUB packages to download."""
|
|
||||||
return [
|
|
||||||
"grub2-common",
|
|
||||||
f"grub-efi-{self.arch}-bin",
|
|
||||||
f"grub-efi-{self.arch}-signed",
|
|
||||||
]
|
|
||||||
|
|
||||||
def extract_uefi_files(self) -> None:
|
|
||||||
"""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
|
|
||||||
self.download_and_extract_package("shim-signed", shim_pkg_dir)
|
|
||||||
for pkg in self.get_uefi_grub_packages():
|
|
||||||
self.download_and_extract_package(pkg, grub_pkg_dir)
|
|
||||||
|
|
||||||
# Add common files for GRUB to tree
|
|
||||||
copy_grub_common_files(grub_pkg_dir, self.iso_root)
|
|
||||||
|
|
||||||
# Add EFI GRUB to tree
|
|
||||||
copy_signed_shim_grub(
|
|
||||||
shim_pkg_dir,
|
|
||||||
grub_pkg_dir,
|
|
||||||
self.efi_suffix,
|
|
||||||
self.grub_target,
|
|
||||||
self.iso_root,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create ESP image for El-Torito catalog and hybrid boot
|
|
||||||
create_eltorito_esp_image(
|
|
||||||
self.logger, self.iso_root, self.scratch.joinpath("cd-boot-efi.img")
|
|
||||||
)
|
|
||||||
|
|
||||||
def uefi_menu_entries(self) -> str:
|
|
||||||
"""Return UEFI firmware menu entries."""
|
|
||||||
return """\
|
|
||||||
menuentry 'Boot from next volume' {
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
menuentry 'UEFI Firmware Settings' {
|
|
||||||
fwsetup
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_uefi_mkisofs_opts(self) -> list[str | pathlib.Path]:
|
|
||||||
"""Return common UEFI mkisofs options."""
|
|
||||||
# To make our ESP / El-Torito image compliant with MBR/GPT standards,
|
|
||||||
# we first append it as a partition and then point the El Torito at
|
|
||||||
# it. See https://lists.debian.org/debian-cd/2019/07/msg00007.html
|
|
||||||
opts: list[str | pathlib.Path] = [
|
|
||||||
"-append_partition",
|
|
||||||
"2",
|
|
||||||
"0xef",
|
|
||||||
self.scratch.joinpath("cd-boot-efi.img"),
|
|
||||||
"-appended_part_as_gpt",
|
|
||||||
]
|
|
||||||
|
|
||||||
# Some BIOSes ignore removable disks with no partitions marked bootable
|
|
||||||
# in the MBR. Make sure our protective MBR partition is marked bootable.
|
|
||||||
opts.append("--mbr-force-bootable")
|
|
||||||
|
|
||||||
# Start a new entry in the el torito boot catalog
|
|
||||||
opts.append("-eltorito-alt-boot")
|
|
||||||
|
|
||||||
# Specify where the el torito UEFI boot image "name". We use a special
|
|
||||||
# syntax available in latest xorriso to point at our newly-created
|
|
||||||
# partition.
|
|
||||||
opts.extend(["-e", "--interval:appended_partition_2:all::"])
|
|
||||||
|
|
||||||
# Whether to emulate a floppy or not is a per-boot-catalog-entry
|
|
||||||
# thing, so we need to say it again.
|
|
||||||
opts.append("-no-emul-boot")
|
|
||||||
|
|
||||||
# Create a partition table entry that covers the iso9660 filesystem
|
|
||||||
opts.extend(["-partition_offset", "16"])
|
|
||||||
|
|
||||||
return opts
|
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import contextlib
|
import contextlib
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import shlex
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
@ -7,7 +8,6 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from isobuilder.apt_state import AptStateManager
|
from isobuilder.apt_state import AptStateManager
|
||||||
from isobuilder.boot import make_boot_configurator_for_arch
|
|
||||||
from isobuilder.gpg_key import EphemeralGPGKey
|
from isobuilder.gpg_key import EphemeralGPGKey
|
||||||
from isobuilder.pool_builder import PoolBuilder
|
from isobuilder.pool_builder import PoolBuilder
|
||||||
|
|
||||||
@ -114,34 +114,27 @@ class ISOBuilder:
|
|||||||
self.workdir = workdir
|
self.workdir = workdir
|
||||||
self.logger = Logger()
|
self.logger = Logger()
|
||||||
self.iso_root = workdir.joinpath("iso-root")
|
self.iso_root = workdir.joinpath("iso-root")
|
||||||
self._config: dict | None = None
|
self._series = self._arch = self._gpg_key = self._apt_state = None
|
||||||
self._gpg_key = self._apt_state = None
|
|
||||||
|
|
||||||
# UTILITY STUFF
|
# UTILITY STUFF
|
||||||
|
|
||||||
def _read_config(self):
|
def _read_config(self):
|
||||||
with self.workdir.joinpath("config.json").open() as fp:
|
with self.workdir.joinpath("config.json").open() as fp:
|
||||||
self._config = json.load(fp)
|
data = json.load(fp)
|
||||||
|
self._series = data["series"]
|
||||||
@property
|
self._arch = data["arch"]
|
||||||
def config(self):
|
|
||||||
if self._config is None:
|
|
||||||
self._read_config()
|
|
||||||
return self._config
|
|
||||||
|
|
||||||
def save_config(self):
|
|
||||||
with self.workdir.joinpath("config.json").open("w") as fp:
|
|
||||||
json.dump(self._config, fp)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def arch(self):
|
def arch(self):
|
||||||
return self.config["arch"]
|
if self._arch is None:
|
||||||
|
self._read_config()
|
||||||
|
return self._arch
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def series(self):
|
def series(self):
|
||||||
if self._config is None:
|
if self._series is None:
|
||||||
self._read_config()
|
self._read_config()
|
||||||
return self._config["series"]
|
return self._series
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def gpg_key(self):
|
def gpg_key(self):
|
||||||
@ -169,8 +162,8 @@ class ISOBuilder:
|
|||||||
dot_disk.mkdir()
|
dot_disk.mkdir()
|
||||||
|
|
||||||
self.logger.log("saving config")
|
self.logger.log("saving config")
|
||||||
self._config = {"arch": arch, "series": series}
|
with self.workdir.joinpath("config.json").open("w") as fp:
|
||||||
self.save_config()
|
json.dump({"arch": arch, "series": series}, fp)
|
||||||
|
|
||||||
self.logger.log("populating .disk")
|
self.logger.log("populating .disk")
|
||||||
dot_disk.joinpath("base_installable").touch()
|
dot_disk.joinpath("base_installable").touch()
|
||||||
@ -226,8 +219,9 @@ class ISOBuilder:
|
|||||||
# can verify it's booting from the right media.
|
# can verify it's booting from the right media.
|
||||||
with self.logger.logged("extracting casper uuids"):
|
with self.logger.logged("extracting casper uuids"):
|
||||||
casper_dir = self.iso_root.joinpath("casper")
|
casper_dir = self.iso_root.joinpath("casper")
|
||||||
|
prefix = "filesystem.initrd-"
|
||||||
dot_disk = self.iso_root.joinpath(".disk")
|
dot_disk = self.iso_root.joinpath(".disk")
|
||||||
for initrd in casper_dir.glob("*initrd"):
|
for initrd in casper_dir.glob(f"{prefix}*"):
|
||||||
initrddir = self.workdir.joinpath("initrd")
|
initrddir = self.workdir.joinpath("initrd")
|
||||||
with self.logger.logged(
|
with self.logger.logged(
|
||||||
f"unpacking {initrd.name} ...", done_msg="... done"
|
f"unpacking {initrd.name} ...", done_msg="... done"
|
||||||
@ -237,7 +231,9 @@ class ISOBuilder:
|
|||||||
# - Platforms with early firmware: subdirs like "main/" or "early/"
|
# - Platforms with early firmware: subdirs like "main/" or "early/"
|
||||||
# containing conf/uuid.conf
|
# containing conf/uuid.conf
|
||||||
# - Other platforms: conf/uuid.conf directly in the root
|
# - Other platforms: conf/uuid.conf directly in the root
|
||||||
# Try to find uuid.conf in both locations.
|
# Try to find uuid.conf in both locations. The [uuid_conf] = confs
|
||||||
|
# unpacking asserts exactly one match; multiple matches would
|
||||||
|
# indicate an unexpected initrd structure.
|
||||||
confs = list(initrddir.glob("*/conf/uuid.conf"))
|
confs = list(initrddir.glob("*/conf/uuid.conf"))
|
||||||
if confs:
|
if confs:
|
||||||
[uuid_conf] = confs
|
[uuid_conf] = confs
|
||||||
@ -246,31 +242,33 @@ class ISOBuilder:
|
|||||||
else:
|
else:
|
||||||
raise Exception("uuid.conf not found")
|
raise Exception("uuid.conf not found")
|
||||||
self.logger.log(f"found {uuid_conf.relative_to(initrddir)}")
|
self.logger.log(f"found {uuid_conf.relative_to(initrddir)}")
|
||||||
if initrd.name == "initrd":
|
uuid_conf.rename(
|
||||||
suffix = "generic"
|
dot_disk.joinpath("casper-uuid-" + initrd.name[len(prefix) :])
|
||||||
elif initrd.name == "hwe-initrd":
|
)
|
||||||
suffix = "generic-hwe"
|
|
||||||
else:
|
|
||||||
raise Exception(f"unexpected initrd name {initrd.name}")
|
|
||||||
uuid_conf.rename(dot_disk.joinpath(f"casper-uuid-{suffix}"))
|
|
||||||
shutil.rmtree(initrddir)
|
shutil.rmtree(initrddir)
|
||||||
|
|
||||||
def add_live_filesystem(self, artifact_prefix: pathlib.Path):
|
def add_live_filesystem(self, artifact_prefix: pathlib.Path):
|
||||||
|
# Link build artifacts into the ISO's casper directory. We use hardlinks
|
||||||
|
# (not copies) for filesystem efficiency - they reference the same inode.
|
||||||
|
#
|
||||||
|
# Artifacts come from the layered build with names like "for-iso.base.squashfs"
|
||||||
|
# and need to be renamed for casper. The prefix is stripped, so:
|
||||||
|
# for-iso.base.squashfs -> base.squashfs
|
||||||
|
# for-iso.kernel-generic -> filesystem.kernel-generic
|
||||||
|
#
|
||||||
|
# Kernel and initrd get the extra "filesystem." prefix because debian-cd
|
||||||
|
# expects names like filesystem.kernel-* and filesystem.initrd-*.
|
||||||
casper_dir = self.iso_root.joinpath("casper")
|
casper_dir = self.iso_root.joinpath("casper")
|
||||||
artifact_dir = artifact_prefix.parent
|
artifact_dir = artifact_prefix.parent
|
||||||
filename_prefix = artifact_prefix.name
|
filename_prefix = artifact_prefix.name
|
||||||
|
|
||||||
def link(src: pathlib.Path, target_name: str):
|
def link(src, target_name):
|
||||||
target = casper_dir.joinpath(target_name)
|
target = casper_dir.joinpath(target_name)
|
||||||
self.logger.log(
|
self.logger.log(
|
||||||
f"creating link from $ISOROOT/casper/{target_name} to $src/{src.name}"
|
f"creating link from $ISOROOT/casper/{target_name} to $src/{src.name}"
|
||||||
)
|
)
|
||||||
target.hardlink_to(src)
|
target.hardlink_to(src)
|
||||||
|
|
||||||
kernel_name = "vmlinuz"
|
|
||||||
if self.arch in ("ppc64el", "riscv64"):
|
|
||||||
kernel_name = "vmlinux"
|
|
||||||
|
|
||||||
with self.logger.logged(
|
with self.logger.logged(
|
||||||
f"linking artifacts from {casper_dir} to {artifact_dir}"
|
f"linking artifacts from {casper_dir} to {artifact_dir}"
|
||||||
):
|
):
|
||||||
@ -278,41 +276,64 @@ class ISOBuilder:
|
|||||||
for path in artifact_dir.glob(f"{filename_prefix}*.{ext}"):
|
for path in artifact_dir.glob(f"{filename_prefix}*.{ext}"):
|
||||||
newname = path.name[len(filename_prefix) :]
|
newname = path.name[len(filename_prefix) :]
|
||||||
link(path, newname)
|
link(path, newname)
|
||||||
|
for item in "kernel", "initrd":
|
||||||
for kernel_path in artifact_dir.glob(f"{filename_prefix}kernel*"):
|
for path in artifact_dir.glob(f"{filename_prefix}{item}-*"):
|
||||||
suffix = kernel_path.name[len(filename_prefix) + len("kernel") :]
|
newname = "filesystem." + path.name[len(filename_prefix) :]
|
||||||
prefix = "hwe-" if suffix.endswith("-hwe") else ""
|
link(path, newname)
|
||||||
link(
|
|
||||||
artifact_dir.joinpath(f"{filename_prefix}kernel{suffix}"),
|
|
||||||
f"{prefix}{kernel_name}",
|
|
||||||
)
|
|
||||||
link(
|
|
||||||
artifact_dir.joinpath(f"{filename_prefix}initrd{suffix}"),
|
|
||||||
f"{prefix}initrd",
|
|
||||||
)
|
|
||||||
self._extract_casper_uuids()
|
self._extract_casper_uuids()
|
||||||
|
|
||||||
def make_bootable(self, project: str, capproject: str, subarch: str):
|
def make_bootable(self, project: str, capproject: str, subarch: str):
|
||||||
configurator = make_boot_configurator_for_arch(
|
# debian-cd is Ubuntu's CD/ISO image build system. It contains
|
||||||
self.arch,
|
# architecture and series-specific boot configuration scripts that set up
|
||||||
self.logger,
|
# GRUB, syslinux, EFI boot, etc. The tools/boot/$series/boot-$arch script
|
||||||
self.apt_state,
|
# knows how to make an ISO bootable for each architecture.
|
||||||
self.workdir,
|
#
|
||||||
self.iso_root,
|
# TODO: The boot configuration logic should eventually be ported directly
|
||||||
)
|
# into isobuilder to avoid this external dependency and git clone.
|
||||||
configurator.make_bootable(
|
debian_cd_dir = self.workdir.joinpath("debian-cd")
|
||||||
project,
|
with self.logger.logged("cloning debian-cd"):
|
||||||
capproject,
|
self.logger.run(
|
||||||
subarch,
|
[
|
||||||
self.iso_root.joinpath("casper/hwe-initrd").exists(),
|
"git",
|
||||||
|
"clone",
|
||||||
|
"--depth=1",
|
||||||
|
"https://git.launchpad.net/~ubuntu-cdimage/debian-cd/+git/ubuntu",
|
||||||
|
debian_cd_dir,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
# Override apt-selection to use our ISO's apt configuration instead of
|
||||||
|
# debian-cd's default. This ensures the boot scripts get packages from
|
||||||
|
# the correct repository when installing boot packages.
|
||||||
|
apt_selection = debian_cd_dir.joinpath("tools/apt-selection")
|
||||||
|
with self.logger.logged("overwriting apt-selection"):
|
||||||
|
apt_selection.write_text(
|
||||||
|
"#!/bin/sh\n" f"APT_CONFIG={self.apt_state.apt_conf_path} apt-get $@\n"
|
||||||
|
)
|
||||||
|
env = dict(
|
||||||
|
os.environ,
|
||||||
|
BASEDIR=str(debian_cd_dir),
|
||||||
|
DIST=self.series,
|
||||||
|
PROJECT=project,
|
||||||
|
CAPPROJECT=capproject,
|
||||||
|
SUBARCH=subarch,
|
||||||
)
|
)
|
||||||
|
tool_name = f"tools/boot/{self.series}/boot-{self.arch}"
|
||||||
|
with self.logger.logged(f"running {tool_name} ...", done_msg="... done"):
|
||||||
|
self.logger.run(
|
||||||
|
[
|
||||||
|
debian_cd_dir.joinpath(tool_name),
|
||||||
|
"1",
|
||||||
|
self.iso_root,
|
||||||
|
],
|
||||||
|
env=env,
|
||||||
|
)
|
||||||
|
|
||||||
def checksum(self):
|
def checksum(self):
|
||||||
# Generate md5sum.txt for ISO integrity verification.
|
# Generate md5sum.txt for ISO integrity verification.
|
||||||
# - Symlinks are excluded because their targets are already checksummed
|
# - Symlinks are excluded because their targets are already checksummed
|
||||||
# - Files are sorted for deterministic, reproducible output across builds
|
# - Files are sorted for deterministic, reproducible output across builds
|
||||||
# - Paths use "./" prefix and we run md5sum from iso_root so the output
|
# - Paths use "./" prefix and we run md5sum from iso_root so the output
|
||||||
# matches what users get when they verify with "md5sum -c" from the ISO
|
# matches what casper-md5check expects.
|
||||||
all_files = []
|
all_files = []
|
||||||
for dirpath, dirnames, filenames in self.iso_root.walk():
|
for dirpath, dirnames, filenames in self.iso_root.walk():
|
||||||
filepaths = [dirpath.joinpath(filename) for filename in filenames]
|
filepaths = [dirpath.joinpath(filename) for filename in filenames]
|
||||||
@ -330,20 +351,14 @@ class ISOBuilder:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def make_iso(self, dest: pathlib.Path, volid: str | None):
|
def make_iso(self, dest: pathlib.Path, volid: str | None):
|
||||||
|
# 1.mkisofs_opts is generated by debian-cd's make_bootable step. The "1"
|
||||||
|
# refers to "pass 1" of the build (a legacy naming convention). It contains
|
||||||
|
# architecture-specific xorriso options for boot sectors, EFI images, etc.
|
||||||
|
mkisofs_opts = shlex.split(self.workdir.joinpath("1.mkisofs_opts").read_text())
|
||||||
|
self.checksum()
|
||||||
# xorriso with "-as mkisofs" runs in mkisofs compatibility mode.
|
# xorriso with "-as mkisofs" runs in mkisofs compatibility mode.
|
||||||
# -r enables Rock Ridge extensions for Unix metadata (permissions, symlinks).
|
# -r enables Rock Ridge extensions for Unix metadata (permissions, symlinks).
|
||||||
# -iso-level 3 (amd64 only) allows files >4GB which some amd64 ISOs need.
|
# -iso-level 3 (amd64 only) allows files >4GB which some amd64 ISOs need.
|
||||||
# mkisofs_opts comes from the boot configurator and contains architecture-
|
|
||||||
# specific options for boot sectors, EFI images, etc.
|
|
||||||
self.checksum()
|
|
||||||
configurator = make_boot_configurator_for_arch(
|
|
||||||
self.arch,
|
|
||||||
self.logger,
|
|
||||||
self.apt_state,
|
|
||||||
self.workdir,
|
|
||||||
self.iso_root,
|
|
||||||
)
|
|
||||||
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":
|
||||||
# For $reasons, xorriso is not run in mkisofs mode on riscv64 only.
|
# For $reasons, xorriso is not run in mkisofs mode on riscv64 only.
|
||||||
@ -365,4 +380,7 @@ class ISOBuilder:
|
|||||||
cmd.extend(mkisofs_opts + [self.iso_root, "-o", dest])
|
cmd.extend(mkisofs_opts + [self.iso_root, "-o", dest])
|
||||||
with self.logger.logged("running xorriso"):
|
with self.logger.logged("running xorriso"):
|
||||||
self.logger.run(cmd, cwd=self.workdir, check=True, limit_length=False)
|
self.logger.run(cmd, cwd=self.workdir, check=True, limit_length=False)
|
||||||
configurator.post_process_iso(dest)
|
if self.arch == "riscv64":
|
||||||
|
debian_cd_dir = self.workdir.joinpath("debian-cd")
|
||||||
|
add_riscv_gpt = debian_cd_dir.joinpath("tools/add_riscv_gpt")
|
||||||
|
self.logger.run([add_riscv_gpt, dest], cwd=self.workdir)
|
||||||
|
|||||||
@ -1,9 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
# Create kernel/initrd artifacts for isobuilder to consume.
|
|
||||||
# The standard MAKE_ISO flow in auto/build expects files named
|
|
||||||
# ${PREFIX}.kernel-${flavour} and ${PREFIX}.initrd-${flavour}.
|
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
case $ARCH in
|
case $ARCH in
|
||||||
@ -14,7 +10,68 @@ case $ARCH in
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
PREFIX="livecd.${PROJECT}"
|
. config/binary
|
||||||
|
|
||||||
cp chroot/boot/vmlinuz "${PREFIX}.kernel-generic"
|
KERNEL=chroot/boot/vmlinuz
|
||||||
cp chroot/boot/initrd.img "${PREFIX}.initrd-generic"
|
INITRD=chroot/boot/initrd.img
|
||||||
|
|
||||||
|
git clone https://git.launchpad.net/~ubuntu-cdimage/debian-cd/+git/ubuntu debian-cd
|
||||||
|
export BASEDIR=$(readlink -f debian-cd) DIST=$LB_DISTRIBUTION
|
||||||
|
|
||||||
|
cat > apt.conf <<EOF
|
||||||
|
Dir "$(pwd)/chroot";
|
||||||
|
EOF
|
||||||
|
|
||||||
|
case $ARCH in
|
||||||
|
amd64)
|
||||||
|
mkdir -p "ubuntu-mini-iso/amd64/tree/casper"
|
||||||
|
cp "$KERNEL" ubuntu-mini-iso/amd64/tree/casper/filesystem.kernel-generic
|
||||||
|
cp "$INITRD" ubuntu-mini-iso/amd64/tree/casper/filesystem.initrd-generic
|
||||||
|
APT_CONFIG_amd64=$(pwd)/apt.conf $BASEDIR/tools/boot/$LB_DISTRIBUTION/boot-amd64 1 $(readlink -f ubuntu-mini-iso/amd64/tree)
|
||||||
|
# Overwrite the grub.cfg that debian-cd generates by default
|
||||||
|
cat > ubuntu-mini-iso/amd64/tree/boot/grub/grub.cfg <<EOF
|
||||||
|
menuentry "Choose an Ubuntu version to install" {
|
||||||
|
set gfxpayload=keep
|
||||||
|
linux /casper/vmlinuz iso-chooser-menu ip=dhcp ---
|
||||||
|
initrd /casper/initrd
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
rm -f ubuntu-mini-iso/amd64/tree/boot/grub/loopback.cfg ubuntu-mini-iso/amd64/tree/boot/memtest*.bin
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
mkdir -p ubuntu-mini-iso/$ARCH/tree/.disk
|
||||||
|
|
||||||
|
touch ubuntu-mini-iso/$ARCH/tree/.disk/base_installable
|
||||||
|
|
||||||
|
tmpdir=$(mktemp -d)
|
||||||
|
unmkinitramfs $INITRD $tmpdir
|
||||||
|
if [ -e $tmpdir/*/conf/uuid.conf ]; then
|
||||||
|
uuid_conf=$tmpdir/*/conf/uuid.conf
|
||||||
|
elif [ -e "$tmpdir/conf/uuid.conf" ]; then
|
||||||
|
uuid_conf="$tmpdir/conf/uuid.conf"
|
||||||
|
else
|
||||||
|
echo "uuid.conf not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cp $uuid_conf ubuntu-mini-iso/$ARCH/tree/.disk/casper-uuid-generic
|
||||||
|
rm -fr $tmpdir
|
||||||
|
|
||||||
|
cat > ubuntu-mini-iso/$ARCH/tree/.disk/cd_type <<EOF
|
||||||
|
full_cd/single
|
||||||
|
EOF
|
||||||
|
|
||||||
|
version=$(distro-info --fullname --series=$LB_DISTRIBUTION \
|
||||||
|
| sed s'/^Ubuntu/ubuntu-mini-iso/')
|
||||||
|
|
||||||
|
cat > ubuntu-mini-iso/$ARCH/tree/.disk/info <<EOF
|
||||||
|
$version - $ARCH ($BUILDSTAMP)
|
||||||
|
EOF
|
||||||
|
|
||||||
|
dest="${PWD}/livecd.${PROJECT}.iso"
|
||||||
|
|
||||||
|
cd ubuntu-mini-iso/$ARCH
|
||||||
|
xorriso -as mkisofs $(cat 1.mkisofs_opts) tree -o $dest
|
||||||
|
cd ../..
|
||||||
|
|
||||||
|
rm -rf ubuntu-mini-iso
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# create the system seed for TPM-backed FDE in the live layer of the installer.
|
# create the system seed for TPM-backed FDE in the live layer of the installer.
|
||||||
|
|
||||||
set -eu
|
set -eux
|
||||||
|
|
||||||
case ${PASS:-} in
|
case ${PASS:-} in
|
||||||
*.live)
|
*.live)
|
||||||
@ -13,15 +13,8 @@ case ${PASS:-} in
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
. config/binary
|
. config/binary
|
||||||
. config/common
|
|
||||||
. config/functions
|
. config/functions
|
||||||
|
|
||||||
set -x
|
|
||||||
|
|
||||||
if ! echo $PASSES | grep --quiet enhanced-secureboot; then
|
|
||||||
# Only run this hook if there is going to be a layer that installs it...
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Naive conversion from YAML to JSON. This is needed because yq is in universe
|
# Naive conversion from YAML to JSON. This is needed because yq is in universe
|
||||||
# (but jq is not).
|
# (but jq is not).
|
||||||
@ -133,25 +126,8 @@ get_components()
|
|||||||
|
|
||||||
# env SNAPPY_STORE_NO_CDN=1 snap known --remote model series=16 brand-id=canonical model=ubuntu-classic-2410-amd64 > config/classic-model.model
|
# env SNAPPY_STORE_NO_CDN=1 snap known --remote model series=16 brand-id=canonical model=ubuntu-classic-2410-amd64 > config/classic-model.model
|
||||||
#
|
#
|
||||||
|
dangerous_model=/usr/share/livecd-rootfs/live-build/${PROJECT}/ubuntu-classic-amd64-dangerous.model
|
||||||
# We used to have the models included in livecd-rootfs itself, but now we pull
|
stable_model=/usr/share/livecd-rootfs/live-build/${PROJECT}/ubuntu-classic-amd64.model
|
||||||
# them from the Launchpad git mirror.
|
|
||||||
canonical_models_tree=$(mktemp -d)
|
|
||||||
git clone --depth 1 https://git.launchpad.net/canonical-models -- "${canonical_models_tree}"
|
|
||||||
|
|
||||||
cleanup_repo()
|
|
||||||
{
|
|
||||||
rm -rf -- "${canonical_models_tree}"
|
|
||||||
}
|
|
||||||
|
|
||||||
trap cleanup_repo EXIT
|
|
||||||
|
|
||||||
echo 'Checked out canonical-models revision' "$(git -C "${canonical_models_tree}" rev-parse HEAD)"
|
|
||||||
|
|
||||||
model_version=$(release_ver | sed 's/\.//')
|
|
||||||
|
|
||||||
dangerous_model="${canonical_models_tree}"/ubuntu-classic-"${model_version}"-amd64-dangerous.model
|
|
||||||
stable_model="${canonical_models_tree}"/ubuntu-classic-"${model_version}"-amd64.model
|
|
||||||
|
|
||||||
prepare_args=()
|
prepare_args=()
|
||||||
|
|
||||||
|
|||||||
109
live-build/ubuntu/ubuntu-classic-amd64-dangerous.model
Normal file
109
live-build/ubuntu/ubuntu-classic-amd64-dangerous.model
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
type: model
|
||||||
|
authority-id: canonical
|
||||||
|
series: 16
|
||||||
|
brand-id: canonical
|
||||||
|
model: ubuntu-classic-2604-amd64-dangerous
|
||||||
|
architecture: amd64
|
||||||
|
base: core24
|
||||||
|
classic: true
|
||||||
|
distribution: ubuntu
|
||||||
|
grade: dangerous
|
||||||
|
snaps:
|
||||||
|
-
|
||||||
|
default-channel: classic-26.04/edge
|
||||||
|
id: UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH
|
||||||
|
name: pc
|
||||||
|
type: gadget
|
||||||
|
-
|
||||||
|
components:
|
||||||
|
nvidia-580-uda-ko:
|
||||||
|
presence: optional
|
||||||
|
nvidia-580-uda-user:
|
||||||
|
presence: optional
|
||||||
|
default-channel: 26.04/beta
|
||||||
|
id: pYVQrBcKmBa0mZ4CCN7ExT6jH8rY1hza
|
||||||
|
name: pc-kernel
|
||||||
|
type: kernel
|
||||||
|
-
|
||||||
|
default-channel: latest/edge
|
||||||
|
id: amcUKQILKXHHTlmSa7NMdnXSx02dNeeT
|
||||||
|
name: core22
|
||||||
|
type: base
|
||||||
|
-
|
||||||
|
default-channel: latest/edge
|
||||||
|
id: dwTAh7MZZ01zyriOZErqd1JynQLiOGvM
|
||||||
|
name: core24
|
||||||
|
type: base
|
||||||
|
-
|
||||||
|
default-channel: latest/edge
|
||||||
|
id: cUqM61hRuZAJYmIS898Ux66VY61gBbZf
|
||||||
|
name: core26
|
||||||
|
type: base
|
||||||
|
-
|
||||||
|
default-channel: latest/edge
|
||||||
|
id: PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4
|
||||||
|
name: snapd
|
||||||
|
type: snapd
|
||||||
|
-
|
||||||
|
default-channel: latest/edge
|
||||||
|
id: EISPgh06mRh1vordZY9OZ34QHdd7OrdR
|
||||||
|
name: bare
|
||||||
|
type: base
|
||||||
|
-
|
||||||
|
default-channel: latest/edge
|
||||||
|
id: HyhSEBPv3vHsW6uOHkQR384NgI7S6zpj
|
||||||
|
name: mesa-2404
|
||||||
|
type: app
|
||||||
|
-
|
||||||
|
default-channel: 1/edge
|
||||||
|
id: EI0D1KHjP8XiwMZKqSjuh6W8zvcowUVP
|
||||||
|
name: firmware-updater
|
||||||
|
type: app
|
||||||
|
-
|
||||||
|
default-channel: 1/edge
|
||||||
|
id: FppXWunWzuRT2NUT9CwoBPNJNZBYOCk0
|
||||||
|
name: desktop-security-center
|
||||||
|
type: app
|
||||||
|
-
|
||||||
|
default-channel: 1/edge
|
||||||
|
id: aoc5lfC8aUd2VL8VpvynUJJhGXp5K6Dj
|
||||||
|
name: prompting-client
|
||||||
|
type: app
|
||||||
|
-
|
||||||
|
default-channel: 2/edge
|
||||||
|
id: gjf3IPXoRiipCu9K0kVu52f0H56fIksg
|
||||||
|
name: snap-store
|
||||||
|
type: app
|
||||||
|
-
|
||||||
|
default-channel: latest/edge
|
||||||
|
id: jZLfBRzf1cYlYysIjD2bwSzNtngY0qit
|
||||||
|
name: gtk-common-themes
|
||||||
|
type: app
|
||||||
|
-
|
||||||
|
default-channel: latest/edge
|
||||||
|
id: 3wdHCAVyZEmYsCMFDE9qt92UV8rC8Wdk
|
||||||
|
name: firefox
|
||||||
|
type: app
|
||||||
|
-
|
||||||
|
default-channel: latest/edge
|
||||||
|
id: ew7OxpbRTxfK7ImpIygRR85lkxvU7Pzt
|
||||||
|
name: gnome-46-2404
|
||||||
|
type: app
|
||||||
|
-
|
||||||
|
default-channel: latest/edge
|
||||||
|
id: IrwRHakqtzhFRHJOOPxKVPU0Kk7Erhcu
|
||||||
|
name: snapd-desktop-integration
|
||||||
|
type: app
|
||||||
|
timestamp: 2025-12-09T12:00:00.0Z
|
||||||
|
sign-key-sha3-384: 9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn
|
||||||
|
|
||||||
|
AcLBXAQAAQoABgUCaUFt7QAKCRDgT5vottzAEhdnD/92LBcQm3iw/kPao4KqGE0OhfXDFd7Z6+Qv
|
||||||
|
A1Dlzz6Cw0tuj0r5aZH7vJQCx4kC1Eaoi8apg3XhqAyhr74/MsIwMhPPL8qcSNv8ZWruoGwFp/rx
|
||||||
|
M6NSBKc6hrYqACYfEkBwfq9SgmIDQKFeBVudwswLK2SN58wrDNJjuWz/eJ5hUIIe3ga5ScfzO4Jr
|
||||||
|
jTWS4kh5lpttCPFX8ouLkMgLUxijQpxFbHoF1trXJndFvavStT0yuC0y5TXzb3wJbbiF/MXZWyjV
|
||||||
|
/4U+oQLodO77MhaD01kk2y5bZ62YuQ3MPL0fQGypon12GPHeNNcEcYWRZlFv+JkWAduWlnuefj1D
|
||||||
|
dVWV8dQQmSZGZNiGTsIJxkY9+4B+t/OhosGDc6jEmEZcKNVi9fnl0+awkzK6scNNmupZ8NwJl8ZR
|
||||||
|
mJSsfaBcH4paYV1x31y4uTELv+OuDWAJ3D0RoCR8H0djTBxRhsF2/JpSJasxVmSbzWHPSeM3f1aO
|
||||||
|
ChZGwbD6J2SpzsrdogUP/9z6o8YuVnJkOxoBYuXhT1pEYTd93/hE++j3MpOqey/xw8UDbYmq5oJf
|
||||||
|
uKaYLOMphqDm5hUCZmxQp8gTzDleZGjxYS2fOS4qFUJlvyVwsSoJMXU+6YfA6tgEQ4Dbh6zp6r78
|
||||||
|
MjEqfWn4lL16xW2Zzr6e8xWwUrM7T3Gp4WTA7/xOeA==
|
||||||
104
live-build/ubuntu/ubuntu-classic-amd64.model
Normal file
104
live-build/ubuntu/ubuntu-classic-amd64.model
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
type: model
|
||||||
|
authority-id: canonical
|
||||||
|
series: 16
|
||||||
|
brand-id: canonical
|
||||||
|
model: ubuntu-classic-2604-amd64
|
||||||
|
architecture: amd64
|
||||||
|
base: core24
|
||||||
|
classic: true
|
||||||
|
distribution: ubuntu
|
||||||
|
grade: signed
|
||||||
|
snaps:
|
||||||
|
-
|
||||||
|
default-channel: classic-26.04/stable
|
||||||
|
id: UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH
|
||||||
|
name: pc
|
||||||
|
type: gadget
|
||||||
|
-
|
||||||
|
components:
|
||||||
|
nvidia-580-uda-ko:
|
||||||
|
presence: optional
|
||||||
|
nvidia-580-uda-user:
|
||||||
|
presence: optional
|
||||||
|
default-channel: 26.04/stable
|
||||||
|
id: pYVQrBcKmBa0mZ4CCN7ExT6jH8rY1hza
|
||||||
|
name: pc-kernel
|
||||||
|
type: kernel
|
||||||
|
-
|
||||||
|
default-channel: latest/stable
|
||||||
|
id: amcUKQILKXHHTlmSa7NMdnXSx02dNeeT
|
||||||
|
name: core22
|
||||||
|
type: base
|
||||||
|
-
|
||||||
|
default-channel: latest/stable
|
||||||
|
id: dwTAh7MZZ01zyriOZErqd1JynQLiOGvM
|
||||||
|
name: core24
|
||||||
|
type: base
|
||||||
|
-
|
||||||
|
default-channel: latest/stable
|
||||||
|
id: PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4
|
||||||
|
name: snapd
|
||||||
|
type: snapd
|
||||||
|
-
|
||||||
|
default-channel: latest/stable
|
||||||
|
id: EISPgh06mRh1vordZY9OZ34QHdd7OrdR
|
||||||
|
name: bare
|
||||||
|
type: base
|
||||||
|
-
|
||||||
|
default-channel: latest/stable/ubuntu-26.04
|
||||||
|
id: HyhSEBPv3vHsW6uOHkQR384NgI7S6zpj
|
||||||
|
name: mesa-2404
|
||||||
|
type: app
|
||||||
|
-
|
||||||
|
default-channel: 1/stable/ubuntu-26.04
|
||||||
|
id: EI0D1KHjP8XiwMZKqSjuh6W8zvcowUVP
|
||||||
|
name: firmware-updater
|
||||||
|
type: app
|
||||||
|
-
|
||||||
|
default-channel: 1/stable/ubuntu-26.04
|
||||||
|
id: FppXWunWzuRT2NUT9CwoBPNJNZBYOCk0
|
||||||
|
name: desktop-security-center
|
||||||
|
type: app
|
||||||
|
-
|
||||||
|
default-channel: 1/stable/ubuntu-26.04
|
||||||
|
id: aoc5lfC8aUd2VL8VpvynUJJhGXp5K6Dj
|
||||||
|
name: prompting-client
|
||||||
|
type: app
|
||||||
|
-
|
||||||
|
default-channel: 2/stable/ubuntu-26.04
|
||||||
|
id: gjf3IPXoRiipCu9K0kVu52f0H56fIksg
|
||||||
|
name: snap-store
|
||||||
|
type: app
|
||||||
|
-
|
||||||
|
default-channel: latest/stable/ubuntu-26.04
|
||||||
|
id: jZLfBRzf1cYlYysIjD2bwSzNtngY0qit
|
||||||
|
name: gtk-common-themes
|
||||||
|
type: app
|
||||||
|
-
|
||||||
|
default-channel: latest/stable/ubuntu-26.04
|
||||||
|
id: 3wdHCAVyZEmYsCMFDE9qt92UV8rC8Wdk
|
||||||
|
name: firefox
|
||||||
|
type: app
|
||||||
|
-
|
||||||
|
default-channel: latest/stable/ubuntu-26.04
|
||||||
|
id: ew7OxpbRTxfK7ImpIygRR85lkxvU7Pzt
|
||||||
|
name: gnome-46-2404
|
||||||
|
type: app
|
||||||
|
-
|
||||||
|
default-channel: latest/stable/ubuntu-26.04
|
||||||
|
id: IrwRHakqtzhFRHJOOPxKVPU0Kk7Erhcu
|
||||||
|
name: snapd-desktop-integration
|
||||||
|
type: app
|
||||||
|
timestamp: 2025-12-09T12:00:00.0Z
|
||||||
|
sign-key-sha3-384: 9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn
|
||||||
|
|
||||||
|
AcLBXAQAAQoABgUCaYzP9QAKCRDgT5vottzAEus2D/4jJVutpoPmDrLjNQLn2KNf/f1L2zU8ESSe
|
||||||
|
VpFjy+9Ff7AxXckALM4eEy/J5mc+UNhHQ/7Thp4XYy2NiH14n9Lv5kVqZCz8udiEfcfLy5gGveio
|
||||||
|
oXyGX7J5x9sq3YXV1IHS84aqJS0si80TTLCRQXUN8oUZIVRkgFOGIVVneQkn1ppNs87kNgvBT1ow
|
||||||
|
nwr9fVvZnt5bTprCxs4R5cEUlWTJMN4l96Eh530Q+wqCjFxbTs6FADUYielsFnBDl/Q1M0fozg4F
|
||||||
|
Ct4gBbvFGWZhp8LXiCbJvTd3PAAV1HYAgtKDKZT0NQp8qaU5DpgTDiUzIjaAJP7feSU5AYDLuVSH
|
||||||
|
V3zD8sosg1nmPvVtuSi2q5Z+/zd6gmG+vLn5d16whNqELDnX0O9Hxarc/3DD3ANZrrbXlq/PEJNB
|
||||||
|
Lor5osHLN4utW7CUC5MIEQ5/Z/6cSuav6rQ+bBiAOzQSHRCbhfyCGSMMINX2CE3ePw3moi9gwXeh
|
||||||
|
vKw1iItEOxywEKbeBNEvddnGsvmzoqf9Jg53/X0yrQQVZTHYFsQlTRk9ggajdZnPjJMTqlAqjXnP
|
||||||
|
QCsgnprvln0akW4IfEzc+IgoF5eiShJd4IidkBbbdNXRRYlHfmOG7ZvR9upJwe1M73Zfu1nQFEvT
|
||||||
|
fly59e2Vw8O50ljOVW3jT5fW36z8h1+ttxkKwVsQJg==
|
||||||
Loading…
x
Reference in New Issue
Block a user