Compare commits
4 Commits
a6c5d56ade
...
80aca5e372
| Author | SHA1 | Date | |
|---|---|---|---|
| 80aca5e372 | |||
| d51618badb | |||
| 19b99cf101 | |||
| 059ec7955f |
11
.gitignore
vendored
11
.gitignore
vendored
@@ -18,8 +18,19 @@ build/rootfs-work/
|
|||||||
|
|
||||||
# OS
|
# OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
._*
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
# Photos / screenshots — keep documentation images under docs/ instead
|
||||||
|
*.PNG
|
||||||
|
*.png
|
||||||
|
*.JPG
|
||||||
|
*.jpg
|
||||||
|
*.JPEG
|
||||||
|
*.jpeg
|
||||||
|
*.HEIC
|
||||||
|
*.heic
|
||||||
|
|
||||||
# Go
|
# Go
|
||||||
update/update-agent
|
update/update-agent
|
||||||
cloud-init/cloud-init-parser
|
cloud-init/cloud-init-parser
|
||||||
|
|||||||
60
Makefile
60
Makefile
@@ -1,8 +1,8 @@
|
|||||||
.PHONY: all fetch kernel build-cloudinit build-update-agent build-cross rootfs initramfs \
|
.PHONY: all fetch kernel build-cloudinit build-update-agent build-cross rootfs initramfs \
|
||||||
iso disk-image oci-image rpi-image \
|
iso disk-image disk-image-arm64 oci-image rpi-image \
|
||||||
kernel-arm64 rootfs-arm64 \
|
kernel-arm64 kernel-rpi rootfs-arm64 rootfs-arm64-rpi \
|
||||||
test-boot test-k8s test-persistence test-deploy test-storage test-security test-all \
|
test-boot test-k8s test-persistence test-deploy test-storage test-security test-all \
|
||||||
test-boot-arm64 test-cloudinit test-update-agent \
|
test-boot-arm64 test-boot-arm64-disk test-cloudinit test-update-agent \
|
||||||
bench-boot bench-resources \
|
bench-boot bench-resources \
|
||||||
dev-vm dev-vm-shell dev-vm-arm64 quick docker-build shellcheck \
|
dev-vm dev-vm-shell dev-vm-arm64 quick docker-build shellcheck \
|
||||||
kernel-audit clean distclean help
|
kernel-audit clean distclean help
|
||||||
@@ -73,21 +73,43 @@ build-cross:
|
|||||||
$(BUILD_DIR)/scripts/build-cross.sh
|
$(BUILD_DIR)/scripts/build-cross.sh
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# ARM64 Raspberry Pi targets
|
# ARM64 generic targets (mainline kernel, UEFI, virtio — for cloud / SBCs)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
kernel-arm64:
|
kernel-arm64:
|
||||||
@echo "==> Building ARM64 kernel for Raspberry Pi..."
|
@echo "==> Building generic ARM64 kernel (mainline LTS)..."
|
||||||
$(BUILD_DIR)/scripts/build-kernel-arm64.sh
|
$(BUILD_DIR)/scripts/build-kernel-arm64.sh
|
||||||
|
|
||||||
|
# Generic ARM64 rootfs consumes the mainline kernel modules.
|
||||||
rootfs-arm64: build-cross
|
rootfs-arm64: build-cross
|
||||||
@echo "==> Preparing ARM64 rootfs..."
|
@echo "==> Preparing generic ARM64 rootfs..."
|
||||||
TARGET_ARCH=arm64 $(BUILD_DIR)/scripts/fetch-components.sh
|
TARGET_ARCH=arm64 $(BUILD_DIR)/scripts/fetch-components.sh
|
||||||
TARGET_ARCH=arm64 $(BUILD_DIR)/scripts/extract-core.sh
|
TARGET_ARCH=arm64 $(BUILD_DIR)/scripts/extract-core.sh
|
||||||
TARGET_ARCH=arm64 $(BUILD_DIR)/scripts/inject-kubesolo.sh
|
TARGET_ARCH=arm64 TARGET_VARIANT=generic $(BUILD_DIR)/scripts/inject-kubesolo.sh
|
||||||
@echo "==> Packing ARM64 initramfs..."
|
@echo "==> Packing generic ARM64 initramfs..."
|
||||||
$(BUILD_DIR)/scripts/pack-initramfs.sh
|
$(BUILD_DIR)/scripts/pack-initramfs.sh
|
||||||
|
|
||||||
rpi-image: rootfs-arm64 kernel-arm64
|
disk-image-arm64: rootfs-arm64 kernel-arm64
|
||||||
|
@echo "==> Creating generic ARM64 disk image (UEFI + GRUB A/B)..."
|
||||||
|
TARGET_ARCH=arm64 $(BUILD_DIR)/scripts/create-disk-image.sh
|
||||||
|
@echo "==> Built: $(OUTPUT_DIR)/$(OS_NAME)-$(VERSION).arm64.img"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# ARM64 Raspberry Pi targets (RPi-patched kernel, firmware blobs, SD card)
|
||||||
|
# =============================================================================
|
||||||
|
kernel-rpi:
|
||||||
|
@echo "==> Building RPi kernel (raspberrypi/linux)..."
|
||||||
|
$(BUILD_DIR)/scripts/build-kernel-rpi.sh
|
||||||
|
|
||||||
|
# RPi-flavoured rootfs consumes the RPi kernel modules.
|
||||||
|
rootfs-arm64-rpi: build-cross
|
||||||
|
@echo "==> Preparing RPi ARM64 rootfs..."
|
||||||
|
TARGET_ARCH=arm64 $(BUILD_DIR)/scripts/fetch-components.sh
|
||||||
|
TARGET_ARCH=arm64 $(BUILD_DIR)/scripts/extract-core.sh
|
||||||
|
TARGET_ARCH=arm64 TARGET_VARIANT=rpi $(BUILD_DIR)/scripts/inject-kubesolo.sh
|
||||||
|
@echo "==> Packing RPi ARM64 initramfs..."
|
||||||
|
$(BUILD_DIR)/scripts/pack-initramfs.sh
|
||||||
|
|
||||||
|
rpi-image: rootfs-arm64-rpi kernel-rpi
|
||||||
@echo "==> Creating Raspberry Pi SD card image..."
|
@echo "==> Creating Raspberry Pi SD card image..."
|
||||||
$(BUILD_DIR)/scripts/create-rpi-image.sh
|
$(BUILD_DIR)/scripts/create-rpi-image.sh
|
||||||
@echo "==> Built: $(OUTPUT_DIR)/$(OS_NAME)-$(VERSION).rpi.img"
|
@echo "==> Built: $(OUTPUT_DIR)/$(OS_NAME)-$(VERSION).rpi.img"
|
||||||
@@ -127,9 +149,13 @@ test-security: iso
|
|||||||
test/integration/test-security-hardening.sh $(OUTPUT_DIR)/$(OS_NAME)-$(VERSION).iso
|
test/integration/test-security-hardening.sh $(OUTPUT_DIR)/$(OS_NAME)-$(VERSION).iso
|
||||||
|
|
||||||
test-boot-arm64:
|
test-boot-arm64:
|
||||||
@echo "==> Testing ARM64 boot in QEMU..."
|
@echo "==> Testing ARM64 boot in QEMU (direct kernel)..."
|
||||||
test/qemu/test-boot-arm64.sh
|
test/qemu/test-boot-arm64.sh
|
||||||
|
|
||||||
|
test-boot-arm64-disk: disk-image-arm64
|
||||||
|
@echo "==> Testing ARM64 UEFI disk boot in QEMU..."
|
||||||
|
test/qemu/test-boot-arm64-disk.sh $(OUTPUT_DIR)/$(OS_NAME)-$(VERSION).arm64.img
|
||||||
|
|
||||||
test-all: test-boot test-k8s test-persistence
|
test-all: test-boot test-k8s test-persistence
|
||||||
|
|
||||||
# Cloud-init Go tests
|
# Cloud-init Go tests
|
||||||
@@ -246,10 +272,15 @@ help:
|
|||||||
@echo " make quick Fast rebuild (re-inject + repack + ISO only)"
|
@echo " make quick Fast rebuild (re-inject + repack + ISO only)"
|
||||||
@echo " make docker-build Reproducible build inside Docker"
|
@echo " make docker-build Reproducible build inside Docker"
|
||||||
@echo ""
|
@echo ""
|
||||||
|
@echo "Build targets (ARM64 generic — UEFI / cloud / SBCs):"
|
||||||
|
@echo " make kernel-arm64 Build mainline ARM64 kernel from kernel.org LTS"
|
||||||
|
@echo " make rootfs-arm64 Prepare generic ARM64 rootfs (mainline kernel modules)"
|
||||||
|
@echo " make disk-image-arm64 Create UEFI-bootable A/B GPT disk image (.arm64.img)"
|
||||||
|
@echo ""
|
||||||
@echo "Build targets (ARM64 Raspberry Pi):"
|
@echo "Build targets (ARM64 Raspberry Pi):"
|
||||||
@echo " make kernel-arm64 Build ARM64 kernel from raspberrypi/linux"
|
@echo " make kernel-rpi Build RPi kernel from raspberrypi/linux"
|
||||||
@echo " make rootfs-arm64 Extract + prepare ARM64 rootfs from piCore64"
|
@echo " make rootfs-arm64-rpi Prepare RPi-flavoured rootfs (RPi kernel modules)"
|
||||||
@echo " make rpi-image Create Raspberry Pi SD card image with A/B partitions"
|
@echo " make rpi-image Create Raspberry Pi SD card image with A/B autoboot"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Test targets:"
|
@echo "Test targets:"
|
||||||
@echo " make test-boot Boot ISO in QEMU, verify boot success"
|
@echo " make test-boot Boot ISO in QEMU, verify boot success"
|
||||||
@@ -262,7 +293,8 @@ help:
|
|||||||
@echo " make test-update-agent Run update agent Go unit tests"
|
@echo " make test-update-agent Run update agent Go unit tests"
|
||||||
@echo " make test-update A/B update cycle integration test"
|
@echo " make test-update A/B update cycle integration test"
|
||||||
@echo " make test-rollback Forced rollback integration test"
|
@echo " make test-rollback Forced rollback integration test"
|
||||||
@echo " make test-boot-arm64 ARM64 boot test in QEMU aarch64"
|
@echo " make test-boot-arm64 ARM64 boot test (direct kernel, fast)"
|
||||||
|
@echo " make test-boot-arm64-disk ARM64 full UEFI disk-boot test"
|
||||||
@echo " make test-all Run core tests (boot + k8s + persistence)"
|
@echo " make test-all Run core tests (boot + k8s + persistence)"
|
||||||
@echo " make test-integ Run full integration suite"
|
@echo " make test-integ Run full integration suite"
|
||||||
@echo " make bench-boot Benchmark boot performance (3 runs)"
|
@echo " make bench-boot Benchmark boot performance (3 runs)"
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
An immutable, bootable Linux distribution purpose-built for [KubeSolo](https://github.com/portainer/kubesolo) — Portainer's ultra-lightweight single-node Kubernetes.
|
An immutable, bootable Linux distribution purpose-built for [KubeSolo](https://github.com/portainer/kubesolo) — Portainer's ultra-lightweight single-node Kubernetes.
|
||||||
|
|
||||||
> **Status:** All 6 phases complete. Boots and runs K8s workloads. Portainer Edge Agent tested and connected.
|
> **Status:** x86_64 is stable — boots and runs K8s workloads, Portainer Edge Agent tested and connected. ARM64 generic UEFI is the active focus for v0.3.0; ARM64 Raspberry Pi support is paused pending physical hardware testing.
|
||||||
|
|
||||||
## What is this?
|
## What is this?
|
||||||
|
|
||||||
@@ -227,13 +227,16 @@ Metrics include: `kubesolo_os_info`, `boot_success`, `boot_counter`, `uptime_sec
|
|||||||
|
|
||||||
| Phase | Scope | Status |
|
| Phase | Scope | Status |
|
||||||
|-------|-------|--------|
|
|-------|-------|--------|
|
||||||
| 1 | PoC: boot Tiny Core + KubeSolo, verify K8s | Complete |
|
| 1 | PoC: boot Tiny Core + KubeSolo, verify K8s | Complete (x86_64) |
|
||||||
| 2 | Cloud-init Go parser, network, hostname | Complete |
|
| 2 | Cloud-init Go parser, network, hostname | Complete |
|
||||||
| 3 | A/B atomic updates, GRUB, rollback agent | Complete |
|
| 3 | A/B atomic updates, GRUB, rollback agent | Complete (x86_64) |
|
||||||
| 4 | Ed25519 signing, Portainer Edge, SSH extension | Complete |
|
| 4 | Ed25519 signing, Portainer Edge, SSH extension | Complete |
|
||||||
| 5 | CI/CD, OCI distribution, Prometheus metrics, ARM64 | Complete |
|
| 5 | CI/CD, OCI distribution, Prometheus metrics, ARM64 cross-compile | Complete |
|
||||||
| 6 | Security hardening, AppArmor, ARM64 RPi support | Complete |
|
| 6 | Security hardening, AppArmor | Complete |
|
||||||
| - | Custom kernel build for container runtime fixes | Complete |
|
| - | Custom kernel build for container runtime fixes | Complete (x86_64) |
|
||||||
|
| 7 | ARM64 generic (mainline kernel, UEFI, virtio) | In progress (v0.3.0) |
|
||||||
|
| 8 | Update engine v2 (state machine, OCI distribution, channels) | In progress (v0.3.0) |
|
||||||
|
| - | ARM64 Raspberry Pi (custom kernel, firmware, SD card image) | Paused — needs hardware |
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
file \
|
file \
|
||||||
flex \
|
flex \
|
||||||
genisoimage \
|
genisoimage \
|
||||||
|
grub-common \
|
||||||
|
grub-efi-amd64-bin \
|
||||||
|
grub-efi-arm64-bin \
|
||||||
|
grub-pc-bin \
|
||||||
|
grub2-common \
|
||||||
gzip \
|
gzip \
|
||||||
isolinux \
|
isolinux \
|
||||||
iptables \
|
iptables \
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
# KubeSolo OS — Raspberry Pi kernel config overrides
|
# KubeSolo OS — Shared kernel config fragment for container workloads
|
||||||
# Applied on top of bcm2711_defconfig (Pi 4) or bcm2712_defconfig (Pi 5)
|
#
|
||||||
# These ensure container runtime support is enabled.
|
# Applied on top of:
|
||||||
|
# - Tiny Core stock config (x86_64) via build-kernel.sh
|
||||||
|
# - mainline kernel.org arm64 defconfig via build-kernel-arm64.sh
|
||||||
|
# - bcm2711_defconfig / bcm2712_defconfig via build-kernel-rpi.sh
|
||||||
|
#
|
||||||
|
# All entries here are architecture-agnostic.
|
||||||
|
# Apply this fragment twice with `make olddefconfig` between passes — TC's stock
|
||||||
|
# config has CONFIG_SECURITY disabled, which causes a single-pass olddefconfig
|
||||||
|
# to strip the security subtree before its dependencies (SYSFS, MULTIUSER) are
|
||||||
|
# resolved.
|
||||||
|
|
||||||
# cgroup v2 (mandatory for containerd/runc)
|
# cgroup v2 (mandatory for containerd/runc)
|
||||||
CONFIG_CGROUPS=y
|
CONFIG_CGROUPS=y
|
||||||
@@ -52,6 +61,7 @@ CONFIG_SECURITYFS=y
|
|||||||
CONFIG_SECURITY_NETWORK=y
|
CONFIG_SECURITY_NETWORK=y
|
||||||
CONFIG_SECURITY_APPARMOR=y
|
CONFIG_SECURITY_APPARMOR=y
|
||||||
CONFIG_DEFAULT_SECURITY_APPARMOR=y
|
CONFIG_DEFAULT_SECURITY_APPARMOR=y
|
||||||
|
CONFIG_LSM=lockdown,yama,apparmor
|
||||||
|
|
||||||
# Security: seccomp
|
# Security: seccomp
|
||||||
CONFIG_SECCOMP=y
|
CONFIG_SECCOMP=y
|
||||||
@@ -60,10 +70,21 @@ CONFIG_SECCOMP_FILTER=y
|
|||||||
# Crypto (image verification)
|
# Crypto (image verification)
|
||||||
CONFIG_CRYPTO_SHA256=y
|
CONFIG_CRYPTO_SHA256=y
|
||||||
|
|
||||||
# Disable unnecessary subsystems for edge appliance
|
# Disable unnecessary subsystems for headless edge appliance
|
||||||
# CONFIG_SOUND is not set
|
# CONFIG_SOUND is not set
|
||||||
# CONFIG_DRM is not set
|
# CONFIG_DRM is not set
|
||||||
|
# CONFIG_KVM is not set
|
||||||
# CONFIG_MEDIA_SUPPORT is not set
|
# CONFIG_MEDIA_SUPPORT is not set
|
||||||
# CONFIG_WIRELESS is not set
|
# CONFIG_WIRELESS is not set
|
||||||
|
# CONFIG_WLAN is not set
|
||||||
|
# CONFIG_CFG80211 is not set
|
||||||
# CONFIG_BT is not set
|
# CONFIG_BT is not set
|
||||||
# CONFIG_NFC is not set
|
# CONFIG_NFC is not set
|
||||||
|
# CONFIG_INFINIBAND is not set
|
||||||
|
# CONFIG_PCMCIA is not set
|
||||||
|
# CONFIG_HAMRADIO is not set
|
||||||
|
# CONFIG_ISDN is not set
|
||||||
|
# CONFIG_ATM is not set
|
||||||
|
# CONFIG_INPUT_JOYSTICK is not set
|
||||||
|
# CONFIG_INPUT_TABLET is not set
|
||||||
|
# CONFIG_FPGA is not set
|
||||||
@@ -9,6 +9,9 @@ TINYCORE_ISO=CorePure64-${TINYCORE_VERSION}.iso
|
|||||||
TINYCORE_ISO_URL=${TINYCORE_MIRROR}/${TINYCORE_VERSION%%.*}.x/${TINYCORE_ARCH}/release/${TINYCORE_ISO}
|
TINYCORE_ISO_URL=${TINYCORE_MIRROR}/${TINYCORE_VERSION%%.*}.x/${TINYCORE_ARCH}/release/${TINYCORE_ISO}
|
||||||
|
|
||||||
# KubeSolo
|
# KubeSolo
|
||||||
|
# Pinned release tag from https://github.com/portainer/kubesolo/releases.
|
||||||
|
# Bump here and re-run `make fetch` to pull a new version.
|
||||||
|
KUBESOLO_VERSION=v1.1.0
|
||||||
KUBESOLO_INSTALL_URL=https://get.kubesolo.io
|
KUBESOLO_INSTALL_URL=https://get.kubesolo.io
|
||||||
|
|
||||||
# Build tools (used inside builder container)
|
# Build tools (used inside builder container)
|
||||||
@@ -38,5 +41,13 @@ RPI_FIRMWARE_URL=https://github.com/raspberrypi/firmware/archive/refs/tags/${RPI
|
|||||||
RPI_KERNEL_BRANCH=rpi-6.6.y
|
RPI_KERNEL_BRANCH=rpi-6.6.y
|
||||||
RPI_KERNEL_REPO=https://github.com/raspberrypi/linux
|
RPI_KERNEL_REPO=https://github.com/raspberrypi/linux
|
||||||
|
|
||||||
|
# Mainline Linux kernel (for generic ARM64 — kernel.org LTS)
|
||||||
|
# Bump within the 6.12 LTS series as patch levels release.
|
||||||
|
# 6.12 LTS is supported until Dec 2029.
|
||||||
|
MAINLINE_KERNEL_VERSION=6.12.10
|
||||||
|
MAINLINE_KERNEL_MAJOR=v6.x
|
||||||
|
MAINLINE_KERNEL_URL=https://cdn.kernel.org/pub/linux/kernel/${MAINLINE_KERNEL_MAJOR}/linux-${MAINLINE_KERNEL_VERSION}.tar.xz
|
||||||
|
MAINLINE_KERNEL_SHA256=""
|
||||||
|
|
||||||
# Output naming
|
# Output naming
|
||||||
OS_NAME=kubesolo-os
|
OS_NAME=kubesolo-os
|
||||||
|
|||||||
86
build/grub/grub-arm64.cfg
Normal file
86
build/grub/grub-arm64.cfg
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# KubeSolo OS — GRUB Configuration (ARM64)
|
||||||
|
# A/B partition boot with automatic rollback.
|
||||||
|
#
|
||||||
|
# Same A/B logic as build/grub/grub.cfg; only the console parameters differ
|
||||||
|
# (ARM64 PL011 / 16550-compat UART rather than x86 ttyS0).
|
||||||
|
#
|
||||||
|
# Partition layout:
|
||||||
|
# (hd0,gpt1) — EFI/Boot (256 MB, FAT32) — contains GRUB + grubenv
|
||||||
|
# (hd0,gpt2) — System A (512 MB, ext4) — vmlinuz + kubesolo-os.gz
|
||||||
|
# (hd0,gpt3) — System B (512 MB, ext4) — vmlinuz + kubesolo-os.gz
|
||||||
|
# (hd0,gpt4) — Data (remaining, ext4) — persistent K8s state
|
||||||
|
|
||||||
|
set default=0
|
||||||
|
set timeout=3
|
||||||
|
|
||||||
|
load_env
|
||||||
|
|
||||||
|
# --- A/B Rollback Logic (identical to amd64 grub.cfg) ---
|
||||||
|
|
||||||
|
if [ "${boot_success}" != "1" ]; then
|
||||||
|
if [ "${boot_counter}" = "0" ]; then
|
||||||
|
if [ "${active_slot}" = "A" ]; then
|
||||||
|
set active_slot=B
|
||||||
|
else
|
||||||
|
set active_slot=A
|
||||||
|
fi
|
||||||
|
save_env active_slot
|
||||||
|
set boot_counter=3
|
||||||
|
save_env boot_counter
|
||||||
|
else
|
||||||
|
if [ "${boot_counter}" = "3" ]; then
|
||||||
|
set boot_counter=2
|
||||||
|
elif [ "${boot_counter}" = "2" ]; then
|
||||||
|
set boot_counter=1
|
||||||
|
elif [ "${boot_counter}" = "1" ]; then
|
||||||
|
set boot_counter=0
|
||||||
|
fi
|
||||||
|
save_env boot_counter
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
set boot_success=0
|
||||||
|
save_env boot_success
|
||||||
|
|
||||||
|
if [ "${active_slot}" = "A" ]; then
|
||||||
|
set root='(hd0,gpt2)'
|
||||||
|
set slot_label="System A"
|
||||||
|
else
|
||||||
|
set root='(hd0,gpt3)'
|
||||||
|
set slot_label="System B"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- ARM64 console string ---
|
||||||
|
# Covers QEMU virt (ttyAMA0), Ampere/RPi-equivalent PL011 (ttyAMA0), and
|
||||||
|
# Graviton/16550-compat (ttyS0). Last `console=` becomes the system console.
|
||||||
|
|
||||||
|
menuentry "KubeSolo OS (${slot_label})" {
|
||||||
|
echo "Booting KubeSolo OS from ${slot_label}..."
|
||||||
|
echo "Boot counter: ${boot_counter}, Boot success: ${boot_success}"
|
||||||
|
linux /vmlinuz kubesolo.data=LABEL=KSOLODATA console=ttyAMA0,115200 console=ttyS0,115200 quiet
|
||||||
|
initrd /kubesolo-os.gz
|
||||||
|
}
|
||||||
|
|
||||||
|
menuentry "KubeSolo OS (${slot_label}) — Debug Mode" {
|
||||||
|
echo "Booting KubeSolo OS (debug) from ${slot_label}..."
|
||||||
|
linux /vmlinuz kubesolo.data=LABEL=KSOLODATA kubesolo.debug console=ttyAMA0,115200 console=ttyS0,115200
|
||||||
|
initrd /kubesolo-os.gz
|
||||||
|
}
|
||||||
|
|
||||||
|
menuentry "KubeSolo OS — Emergency Shell" {
|
||||||
|
echo "Booting to emergency shell..."
|
||||||
|
linux /vmlinuz kubesolo.shell console=ttyAMA0,115200 console=ttyS0,115200
|
||||||
|
initrd /kubesolo-os.gz
|
||||||
|
}
|
||||||
|
|
||||||
|
menuentry "KubeSolo OS — Boot Other Slot" {
|
||||||
|
if [ "${active_slot}" = "A" ]; then
|
||||||
|
set root='(hd0,gpt3)'
|
||||||
|
echo "Booting from System B (passive)..."
|
||||||
|
else
|
||||||
|
set root='(hd0,gpt2)'
|
||||||
|
echo "Booting from System A (passive)..."
|
||||||
|
fi
|
||||||
|
linux /vmlinuz kubesolo.data=LABEL=KSOLODATA kubesolo.debug console=ttyAMA0,115200 console=ttyS0,115200
|
||||||
|
initrd /kubesolo-os.gz
|
||||||
|
}
|
||||||
@@ -1,14 +1,20 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# build-kernel-arm64.sh — Build ARM64 kernel for Raspberry Pi 4/5
|
# build-kernel-arm64.sh — Build generic ARM64 kernel (mainline LTS)
|
||||||
#
|
#
|
||||||
# Uses the official raspberrypi/linux kernel fork with bcm2711_defconfig
|
# Builds a Linux kernel from kernel.org mainline LTS source, suitable for:
|
||||||
# as the base, overlaid with container-critical config options.
|
# - qemu-system-aarch64 -machine virt
|
||||||
|
# - UEFI ARM64 hosts (Ampere, Graviton, generic ARM64 servers)
|
||||||
|
# - Future ARM64 SBCs with UEFI/u-boot generic-distro support
|
||||||
#
|
#
|
||||||
# Output is cached in $CACHE_DIR/custom-kernel-arm64/ and reused across builds.
|
# This is the GENERIC ARM64 build track. For Raspberry Pi specifically
|
||||||
|
# (raspberrypi/linux fork, RPi firmware boot path, custom DTBs), see
|
||||||
|
# build/scripts/build-kernel-rpi.sh.
|
||||||
|
#
|
||||||
|
# Output is cached in $CACHE_DIR/kernel-arm64-generic/ and reused across builds.
|
||||||
#
|
#
|
||||||
# Requirements:
|
# Requirements:
|
||||||
# - gcc-aarch64-linux-gnu (cross-compiler)
|
# - gcc-aarch64-linux-gnu (cross-compiler)
|
||||||
# - Standard kernel build deps (bc, bison, flex, etc.)
|
# - Standard kernel build deps (bc, bison, flex, libelf-dev, libssl-dev)
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
@@ -18,17 +24,18 @@ CACHE_DIR="${CACHE_DIR:-$PROJECT_ROOT/build/cache}"
|
|||||||
# shellcheck source=../config/versions.env
|
# shellcheck source=../config/versions.env
|
||||||
. "$SCRIPT_DIR/../config/versions.env"
|
. "$SCRIPT_DIR/../config/versions.env"
|
||||||
|
|
||||||
CUSTOM_KERNEL_DIR="$CACHE_DIR/custom-kernel-arm64"
|
KVER="$MAINLINE_KERNEL_VERSION"
|
||||||
|
CUSTOM_KERNEL_DIR="$CACHE_DIR/kernel-arm64-generic"
|
||||||
CUSTOM_IMAGE="$CUSTOM_KERNEL_DIR/Image"
|
CUSTOM_IMAGE="$CUSTOM_KERNEL_DIR/Image"
|
||||||
CUSTOM_MODULES="$CUSTOM_KERNEL_DIR/modules"
|
CUSTOM_MODULES="$CUSTOM_KERNEL_DIR/modules"
|
||||||
CUSTOM_DTBS="$CUSTOM_KERNEL_DIR/dtbs"
|
|
||||||
|
|
||||||
mkdir -p "$CACHE_DIR" "$CUSTOM_KERNEL_DIR"
|
mkdir -p "$CACHE_DIR" "$CUSTOM_KERNEL_DIR"
|
||||||
|
|
||||||
# --- Skip if already built ---
|
# --- Skip if already built ---
|
||||||
if [ -f "$CUSTOM_IMAGE" ] && [ -d "$CUSTOM_MODULES" ]; then
|
if [ -f "$CUSTOM_IMAGE" ] && [ -d "$CUSTOM_MODULES/lib/modules/$KVER" ]; then
|
||||||
echo "==> ARM64 kernel already built (cached)"
|
echo "==> Generic ARM64 kernel already built (cached)"
|
||||||
echo " Image: $CUSTOM_IMAGE ($(du -h "$CUSTOM_IMAGE" | cut -f1))"
|
echo " Image: $CUSTOM_IMAGE ($(du -h "$CUSTOM_IMAGE" | cut -f1))"
|
||||||
|
echo " Kernel: $KVER"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -39,38 +46,72 @@ if ! command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "==> Building ARM64 kernel for Raspberry Pi..."
|
echo "==> Building generic ARM64 kernel (mainline $KVER)..."
|
||||||
echo " Branch: $RPI_KERNEL_BRANCH"
|
echo " Source: $MAINLINE_KERNEL_URL"
|
||||||
echo " Repo: $RPI_KERNEL_REPO"
|
|
||||||
|
|
||||||
# --- Download kernel source ---
|
# --- Download mainline kernel source ---
|
||||||
KERNEL_SRC_DIR="$CACHE_DIR/rpi-linux-${RPI_KERNEL_BRANCH}"
|
KERNEL_SRC_ARCHIVE="$CACHE_DIR/linux-${KVER}.tar.xz"
|
||||||
if [ ! -d "$KERNEL_SRC_DIR" ]; then
|
if [ ! -f "$KERNEL_SRC_ARCHIVE" ]; then
|
||||||
echo "==> Downloading RPi kernel source (shallow clone)..."
|
echo "==> Downloading mainline kernel source (~140 MB)..."
|
||||||
git clone --depth 1 --branch "$RPI_KERNEL_BRANCH" \
|
wget -q --show-progress -O "$KERNEL_SRC_ARCHIVE" "$MAINLINE_KERNEL_URL" 2>/dev/null || \
|
||||||
"$RPI_KERNEL_REPO" "$KERNEL_SRC_DIR"
|
curl -fSL "$MAINLINE_KERNEL_URL" -o "$KERNEL_SRC_ARCHIVE"
|
||||||
|
echo " Downloaded: $(du -h "$KERNEL_SRC_ARCHIVE" | cut -f1)"
|
||||||
else
|
else
|
||||||
echo "==> Kernel source already cached"
|
echo "==> Kernel source already cached: $(du -h "$KERNEL_SRC_ARCHIVE" | cut -f1)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Build in /tmp for case-sensitivity ---
|
# --- Verify checksum if pinned ---
|
||||||
KERNEL_BUILD_DIR="/tmp/kernel-build-arm64"
|
if [ -n "${MAINLINE_KERNEL_SHA256:-}" ]; then
|
||||||
|
actual=$(sha256sum "$KERNEL_SRC_ARCHIVE" | awk '{print $1}')
|
||||||
|
if [ "$actual" != "$MAINLINE_KERNEL_SHA256" ]; then
|
||||||
|
echo "ERROR: Kernel source checksum mismatch"
|
||||||
|
echo " Expected: $MAINLINE_KERNEL_SHA256"
|
||||||
|
echo " Got: $actual"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " Checksum OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Extract to case-sensitive fs ---
|
||||||
|
# The kernel source has files differing only by case (xt_mark.h vs xt_MARK.h).
|
||||||
|
# Build in /tmp (ext4 on Linux runners, case-sensitive).
|
||||||
|
KERNEL_BUILD_DIR="/tmp/kernel-build-arm64-generic"
|
||||||
rm -rf "$KERNEL_BUILD_DIR"
|
rm -rf "$KERNEL_BUILD_DIR"
|
||||||
cp -a "$KERNEL_SRC_DIR" "$KERNEL_BUILD_DIR"
|
mkdir -p "$KERNEL_BUILD_DIR"
|
||||||
|
|
||||||
cd "$KERNEL_BUILD_DIR"
|
echo "==> Extracting kernel source..."
|
||||||
|
tar -xf "$KERNEL_SRC_ARCHIVE" -C "$KERNEL_BUILD_DIR"
|
||||||
|
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 extracted source directory"
|
||||||
|
ls -la "$KERNEL_BUILD_DIR"/
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# --- Apply base config (Pi 4 = bcm2711) ---
|
cd "$KERNEL_SRC_DIR"
|
||||||
echo "==> Applying bcm2711_defconfig..."
|
|
||||||
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- bcm2711_defconfig
|
|
||||||
|
|
||||||
# --- Apply container config overrides ---
|
# --- Base config: arm64 defconfig (generic ARMv8) ---
|
||||||
CONFIG_FRAGMENT="$PROJECT_ROOT/build/config/rpi-kernel-config.fragment"
|
echo "==> Applying arm64 defconfig..."
|
||||||
if [ -f "$CONFIG_FRAGMENT" ]; then
|
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
|
||||||
echo "==> Applying KubeSolo config overrides..."
|
|
||||||
|
# --- Apply shared container fragment ---
|
||||||
|
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_fragment() {
|
||||||
|
local fragment="$1"
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
# Skip comments and empty lines
|
case "$line" in
|
||||||
case "$line" in \#*|"") continue ;; esac
|
"# 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%%=*}"
|
key="${line%%=*}"
|
||||||
value="${line#*=}"
|
value="${line#*=}"
|
||||||
case "$value" in
|
case "$value" in
|
||||||
@@ -79,33 +120,54 @@ if [ -f "$CONFIG_FRAGMENT" ]; then
|
|||||||
n) ./scripts/config --disable "${key#CONFIG_}" ;;
|
n) ./scripts/config --disable "${key#CONFIG_}" ;;
|
||||||
*) ./scripts/config --set-str "$key" "$value" ;;
|
*) ./scripts/config --set-str "$key" "$value" ;;
|
||||||
esac
|
esac
|
||||||
done < "$CONFIG_FRAGMENT"
|
done < "$fragment"
|
||||||
fi
|
}
|
||||||
|
|
||||||
# Handle "is not set" comments as disables
|
echo "==> Applying kernel-container.fragment (pass 1)..."
|
||||||
if [ -f "$CONFIG_FRAGMENT" ]; then
|
apply_fragment "$CONFIG_FRAGMENT"
|
||||||
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
|
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- olddefconfig
|
||||||
|
|
||||||
# --- Build kernel + modules + DTBs ---
|
echo "==> Applying kernel-container.fragment (pass 2)..."
|
||||||
|
apply_fragment "$CONFIG_FRAGMENT"
|
||||||
|
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- olddefconfig
|
||||||
|
|
||||||
|
# --- ARM64 virt-host specific enables ---
|
||||||
|
# These are needed for the generic UEFI/virtio boot path but are arch-specific
|
||||||
|
# so they live in this script rather than the shared fragment.
|
||||||
|
echo "==> Enabling ARM64 virt-host configs..."
|
||||||
|
./scripts/config --enable CONFIG_EFI
|
||||||
|
./scripts/config --enable CONFIG_EFI_STUB
|
||||||
|
./scripts/config --enable CONFIG_VIRTIO
|
||||||
|
./scripts/config --enable CONFIG_VIRTIO_PCI
|
||||||
|
./scripts/config --enable CONFIG_VIRTIO_BLK
|
||||||
|
./scripts/config --enable CONFIG_VIRTIO_NET
|
||||||
|
./scripts/config --enable CONFIG_VIRTIO_CONSOLE
|
||||||
|
./scripts/config --enable CONFIG_VIRTIO_MMIO
|
||||||
|
./scripts/config --enable CONFIG_HW_RANDOM_VIRTIO
|
||||||
|
# NVMe for cloud / bare-metal ARM64 hosts that don't use virtio
|
||||||
|
./scripts/config --enable CONFIG_BLK_DEV_NVME
|
||||||
|
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- olddefconfig
|
||||||
|
|
||||||
|
# --- Verify critical configs ---
|
||||||
|
echo "==> Verifying critical configs..."
|
||||||
|
for cfg in CGROUP_BPF SECURITY_APPARMOR AUDIT VIRTIO_BLK EFI_STUB; do
|
||||||
|
if ! grep -q "CONFIG_${cfg}=y" .config; then
|
||||||
|
echo "ERROR: CONFIG_${cfg} not set after olddefconfig"
|
||||||
|
grep "CONFIG_${cfg}" .config || echo " (not found)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " CONFIG_${cfg}=y confirmed"
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- Build kernel + modules (no DTBs — UEFI hosts use ACPI/virtio) ---
|
||||||
NPROC=$(nproc 2>/dev/null || echo 4)
|
NPROC=$(nproc 2>/dev/null || echo 4)
|
||||||
echo ""
|
echo ""
|
||||||
echo "==> Building ARM64 kernel (${NPROC} parallel jobs)..."
|
echo "==> Building ARM64 kernel (${NPROC} parallel jobs)..."
|
||||||
echo " This may take 20-30 minutes..."
|
echo " This may take 20-40 minutes on a 6-core Odroid..."
|
||||||
|
|
||||||
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j"$NPROC" Image modules dtbs 2>&1
|
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j"$NPROC" Image modules 2>&1
|
||||||
|
|
||||||
echo "==> ARM64 kernel build complete"
|
echo "==> Kernel build complete"
|
||||||
|
|
||||||
# --- Install to staging ---
|
# --- Install to staging ---
|
||||||
echo "==> Installing Image..."
|
echo "==> Installing Image..."
|
||||||
@@ -117,28 +179,13 @@ mkdir -p "$CUSTOM_MODULES"
|
|||||||
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
|
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
|
||||||
INSTALL_MOD_STRIP=1 modules_install INSTALL_MOD_PATH="$CUSTOM_MODULES"
|
INSTALL_MOD_STRIP=1 modules_install INSTALL_MOD_PATH="$CUSTOM_MODULES"
|
||||||
|
|
||||||
# Remove build/source symlinks
|
# Pick up actual kernel version (e.g. 6.12.10 if KVER differs from package suffix)
|
||||||
KVER=$(ls "$CUSTOM_MODULES/lib/modules/" | head -1)
|
ACTUAL_KVER=$(ls "$CUSTOM_MODULES/lib/modules/" | head -1)
|
||||||
rm -f "$CUSTOM_MODULES/lib/modules/$KVER/build"
|
rm -f "$CUSTOM_MODULES/lib/modules/$ACTUAL_KVER/build"
|
||||||
rm -f "$CUSTOM_MODULES/lib/modules/$KVER/source"
|
rm -f "$CUSTOM_MODULES/lib/modules/$ACTUAL_KVER/source"
|
||||||
|
|
||||||
# Run depmod
|
depmod -a -b "$CUSTOM_MODULES" "$ACTUAL_KVER" 2>/dev/null || true
|
||||||
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"
|
cp .config "$CUSTOM_KERNEL_DIR/.config"
|
||||||
|
|
||||||
# --- Clean up ---
|
# --- Clean up ---
|
||||||
@@ -148,11 +195,10 @@ rm -rf "$KERNEL_BUILD_DIR"
|
|||||||
|
|
||||||
# --- Summary ---
|
# --- Summary ---
|
||||||
echo ""
|
echo ""
|
||||||
echo "==> ARM64 kernel build complete:"
|
echo "==> Generic ARM64 kernel build complete:"
|
||||||
echo " Image: $CUSTOM_IMAGE ($(du -h "$CUSTOM_IMAGE" | cut -f1))"
|
echo " Image: $CUSTOM_IMAGE ($(du -h "$CUSTOM_IMAGE" | cut -f1))"
|
||||||
echo " Kernel ver: $KVER"
|
echo " Kernel ver: $ACTUAL_KVER"
|
||||||
MOD_COUNT=$(find "$CUSTOM_MODULES/lib/modules/$KVER" -name '*.ko*' 2>/dev/null | wc -l)
|
MOD_COUNT=$(find "$CUSTOM_MODULES/lib/modules/$ACTUAL_KVER" -name '*.ko*' 2>/dev/null | wc -l)
|
||||||
echo " Modules: $MOD_COUNT"
|
echo " Modules: $MOD_COUNT"
|
||||||
echo " Modules size: $(du -sh "$CUSTOM_MODULES/lib/modules/$KVER" 2>/dev/null | cut -f1)"
|
echo " Modules size: $(du -sh "$CUSTOM_MODULES/lib/modules/$ACTUAL_KVER" 2>/dev/null | cut -f1)"
|
||||||
echo " DTBs: $(ls "$CUSTOM_DTBS"/*.dtb 2>/dev/null | wc -l)"
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
161
build/scripts/build-kernel-rpi.sh
Executable file
161
build/scripts/build-kernel-rpi.sh
Executable file
@@ -0,0 +1,161 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# build-kernel-rpi.sh — Build kernel for Raspberry Pi 4/5 (ARM64)
|
||||||
|
#
|
||||||
|
# Uses the official raspberrypi/linux kernel fork with bcm2711_defconfig as the
|
||||||
|
# base, overlaid with the shared container-config fragment.
|
||||||
|
#
|
||||||
|
# This is the RPi-specific build track. For generic ARM64 (UEFI / virtio /
|
||||||
|
# kernel.org mainline) see build/scripts/build-kernel-arm64.sh.
|
||||||
|
#
|
||||||
|
# Output is cached in $CACHE_DIR/custom-kernel-rpi/ 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-rpi"
|
||||||
|
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 "==> RPi 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 RPi kernel (raspberrypi/linux)..."
|
||||||
|
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/kernel-container.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 RPi 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 "==> RPi 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 "==> RPi 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 ""
|
||||||
@@ -85,85 +85,49 @@ echo " Source dir: $(basename "$KERNEL_SRC_DIR")"
|
|||||||
|
|
||||||
cd "$KERNEL_SRC_DIR"
|
cd "$KERNEL_SRC_DIR"
|
||||||
|
|
||||||
# --- Apply stock config + enable CONFIG_CGROUP_BPF ---
|
# --- Apply stock config + shared container-config fragment ---
|
||||||
echo "==> Applying stock Tiny Core config..."
|
echo "==> Applying stock Tiny Core config..."
|
||||||
cp "$KERNEL_CFG" .config
|
cp "$KERNEL_CFG" .config
|
||||||
|
|
||||||
echo "==> Enabling required kernel configs..."
|
CONFIG_FRAGMENT="$PROJECT_ROOT/build/config/kernel-container.fragment"
|
||||||
./scripts/config --enable CONFIG_CGROUP_BPF
|
if [ ! -f "$CONFIG_FRAGMENT" ]; then
|
||||||
./scripts/config --enable CONFIG_DEVTMPFS
|
echo "ERROR: Config fragment not found: $CONFIG_FRAGMENT"
|
||||||
./scripts/config --enable CONFIG_DEVTMPFS_MOUNT
|
exit 1
|
||||||
./scripts/config --enable CONFIG_MEMCG
|
fi
|
||||||
./scripts/config --enable CONFIG_CFS_BANDWIDTH
|
|
||||||
|
|
||||||
# --- Strip unnecessary subsystems for smallest footprint ---
|
# Apply the fragment: each "CONFIG_X=v" line becomes the right scripts/config
|
||||||
# This is a headless K8s edge appliance — no sound, GPU, wireless, etc.
|
# invocation; "# CONFIG_X is not set" comments become --disable.
|
||||||
echo "==> Disabling unnecessary subsystems for minimal footprint..."
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
# Sound subsystem (not needed on headless appliance)
|
# Two-pass apply: TC's stock config has CONFIG_SECURITY disabled, so olddefconfig
|
||||||
./scripts/config --disable SOUND
|
# strips the security subtree before its dependencies resolve. Re-applying the
|
||||||
|
# fragment after the first olddefconfig restores those entries.
|
||||||
# GPU/DRM (serial console only, no display)
|
echo "==> Applying kernel-container.fragment (pass 1)..."
|
||||||
./scripts/config --disable DRM
|
apply_fragment "$CONFIG_FRAGMENT"
|
||||||
|
|
||||||
# KVM hypervisor (this IS the guest/bare metal, not a hypervisor)
|
|
||||||
./scripts/config --disable KVM
|
|
||||||
|
|
||||||
# Media/camera/TV/radio (not needed)
|
|
||||||
./scripts/config --disable MEDIA_SUPPORT
|
|
||||||
|
|
||||||
# Wireless networking (wired edge device)
|
|
||||||
./scripts/config --disable WIRELESS
|
|
||||||
./scripts/config --disable WLAN
|
|
||||||
./scripts/config --disable CFG80211
|
|
||||||
|
|
||||||
# Bluetooth (not needed)
|
|
||||||
./scripts/config --disable BT
|
|
||||||
|
|
||||||
# NFC (not needed)
|
|
||||||
./scripts/config --disable NFC
|
|
||||||
|
|
||||||
# Infiniband (not needed on edge)
|
|
||||||
./scripts/config --disable INFINIBAND
|
|
||||||
|
|
||||||
# PCMCIA (legacy, not needed)
|
|
||||||
./scripts/config --disable PCMCIA
|
|
||||||
|
|
||||||
# Amateur radio (not needed)
|
|
||||||
./scripts/config --disable HAMRADIO
|
|
||||||
|
|
||||||
# ISDN (not needed)
|
|
||||||
./scripts/config --disable ISDN
|
|
||||||
|
|
||||||
# ATM networking (not needed)
|
|
||||||
./scripts/config --disable ATM
|
|
||||||
|
|
||||||
# Joystick/gamepad (not needed)
|
|
||||||
./scripts/config --disable INPUT_JOYSTICK
|
|
||||||
./scripts/config --disable INPUT_TABLET
|
|
||||||
|
|
||||||
# FPGA (not needed)
|
|
||||||
./scripts/config --disable FPGA
|
|
||||||
|
|
||||||
# First pass: resolve base dependencies before adding security configs.
|
|
||||||
# The stock TC config has "# CONFIG_SECURITY is not set" which causes
|
|
||||||
# olddefconfig to strip security-related options if applied in a single pass.
|
|
||||||
make olddefconfig
|
make olddefconfig
|
||||||
|
|
||||||
# Security: AppArmor LSM + Audit subsystem
|
echo "==> Applying kernel-container.fragment (pass 2)..."
|
||||||
# Applied AFTER first olddefconfig to ensure CONFIG_SECURITY dependencies
|
apply_fragment "$CONFIG_FRAGMENT"
|
||||||
# (SYSFS, MULTIUSER) are resolved before enabling the security subtree.
|
|
||||||
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"
|
|
||||||
|
|
||||||
# Second pass: resolve security config dependencies
|
|
||||||
make olddefconfig
|
make olddefconfig
|
||||||
|
|
||||||
# Verify critical configs are set
|
# Verify critical configs are set
|
||||||
|
|||||||
@@ -6,28 +6,61 @@
|
|||||||
# Part 2: System A (512 MB, ext4) — vmlinuz + kubesolo-os.gz (active)
|
# Part 2: System A (512 MB, ext4) — vmlinuz + kubesolo-os.gz (active)
|
||||||
# Part 3: System B (512 MB, ext4) — vmlinuz + kubesolo-os.gz (passive)
|
# Part 3: System B (512 MB, ext4) — vmlinuz + kubesolo-os.gz (passive)
|
||||||
# Part 4: Data (remaining, ext4) — persistent K8s state
|
# Part 4: Data (remaining, ext4) — persistent K8s state
|
||||||
|
#
|
||||||
|
# Supports both x86_64 (default) and ARM64 generic UEFI targets. ARM64 RPi
|
||||||
|
# uses a different image format — see build/scripts/create-rpi-image.sh.
|
||||||
|
#
|
||||||
|
# Environment:
|
||||||
|
# TARGET_ARCH amd64 (default) or arm64
|
||||||
|
# IMG_SIZE_MB Image size in MB (default 4096)
|
||||||
|
# CACHE_DIR Build cache (default <project>/build/cache)
|
||||||
|
# ROOTFS_DIR Rootfs work dir (default <project>/build/rootfs-work)
|
||||||
|
# OUTPUT_DIR Output dir (default <project>/output)
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
ROOTFS_DIR="${ROOTFS_DIR:-$PROJECT_ROOT/build/rootfs-work}"
|
ROOTFS_DIR="${ROOTFS_DIR:-$PROJECT_ROOT/build/rootfs-work}"
|
||||||
|
CACHE_DIR="${CACHE_DIR:-$PROJECT_ROOT/build/cache}"
|
||||||
OUTPUT_DIR="${OUTPUT_DIR:-$PROJECT_ROOT/output}"
|
OUTPUT_DIR="${OUTPUT_DIR:-$PROJECT_ROOT/output}"
|
||||||
VERSION="$(cat "$PROJECT_ROOT/VERSION")"
|
VERSION="$(cat "$PROJECT_ROOT/VERSION")"
|
||||||
OS_NAME="kubesolo-os"
|
OS_NAME="kubesolo-os"
|
||||||
|
TARGET_ARCH="${TARGET_ARCH:-amd64}"
|
||||||
|
|
||||||
IMG_OUTPUT="$OUTPUT_DIR/${OS_NAME}-${VERSION}.img"
|
|
||||||
IMG_SIZE_MB="${IMG_SIZE_MB:-4096}" # 4 GB default (larger for A/B)
|
IMG_SIZE_MB="${IMG_SIZE_MB:-4096}" # 4 GB default (larger for A/B)
|
||||||
|
|
||||||
VMLINUZ="$ROOTFS_DIR/vmlinuz"
|
# --- Arch-specific paths ---
|
||||||
|
case "$TARGET_ARCH" in
|
||||||
|
amd64)
|
||||||
|
IMG_OUTPUT="$OUTPUT_DIR/${OS_NAME}-${VERSION}.img"
|
||||||
|
VMLINUZ="$ROOTFS_DIR/vmlinuz"
|
||||||
|
GRUB_CFG="$PROJECT_ROOT/build/grub/grub.cfg"
|
||||||
|
GRUB_TARGET="x86_64-efi"
|
||||||
|
GRUB_EFI_BIN="bootx64.efi"
|
||||||
|
GRUB_INSTALL_BIOS=true
|
||||||
|
;;
|
||||||
|
arm64)
|
||||||
|
IMG_OUTPUT="$OUTPUT_DIR/${OS_NAME}-${VERSION}.arm64.img"
|
||||||
|
VMLINUZ="$CACHE_DIR/kernel-arm64-generic/Image"
|
||||||
|
GRUB_CFG="$PROJECT_ROOT/build/grub/grub-arm64.cfg"
|
||||||
|
GRUB_TARGET="arm64-efi"
|
||||||
|
GRUB_EFI_BIN="BOOTAA64.EFI"
|
||||||
|
GRUB_INSTALL_BIOS=false
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "ERROR: TARGET_ARCH must be 'amd64' or 'arm64' (got: $TARGET_ARCH)"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
INITRAMFS="$ROOTFS_DIR/kubesolo-os.gz"
|
INITRAMFS="$ROOTFS_DIR/kubesolo-os.gz"
|
||||||
GRUB_CFG="$PROJECT_ROOT/build/grub/grub.cfg"
|
|
||||||
GRUB_ENV_DEFAULTS="$PROJECT_ROOT/build/grub/grub-env-defaults"
|
GRUB_ENV_DEFAULTS="$PROJECT_ROOT/build/grub/grub-env-defaults"
|
||||||
|
|
||||||
for f in "$VMLINUZ" "$INITRAMFS" "$GRUB_CFG" "$GRUB_ENV_DEFAULTS"; do
|
for f in "$VMLINUZ" "$INITRAMFS" "$GRUB_CFG" "$GRUB_ENV_DEFAULTS"; do
|
||||||
[ -f "$f" ] || { echo "ERROR: Missing $f"; exit 1; }
|
[ -f "$f" ] || { echo "ERROR: Missing $f"; exit 1; }
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "==> Creating ${IMG_SIZE_MB}MB disk image with A/B partitions..."
|
echo "==> Creating ${IMG_SIZE_MB}MB ${TARGET_ARCH} disk image with A/B partitions..."
|
||||||
mkdir -p "$OUTPUT_DIR"
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
|
||||||
# Create sparse image
|
# Create sparse image
|
||||||
@@ -161,35 +194,44 @@ else
|
|||||||
mv "$GRUBENV_FILE.tmp" "$GRUBENV_FILE"
|
mv "$GRUBENV_FILE.tmp" "$GRUBENV_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Install GRUB EFI binary if available
|
# Install GRUB EFI binary
|
||||||
if command -v grub-mkimage >/dev/null 2>&1; then
|
# Modules required: part_gpt + fat (boot partition), ext2 (system A/B),
|
||||||
grub-mkimage -O x86_64-efi -o "$MNT_EFI/EFI/BOOT/bootx64.efi" \
|
# normal + linux + echo + configfile + loadenv (boot menu + grubenv),
|
||||||
-p /boot/grub \
|
# search_* (locate partitions by label).
|
||||||
part_gpt ext2 fat normal linux echo all_video test search \
|
# all_video + test are x86-specific (DRM init); leave them out on arm64.
|
||||||
search_fs_uuid search_label configfile loadenv \
|
if [ "$TARGET_ARCH" = "arm64" ]; then
|
||||||
2>/dev/null || echo " WARN: grub-mkimage failed — use QEMU -bios flag"
|
GRUB_MODULES="part_gpt ext2 fat normal linux echo test search search_fs_uuid search_label configfile loadenv"
|
||||||
elif command -v grub2-mkimage >/dev/null 2>&1; then
|
|
||||||
grub2-mkimage -O x86_64-efi -o "$MNT_EFI/EFI/BOOT/bootx64.efi" \
|
|
||||||
-p /boot/grub \
|
|
||||||
part_gpt ext2 fat normal linux echo all_video test search \
|
|
||||||
search_fs_uuid search_label configfile loadenv \
|
|
||||||
2>/dev/null || echo " WARN: grub2-mkimage failed — use QEMU -bios flag"
|
|
||||||
else
|
else
|
||||||
echo " WARN: grub-mkimage not found — EFI boot image not created"
|
GRUB_MODULES="part_gpt ext2 fat normal linux echo all_video test search search_fs_uuid search_label configfile loadenv"
|
||||||
echo " Install grub2-tools or use QEMU -kernel/-initrd flags"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# For BIOS boot: install GRUB i386-pc modules if available
|
# shellcheck disable=SC2086 # GRUB_MODULES is intentionally word-split
|
||||||
if command -v grub-install >/dev/null 2>&1; then
|
if command -v grub-mkimage >/dev/null 2>&1; then
|
||||||
|
grub-mkimage -O "$GRUB_TARGET" -o "$MNT_EFI/EFI/BOOT/$GRUB_EFI_BIN" \
|
||||||
|
-p /boot/grub $GRUB_MODULES \
|
||||||
|
|| echo " WARN: grub-mkimage failed — use QEMU -bios flag"
|
||||||
|
elif command -v grub2-mkimage >/dev/null 2>&1; then
|
||||||
|
grub2-mkimage -O "$GRUB_TARGET" -o "$MNT_EFI/EFI/BOOT/$GRUB_EFI_BIN" \
|
||||||
|
-p /boot/grub $GRUB_MODULES \
|
||||||
|
|| echo " WARN: grub2-mkimage failed — use QEMU -bios flag"
|
||||||
|
else
|
||||||
|
echo " WARN: grub-mkimage not found — EFI boot image not created"
|
||||||
|
echo " Install grub-efi-${TARGET_ARCH}-bin or use QEMU -kernel/-initrd flags"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For BIOS boot: install GRUB i386-pc modules (x86 only — ARM64 is UEFI-only).
|
||||||
|
if [ "$GRUB_INSTALL_BIOS" = "true" ]; then
|
||||||
|
if command -v grub-install >/dev/null 2>&1; then
|
||||||
grub-install --target=i386-pc --boot-directory="$MNT_EFI/boot" \
|
grub-install --target=i386-pc --boot-directory="$MNT_EFI/boot" \
|
||||||
--no-floppy "$LOOP" 2>/dev/null || {
|
--no-floppy "$LOOP" 2>/dev/null || {
|
||||||
echo " WARN: BIOS GRUB install failed — EFI-only or use QEMU -kernel"
|
echo " WARN: BIOS GRUB install failed — EFI-only or use QEMU -kernel"
|
||||||
}
|
}
|
||||||
elif command -v grub2-install >/dev/null 2>&1; then
|
elif command -v grub2-install >/dev/null 2>&1; then
|
||||||
grub2-install --target=i386-pc --boot-directory="$MNT_EFI/boot" \
|
grub2-install --target=i386-pc --boot-directory="$MNT_EFI/boot" \
|
||||||
--no-floppy "$LOOP" 2>/dev/null || {
|
--no-floppy "$LOOP" 2>/dev/null || {
|
||||||
echo " WARN: BIOS GRUB install failed — EFI-only or use QEMU -kernel"
|
echo " WARN: BIOS GRUB install failed — EFI-only or use QEMU -kernel"
|
||||||
}
|
}
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- System A Partition (active) ---
|
# --- System A Partition (active) ---
|
||||||
@@ -213,9 +255,9 @@ done
|
|||||||
sync
|
sync
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "==> Disk image created: $IMG_OUTPUT"
|
echo "==> ${TARGET_ARCH} disk image created: $IMG_OUTPUT"
|
||||||
echo " Size: $(du -h "$IMG_OUTPUT" | cut -f1)"
|
echo " Size: $(du -h "$IMG_OUTPUT" | cut -f1)"
|
||||||
echo " Part 1 (KSOLOEFI): GRUB + A/B boot config"
|
echo " Part 1 (KSOLOEFI): GRUB ($GRUB_TARGET) + A/B boot config"
|
||||||
echo " Part 2 (KSOLOA): System A — kernel + initramfs (active)"
|
echo " Part 2 (KSOLOA): System A — kernel + initramfs (active)"
|
||||||
echo " Part 3 (KSOLOB): System B — kernel + initramfs (passive)"
|
echo " Part 3 (KSOLOB): System B — kernel + initramfs (passive)"
|
||||||
echo " Part 4 (KSOLODATA): Persistent K8s state"
|
echo " Part 4 (KSOLODATA): Persistent K8s state"
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ IMG_OUTPUT="$OUTPUT_DIR/${OS_NAME}-${VERSION}.rpi.img"
|
|||||||
IMG_SIZE_MB="${IMG_SIZE_MB:-2048}" # 2 GB default
|
IMG_SIZE_MB="${IMG_SIZE_MB:-2048}" # 2 GB default
|
||||||
|
|
||||||
# ARM64 kernel (Image format, not bzImage)
|
# ARM64 kernel (Image format, not bzImage)
|
||||||
KERNEL="${CACHE_DIR}/custom-kernel-arm64/Image"
|
KERNEL="${CACHE_DIR}/custom-kernel-rpi/Image"
|
||||||
INITRAMFS="${ROOTFS_DIR}/kubesolo-os.gz"
|
INITRAMFS="${ROOTFS_DIR}/kubesolo-os.gz"
|
||||||
RPI_FIRMWARE_DIR="${CACHE_DIR}/rpi-firmware"
|
RPI_FIRMWARE_DIR="${CACHE_DIR}/rpi-firmware"
|
||||||
# DTBs MUST come from the kernel build (not firmware repo) to match the kernel.
|
# DTBs MUST come from the kernel build (not firmware repo) to match the kernel.
|
||||||
# A DTB mismatch causes sdhci-iproc to silently fail — zero block devices.
|
# A DTB mismatch causes sdhci-iproc to silently fail — zero block devices.
|
||||||
KERNEL_DTBS_DIR="${CACHE_DIR}/custom-kernel-arm64/dtbs"
|
KERNEL_DTBS_DIR="${CACHE_DIR}/custom-kernel-rpi/dtbs"
|
||||||
|
|
||||||
echo "==> Creating ${IMG_SIZE_MB}MB Raspberry Pi disk image..."
|
echo "==> Creating ${IMG_SIZE_MB}MB Raspberry Pi disk image..."
|
||||||
|
|
||||||
|
|||||||
@@ -51,8 +51,7 @@ if [ "$FETCH_ARCH" = "arm64" ]; then
|
|||||||
echo "==> Fetching RPi firmware..."
|
echo "==> Fetching RPi firmware..."
|
||||||
"$SCRIPT_DIR/fetch-rpi-firmware.sh"
|
"$SCRIPT_DIR/fetch-rpi-firmware.sh"
|
||||||
|
|
||||||
# Download ARM64 KubeSolo binary
|
# Download ARM64 KubeSolo binary (KUBESOLO_VERSION set from versions.env)
|
||||||
KUBESOLO_VERSION="${KUBESOLO_VERSION:-v1.1.0}"
|
|
||||||
KUBESOLO_BIN_ARM64="$CACHE_DIR/kubesolo-arm64"
|
KUBESOLO_BIN_ARM64="$CACHE_DIR/kubesolo-arm64"
|
||||||
if [ -f "$KUBESOLO_BIN_ARM64" ]; then
|
if [ -f "$KUBESOLO_BIN_ARM64" ]; then
|
||||||
echo "==> KubeSolo ARM64 binary already cached: $KUBESOLO_BIN_ARM64"
|
echo "==> KubeSolo ARM64 binary already cached: $KUBESOLO_BIN_ARM64"
|
||||||
@@ -112,7 +111,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# --- KubeSolo ---
|
# --- KubeSolo ---
|
||||||
KUBESOLO_VERSION="${KUBESOLO_VERSION:-v1.1.0}"
|
# KUBESOLO_VERSION sourced from versions.env
|
||||||
KUBESOLO_BIN="$CACHE_DIR/kubesolo"
|
KUBESOLO_BIN="$CACHE_DIR/kubesolo"
|
||||||
|
|
||||||
if [ -f "$KUBESOLO_BIN" ]; then
|
if [ -f "$KUBESOLO_BIN" ]; then
|
||||||
|
|||||||
@@ -109,7 +109,19 @@ fi
|
|||||||
# If a custom kernel was built (with CONFIG_CGROUP_BPF=y), use it.
|
# If a custom kernel was built (with CONFIG_CGROUP_BPF=y), use it.
|
||||||
# Otherwise fall back to TCZ-extracted modules with manual modules.dep.
|
# Otherwise fall back to TCZ-extracted modules with manual modules.dep.
|
||||||
if [ "$INJECT_ARCH" = "arm64" ]; then
|
if [ "$INJECT_ARCH" = "arm64" ]; then
|
||||||
CUSTOM_KERNEL_DIR="$CACHE_DIR/custom-kernel-arm64"
|
# TARGET_VARIANT selects which ARM64 kernel to consume:
|
||||||
|
# rpi -> $CACHE_DIR/custom-kernel-rpi/ (raspberrypi/linux fork)
|
||||||
|
# generic -> $CACHE_DIR/kernel-arm64-generic/ (mainline kernel.org LTS)
|
||||||
|
# Default is rpi for backwards compatibility with existing rpi-image target.
|
||||||
|
TARGET_VARIANT="${TARGET_VARIANT:-rpi}"
|
||||||
|
case "$TARGET_VARIANT" in
|
||||||
|
generic) CUSTOM_KERNEL_DIR="$CACHE_DIR/kernel-arm64-generic" ;;
|
||||||
|
rpi) CUSTOM_KERNEL_DIR="$CACHE_DIR/custom-kernel-rpi" ;;
|
||||||
|
*)
|
||||||
|
echo "ERROR: TARGET_VARIANT must be 'rpi' or 'generic' (got: $TARGET_VARIANT)"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
CUSTOM_VMLINUZ="$CUSTOM_KERNEL_DIR/Image"
|
CUSTOM_VMLINUZ="$CUSTOM_KERNEL_DIR/Image"
|
||||||
else
|
else
|
||||||
CUSTOM_KERNEL_DIR="$CACHE_DIR/custom-kernel"
|
CUSTOM_KERNEL_DIR="$CACHE_DIR/custom-kernel"
|
||||||
|
|||||||
124
docs/arm64-architecture.md
Normal file
124
docs/arm64-architecture.md
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# ARM64 Build Architecture
|
||||||
|
|
||||||
|
KubeSolo OS supports ARM64 via two distinct build tracks. This document defines the
|
||||||
|
split, lists which files belong to each track, and identifies the shared substrate.
|
||||||
|
|
||||||
|
## The two tracks
|
||||||
|
|
||||||
|
### Generic ARM64 (UEFI / virtio / GRUB)
|
||||||
|
|
||||||
|
**Target:** Any UEFI-compliant ARM64 host — Ampere/Graviton VMs, generic ARM64
|
||||||
|
servers, `qemu-system-aarch64 -machine virt`, future SBCs that boot via UEFI.
|
||||||
|
|
||||||
|
**Boot path:** UEFI firmware → GRUB-EFI → kernel + initramfs → KubeSolo init.
|
||||||
|
|
||||||
|
**Kernel:** Mainline Linux (kernel.org LTS), built from `defconfig` + shared
|
||||||
|
container-config fragment.
|
||||||
|
|
||||||
|
**Storage:** virtio-blk / NVMe / SATA — detected and probed by mainline drivers.
|
||||||
|
|
||||||
|
**Disk image format:** GPT, identical 4-partition layout to x86_64 (EFI + System A
|
||||||
|
+ System B + Data).
|
||||||
|
|
||||||
|
### Raspberry Pi ARM64
|
||||||
|
|
||||||
|
**Target:** Raspberry Pi 4 and 5 specifically.
|
||||||
|
|
||||||
|
**Boot path:** RPi EEPROM → VideoCore firmware (`start4.elf`) → `config.txt` →
|
||||||
|
kernel + DTB + initramfs → KubeSolo init. (No UEFI, no GRUB — `autoboot.txt`
|
||||||
|
provides the A/B selection.)
|
||||||
|
|
||||||
|
**Kernel:** Built from `raspberrypi/linux` fork with `bcm2711_defconfig`
|
||||||
|
(Pi 4) or `bcm2712_defconfig` (Pi 5). RPi-patched, includes BCM-specific drivers
|
||||||
|
(sdhci-iproc, bcm2835-mmc, GPIO, mailbox).
|
||||||
|
|
||||||
|
**Storage:** SD card via `sdhci-iproc` driver — requires kernel-built DTBs to match
|
||||||
|
the kernel binary.
|
||||||
|
|
||||||
|
**Disk image format:** MBR with `autoboot.txt` A/B redirect:
|
||||||
|
- Part 1: Boot/Control (FAT32, firmware + fallback kernel)
|
||||||
|
- Part 2: Boot A (FAT32, kernel + DTBs + initramfs)
|
||||||
|
- Part 3: Boot B (FAT32, same as A initially)
|
||||||
|
- Part 4: Data (ext4)
|
||||||
|
|
||||||
|
## File-by-file ownership
|
||||||
|
|
||||||
|
### Shared substrate (used by both tracks)
|
||||||
|
|
||||||
|
| Path | Why shared |
|
||||||
|
|------|------------|
|
||||||
|
| `init/` (all of it) | Boot is identical post-kernel — same staged init, same persistent mount, same KubeSolo launch |
|
||||||
|
| `cloud-init/` | Arch-agnostic Go binary |
|
||||||
|
| `update/` | Arch-agnostic Go binary; bootenv abstraction handles GRUB vs RPi-autoboot variants |
|
||||||
|
| `build/scripts/inject-kubesolo.sh` | Single script; switches `LIB_ARCH` / `LD_SO` based on `TARGET_ARCH` |
|
||||||
|
| `build/scripts/extract-core.sh` | Single script; arm64 branch uses piCore64 userland (arch-agnostic BusyBox) |
|
||||||
|
| `build/config/modules-arm64.list` | Already generic — no BCM-specific modules; works in QEMU virt, AWS Graviton, and RPi |
|
||||||
|
| `build/config/rpi-kernel-config.fragment` | **Misnamed.** Contents (cgroup, namespaces, netfilter, AppArmor) are arch-agnostic. Will be renamed `kernel-container.fragment` in Phase 2 and applied to x86, generic-ARM64, and RPi kernels alike. |
|
||||||
|
| `hack/dev-vm-arm64.sh` | Uses `-machine virt` + virtio — generic, not RPi-specific |
|
||||||
|
| `test/qemu/test-boot-arm64.sh` | Same as above |
|
||||||
|
|
||||||
|
### Generic ARM64 only (to be created in Phases 2–3)
|
||||||
|
|
||||||
|
| Path | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `build/scripts/build-kernel-arm64.sh` *(rewritten in Phase 2)* | Build mainline kernel.org LTS from `defconfig` + shared fragment + arm64-virt enables (`VIRTIO_BLK`, `EFI_STUB`). Replaces the existing RPi-flavoured script of the same name. |
|
||||||
|
| `build/scripts/create-disk-image-arm64.sh` *(new in Phase 3)* | Build UEFI-bootable raw disk image (GPT + System A/B + Data) using `grub-efi-arm64`. Or fold into existing `create-disk-image.sh` with an arch parameter. |
|
||||||
|
| `build/cache/kernel-arm64-generic/` | Build output for mainline ARM64 kernel — keep separate from RPi-kernel cache. |
|
||||||
|
|
||||||
|
### Raspberry Pi only (to be renamed/reorganised in Phase 2)
|
||||||
|
|
||||||
|
| Path | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `build/scripts/build-kernel-rpi.sh` *(renamed from `build-kernel-arm64.sh`)* | Build kernel from `raspberrypi/linux` with `bcm2711_defconfig` + shared fragment + RPi-specific overrides. |
|
||||||
|
| `build/scripts/create-rpi-image.sh` | Build SD card image (MBR + autoboot.txt + firmware blobs + DTBs). Already correctly scoped. |
|
||||||
|
| `build/scripts/fetch-rpi-firmware.sh` | Download VideoCore firmware blobs from `raspberrypi/firmware`. Already correctly scoped. |
|
||||||
|
| `build/config/rpi-kernel-overrides.fragment` *(new, Phase 2)* | Pi-specific kernel config knobs (DMA, audio off, etc.) layered on top of the shared container fragment. |
|
||||||
|
| `build/cache/custom-kernel-rpi/` *(renamed from `custom-kernel-arm64/`)* | Build output for RPi kernel — DTBs, modules, Image. |
|
||||||
|
| `versions.env` keys: `RPI_KERNEL_BRANCH`, `RPI_KERNEL_REPO`, `RPI_FIRMWARE_TAG`, `RPI_FIRMWARE_URL`, `PICORE_*` | Already correctly named. |
|
||||||
|
|
||||||
|
## Make targets
|
||||||
|
|
||||||
|
| Target | Track |
|
||||||
|
|--------|-------|
|
||||||
|
| `make iso` | x86_64 |
|
||||||
|
| `make disk-image` | x86_64 |
|
||||||
|
| `make kernel` | x86_64 |
|
||||||
|
| `make kernel-arm64` *(Phase 2: now builds mainline)* | Generic ARM64 |
|
||||||
|
| `make rootfs-arm64` | Generic ARM64 (and reusable for RPi rootfs) |
|
||||||
|
| `make disk-image-arm64` *(Phase 3: new)* | Generic ARM64 |
|
||||||
|
| `make kernel-rpi` *(Phase 2: renamed from former kernel-arm64)* | RPi |
|
||||||
|
| `make rpi-image` | RPi |
|
||||||
|
|
||||||
|
## Why two tracks, not one
|
||||||
|
|
||||||
|
The RPi boot path is fundamentally different from generic ARM64:
|
||||||
|
|
||||||
|
- **No UEFI.** RPi boots through a multi-stage firmware chain that ends with
|
||||||
|
`config.txt` parsing and direct kernel load. UEFI/GRUB is not an option without
|
||||||
|
third-party firmware (which has its own bugs).
|
||||||
|
- **DTB required.** RPi kernel needs a device tree blob matching the kernel binary;
|
||||||
|
generic ARM64 under UEFI uses ACPI or self-describing virtio.
|
||||||
|
- **Custom drivers.** SD card (sdhci-iproc), GPIO, mailbox interfaces require
|
||||||
|
RPi-patched kernel sources. Mainline support exists but lags behind the
|
||||||
|
raspberrypi/linux fork for new boards.
|
||||||
|
- **A/B selection mechanism.** RPi uses `autoboot.txt` + EEPROM cooperation; generic
|
||||||
|
ARM64 uses GRUB's `boot_default`/`boot_counter` envvars (same as x86_64).
|
||||||
|
|
||||||
|
Trying to unify into a single track would force compromises in both. Two tracks
|
||||||
|
sharing the post-kernel substrate (init, cloud-init, update agent) gives us the best
|
||||||
|
of both: code reuse where it makes sense, divergence only where the hardware demands
|
||||||
|
it.
|
||||||
|
|
||||||
|
## Migration plan
|
||||||
|
|
||||||
|
This document is descriptive of the **target** v0.3.0 layout. The current code
|
||||||
|
(as of v0.2.0) has:
|
||||||
|
|
||||||
|
- `build/scripts/build-kernel-arm64.sh` building the RPi kernel (will be renamed in
|
||||||
|
Phase 2).
|
||||||
|
- `build/config/rpi-kernel-config.fragment` containing generic configs (will be
|
||||||
|
renamed in Phase 2).
|
||||||
|
- No generic ARM64 kernel script (will be created in Phase 2).
|
||||||
|
- No generic ARM64 disk image script (will be created in Phase 3).
|
||||||
|
|
||||||
|
Phases 2 and 3 of the v0.3.0 plan execute the migration.
|
||||||
165
docs/ci-runners.md
Normal file
165
docs/ci-runners.md
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
# CI Runners
|
||||||
|
|
||||||
|
KubeSolo OS is built and tested on Gitea Actions runners. This document records the
|
||||||
|
runners currently in service and how to register a new one if a host is wiped.
|
||||||
|
|
||||||
|
## Active runners
|
||||||
|
|
||||||
|
| Name | Host | Arch | OS | Labels | Notes |
|
||||||
|
|------|------|------|-----|--------|-------|
|
||||||
|
| `odroid-arm64` | `odroid.local` | aarch64 | Ubuntu 22.04 LTS | `arm64-linux`, `ubuntu-latest`, `ubuntu-24.04`, `ubuntu-22.04` | Native ARM64 builder; 6 cores, 1.8 GB RAM + 4 GB swap; runs as systemd service `act_runner` |
|
||||||
|
|
||||||
|
## Workflow targeting
|
||||||
|
|
||||||
|
ARM64-specific jobs target the Odroid via the `arm64-linux` label:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
build-arm64:
|
||||||
|
runs-on: arm64-linux
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: make rootfs-arm64
|
||||||
|
```
|
||||||
|
|
||||||
|
Generic ubuntu jobs that don't care about arch fall through to whichever runner picks
|
||||||
|
them up first; on the Odroid they run in Docker via the `ubuntu-latest` /
|
||||||
|
`ubuntu-22.04` / `ubuntu-24.04` labels.
|
||||||
|
|
||||||
|
## Registering a new runner
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Linux host (Ubuntu / Debian preferred; the install instructions below use Ubuntu
|
||||||
|
22.04+ paths).
|
||||||
|
- Outbound HTTPS to the Gitea instance.
|
||||||
|
- Root access on the runner host (the runner needs to create loop devices and run
|
||||||
|
`mkfs.ext4` for disk-image builds).
|
||||||
|
- A Gitea Actions runner registration token. Get it from:
|
||||||
|
- **Repo-scoped:** `<repo>/settings/actions/runners` → "Create new Runner"
|
||||||
|
- **Org-scoped (preferred for this project):** `<org>/-/settings/actions/runners` →
|
||||||
|
"Create new Runner"
|
||||||
|
- **Site-scoped:** `/-/admin/actions/runners` → "Create new Runner"
|
||||||
|
|
||||||
|
### Step 1 — Add swap if the host has <4 GB RAM
|
||||||
|
|
||||||
|
Kernel builds in later phases need ~2 GB resident; tight hosts will OOM-kill `cc1`
|
||||||
|
without swap.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo fallocate -l 4G /swapfile
|
||||||
|
sudo chmod 600 /swapfile
|
||||||
|
sudo mkswap /swapfile
|
||||||
|
sudo swapon /swapfile
|
||||||
|
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2 — Install the gitea-runner binary
|
||||||
|
|
||||||
|
Pinned to a known-good version. Check
|
||||||
|
<https://gitea.com/gitea/runner/releases> for the current stable tag before
|
||||||
|
bumping.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo -i
|
||||||
|
mkdir -p /opt/act_runner && cd /opt/act_runner
|
||||||
|
|
||||||
|
# Bump VERSION to the current stable release as needed
|
||||||
|
VERSION=1.0.3
|
||||||
|
ARCH=$(uname -m | sed 's/aarch64/arm64/; s/x86_64/amd64/')
|
||||||
|
|
||||||
|
curl -fL "https://gitea.com/gitea/runner/releases/download/v${VERSION}/gitea-runner-${VERSION}-linux-${ARCH}" \
|
||||||
|
-o act_runner
|
||||||
|
chmod +x act_runner
|
||||||
|
./act_runner --version
|
||||||
|
```
|
||||||
|
|
||||||
|
> The upstream project was renamed `act_runner` → `gitea-runner` at the v1.0.0
|
||||||
|
> release. The release asset filenames use `gitea-runner-*` even though we keep the
|
||||||
|
> local binary named `act_runner` to match this systemd unit. The CLI surface
|
||||||
|
> (`register`, `daemon`, `generate-config`) is unchanged.
|
||||||
|
|
||||||
|
### Step 3 — Register against Gitea
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./act_runner register --no-interactive \
|
||||||
|
--instance https://git.oe74.net \
|
||||||
|
--token PASTE_TOKEN_HERE \
|
||||||
|
--name <hostname> \
|
||||||
|
--labels arm64-linux # adjust label for amd64 hosts
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates a `.runner` file with the registration credentials.
|
||||||
|
|
||||||
|
### Step 4 — Generate and tune config
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./act_runner generate-config > config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
In `config.yaml`, confirm the `runner.labels:` block includes the labels you want.
|
||||||
|
The `:host` suffix routes jobs directly to the host (no Docker wrapper) — required
|
||||||
|
for disk-image builds that need loop devices and `mkfs`.
|
||||||
|
|
||||||
|
Example labels for an arm64 host:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
runner:
|
||||||
|
labels:
|
||||||
|
- "arm64-linux:host"
|
||||||
|
- "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest"
|
||||||
|
- "ubuntu-24.04:docker://docker.gitea.com/runner-images:ubuntu-24.04"
|
||||||
|
- "ubuntu-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5 — Install as a systemd service
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat > /etc/systemd/system/act_runner.service << 'EOF'
|
||||||
|
[Unit]
|
||||||
|
Description=Gitea Actions runner
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/opt/act_runner/act_runner daemon --config /opt/act_runner/config.yaml
|
||||||
|
WorkingDirectory=/opt/act_runner
|
||||||
|
User=root
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now act_runner
|
||||||
|
systemctl status act_runner --no-pager
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6 — Verify in Gitea UI
|
||||||
|
|
||||||
|
Visit the runners page at the scope you registered against. The runner should appear
|
||||||
|
as `Idle` with the labels you configured.
|
||||||
|
|
||||||
|
## Removing a runner
|
||||||
|
|
||||||
|
On the host:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl disable --now act_runner
|
||||||
|
rm -rf /opt/act_runner /etc/systemd/system/act_runner.service
|
||||||
|
systemctl daemon-reload
|
||||||
|
```
|
||||||
|
|
||||||
|
Then delete the runner entry from the Gitea Actions UI so Gitea stops trying to
|
||||||
|
schedule against it.
|
||||||
|
|
||||||
|
## Operational notes
|
||||||
|
|
||||||
|
- The runner stores in-progress job working directories under `/tmp/act_runner` by
|
||||||
|
default. Large disk-image builds may need that path moved to a larger volume —
|
||||||
|
edit `host.workdir_parent:` in `config.yaml`.
|
||||||
|
- Logs are visible via `journalctl -u act_runner -f`.
|
||||||
|
- If a job is interrupted (e.g. host reboot mid-build), the Gitea UI will mark it as
|
||||||
|
failed/cancelled. Re-run from the Actions UI.
|
||||||
@@ -1,64 +1,163 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# dev-vm-arm64.sh — Launch ARM64 QEMU VM for development
|
# dev-vm-arm64.sh — Launch ARM64 QEMU VM for development
|
||||||
#
|
#
|
||||||
# Uses qemu-system-aarch64 with -machine virt to emulate an ARM64 system.
|
# Two modes:
|
||||||
# This is useful for testing ARM64/RPi builds on x86_64 hosts.
|
#
|
||||||
|
# Default (direct kernel boot — fast iteration):
|
||||||
|
# qemu loads the kernel Image + initramfs directly via -kernel/-initrd.
|
||||||
|
# Skips bootloader, UEFI firmware, and disk image entirely.
|
||||||
|
# Use this for kernel and init-script changes.
|
||||||
|
#
|
||||||
|
# --disk (full UEFI boot — integration testing):
|
||||||
|
# qemu boots the .arm64.img disk image via UEFI firmware -> GRUB -> kernel.
|
||||||
|
# Exercises the full boot chain. Use this when changing the disk image
|
||||||
|
# layout, GRUB config, or anything that touches the EFI partition.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./hack/dev-vm-arm64.sh # Use default kernel + initramfs
|
# ./hack/dev-vm-arm64.sh # direct kernel boot (default)
|
||||||
# ./hack/dev-vm-arm64.sh <kernel> <initramfs> # Specify custom paths
|
# ./hack/dev-vm-arm64.sh --disk # full UEFI boot from built image
|
||||||
# ./hack/dev-vm-arm64.sh --debug # Enable debug logging
|
# ./hack/dev-vm-arm64.sh --debug # enable kubesolo.debug
|
||||||
# ./hack/dev-vm-arm64.sh --shell # Drop to emergency shell
|
# ./hack/dev-vm-arm64.sh --shell # drop to emergency shell
|
||||||
|
# ./hack/dev-vm-arm64.sh --disk /path/to.img # boot a specific disk image
|
||||||
|
# ./hack/dev-vm-arm64.sh <kernel> <initramfs> # direct boot with custom files
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
VERSION="$(cat "$PROJECT_ROOT/VERSION")"
|
||||||
|
|
||||||
|
MODE="kernel" # kernel | disk
|
||||||
VMLINUZ=""
|
VMLINUZ=""
|
||||||
INITRD=""
|
INITRD=""
|
||||||
|
DISK_IMAGE=""
|
||||||
EXTRA_APPEND=""
|
EXTRA_APPEND=""
|
||||||
|
|
||||||
# Parse arguments
|
while [ $# -gt 0 ]; do
|
||||||
for arg in "$@"; do
|
case "$1" in
|
||||||
case "$arg" in
|
--shell) EXTRA_APPEND="$EXTRA_APPEND kubesolo.shell"; shift ;;
|
||||||
--shell) EXTRA_APPEND="$EXTRA_APPEND kubesolo.shell" ;;
|
--debug) EXTRA_APPEND="$EXTRA_APPEND kubesolo.debug"; shift ;;
|
||||||
--debug) EXTRA_APPEND="$EXTRA_APPEND kubesolo.debug" ;;
|
--disk)
|
||||||
*)
|
MODE="disk"
|
||||||
if [ -z "$VMLINUZ" ]; then
|
shift
|
||||||
VMLINUZ="$arg"
|
# Optional next-arg as disk image path
|
||||||
elif [ -z "$INITRD" ]; then
|
if [ $# -gt 0 ] && [ -f "$1" ]; then
|
||||||
INITRD="$arg"
|
DISK_IMAGE="$1"
|
||||||
|
shift
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
*)
|
||||||
|
if [ "$MODE" = "kernel" ] && [ -z "$VMLINUZ" ]; then
|
||||||
|
VMLINUZ="$1"
|
||||||
|
elif [ "$MODE" = "kernel" ] && [ -z "$INITRD" ]; then
|
||||||
|
INITRD="$1"
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# Defaults
|
# ---------------------------------------------------------------------------
|
||||||
VMLINUZ="${VMLINUZ:-$PROJECT_ROOT/build/cache/custom-kernel-arm64/Image}"
|
# UEFI firmware probe (used for --disk mode)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
find_uefi_firmware() {
|
||||||
|
local candidates=(
|
||||||
|
/usr/share/qemu-efi-aarch64/QEMU_EFI.fd
|
||||||
|
/usr/share/AAVMF/AAVMF_CODE.fd
|
||||||
|
/usr/share/edk2/aarch64/QEMU_EFI.fd
|
||||||
|
/usr/share/qemu/edk2-aarch64-code.fd
|
||||||
|
/opt/homebrew/share/qemu/edk2-aarch64-code.fd
|
||||||
|
/usr/local/share/qemu/edk2-aarch64-code.fd
|
||||||
|
)
|
||||||
|
for f in "${candidates[@]}"; do
|
||||||
|
[ -f "$f" ] && echo "$f" && return 0
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# mkfs.ext4 probe (kernel mode creates a scratch data disk)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
find_mkfs_ext4() {
|
||||||
|
if command -v mkfs.ext4 >/dev/null 2>&1; then
|
||||||
|
echo "mkfs.ext4"
|
||||||
|
elif [ -x "/opt/homebrew/opt/e2fsprogs/sbin/mkfs.ext4" ]; then
|
||||||
|
echo "/opt/homebrew/opt/e2fsprogs/sbin/mkfs.ext4"
|
||||||
|
elif [ -x "/usr/local/opt/e2fsprogs/sbin/mkfs.ext4" ]; then
|
||||||
|
echo "/usr/local/opt/e2fsprogs/sbin/mkfs.ext4"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# Disk mode: boot the built .arm64.img through UEFI firmware + GRUB
|
||||||
|
# ===========================================================================
|
||||||
|
if [ "$MODE" = "disk" ]; then
|
||||||
|
DISK_IMAGE="${DISK_IMAGE:-$PROJECT_ROOT/output/kubesolo-os-${VERSION}.arm64.img}"
|
||||||
|
|
||||||
|
if [ ! -f "$DISK_IMAGE" ]; then
|
||||||
|
echo "ERROR: Disk image not found: $DISK_IMAGE"
|
||||||
|
echo " Run 'make disk-image-arm64' to build it."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
UEFI_FW="$(find_uefi_firmware || true)"
|
||||||
|
if [ -z "$UEFI_FW" ]; then
|
||||||
|
echo "ERROR: No ARM64 UEFI firmware found."
|
||||||
|
echo " Install one of:"
|
||||||
|
echo " apt install qemu-efi-aarch64 # Debian/Ubuntu"
|
||||||
|
echo " dnf install edk2-aarch64 # Fedora/RHEL"
|
||||||
|
echo " brew install qemu # macOS (bundled)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Pad UEFI firmware variable store to 64 MiB if QEMU expects pflash sizing.
|
||||||
|
# Most ARM64 EFI .fd files are 64 MB; if yours is smaller, QEMU may refuse.
|
||||||
|
echo "==> Launching ARM64 QEMU (UEFI disk boot)..."
|
||||||
|
echo " Firmware: $UEFI_FW"
|
||||||
|
echo " Disk: $DISK_IMAGE"
|
||||||
|
echo ""
|
||||||
|
echo " K8s API: localhost:6443"
|
||||||
|
echo " SSH: localhost:2222"
|
||||||
|
echo " Press Ctrl+A X to exit QEMU"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
qemu-system-aarch64 \
|
||||||
|
-machine virt \
|
||||||
|
-cpu cortex-a72 \
|
||||||
|
-m 2048 \
|
||||||
|
-smp 2 \
|
||||||
|
-nographic \
|
||||||
|
-bios "$UEFI_FW" \
|
||||||
|
-drive "file=$DISK_IMAGE,format=raw,if=virtio,media=disk" \
|
||||||
|
-net "nic,model=virtio" \
|
||||||
|
-net "user,hostfwd=tcp::6443-:6443,hostfwd=tcp::2222-:22"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# Kernel mode (default): direct -kernel / -initrd, fast iteration
|
||||||
|
# ===========================================================================
|
||||||
|
VMLINUZ="${VMLINUZ:-$PROJECT_ROOT/build/cache/kernel-arm64-generic/Image}"
|
||||||
INITRD="${INITRD:-$PROJECT_ROOT/build/rootfs-work/kubesolo-os.gz}"
|
INITRD="${INITRD:-$PROJECT_ROOT/build/rootfs-work/kubesolo-os.gz}"
|
||||||
|
|
||||||
# Verify files exist
|
# Fallback: previous-generation RPi kernel cache, in case someone hasn't yet
|
||||||
|
# rebuilt under v0.3 paths.
|
||||||
|
if [ ! -f "$VMLINUZ" ] && [ -f "$PROJECT_ROOT/build/cache/custom-kernel-rpi/Image" ]; then
|
||||||
|
VMLINUZ="$PROJECT_ROOT/build/cache/custom-kernel-rpi/Image"
|
||||||
|
echo "==> Note: falling back to RPi kernel ($VMLINUZ)"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ ! -f "$VMLINUZ" ]; then
|
if [ ! -f "$VMLINUZ" ]; then
|
||||||
echo "ERROR: Kernel not found: $VMLINUZ"
|
echo "ERROR: Kernel not found: $VMLINUZ"
|
||||||
echo " Run 'make kernel-arm64' to build the ARM64 kernel."
|
echo " Run 'make kernel-arm64' (generic) or 'make kernel-rpi' to build a kernel."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [ ! -f "$INITRD" ]; then
|
if [ ! -f "$INITRD" ]; then
|
||||||
echo "ERROR: Initrd not found: $INITRD"
|
echo "ERROR: Initrd not found: $INITRD"
|
||||||
echo " Run 'make initramfs' to build the initramfs."
|
echo " Run 'make rootfs-arm64' to build the initramfs."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Find mkfs.ext4
|
MKFS_EXT4="$(find_mkfs_ext4)"
|
||||||
MKFS_EXT4=""
|
|
||||||
if command -v mkfs.ext4 >/dev/null 2>&1; then
|
|
||||||
MKFS_EXT4="mkfs.ext4"
|
|
||||||
elif [ -x "/opt/homebrew/opt/e2fsprogs/sbin/mkfs.ext4" ]; then
|
|
||||||
MKFS_EXT4="/opt/homebrew/opt/e2fsprogs/sbin/mkfs.ext4"
|
|
||||||
elif [ -x "/usr/local/opt/e2fsprogs/sbin/mkfs.ext4" ]; then
|
|
||||||
MKFS_EXT4="/usr/local/opt/e2fsprogs/sbin/mkfs.ext4"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$MKFS_EXT4" ]; then
|
if [ -z "$MKFS_EXT4" ]; then
|
||||||
echo "ERROR: mkfs.ext4 not found. Install e2fsprogs:"
|
echo "ERROR: mkfs.ext4 not found. Install e2fsprogs:"
|
||||||
if [ "$(uname)" = "Darwin" ]; then
|
if [ "$(uname)" = "Darwin" ]; then
|
||||||
@@ -70,13 +169,12 @@ if [ -z "$MKFS_EXT4" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create data disk
|
|
||||||
DATA_DISK="$(mktemp /tmp/kubesolo-arm64-data-XXXXXX).img"
|
DATA_DISK="$(mktemp /tmp/kubesolo-arm64-data-XXXXXX).img"
|
||||||
dd if=/dev/zero of="$DATA_DISK" bs=1M count=1024 2>/dev/null
|
dd if=/dev/zero of="$DATA_DISK" bs=1M count=1024 2>/dev/null
|
||||||
"$MKFS_EXT4" -q -L KSOLODATA "$DATA_DISK" 2>/dev/null
|
"$MKFS_EXT4" -q -L KSOLODATA "$DATA_DISK" 2>/dev/null
|
||||||
trap 'rm -f "$DATA_DISK"' EXIT
|
trap 'rm -f "$DATA_DISK"' EXIT
|
||||||
|
|
||||||
echo "==> Launching ARM64 QEMU VM..."
|
echo "==> Launching ARM64 QEMU (direct kernel boot)..."
|
||||||
echo " Kernel: $VMLINUZ"
|
echo " Kernel: $VMLINUZ"
|
||||||
echo " Initrd: $INITRD"
|
echo " Initrd: $INITRD"
|
||||||
echo " Data: $DATA_DISK"
|
echo " Data: $DATA_DISK"
|
||||||
|
|||||||
129
test/qemu/test-boot-arm64-disk.sh
Executable file
129
test/qemu/test-boot-arm64-disk.sh
Executable file
@@ -0,0 +1,129 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# test-boot-arm64-disk.sh — Boot the ARM64 .arm64.img via UEFI + GRUB and
|
||||||
|
# verify the init system reaches stage 90.
|
||||||
|
#
|
||||||
|
# This is the full-stack integration test: UEFI firmware -> GRUB -> kernel ->
|
||||||
|
# initramfs -> staged init. Contrast with test-boot-arm64.sh which skips the
|
||||||
|
# bootloader and loads kernel/initramfs directly.
|
||||||
|
#
|
||||||
|
# Exit 0 = PASS, Exit 1 = FAIL.
|
||||||
|
#
|
||||||
|
# Usage: ./test/qemu/test-boot-arm64-disk.sh [disk.img]
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
VERSION="$(cat "$PROJECT_ROOT/VERSION")"
|
||||||
|
|
||||||
|
DISK_IMAGE="${1:-$PROJECT_ROOT/output/kubesolo-os-${VERSION}.arm64.img}"
|
||||||
|
TIMEOUT=180
|
||||||
|
|
||||||
|
echo "==> ARM64 UEFI Disk Boot Test"
|
||||||
|
echo " Disk image: $DISK_IMAGE"
|
||||||
|
echo " Timeout: ${TIMEOUT}s"
|
||||||
|
|
||||||
|
if [ ! -f "$DISK_IMAGE" ]; then
|
||||||
|
echo "ERROR: Disk image not found: $DISK_IMAGE"
|
||||||
|
echo " Run 'make disk-image-arm64' to build it."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v qemu-system-aarch64 >/dev/null 2>&1; then
|
||||||
|
echo "ERROR: qemu-system-aarch64 not found."
|
||||||
|
echo " apt install qemu-system-arm # Debian/Ubuntu"
|
||||||
|
echo " dnf install qemu-system-aarch64 # Fedora/RHEL"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Locate UEFI firmware ---
|
||||||
|
UEFI_FW=""
|
||||||
|
for candidate in \
|
||||||
|
/usr/share/qemu-efi-aarch64/QEMU_EFI.fd \
|
||||||
|
/usr/share/AAVMF/AAVMF_CODE.fd \
|
||||||
|
/usr/share/edk2/aarch64/QEMU_EFI.fd \
|
||||||
|
/usr/share/qemu/edk2-aarch64-code.fd \
|
||||||
|
/opt/homebrew/share/qemu/edk2-aarch64-code.fd \
|
||||||
|
/usr/local/share/qemu/edk2-aarch64-code.fd
|
||||||
|
do
|
||||||
|
if [ -f "$candidate" ]; then
|
||||||
|
UEFI_FW="$candidate"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$UEFI_FW" ]; then
|
||||||
|
echo "ERROR: No ARM64 UEFI firmware found."
|
||||||
|
echo " apt install qemu-efi-aarch64"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " UEFI fw: $UEFI_FW"
|
||||||
|
|
||||||
|
# Copy disk image to a scratch file so the test doesn't mutate the source.
|
||||||
|
# UEFI will write to grubenv on the EFI partition; we don't want to bake those
|
||||||
|
# changes into the canonical build artifact.
|
||||||
|
SCRATCH_DISK=$(mktemp /tmp/kubesolo-arm64-disk-test-XXXXXX.img)
|
||||||
|
SERIAL_LOG=$(mktemp /tmp/kubesolo-arm64-disk-serial-XXXXXX.log)
|
||||||
|
QEMU_PID=""
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
[ -n "$QEMU_PID" ] && kill "$QEMU_PID" 2>/dev/null || true
|
||||||
|
rm -f "$SCRATCH_DISK" "$SERIAL_LOG"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
cp --reflink=auto "$DISK_IMAGE" "$SCRATCH_DISK" 2>/dev/null || cp "$DISK_IMAGE" "$SCRATCH_DISK"
|
||||||
|
|
||||||
|
# --- Launch QEMU ---
|
||||||
|
qemu-system-aarch64 \
|
||||||
|
-machine virt \
|
||||||
|
-cpu cortex-a72 \
|
||||||
|
-m 2048 \
|
||||||
|
-smp 2 \
|
||||||
|
-nographic \
|
||||||
|
-bios "$UEFI_FW" \
|
||||||
|
-drive "file=$SCRATCH_DISK,format=raw,if=virtio,media=disk" \
|
||||||
|
-net nic,model=virtio \
|
||||||
|
-net user \
|
||||||
|
-serial "file:$SERIAL_LOG" &
|
||||||
|
QEMU_PID=$!
|
||||||
|
|
||||||
|
echo " Waiting for boot (PID $QEMU_PID)..."
|
||||||
|
ELAPSED=0
|
||||||
|
SUCCESS=0
|
||||||
|
while [ "$ELAPSED" -lt "$TIMEOUT" ]; do
|
||||||
|
if grep -q "\[kubesolo-init\] \[OK\] Stage 90-kubesolo.sh complete" "$SERIAL_LOG" 2>/dev/null; then
|
||||||
|
SUCCESS=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if grep -q "KubeSolo is running" "$SERIAL_LOG" 2>/dev/null; then
|
||||||
|
SUCCESS=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if ! kill -0 "$QEMU_PID" 2>/dev/null; then
|
||||||
|
echo ""
|
||||||
|
echo "==> FAIL: QEMU exited prematurely"
|
||||||
|
echo " Last 30 lines of serial output:"
|
||||||
|
tail -30 "$SERIAL_LOG" 2>/dev/null || echo " (no output)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
ELAPSED=$((ELAPSED + 2))
|
||||||
|
printf "\r Elapsed: %ds / %ds" "$ELAPSED" "$TIMEOUT"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
kill "$QEMU_PID" 2>/dev/null || true
|
||||||
|
wait "$QEMU_PID" 2>/dev/null || true
|
||||||
|
QEMU_PID=""
|
||||||
|
|
||||||
|
if [ "$SUCCESS" = "1" ]; then
|
||||||
|
echo "==> ARM64 UEFI Disk Boot Test PASSED (${ELAPSED}s)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> ARM64 UEFI Disk Boot Test FAILED (timeout ${TIMEOUT}s)"
|
||||||
|
echo ""
|
||||||
|
echo "==> Last 50 lines of serial output:"
|
||||||
|
tail -50 "$SERIAL_LOG" 2>/dev/null || echo " (no output)"
|
||||||
|
exit 1
|
||||||
Reference in New Issue
Block a user