Zelta/Documentation

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-openembedded layer (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