Yocto integration
The meta-otapulse Yocto layer packages the OTA agent (soc-ota-agent), its configuration template (otapulse.conf), and a systemd service unit into your image. This guide assumes you already have a working Yocto build environment and walks through everything from cloning the layer to confirming the agent is running on-device.
Prerequisites
Section titled “Prerequisites”Yocto release support
| Release | Compatible |
|---|---|
| Kirkstone (4.0 LTS) | Yes |
| Langdale (4.1) | Yes |
| Mickledore (4.2) | Yes |
| Nanbield (4.3) | Yes |
| Scarthgap (5.0 LTS) | Yes |
| Earlier releases | Not tested |
Compatibility is declared in the layer’s LAYERSERIES_COMPAT_meta-soc-monitoring variable in conf/layer.conf.
Build host
Ubuntu 22.04 or later is recommended. Install the standard Yocto build host packages before continuing. Allow at least 50 GB of free disk space for a fresh build; a warm sstate-cache cuts subsequent builds to 10–20 minutes.
Systemd is required. The layer registers soc-ota-agent.service and depends on systemd as the init manager. If your distro config does not already set INIT_MANAGER = "systemd", add it to local.conf (see below).
Verified hardware
Section titled “Verified hardware”What protects a board when a new image installs but cannot boot differs per board. A bootloader revert means the bootloader itself returns to the previous slot before Linux runs, so a board whose new root filesystem will not mount recovers without anyone touching it. Only the boards marked “Proven on hardware” have been shown to do this on a real board.
| Board | Bootloader revert | Status |
|---|---|---|
| Orange Pi Zero 2W | Yes. A U-Boot boot-count check in boot.scr. | Proven on hardware (attended run, 2026-09-25). |
| NXP i.MX8MP FRDM | Yes. The same U-Boot boot-count check. | Proven on hardware (attended run, 2026-09-25). |
| BeaglePlay | Yes. The same U-Boot boot-count check. | Proven on hardware (attended run, 2026-09-25). |
| Radxa CM5 (RK3588) | Yes. A boot-count script in U-Boot. It needs the OTA-Pulse U-Boot build, which is flashed to the board’s uboot partition. An OTA update does not deliver it. | Proven on hardware (2026-10-03). |
| Raspberry Pi 4 (64-bit) | Yes. The Raspberry Pi firmware’s one-shot tryboot boot. | Proven on hardware (2026-08-22). |
| Radxa Dragon Q6A | Designed: systemd-boot automatic boot assessment. | Not verified on hardware. Do not rely on it. |
| NVIDIA Jetson Xavier NX | Tegra-native A/B slots (nvbootctrl). | Rollback not re-verified on hardware since the last reflash. Do not rely on it. |
The U-Boot boot-count check lives in the boot script and bootloader that you flash to the board. An OTA update does not replace them. A board flashed with an older image may not have the check.
Raspberry Pi Zero 2 W and ODROID C4 have no bootloader revert and are not part of our nightly validation. The Raspberry Pi Zero 2 W chip has no tryboot, so it boots straight from cmdline.txt. No iMX8MM board has been validated.
Other machines your BSP layer supports can use the layer, but we have not validated them. Before you ship, check that your board’s bootloader returns to the previous slot when a new image fails to boot. A bootloader that does not do this can leave a board unbootable after a bad update.
If your board already boots through U-Boot or GRUB with an A/B environment, see Bootloader compatibility mode. In that mode the bootloader owns the slot switch and the rollback. Two QEMU targets are qualified. No physical board is qualified in that mode yet, and Raspberry Pi 4 in that mode is not qualified. The table above describes native mode only.
Add the layer
Section titled “Add the layer”-
Clone the repository
From the directory that contains
poky/andmeta-openembedded/:Terminal window git clone https://github.com/binaryblack/OTA-Pulse.git ota-pulseThe Yocto layer lives at
ota-pulse/meta-otapulse/inside the cloned repository. -
Add the layer to
bblayers.confEdit
conf/bblayers.confin your build directory. Appendmeta-otapulseandmeta-oe(required by the layer):BBLAYERS ?= " \/path/to/poky/meta \/path/to/poky/meta-poky \/path/to/poky/meta-yocto-bsp \/path/to/meta-openembedded/meta-oe \/path/to/ota-pulse/meta-otapulse \"Replace
/path/to/with the absolute path on your build host. You can also usebitbake-layers add-layerto append the path:Terminal window bitbake-layers add-layer /path/to/ota-pulse/meta-otapulse
Configure local.conf
Section titled “Configure local.conf”Add the following to conf/local.conf in your build directory. Only OTA_SERVER_URL is strictly required for a first build; the signing variables are optional until you enable signature verification.
# --- Required ---
# Install the OTA agent and signing-keys into the imageIMAGE_INSTALL:append = " soc-ota-agent signing-keys"
# Systemd is required by the layerDISTRO_FEATURES:append = " systemd usrmerge"INIT_MANAGER = "systemd"
# OTA-Pulse server URL — the agent posts here during updatesOTA_SERVER_URL = "https://your-server.example.com"
# --- Optional: signature verification ---
# Set to "1" to enable artifact signature verification on-deviceSOC_OTA_SIGNATURE_VERIFICATION = "0"
# Path where signing-keys recipe installs public keysSOC_OTA_SIGNING_KEYS_DIR = "/etc/soc-monitoring/signing-keys"
# Space-separated list of public key filenames (relative to the active/ subdirectory)SOC_OTA_VERIFY_KEY_FILES = "production-rsa-public.pem production-ecdsa-public.pem"The soc-ota-agent recipe reads OTA_SERVER_URL at build time and writes it into /etc/otapulse/otapulse.conf on the image. Every image and every root filesystem update artifact carries its own copy of that file, so build them all with the same production URL. See Configuration files. The configuration template also sets RootfsPartA and RootfsPartB to /dev/disk/by-partlabel/rootfs_a and rootfs_b respectively, which matches the A/B layout produced by the layer’s WIC kickstart file (see the next section).
Image features and A/B partition layout
Section titled “Image features and A/B partition layout”The layer ships a WIC kickstart template (wic/soc-monitoring.wks.in) that defines the A/B partition layout the OTA agent expects:
| Partition | Label | Size | Purpose |
|---|---|---|---|
/boot | boot | 100 MB | FAT32, kernel + DTB |
/ | rootfs_a | 1 GB | Primary root filesystem |
| (unmounted) | rootfs_b | 1 GB | Inactive slot for OTA writes |
/data | data | 500 MB | Persistent data across updates |
To use this layout, set the WIC file in local.conf:
WKS_FILE = "soc-monitoring.wks.in"IMAGE_FSTYPES = "wic wic.gz wic.bmap ext4"The reference image recipe (soc-monitoring-image.bb) already sets WKS_FILE ?= "soc-monitoring.wks.in", so if you build soc-monitoring-image you get this layout automatically. If you are extending a different image recipe, set it explicitly.
Embedding signing keys
To bake your public keys into the image for signature verification, copy them into the recipe’s files directory and set the recipe variables:
# Copy your exported public keys into the layercp ~/Downloads/production-rsa-public.pem \ meta-otapulse/recipes-core/signing-keys/files/cp ~/Downloads/production-ecdsa-public.pem \ meta-otapulse/recipes-core/signing-keys/files/Then in local.conf:
SOC_OTA_SIGNATURE_VERIFICATION = "1"SOC_OTA_VERIFY_KEY_FILES = "production-rsa-public.pem production-ecdsa-public.pem"The signing-keys recipe installs both files under /etc/soc-monitoring/signing-keys/active/ with read-only permissions (0444). The agent reads this directory when ArtifactVerifyKeys is set in its configuration.
Generate the key pair yourself, for example with openssl genrsa -out release-private.pem 4096 and openssl rsa -in release-private.pem -pubout -out production-rsa-public.pem. Keep the private key secure and sign artifacts with otapulse-artifact write ... --key release-private.pem. Also import the same public key in the console (Signing Keys -> Import Public Key) so the server accepts your signed uploads; the server cannot push keys to devices, so baking it into the image as above is still required. See the End-to-end quickstart for the full flow.
First build and verification
Section titled “First build and verification”source poky/oe-init-build-env buildbitbake soc-monitoring-imageOr, if you are extending your own image recipe:
bitbake <your-image-recipe>The built WIC image lands in tmp/deploy/images/<MACHINE>/. Flash it to your device:
sudo dd if=tmp/deploy/images/<MACHINE>/soc-monitoring-image-<MACHINE>.wic \ of=/dev/sdX bs=4M status=progresssyncAfter boot, log in with the credentials you configured in Hardening before production and verify the agent started successfully. The reference image does not ship a default or empty root password, so you must provide a root SSH key (or a root password hash) at build time before you can log in:
ssh -i ~/.ssh/<your-key> root@<device-ip>systemctl status soc-ota-agentjournalctl -u soc-ota-agent --since "1 minute ago"Within about 30 seconds of the agent starting, the device should appear with an online status badge in the OTA-Pulse console.
Once the device is registered, follow Register your first device for the console-side setup — generating an API key and verifying telemetry is flowing.
Hardening before production
Section titled “Hardening before production”The reference soc-monitoring-image recipe does not enable Yocto’s debug-tweaks image feature. debug-tweaks gives root an empty password and sets PermitRootLogin yes and PermitEmptyPasswords yes in sshd_config, so anyone who can reach port 22 gets a root shell with no credential. Never ship a device built with it.
Before you build an image for production:
-
Do not enable
debug-tweaks. Make sure nolocal.conf, image recipe,.bbappendor.incin your build setsEXTRA_IMAGE_FEATURES += "debug-tweaks"or listsdebug-tweaksinIMAGE_FEATURES. You can check the final value withbitbake -e soc-monitoring-image | grep -E '^(IMAGE_FEATURES|EXTRA_IMAGE_FEATURES)='. -
Use key-only root SSH, or set a root password hash. Bake your public key into the image and force
PermitRootLogin prohibit-passwordandPermitEmptyPasswords no. Add this to asoc-monitoring-image.bbappendin your own layer, and setOTAPULSE_ROOT_SSH_PUBKEYinlocal.confto the contents of your public key file:OTAPULSE_ROOT_SSH_PUBKEY ?= ""ROOTFS_POSTPROCESS_COMMAND += "otapulse_harden_root_ssh;"otapulse_harden_root_ssh() {sed -i -e 's/^#\?PermitRootLogin.*/PermitRootLogin prohibit-password/' \-e 's/^#\?PermitEmptyPasswords.*/PermitEmptyPasswords no/' \${IMAGE_ROOTFS}${sysconfdir}/ssh/sshd_configif [ -n "${OTAPULSE_ROOT_SSH_PUBKEY}" ]; theninstall -d -m 0700 ${IMAGE_ROOTFS}${ROOT_HOME}/.sshecho "${OTAPULSE_ROOT_SSH_PUBKEY}" > ${IMAGE_ROOTFS}${ROOT_HOME}/.ssh/authorized_keyschmod 0600 ${IMAGE_ROOTFS}${ROOT_HOME}/.ssh/authorized_keysfi}If you prefer a root password, generate a SHA-512 hash with
openssl passwd -6and set it with theextrausersclass. Escape every$in the hash as\$:INHERIT += "extrausers"EXTRA_USERS_PARAMS = "usermod -p '\$6\$<salt>\$<hash>' root;" -
Keep SSH reachable only where you need it. Restrict port 22 with your firewall or network policy. The OTA agent only makes outbound connections, so the device does not need inbound SSH from the internet.
-
Verify on a built image before you flash a fleet:
grep '^root:' /etc/shadowmust not show an empty hash, andsshd -T | grep -Ei 'permitrootlogin|permitemptypasswords'must showprohibit-passwordandno.
Common errors
Section titled “Common errors”ERROR: Nothing PROVIDES 'dlt-daemon'
meta-oe is missing from bblayers.conf — the layer’s DLT logging support (dlt-daemon) is provided by meta-oe, not meta-otapulse itself. Add it:
BBLAYERS += "/path/to/meta-openembedded/meta-oe"ERROR: Nothing PROVIDES 'soc-ota-agent'
The layer path in bblayers.conf is wrong or the layer was never added. Verify with bitbake-layers show-layers — meta-soc-monitoring must appear in the output (this is the internal Bitbake collection name for the meta-otapulse layer). If it does not, re-run:
bitbake-layers add-layer /path/to/ota-pulse/meta-otapulseERROR: LIC_FILES_CHKSUM for soc-ota-agent_1.0.0.bb does not match
A local edit changed the Apache-2.0 license file checksum. Restore the common license file to its canonical content, or update the checksum in the recipe:
md5sum ${COMMON_LICENSE_DIR}/Apache-2.0# Update LIC_FILES_CHKSUM in soc-ota-agent_1.0.0.bb to matchsoc-ota-agent.service: Failed to start — can't open /etc/otapulse/otapulse.conf
The configuration file was not generated during the build. This usually means OTA_SERVER_URL was not set in local.conf when the recipe ran, so the template substitution produced an empty or invalid file. Set the variable and do a clean rebuild:
bitbake -c cleanall soc-ota-agentbitbake soc-ota-agentOTA update fails with “signature verification failed” after flashing a new artifact
Either the public key in the image does not match the private key used to sign the artifact, or SOC_OTA_SIGNATURE_VERIFICATION is set to "1" but signing-keys was not included in IMAGE_INSTALL. Confirm both:
# On device — check keys are presentls -la /etc/soc-monitoring/signing-keys/active/
# In your build — check both packages were includedbitbake -e soc-monitoring-image | grep '^IMAGE_INSTALL'If the key files are missing, add signing-keys to IMAGE_INSTALL:append and rebuild.