fcb_looper/image/implementation_plan.md

25 KiB

Yocto FCB1010 Looper System - Implementation Guide

Project Overview

Build a minimal, real-time Linux system for an audio looper pedal using Yocto Project. The system boots from initramfs only for maximum speed, includes RT kernel patches, and supports over-the-air updates via SWUpdate with A/B partitioning.

Key Requirements:

  • x64 hardware (NUC-class)
  • Static IP: 192.168.0.50
  • SSH access with public key authentication
  • USB live installer that can dd copy to disk
  • A/B partition scheme for reliable updates
  • RT kernel with PREEMPT_RT
  • JACK audio stack
  • SWUpdate for OTA updates

Architecture Decisions

Boot Strategy: EFI boot → initramfs-only (no rootfs mounting) Network: WiFi with static IP, credentials baked into image Updates: SWUpdate web interface + SSH API access Installation: USB boot → SSH → dd copy entire USB to target disk Partition Layout: EFI boot partition with initramfs-A.cpio.gz and initramfs-B.cpio.gz

Yocto Implementation Approach

Custom Layer Structure: Create meta-looper with recipes for kernel config (RT patches), network setup (static IP + WiFi), SSH keys, audio stack (JACK), and two image types: main system (initramfs) and USB installer.

Build Configuration:

  • MACHINE = "genericx86-64"
  • Kernel: linux-yocto with RT features
  • Minimal install, not Pokey distribution

Key Implementation Points

Kernel: Enable PREEMPT_RT, high-res timers, and audio subsystem. Use kernel fragment files (.cfg) to configure RT options.

Networking: Create recipes for wpa_supplicant.conf and network interfaces with static IP. Include WiFi credentials at build time.

SSH: Recipe to install public key to root's authorized_keys during image build.

SWUpdate: Configure for A/B updates, web interface on port 8080, and integration with systemd-boot for partition switching.

USB Installer: Second image that boots live system with installation script. Script partitions target disk, creates EFI boot partition, and dd copies the installer image to create installed system.

Running

The build and testing platform is built in Docker. A Compose file is used to script the separate targets:

  • docker compose run disk-image
  • docker compose run qemu-server
  • docker compose run push-update
  • docker compose run ssh-client

The compose file mounts the proper directories for cache, output, ... It also forwards the proper ports for qemu.

The run script wraps docker compose run and adds some environment variables.

Testing Strategy for AI Agent

QEMU Testing: Use runqemu with network forwarding to test SSH, networking, and SWUpdate functionality. QEMU supports graphics for GUI testing and USB forwarding for hardware validation.

Build Validation: Verify initramfs size (<200MB), boot time (<10 seconds), and RT kernel configuration. Test static IP assignment and SSH key authentication.

Update Testing: Create test SWUpdate packages and verify A/B switching works correctly. Test both web interface and SSH-based update deployment.

Audio Validation: Confirm JACK installation, RT scheduling capabilities with cyclictest, and audio device detection.

Installation Testing: Boot USB installer in QEMU, SSH in, run installation script on virtual disk, then boot installed system to verify complete workflow.

The implementation should focus on creating minimal, working recipes that can be tested incrementally in QEMU before hardware deployment. Each component (kernel, network, SSH, audio, updates) should be testable independently.

Core Architecture and Directory Structure

The system integrates Yocto Project 5.0 Scarthgap LTS with Docker containerization, organizing all components within a Rust workspace directory structure. This approach minimizes host dependencies while providing a robust embedded Linux development environment with reliable over-the-air updates.

Project Organization Within Rust Workspace

image/
├── Dockerfile
├── docker-compose.yml
├── run
│
├── yocto/
│   ├── poky/
│   ├── meta-openembedded/
│   ├── meta-rust-bin/          # Pre-built Rust toolchain
│   └── meta-swupdate/          # SWUpdate integration
├── meta-layers/
│   ├── meta-custom/            # Custom project layer
│   │   ├── conf/
│   │   │   ├── distro/
│   │   │   ├── machine/
│   │   │   └── layer.conf
│   │   ├── recipes-apps/
│   │   │   └── egui-kiosk/
│   │   ├── recipes-core/
│   │   │   └── images/
│   │   └── recipes-support/
│   │       └── swupdate/
│   └── meta-custom-bsp/        # BSP-specific configurations
├── scripts/
│   ├── setup-environment.sh
│   ├── build-images.sh
│   ├── deploy-qemu.sh
│   └── create-update.sh
└── conf/
    ├── local.conf
    └── bblayers.conf

The Rust workspace configuration unifies all application components:

# rust-workspace/Cargo.toml
[workspace]
resolver = "2"
members = [
    "apps/*",
    "libs/*"
]

[workspace.package]
version = "1.0.0"
edition = "2021"
rust-version = "1.70"

[workspace.dependencies]
egui = "0.24"
eframe = { version = "0.24", features = ["wgpu"] }
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["rt-multi-thread"] }

Yocto Layer Creation and Rust Integration

The custom layer provides comprehensive Rust application support with modern toolchain integration using meta-rust-bin for faster builds and easier maintenance.

Custom Layer Configuration

# meta-layers/meta-custom/conf/layer.conf
BBPATH .= ":${LAYERDIR}"
BBFILES += "${LAYERDIR}/recipes-*/*/*.bb ${LAYERDIR}/recipes-*/*/*.bbappend"

BBFILE_COLLECTIONS += "custom-layer"
BBFILE_PATTERN_custom-layer = "^${LAYERDIR}/"
BBFILE_PRIORITY_custom-layer = "10"

LAYERVERSION_custom-layer = "1"
LAYERSERIES_COMPAT_custom-layer = "scarthgap"

LAYERDEPENDS_custom-layer = "core openembedded-layer meta-rust-bin"

Rust egui Application Recipe

The recipe integrates seamlessly with the Rust workspace, handling cross-compilation and dependencies automatically:

# meta-layers/meta-custom/recipes-apps/egui-kiosk/egui-kiosk_1.0.bb
SUMMARY = "Rust egui Fullscreen Kiosk Application"
DESCRIPTION = "Cross-platform GUI kiosk application built with egui framework"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=935a9b2a57ae70704d8125b9c0e39059"

inherit cargo_bin systemd

# Enable network access for cargo dependencies
do_compile[network] = "1"

SRC_URI = "\
    git://localhost/rust-workspace.git;protocol=file;branch=main \
    file://egui-kiosk.service \
    file://egui-kiosk-init.sh \
    file://weston.ini \
"

SRCREV = "${AUTOREV}"
S = "${WORKDIR}/git"

# Point to specific app in workspace
CARGO_SRC_DIR = "apps/egui-kiosk"

# Cargo features for kiosk mode
CARGO_FEATURES = "kiosk-mode fullscreen wayland"

# Runtime dependencies for GUI applications
RDEPENDS_${PN} += "\
    wayland \
    weston \
    wayland-protocols \
    libxkbcommon \
    fontconfig \
    liberation-fonts \
    systemd \
"

# Build dependencies
DEPENDS += "\
    wayland-native \
    wayland-protocols-native \
    libxkbcommon \
    fontconfig \
"

# Install systemd service and configuration
do_install_append() {
    install -d ${D}${systemd_system_unitdir}
    install -m 0644 ${WORKDIR}/egui-kiosk.service ${D}${systemd_system_unitdir}/

    install -d ${D}${bindir}
    install -m 0755 ${WORKDIR}/egui-kiosk-init.sh ${D}${bindir}/

    install -d ${D}${sysconfdir}/xdg/weston
    install -m 0644 ${WORKDIR}/weston.ini ${D}${sysconfdir}/xdg/weston/
}

SYSTEMD_SERVICE_${PN} = "egui-kiosk.service"
SYSTEMD_AUTO_ENABLE_${PN} = "enable"

FILES_${PN} += "\
    ${systemd_system_unitdir}/egui-kiosk.service \
    ${bindir}/egui-kiosk-init.sh \
    ${sysconfdir}/xdg/weston/weston.ini \
"

Rust egui Application Implementation

The application leverages egui 0.24 with fullscreen kiosk capabilities and system integration:

// rust-workspace/apps/egui-kiosk/src/main.rs
use eframe::{egui, App, Frame, NativeOptions};
use std::env;

struct KioskApp {
    counter: i32,
    show_confirmation_dialog: bool,
}

impl KioskApp {
    fn new() -> Self {
        Self {
            counter: 0,
            show_confirmation_dialog: false,
        }
    }
}

impl App for KioskApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut Frame) {
        egui::CentralPanel::default().show(ctx, |ui| {
            ui.heading("Production Kiosk System");
            
            ui.add_space(20.0);
            
            ui.horizontal(|ui| {
                if ui.button("Increment").clicked() {
                    self.counter += 1;
                }
                ui.label(format!("Counter: {}", self.counter));
            });
            
            ui.add_space(20.0);
            
            if ui.button("System Settings").clicked() {
                self.show_confirmation_dialog = true;
            }
        });

        if self.show_confirmation_dialog {
            egui::Window::new("Confirm Action")
                .collapsible(false)
                .resizable(false)
                .show(ctx, |ui| {
                    ui.label("Are you sure you want to access system settings?");
                    ui.horizontal(|ui| {
                        if ui.button("Yes").clicked() {
                            // Handle system settings access
                            self.show_confirmation_dialog = false;
                        }
                        if ui.button("No").clicked() {
                            self.show_confirmation_dialog = false;
                        }
                    });
                });
        }
    }
}

fn main() -> Result<(), eframe::Error> {
    let options = NativeOptions {
        fullscreen: true,
        decorated: false,
        resizable: false,
        always_on_top: true,
        ..Default::default()
    };
    
    eframe::run_native(
        "Kiosk Application",
        options,
        Box::new(|_cc| Box::new(KioskApp::new())),
    )
}

Systemd Service Configuration for Auto-Start

The systemd service ensures reliable application startup with proper dependencies and restart capabilities:

# files/egui-kiosk.service
[Unit]
Description=Rust egui Kiosk Application
Documentation=https://github.com/company/egui-kiosk
After=graphical.target weston.service
Wants=weston.service
Requires=graphical.target
StartLimitIntervalSec=30
StartLimitBurst=3

[Service]
Type=simple
User=kiosk
Group=kiosk
Environment="DISPLAY=:0"
Environment="WAYLAND_DISPLAY=wayland-0"
Environment="XDG_RUNTIME_DIR=/run/user/1000"
Environment="RUST_LOG=info"

ExecStartPre=/bin/sleep 3
ExecStart=/usr/bin/egui-kiosk
ExecReload=/bin/kill -HUP $MAINPID

Restart=always
RestartSec=5
WatchdogSec=30

# Resource limits
MemoryMax=256M
CPUQuota=80%

# Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/tmp /run/user/1000

[Install]
WantedBy=graphical.target

initramfs Integration Strategy

The initramfs approach embeds the Rust application directly in the boot image for fastest startup times and minimal attack surface.

initramfs Image Recipe

# meta-layers/meta-custom/recipes-core/images/initramfs-egui-image.bb
DESCRIPTION = "Minimal initramfs image with Rust GUI application"

PACKAGE_INSTALL = "\
    initramfs-framework-base \
    busybox \
    base-files \
    egui-kiosk \
    weston \
    liberation-fonts \
    ${VIRTUAL-RUNTIME_base-utils} \
"

# Minimize image size
IMAGE_LINGUAS = ""
LICENSE = "MIT"

inherit core-image

# Optimize for minimal size
IMAGE_ROOTFS_SIZE ?= "32768"
IMAGE_ROOTFS_EXTRA_SPACE = "0"
IMAGE_OVERHEAD_FACTOR = "1.0"

# Bundle with kernel
INITRAMFS_IMAGE_BUNDLE = "1"

initramfs Boot Module

# meta-layers/meta-custom/recipes-core/initramfs-egui-boot/files/egui-boot
#!/bin/sh

egui_boot_enabled() {
    return 0
}

egui_boot_run() {
    msg "Starting GUI boot sequence..."
    
    # Mount essential filesystems
    mount -t proc proc /proc
    mount -t sysfs sysfs /sys
    mount -t devtmpfs devtmpfs /dev
    
    # Setup runtime directories
    mkdir -p /run/user/1000
    chown 1000:1000 /run/user/1000
    
    # Start Wayland compositor
    export XDG_RUNTIME_DIR="/run/user/1000"
    export WAYLAND_DISPLAY="wayland-0"
    
    weston --backend=drm-backend.so --idle-time=0 &
    sleep 2
    
    # Start Rust GUI application
    egui-kiosk &
    
    msg "GUI application started successfully"
}

SWUpdate A/B Partitioning System

The A/B update system provides atomic updates with automatic rollback, ensuring system reliability even during power failures or failed updates.

Partition Layout Configuration

# Recommended eMMC/SSD partition layout
/dev/sda
├── sda1    # Boot partition (EFI/BIOS, 64MB)
├── sda2    # RootFS A (ext4, 2GB)
├── sda3    # RootFS B (ext4, 2GB)  
├── sda4    # Data partition (ext4, remaining)
└── sda5    # U-Boot environment (1MB)

WIC Image Configuration for A/B Layout

# meta-layers/meta-custom/wic/dual-rootfs.wks
# Boot partition with bootloader
part /boot --source bootimg-partition --sourceparams="loader=u-boot" \
     --ondisk sda --fstype=vfat --label boot --size 64M --align 1024

# Root filesystem A (active partition)
part / --source rootfs --rootfs-dir=${IMAGE_ROOTFS} \
     --ondisk sda --fstype=ext4 --label rootfs_a --size 2G

# Root filesystem B (update target, initially empty)
part --ondisk sda --fstype=ext4 --label rootfs_b --size 2G

# Persistent data partition
part /data --ondisk sda --fstype=ext4 --label data --size 1G \
     --fsoptions="defaults"

# U-Boot environment for A/B switching
part --ondisk sda --size 1M --label uboot_env

bootloader u-boot

SWUpdate Configuration

# files/sw-description
software =
{
    version = "1.0.0";
    description = "Dual-copy Rust GUI system update";
    
    hardware-compatibility: [ "1.0", "2.0" ];
    
    stable = {
        copy1 = {
            images: (
                {
                    filename = "core-image-egui-minimal.ext4.gz";
                    type = "raw";
                    compressed = true;
                    device = "/dev/sda2";
                    sha256 = "$swupdate_get_sha256(core-image-egui-minimal.ext4.gz)";
                }
            );
            
            scripts: (
                {
                    filename = "post-update.sh";
                    type = "shellscript";
                }
            );
            
            bootenv: (
                {
                    name = "rootfs_part";
                    value = "2";
                },
                {
                    name = "upgrade_available";
                    value = "1";
                }
            );
        };
        
        copy2 = {
            images: (
                {
                    filename = "core-image-egui-minimal.ext4.gz";
                    type = "raw";
                    compressed = true;
                    device = "/dev/sda3";
                    sha256 = "$swupdate_get_sha256(core-image-egui-minimal.ext4.gz)";
                }
            );
            
            scripts: (
                {
                    filename = "post-update.sh";
                    type = "shellscript";
                }
            );
            
            bootenv: (
                {
                    name = "rootfs_part";
                    value = "3";
                },
                {
                    name = "upgrade_available";
                    value = "1";
                }
            );
        };
    };
    
    scripts: (
        {
            filename = "partition-select.lua";
            type = "lua";
        }
    );
}

Automatic Partition Selection Logic

-- files/partition-select.lua
function preinst()
    -- Determine current root partition
    local f = io.open("/proc/cmdline", "r")
    local cmdline = f:read("*all")
    f:close()
    
    local current_part = cmdline:match("root=/dev/sda(%d)")
    
    if current_part == "2" then
        -- Currently on partition 2, update partition 3
        swupdate.notify(RECOVERY, "Updating partition 3 (copy2)")
        return 0, "copy2"
    else
        -- Currently on partition 3 or default, update partition 2
        swupdate.notify(RECOVERY, "Updating partition 2 (copy1)")
        return 0, "copy1"
    end
end

function postinst()
    swupdate.notify(RECOVERY, "Update installation completed")
    return 0
end

QEMU Testing Configuration

QEMU provides comprehensive testing capabilities with KVM acceleration and proper graphics support for egui application validation.

QEMU Launch Configuration

#!/bin/bash
# scripts/deploy-qemu.sh

set -e

IMAGE_PATH="yocto/build/tmp/deploy/images/qemux86-64"
KERNEL="${IMAGE_PATH}/bzImage"
ROOTFS="${IMAGE_PATH}/core-image-egui-minimal-qemux86-64.ext4"

# Launch QEMU with graphics support
qemu-system-x86_64 \
    -enable-kvm \
    -m 2048 \
    -smp 4 \
    -cpu host \
    -kernel "${KERNEL}" \
    -drive file="${ROOTFS}",if=virtio,format=raw \
    -append "root=/dev/vda rw console=ttyS0 console=tty0 quiet" \
    -netdev user,id=net0,hostfwd=tcp::8080-:8080,hostfwd=tcp::2222-:22 \
    -device virtio-net-pci,netdev=net0 \
    -device virtio-vga-gl \
    -display gtk,gl=on \
    -audiodev pulse,id=audio0 \
    -device intel-hda \
    -device hda-duplex,audiodev=audio0 \
    -serial stdio \
    -show-cursor

Automated Testing Framework

#!/bin/bash
# scripts/test-system.sh

test_boot_time() {
    echo "Testing boot time..."
    start_time=$(date +%s.%N)
    
    timeout 60 qemu-system-x86_64 \
        -enable-kvm -m 1024 -nographic \
        -kernel "${KERNEL}" \
        -drive file="${ROOTFS}",if=virtio,format=raw \
        -append "root=/dev/vda rw console=ttyS0 init=/bin/sh" &
    
    QEMU_PID=$!
    wait $QEMU_PID
    
    end_time=$(date +%s.%N)
    boot_time=$(echo "$end_time - $start_time" | bc)
    
    echo "Boot time: ${boot_time} seconds"
    if (( $(echo "$boot_time < 15" | bc -l) )); then
        echo "PASS: Boot time acceptable"
    else
        echo "FAIL: Boot time too slow"
        exit 1
    fi
}

test_application_startup() {
    echo "Testing application startup..."
    # Launch system and verify egui application starts
    timeout 120 expect -c '
        spawn qemu-system-x86_64 -enable-kvm -m 1024 -nographic \
            -kernel '"${KERNEL}"' \
            -drive file='"${ROOTFS}"',if=virtio,format=raw \
            -append "root=/dev/vda rw console=ttyS0"
        
        expect "login:" { send "root\r" }
        expect "# " { send "systemctl status egui-kiosk\r" }
        expect "active (running)" { 
            puts "SUCCESS: egui application running"
            exit 0 
        }
        timeout { 
            puts "FAIL: Application not started in time"
            exit 1 
        }
    '
}

test_boot_time
test_application_startup
echo "All tests passed!"

Minimal Linux System Configuration

The system optimizes for minimal resource usage while maintaining essential functionality for the Rust GUI application.

Minimal Image Recipe

# meta-layers/meta-custom/recipes-core/images/core-image-egui-minimal.bb
SUMMARY = "Minimal Linux system with Rust egui application"

IMAGE_INSTALL = "\
    packagegroup-core-boot \
    egui-kiosk \
    weston \
    liberation-fonts \
    systemd \
    networkmanager \
    swupdate \
    ${CORE_IMAGE_EXTRA_INSTALL} \
"

# Remove unnecessary features
DISTRO_FEATURES_remove = "alsa bluetooth wifi nfs zeroconf pci 3g nfc x11"
IMAGE_FEATURES = "read-only-rootfs"

# Package optimization
PACKAGE_CLASSES = "package_ipk"
IMAGE_FSTYPES = "ext4 ext4.gz wic"

# Size constraints
IMAGE_ROOTFS_SIZE ?= "512000"  # 512MB
IMAGE_OVERHEAD_FACTOR = "1.1"

LICENSE = "MIT"

inherit core-image

# Remove debug packages
PACKAGE_EXCLUDE = "\
    gdb \
    gdbserver \
    strace \
    ${@bb.utils.contains('DISTRO_FEATURES', 'x11', 'x11-common', '', d)} \
"

# Optimize systemd
IMAGE_INSTALL_append = " systemd-analyze"
SYSTEMD_DEFAULT_TARGET = "graphical.target"

System Optimization Configuration

# meta-layers/meta-custom/recipes-core/systemd/systemd/system.conf
[Manager]
LogLevel=warning
LogTarget=kmsg
SystemMaxUse=50M
RuntimeMaxUse=50M
DefaultTimeoutStartSec=10s
DefaultTimeoutStopSec=5s
DefaultRestartSec=5s

Build and Deployment Workflows

The complete workflow integrates Docker containerization with automated builds, testing, and deployment.

Environment Setup Script

#!/bin/bash
# scripts/setup-environment.sh

set -e

echo "Setting up Yocto development environment..."

# Export user ID for Docker
export USER_ID=$(id -u)
export GROUP_ID=$(id -g)

# Initialize Git repositories if not present
if [ ! -d "yocto/sources/poky" ]; then
    echo "Cloning Yocto repositories..."
    mkdir -p yocto/sources
    cd yocto/sources
    
    git clone git://git.yoctoproject.org/poky -b scarthgap
    git clone git://git.openembedded.org/meta-openembedded -b scarthgap
    git clone https://github.com/rust-embedded/meta-rust-bin.git -b master
    git clone https://github.com/sbabic/meta-swupdate.git -b scarthgap
    
    cd ../..
fi

# Start Docker environment
echo "Starting containerized build environment..."
docker-compose up -d yocto-builder

# Wait for container to be ready
sleep 5

# Initialize build environment inside container
docker-compose exec yocto-builder bash -c "
    cd /workspace/yocto &&
    source sources/poky/oe-init-build-env build &&
    
    # Configure layers
    bitbake-layers add-layer ../sources/meta-openembedded/meta-oe
    bitbake-layers add-layer ../sources/meta-openembedded/meta-python
    bitbake-layers add-layer ../sources/meta-openembedded/meta-networking
    bitbake-layers add-layer ../sources/meta-rust-bin
    bitbake-layers add-layer ../sources/meta-swupdate
    bitbake-layers add-layer ../../meta-layers/meta-custom
    
    # Configure build
    cat >> conf/local.conf << 'EOF'
MACHINE = \"qemux86-64\"
DISTRO = \"poky\"

# Rust support
ENABLE_RUST = \"1\"

# Performance optimizations
BB_NUMBER_THREADS = \"8\"
PARALLEL_MAKE = \"-j8\"

# Package management
PACKAGE_CLASSES = \"package_ipk\"

# SWUpdate integration
PREFERRED_PROVIDER_u-boot-fw-utils = \"libubootenv\"
IMAGE_FSTYPES += \"ext4.gz wic\"

# Add applications to rootfs
CORE_IMAGE_EXTRA_INSTALL += \"egui-kiosk swupdate swupdate-www\"

# Enable systemd
DISTRO_FEATURES_append = \" systemd\"
VIRTUAL-RUNTIME_init_manager = \"systemd\"
DISTRO_FEATURES_BACKFILL_CONSIDERED = \"sysvinit\"
VIRTUAL-RUNTIME_initscripts = \"\"
EOF
"

echo "Environment setup complete!"
echo "To enter the build environment, run: docker-compose exec yocto-builder bash"

Complete Build Script

#!/bin/bash
# scripts/build-images.sh

set -e

echo "Building Yocto images..."

# Build in container
docker-compose exec yocto-builder bash -c "
    cd /workspace/yocto &&
    source sources/poky/oe-init-build-env build &&
    
    # Build core system
    echo 'Building minimal egui system...'
    bitbake core-image-egui-minimal
    
    # Build update image
    echo 'Building SWUpdate package...'
    bitbake update-image
    
    # Build SDK for development
    echo 'Building extensible SDK...'
    bitbake core-image-egui-minimal -c populate_sdk_ext
    
    echo 'Build completed successfully!'
    echo 'Images available in: build/tmp/deploy/images/qemux86-64/'
"

# Test the built image
echo "Testing built image..."
./scripts/test-system.sh

echo "Build and test completed successfully!"

Update Package Creation

#!/bin/bash
# scripts/create-update.sh

set -e

VERSION=${1:-$(date +%Y%m%d-%H%M%S)}
OUTPUT_DIR="updates"

echo "Creating SWUpdate package version: $VERSION"

mkdir -p $OUTPUT_DIR

# Build update in container
docker-compose exec yocto-builder bash -c "
    cd /workspace/yocto &&
    source sources/poky/oe-init-build-env build &&
    
    # Update version in sw-description
    sed -i 's/version = \".*\"/version = \"$VERSION\"/' \
        /workspace/meta-layers/meta-custom/recipes-support/update-image/files/sw-description
    
    # Build update package
    bitbake update-image
    
    # Copy to output directory
    cp tmp/deploy/images/qemux86-64/update-image-qemux86-64.swu \
       /workspace/$OUTPUT_DIR/system-update-$VERSION.swu
"

echo "Update package created: $OUTPUT_DIR/system-update-$VERSION.swu"
echo "Deploy with: swupdate -i $OUTPUT_DIR/system-update-$VERSION.swu"

Production Deployment and Validation

This comprehensive system provides a production-ready embedded Linux platform with automatic updates, security hardening, and reliable operation. The containerized build approach ensures consistent results across development teams, while the A/B update system provides robust field update capabilities with automatic rollback protection.

Key advantages of this architecture:

  • Minimal host dependencies through complete Docker containerization
  • Fast development cycles with shared state caching and incremental builds
  • Reliable updates with atomic A/B partition switching and automatic rollback
  • Optimized performance with minimal Linux configuration and Rust applications
  • Comprehensive testing with QEMU emulation and automated validation

The system scales from single-developer projects to large embedded teams, providing a solid foundation for Rust-based embedded GUI applications with modern DevOps practices and reliable over-the-air update capabilities.