Upgrading Custom Host OS with OpenBalena

Hi,

What is the current status of custom Host OS upgrade in OpenBalena?

Most threads indicate that upgrading from a custom host OS is on the roadmap but not yet supported in OpenBalena.

I have seen this one stating that OS updates (not necessarily custom?) are possible but not using the dashboard (Mar 19): Roadmap for BalenaOS Updates in OpenBalena

And this one stating that Host OS upgrade is not supported (later in Sept 19): OS Upgrade in OpenBalena

So can the Host OS be upgraded using OpenBalena, and if so can it also be done using custom Host OS builds?

Thanks for any hints

Hi @travelina

We do not support Host OS upgrades in openBalena yet, so this is unfortunately not possible at this stage.

Kind regards
Alida

Hi Alida,

Thanks for the reply.

Does that mean that the upgrade described at the following link is no longer possible?

Many thanks

Hi there, I can see that there is some inconsistency in the statements the balena team have made on this subject. As far as I know, we donā€™t support Host OS updates as a core feature in openBalena, but it is possible (although in a unsupported way) by running a number of sub-commands on the Host. Iā€™ll ask the team and find out what the exact situation is.

1 Like

Really keen to know about sub commands to update @lucianbuzzo !!! I suspected there may be a ā€˜hackyā€™ way to do this? Maybe customers could create a docker container that does something like:

  • If new base/host OS image is available download it
  • Check with is inactive partition
  • dd it onto partition
  • swap nextboot marker and reboot

Is there any vague idea when this functionality might make its way to openbalena? or is unlikley to ever? Itā€™s the main thing thats blocking me from using balena in our projects.

Hello @gk1, please see this post regarding your questions: PSA: Host OS Updates & openBalena

The one from 2yrs ago? :confused:

hey @gk1,
not sure which post you are refering to from 2 years ago? The one Alan linked above is from Mar 17, 2020

Thanks

Oh, haha, sorry! I interpreted it as March 2017!!!

So it sounds like there is work in progress to being this to OpenBalena - Are there steps that we could manually run ourselves to dd an image from our own servers onto the inactive partition to perform host OS update?

No, this is not something we support or can help with. Please read again the post linked above.

Since the last reply was 2 years ago on this topic I thought Iā€™d follow up and see if openBalena supports Host OS updates. I donā€™t see anything obvious in docs. Thanks!

Hi @brownster, we also needed this functionality, so we decided to build it out ourselves. We have plans to release our custom balenaos as open source once we get around to genericizing it sufficiently, but for now Iā€™m happy to share the two components that enabled this for us.

First off, we had to modify the OS build process to use the balena jenkins scripts. Itā€™s a pretty involved build process, but the key that is needed for OTA OS updates is the part where the build scripts deploy the OS to openbalena, similar to an app. To do this you first need to create a user in your openbalena instance called ā€œbalena_osā€ and then create an application for each new device type you want to deploy an OS for, calling them ā€œbalena_os/ā€. You also need to add that custom device type (and associated alias) to your openbalena instance. To do all of this you will need to use a tool such as open-balena-admin or I suppose you can manually just add them in the database as well, because there is no out of the box tool available to manage users or custom device types in openbalena.

Once you have this, you then need your own wrapper script to slightly tweak the balena barys build script. Below is our example, yours may differ depending on how you want to manage your update process (i.e. do you want to use timestamp based updates?). By using this process it also allows you to tag certain devices to certain OS versions.

Build script wrapper (we call it harmoni_jenkins_build.sh):

#!/bin/bash

WORKSPACE="$(pwd)"
VARIANT="prod"
JENKINS_ARGUMENTS_VAR=""

export deploy=yes

# Process script arguments
args_number="$#"
while [[ $# -ge 1 ]]; do
	arg=$1
	case $arg in
		-m|--machine)
			if [ -z "$2" ]; then
				echo "-m|--machine argument needs a machine name"
				exit 1
			fi
			MACHINE="$2"
			;;
		-r|--rootdir)
			if [ -z "$2" ]; then
				echo "--rootdir needs directory name (root directory of the yocto project)"
				exit 1
			fi
			WORKSPACE="${WORKSPACE:-$2}"
			shift
			;;
		-d|--dev)
			VARIANT="dev"
			;;
		-b|--build-only)
			export deploy=no
			;;
		--preserve-build)
			JENKINS_ARGUMENTS_VAR="$JENKINS_ARGUMENTS_VAR $1"
			;;
		--preserve-container)
			JENKINS_ARGUMENTS_VAR="$JENKINS_ARGUMENTS_VAR $1"
			;;
	esac
	shift
done

if [ -z "$MACHINE" ]; then
	echo -e "[ERROR] $0: You must specify -m <machine type> or --machine <machine type>"
	exit 1
elif [ ! -z "$MACHINE" ] && [ ! -f "${WORKSPACE}/${MACHINE}.coffee" ]; then
	echo -e "[ERROR] $0: Invalid machine type specified: $MACHINE"
	exit 1
fi

if [ ! -d "$WORKSPACE/layers" ]; then
	echo -e "[ERROR] $0: Script must be run from root of yocto project, or specify root via -r <rootdir> or --rootdir <rootdir>"
	exit 1
fi

# Get secrets
source $WORKSPACE/../.harmoni-balenaos.env

# Tweak build scripts
sedi () {
    sed --version >/dev/null 2>&1 && sed -i -- "$@" || sed -i "" "$@"
}

echo "[INFO] $0: Tweaking build scripts."
# Skip public dockerhub image push
sedi "s/balena_deploy_to_dockerhub/#balena_deploy_to_dockerhub/g" $WORKSPACE/balena-yocto-scripts/automation/jenkins_build.sh
# Use openbalena environment instead of balena-cloud
sedi "s/balena-cloud.com/"$balena_lib_environment"/g" $WORKSPACE/balena-yocto-scripts/automation/include/balena-lib.inc
sedi "s/{_s3_secret_key}/{_s3_secret_key} --endpoint-url https:\/\/s3."$balena_lib_environment"/g" $WORKSPACE/balena-yocto-scripts/automation/include/balena-deploy.inc
# Provide public path to balena-img build image
sedi "s/_namespace=\"\${4}\"/_namespace=\"aggurio\"/g" $WORKSPACE/balena-yocto-scripts/automation/include/balena-deploy.inc
# Change default S3 bucket name
sedi "s/resin-production-img-cloudformation/balena-os/g" $WORKSPACE/balena-yocto-scripts/automation/include/balena-deploy.inc

# Update repo tag
HOSTOS_VERSION="v$(cat $WORKSPACE/VERSION)"
HARMONI_BUILD_TS="$(date +%Y-%m-%dT%H-%M-%S)"
HARMONI_IMAGE_TAG="${HOSTOS_VERSION}.${VARIANT}_${HARMONI_BUILD_TS}"
echo "$HARMONI_IMAGE_TAG" > ${WORKSPACE}/layers/meta-balena-harmoni/recipes-support/harmoni-image-tag/files/HARMONI_IMAGE_TAG
pushd "$WORKSPACE" > /dev/null 2>&1
    git tag -a "$HARMONI_IMAGE_TAG" -m "harmoni_jenkins_build"
popd > /dev/null 2>&1

$WORKSPACE/balena-yocto-scripts/automation/jenkins_build.sh -m $MACHINE --shared-dir $WORKSPACE/shared-dir --build-flavor $VARIANT $JENKINS_ARGUMENTS_VAR

# Un-tweak build scripts
echo "[INFO] $0: Un-tweaking build scripts."
sedi "s/#balena_deploy_to_dockerhub/balena_deploy_to_dockerhub/g" $WORKSPACE/balena-yocto-scripts/automation/jenkins_build.sh
sedi "s/"$balena_lib_environment"/balena-cloud.com/g" $WORKSPACE/balena-yocto-scripts/automation/include/balena-lib.inc
sedi "s/{_s3_secret_key} --endpoint-url https:\/\/s3."$balena_lib_environment"/{_s3_secret_key}/g" $WORKSPACE/balena-yocto-scripts/automation/include/balena-deploy.inc
sedi "s/_namespace=\"aggurio\"/_namespace=\"\${4}\"/g" $WORKSPACE/balena-yocto-scripts/automation/include/balena-deploy.inc
sedi "s/balena-os/resin-production-img-cloudformation/g" $WORKSPACE/balena-yocto-scripts/automation/include/balena-deploy.inc

# Clean up build artifacts
rm -rf ${WORKSPACE}/layers/meta-balena-harmoni/recipes-support/harmoni-image-tag/files/HARMONI_IMAGE_TAG
rm -rf $WORKSPACE/deploy-s3
rm -rf $WORKSPACE/deploy-jenkins

You then need to include a bitbake recipe on your device which runs via a cron job or some other regular process to check for updates. If you have your own build of balenaos, Iā€™m assuming you will know how to add a recipe to do that (we called our recipe harmoni-hostapp-update and it runs a cron job every x minutes to check for OS / supervisor updates). Below is the script that is run by the cron job which manages the update process:

#!/bin/bash

# Hostapp update configuration
BALENA_API_ENDPOINT="$(jq --raw-output .apiEndpoint /mnt/boot/config.json)"
BALENA_API_KEY="$(jq --raw-output .deviceApiKey /mnt/boot/config.json)"
DEVICE_TYPE="$(jq --raw-output .deviceType /mnt/boot/config.json)"
FLEET_ID="$(jq --raw-output .applicationId /mnt/boot/config.json)"
DEVICE_UUID="$(jq --raw-output .uuid /mnt/boot/config.json)"
FLEET_TAG_KEY="os_version"
DEVICE_TAG_KEY="os_version"
readarray -t -d "." BALENA_API_ENDPOINT_ARR <<<"$BALENA_API_ENDPOINT"
unset BALENA_API_ENDPOINT_ARR[0]
BALENA_HOST=$(IFS=. ; echo "${BALENA_API_ENDPOINT_ARR[*]}")
TARGET_IMAGE_DIR=/mnt/data/harmoni-hostapp-update
TARGET_IMAGE_FILE="$TARGET_IMAGE_DIR/image.tar.gz"
TARGET_IMAGE_TEMP_DIR="$TARGET_IMAGE_DIR/tmp"

# Get and parse current os image tag
CURR_IMAGE="$(cat /mnt/boot/HARMONI_IMAGE_TAG)"
readarray -t -d "_" CURR_IMAGE_SPLIT <<<"$CURR_IMAGE"
readarray -t -d "." CURR_HOSTOS_VAR <<<"${CURR_IMAGE_SPLIT[0]}"
CURR_VAR=${CURR_HOSTOS_VAR[-1]}
echo "Current hostos version: ${CURR_IMAGE}"

# Determine target os image for fleet and device
FLEET_TARGET_IMAGE=$(curl -s -H "Authorization: Bearer $BALENA_API_KEY" "$BALENA_API_ENDPOINT/v6/application_tag?\$filter=application%20eq%20${FLEET_ID}%20and%20tag_key%20eq%20%27${FLEET_TAG_KEY}%27" | jq -r '.d[0].value')
DEVICE_TARGET_IMAGE=$(curl -s -H "Authorization: Bearer $BALENA_API_KEY" "$BALENA_API_ENDPOINT/v6/device_tag?\$filter=device/uuid%20eq%20%27${DEVICE_UUID}%27%20and%20tag_key%20eq%20%27${DEVICE_TAG_KEY}%27" | jq -r '.d[0].value')

# Set target os image - prioritize device, then fleet, then default to latest
if [ ! -z $DEVICE_TARGET ]; then
    TARGET_IMAGE=$DEVICE_TARGET
elif [ ! -z $FLEET_TARGET ]; then
    TARGET_IMAGE=$FLEET_TARGET
else
    TARGET_IMAGE="latest"
fi

# If target os image is set to latest, determine the latest version
if [ "$TARGET_IMAGE" == "latest" ]; then
    # Get all release tags for target device type, sorted reverse by value
    RELEASES=$(curl -s -H "Authorization: Bearer $BALENA_API_KEY" "$BALENA_API_ENDPOINT/v6/release_tag?\$filter=release/any(r:r/belongs_to-application/any(bta:bta/slug%20eq%20%27balena_os/${DEVICE_TYPE}%27))&\$orderby=value%20desc" | jq -r '.d | map( [(.value)] ) | add')
    declare -a RELEASES_ARR=($(jq -r '.[]' <<<"$RELEASES"))
    for i in "${RELEASES_ARR[@]}"; do
        readarray -t -d "_" TARGET_IMAGE_SPLIT <<<"$i"
        readarray -t -d "." TARGET_HOSTOS_VAR <<<"${TARGET_IMAGE_SPLIT[0]}"
        TEST_VAR=${TARGET_HOSTOS_VAR[-1]}
        if [ "$TEST_VAR" == "$CURR_VAR" ]; then
            TARGET_IMAGE="$i"
            break
        fi
    done
else
    TARGET_IMAGE="$TARGET_IMAGE"
fi

echo "Target hostos version: ${TARGET_IMAGE}"        

# Determine if device is running its target image
if [ "$CURR_IMAGE" == "v$TARGET_IMAGE" ]; then
    echo "Target hostos already installed!"
else
    echo "Target hostos not installed, downloading target..."

    # Get image from release (uses first image, should only be one)
    RELEASE_ID=$(curl -s -H "Authorization: Bearer $BALENA_API_KEY" "$BALENA_API_ENDPOINT/v6/release_tag?\$filter=release/any(r:r/belongs_to-application/any(bta:bta/slug%20eq%20%27balena_os/${DEVICE_TYPE}%27))%20and%20tag_key%20eq%20%27version%27%20and%20value%20eq%20%27${TARGET_IMAGE}%27" | jq -r '.d[0].release.__id')
    IMAGE_ID=$(curl -s -H "Authorization: Bearer $BALENA_API_KEY" "$BALENA_API_ENDPOINT/v6/image-is_part_of-release?\$filter=is_part_of-release%20eq%20${RELEASE_ID}" | jq -r '.d[0].image.__id')

    # Get target image and manifest
    IMAGE_RECORD=$(curl -s -H "Authorization: Bearer $BALENA_API_KEY" "$BALENA_API_ENDPOINT/v6/image?\$filter=id%20eq%20${IMAGE_ID}")
    IMAGE_LOCATION=$(jq -r .d[0].is_stored_at__image_location <<< "$IMAGE_RECORD")
    IMAGE_SERVICE=$(echo $IMAGE_LOCATION | cut -d/ -f1)
    IMAGE_REPOSITORY="$(echo $IMAGE_LOCATION | cut -d/ -f2)/$(echo $IMAGE_LOCATION | cut -d/ -f3)"
    IMAGE_MANIFEST=$(jq -r .d[0].content_hash <<< "$IMAGE_RECORD")

    # Get authorization token for registry from api
    JWT=$(curl -s -H "Authorization: Bearer $BALENA_API_KEY" "$BALENA_API_ENDPOINT/auth/v1/token?service=$IMAGE_SERVICE&scope=repository:$IMAGE_REPOSITORY:pull" | jq -r .token)

    mkdir -p "$TARGET_IMAGE_TEMP_DIR"

    # Get manifest from registry
    TARGET_IMAGE_MANIFEST=$(curl -s -H "Authorization: Bearer $JWT" "https://registry.$BALENA_HOST/v2/$IMAGE_REPOSITORY/manifests/$IMAGE_MANIFEST")

    # Download config from registry
    CONFIG_DIGEST=$(echo "$TARGET_IMAGE_MANIFEST" | jq -r ".config.digest")
    CONFIG_FILE="config.json"
    curl -s -L -H "Authorization: Bearer $JWT" "https://registry.$BALENA_HOST/v2/$IMAGE_REPOSITORY/blobs/$CONFIG_DIGEST" --output "$TARGET_IMAGE_TEMP_DIR/$CONFIG_FILE"

    # Download layers from registry
    LAYER_IDX=0
    declare -a LAYER_FILES
    for LAYER_DIGEST in $(echo "$TARGET_IMAGE_MANIFEST" | jq -r ".layers[].digest"); do
        LAYER_FILES[$LAYER_IDX]="layer$(expr $LAYER_IDX + 1).tar.gz"
        curl -s -L -H "Authorization: Bearer $JWT" "https://registry.$BALENA_HOST/v2/$IMAGE_REPOSITORY/blobs/$LAYER_DIGEST" --output "$TARGET_IMAGE_TEMP_DIR/${LAYER_FILES[${LAYER_IDX}]}"
        LAYER_IDX=$(expr $LAYER_IDX + 1)
    done

    # Build manifest.json for newly downloaded image
    MANIFEST_JSON=$(jq --arg config "$CONFIG_FILE" --arg layer "${LAYER_FILES[0]}" '. | .[0].Config=$config' <<< "[]")
    for idx in "${!LAYER_FILES[@]}"; do
        MANIFEST_JSON=$(jq --arg layer "${LAYER_FILES[${idx}]}" '. | .[0].Layers['"${idx}"']=$layer' <<< "$MANIFEST_JSON")
    done
    echo "$MANIFEST_JSON" > "$TARGET_IMAGE_TEMP_DIR/manifest.json"

    # Create image file and cleanup temp directory
    tar -czf "$TARGET_IMAGE_FILE" -C "$TARGET_IMAGE_TEMP_DIR" .
    rm -rf "$TARGET_IMAGE_TEMP_DIR"

    echo "Updating host OS..."
    hostapp-update -f ${TARGET_IMAGE_FILE}

    if [ $? -eq 0 ]; then
        echo "Host OS update complete."
        
        echo "Cleaning up downloaded items..."
        rm -rf ${TARGET_IMAGE_DIR}

        echo "Rebooting."
        reboot
        exit 0
    else
        echo "Host OS update error."

        echo "Cleaning up downloaded items..."
        rm -rf ${TARGET_IMAGE_DIR}
        exit 1
    fi
fi

## Update supervisor

source /etc/balena-supervisor/supervisor.conf
if [ ! -z "$SUPERVISOR_TAG" ]; then
    SUPERVISOR_IMAGE=$(curl -X GET --silent -k \
    "https://api.balena-cloud.com/v6/supervisor_release?\$top=1&\$select=image_name&\$filter=(supervisor_version%20eq%20%27${SUPERVISOR_TAG}%27)%20and%20(is_for__device_type/any(ifdt:ifdt/is_of__cpu_architecture/any(ioca:ioca/slug%20eq%20%27$(arch)%27)))" \
    -H "Content-Type: application/json" | jq -r '.d[].image_name')
    SUPERVISOR_VERSION=$(cut -d "/" -f 3 <<< "$SUPERVISOR_IMAGE")  
fi
echo "Current supervisor version: ${SUPERVISOR_VERSION}"

SUPERVISOR_TARGET=${SUPERVISOR_VERSION}
if [ -f /mnt/boot/HARMONI_SUPERVISOR_TARGET ]; then
    SUPERVISOR_TARGET=$(cat /mnt/boot/HARMONI_SUPERVISOR_TARGET)
    SUPERVISOR_TARGET_IMAGE=$(curl -X GET --silent -k \
    "https://api.balena-cloud.com/v6/supervisor_release?\$top=1&\$select=image_name&\$filter=(supervisor_version%20eq%20%27${SUPERVISOR_TARGET}%27)%20and%20(is_for__device_type/any(ifdt:ifdt/is_of__cpu_architecture/any(ioca:ioca/slug%20eq%20%27$(arch)%27)))" \
    -H "Content-Type: application/json" | jq -r '.d[].image_name')
    SUPERVISOR_TARGET_VERSION=$(cut -d "/" -f 3 <<< "$SUPERVISOR_TARGET_IMAGE")   
fi

echo "Target supervisor version: ${SUPERVISOR_TARGET_VERSION}"

if [[ "${SUPERVISOR_VERSION}" == "${SUPERVISOR_TARGET_VERSION}" ]]; then
    echo "Target supervisor already installed!"
else
    echo "Newer supervisor available, updating..."
    /usr/bin/update-balena-supervisor -i "${SUPERVISOR_TARGET_IMAGE}"
    echo "Supervisor update complete."
fi

exit 0

This script is essentially recreating the OS image that was pushed to the openbalena registry by the build process. The device has access to the registry via its API key. Thereā€™s a lot more detail around all of this stuff which is why I want to ultimately release the custom OS as a project, but I thought this might help anyone who is looking to do this in the meanwhile.

1 Like

Nice writeup! Appreciated. Iā€™ll have the team take a look at it.

Thanks to some guidance from @richbayliss I am happy to share a significantly refactored version of the custom host OS update script, it no longer needs to reassemble the docker image from scratch! I also found some code in the balena-supervisor repo that shows how devices can log in to the openbalena docker registry with username of d_ and password of the API key, So between these two things, there is no longer even a need to download hostapp images anymore, you can just log in to your private repo and pass the image location to the stock hostapp-update script - and voila, hostapp is updated!

Here is the new code - note this also includes code to update the supervisor as well. In our case we run it as a cron job every X minutes on our host OS to check for updates and do all of this automagically, but it can be manually run as well. Note that this applies to people using custom balena_os builds, if youā€™re using stock balena_os builds, your life will be much simplerā€¦

#!/bin/bash

# Hostapp update configuration
BALENA_API_ENDPOINT="$(jq --raw-output .apiEndpoint /mnt/boot/config.json)"
BALENA_API_KEY="$(jq --raw-output .deviceApiKey /mnt/boot/config.json)"
DEVICE_TYPE="$(jq --raw-output .deviceType /mnt/boot/config.json)"
FLEET_ID="$(jq --raw-output .applicationId /mnt/boot/config.json)"
DEVICE_UUID="$(jq --raw-output .uuid /mnt/boot/config.json)"
FLEET_TAG_KEY="os_version"
DEVICE_TAG_KEY="os_version"
readarray -t -d "." BALENA_API_ENDPOINT_ARR <<<"$BALENA_API_ENDPOINT"
unset BALENA_API_ENDPOINT_ARR[0]
BALENA_HOST=$(IFS=. ; echo "${BALENA_API_ENDPOINT_ARR[*]}")

# Get and parse current os image tag
CURR_IMAGE="$(cat /mnt/boot/HARMONI_IMAGE_TAG)"
readarray -t -d "_" CURR_IMAGE_SPLIT <<<"$CURR_IMAGE"
readarray -t -d "." CURR_HOSTOS_VAR <<<"${CURR_IMAGE_SPLIT[0]}"
CURR_VAR=${CURR_HOSTOS_VAR[-1]}
echo "Current hostos version: ${CURR_IMAGE}"

# Determine target os image for fleet and device
FLEET_TARGET_IMAGE=$(curl -s -H "Authorization: Bearer $BALENA_API_KEY" "$BALENA_API_ENDPOINT/v6/application_tag?\$filter=application%20eq%20${FLEET_ID}%20and%20tag_key%20eq%20%27${FLEET_TAG_KEY}%27" | jq -r '.d[0].value')
DEVICE_TARGET_IMAGE=$(curl -s -H "Authorization: Bearer $BALENA_API_KEY" "$BALENA_API_ENDPOINT/v6/device_tag?\$filter=device/uuid%20eq%20%27${DEVICE_UUID}%27%20and%20tag_key%20eq%20%27${DEVICE_TAG_KEY}%27" | jq -r '.d[0].value')

# Set target os image - prioritize device, then fleet, then default to latest
if [ ! -z $DEVICE_TARGET ]; then
    TARGET_IMAGE=$DEVICE_TARGET
elif [ ! -z $FLEET_TARGET ]; then
    TARGET_IMAGE=$FLEET_TARGET
else
    TARGET_IMAGE="latest"
fi

# If target os image is set to latest, determine the latest version
if [ "$TARGET_IMAGE" == "latest" ]; then
    # Get all release tags for target device type, sorted reverse by value
    RELEASES=$(curl -s -H "Authorization: Bearer $BALENA_API_KEY" "$BALENA_API_ENDPOINT/v6/release_tag?\$filter=release/any(r:r/belongs_to-application/any(bta:bta/slug%20eq%20%27balena_os/${DEVICE_TYPE}%27))&\$orderby=value%20desc" | jq -r '.d | map( [(.value)] ) | add')
    declare -a RELEASES_ARR=($(jq -r '.[]' <<<"$RELEASES"))
    for i in "${RELEASES_ARR[@]}"; do
        readarray -t -d "_" TARGET_IMAGE_SPLIT <<<"$i"
        readarray -t -d "." TARGET_HOSTOS_VAR <<<"${TARGET_IMAGE_SPLIT[0]}"
        TEST_VAR=${TARGET_HOSTOS_VAR[-1]}
        if [ "$TEST_VAR" == "$CURR_VAR" ]; then
            TARGET_IMAGE="$i"
            break
        fi
    done
else
    TARGET_IMAGE="$TARGET_IMAGE"
fi

echo "Target hostos version: ${TARGET_IMAGE}"        

# Determine if device is running its target image
if [ "$CURR_IMAGE" == "v$TARGET_IMAGE" ]; then
    echo "Target hostos already installed!"
else
    echo "Target hostos not installed."

    # Get image location and service from release
    RELEASE_ID=$(curl -s -H "Authorization: Bearer $BALENA_API_KEY" "$BALENA_API_ENDPOINT/v6/release_tag?\$filter=release/any(r:r/belongs_to-application/any(bta:bta/slug%20eq%20%27balena_os/${DEVICE_TYPE}%27))%20and%20tag_key%20eq%20%27version%27%20and%20value%20eq%20%27${TARGET_IMAGE}%27" | jq -r '.d[0].release.__id')
    IMAGE_ID=$(curl -s -H "Authorization: Bearer $BALENA_API_KEY" "$BALENA_API_ENDPOINT/v6/image-is_part_of-release?\$filter=is_part_of-release%20eq%20${RELEASE_ID}" | jq -r '.d[0].image.__id')
    IMAGE_RECORD=$(curl -s -H "Authorization: Bearer $BALENA_API_KEY" "$BALENA_API_ENDPOINT/v6/image?\$filter=id%20eq%20${IMAGE_ID}")
    IMAGE_LOCATION=$(jq -r .d[0].is_stored_at__image_location <<< "$IMAGE_RECORD")
    IMAGE_SERVICE=$(echo $IMAGE_LOCATION | cut -d/ -f1)
    
    balena login $IMAGE_SERVICE -u d_$DEVICE_UUID -p $BALENA_API_KEY

    echo "Updating host OS..."
    hostapp-update -i $IMAGE_LOCATION

    if [ $? -eq 0 ]; then
        echo "Host OS update complete, rebooting..."
        reboot
        exit 0
    else
        echo "Host OS update error."
        exit 1
    fi
fi

## Update supervisor

source /etc/balena-supervisor/supervisor.conf
if [ -z "$SUPERVISOR_IMAGE" ]; then
    SUPERVISOR_IMAGE=$(curl -X GET --silent -k \
    "https://api.balena-cloud.com/v6/supervisor_release?\$top=1&\$select=image_name&\$filter=(supervisor_version%20eq%20%27${SUPERVISOR_TAG}%27)%20and%20(is_for__device_type/any(ifdt:ifdt/is_of__cpu_architecture/any(ioca:ioca/slug%20eq%20%27$(arch)%27)))" \
    -H "Content-Type: application/json" | jq -r '.d[].image_name')
fi
SUPERVISOR_VERSION=$(cut -d "/" -f 3 <<< "$SUPERVISOR_IMAGE")  
echo "Current supervisor version: ${SUPERVISOR_VERSION}"

SUPERVISOR_TARGET=${SUPERVISOR_VERSION}
if [ -f /mnt/boot/HARMONI_SUPERVISOR_TARGET ]; then
    SUPERVISOR_TARGET=$(cat /mnt/boot/HARMONI_SUPERVISOR_TARGET)
    SUPERVISOR_TARGET_IMAGE=$(curl -X GET --silent -k \
    "https://api.balena-cloud.com/v6/supervisor_release?\$top=1&\$select=image_name&\$filter=(supervisor_version%20eq%20%27${SUPERVISOR_TARGET}%27)%20and%20(is_for__device_type/any(ifdt:ifdt/is_of__cpu_architecture/any(ioca:ioca/slug%20eq%20%27$(arch)%27)))" \
    -H "Content-Type: application/json" | jq -r '.d[].image_name')
    SUPERVISOR_TARGET_VERSION=$(cut -d "/" -f 3 <<< "$SUPERVISOR_TARGET_IMAGE")   
fi

echo "Target supervisor version: ${SUPERVISOR_TARGET_VERSION}"

if [[ "${SUPERVISOR_VERSION}" == "${SUPERVISOR_TARGET_VERSION}" ]]; then
    echo "Target supervisor already installed!"
else
    echo "Newer supervisor available, updating..."
    /usr/bin/update-balena-supervisor -i "${SUPERVISOR_TARGET_IMAGE}"
    echo "Supervisor update complete."
fi

exit 0
1 Like

Thank you for your contribution to the community @drcnyc . Appreciated.

Hi @drcnyc can you expand on life being simpler if using the stock balena os builds? We are running openbalena and devices with stock images and also need to perform Host OS updates.

Thanks.

@g.corrigan In thinking about this further, while a lot of things would simplify when using a stock OS, those things have to do with the OS build process and not having to push OS builds to the registry. With stock Balena OS you can just point to the public registry which has all of the images you need already there. But unfortunately when using stock Balena OS it also means you canā€™t build in the cron job and script to actually run the update, so Iā€™m afraid I donā€™t have a good solution for automated host os updates with stock Balena OS.

@drcnyc thanks for the reply. We will look into moving to custom builds instead.