Closes the full arc: banger kernel pull + image pull + vm create + vm ssh now works end-to-end against docker.io/library/debian:bookworm with zero manual image building. Generic kernel: - New scripts/make-generic-kernel.sh builds vmlinux from upstream kernel.org sources using Firecracker's official minimal config (configs/firecracker-x86_64-6.1.config). All critical drivers (virtio_blk, virtio_net, ext4, vsock) compiled in — no modules, no initramfs needed. - Published as generic-6.12 in the catalog (kernels.thaloco.com). - catalog.json updated with the new entry. Direct-boot init= override (vm_lifecycle.go): - For images without an initrd (direct-boot / OCI-pulled), banger now passes init=/usr/local/libexec/banger-first-boot on the kernel cmdline. The script runs as PID 1, mounts /proc /sys /dev /run, checks for systemd — if present execs it immediately; if not (container images), installs systemd-sysv + openssh-server via the guest's package manager, then execs systemd. - Also passes kernel-level ip= parameter via BuildBootArgsWithKernelIP so the kernel configures the network interface before init runs (container images don't ship iproute2, so the userspace bootstrap script can't call ip(8)). - Masks dev-ttyS0.device and dev-vdb.device systemd units that otherwise wait 90s for udev events that never fire in Firecracker guests started from container rootfses. first-boot.sh rewritten as universal init wrapper: - Works as PID 1 (mounts essential filesystems) OR as a systemd oneshot (existing behavior). - Installs both systemd-sysv AND openssh-server (container images have neither). - Dispatch updated: debian, alpine, fedora, arch, opensuse families + ID_LIKE fallback. All tests updated. Opencode capability skip for direct-boot images: - The opencode readiness check (WaitReady on vsock port 4096) now returns nil for images without an initrd, since pulled container images don't ship the opencode service. Without this, the VM would be marked as error for lacking an opinionated add-on. Docs: README and kernel-catalog.md updated to recommend generic-6.12 as the default kernel for OCI-pulled images. AGENTS.md notes the new build script. Verified live: - banger kernel pull generic-6.12 - banger image pull docker.io/library/debian:bookworm --kernel-ref generic-6.12 - banger vm create --image debian-bookworm --name testbox --nat - banger vm ssh testbox -- "id; uname -r; systemctl is-active banger-vsock-agent" → uid=0(root), kernel 6.12.8, Debian bookworm, vsock-agent active, sshd running, SSH working. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
142 lines
4.5 KiB
Bash
142 lines
4.5 KiB
Bash
#!/bin/sh
|
|
# banger-first-boot — universal init wrapper for banger VMs.
|
|
#
|
|
# When passed as init= on the kernel cmdline (direct-boot images without
|
|
# an initramfs), this script runs as PID 1. It:
|
|
# 1. Mounts the essential virtual filesystems (/proc, /sys, /dev, /run)
|
|
# 2. If systemd (or any init) is already installed, execs it immediately
|
|
# 3. Otherwise: brings up the network, installs systemd + openssh-server
|
|
# via the guest's native package manager, then execs systemd
|
|
#
|
|
# On subsequent boots (after systemd is installed), step 2 fires in <10ms.
|
|
#
|
|
# Test hooks:
|
|
# RUN_PLAN=1 echo the install command instead of executing it
|
|
# OS_RELEASE_FILE=<path> override /etc/os-release for distro detection
|
|
# BANGER_FIRST_BOOT_MARKER=<path> override the marker file path
|
|
|
|
set -eu
|
|
|
|
log() { printf '[banger-first-boot] %s\n' "$*" >&2; }
|
|
|
|
# --- Step 1: essential mounts (only when running as PID 1) ---
|
|
if [ "$$" = "1" ]; then
|
|
mount -t proc proc /proc 2>/dev/null || true
|
|
mount -t sysfs sysfs /sys 2>/dev/null || true
|
|
mount -t devtmpfs devtmpfs /dev 2>/dev/null || true
|
|
mount -t tmpfs tmpfs /run 2>/dev/null || true
|
|
mount -t tmpfs tmpfs /tmp 2>/dev/null || true
|
|
fi
|
|
|
|
# --- Step 2: if a real init exists, hand off immediately ---
|
|
# (RUN_PLAN mode skips this so the dispatch logic can be tested on hosts
|
|
# that have systemd installed.)
|
|
if [ "${RUN_PLAN:-0}" != "1" ]; then
|
|
for candidate_init in /usr/lib/systemd/systemd /lib/systemd/systemd /sbin/init; do
|
|
if [ -x "$candidate_init" ]; then
|
|
MARKER="${BANGER_FIRST_BOOT_MARKER:-/var/lib/banger/first-boot-pending}"
|
|
if [ -f "$MARKER" ]; then
|
|
rm -f "$MARKER"
|
|
fi
|
|
log "found init at $candidate_init; handing off"
|
|
exec "$candidate_init" "$@"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# --- Step 3: no init found — we're on a container image, provision it ---
|
|
log "no init system found; installing systemd + openssh-server"
|
|
|
|
# Bring up network so apt-get/apk can reach package repos.
|
|
# banger-network-bootstrap reads IP from /proc/cmdline (kernel ip= arg)
|
|
# or /etc/banger-network.conf (written by vm_disk.patchRootOverlay).
|
|
if [ -x /usr/local/libexec/banger-network-bootstrap ]; then
|
|
log "bringing up network"
|
|
/usr/local/libexec/banger-network-bootstrap || log "network bootstrap failed (continuing anyway)"
|
|
fi
|
|
|
|
# Detect distro
|
|
DIST=""
|
|
FAMILY=""
|
|
OS_RELEASE_FILE="${OS_RELEASE_FILE:-/etc/os-release}"
|
|
if [ -r "$OS_RELEASE_FILE" ]; then
|
|
# shellcheck source=/dev/null
|
|
. "$OS_RELEASE_FILE"
|
|
DIST="${ID:-}"
|
|
FAMILY="${ID_LIKE:-}"
|
|
fi
|
|
log "detected distro: ID=$DIST ID_LIKE=$FAMILY"
|
|
|
|
# Dispatch install command
|
|
CMD=""
|
|
case "$DIST" in
|
|
debian|ubuntu|kali|raspbian|linuxmint|pop)
|
|
CMD="apt-get update && apt-get install -y systemd-sysv openssh-server"
|
|
;;
|
|
alpine)
|
|
CMD="apk add --no-cache openrc openssh systemd"
|
|
;;
|
|
fedora|rhel|centos|rocky|almalinux)
|
|
CMD="dnf install -y systemd openssh-server"
|
|
;;
|
|
arch|archlinux|manjaro)
|
|
CMD="pacman -Sy --noconfirm openssh"
|
|
;;
|
|
opensuse*|suse)
|
|
CMD="zypper --non-interactive install -y systemd openssh"
|
|
;;
|
|
*)
|
|
case " $FAMILY " in
|
|
*" debian "*)
|
|
CMD="apt-get update && apt-get install -y systemd-sysv openssh-server"
|
|
;;
|
|
*" rhel "* | *" fedora "*)
|
|
CMD="dnf install -y systemd openssh-server"
|
|
;;
|
|
*" arch "*)
|
|
CMD="pacman -Sy --noconfirm openssh"
|
|
;;
|
|
*" suse "*)
|
|
CMD="zypper --non-interactive install -y systemd openssh"
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|
|
|
|
if [ -z "$CMD" ]; then
|
|
log "FATAL: no known install command for distro '$DIST' (ID_LIKE='$FAMILY')"
|
|
log "drop to emergency shell"
|
|
exec /bin/sh
|
|
fi
|
|
|
|
if [ "${RUN_PLAN:-0}" = "1" ]; then
|
|
printf '%s\n' "$CMD"
|
|
exit 0
|
|
fi
|
|
|
|
log "running: $CMD"
|
|
eval "$CMD" || {
|
|
log "package install failed; dropping to shell"
|
|
exec /bin/sh
|
|
}
|
|
|
|
# Remove first-boot marker
|
|
MARKER="${BANGER_FIRST_BOOT_MARKER:-/var/lib/banger/first-boot-pending}"
|
|
rm -f "$MARKER"
|
|
|
|
# systemd should now be installed — find and exec it
|
|
for candidate_init in /usr/lib/systemd/systemd /lib/systemd/systemd /sbin/init; do
|
|
if [ -x "$candidate_init" ]; then
|
|
log "provisioning complete; starting $candidate_init"
|
|
# Unmount our temp mounts — systemd will re-mount them properly
|
|
umount /tmp 2>/dev/null || true
|
|
umount /run 2>/dev/null || true
|
|
umount /dev 2>/dev/null || true
|
|
umount /sys 2>/dev/null || true
|
|
umount /proc 2>/dev/null || true
|
|
exec "$candidate_init" "$@"
|
|
fi
|
|
done
|
|
|
|
log "FATAL: init not found after install; dropping to shell"
|
|
exec /bin/sh
|