# SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield # # SPDX-License-Identifier: MIT # # vcontainer-tarball.bb # =========================================================================== # Standalone SDK-style tarball for vdkr/vpdmn container tools # =========================================================================== # # This recipe uses Yocto's populate_sdk infrastructure to create a # relocatable standalone distribution of vdkr (Docker) and vpdmn (Podman). # # USAGE: # MACHINE=qemux86-64 bitbake vcontainer-tarball # MACHINE=qemuarm64 bitbake vcontainer-tarball # # OUTPUT: # tmp/deploy/sdk/vcontainer-standalone-.tar.xz # tmp/deploy/sdk/vcontainer-standalone-.sh (self-extracting installer) # # =========================================================================== SUMMARY = "Standalone SDK tarball for vdkr and vpdmn container tools" DESCRIPTION = "A relocatable standalone distribution of vdkr (Docker) and \ vpdmn (Podman) CLI tools using Yocto SDK infrastructure." HOMEPAGE = "https://git.yoctoproject.org/meta-virtualization/" LICENSE = "MIT" LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" # file:// SRC_URI entries land directly in UNPACKDIR, not a subdirectory S = "${UNPACKDIR}" # Use our custom SDK installer template with vcontainer-specific messages FILESEXTRAPATHS:prepend := "${THISDIR}/files:" TOOLCHAIN_SHAR_EXT_TMPL = "${THISDIR}/files/toolchain-shar-extract.sh" # Declare script sources so BitBake tracks changes and rebuilds when they change SRC_URI = "\ file://vrunner.sh \ file://vrunner-backend-qemu.sh \ file://vrunner-backend-xen.sh \ file://vcontainer-common.sh \ file://vdkr.sh \ file://vpdmn.sh \ file://toolchain-shar-extract.sh \ " # No target sysroot - host tools only (like buildtools-tarball) TOOLCHAIN_TARGET_TASK = "" TARGET_ARCH = "none" TARGET_OS = "none" # Host tools to include via SDK # Note: nativesdk-qemu-vcontainer is a minimal QEMU without OpenGL/virgl # to avoid mesa -> llvm -> clang build dependency chain TOOLCHAIN_HOST_TASK = "\ nativesdk-sdk-provides-dummy \ nativesdk-qemu-vcontainer \ nativesdk-socat \ nativesdk-expect \ " # SDK naming and metadata TOOLCHAIN_OUTPUTNAME = "vcontainer-standalone" SDK_TITLE = "vcontainer tools (vdkr/vpdmn)" # SDK configuration (same pattern as buildtools-tarball) MULTIMACH_TARGET_SYS = "${SDK_ARCH}-nativesdk${SDK_VENDOR}-${SDK_OS}" PACKAGE_ARCH = "${SDK_ARCH}_${SDK_OS}" PACKAGE_ARCHS = "" SDK_PACKAGE_ARCHS += "vcontainer-dummy-${SDKPKGSUFFIX}" RDEPENDS = "${TOOLCHAIN_HOST_TASK}" EXCLUDE_FROM_WORLD = "1" inherit populate_sdk inherit toolchain-scripts-base inherit nopackages inherit container-registry # Must be set AFTER inherit populate_sdk (class sets it to target arch) REAL_MULTIMACH_TARGET_SYS = "none" # Disable tasks we don't need deltask install deltask populate_sysroot # No config site needed TOOLCHAIN_NEED_CONFIGSITE_CACHE = "" INHIBIT_DEFAULT_DEPS = "1" do_populate_sdk[stamp-extra-info] = "${PACKAGE_ARCH}" # =========================================================================== # Architecture mapping # =========================================================================== VCONTAINER_TARGET_ARCH = "${@d.getVar('MACHINE').replace('qemuarm64', 'aarch64').replace('qemux86-64', 'x86_64')}" VCONTAINER_KERNEL_NAME = "${@'Image' if d.getVar('MACHINE') == 'qemuarm64' else 'bzImage'}" VCONTAINER_MC = "${@'vruntime-aarch64' if d.getVar('MACHINE') == 'qemuarm64' else 'vruntime-x86-64'}" # =========================================================================== # Multiconfig dependencies for blobs # =========================================================================== # By default, builds BOTH x86_64 and aarch64 via mcdepends. # To limit to a single architecture, set in local.conf: # VCONTAINER_ARCHITECTURES = "x86_64" # x86_64 only # VCONTAINER_ARCHITECTURES = "aarch64" # aarch64 only # # Helper function (optional - for auto-detection if needed) def get_available_architectures(d): import os topdir = d.getVar('TOPDIR') available = [] arch_info = { 'x86_64': ('vruntime-x86-64', 'qemux86-64'), 'aarch64': ('vruntime-aarch64', 'qemuarm64'), } for arch, (mc, machine) in arch_info.items(): deploy_dir = os.path.join(topdir, 'tmp-%s' % mc, 'deploy', 'images', machine, 'vdkr', arch) if os.path.isdir(deploy_dir): available.append(arch) # If nothing available yet, default to current MACHINE's arch (will be built) if not available: available.append(d.getVar('VCONTAINER_TARGET_ARCH')) return " ".join(sorted(available)) # Default to both architectures. Override in local.conf if you only want one: # VCONTAINER_ARCHITECTURES = "x86_64" # VCONTAINER_ARCHITECTURES = "aarch64" VCONTAINER_ARCHITECTURES ?= "x86_64 aarch64" # Conditionally set mcdepends based on available multiconfigs # (avoids parse errors when BBMULTICONFIG is not set, e.g. yocto-check-layer) python () { bbmulticonfig = (d.getVar('BBMULTICONFIG') or "").split() mcdeps = [] for mc in ['vruntime-x86-64', 'vruntime-aarch64']: if mc in bbmulticonfig: mcdeps.append('mc::%s:vdkr-initramfs-create:do_deploy' % mc) mcdeps.append('mc::%s:vpdmn-initramfs-create:do_deploy' % mc) if mcdeps: d.setVarFlag('do_populate_sdk', 'mcdepends', ' '.join(mcdeps)) # Build-time banner is in do_populate_sdk:append() below } # =========================================================================== # Custom SDK files - environment script AND blobs/scripts # =========================================================================== # IMPORTANT: All custom files must be added in create_sdk_files:append() # because SDK_POSTPROCESS_COMMAND runs archive_sdk BEFORE any appended commands. # The order is: create_sdk_files -> check_sdk_sysroots -> archive_sdk -> ... # =========================================================================== create_sdk_files:append () { # Variables - these are Yocto variables expanded at parse time TOPDIR="${TOPDIR}" THISDIR="${THISDIR}" ARCHITECTURES="${VCONTAINER_ARCHITECTURES}" CONTAINER_REGISTRY_SECURE="${CONTAINER_REGISTRY_SECURE}" CONTAINER_REGISTRY_CA_CERT="${CONTAINER_REGISTRY_CA_CERT}" # SDK output directory SDK_OUT="${SDK_OUTPUT}/${SDKPATH}" bbnote "TOPDIR=${TOPDIR}" bbnote "THISDIR=${THISDIR}" bbnote "ARCHITECTURES=${ARCHITECTURES}" bbnote "SDK_OUT=${SDK_OUT}" # ----------------------------------------------------------------------- # Step 1: Copy blobs and scripts (MUST happen before archive_sdk) # ----------------------------------------------------------------------- VDKR_INCLUDED=0 VPDMN_INCLUDED=0 # Copy blobs for each architecture for ARCH in ${ARCHITECTURES}; do bbnote "Adding vcontainer blobs for ${ARCH}" # Determine multiconfig and kernel name for this arch case "${ARCH}" in aarch64) MC="vruntime-aarch64" MC_MACHINE="qemuarm64" KERNEL="Image" ;; x86_64) MC="vruntime-x86-64" MC_MACHINE="qemux86-64" KERNEL="bzImage" ;; *) bbwarn "Unknown architecture: ${ARCH}" continue ;; esac MC_DEPLOY="${TOPDIR}/tmp-${MC}/deploy/images/${MC_MACHINE}" bbnote "MC_DEPLOY=${MC_DEPLOY} for ${ARCH}" # Create blob directories mkdir -p "${SDK_OUT}/vdkr-blobs/${ARCH}" mkdir -p "${SDK_OUT}/vpdmn-blobs/${ARCH}" # Copy vdkr blobs VDKR_SRC="${MC_DEPLOY}/vdkr/${ARCH}" if [ -d "${VDKR_SRC}" ]; then for blob in ${KERNEL} initramfs.cpio.gz rootfs.img; do if [ -f "${VDKR_SRC}/${blob}" ]; then cp "${VDKR_SRC}/${blob}" "${SDK_OUT}/vdkr-blobs/${ARCH}/" bbnote "Copied vdkr blob: ${ARCH}/${blob}" else bbfatal "vdkr blob not found: ${VDKR_SRC}/${blob}" fi done VDKR_INCLUDED=1 else bbfatal "vdkr blobs not found for ${ARCH}. Build them first with: bitbake mc:${MC}:vdkr-initramfs-create" fi # Copy vpdmn blobs VPDMN_SRC="${MC_DEPLOY}/vpdmn/${ARCH}" if [ -d "${VPDMN_SRC}" ]; then for blob in ${KERNEL} initramfs.cpio.gz rootfs.img; do if [ -f "${VPDMN_SRC}/${blob}" ]; then cp "${VPDMN_SRC}/${blob}" "${SDK_OUT}/vpdmn-blobs/${ARCH}/" bbnote "Copied vpdmn blob: ${ARCH}/${blob}" else bbfatal "vpdmn blob not found: ${VPDMN_SRC}/${blob}" fi done VPDMN_INCLUDED=1 else bbfatal "vpdmn blobs not found for ${ARCH}. Build them first with: bitbake mc:${MC}:vpdmn-initramfs-create" fi done # Copy scripts from layer FILES_DIR="${THISDIR}/files" # Copy shared scripts for script in vrunner.sh vrunner-backend-qemu.sh vrunner-backend-xen.sh vcontainer-common.sh; do if [ -f "${FILES_DIR}/${script}" ]; then cp "${FILES_DIR}/${script}" "${SDK_OUT}/" chmod 755 "${SDK_OUT}/${script}" bbnote "Copied ${script}" else bbfatal "${script} not found in ${FILES_DIR}" fi done # Copy and set up vdkr if [ "${VDKR_INCLUDED}" = "1" ] && [ -f "${FILES_DIR}/vdkr.sh" ]; then cp "${FILES_DIR}/vdkr.sh" "${SDK_OUT}/vdkr" chmod 755 "${SDK_OUT}/vdkr" # Create symlinks for each included architecture for ARCH in ${ARCHITECTURES}; do if [ -d "${SDK_OUT}/vdkr-blobs/${ARCH}" ]; then ln -sf vdkr "${SDK_OUT}/vdkr-${ARCH}" bbnote "Created symlink vdkr-${ARCH}" fi done bbnote "Installed vdkr" fi # Copy and set up vpdmn if [ "${VPDMN_INCLUDED}" = "1" ] && [ -f "${FILES_DIR}/vpdmn.sh" ]; then cp "${FILES_DIR}/vpdmn.sh" "${SDK_OUT}/vpdmn" chmod 755 "${SDK_OUT}/vpdmn" # Create symlinks for each included architecture for ARCH in ${ARCHITECTURES}; do if [ -d "${SDK_OUT}/vpdmn-blobs/${ARCH}" ]; then ln -sf vpdmn "${SDK_OUT}/vpdmn-${ARCH}" bbnote "Created symlink vpdmn-${ARCH}" fi done bbnote "Installed vpdmn" fi # Copy CA certificate for secure registry mode (if available) SECURE_MODE="${CONTAINER_REGISTRY_SECURE}" CA_CERT="${CONTAINER_REGISTRY_CA_CERT}" if [ "${SECURE_MODE}" = "1" ] && [ -f "${CA_CERT}" ]; then mkdir -p "${SDK_OUT}/registry" cp "${CA_CERT}" "${SDK_OUT}/registry/ca.crt" bbnote "Included secure registry CA certificate" elif [ "${SECURE_MODE}" = "1" ]; then bbwarn "Secure registry mode enabled but CA cert not found at ${CA_CERT}" bbwarn "Run: bitbake container-registry-index -c generate_registry_script" fi # Create README cat > "${SDK_OUT}/README.txt" < - Docker CLI wrapper vpdmn, vpdmn- - Podman CLI wrapper vrunner.sh - Shared QEMU runner vcontainer-common.sh - Shared CLI code vdkr-blobs/ - Docker QEMU blobs (per-arch subdirectories) vpdmn-blobs/ - Podman QEMU blobs (per-arch subdirectories) sysroots/ - SDK binaries (QEMU, socat, libraries) registry/ca.crt - CA cert for secure registry (if CONTAINER_REGISTRY_SECURE=1) Secure Registry Usage: vdkr --secure-registry --ca-cert registry/ca.crt pull myimage Requirements: - Linux x86_64 host - KVM support recommended (for performance) For more information: https://git.yoctoproject.org/meta-virtualization/ EOF bbnote "vcontainer blobs and scripts added to SDK" # ----------------------------------------------------------------------- # Step 2: Create environment script (after blobs so we can check for them) # ----------------------------------------------------------------------- # Remove ALL default SDK files - we create our own rm -f ${SDK_OUTPUT}/${SDKPATH}/site-config-* rm -f ${SDK_OUTPUT}/${SDKPATH}/version-* rm -f ${SDK_OUTPUT}/${SDKPATH}/environment-setup-* # Create vcontainer-specific environment script # Keep the environment-setup-* naming so SDK installer works script=${SDK_OUTPUT}/${SDKPATH}/environment-setup-${REAL_MULTIMACH_TARGET_SYS} # Create environment script # Set OECORE_NATIVE_SYSROOT temporarily for SDK relocation, then unset it # (like buildtools-tarball does to avoid confusing other Yocto tools) cat > $script <<'HEADER' #!/bin/bash # vcontainer environment setup script # Source this file: source environment-setup-none # Or use the symlink: source init-env.sh VCONTAINER_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" HEADER # Yocto variables (${SDK_SYS}, ${SDKPATHNATIVE}) expand at parse time # Shell variables use $VAR to avoid Yocto expansion echo 'export OECORE_NATIVE_SYSROOT="'"${SDKPATHNATIVE}"'"' >> $script echo 'export PATH="$VCONTAINER_DIR:$OECORE_NATIVE_SYSROOT/usr/bin:/usr/bin:/bin:$PATH"' >> $script cat >> $script <<'FOOTER' echo "vcontainer environment configured." echo "" FOOTER # Add usage info based on what's included (check files we just copied) if [ -f "${SDK_OUT}/vdkr" ]; then cat >> $script <<'ENVEOF' echo "vdkr (Docker) commands:" echo " vdkr images # List docker images" echo " vdkr vimport ./oci/ app # Import OCI directory" ENVEOF fi if [ -f "${SDK_OUT}/vpdmn" ]; then cat >> $script <<'ENVEOF' echo "vpdmn (Podman) commands:" echo " vpdmn images # List podman images" echo " vpdmn vimport ./oci/ app # Import OCI directory" ENVEOF fi cat >> $script <> $script echo '# Clean up - unset to avoid confusing other Yocto tools' >> $script echo 'unset OECORE_NATIVE_SYSROOT' >> $script # Replace placeholder with actual SDK_SYS sed -i -e "s:@SDK_SYS@:${SDK_SYS}:g" $script chmod 755 $script # Create init-env.sh symlink for convenience ln -sf environment-setup-${REAL_MULTIMACH_TARGET_SYS} ${SDK_OUTPUT}/${SDKPATH}/init-env.sh # Create version file echo "vcontainer SDK version: ${PV}" > ${SDK_OUTPUT}/${SDKPATH}/version.txt echo "Built: $(date)" >> ${SDK_OUTPUT}/${SDKPATH}/version.txt echo "Architectures: ${VCONTAINER_ARCHITECTURES}" >> ${SDK_OUTPUT}/${SDKPATH}/version.txt # ----------------------------------------------------------------------- # Step 3: Remove unnecessary files to reduce SDK size (~100MB savings) # ----------------------------------------------------------------------- bbnote "Removing unnecessary files from SDK..." # Remove locale data (~97MB) - not needed for vcontainer tools rm -rf ${SDK_OUTPUT}/${SDKPATHNATIVE}/usr/lib/locale bbnote "Removed locale data" # Remove QEMU user-mode emulators (we only need system emulators) # Keep: qemu-system-aarch64, qemu-system-x86_64 for qemu_bin in ${SDK_OUTPUT}/${SDKPATHNATIVE}/usr/bin/qemu-*; do case "$(basename $qemu_bin)" in qemu-system-aarch64|qemu-system-x86_64|qemu-img|qemu-nbd) # Keep these ;; qemu-system-*) # Remove other system emulators (handled by QEMU_TARGETS now, but just in case) rm -f "$qemu_bin" ;; *) # Remove user-mode emulators (qemu-aarch64, qemu-arm, etc.) rm -f "$qemu_bin" bbnote "Removed $(basename $qemu_bin)" ;; esac done bbnote "SDK size optimization complete" } create_sdk_files[vardeps] += "VCONTAINER_TARGET_ARCH VCONTAINER_KERNEL_NAME VCONTAINER_MC" # =========================================================================== # Substitute custom placeholders in installer script # =========================================================================== # This runs AFTER archive_sdk to replace placeholders in our custom template SDK_POSTPROCESS_COMMAND += "substitute_vcontainer_vars;" substitute_vcontainer_vars () { # Replace placeholders in the installer script # Use SDKDEPLOYDIR (work directory) not SDK_DEPLOY (final deploy location) sed -i -e "s|@VCONTAINER_ARCHITECTURES@|${VCONTAINER_ARCHITECTURES}|g" ${SDKDEPLOYDIR}/${TOOLCHAIN_OUTPUTNAME}.sh bbnote "Substituted VCONTAINER_ARCHITECTURES=${VCONTAINER_ARCHITECTURES} in installer" } substitute_vcontainer_vars[vardeps] += "VCONTAINER_ARCHITECTURES" # =========================================================================== # Print usage information after SDK is built # =========================================================================== python do_populate_sdk:append() { import os deploy_dir = d.getVar('SDK_DEPLOY') toolchain_outputname = d.getVar('TOOLCHAIN_OUTPUTNAME') architectures = d.getVar('VCONTAINER_ARCHITECTURES').split() # Find the installer script installer = os.path.join(deploy_dir, toolchain_outputname + '.sh') installer_size = 0 if os.path.exists(installer): installer_size = os.path.getsize(installer) // (1024 * 1024) # Check what was included sdk_output = d.getVar('SDK_OUTPUT') sdkpath = d.getVar('SDKPATH') sdk_out = os.path.join(sdk_output, sdkpath.lstrip('/')) vdkr_included = os.path.exists(os.path.join(sdk_out, 'vdkr')) vpdmn_included = os.path.exists(os.path.join(sdk_out, 'vpdmn')) bb.plain("") bb.plain("=" * 70) bb.plain("vcontainer SDK tarball created:") bb.plain(" %s" % installer) bb.plain(" Size: %d MB" % installer_size) bb.plain(" Architectures: %s" % " ".join(architectures)) bb.plain(" vdkr (Docker): %s" % ("included" if vdkr_included else "NOT included")) bb.plain(" vpdmn (Podman): %s" % ("included" if vpdmn_included else "NOT included")) bb.plain("") bb.plain("To extract and use:") bb.plain(" %s -d /tmp/vcontainer -y" % installer) bb.plain(" cd /tmp/vcontainer") bb.plain(" source init-env.sh") for arch in architectures: if vdkr_included: bb.plain(" vdkr-%s images # Docker for %s" % (arch, arch)) if vpdmn_included: bb.plain(" vpdmn-%s images # Podman for %s" % (arch, arch)) bb.plain("=" * 70) }