]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Add a keyring cache and use it for Arch Linux 3366/head
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 15 Jan 2025 22:07:07 +0000 (23:07 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 16 Jan 2025 09:23:45 +0000 (10:23 +0100)
Currently, the Arch keyring from the host or tools tree is always
used directly without any option to modify it (except by modifying
it directly on the host or in the tools tree). We also don't support
RepositoryKeyFetch= for the Arch Linux keyring and it's one of the
sources of annoying mounts outside of /usr which complicates
"mkosi sandbox".

Let's improve the situation by switching to our own pacman keyring
instead of using the one from the tools tree or host. Instead, we'll
only use /usr/share/pacman/keyrings from the host or tools tree to
populate the keyring we maintain ourselves. Users can extend the
keyring created by mkosi with their own keys via sandbox trees. If
RepositoryKeyFetch= is enabled, we'll download the archlinux-keyring
package and extract the keyring from there into the sandbox.

The keyring cache is maintained together with the repository metadata
cache. The only difference is that sync_repository_metadata() will
update the global package cache whereas the keyring directory is always
maintained per cache directory instead of globally.

We take the opportunity to stop using Michel's kernel-utils ppa with a
more recent archlinux-keyring package in favor of enabling
RepositoryKeyFetch= by default for Arch builds on Ubuntu.

15 files changed:
action.yaml
mkosi.conf.d/20-ubuntu/mkosi.conf.d/20-jammy.conf [deleted file]
mkosi.conf.d/20-ubuntu/mkosi.sandbox/etc/apt/sources.list.d/kernel-utils.sources [deleted file]
mkosi.prepare.chroot [deleted file]
mkosi/__init__.py
mkosi/archive.py
mkosi/config.py
mkosi/context.py
mkosi/distributions/__init__.py
mkosi/distributions/arch.py
mkosi/installer/__init__.py
mkosi/installer/pacman.py
mkosi/mounts.py
mkosi/resources/man/mkosi.1.md
mkosi/resources/mkosi-tools/mkosi.prepare.chroot [deleted file]

index 8a5b574ed37b5504c1df789cfb22521ff1cd5316..c57df133b1ef219ee312751434303637deeda11b 100644 (file)
@@ -56,7 +56,7 @@ runs:
     - name: Create missing mountpoints
       shell: bash
       run: |
-        for p in /etc/pki /etc/pacman.d/gnupg /etc/ssl /etc/ca-certificates /var/lib/ca-certificates /etc/crypto-policies; do
+        for p in /etc/pki /etc/ssl /etc/ca-certificates /var/lib/ca-certificates /etc/crypto-policies; do
           if [[ ! -e "$p" ]]; then
             sudo mkdir -p "$p"
           fi
@@ -88,7 +88,6 @@ runs:
         # This is added by default, and it is often broken, but we don't need anything from it
         sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list
         # For archlinux-keyring and pacman
-        sudo add-apt-repository ppa:michel-slm/kernel-utils
         sudo apt-get update
         mkosi dependencies | xargs -d '\n' sudo apt-get install --assume-yes --no-install-recommends
 
diff --git a/mkosi.conf.d/20-ubuntu/mkosi.conf.d/20-jammy.conf b/mkosi.conf.d/20-ubuntu/mkosi.conf.d/20-jammy.conf
deleted file mode 100644 (file)
index dbb5f8c..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-# SPDX-License-Identifier: LGPL-2.1-or-later
-
-[Match]
-Release=jammy
-
-[Build]
-SandboxTrees=mkosi.sandbox
diff --git a/mkosi.conf.d/20-ubuntu/mkosi.sandbox/etc/apt/sources.list.d/kernel-utils.sources b/mkosi.conf.d/20-ubuntu/mkosi.sandbox/etc/apt/sources.list.d/kernel-utils.sources
deleted file mode 100644 (file)
index c79e0c4..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-# SPDX-License-Identifier: LGPL-2.1-or-later
-Enabled: yes
-Types: deb
-URIs: https://ppa.launchpadcontent.net/michel-slm/kernel-utils/ubuntu
-Suites: jammy
-Components: main
-Signed-By:
- -----BEGIN PGP PUBLIC KEY BLOCK-----
- .
- xsFNBGJyEb0BEADR6CoHmzotr+Z1UmqkNQZ/z+tm4u3/KbjD/UKHOloAJK2L2Mf6
- Eq1hbs2MlEYa7VtYcfq+NqluvTtqHckgE6sTFGbxQXMUDK+bcxqXmQUQcRw/Wytl
- BgRr6fCA+pK82W6Z5eFyYsfbZMqnIqw3rbtx02K43KFGOiP8Pj/FFcPXzf9q3+3k
- EXELs8y6N7OEYeloEs45RnBwkKETvMX08zbTZfn5owfYZRd4VpIZJ2BnZprzdzfP
- Z1ZTkkDTAUpZvpXFi5WtDx6rVP92+7OYxOjUKzQ9wFbKdkZVzqhfJIR2SHM2tHVz
- GwIZl6vYsAqLLZccQSS4nBCXIzUwRO28LwqzacjKabl197fk0C/IKFw2Z4/ZsCHb
- VcrnttD3G1AvQ+DMvEPTzB9vD4R7uNEkmg4UYokzk6yW9/KQm3lNbQBS+D5jZREv
- hRyz4ZLW9wzz13H655nXzJCIZvLWVpRLNzQDscxYlkYBONoH+HiGafsgZqw+ITc3
- tDPTw0HqRNe+/oendXqOhtKhY2PRhD3vu5NgXLX7GuAHr7Dq9HMUyA5MxKH90e28
- vaYUmwU0jfYGgNnSCpRrAOx0SlqKpMlwW9VkpJctGrYMZ/ts6yPdJC2OSWiJRlMa
- Xmf4IvsLrCGobd27y03TYl5Uq6OOpD0DSP2hGZQxYHOgMhhZxT99IJUlMwARAQAB
- zShMYXVuY2hwYWQgUFBBIGZvciBNaWNoZWwgQWxleGFuZHJlIFNhbGltwsGOBBMB
- CgA4FiEE+8ojhpUuuImlrBJjySgIhqCuA8oFAmJyEb0CGwMFCwkIBwIGFQoJCAsC
- BBYCAwECHgECF4AACgkQySgIhqCuA8orpw//YWpifbMc2F3zNx8oW2UyTsr2IXtu
- 4/pHVtDroYokMOvCy2IR6FhzXSMM35yQBVfn92T5MiG0pHqXNUIZstt/m3Qo7tnj
- 1AR9f0mRLTKHONQCUP91CiVHGGKfaYHiyQ9Pxxp+LUxFkoEXUfQPl6N0wfGCd0Rb
- k9gcFlOo3+duOFsd+1Aw3Gi83SFcl6Bc4P/3i+dfB4g1Nbte0ZzPnFWKKYRV0K6z
- 5uDbJJYdIS+nwIxVXb7cnMUrrHBr4cUDsIXnAwVN+zeK7Q4CrJOpR6ZDGNb5SGcO
- TaJPEOpCIcIKkzW9IzYm5NTzxhQHg7jvCPrGBuX3nTt1fEzCn5L2se9iwehtsMat
- WXwi+yIYlpce2vHRPZEb8ILMoCL50veAAZ2tAlHx9UnAPNtT+1PPzrKPcIVCAB2e
- fBgUBcCaQ62LWsIQX1B9qL4xhGX7Z4nFk2aXNlrHjnnf5gwFCJ/XiVuFgGetfRrV
- r2PgfFFOfUanJ4LMu8sfqurrNJXrYMHfA8+qIbTLyltlqsOiEROOa/Qje5KEqmbe
- vg/hbqRpGNHdYKP1OynqBK8VAgG9/g5qGR8FLXr1DXl5dzlqyiIkRQINd9O6XjnX
- LWPl1wsOXOCY/jWgMxktt8Mv9qaaZ4CT9cuwsm/aml270A3GKRYHLDFP3CkuMnqd
- 0vsZgWMIQtgQmXU=
- =7vwW
- -----END PGP PUBLIC KEY BLOCK-----
diff --git a/mkosi.prepare.chroot b/mkosi.prepare.chroot
deleted file mode 100755 (executable)
index 1749c09..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/sh
-# SPDX-License-Identifier: LGPL-2.1-or-later
-set -e
-
-# Users might set $CONFIG but that influences pacman
-# so we unset it here to avoid any issues.
-unset CONFIG
-
-if [ "$1" = "final" ] && command -v pacman-key; then
-    pacman-key --init
-    pacman-key --populate archlinux
-fi
index 7ac87b324e975f27bd5792eca43830cf11eeafc7..1033e802254e5af8d2942a752a81eb64daf29968 100644 (file)
@@ -1312,7 +1312,9 @@ def build_default_initrd(context: Context) -> Path:
                 config,
                 workspace=workspace,
                 resources=context.resources,
-                # Reuse the repository metadata snapshot from the main image for the initrd.
+                # Reuse the keyring, repository metadata and local package repository from the main image for
+                # the default initrd.
+                keyring_dir=context.keyring_dir,
                 metadata_dir=context.metadata_dir,
                 package_dir=context.package_dir,
             )
@@ -4365,8 +4367,8 @@ def run_clean(args: Args, config: Config) -> None:
             rmtree(*config.build_dir.iterdir(), sandbox=sandbox)
 
     if remove_image_cache and config.cache_dir:
-        metadata = [metadata_cache(config)] if not config.image else []
-        remove_cache_entries(config, extra=metadata)
+        extra = [keyring_cache(config), metadata_cache(config)] if not config.image else []
+        remove_cache_entries(config, extra=extra)
 
     if remove_package_cache and any(config.package_cache_dir_or_default().glob("*")):
         subdir = config.distribution.package_manager(config).subdir(config)
@@ -4402,6 +4404,13 @@ def ensure_directories_exist(config: Config) -> None:
             config.build_dir.chmod(stat.S_IMODE(st.st_mode) & ~(stat.S_ISGID | stat.S_ISUID))
 
 
+def keyring_cache(config: Config) -> Path:
+    assert config.cache_dir
+    fragments = [config.distribution, config.release, config.architecture]
+
+    return config.cache_dir / f"{'~'.join(str(s) for s in fragments)}.keyring.cache"
+
+
 def metadata_cache(config: Config) -> Path:
     assert config.cache_dir
     fragments = [config.distribution, config.release, config.architecture]
@@ -4409,19 +4418,42 @@ def metadata_cache(config: Config) -> Path:
     return config.cache_dir / f"{'~'.join(str(s) for s in fragments)}.metadata.cache"
 
 
-def sync_repository_metadata(args: Args, images: Sequence[Config], *, resources: Path, dst: Path) -> None:
+def sync_repository_metadata(
+    args: Args,
+    images: Sequence[Config],
+    *,
+    resources: Path,
+    keyring_dir: Path,
+    metadata_dir: Path,
+) -> None:
     last = images[-1]
 
     # If we have a metadata cache and any cached image and using cached metadata is not explicitly disabled,
     # reuse the metadata cache.
     if (
         last.incremental
+        and keyring_cache(last).exists()
         and metadata_cache(last).exists()
         and last.cacheonly != Cacheonly.never
         and any(have_cache(config) for config in images)
     ):
+        if any(keyring_cache(last).iterdir()):
+            with complete_step("Copying cached package manager keyring"):
+                copy_tree(
+                    keyring_cache(last),
+                    keyring_dir,
+                    use_subvolumes=last.use_subvolumes,
+                    sandbox=last.sandbox,
+                )
+
         with complete_step("Copying cached package manager metadata"):
-            copy_tree(metadata_cache(last), dst, use_subvolumes=last.use_subvolumes, sandbox=last.sandbox)
+            copy_tree(
+                metadata_cache(last),
+                metadata_dir,
+                use_subvolumes=last.use_subvolumes,
+                sandbox=last.sandbox,
+            )
+
         return
 
     subdir = last.distribution.package_manager(last).subdir(last)
@@ -4431,16 +4463,13 @@ def sync_repository_metadata(args: Args, images: Sequence[Config], *, resources:
 
     # Sync repository metadata unless explicitly disabled.
     if last.cacheonly not in (Cacheonly.always, Cacheonly.metadata):
-        with (
-            complete_step("Syncing package manager metadata"),
-            lock_repository_metadata(last),
-            setup_workspace(args, last) as workspace,
-        ):
+        with setup_workspace(args, last) as workspace:
             context = Context(
                 args,
                 last,
                 workspace=workspace,
                 resources=resources,
+                keyring_dir=keyring_dir,
                 metadata_dir=last.package_cache_dir_or_default(),
             )
             context.root.mkdir(mode=0o755)
@@ -4448,24 +4477,36 @@ def sync_repository_metadata(args: Args, images: Sequence[Config], *, resources:
             install_sandbox_trees(context.config, context.sandbox_tree)
             context.config.distribution.setup(context)
 
-            context.config.distribution.package_manager(context.config).sync(
-                context,
-                force=context.args.force > 1 or context.config.cacheonly == Cacheonly.never,
-            )
+            context.config.distribution.keyring(context)
+
+            with complete_step("Syncing package manager metadata"), lock_repository_metadata(last):
+                context.config.distribution.package_manager(context.config).sync(
+                    context,
+                    force=context.args.force > 1 or context.config.cacheonly == Cacheonly.never,
+                )
 
     src = last.package_cache_dir_or_default() / "cache" / subdir
     for p in last.distribution.package_manager(last).cache_subdirs(src):
         p.mkdir(parents=True, exist_ok=True)
 
-    # If we're in incremental mode and caching metadata is not explicitly disabled, cache the synced
-    # repository metadata so we can reuse it later.
+    # If we're in incremental mode and caching metadata is not explicitly disabled, cache the keyring and the
+    # synced repository metadata so we can reuse them later.
     if last.incremental and last.cacheonly != Cacheonly.never:
-        rmtree(metadata_cache(last), sandbox=last.sandbox)
-        make_tree(metadata_cache(last), use_subvolumes=last.use_subvolumes, sandbox=last.sandbox)
+        rmtree(keyring_cache(last), metadata_cache(last), sandbox=last.sandbox)
+
+        for p in (keyring_cache(last), metadata_cache(last)):
+            make_tree(p, use_subvolumes=last.use_subvolumes, sandbox=last.sandbox)
+
+        copy_tree(keyring_dir, keyring_cache(last), use_subvolumes=last.use_subvolumes, sandbox=last.sandbox)
         copy_repository_metadata(last, metadata_cache(last))
-        copy_tree(metadata_cache(last), dst, use_subvolumes=last.use_subvolumes, sandbox=last.sandbox)
+        copy_tree(
+            metadata_cache(last),
+            metadata_dir,
+            use_subvolumes=last.use_subvolumes,
+            sandbox=last.sandbox,
+        )
     else:
-        copy_repository_metadata(last, dst)
+        copy_repository_metadata(last, metadata_dir)
 
 
 def run_build(
@@ -4473,6 +4514,7 @@ def run_build(
     config: Config,
     *,
     resources: Path,
+    keyring_dir: Path,
     metadata_dir: Path,
     package_dir: Optional[Path] = None,
 ) -> None:
@@ -4520,6 +4562,7 @@ def run_build(
                 config,
                 workspace=workspace,
                 resources=resources,
+                keyring_dir=keyring_dir,
                 metadata_dir=metadata_dir,
                 package_dir=package_dir,
             )
@@ -4710,12 +4753,31 @@ def run_verb(args: Args, images: Sequence[Config], *, resources: Path) -> None:
         check_tools(tools, Verb.build)
         ensure_directories_exist(tools)
 
-        with tempfile.TemporaryDirectory(
-            dir=tools.workspace_dir_or_default(),
-            prefix="mkosi-metadata-",
-        ) as metadata_dir:
-            sync_repository_metadata(args, [tools], resources=resources, dst=Path(metadata_dir))
-            fork_and_wait(run_build, args, tools, resources=resources, metadata_dir=Path(metadata_dir))
+        with (
+            tempfile.TemporaryDirectory(
+                dir=tools.workspace_dir_or_default(),
+                prefix="mkosi-keyring-",
+            ) as keyring_dir,
+            tempfile.TemporaryDirectory(
+                dir=tools.workspace_dir_or_default(),
+                prefix="mkosi-metadata-",
+            ) as metadata_dir,
+        ):
+            sync_repository_metadata(
+                args,
+                [tools],
+                resources=resources,
+                keyring_dir=Path(keyring_dir),
+                metadata_dir=Path(metadata_dir),
+            )
+            fork_and_wait(
+                run_build,
+                args,
+                tools,
+                resources=resources,
+                keyring_dir=Path(keyring_dir),
+                metadata_dir=Path(metadata_dir),
+            )
 
     if not args.verb.needs_build():
         return {
@@ -4759,13 +4821,25 @@ def run_verb(args: Args, images: Sequence[Config], *, resources: Path) -> None:
 
         with (
             tempfile.TemporaryDirectory(
-                dir=last.workspace_dir_or_default(), prefix="mkosi-metadata-"
+                dir=last.workspace_dir_or_default(),
+                prefix="mkosi-keyring-",
+            ) as keyring_dir,
+            tempfile.TemporaryDirectory(
+                dir=last.workspace_dir_or_default(),
+                prefix="mkosi-metadata-",
             ) as metadata_dir,
             tempfile.TemporaryDirectory(
-                dir=last.workspace_dir_or_default(), prefix="mkosi-packages-"
+                dir=last.workspace_dir_or_default(),
+                prefix="mkosi-packages-",
             ) as package_dir,
         ):
-            sync_repository_metadata(args, images, resources=resources, dst=Path(metadata_dir))
+            sync_repository_metadata(
+                args,
+                images,
+                resources=resources,
+                keyring_dir=Path(keyring_dir),
+                metadata_dir=Path(metadata_dir),
+            )
 
             for config in images:
                 run_sync_scripts(config)
@@ -4785,6 +4859,7 @@ def run_verb(args: Args, images: Sequence[Config], *, resources: Path) -> None:
                     args,
                     config,
                     resources=resources,
+                    keyring_dir=Path(keyring_dir),
                     metadata_dir=Path(metadata_dir),
                     package_dir=Path(package_dir),
                 )
index be323ec7809884dc3cf78d63b916da4c15dfe438..7a42d9a9e958757083e9a83e3f7aab2cc00f879a 100644 (file)
@@ -67,6 +67,7 @@ def extract_tar(
     dst: Path,
     *,
     log: bool = True,
+    dirs: Sequence[PathString] = (),
     options: Sequence[PathString] = (),
     sandbox: SandboxProtocol = nosandbox,
 ) -> None:
@@ -93,6 +94,7 @@ def extract_tar(
             "--force-local",
             *tar_exclude_apivfs_tmp(),
             *options,
+            *dirs,
         ],
         sandbox=sandbox(
             # Make sure tar uses user/group information from the root directory instead of the host.
index 525a73a353780b3f0f6f6a3fc1e60e086c087613..afecd05e9b2a853fd4711bc0b0e877832d1f550b 100644 (file)
@@ -902,26 +902,25 @@ def config_default_tools_tree_distribution(namespace: argparse.Namespace) -> Dis
 
 
 def config_default_repository_key_fetch(namespace: argparse.Namespace) -> bool:
+    def needs_repository_key_fetch(distribution: Distribution) -> bool:
+        return distribution == Distribution.arch or distribution.is_rpm_distribution()
+
     if detect_distribution()[0] != Distribution.ubuntu:
         return False
 
     if namespace.tools_tree is None:
-        return cast(bool, namespace.distribution.is_rpm_distribution())
+        return needs_repository_key_fetch(namespace.distribution)
 
     if namespace.tools_tree != Path("default"):
         return (
             detect_distribution(namespace.tools_tree)[0] == Distribution.ubuntu
-            and namespace.distribution.is_rpm_distribution()
-        )
+            and needs_repository_key_fetch(namespace.distribution)
+        )  # fmt: skip
 
-    return cast(
-        bool,
-        (
-            namespace.tools_tree_distribution == Distribution.ubuntu
-            and namespace.distribution.is_rpm_distribution()
-        )
-        or namespace.tools_tree_distribution.is_rpm_distribution(),
-    )
+    return (
+        namespace.tools_tree_distribution == Distribution.ubuntu
+        and needs_repository_key_fetch(namespace.distribution)
+    ) or needs_repository_key_fetch(namespace.tools_tree_distribution)
 
 
 def config_default_source_date_epoch(namespace: argparse.Namespace) -> Optional[int]:
index 2f8d32374ecfd8ca22d68d3741a27f4f14f24fc6..109cdc5564d292917ded9e30e659b712b85c1eea 100644 (file)
@@ -19,6 +19,7 @@ class Context:
         *,
         workspace: Path,
         resources: Path,
+        keyring_dir: Path,
         metadata_dir: Path,
         package_dir: Optional[Path] = None,
     ) -> None:
@@ -26,6 +27,7 @@ class Context:
         self.config = config
         self.workspace = workspace
         self.resources = resources
+        self.keyring_dir = keyring_dir
         self.metadata_dir = metadata_dir
         self.package_dir = package_dir or (self.workspace / "packages")
 
index 4d7bb4b3c7c9737c1b30a2e0c034e6a8b9295203..0800bafc0e80c26b1180eeea56d3430050b315d8 100644 (file)
@@ -31,6 +31,10 @@ class DistributionInstaller:
     def package_manager(cls, config: "Config") -> type["PackageManager"]:
         raise NotImplementedError
 
+    @classmethod
+    def keyring(cls, context: "Context") -> None:
+        pass
+
     @classmethod
     def setup(cls, context: "Context") -> None:
         raise NotImplementedError
@@ -123,6 +127,9 @@ class Distribution(StrEnum):
     def package_manager(self, config: "Config") -> type["PackageManager"]:
         return self.installer().package_manager(config)
 
+    def keyring(self, context: "Context") -> None:
+        return self.installer().keyring(context)
+
     def setup(self, context: "Context") -> None:
         return self.installer().setup(context)
 
index 86711f88b79c230ca1d8962855de0c90c167eeef..cd9a81f50a14b734fad5e6fae9f67045d49ba094 100644 (file)
@@ -1,13 +1,17 @@
 # SPDX-License-Identifier: LGPL-2.1-or-later
 
+import tempfile
 from collections.abc import Iterable, Sequence
+from pathlib import Path
 
+from mkosi.archive import extract_tar
 from mkosi.config import Architecture, Config
 from mkosi.context import Context
+from mkosi.curl import curl
 from mkosi.distributions import DistributionInstaller, PackageType
 from mkosi.installer import PackageManager
 from mkosi.installer.pacman import Pacman, PacmanRepository
-from mkosi.log import die
+from mkosi.log import complete_step, die
 from mkosi.util import sort_packages
 
 
@@ -32,6 +36,27 @@ class Installer(DistributionInstaller):
     def package_manager(cls, config: "Config") -> type[PackageManager]:
         return Pacman
 
+    @classmethod
+    def keyring(cls, context: Context) -> None:
+        if context.config.repository_key_fetch:
+            with (
+                complete_step(f"Downloading {cls.pretty_name()} keyring"),
+                tempfile.TemporaryDirectory() as d,
+            ):
+                curl(
+                    context.config,
+                    "https://archlinux.org/packages/core/any/archlinux-keyring/download",
+                    Path(d),
+                )
+                extract_tar(
+                    next(Path(d).iterdir()),
+                    context.sandbox_tree,
+                    dirs=["usr/share/pacman/keyrings"],
+                    sandbox=context.sandbox,
+                )
+
+        Pacman.keyring(context)
+
     @classmethod
     def setup(cls, context: Context) -> None:
         Pacman.setup(context, list(cls.repositories(context)))
index 5f78a07ee67534bc29975c39262fc5d8d359bdb1..18352c6c39da9e7d55d1bab8cd7d1766a6d92ea2 100644 (file)
@@ -109,6 +109,8 @@ class PackageManager:
             "--suppress-chown",
             # Make sure /etc/machine-id is not overwritten by any package manager post install scripts.
             "--ro-bind-try", Path(root) / "etc/machine-id", "/buildroot/etc/machine-id",
+            # Nudge gpg to create its sockets in /run by making sure /run/user/0 exists.
+            "--dir", "/run/user/0",
             # Some package managers (e.g. dpkg) read from the host's /etc/passwd instead of the buildroot's
             # /etc/passwd so we symlink /etc/passwd from the buildroot to make sure it gets used.
             *(finalize_passwd_symlinks("/buildroot") if apivfs else []),
index e8b7b81afd6d53f74de88edf2d862fd8276002c7..56e354f425e86f6e25a94c1fb830f1e3ab71b75b 100644 (file)
@@ -4,13 +4,17 @@ import dataclasses
 import shutil
 import textwrap
 from collections.abc import Sequence
+from contextlib import AbstractContextManager
 from pathlib import Path
 
 from mkosi.config import Config
 from mkosi.context import Context
+from mkosi.distributions import detect_distribution
 from mkosi.installer import PackageManager
+from mkosi.log import complete_step
 from mkosi.run import run, workdir
 from mkosi.sandbox import umask
+from mkosi.tree import copy_tree
 from mkosi.types import _FILE, CompletedProcess, PathString
 from mkosi.versioncomp import GenericVersion
 
@@ -55,6 +59,7 @@ class Pacman(PackageManager):
             # 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.repository, "/var/cache/pacman/mkosi",
+            "--ro-bind", context.keyring_dir, "/etc/pacman.d/gnupg",
         ]  # fmt: skip
 
         if (context.root / "var/lib/pacman/local").exists():
@@ -175,6 +180,36 @@ class Pacman(PackageManager):
             stdout=stdout,
         )
 
+    @classmethod
+    def keyring(cls, context: Context) -> None:
+        def sandbox() -> AbstractContextManager[list[PathString]]:
+            return cls.sandbox(
+                context,
+                apivfs=False,
+                # By default the keyring is mounted read-only so we override the read-only mount with a
+                # writable mount to make it writable for the following pacman-key commands.
+                options=["--bind", context.keyring_dir, "/etc/pacman.d/gnupg"],
+            )
+
+        if (
+            (d := detect_distribution(context.config.tools())[0])
+            and d.is_apt_distribution()
+            and (context.sandbox_tree / "usr/share/pacman/keyrings").exists()
+        ):
+            # pacman on Debian/Ubuntu looks for keyrings in /usr/share/keyrings so make sure all sandbox
+            # trees keyrings are available in that location as well.
+            (context.sandbox_tree / "usr/share").mkdir(parents=True, exist_ok=True)
+            copy_tree(
+                context.sandbox_tree / "usr/share/pacman/keyrings",
+                context.sandbox_tree / "usr/share/keyrings",
+                dereference=True,
+                sandbox=context.sandbox,
+            )
+
+        with complete_step("Populating pacman keyring"):
+            run(["pacman-key", "--init"], sandbox=sandbox())
+            run(["pacman-key", "--populate"], sandbox=sandbox())
+
     @classmethod
     def sync(cls, context: Context, force: bool) -> None:
         cls.invoke(context, "--sync", ["--refresh", *(["--refresh"] if force else [])])
index a04d7f4b9b16c25c29db5154002e28726c0171d0..78ab8fc62e90686da97550dec050722ec4f83a24 100644 (file)
@@ -103,11 +103,6 @@ def finalize_crypto_mounts(config: Config, relaxed: bool = False) -> list[PathSt
         ]
 
     if not relaxed or config.tools() != Path("/"):
-        # This contains the Arch Linux keyring, which isn't certificates so ToolsTreeCertificates= doesn't
-        # apply.
-        if (config.tools() / "etc/pacman.d/gnupg").exists():
-            mounts += [(config.tools() / "etc/pacman.d/gnupg", Path("/etc/pacman.d/gnupg"))]
-
         if (config.tools() / "etc/crypto-policies").exists():
             mounts += [(config.tools() / "etc/crypto-policies", Path("/etc/crypto-policies"))]
 
index b3edb7dff7e91e7c39baa8fe93f64d21adb79d98..f2bd013bb0cc991a1924fd981ebe8009acc1e72b 100644 (file)
@@ -492,12 +492,12 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
 `RepositoryKeyFetch=`, `--repository-key-fetch=`
 :   Controls whether **mkosi** will fetch distribution GPG keys remotely. Enabled by
     default on Ubuntu when not using a tools tree or when using Ubuntu tools trees to build
-    RPM-based distributions. Disabled by default on all other distributions. When disabled,
-    the distribution GPG keys for the target distribution have to be installed locally on the
-    host system alongside the package manager for that distribution.
+    Arch Linux or RPM-based distributions. Disabled by default on all other distributions.
+    When disabled, the distribution GPG keys for the target distribution have to be installed
+    locally on the host system alongside the package manager for that distribution.
 
-    This setting is only implemented for distributions using **dnf** or **zypper** as their
-    package manager. For other distributions the distribution GPG keys are always looked
+    This setting is only implemented for distributions using **dnf**, **pacman** or **zypper**
+    as their package manager. For other distributions the distribution GPG keys are always looked
     up locally regardless of the value of this setting. To make the distribution GPG keys
     for distributions available without enabling this setting, the corresponding package
     has to be installed on the host. This is usually one of `archlinux-keyring`,
diff --git a/mkosi/resources/mkosi-tools/mkosi.prepare.chroot b/mkosi/resources/mkosi-tools/mkosi.prepare.chroot
deleted file mode 100755 (executable)
index 1749c09..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/sh
-# SPDX-License-Identifier: LGPL-2.1-or-later
-set -e
-
-# Users might set $CONFIG but that influences pacman
-# so we unset it here to avoid any issues.
-unset CONFIG
-
-if [ "$1" = "final" ] && command -v pacman-key; then
-    pacman-key --init
-    pacman-key --populate archlinux
-fi