Integrating Mender State Scripts in Yocto - A Step by Step Guide

Integrating Mender State Scripts in Yocto - A Step by Step Guide

This tutorial will explain the concept of state scripts in mender and give an example on how to integrate them into yocto.

Prerequisites

  • kas installed on the system
  • Raspberry pi 4 with an SDCard available
  • No explanation is needed on how to flash and SDcard

This tutorial has been tested with Ubuntu 20.04.

Setting the field

The example will be shown on the Raspberry Pi 4.
To start the example a working Yocto build with mender integration must be set up.

Configuring the build

Please save the linked content into a file called mender-rpi4.yml.

Set the MENDER_TENANT_TOKEN the value associated with your account.

Building the basic image

Once that is set run the command below and expect a few hours for the build process to finish.

kas shell mender-rpi4.yml
bitbake core-image-minimal

Gather the build files and upload them to Hosted Mender or flash to the SDCard.

Introducing state scripts

State scripts are a way to customize the update process. This is a similar concept to the pre-install or post-install scripts mechanism provided by the debian package management system.

Without the scripts, the unmodified update process goes through a list of states from start to finish. In a simplified manner it looks like this:

State scripts allow extension of those states with custom steps:

The image shows all the state scripts for a non error case, you can implement multiple for the same state or none at all.

How do the state scripts reach the device?

There are two ways for state scripts to reach the device and get executed:

  • it comes embedded in the update which is taking place - Artifact state scripts
  • it exists on the device up front before the update is started - Root file system state scripts

How to include Mender State Scripts in the Yocto Build Process

Including the state scripts into yocto will require a creation of a dedicated recipe which uses a helper class for installing state scripts.

The steps below, will create the new layer with the state scripts recipe in it.

kas shell mender-rpi4.yml
bitbake-layers create-layer meta-example
bitbake-layers add-layer meta-example

mkdir -p  meta-example/recipes-example/state-scripts/files

cat > meta-example/recipes-example/state-scripts/files/Download_Leave_10 << EOF
#!/usr/bin/env sh
>&2 echo "Printed from Download_Leave_10"
EOF

cat > meta-example/recipes-example/state-scripts/files/ArtifactInstall_Enter_10 << EOF
#!/usr/bin/env sh
>&2 echo "Printed from ArtifactInstall_Enter_10"
EOF

cat >  meta-example/recipes-example/state-scripts/state-scripts.bb << "EOF"
LICENSE="CLOSED"
FILESEXTRAPATHS_prepend := "${THISDIR}/files:"
SRC_URI += "file://Download_Leave_10;subdir=${BPN}-${PV} \
            file://ArtifactInstall_Enter_10;subdir=${BPN}-${PV}"
inherit mender-state-scripts

do_compile() {
    cp Download_Leave_10 ${MENDER_STATE_SCRIPTS_DIR}/Download_Leave_10
    cp ArtifactInstall_Enter_10 ${MENDER_STATE_SCRIPTS_DIR}/ArtifactInstall_Enter_10
}

EOF

With the recipe and layer created, the state-script recipe must now be included into the build.
In addition to that a new name should be given to separate the newly built files from the old ones.

cat >> conf/local.conf << EOF
IMAGE_INSTALL_append = " state-scripts"
EOF

sed -i 's/^MENDER_ARTIFACT_NAME.*/MENDER_ARTIFACT_NAME = "rpi4-kas-build-state-scripts"/g' conf/local.conf

Building the image with state scripts

Once that is done kick off the build.

bitbake core-image-minimal

Gather the build files and upload them to Hosted Mender.

Seeing the scripts in action

As you deploy the new release to your board the Artifact state script (ArtifactInstall_Enter_10) will be executed.
This can be tracked by going to the device shell (either via UART/SSH or Remote Terminal) and running journalctl -fu mender-client.

You should see an output like this when doing an update from version rpi4-kas-build to rpi4-kas-build-state-scripts:

level=info msg="Executing script: ArtifactInstall_Enter_10"
level=info msg="Collected output (stderr) while running script /var/lib/mender/scripts/ArtifactInstall_Enter_10\nPrinted from ArtifactInstall_Enter_10\n\n---------- end of script output"

There is no output for the Download_Leave_10 as it is a Root file system state scripts and it isn’t present on the rpi4-kas-build version. However as the device is now running rpi4-kas-build-state-scripts, this version has /etc/mender/scripts/Download_Leave_10 present.

As a result of this doing an update from version rpi4-kas-build-state-scripts to rpi4-kas-build will give the following output:

level=info msg="Executing script: Download_Leave_10"
level=info msg="Collected output (stderr) while running script /etc/mender/scripts/Download_Leave_10\nPrinted from Download_Leave_10\n\n---------- end of script output"

Conclusion

In this tutorial we have shown how to extend the default Mender OTA behaviour with state scripts.
The concept behind the states scripts was explained where they were conceptually compared to the pre and post install scripts of package managers.
Additionally an example is given on integrating the state scripts in the Yocto build process which required the creation of a custom recipe.

Appendix

These are chapters and know-how steps which are referenced throughout the tutorial.

How to find the value of the tenant token?

To get the tenant token value, log into Hosted Mender, from the upper left corner select My Organisation and copy the Organisation token to clipboard.

This is the value of your MENDER_TENANT_TOKEN.

getting-the-token

Back to “Configuring the build”

Gathering the build output

As a result of the build Yocto creates a lot of files buried deep into the directory structure.
From a practical stand point, we only need two.

The command below will gather needed files in the artifacts into an accessible directory.

# Executed from the build dir (bitbake in path)
mkdir artifacts

BITBAKE_ENV=$(bitbake -e core-image-minimal | grep -E "^DEPLOY_DIR_IMAGE=|^IMAGE_LINK_NAME")
export $(echo $BITBAKE_ENV | sed 's/"//g')
cp ${DEPLOY_DIR_IMAGE}/${IMAGE_LINK_NAME}.mender artifacts
cp ${DEPLOY_DIR_IMAGE}/${IMAGE_LINK_NAME}.sdimg artifacts

Uploading and flashing

Two artifacts will be available in the artifacts directory.
They have different purposes:

  • .sdimage
    • flash it directly on the SDCard
    • this only needs to be done once
  • .mender
    • represents the release which can be deployed to a device over the air

Please flash your SDCard with the .sdimg, place it in the Raspberry Pi 4 and power it up.
The device should soon be visible in Hosted Mender as pending.

In addition to that please upload the .mender file (Mender artifact) to Hosted Mender.
Log into Hosted Mender, select Releases from the menu on the left and click Upload.

mender-rpi4.yml

The content below should be copied to the file called mender-rpi4.yml.

header:
  version: 10

machine: raspberrypi4

repos:
  poky:
    url: https://git.yoctoproject.org/git/poky
    refspec: "dunfell"
    layers:
      meta:
      meta-poky:
      meta-yocto-bsp:

  meta-openembedded:
    url: http://git.openembedded.org/meta-openembedded
    refspec: "dunfell"
    layers:
      meta-oe:
      meta-python:
      meta-networking:
      meta-multimedia:

  meta-raspberrypi:
    url: https://github.com/agherzan/meta-raspberrypi
    refspec: "dunfell"
    layers:
      .:

  meta-mender:
    url: https://github.com/mendersoftware/meta-mender
    refspec: "dunfell"
    layers:
      meta-mender-core:
      meta-mender-demo:
      meta-mender-raspberrypi:

bblayers_conf_header:
  standard: |
    POKY_BBLAYERS_CONF_VERSION = "1"
    BBPATH = "${TOPDIR}"
    BBFILES ?= ""

local_conf_header:
  enable_systemd: |
    DISTRO_FEATURES_append = " systemd"
    VIRTUAL-RUNTIME_init_manager = "systemd"
    VIRTUAL-RUNTIME_initscripts = ""

  general_helpers: |
    INHERIT += "rm_work"
    IMAGE_FEATURES += "ssh-server-openssh allow-empty-password debug-tweaks"
    IMAGE_INSTALL_append = " python3"

  rpi_helpers: |
    ENABLE_UART = "1"
    RPI_EXTRA_CONFIG="dtoverlay=disable-bt"

  mender_device_integration: |
    IMAGE_LINK_NAME_append = "-${MENDER_ARTIFACT_NAME}"
    INHERIT += "mender-full"
    RPI_USE_U_BOOT = "1"
    IMAGE_FSTYPES_remove += " rpi-sdimg"
    MENDER_FEATURES_ENABLE_append = " mender-uboot mender-image-sd"
    MENDER_FEATURES_DISABLE_append = " mender-grub mender-image-uefi"
    MENDER_BOOT_PART_SIZE_MB = "40"

  dynamic: |
    MENDER_ARTIFACT_NAME = "rpi4-kas-build"
    MENDER_SERVER_URL = "https://hosted.mender.io"
    MENDER_TENANT_TOKEN = "" <--- Insert correct value

Back to “Configuring the build”