From f30d31a24fbdb9e3f85a8f923bdc536d698b95b9 Mon Sep 17 00:00:00 2001 From: geens Date: Wed, 2 Jul 2025 17:40:59 +0200 Subject: [PATCH] Improved yocto directory structure --- .gitmodules | 2 +- image/.gitignore | 2 + image/Dockerfile | 18 +- image/Implementation_plan.md | 68 -- image/conf/bblayers.conf | 13 + image/conf/local.conf | 13 + image/conf/templateconf.cfg | 1 + image/docker-compose.yml | 38 +- image/implementation_plan.md | 909 ++++++++++++++++++ .../meta-fcb-looper/conf/layer.conf | 15 + .../openssh/openssh_%.bbappend | 4 + image/run | 4 +- 12 files changed, 988 insertions(+), 99 deletions(-) create mode 100644 image/.gitignore delete mode 100644 image/Implementation_plan.md create mode 100644 image/conf/bblayers.conf create mode 100644 image/conf/local.conf create mode 100644 image/conf/templateconf.cfg create mode 100644 image/implementation_plan.md create mode 100644 image/meta-layers/meta-fcb-looper/conf/layer.conf create mode 100644 image/meta-layers/meta-fcb-looper/recipes-support/openssh/openssh_%.bbappend diff --git a/.gitmodules b/.gitmodules index 57c5575..cab7898 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "firmware/stm32f0xx-hal"] path = firmware/stm32f0xx-hal - url = https://github.com/stm32-rs/stm32f0xx-hal.git + url = https://git.nielsgeens.be/guitar/stm32f0xx-hal.git [submodule "image/yocto/poky"] path = image/yocto/poky url = https://git.yoctoproject.org/poky diff --git a/image/.gitignore b/image/.gitignore new file mode 100644 index 0000000..0b4a2f7 --- /dev/null +++ b/image/.gitignore @@ -0,0 +1,2 @@ +.claude/ +build/ \ No newline at end of file diff --git a/image/Dockerfile b/image/Dockerfile index e89920b..117f10b 100644 --- a/image/Dockerfile +++ b/image/Dockerfile @@ -42,9 +42,15 @@ ENV LANG=en_US.UTF-8 ENV LANGUAGE=en_US:en ENV LC_ALL=en_US.UTF-8 -# Create non-root user for Yocto builds (Yocto doesn't like running as root) -RUN useradd -m -s /bin/bash yocto && \ - echo "yocto ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers +# Create build user matching host UID/GID +ARG USER_ID=1000 +ARG GROUP_ID=1000 +RUN groupadd -g ${GROUP_ID} builder && \ + useradd -u ${USER_ID} -g ${GROUP_ID} -ms /bin/bash builder + +# Enable compiler cache for performance +ENV USE_CCACHE=1 +ENV CCACHE_DIR=/workspace/.ccache # Set working directory WORKDIR /workspace @@ -53,11 +59,7 @@ WORKDIR /workspace RUN chown -R yocto:yocto /workspace # Switch to yocto user -USER yocto - -# Set up git configuration (required for Yocto) -RUN git config --global user.name "Yocto Builder" && \ - git config --global user.email "yocto@builder.local" +USER builder # Default command to start interactive shell CMD ["/bin/bash"] \ No newline at end of file diff --git a/image/Implementation_plan.md b/image/Implementation_plan.md deleted file mode 100644 index 3a7afcf..0000000 --- a/image/Implementation_plan.md +++ /dev/null @@ -1,68 +0,0 @@ -# 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. - -## 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. \ No newline at end of file diff --git a/image/conf/bblayers.conf b/image/conf/bblayers.conf new file mode 100644 index 0000000..bc6bb62 --- /dev/null +++ b/image/conf/bblayers.conf @@ -0,0 +1,13 @@ +# POKY_BBLAYERS_CONF_VERSION is increased each time build/conf/bblayers.conf +# changes incompatibly +POKY_BBLAYERS_CONF_VERSION = "2" + +BBPATH = "${TOPDIR}" +BBFILES ?= "" + +BBLAYERS ?= " \ + /workspace/yocto/poky/meta \ + /workspace/yocto/poky/meta-poky \ + /workspace/yocto/poky/meta-yocto-bsp \ + /workspace/meta-layers/meta-fcb-looper \ + " diff --git a/image/conf/local.conf b/image/conf/local.conf new file mode 100644 index 0000000..ccc7e85 --- /dev/null +++ b/image/conf/local.conf @@ -0,0 +1,13 @@ +MACHINE = "genericx86-64" +DISTRO = "poky" +PREFERRED_PROVIDER_virtual/kernel = "linux-yocto" +IMAGE_INSTALL:append = " openssh" +EXTRA_IMAGE_FEATURES += "ssh-server-openssh" + +# Memory optimization settings +BB_NUMBER_THREADS = "2" +PARALLEL_MAKE = "-j 2" + +# Limit memory usage for builds +BB_SCHEDULER = "completion" +INHERIT += "rm_work" \ No newline at end of file diff --git a/image/conf/templateconf.cfg b/image/conf/templateconf.cfg new file mode 100644 index 0000000..0fcf0ff --- /dev/null +++ b/image/conf/templateconf.cfg @@ -0,0 +1 @@ +meta-poky/conf/templates/default diff --git a/image/docker-compose.yml b/image/docker-compose.yml index 4a00939..d45d5e1 100644 --- a/image/docker-compose.yml +++ b/image/docker-compose.yml @@ -1,24 +1,22 @@ services: - yocto: - build: . - container_name: fcb-looper-yocto + disk-image: + build: + context: . + dockerfile: Dockerfile + args: + USER_ID: ${USER_ID:-1000} + GROUP_ID: ${GROUP_ID:-1000} + container_name: disk-image + hostname: disk-image working_dir: /workspace - user: "${HOST_UID:-1000}:${HOST_GID:-1000}" volumes: - # Mount workspace for persistent development - - ./workspace:/workspace - # Mount Poky submodule for Yocto source - - ./yocto/poky:/yocto/poky - # Mount downloads cache to avoid re-downloading sources - - ./yocto-downloads:/workspace/downloads - # Mount sstate cache for faster builds - - ./yocto-sstate:/workspace/sstate-cache - # Mount output directory - - ./output:/workspace/output - environment: - - TERM=xterm-256color - - UID=${HOST_UID:-1000} - - GID=${HOST_GID:-1000} - stdin_open: true + - ./:/workspace tty: true - command: /bin/bash \ No newline at end of file + stdin_open: true + command: > + bash -c " + source /workspace/yocto/poky/oe-init-build-env /workspace/build && + cp /workspace/conf/* /workspace/build/conf/ && + bitbake core-image-minimal && + echo 'Build complete. Image files available in output directory.' + " \ No newline at end of file diff --git a/image/implementation_plan.md b/image/implementation_plan.md new file mode 100644 index 0000000..9450b44 --- /dev/null +++ b/image/implementation_plan.md @@ -0,0 +1,909 @@ +# 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: + +```toml +# 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 + +```bash +# 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: + +```bash +# 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 +// 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: + +```ini +# 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 + +```bash +# 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 + +```bash +# 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 + +```bash +# 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 + +```bash +# 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 + +```libconfig +# 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 + +```lua +-- 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 + +```bash +#!/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 + +```bash +#!/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 + +```bash +# 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 + +```bash +# 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 + +```bash +#!/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 + +```bash +#!/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 + +```bash +#!/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. \ No newline at end of file diff --git a/image/meta-layers/meta-fcb-looper/conf/layer.conf b/image/meta-layers/meta-fcb-looper/conf/layer.conf new file mode 100644 index 0000000..afabb1a --- /dev/null +++ b/image/meta-layers/meta-fcb-looper/conf/layer.conf @@ -0,0 +1,15 @@ +# Layer configuration for FCB1010 Looper System +BBPATH .= ":${LAYERDIR}" + +# Layer recipes +BBFILES += "${LAYERDIR}/recipes-*/*/*.bb ${LAYERDIR}/recipes-*/*/*.bbappend" + +BBFILE_COLLECTIONS += "meta-fcb-looper" +BBFILE_PATTERN_meta-fcb-looper = "^${LAYERDIR}/" +BBFILE_PRIORITY_meta-fcb-looper = "10" + +LAYERVERSION_meta-fcb-looper = "1" +LAYERSERIES_COMPAT_meta-fcb-looper = "whinlatter" + +# Dependencies +LAYERDEPENDS_meta-fcb-looper = "core" \ No newline at end of file diff --git a/image/meta-layers/meta-fcb-looper/recipes-support/openssh/openssh_%.bbappend b/image/meta-layers/meta-fcb-looper/recipes-support/openssh/openssh_%.bbappend new file mode 100644 index 0000000..3a35fbc --- /dev/null +++ b/image/meta-layers/meta-fcb-looper/recipes-support/openssh/openssh_%.bbappend @@ -0,0 +1,4 @@ +# FCB Looper SSH configuration +# Enable SSH server and add to image install + +EXTRA_IMAGE_FEATURES += "ssh-server-openssh" \ No newline at end of file diff --git a/image/run b/image/run index c16029f..8244523 100755 --- a/image/run +++ b/image/run @@ -6,8 +6,8 @@ set -e # Get current user's UID and GID -export HOST_UID=$(id -u) -export HOST_GID=$(id -g) +export USER_ID=$(id -u) +export GROUP_ID=$(id -g) # Check if service name is provided if [ $# -eq 0 ]; then