# Customized rollback example - banner

**URL:** <https://hub.mender.io/t/customized-rollback-example-banner/8151>\
**Category:** Update Modules\
**Created:** [December 23, 2025, 11:05am UTC](https://hub.mender.io/t/customized-rollback-example-banner/8151 "2025-12-23T11:05:05Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![TheYoctoJester](https://yyz2.discourse-cdn.com/flex036/user_avatar/hub.mender.io/theyoctojester/32/1444_2.png) [@TheYoctoJester](https://hub.mender.io/u/TheYoctoJester)\
**Post date:** [December 23, 2025, 11:05am UTC](https://hub.mender.io/t/customized-rollback-example-banner/8151/1 "2025-12-23T11:05:05Z")

</div>

### Description

The Banner Update Module manages the system’s message of the day (MOTD) displayed when users log in via SSH or console. It uses metadata-driven updates to change the banner text without requiring any file payload, supports rollback functionality, and operates without requiring system reboots.

### Specification

| | |
| --- | --- |
| Module name | banner |
| Supports rollback | yes |
| Requires restart | no |
| Artifact generation script | yes |
| Full operating system updater | no |
| Source code | [Update Module](https://github.com/mendersoftware/mender-update-modules/tree/master/banner/module/banner), [Artifact Generator](https://github.com/mendersoftware/mender-update-modules/tree/master/banner/module-artifact-gen/banner-artifact-gen) |
| Maintainer | Community |

## Prepare the device

This section describes how to setup your target device, i.e. the device to be updated. This will also be referred to as the device environment.

All commands outlined in this section should be run in the device environment.

### Prerequisites

This update module has the following prerequisites for the device environment:

- [Install the Mender client](https://docs.mender.io/client-installation/install-with-debian-package), version 2.0 or later
- A recent version of the JSON parser `jq` needs to be installed on the device
- A POSIX-compatible shell

### Install the Update Module

Download the latest version of this Update Module by running:

```bash
mkdir -p /usr/share/mender/modules/v3 && wget -P /usr/share/mender/modules/v3 https://raw.githubusercontent.com/mendersoftware/mender-update-modules/master/banner/module/banner

```

## Prepare the development environment on your workstation

This section describes how to set up your development environment on your workstation.

All commands outlined in this section should be run in the development environment.

### Prerequisites

This Update Module has the following prerequisites for the development environment:

- [Install mender-artifact](https://docs.mender.io/downloads), version 3.1.0 or later
- A recent version of the JSON parser `jq`

### Artifact creation

For convenience, an Artifact generator tool `banner-artifact-gen` is provided along the module. This tool will generate Mender Artifacts in the same format that the Update Module expects them.

Download `banner-artifact-gen`, by running the following command:

```bash
wget https://raw.githubusercontent.com/mendersoftware/mender-update-modules/master/banner/module-artifact-gen/banner-artifact-gen

```

Make it executable:

```bash
chmod +x banner-artifact-gen

```

Now generate a Mender Artifact using the following command:

```bash
ARTIFACT_NAME="my-banner-update-1.0"
DEVICE_TYPE="my-device-type"
OUTPUT_PATH="my-banner-update-1.0.mender"
BANNER_TEXT="Welcome to Production!"
./banner-artifact-gen -n ${ARTIFACT_NAME} -t ${DEVICE_TYPE} -o ${OUTPUT_PATH} -b "${BANNER_TEXT}"

```

- `ARTIFACT_NAME` - The name of the Mender Artifact
- `DEVICE_TYPE` - The compatible device type of this Mender Artifact
- `OUTPUT_PATH` - The path where to place the output Mender Artifact. This should always have a `.mender` suffix
- `BANNER_TEXT` - The banner text to display as the message of the day

You can either deploy this Artifact in managed mode with the Mender server (upload it under Releases in the server UI) or by using the Mender client only in [Standalone deployments](https://docs.mender.io/artifact-creation/standalone-deployment).

### Artifact technical details

The Mender Artifact used by this Update Module has no payload files. Instead it uses the `Metadata` field to store the banner text, which will be written to `/etc/motd` by the device. This meta-data is composed by a single `banner_text` JSON key with the text to be displayed.

As an example, the artifact created as described above can be inspected with `mender-artifact read my-banner-update-1.0.mender`, which yield the following information:

```bash
Updates:
  - Type: banner
    Provides:
      rootfs-image.banner.version: my-banner-update-1.0
    Depends: {}
    Clears Provides: [rootfs-image.banner.*]
    Metadata:
      {
        "banner_text": "Welcome to Production!"
      }
    Files: []

```

# Full explanation and tutorial

## Understanding the Implementation

The banner module demonstrates a metadata-only update pattern. Unlike modules that deploy payload files, the banner text is embedded directly in the artifact metadata. This approach suits small configuration values where a separate payload file would be unnecessary overhead.

### How Metadata-Only Artifacts Work

Standard update modules receive payload files in `$FILES/files/`. The banner module instead reads from `$FILES/header/meta-data`, a JSON file containing artifact metadata:

```json
{
  "banner_text": "Welcome to Production!"
}

```

The artifact generator creates this structure using `mender-artifact write module-image` with the `--meta-data` flag rather than `--file`:

```bash
mender-artifact write module-image \
  --type banner \
  --artifact-name "$ARTIFACT_NAME" \
  --device-type "$DEVICE_TYPE" \
  --meta-data "$METADATA_FILE" \
  --output-path "$OUTPUT_PATH"

```

Inspecting a banner artifact with `mender-artifact read` shows no payload files—only metadata in the header.

### Module Internals

The module handles six states. Two are queries that return values to stdout:

```bash
NeedsArtifactReboot)
  echo "No"
  ;;

SupportsRollback)
  echo "Yes"
  ;;

```

Four are actions that perform work:

**ArtifactInstall** : Backs up the existing `/etc/motd`, extracts `banner_text` from metadata using `jq`, writes to `/etc/motd`. Fails if no banner text is found.

**ArtifactCommit** : Removes the backup file, making the change permanent.

**ArtifactRollback** : Restores the backup to `/etc/motd`, or removes `/etc/motd` if it didn’t exist before.

**ArtifactFailure** : Same as rollback—restores original state on any failure.

The backup location `/tmp/banner.backup` persists across module invocations within a single update transaction but is cleared on reboot.

### JSON Extraction with jq

The module uses `jq` to extract the banner text safely:

```bash
BANNER_TEXT=$(jq -r '.banner_text' "$FILES/header/meta-data" 2>/dev/null || echo "")

```

The `-r` flag outputs raw strings without JSON quoting. The fallback to empty string handles missing files gracefully, with explicit validation afterward:

```bash
if [-z "$BANNER_TEXT"] || ["$BANNER_TEXT" = "null"]; then
  echo "Error: No banner_text found in metadata"
  exit 1
fi

```

## Yocto Integration

For devices built with Yocto/OpenEmbedded, include the module in your image with a recipe.

### Module Recipe

Create `mender-banner-module.bb`:

```bitbake
SUMMARY = "Mender banner update module"
DESCRIPTION = "Update module for deploying MOTD banners via Mender"
HOMEPAGE = "https://github.com/mendersoftware/mender-update-modules"
LICENSE = "Apache-2.0"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/Apache-2.0;md5=89aea4e17d99a7cacdbeed46a0096b10"

RDEPENDS:${PN} = "jq"
SRC_URI = "file://banner"
S = "${WORKDIR}"

do_install() {
  install -d ${D}${datadir}/mender/modules/v3
  install -m 0755 ${WORKDIR}/banner ${D}${datadir}/mender/modules/v3/banner
}
FILES:${PN} = "${datadir}/mender/modules/v3/banner"

```

Place the module script in `files/banner` alongside the recipe.  
The `RDEPENDS:${PN} = "jq"` line ensures `jq` is installed on the target device—the module will fail without it.

### Adding to Your Image

In your image recipe or `local.conf`:

```bitbake
IMAGE_INSTALL:append = " mender-banner-module"

```

### Fetching from GitHub

Alternatively, fetch the module directly from the repository:

```bitbake
SUMMARY = "Mender banner update module"
DESCRIPTION = "Update module for deploying MOTD banners via Mender"
HOMEPAGE = "https://github.com/mendersoftware/mender-update-modules"
LICENSE = "Apache-2.0"
LIC_FILES_CHKSUM = "file://LICENSE;md5=86d3f3a95c324c9479bd8986968f4327"

RDEPENDS:${PN} = "jq"
SRC_URI = "git://github.com/mendersoftware/mender-update-modules.git;protocol=https;branch=master"
SRCREV = "${AUTOREV}"
S = "${WORKDIR}/git"

do_install() {
  install -d ${D}${datadir}/mender/modules/v3
  install -m 0755 ${S}/banner/module/banner ${D}${datadir}/mender/modules/v3/banner
}
FILES:${PN} = "${datadir}/mender/modules/v3/banner"

```

Pin `SRCREV` to a specific commit for reproducible builds in production.

## Testing

### Verifying Module Installation

On the device, confirm the module is present and executable:

```bash
ls -l /usr/share/mender/modules/v3/banner

```

Verify `jq` is available:

```bash
which jq
jq --version

```

### Manual Module Testing

Test the module directly without creating artifacts. First, simulate the context directory structure:

```bash
mkdir -p /tmp/test-banner/header
echo '{"banner_text": "Test Banner Message"}' > /tmp/test-banner/header/meta-data

```

Test each state:

```bash
# Query rollback support
/usr/share/mender/modules/v3/banner SupportsRollback /tmp/test-banner
# Should output: Yes

# Query reboot requirement
/usr/share/mender/modules/v3/banner NeedsArtifactReboot /tmp/test-banner
# Should output: No

# Install the banner
/usr/share/mender/modules/v3/banner ArtifactInstall /tmp/test-banner

# Verify installation
cat /etc/motd
# Should show: Test Banner Message

# Verify backup exists
cat /tmp/banner.backup

# Test rollback
/usr/share/mender/modules/v3/banner ArtifactRollback /tmp/test-banner

# Verify original content restored
cat /etc/motd

```

### End-to-End Testing

Create and deploy an artifact:

```bash
# On your workstation
./banner-artifact-gen \
  -n "test-banner-v1" \
  -t "your-device-type" \
  -o test-banner.mender \
  -b "Welcome to the Test Environment"

# Deploy locally on device
mender install test-banner.mender

# Check result
cat /etc/motd

# Commit if satisfied
mender commit

# Or rollback
mender rollback

```

Monitor the update through system logs:

```bash
journalctl -u mender-updated -f

```

### Testing Rollback Scenarios

Verify rollback works in failure scenarios:

```bash
# Install first version
./banner-artifact-gen -n "v1" -t "device" -o v1.mender -b "Version 1"
mender install v1.mender
mender commit

# Install second version but don't commit
./banner-artifact-gen -n "v2" -t "device" -o v2.mender -b "Version 2"
mender install v2.mender

# Verify new banner
cat /etc/motd
# Shows: Version 2

# Trigger rollback
mender rollback

# Verify original restored
cat /etc/motd
# Shows: Version 1

```

## Troubleshooting

### “No banner\_text found in metadata”

The artifact metadata is missing or malformed. Verify the artifact structure:

```bash
mender-artifact read your-artifact.mender

```

Look for the metadata section. If missing, regenerate with the artifact generator.

### “jq: command not found”

Install `jq` on the device:

```bash
# Debian/Ubuntu
apt-get install jq
# For Yocto, add to RDEPENDS or IMAGE_INSTALL

```

### Banner not updating

Check file permissions on `/etc/motd`:

```bash
ls -l /etc/motd

```

The Mender client runs as root, so permission issues are rare but possible with immutable flags or SELinux policies.

### Rollback restores empty file

This is expected behavior when `/etc/motd` didn’t exist before the first deployment. The module creates an empty backup file as a marker for “file did not exist.”

### Module not found

```auto
Artifact Payload type 'banner' is not supported by this Mender Client

```

Verify installation path and permissions:

```bash
ls -l /usr/share/mender/modules/v3/banner
# Should show: -rwxr-xr-x ... banner

```

If the file exists but isn’t executable:

```bash
chmod +x /usr/share/mender/modules/v3/banner

```

## Adapting the Pattern

The banner module serves as a template for similar metadata-only updates. To adapt it for other single-value configurations:

1. Change `BANNER_FILE` to your target path
2. Change the metadata key from `banner_text` to your value
3. Update the artifact generator to accept your parameters
4. Adjust backup location if `/tmp` isn’t suitable (consider persistence requirements)

For multi-value configurations or file deployments, use the files-based approach with payload in `$FILES/files/` instead of metadata.
