feat: initial Phase 1 PoC scaffolding for KubeSolo OS

Complete Phase 1 implementation of KubeSolo OS — an immutable, bootable
Linux distribution built on Tiny Core Linux for running KubeSolo
single-node Kubernetes.

Build system:
- Makefile with fetch, rootfs, initramfs, iso, disk-image targets
- Dockerfile.builder for reproducible builds
- Scripts to download Tiny Core, extract rootfs, inject KubeSolo,
  pack initramfs, and create bootable ISO/disk images

Init system (10 POSIX sh stages):
- Early mount (proc/sys/dev/cgroup2), cmdline parsing, persistent
  mount with bind-mounts, kernel module loading, sysctl, DHCP
  networking, hostname, clock sync, containerd prep, KubeSolo exec

Shared libraries:
- functions.sh (device wait, IP lookup, config helpers)
- network.sh (static IP, config persistence, interface detection)
- health.sh (containerd, API server, node readiness checks)
- Emergency shell for boot failure debugging

Testing:
- QEMU boot test with serial log marker detection
- K8s readiness test with kubectl verification
- Persistence test (reboot + verify state survives)
- Workload deployment test (nginx pod)
- Local storage test (PVC + local-path provisioner)
- Network policy test
- Reusable run-vm.sh launcher

Developer tools:
- dev-vm.sh (interactive QEMU with port forwarding)
- rebuild-initramfs.sh (fast iteration)
- inject-ssh.sh (dropbear SSH for debugging)
- extract-kernel-config.sh + kernel-audit.sh

Documentation:
- Full design document with architecture research
- Boot flow documentation covering all 10 init stages
- Cloud-init examples (DHCP, static IP, Portainer Edge, air-gapped)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 10:18:42 -06:00
commit e372df578b
50 changed files with 4392 additions and 0 deletions

34
build/Dockerfile.builder Normal file
View File

@@ -0,0 +1,34 @@
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
bash \
bsdtar \
cpio \
curl \
dosfstools \
e2fsprogs \
fdisk \
genisoimage \
gzip \
isolinux \
losetup \
make \
parted \
squashfs-tools \
syslinux \
syslinux-common \
syslinux-utils \
wget \
xorriso \
xz-utils \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build
COPY . /build
RUN chmod +x build/scripts/*.sh build/config/*.sh
ENTRYPOINT ["/usr/bin/make"]
CMD ["iso"]

169
build/config/kernel-audit.sh Executable file
View File

@@ -0,0 +1,169 @@
#!/bin/bash
# kernel-audit.sh — Verify kernel config has all required features for KubeSolo
# Usage: ./kernel-audit.sh [/path/to/kernel/.config]
# If no path given, attempts to read from /proc/config.gz or boot config
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# --- Locate kernel config ---
find_kernel_config() {
if [[ -n "${1:-}" ]] && [[ -f "$1" ]]; then
echo "$1"
return 0
fi
# Try /proc/config.gz (if CONFIG_IKCONFIG_PROC=y)
if [[ -f /proc/config.gz ]]; then
local tmp
tmp=$(mktemp)
zcat /proc/config.gz > "$tmp"
echo "$tmp"
return 0
fi
# Try /boot/config-$(uname -r)
local boot_config="/boot/config-$(uname -r)"
if [[ -f "$boot_config" ]]; then
echo "$boot_config"
return 0
fi
echo ""
return 1
}
CONFIG_FILE=$(find_kernel_config "${1:-}") || {
echo -e "${RED}ERROR: Cannot find kernel config.${NC}"
echo "Provide path as argument, or ensure /proc/config.gz or /boot/config-\$(uname -r) exists."
exit 1
}
echo "==> Auditing kernel config: $CONFIG_FILE"
echo ""
PASS=0
FAIL=0
WARN=0
check_config() {
local option="$1"
local required="$2" # "mandatory" or "recommended"
local description="$3"
local value
value=$(grep -E "^${option}=" "$CONFIG_FILE" 2>/dev/null || true)
if [[ -n "$value" ]]; then
local setting="${value#*=}"
echo -e " ${GREEN}${NC} ${option}=${setting}${description}"
((PASS++))
elif grep -qE "^# ${option} is not set" "$CONFIG_FILE" 2>/dev/null; then
if [[ "$required" == "mandatory" ]]; then
echo -e " ${RED}${NC} ${option} is NOT SET — ${description} [REQUIRED]"
((FAIL++))
else
echo -e " ${YELLOW}${NC} ${option} is NOT SET — ${description} [recommended]"
((WARN++))
fi
else
if [[ "$required" == "mandatory" ]]; then
echo -e " ${RED}?${NC} ${option} not found in config — ${description} [REQUIRED]"
((FAIL++))
else
echo -e " ${YELLOW}?${NC} ${option} not found in config — ${description} [recommended]"
((WARN++))
fi
fi
}
# --- cgroup v2 ---
echo "cgroup v2:"
check_config CONFIG_CGROUPS mandatory "Control groups support"
check_config CONFIG_CGROUP_CPUACCT mandatory "CPU accounting"
check_config CONFIG_CGROUP_DEVICE mandatory "Device controller"
check_config CONFIG_CGROUP_FREEZER mandatory "Freezer controller"
check_config CONFIG_CGROUP_SCHED mandatory "CPU scheduler controller"
check_config CONFIG_CGROUP_PIDS mandatory "PIDs controller"
check_config CONFIG_MEMCG mandatory "Memory controller"
check_config CONFIG_CGROUP_BPF recommended "BPF controller"
echo ""
# --- Namespaces ---
echo "Namespaces:"
check_config CONFIG_NAMESPACES mandatory "Namespace support"
check_config CONFIG_NET_NS mandatory "Network namespaces"
check_config CONFIG_PID_NS mandatory "PID namespaces"
check_config CONFIG_USER_NS mandatory "User namespaces"
check_config CONFIG_UTS_NS mandatory "UTS namespaces"
check_config CONFIG_IPC_NS mandatory "IPC namespaces"
echo ""
# --- Filesystem ---
echo "Filesystem:"
check_config CONFIG_OVERLAY_FS mandatory "OverlayFS (containerd)"
check_config CONFIG_SQUASHFS mandatory "SquashFS (Tiny Core root)"
check_config CONFIG_BLK_DEV_LOOP mandatory "Loop device (SquashFS mount)"
check_config CONFIG_EXT4_FS mandatory "ext4 (persistent partition)"
echo ""
# --- Networking ---
echo "Networking:"
check_config CONFIG_BRIDGE mandatory "Bridge (K8s pod networking)"
check_config CONFIG_NETFILTER mandatory "Netfilter framework"
check_config CONFIG_NF_NAT mandatory "NAT support"
check_config CONFIG_NF_CONNTRACK mandatory "Connection tracking"
check_config CONFIG_IP_NF_IPTABLES mandatory "iptables"
check_config CONFIG_IP_NF_NAT mandatory "iptables NAT"
check_config CONFIG_IP_NF_FILTER mandatory "iptables filter"
check_config CONFIG_VETH mandatory "Virtual ethernet pairs"
check_config CONFIG_VXLAN mandatory "VXLAN (overlay networking)"
check_config CONFIG_NET_SCH_HTB recommended "HTB qdisc (bandwidth limiting)"
echo ""
# --- Security ---
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"
echo ""
# --- Crypto ---
echo "Crypto:"
check_config CONFIG_CRYPTO_SHA256 recommended "SHA-256 (image verification)"
echo ""
# --- IPVS (optional, for kube-proxy IPVS mode) ---
echo "IPVS (optional, kube-proxy IPVS mode):"
check_config CONFIG_IP_VS recommended "IPVS core"
check_config CONFIG_IP_VS_RR recommended "IPVS round-robin"
check_config CONFIG_IP_VS_WRR recommended "IPVS weighted round-robin"
check_config CONFIG_IP_VS_SH recommended "IPVS source hashing"
echo ""
# --- Summary ---
echo "========================================"
echo -e " ${GREEN}Passed:${NC} $PASS"
echo -e " ${RED}Failed:${NC} $FAIL"
echo -e " ${YELLOW}Warnings:${NC} $WARN"
echo "========================================"
if [[ $FAIL -gt 0 ]]; then
echo ""
echo -e "${RED}FAIL: $FAIL mandatory kernel config(s) missing.${NC}"
echo "Options:"
echo " 1. Check if missing features are available as loadable modules (=m)"
echo " 2. Recompile the kernel with missing options enabled"
echo " 3. Use a different kernel (e.g., Alpine Linux kernel)"
exit 1
else
echo ""
echo -e "${GREEN}PASS: All mandatory kernel configs present.${NC}"
if [[ $WARN -gt 0 ]]; then
echo -e "${YELLOW}Note: $WARN recommended configs missing (non-blocking).${NC}"
fi
exit 0
fi

31
build/config/modules.list Normal file
View File

@@ -0,0 +1,31 @@
# Kernel modules loaded at boot by init
# One module per line. Lines starting with # are ignored.
# Modules are loaded in order listed.
# Networking — bridge and netfilter (required for K8s pod networking)
br_netfilter
bridge
veth
vxlan
# Netfilter / iptables (required for kube-proxy and service routing)
ip_tables
iptable_nat
iptable_filter
iptable_mangle
nf_nat
nf_conntrack
nf_conntrack_netlink
# Filesystem — overlay (required for containerd)
overlay
# Conntrack (required for K8s services)
nf_conntrack
# Optional — useful for CNI plugins and diagnostics
tun
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh

19
build/config/versions.env Normal file
View File

@@ -0,0 +1,19 @@
# KubeSolo OS Component Versions
# All external dependencies pinned here for reproducible builds
# Tiny Core Linux
TINYCORE_VERSION=17.0
TINYCORE_ARCH=x86_64
TINYCORE_MIRROR=http://www.tinycorelinux.net
TINYCORE_ISO=CorePure64-${TINYCORE_VERSION}.iso
TINYCORE_ISO_URL=${TINYCORE_MIRROR}/${TINYCORE_VERSION%%.*}.x/${TINYCORE_ARCH}/release/${TINYCORE_ISO}
# KubeSolo
KUBESOLO_INSTALL_URL=https://get.kubesolo.io
# Build tools (used inside builder container)
GRUB_VERSION=2.12
SYSLINUX_VERSION=6.03
# Output naming
OS_NAME=kubesolo-os

View File

@@ -0,0 +1,22 @@
# KubeSolo OS — Default KubeSolo Configuration
# These defaults are used when no cloud-init or persistent config is found.
# Overridden by: /etc/kubesolo/config.yaml (persistent) or cloud-init
# Data directory for K8s state (certs, etcd/sqlite, manifests)
data-dir: /var/lib/kubesolo
# Enable local-path provisioner for PersistentVolumeClaims
local-storage: true
# API server will listen on all interfaces
bind-address: 0.0.0.0
# Cluster CIDR ranges
cluster-cidr: 10.42.0.0/16
service-cidr: 10.43.0.0/16
# Disable components not needed for single-node
# (KubeSolo may handle this internally)
# disable:
# - traefik
# - servicelb

View File

@@ -0,0 +1,17 @@
# Kubernetes networking requirements
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
# inotify limits (containerd + kubelet watch requirements)
fs.inotify.max_user_instances = 1024
fs.inotify.max_user_watches = 524288
# Connection tracking (kube-proxy)
net.netfilter.nf_conntrack_max = 131072
# File descriptor limits
fs.file-max = 1048576
# Disable swap (K8s requirement — though we have no swap anyway)
vm.swappiness = 0

View File

@@ -0,0 +1,63 @@
#!/bin/sh
# health.sh — Health check functions for KubeSolo OS
# Used by init health monitoring and update agent rollback logic
# POSIX sh only.
KUBECONFIG_PATH="/var/lib/kubesolo/pki/admin/admin.kubeconfig"
# Check if containerd socket is responding
check_containerd() {
[ -S /run/containerd/containerd.sock ] || return 1
# If ctr is available, try listing containers
if command -v ctr >/dev/null 2>&1; then
ctr --connect-timeout 5s version >/dev/null 2>&1
else
return 0 # socket exists, assume ok
fi
}
# Check if the K8s API server is responding
check_apiserver() {
kubeconfig="${1:-$KUBECONFIG_PATH}"
if [ ! -f "$kubeconfig" ]; then
return 1
fi
if command -v kubectl >/dev/null 2>&1; then
kubectl --kubeconfig="$kubeconfig" get --raw /healthz >/dev/null 2>&1
elif command -v curl >/dev/null 2>&1; then
# Fallback: direct API call
server=$(sed -n 's/.*server: *//p' "$kubeconfig" 2>/dev/null | head -1)
[ -n "$server" ] && curl -sk "${server}/healthz" >/dev/null 2>&1
else
return 1
fi
}
# Check if the node has reached Ready status
check_node_ready() {
kubeconfig="${1:-$KUBECONFIG_PATH}"
[ -f "$kubeconfig" ] || return 1
command -v kubectl >/dev/null 2>&1 || return 1
kubectl --kubeconfig="$kubeconfig" get nodes 2>/dev/null | grep -q "Ready"
}
# Combined health check — returns 0 only if all components are healthy
check_health() {
check_containerd || return 1
check_apiserver || return 1
check_node_ready || return 1
return 0
}
# Wait for system to become healthy with timeout
wait_for_healthy() {
timeout="${1:-300}"
interval="${2:-5}"
elapsed=0
while [ "$elapsed" -lt "$timeout" ]; do
check_health && return 0
sleep "$interval"
elapsed=$((elapsed + interval))
done
return 1
}

View File

@@ -0,0 +1,80 @@
#!/bin/sh
# network.sh — Network configuration helpers for KubeSolo OS init
# Sourced by init stages. POSIX sh only.
# Configure a static IP address on an interface
# Usage: static_ip <iface> <ip/prefix> <gateway> [dns1] [dns2]
static_ip() {
iface="$1" addr="$2" gw="$3" dns1="${4:-}" dns2="${5:-}"
ip link set "$iface" up
ip addr add "$addr" dev "$iface"
ip route add default via "$gw" dev "$iface"
# Write resolv.conf
: > /etc/resolv.conf
[ -n "$dns1" ] && echo "nameserver $dns1" >> /etc/resolv.conf
[ -n "$dns2" ] && echo "nameserver $dns2" >> /etc/resolv.conf
}
# Save current network configuration for persistence across reboots
# Writes a shell script that can be sourced to restore networking
save_network_config() {
dest="${1:-/mnt/data/network/interfaces.sh}"
mkdir -p "$(dirname "$dest")"
iface=""
for d in /sys/class/net/*; do
name="$(basename "$d")"
case "$name" in lo|docker*|veth*|br*|cni*) continue ;; esac
iface="$name"
break
done
[ -z "$iface" ] && return 1
addr=$(ip -4 addr show "$iface" | sed -n 's/.*inet \([0-9./]*\).*/\1/p' | head -1)
gw=$(ip route show default 2>/dev/null | sed -n 's/default via \([0-9.]*\).*/\1/p' | head -1)
cat > "$dest" << SCRIPT
#!/bin/sh
# Auto-saved network config — generated by KubeSolo OS
ip link set $iface up
ip addr add $addr dev $iface
ip route add default via $gw dev $iface
SCRIPT
# Append DNS if resolv.conf has entries
if [ -f /etc/resolv.conf ]; then
echo ": > /etc/resolv.conf" >> "$dest"
sed -n 's/^nameserver \(.*\)/echo "nameserver \1" >> \/etc\/resolv.conf/p' \
/etc/resolv.conf >> "$dest"
fi
chmod +x "$dest"
}
# Get the primary network interface name
get_primary_iface() {
for d in /sys/class/net/*; do
name="$(basename "$d")"
case "$name" in lo|docker*|veth*|br*|cni*) continue ;; esac
echo "$name"
return 0
done
return 1
}
# Wait for link on an interface
wait_for_link() {
iface="$1"
timeout="${2:-15}"
i=0
while [ "$i" -lt "$timeout" ]; do
if ip link show "$iface" 2>/dev/null | grep -q 'state UP'; then
return 0
fi
sleep 1
i=$((i + 1))
done
return 1
}

View File

@@ -0,0 +1,110 @@
#!/bin/bash
# create-disk-image.sh — Create a raw disk image with boot + data partitions
# Phase 1: simple layout (boot + data). Phase 3 adds A/B system partitions.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
ROOTFS_DIR="${ROOTFS_DIR:-$PROJECT_ROOT/build/rootfs-work}"
OUTPUT_DIR="${OUTPUT_DIR:-$PROJECT_ROOT/output}"
VERSION="$(cat "$PROJECT_ROOT/VERSION")"
OS_NAME="kubesolo-os"
IMG_OUTPUT="$OUTPUT_DIR/${OS_NAME}-${VERSION}.img"
IMG_SIZE_MB="${IMG_SIZE_MB:-2048}" # 2 GB default
VMLINUZ="$ROOTFS_DIR/vmlinuz"
INITRAMFS="$ROOTFS_DIR/kubesolo-os.gz"
for f in "$VMLINUZ" "$INITRAMFS"; do
[ -f "$f" ] || { echo "ERROR: Missing $f — run 'make initramfs'"; exit 1; }
done
echo "==> Creating ${IMG_SIZE_MB}MB disk image..."
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: 256MB boot (ext4) + rest data (ext4)
# Using sfdisk for scriptability
sfdisk "$IMG_OUTPUT" << EOF
label: dos
unit: sectors
# Boot partition: 256 MB, bootable
start=2048, size=524288, type=83, bootable
# Data partition: remaining space
start=526336, type=83
EOF
# Set up loop device
LOOP=$(losetup --show -fP "$IMG_OUTPUT")
echo "==> Loop device: $LOOP"
cleanup() {
umount "${LOOP}p1" 2>/dev/null || true
umount "${LOOP}p2" 2>/dev/null || true
losetup -d "$LOOP" 2>/dev/null || true
rm -rf "$MNT_BOOT" "$MNT_DATA" 2>/dev/null || true
}
trap cleanup EXIT
# Format partitions
mkfs.ext4 -q -L KSOLOBOOT "${LOOP}p1"
mkfs.ext4 -q -L KSOLODATA "${LOOP}p2"
# Mount and populate boot partition
MNT_BOOT=$(mktemp -d)
MNT_DATA=$(mktemp -d)
mount "${LOOP}p1" "$MNT_BOOT"
mount "${LOOP}p2" "$MNT_DATA"
# Install syslinux + kernel + initramfs to boot partition
mkdir -p "$MNT_BOOT/boot/syslinux"
cp "$VMLINUZ" "$MNT_BOOT/boot/vmlinuz"
cp "$INITRAMFS" "$MNT_BOOT/boot/kubesolo-os.gz"
# Syslinux config for disk boot (extlinux)
cat > "$MNT_BOOT/boot/syslinux/syslinux.cfg" << 'EOF'
DEFAULT kubesolo
TIMEOUT 30
PROMPT 0
LABEL kubesolo
KERNEL /boot/vmlinuz
INITRD /boot/kubesolo-os.gz
APPEND quiet kubesolo.data=LABEL=KSOLODATA
LABEL kubesolo-debug
KERNEL /boot/vmlinuz
INITRD /boot/kubesolo-os.gz
APPEND kubesolo.data=LABEL=KSOLODATA kubesolo.debug console=ttyS0,115200n8
LABEL kubesolo-shell
KERNEL /boot/vmlinuz
INITRD /boot/kubesolo-os.gz
APPEND kubesolo.shell console=ttyS0,115200n8
EOF
# Install extlinux bootloader
if command -v extlinux >/dev/null 2>&1; then
extlinux --install "$MNT_BOOT/boot/syslinux" 2>/dev/null || {
echo "WARN: extlinux install failed — image may not be directly bootable"
echo " Use with QEMU -kernel/-initrd flags instead"
}
fi
# Prepare data partition structure
for dir in kubesolo containerd etc-kubesolo log usr-local network; do
mkdir -p "$MNT_DATA/$dir"
done
sync
echo ""
echo "==> Disk image created: $IMG_OUTPUT"
echo " Size: $(du -h "$IMG_OUTPUT" | cut -f1)"
echo " Boot partition (KSOLOBOOT): kernel + initramfs"
echo " Data partition (KSOLODATA): persistent K8s state"

140
build/scripts/create-iso.sh Executable file
View File

@@ -0,0 +1,140 @@
#!/bin/bash
# create-iso.sh — Create a bootable ISO from kernel + initramfs
# Uses isolinux (syslinux) for Phase 1 simplicity (GRUB in Phase 3)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
ROOTFS_DIR="${ROOTFS_DIR:-$PROJECT_ROOT/build/rootfs-work}"
OUTPUT_DIR="${OUTPUT_DIR:-$PROJECT_ROOT/output}"
VERSION="$(cat "$PROJECT_ROOT/VERSION")"
OS_NAME="kubesolo-os"
ISO_STAGING="$ROOTFS_DIR/iso-staging"
ISO_OUTPUT="$OUTPUT_DIR/${OS_NAME}-${VERSION}.iso"
VMLINUZ="$ROOTFS_DIR/vmlinuz"
INITRAMFS="$ROOTFS_DIR/kubesolo-os.gz"
# Validate inputs
for f in "$VMLINUZ" "$INITRAMFS"; do
if [ ! -f "$f" ]; then
echo "ERROR: Missing required file: $f"
echo "Run 'make initramfs' first."
exit 1
fi
done
# Check for required tools
for cmd in mkisofs xorriso genisoimage; do
if command -v "$cmd" >/dev/null 2>&1; then
MKISO_CMD="$cmd"
break
fi
done
if [ -z "${MKISO_CMD:-}" ]; then
echo "ERROR: Need mkisofs, genisoimage, or xorriso to create ISO"
exit 1
fi
# --- Stage ISO contents ---
rm -rf "$ISO_STAGING"
mkdir -p "$ISO_STAGING/boot/isolinux"
cp "$VMLINUZ" "$ISO_STAGING/boot/vmlinuz"
cp "$INITRAMFS" "$ISO_STAGING/boot/kubesolo-os.gz"
# Find isolinux.bin
ISOLINUX_BIN=""
for path in /usr/lib/ISOLINUX/isolinux.bin /usr/lib/syslinux/isolinux.bin \
/usr/share/syslinux/isolinux.bin /usr/lib/syslinux/bios/isolinux.bin; do
[ -f "$path" ] && ISOLINUX_BIN="$path" && break
done
if [ -z "$ISOLINUX_BIN" ]; then
echo "ERROR: Cannot find isolinux.bin. Install syslinux/isolinux package."
exit 1
fi
cp "$ISOLINUX_BIN" "$ISO_STAGING/boot/isolinux/"
# Copy ldlinux.c32 if it exists (needed by syslinux 6+)
LDLINUX_DIR="$(dirname "$ISOLINUX_BIN")"
for mod in ldlinux.c32 libcom32.c32 libutil.c32 mboot.c32; do
[ -f "$LDLINUX_DIR/$mod" ] && cp "$LDLINUX_DIR/$mod" "$ISO_STAGING/boot/isolinux/"
done
# Isolinux config
cat > "$ISO_STAGING/boot/isolinux/isolinux.cfg" << 'EOF'
DEFAULT kubesolo
TIMEOUT 30
PROMPT 0
LABEL kubesolo
MENU LABEL KubeSolo OS
KERNEL /boot/vmlinuz
INITRD /boot/kubesolo-os.gz
APPEND quiet kubesolo.data=LABEL=KSOLODATA
LABEL kubesolo-debug
MENU LABEL KubeSolo OS (debug)
KERNEL /boot/vmlinuz
INITRD /boot/kubesolo-os.gz
APPEND kubesolo.data=LABEL=KSOLODATA kubesolo.debug console=ttyS0,115200n8
LABEL kubesolo-shell
MENU LABEL KubeSolo OS (emergency shell)
KERNEL /boot/vmlinuz
INITRD /boot/kubesolo-os.gz
APPEND kubesolo.shell console=ttyS0,115200n8
LABEL kubesolo-nopersist
MENU LABEL KubeSolo OS (RAM only, no persistence)
KERNEL /boot/vmlinuz
INITRD /boot/kubesolo-os.gz
APPEND kubesolo.nopersist
EOF
# --- Create ISO ---
mkdir -p "$OUTPUT_DIR"
case "$MKISO_CMD" in
xorriso)
xorriso -as mkisofs \
-o "$ISO_OUTPUT" \
-isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin 2>/dev/null || true \
-c boot/isolinux/boot.cat \
-b boot/isolinux/isolinux.bin \
-no-emul-boot \
-boot-load-size 4 \
-boot-info-table \
"$ISO_STAGING"
;;
*)
"$MKISO_CMD" \
-o "$ISO_OUTPUT" \
-b boot/isolinux/isolinux.bin \
-c boot/isolinux/boot.cat \
-no-emul-boot \
-boot-load-size 4 \
-boot-info-table \
-J -R -V "KUBESOLOOS" \
"$ISO_STAGING"
;;
esac
# Make ISO hybrid-bootable (USB stick)
if command -v isohybrid >/dev/null 2>&1; then
isohybrid "$ISO_OUTPUT" 2>/dev/null || true
fi
# Clean staging
rm -rf "$ISO_STAGING"
echo ""
echo "==> ISO created: $ISO_OUTPUT"
echo " Size: $(du -h "$ISO_OUTPUT" | cut -f1)"
echo ""
echo " Boot in QEMU: make dev-vm"
echo " Write to USB: dd if=$ISO_OUTPUT of=/dev/sdX bs=4M status=progress"

83
build/scripts/extract-core.sh Executable file
View File

@@ -0,0 +1,83 @@
#!/bin/bash
# extract-core.sh — Extract Tiny Core Linux rootfs from ISO
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}"
ROOTFS_DIR="${ROOTFS_DIR:-$PROJECT_ROOT/build/rootfs-work}"
# shellcheck source=../config/versions.env
. "$SCRIPT_DIR/../config/versions.env"
TC_ISO="$CACHE_DIR/$TINYCORE_ISO"
ISO_MNT="$ROOTFS_DIR/iso-mount"
if [ ! -f "$TC_ISO" ]; then
echo "ERROR: Tiny Core ISO not found: $TC_ISO"
echo "Run 'make fetch' first."
exit 1
fi
# Clean previous rootfs
rm -rf "$ROOTFS_DIR"
mkdir -p "$ROOTFS_DIR" "$ISO_MNT"
# --- Mount ISO and extract kernel + initramfs ---
echo "==> Mounting ISO: $TC_ISO"
mount -o loop,ro "$TC_ISO" "$ISO_MNT" 2>/dev/null || {
# Fallback for non-root: use 7z or bsdtar
echo " mount failed (need root?), trying bsdtar..."
mkdir -p "$ISO_MNT"
bsdtar xf "$TC_ISO" -C "$ISO_MNT" 2>/dev/null || {
echo " bsdtar failed, trying 7z..."
7z x -o"$ISO_MNT" "$TC_ISO" >/dev/null 2>&1
}
}
# Find vmlinuz and core.gz (path varies by Tiny Core version/arch)
VMLINUZ=""
COREGZ=""
for f in "$ISO_MNT"/boot/vmlinuz64 "$ISO_MNT"/boot/vmlinuz; do
[ -f "$f" ] && VMLINUZ="$f" && break
done
for f in "$ISO_MNT"/boot/corepure64.gz "$ISO_MNT"/boot/core.gz; do
[ -f "$f" ] && COREGZ="$f" && break
done
if [ -z "$VMLINUZ" ] || [ -z "$COREGZ" ]; then
echo "ERROR: Could not find vmlinuz/core.gz in ISO"
echo "ISO contents:"
find "$ISO_MNT" -type f
umount "$ISO_MNT" 2>/dev/null || true
exit 1
fi
echo "==> Found kernel: $VMLINUZ"
echo "==> Found initramfs: $COREGZ"
# Copy kernel
cp "$VMLINUZ" "$ROOTFS_DIR/vmlinuz"
# --- Extract initramfs (core.gz → rootfs) ---
echo "==> Extracting initramfs..."
mkdir -p "$ROOTFS_DIR/rootfs"
cd "$ROOTFS_DIR/rootfs"
zcat "$COREGZ" | cpio -idm 2>/dev/null
# Unmount ISO
cd "$PROJECT_ROOT"
umount "$ISO_MNT" 2>/dev/null || true
rm -rf "$ISO_MNT"
echo "==> Rootfs extracted: $ROOTFS_DIR/rootfs"
echo " Size: $(du -sh "$ROOTFS_DIR/rootfs" | cut -f1)"
echo " Kernel: $ROOTFS_DIR/vmlinuz ($(du -h "$ROOTFS_DIR/vmlinuz" | cut -f1))"
# --- Audit kernel config if available ---
if [ -f "$ROOTFS_DIR/rootfs/proc/config.gz" ]; then
echo "==> Kernel config found in rootfs, auditing..."
"$SCRIPT_DIR/../config/kernel-audit.sh" <(zcat "$ROOTFS_DIR/rootfs/proc/config.gz") || true
fi
echo "==> Extract complete."

View File

@@ -0,0 +1,72 @@
#!/bin/bash
# fetch-components.sh — Download Tiny Core ISO and KubeSolo binary
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}"
# Load versions
# shellcheck source=../config/versions.env
. "$SCRIPT_DIR/../config/versions.env"
mkdir -p "$CACHE_DIR"
# --- Tiny Core Linux ISO ---
TC_ISO="$CACHE_DIR/$TINYCORE_ISO"
TC_URL="${TINYCORE_MIRROR}/${TINYCORE_VERSION%%.*}.x/${TINYCORE_ARCH}/release/${TINYCORE_ISO}"
if [ -f "$TC_ISO" ]; then
echo "==> Tiny Core ISO already cached: $TC_ISO"
else
echo "==> Downloading Tiny Core Linux ${TINYCORE_VERSION} (${TINYCORE_ARCH})..."
echo " URL: $TC_URL"
wget -q --show-progress -O "$TC_ISO" "$TC_URL" || {
# Fallback: try alternate mirror structure
TC_URL_ALT="${TINYCORE_MIRROR}/${TINYCORE_VERSION%%.*}.x/${TINYCORE_ARCH}/release/CorePure64-current.iso"
echo " Primary URL failed, trying: $TC_URL_ALT"
wget -q --show-progress -O "$TC_ISO" "$TC_URL_ALT"
}
echo "==> Downloaded: $TC_ISO ($(du -h "$TC_ISO" | cut -f1))"
fi
# --- KubeSolo ---
KUBESOLO_INSTALLER="$CACHE_DIR/install-kubesolo.sh"
KUBESOLO_BIN="$CACHE_DIR/kubesolo"
if [ -f "$KUBESOLO_BIN" ]; then
echo "==> KubeSolo binary already cached: $KUBESOLO_BIN"
else
echo "==> Downloading KubeSolo installer..."
curl -sfL "$KUBESOLO_INSTALL_URL" -o "$KUBESOLO_INSTALLER"
echo "==> Extracting KubeSolo binary..."
echo " NOTE: The installer normally runs 'install'. We extract the binary URL instead."
echo " For Phase 1 PoC, install KubeSolo on a host and copy the binary."
echo ""
echo " Manual step required:"
echo " 1. On a Linux x86_64 host: curl -sfL https://get.kubesolo.io | sudo sh -"
echo " 2. Copy /usr/local/bin/kubesolo to: $KUBESOLO_BIN"
echo " 3. Re-run: make rootfs"
echo ""
# Try to extract download URL from installer script
BINARY_URL=$(grep -oP 'https://[^ ]+kubesolo[^ ]+' "$KUBESOLO_INSTALLER" 2>/dev/null | head -1 || true)
if [ -n "$BINARY_URL" ]; then
echo " Attempting direct download from: $BINARY_URL"
curl -sfL "$BINARY_URL" -o "$KUBESOLO_BIN" && chmod +x "$KUBESOLO_BIN" || {
echo " Direct download failed. Use manual step above."
}
fi
if [ -f "$KUBESOLO_BIN" ]; then
echo "==> KubeSolo binary: $KUBESOLO_BIN ($(du -h "$KUBESOLO_BIN" | cut -f1))"
fi
fi
# --- Summary ---
echo ""
echo "==> Component cache:"
ls -lh "$CACHE_DIR"/ 2>/dev/null || true
echo ""
echo "==> Fetch complete."

124
build/scripts/inject-kubesolo.sh Executable file
View File

@@ -0,0 +1,124 @@
#!/bin/bash
# inject-kubesolo.sh — Add KubeSolo binary, init system, and configs to rootfs
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}"
ROOTFS_DIR="${ROOTFS_DIR:-$PROJECT_ROOT/build/rootfs-work}"
ROOTFS="$ROOTFS_DIR/rootfs"
VERSION="$(cat "$PROJECT_ROOT/VERSION")"
if [ ! -d "$ROOTFS" ]; then
echo "ERROR: Rootfs not found: $ROOTFS"
echo "Run extract-core.sh first."
exit 1
fi
KUBESOLO_BIN="$CACHE_DIR/kubesolo"
if [ ! -f "$KUBESOLO_BIN" ]; then
echo "ERROR: KubeSolo binary not found: $KUBESOLO_BIN"
echo "See fetch-components.sh output for instructions."
exit 1
fi
echo "==> Injecting KubeSolo into rootfs..."
# --- 1. KubeSolo binary ---
mkdir -p "$ROOTFS/usr/local/bin"
cp "$KUBESOLO_BIN" "$ROOTFS/usr/local/bin/kubesolo"
chmod +x "$ROOTFS/usr/local/bin/kubesolo"
echo " Installed KubeSolo binary ($(du -h "$KUBESOLO_BIN" | cut -f1))"
# --- 2. Custom init system ---
echo " Installing init system..."
# Main init
cp "$PROJECT_ROOT/init/init.sh" "$ROOTFS/sbin/init"
chmod +x "$ROOTFS/sbin/init"
# Init stages
mkdir -p "$ROOTFS/usr/lib/kubesolo-os/init.d"
for stage in "$PROJECT_ROOT"/init/lib/*.sh; do
[ -f "$stage" ] || continue
cp "$stage" "$ROOTFS/usr/lib/kubesolo-os/init.d/"
chmod +x "$ROOTFS/usr/lib/kubesolo-os/init.d/$(basename "$stage")"
done
echo " Installed $(ls "$ROOTFS/usr/lib/kubesolo-os/init.d/" | wc -l) init stages"
# Shared functions
if [ -f "$PROJECT_ROOT/init/lib/functions.sh" ]; then
cp "$PROJECT_ROOT/init/lib/functions.sh" "$ROOTFS/usr/lib/kubesolo-os/functions.sh"
fi
# Emergency shell
if [ -f "$PROJECT_ROOT/init/emergency-shell.sh" ]; then
cp "$PROJECT_ROOT/init/emergency-shell.sh" "$ROOTFS/usr/lib/kubesolo-os/emergency-shell.sh"
chmod +x "$ROOTFS/usr/lib/kubesolo-os/emergency-shell.sh"
fi
# Shared library scripts (network, health)
for lib in network.sh health.sh; do
src="$PROJECT_ROOT/build/rootfs/usr/lib/kubesolo-os/$lib"
[ -f "$src" ] && cp "$src" "$ROOTFS/usr/lib/kubesolo-os/$lib"
done
# --- 3. Kernel modules list ---
cp "$PROJECT_ROOT/build/config/modules.list" "$ROOTFS/usr/lib/kubesolo-os/modules.list"
# --- 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"
# --- 5. OS metadata ---
echo "$VERSION" > "$ROOTFS/etc/kubesolo-os-version"
cat > "$ROOTFS/etc/os-release" << EOF
NAME="KubeSolo OS"
VERSION="$VERSION"
ID=kubesolo-os
VERSION_ID=$VERSION
PRETTY_NAME="KubeSolo OS $VERSION"
HOME_URL="https://github.com/portainer/kubesolo"
BUG_REPORT_URL="https://github.com/portainer/kubesolo/issues"
EOF
# --- 6. Default KubeSolo config ---
mkdir -p "$ROOTFS/etc/kubesolo"
if [ -f "$PROJECT_ROOT/build/rootfs/etc/kubesolo/defaults.yaml" ]; then
cp "$PROJECT_ROOT/build/rootfs/etc/kubesolo/defaults.yaml" "$ROOTFS/etc/kubesolo/defaults.yaml"
fi
# --- 7. Essential directories ---
mkdir -p "$ROOTFS/var/lib/kubesolo"
mkdir -p "$ROOTFS/var/lib/containerd"
mkdir -p "$ROOTFS/etc/kubesolo"
mkdir -p "$ROOTFS/etc/cni/net.d"
mkdir -p "$ROOTFS/opt/cni/bin"
mkdir -p "$ROOTFS/var/log"
mkdir -p "$ROOTFS/usr/local"
mkdir -p "$ROOTFS/mnt/data"
mkdir -p "$ROOTFS/run/containerd"
# --- 8. Ensure /etc/hosts and /etc/resolv.conf exist ---
if [ ! -f "$ROOTFS/etc/hosts" ]; then
cat > "$ROOTFS/etc/hosts" << EOF
127.0.0.1 localhost
::1 localhost
EOF
fi
if [ ! -f "$ROOTFS/etc/resolv.conf" ]; then
cat > "$ROOTFS/etc/resolv.conf" << EOF
nameserver 8.8.8.8
nameserver 1.1.1.1
EOF
fi
# --- Summary ---
echo ""
echo "==> Injection complete. Rootfs contents:"
echo " Total size: $(du -sh "$ROOTFS" | cut -f1)"
echo " KubeSolo: $(du -h "$ROOTFS/usr/local/bin/kubesolo" | cut -f1)"
echo " Init stages: $(ls "$ROOTFS/usr/lib/kubesolo-os/init.d/" | wc -l)"
echo ""

23
build/scripts/pack-initramfs.sh Executable file
View File

@@ -0,0 +1,23 @@
#!/bin/bash
# pack-initramfs.sh — Repack modified rootfs into kubesolo-os.gz
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
ROOTFS_DIR="${ROOTFS_DIR:-$PROJECT_ROOT/build/rootfs-work}"
ROOTFS="$ROOTFS_DIR/rootfs"
OUTPUT="$ROOTFS_DIR/kubesolo-os.gz"
if [ ! -d "$ROOTFS" ]; then
echo "ERROR: Rootfs not found: $ROOTFS"
exit 1
fi
echo "==> Packing initramfs..."
cd "$ROOTFS"
find . | cpio -o -H newc 2>/dev/null | gzip -9 > "$OUTPUT"
echo "==> Built: $OUTPUT"
echo " Size: $(du -h "$OUTPUT" | cut -f1)"
echo " (Original Tiny Core core.gz is ~11 MB for reference)"