Markus Wernig

UNIX / Network /
Security Engineer
CCSA, CCSE
CISSP


PGP Schlüsseltransition
GPG Schlüssel
 (nach 9. Aug. 2013)

alter GPG Schlüssel
 (bis 9. Aug. 2013)

Persönlich | Beruflich | Informatik | Schreiberei en

A customized binary Gentoo kernel package

After having set up a build server/binhost for use with my Gentoo desktop machines, there was one piece of software left that still needed to be compiled on the client machines: the kernel.

While Gentoo does have a binary kernel package nowadays, this "distribution kernel" shares one characteristic with the kernels of all other binary GNU/Linux distributions: It is very generic and has almost every possible feature/option enabled. This makes sense, as it must be able to run on any supported hardware, and people should not need to go back to compiling their own kernels as soon as they want to use a more uncommon option or piece of hardware.

But that characteristic (or the lack of it, more precisely) is exactly why I am using Gentoo: because it gives me fine-grained control over the software I'm using and over which features this software has and does not have. So using the "distribution kernel" does not do for me, I had to find a different approach. One that would combine the convenience of binary packaging with the type of control that I've grown to appreciate over the years. The same, basically, that I had achieved with the binhost, where all packages are built with the exact flags that I want to use.

So I started hacking together a "custom-kernel-bin" ebuild that would allow me to build a custom kernel binary package, based on the gentoo-sources kernel source package, from which I have built every of my kernels in the last 15 years.

The following assumes that you have a build environment for binary packages already set up. In my case this build environment (or rather its PKGDIR the binary packages are installed into) also serves as binhost, from where the clients download their binary xpak files, but that is out of scope here and not relevant for the kernel package.

Important notes

  • This package now allows building a kernel with or without initrd.
    The "initramfs" USE flag controls this behaviour. dracut is used to build the initramfs after installation if the flag is set (which is why it is a conditional RDEPEND dependency).
  • This package does not maintain earlier kernel versions in /boot!
    Like any other package it removes the files from earlier versions of itself. It does not, however, remove kernels from /boot that were installed there manually. So it might be a good idea to keep your last manually compiled kernel there in case a new one doesn't boot.

/etc/portage/make.conf

I use the following build flags, among others:

CBUILD="x86_64-pc-linux-gnu"
CHOST="x86_64-pc-linux-gnu"
ACCEPT_KEYWORDS="amd64"

PORTDIR_OVERLAY=/usr/local/portage
CFLAGS="${COMMON_FLAGS} -march=x86-64 -mtune=generic --param l1-cache-size=32 --param l1-cache-line-size=64
 --param l2-cache-size=8192"
FEATURES="-sandbox -preserve-libs -ccache userpriv buildpkg binpkg-multi-instance"
EMERGE_DEFAULT_OPTS="--backtrack=200 --with-bdeps=y --keep-going=y --ask-enter-invalid"

Some of these may need to be adjusted for other environments. Note that this particular buildhost is set up to also use the same files locally that it installs as binpkg into $PKGDIR. This may not be possible with every CPU combination.

/usr/local/portage

This is where the ebuild is located (if PORTDIR_OVERLAY points somewhere else on your buildhost, use that instead). The following shows two ebuilds, one for kernel 5.5.9, one for 5.6.7.
The examples use the placeholder "$version" for the package version. Replace "$version" with the actual kernel version you are building.

buildsrv # ls -R1 /usr/local/portage/
/usr/local/portage/:
sys-kernel

/usr/local/portage/sys-kernel:
custom-kernel-bin

/usr/local/portage/sys-kernel/custom-kernel-bin:
custom-kernel-bin-5.5.9.ebuild
custom-kernel-bin-5.6.7.ebuild
files
metadata.xml
Manifest

/usr/local/portage/sys-kernel/custom-kernel-bin/files:
config-5.5.9-gentoo
config-5.6.7-gentoo

There are basically four files here that we need, one of which is optional.
The files need to be created by hand, relative to (in this example) /usr/local/portage/sys-kernel/custom-kernel-bin:

  • custom-kernel-bin-$version.ebuild: This is the ebuild file that controls both, the build and installation process
  • files/config-$version-gentoo: This is the kernel configuration (.config)
  • Manifest: This file contains the checksums of all involved files
  • metadata.xml (optional): This file contains additional information about the package
  1. custom-kernel-bin-$version.ebuild
    # Copyright 2021 Gentoo Authors
    # Distributed under the terms of the GNU General Public License v2
    
    EAPI=7
    
    DESCRIPTION="Custom-built amd64 kernel binpkg from gentoo-sources"
    HOMEPAGE="https://www.kernel.org"
    SRC_URI=""
    
    LICENSE="GPL-2"
    SLOT="0"
    KEYWORDS="amd64 ~amd64"
    IUSE="initramfs"
    
    DEPEND=""
    RDEPEND="
            ${DEPEND}
            !sys-kernel/gentoo-kernel:${SLOT}
            !sys-kernel/vanilla-kernel:${SLOT}
            !sys-kernel/vanilla-kernel-bin:${SLOT}
            initramfs? ( sys-kernel/dracut )
            "
    BDEPEND="=sys-kernel/gentoo-sources-${PVR}"
    
    QA_PREBUILT='*'
    
    S=${WORKDIR}
    KVER="${PV}-gentoo"
    if [[ $PV != $PVR ]]; then
            KVER="${PV}-gentoo-${PR}"
    fi
    MOD_DEST="/lib/modules/${KVER}"
    SRCDIR="/usr/src/linux-${KVER}"
    BUILDDIR="${T}/build-${PVR}"
    
    # We set up a temp builddir, not /usr/src/linux
    src_prepare() {
            default
            cp -r ${SRCDIR}/ ${BUILDDIR}
    }
    
    # If we have a config file for the kernel (package) version ${PVR}
    # that we are building for, we copy that to the temp builddir.
    # Else we try to find and use a kernel config from an earlier build.
    # Note that all emerge-specific flags from make.conf are also set.
    src_configure() {
            unset ARCH
            if [ -e "${FILESDIR}/config-${PVR}-gentoo" ]; then
                    cp ${FILESDIR}/config-${PVR}-gentoo ${BUILDDIR}/.config
                    cd $BUILDDIR 
                    emake olddefconfig && return
            elif [ -d ${FILESDIR}/ ]; then
                    latest=`ls ${FILESDIR}/config-* | sort -V | tail -1`
                    if [ -e ${latest} ]; then
                            cp ${latest} ${BUILDDIR}/.config
                            cd $BUILDDIR 
                            emake olddefconfig && return
                    fi
            fi
            # fallback
            die "Failed to configure kernel source"
    }
    
    # Compile normally.
    # Note that all emerge-specific flags from make.conf are also set.
    src_compile() {
            cd $BUILDDIR
            emake
            emake INSTALL_MOD_PATH="${T}" modules_install && return
            # fallback
            die "Failed to compile kernel source"
    }
    
    # We copy only the kernel, System.map and .config to /boot.
    # There is NO INITRD here! It is generated by dracut in the postinst step if
    # the "initramfs" USE flag is set.
    # Then copy the modules.
    # Note: The kernel sources are not required on the target system, so the various symlinks in /lib/modules/$version
    #       that usually point to /usr/src/linux are not set here. Uncomment below to change this.
    src_install() {
            dodir /boot
            insinto /boot
            newins "${BUILDDIR}/arch/x86/boot/bzImage" vmlinuz-${KVER}
            newins "${BUILDDIR}/.config" config-${KVER}
            newins "${BUILDDIR}/System.map" System.map-${KVER}
    
            rm -f ${T}${MOD_DEST}/build
            rm -f ${T}${MOD_DEST}/source
    #       ln -s ${SRCDIR} ${T}/lib/modules/${PVR}-gentoo/build
    #       ln -s ${SRCDIR} ${T}/lib/modules/${PVR}-gentoo/source
            dodir "$MOD_DEST"
            insinto "/lib/modules"
            doins -r "${T}/$MOD_DEST"
    }
    
    # Generate grub config for new kernel and module maps.
    pkg_postinst() {
            if use initramfs ; then
                    elog "Creating initramfs" 
                    dracut --hostonly --force --kver ${KVER}
            fi
            elog "Kernel files were installed successfully. Running grub-mkconfig -o /boot/grub/grub.cfg"
            grub-mkconfig -o /boot/grub/grub.cfg
            depmod -a ${KVER}
    }
    

  2. files/config-$version-gentoo

    Copy a .config file for the kernel $version you are building (!!!) here.

    If you don't have one (which is to be expected for a new kernel version), you can copy the .config from an earlier kernel build into the kernel source tree under /usr/src/linux-$version-gentoo and then run "make oldconfig" on the tree. Then copy the resulting .config to /usr/local/portage/sys-kernel/custom-kernel-bin/files/config-$version-gentoo.

  3. metadata.xml

    You can put much information about your custom kernel package here (see the Gentoo developer handbook). I personally found it useful to add a comment that clarifies what the initramfs USE flag is used for. This file is optional. If you have it, equery and other tools will display the information in it, else not.
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE pkgmetadata SYSTEM "http://www.gentoo.org/dtd/metadata.dtd">
    <pkgmetadata>
      <maintainer type="person">
        <email>build@some.domain</email>
        <name>Build Root</name>
      </maintainer>
      <use>
        <flag name="initramfs">Build an initramfs after installation (requires dracut)</flag>
      </use>
    </pkgmetadata>
    
    Output of equery when you add this file:
    # equery u custom-kernel-bin
    [...]
     - - initramfs : Build an initramfs after installation (requires dracut)
    
    Output of equery without this file:
    # equery u custom-kernel-bin
    [...]
     - - initramfs : <unknown>
    
  4. Manifest

    After you have created the files above, run this command:
    ebuild /usr/local/portage/sys-kernel/custom-kernel-bin/custom-kernel-bin-$version.ebuild digest
    This will generate the Manifest file.

Build the package

Run the following command:

emerge --buildpkgonly custom-kernel-bin
This will compile the kernel and create the package file in
/var/cache/binpkgs/sys-kernel/custom-kernel-bin/custom-kernel-bin-$version-1.xpak
(or wherever your $PKGDIR points to).

Install the package

There is more than one way to do this. My method uses the binhost (where the above command installed the package into $PKGDIR, which serves as my binhost)

The target machine (client) needs to have the same ebuild and Manifest files that the package was built with. Either set up a repository from which it can be downloaded with emerge --sync (out of scope here), or simply copy them to the target. Assuming that you have PORTDIR_OVERLAY=/usr/local/portage, you could eg.:

mkdir /usr/local/portage/sys-kernel/
scp -r buildhost:/usr/local/portage/sys-kernel/custom-kernel-bin /usr/local/portage/sys-kernel/
Also put this into /etc/portage/make.conf:
(all the same USE flags etc. as on the buildhost)

PORTDIR_OVERLAY=/usr/local/portage
PORTAGE_BINHOST="https://$binhost/path/to/$PKGDIR"
FEATURES="-sandbox -ccache -buildpkg preserve-libs getbinpkg"

If everything is set up correctly, emerge should find the new package
emerge -s custom-kernel-bin

*  sys-kernel/custom-kernel-bin
      Latest version available: 5.6.7
      Latest version installed: [ Not Installed ]
      Size of files: 0 KiB
      Homepage:      https://www.kernel.org
      Description:   Custom-built amd4 kernel binpkg from gentoo-sources
      License:       GPL-2

Now install the package with emerge -K custom-kernel-bin.

The "-K" makes sure it will rather fail than try to build the package if the package is not installed eg. because of some flag mismatch.

Note for initramfs:

If you want dracut to build an initramfs after installation, you need to set the initramfs USE flag for the custom-kernel-bin package on the build host and on the target system (!), i.e. the system you are installing the custom kernel to. To do so, add the following line to /etc/portage/package.use or, if that is not a file but a directory, to any file under it:

sys-kernel/custom-kernel-bin initramfs
This will install dracut and its dependencies before installing custom-kernel-bin. If you are using a binhost, make sure the dracut package (and its dependencies) are available there.

If you want to be able to install the same custom binary kernel on multiple targets with and without initrd, you need to build the kernel package twice, one time with, one time without the initramfs USE flag. Make sure that the binpkg-multi-instance flag is set in FEATURES, else the two package versions will overwrite each other.

You can customize dracut's operation locally in /etc/dracut.conf. (This might be necessary if you need to modify the modules that dracut puts into the initramfs.)


Markus Wernig

webmaster wernig net