Setting a Custom Compiled/Built Kernel RPM to Boot by Default

Hello,

I have a hacky bash script to setup an rpmbuild environment, pull down the latest Rocky Linux 9 x86_64 kernel RPM src package, apply some patches to the kernel source, compile it, and build the associated RPMs. See below (might help someone else who wants to do something similar):

#!/usr/bin/env bash
# Builds the rpmbuild environment, pulls down the latest kernel
# rpm src, applies custom patches and kconfigs to the kernel source,
# compiles the kernel, and deploys the RPMs to a DNF server via NFS.

# Exit on error, fail if an unset variable is referenced, turn on tracing, and fail if a command fails in a pipe.
set -o errexit -o nounset -o xtrace -o pipefail

#DNF_SERVER="<dnf_repo_server>"
REMOTE_MNT_LOCATION="/var_dnf_repo"
KERNEL_SRC_REPO_FQDN="mirrors.vcea.wsu.edu"
LOCAL_MNT_LOCATION="/mnt/dnf_repo/"
PATCHES_DIRECTORY="/root/patches/kernel"
KCONFIGS_DIRECTORY="/root/patches/kernel/kconfigs"
NEW_KERNEL_FEATURES=(CONFIG_ATH10K_THERMAL)

function generate_var() {
    # Variables section
    ## Get CentOS release version
    ROCKY_RELEASE=$(head /etc/redhat-release | cut --delimiter ' ' --fields 4 | cut --delimiter '.' --fields 1)

    # Build base URL
    KERNEL_SRC_BASE_URL="https://"${KERNEL_SRC_REPO_FQDN}"/rocky/"${ROCKY_RELEASE}"/BaseOS/source/tree/Packages/k/"

    ## Grab the current kernel version for our helper config file
    if [[ ! -e /etc/kernel-version ]]; then
        CURRENT_KERNEL_VERSION=""
    else
        CURRENT_KERNEL_VERSION=$(cat /etc/kernel-version)
    fi

    ## Grab the latest kernel version from vault.centos.org
    NEW_KERNEL_VERSION_SRC_RPM=$(curl -s "${KERNEL_SRC_BASE_URL}" | grep --extended -io "a href=\"kernel-[0-9]*\.[0-9]*\.[0-9]*\-[0-9]*\.?.*\.el${ROCKY_RELEASE}.*\.src\.rpm\"" | cut --delimiter '"' --fields 2 | sort --version-sort | tail -1)

    ## Get new kernel version without src rpm extension
    NEW_KERNEL_VERSION=$(echo "${NEW_KERNEL_VERSION_SRC_RPM}" | rev | cut --delimiter '.' --fields 3- | rev)

    ## Construct the URL to download the SRC RPM for the kernel
    NEW_KERNEL_VERSION_URL="${KERNEL_SRC_BASE_URL}${NEW_KERNEL_VERSION_SRC_RPM}"

    ## Get this computers architecture
    ARCH=$(uname -m)

    # Construct an array of patches we will apply
    readarray -d '' PATCHES < <(find "${PATCHES_DIRECTORY}" -mindepth 1 -maxdepth 1 -type f -name "*.patch" -printf "%f\0")

    # Construct an array of Kconfigs we will apply
    readarray -d '' KCONFIGS < <(find "${KCONFIGS_DIRECTORY}" -mindepth 1 -maxdepth 1 -type f -name "*.patch" -printf "%f\0")

    echo "VARIABLES:"
    echo "ROCKY_RELEASE          = ${ROCKY_RELEASE}"
    echo "CURRENT_KERNEL_VERSION = ${CURRENT_KERNEL_VERSION}"
    echo "NEW_KERNEL_VERSION     = ${NEW_KERNEL_VERSION}"
    echo "NEW_KERNEL_VERSION_URL = ${NEW_KERNEL_VERSION_URL}"
    echo "ARCH                   = ${ARCH}"
    echo "PATCHES                = ${PATCHES[*]}"
    echo "KCONFIGS               = ${KCONFIGS[*]}"
    echo "GENERATE ENVIRONMENT   = ${GENERATE}"
    echo "BUILD KERNEL           = ${BUILD}"
    echo "DEPLOY KERNEL          = ${DEPLOY}"
    echo "RPM                    = ${RPM}"
    echo "NEW_KERNEL_FEATURES    = ${NEW_KERNEL_FEATURES[*]}"
    echo ""
}

function generate_env {
    echo -e "[\e[93mDOING\e[39m] Verifying new kernel version is not the same as the current version before proceeding."
    if [[ "${NEW_KERNEL_VERSION}" == "${CURRENT_KERNEL_VERSION}" ]]; then
        if [[ ${FORCE} == False ]]; then
            echo -e "[\e[92mOK\e[39m] Current kernel version ${CURRENT_KERNEL_VERSION} is the same as the new version ${NEW_KERNEL_VERSION}"
            exit 0
        else
            echo -e "[\e[92mOK\e[39m] Current kernel version ${CURRENT_KERNEL_VERSION} is the same as the new version ${NEW_KERNEL_VERSION}, but FORCE is set to True so continuing anyway."
        fi
    else
        echo -e "[\e[92mOK\e[39m] Current kernel version ${CURRENT_KERNEL_VERSION} is NOT the same as the new version ${NEW_KERNEL_VERSION}"
    fi

    echo -e "[\e[93mDOING\e[39m] Removing build directories if they exit."
    rm --recursive --force ~/rpmbuild

    echo -e "[\e[93mDOING\e[39m] Setting up build directories."
    mkdir --parents ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}

    echo -e "[\e[93mDOING\e[39m] Updating macros."
    echo '%_topdir %(echo ${HOME})/rpmbuild' > ~/.rpmmacros

    echo -e "[\e[93mDOING\e[39m] Downloading the kernel src rpm to /tmp/${NEW_KERNEL_VERSION_SRC_RPM}"
    wget --quiet --directory-prefix=/tmp "${NEW_KERNEL_VERSION_URL}"

    echo -e "[\e[93mDOING\e[39m] Installing kernel src rpm /tmp/${NEW_KERNEL_VERSION_SRC_RPM}."
    rpm --install /tmp/"${NEW_KERNEL_VERSION_SRC_RPM}"

    echo -e "[\e[93mDOING\e[39m] Removing kernel src rpm /tmp/${NEW_KERNEL_VERSION_SRC_RPM}."
    rm --force /tmp/"${NEW_KERNEL_VERSION_SRC_RPM}"

    echo -e "[\e[93mDOING\e[39m] Attempting to build environment if no package dependencies are necessary.  This will take a little while"
    PACKAGES=$( (rpmbuild -bp --target="${ARCH}" ~/rpmbuild/SPECS/kernel.spec || true) 2>&1 | (grep 'is needed by' || true) | awk '{print $1}' | tr '\n' ' ')
    if [[ ${PACKAGES} != "" ]]; then
        for pkg in ${PACKAGES}; do
            echo -e "[\e[93mDOING\e[39m] (sudo) Attempting to install package dependency ${pkg}"
            dnf install "${pkg}" --assumeyes --quiet
        done
        echo -e "\n[\e[93mDOING\e[39m] Setting up build environment after installing dependencies.  This will take a little while."
        rpmbuild -bp --target="${ARCH}" ~/rpmbuild/SPECS/kernel.spec 2>&1
    fi

    local linux_dir
    linux_dir=$(echo ~/rpmbuild/BUILD/kernel-*/linux-*/)
    local arch_config
    arch_config=$(ls ~/rpmbuild/BUILD/kernel-*/linux-*/configs/kernel*-"${ARCH}".config)

    echo -e "[\e[93mDOING\e[39m] Copying patches to the SOURCES directory."
    command cp "${PATCHES_DIRECTORY}"/*.patch ~/rpmbuild/SOURCES

    echo -e "[\e[93mDOING\e[39m] Copying our current boot .config to the BUILD directory."
    command cp "${arch_config}" "${linux_dir}".config

    echo -e "[\e[93mDOING\e[39m] Executing make oldconfig"
    build_dir=$(readlink -f ~/rpmbuild/BUILD/kernel-*/linux-*/)
    make --directory="${build_dir}" olddefconfig 2>&1

    echo -e "\n[\e[93mDOING\e[39m] Adding the ARCH ${ARCH} to the top of the .config file."
    sed --in-place -- "1i # ${ARCH}" "${linux_dir}".config

    echo -e "[\e[93mDOING\e[39m] Patching Kconfigs prior to adding new modules/features."
    for i in "${!KCONFIGS[@]}"; do
        if [[ $( (grep --extended "^\+\+\+ b.+" "${KCONFIGS_DIRECTORY}"/"${KCONFIGS[i]}" || true) | wc -l) != "1" ]]; then
            echo -e "[\e[31mFAIL\e[39m] Unable to determine patch file location in kconfig patch ${KCONFIGS[i]}."
            exit 1
        fi
        kconfig_mod_line=$( (grep --extended "^\+\+\+ b.+" "${KCONFIGS_DIRECTORY}"/"${KCONFIGS[i]}" || true) | cut --delimiter " " --fields 2 | cut --delimiter "/" --fields 2-)
        echo -e "[\e[93mDOING\e[39m] Patching ${kconfig_mod_line} with patch ${KCONFIGS[i]}."
        patch -u "${build_dir}"/"${kconfig_mod_line}" "${KCONFIGS_DIRECTORY}"/"${KCONFIGS[i]}"
    done

    echo -e "[\e[93mDOING\e[39m] Adding new modules/features."
    for i in "${!NEW_KERNEL_FEATURES[@]}"; do
        echo -e "[\e[93mDOING\e[39m] Adding new feature ${NEW_KERNEL_FEATURES[i]}."
        "${build_dir}"/scripts/config --set-val "${NEW_KERNEL_FEATURES[i]}" y
    done

    echo -e "[\e[93mDOING\e[39m] Copying .config to the configs/kernel-.*-${ARCH}.config file."
    command cp ~/rpmbuild/BUILD/kernel-*/linux-*/.config "${arch_config}"

    echo -e "[\e[93mDOING\e[39m] Copying configs to the SOURCES directory."
    command cp ~/rpmbuild/BUILD/kernel-*/linux-*/configs/* ~/rpmbuild/SOURCES/

    echo -e "[\e[93mDOING\e[39m] Backup the kernel.spec file to kernel.spec.distro."
    command cp ~/rpmbuild/SPECS/kernel.spec ~/rpmbuild/SPECS/kernel.spec.distro

    echo -e "[\e[93mDOING\e[39m] Create a unique buildid for the kernel name."
    sed --in-place -- 's/# define buildid .local/%define buildid .ath_hack/g' ~/rpmbuild/SPECS/kernel.spec

    echo -e "[\e[93mDOING\e[39m] Updating the pkg_release variable in the kernel.spec file."
    sed --in-place -- 's/%define pkg_release %{specrelease}%{?buildid}/%define pkg_release %{pkgrelease}%{?buildid}/g' ~/rpmbuild/SPECS/kernel.spec

    echo -e "[\e[93mDOING\e[39m] Adding patch definitions to the kernel spec."
    local test_patch_line
    test_patch_line=$(( $(sed --quiet '/Patch999999: linux-kernel-test.patch/=' ~/rpmbuild/SPECS/kernel.spec) - 1 ))
    if [[ "${test_patch_line}" == "" ]]; then
        echo -e "[\e[31mFAIL\e[39m] Failed to find the final patch line for Patch999999 in ~/rpmbuild/SPECS/kernel.spec."
        exit 1
    fi
    for i in "${!PATCHES[@]}"; do
        echo -e "[\e[93mDOING\e[39m] Adding patch 'Patch$((i+2)): ${PATCHES[i]}' to the kernel.spec file."
        sed --in-place -- "$(( test_patch_line + i ))i Patch$((i+2)): ${PATCHES[i]}"  ~/rpmbuild/SPECS/kernel.spec
    done
    local test_apply_optional_patch_line
    test_apply_optional_patch_line=$(sed --quiet '/ApplyOptionalPatch linux-kernel-test.patch/=' ~/rpmbuild/SPECS/kernel.spec)
    if [[ "${test_apply_optional_patch_line}" == "" ]]; then
        echo -e "[\e[31mFAIL\e[39m] Failed to find beginning of the ApplyOptionalPatch section in ~/rpmbuild/SPECS/kernel.spec."
        exit 1
    fi
    for i in "${!PATCHES[@]}"; do
        echo -e "[\e[93mDOING\e[39m] Adding patch 'ApplyOptionalPatch ${PATCHES[i]}' to the kernel.spec file."
        sed --in-place -- "$(( test_apply_optional_patch_line + i ))i ApplyOptionalPatch ${PATCHES[i]}" ~/rpmbuild/SPECS/kernel.spec
    done
    echo -e "[\e[92mOK\e[39m] Finished preparing the kernel build environment."
}

function build_kernel() {
    echo -e "\n[\e[93mDOING\e[39m] Building the kernel.  The build log is in /tmp/build-out.log and the error log is in /tmp/build-err.log"
    rpmbuild --with baseonly --without debug --without debuginfo --without kabichk -bb --target="${ARCH}" ~/rpmbuild/SPECS/kernel.spec 2> /tmp/build-err.log | tee /tmp/build-out.log
    echo -e "[\e[92mOK\e[39m] Finished building the kernel."
}

function deploy_kernel() {
    echo -e "\n[\e[93mDOING\e[39m] Copying RPMs to /tmp"
    cp ~/rpmbuild/RPMS/"${ARCH}"/kernel-*.rpm /tmp

    echo -e "[\e[93mDOING\e[39m] Mounting the DNF repo directory."
    mount --types nfs4 --options sec=sys "${DNF_SERVER}":"${REMOTE_MNT_LOCATION}" "${LOCAL_MNT_LOCATION}"

    echo -e "[\e[93mDOING\e[39m] Moving RPMs to the yum REPO."
    mv ~/rpmbuild/RPMS/"${ARCH}"/kernel-*.rpm "${REMOTE_MNT_LOCATION}"

    echo -e "[\e[93mDOING\e[39m] Unmounting the DNF repo directory."
    umount "${LOCAL_MNT_LOCATION}"

    echo -e "[\e[93mDOING\e[39m] Writing out the new kernel version to our helper config file."
    bash -c "echo ${NEW_KERNEL_VERSION} > /etc/kernel-version"

    echo -e "[\e[93mDOING\e[39m] Removing the rpmbuild directory."
    rm --recursive --force ~/rpmbuild
}

function usage() {
    echo "Usage: build_custom_kernel.sh [-g <generates_userspace>] [-b <builds_the_kernel>] [-i <deploys_the_kernel>] [-r <location_of_kernel_src_rpm>] [-f <force_build_if_new_kernel_equals_old_kernel>]"
    exit 0
}

BUILD=False
DEPLOY=False
FORCE=False
GENERATE=False
RPM=""

# Parse arguments
while getopts ":befghr:" opt; do
    case ${opt} in
        b) BUILD=True;;
        e) DEPLOY=True;;
        f) FORCE=True;;
        g) GENERATE=True;;
        h) usage;;
        r) RPM=${OPTARG};;
        \?) usage;;
    esac
done

# Do everything if no option is specified.
if [[ ${GENERATE} = False ]] && [[ ${BUILD} == False ]] && [[ ${DEPLOY} == False ]]; then
    GENERATE=True
    BUILD=True
    DEPLOY=True
fi

generate_var

if [[ ${GENERATE} == True ]]; then
    generate_env ${FORCE}
fi

if [[ ${BUILD} == True ]]; then
    build_kernel
fi

if [[ ${DEPLOY} == True ]]; then
    deploy_kernel
fi

It works well for what it. However, despite using the same RPM spec as Rocky Linux, when I install the compiled kernel it is NOT set as the default boot kernel. I have to do that manually with a grubby --set-default /boot/vmlinuz... command. Am I missing something when building the RPM with rpmbuild under the function build_kernel() section? Otherwise, is my only other option an ugly hack in the %post section to run the grubby --set-default command?

I wonder if that would happen with manual install of an official kernel? Is there anything in the spec file regarding changing boot order?

I think I found the issue in my /etc/sysconfig/kernel config:

# UPDATEDEFAULT specifies if kernel-install should make
# new kernels the default
UPDATEDEFAULT=yes

# DEFAULTKERNEL specifies the default kernel package type
DEFAULTKERNEL=kernel-debug-core

I think DEFAULTKERNEL should be set to kernel-core. I’m not even installing kernel-debug-core, so something is touching that file but I’m not sure what yet. It’s probably something in my script. I’ll test it out when a new version of the kernel is released.

Yep, setting DEFAULTKERNEL to kernel-core fixed the issue.