]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Only write repositories to image on Debian/Ubuntu if apt was installed
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Sat, 15 Apr 2023 19:03:18 +0000 (21:03 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Mon, 17 Apr 2023 09:27:33 +0000 (11:27 +0200)
For other distributions, we don't install the repositories at all but
because Debian and Ubuntu do not have an easy way to install the
repositories via a package we give them some leeway and install the
repositories if apt was installed.

We also rework repository handling to accomodate this. When running
apt ourselves on the host, we now use the apt directory in the
workspace as the config directory for apt instead of using etc/apt
in the image.

Additionally, we also use the keyring from the host instead of the
one from the image when running apt.

Finally, we switch to the http mirror for debian security updates
to avoid having to install ca-certificates in the image for apt
to work properly.

NEWS.md
action.yaml
mkosi/distributions/debian.py
mkosi/distributions/ubuntu.py

diff --git a/NEWS.md b/NEWS.md
index ffe9a38c4a01aad1898b4834e6d03e382ff852dc..e0166786a9f97be7e3c4cdfc0d9e5c07580dd4e5 100644 (file)
--- a/NEWS.md
+++ b/NEWS.md
@@ -73,6 +73,7 @@
 - The RPM db is no longer rebuilt in bdb format on CentOS Stream 8. To be able to install packages on a
   CentOS Stream 8 image with a RPM db in sqlite format, rewrite the db in bdb format using
   `rpm --rebuilddb --define _db_backend bdb`.
+- Repositories are now only written to /etc/apt/sources.list if apt is installed in the image.
 
 ## v14
 
index cc36e9f5e6bedf8ab399394c1e7c8b4fbe60e3b0..a48556c90afd22f9861bc4a04fd7b73af2ba525d 100644 (file)
@@ -17,6 +17,7 @@ runs:
         dnf \
         pacman-package-manager \
         archlinux-keyring \
+        debian-archive-keyring \
         makepkg \
         systemd-container \
         qemu-system-x86 \
index 9c33ddece775275473a58f0d1c32e8513bec6f7b..18565fb5fef79595cced67015c25e09f4ec55177 100644 (file)
@@ -28,6 +28,28 @@ class DebianInstaller(DistributionInstaller):
     def initrd_path(kver: str) -> Path:
         return Path("boot") / f"initrd.img-{kver}"
 
+    @staticmethod
+    def repositories(state: MkosiState, local: bool = True) -> list[str]:
+        repos = ' '.join(("main", *state.config.repositories))
+
+        if state.config.local_mirror and local:
+            return [f"deb [trusted=yes] {state.config.local_mirror} {state.config.release} {repos}"]
+
+        main = f"deb {state.config.mirror} {state.config.release} {repos}"
+
+        if state.config.release in ("unstable", "sid"):
+            return [main]
+
+        updates = f"deb {state.config.mirror} {state.config.release}-updates {repos}"
+
+        # Security updates repos are never mirrored
+        if state.config.release in ("stretch", "buster"):
+            security = f"deb http://security.debian.org/debian-security {state.config.release}/updates {repos}"
+        else:
+            security = f"deb http://security.debian.org/debian-security {state.config.release}-security {repos}"
+
+        return [main, updates, security]
+
     @classmethod
     def install(cls, state: MkosiState) -> None:
         repos = {"main", *state.config.repositories}
@@ -64,12 +86,6 @@ class DebianInstaller(DistributionInstaller):
             if "VERSION_ID" not in os_release and "BUILD_ID" not in os_release:
                 f.write(f"BUILD_ID=mkosi-{state.config.release}\n")
 
-        if not state.config.local_mirror:
-            cls._add_apt_auxiliary_repos(state, repos)
-        else:
-            # Add a single local offline repository, and then remove it after apt has ran
-            state.root.joinpath("etc/apt/sources.list.d/mirror.list").write_text(f"deb [trusted=yes] {state.config.local_mirror} {state.config.release} main\n")
-
         install_skeleton_trees(state, False, late=True)
 
         cls.install_packages(state, ["base-passwd"])
@@ -77,13 +93,6 @@ class DebianInstaller(DistributionInstaller):
         # Ensure /efi exists so that the ESP is mounted there, and we never run dpkg -i on vfat
         state.root.joinpath("efi").mkdir(mode=0o755, exist_ok=True)
 
-        # Now clean up and add the real repositories, so that the image is ready
-        if state.config.local_mirror:
-            main_repo = f"deb {state.config.mirror} {state.config.release} {' '.join(repos)}\n"
-            state.root.joinpath("etc/apt/sources.list").write_text(main_repo)
-            state.root.joinpath("etc/apt/sources.list.d/mirror.list").unlink()
-            cls._add_apt_auxiliary_repos(state, repos)
-
         # Make sure preset doesn't touch services unless explicitly configured.
         presetdir = state.root / "etc/systemd/system-preset"
         presetdir.mkdir(exist_ok=True, mode=0o755)
@@ -100,26 +109,17 @@ class DebianInstaller(DistributionInstaller):
         policyrcd.write_text("#!/bin/sh\nexit 101\n")
         policyrcd.chmod(0o755)
 
+        setup_apt(state, cls.repositories(state))
         invoke_apt(state, "get", "update")
         invoke_apt(state, "get", "install", packages)
 
         policyrcd.unlink()
 
-    @classmethod
-    def _add_apt_auxiliary_repos(cls, state: MkosiState, repos: set[str]) -> None:
-        if state.config.release in ("unstable", "sid"):
-            return
-
-        updates = f"deb {state.config.mirror} {state.config.release}-updates {' '.join(repos)}"
-        state.root.joinpath(f"etc/apt/sources.list.d/{state.config.release}-updates.list").write_text(f"{updates}\n")
-
-        # Security updates repos are never mirrored
-        if state.config.release in ("stretch", "buster"):
-            security = f"deb http://security.debian.org/debian-security/ {state.config.release}/updates main"
-        else:
-            security = f"deb https://security.debian.org/debian-security {state.config.release}-security main"
-
-        state.root.joinpath(f"etc/apt/sources.list.d/{state.config.release}-security.list").write_text(f"{security}\n")
+        sources = state.root / "etc/apt/sources.list"
+        if not sources.exists() and state.root.joinpath("usr/bin/apt").exists():
+            with sources.open("w") as f:
+                for repo in cls.repositories(state, local=False):
+                    f.write(f"{repo}\n")
 
     @classmethod
     def remove_packages(cls, state: MkosiState, packages: Sequence[str]) -> None:
@@ -151,24 +151,20 @@ def debootstrap_knows_arg(arg: str) -> bool:
                                                        stdout=subprocess.PIPE, check=False).stdout
 
 
-def invoke_apt(
-    state: MkosiState,
-    subcommand: str,
-    operation: str,
-    extra: Sequence[str] = tuple(),
-) -> CompletedProcess:
-
+def setup_apt(state: MkosiState, repos: Sequence[str]) -> None:
     state.workspace.joinpath("apt").mkdir(exist_ok=True)
+    state.workspace.joinpath("apt/apt.conf.d").mkdir(exist_ok=True)
+    state.workspace.joinpath("apt/preferences.d").mkdir(exist_ok=True)
     state.workspace.joinpath("apt/log").mkdir(exist_ok=True)
     state.root.joinpath("var").mkdir(mode=0o755, exist_ok=True)
     state.root.joinpath("var/lib").mkdir(mode=0o755, exist_ok=True)
     state.root.joinpath("var/lib/dpkg").mkdir(mode=0o755, exist_ok=True)
     state.root.joinpath("var/lib/dpkg/status").touch()
 
-    config_file = state.workspace / "apt/apt.conf"
+    config = state.workspace / "apt/apt.conf"
     debarch = DEBIAN_ARCHITECTURES[state.config.architecture]
 
-    config_file.write_text(
+    config.write_text(
         dedent(
             f"""\
             APT::Architecture "{debarch}";
@@ -179,7 +175,9 @@ def invoke_apt(
             Dir::Cache "{state.cache}";
             Dir::State "{state.workspace / "apt"}";
             Dir::State::status "{state.root / "var/lib/dpkg/status"}";
-            Dir::Etc "{state.root / "etc/apt"}";
+            Dir::Etc "{state.workspace / "apt"}";
+            Dir::Etc::trusted "/usr/share/keyrings/{state.config.release}-archive-keyring";
+            Dir::Etc::trustedparts "/usr/share/keyrings";
             Dir::Log "{state.workspace / "apt/log"}";
             Dir::Bin::dpkg "dpkg";
             DPkg::Path "{os.environ["PATH"]}";
@@ -191,13 +189,24 @@ def invoke_apt(
         )
     )
 
+    with state.workspace.joinpath("apt/sources.list").open("w") as f:
+        for repo in repos:
+            f.write(f"{repo}\n")
+
+
+def invoke_apt(
+    state: MkosiState,
+    subcommand: str,
+    operation: str,
+    extra: Sequence[str] = tuple(),
+) -> CompletedProcess:
     cmdline = [
         f"/usr/bin/apt-{subcommand}",
         operation,
         *extra,
     ]
     env: dict[str, PathString] = dict(
-        APT_CONFIG=config_file,
+        APT_CONFIG=state.workspace / "apt/apt.conf",
         DEBIAN_FRONTEND="noninteractive",
         DEBCONF_INTERACTIVE_SEEN="true",
         KERNEL_INSTALL_BYPASS="1",
index 0cb89573246c56dd34460db8f85460b2a4f309e7..594cba0d11aac5ac43701abaacf55fb9a8471d8a 100644 (file)
@@ -5,18 +5,20 @@ from mkosi.distributions.debian import DebianInstaller
 
 
 class UbuntuInstaller(DebianInstaller):
-    @classmethod
-    def _add_apt_auxiliary_repos(cls, state: MkosiState, repos: set[str]) -> None:
-        if state.config.release in ("unstable", "sid"):
-            return
+    @staticmethod
+    def repositories(state: MkosiState, local: bool = True) -> list[str]:
+        repos = ' '.join(("main", *state.config.repositories))
 
-        updates = f"deb {state.config.mirror} {state.config.release}-updates {' '.join(repos)}"
-        state.root.joinpath(f"etc/apt/sources.list.d/{state.config.release}-updates.list").write_text(f"{updates}\n")
+        if state.config.local_mirror and local:
+            return [f"deb [trusted=yes] {state.config.local_mirror} {state.config.release} {repos}"]
+
+        main = f"deb {state.config.mirror} {state.config.release} {repos}"
+        updates = f"deb {state.config.mirror} {state.config.release}-updates {repos}"
 
         # Security updates repos are never mirrored. But !x86 are on the ports server.
         if state.config.architecture in ["x86", "x86_64"]:
-            security = f"deb http://security.ubuntu.com/ubuntu/ {state.config.release}-security {' '.join(repos)}"
+            security = f"deb http://security.ubuntu.com/ubuntu/ {state.config.release}-security {repos}"
         else:
-            security = f"deb http://ports.ubuntu.com/ {state.config.release}-security {' '.join(repos)}"
+            security = f"deb http://ports.ubuntu.com/ {state.config.release}-security {repos}"
 
-        state.root.joinpath(f"etc/apt/sources.list.d/{state.config.release}-security.list").write_text(f"{security}\n")
+        return [main, updates, security]