From: Daan De Meyer Date: Wed, 15 Feb 2023 17:09:29 +0000 (+0100) Subject: Add --initrd option X-Git-Tag: v15~303 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d0ac2eb9bcb5f94f10dab50ca2f3a4a082f8b8c0;p=thirdparty%2Fmkosi.git Add --initrd option --initrd allows users to provide their own initrds. When used, we'll automatically create another initrd per kernel containing just the kernel modules and all the initrds to ukify. We don't compress the kernel modules initrd as the kernel modules should be compressed themselves already. --- diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43021c69e..d635856b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,7 +100,7 @@ jobs: runs-on: ubuntu-22.04 needs: unit-test concurrency: - group: ${{ github.workflow }}-${{ matrix.distro }}-${{ matrix.format }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ matrix.distro }}-${{ matrix.format }}-${{ matrix.prebuilt-initrd }}-${{ github.ref }} cancel-in-progress: true strategy: fail-fast: false @@ -120,6 +120,14 @@ jobs: - tar - cpio - disk + prebuilt-initrd: + - no + - yes + exclude: + # Debian prebuilt initrds are so large they cause OOMs. + # TODO: Revisit this if Debian ever decides to compress their kernel modules. + - distro: debian + prebuilt-initrd: yes steps: - uses: actions/checkout@v3 @@ -162,6 +170,10 @@ jobs: - name: Install run: sudo python3 -m pip install . + - name: Build ${{ matrix.distro }} initrd + if: matrix.prebuilt-initrd == 'yes' + run: python3 -m mkosi -d ${{ matrix.distro }} -t cpio -p systemd -p udev -p kmod -o $PWD/initrd build + - name: Configure ${{ matrix.distro }}/${{ matrix.format }} run: | mkdir -p mkosi.conf.d @@ -193,6 +205,14 @@ jobs: sync-uri = https://raw.githubusercontent.com/257/binpkgs/master EOF + - name: Configure ${{ matrix.distro }}/${{ matrix.format }} initrd + if: matrix.prebuilt-initrd == 'yes' + run: | + tee mkosi.conf.d/initrd.conf <<- EOF + [Distribution] + Initrd=initrd.zst + EOF + - name: Configure EPEL if: matrix.distro == 'centos' || matrix.distro == 'rocky' || matrix.distro == 'alma' run: | diff --git a/docs/initrd.md b/docs/initrd.md new file mode 100644 index 000000000..b83a1c5e5 --- /dev/null +++ b/docs/initrd.md @@ -0,0 +1,20 @@ +# Building a custom initrd and using it in a mkosi image + +Building an image with a mkosi-built initrd is a two step process, because you will build two images - the initrd and your distribution image. +1. Build an initrd image using the `cpio` output format with the same target distribtution as you want to use for your distribution image. mkosi compresses the `cpio` output format by default. +``` +[Output] +Format=cpio + +[Content] +Packages=systemd + udev + kmod +``` +2. Invoke `mkosi` passing the initrd image via the `--initrd` option or add the `Initrd=` option to your mkosi config when building your distribution image. +```bash +mkosi --initrd= ... +``` +This will build an image using the provided initrd image. +mkosi will add the kernel modules found in the distribution image to this initrd. + diff --git a/mkosi.md b/mkosi.md index 3d54fc677..886a23aac 100644 --- a/mkosi.md +++ b/mkosi.md @@ -453,6 +453,12 @@ a boolean argument: either "1", "yes", or "true" to enable, or "0", in building or further container import stages. This option strips SELinux context attributes from the resulting tar archive. +`Initrd=`, `--initrd` + +: Use user-provided initrd(s). Takes a comma separated list of paths to initrd + files. This option may be used multiple times in which case the initrd lists + are combined. + ### [Content] Section `BasePackages=`, `--base-packages` diff --git a/mkosi/__init__.py b/mkosi/__init__.py index 4b0d307e9..5c804e3ab 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -205,7 +205,7 @@ def configure_hostname(state: MkosiState, cached: bool) -> None: def configure_dracut(state: MkosiState, cached: bool) -> None: - if not state.config.bootable or cached: + if not state.config.bootable or cached or state.config.initrds: return dracut_dir = state.root / "etc/dracut.conf.d" @@ -790,6 +790,22 @@ def gen_kernel_images(state: MkosiState) -> Iterator[tuple[str, Path]]: yield kver.name, kimg +def gen_kernel_modules_initrd(state: MkosiState, kver: str) -> Path: + kmods = state.workspace / f"initramfs-kernel-modules-{kver}.img" + + def files() -> Iterator[Path]: + yield state.root.joinpath("usr/lib/modules").relative_to(state.root) + yield state.root.joinpath("usr/lib/modules").joinpath(kver).relative_to(state.root) + for p in find_files(state.root / "usr/lib/modules" / kver, state.root): + if p.name != "vmlinuz": + yield p + + with complete_step(f"Generating kernel modules initrd for kernel {kver}"): + make_cpio(state.root, files(), kmods) + + return kmods + + def install_unified_kernel(state: MkosiState, roothash: Optional[str]) -> None: # Iterates through all kernel versions included in the image and generates a combined # kernel+initrd+cmdline+osrelease EFI file from it and places it in the /EFI/Linux directory of the ESP. @@ -872,11 +888,16 @@ def install_unified_kernel(state: MkosiState, roothash: Optional[str]) -> None: "--pcr-banks", "sha1,sha256" ] - initrd = state.root.joinpath(state.installer.initrd_path(kver)) - if not initrd.exists(): - die(f"Initrd not found at {initrd}") + if state.config.initrds: + initrds = state.config.initrds + [gen_kernel_modules_initrd(state, kver)] + else: + initrd = state.root / state.installer.initrd_path(kver) + if not initrd.exists(): + die(f"Initrd not found at {initrd}") + + initrds = [initrd] - cmd += [state.root / kimg, initrd] + cmd += [state.root / kimg] + initrds run(cmd) @@ -1633,6 +1654,15 @@ def create_parser() -> ArgumentParserMkosi: dest="repart_dir", help="Directory containing systemd-repart partition definitions", ) + group.add_argument( + "--initrd", + dest="initrds", + action=CommaDelimitedListAction, + default=[], + help="Add a user-provided initrd to image", + type=Path, + metavar="PATH", + ) group = parser.add_argument_group("Content options") group.add_argument( @@ -2574,6 +2604,14 @@ def load_args(args: argparse.Namespace) -> MkosiConfig: if args.repositories and not is_dnf_distribution(args.distribution) and args.distribution not in (Distribution.debian, Distribution.ubuntu): die("Sorry, the --repositories option is only supported on DNF/Debian based distributions") + if args.initrds: + args.initrds = [p.absolute() for p in args.initrds] + for p in args.initrds: + if not p.exists(): + die(f"Initrd {p} not found") + if not p.is_file(): + die(f"Initrd {p} is not a file") + return MkosiConfig(**vars(args)) @@ -2707,6 +2745,9 @@ def print_summary(config: MkosiConfig) -> None: if config.repositories is not None and len(config.repositories) > 0: print(" Repositories:", ",".join(config.repositories)) + if config.initrds: + print(" Initrds:", ",".join(os.fspath(p) for p in config.initrds)) + print("\nOUTPUT:") if config.hostname: @@ -2936,6 +2977,20 @@ def configure_netdev(state: MkosiState, cached: bool) -> None: run(["systemctl", "--root", state.root, "enable", "systemd-networkd"]) +def configure_initrd(state: MkosiState) -> None: + if state.for_cache or not state.config.output_format == OutputFormat.cpio: + return + + if not state.root.joinpath("init").exists(): + state.root.joinpath("init").symlink_to("/usr/lib/systemd/systemd") + + if state.root.joinpath("etc/initrd-release").exists(): + return + + state.root.joinpath("etc/os-release").rename(state.root / "etc/initrd-release") + state.root.joinpath("etc/os-release").symlink_to("/etc/initrd-release") + + def run_kernel_install(state: MkosiState, cached: bool) -> None: if not state.config.bootable: return @@ -2954,7 +3009,18 @@ def run_kernel_install(state: MkosiState, cached: bool) -> None: else: machine_id = None - with complete_step("Generating initramfs images…"): + # Fedora unconditionally pulls in dracut when installing a kernel and upstream dracut refuses to skip + # running when implement KERNEL_INSTALL_INITRD_GENERATOR support so let's disable it manually here if the + # user provided initrds. + + if state.config.initrds: + for p in state.root.joinpath("usr/lib/kernel/install.d").iterdir(): + if "dracut" in p.name: + install = state.root.joinpath("etc/kernel/install.d") + install.mkdir(parents=True, exist_ok=True) + install.joinpath(p.name).symlink_to("/dev/null") + + with complete_step("Running kernel-install…"): for kver, kimg in gen_kernel_images(state): cmd: list[PathString] = ["kernel-install", "add", kver, Path("/") / kimg] @@ -2966,6 +3032,11 @@ def run_kernel_install(state: MkosiState, cached: bool) -> None: if machine_id and (p := state.root / "boot" / machine_id / kver / "initrd").exists(): shutil.move(p, state.root / state.installer.initrd_path(kver)) + if state.config.initrds: + for p in state.root.joinpath("etc/kernel/install.d").iterdir(): + if "dracut" in p.name: + os.unlink(p) + if machine_id and (p := state.root / "boot" / machine_id).exists(): shutil.rmtree(p) @@ -3116,6 +3187,7 @@ def build_image(state: MkosiState, *, manifest: Optional[Manifest] = None) -> No configure_autologin(state, cached) configure_dracut(state, cached) configure_netdev(state, cached) + configure_initrd(state) run_prepare_script(state, cached, build=False) install_build_packages(state, cached) run_prepare_script(state, cached, build=True) diff --git a/mkosi/backend.py b/mkosi/backend.py index b42c17d44..e44de931d 100644 --- a/mkosi/backend.py +++ b/mkosi/backend.py @@ -309,6 +309,7 @@ class MkosiConfig: debug: list[str] auto_bump: bool workspace_dir: Optional[Path] + initrds: list[Path] # QEMU-specific options qemu_headless: bool diff --git a/mkosi/distributions/arch.py b/mkosi/distributions/arch.py index 1e00e031f..867a73e87 100644 --- a/mkosi/distributions/arch.py +++ b/mkosi/distributions/arch.py @@ -93,7 +93,7 @@ def install_arch(state: MkosiState) -> None: packages = state.config.packages.copy() add_packages(state.config, packages, "base") - if state.config.bootable: + if state.config.bootable and not state.config.initrds: add_packages(state.config, packages, "dracut") official_kernel_packages = { @@ -128,4 +128,7 @@ def invoke_pacman(state: MkosiState, packages: Sequence[str]) -> None: "-Sy", *sort_packages(packages), ] + if state.config.initrds: + cmdline += ["--assume-installed", "initramfs"] + run_with_apivfs(state, cmdline, env=dict(KERNEL_INSTALL_BYPASS="1")) diff --git a/mkosi/distributions/centos.py b/mkosi/distributions/centos.py index 99952afbc..e821dc17f 100644 --- a/mkosi/distributions/centos.py +++ b/mkosi/distributions/centos.py @@ -72,7 +72,9 @@ class CentosInstaller(DistributionInstaller): packages = state.config.packages.copy() add_packages(state.config, packages, "systemd", "rpm") if state.config.bootable: - add_packages(state.config, packages, "kernel", "dracut", "dracut-config-generic") + add_packages(state.config, packages, "kernel") + if not state.config.initrds: + add_packages(state.config, packages, "dracut", "dracut-config-generic") add_packages(state.config, packages, "systemd-udev", conditional="systemd") if state.config.ssh: add_packages(state.config, packages, "openssh-server") diff --git a/mkosi/distributions/debian.py b/mkosi/distributions/debian.py index 4e52eca5c..800b40694 100644 --- a/mkosi/distributions/debian.py +++ b/mkosi/distributions/debian.py @@ -97,7 +97,8 @@ class DebianInstaller(DistributionInstaller): add_packages(state.config, packages, "systemd", "systemd-sysv", "dbus", "libpam-systemd") if state.config.bootable: - add_packages(state.config, packages, "dracut", "dracut-config-generic") + if not state.config.initrds: + add_packages(state.config, packages, "dracut", "dracut-config-generic") cls._add_default_kernel_package(state, packages) if state.config.ssh: @@ -182,9 +183,6 @@ class DebianInstaller(DistributionInstaller): cls._fixup_resolved(state, packages) - write_resource(state.root / "etc/kernel/install.d/50-mkosi-dpkg-reconfigure-dracut.install", - "mkosi.resources", "dpkg-reconfigure-dracut.install", executable=True) - # Debian/Ubuntu use a different path to store the locale so let's make sure that path is a symlink to # etc/locale.conf. state.root.joinpath("etc/default/locale").unlink(missing_ok=True) @@ -195,6 +193,10 @@ class DebianInstaller(DistributionInstaller): presetdir.mkdir(exist_ok=True, mode=0o755) presetdir.joinpath("99-mkosi-disable.preset").write_text("disable *") + if state.config.bootable and not state.config.initrds: + write_resource(state.root / "etc/kernel/install.d/50-mkosi-dpkg-reconfigure-dracut.install", + "mkosi.resources", "dpkg-reconfigure-dracut.install", executable=True) + @classmethod def install_packages(cls, state: MkosiState, packages: Sequence[str]) -> None: invoke_apt(state, "get", "install", ["--assume-yes", "--no-install-recommends", *packages]) diff --git a/mkosi/distributions/fedora.py b/mkosi/distributions/fedora.py index 7310d53ea..a303868cc 100644 --- a/mkosi/distributions/fedora.py +++ b/mkosi/distributions/fedora.py @@ -103,9 +103,11 @@ def install_fedora(state: MkosiState) -> None: add_packages(state.config, packages, "systemd", "util-linux", "rpm") if state.config.bootable: - add_packages(state.config, packages, "kernel-core", "kernel-modules", "dracut", "dracut-config-generic") + add_packages(state.config, packages, "kernel-core", "kernel-modules") add_packages(state.config, packages, "systemd-udev", conditional="systemd") - if state.config.netdev: + if not state.config.initrds: + add_packages(state.config, packages, "dracut", "dracut-config-generic") + if state.config.netdev: add_packages(state.config, packages, "systemd-networkd", conditional="systemd") if state.config.ssh: add_packages(state.config, packages, "openssh-server") diff --git a/mkosi/distributions/mageia.py b/mkosi/distributions/mageia.py index b4b51294f..ff02d1926 100644 --- a/mkosi/distributions/mageia.py +++ b/mkosi/distributions/mageia.py @@ -63,8 +63,8 @@ def install_mageia(state: MkosiState) -> None: packages = state.config.packages.copy() add_packages(state.config, packages, "basesystem-minimal", "dnf") - if state.config.bootable: - add_packages(state.config, packages, "kernel-server-latest", "dracut") + if state.config.bootable and not state.config.initrds: + add_packages(state.config, packages, "dracut") # Mageia ships /etc/50-mageia.conf that omits systemd from the initramfs and disables hostonly. # We override that again so our defaults get applied correctly on Mageia as well. state.root.joinpath("etc/dracut.conf.d/51-mkosi-override-mageia.conf").write_text( diff --git a/mkosi/distributions/openmandriva.py b/mkosi/distributions/openmandriva.py index a0854514d..4318f9c3c 100644 --- a/mkosi/distributions/openmandriva.py +++ b/mkosi/distributions/openmandriva.py @@ -67,7 +67,9 @@ def install_openmandriva(state: MkosiState) -> None: add_packages(state.config, packages, "basesystem-minimal", "systemd", "dnf") if state.config.bootable: add_packages(state.config, packages, "systemd-boot", "systemd-cryptsetup", conditional="systemd") - add_packages(state.config, packages, "kernel-release-server", "dracut", "timezone") + add_packages(state.config, packages, "kernel-release-server", "timezone") + if not state.config.initrds: + add_packages(state.config, packages, "dracut") if state.config.netdev: add_packages(state.config, packages, "systemd-networkd", conditional="systemd") if state.config.ssh: diff --git a/mkosi/distributions/opensuse.py b/mkosi/distributions/opensuse.py index 6d2bf6049..54f1cda63 100644 --- a/mkosi/distributions/opensuse.py +++ b/mkosi/distributions/opensuse.py @@ -156,7 +156,9 @@ def install_opensuse(state: MkosiState) -> None: add_packages(state.config, packages, "patterns-base-minimal_base") if state.config.bootable: - add_packages(state.config, packages, "kernel-default", "dracut") + add_packages(state.config, packages, "kernel-default") + if not state.config.initrds: + add_packages(state.config, packages, "dracut") if state.config.netdev: add_packages(state.config, packages, "systemd-network") @@ -198,8 +200,7 @@ def install_opensuse(state: MkosiState) -> None: shutil.copy2(state.root / f"usr/{prefix}/pam.d/login", state.root / "etc/pam.d/login") break - if state.config.bootable: + if state.config.bootable and not state.config.initrds: dracut_dir = state.root / "etc/dracut.conf.d" dracut_dir.mkdir(mode=0o755, exist_ok=True) - dracut_dir.joinpath("30-mkosi-opensuse.conf").write_text('hostonly=no\n')