]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Enable --repo-dir for Debian and Ubuntu
authorGeorges Discry <georges@discry.be>
Sun, 23 Apr 2023 10:27:01 +0000 (12:27 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Tue, 25 Apr 2023 05:32:54 +0000 (07:32 +0200)
Additional repositories can now be configured for Debian and Ubuntu.

Because APT can only be configured with a single path for
`Dir::Etc::sourceparts`, the content of the directories listed in
`repo_dirs` are copied in a `apt/sources.list.d` directory managed by
mkosi outside the image root. Therefore, only one file will be kept if
several files have the same name.

Like the main `sources.list` generated by mkosi, the additional
repositories are copied in the image if APT is installed.

mkosi.md
mkosi/config.py
mkosi/distributions/debian.py
mkosi/install.py
mkosi/util.py

index 90037e91a042323ef07a28dac36de457fc53b6f5..899a98ea1820aa86dcedf253d42b998b89e820b6 100644 (file)
--- a/mkosi.md
+++ b/mkosi.md
@@ -287,11 +287,12 @@ a boolean argument: either "1", "yes", or "true" to enable, or "0",
 
 `RepositoryDirectories`, `--repo-dir=`
 
-: This option can (for now) only be used with RPM-based distributions and Arch
-  Linux. It takes a comma separated list of directories containing extra repository
-  definitions that will be used when installing packages. The files are passed
-  directly to the corresponding package manager and should be written in the format
-  expected by the package manager of the image's distro.
+: This option can (for now) only be used with RPM-based distributions,
+  Debian-based distributions and Arch Linux. It takes a comma separated list of
+  directories containing extra repository definitions that will be used when
+  installing packages. The files are passed directly to the corresponding
+  package manager and should be written in the format expected by the package
+  manager of the image's distro.
 
 `Architecture=`, `--architecture=`
 
index 4fcf16600b2ad5799e9c2d00529b5d2a06d88cc5..6f223c79a4faa889138300bf98f2c48b2ef5175c 100644 (file)
@@ -29,6 +29,7 @@ from mkosi.util import (
     current_user,
     detect_distribution,
     flatten,
+    is_apt_distribution,
     is_dnf_distribution,
     prepend_to_environ_path,
     qemu_check_kvm_support,
@@ -1874,8 +1875,12 @@ def load_args(args: argparse.Namespace) -> MkosiConfig:
         if args.compress_output != Compression.none:
             die(f"Sorry, can't {opname} with a compressed image.")
 
-    if args.repo_dirs and not (is_dnf_distribution(args.distribution) or args.distribution == Distribution.arch):
-        die("--repo-dir is only supported on DNF based distributions and Arch")
+    if args.repo_dirs and not (
+        is_dnf_distribution(args.distribution)
+        or is_apt_distribution(args.distribution)
+        or args.distribution == Distribution.arch
+    ):
+        die("--repo-dir is only supported on DNF/Debian based distributions and Arch")
 
     if args.qemu_kvm is True and not qemu_check_kvm_support():
         die("Sorry, the host machine does not support KVM acceleration.")
@@ -1883,7 +1888,7 @@ def load_args(args: argparse.Namespace) -> MkosiConfig:
     if args.qemu_kvm is None:
         args.qemu_kvm = qemu_check_kvm_support()
 
-    if args.repositories and not is_dnf_distribution(args.distribution) and args.distribution not in (Distribution.debian, Distribution.ubuntu):
+    if args.repositories and not (is_dnf_distribution(args.distribution) or is_apt_distribution(args.distribution)):
         die("Sorry, the --repositories option is only supported on DNF/Debian based distributions")
 
     if args.initrds:
@@ -1903,4 +1908,3 @@ def load_args(args: argparse.Namespace) -> MkosiConfig:
             die("This unprivileged build configuration requires at least Linux v5.11")
 
     return MkosiConfig(**vars(args))
-
index 6a6d104a140815fbf044b42f994c04fd1816fd40..f33924714ecf66f5744e1252137d54d8d4b76d2f 100644 (file)
@@ -126,15 +126,10 @@ class DebianInstaller(DistributionInstaller):
         setup_apt(state, cls.repositories(state))
         invoke_apt(state, "update", apivfs=False)
         invoke_apt(state, "install", packages, apivfs=apivfs)
+        install_apt_sources(state, cls.repositories(state, local=False))
 
         policyrcd.unlink()
 
-        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:
         invoke_apt(state, "purge", packages)
@@ -163,6 +158,7 @@ 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/sources.list.d").mkdir(exist_ok=True)
     state.workspace.joinpath("apt/log").mkdir(exist_ok=True)
 
     # TODO: Drop once apt 2.5.4 is widely available.
@@ -206,6 +202,13 @@ def setup_apt(state: MkosiState, repos: Sequence[str]) -> None:
         for repo in repos:
             f.write(f"{repo}\n")
 
+    for repo_dir in state.config.repo_dirs:
+        for src in repo_dir.iterdir():
+            if not src.is_file():
+                continue
+            if src.suffix in (".list", ".sources"):
+                shutil.copyfile(src, state.workspace.joinpath("apt/sources.list.d", src.name))
+
 
 def invoke_apt(
     state: MkosiState,
@@ -222,3 +225,21 @@ def invoke_apt(
     )
 
     return bwrap(["apt-get", operation, *extra], apivfs=state.root if apivfs else None, env=env | state.environment)
+
+
+def install_apt_sources(state: MkosiState, repos: Sequence[str]) -> None:
+    if not state.root.joinpath("usr/bin/apt").exists():
+        return
+
+    sources = state.root / "etc/apt/sources.list"
+    if not sources.exists():
+        with sources.open("w") as f:
+            for repo in repos:
+                f.write(f"{repo}\n")
+
+    # Already contains a merged tree of repo_dirs after setup_apt
+    for src in state.workspace.joinpath("apt/sources.list.d").iterdir():
+        dst = state.root.joinpath("etc/apt/sources.list.d", src.name)
+        if dst.exists():
+            continue
+        shutil.copyfile(src, dst)
index 4b02e0193b89adba7204766457cc7fc40f1ac29a..11d128a68550155796ab77c00096c9481e92eaa4 100644 (file)
@@ -47,11 +47,17 @@ def flock(path: Path) -> Iterator[Path]:
         os.close(fd)
 
 
-def copy_path(src: Path, dst: Path, preserve_owner: bool = True) -> None:
+def copy_path(
+    src: Path,
+    dst: Path,
+    *,
+    dereference: bool = False,
+    preserve_owner: bool = True,
+) -> None:
     run([
         "cp",
         "--recursive",
-        "--no-dereference",
+        f"--{'' if dereference else 'no-'}dereference",
         f"--preserve=mode,timestamps,links,xattr{',ownership' if preserve_owner else ''}",
         "--no-target-directory",
         "--reflink=auto",
index b19b28ef999ec98246a4e8ab0a92529c17ec0302..5caeb6644921bdfd5c0ca1715db49ad4f6071cf8 100644 (file)
@@ -166,6 +166,10 @@ def is_dnf_distribution(d: Distribution) -> bool:
     )
 
 
+def is_apt_distribution(d: Distribution) -> bool:
+    return d in (Distribution.debian, Distribution.ubuntu)
+
+
 class OutputFormat(str, enum.Enum):
     directory = "directory"
     subvolume = "subvolume"