#!/usr/bin/env bash # ============================================================================ # qcrows-initrd-regen — Environment-aware initrd/ramfs/cramfs regeneration # # Rebuilds the initrd component of a QCrows bundle with precise awareness # of the Kata Containers runtime environment. Unlike dracut or mkinitramfs, # this script produces an initrd where kata-agent IS the final process # (no pivot_root, no switch_root) and includes ONLY the kernel modules # required by the detected VMM transport layer. # # Usage: # qcrows-initrd-regen image.qcrows [options] # qcrows-initrd-regen image.qcrows --vmm qemu --compress lz4 -o new-initrd.img # qcrows-initrd-regen image.qcrows --in-place # # Environment detection (automatic when --vmm is not specified): # 1. Reads /etc/kata-containers/configuration.toml or the path in # KATA_CFG to determine the active hypervisor profile. # 2. Inspects the QCrows bundle's kernel/.config to determine which # compression algorithms the guest kernel supports. # 3. Examines the rootfs to detect init system type (systemd, OpenRC, # busybox /init) and locate the kata-agent binary. # 4. Detects host architecture for correct module paths. # # Output formats: # cpio-gzip — gzip-compressed cpio (default, universal) # cpio-lz4 — lz4-compressed cpio (fastest boot, kernel must support) # cpio-xz — xz-compressed cpio (smallest size, slowest boot) # cpio-zstd — zstd-compressed cpio (balanced, kernel 5.15+) # cramfs — cramfs filesystem (read-only, extremely minimal) # ============================================================================ set -euo pipefail # ─── Version ──────────────────────────────────────────────────────────────── VERSION="0.1.0" # ─── Colors ───────────────────────────────────────────────────────────────── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' BLUE='\033[0;34m' NC='\033[0m' log() { echo -e "${GREEN}[initrd-regen]${NC} $*"; } warn() { echo -e "${YELLOW}[initrd-regen]${NC} WARNING: $*"; } error() { echo -e "${RED}[initrd-regen]${NC} ERROR: $*" >&2; } die() { error "$@"; exit 1; } # ─── Defaults ─────────────────────────────────────────────────────────────── INPUT="" OUTPUT="" VMM="" # auto-detected from kata config if empty COMPRESS="" # auto-detected from kernel config if empty FORMAT="" # cpio-gzip, cpio-lz4, cpio-xz, cpio-zstd, cramfs AGENT_PATH="" # auto-discovered from rootfs if empty INIT_STYLE="" # auto-detected: systemd, openrc, busybox IN_PLACE=false KEEP_TEMP=false STRIP_LOCALES=true STRIP_DOCS=true STRIP_MAN=true EXTRA_MODULES="" # comma-separated additional kernel modules EXCLUDE_MODULES="" # comma-separated modules to exclude KATA_CFG="" # path to configuration.toml (auto-detected if empty) VERBOSE=false KERNEL_MODULES_DIR="" # auto-discovered from bundle kernel version # ─── Temp directory ──────────────────────────────────────────────────────── TMPDIR="" cleanup() { if [[ -n "$TMPDIR" && -d "$TMPDIR" && "$KEEP_TEMP" == "false" ]]; then rm -rf "$TMPDIR" elif [[ -n "$TMPDIR" && -d "$TMPDIR" && "$KEEP_TEMP" == "true" ]]; then log "Temp directory preserved: ${TMPDIR}" fi } trap cleanup EXIT # ============================================================================ # VMM Kernel Module Matrix # ============================================================================ # Each VMM requires a specific set of virtio transports and guest drivers. # This table drives module selection — no nested ifs, just array lookup. # # Format: VMM:required_modules:optional_modules # required — MUST be included for the VMM to function # optional — included only if present in the kernel and not excluded declare -A VMM_REQUIRED_MODULES declare -A VMM_OPTIONAL_MODULES VMM_REQUIRED_MODULES[qemu]="virtio_pci virtio_blk virtio_net virtio_rng virtio_balloon virtio_console virtio_gpu" VMM_OPTIONAL_MODULES[qemu]="virtio_scsi virtio_input virtio_crypto virtio_mem virtio_pmem 9pnet_virtio" VMM_REQUIRED_MODULES[cloud-hypervisor]="virtio_mmio virtio_blk virtio_net virtio_rng virtio_console virtio_balloon" VMM_OPTIONAL_MODULES[cloud-hypervisor]="virtio_gpu virtio_scsi 9pnet_virtio" VMM_REQUIRED_MODULES[firecracker]="virtio_mmio virtio_blk virtio_net virtio_rng" VMM_OPTIONAL_MODULES[firecracker]="virtio_console" VMM_REQUIRED_MODULES[dragonball]="virtio_blk virtio_net virtio_rng virtio_console" VMM_OPTIONAL_MODULES[dragonball]="virtio_balloon virtio_gpu 9pnet_virtio" # Core modules needed regardless of VMM — filesystem and device infrastructure CORE_MODULES="ext4 vfat nls_cp437 nls_iso8859_1 dm_mod dm_bufio loop squashfs overlay" # ============================================================================ # Compression detection from kernel .config # ============================================================================ # The guest kernel must be compiled with support for the chosen initrd # compression algorithm. These config symbols determine what works. declare -A COMPRESS_CONFIG_MAP COMPRESS_CONFIG_MAP[cpio-gzip]="CONFIG_RD_GZIP" COMPRESS_CONFIG_MAP[cpio-lz4]="CONFIG_RD_LZ4" COMPRESS_CONFIG_MAP[cpio-xz]="CONFIG_RD_XZ" COMPRESS_CONFIG_MAP[cpio-zstd]="CONFIG_RD_ZSTD" # Compression commands: how to compress the cpio stream declare -A COMPRESS_CMD COMPRESS_CMD[cpio-gzip]="gzip -9" COMPRESS_CMD[cpio-lz4]="lz4 -l" COMPRESS_CMD[cpio-xz]="xz -9 --check=crc32" COMPRESS_CMD[cpio-zstd]="zstd -19" # Compression file extensions declare -A COMPRESS_EXT COMPRESS_EXT[cpio-gzip]=".img" COMPRESS_EXT[cpio-lz4]=".cpio.lz4" COMPRESS_EXT[cpio-xz]=".cpio.xz" COMPRESS_EXT[cpio-zstd]=".cpio.zst" # Priority order for auto-detection (fastest boot first) COMPRESS_PRIORITY=("cpio-lz4" "cpio-zstd" "cpio-gzip" "cpio-xz") # ============================================================================ # Kata configuration.toml VMM detection # ============================================================================ # Maps hypervisor entries in configuration.toml to our VMM identifiers. declare -A CFG_HYPERVISOR_MAP CFG_HYPERVISOR_MAP[qemu]="qemu" CFG_HYPERVISOR_MAP[cloud-hypervisor]="cloud-hypervisor" CFG_HYPERVISOR_MAP[clh]="cloud-hypervisor" CFG_HYPERVISOR_MAP[firecracker]="firecracker" CFG_HYPERVISOR_MAP[fc]="firecracker" CFG_HYPERVISOR_MAP[dragonball]="dragonball" # ============================================================================ # Usage # ============================================================================ usage() { cat < [options]" [[ ! -f "$INPUT" ]] && die "File not found: ${INPUT}" # Validate --vmm value against known VMMs local -a known_vmms=("qemu" "cloud-hypervisor" "firecracker" "dragonball") if [[ -n "$VMM" ]]; then local vmm_valid=false local known for known in "${known_vmms[@]}"; do [[ "$VMM" == "$known" ]] && { vmm_valid=true; break; } done [[ "$vmm_valid" == "false" ]] && die "Unknown VMM: ${VMM}. Supported: ${known_vmms[*]}" fi # Validate --init-style local -a known_init_styles=("systemd" "openrc" "busybox") if [[ -n "$INIT_STYLE" ]]; then local style_valid=false local known for known in "${known_init_styles[@]}"; do [[ "$INIT_STYLE" == "$known" ]] && { style_valid=true; break; } done [[ "$style_valid" == "false" ]] && die "Unknown init style: ${INIT_STYLE}. Supported: ${known_init_styles[*]}" fi # Validate --format local -a known_formats=("cpio-gzip" "cpio-lz4" "cpio-xz" "cpio-zstd" "cramfs") if [[ -n "$FORMAT" ]]; then local fmt_valid=false local known for known in "${known_formats[@]}"; do [[ "$FORMAT" == "$known" ]] && { fmt_valid=true; break; } done [[ "$fmt_valid" == "false" ]] && die "Unknown format: ${FORMAT}. Supported: ${known_formats[*]}" fi # Validate --agent path if specified if [[ -n "$AGENT_PATH" ]]; then [[ ! -f "$AGENT_PATH" ]] && die "Agent binary not found: ${AGENT_PATH}" [[ ! -x "$AGENT_PATH" ]] && die "Agent binary not executable: ${AGENT_PATH}" fi # In-place requires output to be empty [[ "$IN_PLACE" == "true" && -n "$OUTPUT" ]] && die "Cannot use --in-place and --output together" # cramfs format cannot be used in-place (different structure) [[ "$IN_PLACE" == "true" && "$FORMAT" == "cramfs" ]] && die "cramfs format is not supported with --in-place (use cpio format instead)" } # ============================================================================ # Extract QCrows bundle # ============================================================================ extract_qcrows() { local archive="$1" local dest="$2" log "Extracting QCrows bundle: $(basename "$archive")" mkdir -p "$dest" # Detect compression and extract case "$archive" in *.gz|*.qcrows.gz|*.tar.gz) tar -xzf "$archive" -C "$dest" ;; *) # Try gzip first, then uncompressed if tar -tzf "$archive" &>/dev/null; then tar -xzf "$archive" -C "$dest" else tar -xf "$archive" -C "$dest" fi ;; esac # Verify required components exist local -a required_files=("metadata.toml" "rootfs.tar.gz" "kernel/") local req for req in "${required_files[@]}"; do [[ -e "${dest}/${req}" ]] || die "Missing required component in bundle: ${req}" done # Verify kernel binary exists local kernel_found=false [[ -f "${dest}/kernel/vmlinuz" ]] && kernel_found=true [[ -f "${dest}/kernel/vmlinux" ]] && kernel_found=true [[ "$kernel_found" == "false" ]] && die "No kernel binary found in bundle (expected kernel/vmlinuz or kernel/vmlinux)" # Verify kernel config exists (QCrows v0.2 requirement) [[ -f "${dest}/kernel/config" ]] || die "No kernel config found in bundle — required for env-aware module selection" log "Bundle extracted to: ${dest}" } # ============================================================================ # Auto-detect VMM from kata configuration.toml # ============================================================================ detect_vmm() { [[ -n "$VMM" ]] && return log "Auto-detecting VMM from Kata configuration..." # Candidate configuration.toml paths local -a cfg_candidates=( "${KATA_CFG:-}" "/etc/kata-containers/configuration.toml" "/usr/share/defaults/kata-containers/configuration.toml" "/usr/share/kata-containers/configuration.toml" "/opt/kata/share/defaults/kata-containers/configuration.toml" ) local cfg_path="" local candidate for candidate in "${cfg_candidates[@]}"; do [[ -n "$candidate" && -f "$candidate" ]] && { cfg_path="$candidate"; break; } done if [[ -z "$cfg_path" ]]; then warn "No configuration.toml found — defaulting to qemu" VMM="qemu" return fi log "Found configuration: ${cfg_path}" # Extract the hypervisor type from configuration.toml. # The active hypervisor section is identified by the [hypervisor.qemu] / # [hypervisor.cloud-hypervisor] / etc. section headers. # The default runtime determines which section is active. local runtime_link="" if [[ -L "/usr/bin/kata-runtime" ]]; then runtime_link="$(readlink -f /usr/bin/kata-runtime 2>/dev/null || true)" fi # Check kata-runtime symlink naming convention local -a runtime_vmm_map=( "kata-qemu:qemu" "kata-clh:cloud-hypervisor" "kata-cloud-hypervisor:cloud-hypervisor" "kata-fc:firecracker" "kata-firecracker:firecracker" "kata-dragonball:dragonball" ) local entry pattern vmm_id for entry in "${runtime_vmm_map[@]}"; do pattern="${entry%%:*}" vmm_id="${entry##*:}" if [[ "$runtime_link" == *"$pattern"* ]]; then VMM="$vmm_id" log "Detected VMM from runtime symlink: ${VMM}" return fi done # Fallback: parse configuration.toml for the first hypervisor section local -a section_patterns=("qemu" "cloud-hypervisor" "clh" "firecracker" "fc" "dragonball") local sect for sect in "${section_patterns[@]}"; do if grep -q "\\[hypervisor\\.${sect}\\]" "$cfg_path" 2>/dev/null; then VMM="${CFG_HYPERVISOR_MAP[$sect]:-$sect}" log "Detected VMM from config section: ${VMM}" return fi done # Check which runtime is actually active via kata-runtime local kata_runtime kata_runtime="$(which kata-runtime 2>/dev/null || true)" if [[ -n "$kata_runtime" ]]; then local runtime_info runtime_info="$("$kata_runtime" --version 2>/dev/null | head -1 || true)" [[ "$VERBOSE" == "true" ]] && log "kata-runtime version: ${runtime_info}" fi warn "Could not determine active VMM — defaulting to qemu" VMM="qemu" } # ============================================================================ # Auto-detect compression from kernel .config # ============================================================================ detect_compression() { [[ -n "$FORMAT" && "$FORMAT" != "cramfs" ]] && return log "Auto-detecting compression from kernel config..." local kernel_config="${EXTRACT_DIR}/kernel/config" [[ ! -f "$kernel_config" ]] && { warn "No kernel config available — defaulting to gzip"; FORMAT="${FORMAT:-cpio-gzip}"; return; } # Check each compression algorithm in priority order local algo config_key for algo in "${COMPRESS_PRIORITY[@]}"; do config_key="${COMPRESS_CONFIG_MAP[$algo]:-}" [[ -z "$config_key" ]] && continue if grep -q "^${config_key}=y" "$kernel_config" 2>/dev/null; then FORMAT="$algo" log "Kernel supports ${algo##*-} — selected as optimal compression" return fi done # No supported compression found in kernel config — this should not happen warn "No initrd compression support detected in kernel config — defaulting to gzip" FORMAT="${FORMAT:-cpio-gzip}" } # ============================================================================ # Auto-detect init style from rootfs # ============================================================================ detect_init_style() { [[ -n "$INIT_STYLE" ]] && return log "Auto-detecting init system from rootfs..." local rootfs_dir="${EXTRACT_DIR}/rootfs-extracted" mkdir -p "$rootfs_dir" # Extract just enough of the rootfs to detect init system. # We only need the directory listing, not the full content. tar -tzf "${EXTRACT_DIR}/rootfs.tar.gz" 2>/dev/null | head -500 > "${TMPDIR}/rootfs-listing.txt" if grep -q "sbin/init$\|sbin/systemd$" "${TMPDIR}/rootfs-listing.txt"; then INIT_STYLE="systemd" log "Detected init system: systemd" elif grep -q "sbin/openrc-init$" "${TMPDIR}/rootfs-listing.txt"; then INIT_STYLE="openrc" log "Detected init system: OpenRC" else INIT_STYLE="busybox" log "Detected init system: busybox (kata-agent as PID 1)" fi } # ============================================================================ # Discover kata-agent in rootfs # ============================================================================ discover_agent() { [[ -n "$AGENT_PATH" && -f "$AGENT_PATH" ]] && return log "Discovering kata-agent in rootfs..." local listing="${TMPDIR}/rootfs-listing.txt" # Agent candidate paths in the rootfs local -a agent_candidates=( "usr/bin/kata-agent" "usr/local/bin/kata-agent" "usr/sbin/kata-agent" "sbin/kata-agent" ) local candidate for candidate in "${agent_candidates[@]}"; do if grep -q "^\\./${candidate}$\\|^${candidate}$" "$listing" 2>/dev/null; then AGENT_PATH="${EXTRACT_DIR}/rootfs-extracted/${candidate}" log "Found kata-agent at: /${candidate}" return fi done die "kata-agent not found in rootfs. Specify path with --agent" } # ============================================================================ # Extract rootfs for initrd building # ============================================================================ extract_rootfs() { local rootfs_dir="${EXTRACT_DIR}/rootfs-extracted" [[ -d "$rootfs_dir" && -f "${rootfs_dir}/init" ]] && return log "Extracting rootfs for initrd assembly..." mkdir -p "$rootfs_dir" tar -xzf "${EXTRACT_DIR}/rootfs.tar.gz" -C "$rootfs_dir" 2>/dev/null || \ tar -xf "${EXTRACT_DIR}/rootfs.tar.gz" -C "$rootfs_dir" 2>/dev/null || \ die "Failed to extract rootfs" log "Rootfs extracted: $(du -sh "$rootfs_dir" | cut -f1)" } # ============================================================================ # Resolve kernel module paths # ============================================================================ resolve_kernel_modules() { log "Resolving kernel modules for VMM: ${VMM}" local kernel_config="${EXTRACT_DIR}/kernel/config" # Determine kernel version from the bundled kernel local kernel_version kernel_version="$(strings "${EXTRACT_DIR}/kernel/vmlinuz" "${EXTRACT_DIR}/kernel/vmlinux" 2>/dev/null \ | grep -oP '^\d+\.\d+\.\d+' | head -1 || true)" if [[ -z "$kernel_version" ]]; then # Fallback: extract from kernel config kernel_version="$(grep "^CONFIG_VERSION_SIGNATURE=" "$kernel_config" 2>/dev/null \ | grep -oP '\d+\.\d+\.\d+' | head -1 || true)" fi if [[ -z "$kernel_version" ]]; then warn "Could not determine kernel version — module inclusion will be name-only" else log "Kernel version: ${kernel_version}" fi # Build the complete module list from the matrix local -a required_modules=() local -a optional_modules=() # Core modules (always needed) local core for core in $CORE_MODULES; do required_modules+=("$core") done # VMM-specific required modules local vmm_req="${VMM_REQUIRED_MODULES[$VMM]:-}" local mod for mod in $vmm_req; do required_modules+=("$mod") done # VMM-specific optional modules (only if compiled in kernel) local vmm_opt="${VMM_OPTIONAL_MODULES[$VMM]:-}" for mod in $vmm_opt; do # Check if the module is compiled (=y or =m) in the kernel config local config_name="CONFIG_${mod^^}" if grep -q "^${config_name}=y\\|^${config_name}=m" "$kernel_config" 2>/dev/null; then optional_modules+=("$mod") fi done # Extra modules from --extra-modules if [[ -n "$EXTRA_MODULES" ]]; then IFS=',' read -ra extra_arr <<< "$EXTRA_MODULES" for mod in "${extra_arr[@]}"; do [[ -n "$mod" ]] && required_modules+=("$mod") done fi # Apply exclusion list from --exclude-modules local -a exclude_arr=() if [[ -n "$EXCLUDE_MODULES" ]]; then IFS=',' read -ra exclude_arr <<< "$EXCLUDE_MODULES" fi # Final module list (deduplicated, exclusions applied) local -A module_set=() local m for m in "${required_modules[@]}" "${optional_modules[@]}"; do local excluded=false local ex for ex in "${exclude_arr[@]}"; do [[ "$m" == "$ex" ]] && { excluded=true; break; } done [[ "$excluded" == "false" ]] && module_set[$m]=1 done # Store final module list FINAL_MODULES=() for m in "${!module_set[@]}"; do FINAL_MODULES+=("$m") done log "Module list (${#FINAL_MODULES[@]} modules): ${FINAL_MODULES[*]}" KERNEL_VERSION="$kernel_version" } # ============================================================================ # Copy kernel modules from rootfs # ============================================================================ copy_kernel_modules() { local initrd_root="$1" local rootfs_dir="${EXTRACT_DIR}/rootfs-extracted" [[ -z "$KERNEL_VERSION" ]] && { warn "No kernel version — skipping module copy"; return; } local modules_src="${rootfs_dir}/lib/modules/${KERNEL_VERSION}" local modules_dest="${initrd_root}/lib/modules/${KERNEL_VERSION}" # Check if modules exist in the rootfs if [[ ! -d "$modules_src" ]]; then warn "Kernel modules directory not found in rootfs: ${modules_src}" warn "Initrd will not include loadable kernel modules (built-in only)" return fi log "Copying kernel modules for ${KERNEL_VERSION}..." mkdir -p "$modules_dest" # Copy module files for selected modules local mod_count=0 local mod for mod in "${FINAL_MODULES[@]}"; do # Find the module .ko file (multiple possible paths and extensions) local -a ko_candidates mapfile -t ko_candidates < <( find "$modules_src" -name "${mod}.ko*" -type f 2>/dev/null || true ) local ko for ko in "${ko_candidates[@]}"; do local rel_path="${ko#${modules_src}/}" mkdir -p "${modules_dest}/$(dirname "$rel_path")" cp "$ko" "${modules_dest}/${rel_path}" mod_count=$((mod_count + 1)) [[ "$VERBOSE" == "true" ]] && log " Module: ${mod} → ${rel_path}" done done # Copy module metadata files local dep_file for dep_file in modules.dep modules.dep.bin modules.alias modules.alias.bin modules.symbols modules.symbols.bin; do [[ -f "${modules_src}/${dep_file}" ]] && cp "${modules_src}/${dep_file}" "${modules_dest}/" done # Generate a minimal modules.dep if not present if [[ ! -f "${modules_dest}/modules.dep" ]]; then (cd "$modules_dest" && find . -name '*.ko*' -print0 | xargs -0 -I{} echo "{}: " | sort > modules.dep) fi log "Copied ${mod_count} kernel modules" } # ============================================================================ # Generate /init for the initrd # ============================================================================ generate_init() { local initrd_root="$1" local init_file="${initrd_root}/init" log "Generating /init for ${INIT_STYLE} style..." case "$INIT_STYLE" in systemd) # systemd init: mount virtual filesystems, then exec systemd which # will start kata-agent.service via the kata-containers.target cat > "$init_file" << 'INITEOF' #!/bin/sh # Kata Containers initrd — systemd init style # Mounts virtual filesystems, then hands off to systemd which starts kata-agent. mount -t proc proc /proc mount -t sysfs sysfs /sys mount -t devtmpfs devtmpfs /dev mkdir -p /dev/pts /dev/shm /run mount -t devpts devpts /dev/pts mount -t tmpfs tmpfs /dev/shm mount -t tmpfs tmpfs /run # Load kernel modules from initrd [ -d /lib/modules ] && { KVER=$(ls /lib/modules/ 2>/dev/null | head -1) [ -n "$KVER" ] && depmod -a "$KVER" 2>/dev/null } exec /sbin/init INITEOF ;; openrc) # OpenRC init: mount virtual filesystems, then exec openrc-init cat > "$init_file" << 'INITEOF' #!/bin/sh # Kata Containers initrd — OpenRC init style # Mounts virtual filesystems, then hands off to openrc-init which starts kata-agent. mount -t proc proc /proc mount -t sysfs sysfs /sys mount -t devtmpfs devtmpfs /dev mkdir -p /dev/pts /dev/shm /run mount -t devpts devpts /dev/pts mount -t tmpfs tmpfs /dev/shm mount -t tmpfs tmpfs /run # Load kernel modules from initrd [ -d /lib/modules ] && { KVER=$(ls /lib/modules/ 2>/dev/null | head -1) [ -n "$KVER" ] && depmod -a "$KVER" 2>/dev/null } exec /sbin/openrc-init INITEOF ;; busybox|*) # Busybox /init: mount virtual filesystems and exec kata-agent as PID 1. # This is the minimal, fastest boot path — no service manager overhead. cat > "$init_file" << 'INITEOF' #!/bin/sh # Kata Containers initrd — busybox init style # Mounts virtual filesystems and execs kata-agent as PID 1. # This is the minimal boot path for fastest VM startup. mount -t proc proc /proc mount -t sysfs sysfs /sys mount -t devtmpfs devtmpfs /dev mkdir -p /dev/pts /dev/shm /run mount -t devpts devpts /dev/pts mount -t tmpfs tmpfs /dev/shm mount -t tmpfs tmpfs /run # Load kernel modules from initrd [ -d /lib/modules ] && { KVER=$(ls /lib/modules/ 2>/dev/null | head -1) [ -n "$KVER" ] && depmod -a "$KVER" 2>/dev/null } # Execute kata-agent as the final process (PID 1) exec /usr/bin/kata-agent INITEOF ;; esac chmod 755 "$init_file" log "Generated /init (${INIT_STYLE} style, $(wc -l < "$init_file") lines)" } # ============================================================================ # Helper: Find first existing file in rootfs # ============================================================================ first_in_rootfs() { local name="$1" local rootfs="$2" local -a paths=("bin/${name}" "sbin/${name}" "usr/bin/${name}" "usr/sbin/${name}" "usr/local/bin/${name}") local p for p in "${paths[@]}"; do if [[ -f "${rootfs}/${p}" ]]; then echo "${rootfs}/${p}" return 0 fi done return 1 } # ============================================================================ # Helper: Copy if source exists # ============================================================================ copy_if_exists() { local src="$1" local dest="$2" if [[ -e "$src" ]]; then if [[ -d "$src" ]]; then mkdir -p "$dest" cp -r "${src}/"* "$dest/" 2>/dev/null || true else mkdir -p "$(dirname "$dest")" cp "$src" "$dest" fi fi } # ============================================================================ # Copy required shared libraries # ============================================================================ copy_required_libs() { local initrd_root="$1" local rootfs_dir="$2" log "Resolving shared library dependencies..." # Collect all ELF binaries in the initrd local -a elf_files=() local elf while IFS= read -r -d '' elf; do elf_files+=("$elf") done < <(find "${initrd_root}" -type f -executable -print0 2>/dev/null | head -50) # Use ldd to resolve dependencies (if available) if command -v ldd &>/dev/null; then local -a lib_set=() local file for file in "${elf_files[@]}"; do local libs libs="$(ldd "$file" 2>/dev/null | grep -oP '/[^ ]+\.so[^ ]*' || true)" local lib for lib in $libs; do lib_set+=("$lib") done done # Deduplicate and copy local -a unique_libs=() local -A seen=() local lib for lib in "${lib_set[@]}"; do [[ -z "${seen[$lib]:-}" ]] && { seen[$lib]=1; unique_libs+=("$lib"); } done local copied=0 for lib in "${unique_libs[@]}"; do if [[ -f "$lib" ]]; then local dest_dir="${initrd_root}/$(dirname "$lib")" mkdir -p "$dest_dir" cp "$lib" "${initrd_root}/${lib}" 2>/dev/null && copied=$((copied + 1)) || true fi done log "Copied ${copied} shared libraries" # Copy the dynamic linker local -a linker_candidates=( "${rootfs_dir}/lib64/ld-linux-x86-64.so.2" "${rootfs_dir}/lib/ld-linux-x86-64.so.2" "${rootfs_dir}/lib64/ld-musl-x86_64.so.1" "${rootfs_dir}/lib/ld-musl-x86_64.so.1" ) local linker for linker in "${linker_candidates[@]}"; do if [[ -f "$linker" ]]; then mkdir -p "${initrd_root}/$(dirname "${linker#${rootfs_dir}}")" cp "$linker" "${initrd_root}/${linker#${rootfs_dir}}" log "Copied dynamic linker: ${linker#${rootfs_dir}}" break fi done else # No ldd — copy common library paths from rootfs warn "ldd not available — copying common library paths from rootfs" local -a lib_dirs=("lib" "lib64" "usr/lib" "usr/lib64") local ldir for ldir in "${lib_dirs[@]}"; do if [[ -d "${rootfs_dir}/${ldir}" ]]; then mkdir -p "${initrd_root}/${ldir}" cp "${rootfs_dir}/${ldir}/"*.so* "${initrd_root}/${ldir}/" 2>/dev/null || true fi done fi } # ============================================================================ # Strip unnecessary files from initrd # ============================================================================ strip_initrd() { local initrd_root="$1" log "Stripping unnecessary files from initrd..." # Stripping targets driven by flag-state table local -a strip_targets=() [[ "$STRIP_LOCALES" == "true" ]] && strip_targets+=("usr/share/locale") [[ "$STRIP_DOCS" == "true" ]] && strip_targets+=("usr/share/doc") [[ "$STRIP_MAN" == "true" ]] && strip_targets+=("usr/share/man") # Always strip these strip_targets+=("usr/share/info" "usr/share/groff" "usr/share/emacs") strip_targets+=("var/cache" "var/lib/apt" "var/lib/dpkg" "var/lib/pacman") local path_pattern for path_pattern in "${strip_targets[@]}"; do rm -rf "${initrd_root}/${path_pattern}" 2>/dev/null || true done # Remove Python cache files find "$initrd_root" -name '*.pyc' -delete 2>/dev/null || true find "$initrd_root" -name '__pycache__' -type d -exec rm -rf {} + 2>/dev/null || true # Remove .a and .la files (static libraries not needed at runtime) find "$initrd_root" -name '*.a' -delete 2>/dev/null || true find "$initrd_root" -name '*.la' -delete 2>/dev/null || true # Strip debug symbols from ELF binaries (reduces size significantly) if command -v strip &>/dev/null; then find "$initrd_root" -type f -executable -print0 2>/dev/null \ | xargs -0 strip --strip-unneeded 2>/dev/null || true fi log "Stripping complete: $(du -sh "$initrd_root" | cut -f1)" } # ============================================================================ # Build the initrd root filesystem # ============================================================================ build_initrd_root() { local initrd_root="${TMPDIR}/initrd-root" local rootfs_dir="${EXTRACT_DIR}/rootfs-extracted" log "Building initrd root filesystem..." mkdir -p "$initrd_root" # Essential directory structure local -a essential_dirs=( "bin" "sbin" "usr/bin" "usr/sbin" "usr/lib" "usr/lib64" "lib" "lib64" "etc" "proc" "sys" "dev" "run" "dev/pts" "dev/shm" "tmp" "root" "var/run" "var/log" "usr/share" "usr/libexec" ) local dir for dir in "${essential_dirs[@]}"; do mkdir -p "${initrd_root}/${dir}" done # ── Copy kata-agent ─────────────────────────────────────────────────── if [[ -n "$AGENT_PATH" && -f "$AGENT_PATH" ]]; then cp "$AGENT_PATH" "${initrd_root}/usr/bin/kata-agent" chmod 755 "${initrd_root}/usr/bin/kata-agent" log "Installed kata-agent from: ${AGENT_PATH}" else # Copy from rootfs local -a agent_src_candidates=( "${rootfs_dir}/usr/bin/kata-agent" "${rootfs_dir}/usr/local/bin/kata-agent" "${rootfs_dir}/usr/sbin/kata-agent" "${rootfs_dir}/sbin/kata-agent" ) local src for src in "${agent_src_candidates[@]}"; do if [[ -f "$src" ]]; then cp "$src" "${initrd_root}/usr/bin/kata-agent" chmod 755 "${initrd_root}/usr/bin/kata-agent" log "Installed kata-agent from rootfs: ${src#${rootfs_dir}}" break fi done fi [[ ! -f "${initrd_root}/usr/bin/kata-agent" ]] && die "kata-agent not found — cannot build initrd" # ── Copy essential binaries and libraries ────────────────────────────── # Minimal set needed for init and kata-agent operation local -a essential_bins=( "sh" "mount" "umount" "mkdir" "mknod" "ln" "cp" "mv" "rm" "ls" "cat" "echo" "sleep" "depmod" "modprobe" ) local bin for bin in "${essential_bins[@]}"; do local bin_src bin_src="$(first_in_rootfs "$bin" "$rootfs_dir")" || true if [[ -n "$bin_src" ]]; then cp "$bin_src" "${initrd_root}/bin/${bin}" 2>/dev/null || true fi done # Copy busybox if available (provides most essential binaries via symlinks) if [[ -f "${rootfs_dir}/bin/busybox" ]]; then cp "${rootfs_dir}/bin/busybox" "${initrd_root}/bin/busybox" chmod 755 "${initrd_root}/bin/busybox" # Create busybox symlinks for our essential bins that aren't already present local sym_bin for sym_bin in "${essential_bins[@]}"; do [[ ! -f "${initrd_root}/bin/${sym_bin}" ]] && \ ln -sf busybox "${initrd_root}/bin/${sym_bin}" 2>/dev/null || true done log "Installed busybox with symlinks" fi # ── Copy shared libraries ───────────────────────────────────────────── copy_required_libs "$initrd_root" "$rootfs_dir" # ── Copy kernel modules ─────────────────────────────────────────────── copy_kernel_modules "$initrd_root" # ── Copy init system files ──────────────────────────────────────────── case "$INIT_STYLE" in systemd) copy_if_exists "${rootfs_dir}/sbin/init" "${initrd_root}/sbin/init" copy_if_exists "${rootfs_dir}/lib/systemd" "${initrd_root}/lib/systemd" copy_if_exists "${rootfs_dir}/usr/lib/systemd" "${initrd_root}/usr/lib/systemd" copy_if_exists "${rootfs_dir}/etc/systemd" "${initrd_root}/etc/systemd" # Ensure kata-agent service unit exists mkdir -p "${initrd_root}/etc/systemd/system" mkdir -p "${initrd_root}/etc/systemd/target" if [[ -f "${rootfs_dir}/etc/systemd/system/kata-agent.service" ]]; then cp "${rootfs_dir}/etc/systemd/system/kata-agent.service" "${initrd_root}/etc/systemd/system/" else # Generate minimal service unit cat > "${initrd_root}/etc/systemd/system/kata-agent.service" << 'EOF' [Unit] Description=kata-agent Requires=kata-containers.target After=kata-containers.target [Service] ExecStart=/usr/bin/kata-agent Restart=always RestartSec=1 [Install] WantedBy=kata-containers.target EOF fi # Generate kata-containers.target cat > "${initrd_root}/etc/systemd/system/kata-containers.target" << 'EOF' [Unit] Description=kata containers target Requires=multi-user.target After=multi-user.target EOF ;; openrc) copy_if_exists "${rootfs_dir}/sbin/openrc-init" "${initrd_root}/sbin/openrc-init" copy_if_exists "${rootfs_dir}/etc/init.d" "${initrd_root}/etc/init.d" # Generate OpenRC init script for kata-agent mkdir -p "${initrd_root}/etc/init.d" cat > "${initrd_root}/etc/init.d/kata-agent" << 'EOF' #!/sbin/openrc-run description="kata-agent" command="/usr/bin/kata-agent" command_background=true pidfile="/run/kata-agent.pid" depend() { after localmount } EOF chmod 755 "${initrd_root}/etc/init.d/kata-agent" ;; esac # ── Generate /init ──────────────────────────────────────────────────── generate_init "$initrd_root" # ── Copy DNS config ─────────────────────────────────────────────────── if [[ -f "${rootfs_dir}/etc/resolv.conf" ]]; then cp "${rootfs_dir}/etc/resolv.conf" "${initrd_root}/etc/resolv.conf" else echo "nameserver 8.8.8.8" > "${initrd_root}/etc/resolv.conf" fi # ── Copy /etc/passwd and /etc/group (minimal) ──────────────────────── copy_if_exists "${rootfs_dir}/etc/passwd" "${initrd_root}/etc/passwd" copy_if_exists "${rootfs_dir}/etc/group" "${initrd_root}/etc/group" # Generate minimal passwd/group if not present [[ ! -f "${initrd_root}/etc/passwd" ]] && echo "root:x:0:0:root:/root:/bin/sh" > "${initrd_root}/etc/passwd" [[ ! -f "${initrd_root}/etc/group" ]] && echo "root:x:0:" > "${initrd_root}/etc/group" # ── Strip unnecessary files ─────────────────────────────────────────── strip_initrd "$initrd_root" log "Initrd root built: $(du -sh "$initrd_root" | cut -f1)" } # ============================================================================ # Create cpio archive # ============================================================================ create_cpio_archive() { local initrd_root="${TMPDIR}/initrd-root" local output_file="${OUTPUT:-kata-initrd${COMPRESS_EXT[$FORMAT]:-.img}}" log "Creating ${FORMAT} initrd image..." # Verify /init exists [[ -f "${initrd_root}/init" ]] || die "Missing /init in initrd root — cannot create archive" local compress_cmd="${COMPRESS_CMD[$FORMAT]:-cat}" # Create cpio archive with compression (cd "$initrd_root" && find . | cpio -o -H newc 2>/dev/null) | $compress_cmd > "$output_file" local size size="$(du -sh "$output_file" | cut -f1)" log "Initrd created: ${output_file} (${size})" # Validate the output validate_initrd "$output_file" OUTPUT_FILE="$output_file" } # ============================================================================ # Create cramfs image # ============================================================================ create_cramfs_image() { local initrd_root="${TMPDIR}/initrd-root" local output_file="${OUTPUT:-kata-initrd.cramfs}" log "Creating cramfs initrd image..." command -v mkcramfs &>/dev/null || die "mkcramfs not found — install cramfsprogs or util-linux" mkcramfs "$initrd_root" "$output_file" local size size="$(du -sh "$output_file" | cut -f1)" log "Cramfs initrd created: ${output_file} (${size})" OUTPUT_FILE="$output_file" } # ============================================================================ # Validate the generated initrd # ============================================================================ validate_initrd() { local initrd_file="$1" log "Validating generated initrd..." local -a checks=() # Check 1: File exists and is non-empty [[ -f "$initrd_file" && -s "$initrd_file" ]] && checks+=("PASS:file_exists") || checks+=("FAIL:file_exists") # Check 2: File format matches expected local file_type file_type="$(file -b "$initrd_file" 2>/dev/null || true)" case "$FORMAT" in cpio-gzip) echo "$file_type" | grep -qi "gzip" && checks+=("PASS:format_match") || checks+=("FAIL:format_match:${file_type}") ;; cpio-lz4) echo "$file_type" | grep -qi "lz4" && checks+=("PASS:format_match") || checks+=("WARN:format_match:file(1) may not identify LZ4") ;; cpio-xz) echo "$file_type" | grep -qi "xz" && checks+=("PASS:format_match") || checks+=("FAIL:format_match:${file_type}") ;; cpio-zstd) echo "$file_type" | grep -qi "zstd" && checks+=("PASS:format_match") || checks+=("WARN:format_match:file(1) may not identify ZSTD") ;; cramfs) echo "$file_type" | grep -qi "cramfs" && checks+=("PASS:format_match") || checks+=("FAIL:format_match:${file_type}") ;; esac # Check 3: Can list archive contents (cpio formats only) if [[ "$FORMAT" != "cramfs" ]]; then local decompress_cmd case "$FORMAT" in cpio-gzip) decompress_cmd="zcat" ;; cpio-lz4) decompress_cmd="lz4 -d -c" ;; cpio-xz) decompress_cmd="xzcat" ;; cpio-zstd) decompress_cmd="zstdcat" ;; esac local listing listing="$($decompress_cmd "$initrd_file" 2>/dev/null | cpio -t 2>/dev/null | head -50 || true)" if echo "$listing" | grep -q "init"; then checks+=("PASS:init_exists") else checks+=("FAIL:init_exists") fi if echo "$listing" | grep -q "kata-agent"; then checks+=("PASS:agent_exists") else checks+=("FAIL:agent_exists") fi fi # Check 4: Size is reasonable (< 512MB for initrd) local size_bytes size_bytes="$(stat --format="%s" "$initrd_file" 2>/dev/null || stat -f "%z" "$initrd_file" 2>/dev/null || echo 0)" local size_mb=$(( size_bytes / (1024 * 1024) )) if [[ $size_mb -lt 512 ]]; then checks+=("PASS:size_reasonable:${size_mb}MB") else checks+=("WARN:size_large:${size_mb}MB — consider stripping more content") fi # Report results local pass=0 fail=0 warn=0 local check for check in "${checks[@]}"; do local status="${check%%:*}" case "$status" in PASS) pass=$((pass + 1)) ;; FAIL) fail=$((fail + 1)); error " ${check}" ;; WARN) warn=$((warn + 1)); warn " ${check}" ;; esac done log "Validation: ${pass} passed, ${fail} failed, ${warn} warnings" [[ $fail -gt 0 ]] && die "Initrd validation failed — see errors above" } # ============================================================================ # Update QCrows bundle in-place # ============================================================================ update_qcrows_inplace() { [[ "$IN_PLACE" != "true" ]] && return local qcrows_file="$INPUT" local new_initrd="$OUTPUT_FILE" log "Updating QCrows bundle in-place with new initrd..." # Create a new bundle with the updated initrd local staging="${TMPDIR}/inplace-staging" mkdir -p "$staging" # Copy all existing bundle contents cp -r "${EXTRACT_DIR}/"* "$staging/" 2>/dev/null || true # Replace the initrd local initrd_name initrd_name="$(ls "${staging}"/initrd* 2>/dev/null | head -1 || true)" if [[ -n "$initrd_name" ]]; then rm -f "${staging}"/initrd.* cp "$new_initrd" "${staging}/initrd.img" else cp "$new_initrd" "${staging}/initrd.img" fi # Recompute hashes (cd "$staging" && find . -type f ! -name "hashes.sha256" -print0 | sort -z | xargs -0 sha256sum) \ > "${staging}/hashes.sha256" # Rebuild the archive local backup="${qcrows_file}.bak" cp "$qcrows_file" "$backup" log "Backup created: ${backup}" (cd "$staging" && tar czf - ./*) > "$qcrows_file" log "QCrows bundle updated: ${qcrows_file}" } # ============================================================================ # Print summary # ============================================================================ print_summary() { local initrd_file="${OUTPUT_FILE:-}" local size="" [[ -f "$initrd_file" ]] && size="$(du -sh "$initrd_file" | cut -f1)" echo "" echo -e "${GREEN}╔══════════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ Initrd regenerated successfully ║${NC}" echo -e "${GREEN}╚══════════════════════════════════════════════════════╝${NC}" echo "" echo -e " Output: ${CYAN}${initrd_file}${NC}" echo -e " Size: ${CYAN}${size}${NC}" echo -e " Format: ${CYAN}${FORMAT}${NC}" echo -e " VMM: ${CYAN}${VMM}${NC}" echo -e " Init style: ${CYAN}${INIT_STYLE}${NC}" echo -e " Kernel: ${CYAN}${KERNEL_VERSION:-unknown}${NC}" echo -e " Modules: ${CYAN}${#FINAL_MODULES[@]} included${NC}" echo -e " In-place: ${CYAN}${IN_PLACE}${NC}" echo "" echo -e " Next steps:" echo -e " ${CYAN}qcrows-verify ${INPUT}${NC}" echo -e " ${CYAN}qcrows-export --format qcow2 ${INPUT} -o disk.qcow2${NC}" echo -e " ${CYAN}qcrows-export --format pxe ${INPUT} --server-ip 192.168.1.1${NC}" echo "" } # ============================================================================ # Entry Point # ============================================================================ EXTRACT_DIR="" KERNEL_VERSION="" OUTPUT_FILE="" declare -a FINAL_MODULES=() parse_args "$@" validate_inputs # ── 1. Setup temp directory ───────────────────────────────────────────── TMPDIR="$(mktemp -d /tmp/qcrows-initrd-regen-XXXXXX)" EXTRACT_DIR="${TMPDIR}/bundle" log "Working directory: ${TMPDIR}" # ── 2. Extract QCrows bundle ──────────────────────────────────────────── extract_qcrows "$INPUT" "$EXTRACT_DIR" # ── 3. Detect environment ─────────────────────────────────────────────── detect_vmm detect_init_style detect_compression # ── 4. Extract rootfs for content access ──────────────────────────────── extract_rootfs # ── 5. Discover kata-agent ────────────────────────────────────────────── discover_agent # ── 6. Resolve kernel modules ─────────────────────────────────────────── resolve_kernel_modules # ── 7. Build initrd root filesystem ───────────────────────────────────── build_initrd_root # ── 8. Create the initrd image ────────────────────────────────────────── if [[ "$FORMAT" == "cramfs" ]]; then create_cramfs_image else create_cpio_archive fi # ── 9. Update bundle in-place if requested ────────────────────────────── update_qcrows_inplace # ── 10. Print summary ─────────────────────────────────────────────────── print_summary