Files
kata-containers/tools/osbuilder/rootfs-builder/devkit/devkit-init.sh
Fabiano Fidêncio 5b1ac36626 packaging: add generic devkit debug guest extension image
Add a self-contained "devkit" guest extension: a minimal Ubuntu (glibc +
busybox + apt) rootfs with common debug tools prebaked (strace, ltrace,
iproute2, procps, lsof, tcpdump, pciutils, util-linux, ...), built as a
measured erofs+dm-verity image mounted at /run/kata-extensions/devkit.

The production guest rootfs is minimal (and, for some bases, shell-less), so
the agent debug console has no rich interactive shell. Rather than rebuilding
the whole rootfs with debug tooling, this optional extension can be
cold-plugged alongside any base image. At runtime the guest helper scripts
overlay a writable tmpfs on the read-only extension and chroot in, so apt and
every tool run natively against a normal root filesystem; `apt install <pkg>`
inside the debug shell pulls anything else into the overlay on demand.

busybox-static (/usr/bin/busybox.static) bootstraps the overlay/chroot from the
shell-less base (the guest's dynamic loader is not present there yet); it is
installed at a dedicated path so it never clobbers the extension's own busybox
or /bin/sh, which are needed unclobbered inside the chroot.

The rootfs is built through osbuilder (rootfs.sh, with a ROOTFS_ONLY mode that
skips the agent/systemd setup) using mmdebstrap, the same tool the base guest
rootfs uses, so the devkit needs no bespoke chroot or docker-export logic. The
resulting tree is handed to image_builder.sh (mirroring the CoCo extension): it
ships no kata-agent, kernel or driver userspace.

No runtime wiring is added here; that follows in later commits. Nothing
consumes the image yet.

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
Assisted-by: Cursor <cursoragent@cursor.com>
2026-07-23 19:05:15 +02:00

117 lines
4.2 KiB
Bash

#!/run/kata-extensions/devkit/bin/busybox.static sh
# shellcheck shell=dash
#
# Copyright (c) Kata Containers Community
#
# SPDX-License-Identifier: Apache-2.0
#
# Shared devkit guest runtime library, sourced by devkit-enter.
#
# The devkit extension (read-only at ${DEVKIT}) is a full minimal Ubuntu rootfs.
# We overlay a writable, exec-enabled tmpfs on it and chroot in, so apt and the
# prebaked tools run natively against a normal root filesystem.
#
# BB is a statically-linked busybox (busybox-static, /bin/busybox.static), the
# ONLY binary we can exec on the shell-less guest base before the glibc dynamic
# loader is reachable there. It is installed at a dedicated path so it never
# clobbers the rootfs's own /bin/busybox or GNU coreutils.
DEVKIT_INIT_VERSION=1
DEVKIT="${DEVKIT:-/run/kata-extensions/devkit}"
WRITABLE="${WRITABLE:-/run/kata-devkit-writable}"
BB="${BB:-${DEVKIT}/bin/busybox.static}"
UPPER="${WRITABLE}/upper"
WORK="${WRITABLE}/work"
MERGED="${WRITABLE}/merged"
devkit_is_mounted() {
"${BB}" grep -q "[[:space:]]${1}[[:space:]]" /proc/mounts 2>/dev/null
}
# Mount an exec-enabled tmpfs at ${WRITABLE}: /run is typically noexec, so the
# overlay upper/work (and the fallback rootfs copy) must live on our own tmpfs
# for the prebaked and apt-installed binaries to exec.
devkit_mount_writable() {
"${BB}" mkdir -p "${WRITABLE}"
devkit_is_mounted "${WRITABLE}" && return 0
"${BB}" mount -t tmpfs -o mode=755,size=2G tmpfs "${WRITABLE}"
}
# Overlay (devkit ro lower + tmpfs rw upper), falling back to a plain copy of the
# rootfs into the tmpfs if this kernel refuses an overlay mount.
devkit_mount_root() {
"${BB}" mkdir -p "${UPPER}" "${WORK}" "${MERGED}"
devkit_is_mounted "${MERGED}" && return 0
"${BB}" test -f "${WRITABLE}/.copied" && return 0
local ovl_err
if ovl_err="$("${BB}" mount -t overlay overlay "${MERGED}" \
-o "lowerdir=${DEVKIT},upperdir=${UPPER},workdir=${WORK}" 2>&1)" \
&& devkit_is_mounted "${MERGED}"; then
return 0
fi
# Fallback: overlay unavailable on this kernel - copy the rootfs into tmpfs.
"${BB}" echo "devkit: overlay mount unavailable (${ovl_err:-unknown}), copying rootfs into tmpfs" >&2
"${BB}" cp -a "${DEVKIT}/." "${MERGED}/"
"${BB}" touch "${WRITABLE}/.copied"
}
# Bind the kernel virtual filesystems so apt and the debug tools behave; /dev is
# rbind'd to bring in pts/shm for interactive shells.
devkit_bind_mounts() {
"${BB}" mkdir -p "${MERGED}/proc" "${MERGED}/sys" "${MERGED}/dev"
devkit_is_mounted "${MERGED}/proc" || "${BB}" mount -t proc proc "${MERGED}/proc"
devkit_is_mounted "${MERGED}/sys" || "${BB}" mount -t sysfs sysfs "${MERGED}/sys"
if ! devkit_is_mounted "${MERGED}/dev"; then
if ! "${BB}" mount -o rbind /dev "${MERGED}/dev" 2>/dev/null; then
"${BB}" mount -t devtmpfs devtmpfs "${MERGED}/dev" 2>/dev/null \
|| "${BB}" mount -t tmpfs tmpfs "${MERGED}/dev"
"${BB}" mkdir -p "${MERGED}/dev/pts"
"${BB}" mount -t devpts devpts "${MERGED}/dev/pts" 2>/dev/null || true
fi
fi
}
# Give apt/curl working DNS by importing the guest resolver config.
devkit_seed_resolv_conf() {
"${BB}" test -e /etc/resolv.conf || return 0
"${BB}" mkdir -p "${MERGED}/etc"
"${BB}" cp -L /etc/resolv.conf "${MERGED}/etc/resolv.conf" 2>/dev/null || true
}
# Expose the guest's real root as /real_root so container rootfses and other
# guest state are reachable from the debug shell (e.g.
# /real_root/run/kata-containers/<id>/rootfs).
#
# A symlink to /proc/1/root, not a bind mount: proc is mounted in the chroot, so
# the kernel resolves it to PID 1's root regardless of the chroot, with no
# bind-mount recursion or teardown ordering to worry about.
devkit_link_real_root() {
"${BB}" test -e "${MERGED}/real_root" && return 0
"${BB}" ln -s /proc/1/root "${MERGED}/real_root" 2>/dev/null || true
}
# Idempotent: safe to call from every devkit-* invocation.
devkit_setup_chroot() {
devkit_mount_writable || return 1
devkit_mount_root || return 1
devkit_bind_mounts || return 1
devkit_seed_resolv_conf
devkit_link_real_root
"${BB}" echo "${DEVKIT_INIT_VERSION}" > "${WRITABLE}/.initialized" 2>/dev/null || true
return 0
}
devkit_chroot_exec() {
devkit_setup_chroot || {
"${BB}" echo "devkit: failed to set up chroot environment" >&2
return 1
}
exec "${BB}" chroot "${MERGED}" "$@"
}