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
19 changes: 9 additions & 10 deletions meta-balena-common/classes/image-balena.bbclass
Original file line number Diff line number Diff line change
Expand Up @@ -459,20 +459,19 @@ python do_image_size_check() {
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))
# mobynit init only is stored on the inactive root slot during HUP
boot_init = os.path.join(d.getVar("IMAGE_ROOTFS"), "boot", "init")
boot_init_size_kb = int(disk_aligned(d, os.path.getsize(boot_init) / 1024 if os.path.isfile(boot_init) else 0))

# Total space required = docker image + boot volume
total_required = image_size_aligned + boot_size_aligned
# Total space required = docker image + mobynit init on root slot
total_required = image_size_aligned + boot_init_size_kb

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"
% (image_size_aligned, boot_size_aligned, total_required, available))
bb.fatal("HUP size check failed: docker image (%d KiB) + mobynit init (%d KiB) = %d KiB exceeds available space %d KiB"
% (image_size_aligned, boot_init_size_kb, total_required, available))

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

# Equivalent to:
Expand Down
24 changes: 22 additions & 2 deletions meta-balena-common/classes/image_types_balena.bbclass
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
inherit image_types

require ${@os.path.join(d.getVar('BALENA_COREBASE'), 'recipes-containers/docker-disk/balena-data-partition.inc')}

#
# Create a raw image that can by written onto a storage device using dd/etcher.
#
Expand Down Expand Up @@ -124,6 +126,7 @@ BALENA_BOOT_FINGERPRINT_PATH ?= "${WORKDIR}/${BALENA_FINGERPRINT_FILENAME}.${BAL
BALENA_IMAGE_BOOTLOADER ?= "virtual/bootloader"
BALENA_RAW_IMG_COMPRESSION ?= ""
BALENA_DATA_FS ?= "${DEPLOY_DIR_IMAGE}/${BALENA_DATA_FS_LABEL}.img"
BALENA_DATA_STAGING_TAR ?= "${DEPLOY_DIR_IMAGE}/${BALENA_DATA_STAGING}"
BALENA_BOOT_FS = "${WORKDIR}/${BALENA_BOOT_FS_LABEL}.img"
BALENA_ROOTB_FS = "${WORKDIR}/${BALENA_ROOTB_FS_LABEL}.img"
BALENA_STATE_FS ?= "${WORKDIR}/${BALENA_STATE_FS_LABEL}.img"
Expand Down Expand Up @@ -180,7 +183,7 @@ IMAGE_CMD:balenaos-img () {

# resin-data
if [ -n "${BALENA_DATA_FS}" ]; then
BALENA_DATA_SIZE=`du -bks ${BALENA_DATA_FS} | awk '{print $1}'`
BALENA_DATA_SIZE=$(expr ${BALENA_DATA_PARTITION_SIZE_MB} \* 1024)
BALENA_DATA_SIZE_ALIGNED=$(expr ${BALENA_DATA_SIZE} \+ ${BALENA_IMAGE_ALIGNMENT} \- 1)
BALENA_DATA_SIZE_ALIGNED=$(expr ${BALENA_DATA_SIZE_ALIGNED} \- ${BALENA_DATA_SIZE_ALIGNED} \% ${BALENA_IMAGE_ALIGNMENT})
else
Expand Down Expand Up @@ -331,8 +334,25 @@ IMAGE_CMD:balenaos-img () {
bbfatal "Rootfs labeling for type '${BALENA_ROOT_FSTYPE}' has not been implemented!"
fi

# Build resin-data.img: docker-disk is saved as a tar, we will add boot-A dir into it with all boot files.
if [ -n "${BALENA_DATA_FS}" ]; then
e2label ${BALENA_DATA_FS} ${BALENA_DATA_FS_LABEL}
if [ ! -f "${BALENA_DATA_STAGING_TAR}" ]; then
bbfatal "resin-data: ${BALENA_DATA_STAGING_TAR} missing"
fi
if [ ! -d "${IMAGE_ROOTFS}/boot" ] || [ -z "$(ls -A "${IMAGE_ROOTFS}/boot" 2>/dev/null)" ]; then
bbfatal "resin-data: ${IMAGE_ROOTFS}/boot is empty"
fi
_balena_data_root=$(mktemp -d)
tar -xf "${BALENA_DATA_STAGING_TAR}" -C "${_balena_data_root}"
mkdir -p "${_balena_data_root}/boot-A"
cp -a "${IMAGE_ROOTFS}/boot/." "${_balena_data_root}/boot-A/"
rm -f "${BALENA_DATA_FS}"
dd if=/dev/zero of="${BALENA_DATA_FS}" bs=1M count=0 seek="${BALENA_DATA_PARTITION_SIZE_MB}"
mkfs.ext4 -E lazy_itable_init=0,lazy_journal_init=0 -T default -b ${BALENA_DATA_FS_BLOCK_SIZE} -i 8192 -d "${_balena_data_root}" -F "${BALENA_DATA_FS}" || \
bbfatal "resin-data: mkfs.ext4 failed (try increasing BALENA_DATA_PARTITION_SIZE_MB)"
e2label "${BALENA_DATA_FS}" "${BALENA_DATA_FS_LABEL}"
rm -rf "${_balena_data_root}"
bbnote "Built ${BALENA_DATA_FS} from ${BALENA_DATA_STAGING_TAR} and boot-A"
fi

#
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,29 +39,44 @@ function get_root_uuid {
done
}

menuentry 'boot'{
if [ ${bootcount} = 2 ] ; then
if [ ${resin_root_part} = 2 ] ; then
get_root_uuid "resin-rootB"
else
get_root_uuid "resin-rootA"
fi
else
if [ ${resin_root_part} = 2 ] ; then
get_root_uuid "resin-rootA"
function select_root_slot_and_boot_dir {
if [ ${bootcount} = 2 ] ; then
if [ ${resin_root_part} = 2 ] ; then
set root_slot_label=resin-rootB
set data_boot_dir=/boot-B
else
set root_slot_label=resin-rootA
set data_boot_dir=/boot-A
fi
else
get_root_uuid "resin-rootB"
if [ ${resin_root_part} = 2 ] ; then
set root_slot_label=resin-rootA
set data_boot_dir=/boot-A
else
set root_slot_label=resin-rootB
set data_boot_dir=/boot-B
fi
fi
fi
linux /boot/@@KERNEL_IMAGETYPE@@ root=UUID=${root_uuid} @@KERNEL_CMDLINE@@ ${extra_os_cmdline} ${extra_os_firmware_class_path}
}

menuentry 'boot' {
select_root_slot_and_boot_dir
get_root_uuid ${root_slot_label}
set root_slot_uuid=${root_uuid}
get_root_uuid "resin-data"
linux ${data_boot_dir}/@@KERNEL_IMAGETYPE@@ root=UUID=${root_slot_uuid} @@KERNEL_CMDLINE@@ ${extra_os_cmdline} ${extra_os_firmware_class_path}
}

menuentry 'manualfallbackA' {
get_root_uuid "resin-rootA"
linux /boot/@@KERNEL_IMAGETYPE@@ root=UUID=${root_uuid} @@KERNEL_CMDLINE@@ ${extra_os_cmdline} ${extra_os_firmware_class_path}
get_root_uuid "resin-rootA"
set root_slot_uuid=${root_uuid}
get_root_uuid "resin-data"
linux /boot-A/@@KERNEL_IMAGETYPE@@ root=UUID=${root_slot_uuid} @@KERNEL_CMDLINE@@ ${extra_os_cmdline} ${extra_os_firmware_class_path}
}

menuentry 'manualfallbackB' {
get_root_uuid "resin-rootB"
linux /boot/@@KERNEL_IMAGETYPE@@ root=UUID=${root_uuid} @@KERNEL_CMDLINE@@ ${extra_os_cmdline} ${extra_os_firmware_class_path}
get_root_uuid "resin-rootB"
set root_slot_uuid=${root_uuid}
get_root_uuid "resin-data"
linux /boot-B/@@KERNEL_IMAGETYPE@@ root=UUID=${root_slot_uuid} @@KERNEL_CMDLINE@@ ${extra_os_cmdline} ${extra_os_firmware_class_path}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Shared resin-data partition settings (docker-disk seed + balenaos-img mkfs).
BALENA_DATA_PARTITION_SIZE_MB ?= "256"
BALENA_DATA_FS_BLOCK_SIZE ?= "4k"
BALENA_DATA_STAGING ?= "resin-data-staging.tar"
13 changes: 3 additions & 10 deletions meta-balena-common/recipes-containers/docker-disk/docker-disk.bb
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ B = "${S}/build"

inherit deploy
require docker-disk.inc
require balena-data-partition.inc
require recipes-containers/balena-supervisor/balena-supervisor.inc

PARTITION_SIZE ?= "192"
FS_BLOCK_SIZE ?= "4k"

PV = "${HOSTOS_VERSION}"

RDEPENDS:${PN} = "balena"
Expand All @@ -29,10 +27,6 @@ do_compile () {
if [ -z "${SUPERVISOR_FLEET}" ] || [ -z "${SUPERVISOR_VERSION}" ]; then
bbfatal "docker-disk: SUPERVISOR_FLEET and/or SUPERVISOR_VERSION not set."
fi
if [ -z "${PARTITION_SIZE}" ]; then
bbfatal "docker-disk: PARTITION_SIZE needs to have a value (megabytes)."
fi

# At this point we really need internet connectivity for building the
# docker image
if [ "x${@connected(d)}" != "xyes" ]; then
Expand Down Expand Up @@ -65,8 +59,7 @@ do_compile () {
-e HOSTAPP_PLATFORM="${HOSTAPP_PLATFORM}" \
-e BALENA_API_ENV="${BALENA_API_ENV}" \
-e BALENA_API_TOKEN="${_token}" \
-e PARTITION_SIZE="${PARTITION_SIZE}" \
-e FS_BLOCK_SIZE="${FS_BLOCK_SIZE}" \
-e BALENA_DATA_STAGING="${BALENA_DATA_STAGING}" \
-v /sys/fs/cgroup:/sys/fs/cgroup:ro -v ${B}:/build \
--name ${_container_name} ${_image_name}
$DOCKER rmi -f ${_image_name}
Expand All @@ -82,7 +75,7 @@ do_install () {
FILES:${PN} += "/etc/hostapp-extensions.conf"

do_deploy () {
install -m 644 ${B}/resin-data.img ${DEPLOYDIR}/resin-data.img
install -m 644 ${B}/${BALENA_DATA_STAGING} ${DEPLOYDIR}/${BALENA_DATA_STAGING}
}
addtask deploy before do_package after do_install

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ set -o nounset
DOCKER_TIMEOUT=20 # Wait 20 seconds for docker to start
DATA_VOLUME=/resin-data
BUILD=/build
PARTITION_SIZE=${PARTITION_SIZE:-1024}
DOCKER_HOST=unix:///var/run/docker.sock

finish() {
Expand Down Expand Up @@ -74,8 +73,4 @@ kill -TERM "$(cat /var/run/docker.pid)"
# don't let wait() error out and crash the build if the docker daemon has already been stopped
wait "$(cat /var/run/docker.pid)" || true

# Export the final data filesystem
dd if=/dev/zero of=${BUILD}/resin-data.img bs=1M count=0 seek="${PARTITION_SIZE}"

# Usage type default, block size 4k defined in recipe. See https://github.com/tytso/e2fsprogs/issues/50
mkfs.ext4 -E lazy_itable_init=0,lazy_journal_init=0 -T default -b ${FS_BLOCK_SIZE} -i 8192 -d ${DATA_VOLUME} -F ${BUILD}/resin-data.img
tar -cf ${BUILD}/${BALENA_DATA_STAGING} -C ${DATA_VOLUME} .
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,77 @@ ERROR() {
fi
}

# Map the inactive root slot to resin-data boot dir (boot-A / boot-B).
data_boot_slot() {
if [ ! -e /dev/disk/by-state/inactive ] || [ ! -e /dev/disk/by-state/resin-rootA ]; then
return 1
fi
inactive_dev=$(readlink -f /dev/disk/by-state/inactive)
roota_dev=$(readlink -f /dev/disk/by-state/resin-rootA)
if [ "$inactive_dev" = "$roota_dev" ]; then
echo "boot-A"
else
echo "boot-B"
fi
}

# Stage hostapp /boot on resin-data boot-{A|B} for GRUB
stage_boot_to_data_partition() {
container_id="$1"
slot=""

if ! mountpoint -q /mnt/data 2>/dev/null; then
INFO "Skipping data-partition boot staging (/mnt/data not mounted)"
return 0
fi

if ! slot=$(data_boot_slot); then
INFO "Skipping data-partition boot staging (inactive root slot unknown)"
return 0
fi

data_boot_dir="/mnt/data/${slot}"
data_boot_new="${data_boot_dir}.new"

rm -rf "$data_boot_new"
mkdir -p "$data_boot_new"
if ! _out=$(balena cp "${container_id}:/boot/." "${data_boot_new}/" 2>&1); then
ERROR "Failed to copy boot files to data partition: ${_out}"
exit 1
fi
if [ -z "$(ls -A "$data_boot_new" 2>/dev/null)" ]; then
ERROR "Hostapp /boot is empty"
exit 1
fi
sync -f /mnt/data

rm -rf "${data_boot_dir}.old" 2>/dev/null || true
if [ -d "$data_boot_dir" ]; then
mv "$data_boot_dir" "${data_boot_dir}.old"
fi
mv -T "$data_boot_new" "$data_boot_dir"
sync -f /mnt/data
rm -rf "${data_boot_dir}.old" 2>/dev/null || true

INFO "Staged boot payload to ${data_boot_dir}/"
}

# Root-slot boot dir holds mobynit init only
install_hostapp_init() {
container_id="$1"
hostapp_boot="$2"

mkdir -p "$hostapp_boot"
if ! _out=$(balena cp "${container_id}:/boot/init" "${hostapp_boot}/" 2>&1); then
ERROR "Failed to copy mobynit init: ${_out}"
exit 1
fi
if [ ! -x "${hostapp_boot}/init" ]; then
ERROR "Hostapp /boot/init missing or not executable"
exit 1
fi
}

run_current_hooks_and_recover () {
if [ "$hooks_rollback" = 1 ]; then
# Run the current ones to cleanup the system.
Expand Down Expand Up @@ -124,13 +195,13 @@ 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)
CONTAINER_ID=$(balena create --runtime="bare" "$HOSTAPP_IMAGE" /bin/sh)

stage_boot_to_data_partition "$CONTAINER_ID"

# Create boot entry
rm -rf "$SYSROOT/hostapps/.new"
mkdir -p "$SYSROOT/hostapps/.new"
ln -sr "$BOOTSTRAP" "$SYSROOT/hostapps/.new/boot"
install_hostapp_init "$CONTAINER_ID" "$SYSROOT/hostapps/.new/boot"
sync -f "$SYSROOT"
mv -T "$SYSROOT/hostapps/.new" "$SYSROOT/hostapps/$CONTAINER_ID"
sync -f "$SYSROOT"
Expand Down
Loading