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
- 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
- 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
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: |
--- /dev/null
+# 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=<path-to-initrd-image> ...
+```
+This will build an image using the provided initrd image.
+mkosi will add the kernel modules found in the distribution image to this initrd.
+
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`
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"
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.
"--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)
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(
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))
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:
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
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]
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)
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)
debug: list[str]
auto_bump: bool
workspace_dir: Optional[Path]
+ initrds: list[Path]
# QEMU-specific options
qemu_headless: bool
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 = {
"-Sy", *sort_packages(packages),
]
+ if state.config.initrds:
+ cmdline += ["--assume-installed", "initramfs"]
+
run_with_apivfs(state, cmdline, env=dict(KERNEL_INSTALL_BYPASS="1"))
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")
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:
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)
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])
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")
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(
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:
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")
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')