Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion mkosi.conf
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ ShimBootloader=unsigned
Packages=
binutils
gdb
wireless-regdb

InitrdProfiles=lvm

Expand Down
17 changes: 17 additions & 0 deletions mkosi.conf.d/alinux.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# SPDX-License-Identifier: LGPL-2.1-or-later

[Match]
Distribution=alinux

[Distribution]
Release=3
Repositories=epel

[Content]
# Alibaba Cloud Linux 3 (systemd 239) does not ship systemd-boot or an unsigned shim.
Bootloader=grub
ShimBootloader=none

[Runtime]
# Unsigned grub cannot start under the default secure-boot OVMF firmware.
Firmware=uefi
1 change: 1 addition & 0 deletions mkosi.conf.d/azure-centos-fedora/mkosi.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[Match]
Distribution=|centos
Distribution=|alma
Distribution=|alinux
Distribution=|rocky
Distribution=|fedora
Distribution=|azure
Expand Down
9 changes: 9 additions & 0 deletions mkosi.conf.d/wireless-regdb.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: LGPL-2.1-or-later

[Match]
# Alibaba Cloud Linux 3 / EPEL 8 do not ship wireless-regdb.
Distribution=!alinux

[Content]
Packages=
wireless-regdb
8 changes: 7 additions & 1 deletion mkosi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3269,9 +3269,15 @@ def run_firstboot(context: Context) -> None:
if not options and not creds:
return

cmdline = ["systemd-firstboot", "--root=/buildroot"]
# --force was added in systemd 246; Alibaba Cloud Linux 3 ships systemd 239.
if systemd_tool_version("systemd-firstboot", sandbox=context.sandbox) >= 246:
cmdline += ["--force"]
cmdline += options

with complete_step("Applying first boot settings"):
run(
["systemd-firstboot", "--root=/buildroot", "--force", *options],
cmdline,
sandbox=context.sandbox(options=context.rootoptions()),
)

Expand Down
41 changes: 40 additions & 1 deletion mkosi/bootloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,39 @@ def find_grub_binary(config: Config, binary: str) -> Optional[Path]:
return config.find_binary(f"grub-{binary}", f"grub2-{binary}", f"/usr/lib/grub/i386-pc/grub-{binary}")


def grub_supports_disable_shim_lock(context: Context, mkimage: Path) -> bool:
# --disable-shim-lock was added in GRUB 2.06; Alibaba Cloud Linux 3 ships GRUB 2.02.
return (
"--disable-shim-lock"
in run(
[mkimage, "--help"],
stdout=subprocess.PIPE,
sandbox=context.sandbox(),
).stdout
)


def ensure_grubenv(context: Context) -> None:
# RHEL-family grub2-efi packages ship /boot/grub2/grubenv as a symlink to a
# %ghost file under /boot/efi/EFI/<vendor>/grubenv. Create the target so
# systemd-repart CopyFiles=/boot:/ does not fail when populating the ESP.
link = context.root / "boot/grub2/grubenv"
if not link.is_symlink() or link.exists():
return

target = (link.parent / link.readlink()).resolve(strict=False)
with umask(~0o700):
target.parent.mkdir(parents=True, exist_ok=True)

# GRUB environment blocks are exactly 1024 bytes.
header = b"# GRUB Environment Block\n"
with umask(~0o600):
target.write_bytes(header + b"#" * (1024 - len(header)))


def prepare_grub_config(context: Context) -> Optional[Path]:
ensure_grubenv(context)

config = context.root / "efi" / context.config.distribution.installer.grub_prefix() / "grub.cfg"
with umask(~0o700):
config.parent.mkdir(exist_ok=True)
Expand Down Expand Up @@ -258,7 +290,12 @@ def grub_mkimage(
"--output", workdir(output) if output else "/grub/core.img",
"--format", target,
*(["--sbat", os.fspath(workdir(sbat))] if sbat else []),
*(["--disable-shim-lock"] if context.config.shim_bootloader == ShimBootloader.none else []),
*(
["--disable-shim-lock"]
if context.config.shim_bootloader == ShimBootloader.none
and grub_supports_disable_shim_lock(context, mkimage)
else []
),
"cat",
"cmp",
"div",
Expand Down Expand Up @@ -365,6 +402,8 @@ def install_grub(context: Context) -> None:
if not want_grub_bios(context) and not want_grub_efi(context):
return

ensure_grubenv(context)

if want_grub_bios(context):
grub_mkimage(context, target="i386-pc", modules=("biosdisk",))

Expand Down
3 changes: 3 additions & 0 deletions mkosi/distribution/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,15 @@ class Distribution(StrEnum):
openmandriva = enum.auto()
rocky = enum.auto()
alma = enum.auto()
alinux = enum.auto()
azure = enum.auto()
custom = enum.auto()

def is_centos_variant(self) -> bool:
return self in (
Distribution.centos,
Distribution.alma,
Distribution.alinux,
Distribution.rocky,
Distribution.rhel,
Distribution.rhel_ubi,
Expand All @@ -68,6 +70,7 @@ def is_rpm_distribution(self) -> bool:
Distribution.openmandriva,
Distribution.rocky,
Distribution.alma,
Distribution.alinux,
)

@property
Expand Down
195 changes: 195 additions & 0 deletions mkosi/distribution/alinux.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# SPDX-License-Identifier: LGPL-2.1-or-later

from collections.abc import Iterable, Sequence
from pathlib import Path

from mkosi.config import Architecture
from mkosi.context import Context
from mkosi.distribution import Distribution, centos, join_mirror
from mkosi.installer.dnf import Dnf
from mkosi.installer.rpm import RpmRepository, find_rpm_gpgkey, setup_rpm
from mkosi.log import die
from mkosi.versioncomp import GenericVersion


def _is_kernel_rpm(package: str) -> bool:
return package == "kernel" or package.startswith("kernel-")


class Installer(centos.Installer, distribution=Distribution.alinux):
@classmethod
def pretty_name(cls) -> str:
return "Alibaba Cloud Linux"

@classmethod
def default_release(cls) -> str:
return "3"

@classmethod
def setup(cls, context: Context) -> None:
if GenericVersion(cls.major_release(context.config)) != 3:
die(f"Only {cls.pretty_name()} 3 is currently supported")

setup_rpm(context, dbpath=cls.dbpath(context))
Dnf.setup(context, list(cls.repositories(context)))

@classmethod
def install(cls, context: Context) -> None:
cls.install_packages(context, ["filesystem", "alinux-release"], apivfs=False)

# alinux-release only ships /etc/os-release; mkosi expects /usr/lib/os-release.
etc_os_release = context.root / "etc/os-release"
usr_lib_os_release = context.root / "usr/lib/os-release"
if etc_os_release.exists() and not usr_lib_os_release.exists():
usr_lib_os_release.parent.mkdir(parents=True, exist_ok=True)
usr_lib_os_release.write_bytes(etc_os_release.read_bytes())

@classmethod
def install_packages(
cls,
context: Context,
packages: Sequence[str],
*,
apivfs: bool = True,
allow_downgrade: bool = False,
) -> None:
kernels = [p for p in packages if _is_kernel_rpm(p)]
others = [p for p in packages if not _is_kernel_rpm(p)]

# Alinux kernel %posttrans runs `grubby --update-kernel` after kernel-install. Under an
# installroot there are no BLS entries for /boot/vmlinuz-*, so grubby exits 1 and newer
# dnf5/rpm abort the transaction. mkosi configures the bootloader itself later, so install
# grubby first, temporarily stub it out, install the kernel packages, then restore it.
if kernels:
if "grubby" not in others:
others = [*others, "grubby"]

if others:
super().install_packages(
context,
others,
apivfs=apivfs,
allow_downgrade=allow_downgrade,
)

if not kernels:
return

stubs = [
context.root / "usr/sbin/grubby",
context.root / "usr/libexec/grubby/grubby-bls",
]
saved: dict[Path, bytes] = {}
for path in stubs:
if path.exists():
saved[path] = path.read_bytes()
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("#!/bin/sh\nexit 0\n")
path.chmod(0o755)

try:
super().install_packages(
context,
kernels,
apivfs=apivfs,
allow_downgrade=allow_downgrade,
)
finally:
for path, content in saved.items():
path.write_bytes(content)
path.chmod(0o755)

@classmethod
def architecture(cls, arch: Architecture) -> str:
a = {
Architecture.x86_64: "x86_64",
Architecture.arm64: "aarch64",
}.get(arch) # fmt: skip

if not a:
die(f"Architecture {arch} is not supported by {cls.pretty_name()}")

return a

@classmethod
def _default_mirror(cls) -> str:
return "https://mirrors.aliyun.com/alinux"

@classmethod
def _epel_mirror(cls, context: Context) -> str:
if epel := context.config.finalize_environment().get("EPEL_MIRROR"):
return epel

# Alinux mirrors keep EPEL as a sibling of the alinux tree (…/alinux → …/epel).
mirror = context.config.mirror or cls._default_mirror()
return join_mirror(mirror, "..").rstrip("/")

@classmethod
def gpgurls(cls, context: Context) -> tuple[str, ...]:
major = cls.major_release(context.config)
mirror = context.config.mirror or cls._default_mirror()
keyurl = join_mirror(mirror, f"{major}/RPM-GPG-KEY-ALINUX-{major}")

# Prefer a locally installed key. Do not fall back to RPM-GPG-KEY-ANOLIS from
# distribution-gpg-keys: that file contains multiple keys and dnf may import the
# Anolis OS key instead of the Alibaba Cloud Linux package-signing key.
key = find_rpm_gpgkey(context, f"RPM-GPG-KEY-ALINUX-{major}", required=False)
return (key or keyurl,)

@classmethod
def repository_variants(
cls,
context: Context,
gpgurls: tuple[str, ...],
repo: str,
) -> list[RpmRepository]:
if context.config.snapshot:
die(f"Snapshot= is not supported for {cls.pretty_name()}")

relpath = f"$releasever/{repo.lower()}/$basearch"
mirror = context.config.mirror or cls._default_mirror()
url = f"baseurl={join_mirror(mirror, relpath)}"

return [RpmRepository(repo, url, gpgurls, repo_gpgcheck=False)]

@classmethod
def repositories(cls, context: Context) -> Iterable[RpmRepository]:
if context.config.local_mirror:
gpgurls = cls.gpgurls(context)
yield RpmRepository(
"local",
f"baseurl={context.config.local_mirror}",
gpgurls,
repo_gpgcheck=False,
)
return

gpgurls = cls.gpgurls(context)

yield from cls.repository_variants(context, gpgurls, "os")
yield from cls.repository_variants(context, gpgurls, "updates")
yield from cls.repository_variants(context, gpgurls, "plus")
yield from cls.repository_variants(context, gpgurls, "module")
yield from cls.repository_variants(context, gpgurls, "powertools")

epel_mirror = cls._epel_mirror(context)
epel_gpgurls = (
find_rpm_gpgkey(
context,
"RPM-GPG-KEY-EPEL-8",
join_mirror(epel_mirror, "epel/RPM-GPG-KEY-EPEL-8"),
),
)
yield RpmRepository(
"epel",
f"baseurl={join_mirror(epel_mirror, 'epel/8/Everything/$basearch')}",
epel_gpgurls,
enabled=False,
repo_gpgcheck=False,
)

yield from cls.sig_repositories(context)

@classmethod
def sig_repositories(cls, context: Context) -> list[RpmRepository]:
return []
10 changes: 7 additions & 3 deletions mkosi/installer/rpm.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,16 @@ def setup_rpm(
if not (confdir / "macros.pkgverify_level").exists():
(confdir / "macros.pkgverify_level").write_text("%_pkgverify_level digest")

if context.config.distribution == Distribution.opensuse or (
context.config.distribution.is_centos_variant() and context.config.release == "9"
if (
context.config.distribution == Distribution.opensuse
or context.config.distribution == Distribution.alinux
or (context.config.distribution.is_centos_variant() and context.config.release == "9")
):
# Write an rpm sequoia policy that makes sure "sha1.second_preimage_resistance = always" is
# configured and makes sure that a minimal config is in place to make sure builds succeed.
# TODO: Remove when distributions GPG keys are accepted by the default rpm-sequoia config everywhere.
# Needed for Alibaba Cloud Linux 3 keys that still use SHA1 binding signatures.
# TODO: Remove when distribution GPG keys are accepted by the default
# rpm-sequoia config everywhere.

p = context.sandbox_tree / "etc/crypto-policies/back-ends/rpm-sequoia.config"
p.parent.mkdir(parents=True, exist_ok=True)
Expand Down
4 changes: 3 additions & 1 deletion mkosi/kmod.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,9 @@ def resolve_module_dependencies(
with chdir(context.root):
nametofile = {module_path_to_name(m): m for m in all_modules(modulesd)}

todo = {*builtin, *modules}
# Only query loadable modules. Built-in modules are not .ko files; older modinfo (e.g. on
# Alibaba Cloud Linux 3) exits non-zero when asked about them.
todo = {m for m in modules if m in nametofile}
mods: set[str] = set()
firmware = set()

Expand Down
Loading
Loading