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 archive
sw-description
sw-description.sig
install
.lua
artifact1.bin
artifact2.bin
6734 blocks
--- Done
SWU image: 9yNAUeKHtGGZ.swu
Script
#!/usr/bin/env bash
#
# Copyright (c) 2024. Robert Bosch GmbH
#
# DISCLAIMER: This script is for demonstration purposes only.
#
set
-e
# fail fast
VERSION=
"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 them
into 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 information
Example
./$(
basename
"$0"
) -a \$
"ACCESS_TOKEN"
-r
"l71jmPfjDl2r"
""
"
if
!
command
-
v
jq 2>&1 >
/dev/null
;
then
echo
"jq could not be found"
exit
1
fi
POSITIONAL=()
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
;;
esac
done
set
--
"${POSITIONAL[@]}"
# restore positional parameters
if
[ -z
"${ACCESS_TOKEN}"
];
then
echo
"Parameter --access-token (-a) is mandatory! For more information, use --help."
exit
1
fi
if
[ -z
"${RECIPE}"
];
then
echo
"Parameter --recipe-id (-r) is mandatory! For more information, use --help."
exit
1
fi
download() {
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 + signature
echo
"+ 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 folder
while
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 folder
while
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
$
file
done
<<<
"$(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
"+===========================================================================+ "