Files
kubesolo-os/build/scripts/build-kernel.sh
Adolfo Delorenzo d51618badb build: separate generic ARM64 from Raspberry Pi kernel builds
Splits the ARM64 build into two tracks per docs/arm64-architecture.md:

Generic ARM64 (mainline kernel.org, UEFI, virtio, GRUB):
- New build/scripts/build-kernel-arm64.sh builds mainline LTS (6.12.x by default)
  from arm64 defconfig + shared container fragment + arm64-virt enables
  (VIRTIO_*, EFI_STUB, NVMe). Output: build/cache/kernel-arm64-generic/.
- New Makefile targets: kernel-arm64, rootfs-arm64 (now consumes the mainline
  kernel modules via TARGET_VARIANT=generic).
- versions.env: pin MAINLINE_KERNEL_VERSION=6.12.10, declare cdn.kernel.org URL
  and SHA256 placeholder.

Raspberry Pi (raspberrypi/linux fork, custom DTBs, autoboot.txt):
- build-kernel-arm64.sh (RPi-flavoured) renamed to build-kernel-rpi.sh; cache
  dir renamed from custom-kernel-arm64 to custom-kernel-rpi.
- New Makefile targets: kernel-rpi, rootfs-arm64-rpi (uses TARGET_VARIANT=rpi).
- rpi-image now depends on rootfs-arm64-rpi + kernel-rpi instead of the generic
  rootfs-arm64.
- create-rpi-image.sh + inject-kubesolo.sh updated to reference the new cache
  path. inject-kubesolo.sh now takes a TARGET_VARIANT env var (rpi|generic) to
  select which ARM64 kernel modules to consume.

Shared substrate:
- rpi-kernel-config.fragment renamed to kernel-container.fragment. The contents
  were never RPi-specific (cgroup, namespaces, AppArmor, netfilter) — just
  misnamed. Extended with extra subsystem disables (KVM, WLAN, CFG80211,
  INFINIBAND, PCMCIA, HAMRADIO, ISDN, ATM, INPUT_JOYSTICK, INPUT_TABLET, FPGA)
  and CONFIG_LSM=lockdown,yama,apparmor.
- build-kernel.sh (x86) refactored to apply the shared fragment via a generic
  apply_fragment function (two-pass for the TC stock config security dance),
  killing ~50 lines of inline config duplication.

Note: rename detection shows build-kernel-arm64.sh as 'modified' because the
new file at that path is the mainline build, while the old RPi-flavoured
content lives in build-kernel-rpi.sh (which appears as a new file). The git
log for build-kernel-rpi.sh is empty; the RPi history is preserved at the
original path until this commit.

No actual kernel build runs in this commit — that's Phase 3 work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:30:11 -06:00

205 lines
7.4 KiB
Bash
Executable File

#!/bin/bash
# build-kernel.sh — Build custom Tiny Core kernel with CONFIG_CGROUP_BPF=y
#
# The stock Tiny Core 17.0 kernel (6.18.2-tinycore64) lacks CONFIG_CGROUP_BPF,
# which is required for cgroup v2 device control in runc/containerd.
# This script downloads the TC-patched kernel source, enables CONFIG_CGROUP_BPF,
# and builds vmlinuz + modules.
#
# Output is cached in $CACHE_DIR/custom-kernel/ and reused across builds.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
CACHE_DIR="${CACHE_DIR:-$PROJECT_ROOT/build/cache}"
# shellcheck source=../config/versions.env
. "$SCRIPT_DIR/../config/versions.env"
KVER="6.18.2-tinycore64"
KERNEL_BASE_URL="https://distro.ibiblio.org/tinycorelinux/${TINYCORE_VERSION%%.*}.x/${TINYCORE_ARCH}/release/src/kernel"
KERNEL_SRC_URL="${KERNEL_BASE_URL}/linux-6.18.2-patched.tar.xz"
KERNEL_CFG_URL="${KERNEL_BASE_URL}/config-${KVER}"
CUSTOM_KERNEL_DIR="$CACHE_DIR/custom-kernel"
CUSTOM_VMLINUZ="$CUSTOM_KERNEL_DIR/vmlinuz"
CUSTOM_MODULES="$CUSTOM_KERNEL_DIR/modules"
mkdir -p "$CACHE_DIR" "$CUSTOM_KERNEL_DIR"
# --- Skip if already built ---
if [ -f "$CUSTOM_VMLINUZ" ] && [ -d "$CUSTOM_MODULES/lib/modules/$KVER" ]; then
echo "==> Custom kernel already built (cached)"
echo " vmlinuz: $CUSTOM_VMLINUZ ($(du -h "$CUSTOM_VMLINUZ" | cut -f1))"
MOD_COUNT=$(find "$CUSTOM_MODULES/lib/modules/$KVER" -name '*.ko*' | wc -l)
echo " Modules: $MOD_COUNT modules in $CUSTOM_MODULES/lib/modules/$KVER"
exit 0
fi
echo "==> Building custom kernel with CONFIG_CGROUP_BPF=y..."
echo " Kernel version: $KVER"
# --- Download kernel source ---
KERNEL_SRC_ARCHIVE="$CACHE_DIR/linux-6.18.2-patched.tar.xz"
if [ ! -f "$KERNEL_SRC_ARCHIVE" ]; then
echo "==> Downloading kernel source (~149 MB)..."
echo " URL: $KERNEL_SRC_URL"
wget -q --show-progress -O "$KERNEL_SRC_ARCHIVE" "$KERNEL_SRC_URL" 2>/dev/null || \
curl -fSL "$KERNEL_SRC_URL" -o "$KERNEL_SRC_ARCHIVE"
echo " Downloaded: $(du -h "$KERNEL_SRC_ARCHIVE" | cut -f1)"
else
echo "==> Kernel source already cached: $(du -h "$KERNEL_SRC_ARCHIVE" | cut -f1)"
fi
# --- Download stock config ---
KERNEL_CFG="$CACHE_DIR/config-${KVER}"
if [ ! -f "$KERNEL_CFG" ]; then
echo "==> Downloading stock kernel config..."
echo " URL: $KERNEL_CFG_URL"
wget -q -O "$KERNEL_CFG" "$KERNEL_CFG_URL" 2>/dev/null || \
curl -fSL "$KERNEL_CFG_URL" -o "$KERNEL_CFG"
else
echo "==> Stock kernel config already cached"
fi
# --- Extract source ---
# IMPORTANT: Must extract on a case-sensitive filesystem. The kernel source has
# files that differ only by case (e.g., xt_mark.h vs xt_MARK.h). If the cache
# is on macOS (case-insensitive APFS), extraction silently loses files.
# Use /tmp inside the container (ext4, case-sensitive) for the build.
KERNEL_BUILD_DIR="/tmp/kernel-build"
rm -rf "$KERNEL_BUILD_DIR"
mkdir -p "$KERNEL_BUILD_DIR"
echo "==> Extracting kernel source (case-sensitive filesystem)..."
tar -xf "$KERNEL_SRC_ARCHIVE" -C "$KERNEL_BUILD_DIR"
# Find the extracted source directory (could be linux-6.18.2 or linux-6.18.2-patched)
KERNEL_SRC_DIR=$(find "$KERNEL_BUILD_DIR" -maxdepth 1 -type d -name 'linux-*' | head -1)
if [ -z "$KERNEL_SRC_DIR" ]; then
echo "ERROR: Could not find kernel source directory after extraction"
ls -la "$KERNEL_BUILD_DIR"/
exit 1
fi
echo " Source dir: $(basename "$KERNEL_SRC_DIR")"
cd "$KERNEL_SRC_DIR"
# --- Apply stock config + shared container-config fragment ---
echo "==> Applying stock Tiny Core config..."
cp "$KERNEL_CFG" .config
CONFIG_FRAGMENT="$PROJECT_ROOT/build/config/kernel-container.fragment"
if [ ! -f "$CONFIG_FRAGMENT" ]; then
echo "ERROR: Config fragment not found: $CONFIG_FRAGMENT"
exit 1
fi
# Apply the fragment: each "CONFIG_X=v" line becomes the right scripts/config
# invocation; "# CONFIG_X is not set" comments become --disable.
apply_fragment() {
local fragment="$1"
while IFS= read -r line; do
case "$line" in
"# CONFIG_"*" is not set")
key=$(echo "$line" | sed -n 's/^# \(CONFIG_[A-Z0-9_]*\) is not set$/\1/p')
[ -n "$key" ] && ./scripts/config --disable "${key#CONFIG_}"
continue
;;
\#*|"") continue ;;
esac
key="${line%%=*}"
value="${line#*=}"
case "$value" in
y) ./scripts/config --enable "$key" ;;
m) ./scripts/config --module "$key" ;;
n) ./scripts/config --disable "${key#CONFIG_}" ;;
*) ./scripts/config --set-str "$key" "$value" ;;
esac
done < "$fragment"
}
# Two-pass apply: TC's stock config has CONFIG_SECURITY disabled, so olddefconfig
# strips the security subtree before its dependencies resolve. Re-applying the
# fragment after the first olddefconfig restores those entries.
echo "==> Applying kernel-container.fragment (pass 1)..."
apply_fragment "$CONFIG_FRAGMENT"
make olddefconfig
echo "==> Applying kernel-container.fragment (pass 2)..."
apply_fragment "$CONFIG_FRAGMENT"
make olddefconfig
# Verify critical configs are set
if ! grep -q 'CONFIG_CGROUP_BPF=y' .config; then
echo "ERROR: CONFIG_CGROUP_BPF not set after olddefconfig"
grep 'CGROUP_BPF' .config || echo " (CGROUP_BPF not found in .config)"
echo ""
echo "Prerequisites check:"
grep -E 'CONFIG_BPF=|CONFIG_BPF_SYSCALL=' .config || echo " BPF not found"
exit 1
fi
echo " CONFIG_CGROUP_BPF=y confirmed"
if ! grep -q 'CONFIG_SECURITY_APPARMOR=y' .config; then
echo "ERROR: CONFIG_SECURITY_APPARMOR not set after olddefconfig"
echo " Security-related configs:"
grep -E 'CONFIG_SECURITY=|CONFIG_SECURITYFS=|CONFIG_SECURITY_APPARMOR=' .config
exit 1
fi
echo " CONFIG_SECURITY_APPARMOR=y confirmed"
if ! grep -q 'CONFIG_AUDIT=y' .config; then
echo "ERROR: CONFIG_AUDIT not set after olddefconfig"
exit 1
fi
echo " CONFIG_AUDIT=y confirmed"
# Show what changed (security-related)
echo " Key config values:"
grep -E 'CONFIG_SECURITY=|CONFIG_SECURITY_APPARMOR=|CONFIG_AUDIT=|CONFIG_LSM=|CONFIG_CGROUP_BPF=' .config | sed 's/^/ /'
# --- Build kernel + modules ---
NPROC=$(nproc 2>/dev/null || echo 4)
echo ""
echo "==> Building kernel (${NPROC} parallel jobs)..."
echo " This may take 15-25 minutes..."
make -j"$NPROC" bzImage modules 2>&1
echo "==> Kernel build complete"
# --- Install to staging ---
echo "==> Installing vmlinuz..."
cp arch/x86/boot/bzImage "$CUSTOM_VMLINUZ"
echo "==> Installing modules (stripped)..."
rm -rf "$CUSTOM_MODULES"
mkdir -p "$CUSTOM_MODULES"
make INSTALL_MOD_STRIP=1 modules_install INSTALL_MOD_PATH="$CUSTOM_MODULES"
# Remove build/source symlinks (they point to the build dir which won't exist in rootfs)
rm -f "$CUSTOM_MODULES/lib/modules/$KVER/build"
rm -f "$CUSTOM_MODULES/lib/modules/$KVER/source"
# Run depmod to generate proper module dependency files
echo "==> Running depmod..."
depmod -a -b "$CUSTOM_MODULES" "$KVER" 2>/dev/null || true
# Save the final config for reference
cp .config "$CUSTOM_KERNEL_DIR/.config"
# --- Clean up build dir (large, ~1.5 GB) ---
echo "==> Cleaning kernel build directory..."
cd /
rm -rf "$KERNEL_BUILD_DIR"
# --- Summary ---
echo ""
echo "==> Custom kernel build complete:"
echo " vmlinuz: $CUSTOM_VMLINUZ ($(du -h "$CUSTOM_VMLINUZ" | cut -f1))"
MOD_COUNT=$(find "$CUSTOM_MODULES/lib/modules/$KVER" -name '*.ko*' | wc -l)
echo " Modules: $MOD_COUNT modules"
echo " Modules size: $(du -sh "$CUSTOM_MODULES/lib/modules/$KVER" | cut -f1)"
echo ""