Yocto Integration
Integrate Zelta OTA updates into your Yocto-based Linux distribution.
Overview
Zelta's embedded SDK is designed to work seamlessly with Yocto Project builds. This guide covers:
- Creating a Yocto recipe for the SDK
- Integrating with your image
- Runtime configuration
- Update strategies for embedded Linux
Prerequisites
- Yocto Project (Kirkstone, Scarthgap, or later)
meta-openembeddedlayer (for curl)- Basic Yocto knowledge
Layer Setup
Create a custom layer for Zelta:
cd poky
bitbake-layers create-layer ../meta-zelta
bitbake-layers add-layer ../meta-zelta
Recipe: zelta-sdk
Create the SDK recipe:
mkdir -p meta-zelta/recipes-ota/zelta
meta-zelta/recipes-ota/zelta/zelta-sdk_1.0.0.bb:
SUMMARY = "Zelta OTA Update SDK"
DESCRIPTION = "Embedded SDK for ZeltaSoft firmware over-the-air updates"
HOMEPAGE = "https://github.com/didavie/Zelta"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=xxxx"
SRC_URI = "git://github.com/didavie/Zelta.git;branch=main;protocol=https"
SRCREV = "${AUTOREV}"
S = "${WORKDIR}/git/embedded"
DEPENDS = "curl openssl mbedtls"
inherit cmake
EXTRA_OECMAKE = "-DZELTA_USE_MBEDTLS=ON"
do_install() {
install -d ${D}${libdir}
install -m 0644 ${B}/libzelta.a ${D}${libdir}
install -d ${D}${includedir}/zelta
install -m 0644 ${S}/include/*.h ${D}${includedir}/zelta/
}
# For dynamic linking (optional)
# install -m 0755 ${B}/libzelta.so.* ${D}${libdir}
ALLOW_EMPTY:${PN} = "1"
Recipe: zelta-client
Create a client application recipe:
meta-zelta/recipes-ota/zelta/zelta-client_1.0.0.bb:
SUMMARY = "Zelta OTA Update Client"
DESCRIPTION = "Background service for checking and applying OTA updates"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=xxxx"
SRC_URI = "git://github.com/didavie/Zelta.git;branch=main;protocol=https \
file://zelta-client.service \
file://zelta.conf \
"
SRCREV = "${AUTOREV}"
S = "${WORKDIR}/git/embedded/examples/linux"
DEPENDS = "zelta-sdk curl openssl"
inherit cmake systemd
SYSTEMD_SERVICE:${PN} = "zelta-client.service"
SYSTEMD_AUTO_ENABLE = "enable"
do_install() {
install -d ${D}${bindir}
install -m 0755 ${B}/zelta-client ${D}${bindir}
install -d ${D}${sysconfdir}/zelta
install -m 0600 ${WORKDIR}/zelta.conf ${D}${sysconfdir}/zelta/
install -d ${D}${systemd_system_unitdir}
install -m 0644 ${WORKDIR}/zelta-client.service ${D}${systemd_system_unitdir}
}
CONFFILES:${PN} = "${sysconfdir}/zelta/zelta.conf"
Systemd Service
meta-zelta/recipes-ota/zelta/files/zelta-client.service:
[Unit]
Description=ZeltaSoft OTA Update Client
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/bin/zelta-client
Restart=on-failure
RestartSec=30
User=root
# Security hardening
ProtectSystem=strict
ReadWritePaths=/var/lib/zelta /tmp
PrivateTmp=true
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
Configuration File
meta-zelta/recipes-ota/zelta/files/zelta.conf:
# Zelta OTA Configuration
[server]
url = https://api.zeltasoft.com
api_key = YOUR_API_KEY_HERE
[device]
product_id = YOUR_PRODUCT_ID
hardware_id = ${MACHINE}
[update]
check_interval = 3600
auto_apply = false
[paths]
download_dir = /var/lib/zelta/downloads
state_file = /var/lib/zelta/state.json
[logging]
level = info
file = /var/log/zelta.log
Image Integration
Add to your image recipe:
meta-zelta/recipes-core/images/my-image.bbappend:
IMAGE_INSTALL:append = " zelta-client"
# Ensure persistent storage for state
IMAGE_FEATURES:append = " read-only-rootfs"
A/B Partition Support
For robust updates with A/B partitioning:
meta-zelta/recipes-ota/zelta/zelta-ab.bb:
SUMMARY = "Zelta A/B Partition Handler"
LICENSE = "MIT"
SRC_URI = "file://zelta-ab-handler.sh"
do_install() {
install -d ${D}${sbindir}
install -m 0755 ${WORKDIR}/zelta-ab-handler.sh ${D}${sbindir}
}
RDEPENDS:${PN} = "util-linux"
A/B Handler Script:
#!/bin/bash
# zelta-ab-handler.sh - Handle A/B partition switching
CURRENT_SLOT=$(cat /proc/cmdline | grep -oP 'root=\K[^ ]+')
STATE_FILE="/var/lib/zelta/ab_state"
get_inactive_slot() {
if [[ "$CURRENT_SLOT" == *"rootfs_a"* ]]; then
echo "/dev/mmcblk0p3" # rootfs_b
else
echo "/dev/mmcblk0p2" # rootfs_a
fi
}
apply_update() {
local firmware_path="$1"
local inactive=$(get_inactive_slot)
echo "Applying update to $inactive..."
dd if="$firmware_path" of="$inactive" bs=4M status=progress
sync
# Update bootloader to boot from new partition
fw_setenv boot_slot $(basename $inactive)
echo "Update applied. Reboot to activate."
}
rollback() {
echo "Rolling back to previous slot..."
fw_setenv boot_slot "$CURRENT_SLOT"
}
case "$1" in
apply)
apply_update "$2"
;;
rollback)
rollback
;;
status)
echo "Current: $CURRENT_SLOT"
echo "Inactive: $(get_inactive_slot)"
;;
*)
echo "Usage: $0 {apply|rollback|status}"
exit 1
;;
esac
Machine Configuration
Add Zelta variables to your machine config:
meta-zelta/conf/machine/include/zelta.inc:
# Zelta OTA Configuration
ZELTA_PRODUCT_ID ?= ""
ZELTA_HARDWARE_ID ?= "${MACHINE}"
# Generate device-specific config
do_install:append() {
sed -i "s/YOUR_PRODUCT_ID/${ZELTA_PRODUCT_ID}/" \
${D}${sysconfdir}/zelta/zelta.conf
sed -i "s/\${MACHINE}/${ZELTA_HARDWARE_ID}/" \
${D}${sysconfdir}/zelta/zelta.conf
}
Build and Deploy
# Build the image with Zelta
bitbake my-image
# Flash to device
dd if=tmp/deploy/images/my-machine/my-image.wic of=/dev/sdX bs=4M
Runtime Usage
On the device:
# Check service status
systemctl status zelta-client
# View logs
journalctl -u zelta-client -f
# Manual update check
zelta-client --check-now
# Apply pending update
zelta-client --apply
Best Practices
1. Read-Only Root Filesystem
Use overlay filesystem for writable areas:
IMAGE_FEATURES:append = " read-only-rootfs"
VOLATILE_BINDS:append = " /var/lib/zelta"
2. Secure API Key Storage
Don't embed keys in the image - provision at first boot:
# During manufacturing/provisioning
echo "api_key = $(generate_unique_key)" >> /etc/zelta/zelta.conf
3. Update Verification
Enable signature verification in config:
[security]
verify_signature = true
public_key_file = /etc/zelta/public.pem
4. Bandwidth Management
For cellular/metered connections:
[network]
max_bandwidth = 100000 # 100 KB/s
metered_connection = true
update_window_start = 02:00
update_window_end = 05:00
Troubleshooting
Build Errors
# Missing dependencies
bitbake -c devshell zelta-sdk
# Check recipe
bitbake -e zelta-sdk | grep ^SRC_URI=
Runtime Issues
# Check if service is running
systemctl status zelta-client
# Test connectivity
curl -v https://api.zeltasoft.com/health
# Verify configuration
cat /etc/zelta/zelta.conf
Next Steps
- Buildroot Integration - For Buildroot-based systems
- A/B Update Strategy - Detailed A/B partition guide
- Security Best Practices - Secure your deployment