From c0f6d3543872b0a17f75692c56423ee82d9beaa2 Mon Sep 17 00:00:00 2001 From: Cosku Bas Date: Fri, 6 Feb 2026 14:25:00 +0100 Subject: [PATCH 1/3] Add custom-kernel module --- modules/custom-kernel/README.md | 81 ++++ modules/custom-kernel/custom-kernel.sh | 476 ++++++++++++++++++++++++ modules/custom-kernel/custom-kernel.tsp | 69 ++++ modules/custom-kernel/module.yml | 17 + 4 files changed, 643 insertions(+) create mode 100644 modules/custom-kernel/README.md create mode 100644 modules/custom-kernel/custom-kernel.sh create mode 100644 modules/custom-kernel/custom-kernel.tsp create mode 100644 modules/custom-kernel/module.yml diff --git a/modules/custom-kernel/README.md b/modules/custom-kernel/README.md new file mode 100644 index 00000000..c91ba2ee --- /dev/null +++ b/modules/custom-kernel/README.md @@ -0,0 +1,81 @@ +# custom-kernel + +The custom-kernel module installs a custom Linux kernel on Fedora-based images. +The module currently supports CachyOS kernel variants and can optionally install the NVIDIA driver and perform Secure Boot signing. + +## Description + +The custom-kernel module replaces or augments the default Fedora kernel with a selected CachyOS kernel variant. +The module can generate an initramfs for the installed kernel and can build NVIDIA kernel modules that match the selected kernel version. + +The custom-kernel module can optionally sign the kernel image and kernel modules for use with Secure Boot. +When Secure Boot signing is enabled, a Machine Owner Key (MOK) is enrolled on first boot using the provided credentials. + +## Configuration + +The custom-kernel module is configured using the following options. + +### `kernel` + +The `kernel` field selects the CachyOS kernel variant to install. +If not specified, the default value `cachyos-lto` is used. + +Supported values include: + +- `cachyos` +- `cachyos-lto` +- `cachyos-lts` +- `cachyos-lts-lto` +- `cachyos-rt` + +### `initramfs` + +The `initramfs` field controls whether an initramfs is generated for the installed kernel. +If not specified, the default value `false` is used. + +### `nvidia` + +The `nvidia` field controls whether the NVIDIA driver is built and installed for the selected kernel. +If not specified, the default value `false` is used. + +### `sign` + +The `sign` field enables Secure Boot signing of the kernel and kernel modules. +The `sign` field is optional, but when it is defined, **all fields inside the object must be provided**. + +When the image is booted for the first time, the provided Machine Owner Key (MOK) is **automatically enrolled** using the configured credentials. +This enrollment is performed during early boot and requires a reboot to complete. + +If Secure Boot signing is enabled, the kernel image and all kernel modules are signed during the image build process. + +#### `sign.key` + +The `key` field specifies the path to the private key used for signing. +The key file must be available inside the build environment and mounted to the container (see example) during build-time. + +#### `sign.cert` + +The `cert` field specifies the path to the public certificate in PEM format. +The certificate must correspond to the private signing key. + +#### `sign.mok-password` + +The `mok-password` field specifies the password used during MOK enrollment on first boot. + +## Example + +```yaml +type: custom-kernel +kernel: cachyos-lto +initramfs: true +nvidia: true +sign: + key: /tmp/certs/MOK.key + cert: /path/to/MOK.pem + mok-password: BlueBuild +secrets: + - type: file + source: ./MOK.key + mount: + type: file + destination: /tmp/certs/MOK.key \ No newline at end of file diff --git a/modules/custom-kernel/custom-kernel.sh b/modules/custom-kernel/custom-kernel.sh new file mode 100644 index 00000000..589fa4ad --- /dev/null +++ b/modules/custom-kernel/custom-kernel.sh @@ -0,0 +1,476 @@ +#!/usr/bin/env bash +set -euo pipefail + +log() { + local PREFIX="[custom-kernel]" + # local BOLD_CYAN='\033[1;36m' + # local RESET='\033[0m' + # echo -e "${BOLD_CYAN}${PREFIX}${RESET} $*" + echo -e "${PREFIX} $*" +} + +error() { + local PREFIX="[custom-kernel] Error:" + # local BOLD_RED='\033[1;31m' + # local RESET='\033[0m' + # echo -e "${BOLD_RED}${PREFIX}${RESET} $*" + echo -e "${PREFIX} $*" +} +log "Starting custom-kernel module..." + +# Read configuration from the first argument ($1) using jq +KERNEL_TYPE=$(echo "$1" | jq -r '.kernel // "cachyos-lto"') +INITRAMFS=$(echo "$1" | jq -r '.initramfs // false') +NVIDIA=$(echo "$1" | jq -r '.nvidia // false') +SIGNING_KEY=$(echo "$1" | jq -r '.sign.key // ""') +SIGNING_CERT=$(echo "$1" | jq -r '.sign.cert // ""') +MOK_PASSWORD=$(echo "$1" | jq -r '.sign.["mok-password"] // ""') +SECURE_BOOT=false + +# Checking key, cert and password. Can't continue without them +if [[ -z "${SIGNING_KEY}" && -z "${SIGNING_CERT}" && -z "${MOK_PASSWORD}" ]]; then + log "SecureBoot signing disabled." +elif [[ -f "${SIGNING_KEY}" && -f "${SIGNING_CERT}" && -n "${MOK_PASSWORD}" ]]; then + log "SecureBoot signing enabled." + SECURE_BOOT=true +else + error "Invalid signing config:" + error " sign.key: ${SIGNING_KEY:-}" + error " sign.cert: ${SIGNING_CERT:-}" + error " sign.mok-password: ${MOK_PASSWORD:-}" + exit 1 +fi + +# Double check everything about keys and certs +if [[ ${SECURE_BOOT} == true ]]; then + openssl pkey -in "${SIGNING_KEY}" -noout >/dev/null 2>&1 \ + || { error "sign.key is not a valid private key"; exit 1; } + + openssl x509 -in "${SIGNING_CERT}" -noout >/dev/null 2>&1 \ + || { error "sign.cert is not a valid X509 cert"; exit 1; } + + if ! diff -q \ + <(openssl pkey -in "${SIGNING_KEY}" -pubout) \ + <(openssl x509 -in "${SIGNING_CERT}" -pubkey -noout); then + error "sign.key and sign.cert do not match" + exit 1 + fi +fi + +# Resolve kernel settings based on the kernel type +COPR_REPOS=() +KERNEL_PACKAGES=() +EXTRA_PACKAGES=( + akmods +) + +case "${KERNEL_TYPE}" in +cachyos-lto) + COPR_REPOS=( + bieszczaders/kernel-cachyos-lto + ) + KERNEL_PACKAGES=( + kernel-cachyos-lto + kernel-cachyos-lto-core + kernel-cachyos-lto-modules + kernel-cachyos-lto-devel-matched + ) + ;; +cachyos-lts-lto) + COPR_REPOS=( + bieszczaders/kernel-cachyos-lto + ) + KERNEL_PACKAGES=( + kernel-cachyos-lts-lto + kernel-cachyos-lts-lto-core + kernel-cachyos-lts-lto-modules + kernel-cachyos-lts-lto-devel-matched + ) + ;; +cachyos) + COPR_REPOS=( + bieszczaders/kernel-cachyos + ) + KERNEL_PACKAGES=( + kernel-cachyos + kernel-cachyos-core + kernel-cachyos-modules + kernel-cachyos-devel-matched + ) + ;; +cachyos-rt) + COPR_REPOS=( + bieszczaders/kernel-cachyos + ) + KERNEL_PACKAGES=( + kernel-cachyos-rt + kernel-cachyos-rt-core + kernel-cachyos-rt-modules + kernel-cachyos-rt-devel-matched + ) + ;; +cachyos-lts) + COPR_REPOS=( + bieszczaders/kernel-cachyos + ) + KERNEL_PACKAGES=( + kernel-cachyos-lts + kernel-cachyos-lts-core + kernel-cachyos-lts-modules + kernel-cachyos-lts-devel-matched + ) + ;; +*) + error "Unsupported kernel type: ${KERNEL_TYPE}" + exit 1 + ;; +esac + +restore_kernel_install_hooks() { + local RPMOSTREE=/usr/lib/kernel/install.d/05-rpmostree.install + local DRACUT=/usr/lib/kernel/install.d/50-dracut.install + + if [[ -f "${RPMOSTREE}.bak" ]]; then + mv -f "${RPMOSTREE}.bak" "${RPMOSTREE}" + fi + + if [[ -f "${DRACUT}.bak" ]]; then + mv -f "${DRACUT}.bak" "${DRACUT}" + fi +} + +disable_kernel_install_hooks() { + local RPMOSTREE=/usr/lib/kernel/install.d/05-rpmostree.install + local DRACUT=/usr/lib/kernel/install.d/50-dracut.install + + if [[ -f "${RPMOSTREE}" ]]; then + mv "${RPMOSTREE}" "${RPMOSTREE}.bak" + printf '%s\n' '#!/bin/sh' 'exit 0' >"${RPMOSTREE}" + chmod +x "${RPMOSTREE}" + fi + + if [[ -f "${DRACUT}" ]]; then + mv "${DRACUT}" "${DRACUT}.bak" + printf '%s\n' '#!/bin/sh' 'exit 0' >"${DRACUT}" + chmod +x "${DRACUT}" + fi +} + +# Installing custom kernel +log "Temporarily disabling kernel install scripts." +disable_kernel_install_hooks + +log "Removing default kernel packages." +dnf -y remove \ + kernel \ + kernel-core \ + kernel-modules \ + kernel-modules-core \ + kernel-modules-extra \ + kernel-devel \ + kernel-devel-matched || true +rm -rf /usr/lib/modules/* || true + +for repo in "${COPR_REPOS[@]}"; do + log "Enabling COPR repo: ${repo}" + dnf -y copr enable "${repo}" +done + +log "Installing kernel packages: ${KERNEL_PACKAGES[*]}" +dnf -y install \ + "${KERNEL_PACKAGES[@]}" \ + "${EXTRA_PACKAGES[@]}" + +KERNEL_VERSION="$(rpm -q "${KERNEL_PACKAGES[0]}" --queryformat '%{VERSION}-%{RELEASE}.%{ARCH}')" || exit 1 +log "Detected kernel version: ${KERNEL_VERSION}" + +log "Restoring kernel install scripts." +restore_kernel_install_hooks + +log "Cleaning up custom kernel repos." +rm -f /etc/yum.repos.d/*copr* + +# Install Nvidia if needed +disable_akmodsbuild() { + local AK="/usr/sbin/akmodsbuild" + local BAK="${AK}.backup" + + if [[ ! -f "${AK}" ]]; then + error "akmodsbuild not found: ${AK}" + return 1 + fi + + cp -a "${AK}" "${BAK}" || return 1 + + # remove the problematic block + sed -i '/if \[\[ -w \/var \]\] ; then/,/fi/d' "${AK}" || return 1 +} + +restore_akmodsbuild() { + local AK="/usr/sbin/akmodsbuild" + local BAK="${AK}.backup" + + if [[ -f "${BAK}" ]]; then + mv -f "${BAK}" "${AK}" + fi +} + +if [[ ${NVIDIA} == true ]]; then + log "Enabling Nvidia repositories." + curl -fsSL --retry 5 --create-dirs \ + https://nvidia.github.io/libnvidia-container/stable/rpm/nvidia-container-toolkit.repo \ + -o /etc/yum.repos.d/nvidia-container-toolkit.repo + curl -fsSL --retry 5 --create-dirs \ + https://negativo17.org/repos/fedora-nvidia.repo \ + -o /etc/yum.repos.d/fedora-nvidia.repo + + + log "Temporarily disabling akmodsbuild script." + disable_akmodsbuild || exit 1 + + log "Building and installing Nvidia kernel module packages." + dnf install -y --setopt=install_weak_deps=False --setopt=tsflags=noscripts \ + akmod-nvidia \ + nvidia-kmod-common \ + nvidia-modprobe + akmods --force --verbose --kernels "${KERNEL_VERSION}" --kmod "nvidia" + + log "Restoring akmodsbuild script." + restore_akmodsbuild + + log "Installing Nvidia userspace packages." + dnf install -y --setopt=skip_unavailable=1 \ + libva-nvidia-driver \ + nvidia-driver \ + nvidia-persistenced \ + nvidia-settings \ + nvidia-driver-cuda \ + libnvidia-cfg \ + libnvidia-fbc \ + libnvidia-ml \ + libnvidia-gpucomp \ + nvidia-driver-libs.i686 \ + nvidia-driver-cuda-libs.i686 \ + libnvidia-fbc.i686 \ + libnvidia-ml.i686 \ + libnvidia-gpucomp.i686 \ + nvidia-container-toolkit + + log "Cleaning Nvidia repositories." + rm -f /etc/yum.repos.d/*nvidia* + + log "Installing Nvidia SELinux policy." + curl -fsSL --retry 5 --create-dirs \ + https://raw.githubusercontent.com/NVIDIA/dgx-selinux/master/bin/RHEL9/nvidia-container.pp \ + -o nvidia-container.pp + semodule -i nvidia-container.pp + rm -f nvidia-container.pp + + log "Installing Nvidia container toolkit service and preset." + install -D -m 0644 /dev/stdin /usr/lib/systemd/system/nvctk-cdi.service <<'EOF' +[Unit] +Description=NVIDIA Container Toolkit CDI auto-generation +ConditionFileIsExecutable=/usr/bin/nvidia-ctk +ConditionPathExists=!/etc/cdi/nvidia.yaml +After=local-fs.target + +[Service] +Type=oneshot +ExecStart=/usr/bin/nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml + +[Install] +WantedBy=multi-user.target +EOF + + install -D -m 0644 /dev/stdin /usr/lib/systemd/system-preset/70-nvctk-cdi.preset <<'EOF' +enable nvctk-cdi.service +EOF + + log "Setting up Nvidia modules." + install -D -m 0644 /dev/stdin /etc/modprobe.d/nvidia.conf <<'EOF' +blacklist nouveau +options nouveau modeset=0 +options nvidia-drm modeset=1 fbdev=1 +EOF + + log "Setting up GPU modules for initramfs." + install -D -m 0644 /dev/stdin /usr/lib/dracut/dracut.conf.d/99-nvidia.conf <<'EOF' +# Force the i915 amdgpu nvidia drivers to the ramdisk +force_drivers+=" i915 amdgpu nvidia nvidia_drm nvidia_modeset nvidia_peermem nvidia_uvm " +EOF + + log "Injecting Nvidia kernel args" + install -D -m 0644 /dev/stdin /usr/lib/bootc/kargs.d/90-nvidia.toml <<'EOF' +kargs = [ +"rd.driver.blacklist=nouveau", +"modprobe.blacklist=nouveau", +"rd.driver.pre=nvidia", +"nvidia-drm.modeset=1", +"nvidia-drm.fbdev=1" +] +EOF +fi + +# Sign the kernel and modules +sign_kernel() { + local MODULE_ROOT="/usr/lib/modules/${KERNEL_VERSION}" + local VMLINUZ="${MODULE_ROOT}/vmlinuz" + + # Sign kernel + if [[ -f "${VMLINUZ}" ]]; then + log "Kernel image: ${VMLINUZ}" + + SIGNED_VMLINUZ="$(mktemp)" + + # Sign kernel into temp file + sbsign \ + --key "${SIGNING_KEY}" \ + --cert "${SIGNING_CERT}" \ + --output "${SIGNED_VMLINUZ}" \ + "${VMLINUZ}" + + # Verify signature before installing + if ! sbverify --cert "${SIGNING_CERT}" "${SIGNED_VMLINUZ}"; then + error "Kernel signature verification failed" + rm -f "${SIGNED_VMLINUZ}" + return 1 + fi + + log "Verification successful. Installing signed kernel." + + # Atomically replace original kernel with signed one + install -m 0644 "${SIGNED_VMLINUZ}" "${VMLINUZ}" + rm -f "${SIGNED_VMLINUZ}" + else + error "Can't find kernel image: ${VMLINUZ}" + return 1 + fi + + # For final check later + sha256sum "${VMLINUZ}" > /tmp/vmlinuz.sha +} + +sign_kernel_modules() { + local MODULE_ROOT="/usr/lib/modules/${KERNEL_VERSION}" + local SIGN_FILE="${MODULE_ROOT}/build/scripts/sign-file" + + if [[ ! -x "${SIGN_FILE}" ]]; then + error "sign-file not found or not executable: ${SIGN_FILE}" + return 1 + fi + + while IFS= read -r -d '' mod; do + case "${mod}" in + *.ko) + "${SIGN_FILE}" sha256 "${SIGNING_KEY}" "${SIGNING_CERT}" "${mod}" || return 1 + ;; + *.ko.xz) + xz -d "${mod}" + raw="${mod%.xz}" + "${SIGN_FILE}" sha256 "${SIGNING_KEY}" "${SIGNING_CERT}" "${raw}" || return 1 + xz -z "${raw}" + ;; + *.ko.zst) + zstd -d --rm "${mod}" + raw="${mod%.zst}" + "${SIGN_FILE}" sha256 "${SIGNING_KEY}" "${SIGNING_CERT}" "${raw}" || return 1 + zstd -q "${raw}" + ;; + *.ko.gz) + gunzip "${mod}" + raw="${mod%.gz}" + "${SIGN_FILE}" sha256 "${SIGNING_KEY}" "${SIGNING_CERT}" "${raw}" || return 1 + gzip "${raw}" + ;; + esac + done < <(find "${MODULE_ROOT}" -type f \( -name "*.ko" -o -name "*.ko.xz" -o -name "*.ko.zst" -o -name "*.ko.gz" \) -print0) +} + +create_mok_enroll_unit() { + local UNIT_NAME="mok-enroll.service" + local UNIT_FILE="/usr/lib/systemd/system/${UNIT_NAME}" + local MOK_CERT="/usr/share/cert/MOK.der" + local TMP_DER + + TMP_DER="$(mktemp)" + + openssl x509 \ + -in "${SIGNING_CERT}" \ + -outform DER \ + -out "${TMP_DER}" || { + rm -f "${TMP_DER}" + return 1 + } + install -D -m 0644 "${TMP_DER}" "${MOK_CERT}" + rm -f "${TMP_DER}" + + install -D -m 0644 /dev/stdin "${UNIT_FILE}" < /dev/null; then + error "Missing Nvidia module: ${DIR}/${name}.*" + exit 1 + fi + done + + log "All Nvidia modules present." +fi + +log "Custom kernel installation complete." \ No newline at end of file diff --git a/modules/custom-kernel/custom-kernel.tsp b/modules/custom-kernel/custom-kernel.tsp new file mode 100644 index 00000000..dafa8a11 --- /dev/null +++ b/modules/custom-kernel/custom-kernel.tsp @@ -0,0 +1,69 @@ +import "@typespec/json-schema"; +using TypeSpec.JsonSchema; + +@jsonSchema("/modules/custom-kernel-latest.json") +model CustomKernelModuleLatest { + ...CustomKernelModuleV1; +} + +@jsonSchema("/modules/custom-kernel-v1.json") +model CustomKernelModuleV1 { + /** + * A module to install custom kernels and optionally the NVIDIA driver. + * Currently only CachyOS kernel variants are supported. + * https://blue-build.org/reference/modules/custom-kernel/ + */ + type: "custom-kernel"; + + /** + * Kernel variant to install. + * Currently only CachyOS kernels are supported. + * + * @default "cachyos-lto" + */ + kernel?: "cachyos-lto"; + + /** + * Whether to generate an initramfs for the installed kernel. + * + * @default false + */ + initramfs?: boolean; + + /** + * Whether to install and build the NVIDIA driver for this kernel. + * + * @default false + */ + nvidia?: boolean; + + /** + * Kernel and module signing configuration. + * + * If this object is defined, **all fields inside it must be provided**. + * Either define the full signing configuration or omit this entirely. + */ + sign?: KernelSigningConfigV1; +} + +/** + * Configuration for Secure Boot kernel and module signing. + * + * All properties are required if this object is present. + */ +model KernelSigningConfigV1 { + /** + * Path to the private key used for signing. + */ + key: string; + + /** + * Path to the public certificate in PEM format. + */ + cert: string; + + /** + * Password used for MOK enrollment. + */ + "mok-password": string; +} \ No newline at end of file diff --git a/modules/custom-kernel/module.yml b/modules/custom-kernel/module.yml new file mode 100644 index 00000000..568caf44 --- /dev/null +++ b/modules/custom-kernel/module.yml @@ -0,0 +1,17 @@ +name: custom-kernel +shortdesc: Install a custom kernel on Fedora-based images. +example: | + type: custom-kernel + kernel: cachyos-lto # Default: cachyos-lto, can be: cachyos, cachyos-lts, cachyos-rt, cachyos-lts-lto + initramfs: true # Default: false + nvidia: true # Default: false + sign: # Optional + key: /tmp/certs/MOK.key + cert: /path/to/MOK.pem + mok-password: BlueBuild + secrets: + - type: file + source: ./MOK.key + mount: + type: file + destination: /tmp/certs/MOK.key \ No newline at end of file From e0b620db9ef8170530823b8cfebfcd98c7c97a59 Mon Sep 17 00:00:00 2001 From: Cosku Bas Date: Fri, 6 Feb 2026 14:52:02 +0100 Subject: [PATCH 2/3] Silence module signing --- modules/custom-kernel/custom-kernel.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/custom-kernel/custom-kernel.sh b/modules/custom-kernel/custom-kernel.sh index 589fa4ad..5caa4d0d 100644 --- a/modules/custom-kernel/custom-kernel.sh +++ b/modules/custom-kernel/custom-kernel.sh @@ -365,22 +365,22 @@ sign_kernel_modules() { "${SIGN_FILE}" sha256 "${SIGNING_KEY}" "${SIGNING_CERT}" "${mod}" || return 1 ;; *.ko.xz) - xz -d "${mod}" + xz -d -q "${mod}" raw="${mod%.xz}" "${SIGN_FILE}" sha256 "${SIGNING_KEY}" "${SIGNING_CERT}" "${raw}" || return 1 - xz -z "${raw}" + xz -z -q "${raw}" ;; *.ko.zst) - zstd -d --rm "${mod}" + zstd -d -q --rm "${mod}" raw="${mod%.zst}" "${SIGN_FILE}" sha256 "${SIGNING_KEY}" "${SIGNING_CERT}" "${raw}" || return 1 zstd -q "${raw}" ;; *.ko.gz) - gunzip "${mod}" + gunzip -q "${mod}" raw="${mod%.gz}" "${SIGN_FILE}" sha256 "${SIGNING_KEY}" "${SIGNING_CERT}" "${raw}" || return 1 - gzip "${raw}" + gzip -q "${raw}" ;; esac done < <(find "${MODULE_ROOT}" -type f \( -name "*.ko" -o -name "*.ko.xz" -o -name "*.ko.zst" -o -name "*.ko.gz" \) -print0) From 6b7a937ec0545bf63735225b5cd0ba4217e4dae0 Mon Sep 17 00:00:00 2001 From: Cosku Bas Date: Fri, 6 Feb 2026 21:32:13 +0100 Subject: [PATCH 3/3] Fix akmods build failure and add logging --- modules/custom-kernel/custom-kernel.sh | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/modules/custom-kernel/custom-kernel.sh b/modules/custom-kernel/custom-kernel.sh index 5caa4d0d..510338e6 100644 --- a/modules/custom-kernel/custom-kernel.sh +++ b/modules/custom-kernel/custom-kernel.sh @@ -232,9 +232,26 @@ if [[ ${NVIDIA} == true ]]; then dnf install -y --setopt=install_weak_deps=False --setopt=tsflags=noscripts \ akmod-nvidia \ nvidia-kmod-common \ - nvidia-modprobe + nvidia-modprobe \ + gcc-c++ akmods --force --verbose --kernels "${KERNEL_VERSION}" --kmod "nvidia" + # akmods always fails with exit 0 so we have to check explicitly + FAIL_LOG_GLOB=/var/cache/akmods/nvidia/*-for-${KERNEL_VERSION}.failed.log + + shopt -s nullglob + FAIL_LOGS=( ${FAIL_LOG_GLOB} ) + shopt -u nullglob + + if (( ${#FAIL_LOGS[@]} )); then + error "Nvidia akmod build failed" + for f in "${FAIL_LOGS[@]}"; do + cat "${f}" || log "Failed to read ${f}" + log "--------------" + done + exit 1 + fi + log "Restoring akmodsbuild script." restore_akmodsbuild