Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions meta-balena-common/classes/image-balena.bbclass
Original file line number Diff line number Diff line change
Expand Up @@ -454,24 +454,20 @@ def get_dir_size_kb(path):
python do_image_size_check() {
imgfile = d.getVar("BALENA_DOCKER_IMG")
ext4file = d.getVar("BALENA_ROOTB_FS")
rfs_alignment = d.getVar("IMAGE_ROOTFS_ALIGNMENT")
rfs_size = int(get_rootfs_size(d))
image_size_aligned = int(disk_aligned(d, os.stat(imgfile).st_size / 1024))
available = int(disk_aligned(d, available_space(ext4file, d)))

# Calculate /boot directory size - copied to volume during HUP
boot_dir = os.path.join(d.getVar("IMAGE_ROOTFS"), "boot")
boot_size_kb = get_dir_size_kb(boot_dir)
boot_size_aligned = int(disk_aligned(d, boot_size_kb))
boot_imgfile = d.getVar("BALENA_BOOT_DOCKER_IMG")
boot_size_aligned = int(disk_aligned(d, os.stat(boot_imgfile).st_size / 1024))

# Total space required = docker image + boot volume
# Total space required = main hostapp image + boot image staged on root slot
total_required = image_size_aligned + boot_size_aligned

if total_required > available:
bb.fatal("HUP size check failed: docker image (%d KiB) + /boot volume (%d KiB) = %d KiB exceeds available space %d KiB"
bb.fatal("HUP size check failed: hostapp image (%d KiB) + boot image (%d KiB) = %d KiB exceeds available space %d KiB"
% (image_size_aligned, boot_size_aligned, total_required, available))

bb.debug(1, 'HUP size check: docker image %d KiB, /boot volume %d KiB, total %d KiB, available %d KiB'
bb.debug(1, 'HUP size check: hostapp image %d KiB, boot image %d KiB, total %d KiB, available %d KiB'
% (image_size_aligned, boot_size_aligned, total_required, available))
}

Expand Down
20 changes: 17 additions & 3 deletions meta-balena-common/classes/image_types_balena.bbclass
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,14 @@ python() {
d.setVar('BALENA_RAW_IMG', '${IMGDEPLOYDIR}/${IMAGE_NAME}.balenaos-img')
d.setVar('BALENA_RAW_BMAP', '${IMGDEPLOYDIR}/${IMAGE_NAME}.bmap')
d.setVar('BALENA_DOCKER_IMG', '${IMGDEPLOYDIR}/${IMAGE_NAME}.docker')
d.setVar('BALENA_BOOT_DOCKER_IMG', '${IMGDEPLOYDIR}/${IMAGE_NAME}-boot.docker')
d.setVar('BALENA_HOSTAPP_IMG', '${IMGDEPLOYDIR}/${IMAGE_NAME}.${BALENA_ROOT_FSTYPE}')
else:
d.setVar('BALENA_ROOT_FS', '${DEPLOY_DIR_IMAGE}/${IMAGE_LINK_NAME}.${BALENA_ROOT_FSTYPE}')
d.setVar('BALENA_RAW_IMG', '${DEPLOY_DIR_IMAGE}/${IMAGE_NAME}.balenaos-img')
d.setVar('BALENA_RAW_BMAP', '${DEPLOY_DIR_IMAGE}/${IMAGE_NAME}.bmap')
d.setVar('BALENA_DOCKER_IMG', '${DEPLOY_DIR_IMAGE}/${IMAGE_NAME}.docker')
d.setVar('BALENA_BOOT_DOCKER_IMG', '${DEPLOY_DIR_IMAGE}/${IMAGE_NAME}-boot.docker')
d.setVar('BALENA_HOSTAPP_IMG', '${DEPLOY_DIR_IMAGE}/${IMAGE_NAME}.${BALENA_ROOT_FSTYPE}')

d.setVar('BALENA_IMAGE_BOOTLOADER_DEPLOY_TASK', ' '.join(bootloader + ':do_populate_sysroot' for bootloader in d.getVar("BALENA_IMAGE_BOOTLOADER", True).split()))
Expand Down Expand Up @@ -386,19 +388,31 @@ do_rootfs[vardeps] += "BALENA_BOOT_PARTITION_FILES"

# XXX(petrosagg): This should be eventually implemented using a docker-native daemon
IMAGE_CMD:docker () {
DOCKER_IMAGE=$(${IMAGE_CMD_TAR} -cv -C ${IMAGE_ROOTFS} . | DOCKER_API_VERSION=${BALENA_API_VERSION} docker import -)
DOCKER_IMAGE=$(${IMAGE_CMD_TAR} --exclude=./boot -cv -C ${IMAGE_ROOTFS} . | DOCKER_API_VERSION=${BALENA_API_VERSION} docker import -)
DOCKER_API_VERSION=${BALENA_API_VERSION} docker save ${DOCKER_IMAGE} > ${BALENA_DOCKER_IMG}
}

IMAGE_TYPEDEP:hostapp-ext4 = "docker"
IMAGE_CMD:boot-docker () {
if [ ! -d "${IMAGE_ROOTFS}/boot" ] || [ -z "$(ls -A "${IMAGE_ROOTFS}/boot" 2>/dev/null)" ]; then
bbfatal "Boot image /boot is empty"
fi
BOOT_STAGE="${WORKDIR}/boot-image-root"
rm -rf "${BOOT_STAGE}"
mkdir -p "${BOOT_STAGE}/boot"
cp -a "${IMAGE_ROOTFS}/boot/." "${BOOT_STAGE}/boot/"
DOCKER_IMAGE=$(${IMAGE_CMD_TAR} -cv -C "${BOOT_STAGE}" . | DOCKER_API_VERSION=${BALENA_API_VERSION} docker import -)
DOCKER_API_VERSION=${BALENA_API_VERSION} docker save ${DOCKER_IMAGE} > ${BALENA_BOOT_DOCKER_IMG}
}

IMAGE_TYPEDEP:hostapp-ext4 = "docker boot-docker"

do_image_hostapp_ext4[depends] = " \
mkfs-hostapp-native:do_populate_sysroot \
"

IMAGE_CMD:hostapp-ext4 () {
truncate -s "$(expr ${ROOTFS_SIZE} \* 1024)" "${BALENA_HOSTAPP_IMG}"
mkfs.hostapp -t "${TMPDIR}" -s "${STAGING_DIR_NATIVE}" -i ${BALENA_DOCKER_IMG} -o ${BALENA_HOSTAPP_IMG}
mkfs.hostapp -t "${TMPDIR}" -s "${STAGING_DIR_NATIVE}" -i ${BALENA_DOCKER_IMG} -b ${BALENA_BOOT_DOCKER_IMG} -o ${BALENA_HOSTAPP_IMG}
}

IMAGE_TYPEDEP:balenaos-img.sig = "balenaos-img"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ run_current_hooks_and_recover () {
}

local_image=""
boot_image=""
remote_image=""
reboot=0
hooks=1
hooks_rollback=1

while getopts 'f:i:rnx' flag; do
while getopts 'b:f:i:rnx' flag; do
case "${flag}" in
b) boot_image="${OPTARG}" ;;
f) local_image=$(realpath "${OPTARG}") ;;
i) remote_image="${OPTARG}" ;;
r) reboot=1 ;;
Expand All @@ -54,11 +56,16 @@ while getopts 'f:i:rnx' flag; do
esac
done

if [ "$local_image" = "" ] && [ "$remote_image" = "" ]; then
if [ "$local_image" = "" ] && [ "$remote_image" = "" ]; then
ERROR "At least one of -f or -i is required"
exit 1
fi

if [ "$boot_image" != "" ] && [ "$local_image" != "" ] && [ "$remote_image" != "" ]; then
ERROR "Cannot use -b with both -f and -i"
exit 1
fi

INFO "Running hostapp update..."

export DOCKER_HOST="unix:///var/run/balena-host.sock"
Expand Down Expand Up @@ -105,6 +112,50 @@ for hostapp in "$SYSROOT/hostapps/"*; do
done
sync -f "$SYSROOT"

rm -rf "$SYSROOT/hostapps/.new"
mkdir -p "$SYSROOT/hostapps/.new"

# Dual-image path (-b): stage boot on sysroot before main image load/pull.
# -b is a local tarball if the path exists, otherwise a remote image reference.
if [ "$boot_image" != "" ]; then
if [ -f "$boot_image" ]; then
if ! mountpoint "${SYSROOT}/balena/tmp" >/dev/null; then
mkdir -p "${LOADTMP}"
mount --bind "${LOADTMP}" "${SYSROOT}/balena/tmp"
fi
INFO "Loading boot image from local: $boot_image"
BOOT_IMAGE=$(balena load --quiet -i "$(realpath "$boot_image")" | cut -d: -f1 --complement | tr -d ' ')
else
INFO "Pulling boot image from remote: $boot_image"
_out="$(balena pull "$boot_image" 2>&1)"
INFO "${_out}"
BOOT_IMAGE="$boot_image"
fi
INFO "Creating boot container from image: $BOOT_IMAGE"
BOOT_CONTAINER_ID=$(balena create --runtime="bare" "$BOOT_IMAGE" /bin/sh)
INFO "Extracting boot files from container: $BOOT_CONTAINER_ID"
mkdir -p "$SYSROOT/hostapps/.new/boot"
if ! _out=$(balena cp "$BOOT_CONTAINER_ID:/boot/." "$SYSROOT/hostapps/.new/boot/" 2>&1); then
ERROR "Failed to copy boot files from boot image: ${_out}"
exit 1
fi
INFO "${_out}"
if [ -z "$(ls -A "$SYSROOT/hostapps/.new/boot" 2>/dev/null)" ]; then
ERROR "Boot image /boot is empty"
exit 1
fi
if ! _out=$(balena rm --force "$BOOT_CONTAINER_ID" 2>&1); then
ERROR "Failed to remove boot container: ${_out}"
exit 1
fi
INFO "${_out}"
if ! _out=$(balena rmi --force "$BOOT_IMAGE" 2>&1); then
ERROR "Failed to remove boot image: ${_out}"
exit 1
fi
INFO "${_out}"
fi

# Load new hostapp
if [ "$local_image" != "" ]; then
# bind mount the data partition for temporary extract/load files
Expand All @@ -124,13 +175,16 @@ elif [ "$remote_image" != "" ]; then
_out="$(balena pull "$HOSTAPP_IMAGE" 2>&1)"
INFO "${_out}"
fi
CONTAINER_ID=$(balena create --runtime="bare" --volume=/boot "$HOSTAPP_IMAGE" /bin/sh)
BOOTSTRAP=$(balena inspect -f "{{range .Mounts}}{{.Destination}} {{.Source}}{{end}}" "$CONTAINER_ID" | awk '$1 == "/boot" { print $2 }' | head -n1)

# Create boot entry
rm -rf "$SYSROOT/hostapps/.new"
mkdir -p "$SYSROOT/hostapps/.new"
ln -sr "$BOOTSTRAP" "$SYSROOT/hostapps/.new/boot"
if [ "$boot_image" != "" ]; then
CONTAINER_ID=$(balena create --runtime="bare" "$HOSTAPP_IMAGE" /bin/sh)
else
CONTAINER_ID=$(balena create --runtime="bare" --volume=/boot "$HOSTAPP_IMAGE" /bin/sh)
BOOTSTRAP=$(balena inspect -f "{{range .Mounts}}{{.Destination}} {{.Source}}{{end}}" "$CONTAINER_ID" | awk '$1 == "/boot" { print $2 }' | head -n1)

# Wire the anonymous /boot volume into the root-slot layout.
ln -sr "$BOOTSTRAP" "$SYSROOT/hostapps/.new/boot"
fi
sync -f "$SYSROOT"
mv -T "$SYSROOT/hostapps/.new" "$SYSROOT/hostapps/$CONTAINER_ID"
sync -f "$SYSROOT"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ balenad -s=@BALENA_STORAGE@ --data-root="$SYSROOT/balena" -H unix:///var/run/bal
pid=$!
sleep 5

hostapp-update -f /input -n
hostapp-update -f /input -b /input-boot -n

kill $pid
wait $pid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ sysroot="/"
tmpdir=""
fstype="ext4"

while getopts 'i:o:s:t:f:' flag; do
while getopts 'b:i:o:s:t:f:' flag; do
case "${flag}" in
b) boot_input=$(realpath "${OPTARG}") ;;
i) input=$(realpath "${OPTARG}") ;;
o) output=$(realpath "${OPTARG}") ;;
s) sysroot=$(realpath "${OPTARG}") ;;
Expand All @@ -33,6 +34,11 @@ if ! [ -f "$input" ]; then
exit 1
fi

if ! [ -f "$boot_input" ]; then
echo "File does not exist: $boot_input"
exit 1
fi

if ! [ -f "$output" ]; then
echo "File does not exist: $output"
exit 1
Expand All @@ -45,4 +51,10 @@ cleanup_docker() {
$DOCKER load -i "$sysroot/usr/share/mkfs-hostapp-image.tar"
trap cleanup_docker EXIT

$DOCKER run --privileged --rm -v "$input:/input:ro" -v "$tmpdir:$tmpdir:ro" -v "$output:/output" -e "PATH=$PATH:$HOST_PATH" @IMAGE@ create.${fstype}
$DOCKER run --privileged --rm \
-v "$input:/input:ro" \
-v "$boot_input:/input-boot:ro" \
-v "$tmpdir:$tmpdir:ro" \
-v "$output:/output" \
-e "PATH=$PATH:$HOST_PATH" \
@IMAGE@ create.${fstype}
Loading