State-scripts documentation: how to use them without Yocto

The documentation doesn’t really help to understand how to implement and include state scripts.
Question I have (and probably other newcomers will):

  • can state scripts be included in artifacts or they are a part of Mender client? (covered in docs)
  • how to design them? (covered in docs)
  • where to place them/how to include in the created artifact/image?
  • can they differ for different types of artifact/update-modules (e.g. rootfs vs single file) ?

Will be extremely thankful for any hints where to look at.

Regarding storage location, see https://docs.mender.io/2.0/artifacts/state-scripts#root-file-system-and-artifact-scripts for more details. Basically the “Root Filesystem” scripts go in /etc/Mender/scripts and the artifact scripts are added by the mender-artifact invocation. See ‘mender-artifact write module-image --help’ for more details.

The specifics for full-image versus partial updates can be found here: https://github.com/mendersoftware/mender/blob/2.0.0/Documentation/update-modules-v3-file-api.md

This is an ambiguous question. No, they can’t, in the sense that they are update module agnostic, they don’t care (and in fact, cannot find out) which type of update is in progress. This is intentional, since in the future, multiple updates may be supported inside one artifact.

But on the other hand, yes they can, in the sense that you can include any script you want in the artifact, and if you make a new artifact, with a different update type, you can include a different state script.

so no out-of-the-box way of adding state scripts to artifacts generated by mender-convert for now?

so no out-of-the-box way of adding state scripts to artifacts generated by mender-convert for now?

Correct, you will need to create a new Mender Artifact using mender-artifact and the ext4 image once the mender-convert process is completed.

One more question:
The naming of state scripts intended to be included in roofs is well documented.
But the naming requirements for scripts coming inside artifacts are confusing.

From one side, docs say it should follow the following format, same as ‘normal’ state scripts:

<STATE_NAME>_<ACTION>_<ORDERING_NUMBER>_<OPTIONAL_DESCRIPTION>

From the other hand, Mender Artifact file format spec shows example of state-scripts named as (using dots)

+---header.tar.gz (tar format)
  |    |
  |    +---header-info
  |    |
  |    +---scripts
  |    |    |
  |    |    +---State.Enter
  |    |    +---State.Leave
  |    |    +---State.Error
  |    |    `---<more scripts>

Another thing which is unclear (just a general case, not related to my use case) is whether Artifact-prefixed state scripts are only intended to be included in the artifacts or can be placed in rootfs (/etc/mender/scripts) as well?


My use case is simple and common: I need to copy saved wifi credentials (wpa_supplicant.conf file in case of raspbian) from old rootfs to new one when full image updates are performed to not loose connectivity after reboot (considering symlinking to /data/ is not an option). I saw retain-ssh-keys example, but as I plan to perform not only full-image updates, but also single file updates without changing rootfs partition, I don’t need to perform this script before every artifact install. That’s why I think such script should be included in Artifacts with full-os updates only. Is it the right path to follow?

@kacf might have some input on the naming inconsistencies.

Another thing which is unclear (just a general case, not related to my use case) is whether Artifact -prefixed state scripts are only intended to be included in the artifacts or can be placed in rootfs ( /etc/mender/scripts ) as well?

Artifact prefixed artifacts are only intended to be included in Artifacts and can not be placed in rootfs.

That’s why I think such script should be included in Artifacts with full-os updates only. Is it the right path to follow?

Yes, this looks like a good path for me.

Ok I will summarize info to potentially help others

can state scripts be included in artifacts or they are a part of Mender client?

Both. Client-side state scripts (those for states which are not prefixed with Artifact) are executed by the mender client regardless of the contents or type of the artifact it installs.

State scripts for states perfixed with Artifact are intended to be only included in the Artifact itself, thus may vary between Artifacts.

how to design them?

Some examples of client-side state scripts are in the mender client repo. Artifact state scripts may follow the same logic and should follow the same naming format, i.e.
<STATE_NAME>_<ACTION>_<ORDERING_NUMBER>_<OPTIONAL_DESCRIPTION>

retain-ssh-keys example may give an idea how to transfer files between old and new filesystems when performing full-rootfs updates, if storing persistent files in /data/ is not an option. Yet, IMHO this type of actions should rather be included in Artifact as ArtifactInstall_Enter_... script as such actions are only needed for full-os updates and not needed to be executed if you perform updates without swithing partitions (i.e. using update modules)

where to place state scripts/how to include in the created artifact/image?

Client state scripts should be placed in /etc/Mender/scripts of the new rootfs. To add them to an SD image, you can use Modifying disk images workflow from the docs.

To add client state scripts to a Mender artifact, you can use mender-artifact cp. Keep in mind it is a slow process and takes ~5 min per one file on ~300 MB Artifacts.

In addition, there’s no way of adding Artifact state scripts to already created Mender artifacts with mender-artifact. Thus if you use mender-convert workflow and need to include both clien and artifact state scripts, you can discard .mender file generated by mender-convert, mount .ext4 and copy all required client state scripts inside (as well as your mender.config, identity and inventory if needed), and then create mender Artifact from .ext4 yourself using mender-artifact write rootfs-image adding all your Artifact scripts by using --script, something like

mender-artifact --compression "lzma" write rootfs-image   \
   --file "$releaseName-mender.ext4"      \
   --device-type "raspberrypi3"           \
   --artifact-name "$releaseName-mender.mender"  \
   --script "$scriptsDir/ArtifactInstall_Enter_01_retain_wpa_supplicant" \ 
   --script "$scriptsDir/another_script"  

To verify that the scripts are added to the Artifact, you can open .mender as a .tar and check the contents of /header.tar.gz/scripts/

3 Likes

From one side, docs say it should follow the following format, same as ‘normal’ state scripts:

From the other hand, Mender Artifact file format spec shows example of state-scripts named as (using dots)

Yeah, this is wrong/outdated. I have made some cleanups here.

Can the state scripts be used along side module-image or do the states needs to be implement as part of the update module it self ?

My artifact looks like this but I don’t see the state script ArtifactCommit_Leave being triggered.

Mender artifact:
  Name: dummy_artifact_v1.0.0
  Format: mender
  Version: 3
  Signature: signed but no key for verification provided; please use `-k` option for providing verification key
  Compatible devices: '[armv7l]'
  Provides group:
  Depends on one of artifact(s): []
  Depends on one of group(s): []
  State scripts:
    ArtifactCommit_Leave_00

Updates:
    0:
    Type:   custom-image
    Provides: Nothing
    Depends: Nothing
    Metadata: Nothing
    Files:
      name:     artifact_info
      size:     36
      modified: 2020-06-06 17:10:17 +0530 IST
      checksum: 7f760b613d878a38cb1b4807a2aaef1b340efdcdc21454970d13c463cf8bb3f7
      name:     dummy_artifact_v1.0.0.tar.gz
      size:     3072
      modified: 2020-06-06 17:10:17 +0530 IST
      checksum: 43e380153fe157ac9f5f45d7487bb182d571c8c47c225b3ef5284b14b0ef76f6```

State scripts should be triggered no matter whether you are using
rootfs-image or module-image. Can you try a different type of state
script, such as ArtifactInstall_Enter to see if that works?

Do I understand this correctly?

If I need to add Artifact state scripts and I use mender-convert, I must discard the mender artifact created and add the state script using mender-artifact [...] --script "path/to/my/script"?

Best is to use an overlay. This page explains how to add state scripts when using mender-convert: https://docs.mender.io/system-updates-debian-family/convert-a-mender-debian-image/state-scripts

Thanks a lot for pointing me to the correct location.

When following the guide you posted, I do get an error message stating:

	mender-artifact --compression gzip write rootfs-image --file work/rootfs.img --output-path deploy/2020-08-20-raspios-buster-armhf-lite-raspberrypi-cm3-mender.mender --artifact-name 0_0_0-ensiDebug --device-type raspberrypi-cm3  --state ArtifactStateScripts/ArtifactInstall_Leave_1_MigrateConfiguration.sh
	Incorrect Usage: flag provided but not defined: -state

I assume that this has to do with the file path I provide that contains the state script is somehow not accessible. Where should the state scripts be put in order for them to be accessible.

Note that I’m using docker-mender-convert

The command line option should be “–script” rather than “–state”. The files are just loaded with the pathname given so if it’s a relative path it should be relative to the directory you are in when you invoke mender-artifact.

Drew

Outch, thanks for correction!

The more I read about this more complicate it sounds

So to give an example of what I am trying to achieve.

Recently I found out that I had provide a patch to systemd so that I could change timezone in read only root file system.
So the fix was in two areas.

  1. I had to update rootfs
  2. Add necessary file in persistent data section

As shown below

root@raspberrypi-cm3:/etc# ls -lrt | grep localtime
lrwxrwxrwx 1 root root 22 Aug 19 11:56 localtime -> /data/system/localtime
root@raspberrypi-cm3:/etc#

So now I have to create that structure (/data/system/localtime) in persistent data location.

And to achieve point 2, I have to create a state script to do so (As shown below)

#!/bin/sh
#
# Retain provide configuration files for timedatectl
#

mkdir /data/system/
if [[ $? -eq 0 ]] ; then
	echo "Universal" > /data/system/timezone
else
	exit 1
fi

ln -s /usr/share/zoneinfo/Universal /data/system/localtime

if [[ $? -ne 0 ]]; then
	exit 1
fi  

Then I need to name that state script (example Download_Leave_02)
So once I create the state script then I run this command

mender-artifact --compression "lzma" write rootfs-image   \
   --file "$releaseName-mender.ext4"      \
   --device-type "raspberrypi3"           \
   --artifact-name "$releaseName-mender.mender"  \
   --script "$scriptsDir/Download_Leave_02" \ 
   --script "$scriptsDir/another_script"

And then create a delta update and use that output to update my machine?

Other question I do have a .ext4 file but I don’t have an mender.ext4 file. Is the naming convention that important?

That sounds about right. The mender.ext4 file should be fine.

I did try that out. But I keep getting the below mentioned error

error: transient error: error executing leave script for after-reboot state: error running leave state script(s) for ArtifactReboot_Leave state: statescript: error executing ‘ArtifactReboot_Leave_02’: -1 : fork/exec /var/lib/mender/scripts/ArtifactReboot_Leave_02: no such file or directory

But I do see the file

root@raspberrypi-cm3:/var/lib/mender/scripts# ls
ArtifactReboot_Leave_02

This is the command that I am using to generate the mender artifact.

mender-artifact --compression "lzma" write rootfs-image --file "$releaseName-mender.ext4" --device-type "raspberrypi-cm3" --artifact-name "$releaseName-mender.mender" --script "$scriptsDir/ArtifactReboot_Leave_02"

What am I missing? Is my state ordering wrong?