From: Georges Discry Date: Sun, 23 Apr 2023 10:27:01 +0000 (+0200) Subject: Enable --repo-dir for Debian and Ubuntu X-Git-Tag: v15~201 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=99551e506d9e715b0ac28fce6d3a0b0545f8b048;p=thirdparty%2Fmkosi.git Enable --repo-dir for Debian and Ubuntu 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. --- diff --git a/mkosi.md b/mkosi.md index 90037e91a..899a98ea1 100644 --- 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=` diff --git a/mkosi/config.py b/mkosi/config.py index 4fcf16600..6f223c79a 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -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)) - diff --git a/mkosi/distributions/debian.py b/mkosi/distributions/debian.py index 6a6d104a1..f33924714 100644 --- a/mkosi/distributions/debian.py +++ b/mkosi/distributions/debian.py @@ -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) diff --git a/mkosi/install.py b/mkosi/install.py index 4b02e0193..11d128a68 100644 --- a/mkosi/install.py +++ b/mkosi/install.py @@ -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", diff --git a/mkosi/util.py b/mkosi/util.py index b19b28ef9..5caeb6644 100644 --- a/mkosi/util.py +++ b/mkosi/util.py @@ -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"