Create SWUpdate image (*.swu) from recipe
Disclaimer
This script is for demonstration purposes only.
Table of contents:
Introduction
SWUpdate is a popular in-field software update solution for embedded Linux systems. For SWUpdate clients, the update is provided as an image or container (*.swu), which is a CPIO archive that follows a defined internal structure (cf. SWUpdate: Single image delivery):
|
Position |
SWUpdate |
System Software Update |
|
1 |
CPIO header |
|
|
2 |
sw-description |
Default recipe file is a sw-description compatible JSON. |
|
3 |
sw-description.sig |
Signature of the recipe file. |
|
4 - n |
script |
Scripts contained in System distribution sets with a distribution set of type scripts |
|
n - m |
artifacts |
Artifacts contained in Module update definitions with a distribution set, that contains a software module of type firmware |
The provided script uses the given recipe Id and access token to download the required artifacts from the backend. Afterwards, it creates the image.
Usage
To use the script follow the following steps:
Download the script and store as swu-creator.sh
Obtain recipe Id and access token from the UI.
Run the script
The CPIO executable on MacOs (bsdcpio) does not contain the required format option --format crc It is only available on Linux with gnucpio.
Example
$ ./swu-creator.sh -a "$ACCESS_TOKEN" -r "9yNAUeKHtGGZ" +===========================================================================+ | _______ ___ _ _____ _ | | / ____\ \ / / | | | / ____| | | | || (___ \ \ /\ / /| | | | ______ | | _ __ ___ __ _| |_ ___ _ __ | | \___ \ \ \/ \/ / | | | | |______| | | | '__/ _ \/ _' | __/ _ \| '__|| | ____) | \ /\ / | |__| | | |____| | | __/ (_| | || (_) | | | ||_____/ \/ \/ \____/ \_____|_| \___|\__,_|\__\___/|_| | | by Bosch Digital| +===========================================================================+ Version: 1.2.0 Endpoint: https://system.eu1.bosch-iot-rollouts.com--- Start downloading required artifacts+ Download recipe file for recipe id: 9yNAUeKHtGGZ+ Download signature+ Download script: install.lua (28376 bytes)+ Download artifact: artifact1.bin (1704474 bytes)+ Download artifact: artifact2.bin (1704436 bytes)--- Finished downloading required artifacts--- Create CPIO archivesw-descriptionsw-description.siginstall.luaartifact1.binartifact2.bin6734 blocks--- DoneSWU image: 9yNAUeKHtGGZ.swuScript
#!/usr/bin/env bash## Copyright (c) 2024. Robert Bosch GmbH## DISCLAIMER: This script is for demonstration purposes only.#set -e # fail fastVERSION="1.2.1"SOUP_ENDPOINT="https://system.eu1.bosch-iot-rollouts.com"INSTALL_API="${SOUP_ENDPOINT}/api/install/v1"FILES=""FILE_SEPERATOR="%"USAGE="""Version: ${VERSION}Usage: ./$(basename "$0") [-arhv]Script to download recipe, signature, and all artifacts and package theminto a *.swu (CPIO) archive.Parameter: -a, --access-token Access token with scope test-installer -r, --recipe-id Recipe ID of a recipe in state > RELEASE_CANDIATE -h, --help Get usage information -v, --version Get version informationExample ./$(basename "$0") -a \$"ACCESS_TOKEN" -r "l71jmPfjDl2r""""if ! command -v jq 2>&1 >/dev/null; then echo "jq could not be found" exit 1fiPOSITIONAL=()while [[ $# -gt 0 ]]; do key="$1" case $key in -h | --help) echo "${USAGE}" exit ;; -v | --version) echo "Version: ${VERSION}" exit ;; -a | --access-token) ACCESS_TOKEN="$2" shift # past argument shift # past value ;; -r | --recipe) RECIPE="$2" shift # past argument shift # past value ;; esacdoneset -- "${POSITIONAL[@]}" # restore positional parametersif [ -z "${ACCESS_TOKEN}" ]; then echo "Parameter --access-token (-a) is mandatory! For more information, use --help." exit 1fiif [ -z "${RECIPE}" ]; then echo "Parameter --recipe-id (-r) is mandatory! For more information, use --help." exit 1fidownload() { local url=${1} local filename=${2} status_code=$(curl --write-out %{http_code} --silent --location \ -X 'GET' \ "${url}" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H 'accept: application/octet-stream' \ -o "${filename}") if [[ "$status_code" -eq 401 ]]; then echo "[ERROR] HTTP 401 - Unauthorized: Please provide a valid access token." exit 1 fi if [[ "$status_code" -eq 403 ]]; then echo "[ERROR] HTTP 403 - Forbidden: Please verify that your user has the required roles or scopes." exit 1 fi if [[ "$status_code" -eq 404 ]]; then message=$(curl -X 'GET' "${url}" --location -H "Authorization: Bearer ${ACCESS_TOKEN}" --silent -H 'accept: application/octet-stream') printf "[ERROR] HTTP 404 - Not found: ${message}" printf "\n For missing recipes, please verify that the recipe is in one of the following states: RELEASE_CANDIDATE, RELEASED, REVOKED, INACTIVE." printf "\n For missing artifacts, check if the distribtion set and its software modules are still present via the UI: https://console.eu1.bosch-iot-rollouts.com/distribution-sets \n" exit 1 fi}echo ""echo " +===========================================================================+ "echo " | _______ ___ _ _____ _ | "echo " | / ____\ \ / / | | | / ____| | | | "echo " || (___ \ \ /\ / /| | | | ______ | | _ __ ___ __ _| |_ ___ _ __ | "echo " | \___ \ \ \/ \/ / | | | | |______| | | | '__/ _ \/ _' | __/ _ \| '__|| "echo " | ____) | \ /\ / | |__| | | |____| | | __/ (_| | || (_) | | | "echo " ||_____/ \/ \/ \____/ \_____|_| \___|\__,_|\__\___/|_| | "echo " | by Bosch Digital| "echo " +===========================================================================+ "echo ""echo " Version: ${VERSION}"echo " Endpoint: ${SOUP_ENDPOINT}"echo ""echo "--- Start downloading required artifacts"# Download recipe + signatureecho "+ Download recipe file for recipe id: ${RECIPE}"download "${INSTALL_API}/recipes/${RECIPE}/file" "sw-description"FILES+="sw-description"echo "+ Download signature"download "${INSTALL_API}/recipes/${RECIPE}/signature" "sw-description.sig"FILES+="${FILE_SEPERATOR}sw-description.sig"# iterate over recipe to download all scripts to artifacts folderwhile read script; do FILENAME=$(echo "$script" | jq -r .filename) ID=$(echo "$script" | jq -r .id) SIZE=$(echo "$script" | jq -r .byteSize) # handle case, if no script exists if [ -z "${ID}" ]; then break; fi echo "+ Download script: ${FILENAME} (${SIZE} bytes)" download "${INSTALL_API}/software-artifacts/${ID}/file" "${FILENAME}" FILES+="${FILE_SEPERATOR}${FILENAME}"done <<<"$(jq -c '.software.scripts[]' sw-description)"# iterate over recipe to download all firmware to artifacts folderwhile read image; do FILENAME=$(echo $image | jq -r '.filename') ARTIFACT=$(echo $image | jq --arg filename "${FILENAME}" '.artifacts[] | select(.filename == $filename)') ID=$(echo "$ARTIFACT" | jq -r .id) SIZE=$(echo "$ARTIFACT" | jq -r .byteSize) echo "+ Download artifact: ${FILENAME} (${SIZE} bytes)" download "${INSTALL_API}/software-artifacts/${ID}/file" "${FILENAME}" FILES+="${FILE_SEPERATOR}${FILENAME}"done <<<"$(jq -c '.software.images[] | { "filename": .filename, "artifacts": ._software.artifacts }' sw-description)"echo "--- Finished downloading required artifacts"echo ""echo "--- Create CPIO archive"LIST_OF_FILES=$(echo "$FILES" | tr "${FILE_SEPERATOR}" '\n')while read file; do echo $filedone <<<"$(echo "$LIST_OF_FILES")" | cpio --create --format crc --verbose >${RECIPE}.swu# '--format crc' only works with gnucpio (i.e. not on MacOs)echo "--- Done"echo ""echo "SWU image: ${RECIPE}.swu"echo "+===========================================================================+ "