Implement a lightweight cloud-init system for first-boot configuration: - Go parser for YAML config (hostname, network, KubeSolo settings) - Static/DHCP network modes with DNS override - KubeSolo extra flags and API server SAN configuration - Portainer Edge Agent and air-gapped deployment support - New init stage 45-cloud-init.sh runs before network/hostname stages - Stages 50/60 skip gracefully when cloud-init has already applied - Build script compiles static Linux/amd64 binary (~2.7 MB) - 17 unit tests covering parsing, validation, and example files - Full documentation at docs/cloud-init.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
135 lines
4.2 KiB
Bash
Executable File
135 lines
4.2 KiB
Bash
Executable File
#!/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
|
|
|
|
# Cloud-init binary (Go, built separately)
|
|
CLOUDINIT_BIN="$CACHE_DIR/kubesolo-cloudinit"
|
|
if [ -f "$CLOUDINIT_BIN" ]; then
|
|
cp "$CLOUDINIT_BIN" "$ROOTFS/usr/lib/kubesolo-os/kubesolo-cloudinit"
|
|
chmod +x "$ROOTFS/usr/lib/kubesolo-os/kubesolo-cloudinit"
|
|
echo " Installed cloud-init binary ($(du -h "$CLOUDINIT_BIN" | cut -f1))"
|
|
else
|
|
echo " WARN: Cloud-init binary not found (run 'make build-cloudinit' to build)"
|
|
fi
|
|
|
|
# --- 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 ""
|