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:
34
build/Dockerfile.builder
Normal file
34
build/Dockerfile.builder
Normal 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
169
build/config/kernel-audit.sh
Executable 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
31
build/config/modules.list
Normal 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
19
build/config/versions.env
Normal 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
|
||||
22
build/rootfs/etc/kubesolo/defaults.yaml
Normal file
22
build/rootfs/etc/kubesolo/defaults.yaml
Normal 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
|
||||
17
build/rootfs/etc/sysctl.d/k8s.conf
Normal file
17
build/rootfs/etc/sysctl.d/k8s.conf
Normal 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
|
||||
63
build/rootfs/usr/lib/kubesolo-os/health.sh
Executable file
63
build/rootfs/usr/lib/kubesolo-os/health.sh
Executable 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
|
||||
}
|
||||
80
build/rootfs/usr/lib/kubesolo-os/network.sh
Executable file
80
build/rootfs/usr/lib/kubesolo-os/network.sh
Executable 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
|
||||
}
|
||||
110
build/scripts/create-disk-image.sh
Executable file
110
build/scripts/create-disk-image.sh
Executable 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
140
build/scripts/create-iso.sh
Executable 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
83
build/scripts/extract-core.sh
Executable 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."
|
||||
72
build/scripts/fetch-components.sh
Executable file
72
build/scripts/fetch-components.sh
Executable 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
124
build/scripts/inject-kubesolo.sh
Executable 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
23
build/scripts/pack-initramfs.sh
Executable 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)"
|
||||
Reference in New Issue
Block a user