Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add APK and postmarketOS support #3236

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
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
22 changes: 22 additions & 0 deletions mkosi.conf.d/20-alpine.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# SPDX-License-Identifier: LGPL-2.1-or-later

[Match]
Distribution=postmarketos

[Distribution]
# keyring is only available for Arch
RepositoryKeyCheck=no

[Content]
InitrdPackages=
systemd-networkd # required for systemd-network-generator
Packages=
bash
systemd-boot
systemd-networkd # required for systemd-network-generator
linux-virt
# stubbyboot-efistub
# gummiboot-efistub

Bootable=yes
ShimBootloader=none
2 changes: 1 addition & 1 deletion mkosi.prepare
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ if [ "$1" = "build" ]; then
exit 0
fi

mkosi-chroot "$SRCDIR"/bin/mkosi dependencies | xargs -d '\n' mkosi-install
# mkosi-chroot "$SRCDIR"/bin/mkosi dependencies | xargs -d '\n' mkosi-install
10 changes: 9 additions & 1 deletion mkosi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1160,14 +1160,22 @@ def gzip_binary(context: Context) -> str:
return "pigz" if context.config.find_binary("pigz") else "gzip"


def _kernel_inspect(path: Path) -> str:
r = run(["file", "-bL", path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
match = re.search("version ([^ ]*)", r.stdout)
return match.group(1) if match else "unknown"



def fixup_vmlinuz_location(context: Context) -> None:
# Some architectures ship an uncompressed vmlinux (ppc64el, riscv64)
for type in ("vmlinuz", "vmlinux"):
for d in context.root.glob(f"boot/{type}-*"):
if d.is_symlink():
continue

kver = d.name.removeprefix(f"{type}-")
# kver = d.name.removeprefix(f"{type}-")
kver = _kernel_inspect(d)
vmlinuz = context.root / "usr/lib/modules" / kver / type
if not vmlinuz.parent.exists():
continue
Expand Down
2 changes: 2 additions & 0 deletions mkosi/distributions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class PackageType(StrEnum):
rpm = enum.auto()
deb = enum.auto()
pkg = enum.auto()
apk = enum.auto()


class DistributionInstaller:
Expand Down Expand Up @@ -79,6 +80,7 @@ class Distribution(StrEnum):
debian = enum.auto()
kali = enum.auto()
ubuntu = enum.auto()
postmarketos = enum.auto()
arch = enum.auto()
opensuse = enum.auto()
mageia = enum.auto()
Expand Down
2 changes: 1 addition & 1 deletion mkosi/distributions/arch.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def default_release(cls) -> str:
return "rolling"

@classmethod
def package_manager(cls, config: "Config") -> type[PackageManager]:
def package_manager(cls, config: Config) -> type[PackageManager]:
return Pacman

@classmethod
Expand Down
92 changes: 92 additions & 0 deletions mkosi/distributions/postmarketos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# SPDX-License-Identifier: LGPL-2.1+

from collections.abc import Iterable, Sequence

from mkosi.config import Architecture, Config
from mkosi.context import Context
from mkosi.distributions import Distribution, DistributionInstaller, PackageType
from mkosi.installer import PackageManager
from mkosi.installer.apk import Apk
from mkosi.log import die
from mkosi.util import sort_packages


class Installer(DistributionInstaller):
@classmethod
def pretty_name(cls) -> str:
return "postmarketOS"

@classmethod
def filesystem(cls) -> str:
return "ext4"

@classmethod
def package_type(cls) -> PackageType:
return PackageType.apk

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

@classmethod
def default_tools_tree_distribution(cls) -> Distribution:
return Distribution.postmarketos

@classmethod
def package_manager(cls, config: Config) -> type[PackageManager]:
return Apk

@classmethod
def setup(cls, context: Context) -> None:
Apk.setup(context, list(cls.repositories(context)))

@classmethod
def install(cls, context: Context) -> None:
for dir in ["lib", "bin", "sbin"]:
(context.root / "usr" / dir).mkdir(parents=True, exist_ok=True)
(context.root / dir).symlink_to(f"usr/{dir}")

cls.install_packages(context, [
"--initdb",
"alpine-base",
"postmarketos-base",
# "device-qemu-amd64",
# "device-qemu-amd64-kernel-edge",
], apivfs=False)

@classmethod
def install_packages(cls, context: Context, packages: Sequence[str], apivfs: bool = True) -> None:
Apk.invoke(
context,
"add",
[*sort_packages(packages)],
apivfs=apivfs,
)

@classmethod
def remove_packages(cls, context: Context, packages: Sequence[str]) -> None:
Apk.invoke(context, "del", [*packages], apivfs=True)

@classmethod
def repositories(cls, context: Context) -> Iterable[str]:
print("fetch repos")
return iter([
"https://mirror.postmarketos.org/postmarketos/staging/systemd/master/",
"http://mirror.postmarketos.org/postmarketos/master",
"http://dl-cdn.alpinelinux.org/alpine/edge/main",
"http://dl-cdn.alpinelinux.org/alpine/edge/community",
"http://dl-cdn.alpinelinux.org/alpine/edge/testing",
])

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

if not a:
die(f"Architecture {a} is not supported by Alpine Linux")

return a
114 changes: 114 additions & 0 deletions mkosi/installer/apk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# SPDX-License-Identifier: LGPL-2.1+
from collections.abc import Iterable, Sequence
from pathlib import Path

from mkosi.config import Config
from mkosi.context import Context
from mkosi.installer import PackageManager
from mkosi.run import run
from mkosi.types import _FILE, CompletedProcess, PathString


class Apk(PackageManager):
@classmethod
def executable(cls, config: Config) -> str:
return "apk"

@classmethod
def subdir(cls, config: Config) -> Path:
return Path("apk")

@classmethod
def cache_subdirs(cls, cache: Path) -> list[Path]:
return [cache / "apk"]

@classmethod
def scripts(cls, context: Context) -> dict[str, list[PathString]]:
return {
"apk": cls.apivfs_script_cmd(context) + cls.cmd(context),
"mkosi-install": ["apk", "--update-cache", "add"],
"mkosi-upgrade": ["apk", "--update-cache", "upgrade"],
"mkosi-remove": ["apk", "--remove", "del"],
"mkosi-reinstall": ["apk", "--update-cache", "fix"],
} # fmt: skip

@classmethod
def mounts(cls, context: Context) -> list[PathString]:
mounts: list[PathString] = [
*super().mounts(context),
# pacman writes downloaded packages to the first writable cache directory. We don't want it to write to our
# local repository directory so we expose it as a read-only directory to pacman.
# "--ro-bind", context.packages, "/var/cache/apk",
]

return mounts

@classmethod
def setup(cls, context: Context, repositories: Iterable[str]) -> None:
config = context.root / "etc/apk/repositories"
if config.exists():
print("apk setup() repo file exists!")
return

config.parent.mkdir(exist_ok=True, parents=True)

with config.open("w") as f:
for repo in repositories:
f.write(f"{repo}\n")

@classmethod
def cmd(cls, context: Context) -> list[PathString]:
return [
"apk",
"--root", "/buildroot",
# Make sure pacman looks at our local repository first by putting it as the first cache directory. We mount
# it read-only so the second directory will still be used for writing new cache entries.
#"--cache-dir=" + str(context.root / "var/cache/apk/mkosi"),
"--cache-dir", "/var/cache/apk",
"--arch", context.config.distribution.architecture(context.config.architecture),
"--no-interactive",
"--update-cache",
*(["--allow-untrusted"] if not context.config.repository_key_check else []),
] # fmt: skip

@classmethod
def invoke(
cls,
context: Context,
operation: str,
arguments: Sequence[str] = (),
*,
apivfs: bool = False,
stdout: _FILE = None,
) -> CompletedProcess:
return run(
cls.cmd(context) + [operation, *arguments],
sandbox=cls.sandbox(context, apivfs=apivfs),
env=cls.finalize_environment(context),
stdout=stdout,
)

@classmethod
def sync(cls, context: Context, force: bool) -> None:
# TODO implement force
if (context.root / "etc/apk/world").exists():
cls.invoke(context, "update", [])

@classmethod
def createrepo(cls, context: Context) -> None:
print("apk createrepo() called!")
# run(
# [
# "repo-add",
# "--quiet",
# context.packages / "mkosi.db.tar",
# *sorted(context.packages.glob("*.pkg.tar*"), key=lambda p: GenericVersion(Path(p).name))
# ],
# sandbox=context.sandbox(options=["--bind", context.packages, context.packages]),
# )

# # pacman can't sync a single repository, so we go behind its back and do it ourselves.
# shutil.move(
# context.packages / "mkosi.db.tar",
# context.package_cache_dir / "lib/pacman/sync/mkosi.db"
# )
3 changes: 2 additions & 1 deletion mkosi/qemu.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
want_selinux_relabel,
yes_no,
)
from mkosi.distributions import Distribution
from mkosi.log import ARG_DEBUG, die
from mkosi.partition import finalize_root, find_partitions
from mkosi.run import SD_LISTEN_FDS_START, AsyncioThread, find_binary, fork_and_wait, run, spawn, workdir
Expand Down Expand Up @@ -1335,7 +1336,7 @@ def add_virtiofs_mount(

for k, v in credentials.items():
payload = base64.b64encode(v.encode()).decode()
if config.architecture.supports_smbios(firmware):
if config.architecture.supports_smbios(firmware) and not config.distribution == Distribution.postmarketos:
cmdline += ["-smbios", f"type=11,value=io.systemd.credential.binary:{k}={payload}"]
# qemu's fw_cfg device only supports keys up to 55 characters long.
elif config.architecture.supports_fw_cfg() and len(k) <= 55 - len("opt/io.systemd.credentials/"):
Expand Down
7 changes: 7 additions & 0 deletions mkosi/resources/mkosi-vm/mkosi.conf.d/alpine.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-or-later

[Match]
Distribution=postmarketos

[Content]
Packages=systemd-boot
Loading