feat: add security hardening, AppArmor, and ARM64 Raspberry Pi support (Phase 6)
Security hardening: bind kubeconfig server to localhost, mount hardening (noexec/nosuid/nodev on tmpfs), sysctl network hardening, kernel module loading lock after boot, SHA256 checksum verification for downloads, kernel AppArmor + Audit support, complain-mode AppArmor profiles for containerd and kubelet, and security integration test. ARM64 Raspberry Pi support: piCore64 base extraction, RPi kernel build from raspberrypi/linux fork, RPi firmware fetch, SD card image with 4- partition GPT and tryboot A/B mechanism, BootEnv Go interface abstracting GRUB vs RPi boot environments, architecture-aware build scripts, QEMU aarch64 dev VM and boot test. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
syslinux \
|
||||
syslinux-common \
|
||||
syslinux-utils \
|
||||
apparmor \
|
||||
apparmor-utils \
|
||||
gcc-aarch64-linux-gnu \
|
||||
binutils-aarch64-linux-gnu \
|
||||
git \
|
||||
wget \
|
||||
xorriso \
|
||||
xz-utils \
|
||||
|
||||
@@ -128,7 +128,12 @@ echo "Security:"
|
||||
check_config CONFIG_SECCOMP recommended "Seccomp (container security)"
|
||||
check_config CONFIG_SECCOMP_FILTER recommended "Seccomp BPF filter"
|
||||
check_config CONFIG_BPF_SYSCALL recommended "BPF syscall"
|
||||
check_config CONFIG_AUDIT recommended "Audit framework"
|
||||
check_config CONFIG_AUDIT mandatory "Audit framework"
|
||||
check_config CONFIG_AUDITSYSCALL mandatory "Audit system call events"
|
||||
check_config CONFIG_SECURITY mandatory "Security framework"
|
||||
check_config CONFIG_SECURITYFS mandatory "Security filesystem"
|
||||
check_config CONFIG_SECURITY_APPARMOR mandatory "AppArmor LSM"
|
||||
check_config CONFIG_SECURITY_NETWORK recommended "Network security hooks"
|
||||
echo ""
|
||||
|
||||
# --- Crypto ---
|
||||
|
||||
81
build/config/modules-arm64.list
Normal file
81
build/config/modules-arm64.list
Normal file
@@ -0,0 +1,81 @@
|
||||
# Kernel modules loaded at boot by init (ARM64 / Raspberry Pi)
|
||||
# One module per line. Lines starting with # are ignored.
|
||||
# Modules are loaded in order listed — dependencies must come first.
|
||||
|
||||
# Network device drivers (loaded early so interfaces are available)
|
||||
# Note: no e1000/e1000e on ARM64 — those are x86 Intel NIC drivers
|
||||
virtio_net
|
||||
|
||||
# Virtio support (for QEMU VMs — block, entropy)
|
||||
virtio_blk
|
||||
virtio_rng
|
||||
|
||||
# Raspberry Pi specific (USB Ethernet on Pi 4 is built-in, no module needed)
|
||||
# Pi 5 uses PCIe ethernet, also typically built-in
|
||||
|
||||
# Filesystem — overlay (required for containerd)
|
||||
overlay
|
||||
|
||||
# Netfilter dependencies (must load before conntrack)
|
||||
nf_defrag_ipv4
|
||||
nf_defrag_ipv6
|
||||
|
||||
# Netfilter / connection tracking (required for kube-proxy)
|
||||
nf_conntrack
|
||||
nf_nat
|
||||
nf_conntrack_netlink
|
||||
|
||||
# nftables (modern iptables backend)
|
||||
nf_tables
|
||||
nft_compat
|
||||
nft_chain_nat
|
||||
nft_ct
|
||||
nft_masq
|
||||
nft_nat
|
||||
nft_redir
|
||||
|
||||
# Netfilter xt match/target modules (used by kube-proxy iptables rules via nft_compat)
|
||||
xt_conntrack
|
||||
xt_MASQUERADE
|
||||
xt_mark
|
||||
xt_comment
|
||||
xt_multiport
|
||||
xt_nat
|
||||
xt_addrtype
|
||||
xt_connmark
|
||||
xt_REDIRECT
|
||||
xt_recent
|
||||
xt_statistic
|
||||
xt_set
|
||||
|
||||
# nft extras (reject, fib — used by kube-proxy nf_tables rules)
|
||||
nft_reject
|
||||
nft_reject_ipv4
|
||||
nft_reject_ipv6
|
||||
nft_fib
|
||||
nft_fib_ipv4
|
||||
nft_fib_ipv6
|
||||
|
||||
# Reject targets (used by kube-proxy iptables-restore rules)
|
||||
nf_reject_ipv4
|
||||
nf_reject_ipv6
|
||||
ipt_REJECT
|
||||
ip6t_REJECT
|
||||
|
||||
# nfacct extension (kube-proxy probes for it)
|
||||
xt_nfacct
|
||||
|
||||
# Networking — bridge and netfilter (required for K8s pod networking)
|
||||
# Load order: llc → stp → bridge → br_netfilter
|
||||
llc
|
||||
stp
|
||||
bridge
|
||||
br_netfilter
|
||||
veth
|
||||
vxlan
|
||||
|
||||
# IPVS — useful for kube-proxy IPVS mode and CNI plugins
|
||||
ip_vs
|
||||
ip_vs_rr
|
||||
ip_vs_wrr
|
||||
ip_vs_sh
|
||||
69
build/config/rpi-kernel-config.fragment
Normal file
69
build/config/rpi-kernel-config.fragment
Normal file
@@ -0,0 +1,69 @@
|
||||
# KubeSolo OS — Raspberry Pi kernel config overrides
|
||||
# Applied on top of bcm2711_defconfig (Pi 4) or bcm2712_defconfig (Pi 5)
|
||||
# These ensure container runtime support is enabled.
|
||||
|
||||
# cgroup v2 (mandatory for containerd/runc)
|
||||
CONFIG_CGROUPS=y
|
||||
CONFIG_CGROUP_CPUACCT=y
|
||||
CONFIG_CGROUP_DEVICE=y
|
||||
CONFIG_CGROUP_FREEZER=y
|
||||
CONFIG_CGROUP_SCHED=y
|
||||
CONFIG_CGROUP_PIDS=y
|
||||
CONFIG_MEMCG=y
|
||||
CONFIG_CGROUP_BPF=y
|
||||
CONFIG_CFS_BANDWIDTH=y
|
||||
|
||||
# BPF (required for cgroup v2 device control)
|
||||
CONFIG_BPF=y
|
||||
CONFIG_BPF_SYSCALL=y
|
||||
|
||||
# Namespaces (mandatory for containers)
|
||||
CONFIG_NAMESPACES=y
|
||||
CONFIG_NET_NS=y
|
||||
CONFIG_PID_NS=y
|
||||
CONFIG_USER_NS=y
|
||||
CONFIG_UTS_NS=y
|
||||
CONFIG_IPC_NS=y
|
||||
|
||||
# Device management
|
||||
CONFIG_DEVTMPFS=y
|
||||
CONFIG_DEVTMPFS_MOUNT=y
|
||||
|
||||
# Filesystem
|
||||
CONFIG_OVERLAY_FS=y
|
||||
CONFIG_SQUASHFS=y
|
||||
CONFIG_EXT4_FS=y
|
||||
CONFIG_VFAT_FS=y
|
||||
|
||||
# Networking
|
||||
CONFIG_BRIDGE=m
|
||||
CONFIG_NETFILTER=y
|
||||
CONFIG_NF_CONNTRACK=m
|
||||
CONFIG_NF_NAT=m
|
||||
CONFIG_NF_TABLES=m
|
||||
CONFIG_VETH=m
|
||||
CONFIG_VXLAN=m
|
||||
|
||||
# Security: AppArmor + Audit
|
||||
CONFIG_AUDIT=y
|
||||
CONFIG_AUDITSYSCALL=y
|
||||
CONFIG_SECURITY=y
|
||||
CONFIG_SECURITYFS=y
|
||||
CONFIG_SECURITY_NETWORK=y
|
||||
CONFIG_SECURITY_APPARMOR=y
|
||||
CONFIG_DEFAULT_SECURITY_APPARMOR=y
|
||||
|
||||
# Security: seccomp
|
||||
CONFIG_SECCOMP=y
|
||||
CONFIG_SECCOMP_FILTER=y
|
||||
|
||||
# Crypto (image verification)
|
||||
CONFIG_CRYPTO_SHA256=y
|
||||
|
||||
# Disable unnecessary subsystems for edge appliance
|
||||
# CONFIG_SOUND is not set
|
||||
# CONFIG_DRM is not set
|
||||
# CONFIG_MEDIA_SUPPORT is not set
|
||||
# CONFIG_WIRELESS is not set
|
||||
# CONFIG_BT is not set
|
||||
# CONFIG_NFC is not set
|
||||
@@ -15,5 +15,28 @@ KUBESOLO_INSTALL_URL=https://get.kubesolo.io
|
||||
GRUB_VERSION=2.12
|
||||
SYSLINUX_VERSION=6.03
|
||||
|
||||
# SHA256 checksums for supply chain verification
|
||||
# Populate by running: sha256sum build/cache/<file>
|
||||
# Leave empty to skip verification (useful for first fetch)
|
||||
TINYCORE_ISO_SHA256=""
|
||||
KUBESOLO_SHA256=""
|
||||
NETFILTER_TCZ_SHA256=""
|
||||
NET_BRIDGING_TCZ_SHA256=""
|
||||
IPTABLES_TCZ_SHA256=""
|
||||
|
||||
# piCore64 (ARM64 — Raspberry Pi)
|
||||
PICORE_VERSION=15.0
|
||||
PICORE_ARCH=aarch64
|
||||
PICORE_IMAGE=piCore-${PICORE_VERSION}.img.gz
|
||||
PICORE_IMAGE_URL=http://www.tinycorelinux.net/${PICORE_VERSION%%.*}.x/${PICORE_ARCH}/releases/RPi/${PICORE_IMAGE}
|
||||
|
||||
# Raspberry Pi firmware (boot blobs, DTBs)
|
||||
RPI_FIRMWARE_TAG=1.20240529
|
||||
RPI_FIRMWARE_URL=https://github.com/raspberrypi/firmware/archive/refs/tags/${RPI_FIRMWARE_TAG}.tar.gz
|
||||
|
||||
# Raspberry Pi kernel source
|
||||
RPI_KERNEL_BRANCH=rpi-6.6.y
|
||||
RPI_KERNEL_REPO=https://github.com/raspberrypi/linux
|
||||
|
||||
# Output naming
|
||||
OS_NAME=kubesolo-os
|
||||
|
||||
52
build/rootfs/etc/apparmor.d/containerd
Normal file
52
build/rootfs/etc/apparmor.d/containerd
Normal file
@@ -0,0 +1,52 @@
|
||||
# AppArmor profile for containerd
|
||||
# Start in complain mode to log without blocking
|
||||
|
||||
#include <tunables/global>
|
||||
|
||||
profile containerd /usr/bin/containerd flags=(complain) {
|
||||
#include <abstractions/base>
|
||||
|
||||
# Binary and shared libraries
|
||||
/usr/bin/containerd mr,
|
||||
/usr/lib/** mr,
|
||||
/lib/** mr,
|
||||
|
||||
# Containerd runtime state
|
||||
/var/lib/containerd/** rw,
|
||||
/run/containerd/** rw,
|
||||
|
||||
# Container image layers and snapshots
|
||||
/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/** rw,
|
||||
|
||||
# CNI networking
|
||||
/etc/cni/** r,
|
||||
/opt/cni/bin/** ix,
|
||||
|
||||
# Proc and sys access for containers
|
||||
@{PROC}/** r,
|
||||
/sys/** r,
|
||||
|
||||
# Device access for containers
|
||||
/dev/** rw,
|
||||
|
||||
# Network access
|
||||
network,
|
||||
|
||||
# Container runtime needs broad capabilities
|
||||
capability,
|
||||
|
||||
# Allow executing container runtimes
|
||||
/usr/bin/containerd-shim-runc-v2 ix,
|
||||
/usr/bin/runc ix,
|
||||
/usr/sbin/runc ix,
|
||||
|
||||
# Temp files
|
||||
/tmp/** rw,
|
||||
|
||||
# Log files
|
||||
/var/log/** rw,
|
||||
|
||||
# Signal handling for child processes
|
||||
signal,
|
||||
ptrace,
|
||||
}
|
||||
56
build/rootfs/etc/apparmor.d/kubelet
Normal file
56
build/rootfs/etc/apparmor.d/kubelet
Normal file
@@ -0,0 +1,56 @@
|
||||
# AppArmor profile for kubesolo (kubelet + control plane)
|
||||
# Start in complain mode to log without blocking
|
||||
|
||||
#include <tunables/global>
|
||||
|
||||
profile kubesolo /usr/bin/kubesolo flags=(complain) {
|
||||
#include <abstractions/base>
|
||||
|
||||
# Binary and shared libraries
|
||||
/usr/bin/kubesolo mr,
|
||||
/usr/lib/** mr,
|
||||
/lib/** mr,
|
||||
|
||||
# KubeSolo state (etcd/SQLite, certificates, manifests)
|
||||
/var/lib/kubesolo/** rw,
|
||||
|
||||
# KubeSolo configuration
|
||||
/etc/kubesolo/** r,
|
||||
|
||||
# Containerd socket
|
||||
/run/containerd/** rw,
|
||||
|
||||
# CNI networking
|
||||
/etc/cni/** r,
|
||||
/opt/cni/bin/** ix,
|
||||
|
||||
# Proc and sys access
|
||||
@{PROC}/** r,
|
||||
/sys/** r,
|
||||
|
||||
# Device access
|
||||
/dev/** rw,
|
||||
|
||||
# Network access (API server, kubelet, etcd)
|
||||
network,
|
||||
|
||||
# Control plane needs broad capabilities
|
||||
capability,
|
||||
|
||||
# Kubectl and other tools
|
||||
/usr/bin/kubectl ix,
|
||||
/usr/local/bin/** ix,
|
||||
|
||||
# Temp files
|
||||
/tmp/** rw,
|
||||
|
||||
# Log files
|
||||
/var/log/** rw,
|
||||
|
||||
# Kubelet needs to manage pods
|
||||
/var/lib/kubelet/** rw,
|
||||
|
||||
# Signal handling
|
||||
signal,
|
||||
ptrace,
|
||||
}
|
||||
27
build/rootfs/etc/sysctl.d/security.conf
Normal file
27
build/rootfs/etc/sysctl.d/security.conf
Normal file
@@ -0,0 +1,27 @@
|
||||
# Security hardening — applied automatically by 40-sysctl.sh
|
||||
# Network: anti-spoofing
|
||||
net.ipv4.conf.all.rp_filter = 1
|
||||
net.ipv4.conf.default.rp_filter = 1
|
||||
# Network: SYN flood protection
|
||||
net.ipv4.tcp_syncookies = 1
|
||||
# Network: ICMP hardening
|
||||
net.ipv4.conf.all.accept_redirects = 0
|
||||
net.ipv4.conf.default.accept_redirects = 0
|
||||
net.ipv4.conf.all.send_redirects = 0
|
||||
net.ipv4.conf.default.send_redirects = 0
|
||||
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
||||
net.ipv4.icmp_ignore_bogus_error_responses = 1
|
||||
net.ipv4.conf.all.log_martians = 1
|
||||
# Network: IPv6 hardening
|
||||
net.ipv6.conf.all.accept_redirects = 0
|
||||
net.ipv6.conf.default.accept_redirects = 0
|
||||
net.ipv6.conf.all.accept_ra = 0
|
||||
# Network: source routing
|
||||
net.ipv4.conf.all.accept_source_route = 0
|
||||
net.ipv4.conf.default.accept_source_route = 0
|
||||
# Kernel: information disclosure
|
||||
kernel.kptr_restrict = 2
|
||||
kernel.dmesg_restrict = 1
|
||||
kernel.perf_event_paranoid = 3
|
||||
# Kernel: core dump safety
|
||||
fs.suid_dumpable = 0
|
||||
158
build/scripts/build-kernel-arm64.sh
Executable file
158
build/scripts/build-kernel-arm64.sh
Executable file
@@ -0,0 +1,158 @@
|
||||
#!/bin/bash
|
||||
# build-kernel-arm64.sh — Build ARM64 kernel for Raspberry Pi 4/5
|
||||
#
|
||||
# Uses the official raspberrypi/linux kernel fork with bcm2711_defconfig
|
||||
# as the base, overlaid with container-critical config options.
|
||||
#
|
||||
# Output is cached in $CACHE_DIR/custom-kernel-arm64/ and reused across builds.
|
||||
#
|
||||
# Requirements:
|
||||
# - gcc-aarch64-linux-gnu (cross-compiler)
|
||||
# - Standard kernel build deps (bc, bison, flex, etc.)
|
||||
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"
|
||||
|
||||
CUSTOM_KERNEL_DIR="$CACHE_DIR/custom-kernel-arm64"
|
||||
CUSTOM_IMAGE="$CUSTOM_KERNEL_DIR/Image"
|
||||
CUSTOM_MODULES="$CUSTOM_KERNEL_DIR/modules"
|
||||
CUSTOM_DTBS="$CUSTOM_KERNEL_DIR/dtbs"
|
||||
|
||||
mkdir -p "$CACHE_DIR" "$CUSTOM_KERNEL_DIR"
|
||||
|
||||
# --- Skip if already built ---
|
||||
if [ -f "$CUSTOM_IMAGE" ] && [ -d "$CUSTOM_MODULES" ]; then
|
||||
echo "==> ARM64 kernel already built (cached)"
|
||||
echo " Image: $CUSTOM_IMAGE ($(du -h "$CUSTOM_IMAGE" | cut -f1))"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- Verify cross-compiler ---
|
||||
if ! command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then
|
||||
echo "ERROR: aarch64-linux-gnu-gcc not found"
|
||||
echo "Install: apt-get install gcc-aarch64-linux-gnu"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Building ARM64 kernel for Raspberry Pi..."
|
||||
echo " Branch: $RPI_KERNEL_BRANCH"
|
||||
echo " Repo: $RPI_KERNEL_REPO"
|
||||
|
||||
# --- Download kernel source ---
|
||||
KERNEL_SRC_DIR="$CACHE_DIR/rpi-linux-${RPI_KERNEL_BRANCH}"
|
||||
if [ ! -d "$KERNEL_SRC_DIR" ]; then
|
||||
echo "==> Downloading RPi kernel source (shallow clone)..."
|
||||
git clone --depth 1 --branch "$RPI_KERNEL_BRANCH" \
|
||||
"$RPI_KERNEL_REPO" "$KERNEL_SRC_DIR"
|
||||
else
|
||||
echo "==> Kernel source already cached"
|
||||
fi
|
||||
|
||||
# --- Build in /tmp for case-sensitivity ---
|
||||
KERNEL_BUILD_DIR="/tmp/kernel-build-arm64"
|
||||
rm -rf "$KERNEL_BUILD_DIR"
|
||||
cp -a "$KERNEL_SRC_DIR" "$KERNEL_BUILD_DIR"
|
||||
|
||||
cd "$KERNEL_BUILD_DIR"
|
||||
|
||||
# --- Apply base config (Pi 4 = bcm2711) ---
|
||||
echo "==> Applying bcm2711_defconfig..."
|
||||
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- bcm2711_defconfig
|
||||
|
||||
# --- Apply container config overrides ---
|
||||
CONFIG_FRAGMENT="$PROJECT_ROOT/build/config/rpi-kernel-config.fragment"
|
||||
if [ -f "$CONFIG_FRAGMENT" ]; then
|
||||
echo "==> Applying KubeSolo config overrides..."
|
||||
while IFS= read -r line; do
|
||||
# Skip comments and empty lines
|
||||
case "$line" in \#*|"") 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 < "$CONFIG_FRAGMENT"
|
||||
fi
|
||||
|
||||
# Handle "is not set" comments as disables
|
||||
if [ -f "$CONFIG_FRAGMENT" ]; then
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
"# CONFIG_"*" is not set")
|
||||
key=$(echo "$line" | sed -n 's/^# \(CONFIG_[A-Z_]*\) is not set$/\1/p')
|
||||
[ -n "$key" ] && ./scripts/config --disable "${key#CONFIG_}"
|
||||
;;
|
||||
esac
|
||||
done < "$CONFIG_FRAGMENT"
|
||||
fi
|
||||
|
||||
# Resolve dependencies
|
||||
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- olddefconfig
|
||||
|
||||
# --- Build kernel + modules + DTBs ---
|
||||
NPROC=$(nproc 2>/dev/null || echo 4)
|
||||
echo ""
|
||||
echo "==> Building ARM64 kernel (${NPROC} parallel jobs)..."
|
||||
echo " This may take 20-30 minutes..."
|
||||
|
||||
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j"$NPROC" Image modules dtbs 2>&1
|
||||
|
||||
echo "==> ARM64 kernel build complete"
|
||||
|
||||
# --- Install to staging ---
|
||||
echo "==> Installing Image..."
|
||||
cp arch/arm64/boot/Image "$CUSTOM_IMAGE"
|
||||
|
||||
echo "==> Installing modules (stripped)..."
|
||||
rm -rf "$CUSTOM_MODULES"
|
||||
mkdir -p "$CUSTOM_MODULES"
|
||||
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
|
||||
INSTALL_MOD_STRIP=1 modules_install INSTALL_MOD_PATH="$CUSTOM_MODULES"
|
||||
|
||||
# Remove build/source symlinks
|
||||
KVER=$(ls "$CUSTOM_MODULES/lib/modules/" | head -1)
|
||||
rm -f "$CUSTOM_MODULES/lib/modules/$KVER/build"
|
||||
rm -f "$CUSTOM_MODULES/lib/modules/$KVER/source"
|
||||
|
||||
# Run depmod
|
||||
depmod -a -b "$CUSTOM_MODULES" "$KVER" 2>/dev/null || true
|
||||
|
||||
echo "==> Installing Device Tree Blobs..."
|
||||
rm -rf "$CUSTOM_DTBS"
|
||||
mkdir -p "$CUSTOM_DTBS/overlays"
|
||||
# Pi 4 DTBs
|
||||
cp arch/arm64/boot/dts/broadcom/bcm2711*.dtb "$CUSTOM_DTBS/" 2>/dev/null || true
|
||||
# Pi 5 DTBs
|
||||
cp arch/arm64/boot/dts/broadcom/bcm2712*.dtb "$CUSTOM_DTBS/" 2>/dev/null || true
|
||||
# Overlays we need
|
||||
for overlay in disable-wifi disable-bt; do
|
||||
[ -f "arch/arm64/boot/dts/overlays/${overlay}.dtbo" ] && \
|
||||
cp "arch/arm64/boot/dts/overlays/${overlay}.dtbo" "$CUSTOM_DTBS/overlays/"
|
||||
done
|
||||
|
||||
# Save config for reference
|
||||
cp .config "$CUSTOM_KERNEL_DIR/.config"
|
||||
|
||||
# --- Clean up ---
|
||||
echo "==> Cleaning kernel build directory..."
|
||||
cd /
|
||||
rm -rf "$KERNEL_BUILD_DIR"
|
||||
|
||||
# --- Summary ---
|
||||
echo ""
|
||||
echo "==> ARM64 kernel build complete:"
|
||||
echo " Image: $CUSTOM_IMAGE ($(du -h "$CUSTOM_IMAGE" | cut -f1))"
|
||||
echo " Kernel ver: $KVER"
|
||||
MOD_COUNT=$(find "$CUSTOM_MODULES/lib/modules/$KVER" -name '*.ko*' 2>/dev/null | wc -l)
|
||||
echo " Modules: $MOD_COUNT"
|
||||
echo " Modules size: $(du -sh "$CUSTOM_MODULES/lib/modules/$KVER" 2>/dev/null | cut -f1)"
|
||||
echo " DTBs: $(ls "$CUSTOM_DTBS"/*.dtb 2>/dev/null | wc -l)"
|
||||
echo ""
|
||||
@@ -96,6 +96,17 @@ echo "==> Enabling required kernel configs..."
|
||||
./scripts/config --enable CONFIG_MEMCG
|
||||
./scripts/config --enable CONFIG_CFS_BANDWIDTH
|
||||
|
||||
# Security: AppArmor LSM + Audit subsystem
|
||||
echo "==> Enabling AppArmor + Audit kernel configs..."
|
||||
./scripts/config --enable CONFIG_AUDIT
|
||||
./scripts/config --enable CONFIG_AUDITSYSCALL
|
||||
./scripts/config --enable CONFIG_SECURITY
|
||||
./scripts/config --enable CONFIG_SECURITYFS
|
||||
./scripts/config --enable CONFIG_SECURITY_NETWORK
|
||||
./scripts/config --enable CONFIG_SECURITY_APPARMOR
|
||||
./scripts/config --set-str CONFIG_LSM "lockdown,yama,apparmor"
|
||||
./scripts/config --set-str CONFIG_DEFAULT_SECURITY "apparmor"
|
||||
|
||||
# --- Strip unnecessary subsystems for smallest footprint ---
|
||||
# This is a headless K8s edge appliance — no sound, GPU, wireless, etc.
|
||||
echo "==> Disabling unnecessary subsystems for minimal footprint..."
|
||||
|
||||
203
build/scripts/create-rpi-image.sh
Executable file
203
build/scripts/create-rpi-image.sh
Executable file
@@ -0,0 +1,203 @@
|
||||
#!/bin/bash
|
||||
# create-rpi-image.sh — Create a raw disk image for Raspberry Pi SD card
|
||||
#
|
||||
# Partition layout (GPT):
|
||||
# Part 1: Boot Control (16 MB, FAT32, label KSOLOCTL) — autoboot.txt only
|
||||
# Part 2: Boot A (256 MB, FAT32, label KSOLOA) — firmware + kernel + DTBs + initramfs
|
||||
# Part 3: Boot B (256 MB, FAT32, label KSOLOB) — same as Boot A (initially identical)
|
||||
# Part 4: Data (remaining of 2GB, ext4, label KSOLODATA)
|
||||
#
|
||||
# The RPi uses autoboot.txt in the control partition to implement A/B boot
|
||||
# via the tryboot mechanism (tryboot_a_b=1). Normal boot → partition 2 (Boot A),
|
||||
# tryboot → partition 3 (Boot B).
|
||||
#
|
||||
# Usage: build/scripts/create-rpi-image.sh
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# shellcheck source=../config/versions.env
|
||||
. "$SCRIPT_DIR/../config/versions.env"
|
||||
|
||||
ROOTFS_DIR="${ROOTFS_DIR:-$PROJECT_ROOT/build/rootfs-work}"
|
||||
OUTPUT_DIR="${OUTPUT_DIR:-$PROJECT_ROOT/output}"
|
||||
CACHE_DIR="${CACHE_DIR:-$PROJECT_ROOT/build/cache}"
|
||||
VERSION="$(cat "$PROJECT_ROOT/VERSION")"
|
||||
|
||||
IMG_OUTPUT="$OUTPUT_DIR/${OS_NAME}-${VERSION}.rpi.img"
|
||||
IMG_SIZE_MB="${IMG_SIZE_MB:-2048}" # 2 GB default
|
||||
|
||||
# ARM64 kernel (Image format, not bzImage)
|
||||
KERNEL="${CACHE_DIR}/custom-kernel-arm64/Image"
|
||||
INITRAMFS="${ROOTFS_DIR}/kubesolo-os.gz"
|
||||
RPI_FIRMWARE_DIR="${CACHE_DIR}/rpi-firmware"
|
||||
|
||||
echo "==> Creating ${IMG_SIZE_MB}MB Raspberry Pi disk image..."
|
||||
|
||||
# --- Verify required files ---
|
||||
MISSING=0
|
||||
for f in "$KERNEL" "$INITRAMFS"; do
|
||||
if [ ! -f "$f" ]; then
|
||||
echo "ERROR: Missing $f"
|
||||
MISSING=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ! -d "$RPI_FIRMWARE_DIR" ]; then
|
||||
echo "ERROR: Missing RPi firmware directory: $RPI_FIRMWARE_DIR"
|
||||
echo " Run 'make fetch' to download firmware blobs."
|
||||
MISSING=1
|
||||
fi
|
||||
|
||||
if [ "$MISSING" = "1" ]; then
|
||||
echo ""
|
||||
echo "Required files:"
|
||||
echo " Kernel: $KERNEL (run 'make kernel-arm64')"
|
||||
echo " Initramfs: $INITRAMFS (run 'make initramfs')"
|
||||
echo " Firmware: $RPI_FIRMWARE_DIR/ (run 'make fetch')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# --- Create sparse image ---
|
||||
dd if=/dev/zero of="$IMG_OUTPUT" bs=1M count=0 seek="$IMG_SIZE_MB" 2>/dev/null
|
||||
|
||||
# --- Partition table (GPT) ---
|
||||
# Part 1: Boot Control 16 MB FAT32
|
||||
# Part 2: Boot A 256 MB FAT32
|
||||
# Part 3: Boot B 256 MB FAT32
|
||||
# Part 4: Data remaining ext4
|
||||
sfdisk "$IMG_OUTPUT" << EOF
|
||||
label: gpt
|
||||
|
||||
# Boot Control partition: 16 MB
|
||||
start=2048, size=32768, type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7, name="BootCtl"
|
||||
# Boot A partition: 256 MB
|
||||
size=524288, type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7, name="BootA"
|
||||
# Boot B partition: 256 MB
|
||||
size=524288, type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7, name="BootB"
|
||||
# Data partition: remaining
|
||||
type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name="Data"
|
||||
EOF
|
||||
|
||||
# --- Set up loop device ---
|
||||
LOOP=$(losetup --show -fP "$IMG_OUTPUT")
|
||||
echo "==> Loop device: $LOOP"
|
||||
|
||||
MNT_CTL=$(mktemp -d)
|
||||
MNT_BOOTA=$(mktemp -d)
|
||||
MNT_BOOTB=$(mktemp -d)
|
||||
MNT_DATA=$(mktemp -d)
|
||||
|
||||
cleanup() {
|
||||
umount "$MNT_CTL" 2>/dev/null || true
|
||||
umount "$MNT_BOOTA" 2>/dev/null || true
|
||||
umount "$MNT_BOOTB" 2>/dev/null || true
|
||||
umount "$MNT_DATA" 2>/dev/null || true
|
||||
losetup -d "$LOOP" 2>/dev/null || true
|
||||
rm -rf "$MNT_CTL" "$MNT_BOOTA" "$MNT_BOOTB" "$MNT_DATA" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# --- Format partitions ---
|
||||
mkfs.vfat -F 32 -n KSOLOCTL "${LOOP}p1"
|
||||
mkfs.vfat -F 32 -n KSOLOA "${LOOP}p2"
|
||||
mkfs.vfat -F 32 -n KSOLOB "${LOOP}p3"
|
||||
mkfs.ext4 -q -L KSOLODATA "${LOOP}p4"
|
||||
|
||||
# --- Mount all partitions ---
|
||||
mount "${LOOP}p1" "$MNT_CTL"
|
||||
mount "${LOOP}p2" "$MNT_BOOTA"
|
||||
mount "${LOOP}p3" "$MNT_BOOTB"
|
||||
mount "${LOOP}p4" "$MNT_DATA"
|
||||
|
||||
# --- Boot Control Partition (KSOLOCTL) ---
|
||||
echo " Writing autoboot.txt..."
|
||||
cat > "$MNT_CTL/autoboot.txt" << 'AUTOBOOT'
|
||||
[all]
|
||||
tryboot_a_b=1
|
||||
boot_partition=2
|
||||
[tryboot]
|
||||
boot_partition=3
|
||||
AUTOBOOT
|
||||
|
||||
# --- Helper: populate a boot partition ---
|
||||
populate_boot_partition() {
|
||||
local MNT="$1"
|
||||
local LABEL="$2"
|
||||
|
||||
echo " Populating $LABEL..."
|
||||
|
||||
# config.txt — Raspberry Pi boot configuration
|
||||
cat > "$MNT/config.txt" << 'CFGTXT'
|
||||
arm_64bit=1
|
||||
kernel=kernel8.img
|
||||
initramfs kubesolo-os.gz followkernel
|
||||
enable_uart=1
|
||||
gpu_mem=16
|
||||
dtoverlay=disable-wifi
|
||||
dtoverlay=disable-bt
|
||||
CFGTXT
|
||||
|
||||
# cmdline.txt — kernel command line
|
||||
# Note: must be a single line
|
||||
echo "console=serial0,115200 console=tty1 kubesolo.data=LABEL=KSOLODATA quiet" > "$MNT/cmdline.txt"
|
||||
|
||||
# Copy kernel as kernel8.img (RPi 3/4/5 ARM64 convention)
|
||||
cp "$KERNEL" "$MNT/kernel8.img"
|
||||
|
||||
# Copy initramfs
|
||||
cp "$INITRAMFS" "$MNT/kubesolo-os.gz"
|
||||
|
||||
# Copy firmware blobs (start*.elf, fixup*.dat)
|
||||
if ls "$RPI_FIRMWARE_DIR"/start*.elf 1>/dev/null 2>&1; then
|
||||
cp "$RPI_FIRMWARE_DIR"/start*.elf "$MNT/"
|
||||
fi
|
||||
if ls "$RPI_FIRMWARE_DIR"/fixup*.dat 1>/dev/null 2>&1; then
|
||||
cp "$RPI_FIRMWARE_DIR"/fixup*.dat "$MNT/"
|
||||
fi
|
||||
if [ -f "$RPI_FIRMWARE_DIR/bootcode.bin" ]; then
|
||||
cp "$RPI_FIRMWARE_DIR/bootcode.bin" "$MNT/"
|
||||
fi
|
||||
|
||||
# Copy DTB overlays
|
||||
if [ -d "$RPI_FIRMWARE_DIR/overlays" ]; then
|
||||
cp -r "$RPI_FIRMWARE_DIR/overlays" "$MNT/"
|
||||
fi
|
||||
|
||||
# Copy base DTBs (bcm2710-*, bcm2711-*, bcm2712-*)
|
||||
if ls "$RPI_FIRMWARE_DIR"/bcm27*.dtb 1>/dev/null 2>&1; then
|
||||
cp "$RPI_FIRMWARE_DIR"/bcm27*.dtb "$MNT/"
|
||||
fi
|
||||
|
||||
# Write version marker
|
||||
echo "$VERSION" > "$MNT/version.txt"
|
||||
}
|
||||
|
||||
# --- Boot A Partition (KSOLOA) ---
|
||||
populate_boot_partition "$MNT_BOOTA" "Boot A (KSOLOA)"
|
||||
|
||||
# --- Boot B Partition (KSOLOB, initially identical) ---
|
||||
populate_boot_partition "$MNT_BOOTB" "Boot B (KSOLOB)"
|
||||
|
||||
# --- Data Partition (KSOLODATA) ---
|
||||
echo " Preparing data partition..."
|
||||
for dir in kubesolo containerd etc-kubesolo log usr-local network images; do
|
||||
mkdir -p "$MNT_DATA/$dir"
|
||||
done
|
||||
|
||||
sync
|
||||
|
||||
echo ""
|
||||
echo "==> Raspberry Pi disk image created: $IMG_OUTPUT"
|
||||
echo " Size: $(du -h "$IMG_OUTPUT" | cut -f1)"
|
||||
echo " Part 1 (KSOLOCTL): Boot control (autoboot.txt)"
|
||||
echo " Part 2 (KSOLOA): Boot A — firmware + kernel + initramfs"
|
||||
echo " Part 3 (KSOLOB): Boot B — firmware + kernel + initramfs"
|
||||
echo " Part 4 (KSOLODATA): Persistent K8s state"
|
||||
echo ""
|
||||
echo "Write to SD card with:"
|
||||
echo " sudo dd if=$IMG_OUTPUT of=/dev/sdX bs=4M status=progress"
|
||||
echo ""
|
||||
@@ -10,6 +10,94 @@ ROOTFS_DIR="${ROOTFS_DIR:-$PROJECT_ROOT/build/rootfs-work}"
|
||||
# shellcheck source=../config/versions.env
|
||||
. "$SCRIPT_DIR/../config/versions.env"
|
||||
|
||||
EXTRACT_ARCH="${TARGET_ARCH:-amd64}"
|
||||
|
||||
# Clean previous rootfs
|
||||
rm -rf "$ROOTFS_DIR"
|
||||
mkdir -p "$ROOTFS_DIR"
|
||||
|
||||
# =========================================================================
|
||||
# ARM64: piCore64 .img.gz extraction (SD card image, not ISO)
|
||||
# =========================================================================
|
||||
if [ "$EXTRACT_ARCH" = "arm64" ]; then
|
||||
PICORE_IMG="$CACHE_DIR/$PICORE_IMAGE"
|
||||
if [ ! -f "$PICORE_IMG" ]; then
|
||||
echo "ERROR: piCore64 image not found: $PICORE_IMG"
|
||||
echo "Run 'TARGET_ARCH=arm64 make fetch' first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Extracting piCore64 image: $PICORE_IMG"
|
||||
|
||||
# Decompress .img.gz to raw image
|
||||
PICORE_RAW="$CACHE_DIR/piCore-${PICORE_VERSION}.img"
|
||||
if [ ! -f "$PICORE_RAW" ]; then
|
||||
echo " Decompressing..."
|
||||
gunzip -k "$PICORE_IMG" 2>/dev/null || \
|
||||
zcat "$PICORE_IMG" > "$PICORE_RAW"
|
||||
fi
|
||||
|
||||
# Mount the piCore rootfs partition (partition 2 in the SD image)
|
||||
# Use losetup to find the partition offset
|
||||
IMG_MNT=$(mktemp -d)
|
||||
echo " Mounting piCore rootfs partition..."
|
||||
|
||||
# Get partition 2 offset (piCore layout: boot=p1, rootfs=p2)
|
||||
OFFSET=$(fdisk -l "$PICORE_RAW" 2>/dev/null | awk '/^.*img2/{print $2}')
|
||||
if [ -z "$OFFSET" ]; then
|
||||
# Fallback: try sfdisk
|
||||
OFFSET=$(sfdisk -d "$PICORE_RAW" 2>/dev/null | awk -F'[=,]' '/start=/{print $2; exit}' | tr -d ' ')
|
||||
fi
|
||||
if [ -z "$OFFSET" ]; then
|
||||
echo "ERROR: Could not determine partition offset in piCore image"
|
||||
fdisk -l "$PICORE_RAW" || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BYTE_OFFSET=$((OFFSET * 512))
|
||||
mount -o loop,ro,offset="$BYTE_OFFSET" "$PICORE_RAW" "$IMG_MNT" || {
|
||||
echo "ERROR: Failed to mount piCore rootfs (need root for losetup)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Find initramfs in the piCore rootfs
|
||||
COREGZ=""
|
||||
for f in "$IMG_MNT"/boot/corepure64.gz "$IMG_MNT"/boot/core.gz "$IMG_MNT"/*.gz; do
|
||||
[ -f "$f" ] && COREGZ="$f" && break
|
||||
done
|
||||
|
||||
if [ -z "$COREGZ" ]; then
|
||||
echo "ERROR: Could not find initramfs in piCore image"
|
||||
echo "Contents:"
|
||||
ls -la "$IMG_MNT"/
|
||||
ls -la "$IMG_MNT"/boot/ 2>/dev/null || true
|
||||
umount "$IMG_MNT" 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Found initramfs: $COREGZ"
|
||||
|
||||
# Extract initramfs
|
||||
mkdir -p "$ROOTFS_DIR/rootfs"
|
||||
cd "$ROOTFS_DIR/rootfs"
|
||||
zcat "$COREGZ" | cpio -idm 2>/dev/null
|
||||
|
||||
# Note: ARM64 kernel comes from build-kernel-arm64.sh, not from piCore
|
||||
# We only use piCore for the BusyBox userland
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
umount "$IMG_MNT" 2>/dev/null || true
|
||||
rm -rf "$IMG_MNT"
|
||||
|
||||
echo "==> ARM64 rootfs extracted: $ROOTFS_DIR/rootfs"
|
||||
echo " Size: $(du -sh "$ROOTFS_DIR/rootfs" | cut -f1)"
|
||||
echo "==> Extract complete (ARM64). Kernel will come from build-kernel-arm64.sh"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# =========================================================================
|
||||
# x86_64: Tiny Core ISO extraction
|
||||
# =========================================================================
|
||||
TC_ISO="$CACHE_DIR/$TINYCORE_ISO"
|
||||
ISO_MNT="$ROOTFS_DIR/iso-mount"
|
||||
|
||||
@@ -19,9 +107,7 @@ if [ ! -f "$TC_ISO" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean previous rootfs
|
||||
rm -rf "$ROOTFS_DIR"
|
||||
mkdir -p "$ROOTFS_DIR" "$ISO_MNT"
|
||||
mkdir -p "$ISO_MNT"
|
||||
|
||||
# --- Mount ISO and extract kernel + initramfs ---
|
||||
echo "==> Mounting ISO: $TC_ISO"
|
||||
|
||||
@@ -10,9 +10,56 @@ CACHE_DIR="${CACHE_DIR:-$PROJECT_ROOT/build/cache}"
|
||||
# shellcheck source=../config/versions.env
|
||||
. "$SCRIPT_DIR/../config/versions.env"
|
||||
|
||||
# Verify SHA256 checksum of a downloaded file
|
||||
verify_checksum() {
|
||||
local file="$1" expected="$2" name="$3"
|
||||
# Skip if no expected checksum provided
|
||||
[ -z "$expected" ] && return 0
|
||||
local actual
|
||||
actual=$(sha256sum "$file" | awk '{print $1}')
|
||||
if [ "$actual" = "$expected" ]; then
|
||||
echo " Checksum OK: $name"
|
||||
return 0
|
||||
else
|
||||
echo "ERROR: Checksum mismatch for $name"
|
||||
echo " Expected: $expected"
|
||||
echo " Got: $actual"
|
||||
rm -f "$file"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
mkdir -p "$CACHE_DIR"
|
||||
|
||||
# --- Tiny Core Linux ISO ---
|
||||
# Detect target architecture
|
||||
FETCH_ARCH="${TARGET_ARCH:-amd64}"
|
||||
|
||||
# --- ARM64: piCore64 image instead of x86_64 ISO ---
|
||||
if [ "$FETCH_ARCH" = "arm64" ]; then
|
||||
PICORE_IMG="$CACHE_DIR/$PICORE_IMAGE"
|
||||
if [ -f "$PICORE_IMG" ]; then
|
||||
echo "==> piCore64 image already cached: $PICORE_IMG"
|
||||
else
|
||||
echo "==> Downloading piCore64 ${PICORE_VERSION} (${PICORE_ARCH})..."
|
||||
echo " URL: $PICORE_IMAGE_URL"
|
||||
wget -q --show-progress -O "$PICORE_IMG" "$PICORE_IMAGE_URL" 2>/dev/null || \
|
||||
curl -fSL "$PICORE_IMAGE_URL" -o "$PICORE_IMG"
|
||||
echo "==> Downloaded: $PICORE_IMG ($(du -h "$PICORE_IMG" | cut -f1))"
|
||||
fi
|
||||
|
||||
# Also fetch RPi firmware
|
||||
echo "==> Fetching RPi firmware..."
|
||||
"$SCRIPT_DIR/fetch-rpi-firmware.sh"
|
||||
|
||||
# Skip x86_64 ISO and TCZ downloads for ARM64
|
||||
echo ""
|
||||
echo "==> ARM64 fetch complete."
|
||||
echo "==> Component cache:"
|
||||
ls -lh "$CACHE_DIR"/ 2>/dev/null || true
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- x86_64: Tiny Core Linux ISO ---
|
||||
TC_ISO="$CACHE_DIR/$TINYCORE_ISO"
|
||||
TC_URL="${TINYCORE_MIRROR}/${TINYCORE_VERSION%%.*}.x/${TINYCORE_ARCH}/release/${TINYCORE_ISO}"
|
||||
|
||||
@@ -28,6 +75,7 @@ else
|
||||
wget -q --show-progress -O "$TC_ISO" "$TC_URL_ALT"
|
||||
}
|
||||
echo "==> Downloaded: $TC_ISO ($(du -h "$TC_ISO" | cut -f1))"
|
||||
verify_checksum "$TC_ISO" "$TINYCORE_ISO_SHA256" "Tiny Core ISO"
|
||||
fi
|
||||
|
||||
# --- KubeSolo ---
|
||||
@@ -88,6 +136,7 @@ else
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
echo "==> KubeSolo binary: $KUBESOLO_BIN ($(du -h "$KUBESOLO_BIN" | cut -f1))"
|
||||
verify_checksum "$KUBESOLO_BIN" "$KUBESOLO_SHA256" "KubeSolo binary"
|
||||
fi
|
||||
|
||||
# --- Tiny Core kernel module extensions (netfilter, iptables) ---
|
||||
@@ -114,6 +163,7 @@ else
|
||||
if wget -q --show-progress -O "$NETFILTER_TCZ" "$NETFILTER_TCZ_URL" 2>/dev/null || \
|
||||
curl -fSL "$NETFILTER_TCZ_URL" -o "$NETFILTER_TCZ" 2>/dev/null; then
|
||||
echo "==> Downloaded: $NETFILTER_TCZ ($(du -h "$NETFILTER_TCZ" | cut -f1))"
|
||||
verify_checksum "$NETFILTER_TCZ" "$NETFILTER_TCZ_SHA256" "netfilter TCZ"
|
||||
else
|
||||
echo "WARN: Failed to download netfilter modules. kube-proxy may not work."
|
||||
rm -f "$NETFILTER_TCZ"
|
||||
@@ -131,6 +181,7 @@ else
|
||||
if wget -q --show-progress -O "$NET_BRIDGING_TCZ" "$NET_BRIDGING_TCZ_URL" 2>/dev/null || \
|
||||
curl -fSL "$NET_BRIDGING_TCZ_URL" -o "$NET_BRIDGING_TCZ" 2>/dev/null; then
|
||||
echo "==> Downloaded: $NET_BRIDGING_TCZ ($(du -h "$NET_BRIDGING_TCZ" | cut -f1))"
|
||||
verify_checksum "$NET_BRIDGING_TCZ" "$NET_BRIDGING_TCZ_SHA256" "net-bridging TCZ"
|
||||
else
|
||||
echo "WARN: Failed to download net-bridging modules. CNI bridge may not work."
|
||||
rm -f "$NET_BRIDGING_TCZ"
|
||||
@@ -148,6 +199,7 @@ else
|
||||
if wget -q --show-progress -O "$IPTABLES_TCZ" "$IPTABLES_TCZ_URL" 2>/dev/null || \
|
||||
curl -fSL "$IPTABLES_TCZ_URL" -o "$IPTABLES_TCZ" 2>/dev/null; then
|
||||
echo "==> Downloaded: $IPTABLES_TCZ ($(du -h "$IPTABLES_TCZ" | cut -f1))"
|
||||
verify_checksum "$IPTABLES_TCZ" "$IPTABLES_TCZ_SHA256" "iptables TCZ"
|
||||
else
|
||||
echo "WARN: Failed to download iptables. KubeSolo bundles its own but this is a fallback."
|
||||
rm -f "$IPTABLES_TCZ"
|
||||
|
||||
88
build/scripts/fetch-rpi-firmware.sh
Executable file
88
build/scripts/fetch-rpi-firmware.sh
Executable file
@@ -0,0 +1,88 @@
|
||||
#!/bin/bash
|
||||
# fetch-rpi-firmware.sh — Download Raspberry Pi firmware blobs for boot
|
||||
#
|
||||
# Downloads firmware from the official raspberrypi/firmware GitHub repository.
|
||||
# Extracts only the boot files needed: start*.elf, fixup*.dat, DTBs, bootcode.bin.
|
||||
#
|
||||
# Output: build/cache/rpi-firmware/ containing all required boot files.
|
||||
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"
|
||||
|
||||
RPI_FW_DIR="$CACHE_DIR/rpi-firmware"
|
||||
RPI_FW_ARCHIVE="$CACHE_DIR/rpi-firmware-${RPI_FIRMWARE_TAG}.tar.gz"
|
||||
|
||||
# --- Skip if already fetched ---
|
||||
if [ -d "$RPI_FW_DIR" ] && [ -f "$RPI_FW_DIR/start4.elf" ]; then
|
||||
echo "==> RPi firmware already cached: $RPI_FW_DIR"
|
||||
echo " Files: $(ls "$RPI_FW_DIR" | wc -l)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "==> Downloading Raspberry Pi firmware (tag: ${RPI_FIRMWARE_TAG})..."
|
||||
mkdir -p "$CACHE_DIR" "$RPI_FW_DIR"
|
||||
|
||||
# --- Download firmware archive ---
|
||||
if [ ! -f "$RPI_FW_ARCHIVE" ]; then
|
||||
echo " URL: $RPI_FIRMWARE_URL"
|
||||
wget -q --show-progress -O "$RPI_FW_ARCHIVE" "$RPI_FIRMWARE_URL" 2>/dev/null || \
|
||||
curl -fSL "$RPI_FIRMWARE_URL" -o "$RPI_FW_ARCHIVE"
|
||||
echo " Downloaded: $(du -h "$RPI_FW_ARCHIVE" | cut -f1)"
|
||||
else
|
||||
echo " Archive already cached: $(du -h "$RPI_FW_ARCHIVE" | cut -f1)"
|
||||
fi
|
||||
|
||||
# --- Extract boot files only ---
|
||||
echo "==> Extracting boot files..."
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
trap "rm -rf '$TEMP_DIR'" EXIT
|
||||
|
||||
# Extract only the boot/ directory from the archive
|
||||
# Archive structure: firmware-<tag>/boot/...
|
||||
tar -xzf "$RPI_FW_ARCHIVE" -C "$TEMP_DIR" --strip-components=1 '*/boot/'
|
||||
|
||||
BOOT_SRC="$TEMP_DIR/boot"
|
||||
if [ ! -d "$BOOT_SRC" ]; then
|
||||
echo "ERROR: boot/ directory not found in firmware archive"
|
||||
ls -la "$TEMP_DIR"/
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy GPU firmware (required for boot)
|
||||
for f in "$BOOT_SRC"/start*.elf "$BOOT_SRC"/fixup*.dat; do
|
||||
[ -f "$f" ] && cp "$f" "$RPI_FW_DIR/"
|
||||
done
|
||||
|
||||
# Copy bootcode.bin (first-stage boot for Pi 3 and older)
|
||||
[ -f "$BOOT_SRC/bootcode.bin" ] && cp "$BOOT_SRC/bootcode.bin" "$RPI_FW_DIR/"
|
||||
|
||||
# Copy Device Tree Blobs for Pi 4 + Pi 5
|
||||
for dtb in bcm2711-rpi-4-b.dtb bcm2711-rpi-400.dtb bcm2711-rpi-cm4.dtb \
|
||||
bcm2712-rpi-5-b.dtb bcm2712d0-rpi-5-b.dtb; do
|
||||
[ -f "$BOOT_SRC/$dtb" ] && cp "$BOOT_SRC/$dtb" "$RPI_FW_DIR/"
|
||||
done
|
||||
|
||||
# Copy overlays directory (needed for config.txt dtoverlay= directives)
|
||||
if [ -d "$BOOT_SRC/overlays" ]; then
|
||||
mkdir -p "$RPI_FW_DIR/overlays"
|
||||
# Only copy overlays we actually use (disable-wifi, disable-bt)
|
||||
for overlay in disable-wifi.dtbo disable-bt.dtbo; do
|
||||
[ -f "$BOOT_SRC/overlays/$overlay" ] && \
|
||||
cp "$BOOT_SRC/overlays/$overlay" "$RPI_FW_DIR/overlays/"
|
||||
done
|
||||
fi
|
||||
|
||||
trap - EXIT
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
# --- Summary ---
|
||||
echo ""
|
||||
echo "==> RPi firmware extracted to: $RPI_FW_DIR"
|
||||
echo " Files:"
|
||||
ls -1 "$RPI_FW_DIR" | head -20
|
||||
echo " Total size: $(du -sh "$RPI_FW_DIR" | cut -f1)"
|
||||
@@ -8,6 +8,16 @@ CACHE_DIR="${CACHE_DIR:-$PROJECT_ROOT/build/cache}"
|
||||
ROOTFS_DIR="${ROOTFS_DIR:-$PROJECT_ROOT/build/rootfs-work}"
|
||||
ROOTFS="$ROOTFS_DIR/rootfs"
|
||||
VERSION="$(cat "$PROJECT_ROOT/VERSION")"
|
||||
INJECT_ARCH="${TARGET_ARCH:-amd64}"
|
||||
|
||||
# Architecture-specific paths
|
||||
if [ "$INJECT_ARCH" = "arm64" ]; then
|
||||
LIB_ARCH="aarch64-linux-gnu"
|
||||
LD_SO="/lib/ld-linux-aarch64.so.1"
|
||||
else
|
||||
LIB_ARCH="x86_64-linux-gnu"
|
||||
LD_SO="/lib64/ld-linux-x86-64.so.2"
|
||||
fi
|
||||
|
||||
if [ ! -d "$ROOTFS" ]; then
|
||||
echo "ERROR: Rootfs not found: $ROOTFS"
|
||||
@@ -90,8 +100,13 @@ fi
|
||||
# --- 3. Custom kernel or TCZ kernel modules ---
|
||||
# If a custom kernel was built (with CONFIG_CGROUP_BPF=y), use it.
|
||||
# Otherwise fall back to TCZ-extracted modules with manual modules.dep.
|
||||
CUSTOM_KERNEL_DIR="$CACHE_DIR/custom-kernel"
|
||||
CUSTOM_VMLINUZ="$CUSTOM_KERNEL_DIR/vmlinuz"
|
||||
if [ "$INJECT_ARCH" = "arm64" ]; then
|
||||
CUSTOM_KERNEL_DIR="$CACHE_DIR/custom-kernel-arm64"
|
||||
CUSTOM_VMLINUZ="$CUSTOM_KERNEL_DIR/Image"
|
||||
else
|
||||
CUSTOM_KERNEL_DIR="$CACHE_DIR/custom-kernel"
|
||||
CUSTOM_VMLINUZ="$CUSTOM_KERNEL_DIR/vmlinuz"
|
||||
fi
|
||||
CUSTOM_MODULES="$CUSTOM_KERNEL_DIR/modules"
|
||||
|
||||
# Detect kernel version from rootfs
|
||||
@@ -131,7 +146,11 @@ if [ -f "$CUSTOM_VMLINUZ" ] && [ -d "$CUSTOM_MODULES/lib/modules/$KVER" ]; then
|
||||
done
|
||||
|
||||
# Use modprobe --show-depends to resolve each module + its transitive deps
|
||||
MODULES_LIST="$PROJECT_ROOT/build/config/modules.list"
|
||||
if [ "$INJECT_ARCH" = "arm64" ]; then
|
||||
MODULES_LIST="$PROJECT_ROOT/build/config/modules-arm64.list"
|
||||
else
|
||||
MODULES_LIST="$PROJECT_ROOT/build/config/modules.list"
|
||||
fi
|
||||
NEEDED_MODS=$(mktemp)
|
||||
while IFS= read -r mod; do
|
||||
# Skip comments and blank lines
|
||||
@@ -291,21 +310,22 @@ if [ -f /usr/sbin/xtables-nft-multi ]; then
|
||||
ln -sf xtables-nft-multi "$ROOTFS/usr/sbin/$cmd"
|
||||
done
|
||||
|
||||
# Copy required shared libraries
|
||||
mkdir -p "$ROOTFS/usr/lib/x86_64-linux-gnu" "$ROOTFS/lib/x86_64-linux-gnu" "$ROOTFS/lib64"
|
||||
# Copy required shared libraries (architecture-aware paths)
|
||||
mkdir -p "$ROOTFS/usr/lib/$LIB_ARCH" "$ROOTFS/lib/$LIB_ARCH"
|
||||
[ "$INJECT_ARCH" != "arm64" ] && mkdir -p "$ROOTFS/lib64"
|
||||
for lib in \
|
||||
/lib/x86_64-linux-gnu/libxtables.so.12* \
|
||||
/lib/x86_64-linux-gnu/libmnl.so.0* \
|
||||
/lib/x86_64-linux-gnu/libnftnl.so.11* \
|
||||
/lib/x86_64-linux-gnu/libc.so.6 \
|
||||
/lib64/ld-linux-x86-64.so.2; do
|
||||
"/lib/$LIB_ARCH/libxtables.so.12"* \
|
||||
"/lib/$LIB_ARCH/libmnl.so.0"* \
|
||||
"/lib/$LIB_ARCH/libnftnl.so.11"* \
|
||||
"/lib/$LIB_ARCH/libc.so.6" \
|
||||
"$LD_SO"; do
|
||||
[ -e "$lib" ] && cp -aL "$lib" "$ROOTFS${lib}" 2>/dev/null || true
|
||||
done
|
||||
|
||||
# Copy xtables modules directory (match extensions)
|
||||
if [ -d /usr/lib/x86_64-linux-gnu/xtables ]; then
|
||||
mkdir -p "$ROOTFS/usr/lib/x86_64-linux-gnu/xtables"
|
||||
cp -a /usr/lib/x86_64-linux-gnu/xtables/*.so "$ROOTFS/usr/lib/x86_64-linux-gnu/xtables/" 2>/dev/null || true
|
||||
if [ -d "/usr/lib/$LIB_ARCH/xtables" ]; then
|
||||
mkdir -p "$ROOTFS/usr/lib/$LIB_ARCH/xtables"
|
||||
cp -a "/usr/lib/$LIB_ARCH/xtables/"*.so "$ROOTFS/usr/lib/$LIB_ARCH/xtables/" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo " Installed iptables-nft (xtables-nft-multi) + shared libs"
|
||||
@@ -314,11 +334,16 @@ else
|
||||
fi
|
||||
|
||||
# Kernel modules list (for init to load at boot)
|
||||
cp "$PROJECT_ROOT/build/config/modules.list" "$ROOTFS/usr/lib/kubesolo-os/modules.list"
|
||||
if [ "$INJECT_ARCH" = "arm64" ]; then
|
||||
cp "$PROJECT_ROOT/build/config/modules-arm64.list" "$ROOTFS/usr/lib/kubesolo-os/modules.list"
|
||||
else
|
||||
cp "$PROJECT_ROOT/build/config/modules.list" "$ROOTFS/usr/lib/kubesolo-os/modules.list"
|
||||
fi
|
||||
|
||||
# --- 4. Sysctl config ---
|
||||
mkdir -p "$ROOTFS/etc/sysctl.d"
|
||||
cp "$PROJECT_ROOT/build/rootfs/etc/sysctl.d/k8s.conf" "$ROOTFS/etc/sysctl.d/k8s.conf"
|
||||
cp "$PROJECT_ROOT/build/rootfs/etc/sysctl.d/security.conf" "$ROOTFS/etc/sysctl.d/security.conf"
|
||||
|
||||
# --- 5. OS metadata ---
|
||||
echo "$VERSION" > "$ROOTFS/etc/kubesolo-os-version"
|
||||
@@ -362,7 +387,35 @@ else
|
||||
echo " WARN: No CA certificates found in builder — TLS verification will fail"
|
||||
fi
|
||||
|
||||
# --- 9. Ensure /etc/hosts and /etc/resolv.conf exist ---
|
||||
# --- 9. AppArmor parser + profiles ---
|
||||
echo " Installing AppArmor..."
|
||||
if [ -f /usr/sbin/apparmor_parser ]; then
|
||||
mkdir -p "$ROOTFS/usr/sbin"
|
||||
cp /usr/sbin/apparmor_parser "$ROOTFS/usr/sbin/apparmor_parser"
|
||||
chmod +x "$ROOTFS/usr/sbin/apparmor_parser"
|
||||
|
||||
# Copy shared libraries required by apparmor_parser
|
||||
for lib in "/lib/$LIB_ARCH/libapparmor.so.1"*; do
|
||||
[ -e "$lib" ] && cp -aL "$lib" "$ROOTFS${lib}" 2>/dev/null || true
|
||||
done
|
||||
|
||||
echo " Installed apparmor_parser + shared libs"
|
||||
else
|
||||
echo " WARN: apparmor_parser not found in builder (install apparmor package)"
|
||||
fi
|
||||
|
||||
# Copy AppArmor profiles
|
||||
APPARMOR_PROFILES="$PROJECT_ROOT/build/rootfs/etc/apparmor.d"
|
||||
if [ -d "$APPARMOR_PROFILES" ]; then
|
||||
mkdir -p "$ROOTFS/etc/apparmor.d"
|
||||
cp "$APPARMOR_PROFILES"/* "$ROOTFS/etc/apparmor.d/" 2>/dev/null || true
|
||||
PROFILE_COUNT=$(ls "$ROOTFS/etc/apparmor.d/" 2>/dev/null | wc -l)
|
||||
echo " Installed $PROFILE_COUNT AppArmor profiles"
|
||||
else
|
||||
echo " WARN: No AppArmor profiles found at $APPARMOR_PROFILES"
|
||||
fi
|
||||
|
||||
# --- 10. Ensure /etc/hosts and /etc/resolv.conf exist ---
|
||||
if [ ! -f "$ROOTFS/etc/hosts" ]; then
|
||||
cat > "$ROOTFS/etc/hosts" << EOF
|
||||
127.0.0.1 localhost
|
||||
|
||||
Reference in New Issue
Block a user