From: Daan De Meyer Date: Sat, 15 Apr 2023 19:03:18 +0000 (+0200) Subject: Only write repositories to image on Debian/Ubuntu if apt was installed X-Git-Tag: v15~246^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e49d645e4927c52a2a581d7e3d4bde399a1b34d4;p=thirdparty%2Fmkosi.git Only write repositories to image on Debian/Ubuntu if apt was installed 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. --- diff --git a/NEWS.md b/NEWS.md index ffe9a38c4..e0166786a 100644 --- 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 diff --git a/action.yaml b/action.yaml index cc36e9f5e..a48556c90 100644 --- a/action.yaml +++ b/action.yaml @@ -17,6 +17,7 @@ runs: dnf \ pacman-package-manager \ archlinux-keyring \ + debian-archive-keyring \ makepkg \ systemd-container \ qemu-system-x86 \ diff --git a/mkosi/distributions/debian.py b/mkosi/distributions/debian.py index 9c33ddece..18565fb5f 100644 --- a/mkosi/distributions/debian.py +++ b/mkosi/distributions/debian.py @@ -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", diff --git a/mkosi/distributions/ubuntu.py b/mkosi/distributions/ubuntu.py index 0cb895732..594cba0d1 100644 --- a/mkosi/distributions/ubuntu.py +++ b/mkosi/distributions/ubuntu.py @@ -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]