Files
Joachim Wiberg 17a1b80b83 Namespace Infix Kconfig options under IX_
Infix defines its own Kconfig options with unprefixed names (IMAGE_*,
QEMU_*, TRUSTED_KEYS*) and an INFIX_ prefix.  Unprefixed names risk
clashing with Buildroot and with other br2-externals/spins that source
our tree.

Rename all 86 options to a common IX_ prefix, collapsing the existing
INFIX_ ones, e.g. INFIX_IMAGE_ID becomes IX_IMAGE_ID.  The os-release
INFIX_DESC field is a runtime interface, not a Kconfig option, and is
kept; the qemu Config.in generator's @ARCH@ symbol is updated to match.

Closes #1305

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-06 05:28:11 +02:00

699 lines
21 KiB
Bash
Executable File

#!/bin/sh
# Unified SD card image creation script for all boards
# Consolidates logic from board-specific mkimage.sh scripts
set -e
STANDALONE=""
if [ -z "$BR2_EXTERNAL_INFIX_PATH" ]; then
SCRIPT_DIR=$(dirname "$0")
BR2_EXTERNAL_INFIX_PATH=$(cd "$SCRIPT_DIR/.." && pwd)
fi
usage()
{
cat <<EOF
Create SD card/eMMC image from build artifacts for any supported board.
Usage:
$0 [OPTIONS] <board-name>
Options:
-b boot-dir Path to bootloader build directory (default: same as -r, or O= or output/)
-B Boot-only image (no rootfs, for bootloader testing)
-d Download bootloader files from latest-boot release
-f Force re-download of bootloader even if cached
-h This help text
-l List available boards
-o Override auto-detection of genimage.sh, use host installed version
-r root-dir Path to rootfs build directory or rootfs.squashfs file (default: O= or output/)
-t target Image target type: sdcard or emmc (default: sdcard)
Arguments:
board-name Board identifier (must come after options)
Description:
When called from Buildroot (no options), uses environment variables:
BINARIES_DIR, BUILD_DIR, BR2_EXTERNAL_INFIX_PATH, RELEASE, IX_ID
When called with -b/-r options, enters standalone mode and combines artifacts
from separate boot and rootfs sources. Useful for CI or manual image creation.
Output:
Image saved to \$BINARIES_DIR/*-sdcard.img or *-emmc.img
Examples:
# From Buildroot build:
$0 raspberrypi-rpi64
# Standalone with separate boot/rootfs builds:
$0 -b x-boot -r output raspberrypi-rpi64
# Rootfs-only (no separate bootloader build, e.g. EspressoBIN):
$0 -r x-aarch64 marvell-espressobin
# With downloaded rootfs and bootloader:
$0 -d -r ~/Downloads/rootfs.squashfs friendlyarm-nanopi-r2s
# Download bootloader and compose with Linux image in output directory:
$0 -od bananapi-bpi-r3
# Create eMMC image instead of SD card:
$0 -t emmc bananapi-bpi-r3
# Create boot-only image for bootloader testing:
$0 -B -b build-boot microchip-sama7g54-ek
EOF
}
log()
{
printf '\033[7m>>> %s\033[0m\n' "$*"
}
err()
{
echo "Error: $*" >&2
}
die()
{
err "$*"
exit 1
}
# List all supported boards by scanning for genimage.cfg.in files
list_boards()
{
script_dir=$(dirname "$0")
board_base=$(cd "$script_dir/../board" 2>/dev/null && pwd)
if [ -z "$board_base" ] || [ ! -d "$board_base" ]; then
echo "Error: Could not find board directory" >&2
return 1
fi
echo "Available boards:"
find "$board_base" -name "genimage.cfg.in" -type f 2>/dev/null | \
grep -v '/common/' | \
sed 's|.*/board/\([^/]*\)/\([^/]*\)/.*| \2 (\1)|' | \
sort -u
}
# Run genimage directly (fallback when Buildroot wrapper not available)
run_genimage()
{
genimage_cfg="$1"
genimage_tmp="${BUILD_DIR}/genimage.tmp"
rootpath_tmp=$(mktemp -d)
trap 'rm -rf "$rootpath_tmp"' EXIT
rm -rf "$genimage_tmp"
genimage \
--rootpath "$rootpath_tmp" \
--tmppath "$genimage_tmp" \
--inputpath "${BINARIES_DIR}" \
--outputpath "${BINARIES_DIR}" \
--config "$genimage_cfg"
if command -v bmaptool >/dev/null 2>&1; then
for img in "${BINARIES_DIR}"/*-sdcard*.img "${BINARIES_DIR}"/*-emmc*.img; do
[ -f "$img" ] || continue
log "Generating block map for $(basename "$img")..."
bmaptool create -o "${img}.bmap" "$img"
done
fi
}
# Validate board argument and find board directory
# Sets BOARD and BOARD_DIR globals or exits with error
validate_board()
{
BOARD="$1"
if [ -z "$BOARD" ]; then
err "Board name required. Use -h for help."
return 1
fi
board_underscore=$(echo "$BOARD" | tr '-' '_')
for arch in arm aarch64 x86_64 riscv64; do
for variant in "$BOARD" "$board_underscore"; do
candidate="$BR2_EXTERNAL_INFIX_PATH/board/$arch/$variant"
if [ -d "$candidate" ]; then
BOARD_DIR="$candidate"
return 0
fi
done
done
err "Board directory not found for: $BOARD"
return 1
}
# Find build directory by checking O= or output/
find_build_dir()
{
# Check O= environment variable first
if [ -n "$O" ] && [ -f "$O/.config" ]; then
echo "$O"
return 0
fi
# Check output/ directory
if [ -f "output/.config" ]; then
echo "output"
return 0
fi
return 1
}
# Map board name to bootloader identifier
# Returns bootloader name used in artifact naming
get_bootloader_name()
{
board="$1"
target="$2"
case "$board" in
raspberrypi-rpi2)
echo "rpi2_boot"
;;
raspberrypi-rpi64)
echo "rpi64_boot"
;;
acer-connect-vero-w6m)
echo "bpi_r3_emmc_boot"
;;
bananapi-bpi-r3)
if [ "$target" = "emmc" ]; then
echo "bpi_r3_emmc_boot"
else
echo "bpi_r3_sd_boot"
fi
;;
bananapi-bpi-r4)
if [ "$target" = "emmc" ]; then
echo "bpi_r4_emmc_boot"
else
echo "bpi_r4_sd_boot"
fi
;;
bananapi-bpi-r64)
if [ "$target" = "emmc" ]; then
echo "bpi_r64_emmc_boot"
else
echo "bpi_r64_sd_boot"
fi
;;
friendlyarm-nanopi-r2s)
echo "nanopi_r2s_boot"
;;
microchip-sama7g54-ek)
if [ "$target" = "emmc" ]; then
echo "sama7g54_ek_emmc_boot"
else
echo "sama7g54_ek_sd_boot"
fi
;;
*)
err "Unknown bootloader for board: $board"
return 1
;;
esac
}
# Download and extract bootloader from latest-boot release
# Downloads to dl/bootloader/ cache and extracts to temporary build directory
# Returns the temporary directory path in SDCARD_TEMP_DIR variable
download_bootloader()
{
board="$1"
build_dir="$2"
target="$3"
bootloader=$(get_bootloader_name "$board" "$target") || return 1
if ! command -v gh >/dev/null 2>&1; then
die "gh CLI not found. Install it or build bootloader locally."
fi
# Set up download cache directory
dl_dir="${BR2_EXTERNAL_INFIX_PATH}/dl/bootloader"
mkdir -p "$dl_dir"
# Convert underscores to dashes for filename pattern matching
bootloader_pattern=$(echo "$bootloader" | tr '_' '-')
# Find or download bootloader tarball
tarball=$(ls "$dl_dir"/${bootloader_pattern}*.tar.gz 2>/dev/null | head -n1)
if [ -z "$tarball" ] || [ -n "$FORCE_DOWNLOAD" ]; then
if [ -n "$FORCE_DOWNLOAD" ] && [ -n "$tarball" ]; then
log "Force re-downloading bootloader..."
rm -f "$tarball" "$tarball.sha256"
else
log "Downloading bootloader $bootloader from latest-boot release..."
fi
if ! gh release download latest-boot \
--repo kernelkit/infix \
--pattern "*${bootloader_pattern}*.tar.gz" \
--dir "$dl_dir"; then
die "Failed downloading bootloader from latest-boot release. Check gh authentication and network connectivity."
fi
tarball=$(ls "$dl_dir"/${bootloader_pattern}*.tar.gz 2>/dev/null | head -n1)
[ -n "$tarball" ] || die "Downloaded tarball not found in $dl_dir"
else
log "Using cached bootloader: $(basename "$tarball")"
fi
# Create temporary directory for SD card composition
SDCARD_TEMP_DIR="${build_dir}/sdcard-${board}-$$"
mkdir -p "$SDCARD_TEMP_DIR"
log "Extracting bootloader to $SDCARD_TEMP_DIR..."
tar -xzf "$tarball" --strip-components=1 -C "$SDCARD_TEMP_DIR/"
log "Bootloader ready in temporary directory"
}
# Build EN8811H firmware partition image as an ext4 filesystem containing
# EthMD32.dm.bin and EthMD32.DSP.bin. Returns 0 when created, 1 when source
# files are missing.
prepare_en8811h_fw_image()
{
src_dir="$1"
out_img="$2"
dm_fw="${src_dir}/EthMD32.dm.bin"
dsp_fw="${src_dir}/EthMD32.DSP.bin"
fw_staging=""
[ -f "$dm_fw" ] && [ -f "$dsp_fw" ] || return 1
fw_staging=$(mktemp -d)
cp "$dm_fw" "$fw_staging/EthMD32.dm.bin"
cp "$dsp_fw" "$fw_staging/EthMD32.DSP.bin"
rm -f "$out_img"
truncate -s 2M "$out_img"
if command -v mke2fs >/dev/null 2>&1; then
mke2fs -q -t ext4 -L en8811h_fw -d "$fw_staging" "$out_img"
elif command -v mkfs.ext4 >/dev/null 2>&1; then
mkfs.ext4 -q -L en8811h_fw -d "$fw_staging" "$out_img"
else
rm -rf "$fw_staging"
die "mke2fs/mkfs.ext4 not found, cannot create en8811h-fw.bin"
fi
rm -rf "$fw_staging"
return 0
}
maybe_prepare_en8811h_fw_image()
{
out_img="${BINARIES_DIR}/en8811h-fw.bin"
[ -f "$out_img" ] && return 0
prepare_en8811h_fw_image "${ROOT_DIR}/images/airoha" "$out_img" || \
prepare_en8811h_fw_image "${ROOT_DIR}/airoha" "$out_img" || \
prepare_en8811h_fw_image "${BINARIES_DIR}/airoha" "$out_img" || return 0
}
# Discover boot files for Raspberry Pi boot partition
# Scans rpi-firmware directory and builds file list for genimage
discover_rpi_boot_files()
{
binaries_dir="$1"
files=""
# Move any .dtbo overlays to proper location
find "${binaries_dir}" -type f -name '*.dtbo' ! -path "${binaries_dir}/rpi-firmware/overlays/*" -exec \
mv '{}' "${binaries_dir}/rpi-firmware/overlays/" \; 2>/dev/null || true
# Scan rpi-firmware directory for boot files
for f in "${binaries_dir}"/rpi-firmware/*; do
[ -e "$f" ] || continue
case "$f" in
*~ | *.bak)
continue
;;
esac
# Skip if already in list
echo "${files}" | grep -q "$(basename "$f")" && continue
files="${files}\t\t\t\"${f#"${binaries_dir}/"}\",\n"
done
# Add splash screen if it exists
if [ -f "${binaries_dir}/splash.bmp" ]; then
files="${files}\t\t\t\"splash.bmp\",\n"
fi
# Add kernel (extract name from config.txt)
if [ -f "${binaries_dir}/rpi-firmware/config.txt" ]; then
kernel=$(sed -n 's/^kernel=//p' "${binaries_dir}/rpi-firmware/config.txt")
files="${files}\t\t\t\"${kernel}\""
fi
echo "$files"
}
# Filter genimage.cfg.in for boot-only image
# Removes rootfs-related blocks: image cfg.ext4, image var.ext4,
# and partitions: aux, primary, secondary, cfg, var
filter_bootonly_genimage()
{
awk '
BEGIN { skip=0; depth=0 }
/^image cfg\.ext4/ || /^image var\.ext4/ {
skip=1; depth=1; next
}
/^[[:space:]]*partition (aux|primary|secondary|cfg|var)[[:space:]]*\{/ {
skip=1; depth=1; next
}
skip==1 {
# Count braces on this line to track nesting depth
n = gsub(/{/, "{")
m = gsub(/}/, "}")
depth = depth + n - m
if (depth <= 0) { skip=0; depth=0 }
next
}
{ print }
'
}
while getopts "hldfoBb:r:t:" opt; do
case $opt in
b)
BOOT_DIR="$OPTARG"
STANDALONE=1
;;
B)
BOOT_ONLY=1
STANDALONE=1
;;
d)
DOWNLOAD_BOOT=1
STANDALONE=1
;;
f)
FORCE_DOWNLOAD=1
;;
h)
usage
exit 0
;;
l)
list_boards
exit 0
;;
o)
OVERRIDE=1
;;
r)
ROOT_DIR="$OPTARG"
STANDALONE=1
;;
t)
TARGET="$OPTARG"
;;
*)
usage
exit 1
;;
esac
done
shift $((OPTIND - 1))
if ! validate_board "$1"; then
usage
exit 1
fi
# Validate and set default target
: "${TARGET:=sdcard}"
case "$TARGET" in
sd|sdcard)
TARGET="sdcard"
;;
emmc)
TARGET="emmc"
;;
*)
err "Invalid target: $TARGET. Must be 'sdcard' or 'emmc'"
usage
exit 1
;;
esac
# Standalone mode: set up environment from build directories
if [ -n "$STANDALONE" ]; then
# In download mode without explicit dirs, default to same location for both
if [ -n "$DOWNLOAD_BOOT" ]; then
default_dir=$(find_build_dir) || die "Could not find build directory. Set O= or use -b/-r option"
: "${BOOT_DIR:=$default_dir}"
: "${ROOT_DIR:=$default_dir}"
elif [ -n "$BOOT_ONLY" ]; then
# Boot-only mode: only need bootloader directory
if [ -z "$BOOT_DIR" ]; then
BOOT_DIR=$(find_build_dir) || die "Could not find boot directory. Use -b option"
fi
else
if [ -z "$ROOT_DIR" ]; then
ROOT_DIR=$(find_build_dir) || die "Could not find rootfs directory. Set O= or use -r option"
fi
if [ -z "$BOOT_DIR" ]; then
# For boards without a separate bootloader build (e.g. EspressoBIN,
# where U-Boot lives in SPI NOR), -r alone is sufficient: default
# the boot directory to the rootfs directory.
if [ -n "$ROOT_DIR" ]; then
BOOT_DIR="$ROOT_DIR"
else
BOOT_DIR=$(find_build_dir) || die "Could not find boot directory. Use -b option"
fi
fi
fi
# Set up environment variables, some required by genimage.sh
export BINARIES_DIR="$BOOT_DIR/images"
export BUILD_DIR="$BOOT_DIR/build"
export BR2_EXTERNAL_INFIX_PATH
export RELEASE="${RELEASE:-""}"
export IX_ID="${IX_ID:-"infix"}"
# Add host tools to PATH (for genimage, bmaptool, etc.)
for dir in "$BOOT_DIR" "$ROOT_DIR"; do
[ -n "$dir" ] || continue
if [ -d "$dir/host/bin" ]; then
export PATH="$dir/host/bin:$PATH"
break
fi
done
# Copy rootfs and partition images to BINARIES_DIR (skip in boot-only mode)
mkdir -p "$BINARIES_DIR"
if [ -z "$BOOT_ONLY" ]; then
# Normalize paths for comparison
boot_images=$(cd "$BOOT_DIR" && pwd)/images
root_images=""
if [ -f "$ROOT_DIR" ]; then
# Direct path to rootfs.squashfs file
log "Copying rootfs from $ROOT_DIR to $BINARIES_DIR/rootfs.squashfs"
cp "$ROOT_DIR" "$BINARIES_DIR/rootfs.squashfs"
elif [ -f "$ROOT_DIR/images/rootfs.squashfs" ]; then
root_images=$(cd "$ROOT_DIR" && pwd)/images
# Only copy if different directories
if [ "$boot_images" != "$root_images" ]; then
# Build directory with images/ - copy rootfs and partition images
log "Copying artifacts from $ROOT_DIR/images/ to $BINARIES_DIR/"
cp "$ROOT_DIR/images/rootfs.squashfs" "$BINARIES_DIR/"
# Copy partition images and rootfs variants if they exist
for img in aux.ext4 cfg.ext4 var.ext4 rootfs.ext2; do
if [ -f "$ROOT_DIR/images/$img" ]; then
cp "$ROOT_DIR/images/$img" "$BINARIES_DIR/"
fi
done
else
log "Rootfs already in place at $BINARIES_DIR/"
fi
elif [ -f "$ROOT_DIR/rootfs.squashfs" ]; then
# Directory directly containing rootfs.squashfs
log "Copying rootfs from $ROOT_DIR/rootfs.squashfs"
cp "$ROOT_DIR/rootfs.squashfs" "$BINARIES_DIR/"
# Copy partition images and rootfs variants if they exist
for img in aux.ext4 cfg.ext4 var.ext4 rootfs.ext2; do
if [ -f "$ROOT_DIR/$img" ]; then
cp "$ROOT_DIR/$img" "$BINARIES_DIR/"
fi
done
else
die "Could not find rootfs.squashfs in $ROOT_DIR"
fi
fi
else
# Export for Buildroot genimage.sh wrapper
export BINARIES_DIR
export BUILD_DIR
fi
# Validate required environment variables
: "${BINARIES_DIR:?'not set'}"
: "${BUILD_DIR:?'not set'}"
: "${BR2_EXTERNAL_INFIX_PATH:?'not set'}"
# Set defaults for optional variables
: "${RELEASE:=""}"
: "${IX_ID:="infix"}"
# Download bootloader if requested
if [ -n "$DOWNLOAD_BOOT" ]; then
# Save original output location
ORIGINAL_BINARIES_DIR="$BINARIES_DIR"
download_bootloader "$BOARD" "$BUILD_DIR" "$TARGET"
# Now use the temporary directory for composition
BINARIES_DIR="$SDCARD_TEMP_DIR"
# Link rootfs and partition images to temp directory (skip in boot-only mode)
if [ -z "$BOOT_ONLY" ]; then
log "Linking rootfs files to $BINARIES_DIR..."
if [ -f "$ROOT_DIR" ]; then
# Direct path to rootfs.squashfs file
ln -sf "$(realpath "$ROOT_DIR")" "$BINARIES_DIR/rootfs.squashfs"
elif [ -f "$ROOT_DIR/images/rootfs.squashfs" ]; then
ln -sf "$(realpath "$ROOT_DIR/images/rootfs.squashfs")" "$BINARIES_DIR/rootfs.squashfs"
# Link partition images and rootfs variants if they exist
for img in aux.ext4 cfg.ext4 var.ext4 rootfs.ext2; do
if [ -f "$ROOT_DIR/images/$img" ]; then
ln -sf "$(realpath "$ROOT_DIR/images/$img")" "$BINARIES_DIR/$img"
fi
done
elif [ -f "$ROOT_DIR/rootfs.squashfs" ]; then
ln -sf "$(realpath "$ROOT_DIR/rootfs.squashfs")" "$BINARIES_DIR/rootfs.squashfs"
# Link partition images and rootfs variants if they exist
for img in aux.ext4 cfg.ext4 var.ext4 rootfs.ext2; do
if [ -f "$ROOT_DIR/$img" ]; then
ln -sf "$(realpath "$ROOT_DIR/$img")" "$BINARIES_DIR/$img"
fi
done
else
die "Could not find rootfs.squashfs in $ROOT_DIR"
fi
fi
fi
if [ "$BOARD" = "bananapi-bpi-r3" ]; then
maybe_prepare_en8811h_fw_image
[ -f "${BINARIES_DIR}/en8811h-fw.bin" ] || die "Missing EN8811H firmware image: ${BINARIES_DIR}/en8811h-fw.bin"
fi
# Template expansion
log "Generating genimage configuration for $BOARD..."
GENIMAGE_CFG="${BUILD_DIR}/genimage.cfg"
GENIMAGE_TEMPLATE="$BOARD_DIR/genimage.cfg.in"
[ -f "$GENIMAGE_TEMPLATE" ] || die "$(basename "$GENIMAGE_TEMPLATE") not found in $BOARD_DIR"
# Check if board needs special boot file discovery (Raspberry Pi)
if { [ "$BOARD" = "raspberrypi-rpi2" ] || [ "$BOARD" = "raspberrypi-rpi64" ]; } && grep -q '#BOOT_FILES#' "$GENIMAGE_TEMPLATE"; then
log "Discovering Raspberry Pi boot files..."
BOOT_FILES=$(discover_rpi_boot_files "$BINARIES_DIR")
# Create temp file with interpreted escape sequences
bootfiles_tmp="${BUILD_DIR}/bootfiles.tmp"
printf '%b' "$BOOT_FILES" > "$bootfiles_tmp"
# Use sed to insert content and delete placeholder
sed -e "/#BOOT_FILES#/r $bootfiles_tmp" -e "/#BOOT_FILES#/d" "$GENIMAGE_TEMPLATE" > "${GENIMAGE_CFG}.tmp"
rm -f "$bootfiles_tmp"
GENIMAGE_TEMPLATE="${GENIMAGE_CFG}.tmp"
fi
# Filter for boot-only mode if requested
if [ -n "$BOOT_ONLY" ]; then
log "Filtering genimage config for boot-only image..."
filter_bootonly_genimage < "$GENIMAGE_TEMPLATE" > "${GENIMAGE_CFG}.filtered"
GENIMAGE_TEMPLATE="${GENIMAGE_CFG}.filtered"
fi
# Expand template variables
# For boot-only mode, append "-boot" to the target name in the image filename
if [ -n "$BOOT_ONLY" ]; then
sed "s|#VERSION#|${RELEASE}|" "$GENIMAGE_TEMPLATE" | \
sed "s|#IX_ID#|${IX_ID}|" | \
sed "s|#TARGET#|${TARGET}|" | \
sed "s|-${TARGET}\.img|-${TARGET}-boot.img|" > "$GENIMAGE_CFG"
else
sed "s|#VERSION#|${RELEASE}|" "$GENIMAGE_TEMPLATE" | \
sed "s|#IX_ID#|${IX_ID}|" | \
sed "s|#TARGET#|${TARGET}|" > "$GENIMAGE_CFG"
fi
# Clean up temp files if created
#rm -f "${GENIMAGE_CFG}.tmp" "${GENIMAGE_CFG}.filtered"
# Find and set up for calling genimage/genimage.sh
if [ -z "$BR2_CONFIG" ]; then
BR2_CONFIG="/dev/null"
if [ -f "${BUILD_DIR}/../.config" ]; then
BR2_CONFIG="$(realpath "${BUILD_DIR}/../.config")"
fi
export BR2_CONFIG
if [ -f "$BR2_EXTERNAL_INFIX_PATH/../buildroot/support/scripts/genimage.sh" ]; then
GENIMAGE_WRAPPER="$(realpath "$BR2_EXTERNAL_INFIX_PATH/../buildroot/support/scripts/genimage.sh")"
fi
else
GENIMAGE_WRAPPER="support/scripts/genimage.sh"
fi
if [ -z "$OVERRIDE" ] && command -v "$GENIMAGE_WRAPPER" >/dev/null 2>&1; then
log "Creating $TARGET image using Buildroot $(basename "$GENIMAGE_WRAPPER") ..."
"$GENIMAGE_WRAPPER" -c "$GENIMAGE_CFG"
else
log "Creating $TARGET image ..."
run_genimage "$GENIMAGE_CFG"
fi
# Post-processing: move images and cleanup if using download mode
if [ -n "$DOWNLOAD_BOOT" ]; then
log "Moving $TARGET images to $ORIGINAL_BINARIES_DIR..."
mkdir -p "$ORIGINAL_BINARIES_DIR"
for img in "${BINARIES_DIR}"/*-sdcard*.img* "${BINARIES_DIR}"/*-emmc*.img*; do
if [ -f "$img" ]; then
mv "$img" "$ORIGINAL_BINARIES_DIR/"
log " $(basename "$img")"
fi
done
log "Cleaning up temporary directory..."
rm -rf "$SDCARD_TEMP_DIR"
# Update BINARIES_DIR for final output message
BINARIES_DIR="$ORIGINAL_BINARIES_DIR"
fi
if [ -n "$BOOT_ONLY" ]; then
log "$TARGET boot-only image created successfully:"
else
log "$TARGET image created successfully:"
fi
for img in "${BINARIES_DIR}"/*-sdcard*.img* "${BINARIES_DIR}"/*-emmc*.img*; do
if [ -f "$img" ]; then
if [ -n "$STANDALONE" ]; then
# Show relative path in standalone mode
rel_path=$(realpath --relative-to="$PWD" "$img" 2>/dev/null || echo "$img")
echo " $rel_path"
else
echo " $(basename "$img")"
fi
fi
done